Don't assume an error location for PAD::GetEffectivePolygon().

While ERROR_INSIDE was good for plotting, 3D generation, etc., it's
not good for generating router hulls.

Also reverts part of the change to always use polygons for PNS::SOLIDs.  A single shape in a SHAPE_COMPOUND will be faster (and
more accurate).

Fixes https://gitlab.com/kicad/code/kicad/-/issues/14898
This commit is contained in:
Jeff Young 2023-10-13 12:25:52 +01:00
parent 38c7fa6db9
commit 62d959ed0e
17 changed files with 62 additions and 60 deletions

View File

@ -737,7 +737,7 @@ void BOARD_ADAPTER::buildPadOutlineAsSegments( const PAD* aPad, CONTAINER_2D_BAS
else else
{ {
// For other shapes, add outlines as thick segments in polygon buffer // For other shapes, add outlines as thick segments in polygon buffer
const std::shared_ptr<SHAPE_POLY_SET>& corners = aPad->GetEffectivePolygon(); const std::shared_ptr<SHAPE_POLY_SET>& corners = aPad->GetEffectivePolygon( ERROR_INSIDE );
const SHAPE_LINE_CHAIN& path = corners->COutline( 0 ); const SHAPE_LINE_CHAIN& path = corners->COutline( 0 );
for( int j = 0; j < path.PointCount(); j++ ) for( int j = 0; j < path.PointCount(); j++ )

View File

@ -72,7 +72,7 @@ void buildPadOutlineAsPolygon( const PAD* aPad, SHAPE_POLY_SET& aBuffer, int aWi
else else
{ {
// For other shapes, add outlines as thick segments in polygon buffer // For other shapes, add outlines as thick segments in polygon buffer
const SHAPE_LINE_CHAIN& path = aPad->GetEffectivePolygon()->COutline( 0 ); const SHAPE_LINE_CHAIN& path = aPad->GetEffectivePolygon( ERROR_INSIDE )->COutline( 0 );
for( int ii = 0; ii < path.PointCount(); ++ii ) for( int ii = 0; ii < path.PointCount(); ++ii )
{ {

View File

@ -40,7 +40,7 @@
* or inside of the curve? (Generally speaking filled shape errors go on the inside * or inside of the curve? (Generally speaking filled shape errors go on the inside
* and knockout errors go on the outside. This preserves minimum clearances.) * and knockout errors go on the outside. This preserves minimum clearances.)
*/ */
enum ERROR_LOC { ERROR_OUTSIDE, ERROR_INSIDE }; enum ERROR_LOC { ERROR_OUTSIDE = 0, ERROR_INSIDE };
/** /**
* @return the number of segments to approximate a arc by segments * @return the number of segments to approximate a arc by segments

View File

@ -1975,7 +1975,7 @@ std::tuple<int, double, double> BOARD::GetTrackLength( const PCB_TRACK& aTrack )
VECTOR2I loc; VECTOR2I loc;
// We may not collide even if we passed the bounding-box hit test // We may not collide even if we passed the bounding-box hit test
if( pad->GetEffectivePolygon()->Collide( trackSeg, 0, nullptr, &loc ) ) if( pad->GetEffectivePolygon( ERROR_INSIDE )->Collide( trackSeg, 0, nullptr, &loc ) )
{ {
// Part 1: length of the seg to the intersection with the pad poly // Part 1: length of the seg to the intersection with the pad poly
if( hitStart ) if( hitStart )

View File

@ -448,7 +448,7 @@ bool CONNECTIVITY_DATA::IsConnectedOnLayer( const BOARD_CONNECTED_ITEM *aItem, i
if( zone->IsFilled() ) if( zone->IsFilled() )
{ {
const SHAPE_POLY_SET* zoneFill = zone->GetFill( ToLAYER_ID( aLayer ) ); const SHAPE_POLY_SET* zoneFill = zone->GetFill( ToLAYER_ID( aLayer ) );
const SHAPE_LINE_CHAIN& padHull = pad->GetEffectivePolygon()->Outline( 0 ); const SHAPE_LINE_CHAIN& padHull = pad->GetEffectivePolygon( ERROR_INSIDE )->Outline( 0 );
for( const VECTOR2I& pt : zoneFill->COutline( islandIdx ).CPoints() ) for( const VECTOR2I& pt : zoneFill->COutline( islandIdx ).CPoints() )
{ {

View File

@ -142,7 +142,8 @@ static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape
{ {
SHAPE_POLY_SET poly = aShape.CloneDropTriangulation(); SHAPE_POLY_SET poly = aShape.CloneDropTriangulation();
poly.BooleanIntersection( *pad->GetEffectivePolygon(), SHAPE_POLY_SET::PM_FAST ); poly.BooleanIntersection( *pad->GetEffectivePolygon( ERROR_INSIDE ),
SHAPE_POLY_SET::PM_FAST );
if( poly.OutlineCount() == 0 ) if( poly.OutlineCount() == 0 )
{ {

View File

@ -215,7 +215,7 @@ STEP_PCB_MODEL::~STEP_PCB_MODEL()
bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin ) bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
{ {
const std::shared_ptr<SHAPE_POLY_SET>& pad_shape = aPad->GetEffectivePolygon(); const std::shared_ptr<SHAPE_POLY_SET>& pad_shape = aPad->GetEffectivePolygon( ERROR_INSIDE );
bool success = true; bool success = true;
VECTOR2I pos = aPad->GetPosition(); VECTOR2I pos = aPad->GetPosition();

View File

@ -356,12 +356,12 @@ void PAD::SetChamferRectRatio( double aChamferScale )
} }
const std::shared_ptr<SHAPE_POLY_SET>& PAD::GetEffectivePolygon() const const std::shared_ptr<SHAPE_POLY_SET>& PAD::GetEffectivePolygon( ERROR_LOC aErrorLoc ) const
{ {
if( m_polyDirty ) if( m_polyDirty[ aErrorLoc ] )
BuildEffectivePolygon(); BuildEffectivePolygon( aErrorLoc );
return m_effectivePolygon; return m_effectivePolygon[ aErrorLoc ];
} }
@ -414,7 +414,7 @@ std::shared_ptr<SHAPE_SEGMENT> PAD::GetEffectiveHoleShape() const
int PAD::GetBoundingRadius() const int PAD::GetBoundingRadius() const
{ {
if( m_polyDirty ) if( m_polyDirty )
BuildEffectivePolygon(); BuildEffectivePolygon( ERROR_OUTSIDE );
return m_effectiveBoundingRadius; return m_effectiveBoundingRadius;
} }
@ -595,31 +595,35 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const
} }
void PAD::BuildEffectivePolygon() const void PAD::BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const
{ {
std::lock_guard<std::mutex> RAII_lock( m_polyBuildingLock ); std::lock_guard<std::mutex> RAII_lock( m_polyBuildingLock );
// If we had to wait for the lock then we were probably waiting for someone else to // If we had to wait for the lock then we were probably waiting for someone else to
// finish rebuilding the shapes. So check to see if they're clean now. // finish rebuilding the shapes. So check to see if they're clean now.
if( !m_polyDirty ) if( !m_polyDirty[ aErrorLoc ] )
return; return;
const BOARD* board = GetBoard(); const BOARD* board = GetBoard();
int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
// Polygon // Polygon
m_effectivePolygon = std::make_shared<SHAPE_POLY_SET>(); std::shared_ptr<SHAPE_POLY_SET>& effectivePolygon = m_effectivePolygon[ aErrorLoc ];
TransformShapeToPolygon( *m_effectivePolygon, UNDEFINED_LAYER, 0, maxError, ERROR_INSIDE );
effectivePolygon = std::make_shared<SHAPE_POLY_SET>();
TransformShapeToPolygon( *effectivePolygon, UNDEFINED_LAYER, 0, maxError, aErrorLoc );
// Bounding radius // Bounding radius
// //
// PADSTACKS TODO: these will both need to cycle through all layers to get the largest // PADSTACKS TODO: these will both need to cycle through all layers to get the largest
// values.... // values....
if( aErrorLoc == ERROR_OUTSIDE )
{
m_effectiveBoundingRadius = 0; m_effectiveBoundingRadius = 0;
for( int cnt = 0; cnt < m_effectivePolygon->OutlineCount(); ++cnt ) for( int cnt = 0; cnt < effectivePolygon->OutlineCount(); ++cnt )
{ {
const SHAPE_LINE_CHAIN& poly = m_effectivePolygon->COutline( cnt ); const SHAPE_LINE_CHAIN& poly = effectivePolygon->COutline( cnt );
for( int ii = 0; ii < poly.PointCount(); ++ii ) for( int ii = 0; ii < poly.PointCount(); ++ii )
{ {
@ -627,9 +631,10 @@ void PAD::BuildEffectivePolygon() const
m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist ); m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist );
} }
} }
}
// All done // All done
m_polyDirty = false; m_polyDirty[ aErrorLoc ] = false;
} }
@ -1086,7 +1091,7 @@ bool PAD::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) ) if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) )
return false; return false;
return GetEffectivePolygon()->Contains( aPosition, -1, aAccuracy ); return GetEffectivePolygon( ERROR_INSIDE )->Contains( aPosition, -1, aAccuracy );
} }
@ -1109,7 +1114,7 @@ bool PAD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
if( !arect.Intersects( bbox ) ) if( !arect.Intersects( bbox ) )
return false; return false;
const std::shared_ptr<SHAPE_POLY_SET>& poly = GetEffectivePolygon(); const std::shared_ptr<SHAPE_POLY_SET>& poly = GetEffectivePolygon( ERROR_INSIDE );
int count = poly->TotalVertices(); int count = poly->TotalVertices();

View File

@ -356,13 +356,14 @@ public:
bool IsDirty() const bool IsDirty() const
{ {
return m_shapesDirty || m_polyDirty; return m_shapesDirty || m_polyDirty[ERROR_INSIDE] || m_polyDirty[ERROR_OUTSIDE];
} }
void SetDirty() void SetDirty()
{ {
m_shapesDirty = true; m_shapesDirty = true;
m_polyDirty = true; m_polyDirty[ERROR_INSIDE] = true;
m_polyDirty[ERROR_OUTSIDE] = true;
} }
void SetLayerSet( LSET aLayers ) override { m_layerMask = aLayers; } void SetLayerSet( LSET aLayers ) override { m_layerMask = aLayers; }
@ -443,7 +444,7 @@ public:
GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER, GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
FLASHING flashPTHPads = FLASHING::DEFAULT ) const override; FLASHING flashPTHPads = FLASHING::DEFAULT ) const override;
const std::shared_ptr<SHAPE_POLY_SET>& GetEffectivePolygon() const; const std::shared_ptr<SHAPE_POLY_SET>& GetEffectivePolygon( ERROR_LOC aErrorLoc ) const;
/** /**
* Return a SHAPE_SEGMENT object representing the pad's hole. * Return a SHAPE_SEGMENT object representing the pad's hole.
@ -695,7 +696,7 @@ public:
* the dirty bit. * the dirty bit.
*/ */
void BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const; void BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const;
void BuildEffectivePolygon() const; void BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const;
virtual void ViewGetLayers( int aLayers[], int& aCount ) const override; virtual void ViewGetLayers( int aLayers[], int& aCount ) const override;
@ -754,9 +755,9 @@ private:
mutable std::shared_ptr<SHAPE_COMPOUND> m_effectiveShape; mutable std::shared_ptr<SHAPE_COMPOUND> m_effectiveShape;
mutable std::shared_ptr<SHAPE_SEGMENT> m_effectiveHoleShape; mutable std::shared_ptr<SHAPE_SEGMENT> m_effectiveHoleShape;
mutable bool m_polyDirty; mutable bool m_polyDirty[2];
mutable std::mutex m_polyBuildingLock; mutable std::mutex m_polyBuildingLock;
mutable std::shared_ptr<SHAPE_POLY_SET> m_effectivePolygon; mutable std::shared_ptr<SHAPE_POLY_SET> m_effectivePolygon[2];
mutable int m_effectiveBoundingRadius; mutable int m_effectiveBoundingRadius;
int m_subRatsnest; // Variable used to handle subnet (block) number in int m_subRatsnest; // Variable used to handle subnet (block) number in

View File

@ -260,7 +260,7 @@ void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_
default: default:
case PAD_SHAPE::CUSTOM: case PAD_SHAPE::CUSTOM:
{ {
const std::shared_ptr<SHAPE_POLY_SET>& polygons = aPad->GetEffectivePolygon(); const std::shared_ptr<SHAPE_POLY_SET>& polygons = aPad->GetEffectivePolygon( ERROR_INSIDE );
if( polygons->OutlineCount() ) if( polygons->OutlineCount() )
{ {

View File

@ -1185,23 +1185,26 @@ std::unique_ptr<PNS::SOLID> PNS_KICAD_IFACE_BASE::syncPad( PAD* aPad )
if( aPad->GetDrillSize().x > 0 ) if( aPad->GetDrillSize().x > 0 )
solid->SetHole( new PNS::HOLE( aPad->GetEffectiveHoleShape()->Clone() ) ); solid->SetHole( new PNS::HOLE( aPad->GetEffectiveHoleShape()->Clone() ) );
std::shared_ptr<SHAPE> shape = aPad->GetEffectiveShape( UNDEFINED_LAYER, // We generate a single SOLID for a pad, so we have to treat it as ALWAYS_FLASHED and then
// perform layer-specific flashing tests internally.
const std::shared_ptr<SHAPE>& shape = aPad->GetEffectiveShape( UNDEFINED_LAYER,
FLASHING::ALWAYS_FLASHED ); FLASHING::ALWAYS_FLASHED );
std::shared_ptr<SHAPE_POLY_SET> polygon = aPad->GetEffectivePolygon();
if( shape->HasIndexableSubshapes() && polygon->OutlineCount() ) if( shape->HasIndexableSubshapes() && shape->GetIndexableSubshapeCount() == 1 )
{ {
solid->SetShape( new SHAPE_SIMPLE( polygon->Outline( 0 ) ) ); std::vector<const SHAPE*> subshapes;
shape->GetIndexableSubshapes( subshapes );
// GetEffectivePolygon may produce an approximation of the shape, so we need to account for solid->SetShape( subshapes[0]->Clone() );
// this when building hulls around this shape.
solid->SetExtraClearance( m_board->GetDesignSettings().m_MaxError );
} }
// For anything that's not a single shape we use a polygon. Multiple shapes have a tendency
// to confuse the hull generator. https://gitlab.com/kicad/code/kicad/-/issues/15553
else else
{ {
// Prefer using the original shape if it's not a compound shape; the hulls for const std::shared_ptr<SHAPE_POLY_SET>& poly = aPad->GetEffectivePolygon( ERROR_OUTSIDE );
// circular and rectangular pads can be exact.
solid->SetShape( shape->Clone() ); if( poly->OutlineCount() )
solid->SetShape( new SHAPE_SIMPLE( poly->Outline( 0 ) ) );
} }
return solid; return solid;

View File

@ -41,8 +41,6 @@ const SHAPE_LINE_CHAIN SOLID::Hull( int aClearance, int aWalkaroundThickness, in
if( !m_shape ) if( !m_shape )
return SHAPE_LINE_CHAIN(); return SHAPE_LINE_CHAIN();
aClearance += ExtraClearance();
if( m_shape->Type() == SH_COMPOUND ) if( m_shape->Type() == SH_COMPOUND )
{ {
SHAPE_COMPOUND* cmpnd = static_cast<SHAPE_COMPOUND*>( m_shape ); SHAPE_COMPOUND* cmpnd = static_cast<SHAPE_COMPOUND*>( m_shape );

View File

@ -38,8 +38,7 @@ public:
SOLID() : SOLID() :
ITEM( SOLID_T ), ITEM( SOLID_T ),
m_shape( nullptr ), m_shape( nullptr ),
m_hole( nullptr ), m_hole( nullptr )
m_extraClearance( 0 )
{ {
m_movable = false; m_movable = false;
m_padToDie = 0; m_padToDie = 0;
@ -66,7 +65,6 @@ public:
m_padToDie = aSolid.m_padToDie; m_padToDie = aSolid.m_padToDie;
m_orientation = aSolid.m_orientation; m_orientation = aSolid.m_orientation;
m_anchorPoints = aSolid.m_anchorPoints; m_anchorPoints = aSolid.m_anchorPoints;
m_extraClearance = aSolid.m_extraClearance;
} }
SOLID& operator=( const SOLID& aB ) SOLID& operator=( const SOLID& aB )
@ -81,7 +79,6 @@ public:
m_padToDie = aB.m_padToDie; m_padToDie = aB.m_padToDie;
m_orientation = aB.m_orientation; m_orientation = aB.m_orientation;
m_anchorPoints = aB.m_anchorPoints; m_anchorPoints = aB.m_anchorPoints;
m_extraClearance = aB.m_extraClearance;
return *this; return *this;
} }
@ -138,9 +135,6 @@ public:
virtual bool HasHole() const override { return m_hole != nullptr; } virtual bool HasHole() const override { return m_hole != nullptr; }
virtual HOLE *Hole() const override { return m_hole; } virtual HOLE *Hole() const override { return m_hole; }
int ExtraClearance() const { return m_extraClearance; }
void SetExtraClearance( int aClearance ) { m_extraClearance = aClearance; }
private: private:
VECTOR2I m_pos; VECTOR2I m_pos;
SHAPE* m_shape; SHAPE* m_shape;
@ -149,7 +143,6 @@ private:
EDA_ANGLE m_orientation; EDA_ANGLE m_orientation;
HOLE* m_hole; HOLE* m_hole;
std::vector<VECTOR2I> m_anchorPoints; std::vector<VECTOR2I> m_anchorPoints;
int m_extraClearance;
}; };
} }

View File

@ -367,7 +367,7 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ITEM* aStart, SOLID** aStartPad, SO
auto clipLineToPad = auto clipLineToPad =
[]( SHAPE_LINE_CHAIN& aLine, PAD* aPad, bool aForward = true ) []( SHAPE_LINE_CHAIN& aLine, PAD* aPad, bool aForward = true )
{ {
const std::shared_ptr<SHAPE_POLY_SET>& shape = aPad->GetEffectivePolygon(); const auto& shape = aPad->GetEffectivePolygon( ERROR_INSIDE );
int start = aForward ? 0 : aLine.PointCount() - 1; int start = aForward ? 0 : aLine.PointCount() - 1;
int delta = aForward ? 1 : -1; int delta = aForward ? 1 : -1;
@ -413,7 +413,7 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ITEM* aStart, SOLID** aStartPad, SO
auto processPad = auto processPad =
[&]( const JOINT* aJoint, PAD* aPad ) [&]( const JOINT* aJoint, PAD* aPad )
{ {
const std::shared_ptr<SHAPE_POLY_SET>& shape = aPad->GetEffectivePolygon(); const auto& shape = aPad->GetEffectivePolygon( ERROR_INSIDE );
for( int idx = 0; idx < initialPath.Size(); idx++ ) for( int idx = 0; idx < initialPath.Size(); idx++ )
{ {

View File

@ -642,7 +642,7 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos
default: default:
{ {
const std::shared_ptr<SHAPE_POLY_SET>& outline = aPad->GetEffectivePolygon(); const auto& outline = aPad->GetEffectivePolygon( ERROR_INSIDE );
if( !outline->IsEmpty() ) if( !outline->IsEmpty() )
{ {

View File

@ -328,7 +328,8 @@ void TRACKS_CLEANER::deleteTracksInPads()
track->TransformShapeToPolygon( poly, track->GetLayer(), 0, ARC_HIGH_DEF, track->TransformShapeToPolygon( poly, track->GetLayer(), 0, ARC_HIGH_DEF,
ERROR_INSIDE ); ERROR_INSIDE );
poly.BooleanSubtract( *pad->GetEffectivePolygon(), SHAPE_POLY_SET::PM_FAST ); poly.BooleanSubtract( *pad->GetEffectivePolygon( ERROR_INSIDE ),
SHAPE_POLY_SET::PM_FAST );
if( poly.IsEmpty() ) if( poly.IsEmpty() )
{ {

View File

@ -125,7 +125,7 @@ bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aPare
if( pad->IsDirty() ) if( pad->IsDirty() )
{ {
pad->BuildEffectiveShapes( UNDEFINED_LAYER ); pad->BuildEffectiveShapes( UNDEFINED_LAYER );
pad->BuildEffectivePolygon(); pad->BuildEffectivePolygon( ERROR_OUTSIDE );
} }
} }
@ -1864,7 +1864,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
seg.B += pad->ShapePos(); seg.B += pad->ShapePos();
// Make sure seg.A is the origin // Make sure seg.A is the origin
if( !pad->GetEffectivePolygon()->Contains( seg.A ) ) if( !pad->GetEffectivePolygon( ERROR_OUTSIDE )->Contains( seg.A ) )
seg.Reverse(); seg.Reverse();
// Trim seg.B to the thermal outline // Trim seg.B to the thermal outline