/** * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "ipc2581_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include IPC2581_PLUGIN::~IPC2581_PLUGIN() { clearLoadedFootprints(); } void IPC2581_PLUGIN::clearLoadedFootprints() { for( FOOTPRINT* fp : m_loaded_footprints ) { delete fp; } m_loaded_footprints.clear(); } const wxString IPC2581_PLUGIN::PluginName() const { return wxT( "IPC-2581" ); } std::vector IPC2581_PLUGIN::GetImportedCachedLibraryFootprints() { std::vector retval; for( FOOTPRINT* fp : m_loaded_footprints ) { retval.push_back( static_cast( fp->Clone() ) ); } return retval; } void IPC2581_PLUGIN::insertNode( wxXmlNode* aParent, wxXmlNode* aNode ) { // insertNode places the node at the start of the list of children if( aParent->GetChildren() ) aNode->SetNext( aParent->GetChildren() ); else aNode->SetNext( nullptr ); aParent->SetChildren( aNode ); aNode->SetParent( aParent ); } void IPC2581_PLUGIN::insertNodeAfter( wxXmlNode* aPrev, wxXmlNode* aNode ) { // insertNode places the node directly after aPrev aNode->SetNext( aPrev->GetNext() ); aPrev->SetNext( aNode ); aNode->SetParent( aPrev->GetParent() ); } wxXmlNode* IPC2581_PLUGIN::insertNode( wxXmlNode* aParent, const wxString& aName ) { // Opening tag, closing tag, brackets and the closing slash m_total_bytes += 2 * aName.size() + 5; wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName ); insertNode( aParent, node ); return node; } wxXmlNode* IPC2581_PLUGIN::appendNode( wxXmlNode* aParent, const wxString& aName ) { // AddChild iterates through the entire list of children, so we want to avoid // that if possible. When we share a parent and our next sibling is null, // then we are the last child and can just append to the end of the list. static wxXmlNode* lastNode = nullptr; wxXmlNode* node = new wxXmlNode( wxXML_ELEMENT_NODE, aName ); if( lastNode && lastNode->GetParent() == aParent && lastNode->GetNext() == nullptr ) { node->SetParent( aParent ); lastNode->SetNext( node ); } else { aParent->AddChild( node ); } lastNode = node; // Opening tag, closing tag, brackets and the closing slash m_total_bytes += 2 * aName.size() + 5; return node; } wxString IPC2581_PLUGIN::genString( const wxString& aStr, const char* aPrefix ) { wxString str; if( m_version == 'C' ) { str = aStr; str.Replace( wxT( ":" ), wxT( "_" ) ); if( aPrefix ) str.Prepend( wxString( aPrefix ) + wxT( ":" ) ); else str.Prepend( wxT( "KI:" ) ); } else { if( aPrefix ) str = wxString::Format( "%s_", aPrefix ); for( wxString::const_iterator iter = aStr.begin(); iter != aStr.end(); ++iter ) { if( !m_acceptable_chars.count( *iter ) ) str.Append( '_' ); else str.Append( *iter ); } } return str; } wxString IPC2581_PLUGIN::floatVal( double aVal ) { wxString str = wxString::FromCDouble( aVal, m_sigfig ); // Remove all but the last trailing zeros from str while( str.EndsWith( wxT( "00" ) ) ) str.RemoveLast(); // We don't want to output -0.0 as this value is just 0 for fabs if( str == wxT( "-0.0" ) ) return wxT( "0.0" ); return str; } void IPC2581_PLUGIN::addXY( wxXmlNode* aNode, const VECTOR2I& aVec, const char* aXName, const char* aYName ) { if( aXName ) addAttribute( aNode, aXName, floatVal( m_scale * aVec.x ) ); else addAttribute( aNode, "x", floatVal( m_scale * aVec.x ) ); if( aYName ) addAttribute( aNode, aYName, floatVal( -m_scale * aVec.y ) ); else addAttribute( aNode, "y", floatVal( -m_scale * aVec.y ) ); } void IPC2581_PLUGIN::addAttribute( wxXmlNode* aNode, const wxString& aName, const wxString& aValue ) { m_total_bytes += aName.size() + aValue.size() + 4; aNode->AddAttribute( aName, aValue ); } wxXmlNode* IPC2581_PLUGIN::generateXmlHeader() { wxXmlNode* xmlHeaderNode = new wxXmlNode(wxXML_ELEMENT_NODE, "IPC-2581"); addAttribute( xmlHeaderNode, "revision", m_version); addAttribute( xmlHeaderNode, "xmlns", "http://webstds.ipc.org/2581"); addAttribute( xmlHeaderNode, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); addAttribute( xmlHeaderNode, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema"); if( m_version == 'B' ) addAttribute( xmlHeaderNode, "xsi:schemaLocation", "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581B1.xsd"); else addAttribute( xmlHeaderNode, "xsi:schemaLocation", "http://webstds.ipc.org/2581 http://webstds.ipc.org/2581/IPC-2581C.xsd"); m_xml_doc->SetRoot(xmlHeaderNode); return xmlHeaderNode; } wxXmlNode* IPC2581_PLUGIN::generateContentSection() { if( m_progress_reporter ) m_progress_reporter->AdvancePhase( _( "Generating content section" ) ); wxXmlNode* contentNode = appendNode( m_xml_root, "Content" ); addAttribute( contentNode, "roleRef", "Owner" ); wxXmlNode* node = appendNode( contentNode, "FunctionMode" ); addAttribute( node, "mode", "ASSEMBLY" ); // This element is deprecated in revision 'C' and later if( m_version == 'B' ) addAttribute( node, "level", "3" ); node = appendNode( contentNode, "StepRef" ); wxFileName fn( m_board->GetFileName() ); addAttribute( node, "name", genString( fn.GetName(), "BOARD" ) ); wxXmlNode* color_node = generateContentStackup( contentNode ); if( m_version == 'C' ) { contentNode->AddChild( color_node ); m_line_node = appendNode( contentNode, "DictionaryLineDesc" ); addAttribute( m_line_node, "units", m_units_str ); wxXmlNode* fillNode = appendNode( contentNode, "DictionaryFillDesc" ); addAttribute( fillNode, "units", m_units_str ); m_shape_std_node = appendNode( contentNode, "DictionaryStandard" ); addAttribute( m_shape_std_node, "units", m_units_str ); m_shape_user_node = appendNode( contentNode, "DictionaryUser" ); addAttribute( m_shape_user_node, "units", m_units_str ); } else { m_shape_std_node = appendNode( contentNode, "DictionaryStandard" ); addAttribute( m_shape_std_node, "units", m_units_str ); m_shape_user_node = appendNode( contentNode, "DictionaryUser" ); addAttribute( m_shape_user_node, "units", m_units_str ); m_line_node = appendNode( contentNode, "DictionaryLineDesc" ); addAttribute( m_line_node, "units", m_units_str ); contentNode->AddChild( color_node ); } return contentNode; } void IPC2581_PLUGIN::addLocationNode( wxXmlNode* aNode, double aX, double aY ) { wxXmlNode* location_node = appendNode( aNode, "Location" ); addXY( location_node, VECTOR2I( aX, aY ) ); } void IPC2581_PLUGIN::addLocationNode( wxXmlNode* aNode, const PAD& aPad, bool aRelative ) { VECTOR2D pos{}; if( aRelative ) pos = aPad.GetFPRelativePosition(); else pos = aPad.GetPosition(); addLocationNode( aNode, pos.x, pos.y ); } void IPC2581_PLUGIN::addLocationNode( wxXmlNode* aNode, const PCB_SHAPE& aShape ) { VECTOR2D pos{}; switch( aShape.GetShape() ) { // Rectangles in KiCad are mapped by their corner while IPC2581 uses the center case SHAPE_T::RECTANGLE: pos = aShape.GetPosition() + VECTOR2I( aShape.GetRectangleWidth() / 2.0, aShape.GetRectangleHeight() / 2.0 ); break; // Both KiCad and IPC2581 use the center of the circle case SHAPE_T::CIRCLE: pos = aShape.GetPosition(); break; // KiCad uses the exact points on the board, so we want the reference location to be 0,0 case SHAPE_T::POLY: case SHAPE_T::BEZIER: case SHAPE_T::SEGMENT: case SHAPE_T::ARC: pos = VECTOR2D( 0, 0 ); break; } addLocationNode( aNode, pos.x, pos.y ); } size_t IPC2581_PLUGIN::lineHash( int aWidth, LINE_STYLE aDashType ) { size_t hash = hash_val( aWidth ); hash_combine( hash, aDashType ); return hash; } size_t IPC2581_PLUGIN::shapeHash( const PCB_SHAPE& aShape ) { return hash_fp_item( &aShape, HASH_POS | REL_COORD ); } wxXmlNode* IPC2581_PLUGIN::generateContentStackup( wxXmlNode* aContentNode ) { BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); BOARD_STACKUP& stackup = bds.GetStackupDescriptor(); stackup.SynchronizeWithBoard( &bds ); wxXmlNode* color_node = new wxXmlNode( wxXML_ELEMENT_NODE, "DictionaryColor" ); for( BOARD_STACKUP_ITEM* item: stackup.GetList() ) { wxString layer_name = item->GetLayerName(); int sub_layer_count = 1; if( layer_name.empty() ) layer_name = m_board->GetLayerName( item->GetBrdLayerId() ); layer_name = genString( layer_name, "LAYER" ); if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { layer_name = genString( wxString::Format( "DIELECTRIC_%d", item->GetDielectricLayerId() ), "LAYER" ); sub_layer_count = item->GetSublayersCount(); } else { m_layer_name_map.emplace( item->GetBrdLayerId(), layer_name ); } for( int sub_idx = 0; sub_idx < sub_layer_count; sub_idx++ ) { wxString sub_layer_name = layer_name; if( sub_idx > 0 ) sub_layer_name += wxString::Format( "_%d", sub_idx ); wxXmlNode* node = appendNode( aContentNode, "LayerRef" ); addAttribute( node, "name", sub_layer_name ); if( !IsPrmSpecified( item->GetColor( sub_idx ) ) ) continue; wxXmlNode* entry_color = appendNode( color_node, "EntryColor" ); addAttribute( entry_color, "id", genString( sub_layer_name, "COLOR" ) ); wxXmlNode* color = appendNode( entry_color, "Color" ); wxString colorName = item->GetColor( sub_idx ); if( colorName.StartsWith( wxT( "#" ) ) ) // This is a user defined color, // not in standard color list. { COLOR4D layer_color( colorName ); addAttribute( color, "r", wxString::Format( "%d", KiROUND( layer_color.r * 255 ) ) ); addAttribute( color, "g", wxString::Format( "%d", KiROUND( layer_color.g * 255 ) ) ); addAttribute( color, "b", wxString::Format( "%d", KiROUND( layer_color.b * 255 ) ) ); } else { for( const FAB_LAYER_COLOR& fab_color : GetStandardColors( item->GetType() ) ) { if( fab_color.GetName() == colorName ) { addAttribute( color, "r", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).r * 255 ) ) ); addAttribute( color, "g", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).g * 255 ) ) ); addAttribute( color, "b", wxString::Format( "%d", KiROUND( fab_color.GetColor( item->GetType() ).b * 255 ) ) ); break; } } } } } return color_node; } void IPC2581_PLUGIN::addFillDesc( wxXmlNode* aNode, FILL_T aFill, bool aForce ) { if( aFill == FILL_T::FILLED_SHAPE ) { // By default, we do not fill shapes because FILL is the default value for most. // But for some outlines, we may need to force a fill. if( aForce ) { wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" ); addAttribute( fillDesc_node, "fillProperty", "FILL" ); } } else { wxXmlNode* fillDesc_node = appendNode( aNode, "FillDesc" ); addAttribute( fillDesc_node, "fillProperty", "HOLLOW" ); } } void IPC2581_PLUGIN::addLineDesc( wxXmlNode* aNode, int aWidth, LINE_STYLE aDashType, bool aForce ) { wxCHECK_RET( aNode, "aNode is null" ); if( aWidth < 0 ) return; wxXmlNode* entry_node = nullptr; if( !aForce ) { size_t hash = lineHash( aWidth, aDashType ); wxString name = wxString::Format( "LINE_%zu", m_line_dict.size() + 1 ); auto[ iter, inserted ] = m_line_dict.emplace( hash, name ); // Either add a new entry or reference an existing one wxXmlNode* lineDesc_node = appendNode( aNode, "LineDescRef" ); addAttribute( lineDesc_node, "id", iter->second ); if( !inserted ) return; entry_node = appendNode( m_line_node, "EntryLineDesc" ); addAttribute( entry_node, "id", name ); } else { // Force the LineDesc to be added directly to the parent node entry_node = aNode; } wxXmlNode* line_node = appendNode( entry_node, "LineDesc" ); addAttribute( line_node, "lineWidth", floatVal( m_scale * aWidth ) ); addAttribute( line_node, "lineEnd", "ROUND" ); switch( aDashType ) { case LINE_STYLE::DOT: addAttribute( line_node, "lineProperty", "DOTTED" ); break; case LINE_STYLE::DASH: addAttribute( line_node, "lineProperty", "DASHED" ); break; case LINE_STYLE::DASHDOT: addAttribute( line_node, "lineProperty", "CENTER" ); break; case LINE_STYLE::DASHDOTDOT: addAttribute( line_node, "lineProperty", "PHANTOM" ); break; default: break; } } void IPC2581_PLUGIN::addText( wxXmlNode* aContentNode, EDA_TEXT* aText, const KIFONT::METRICS& aFontMetrics ) { KIGFX::GAL_DISPLAY_OPTIONS empty_opts; KIFONT::FONT* font = aText->GetFont(); TEXT_ATTRIBUTES attrs = aText->GetAttributes(); attrs.m_StrokeWidth = aText->GetEffectiveTextPenWidth(); attrs.m_Angle = aText->GetDrawRotation(); attrs.m_Multiline = false; wxXmlNode* text_node = appendNode( aContentNode, "UserSpecial" ); if( !font ) font = KIFONT::FONT::GetFont(); std::list pts; auto push_pts = [&]() { if( pts.size() < 2 ) return; wxXmlNode* line_node = nullptr; // Polylines are only allowed for more than 3 points (in version B). // Otherwise, we have to use a line if( pts.size() < 3 ) { line_node = appendNode( text_node, "Line" ); addXY( line_node, pts.front(), "startX", "startY" ); addXY( line_node, pts.back(), "endX", "endY" ); } else { line_node = appendNode( text_node, "Polyline" ); wxXmlNode* point_node = appendNode( line_node, "PolyBegin" ); addXY( point_node, pts.front() ); auto iter = pts.begin(); for( ++iter; iter != pts.end(); ++iter ) { wxXmlNode* point_node = appendNode( line_node, "PolyStepSegment" ); addXY( point_node, *iter ); } } addLineDesc( line_node, attrs.m_StrokeWidth, LINE_STYLE::SOLID ); pts.clear(); }; CALLBACK_GAL callback_gal( empty_opts, // Stroke callback [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 ) { if( !pts.empty() ) { if( aPt1 == pts.back() ) pts.push_back( aPt2 ); else if( aPt2 == pts.front() ) pts.push_front( aPt1 ); else if( aPt1 == pts.front() ) pts.push_front( aPt2 ); else if( aPt2 == pts.back() ) pts.push_back( aPt1 ); else { push_pts(); pts.push_back( aPt1 ); pts.push_back( aPt2 ); } } else { pts.push_back( aPt1 ); pts.push_back( aPt2 ); } }, // Polygon callback [&]( const SHAPE_LINE_CHAIN& aPoly ) { if( aPoly.PointCount() < 3 ) return; wxXmlNode* outline_node = appendNode( text_node, "Outline" ); wxXmlNode* poly_node = appendNode( outline_node, "Polygon" ); const std::vector& pts = aPoly.CPoints(); wxXmlNode* point_node = appendNode( poly_node, "PolyBegin" ); addXY( point_node, pts.front() ); for( size_t ii = 1; ii < pts.size(); ++ii ) { wxXmlNode* point_node = appendNode( poly_node, "PolyStepSegment" ); addXY( point_node, pts[ii] ); } point_node = appendNode( poly_node, "PolyStepSegment" ); addXY( point_node, pts.front() ); } ); //TODO: handle knockout text and multiline font->Draw( &callback_gal, aText->GetShownText( true ), aText->GetTextPos(), attrs, aFontMetrics ); if( !pts.empty() ) push_pts(); if( text_node->GetChildren() == nullptr ) { aContentNode->RemoveChild( text_node ); delete text_node; } } void IPC2581_PLUGIN::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAYER_ID aLayer ) { size_t hash = hash_fp_item( &aPad, 0 ); auto iter = m_std_shape_dict.find( hash ); if( iter != m_std_shape_dict.end() ) { wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" ); addAttribute( shape_node, "id", iter->second ); return; } int maxError = m_board->GetDesignSettings().m_MaxError; wxString name; VECTOR2I expansion{ 0, 0 }; if( LSET( 2, F_Mask, B_Mask ).Contains( aLayer ) ) expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion(); if( LSET( 2, F_Paste, B_Paste ).Contains( aLayer ) ) expansion = 2 * aPad.GetSolderPasteMargin(); switch( aPad.GetShape() ) { case PAD_SHAPE::CIRCLE: { name = wxString::Format( "CIRCLE_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); wxXmlNode* circle_node = appendNode( entry_node, "Circle" ); circle_node->AddAttribute( "diameter", floatVal( m_scale * ( expansion.x + aPad.GetSizeX() ) ) ); break; } case PAD_SHAPE::RECTANGLE: { name = wxString::Format( "RECT_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); wxXmlNode* rect_node = appendNode( entry_node, "RectCenter" ); VECTOR2D pad_size = aPad.GetSize() + expansion; addAttribute( rect_node, "width", floatVal( m_scale * std::abs( pad_size.x ) ) ); addAttribute( rect_node, "height", floatVal( m_scale * std::abs( pad_size.y ) ) ); break; } case PAD_SHAPE::OVAL: { name = wxString::Format( "OVAL_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); wxXmlNode* oval_node = appendNode( entry_node, "Oval" ); VECTOR2D pad_size = aPad.GetSize() + expansion; addAttribute( oval_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( oval_node, "height", floatVal( m_scale * pad_size.y ) ); break; } case PAD_SHAPE::ROUNDRECT: { name = wxString::Format( "ROUNDRECT_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); wxXmlNode* roundrect_node = appendNode( entry_node, "RectRound" ); VECTOR2D pad_size = aPad.GetSize() + expansion; addAttribute( roundrect_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( roundrect_node, "height", floatVal( m_scale * pad_size.y ) ); roundrect_node->AddAttribute( "radius", floatVal( m_scale * aPad.GetRoundRectCornerRadius() ) ); addAttribute( roundrect_node, "upperRight", "true" ); addAttribute( roundrect_node, "upperLeft", "true" ); addAttribute( roundrect_node, "lowerRight", "true" ); addAttribute( roundrect_node, "lowerLeft", "true" ); break; } case PAD_SHAPE::CHAMFERED_RECT: { name = wxString::Format( "RECTCHAMFERED_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); wxXmlNode* chamfered_node = appendNode( entry_node, "RectCham" ); VECTOR2D pad_size = aPad.GetSize() + expansion; addAttribute( chamfered_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( chamfered_node, "height", floatVal( m_scale * pad_size.y ) ); int shorterSide = std::min( pad_size.x, pad_size.y ); int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio() * shorterSide ) ); addAttribute( chamfered_node, "chamfer", floatVal( m_scale * chamfer ) ); int positions = aPad.GetChamferPositions(); if( positions & RECT_CHAMFER_TOP_LEFT ) addAttribute( chamfered_node, "upperLeft", "true" ); if( positions & RECT_CHAMFER_TOP_RIGHT ) addAttribute( chamfered_node, "upperRight", "true" ); if( positions & RECT_CHAMFER_BOTTOM_LEFT ) addAttribute( chamfered_node, "lowerLeft", "true" ); if( positions & RECT_CHAMFER_BOTTOM_RIGHT ) addAttribute( chamfered_node, "lowerRight", "true" ); break; } case PAD_SHAPE::TRAPEZOID: { name = wxString::Format( "TRAPEZOID_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); VECTOR2I pad_size = aPad.GetSize(); VECTOR2I trap_delta = aPad.GetDelta(); SHAPE_POLY_SET outline; outline.NewOutline(); int dx = pad_size.x / 2; int dy = pad_size.y / 2; int ddx = trap_delta.x / 2; int ddy = trap_delta.y / 2; outline.Append( -dx - ddy, dy + ddx ); outline.Append( dx + ddy, dy - ddx ); outline.Append( dx - ddy, -dy + ddx ); outline.Append( -dx + ddy, -dy - ddx ); // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate() // which can create bad shapes if margin.x is < 0 if( expansion.x ) { outline.InflateWithLinkedHoles( expansion.x, CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError, SHAPE_POLY_SET::PM_FAST ); } addContourNode( entry_node, outline ); break; } case PAD_SHAPE::CUSTOM: { name = wxString::Format( "CUSTOM_%zu", m_std_shape_dict.size() + 1 ); m_std_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); SHAPE_POLY_SET shape; aPad.MergePrimitivesAsPolygon( &shape ); if( expansion != VECTOR2I( 0, 0 ) ) { shape.InflateWithLinkedHoles( std::max( expansion.x, expansion.y ), CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError, SHAPE_POLY_SET::PM_FAST ); } addContourNode( entry_node, shape ); break; } default: wxLogError( "Unknown pad type" ); break; } if( !name.empty() ) { m_std_shape_dict.emplace( hash, name ); wxXmlNode* shape_node = appendNode( aContentNode, "StandardPrimitiveRef" ); addAttribute( shape_node, "id", name ); } } void IPC2581_PLUGIN::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape ) { size_t hash = shapeHash( aShape ); auto iter = m_user_shape_dict.find( hash ); wxString name; if( iter != m_user_shape_dict.end() ) { wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" ); addAttribute( shape_node, "id", iter->second ); return; } switch( aShape.GetShape() ) { case SHAPE_T::CIRCLE: { name = wxString::Format( "UCIRCLE_%zu", m_user_shape_dict.size() + 1 ); m_user_shape_dict.emplace( hash, name ); int diameter = aShape.GetRadius() * 2.0; int width = aShape.GetStroke().GetWidth(); LINE_STYLE dash = aShape.GetStroke().GetLineStyle(); wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" ); addAttribute( entry_node, "id", name ); wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" ); wxXmlNode* circle_node = appendNode( special_node, "Circle" ); if( aShape.GetFillMode() == FILL_T::NO_FILL ) { addAttribute( circle_node, "diameter", floatVal( m_scale * diameter ) ); addLineDesc( circle_node, width, dash, true ); } else { // IPC2581 does not allow strokes on filled elements addAttribute( circle_node, "diameter", floatVal( m_scale * ( diameter + width ) ) ); } addFillDesc( circle_node, aShape.GetFillMode() ); break; } case SHAPE_T::RECTANGLE: { name = wxString::Format( "URECT_%zu", m_user_shape_dict.size() + 1 ); m_user_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" ); addAttribute( entry_node, "id", name ); wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" ); int width = std::abs( aShape.GetRectangleWidth() ); int height = std::abs( aShape.GetRectangleHeight() ); int stroke_width = aShape.GetStroke().GetWidth(); LINE_STYLE dash = aShape.GetStroke().GetLineStyle(); wxXmlNode* rect_node = appendNode( special_node, "RectRound" ); addLineDesc( rect_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(), true ); if( aShape.GetFillMode() == FILL_T::NO_FILL ) { addAttribute( rect_node, "upperRight", "false" ); addAttribute( rect_node, "upperLeft", "false" ); addAttribute( rect_node, "lowerRight", "false" ); addAttribute( rect_node, "lowerLeft", "false" ); } else { addAttribute( rect_node, "upperRight", "true" ); addAttribute( rect_node, "upperLeft", "true" ); addAttribute( rect_node, "lowerRight", "true" ); addAttribute( rect_node, "lowerLeft", "true" ); width += stroke_width; height += stroke_width; } addFillDesc( rect_node, aShape.GetFillMode() ); addAttribute( rect_node, "width", floatVal( m_scale * width ) ); addAttribute( rect_node, "height", floatVal( m_scale * height ) ); addAttribute( rect_node, "radius", floatVal( m_scale * ( stroke_width / 2.0 ) ) ); break; } case SHAPE_T::POLY: { name = wxString::Format( "UPOLY_%zu", m_user_shape_dict.size() + 1 ); m_user_shape_dict.emplace( hash, name ); wxXmlNode* entry_node = appendNode( m_shape_user_node, "EntryUser" ); addAttribute( entry_node, "id", name ); // If we are stroking a polygon, we need two contours. This is only allowed // inside a "UserSpecial" shape wxXmlNode* special_node = appendNode( entry_node, "UserSpecial" ); const SHAPE_POLY_SET& poly_set = aShape.GetPolyShape(); for( int ii = 0; ii < poly_set.OutlineCount(); ++ii ) { if( aShape.GetFillMode() != FILL_T::NO_FILL ) { // IPC2581 does not allow strokes on filled elements addContourNode( special_node, poly_set, ii, FILL_T::FILLED_SHAPE, 0, LINE_STYLE::SOLID ); } addContourNode( special_node, poly_set, ii, FILL_T::NO_FILL, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle() ); } break; } case SHAPE_T::ARC: { wxXmlNode* arc_node = appendNode( aContentNode, "Arc" ); addXY( arc_node, aShape.GetStart(), "startX", "startY" ); addXY( arc_node, aShape.GetEnd(), "endX", "endY" ); addXY( arc_node, aShape.GetCenter(), "centerX", "centerY" ); //N.B. because our coordinate system is flipped, we need to flip the arc direction addAttribute( arc_node, "clockwise", !aShape.IsClockwiseArc() ? "true" : "false" ); if( aShape.GetStroke().GetWidth() > 0 ) { addLineDesc( arc_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(), true ); } break; } case SHAPE_T::BEZIER: { wxXmlNode* polyline_node = appendNode( aContentNode, "Polyline" ); const std::vector& points = aShape.GetBezierPoints(); wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" ); addXY( point_node, points[0] ); for( size_t i = 1; i < points.size(); i++ ) { wxXmlNode* point_node = appendNode( polyline_node, "PolyStepSegment" ); addXY( point_node, points[i] ); } if( aShape.GetStroke().GetWidth() > 0 ) { addLineDesc( polyline_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(), true ); } break; } case SHAPE_T::SEGMENT: { wxXmlNode* line_node = appendNode( aContentNode, "Line" ); addXY( line_node, aShape.GetStart(), "startX", "startY" ); addXY( line_node, aShape.GetEnd(), "endX", "endY" ); if( aShape.GetStroke().GetWidth() > 0 ) { addLineDesc( line_node, aShape.GetStroke().GetWidth(), aShape.GetStroke().GetLineStyle(), true ); } break; } } if( !name.empty() ) { wxXmlNode* shape_node = appendNode( aContentNode, "UserPrimitiveRef" ); addAttribute( shape_node, "id", name ); } } void IPC2581_PLUGIN::addSlotCavity( wxXmlNode* aNode, const PAD& aPad, const wxString& aName ) { wxXmlNode* slotNode = appendNode( aNode, "SlotCavity" ); addAttribute( slotNode, "name", aName ); addAttribute( slotNode, "platingStatus", aPad.GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" ); addAttribute( slotNode, "plusTol", "0.0" ); addAttribute( slotNode, "minusTol", "0.0" ); if( m_version > 'B' ) addLocationNode( slotNode, 0.0, 0.0 ); SHAPE_POLY_SET poly_set; aPad.GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE ); addOutlineNode( slotNode, poly_set ); } wxXmlNode* IPC2581_PLUGIN::generateLogisticSection() { wxXmlNode* logisticNode = appendNode( m_xml_root, "LogisticHeader" ); wxXmlNode* roleNode = appendNode( logisticNode, "Role" ); addAttribute( roleNode, "id", "Owner" ); addAttribute( roleNode, "roleFunction", "SENDER" ); m_enterpriseNode = appendNode( logisticNode, "Enterprise" ); addAttribute( m_enterpriseNode, "id", "UNKNOWN" ); addAttribute( m_enterpriseNode, "code", "NONE" ); wxXmlNode* personNode = appendNode( logisticNode, "Person" ); addAttribute( personNode, "name", "UNKNOWN" ); addAttribute( personNode, "enterpriseRef", "UNKNOWN" ); addAttribute( personNode, "roleRef", "Owner" ); return logisticNode; } wxXmlNode* IPC2581_PLUGIN::generateHistorySection() { if( m_progress_reporter ) m_progress_reporter->AdvancePhase( _( "Generating history section" ) ); wxXmlNode* historyNode = appendNode( m_xml_root, "HistoryRecord" ); addAttribute( historyNode, "number", "1" ); addAttribute( historyNode, "origination", wxDateTime::Now().FormatISOCombined() ); addAttribute( historyNode, "software", "KiCad EDA" ); addAttribute( historyNode, "lastChange", wxDateTime::Now().FormatISOCombined() ); wxXmlNode* fileRevisionNode = appendNode( historyNode, "FileRevision" ); addAttribute( fileRevisionNode, "fileRevisionId", "1" ); addAttribute( fileRevisionNode, "comment", "NO COMMENT" ); addAttribute( fileRevisionNode, "label", "NO LABEL" ); wxXmlNode* softwarePackageNode = appendNode( fileRevisionNode, "SoftwarePackage" ); addAttribute( softwarePackageNode, "name", "KiCad" ); addAttribute( softwarePackageNode, "revision", GetMajorMinorPatchVersion() ); addAttribute( softwarePackageNode, "vendor", "KiCad EDA" ); wxXmlNode* certificationNode = appendNode( softwarePackageNode, "Certification" ); addAttribute( certificationNode, "certificationStatus", "SELFTEST" ); return historyNode; } wxXmlNode* IPC2581_PLUGIN::generateBOMSection( wxXmlNode* aEcadNode ) { if( m_progress_reporter ) m_progress_reporter->AdvancePhase( _( "Generating BOM section" ) ); struct REFDES { wxString m_name; wxString m_pkg; bool m_populate; wxString m_layer; }; struct BOM_ENTRY { BOM_ENTRY() { m_refdes = new std::vector(); m_props = new std::map(); m_count = 0; m_pads = 0; } ~BOM_ENTRY() { delete m_refdes; delete m_props; } wxString m_OEMDesignRef; // String combining LIB+FP+VALUE int m_count; int m_pads; wxString m_type; std::vector* m_refdes; std::map* m_props; }; std::set, std::function&, const std::unique_ptr& )>> bom_entries( []( const std::unique_ptr& a, const std::unique_ptr& b ) { return a->m_OEMDesignRef < b->m_OEMDesignRef; } ); for( FOOTPRINT* fp_it : m_board->Footprints() ) { if( fp_it->GetAttributes() & FP_EXCLUDE_FROM_BOM ) continue; std::unique_ptr fp( static_cast( fp_it->Clone() ) ); fp->SetParentGroup( nullptr ); fp->SetPosition( {0, 0} ); if( fp->GetLayer() != F_Cu ) fp->Flip( fp->GetPosition(), false ); fp->SetOrientation( EDA_ANGLE::m_Angle0 ); size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD ); auto iter = m_footprint_dict.find( hash ); if( iter == m_footprint_dict.end() ) { wxLogError( "Footprint %s not found in dictionary", fp->GetFPID().GetLibItemName().wx_str() ); continue; } auto entry = std::make_unique(); /// We assume that the m_OEMRef_dict is populated already by the generateComponents function /// This will either place a unique string in the dictionary or field reference. if( auto it = m_OEMRef_dict.find( fp_it ); it != m_OEMRef_dict.end() ) { entry->m_OEMDesignRef = it->second; } else { wxLogError( "Footprint %s not found in OEMRef dictionary", fp->GetFPID().GetLibItemName().wx_str() ); } entry->m_OEMDesignRef = genString( entry->m_OEMDesignRef, "REF" ); entry->m_count = 1; entry->m_pads = fp->GetPadCount(); // TODO: The options are "ELECTRICAL", "MECHANICAL", "PROGRAMMABLE", "DOCUMENT", "MATERIAL" // We need to figure out how to determine this. if( entry->m_pads == 0 ) entry->m_type = "DOCUMENT"; else entry->m_type = "ELECTRICAL"; auto[ bom_iter, inserted ] = bom_entries.insert( std::move( entry ) ); if( !inserted ) ( *bom_iter )->m_count++; REFDES refdes; refdes.m_name = fp->Reference().GetShownText( false ); refdes.m_pkg = fp->GetFPID().GetLibItemName().wx_str(); refdes.m_populate = !fp->IsDNP(); refdes.m_layer = m_layer_name_map[fp_it->GetLayer()]; ( *bom_iter )->m_refdes->push_back( refdes ); // TODO: This amalgamates all the properties from all the footprints. We need to decide // if we want to group footprints by their properties for( PCB_FIELD* prop : fp->GetFields() ) { // We don't need ref, footprint or datasheet in the BOM characteristics. Just value // and any additional fields the user has added. Ref and footprint are captured above. if( prop->IsMandatoryField() && !prop->IsValue() ) continue; ( *bom_iter )->m_props->emplace( prop->GetName(), prop->GetText() ); } } wxFileName fn( m_board->GetFileName() ); wxXmlNode* bomNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Bom" ); m_xml_root->InsertChild( bomNode, aEcadNode ); addAttribute( bomNode, "name", genString( fn.GetName(), "BOM" ) ); wxXmlNode* bomHeaderNode = appendNode( bomNode, "BomHeader" ); addAttribute( bomHeaderNode, "revision", "1.0" ); addAttribute( bomHeaderNode, "assembly", wxString::Format( "%s", fn.GetName() ) ); wxXmlNode* stepRefNode = appendNode( bomHeaderNode, "StepRef" ); addAttribute( stepRefNode, "name", genString( fn.GetName(), "BOARD" ) ); for( const auto& entry : bom_entries ) { wxXmlNode* bomEntryNode = appendNode( bomNode, "BomItem" ); addAttribute( bomEntryNode, "OEMDesignNumberRef", entry->m_OEMDesignRef ); addAttribute( bomEntryNode, "quantity", wxString::Format( "%d", entry->m_count ) ); addAttribute( bomEntryNode, "pinCount", wxString::Format( "%d", entry->m_pads ) ); addAttribute( bomEntryNode, "category", entry->m_type ); for( const REFDES& refdes : *( entry->m_refdes ) ) { wxXmlNode* refdesNode = appendNode( bomEntryNode, "RefDes" ); addAttribute( refdesNode, "name", genString( refdes.m_name, "CMP" ) ); addAttribute( refdesNode, "packageRef", genString( refdes.m_pkg, "PKG" ) ); addAttribute( refdesNode, "populate", refdes.m_populate ? "true" : "false" ); addAttribute( refdesNode, "layerRef", refdes.m_layer ); } wxXmlNode* characteristicsNode = appendNode( bomEntryNode, "Characteristics" ); addAttribute( characteristicsNode, "category", "ELECTRICAL" ); for( const auto& prop : *( entry->m_props ) ) { wxXmlNode* textualDefNode = appendNode( characteristicsNode, "Textual" ); addAttribute( textualDefNode, "definitionSource", "KICAD" ); addAttribute( textualDefNode, "textualCharacteristicName", prop.first ); addAttribute( textualDefNode, "textualCharacteristicValue", prop.second ); } } return bomNode; } wxXmlNode* IPC2581_PLUGIN::generateEcadSection() { if( m_progress_reporter ) m_progress_reporter->AdvancePhase( _( "Generating CAD data" ) ); wxXmlNode* ecadNode = appendNode( m_xml_root, "Ecad" ); addAttribute( ecadNode, "name", "Design" ); addCadHeader( ecadNode ); wxXmlNode* cadDataNode = appendNode( ecadNode, "CadData" ); generateCadLayers( cadDataNode ); generateDrillLayers( cadDataNode); generateStepSection( cadDataNode ); return ecadNode; } void IPC2581_PLUGIN::addCadHeader( wxXmlNode* aEcadNode ) { wxXmlNode* cadHeaderNode = appendNode( aEcadNode, "CadHeader" ); addAttribute( cadHeaderNode, "units", m_units_str ); } void IPC2581_PLUGIN::addLayerAttributes( wxXmlNode* aNode, PCB_LAYER_ID aLayer ) { switch( aLayer ) { case F_Adhes: case B_Adhes: addAttribute( aNode, "layerFunction", "GLUE" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_Adhes ? "TOP" : "BOTTOM" ); break; case F_Paste: case B_Paste: addAttribute( aNode, "layerFunction", "SOLDERPASTE" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_Paste ? "TOP" : "BOTTOM" ); break; case F_SilkS: case B_SilkS: addAttribute( aNode, "layerFunction", "SILKSCREEN" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_SilkS ? "TOP" : "BOTTOM" ); break; case F_Mask: case B_Mask: addAttribute( aNode, "layerFunction", "SOLDERMASK" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_Mask ? "TOP" : "BOTTOM" ); break; case Edge_Cuts: addAttribute( aNode, "layerFunction", "BOARD_OUTLINE" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", "ALL" ); break; case B_CrtYd: case F_CrtYd: addAttribute( aNode, "layerFunction", "COURTYARD" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_CrtYd ? "TOP" : "BOTTOM" ); break; case B_Fab: case F_Fab: addAttribute( aNode, "layerFunction", "ASSEMBLY" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_Fab ? "TOP" : "BOTTOM" ); break; case Dwgs_User: case Cmts_User: case Eco1_User: case Eco2_User: case Margin: case User_1: case User_2: case User_3: case User_4: case User_5: case User_6: case User_7: case User_8: case User_9: addAttribute( aNode, "layerFunction", "DOCUMENT" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", "NONE" ); break; default: if( IsCopperLayer( aLayer ) ) { addAttribute( aNode, "layerFunction", "CONDUCTOR" ); addAttribute( aNode, "polarity", "POSITIVE" ); addAttribute( aNode, "side", aLayer == F_Cu ? "TOP" : aLayer == B_Cu ? "BOTTOM" : "INTERNAL" ); } break; // Do not handle other layers } } void IPC2581_PLUGIN::generateCadLayers( wxXmlNode* aCadLayerNode ) { BOARD_DESIGN_SETTINGS& dsnSettings = m_board->GetDesignSettings(); BOARD_STACKUP& stackup = dsnSettings.GetStackupDescriptor(); stackup.SynchronizeWithBoard( &dsnSettings ); std::vector layers = stackup.GetList(); std::set added_layers; for( int i = 0; i < stackup.GetCount(); i++ ) { BOARD_STACKUP_ITEM* stackup_item = layers.at( i ); for( int sublayer_id = 0; sublayer_id < stackup_item->GetSublayersCount(); sublayer_id++ ) { wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" ); wxString ly_name = stackup_item->GetLayerName(); if( ly_name.IsEmpty() ) { if( IsValidLayer( stackup_item->GetBrdLayerId() ) ) ly_name = m_board->GetLayerName( stackup_item->GetBrdLayerId() ); if( ly_name.IsEmpty() && stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) ly_name = wxString::Format( "DIELECTRIC_%d", stackup_item->GetDielectricLayerId() ); } ly_name = genString( ly_name, "LAYER" ); addAttribute( cadLayerNode, "name", ly_name ); if( stackup_item->GetType() == BS_ITEM_TYPE_DIELECTRIC ) { if( stackup_item->GetTypeName() == KEY_CORE ) addAttribute( cadLayerNode, "layerFunction", "DIELCORE" ); else addAttribute( cadLayerNode, "layerFunction", "DIELPREG" ); addAttribute( cadLayerNode, "polarity", "POSITIVE" ); addAttribute( cadLayerNode, "side", "INTERNAL" ); continue; } else { added_layers.insert( stackup_item->GetBrdLayerId() ); addLayerAttributes( cadLayerNode, stackup_item->GetBrdLayerId() ); m_layer_name_map.emplace( stackup_item->GetBrdLayerId(), ly_name ); } } } LSEQ layer_seq = m_board->GetEnabledLayers().Seq(); for( PCB_LAYER_ID layer : layer_seq ) { if( added_layers.find( layer ) != added_layers.end() ) continue; wxString ly_name = genString( m_board->GetLayerName( layer ), "LAYER" ); m_layer_name_map.emplace( layer, ly_name ); added_layers.insert( layer ); wxXmlNode* cadLayerNode = appendNode( aCadLayerNode, "Layer" ); addAttribute( cadLayerNode, "name", ly_name ); addLayerAttributes( cadLayerNode, layer ); } } void IPC2581_PLUGIN::generateDrillLayers( wxXmlNode* aCadLayerNode ) { for( BOARD_ITEM* item : m_board->Tracks() ) { if( item->Type() == PCB_VIA_T ) { PCB_VIA* via = static_cast( item ); m_drill_layers[std::make_pair( via->TopLayer(), via->BottomLayer() )].push_back( via ); } } for( FOOTPRINT* fp : m_board->Footprints() ) { for( PAD* pad : fp->Pads() ) { if( pad->HasHole() && pad->GetDrillSizeX() != pad->GetDrillSizeY() ) m_slot_holes[std::make_pair( F_Cu, B_Cu )].push_back( pad ); else if( pad->HasHole() ) m_drill_layers[std::make_pair( F_Cu, B_Cu )].push_back( pad ); } } for( const auto& [layer_pair, vec] : m_drill_layers ) { wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" ); drillNode->AddAttribute( "name", genString( wxString::Format( "%s_%s", m_board->GetLayerName( layer_pair.first ), m_board->GetLayerName( layer_pair.second ) ), "DRILL" ) ); addAttribute( drillNode, "layerFunction", "DRILL" ); addAttribute( drillNode, "polarity", "POSITIVE" ); addAttribute( drillNode, "side", "ALL" ); wxXmlNode* spanNode = appendNode( drillNode, "Span" ); addAttribute( spanNode, "fromLayer", genString( m_board->GetLayerName( layer_pair.first ), "LAYER" ) ); addAttribute( spanNode, "toLayer", genString( m_board->GetLayerName( layer_pair.second ), "LAYER" ) ); } for( const auto& [layer_pair, vec] : m_slot_holes ) { wxXmlNode* drillNode = appendNode( aCadLayerNode, "Layer" ); drillNode->AddAttribute( "name", genString( wxString::Format( "%s_%s", m_board->GetLayerName( layer_pair.first ), m_board->GetLayerName( layer_pair.second ) ), "SLOT" ) ); addAttribute( drillNode, "layerFunction", "ROUT" ); addAttribute( drillNode, "polarity", "POSITIVE" ); addAttribute( drillNode, "side", "ALL" ); wxXmlNode* spanNode = appendNode( drillNode, "Span" ); addAttribute( spanNode, "fromLayer", genString( m_board->GetLayerName( layer_pair.first ), "LAYER" ) ); addAttribute( spanNode, "toLayer", genString( m_board->GetLayerName( layer_pair.second ), "LAYER" ) ); } } void IPC2581_PLUGIN::generateStepSection( wxXmlNode* aCadNode ) { wxXmlNode* stepNode = appendNode( aCadNode, "Step" ); wxFileName fn( m_board->GetFileName() ); addAttribute( stepNode, "name", genString( fn.GetName(), "BOARD" ) ); if( m_version > 'B' ) addAttribute( stepNode, "type", "BOARD" ); wxXmlNode* datumNode = appendNode( stepNode, "Datum" ); addAttribute( datumNode, "x", "0.0" ); addAttribute( datumNode, "y", "0.0" ); generateProfile( stepNode ); generateComponents( stepNode ); m_last_padstack = insertNode( stepNode, "NonstandardAttribute" ); addAttribute( m_last_padstack, "name", "FOOTPRINT_COUNT" ); addAttribute( m_last_padstack, "type", "INTEGER" ); addAttribute( m_last_padstack, "value", wxString::Format( "%zu", m_board->Footprints().size() ) ); generateLayerFeatures( stepNode ); generateLayerSetDrill( stepNode ); } void IPC2581_PLUGIN::addPad( wxXmlNode* aContentNode, const PAD* aPad, PCB_LAYER_ID aLayer ) { wxXmlNode* padNode = appendNode( aContentNode, "Pad" ); FOOTPRINT* fp = aPad->GetParentFootprint(); addPadStack( padNode, aPad ); if( aPad->GetOrientation() != EDA_ANGLE::m_Angle0 ) { wxXmlNode* xformNode = appendNode( padNode, "Xform" ); xformNode->AddAttribute( "rotation", floatVal( aPad->GetOrientation().Normalize().AsDegrees() ) ); if( fp && fp->IsFlipped() ) addAttribute( xformNode, "mirror", "true" ); } addLocationNode( padNode, *aPad, false ); addShape( padNode, *aPad, aLayer ); if( fp ) { wxXmlNode* pinRefNode = appendNode( padNode, "PinRef" ); wxString name = aPad->GetNumber(); if( aPad->GetAttribute() == PAD_ATTRIB::NPTH ) name = wxString::Format( "NPTH_%d_%d", aPad->GetX(), aPad->GetY() ); addAttribute( pinRefNode, "pin", genString( name, "PIN" ) ); addAttribute( pinRefNode, "componentRef", genString( fp->GetReferenceAsString(), "CMP" ) ); } } void IPC2581_PLUGIN::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_LAYER_ID aLayer ) { if( !aVia->FlashLayer( aLayer ) ) return; wxXmlNode* padNode = appendNode( aContentNode, "Pad" ); addPadStack( padNode, aVia ); addLocationNode( padNode, aVia->GetPosition().x, aVia->GetPosition().y ); PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE ); shape.SetEnd( { KiROUND( aVia->GetWidth() / 2.0 ), 0 } ); addShape( padNode, shape ); } void IPC2581_PLUGIN::addPadStack( wxXmlNode* aPadNode, const PAD* aPad ) { size_t hash = hash_fp_item( aPad, 0 ); wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 ); auto [ th_pair, success ] = m_padstack_dict.emplace( hash, name ); addAttribute( aPadNode, "padstackDefRef", th_pair->second ); // If we did not insert a new padstack, then we have already added it to the XML // and we don't need to add it again. if( !success ) return; wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" ); addAttribute( padStackDefNode, "name", name ); m_padstacks.push_back( padStackDefNode ); // Only handle round holes here because IPC2581 does not support non-round holes // These will be handled in a slot layer if( aPad->HasHole() && aPad->GetDrillSizeX() == aPad->GetDrillSizeY() ) { wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" ); padStackHoleNode->AddAttribute( "name", wxString::Format( "%s%d_%d", aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PTH" : "NPTH", aPad->GetDrillSizeX(), aPad->GetDrillSizeY() ) ); addAttribute( padStackHoleNode, "diameter", floatVal( m_scale * aPad->GetDrillSizeX() ) ); addAttribute( padStackHoleNode, "platingStatus", aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" ); addAttribute( padStackHoleNode, "plusTol", "0.0" ); addAttribute( padStackHoleNode, "minusTol", "0.0" ); addXY( padStackHoleNode, aPad->GetOffset() ); } LSEQ layer_seq = aPad->GetLayerSet().Seq(); for( PCB_LAYER_ID layer : layer_seq ) { FOOTPRINT* fp = aPad->GetParentFootprint(); if( !m_board->IsLayerEnabled( layer ) ) continue; wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" ); addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] ); addAttribute( padStackPadDefNode, "padUse", "REGULAR" ); addLocationNode( padStackPadDefNode, *aPad, true ); if( aPad->HasHole() || !aPad->FlashLayer( layer ) ) { PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE ); shape.SetStart( aPad->GetOffset() ); shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 ); addShape( padStackPadDefNode, shape ); } else { addShape( padStackPadDefNode, *aPad, layer ); } } } void IPC2581_PLUGIN::addPadStack( wxXmlNode* aContentNode, const PCB_VIA* aVia ) { size_t hash = hash_fp_item( aVia, 0 ); wxString name = wxString::Format( "PADSTACK_%zu", m_padstack_dict.size() + 1 ); auto [ via_pair, success ] = m_padstack_dict.emplace( hash, name ); addAttribute( aContentNode, "padstackDefRef", via_pair->second ); // If we did not insert a new padstack, then we have already added it to the XML // and we don't need to add it again. if( !success ) return; wxXmlNode* padStackDefNode = new wxXmlNode( wxXML_ELEMENT_NODE, "PadStackDef" ); insertNodeAfter( m_last_padstack, padStackDefNode ); m_last_padstack = padStackDefNode; addAttribute( padStackDefNode, "name", name ); wxXmlNode* padStackHoleNode = appendNode( padStackDefNode, "PadstackHoleDef" ); addAttribute( padStackHoleNode, "name", wxString::Format( "PH%d", aVia->GetDrillValue() ) ); padStackHoleNode->AddAttribute( "diameter", floatVal( m_scale * aVia->GetDrillValue() ) ); addAttribute( padStackHoleNode, "platingStatus", "VIA" ); addAttribute( padStackHoleNode, "plusTol", "0.0" ); addAttribute( padStackHoleNode, "minusTol", "0.0" ); addAttribute( padStackHoleNode, "x", "0.0" ); addAttribute( padStackHoleNode, "y", "0.0" ); LSEQ layer_seq = aVia->GetLayerSet().Seq(); for( PCB_LAYER_ID layer : layer_seq ) { if( !aVia->FlashLayer( layer ) || !m_board->IsLayerEnabled( layer ) ) continue; PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE ); shape.SetEnd( { KiROUND( aVia->GetWidth() / 2.0 ), 0 } ); wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" ); addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] ); addAttribute( padStackPadDefNode, "padUse", "REGULAR" ); addLocationNode( padStackPadDefNode, 0.0, 0.0 ); addShape( padStackPadDefNode, shape ); } } bool IPC2581_PLUGIN::addPolygonNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET::POLYGON& aPolygon, FILL_T aFillType, int aWidth, LINE_STYLE aDashType ) { wxXmlNode* polygonNode = nullptr; if( aPolygon.empty() || aPolygon[0].PointCount() < 3 ) return false; auto make_node = [&]() { polygonNode = appendNode( aParentNode, "Polygon" ); wxXmlNode* polybeginNode = appendNode( polygonNode, "PolyBegin" ); const std::vector& pts = aPolygon[0].CPoints(); addXY( polybeginNode, pts[0] ); for( size_t ii = 1; ii < pts.size(); ++ii ) { wxXmlNode* polyNode = appendNode( polygonNode, "PolyStepSegment" ); addXY( polyNode, pts[ii] ); } wxXmlNode* polyendNode = appendNode( polygonNode, "PolyStepSegment" ); addXY( polyendNode, pts[0] ); }; // Allow the case where we don't want line/fill information in the polygon if( aFillType == FILL_T::NO_FILL ) { make_node(); // If we specify a line width, we need to add a LineDescRef node and // since this is only valid for a non-filled polygon, we need to create // the fillNode as well if( aWidth > 0 ) addLineDesc( polygonNode, aWidth, aDashType, true ); } else { wxCHECK( aWidth == 0, false ); make_node(); } addFillDesc( polygonNode, aFillType ); return true; } bool IPC2581_PLUGIN::addPolygonCutouts( wxXmlNode* aParentNode, const SHAPE_POLY_SET::POLYGON& aPolygon ) { for( size_t ii = 1; ii < aPolygon.size(); ++ii ) { wxCHECK2( aPolygon[ii].PointCount() >= 3, continue ); wxXmlNode* cutoutNode = appendNode( aParentNode, "Cutout" ); wxXmlNode* polybeginNode = appendNode( cutoutNode, "PolyBegin" ); const std::vector& hole = aPolygon[ii].CPoints(); addXY( polybeginNode, hole[0] ); for( size_t jj = 1; jj < hole.size(); ++jj ) { wxXmlNode* polyNode = appendNode( cutoutNode, "PolyStepSegment" ); addXY( polyNode, hole[jj] ); } wxXmlNode* polyendNode = appendNode( cutoutNode, "PolyStepSegment" ); addXY( polyendNode, hole[0] ); } return true; } bool IPC2581_PLUGIN::addOutlineNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet, int aWidth, LINE_STYLE aDashType ) { if( aPolySet.OutlineCount() == 0 ) return false; wxXmlNode* outlineNode = appendNode( aParentNode, "Outline" ); for( int ii = 0; ii < aPolySet.OutlineCount(); ++ii ) { wxCHECK2( aPolySet.Outline( ii ).PointCount() >= 3, continue ); if( !addPolygonNode( outlineNode, aPolySet.Polygon( ii ) ) ) wxLogDebug( "Failed to add polygon to outline" ); } if( !outlineNode->GetChildren() ) { aParentNode->RemoveChild( outlineNode ); delete outlineNode; return false; } addLineDesc( outlineNode, aWidth, aDashType ); return true; } bool IPC2581_PLUGIN::addContourNode( wxXmlNode* aParentNode, const SHAPE_POLY_SET& aPolySet, int aOutline, FILL_T aFillType, int aWidth, LINE_STYLE aDashType ) { if( aPolySet.OutlineCount() < ( aOutline + 1 ) ) return false; wxXmlNode* contourNode = appendNode( aParentNode, "Contour" ); if( addPolygonNode( contourNode, aPolySet.Polygon( aOutline ), aFillType, aWidth, aDashType ) ) { // Do not attempt to add cutouts to shapes that are already hollow if( aFillType != FILL_T::NO_FILL ) addPolygonCutouts( contourNode, aPolySet.Polygon( aOutline ) ); } else { aParentNode->RemoveChild( contourNode ); delete contourNode; return false; } return true; } void IPC2581_PLUGIN::generateProfile( wxXmlNode* aStepNode ) { SHAPE_POLY_SET board_outline; if( ! m_board->GetBoardPolygonOutlines( board_outline ) ) { wxLogError( "Failed to get board outline" ); return; } wxXmlNode* profileNode = appendNode( aStepNode, "Profile" ); if( !addPolygonNode( profileNode, board_outline.Polygon( 0 ) ) ) { wxLogDebug( "Failed to add polygon to profile" ); aStepNode->RemoveChild( profileNode ); delete profileNode; } } wxXmlNode* IPC2581_PLUGIN::addPackage( wxXmlNode* aContentNode, FOOTPRINT* aFp ) { std::unique_ptr fp( static_cast( aFp->Clone() ) ); fp->SetParentGroup( nullptr ); fp->SetPosition( { 0, 0 } ); if( fp->GetLayer() != F_Cu ) fp->Flip( fp->GetPosition(), false ); fp->SetOrientation( EDA_ANGLE::m_Angle0 ); size_t hash = hash_fp_item( fp.get(), HASH_POS | REL_COORD ); wxString name = genString( wxString::Format( "%s_%zu", fp->GetFPID().GetLibItemName().wx_str(), m_footprint_dict.size() + 1 ) ); auto [ iter, success ] = m_footprint_dict.emplace( hash, name ); addAttribute( aContentNode, "packageRef", iter->second ); if( !success) return nullptr; // Package and Component nodes are at the same level, so we need to find the parent // which should be the Step node wxXmlNode* packageNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Package" ); wxXmlNode* otherSideViewNode = nullptr; // Only set this if we have elements on the back side addAttribute( packageNode, "name", name ); addAttribute( packageNode, "type", "OTHER" ); // TODO: Replace with actual package type once we encode this // We don't specially identify pin 1 in our footprints, so we need to guess if( fp->FindPadByNumber( "1" ) ) addAttribute( packageNode, "pinOne", "1" ); else if ( fp->FindPadByNumber( "A1" ) ) addAttribute( packageNode, "pinOne", "A1" ); else if ( fp->FindPadByNumber( "A" ) ) addAttribute( packageNode, "pinOne", "A" ); else if ( fp->FindPadByNumber( "a" ) ) addAttribute( packageNode, "pinOne", "a" ); else if ( fp->FindPadByNumber( "a1" ) ) addAttribute( packageNode, "pinOne", "a1" ); else if ( fp->FindPadByNumber( "Anode" ) ) addAttribute( packageNode, "pinOne", "Anode" ); else if ( fp->FindPadByNumber( "ANODE" ) ) addAttribute( packageNode, "pinOne", "ANODE" ); else addAttribute( packageNode, "pinOne", "UNKNOWN" ); addAttribute( packageNode, "pinOneOrientation", "OTHER" ); const SHAPE_POLY_SET& courtyard = fp->GetCourtyard( F_CrtYd ); const SHAPE_POLY_SET& courtyard_back = fp->GetCourtyard( B_CrtYd ); if( courtyard.OutlineCount() > 0 ) addOutlineNode( packageNode, courtyard, courtyard.Outline( 0 ).Width(), LINE_STYLE::SOLID ); if( courtyard_back.OutlineCount() > 0 ) { otherSideViewNode = appendNode( packageNode, "OtherSideView" ); addOutlineNode( otherSideViewNode, courtyard_back, courtyard_back.Outline( 0 ).Width(), LINE_STYLE::SOLID ); } if( !courtyard.OutlineCount() && !courtyard_back.OutlineCount() ) { SHAPE_POLY_SET bbox = fp->GetBoundingHull(); addOutlineNode( packageNode, bbox ); } wxXmlNode* pickupPointNode = appendNode( packageNode, "PickupPoint" ); addAttribute( pickupPointNode, "x", "0.0" ); addAttribute( pickupPointNode, "y", "0.0" ); std::map>> elements; for( BOARD_ITEM* item : fp->GraphicalItems() ) { PCB_LAYER_ID layer = item->GetLayer(); /// IPC2581 only supports the documentation layers for production and post-production /// All other layers are ignored /// TODO: Decide if we should place the other layers from footprints on the board if( layer != F_SilkS && layer != B_SilkS && layer != F_Fab && layer != B_Fab ) continue; bool is_abs = true; if( item->Type() == PCB_SHAPE_T ) { PCB_SHAPE* shape = static_cast( item ); // Circles and Rectanges only have size information so we need to place them in // a separate node that has a location if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE ) is_abs = false; } elements[item->GetLayer()][is_abs].push_back( item ); } auto add_base_node = [&]( PCB_LAYER_ID aLayer ) -> wxXmlNode* { wxXmlNode* parent = packageNode; bool is_back = aLayer == B_SilkS || aLayer == B_Fab; if( is_back ) { if( !otherSideViewNode ) otherSideViewNode = appendNode( packageNode, "OtherSideView" ); parent = otherSideViewNode; } wxString name; if( aLayer == F_SilkS || aLayer == B_SilkS ) name = "SilkScreen"; else if( aLayer == F_Fab || aLayer == B_Fab ) name = "AssemblyDrawing"; else wxASSERT( false ); wxXmlNode* new_node = appendNode( parent, name ); return new_node; }; auto add_marking_node = [&]( wxXmlNode* aNode ) -> wxXmlNode* { wxXmlNode* marking_node = appendNode( aNode, "Marking" ); addAttribute( marking_node, "markingUsage", "NONE" ); return marking_node; }; std::map layer_nodes; std::map layer_bbox; for( auto layer : { F_Fab, B_Fab } ) { if( elements.find( layer ) != elements.end() ) { if( elements[layer][true].size() > 0 ) layer_bbox[layer] = elements[layer][true][0]->GetBoundingBox(); else if( elements[layer][false].size() > 0 ) layer_bbox[layer] = elements[layer][false][0]->GetBoundingBox(); } } for( auto& [layer, map] : elements ) { wxXmlNode* layer_node = add_base_node( layer ); wxXmlNode* marking_node = add_marking_node( layer_node ); wxXmlNode* group_node = appendNode( marking_node, "UserSpecial" ); bool update_bbox = false; if( layer == F_Fab || layer == B_Fab ) { layer_nodes[layer] = layer_node; update_bbox = true; } for( auto& [is_abs, vec] : map ) { for( BOARD_ITEM* item : vec ) { wxXmlNode* output_node = nullptr; if( update_bbox ) layer_bbox[layer].Merge( item->GetBoundingBox() ); if( !is_abs ) output_node = add_marking_node( layer_node ); else output_node = group_node; switch( item->Type() ) { case PCB_TEXT_T: { PCB_TEXT* text = static_cast( item ); addText( output_node, text, text->GetFontMetrics() ); break; } case PCB_TEXTBOX_T: { PCB_TEXTBOX* text = static_cast( item ); addText( output_node, text, text->GetFontMetrics() ); // We want to force this to be a polygon to get absolute coordinates if( text->IsBorderEnabled() ) { SHAPE_POLY_SET poly_set; text->GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE ); addContourNode( output_node, poly_set, 0, FILL_T::NO_FILL, text->GetBorderWidth() ); } break; } case PCB_SHAPE_T: { if( !is_abs ) addLocationNode( output_node, *static_cast( item ) ); addShape( output_node, *static_cast( item ) ); break; } default: break; } } } if( group_node->GetChildren() == nullptr ) { marking_node->RemoveChild( group_node ); layer_node->RemoveChild( marking_node ); delete group_node; delete marking_node; } } for( auto&[layer, bbox] : layer_bbox) { if( bbox.GetWidth() > 0 ) { wxXmlNode* outlineNode = insertNode( layer_nodes[layer], "Outline" ); SHAPE_POLY_SET::POLYGON outline( 1 ); std::vector points( 4 ); points[0] = bbox.GetPosition(); points[2] = bbox.GetEnd(); points[1].x = points[0].x; points[1].y = points[2].y; points[3].x = points[2].x; points[3].y = points[0].y; outline[0].Append( points ); addPolygonNode( outlineNode, outline, FILL_T::NO_FILL, 0 ); addLineDesc( outlineNode, 0, LINE_STYLE::SOLID ); } } for( size_t ii = 0; ii < fp->Pads().size(); ++ii ) { PAD* pad = fp->Pads()[ii]; wxXmlNode* pinNode = appendNode( packageNode, "Pin" ); wxString name = pad->GetNumber(); // Pins are required to have names, so if our pad doesn't have a name, we need to // generate one that is unique if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) name = wxString::Format( "NPTH%zu", ii ); else if( name.empty() ) name = wxString::Format( "PAD%zu", ii ); addAttribute( pinNode, "number", name ); m_net_pin_dict[pad->GetNetCode()].emplace_back( genString( fp->GetReference(), "CMP" ), genString( name, "PIN" ) ); if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) addAttribute( pinNode, "electricalType", "MECHANICAL" ); else if( pad->IsOnCopperLayer() ) addAttribute( pinNode, "electricalType", "ELECTRICAL" ); else addAttribute( pinNode, "electricalType", "UNDEFINED" ); if( pad->HasHole() ) addAttribute( pinNode, "type", "THRU" ); else addAttribute( pinNode, "type", "SURFACE" ); if( pad->GetFPRelativeOrientation() != EDA_ANGLE::m_Angle0 ) { wxXmlNode* xformNode = appendNode( pinNode, "Xform" ); xformNode->AddAttribute( "rotation", floatVal( pad->GetFPRelativeOrientation().Normalize().AsDegrees() ) ); } addLocationNode( pinNode, *pad, true ); addShape( pinNode, *pad, pad->GetLayer() ); // We just need the padstack, we don't need the reference here. The reference will be created // in the LayerFeature set wxXmlNode dummy; addPadStack( &dummy, pad ); } return packageNode; } void IPC2581_PLUGIN::generateComponents( wxXmlNode* aStepNode ) { std::vector componentNodes; std::vector packageNodes; std::set packageNames; bool generate_unique = m_OEMRef.empty(); for( FOOTPRINT* fp : m_board->Footprints() ) { wxXmlNode* componentNode = new wxXmlNode( wxXML_ELEMENT_NODE, "Component" ); addAttribute( componentNode, "refDes", genString( fp->GetReference(), "CMP" ) ); wxXmlNode* pkg = addPackage( componentNode, fp ); if( pkg ) packageNodes.push_back( pkg ); wxString name; PCB_FIELD* field = nullptr; if( !generate_unique ) field = fp->GetFieldByName( m_OEMRef ); if( field && !field->GetText().empty() ) { name = field->GetShownText( false ); } else { name = wxString::Format( "%s_%s_%s", fp->GetFPID().GetFullLibraryName(), fp->GetFPID().GetLibItemName().wx_str(), fp->GetValue() ); } if( !m_OEMRef_dict.emplace( fp, name ).second ) wxLogError( "Duplicate footprint pointers. Please report this bug." ); addAttribute( componentNode, "part", genString( name, "REF" ) ); addAttribute( componentNode, "layerRef", m_layer_name_map[fp->GetLayer()] ); if( fp->GetAttributes() & FP_THROUGH_HOLE ) addAttribute( componentNode, "mountType", "THMT" ); else if( fp->GetAttributes() & FP_SMD ) addAttribute( componentNode, "mountType", "SMT" ); else addAttribute( componentNode, "mountType", "OTHER" ); if( fp->GetOrientation() != EDA_ANGLE::m_Angle0 || fp->GetLayer() != F_Cu ) { wxXmlNode* xformNode = appendNode( componentNode, "Xform" ); if( fp->GetOrientation() != EDA_ANGLE::m_Angle0 ) addAttribute( xformNode, "rotation", floatVal( fp->GetOrientation().Normalize().AsDegrees() ) ); if( fp->GetLayer() != F_Cu ) addAttribute( xformNode, "mirror", "true" ); } addLocationNode( componentNode, fp->GetPosition().x, fp->GetPosition().y ); componentNodes.push_back( componentNode ); } for( wxXmlNode* padstack : m_padstacks ) { insertNode( aStepNode, padstack ); m_last_padstack = padstack; } for( wxXmlNode* pkg : packageNodes ) aStepNode->AddChild( pkg ); for( wxXmlNode* cmp : componentNodes ) aStepNode->AddChild( cmp ); } void IPC2581_PLUGIN::generateLogicalNets( wxXmlNode* aStepNode ) { for( auto& [ net, pin_pair] : m_net_pin_dict ) { wxXmlNode* netNode = appendNode( aStepNode, "LogicalNet" ); addAttribute( netNode, "name", genString( m_board->GetNetInfo().GetNetItem( net )->GetNetname(), "NET" ) ) ; for( auto& [cmp, pin] : pin_pair ) { wxXmlNode* netPinNode = appendNode( netNode, "PinRef" ); addAttribute( netPinNode, "componentRef", cmp ); addAttribute( netPinNode, "pin", pin ); } //TODO: Finish } } //TODO: Add PhyNetGroup section void IPC2581_PLUGIN::generateLayerFeatures( wxXmlNode* aStepNode ) { LSEQ layers = m_board->GetEnabledLayers().Seq(); const NETINFO_LIST& nets = m_board->GetNetInfo(); std::vector> footprints; // To avoid the overhead of repeatedly cycling through the layers and nets, // we pre-sort the board items into a map of layer -> net -> items std::map>> elements; std::for_each( m_board->Tracks().begin(), m_board->Tracks().end(), [&layers, &elements]( PCB_TRACK* aTrack ) { if( aTrack->Type() == PCB_VIA_T ) { PCB_VIA* via = static_cast( aTrack ); for( PCB_LAYER_ID layer : layers ) { if( via->FlashLayer( layer ) ) elements[layer][via->GetNetCode()].push_back( via ); } } else { elements[aTrack->GetLayer()][aTrack->GetNetCode()].push_back( aTrack ); } } ); std::for_each( m_board->Zones().begin(), m_board->Zones().end(), [ &elements ]( ZONE* zone ) { LSEQ zone_layers = zone->GetLayerSet().Seq(); for( PCB_LAYER_ID layer : zone_layers ) { elements[layer][zone->GetNetCode()].push_back( zone ); } } ); for( BOARD_ITEM* item : m_board->Drawings() ) { if( BOARD_CONNECTED_ITEM* conn_it = dynamic_cast( item ) ) elements[conn_it->GetLayer()][conn_it->GetNetCode()].push_back( conn_it ); else elements[item->GetLayer()][0].push_back( item ); } for( FOOTPRINT* fp : m_board->Footprints() ) { for( PCB_FIELD* field : fp->GetFields() ) elements[field->GetLayer()][0].push_back( field ); for( BOARD_ITEM* item : fp->GraphicalItems() ) elements[item->GetLayer()][0].push_back( item ); for( PAD* pad : fp->Pads() ) { LSEQ pad_layers = pad->GetLayerSet().Seq(); for( PCB_LAYER_ID layer : pad_layers ) { if( fp->IsFlipped() ) layer = FlipLayer( layer ); if( pad->FlashLayer( layer ) ) elements[layer][pad->GetNetCode()].push_back( pad ); } } } for( PCB_LAYER_ID layer : layers ) { if( m_progress_reporter ) m_progress_reporter->SetMaxProgress( nets.GetNetCount() * layers.size() ); wxXmlNode* layerNode = appendNode( aStepNode, "LayerFeature" ); addAttribute( layerNode, "layerRef", m_layer_name_map[layer] ); for( const NETINFO_ITEM* net : nets ) { if( m_progress_reporter ) { m_progress_reporter->Report( wxString::Format( _( "Exporting Layer %s, Net %s" ), m_board->GetLayerName( layer ), net->GetNetname() ) ); m_progress_reporter->AdvanceProgress(); } std::vector& vec = elements[layer][net->GetNetCode()]; std::stable_sort( vec.begin(), vec.end(), []( BOARD_ITEM* a, BOARD_ITEM* b ) { if( a->GetParentFootprint() == b->GetParentFootprint() ) return a->Type() < b->Type(); return a->GetParentFootprint() < b->GetParentFootprint(); } ); if( vec.empty() ) continue; generateLayerSetNet( layerNode, layer, vec ); } if( layerNode->GetChildren() == nullptr ) { aStepNode->RemoveChild( layerNode ); delete layerNode; } } } void IPC2581_PLUGIN::generateLayerSetDrill( wxXmlNode* aLayerNode ) { int hole_count = 1; for( const auto& [layer_pair, vec] : m_drill_layers ) { wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" ); layerNode->AddAttribute( "layerRef", genString( wxString::Format( "%s_%s", m_board->GetLayerName( layer_pair.first ), m_board->GetLayerName( layer_pair.second ) ), "DRILL" ) ); for( BOARD_ITEM* item : vec ) { if( item->Type() == PCB_VIA_T ) { PCB_VIA* via = static_cast( item ); auto it = m_padstack_dict.find( hash_fp_item( via, 0 ) ); if( it == m_padstack_dict.end() ) { wxLogError( "Failed to find padstack for via" ); continue; } wxXmlNode* padNode = appendNode( layerNode, "Set" ); addAttribute( padNode, "geometry", it->second ); if( via->GetNetCode() > 0 ) addAttribute( padNode, "net", genString( via->GetNetname(), "NET" ) ); wxXmlNode* holeNode = appendNode( padNode, "Hole" ); addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) ); addAttribute( holeNode, "diameter", floatVal( m_scale * via->GetDrillValue() ) ); addAttribute( holeNode, "platingStatus", "VIA" ); addAttribute( holeNode, "plusTol", "0.0" ); addAttribute( holeNode, "minusTol", "0.0" ); addXY( holeNode, via->GetPosition() ); } else if( item->Type() == PCB_PAD_T ) { PAD* pad = static_cast( item ); auto it = m_padstack_dict.find( hash_fp_item( pad, 0 ) ); if( it == m_padstack_dict.end() ) { wxLogError( "Failed to find padstack for pad" ); continue; } wxXmlNode* padNode = appendNode( layerNode, "Set" ); addAttribute( padNode, "geometry", it->second ); if( pad->GetNetCode() > 0 ) addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) ); wxXmlNode* holeNode = appendNode( padNode, "Hole" ); addAttribute( holeNode, "name", wxString::Format( "H%d", hole_count++ ) ); addAttribute( holeNode, "diameter", floatVal( m_scale * pad->GetDrillSizeX() ) ); addAttribute( holeNode, "platingStatus", pad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" ); addAttribute( holeNode, "plusTol", "0.0" ); addAttribute( holeNode, "minusTol", "0.0" ); addXY( holeNode, pad->GetPosition() ); } } } hole_count = 1; for( const auto& [layer_pair, vec] : m_slot_holes ) { wxXmlNode* layerNode = appendNode( aLayerNode, "LayerFeature" ); layerNode->AddAttribute( "layerRef", genString( wxString::Format( "%s_%s", m_board->GetLayerName( layer_pair.first ), m_board->GetLayerName( layer_pair.second ) ), "SLOT" ) ); for( PAD* pad : vec ) { wxXmlNode* padNode = appendNode( layerNode, "Set" ); if( pad->GetNetCode() > 0 ) addAttribute( padNode, "net", genString( pad->GetNetname(), "NET" ) ); addSlotCavity( padNode, *pad, wxString::Format( "SLOT%d", hole_count++ ) ); } } } void IPC2581_PLUGIN::generateLayerSetNet( wxXmlNode* aLayerNode, PCB_LAYER_ID aLayer, std::vector& aItems ) { auto it = aItems.begin(); wxXmlNode* layerSetNode = appendNode( aLayerNode, "Set" ); wxXmlNode* featureSetNode = appendNode( layerSetNode, "Features" ); wxXmlNode* specialNode = appendNode( featureSetNode, "UserSpecial" ); bool has_via = false; bool has_pad = false; wxXmlNode* padSetNode = nullptr; wxXmlNode* viaSetNode = nullptr; wxXmlNode* teardropLayerSetNode = nullptr; wxXmlNode* teardropFeatureSetNode = nullptr; if( BOARD_CONNECTED_ITEM* item = dynamic_cast( *it ); IsCopperLayer( aLayer ) && item ) { if( item->GetNetCode() > 0 ) addAttribute( layerSetNode, "net", genString( item->GetNetname(), "NET" ) ); } auto add_track = [&]( PCB_TRACK* track ) { if( track->Type() == PCB_TRACE_T ) { PCB_SHAPE shape( nullptr, SHAPE_T::SEGMENT ); shape.SetStart( track->GetStart() ); shape.SetEnd( track->GetEnd() ); shape.SetWidth( track->GetWidth() ); addShape( specialNode, shape ); } else if( track->Type() == PCB_ARC_T ) { PCB_ARC* arc = static_cast( track ); PCB_SHAPE shape( nullptr, SHAPE_T::ARC ); shape.SetArcGeometry( arc->GetStart(), arc->GetMid(), arc->GetEnd() ); shape.SetWidth( arc->GetWidth() ); addShape( specialNode, shape ); } else { if( !viaSetNode ) { if( !has_pad ) { viaSetNode = layerSetNode; has_via = true; } else { viaSetNode = appendNode( layerSetNode, "Set" ); if( track->GetNetCode() > 0 ) addAttribute( viaSetNode, "net", genString( track->GetNetname(), "NET" ) ); } addAttribute( viaSetNode, "padUsage", "VIA" ); } addVia( viaSetNode, static_cast( track ), aLayer ); } }; auto add_zone = [&]( ZONE* zone ) { wxXmlNode* zoneFeatureNode = specialNode; if( zone->IsTeardropArea() && m_version > 'B' ) { if( !teardropFeatureSetNode ) { teardropLayerSetNode = appendNode( aLayerNode, "Set" ); addAttribute( teardropLayerSetNode, "geometryUsage", "TEARDROP" ); if( zone->GetNetCode() > 0 ) addAttribute( teardropLayerSetNode, "net", genString( zone->GetNetname(), "NET" ) ); wxXmlNode* new_teardrops = appendNode( teardropLayerSetNode, "Features" ); addLocationNode( new_teardrops, 0.0, 0.0 ); teardropFeatureSetNode = appendNode( new_teardrops, "UserSpecial" ); } zoneFeatureNode = teardropFeatureSetNode; } else { if( FOOTPRINT* fp = zone->GetParentFootprint() ) { wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" ); addAttribute( tempSetNode, "componentRef", genString( fp->GetReference(), "CMP" ) ); wxXmlNode* newFeatures = appendNode( tempSetNode, "Features" ); addLocationNode( newFeatures, 0.0, 0.0 ); zoneFeatureNode = appendNode( newFeatures, "UserSpecial" ); } } SHAPE_POLY_SET& zone_shape = *zone->GetFilledPolysList( aLayer ); for( int ii = 0; ii < zone_shape.OutlineCount(); ++ii ) addContourNode( zoneFeatureNode, zone_shape, ii ); }; auto add_shape = [&] ( PCB_SHAPE* shape ) { FOOTPRINT* fp = shape->GetParentFootprint(); if( fp ) { wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" ); if( m_version > 'B' ) addAttribute( tempSetNode, "geometryUsage", "GRAPHIC" ); addAttribute( tempSetNode, "componentRef", genString( fp->GetReference(), "CMP" ) ); wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" ); addLocationNode( tempFeature, *shape ); addShape( tempFeature, *shape ); } else if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE ) { wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" ); if( shape->GetNetCode() > 0 ) addAttribute( tempSetNode, "net", genString( shape->GetNetname(), "NET" ) ); wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" ); addLocationNode( tempFeature, *shape ); addShape( tempFeature, *shape ); } else { addShape( specialNode, *shape ); } }; auto add_text = [&] ( BOARD_ITEM* text ) { EDA_TEXT* text_item; FOOTPRINT* fp = text->GetParentFootprint(); if( PCB_TEXT* tmp_text = dynamic_cast( text ) ) text_item = static_cast( tmp_text ); else if( PCB_TEXTBOX* tmp_text = dynamic_cast( text ) ) text_item = static_cast( tmp_text ); if( text_item->GetShownText( false ).empty() ) return; wxXmlNode* tempSetNode = appendNode( aLayerNode, "Set" ); if( m_version > 'B' ) addAttribute( tempSetNode, "geometryUsage", "TEXT" ); if( fp ) addAttribute( tempSetNode, "componentRef", genString( fp->GetReference(), "CMP" ) ); wxXmlNode* nonStandardAttributeNode = appendNode( tempSetNode, "NonstandardAttribute" ); addAttribute( nonStandardAttributeNode, "name", "TEXT" ); addAttribute( nonStandardAttributeNode, "value", text_item->GetShownText( false ) ); addAttribute( nonStandardAttributeNode, "type", "STRING" ); wxXmlNode* tempFeature = appendNode( tempSetNode, "Features" ); addLocationNode( tempFeature, 0.0, 0.0 ); addText( tempFeature, text_item, text->GetFontMetrics() ); if( text->Type() == PCB_TEXTBOX_T ) { PCB_TEXTBOX* textbox = static_cast( text ); if( textbox->IsBorderEnabled() ) addShape( tempFeature, *static_cast( textbox ) ); } }; auto add_pad = [&]( PAD* pad ) { if( !padSetNode ) { if( !has_via ) { padSetNode = layerSetNode; has_pad = true; } else { padSetNode = appendNode( aLayerNode, "Set" ); if( pad->GetNetCode() > 0 ) addAttribute( padSetNode, "net", genString( pad->GetNetname(), "NET" ) ); } } FOOTPRINT* fp = pad->GetParentFootprint(); if( fp && fp->IsFlipped() ) addPad( padSetNode, pad, FlipLayer( aLayer ) ); else addPad( padSetNode, pad, aLayer ); }; for( BOARD_ITEM* item : aItems ) { switch( item->Type() ) { case PCB_TRACE_T: case PCB_ARC_T: case PCB_VIA_T: add_track( static_cast( item ) ); break; case PCB_ZONE_T: add_zone( static_cast( item ) ); break; case PCB_PAD_T: add_pad( static_cast( item ) ); break; case PCB_SHAPE_T: add_shape( static_cast( item ) ); break; case PCB_TEXT_T: case PCB_TEXTBOX_T: case PCB_FIELD_T: add_text( item ); break; case PCB_DIMENSION_T: case PCB_TARGET_T: case PCB_DIM_ALIGNED_T: case PCB_DIM_LEADER_T: case PCB_DIM_CENTER_T: case PCB_DIM_RADIAL_T: case PCB_DIM_ORTHOGONAL_T: //TODO: Add support for dimensions break; default: wxLogDebug( "Unhandled type %s", ENUM_MAP::Instance().ToString( item->Type() ) ); } } if( specialNode->GetChildren() == nullptr ) { featureSetNode->RemoveChild( specialNode ); layerSetNode->RemoveChild( featureSetNode ); aLayerNode->RemoveChild( layerSetNode ); delete specialNode; delete featureSetNode; delete layerSetNode; } } wxXmlNode* IPC2581_PLUGIN::generateAvlSection() { if( m_progress_reporter ) m_progress_reporter->AdvancePhase( _( "Generating BOM section" ) ); wxXmlNode* avl = appendNode( m_xml_root, "Avl" ); addAttribute( avl, "name", "Primary_Vendor_List" ); wxXmlNode* header = appendNode( avl, "AvlHeader" ); addAttribute( header, "title", "BOM" ); addAttribute( header, "source", "KiCad" ); addAttribute( header, "author", "OWNER" ); addAttribute( header, "datetime", wxDateTime::Now().FormatISOCombined() ); addAttribute( header, "version", "1" ); std::set unique_parts; std::map unique_vendors; for( auto& [fp, name] : m_OEMRef_dict ) { auto [ it, success ] = unique_parts.insert( name ); if( !success ) continue; wxXmlNode* part = appendNode( avl, "AvlItem" ); addAttribute( part, "OEMDesignNumber", genString( name, "REF" ) ); PCB_FIELD* nums[2] = { fp->GetFieldByName( m_mpn ), fp->GetFieldByName( m_distpn ) }; PCB_FIELD* company[2] = { fp->GetFieldByName( m_mfg ), nullptr }; wxString company_name[2] = { m_mfg, m_dist }; for ( int ii = 0; ii < 2; ++ii ) { if( nums[ii] ) { wxString mpn_name = nums[ii]->GetShownText( false ); if( mpn_name.empty() ) continue; wxXmlNode* vmpn = appendNode( part, "AvlVmpn" ); addAttribute( vmpn, "qualified", "false" ); addAttribute( vmpn, "chosen", "false" ); wxXmlNode* mpn = appendNode( vmpn, "AvlMpn" ); addAttribute( mpn, "name", mpn_name ); wxXmlNode* vendor = appendNode( vmpn, "AvlVendor" ); wxString name = wxT( "UNKNOWN" ); // If the field resolves, then use that field content unless it is empty if( !ii && company[ii] ) { wxString tmp = company[ii]->GetShownText( false ); if( !tmp.empty() ) name = tmp; } // If it doesn't resolve but there is content from the dialog, use the static content else if( !ii && !company_name[ii].empty() ) { name = company_name[ii]; } else if( ii && !m_dist.empty() ) { name = m_dist; } auto [vendor_id, inserted] = unique_vendors.emplace( name, wxString::Format( "VENDOR_%zu", unique_vendors.size() ) ); addAttribute( vendor, "enterpriseRef", vendor_id->second ); if( inserted ) { wxXmlNode* new_vendor = new wxXmlNode( wxXML_ELEMENT_NODE, "Enterprise" ); addAttribute( new_vendor, "id", vendor_id->second ); addAttribute( new_vendor, "name", name ); addAttribute( new_vendor, "code", "NONE" ); insertNodeAfter( m_enterpriseNode, new_vendor ); m_enterpriseNode = new_vendor; } } } } return avl; } void IPC2581_PLUGIN::SaveBoard( const wxString& aFileName, BOARD* aBoard, const STRING_UTF8_MAP* aProperties, PROGRESS_REPORTER* aProgressReporter ) { m_board = aBoard; m_units_str = "MILLIMETER"; m_scale = 1.0 / PCB_IU_PER_MM; m_sigfig = 4; m_progress_reporter = aProgressReporter; if( auto it = aProperties->find( "units" ); it != aProperties->end() ) { if( it->second == "inch" ) { m_units_str = "INCH"; m_scale = 25.4 / PCB_IU_PER_MM; } } if( auto it = aProperties->find( "sigfig" ); it != aProperties->end() ) m_sigfig = std::stoi( it->second ); if( auto it = aProperties->find( "version" ); it != aProperties->end() ) m_version = it->second.c_str()[0]; if( auto it = aProperties->find( "OEMRef" ); it != aProperties->end() ) m_OEMRef = it->second.wx_str(); if( auto it = aProperties->find( "mpn" ); it != aProperties->end() ) m_mpn = it->second.wx_str(); if( auto it = aProperties->find( "mfg" ); it != aProperties->end() ) m_mfg = it->second.wx_str(); if( auto it = aProperties->find( "dist" ); it != aProperties->end() ) m_dist = it->second.wx_str(); if( auto it = aProperties->find( "distpn" ); it != aProperties->end() ) m_distpn = it->second.wx_str(); if( m_version == 'B' ) { for( char c = 'a'; c <= 'z'; ++c ) m_acceptable_chars.insert( c ); for( char c = 'A'; c <= 'Z'; ++c ) m_acceptable_chars.insert( c ); for( char c = '0'; c <= '9'; ++c ) m_acceptable_chars.insert( c ); // Add special characters std::string specialChars = "_\\-.+><"; for( char c : specialChars ) m_acceptable_chars.insert( c ); } m_xml_doc = new wxXmlDocument(); m_xml_root = generateXmlHeader(); generateContentSection(); if( m_progress_reporter ) { m_progress_reporter->SetNumPhases( 7 ); m_progress_reporter->BeginPhase( 1 ); m_progress_reporter->Report( _( "Generating logistic section" ) ); } generateLogisticSection(); generateHistorySection(); wxXmlNode* ecad_node = generateEcadSection(); generateBOMSection( ecad_node ); generateAvlSection(); if( m_progress_reporter ) { m_progress_reporter->AdvancePhase( _( "Saving file" ) ); } wxFileOutputStreamWithProgress out_stream( aFileName ); double written_bytes = 0.0; double last_yield = 0.0; // This is a rough estimation of the size of the spaces in the file // We just need to total to be slightly larger than the value of the // progress bar, so accurately counting spaces is not terribly important m_total_bytes += m_total_bytes / 10; auto update_progress = [&]( size_t aBytes ) { written_bytes += aBytes; double percent = written_bytes / static_cast( m_total_bytes ); if( m_progress_reporter ) { // Only update every percent if( last_yield + 0.01 < percent ) { last_yield = percent; m_progress_reporter->SetCurrentProgress( percent ); } } }; out_stream.SetProgressCallback( update_progress ); if( !m_xml_doc->Save( out_stream ) ) { wxLogError( _( "Failed to save file to buffer" ) ); return; } size_t size = out_stream.GetSize(); }