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