Fix some issues related to SHAPE_ARC:

- Some are related to shape errors when the allowed error to approximate circle
by segment is large and arc radius small.
- fix the actual error used in ConvertToPolyline().
- Use SHAPE_ARC::DefaultAccuracyForPCB() instead of a fixed value as extra margin
in zones. It should not change something, because it is also a fixed value
(5 micrometers), but it is not a magic number.
-TransformArcToPolygon() fix some issues and add a new algo, based on the arc actual
outline shape (initial algo is still available in code, just in case).
This commit is contained in:
jean-pierre charras 2021-06-30 13:33:49 +02:00
parent 0d8f0d361e
commit 17737af130
4 changed files with 154 additions and 27 deletions

View File

@ -164,6 +164,15 @@ public:
*/ */
double GetLength() const; double GetLength() const;
/**
* @return a default accuray value for ConvertToPolyline() to build the polyline.
* ** Note that the default is ARC_HIGH_DEF in PCBNew units
* This is to allow common geometry collision functions
* Other programs should call this using explicit accuracy values
* TODO: unify KiCad internal units
*/
static double DefaultAccuracyForPCB(){ return 0.005 * PCB_IU_PER_MM; }
/** /**
* Constructs a SHAPE_LINE_CHAIN of segments from a given arc * Constructs a SHAPE_LINE_CHAIN of segments from a given arc
* @param aAccuracy maximum divergence from true arc given in internal units * @param aAccuracy maximum divergence from true arc given in internal units
@ -171,10 +180,14 @@ public:
* This is to allow common geometry collision functions * This is to allow common geometry collision functions
* Other programs should call this using explicit accuracy values * Other programs should call this using explicit accuracy values
* TODO: unify KiCad internal units * TODO: unify KiCad internal units
* @param aEffectiveAccuracy is the actual divergence from true arc given
* the approximation error is between -aEffectiveAccuracy/2 and +aEffectiveAccuracy/2
* in internal units
* *
* @return a SHAPE_LINE_CHAIN * @return a SHAPE_LINE_CHAIN
*/ */
const SHAPE_LINE_CHAIN ConvertToPolyline( double aAccuracy = 0.005 * PCB_IU_PER_MM ) const; const SHAPE_LINE_CHAIN ConvertToPolyline( double aAccuracy = DefaultAccuracyForPCB(),
double* aEffectiveAccuracy = nullptr ) const;
private: private:
bool ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) const bool ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) const

View File

