7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-19 18:31:40 +00:00

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
This commit is contained in:
Andrzej.W 2024-09-16 02:39:12 +02:00 committed by Wayne Stambaugh
parent a34c6ecd03
commit b49ebaeb16
17 changed files with 914 additions and 223 deletions

View File

@ -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 );

View File

@ -260,8 +260,8 @@ void BOARD_ADAPTER::addFootprintShapes( const FOOTPRINT* aFootprint, CONTAINER_2
{
PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( 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<const PCB_SHAPE*>( aTextBox ), aContainer, aOwner );
addShape( static_cast<const PCB_SHAPE*>( aTextBox ), aContainer, aOwner, UNDEFINED_LAYER );
}
else
{

View File

@ -611,7 +611,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
switch( item->Type() )
{
case PCB_SHAPE_T:
addShape( static_cast<PCB_SHAPE*>( item ), layerContainer, item );
addShape( static_cast<PCB_SHAPE*>( 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<PCB_SHAPE*>( item ), layerContainer, item );
addShape( static_cast<PCB_SHAPE*>( item ), layerContainer, item, layer );
break;
case PCB_TEXT_T:

View File

@ -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 );
}

View File

@ -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.
*

View File

@ -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,

View File

@ -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() )

View File

@ -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 );
}

File diff suppressed because it is too large Load Diff

View File

@ -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:

View File

@ -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() ) );

View File

@ -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

View File

@ -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" );
}
}

View File

@ -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 );

View File

@ -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<VECTOR2I> PCB_SHAPE::GetConnectionPoints() const
{
std::vector<VECTOR2I> 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<PCB_SHAPE*>( aItem ) )
return IsExternalCopperLayer( shape->GetLayer() );
return false;
};
propMgr.AddProperty( new PROPERTY<PCB_SHAPE, bool>( _HKI( "Soldermask" ),
&PCB_SHAPE::SetHasSolderMask,
&PCB_SHAPE::HasSolderMask ),
groupTechLayers )
.SetAvailableFunc( isExternalCuLayer );
propMgr.AddProperty( new PROPERTY<PCB_SHAPE, std::optional<int>>(
_HKI( "Soldermask Margin Override" ),
&PCB_SHAPE::SetLocalSolderMaskMargin,
&PCB_SHAPE::GetLocalSolderMaskMargin,
PROPERTY_DISPLAY::PT_SIZE ),
groupTechLayers )
.SetAvailableFunc( isExternalCuLayer );
}
} _PCB_SHAPE_DESC;

View File

@ -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<int> aMargin ) { m_solderMaskMargin = aMargin; }
std::optional<int> 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<int> m_solderMaskMargin;
};

View File

@ -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<GERBER_PLOTTER*>( 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<GERBER_PLOTTER*>( 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,