Only dogbone inside corners

Fixes https://gitlab.com/kicad/code/kicad/issues/18880
This commit is contained in:
Seth Hillbrand 2025-09-07 13:02:22 -07:00
parent b98621c8c8
commit ac1b44715b
2 changed files with 93 additions and 0 deletions

View File

@ -28,12 +28,16 @@
#include <geometry/oval.h>
#include <geometry/roundrect.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_utils.h>
#include <geometry/vector_utils.h>
#include <math.h>
#include <pad.h>
#include <pcb_track.h>
#include <tools/pcb_tool_utils.h>
#include <confirm.h>
#include <board.h>
#include <wx/log.h>
namespace
{
@ -293,7 +297,17 @@ std::optional<wxString> 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> 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<BOARD*>( 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
{

View File

@ -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
};