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