@ -377,23 +377,120 @@ void TransformRoundChamferedRectToPolygon( SHAPE_POLY_SET& aCornerBuffer, const
} }
static int convertArcToPolyline( SHAPE_LINE_CHAIN& aPolyline, VECTOR2I aCenter, int aRadius,
double aStartAngle, double aArcAngle, double aAccuracy,
ERROR_LOC aErrorLoc )
{
double endAngle = aStartAngle + aArcAngle;
int n = 2;
if( aRadius >= aAccuracy )
n = GetArcToSegmentCount( aRadius, aAccuracy, aArcAngle )+1; // n >= 3
if( aErrorLoc == ERROR_OUTSIDE )
{
int seg360 = std::abs( KiROUND( n * 360.0 / aArcAngle ) );
int actual_delta_radius = CircleToEndSegmentDeltaRadius( aRadius, seg360 );
aRadius += actual_delta_radius;
}
for( int i = 0; i <= n ; i++ )
{
double rot = aStartAngle;
rot += ( aArcAngle * i ) / n;
double x = aCenter.x + aRadius * cos( rot * M_PI / 180.0 );
double y = aCenter.y + aRadius * sin( rot * M_PI / 180.0 );
aPolyline.Append( KiROUND( x ), KiROUND( y ) );
}
return n;
}
void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aMid, void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoint aMid,
wxPoint aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc ) wxPoint aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc )
{ {
SHAPE_ARC arc( aStart, aMid, aEnd, aWidth ); SHAPE_ARC arc( aStart, aMid, aEnd, aWidth );
SHAPE_LINE_CHAIN arcSpine = arc.ConvertToPolyline( aError ); // Currentlye have currently 2 algos:
// the first approximates the thick arc from its outlines
// the second approximates the thick arc from segments given by SHAPE_ARC
// using SHAPE_ARC::ConvertToPolyline
// The actual approximation errors are similar but not exactly the same.
//
// For now, both algorithms are kept, the second is the initial algo used in Kicad.
#if 1
// This appproximation convert the 2 ends to polygons, arc outer to polyline
// and arc inner to polyline and merge shapes.
int radial_offset = ( aWidth + 1 ) / 2; int radial_offset = ( aWidth + 1 ) / 2;
SHAPE_POLY_SET polyshape; SHAPE_POLY_SET polyshape;
std::vector<VECTOR2I> outside_pts; std::vector<VECTOR2I> outside_pts;
/// We start by making rounded ends on the arc /// We start by making rounded ends on the arc
TransformCircleToPolygon( polyshape, TransformCircleToPolygon( polyshape, aStart, radial_offset, aError, aErrorLoc );
wxPoint( arcSpine.GetPoint( 0 ).x, arcSpine.GetPoint( 0 ).y ), radial_offset, aError, TransformCircleToPolygon( polyshape, aEnd, radial_offset, aError, aErrorLoc );
aErrorLoc );
TransformCircleToPolygon( polyshape, // The circle polygon is built with a even number of segments, so the
wxPoint( arcSpine.GetPoint( -1 ).x, arcSpine.GetPoint( -1 ).y ), radial_offset, aError, // horizontal diameter has 2 corners on the biggest diameter
aErrorLoc ); // Rotate these 2 corners to match the start and ens points of inner and outer
// end points of the arc appoximation outlines, build below.
// The final shape is much better.
double arc_angle_start_deg = arc.GetStartAngle();
double arc_angle = arc.GetCentralAngle();
double arc_angle_end_deg = arc_angle_start_deg + arc_angle;
if( arc_angle_start_deg != 0 && arc_angle_start_deg != 180.0 )
polyshape.Outline(0).Rotate( arc_angle_start_deg * M_PI/180.0, aStart );
if( arc_angle_end_deg != 0 && arc_angle_end_deg != 180.0 )
polyshape.Outline(1).Rotate( arc_angle_end_deg * M_PI/180.0, aEnd );
VECTOR2I center = arc.GetCenter();
int radius = arc.GetRadius();
int arc_outer_radius = radius + radial_offset;
int arc_inner_radius = radius - radial_offset;
ERROR_LOC errorLocInner = ERROR_OUTSIDE;
ERROR_LOC errorLocOuter = ERROR_INSIDE;
if( aErrorLoc == ERROR_OUTSIDE )
{
errorLocInner = ERROR_INSIDE;
errorLocOuter = ERROR_OUTSIDE;
}
polyshape.NewOutline();
convertArcToPolyline( polyshape.Outline(2), center, arc_outer_radius,
arc_angle_start_deg, arc_angle, aError, errorLocOuter );
if( arc_inner_radius > 0 )
convertArcToPolyline( polyshape.Outline(2), center, arc_inner_radius,
arc_angle_end_deg, -arc_angle, aError, errorLocInner );
else
polyshape.Append( center );
#else
// This appproximation use SHAPE_ARC to convert the 2 ends to polygons,
// approximate arc to polyline, convert the polyline corners to outer and inner
// corners of outer and inner utliners and merge shapes.
double defaultErr;
SHAPE_LINE_CHAIN arcSpine = arc.ConvertToPolyline( SHAPE_ARC::DefaultAccuracyForPCB(),
&defaultErr);
int radius = arc.GetRadius();
int radial_offset = ( aWidth + 1 ) / 2;
SHAPE_POLY_SET polyshape;
std::vector<VECTOR2I> outside_pts;
// delta is the effective error approximation to build a polyline from an arc
int segCnt360 = arcSpine.GetSegmentCount()*360.0/arc.GetCentralAngle();;
int delta = CircleToEndSegmentDeltaRadius( radius+radial_offset, std::abs(segCnt360) );
/// We start by making rounded ends on the arc
TransformCircleToPolygon( polyshape, aStart, radial_offset, aError, aErrorLoc );
TransformCircleToPolygon( polyshape, aEnd, radial_offset, aError, aErrorLoc );
// The circle polygon is built with a even number of segments, so the // The circle polygon is built with a even number of segments, so the
// horizontal diameter has 2 corners on the biggest diameter // horizontal diameter has 2 corners on the biggest diameter
@ -411,9 +508,9 @@ void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoi
polyshape.Outline(1).Rotate( arc_angle_end_deg * M_PI/180.0, arcSpine.GetPoint( -1 ) ); polyshape.Outline(1).Rotate( arc_angle_end_deg * M_PI/180.0, arcSpine.GetPoint( -1 ) );
if( aErrorLoc == ERROR_OUTSIDE ) if( aErrorLoc == ERROR_OUTSIDE )
radial_offset += aError; radial_offset += delta + defaultErr/2;
else else
radial_offset -= aError/2; radial_offset -= defaultErr/2;
if( radial_offset < 0 ) if( radial_offset < 0 )
radial_offset = 0; radial_offset = 0;
@ -421,7 +518,6 @@ void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoi
polyshape.NewOutline(); polyshape.NewOutline();
VECTOR2I center = arc.GetCenter(); VECTOR2I center = arc.GetCenter();
int radius = ( arc.GetP0() - center ).EuclideanNorm();
int last_index = arcSpine.GetPointCount() -1; int last_index = arcSpine.GetPointCount() -1;
for( std::size_t ii = 0; ii <= last_index; ++ii ) for( std::size_t ii = 0; ii <= last_index; ++ii )
@ -429,16 +525,13 @@ void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoi
VECTOR2I offset = arcSpine.GetPoint( ii ) - center; VECTOR2I offset = arcSpine.GetPoint( ii ) - center;
int curr_rd = radius; int curr_rd = radius;
// This correction gives a better position of intermediate points of the sides of arc.
if( ii > 0 && ii < last_index )
curr_rd += aError/2;
polyshape.Append( offset.Resize( curr_rd - radial_offset ) + center ); polyshape.Append( offset.Resize( curr_rd - radial_offset ) + center );
outside_pts.emplace_back( offset.Resize( curr_rd + radial_offset ) + center ); outside_pts.emplace_back( offset.Resize( curr_rd + radial_offset ) + center );
} }
for( auto it = outside_pts.rbegin(); it != outside_pts.rend(); ++it ) for( auto it = outside_pts.rbegin(); it != outside_pts.rend(); ++it )
polyshape.Append( *it ); polyshape.Append( *it );
#endif
// Can be removed, but usefull to display the outline: // Can be removed, but usefull to display the outline:
polyshape.Simplify( SHAPE_POLY_SET::PM_FAST ); polyshape.Simplify( SHAPE_POLY_SET::PM_FAST );

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2017 CERN * Copyright (C) 2017 CERN
* Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -414,29 +414,43 @@ double SHAPE_ARC::GetRadius() const
} }
const SHAPE_LINE_CHAIN SHAPE_ARC::ConvertToPolyline( double aAccuracy ) const const SHAPE_LINE_CHAIN SHAPE_ARC::ConvertToPolyline( double aAccuracy,
double* aEffectiveAccuracy ) const
{ {
SHAPE_LINE_CHAIN rv; SHAPE_LINE_CHAIN rv;
double r = GetRadius(); double r = GetRadius();
double sa = GetStartAngle(); double sa = GetStartAngle();
auto c = GetCenter(); VECTOR2I c = GetCenter();
double ca = GetCentralAngle(); double ca = GetCentralAngle();
int n; int n;
// To calculate the arc to segment count, use the external radius instead of the radius. // To calculate the arc to segment count, use the external radius instead of the radius.
// for a arc with small radius and large width, the difference can be significant // for a arc with small radius and large width, the difference can be significant
double external_radius = r+(m_width/2); double external_radius = r+(m_width/2);
double effectiveAccuracy;
if( external_radius < aAccuracy ) if( external_radius < aAccuracy/2 ) // Should be a very rare case
{
// In this case, the arc is approximated by one segment, with a effective error
// between -aAccuracy/2 and +aAccuracy/2, as expected.
n = 0; n = 0;
effectiveAccuracy = external_radius;
}
else else
n = GetArcToSegmentCount( external_radius, aAccuracy, ca ); {
double arc_angle = std::abs( ca );
n = GetArcToSegmentCount( external_radius, aAccuracy, arc_angle );
// Recalculate the effective error of approximation, that can be < aAccuracy
int seg360 = n * 360.0 / arc_angle;
effectiveAccuracy = CircleToEndSegmentDeltaRadius( external_radius, seg360 );
}
// Split the error on either side of the arc. Since we want the start and end points // Split the error on either side of the arc. Since we want the start and end points
// to be exactly on the arc, the first and last segments need to be shorter to stay within // to be exactly on the arc, the first and last segments need to be shorter to stay within
// the error band (since segments normally start 1/2 the error band outside the arc). // the error band (since segments normally start 1/2 the error band outside the arc).
r += aAccuracy / 2; r += effectiveAccuracy / 2;
n = n * 2; n = n * 2;
rv.Append( m_start ); rv.Append( m_start );
@ -456,6 +470,9 @@ const SHAPE_LINE_CHAIN SHAPE_ARC::ConvertToPolyline( double aAccuracy ) const
rv.Append( m_end ); rv.Append( m_end );
if( aEffectiveAccuracy )
*aEffectiveAccuracy = effectiveAccuracy;
return rv; return rv;
} }

View File

@ -768,12 +768,16 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLa
} }
else else
{ {
// Gives more clearance to arcs (the arc to area conv is not perfect) // Gives more clearance to arcs (the arc to area drc test is not perfect)
// extra_margin is not enought here // extra_margin is not enought here
// This is a workaround, that can be removed when (if?) the arcs // This is a workaround, that can be removed when (if?) the DRC arcs
// issues are fixed // issues are fixed
// The root cause is the fact the DRC approximates the arc shape
// by a segmentlist with a error = +- SHAPE_ARC::DefaultAccuracyForPCB()/2
// and the arc to polygons approximation also creates approxiamtions when
// filling the zone
if( aTrack->Type() == PCB_ARC_T ) if( aTrack->Type() == PCB_ARC_T )
gap += Millimeter2iu( 0.004 ); gap += SHAPE_ARC::DefaultAccuracyForPCB();
aTrack->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap, aTrack->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap,
m_maxError, ERROR_OUTSIDE ); m_maxError, ERROR_OUTSIDE );