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;
/**
* @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
* @param aAccuracy maximum divergence from true arc given in internal units
@ -171,10 +180,14 @@ public:
* This is to allow common geometry collision functions
* Other programs should call this using explicit accuracy values
* 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
*/
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:
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,
wxPoint aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc )
{
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;
SHAPE_POLY_SET polyshape;
std::vector<VECTOR2I> outside_pts;
/// We start by making rounded ends on the arc
TransformCircleToPolygon( polyshape,
wxPoint( arcSpine.GetPoint( 0 ).x, arcSpine.GetPoint( 0 ).y ), radial_offset, aError,
aErrorLoc );
TransformCircleToPolygon( polyshape, aStart, radial_offset, aError, aErrorLoc );
TransformCircleToPolygon( polyshape, aEnd, radial_offset, aError, aErrorLoc );
TransformCircleToPolygon( polyshape,
wxPoint( arcSpine.GetPoint( -1 ).x, arcSpine.GetPoint( -1 ).y ), radial_offset, aError,
aErrorLoc );
// The circle polygon is built with a even number of segments, so the
// horizontal diameter has 2 corners on the biggest diameter
// 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
// 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 ) );
if( aErrorLoc == ERROR_OUTSIDE )
radial_offset += aError;
radial_offset += delta + defaultErr/2;
else
radial_offset -= aError/2;
radial_offset -= defaultErr/2;
if( radial_offset < 0 )
radial_offset = 0;
@ -421,7 +518,6 @@ void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, wxPoint aStart, wxPoi
polyshape.NewOutline();
VECTOR2I center = arc.GetCenter();
int radius = ( arc.GetP0() - center ).EuclideanNorm();
int last_index = arcSpine.GetPointCount() -1;
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;
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 );
outside_pts.emplace_back( offset.Resize( curr_rd + radial_offset ) + center );
}
for( auto it = outside_pts.rbegin(); it != outside_pts.rend(); ++it )
polyshape.Append( *it );
#endif
// Can be removed, but usefull to display the outline:
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.
*
* 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>
*
* 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;
double r = GetRadius();
double sa = GetStartAngle();
auto c = GetCenter();
double ca = GetCentralAngle();
double r = GetRadius();
double sa = GetStartAngle();
VECTOR2I c = GetCenter();
double ca = GetCentralAngle();
int n;
// 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
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;
effectiveAccuracy = external_radius;
}
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
// 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).
r += aAccuracy / 2;
r += effectiveAccuracy / 2;
n = n * 2;
rv.Append( m_start );
@ -456,6 +470,9 @@ const SHAPE_LINE_CHAIN SHAPE_ARC::ConvertToPolyline( double aAccuracy ) const
rv.Append( m_end );
if( aEffectiveAccuracy )
*aEffectiveAccuracy = effectiveAccuracy;
return rv;
}

View File

@ -768,12 +768,16 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLa
}
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
// 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
// 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 )
gap += Millimeter2iu( 0.004 );
gap += SHAPE_ARC::DefaultAccuracyForPCB();
aTrack->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap,
m_maxError, ERROR_OUTSIDE );