diff --git a/pcbnew/tools/item_modification_routine.cpp b/pcbnew/tools/item_modification_routine.cpp index a4e359b366..accfb09a6b 100644 --- a/pcbnew/tools/item_modification_routine.cpp +++ b/pcbnew/tools/item_modification_routine.cpp @@ -28,12 +28,16 @@ #include #include #include +#include #include +#include #include #include #include #include +#include +#include namespace { @@ -293,7 +297,17 @@ std::optional DOGBONE_CORNER_ROUTINE::GetStatusMessage( int aSegmentCo void DOGBONE_CORNER_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) { if( aLineA.GetLength() == 0.0 || aLineB.GetLength() == 0.0 ) + { + wxLogTrace( "DOGBONE", "Skip: zero-length line(s) (A len=%f, B len=%f)", + aLineA.GetLength(), aLineB.GetLength() ); return; + } + + if( !EnsureBoardOutline() ) + { + wxLogTrace( "DOGBONE", "Skip: board outline unavailable" ); + return; + } SEG seg_a( aLineA.GetStart(), aLineA.GetEnd() ); SEG seg_b( aLineB.GetStart(), aLineB.GetEnd() ); @@ -302,27 +316,74 @@ void DOGBONE_CORNER_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLin if( !a_pt || !b_pt ) { + wxLogTrace( "DOGBONE", "Skip: segments do not share endpoint" ); return; } // Cannot handle parallel lines if( seg_a.Angle( seg_b ).IsHorizontal() ) { + wxLogTrace( "DOGBONE", "Skip: parallel segments" ); AddFailure(); return; } + // Determine if this corner points into the board outline: we construct the bisector + // vector (as done in ComputeDogbone) and test a point a small distance in the opposite + // direction (i.e. exterior). If that opposite point is INSIDE the board outline, the + // corner indentation points inward (needs dogbone). If the opposite test point is + // outside, skip. + + const VECTOR2I corner = *a_pt; // shared endpoint + // Build vectors from corner toward other ends + const VECTOR2I vecA = ( seg_a.A == corner ? seg_a.B - corner : seg_a.A - corner ); + const VECTOR2I vecB = ( seg_b.A == corner ? seg_b.B - corner : seg_b.A - corner ); + // Normalize (resize) to common length to form bisector reliably + int maxLen = std::max( vecA.EuclideanNorm(), vecB.EuclideanNorm() ); + if( maxLen == 0 ) + { + wxLogTrace( "DOGBONE", "Skip: degenerate corner (maxLen==0)" ); + return; + } + VECTOR2I vecAn = vecA.Resize( maxLen ); + VECTOR2I vecBn = vecB.Resize( maxLen ); + VECTOR2I bisectorOutward = vecAn + vecBn; // direction inside angle region + + // If vectors are nearly opposite, no meaningful dogbone + if( bisectorOutward.EuclideanNorm() == 0 ) + { + wxLogTrace( "DOGBONE", "Skip: bisector zero (vectors opposite)" ); + return; + } + + // Opposite direction of bisector (points "outside" if angle is convex, or further into + // material if reflex). We'll sample a point a small distance along -bisectorOutward. + VECTOR2I sampleDir = ( -bisectorOutward ).Resize( std::min( 1000, m_params.DogboneRadiusIU ) ); + VECTOR2I samplePoint = corner + sampleDir; // test point opposite bisector + + bool oppositeInside = m_boardOutline.Contains( samplePoint ); + + if( !oppositeInside ) + { + wxLogTrace( "DOGBONE", "Skip: corner not inward (sample outside polygon)" ); + return; + } + std::optional dogbone_result = ComputeDogbone( seg_a, seg_b, m_params.DogboneRadiusIU, m_params.AddSlots ); if( !dogbone_result ) { + wxLogTrace( "DOGBONE", "Skip: ComputeDogbone failed (radius=%d slots=%d)", + m_params.DogboneRadiusIU, (int) m_params.AddSlots ); AddFailure(); return; } if( dogbone_result->m_small_arc_mouth ) { + wxLogTrace( "DOGBONE", "Info: small arc mouth (slots %s)", + m_params.AddSlots ? "enabled" : "disabled" ); // The arc is too small to fit the radius m_haveNarrowMouths = true; } @@ -365,9 +426,36 @@ void DOGBONE_CORNER_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLin ModifyLineOrDeleteIfZeroLength( aLineA, dogbone_result->m_updated_seg_a ); ModifyLineOrDeleteIfZeroLength( aLineB, dogbone_result->m_updated_seg_b ); + wxLogTrace( "DOGBONE", "Success: dogbone added at (%d,%d)", corner.x, corner.y ); AddSuccess(); } +bool DOGBONE_CORNER_ROUTINE::EnsureBoardOutline() const +{ + if( m_boardOutlineCached ) + return !m_boardOutline.IsEmpty(); + + m_boardOutlineCached = true; + + BOARD* board = dynamic_cast( GetBoard() ); + if( !board ) + { + wxLogTrace( "DOGBONE", "EnsureBoardOutline: board cast failed" ); + return false; + } + + // Build outlines; ignore errors and arcs for this classification + if( !board->GetBoardPolygonOutlines( m_boardOutline ) ) + { + wxLogTrace( "DOGBONE", "EnsureBoardOutline: GetBoardPolygonOutlines failed" ); + return false; + } + + bool ok = !m_boardOutline.IsEmpty(); + wxLogTrace( "DOGBONE", "EnsureBoardOutline: outline %s", ok ? "ready" : "empty" ); + return ok; +} + wxString LINE_EXTENSION_ROUTINE::GetCommitDescription() const { diff --git a/pcbnew/tools/item_modification_routine.h b/pcbnew/tools/item_modification_routine.h index ba850e21d9..ce8bd381ec 100644 --- a/pcbnew/tools/item_modification_routine.h +++ b/pcbnew/tools/item_modification_routine.h @@ -327,8 +327,13 @@ public: void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override; private: + // Lazily load board outline polygons (outer outline + any holes) + bool EnsureBoardOutline() const; + PARAMETERS m_params; bool m_haveNarrowMouths; + mutable bool m_boardOutlineCached = false; + mutable SHAPE_POLY_SET m_boardOutline; ///< Cached board outline polygons };