Add additional handling for arc collisions

Provides nearest point calculation for circles, segments and rects

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18203
This commit is contained in:
Seth Hillbrand 2025-01-14 12:16:13 -08:00
parent bcebc694b8
commit bfb3875a68
8 changed files with 494 additions and 18 deletions

View File

@ -32,7 +32,9 @@
#include <geometry/eda_angle.h> #include <geometry/eda_angle.h>
class CIRCLE; class CIRCLE;
class SHAPE_CIRCLE;
class SHAPE_LINE_CHAIN; class SHAPE_LINE_CHAIN;
class SHAPE_RECT;
class SHAPE_ARC : public SHAPE class SHAPE_ARC : public SHAPE
{ {
@ -120,7 +122,6 @@ public:
VECTOR2I NearestPoint( const VECTOR2I& aP ) const; VECTOR2I NearestPoint( const VECTOR2I& aP ) const;
/** /**
* Compute closest points between this arc and \a aArc. * Compute closest points between this arc and \a aArc.
* *
@ -131,6 +132,36 @@ public:
*/ */
bool NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& aPtB, int64_t& aDistSq ) const; bool NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& aPtB, int64_t& aDistSq ) const;
/**
* Compute closest points between this arc and \a aCircle.
*
* @param aPtA point on this arc (output)
* @param aPtB point on the circle (output)
* @param aDistSq squared distance between points (output)
* @return true if the operation was successful
*/
bool NearestPoints( const SHAPE_CIRCLE& aCircle, VECTOR2I& aPtA, VECTOR2I& aPtB, int64_t& aDistSq ) const;
/**
* Compute closest points between this arc and \a aSeg.
*
* @param aPtA point on this arc (output)
* @param aPtB point on the segment (output)
* @param aDistSq squared distance between points (output)
* @return true if the operation was successful
*/
bool NearestPoints( const SEG& aSeg, VECTOR2I& aPtA, VECTOR2I& aPtB, int64_t& aDistSq ) const;
/**
* Compute closest points between this arc and \a aRect.
*
* @param aPtA point on this arc (output)
* @param aPtB point on the rectangle (output)
* @param aDistSq squared distance between points (output)
* @return true if the operation was successful
*/
bool NearestPoints( const SHAPE_RECT& aRect, VECTOR2I& aPtA, VECTOR2I& aPtB, int64_t& aDistSq ) const;
bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr, bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr,
VECTOR2I* aLocation = nullptr ) const override; VECTOR2I* aLocation = nullptr ) const override;
bool Collide( const VECTOR2I& aP, int aClearance = 0, int* aActual = nullptr, bool Collide( const VECTOR2I& aP, int aClearance = 0, int* aActual = nullptr,

View File

@ -506,7 +506,7 @@ public:
if( !m_init ) if( !m_init )
return false; return false;
Vec closest = ClosestPointTo( aCenter ); Vec closest = NearestPoint( aCenter );
double dx = static_cast<double>( aCenter.x ) - closest.x; double dx = static_cast<double>( aCenter.x ) - closest.x;
double dy = static_cast<double>( aCenter.y ) - closest.y; double dy = static_cast<double>( aCenter.y ) - closest.y;
@ -848,7 +848,7 @@ public:
/** /**
* Return the point in this rect that is closest to the provided point * Return the point in this rect that is closest to the provided point
*/ */
constexpr Vec ClosestPointTo( const Vec& aPoint ) const constexpr Vec NearestPoint( const Vec& aPoint ) const
{ {
BOX2<Vec> me( *this ); BOX2<Vec> me( *this );

View File

@ -29,6 +29,7 @@
#include <geometry/shape_arc.h> #include <geometry/shape_arc.h>
#include <geometry/shape_circle.h> #include <geometry/shape_circle.h>
#include <geometry/shape_line_chain.h> #include <geometry/shape_line_chain.h>
#include <geometry/shape_rect.h>
#include <convert_basic_shapes_to_polygon.h> #include <convert_basic_shapes_to_polygon.h>
#include <trigo.h> #include <trigo.h>
@ -446,6 +447,185 @@ VECTOR2I SHAPE_ARC::NearestPoint( const VECTOR2I& aP ) const
} }
bool SHAPE_ARC::NearestPoints( const SHAPE_CIRCLE& aCircle, VECTOR2I& aPtA, VECTOR2I& aPtB,
int64_t& aDistSq ) const
{
if( GetCenter() == aCircle.GetCenter() && GetRadius() == aCircle.GetRadius() )
{
aPtA = aPtB = GetP0();
aDistSq = 0;
return true;
}
aDistSq = std::numeric_limits<int64_t>::max();
CIRCLE circle1( GetCenter(), GetRadius() );
CIRCLE circle2( aCircle.GetCircle() );
std::vector<VECTOR2I> intersections = circle1.Intersect( circle2 );
for( const VECTOR2I& pt : intersections )
{
if( sliceContainsPoint( pt ) )
{
aPtA = aPtB = pt;
aDistSq = 0;
return true;
}
}
std::vector<VECTOR2I> pts = { m_start, m_end, circle1.NearestPoint( GetCenter() ) };
for( const VECTOR2I& pt : pts )
{
if( sliceContainsPoint( pt ) )
{
VECTOR2I nearestPt2 = circle2.NearestPoint( pt );
int64_t distSq = pt.SquaredDistance( nearestPt2 );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = pt;
aPtB = nearestPt2;
}
}
}
return true;
}
bool SHAPE_ARC::NearestPoints( const SEG& aSeg, VECTOR2I& aPtA, VECTOR2I& aPtB,
int64_t& aDistSq ) const
{
aDistSq = std::numeric_limits<int64_t>::max();
CIRCLE circle( GetCenter(), GetRadius() );
// First check for intersections on the circle
std::vector<VECTOR2I> intersections = circle.Intersect( aSeg );
for( const VECTOR2I& pt : intersections )
{
if( sliceContainsPoint( pt ) )
{
aPtA = aPtB = pt;
aDistSq = 0;
return true;
}
}
// Check the endpoints of the segment against the nearest point on the arc
for( const VECTOR2I& pt : { aSeg.A, aSeg.B } )
{
if( sliceContainsPoint( pt ) )
{
VECTOR2I nearestPt = circle.NearestPoint( pt );
int64_t distSq = pt.SquaredDistance( nearestPt );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = nearestPt;
aPtB = pt;
}
}
}
// Check the endpoints of the arc against the nearest point on the segment
for( const VECTOR2I& pt : { m_start, m_end } )
{
VECTOR2I nearestPt = aSeg.NearestPoint( pt );
int64_t distSq = pt.SquaredDistance( nearestPt );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = pt;
aPtB = nearestPt;
}
}
// Check the closest points on the segment to the circle
VECTOR2I segNearestPt = aSeg.NearestPoint( GetCenter() );
if( sliceContainsPoint( segNearestPt ) )
{
VECTOR2I circleNearestPt = circle.NearestPoint( segNearestPt );
int64_t distSq = segNearestPt.SquaredDistance( circleNearestPt );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = segNearestPt;
aPtB = circleNearestPt;
}
}
return true;
}
bool SHAPE_ARC::NearestPoints( const SHAPE_RECT& aRect, VECTOR2I& aPtA, VECTOR2I& aPtB,
int64_t& aDistSq ) const
{
BOX2I bbox = aRect.BBox();
CIRCLE circle( GetCenter(), GetRadius() );
aDistSq = std::numeric_limits<int64_t>::max();
// First check for intersections
SHAPE_LINE_CHAIN lineChain( aRect.Outline() );
for( int i = 0; i < 4; ++i )
{
SEG seg( lineChain.CPoint( i ), lineChain.CPoint( i + 1 ) );
std::vector<VECTOR2I> intersections = circle.Intersect( seg );
for( const VECTOR2I& pt : intersections )
{
if( sliceContainsPoint( pt ) )
{
aPtA = aPtB = pt;
aDistSq = 0;
return true;
}
}
}
// Check the endpoints of the arc against the nearest point on the rectangle
for( const VECTOR2I& pt : { m_start, m_end } )
{
VECTOR2I nearestPt = bbox.NearestPoint( pt );
int64_t distSq = pt.SquaredDistance( nearestPt );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = pt;
aPtB = nearestPt;
}
}
// Check the closest points on the rectangle to the circle
VECTOR2I rectNearestPt = bbox.NearestPoint( GetCenter() );
if( sliceContainsPoint( rectNearestPt ) )
{
VECTOR2I circleNearestPt = circle.NearestPoint( rectNearestPt );
int64_t distSq = rectNearestPt.SquaredDistance( circleNearestPt );
if( distSq < aDistSq )
{
aDistSq = distSq;
aPtA = rectNearestPt;
aPtB = circleNearestPt;
}
}
return true;
}
bool SHAPE_ARC::NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& aPtB, bool SHAPE_ARC::NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& aPtB,
int64_t& aDistSq ) const int64_t& aDistSq ) const
{ {

View File

@ -548,14 +548,30 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_RECT& aB, int aClea
static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_CIRCLE& aB, int aClearance, static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_CIRCLE& aB, int aClearance,
int* aActual, VECTOR2I* aLocation, VECTOR2I* aMTV ) int* aActual, VECTOR2I* aLocation, VECTOR2I* aMTV )
{ {
const SHAPE_LINE_CHAIN lc( aA ); VECTOR2I ptA, ptB;
int64_t dist_sq = std::numeric_limits<int64_t>::max();
aA.NearestPoints( aB, ptA, ptB, dist_sq );
int half_width = ( aA.GetWidth() + 1 ) / 2;
int min_dist = aClearance + half_width;
bool rv = Collide( aB, lc, aClearance + aA.GetWidth() / 2, aActual, aLocation, aMTV ); if( dist_sq < SEG::Square( min_dist ) )
{
if( aLocation )
*aLocation = ( ptA + ptB ) / 2;
if( rv && aActual ) if( aActual )
*aActual = std::max( 0, *aActual - aA.GetWidth() / 2 ); *aActual = std::max( 0, KiROUND( std::sqrt( dist_sq ) - half_width ) );
return rv; if( aMTV )
{
const VECTOR2I delta = ptB - ptA;
*aMTV = delta.Resize( min_dist - std::sqrt( dist_sq ) + 3 );
}
return true;
}
return false;
} }

