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:
parent
0d8f0d361e
commit
17737af130
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
Loading…
Reference in New Issue