From b49ebaeb162584a873968b17663320004fc03faf Mon Sep 17 00:00:00 2001 From: "Andrzej.W" Date: Mon, 16 Sep 2024 02:39:12 +0200 Subject: [PATCH] ADDED: Soldermask layer option for graphic shapes Allows adding a soldermask opening for shapes on a copper layer. Soldermask expansion can also be specified. Fixes: https://gitlab.com/kicad/code/kicad/-/issues/2125 --- 3d-viewer/3d_canvas/board_adapter.h | 2 +- .../3d_canvas/create_3Dgraphic_brd_items.cpp | 38 +- 3d-viewer/3d_canvas/create_layer_items.cpp | 4 +- common/plotters/plotter.cpp | 5 +- include/layer_ids.h | 11 + include/plotters/plotter.h | 4 +- pcbnew/dialogs/dialog_shape_properties.cpp | 45 ++ .../dialogs/dialog_shape_properties_base.cpp | 37 +- .../dialogs/dialog_shape_properties_base.fbp | 707 +++++++++++++----- pcbnew/dialogs/dialog_shape_properties_base.h | 9 +- .../pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp | 13 +- .../pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h | 3 +- .../kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp | 12 +- pcbnew/pcb_painter.cpp | 39 +- pcbnew/pcb_shape.cpp | 122 +++ pcbnew/pcb_shape.h | 16 + pcbnew/plot_brditems_plotter.cpp | 70 +- 17 files changed, 914 insertions(+), 223 deletions(-) diff --git a/3d-viewer/3d_canvas/board_adapter.h b/3d-viewer/3d_canvas/board_adapter.h index a37ee8fe3e..55d6b0f791 100644 --- a/3d-viewer/3d_canvas/board_adapter.h +++ b/3d-viewer/3d_canvas/board_adapter.h @@ -388,7 +388,7 @@ private: const BOARD_ITEM* aOwner ); void addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aContainer, - const BOARD_ITEM* aOwner ); + const BOARD_ITEM* aOwner, PCB_LAYER_ID aLayer ); void addShape( const PCB_DIMENSION_BASE* aDimension, CONTAINER_2D_BASE* aDstContainer, const BOARD_ITEM* aOwner ); diff --git a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp index 179888150d..122539eb24 100644 --- a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp +++ b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp @@ -260,8 +260,8 @@ void BOARD_ADAPTER::addFootprintShapes( const FOOTPRINT* aFootprint, CONTAINER_2 { PCB_SHAPE* shape = static_cast( item ); - if( shape->GetLayer() == aLayerId ) - addShape( shape, aContainer, aFootprint ); + if( shape->IsOnLayer( aLayerId ) ) + addShape( shape, aContainer, aFootprint, aLayerId ); break; } @@ -600,10 +600,22 @@ void BOARD_ADAPTER::createArcSegments( const VECTOR2I& aCentre, const VECTOR2I& void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aContainer, - const BOARD_ITEM* aOwner ) + const BOARD_ITEM* aOwner, PCB_LAYER_ID aLayer ) { // The full width of the lines to create - const float linewidth3DU = TO_3DU( aShape->GetWidth() ); + int linewidth = aShape->GetWidth(); + int margin = 0; + + if( IsSolderMaskLayer( aLayer ) + && aShape->HasSolderMask() + && IsExternalCopperLayer( aShape->GetLayer() ) ) + { + margin = aShape->GetSolderMaskExpansion(); + linewidth += margin * 2; + } + + float linewidth3DU = TO_3DU( linewidth ); + LINE_STYLE lineStyle = aShape->GetStroke().GetLineStyle(); if( lineStyle <= LINE_STYLE::FIRST_TYPE ) @@ -634,6 +646,12 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta polyList.Simplify( SHAPE_POLY_SET::PM_FAST ); + if( margin != 0 ) + { + polyList.Inflate( margin, CORNER_STRATEGY::ROUND_ALL_CORNERS, + GetBoard()->GetDesignSettings().m_MaxError ); + } + ConvertPolygonToTriangles( polyList, *aContainer, m_biuTo3Dunits, *aOwner ); } else @@ -656,7 +674,7 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta unsigned int segCount = GetCircleSegmentCount( aShape->GetBoundingBox().GetSizeMax() ); createArcSegments( aShape->GetCenter(), aShape->GetStart(), aShape->GetArcAngle(), - segCount, aShape->GetWidth(), aContainer, *aOwner ); + segCount, linewidth, aContainer, *aOwner ); break; } @@ -681,6 +699,14 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta if( polyList.IsEmpty() ) // Just for caution break; + if( margin != 0 ) + { + CORNER_STRATEGY cornerStr = margin >= 0 ? CORNER_STRATEGY::ROUND_ALL_CORNERS + : CORNER_STRATEGY::ALLOW_ACUTE_CORNERS; + + polyList.Inflate( margin, cornerStr, GetBoard()->GetDesignSettings().m_MaxError ); + } + ConvertPolygonToTriangles( polyList, *aContainer, m_biuTo3Dunits, *aOwner ); break; } @@ -733,7 +759,7 @@ void BOARD_ADAPTER::addShape( const PCB_TEXTBOX* aTextBox, CONTAINER_2D_BASE* aC if( aTextBox->GetShape() == SHAPE_T::RECTANGLE ) { - addShape( static_cast( aTextBox ), aContainer, aOwner ); + addShape( static_cast( aTextBox ), aContainer, aOwner, UNDEFINED_LAYER ); } else { diff --git a/3d-viewer/3d_canvas/create_layer_items.cpp b/3d-viewer/3d_canvas/create_layer_items.cpp index 909623fcce..9b7374fcd5 100644 --- a/3d-viewer/3d_canvas/create_layer_items.cpp +++ b/3d-viewer/3d_canvas/create_layer_items.cpp @@ -611,7 +611,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter ) switch( item->Type() ) { case PCB_SHAPE_T: - addShape( static_cast( item ), layerContainer, item ); + addShape( static_cast( item ), layerContainer, item, layer ); break; case PCB_TEXT_T: @@ -844,7 +844,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter ) switch( item->Type() ) { case PCB_SHAPE_T: - addShape( static_cast( item ), layerContainer, item ); + addShape( static_cast( item ), layerContainer, item, layer ); break; case PCB_TEXT_T: diff --git a/common/plotters/plotter.cpp b/common/plotters/plotter.cpp index b1fbf73c57..89c8a7f67d 100644 --- a/common/plotters/plotter.cpp +++ b/common/plotters/plotter.cpp @@ -593,7 +593,8 @@ void PLOTTER::ThickArc( const VECTOR2D& centre, const EDA_ANGLE& aStartAngle, } -void PLOTTER::ThickArc( const EDA_SHAPE& aArcShape, OUTLINE_MODE aTraceMode, void* aData ) +void PLOTTER::ThickArc( const EDA_SHAPE& aArcShape, OUTLINE_MODE aTraceMode, void* aData, + int aWidth ) { VECTOR2D center = aArcShape.getCenter(); VECTOR2D mid = aArcShape.GetArcMid(); @@ -614,7 +615,7 @@ void PLOTTER::ThickArc( const EDA_SHAPE& aArcShape, OUTLINE_MODE aTraceMode, voi double radius = ( start - center ).EuclideanNorm(); - ThickArc( center, startAngle, angle, radius, aArcShape.GetWidth(), aTraceMode, aData ); + ThickArc( center, startAngle, angle, radius, aWidth, aTraceMode, aData ); } diff --git a/include/layer_ids.h b/include/layer_ids.h index 09cda80153..388bbe383c 100644 --- a/include/layer_ids.h +++ b/include/layer_ids.h @@ -532,6 +532,17 @@ inline bool IsCopperLayer( int aLayerId ) return !( aLayerId & 1 ) && aLayerId <= PCB_LAYER_ID_COUNT; } +/** + * Tests whether a layer is an external (F_Cu or B_Cu) copper layer. + * + * @param aLayerId = Layer to test + * @return true if aLayer is a valid external copper layer + */ +inline bool IsExternalCopperLayer( int aLayerId ) +{ + return aLayerId == F_Cu || aLayerId == B_Cu; +} + /** * Test whether a layer is a non copper layer. * diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h index d32b5c53b7..3f423d2ba5 100644 --- a/include/plotters/plotter.h +++ b/include/plotters/plotter.h @@ -301,8 +301,8 @@ public: virtual void ThickSegment( const VECTOR2I& start, const VECTOR2I& end, int width, OUTLINE_MODE tracemode, void* aData ); - virtual void ThickArc( const EDA_SHAPE& aArcShape, - OUTLINE_MODE aTraceMode, void* aData ); + virtual void ThickArc( const EDA_SHAPE& aArcShape, OUTLINE_MODE aTraceMode, void* aData, + int aWidth ); virtual void ThickArc( const VECTOR2D& aCentre, const EDA_ANGLE& aStAngle, const EDA_ANGLE& aAngle, double aRadius, int aWidth, diff --git a/pcbnew/dialogs/dialog_shape_properties.cpp b/pcbnew/dialogs/dialog_shape_properties.cpp index cad775326a..6ac5149069 100644 --- a/pcbnew/dialogs/dialog_shape_properties.cpp +++ b/pcbnew/dialogs/dialog_shape_properties.cpp @@ -56,6 +56,8 @@ private: void onLayerSelection( wxCommandEvent& event ) override; + void onTechLayersChanged( wxCommandEvent& event ) override; + bool Validate() override; // Show/hide the widgets used in net selection (shown only for copper layers) @@ -67,6 +69,19 @@ private: m_netLabel->Show( isCopper ); } + void showHideTechLayers() + { + bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() ); + + m_techLayersLabel->Enable( isExtCopper ); + m_hasSolderMask->Enable( isExtCopper ); + + bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue(); + + m_solderMaskMarginLabel->Enable( showMaskMargin ); + m_solderMaskMarginCtrl->Enable( showMaskMargin ); + m_solderMaskMarginUnit->Enable( showMaskMargin ); + } private: PCB_BASE_EDIT_FRAME* m_parent; @@ -82,6 +97,7 @@ private: UNIT_BINDER m_rectangleWidth; UNIT_BINDER m_bezierCtrl1X, m_bezierCtrl1Y; UNIT_BINDER m_bezierCtrl2X, m_bezierCtrl2Y; + UNIT_BINDER m_solderMaskMargin; bool m_flipStartEnd; }; @@ -104,11 +120,15 @@ DIALOG_SHAPE_PROPERTIES::DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, m_bezierCtrl1Y( aParent, m_BezierPointC1YLabel, m_BezierC1Y_Ctrl, m_BezierPointC1YUnit ), m_bezierCtrl2X( aParent, m_BezierPointC2XLabel, m_BezierC2X_Ctrl, m_BezierPointC2XUnit ), m_bezierCtrl2Y( aParent, m_BezierPointC2YLabel, m_BezierC2Y_Ctrl, m_BezierPointC2YUnit ), + m_solderMaskMargin( aParent, m_solderMaskMarginLabel, m_solderMaskMarginCtrl, m_solderMaskMarginUnit ), m_flipStartEnd( false ) { SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) ); m_hash_key = TO_UTF8( GetTitle() ); + wxFont infoFont = KIUI::GetInfoFont( this ); + m_techLayersLabel->SetFont( infoFont ); + // Configure display origin transforms m_startX.SetCoordType( ORIGIN_TRANSFORMS::ABS_X_COORD ); m_startY.SetCoordType( ORIGIN_TRANSFORMS::ABS_Y_COORD ); @@ -240,6 +260,8 @@ void DIALOG_SHAPE_PROPERTIES::onLayerSelection( wxCommandEvent& event ) { showHideNetInfo(); } + + showHideTechLayers(); } @@ -266,6 +288,13 @@ void DIALOG_SHAPE_PROPERTIES::onFilledCheckbox( wxCommandEvent& event ) } } + +void DIALOG_SHAPE_PROPERTIES::onTechLayersChanged( wxCommandEvent& event ) +{ + showHideTechLayers(); +} + + bool DIALOG_SHAPE_PROPERTIES::TransferDataToWindow() { if( !m_item ) @@ -340,7 +369,16 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataToWindow() wxFAIL_MSG( "Line type not found in the type lookup map" ); m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() ); + + m_hasSolderMask->SetValue( m_item->HasSolderMask() ); + + if( m_item->GetLocalSolderMaskMargin().has_value() ) + m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() ); + else + m_solderMaskMargin.SetValue( wxEmptyString ); + showHideNetInfo(); + showHideTechLayers(); return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow(); } @@ -519,6 +557,13 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow() m_item->SetLayer( ToLAYER_ID( layer ) ); + m_item->SetHasSolderMask( m_hasSolderMask->GetValue() ); + + if( m_solderMaskMargin.IsNull() ) + m_item->SetLocalSolderMaskMargin( {} ); + else + m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() ); + m_item->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); if( m_item->IsOnCopperLayer() ) diff --git a/pcbnew/dialogs/dialog_shape_properties_base.cpp b/pcbnew/dialogs/dialog_shape_properties_base.cpp index 399627666a..49fad33f48 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.cpp +++ b/pcbnew/dialogs/dialog_shape_properties_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf) +// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -253,12 +253,41 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx m_LayerSelectionCtrl = new PCB_LAYER_BOX_SELECTOR( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); gbSizer2->Add( m_LayerSelectionCtrl, wxGBPosition( 6, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + m_techLayersLabel = new wxStaticText( this, wxID_ANY, _("Technical Layers:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_techLayersLabel->Wrap( -1 ); + gbSizer2->Add( m_techLayersLabel, wxGBPosition( 7, 0 ), wxGBSpan( 1, 1 ), wxLEFT, 5 ); + + wxFlexGridSizer* fgSizer2; + fgSizer2 = new wxFlexGridSizer( 0, 4, 0, 0 ); + fgSizer2->AddGrowableCol( 2 ); + fgSizer2->SetFlexibleDirection( wxBOTH ); + fgSizer2->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + m_hasSolderMask = new wxCheckBox( this, wxID_ANY, _("Solder mask"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer2->Add( m_hasSolderMask, 0, wxALL, 5 ); + + m_solderMaskMarginLabel = new wxStaticText( this, wxID_ANY, _("Expansion:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_solderMaskMarginLabel->Wrap( -1 ); + fgSizer2->Add( m_solderMaskMarginLabel, 0, wxALL, 5 ); + + m_solderMaskMarginCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_solderMaskMarginCtrl->SetToolTip( _("This is the local clearance between the shape and the solder mask opening.\nLeave blank to use the value defined in the Board Setup.") ); + + fgSizer2->Add( m_solderMaskMarginCtrl, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); + + m_solderMaskMarginUnit = new wxStaticText( this, wxID_ANY, _("unit"), wxDefaultPosition, wxDefaultSize, 0 ); + m_solderMaskMarginUnit->Wrap( -1 ); + fgSizer2->Add( m_solderMaskMarginUnit, 0, wxALL, 5 ); + + + gbSizer2->Add( fgSizer2, wxGBPosition( 8, 0 ), wxGBSpan( 1, 3 ), wxEXPAND, 5 ); + m_netLabel = new wxStaticText( this, wxID_ANY, _("Net:"), wxDefaultPosition, wxDefaultSize, 0 ); m_netLabel->Wrap( -1 ); - gbSizer2->Add( m_netLabel, wxGBPosition( 7, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + gbSizer2->Add( m_netLabel, wxGBPosition( 9, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); m_netSelector = new NET_SELECTOR( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); - gbSizer2->Add( m_netSelector, wxGBPosition( 7, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); + gbSizer2->Add( m_netSelector, wxGBPosition( 9, 1 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT, 5 ); gbSizer2->AddGrowableCol( 1 ); @@ -285,6 +314,7 @@ DIALOG_SHAPE_PROPERTIES_BASE::DIALOG_SHAPE_PROPERTIES_BASE( wxWindow* parent, wx // Connect Events m_filledCtrl->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onFilledCheckbox ), NULL, this ); m_LayerSelectionCtrl->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onLayerSelection ), NULL, this ); + m_hasSolderMask->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onTechLayersChanged ), NULL, this ); } DIALOG_SHAPE_PROPERTIES_BASE::~DIALOG_SHAPE_PROPERTIES_BASE() @@ -292,5 +322,6 @@ DIALOG_SHAPE_PROPERTIES_BASE::~DIALOG_SHAPE_PROPERTIES_BASE() // Disconnect Events m_filledCtrl->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onFilledCheckbox ), NULL, this ); m_LayerSelectionCtrl->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onLayerSelection ), NULL, this ); + m_hasSolderMask->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SHAPE_PROPERTIES_BASE::onTechLayersChanged ), NULL, this ); } diff --git a/pcbnew/dialogs/dialog_shape_properties_base.fbp b/pcbnew/dialogs/dialog_shape_properties_base.fbp index 2f235bc13f..27c94eb374 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.fbp +++ b/pcbnew/dialogs/dialog_shape_properties_base.fbp @@ -1,34 +1,36 @@ - + - C++ - 1 - source_name - 0 - 0 + + 1 + connect + none + + + 0 + 0 res UTF-8 - connect dialog_shape_properties_base 1000 - none - - 1 + 1 + UI DIALOG_SHAPE_PROPERTIES_BASE - . - + 0 + source_name + 1 + 0 + source_name + + + 1 1 - 1 - 1 - 1 - UI - 0 - 0 0 + 0 0 wxAUI_MGR_DEFAULT @@ -98,10 +100,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -163,10 +165,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -228,10 +230,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -296,10 +298,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -361,10 +363,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -426,10 +428,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -494,10 +496,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -559,10 +561,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -624,10 +626,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -689,10 +691,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -757,10 +759,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -822,10 +824,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -887,10 +889,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -955,10 +957,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1037,10 +1039,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1102,10 +1104,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1167,10 +1169,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1235,10 +1237,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1300,10 +1302,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1365,10 +1367,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1433,10 +1435,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1498,10 +1500,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1563,10 +1565,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1628,10 +1630,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1696,10 +1698,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1761,10 +1763,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1826,10 +1828,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1894,10 +1896,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -1983,10 +1985,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2045,10 +2047,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2110,10 +2112,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2172,10 +2174,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2234,10 +2236,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2299,10 +2301,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2361,10 +2363,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2423,10 +2425,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2488,10 +2490,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2550,10 +2552,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2612,10 +2614,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2677,10 +2679,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2739,10 +2741,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2801,10 +2803,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2866,10 +2868,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -2950,10 +2952,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3018,10 +3020,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3087,10 +3089,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3152,10 +3154,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3220,10 +3222,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3285,10 +3287,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3350,10 +3352,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3419,10 +3421,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3484,10 +3486,10 @@ 1 1 1 - + 0 - - + 0 + 0 @@ -3542,22 +3544,363 @@ onLayerSelection + + 5 + 1 + 0 + wxLEFT + 7 + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 0 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Technical Layers: + 0 + + 0 + + + 0 + + 1 + m_techLayersLabel + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + 5 + 3 + 0 + wxEXPAND + 8 + 1 + + 4 + wxBOTH + 2 + + 0 + + fgSizer2 + wxFLEX_GROWMODE_SPECIFIED + none + 0 + 0 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Solder mask + + 0 + + + 0 + + 1 + m_hasSolderMask + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onTechLayersChanged + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 0 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Expansion: + 0 + + 0 + + + 0 + + 1 + m_solderMaskMarginLabel + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER_VERTICAL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + -1,-1 + 1 + m_solderMaskMarginCtrl + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + This is the local clearance between the shape and the solder mask opening. Leave blank to use the value defined in the Board Setup. + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + unit + 0 + + 0 + + + 0 + + 1 + m_solderMaskMarginUnit + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + 5 1 0 wxALIGN_CENTER_VERTICAL|wxLEFT - 7 + 9 1 1 1 1 1 - + 0 - - + 0 + 0 @@ -3612,17 +3955,17 @@ 2 1 wxALIGN_CENTER_VERTICAL|wxEXPAND|wxRIGHT - 7 + 9 1 1 1 1 1 - + 0 - - + 0 + 0 diff --git a/pcbnew/dialogs/dialog_shape_properties_base.h b/pcbnew/dialogs/dialog_shape_properties_base.h index 0fa7f87aa5..9863dd627f 100644 --- a/pcbnew/dialogs/dialog_shape_properties_base.h +++ b/pcbnew/dialogs/dialog_shape_properties_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf) +// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -30,7 +30,6 @@ class PCB_LAYER_BOX_SELECTOR; /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// /// Class DIALOG_SHAPE_PROPERTIES_BASE /////////////////////////////////////////////////////////////////////////////// @@ -94,6 +93,11 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM wxBitmapComboBox* m_lineStyleCombo; wxStaticText* m_LayerLabel; PCB_LAYER_BOX_SELECTOR* m_LayerSelectionCtrl; + wxStaticText* m_techLayersLabel; + wxCheckBox* m_hasSolderMask; + wxStaticText* m_solderMaskMarginLabel; + wxTextCtrl* m_solderMaskMarginCtrl; + wxStaticText* m_solderMaskMarginUnit; wxStaticText* m_netLabel; NET_SELECTOR* m_netSelector; wxStdDialogButtonSizer* m_StandardButtonsSizer; @@ -103,6 +107,7 @@ class DIALOG_SHAPE_PROPERTIES_BASE : public DIALOG_SHIM // Virtual event handlers, override them in your derived class virtual void onFilledCheckbox( wxCommandEvent& event ) { event.Skip(); } virtual void onLayerSelection( wxCommandEvent& event ) { event.Skip(); } + virtual void onTechLayersChanged( wxCommandEvent& event ) { event.Skip(); } public: diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp index ad03b019b8..c2840d303e 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -1058,7 +1058,18 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_SHAPE* aShape, int aNestLevel ) const m_out->Print( 0, aShape->IsFilled() ? " (fill solid)" : " (fill none)" ); } - formatLayer( aShape->GetLayer() ); + if( aShape->GetLayerSet().count() > 1 ) + formatLayers( aShape->GetLayerSet() ); + else + formatLayer( aShape->GetLayer() ); + + if( aShape->HasSolderMask() + && aShape->GetLocalSolderMaskMargin().has_value() + && IsExternalCopperLayer( aShape->GetLayer() ) ) + { + m_out->Print( 0, " (solder_mask_margin %s)", + formatInternalUnits( aShape->GetLocalSolderMaskMargin().value() ).c_str() ); + } if( aShape->GetNetCode() > 0 ) m_out->Print( 0, " (net %d)", m_mapping->Translate( aShape->GetNetCode() ) ); diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h index eb3f9bbaa8..436927d6e6 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -167,7 +167,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl //#define SEXPR_BOARD_FILE_VERSION 20240929 // Complex padstacks //#define SEXPR_BOARD_FILE_VERSION 20241006 // Via stacks //#define SEXPR_BOARD_FILE_VERSION 20241007 // Tracks can have soldermask layer and margin -#define SEXPR_BOARD_FILE_VERSION 20241009 // Evolve placement rule areas file format +//#define SEXPR_BOARD_FILE_VERSION 20241009 // Evolve placement rule areas file format +#define SEXPR_BOARD_FILE_VERSION 20241010 // Graphic shapes can have soldermask layer and margin #define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag #define LEGACY_ARC_FORMATTING 20210925 ///< These were the last to use old arc formatting diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp index b5af2a880c..7197dfea27 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp @@ -3074,6 +3074,15 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) NeedRIGHT(); break; + case T_layers: + shape->SetLayerSet( parseBoardItemLayersAsMask() ); + break; + + case T_solder_mask_margin: + shape->SetLocalSolderMaskMargin( parseBoardUnits( "local solder mask margin value" ) ); + NeedRIGHT(); + break; + case T_width: // legacy token stroke.SetWidth( parseBoardUnits( T_width ) ); NeedRIGHT(); @@ -3149,7 +3158,8 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) break; default: - Expecting( "layer, width, fill, tstamp, uuid, locked, net or status" ); + Expecting( "layer, width, fill, tstamp, uuid, locked, net, status, " + "or solder_mask_margin" ); } } diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index 4564b907f7..d8ba1b6ea0 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -1763,6 +1763,13 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) int thickness = getLineThickness( aShape->GetWidth() ); LINE_STYLE lineStyle = aShape->GetStroke().GetLineStyle(); + if( IsSolderMaskLayer( aLayer ) + && aShape->HasSolderMask() + && IsExternalCopperLayer( aShape->GetLayer() ) ) + { + thickness += aShape->GetSolderMaskExpansion() * 2; + } + if( IsNetnameLayer( aLayer ) ) { // Net names are shown only in board editor: @@ -1892,6 +1899,12 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) for( const VECTOR2I& pt : pts ) poly.Append( pt ); + if( thickness < 0 ) + { + poly.Inflate( thickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, + m_maxError ); + } + m_gal->DrawPolygon( poly ); } } @@ -1933,7 +1946,15 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) m_gal->SetIsStroke( thickness > 0 ); m_gal->SetLineWidth( thickness ); - m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() ); + int radius = aShape->GetRadius(); + + if( thickness < 0 ) + { + radius += thickness / 2; + radius = std::max( radius, 0 ); + } + + m_gal->DrawCircle( aShape->GetStart(), radius ); } break; @@ -1969,7 +1990,18 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) if( m_gal->IsOpenGlEngine() && !shape.IsTriangulationUpToDate() ) shape.CacheTriangulation( true, true ); - m_gal->DrawPolygon( shape ); + + if( thickness >= 0 ) + { + m_gal->DrawPolygon( shape ); + } + else + { + SHAPE_POLY_SET deflated_shape = shape; + deflated_shape.Inflate( thickness / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, + m_maxError ); + m_gal->DrawPolygon( deflated_shape ); + } } } @@ -2029,7 +2061,8 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) for( SHAPE* shape : shapes ) { - STROKE_PARAMS::Stroke( shape, lineStyle, thickness, &m_pcbSettings, + STROKE_PARAMS::Stroke( shape, lineStyle, getLineThickness( aShape->GetWidth() ), + &m_pcbSettings, [&]( const VECTOR2I& a, const VECTOR2I& b ) { m_gal->DrawSegment( a, b, thickness ); diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp index 26d31508bc..a9ff8354e8 100644 --- a/pcbnew/pcb_shape.cpp +++ b/pcbnew/pcb_shape.cpp @@ -52,6 +52,7 @@ PCB_SHAPE::PCB_SHAPE( BOARD_ITEM* aParent, KICAD_T aItemType, SHAPE_T aShapeType BOARD_CONNECTED_ITEM( aParent, aItemType ), EDA_SHAPE( aShapeType, pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ), FILL_T::NO_FILL ) { + m_hasSolderMask = false; } @@ -59,6 +60,7 @@ PCB_SHAPE::PCB_SHAPE( BOARD_ITEM* aParent, SHAPE_T shapetype ) : BOARD_CONNECTED_ITEM( aParent, PCB_SHAPE_T ), EDA_SHAPE( shapetype, pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ), FILL_T::NO_FILL ) { + m_hasSolderMask = false; } @@ -176,6 +178,8 @@ void PCB_SHAPE::Serialize( google::protobuf::Any &aContainer ) const wxASSERT_MSG( false, "Unhandled shape in PCB_SHAPE::Serialize" ); } + // TODO m_hasSolderMask and m_solderMaskMargin + aContainer.PackFrom( msg ); } @@ -274,6 +278,8 @@ bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer ) RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); } + // TODO m_hasSolderMask and m_solderMaskMargin + return true; } @@ -326,6 +332,75 @@ void PCB_SHAPE::SetLayer( PCB_LAYER_ID aLayer ) } +int PCB_SHAPE::GetSolderMaskExpansion() const +{ + int margin = m_solderMaskMargin.value_or( 0 ); + + // If no local margin is set, get the board's solder mask expansion value + if( !m_solderMaskMargin.has_value() ) + { + const BOARD* board = GetBoard(); + + if( board ) + margin = board->GetDesignSettings().m_SolderMaskExpansion; + } + + // Ensure the resulting mask opening has a non-negative size + if( margin < 0 && !IsFilled() ) + margin = std::max( margin, -GetWidth() / 2 ); + + return margin; +} + + +bool PCB_SHAPE::IsOnLayer( PCB_LAYER_ID aLayer ) const +{ + if( aLayer == m_layer ) + { + return true; + } + + if( m_hasSolderMask + && ( ( aLayer == F_Mask && m_layer == F_Cu ) + || ( aLayer == B_Mask && m_layer == B_Cu ) ) ) + { + return true; + } + + return false; +} + + +LSET PCB_SHAPE::GetLayerSet() const +{ + LSET layermask( { m_layer } ); + + if( m_hasSolderMask ) + { + if( layermask.test( F_Cu ) ) + layermask.set( F_Mask ); + + if( layermask.test( B_Cu ) ) + layermask.set( B_Mask ); + } + + return layermask; +} + + +void PCB_SHAPE::SetLayerSet( const LSET& aLayerSet ) +{ + aLayerSet.RunOnLayers( + [&]( PCB_LAYER_ID layer ) + { + if( IsCopperLayer( layer ) ) + SetLayer( layer ); + else if( IsSolderMaskLayer( layer ) ) + SetHasSolderMask( true ); + } ); +} + + std::vector PCB_SHAPE::GetConnectionPoints() const { std::vector ret; @@ -693,6 +768,14 @@ void PCB_SHAPE::ViewGetLayers( int aLayers[], int& aCount ) const { aLayers[1] = GetNetnameLayer( aLayers[0] ); aCount = 2; + + if( m_hasSolderMask ) + { + if( m_layer == F_Cu ) + aLayers[ aCount++ ] = F_Mask; + else if( m_layer == B_Cu ) + aLayers[ aCount++ ] = B_Mask; + } } else { @@ -817,6 +900,8 @@ void PCB_SHAPE::swapData( BOARD_ITEM* aImage ) std::swap( m_parent, image->m_parent ); std::swap( m_forceVisible, image->m_forceVisible ); std::swap( m_netinfo, image->m_netinfo ); + std::swap( m_hasSolderMask, image->m_hasSolderMask ); + std::swap( m_solderMaskMargin, image->m_solderMaskMargin ); } @@ -886,6 +971,12 @@ bool PCB_SHAPE::operator==( const PCB_SHAPE& aOther ) const if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() ) return false; + if( m_hasSolderMask != other.m_hasSolderMask ) + return false; + + if( m_solderMaskMargin != other.m_solderMaskMargin ) + return false; + return EDA_SHAPE::operator==( other ); } @@ -917,6 +1008,12 @@ double PCB_SHAPE::Similarity( const BOARD_ITEM& aOther ) const if( m_netinfo->GetNetCode() != other.m_netinfo->GetNetCode() ) similarity *= 0.9; + if( m_hasSolderMask != other.m_hasSolderMask ) + similarity *= 0.9; + + if( m_solderMaskMargin != other.m_solderMaskMargin ) + similarity *= 0.9; + similarity *= EDA_SHAPE::Similarity( other ); return similarity; @@ -1041,5 +1138,30 @@ static struct PCB_SHAPE_DESC groupPadPrimitives ) .SetAvailableFunc( showSpokeTemplateProperty ) .SetIsHiddenFromRulesEditor(); + + const wxString groupTechLayers = _HKI( "Technical Layers" ); + + auto isExternalCuLayer = + []( INSPECTABLE* aItem ) + { + if( auto shape = dynamic_cast( aItem ) ) + return IsExternalCopperLayer( shape->GetLayer() ); + + return false; + }; + + propMgr.AddProperty( new PROPERTY( _HKI( "Soldermask" ), + &PCB_SHAPE::SetHasSolderMask, + &PCB_SHAPE::HasSolderMask ), + groupTechLayers ) + .SetAvailableFunc( isExternalCuLayer ); + + propMgr.AddProperty( new PROPERTY>( + _HKI( "Soldermask Margin Override" ), + &PCB_SHAPE::SetLocalSolderMaskMargin, + &PCB_SHAPE::GetLocalSolderMaskMargin, + PROPERTY_DISPLAY::PT_SIZE ), + groupTechLayers ) + .SetAvailableFunc( isExternalCuLayer ); } } _PCB_SHAPE_DESC; diff --git a/pcbnew/pcb_shape.h b/pcbnew/pcb_shape.h index 6619161e0f..a89473c29e 100644 --- a/pcbnew/pcb_shape.h +++ b/pcbnew/pcb_shape.h @@ -68,6 +68,11 @@ public: void SetLayer( PCB_LAYER_ID aLayer ) override; PCB_LAYER_ID GetLayer() const override { return m_layer; } + bool IsOnLayer( PCB_LAYER_ID aLayer ) const override; + + virtual LSET GetLayerSet() const override; + virtual void SetLayerSet( const LSET& aLayers ) override; + void SetPosition( const VECTOR2I& aPos ) override { setPosition( aPos ); } VECTOR2I GetPosition() const override { return getPosition(); } @@ -174,6 +179,14 @@ public: bool operator==( const PCB_SHAPE& aShape ) const; bool operator==( const BOARD_ITEM& aBoardItem ) const override; + void SetHasSolderMask( bool aVal ) { m_hasSolderMask = aVal; } + bool HasSolderMask() const { return m_hasSolderMask; } + + void SetLocalSolderMaskMargin( std::optional aMargin ) { m_solderMaskMargin = aMargin; } + std::optional GetLocalSolderMaskMargin() const { return m_solderMaskMargin; } + + int GetSolderMaskExpansion() const; + #if defined(DEBUG) void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } #endif @@ -185,4 +198,7 @@ protected: { bool operator()( const BOARD_ITEM* aFirst, const BOARD_ITEM* aSecond ) const; }; + + bool m_hasSolderMask; + std::optional m_solderMaskMargin; }; diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index 2f568c384d..b3f60a3e58 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -621,7 +621,7 @@ void BRDITEMS_PLOTTER::PlotFootprintGraphicItems( const FOOTPRINT* aFootprint ) if( aFootprint->IsDNP() && hideDNPItems( itemLayer ) ) continue; - if( !m_layerMask[ itemLayer ] ) + if( !( m_layerMask & item->GetLayerSet() ).any() ) continue; switch( item->Type() ) @@ -831,12 +831,23 @@ void BRDITEMS_PLOTTER::PlotZone( const ZONE* aZone, PCB_LAYER_ID aLayer, void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) { - if( !m_layerMask[aShape->GetLayer()] ) + if( !( m_layerMask & aShape->GetLayerSet() ).any() ) return; - OUTLINE_MODE plotMode = GetPlotMode(); + OUTLINE_MODE plotMode = GetPlotMode(); int thickness = aShape->GetWidth(); + int margin = thickness; // unclamped thickness (can be negative) LINE_STYLE lineStyle = aShape->GetStroke().GetLineStyle(); + bool onCopperLayer = ( LSET::AllCuMask() & m_layerMask ).any(); + bool onSolderMaskLayer = ( LSET( { F_Mask, B_Mask } ) & m_layerMask ).any(); + + if( onSolderMaskLayer + && aShape->HasSolderMask() + && IsExternalCopperLayer( aShape->GetLayer() ) ) + { + margin += 2 * aShape->GetSolderMaskExpansion(); + thickness = std::max( margin, 0 ); + } m_plotter->SetColor( getColor( aShape->GetLayer() ) ); @@ -859,7 +870,7 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) { gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT ); } - else if( IsCopperLayer( aShape->GetLayer() ) ) + else if( onCopperLayer ) { if( parentFP ) { @@ -894,8 +905,15 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) case SHAPE_T::CIRCLE: if( aShape->IsFilled() ) { - m_plotter->FilledCircle( aShape->GetStart(), aShape->GetRadius() * 2 + thickness, - plotMode, &gbr_metadata ); + int diameter = aShape->GetRadius() * 2 + thickness; + + if( margin < 0 ) + { + diameter += margin; + diameter = std::max( diameter, 0 ); + } + + m_plotter->FilledCircle( aShape->GetStart(), diameter, plotMode, &gbr_metadata ); } else { @@ -916,7 +934,7 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) } else { - m_plotter->ThickArc( *aShape, plotMode, &gbr_metadata ); + m_plotter->ThickArc( *aShape, plotMode, &gbr_metadata, thickness ); } break; @@ -949,6 +967,13 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) // from generating invalid Gerber files SHAPE_POLY_SET tmpPoly = aShape->GetPolyShape().CloneDropTriangulation(); tmpPoly.Fracture( SHAPE_POLY_SET::PM_FAST ); + + if( margin < 0 ) + { + tmpPoly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, + m_board->GetDesignSettings().m_MaxError ); + } + FILL_T fill = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj ) @@ -988,23 +1013,33 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) } else { - SHAPE_LINE_CHAIN poly; + SHAPE_POLY_SET poly; + poly.NewOutline(); for( const VECTOR2I& pt : pts ) poly.Append( pt ); - poly.Append( pts[0] ); // Close polygon. + if( margin < 0 ) + { + poly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS, + m_board->GetDesignSettings().m_MaxError ); + } FILL_T fill_mode = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL; - if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER ) + if( poly.OutlineCount() > 0 ) { - GERBER_PLOTTER* gbr_plotter = static_cast( m_plotter ); - gbr_plotter->PlotPolyAsRegion( poly, fill_mode, thickness, &gbr_metadata ); - } - else - { - m_plotter->PlotPoly( poly, fill_mode, thickness, &gbr_metadata ); + if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER ) + { + GERBER_PLOTTER* gbr_plotter = static_cast( m_plotter ); + gbr_plotter->PlotPolyAsRegion( poly.COutline( 0 ), fill_mode, thickness, + &gbr_metadata ); + } + else + { + m_plotter->PlotPoly( poly.COutline( 0 ), fill_mode, thickness, + &gbr_metadata ); + } } } @@ -1021,7 +1056,8 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape ) for( SHAPE* shape : shapes ) { - STROKE_PARAMS::Stroke( shape, lineStyle, thickness, m_plotter->RenderSettings(), + STROKE_PARAMS::Stroke( shape, lineStyle, aShape->GetWidth(), + m_plotter->RenderSettings(), [&]( const VECTOR2I& a, const VECTOR2I& b ) { m_plotter->ThickSegment( a, b, thickness, plotMode,