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