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;
1146
1147 int maxError = m_board->GetDesignSettings().m_MaxError;
1148 aPad.TransformHoleToPolygon( poly_set, 0, maxError, ERROR_INSIDE );
1149
1150 addOutlineNode( slotNode, poly_set );
1151}
1152
1153
1155{
1156 wxXmlNode* logisticNode = appendNode( m_xml_root, "LogisticHeader" );
1157
1158 wxXmlNode* roleNode = appendNode( logisticNode, "Role" );
1159 addAttribute( roleNode, "id", "Owner" );
1160 addAttribute( roleNode, "roleFunction", "SENDER" );
1161
1162 m_enterpriseNode = appendNode( logisticNode, "Enterprise" );
1163 addAttribute( m_enterpriseNode, "id", "UNKNOWN" );
1164 addAttribute( m_enterpriseNode, "code", "NONE" );
1165
1166 wxXmlNode* personNode = appendNode( logisticNode, "Person" );
1167 addAttribute( personNode, "name", "UNKNOWN" );
1168 addAttribute( personNode, "enterpriseRef", "UNKNOWN" );
1169 addAttribute( personNode, "roleRef", "Owner" );
1170
1171 return logisticNode;
1172}
1173
1174
1176{
1177 if( m_progressReporter )
1178 m_progressReporter->AdvancePhase( _( "Generating history section" ) );
1179
1180 wxXmlNode* historyNode = appendNode( m_xml_root, "HistoryRecord" );
1181 addAttribute( historyNode, "number", "1" );
1182 addAttribute( historyNode, "origination", wxDateTime::Now().FormatISOCombined() );
1183 addAttribute( historyNode, "software", "KiCad EDA" );
1184 addAttribute( historyNode, "lastChange", wxDateTime::Now().FormatISOCombined() );
1185
1186 wxXmlNode* fileRevisionNode = appendNode( historyNode, "FileRevision" );
1187 addAttribute( fileRevisionNode, "fileRevisionId", "1" );
1188 addAttribute( fileRevisionNode, "comment", "NO COMMENT" );
1189 addAttribute( fileRevisionNode, "label", "NO LABEL" );
1190
1191 wxXmlNode* softwarePackageNode = appendNode( fileRevisionNode, "SoftwarePackage" );
1192 addAttribute( softwarePackageNode, "name", "KiCad" );
1193 addAttribute( softwarePackageNode, "revision", GetMajorMinorPatchVersion() );
1194 addAttribute( softwarePackageNode, "vendor", "KiCad EDA" );
1195
1196 wxXmlNode* certificationNode = appendNode( softwarePackageNode, "Certification" );
1197 addAttribute( certificationNode, "certificationStatus", "SELFTEST" );
1198
1199 return historyNode;
1200}
1201
1202
1203wxXmlNode* PCB_IO_IPC2581::generateBOMSection( wxXmlNode* aEcadNode )
1204{
1205 if( m_progressReporter )
1206 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
1207
1208 struct REFDES
1209 {
1210 wxString m_name;
1211 wxString m_pkg;
1212 bool m_populate;
1213 wxString m_layer;
1214 };
1215
1216 struct BOM_ENTRY
1217 {
1218 BOM_ENTRY()
1219 {
1220 m_refdes = new std::vector<REFDES>();
1221 m_props = new std::map<wxString, wxString>();
1222 m_count = 0;
1223 m_pads = 0;
1224 }
1225
1226 ~BOM_ENTRY()
1227 {
1228 delete m_refdes;
1229 delete m_props;
1230 }
1231
1232 wxString m_OEMDesignRef; // String combining LIB+FP+VALUE
1233 int m_count;
1234 int m_pads;
1235 wxString m_type;
1236 wxString m_description;
1237
1238 std::vector<REFDES>* m_refdes;
1239 std::map<wxString, wxString>* m_props;
1240 };
1241
1242 std::set<std::unique_ptr<struct BOM_ENTRY>,
1243 std::function<bool( const std::unique_ptr<struct BOM_ENTRY>&,
1244 const std::unique_ptr<struct BOM_ENTRY>& )>> bom_entries(
1245 []( const std::unique_ptr<struct BOM_ENTRY>& a,
1246 const std::unique_ptr<struct BOM_ENTRY>& b )
1247 {
1248 return a->m_OEMDesignRef < b->m_OEMDesignRef;
1249 } );
1250
1251 for( FOOTPRINT* fp_it : m_board->Footprints() )
1252 {
1253 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( fp_it->Clone() ) );
1254 fp->SetParentGroup( nullptr );
1255 fp->SetPosition( {0, 0} );
1256 fp->SetOrientation( ANGLE_0 );
1257
1258 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
1259 auto iter = m_footprint_dict.find( hash );
1260
1261 if( iter == m_footprint_dict.end() )
1262 {
1263 Report( wxString::Format( _( "Footprint %s not found in dictionary; BOM data may be incomplete." ),
1264 fp->GetFPID().GetLibItemName().wx_str() ),
1266 continue;
1267 }
1268
1269 auto entry = std::make_unique<struct BOM_ENTRY>();
1270
1273 if( auto it = m_OEMRef_dict.find( fp_it ); it != m_OEMRef_dict.end() )
1274 {
1275 entry->m_OEMDesignRef = it->second;
1276 }
1277 else
1278 {
1279 Report( wxString::Format( _( "Component \"%s\" missing OEM reference; BOM entry will be skipped." ),
1280 fp->GetFPID().GetLibItemName().wx_str() ),
1282 }
1283
1284 entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" );
1285 entry->m_count = 1;
1286 entry->m_pads = fp->GetPadCount();
1287
1288 // TODO: The options are "ELECTRICAL", "MECHANICAL", "PROGRAMMABLE", "DOCUMENT", "MATERIAL"
1289 // We need to figure out how to determine this.
1290 if( entry->m_pads == 0 || fp_it->GetAttributes() & FP_EXCLUDE_FROM_BOM )
1291 entry->m_type = "DOCUMENT";
1292 else
1293 entry->m_type = "ELECTRICAL";
1294
1295 // Use the footprint's Description field if it exists
1296 const PCB_FIELD* descField = fp_it->GetField( FIELD_T::DESCRIPTION );
1297
1298 if( descField && !descField->GetText().IsEmpty() )
1299 entry->m_description = descField->GetText();
1300
1301 auto[ bom_iter, inserted ] = bom_entries.insert( std::move( entry ) );
1302
1303 if( !inserted )
1304 ( *bom_iter )->m_count++;
1305
1306 REFDES refdes;
1307 refdes.m_name = componentName( fp_it );
1308 refdes.m_pkg = fp->GetFPID().GetLibItemName().wx_str();
1309 refdes.m_populate = !fp->IsDNP() && !( fp->GetAttributes() & FP_EXCLUDE_FROM_BOM );
1310 refdes.m_layer = m_layer_name_map[fp_it->GetLayer()];
1311
1312 ( *bom_iter )->m_refdes->push_back( refdes );
1313
1314 // TODO: This amalgamates all the properties from all the footprints. We need to decide
1315 // if we want to group footprints by their properties
1316 for( PCB_FIELD* prop : fp->GetFields() )
1317 {
1318 // We don't include Reference, Datasheet, or Description in BOM characteristics.
1319 // Value and any user-defined fields are included. Reference is captured above,
1320 // and Description is used for the BomItem description attribute.
1321 if( prop->IsMandatory() && !prop->IsValue() )
1322 continue;
1323
1324 ( *bom_iter )->m_props->emplace( prop->GetName(), prop->GetText() );
1325 }
1326 }
1327
1328 if( bom_entries.empty() )
1329 return nullptr;
1330
1331 wxFileName fn( m_board->GetFileName() );
1332
1333 wxXmlNode* bomNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Bom" );
1334 m_xml_root->InsertChild( bomNode, aEcadNode );
1335 addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) );
1336
1337 wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" );
1338 addAttribute( bomHeaderNode, "revision", "1.0" );
1339 addAttribute( bomHeaderNode, "assembly", genString( fn.GetName() ) );
1340
1341 wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" );
1342 addAttribute( stepRefNode, "name", genString( fn.GetName(), "BOARD" ) );
1343
1344 for( const auto& entry : bom_entries )
1345 {
1346 wxXmlNode* bomEntryNode = appendNode( bomNode, "BomItem" );
1347 addAttribute( bomEntryNode, "OEMDesignNumberRef", entry->m_OEMDesignRef );
1348 addAttribute( bomEntryNode, "quantity", wxString::Format( "%d", entry->m_count ) );
1349 addAttribute( bomEntryNode, "pinCount", wxString::Format( "%d", entry->m_pads ) );
1350 addAttribute( bomEntryNode, "category", entry->m_type );
1351
1352 if( !entry->m_description.IsEmpty() )
1353 addAttribute( bomEntryNode, "description", entry->m_description );
1354
1355 for( const REFDES& refdes : *( entry->m_refdes ) )
1356 {
1357 wxXmlNode* refdesNode = appendNode( bomEntryNode, "RefDes" );
1358 addAttribute( refdesNode, "name", refdes.m_name );
1359 addAttribute( refdesNode, "packageRef", genString( refdes.m_pkg, "PKG" ) );
1360 addAttribute( refdesNode, "populate", refdes.m_populate ? "true" : "false" );
1361 addAttribute( refdesNode, "layerRef", refdes.m_layer );
1362 }
1363
1364 wxXmlNode* characteristicsNode = appendNode( bomEntryNode, "Characteristics" );
1365 addAttribute( characteristicsNode, "category", entry->m_type );
1366
1367 for( const auto& prop : *( entry->m_props ) )
1368 {
1369 wxXmlNode* textualDefNode = appendNode( characteristicsNode, "Textual" );
1370 addAttribute( textualDefNode, "definitionSource", "KICAD" );
1371 addAttribute( textualDefNode, "textualCharacteristicName", prop.first );
1372 addAttribute( textualDefNode, "textualCharacteristicValue", prop.second );
1373 }
1374 }
1375
1376 return bomNode;
1377}
1378
1379
1381{
1382 if( m_progressReporter )
1383 m_progressReporter->AdvancePhase( _( "Generating CAD data" ) );
1384
1385 wxXmlNode* ecadNode = appendNode( m_xml_root, "Ecad" );
1386 addAttribute( ecadNode, "name", "Design" );
1387
1388 addCadHeader( ecadNode );
1389
1390 wxXmlNode* cadDataNode = appendNode( ecadNode, "CadData" );
1391 generateCadLayers( cadDataNode );
1392 generateDrillLayers( cadDataNode );
1393 generateAuxilliaryLayers( cadDataNode );
1394 generateStackup( cadDataNode );
1395 generateStepSection( cadDataNode );
1396
1397 return ecadNode;
1398}
1399
1400
1401void PCB_IO_IPC2581::generateCadSpecs( wxXmlNode* aCadLayerNode )
1402{
1403 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1404 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1405 stackup.SynchronizeWithBoard( &dsnSettings );
1406
1407 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1408 std::set<PCB_LAYER_ID> added_layers;
1409
1410 for( int i = 0; i < stackup.GetCount(); i++ )
1411 {
1412 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1413
1414 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1415 {
1416 wxString ly_name = stackup_item->GetLayerName();
1417
1418 if( ly_name.IsEmpty() )
1419 {
1420 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1421 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1422
1423 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1424 {
1425 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1426
1427 if( sublayer_id > 0 )
1428 ly_name += wxString::Format( "_%d", sublayer_id );
1429 }
1430 }
1431
1432 ly_name = genString( ly_name, "SPEC_LAYER" );
1433
1434 wxXmlNode* specNode = appendNode( aCadLayerNode, "Spec" );
1435 addAttribute( specNode, "name", ly_name );
1436 wxXmlNode* generalNode = appendNode( specNode, "General" );
1437 addAttribute( generalNode, "type", "MATERIAL" );
1438 wxXmlNode* propertyNode = appendNode( generalNode, "Property" );
1439
1440 switch ( stackup_item->GetType() )
1441 {
1443 {
1444 addAttribute( propertyNode, "text", "COPPER" );
1445 wxXmlNode* conductorNode = appendNode( specNode, "Conductor" );
1446 addAttribute( conductorNode, "type", "CONDUCTIVITY" );
1447 propertyNode = appendNode( conductorNode, "Property" );
1448 addAttribute( propertyNode, "unit", wxT( "SIEMENS/M" ) );
1449 addAttribute( propertyNode, "value", wxT( "5.959E7" ) );
1450 break;
1451 }
1453 {
1454 addAttribute( propertyNode, "text", stackup_item->GetMaterial() );
1455 propertyNode = appendNode( generalNode, "Property" );
1456 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1457 stackup_item->GetTypeName() ) );
1458 wxXmlNode* dielectricNode = appendNode( specNode, "Dielectric" );
1459 addAttribute( dielectricNode, "type", "DIELECTRIC_CONSTANT" );
1460 propertyNode = appendNode( dielectricNode, "Property" );
1461 addAttribute( propertyNode, "value",
1462 floatVal( stackup_item->GetEpsilonR( sublayer_id ) ) );
1463 dielectricNode = appendNode( specNode, "Dielectric" );
1464 addAttribute( dielectricNode, "type", "LOSS_TANGENT" );
1465 propertyNode = appendNode( dielectricNode, "Property" );
1466 addAttribute( propertyNode, "value",
1467 floatVal( stackup_item->GetLossTangent( sublayer_id ) ) );
1468 break;
1469 }
1471 addAttribute( propertyNode, "text", stackup_item->GetTypeName() );
1472 propertyNode = appendNode( generalNode, "Property" );
1473 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1474 stackup_item->GetColor() ) );
1475 propertyNode = appendNode( generalNode, "Property" );
1476 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1477 stackup_item->GetTypeName() ) );
1478 break;
1480 addAttribute( propertyNode, "text", "SOLDERMASK" );
1481 propertyNode = appendNode( generalNode, "Property" );
1482 addAttribute( propertyNode, "text", wxString::Format( "Color : %s",
1483 stackup_item->GetColor() ) );
1484 propertyNode = appendNode( generalNode, "Property" );
1485 addAttribute( propertyNode, "text", wxString::Format( "Type : %s",
1486 stackup_item->GetTypeName() ) );
1487 break;
1488 default:
1489 break;
1490 }
1491 }
1492 }
1493}
1494
1495
1496void PCB_IO_IPC2581::addCadHeader( wxXmlNode* aEcadNode )
1497{
1498 wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" );
1499 addAttribute( cadHeaderNode, "units", m_units_str );
1500
1501 generateCadSpecs( cadHeaderNode );
1502}
1503
1504
1506{
1507 return ( aLayer >= F_Cu && aLayer <= User_9 ) || aLayer == UNDEFINED_LAYER;
1508}
1509
1510
1511void PCB_IO_IPC2581::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer )
1512{
1513 switch( aLayer )
1514 {
1515 case F_Adhes:
1516 case B_Adhes:
1517 addAttribute( aNode, "layerFunction", "GLUE" );
1518 addAttribute( aNode, "polarity", "POSITIVE" );
1519 addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" );
1520 break;
1521 case F_Paste:
1522 case B_Paste:
1523 addAttribute( aNode, "layerFunction", "SOLDERPASTE" );
1524 addAttribute( aNode, "polarity", "POSITIVE" );
1525 addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" );
1526 break;
1527 case F_SilkS:
1528 case B_SilkS:
1529 addAttribute( aNode, "layerFunction", "SILKSCREEN" );
1530 addAttribute( aNode, "polarity", "POSITIVE" );
1531 addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" );
1532 break;
1533 case F_Mask:
1534 case B_Mask:
1535 addAttribute( aNode, "layerFunction", "SOLDERMASK" );
1536 addAttribute( aNode, "polarity", "POSITIVE" );
1537 addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" );
1538 break;
1539 case Edge_Cuts:
1540 addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" );
1541 addAttribute( aNode, "polarity", "POSITIVE" );
1542 addAttribute( aNode, "side", "ALL" );
1543 break;
1544 case B_CrtYd:
1545 case F_CrtYd:
1546 addAttribute( aNode, "layerFunction", "COURTYARD" );
1547 addAttribute( aNode, "polarity", "POSITIVE" );
1548 addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" );
1549 break;
1550 case B_Fab:
1551 case F_Fab:
1552 addAttribute( aNode, "layerFunction", "ASSEMBLY" );
1553 addAttribute( aNode, "polarity", "POSITIVE" );
1554 addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" );
1555 break;
1556 case Dwgs_User:
1557 case Cmts_User:
1558 case Eco1_User:
1559 case Eco2_User:
1560 case Margin:
1561 case User_1:
1562 case User_2:
1563 case User_3:
1564 case User_4:
1565 case User_5:
1566 case User_6:
1567 case User_7:
1568 case User_8:
1569 case User_9:
1570 addAttribute( aNode, "layerFunction", "DOCUMENT" );
1571 addAttribute( aNode, "polarity", "POSITIVE" );
1572 addAttribute( aNode, "side", "NONE" );
1573 break;
1574
1575 default:
1576 if( IsCopperLayer( aLayer ) )
1577 {
1578 addAttribute( aNode, "layerFunction", "CONDUCTOR" );
1579 addAttribute( aNode, "polarity", "POSITIVE" );
1580 addAttribute( aNode, "side",
1581 aLayer == F_Cu ? "TOP"
1582 : aLayer == B_Cu ? "BOTTOM"
1583 : "INTERNAL" );
1584 }
1585
1586 break; // Do not handle other layers
1587 }
1588}
1589
1590
1591void PCB_IO_IPC2581::generateStackup( wxXmlNode* aCadLayerNode )
1592{
1593 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1594 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1595 stackup.SynchronizeWithBoard( &dsnSettings );
1596
1597 wxXmlNode* stackupNode = appendNode( aCadLayerNode, "Stackup" );
1598 addAttribute( stackupNode, "name", "Primary_Stackup" );
1599 addAttribute( stackupNode, "overallThickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1600 addAttribute( stackupNode, "tolPlus", "0.0" );
1601 addAttribute( stackupNode, "tolMinus", "0.0" );
1602 addAttribute( stackupNode, "whereMeasured", "MASK" );
1603
1604 if( m_version > 'B' )
1605 addAttribute( stackupNode, "stackupStatus", "PROPOSED" );
1606
1607 wxXmlNode* stackupGroup = appendNode( stackupNode, "StackupGroup" );
1608 addAttribute( stackupGroup, "name", "Primary_Stackup_Group" );
1609 addAttribute( stackupGroup, "thickness", floatVal( m_scale * stackup.BuildBoardThicknessFromStackup() ) );
1610 addAttribute( stackupGroup, "tolPlus", "0.0" );
1611 addAttribute( stackupGroup, "tolMinus", "0.0" );
1612
1613 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1614 std::set<PCB_LAYER_ID> added_layers;
1615
1616 for( int i = 0; i < stackup.GetCount(); i++ )
1617 {
1618 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1619
1620 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1621 {
1622
1623 wxXmlNode* stackupLayer = appendNode( stackupGroup, "StackupLayer" );
1624 wxString ly_name = stackup_item->GetLayerName();
1625
1626 if( ly_name.IsEmpty() )
1627 {
1628 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1629 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1630
1631 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1632 {
1633 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1634
1635 if( sublayer_id > 0 )
1636 ly_name += wxString::Format( "_%d", sublayer_id );
1637 }
1638 }
1639
1640 ly_name = genString( ly_name, "LAYER" );
1641
1642 addAttribute( stackupLayer, "layerOrGroupRef", ly_name );
1643 addAttribute( stackupLayer, "thickness", floatVal( m_scale * stackup_item->GetThickness() ) );
1644 addAttribute( stackupLayer, "tolPlus", "0.0" );
1645 addAttribute( stackupLayer, "tolMinus", "0.0" );
1646 addAttribute( stackupLayer, "sequence", wxString::Format( "%d", i ) );
1647
1648 wxXmlNode* specLayerNode = appendNode( stackupLayer, "SpecRef" );
1649 addAttribute( specLayerNode, "id", wxString::Format( "SPEC_%s", ly_name ) );
1650 }
1651 }
1652}
1653
1654
1655void PCB_IO_IPC2581::generateCadLayers( wxXmlNode* aCadLayerNode )
1656{
1657
1658 BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings();
1659 BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor();
1660 stackup.SynchronizeWithBoard( &dsnSettings );
1661
1662 std::vector<BOARD_STACKUP_ITEM*> layers = stackup.GetList();
1663 std::set<PCB_LAYER_ID> added_layers;
1664
1665 for( int i = 0; i < stackup.GetCount(); i++ )
1666 {
1667 BOARD_STACKUP_ITEM* stackup_item = layers.at( i );
1668
1669 if( !isValidLayerFor2581( stackup_item->GetBrdLayerId() ) )
1670 continue;
1671
1672 for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ )
1673 {
1674 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
1675 wxString ly_name = stackup_item->GetLayerName();
1676
1677 if( ly_name.IsEmpty() )
1678 {
1679
1680 if( IsValidLayer( stackup_item->GetBrdLayerId() ) )
1681 ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() );
1682
1683 if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1684 {
1685 ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() );
1686
1687 if( sublayer_id > 0 )
1688 ly_name += wxString::Format( "_%d", sublayer_id );
1689 }
1690 }
1691
1692 ly_name = genString( ly_name, "LAYER" );
1693
1694 addAttribute( cadLayerNode, "name", ly_name );
1695
1696 if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
1697 {
1698 if( stackup_item->GetTypeName() == KEY_CORE )
1699 addAttribute( cadLayerNode, "layerFunction", "DIELCORE" );
1700 else
1701 addAttribute( cadLayerNode, "layerFunction", "DIELPREG" );
1702
1703 addAttribute( cadLayerNode, "polarity", "POSITIVE" );
1704 addAttribute( cadLayerNode, "side", "INTERNAL" );
1705 continue;
1706 }
1707 else
1708 {
1709 added_layers.insert( stackup_item->GetBrdLayerId() );
1710 addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() );
1711 m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name );
1712 }
1713 }
1714 }
1715
1716 LSEQ layer_seq = m_board->GetEnabledLayers().Seq();
1717
1718 for( PCB_LAYER_ID layer : layer_seq )
1719 {
1720 if( added_layers.find( layer ) != added_layers.end() || !isValidLayerFor2581( layer ) )
1721 continue;
1722
1723 wxString ly_name = genLayerString( layer, "LAYER" );
1724 m_layer_name_map.emplace( layer, ly_name );
1725 added_layers.insert( layer );
1726 wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" );
1727 addAttribute( cadLayerNode, "name", ly_name );
1728
1729 addLayerAttributes( cadLayerNode, layer );
1730 }
1731}
1732
1733
1734void PCB_IO_IPC2581::generateDrillLayers( wxXmlNode* aCadLayerNode )
1735{
1736 for( BOARD_ITEM* item : m_board->Tracks() )
1737 {
1738 if( item->Type() == PCB_VIA_T )
1739 {
1740 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1741 m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via );
1742 }
1743 }
1744
1745 for( FOOTPRINT* fp : m_board->Footprints() )
1746 {
1747 for( PAD* pad : fp->Pads() )
1748 {
1749 if( pad->HasDrilledHole() )
1750 m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad );
1751 else if( pad->HasHole() )
1752 m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad );
1753 }
1754 }
1755
1756 for( const auto& [layers, vec] : m_drill_layers )
1757 {
1758 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
1759 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "DRILL" ) );
1760 addAttribute( drillNode, "layerFunction", "DRILL" );
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 for( const auto& [layers, vec] : m_slot_holes )
1770 {
1771 wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" );
1772 drillNode->AddAttribute( "name", genLayersString( layers.first, layers.second, "SLOT" ) );
1773
1774 addAttribute( drillNode, "layerFunction", "ROUT" );
1775 addAttribute( drillNode, "polarity", "POSITIVE" );
1776 addAttribute( drillNode, "side", "ALL" );
1777
1778 wxXmlNode* spanNode = appendNode( drillNode, "Span" );
1779 addAttribute( spanNode, "fromLayer", genLayerString( layers.first, "LAYER" ) );
1780 addAttribute( spanNode, "toLayer", genLayerString( layers.second, "LAYER" ) );
1781 }
1782}
1783
1784
1785void PCB_IO_IPC2581::generateAuxilliaryLayers( wxXmlNode* aCadLayerNode )
1786{
1787 for( BOARD_ITEM* item : m_board->Tracks() )
1788 {
1789 if( item->Type() != PCB_VIA_T )
1790 continue;
1791
1792 PCB_VIA* via = static_cast<PCB_VIA*>( item );
1793
1794 std::vector<std::tuple<auxLayerType, PCB_LAYER_ID, PCB_LAYER_ID>> new_layers;
1795
1796 if( via->Padstack().IsFilled().value_or( false ) )
1797 new_layers.emplace_back( auxLayerType::FILLING, via->TopLayer(), via->BottomLayer() );
1798
1799 if( via->Padstack().IsCapped().value_or( false ) )
1800 new_layers.emplace_back( auxLayerType::CAPPING, via->TopLayer(), via->BottomLayer() );
1801
1802 for( PCB_LAYER_ID layer : { via->TopLayer(), via->BottomLayer() } )
1803 {
1804 if( via->Padstack().IsPlugged( layer ).value_or( false ) )
1805 new_layers.emplace_back( auxLayerType::PLUGGING, layer, UNDEFINED_LAYER );
1806
1807 if( via->Padstack().IsCovered( layer ).value_or( false ) )
1808 new_layers.emplace_back( auxLayerType::COVERING, layer, UNDEFINED_LAYER );
1809
1810 if( via->Padstack().IsTented( layer ).value_or( false ) )
1811 new_layers.emplace_back( auxLayerType::TENTING, layer, UNDEFINED_LAYER );
1812 }
1813
1814 for( auto& tuple : new_layers )
1815 m_auxilliary_Layers[tuple].push_back( via );
1816 }
1817
1818 for( const auto& [layers, vec] : m_auxilliary_Layers )
1819 {
1820 bool add_node = true;
1821
1822 wxString name;
1823 wxString layerFunction;
1824
1825 // clang-format off: suggestion is inconsitent
1826 switch( std::get<0>(layers) )
1827 {
1829 name = "COVERING";
1830 layerFunction = "COATINGNONCOND";
1831 break;
1833 name = "PLUGGING";
1834 layerFunction = "HOLEFILL";
1835 break;
1837 name = "TENTING";
1838 layerFunction = "COATINGNONCOND";
1839 break;
1841 name = "FILLING";
1842 layerFunction = "HOLEFILL";
1843 break;
1845 name = "CAPPING";
1846 layerFunction = "COATINGCOND";
1847 break;
1848 default:
1849 add_node = false;
1850 break;
1851 }
1852 // clang-format on: suggestion is inconsitent
1853
1854 if( add_node && !vec.empty() )
1855 {
1856 wxXmlNode* node = appendNode( aCadLayerNode, "LAYER" );
1857 addAttribute( node, "layerFunction", layerFunction );
1858 addAttribute( node, "polarity", "POSITIVE" );
1859
1860 if( std::get<2>( layers ) == UNDEFINED_LAYER )
1861 {
1862 addAttribute( node, "name", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
1863 addAttribute( node, "side", IsFrontLayer( std::get<1>( layers ) ) ? "TOP" : "BOTTOM" );
1864 }
1865 else
1866 {
1867 addAttribute( node, "name",
1868 genLayersString( std::get<1>( layers ), std::get<2>( layers ), TO_UTF8( name ) ) );
1869
1870 const bool first_external = std::get<1>( layers ) == F_Cu || std::get<1>( layers ) == B_Cu;
1871 const bool second_external = std::get<2>( layers ) == F_Cu || std::get<2>( layers ) == B_Cu;
1872
1873 if( first_external )
1874 {
1875 if( second_external )
1876 addAttribute( node, "side", "ALL" );
1877 else
1878 addAttribute( node, "side", "FRONT" );
1879 }
1880 else
1881 {
1882 if( second_external )
1883 addAttribute( node, "side", "BACK" );
1884 else
1885 addAttribute( node, "side", "INTERNAL" );
1886 }
1887
1888 wxXmlNode* spanNode = appendNode( node, "SPAN" );
1889 addAttribute( spanNode, "fromLayer", genLayerString( std::get<1>( layers ), "LAYER" ) );
1890 addAttribute( spanNode, "toLayer", genLayerString( std::get<2>( layers ), "LAYER" ) );
1891 }
1892 }
1893 }
1894}
1895
1896
1897void PCB_IO_IPC2581::generateStepSection( wxXmlNode* aCadNode )
1898{
1899 wxXmlNode* stepNode = appendNode( aCadNode, "Step" );
1900 wxFileName fn( m_board->GetFileName() );
1901 addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) );
1902
1903 if( m_version > 'B' )
1904 addAttribute( stepNode, "type", "BOARD" );
1905
1906 wxXmlNode* datumNode = appendNode( stepNode, "Datum" );
1907 addAttribute( datumNode, "x", "0.0" );
1908 addAttribute( datumNode, "y", "0.0" );
1909
1910 generateProfile( stepNode );
1911 generateComponents( stepNode );
1912
1913 m_last_padstack = insertNode( stepNode, "NonstandardAttribute" );
1914 addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" );
1915 addAttribute( m_last_padstack, "type", "INTEGER" );
1916 addAttribute( m_last_padstack, "value", wxString::Format( "%zu", m_board->Footprints().size() ) );
1917
1918 generateLayerFeatures( stepNode );
1919 generateLayerSetDrill( stepNode );
1920 generateLayerSetAuxilliary( stepNode );
1921}
1922
1923
1924void PCB_IO_IPC2581::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer )
1925{
1926 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
1927 FOOTPRINT* fp = aPad->GetParentFootprint();
1928
1929 addPadStack( padNode, aPad );
1930
1931 if( aPad->GetOrientation() != ANGLE_0 )
1932 {
1933 wxXmlNode* xformNode = appendNode( padNode, "Xform" );
1934 EDA_ANGLE angle = aPad->GetOrientation().Normalize();
1935
1936 xformNode->AddAttribute( "rotation", floatVal( angle.AsDegrees() ) );
1937 }
1938
1939 addLocationNode( padNode, *aPad, false );
1940 addShape( padNode, *aPad, aLayer );
1941
1942 if( fp )
1943 {
1944 wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" );
1945
1946 addAttribute( pinRefNode, "componentRef", componentName( fp ) );
1947 addAttribute( pinRefNode, "pin", pinName( aPad ) );
1948 }
1949}
1950
1951
1952void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer )
1953{
1954 if( !aVia->FlashLayer( aLayer ) )
1955 return;
1956
1957 wxXmlNode* padNode = appendNode( aContentNode, "Pad" );
1958
1959 addPadStack( padNode, aVia );
1960 addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y );
1961
1962 PAD dummy( nullptr );
1963 int hole = aVia->GetDrillValue();
1964 dummy.SetDrillSize( VECTOR2I( hole, hole ) );
1965 dummy.SetPosition( aVia->GetStart() );
1966 dummy.SetSize( aLayer, VECTOR2I( aVia->GetWidth( aLayer ), aVia->GetWidth( aLayer ) ) );
1967
1968 addShape( padNode, dummy, aLayer );
1969}
1970
1971
1972void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad )
1973{
1974 size_t hash = hash_fp_item( aPad, 0 );
1975 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
1976 auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name );
1977
1978 addAttribute( aPadNode, "padstackDefRef", th_pair->second );
1979
1980 // If we did not insert a new padstack, then we have already added it to the XML
1981 // and we don't need to add it again.
1982 if( !success )
1983 return;
1984
1985 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
1986 addAttribute( padStackDefNode, "name", name );
1987 m_padstacks.push_back( padStackDefNode );
1988
1989 if( m_last_padstack )
1990 {
1991 insertNodeAfter( m_last_padstack, padStackDefNode );
1992 m_last_padstack = padStackDefNode;
1993 }
1994
1995 // Only handle round holes here because IPC2581 does not support non-round holes
1996 // These will be handled in a slot layer
1997 if( aPad->HasDrilledHole() )
1998 {
1999 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2000 padStackHoleNode->AddAttribute( "name",
2001 wxString::Format( "%s%d_%d",
2002 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH",
2003 aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) );
2004
2005 addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) );
2006 addAttribute( padStackHoleNode, "platingStatus",
2007 aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
2008 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2009 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2010 addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2011 }
2012
2013 LSEQ layer_seq = aPad->GetLayerSet().Seq();
2014
2015 for( PCB_LAYER_ID layer : layer_seq )
2016 {
2017 FOOTPRINT* fp = aPad->GetParentFootprint();
2018
2019 if( !m_board->IsLayerEnabled( layer ) )
2020 continue;
2021
2022 wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" );
2023 addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] );
2024 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2025 addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y );
2026
2027 if( aPad->HasHole() || !aPad->FlashLayer( layer ) )
2028 {
2029 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2030 shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) );
2031 shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 );
2032 addShape( padStackPadDefNode, shape );
2033 }
2034 else
2035 {
2036 addShape( padStackPadDefNode, *aPad, layer );
2037 }
2038 }
2039}
2040
2041
2042void PCB_IO_IPC2581::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia )
2043{
2044 size_t hash = hash_fp_item( aVia, 0 );
2045 wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 );
2046 auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name );
2047
2048 addAttribute( aContentNode, "padstackDefRef", via_pair->second );
2049
2050 // If we did not insert a new padstack, then we have already added it to the XML
2051 // and we don't need to add it again.
2052 if( !success )
2053 return;
2054
2055 wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" );
2056 insertNodeAfter( m_last_padstack, padStackDefNode );
2057 m_last_padstack = padStackDefNode;
2058 addAttribute( padStackDefNode, "name", name );
2059
2060 wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" );
2061 addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) );
2062 padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) );
2063 addAttribute( padStackHoleNode, "platingStatus", "VIA" );
2064 addAttribute( padStackHoleNode, "plusTol", "0.0" );
2065 addAttribute( padStackHoleNode, "minusTol", "0.0" );
2066 addAttribute( padStackHoleNode, "x", "0.0" );
2067 addAttribute( padStackHoleNode, "y", "0.0" );
2068
2069 LSEQ layer_seq = aVia->GetLayerSet().Seq();
2070
2071 auto addPadShape{ [&]( PCB_LAYER_ID layer, const PCB_VIA* aVia, const wxString& name,
2072 bool drill ) -> void
2073 {
2074 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
2075
2076 if( drill )
2077 shape.SetEnd( { KiROUND( aVia->GetDrillValue() / 2.0 ), 0 } );
2078 else
2079 shape.SetEnd( { KiROUND( aVia->GetWidth( layer ) / 2.0 ), 0 } );
2080
2081 wxXmlNode* padStackPadDefNode =
2082 appendNode( padStackDefNode, "PadstackPadDef" );
2083 addAttribute( padStackPadDefNode, "layerRef", name );
2084 addAttribute( padStackPadDefNode, "padUse", "REGULAR" );
2085
2086 addLocationNode( padStackPadDefNode, 0.0, 0.0 );
2087 addShape( padStackPadDefNode, shape );
2088 } };
2089
2090 for( PCB_LAYER_ID layer : layer_seq )
2091 {
2092 if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) )
2093 continue;
2094
2095 addPadShape( layer, aVia, m_layer_name_map[layer], false );
2096 }
2097
2098 if( aVia->Padstack().IsFilled().value_or( false ) )
2099 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "FILLING" ), true );
2100
2101 if( aVia->Padstack().IsCapped().value_or( false ) )
2102 addPadShape( UNDEFINED_LAYER, aVia, genLayersString( aVia->TopLayer(), aVia->BottomLayer(), "CAPPING" ), true );
2103
2104 for( PCB_LAYER_ID layer : { aVia->TopLayer(), aVia->BottomLayer() } )
2105 {
2106 if( aVia->Padstack().IsPlugged( layer ).value_or( false ) )
2107 addPadShape( layer, aVia, genLayerString( layer, "PLUGGING" ), true );
2108
2109 if( aVia->Padstack().IsCovered( layer ).value_or( false ) )
2110 addPadShape( layer, aVia, genLayerString( layer, "COVERING" ), false );
2111
2112 if( aVia->Padstack().IsTented( layer ).value_or( false ) )
2113 addPadShape( layer, aVia, genLayerString( layer, "TENTING" ), false );
2114 }
2115}
2116
2117
2118bool PCB_IO_IPC2581::addPolygonNode( wxXmlNode* aParentNode,
2119 const SHAPE_LINE_CHAIN& aPolygon, FILL_T aFillType,
2120 int aWidth, LINE_STYLE aDashType )
2121{
2122 wxXmlNode* polygonNode = nullptr;
2123
2124 if( aPolygon.PointCount() < 3 )
2125 return false;
2126
2127 auto make_node =
2128 [&]()
2129 {
2130 polygonNode = appendNode( aParentNode, "Polygon" );
2131 wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" );
2132
2133 const std::vector<VECTOR2I>& pts = aPolygon.CPoints();
2134 addXY( polybeginNode, pts[0] );
2135
2136 for( size_t ii = 1; ii < pts.size(); ++ii )
2137 {
2138 wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" );
2139 addXY( polyNode, pts[ii] );
2140 }
2141
2142 wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" );
2143 addXY( polyendNode, pts[0] );
2144 };
2145
2146 // Allow the case where we don't want line/fill information in the polygon
2147 if( aFillType == FILL_T::NO_FILL )
2148 {
2149 make_node();
2150 // If we specify a line width, we need to add a LineDescRef node and
2151 // since this is only valid for a non-filled polygon, we need to create
2152 // the fillNode as well
2153 if( aWidth > 0 )
2154 addLineDesc( polygonNode, aWidth, aDashType, true );
2155 }
2156 else
2157 {
2158 wxCHECK( aWidth == 0, false );
2159 make_node();
2160 }
2161
2162 addFillDesc( polygonNode, aFillType );
2163
2164 return true;
2165}
2166
2167
2168bool PCB_IO_IPC2581::addPolygonCutouts( wxXmlNode* aParentNode,
2169 const SHAPE_POLY_SET::POLYGON& aPolygon )
2170{
2171 for( size_t ii = 1; ii < aPolygon.size(); ++ii )
2172 {
2173 wxCHECK2( aPolygon[ii].PointCount() >= 3, continue );
2174
2175 wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" );
2176 wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" );
2177
2178 const std::vector<VECTOR2I>& hole = aPolygon[ii].CPoints();
2179 addXY( polybeginNode, hole[0] );
2180
2181 for( size_t jj = 1; jj < hole.size(); ++jj )
2182 {
2183 wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" );
2184 addXY( polyNode, hole[jj] );
2185 }
2186
2187 wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" );
2188 addXY( polyendNode, hole[0] );
2189 }
2190
2191 return true;
2192}
2193
2194
2195bool PCB_IO_IPC2581::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2196 int aWidth, LINE_STYLE aDashType )
2197{
2198 if( aPolySet.OutlineCount() == 0 )
2199 return false;
2200
2201 wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" );
2202
2203 // Outlines can only have one polygon according to the IPC-2581 spec, so
2204 // if there are more than one, we need to combine them into a single polygon
2205 const SHAPE_LINE_CHAIN* outline = &aPolySet.Outline( 0 );
2206 SHAPE_LINE_CHAIN bbox_outline;
2207 BOX2I bbox = outline->BBox();
2208
2209 if( aPolySet.OutlineCount() > 1 )
2210 {
2211 for( int ii = 1; ii < aPolySet.OutlineCount(); ++ii )
2212 {
2213 wxCHECK2( aPolySet.Outline( ii ).PointCount() >= 3, continue );
2214 bbox.Merge( aPolySet.Outline( ii ).BBox() );
2215 }
2216
2217 bbox_outline.Append( bbox.GetLeft(), bbox.GetTop() );
2218 bbox_outline.Append( bbox.GetRight(), bbox.GetTop() );
2219 bbox_outline.Append( bbox.GetRight(), bbox.GetBottom() );
2220 bbox_outline.Append( bbox.GetLeft(), bbox.GetBottom() );
2221 outline = &bbox_outline;
2222 }
2223
2224
2225 if( !addPolygonNode( outlineNode, *outline ) )
2226 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to outline" ) );
2227
2228 if( !outlineNode->GetChildren() )
2229 {
2230 aParentNode->RemoveChild( outlineNode );
2231 delete outlineNode;
2232 return false;
2233 }
2234
2235 addLineDesc( outlineNode, aWidth, aDashType );
2236
2237 return true;
2238}
2239
2240
2241bool PCB_IO_IPC2581::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet,
2242 int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType )
2243{
2244 if( aPolySet.OutlineCount() < ( aOutline + 1 ) )
2245 return false;
2246
2247 wxXmlNode* contourNode = appendNode( aParentNode, "Contour" );
2248
2249 if( addPolygonNode( contourNode, aPolySet.Outline( aOutline ), aFillType, aWidth, aDashType ) )
2250 {
2251 // Do not attempt to add cutouts to shapes that are already hollow
2252 if( aFillType != FILL_T::NO_FILL )
2253 addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) );
2254 }
2255 else
2256 {
2257 aParentNode->RemoveChild( contourNode );
2258 delete contourNode;
2259 return false;
2260 }
2261
2262 return true;
2263}
2264
2265
2266void PCB_IO_IPC2581::generateProfile( wxXmlNode* aStepNode )
2267{
2268 SHAPE_POLY_SET board_outline;
2269
2270 if( ! m_board->GetBoardPolygonOutlines( board_outline ) )
2271 {
2272 Report( _( "Board outline is invalid or missing. Please run DRC." ), RPT_SEVERITY_ERROR );
2273 return;
2274 }
2275
2276 wxXmlNode* profileNode = appendNode( aStepNode, "Profile" );
2277
2278 if( !addPolygonNode( profileNode, board_outline.Outline( 0 ) ) )
2279 {
2280 wxLogTrace( traceIpc2581, wxS( "Failed to add polygon to profile" ) );
2281 aStepNode->RemoveChild( profileNode );
2282 delete profileNode;
2283 }
2284}
2285
2286
2287static bool isOppositeSideSilk( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer )
2288{
2289 if( !aFootprint )
2290 return false;
2291
2292 if( aLayer != F_SilkS && aLayer != B_SilkS )
2293 return false;
2294
2295 if( aFootprint->IsFlipped() )
2296 return aLayer == F_SilkS;
2297
2298 return aLayer == B_SilkS;
2299}
2300
2301
2302wxXmlNode* PCB_IO_IPC2581::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp )
2303{
2304 std::unique_ptr<FOOTPRINT> fp( static_cast<FOOTPRINT*>( aFp->Clone() ) );
2305 fp->SetParentGroup( nullptr );
2306 fp->SetPosition( { 0, 0 } );
2307 fp->SetOrientation( ANGLE_0 );
2308
2309 size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD );
2310 wxString name = genString( wxString::Format( "%s_%zu",
2311 fp->GetFPID().GetLibItemName().wx_str(),
2312 m_footprint_dict.size() + 1 ) );
2313
2314 auto [ iter, success ] = m_footprint_dict.emplace( hash, name );
2315 addAttribute( aContentNode, "packageRef", iter->second );
2316
2317 if( !success)
2318 return nullptr;
2319
2320 // Package and Component nodes are at the same level, so we need to find the parent
2321 // which should be the Step node
2322 wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" );
2323 wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side
2324
2325 addAttribute( packageNode, "name", name );
2326 addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this
2327
2328 // We don't specially identify pin 1 in our footprints, so we need to guess
2329 if( fp->FindPadByNumber( "1" ) )
2330 addAttribute( packageNode, "pinOne", "1" );
2331 else if ( fp->FindPadByNumber( "A1" ) )
2332 addAttribute( packageNode, "pinOne", "A1" );
2333 else if ( fp->FindPadByNumber( "A" ) )
2334 addAttribute( packageNode, "pinOne", "A" );
2335 else if ( fp->FindPadByNumber( "a" ) )
2336 addAttribute( packageNode, "pinOne", "a" );
2337 else if ( fp->FindPadByNumber( "a1" ) )
2338 addAttribute( packageNode, "pinOne", "a1" );
2339 else if ( fp->FindPadByNumber( "Anode" ) )
2340 addAttribute( packageNode, "pinOne", "Anode" );
2341 else if ( fp->FindPadByNumber( "ANODE" ) )
2342 addAttribute( packageNode, "pinOne", "ANODE" );
2343 else
2344 addAttribute( packageNode, "pinOne", "UNKNOWN" );
2345
2346 addAttribute( packageNode, "pinOneOrientation", "OTHER" );
2347
2348 const SHAPE_POLY_SET& courtyard = fp->GetCourtyard( F_CrtYd );
2349 const SHAPE_POLY_SET& courtyard_back = fp->GetCourtyard( B_CrtYd );
2350
2351 if( courtyard.OutlineCount() > 0 )
2352 addOutlineNode( packageNode, courtyard, courtyard.Outline( 0 ).Width(), LINE_STYLE::SOLID );
2353
2354 if( courtyard_back.OutlineCount() > 0 )
2355 {
2356 if( m_version > 'B' )
2357 {
2358 otherSideViewNode = appendNode( packageNode, "OtherSideView" );
2359 addOutlineNode( otherSideViewNode, courtyard_back, courtyard_back.Outline( 0 ).Width(),
2361 }
2362 }
2363
2364 if( !courtyard.OutlineCount() && !courtyard_back.OutlineCount() )
2365 {
2366 SHAPE_POLY_SET bbox = fp->GetBoundingHull();
2367 addOutlineNode( packageNode, bbox );
2368 }
2369
2370 wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" );
2371 addAttribute( pickupPointNode, "x", "0.0" );
2372 addAttribute( pickupPointNode, "y", "0.0" );
2373
2374 std::map<PCB_LAYER_ID, std::map<bool, std::vector<BOARD_ITEM*>>> elements;
2375
2376 for( BOARD_ITEM* item : fp->GraphicalItems() )
2377 {
2378 PCB_LAYER_ID layer = item->GetLayer();
2379
2383 if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab )
2384 continue;
2385
2386 if( m_version == 'B' && isOppositeSideSilk( fp.get(), layer ) )
2387 continue;
2388
2389 bool is_abs = true;
2390
2391 if( item->Type() == PCB_SHAPE_T )
2392 {
2393 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2394
2395 // Circles and Rectanges only have size information so we need to place them in
2396 // a separate node that has a location
2397 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE )
2398 is_abs = false;
2399 }
2400
2401 elements[item->GetLayer()][is_abs].push_back( item );
2402 }
2403
2404 auto add_base_node =
2405 [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode*
2406 {
2407 wxXmlNode* parent = packageNode;
2408 bool is_back = aLayer == B_SilkS || aLayer == B_Fab;
2409
2410 if( is_back && m_version > 'B' )
2411 {
2412 if( !otherSideViewNode )
2413 otherSideViewNode = new wxXmlNode( wxXML_ELEMENT_NODE, "OtherSideView" );
2414
2415 parent = otherSideViewNode;
2416 }
2417
2418 wxString name;
2419
2420 if( aLayer == F_SilkS || aLayer == B_SilkS )
2421 name = "SilkScreen";
2422 else if( aLayer == F_Fab || aLayer == B_Fab )
2423 name = "AssemblyDrawing";
2424 else
2425 wxASSERT( false );
2426
2427 wxXmlNode* new_node = appendNode( parent, name );
2428 return new_node;
2429 };
2430
2431 auto add_marking_node =
2432 [&]( wxXmlNode* aNode ) -> wxXmlNode*
2433 {
2434 wxXmlNode* marking_node = appendNode( aNode, "Marking" );
2435 addAttribute( marking_node, "markingUsage", "NONE" );
2436 return marking_node;
2437 };
2438
2439 std::map<PCB_LAYER_ID, wxXmlNode*> layer_nodes;
2440 std::map<PCB_LAYER_ID, BOX2I> layer_bbox;
2441
2442 for( auto layer : { F_Fab, B_Fab } )
2443 {
2444 if( elements.find( layer ) != elements.end() )
2445 {
2446 if( elements[layer][true].size() > 0 )
2447 layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox();
2448 else if( elements[layer][false].size() > 0 )
2449 layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox();
2450 }
2451 }
2452
2453 for( auto& [layer, map] : elements )
2454 {
2455 wxXmlNode* layer_node = add_base_node( layer );
2456 wxXmlNode* marking_node = add_marking_node( layer_node );
2457 wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" );
2458 bool update_bbox = false;
2459
2460 if( layer == F_Fab || layer == B_Fab )
2461 {
2462 layer_nodes[layer] = layer_node;
2463 update_bbox = true;
2464 }
2465
2466 for( auto& [is_abs, vec] : map )
2467 {
2468 for( BOARD_ITEM* item : vec )
2469 {
2470 wxXmlNode* output_node = nullptr;
2471
2472 if( update_bbox )
2473 layer_bbox[layer].Merge( item->GetBoundingBox() );
2474
2475 if( !is_abs )
2476 output_node = add_marking_node( layer_node );
2477 else
2478 output_node = group_node;
2479
2480 switch( item->Type() )
2481 {
2482 case PCB_TEXT_T:
2483 {
2484 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
2485
2486 if( text->IsKnockout() )
2487 addKnockoutText( output_node, text );
2488 else
2489 addText( output_node, text, text->GetFontMetrics() );
2490
2491 break;
2492 }
2493
2494 case PCB_TEXTBOX_T:
2495 {
2496 PCB_TEXTBOX* text = static_cast<PCB_TEXTBOX*>( item );
2497 addText( output_node, text, text->GetFontMetrics() );
2498
2499 // We want to force this to be a polygon to get absolute coordinates
2500 if( text->IsBorderEnabled() )
2501 {
2502 SHAPE_POLY_SET poly_set;
2503 text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE );
2504 addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL,
2505 text->GetBorderWidth() );
2506 }
2507
2508 break;
2509 }
2510
2511 case PCB_SHAPE_T:
2512 {
2513 if( !is_abs )
2514 addLocationNode( output_node, *static_cast<PCB_SHAPE*>( item ) );
2515
2516 addShape( output_node, *static_cast<PCB_SHAPE*>( item ) );
2517
2518 break;
2519 }
2520
2521 default: break;
2522 }
2523 }
2524 }
2525
2526 if( group_node->GetChildren() == nullptr )
2527 {
2528 marking_node->RemoveChild( group_node );
2529 layer_node->RemoveChild( marking_node );
2530 delete group_node;
2531 delete marking_node;
2532 }
2533 }
2534
2535 for( auto&[layer, bbox] : layer_bbox )
2536 {
2537 if( bbox.GetWidth() > 0 )
2538 {
2539 wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" );
2540
2541 SHAPE_LINE_CHAIN outline;
2542 std::vector<VECTOR2I> points( 4 );
2543 points[0] = bbox.GetPosition();
2544 points[2] = bbox.GetEnd();
2545 points[1].x = points[0].x;
2546 points[1].y = points[2].y;
2547 points[3].x = points[2].x;
2548 points[3].y = points[0].y;
2549
2550 outline.Append( points );
2551 addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 );
2552 addLineDesc( outlineNode, 0, LINE_STYLE::SOLID );
2553 }
2554 }
2555
2556 std::map<wxString, wxXmlNode*> pin_nodes;
2557
2558 for( size_t ii = 0; ii < fp->Pads().size(); ++ii )
2559 {
2560 PAD* pad = fp->Pads()[ii];
2561 wxString name = pinName( pad );
2562 wxXmlNode* pinNode = nullptr;
2563
2564 auto [ it, inserted ] = pin_nodes.emplace( name, nullptr );
2565
2566 if( inserted )
2567 {
2568 pinNode = appendNode( packageNode, "Pin" );
2569 it->second = pinNode;
2570
2571 addAttribute( pinNode, "number", name );
2572
2573 m_net_pin_dict[pad->GetNetCode()].emplace_back(
2574 genString( fp->GetReference(), "CMP" ), name );
2575
2576 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
2577 addAttribute( pinNode, "electricalType", "MECHANICAL" );
2578 else if( pad->IsOnCopperLayer() )
2579 addAttribute( pinNode, "electricalType", "ELECTRICAL" );
2580 else
2581 addAttribute( pinNode, "electricalType", "UNDEFINED" );
2582
2583 if( pad->HasHole() )
2584 addAttribute( pinNode, "type", "THRU" );
2585 else
2586 addAttribute( pinNode, "type", "SURFACE" );
2587
2588 if( pad->GetFPRelativeOrientation() != ANGLE_0 )//|| fp->IsFlipped() )
2589 {
2590 wxXmlNode* xformNode = appendNode( pinNode, "Xform" );
2591 EDA_ANGLE pad_angle = pad->GetFPRelativeOrientation().Normalize();
2592
2593 if( fp->IsFlipped() )
2594 pad_angle = ( pad_angle.Invert() - ANGLE_180 ).Normalize();
2595
2596 if( pad_angle != ANGLE_0 )
2597 xformNode->AddAttribute( "rotation", floatVal( pad_angle.AsDegrees() ) );
2598 }
2599 }
2600 else
2601 {
2602 pinNode = it->second;
2603 }
2604
2605 addLocationNode( pinNode, *pad, true );
2606 addShape( pinNode, *pad, pad->GetLayer() );
2607
2608 // We just need the padstack, we don't need the reference here. The reference will be
2609 // created in the LayerFeature set
2610 wxXmlNode dummy;
2611 addPadStack( &dummy, pad );
2612 }
2613
2614 return packageNode;
2615}
2616
2617
2618void PCB_IO_IPC2581::generateComponents( wxXmlNode* aStepNode )
2619{
2620 std::vector<wxXmlNode*> componentNodes;
2621 std::vector<wxXmlNode*> packageNodes;
2622 std::set<wxString> packageNames;
2623
2624 bool generate_unique = m_OEMRef.empty();
2625
2626 for( FOOTPRINT* fp : m_board->Footprints() )
2627 {
2628 wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" );
2629 addAttribute( componentNode, "refDes", componentName( fp ) );
2630 wxXmlNode* pkg = addPackage( componentNode, fp );
2631
2632 if( pkg )
2633 packageNodes.push_back( pkg );
2634
2635 wxString name;
2636
2637 PCB_FIELD* field = nullptr;
2638
2639 if( !generate_unique )
2640 field = fp->GetField( m_OEMRef );
2641
2642 if( field && !field->GetText().empty() )
2643 {
2644 name = field->GetShownText( false );
2645 }
2646 else
2647 {
2648 name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(),
2649 fp->GetFPID().GetLibItemName().wx_str(),
2650 fp->GetValue() );
2651 }
2652
2653 if( !m_OEMRef_dict.emplace( fp, name ).second )
2654 Report( _( "Duplicate footprint pointers encountered; IPC-2581 output may be incorrect." ),
2656
2657 addAttribute( componentNode, "part", genString( name, "REF" ) );
2658 addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] );
2659
2660 if( fp->GetAttributes() & FP_THROUGH_HOLE )
2661 addAttribute( componentNode, "mountType", "THMT" );
2662 else if( fp->GetAttributes() & FP_SMD )
2663 addAttribute( componentNode, "mountType", "SMT" );
2664 else
2665 addAttribute( componentNode, "mountType", "OTHER" );
2666
2667 if( fp->GetOrientation() != ANGLE_0 || fp->IsFlipped() )
2668 {
2669 wxXmlNode* xformNode = appendNode( componentNode, "Xform" );
2670
2671 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
2672
2673 if( fp->IsFlipped() )
2674 fp_angle = ( fp_angle.Invert() - ANGLE_180 ).Normalize();
2675
2676 if( fp_angle != ANGLE_0 )
2677 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
2678
2679 if( fp->IsFlipped() )
2680 addAttribute( xformNode, "mirror", "true" );
2681 }
2682
2683 addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y );
2684
2685 componentNodes.push_back( componentNode );
2686 }
2687
2688 for( wxXmlNode* padstack : m_padstacks )
2689 {
2690 insertNode( aStepNode, padstack );
2691 m_last_padstack = padstack;
2692 }
2693
2694 for( wxXmlNode* pkg : packageNodes )
2695 aStepNode->AddChild( pkg );
2696
2697 for( wxXmlNode* cmp : componentNodes )
2698 aStepNode->AddChild( cmp );
2699}
2700
2701
2702void PCB_IO_IPC2581::generateLogicalNets( wxXmlNode* aStepNode )
2703{
2704 for( auto& [ net, pin_pair] : m_net_pin_dict )
2705 {
2706 wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" );
2707 addAttribute( netNode, "name",
2708 genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ;
2709
2710 for( auto& [cmp, pin] : pin_pair )
2711 {
2712 wxXmlNode* netPinNode = appendNode( netNode, "PinRef" );
2713 addAttribute( netPinNode, "componentRef", cmp );
2714 addAttribute( netPinNode, "pin", pin );
2715 }
2716 //TODO: Finish
2717 }
2718}
2719
2720//TODO: Add PhyNetGroup section
2721
2722void PCB_IO_IPC2581::generateLayerFeatures( wxXmlNode* aStepNode )
2723{
2724 LSEQ layers = m_board->GetEnabledLayers().Seq();
2725 const NETINFO_LIST& nets = m_board->GetNetInfo();
2726 std::vector<std::unique_ptr<FOOTPRINT>> footprints;
2727
2728 // To avoid the overhead of repeatedly cycling through the layers and nets,
2729 // we pre-sort the board items into a map of layer -> net -> items
2730 std::map<PCB_LAYER_ID, std::map<int, std::vector<BOARD_ITEM*>>> elements;
2731
2732 std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(),
2733 [&layers, &elements]( PCB_TRACK* aTrack )
2734 {
2735 if( aTrack->Type() == PCB_VIA_T )
2736 {
2737 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
2738
2739 for( PCB_LAYER_ID layer : layers )
2740 {
2741 if( via->FlashLayer( layer ) )
2742 elements[layer][via->GetNetCode()].push_back( via );
2743 }
2744 }
2745 else
2746 {
2747 elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack );
2748 }
2749 } );
2750
2751 std::for_each( m_board->Zones().begin(), m_board->Zones().end(),
2752 [ &elements ]( ZONE* zone )
2753 {
2754 LSEQ zone_layers = zone->GetLayerSet().Seq();
2755
2756 for( PCB_LAYER_ID layer : zone_layers )
2757 elements[layer][zone->GetNetCode()].push_back( zone );
2758 } );
2759
2760 for( BOARD_ITEM* item : m_board->Drawings() )
2761 {
2762 if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
2763 elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it );
2764 else
2765 elements[item->GetLayer()][0].push_back( item );
2766 }
2767
2768 for( FOOTPRINT* fp : m_board->Footprints() )
2769 {
2770 for( PCB_FIELD* field : fp->GetFields() )
2771 elements[field->GetLayer()][0].push_back( field );
2772
2773 for( BOARD_ITEM* item : fp->GraphicalItems() )
2774 elements[item->GetLayer()][0].push_back( item );
2775
2776 for( PAD* pad : fp->Pads() )
2777 {
2778 LSEQ pad_layers = pad->GetLayerSet().Seq();
2779
2780 for( PCB_LAYER_ID layer : pad_layers )
2781 {
2782 if( pad->FlashLayer( layer ) )
2783 elements[layer][pad->GetNetCode()].push_back( pad );
2784 }
2785 }
2786 }
2787
2788 for( PCB_LAYER_ID layer : layers )
2789 {
2790 if( m_progressReporter )
2791 m_progressReporter->SetMaxProgress( nets.GetNetCount() * layers.size() );
2792
2793 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
2794 addAttribute( layerNode, "layerRef", m_layer_name_map[layer] );
2795
2796 auto process_net = [&] ( int net )
2797 {
2798 std::vector<BOARD_ITEM*>& vec = elements[layer][net];
2799
2800 if( vec.empty() )
2801 return;
2802
2803 std::stable_sort( vec.begin(), vec.end(),
2804 []( BOARD_ITEM* a, BOARD_ITEM* b )
2805 {
2806 if( a->GetParentFootprint() == b->GetParentFootprint() )
2807 return a->Type() < b->Type();
2808
2809 return a->GetParentFootprint() < b->GetParentFootprint();
2810 } );
2811
2812 generateLayerSetNet( layerNode, layer, vec );
2813 };
2814
2815 for( const NETINFO_ITEM* net : nets )
2816 {
2817 if( m_progressReporter )
2818 {
2819 m_progressReporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ),
2820 m_board->GetLayerName( layer ),
2821 net->GetNetname() ) );
2822 m_progressReporter->AdvanceProgress();
2823 }
2824
2825 process_net( net->GetNetCode() );
2826 }
2827
2828 if( layerNode->GetChildren() == nullptr )
2829 {
2830 aStepNode->RemoveChild( layerNode );
2831 delete layerNode;
2832 }
2833 }
2834}
2835
2836
2837void PCB_IO_IPC2581::generateLayerSetDrill( wxXmlNode* aLayerNode )
2838{
2839 int hole_count = 1;
2840
2841 for( const auto& [layers, vec] : m_drill_layers )
2842 {
2843 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
2844 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "DRILL" ) );
2845
2846 for( BOARD_ITEM* item : vec )
2847 {
2848 if( item->Type() == PCB_VIA_T )
2849 {
2850 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2851 auto it = m_padstack_dict.find( hash_fp_item( via, 0 ) );
2852
2853 if( it == m_padstack_dict.end() )
2854 {
2855 Report( _( "Via uses unsupported padstack; omitted from drill data." ),
2857 continue;
2858 }
2859
2860 wxXmlNode* padNode = appendNode( layerNode, "Set" );
2861 addAttribute( padNode, "geometry", it->second );
2862
2863 if( via->GetNetCode() > 0 )
2864 addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) );
2865
2866 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
2867 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
2868 addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) );
2869 addAttribute( holeNode, "platingStatus", "VIA" );
2870 addAttribute( holeNode, "plusTol", "0.0" );
2871 addAttribute( holeNode, "minusTol", "0.0" );
2872 addXY( holeNode, via->GetPosition() );
2873 }
2874 else if( item->Type() == PCB_PAD_T )
2875 {
2876 PAD* pad = static_cast<PAD*>( item );
2877 auto it = m_padstack_dict.find( hash_fp_item( pad, 0 ) );
2878
2879 if( it == m_padstack_dict.end() )
2880 {
2881 Report( _( "Pad uses unsupported padstack; hole was omitted from drill data." ),
2883 continue;
2884 }
2885
2886 wxXmlNode* padNode = appendNode( layerNode, "Set" );
2887 addAttribute( padNode, "geometry", it->second );
2888
2889 if( pad->GetNetCode() > 0 )
2890 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
2891
2892 wxXmlNode* holeNode = appendNode( padNode, "Hole" );
2893 addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) );
2894 addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) );
2895 addAttribute( holeNode, "platingStatus",
2896 pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" );
2897 addAttribute( holeNode, "plusTol", "0.0" );
2898 addAttribute( holeNode, "minusTol", "0.0" );
2899 addXY( holeNode, pad->GetPosition() );
2900 }
2901 }
2902 }
2903
2904 hole_count = 1;
2905
2906 for( const auto& [layers, vec] : m_slot_holes )
2907 {
2908 wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" );
2909 layerNode->AddAttribute( "layerRef", genLayersString( layers.first, layers.second, "SLOT" ) );
2910
2911 for( PAD* pad : vec )
2912 {
2913 wxXmlNode* padNode = appendNode( layerNode, "Set" );
2914
2915 if( pad->GetNetCode() > 0 )
2916 addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) );
2917
2918 addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) );
2919 }
2920 }
2921}
2922
2923
2924void PCB_IO_IPC2581::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer,
2925 std::vector<BOARD_ITEM*>& aItems )
2926{
2927 auto it = aItems.begin();
2928 wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" );
2929 wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" );
2930 wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" );
2931
2932 bool has_via = false;
2933 bool has_pad = false;
2934
2935 wxXmlNode* padSetNode = nullptr;
2936
2937 wxXmlNode* viaSetNode = nullptr;
2938
2939 wxXmlNode* teardropLayerSetNode = nullptr;
2940 wxXmlNode* teardropFeatureSetNode = nullptr;
2941
2942 bool teardrop_warning = false;
2943
2944 if( BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( *it );
2945 IsCopperLayer( aLayer ) && item )
2946 {
2947 if( item->GetNetCode() > 0 )
2948 addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) );
2949 }
2950
2951 auto add_track =
2952 [&]( PCB_TRACK* track )
2953 {
2954 if( track->Type() == PCB_TRACE_T )
2955 {
2956 PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT );
2957 shape.SetStart( track->GetStart() );
2958 shape.SetEnd( track->GetEnd() );
2959 shape.SetWidth( track->GetWidth() );
2960 addShape( specialNode, shape );
2961 }
2962 else if( track->Type() == PCB_ARC_T )
2963 {
2964 PCB_ARC* arc = static_cast<PCB_ARC*>( track );
2965 PCB_SHAPE shape( nullptr, SHAPE_T::ARC );
2966 shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() );
2967 shape.SetWidth( arc->GetWidth() );
2968 addShape( specialNode, shape );
2969 }
2970 else
2971 {
2972 if( !viaSetNode )
2973 {
2974 if( !has_pad )
2975 {
2976 viaSetNode = layerSetNode;
2977 has_via = true;
2978 }
2979 else
2980 {
2981 viaSetNode = appendNode( layerSetNode, "Set" );
2982
2983 if( track->GetNetCode() > 0 )
2984 addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) );
2985 }
2986
2987 addAttribute( viaSetNode, "padUsage", "VIA" );
2988 }
2989
2990 addVia( viaSetNode, static_cast<PCB_VIA*>( track ), aLayer );
2991 }
2992 };
2993
2994 auto add_zone =
2995 [&]( ZONE* zone )
2996 {
2997 wxXmlNode* zoneFeatureNode = specialNode;
2998
2999 if( zone->IsTeardropArea() )
3000 {
3001 if( m_version > 'B' )
3002 {
3003 if( !teardropFeatureSetNode )
3004 {
3005 teardropLayerSetNode = appendNode( aLayerNode, "Set" );
3006 addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" );
3007
3008 if( zone->GetNetCode() > 0 )
3009 {
3010 addAttribute( teardropLayerSetNode, "net",
3011 genString( zone->GetNetname(), "NET" ) );
3012 }
3013
3014 wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" );
3015 addLocationNode( new_teardrops, 0.0, 0.0 );
3016 teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" );
3017 }
3018
3019 zoneFeatureNode = teardropFeatureSetNode;
3020 }
3021 else if( !teardrop_warning )
3022 {
3023 Report( _( "Teardrops are not supported in IPC-2581 revision B; they were exported as zones." ),
3025 teardrop_warning = true;
3026 }
3027 }
3028 else
3029 {
3030 if( FOOTPRINT* fp = zone->GetParentFootprint() )
3031 {
3032 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3033 wxString refDes = componentName( zone->GetParentFootprint() );
3034 addAttribute( tempSetNode, "componentRef", refDes );
3035 wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" );
3036 addLocationNode( newFeatures, 0.0, 0.0 );
3037 zoneFeatureNode = appendNode( newFeatures, "UserSpecial" );
3038 }
3039 }
3040
3041 SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer );
3042
3043 for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii )
3044 addContourNode( zoneFeatureNode, zone_shape, ii );
3045 };
3046
3047 auto add_shape =
3048 [&] ( PCB_SHAPE* shape )
3049 {
3050 FOOTPRINT* fp = shape->GetParentFootprint();
3051
3052 if( fp )
3053 {
3054 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3055
3056 if( m_version > 'B' )
3057 addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" );
3058
3059 bool link_to_component = true;
3060
3061 if( m_version == 'B' && isOppositeSideSilk( fp, shape->GetLayer() ) )
3062 link_to_component = false;
3063
3064 if( link_to_component )
3065 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3066
3067 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3068 addLocationNode( tempFeature, *shape );
3069
3070 EDA_ANGLE fp_angle = fp->GetOrientation().Normalize();
3071
3072 if( fp_angle != ANGLE_0 )
3073 {
3074 wxXmlNode* xformNode = appendNode( tempFeature, "Xform" );
3075 addAttribute( xformNode, "rotation", floatVal( fp_angle.AsDegrees(), 2 ) );
3076 }
3077
3078 addShape( tempFeature, *shape );
3079 }
3080 else if( shape->GetShape() == SHAPE_T::CIRCLE
3081 || shape->GetShape() == SHAPE_T::RECTANGLE
3082 || shape->GetShape() == SHAPE_T::POLY )
3083 {
3084 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3085
3086 if( shape->GetNetCode() > 0 )
3087 addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) );
3088
3089 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3090 addLocationNode( tempFeature, *shape );
3091 addShape( tempFeature, *shape );
3092 }
3093 else
3094 {
3095 addShape( specialNode, *shape );
3096 }
3097 };
3098
3099 auto add_text =
3100 [&] ( BOARD_ITEM* text )
3101 {
3102 EDA_TEXT* text_item;
3103 FOOTPRINT* fp = text->GetParentFootprint();
3104
3105 if( PCB_TEXT* tmp_text = dynamic_cast<PCB_TEXT*>( text ) )
3106 text_item = static_cast<EDA_TEXT*>( tmp_text );
3107 else if( PCB_TEXTBOX* tmp_text = dynamic_cast<PCB_TEXTBOX*>( text ) )
3108 text_item = static_cast<EDA_TEXT*>( tmp_text );
3109
3110 if( !text_item->IsVisible() || text_item->GetShownText( false ).empty() )
3111 return;
3112
3113 wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" );
3114
3115 if( m_version > 'B' )
3116 addAttribute( tempSetNode, "geometryUsage", "TEXT" );
3117
3118 bool link_to_component = fp != nullptr;
3119
3120 if( m_version == 'B' && fp && isOppositeSideSilk( fp, text->GetLayer() ) )
3121 link_to_component = false;
3122
3123 if( link_to_component )
3124 addAttribute( tempSetNode, "componentRef", componentName( fp ) );
3125
3126 wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" );
3127 addAttribute( nonStandardAttributeNode, "name", "TEXT" );
3128 addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) );
3129 addAttribute( nonStandardAttributeNode, "type", "STRING" );
3130
3131 wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" );
3132 addLocationNode( tempFeature, 0.0, 0.0 );
3133
3134 if( text->Type() == PCB_TEXT_T && static_cast<PCB_TEXT*>( text )->IsKnockout() )
3135 addKnockoutText( tempFeature, static_cast<PCB_TEXT*>( text ) );
3136 else
3137 addText( tempFeature, text_item, text->GetFontMetrics() );
3138
3139 if( text->Type() == PCB_TEXTBOX_T )
3140 {
3141 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( text );
3142
3143 if( textbox->IsBorderEnabled() )
3144 addShape( tempFeature, *static_cast<PCB_SHAPE*>( textbox ) );
3145 }
3146 };
3147
3148 auto add_pad =
3149 [&]( PAD* pad )
3150 {
3151 if( !padSetNode )
3152 {
3153 if( !has_via )
3154 {
3155 padSetNode = layerSetNode;
3156 has_pad = true;
3157 }
3158 else
3159 {
3160 padSetNode = appendNode( aLayerNode, "Set" );
3161
3162 if( pad->GetNetCode() > 0 )
3163 addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) );
3164 }
3165 }
3166
3167 FOOTPRINT* fp = pad->GetParentFootprint();
3168
3169 if( fp && fp->IsFlipped() )
3170 addPad( padSetNode, pad, FlipLayer( aLayer ) );
3171 else
3172 addPad( padSetNode, pad, aLayer );
3173 };
3174
3175 for( BOARD_ITEM* item : aItems )
3176 {
3177 switch( item->Type() )
3178 {
3179 case PCB_TRACE_T:
3180 case PCB_ARC_T:
3181 case PCB_VIA_T:
3182 add_track( static_cast<PCB_TRACK*>( item ) );
3183 break;
3184
3185 case PCB_ZONE_T:
3186 add_zone( static_cast<ZONE*>( item ) );
3187 break;
3188
3189 case PCB_PAD_T:
3190 add_pad( static_cast<PAD*>( item ) );
3191 break;
3192
3193 case PCB_SHAPE_T:
3194 add_shape( static_cast<PCB_SHAPE*>( item ) );
3195 break;
3196
3197 case PCB_TEXT_T:
3198 case PCB_TEXTBOX_T:
3199 case PCB_FIELD_T:
3200 add_text( item );
3201 break;
3202
3203 case PCB_DIMENSION_T:
3204 case PCB_TARGET_T:
3205 case PCB_DIM_ALIGNED_T:
3206 case PCB_DIM_LEADER_T:
3207 case PCB_DIM_CENTER_T:
3208 case PCB_DIM_RADIAL_T:
3210 //TODO: Add support for dimensions
3211 break;
3212
3213 default:
3214 wxLogTrace( traceIpc2581, wxS( "Unhandled type %s" ),
3215 ENUM_MAP<KICAD_T>::Instance().ToString( item->Type() ) );
3216 }
3217 }
3218
3219 if( specialNode->GetChildren() == nullptr )
3220 {
3221 featureSetNode->RemoveChild( specialNode );
3222 delete specialNode;
3223 }
3224
3225 if( featureSetNode->GetChildren() == nullptr )
3226 {
3227 layerSetNode->RemoveChild( featureSetNode );
3228 delete featureSetNode;
3229 }
3230
3231 if( layerSetNode->GetChildren() == nullptr )
3232 {
3233 aLayerNode->RemoveChild( layerSetNode );
3234 delete layerSetNode;
3235 }
3236}
3237
3239{
3240 int hole_count = 1;
3241
3242 for( const auto& [layers, vec] : m_auxilliary_Layers )
3243 {
3244 hole_count = 1;
3245 bool add_node = true;
3246
3247 wxString name;
3248 bool hole = false;
3249
3250 // clang-format off: suggestion is inconsitent
3251 switch( std::get<0>(layers) )
3252 {
3254 name = "COVERING";
3255 break;
3257 name = "PLUGGING";
3258 hole = true;
3259 break;
3261 name = "TENTING";
3262 break;
3264 name = "FILLING";
3265 hole = true;
3266 break;
3268 name = "CAPPING";
3269 hole = true;
3270 break;
3271 default:
3272 add_node = false;
3273 break;
3274 }
3275 // clang-format on: suggestion is inconsitent
3276
3277 if( !add_node )
3278 continue;
3279
3280 wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" );
3281 if( std::get<2>( layers ) == UNDEFINED_LAYER )
3282 layerNode->AddAttribute( "layerRef", genLayerString( std::get<1>( layers ), TO_UTF8( name ) ) );
3283 else
3284 layerNode->AddAttribute( "layerRef", genLayersString( std::get<1>( layers ),
3285 std::get<2>( layers ), TO_UTF8( name ) ) );
3286
3287 for( BOARD_ITEM* item : vec )
3288 {
3289 if( item->Type() == PCB_VIA_T )
3290 {
3291 PCB_VIA* via = static_cast<PCB_VIA*>( item );
3292
3293 PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE );
3294
3295 if( hole )
3296 shape.SetEnd( { KiROUND( via->GetDrillValue() / 2.0 ), 0 } );
3297 else
3298 shape.SetEnd( { KiROUND( via->GetWidth( std::get<1>( layers ) ) / 2.0 ), 0 } );
3299
3300 wxXmlNode* padNode = appendNode( layerNode, "Pad" );
3301 addPadStack( padNode, via );
3302
3303 addLocationNode( padNode, 0.0, 0.0 );
3304 addShape( padNode, shape );
3305 }
3306 }
3307 }
3308}
3309
3310
3312{
3313 if( m_progressReporter )
3314 m_progressReporter->AdvancePhase( _( "Generating BOM section" ) );
3315
3316 wxXmlNode* avl = appendNode( m_xml_root, "Avl" );
3317 addAttribute( avl, "name", "Primary_Vendor_List" );
3318
3319 wxXmlNode* header = appendNode( avl, "AvlHeader" );
3320 addAttribute( header, "title", "BOM" );
3321 addAttribute( header, "source", "KiCad" );
3322 addAttribute( header, "author", "OWNER" );
3323 addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() );
3324 addAttribute( header, "version", "1" );
3325
3326 std::set<wxString> unique_parts;
3327 std::map<wxString,wxString> unique_vendors;
3328
3329 for( auto& [fp, name] : m_OEMRef_dict )
3330 {
3331 auto [ it, success ] = unique_parts.insert( name );
3332
3333 if( !success )
3334 continue;
3335
3336 wxXmlNode* part = appendNode( avl, "AvlItem" );
3337 addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) );
3338
3339 PCB_FIELD* nums[2] = { fp->GetField( m_mpn ), fp->GetField( m_distpn ) };
3340 PCB_FIELD* company[2] = { fp->GetField( m_mfg ), nullptr };
3341 wxString company_name[2] = { m_mfg, m_dist };
3342
3343 for ( int ii = 0; ii < 2; ++ii )
3344 {
3345 if( nums[ii] )
3346 {
3347 wxString mpn_name = nums[ii]->GetShownText( false );
3348
3349 if( mpn_name.empty() )
3350 continue;
3351
3352 wxXmlNode* vmpn = appendNode( part, "AvlVmpn" );
3353 addAttribute( vmpn, "qualified", "false" );
3354 addAttribute( vmpn, "chosen", "false" );
3355
3356 wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" );
3357 addAttribute( mpn, "name", mpn_name );
3358
3359 wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" );
3360
3361 wxString name = wxT( "UNKNOWN" );
3362
3363 // If the field resolves, then use that field content unless it is empty
3364 if( !ii && company[ii] )
3365 {
3366 wxString tmp = company[ii]->GetShownText( false );
3367
3368 if( !tmp.empty() )
3369 name = tmp;
3370 }
3371 // If it doesn't resolve but there is content from the dialog, use the static content
3372 else if( !ii && !company_name[ii].empty() )
3373 {
3374 name = company_name[ii];
3375 }
3376 else if( ii && !m_dist.empty() )
3377 {
3378 name = m_dist;
3379 }
3380
3381 auto [vendor_id, inserted] = unique_vendors.emplace(
3382 name,
3383 wxString::Format( "VENDOR_%zu", unique_vendors.size() ) );
3384
3385 addAttribute( vendor, "enterpriseRef", vendor_id->second );
3386
3387 if( inserted )
3388 {
3389 wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" );
3390 addAttribute( new_vendor, "id", vendor_id->second );
3391 addAttribute( new_vendor, "name", name );
3392 addAttribute( new_vendor, "code", "NONE" );
3393 insertNodeAfter( m_enterpriseNode, new_vendor );
3394 m_enterpriseNode = new_vendor;
3395 }
3396 }
3397 }
3398 }
3399
3400 return avl;
3401}
3402
3403
3404void PCB_IO_IPC2581::SaveBoard( const wxString& aFileName, BOARD* aBoard,
3405 const std::map<std::string, UTF8>* aProperties )
3406{
3407 m_board = aBoard;
3408 m_units_str = "MILLIMETER";
3409 m_scale = 1.0 / PCB_IU_PER_MM;
3410 m_sigfig = 6;
3411
3412 if( auto it = aProperties->find( "units" ); it != aProperties->end() )
3413 {
3414 if( it->second == "inch" )
3415 {
3416 m_units_str = "INCH";
3417 m_scale = ( 1.0 / 25.4 ) / PCB_IU_PER_MM;
3418 }
3419 }
3420
3421 if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() )
3422 m_sigfig = std::stoi( it->second );
3423
3424 if( auto it = aProperties->find( "version" ); it != aProperties->end() )
3425 m_version = it->second.c_str()[0];
3426
3427 if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() )
3428 m_OEMRef = it->second.wx_str();
3429
3430 if( auto it = aProperties->find( "mpn" ); it != aProperties->end() )
3431 m_mpn = it->second.wx_str();
3432
3433 if( auto it = aProperties->find( "mfg" ); it != aProperties->end() )
3434 m_mfg = it->second.wx_str();
3435
3436 if( auto it = aProperties->find( "dist" ); it != aProperties->end() )
3437 m_dist = it->second.wx_str();
3438
3439 if( auto it = aProperties->find( "distpn" ); it != aProperties->end() )
3440 m_distpn = it->second.wx_str();
3441
3442 if( m_version == 'B' )
3443 {
3444 for( char c = 'a'; c <= 'z'; ++c )
3445 m_acceptable_chars.insert( c );
3446
3447 for( char c = 'A'; c <= 'Z'; ++c )
3448 m_acceptable_chars.insert( c );
3449
3450 for( char c = '0'; c <= '9'; ++c )
3451 m_acceptable_chars.insert( c );
3452
3453 // Add special characters
3454 std::string specialChars = "_\\-.+><";
3455
3456 for( char c : specialChars )
3457 m_acceptable_chars.insert( c );
3458 }
3459
3460 m_xml_doc = new wxXmlDocument();
3462
3464
3465 if( m_progressReporter )
3466 {
3467 m_progressReporter->SetNumPhases( 7 );
3468 m_progressReporter->BeginPhase( 1 );
3469 m_progressReporter->Report( _( "Generating logistic section" ) );
3470 }
3471
3474
3475 wxXmlNode* ecad_node = generateEcadSection();
3476 generateBOMSection( ecad_node );
3478
3479 if( m_progressReporter )
3480 {
3481 m_progressReporter->AdvancePhase( _( "Saving file" ) );
3482 }
3483
3484 wxFileOutputStreamWithProgress out_stream( aFileName );
3485 double written_bytes = 0.0;
3486 double last_yield = 0.0;
3487
3488 // This is a rough estimation of the size of the spaces in the file
3489 // We just need to total to be slightly larger than the value of the
3490 // progress bar, so accurately counting spaces is not terribly important
3492
3493 auto update_progress = [&]( size_t aBytes )
3494 {
3495 written_bytes += aBytes;
3496 double percent = written_bytes / static_cast<double>( m_total_bytes );
3497
3498 if( m_progressReporter )
3499 {
3500 // Only update every percent
3501 if( last_yield + 0.01 < percent )
3502 {
3503 last_yield = percent;
3504 m_progressReporter->SetCurrentProgress( percent );
3505 }
3506 }
3507 };
3508
3509 out_stream.SetProgressCallback( update_progress );
3510
3511 if( !m_xml_doc->Save( out_stream ) )
3512 {
3513 Report( _( "Failed to save IPC-2581 data to buffer." ), RPT_SEVERITY_ERROR );
3514 return;
3515 }
3516
3517 size_t size = out_stream.GetSize();
3518}
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:322
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double AsDegrees() const
Definition eda_angle.h:116
EDA_ANGLE Invert() const
Definition eda_angle.h:173
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:258
FILL_T GetFillMode() const
Definition eda_shape.h:142
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:336
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:721
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:232
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:238
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:212
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:2691
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
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
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition pad.cpp:2007
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition pad.h:264
const VECTOR2I & GetMid() const
Definition pcb_track.h:347
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:573
const VECTOR2I & GetStart() const
Definition pcb_track.h:154
const VECTOR2I & GetEnd() const
Definition pcb_track.h:151
virtual int GetWidth() const
Definition pcb_track.h:148
PCB_LAYER_ID BottomLayer() const
VECTOR2I GetPosition() const override
Definition pcb_track.h:586
bool FlashLayer(int aLayer) const
Check to see whether the via should have a pad on the specific layer.
const PADSTACK & Padstack() const
Definition pcb_track.h:463
int GetWidth() const override
PCB_LAYER_ID TopLayer() const
int GetDrillValue() const
Calculate the drill value for vias (m_drill if > 0, or default drill value for the board).
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Width() const
Get the current width of the segments in the chain.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
int GetWidth() const
LINE_STYLE GetLineStyle() const
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void SetProgressCallback(std::function< void(size_t)> aCallback)
@ ROUND_ALL_CORNERS
All angles are rounded.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_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:58
Hashing functions for EDA_ITEMs.
@ HASH_POS
Definition hash_eda.h:47
@ REL_COORD
Use coordinates relative to the parent object.
Definition hash_eda.h:50
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:172
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ User_8
Definition layer_ids.h:131
@ F_CrtYd
Definition layer_ids.h:116
@ B_Adhes
Definition layer_ids.h:103
@ Edge_Cuts
Definition layer_ids.h:112
@ Dwgs_User
Definition layer_ids.h:107
@ F_Paste
Definition layer_ids.h:104
@ Cmts_User
Definition layer_ids.h:108
@ User_6
Definition layer_ids.h:129
@ User_7
Definition layer_ids.h:130
@ F_Adhes
Definition layer_ids.h:102
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ User_5
Definition layer_ids.h:128
@ Eco1_User
Definition layer_ids.h:109
@ F_Mask
Definition layer_ids.h:97
@ B_Paste
Definition layer_ids.h:105
@ User_9
Definition layer_ids.h:132
@ F_Fab
Definition layer_ids.h:119
@ Margin
Definition layer_ids.h:113
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Eco2_User
Definition layer_ids.h:110
@ User_3
Definition layer_ids.h:126
@ User_1
Definition layer_ids.h:124
@ B_SilkS
Definition layer_ids.h:101
@ User_4
Definition layer_ids.h:127
@ User_2
Definition layer_ids.h:125
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
bool IsValidLayer(int aLayerId)
Test whether a given integer is a valid layer index, i.e.
Definition layer_ids.h:655
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.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:107
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:100
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694