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