KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_ipc2581.cpp
Go to the documentation of this file.
1
19
20#include "pcb_io_ipc2581.h"
21#include "ipc2581_types.h"
22
23#include <base_units.h>
24#include <bezier_curves.h>
25#include <board.h>
28#include <build_version.h>
29#include <callback_gal.h>
33#include <font/font.h>
34#include <hash_eda.h>
35#include <pcb_dimension.h>
36#include <pcb_textbox.h>
37#include <pgm_base.h>
38#include <progress_reporter.h>
40#include <string_utils.h>
41#include <wx_fstream_progress.h>
42
46
47#include <wx/log.h>
48#include <wx/numformatter.h>
49#include <wx/xml/xml.h>
50#include <properties/property.h>
52
53
59static const wxChar traceIpc2581[] = wxT( "KICAD_IPC_2581" );
60
61
65static const std::map<wxString, surfaceFinishType> surfaceFinishMap =
66{
67 { wxEmptyString, surfaceFinishType::NONE },
68 { wxT( "ENIG" ), surfaceFinishType::ENIG_N },
69 { wxT( "ENEPIG" ), surfaceFinishType::ENEPIG_N },
70 { wxT( "HAL SNPB" ), surfaceFinishType::S },
71 { wxT( "HAL LEAD-FREE" ), surfaceFinishType::S },
72 { wxT( "HARD GOLD" ), surfaceFinishType::G },
73 { wxT( "IMMERSION TIN" ), surfaceFinishType::ISN },
74 { wxT( "IMMERSION NICKEL" ), surfaceFinishType::N },
75 { wxT( "IMMERSION SILVER" ), surfaceFinishType::IAG },
76 { wxT( "IMMERSION GOLD" ), surfaceFinishType::DIG },
77 { wxT( "HT_OSP" ), surfaceFinishType::HT_OSP },
78 { wxT( "OSP" ), surfaceFinishType::OSP },
79 { wxT( "NONE" ), surfaceFinishType::NONE },
80 { wxT( "NOT SPECIFIED" ), surfaceFinishType::NONE },
81 { wxT( "USER DEFINED" ), surfaceFinishType::NONE },
82};
83
84
88static const std::map<surfaceFinishType, wxString> surfaceFinishTypeToString =
89{
90 { surfaceFinishType::ENIG_N, wxT( "ENIG-N" ) },
91 { surfaceFinishType::ENEPIG_N, wxT( "ENEPIG-N" ) },
92 { surfaceFinishType::OSP, wxT( "OSP" ) },
93 { surfaceFinishType::HT_OSP, wxT( "HT_OSP" ) },
94 { surfaceFinishType::IAG, wxT( "IAg" ) },
95 { surfaceFinishType::ISN, wxT( "ISn" ) },
96 { surfaceFinishType::G, wxT( "G" ) },
97 { surfaceFinishType::N, wxT( "N" ) },
98 { surfaceFinishType::DIG, wxT( "DIG" ) },
99 { surfaceFinishType::S, wxT( "S" ) },
100 { surfaceFinishType::OTHER, wxT( "OTHER" ) },
101};
102
103
104static surfaceFinishType getSurfaceFinishType( const wxString& aFinish )
105{
106 auto it = surfaceFinishMap.find( aFinish.Upper() );
107 return ( it != surfaceFinishMap.end() ) ? it->second : surfaceFinishType::OTHER;
108}
109
110
116
117
119{
120 for( FOOTPRINT* fp : m_loaded_footprints )
121 delete fp;
122
123 m_loaded_footprints.clear();
124}
125
126
128{
129 std::vector<FOOTPRINT*> retval;
130
131 for( FOOTPRINT* fp : m_loaded_footprints )
132 retval.push_back( static_cast<FOOTPRINT*>( fp->Clone() ) );
133
134 return retval;
135}
136
137
138void PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, wxXmlNode* aNode )
139{
140 // insertNode places the node at the start of the list of children
141
142 if( aParent->GetChildren() )
143 aNode->SetNext( aParent->GetChildren() );
144 else
145 aNode->SetNext( nullptr );
146
147 aParent->SetChildren( aNode );
148 aNode->SetParent( aParent );
149 m_total_bytes += 2 * aNode->GetName().size() + 5;
150}
151
152
153void PCB_IO_IPC2581::insertNodeAfter( wxXmlNode* aPrev, wxXmlNode* aNode )
154{
155 // insertNode places the node directly after aPrev
156
157 aNode->SetNext( aPrev->GetNext() );
158 aPrev->SetNext( aNode );
159 aNode->SetParent( aPrev->GetParent() );
160 m_total_bytes += 2 * aNode->GetName().size() + 5;
161}
162
163
164void PCB_IO_IPC2581::deleteNode( wxXmlNode*& aNode )
165{
166 // When deleting a node, invalidate the appendNode optimization cache if it points
167 // to the node being deleted or any of its descendants
169 {
170 wxXmlNode* check = m_lastAppendedNode;
171
172 while( check )
173 {
174 if( check == aNode )
175 {
176 m_lastAppendedNode = nullptr;
177 break;
178 }
179
180 check = check->GetParent();
181 }
182 }
183
184 delete aNode;
185 aNode = nullptr;
186}
187
188
189wxXmlNode* PCB_IO_IPC2581::insertNode( wxXmlNode* aParent, const wxString& aName )
190{
191 // Opening tag, closing tag, brackets and the closing slash
192 m_total_bytes += 2 * aName.size() + 5;
193 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
194 insertNode( aParent, node );
195 return node;
196}
197
198
199void PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, wxXmlNode* aNode )
200{
201 // AddChild iterates through the entire list of children, so we want to avoid
202 // that if possible. When we share a parent and our next sibling is null,
203 // then we are the last child and can just append to the end of the list.
204
205 if( m_lastAppendedNode && m_lastAppendedNode->GetParent() == aParent
206 && m_lastAppendedNode->GetNext() == nullptr )
207 {
208 aNode->SetParent( aParent );
209 m_lastAppendedNode->SetNext( aNode );
210 }
211 else
212 {
213 aParent->AddChild( aNode );
214 }
215
216 m_lastAppendedNode = aNode;
217
218 // Opening tag, closing tag, brackets and the closing slash
219 m_total_bytes += 2 * aNode->GetName().size() + 5;
220}
221
222
223wxXmlNode* PCB_IO_IPC2581::appendNode( wxXmlNode* aParent, const wxString& aName )
224{
225 wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName );
226
227 appendNode( aParent, node );
228 return node;
229}
230
231
232wxString PCB_IO_IPC2581::sanitizeId( const wxString& aStr ) const
233{
234 wxString str;
235
236 if( m_version == 'C' )
237 {
238 str = aStr;
239 str.Replace( wxT( ":" ), wxT( "_" ) );
240 }
241 else
242 {
243 for( wxString::const_iterator iter = aStr.begin(); iter != aStr.end(); ++iter )
244 {
245 if( !m_acceptable_chars.count( *iter ) )
246 str.Append( '_' );
247 else
248 str.Append( *iter );
249 }
250 }
251
252 return str;
253}
254
255
256wxString PCB_IO_IPC2581::genString( const wxString& aStr, const char* aPrefix ) const
257{
258 // Build a key using the prefix and original string so that repeated calls for the same
259 // element return the same generated name.
260 wxString key = aPrefix ? wxString( aPrefix ) + wxT( ":" ) + aStr : aStr;
261
262 auto it = m_generated_names.find( key );
263
264 if( it != m_generated_names.end() )
265 return it->second;
266
267 wxString str = sanitizeId( aStr );
268
269 wxString base = str;
270 wxString name = base;
271 int suffix = 1;
272
273 while( m_element_names.count( name ) )
274 name = wxString::Format( "%s_%d", base, suffix++ );
275
276 m_element_names.insert( name );
277 m_generated_names[key] = name;
278
279 return name;
280}
281
282
283wxString PCB_IO_IPC2581::genLayerString( PCB_LAYER_ID aLayer, const char* aPrefix ) const
284{
285 return genString( m_board->GetLayerName( aLayer ), aPrefix );
286}
287
288
290 const char* aPrefix ) const
291{
292 return genString( wxString::Format( wxS( "%s_%s" ),
293 m_board->GetLayerName( aTop ),
294 m_board->GetLayerName( aBottom ) ), aPrefix );
295}
296
297
298wxString PCB_IO_IPC2581::pinName( const PAD* aPad ) const
299{
300 wxString name = aPad->GetNumber();
301
302 FOOTPRINT* fp = aPad->GetParentFootprint();
303 size_t ii = 0;
304
305 if( name.empty() && fp )
306 {
307 for( ii = 0; ii < fp->GetPadCount(); ++ii )
308 {
309 if( fp->Pads()[ii] == aPad )
310 break;
311 }
312 }
313
314 // Pins are required to have names, so if our pad doesn't have a name, we need to
315 // generate one that is unique
316 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
317 name = wxString::Format( "NPTH%zu", ii );
318 else if( name.empty() )
319 name = wxString::Format( "PAD%zu", ii );
320
321 // Pins are scoped per-package, so we only sanitize; uniqueness is handled by
322 // the per-package pin_nodes map in addPackage().
323 return sanitizeId( name );
324}
325
326
328{
329 auto tryInsert =
330 [&]( const wxString& aName )
331 {
332 if( m_footprint_refdes_dict.count( aName ) )
333 {
334 if( m_footprint_refdes_dict.at( aName ) != aFootprint )
335 return false;
336 }
337 else
338 {
339 m_footprint_refdes_dict.insert( { aName, aFootprint } );
340 }
341
342 return true;
343 };
344
345 if( m_footprint_refdes_reverse_dict.count( aFootprint ) )
346 return m_footprint_refdes_reverse_dict.at( aFootprint );
347
348 wxString baseName = genString( aFootprint->GetReference(), "CMP" );
349 wxString name = baseName;
350 int suffix = 1;
351
352 while( !tryInsert( name ) )
353 name = wxString::Format( "%s_%d", baseName, suffix++ );
354
356
357 return name;
358}
359
360
361wxString PCB_IO_IPC2581::floatVal( double aVal, int aSigFig ) const
362{
363 wxString str = wxString::FromCDouble( aVal, aSigFig == -1 ? m_sigfig : aSigFig );
364
365 // Remove all but the last trailing zeros from str
366 while( str.EndsWith( wxT( "00" ) ) )
367 str.RemoveLast();
368
369 // We don't want to output -0.0 as this value is just 0 for fabs
370 if( str == wxT( "-0.0" ) )
371 return wxT( "0.0" );
372
373 return str;
374}
375
376
377void PCB_IO_IPC2581::addXY( wxXmlNode* aNode, const VECTOR2I& aVec, const char* aXName,
378 const char* aYName )
379{
380 if( aXName )
381 addAttribute( aNode, aXName, floatVal( m_scale * aVec.x ) );
382 else
383 addAttribute( aNode, "x", floatVal( m_scale * aVec.x ) );
384
385 if( aYName )
386 addAttribute( aNode, aYName, floatVal( -m_scale * aVec.y ) );
387 else
388 addAttribute( aNode, "y", floatVal( -m_scale * aVec.y ) );
389}
390
391
392void PCB_IO_IPC2581::addAttribute( wxXmlNode* aNode, const wxString& aName, const wxString& aValue )
393{
394 m_total_bytes += aName.size() + aValue.size() + 4;
395 aNode->AddAttribute( aName, aValue );
396}
397
398
400{
401 wxXmlNode* xmlHeaderNode = new wxXmlNode(wxXML_ELEMENT_NODE, "IPC-2581");
402 addAttribute( xmlHeaderNode, "revision", m_version);
403 addAttribute( xmlHeaderNode, "xmlns", "http://webstds.ipc.org/2581");
404 addAttribute( xmlHeaderNode, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
405 addAttribute( xmlHeaderNode, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
406
407 if( m_version == 'B' )
408 {
409 addAttribute( xmlHeaderNode, "xsi:schemaLocation",
410 "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581B1.xsd" );
411 }
412 else
413 {
414 addAttribute( xmlHeaderNode, "xsi:schemaLocation",
415 "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581C.xsd" );
416 }
417
418 m_xml_doc->SetRoot( xmlHeaderNode );
419
420 return xmlHeaderNode;
421}
422
423
425{
427 m_progressReporter->AdvancePhase( _( "Generating content section" ) );
428
429 wxXmlNode* contentNode = appendNode( m_xml_root, "Content" );
430 addAttribute( contentNode, "roleRef", "Owner" );
431
432 wxXmlNode* node = appendNode( contentNode, "FunctionMode" );
433 addAttribute( node, "mode", "ASSEMBLY" );
434
435 // This element is deprecated in revision 'C' and later
436 if( m_version == 'B' )
437 addAttribute( node, "level", "3" );
438
439 node = appendNode( contentNode, "StepRef" );
440 wxFileName fn( m_board->GetFileName() );
441 addAttribute( node, "name", genString( fn.GetName(), "BOARD" ) );
442
443 wxXmlNode* color_node = generateContentStackup( contentNode );
444
445 if( m_version == 'C' )
446 {
447 contentNode->AddChild( color_node );
448 m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
450
451 wxXmlNode* fillNode = appendNode( contentNode, "DictionaryFillDesc" );
452 addAttribute( fillNode, "units", m_units_str );
453
454 m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
456
457 m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
459 }
460 else
461 {
462 m_shape_std_node = appendNode( contentNode, "DictionaryStandard" );
464
465 m_shape_user_node = appendNode( contentNode, "DictionaryUser" );
467
468 m_line_node = appendNode( contentNode, "DictionaryLineDesc" );
470
471 contentNode->AddChild( color_node );
472 }
473
474 return contentNode;
475}
476
477
478void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, double aX, double aY )
479{
480 wxXmlNode* location_node = appendNode( aNode, "Location" );
481 addXY( location_node, VECTOR2I( aX, aY ) );
482}
483
484
485void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PAD& aPad, bool aRelative )
486{
487 VECTOR2D pos{};
488
489 if( aRelative )
490 pos = aPad.GetFPRelativePosition();
491 else
492 pos = aPad.GetPosition();
493
494 if( aPad.GetOffset( PADSTACK::ALL_LAYERS ).x != 0 || aPad.GetOffset( PADSTACK::ALL_LAYERS ).y != 0 )
495 pos += aPad.GetOffset( PADSTACK::ALL_LAYERS );
496
497 addLocationNode( aNode, pos.x, pos.y );
498}
499
500
501void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PCB_SHAPE& aShape )
502{
503 VECTOR2D pos{};
504
505 switch( aShape.GetShape() )
506 {
507 // Rectangles in KiCad are mapped by their corner while IPC2581 uses the center
509 pos = aShape.GetPosition()
510 + VECTOR2I( aShape.GetRectangleWidth() / 2.0, aShape.GetRectangleHeight() / 2.0 );
511 break;
512 // Both KiCad and IPC2581 use the center of the circle
513 case SHAPE_T::CIRCLE:
514 pos = aShape.GetPosition();
515 break;
516
517 // KiCad uses the exact points on the board, so we want the reference location to be 0,0
518 case SHAPE_T::POLY:
519 case SHAPE_T::BEZIER:
520 case SHAPE_T::SEGMENT:
521 case SHAPE_T::ARC:
522 pos = VECTOR2D( 0, 0 );
523 break;
524
526 wxFAIL;
527 }
528
529 addLocationNode( aNode, pos.x, pos.y );
530}
531
532
533size_t PCB_IO_IPC2581::lineHash( int aWidth, LINE_STYLE aDashType )
534{
535 size_t hash = hash_val( aWidth );
536 hash_combine( hash, aDashType );
537
538 return hash;
539}
540
541
543{
544 return hash_fp_item( &aShape, HASH_POS | REL_COORD );
545}
546
547
548wxXmlNode* PCB_IO_IPC2581::generateContentStackup( wxXmlNode* aContentNode )
549{
550
551 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
552 BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
553 stackup.SynchronizeWithBoard( &bds );
554
555 wxXmlNode* color_node = new wxXmlNode( wxXML_ELEMENT_NODE, "DictionaryColor" );
556
557 for( BOARD_STACKUP_ITEM* item: stackup.GetList() )
558 {
559 wxString layer_name = item->GetLayerName();
560 int sub_layer_count = 1;
561
562 if( layer_name.empty() )
563 layer_name = m_board->GetLayerName( item->GetBrdLayerId() );
564
565 layer_name = genString( layer_name, "LAYER" );
566
567 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
568 {
569 layer_name = genString( wxString::Format( "DIELECTRIC_%d", item->GetDielectricLayerId() ),
570 "LAYER" );
571 sub_layer_count = item->GetSublayersCount();
572 }
573 else
574 {
575 m_layer_name_map.emplace( item->GetBrdLayerId(), layer_name );
576 }
577
578 for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ )
579 {
580 wxString sub_layer_name = layer_name;
581
582 if( sub_idx > 0 )
583 sub_layer_name += wxString::Format( "_%d", sub_idx );
584
585 wxXmlNode* node = appendNode( aContentNode, "LayerRef" );
586 addAttribute( node, "name", sub_layer_name );
587
588 if( !IsPrmSpecified( item->GetColor( sub_idx ) ) )
589 continue;
590
591 wxXmlNode* entry_color = appendNode( color_node, "EntryColor" );
592 addAttribute( entry_color, "id", genString( sub_layer_name, "COLOR" ) );
593 wxXmlNode* color = appendNode( entry_color, "Color" );
594
595 wxString colorName = item->GetColor( sub_idx );
596
597 if( colorName.StartsWith( wxT( "#" ) ) ) // This is a user defined color,
598 // not in standard color list.
599 {
600 COLOR4D layer_color( colorName );
601 addAttribute( color, "r", wxString::Format( "%d",
602 KiROUND( layer_color.r * 255 ) ) );
603 addAttribute( color, "g", wxString::Format( "%d",
604 KiROUND( layer_color.g * 255 ) ) );
605 addAttribute( color, "b", wxString::Format( "%d",
606 KiROUND( layer_color.b * 255 ) ) );
607 }
608 else
609 {
610 for( const FAB_LAYER_COLOR& fab_color : GetStandardColors( item->GetType() ) )
611 {
612 if( fab_color.GetName() == colorName )
613 {
614 addAttribute( color, "r", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).r * 255 ) ) );
615 addAttribute( color, "g", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).g * 255 ) ) );
616 addAttribute( color, "b", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).b * 255 ) ) );
617 break;
618 }
619 }
620 }
621 }
622 }
623
624 return color_node;
625}
626
627
628void PCB_IO_IPC2581::addFillDesc( wxXmlNode* aNode, FILL_T aFill, bool aForce )
629{
630 if( aFill == FILL_T::FILLED_SHAPE )
631 {
632 // By default, we do not fill shapes because FILL is the default value for most.
633 // But for some outlines, we may need to force a fill.
634 if( aForce )
635 {
636 wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
637 addAttribute( fillDesc_node, "fillProperty", "FILL" );
638 }
639 }
640 else
641 {
642 wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" );
643 addAttribute( fillDesc_node, "fillProperty", "HOLLOW" );
644 }
645}
646
647
648void PCB_IO_IPC2581::addLineDesc( wxXmlNode* aNode, int aWidth, LINE_STYLE aDashType, bool aForce )
649{
650 wxCHECK_RET( aNode, "aNode is null" );
651
652 if( aWidth < 0 )
653 return;
654
655 wxXmlNode* entry_node = nullptr;
656
657 if( !aForce )
658 {
659 size_t hash = lineHash( aWidth, aDashType );
660 wxString name = wxString::Format( "LINE_%zu", m_line_dict.size() + 1 );
661 auto[ iter, inserted ] = m_line_dict.emplace( hash, name );
662
663 // Either add a new entry or reference an existing one
664 wxXmlNode* lineDesc_node = appendNode( aNode, "LineDescRef" );
665 addAttribute( lineDesc_node, "id", iter->second );
666
667 if( !inserted )
668 return;
669
670 entry_node = appendNode( m_line_node, "EntryLineDesc" );
671 addAttribute( entry_node, "id", name );
672 }
673 else
674 {
675 // Force the LineDesc to be added directly to the parent node
676 entry_node = aNode;
677 }
678
679 wxXmlNode* line_node = appendNode( entry_node, "LineDesc" );
680 addAttribute( line_node, "lineWidth", floatVal( m_scale * aWidth ) );
681 addAttribute( line_node, "lineEnd", "ROUND" );
682
683 switch( aDashType )
684 {
685 case LINE_STYLE::DOT:
686 addAttribute( line_node, "lineProperty", "DOTTED" );
687 break;
688 case LINE_STYLE::DASH:
689 addAttribute( line_node, "lineProperty", "DASHED" );
690 break;
692 addAttribute( line_node, "lineProperty", "CENTER" );
693 break;
695 addAttribute( line_node, "lineProperty", "PHANTOM" );
696 break;
697 default:
698 break;
699 }
700}
701
702
703void PCB_IO_IPC2581::addKnockoutText( wxXmlNode* aContentNode, PCB_TEXT* aText )
704{
705 SHAPE_POLY_SET finalPoly;
706
707 aText->TransformTextToPolySet( finalPoly, 0, ARC_HIGH_DEF, ERROR_INSIDE );
708 finalPoly.Fracture();
709
710 for( int ii = 0; ii < finalPoly.OutlineCount(); ++ii )
711 addContourNode( aContentNode, finalPoly, ii );
712}
713
714
715void PCB_IO_IPC2581::addText( wxXmlNode* aContentNode, EDA_TEXT* aText,
716 const KIFONT::METRICS& aFontMetrics )
717{
719 KIFONT::FONT* font = aText->GetDrawFont( nullptr );
720 TEXT_ATTRIBUTES attrs = aText->GetAttributes();
721
723 attrs.m_Angle = aText->GetDrawRotation();
724 attrs.m_Multiline = false;
725
726 wxXmlNode* text_node = appendNode( aContentNode, "UserSpecial" );
727
728 std::list<VECTOR2I> pts;
729
730 auto push_pts =
731 [&]()
732 {
733 if( pts.size() < 2 )
734 return;
735
736 wxXmlNode* line_node = nullptr;
737
738 // Polylines are only allowed for more than 3 points (in version B).
739 // Otherwise, we have to use a line
740 if( pts.size() < 3 )
741 {
742 line_node = appendNode( text_node, "Line" );
743 addXY( line_node, pts.front(), "startX", "startY" );
744 addXY( line_node, pts.back(), "endX", "endY" );
745 }
746 else
747 {
748 line_node = appendNode( text_node, "Polyline" );
749 wxXmlNode* point_node = appendNode( line_node, "PolyBegin" );
750 addXY( point_node, pts.front() );
751
752 auto iter = pts.begin();
753
754 for( ++iter; iter != pts.end(); ++iter )
755 {
756 wxXmlNode* point_node = appendNode( line_node, "PolyStepSegment" );
757 addXY( point_node, *iter );
758 }
759
760 }
761
762 addLineDesc( line_node, attrs.m_StrokeWidth, LINE_STYLE::SOLID );
763 pts.clear();
764 };
765
766 CALLBACK_GAL callback_gal( empty_opts,
767 // Stroke callback
768 [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
769 {
770 if( !pts.empty() )
771 {
772 if( aPt1 == pts.back() )
773 pts.push_back( aPt2 );
774 else if( aPt2 == pts.front() )
775 pts.push_front( aPt1 );
776 else if( aPt1 == pts.front() )
777 pts.push_front( aPt2 );
778 else if( aPt2 == pts.back() )
779 pts.push_back( aPt1 );
780 else
781 {
782 push_pts();
783 pts.push_back( aPt1 );
784 pts.push_back( aPt2 );
785 }
786 }
787 else
788 {
789 pts.push_back( aPt1 );
790 pts.push_back( aPt2 );
791 }
792 },
793 // Polygon callback
794 [&]( const SHAPE_LINE_CHAIN& aPoly )
795 {
796 if( aPoly.PointCount() < 3 )
797 return;
798
799 wxXmlNode* outline_node = appendNode( text_node, "Outline" );
800 wxXmlNode* poly_node = appendNode( outline_node, "Polygon" );
801 addLineDesc( outline_node, 0, LINE_STYLE::SOLID );
802
803 const std::vector<VECTOR2I>& pts = aPoly.CPoints();
804 wxXmlNode* point_node = appendNode( poly_node, "PolyBegin" );
805 addXY( point_node, pts.front() );
806
807 for( size_t ii = 1; ii < pts.size(); ++ii )
808 {
809 wxXmlNode* point_node =
810 appendNode( poly_node, "PolyStepSegment" );
811 addXY( point_node, pts[ii] );
812 }
813
814 point_node = appendNode( poly_node, "PolyStepSegment" );
815 addXY( point_node, pts.front() );
816 } );
817
818 //TODO: handle multiline text
819
820 font->Draw( &callback_gal, aText->GetShownText( true ), aText->GetTextPos(), attrs,
821 aFontMetrics );
822
823 if( !pts.empty() )
824 push_pts();
825
826 if( text_node->GetChildren() == nullptr )
827 {
828 aContentNode->RemoveChild( text_node );
829 delete text_node;
830 }
831}
832
833
834void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAYER_ID aLayer )
835{
836 size_t hash = hash_fp_item( &aPad, 0 );
837 auto iter = m_std_shape_dict.find( hash );
838
839 if( iter != m_std_shape_dict.end() )
840 {
841 wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
842 addAttribute( shape_node, "id", iter->second );
843 return;
844 }
845
846 int maxError = m_board->GetDesignSettings().m_MaxError;
847 wxString name;
848 VECTOR2I expansion{ 0, 0 };
849
850 if( LSET( { F_Mask, B_Mask } ).Contains( aLayer ) )
851 expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion( PADSTACK::ALL_LAYERS );
852
853 if( LSET( { F_Paste, B_Paste } ).Contains( aLayer ) )
854 expansion = 2 * aPad.GetSolderPasteMargin( PADSTACK::ALL_LAYERS );
855
856 switch( aPad.GetShape( PADSTACK::ALL_LAYERS ) )
857 {
859 {
860 name = wxString::Format( "CIRCLE_%zu", m_std_shape_dict.size() + 1 );
861 m_std_shape_dict.emplace( hash, name );
862
863 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
864 addAttribute( entry_node, "id", name );
865
866 wxXmlNode* circle_node = appendNode( entry_node, "Circle" );
867 circle_node->AddAttribute( "diameter",
868 floatVal( m_scale * ( expansion.x + aPad.GetSizeX() ) ) );
869 break;
870 }
871
873 {
874 name = wxString::Format( "RECT_%zu", m_std_shape_dict.size() + 1 );
875 m_std_shape_dict.emplace( hash, name );
876
877 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
878 addAttribute( entry_node, "id", name );
879
880 wxXmlNode* rect_node = appendNode( entry_node, "RectCenter" );
881 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
882 addAttribute( rect_node, "width", floatVal( m_scale * std::abs( pad_size.x ) ) );
883 addAttribute( rect_node, "height", floatVal( m_scale * std::abs( pad_size.y ) ) );
884
885 break;
886
887 }
888 case PAD_SHAPE::OVAL:
889 {
890 name = wxString::Format( "OVAL_%zu", m_std_shape_dict.size() + 1 );
891 m_std_shape_dict.emplace( hash, name );
892
893 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
894 addAttribute( entry_node, "id", name );
895
896 wxXmlNode* oval_node = appendNode( entry_node, "Oval" );
897 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
898 addAttribute( oval_node, "width", floatVal( m_scale * pad_size.x ) );
899 addAttribute( oval_node, "height", floatVal( m_scale * pad_size.y ) );
900
901 break;
902 }
903
905 {
906 name = wxString::Format( "ROUNDRECT_%zu", m_std_shape_dict.size() + 1 );
907 m_std_shape_dict.emplace( hash, name );
908
909 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
910 addAttribute( entry_node, "id", name );
911
912 wxXmlNode* roundrect_node = appendNode( entry_node, "RectRound" );
913 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
914 addAttribute( roundrect_node, "width", floatVal( m_scale * pad_size.x ) );
915 addAttribute( roundrect_node, "height", floatVal( m_scale * pad_size.y ) );
916 roundrect_node->AddAttribute( "radius",
918 addAttribute( roundrect_node, "upperRight", "true" );
919 addAttribute( roundrect_node, "upperLeft", "true" );
920 addAttribute( roundrect_node, "lowerRight", "true" );
921 addAttribute( roundrect_node, "lowerLeft", "true" );
922
923 break;
924 }
925
927 {
928 name = wxString::Format( "RECTCHAMFERED_%zu", m_std_shape_dict.size() + 1 );
929 m_std_shape_dict.emplace( hash, name );
930
931 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
932 addAttribute( entry_node, "id", name );
933
934 wxXmlNode* chamfered_node = appendNode( entry_node, "RectCham" );
935 VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion;
936 addAttribute( chamfered_node, "width", floatVal( m_scale * pad_size.x ) );
937 addAttribute( chamfered_node, "height", floatVal( m_scale * pad_size.y ) );
938
939 int shorterSide = std::min( pad_size.x, pad_size.y );
940 int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * shorterSide ) );
941
942 addAttribute( chamfered_node, "chamfer", floatVal( m_scale * chamfer ) );
943
944 int positions = aPad.GetChamferPositions( PADSTACK::ALL_LAYERS );
945
946 if( positions & RECT_CHAMFER_TOP_LEFT )
947 addAttribute( chamfered_node, "upperLeft", "true" );
948 if( positions & RECT_CHAMFER_TOP_RIGHT )
949 addAttribute( chamfered_node, "upperRight", "true" );
950 if( positions & RECT_CHAMFER_BOTTOM_LEFT )
951 addAttribute( chamfered_node, "lowerLeft", "true" );
952 if( positions & RECT_CHAMFER_BOTTOM_RIGHT )
953 addAttribute( chamfered_node, "lowerRight", "true" );
954
955 break;
956 }
957
959 {
960 name = wxString::Format( "TRAPEZOID_%zu", m_std_shape_dict.size() + 1 );
961 m_std_shape_dict.emplace( hash, name );
962
963 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
964 addAttribute( entry_node, "id", name );
965
966 VECTOR2I pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS );
967 VECTOR2I trap_delta = aPad.GetDelta( PADSTACK::ALL_LAYERS );
968 SHAPE_POLY_SET outline;
969 outline.NewOutline();
970 int dx = pad_size.x / 2;
971 int dy = pad_size.y / 2;
972 int ddx = trap_delta.x / 2;
973 int ddy = trap_delta.y / 2;
974
975 outline.Append( -dx - ddy, dy + ddx );
976 outline.Append( dx + ddy, dy - ddx );
977 outline.Append( dx - ddy, -dy + ddx );
978 outline.Append( -dx + ddy, -dy - ddx );
979
980 // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
981 // which can create bad shapes if margin.x is < 0
982 if( expansion.x )
983 {
985 maxError );
986 }
987
988 addContourNode( entry_node, outline );
989
990 break;
991 }
993 {
994 name = wxString::Format( "CUSTOM_%zu", m_std_shape_dict.size() + 1 );
995 m_std_shape_dict.emplace( hash, name );
996
997 wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" );
998 addAttribute( entry_node, "id", name );
999
1000 SHAPE_POLY_SET shape;
1002
1003 if( expansion != VECTOR2I( 0, 0 ) )
1004 {
1005 shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ),
1007 }
1008
1009 addContourNode( entry_node, shape );
1010 break;
1011 }
1012 default:
1013 Report( _( "Pad has unsupported type; it was skipped." ), RPT_SEVERITY_WARNING );
1014 break;
1015 }
1016
1017 if( !name.empty() )
1018 {
1019 m_std_shape_dict.emplace( hash, name );
1020 wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" );
1021 addAttribute( shape_node, "id", name );
1022 }
1023}
1024
1025
1026void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape, bool aInline )
1027{
1028 size_t hash = shapeHash( aShape );
1029 auto iter = m_user_shape_dict.find( hash );
1030 wxString name;
1031
1032 // When not inline, check for existing shape in dictionary and reference it
1033 if( !aInline && iter != m_user_shape_dict.end() )
1034 {
1035 wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
1036 addAttribute( shape_node, "id", iter->second );
1037 return;
1038 }
1039
1040 switch( aShape.GetShape() )
1041 {
1042 case SHAPE_T::CIRCLE:
1043 {
1044 if( aInline )
1045 {
1046 // For inline shapes (e.g., in Marking elements), output geometry directly as a
1047 // Polyline with two arcs forming a circle
1048 int radius = aShape.GetRadius();
1049 int width = aShape.GetStroke().GetWidth();
1050 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1051
1052 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1053
1054 // Create a circle using two semicircular arcs
1055 // Start at the rightmost point of the circle
1056 VECTOR2I center = aShape.GetCenter();
1057 VECTOR2I start( center.x + radius, center.y );
1058 VECTOR2I mid( center.x - radius, center.y );
1059
1060 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1061 addXY( begin_node, start );
1062
1063 // First arc from start to mid (top semicircle)
1064 wxXmlNode* arc1_node = appendNode( polyline_node, "PolyStepCurve" );
1065 addXY( arc1_node, mid );
1066 addXY( arc1_node, center, "centerX", "centerY" );
1067 addAttribute( arc1_node, "clockwise", "true" );
1068
1069 // Second arc from mid back to start (bottom semicircle)
1070 wxXmlNode* arc2_node = appendNode( polyline_node, "PolyStepCurve" );
1071 addXY( arc2_node, start );
1072 addXY( arc2_node, center, "centerX", "centerY" );
1073 addAttribute( arc2_node, "clockwise", "true" );
1074
1075 if( width > 0 )
1076 addLineDesc( polyline_node, width, dash, true );
1077
1078 break;
1079 }
1080
1081 name = wxString::Format( "UCIRCLE_%zu", m_user_shape_dict.size() + 1 );
1082 m_user_shape_dict.emplace( hash, name );
1083 int diameter = aShape.GetRadius() * 2.0;
1084 int width = aShape.GetStroke().GetWidth();
1085 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1086
1087
1088 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1089 addAttribute( entry_node, "id", name );
1090 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1091
1092 wxXmlNode* circle_node = appendNode( special_node, "Circle" );
1093
1094 if( aShape.GetFillMode() == FILL_T::NO_FILL )
1095 {
1096 addAttribute( circle_node, "diameter", floatVal( m_scale * diameter ) );
1097 addLineDesc( circle_node, width, dash, true );
1098 }
1099 else
1100 {
1101 // IPC2581 does not allow strokes on filled elements
1102 addAttribute( circle_node, "diameter", floatVal( m_scale * ( diameter + width ) ) );
1103 }
1104
1105 addFillDesc( circle_node, aShape.GetFillMode() );
1106
1107 break;
1108 }
1109
1110 case SHAPE_T::RECTANGLE:
1111 {
1112 if( aInline )
1113 {
1114 // For inline shapes, output as a Polyline with the rectangle corners
1115 int stroke_width = aShape.GetStroke().GetWidth();
1116 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1117
1118 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1119
1120 // Get the rectangle corners. Use GetRectCorners for proper handling
1121 std::vector<VECTOR2I> corners = aShape.GetRectCorners();
1122
1123 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1124 addXY( begin_node, corners[0] );
1125
1126 for( size_t i = 1; i < corners.size(); ++i )
1127 {
1128 wxXmlNode* step_node = appendNode( polyline_node, "PolyStepSegment" );
1129 addXY( step_node, corners[i] );
1130 }
1131
1132 // Close the rectangle
1133 wxXmlNode* close_node = appendNode( polyline_node, "PolyStepSegment" );
1134 addXY( close_node, corners[0] );
1135
1136 if( stroke_width > 0 )
1137 addLineDesc( polyline_node, stroke_width, dash, true );
1138
1139 break;
1140 }
1141
1142 name = wxString::Format( "URECT_%zu", m_user_shape_dict.size() + 1 );
1143 m_user_shape_dict.emplace( hash, name );
1144
1145 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1146 addAttribute( entry_node, "id", name );
1147 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1148
1149 int width = std::abs( aShape.GetRectangleWidth() );
1150 int height = std::abs( aShape.GetRectangleHeight() );
1151 int stroke_width = aShape.GetStroke().GetWidth();
1152 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1153
1154 wxXmlNode* rect_node = appendNode( special_node, "RectRound" );
1155 addLineDesc( rect_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(),
1156 true );
1157
1158 if( aShape.GetFillMode() == FILL_T::NO_FILL )
1159 {
1160 addAttribute( rect_node, "upperRight", "false" );
1161 addAttribute( rect_node, "upperLeft", "false" );
1162 addAttribute( rect_node, "lowerRight", "false" );
1163 addAttribute( rect_node, "lowerLeft", "false" );
1164 }
1165 else
1166 {
1167 addAttribute( rect_node, "upperRight", "true" );
1168 addAttribute( rect_node, "upperLeft", "true" );
1169 addAttribute( rect_node, "lowerRight", "true" );
1170 addAttribute( rect_node, "lowerLeft", "true" );
1171 width += stroke_width;
1172 height += stroke_width;
1173 }
1174
1175 addFillDesc( rect_node, aShape.GetFillMode() );
1176
1177 addAttribute( rect_node, "width", floatVal( m_scale * width ) );
1178 addAttribute( rect_node, "height", floatVal( m_scale * height ) );
1179 addAttribute( rect_node, "radius", floatVal( m_scale * ( stroke_width / 2.0 ) ) );
1180
1181 break;
1182 }
1183
1184 case SHAPE_T::POLY:
1185 {
1186 if( aInline )
1187 {
1188 // For inline shapes, output as Polyline elements directly
1189 const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape();
1190 int stroke_width = aShape.GetStroke().GetWidth();
1191 LINE_STYLE dash = aShape.GetStroke().GetLineStyle();
1192
1193 for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
1194 {
1195 const SHAPE_LINE_CHAIN& outline = poly_set.Outline( ii );
1196
1197 if( outline.PointCount() < 2 )
1198 continue;
1199
1200 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1201 const std::vector<VECTOR2I>& pts = outline.CPoints();
1202
1203 wxXmlNode* begin_node = appendNode( polyline_node, "PolyBegin" );
1204 addXY( begin_node, pts[0] );
1205
1206 for( size_t jj = 1; jj < pts.size(); ++jj )
1207 {
1208 wxXmlNode* step_node = appendNode( polyline_node, "PolyStepSegment" );
1209 addXY( step_node, pts[jj] );
1210 }
1211
1212 // Close the polygon if needed
1213 if( pts.size() > 2 && pts.front() != pts.back() )
1214 {
1215 wxXmlNode* close_node = appendNode( polyline_node, "PolyStepSegment" );
1216 addXY( close_node, pts[0] );
1217 }
1218
1219 if( stroke_width > 0 )
1220 addLineDesc( polyline_node, stroke_width, dash, true );
1221 }
1222
1223 break;
1224 }
1225
1226 name = wxString::Format( "UPOLY_%zu", m_user_shape_dict.size() + 1 );
1227 m_user_shape_dict.emplace( hash, name );
1228
1229 wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" );
1230 addAttribute( entry_node, "id", name );
1231
1232 // If we are stroking a polygon, we need two contours. This is only allowed
1233 // inside a "UserSpecial" shape
1234 wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" );
1235
1236 const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape();
1237
1238 for( int ii = 0; ii < poly_set.OutlineCount(); ++ii )
1239 {
1240 if( aShape.GetFillMode() != FILL_T::NO_FILL )
1241 {
1242 // IPC2581 does not allow strokes on filled elements
1243 addContourNode( special_node, poly_set, ii, FILL_T::FILLED_SHAPE, 0,
1245 }
1246
1247 addContourNode( special_node, poly_set, ii, FILL_T::NO_FILL,
1248 aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle() );
1249 }
1250
1251 break;
1252 }
1253
1254 case SHAPE_T::ARC:
1255 {
1256 wxXmlNode* arc_node = appendNode( aContentNode, "Arc" );
1257 addXY( arc_node, aShape.GetStart(), "startX", "startY" );
1258 addXY( arc_node, aShape.GetEnd(), "endX", "endY" );
1259 addXY( arc_node, aShape.GetCenter(), "centerX", "centerY" );
1260
1261 //N.B. because our coordinate system is flipped, we need to flip the arc direction
1262 addAttribute( arc_node, "clockwise", !aShape.IsClockwiseArc() ? "true" : "false" );
1263
1264 if( aShape.GetStroke().GetWidth() > 0 )
1265 {
1266 addLineDesc( arc_node, aShape.GetStroke().GetWidth(),
1267 aShape.GetStroke().GetLineStyle(), true );
1268 }
1269
1270 break;
1271 }
1272
1273 case SHAPE_T::BEZIER:
1274 {
1275 wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" );
1276 std::vector<VECTOR2I> ctrlPoints = { aShape.GetStart(), aShape.GetBezierC1(),
1277 aShape.GetBezierC2(), aShape.GetEnd() };
1278 BEZIER_POLY converter( ctrlPoints );
1279 std::vector<VECTOR2I> points;
1280 converter.GetPoly( points, ARC_HIGH_DEF );
1281
1282 wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
1283 addXY( point_node, points[0] );
1284
1285 for( size_t i = 1; i < points.size(); i++ )
1286 {
1287 wxXmlNode* point_node = appendNode( polyline_node, "PolyStepSegment" );
1288 addXY( point_node, points[i] );
1289 }
1290
1291 if( aShape.GetStroke().GetWidth() > 0 )
1292 {
1293 addLineDesc( polyline_node, aShape.GetStroke().GetWidth(),
1294 aShape.GetStroke().GetLineStyle(), true );
1295 }
1296
1297 break;
1298 }
1299
1300 case SHAPE_T::SEGMENT:
1301 {
1302 wxXmlNode* line_node = appendNode( aContentNode, "Line" );
1303 addXY( line_node, aShape.GetStart(), "startX", "startY" );
1304 addXY( line_node, aShape.GetEnd(), "endX", "endY" );
1305
1306 if( aShape.GetStroke().GetWidth() > 0 )
1307 {
1308 addLineDesc( line_node, aShape.GetStroke().GetWidth(),
1309 aShape.GetStroke().GetLineStyle(), true );
1310 }
1311
1312 break;
1313 }
1314
1315 case SHAPE_T::UNDEFINED:
1316 wxFAIL;
1317 }
1318
1319 // Only add UserPrimitiveRef when not in inline mode and a dictionary entry was created
1320 if( !aInline && !name.empty() )
1321 {
1322 wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" );
1323 addAttribute( shape_node, "id", name );
1324 }
1325
1326}
1327
1328
1329void PCB_IO_IPC2581::addSlotCavity( wxXmlNode* aNode, const PAD& aPad, const wxString& aName )
1330{
1331 wxXmlNode* slotNode = appendNode( aNode, "SlotCavity" );
1332 addAttribute( slotNode, "name", aName );
1333 addAttribute( slotNode, "platingStatus", aPad.GetAttribute() == PAD_ATTRIB::PTH ? "PLATED"
1334 : "NONPLATED" );
1335 addAttribute( slotNode, "plusTol", "0.0" );
1336 addAttribute( slotNode, "minusTol", "0.0" );
1337
1338 if( m_version > 'B' )
1339 addLocationNode( slotNode, aPad, false );
1340
1341 // Normally only oblong drill shapes should reach this code path since m_slot_holes
1342 // is filtered to pads where DrillSizeX != DrillSizeY. However, use a fallback to
1343 // ensure valid XML is always generated.
1345 {
1346 VECTOR2I drill_size = aPad.GetDrillSize();
1347 EDA_ANGLE rotation = aPad.GetOrientation();
1348
1349 // IPC-2581C requires width >= height for Oval primitive
1350 // Swap dimensions if needed and adjust rotation accordingly
1351 if( drill_size.y > drill_size.x )
1352 {
1353 std::swap( drill_size.x, drill_size.y );
1354 rotation += ANGLE_90;
1355 }
1356
1357 // Add Xform if rotation is needed (must come before Feature per IPC-2581C schema)
1358 if( rotation != ANGLE_0 )
1359 {
1360 wxXmlNode* xformNode = appendNode( slotNode, "Xform" );
1361 addAttribute( xformNode, "rotation", floatVal( rotation.AsDegrees() ) );
1362 }
1363
1364 // Use IPC-2581 Oval primitive for oblong slots
1365 wxXmlNode* ovalNode = appendNode( slotNode, "Oval" );
1366 addAttribute( ovalNode, "width", floatVal( m_scale * drill_size.x ) );
1367 addAttribute( ovalNode, "height", floatVal( m_scale * drill_size.y ) );
1368 }
1369 else
1370 {
1371 // Fallback to polygon outline for non-oblong shapes
1372 SHAPE_POLY_SET poly_set;
1373 int maxError = m_board->GetDesignSettings().m_MaxError;
1374 aPad.TransformHoleToPolygon( poly_set, 0, maxError, ERROR_INSIDE );
1375
1376 addOutlineNode( slotNode, poly_set );
1377 }
1378}
1379
1380
1382{
1383 wxXmlNode* logisticNode = appendNode( m_xml_root, "LogisticHeader" );
1384
1385 wxXmlNode* roleNode = appendNode( logisticNode, "Role" );
1386 addAttribute( roleNode, "id", "Owner" );
1387 addAttribute( roleNode, "roleFunction", "SENDER" );
1388
1389 m_enterpriseNode = appendNode( logisticNode, "Enterprise" );
1390 addAttribute( m_enterpriseNode, "id", "UNKNOWN" );
1391 addAttribute( m_enterpriseNode, "code", "NONE" );
1392
1393 wxXmlNode* personNode = appendNode( logisticNode, "Person" );
1394 addAttribute( personNode, "name", "UNKNOWN" );
1395 addAttribute( personNode, "enterpriseRef", "UNKNOWN" );
1396 addAttribute( personNode, "roleRef", "Owner" );
1397
1398 return logisticNode;
1399}
1400
1401
1403{
1404 if( m_progressReporter )
1405 m_progressReporter->AdvancePhase( _( "Generating history section" ) );
1406
1407 wxXmlNode* historyNode = appendNode( m_xml_root, "HistoryRecord" );
1408 addAttribute( historyNode, "number", "1" );
1409 addAttribute( historyNode, "origination", wxDateTime::Now().FormatISOCombined() );
1410 addAttribute( historyNode, "software", "KiCad EDA" );
1411 addAttribute( historyNode, "lastChange", wxDateTime::Now().FormatISOCombined() );
1412
1413 wxXmlNode* fileRevisionNode = appendNode( historyNode, "FileRevision" );
1414 addAttribute( fileRevisionNode, "fileRevisionId", "1" );
1415 addAttribute( fileRevisionNode, "comment", "NO COMMENT" );
1416 addAttribute( fileRevisionNode, "label", "NO LABEL" );
1417
1418 wxXmlNode* softwarePackageNode = appendNode( fileRevisionNode, "SoftwarePackage" );
1419 addAttribute( softwarePackageNode, "name", "KiCad" );
1420 addAttribute( softwarePackageNode, "revision", GetMajorMinorPatchVersion() );
1421 addAttribute( softwarePackageNode, "vendor", "KiCad EDA" );
1422
1423 wxXmlNode* certificationNode = appendNode( softwarePackageNode, "Certification" );
1424 addAttribute( certificationNode, "certificationStatus", "SELFTEST" );
1425
1426 return historyNode;
1427}
1428
1429
1430wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
1431{
1432 if( m_progressReporter )
1433 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
1434
1435 struct REFDES
1436 {
1437 wxString m_name;
1438 wxString m_pkg;
1439 bool m_populate;
1440 wxString m_layer;
1441 };
1442
1443 struct BOM_ENTRY
1444 {
1445 BOM_ENTRY()
1446 {
1447 m_refdes = new std::vector<REFDES>();
1448 m_props = new std::map<wxString, wxString>();
1449 m_count = 0;
1450 m_pads = 0;
1451 }
1452
1453 ~BOM_ENTRY()
1454 {
1455 delete m_refdes;
1456 delete m_props;
1457 }
1458
1459 wxString m_OEMDesignRef; // String combining LIB+FP+VALUE
1460 int m_count;
1461 int m_pads;
1462 wxString m_type;
1463 wxString m_description;
1464
1465 std::vector<REFDES>* m_refdes;
1466 std::map<wxString, wxString>* m_props;
1467 };
1468
1469 std::set<std::unique_ptr<struct BOM_ENTRY>,
1470 std::function<bool( const std::unique_ptr<struct BOM_ENTRY>&,
1471 const std::unique_ptr<struct BOM_ENTRY>& )>> bom_entries(
1472 []( const std::unique_ptr<struct BOM_ENTRY>& a,
1473 const std::unique_ptr<struct BOM_ENTRY>& b )
1474 {
1475 return a->m_OEMDesignRef < b->m_OEMDesignRef;
1476 } );
1477
1478 for( FOOTPRINT* fp_it : m_board->Footprints() )
1479 {
1480 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( fp_it->Clone() ) );
1481 fp->SetParentGroup( nullptr );
1482 fp->SetPosition( {0, 0} );
1483 fp->SetOrientation( ANGLE_0 );
1484
1485 // Normalize to unflipped state to match hash computed in addPackage
1486 if( fp->IsFlipped() )
1487 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
1488
1489 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
1490 auto iter = m_footprint_dict.find( hash );
1491
1492 if( iter == m_footprint_dict.end() )
1493 {
1494 Report( wxString::Format( _( "Footprint %s not found in dictionary; BOM data may be incomplete." ),
1495 fp->GetFPID().GetLibItemName().wx_str() ),
1497 continue;
1498 }
1499
1500 auto entry = std::make_unique<struct BOM_ENTRY>();
1501
1504 if( auto it = m_OEMRef_dict.find( fp_it ); it != m_OEMRef_dict.end() )
1505 {
1506 entry->m_OEMDesignRef = it->second;
1507 }
1508 else
1509 {
1510 Report( wxString::Format( _( "Component \"%s\" missing OEM reference; BOM entry will be skipped." ),
1511 fp->GetFPID().GetLibItemName().wx_str() ),
1513 }
1514
1515 entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" );
1516 entry->m_count = 1;
1517 entry->m_pads = fp->GetPadCount();
1518
1519 // TODO: The options are "ELECTRICAL", "MECHANICAL", "PROGRAMMABLE", "DOCUMENT", "MATERIAL"
1520 // We need to figure out how to determine this.
1521 const wxString variantName = m_board ? m_board->GetCurrentVariant() : wxString();
1522
1523 if( entry->m_pads == 0 || fp_it->GetExcludedFromBOMForVariant( variantName ) )
1524 entry->m_type = "DOCUMENT";
1525 else
1526 entry->m_type = "ELECTRICAL";
1527
1528 // Use the footprint's Description field if it exists
1529 const PCB_FIELD* descField = fp_it->GetField( FIELD_T::DESCRIPTION );
1530
1531 if( descField && !descField->GetShownText( false ).IsEmpty() )
1532 entry->m_description = descField->GetShownText( false );
1533
1534 auto[ bom_iter, inserted ] = bom_entries.insert( std::move( entry ) );
1535
1536 if( !inserted )
1537 ( *bom_iter )->m_count++;
1538
1539 REFDES refdes;
1540 refdes.m_name = componentName( fp_it );
1541 refdes.m_pkg = fp->GetFPID().GetLibItemName().wx_str();
1542 refdes.m_populate = !fp->GetDNPForVariant( variantName )
1543 && !fp->GetExcludedFromBOMForVariant( variantName );
1544 refdes.m_layer = m_layer_name_map[fp_it->GetLayer()];
1545
1546 ( *bom_iter )->m_refdes->push_back( refdes );
1547
1548 // TODO: This amalgamates all the properties from all the footprints. We need to decide
1549 // if we want to group footprints by their properties
1550 for( PCB_FIELD* prop : fp->GetFields() )
1551 {
1552 // We don't include Reference, Datasheet, or Description in BOM characteristics.
1553 // Value and any user-defined fields are included. Reference is captured above,
1554 // and Description is used for the BomItem description attribute.
1555 if( prop->IsMandatory() && !prop->IsValue() )
1556 continue;
1557
1558 ( *bom_iter )->m_props->emplace( prop->GetName(), prop->GetShownText( false ) );
1559 }
1560 }
1561
1562 if( bom_entries.empty() )
1563 return nullptr;
1564
1565 wxFileName fn( m_board->GetFileName() );
1566
1567 wxXmlNode* bomNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Bom" );
1568 m_xml_root->InsertChild( bomNode, aEcadNode );
1569 addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) );
1570
1571 wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" );
1572 addAttribute( bomHeaderNode, "revision", "1.0" );
1573 addAttribute( bomHeaderNode, "assembly", genString( fn.GetName() ) );
1574
1575 wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" );
1576 addAttribute( stepRefNode, "name", genString( fn.GetName(), "BOARD" ) );
1577
1578 for( const auto& entry : bom_entries )
1579 {
1580 wxXmlNode* bomEntryNode = appendNode( bomNode, "BomItem" );
1581 addAttribute( bomEntryNode, "OEMDesignNumberRef", entry->m_OEMDesignRef );
1582 addAttribute( bomEntryNode, "quantity", wxString::Format( "%d", entry->m_count ) );
1583 addAttribute( bomEntryNode, "pinCount", wxString::Format( "%d", entry->m_pads ) );
1584 addAttribute( bomEntryNode, "category", entry->m_type );
1585
1586 if( !entry->m_description.IsEmpty() )
1587 addAttribute( bomEntryNode, "description", entry->m_description );
1588
1589 for( const REFDES& refdes : *( entry->m_refdes ) )
1590 {
1591 wxXmlNode* refdesNode = appendNode( bomEntryNode, "RefDes" );
1592 addAttribute( refdesNode, "name", refdes.m_name );
1593 addAttribute( refdesNode, "packageRef", genString( refdes.m_pkg, "PKG" ) );
1594 addAttribute( refdesNode, "populate", refdes.m_populate ? "true" : "false" );
1595 addAttribute( refdesNode, "layerRef", refdes.m_layer );
1596 }
1597
1598 wxXmlNode* characteristicsNode = appendNode( bomEntryNode, "Characteristics" );
1599 addAttribute( characteristicsNode, "category", entry->m_type );
1600
1601 for( const auto& prop : *( entry->m_props ) )
1602 {
1603 wxXmlNode* textualDefNode = appendNode( characteristicsNode, "Textual" );
1604 addAttribute( textualDefNode, "definitionSource", "KICAD" );
1605 addAttribute( textualDefNode, "textualCharacteristicName", prop.first );
1606 addAttribute( textualDefNode, "textualCharacteristicValue", prop.second );
1607 }
1608 }
1609
1610 return bomNode;
1611}
1612
1613
1615{
1616 if( m_progressReporter )
1617 m_progressReporter->AdvancePhase( _( "Generating CAD data" ) );
1618
1619 wxXmlNode* ecadNode = appendNode( m_xml_root, "Ecad" );
1620 addAttribute( ecadNode, "name", "Design" );
1621
1622 addCadHeader( ecadNode );
1623
1624 wxXmlNode* cadDataNode = appendNode( ecadNode, "CadData" );
1625 generateCadLayers( cadDataNode );
1626 generateDrillLayers( cadDataNode );
1627 generateAuxilliaryLayers( cadDataNode );
1628 generateStackup( cadDataNode );
1629 generateStepSection( cadDataNode );
1630
1632
1633 return ecadNode;
1634}
1635
1636
1637void PCB_IO_IPC2581::generateCadSpecs( wxXmlNode* aCadLayerNode )
1638{
1639 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1640 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1641 stackup.SynchronizeWithBoard( &dsnSettings );
1642
1643 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1644 std::set<PCB_LAYER_ID> added_layers;
1645
1646 for( int i = 0; i < stackup.GetCount(); i++ )
1647 {
1648 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1649
1650 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1651 {
1652 wxString ly_name = stackup_item->GetLayerName();
1653
1654 if( ly_name.IsEmpty() )
1655 {
1656 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1657 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1658
1659 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1660 {
1661 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1662
1663 if( sublayer_id > 0 )
1664 ly_name += wxString::Format( "_%d", sublayer_id );
1665 }
1666 }
1667
1668 ly_name = genString( ly_name, "SPEC_LAYER" );
1669
1670 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1671 addAttribute( specNode, "name", ly_name );
1672 wxXmlNode* generalNode = appendNode( specNode, "General" );
1673 addAttribute( generalNode, "type", "MATERIAL" );
1674 wxXmlNode* propertyNode = appendNode( generalNode, "Property" );
1675
1676 switch ( stackup_item->GetType() )
1677 {
1679 {
1680 addAttribute( propertyNode, "text", "COPPER" );
1681 wxXmlNode* conductorNode = appendNode( specNode, "Conductor" );
1682 addAttribute( conductorNode, "type", "CONDUCTIVITY" );
1683 propertyNode = appendNode( conductorNode, "Property" );
1684 addAttribute( propertyNode, "unit", wxT( "SIEMENS/M" ) );
1685 addAttribute( propertyNode, "value", wxT( "5.959E7" ) );
1686 break;
1687 }
1689 {
1690 addAttribute( propertyNode, "text", stackup_item->GetMaterial() );
1691 propertyNode = appendNode( generalNode, "Property" );
1692 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1693 stackup_item->GetTypeName() ) );
1694 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1695 addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
1696 propertyNode = appendNode( dielectricNode, "Property" );
1697 addAttribute( propertyNode, "value",
1698 floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
1699 dielectricNode = appendNode( specNode, "Dielectric" );
1700 addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
1701 propertyNode = appendNode( dielectricNode, "Property" );
1702 addAttribute( propertyNode, "value",
1703 floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
1704 break;
1705 }
1707 addAttribute( propertyNode, "text", stackup_item->GetTypeName() );
1708 propertyNode = appendNode( generalNode, "Property" );
1709 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1710 stackup_item->GetColor() ) );
1711 propertyNode = appendNode( generalNode, "Property" );
1712 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1713 stackup_item->GetTypeName() ) );
1714 break;
1716 {
1717 addAttribute( propertyNode, "text", "SOLDERMASK" );
1718 propertyNode = appendNode( generalNode, "Property" );
1719 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1720 stackup_item->GetColor() ) );
1721 propertyNode = appendNode( generalNode, "Property" );
1722 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1723 stackup_item->GetTypeName() ) );
1724
1725 // Generate Epsilon R if > 1.0 (value <= 1.0 means not specified)
1726 if( stackup_item->GetEpsilonR( sublayer_id ) > 1.0 )
1727 {
1728 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1729 addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
1730 propertyNode = appendNode( dielectricNode, "Property" );
1731 addAttribute( propertyNode, "value", floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
1732 }
1733
1734 // Generate LossTangent if > 0.0 (value <= 0.0 means not specified)
1735 if( stackup_item->GetLossTangent( sublayer_id ) > 0.0 )
1736 {
1737 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1738 addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
1739 propertyNode = appendNode( dielectricNode, "Property" );
1740 addAttribute( propertyNode, "value", floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
1741 }
1742 break;
1743 }
1744 default:
1745 break;
1746 }
1747 }
1748 }
1749
1750 // Generate SurfaceFinish spec from board's copper finish setting
1751 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1752
1753 if( finishType != surfaceFinishType::NONE )
1754 {
1755 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1756 addAttribute( specNode, "name", "SURFACE_FINISH" );
1757
1758 wxXmlNode* surfaceFinishNode = appendNode( specNode, "SurfaceFinish" );
1759 wxXmlNode* finishNode = appendNode( surfaceFinishNode, "Finish" );
1760 addAttribute( finishNode, "type", surfaceFinishTypeToString.at( finishType ) );
1761
1762 // Add original finish string as comment if it maps to OTHER
1763 if( finishType == surfaceFinishType::OTHER )
1764 addAttribute( finishNode, "comment", stackup.m_FinishType );
1765 }
1766}
1767
1768
1769void PCB_IO_IPC2581::addCadHeader( wxXmlNode* aEcadNode )
1770{
1771 wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" );
1772 addAttribute( cadHeaderNode, "units", m_units_str );
1773
1774 m_cad_header_node = cadHeaderNode;
1775
1776 generateCadSpecs( cadHeaderNode );
1777}
1778
1779
1781{
1782 return ( aLayer >= F_Cu && aLayer <= User_9 ) || aLayer == UNDEFINED_LAYER;
1783}
1784
1785
1786void PCB_IO_IPC2581::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer )
1787{
1788 switch( aLayer )
1789 {
1790 case F_Adhes:
1791 case B_Adhes:
1792 addAttribute( aNode, "layerFunction", "GLUE" );
1793 addAttribute( aNode, "polarity", "POSITIVE" );
1794 addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" );
1795 break;
1796 case F_Paste:
1797 case B_Paste:
1798 addAttribute( aNode, "layerFunction", "SOLDERPASTE" );
1799 addAttribute( aNode, "polarity", "POSITIVE" );
1800 addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" );
1801 break;
1802 case F_SilkS:
1803 case B_SilkS:
1804 addAttribute( aNode, "layerFunction", "SILKSCREEN" );
1805 addAttribute( aNode, "polarity", "POSITIVE" );
1806 addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" );
1807 break;
1808 case F_Mask:
1809 case B_Mask:
1810 addAttribute( aNode, "layerFunction", "SOLDERMASK" );
1811 addAttribute( aNode, "polarity", "POSITIVE" );
1812 addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" );
1813 break;
1814 case Edge_Cuts:
1815 addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" );
1816 addAttribute( aNode, "polarity", "POSITIVE" );
1817 addAttribute( aNode, "side", "ALL" );
1818 break;
1819 case B_CrtYd:
1820 case F_CrtYd:
1821 addAttribute( aNode, "layerFunction", "COURTYARD" );
1822 addAttribute( aNode, "polarity", "POSITIVE" );
1823 addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" );
1824 break;
1825 case B_Fab:
1826 case F_Fab:
1827 addAttribute( aNode, "layerFunction", "ASSEMBLY" );
1828 addAttribute( aNode, "polarity", "POSITIVE" );
1829 addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" );
1830 break;
1831 case Dwgs_User:
1832 case Cmts_User:
1833 case Eco1_User:
1834 case Eco2_User:
1835 case Margin:
1836 case User_1:
1837 case User_2:
1838 case User_3:
1839 case User_4:
1840 case User_5:
1841 case User_6:
1842 case User_7:
1843 case User_8:
1844 case User_9:
1845 addAttribute( aNode, "layerFunction", "DOCUMENT" );
1846 addAttribute( aNode, "polarity", "POSITIVE" );
1847 addAttribute( aNode, "side", "NONE" );
1848 break;
1849
1850 default:
1851 if( IsCopperLayer( aLayer ) )
1852 {
1853 addAttribute( aNode, "layerFunction", "CONDUCTOR" );
1854 addAttribute( aNode, "polarity", "POSITIVE" );
1855 addAttribute( aNode, "side",
1856 aLayer == F_Cu ? "TOP"
1857 : aLayer == B_Cu ? "BOTTOM"
1858 : "INTERNAL" );
1859 }
1860
1861 break; // Do not handle other layers
1862 }
1863}
1864
1865
1866void PCB_IO_IPC2581::generateStackup( wxXmlNode* aCadLayerNode )
1867{
1868 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1869 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1870 stackup.SynchronizeWithBoard( &dsnSettings );
1871
1872 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1873 bool hasCoating = ( finishType != surfaceFinishType::NONE );
1874
1875 wxXmlNode* stackupNode = appendNode( aCadLayerNode, "Stackup" );
1876 addAttribute( stackupNode, "name", "Primary_Stackup" );
1877 addAttribute( stackupNode, "overallThickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1878 addAttribute( stackupNode, "tolPlus", "0.0" );
1879 addAttribute( stackupNode, "tolMinus", "0.0" );
1880 addAttribute( stackupNode, "whereMeasured", "MASK" );
1881
1882 if( m_version > 'B' )
1883 addAttribute( stackupNode, "stackupStatus", "PROPOSED" );
1884
1885 wxXmlNode* stackupGroup = appendNode( stackupNode, "StackupGroup" );
1886 addAttribute( stackupGroup, "name", "Primary_Stackup_Group" );
1887 addAttribute( stackupGroup, "thickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1888 addAttribute( stackupGroup, "tolPlus", "0.0" );
1889 addAttribute( stackupGroup, "tolMinus", "0.0" );
1890
1891 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1892 std::set<PCB_LAYER_ID> added_layers;
1893 int sequence = 0;
1894
1895 for( int i = 0; i < stackup.GetCount(); i++ )
1896 {
1897 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1898
1899 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1900 {
1901 PCB_LAYER_ID layer_id = stackup_item->GetBrdLayerId();
1902
1903 // Insert top coating layer before F.Cu
1904 if( hasCoating && layer_id == F_Cu && sublayer_id == 0 )
1905 {
1906 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
1907 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_TOP" );
1908 addAttribute( coatingLayer, "thickness", "0.0" );
1909 addAttribute( coatingLayer, "tolPlus", "0.0" );
1910 addAttribute( coatingLayer, "tolMinus", "0.0" );
1911 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1912
1913 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
1914 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
1915 }
1916
1917 wxXmlNode* stackupLayer = appendNode( stackupGroup, "StackupLayer" );
1918 wxString ly_name = stackup_item->GetLayerName();
1919
1920 if( ly_name.IsEmpty() )
1921 {
1922 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1923 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1924
1925 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1926 {
1927 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1928
1929 if( sublayer_id > 0 )
1930 ly_name += wxString::Format( "_%d", sublayer_id );
1931 }
1932 }
1933
1934 wxString spec_name = genString( ly_name, "SPEC_LAYER" );
1935 ly_name = genString( ly_name, "LAYER" );
1936
1937 addAttribute( stackupLayer, "layerOrGroupRef", ly_name );
1938 addAttribute( stackupLayer, "thickness", floatVal( m_scale * stackup_item->GetThickness() ) );
1939 addAttribute( stackupLayer, "tolPlus", "0.0" );
1940 addAttribute( stackupLayer, "tolMinus", "0.0" );
1941 addAttribute( stackupLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1942
1943 wxXmlNode* specLayerNode = appendNode( stackupLayer, "SpecRef" );
1944 addAttribute( specLayerNode, "id", spec_name );
1945
1946 // Insert bottom coating layer after B.Cu
1947 if( hasCoating && layer_id == B_Cu && sublayer_id == stackup_item->GetSublayersCount() - 1 )
1948 {
1949 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
1950 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_BOTTOM" );
1951 addAttribute( coatingLayer, "thickness", "0.0" );
1952 addAttribute( coatingLayer, "tolPlus", "0.0" );
1953 addAttribute( coatingLayer, "tolMinus", "0.0" );
1954 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1955
1956 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
1957 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
1958 }
1959 }
1960 }
1961}
1962
1963
1964void PCB_IO_IPC2581::generateCadLayers( wxXmlNode* aCadLayerNode )
1965{
1966
1967 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1968 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1969 stackup.SynchronizeWithBoard( &dsnSettings );
1970
1971 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1972 std::set<PCB_LAYER_ID> added_layers;
1973
1974 for( int i = 0; i < stackup.GetCount(); i++ )
1975 {
1976 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1977
1978 if( !isValidLayerFor2581( stackup_item->GetBrdLayerId() ) )
1979 continue;
1980
1981 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1982 {
1983 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
1984 wxString ly_name = stackup_item->GetLayerName();
1985
1986 if( ly_name.IsEmpty() )
1987 {
1988
1989 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1990 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1991
1992 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1993 {
1994 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1995
1996 if( sublayer_id > 0 )
1997 ly_name += wxString::Format( "_%d", sublayer_id );
1998 }
1999 }
2000
2001 ly_name = genString( ly_name, "LAYER" );
2002
2003 addAttribute( cadLayerNode, "name", ly_name );
2004
2005 if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2006 {
2007 if( stackup_item->GetTypeName() == KEY_CORE )
2008 addAttribute( cadLayerNode, "layerFunction", "DIELCORE" );
2009 else
2010 addAttribute( cadLayerNode, "layerFunction", "DIELPREG" );
2011
2012 addAttribute( cadLayerNode, "polarity", "POSITIVE" );
2013 addAttribute( cadLayerNode, "side", "INTERNAL" );
2014 continue;
2015 }
2016 else
2017 {
2018 added_layers.insert( stackup_item->GetBrdLayerId() );
2019 addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() );
2020 m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name );
2021 }
2022 }
2023 }
2024
2025 LSEQ layer_seq = m_board->GetEnabledLayers().Seq();
2026
2027 for( PCB_LAYER_ID layer : layer_seq )
2028 {
2029 if( added_layers.find( layer ) != added_layers.end() || !isValidLayerFor2581( layer ) )
2030 continue;
2031
2032 wxString ly_name = genLayerString( layer, "LAYER" );
2033 m_layer_name_map.emplace( layer, ly_name );
2034 added_layers.insert( layer );
2035 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
2036 addAttribute( cadLayerNode, "name", ly_name );
2037
2038 addLayerAttributes( cadLayerNode, layer );
2039 }
2040
2041 // Generate COATINGCOND layers for surface finish if specified
2042 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
2043
2044 if( finishType != surfaceFinishType::NONE )
2045 {
2046 wxXmlNode* topCoatingNode = appendNode( aCadLayerNode, "Layer" );
2047 addAttribute( topCoatingNode, "name", "COATING_TOP" );
2048 addAttribute( topCoatingNode, "layerFunction", "COATINGCOND" );
2049 addAttribute( topCoatingNode, "side", "TOP" );
2050 addAttribute( topCoatingNode, "polarity", "POSITIVE" );
2051
2052 wxXmlNode* botCoatingNode = appendNode( aCadLayerNode, "Layer" );
2053 addAttribute( botCoatingNode, "name", "COATING_BOTTOM" );
2054 addAttribute( botCoatingNode, "layerFunction", "COATINGCOND" );
2055 addAttribute( botCoatingNode, "side", "BOTTOM" );
2056 addAttribute( botCoatingNode, "polarity", "POSITIVE" );
2057 }
2058}
2059
2060
2061void PCB_IO_IPC2581::generateDrillLayers( wxXmlNode* aCadLayerNode )
2062{
2063 for( BOARD_ITEM* item : m_board->Tracks() )
2064 {
2065 if( item->Type() == PCB_VIA_T )
2066 {
2067 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2068 m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via );
2069 }
2070 }
2071
2072 for( FOOTPRINT* fp : m_board->Footprints() )
2073 {
2074 for( PAD* pad : fp->Pads() )
2075 {
2076 if( pad->HasDrilledHole() )
2077 m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2078 else if( pad->HasHole() )
2079 m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2080 }
2081 }
2082
2083 for( const auto& [layers, vec] : m_drill_layers )
2084 {
2085 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2086 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "DRILL" ) );
2087 addAttribute( drillNode, "layerFunction", "DRILL" );
2088 addAttribute( drillNode, "polarity", "POSITIVE" );
2089 addAttribute( drillNode, "side", "ALL" );
2090
2091 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2092 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2093 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2094 }
2095
2096 for( const auto& [layers, vec] : m_slot_holes )
2097 {
2098 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2099 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "SLOT" ) );
2100
2101 addAttribute( drillNode, "layerFunction", "ROUT" );
2102 addAttribute( drillNode, "polarity", "POSITIVE" );
2103 addAttribute( drillNode, "side", "ALL" );
2104
2105 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2106 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2107 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2108 }
2109}
2110
2111
2112void PCB_IO_IPC2581::generateAuxilliaryLayers( wxXmlNode* aCadLayerNode )
2113{
2114 for( BOARD_ITEM* item : m_board->Tracks() )
2115 {
2116 if( item->Type() != PCB_VIA_T )
2117 continue;
2118
2119 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2120
2121 std::vector<std::tuple<auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID>> new_layers;
2122
2123 if( via->Padstack().IsFilled().value_or( false ) )
2124 new_layers.emplace_back( auxLayerType::FILLING, via->TopLayer(), via->BottomLayer() );
2125
2126 if( via->Padstack().IsCapped().value_or( false ) )
2127 new_layers.emplace_back( auxLayerType::CAPPING, via->TopLayer(), via->BottomLayer() );
2128
2129 for( PCB_LAYER_ID layer : { via->TopLayer(), via->BottomLayer() } )
2130 {
2131 if( via->Padstack().IsPlugged( layer ).value_or( false ) )
2132 new_layers.emplace_back( auxLayerType::PLUGGING, layer, UNDEFINED_LAYER );
2133
2134 if( via->Padstack().IsCovered( layer ).value_or( false ) )
2135 new_layers.emplace_back( auxLayerType::COVERING, layer, UNDEFINED_LAYER );
2136
2137 if( via->Padstack().IsTented( layer ).value_or( false ) )
2138 new_layers.emplace_back( auxLayerType::TENTING, layer, UNDEFINED_LAYER );
2139 }
2140
2141 for( auto& tuple : new_layers )
2142 m_auxilliary_Layers[tuple].push_back( via );
2143 }
2144
2145 for( const auto& [layers, vec] : m_auxilliary_Layers )
2146 {
2147 bool add_node = true;
2148
2149 wxString name;
2150 wxString layerFunction;
2151
2152 // clang-format off: suggestion is inconsitent
2153 switch( std::get<0>(layers) )
2154 {
2156 name = "COVERING";
2157 layerFunction = "COATINGNONCOND";
2158 break;
2160 name = "PLUGGING";
2161 layerFunction = "HOLEFILL";
2162 break;
2164 name = "TENTING";
2165 layerFunction = "COATINGNONCOND";
2166 break;
2168 name = "FILLING";
2169 layerFunction = "HOLEFILL";
2170 break;
2172 name = "CAPPING";
2173 layerFunction = "COATINGCOND";
2174 break;
2175 default:
2176 add_node = false;
2177 break;
2178 }
2179 // clang-format on: suggestion is inconsitent
2180
2181 if( add_node && !vec.empty() )
2182 {
2183 wxXmlNode* node = appendNode( aCadLayerNode, "LAYER" );
2184 addAttribute( node, "layerFunction", layerFunction );
2185 addAttribute( node, "polarity", "POSITIVE" );
2186
2187 if( std::get<2>( layers ) == UNDEFINED_LAYER )
2188 {
2189 addAttribute( node, "name", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
2190 addAttribute( node, "side", IsFrontLayer( std::get<1>( layers ) ) ? "TOP" : "BOTTOM" );
2191 }
2192 else
2193 {
2194 addAttribute( node, "name",
2195 genLayersString( std::get<1>( layers ), std::get<2>( layers ), TO_UTF8( name ) ) );
2196
2197 const bool first_external = std::get<1>( layers ) == F_Cu || std::get<1>( layers ) == B_Cu;
2198 const bool second_external = std::get<2>( layers ) == F_Cu || std::get<2>( layers ) == B_Cu;
2199
2200 if( first_external )
2201 {
2202 if( second_external )
2203 addAttribute( node, "side", "ALL" );
2204 else
2205 addAttribute( node, "side", "FRONT" );
2206 }
2207 else
2208 {
2209 if( second_external )
2210 addAttribute( node, "side", "BACK" );
2211 else
2212 addAttribute( node, "side", "INTERNAL" );
2213 }
2214
2215 wxXmlNode* spanNode = appendNode( node, "SPAN" );
2216 addAttribute( spanNode, "fromLayer", genLayerString( std::get<1>( layers ), "LAYER" ) );
2217 addAttribute( spanNode, "toLayer", genLayerString( std::get<2>( layers ), "LAYER" ) );
2218 }
2219 }
2220 }
2221}
2222
2223
2224void PCB_IO_IPC2581::generateStepSection( wxXmlNode* aCadNode )
2225{
2226 wxXmlNode* stepNode = appendNode( aCadNode, "Step" );
2227 wxFileName fn( m_board->GetFileName() );
2228 addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) );
2229
2230 if( m_version > 'B' )
2231 addAttribute( stepNode, "type", "BOARD" );
2232
2233 wxXmlNode* datumNode = appendNode( stepNode, "Datum" );
2234 addAttribute( datumNode, "x", "0.0" );
2235 addAttribute( datumNode, "y", "0.0" );
2236
2237 generateProfile( stepNode );
2238 generateComponents( stepNode );
2239
2240 m_last_padstack = insertNode( stepNode, "NonstandardAttribute" );
2241 addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" );
2242 addAttribute( m_last_padstack, "type", "INTEGER" );
2243 addAttribute( m_last_padstack, "value", wxString::Format( "%zu", m_board->Footprints().size() ) );
2244
2245 generateLayerFeatures( stepNode );
2246 generateLayerSetDrill( stepNode );
2247 generateLayerSetAuxilliary( stepNode );
2248}
2249
2250
2251void PCB_IO_IPC2581::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer )
2252{
2253 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2254 FOOTPRINT* fp = aPad->GetParentFootprint();
2255
2256 addPadStack( padNode, aPad );
2257
2258 if( aPad->GetOrientation() != ANGLE_0 )
2259 {
2260 wxXmlNode* xformNode = appendNode( padNode, "Xform" );
2261 EDA_ANGLE angle = aPad->GetOrientation().Normalize();
2262
2263 xformNode->AddAttribute( "rotation", floatVal( angle.AsDegrees() ) );
2264 }
2265
2266 addLocationNode( padNode, *aPad, false );
2267 addShape( padNode, *aPad, aLayer );
2268
2269 if( fp )
2270 {
2271 wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" );
2272
2273 addAttribute( pinRefNode, "componentRef", componentName( fp ) );
2274 addAttribute( pinRefNode, "pin", pinName( aPad ) );
2275 }
2276}
2277
2278
2279void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
2280{
2281 if( !aVia->FlashLayer( aLayer ) )
2282 return;
2283
2284 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2285
2286 addPadStack( padNode, aVia );
2287 addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y );
2288
2289 PAD dummy( nullptr );
2290 int hole = aVia->GetDrillValue();
2291 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
2292 dummy.SetPosition( aVia->GetStart() );
2293 dummy.SetSize( aLayer, VECTOR2I( aVia->GetWidth( aLayer ), aVia->GetWidth( aLayer ) ) );
2294
2295 addShape( padNode, dummy, aLayer );
2296}
2297
2298
2299void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad )
2300{
2301 size_t hash = hash_fp_item( aPad, 0 );
2302 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2303 auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name );
2304
2305 addAttribute( aPadNode, "padstackDefRef", th_pair->second );
2306
2307 // If we did not insert a new padstack, then we have already added it to the XML
2308 // and we don't need to add it again.
2309 if( !success )
2310 return;
2311
2312 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2313 addAttribute( padStackDefNode, "name", name );
2315 m_padstacks.push_back( padStackDefNode );
2316
2317 if( m_last_padstack )
2318 {
2319 insertNodeAfter( m_last_padstack, padStackDefNode );
2320 m_last_padstack = padStackDefNode;
2321 }
2322
2323 // Only handle round holes here because IPC2581 does not support non-round holes
2324 // These will be handled in a slot layer
2325 if( aPad->HasDrilledHole() )
2326 {
2327 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2328 padStackHoleNode->AddAttribute( "name",
2329 wxString::Format( "%s%d_%d",
2330 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH",
2331 aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
2332
2333 addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) );
2334 addAttribute( padStackHoleNode, "platingStatus",
2335 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
2336 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2337 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2338 addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2339 }
2340
2341 LSEQ layer_seq = aPad->GetLayerSet().Seq();
2342
2343 for( PCB_LAYER_ID layer : layer_seq )
2344 {
2345 FOOTPRINT* fp = aPad->GetParentFootprint();
2346
2347 if( !m_board->IsLayerEnabled( layer ) )
2348 continue;
2349
2350 wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
2351 addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
2352 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2353 addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y );
2354
2355 if( aPad->HasHole() || !aPad->FlashLayer( layer ) )
2356 {
2357 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2358 shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2359 shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 );
2360 addShape( padStackPadDefNode, shape );
2361 }
2362 else
2363 {
2364 addShape( padStackPadDefNode, *aPad, layer );
2365 }
2366 }
2367}
2368
2369
2370void PCB_IO_IPC2581::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia )
2371{
2372 size_t hash = hash_fp_item( aVia, 0 );
2373 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2374 auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name );
2375
2376 addAttribute( aContentNode, "padstackDefRef", via_pair->second );
2377
2378 // If we did not insert a new padstack, then we have already added it to the XML
2379 // and we don't need to add it again.
2380 if( !success )
2381 return;
2382
2383 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2384 insertNodeAfter( m_last_padstack, padStackDefNode );
2385 m_last_padstack = padStackDefNode;
2386 addAttribute( padStackDefNode, "name", name );
2388
2389 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2390 addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) );
2391 padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) );
2392 addAttribute( padStackHoleNode, "platingStatus", "VIA" );
2393 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2394 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2395 addAttribute( padStackHoleNode, "x", "0.0" );
2396 addAttribute( padStackHoleNode, "y", "0.0" );
2397
2398 LSEQ layer_seq = aVia->GetLayerSet().Seq();
2399
2400 auto addPadShape{ [&]( PCB_LAYER_ID layer, const PCB_VIA* aVia, const wxString& name,
2401 bool drill ) -> void
2402 {
2403 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2404
2405 if( drill )
2406 shape.SetEnd( { KiROUND( aVia->GetDrillValue() / 2.0 ), 0 } );
2407 else
2408 shape.SetEnd( { KiROUND( aVia->GetWidth( layer ) / 2.0 ), 0 } );
2409
2410 wxXmlNode* padStackPadDefNode =
2411 appendNode( padStackDefNode, "PadstackPadDef" );
2412 addAttribute( padStackPadDefNode, "layerRef", name );
2413 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2414
2415 addLocationNode( padStackPadDefNode, 0.0, 0.0 );
2416 addShape( padStackPadDefNode, shape );
2417 } };
2418
2419 for( PCB_LAYER_ID layer : layer_seq )
2420 {
2421 if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) )
2422 continue;
2423
2424 addPadShape( layer, aVia, m_layer_name_map[layer], false );
2425 }
2426
2427 if( aVia->Padstack().IsFilled().value_or( false ) )
2428 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "FILLING" ), true );
2429
2430 if( aVia->Padstack().IsCapped().value_or( false ) )
2431 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "CAPPING" ), true );
2432
2433 for( PCB_LAYER_ID layer : { aVia->TopLayer(), aVia->BottomLayer() } )
2434 {
2435 if( aVia->Padstack().IsPlugged( layer ).value_or( false ) )
2436 addPadShape( layer, aVia, genLayerString( layer, "PLUGGING" ), true );
2437
2438 if( aVia->Padstack().IsCovered( layer ).value_or( false ) )
2439 addPadShape( layer, aVia, genLayerString( layer, "COVERING" ), false );
2440
2441 if( aVia->Padstack().IsTented( layer ).value_or( false ) )
2442 addPadShape( layer, aVia, genLayerString( layer, "TENTING" ), false );
2443 }
2444}
2445
2446
2447void PCB_IO_IPC2581::ensureBackdrillSpecs( const wxString& aPadstackName, const PADSTACK& aPadstack )
2448{
2449 if( m_padstack_backdrill_specs.find( aPadstackName ) != m_padstack_backdrill_specs.end() )
2450 return;
2451
2452 const PADSTACK::DRILL_PROPS& secondary = aPadstack.SecondaryDrill();
2453
2454 if( secondary.start == UNDEFINED_LAYER || secondary.end == UNDEFINED_LAYER )
2455 return;
2456
2457 if( secondary.size.x <= 0 && secondary.size.y <= 0 )
2458 return;
2459
2460 if( !m_cad_header_node )
2461 return;
2462
2463 auto layerHasRef = [&]( PCB_LAYER_ID aLayer ) -> bool
2464 {
2465 return m_layer_name_map.find( aLayer ) != m_layer_name_map.end();
2466 };
2467
2468 if( !layerHasRef( secondary.start ) || !layerHasRef( secondary.end ) )
2469 return;
2470
2471 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
2472 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
2473 stackup.SynchronizeWithBoard( &dsnSettings );
2474
2475 auto createSpec = [&]( const PADSTACK::DRILL_PROPS& aDrill, const wxString& aSpecName ) -> wxString
2476 {
2477 if( aDrill.start == UNDEFINED_LAYER || aDrill.end == UNDEFINED_LAYER )
2478 return wxString();
2479
2480 auto startLayer = m_layer_name_map.find( aDrill.start );
2481 auto endLayer = m_layer_name_map.find( aDrill.end );
2482
2483 if( startLayer == m_layer_name_map.end() || endLayer == m_layer_name_map.end() )
2484 return wxString();
2485
2486 wxXmlNode* specNode = appendNode( m_cad_header_node, "Spec" );
2487 addAttribute( specNode, "name", aSpecName );
2488
2489 wxXmlNode* backdrillNode = appendNode( specNode, "Backdrill" );
2490 addAttribute( backdrillNode, "startLayerRef", startLayer->second );
2491 addAttribute( backdrillNode, "mustNotCutLayerRef", endLayer->second );
2492
2493 int stubLength = stackup.GetLayerDistance( aDrill.start, aDrill.end );
2494
2495 if( stubLength < 0 )
2496 stubLength = 0;
2497
2498 addAttribute( backdrillNode, "maxStubLength", floatVal( m_scale * stubLength ) );
2499
2501
2502 if( aDrill.start == F_Cu )
2503 pm_mode = aPadstack.FrontPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2504 else if( aDrill.start == B_Cu )
2505 pm_mode = aPadstack.BackPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2506
2507 bool isPostMachined = ( pm_mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ||
2509
2510 addAttribute( backdrillNode, "postMachining", isPostMachined ? wxT( "true" )
2511 : wxT( "false" ) );
2512
2513 m_backdrill_spec_nodes[aSpecName] = specNode;
2514
2515 return aSpecName;
2516 };
2517
2518 int specIndex = m_backdrill_spec_index + 1;
2519
2520 const PADSTACK::DRILL_PROPS& primary = aPadstack.Drill();
2521 wxString primarySpec = createSpec( primary, wxString::Format( wxT( "BD_%dA" ), specIndex ) );
2522
2523 wxString secondarySpec = createSpec( secondary, wxString::Format( wxT( "BD_%dB" ), specIndex ) );
2524
2525 if( primarySpec.IsEmpty() && secondarySpec.IsEmpty() )
2526 return;
2527
2528 m_backdrill_spec_index = specIndex;
2529 m_padstack_backdrill_specs.emplace( aPadstackName, std::make_pair( primarySpec, secondarySpec ) );
2530}
2531
2532
2533void PCB_IO_IPC2581::addBackdrillSpecRefs( wxXmlNode* aHoleNode, const wxString& aPadstackName )
2534{
2535 auto it = m_padstack_backdrill_specs.find( aPadstackName );
2536
2537 if( it == m_padstack_backdrill_specs.end() )
2538 return;
2539
2540 auto addRef = [&]( const wxString& aSpecName )
2541 {
2542 if( aSpecName.IsEmpty() )
2543 return;
2544
2545 wxXmlNode* specRefNode = appendNode( aHoleNode, "SpecRef" );
2546 addAttribute( specRefNode, "id", aSpecName );
2547 m_backdrill_spec_used.insert( aSpecName );
2548 };
2549
2550 addRef( it->second.first );
2551 addRef( it->second.second );
2552}
2553
2554
2556{
2557 if( !m_cad_header_node )
2558 return;
2559
2560 auto it = m_backdrill_spec_nodes.begin();
2561
2562 while( it != m_backdrill_spec_nodes.end() )
2563 {
2564 if( m_backdrill_spec_used.find( it->first ) == m_backdrill_spec_used.end() )
2565 {
2566 wxXmlNode* specNode = it->second;
2567
2568 if( specNode )
2569 {
2570 m_cad_header_node->RemoveChild( specNode );
2571 deleteNode( specNode );
2572 }
2573
2574 it = m_backdrill_spec_nodes.erase( it );
2575 }
2576 else
2577 {
2578 ++it;
2579 }
2580 }
2581}
2582
2583
2584bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
2585 const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
2586 int aWidth, LINE_STYLE aDashType )
2587{
2588 wxXmlNode* polygonNode = nullptr;
2589
2590 if( aPolygon.PointCount() < 3 )
2591 return false;
2592
2593 auto make_node =
2594 [&]()
2595 {
2596 polygonNode = appendNode( aParentNode, "Polygon" );
2597 wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
2598
2599 const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
2600 addXY( polybeginNode, pts[0] );
2601
2602 for( size_t ii = 1; ii < pts.size(); ++ii )
2603 {
2604 wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
2605 addXY( polyNode, pts[ii] );
2606 }
2607
2608 wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
2609 addXY( polyendNode, pts[0] );
2610 };
2611
2612 // Allow the case where we don't want line/fill information in the polygon
2613 if( aFillType == FILL_T::NO_FILL )
2614 {
2615 make_node();
2616 // If we specify a line width, we need to add a LineDescRef node and
2617 // since this is only valid for a non-filled polygon, we need to create
2618 // the fillNode as well
2619 if( aWidth > 0 )
2620 addLineDesc( polygonNode, aWidth, aDashType, true );
2621 }
2622 else
2623 {
2624 wxCHECK( aWidth == 0, false );
2625 make_node();
2626 }
2627
2628 addFillDesc( polygonNode, aFillType );
2629
2630 return true;
2631}
2632
2633
2634bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
2635 const SHAPE_POLY_SET::POLYGON& aPolygon )
2636{
2637 for( size_t ii = 1; ii < aPolygon.size(); ++ii )
2638 {
2639 wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
2640
2641 wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
2642 wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
2643
2644 const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
2645 addXY( polybeginNode, hole[0] );
2646
2647 for( size_t jj = 1; jj < hole.size(); ++jj )
2648 {
2649 wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
2650 addXY( polyNode, hole[jj] );
2651 }
2652
2653 wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
2654 addXY( polyendNode, hole[0] );
2655 }
2656
2657 return true;
2658}
2659
2660
2661bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2662 int aWidth, LINE_STYLE aDashType )
2663{
2664 if( aPolySet.OutlineCount() == 0 )
2665 return false;
2666
2667 wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
2668
2669 // Outlines can only have one polygon according to the IPC-2581 spec, so
2670 // if there are more than one, we need to combine them into a single polygon
2671 const SHAPE_LINE_CHAIN* outline = &aPolySet.Outline( 0 );
2672 SHAPE_LINE_CHAIN bbox_outline;
2673 BOX2I bbox = outline->BBox();
2674
2675 if( aPolySet.OutlineCount() > 1 )
2676 {
2677 for( int ii = 1; ii < aPolySet.OutlineCount(); ++ii )
2678 {
2679 wxCHECK2( aPolySet.Outline( ii ).PointCount() >= 3, continue );
2680 bbox.Merge( aPolySet.Outline( ii ).BBox() );
2681 }
2682
2683 bbox_outline.Append( bbox.GetLeft(), bbox.GetTop() );
2684 bbox_outline.Append( bbox.GetRight(), bbox.GetTop() );
2685 bbox_outline.Append( bbox.GetRight(), bbox.GetBottom() );
2686 bbox_outline.Append( bbox.GetLeft(), bbox.GetBottom() );
2687 outline = &bbox_outline;
2688 }
2689
2690
2691 if( !addPolygonNode( outlineNode, *outline ) )
2692 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
2693
2694 if( !outlineNode->GetChildren() )
2695 {
2696 aParentNode->RemoveChild( outlineNode );
2697 deleteNode( outlineNode );
2698 return false;
2699 }
2700
2701 addLineDesc( outlineNode, aWidth, aDashType );
2702
2703 return true;
2704}
2705
2706
2707bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2708 int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType )
2709{
2710 if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
2711 return false;
2712
2713 wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
2714
2715 if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
2716 {
2717 // Do not attempt to add cutouts to shapes that are already hollow
2718 if( aFillType != FILL_T::NO_FILL )
2719 addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
2720 }
2721 else
2722 {
2723 aParentNode->RemoveChild( contourNode );
2724 deleteNode( contourNode );
2725 return false;
2726 }
2727
2728 return true;
2729}
2730
2731
2732void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
2733{
2734 SHAPE_POLY_SET board_outline;
2735
2736 if( ! m_board->GetBoardPolygonOutlines( board_outline, false ) )
2737 {
2738 Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
2739 return;
2740 }
2741
2742 wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
2743
2744 if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
2745 {
2746 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
2747 aStepNode->RemoveChild( profileNode );
2748 deleteNode( profileNode );
2749 }
2750}
2751
2752
2753static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
2754{
2755 if( !aFootprint )
2756 return false;
2757
2758 if( aLayer != F_SilkS && aLayer != B_SilkS )
2759 return false;
2760
2761 if( aFootprint->IsFlipped() )
2762 return aLayer == F_SilkS;
2763
2764 return aLayer == B_SilkS;
2765}
2766
2767
2768wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
2769{
2770 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
2771 fp->SetParentGroup( nullptr );
2772 fp->SetPosition( { 0, 0 } );
2773 fp->SetOrientation( ANGLE_0 );
2774
2775 // Track original flipped state before normalization. This is needed to correctly
2776 // determine OtherSideView content per IPC-2581C. After flipping, layer IDs swap,
2777 // so for bottom components, B_SilkS/B_Fab after flip is actually the primary view.
2778 bool wasFlipped = fp->IsFlipped();
2779
2780 // Normalize package geometry to the unflipped footprint coordinate system.
2781 if( fp->IsFlipped() )
2782 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
2783
2784 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
2785 wxString name = genString( wxString::Format( "%s_%zu",
2786 fp->GetFPID().GetLibItemName().wx_str(),
2787 m_footprint_dict.size() + 1 ) );
2788
2789 auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
2790 addAttribute( aContentNode, "packageRef", iter->second );
2791
2792 if( !success)
2793 return nullptr;
2794
2795 // Package and Component nodes are at the same level, so we need to find the parent
2796 // which should be the Step node
2797 wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
2798 wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
2799
2800 addAttribute( packageNode, "name", name );
2801 addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
2802
2803 // We don't specially identify pin 1 in our footprints, so we need to guess
2804 if( fp->FindPadByNumber( "1" ) )
2805 addAttribute( packageNode, "pinOne", "1" );
2806 else if ( fp->FindPadByNumber( "A1" ) )
2807 addAttribute( packageNode, "pinOne", "A1" );
2808 else if ( fp->FindPadByNumber( "A" ) )
2809 addAttribute( packageNode, "pinOne", "A" );
2810 else if ( fp->FindPadByNumber( "a" ) )
2811 addAttribute( packageNode, "pinOne", "a" );
2812 else if ( fp->FindPadByNumber( "a1" ) )
2813 addAttribute( packageNode, "pinOne", "a1" );
2814 else if ( fp->FindPadByNumber( "Anode" ) )
2815 addAttribute( packageNode, "pinOne", "Anode" );
2816 else if ( fp->FindPadByNumber( "ANODE" ) )
2817 addAttribute( packageNode, "pinOne", "ANODE" );
2818 else
2819 addAttribute( packageNode, "pinOne", "UNKNOWN" );
2820
2821 addAttribute( packageNode, "pinOneOrientation", "OTHER" );
2822
2823 // After normalization: F_CrtYd is top, B_CrtYd is bottom.
2824 // For bottom components (wasFlipped), these are swapped from original orientation.
2825 const SHAPE_POLY_SET& courtyard_primary = wasFlipped ? fp->GetCourtyard( B_CrtYd )
2826 : fp->GetCourtyard( F_CrtYd );
2827 const SHAPE_POLY_SET& courtyard_other = wasFlipped ? fp->GetCourtyard( F_CrtYd )
2828 : fp->GetCourtyard( B_CrtYd );
2829
2830 if( courtyard_primary.OutlineCount() > 0 )
2831 addOutlineNode( packageNode, courtyard_primary, courtyard_primary.Outline( 0 ).Width(),
2833
2834 if( courtyard_other.OutlineCount() > 0 )
2835 {
2836 if( m_version > 'B' )
2837 {
2838 otherSideViewNode = appendNode( packageNode, "OtherSideView" );
2839 addOutlineNode( otherSideViewNode, courtyard_other, courtyard_other.Outline( 0 ).Width(),
2841 }
2842 }
2843
2844 if( !courtyard_primary.OutlineCount() && !courtyard_other.OutlineCount() )
2845 {
2846 SHAPE_POLY_SET bbox = fp->GetBoundingHull();
2847 addOutlineNode( packageNode, bbox );
2848 }
2849
2850 wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
2851 addAttribute( pickupPointNode, "x", "0.0" );
2852 addAttribute( pickupPointNode, "y", "0.0" );
2853
2854 std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
2855
2856 for( BOARD_ITEM* item : fp->GraphicalItems() )
2857 {
2858 PCB_LAYER_ID layer = item->GetLayer();
2859
2863 if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
2864 continue;
2865
2866 if( m_version == 'B' && isOppositeSideSilk( fp.get(), layer ) )
2867 continue;
2868
2869 bool is_abs = true;
2870
2871 if( item->Type() == PCB_SHAPE_T )
2872 {
2873 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2874
2875 // Circles and Rectanges only have size information so we need to place them in
2876 // a separate node that has a location
2877 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
2878 is_abs = false;
2879 }
2880
2881 elements[item->GetLayer()][is_abs].push_back( item );
2882 }
2883
2884 auto add_base_node =
2885 [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
2886 {
2887 wxXmlNode* parent = packageNode;
2888
2889 // Determine if this layer content should go in OtherSideView.
2890 // Per IPC-2581C, OtherSideView contains geometry visible from the opposite
2891 // side of the package body from the primary view.
2892 //
2893 // For non-flipped (top) components: B_SilkS/B_Fab → OtherSideView
2894 // For flipped (bottom) components after normalization: F_SilkS/F_Fab → OtherSideView
2895 // (because after flip, B_SilkS/B_Fab contains the original primary graphics)
2896 bool is_other_side = wasFlipped ? ( aLayer == F_SilkS || aLayer == F_Fab )
2897 : ( aLayer == B_SilkS || aLayer == B_Fab );
2898
2899 if( is_other_side && m_version > 'B' )
2900 {
2901 if( !otherSideViewNode )
2902 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
2903
2904 parent = otherSideViewNode;
2905 }
2906
2907 wxString name;
2908
2909 if( aLayer == F_SilkS || aLayer == B_SilkS )
2910 name = "SilkScreen";
2911 else if( aLayer == F_Fab || aLayer == B_Fab )
2912 name = "AssemblyDrawing";
2913 else
2914 wxASSERT( false );
2915
2916 wxXmlNode* new_node = appendNode( parent, name );
2917 return new_node;
2918 };
2919
2920 auto add_marking_node =
2921 [&]( wxXmlNode* aNode ) -> wxXmlNode*
2922 {
2923 wxXmlNode* marking_node = appendNode( aNode, "Marking" );
2924 addAttribute( marking_node, "markingUsage", "NONE" );
2925 return marking_node;
2926 };
2927
2928 std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
2929 std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
2930
2931 for( auto layer : { F_Fab, B_Fab } )
2932 {
2933 if( elements.find( layer ) != elements.end() )
2934 {
2935 if( elements[layer][true].size() > 0 )
2936 layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
2937 else if( elements[layer][false].size() > 0 )
2938 layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
2939 }
2940 }
2941
2942 for( auto& [layer, map] : elements )
2943 {
2944 wxXmlNode* layer_node = add_base_node( layer );
2945 wxXmlNode* marking_node = add_marking_node( layer_node );
2946 wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
2947 bool update_bbox = false;
2948
2949 if( layer == F_Fab || layer == B_Fab )
2950 {
2951 layer_nodes[layer] = layer_node;
2952 update_bbox = true;
2953 }
2954
2955 for( auto& [is_abs, vec] : map )
2956 {
2957 for( BOARD_ITEM* item : vec )
2958 {
2959 wxXmlNode* output_node = nullptr;
2960
2961 if( update_bbox )
2962 layer_bbox[layer].Merge( item->GetBoundingBox() );
2963
2964 if( !is_abs )
2965 output_node = add_marking_node( layer_node );
2966 else
2967 output_node = group_node;
2968
2969 switch( item->Type() )
2970 {
2971 case PCB_TEXT_T:
2972 {
2973 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
2974
2975 if( text->IsKnockout() )
2976 addKnockoutText( output_node, text );
2977 else
2978 addText( output_node, text, text->GetFontMetrics() );
2979
2980 break;
2981 }
2982
2983 case PCB_TEXTBOX_T:
2984 {
2985 PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
2986 addText( output_node, text, text->GetFontMetrics() );
2987
2988 // We want to force this to be a polygon to get absolute coordinates
2989 if( text->IsBorderEnabled() )
2990 {
2991 SHAPE_POLY_SET poly_set;
2992 text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
2993 addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
2994 text->GetBorderWidth() );
2995 }
2996
2997 break;
2998 }
2999
3000 case PCB_SHAPE_T:
3001 {
3002 if( !is_abs )
3003 addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
3004
3005 // When in Marking context (!is_abs), use inline geometry to avoid
3006 // unresolved UserPrimitiveRef errors in validators like Vu2581
3007 addShape( output_node, *static_cast<PCB_SHAPE*>( item ), !is_abs );
3008
3009 break;
3010 }
3011
3012 default: break;
3013 }
3014 }
3015 }
3016
3017 if( group_node->GetChildren() == nullptr )
3018 {
3019 marking_node->RemoveChild( group_node );
3020 layer_node->RemoveChild( marking_node );
3021 delete group_node;
3022 delete marking_node;
3023 }
3024 }
3025
3026 for( auto&[layer, bbox] : layer_bbox )
3027 {
3028 if( bbox.GetWidth() > 0 )
3029 {
3030 wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
3031
3032 SHAPE_LINE_CHAIN outline;
3033 std::vector<VECTOR2I> points( 4 );
3034 points[0] = bbox.GetPosition();
3035 points[2] = bbox.GetEnd();
3036 points[1].x = points[0].x;
3037 points[1].y = points[2].y;
3038 points[3].x = points[2].x;
3039 points[3].y = points[0].y;
3040
3041 outline.Append( points );
3042 addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
3043 addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
3044 }
3045 }
3046
3047 std::map<wxString, wxXmlNode*> pin_nodes;
3048
3049 for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
3050 {
3051 PAD* pad = fp->Pads()[ii];
3052 wxString name = pinName( pad );
3053 wxXmlNode* pinNode = nullptr;
3054
3055 auto [ it, inserted ] = pin_nodes.emplace( name, nullptr );
3056
3057 if( inserted )
3058 {
3059 pinNode = appendNode( packageNode, "Pin" );
3060 it->second = pinNode;
3061
3062 addAttribute( pinNode, "number", name );
3063
3064 m_net_pin_dict[pad->GetNetCode()].emplace_back(
3065 genString( fp->GetReference(), "CMP" ), name );
3066
3067 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
3068 addAttribute( pinNode, "electricalType", "MECHANICAL" );
3069 else if( pad->IsOnCopperLayer() )
3070 addAttribute( pinNode, "electricalType", "ELECTRICAL" );
3071 else
3072 addAttribute( pinNode, "electricalType", "UNDEFINED" );
3073
3074 if( pad->HasHole() )
3075 addAttribute( pinNode, "type", "THRU" );
3076 else
3077 addAttribute( pinNode, "type", "SURFACE" );
3078
3079 if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
3080 {
3081 wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
3082 EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
3083
3084 if( fp->IsFlipped() )
3085 pad_angle = pad_angle.Invert().Normalize();
3086
3087 if( pad_angle != ANGLE_0 )
3088 xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
3089 }
3090
3091 addLocationNode( pinNode, *pad, true );
3092 addShape( pinNode, *pad, pad->GetLayer() );
3093 }
3094
3095 // We just need the padstack, we don't need the reference here. The reference will be
3096 // created in the LayerFeature set
3097 wxXmlNode dummy;
3098 addPadStack( &dummy, pad );
3099 }
3100
3101 return packageNode;
3102}
3103
3104
3105void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
3106{
3107 std::vector<wxXmlNode*> componentNodes;
3108 std::vector<wxXmlNode*> packageNodes;
3109 std::set<wxString> packageNames;
3110
3111 bool generate_unique = m_OEMRef.empty();
3112
3113 for( FOOTPRINT* fp : m_board->Footprints() )
3114 {
3115 wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
3116 addAttribute( componentNode, "refDes", componentName( fp ) );
3117 wxXmlNode* pkg = addPackage( componentNode, fp );
3118
3119 if( pkg )
3120 packageNodes.push_back( pkg );
3121
3122 wxString name;
3123
3124 PCB_FIELD* field = nullptr;
3125
3126 if( !generate_unique )
3127 field = fp->GetField( m_OEMRef );
3128
3129 if( field && !field->GetText().empty() )
3130 {
3131 name = field->GetShownText( false );
3132 }
3133 else
3134 {
3135 name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(),
3136 fp->GetFPID().GetLibItemName().wx_str(),
3137 fp->GetValue() );
3138 }
3139
3140 if( !m_OEMRef_dict.emplace( fp, name ).second )
3141 Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
3143
3144 addAttribute( componentNode, "part", genString( name, "REF" ) );
3145 addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
3146
3147 if( fp->GetAttributes() & FP_THROUGH_HOLE )
3148 addAttribute( componentNode, "mountType", "THMT" );
3149 else if( fp->GetAttributes() & FP_SMD )
3150 addAttribute( componentNode, "mountType", "SMT" );
3151 else
3152 addAttribute( componentNode, "mountType", "OTHER" );
3153
3154 if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
3155 {
3156 wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
3157
3158 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3159
3160 if( fp->IsFlipped() )
3161 fp_angle = fp_angle.Invert().Normalize();
3162
3163 if( fp_angle != ANGLE_0 )
3164 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3165
3166 if( fp->IsFlipped() )
3167 addAttribute( xformNode, "mirror", "true" );
3168 }
3169
3170 addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
3171
3172 componentNodes.push_back( componentNode );
3173 }
3174
3175 for( wxXmlNode* padstack : m_padstacks )
3176 {
3177 insertNode( aStepNode, padstack );
3178 m_last_padstack = padstack;
3179 }
3180
3181 for( wxXmlNode* pkg : packageNodes )
3182 aStepNode->AddChild( pkg );
3183
3184 for( wxXmlNode* cmp : componentNodes )
3185 aStepNode->AddChild( cmp );
3186}
3187
3188
3189void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
3190{
3191 for( auto& [ net, pin_pair] : m_net_pin_dict )
3192 {
3193 wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
3194 addAttribute( netNode, "name",
3195 genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ;
3196
3197 for( auto& [cmp, pin] : pin_pair )
3198 {
3199 wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
3200 addAttribute( netPinNode, "componentRef", cmp );
3201 addAttribute( netPinNode, "pin", pin );
3202 }
3203 //TODO: Finish
3204 }
3205}
3206
3207//TODO: Add PhyNetGroup section
3208
3209void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
3210{
3211 LSEQ layers = m_board->GetEnabledLayers().Seq();
3212 const NETINFO_LIST& nets = m_board->GetNetInfo();
3213 std::vector<std::unique_ptr<FOOTPRINT>> footprints;
3214
3215 // To avoid the overhead of repeatedly cycling through the layers and nets,
3216 // we pre-sort the board items into a map of layer -> net -> items
3217 std::map<PCB_LAYER_ID, std::map<int, std::vector<BOARD_ITEM*>>> elements;
3218
3219 std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(),
3220 [&layers, &elements]( PCB_TRACK* aTrack )
3221 {
3222 if( aTrack->Type() == PCB_VIA_T )
3223 {
3224 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
3225
3226 for( PCB_LAYER_ID layer : layers )
3227 {
3228 if( via->FlashLayer( layer ) )
3229 elements[layer][via->GetNetCode()].push_back( via );
3230 }
3231 }
3232 else
3233 {
3234 elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack );
3235 }
3236 } );
3237
3238 std::for_each( m_board->Zones().begin(), m_board->Zones().end(),
3239 [ &elements ]( ZONE* zone )
3240 {
3241 LSEQ zone_layers = zone->GetLayerSet().Seq();
3242
3243 for( PCB_LAYER_ID layer : zone_layers )
3244 elements[layer][zone->GetNetCode()].push_back( zone );
3245 } );
3246
3247 for( BOARD_ITEM* item : m_board->Drawings() )
3248 {
3249 if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
3250 elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it );
3251 else
3252 elements[item->GetLayer()][0].push_back( item );
3253 }
3254
3255 for( FOOTPRINT* fp : m_board->Footprints() )
3256 {
3257 for( PCB_FIELD* field : fp->GetFields() )
3258 elements[field->GetLayer()][0].push_back( field );
3259
3260 for( BOARD_ITEM* item : fp->GraphicalItems() )
3261 elements[item->GetLayer()][0].push_back( item );
3262
3263 for( PAD* pad : fp->Pads() )
3264 {
3265 LSEQ pad_layers = pad->GetLayerSet().Seq();
3266
3267 for( PCB_LAYER_ID layer : pad_layers )
3268 {
3269 if( pad->FlashLayer( layer ) )
3270 elements[layer][pad->GetNetCode()].push_back( pad );
3271 }
3272
3273 // SMD pads have implicit solder mask and paste openings that are not in the layer
3274 // set. Add them to the corresponding tech layers if the pad is on a copper layer.
3275 if( pad->IsOnLayer( F_Cu ) && pad->FlashLayer( F_Cu ) )
3276 {
3277 if( !pad->IsOnLayer( F_Mask ) )
3278 elements[F_Mask][pad->GetNetCode()].push_back( pad );
3279
3280 if( !pad->IsOnLayer( F_Paste ) )
3281 elements[F_Paste][pad->GetNetCode()].push_back( pad );
3282 }
3283
3284 if( pad->IsOnLayer( B_Cu ) && pad->FlashLayer( B_Cu ) )
3285 {
3286 if( !pad->IsOnLayer( B_Mask ) )
3287 elements[B_Mask][pad->GetNetCode()].push_back( pad );
3288
3289 if( !pad->IsOnLayer( B_Paste ) )
3290 elements[B_Paste][pad->GetNetCode()].push_back( pad );
3291 }
3292 }
3293 }
3294
3295 for( PCB_LAYER_ID layer : layers )
3296 {
3297 if( m_progressReporter )
3298 m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
3299
3300 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3301 addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
3302
3303 auto process_net = [&] ( int net )
3304 {
3305 std::vector<BOARD_ITEM*>& vec = elements[layer][net];
3306
3307 if( vec.empty() )
3308 return;
3309
3310 std::stable_sort( vec.begin(), vec.end(),
3311 []( BOARD_ITEM* a, BOARD_ITEM* b )
3312 {
3313 if( a->GetParentFootprint() == b->GetParentFootprint() )
3314 return a->Type() < b->Type();
3315
3316 return a->GetParentFootprint() < b->GetParentFootprint();
3317 } );
3318
3319 generateLayerSetNet( layerNode, layer, vec );
3320 };
3321
3322 for( const NETINFO_ITEM* net : nets )
3323 {
3324 if( m_progressReporter )
3325 {
3326 m_progressReporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ),
3327 m_board->GetLayerName( layer ),
3328 net->GetNetname() ) );
3329 m_progressReporter->AdvanceProgress();
3330 }
3331
3332 process_net( net->GetNetCode() );
3333 }
3334
3335 if( layerNode->GetChildren() == nullptr )
3336 {
3337 aStepNode->RemoveChild( layerNode );
3338 deleteNode( layerNode );
3339 }
3340 }
3341}
3342
3343
3344void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
3345{
3346 int hole_count = 1;
3347
3348 for( const auto& [layers, vec] : m_drill_layers )
3349 {
3350 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3351 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "DRILL" ) );
3352
3353 for( BOARD_ITEM* item : vec )
3354 {
3355 if( item->Type() == PCB_VIA_T )
3356 {
3357 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3358 auto it = m_padstack_dict.find( hash_fp_item( via, 0 ) );
3359
3360 if( it == m_padstack_dict.end() )
3361 {
3362 Report( _( "Via uses unsupported padstack; omitted from drill data." ),
3364 continue;
3365 }
3366
3367 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3368 addAttribute( padNode, "geometry", it->second );
3369
3370 if( via->GetNetCode() > 0 )
3371 addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) );
3372
3373 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3374 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3375 addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) );
3376 addAttribute( holeNode, "platingStatus", "VIA" );
3377 addAttribute( holeNode, "plusTol", "0.0" );
3378 addAttribute( holeNode, "minusTol", "0.0" );
3379 addXY( holeNode, via->GetPosition() );
3380 addBackdrillSpecRefs( holeNode, it->second );
3381 }
3382 else if( item->Type() == PCB_PAD_T )
3383 {
3384 PAD* pad = static_cast<PAD*>( item );
3385 auto it = m_padstack_dict.find( hash_fp_item( pad, 0 ) );
3386
3387 if( it == m_padstack_dict.end() )
3388 {
3389 Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
3391 continue;
3392 }
3393
3394 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3395 addAttribute( padNode, "geometry", it->second );
3396
3397 if( pad->GetNetCode() > 0 )
3398 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3399
3400 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3401 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3402 addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) );
3403 addAttribute( holeNode, "platingStatus",
3404 pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
3405 addAttribute( holeNode, "plusTol", "0.0" );
3406 addAttribute( holeNode, "minusTol", "0.0" );
3407 addXY( holeNode, pad->GetPosition() );
3408 addBackdrillSpecRefs( holeNode, it->second );
3409 }
3410 }
3411 }
3412
3413 hole_count = 1;
3414
3415 for( const auto& [layers, vec] : m_slot_holes )
3416 {
3417 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3418 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
3419
3420 for( PAD* pad : vec )
3421 {
3422 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3423
3424 if( pad->GetNetCode() > 0 )
3425 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3426
3427 addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
3428 }
3429 }
3430}
3431
3432
3433void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
3434 std::vector<BOARD_ITEM*>& aItems )
3435{
3436 auto it = aItems.begin();
3437 wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
3438 wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
3439 wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
3440
3441 bool has_via = false;
3442 bool has_pad = false;
3443
3444 wxXmlNode* padSetNode = nullptr;
3445
3446 wxXmlNode* viaSetNode = nullptr;
3447
3448 wxXmlNode* teardropLayerSetNode = nullptr;
3449 wxXmlNode* teardropFeatureSetNode = nullptr;
3450
3451 bool teardrop_warning = false;
3452
3453 if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
3454 IsCopperLayer( aLayer ) && item )
3455 {
3456 if( item->GetNetCode() > 0 )
3457 addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) );
3458 }
3459
3460 auto add_track =
3461 [&]( PCB_TRACK* track )
3462 {
3463 if( track->Type() == PCB_TRACE_T )
3464 {
3465 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3466 shape.SetStart( track->GetStart() );
3467 shape.SetEnd( track->GetEnd() );
3468 shape.SetWidth( track->GetWidth() );
3469 addShape( specialNode, shape );
3470 }
3471 else if( track->Type() == PCB_ARC_T )
3472 {
3473 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
3474 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3475 shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
3476 shape.SetWidth( arc->GetWidth() );
3477 addShape( specialNode, shape );
3478 }
3479 else
3480 {
3481 if( !viaSetNode )
3482 {
3483 if( !has_pad )
3484 {
3485 viaSetNode = layerSetNode;
3486 has_via = true;
3487 }
3488 else
3489 {
3490 viaSetNode = appendNode( layerSetNode, "Set" );
3491
3492 if( track->GetNetCode() > 0 )
3493 addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) );
3494 }
3495
3496 addAttribute( viaSetNode, "padUsage", "VIA" );
3497 }
3498
3499 addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
3500 }
3501 };
3502
3503 auto add_zone =
3504 [&]( ZONE* zone )
3505 {
3506 wxXmlNode* zoneFeatureNode = specialNode;
3507
3508 if( zone->IsTeardropArea() )
3509 {
3510 if( m_version > 'B' )
3511 {
3512 if( !teardropFeatureSetNode )
3513 {
3514 teardropLayerSetNode = appendNode( aLayerNode, "Set" );
3515 addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
3516
3517 if( zone->GetNetCode() > 0 )
3518 {
3519 addAttribute( teardropLayerSetNode, "net",
3520 genString( zone->GetNetname(), "NET" ) );
3521 }
3522
3523 wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
3524 addLocationNode( new_teardrops, 0.0, 0.0 );
3525 teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
3526 }
3527
3528 zoneFeatureNode = teardropFeatureSetNode;
3529 }
3530 else if( !teardrop_warning )
3531 {
3532 Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
3534 teardrop_warning = true;
3535 }
3536 }
3537 else
3538 {
3539 if( FOOTPRINT* fp = zone->GetParentFootprint() )
3540 {
3541 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3542 wxString refDes = componentName( zone->GetParentFootprint() );
3543 addAttribute( tempSetNode, "componentRef", refDes );
3544 wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
3545 addLocationNode( newFeatures, 0.0, 0.0 );
3546 zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
3547 }
3548 }
3549
3550 SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
3551
3552 for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
3553 addContourNode( zoneFeatureNode, zone_shape, ii );
3554 };
3555
3556 auto add_shape =
3557 [&] ( PCB_SHAPE* shape )
3558 {
3559 FOOTPRINT* fp = shape->GetParentFootprint();
3560
3561 if( fp )
3562 {
3563 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3564
3565 if( m_version > 'B' )
3566 addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
3567
3568 bool link_to_component = true;
3569
3570 if( m_version == 'B' && isOppositeSideSilk( fp, shape->GetLayer() ) )
3571 link_to_component = false;
3572
3573 if( link_to_component )
3574 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3575
3576 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3577
3578 // Per IPC-2581 schema, element order in Features must be: Xform, Location, Feature
3579 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3580
3581 if( fp_angle != ANGLE_0 )
3582 {
3583 wxXmlNode* xformNode = appendNode( tempFeature, "Xform" );
3584 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3585 }
3586
3587 addLocationNode( tempFeature, *shape );
3588 addShape( tempFeature, *shape );
3589 }
3590 else if( shape->GetShape() == SHAPE_T::CIRCLE
3591 || shape->GetShape() == SHAPE_T::RECTANGLE
3592 || shape->GetShape() == SHAPE_T::POLY )
3593 {
3594 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3595
3596 if( shape->GetNetCode() > 0 )
3597 addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) );
3598
3599 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3600 addLocationNode( tempFeature, *shape );
3601 addShape( tempFeature, *shape );
3602 }
3603 else
3604 {
3605 addShape( specialNode, *shape );
3606 }
3607 };
3608
3609 auto add_text =
3610 [&] ( BOARD_ITEM* text )
3611 {
3612 EDA_TEXT* text_item;
3613 FOOTPRINT* fp = text->GetParentFootprint();
3614
3615 if( PCB_TEXT* tmp_text = dynamic_cast<PCB_TEXT*>( text ) )
3616 text_item = static_cast<EDA_TEXT*>( tmp_text );
3617 else if( PCB_TEXTBOX* tmp_text = dynamic_cast<PCB_TEXTBOX*>( text ) )
3618 text_item = static_cast<EDA_TEXT*>( tmp_text );
3619
3620 if( !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
3621 return;
3622
3623 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3624
3625 if( m_version > 'B' )
3626 addAttribute( tempSetNode, "geometryUsage", "TEXT" );
3627
3628 bool link_to_component = fp != nullptr;
3629
3630 if( m_version == 'B' && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
3631 link_to_component = false;
3632
3633 if( link_to_component )
3634 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3635
3636 wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
3637 addAttribute( nonStandardAttributeNode, "name", "TEXT" );
3638 addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) );
3639 addAttribute( nonStandardAttributeNode, "type", "STRING" );
3640
3641 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3642 addLocationNode( tempFeature, 0.0, 0.0 );
3643
3644 if( text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout() )
3645 addKnockoutText( tempFeature, static_cast<PCB_TEXT*>( text ) );
3646 else
3647 addText( tempFeature, text_item, text->GetFontMetrics() );
3648
3649 if( text->Type() == PCB_TEXTBOX_T )
3650 {
3651 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( text );
3652
3653 if( textbox->IsBorderEnabled() )
3654 addShape( tempFeature, *static_cast<PCB_SHAPE*>( textbox ) );
3655 }
3656 };
3657
3658 auto add_pad =
3659 [&]( PAD* pad )
3660 {
3661 if( !padSetNode )
3662 {
3663 if( !has_via )
3664 {
3665 padSetNode = layerSetNode;
3666 has_pad = true;
3667 }
3668 else
3669 {
3670 padSetNode = appendNode( aLayerNode, "Set" );
3671
3672 if( pad->GetNetCode() > 0 )
3673 addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) );
3674 }
3675 }
3676
3677 FOOTPRINT* fp = pad->GetParentFootprint();
3678
3679 if( fp && fp->IsFlipped() )
3680 addPad( padSetNode, pad, FlipLayer( aLayer ) );
3681 else
3682 addPad( padSetNode, pad, aLayer );
3683 };
3684
3685 for( BOARD_ITEM* item : aItems )
3686 {
3687 switch( item->Type() )
3688 {
3689 case PCB_TRACE_T:
3690 case PCB_ARC_T:
3691 case PCB_VIA_T:
3692 add_track( static_cast<PCB_TRACK*>( item ) );
3693 break;
3694
3695 case PCB_ZONE_T:
3696 add_zone( static_cast<ZONE*>( item ) );
3697 break;
3698
3699 case PCB_PAD_T:
3700 add_pad( static_cast<PAD*>( item ) );
3701 break;
3702
3703 case PCB_SHAPE_T:
3704 add_shape( static_cast<PCB_SHAPE*>( item ) );
3705 break;
3706
3707 case PCB_TEXT_T:
3708 case PCB_TEXTBOX_T:
3709 case PCB_FIELD_T:
3710 add_text( item );
3711 break;
3712
3713 case PCB_DIMENSION_T:
3714 case PCB_TARGET_T:
3715 case PCB_DIM_ALIGNED_T:
3716 case PCB_DIM_LEADER_T:
3717 case PCB_DIM_CENTER_T:
3718 case PCB_DIM_RADIAL_T:
3720 //TODO: Add support for dimensions
3721 break;
3722
3723 default:
3724 wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
3725 ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
3726 }
3727 }
3728
3729 if( specialNode->GetChildren() == nullptr )
3730 {
3731 featureSetNode->RemoveChild( specialNode );
3732 deleteNode( specialNode );
3733 }
3734
3735 if( featureSetNode->GetChildren() == nullptr )
3736 {
3737 layerSetNode->RemoveChild( featureSetNode );
3738 deleteNode( featureSetNode );
3739 }
3740
3741 if( layerSetNode->GetChildren() == nullptr )
3742 {
3743 aLayerNode->RemoveChild( layerSetNode );
3744 deleteNode( layerSetNode );
3745 }
3746}
3747
3749{
3750 int hole_count = 1;
3751
3752 for( const auto& [layers, vec] : m_auxilliary_Layers )
3753 {
3754 hole_count = 1;
3755 bool add_node = true;
3756
3757 wxString name;
3758 bool hole = false;
3759
3760 // clang-format off: suggestion is inconsitent
3761 switch( std::get<0>(layers) )
3762 {
3764 name = "COVERING";
3765 break;
3767 name = "PLUGGING";
3768 hole = true;
3769 break;
3771 name = "TENTING";
3772 break;
3774 name = "FILLING";
3775 hole = true;
3776 break;
3778 name = "CAPPING";
3779 hole = true;
3780 break;
3781 default:
3782 add_node = false;
3783 break;
3784 }
3785 // clang-format on: suggestion is inconsitent
3786
3787 if( !add_node )
3788 continue;
3789
3790 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3791 if( std::get<2>( layers ) == UNDEFINED_LAYER )
3792 layerNode->AddAttribute( "layerRef", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
3793 else
3794 layerNode->AddAttribute( "layerRef", genLayersString( std::get<1>( layers ),
3795 std::get<2>( layers ), TO_UTF8( name ) ) );
3796
3797 for( BOARD_ITEM* item : vec )
3798 {
3799 if( item->Type() == PCB_VIA_T )
3800 {
3801 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3802
3803 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
3804
3805 if( hole )
3806 shape.SetEnd( { KiROUND( via->GetDrillValue() / 2.0 ), 0 } );
3807 else
3808 shape.SetEnd( { KiROUND( via->GetWidth( std::get<1>( layers ) ) / 2.0 ), 0 } );
3809
3810 wxXmlNode* padNode = appendNode( layerNode, "Pad" );
3811 addPadStack( padNode, via );
3812
3813 addLocationNode( padNode, 0.0, 0.0 );
3814 addShape( padNode, shape );
3815 }
3816 }
3817 }
3818}
3819
3820
3822{
3823 if( m_progressReporter )
3824 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
3825
3826 // Per IPC-2581 schema, Avl requires at least one AvlItem child element.
3827 // Don't emit Avl section if there are no items.
3828 if( m_OEMRef_dict.empty() )
3829 return nullptr;
3830
3831 wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
3832 addAttribute( avl, "name", "Primary_Vendor_List" );
3833
3834 wxXmlNode* header = appendNode( avl, "AvlHeader" );
3835 addAttribute( header, "title", "BOM" );
3836 addAttribute( header, "source", "KiCad" );
3837 addAttribute( header, "author", "OWNER" );
3838 addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
3839 addAttribute( header, "version", "1" );
3840
3841 std::set<wxString> unique_parts;
3842 std::map<wxString,wxString> unique_vendors;
3843
3844 for( auto& [fp, name] : m_OEMRef_dict )
3845 {
3846 auto [ it, success ] = unique_parts.insert( name );
3847
3848 if( !success )
3849 continue;
3850
3851 wxXmlNode* part = appendNode( avl, "AvlItem" );
3852 addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
3853
3854 PCB_FIELD* nums[2] = { fp->GetField( m_mpn ), fp->GetField( m_distpn ) };
3855 PCB_FIELD* company[2] = { fp->GetField( m_mfg ), nullptr };
3856 wxString company_name[2] = { m_mfg, m_dist };
3857
3858 for ( int ii = 0; ii < 2; ++ii )
3859 {
3860 if( nums[ii] )
3861 {
3862 wxString mpn_name = nums[ii]->GetShownText( false );
3863
3864 if( mpn_name.empty() )
3865 continue;
3866
3867 wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
3868 addAttribute( vmpn, "qualified", "false" );
3869 addAttribute( vmpn, "chosen", "false" );
3870
3871 wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
3872 addAttribute( mpn, "name", mpn_name );
3873
3874 wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
3875
3876 wxString name = wxT( "UNKNOWN" );
3877
3878 // If the field resolves, then use that field content unless it is empty
3879 if( !ii && company[ii] )
3880 {
3881 wxString tmp = company[ii]->GetShownText( false );
3882
3883 if( !tmp.empty() )
3884 name = tmp;
3885 }
3886 // If it doesn't resolve but there is content from the dialog, use the static content
3887 else if( !ii && !company_name[ii].empty() )
3888 {
3889 name = company_name[ii];
3890 }
3891 else if( ii && !m_dist.empty() )
3892 {
3893 name = m_dist;
3894 }
3895
3896 auto [vendor_id, inserted] = unique_vendors.emplace(
3897 name,
3898 wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
3899
3900 addAttribute( vendor, "enterpriseRef", vendor_id->second );
3901
3902 if( inserted )
3903 {
3904 wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
3905 addAttribute( new_vendor, "id", vendor_id->second );
3906 addAttribute( new_vendor, "name", name );
3907 addAttribute( new_vendor, "code", "NONE" );
3908 insertNodeAfter( m_enterpriseNode, new_vendor );
3909 m_enterpriseNode = new_vendor;
3910 }
3911 }
3912 }
3913 }
3914
3915 return avl;
3916}
3917
3918
3919void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
3920 const std::map<std::string, UTF8>* aProperties )
3921{
3922 // Clean up any previous export state to allow multiple exports per plugin instance
3923 delete m_xml_doc;
3924 m_xml_doc = nullptr;
3925 m_xml_root = nullptr;
3926 m_lastAppendedNode = nullptr;
3927
3928 m_board = aBoard;
3930 m_backdrill_spec_nodes.clear();
3931 m_backdrill_spec_used.clear();
3933 m_cad_header_node = nullptr;
3934 m_layer_name_map.clear();
3935
3936 // Clear all internal dictionaries and caches
3937 m_user_shape_dict.clear();
3938 m_shape_user_node = nullptr;
3939 m_std_shape_dict.clear();
3940 m_shape_std_node = nullptr;
3941 m_line_dict.clear();
3942 m_line_node = nullptr;
3943 m_padstack_dict.clear();
3944 m_padstacks.clear();
3945 m_last_padstack = nullptr;
3946 m_footprint_dict.clear();
3949 m_OEMRef_dict.clear();
3950 m_net_pin_dict.clear();
3951 m_drill_layers.clear();
3952 m_slot_holes.clear();
3953 m_auxilliary_Layers.clear();
3954 m_element_names.clear();
3955 m_generated_names.clear();
3956 m_acceptable_chars.clear();
3957 m_total_bytes = 0;
3958
3959 m_units_str = "MILLIMETER";
3960 m_scale = 1.0 / PCB_IU_PER_MM;
3961 m_sigfig = 6;
3962
3963 if( auto it = aProperties->find( "units" ); it != aProperties->end() )
3964 {
3965 if( it->second == "inch" )
3966 {
3967 m_units_str = "INCH";
3968 m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
3969 }
3970 }
3971
3972 if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
3973 m_sigfig = std::stoi( it->second );
3974
3975 if( auto it = aProperties->find( "version" ); it != aProperties->end() )
3976 m_version = it->second.c_str()[0];
3977
3978 if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
3979 m_OEMRef = it->second.wx_str();
3980
3981 if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
3982 m_mpn = it->second.wx_str();
3983
3984 if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
3985 m_mfg = it->second.wx_str();
3986
3987 if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
3988 m_dist = it->second.wx_str();
3989
3990 if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
3991 m_distpn = it->second.wx_str();
3992
3993 if( m_version == 'B' )
3994 {
3995 for( char c = 'a'; c <= 'z'; ++c )
3996 m_acceptable_chars.insert( c );
3997
3998 for( char c = 'A'; c <= 'Z'; ++c )
3999 m_acceptable_chars.insert( c );
4000
4001 for( char c = '0'; c <= '9'; ++c )
4002 m_acceptable_chars.insert( c );
4003
4004 // Add special characters
4005 std::string specialChars = "_\\-.+><";
4006
4007 for( char c : specialChars )
4008 m_acceptable_chars.insert( c );
4009 }
4010
4011 m_xml_doc = new wxXmlDocument();
4013
4015
4016 if( m_progressReporter )
4017 {
4018 m_progressReporter->SetNumPhases( 7 );
4019 m_progressReporter->BeginPhase( 1 );
4020 m_progressReporter->Report( _( "Generating logistic section" ) );
4021 }
4022
4025
4026 wxXmlNode* ecad_node = generateEcadSection();
4027 generateBOMSection( ecad_node );
4029
4030 if( m_progressReporter )
4031 {
4032 m_progressReporter->AdvancePhase( _( "Saving file" ) );
4033 }
4034
4035 wxFileOutputStreamWithProgress out_stream( aFileName );
4036 double written_bytes = 0.0;
4037 double last_yield = 0.0;
4038
4039 // This is a rough estimation of the size of the spaces in the file
4040 // We just need to total to be slightly larger than the value of the
4041 // progress bar, so accurately counting spaces is not terribly important
4043
4044 auto update_progress = [&]( size_t aBytes )
4045 {
4046 written_bytes += aBytes;
4047 double percent = written_bytes / static_cast<double>( m_total_bytes );
4048
4049 if( m_progressReporter )
4050 {
4051 // Only update every percent
4052 if( last_yield + 0.01 < percent )
4053 {
4054 last_yield = percent;
4055 m_progressReporter->SetCurrentProgress( percent );
4056 }
4057 }
4058 };
4059
4060 out_stream.SetProgressCallback( update_progress );
4061
4062 if( !m_xml_doc->Save( out_stream ) )
4063 {
4064 Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
4065 return;
4066 }
4067
4068 size_t size = out_stream.GetSize();
4069}
const char * name
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:129
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
Definition base_units.h:70
bool IsPrmSpecified(const wxString &aPrmValue)
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
wxString GetMajorMinorPatchVersion()
Get the major, minor and patch version in a string major.minor.patch This is extracted by CMake from ...
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsKnockout() const
Definition board_item.h:324
FOOTPRINT * GetParentFootprint() const
VECTOR2I GetFPRelativePosition() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
double GetEpsilonR(int aDielectricSubLayer=0) const
wxString GetColor(int aDielectricSubLayer=0) const
wxString GetLayerName() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
wxString GetMaterial(int aDielectricSubLayer=0) const
int GetDielectricLayerId() const
double GetLossTangent(int aDielectricSubLayer=0) const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int GetCount() const
bool SynchronizeWithBoard(BOARD_DESIGN_SETTINGS *aSettings)
Synchronize the BOARD_STACKUP_ITEM* list with the board.
int BuildBoardThicknessFromStackup() const
int GetLayerDistance(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Calculate the distance (height) between the two given copper layers.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double AsDegrees() const
Definition eda_angle.h:116
EDA_ANGLE Invert() const
Definition eda_angle.h:173
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:259
FILL_T GetFillMode() const
Definition eda_shape.h:142
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:169
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:216
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
std::vector< VECTOR2I > GetRectCorners() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:256
int GetRectangleHeight() const
bool IsClockwiseArc() const
void SetWidth(int aWidth)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:80
const VECTOR2I & GetTextPos() const
Definition eda_text.h:273
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:98
virtual bool IsVisible() const
Definition eda_text.h:187
virtual EDA_ANGLE GetDrawRotation() const
Definition eda_text.h:379
virtual KIFONT::FONT * GetDrawFont(const RENDER_SETTINGS *aSettings) const
Definition eda_text.cpp:661
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:231
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:479
virtual wxString GetShownText(bool aAllowExtraText, int aDepth=0) const
Return the string actually shown after processing of the base text.
Definition eda_text.h:109
static ENUM_MAP< T > & Instance()
Definition property.h:721
EDA_ANGLE GetOrientation() const
Definition footprint.h:330
unsigned GetPadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of pads.
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:306
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:339
bool IsFlipped() const
Definition footprint.h:524
const wxString & GetReference() const
Definition footprint.h:751
wxString m_name
Name of the IO loader.
Definition io_base.h:234
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Definition io_base.cpp:124
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:98
void Draw(KIGFX::GAL *aGal, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aCursor, const TEXT_ATTRIBUTES &aAttributes, const METRICS &aFontMetrics, std::optional< VECTOR2I > aMousePos=std::nullopt, wxString *aActiveUrl=nullptr) const
Draw a string.
Definition font.cpp:250
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
double r
Red component.
Definition color4d.h:393
double g
Green component.
Definition color4d.h:394
double b
Blue component.
Definition color4d.h:395
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:313
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Handle the data for a net.
Definition netinfo.h:54
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:212
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
std::optional< bool > IsFilled() const
std::optional< bool > IsTented(PCB_LAYER_ID aSide) const
Checks if this padstack is tented (covered in soldermask) on the given side.
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:357
std::optional< bool > IsPlugged(PCB_LAYER_ID aSide) const
DRILL_PROPS & Drill()
Definition padstack.h:348
std::optional< bool > IsCapped() const
std::optional< bool > IsCovered(PCB_LAYER_ID aSide) const
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:351
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:360
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:560
int GetSizeX() const
Definition pad.h:285
void MergePrimitivesAsPolygon(PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aMergedPolygon, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Merge all basic shapes to a SHAPE_POLY_SET.
Definition pad.cpp:3174
int GetRoundRectCornerRadius(PCB_LAYER_ID aLayer) const
Definition pad.cpp:891
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:440
int GetDrillSizeY() const
Definition pad.h:321
const VECTOR2I & GetDrillSize() const
Definition pad.h:317
PAD_ATTRIB GetAttribute() const
Definition pad.h:563
const wxString & GetNumber() const
Definition pad.h:137
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:304
VECTOR2I GetPosition() const override
Definition pad.h:209
int GetDrillSizeX() const
Definition pad.h:319
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:196
int GetSolderMaskExpansion(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1639
const PADSTACK & Padstack() const
Definition pad.h:333
const VECTOR2I & GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.h:329
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.h:420
PAD_DRILL_SHAPE GetDrillShape() const
Definition pad.h:437
int GetChamferPositions(PCB_LAYER_ID aLayer) const
Definition pad.h:836
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition pad.h:819
VECTOR2I GetSolderPasteMargin(PCB_LAYER_ID aLayer) const
Usually < 0 (mask shape smaller than pad)because the margin can be dependent on the pad size,...
Definition pad.cpp:1694
bool HasDrilledHole() const override
Definition pad.h:112
bool HasHole() const override
Definition pad.h:107
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition pad.cpp:2491
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition pad.h:264
const VECTOR2I & GetMid() const
Definition pcb_track.h:347
wxString GetShownText(bool aAllowExtraText, int aDepth=0) const override
Return the string actually shown after processing of the base text.
void addText(wxXmlNode *aContentNode, EDA_TEXT *aShape, const KIFONT::METRICS &aFontMetrics)
wxString floatVal(double aVal, int aSigFig=-1) const
void generateLayerSetDrill(wxXmlNode *aStepNode)
wxString genLayerString(PCB_LAYER_ID aLayer, const char *aPrefix) const
wxXmlNode * generateContentSection()
Creates the Content section of the XML file.
wxXmlNode * appendNode(wxXmlNode *aParent, const wxString &aName)
void addBackdrillSpecRefs(wxXmlNode *aHoleNode, const wxString &aPadstackName)
void generateDrillLayers(wxXmlNode *aCadLayerNode)
wxString sanitizeId(const wxString &aStr) const
wxXmlNode * addPackage(wxXmlNode *aStepNode, FOOTPRINT *aFootprint)
void generateComponents(wxXmlNode *aStepNode)
bool addContourNode(wxXmlNode *aParentNode, const SHAPE_POLY_SET &aPolySet, int aOutline=0, FILL_T aFillType=FILL_T::FILLED_SHAPE, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
wxString componentName(FOOTPRINT *aFootprint)
void addShape(wxXmlNode *aContentNode, const PCB_SHAPE &aShape, bool aInline=false)
bool addOutlineNode(wxXmlNode *aParentNode, const SHAPE_POLY_SET &aPolySet, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
void generateStackup(wxXmlNode *aCadLayerNode)
std::map< std::tuple< auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< BOARD_ITEM * > > m_auxilliary_Layers
bool addPolygonNode(wxXmlNode *aParentNode, const SHAPE_LINE_CHAIN &aPolygon, FILL_T aFillType=FILL_T::FILLED_SHAPE, int aWidth=0, LINE_STYLE aDashType=LINE_STYLE::SOLID)
void generateLayerSetNet(wxXmlNode *aLayerNode, PCB_LAYER_ID aLayer, std::vector< BOARD_ITEM * > &aItems)
bool isValidLayerFor2581(PCB_LAYER_ID aLayer)
void insertNodeAfter(wxXmlNode *aPrev, wxXmlNode *aNode)
void generateCadLayers(wxXmlNode *aCadLayerNode)
std::vector< wxXmlNode * > m_padstacks
wxString pinName(const PAD *aPad) const
void generateCadSpecs(wxXmlNode *aCadLayerNode)
void addVia(wxXmlNode *aContentNode, const PCB_VIA *aVia, PCB_LAYER_ID aLayer)
void addSlotCavity(wxXmlNode *aContentNode, const PAD &aPad, const wxString &aName)
void deleteNode(wxXmlNode *&aNode)
wxXmlNode * m_last_padstack
size_t lineHash(int aWidth, LINE_STYLE aDashType)
std::map< size_t, wxString > m_std_shape_dict
void addAttribute(wxXmlNode *aNode, const wxString &aName, const wxString &aValue)
wxXmlNode * m_shape_user_node
wxXmlNode * generateAvlSection()
Creates the Approved Vendor List section.
wxXmlNode * generateHistorySection()
Creates the history section.
void addXY(wxXmlNode *aNode, const VECTOR2I &aVec, const char *aXName=nullptr, const char *aYName=nullptr)
wxXmlNode * m_shape_std_node
void addPadStack(wxXmlNode *aContentNode, const PAD *aPad)
std::map< FOOTPRINT *, wxString > m_OEMRef_dict
void clearLoadedFootprints()
Frees the memory allocated for the loaded footprints in m_loaded_footprints.
std::map< wxString, wxXmlNode * > m_backdrill_spec_nodes
std::map< std::pair< PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< PAD * > > m_slot_holes
std::map< size_t, wxString > m_footprint_dict
wxXmlNode * generateLogisticSection()
Creates the logistical data header.
std::set< wxString > m_element_names
std::map< size_t, wxString > m_line_dict
void addLineDesc(wxXmlNode *aNode, int aWidth, LINE_STYLE aDashType, bool aForce=false)
void addKnockoutText(wxXmlNode *aContentNode, PCB_TEXT *aText)
std::map< size_t, wxString > m_padstack_dict
std::map< std::pair< PCB_LAYER_ID, PCB_LAYER_ID >, std::vector< BOARD_ITEM * > > m_drill_layers
std::map< wxString, FOOTPRINT * > m_footprint_refdes_dict
void generateProfile(wxXmlNode *aStepNode)
void generateLayerFeatures(wxXmlNode *aStepNode)
void SaveBoard(const wxString &aFileName, BOARD *aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
std::map< wxString, std::pair< wxString, wxString > > m_padstack_backdrill_specs
wxXmlNode * generateBOMSection(wxXmlNode *aEcadNode)
Creates the BOM section.
wxXmlNode * generateContentStackup(wxXmlNode *aContentNode)
void generateAuxilliaryLayers(wxXmlNode *aCadLayerNode)
wxXmlNode * m_cad_header_node
void addCadHeader(wxXmlNode *aEcadNode)
void generateLayerSetAuxilliary(wxXmlNode *aStepNode)
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
const std::map< std::string, UTF8 > * m_props
wxString genLayersString(PCB_LAYER_ID aTop, PCB_LAYER_ID aBottom, const char *aPrefix) const
void generateLogicalNets(wxXmlNode *aStepNode)
void addFillDesc(wxXmlNode *aNode, FILL_T aFillType, bool aForce=false)
std::map< size_t, wxString > m_user_shape_dict
size_t shapeHash(const PCB_SHAPE &aShape)
~PCB_IO_IPC2581() override
wxString genString(const wxString &aStr, const char *aPrefix=nullptr) const
wxXmlNode * m_xml_root
std::vector< FOOTPRINT * > m_loaded_footprints
bool addPolygonCutouts(wxXmlNode *aParentNode, const SHAPE_POLY_SET::POLYGON &aPolygon)
void pruneUnusedBackdrillSpecs()
std::set< wxUniChar > m_acceptable_chars
void addLayerAttributes(wxXmlNode *aNode, PCB_LAYER_ID aLayer)
wxXmlNode * generateXmlHeader()
Creates the XML header for IPC-2581.
void ensureBackdrillSpecs(const wxString &aPadstackName, const PADSTACK &aPadstack)
wxXmlNode * generateEcadSection()
Creates the ECAD section.
wxXmlDocument * m_xml_doc
wxXmlNode * insertNode(wxXmlNode *aParent, const wxString &aName)
void addPad(wxXmlNode *aContentNode, const PAD *aPad, PCB_LAYER_ID aLayer)
void generateStepSection(wxXmlNode *aCadNode)
std::map< PCB_LAYER_ID, wxString > m_layer_name_map
std::map< wxString, wxString > m_generated_names
std::set< wxString > m_backdrill_spec_used
void addLocationNode(wxXmlNode *aContentNode, double aX, double aY)
wxXmlNode * m_line_node
std::map< int, std::vector< std::pair< wxString, wxString > > > m_net_pin_dict
std::map< FOOTPRINT *, wxString > m_footprint_refdes_reverse_dict
wxXmlNode * m_lastAppendedNode
Optimization for appendNode to avoid O(n) child traversal.
wxXmlNode * m_enterpriseNode
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
STROKE_PARAMS GetStroke() const override
Definition pcb_shape.h:91
VECTOR2I GetPosition() const override
Definition pcb_shape.h:79
bool IsBorderEnabled() const
Disables the border, this is done by changing the stroke internally.
void TransformTextToPolySet(SHAPE_POLY_SET &aBuffer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc) const
Function TransformTextToPolySet Convert the text to a polygonSet describing the actual character stro...
Definition pcb_text.cpp:570
const VECTOR2I & GetStart() const
Definition pcb_track.h:154
const VECTOR2I & GetEnd() const
Definition pcb_track.h:151
virtual int GetWidth() const
Definition pcb_track.h:148
PCB_LAYER_ID BottomLayer() const
VECTOR2I GetPosition() const override
Definition pcb_track.h:614
bool FlashLayer(int aLayer) const
Check to see whether the via should have a pad on the specific layer.
const PADSTACK & Padstack() const
Definition pcb_track.h:463
int GetWidth() const override
PCB_LAYER_ID TopLayer() const
int GetDrillValue() const
Calculate the drill value for vias (m_drill if > 0, or default drill value for the board).
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Width() const
Get the current width of the segments in the chain.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
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)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
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.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int GetWidth() const
LINE_STYLE GetLineStyle() const
Handle a list of polygons defining a copper zone.
Definition zone.h:73
void SetProgressCallback(std::function< void(size_t)> aCallback)
@ ROUND_ALL_CORNERS
All angles are rounded.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ UNDEFINED
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
FILL_T
Definition eda_shape.h:56
@ NO_FILL
Definition eda_shape.h:57
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:58
@ FP_SMD
Definition footprint.h:85
@ FP_THROUGH_HOLE
Definition footprint.h:84
static const wxChar traceIpc2581[]
This program source code file is part of KiCad, a free EDA CAD application.
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:32
static constexpr std::size_t hash_val(const Types &... args)
Definition hash.h:51
size_t hash_fp_item(const EDA_ITEM *aItem, int aFlags)
Calculate hash of an EDA_ITEM.
Definition hash_eda.cpp:58
Hashing functions for EDA_ITEMs.
@ HASH_POS
Definition hash_eda.h:47
@ REL_COORD
Use coordinates relative to the parent object.
Definition hash_eda.h:50
surfaceFinishType
IPC-6012 surface finish types from Table 3-3 "Final Finish and Coating Requirements".
@ OTHER
Non-standard finish.
@ DIG
Direct Immersion Gold.
@ IAG
Immersion Silver.
@ ISN
Immersion Tin.
@ S
Solder (HASL/SMOBC)
@ HT_OSP
High Temperature OSP.
@ ENEPIG_N
ENEPIG for soldering (normal gold thickness)
@ NONE
No surface finish / not specified - skip coating layer generation.
@ OSP
Organic Solderability Preservative.
@ G
Gold (hard gold)
@ ENIG_N
ENIG for soldering (normal gold thickness)
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:172
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ User_8
Definition layer_ids.h:131
@ F_CrtYd
Definition layer_ids.h:116
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ Dwgs_User
Definition layer_ids.h:107
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ User_6
Definition layer_ids.h:129
@ User_7
Definition layer_ids.h:130
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ User_5
Definition layer_ids.h:128
@ Eco1_User
Definition layer_ids.h:109
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ User_9
Definition layer_ids.h:132
@ F_Fab
Definition layer_ids.h:119
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Eco2_User
Definition layer_ids.h:110
@ User_3
Definition layer_ids.h:126
@ User_1
Definition layer_ids.h:124
@ B_SilkS
Definition layer_ids.h:101
@ User_4
Definition layer_ids.h:127
@ User_2
Definition layer_ids.h:125
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
bool IsValidLayer(int aLayerId)
Test whether a given integer is a valid layer index, i.e.
Definition layer_ids.h:655
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:76
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
static const std::map< surfaceFinishType, wxString > surfaceFinishTypeToString
Map surfaceFinishType enum to IPC-2581 XML string values.
static bool isOppositeSideSilk(const FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer)
static const std::map< wxString, surfaceFinishType > surfaceFinishMap
Map KiCad surface finish strings to IPC-6012 surfaceFinishType enum.
static surfaceFinishType getSurfaceFinishType(const wxString &aFinish)
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
std::vector< FAB_LAYER_COLOR > dummy
const std::vector< FAB_LAYER_COLOR > & GetStandardColors(BOARD_STACKUP_ITEM_TYPE aType)
#define KEY_CORE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
LINE_STYLE
Dashed line types.
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:266
PCB_LAYER_ID start
Definition padstack.h:269
PCB_LAYER_ID end
Definition padstack.h:270
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:267
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:281
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
KIBIS_PIN * pin
VECTOR2I center
int radius
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:107
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:100
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694