ADDED: custom-shaped pad spoke templates.

This commit is contained in:
Jeff Young 2023-09-13 11:27:04 +01:00
parent 31c88d1bcb
commit d6b75c64e1
12 changed files with 215 additions and 132 deletions

View File

@ -47,7 +47,7 @@ EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
m_rectangleWidth( 0 ),
m_segmentLength( 0 ),
m_editState( 0 ),
m_annotationProxy( false )
m_proxyItem( false )
{
}
@ -59,18 +59,27 @@ EDA_SHAPE::~EDA_SHAPE()
wxString EDA_SHAPE::ShowShape() const
{
if( IsAnnotationProxy() )
return _( "Number Box" );
switch( m_shape )
if( IsProxyItem() )
{
case SHAPE_T::SEGMENT: return _( "Line" );
case SHAPE_T::RECTANGLE: return _( "Rect" );
case SHAPE_T::ARC: return _( "Arc" );
case SHAPE_T::CIRCLE: return _( "Circle" );
case SHAPE_T::BEZIER: return _( "Bezier Curve" );
case SHAPE_T::POLY: return _( "Polygon" );
default: return wxT( "??" );
switch( m_shape )
{
case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
case SHAPE_T::RECTANGLE: return _( "Number Box" );
default: return wxT( "??" );
}
}
else
{
switch( m_shape )
{
case SHAPE_T::SEGMENT: return _( "Line" );
case SHAPE_T::RECTANGLE: return _( "Rect" );
case SHAPE_T::ARC: return _( "Arc" );
case SHAPE_T::CIRCLE: return _( "Circle" );
case SHAPE_T::BEZIER: return _( "Bezier Curve" );
case SHAPE_T::POLY: return _( "Polygon" );
default: return wxT( "??" );
}
}
}
@ -674,15 +683,27 @@ void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeA
wxString EDA_SHAPE::GetFriendlyName() const
{
switch( m_shape )
if( IsProxyItem() )
{
case SHAPE_T::CIRCLE: return _( "Circle" );
case SHAPE_T::ARC: return _( "Arc" );
case SHAPE_T::BEZIER: return _( "Curve" );
case SHAPE_T::POLY: return _( "Polygon" );
case SHAPE_T::RECTANGLE: return IsAnnotationProxy() ? _( "Pad Number Box" ) : _( "Rectangle" );
case SHAPE_T::SEGMENT: return _( "Segment" );
default: return _( "Unrecognized" );
switch( m_shape )
{
case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
default: return _( "Unrecognized" );
}
}
else
{
switch( m_shape )
{
case SHAPE_T::CIRCLE: return _( "Circle" );
case SHAPE_T::ARC: return _( "Arc" );
case SHAPE_T::BEZIER: return _( "Curve" );
case SHAPE_T::POLY: return _( "Polygon" );
case SHAPE_T::RECTANGLE: return _( "Rectangle" );
case SHAPE_T::SEGMENT: return _( "Segment" );
default: return _( "Unrecognized" );
}
}
}
@ -880,7 +901,7 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
case SHAPE_T::RECTANGLE:
if( IsAnnotationProxy() || IsFilled() ) // Filled rect hit-test
if( IsProxyItem() || IsFilled() ) // Filled rect hit-test
{
SHAPE_POLY_SET poly;
poly.NewOutline();
@ -1175,7 +1196,7 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh
{
std::vector<VECTOR2I> pts = GetRectCorners();
if( ( IsFilled() || IsAnnotationProxy() ) && !aEdgeOnly )
if( ( IsFilled() || IsProxyItem() ) && !aEdgeOnly )
effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
if( width > 0 || !IsFilled() || aEdgeOnly )
@ -1598,7 +1619,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance
{
std::vector<VECTOR2I> pts = GetRectCorners();
if( IsFilled() || IsAnnotationProxy() )
if( IsFilled() || IsProxyItem() )
{
aBuffer.NewOutline();

View File

@ -85,8 +85,8 @@ public:
wxString SHAPE_T_asString() const;
virtual bool IsAnnotationProxy() const { return m_annotationProxy; }
virtual void SetIsAnnotationProxy( bool aIsProxy = true ) { m_annotationProxy = aIsProxy; }
virtual bool IsProxyItem() const { return m_proxyItem; }
virtual void SetIsProxyItem( bool aIsProxy = true ) { m_proxyItem = aIsProxy; }
bool IsFilled() const
{
@ -399,7 +399,8 @@ protected:
SHAPE_POLY_SET m_poly; // Stores the S_POLYGON shape
int m_editState;
bool m_annotationProxy; // A shape storing the position of an annotation
bool m_proxyItem; // A shape storing proxy information (ie: a pad
// number box, thermal spoke template, etc.)
};
#ifndef SWIG

View File

@ -566,7 +566,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const
{
for( const std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
{
if( !primitive->IsAnnotationProxy() )
if( !primitive->IsProxyItem() )
{
for( SHAPE* shape : primitive->MakeEffectiveShapes() )
{

View File

@ -124,7 +124,7 @@ void PAD::addPadPrimitivesToPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError,
for( const std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
{
if( !primitive->IsAnnotationProxy() )
if( !primitive->IsProxyItem() )
primitive->TransformShapeToPolygon( polyset, UNDEFINED_LAYER, 0, aError, aErrorLoc );
}

View File

@ -1171,7 +1171,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
{
const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
if( shape->IsAnnotationProxy() )
if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::RECTANGLE )
{
position = shape->GetCenter();
padsize = shape->GetBotRight() - shape->GetTopLeft();
@ -1190,7 +1190,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
// See if we have a number box
for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives() )
{
if( primitive->IsAnnotationProxy() )
if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE )
{
position = aPad->GetPosition() + primitive->GetCenter();
padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x );
@ -1734,7 +1734,14 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
switch( aShape->GetShape() )
{
case SHAPE_T::SEGMENT:
if( outline_mode )
if( aShape->IsProxyItem() )
{
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
}
else if( outline_mode )
{
m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
}
@ -1752,7 +1759,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
{
std::vector<VECTOR2I> pts = aShape->GetRectCorners();
if( aShape->IsAnnotationProxy() )
if( aShape->IsProxyItem() )
{
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->DrawLine( pts[0], pts[1] );

View File

@ -354,14 +354,45 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis )
}
void PCB_SHAPE::SetIsAnnotationProxy( bool aIsProxy )
void PCB_SHAPE::SetIsProxyItem( bool aIsProxy )
{
if( aIsProxy && !m_annotationProxy )
SetStroke( STROKE_PARAMS( 1 ) );
else if( m_annotationProxy && !aIsProxy )
SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) ) );
PAD* parentPad = nullptr;
m_annotationProxy = aIsProxy;
if( GetBoard() && GetBoard()->IsFootprintHolder() )
{
for( FOOTPRINT* fp : GetBoard()->Footprints() )
{
for( PAD* pad : fp->Pads() )
{
if( pad->IsEntered() )
{
parentPad = pad;
break;
}
}
}
}
if( aIsProxy && !m_proxyItem )
{
if( GetShape() == SHAPE_T::SEGMENT )
{
if( parentPad && parentPad->GetThermalSpokeWidth() )
SetWidth( parentPad->GetThermalSpokeWidth() );
else
SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
}
else
{
SetWidth( 1 );
}
}
else if( m_proxyItem && !aIsProxy )
{
SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) );
}
m_proxyItem = aIsProxy;
}
@ -591,21 +622,42 @@ static struct PCB_SHAPE_DESC
_HKI( "Net" ), isCopper );
auto isPadEditMode =
[]( INSPECTABLE* aItem ) -> bool
[]( BOARD* aBoard ) -> bool
{
if( aBoard && aBoard->IsFootprintHolder() )
{
for( FOOTPRINT* fp : aBoard->Footprints() )
{
for( PAD* pad : fp->Pads() )
{
if( pad->IsEntered() )
return true;
}
}
}
return false;
};
auto showNumberBoxProperty =
[&]( INSPECTABLE* aItem ) -> bool
{
if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aItem ) )
{
if( shape->GetBoard() && shape->GetBoard()->IsFootprintHolder() )
{
for( FOOTPRINT* fp : shape->GetBoard()->Footprints() )
{
for( PAD* pad : fp->Pads() )
{
if( pad->IsEntered() )
return true;
}
}
}
if( shape->GetShape() == SHAPE_T::RECTANGLE )
return isPadEditMode( shape->GetBoard() );
}
return false;
};
auto showSpokeTemplateProperty =
[&]( INSPECTABLE* aItem ) -> bool
{
if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aItem ) )
{
if( shape->GetShape() == SHAPE_T::SEGMENT )
return isPadEditMode( shape->GetBoard() );
}
return false;
@ -614,8 +666,15 @@ static struct PCB_SHAPE_DESC
const wxString groupPadPrimitives = _HKI( "Pad Primitives" );
propMgr.AddProperty( new PROPERTY<PCB_SHAPE, bool>( _HKI( "Number Box" ),
&PCB_SHAPE::SetIsAnnotationProxy, &PCB_SHAPE::IsAnnotationProxy ),
&PCB_SHAPE::SetIsProxyItem,
&PCB_SHAPE::IsProxyItem ),
groupPadPrimitives )
.SetAvailableFunc( isPadEditMode );
.SetAvailableFunc( showNumberBoxProperty );
propMgr.AddProperty( new PROPERTY<PCB_SHAPE, bool>( _HKI( "Thermal Spoke Template" ),
&PCB_SHAPE::SetIsProxyItem,
&PCB_SHAPE::IsProxyItem ),
groupPadPrimitives )
.SetAvailableFunc( showSpokeTemplateProperty );
}
} _PCB_SHAPE_DESC;

