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