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->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
2718
2719 // A round drill with unequal axes is drilled at its smaller axis, like its layer hole
2720 int diameter = std::min( aPad->GetDrillSizeX(), aPad->GetDrillSizeY() );
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->GetDrillSizeX(), aPad->GetDrillSizeY() ) / 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
2953 // must-cut layer. KiCad has no explicit fabricator tolerance, so use
2954 // half the dielectric thickness between must-cut and must-not-cut as
2955 // a nominal midpoint. Falls back to zero if no inner signal exists.
2956 int stubLength = 0;
2957
2958 if( mustNotCut != UNDEFINED_LAYER )
2959 {
2960 int dielectric = stackup.GetLayerDistance( aDrill.end, mustNotCut );
2961
2962 if( dielectric > 0 )
2963 stubLength = dielectric / 2;
2964 }
2965
2966 {
2967 wxXmlNode* bd = appendNode( specNode, "Backdrill" );
2968 addAttribute( bd, "type", wxT( "MAX_STUB_LENGTH" ) );
2969
2970 wxXmlNode* p = appendNode( bd, "Property" );
2971 addAttribute( p, "value", floatVal( m_scale * stubLength ) );
2973 }
2974
2975 m_backdrill_spec_nodes[aSpecName] = specNode;
2976
2977 return aSpecName;
2978 };
2979
2980 int specIndex = m_backdrill_spec_index + 1;
2981
2982 wxString secondarySpec = createSpec( secondary, wxString::Format( wxT( "BD_%dA" ), specIndex ) );
2983 wxString tertiarySpec = createSpec( tertiary, wxString::Format( wxT( "BD_%dB" ), specIndex ) );
2984
2985 if( secondarySpec.IsEmpty() && tertiarySpec.IsEmpty() )
2986 return;
2987
2988 m_backdrill_spec_index = specIndex;
2989 m_padstack_backdrill_specs.emplace( aPadstackName,
2990 std::array<wxString, 2>{ secondarySpec, tertiarySpec } );
2991}
2992
2993
2994void PCB_IO_IPC2581::addBackdrillSpecRefs( wxXmlNode* aHoleNode, const wxString& aPadstackName )
2995{
2996 auto it = m_padstack_backdrill_specs.find( aPadstackName );
2997
2998 if( it == m_padstack_backdrill_specs.end() )
2999 return;
3000
3001 auto addRef = [&]( const wxString& aSpecName )
3002 {
3003 if( aSpecName.IsEmpty() )
3004 return;
3005
3006 wxXmlNode* specRefNode = appendNode( aHoleNode, "SpecRef" );
3007 addAttribute( specRefNode, "id", aSpecName );
3008 m_backdrill_spec_used.insert( aSpecName );
3009 };
3010
3011 for( const wxString& specName : it->second )
3012 addRef( specName );
3013}
3014
3015
3016void PCB_IO_IPC2581::addPostMachiningSpecRefs( wxXmlNode* aHoleNode, const DRILL_OPERATION& aOperation )
3017{
3019 return;
3020
3021 auto addSide = [&]( const DRILL_POST_MACHINING& aMachining, bool aFront )
3022 {
3023 bool countersink = aMachining.m_Mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK;
3024 bool counterbore = aMachining.m_Mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE;
3025
3026 if( aMachining.m_Size <= 0 || ( !countersink && !counterbore ) )
3027 return;
3028
3029 auto key = std::make_tuple( aFront, static_cast<int>( aMachining.m_Mode ), aMachining.m_Size,
3030 counterbore ? aMachining.m_Depth : 0, countersink ? aMachining.m_Angle : 0 );
3031 auto [it, inserted] = m_post_machining_specs.try_emplace( key );
3032
3033 if( inserted )
3034 {
3035 it->second = wxString::Format( countersink ? wxT( "CSK_%zu" ) : wxT( "CBORE_%zu" ),
3036 m_post_machining_specs.size() );
3037 wxXmlNode* spec = appendNode( m_cad_header_node, "Spec" );
3038 addAttribute( spec, "name", it->second );
3039
3040 auto addValue = [&]( const wxString& aType, const wxString& aValue, const wxString& aUnit )
3041 {
3042 wxXmlNode* drill = appendNode( spec, "SecondaryDrill" );
3043 addAttribute( drill, "type", aType );
3044 wxXmlNode* property = appendNode( drill, "Property" );
3045 addAttribute( property, "value", aValue );
3046 addAttribute( property, "unit", aUnit );
3047 };
3048
3049 wxString lengthUnit = propertyUnitForCadUnits( m_units_str );
3050 addValue( wxT( "MAJOR_DIAMETER" ), floatVal( m_scale * aMachining.m_Size ), lengthUnit );
3051
3052 if( counterbore && aMachining.m_Depth > 0 )
3053 addValue( wxT( "DEPTH" ), floatVal( m_scale * aMachining.m_Depth ), lengthUnit );
3054
3055 if( countersink && aMachining.m_Angle > 0 )
3056 addValue( wxT( "ANGLE" ), floatVal( aMachining.m_Angle / 10.0 ), wxT( "DEGREES" ) );
3057
3058 wxXmlNode* side = appendNode( spec, "SecondaryDrill" );
3059 addAttribute( side, "type", "SIDE" );
3060 wxXmlNode* property = appendNode( side, "Property" );
3061 addAttribute( property, "text", aFront ? wxT( "TOP" ) : wxT( "BOTTOM" ) );
3062
3063 if( ( counterbore && aMachining.m_Depth <= 0 ) || ( countersink && aMachining.m_Angle <= 0 ) )
3064 {
3065 wxXmlNode* other = appendNode( spec, "SecondaryDrill" );
3066 addAttribute( other, "type", "OTHER" );
3067 addAttribute( other, "comment", counterbore ? wxT( "COUNTERBORE" ) : wxT( "COUNTERSINK" ) );
3068 }
3069 }
3070
3071 wxXmlNode* ref = appendNode( aHoleNode, "SpecRef" );
3072 addAttribute( ref, "id", it->second );
3073 };
3074
3075 addSide( aOperation.m_FrontPostMachining, true );
3076 addSide( aOperation.m_BackPostMachining, false );
3077}
3078
3079
3081{
3082 if( !m_cad_header_node )
3083 return;
3084
3085 auto it = m_backdrill_spec_nodes.begin();
3086
3087 while( it != m_backdrill_spec_nodes.end() )
3088 {
3089 if( m_backdrill_spec_used.find( it->first ) == m_backdrill_spec_used.end() )
3090 {
3091 wxXmlNode* specNode = it->second;
3092
3093 if( specNode )
3094 {
3095 m_cad_header_node->RemoveChild( specNode );
3096 deleteNode( specNode );
3097 }
3098
3099 it = m_backdrill_spec_nodes.erase( it );
3100 }
3101 else
3102 {
3103 ++it;
3104 }
3105 }
3106}
3107
3108
3109bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
3110 const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
3111 int aWidth, LINE_STYLE aDashType )
3112{
3113 wxXmlNode* polygonNode = nullptr;
3114
3115 if( aPolygon.PointCount() < 3 )
3116 return false;
3117
3118 auto make_node =
3119 [&]()
3120 {
3121 polygonNode = appendNode( aParentNode, "Polygon" );
3122 wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
3123
3124 const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
3125 addXY( polybeginNode, pts[0] );
3126
3127 for( size_t ii = 1; ii < pts.size(); ++ii )
3128 {
3129 wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
3130 addXY( polyNode, pts[ii] );
3131 }
3132
3133 wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
3134 addXY( polyendNode, pts[0] );
3135 };
3136
3137 // Allow the case where we don't want line/fill information in the polygon
3138 if( aFillType == FILL_T::NO_FILL )
3139 {
3140 make_node();
3141 // If we specify a line width, we need to add a LineDescRef node and
3142 // since this is only valid for a non-filled polygon, we need to create
3143 // the fillNode as well
3144 if( aWidth > 0 )
3145 addLineDesc( polygonNode, aWidth, aDashType, true );
3146 }
3147 else
3148 {
3149 wxCHECK( aWidth == 0, false );
3150 make_node();
3151 }
3152
3153 addFillDesc( polygonNode, aFillType );
3154
3155 return true;
3156}
3157
3158
3159bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
3160 const SHAPE_POLY_SET::POLYGON& aPolygon )
3161{
3162 for( size_t ii = 1; ii < aPolygon.size(); ++ii )
3163 {
3164 wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
3165
3166 wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
3167 wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
3168
3169 const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
3170 addXY( polybeginNode, hole[0] );
3171
3172 for( size_t jj = 1; jj < hole.size(); ++jj )
3173 {
3174 wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
3175 addXY( polyNode, hole[jj] );
3176 }
3177
3178 wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
3179 addXY( polyendNode, hole[0] );
3180 }
3181
3182 return true;
3183}
3184
3185
3186bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
3187 int aWidth, LINE_STYLE aDashType )
3188{
3189 if( aPolySet.OutlineCount() == 0 )
3190 return false;
3191
3192 wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
3193
3194 const SHAPE_POLY_SET* source = &aPolySet;
3195 SHAPE_POLY_SET merged;
3196
3197 if( aPolySet.OutlineCount() > 1 )
3198 {
3199 merged = aPolySet;
3200 merged.Simplify();
3201
3202 if( merged.OutlineCount() > 0 )
3203 source = &merged;
3204 }
3205
3206 for( int ii = 0; ii < source->OutlineCount(); ++ii )
3207 {
3208 if( !addPolygonNode( outlineNode, source->Outline( ii ) ) )
3209 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
3210 }
3211
3212 if( !outlineNode->GetChildren() )
3213 {
3214 aParentNode->RemoveChild( outlineNode );
3215 deleteNode( outlineNode );
3216 return false;
3217 }
3218
3219 addLineDesc( outlineNode, aWidth, aDashType );
3220
3221 return true;
3222}
3223
3224
3225bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
3226 int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType )
3227{
3228 if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
3229 return false;
3230
3231 wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
3232
3233 if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
3234 {
3235 // Do not attempt to add cutouts to shapes that are already hollow
3236 if( aFillType != FILL_T::NO_FILL )
3237 addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
3238 }
3239 else
3240 {
3241 aParentNode->RemoveChild( contourNode );
3242 deleteNode( contourNode );
3243 return false;
3244 }
3245
3246 return true;
3247}
3248
3249
3250void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
3251{
3252 SHAPE_POLY_SET board_outline;
3253
3254 if( !m_board->GetBoardPolygonOutlines( board_outline, false ) || board_outline.OutlineCount() == 0 )
3255 {
3256 Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
3257 return;
3258 }
3259
3260 wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
3261
3262 if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
3263 {
3264 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
3265 aStepNode->RemoveChild( profileNode );
3266 deleteNode( profileNode );
3267 return;
3268 }
3269
3270 addPolygonCutouts( profileNode, board_outline.Polygon( 0 ) );
3271}
3272
3273
3274static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
3275{
3276 if( !aFootprint )
3277 return false;
3278
3279 if( aLayer != F_SilkS && aLayer != B_SilkS )
3280 return false;
3281
3282 if( aFootprint->IsFlipped() )
3283 return aLayer == F_SilkS;
3284
3285 return aLayer == B_SilkS;
3286}
3287
3288
3289wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
3290{
3291 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
3292 fp->SetParentGroup( nullptr );
3293 fp->SetPosition( { 0, 0 } );
3294 fp->SetOrientation( ANGLE_0 );
3295
3296 // Track original flipped state before normalization. This is needed to correctly
3297 // determine OtherSideView content per IPC-2581C. After flipping, layer IDs swap,
3298 // so for bottom components, B_SilkS/B_Fab after flip is actually the primary view.
3299 bool wasFlipped = fp->IsFlipped();
3300
3301 // Normalize package geometry to the unflipped footprint coordinate system.
3302 if( fp->IsFlipped() )
3303 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
3304
3305 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
3306 wxString name = genString( wxString::Format( "%s_%zu",
3307 fp->GetFPID().GetLibItemName().wx_str(),
3308 m_footprint_dict.size() + 1 ) );
3309
3310 auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
3311
3313 addAttribute( aContentNode, "packageRef", iter->second );
3314
3315 // The BOM needs the name but the geometry below goes into the shape dictionaries
3316 // The export removes the Package thus it must not keep the geometry
3317 if( !success || !included( IPC2581::SECTION::PACKAGES ) )
3318 return nullptr;
3319
3320 // Package and Component nodes are at the same level, so we need to find the parent
3321 // which should be the Step node
3322 wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
3323 wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
3324
3325 addAttribute( packageNode, "name", name );
3326 addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
3327
3328 // We don't specially identify pin 1 in our footprints, so we need to guess
3329 if( fp->FindPadByNumber( "1" ) )
3330 addAttribute( packageNode, "pinOne", "1" );
3331 else if ( fp->FindPadByNumber( "A1" ) )
3332 addAttribute( packageNode, "pinOne", "A1" );
3333 else if ( fp->FindPadByNumber( "A" ) )
3334 addAttribute( packageNode, "pinOne", "A" );
3335 else if ( fp->FindPadByNumber( "a" ) )
3336 addAttribute( packageNode, "pinOne", "a" );
3337 else if ( fp->FindPadByNumber( "a1" ) )
3338 addAttribute( packageNode, "pinOne", "a1" );
3339 else if ( fp->FindPadByNumber( "Anode" ) )
3340 addAttribute( packageNode, "pinOne", "Anode" );
3341 else if ( fp->FindPadByNumber( "ANODE" ) )
3342 addAttribute( packageNode, "pinOne", "ANODE" );
3343 else
3344 addAttribute( packageNode, "pinOne", "UNKNOWN" );
3345
3346 // Infer pinOneOrientation from pin 1 position relative to package centroid.
3347 // IPC-2581C 8.2.3.6 requires a comment attribute when OTHER is used.
3348 PAD* pinOnePad = fp->FindPadByNumber( "1" );
3349
3350 if( !pinOnePad )
3351 pinOnePad = fp->FindPadByNumber( "A1" );
3352
3353 if( pinOnePad && fp->Pads().size() >= 2 )
3354 {
3355 VECTOR2I pinPos = pinOnePad->GetFPRelativePosition();
3356 BOX2I fpBBox = fp->GetBoundingBox();
3357 VECTOR2I center = fpBBox.GetCenter();
3358
3359 // Use 5% of each dimension as the centerline tolerance band
3360 int tolX = fpBBox.GetWidth() / 20;
3361 int tolY = fpBBox.GetHeight() / 20;
3362
3363 bool onCenterX = std::abs( pinPos.x - center.x ) <= tolX;
3364 bool onCenterY = std::abs( pinPos.y - center.y ) <= tolY;
3365
3366 const char* orientation = "OTHER";
3367
3368 if( onCenterX && onCenterY )
3369 orientation = "CENTER";
3370 else if( onCenterX && pinPos.y < center.y )
3371 orientation = "UPPER_CENTER";
3372 else if( onCenterX && pinPos.y > center.y )
3373 orientation = "LOWER_CENTER";
3374 else if( onCenterY && pinPos.x < center.x )
3375 orientation = "LEFT";
3376 else if( onCenterY && pinPos.x > center.x )
3377 orientation = "RIGHT";
3378 else if( pinPos.x < center.x && pinPos.y < center.y )
3379 orientation = "UPPER_LEFT";
3380 else if( pinPos.x > center.x && pinPos.y < center.y )
3381 orientation = "UPPER_RIGHT";
3382 else if( pinPos.x < center.x && pinPos.y > center.y )
3383 orientation = "LOWER_LEFT";
3384 else
3385 orientation = "LOWER_RIGHT";
3386
3387 addAttribute( packageNode, "pinOneOrientation", orientation );
3388 }
3389 else
3390 {
3391 addAttribute( packageNode, "pinOneOrientation", "OTHER" );
3392 addAttribute( packageNode, "comment", "Pin 1 orientation could not be determined" );
3393 }
3394
3395 // After normalization: F_CrtYd is top, B_CrtYd is bottom.
3396 // For bottom components (wasFlipped), these are swapped from original orientation.
3397 const SHAPE_POLY_SET& courtyard_primary = wasFlipped ? fp->GetCourtyard( B_CrtYd )
3398 : fp->GetCourtyard( F_CrtYd );
3399 const SHAPE_POLY_SET& courtyard_other = wasFlipped ? fp->GetCourtyard( F_CrtYd )
3400 : fp->GetCourtyard( B_CrtYd );
3401
3402 if( courtyard_primary.OutlineCount() > 0 )
3403 {
3404 addOutlineNode( packageNode, courtyard_primary, courtyard_primary.Outline( 0 ).Width(),
3406 }
3407 else
3408 {
3409 SHAPE_POLY_SET bbox = fp->GetBoundingHull();
3410 addOutlineNode( packageNode, bbox );
3411 }
3412
3413 if( courtyard_other.OutlineCount() > 0 )
3414 {
3416 {
3417 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
3418 addOutlineNode( otherSideViewNode, courtyard_other, courtyard_other.Outline( 0 ).Width(),
3420 }
3421 }
3422
3423 wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
3424 addAttribute( pickupPointNode, "x", "0.0" );
3425 addAttribute( pickupPointNode, "y", "0.0" );
3426
3427 std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
3428
3429 for( BOARD_ITEM* item : fp->GraphicalItems() )
3430 {
3431 PCB_LAYER_ID layer = item->GetLayer();
3432
3436 if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
3437 continue;
3438
3439 if( m_revision == IPC2581::REVISION::B && isOppositeSideSilk( fp.get(), layer ) )
3440 continue;
3441
3442 bool is_abs = true;
3443
3444 if( item->Type() == PCB_SHAPE_T )
3445 {
3446 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
3447
3448 // Circles and Rectanges only have size information so we need to place them in
3449 // a separate node that has a location
3450 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
3451 is_abs = false;
3452 }
3453
3454 elements[item->GetLayer()][is_abs].push_back( item );
3455 }
3456
3457 auto add_base_node =
3458 [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
3459 {
3460 wxXmlNode* parent = packageNode;
3461
3462 // Determine if this layer content should go in OtherSideView.
3463 // Per IPC-2581C, OtherSideView contains geometry visible from the opposite
3464 // side of the package body from the primary view.
3465 //
3466 // For non-flipped (top) components: B_SilkS/B_Fab → OtherSideView
3467 // For flipped (bottom) components after normalization: F_SilkS/F_Fab → OtherSideView
3468 // (because after flip, B_SilkS/B_Fab contains the original primary graphics)
3469 bool is_other_side = wasFlipped ? ( aLayer == F_SilkS || aLayer == F_Fab )
3470 : ( aLayer == B_SilkS || aLayer == B_Fab );
3471
3472 if( is_other_side && m_revision == IPC2581::REVISION::C )
3473 {
3474 if( !otherSideViewNode )
3475 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
3476
3477 parent = otherSideViewNode;
3478 }
3479
3480 wxString nodeName;
3481
3482 if( aLayer == F_SilkS || aLayer == B_SilkS )
3483 nodeName = "SilkScreen";
3484 else if( aLayer == F_Fab || aLayer == B_Fab )
3485 nodeName = "AssemblyDrawing";
3486 else
3487 wxASSERT( false );
3488
3489 wxXmlNode* new_node = appendNode( parent, nodeName );
3490 return new_node;
3491 };
3492
3493 auto add_marking_node =
3494 [&]( wxXmlNode* aNode ) -> wxXmlNode*
3495 {
3496 wxXmlNode* marking_node = appendNode( aNode, "Marking" );
3497 addAttribute( marking_node, "markingUsage", "NONE" );
3498 return marking_node;
3499 };
3500
3501 std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
3502 std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
3503
3504 for( auto layer : { F_Fab, B_Fab } )
3505 {
3506 if( elements.find( layer ) != elements.end() )
3507 {
3508 if( elements[layer][true].size() > 0 )
3509 layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
3510 else if( elements[layer][false].size() > 0 )
3511 layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
3512 }
3513 }
3514
3515 for( auto& [layer, map] : elements )
3516 {
3517 wxXmlNode* layer_node = add_base_node( layer );
3518 wxXmlNode* marking_node = add_marking_node( layer_node );
3519 wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
3520 bool update_bbox = false;
3521
3522 if( layer == F_Fab || layer == B_Fab )
3523 {
3524 layer_nodes[layer] = layer_node;
3525 update_bbox = true;
3526 }
3527
3528 for( auto& [is_abs, vec] : map )
3529 {
3530 for( BOARD_ITEM* item : vec )
3531 {
3532 wxXmlNode* output_node = nullptr;
3533
3534 if( update_bbox )
3535 layer_bbox[layer].Merge( item->GetBoundingBox() );
3536
3537 if( !is_abs )
3538 output_node = add_marking_node( layer_node );
3539 else
3540 output_node = group_node;
3541
3542 switch( item->Type() )
3543 {
3544 case PCB_TEXT_T:
3545 {
3546 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
3547
3548 if( text->IsKnockout() )
3549 addKnockoutText( output_node, text );
3550 else
3551 addText( output_node, text, text->GetFontMetrics() );
3552
3553 break;
3554 }
3555
3556 case PCB_TEXTBOX_T:
3557 {
3558 PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
3559 addText( output_node, text, text->GetFontMetrics() );
3560
3561 // We want to force this to be a polygon to get absolute coordinates
3562 if( text->IsBorderEnabled() )
3563 {
3564 SHAPE_POLY_SET poly_set;
3565 text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
3566 addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
3567 text->GetBorderWidth() );
3568 }
3569
3570 break;
3571 }
3572
3573 case PCB_SHAPE_T:
3574 {
3575 if( !is_abs )
3576 addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
3577
3578 // When in Marking context (!is_abs), use inline geometry to avoid
3579 // unresolved UserPrimitiveRef errors in validators like Vu2581
3580 addShape( output_node, *static_cast<PCB_SHAPE*>( item ), !is_abs );
3581
3582 break;
3583 }
3584
3585 default: break;
3586 }
3587 }
3588 }
3589
3590 if( group_node->GetChildren() == nullptr )
3591 {
3592 marking_node->RemoveChild( group_node );
3593 layer_node->RemoveChild( marking_node );
3594 delete group_node;
3595 delete marking_node;
3596 }
3597 }
3598
3599 for( auto&[layer, bbox] : layer_bbox )
3600 {
3601 if( bbox.GetWidth() > 0 )
3602 {
3603 wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
3604
3605 SHAPE_LINE_CHAIN outline;
3606 std::vector<VECTOR2I> points( 4 );
3607 points[0] = bbox.GetPosition();
3608 points[2] = bbox.GetEnd();
3609 points[1].x = points[0].x;
3610 points[1].y = points[2].y;
3611 points[3].x = points[2].x;
3612 points[3].y = points[0].y;
3613
3614 outline.Append( points );
3615 addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
3616 addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
3617 }
3618 }
3619
3620 std::map<wxString, wxXmlNode*> pin_nodes;
3621
3622 for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
3623 {
3624 PAD* pad = fp->Pads()[ii];
3625 FAB_PIN fabPin = MakeFabPin( *pad, ii );
3626 wxString pin_name = sanitizeId( fabPin.m_name );
3627 wxXmlNode* pinNode = nullptr;
3628
3629 auto [ it, inserted ] = pin_nodes.emplace( pin_name, nullptr );
3630
3631 if( inserted )
3632 {
3633 pinNode = appendNode( packageNode, "Pin" );
3634 it->second = pinNode;
3635
3636 addAttribute( pinNode, "number", pin_name );
3637
3638 switch( fabPin.m_electrical )
3639 {
3640 case FAB_ELECTRICAL::MECHANICAL: addAttribute( pinNode, "electricalType", "MECHANICAL" ); break;
3641 case FAB_ELECTRICAL::ELECTRICAL: addAttribute( pinNode, "electricalType", "ELECTRICAL" ); break;
3642 case FAB_ELECTRICAL::UNDEFINED: addAttribute( pinNode, "electricalType", "UNDEFINED" ); break;
3643 }
3644
3645 if( pad->HasHole() )
3646 addAttribute( pinNode, "type", "THRU" );
3647 else
3648 addAttribute( pinNode, "type", "SURFACE" );
3649
3650 if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
3651 {
3652 wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
3653 EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
3654
3655 if( fp->IsFlipped() )
3656 pad_angle = pad_angle.Invert().Normalize();
3657
3658 if( pad_angle != ANGLE_0 )
3659 xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
3660 }
3661
3662 addLocationNode( pinNode, *pad, true, pad->GetLayer() );
3663 addShape( pinNode, *pad, pad->GetLayer() );
3664 }
3665
3666 // We just need the padstack, we don't need the reference here. The reference will be
3667 // created in the LayerFeature set
3668 wxXmlNode dummy;
3669 addPadStack( &dummy, pad );
3670 }
3671
3672 if( otherSideViewNode )
3673 packageNode->AddChild( otherSideViewNode );
3674
3675 return packageNode;
3676}
3677
3678
3679void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
3680{
3681 std::vector<wxXmlNode*> componentNodes;
3682 std::vector<wxXmlNode*> packageNodes;
3683 std::set<wxString> packageNames;
3684
3685 bool generate_unique = m_OEMRef.empty();
3686
3687 reportPhase( _( "Generating components" ) );
3688
3689 if( m_progressReporter )
3690 m_progressReporter->SetMaxProgress( m_board->Footprints().size() );
3691
3692 for( FOOTPRINT* fp : m_board->Footprints() )
3693 {
3694 tickProgress();
3696
3697 wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
3698 if( refDesEmitted() )
3699 addAttribute( componentNode, "refDes", componentName( fp ) );
3700
3701 // addPackage gets a copy without the duplicates
3702 // Thus it records only the first footprint of each Package
3703 for( PAD* pad : fp->Pads() )
3704 {
3705 if( pad->GetNetCode() > 0 )
3706 m_net_pin_dict[pad->GetNetCode()].emplace_back( componentName( fp ), pinName( pad ) );
3707 }
3708 wxXmlNode* pkg = addPackage( componentNode, fp );
3709
3710 if( pkg )
3711 packageNodes.push_back( pkg );
3712
3713 wxString name = generate_unique ? wxString() : row.Field( m_OEMRef );
3714
3715 if( name.IsEmpty() )
3716 {
3717 name = wxString::Format( "%s_%s_%s",
3718 fp->GetFPID().GetFullLibraryName(),
3719 fp->GetFPID().GetLibItemName().wx_str(),
3720 row.Value() );
3721 }
3722
3723 if( !m_OEMRef_dict.emplace( fp, name ).second )
3724 {
3725 Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
3727 }
3728
3729 addAttribute( componentNode, "part", genString( name, "REF" ) );
3730 addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
3731
3732 if( row.m_mount == FAB_MOUNT::THT )
3733 addAttribute( componentNode, "mountType", "THMT" );
3734 else if( row.m_mount == FAB_MOUNT::SMT )
3735 addAttribute( componentNode, "mountType", "SMT" );
3736 else
3737 addAttribute( componentNode, "mountType", "OTHER" );
3738
3739 if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
3740 {
3741 wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
3742
3743 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3744
3745 if( fp->IsFlipped() )
3746 fp_angle = ( fp_angle.Invert() - ANGLE_180 ).Normalize();
3747
3748 if( fp_angle != ANGLE_0 )
3749 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3750
3751 if( fp->IsFlipped() )
3752 addAttribute( xformNode, "mirror", "true" );
3753 }
3754
3755 addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
3756
3757 componentNodes.push_back( componentNode );
3758 }
3759
3760 // The loop above fills the dictionaries that the BOM and the LogicalNet read
3761 // Thus it always runs and only the attachments below use the function mode
3762 auto attachOrDrop =
3763 [&]( std::vector<wxXmlNode*>& aNodes, IPC2581::SECTION aSection, bool aInsert )
3764 {
3765 bool keep = included( aSection );
3766
3767 for( wxXmlNode*& node : aNodes )
3768 {
3769 if( !keep )
3770 {
3771 deleteNode( node );
3772 }
3773 else if( aInsert )
3774 {
3775 insertNode( aStepNode, node );
3776 m_last_padstack = node;
3777 }
3778 else
3779 {
3780 aStepNode->AddChild( node );
3781 }
3782 }
3783
3784 if( !keep )
3785 aNodes.clear();
3786 };
3787
3788 attachOrDrop( m_padstacks, IPC2581::SECTION::PADSTACKS, true );
3789 attachOrDrop( packageNodes, IPC2581::SECTION::PACKAGES, false );
3790 attachOrDrop( componentNodes, IPC2581::SECTION::COMPONENTS, false );
3791}
3792
3793
3794void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
3795{
3796 reportPhase( _( "Generating logical netlist" ) );
3797
3798 for( auto& [ net, pin_pair] : m_net_pin_dict )
3799 {
3800 wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
3801 addAttribute( netNode, "name",
3802 netName( m_board->GetNetInfo().GetNetItem( net )->GetNetname() ) ) ;
3803
3804 for( auto& [cmp, pin] : pin_pair )
3805 {
3806 wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
3807
3809 addAttribute( netPinNode, "componentRef", cmp );
3810 addAttribute( netPinNode, "pin", pin );
3811 }
3812 //TODO: Finish
3813 }
3814}
3815
3816
3817void PCB_IO_IPC2581::generatePhyNetGroup( wxXmlNode* aStepNode )
3818{
3819 struct NET_POINT
3820 {
3821 VECTOR2I m_pos;
3822 PCB_LAYER_ID m_layer;
3823 PCB_LAYER_ID m_secondary;
3824 wxString m_exposure;
3825 bool m_end;
3826 bool m_via;
3827 int m_width;
3828 int m_drill;
3829 const PAD* m_pad;
3830 };
3831
3832 std::map<int, std::vector<NET_POINT>> byNet;
3833
3834 reportPhase( _( "Generating physical netlist" ) );
3835 std::vector<FAB_TEST_POINT> testPoints = FabTestPoints( *m_board );
3836
3837 if( m_progressReporter )
3838 m_progressReporter->SetMaxProgress( static_cast<int>( testPoints.size() ) );
3839
3840 // Section 8.2.3.9.1 makes a physical net point a location that you can probe
3841 // Thus only a feature on an outer layer applies
3842 auto exposure =
3843 []( const FAB_TEST_POINT& aPoint ) -> wxString
3844 {
3845 bool frontOpen = !aPoint.m_front || aPoint.m_frontMask;
3846 bool backOpen = !aPoint.m_back || aPoint.m_backMask;
3847
3848 if( frontOpen && backOpen )
3849 return wxT( "EXPOSED" );
3850
3851 if( backOpen )
3852 return wxT( "COVERED_PRIMARY" );
3853
3854 if( frontOpen )
3855 return wxT( "COVERED_SECONDARY" );
3856
3857 return wxT( "COVERED" );
3858 };
3859
3860 // Keep IPC-2581's pad-first order within each physical net
3861 for( bool wantPad : { true, false } )
3862 {
3863 for( const FAB_TEST_POINT& testPoint : testPoints )
3864 {
3865 // Buried vias and inner-only pads cannot be probed
3866 if( ( testPoint.m_pad != nullptr ) != wantPad || testPoint.m_netCode <= 0
3867 || ( !testPoint.m_front && !testPoint.m_back ) )
3868 {
3869 continue;
3870 }
3871
3872 NET_POINT point;
3873 point.m_pos = testPoint.m_position;
3874 point.m_layer = testPoint.m_front ? F_Cu : B_Cu;
3875 point.m_secondary = ( testPoint.m_front && testPoint.m_back ) ? B_Cu : UNDEFINED_LAYER;
3876 point.m_exposure = exposure( testPoint );
3877 point.m_end = testPoint.m_netEnd;
3878 point.m_via = testPoint.m_via != nullptr;
3879 point.m_width = testPoint.m_pad ? testPoint.m_pad->GetSize( point.m_layer ).x
3880 : testPoint.m_via->GetWidth( point.m_layer );
3881 point.m_drill = testPoint.m_drill;
3882 point.m_pad = testPoint.m_pad;
3883 byNet[testPoint.m_netCode].push_back( point );
3884 tickProgress();
3885 }
3886 }
3887
3888 if( byNet.empty() )
3889 return;
3890
3891 wxXmlNode* groupNode = appendNode( aStepNode, "PhyNetGroup" );
3892 addAttribute( groupNode, "name", "NETS" );
3893
3894 for( const auto& [netcode, points] : byNet )
3895 {
3896 NETINFO_ITEM* net = m_board->GetNetInfo().GetNetItem( netcode );
3897
3898 if( !net )
3899 continue;
3900
3901 // NetKey is one key for LogicalNet@name and PhyNet@name
3902 // Thus the two views of a net must not have the same name
3903 wxXmlNode* netNode = appendNode( groupNode, "PhyNet" );
3904 addAttribute( netNode, "name", netName( net->GetNetname(), wxT( "_PHY" ) ) );
3905
3906 for( const NET_POINT& point : points )
3907 {
3908 wxXmlNode* pointNode = appendNode( netNode, "PhyNetPoint" );
3909 addXY( pointNode, point.m_pos );
3910 addAttribute( pointNode, "layerRef", m_layer_name_map[point.m_layer] );
3911
3912 if( point.m_secondary != UNDEFINED_LAYER )
3913 {
3914 addAttribute( pointNode, "secondaryLayerRef",
3915 m_layer_name_map[point.m_secondary] );
3916 }
3917
3918 addAttribute( pointNode, "netNode", point.m_end ? "END" : "MIDDLE" );
3919 addAttribute( pointNode, "exposure", point.m_exposure );
3920
3921 if( point.m_via )
3922 addAttribute( pointNode, "via", "true" );
3923
3924 if( point.m_pad )
3925 {
3926 addShape( pointNode, *point.m_pad, point.m_layer );
3927 }
3928 else
3929 {
3930 PAD dummy( nullptr );
3931 dummy.SetPosition( point.m_pos );
3932 dummy.SetSize( point.m_layer, VECTOR2I( point.m_width, point.m_width ) );
3933
3934 if( point.m_drill > 0 )
3935 dummy.SetDrillSize( VECTOR2I( point.m_drill, point.m_drill ) );
3936
3937 addShape( pointNode, dummy, point.m_layer );
3938 }
3939 }
3940 }
3941}
3942
3943
3944void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
3945{
3946 LSEQ layers = m_board->GetEnabledLayers().Seq();
3947 const NETINFO_LIST& nets = m_board->GetNetInfo();
3949
3950 for( PCB_LAYER_ID layer : layers )
3951 {
3952 if( !layerIncluded( layer ) )
3953 continue;
3954
3955 if( m_progressReporter )
3956 m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
3957
3958
3959 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3960 addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
3961
3962 const FAB_LAYER_INDEX::NET_ITEMS& netsOnLayer = index.Items( layer );
3963
3964 auto process_net = [&] ( int net )
3965 {
3966 auto netItems = netsOnLayer.find( net );
3967
3968 if( netItems == netsOnLayer.end() || netItems->second.empty() )
3969 return;
3970
3971 generateLayerSetNet( layerNode, layer, netItems->second );
3972 };
3973
3974 for( const NETINFO_ITEM* net : nets )
3975 {
3976 tickProgress( wxString::Format( _( "Exporting layer %s, net %s" ),
3977 m_board->GetLayerName( layer ),
3978 net->GetNetname() ) );
3979
3980 process_net( net->GetNetCode() );
3981 }
3982
3983 if( layerNode->GetChildren() == nullptr )
3984 {
3985 aStepNode->RemoveChild( layerNode );
3986 deleteNode( layerNode );
3987 }
3988 }
3989}
3990
3991
3992void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
3993{
3994 if( !included( IPC2581::SECTION::DRILL_ROUT ) )
3995 return;
3996
3997 int hole_count = 1;
3998
3999 for( const auto& [span, vec] : m_drill_layers )
4000 {
4001 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
4002 layerNode->AddAttribute( "layerRef", genLayersString( span.TopLayer(), span.BottomLayer(), "DRILL" ) );
4003
4004 for( BOARD_ITEM* item : vec )
4005 {
4006 if( item->Type() == PCB_VIA_T )
4007 {
4008 PCB_VIA* via = static_cast<PCB_VIA*>( item );
4009 const DRILL_OPERATION* operation = m_fabDrillModel->Find( span, via );
4010
4011 if( !operation )
4012 continue;
4013
4014 auto it = m_padstack_dict.find( ipcPadstackHash( via, operation ) );
4015
4016 if( it == m_padstack_dict.end() )
4017 {
4018 Report( _( "Via uses unsupported padstack; omitted from drill data." ),
4020 continue;
4021 }
4022
4023 wxXmlNode* padNode = appendNode( layerNode, "Set" );
4024 addAttribute( padNode, "geometry", it->second );
4025
4026 if( via->GetNetCode() > 0 )
4027 addAttribute( padNode, "net", netName( via->GetNetname() ) );
4028
4029 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
4030 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
4031 addAttribute( holeNode, "diameter", floatVal( m_scale * operation->m_Diameter ) );
4032 addAttribute( holeNode, "platingStatus", "VIA" );
4033 addAttribute( holeNode, "plusTol", "0.0" );
4034 addAttribute( holeNode, "minusTol", "0.0" );
4035 addXY( holeNode, via->GetPosition() );
4036 addBackdrillSpecRefs( holeNode, it->second );
4037 addPostMachiningSpecRefs( holeNode, *operation );
4038 }
4039 else if( item->Type() == PCB_PAD_T )
4040 {
4041 PAD* pad = static_cast<PAD*>( item );
4042 const DRILL_OPERATION* operation = m_fabDrillModel->Find( span, pad, true );
4043
4044 if( !operation )
4045 continue;
4046
4047 auto it = m_padstack_dict.find( ipcPadstackHash( pad ) );
4048
4049 if( it == m_padstack_dict.end() )
4050 {
4051 Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
4053 continue;
4054 }
4055
4056 wxXmlNode* padNode = appendNode( layerNode, "Set" );
4057 addAttribute( padNode, "geometry", it->second );
4058
4059 if( pad->GetNetCode() > 0 )
4060 addAttribute( padNode, "net", netName( pad->GetNetname() ) );
4061
4062 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
4063 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
4064 addAttribute( holeNode, "diameter", floatVal( m_scale * operation->m_Diameter ) );
4065 addAttribute( holeNode, "platingStatus",
4066 pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
4067 addAttribute( holeNode, "plusTol", "0.0" );
4068 addAttribute( holeNode, "minusTol", "0.0" );
4069 addXY( holeNode, pad->GetPosition() );
4070 addBackdrillSpecRefs( holeNode, it->second );
4071 addPostMachiningSpecRefs( holeNode, *operation );
4072 }
4073 }
4074 }
4075
4076 hole_count = 1;
4077
4078 for( const auto& [layers, vec] : m_slot_holes )
4079 {
4080 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
4081 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
4082
4083 for( PAD* pad : vec )
4084 {
4085 wxXmlNode* padNode = appendNode( layerNode, "Set" );
4086
4087 if( pad->GetNetCode() > 0 )
4088 addAttribute( padNode, "net", netName( pad->GetNetname() ) );
4089
4090 addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
4091
4092 // SlotCavity has no SpecRef, so the slot's own Set carries its post-machining
4093 DRILL_SPAN span( layers.first, layers.second, false, false );
4094
4095 if( const DRILL_OPERATION* operation = m_fabDrillModel->Find( span, pad, true ) )
4096 addPostMachiningSpecRefs( padNode, *operation );
4097 }
4098 }
4099}
4100
4101
4102void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
4103 const std::vector<BOARD_ITEM*>& aItems )
4104{
4105 auto it = aItems.begin();
4106 wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
4107 wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
4108 wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
4109
4110 bool has_via = false;
4111 bool has_pad = false;
4112
4113 wxXmlNode* padSetNode = nullptr;
4114
4115 wxXmlNode* viaSetNode = nullptr;
4116
4117 wxXmlNode* teardropLayerSetNode = nullptr;
4118 wxXmlNode* teardropFeatureSetNode = nullptr;
4119
4120 bool teardrop_warning = false;
4121
4122 if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
4123 IsCopperLayer( aLayer ) && item )
4124 {
4125 if( item->GetNetCode() > 0 )
4126 addAttribute( layerSetNode, "net", netName( item->GetNetname() ) );
4127 }
4128
4129 auto add_track =
4130 [&]( PCB_TRACK* track )
4131 {
4132 if( track->Type() == PCB_TRACE_T )
4133 {
4134 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4135 shape.SetStart( track->GetStart() );
4136 shape.SetEnd( track->GetEnd() );
4137 shape.SetWidth( FabTrackWidth( *track, aLayer ) );
4138 addShape( specialNode, shape );
4139 }
4140 else if( track->Type() == PCB_ARC_T )
4141 {
4142 const PCB_ARC* arc = static_cast<const PCB_ARC*>( track );
4143
4144 // Too small arcs cannot be really handled: arc center (and arc radius)
4145 // cannot be safely computed
4146 if( !arc->IsDegenerated( 10 /* in IU */ ) )
4147 {
4148 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
4149 shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
4150 shape.SetWidth( FabTrackWidth( *arc, aLayer ) );
4151 addShape( specialNode, shape );
4152 }
4153 else
4154 {
4155 // Approximate this very small arc by a segment.
4156 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
4157 shape.SetStart( track->GetStart() );
4158 shape.SetEnd( track->GetEnd() );
4159 shape.SetWidth( FabTrackWidth( *track, aLayer ) );
4160 addShape( specialNode, shape );
4161 }
4162 }
4163 else
4164 {
4165 if( !viaSetNode )
4166 {
4167 if( !has_pad )
4168 {
4169 viaSetNode = layerSetNode;
4170 has_via = true;
4171 }
4172 else
4173 {
4174 viaSetNode = appendNode( layerSetNode, "Set" );
4175
4176 if( track->GetNetCode() > 0 )
4177 addAttribute( viaSetNode, "net", netName( track->GetNetname() ) );
4178 }
4179
4180 addAttribute( viaSetNode, "padUsage", "VIA" );
4181 }
4182
4183 addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
4184 }
4185 };
4186
4187 auto add_zone =
4188 [&]( ZONE* zone )
4189 {
4190 wxXmlNode* zoneFeatureNode = specialNode;
4191
4192 if( zone->IsTeardropArea() )
4193 {
4195 {
4196 if( !teardropFeatureSetNode )
4197 {
4198 teardropLayerSetNode = appendNode( aLayerNode, "Set" );
4199 addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
4200
4201 if( zone->GetNetCode() > 0 )
4202 addAttribute( teardropLayerSetNode, "net", netName( zone->GetNetname() ) );
4203
4204 wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
4205 addLocationNode( new_teardrops, 0.0, 0.0 );
4206 teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
4207 }
4208
4209 zoneFeatureNode = teardropFeatureSetNode;
4210 }
4211 else if( !teardrop_warning )
4212 {
4213 Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
4215 teardrop_warning = true;
4216 }
4217 }
4218 else
4219 {
4220 if( FOOTPRINT* fp = zone->GetParentFootprint() )
4221 {
4222 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
4223 wxString refDes = componentName( fp );
4224
4225 if( componentRefEmitted() )
4226 addAttribute( tempSetNode, "componentRef", refDes );
4227
4228 wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
4229 addLocationNode( newFeatures, 0.0, 0.0 );
4230 zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
4231 }
4232 }
4233
4234 SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
4235
4236 for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
4237 addContourNode( zoneFeatureNode, zone_shape, ii );
4238 };
4239
4240 auto add_shape =
4241 [&] ( PCB_SHAPE* shape )
4242 {
4243 PCB_SHAPE adjusted( nullptr );
4244
4245 if( FabMaskShape( *shape, aLayer, adjusted ) )
4246 shape = &adjusted;
4247
4248 FOOTPRINT* fp = shape->GetParentFootprint();
4249
4250 if( fp )
4251 {
4252 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
4253
4255 addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
4256
4257 bool link_to_component = true;
4258
4260 link_to_component = false;
4261
4262 if( link_to_component )
4263 {
4264 if( componentRefEmitted() )
4265 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
4266 }
4267
4268 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
4269
4270 addLocationNode( tempFeature, *shape );
4271 addShape( tempFeature, *shape );
4272 }
4273 else if( shape->GetShape() == SHAPE_T::CIRCLE
4274 || shape->GetShape() == SHAPE_T::RECTANGLE
4275 || shape->GetShape() == SHAPE_T::POLY )
4276 {
4277 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
4278
4279 // The net only belongs on the copper feature; the mask copy of a netted
4280 // copper shape must not carry a net attribute.
4281 if( shape->GetNetCode() > 0 && IsCopperLayer( aLayer ) )
4282 addAttribute( tempSetNode, "net", netName( shape->GetNetname() ) );
4283
4284 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
4285 addLocationNode( tempFeature, *shape );
4286 addShape( tempFeature, *shape );
4287 }
4288 else
4289 {
4290 addShape( specialNode, *shape );
4291 }
4292 };
4293
4294 auto add_text =
4295 [&] ( BOARD_ITEM* text )
4296 {
4297 EDA_TEXT* text_item = nullptr;
4298 FOOTPRINT* fp = text->GetParentFootprint();
4299
4300 if( PCB_TEXT* pcb_text = dynamic_cast<PCB_TEXT*>( text ) )
4301 text_item = static_cast<EDA_TEXT*>( pcb_text );
4302 else if( PCB_TEXTBOX* pcb_textbox = dynamic_cast<PCB_TEXTBOX*>( text ) )
4303 text_item = static_cast<EDA_TEXT*>( pcb_textbox );
4304
4305 if( !text_item || !text_item->IsVisible() )
4306 return;
4307
4308 wxString content = text_item->GetShownText( RESOLVED );
4309
4310 if( content.empty() )
4311 return;
4312
4313 bool isWhitespace = content.Strip( wxString::both ).empty();
4314 bool isKnockout = text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout();
4315 bool hasBorder = text->Type() == PCB_TEXTBOX_T && static_cast<PCB_TEXTBOX*>( text )->IsBorderEnabled();
4316
4317 if( isWhitespace && !isKnockout && !hasBorder )
4318 return;
4319
4320 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
4321
4323 addAttribute( tempSetNode, "geometryUsage", "TEXT" );
4324
4325 bool link_to_component = fp != nullptr;
4326
4327 if( m_revision == IPC2581::REVISION::B && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
4328 link_to_component = false;
4329
4330 if( link_to_component )
4331 {
4332 if( componentRefEmitted() )
4333 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
4334 }
4335
4336 wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
4337 addAttribute( nonStandardAttributeNode, "name", "TEXT" );
4338 addAttribute( nonStandardAttributeNode, "value", content );
4339 addAttribute( nonStandardAttributeNode, "type", "STRING" );
4340
4341 if( !isWhitespace || isKnockout )
4342 {
4343 wxXmlNode* glyphFeature = appendNode( tempSetNode, "Features" );
4344 addLocationNode( glyphFeature, 0.0, 0.0 );
4345
4346 if( isKnockout )
4347 addKnockoutText( glyphFeature, static_cast<PCB_TEXT*>( text ) );
4348 else
4349 addText( glyphFeature, text_item, text->GetFontMetrics() );
4350 }
4351
4352 if( hasBorder )
4353 {
4354 PCB_SHAPE* border = static_cast<PCB_TEXTBOX*>( text );
4355 wxXmlNode* borderFeature = appendNode( tempSetNode, "Features" );
4356
4357 addLocationNode( borderFeature, *border );
4358 addShape( borderFeature, *border );
4359 }
4360 };
4361
4362 auto add_pad =
4363 [&]( PAD* pad )
4364 {
4365 if( !padSetNode )
4366 {
4367 if( !has_via )
4368 {
4369 padSetNode = layerSetNode;
4370 has_pad = true;
4371 }
4372 else
4373 {
4374 padSetNode = appendNode( aLayerNode, "Set" );
4375
4376 if( pad->GetNetCode() > 0 )
4377 addAttribute( padSetNode, "net", netName( pad->GetNetname() ) );
4378 }
4379 }
4380
4381 addPad( padSetNode, pad, aLayer );
4382 };
4383
4384 for( BOARD_ITEM* item : aItems )
4385 {
4386 switch( item->Type() )
4387 {
4388 case PCB_TRACE_T:
4389 case PCB_ARC_T:
4390 case PCB_VIA_T:
4391 add_track( static_cast<PCB_TRACK*>( item ) );
4392 break;
4393
4394 case PCB_ZONE_T:
4395 add_zone( static_cast<ZONE*>( item ) );
4396 break;
4397
4398 case PCB_PAD_T:
4399 add_pad( static_cast<PAD*>( item ) );
4400 break;
4401
4402 case PCB_SHAPE_T:
4403 add_shape( static_cast<PCB_SHAPE*>( item ) );
4404 break;
4405
4406 case PCB_TEXT_T:
4407 case PCB_TEXTBOX_T:
4408 case PCB_FIELD_T:
4409 add_text( item );
4410 break;
4411
4412 case PCB_DIMENSION_T:
4413 case PCB_TARGET_T:
4414 case PCB_DIM_ALIGNED_T:
4415 case PCB_DIM_LEADER_T:
4416 case PCB_DIM_CENTER_T:
4417 case PCB_DIM_RADIAL_T:
4419 //TODO: Add support for dimensions
4420 break;
4421
4422 default:
4423 wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
4424 ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
4425 }
4426 }
4427
4428 if( specialNode->GetChildren() == nullptr )
4429 {
4430 featureSetNode->RemoveChild( specialNode );
4431 deleteNode( specialNode );
4432 }
4433
4434 if( featureSetNode->GetChildren() == nullptr )
4435 {
4436 layerSetNode->RemoveChild( featureSetNode );
4437 deleteNode( featureSetNode );
4438 }
4439
4440 if( layerSetNode->GetChildren() == nullptr )
4441 {
4442 aLayerNode->RemoveChild( layerSetNode );
4443 deleteNode( layerSetNode );
4444 }
4445}
4446
4448{
4449 if( !included( IPC2581::SECTION::MISC_FAB ) )
4450 return;
4451
4452 for( const auto& [layers, vec] : m_auxilliary_Layers )
4453 {
4454 bool add_node = true;
4455
4456 wxString name;
4457 bool hole = false;
4458
4459 // clang-format off: suggestion is inconsitent
4460 switch( std::get<0>(layers) )
4461 {
4463 name = "COVERING";
4464 break;
4466 name = "PLUGGING";
4467 hole = true;
4468 break;
4470 name = "TENTING";
4471 break;
4473 name = "FILLING";
4474 hole = true;
4475 break;
4477 name = "CAPPING";
4478 hole = true;
4479 break;
4480 default:
4481 add_node = false;
4482 break;
4483 }
4484 // clang-format on: suggestion is inconsitent
4485
4486 if( !add_node )
4487 continue;
4488
4489 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
4490
4491 if( std::get<2>( layers ) == UNDEFINED_LAYER )
4492 {
4493 layerNode->AddAttribute( "layerRef", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
4494 }
4495 else
4496 {
4497 layerNode->AddAttribute( "layerRef", genLayersString( std::get<1>( layers ),
4498 std::get<2>( layers ), TO_UTF8( name ) ) );
4499 }
4500
4501 wxXmlNode* setNode = appendNode( layerNode, "Set" );
4502
4503 for( BOARD_ITEM* item : vec )
4504 {
4505 if( item->Type() != PCB_VIA_T )
4506 continue;
4507
4508 PCB_VIA* via = static_cast<PCB_VIA*>( item );
4509
4510 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
4511
4512 if( hole )
4513 shape.SetEnd( { KiROUND( via->GetDrillValue() / 2.0 ), 0 } );
4514 else
4515 shape.SetEnd( { KiROUND( via->GetWidth( std::get<1>( layers ) ) / 2.0 ), 0 } );
4516
4517 wxXmlNode* padNode = appendNode( setNode, "Pad" );
4518 addPadStack( padNode, via );
4519
4520 addLocationNode( padNode, via->GetPosition().x, via->GetPosition().y );
4521 addShape( padNode, shape );
4522 }
4523 }
4524}
4525
4526
4528{
4529 if( !included( IPC2581::SECTION::BOM_AVL ) )
4530 return nullptr;
4531
4532 reportPhase( _( "Generating vendor list" ) );
4533
4534 // Per IPC-2581 schema, Avl requires at least one AvlItem child element.
4535 // Don't emit Avl section if there are no items.
4536 if( m_OEMRef_dict.empty() )
4537 return nullptr;
4538
4539 wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
4540 addAttribute( avl, "name", "Primary_Vendor_List" );
4541
4542 wxXmlNode* header = appendNode( avl, "AvlHeader" );
4543 addAttribute( header, "title", "BOM" );
4544 addAttribute( header, "source", "KiCad" );
4545 addAttribute( header, "author", "OWNER" );
4546 addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
4547 addAttribute( header, "version", "1" );
4548
4549 std::set<wxString> unique_parts;
4550 std::map<wxString,wxString> unique_vendors;
4551 std::vector<FOOTPRINT*> footprints;
4552 std::vector<AVL_ROW> rows;
4553
4554 for( FOOTPRINT* fp : m_board->Footprints() )
4555 {
4556 auto it = m_OEMRef_dict.find( fp );
4557
4558 if( it == m_OEMRef_dict.end() )
4559 continue;
4560
4561 footprints.push_back( fp );
4562 rows.push_back( { it->second, fp->GetReference() } );
4563 }
4564
4565 for( size_t index : OrderAvlRows( rows ) )
4566 {
4567 FOOTPRINT* fp = footprints[index];
4568 const wxString& name = rows[index].m_oemName;
4569 auto [ it, success ] = unique_parts.insert( name );
4570
4571 if( !success )
4572 continue;
4573
4574 wxXmlNode* part = appendNode( avl, "AvlItem" );
4575 addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
4576
4577 PCB_FIELD* nums[2] = { fp->GetField( m_mpn ), fp->GetField( m_distpn ) };
4578 PCB_FIELD* company[2] = { fp->GetField( m_mfg ), nullptr };
4579 wxString company_name[2] = { m_mfg, m_dist };
4580
4581 for ( int ii = 0; ii < 2; ++ii )
4582 {
4583 if( nums[ii] )
4584 {
4585 wxString mpn_name = nums[ii]->GetShownText( RESOLVED );
4586
4587 if( mpn_name.empty() )
4588 continue;
4589
4590 wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
4591 addAttribute( vmpn, "qualified", "false" );
4592 addAttribute( vmpn, "chosen", "false" );
4593
4594 wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
4595 addAttribute( mpn, "name", mpn_name );
4596
4597 wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
4598
4599 wxString vendor_name = wxT( "UNKNOWN" );
4600
4601 // If the field resolves, then use that field content unless it is empty
4602 if( !ii && company[ii] )
4603 {
4604 wxString tmp = company[ii]->GetShownText( RESOLVED );
4605
4606 if( !tmp.empty() )
4607 vendor_name = tmp;
4608 }
4609 // If it doesn't resolve but there is content from the dialog, use the static content
4610 else if( !ii && !company_name[ii].empty() )
4611 {
4612 vendor_name = company_name[ii];
4613 }
4614 else if( ii && !m_dist.empty() )
4615 {
4616 vendor_name = m_dist;
4617 }
4618
4619 auto [vendor_id, inserted] = unique_vendors.emplace(
4620 vendor_name,
4621 wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
4622
4623 addAttribute( vendor, "enterpriseRef", vendor_id->second );
4624
4625 if( inserted )
4626 {
4627 wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
4628 addAttribute( new_vendor, "id", vendor_id->second );
4629 addAttribute( new_vendor, "name", vendor_name );
4630 addAttribute( new_vendor, "code", "NONE" );
4631 insertNodeAfter( m_enterpriseNode, new_vendor );
4632 m_enterpriseNode = new_vendor;
4633 }
4634 }
4635 }
4636 }
4637
4638 return avl;
4639}
4640
4641
4642void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD& aBoard,
4643 const std::map<std::string, UTF8>* aProperties )
4644{
4645 // Clean up any previous export state to allow multiple exports per plugin instance
4646 delete m_xml_doc;
4647 m_xml_doc = nullptr;
4648 m_xml_root = nullptr;
4649 m_contentNode = nullptr;
4650 m_lastAppendedNode = nullptr;
4651
4652 m_board = &aBoard;
4653 FAB_STACKUP fabStackup( aBoard );
4654 SCOPED_SET_RESET<const FAB_STACKUP*> fabStackupScope( m_fabStackup, &fabStackup );
4655 FAB_DRILL_MODEL fabDrillModel( aBoard );
4656 SCOPED_SET_RESET<const FAB_DRILL_MODEL*> fabDrillScope( m_fabDrillModel, &fabDrillModel );
4659 m_backdrill_spec_nodes.clear();
4660 m_backdrill_spec_used.clear();
4662 m_post_machining_specs.clear();
4663 m_cad_header_node = nullptr;
4664 m_layer_name_map.clear();
4665
4666 // Clear all internal dictionaries and caches
4667 m_user_shape_dict.clear();
4668 m_shape_user_node = nullptr;
4669 m_std_shape_dict.clear();
4670 m_shape_std_node = nullptr;
4671 m_line_dict.clear();
4672 m_line_node = nullptr;
4673 m_padstack_dict.clear();
4674 m_padstacks.clear();
4675 m_last_padstack = nullptr;
4676 m_footprint_dict.clear();
4679 m_OEMRef_dict.clear();
4680 m_net_pin_dict.clear();
4681 m_net_name_dict.clear();
4682 m_anon_refdes_dict.clear();
4683 m_drill_layers.clear();
4684 m_slot_holes.clear();
4685 m_auxilliary_Layers.clear();
4686 m_element_names.clear();
4687 m_generated_names.clear();
4688 m_acceptable_chars.clear();
4689 m_total_bytes = 0;
4690
4691 m_anonymizeNets = false;
4692 m_omitRefDes = false;
4693 m_haveCadData = true;
4694 m_step_node = nullptr;
4695 m_functionModeNode = nullptr;
4696
4697 m_units_str = "MILLIMETER";
4698 m_scale = 1.0 / PCB_IU_PER_MM;
4699 m_sigfig = 6;
4700
4701 // The base PCB_IO interface permits a null property set; alias it to an empty
4702 // map so the optional lookups below remain valid.
4703 const std::map<std::string, UTF8> emptyProperties;
4704
4705 if( !aProperties )
4706 aProperties = &emptyProperties;
4707
4708 if( auto it = aProperties->find( "variant" ); it != aProperties->end() )
4709 m_variantName = it->second.wx_str();
4710
4711 if( auto it = aProperties->find( "units" ); it != aProperties->end() )
4712 {
4713 if( it->second == "inch" )
4714 {
4715 m_units_str = "INCH";
4716 m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
4717 }
4718 }
4719
4720 if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
4721 m_sigfig = std::stoi( it->second );
4722
4723 if( auto it = aProperties->find( "version" ); it != aProperties->end() )
4725
4726 if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
4727 m_OEMRef = it->second.wx_str();
4728
4729 if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
4730 m_mpn = it->second.wx_str();
4731
4732 if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
4733 m_mfg = it->second.wx_str();
4734
4735 if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
4736 m_dist = it->second.wx_str();
4737
4738 if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
4739 m_distpn = it->second.wx_str();
4740
4741 if( auto it = aProperties->find( "bomrev" ); it != aProperties->end() )
4742 m_bomRev = it->second.wx_str();
4743
4744 m_mode = IPC2581::MODE::USERDEF;
4746
4747 if( auto it = aProperties->find( "mode" ); it != aProperties->end() )
4748 {
4749 std::optional<IPC2581::MODE> mode = IPC2581::ModeFromToken( it->second.wx_str() );
4750
4751 if( !mode )
4752 THROW_IO_ERROR( wxString::Format( _( "Unknown IPC-2581 data set '%s'." ),
4753 it->second.wx_str() ) );
4754
4755 m_mode = *mode;
4757 }
4758
4759 if( auto it = aProperties->find( "sections" ); it != aProperties->end() )
4760 {
4761 if( !IPC2581::SectionSetFromKeyString( it->second.wx_str(), m_requestedSections ) )
4762 {
4763 THROW_IO_ERROR( wxString::Format( _( "Invalid IPC-2581 section key '%s'." ),
4764 it->second.wx_str() ) );
4765 }
4766 }
4767
4768 if( auto it = aProperties->find( "netnames" ); it != aProperties->end() )
4769 m_anonymizeNets = it->second == "anonymize";
4770
4771 if( m_anonymizeNets )
4772 {
4773 for( const auto& [code, name] : FabAnonymousNetNames( *m_board ) )
4774 {
4775 if( NETINFO_ITEM* net = m_board->GetNetInfo().GetNetItem( code ) )
4776 m_net_name_dict.emplace( net->GetNetname(), name );
4777 }
4778 }
4779
4780 if( auto it = aProperties->find( "refdes" ); it != aProperties->end() )
4781 m_omitRefDes = it->second == "omit";
4782
4784
4785 if( !m_resolved.Ok() )
4786 THROW_IO_ERROR( m_resolved.m_conflicts.front().m_message );
4787
4789 {
4790 for( char c = 'a'; c <= 'z'; ++c )
4791 m_acceptable_chars.insert( c );
4792
4793 for( char c = 'A'; c <= 'Z'; ++c )
4794 m_acceptable_chars.insert( c );
4795
4796 for( char c = '0'; c <= '9'; ++c )
4797 m_acceptable_chars.insert( c );
4798
4799 // Add special characters
4800 std::string specialChars = "_\\-.+><";
4801
4802 for( char c : specialChars )
4803 m_acceptable_chars.insert( c );
4804 }
4805
4806 m_xml_doc = new wxXmlDocument();
4808
4809 if( m_progressReporter )
4810 {
4811 int phases = EXPORT_PHASES;
4812
4813 if( !included( IPC2581::SECTION::LOGICAL_NET ) )
4814 --phases;
4815
4816 if( !included( IPC2581::SECTION::PHYSICAL_NET ) )
4817 --phases;
4818
4819 if( !included( IPC2581::SECTION::BOM_AVL ) )
4820 phases -= 2;
4821
4822 m_progressReporter->SetNumPhases( phases );
4823 m_progressReporter->BeginPhase( 0 );
4824 }
4825
4827
4828 reportPhase( _( "Generating logistic section" ) );
4831
4832 wxXmlNode* ecad_node = generateEcadSection();
4833 wxXmlNode* bom_node = generateBOMSection( ecad_node );
4834 wxXmlNode* avl_node = generateAvlSection();
4835
4836 // Insert BomRef/AvlRef into Content section per IPC-2581C 4.1.1.2.
4837 // They go after LayerRef and before Dictionary* nodes.
4838 if( m_contentNode && ( bom_node || avl_node ) )
4839 {
4840 wxXmlNode* insertBefore = nullptr;
4841
4842 for( wxXmlNode* child = m_contentNode->GetChildren(); child; child = child->GetNext() )
4843 {
4844 if( child->GetName().StartsWith( "Dictionary" ) )
4845 {
4846 insertBefore = child;
4847 break;
4848 }
4849 }
4850
4851 auto insertRef =
4852 [&]( const wxString& aNodeName, wxXmlNode* aSection )
4853 {
4854 if( !aSection )
4855 return;
4856
4857 wxXmlNode* ref = new wxXmlNode( wxXML_ELEMENT_NODE, aNodeName );
4858 ref->AddAttribute( "name", aSection->GetAttribute( "name" ) );
4859
4860 if( insertBefore )
4861 m_contentNode->InsertChild( ref, insertBefore );
4862 else
4863 m_contentNode->AddChild( ref );
4864 };
4865
4866 insertRef( "BomRef", bom_node );
4867 insertRef( "AvlRef", avl_node );
4868 }
4869
4870 reportPhase( _( "Resolving references" ) );
4871
4872 if( m_functionModeNode )
4873 {
4874 addAttribute( m_functionModeNode, "sectionKey",
4876 }
4877
4878 reportPhase( _( "Saving file" ) );
4879
4880 wxFileOutputStreamWithProgress out_stream( aFileName );
4881 double written_bytes = 0.0;
4882 double last_yield = 0.0;
4883
4884 // This is a rough estimation of the size of the spaces in the file
4885 // We just need to total to be slightly larger than the value of the
4886 // progress bar, so accurately counting spaces is not terribly important
4888
4889 auto update_progress = [&]( size_t aBytes )
4890 {
4891 written_bytes += aBytes;
4892 double percent = written_bytes / static_cast<double>( m_total_bytes );
4893
4894 if( m_progressReporter )
4895 {
4896 // Only update every percent
4897 if( last_yield + 0.01 < percent )
4898 {
4899 last_yield = percent;
4900 m_progressReporter->SetCurrentProgress( percent );
4901 m_progressReporter->KeepRefreshing();
4902 }
4903 }
4904 };
4905
4906 out_stream.SetProgressCallback( update_progress );
4907
4908
4909 // wxXmlDocument writes a small number of bytes at a time
4910 // An unbuffered stream then makes millions of small writes
4911 wxBufferedOutputStream buffered_stream( out_stream, 1 << 20 );
4912
4913 if( !m_xml_doc->Save( buffered_stream ) )
4914 THROW_IO_ERROR( _( "Failed to save IPC-2581 data to buffer." ) );
4915
4916 buffered_stream.Sync();
4917
4918 // A large document needs seconds to release
4919 // The destructor releases it after the progress reporter stops and the dialog stops
4920 reportPhase( _( "Finishing" ) );
4921 delete m_xml_doc;
4922 m_xml_doc = nullptr;
4923 m_xml_root = nullptr;
4924 m_contentNode = nullptr;
4925 m_lastAppendedNode = nullptr;
4926 m_functionModeNode = nullptr;
4927 m_step_node = nullptr;
4928 m_last_padstack = nullptr;
4929 m_padstacks.clear();
4930
4931}
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
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:84
FOOTPRINT * GetParentFootprint() const
VECTOR2I GetFPRelativePosition() const
int GetMaxError() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
double GetEpsilonR(int aDielectricSubLayer=0) const
wxString GetColor(int aDielectricSubLayer=0) const
wxString GetLayerName() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
wxString GetMaterial(int aDielectricSubLayer=0) const
int GetDielectricLayerId() const
double GetLossTangent(int aDielectricSubLayer=0) const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int GetCount() const
int BuildBoardThicknessFromStackup() const
int GetLayerDistance(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Calculate the distance (height) between the two given copper layers.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
wxString GetCurrentVariant() const
Definition board.h:529
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:395
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:368
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:423
FILL_T GetFillMode() const
Definition eda_shape.h:148
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:404
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
std::vector< VECTOR2I > GetRectCorners() const
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:414
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:365
int GetRectangleHeight() const
bool IsClockwiseArc() const
int GetCornerRadius() const
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
virtual 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:783
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:405
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:450
bool IsFlipped() const
Definition footprint.h:662
const wxString & GetReference() const
Definition footprint.h:909
wxString m_name
Name of the IO loader.
Definition io_base.h:235
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) const
Definition io_base.cpp:124
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:241
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:94
void Draw(KIGFX::GAL *aGal, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aCursor, const TEXT_ATTRIBUTES &aAttributes, const METRICS &aFontMetrics, std::optional< VECTOR2I > aMousePos=std::nullopt, wxString *aActiveUrl=nullptr) const
Draw a string.
Definition font.cpp:240
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h: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:365
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:362
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:359
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:368
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:687
int GetDrillSizeY() const
Definition pad.h:324
PAD_ATTRIB GetAttribute() const
Definition pad.h:560
const wxString & GetNumber() const
Definition pad.h:143
VECTOR2I GetPosition() const override
Definition pad.cpp:256
VECTOR2I GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.cpp:838
VECTOR2I GetDrillSize() const
Definition pad.h:320
int GetDrillSizeX() const
Definition pad.h:322
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:205
int GetSolderMaskExpansion(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1998
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:298
const PADSTACK & Padstack() const
Definition pad.h:331
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1757
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:2061
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:3013
bool IsDegenerated(int aThreshold=5) const
const VECTOR2I & GetMid() const
Definition pcb_track.h:295
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:78
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:76
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:599
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:427
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:91
@ FOR_CANVAS
Definition common.h:90
@ RESOLVED
Definition common.h:95
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:785
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:806
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
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:681
@ 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