diff --git a/libs/kimath/include/geometry/shape_arc.h b/libs/kimath/include/geometry/shape_arc.h index 99cf81a3f1..c62af2d6b4 100644 --- a/libs/kimath/include/geometry/shape_arc.h +++ b/libs/kimath/include/geometry/shape_arc.h @@ -32,7 +32,9 @@ #include <geometry/eda_angle.h> class CIRCLE; +class SHAPE_CIRCLE; class SHAPE_LINE_CHAIN; +class SHAPE_RECT; class SHAPE_ARC : public SHAPE { @@ -120,7 +122,6 @@ public: VECTOR2I NearestPoint( const VECTOR2I& aP ) const; - /** * 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; + /** + * 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, VECTOR2I* aLocation = nullptr ) const override; bool Collide( const VECTOR2I& aP, int aClearance = 0, int* aActual = nullptr, diff --git a/libs/kimath/include/math/box2.h b/libs/kimath/include/math/box2.h index b5868b4fcc..e443d0cb36 100644 --- a/libs/kimath/include/math/box2.h +++ b/libs/kimath/include/math/box2.h @@ -506,7 +506,7 @@ public: if( !m_init ) return false; - Vec closest = ClosestPointTo( aCenter ); + Vec closest = NearestPoint( aCenter ); double dx = static_cast<double>( aCenter.x ) - closest.x; 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 */ - constexpr Vec ClosestPointTo( const Vec& aPoint ) const + constexpr Vec NearestPoint( const Vec& aPoint ) const { BOX2<Vec> me( *this ); diff --git a/libs/kimath/src/geometry/shape_arc.cpp b/libs/kimath/src/geometry/shape_arc.cpp index 66867e8dec..8c5cadb161 100644 --- a/libs/kimath/src/geometry/shape_arc.cpp +++ b/libs/kimath/src/geometry/shape_arc.cpp @@ -29,6 +29,7 @@ #include <geometry/shape_arc.h> #include <geometry/shape_circle.h> #include <geometry/shape_line_chain.h> +#include <geometry/shape_rect.h> #include <convert_basic_shapes_to_polygon.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, int64_t& aDistSq ) const { diff --git a/libs/kimath/src/geometry/shape_collisions.cpp b/libs/kimath/src/geometry/shape_collisions.cpp index a67c443b6d..883b7ee380 100644 --- a/libs/kimath/src/geometry/shape_collisions.cpp +++ b/libs/kimath/src/geometry/shape_collisions.cpp @@ -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, 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 ) - *aActual = std::max( 0, *aActual - aA.GetWidth() / 2 ); + if( aActual ) + *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; } diff --git a/pcbnew/router/pns_line_placer.cpp b/pcbnew/router/pns_line_placer.cpp index 2202a8ead2..7148bfaaf7 100644 --- a/pcbnew/router/pns_line_placer.cpp +++ b/pcbnew/router/pns_line_placer.cpp @@ -831,7 +831,7 @@ bool LINE_PLACER::rhMarkObstacles( const VECTOR2I& aP, LINE& aNewHead, LINE& aNe DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode(); if( cornerMode == DIRECTION_45::MITERED_90 || cornerMode == DIRECTION_45::ROUNDED_90 ) - nearest = hull.BBox().ClosestPointTo( aP ); + nearest = hull.BBox().NearestPoint( aP ); else nearest = hull.NearestPoint( aP ); diff --git a/qa/data/pcbnew/issue18203.kicad_pcb b/qa/data/pcbnew/issue18203.kicad_pcb new file mode 100644 index 0000000000..b85d31ebb4 --- /dev/null +++ b/qa/data/pcbnew/issue18203.kicad_pcb @@ -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") + ) +) diff --git a/qa/tests/libs/kimath/math/test_box2.cpp b/qa/tests/libs/kimath/math/test_box2.cpp index 072de2a4cb..d2d4cce6aa 100644 --- a/qa/tests/libs/kimath/math/test_box2.cpp +++ b/qa/tests/libs/kimath/math/test_box2.cpp @@ -119,31 +119,31 @@ BOOST_AUTO_TEST_CASE( test_closest_point_to, *boost::unit_test::tolerance( 0.000 // check all quadrants // top left - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 0 ) ) == VECTOR2D( 1, 2 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 0 ) ) == VECTOR2D( 1, 2 ) ); // top - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 2, 0 ) ) == VECTOR2D( 2, 2 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 2, 0 ) ) == VECTOR2D( 2, 2 ) ); // top right - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 0 ) ) == VECTOR2D( 4, 2 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 0 ) ) == VECTOR2D( 4, 2 ) ); // right - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 5 ) ) == VECTOR2D( 4, 5 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 5 ) ) == VECTOR2D( 4, 5 ) ); // bottom right - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 6, 7 ) ) == VECTOR2D( 4, 6 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 6, 7 ) ) == VECTOR2D( 4, 6 ) ); // bottom - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 3, 7 ) ) == VECTOR2D( 3, 6 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 3, 7 ) ) == VECTOR2D( 3, 6 ) ); // bottom left - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 7 ) ) == VECTOR2D( 1, 6 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 7 ) ) == VECTOR2D( 1, 6 ) ); // left - BOOST_TEST( box.ClosestPointTo( VECTOR2D( 0, 3 ) ) == VECTOR2D( 1, 3 ) ); + BOOST_TEST( box.NearestPoint( VECTOR2D( 0, 3 ) ) == VECTOR2D( 1, 3 ) ); // 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 ) ) diff --git a/qa/tests/pcbnew/drc/test_drc_regressions.cpp b/qa/tests/pcbnew/drc/test_drc_regressions.cpp index bc9db3f9e3..8d46d56be8 100644 --- a/qa/tests/pcbnew/drc/test_drc_regressions.cpp +++ b/qa/tests/pcbnew/drc/test_drc_regressions.cpp @@ -66,6 +66,7 @@ BOOST_FIXTURE_TEST_CASE( DRCFalsePositiveRegressions, DRC_REGRESSION_TEST_FIXTUR "issue15280", // Very wide spokes mis-counted as being single spoke "issue14008", // Net-tie clearance error "issue17967/issue17967", // Arc dp coupling + "issue18203", // DRC error due to colliding arc and circle "unconnected-netnames/unconnected-netnames", // Raised false schematic partity error };