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