View File

@ -831,7 +831,7 @@ bool LINE_PLACER::rhMarkObstacles( const VECTOR2I& aP, LINE& aNewHead, LINE& aNe
DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode(); DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode();
if( cornerMode == DIRECTION_45::MITERED_90 || cornerMode == DIRECTION_45::ROUNDED_90 ) if( cornerMode == DIRECTION_45::MITERED_90 || cornerMode == DIRECTION_45::ROUNDED_90 )
nearest = hull.BBox().ClosestPointTo( aP ); nearest = hull.BBox().NearestPoint( aP );
else else
nearest = hull.NearestPoint( aP ); nearest = hull.NearestPoint( aP );

View File

@ -0,0 +1,248 @@
(kicad_pcb
(version 20240108)
(generator "pcbnew")
(generator_version "8.0")
(general
(thickness 1.6)
(legacy_teardrops no)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(32 "B.Adhes" user "B.Adhesive")
(33 "F.Adhes" user "F.Adhesive")
(34 "B.Paste" user)
(35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen")
(37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user)
(39 "F.Mask" user)
(40 "Dwgs.User" user "User.Drawings")
(41 "Cmts.User" user "User.Comments")
(42 "Eco1.User" user "User.Eco1")
(43 "Eco2.User" user "User.Eco2")
(44 "Edge.Cuts" user)
(45 "Margin" user)
(46 "B.CrtYd" user "B.Courtyard")
(47 "F.CrtYd" user "F.Courtyard")
(48 "B.Fab" user)
(49 "F.Fab" user)
(50 "User.1" user)
(51 "User.2" user)
(52 "User.3" user)
(53 "User.4" user)
(54 "User.5" user)
(55 "User.6" user)
(56 "User.7" user)
(57 "User.8" user)
(58 "User.9" user)
)
(setup
(pad_to_mask_clearance 0)
(allow_soldermask_bridges_in_footprints no)
(pcbplotparams
(layerselection 0x00010fc_ffffffff)
(plot_on_all_layers_selection 0x0000000_00000000)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(usegerberadvancedattributes yes)
(creategerberjobfile yes)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 4)
(plotframeref no)
(viasonmask no)
(mode 1)
(useauxorigin no)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(pdf_front_fp_property_popups yes)
(pdf_back_fp_property_popups yes)
(dxfpolygonmode yes)
(dxfimperialunits yes)
(dxfusepcbnewfont yes)
(psnegative no)
(psa4output no)
(plotreference yes)
(plotvalue yes)
(plotfptext yes)
(plotinvisibletext no)
(sketchpadsonfab no)
(subtractmaskfromsilk no)
(outputformat 1)
(mirror no)
(drillshape 1)
(scaleselection 1)
(outputdirectory "")
)
)
(net 0 "")
(footprint "MountingHole:MountingHole_3.2mm_M3_ISO14580_Pad"
(layer "F.Cu")
(uuid "7726c890-db15-4ad7-814e-dd8447b43ed2")
(at 70.3 153.4)
(descr "Mounting Hole 3.2mm, M3, ISO14580")
(tags "mounting hole 3.2mm m3 iso14580")
(property "Reference" "H205"
(at 0 -3.75 0)
(layer "F.SilkS")
(hide yes)
(uuid "69cccef0-a8a2-48a2-be02-3b276d7072c3")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "M3"
(at 0 3.75 0)
(layer "F.Fab")
(hide yes)
(uuid "1b7e40fa-418f-45d5-b0d9-c3f086a93cf6")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Footprint" "MountingHole:MountingHole_3.2mm_M3_ISO14580_Pad"
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "cda40eb5-55d9-4984-922a-3cd45a7fcb03")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "9612decc-23e6-4abe-a20f-28237de64d36")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" "Mounting Hole with connection"
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "33191b5e-4a3b-4d2f-bdd1-90692f6686a8")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr exclude_from_pos_files exclude_from_bom)
(fp_circle
(center 0 0)
(end 2.75 0)
(stroke
(width 0.15)
(type solid)
)
(fill none)
(layer "Cmts.User")
(uuid "49f8db1f-3f32-4c10-83f5-25b68933e968")
)
(fp_circle
(center 0 0)
(end 3 0)
(stroke
(width 0.05)
(type solid)
)
(fill none)
(layer "F.CrtYd")
(uuid "618b7e68-8d12-46da-9dff-96876e14c0ad")
)
(fp_text user "${REFERENCE}"
(at 0 0 0)
(layer "F.Fab")
(uuid "29fb7ad8-c6ed-480b-ade2-449708344f36")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole circle
(at 0 0)
(size 5 5.5)
(drill 3.2)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(pinfunction "1")
(pintype "input+no_connect")
(uuid "d7f69fe4-85ef-4985-a12a-043b32cbe18d")
)
)
(gr_line
(start 67.3 153.4)
(end 67.3 140.7)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "97b5451b-be12-48cb-97bb-046b12351275")
)
(gr_line
(start 67.3 140.7)
(end 80.1 140.7)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "a45d033f-eded-4908-8b8b-a4856e094f4d")
)
(gr_arc
(start 70.3 156.4)
(mid 68.178686 155.521314)
(end 67.3 153.4)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "aa375595-1206-4739-8744-4e01967a5e45")
)
(gr_line
(start 80.1 156.4)
(end 70.3 156.4)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "d1e9169a-9d96-4c1d-9c99-bceeb7cce997")
)
(gr_line
(start 80.1 140.7)
(end 80.1 156.4)
(stroke
(width 0.1)
(type default)
)
(layer "Edge.Cuts")
(uuid "f6a2d3af-1670-4475-9393-9f14f5ab578f")
)
)

