Use std::optional for pad connection overrides.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/19555
This commit is contained in:
Jeff Young 2025-01-09 17:31:14 +00:00
parent 1ebcd24944
commit dd7c076bc9
16 changed files with 102 additions and 41 deletions

View File

@ -555,9 +555,17 @@ wxString PGPROPERTY_ANGLE::ValueToString( wxVariant& aVariant,
wxString PGPROPERTY_ANGLE::ValueToString( wxVariant& aVariant, int aArgFlags ) const wxString PGPROPERTY_ANGLE::ValueToString( wxVariant& aVariant, int aArgFlags ) const
#endif #endif
{ {
if( aVariant.GetType() == wxPG_VARIANT_TYPE_DOUBLE ) if( aVariant.GetType() == wxT( "std::optional<double>" ) )
{
auto* variantData = static_cast<STD_OPTIONAL_DOUBLE_VARIANT_DATA*>( aVariant.GetData() );
if( variantData->Value().has_value() )
return wxString::Format( wxS( "%g\u00B0" ), variantData->Value().value() / m_scale );
else
return wxEmptyString;
}
else if( aVariant.GetType() == wxPG_VARIANT_TYPE_DOUBLE )
{ {
// TODO(JE) Is this still needed?
return wxString::Format( wxS( "%g\u00B0" ), aVariant.GetDouble() / m_scale ); return wxString::Format( wxS( "%g\u00B0" ), aVariant.GetDouble() / m_scale );
} }
else if( aVariant.GetType() == wxS( "EDA_ANGLE" ) ) else if( aVariant.GetType() == wxS( "EDA_ANGLE" ) )

View File

@ -655,15 +655,20 @@ bool UNIT_BINDER::IsIndeterminate() const
bool UNIT_BINDER::IsNull() const bool UNIT_BINDER::IsNull() const
{ {
wxTextEntry* te = dynamic_cast<wxTextEntry*>( m_valueCtrl ); if( wxTextEntry* te = dynamic_cast<wxTextEntry*>( m_valueCtrl ) )
if( te )
return te->GetValue().IsEmpty(); return te->GetValue().IsEmpty();
return false; return false;
} }
void UNIT_BINDER::SetNull()
{
if( wxTextEntry* te = dynamic_cast<wxTextEntry*>( m_valueCtrl ) )
return te->SetValue( wxEmptyString );
}
void UNIT_BINDER::SetLabel( const wxString& aLabel ) void UNIT_BINDER::SetLabel( const wxString& aLabel )
{ {
m_label->SetLabel( aLabel ); m_label->SetLabel( aLabel );

View File

@ -112,9 +112,16 @@ public:
int val = 0; int val = 0;
if( aValue.CheckType<int>() ) if( aValue.CheckType<int>() )
{
val = aValue.As<int>(); val = aValue.As<int>();
}
else if( aValue.CheckType<std::optional<int>>() ) else if( aValue.CheckType<std::optional<int>>() )
val = aValue.As<std::optional<int>>().value_or( 0 ); {
if( aValue.As<std::optional<int>>().has_value() )
val = aValue.As<std::optional<int>>().value();
else
return std::nullopt; // no value for a std::optional is always valid
}
if( val > Max ) if( val > Max )
return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( val, Max ); return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( val, Max );
@ -132,9 +139,16 @@ public:
int val = 0; int val = 0;
if( aValue.CheckType<int>() ) if( aValue.CheckType<int>() )
{
val = aValue.As<int>(); val = aValue.As<int>();
}
else if( aValue.CheckType<std::optional<int>>() ) else if( aValue.CheckType<std::optional<int>>() )
val = aValue.As<std::optional<int>>().value_or( 0 ); {
if( aValue.As<std::optional<int>>().has_value() )
val = aValue.As<std::optional<int>>().value();
else
return std::nullopt; // no value for a std::optional is always valid
}
if( val < 0 ) if( val < 0 )
return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( val, 0 ); return std::make_unique<VALIDATION_ERROR_TOO_SMALL<int>>( val, 0 );

View File

@ -157,6 +157,7 @@ public:
* Return true if the control holds no value (ie: empty string, **not** 0). * Return true if the control holds no value (ie: empty string, **not** 0).
*/ */
bool IsNull() const; bool IsNull() const;
void SetNull();
/** /**
* Validate the control against the given range, informing the user of any errors found. * Validate the control against the given range, informing the user of any errors found.

View File

@ -672,9 +672,17 @@ void DIALOG_PAD_PROPERTIES::initValues()
else else
m_pasteMarginRatio.ChangeValue( wxEmptyString ); m_pasteMarginRatio.ChangeValue( wxEmptyString );
m_spokeWidth.ChangeValue( m_previewPad->GetThermalSpokeWidth() ); if( m_previewPad->GetLocalThermalSpokeWidthOverride().has_value() )
m_spokeWidth.ChangeValue( m_previewPad->GetLocalThermalSpokeWidthOverride().value() );
else
m_spokeWidth.SetNull();
if( m_previewPad->GetLocalThermalGapOverride().has_value() )
m_thermalGap.ChangeValue( m_previewPad->GetLocalThermalGapOverride().value() );
else
m_thermalGap.SetNull();
m_spokeAngle.ChangeAngleValue( m_previewPad->GetThermalSpokeAngle() ); m_spokeAngle.ChangeAngleValue( m_previewPad->GetThermalSpokeAngle() );
m_thermalGap.ChangeValue( m_previewPad->GetThermalGap() );
m_pad_orientation.ChangeAngleValue( m_previewPad->GetOrientation() ); m_pad_orientation.ChangeAngleValue( m_previewPad->GetOrientation() );
m_cbTeardrops->SetValue( m_previewPad->GetTeardropParams().m_Enabled ); m_cbTeardrops->SetValue( m_previewPad->GetTeardropParams().m_Enabled );
@ -1734,9 +1742,13 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad )
else else
aPad->SetLocalSolderPasteMarginRatio( m_pasteMarginRatio.GetDoubleValue() / 100.0 ); aPad->SetLocalSolderPasteMarginRatio( m_pasteMarginRatio.GetDoubleValue() / 100.0 );
aPad->SetThermalSpokeWidth( m_spokeWidth.GetIntValue() ); if( !m_spokeWidth.IsNull() )
aPad->SetLocalThermalSpokeWidthOverride( m_spokeWidth.GetIntValue() );
if( !m_thermalGap.IsNull() )
aPad->SetLocalThermalGapOverride( m_thermalGap.GetIntValue() );
aPad->SetThermalSpokeAngle( m_spokeAngle.GetAngleValue() ); aPad->SetThermalSpokeAngle( m_spokeAngle.GetAngleValue() );
aPad->SetThermalGap( m_thermalGap.GetIntValue() );
// And rotation // And rotation
aPad->SetOrientation( m_pad_orientation.GetAngleValue() ); aPad->SetOrientation( m_pad_orientation.GetAngleValue() );

View File

@ -233,13 +233,15 @@ bool padHasOverrides( const PAD* a, const PAD* b, REPORTER& aReporter )
REPORT_MSG( _( "%s has zone connection override." ), PAD_DESC( a ) ); REPORT_MSG( _( "%s has zone connection override." ), PAD_DESC( a ) );
} }
if( a->GetThermalGap() != b->GetThermalGap() ) if( a->GetLocalThermalGapOverride().has_value()
&& a->GetThermalGap() != b->GetThermalGap() )
{ {
diff = true; diff = true;
REPORT_MSG( _( "%s has thermal relief gap override." ), PAD_DESC( a ) ); REPORT_MSG( _( "%s has thermal relief gap override." ), PAD_DESC( a ) );
} }
if( a->GetThermalSpokeWidth() != b->GetThermalSpokeWidth() ) if( a->GetLocalThermalSpokeWidthOverride().has_value()
&& a->GetLocalThermalSpokeWidthOverride() != b->GetLocalThermalSpokeWidthOverride() )
{ {
diff = true; diff = true;
REPORT_MSG( _( "%s has thermal relief spoke width override." ), PAD_DESC( a ) ); REPORT_MSG( _( "%s has thermal relief spoke width override." ), PAD_DESC( a ) );

View File

@ -1265,7 +1265,7 @@ int PAD::GetLocalThermalGapOverride( wxString* aSource ) const
if( m_padStack.ThermalGap().has_value() && aSource ) if( m_padStack.ThermalGap().has_value() && aSource )
*aSource = _( "pad" ); *aSource = _( "pad" );
return m_padStack.ThermalGap().value_or( 0 ); return GetLocalThermalGapOverride().value_or( 0 );
} }
@ -1877,9 +1877,9 @@ void PAD::ImportSettingsFrom( const PAD& aMasterPad )
SetLocalSolderPasteMarginRatio( aMasterPad.GetLocalSolderPasteMarginRatio() ); SetLocalSolderPasteMarginRatio( aMasterPad.GetLocalSolderPasteMarginRatio() );
SetLocalZoneConnection( aMasterPad.GetLocalZoneConnection() ); SetLocalZoneConnection( aMasterPad.GetLocalZoneConnection() );
SetThermalSpokeWidth( aMasterPad.GetThermalSpokeWidth() ); SetLocalThermalSpokeWidthOverride( aMasterPad.GetLocalThermalSpokeWidthOverride() );
SetThermalSpokeAngle( aMasterPad.GetThermalSpokeAngle() ); SetThermalSpokeAngle( aMasterPad.GetThermalSpokeAngle() );
SetThermalGap( aMasterPad.GetThermalGap() ); SetLocalThermalGapOverride( aMasterPad.GetLocalThermalGapOverride() );
SetCustomShapeInZoneOpt( aMasterPad.GetCustomShapeInZoneOpt() ); SetCustomShapeInZoneOpt( aMasterPad.GetCustomShapeInZoneOpt() );
@ -2720,17 +2720,20 @@ static struct PAD_DESC
constexpr int minZoneWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM ); constexpr int minZoneWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM );
propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Spoke Width" ), propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
&PAD::SetThermalSpokeWidth, &PAD::GetThermalSpokeWidth, _HKI( "Thermal Relief Spoke Width" ),
&PAD::SetLocalThermalSpokeWidthOverride, &PAD::GetLocalThermalSpokeWidthOverride,
PROPERTY_DISPLAY::PT_SIZE ), groupOverrides ) PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
.SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minZoneWidth, INT_MAX> ); .SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minZoneWidth, INT_MAX> );
propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Thermal Relief Spoke Angle" ), propMgr.AddProperty( new PROPERTY<PAD, double>(
_HKI( "Thermal Relief Spoke Angle" ),
&PAD::SetThermalSpokeAngleDegrees, &PAD::GetThermalSpokeAngleDegrees, &PAD::SetThermalSpokeAngleDegrees, &PAD::GetThermalSpokeAngleDegrees,
PROPERTY_DISPLAY::PT_DEGREE ), groupOverrides ); PROPERTY_DISPLAY::PT_DEGREE ), groupOverrides );
propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Gap" ), propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
&PAD::SetThermalGap, &PAD::GetThermalGap, _HKI( "Thermal Relief Gap" ),
&PAD::SetLocalThermalGapOverride, &PAD::GetLocalThermalGapOverride,
PROPERTY_DISPLAY::PT_SIZE ), groupOverrides ) PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
.SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator ); .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );

View File

@ -594,8 +594,14 @@ public:
* Set the width of the thermal spokes connecting the pad to a zone. If != 0 this will * Set the width of the thermal spokes connecting the pad to a zone. If != 0 this will
* override similar settings in the parent footprint and zone. * override similar settings in the parent footprint and zone.
*/ */
void SetThermalSpokeWidth( int aWidth ) { m_padStack.ThermalSpokeWidth() = aWidth; } void SetLocalThermalSpokeWidthOverride( std::optional<int> aWidth )
int GetThermalSpokeWidth() const { return m_padStack.ThermalSpokeWidth().value_or( 0 ); } {
m_padStack.ThermalSpokeWidth() = aWidth;
}
std::optional<int> GetLocalThermalSpokeWidthOverride() const
{
return m_padStack.ThermalSpokeWidth();
}
int GetLocalSpokeWidthOverride( wxString* aSource = nullptr ) const; int GetLocalSpokeWidthOverride( wxString* aSource = nullptr ) const;
@ -626,7 +632,16 @@ public:
void SetThermalGap( int aGap ) { m_padStack.ThermalGap() = aGap; } void SetThermalGap( int aGap ) { m_padStack.ThermalGap() = aGap; }
int GetThermalGap() const { return m_padStack.ThermalGap().value_or( 0 ); } int GetThermalGap() const { return m_padStack.ThermalGap().value_or( 0 ); }
int GetLocalThermalGapOverride( wxString* aSource = nullptr ) const; int GetLocalThermalGapOverride( wxString* aSource ) const;
std::optional<int> GetLocalThermalGapOverride() const
{
return m_padStack.ThermalGap();
}
void SetLocalThermalGapOverride( const std::optional<int>& aOverride )
{
m_padStack.ThermalGap() = aOverride;
}
/** /**
* Has meaning only for rounded rectangle pads. * Has meaning only for rounded rectangle pads.

View File

@ -40,9 +40,9 @@ PADSTACK::PADSTACK( BOARD_ITEM* aParent ) :
{ {
m_copperProps[PADSTACK::ALL_LAYERS].shape = SHAPE_PROPS(); m_copperProps[PADSTACK::ALL_LAYERS].shape = SHAPE_PROPS();
m_copperProps[PADSTACK::ALL_LAYERS].zone_connection = ZONE_CONNECTION::INHERITED; m_copperProps[PADSTACK::ALL_LAYERS].zone_connection = ZONE_CONNECTION::INHERITED;
m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_width = 0; m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_width = std::nullopt;
m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_angle = ANGLE_45; m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_angle = ANGLE_45;
m_copperProps[PADSTACK::ALL_LAYERS].thermal_gap = 0; m_copperProps[PADSTACK::ALL_LAYERS].thermal_gap = std::nullopt;
m_drill.shape = PAD_DRILL_SHAPE::CIRCLE; m_drill.shape = PAD_DRILL_SHAPE::CIRCLE;
m_drill.start = F_Cu; m_drill.start = F_Cu;
@ -247,8 +247,8 @@ bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer )
else else
{ {
CopperLayer( ALL_LAYERS ).zone_connection = ZONE_CONNECTION::INHERITED; CopperLayer( ALL_LAYERS ).zone_connection = ZONE_CONNECTION::INHERITED;
CopperLayer( ALL_LAYERS ).thermal_gap = 0; CopperLayer( ALL_LAYERS ).thermal_gap = std::nullopt;
CopperLayer( ALL_LAYERS ).thermal_spoke_width = 0; CopperLayer( ALL_LAYERS ).thermal_spoke_width = std::nullopt;
CopperLayer( ALL_LAYERS ).thermal_spoke_angle = DefaultThermalSpokeAngleForShape( F_Cu ); CopperLayer( ALL_LAYERS ).thermal_spoke_angle = DefaultThermalSpokeAngleForShape( F_Cu );
} }

View File

@ -1236,7 +1236,7 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad,
pad->SetThermalGap( getKiCadLength( csPadcode.ReliefClearance ) ); pad->SetThermalGap( getKiCadLength( csPadcode.ReliefClearance ) );
if( csPadcode.ReliefWidth != UNDEFINED_VALUE ) if( csPadcode.ReliefWidth != UNDEFINED_VALUE )
pad->SetThermalSpokeWidth( getKiCadLength( csPadcode.ReliefWidth ) ); pad->SetLocalThermalSpokeWidthOverride( getKiCadLength( csPadcode.ReliefWidth ) );
if( csPadcode.DrillDiameter != UNDEFINED_VALUE ) if( csPadcode.DrillDiameter != UNDEFINED_VALUE )
{ {

View File

@ -1567,12 +1567,12 @@ void PCB_IO_KICAD_LEGACY::loadPAD( FOOTPRINT* aFootprint )
else if( TESTLINE( ".ThermalWidth" ) ) else if( TESTLINE( ".ThermalWidth" ) )
{ {
BIU tmp = biuParse( line + SZ( ".ThermalWidth" ) ); BIU tmp = biuParse( line + SZ( ".ThermalWidth" ) );
pad->SetThermalSpokeWidth( tmp ); pad->SetLocalThermalSpokeWidthOverride( tmp );
} }
else if( TESTLINE( ".ThermalGap" ) ) else if( TESTLINE( ".ThermalGap" ) )
{ {
BIU tmp = biuParse( line + SZ( ".ThermalGap" ) ); BIU tmp = biuParse( line + SZ( ".ThermalGap" ) );
pad->SetThermalGap( tmp ); pad->SetLocalThermalGapOverride( tmp );
} }
else if( TESTLINE( "$EndPAD" ) ) else if( TESTLINE( "$EndPAD" ) )
{ {

View File

@ -1668,10 +1668,10 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad ) const
static_cast<int>( aPad->GetLocalZoneConnection() ) ); static_cast<int>( aPad->GetLocalZoneConnection() ) );
} }
if( aPad->GetThermalSpokeWidth() != 0 ) if( aPad->GetLocalThermalSpokeWidthOverride().has_value() )
{ {
m_out->Print( "(thermal_bridge_width %s)", m_out->Print( "(thermal_bridge_width %s)",
formatInternalUnits( aPad->GetThermalSpokeWidth() ).c_str() ); formatInternalUnits( aPad->GetLocalThermalSpokeWidthOverride().value() ).c_str() );
} }
EDA_ANGLE defaultThermalSpokeAngle = ANGLE_90; EDA_ANGLE defaultThermalSpokeAngle = ANGLE_90;
@ -1689,10 +1689,10 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad ) const
EDA_UNIT_UTILS::FormatAngle( aPad->GetThermalSpokeAngle() ).c_str() ); EDA_UNIT_UTILS::FormatAngle( aPad->GetThermalSpokeAngle() ).c_str() );
} }
if( aPad->GetThermalGap() != 0 ) if( aPad->GetLocalThermalGapOverride().has_value() )
{ {
m_out->Print( "(thermal_gap %s)", m_out->Print( "(thermal_gap %s)",
formatInternalUnits( aPad->GetThermalGap() ).c_str() ); formatInternalUnits( aPad->GetLocalThermalGapOverride().value() ).c_str() );
} }
auto anchorShape = auto anchorShape =

View File

@ -5235,7 +5235,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent )
case T_thermal_width: // legacy token case T_thermal_width: // legacy token
case T_thermal_bridge_width: case T_thermal_bridge_width:
pad->SetThermalSpokeWidth( parseBoardUnits( token ) ); pad->SetLocalThermalSpokeWidthOverride( parseBoardUnits( token ) );
NeedRIGHT(); NeedRIGHT();
break; break;

View File

@ -550,8 +550,8 @@ void PCB_SHAPE::SetIsProxyItem( bool aIsProxy )
{ {
if( GetShape() == SHAPE_T::SEGMENT ) if( GetShape() == SHAPE_T::SEGMENT )
{ {
if( parentPad && parentPad->GetThermalSpokeWidth() ) if( parentPad && parentPad->GetLocalThermalSpokeWidthOverride().has_value() )
SetWidth( parentPad->GetThermalSpokeWidth() ); SetWidth( parentPad->GetLocalThermalSpokeWidthOverride().value() );
else else
SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) ); SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
} }

View File

@ -896,8 +896,8 @@ void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommi
if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT ) if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT )
{ {
if( aPad->GetThermalSpokeWidth() ) if( aPad->GetLocalThermalSpokeWidthOverride().has_value() )
shape->SetWidth( aPad->GetThermalSpokeWidth() ); shape->SetWidth( aPad->GetLocalThermalSpokeWidthOverride().value() );
else else
shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) ); shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
} }

View File

@ -367,7 +367,8 @@ void CheckFpPad( const PAD* expected, const PAD* pad )
BOOST_CHECK_EQUAL( expected->GetLocalClearance().value_or( 0 ), BOOST_CHECK_EQUAL( expected->GetLocalClearance().value_or( 0 ),
pad->GetLocalClearance().value_or( 0 ) ); pad->GetLocalClearance().value_or( 0 ) );
CHECK_ENUM_CLASS_EQUAL( expected->GetLocalZoneConnection(), pad->GetLocalZoneConnection() ); CHECK_ENUM_CLASS_EQUAL( expected->GetLocalZoneConnection(), pad->GetLocalZoneConnection() );
BOOST_CHECK_EQUAL( expected->GetThermalSpokeWidth(), pad->GetThermalSpokeWidth() ); BOOST_CHECK_EQUAL( expected->GetLocalThermalSpokeWidthOverride().value_or( 0 ),
pad->GetLocalThermalSpokeWidthOverride().value_or( 0 ) );
BOOST_CHECK_EQUAL( expected->GetThermalSpokeAngle(), pad->GetThermalSpokeAngle() ); BOOST_CHECK_EQUAL( expected->GetThermalSpokeAngle(), pad->GetThermalSpokeAngle() );
BOOST_CHECK_EQUAL( expected->GetThermalGap(), pad->GetThermalGap() ); BOOST_CHECK_EQUAL( expected->GetThermalGap(), pad->GetThermalGap() );
BOOST_CHECK_EQUAL( expected->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ), BOOST_CHECK_EQUAL( expected->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ),