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