KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_eagle.cpp
Go to the documentation of this file.
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 <[email protected]>
5 * Copyright (C) 2012-2024 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
28Pcbnew PLUGIN for Eagle 6.x XML *.brd and footprint format.
29
30XML parsing and converting:
31Getting line numbers and byte offsets from the source XML file is not
32possible using currently available XML libraries within KiCad project:
33wxXmlDocument and boost::property_tree.
34
35property_tree will give line numbers but no byte offsets, and only during
36document loading. This means that if we have a problem after the document is
37successfully loaded, there is no way to correlate back to line number and byte
38offset of the problem. So a different approach is taken, one which relies on the
39XML elements themselves using an XPATH type of reporting mechanism. The path to
40the problem is reported in the error messages. This means keeping track of that
41path as we traverse the XML document for the sole purpose of accurate error
42reporting.
43
44User can load the source XML file into firefox or other xml browser and follow
45our error message.
46
47Load() 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#include <wx/txtstrm.h>
61#include <wx/window.h>
62
64#include <font/fontconfig.h>
65#include <string_utils.h>
66#include <locale_io.h>
67#include <string_utf8_map.h>
68#include <trigo.h>
69#include <progress_reporter.h>
70#include <project.h>
71#include <board.h>
73#include <footprint.h>
74#include <pad.h>
75#include <pcb_track.h>
76#include <pcb_shape.h>
77#include <zone.h>
78#include <padstack.h>
79#include <pcb_text.h>
80#include <pcb_dimension.h>
81#include <reporter.h>
82
83#include <pcb_io/pcb_io.h>
85
86using namespace std;
87
88
91static int parseEagle( const wxString& aDistance )
92{
93 ECOORD::EAGLE_UNIT unit = ( aDistance.npos != aDistance.find( "mil" ) )
96
97 ECOORD coord( aDistance, unit );
98
99 return coord.ToPcbUnits();
100}
101
102
103// In Eagle one can specify DRC rules where min value > max value,
104// in such case the max value has the priority
105template<typename T>
106static T eagleClamp( T aMin, T aValue, T aMax )
107{
108 T ret = std::max( aMin, aValue );
109 return std::min( aMax, ret );
110}
111
112
115static wxString makeKey( const wxString& aFirst, const wxString& aSecond )
116{
117 wxString key = aFirst + '\x02' + aSecond;
118 return key;
119}
120
121
122void PCB_IO_EAGLE::setKeepoutSettingsToZone( ZONE* aZone, int aLayer ) const
123{
124 if( aLayer == EAGLE_LAYER::TRESTRICT || aLayer == EAGLE_LAYER::BRESTRICT )
125 {
126 aZone->SetIsRuleArea( true );
127 aZone->SetDoNotAllowVias( true );
128 aZone->SetDoNotAllowTracks( true );
129 aZone->SetDoNotAllowCopperPour( true );
130 aZone->SetDoNotAllowPads( true );
131 aZone->SetDoNotAllowFootprints( false );
132
133 if( aLayer == EAGLE_LAYER::TRESTRICT ) // front layer keepout
134 aZone->SetLayer( F_Cu );
135 else // bottom layer keepout
136 aZone->SetLayer( B_Cu );
137 }
138 else if( aLayer == EAGLE_LAYER::VRESTRICT )
139 {
140 aZone->SetIsRuleArea( true );
141 aZone->SetDoNotAllowVias( true );
142 aZone->SetDoNotAllowTracks( false );
143 aZone->SetDoNotAllowCopperPour( false );
144 aZone->SetDoNotAllowPads( false );
145 aZone->SetDoNotAllowFootprints( false );
146
147 aZone->SetLayerSet( LSET::AllCuMask() );
148 }
149 else // copper pour cutout
150 {
151 aZone->SetIsRuleArea( true );
152 aZone->SetDoNotAllowVias( false );
153 aZone->SetDoNotAllowTracks( false );
154 aZone->SetDoNotAllowCopperPour( true );
155 aZone->SetDoNotAllowPads( false );
156 aZone->SetDoNotAllowFootprints( false );
157
158 aZone->SetLayerSet( kicad_layer( aLayer ) );
159 }
160}
161
162
163void ERULES::parse( wxXmlNode* aRules, std::function<void()> aCheckpoint )
164{
165 wxXmlNode* child = aRules->GetChildren();
166
167 while( child )
168 {
169 aCheckpoint();
170
171 if( child->GetName() == wxT( "param" ) )
172 {
173 const wxString& name = child->GetAttribute( wxT( "name" ) );
174 const wxString& value = child->GetAttribute( wxT( "value" ) );
175
176 if( name == wxT( "psElongationLong" ) )
177 psElongationLong = wxAtoi( value );
178 else if( name == wxT( "psElongationOffset" ) )
179 psElongationOffset = wxAtoi( value );
180 else if( name == wxT( "mvStopFrame" ) )
181 value.ToCDouble( &mvStopFrame );
182 else if( name == wxT( "mvCreamFrame" ) )
183 value.ToCDouble( &mvCreamFrame );
184 else if( name == wxT( "mlMinStopFrame" ) )
185 mlMinStopFrame = parseEagle( value );
186 else if( name == wxT( "mlMaxStopFrame" ) )
187 mlMaxStopFrame = parseEagle( value );
188 else if( name == wxT( "mlMinCreamFrame" ) )
189 mlMinCreamFrame = parseEagle( value );
190 else if( name == wxT( "mlMaxCreamFrame" ) )
191 mlMaxCreamFrame = parseEagle( value );
192 else if( name == wxT( "srRoundness" ) )
193 value.ToCDouble( &srRoundness );
194 else if( name == wxT( "srMinRoundness" ) )
195 srMinRoundness = parseEagle( value );
196 else if( name == wxT( "srMaxRoundness" ) )
197 srMaxRoundness = parseEagle( value );
198 else if( name == wxT( "psTop" ) )
199 psTop = wxAtoi( value );
200 else if( name == wxT( "psBottom" ) )
201 psBottom = wxAtoi( value );
202 else if( name == wxT( "psFirst" ) )
203 psFirst = wxAtoi( value );
204 else if( name == wxT( "rvPadTop" ) )
205 value.ToCDouble( &rvPadTop );
206 else if( name == wxT( "rlMinPadTop" ) )
207 rlMinPadTop = parseEagle( value );
208 else if( name == wxT( "rlMaxPadTop" ) )
209 rlMaxPadTop = parseEagle( value );
210 else if( name == wxT( "rvViaOuter" ) )
211 value.ToCDouble( &rvViaOuter );
212 else if( name == wxT( "rlMinViaOuter" ) )
213 rlMinViaOuter = parseEagle( value );
214 else if( name == wxT( "rlMaxViaOuter" ) )
215 rlMaxViaOuter = parseEagle( value );
216 else if( name == wxT( "mdWireWire" ) )
217 mdWireWire = parseEagle( value );
218 }
219
220 child = child->GetNext();
221 }
222}
223
224
226 PCB_IO( wxS( "Eagle" ) ),
227 m_rules( new ERULES() ),
228 m_xpath( new XPATH() ),
229 m_progressReporter( nullptr ),
230 m_doneCount( 0 ),
231 m_lastProgressCount( 0 ),
232 m_totalCount( 0 ),
233 m_mod_time( wxDateTime::Now() )
234{
235 using namespace std::placeholders;
236
237 init( nullptr );
238 clear_cu_map();
240}
241
242
244{
246 delete m_rules;
247 delete m_xpath;
248}
249
250
251bool PCB_IO_EAGLE::CanReadBoard( const wxString& aFileName ) const
252{
253 if( !PCB_IO::CanReadBoard( aFileName ) )
254 return false;
255
256 return checkHeader( aFileName );
257}
258
259
260bool PCB_IO_EAGLE::CanReadLibrary( const wxString& aFileName ) const
261{
262 if( !PCB_IO::CanReadLibrary( aFileName ) )
263 return false;
264
265 return checkHeader( aFileName );
266}
267
268
269bool PCB_IO_EAGLE::CanReadFootprint( const wxString& aFileName ) const
270{
271 return CanReadLibrary( aFileName );
272}
273
274
275bool PCB_IO_EAGLE::checkHeader(const wxString& aFileName) const
276{
277 wxFileInputStream input( aFileName );
278
279 if( !input.IsOk() )
280 return false;
281
282 wxTextInputStream text( input );
283
284 for( int i = 0; i < 4; i++ )
285 {
286 if( input.Eof() )
287 return false;
288
289 if( text.ReadLine().Contains( wxS( "<eagle" ) ) )
290 return true;
291 }
292
293 return false;
294}
295
296
298{
299 const unsigned PROGRESS_DELTA = 50;
300
302 {
303 if( ++m_doneCount > m_lastProgressCount + PROGRESS_DELTA )
304 {
306 / std::max( 1U, m_totalCount ) );
307
309 THROW_IO_ERROR( _( "Open cancelled by user." ) );
310
312 }
313 }
314}
315
316
317VECTOR2I inline PCB_IO_EAGLE::kicad_fontsize( const ECOORD& d, int aTextThickness ) const
318{
319 // Eagle includes stroke thickness in the text size, KiCAD does not
320 int kz = d.ToPcbUnits();
321 return VECTOR2I( kz - aTextThickness, kz - aTextThickness );
322}
323
324
325BOARD* PCB_IO_EAGLE::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
326 const STRING_UTF8_MAP* aProperties, PROJECT* aProject )
327{
328 LOCALE_IO toggle; // toggles on, then off, the C locale.
329 wxXmlNode* doc;
330
332
333 init( aProperties );
334
335 m_board = aAppendToMe ? aAppendToMe : new BOARD();
336
337 // Give the filename to the board if it's new
338 if( !aAppendToMe )
339 m_board->SetFileName( aFileName );
340
341 // delete on exception, if I own m_board, according to aAppendToMe
342 unique_ptr<BOARD> deleter( aAppendToMe ? nullptr : m_board );
343
344 try
345 {
347 {
348 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
349
351 THROW_IO_ERROR( _( "Open cancelled by user." ) );
352 }
353
354 wxFileName fn = aFileName;
355
356 // Load the document
357 wxFFileInputStream stream( fn.GetFullPath() );
358 wxXmlDocument xmlDocument;
359
360 if( !stream.IsOk() || !xmlDocument.Load( stream ) )
361 {
362 THROW_IO_ERROR( wxString::Format( _( "Unable to read file '%s'" ),
363 fn.GetFullPath() ) );
364 }
365
366 doc = xmlDocument.GetRoot();
367
368 m_min_trace = INT_MAX;
369 m_min_hole = INT_MAX;
370 m_min_via = INT_MAX;
371 m_min_annulus = INT_MAX;
372
373 loadAllSections( doc );
374
376
377 if( m_min_trace < bds.m_TrackMinWidth )
379
380 if( m_min_via < bds.m_ViasMinSize )
382
383 if( m_min_hole < bds.m_MinThroughDrill )
385
388
389 if( m_rules->mdWireWire )
391
392 NETCLASS defaults( wxT( "dummy" ) );
393
394 auto finishNetclass =
395 [&]( std::shared_ptr<NETCLASS> netclass )
396 {
397 // If Eagle has a clearance matrix then we'll build custom rules from that.
398 // Netclasses should just be the board minimum clearance.
399 netclass->SetClearance( KiROUND( bds.m_MinClearance ) );
400
401 if( netclass->GetTrackWidth() == INT_MAX )
402 netclass->SetTrackWidth( defaults.GetTrackWidth() );
403
404 if( netclass->GetViaDiameter() == INT_MAX )
405 netclass->SetViaDiameter( defaults.GetViaDiameter() );
406
407 if( netclass->GetViaDrill() == INT_MAX )
408 netclass->SetViaDrill( defaults.GetViaDrill() );
409 };
410
411 std::shared_ptr<NET_SETTINGS>& netSettings = bds.m_NetSettings;
412
413 finishNetclass( netSettings->m_DefaultNetClass );
414
415 for( const auto& [ name, netclass ] : netSettings->m_NetClasses )
416 finishNetclass( netclass );
417
420
421 fn.SetExt( wxT( "kicad_dru" ) );
422 wxFile rulesFile( fn.GetFullPath(), wxFile::write );
423 rulesFile.Write( m_customRules );
424
425 // should be empty, else missing m_xpath->pop()
426 wxASSERT( m_xpath->Contents().size() == 0 );
427 }
428 catch( const XML_PARSER_ERROR &exc )
429 {
430 wxString errmsg = exc.what();
431
432 errmsg += wxT( "\n@ " );
433 errmsg += m_xpath->Contents();
434
435 THROW_IO_ERROR( errmsg );
436 }
437
438 // IO_ERROR exceptions are left uncaught, they pass upwards from here.
439
441 centerBoard();
442
443 deleter.release();
444 return m_board;
445}
446
447
449{
450 std::vector<FOOTPRINT*> retval;
451
452 for( const auto& [ name, footprint ] : m_templates )
453 retval.push_back( static_cast<FOOTPRINT*>( footprint->Clone() ) );
454
455 return retval;
456}
457
458
459void PCB_IO_EAGLE::init( const STRING_UTF8_MAP* aProperties )
460{
461 m_hole_count = 0;
462 m_min_trace = 0;
463 m_min_hole = 0;
464 m_min_via = 0;
465 m_min_annulus = 0;
466 m_xpath->clear();
467 m_pads_to_nets.clear();
468
469 m_board = nullptr;
470 m_props = aProperties;
471
472
473 delete m_rules;
474 m_rules = new ERULES();
475}
476
477
479{
480 // All cu layers are invalid until we see them in the <layers> section while
481 // loading either a board or library. See loadLayerDefs().
482 for( unsigned i = 0; i < arrayDim(m_cu_map); ++i )
483 m_cu_map[i] = -1;
484}
485
486
487void PCB_IO_EAGLE::loadAllSections( wxXmlNode* aDoc )
488{
489 wxXmlNode* drawing = MapChildren( aDoc )["drawing"];
490 NODE_MAP drawingChildren = MapChildren( drawing );
491
492 wxXmlNode* board = drawingChildren["board"];
493 NODE_MAP boardChildren = MapChildren( board );
494
495 auto count_children = [this]( wxXmlNode* aNode )
496 {
497 if( aNode )
498 {
499 wxXmlNode* child = aNode->GetChildren();
500
501 while( child )
502 {
503 m_totalCount++;
504 child = child->GetNext();
505 }
506 }
507 };
508
509 wxXmlNode* designrules = boardChildren["designrules"];
510 wxXmlNode* layers = drawingChildren["layers"];
511 wxXmlNode* plain = boardChildren["plain"];
512 wxXmlNode* classes = boardChildren["classes"];
513 wxXmlNode* signals = boardChildren["signals"];
514 wxXmlNode* libs = boardChildren["libraries"];
515 wxXmlNode* elems = boardChildren["elements"];
516
518 {
519 m_totalCount = 0;
520 m_doneCount = 0;
521
522 count_children( designrules );
523 count_children( layers );
524 count_children( plain );
525 count_children( signals );
526 count_children( elems );
527
528 while( libs )
529 {
530 count_children( MapChildren( libs )["packages"] );
531 libs = libs->GetNext();
532 }
533
534 // Rewind
535 libs = boardChildren["libraries"];
536 }
537
538 m_xpath->push( "eagle.drawing" );
539
540 {
541 m_xpath->push( "board" );
542
543 loadDesignRules( designrules );
544
545 m_xpath->pop();
546 }
547
548 {
549 m_xpath->push( "layers" );
550
551 loadLayerDefs( layers );
553
554 m_xpath->pop();
555 }
556
557 {
558 m_xpath->push( "board" );
559
560 loadPlain( plain );
561 loadClasses( classes );
562 loadSignals( signals );
563 loadLibraries( libs );
564 loadElements( elems );
565
566 m_xpath->pop();
567 }
568
569 m_xpath->pop(); // "eagle.drawing"
570}
571
572
573void PCB_IO_EAGLE::loadDesignRules( wxXmlNode* aDesignRules )
574{
575 if( aDesignRules )
576 {
577 m_xpath->push( "designrules" );
578 m_rules->parse( aDesignRules, [this](){ checkpoint(); } );
579 m_xpath->pop(); // "designrules"
580 }
581}
582
583
584void PCB_IO_EAGLE::loadLayerDefs( wxXmlNode* aLayers )
585{
586 if( !aLayers )
587 return;
588
589 ELAYERS cu; // copper layers
590
591 // Get the first layer and iterate
592 wxXmlNode* layerNode = aLayers->GetChildren();
593
594 m_eagleLayers.clear();
595 m_eagleLayersIds.clear();
596
597 while( layerNode )
598 {
599 ELAYER elayer( layerNode );
600 m_eagleLayers.insert( std::make_pair( elayer.number, elayer ) );
601 m_eagleLayersIds.insert( std::make_pair( elayer.name, elayer.number ) );
602
603 // find the subset of layers that are copper and active
604 if( elayer.number >= 1 && elayer.number <= 16 && ( !elayer.active || *elayer.active ) )
605 cu.push_back( elayer );
606
607 layerNode = layerNode->GetNext();
608 }
609
610 // establish cu layer map:
611 int ki_layer_count = 0;
612
613 for( EITER it = cu.begin(); it != cu.end(); ++it, ++ki_layer_count )
614 {
615 if( ki_layer_count == 0 )
616 {
617 m_cu_map[it->number] = F_Cu;
618 }
619 else if( ki_layer_count == int( cu.size()-1 ) )
620 {
621 m_cu_map[it->number] = B_Cu;
622 }
623 else
624 {
625 // some eagle boards do not have contiguous layer number sequences.
626 m_cu_map[it->number] = ki_layer_count;
627 }
628 }
629
630 // Set the layer names and cu count if we're loading a board.
631 if( m_board )
632 {
633 m_board->SetCopperLayerCount( cu.size() );
634
635 for( EITER it = cu.begin(); it != cu.end(); ++it )
636 {
637 PCB_LAYER_ID layer = kicad_layer( it->number );
638
639 // these function provide their own protection against non enabled layers:
640 if( layer >= 0 && layer < PCB_LAYER_ID_COUNT ) // layer should be valid
641 {
642 m_board->SetLayerName( layer, it->name );
643 m_board->SetLayerType( layer, LT_SIGNAL );
644 }
645
646 // could map the colors here
647 }
648 }
649}
650
651
652#define DIMENSION_PRECISION DIM_PRECISION::X_XX // 0.01 mm
653
654
655void PCB_IO_EAGLE::loadPlain( wxXmlNode* aGraphics )
656{
657 if( !aGraphics )
658 return;
659
660 m_xpath->push( "plain" );
661
662 // Get the first graphic and iterate
663 wxXmlNode* gr = aGraphics->GetChildren();
664
665 // (polygon | wire | text | circle | rectangle | frame | hole)*
666 while( gr )
667 {
668 checkpoint();
669
670 wxString grName = gr->GetName();
671
672 if( grName == wxT( "wire" ) )
673 {
674 m_xpath->push( "wire" );
675
676 EWIRE w( gr );
677 PCB_LAYER_ID layer = kicad_layer( w.layer );
678
679 VECTOR2I start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
680 VECTOR2I end( kicad_x( w.x2 ), kicad_y( w.y2 ) );
681
682 if( layer != UNDEFINED_LAYER )
683 {
684 PCB_SHAPE* shape = new PCB_SHAPE( m_board );
685 int width = w.width.ToPcbUnits();
686
687 // KiCad cannot handle zero or negative line widths
688 if( width <= 0 )
689 width = m_board->GetDesignSettings().GetLineThickness( layer );
690
691 m_board->Add( shape, ADD_MODE::APPEND );
692
693 if( !w.curve )
694 {
695 shape->SetShape( SHAPE_T::SEGMENT );
696 shape->SetStart( start );
697 shape->SetEnd( end );
698 }
699 else
700 {
701 VECTOR2I center = ConvertArcCenter( start, end, *w.curve );
702
703 shape->SetShape( SHAPE_T::ARC );
704 shape->SetCenter( center );
705 shape->SetStart( start );
706 shape->SetArcAngleAndEnd( -EDA_ANGLE( *w.curve, DEGREES_T ), true ); // KiCad rotates the other way
707 }
708
709 shape->SetLayer( layer );
710 shape->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
711 }
712
713 m_xpath->pop();
714 }
715 else if( grName == wxT( "text" ) )
716 {
717 m_xpath->push( "text" );
718
719 ETEXT t( gr );
720 PCB_LAYER_ID layer = kicad_layer( t.layer );
721
722 if( layer != UNDEFINED_LAYER )
723 {
724 PCB_TEXT* pcbtxt = new PCB_TEXT( m_board );
725 m_board->Add( pcbtxt, ADD_MODE::APPEND );
726
727 pcbtxt->SetLayer( layer );
728 wxString kicadText = interpretText( t.text );
729 pcbtxt->SetText( kicadText );
730
731 double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default
732 int textThickness = KiROUND( t.size.ToPcbUnits() * ratio / 100 );
733 pcbtxt->SetTextThickness( textThickness );
734 pcbtxt->SetTextSize( kicad_fontsize( t.size, textThickness ) );
735 pcbtxt->SetKeepUpright( false );
736
737 // Eagle's anchor is independent of text justification; KiCad's is not.
738 VECTOR2I eagleAnchor( kicad_x( t.x ), kicad_y( t.y ) );
739 int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT;
740 BOX2I textbox = pcbtxt->GetBoundingBox();
741 VECTOR2I offset( 0, 0 );
742 double degrees = 0;
743 int signX = 0;
744 int signY = 0;
745
746 if( t.rot )
747 {
748 if( !t.rot->spin )
749 degrees = t.rot->degrees;
750
751 if( t.rot->mirror )
752 pcbtxt->SetMirrored( t.rot->mirror );
753
754 if( degrees > 90 && degrees <= 270 )
755 {
756 if( degrees == 270 && t.rot->mirror )
757 degrees = 270; // an odd special-case
758 else
759 degrees -= 180;
760
761 signX = t.rot->mirror ? 1 : -1;
762 signY = 1;
763 }
764
765 pcbtxt->SetTextAngle( EDA_ANGLE( degrees, DEGREES_T ) );
766 }
767
768 switch( align )
769 {
773 offset.y = signY * (int) textbox.GetHeight();
774 break;
775
777 case ETEXT::TOP_LEFT:
778 case ETEXT::TOP_RIGHT:
779 offset.y = -signY * (int) textbox.GetHeight();
780 break;
781
782 default:
783 break;
784 }
785
786 switch( align )
787 {
788 case ETEXT::TOP_LEFT:
791 offset.x = signX * (int) textbox.GetWidth();
792 break;
793
794 case ETEXT::TOP_RIGHT:
797 offset.x = -signX * (int) textbox.GetWidth();
798 break;
799
800 default:
801 break;
802 }
803
804 RotatePoint( offset, EDA_ANGLE( degrees, DEGREES_T ) );
805 pcbtxt->SetTextPos( eagleAnchor + offset );
806
807 switch( align )
808 {
809 case ETEXT::CENTER:
812 break;
813
817 break;
818
822 break;
823
827 break;
828
829 case ETEXT::TOP_LEFT:
832 break;
833
834 case ETEXT::TOP_RIGHT:
837 break;
838
842 break;
843
847 break;
848
852 break;
853 }
854 }
855
856 m_xpath->pop();
857 }
858 else if( grName == wxT( "circle" ) )
859 {
860 m_xpath->push( "circle" );
861
862 ECIRCLE c( gr );
863
864 int width = c.width.ToPcbUnits();
865 int radius = c.radius.ToPcbUnits();
866
869 {
870 ZONE* zone = new ZONE( m_board );
871 m_board->Add( zone, ADD_MODE::APPEND );
872
874
875 // approximate circle as polygon
876 VECTOR2I center( kicad_x( c.x ), kicad_y( c.y ) );
877 int outlineRadius = radius + ( width / 2 );
878 int segsInCircle = GetArcToSegmentCount( outlineRadius, ARC_HIGH_DEF, FULL_CIRCLE );
879 EDA_ANGLE delta = ANGLE_360 / segsInCircle;
880
881 for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
882 {
883 VECTOR2I rotatedPoint( outlineRadius, 0 );
884 RotatePoint( rotatedPoint, angle );
885 zone->AppendCorner( center + rotatedPoint, -1 );
886 }
887
888 if( width > 0 )
889 {
890 zone->NewHole();
891 int innerRadius = radius - ( width / 2 );
892 segsInCircle = GetArcToSegmentCount( innerRadius, ARC_HIGH_DEF, FULL_CIRCLE );
893 delta = ANGLE_360 / segsInCircle;
894
895 for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
896 {
897 VECTOR2I rotatedPoint( innerRadius, 0 );
898 RotatePoint( rotatedPoint, angle );
899 zone->AppendCorner( center + rotatedPoint, 0 );
900 }
901 }
902
903 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
905 }
906 else
907 {
908 PCB_LAYER_ID layer = kicad_layer( c.layer );
909
910 if( layer != UNDEFINED_LAYER ) // unsupported layer
911 {
912 PCB_SHAPE* shape = new PCB_SHAPE( m_board, SHAPE_T::CIRCLE );
913 m_board->Add( shape, ADD_MODE::APPEND );
914 shape->SetFilled( false );
915 shape->SetLayer( layer );
916 shape->SetStart( VECTOR2I( kicad_x( c.x ), kicad_y( c.y ) ) );
917 shape->SetEnd( VECTOR2I( kicad_x( c.x ) + radius, kicad_y( c.y ) ) );
918 shape->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
919 }
920 }
921
922 m_xpath->pop();
923 }
924 else if( grName == wxT( "rectangle" ) )
925 {
926 // This seems to be a simplified rectangular [copper] zone, cannot find any
927 // net related info on it from the DTD.
928 m_xpath->push( "rectangle" );
929
930 ERECT r( gr );
931 PCB_LAYER_ID layer = kicad_layer( r.layer );
932
933 if( layer != UNDEFINED_LAYER )
934 {
935 ZONE* zone = new ZONE( m_board );
936
937 m_board->Add( zone, ADD_MODE::APPEND );
938
939 zone->SetLayer( layer );
941
942 ZONE_BORDER_DISPLAY_STYLE outline_hatch = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE;
943
944 const int outlineIdx = -1; // this is the id of the copper zone main outline
945 zone->AppendCorner( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y1 ) ), outlineIdx );
946 zone->AppendCorner( VECTOR2I( kicad_x( r.x2 ), kicad_y( r.y1 ) ), outlineIdx );
947 zone->AppendCorner( VECTOR2I( kicad_x( r.x2 ), kicad_y( r.y2 ) ), outlineIdx );
948 zone->AppendCorner( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y2 ) ), outlineIdx );
949
950 if( r.rot )
951 {
952 VECTOR2I center( ( kicad_x( r.x1 ) + kicad_x( r.x2 ) ) / 2,
953 ( kicad_y( r.y1 ) + kicad_y( r.y2 ) ) / 2 );
954 zone->Rotate( center, EDA_ANGLE( r.rot->degrees, DEGREES_T ) );
955 }
956
957 // this is not my fault:
958 zone->SetBorderDisplayStyle( outline_hatch, ZONE::GetDefaultHatchPitch(), true );
959 }
960
961 m_xpath->pop();
962 }
963 else if( grName == wxT( "hole" ) )
964 {
965 m_xpath->push( "hole" );
966
967 // Fabricate a FOOTPRINT with a single PAD_ATTRIB::NPTH pad.
968 // Use m_hole_count to gen up a unique name.
969
970 FOOTPRINT* footprint = new FOOTPRINT( m_board );
971 m_board->Add( footprint, ADD_MODE::APPEND );
972 footprint->SetReference( wxString::Format( wxT( "@HOLE%d" ), m_hole_count++ ) );
973 footprint->Reference().SetVisible( false );
974
975 packageHole( footprint, gr, true );
976
977 m_xpath->pop();
978 }
979 else if( grName == wxT( "frame" ) )
980 {
981 // picture this
982 }
983 else if( grName == wxT( "polygon" ) )
984 {
985 m_xpath->push( "polygon" );
986 loadPolygon( gr );
987 m_xpath->pop(); // "polygon"
988 }
989 else if( grName == wxT( "dimension" ) )
990 {
991 const BOARD_DESIGN_SETTINGS& designSettings = m_board->GetDesignSettings();
992
993 EDIMENSION d( gr );
994 PCB_LAYER_ID layer = kicad_layer( d.layer );
995 VECTOR2I pt1( kicad_x( d.x1 ), kicad_y( d.y1 ) );
996 VECTOR2I pt2( kicad_x( d.x2 ), kicad_y( d.y2 ) );
997 VECTOR2I pt3( kicad_x( d.x3 ), kicad_y( d.y3 ) );
998 VECTOR2I textSize = designSettings.GetTextSize( layer );
999 int textThickness = designSettings.GetLineThickness( layer );
1000
1001 if( d.textsize )
1002 {
1003 double ratio = 8; // DTD says 8 is default
1004 textThickness = KiROUND( d.textsize->ToPcbUnits() * ratio / 100 );
1005 textSize = kicad_fontsize( *d.textsize, textThickness );
1006 }
1007
1008 if( layer != UNDEFINED_LAYER )
1009 {
1010 if( d.dimensionType == wxT( "angle" ) )
1011 {
1012 // Kicad doesn't (at present) support angle dimensions
1013 }
1014 else if( d.dimensionType == wxT( "radius" ) )
1015 {
1016 PCB_DIM_RADIAL* dimension = new PCB_DIM_RADIAL( m_board );
1017 m_board->Add( dimension, ADD_MODE::APPEND );
1018
1019 dimension->SetLayer( layer );
1020 dimension->SetPrecision( DIMENSION_PRECISION );
1021
1022 dimension->SetStart( pt1 );
1023 dimension->SetEnd( pt2 );
1024 dimension->SetTextPos( pt3 );
1025 dimension->SetTextSize( textSize );
1026 dimension->SetTextThickness( textThickness );
1027 dimension->SetLineThickness( designSettings.GetLineThickness( layer ) );
1028 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1029 }
1030 else if( d.dimensionType == wxT( "leader" ) )
1031 {
1032 PCB_DIM_LEADER* leader = new PCB_DIM_LEADER( m_board );
1033 m_board->Add( leader, ADD_MODE::APPEND );
1034
1035 leader->SetLayer( layer );
1037
1038 leader->SetStart( pt1 );
1039 leader->SetEnd( pt2 );
1040 leader->SetTextPos( pt3 );
1041 leader->SetTextSize( textSize );
1042 leader->SetTextThickness( textThickness );
1043 leader->SetOverrideText( wxEmptyString );
1044 leader->SetLineThickness( designSettings.GetLineThickness( layer ) );
1045 }
1046 else // horizontal, vertical, <default>, diameter
1047 {
1049 m_board->Add( dimension, ADD_MODE::APPEND );
1050
1051 if( d.dimensionType )
1052 {
1053 // Eagle dimension graphic arms may have different lengths, but they look
1054 // incorrect in KiCad (the graphic is tilted). Make them even length in
1055 // such case.
1056 if( *d.dimensionType == wxT( "horizontal" ) )
1057 {
1058 int newY = ( pt1.y + pt2.y ) / 2;
1059 pt1.y = newY;
1060 pt2.y = newY;
1061 }
1062 else if( *d.dimensionType == wxT( "vertical" ) )
1063 {
1064 int newX = ( pt1.x + pt2.x ) / 2;
1065 pt1.x = newX;
1066 pt2.x = newX;
1067 }
1068 }
1069
1070 dimension->SetLayer( layer );
1071 dimension->SetPrecision( DIMENSION_PRECISION );
1072
1073 // The origin and end are assumed to always be in this order from eagle
1074 dimension->SetStart( pt1 );
1075 dimension->SetEnd( pt2 );
1076 dimension->SetTextSize( textSize );
1077 dimension->SetTextThickness( textThickness );
1078 dimension->SetLineThickness( designSettings.GetLineThickness( layer ) );
1079 dimension->SetUnits( EDA_UNITS::MILLIMETRES );
1080
1081 // check which axis the dimension runs in
1082 // because the "height" of the dimension is perpendicular to that axis
1083 // Note the check is just if two axes are close enough to each other
1084 // Eagle appears to have some rounding errors
1085 if( abs( pt1.x - pt2.x ) < 50000 ) // 50000 nm = 0.05 mm
1086 {
1087 int offset = pt3.x - pt1.x;
1088
1089 if( pt1.y > pt2.y )
1090 dimension->SetHeight( offset );
1091 else
1092 dimension->SetHeight( -offset );
1093 }
1094 else if( abs( pt1.y - pt2.y ) < 50000 )
1095 {
1096 int offset = pt3.y - pt1.y;
1097
1098 if( pt1.x > pt2.x )
1099 dimension->SetHeight( -offset );
1100 else
1101 dimension->SetHeight( offset );
1102 }
1103 else
1104 {
1105 int offset = KiROUND( pt3.Distance( pt1 ) );
1106
1107 if( pt1.y > pt2.y )
1108 dimension->SetHeight( offset );
1109 else
1110 dimension->SetHeight( -offset );
1111 }
1112 }
1113 }
1114 }
1115
1116 // Get next graphic
1117 gr = gr->GetNext();
1118 }
1119
1120 m_xpath->pop();
1121}
1122
1123
1124void PCB_IO_EAGLE::loadLibrary( wxXmlNode* aLib, const wxString* aLibName )
1125{
1126 if( !aLib )
1127 return;
1128
1129 wxString urn = aLib->GetAttribute( "urn" );
1130
1131 wxString urnOrdinal;
1132
1133 if( !urn.IsEmpty() )
1134 {
1135 urnOrdinal = urn.AfterLast( ':' );
1136 }
1137
1138 // library will have <xmlattr> node, skip that and get the single packages node
1139 wxXmlNode* packages = MapChildren( aLib )["packages"];
1140
1141 if( !packages )
1142 return;
1143
1144 m_xpath->push( "packages" );
1145
1146 // Create a FOOTPRINT for all the eagle packages, for use later via a copy constructor
1147 // to instantiate needed footprints in our BOARD. Save the FOOTPRINT templates in
1148 // a FOOTPRINT_MAP using a single lookup key consisting of libname+pkgname.
1149
1150 // Get the first package and iterate
1151 wxXmlNode* package = packages->GetChildren();
1152
1153 while( package )
1154 {
1155 checkpoint();
1156
1157 m_xpath->push( "package", "name" );
1158
1159 wxString pack_ref = package->GetAttribute( "name" );
1160
1161 if( !urnOrdinal.IsEmpty() )
1162 pack_ref += wxS( "_" ) + urnOrdinal;
1163
1164 ReplaceIllegalFileNameChars( pack_ref, '_' );
1165
1166 m_xpath->Value( pack_ref.ToUTF8() );
1167
1168 wxString key = aLibName ? makeKey( *aLibName, pack_ref ) : pack_ref;
1169
1170 FOOTPRINT* footprint = makeFootprint( package, pack_ref );
1171
1172 // add the templating FOOTPRINT to the FOOTPRINT template factory "m_templates"
1173 auto r = m_templates.insert( { key, footprint } );
1174
1175 if( !r.second /* && !( m_props && m_props->Value( "ignore_duplicates" ) ) */ )
1176 {
1177 wxString lib = aLibName ? *aLibName : m_lib_path;
1178 const wxString& pkg = pack_ref;
1179
1180 wxString emsg = wxString::Format( _( "<package> '%s' duplicated in <library> '%s'" ),
1181 pkg,
1182 lib );
1183 THROW_IO_ERROR( emsg );
1184 }
1185
1186 m_xpath->pop();
1187
1188 package = package->GetNext();
1189 }
1190
1191 m_xpath->pop(); // "packages"
1192}
1193
1194
1195void PCB_IO_EAGLE::loadLibraries( wxXmlNode* aLibs )
1196{
1197 if( !aLibs )
1198 return;
1199
1200 m_xpath->push( "libraries.library", "name" );
1201
1202 // Get the first library and iterate
1203 wxXmlNode* library = aLibs->GetChildren();
1204
1205 while( library )
1206 {
1207 const wxString& lib_name = library->GetAttribute( "name" );
1208
1209 m_xpath->Value( lib_name.c_str() );
1210 loadLibrary( library, &lib_name );
1211 library = library->GetNext();
1212 }
1213
1214 m_xpath->pop();
1215}
1216
1217
1218void PCB_IO_EAGLE::loadElements( wxXmlNode* aElements )
1219{
1220 if( !aElements )
1221 return;
1222
1223 m_xpath->push( "elements.element", "name" );
1224
1225 EATTR name;
1226 EATTR value;
1227 bool refanceNamePresetInPackageLayout;
1228 bool valueNamePresetInPackageLayout;
1229
1230 // Get the first element and iterate
1231 wxXmlNode* element = aElements->GetChildren();
1232
1233 while( element )
1234 {
1235 checkpoint();
1236
1237 if( element->GetName() != wxT( "element" ) )
1238 {
1239 // Get next item
1240 element = element->GetNext();
1241 continue;
1242 }
1243
1244 EELEMENT e( element );
1245
1246 // use "NULL-ness" as an indication of presence of the attribute:
1247 EATTR* nameAttr = nullptr;
1248 EATTR* valueAttr = nullptr;
1249
1250 m_xpath->Value( e.name.c_str() );
1251
1252 wxString packageName = e.package;
1253
1254 if( e.library_urn )
1255 {
1256 wxString libOrdinal = *e.library_urn;
1257 packageName = e.package + wxS( "_" ) + libOrdinal.AfterLast( ':' );
1258 }
1259
1260 wxString pkg_key = makeKey( e.library, packageName );
1261 auto it = m_templates.find( pkg_key );
1262
1263 if( it == m_templates.end() )
1264 {
1265 wxString emsg = wxString::Format( _( "No '%s' package in library '%s'." ),
1266 packageName, e.library );
1267 THROW_IO_ERROR( emsg );
1268 }
1269
1270 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( it->second->Duplicate() );
1271
1272 m_board->Add( footprint, ADD_MODE::APPEND );
1273
1274 // update the nets within the pads of the clone
1275 for( PAD* pad : footprint->Pads() )
1276 {
1277 wxString pn_key = makeKey( e.name, pad->GetNumber() );
1278
1279 NET_MAP_CITER ni = m_pads_to_nets.find( pn_key );
1280 if( ni != m_pads_to_nets.end() )
1281 {
1282 const ENET* enet = &ni->second;
1283 pad->SetNetCode( enet->netcode );
1284 }
1285 }
1286
1287 refanceNamePresetInPackageLayout = true;
1288 valueNamePresetInPackageLayout = true;
1289 footprint->SetPosition( VECTOR2I( kicad_x( e.x ), kicad_y( e.y ) ) );
1290
1291 // Is >NAME field set in package layout ?
1292 if( footprint->GetReference().size() == 0 )
1293 {
1294 footprint->Reference().SetVisible( false ); // No so no show
1295 refanceNamePresetInPackageLayout = false;
1296 }
1297
1298 // Is >VALUE field set in package layout
1299 if( footprint->GetValue().size() == 0 )
1300 {
1301 footprint->Value().SetVisible( false ); // No so no show
1302 valueNamePresetInPackageLayout = false;
1303 }
1304
1305 wxString reference = e.name;
1306
1307 // EAGLE allows references to be single digits. This breaks KiCad
1308 // netlisting, which requires parts to have non-digit + digit
1309 // annotation. If the reference begins with a number, we prepend
1310 // 'UNK' (unknown) for the symbol designator.
1311 if( reference.find_first_not_of( "0123456789" ) != 0 )
1312 reference.Prepend( "UNK" );
1313
1314 // EAGLE allows designator to start with # but that is used in KiCad
1315 // for symbols which do not have a footprint
1316 if( reference.find_first_not_of( "#" ) != 0 )
1317 reference.Prepend( "UNK" );
1318
1319 // reference must end with a number but EAGLE does not enforce this
1320 if( reference.find_last_not_of( "0123456789" ) == (reference.Length()-1) )
1321 reference.Append( "0" );
1322
1323 footprint->SetReference( reference );
1324 footprint->SetValue( e.value );
1325
1326 if( !e.smashed )
1327 {
1328 // Not smashed so show NAME & VALUE
1329 if( valueNamePresetInPackageLayout )
1330 footprint->Value().SetVisible( true ); // Only if place holder in package layout
1331
1332 if( refanceNamePresetInPackageLayout )
1333 footprint->Reference().SetVisible( true ); // Only if place holder in package layout
1334 }
1335 else if( *e.smashed == true )
1336 {
1337 // Smashed so set default to no show for NAME and VALUE
1338 footprint->Value().SetVisible( false );
1339 footprint->Reference().SetVisible( false );
1340
1341 // initialize these to default values in case the <attribute> elements are not present.
1342 m_xpath->push( "attribute", "name" );
1343
1344 // VALUE and NAME can have something like our text "effects" overrides
1345 // in SWEET and new schematic. Eagle calls these XML elements "attribute".
1346 // There can be one for NAME and/or VALUE both. Features present in the
1347 // EATTR override the ones established in the package only if they are
1348 // present here (except for rot, which if not present means angle zero).
1349 // So the logic is a bit different than in packageText() and in plain text.
1350
1351 // Get the first attribute and iterate
1352 wxXmlNode* attribute = element->GetChildren();
1353
1354 while( attribute )
1355 {
1356 if( attribute->GetName() != wxT( "attribute" ) )
1357 {
1358 attribute = attribute->GetNext();
1359 continue;
1360 }
1361
1362 EATTR a( attribute );
1363
1364 if( a.name == wxT( "NAME" ) )
1365 {
1366 name = a;
1367 nameAttr = &name;
1368
1369 // do we have a display attribute ?
1370 if( a.display )
1371 {
1372 // Yes!
1373 switch( *a.display )
1374 {
1375 case EATTR::VALUE :
1376 {
1377 nameAttr->name = reference;
1378
1379 if( refanceNamePresetInPackageLayout )
1380 footprint->Reference().SetVisible( true );
1381
1382 break;
1383 }
1384
1385 case EATTR::NAME :
1386 if( refanceNamePresetInPackageLayout )
1387 {
1388 footprint->SetReference( "NAME" );
1389 footprint->Reference().SetVisible( true );
1390 }
1391
1392 break;
1393
1394 case EATTR::BOTH :
1395 if( refanceNamePresetInPackageLayout )
1396 footprint->Reference().SetVisible( true );
1397
1398 nameAttr->name = nameAttr->name + wxT( " = " ) + e.name;
1399 footprint->SetReference( wxT( "NAME = " ) + e.name );
1400 break;
1401
1402 case EATTR::Off :
1403 footprint->Reference().SetVisible( false );
1404 break;
1405
1406 default:
1407 nameAttr->name = e.name;
1408
1409 if( refanceNamePresetInPackageLayout )
1410 footprint->Reference().SetVisible( true );
1411 }
1412 }
1413 else
1414 {
1415 // No display, so default is visible, and show value of NAME
1416 footprint->Reference().SetVisible( true );
1417 }
1418 }
1419 else if( a.name == wxT( "VALUE" ) )
1420 {
1421 value = a;
1422 valueAttr = &value;
1423
1424 if( a.display )
1425 {
1426 // Yes!
1427 switch( *a.display )
1428 {
1429 case EATTR::VALUE :
1430 valueAttr->value = opt_wxString( e.value );
1431 footprint->SetValue( e.value );
1432
1433 if( valueNamePresetInPackageLayout )
1434 footprint->Value().SetVisible( true );
1435
1436 break;
1437
1438 case EATTR::NAME :
1439 if( valueNamePresetInPackageLayout )
1440 footprint->Value().SetVisible( true );
1441
1442 footprint->SetValue( wxT( "VALUE" ) );
1443 break;
1444
1445 case EATTR::BOTH :
1446 if( valueNamePresetInPackageLayout )
1447 footprint->Value().SetVisible( true );
1448
1449 valueAttr->value = opt_wxString( wxT( "VALUE = " ) + e.value );
1450 footprint->SetValue( wxT( "VALUE = " ) + e.value );
1451 break;
1452
1453 case EATTR::Off :
1454 footprint->Value().SetVisible( false );
1455 break;
1456
1457 default:
1458 valueAttr->value = opt_wxString( e.value );
1459
1460 if( valueNamePresetInPackageLayout )
1461 footprint->Value().SetVisible( true );
1462 }
1463 }
1464 else
1465 {
1466 // No display, so default is visible, and show value of NAME
1467 footprint->Value().SetVisible( true );
1468 }
1469
1470 }
1471
1472 attribute = attribute->GetNext();
1473 }
1474
1475 m_xpath->pop(); // "attribute"
1476 }
1477
1478 orientFootprintAndText( footprint, e, nameAttr, valueAttr );
1479
1480 // Get next element
1481 element = element->GetNext();
1482 }
1483
1484 m_xpath->pop(); // "elements.element"
1485}
1486
1487
1488ZONE* PCB_IO_EAGLE::loadPolygon( wxXmlNode* aPolyNode )
1489{
1490 EPOLYGON p( aPolyNode );
1491 PCB_LAYER_ID layer = kicad_layer( p.layer );
1492 bool keepout = ( p.layer == EAGLE_LAYER::TRESTRICT
1494 || p.layer == EAGLE_LAYER::VRESTRICT );
1495
1496 if( layer == UNDEFINED_LAYER )
1497 {
1498 wxLogMessage( wxString::Format( _( "Ignoring a polygon since Eagle layer '%s' (%d) "
1499 "was not mapped" ),
1500 eagle_layer_name( p.layer ), p.layer ) );
1501 return nullptr;
1502 }
1503
1504 // use a "netcode = 0" type ZONE:
1505 std::unique_ptr<ZONE> zone = std::make_unique<ZONE>( m_board );
1506
1507 if( !keepout )
1508 zone->SetLayer( layer );
1509 else
1510 setKeepoutSettingsToZone( zone.get(), p.layer );
1511
1512 // Get the first vertex and iterate
1513 wxXmlNode* vertex = aPolyNode->GetChildren();
1514 std::vector<EVERTEX> vertices;
1515
1516 // Create a circular vector of vertices
1517 // The "curve" parameter indicates a curve from the current
1518 // to the next vertex, so we keep the first at the end as well
1519 // to allow the curve to link back
1520 while( vertex )
1521 {
1522 if( vertex->GetName() == wxT( "vertex" ) )
1523 vertices.emplace_back( vertex );
1524
1525 vertex = vertex->GetNext();
1526 }
1527
1528 // According to Eagle's doc, by default, the orphans (islands in KiCad parlance)
1529 // are always removed
1530 if( !p.orphans || !p.orphans.Get() )
1531 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::ALWAYS );
1532 else
1533 zone->SetIslandRemovalMode( ISLAND_REMOVAL_MODE::NEVER );
1534
1535 vertices.push_back( vertices[0] );
1536
1537 SHAPE_POLY_SET polygon;
1538 polygon.NewOutline();
1539
1540 for( size_t i = 0; i < vertices.size() - 1; i++ )
1541 {
1542 EVERTEX v1 = vertices[i];
1543
1544 // Append the corner
1545 polygon.Append( kicad_x( v1.x ), kicad_y( v1.y ) );
1546
1547 if( v1.curve )
1548 {
1549 EVERTEX v2 = vertices[i + 1];
1550 VECTOR2I center =
1551 ConvertArcCenter( VECTOR2I( kicad_x( v1.x ), kicad_y( v1.y ) ),
1552 VECTOR2I( kicad_x( v2.x ), kicad_y( v2.y ) ), *v1.curve );
1553 double angle = DEG2RAD( *v1.curve );
1554 double end_angle = atan2( kicad_y( v2.y ) - center.y, kicad_x( v2.x ) - center.x );
1555 double radius = sqrt( pow( center.x - kicad_x( v1.x ), 2 )
1556 + pow( center.y - kicad_y( v1.y ), 2 ) );
1557
1558 int segCount = GetArcToSegmentCount( KiROUND( radius ), ARC_HIGH_DEF,
1559 EDA_ANGLE( *v1.curve, DEGREES_T ) );
1560 double delta_angle = angle / segCount;
1561
1562 for( double a = end_angle + angle; fabs( a - end_angle ) > fabs( delta_angle );
1563 a -= delta_angle )
1564 {
1565 polygon.Append( KiROUND( radius * cos( a ) ) + center.x,
1566 KiROUND( radius * sin( a ) ) + center.y );
1567 }
1568 }
1569 }
1570
1571 // Eagle traces the zone such that half of the pen width is outside the polygon.
1572 // We trace the zone such that the copper is completely inside.
1573 if( p.width.ToPcbUnits() > 0 )
1574 {
1575 polygon.Inflate( p.width.ToPcbUnits() / 2, CORNER_STRATEGY::ALLOW_ACUTE_CORNERS,
1576 ARC_HIGH_DEF, true );
1577 }
1578
1579 if( polygon.OutlineCount() != 1 )
1580 {
1581 wxLogMessage( wxString::Format(
1582 _( "Skipping a polygon on layer '%s' (%d): outline count is not 1" ),
1583 eagle_layer_name( p.layer ), p.layer ) );
1584
1585 return nullptr;
1586 }
1587
1588 zone->AddPolygon( polygon.COutline( 0 ) );
1589
1590 // If the pour is a cutout it needs to be set to a keepout
1591 if( p.pour == EPOLYGON::CUTOUT )
1592 {
1593 zone->SetIsRuleArea( true );
1594 zone->SetDoNotAllowVias( false );
1595 zone->SetDoNotAllowTracks( false );
1596 zone->SetDoNotAllowPads( false );
1597 zone->SetDoNotAllowFootprints( false );
1598 zone->SetDoNotAllowCopperPour( true );
1599 zone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH );
1600 }
1601 else if( p.pour == EPOLYGON::HATCH )
1602 {
1603 int spacing = p.spacing ? p.spacing->ToPcbUnits() : 50 * pcbIUScale.IU_PER_MILS;
1604
1605 zone->SetFillMode( ZONE_FILL_MODE::HATCH_PATTERN );
1606 zone->SetHatchThickness( p.width.ToPcbUnits() );
1607 zone->SetHatchGap( spacing - p.width.ToPcbUnits() );
1608 zone->SetHatchOrientation( ANGLE_0 );
1609 }
1610
1611 // We divide the thickness by half because we are tracing _inside_ the zone outline
1612 // This means the radius of curvature will be twice the size for an equivalent EAGLE zone
1613 zone->SetMinThickness( std::max<int>( ZONE_THICKNESS_MIN_VALUE_MM * pcbIUScale.IU_PER_MM,
1614 p.width.ToPcbUnits() / 2 ) );
1615
1616 if( p.isolate )
1617 zone->SetLocalClearance( p.isolate->ToPcbUnits() );
1618 else
1619 zone->SetLocalClearance( 1 ); // @todo: set minimum clearance value based on board settings
1620
1621 // missing == yes per DTD.
1622 bool thermals = !p.thermals || *p.thermals;
1623 zone->SetPadConnection( thermals ? ZONE_CONNECTION::THERMAL : ZONE_CONNECTION::FULL );
1624
1625 if( thermals )
1626 {
1627 // FIXME: eagle calculates dimensions for thermal spokes
1628 // based on what the zone is connecting to.
1629 // (i.e. width of spoke is half of the smaller side of an smd pad)
1630 // This is a basic workaround
1631 zone->SetThermalReliefGap( p.width.ToPcbUnits() + 50000 ); // 50000nm == 0.05mm
1632 zone->SetThermalReliefSpokeWidth( p.width.ToPcbUnits() + 50000 );
1633 }
1634
1635 int rank = p.rank ? (p.max_priority - *p.rank) : p.max_priority;
1636 zone->SetAssignedPriority( rank );
1637
1638 ZONE* zonePtr = zone.release();
1639 m_board->Add( zonePtr, ADD_MODE::APPEND );
1640
1641 return zonePtr;
1642}
1643
1644
1646 const EATTR* aNameAttr, const EATTR* aValueAttr )
1647{
1648 if( e.rot )
1649 {
1650 if( e.rot->mirror )
1651 {
1652 aFootprint->SetOrientation( EDA_ANGLE( e.rot->degrees + 180.0, DEGREES_T ) );
1653 aFootprint->Flip( aFootprint->GetPosition(), false );
1654 }
1655 else
1656 {
1657 aFootprint->SetOrientation( EDA_ANGLE( e.rot->degrees, DEGREES_T ) );
1658 }
1659 }
1660
1661 orientFPText( aFootprint, e, &aFootprint->Reference(), aNameAttr );
1662 orientFPText( aFootprint, e, &aFootprint->Value(), aValueAttr );
1663}
1664
1665
1666void PCB_IO_EAGLE::orientFPText( FOOTPRINT* aFootprint, const EELEMENT& e, PCB_TEXT* aFPText,
1667 const EATTR* aAttr )
1668{
1669 // Smashed part ?
1670 if( aAttr )
1671 {
1672 // Yes
1673 const EATTR& a = *aAttr;
1674
1675 if( a.value )
1676 {
1677 aFPText->SetText( *a.value );
1678 }
1679
1680 if( a.x && a.y ) // std::optional
1681 {
1682 VECTOR2I pos( kicad_x( *a.x ), kicad_y( *a.y ) );
1683 aFPText->SetTextPos( pos );
1684 }
1685
1686 // Even though size and ratio are both optional, I am not seeing
1687 // a case where ratio is present but size is not.
1688 double ratio = 8;
1689
1690 if( a.ratio )
1691 ratio = *a.ratio;
1692
1693 VECTOR2I fontz = aFPText->GetTextSize();
1694 int textThickness = KiROUND( fontz.y * ratio / 100 );
1695
1696 aFPText->SetTextThickness( textThickness );
1697 if( a.size )
1698 {
1699 fontz = kicad_fontsize( *a.size, textThickness );
1700 aFPText->SetTextSize( fontz );
1701 }
1702
1703
1704 int align = ETEXT::BOTTOM_LEFT; // bottom-left is eagle default
1705
1706 if( a.align )
1707 align = *a.align;
1708
1709 // The "rot" in a EATTR seems to be assumed to be zero if it is not
1710 // present, and this zero rotation becomes an override to the
1711 // package's text field. If they did not want zero, they specify
1712 // what they want explicitly.
1713 double degrees = a.rot ? a.rot->degrees : 0.0;
1714
1715 int sign = 1;
1716 bool spin = false;
1717
1718 if( a.rot )
1719 {
1720 spin = a.rot->spin;
1721 sign = a.rot->mirror ? -1 : 1;
1722 aFPText->SetMirrored( a.rot->mirror );
1723 }
1724
1725 if( degrees == 90 || degrees == 0 || spin )
1726 {
1727 aFPText->SetTextAngle( EDA_ANGLE( sign * degrees, DEGREES_T ) );
1728 }
1729 else if( degrees == 180 )
1730 {
1731 aFPText->SetTextAngle( EDA_ANGLE( sign * 0, DEGREES_T ) );
1732 align = -align;
1733 }
1734 else if( degrees == 270 )
1735 {
1736 align = -align;
1737 aFPText->SetTextAngle( EDA_ANGLE( sign * 90, DEGREES_T ) );
1738 }
1739 else
1740 {
1741 aFPText->SetTextAngle( EDA_ANGLE( sign * 90 - degrees, DEGREES_T ) );
1742 }
1743
1744 switch( align )
1745 {
1746 case ETEXT::TOP_RIGHT:
1749 break;
1750
1751 case ETEXT::BOTTOM_LEFT:
1754 break;
1755
1756 case ETEXT::TOP_LEFT:
1759 break;
1760
1764 break;
1765
1766 case ETEXT::TOP_CENTER:
1769 break;
1770
1774 break;
1775
1776 case ETEXT::CENTER:
1779 break;
1780
1781 case ETEXT::CENTER_LEFT:
1784 break;
1785
1789 break;
1790
1791 default:
1792 ;
1793 }
1794 }
1795 else
1796 {
1797 // Part is not smash so use Lib default for NAME/VALUE
1798 // the text is per the original package, sans <attribute>.
1799 double degrees = aFPText->GetTextAngle().AsDegrees()
1800 + aFootprint->GetOrientation().AsDegrees();
1801
1802 // @todo there are a few more cases than these to contend with:
1803 if( ( !aFPText->IsMirrored() && ( abs( degrees ) == 180 || abs( degrees ) == 270 ) )
1804 || ( aFPText->IsMirrored() && ( degrees == 360 ) ) )
1805 {
1806 // ETEXT::TOP_RIGHT:
1809 }
1810 }
1811}
1812
1813
1814FOOTPRINT* PCB_IO_EAGLE::makeFootprint( wxXmlNode* aPackage, const wxString& aPkgName )
1815{
1816 std::unique_ptr<FOOTPRINT> m = std::make_unique<FOOTPRINT>( m_board );
1817
1818 LIB_ID fpID;
1819 fpID.Parse( aPkgName, true );
1820 m->SetFPID( fpID );
1821
1822 // Get the first package item and iterate
1823 wxXmlNode* packageItem = aPackage->GetChildren();
1824
1825 // layer 27 is default layer for tValues
1826 // set default layer for created footprint
1827 PCB_LAYER_ID layer = kicad_layer( 27 );
1828 m.get()->Value().SetLayer( layer );
1829
1830 while( packageItem )
1831 {
1832 const wxString& itemName = packageItem->GetName();
1833
1834 if( itemName == wxT( "description" ) )
1835 {
1836 wxString descr = convertDescription( UnescapeHTML( packageItem->GetNodeContent() ) );
1837 m->SetLibDescription( descr );
1838 }
1839 else if( itemName == wxT( "wire" ) )
1840 packageWire( m.get(), packageItem );
1841 else if( itemName == wxT( "pad" ) )
1842 packagePad( m.get(), packageItem );
1843 else if( itemName == wxT( "text" ) )
1844 packageText( m.get(), packageItem );
1845 else if( itemName == wxT( "rectangle" ) )
1846 packageRectangle( m.get(), packageItem );
1847 else if( itemName == wxT( "polygon" ) )
1848 packagePolygon( m.get(), packageItem );
1849 else if( itemName == wxT( "circle" ) )
1850 packageCircle( m.get(), packageItem );
1851 else if( itemName == wxT( "hole" ) )
1852 packageHole( m.get(), packageItem, false );
1853 else if( itemName == wxT( "smd" ) )
1854 packageSMD( m.get(), packageItem );
1855
1856 packageItem = packageItem->GetNext();
1857 }
1858
1859 return m.release();
1860}
1861
1862
1863void PCB_IO_EAGLE::packageWire( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
1864{
1865 EWIRE w( aTree );
1866 PCB_LAYER_ID layer = kicad_layer( w.layer );
1867 VECTOR2I start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
1868 VECTOR2I end( kicad_x( w.x2 ), kicad_y( w.y2 ) );
1869 int width = w.width.ToPcbUnits();
1870
1871 if( layer == UNDEFINED_LAYER )
1872 {
1873 wxLogMessage( wxString::Format( _( "Ignoring a wire since Eagle layer '%s' (%d) "
1874 "was not mapped" ),
1875 eagle_layer_name( w.layer ), w.layer ) );
1876 return;
1877 }
1878
1879 // KiCad cannot handle zero or negative line widths which apparently have meaning in Eagle.
1880 if( width <= 0 )
1881 {
1882 BOARD* board = aFootprint->GetBoard();
1883
1884 if( board )
1885 {
1886 width = board->GetDesignSettings().GetLineThickness( layer );
1887 }
1888 else
1889 {
1890 // When loading footprint libraries, there is no board so use the default KiCad
1891 // line widths.
1892 switch( layer )
1893 {
1894 case Edge_Cuts: width = pcbIUScale.mmToIU( DEFAULT_EDGE_WIDTH ); break;
1895
1896 case F_SilkS:
1897 case B_SilkS: width = pcbIUScale.mmToIU( DEFAULT_SILK_LINE_WIDTH ); break;
1898
1899 case F_CrtYd:
1900 case B_CrtYd: width = pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ); break;
1901
1902 default: width = pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ); break;
1903 }
1904 }
1905 }
1906
1907 // FIXME: the cap attribute is ignored because KiCad can't create lines with flat ends.
1908 PCB_SHAPE* dwg;
1909
1910 if( !w.curve )
1911 {
1912 dwg = new PCB_SHAPE( aFootprint, SHAPE_T::SEGMENT );
1913
1914 dwg->SetStart( start );
1915 dwg->SetEnd( end );
1916 }
1917 else
1918 {
1919 dwg = new PCB_SHAPE( aFootprint, SHAPE_T::ARC );
1920 VECTOR2I center = ConvertArcCenter( start, end, *w.curve );
1921
1922 dwg->SetCenter( center );
1923 dwg->SetStart( start );
1924 dwg->SetArcAngleAndEnd( -EDA_ANGLE( *w.curve, DEGREES_T ), true ); // KiCad rotates the other way
1925 }
1926
1927 dwg->SetLayer( layer );
1928 dwg->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
1929 dwg->Rotate( { 0, 0 }, aFootprint->GetOrientation() );
1930 dwg->Move( aFootprint->GetPosition() );
1931
1932 aFootprint->Add( dwg );
1933}
1934
1935
1936void PCB_IO_EAGLE::packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree )
1937{
1938 // this is thru hole technology here, no SMDs
1939 EPAD e( aTree );
1940 int shape = EPAD::UNDEF;
1941 int eagleDrillz = e.drill.ToPcbUnits();
1942
1943 std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
1944 transferPad( e, pad.get() );
1945
1946 if( e.first && *e.first && m_rules->psFirst != EPAD::UNDEF )
1947 shape = m_rules->psFirst;
1948 else if( aFootprint->GetLayer() == F_Cu && m_rules->psTop != EPAD::UNDEF )
1949 shape = m_rules->psTop;
1950 else if( aFootprint->GetLayer() == B_Cu && m_rules->psBottom != EPAD::UNDEF )
1951 shape = m_rules->psBottom;
1952
1953 pad->SetDrillSize( VECTOR2I( eagleDrillz, eagleDrillz ) );
1954 pad->SetLayerSet( LSET::AllCuMask() );
1955
1956 if( eagleDrillz < m_min_hole )
1957 m_min_hole = eagleDrillz;
1958
1959 // Solder mask
1960 if( !e.stop || *e.stop == true ) // enabled by default
1961 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask ).set( F_Mask ) );
1962
1963 if( shape == EPAD::ROUND || shape == EPAD::SQUARE || shape == EPAD::OCTAGON )
1964 e.shape = shape;
1965
1966 if( e.shape )
1967 {
1968 switch( *e.shape )
1969 {
1970 case EPAD::ROUND:
1971 pad->SetShape( PAD_SHAPE::CIRCLE );
1972 break;
1973
1974 case EPAD::OCTAGON:
1975 pad->SetShape( PAD_SHAPE::CHAMFERED_RECT );
1976 pad->SetChamferPositions( RECT_CHAMFER_ALL );
1977 pad->SetChamferRectRatio( 1 - M_SQRT1_2 ); // Regular polygon
1978 break;
1979
1980 case EPAD::LONG:
1981 pad->SetShape( PAD_SHAPE::OVAL );
1982 break;
1983
1984 case EPAD::SQUARE:
1985 pad->SetShape( PAD_SHAPE::RECTANGLE );
1986 break;
1987
1988 case EPAD::OFFSET:
1989 pad->SetShape( PAD_SHAPE::OVAL );
1990 break;
1991 }
1992 }
1993 else
1994 {
1995 // if shape is not present, our default is circle and that matches their default "round"
1996 }
1997
1998 if( e.diameter && e.diameter->value > 0 )
1999 {
2000 int diameter = e.diameter->ToPcbUnits();
2001 pad->SetSize( VECTOR2I( diameter, diameter ) );
2002 }
2003 else
2004 {
2005 double drillz = pad->GetDrillSize().x;
2006 double annulus = drillz * m_rules->rvPadTop; // copper annulus, eagle "restring"
2007 annulus = eagleClamp( m_rules->rlMinPadTop, annulus, m_rules->rlMaxPadTop );
2008 int diameter = KiROUND( drillz + 2 * annulus );
2009 pad->SetSize( VECTOR2I( KiROUND( diameter ), KiROUND( diameter ) ) );
2010 }
2011
2012 if( pad->GetShape() == PAD_SHAPE::OVAL )
2013 {
2014 // The Eagle "long" pad is wider than it is tall; m_elongation is percent elongation
2015 VECTOR2I sz = pad->GetSize();
2016 sz.x = ( sz.x * ( 100 + m_rules->psElongationLong ) ) / 100;
2017 pad->SetSize( sz );
2018
2019 if( e.shape && *e.shape == EPAD::OFFSET )
2020 {
2021 int offset = KiROUND( ( sz.x - sz.y ) / 2.0 );
2022 pad->SetOffset( VECTOR2I( offset, 0 ) );
2023 }
2024 }
2025
2026 if( e.rot )
2027 pad->SetOrientation( EDA_ANGLE( e.rot->degrees, DEGREES_T ) );
2028
2029 // Eagle spokes are always '+'
2030 pad->SetThermalSpokeAngle( ANGLE_0 );
2031
2032 if( pad->GetSizeX() > 0 && pad->GetSizeY() > 0 )
2033 {
2034 aFootprint->Add( pad.release() );
2035 }
2036 else
2037 {
2038 wxLogError( _( "Invalid zero-sized pad ignored in\nfile: %s" ), m_board->GetFileName() );
2039 }
2040}
2041
2042
2043void PCB_IO_EAGLE::packageText( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2044{
2045 ETEXT t( aTree );
2046 PCB_LAYER_ID layer = kicad_layer( t.layer );
2047
2048 if( layer == UNDEFINED_LAYER )
2049 {
2050 wxLogMessage( wxString::Format( _( "Ignoring a text since Eagle layer '%s' (%d) "
2051 "was not mapped" ),
2052 eagle_layer_name( t.layer ), t.layer ) );
2053 return;
2054 }
2055
2056 PCB_TEXT* textItem;
2057
2058 if( t.text.Upper() == wxT( ">NAME" ) && aFootprint->GetReference().IsEmpty() )
2059 {
2060 textItem = &aFootprint->Reference();
2061
2062 textItem->SetText( wxT( "REF**" ) );
2063 }
2064 else if( t.text.Upper() == wxT( ">VALUE" ) && aFootprint->GetValue().IsEmpty() )
2065 {
2066 textItem = &aFootprint->Value();
2067
2068 textItem->SetText( aFootprint->GetFPID().GetLibItemName() );
2069 }
2070 else
2071 {
2072 textItem = new PCB_TEXT( aFootprint );
2073 aFootprint->Add( textItem );
2074
2075 textItem->SetText( interpretText( t.text ) );
2076 }
2077
2078 VECTOR2I pos( kicad_x( t.x ), kicad_y( t.y ) );
2079
2080 textItem->SetPosition( pos );
2081 textItem->SetLayer( layer );
2082
2083 double ratio = t.ratio ? *t.ratio : 8; // DTD says 8 is default
2084 int textThickness = KiROUND( t.size.ToPcbUnits() * ratio / 100 );
2085
2086 textItem->SetTextThickness( textThickness );
2087 textItem->SetTextSize( kicad_fontsize( t.size, textThickness ) );
2088 textItem->SetKeepUpright( false );
2089
2090 int align = t.align ? *t.align : ETEXT::BOTTOM_LEFT; // bottom-left is eagle default
2091
2092 // An eagle package is never rotated, the DTD does not allow it.
2093 // angle -= aFootprint->GetOrienation();
2094
2095 if( t.rot )
2096 {
2097 int sign = t.rot->mirror ? -1 : 1;
2098 textItem->SetMirrored( t.rot->mirror );
2099
2100 double degrees = t.rot->degrees;
2101
2102 if( degrees == 90 || t.rot->spin )
2103 {
2104 textItem->SetTextAngle( EDA_ANGLE( sign * degrees, DEGREES_T ) );
2105 }
2106 else if( degrees == 180 )
2107 {
2108 align = ETEXT::TOP_RIGHT;
2109 }
2110 else if( degrees == 270 )
2111 {
2112 align = ETEXT::TOP_RIGHT;
2113 textItem->SetTextAngle( EDA_ANGLE( sign * 90, DEGREES_T ) );
2114 }
2115 }
2116
2117 switch( align )
2118 {
2119 case ETEXT::CENTER:
2122 break;
2123
2124 case ETEXT::CENTER_LEFT:
2127 break;
2128
2132 break;
2133
2134 case ETEXT::TOP_CENTER:
2137 break;
2138
2139 case ETEXT::TOP_LEFT:
2142 break;
2143
2144 case ETEXT::TOP_RIGHT:
2147 break;
2148
2152 break;
2153
2154 case ETEXT::BOTTOM_LEFT:
2157 break;
2158
2162 break;
2163 }
2164}
2165
2166
2167void PCB_IO_EAGLE::packageRectangle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2168{
2169 ERECT r( aTree );
2170
2173 {
2174 ZONE* zone = new ZONE( aFootprint );
2175 aFootprint->Add( zone, ADD_MODE::APPEND );
2176
2177 setKeepoutSettingsToZone( zone, r.layer );
2178
2179 const int outlineIdx = -1; // this is the id of the copper zone main outline
2180 zone->AppendCorner( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y1 ) ), outlineIdx );
2181 zone->AppendCorner( VECTOR2I( kicad_x( r.x2 ), kicad_y( r.y1 ) ), outlineIdx );
2182 zone->AppendCorner( VECTOR2I( kicad_x( r.x2 ), kicad_y( r.y2 ) ), outlineIdx );
2183 zone->AppendCorner( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y2 ) ), outlineIdx );
2184
2185 if( r.rot )
2186 {
2187 VECTOR2I center( ( kicad_x( r.x1 ) + kicad_x( r.x2 ) ) / 2,
2188 ( kicad_y( r.y1 ) + kicad_y( r.y2 ) ) / 2 );
2189 zone->Rotate( center, EDA_ANGLE( r.rot->degrees, DEGREES_T ) );
2190 }
2191
2192 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2194 }
2195 else
2196 {
2197 PCB_LAYER_ID layer = kicad_layer( r.layer );
2198
2199 if( layer == UNDEFINED_LAYER )
2200 {
2201 wxLogMessage( wxString::Format( _( "Ignoring a rectangle since Eagle layer '%s' (%d) "
2202 "was not mapped" ),
2203 eagle_layer_name( r.layer ), r.layer ) );
2204 return;
2205 }
2206
2207 PCB_SHAPE* dwg = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
2208
2209 aFootprint->Add( dwg );
2210
2211 dwg->SetLayer( layer );
2212 dwg->SetStroke( STROKE_PARAMS( 0 ) );
2213 dwg->SetFilled( true );
2214
2215 std::vector<VECTOR2I> pts;
2216
2217 VECTOR2I start( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y1 ) ) );
2218 VECTOR2I end( VECTOR2I( kicad_x( r.x1 ), kicad_y( r.y2 ) ) );
2219
2220 pts.push_back( start );
2221 pts.emplace_back( kicad_x( r.x2 ), kicad_y( r.y1 ) );
2222 pts.emplace_back( kicad_x( r.x2 ), kicad_y( r.y2 ) );
2223 pts.push_back( end );
2224
2225 dwg->SetPolyPoints( pts );
2226
2227 if( r.rot )
2228 dwg->Rotate( dwg->GetCenter(), EDA_ANGLE( r.rot->degrees, DEGREES_T ) );
2229
2230 dwg->Rotate( { 0, 0 }, aFootprint->GetOrientation() );
2231 dwg->Move( aFootprint->GetPosition() );
2232 }
2233}
2234
2235
2236void PCB_IO_EAGLE::packagePolygon( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2237{
2238 EPOLYGON p( aTree );
2239
2240 std::vector<VECTOR2I> pts;
2241
2242 // Get the first vertex and iterate
2243 wxXmlNode* vertex = aTree->GetChildren();
2244 std::vector<EVERTEX> vertices;
2245
2246 // Create a circular vector of vertices
2247 // The "curve" parameter indicates a curve from the current
2248 // to the next vertex, so we keep the first at the end as well
2249 // to allow the curve to link back
2250 while( vertex )
2251 {
2252 if( vertex->GetName() == wxT( "vertex" ) )
2253 vertices.emplace_back( vertex );
2254
2255 vertex = vertex->GetNext();
2256 }
2257
2258 vertices.push_back( vertices[0] );
2259
2260 for( size_t i = 0; i < vertices.size() - 1; i++ )
2261 {
2262 EVERTEX v1 = vertices[i];
2263
2264 // Append the corner
2265 pts.emplace_back( kicad_x( v1.x ), kicad_y( v1.y ) );
2266
2267 if( v1.curve )
2268 {
2269 EVERTEX v2 = vertices[i + 1];
2270 VECTOR2I center =
2271 ConvertArcCenter( VECTOR2I( kicad_x( v1.x ), kicad_y( v1.y ) ),
2272 VECTOR2I( kicad_x( v2.x ), kicad_y( v2.y ) ), *v1.curve );
2273 double angle = DEG2RAD( *v1.curve );
2274 double end_angle = atan2( kicad_y( v2.y ) - center.y, kicad_x( v2.x ) - center.x );
2275 double radius = sqrt( pow( center.x - kicad_x( v1.x ), 2 )
2276 + pow( center.y - kicad_y( v1.y ), 2 ) );
2277
2278 // Don't allow a zero-radius curve
2279 if( KiROUND( radius ) == 0 )
2280 radius = 1.0;
2281
2282 int segCount = GetArcToSegmentCount( KiROUND( radius ), ARC_HIGH_DEF,
2283 EDA_ANGLE( *v1.curve, DEGREES_T ) );
2284 double delta = angle / segCount;
2285
2286 for( double a = end_angle + angle; fabs( a - end_angle ) > fabs( delta ); a -= delta )
2287 {
2288 pts.push_back(
2289 VECTOR2I( KiROUND( radius * cos( a ) ), KiROUND( radius * sin( a ) ) )
2290 + center );
2291 }
2292 }
2293 }
2294
2295 PCB_LAYER_ID layer = kicad_layer( p.layer );
2296
2297 if( ( p.pour == EPOLYGON::CUTOUT && layer != UNDEFINED_LAYER )
2301 {
2302 ZONE* zone = new ZONE( aFootprint );
2303 aFootprint->Add( zone, ADD_MODE::APPEND );
2304
2305 setKeepoutSettingsToZone( zone, p.layer );
2306
2307 SHAPE_LINE_CHAIN outline( pts );
2308 outline.SetClosed( true );
2309 zone->Outline()->AddOutline( outline );
2310
2311 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2313 }
2314 else
2315 {
2316 if( layer == UNDEFINED_LAYER )
2317 {
2318 wxLogMessage( wxString::Format( _( "Ignoring a polygon since Eagle layer '%s' (%d) "
2319 "was not mapped" ),
2320 eagle_layer_name( p.layer ), p.layer ) );
2321 return;
2322 }
2323
2324 PCB_SHAPE* dwg = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
2325
2326 aFootprint->Add( dwg );
2327
2328 dwg->SetStroke( STROKE_PARAMS( 0 ) );
2329 dwg->SetFilled( true );
2330 dwg->SetLayer( layer );
2331
2332 dwg->SetPolyPoints( pts );
2333 dwg->Rotate( { 0, 0 }, aFootprint->GetOrientation() );
2334 dwg->Move( aFootprint->GetPosition() );
2335 dwg->GetPolyShape().Inflate( p.width.ToPcbUnits() / 2,
2336 CORNER_STRATEGY::ALLOW_ACUTE_CORNERS, ARC_HIGH_DEF );
2337 }
2338}
2339
2340
2341void PCB_IO_EAGLE::packageCircle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2342{
2343 ECIRCLE e( aTree );
2344
2345 int width = e.width.ToPcbUnits();
2346 int radius = e.radius.ToPcbUnits();
2347
2351 {
2352 ZONE* zone = new ZONE( aFootprint );
2353 aFootprint->Add( zone, ADD_MODE::APPEND );
2354
2355 setKeepoutSettingsToZone( zone, e.layer );
2356
2357 // approximate circle as polygon
2358 VECTOR2I center( kicad_x( e.x ), kicad_y( e.y ) );
2359 int outlineRadius = radius + ( width / 2 );
2360 int segsInCircle = GetArcToSegmentCount( outlineRadius, ARC_HIGH_DEF, FULL_CIRCLE );
2361 EDA_ANGLE delta = ANGLE_360 / segsInCircle;
2362
2363 for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
2364 {
2365 VECTOR2I rotatedPoint( outlineRadius, 0 );
2366 RotatePoint( rotatedPoint, angle );
2367 zone->AppendCorner( center + rotatedPoint, -1 );
2368 }
2369
2370 if( width > 0 )
2371 {
2372 zone->NewHole();
2373 int innerRadius = radius - ( width / 2 );
2374 segsInCircle = GetArcToSegmentCount( innerRadius, ARC_HIGH_DEF, FULL_CIRCLE );
2375 delta = ANGLE_360 / segsInCircle;
2376
2377 for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
2378 {
2379 VECTOR2I rotatedPoint( innerRadius, 0 );
2380 RotatePoint( rotatedPoint, angle );
2381 zone->AppendCorner( center + rotatedPoint, 0 );
2382 }
2383 }
2384
2385 zone->SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE,
2387 }
2388 else
2389 {
2390 PCB_LAYER_ID layer = kicad_layer( e.layer );
2391
2392 if( layer == UNDEFINED_LAYER )
2393 {
2394 wxLogMessage( wxString::Format( _( "Ignoring a circle since Eagle layer '%s' (%d) "
2395 "was not mapped" ),
2396 eagle_layer_name( e.layer ), e.layer ) );
2397 return;
2398 }
2399
2400 PCB_SHAPE* gr = new PCB_SHAPE( aFootprint, SHAPE_T::CIRCLE );
2401
2402 // width == 0 means filled circle
2403 if( width <= 0 )
2404 {
2405 width = radius;
2406 radius = radius / 2;
2407 gr->SetFilled( true );
2408 }
2409
2410 aFootprint->Add( gr );
2411 gr->SetStroke( STROKE_PARAMS( width, LINE_STYLE::SOLID ) );
2412
2413 switch( (int) layer )
2414 {
2415 case UNDEFINED_LAYER:
2416 layer = Cmts_User;
2417 break;
2418 default:
2419 break;
2420 }
2421
2422 gr->SetLayer( layer );
2423 gr->SetStart( VECTOR2I( kicad_x( e.x ), kicad_y( e.y ) ) );
2424 gr->SetEnd( VECTOR2I( kicad_x( e.x ) + radius, kicad_y( e.y ) ) );
2425 gr->Rotate( { 0, 0 }, aFootprint->GetOrientation() );
2426 gr->Move( aFootprint->GetPosition() );
2427 }
2428}
2429
2430
2431void PCB_IO_EAGLE::packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aCenter ) const
2432{
2433 EHOLE e( aTree );
2434
2435 if( e.drill.value == 0 )
2436 return;
2437
2438 // we add a PAD_ATTRIB::NPTH pad to this footprint.
2439 PAD* pad = new PAD( aFootprint );
2440 aFootprint->Add( pad );
2441
2442 pad->SetShape( PAD_SHAPE::CIRCLE );
2443 pad->SetAttribute( PAD_ATTRIB::NPTH );
2444
2445 // Mechanical purpose only:
2446 // no offset, no net name, no pad name allowed
2447 // pad->SetOffset( VECTOR2I( 0, 0 ) );
2448 // pad->SetNumber( wxEmptyString );
2449
2450 VECTOR2I padpos( kicad_x( e.x ), kicad_y( e.y ) );
2451
2452 if( aCenter )
2453 {
2454 aFootprint->SetPosition( padpos );
2455 pad->SetPosition( padpos );
2456 }
2457 else
2458 {
2459 pad->SetPosition( padpos + aFootprint->GetPosition() );
2460 }
2461
2462 VECTOR2I sz( e.drill.ToPcbUnits(), e.drill.ToPcbUnits() );
2463
2464 pad->SetDrillSize( sz );
2465 pad->SetSize( sz );
2466
2467 pad->SetLayerSet( LSET::AllCuMask().set( B_Mask ).set( F_Mask ) );
2468}
2469
2470
2471void PCB_IO_EAGLE::packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const
2472{
2473 ESMD e( aTree );
2474 PCB_LAYER_ID layer = kicad_layer( e.layer );
2475
2476 if( !IsCopperLayer( layer ) || e.dx.value == 0 || e.dy.value == 0 )
2477 return;
2478
2479 PAD* pad = new PAD( aFootprint );
2480 aFootprint->Add( pad );
2481 transferPad( e, pad );
2482
2483 pad->SetShape( PAD_SHAPE::RECTANGLE );
2484 pad->SetAttribute( PAD_ATTRIB::SMD );
2485
2486 VECTOR2I padSize( e.dx.ToPcbUnits(), e.dy.ToPcbUnits() );
2487 pad->SetSize( padSize );
2488 pad->SetLayer( layer );
2489
2490 const LSET front( { F_Cu, F_Paste, F_Mask } );
2491 const LSET back( { B_Cu, B_Paste, B_Mask } );
2492
2493 if( layer == F_Cu )
2494 pad->SetLayerSet( front );
2495 else if( layer == B_Cu )
2496 pad->SetLayerSet( back );
2497
2498 int minPadSize = std::min( padSize.x, padSize.y );
2499
2500 // Rounded rectangle pads
2501 int roundRadius =
2502 eagleClamp( m_rules->srMinRoundness * 2, (int) ( minPadSize * m_rules->srRoundness ),
2503 m_rules->srMaxRoundness * 2 );
2504
2505 if( e.roundness || roundRadius > 0 )
2506 {
2507 double roundRatio = (double) roundRadius / minPadSize / 2.0;
2508
2509 // Eagle uses a different definition of roundness, hence division by 200
2510 if( e.roundness )
2511 roundRatio = std::fmax( *e.roundness / 200.0, roundRatio );
2512
2513 pad->SetShape( PAD_SHAPE::ROUNDRECT );
2514 pad->SetRoundRectRadiusRatio( roundRatio );
2515 }
2516
2517 if( e.rot )
2518 pad->SetOrientation( EDA_ANGLE( e.rot->degrees, DEGREES_T ) );
2519
2520 // Eagle spokes are always '+'
2521 pad->SetThermalSpokeAngle( ANGLE_0 );
2522
2523 pad->SetLocalSolderPasteMargin( -eagleClamp( m_rules->mlMinCreamFrame,
2524 (int) ( m_rules->mvCreamFrame * minPadSize ),
2525 m_rules->mlMaxCreamFrame ) );
2526
2527 // Solder mask
2528 if( e.stop && *e.stop == false ) // enabled by default
2529 {
2530 if( layer == F_Cu )
2531 pad->SetLayerSet( pad->GetLayerSet().set( F_Mask, false ) );
2532 else if( layer == B_Cu )
2533 pad->SetLayerSet( pad->GetLayerSet().set( B_Mask, false ) );
2534 }
2535
2536 // Solder paste (only for SMD pads)
2537 if( e.cream && *e.cream == false ) // enabled by default
2538 {
2539 if( layer == F_Cu )
2540 pad->SetLayerSet( pad->GetLayerSet().set( F_Paste, false ) );
2541 else if( layer == B_Cu )
2542 pad->SetLayerSet( pad->GetLayerSet().set( B_Paste, false ) );
2543 }
2544}
2545
2546
2547void PCB_IO_EAGLE::transferPad( const EPAD_COMMON& aEaglePad, PAD* aPad ) const
2548{
2549 aPad->SetNumber( aEaglePad.name );
2550
2551 VECTOR2I padPos( kicad_x( aEaglePad.x ), kicad_y( aEaglePad.y ) );
2552
2553 // Solder mask
2554 const VECTOR2I& padSize( aPad->GetSize() );
2555
2557 eagleClamp( m_rules->mlMinStopFrame,
2558 (int) ( m_rules->mvStopFrame * std::min( padSize.x, padSize.y ) ),
2559 m_rules->mlMaxStopFrame ) );
2560
2561 // Solid connection to copper zones
2562 if( aEaglePad.thermals && !*aEaglePad.thermals )
2563 aPad->SetLocalZoneConnection( ZONE_CONNECTION::FULL );
2564
2565 FOOTPRINT* footprint = aPad->GetParentFootprint();
2566 wxCHECK( footprint, /* void */ );
2567 RotatePoint( padPos, footprint->GetOrientation() );
2568 aPad->SetPosition( padPos + footprint->GetPosition() );
2569}
2570
2571
2573{
2574 for( const auto& [ name, footprint ] : m_templates )
2575 {
2576 footprint->SetParent( nullptr );
2577 delete footprint;
2578 }
2579
2580 m_templates.clear();
2581}
2582
2583
2584void PCB_IO_EAGLE::loadClasses( wxXmlNode* aClasses )
2585{
2586 // Eagle board DTD defines the "classes" element as 0 or 1.
2587 if( !aClasses )
2588 return;
2589
2590 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2591
2592 m_xpath->push( "classes.class", "number" );
2593
2594 std::vector<ECLASS> eClasses;
2595 wxXmlNode* classNode = aClasses->GetChildren();
2596
2597 while( classNode )
2598 {
2599 checkpoint();
2600
2601 ECLASS eClass( classNode );
2602 std::shared_ptr<NETCLASS> netclass;
2603
2604 if( eClass.name.CmpNoCase( wxT( "default" ) ) == 0 )
2605 {
2606 netclass = bds.m_NetSettings->m_DefaultNetClass;
2607 }
2608 else
2609 {
2610 netclass.reset( new NETCLASS( eClass.name ) );
2611 bds.m_NetSettings->m_NetClasses[ eClass.name ] = netclass;
2612 }
2613
2614 netclass->SetTrackWidth( INT_MAX );
2615 netclass->SetViaDiameter( INT_MAX );
2616 netclass->SetViaDrill( INT_MAX );
2617
2618 eClasses.emplace_back( eClass );
2619 m_classMap[ eClass.number ] = netclass;
2620
2621 // Get next class
2622 classNode = classNode->GetNext();
2623 }
2624
2625 m_customRules = wxT( "(version 1)" );
2626
2627 for( ECLASS& eClass : eClasses )
2628 {
2629 for( std::pair<const wxString&, ECOORD> entry : eClass.clearanceMap )
2630 {
2631 if( m_classMap[ entry.first ] != nullptr )
2632 {
2633 wxString rule;
2634 rule.Printf( wxT( "(rule \"class %s:%s\"\n"
2635 " (condition \"A.NetClass == '%s' && B.NetClass == '%s'\")\n"
2636 " (constraint clearance (min %smm)))\n" ),
2637 eClass.number,
2638 entry.first,
2639 eClass.name,
2640 m_classMap[ entry.first ]->GetName(),
2641 EDA_UNIT_UTILS::UI::StringFromValue( pcbIUScale, EDA_UNITS::MILLIMETRES, entry.second.ToPcbUnits() ) );
2642
2643 m_customRules += wxT( "\n" ) + rule;
2644 }
2645 }
2646 }
2647
2648 m_xpath->pop(); // "classes.class"
2649}
2650
2651
2652void PCB_IO_EAGLE::loadSignals( wxXmlNode* aSignals )
2653{
2654 // Eagle board DTD defines the "signals" element as 0 or 1.
2655 if( !aSignals )
2656 return;
2657
2658 ZONES zones; // per net
2659 int netCode = 1;
2660
2661 m_xpath->push( "signals.signal", "name" );
2662
2663 // Get the first signal and iterate
2664 wxXmlNode* net = aSignals->GetChildren();
2665
2666 while( net )
2667 {
2668 checkpoint();
2669
2670 bool sawPad = false;
2671
2672 zones.clear();
2673
2674 const wxString& netName = escapeName( net->GetAttribute( "name" ) );
2675 NETINFO_ITEM* netInfo = new NETINFO_ITEM( m_board, netName, netCode );
2676 std::shared_ptr<NETCLASS> netclass;
2677
2678 if( net->HasAttribute( "class" ) )
2679 {
2680 auto netclassIt = m_classMap.find( net->GetAttribute( "class" ) );
2681
2682 if( netclassIt != m_classMap.end() )
2683 {
2684 m_board->GetDesignSettings().m_NetSettings->m_NetClassPatternAssignments.push_back(
2685 { std::make_unique<EDA_COMBINED_MATCHER>( netName, CTX_NETCLASS ),
2686 netclassIt->second->GetName() } );
2687
2688 netInfo->SetNetClass( netclassIt->second );
2689 netclass = netclassIt->second;
2690 }
2691 }
2692
2693 m_board->Add( netInfo );
2694
2695 m_xpath->Value( netName.c_str() );
2696
2697 // Get the first net item and iterate
2698 wxXmlNode* netItem = net->GetChildren();
2699
2700 // (contactref | polygon | wire | via)*
2701 while( netItem )
2702 {
2703 const wxString& itemName = netItem->GetName();
2704
2705 if( itemName == wxT( "wire" ) )
2706 {
2707 m_xpath->push( "wire" );
2708
2709 EWIRE w( netItem );
2710 PCB_LAYER_ID layer = kicad_layer( w.layer );
2711
2712 if( IsCopperLayer( layer ) )
2713 {
2714 VECTOR2I start( kicad_x( w.x1 ), kicad_y( w.y1 ) );
2715 VECTOR2I end( kicad_x( w.x2 ), kicad_y( w.y2 ) );
2716
2717 int width = w.width.ToPcbUnits();
2718
2719 if( width < m_min_trace )
2720 m_min_trace = width;
2721
2722 if( netclass && width < netclass->GetTrackWidth() )
2723 netclass->SetTrackWidth( width );
2724
2725 if( w.curve )
2726 {
2727 VECTOR2I center = ConvertArcCenter( start, end, *w.curve );
2728 double radius = sqrt( pow( center.x - kicad_x( w.x1 ), 2 ) +
2729 pow( center.y - kicad_y( w.y1 ), 2 ) );
2730 VECTOR2I mid = CalcArcMid( start, end, center, true );
2731 VECTOR2I otherMid = CalcArcMid( start, end, center, false );
2732
2733 double radiusA = ( mid - center ).EuclideanNorm();
2734 double radiusB = ( otherMid - center ).EuclideanNorm();
2735
2736 if( abs( radiusA - radius ) > abs( radiusB - radius ) )
2737 std::swap( mid, otherMid );
2738
2739 PCB_ARC* arc = new PCB_ARC( m_board );
2740
2741 arc->SetPosition( start );
2742 arc->SetMid( mid );
2743 arc->SetEnd( end );
2744 arc->SetWidth( width );
2745 arc->SetLayer( layer );
2746 arc->SetNetCode( netCode );
2747
2748 m_board->Add( arc );
2749 }
2750 else
2751 {
2752 PCB_TRACK* track = new PCB_TRACK( m_board );
2753
2754 track->SetPosition( start );
2755 track->SetEnd( VECTOR2I( kicad_x( w.x2 ), kicad_y( w.y2 ) ) );
2756 track->SetWidth( width );
2757 track->SetLayer( layer );
2758 track->SetNetCode( netCode );
2759
2760 m_board->Add( track );
2761 }
2762 }
2763 else
2764 {
2765 // put non copper wires where the sun don't shine.
2766 }
2767
2768 m_xpath->pop();
2769 }
2770 else if( itemName == wxT( "via" ) )
2771 {
2772 m_xpath->push( "via" );
2773 EVIA v( netItem );
2774
2776 std::swap( v.layer_front_most, v.layer_back_most );
2777
2778 PCB_LAYER_ID layer_front_most = kicad_layer( v.layer_front_most );
2779 PCB_LAYER_ID layer_back_most = kicad_layer( v.layer_back_most );
2780
2781 if( IsCopperLayer( layer_front_most ) && IsCopperLayer( layer_back_most )
2782 && layer_front_most != layer_back_most )
2783 {
2784 int kidiam;
2785 int drillz = v.drill.ToPcbUnits();
2786 PCB_VIA* via = new PCB_VIA( m_board );
2787 m_board->Add( via );
2788
2789 if( v.diam )
2790 {
2791 kidiam = v.diam->ToPcbUnits();
2792 via->SetWidth( kidiam );
2793 }
2794 else
2795 {
2796 double annulus = drillz * m_rules->rvViaOuter; // eagle "restring"
2797 annulus = eagleClamp( m_rules->rlMinViaOuter, annulus,
2798 m_rules->rlMaxViaOuter );
2799 kidiam = KiROUND( drillz + 2 * annulus );
2800 via->SetWidth( kidiam );
2801 }
2802
2803 via->SetDrill( drillz );
2804
2805 // make sure the via diameter respects the restring rules
2806
2807 if( !v.diam || via->GetWidth() <= via->GetDrill() )
2808 {
2809 double annulus =
2810 eagleClamp( m_rules->rlMinViaOuter,
2811 (double) ( via->GetWidth() / 2 - via->GetDrill() ),
2812 m_rules->rlMaxViaOuter );
2813 via->SetWidth( drillz + 2 * annulus );
2814 }
2815
2816 if( kidiam < m_min_via )
2817 m_min_via = kidiam;
2818
2819 if( netclass && kidiam < netclass->GetViaDiameter() )
2820 netclass->SetViaDiameter( kidiam );
2821
2822 if( drillz < m_min_hole )
2823 m_min_hole = drillz;
2824
2825 if( netclass && drillz < netclass->GetViaDrill() )
2826 netclass->SetViaDrill( drillz );
2827
2828 if( ( kidiam - drillz ) / 2 < m_min_annulus )
2829 m_min_annulus = ( kidiam - drillz ) / 2;
2830
2831 if( layer_front_most == F_Cu && layer_back_most == B_Cu )
2832 {
2833 via->SetViaType( VIATYPE::THROUGH );
2834 }
2838 else if( v.layer_back_most - v.layer_front_most == 1 )
2839 {
2840 via->SetViaType( VIATYPE::MICROVIA );
2841 }
2842 else
2843 {
2844 via->SetViaType( VIATYPE::BLIND_BURIED );
2845 }
2846
2847 VECTOR2I pos( kicad_x( v.x ), kicad_y( v.y ) );
2848
2849 via->SetLayerPair( layer_front_most, layer_back_most );
2850 via->SetPosition( pos );
2851 via->SetEnd( pos );
2852
2853 via->SetNetCode( netCode );
2854 }
2855
2856 m_xpath->pop();
2857 }
2858
2859 else if( itemName == wxT( "contactref" ) )
2860 {
2861 m_xpath->push( "contactref" );
2862 // <contactref element="RN1" pad="7"/>
2863
2864 const wxString& reference = netItem->GetAttribute( "element" );
2865 const wxString& pad = netItem->GetAttribute( "pad" );
2866 wxString key = makeKey( reference, pad ) ;
2867
2868 m_pads_to_nets[ key ] = ENET( netCode, netName );
2869
2870 m_xpath->pop();
2871
2872 sawPad = true;
2873 }
2874
2875 else if( itemName == wxT( "polygon" ) )
2876 {
2877 m_xpath->push( "polygon" );
2878 auto* zone = loadPolygon( netItem );
2879
2880 if( zone )
2881 {
2882 zones.push_back( zone );
2883
2884 if( !zone->GetIsRuleArea() )
2885 zone->SetNetCode( netCode );
2886 }
2887
2888 m_xpath->pop(); // "polygon"
2889 }
2890
2891 netItem = netItem->GetNext();
2892 }
2893
2894 if( zones.size() && !sawPad )
2895 {
2896 // KiCad does not support an unconnected zone with its own non-zero netcode,
2897 // but only when assigned netcode = 0 w/o a name...
2898 for( ZONE* zone : zones )
2899 zone->SetNetCode( NETINFO_LIST::UNCONNECTED );
2900
2901 // therefore omit this signal/net.
2902 }
2903 else
2904 {
2905 netCode++;
2906 }
2907
2908 // Get next signal
2909 net = net->GetNext();
2910 }
2911
2912 m_xpath->pop(); // "signals.signal"
2913}
2914
2915
2916std::map<wxString, PCB_LAYER_ID> PCB_IO_EAGLE::DefaultLayerMappingCallback(
2917 const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector )
2918{
2919 std::map<wxString, PCB_LAYER_ID> layer_map;
2920
2921 for ( const INPUT_LAYER_DESC& layer : aInputLayerDescriptionVector )
2922 {
2923 PCB_LAYER_ID layerId = std::get<0>( defaultKicadLayer( eagle_layer_id( layer.Name ) ) );
2924 layer_map.emplace( layer.Name, layerId );
2925 }
2926
2927 return layer_map;
2928}
2929
2930
2931void PCB_IO_EAGLE::mapEagleLayersToKicad( bool aIsLibraryCache )
2932{
2933 std::vector<INPUT_LAYER_DESC> inputDescs;
2934
2935 for ( const std::pair<const int, ELAYER>& layerPair : m_eagleLayers )
2936 {
2937 const ELAYER& eLayer = layerPair.second;
2938
2939 INPUT_LAYER_DESC layerDesc;
2940 std::tie( layerDesc.AutoMapLayer, layerDesc.PermittedLayers, layerDesc.Required ) =
2941 defaultKicadLayer( eLayer.number, aIsLibraryCache );
2942
2943 if( layerDesc.AutoMapLayer == UNDEFINED_LAYER )
2944 continue; // Ignore unused copper layers
2945
2946 layerDesc.Name = eLayer.name;
2947
2948 inputDescs.push_back( layerDesc );
2949 }
2950
2951 if( m_progressReporter && dynamic_cast<wxWindow*>( m_progressReporter ) )
2952 dynamic_cast<wxWindow*>( m_progressReporter )->Hide();
2953
2954 m_layer_map = m_layer_mapping_handler( inputDescs );
2955
2956 if( m_progressReporter && dynamic_cast<wxWindow*>( m_progressReporter ))
2957 dynamic_cast<wxWindow*>( m_progressReporter )->Show();
2958}
2959
2960
2962{
2963 auto result = m_layer_map.find( eagle_layer_name( aEagleLayer ) );
2964 return result == m_layer_map.end() ? UNDEFINED_LAYER : result->second;
2965}
2966
2967
2968std::tuple<PCB_LAYER_ID, LSET, bool> PCB_IO_EAGLE::defaultKicadLayer( int aEagleLayer,
2969 bool aIsLibraryCache ) const
2970{
2971 // eagle copper layer:
2972 if( aEagleLayer >= 1 && aEagleLayer < int( arrayDim( m_cu_map ) ) )
2973 {
2974 LSET copperLayers;
2975
2976 for( int copperLayer : m_cu_map )
2977 {
2978 if( copperLayer >= 0 )
2979 copperLayers[copperLayer] = true;
2980 }
2981
2982 return { PCB_LAYER_ID( m_cu_map[aEagleLayer] ), copperLayers, true };
2983 }
2984
2985 int kiLayer = UNSELECTED_LAYER;
2986 bool required = false;
2987 LSET permittedLayers;
2988
2989 permittedLayers.set();
2990
2991 // translate non-copper eagle layer to pcbnew layer
2992 switch( aEagleLayer )
2993 {
2994 // Eagle says "Dimension" layer, but it's for board perimeter
2996 kiLayer = Edge_Cuts;
2997 required = true;
2998 permittedLayers = LSET( Edge_Cuts );
2999 break;
3000
3002 kiLayer = F_SilkS;
3003 break;
3005 kiLayer = B_SilkS;
3006 break;
3008 kiLayer = F_SilkS;
3009 break;
3011 kiLayer = B_SilkS;
3012 break;
3014 kiLayer = F_Fab;
3015 break;
3017 kiLayer = B_Fab;
3018 break;
3019 case EAGLE_LAYER::TSTOP:
3020 kiLayer = F_Mask;
3021 break;
3022 case EAGLE_LAYER::BSTOP:
3023 kiLayer = B_Mask;
3024 break;
3026 kiLayer = F_Paste;
3027 break;
3029 kiLayer = B_Paste;
3030 break;
3032 kiLayer = F_Mask;
3033 break;
3035 kiLayer = B_Mask;
3036 break;
3037 case EAGLE_LAYER::TGLUE:
3038 kiLayer = F_Adhes;
3039 break;
3040 case EAGLE_LAYER::BGLUE:
3041 kiLayer = B_Adhes;
3042 break;
3044 kiLayer = Cmts_User;
3045 break;
3047 kiLayer = Cmts_User;
3048 break;
3050 kiLayer = Cmts_User;
3051 break;
3052
3053 // Packages show the future chip pins on SMD parts using layer 51.
3054 // This is an area slightly smaller than the PAD/SMD copper area.
3055 // Carry those visual aids into the FOOTPRINT on the fabrication layer,
3056 // not silkscreen. This is perhaps not perfect, but there is not a lot
3057 // of other suitable paired layers
3058 case EAGLE_LAYER::TDOCU:
3059 kiLayer = F_Fab;
3060 break;
3061 case EAGLE_LAYER::BDOCU:
3062 kiLayer = B_Fab;
3063 break;
3064
3065 // these layers are defined as user layers. put them on ECO layers
3067 kiLayer = Eco1_User;
3068 break;
3070 kiLayer = Eco2_User;
3071 break;
3072
3073 // these will also appear in the ratsnest, so there's no need for a warning
3075 kiLayer = Dwgs_User;
3076 break;
3077
3079 kiLayer = F_CrtYd;
3080 break;
3082 kiLayer = B_CrtYd;
3083 break;
3084
3086 case EAGLE_LAYER::TTEST:
3087 case EAGLE_LAYER::BTEST:
3088 case EAGLE_LAYER::HOLES:
3089 default:
3090 if( aIsLibraryCache )
3091 kiLayer = UNDEFINED_LAYER;
3092 else
3093 kiLayer = UNSELECTED_LAYER;
3094
3095 break;
3096 }
3097
3098 return { PCB_LAYER_ID( kiLayer ), permittedLayers, required };
3099}
3100
3101
3102const wxString& PCB_IO_EAGLE::eagle_layer_name( int aLayer ) const
3103{
3104 static const wxString unknown( "unknown" );
3105 auto it = m_eagleLayers.find( aLayer );
3106 return it == m_eagleLayers.end() ? unknown : it->second.name;
3107}
3108
3109
3110int PCB_IO_EAGLE::eagle_layer_id( const wxString& aLayerName ) const
3111{
3112 static const int unknown = -1;
3113 auto it = m_eagleLayersIds.find( aLayerName );
3114 return it == m_eagleLayersIds.end() ? unknown : it->second;
3115}
3116
3117
3119{
3120 if( m_props )
3121 {
3122 UTF8 page_width;
3123 UTF8 page_height;
3124
3125 if( m_props->Value( "page_width", &page_width ) &&
3126 m_props->Value( "page_height", &page_height ) )
3127 {
3128 BOX2I bbbox = m_board->GetBoardEdgesBoundingBox();
3129
3130 int w = atoi( page_width.c_str() );
3131 int h = atoi( page_height.c_str() );
3132
3133 int desired_x = ( w - bbbox.GetWidth() ) / 2;
3134 int desired_y = ( h - bbbox.GetHeight() ) / 2;
3135
3136 m_board->Move( VECTOR2I( desired_x - bbbox.GetX(), desired_y - bbbox.GetY() ) );
3137 }
3138 }
3139}
3140
3141
3142wxDateTime PCB_IO_EAGLE::getModificationTime( const wxString& aPath )
3143{
3144 // File hasn't been loaded yet.
3145 if( aPath.IsEmpty() )
3146 return wxDateTime::Now();
3147
3148 wxFileName fn( aPath );
3149
3150 if( fn.IsFileReadable() )
3151 return fn.GetModificationTime();
3152 else
3153 return wxDateTime( 0.0 );
3154}
3155
3156
3157void PCB_IO_EAGLE::cacheLib( const wxString& aLibPath )
3158{
3160
3161 try
3162 {
3163 wxDateTime modtime = getModificationTime( aLibPath );
3164
3165 // Fixes assertions in wxWidgets debug builds for the wxDateTime object. Refresh the
3166 // cache if either of the wxDateTime objects are invalid or the last file modification
3167 // time differs from the current file modification time.
3168 bool load = !m_mod_time.IsValid() || !modtime.IsValid() || m_mod_time != modtime;
3169
3170 if( aLibPath != m_lib_path || load )
3171 {
3172 wxXmlNode* doc;
3173 LOCALE_IO toggle; // toggles on, then off, the C locale.
3174
3175 deleteTemplates();
3176
3177 // Set this before completion of loading, since we rely on it for
3178 // text of an exception. Delay setting m_mod_time until after successful load
3179 // however.
3180 m_lib_path = aLibPath;
3181
3182 // 8 bit "filename" should be encoded according to disk filename encoding,
3183 // (maybe this is current locale, maybe not, its a filesystem issue),
3184 // and is not necessarily utf8.
3185 string filename = (const char*) aLibPath.char_str( wxConvFile );
3186
3187 // Load the document
3188 wxFileName fn( filename );
3189 wxFFileInputStream stream( fn.GetFullPath() );
3190 wxXmlDocument xmlDocument;
3191
3192 if( !stream.IsOk() || !xmlDocument.Load( stream ) )
3193 {
3194 THROW_IO_ERROR( wxString::Format( _( "Unable to read file '%s'." ),
3195 fn.GetFullPath() ) );
3196 }
3197
3198 doc = xmlDocument.GetRoot();
3199
3200 wxXmlNode* drawing = MapChildren( doc )["drawing"];
3201 NODE_MAP drawingChildren = MapChildren( drawing );
3202
3203 // clear the cu map and then rebuild it.
3204 clear_cu_map();
3205
3206 m_xpath->push( "eagle.drawing.layers" );
3207 wxXmlNode* layers = drawingChildren["layers"];
3208 loadLayerDefs( layers );
3209 mapEagleLayersToKicad( true );
3210 m_xpath->pop();
3211
3212 m_xpath->push( "eagle.drawing.library" );
3213 wxXmlNode* library = drawingChildren["library"];
3214
3215 loadLibrary( library, nullptr );
3216 m_xpath->pop();
3217
3218 m_mod_time = modtime;
3219 }
3220 }
3221 catch(...){}
3222 // TODO: Handle exceptions
3223 // catch( file_parser_error fpe )
3224 // {
3225 // // for xml_parser_error, what() has the line number in it,
3226 // // but no byte offset. That should be an adequate error message.
3227 // THROW_IO_ERROR( fpe.what() );
3228 // }
3229 //
3230 // // Class ptree_error is a base class for xml_parser_error & file_parser_error,
3231 // // so one catch should be OK for all errors.
3232 // catch( ptree_error pte )
3233 // {
3234 // string errmsg = pte.what();
3235 //
3236 // errmsg += " @\n";
3237 // errmsg += m_xpath->Contents();
3238 //
3239 // THROW_IO_ERROR( errmsg );
3240 // }
3241}
3242
3243
3244void PCB_IO_EAGLE::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath,
3245 bool aBestEfforts, const STRING_UTF8_MAP* aProperties )
3246{
3247 wxString errorMsg;
3248
3249 init( aProperties );
3250
3251 try
3252 {
3253 cacheLib( aLibraryPath );
3254 }
3255 catch( const IO_ERROR& ioe )
3256 {
3257 errorMsg = ioe.What();
3258 }
3259
3260 // Some of the files may have been parsed correctly so we want to add the valid files to
3261 // the library.
3262
3263 for( const auto& [ name, footprint ] : m_templates )
3264 aFootprintNames.Add( name );
3265
3266 if( !errorMsg.IsEmpty() && !aBestEfforts )
3267 THROW_IO_ERROR( errorMsg );
3268}
3269
3270
3271FOOTPRINT* PCB_IO_EAGLE::FootprintLoad( const wxString& aLibraryPath,
3272 const wxString& aFootprintName, bool aKeepUUID,
3273 const STRING_UTF8_MAP* aProperties )
3274{
3275 init( aProperties );
3276 cacheLib( aLibraryPath );
3277 auto it = m_templates.find( aFootprintName );
3278
3279 if( it == m_templates.end() )
3280 return nullptr;
3281
3282 // Return a copy of the template
3283 FOOTPRINT* copy = (FOOTPRINT*) it->second->Duplicate();
3284 copy->SetParent( nullptr );
3285 return copy;
3286}
3287
3288
3290{
3291 int minLayerCount = 2;
3292
3293 std::map<wxString, PCB_LAYER_ID>::const_iterator it;
3294
3295 for( it = m_layer_map.begin(); it != m_layer_map.end(); ++it )
3296 {
3297 PCB_LAYER_ID layerId = it->second;
3298
3299 if( IsCopperLayer( layerId ) && layerId != F_Cu && layerId != B_Cu
3300 && ( layerId + 2 ) > minLayerCount )
3301 minLayerCount = layerId + 2;
3302 }
3303
3304 // Ensure the copper layers count is a multiple of 2
3305 // Pcbnew does not like boards with odd layers count
3306 // (these boards cannot exist. they actually have a even layers count)
3307 if( ( minLayerCount % 2 ) != 0 )
3308 minLayerCount++;
3309
3310 return minLayerCount;
3311}
const char * name
Definition: DXF_plotter.cpp:57
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
constexpr int ARC_HIGH_DEF
Definition: base_units.h:120
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
@ LT_SIGNAL
Definition: board.h:154
#define DEFAULT_SILK_LINE_WIDTH
#define DEFAULT_EDGE_WIDTH
#define DEFAULT_LINE_WIDTH
#define DEFAULT_COURTYARD_WIDTH
BASE_SET & set(size_t pos=std::numeric_limits< size_t >::max(), bool value=true)
Definition: base_set.h:61
bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
VECTOR2I GetTextSize(PCB_LAYER_ID aLayer) const
Return the default text size from the layer class for the given layer.
int GetLineThickness(PCB_LAYER_ID aLayer) const
Return the default graphic segment thickness from the layer class for the given layer.
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:276
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Definition: board_item.cpp:47
FOOTPRINT * GetParentFootprint() const
Definition: board_item.cpp:264
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:289
bool m_LegacyDesignSettingsLoaded
True if the legacy board design settings were loaded from a file.
Definition: board.h:372
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: board.cpp:982
void SetFileName(const wxString &aFileName)
Definition: board.h:324
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition: board.cpp:591
bool m_LegacyNetclassesLoaded
True if netclasses were loaded from the file.
Definition: board.h:376
void SetCopperLayerCount(int aCount)
Definition: board.cpp:739
bool SetLayerType(PCB_LAYER_ID aLayer, LAYER_T aLayerType)
Change the type of the layer given by aLayer.
Definition: board.cpp:624
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:874
size_type GetHeight() const
Definition: box2.h:205
size_type GetWidth() const
Definition: box2.h:204
coord_type GetY() const
Definition: box2.h:198
coord_type GetX() const
Definition: box2.h:197
double AsDegrees() const
Definition: eda_angle.h:113
void SetCenter(const VECTOR2I &aCenter)
Definition: eda_shape.cpp:564
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:279
void SetFilled(bool aFlag)
Definition: eda_shape.h:101
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:134
void SetShape(SHAPE_T aShape)
Definition: eda_shape.h:124
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:171
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
Definition: eda_shape.cpp:710
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
Definition: eda_shape.cpp:1236
const EDA_ANGLE & GetTextAngle() const
Definition: eda_text.h:130
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition: eda_text.cpp:373
void SetTextPos(const VECTOR2I &aPoint)
Definition: eda_text.cpp:418
void SetMirrored(bool isMirrored)
Definition: eda_text.cpp:251
void SetVertJustify(GR_TEXT_V_ALIGN_T aType)
Definition: eda_text.cpp:275
virtual void SetVisible(bool aVisible)
Definition: eda_text.cpp:244
void SetTextThickness(int aWidth)
The TextThickness is that set by the user.
Definition: eda_text.cpp:196
bool IsMirrored() const
Definition: eda_text.h:150
void SetKeepUpright(bool aKeepUpright)
Definition: eda_text.cpp:283
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:182
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition: eda_text.cpp:204
VECTOR2I GetTextSize() const
Definition: eda_text.h:218
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition: eda_text.cpp:267
void SetPosition(const VECTOR2I &aPos) override
Definition: footprint.cpp:2343
EDA_ANGLE GetOrientation() const
Definition: footprint.h:216
void SetOrientation(const EDA_ANGLE &aNewAngle)
Definition: footprint.cpp:2415
PCB_FIELD & Value()
read/write accessors:
Definition: footprint.h:627
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: footprint.h:225
PADS & Pads()
Definition: footprint.h:195
const LIB_ID & GetFPID() const
Definition: footprint.h:237
void SetReference(const wxString &aReference)
Definition: footprint.h:597
void SetValue(const wxString &aValue)
Definition: footprint.h:618
PCB_FIELD & Reference()
Definition: footprint.h:628
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: footprint.cpp:978
void Flip(const VECTOR2I &aCentre, bool aFlipLeftRight) override
Flip this object, i.e.
Definition: footprint.cpp:2284
const wxString & GetValue() const
Definition: footprint.h:613
const wxString & GetReference() const
Definition: footprint.h:591
VECTOR2I GetPosition() const override
Definition: footprint.h:213
virtual bool CanReadLibrary(const wxString &aFileName) const
Checks if this IO object can read the specified library file/directory.
Definition: io_base.cpp:69
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:77
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:30
virtual void RegisterCallback(LAYER_MAPPING_HANDLER aLayerMappingHandler)
Register a different handler to be called when mapping of input layers to KiCad layers occurs.
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition: lib_id.cpp:51
const UTF8 & GetLibItemName() const
Definition: lib_id.h:102
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:35
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:732
A collection of nets and the parameters used to route or test these nets.
Definition: netclass.h:44
int GetViaDiameter() const
Definition: netclass.h:77
int GetViaDrill() const
Definition: netclass.h:81
int GetTrackWidth() const
Definition: netclass.h:73
Handle the data for a net.
Definition: netinfo.h:56
void SetNetClass(const std::shared_ptr< NETCLASS > &aNetClass)
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition: netinfo.h:381
T & Get()
Return a reference to the value of the attribute assuming it is available.
Definition: eagle_parser.h:305
Definition: pad.h:54
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition: pad.h:133
void SetLocalSolderMaskMargin(std::optional< int > aMargin)
Definition: pad.h:414
void SetLocalZoneConnection(ZONE_CONNECTION aType)
Definition: pad.h:434
void SetPosition(const VECTOR2I &aPos) override
Definition: pad.h:195
const VECTOR2I & GetSize() const
Definition: pad.h:256
void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_track.h:283
void SetMid(const VECTOR2I &aMid)
Definition: pcb_track.h:280
void SetUnits(EDA_UNITS aUnits)
void SetLineThickness(int aWidth)
virtual void SetEnd(const VECTOR2I &aPoint)
void SetPrecision(DIM_PRECISION aPrecision)
virtual void SetStart(const VECTOR2I &aPoint)
void SetOverrideText(const wxString &aValue)
For better understanding of the points that make a dimension:
void SetHeight(int aHeight)
Set the distance from the feature points to the crossbar line.
A leader is a dimension-like object pointing to a specific point.
A radial dimension indicates either the radius or diameter of an arc or circle.
std::vector< ELAYER > ELAYERS
Definition: pcb_io_eagle.h:326
void packageSMD(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
Handles common pad properties.
void loadPlain(wxXmlNode *aPlain)
int m_min_trace
smallest trace we find on Load(), in BIU.
Definition: pcb_io_eagle.h:358
void orientFPText(FOOTPRINT *aFootprint, const EELEMENT &e, PCB_TEXT *aFPText, const EATTR *aAttr)
VECTOR2I kicad_fontsize(const ECOORD &d, int aTextThickness) const
create a font size (fontz) from an eagle font size scalar and KiCad font thickness
std::map< wxString, PCB_LAYER_ID > DefaultLayerMappingCallback(const std::vector< INPUT_LAYER_DESC > &aInputLayerDescriptionVector)
Return the automapped layers.
bool checkHeader(const wxString &aFileName) const
void loadElements(wxXmlNode *aElements)
FOOTPRINT * makeFootprint(wxXmlNode *aPackage, const wxString &aPkgName)
Create a FOOTPRINT from an Eagle package.
int m_min_hole
smallest diameter hole we find on Load(), in BIU.
Definition: pcb_io_eagle.h:359
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
XPATH * m_xpath
keeps track of what we are working on within XML document during a Load().
Definition: pcb_io_eagle.h:340
unsigned m_totalCount
for progress reporting
Definition: pcb_io_eagle.h:356
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const STRING_UTF8_MAP *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
void cacheLib(const wxString &aLibraryPath)
This PLUGIN only caches one footprint library, this determines which one.
int m_hole_count
generates unique footprint names from eagle "hole"s.
Definition: pcb_io_eagle.h:343
std::map< wxString, FOOTPRINT * > m_templates
is part of a FOOTPRINT factory that operates using copy construction.
Definition: pcb_io_eagle.h:347
std::map< wxString, int > m_eagleLayersIds
Eagle layer ids stored by layer name.
Definition: pcb_io_eagle.h:331
void mapEagleLayersToKicad(bool aIsLibraryCache=false)
Generate mapping between Eagle and KiCad layers.
std::tuple< PCB_LAYER_ID, LSET, bool > defaultKicadLayer(int aEagleLayer, bool aIsLibraryCache=false) const
Get the default KiCad layer corresponding to an Eagle layer of the board, a set of sensible layer map...
void loadLibraries(wxXmlNode *aLibs)
int eagle_layer_id(const wxString &aLayerName) const
Get Eagle layer number by its name.
void loadAllSections(wxXmlNode *aDocument)
bool CanReadFootprint(const wxString &aFileName) const override
Checks if this PCB_IO can read a footprint from specified file or directory.
void packageWire(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
const wxString & eagle_layer_name(int aLayer) const
Get Eagle layer name by its number.
ELAYERS::const_iterator EITER
Definition: pcb_io_eagle.h:327
void packageRectangle(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
int kicad_y(const ECOORD &y) const
Convert an Eagle distance to a KiCad distance.
Definition: pcb_io_eagle.h:200
void loadClasses(wxXmlNode *aClasses)
std::map< int, ELAYER > m_eagleLayers
Eagle layer data stored by layer number.
Definition: pcb_io_eagle.h:330
wxString m_customRules
Definition: pcb_io_eagle.h:337
NET_MAP m_pads_to_nets
net list
Definition: pcb_io_eagle.h:345
void packageText(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
int getMinimumCopperLayerCount() const
Determines the minimum copper layer stackup count that includes all mapped layers.
void packageHole(FOOTPRINT *aFootprint, wxXmlNode *aTree, bool aCenter) const
FOOTPRINT * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, bool aKeepUUID=false, const STRING_UTF8_MAP *aProperties=nullptr) override
Load a footprint having aFootprintName from the aLibraryPath containing a library format that this PC...
void packagePolygon(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
void centerBoard()
move the BOARD into the center of the page
unsigned m_lastProgressCount
Definition: pcb_io_eagle.h:355
int m_cu_map[17]
map eagle to KiCad, cu layers only.
Definition: pcb_io_eagle.h:329
ZONE * loadPolygon(wxXmlNode *aPolyNode)
Load a copper or keepout polygon and adds it to the board.
int m_min_via
smallest via we find on Load(), in BIU.
Definition: pcb_io_eagle.h:360
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
void clear_cu_map()
void packageCircle(FOOTPRINT *aFootprint, wxXmlNode *aTree) const
void loadLibrary(wxXmlNode *aLib, const wxString *aLibName)
Load the Eagle "library" XML element, which can occur either under a "libraries" element (if a *....
void packagePad(FOOTPRINT *aFootprint, wxXmlNode *aTree)
void loadSignals(wxXmlNode *aSignals)
ERULES * m_rules
Eagle design rules.
Definition: pcb_io_eagle.h:339
PCB_LAYER_ID kicad_layer(int aLayer) const
Convert an Eagle layer to a KiCad layer.
void orientFootprintAndText(FOOTPRINT *aFootprint, const EELEMENT &e, const EATTR *aNameAttr, const EATTR *aValueAttr)
static wxDateTime getModificationTime(const wxString &aPath)
get a file's or dir's modification time.
int m_min_annulus
smallest via annulus we find on Load(), in BIU.
Definition: pcb_io_eagle.h:361
void loadLayerDefs(wxXmlNode *aLayers)
void init(const STRING_UTF8_MAP *aProperties)
initialize PLUGIN like a constructor would, and futz with fresh BOARD if needed.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const STRING_UTF8_MAP *aProperties=nullptr, PROJECT *aProject=nullptr) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
void setKeepoutSettingsToZone(ZONE *aZone, int aLayer) const
void transferPad(const EPAD_COMMON &aEaglePad, PAD *aPad) const
Deletes the footprint templates list.
void deleteTemplates()
int kicad_x(const ECOORD &x) const
Definition: pcb_io_eagle.h:201
void loadDesignRules(wxXmlNode *aDesignRules)
PROGRESS_REPORTER * m_progressReporter
optional; may be nullptr
Definition: pcb_io_eagle.h:353
unsigned m_doneCount
Definition: pcb_io_eagle.h:354
A base class that BOARD loading and saving plugins should derive from.
Definition: pcb_io.h:72
BOARD * m_board
The board BOARD being worked on, no ownership here.
Definition: pcb_io.h:343
const STRING_UTF8_MAP * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition: pcb_io.h:346
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PCB_IO can read the specified board file.
Definition: pcb_io.cpp:43
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_shape.h:75
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition: pcb_shape.cpp:526
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: pcb_shape.cpp:317
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition: pcb_shape.cpp:455
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition: pcb_shape.h:86
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition: pcb_text.cpp:393
virtual void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_text.h:87
void SetWidth(int aWidth)
Definition: pcb_track.h:115
void SetEnd(const VECTOR2I &aEnd)
Definition: pcb_track.h:118
void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_track.h:111
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
Container for project specific data.
Definition: project.h:62
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
A name/value tuple with unique names and optional values.
Simple container to manage line stroke parameters.
Definition: stroke_params.h:81
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition: utf8.h:72
const char * c_str() const
Definition: utf8.h:103
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition: vector2d.h:550
T y
Definition: vector3.h:63
T x
Definition: vector3.h:62
static REPORTER & GetInstance()
Definition: reporter.cpp:212
Keep track of what we are working on within a PTREE.
Definition: eagle_parser.h:124
void pop()
Definition: eagle_parser.h:135
void clear()
Definition: eagle_parser.h:133
void push(const char *aPathSegment, const char *aAttribute="")
Definition: eagle_parser.h:128
wxString Contents()
return the contents of the XPATH as a single string
Definition: eagle_parser.h:150
Handle a list of polygons defining a copper zone.
Definition: zone.h:73
void SetDoNotAllowPads(bool aEnable)
Definition: zone.h:722
void SetBorderDisplayStyle(ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle, int aBorderHatchPitch, bool aRebuilBorderdHatch)
Set all hatch parameters for the zone.
Definition: zone.cpp:878
void SetDoNotAllowCopperPour(bool aEnable)
Definition: zone.h:719
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: zone.cpp:263
SHAPE_POLY_SET * Outline()
Definition: zone.h:337
void NewHole()
Create a new hole on the zone; i.e., a new contour on the zone's outline.
Definition: zone.h:589
void SetIsRuleArea(bool aEnable)
Definition: zone.h:718
void SetDoNotAllowTracks(bool aEnable)
Definition: zone.h:721
void Rotate(const VECTOR2I &aCentre, const EDA_ANGLE &aAngle) override
Rotate the outlines.
Definition: zone.cpp:715
void SetDoNotAllowVias(bool aEnable)
Definition: zone.h:720
void SetLayerSet(LSET aLayerSet) override
Definition: zone.cpp:269
void SetDoNotAllowFootprints(bool aEnable)
Definition: zone.h:723
bool AppendCorner(VECTOR2I aPosition, int aHoleIdx, bool aAllowDuplication=false)
Add a new corner to the zone outline (to the main outline or a hole)
Definition: zone.cpp:804
static int GetDefaultHatchPitch()
Definition: zone.cpp:1054
static void SetReporter(REPORTER *aReporter)
Set the reporter to use for reporting font substitution warnings.
Definition: fontconfig.cpp:64
NODE_MAP MapChildren(wxXmlNode *aCurrentNode)
Provide an easy access to the children of an XML node via their names.
wxString escapeName(const wxString &aNetName)
Interprets special characters in Eagle text and converts them to KiCAD notation.
wxString interpretText(const wxString &aText)
Translates Eagle special text reference to a KiCad variable reference.
VECTOR2I ConvertArcCenter(const VECTOR2I &aStart, const VECTOR2I &aEnd, double aAngle)
wxString convertDescription(wxString aDescr)
OPTIONAL_XML_ATTRIBUTE< wxString > opt_wxString
Definition: eagle_parser.h:390
std::unordered_map< wxString, wxXmlNode * > NODE_MAP
Definition: eagle_parser.h:54
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition: eda_angle.h:401
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE FULL_CIRCLE
Definition: eda_angle.h:399
static constexpr EDA_ANGLE ANGLE_360
Definition: eda_angle.h:407
@ CTX_NETCLASS
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
bool IsCopperLayer(int aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:531
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ F_CrtYd
Definition: layer_ids.h:117
@ B_Adhes
Definition: layer_ids.h:97
@ Edge_Cuts
Definition: layer_ids.h:113
@ Dwgs_User
Definition: layer_ids.h:109
@ F_Paste
Definition: layer_ids.h:101
@ Cmts_User
Definition: layer_ids.h:110
@ F_Adhes
Definition: layer_ids.h:98
@ B_Mask
Definition: layer_ids.h:106
@ B_Cu
Definition: layer_ids.h:95
@ Eco1_User
Definition: layer_ids.h:111
@ F_Mask
Definition: layer_ids.h:107
@ B_Paste
Definition: layer_ids.h:100
@ UNSELECTED_LAYER
Definition: layer_ids.h:62
@ F_Fab
Definition: layer_ids.h:120
@ F_SilkS
Definition: layer_ids.h:104
@ B_CrtYd
Definition: layer_ids.h:116
@ UNDEFINED_LAYER
Definition: layer_ids.h:61
@ Eco2_User
Definition: layer_ids.h:112
@ B_SilkS
Definition: layer_ids.h:103
@ PCB_LAYER_ID_COUNT
Definition: layer_ids.h:137
@ F_Cu
Definition: layer_ids.h:64
@ B_Fab
Definition: layer_ids.h:119
KICOMMON_API wxString StringFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Returns the string from aValue according to aUnits (inch, mm ...) for display.
Definition: eda_units.cpp:300
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:390
static wxString makeKey(const wxString &aFirst, const wxString &aSecond)
Assemble a two part key as a simple concatenation of aFirst and aSecond parts, using a separator.
#define DIMENSION_PRECISION
static int parseEagle(const wxString &aDistance)
Parse an eagle distance which is either mm, or mils if there is "mil" suffix.
static T eagleClamp(T aMin, T aValue, T aMax)
std::vector< ZONE * > ZONES
Definition: pcb_io_eagle.h:47
NET_MAP::const_iterator NET_MAP_CITER
Definition: pcb_io_eagle.h:49
bool ReplaceIllegalFileNameChars(std::string *aName, int aReplaceChar)
Checks aName for illegal file name characters.
wxString UnescapeHTML(const wxString &aString)
Return a new wxString unescaped from HTML format.
Parse an Eagle "attribute" XML element.
Definition: eagle_parser.h:793
opt_double ratio
Definition: eagle_parser.h:821
opt_wxString value
Definition: eagle_parser.h:815
opt_ecoord size
Definition: eagle_parser.h:818
opt_ecoord y
Definition: eagle_parser.h:817
wxString name
Definition: eagle_parser.h:814
opt_erot rot
Definition: eagle_parser.h:822
opt_int align
Definition: eagle_parser.h:833
opt_int display
Definition: eagle_parser.h:832
opt_ecoord x
Definition: eagle_parser.h:816
Eagle circle.
Definition: eagle_parser.h:719
ECOORD x
Definition: eagle_parser.h:731
ECOORD radius
Definition: eagle_parser.h:733
ECOORD y
Definition: eagle_parser.h:732
ECOORD width
Definition: eagle_parser.h:734
wxString number
wxString name
@ EU_MM
millimeters
Definition: eagle_parser.h:445
@ EU_MIL
mils/thous
Definition: eagle_parser.h:447
long long int value
Unit used for the value field.
Definition: eagle_parser.h:451
int ToPcbUnits() const
Definition: eagle_parser.h:489
const double IU_PER_MM
Definition: base_units.h:76
const double IU_PER_MILS
Definition: base_units.h:77
constexpr int mmToIU(double mm) const
Definition: base_units.h:88
Eagle dimension element.
Definition: eagle_parser.h:844
opt_wxString dimensionType
Definition: eagle_parser.h:876
opt_ecoord textsize
Definition: eagle_parser.h:874
Eagle element element.
opt_erot rot
wxString name
wxString library
opt_wxString library_urn
wxString package
ECOORD y
opt_bool smashed
ECOORD x
wxString value
Eagle hole element.
ECOORD y
ECOORD drill
ECOORD x
wxString name
opt_bool active
int number
Eagle net.
Definition: eagle_parser.h:513
int netcode
Definition: eagle_parser.h:522
Structure holding common properties for through-hole and SMD pads.
Definition: eagle_parser.h:985
opt_bool thermals
Definition: eagle_parser.h:990
opt_bool stop
Definition: eagle_parser.h:989
wxString name
Definition: eagle_parser.h:986
opt_erot rot
Definition: eagle_parser.h:988
Eagle thru hole pad.
Definition: eagle_parser.h:998
ECOORD drill
opt_ecoord diameter
opt_bool first
opt_int shape
Eagle polygon, without vertices which are parsed as needed.
opt_bool orphans
opt_int rank
opt_bool thermals
opt_ecoord spacing
static const int max_priority
ECOORD width
opt_ecoord isolate
Eagle XML rectangle in binary.
Definition: eagle_parser.h:745
ECOORD x2
Definition: eagle_parser.h:760
ECOORD y1
Definition: eagle_parser.h:759
opt_erot rot
Definition: eagle_parser.h:763
int layer
Definition: eagle_parser.h:762
ECOORD y2
Definition: eagle_parser.h:761
ECOORD x1
Definition: eagle_parser.h:758
double degrees
Definition: eagle_parser.h:544
bool spin
Definition: eagle_parser.h:543
bool mirror
Definition: eagle_parser.h:542
subset of eagle.drawing.board.designrules in the XML document
Definition: pcb_io_eagle.h:54
int psBottom
Shape of the bottom pads.
Definition: pcb_io_eagle.h:104
int psElongationLong
Definition: pcb_io_eagle.h:89
double mvStopFrame
solderpaste mask, expressed as percentage of the smaller pad/via dimension
Definition: pcb_io_eagle.h:94
double srRoundness
corner rounding ratio for SMD pads (percentage)
Definition: pcb_io_eagle.h:107
double rlMinViaOuter
minimum copper annulus on via
Definition: pcb_io_eagle.h:122
int mlMaxCreamFrame
solder paste mask, maximum size (Eagle mils, here nanometers)
Definition: pcb_io_eagle.h:101
int mlMinCreamFrame
solder paste mask, minimum size (Eagle mils, here nanometers)
Definition: pcb_io_eagle.h:100
int psTop
Shape of the top pads.
Definition: pcb_io_eagle.h:103
double rlMaxViaOuter
maximum copper annulus on via
Definition: pcb_io_eagle.h:123
void parse(wxXmlNode *aRules, std::function< void()> aCheckpoint)
percent over 100%.
double mdWireWire
wire to wire spacing I presume.
Definition: pcb_io_eagle.h:124
double rvPadTop
top pad size as percent of drill size
Definition: pcb_io_eagle.h:115
int srMaxRoundness
Definition: pcb_io_eagle.h:113
double rvViaOuter
copper annulus is this percent of via hole
Definition: pcb_io_eagle.h:121
double rlMinPadTop
minimum copper annulus on through hole pads
Definition: pcb_io_eagle.h:118
double mvCreamFrame
Definition: pcb_io_eagle.h:97
int psElongationOffset
the offset of the hole within the "long" pad.
Definition: pcb_io_eagle.h:91
int mlMinStopFrame
solder mask, minimum size (Eagle mils, here nanometers)
Definition: pcb_io_eagle.h:98
int srMinRoundness
corner rounding radius, maximum size (Eagle mils, here nanometers)
Definition: pcb_io_eagle.h:110
int mlMaxStopFrame
solder mask, maximum size (Eagle mils, here nanometers)
Definition: pcb_io_eagle.h:99
double rlMaxPadTop
maximum copper annulus on through hole pads
Definition: pcb_io_eagle.h:119
int psFirst
Shape of the first pads.
Definition: pcb_io_eagle.h:105
Eagle SMD pad.
opt_int roundness
ECOORD dx
int layer
opt_bool cream
ECOORD dy
Eagle text element.
Definition: eagle_parser.h:894
opt_double ratio
Definition: eagle_parser.h:916
@ BOTTOM_CENTER
Definition: eagle_parser.h:928
@ BOTTOM_RIGHT
Definition: eagle_parser.h:930
@ TOP_CENTER
Definition: eagle_parser.h:922
@ TOP_LEFT
Definition: eagle_parser.h:923
@ TOP_RIGHT
Definition: eagle_parser.h:924
@ CENTER_RIGHT
Definition: eagle_parser.h:927
@ CENTER_LEFT
Definition: eagle_parser.h:921
@ BOTTOM_LEFT
Definition: eagle_parser.h:929
wxString text
Definition: eagle_parser.h:910
ECOORD y
Definition: eagle_parser.h:912
ECOORD size
Definition: eagle_parser.h:913
opt_erot rot
Definition: eagle_parser.h:917
opt_int align
Definition: eagle_parser.h:933
ECOORD x
Definition: eagle_parser.h:911
int layer
Definition: eagle_parser.h:914
Eagle vertex.
Definition: eagle_parser.h:562
Eagle via.
Definition: eagle_parser.h:688
opt_ecoord diam
Definition: eagle_parser.h:707
ECOORD drill
< inclusive
Definition: eagle_parser.h:706
ECOORD y
Definition: eagle_parser.h:703
int layer_front_most
Definition: eagle_parser.h:704
int layer_back_most
< extent
Definition: eagle_parser.h:705
ECOORD x
Definition: eagle_parser.h:702
Eagle wire.
Definition: eagle_parser.h:582
ECOORD width
Definition: eagle_parser.h:605
int layer
Definition: eagle_parser.h:606
ECOORD x2
Definition: eagle_parser.h:603
ECOORD y2
Definition: eagle_parser.h:604
ECOORD x1
Definition: eagle_parser.h:601
ECOORD y1
Definition: eagle_parser.h:602
opt_double curve
range is -359.9..359.9
Definition: eagle_parser.h:616
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_LAYER_ID AutoMapLayer
Best guess as to what the equivalent KiCad layer might be.
bool Required
Should we require the layer to be assigned?
LSET PermittedLayers
KiCad layers that the imported layer can be mapped onto.
wxString Name
Imported layer name as displayed in original application.
Implement a simple wrapper around runtime_error to isolate the errors thrown by the Eagle XML parser.
Definition: eagle_parser.h:82
VECTOR3I v1(5, 5, 5)
VECTOR2I v2(1, 0)
Test suite for KiCad math code.
constexpr int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
const VECTOR2I CalcArcMid(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aMinArcAngle=true)
Return the middle point of an arc, half-way between aStart and aEnd.
Definition: trigo.cpp:208
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:228
double DEG2RAD(double deg)
Definition: trigo.h:140
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition: typeinfo.h:101
int sign(T val)
Definition: util.h:171
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:121
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:673
ZONE_BORDER_DISPLAY_STYLE
Zone border styles.
Definition: zone_settings.h:50
#define ZONE_THICKNESS_MIN_VALUE_MM
Definition: zones.h:36