View File

@ -119,31 +119,31 @@ BOOST_AUTO_TEST_CASE( test_closest_point_to, *boost::unit_test::tolerance( 0.000
// check all quadrants // check all quadrants
// top left // top left
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 0 ) ) == VECTOR2D( 1, 2 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 0 ) ) == VECTOR2D( 1, 2 ) );
// top // top
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 2, 0 ) ) == VECTOR2D( 2, 2 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 2, 0 ) ) == VECTOR2D( 2, 2 ) );
// top right // top right
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 0 ) ) == VECTOR2D( 4, 2 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 0 ) ) == VECTOR2D( 4, 2 ) );
// right // right
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 5 ) ) == VECTOR2D( 4, 5 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 5 ) ) == VECTOR2D( 4, 5 ) );
// bottom right // bottom right
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 7 ) ) == VECTOR2D( 4, 6 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 7 ) ) == VECTOR2D( 4, 6 ) );
// bottom // bottom
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 3, 7 ) ) == VECTOR2D( 3, 6 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 3, 7 ) ) == VECTOR2D( 3, 6 ) );
// bottom left // bottom left
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 7 ) ) == VECTOR2D( 1, 6 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 7 ) ) == VECTOR2D( 1, 6 ) );
// left // left
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 3 ) ) == VECTOR2D( 1, 3 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 3 ) ) == VECTOR2D( 1, 3 ) );
// inside // inside
BOOST_TEST( box.ClosestPointTo( VECTOR2D( 2, 4 ) ) == VECTOR2D( 2, 4 ) ); BOOST_TEST( box.NearestPoint( VECTOR2D( 2, 4 ) ) == VECTOR2D( 2, 4 ) );
} }
BOOST_AUTO_TEST_CASE( test_farthest_point_to, *boost::unit_test::tolerance( 0.000001 ) ) BOOST_AUTO_TEST_CASE( test_farthest_point_to, *boost::unit_test::tolerance( 0.000001 ) )

View File

@ -66,6 +66,7 @@ BOOST_FIXTURE_TEST_CASE( DRCFalsePositiveRegressions, DRC_REGRESSION_TEST_FIXTUR
"issue15280", // Very wide spokes mis-counted as being single spoke "issue15280", // Very wide spokes mis-counted as being single spoke
"issue14008", // Net-tie clearance error "issue14008", // Net-tie clearance error
"issue17967/issue17967", // Arc dp coupling "issue17967/issue17967", // Arc dp coupling
"issue18203", // DRC error due to colliding arc and circle
"unconnected-netnames/unconnected-netnames", // Raised false schematic partity error "unconnected-netnames/unconnected-netnames", // Raised false schematic partity error
}; };