View File

@ -102,8 +102,8 @@ public:
std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
FLASHING aFlash = FLASHING::DEFAULT ) const override;
bool IsAnnotationProxy() const override { return m_annotationProxy; }
void SetIsAnnotationProxy( bool aIsProxy = true ) override;
bool IsProxyItem() const override { return m_proxyItem; }
void SetIsProxyItem( bool aIsProxy = true ) override;
void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;

View File

@ -4585,7 +4585,7 @@ PAD* PCB_PARSER::parsePAD( FOOTPRINT* aParent )
case T_gr_bbox:
{
PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr );
numberBox->SetIsAnnotationProxy();
numberBox->SetIsProxyItem();
pad->AddPrimitive( numberBox );
break;
}

View File

@ -1704,7 +1704,7 @@ void PCB_PLUGIN::format( const PAD* aPad, int aNestLevel ) const
break;
case SHAPE_T::RECTANGLE:
if( primitive->IsAnnotationProxy() )
if( primitive->IsProxyItem() )
{
m_out->Print( nested_level, "(gr_bbox (start %s) (end %s)",
formatInternalUnits( primitive->GetStart() ).c_str(),

View File

@ -766,48 +766,21 @@ PCB_LAYER_ID PAD_TOOL::explodePad( PAD* aPad )
for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives() )
{
PCB_SHAPE* shape = new PCB_SHAPE( board()->GetFirstFootprint() );
shape->SetShape( primitive->GetShape() );
shape->SetIsAnnotationProxy( primitive->IsAnnotationProxy());
shape->SetFilled( primitive->IsFilled() );
shape->SetStroke( primitive->GetStroke() );
switch( shape->GetShape() )
{
case SHAPE_T::SEGMENT:
case SHAPE_T::RECTANGLE:
case SHAPE_T::CIRCLE:
shape->SetStart( primitive->GetStart() );
shape->SetEnd( primitive->GetEnd() );
break;
case SHAPE_T::ARC:
shape->SetStart( primitive->GetStart() );
shape->SetEnd( primitive->GetEnd() );
shape->SetCenter( primitive->GetCenter() );
break;
case SHAPE_T::BEZIER:
shape->SetStart( primitive->GetStart() );
shape->SetEnd( primitive->GetEnd() );
shape->SetBezierC1( primitive->GetBezierC1() );
shape->SetBezierC2( primitive->GetBezierC2() );
break;
case SHAPE_T::POLY:
shape->SetPolyShape( primitive->GetPolyShape() );
break;
default:
UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
}
PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( primitive->Duplicate() );
shape->SetParent( board()->GetFirstFootprint() );
shape->Rotate( VECTOR2I( 0, 0 ), aPad->GetOrientation() );
shape->Move( aPad->ShapePos() );
shape->SetLayer( layer );
if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT )
{
if( aPad->GetThermalSpokeWidth() )
shape->SetWidth( aPad->GetThermalSpokeWidth() );
else
shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
}
commit.Add( shape );
}
@ -851,7 +824,7 @@ std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun, BOARD
if( shape->GetLayer() != aLayer )
continue;
if( shape->IsAnnotationProxy() ) // Pad number (and net name) box
if( shape->IsProxyItem() ) // Pad number (and net name) box
return shape;
SHAPE_POLY_SET drawPoly;
@ -938,46 +911,12 @@ std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun, BOARD
if( !aIsDryRun )
{
PCB_SHAPE* primitive = new PCB_SHAPE;
primitive->SetShape( fpShape->GetShape() );
primitive->SetFilled( fpShape->IsFilled() );
primitive->SetStroke( fpShape->GetStroke() );
switch( primitive->GetShape() )
{
case SHAPE_T::SEGMENT:
case SHAPE_T::RECTANGLE:
case SHAPE_T::CIRCLE:
primitive->SetStart( fpShape->GetStart() );
primitive->SetEnd( fpShape->GetEnd() );
break;
case SHAPE_T::ARC:
primitive->SetStart( fpShape->GetStart() );
primitive->SetEnd( fpShape->GetEnd() );
primitive->SetCenter( fpShape->GetCenter() );
break;
case SHAPE_T::BEZIER:
primitive->SetStart( fpShape->GetStart() );
primitive->SetEnd( fpShape->GetEnd() );
primitive->SetBezierC1( fpShape->GetBezierC1() );
primitive->SetBezierC2( fpShape->GetBezierC2() );
break;
case SHAPE_T::POLY:
primitive->SetPolyShape( fpShape->GetPolyShape() );
break;
default:
UNIMPLEMENTED_FOR( primitive->SHAPE_T_asString() );
}
PCB_SHAPE* primitive = static_cast<PCB_SHAPE*>( fpShape->Duplicate() );
primitive->SetParent( nullptr );
primitive->Move( - aPad->ShapePos() );
primitive->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
primitive->SetIsAnnotationProxy( fpShape->IsAnnotationProxy() );
aPad->AddPrimitive( primitive );
aCommit.Remove( fpShape );

View File

@ -1303,7 +1303,7 @@ void PCB_POINT_EDITOR::updateItem() const
break;
}
if( shape->IsAnnotationProxy() )
if( shape->IsProxyItem() )
{
for( PAD* pad : shape->GetParentFootprint()->Pads() )
{

View File

@ -1774,6 +1774,20 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
if( !( itemBB.Intersects( zoneBB ) ) )
continue;
bool customSpokes = false;
if( pad->GetShape() == PAD_SHAPE::CUSTOM )
{
for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives() )
{
if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
{
customSpokes = true;
break;
}
}
}
// Thermal spokes consist of square-ended segments from the pad center to points just
// outside the thermal relief. The outside end has an extra center point (which must be
// at idx 3) which is used for testing whether or not the spoke connects to copper in the
@ -1826,9 +1840,51 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
}
};
if( customSpokes )
{
SHAPE_POLY_SET thermalPoly;
SHAPE_LINE_CHAIN thermalOutline;
pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon,
m_maxError, ERROR_OUTSIDE );
if( thermalPoly.OutlineCount() )
thermalOutline = thermalPoly.Outline( 0 );
for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives() )
{
if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
{
SEG seg( primitive->GetStart(), primitive->GetEnd() );
SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
// Make sure seg.A is the origin
if( !pad->GetEffectivePolygon()->Contains( seg.A ) )
seg.Reverse();
// Trim seg.B to the thermal outline
if( thermalOutline.Intersect( seg, intersections ) )
{
seg.B = intersections.front().p;
VECTOR2I offset = ( seg.B - seg.A ).Perpendicular().Resize( spoke_half_w );
SHAPE_LINE_CHAIN spoke;
spoke.Append( seg.A + offset );
spoke.Append( seg.A - offset );
spoke.Append( seg.B - offset );
spoke.Append( seg.B ); // test pt
spoke.Append( seg.B + offset );
spoke.SetClosed( true );
aSpokesList.push_back( std::move( spoke ) );
}
}
}
}
// If the spokes are at a cardinal angle then we can generate them from a bounding box
// without trig.
if( ( pad->GetOrientation() + pad->GetThermalSpokeAngle() ).IsCardinal() )
else if( ( pad->GetOrientation() + pad->GetThermalSpokeAngle() ).IsCardinal() )
{
BOX2I spokesBox = pad->GetBoundingBox();
spokesBox.Inflate( thermalReliefGap + epsilon );