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