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 // Generate SurfaceFinish spec from board's copper finish setting
1769 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1770
1771 if( finishType != surfaceFinishType::NONE )
1772 {
1773 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1774 addAttribute( specNode, "name", "SURFACE_FINISH" );
1775
1776 wxXmlNode* surfaceFinishNode = appendNode( specNode, "SurfaceFinish" );
1777 wxXmlNode* finishNode = appendNode( surfaceFinishNode, "Finish" );
1778 addAttribute( finishNode, "type", surfaceFinishTypeToString.at( finishType ) );
1779
1780 // Add original finish string as comment if it maps to OTHER
1781 if( finishType == surfaceFinishType::OTHER )
1782 addAttribute( finishNode, "comment", stackup.m_FinishType );
1783 }
1784}
1785
1786
1787void PCB_IO_IPC2581::addCadHeader( wxXmlNode* aEcadNode )
1788{
1789 wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" );
1790 addAttribute( cadHeaderNode, "units", m_units_str );
1791
1792 m_cad_header_node = cadHeaderNode;
1793
1794 generateCadSpecs( cadHeaderNode );
1795}
1796
1797
1799{
1800 return ( aLayer >= F_Cu && aLayer <= User_9 ) || aLayer == UNDEFINED_LAYER;
1801}
1802
1803
1804void PCB_IO_IPC2581::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer )
1805{
1806 switch( aLayer )
1807 {
1808 case F_Adhes:
1809 case B_Adhes:
1810 addAttribute( aNode, "layerFunction", "GLUE" );
1811 addAttribute( aNode, "polarity", "POSITIVE" );
1812 addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" );
1813 break;
1814 case F_Paste:
1815 case B_Paste:
1816 addAttribute( aNode, "layerFunction", "SOLDERPASTE" );
1817 addAttribute( aNode, "polarity", "POSITIVE" );
1818 addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" );
1819 break;
1820 case F_SilkS:
1821 case B_SilkS:
1822 addAttribute( aNode, "layerFunction", "SILKSCREEN" );
1823 addAttribute( aNode, "polarity", "POSITIVE" );
1824 addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" );
1825 break;
1826 case F_Mask:
1827 case B_Mask:
1828 addAttribute( aNode, "layerFunction", "SOLDERMASK" );
1829 addAttribute( aNode, "polarity", "POSITIVE" );
1830 addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" );
1831 break;
1832 case Edge_Cuts:
1833 addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" );
1834 addAttribute( aNode, "polarity", "POSITIVE" );
1835 addAttribute( aNode, "side", "ALL" );
1836 break;
1837 case B_CrtYd:
1838 case F_CrtYd:
1839 addAttribute( aNode, "layerFunction", "COURTYARD" );
1840 addAttribute( aNode, "polarity", "POSITIVE" );
1841 addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" );
1842 break;
1843 case B_Fab:
1844 case F_Fab:
1845 addAttribute( aNode, "layerFunction", "ASSEMBLY" );
1846 addAttribute( aNode, "polarity", "POSITIVE" );
1847 addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" );
1848 break;
1849 case Dwgs_User:
1850 case Cmts_User:
1851 case Eco1_User:
1852 case Eco2_User:
1853 case Margin:
1854 case User_1:
1855 case User_2:
1856 case User_3:
1857 case User_4:
1858 case User_5:
1859 case User_6:
1860 case User_7:
1861 case User_8:
1862 case User_9:
1863 addAttribute( aNode, "layerFunction", "DOCUMENT" );
1864 addAttribute( aNode, "polarity", "POSITIVE" );
1865 addAttribute( aNode, "side", "NONE" );
1866 break;
1867
1868 default:
1869 if( IsCopperLayer( aLayer ) )
1870 {
1871 addAttribute( aNode, "layerFunction", "CONDUCTOR" );
1872 addAttribute( aNode, "polarity", "POSITIVE" );
1873 addAttribute( aNode, "side",
1874 aLayer == F_Cu ? "TOP"
1875 : aLayer == B_Cu ? "BOTTOM"
1876 : "INTERNAL" );
1877 }
1878
1879 break; // Do not handle other layers
1880 }
1881}
1882
1883
1884void PCB_IO_IPC2581::generateStackup( wxXmlNode* aCadLayerNode )
1885{
1886 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1887 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1888 stackup.SynchronizeWithBoard( &dsnSettings );
1889
1890 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
1891 bool hasCoating = ( finishType != surfaceFinishType::NONE );
1892
1893 wxXmlNode* stackupNode = appendNode( aCadLayerNode, "Stackup" );
1894 addAttribute( stackupNode, "name", "Primary_Stackup" );
1895 addAttribute( stackupNode, "overallThickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1896 addAttribute( stackupNode, "tolPlus", "0.0" );
1897 addAttribute( stackupNode, "tolMinus", "0.0" );
1898 addAttribute( stackupNode, "whereMeasured", "MASK" );
1899
1900 if( m_version > 'B' )
1901 addAttribute( stackupNode, "stackupStatus", "PROPOSED" );
1902
1903 wxXmlNode* stackupGroup = appendNode( stackupNode, "StackupGroup" );
1904 addAttribute( stackupGroup, "name", "Primary_Stackup_Group" );
1905 addAttribute( stackupGroup, "thickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1906 addAttribute( stackupGroup, "tolPlus", "0.0" );
1907 addAttribute( stackupGroup, "tolMinus", "0.0" );
1908
1909 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1910 std::set<PCB_LAYER_ID> added_layers;
1911 int sequence = 0;
1912
1913 for( int i = 0; i < stackup.GetCount(); i++ )
1914 {
1915 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1916
1917 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1918 {
1919 PCB_LAYER_ID layer_id = stackup_item->GetBrdLayerId();
1920
1921 // Insert top coating layer before F.Cu
1922 if( hasCoating && layer_id == F_Cu && sublayer_id == 0 )
1923 {
1924 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
1925 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_TOP" );
1926 addAttribute( coatingLayer, "thickness", "0.0" );
1927 addAttribute( coatingLayer, "tolPlus", "0.0" );
1928 addAttribute( coatingLayer, "tolMinus", "0.0" );
1929 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1930
1931 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
1932 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
1933 }
1934
1935 wxXmlNode* stackupLayer = appendNode( stackupGroup, "StackupLayer" );
1936 wxString ly_name = stackup_item->GetLayerName();
1937
1938 if( ly_name.IsEmpty() )
1939 {
1940 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1941 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1942
1943 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1944 {
1945 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1946
1947 if( sublayer_id > 0 )
1948 ly_name += wxString::Format( "_%d", sublayer_id );
1949 }
1950 }
1951
1952 wxString spec_name = genString( ly_name, "SPEC_LAYER" );
1953 ly_name = genString( ly_name, "LAYER" );
1954
1955 addAttribute( stackupLayer, "layerOrGroupRef", ly_name );
1956 addAttribute( stackupLayer, "thickness", floatVal( m_scale * stackup_item->GetThickness() ) );
1957 addAttribute( stackupLayer, "tolPlus", "0.0" );
1958 addAttribute( stackupLayer, "tolMinus", "0.0" );
1959 addAttribute( stackupLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1960
1961 wxXmlNode* specLayerNode = appendNode( stackupLayer, "SpecRef" );
1962 addAttribute( specLayerNode, "id", spec_name );
1963
1964 // Insert bottom coating layer after B.Cu
1965 if( hasCoating && layer_id == B_Cu && sublayer_id == stackup_item->GetSublayersCount() - 1 )
1966 {
1967 wxXmlNode* coatingLayer = appendNode( stackupGroup, "StackupLayer" );
1968 addAttribute( coatingLayer, "layerOrGroupRef", "COATING_BOTTOM" );
1969 addAttribute( coatingLayer, "thickness", "0.0" );
1970 addAttribute( coatingLayer, "tolPlus", "0.0" );
1971 addAttribute( coatingLayer, "tolMinus", "0.0" );
1972 addAttribute( coatingLayer, "sequence", wxString::Format( "%d", sequence++ ) );
1973
1974 wxXmlNode* specRefNode = appendNode( coatingLayer, "SpecRef" );
1975 addAttribute( specRefNode, "id", "SURFACE_FINISH" );
1976 }
1977 }
1978 }
1979}
1980
1981
1982void PCB_IO_IPC2581::generateCadLayers( wxXmlNode* aCadLayerNode )
1983{
1984
1985 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1986 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1987 stackup.SynchronizeWithBoard( &dsnSettings );
1988
1989 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1990 std::set<PCB_LAYER_ID> added_layers;
1991
1992 for( int i = 0; i < stackup.GetCount(); i++ )
1993 {
1994 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1995
1996 if( !isValidLayerFor2581( stackup_item->GetBrdLayerId() ) )
1997 continue;
1998
1999 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
2000 {
2001 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
2002 wxString ly_name = stackup_item->GetLayerName();
2003
2004 if( ly_name.IsEmpty() )
2005 {
2006
2007 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
2008 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
2009
2010 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2011 {
2012 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
2013
2014 if( sublayer_id > 0 )
2015 ly_name += wxString::Format( "_%d", sublayer_id );
2016 }
2017 }
2018
2019 ly_name = genString( ly_name, "LAYER" );
2020
2021 addAttribute( cadLayerNode, "name", ly_name );
2022
2023 if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
2024 {
2025 if( stackup_item->GetTypeName() == KEY_CORE )
2026 addAttribute( cadLayerNode, "layerFunction", "DIELCORE" );
2027 else
2028 addAttribute( cadLayerNode, "layerFunction", "DIELPREG" );
2029
2030 addAttribute( cadLayerNode, "polarity", "POSITIVE" );
2031 addAttribute( cadLayerNode, "side", "INTERNAL" );
2032 continue;
2033 }
2034 else
2035 {
2036 added_layers.insert( stackup_item->GetBrdLayerId() );
2037 addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() );
2038 m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name );
2039 }
2040 }
2041 }
2042
2043 LSEQ layer_seq = m_board->GetEnabledLayers().Seq();
2044
2045 for( PCB_LAYER_ID layer : layer_seq )
2046 {
2047 if( added_layers.find( layer ) != added_layers.end() || !isValidLayerFor2581( layer ) )
2048 continue;
2049
2050 wxString ly_name = genLayerString( layer, "LAYER" );
2051 m_layer_name_map.emplace( layer, ly_name );
2052 added_layers.insert( layer );
2053 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
2054 addAttribute( cadLayerNode, "name", ly_name );
2055
2056 addLayerAttributes( cadLayerNode, layer );
2057 }
2058
2059 // Generate COATINGCOND layers for surface finish if specified
2060 surfaceFinishType finishType = getSurfaceFinishType( stackup.m_FinishType );
2061
2062 if( finishType != surfaceFinishType::NONE )
2063 {
2064 wxXmlNode* topCoatingNode = appendNode( aCadLayerNode, "Layer" );
2065 addAttribute( topCoatingNode, "name", "COATING_TOP" );
2066 addAttribute( topCoatingNode, "layerFunction", "COATINGCOND" );
2067 addAttribute( topCoatingNode, "side", "TOP" );
2068 addAttribute( topCoatingNode, "polarity", "POSITIVE" );
2069
2070 wxXmlNode* botCoatingNode = appendNode( aCadLayerNode, "Layer" );
2071 addAttribute( botCoatingNode, "name", "COATING_BOTTOM" );
2072 addAttribute( botCoatingNode, "layerFunction", "COATINGCOND" );
2073 addAttribute( botCoatingNode, "side", "BOTTOM" );
2074 addAttribute( botCoatingNode, "polarity", "POSITIVE" );
2075 }
2076}
2077
2078
2079void PCB_IO_IPC2581::generateDrillLayers( wxXmlNode* aCadLayerNode )
2080{
2081 for( BOARD_ITEM* item : m_board->Tracks() )
2082 {
2083 if( item->Type() == PCB_VIA_T )
2084 {
2085 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2086 m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via );
2087 }
2088 }
2089
2090 for( FOOTPRINT* fp : m_board->Footprints() )
2091 {
2092 for( PAD* pad : fp->Pads() )
2093 {
2094 if( pad->HasDrilledHole() )
2095 m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2096 else if( pad->HasHole() )
2097 m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad );
2098 }
2099 }
2100
2101 for( const auto& [layers, vec] : m_drill_layers )
2102 {
2103 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2104 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "DRILL" ) );
2105 addAttribute( drillNode, "layerFunction", "DRILL" );
2106 addAttribute( drillNode, "polarity", "POSITIVE" );
2107 addAttribute( drillNode, "side", "ALL" );
2108
2109 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2110 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2111 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2112 }
2113
2114 for( const auto& [layers, vec] : m_slot_holes )
2115 {
2116 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
2117 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "SLOT" ) );
2118
2119 addAttribute( drillNode, "layerFunction", "ROUT" );
2120 addAttribute( drillNode, "polarity", "POSITIVE" );
2121 addAttribute( drillNode, "side", "ALL" );
2122
2123 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
2124 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
2125 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
2126 }
2127}
2128
2129
2130void PCB_IO_IPC2581::generateAuxilliaryLayers( wxXmlNode* aCadLayerNode )
2131{
2132 for( BOARD_ITEM* item : m_board->Tracks() )
2133 {
2134 if( item->Type() != PCB_VIA_T )
2135 continue;
2136
2137 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2138
2139 std::vector<std::tuple<auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID>> new_layers;
2140
2141 if( via->Padstack().IsFilled().value_or( false ) )
2142 new_layers.emplace_back( auxLayerType::FILLING, via->TopLayer(), via->BottomLayer() );
2143
2144 if( via->Padstack().IsCapped().value_or( false ) )
2145 new_layers.emplace_back( auxLayerType::CAPPING, via->TopLayer(), via->BottomLayer() );
2146
2147 for( PCB_LAYER_ID layer : { via->TopLayer(), via->BottomLayer() } )
2148 {
2149 if( via->Padstack().IsPlugged( layer ).value_or( false ) )
2150 new_layers.emplace_back( auxLayerType::PLUGGING, layer, UNDEFINED_LAYER );
2151
2152 if( via->Padstack().IsCovered( layer ).value_or( false ) )
2153 new_layers.emplace_back( auxLayerType::COVERING, layer, UNDEFINED_LAYER );
2154
2155 if( via->Padstack().IsTented( layer ).value_or( false ) )
2156 new_layers.emplace_back( auxLayerType::TENTING, layer, UNDEFINED_LAYER );
2157 }
2158
2159 for( auto& tuple : new_layers )
2160 m_auxilliary_Layers[tuple].push_back( via );
2161 }
2162
2163 for( const auto& [layers, vec] : m_auxilliary_Layers )
2164 {
2165 bool add_node = true;
2166
2167 wxString name;
2168 wxString layerFunction;
2169
2170 // clang-format off: suggestion is inconsitent
2171 switch( std::get<0>(layers) )
2172 {
2174 name = "COVERING";
2175 layerFunction = "COATINGNONCOND";
2176 break;
2178 name = "PLUGGING";
2179 layerFunction = "HOLEFILL";
2180 break;
2182 name = "TENTING";
2183 layerFunction = "COATINGNONCOND";
2184 break;
2186 name = "FILLING";
2187 layerFunction = "HOLEFILL";
2188 break;
2190 name = "CAPPING";
2191 layerFunction = "COATINGCOND";
2192 break;
2193 default:
2194 add_node = false;
2195 break;
2196 }
2197 // clang-format on: suggestion is inconsitent
2198
2199 if( add_node && !vec.empty() )
2200 {
2201 wxXmlNode* node = appendNode( aCadLayerNode, "Layer" );
2202 addAttribute( node, "layerFunction", layerFunction );
2203 addAttribute( node, "polarity", "POSITIVE" );
2204
2205 if( std::get<2>( layers ) == UNDEFINED_LAYER )
2206 {
2207 addAttribute( node, "name", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
2208 addAttribute( node, "side", IsFrontLayer( std::get<1>( layers ) ) ? "TOP" : "BOTTOM" );
2209 }
2210 else
2211 {
2212 addAttribute( node, "name",
2213 genLayersString( std::get<1>( layers ), std::get<2>( layers ), TO_UTF8( name ) ) );
2214
2215 const bool first_external = std::get<1>( layers ) == F_Cu || std::get<1>( layers ) == B_Cu;
2216 const bool second_external = std::get<2>( layers ) == F_Cu || std::get<2>( layers ) == B_Cu;
2217
2218 if( first_external )
2219 {
2220 if( second_external )
2221 addAttribute( node, "side", "ALL" );
2222 else
2223 addAttribute( node, "side", "TOP" );
2224 }
2225 else
2226 {
2227 if( second_external )
2228 addAttribute( node, "side", "BOTTOM" );
2229 else
2230 addAttribute( node, "side", "INTERNAL" );
2231 }
2232
2233 wxXmlNode* spanNode = appendNode( node, "Span" );
2234 addAttribute( spanNode, "fromLayer", genLayerString( std::get<1>( layers ), "LAYER" ) );
2235 addAttribute( spanNode, "toLayer", genLayerString( std::get<2>( layers ), "LAYER" ) );
2236 }
2237 }
2238 }
2239}
2240
2241
2242void PCB_IO_IPC2581::generateStepSection( wxXmlNode* aCadNode )
2243{
2244 wxXmlNode* stepNode = appendNode( aCadNode, "Step" );
2245 wxFileName fn( m_board->GetFileName() );
2246 addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) );
2247
2248 if( m_version > 'B' )
2249 addAttribute( stepNode, "type", "BOARD" );
2250
2251 wxXmlNode* datumNode = appendNode( stepNode, "Datum" );
2252 addAttribute( datumNode, "x", "0.0" );
2253 addAttribute( datumNode, "y", "0.0" );
2254
2255 generateProfile( stepNode );
2256 generateComponents( stepNode );
2257
2258 m_last_padstack = insertNode( stepNode, "NonstandardAttribute" );
2259 addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" );
2260 addAttribute( m_last_padstack, "type", "INTEGER" );
2261 addAttribute( m_last_padstack, "value", wxString::Format( "%zu", m_board->Footprints().size() ) );
2262
2263 generateLayerFeatures( stepNode );
2264 generateLayerSetDrill( stepNode );
2265 generateLayerSetAuxilliary( stepNode );
2266}
2267
2268
2269void PCB_IO_IPC2581::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer )
2270{
2271 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2272 FOOTPRINT* fp = aPad->GetParentFootprint();
2273
2274 addPadStack( padNode, aPad );
2275
2276 if( aPad->GetOrientation() != ANGLE_0 )
2277 {
2278 wxXmlNode* xformNode = appendNode( padNode, "Xform" );
2279 EDA_ANGLE angle = aPad->GetOrientation().Normalize();
2280
2281 xformNode->AddAttribute( "rotation", floatVal( angle.AsDegrees() ) );
2282 }
2283
2284 addLocationNode( padNode, *aPad, false );
2285 addShape( padNode, *aPad, aLayer );
2286
2287 if( fp )
2288 {
2289 wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" );
2290
2291 addAttribute( pinRefNode, "componentRef", componentName( fp ) );
2292 addAttribute( pinRefNode, "pin", pinName( aPad ) );
2293 }
2294}
2295
2296
2297void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
2298{
2299 if( !aVia->FlashLayer( aLayer ) )
2300 return;
2301
2302 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
2303
2304 addPadStack( padNode, aVia );
2305 addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y );
2306
2307 PAD dummy( nullptr );
2308 int hole = aVia->GetDrillValue();
2309 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
2310 dummy.SetPosition( aVia->GetStart() );
2311 dummy.SetSize( aLayer, VECTOR2I( aVia->GetWidth( aLayer ), aVia->GetWidth( aLayer ) ) );
2312
2313 addShape( padNode, dummy, aLayer );
2314}
2315
2316
2317void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad )
2318{
2319 size_t hash = hash_fp_item( aPad, 0 );
2320 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2321 auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name );
2322
2323 addAttribute( aPadNode, "padstackDefRef", th_pair->second );
2324
2325 // If we did not insert a new padstack, then we have already added it to the XML
2326 // and we don't need to add it again.
2327 if( !success )
2328 return;
2329
2330 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2331 addAttribute( padStackDefNode, "name", name );
2333 m_padstacks.push_back( padStackDefNode );
2334
2335 if( m_last_padstack )
2336 {
2337 insertNodeAfter( m_last_padstack, padStackDefNode );
2338 m_last_padstack = padStackDefNode;
2339 }
2340
2341 // Only handle round holes here because IPC2581 does not support non-round holes
2342 // These will be handled in a slot layer
2343 if( aPad->HasDrilledHole() )
2344 {
2345 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2346 padStackHoleNode->AddAttribute( "name",
2347 wxString::Format( "%s%d_%d",
2348 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH",
2349 aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
2350
2351 addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) );
2352 addAttribute( padStackHoleNode, "platingStatus",
2353 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
2354 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2355 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2356 addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2357 }
2358
2359 LSEQ layer_seq = aPad->GetLayerSet().Seq();
2360
2361 for( PCB_LAYER_ID layer : layer_seq )
2362 {
2363 if( !m_board->IsLayerEnabled( layer ) )
2364 continue;
2365
2366 wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
2367 addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
2368 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2369 addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y );
2370
2371 if( aPad->HasHole() || !aPad->FlashLayer( layer ) )
2372 {
2373 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2374 shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2375 shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 );
2376 addShape( padStackPadDefNode, shape );
2377 }
2378 else
2379 {
2380 addShape( padStackPadDefNode, *aPad, layer );
2381 }
2382 }
2383}
2384
2385
2386void PCB_IO_IPC2581::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia )
2387{
2388 size_t hash = hash_fp_item( aVia, 0 );
2389 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2390 auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name );
2391
2392 addAttribute( aContentNode, "padstackDefRef", via_pair->second );
2393
2394 // If we did not insert a new padstack, then we have already added it to the XML
2395 // and we don't need to add it again.
2396 if( !success )
2397 return;
2398
2399 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2400 insertNodeAfter( m_last_padstack, padStackDefNode );
2401 m_last_padstack = padStackDefNode;
2402 addAttribute( padStackDefNode, "name", name );
2404
2405 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2406 addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) );
2407 padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) );
2408 addAttribute( padStackHoleNode, "platingStatus", "VIA" );
2409 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2410 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2411 addAttribute( padStackHoleNode, "x", "0.0" );
2412 addAttribute( padStackHoleNode, "y", "0.0" );
2413
2414 LSEQ layer_seq = aVia->GetLayerSet().Seq();
2415
2416 auto addPadShape{ [&]( PCB_LAYER_ID aLayer, const PCB_VIA* aViaShape, const wxString& aLayerRef,
2417 bool aDrill ) -> void
2418 {
2419 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2420
2421 if( aDrill )
2422 shape.SetEnd( { KiROUND( aViaShape->GetDrillValue() / 2.0 ), 0 } );
2423 else
2424 shape.SetEnd( { KiROUND( aViaShape->GetWidth( aLayer ) / 2.0 ), 0 } );
2425
2426 wxXmlNode* padStackPadDefNode =
2427 appendNode( padStackDefNode, "PadstackPadDef" );
2428 addAttribute( padStackPadDefNode, "layerRef", aLayerRef );
2429 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2430
2431 addLocationNode( padStackPadDefNode, 0.0, 0.0 );
2432 addShape( padStackPadDefNode, shape );
2433 } };
2434
2435 for( PCB_LAYER_ID layer : layer_seq )
2436 {
2437 if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) )
2438 continue;
2439
2440 addPadShape( layer, aVia, m_layer_name_map[layer], false );
2441 }
2442
2443 if( aVia->Padstack().IsFilled().value_or( false ) )
2444 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "FILLING" ), true );
2445
2446 if( aVia->Padstack().IsCapped().value_or( false ) )
2447 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "CAPPING" ), true );
2448
2449 for( PCB_LAYER_ID layer : { aVia->TopLayer(), aVia->BottomLayer() } )
2450 {
2451 if( aVia->Padstack().IsPlugged( layer ).value_or( false ) )
2452 addPadShape( layer, aVia, genLayerString( layer, "PLUGGING" ), true );
2453
2454 if( aVia->Padstack().IsCovered( layer ).value_or( false ) )
2455 addPadShape( layer, aVia, genLayerString( layer, "COVERING" ), false );
2456
2457 if( aVia->Padstack().IsTented( layer ).value_or( false ) )
2458 addPadShape( layer, aVia, genLayerString( layer, "TENTING" ), false );
2459 }
2460}
2461
2462
2463void PCB_IO_IPC2581::ensureBackdrillSpecs( const wxString& aPadstackName, const PADSTACK& aPadstack )
2464{
2465 if( m_padstack_backdrill_specs.find( aPadstackName ) != m_padstack_backdrill_specs.end() )
2466 return;
2467
2468 const PADSTACK::DRILL_PROPS& secondary = aPadstack.SecondaryDrill();
2469
2470 if( secondary.start == UNDEFINED_LAYER || secondary.end == UNDEFINED_LAYER )
2471 return;
2472
2473 if( secondary.size.x <= 0 && secondary.size.y <= 0 )
2474 return;
2475
2476 if( !m_cad_header_node )
2477 return;
2478
2479 auto layerHasRef = [&]( PCB_LAYER_ID aLayer ) -> bool
2480 {
2481 return m_layer_name_map.find( aLayer ) != m_layer_name_map.end();
2482 };
2483
2484 if( !layerHasRef( secondary.start ) || !layerHasRef( secondary.end ) )
2485 return;
2486
2487 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
2488 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
2489 stackup.SynchronizeWithBoard( &dsnSettings );
2490
2491 auto createSpec = [&]( const PADSTACK::DRILL_PROPS& aDrill, const wxString& aSpecName ) -> wxString
2492 {
2493 if( aDrill.start == UNDEFINED_LAYER || aDrill.end == UNDEFINED_LAYER )
2494 return wxString();
2495
2496 auto startLayer = m_layer_name_map.find( aDrill.start );
2497 auto endLayer = m_layer_name_map.find( aDrill.end );
2498
2499 if( startLayer == m_layer_name_map.end() || endLayer == m_layer_name_map.end() )
2500 return wxString();
2501
2502 wxXmlNode* specNode = appendNode( m_cad_header_node, "Spec" );
2503 addAttribute( specNode, "name", aSpecName );
2504
2505 wxXmlNode* backdrillNode = appendNode( specNode, "Backdrill" );
2506 addAttribute( backdrillNode, "startLayerRef", startLayer->second );
2507 addAttribute( backdrillNode, "mustNotCutLayerRef", endLayer->second );
2508
2509 int stubLength = stackup.GetLayerDistance( aDrill.start, aDrill.end );
2510
2511 if( stubLength < 0 )
2512 stubLength = 0;
2513
2514 addAttribute( backdrillNode, "maxStubLength", floatVal( m_scale * stubLength ) );
2515
2517
2518 if( aDrill.start == F_Cu )
2519 pm_mode = aPadstack.FrontPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2520 else if( aDrill.start == B_Cu )
2521 pm_mode = aPadstack.BackPostMachining().mode.value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN );
2522
2523 bool isPostMachined = ( pm_mode == PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE ||
2525
2526 addAttribute( backdrillNode, "postMachining", isPostMachined ? wxT( "true" )
2527 : wxT( "false" ) );
2528
2529 m_backdrill_spec_nodes[aSpecName] = specNode;
2530
2531 return aSpecName;
2532 };
2533
2534 int specIndex = m_backdrill_spec_index + 1;
2535
2536 const PADSTACK::DRILL_PROPS& primary = aPadstack.Drill();
2537 wxString primarySpec = createSpec( primary, wxString::Format( wxT( "BD_%dA" ), specIndex ) );
2538
2539 wxString secondarySpec = createSpec( secondary, wxString::Format( wxT( "BD_%dB" ), specIndex ) );
2540
2541 if( primarySpec.IsEmpty() && secondarySpec.IsEmpty() )
2542 return;
2543
2544 m_backdrill_spec_index = specIndex;
2545 m_padstack_backdrill_specs.emplace( aPadstackName, std::make_pair( primarySpec, secondarySpec ) );
2546}
2547
2548
2549void PCB_IO_IPC2581::addBackdrillSpecRefs( wxXmlNode* aHoleNode, const wxString& aPadstackName )
2550{
2551 auto it = m_padstack_backdrill_specs.find( aPadstackName );
2552
2553 if( it == m_padstack_backdrill_specs.end() )
2554 return;
2555
2556 auto addRef = [&]( const wxString& aSpecName )
2557 {
2558 if( aSpecName.IsEmpty() )
2559 return;
2560
2561 wxXmlNode* specRefNode = appendNode( aHoleNode, "SpecRef" );
2562 addAttribute( specRefNode, "id", aSpecName );
2563 m_backdrill_spec_used.insert( aSpecName );
2564 };
2565
2566 addRef( it->second.first );
2567 addRef( it->second.second );
2568}
2569
2570
2572{
2573 if( !m_cad_header_node )
2574 return;
2575
2576 auto it = m_backdrill_spec_nodes.begin();
2577
2578 while( it != m_backdrill_spec_nodes.end() )
2579 {
2580 if( m_backdrill_spec_used.find( it->first ) == m_backdrill_spec_used.end() )
2581 {
2582 wxXmlNode* specNode = it->second;
2583
2584 if( specNode )
2585 {
2586 m_cad_header_node->RemoveChild( specNode );
2587 deleteNode( specNode );
2588 }
2589
2590 it = m_backdrill_spec_nodes.erase( it );
2591 }
2592 else
2593 {
2594 ++it;
2595 }
2596 }
2597}
2598
2599
2600bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
2601 const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
2602 int aWidth, LINE_STYLE aDashType )
2603{
2604 wxXmlNode* polygonNode = nullptr;
2605
2606 if( aPolygon.PointCount() < 3 )
2607 return false;
2608
2609 auto make_node =
2610 [&]()
2611 {
2612 polygonNode = appendNode( aParentNode, "Polygon" );
2613 wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
2614
2615 const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
2616 addXY( polybeginNode, pts[0] );
2617
2618 for( size_t ii = 1; ii < pts.size(); ++ii )
2619 {
2620 wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
2621 addXY( polyNode, pts[ii] );
2622 }
2623
2624 wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
2625 addXY( polyendNode, pts[0] );
2626 };
2627
2628 // Allow the case where we don't want line/fill information in the polygon
2629 if( aFillType == FILL_T::NO_FILL )
2630 {
2631 make_node();
2632 // If we specify a line width, we need to add a LineDescRef node and
2633 // since this is only valid for a non-filled polygon, we need to create
2634 // the fillNode as well
2635 if( aWidth > 0 )
2636 addLineDesc( polygonNode, aWidth, aDashType, true );
2637 }
2638 else
2639 {
2640 wxCHECK( aWidth == 0, false );
2641 make_node();
2642 }
2643
2644 addFillDesc( polygonNode, aFillType );
2645
2646 return true;
2647}
2648
2649
2650bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
2651 const SHAPE_POLY_SET::POLYGON& aPolygon )
2652{
2653 for( size_t ii = 1; ii < aPolygon.size(); ++ii )
2654 {
2655 wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
2656
2657 wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
2658 wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
2659
2660 const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
2661 addXY( polybeginNode, hole[0] );
2662
2663 for( size_t jj = 1; jj < hole.size(); ++jj )
2664 {
2665 wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
2666 addXY( polyNode, hole[jj] );
2667 }
2668
2669 wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
2670 addXY( polyendNode, hole[0] );
2671 }
2672
2673 return true;
2674}
2675
2676
2677bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2678 int aWidth, LINE_STYLE aDashType )
2679{
2680 if( aPolySet.OutlineCount() == 0 )
2681 return false;
2682
2683 wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
2684
2685 const SHAPE_POLY_SET* source = &aPolySet;
2686 SHAPE_POLY_SET merged;
2687
2688 if( aPolySet.OutlineCount() > 1 )
2689 {
2690 merged = aPolySet;
2691 merged.Simplify();
2692
2693 if( merged.OutlineCount() > 0 )
2694 source = &merged;
2695 }
2696
2697 for( int ii = 0; ii < source->OutlineCount(); ++ii )
2698 {
2699 if( !addPolygonNode( outlineNode, source->Outline( ii ) ) )
2700 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
2701 }
2702
2703 if( !outlineNode->GetChildren() )
2704 {
2705 aParentNode->RemoveChild( outlineNode );
2706 deleteNode( outlineNode );
2707 return false;
2708 }
2709
2710 addLineDesc( outlineNode, aWidth, aDashType );
2711
2712 return true;
2713}
2714
2715
2716bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2717 int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType )
2718{
2719 if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
2720 return false;
2721
2722 wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
2723
2724 if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
2725 {
2726 // Do not attempt to add cutouts to shapes that are already hollow
2727 if( aFillType != FILL_T::NO_FILL )
2728 addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
2729 }
2730 else
2731 {
2732 aParentNode->RemoveChild( contourNode );
2733 deleteNode( contourNode );
2734 return false;
2735 }
2736
2737 return true;
2738}
2739
2740
2741void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
2742{
2743 SHAPE_POLY_SET board_outline;
2744
2745 if( ! m_board->GetBoardPolygonOutlines( board_outline, false ) )
2746 {
2747 Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
2748 return;
2749 }
2750
2751 wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
2752
2753 if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
2754 {
2755 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
2756 aStepNode->RemoveChild( profileNode );
2757 deleteNode( profileNode );
2758 return;
2759 }
2760
2761 addPolygonCutouts( profileNode, board_outline.Polygon( 0 ) );
2762}
2763
2764
2765static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
2766{
2767 if( !aFootprint )
2768 return false;
2769
2770 if( aLayer != F_SilkS && aLayer != B_SilkS )
2771 return false;
2772
2773 if( aFootprint->IsFlipped() )
2774 return aLayer == F_SilkS;
2775
2776 return aLayer == B_SilkS;
2777}
2778
2779
2780wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
2781{
2782 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
2783 fp->SetParentGroup( nullptr );
2784 fp->SetPosition( { 0, 0 } );
2785 fp->SetOrientation( ANGLE_0 );
2786
2787 // Track original flipped state before normalization. This is needed to correctly
2788 // determine OtherSideView content per IPC-2581C. After flipping, layer IDs swap,
2789 // so for bottom components, B_SilkS/B_Fab after flip is actually the primary view.
2790 bool wasFlipped = fp->IsFlipped();
2791
2792 // Normalize package geometry to the unflipped footprint coordinate system.
2793 if( fp->IsFlipped() )
2794 fp->Flip( fp->GetPosition(), FLIP_DIRECTION::TOP_BOTTOM );
2795
2796 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
2797 wxString name = genString( wxString::Format( "%s_%zu",
2798 fp->GetFPID().GetLibItemName().wx_str(),
2799 m_footprint_dict.size() + 1 ) );
2800
2801 auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
2802 addAttribute( aContentNode, "packageRef", iter->second );
2803
2804 if( !success)
2805 return nullptr;
2806
2807 // Package and Component nodes are at the same level, so we need to find the parent
2808 // which should be the Step node
2809 wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
2810 wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
2811
2812 addAttribute( packageNode, "name", name );
2813 addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
2814
2815 // We don't specially identify pin 1 in our footprints, so we need to guess
2816 if( fp->FindPadByNumber( "1" ) )
2817 addAttribute( packageNode, "pinOne", "1" );
2818 else if ( fp->FindPadByNumber( "A1" ) )
2819 addAttribute( packageNode, "pinOne", "A1" );
2820 else if ( fp->FindPadByNumber( "A" ) )
2821 addAttribute( packageNode, "pinOne", "A" );
2822 else if ( fp->FindPadByNumber( "a" ) )
2823 addAttribute( packageNode, "pinOne", "a" );
2824 else if ( fp->FindPadByNumber( "a1" ) )
2825 addAttribute( packageNode, "pinOne", "a1" );
2826 else if ( fp->FindPadByNumber( "Anode" ) )
2827 addAttribute( packageNode, "pinOne", "Anode" );
2828 else if ( fp->FindPadByNumber( "ANODE" ) )
2829 addAttribute( packageNode, "pinOne", "ANODE" );
2830 else
2831 addAttribute( packageNode, "pinOne", "UNKNOWN" );
2832
2833 // Infer pinOneOrientation from pin 1 position relative to package centroid.
2834 // IPC-2581C 8.2.3.6 requires a comment attribute when OTHER is used.
2835 PAD* pinOnePad = fp->FindPadByNumber( "1" );
2836
2837 if( !pinOnePad )
2838 pinOnePad = fp->FindPadByNumber( "A1" );
2839
2840 if( pinOnePad && fp->Pads().size() >= 2 )
2841 {
2842 VECTOR2I pinPos = pinOnePad->GetFPRelativePosition();
2843 BOX2I fpBBox = fp->GetBoundingBox();
2844 VECTOR2I center = fpBBox.GetCenter();
2845
2846 // Use 5% of each dimension as the centerline tolerance band
2847 int tolX = fpBBox.GetWidth() / 20;
2848 int tolY = fpBBox.GetHeight() / 20;
2849
2850 bool onCenterX = std::abs( pinPos.x - center.x ) <= tolX;
2851 bool onCenterY = std::abs( pinPos.y - center.y ) <= tolY;
2852
2853 const char* orientation = "OTHER";
2854
2855 if( onCenterX && onCenterY )
2856 orientation = "CENTER";
2857 else if( onCenterX && pinPos.y < center.y )
2858 orientation = "UPPER_CENTER";
2859 else if( onCenterX && pinPos.y > center.y )
2860 orientation = "LOWER_CENTER";
2861 else if( onCenterY && pinPos.x < center.x )
2862 orientation = "LEFT";
2863 else if( onCenterY && pinPos.x > center.x )
2864 orientation = "RIGHT";
2865 else if( pinPos.x < center.x && pinPos.y < center.y )
2866 orientation = "UPPER_LEFT";
2867 else if( pinPos.x > center.x && pinPos.y < center.y )
2868 orientation = "UPPER_RIGHT";
2869 else if( pinPos.x < center.x && pinPos.y > center.y )
2870 orientation = "LOWER_LEFT";
2871 else
2872 orientation = "LOWER_RIGHT";
2873
2874 addAttribute( packageNode, "pinOneOrientation", orientation );
2875 }
2876 else
2877 {
2878 addAttribute( packageNode, "pinOneOrientation", "OTHER" );
2879 addAttribute( packageNode, "comment", "Pin 1 orientation could not be determined" );
2880 }
2881
2882 // After normalization: F_CrtYd is top, B_CrtYd is bottom.
2883 // For bottom components (wasFlipped), these are swapped from original orientation.
2884 const SHAPE_POLY_SET& courtyard_primary = wasFlipped ? fp->GetCourtyard( B_CrtYd )
2885 : fp->GetCourtyard( F_CrtYd );
2886 const SHAPE_POLY_SET& courtyard_other = wasFlipped ? fp->GetCourtyard( F_CrtYd )
2887 : fp->GetCourtyard( B_CrtYd );
2888
2889 if( courtyard_primary.OutlineCount() > 0 )
2890 {
2891 addOutlineNode( packageNode, courtyard_primary, courtyard_primary.Outline( 0 ).Width(),
2893 }
2894 else
2895 {
2896 SHAPE_POLY_SET bbox = fp->GetBoundingHull();
2897 addOutlineNode( packageNode, bbox );
2898 }
2899
2900 if( courtyard_other.OutlineCount() > 0 )
2901 {
2902 if( m_version > 'B' )
2903 {
2904 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
2905 addOutlineNode( otherSideViewNode, courtyard_other, courtyard_other.Outline( 0 ).Width(),
2907 }
2908 }
2909
2910 wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
2911 addAttribute( pickupPointNode, "x", "0.0" );
2912 addAttribute( pickupPointNode, "y", "0.0" );
2913
2914 std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
2915
2916 for( BOARD_ITEM* item : fp->GraphicalItems() )
2917 {
2918 PCB_LAYER_ID layer = item->GetLayer();
2919
2923 if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
2924 continue;
2925
2926 if( m_version == 'B' && isOppositeSideSilk( fp.get(), layer ) )
2927 continue;
2928
2929 bool is_abs = true;
2930
2931 if( item->Type() == PCB_SHAPE_T )
2932 {
2933 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2934
2935 // Circles and Rectanges only have size information so we need to place them in
2936 // a separate node that has a location
2937 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
2938 is_abs = false;
2939 }
2940
2941 elements[item->GetLayer()][is_abs].push_back( item );
2942 }
2943
2944 auto add_base_node =
2945 [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
2946 {
2947 wxXmlNode* parent = packageNode;
2948
2949 // Determine if this layer content should go in OtherSideView.
2950 // Per IPC-2581C, OtherSideView contains geometry visible from the opposite
2951 // side of the package body from the primary view.
2952 //
2953 // For non-flipped (top) components: B_SilkS/B_Fab → OtherSideView
2954 // For flipped (bottom) components after normalization: F_SilkS/F_Fab → OtherSideView
2955 // (because after flip, B_SilkS/B_Fab contains the original primary graphics)
2956 bool is_other_side = wasFlipped ? ( aLayer == F_SilkS || aLayer == F_Fab )
2957 : ( aLayer == B_SilkS || aLayer == B_Fab );
2958
2959 if( is_other_side && m_version > 'B' )
2960 {
2961 if( !otherSideViewNode )
2962 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
2963
2964 parent = otherSideViewNode;
2965 }
2966
2967 wxString nodeName;
2968
2969 if( aLayer == F_SilkS || aLayer == B_SilkS )
2970 nodeName = "SilkScreen";
2971 else if( aLayer == F_Fab || aLayer == B_Fab )
2972 nodeName = "AssemblyDrawing";
2973 else
2974 wxASSERT( false );
2975
2976 wxXmlNode* new_node = appendNode( parent, nodeName );
2977 return new_node;
2978 };
2979
2980 auto add_marking_node =
2981 [&]( wxXmlNode* aNode ) -> wxXmlNode*
2982 {
2983 wxXmlNode* marking_node = appendNode( aNode, "Marking" );
2984 addAttribute( marking_node, "markingUsage", "NONE" );
2985 return marking_node;
2986 };
2987
2988 std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
2989 std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
2990
2991 for( auto layer : { F_Fab, B_Fab } )
2992 {
2993 if( elements.find( layer ) != elements.end() )
2994 {
2995 if( elements[layer][true].size() > 0 )
2996 layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
2997 else if( elements[layer][false].size() > 0 )
2998 layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
2999 }
3000 }
3001
3002 for( auto& [layer, map] : elements )
3003 {
3004 wxXmlNode* layer_node = add_base_node( layer );
3005 wxXmlNode* marking_node = add_marking_node( layer_node );
3006 wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
3007 bool update_bbox = false;
3008
3009 if( layer == F_Fab || layer == B_Fab )
3010 {
3011 layer_nodes[layer] = layer_node;
3012 update_bbox = true;
3013 }
3014
3015 for( auto& [is_abs, vec] : map )
3016 {
3017 for( BOARD_ITEM* item : vec )
3018 {
3019 wxXmlNode* output_node = nullptr;
3020
3021 if( update_bbox )
3022 layer_bbox[layer].Merge( item->GetBoundingBox() );
3023
3024 if( !is_abs )
3025 output_node = add_marking_node( layer_node );
3026 else
3027 output_node = group_node;
3028
3029 switch( item->Type() )
3030 {
3031 case PCB_TEXT_T:
3032 {
3033 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
3034
3035 if( text->IsKnockout() )
3036 addKnockoutText( output_node, text );
3037 else
3038 addText( output_node, text, text->GetFontMetrics() );
3039
3040 break;
3041 }
3042
3043 case PCB_TEXTBOX_T:
3044 {
3045 PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
3046 addText( output_node, text, text->GetFontMetrics() );
3047
3048 // We want to force this to be a polygon to get absolute coordinates
3049 if( text->IsBorderEnabled() )
3050 {
3051 SHAPE_POLY_SET poly_set;
3052 text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
3053 addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
3054 text->GetBorderWidth() );
3055 }
3056
3057 break;
3058 }
3059
3060 case PCB_SHAPE_T:
3061 {
3062 if( !is_abs )
3063 addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
3064
3065 // When in Marking context (!is_abs), use inline geometry to avoid
3066 // unresolved UserPrimitiveRef errors in validators like Vu2581
3067 addShape( output_node, *static_cast<PCB_SHAPE*>( item ), !is_abs );
3068
3069 break;
3070 }
3071
3072 default: break;
3073 }
3074 }
3075 }
3076
3077 if( group_node->GetChildren() == nullptr )
3078 {
3079 marking_node->RemoveChild( group_node );
3080 layer_node->RemoveChild( marking_node );
3081 delete group_node;
3082 delete marking_node;
3083 }
3084 }
3085
3086 for( auto&[layer, bbox] : layer_bbox )
3087 {
3088 if( bbox.GetWidth() > 0 )
3089 {
3090 wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
3091
3092 SHAPE_LINE_CHAIN outline;
3093 std::vector<VECTOR2I> points( 4 );
3094 points[0] = bbox.GetPosition();
3095 points[2] = bbox.GetEnd();
3096 points[1].x = points[0].x;
3097 points[1].y = points[2].y;
3098 points[3].x = points[2].x;
3099 points[3].y = points[0].y;
3100
3101 outline.Append( points );
3102 addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
3103 addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
3104 }
3105 }
3106
3107 std::map<wxString, wxXmlNode*> pin_nodes;
3108
3109 for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
3110 {
3111 PAD* pad = fp->Pads()[ii];
3112 wxString pin_name = pinName( pad );
3113 wxXmlNode* pinNode = nullptr;
3114
3115 auto [ it, inserted ] = pin_nodes.emplace( pin_name, nullptr );
3116
3117 if( inserted )
3118 {
3119 pinNode = appendNode( packageNode, "Pin" );
3120 it->second = pinNode;
3121
3122 addAttribute( pinNode, "number", pin_name );
3123
3124 m_net_pin_dict[pad->GetNetCode()].emplace_back(
3125 genString( fp->GetReference(), "CMP" ), pin_name );
3126
3127 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
3128 addAttribute( pinNode, "electricalType", "MECHANICAL" );
3129 else if( pad->IsOnCopperLayer() )
3130 addAttribute( pinNode, "electricalType", "ELECTRICAL" );
3131 else
3132 addAttribute( pinNode, "electricalType", "UNDEFINED" );
3133
3134 if( pad->HasHole() )
3135 addAttribute( pinNode, "type", "THRU" );
3136 else
3137 addAttribute( pinNode, "type", "SURFACE" );
3138
3139 if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
3140 {
3141 wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
3142 EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
3143
3144 if( fp->IsFlipped() )
3145 pad_angle = pad_angle.Invert().Normalize();
3146
3147 if( pad_angle != ANGLE_0 )
3148 xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
3149 }
3150
3151 addLocationNode( pinNode, *pad, true );
3152 addShape( pinNode, *pad, pad->GetLayer() );
3153 }
3154
3155 // We just need the padstack, we don't need the reference here. The reference will be
3156 // created in the LayerFeature set
3157 wxXmlNode dummy;
3158 addPadStack( &dummy, pad );
3159 }
3160
3161 if( otherSideViewNode )
3162 packageNode->AddChild( otherSideViewNode );
3163
3164 return packageNode;
3165}
3166
3167
3168void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
3169{
3170 std::vector<wxXmlNode*> componentNodes;
3171 std::vector<wxXmlNode*> packageNodes;
3172 std::set<wxString> packageNames;
3173
3174 bool generate_unique = m_OEMRef.empty();
3175
3176 for( FOOTPRINT* fp : m_board->Footprints() )
3177 {
3178 wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
3179 addAttribute( componentNode, "refDes", componentName( fp ) );
3180 wxXmlNode* pkg = addPackage( componentNode, fp );
3181
3182 if( pkg )
3183 packageNodes.push_back( pkg );
3184
3185 wxString name;
3186
3187 PCB_FIELD* field = nullptr;
3188
3189 if( !generate_unique )
3190 field = fp->GetField( m_OEMRef );
3191
3192 if( field && !field->GetText().empty() )
3193 {
3194 name = field->GetShownText( false );
3195 }
3196 else
3197 {
3198 name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(),
3199 fp->GetFPID().GetLibItemName().wx_str(),
3200 fp->GetValue() );
3201 }
3202
3203 if( !m_OEMRef_dict.emplace( fp, name ).second )
3204 Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
3206
3207 addAttribute( componentNode, "part", genString( name, "REF" ) );
3208 addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
3209
3210 if( fp->GetAttributes() & FP_THROUGH_HOLE )
3211 addAttribute( componentNode, "mountType", "THMT" );
3212 else if( fp->GetAttributes() & FP_SMD )
3213 addAttribute( componentNode, "mountType", "SMT" );
3214 else
3215 addAttribute( componentNode, "mountType", "OTHER" );
3216
3217 if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
3218 {
3219 wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
3220
3221 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3222
3223 if( fp_angle != ANGLE_0 )
3224 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3225
3226 if( fp->IsFlipped() )
3227 addAttribute( xformNode, "mirror", "true" );
3228 }
3229
3230 addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
3231
3232 componentNodes.push_back( componentNode );
3233 }
3234
3235 for( wxXmlNode* padstack : m_padstacks )
3236 {
3237 insertNode( aStepNode, padstack );
3238 m_last_padstack = padstack;
3239 }
3240
3241 for( wxXmlNode* pkg : packageNodes )
3242 aStepNode->AddChild( pkg );
3243
3244 for( wxXmlNode* cmp : componentNodes )
3245 aStepNode->AddChild( cmp );
3246}
3247
3248
3249void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
3250{
3251 for( auto& [ net, pin_pair] : m_net_pin_dict )
3252 {
3253 wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
3254 addAttribute( netNode, "name",
3255 genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ;
3256
3257 for( auto& [cmp, pin] : pin_pair )
3258 {
3259 wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
3260 addAttribute( netPinNode, "componentRef", cmp );
3261 addAttribute( netPinNode, "pin", pin );
3262 }
3263 //TODO: Finish
3264 }
3265}
3266
3267//TODO: Add PhyNetGroup section
3268
3269void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
3270{
3271 LSEQ layers = m_board->GetEnabledLayers().Seq();
3272 const NETINFO_LIST& nets = m_board->GetNetInfo();
3273 std::vector<std::unique_ptr<FOOTPRINT>> footprints;
3274
3275 // To avoid the overhead of repeatedly cycling through the layers and nets,
3276 // we pre-sort the board items into a map of layer -> net -> items
3277 std::map<PCB_LAYER_ID, std::map<int, std::vector<BOARD_ITEM*>>> elements;
3278
3279 std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(),
3280 [&layers, &elements]( PCB_TRACK* aTrack )
3281 {
3282 if( aTrack->Type() == PCB_VIA_T )
3283 {
3284 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
3285
3286 for( PCB_LAYER_ID layer : layers )
3287 {
3288 if( via->FlashLayer( layer ) )
3289 elements[layer][via->GetNetCode()].push_back( via );
3290 }
3291 }
3292 else
3293 {
3294 elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack );
3295 }
3296 } );
3297
3298 std::for_each( m_board->Zones().begin(), m_board->Zones().end(),
3299 [ &elements ]( ZONE* zone )
3300 {
3301 LSEQ zone_layers = zone->GetLayerSet().Seq();
3302
3303 for( PCB_LAYER_ID layer : zone_layers )
3304 elements[layer][zone->GetNetCode()].push_back( zone );
3305 } );
3306
3307 for( BOARD_ITEM* item : m_board->Drawings() )
3308 {
3309 if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
3310 elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it );
3311 else
3312 elements[item->GetLayer()][0].push_back( item );
3313 }
3314
3315 for( FOOTPRINT* fp : m_board->Footprints() )
3316 {
3317 for( PCB_FIELD* field : fp->GetFields() )
3318 elements[field->GetLayer()][0].push_back( field );
3319
3320 for( BOARD_ITEM* item : fp->GraphicalItems() )
3321 elements[item->GetLayer()][0].push_back( item );
3322
3323 for( PAD* pad : fp->Pads() )
3324 {
3325 LSEQ pad_layers = pad->GetLayerSet().Seq();
3326
3327 for( PCB_LAYER_ID layer : pad_layers )
3328 {
3329 if( pad->FlashLayer( layer ) )
3330 elements[layer][pad->GetNetCode()].push_back( pad );
3331 }
3332
3333 // SMD pads have implicit solder mask and paste openings that are not in the layer
3334 // set. Add them to the corresponding tech layers if the pad is on a copper layer.
3335 if( pad->IsOnLayer( F_Cu ) && pad->FlashLayer( F_Cu ) )
3336 {
3337 if( !pad->IsOnLayer( F_Mask ) )
3338 elements[F_Mask][pad->GetNetCode()].push_back( pad );
3339
3340 if( !pad->IsOnLayer( F_Paste ) )
3341 elements[F_Paste][pad->GetNetCode()].push_back( pad );
3342 }
3343
3344 if( pad->IsOnLayer( B_Cu ) && pad->FlashLayer( B_Cu ) )
3345 {
3346 if( !pad->IsOnLayer( B_Mask ) )
3347 elements[B_Mask][pad->GetNetCode()].push_back( pad );
3348
3349 if( !pad->IsOnLayer( B_Paste ) )
3350 elements[B_Paste][pad->GetNetCode()].push_back( pad );
3351 }
3352 }
3353 }
3354
3355 for( PCB_LAYER_ID layer : layers )
3356 {
3357 if( m_progressReporter )
3358 m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
3359
3360 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3361 addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
3362
3363 auto process_net = [&] ( int net )
3364 {
3365 std::vector<BOARD_ITEM*>& vec = elements[layer][net];
3366
3367 if( vec.empty() )
3368 return;
3369
3370 std::stable_sort( vec.begin(), vec.end(),
3371 []( BOARD_ITEM* a, BOARD_ITEM* b )
3372 {
3373 if( a->GetParentFootprint() == b->GetParentFootprint() )
3374 return a->Type() < b->Type();
3375
3376 return a->GetParentFootprint() < b->GetParentFootprint();
3377 } );
3378
3379 generateLayerSetNet( layerNode, layer, vec );
3380 };
3381
3382 for( const NETINFO_ITEM* net : nets )
3383 {
3384 if( m_progressReporter )
3385 {
3386 m_progressReporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ),
3387 m_board->GetLayerName( layer ),
3388 net->GetNetname() ) );
3389 m_progressReporter->AdvanceProgress();
3390 }
3391
3392 process_net( net->GetNetCode() );
3393 }
3394
3395 if( layerNode->GetChildren() == nullptr )
3396 {
3397 aStepNode->RemoveChild( layerNode );
3398 deleteNode( layerNode );
3399 }
3400 }
3401}
3402
3403
3404void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
3405{
3406 int hole_count = 1;
3407
3408 for( const auto& [layers, vec] : m_drill_layers )
3409 {
3410 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3411 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "DRILL" ) );
3412
3413 for( BOARD_ITEM* item : vec )
3414 {
3415 if( item->Type() == PCB_VIA_T )
3416 {
3417 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3418 auto it = m_padstack_dict.find( hash_fp_item( via, 0 ) );
3419
3420 if( it == m_padstack_dict.end() )
3421 {
3422 Report( _( "Via uses unsupported padstack; omitted from drill data." ),
3424 continue;
3425 }
3426
3427 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3428 addAttribute( padNode, "geometry", it->second );
3429
3430 if( via->GetNetCode() > 0 )
3431 addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) );
3432
3433 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3434 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3435 addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) );
3436 addAttribute( holeNode, "platingStatus", "VIA" );
3437 addAttribute( holeNode, "plusTol", "0.0" );
3438 addAttribute( holeNode, "minusTol", "0.0" );
3439 addXY( holeNode, via->GetPosition() );
3440 addBackdrillSpecRefs( holeNode, it->second );
3441 }
3442 else if( item->Type() == PCB_PAD_T )
3443 {
3444 PAD* pad = static_cast<PAD*>( item );
3445 auto it = m_padstack_dict.find( hash_fp_item( pad, 0 ) );
3446
3447 if( it == m_padstack_dict.end() )
3448 {
3449 Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
3451 continue;
3452 }
3453
3454 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3455 addAttribute( padNode, "geometry", it->second );
3456
3457 if( pad->GetNetCode() > 0 )
3458 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3459
3460 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
3461 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
3462 addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) );
3463 addAttribute( holeNode, "platingStatus",
3464 pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
3465 addAttribute( holeNode, "plusTol", "0.0" );
3466 addAttribute( holeNode, "minusTol", "0.0" );
3467 addXY( holeNode, pad->GetPosition() );
3468 addBackdrillSpecRefs( holeNode, it->second );
3469 }
3470 }
3471 }
3472
3473 hole_count = 1;
3474
3475 for( const auto& [layers, vec] : m_slot_holes )
3476 {
3477 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
3478 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
3479
3480 for( PAD* pad : vec )
3481 {
3482 wxXmlNode* padNode = appendNode( layerNode, "Set" );
3483
3484 if( pad->GetNetCode() > 0 )
3485 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
3486
3487 addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
3488 }
3489 }
3490}
3491
3492
3493void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
3494 std::vector<BOARD_ITEM*>& aItems )
3495{
3496 auto it = aItems.begin();
3497 wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
3498 wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
3499 wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
3500
3501 bool has_via = false;
3502 bool has_pad = false;
3503
3504 wxXmlNode* padSetNode = nullptr;
3505
3506 wxXmlNode* viaSetNode = nullptr;
3507
3508 wxXmlNode* teardropLayerSetNode = nullptr;
3509 wxXmlNode* teardropFeatureSetNode = nullptr;
3510
3511 bool teardrop_warning = false;
3512
3513 if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
3514 IsCopperLayer( aLayer ) && item )
3515 {
3516 if( item->GetNetCode() > 0 )
3517 addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) );
3518 }
3519
3520 auto add_track =
3521 [&]( PCB_TRACK* track )
3522 {
3523 if( track->Type() == PCB_TRACE_T )
3524 {
3525 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
3526 shape.SetStart( track->GetStart() );
3527 shape.SetEnd( track->GetEnd() );
3528 shape.SetWidth( track->GetWidth() );
3529 addShape( specialNode, shape );
3530 }
3531 else if( track->Type() == PCB_ARC_T )
3532 {
3533 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
3534 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
3535 shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
3536 shape.SetWidth( arc->GetWidth() );
3537 addShape( specialNode, shape );
3538 }
3539 else
3540 {
3541 if( !viaSetNode )
3542 {
3543 if( !has_pad )
3544 {
3545 viaSetNode = layerSetNode;
3546 has_via = true;
3547 }
3548 else
3549 {
3550 viaSetNode = appendNode( layerSetNode, "Set" );
3551
3552 if( track->GetNetCode() > 0 )
3553 addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) );
3554 }
3555
3556 addAttribute( viaSetNode, "padUsage", "VIA" );
3557 }
3558
3559 addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
3560 }
3561 };
3562
3563 auto add_zone =
3564 [&]( ZONE* zone )
3565 {
3566 wxXmlNode* zoneFeatureNode = specialNode;
3567
3568 if( zone->IsTeardropArea() )
3569 {
3570 if( m_version > 'B' )
3571 {
3572 if( !teardropFeatureSetNode )
3573 {
3574 teardropLayerSetNode = appendNode( aLayerNode, "Set" );
3575 addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
3576
3577 if( zone->GetNetCode() > 0 )
3578 {
3579 addAttribute( teardropLayerSetNode, "net",
3580 genString( zone->GetNetname(), "NET" ) );
3581 }
3582
3583 wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
3584 addLocationNode( new_teardrops, 0.0, 0.0 );
3585 teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
3586 }
3587
3588 zoneFeatureNode = teardropFeatureSetNode;
3589 }
3590 else if( !teardrop_warning )
3591 {
3592 Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
3594 teardrop_warning = true;
3595 }
3596 }
3597 else
3598 {
3599 if( FOOTPRINT* fp = zone->GetParentFootprint() )
3600 {
3601 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3602 wxString refDes = componentName( fp );
3603 addAttribute( tempSetNode, "componentRef", refDes );
3604 wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
3605 addLocationNode( newFeatures, 0.0, 0.0 );
3606 zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
3607 }
3608 }
3609
3610 SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
3611
3612 for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
3613 addContourNode( zoneFeatureNode, zone_shape, ii );
3614 };
3615
3616 auto add_shape =
3617 [&] ( PCB_SHAPE* shape )
3618 {
3619 FOOTPRINT* fp = shape->GetParentFootprint();
3620
3621 if( fp )
3622 {
3623 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3624
3625 if( m_version > 'B' )
3626 addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
3627
3628 bool link_to_component = true;
3629
3630 if( m_version == 'B' && isOppositeSideSilk( fp, shape->GetLayer() ) )
3631 link_to_component = false;
3632
3633 if( link_to_component )
3634 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3635
3636 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3637
3638 addLocationNode( tempFeature, *shape );
3639 addShape( tempFeature, *shape );
3640 }
3641 else if( shape->GetShape() == SHAPE_T::CIRCLE
3642 || shape->GetShape() == SHAPE_T::RECTANGLE
3643 || shape->GetShape() == SHAPE_T::POLY )
3644 {
3645 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3646
3647 if( shape->GetNetCode() > 0 )
3648 addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) );
3649
3650 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3651 addLocationNode( tempFeature, *shape );
3652 addShape( tempFeature, *shape );
3653 }
3654 else
3655 {
3656 addShape( specialNode, *shape );
3657 }
3658 };
3659
3660 auto add_text =
3661 [&] ( BOARD_ITEM* text )
3662 {
3663 EDA_TEXT* text_item = nullptr;
3664 FOOTPRINT* fp = text->GetParentFootprint();
3665
3666 if( PCB_TEXT* pcb_text = dynamic_cast<PCB_TEXT*>( text ) )
3667 text_item = static_cast<EDA_TEXT*>( pcb_text );
3668 else if( PCB_TEXTBOX* pcb_textbox = dynamic_cast<PCB_TEXTBOX*>( text ) )
3669 text_item = static_cast<EDA_TEXT*>( pcb_textbox );
3670
3671 if( !text_item || !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
3672 return;
3673
3674 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3675
3676 if( m_version > 'B' )
3677 addAttribute( tempSetNode, "geometryUsage", "TEXT" );
3678
3679 bool link_to_component = fp != nullptr;
3680
3681 if( m_version == 'B' && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
3682 link_to_component = false;
3683
3684 if( link_to_component )
3685 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3686
3687 wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
3688 addAttribute( nonStandardAttributeNode, "name", "TEXT" );
3689 addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) );
3690 addAttribute( nonStandardAttributeNode, "type", "STRING" );
3691
3692 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3693 addLocationNode( tempFeature, 0.0, 0.0 );
3694
3695 if( text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout() )
3696 addKnockoutText( tempFeature, static_cast<PCB_TEXT*>( text ) );
3697 else
3698 addText( tempFeature, text_item, text->GetFontMetrics() );
3699
3700 if( text->Type() == PCB_TEXTBOX_T )
3701 {
3702 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( text );
3703
3704 if( textbox->IsBorderEnabled() )
3705 addShape( tempFeature, *static_cast<PCB_SHAPE*>( textbox ) );
3706 }
3707 };
3708
3709 auto add_pad =
3710 [&]( PAD* pad )
3711 {
3712 if( !padSetNode )
3713 {
3714 if( !has_via )
3715 {
3716 padSetNode = layerSetNode;
3717 has_pad = true;
3718 }
3719 else
3720 {
3721 padSetNode = appendNode( aLayerNode, "Set" );
3722
3723 if( pad->GetNetCode() > 0 )
3724 addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) );
3725 }
3726 }
3727
3728 FOOTPRINT* fp = pad->GetParentFootprint();
3729
3730 if( fp && fp->IsFlipped() )
3731 addPad( padSetNode, pad, FlipLayer( aLayer ) );
3732 else
3733 addPad( padSetNode, pad, aLayer );
3734 };
3735
3736 for( BOARD_ITEM* item : aItems )
3737 {
3738 switch( item->Type() )
3739 {
3740 case PCB_TRACE_T:
3741 case PCB_ARC_T:
3742 case PCB_VIA_T:
3743 add_track( static_cast<PCB_TRACK*>( item ) );
3744 break;
3745
3746 case PCB_ZONE_T:
3747 add_zone( static_cast<ZONE*>( item ) );
3748 break;
3749
3750 case PCB_PAD_T:
3751 add_pad( static_cast<PAD*>( item ) );
3752 break;
3753
3754 case PCB_SHAPE_T:
3755 add_shape( static_cast<PCB_SHAPE*>( item ) );
3756 break;
3757
3758 case PCB_TEXT_T:
3759 case PCB_TEXTBOX_T:
3760 case PCB_FIELD_T:
3761 add_text( item );
3762 break;
3763
3764 case PCB_DIMENSION_T:
3765 case PCB_TARGET_T:
3766 case PCB_DIM_ALIGNED_T:
3767 case PCB_DIM_LEADER_T:
3768 case PCB_DIM_CENTER_T:
3769 case PCB_DIM_RADIAL_T:
3771 //TODO: Add support for dimensions
3772 break;
3773
3774 default:
3775 wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
3776 ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
3777 }
3778 }
3779
3780 if( specialNode->GetChildren() == nullptr )
3781 {
3782 featureSetNode->RemoveChild( specialNode );
3783 deleteNode( specialNode );
3784 }
3785
3786 if( featureSetNode->GetChildren() == nullptr )
3787 {
3788 layerSetNode->RemoveChild( featureSetNode );
3789 deleteNode( featureSetNode );
3790 }
3791
3792 if( layerSetNode->GetChildren() == nullptr )
3793 {
3794 aLayerNode->RemoveChild( layerSetNode );
3795 deleteNode( layerSetNode );
3796 }
3797}
3798
3800{
3801 for( const auto& [layers, vec] : m_auxilliary_Layers )
3802 {
3803 bool add_node = true;
3804
3805 wxString name;
3806 bool hole = false;
3807
3808 // clang-format off: suggestion is inconsitent
3809 switch( std::get<0>(layers) )
3810 {
3812 name = "COVERING";
3813 break;
3815 name = "PLUGGING";
3816 hole = true;
3817 break;
3819 name = "TENTING";
3820 break;
3822 name = "FILLING";
3823 hole = true;
3824 break;
3826 name = "CAPPING";
3827 hole = true;
3828 break;
3829 default:
3830 add_node = false;
3831 break;
3832 }
3833 // clang-format on: suggestion is inconsitent
3834
3835 if( !add_node )
3836 continue;
3837
3838 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3839 if( std::get<2>( layers ) == UNDEFINED_LAYER )
3840 layerNode->AddAttribute( "layerRef", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
3841 else
3842 layerNode->AddAttribute( "layerRef", genLayersString( std::get<1>( layers ),
3843 std::get<2>( layers ), TO_UTF8( name ) ) );
3844
3845 for( BOARD_ITEM* item : vec )
3846 {
3847 if( item->Type() == PCB_VIA_T )
3848 {
3849 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3850
3851 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
3852
3853 if( hole )
3854 shape.SetEnd( { KiROUND( via->GetDrillValue() / 2.0 ), 0 } );
3855 else
3856 shape.SetEnd( { KiROUND( via->GetWidth( std::get<1>( layers ) ) / 2.0 ), 0 } );
3857
3858 wxXmlNode* padNode = appendNode( layerNode, "Pad" );
3859 addPadStack( padNode, via );
3860
3861 addLocationNode( padNode, 0.0, 0.0 );
3862 addShape( padNode, shape );
3863 }
3864 }
3865 }
3866}
3867
3868
3870{
3871 if( m_progressReporter )
3872 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
3873
3874 // Per IPC-2581 schema, Avl requires at least one AvlItem child element.
3875 // Don't emit Avl section if there are no items.
3876 if( m_OEMRef_dict.empty() )
3877 return nullptr;
3878
3879 wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
3880 addAttribute( avl, "name", "Primary_Vendor_List" );
3881
3882 wxXmlNode* header = appendNode( avl, "AvlHeader" );
3883 addAttribute( header, "title", "BOM" );
3884 addAttribute( header, "source", "KiCad" );
3885 addAttribute( header, "author", "OWNER" );
3886 addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
3887 addAttribute( header, "version", "1" );
3888
3889 std::set<wxString> unique_parts;
3890 std::map<wxString,wxString> unique_vendors;
3891
3892 for( auto& [fp, name] : m_OEMRef_dict )
3893 {
3894 auto [ it, success ] = unique_parts.insert( name );
3895
3896 if( !success )
3897 continue;
3898
3899 wxXmlNode* part = appendNode( avl, "AvlItem" );
3900 addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
3901
3902 PCB_FIELD* nums[2] = { fp->GetField( m_mpn ), fp->GetField( m_distpn ) };
3903 PCB_FIELD* company[2] = { fp->GetField( m_mfg ), nullptr };
3904 wxString company_name[2] = { m_mfg, m_dist };
3905
3906 for ( int ii = 0; ii < 2; ++ii )
3907 {
3908 if( nums[ii] )
3909 {
3910 wxString mpn_name = nums[ii]->GetShownText( false );
3911
3912 if( mpn_name.empty() )
3913 continue;
3914
3915 wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
3916 addAttribute( vmpn, "qualified", "false" );
3917 addAttribute( vmpn, "chosen", "false" );
3918
3919 wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
3920 addAttribute( mpn, "name", mpn_name );
3921
3922 wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
3923
3924 wxString vendor_name = wxT( "UNKNOWN" );
3925
3926 // If the field resolves, then use that field content unless it is empty
3927 if( !ii && company[ii] )
3928 {
3929 wxString tmp = company[ii]->GetShownText( false );
3930
3931 if( !tmp.empty() )
3932 vendor_name = tmp;
3933 }
3934 // If it doesn't resolve but there is content from the dialog, use the static content
3935 else if( !ii && !company_name[ii].empty() )
3936 {
3937 vendor_name = company_name[ii];
3938 }
3939 else if( ii && !m_dist.empty() )
3940 {
3941 vendor_name = m_dist;
3942 }
3943
3944 auto [vendor_id, inserted] = unique_vendors.emplace(
3945 vendor_name,
3946 wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
3947
3948 addAttribute( vendor, "enterpriseRef", vendor_id->second );
3949
3950 if( inserted )
3951 {
3952 wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
3953 addAttribute( new_vendor, "id", vendor_id->second );
3954 addAttribute( new_vendor, "name", vendor_name );
3955 addAttribute( new_vendor, "code", "NONE" );
3956 insertNodeAfter( m_enterpriseNode, new_vendor );
3957 m_enterpriseNode = new_vendor;
3958 }
3959 }
3960 }
3961 }
3962
3963 return avl;
3964}
3965
3966
3967void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
3968 const std::map<std::string, UTF8>* aProperties )
3969{
3970 // Clean up any previous export state to allow multiple exports per plugin instance
3971 delete m_xml_doc;
3972 m_xml_doc = nullptr;
3973 m_xml_root = nullptr;
3974 m_contentNode = nullptr;
3975 m_lastAppendedNode = nullptr;
3976
3977 m_board = aBoard;
3979 m_backdrill_spec_nodes.clear();
3980 m_backdrill_spec_used.clear();
3982 m_cad_header_node = nullptr;
3983 m_layer_name_map.clear();
3984
3985 // Clear all internal dictionaries and caches
3986 m_user_shape_dict.clear();
3987 m_shape_user_node = nullptr;
3988 m_std_shape_dict.clear();
3989 m_shape_std_node = nullptr;
3990 m_line_dict.clear();
3991 m_line_node = nullptr;
3992 m_padstack_dict.clear();
3993 m_padstacks.clear();
3994 m_last_padstack = nullptr;
3995 m_footprint_dict.clear();
3998 m_OEMRef_dict.clear();
3999 m_net_pin_dict.clear();
4000 m_drill_layers.clear();
4001 m_slot_holes.clear();
4002 m_auxilliary_Layers.clear();
4003 m_element_names.clear();
4004 m_generated_names.clear();
4005 m_acceptable_chars.clear();
4006 m_total_bytes = 0;
4007
4008 m_units_str = "MILLIMETER";
4009 m_scale = 1.0 / PCB_IU_PER_MM;
4010 m_sigfig = 6;
4011
4012 if( auto it = aProperties->find( "units" ); it != aProperties->end() )
4013 {
4014 if( it->second == "inch" )
4015 {
4016 m_units_str = "INCH";
4017 m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
4018 }
4019 }
4020
4021 if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
4022 m_sigfig = std::stoi( it->second );
4023
4024 if( auto it = aProperties->find( "version" ); it != aProperties->end() )
4025 m_version = it->second.c_str()[0];
4026
4027 if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
4028 m_OEMRef = it->second.wx_str();
4029
4030 if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
4031 m_mpn = it->second.wx_str();
4032
4033 if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
4034 m_mfg = it->second.wx_str();
4035
4036 if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
4037 m_dist = it->second.wx_str();
4038
4039 if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
4040 m_distpn = it->second.wx_str();
4041
4042 if( auto it = aProperties->find( "bomrev" ); it != aProperties->end() )
4043 m_bomRev = it->second.wx_str();
4044
4045 if( m_version == 'B' )
4046 {
4047 for( char c = 'a'; c <= 'z'; ++c )
4048 m_acceptable_chars.insert( c );
4049
4050 for( char c = 'A'; c <= 'Z'; ++c )
4051 m_acceptable_chars.insert( c );
4052
4053 for( char c = '0'; c <= '9'; ++c )
4054 m_acceptable_chars.insert( c );
4055
4056 // Add special characters
4057 std::string specialChars = "_\\-.+><";
4058
4059 for( char c : specialChars )
4060 m_acceptable_chars.insert( c );
4061 }
4062
4063 m_xml_doc = new wxXmlDocument();
4065
4067
4068 if( m_progressReporter )
4069 {
4070 m_progressReporter->SetNumPhases( 7 );
4071 m_progressReporter->BeginPhase( 1 );
4072 m_progressReporter->Report( _( "Generating logistic section" ) );
4073 }
4074
4077
4078 wxXmlNode* ecad_node = generateEcadSection();
4079 wxXmlNode* bom_node = generateBOMSection( ecad_node );
4080 wxXmlNode* avl_node = generateAvlSection();
4081
4082 // Insert BomRef/AvlRef into Content section per IPC-2581C 4.1.1.2.
4083 // They go after LayerRef and before Dictionary* nodes.
4084 if( m_contentNode && ( bom_node || avl_node ) )
4085 {
4086 wxXmlNode* insertBefore = nullptr;
4087
4088 for( wxXmlNode* child = m_contentNode->GetChildren(); child; child = child->GetNext() )
4089 {
4090 if( child->GetName().StartsWith( "Dictionary" ) )
4091 {
4092 insertBefore = child;
4093 break;
4094 }
4095 }
4096
4097 auto insertRef =
4098 [&]( const wxString& aNodeName, wxXmlNode* aSection )
4099 {
4100 if( !aSection )
4101 return;
4102
4103 wxXmlNode* ref = new wxXmlNode( wxXML_ELEMENT_NODE, aNodeName );
4104 ref->AddAttribute( "name", aSection->GetAttribute( "name" ) );
4105
4106 if( insertBefore )
4107 m_contentNode->InsertChild( ref, insertBefore );
4108 else
4109 m_contentNode->AddChild( ref );
4110 };
4111
4112 insertRef( "BomRef", bom_node );
4113 insertRef( "AvlRef", avl_node );
4114 }
4115
4116 if( m_progressReporter )
4117 {
4118 m_progressReporter->AdvancePhase( _( "Saving file" ) );
4119 }
4120
4121 wxFileOutputStreamWithProgress out_stream( aFileName );
4122 double written_bytes = 0.0;
4123 double last_yield = 0.0;
4124
4125 // This is a rough estimation of the size of the spaces in the file
4126 // We just need to total to be slightly larger than the value of the
4127 // progress bar, so accurately counting spaces is not terribly important
4129
4130 auto update_progress = [&]( size_t aBytes )
4131 {
4132 written_bytes += aBytes;
4133 double percent = written_bytes / static_cast<double>( m_total_bytes );
4134
4135 if( m_progressReporter )
4136 {
4137 // Only update every percent
4138 if( last_yield + 0.01 < percent )
4139 {
4140 last_yield = percent;
4141 m_progressReporter->SetCurrentProgress( percent );
4142 }
4143 }
4144 };
4145
4146 out_stream.SetProgressCallback( update_progress );
4147
4148 if( !m_xml_doc->Save( out_stream ) )
4149 {
4150 Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
4151 return;
4152 }
4153}
const char * name
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:129
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
Definition base_units.h:70
bool IsPrmSpecified(const wxString &aPrmValue)
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_SILKSCREEN
@ BS_ITEM_TYPE_DIELECTRIC
@ BS_ITEM_TYPE_SOLDERMASK
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
wxString GetMajorMinorPatchVersion()
Get the major, minor and patch version in a string major.minor.patch This is extracted by CMake from ...
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
BOARD_STACKUP & GetStackupDescriptor()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsKnockout() const
Definition board_item.h:324
FOOTPRINT * GetParentFootprint() const
VECTOR2I GetFPRelativePosition() const
Manage one layer needed to make a physical board.
wxString GetTypeName() const
int GetSublayersCount() const
double GetEpsilonR(int aDielectricSubLayer=0) const
wxString GetColor(int aDielectricSubLayer=0) const
wxString GetLayerName() const
PCB_LAYER_ID GetBrdLayerId() const
int GetThickness(int aDielectricSubLayer=0) const
BOARD_STACKUP_ITEM_TYPE GetType() const
wxString GetMaterial(int aDielectricSubLayer=0) const
int GetDielectricLayerId() const
double GetLossTangent(int aDielectricSubLayer=0) const
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
int GetCount() const
bool SynchronizeWithBoard(BOARD_DESIGN_SETTINGS *aSettings)
Synchronize the BOARD_STACKUP_ITEM* list with the board.
int BuildBoardThicknessFromStackup() const
int GetLayerDistance(PCB_LAYER_ID aFirstLayer, PCB_LAYER_ID aSecondLayer) const
Calculate the distance (height) between the two given copper layers.
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
constexpr 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:527
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:259
FILL_T GetFillMode() const
Definition eda_shape.h:142
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:169
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:216
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:178
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
std::vector< VECTOR2I > GetRectCorners() const
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:220
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:256
int GetRectangleHeight() const
bool IsClockwiseArc() const
void SetWidth(int aWidth)
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:80
const VECTOR2I & GetTextPos() const
Definition eda_text.h:273
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:98
virtual bool IsVisible() const
Definition eda_text.h:187
virtual EDA_ANGLE GetDrawRotation() const
Definition eda_text.h:379
virtual KIFONT::FONT * GetDrawFont(const RENDER_SETTINGS *aSettings) const
Definition eda_text.cpp:661
const TEXT_ATTRIBUTES & GetAttributes() const
Definition eda_text.h:231
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:479
virtual wxString GetShownText(bool aAllowExtraText, int aDepth=0) const
Return the string actually shown after processing of the base text.
Definition eda_text.h:109
static ENUM_MAP< T > & Instance()
Definition property.h:721
unsigned GetPadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of pads.
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:306
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:339
bool IsFlipped() const
Definition footprint.h:524
const wxString & GetReference() const
Definition footprint.h:751
wxString m_name
Name of the IO loader.
Definition io_base.h:234
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:240
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Definition io_base.cpp:124
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h: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:54
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:212
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
std::optional< bool > IsFilled() const
std::optional< bool > IsTented(PCB_LAYER_ID aSide) const
Checks if this padstack is tented (covered in soldermask) on the given side.
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:357
std::optional< bool > IsPlugged(PCB_LAYER_ID aSide) const
DRILL_PROPS & Drill()
Definition padstack.h:348
std::optional< bool > IsCapped() const
std::optional< bool > IsCovered(PCB_LAYER_ID aSide) const
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:351
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:360
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:560
int GetSizeX() const
Definition pad.h:285
void MergePrimitivesAsPolygon(PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aMergedPolygon, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Merge all basic shapes to a SHAPE_POLY_SET.
Definition pad.cpp:3216
int GetRoundRectCornerRadius(PCB_LAYER_ID aLayer) const
Definition pad.cpp:891
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:440
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:1639
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:1702
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:2533
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:586
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:73
void SetProgressCallback(std::function< void(size_t)> aCallback)
@ ROUND_ALL_CORNERS
All angles are rounded.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ UNDEFINED
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
FILL_T
Definition eda_shape.h:56
@ NO_FILL
Definition eda_shape.h:57
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:58
@ FP_SMD
Definition footprint.h:85
@ FP_THROUGH_HOLE
Definition footprint.h:84
static const wxChar traceIpc2581[]
This program source code file is part of KiCad, a free EDA CAD application.
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:32
static constexpr std::size_t hash_val(const Types &... args)
Definition hash.h:51
size_t hash_fp_item(const EDA_ITEM *aItem, int aFlags)
Calculate hash of an EDA_ITEM.
Definition hash_eda.cpp:58
Hashing functions for EDA_ITEMs.
@ HASH_POS
Definition hash_eda.h:47
@ REL_COORD
Use coordinates relative to the parent object.
Definition hash_eda.h:50
surfaceFinishType
IPC-6012 surface finish types from Table 3-3 "Final Finish and Coating Requirements".
@ OTHER
Non-standard finish.
@ DIG
Direct Immersion Gold.
@ IAG
Immersion Silver.
@ ISN
Immersion Tin.
@ S
Solder (HASL/SMOBC)
@ HT_OSP
High Temperature OSP.
@ ENEPIG_N
ENEPIG for soldering (normal gold thickness)
@ NONE
No surface finish / not specified - skip coating layer generation.
@ OSP
Organic Solderability Preservative.
@ G
Gold (hard gold)
@ ENIG_N
ENIG for soldering (normal gold thickness)
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:172
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ User_8
Definition layer_ids.h:131
@ F_CrtYd
Definition layer_ids.h:116
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ Dwgs_User
Definition layer_ids.h:107
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ User_6
Definition layer_ids.h:129
@ User_7
Definition layer_ids.h:130
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ User_5
Definition layer_ids.h:128
@ Eco1_User
Definition layer_ids.h:109
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ User_9
Definition layer_ids.h:132
@ F_Fab
Definition layer_ids.h:119
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Eco2_User
Definition layer_ids.h:110
@ User_3
Definition layer_ids.h:126
@ User_1
Definition layer_ids.h:124
@ B_SilkS
Definition layer_ids.h:101
@ User_4
Definition layer_ids.h:127
@ User_2
Definition layer_ids.h:125
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
bool IsValidLayer(int aLayerId)
Test whether a given integer is a valid layer index, i.e.
Definition layer_ids.h:655
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:76
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
static const std::map< surfaceFinishType, wxString > surfaceFinishTypeToString
Map surfaceFinishType enum to IPC-2581 XML string values.
static bool isOppositeSideSilk(const FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer)
static const std::map< wxString, surfaceFinishType > surfaceFinishMap
Map KiCad surface finish strings to IPC-6012 surfaceFinishType enum.
static surfaceFinishType getSurfaceFinishType(const wxString &aFinish)
see class PGM_BASE
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
std::vector< FAB_LAYER_COLOR > dummy
const std::vector< FAB_LAYER_COLOR > & GetStandardColors(BOARD_STACKUP_ITEM_TYPE aType)
#define KEY_CORE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
LINE_STYLE
Dashed line types.
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:266
PCB_LAYER_ID start
Definition padstack.h:269
PCB_LAYER_ID end
Definition padstack.h:270
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:267
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:281
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
KIBIS_PIN * pin
VECTOR2I center
int radius
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:107
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:100
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694