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