diff --git a/common/drc_rules.keywords b/common/drc_rules.keywords
index 884d21fd19..284d836eff 100644
--- a/common/drc_rules.keywords
+++ b/common/drc_rules.keywords
@@ -43,6 +43,9 @@ rule
severity
silk_clearance
skew
+solder_mask_expansion
+solder_paste_abs_margin
+solder_paste_rel_margin
solid
text
text_height
diff --git a/pcbnew/dialogs/panel_setup_rules.cpp b/pcbnew/dialogs/panel_setup_rules.cpp
index 88f81ba721..876abdbc0c 100644
--- a/pcbnew/dialogs/panel_setup_rules.cpp
+++ b/pcbnew/dialogs/panel_setup_rules.cpp
@@ -259,6 +259,9 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
|| token == wxT( "physical_hole_clearance" )
|| token == wxT( "silk_clearance" )
|| token == wxT( "skew" )
+ || token == wxT( "solder_mask_expansion" )
+ || token == wxT( "solder_paste_abs_margin" )
+ || token == wxT( "solder_paste_rel_margin" )
|| token == wxT( "text_height" )
|| token == wxT( "text_thickness" )
|| token == wxT( "thermal_relief_gap" )
@@ -477,6 +480,9 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
"physical_hole_clearance|"
"silk_clearance|"
"skew|"
+ "solder_mask_expansion|"
+ "solder_paste_abs_margin|"
+ "solder_paste_rel_margin|"
"text_height|"
"text_thickness|"
"thermal_relief_gap|"
diff --git a/pcbnew/dialogs/panel_setup_rules_help_2constraints.md b/pcbnew/dialogs/panel_setup_rules_help_2constraints.md
index c1b583fd1f..45eee2f663 100644
--- a/pcbnew/dialogs/panel_setup_rules_help_2constraints.md
+++ b/pcbnew/dialogs/panel_setup_rules_help_2constraints.md
@@ -1,30 +1,33 @@
### Constraints
-| Constraint type | Argument type | Description |
-|---------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `annular_width` | min/opt/max | Checks the width of annular rings on vias.
|
-| `assertion` | "<expression>" | Checks the given expression.
|
-| `clearance` | min | Specifies the **electrical** clearance between copper objects of different nets. (See `physical_clearance` if you wish to specify clearance between objects regardless of net.)
To allow copper objects to overlap (collide), create a `clearance` constraint with the `min` value less than zero (for example, `-1`).
|
-| `courtyard_clearance` | min | Checks the clearance between footprint courtyards and generates an error if any two courtyards are closer than the `min` distance. If a footprint does not have a courtyard shape, no errors will be generated from this constraint.
|
-| `creepage` | min | Specifies the creepage distance between copper objects of different nets.
|
-| `diff_pair_gap` | min/opt/max | Checks the gap between coupled tracks in a differential pair. Coupled tracks are segments that are parallel to each other. Differential pair gap is not tested on uncoupled portions of a differential pair (for example, the fanout from a component).
|
-| `diff_pair_uncoupled` | max | Checks the distance that a differential pair track is routed uncoupled from the other polarity track in the pair (for example, where the pair fans out from a component, or becomes uncoupled to pass around another object such as a via).
|
-| `disallow` | `track`
`via`
`micro_via`
`buried_via`
`pad`
`zone`
`text`
`graphic`
`hole`
`footprint`
| Specify one or more object types to disallow, separated by spaces. For example, `(constraint disallow track)` or `(constraint disallow track via pad)`. If an object of this type matches the rule condition, a DRC error will be created.
This constraint is essentially the same as a keepout rule area, but can be used to create more specific keepout restrictions.
|
-| `edge_clearance` | min/opt/max | Checks the clearance between objects and the board edge.
This can also be thought of as the "milling tolerance" as the board edge will include all graphical items on the `Edge.Cuts` layer as well as any *oval* pad holes. (See `physical_hole_clearance` for the drilling tolerance.)
|
-| `length` | min/max | Checks the total routed length for the nets that match the rule condition and generates an error for each net that is below the `min` value (if specified) or above the `max` value (if specified) of the constraint.
|
-| `hole` | min/max | Checks the size (diameter) of a drilled hole in a pad or via. For oval holes, the smaller (minor) diameter will be tested against the `min` value (if specified) and the larger (major) diameter will be tested against the `max` value (if specified).
|
-| `hole_clearance` | min | Checks the clearance between a drilled hole in a pad or via and copper objects on a different net. The clearance is measured from the diameter of the hole, not its center.
|
+| Constraint type | Argument type | Description |
+|---------------------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `annular_width` | min/opt/max | Checks the width of annular rings on vias.
|
+| `assertion` | "<expression>" | Checks the given expression.
|
+| `clearance` | min | Specifies the **electrical** clearance between copper objects of different nets. (See `physical_clearance` if you wish to specify clearance between objects regardless of net.)
To allow copper objects to overlap (collide), create a `clearance` constraint with the `min` value less than zero (for example, `-1`).
|
+| `courtyard_clearance` | min | Checks the clearance between footprint courtyards and generates an error if any two courtyards are closer than the `min` distance. If a footprint does not have a courtyard shape, no errors will be generated from this constraint.
|
+| `creepage` | min | Specifies the creepage distance between copper objects of different nets.
|
+| `diff_pair_gap` | min/opt/max | Checks the gap between coupled tracks in a differential pair. Coupled tracks are segments that are parallel to each other. Differential pair gap is not tested on uncoupled portions of a differential pair (for example, the fanout from a component).
|
+| `diff_pair_uncoupled` | max | Checks the distance that a differential pair track is routed uncoupled from the other polarity track in the pair (for example, where the pair fans out from a component, or becomes uncoupled to pass around another object such as a via).
|
+| `disallow` | `track`
`via`
`micro_via`
`buried_via`
`pad`
`zone`
`text`
`graphic`
`hole`
`footprint`
| Specify one or more object types to disallow, separated by spaces. For example, `(constraint disallow track)` or `(constraint disallow track via pad)`. If an object of this type matches the rule condition, a DRC error will be created.
This constraint is essentially the same as a keepout rule area, but can be used to create more specific keepout restrictions.
|
+| `edge_clearance` | min/opt/max | Checks the clearance between objects and the board edge.
This can also be thought of as the "milling tolerance" as the board edge will include all graphical items on the `Edge.Cuts` layer as well as any *oval* pad holes. (See `physical_hole_clearance` for the drilling tolerance.)
|
+| `length` | min/max | Checks the total routed length for the nets that match the rule condition and generates an error for each net that is below the `min` value (if specified) or above the `max` value (if specified) of the constraint.
|
+| `hole` | min/max | Checks the size (diameter) of a drilled hole in a pad or via. For oval holes, the smaller (minor) diameter will be tested against the `min` value (if specified) and the larger (major) diameter will be tested against the `max` value (if specified).
|
+| `hole_clearance` | min | Checks the clearance between a drilled hole in a pad or via and copper objects on a different net. The clearance is measured from the diameter of the hole, not its center.
|
| `hole_to_hole` | min | Checks the clearance between mechanically-drilled holes in pads and vias. The clearance is measured between the diameters of the holes, not between their centers.
This constraint is soley for the protection of drill bits. The clearance between **laser-drilled** (microvias) and other non-mechanically-drilled holes is not checked, nor is the clearance between **milled** (oval-shaped) and other non-mechanically-drilled holes.
|
-| `physical_clearance` | min | Checks the clearance between two objects on a given layer (including non-copper layers).
While this can perform more general-purpose checks than `clearance`, it is much slower. Use `clearance` where possible.
|
-| `physical_hole_clearance` | min | Checks the clearance between a drilled hole in a pad or via and another object, regardless of net. The clearance is measured from the diameter of the hole, not its center.
This can also be thought of as the "drilling tolerance" as it only includes **round** holes (see `edge_clearance` for the milling tolerance).
|
-| `silk_clearance` | min/opt/max | Checks the clearance between objects on silkscreen layers and other objects.
|
-| `skew` | max | Checks the total skew for the nets that match the rule condition, that is, the difference between the length of each net and the average of all the lengths of each net that is matched by the rule. If the absolute value of the difference between that average and the length of any one net is above the constraint `max` value, an error will be generated.
|
-| `thermal_relief_gap` | min | Specifies the width of the gap between a pad and a zone with a thermal-relief connection.
|
-| `thermal_spoke_width` | opt | Specifies the width of the spokes connecting a pad to a zone with a thermal-relief connection.
|
-| `track_width` | min/opt/max | Checks the width of track and arc segments. An error will be generated for each segment that has a width below the `min` value (if specified) or above the `max` value (if specified).
|
-| `track_angle` | min/opt/max | Checks the angle between two connected track segments. An error will be generated for each connected pair with an angle below the `min` value (if specified) or above the `max` value (if specified).
|
-| `track_segment_length` | min/max | Checks the length of track and arc segments. An error will be generated for each segment that has a length below the `min` value (if specified) or above the `max` value (if specified).
|
-| `via_count` | max | Counts the number of vias on every net matched by the rule condition. If that number exceeds the constraint `max` value on any matched net, an error will be generated for that net.
|
-| `zone_connection` | `solid`
`thermal_reliefs`
`none` | Specifies the connection to be made between a zone and a pad.
|
+| `physical_clearance` | min | Checks the clearance between two objects on a given layer (including non-copper layers).
While this can perform more general-purpose checks than `clearance`, it is much slower. Use `clearance` where possible.
|
+| `physical_hole_clearance` | min | Checks the clearance between a drilled hole in a pad or via and another object, regardless of net. The clearance is measured from the diameter of the hole, not its center.
This can also be thought of as the "drilling tolerance" as it only includes **round** holes (see `edge_clearance` for the milling tolerance).
|
+| `silk_clearance` | min/opt/max | Checks the clearance between objects on silkscreen layers and other objects.
|
+| `skew` | max | Checks the total skew for the nets that match the rule condition, that is, the difference between the length of each net and the average of all the lengths of each net that is matched by the rule. If the absolute value of the difference between that average and the length of any one net is above the constraint `max` value, an error will be generated.
|
+| `solder_mask_expansion` | opt | Specifies the solder mask expansion for pads, shapes and tracks. |
+| `solder_paste_abs_margin` | opt | Specifies the absolute solder paste clearance for pads. Usually negative to inset the paste.
The final solder paste clearace will be the absolute clearance plus the relative clearance. |
+| `solder_paste_rel_margin` | opt | Specifies the relative solder paste clearance for pads. Usually negative to inset the paste.
The final solder paste clearace will be the absolute clearance plus the relative clearance. |
+| `thermal_relief_gap` | min | Specifies the width of the gap between a pad and a zone with a thermal-relief connection.
|
+| `thermal_spoke_width` | opt | Specifies the width of the spokes connecting a pad to a zone with a thermal-relief connection.
|
+| `track_width` | min/opt/max | Checks the width of track and arc segments. An error will be generated for each segment that has a width below the `min` value (if specified) or above the `max` value (if specified).
|
+| `track_angle` | min/opt/max | Checks the angle between two connected track segments. An error will be generated for each connected pair with an angle below the `min` value (if specified) or above the `max` value (if specified).
|
+| `track_segment_length` | min/max | Checks the length of track and arc segments. An error will be generated for each segment that has a length below the `min` value (if specified) or above the `max` value (if specified).
|
+| `via_count` | max | Counts the number of vias on every net matched by the rule condition. If that number exceeds the constraint `max` value on any matched net, an error will be generated for that net.
|
+| `zone_connection` | `solid`
`thermal_reliefs`
`none` | Specifies the connection to be made between a zone and a pad.
|
diff --git a/pcbnew/dialogs/panel_setup_rules_help_9more_examples.md b/pcbnew/dialogs/panel_setup_rules_help_9more_examples.md
index 0bf38bf3b4..7a07f7e910 100644
--- a/pcbnew/dialogs/panel_setup_rules_help_9more_examples.md
+++ b/pcbnew/dialogs/panel_setup_rules_help_9more_examples.md
@@ -135,3 +135,15 @@
(constraint hole_to_hole)
(severity ignore))
+
+ # No solder mask expansion for vias.
+ (rule "no mask expansion on vias"
+ (constraint solder_mask_expansion (opt 0mm))
+ (condition "A.type == via"))
+
+
+ # Remove solder paste from DNP footprints.
+ (rule remove_solder_paste_from_DNP
+ (constraint solder_paste_abs_margin (opt -50mm))
+ (condition "A.Do_not_Populate"))
+
diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp
index 986b7246c6..0981d50ed0 100644
--- a/pcbnew/drc/drc_engine.cpp
+++ b/pcbnew/drc/drc_engine.cpp
@@ -40,6 +40,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -708,9 +709,12 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
const ZONE* zone = nullptr;
const FOOTPRINT* parentFootprint = nullptr;
- if( aConstraintType == ZONE_CONNECTION_CONSTRAINT
- || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
- || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
+ if( aConstraintType == ZONE_CONNECTION_CONSTRAINT
+ || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
+ || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT
+ || aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT
+ || aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
+ || aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
{
if( a && a->Type() == PCB_PAD_T )
pad = static_cast( a );
@@ -949,6 +953,64 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
return constraint;
}
}
+ else if( aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT )
+ {
+ std::optional override;
+
+ if( pad )
+ override = pad->GetLocalSolderMaskMargin();
+ else if( a->Type() == PCB_SHAPE_T )
+ override = static_cast( a )->GetLocalSolderMaskMargin();
+ else if( const PCB_TRACK* track = dynamic_cast( a ) )
+ override = track->GetLocalSolderMaskMargin();
+
+ if( override )
+ {
+ REPORT( "" )
+ REPORT( wxString::Format( _( "Local override on %s; solder mask expansion: %s." ),
+ EscapeHTML( pad->GetItemDescription( this, true ) ),
+ EscapeHTML( MessageTextFromValue( override.value() ) ) ) )
+
+ constraint.m_Value.SetOpt( override.value() );
+ return constraint;
+ }
+ }
+ else if( aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT )
+ {
+ std::optional override;
+
+ if( pad )
+ override = pad->GetLocalSolderPasteMargin();
+
+ if( override )
+ {
+ REPORT( "" )
+ REPORT( wxString::Format( _( "Local override on %s; solder paste absolute clearance: %s." ),
+ EscapeHTML( pad->GetItemDescription( this, true ) ),
+ EscapeHTML( MessageTextFromValue( override.value() ) ) ) )
+
+ constraint.m_Value.SetOpt( override.value_or( 0 ) );
+ return constraint;
+ }
+ }
+ else if( aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
+ {
+ std::optional overrideRatio;
+
+ if( pad )
+ overrideRatio = pad->GetLocalSolderPasteMarginRatio();
+
+ if( overrideRatio )
+ {
+ REPORT( "" )
+ REPORT( wxString::Format( _( "Local override on %s; solder paste relative clearance: %s." ),
+ EscapeHTML( pad->GetItemDescription( this, true ) ),
+ EscapeHTML( MessageTextFromValue( overrideRatio.value() * 100.0 ) ) ) )
+
+ constraint.m_Value.SetOpt( KiROUND( overrideRatio.value_or( 0 ) * 1000 ) );
+ return constraint;
+ }
+ }
auto testAssertion =
[&]( const DRC_ENGINE_CONSTRAINT* c )
@@ -1016,6 +1078,24 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
break;
+ case SOLDER_MASK_EXPANSION_CONSTRAINT:
+ REPORT( wxString::Format( _( "Checking %s solder mask expansion: %s." ),
+ EscapeHTML( c->constraint.GetName() ),
+ MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
+ break;
+
+ case SOLDER_PASTE_ABS_MARGIN_CONSTRAINT:
+ REPORT( wxString::Format( _( "Checking %s solder paste absolute cleraance: %s." ),
+ EscapeHTML( c->constraint.GetName() ),
+ MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
+ break;
+
+ case SOLDER_PASTE_REL_MARGIN_CONSTRAINT:
+ REPORT( wxString::Format( _( "Checking %s solder paste relative clearance: %s." ),
+ EscapeHTML( c->constraint.GetName() ),
+ MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
+ break;
+
case MIN_RESOLVED_SPOKES_CONSTRAINT:
REPORT( wxString::Format( _( "Checking %s min spoke count: %s." ),
EscapeHTML( c->constraint.GetName() ),
@@ -1376,13 +1456,15 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
if( constraint.GetParentRule() && !constraint.GetParentRule()->m_Implicit )
return constraint;
- // Special case for pad zone connections which can iherit from their parent footprints.
- // We've already checked for local overrides, and there were no rules targetting the pad
- // itself, so we know we're inheriting and need to see if there are any rules targetting
- // the parent footprint.
+ // Special case for properties which can be inherited from parent footprints. We've already
+ // checked for local overrides, and there were no rules targetting the item itself, so we know
+ // we're inheriting and need to see if there are any rules targetting the parent footprint.
if( pad && parentFootprint && ( aConstraintType == ZONE_CONNECTION_CONSTRAINT
|| aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
- || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT ) )
+ || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT
+ || aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT
+ || aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
+ || aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT ) )
{
REPORT( "" )
REPORT( wxString::Format( _( "Inheriting from parent: %s." ),
@@ -1403,6 +1485,29 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
if( constraint.GetParentRule() && !constraint.GetParentRule()->m_Implicit )
return constraint;
}
+
+ // Found nothing again? Return the defaults.
+ if( aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT )
+ {
+ constraint.SetParentRule( nullptr );
+ constraint.SetName( _( "board setup" ) );
+ constraint.m_Value.SetOpt( m_designSettings->m_SolderMaskExpansion );
+ return constraint;
+ }
+ else if( aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT )
+ {
+ constraint.SetParentRule( nullptr );
+ constraint.SetName( _( "board setup" ) );
+ constraint.m_Value.SetOpt( m_designSettings->m_SolderPasteMargin );
+ return constraint;
+ }
+ else if( aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
+ {
+ constraint.SetParentRule( nullptr );
+ constraint.SetName( _( "board setup" ) );
+ constraint.m_Value.SetOpt( KiROUND( m_designSettings->m_SolderPasteMarginRatio * 1000 ) );
+ return constraint;
+ }
}
// Unfortunately implicit rules don't work for local clearances (such as zones) because
diff --git a/pcbnew/drc/drc_rule.h b/pcbnew/drc/drc_rule.h
index da08c79b9f..5cf1fe44fc 100644
--- a/pcbnew/drc/drc_rule.h
+++ b/pcbnew/drc/drc_rule.h
@@ -63,6 +63,9 @@ enum DRC_CONSTRAINT_T
THERMAL_RELIEF_GAP_CONSTRAINT,
THERMAL_SPOKE_WIDTH_CONSTRAINT,
MIN_RESOLVED_SPOKES_CONSTRAINT,
+ SOLDER_MASK_EXPANSION_CONSTRAINT,
+ SOLDER_PASTE_ABS_MARGIN_CONSTRAINT,
+ SOLDER_PASTE_REL_MARGIN_CONSTRAINT,
DISALLOW_CONSTRAINT,
VIA_DIAMETER_CONSTRAINT,
LENGTH_CONSTRAINT,
diff --git a/pcbnew/drc/drc_rule_parser.cpp b/pcbnew/drc/drc_rule_parser.cpp
index f1034ef6d5..ad0af695e6 100644
--- a/pcbnew/drc/drc_rule_parser.cpp
+++ b/pcbnew/drc/drc_rule_parser.cpp
@@ -476,11 +476,12 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
else if( (int) token == DSN_RIGHT || token == T_EOF )
{
msg.Printf( _( "Missing constraint type.| Expected %s." ),
- wxT( "assertion, clearance, hole_clearance, edge_clearance, "
- "physical_clearance, physical_hole_clearance, courtyard_clearance, "
- "silk_clearance, hole_size, hole_to_hole, track_width, annular_width, "
- "via_diameter, disallow, zone_connection, thermal_relief_gap, "
- "thermal_spoke_width, min_resolved_spokes, length, skew, via_count, "
+ wxT( "assertion, clearance, hole_clearance, edge_clearance, physical_clearance, "
+ "physical_hole_clearance, courtyard_clearance, silk_clearance, hole_size, "
+ "hole_to_hole, track_width, track_angle, track_segment_length, annular_width, "
+ "disallow, zone_connection, thermal_relief_gap, thermal_spoke_width, "
+ "min_resolved_spokes, solder_mask_expansion, solder_paste_abs_margin, "
+ "solder_paste_rel_margin, length, skew, via_count, via_diameter, "
"diff_pair_gap or diff_pair_uncoupled" ) );
reportError( msg );
return;
@@ -509,6 +510,9 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
case T_thermal_relief_gap: c.m_Type = THERMAL_RELIEF_GAP_CONSTRAINT; break;
case T_thermal_spoke_width: c.m_Type = THERMAL_SPOKE_WIDTH_CONSTRAINT; break;
case T_min_resolved_spokes: c.m_Type = MIN_RESOLVED_SPOKES_CONSTRAINT; break;
+ case T_solder_mask_expansion: c.m_Type = SOLDER_MASK_EXPANSION_CONSTRAINT; break;
+ case T_solder_paste_abs_margin: c.m_Type = SOLDER_PASTE_ABS_MARGIN_CONSTRAINT; break;
+ case T_solder_paste_rel_margin: c.m_Type = SOLDER_PASTE_REL_MARGIN_CONSTRAINT; break;
case T_disallow: c.m_Type = DISALLOW_CONSTRAINT; break;
case T_length: c.m_Type = LENGTH_CONSTRAINT; break;
case T_skew: c.m_Type = SKEW_CONSTRAINT; break;
@@ -519,11 +523,12 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
case T_physical_hole_clearance: c.m_Type = PHYSICAL_HOLE_CLEARANCE_CONSTRAINT; break;
default:
msg.Printf( _( "Unrecognized item '%s'.| Expected %s." ), FromUTF8(),
- wxT( "assertion, clearance, hole_clearance, edge_clearance, "
- "physical_clearance, physical_hole_clearance, courtyard_clearance, "
- "silk_clearance, hole_size, hole_to_hole, track_width, track_angle, track_segment_length, annular_width, "
+ wxT( "assertion, clearance, hole_clearance, edge_clearance, physical_clearance, "
+ "physical_hole_clearance, courtyard_clearance, silk_clearance, hole_size, "
+ "hole_to_hole, track_width, track_angle, track_segment_length, annular_width, "
"disallow, zone_connection, thermal_relief_gap, thermal_spoke_width, "
- "min_resolved_spokes, length, skew, via_count, via_diameter, "
+ "min_resolved_spokes, solder_mask_expansion, solder_paste_abs_margin, "
+ "solder_paste_rel_margin, length, skew, via_count, via_diameter, "
"diff_pair_gap or diff_pair_uncoupled" ) );
reportError( msg );
}
diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp
index 583f075065..e237e33537 100644
--- a/pcbnew/pad.cpp
+++ b/pcbnew/pad.cpp
@@ -1172,18 +1172,27 @@ int PAD::GetSolderMaskExpansion( PCB_LAYER_ID aLayer ) const
else
return 0;
- std::optional margin = m_padStack.SolderMaskMargin( aLayer );
+ std::optional margin;
- if( !margin.has_value() )
+ if( const BOARD* board = GetBoard() )
{
- if( FOOTPRINT* parentFootprint = GetParentFootprint() )
- margin = parentFootprint->GetLocalSolderMaskMargin();
+ DRC_CONSTRAINT constraint;
+ std::shared_ptr drcEngine = board->GetDesignSettings().m_DRCEngine;
+
+ constraint = drcEngine->EvalRules( SOLDER_MASK_EXPANSION_CONSTRAINT, this, nullptr, aLayer );
+
+ if( constraint.m_Value.HasOpt() )
+ margin = constraint.m_Value.Opt();
}
-
- if( !margin.has_value() )
+ else
{
- if( const BOARD* brd = GetBoard() )
- margin = brd->GetDesignSettings().m_SolderMaskExpansion;
+ margin = m_padStack.SolderMaskMargin( aLayer );
+
+ if( !margin.has_value() )
+ {
+ if( FOOTPRINT* parentFootprint = GetParentFootprint() )
+ margin = parentFootprint->GetLocalSolderMaskMargin();
+ }
}
int marginValue = margin.value_or( 0 );
@@ -1218,31 +1227,40 @@ VECTOR2I PAD::GetSolderPasteMargin( PCB_LAYER_ID aLayer ) const
else
return VECTOR2I( 0, 0 );
- std::optional margin = m_padStack.SolderPasteMargin( aLayer );
- std::optional mratio = m_padStack.SolderPasteMarginRatio( aLayer );
+ std::optional margin;
+ std::optional mratio;
- if( !margin.has_value() )
+ if( const BOARD* board = GetBoard() )
{
- if( FOOTPRINT* parentFootprint = GetParentFootprint() )
- margin = parentFootprint->GetLocalSolderPasteMargin();
+ DRC_CONSTRAINT constraint;
+ std::shared_ptr drcEngine = board->GetDesignSettings().m_DRCEngine;
+
+ constraint = drcEngine->EvalRules( SOLDER_PASTE_ABS_MARGIN_CONSTRAINT, this, nullptr, aLayer );
+
+ if( constraint.m_Value.HasOpt() )
+ margin = constraint.m_Value.Opt();
+
+ constraint = drcEngine->EvalRules( SOLDER_PASTE_REL_MARGIN_CONSTRAINT, this, nullptr, aLayer );
+
+ if( constraint.m_Value.HasOpt() )
+ mratio = constraint.m_Value.Opt() / 1000.0;
}
-
- if( !margin.has_value() )
+ else
{
- if( const BOARD* board = GetBoard() )
- margin = board->GetDesignSettings().m_SolderPasteMargin;
- }
+ margin = m_padStack.SolderPasteMargin( aLayer );
+ mratio = m_padStack.SolderPasteMarginRatio( aLayer );
- if( !mratio.has_value() )
- {
- if( FOOTPRINT* parentFootprint = GetParentFootprint() )
- mratio = parentFootprint->GetLocalSolderPasteMarginRatio();
- }
+ if( !margin.has_value() )
+ {
+ if( FOOTPRINT* parentFootprint = GetParentFootprint() )
+ margin = parentFootprint->GetLocalSolderPasteMargin();
+ }
- if( !mratio.has_value() )
- {
- if( const BOARD* board = GetBoard() )
- mratio = board->GetDesignSettings().m_SolderPasteMarginRatio;
+ if( !mratio.has_value() )
+ {
+ if( FOOTPRINT* parentFootprint = GetParentFootprint() )
+ mratio = parentFootprint->GetLocalSolderPasteMarginRatio();
+ }
}
PCB_LAYER_ID cuLayer = ( aLayer == B_Paste ) ? B_Cu : F_Cu;
@@ -1252,7 +1270,7 @@ VECTOR2I PAD::GetSolderPasteMargin( PCB_LAYER_ID aLayer ) const
pad_margin.x = margin.value_or( 0 ) + KiROUND( padSize.x * mratio.value_or( 0 ) );
pad_margin.y = margin.value_or( 0 ) + KiROUND( padSize.y * mratio.value_or( 0 ) );
- // ensure mask have a size always >= 0
+ // ensure paste have a size always >= 0
if( m_padStack.Shape( aLayer ) != PAD_SHAPE::CUSTOM )
{
if( pad_margin.x < -padSize.x / 2 )
diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp
index 7148a665a0..4973a3e66c 100644
--- a/pcbnew/pcb_shape.cpp
+++ b/pcbnew/pcb_shape.cpp
@@ -184,15 +184,21 @@ void PCB_SHAPE::SetLayer( PCB_LAYER_ID aLayer )
int PCB_SHAPE::GetSolderMaskExpansion() const
{
- int margin = m_solderMaskMargin.value_or( 0 );
+ int margin = 0;
- // If no local margin is set, get the board's solder mask expansion value
- if( !m_solderMaskMargin.has_value() )
+ if( const BOARD* board = GetBoard() )
{
- const BOARD* board = GetBoard();
+ DRC_CONSTRAINT constraint;
+ std::shared_ptr drcEngine = board->GetDesignSettings().m_DRCEngine;
- if( board )
- margin = board->GetDesignSettings().m_SolderMaskExpansion;
+ constraint = drcEngine->EvalRules( SOLDER_MASK_EXPANSION_CONSTRAINT, this, nullptr, m_layer );
+
+ if( constraint.m_Value.HasOpt() )
+ margin = constraint.m_Value.Opt();
+ }
+ else if( m_solderMaskMargin.has_value() )
+ {
+ margin = m_solderMaskMargin.value();
}
// Ensure the resulting mask opening has a non-negative size
diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp
index 4bba68b404..d4b85ed8c9 100644
--- a/pcbnew/pcb_track.cpp
+++ b/pcbnew/pcb_track.cpp
@@ -1130,15 +1130,21 @@ int PCB_VIA::GetSolderMaskExpansion() const
int PCB_TRACK::GetSolderMaskExpansion() const
{
- int margin = m_solderMaskMargin.value_or( 0 );
+ int margin = 0;
- // If no local margin is set, get the board's solder mask expansion value
- if( !m_solderMaskMargin.has_value() )
+ if( const BOARD* board = GetBoard() )
{
- const BOARD* board = GetBoard();
+ DRC_CONSTRAINT constraint;
+ std::shared_ptr drcEngine = board->GetDesignSettings().m_DRCEngine;
- if( board )
- margin = board->GetDesignSettings().m_SolderMaskExpansion;
+ constraint = drcEngine->EvalRules( SOLDER_MASK_EXPANSION_CONSTRAINT, this, nullptr, m_layer );
+
+ if( constraint.m_Value.HasOpt() )
+ margin = constraint.m_Value.Opt();
+ }
+ else if( m_solderMaskMargin.has_value() )
+ {
+ margin = m_solderMaskMargin.value();
}
// Ensure the resulting mask opening has a non-negative size
diff --git a/pcbnew/tools/board_inspection_tool.cpp b/pcbnew/tools/board_inspection_tool.cpp
index 04b3c031e5..2b850c8963 100644
--- a/pcbnew/tools/board_inspection_tool.cpp
+++ b/pcbnew/tools/board_inspection_tool.cpp
@@ -1422,7 +1422,6 @@ int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent )
r = dialog->AddHTMLPage( _( "Hole Size" ) );
reportHeader( _( "Hole size resolution for:" ), item, r );
- // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer....
constraint = EVAL_RULES( HOLE_SIZE_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
if( compileError )
@@ -1437,6 +1436,53 @@ int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent )
r->Flush();
}
+ if( item->Type() == PCB_PAD_T || item->Type() == PCB_SHAPE_T || dynamic_cast( item ) )
+ {
+ r = dialog->AddHTMLPage( _( "Solder Mask" ) );
+ reportHeader( _( "Solder mask expansion resolution for:" ), item, r );
+
+ constraint = EVAL_RULES( SOLDER_MASK_EXPANSION_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
+
+ if( compileError )
+ reportCompileError( r );
+
+ r->Report( "" );
+ r->Report( wxString::Format( _( "Resolved solder mask expansion: %s." ),
+ reportOpt( m_frame, constraint ) ) );
+
+ r->Flush();
+ }
+
+ if( item->Type() == PCB_PAD_T )
+ {
+ r = dialog->AddHTMLPage( _( "Solder Paste" ) );
+ reportHeader( _( "Solder paste absolute clearance resolution for:" ), item, r );
+
+ constraint = EVAL_RULES( SOLDER_PASTE_ABS_MARGIN_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
+
+ if( compileError )
+ reportCompileError( r );
+
+ r->Report( "" );
+ r->Report( wxString::Format( _( "Resolved solder paste absolute clearance: %s." ),
+ reportOpt( m_frame, constraint ) ) );
+
+ reportHeader( _( "Solder paste relative clearance resolution for:" ), item, r );
+
+ constraint = EVAL_RULES( SOLDER_PASTE_REL_MARGIN_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
+
+ if( compileError )
+ reportCompileError( r );
+
+ r->Report( "" );
+ r->Report( "" );
+ r->Report( "" );
+ r->Report( wxString::Format( _( "Resolved solder paste relative clearance: %s." ),
+ reportOpt( m_frame, constraint ) ) );
+
+ r->Flush();
+ }
+
if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T || item->Type() == PCB_TEXTBOX_T )
{
r = dialog->AddHTMLPage( _( "Text Size" ) );