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:
parent
a34c6ecd03
commit
b49ebaeb16
3d-viewer/3d_canvas
common/plotters
include
pcbnew
@ -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 );
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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:
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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,
|
||||
|
@ -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() )
|
||||
|
@ -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
@ -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:
|
||||
|
@ -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() ) );
|
||||
|
@ -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
|
||||
|
@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user