1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5 * Copyright (C) 2012-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25
26 /*
27
28 Pcbnew PLUGIN for Eagle 6.x XML *.brd and footprint format.
29
30 XML parsing and converting:
31 Getting line numbers and byte offsets from the source XML file is not
32 possible using currently available XML libraries within KiCad project:
33 wxXmlDocument and boost::property_tree.
34
35 property_tree will give line numbers but no byte offsets, and only during
36 document loading. This means that if we have a problem after the document is
37 successfully loaded, there is no way to correlate back to line number and byte
38 offset of the problem. So a different approach is taken, one which relies on the
39 XML elements themselves using an XPATH type of reporting mechanism. The path to
40 the problem is reported in the error messages. This means keeping track of that
41 path as we traverse the XML document for the sole purpose of accurate error
42 reporting.
43
44 User can load the source XML file into firefox or other xml browser and follow
45 our error message.
46
47 Load() TODO's
48
49 *) verify zone fill clearances are correct
50
51 */
52
53 #include <cerrno>
54
55 #include <wx/string.h>
56 #include <wx/xml/xml.h>
57 #include <wx/filename.h>
58 #include <wx/log.h>
59 #include <wx/wfstream.h>
60
61 #include <convert_basic_shapes_to_polygon.h>
62 #include <core/arraydim.h>
63 #include <geometry/geometry_utils.h>
64 #include <string_utils.h>
65 #include <locale_io.h>
66 #include <macros.h>
67 #include <properties.h>
68 #include <trigo.h>
69 #include <math/util.h> // for KiROUND
70 #include <progress_reporter.h>
71 #include <project.h>
72 #include <board.h>
73 #include <board_design_settings.h>
74 #include <footprint.h>
75 #include <pad.h>
76 #include <pcb_track.h>
77 #include <fp_shape.h>
78 #include <zone.h>
79 #include <pad_shapes.h>
80 #include <pcb_text.h>
81 #include <pcb_dimension.h>
82
83 #include <plugins/eagle/eagle_plugin.h>
84
85 using namespace std;
86
87
88 /// Parse an eagle distance which is either mm, or mils if there is "mil" suffix.
89 /// Return is in BIU.
parseEagle(const wxString & aDistance)90 static int parseEagle( const wxString& aDistance )
91 {
92 ECOORD::EAGLE_UNIT unit = ( aDistance.npos != aDistance.find( "mil" ) )
93 ? ECOORD::EAGLE_UNIT::EU_MIL : ECOORD::EAGLE_UNIT::EU_MM;
94
95 ECOORD coord( aDistance, unit );
96
97 return coord.ToPcbUnits();
98 }
99
100
101 // In Eagle one can specify DRC rules where min value > max value,
102 // in such case the max value has the priority
103 template<typename T>
eagleClamp(T aMin,T aValue,T aMax)104 static T eagleClamp( T aMin, T aValue, T aMax )
105 {
106 T ret = std::max( aMin, aValue );
107 return std::min( aMax, ret );
108 }
109
110
111 /// Assemble a two part key as a simple concatenation of aFirst and aSecond parts,
112 /// using a separator.
makeKey(const wxString & aFirst,const wxString & aSecond)113 static wxString makeKey( const wxString& aFirst, const wxString& aSecond )
114 {
115 wxString key = aFirst + '\x02' + aSecond;
116 return key;
117 }
118
119
120 /// interpret special characters in Eagle text and converts them to KiCAD notation
interpret_text(const wxString & aText)121 static wxString interpret_text( const wxString& aText )
122 {
123 wxString text;
124 bool sectionOpen = false;
125
126 for ( wxString::size_type i = 0; i < aText.size(); i++ )
127 {
128 // Interpret escaped characters
129 if ( aText[ i ] == '\\' )
130 {
131 if ( i + 1 != aText.size() )
132 text.Append( aText[ i + 1 ] );
133
134 i++;
135 continue;
136 }
137
138 // Escape ~ for KiCAD
139 if( aText[i] == '~' )
140 {
141 text.Append( '~' );
142 text.Append( '~' );
143 continue;
144 }
145
146 if ( aText[ i ] == '!' )
147 {
148 if ( sectionOpen )
149 {
150 text.Append( '~' );
151 sectionOpen = false;
152 continue;
153 }
154
155 static wxString escapeChars( " )]}'\"" );
156
157 if( i + 1 != aText.size() && escapeChars.Find( aText[i + 1] ) == wxNOT_FOUND )
158 {
159 sectionOpen = true;
160 text.Append( '~' );
161 }
162 else
163 {
164 text.Append( aText[ i ] );
165 }
166 continue;
167 }
168
169 if( aText[i] == ',' && sectionOpen )
170 {
171 text.Append( '~' );
172 sectionOpen = false;
173 }
174
175 text.Append( aText[ i ] );
176 }
177
178 return text;
179 }
180
181
setKeepoutSettingsToZone(ZONE * aZone,LAYER_NUM aLayer)182 static void setKeepoutSettingsToZone( ZONE* aZone, LAYER_NUM aLayer )
183 {
184 if( aLayer == EAGLE_LAYER::TRESTRICT || aLayer == EAGLE_LAYER::BRESTRICT )
185 {
186 aZone->SetIsRuleArea( true );
187 aZone->SetDoNotAllowVias( true );
188 aZone->SetDoNotAllowTracks( true );
189 aZone->SetDoNotAllowCopperPour( true );
190 aZone->SetDoNotAllowPads( true );
191 aZone->SetDoNotAllowFootprints( false );
192
193 if( aLayer == EAGLE_LAYER::TRESTRICT ) // front layer keepout
194 aZone->SetLayer( F_Cu );
195 else // bottom layer keepout
196 aZone->SetLayer( B_Cu );
197 }
198 else if( aLayer == EAGLE_LAYER::VRESTRICT )
199 {
200 aZone->SetIsRuleArea( true );
201 aZone->SetDoNotAllowVias( true );
202 aZone->SetDoNotAllowTracks( false );
203 aZone->SetDoNotAllowCopperPour( false );
204 aZone->SetDoNotAllowPads( false );
205 aZone->SetDoNotAllowFootprints( false );
206
207 aZone->SetLayerSet( LSET::AllCuMask() );
208 }
209 }
210
211
parse(wxXmlNode * aRules,std::function<void ()> aCheckpoint)212 void ERULES::parse( wxXmlNode* aRules, std::function<void()> aCheckpoint )
213 {
214 wxXmlNode* child = aRules->GetChildren();
215
216 while( child )
217 {
218 aCheckpoint();
219
220 if( child->GetName() == "param" )
221 {
222 const wxString& name = child->GetAttribute( "name" );
223 const wxString& value = child->GetAttribute( "value" );
224
225 if( name == "psElongationLong" )
226 psElongationLong = wxAtoi( value );
227 else if( name == "psElongationOffset" )
228 psElongationOffset = wxAtoi( value );
229 else if( name == "mvStopFrame" )
230 value.ToCDouble( &mvStopFrame );
231 else if( name == "mvCreamFrame" )
232 value.ToCDouble( &mvCreamFrame );
233 else if( name == "mlMinStopFrame" )
234 mlMinStopFrame = parseEagle( value );
235 else if( name == "mlMaxStopFrame" )
236 mlMaxStopFrame = parseEagle( value );
237 else if( name == "mlMinCreamFrame" )
238 mlMinCreamFrame = parseEagle( value );
239 else if( name == "mlMaxCreamFrame" )
240 mlMaxCreamFrame = parseEagle( value );
241 else if( name == "srRoundness" )
242 value.ToCDouble( &srRoundness );
243 else if( name == "srMinRoundness" )
244 srMinRoundness = parseEagle( value );
245 else if( name == "srMaxRoundness" )
246 srMaxRoundness = parseEagle( value );
247 else if( name == "psTop" )
248 psTop = wxAtoi( value );
249 else if( name == "psBottom" )
250 psBottom = wxAtoi( value );
251 else if( name == "psFirst" )
252 psFirst = wxAtoi( value );
253 else if( name == "rvPadTop" )
254 value.ToCDouble( &rvPadTop );
255 else if( name == "rlMinPadTop" )
256 rlMinPadTop = parseEagle( value );
257 else if( name == "rlMaxPadTop" )
258 rlMaxPadTop = parseEagle( value );
259 else if( name == "rvViaOuter" )
260 value.ToCDouble( &rvViaOuter );
261 else if( name == "rlMinViaOuter" )
262 rlMinViaOuter = parseEagle( value );
263 else if( name == "rlMaxViaOuter" )
264 rlMaxViaOuter = parseEagle( value );
265 else if( name == "mdWireWire" )
266 mdWireWire = parseEagle( value );
267 }
268
269 child = child->GetNext();
270 }
271 }
272
273
EAGLE_PLUGIN()274 EAGLE_PLUGIN::EAGLE_PLUGIN() :
275 m_rules( new ERULES() ),
276 m_xpath( new XPATH() ),
277 m_progressReporter( nullptr ),
278 m_doneCount( 0 ),
279 m_lastProgressCount( 0 ),
280 m_totalCount( 0 ),
281 m_mod_time( wxDateTime::Now() )
282 {
283 using namespace std::placeholders;
284
285 init( nullptr );
286 clear_cu_map();
287 RegisterLayerMappingCallback( std::bind( &EAGLE_PLUGIN::DefaultLayerMappingCallback,
288 this, _1 ) );
289 }
290
291
~EAGLE_PLUGIN()292 EAGLE_PLUGIN::~EAGLE_PLUGIN()
293 {
294 deleteTemplates();
295 delete m_rules;
296 delete m_xpath;
297 }
298
299
PluginName() const300 const wxString EAGLE_PLUGIN::PluginName() const
301 {
302 return wxT( "Eagle" );
303 }
304
305
GetFileExtension() const306 const wxString EAGLE_PLUGIN::GetFileExtension() const
307 {
308 return wxT( "brd" );
309 }
310
311
checkpoint()312 void EAGLE_PLUGIN::checkpoint()
313 {
314 const unsigned PROGRESS_DELTA = 50;
315
316 if( m_progressReporter )
317 {
318 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
319 {
320 m_progressReporter->SetCurrentProgress( ( (double) m_doneCount )
321 / std::max( 1U, m_totalCount ) );
322
323 if( !m_progressReporter->KeepRefreshing() )
324 THROW_IO_ERROR( ( "Open cancelled by user." ) );
325
326 m_lastProgressCount = m_doneCount;
327 }
328 }
329 }
330
331
kicad_fontz(const ECOORD & d,int aTextThickness) const332 wxSize inline EAGLE_PLUGIN::kicad_fontz( const ECOORD& d, int aTextThickness ) const
333 {
334 // Eagle includes stroke thickness in the text size, KiCAD does not
335 int kz = d.ToPcbUnits();
336 return wxSize( kz - aTextThickness, kz - aTextThickness );
337 }
338
339
Load(const wxString & aFileName,BOARD * aAppendToMe,const PROPERTIES * aProperties,PROJECT * aProject,PROGRESS_REPORTER * aProgressReporter)340 BOARD* EAGLE_PLUGIN::Load( const wxString& aFileName, BOARD* aAppendToMe,
341 const PROPERTIES* aProperties, PROJECT* aProject,
342 PROGRESS_REPORTER* aProgressReporter )
343 {
344 LOCALE_IO toggle; // toggles on, then off, the C locale.
345 wxXmlNode* doc;
346
347 init( aProperties );
348
349 m_board = aAppendToMe ? aAppendToMe : new BOARD();
350 m_progressReporter = aProgressReporter;
351
352 // Give the filename to the board if it's new
353 if( !aAppendToMe )
354 m_board->SetFileName( aFileName );
355
356 // delete on exception, if I own m_board, according to aAppendToMe
357 unique_ptr<BOARD> deleter( aAppendToMe ? nullptr : m_board );
358
359 try
360 {
361 if( m_progressReporter )
362 {
363 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
364
365 if( !m_progressReporter->KeepRefreshing() )
366 THROW_IO_ERROR( ( "Open cancelled by user." ) );
367 }
368
369 wxFileName fn = aFileName;
370
371 // Load the document
372 wxFFileInputStream stream( fn.GetFullPath() );
373 wxXmlDocument xmlDocument;
374
375 if( !stream.IsOk() || !xmlDocument.Load( stream ) )
376 {
377 THROW_IO_ERROR( wxString::Format( _( "Unable to read file '%s'" ),
378 fn.GetFullPath() ) );
379 }
380
381 doc = xmlDocument.GetRoot();
382
383 m_min_trace = INT_MAX;
384 m_min_hole = INT_MAX;
385 m_min_via = INT_MAX;
386 m_min_annulus = INT_MAX;
387
388 loadAllSections( doc );
389
390 BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
391
392 if( m_min_trace < designSettings.m_TrackMinWidth )
393 designSettings.m_TrackMinWidth = m_min_trace;
394
395 if( m_min_via < designSettings.m_ViasMinSize )
396 designSettings.m_ViasMinSize = m_min_via;
397
398 if( m_min_hole < designSettings.m_MinThroughDrill )
399 designSettings.m_MinThroughDrill = m_min_hole;
400
401 if( m_min_annulus < designSettings.m_ViasMinAnnularWidth )
402 designSettings.m_ViasMinAnnularWidth = m_min_annulus;
403
404 if( m_rules->mdWireWire )
405 designSettings.m_MinClearance = KiROUND( m_rules->mdWireWire );
406
407 NETCLASS defaults( "dummy" );
408
409 auto finishNetclass =
410 [&]( NETCLASSPTR netclass )
411 {
412 // If Eagle has a clearance matrix then we'll build custom rules from that.
413 // Netclasses should just be the board minimum clearance.
414 netclass->SetClearance( KiROUND( designSettings.m_MinClearance ) );
415
416 if( netclass->GetTrackWidth() == INT_MAX )
417 netclass->SetTrackWidth( defaults.GetTrackWidth() );
418
419 if( netclass->GetViaDiameter() == INT_MAX )
420 netclass->SetViaDiameter( defaults.GetViaDiameter() );
421
422 if( netclass->GetViaDrill() == INT_MAX )
423 netclass->SetViaDrill( defaults.GetViaDrill() );
424 };
425
426 finishNetclass( designSettings.GetNetClasses().GetDefault() );
427
428 for( const std::pair<const wxString, NETCLASSPTR>& entry : designSettings.GetNetClasses() )
429 finishNetclass( entry.second );
430
431 m_board->m_LegacyNetclassesLoaded = true;
432 m_board->m_LegacyDesignSettingsLoaded = true;
433
434 fn.SetExt( "kicad_dru" );
435 wxFile rulesFile( fn.GetFullPath(), wxFile::write );
436 rulesFile.Write( m_customRules );
437
438 // should be empty, else missing m_xpath->pop()
439 wxASSERT( m_xpath->Contents().size() == 0 );
440 }
441 catch( const XML_PARSER_ERROR &exc )
442 {
443 wxString errmsg = exc.what();
444
445 errmsg += "\n@ ";
446 errmsg += m_xpath->Contents();
447
448 THROW_IO_ERROR( errmsg );
449 }
450
451 // IO_ERROR exceptions are left uncaught, they pass upwards from here.
452
453 // Ensure the copper layers count is a multiple of 2
454 // Pcbnew does not like boards with odd layers count
455 // (these boards cannot exist. they actually have a even layers count)
456 int lyrcnt = m_board->GetCopperLayerCount();
457
458 if( (lyrcnt % 2) != 0 )
459 {
460 lyrcnt++;
461 m_board->SetCopperLayerCount( lyrcnt );
462 }
463
464 centerBoard();
465
466 deleter.release();
467 return m_board;
468 }
469
470
GetImportedCachedLibraryFootprints()471 std::vector<FOOTPRINT*> EAGLE_PLUGIN::GetImportedCachedLibraryFootprints()
472 {
473 std::vector<FOOTPRINT*> retval;
474
475 for( std::pair<wxString, FOOTPRINT*> fp : m_templates )
476 retval.push_back( static_cast<FOOTPRINT*>( fp.second->Clone() ) );
477
478 return retval;
479 }
480
481
init(const PROPERTIES * aProperties)482 void EAGLE_PLUGIN::init( const PROPERTIES* aProperties )
483 {
484 m_hole_count = 0;
485 m_min_trace = 0;
486 m_min_hole = 0;
487 m_min_via = 0;
488 m_min_annulus = 0;
489 m_xpath->clear();
490 m_pads_to_nets.clear();
491
492 m_board = nullptr;
493 m_props = aProperties;
494
495
496 delete m_rules;
497 m_rules = new ERULES();
498 }
499
500
clear_cu_map()501 void EAGLE_PLUGIN::clear_cu_map()
502 {
503 // All cu layers are invalid until we see them in the <layers> section while
504 // loading either a board or library. See loadLayerDefs().
505 for( unsigned i = 0; i < arrayDim(m_cu_map); ++i )
506 m_cu_map[i] = -1;
507 }
508
509
loadAllSections(wxXmlNode * aDoc)510 void EAGLE_PLUGIN::loadAllSections( wxXmlNode* aDoc )
511 {
512 wxXmlNode* drawing = MapChildren( aDoc )["drawing"];
513 NODE_MAP drawingChildren = MapChildren( drawing );
514
515 wxXmlNode* board = drawingChildren["board"];
516 NODE_MAP boardChildren = MapChildren( board );
517
518 auto count_children = [this]( wxXmlNode* aNode )
519 {
520 if( aNode )
521 {
522 wxXmlNode* child = aNode->GetChildren();
523
524 while( child )
525 {
526 m_totalCount++;
527 child = child->GetNext();
528 }
529 }
530 };
531
532 wxXmlNode* designrules = boardChildren["designrules"];
533 wxXmlNode* layers = drawingChildren["layers"];
534 wxXmlNode* plain = boardChildren["plain"];
535 wxXmlNode* classes = boardChildren["classes"];
536 wxXmlNode* signals = boardChildren["signals"];
537 wxXmlNode* libs = boardChildren["libraries"];
538 wxXmlNode* elems = boardChildren["elements"];
539
540 if( m_progressReporter )
541 {
542 m_totalCount = 0;
543 m_doneCount = 0;
544
545 count_children( designrules );
546 count_children( layers );
547 count_children( plain );
548 count_children( signals );
549 count_children( elems );
550
551 while( libs )
552 {
553 count_children( MapChildren( libs )["packages"] );
554 libs = libs->GetNext();
555 }
556
557 // Rewind
558 libs = boardChildren["libraries"];
559 }
560
561 m_xpath->push( "eagle.drawing" );
562
563 {
564 m_xpath->push( "board" );
565
566 loadDesignRules( designrules );
567
568 m_xpath->pop();
569 }
570
571 {
572 m_xpath->push( "layers" );
573
574 loadLayerDefs( layers );
575 mapEagleLayersToKicad();
576
577 m_xpath->pop();
578 }
579
580 {
581 m_xpath->push( "board" );
582
583 loadPlain( plain );
584 loadClasses( classes );
585 loadSignals( signals );
586 loadLibraries( libs );
587 loadElements( elems );
588
589 m_xpath->pop();
590 }
591
592 m_xpath->pop(); // "eagle.drawing"
593 }
594
595
loadDesignRules(wxXmlNode * aDesignRules)596 void EAGLE_PLUGIN::loadDesignRules( wxXmlNode* aDesignRules )
597 {
598 if( aDesignRules )
599 {
600 m_xpath->push( "designrules" );
601 m_rules->parse( aDesignRules, [this](){ checkpoint(); } );
602 m_xpath->pop(); // "designrules"
603 }
604 }
605
606
loadLayerDefs(wxXmlNode * aLayers)607 void EAGLE_PLUGIN::loadLayerDefs( wxXmlNode* aLayers )
608 {
609 if( !aLayers )
610 return;
611
612 ELAYERS cu; // copper layers
613
614 // Get the first layer and iterate
615 wxXmlNode* layerNode = aLayers->GetChildren();
616
617 m_eagleLayers.clear();
618 m_eagleLayersIds.clear();
619
620 while( layerNode )
621 {
622 ELAYER elayer( layerNode );
623 m_eagleLayers.insert( std::make_pair( elayer.number, elayer ) );
624 m_eagleLayersIds.insert( std::make_pair( elayer.name, elayer.number ) );
625
626 // find the subset of layers that are copper and active
627 if( elayer.number >= 1 && elayer.number <= 16 && ( !elayer.active || *elayer.active ) )
628 cu.push_back( elayer );
629
630 layerNode = layerNode->GetNext();
631 }
632
633 // establish cu layer map:
634 int ki_layer_count = 0;
635
636 for( EITER it = cu.begin(); it != cu.end(); ++it, ++ki_layer_count )
637 {
638 if( ki_layer_count == 0 )
639 {
640 m_cu_map[it->number] = F_Cu;
641 }
642 else if( ki_layer_count == int( cu.size()-1 ) )
643 {
644 m_cu_map[it->number] = B_Cu;
645 }
646 else
647 {
648 // some eagle boards do not have contiguous layer number sequences.
649 m_cu_map[it->number] = ki_layer_count;
650 }
651 }
652
653 // Set the layer names and cu count if we're loading a board.
654 if( m_board )
655 {
656 m_board->SetCopperLayerCount( cu.size() );
657
658 for( EITER it = cu.begin(); it != cu.end(); ++it )
659 {
660 PCB_LAYER_ID layer = kicad_layer( it->number );
661
662 // these function provide their own protection against non enabled layers:
663 if( layer >= 0 && layer < PCB_LAYER_ID_COUNT ) // layer should be valid
664 {
665 m_board->SetLayerName( layer, FROM_UTF8( it->name.c_str() ) );
666 m_board->SetLayerType( layer, LT_SIGNAL );
667 }
668
669 // could map the colors here
670 }
671 }
672 }
673
674
675 #define DIMENSION_PRECISION 1 // 0.001 mm
676
677
loadPlain(wxXmlNode * aGraphics)678 void EAGLE_PLUGIN::loadPlain( wxXmlNode* aGraphics )
679 {
680 if( !aGraphics )
681 return;
682
683 m_xpath->push( "plain" );
684
685 // Get the first graphic and iterate
686 wxXmlNode* gr = aGraphics->GetChildren();
687
688 // (polygon | wire | text | circle | rectangle | frame | hole)*
689 while( gr )
690 {
691 checkpoint();
692
693 wxString grName = gr->GetName();
694
695 if( grName == "wire" )
696 {
697 m_xpath->push( "wire" );
698
699 EWIRE w( gr );
700 PCB_LAYER_ID layer = kicad_layer( w.layer );
701
702 wxPoint start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
703 wxPoint end( kicad_x( w.x2 ), kicad_y( w.y2 ) );
704
705 if( layer != UNDEFINED_LAYER )
706 {
707 PCB_SHAPE* shape = new PCB_SHAPE( m_board );
708 int width = w.width.ToPcbUnits();
709
710 // KiCad cannot handle zero or negative line widths
711 if( width <= 0 )
712 width = m_board->GetDesignSettings().GetLineThickness( layer );
713
714 m_board->Add( shape, ADD_MODE::APPEND );
715
716 if( !w.curve )
717 {
718 shape->SetShape( SHAPE_T::SEGMENT );
719 shape->SetStart( start );
720 shape->SetEnd( end );
721 }
722 else
723 {
724 wxPoint center = ConvertArcCenter( start, end, *w.curve );
725
726 shape->SetShape( SHAPE_T::ARC );
727 shape->SetCenter( center );
728 shape->SetStart( start );
729 shape->SetArcAngleAndEnd( *w.curve * -10.0, true ); // KiCad rotates the other way
730 }
731
732 shape->SetLayer( layer );
733 shape->SetWidth( width );
734 }
735
736 m_xpath->pop();
737 }
738 else if( grName == "text" )
739 {
740 m_xpath->push( "text" );
741
742 ETEXT t( gr );
743 PCB_LAYER_ID layer = kicad_layer( t.layer );
744
745 if( layer != UNDEFINED_LAYER )
746 {
747 PCB_TEXT* pcbtxt = new PCB_TEXT( m_board );
748 m_board->Add( pcbtxt, ADD_MODE::APPEND );
749
750 pcbtxt->SetLayer( layer );
751 wxString kicadText = interpret_text( t.text );
752 pcbtxt->SetText( FROM_UTF8( kicadText.c_str() ) );
753 pcbtxt->SetTextPos( wxPoint( kicad_x( t.x ), kicad_y( t.y ) ) );
754
755 double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default
756 int textThickness = KiROUND( t.size.ToPcbUnits() * ratio / 100 );
757 pcbtxt->SetTextThickness( textThickness );
758 pcbtxt->SetTextSize( kicad_fontz( t.size, textThickness ) );
759
760 int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT;
761
762 if( t.rot )
763 {
764 int sign = t.rot->mirror ? -1 : 1;
765 pcbtxt->SetMirrored( t.rot->mirror );
766
767 double degrees = t.rot->degrees;
768
769 if( degrees == 90 || t.rot->spin )
770 {
771 pcbtxt->SetTextAngle( sign * t.rot->degrees * 10 );
772 }
773 else if( degrees == 180 )
774 {
775 align = -align;
776 }
777 else if( degrees == 270 )
778 {
779 pcbtxt->SetTextAngle( sign * 90 * 10 );
780 align = -align;
781 }
782 else
783 {
784 // Ok so text is not at 90,180 or 270 so do some funny stuff to get
785 // placement right.
786 if( ( degrees > 0 ) && ( degrees < 90 ) )
787 {
788 pcbtxt->SetTextAngle( sign * t.rot->degrees * 10 );
789 }
790 else if( ( degrees > 90 ) && ( degrees < 180 ) )
791 {
792 pcbtxt->SetTextAngle( sign * ( t.rot->degrees + 180 ) * 10 );
793 align = ETEXT::TOP_RIGHT;
794 }
795 else if( ( degrees > 180 ) && ( degrees < 270 ) )
796 {
797 pcbtxt->SetTextAngle( sign * ( t.rot->degrees - 180 ) * 10 );
798 align = ETEXT::TOP_RIGHT;
799 }
800 else if( ( degrees > 270 ) && ( degrees < 360 ) )
801 {
802 pcbtxt->SetTextAngle( sign * t.rot->degrees * 10 );
803 align = ETEXT::BOTTOM_LEFT;
804 }
805 }
806 }
807
808 switch( align )
809 {
810 case ETEXT::CENTER:
811 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
812 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
813 break;
814
815 case ETEXT::CENTER_LEFT:
816 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
817 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
818 break;
819
820 case ETEXT::CENTER_RIGHT:
821 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
822 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
823 break;
824
825 case ETEXT::TOP_CENTER:
826 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
827 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
828 break;
829
830 case ETEXT::TOP_LEFT:
831 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
832 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
833 break;
834
835 case ETEXT::TOP_RIGHT:
836 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
837 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
838 break;
839
840 case ETEXT::BOTTOM_CENTER:
841 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
842 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
843 break;
844
845 case ETEXT::BOTTOM_LEFT:
846 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
847 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
848 break;
849
850 case ETEXT::BOTTOM_RIGHT:
851 pcbtxt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
852 pcbtxt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
853 break;
854 }
855 }
856
857 m_xpath->pop();
858 }
859 else if( grName == "circle" )
860 {
861 m_xpath->push( "circle" );
862
863 ECIRCLE c( gr );
864
865 int width = c.width.ToPcbUnits();
866 int radius = c.radius.ToPcbUnits();
867
868 if( c.layer == EAGLE_LAYER::TRESTRICT || c.layer == EAGLE_LAYER::BRESTRICT
869 || c.layer == EAGLE_LAYER::VRESTRICT )
870 {
871 ZONE* zone = new ZONE( m_board );
872 m_board->Add( zone, ADD_MODE::APPEND );
873
874 setKeepoutSettingsToZone( zone, c.layer );
875
876 // approximate circle as polygon with a edge every 10 degree
877 wxPoint center( kicad_x( c.x ), kicad_y( c.y ) );
878 int outlineRadius = radius + ( width / 2 );
879
880 for( int angle = 0; angle < 360; angle += 10 )
881 {
882 wxPoint rotatedPoint( outlineRadius, 0 );
883 RotatePoint( &rotatedPoint, angle * 10. );
884 zone->AppendCorner( center + rotatedPoint, -1 );
885 }
886
887 if( width > 0 )
888 {
889 zone->NewHole();
890 int innerRadius = radius - ( width / 2 );
891
892 for( int angle = 0; angle < 360; angle += 10 )
893 {
894 wxPoint rotatedPoint( innerRadius, 0 );
895 RotatePoint( &rotatedPoint, angle * 10. );
896 zone->AppendCorner( center + rotatedPoint, 0 );
897 }
898 }
899
900 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
901 ZONE::GetDefaultHatchPitch(), true );
902 }
903 else
904 {
905 PCB_LAYER_ID layer = kicad_layer( c.layer );
906
907 if( layer != UNDEFINED_LAYER ) // unsupported layer
908 {
909 PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::CIRCLE );
910 m_board->Add( shape, ADD_MODE::APPEND );
911 shape->SetFilled( false );
912 shape->SetLayer( layer );
913 shape->SetStart( wxPoint( kicad_x( c.x ), kicad_y( c.y ) ) );
914 shape->SetEnd( wxPoint( kicad_x( c.x ) + radius, kicad_y( c.y ) ) );
915 shape->SetWidth( width );
916 }
917 }
918
919 m_xpath->pop();
920 }
921 else if( grName == "rectangle" )
922 {
923 // This seems to be a simplified rectangular [copper] zone, cannot find any
924 // net related info on it from the DTD.
925 m_xpath->push( "rectangle" );
926
927 ERECT r( gr );
928 PCB_LAYER_ID layer = kicad_layer( r.layer );
929
930 if( IsCopperLayer( layer ) )
931 {
932 // use a "netcode = 0" type ZONE:
933 ZONE* zone = new ZONE( m_board );
934 m_board->Add( zone, ADD_MODE::APPEND );
935
936 zone->SetLayer( layer );
937 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
938
939 ZONE_BORDER_DISPLAY_STYLE outline_hatch = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE;
940
941 const int outlineIdx = -1; // this is the id of the copper zone main outline
942 zone->AppendCorner( wxPoint( kicad_x( r.x1 ), kicad_y( r.y1 ) ), outlineIdx );
943 zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y1 ) ), outlineIdx );
944 zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y2 ) ), outlineIdx );
945 zone->AppendCorner( wxPoint( kicad_x( r.x1 ), kicad_y( r.y2 ) ), outlineIdx );
946
947 if( r.rot )
948 zone->Rotate( zone->GetPosition(), r.rot->degrees * 10 );
949
950 // this is not my fault:
951 zone->SetBorderDisplayStyle( outline_hatch, ZONE::GetDefaultHatchPitch(),
952 true );
953 }
954
955 m_xpath->pop();
956 }
957 else if( grName == "hole" )
958 {
959 m_xpath->push( "hole" );
960
961 // Fabricate a FOOTPRINT with a single PAD_ATTRIB::NPTH pad.
962 // Use m_hole_count to gen up a unique name.
963
964 FOOTPRINT* footprint = new FOOTPRINT( m_board );
965 m_board->Add( footprint, ADD_MODE::APPEND );
966 footprint->SetReference( wxString::Format( "@HOLE%d", m_hole_count++ ) );
967 footprint->Reference().SetVisible( false );
968
969 packageHole( footprint, gr, true );
970
971 m_xpath->pop();
972 }
973 else if( grName == "frame" )
974 {
975 // picture this
976 }
977 else if( grName == "polygon" )
978 {
979 m_xpath->push( "polygon" );
980 loadPolygon( gr );
981 m_xpath->pop(); // "polygon"
982 }
983 else if( grName == "dimension" )
984 {
985 EDIMENSION d( gr );
986 PCB_LAYER_ID layer = kicad_layer( d.layer );
987
988 if( layer != UNDEFINED_LAYER )
989 {
990 const BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
991 PCB_DIM_ALIGNED* dimension = new PCB_DIM_ALIGNED( m_board );
992 m_board->Add( dimension, ADD_MODE::APPEND );
993
994 if( d.dimensionType )
995 {
996 // Eagle dimension graphic arms may have different lengths, but they look
997 // incorrect in KiCad (the graphic is tilted). Make them even length in
998 // such case.
999 if( *d.dimensionType == "horizontal" )
1000 {
1001 int newY = ( d.y1.ToPcbUnits() + d.y2.ToPcbUnits() ) / 2;
1002 d.y1 = ECOORD( newY, ECOORD::EAGLE_UNIT::EU_NM );
1003 d.y2 = ECOORD( newY, ECOORD::EAGLE_UNIT::EU_NM );
1004 }
1005 else if( *d.dimensionType == "vertical" )
1006 {
1007 int newX = ( d.x1.ToPcbUnits() + d.x2.ToPcbUnits() ) / 2;
1008 d.x1 = ECOORD( newX, ECOORD::EAGLE_UNIT::EU_NM );
1009 d.x2 = ECOORD( newX, ECOORD::EAGLE_UNIT::EU_NM );
1010 }
1011 }
1012
1013 dimension->SetLayer( layer );
1014 dimension->SetPrecision( DIMENSION_PRECISION );
1015
1016 // The origin and end are assumed to always be in this order from eagle
1017 dimension->SetStart( wxPoint( kicad_x( d.x1 ), kicad_y( d.y1 ) ) );
1018 dimension->SetEnd( wxPoint( kicad_x( d.x2 ), kicad_y( d.y2 ) ) );
1019 dimension->Text().SetTextSize( designSettings.GetTextSize( layer ) );
1020 dimension->Text().SetTextThickness( designSettings.GetTextThickness( layer ) );
1021 dimension->SetLineThickness( designSettings.GetLineThickness( layer ) );
1022 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1023
1024 // check which axis the dimension runs in
1025 // because the "height" of the dimension is perpendicular to that axis
1026 // Note the check is just if two axes are close enough to each other
1027 // Eagle appears to have some rounding errors
1028 if( abs( ( d.x1 - d.x2 ).ToPcbUnits() ) < 50000 ) // 50000 nm = 0.05 mm
1029 dimension->SetHeight( kicad_x( d.x3 - d.x1 ) );
1030 else
1031 dimension->SetHeight( kicad_y( d.y3 - d.y1 ) );
1032 }
1033 }
1034
1035 // Get next graphic
1036 gr = gr->GetNext();
1037 }
1038
1039 m_xpath->pop();
1040 }
1041
1042
loadLibrary(wxXmlNode * aLib,const wxString * aLibName)1043 void EAGLE_PLUGIN::loadLibrary( wxXmlNode* aLib, const wxString* aLibName )
1044 {
1045 if( !aLib )
1046 return;
1047
1048 // library will have <xmlattr> node, skip that and get the single packages node
1049 wxXmlNode* packages = MapChildren( aLib )["packages"];
1050
1051 if( !packages )
1052 return;
1053
1054 m_xpath->push( "packages" );
1055
1056 // Create a FOOTPRINT for all the eagle packages, for use later via a copy constructor
1057 // to instantiate needed footprints in our BOARD. Save the FOOTPRINT templates in
1058 // a FOOTPRINT_MAP using a single lookup key consisting of libname+pkgname.
1059
1060 // Get the first package and iterate
1061 wxXmlNode* package = packages->GetChildren();
1062
1063 while( package )
1064 {
1065 checkpoint();
1066
1067 m_xpath->push( "package", "name" );
1068
1069 wxString pack_ref = package->GetAttribute( "name" );
1070 ReplaceIllegalFileNameChars( pack_ref, '_' );
1071
1072 m_xpath->Value( pack_ref.ToUTF8() );
1073
1074 wxString key = aLibName ? makeKey( *aLibName, pack_ref ) : pack_ref;
1075
1076 FOOTPRINT* m = makeFootprint( package, pack_ref );
1077
1078 // add the templating FOOTPRINT to the FOOTPRINT template factory "m_templates"
1079 std::pair<FOOTPRINT_MAP::iterator, bool> r = m_templates.insert( {key, m} );
1080
1081 if( !r.second /* && !( m_props && m_props->Value( "ignore_duplicates" ) ) */ )
1082 {
1083 wxString lib = aLibName ? *aLibName : m_lib_path;
1084 const wxString& pkg = pack_ref;
1085
1086 wxString emsg = wxString::Format( _( "<package> '%s' duplicated in <library> '%s'" ),
1087 pkg,
1088 lib );
1089 THROW_IO_ERROR( emsg );
1090 }
1091
1092 m_xpath->pop();
1093
1094 package = package->GetNext();
1095 }
1096
1097 m_xpath->pop(); // "packages"
1098 }
1099
1100
loadLibraries(wxXmlNode * aLibs)1101 void EAGLE_PLUGIN::loadLibraries( wxXmlNode* aLibs )
1102 {
1103 if( !aLibs )
1104 return;
1105
1106 m_xpath->push( "libraries.library", "name" );
1107
1108 // Get the first library and iterate
1109 wxXmlNode* library = aLibs->GetChildren();
1110
1111 while( library )
1112 {
1113 const wxString& lib_name = library->GetAttribute( "name" );
1114
1115 m_xpath->Value( lib_name.c_str() );
1116 loadLibrary( library, &lib_name );
1117 library = library->GetNext();
1118 }
1119
1120 m_xpath->pop();
1121 }
1122
1123
loadElements(wxXmlNode * aElements)1124 void EAGLE_PLUGIN::loadElements( wxXmlNode* aElements )
1125 {
1126 if( !aElements )
1127 return;
1128
1129 m_xpath->push( "elements.element", "name" );
1130
1131 EATTR name;
1132 EATTR value;
1133 bool refanceNamePresetInPackageLayout;
1134 bool valueNamePresetInPackageLayout;
1135
1136 // Get the first element and iterate
1137 wxXmlNode* element = aElements->GetChildren();
1138
1139 while( element )
1140 {
1141 checkpoint();
1142
1143 if( element->GetName() != "element" )
1144 {
1145 // Get next item
1146 element = element->GetNext();
1147 continue;
1148 }
1149
1150 EELEMENT e( element );
1151
1152 // use "NULL-ness" as an indication of presence of the attribute:
1153 EATTR* nameAttr = nullptr;
1154 EATTR* valueAttr = nullptr;
1155
1156 m_xpath->Value( e.name.c_str() );
1157
1158 wxString pkg_key = makeKey( e.library, e.package );
1159
1160 FOOTPRINT_MAP::const_iterator it = m_templates.find( pkg_key );
1161
1162 if( it == m_templates.end() )
1163 {
1164 wxString emsg = wxString::Format( _( "No '%s' package in library '%s'." ),
1165 FROM_UTF8( e.package.c_str() ),
1166 FROM_UTF8( e.library.c_str() ) );
1167 THROW_IO_ERROR( emsg );
1168 }
1169
1170 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( it->second->Duplicate() );
1171
1172 m_board->Add( footprint, ADD_MODE::APPEND );
1173
1174 // update the nets within the pads of the clone
1175 for( PAD* pad : footprint->Pads() )
1176 {
1177 wxString pn_key = makeKey( e.name, pad->GetNumber() );
1178
1179 NET_MAP_CITER ni = m_pads_to_nets.find( pn_key );
1180 if( ni != m_pads_to_nets.end() )
1181 {
1182 const ENET* enet = &ni->second;
1183 pad->SetNetCode( enet->netcode );
1184 }
1185 }
1186
1187 refanceNamePresetInPackageLayout = true;
1188 valueNamePresetInPackageLayout = true;
1189 footprint->SetPosition( wxPoint( kicad_x( e.x ), kicad_y( e.y ) ) );
1190
1191 // Is >NAME field set in package layout ?
1192 if( footprint->GetReference().size() == 0 )
1193 {
1194 footprint->Reference().SetVisible( false ); // No so no show
1195 refanceNamePresetInPackageLayout = false;
1196 }
1197
1198 // Is >VALUE field set in package layout
1199 if( footprint->GetValue().size() == 0 )
1200 {
1201 footprint->Value().SetVisible( false ); // No so no show
1202 valueNamePresetInPackageLayout = false;
1203 }
1204
1205 footprint->SetReference( FROM_UTF8( e.name.c_str() ) );
1206 footprint->SetValue( FROM_UTF8( e.value.c_str() ) );
1207
1208 if( !e.smashed )
1209 {
1210 // Not smashed so show NAME & VALUE
1211 if( valueNamePresetInPackageLayout )
1212 footprint->Value().SetVisible( true ); // Only if place holder in package layout
1213
1214 if( refanceNamePresetInPackageLayout )
1215 footprint->Reference().SetVisible( true ); // Only if place holder in package layout
1216 }
1217 else if( *e.smashed == true )
1218 {
1219 // Smashed so set default to no show for NAME and VALUE
1220 footprint->Value().SetVisible( false );
1221 footprint->Reference().SetVisible( false );
1222
1223 // initialize these to default values in case the <attribute> elements are not present.
1224 m_xpath->push( "attribute", "name" );
1225
1226 // VALUE and NAME can have something like our text "effects" overrides
1227 // in SWEET and new schematic. Eagle calls these XML elements "attribute".
1228 // There can be one for NAME and/or VALUE both. Features present in the
1229 // EATTR override the ones established in the package only if they are
1230 // present here (except for rot, which if not present means angle zero).
1231 // So the logic is a bit different than in packageText() and in plain text.
1232
1233 // Get the first attribute and iterate
1234 wxXmlNode* attribute = element->GetChildren();
1235
1236 while( attribute )
1237 {
1238 if( attribute->GetName() != "attribute" )
1239 {
1240 attribute = attribute->GetNext();
1241 continue;
1242 }
1243
1244 EATTR a( attribute );
1245
1246 if( a.name == "NAME" )
1247 {
1248 name = a;
1249 nameAttr = &name;
1250
1251 // do we have a display attribute ?
1252 if( a.display )
1253 {
1254 // Yes!
1255 switch( *a.display )
1256 {
1257 case EATTR::VALUE :
1258 {
1259 wxString reference = e.name;
1260
1261 // EAGLE allows references to be single digits. This breaks KiCad
1262 // netlisting, which requires parts to have non-digit + digit
1263 // annotation. If the reference begins with a number, we prepend
1264 // 'UNK' (unknown) for the symbol designator.
1265 if( reference.find_first_not_of( "0123456789" ) == wxString::npos )
1266 reference.Prepend( "UNK" );
1267
1268 nameAttr->name = reference;
1269 footprint->SetReference( reference );
1270
1271 if( refanceNamePresetInPackageLayout )
1272 footprint->Reference().SetVisible( true );
1273
1274 break;
1275 }
1276
1277 case EATTR::NAME :
1278 if( refanceNamePresetInPackageLayout )
1279 {
1280 footprint->SetReference( "NAME" );
1281 footprint->Reference().SetVisible( true );
1282 }
1283
1284 break;
1285
1286 case EATTR::BOTH :
1287 if( refanceNamePresetInPackageLayout )
1288 footprint->Reference().SetVisible( true );
1289
1290 nameAttr->name = nameAttr->name + " = " + e.name;
1291 footprint->SetReference( "NAME = " + e.name );
1292 break;
1293
1294 case EATTR::Off :
1295 footprint->Reference().SetVisible( false );
1296 break;
1297
1298 default:
1299 nameAttr->name = e.name;
1300
1301 if( refanceNamePresetInPackageLayout )
1302 footprint->Reference().SetVisible( true );
1303 }
1304 }
1305 else
1306 {
1307 // No display, so default is visible, and show value of NAME
1308 footprint->Reference().SetVisible( true );
1309 }
1310 }
1311 else if( a.name == "VALUE" )
1312 {
1313 value = a;
1314 valueAttr = &value;
1315
1316 if( a.display )
1317 {
1318 // Yes!
1319 switch( *a.display )
1320 {
1321 case EATTR::VALUE :
1322 valueAttr->value = opt_wxString( e.value );
1323 footprint->SetValue( e.value );
1324
1325 if( valueNamePresetInPackageLayout )
1326 footprint->Value().SetVisible( true );
1327
1328 break;
1329
1330 case EATTR::NAME :
1331 if( valueNamePresetInPackageLayout )
1332 footprint->Value().SetVisible( true );
1333
1334 footprint->SetValue( "VALUE" );
1335 break;
1336
1337 case EATTR::BOTH :
1338 if( valueNamePresetInPackageLayout )
1339 footprint->Value().SetVisible( true );
1340
1341 valueAttr->value = opt_wxString( "VALUE = " + e.value );
1342 footprint->SetValue( "VALUE = " + e.value );
1343 break;
1344
1345 case EATTR::Off :
1346 footprint->Value().SetVisible( false );
1347 break;
1348
1349 default:
1350 valueAttr->value = opt_wxString( e.value );
1351
1352 if( valueNamePresetInPackageLayout )
1353 footprint->Value().SetVisible( true );
1354 }
1355 }
1356 else
1357 {
1358 // No display, so default is visible, and show value of NAME
1359 footprint->Value().SetVisible( true );
1360 }
1361
1362 }
1363
1364 attribute = attribute->GetNext();
1365 }
1366
1367 m_xpath->pop(); // "attribute"
1368 }
1369
1370 orientFootprintAndText( footprint, e, nameAttr, valueAttr );
1371
1372 // Set the local coordinates for the footprint text items
1373 footprint->Reference().SetLocalCoord();
1374 footprint->Value().SetLocalCoord();
1375
1376 // Get next element
1377 element = element->GetNext();
1378 }
1379
1380 m_xpath->pop(); // "elements.element"
1381 }
1382
1383
loadPolygon(wxXmlNode * aPolyNode)1384 ZONE* EAGLE_PLUGIN::loadPolygon( wxXmlNode* aPolyNode )
1385 {
1386 EPOLYGON p( aPolyNode );
1387 PCB_LAYER_ID layer = kicad_layer( p.layer );
1388 ZONE* zone = nullptr;
1389 bool keepout = ( p.layer == EAGLE_LAYER::TRESTRICT
1390 || p.layer == EAGLE_LAYER::BRESTRICT
1391 || p.layer == EAGLE_LAYER::VRESTRICT );
1392
1393 if( layer == UNDEFINED_LAYER )
1394 {
1395 wxLogMessage( wxString::Format( _( "Ignoring a polygon since Eagle layer '%s' (%d) "
1396 "was not mapped" ),
1397 eagle_layer_name( p.layer ), p.layer ) );
1398 return nullptr;
1399 }
1400
1401 if( !IsCopperLayer( layer ) && !keepout )
1402 return nullptr;
1403
1404 // use a "netcode = 0" type ZONE:
1405 zone = new ZONE( m_board );
1406 m_board->Add( zone, ADD_MODE::APPEND );
1407
1408 if( !keepout )
1409 zone->SetLayer( layer );
1410 else
1411 setKeepoutSettingsToZone( zone, p.layer );
1412
1413 // Get the first vertex and iterate
1414 wxXmlNode* vertex = aPolyNode->GetChildren();
1415 std::vector<EVERTEX> vertices;
1416
1417 // Create a circular vector of vertices
1418 // The "curve" parameter indicates a curve from the current
1419 // to the next vertex, so we keep the first at the end as well
1420 // to allow the curve to link back
1421 while( vertex )
1422 {
1423 if( vertex->GetName() == "vertex" )
1424 vertices.emplace_back( vertex );
1425
1426 vertex = vertex->GetNext();
1427 }
1428
1429 vertices.push_back( vertices[0] );
1430
1431 SHAPE_POLY_SET polygon;
1432 polygon.NewOutline();
1433
1434 for( size_t i = 0; i < vertices.size() - 1; i++ )
1435 {
1436 EVERTEX v1 = vertices[i];
1437
1438 // Append the corner
1439 polygon.Append( kicad_x( v1.x ), kicad_y( v1.y ) );
1440
1441 if( v1.curve )
1442 {
1443 EVERTEX v2 = vertices[i + 1];
1444 wxPoint center = ConvertArcCenter( wxPoint( kicad_x( v1.x ), kicad_y( v1.y ) ),
1445 wxPoint( kicad_x( v2.x ), kicad_y( v2.y ) ),
1446 *v1.curve );
1447 double angle = DEG2RAD( *v1.curve );
1448 double end_angle = atan2( kicad_y( v2.y ) - center.y, kicad_x( v2.x ) - center.x );
1449 double radius = sqrt( pow( center.x - kicad_x( v1.x ), 2 )
1450 + pow( center.y - kicad_y( v1.y ), 2 ) );
1451
1452 int segCount = GetArcToSegmentCount( KiROUND( radius ), ARC_HIGH_DEF, *v1.curve );
1453 double delta_angle = angle / segCount;
1454
1455 for( double a = end_angle + angle; fabs( a - end_angle ) > fabs( delta_angle );
1456 a -= delta_angle )
1457 {
1458 polygon.Append( KiROUND( radius * cos( a ) ) + center.x,
1459 KiROUND( radius * sin( a ) ) + center.y );
1460 }
1461 }
1462 }
1463
1464 // Eagle traces the zone such that half of the pen width is outside the polygon.
1465 // We trace the zone such that the copper is completely inside.
1466 if( p.width.ToPcbUnits() > 0 )
1467 {
1468 polygon.Inflate( p.width.ToPcbUnits() / 2, 32, SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
1469 polygon.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1470 }
1471
1472 zone->AddPolygon( polygon.COutline( 0 ) );
1473
1474 // If the pour is a cutout it needs to be set to a keepout
1475 if( p.pour == EPOLYGON::CUTOUT )
1476 {
1477 zone->SetIsRuleArea( true );
1478 zone->SetDoNotAllowVias( false );
1479 zone->SetDoNotAllowTracks( false );
1480 zone->SetDoNotAllowPads( false );
1481 zone->SetDoNotAllowFootprints( false );
1482 zone->SetDoNotAllowCopperPour( true );
1483 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
1484 }
1485 else if( p.pour == EPOLYGON::HATCH )
1486 {
1487 int spacing = p.spacing ? p.spacing->ToPcbUnits() : 50 * IU_PER_MILS;
1488
1489 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
1490 zone->SetHatchThickness( p.width.ToPcbUnits() );
1491 zone->SetHatchGap( spacing - p.width.ToPcbUnits() );
1492 zone->SetHatchOrientation( 0 );
1493 }
1494
1495 // We divide the thickness by half because we are tracing _inside_ the zone outline
1496 // This means the radius of curvature will be twice the size for an equivalent EAGLE zone
1497 zone->SetMinThickness( std::max<int>( ZONE_THICKNESS_MIN_VALUE_MIL * IU_PER_MILS,
1498 p.width.ToPcbUnits() / 2 ) );
1499
1500 if( p.isolate )
1501 zone->SetLocalClearance( p.isolate->ToPcbUnits() );
1502 else
1503 zone->SetLocalClearance( 1 ); // @todo: set minimum clearance value based on board settings
1504
1505 // missing == yes per DTD.
1506 bool thermals = !p.thermals || *p.thermals;
1507 zone->SetPadConnection( thermals ? ZONE_CONNECTION::THERMAL : ZONE_CONNECTION::FULL );
1508
1509 if( thermals )
1510 {
1511 // FIXME: eagle calculates dimensions for thermal spokes
1512 // based on what the zone is connecting to.
1513 // (i.e. width of spoke is half of the smaller side of an smd pad)
1514 // This is a basic workaround
1515 zone->SetThermalReliefGap( p.width.ToPcbUnits() + 50000 ); // 50000nm == 0.05mm
1516 zone->SetThermalReliefSpokeWidth( p.width.ToPcbUnits() + 50000 );
1517 }
1518
1519 int rank = p.rank ? (p.max_priority - *p.rank) : p.max_priority;
1520 zone->SetPriority( rank );
1521
1522 return zone;
1523 }
1524
1525
orientFootprintAndText(FOOTPRINT * aFootprint,const EELEMENT & e,const EATTR * aNameAttr,const EATTR * aValueAttr)1526 void EAGLE_PLUGIN::orientFootprintAndText( FOOTPRINT* aFootprint, const EELEMENT& e,
1527 const EATTR* aNameAttr, const EATTR* aValueAttr )
1528 {
1529 if( e.rot )
1530 {
1531 if( e.rot->mirror )
1532 {
1533 double orientation = e.rot->degrees + 180.0;
1534 aFootprint->SetOrientation( orientation * 10 );
1535 aFootprint->Flip( aFootprint->GetPosition(), false );
1536 }
1537 else
1538 {
1539 aFootprint->SetOrientation( e.rot->degrees * 10 );
1540 }
1541 }
1542
1543 orientFPText( aFootprint, e, &aFootprint->Reference(), aNameAttr );
1544 orientFPText( aFootprint, e, &aFootprint->Value(), aValueAttr );
1545 }
1546
1547
orientFPText(FOOTPRINT * aFootprint,const EELEMENT & e,FP_TEXT * aFPText,const EATTR * aAttr)1548 void EAGLE_PLUGIN::orientFPText( FOOTPRINT* aFootprint, const EELEMENT& e, FP_TEXT* aFPText,
1549 const EATTR* aAttr )
1550 {
1551 // Smashed part ?
1552 if( aAttr )
1553 {
1554 // Yes
1555 const EATTR& a = *aAttr;
1556
1557 if( a.value )
1558 {
1559 aFPText->SetText( FROM_UTF8( a.value->c_str() ) );
1560 }
1561
1562 if( a.x && a.y ) // OPT
1563 {
1564 wxPoint pos( kicad_x( *a.x ), kicad_y( *a.y ) );
1565 aFPText->SetTextPos( pos );
1566 }
1567
1568 // Even though size and ratio are both optional, I am not seeing
1569 // a case where ratio is present but size is not.
1570 double ratio = 8;
1571 wxSize fontz = aFPText->GetTextSize();
1572 int textThickness = KiROUND( fontz.y * ratio / 100 );
1573
1574 aFPText->SetTextThickness( textThickness );
1575 if( a.size )
1576 {
1577 fontz = kicad_fontz( *a.size, textThickness );
1578 aFPText->SetTextSize( fontz );
1579
1580 if( a.ratio )
1581 ratio = *a.ratio;
1582 }
1583
1584
1585
1586 int align = ETEXT::BOTTOM_LEFT; // bottom-left is eagle default
1587
1588 if( a.align )
1589 align = a.align;
1590
1591 // The "rot" in a EATTR seems to be assumed to be zero if it is not
1592 // present, and this zero rotation becomes an override to the
1593 // package's text field. If they did not want zero, they specify
1594 // what they want explicitly.
1595 double degrees = a.rot ? a.rot->degrees : 0;
1596 double orient; // relative to parent
1597
1598 int sign = 1;
1599 bool spin = false;
1600
1601 if( a.rot )
1602 {
1603 spin = a.rot->spin;
1604 sign = a.rot->mirror ? -1 : 1;
1605 aFPText->SetMirrored( a.rot->mirror );
1606 }
1607
1608 if( degrees == 90 || degrees == 0 || spin )
1609 {
1610 orient = degrees - aFootprint->GetOrientation() / 10;
1611 aFPText->SetTextAngle( sign * orient * 10 );
1612 }
1613 else if( degrees == 180 )
1614 {
1615 orient = 0 - aFootprint->GetOrientation() / 10;
1616 aFPText->SetTextAngle( sign * orient * 10 );
1617 align = -align;
1618 }
1619 else if( degrees == 270 )
1620 {
1621 orient = 90 - aFootprint->GetOrientation() / 10;
1622 align = -align;
1623 aFPText->SetTextAngle( sign * orient * 10 );
1624 }
1625 else
1626 {
1627 orient = 90 - degrees - aFootprint->GetOrientation() / 10;
1628 aFPText->SetTextAngle( sign * orient * 10 );
1629 }
1630
1631 switch( align )
1632 {
1633 case ETEXT::TOP_RIGHT:
1634 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1635 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1636 break;
1637
1638 case ETEXT::BOTTOM_LEFT:
1639 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1640 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1641 break;
1642
1643 case ETEXT::TOP_LEFT:
1644 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1645 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1646 break;
1647
1648 case ETEXT::BOTTOM_RIGHT:
1649 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1650 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1651 break;
1652
1653 case ETEXT::TOP_CENTER:
1654 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
1655 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1656 break;
1657
1658 case ETEXT::BOTTOM_CENTER:
1659 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
1660 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1661 break;
1662
1663 case ETEXT::CENTER:
1664 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
1665 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1666 break;
1667
1668 case ETEXT::CENTER_LEFT:
1669 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1670 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1671 break;
1672
1673 case ETEXT::CENTER_RIGHT:
1674 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1675 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1676 break;
1677
1678 default:
1679 ;
1680 }
1681 }
1682 else
1683 {
1684 // Part is not smash so use Lib default for NAME/VALUE // the text is per the original
1685 // package, sans <attribute>.
1686 double degrees = ( aFPText->GetTextAngle() + aFootprint->GetOrientation() ) / 10;
1687
1688 // @todo there are a few more cases than these to contend with:
1689 if( ( !aFPText->IsMirrored() && ( abs( degrees ) == 180 || abs( degrees ) == 270 ) )
1690 || ( aFPText->IsMirrored() && ( degrees == 360 ) ) )
1691 {
1692 // ETEXT::TOP_RIGHT:
1693 aFPText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1694 aFPText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1695 }
1696 }
1697 }
1698
1699
makeFootprint(wxXmlNode * aPackage,const wxString & aPkgName)1700 FOOTPRINT* EAGLE_PLUGIN::makeFootprint( wxXmlNode* aPackage, const wxString& aPkgName )
1701 {
1702 std::unique_ptr<FOOTPRINT> m = std::make_unique<FOOTPRINT>( m_board );
1703
1704 LIB_ID fpID;
1705 fpID.Parse( aPkgName, true );
1706 m->SetFPID( fpID );
1707
1708 // Get the first package item and iterate
1709 wxXmlNode* packageItem = aPackage->GetChildren();
1710
1711 while( packageItem )
1712 {
1713 const wxString& itemName = packageItem->GetName();
1714
1715 if( itemName == "description" )
1716 m->SetDescription( FROM_UTF8( packageItem->GetNodeContent().c_str() ) );
1717 else if( itemName == "wire" )
1718 packageWire( m.get(), packageItem );
1719 else if( itemName == "pad" )
1720 packagePad( m.get(), packageItem );
1721 else if( itemName == "text" )
1722 packageText( m.get(), packageItem );
1723 else if( itemName == "rectangle" )
1724 packageRectangle( m.get(), packageItem );
1725 else if( itemName == "polygon" )
1726 packagePolygon( m.get(), packageItem );
1727 else if( itemName == "circle" )
1728 packageCircle( m.get(), packageItem );
1729 else if( itemName == "hole" )
1730 packageHole( m.get(), packageItem, false );
1731 else if( itemName == "smd" )
1732 packageSMD( m.get(), packageItem );
1733
1734 packageItem = packageItem->GetNext();
1735 }
1736
1737 return m.release();
1738 }
1739
1740
packageWire(FOOTPRINT * aFootprint,wxXmlNode * aTree) const1741 void EAGLE_PLUGIN::packageWire( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
1742 {
1743 EWIRE w( aTree );
1744 PCB_LAYER_ID layer = kicad_layer( w.layer );
1745 wxPoint start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
1746 wxPoint end( kicad_x( w.x2 ), kicad_y( w.y2 ) );
1747 int width = w.width.ToPcbUnits();
1748
1749 if( layer == UNDEFINED_LAYER )
1750 {
1751 wxLogMessage( wxString::Format( _( "Ignoring a wire since Eagle layer '%s' (%d) "
1752 "was not mapped" ),
1753 eagle_layer_name( w.layer ), w.layer ) );
1754 return;
1755 }
1756
1757 // KiCad cannot handle zero or negative line widths which apparently have meaning in Eagle.
1758 if( width <= 0 )
1759 {
1760 BOARD* board = aFootprint->GetBoard();
1761
1762 if( board )
1763 {
1764 width = board->GetDesignSettings().GetLineThickness( layer );
1765 }
1766 else
1767 {
1768 // When loading footprint libraries, there is no board so use the default KiCad
1769 // line widths.
1770 switch( layer )
1771 {
1772 case Edge_Cuts: width = Millimeter2iu( DEFAULT_EDGE_WIDTH ); break;
1773
1774 case F_SilkS:
1775 case B_SilkS: width = Millimeter2iu( DEFAULT_SILK_LINE_WIDTH ); break;
1776
1777 case F_CrtYd:
1778 case B_CrtYd: width = Millimeter2iu( DEFAULT_COURTYARD_WIDTH ); break;
1779
1780 default: width = Millimeter2iu( DEFAULT_LINE_WIDTH ); break;
1781 }
1782 }
1783 }
1784
1785 // FIXME: the cap attribute is ignored because KiCad can't create lines
1786 // with flat ends.
1787 FP_SHAPE* dwg;
1788
1789 if( !w.curve )
1790 {
1791 dwg = new FP_SHAPE( aFootprint, SHAPE_T::SEGMENT );
1792
1793 dwg->SetStart0( start );
1794 dwg->SetEnd0( end );
1795 }
1796 else
1797 {
1798 dwg = new FP_SHAPE( aFootprint, SHAPE_T::ARC );
1799 wxPoint center = ConvertArcCenter( start, end, *w.curve );
1800
1801 dwg->SetCenter0( center );
1802 dwg->SetStart0( start );
1803 dwg->SetArcAngleAndEnd0( *w.curve * -10.0, true ); // KiCad rotates the other way
1804 }
1805
1806 dwg->SetLayer( layer );
1807 dwg->SetWidth( width );
1808 dwg->SetDrawCoord();
1809
1810 aFootprint->Add( dwg );
1811 }
1812
1813
packagePad(FOOTPRINT * aFootprint,wxXmlNode * aTree)1814 void EAGLE_PLUGIN::packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree )
1815 {
1816 // this is thru hole technology here, no SMDs
1817 EPAD e( aTree );
1818 int shape = EPAD::UNDEF;
1819 int eagleDrillz = e.drill.ToPcbUnits();
1820
1821 PAD* pad = new PAD( aFootprint );
1822 aFootprint->Add( pad );
1823 transferPad( e, pad );
1824
1825 if( e.first && *e.first && m_rules->psFirst != EPAD::UNDEF )
1826 shape = m_rules->psFirst;
1827 else if( aFootprint->GetLayer() == F_Cu && m_rules->psTop != EPAD::UNDEF )
1828 shape = m_rules->psTop;
1829 else if( aFootprint->GetLayer() == B_Cu && m_rules->psBottom != EPAD::UNDEF )
1830 shape = m_rules->psBottom;
1831
1832 pad->SetDrillSize( wxSize( eagleDrillz, eagleDrillz ) );
1833 pad->SetLayerSet( LSET::AllCuMask() );
1834
1835 if( eagleDrillz < m_min_hole )
1836 m_min_hole = eagleDrillz;
1837
1838 // Solder mask
1839 if( !e.stop || *e.stop == true ) // enabled by default
1840 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ).set( F_Mask ) );
1841
1842 if( shape == EPAD::ROUND || shape == EPAD::SQUARE || shape == EPAD::OCTAGON )
1843 e.shape = shape;
1844
1845 if( e.shape )
1846 {
1847 switch( *e.shape )
1848 {
1849 case EPAD::ROUND:
1850 pad->SetShape( PAD_SHAPE::CIRCLE );
1851 break;
1852
1853 case EPAD::OCTAGON:
1854 // no KiCad octagonal pad shape, use PAD_CIRCLE for now.
1855 // pad->SetShape( PAD_OCTAGON );
1856 wxASSERT( pad->GetShape() == PAD_SHAPE::CIRCLE ); // verify set in PAD constructor
1857 pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
1858 pad->SetChamferPositions( RECT_CHAMFER_ALL );
1859 pad->SetChamferRectRatio( 0.25 );
1860 break;
1861
1862 case EPAD::LONG:
1863 pad->SetShape( PAD_SHAPE::OVAL );
1864 break;
1865
1866 case EPAD::SQUARE:
1867 pad->SetShape( PAD_SHAPE::RECT );
1868 break;
1869
1870 case EPAD::OFFSET:
1871 pad->SetShape( PAD_SHAPE::OVAL );
1872 break;
1873 }
1874 }
1875 else
1876 {
1877 // if shape is not present, our default is circle and that matches their default "round"
1878 }
1879
1880 if( e.diameter && e.diameter->value > 0 )
1881 {
1882 int diameter = e.diameter->ToPcbUnits();
1883 pad->SetSize( wxSize( diameter, diameter ) );
1884 }
1885 else
1886 {
1887 double drillz = pad->GetDrillSize().x;
1888 double annulus = drillz * m_rules->rvPadTop; // copper annulus, eagle "restring"
1889 annulus = eagleClamp( m_rules->rlMinPadTop, annulus, m_rules->rlMaxPadTop );
1890 int diameter = KiROUND( drillz + 2 * annulus );
1891 pad->SetSize( wxSize( KiROUND( diameter ), KiROUND( diameter ) ) );
1892 }
1893
1894 if( pad->GetShape() == PAD_SHAPE::OVAL )
1895 {
1896 // The Eagle "long" pad is wider than it is tall,
1897 // m_elongation is percent elongation
1898 wxSize sz = pad->GetSize();
1899 sz.x = ( sz.x * ( 100 + m_rules->psElongationLong ) ) / 100;
1900 pad->SetSize( sz );
1901
1902 if( e.shape && *e.shape == EPAD::OFFSET )
1903 {
1904 int offset = KiROUND( ( sz.x - sz.y ) / 2.0 );
1905 pad->SetOffset( wxPoint( offset, 0 ) );
1906 }
1907 }
1908
1909 if( e.rot )
1910 {
1911 pad->SetOrientation( e.rot->degrees * 10 );
1912 }
1913 }
1914
1915
packageText(FOOTPRINT * aFootprint,wxXmlNode * aTree) const1916 void EAGLE_PLUGIN::packageText( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
1917 {
1918 ETEXT t( aTree );
1919 PCB_LAYER_ID layer = kicad_layer( t.layer );
1920
1921 if( layer == UNDEFINED_LAYER )
1922 {
1923 wxLogMessage( wxString::Format( _( "Ignoring a text since Eagle layer '%s' (%d) "
1924 "was not mapped" ),
1925 eagle_layer_name( t.layer ), t.layer ) );
1926 return;
1927 }
1928
1929 FP_TEXT* txt;
1930
1931 if( t.text == ">NAME" || t.text == ">name" )
1932 txt = &aFootprint->Reference();
1933 else if( t.text == ">VALUE" || t.text == ">value" )
1934 txt = &aFootprint->Value();
1935 else
1936 {
1937 // FIXME: graphical text items are rotated for some reason.
1938 txt = new FP_TEXT( aFootprint );
1939 aFootprint->Add( txt );
1940 }
1941
1942 txt->SetText( FROM_UTF8( t.text.c_str() ) );
1943
1944 wxPoint pos( kicad_x( t.x ), kicad_y( t.y ) );
1945
1946 txt->SetTextPos( pos );
1947 txt->SetPos0( pos - aFootprint->GetPosition() );
1948
1949 txt->SetLayer( layer );
1950
1951 double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default
1952 int textThickness = KiROUND( t.size.ToPcbUnits() * ratio / 100 );
1953
1954 txt->SetTextThickness( textThickness );
1955 txt->SetTextSize( kicad_fontz( t.size, textThickness ) );
1956
1957 int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT; // bottom-left is eagle default
1958
1959 // An eagle package is never rotated, the DTD does not allow it.
1960 // angle -= aFootprint->GetOrienation();
1961
1962 if( t.rot )
1963 {
1964 int sign = t.rot->mirror ? -1 : 1;
1965 txt->SetMirrored( t.rot->mirror );
1966
1967 double degrees = t.rot->degrees;
1968
1969 if( degrees == 90 || t.rot->spin )
1970 {
1971 txt->SetTextAngle( sign * degrees * 10 );
1972 }
1973 else if( degrees == 180 )
1974 {
1975 align = ETEXT::TOP_RIGHT;
1976 }
1977 else if( degrees == 270 )
1978 {
1979 align = ETEXT::TOP_RIGHT;
1980 txt->SetTextAngle( sign * 90 * 10 );
1981 }
1982 }
1983
1984 switch( align )
1985 {
1986 case ETEXT::CENTER:
1987 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
1988 txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1989 break;
1990
1991 case ETEXT::CENTER_LEFT:
1992 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1993 txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1994 break;
1995
1996 case ETEXT::CENTER_RIGHT:
1997 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1998 txt->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
1999 break;
2000
2001 case ETEXT::TOP_CENTER:
2002 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
2003 txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
2004 break;
2005
2006 case ETEXT::TOP_LEFT:
2007 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
2008 txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
2009 break;
2010
2011 case ETEXT::TOP_RIGHT:
2012 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
2013 txt->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
2014 break;
2015
2016 case ETEXT::BOTTOM_CENTER:
2017 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
2018 txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
2019 break;
2020
2021 case ETEXT::BOTTOM_LEFT:
2022 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
2023 txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
2024 break;
2025
2026 case ETEXT::BOTTOM_RIGHT:
2027 txt->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
2028 txt->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
2029 break;
2030 }
2031 }
2032
2033
packageRectangle(FOOTPRINT * aFootprint,wxXmlNode * aTree) const2034 void EAGLE_PLUGIN::packageRectangle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2035 {
2036 ERECT r( aTree );
2037
2038 if( r.layer == EAGLE_LAYER::TRESTRICT || r.layer == EAGLE_LAYER::BRESTRICT
2039 || r.layer == EAGLE_LAYER::VRESTRICT )
2040 {
2041 FP_ZONE* zone = new FP_ZONE( aFootprint );
2042 aFootprint->Add( zone, ADD_MODE::APPEND );
2043
2044 setKeepoutSettingsToZone( zone, r.layer );
2045
2046 const int outlineIdx = -1; // this is the id of the copper zone main outline
2047 zone->AppendCorner( wxPoint( kicad_x( r.x1 ), kicad_y( r.y1 ) ), outlineIdx );
2048 zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y1 ) ), outlineIdx );
2049 zone->AppendCorner( wxPoint( kicad_x( r.x2 ), kicad_y( r.y2 ) ), outlineIdx );
2050 zone->AppendCorner( wxPoint( kicad_x( r.x1 ), kicad_y( r.y2 ) ), outlineIdx );
2051
2052 if( r.rot )
2053 {
2054 wxPoint center( ( kicad_x( r.x1 ) + kicad_x( r.x2 ) ) / 2,
2055 ( kicad_y( r.y1 ) + kicad_y( r.y2 ) ) / 2 );
2056 zone->Rotate( center, r.rot->degrees * 10 );
2057 }
2058
2059 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2060 ZONE::GetDefaultHatchPitch(), true );
2061 }
2062 else
2063 {
2064 PCB_LAYER_ID layer = kicad_layer( r.layer );
2065
2066 if( layer == UNDEFINED_LAYER )
2067 {
2068 wxLogMessage( wxString::Format( _( "Ignoring a rectangle since Eagle layer '%s' (%d) "
2069 "was not mapped" ),
2070 eagle_layer_name( r.layer ), r.layer ) );
2071 return;
2072 }
2073
2074 FP_SHAPE* dwg = new FP_SHAPE( aFootprint, SHAPE_T::POLY );
2075
2076 aFootprint->Add( dwg );
2077
2078 dwg->SetLayer( layer );
2079 dwg->SetWidth( 0 );
2080 dwg->SetFilled( true );
2081
2082 std::vector<wxPoint> pts;
2083
2084 wxPoint start( wxPoint( kicad_x( r.x1 ), kicad_y( r.y1 ) ) );
2085 wxPoint end( wxPoint( kicad_x( r.x1 ), kicad_y( r.y2 ) ) );
2086
2087 pts.push_back( start );
2088 pts.emplace_back( kicad_x( r.x2 ), kicad_y( r.y1 ) );
2089 pts.emplace_back( kicad_x( r.x2 ), kicad_y( r.y2 ) );
2090 pts.push_back( end );
2091
2092 dwg->SetPolyPoints( pts );
2093
2094 dwg->SetStart0( start );
2095 dwg->SetEnd0( end );
2096
2097 if( r.rot )
2098 dwg->Rotate( dwg->GetCenter(), r.rot->degrees * 10 );
2099 }
2100 }
2101
2102
packagePolygon(FOOTPRINT * aFootprint,wxXmlNode * aTree) const2103 void EAGLE_PLUGIN::packagePolygon( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2104 {
2105 EPOLYGON p( aTree );
2106
2107 std::vector<wxPoint> pts;
2108
2109 // Get the first vertex and iterate
2110 wxXmlNode* vertex = aTree->GetChildren();
2111 std::vector<EVERTEX> vertices;
2112
2113 // Create a circular vector of vertices
2114 // The "curve" parameter indicates a curve from the current
2115 // to the next vertex, so we keep the first at the end as well
2116 // to allow the curve to link back
2117 while( vertex )
2118 {
2119 if( vertex->GetName() == "vertex" )
2120 vertices.emplace_back( vertex );
2121
2122 vertex = vertex->GetNext();
2123 }
2124
2125 vertices.push_back( vertices[0] );
2126
2127 for( size_t i = 0; i < vertices.size() - 1; i++ )
2128 {
2129 EVERTEX v1 = vertices[i];
2130
2131 // Append the corner
2132 pts.emplace_back( kicad_x( v1.x ), kicad_y( v1.y ) );
2133
2134 if( v1.curve )
2135 {
2136 EVERTEX v2 = vertices[i + 1];
2137 wxPoint center =
2138 ConvertArcCenter( wxPoint( kicad_x( v1.x ), kicad_y( v1.y ) ),
2139 wxPoint( kicad_x( v2.x ), kicad_y( v2.y ) ), *v1.curve );
2140 double angle = DEG2RAD( *v1.curve );
2141 double end_angle = atan2( kicad_y( v2.y ) - center.y, kicad_x( v2.x ) - center.x );
2142 double radius = sqrt( pow( center.x - kicad_x( v1.x ), 2 )
2143 + pow( center.y - kicad_y( v1.y ), 2 ) );
2144
2145 // Don't allow a zero-radius curve
2146 if( KiROUND( radius ) == 0 )
2147 radius = 1.0;
2148
2149 int segCount = GetArcToSegmentCount( KiROUND( radius ), ARC_HIGH_DEF, *v1.curve );
2150 double delta = angle / segCount;
2151
2152 for( double a = end_angle + angle; fabs( a - end_angle ) > fabs( delta ); a -= delta )
2153 {
2154 pts.push_back( wxPoint( KiROUND( radius * cos( a ) ),
2155 KiROUND( radius * sin( a ) ) ) + center );
2156 }
2157 }
2158 }
2159
2160 if( p.layer == EAGLE_LAYER::TRESTRICT
2161 || p.layer == EAGLE_LAYER::BRESTRICT
2162 || p.layer == EAGLE_LAYER::VRESTRICT )
2163 {
2164 FP_ZONE* zone = new FP_ZONE( aFootprint );
2165 aFootprint->Add( zone, ADD_MODE::APPEND );
2166
2167 setKeepoutSettingsToZone( zone, p.layer );
2168
2169 SHAPE_LINE_CHAIN outline( pts );
2170 outline.SetClosed( true );
2171 zone->Outline()->AddOutline( outline );
2172
2173 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2174 ZONE::GetDefaultHatchPitch(), true );
2175 }
2176 else
2177 {
2178 PCB_LAYER_ID layer = kicad_layer( p.layer );
2179
2180 if( layer == UNDEFINED_LAYER )
2181 {
2182 wxLogMessage( wxString::Format( _( "Ignoring a polygon since Eagle layer '%s' (%d) "
2183 "was not mapped" ),
2184 eagle_layer_name( p.layer ), p.layer ) );
2185 return;
2186 }
2187
2188 FP_SHAPE* dwg = new FP_SHAPE( aFootprint, SHAPE_T::POLY );
2189
2190 aFootprint->Add( dwg );
2191
2192 dwg->SetWidth( 0 ); // it's filled, no need for boundary width
2193 dwg->SetFilled( true );
2194 dwg->SetLayer( layer );
2195
2196 dwg->SetPolyPoints( pts );
2197 dwg->SetStart0( *pts.begin() );
2198 dwg->SetEnd0( pts.back() );
2199 dwg->SetDrawCoord();
2200 dwg->GetPolyShape().Inflate( p.width.ToPcbUnits() / 2, 32,
2201 SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
2202 }
2203 }
2204
2205
packageCircle(FOOTPRINT * aFootprint,wxXmlNode * aTree) const2206 void EAGLE_PLUGIN::packageCircle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2207 {
2208 ECIRCLE e( aTree );
2209
2210 int width = e.width.ToPcbUnits();
2211 int radius = e.radius.ToPcbUnits();
2212
2213 if( e.layer == EAGLE_LAYER::TRESTRICT
2214 || e.layer == EAGLE_LAYER::BRESTRICT
2215 || e.layer == EAGLE_LAYER::VRESTRICT )
2216 {
2217 FP_ZONE* zone = new FP_ZONE( aFootprint );
2218 aFootprint->Add( zone, ADD_MODE::APPEND );
2219
2220 setKeepoutSettingsToZone( zone, e.layer );
2221
2222 // approximate circle as polygon with a edge every 10 degree
2223 wxPoint center( kicad_x( e.x ), kicad_y( e.y ) );
2224 int outlineRadius = radius + ( width / 2 );
2225
2226 for( int angle = 0; angle < 360; angle += 10 )
2227 {
2228 wxPoint rotatedPoint( outlineRadius, 0 );
2229 RotatePoint( &rotatedPoint, angle * 10. );
2230 zone->AppendCorner( center + rotatedPoint, -1 );
2231 }
2232
2233 if( width > 0 )
2234 {
2235 zone->NewHole();
2236 int innerRadius = radius - ( width / 2 );
2237
2238 for( int angle = 0; angle < 360; angle += 10 )
2239 {
2240 wxPoint rotatedPoint( innerRadius, 0 );
2241 RotatePoint( &rotatedPoint, angle * 10. );
2242 zone->AppendCorner( center + rotatedPoint, 0 );
2243 }
2244 }
2245
2246 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2247 ZONE::GetDefaultHatchPitch(), true );
2248 }
2249 else
2250 {
2251 PCB_LAYER_ID layer = kicad_layer( e.layer );
2252
2253 if( layer == UNDEFINED_LAYER )
2254 {
2255 wxLogMessage( wxString::Format( _( "Ignoring a circle since Eagle layer '%s' (%d) "
2256 "was not mapped" ),
2257 eagle_layer_name( e.layer ), e.layer ) );
2258 return;
2259 }
2260
2261 FP_SHAPE* gr = new FP_SHAPE( aFootprint, SHAPE_T::CIRCLE );
2262
2263 // width == 0 means filled circle
2264 if( width <= 0 )
2265 {
2266 width = radius;
2267 radius = radius / 2;
2268 gr->SetFilled( true );
2269 }
2270
2271 aFootprint->Add( gr );
2272 gr->SetWidth( width );
2273
2274 switch( (int) layer )
2275 {
2276 case UNDEFINED_LAYER:
2277 layer = Cmts_User;
2278 break;
2279 default:
2280 break;
2281 }
2282
2283 gr->SetLayer( layer );
2284 gr->SetStart0( wxPoint( kicad_x( e.x ), kicad_y( e.y ) ) );
2285 gr->SetEnd0( wxPoint( kicad_x( e.x ) + radius, kicad_y( e.y ) ) );
2286 gr->SetDrawCoord();
2287 }
2288 }
2289
2290
packageHole(FOOTPRINT * aFootprint,wxXmlNode * aTree,bool aCenter) const2291 void EAGLE_PLUGIN::packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aCenter ) const
2292 {
2293 EHOLE e( aTree );
2294
2295 if( e.drill.value == 0 )
2296 return;
2297
2298 // we add a PAD_ATTRIB::NPTH pad to this footprint.
2299 PAD* pad = new PAD( aFootprint );
2300 aFootprint->Add( pad );
2301
2302 pad->SetShape( PAD_SHAPE::CIRCLE );
2303 pad->SetAttribute( PAD_ATTRIB::NPTH );
2304
2305 // Mechanical purpose only:
2306 // no offset, no net name, no pad name allowed
2307 // pad->SetOffset( wxPoint( 0, 0 ) );
2308 // pad->SetNumber( wxEmptyString );
2309
2310 wxPoint padpos( kicad_x( e.x ), kicad_y( e.y ) );
2311
2312 if( aCenter )
2313 {
2314 pad->SetPos0( wxPoint( 0, 0 ) );
2315 aFootprint->SetPosition( padpos );
2316 pad->SetPosition( padpos );
2317 }
2318 else
2319 {
2320 pad->SetPos0( padpos );
2321 pad->SetPosition( padpos + aFootprint->GetPosition() );
2322 }
2323
2324 wxSize sz( e.drill.ToPcbUnits(), e.drill.ToPcbUnits() );
2325
2326 pad->SetDrillSize( sz );
2327 pad->SetSize( sz );
2328
2329 pad->SetLayerSet( LSET::AllCuMask().set( B_Mask ).set( F_Mask ) );
2330 }
2331
2332
packageSMD(FOOTPRINT * aFootprint,wxXmlNode * aTree) const2333 void EAGLE_PLUGIN::packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2334 {
2335 ESMD e( aTree );
2336 PCB_LAYER_ID layer = kicad_layer( e.layer );
2337
2338 if( !IsCopperLayer( layer ) || e.dx.value == 0 || e.dy.value == 0 )
2339 return;
2340
2341 PAD* pad = new PAD( aFootprint );
2342 aFootprint->Add( pad );
2343 transferPad( e, pad );
2344
2345 pad->SetShape( PAD_SHAPE::RECT );
2346 pad->SetAttribute( PAD_ATTRIB::SMD );
2347
2348 wxSize padSize( e.dx.ToPcbUnits(), e.dy.ToPcbUnits() );
2349 pad->SetSize( padSize );
2350 pad->SetLayer( layer );
2351
2352 const LSET front( 3, F_Cu, F_Paste, F_Mask );
2353 const LSET back( 3, B_Cu, B_Paste, B_Mask );
2354
2355 if( layer == F_Cu )
2356 pad->SetLayerSet( front );
2357 else if( layer == B_Cu )
2358 pad->SetLayerSet( back );
2359
2360 int minPadSize = std::min( padSize.x, padSize.y );
2361
2362 // Rounded rectangle pads
2363 int roundRadius =
2364 eagleClamp( m_rules->srMinRoundness * 2, (int) ( minPadSize * m_rules->srRoundness ),
2365 m_rules->srMaxRoundness * 2 );
2366
2367 if( e.roundness || roundRadius > 0 )
2368 {
2369 double roundRatio = (double) roundRadius / minPadSize / 2.0;
2370
2371 // Eagle uses a different definition of roundness, hence division by 200
2372 if( e.roundness )
2373 roundRatio = std::fmax( *e.roundness / 200.0, roundRatio );
2374
2375 pad->SetShape( PAD_SHAPE::ROUNDRECT );
2376 pad->SetRoundRectRadiusRatio( roundRatio );
2377 }
2378
2379 if( e.rot )
2380 pad->SetOrientation( e.rot->degrees * 10 );
2381
2382 pad->SetLocalSolderPasteMargin( -eagleClamp( m_rules->mlMinCreamFrame,
2383 (int) ( m_rules->mvCreamFrame * minPadSize ),
2384 m_rules->mlMaxCreamFrame ) );
2385
2386 // Solder mask
2387 if( e.stop && *e.stop == false ) // enabled by default
2388 {
2389 if( layer == F_Cu )
2390 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask, false ) );
2391 else if( layer == B_Cu )
2392 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask, false ) );
2393 }
2394
2395 // Solder paste (only for SMD pads)
2396 if( e.cream && *e.cream == false ) // enabled by default
2397 {
2398 if( layer == F_Cu )
2399 pad->SetLayerSet( pad->GetLayerSet().set( F_Paste, false ) );
2400 else if( layer == B_Cu )
2401 pad->SetLayerSet( pad->GetLayerSet().set( B_Paste, false ) );
2402 }
2403 }
2404
2405
transferPad(const EPAD_COMMON & aEaglePad,PAD * aPad) const2406 void EAGLE_PLUGIN::transferPad( const EPAD_COMMON& aEaglePad, PAD* aPad ) const
2407 {
2408 aPad->SetNumber( FROM_UTF8( aEaglePad.name.c_str() ) );
2409
2410 // pad's "Position" is not relative to the footprint's,
2411 // whereas Pos0 is relative to the footprint's but is the unrotated coordinate.
2412 wxPoint padPos( kicad_x( aEaglePad.x ), kicad_y( aEaglePad.y ) );
2413 aPad->SetPos0( padPos );
2414
2415 // Solder mask
2416 const wxSize& padSize( aPad->GetSize() );
2417
2418 aPad->SetLocalSolderMaskMargin(
2419 eagleClamp( m_rules->mlMinStopFrame,
2420 (int) ( m_rules->mvStopFrame * std::min( padSize.x, padSize.y ) ),
2421 m_rules->mlMaxStopFrame ) );
2422
2423 // Solid connection to copper zones
2424 if( aEaglePad.thermals && !*aEaglePad.thermals )
2425 aPad->SetZoneConnection( ZONE_CONNECTION::FULL );
2426
2427 FOOTPRINT* footprint = aPad->GetParent();
2428 wxCHECK( footprint, /* void */ );
2429 RotatePoint( &padPos, footprint->GetOrientation() );
2430 aPad->SetPosition( padPos + footprint->GetPosition() );
2431 }
2432
2433
deleteTemplates()2434 void EAGLE_PLUGIN::deleteTemplates()
2435 {
2436 for( auto& t : m_templates )
2437 delete t.second;
2438
2439 m_templates.clear();
2440 }
2441
2442
loadClasses(wxXmlNode * aClasses)2443 void EAGLE_PLUGIN::loadClasses( wxXmlNode* aClasses )
2444 {
2445 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2446
2447 m_xpath->push( "classes.class", "number" );
2448
2449 std::vector<ECLASS> eClasses;
2450 wxXmlNode* classNode = aClasses->GetChildren();
2451
2452 while( classNode )
2453 {
2454 checkpoint();
2455
2456 ECLASS eClass( classNode );
2457 NETCLASSPTR netclass;
2458
2459 if( eClass.name.CmpNoCase( "default" ) == 0 )
2460 {
2461 netclass = bds.GetNetClasses().GetDefault();
2462 }
2463 else
2464 {
2465 netclass.reset( new NETCLASS( eClass.name ) );
2466 m_board->GetDesignSettings().GetNetClasses().Add( netclass );
2467 }
2468
2469 netclass->SetTrackWidth( INT_MAX );
2470 netclass->SetViaDiameter( INT_MAX );
2471 netclass->SetViaDrill( INT_MAX );
2472
2473 eClasses.emplace_back( eClass );
2474 m_classMap[ eClass.number ] = netclass;
2475
2476 // Get next class
2477 classNode = classNode->GetNext();
2478 }
2479
2480 m_customRules = "(version 1)";
2481
2482 for( ECLASS& eClass : eClasses )
2483 {
2484 for( std::pair<const wxString&, ECOORD> entry : eClass.clearanceMap )
2485 {
2486 wxString rule;
2487 rule.Printf( "(rule \"class %s:%s\"\n"
2488 " (condition \"A.NetClass == '%s' && B.NetClass == '%s'\")\n"
2489 " (constraint clearance (min %smm)))\n",
2490 eClass.number,
2491 entry.first,
2492 eClass.name,
2493 m_classMap[ entry.first ]->GetName(),
2494 StringFromValue( EDA_UNITS::MILLIMETRES, entry.second.ToPcbUnits() ) );
2495
2496 m_customRules += "\n" + rule;
2497 }
2498 }
2499
2500 m_xpath->pop(); // "classes.class"
2501 }
2502
2503
loadSignals(wxXmlNode * aSignals)2504 void EAGLE_PLUGIN::loadSignals( wxXmlNode* aSignals )
2505 {
2506 ZONES zones; // per net
2507 int netCode = 1;
2508
2509 m_xpath->push( "signals.signal", "name" );
2510
2511 // Get the first signal and iterate
2512 wxXmlNode* net = aSignals->GetChildren();
2513
2514 while( net )
2515 {
2516 checkpoint();
2517
2518 bool sawPad = false;
2519
2520 zones.clear();
2521
2522 const wxString& netName = escapeName( net->GetAttribute( "name" ) );
2523 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, netName, netCode );
2524 NETCLASSPTR netclass;
2525
2526 if( net->HasAttribute( "class" ) )
2527 {
2528 netclass = m_classMap[ net->GetAttribute( "class" ) ];
2529
2530 netclass->Add( netName );
2531 netInfo->SetNetClass( netclass );
2532 }
2533
2534 m_board->Add( netInfo );
2535
2536 m_xpath->Value( netName.c_str() );
2537
2538 // Get the first net item and iterate
2539 wxXmlNode* netItem = net->GetChildren();
2540
2541 // (contactref | polygon | wire | via)*
2542 while( netItem )
2543 {
2544 const wxString& itemName = netItem->GetName();
2545
2546 if( itemName == "wire" )
2547 {
2548 m_xpath->push( "wire" );
2549
2550 EWIRE w( netItem );
2551 PCB_LAYER_ID layer = kicad_layer( w.layer );
2552
2553 if( IsCopperLayer( layer ) )
2554 {
2555 wxPoint start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
2556 double angle = 0.0;
2557 double end_angle = 0.0;
2558 double radius = 0.0;
2559 double delta_angle = 0.0;
2560 wxPoint center;
2561
2562 int width = w.width.ToPcbUnits();
2563
2564 if( width < m_min_trace )
2565 m_min_trace = width;
2566
2567 if( netclass && width < netclass->GetTrackWidth() )
2568 netclass->SetTrackWidth( width );
2569
2570 if( w.curve )
2571 {
2572 center = ConvertArcCenter( wxPoint( kicad_x( w.x1 ), kicad_y( w.y1 ) ),
2573 wxPoint( kicad_x( w.x2 ), kicad_y( w.y2 ) ),
2574 *w.curve );
2575
2576 angle = DEG2RAD( *w.curve );
2577
2578 end_angle = atan2( kicad_y( w.y2 ) - center.y,
2579 kicad_x( w.x2 ) - center.x );
2580
2581 radius = sqrt( pow( center.x - kicad_x( w.x1 ), 2 ) +
2582 pow( center.y - kicad_y( w.y1 ), 2 ) );
2583
2584 int segs = GetArcToSegmentCount( KiROUND( radius ), ARC_HIGH_DEF,
2585 *w.curve );
2586 delta_angle = angle / segs;
2587 }
2588
2589 while( fabs( angle ) > fabs( delta_angle ) )
2590 {
2591 wxASSERT( radius > 0.0 );
2592 wxPoint end( KiROUND( radius * cos( end_angle + angle ) + center.x ),
2593 KiROUND( radius * sin( end_angle + angle ) + center.y ) );
2594
2595 PCB_TRACK* t = new PCB_TRACK( m_board );
2596
2597 t->SetPosition( start );
2598 t->SetEnd( end );
2599 t->SetWidth( width );
2600 t->SetLayer( layer );
2601 t->SetNetCode( netCode );
2602
2603 m_board->Add( t );
2604
2605 start = end;
2606 angle -= delta_angle;
2607 }
2608
2609 PCB_TRACK* t = new PCB_TRACK( m_board );
2610
2611 t->SetPosition( start );
2612 t->SetEnd( wxPoint( kicad_x( w.x2 ), kicad_y( w.y2 ) ) );
2613 t->SetWidth( width );
2614 t->SetLayer( layer );
2615 t->SetNetCode( netCode );
2616
2617 m_board->Add( t );
2618 }
2619 else
2620 {
2621 // put non copper wires where the sun don't shine.
2622 }
2623
2624 m_xpath->pop();
2625 }
2626 else if( itemName == "via" )
2627 {
2628 m_xpath->push( "via" );
2629 EVIA v( netItem );
2630
2631 if( v.layer_front_most > v.layer_back_most )
2632 std::swap( v.layer_front_most, v.layer_back_most );
2633
2634 PCB_LAYER_ID layer_front_most = kicad_layer( v.layer_front_most );
2635 PCB_LAYER_ID layer_back_most = kicad_layer( v.layer_back_most );
2636
2637 if( IsCopperLayer( layer_front_most ) && IsCopperLayer( layer_back_most )
2638 && layer_front_most != layer_back_most )
2639 {
2640 int kidiam;
2641 int drillz = v.drill.ToPcbUnits();
2642 PCB_VIA* via = new PCB_VIA( m_board );
2643 m_board->Add( via );
2644
2645 if( v.diam )
2646 {
2647 kidiam = v.diam->ToPcbUnits();
2648 via->SetWidth( kidiam );
2649 }
2650 else
2651 {
2652 double annulus = drillz * m_rules->rvViaOuter; // eagle "restring"
2653 annulus = eagleClamp( m_rules->rlMinViaOuter, annulus,
2654 m_rules->rlMaxViaOuter );
2655 kidiam = KiROUND( drillz + 2 * annulus );
2656 via->SetWidth( kidiam );
2657 }
2658
2659 via->SetDrill( drillz );
2660
2661 // make sure the via diameter respects the restring rules
2662
2663 if( !v.diam || via->GetWidth() <= via->GetDrill() )
2664 {
2665 double annulus =
2666 eagleClamp( m_rules->rlMinViaOuter,
2667 (double) ( via->GetWidth() / 2 - via->GetDrill() ),
2668 m_rules->rlMaxViaOuter );
2669 via->SetWidth( drillz + 2 * annulus );
2670 }
2671
2672 if( kidiam < m_min_via )
2673 m_min_via = kidiam;
2674
2675 if( netclass && kidiam < netclass->GetViaDiameter() )
2676 netclass->SetViaDiameter( kidiam );
2677
2678 if( drillz < m_min_hole )
2679 m_min_hole = drillz;
2680
2681 if( netclass && drillz < netclass->GetViaDrill() )
2682 netclass->SetViaDrill( drillz );
2683
2684 if( ( kidiam - drillz ) / 2 < m_min_annulus )
2685 m_min_annulus = ( kidiam - drillz ) / 2;
2686
2687 if( layer_front_most == F_Cu && layer_back_most == B_Cu )
2688 {
2689 via->SetViaType( VIATYPE::THROUGH );
2690 }
2691 /// This is, at best, a guess. Eagle doesn't seem to differentiate
2692 /// between blind/buried vias that only go one layer and micro vias
2693 /// so the user will need to clean up a bit
2694 else if( v.layer_back_most - v.layer_front_most == 1 )
2695 {
2696 via->SetViaType( VIATYPE::MICROVIA );
2697 }
2698 else
2699 {
2700 via->SetViaType( VIATYPE::BLIND_BURIED );
2701 }
2702
2703 wxPoint pos( kicad_x( v.x ), kicad_y( v.y ) );
2704
2705 via->SetLayerPair( layer_front_most, layer_back_most );
2706 via->SetPosition( pos );
2707 via->SetEnd( pos );
2708
2709 via->SetNetCode( netCode );
2710 }
2711
2712 m_xpath->pop();
2713 }
2714
2715 else if( itemName == "contactref" )
2716 {
2717 m_xpath->push( "contactref" );
2718 // <contactref element="RN1" pad="7"/>
2719
2720 const wxString& reference = netItem->GetAttribute( "element" );
2721 const wxString& pad = netItem->GetAttribute( "pad" );
2722 wxString key = makeKey( reference, pad ) ;
2723
2724 m_pads_to_nets[ key ] = ENET( netCode, netName );
2725
2726 m_xpath->pop();
2727
2728 sawPad = true;
2729 }
2730
2731 else if( itemName == "polygon" )
2732 {
2733 m_xpath->push( "polygon" );
2734 auto* zone = loadPolygon( netItem );
2735
2736 if( zone )
2737 {
2738 zones.push_back( zone );
2739
2740 if( !zone->GetIsRuleArea() )
2741 zone->SetNetCode( netCode );
2742 }
2743
2744 m_xpath->pop(); // "polygon"
2745 }
2746
2747 netItem = netItem->GetNext();
2748 }
2749
2750 if( zones.size() && !sawPad )
2751 {
2752 // KiCad does not support an unconnected zone with its own non-zero netcode,
2753 // but only when assigned netcode = 0 w/o a name...
2754 for( ZONE* zone : zones )
2755 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
2756
2757 // therefore omit this signal/net.
2758 }
2759 else
2760 {
2761 netCode++;
2762 }
2763
2764 // Get next signal
2765 net = net->GetNext();
2766 }
2767
2768 m_xpath->pop(); // "signals.signal"
2769 }
2770
2771
DefaultLayerMappingCallback(const std::vector<INPUT_LAYER_DESC> & aInputLayerDescriptionVector)2772 std::map<wxString, PCB_LAYER_ID> EAGLE_PLUGIN::DefaultLayerMappingCallback(
2773 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
2774 {
2775 std::map<wxString, PCB_LAYER_ID> layer_map;
2776
2777 for ( const INPUT_LAYER_DESC& layer : aInputLayerDescriptionVector )
2778 {
2779 PCB_LAYER_ID layerId = std::get<0>( defaultKicadLayer( eagle_layer_id( layer.Name ) ) );
2780 layer_map.emplace( layer.Name, layerId );
2781 }
2782
2783 return layer_map;
2784 }
2785
2786
mapEagleLayersToKicad()2787 void EAGLE_PLUGIN::mapEagleLayersToKicad()
2788 {
2789 std::vector<INPUT_LAYER_DESC> inputDescs;
2790
2791 for ( const std::pair<const int, ELAYER>& layerPair : m_eagleLayers )
2792 {
2793 const ELAYER& eLayer = layerPair.second;
2794
2795 INPUT_LAYER_DESC layerDesc;
2796 std::tie( layerDesc.AutoMapLayer, layerDesc.PermittedLayers, layerDesc.Required ) =
2797 defaultKicadLayer( eLayer.number );
2798
2799 if( layerDesc.AutoMapLayer == UNDEFINED_LAYER )
2800 continue; // Ignore unused copper layers
2801
2802 layerDesc.Name = eLayer.name;
2803
2804 inputDescs.push_back( layerDesc );
2805 }
2806
2807 if( m_progressReporter && dynamic_cast<wxWindow*>( m_progressReporter ) )
2808 dynamic_cast<wxWindow*>( m_progressReporter )->Hide();
2809
2810 m_layer_map = m_layer_mapping_handler( inputDescs );
2811
2812 if( m_progressReporter && dynamic_cast<wxWindow*>( m_progressReporter ))
2813 dynamic_cast<wxWindow*>( m_progressReporter )->Show();
2814 }
2815
2816
kicad_layer(int aEagleLayer) const2817 PCB_LAYER_ID EAGLE_PLUGIN::kicad_layer( int aEagleLayer ) const
2818 {
2819 auto result = m_layer_map.find( eagle_layer_name( aEagleLayer ) );
2820 return result == m_layer_map.end() ? UNDEFINED_LAYER : result->second;
2821 }
2822
2823
defaultKicadLayer(int aEagleLayer) const2824 std::tuple<PCB_LAYER_ID, LSET, bool> EAGLE_PLUGIN::defaultKicadLayer( int aEagleLayer ) const
2825 {
2826 // eagle copper layer:
2827 if( aEagleLayer >= 1 && aEagleLayer < int( arrayDim( m_cu_map ) ) )
2828 {
2829 LSET copperLayers;
2830
2831 for( int copperLayer : m_cu_map )
2832 {
2833 if( copperLayer >= 0 )
2834 copperLayers[copperLayer] = true;
2835 }
2836
2837 return { PCB_LAYER_ID( m_cu_map[aEagleLayer] ), copperLayers, true };
2838 }
2839
2840 int kiLayer = UNSELECTED_LAYER;
2841 bool required = false;
2842 LSET permittedLayers;
2843
2844 permittedLayers.set();
2845
2846 // translate non-copper eagle layer to pcbnew layer
2847 switch( aEagleLayer )
2848 {
2849 // Eagle says "Dimension" layer, but it's for board perimeter
2850 case EAGLE_LAYER::DIMENSION:
2851 kiLayer = Edge_Cuts;
2852 required = true;
2853 permittedLayers = LSET( 1, Edge_Cuts );
2854 break;
2855
2856 case EAGLE_LAYER::TPLACE:
2857 kiLayer = F_SilkS;
2858 break;
2859 case EAGLE_LAYER::BPLACE:
2860 kiLayer = B_SilkS;
2861 break;
2862 case EAGLE_LAYER::TNAMES:
2863 kiLayer = F_SilkS;
2864 break;
2865 case EAGLE_LAYER::BNAMES:
2866 kiLayer = B_SilkS;
2867 break;
2868 case EAGLE_LAYER::TVALUES:
2869 kiLayer = F_Fab;
2870 break;
2871 case EAGLE_LAYER::BVALUES:
2872 kiLayer = B_Fab;
2873 break;
2874 case EAGLE_LAYER::TSTOP:
2875 kiLayer = F_Mask;
2876 break;
2877 case EAGLE_LAYER::BSTOP:
2878 kiLayer = B_Mask;
2879 break;
2880 case EAGLE_LAYER::TCREAM:
2881 kiLayer = F_Paste;
2882 break;
2883 case EAGLE_LAYER::BCREAM:
2884 kiLayer = B_Paste;
2885 break;
2886 case EAGLE_LAYER::TFINISH:
2887 kiLayer = F_Mask;
2888 break;
2889 case EAGLE_LAYER::BFINISH:
2890 kiLayer = B_Mask;
2891 break;
2892 case EAGLE_LAYER::TGLUE:
2893 kiLayer = F_Adhes;
2894 break;
2895 case EAGLE_LAYER::BGLUE:
2896 kiLayer = B_Adhes;
2897 break;
2898 case EAGLE_LAYER::DOCUMENT:
2899 kiLayer = Cmts_User;
2900 break;
2901 case EAGLE_LAYER::REFERENCELC:
2902 kiLayer = Cmts_User;
2903 break;
2904 case EAGLE_LAYER::REFERENCELS:
2905 kiLayer = Cmts_User;
2906 break;
2907
2908 // Packages show the future chip pins on SMD parts using layer 51.
2909 // This is an area slightly smaller than the PAD/SMD copper area.
2910 // Carry those visual aids into the FOOTPRINT on the fabrication layer,
2911 // not silkscreen. This is perhaps not perfect, but there is not a lot
2912 // of other suitable paired layers
2913 case EAGLE_LAYER::TDOCU:
2914 kiLayer = F_Fab;
2915 break;
2916 case EAGLE_LAYER::BDOCU:
2917 kiLayer = B_Fab;
2918 break;
2919
2920 // these layers are defined as user layers. put them on ECO layers
2921 case EAGLE_LAYER::USERLAYER1:
2922 kiLayer = Eco1_User;
2923 break;
2924 case EAGLE_LAYER::USERLAYER2:
2925 kiLayer = Eco2_User;
2926 break;
2927
2928 // these will also appear in the ratsnest, so there's no need for a warning
2929 case EAGLE_LAYER::UNROUTED:
2930 kiLayer = Dwgs_User;
2931 break;
2932
2933 case EAGLE_LAYER::TKEEPOUT:
2934 kiLayer = F_CrtYd;
2935 break;
2936 case EAGLE_LAYER::BKEEPOUT:
2937 kiLayer = B_CrtYd;
2938 break;
2939
2940 case EAGLE_LAYER::MILLING:
2941 case EAGLE_LAYER::TTEST:
2942 case EAGLE_LAYER::BTEST:
2943 case EAGLE_LAYER::HOLES:
2944 default:
2945 kiLayer = UNSELECTED_LAYER;
2946 break;
2947 }
2948
2949 return { PCB_LAYER_ID( kiLayer ), permittedLayers, required };
2950 }
2951
2952
eagle_layer_name(int aLayer) const2953 const wxString& EAGLE_PLUGIN::eagle_layer_name( int aLayer ) const
2954 {
2955 static const wxString unknown( "unknown" );
2956 auto it = m_eagleLayers.find( aLayer );
2957 return it == m_eagleLayers.end() ? unknown : it->second.name;
2958 }
2959
2960
eagle_layer_id(const wxString & aLayerName) const2961 int EAGLE_PLUGIN::eagle_layer_id( const wxString& aLayerName ) const
2962 {
2963 static const int unknown = -1;
2964 auto it = m_eagleLayersIds.find( aLayerName );
2965 return it == m_eagleLayersIds.end() ? unknown : it->second;
2966 }
2967
2968
centerBoard()2969 void EAGLE_PLUGIN::centerBoard()
2970 {
2971 if( m_props )
2972 {
2973 UTF8 page_width;
2974 UTF8 page_height;
2975
2976 if( m_props->Value( "page_width", &page_width ) &&
2977 m_props->Value( "page_height", &page_height ) )
2978 {
2979 EDA_RECT bbbox = m_board->GetBoardEdgesBoundingBox();
2980
2981 int w = atoi( page_width.c_str() );
2982 int h = atoi( page_height.c_str() );
2983
2984 int desired_x = ( w - bbbox.GetWidth() ) / 2;
2985 int desired_y = ( h - bbbox.GetHeight() ) / 2;
2986
2987 m_board->Move( wxPoint( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() ) );
2988 }
2989 }
2990 }
2991
2992
getModificationTime(const wxString & aPath)2993 wxDateTime EAGLE_PLUGIN::getModificationTime( const wxString& aPath )
2994 {
2995 // File hasn't been loaded yet.
2996 if( aPath.IsEmpty() )
2997 return wxDateTime::Now();
2998
2999 wxFileName fn( aPath );
3000
3001 if( fn.IsFileReadable() )
3002 return fn.GetModificationTime();
3003 else
3004 return wxDateTime( 0.0 );
3005 }
3006
3007
cacheLib(const wxString & aLibPath)3008 void EAGLE_PLUGIN::cacheLib( const wxString& aLibPath )
3009 {
3010 try
3011 {
3012 wxDateTime modtime = getModificationTime( aLibPath );
3013
3014 // Fixes assertions in wxWidgets debug builds for the wxDateTime object. Refresh the
3015 // cache if either of the wxDateTime objects are invalid or the last file modification
3016 // time differs from the current file modification time.
3017 bool load = !m_mod_time.IsValid() || !modtime.IsValid() || m_mod_time != modtime;
3018
3019 if( aLibPath != m_lib_path || load )
3020 {
3021 wxXmlNode* doc;
3022 LOCALE_IO toggle; // toggles on, then off, the C locale.
3023
3024 deleteTemplates();
3025
3026 // Set this before completion of loading, since we rely on it for
3027 // text of an exception. Delay setting m_mod_time until after successful load
3028 // however.
3029 m_lib_path = aLibPath;
3030
3031 // 8 bit "filename" should be encoded according to disk filename encoding,
3032 // (maybe this is current locale, maybe not, its a filesystem issue),
3033 // and is not necessarily utf8.
3034 string filename = (const char*) aLibPath.char_str( wxConvFile );
3035
3036 // Load the document
3037 wxFileName fn( filename );
3038 wxFFileInputStream stream( fn.GetFullPath() );
3039 wxXmlDocument xmlDocument;
3040
3041 if( !stream.IsOk() || !xmlDocument.Load( stream ) )
3042 {
3043 THROW_IO_ERROR( wxString::Format( _( "Unable to read file '%s'." ),
3044 fn.GetFullPath() ) );
3045 }
3046
3047 doc = xmlDocument.GetRoot();
3048
3049 wxXmlNode* drawing = MapChildren( doc )["drawing"];
3050 NODE_MAP drawingChildren = MapChildren( drawing );
3051
3052 // clear the cu map and then rebuild it.
3053 clear_cu_map();
3054
3055 m_xpath->push( "eagle.drawing.layers" );
3056 wxXmlNode* layers = drawingChildren["layers"];
3057 loadLayerDefs( layers );
3058 mapEagleLayersToKicad();
3059 m_xpath->pop();
3060
3061 m_xpath->push( "eagle.drawing.library" );
3062 wxXmlNode* library = drawingChildren["library"];
3063 loadLibrary( library, nullptr );
3064 m_xpath->pop();
3065
3066 m_mod_time = modtime;
3067 }
3068 }
3069 catch(...){}
3070 // TODO: Handle exceptions
3071 // catch( file_parser_error fpe )
3072 // {
3073 // // for xml_parser_error, what() has the line number in it,
3074 // // but no byte offset. That should be an adequate error message.
3075 // THROW_IO_ERROR( fpe.what() );
3076 // }
3077 //
3078 // // Class ptree_error is a base class for xml_parser_error & file_parser_error,
3079 // // so one catch should be OK for all errors.
3080 // catch( ptree_error pte )
3081 // {
3082 // string errmsg = pte.what();
3083 //
3084 // errmsg += " @\n";
3085 // errmsg += m_xpath->Contents();
3086 //
3087 // THROW_IO_ERROR( errmsg );
3088 // }
3089 }
3090
3091
FootprintEnumerate(wxArrayString & aFootprintNames,const wxString & aLibraryPath,bool aBestEfforts,const PROPERTIES * aProperties)3092 void EAGLE_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath,
3093 bool aBestEfforts, const PROPERTIES* aProperties )
3094 {
3095 wxString errorMsg;
3096
3097 init( aProperties );
3098
3099 try
3100 {
3101 cacheLib( aLibraryPath );
3102 }
3103 catch( const IO_ERROR& ioe )
3104 {
3105 errorMsg = ioe.What();
3106 }
3107
3108 // Some of the files may have been parsed correctly so we want to add the valid files to
3109 // the library.
3110
3111 for( FOOTPRINT_MAP::const_iterator it = m_templates.begin(); it != m_templates.end(); ++it )
3112 aFootprintNames.Add( FROM_UTF8( it->first.c_str() ) );
3113
3114 if( !errorMsg.IsEmpty() && !aBestEfforts )
3115 THROW_IO_ERROR( errorMsg );
3116 }
3117
3118
FootprintLoad(const wxString & aLibraryPath,const wxString & aFootprintName,bool aKeepUUID,const PROPERTIES * aProperties)3119 FOOTPRINT* EAGLE_PLUGIN::FootprintLoad( const wxString& aLibraryPath,
3120 const wxString& aFootprintName, bool aKeepUUID,
3121 const PROPERTIES* aProperties )
3122 {
3123 init( aProperties );
3124 cacheLib( aLibraryPath );
3125 FOOTPRINT_MAP::const_iterator it = m_templates.find( aFootprintName );
3126
3127 if( it == m_templates.end() )
3128 return nullptr;
3129
3130 // Return a copy of the template
3131 FOOTPRINT* copy = (FOOTPRINT*) it->second->Duplicate();
3132 copy->SetParent( nullptr );
3133 return copy;
3134 }
3135
3136
FootprintLibOptions(PROPERTIES * aListToAppendTo) const3137 void EAGLE_PLUGIN::FootprintLibOptions( PROPERTIES* aListToAppendTo ) const
3138 {
3139 PLUGIN::FootprintLibOptions( aListToAppendTo );
3140 }
3141