Large rework of BEZIER_POLY

Add direct handling of quadratic beziers to save compute time and number
of points.  Update cubic interpolation to reduce number of points
generated for a given smoothness

Cache data on open and used cached data to avoid multiple re-calcs

Remove minimum line length and number of segments and replace with
standard max error level.  Allows us to specify the tolerance of bezier
interpolation
This commit is contained in:
Seth Hillbrand 2024-06-17 21:47:06 -07:00
parent 0d2c4c91fd
commit bcf6b620a8
31 changed files with 698 additions and 221 deletions

View File

@ -104,7 +104,7 @@ static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" );
static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo" ); static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo" );
static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" ); static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" );
static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibilityRatio" ); static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibilityRatio" );
static const wxChar MinimumSegmentLength[] = wxT( "MinimumSegmentLength" ); static const wxChar FontErrorSize[] = wxT( "FontErrorSize" );
static const wxChar OcePluginLinearDeflection[] = wxT( "OcePluginLinearDeflection" ); static const wxChar OcePluginLinearDeflection[] = wxT( "OcePluginLinearDeflection" );
static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" ); static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" );
static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" ); static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" );
@ -257,7 +257,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_PcbSelectionVisibilityRatio = 1.0; m_PcbSelectionVisibilityRatio = 1.0;
m_MinimumSegmentLength = 50; m_FontErrorSize = 16;
m_OcePluginLinearDeflection = 0.14; m_OcePluginLinearDeflection = 0.14;
m_OcePluginAngularDeflection = 30; m_OcePluginAngularDeflection = 30;
@ -469,9 +469,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
&m_PcbSelectionVisibilityRatio, &m_PcbSelectionVisibilityRatio,
m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) ); m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) );
configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::MinimumSegmentLength, configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::FontErrorSize,
&m_MinimumSegmentLength, &m_FontErrorSize,
m_MinimumSegmentLength, 10, 1000 ) ); m_FontErrorSize, 0.01, 100 ) );
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::OcePluginLinearDeflection, configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::OcePluginLinearDeflection,
&m_OcePluginLinearDeflection, &m_OcePluginLinearDeflection,

View File

@ -352,6 +352,7 @@ void EDA_SHAPE::scale( double aScale )
scalePt( m_end ); scalePt( m_end );
scalePt( m_bezierC1 ); scalePt( m_bezierC1 );
scalePt( m_bezierC2 ); scalePt( m_bezierC2 );
RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 );
break; break;
default: default:
@ -488,12 +489,7 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y ); m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
} }
// Rebuild the poly points shape RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 );
{
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
BEZIER_POLY converter( ctrlPoints );
converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() );
}
break; break;
default: default:
@ -503,7 +499,7 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
} }
void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen ) void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMaxError )
{ {
// Has meaning only for SHAPE_T::BEZIER // Has meaning only for SHAPE_T::BEZIER
if( m_shape != SHAPE_T::BEZIER ) if( m_shape != SHAPE_T::BEZIER )
@ -513,30 +509,18 @@ void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
} }
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen ); m_bezierPoints = buildBezierToSegmentsPointsList( aMaxError );
// Ensure last point respects aMinSegLen parameter
if( m_bezierPoints.size() > 2 )
{
int idx = m_bezierPoints.size() - 1;
if( VECTOR2I( m_bezierPoints[idx] - m_bezierPoints[idx] - 1 ).EuclideanNorm() < aMinSegLen )
{
m_bezierPoints[idx - 1] = m_bezierPoints[idx];
m_bezierPoints.pop_back();
}
}
} }
const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
{ {
std::vector<VECTOR2I> bezierPoints; std::vector<VECTOR2I> bezierPoints;
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end }; std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
BEZIER_POLY converter( ctrlPoints ); BEZIER_POLY converter( ctrlPoints );
converter.GetPoly( bezierPoints, aMinSegLen ); converter.GetPoly( bezierPoints, aMaxError );
return bezierPoints; return bezierPoints;
} }
@ -930,16 +914,25 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
} }
case SHAPE_T::BEZIER: case SHAPE_T::BEZIER:
const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() ); {
const std::vector<VECTOR2I>* pts = &m_bezierPoints;
std::vector<VECTOR2I> updatedBezierPoints;
for( unsigned int i= 1; i < m_bezierPoints.size(); i++) if( m_bezierPoints.empty() )
{ {
if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) ) BEZIER_POLY converter( m_start, m_bezierC1, m_bezierC2, m_end );
converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
pts = &updatedBezierPoints;
}
for( unsigned int i = 1; i < pts->size(); i++ )
{
if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
return true; return true;
} }
return false; return false;
}
case SHAPE_T::SEGMENT: case SHAPE_T::SEGMENT:
return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist ); return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
@ -1132,12 +1125,20 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
// Account for the width of the line // Account for the width of the line
arect.Inflate( GetWidth() / 2 ); arect.Inflate( GetWidth() / 2 );
unsigned count = m_bezierPoints.size(); const std::vector<VECTOR2I>* pts = &m_bezierPoints;
std::vector<VECTOR2I> updatedBezierPoints;
for( unsigned ii = 1; ii < count; ii++ ) if( m_bezierPoints.empty() )
{ {
VECTOR2I vertex = m_bezierPoints[ii - 1]; BEZIER_POLY converter( m_start, m_bezierC1, m_bezierC2, m_end );
VECTOR2I vertexNext = m_bezierPoints[ii]; converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
pts = &updatedBezierPoints;
}
for( unsigned ii = 1; ii < pts->size(); ii++ )
{
VECTOR2I vertex = ( *pts )[ii - 1];
VECTOR2I vertexNext = ( *pts )[ii];
// Test if the point is within aRect // Test if the point is within aRect
if( arect.Contains( vertex ) ) if( arect.Contains( vertex ) )
@ -1278,7 +1279,7 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh
case SHAPE_T::BEZIER: case SHAPE_T::BEZIER:
{ {
std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width ); std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width / 2 );
VECTOR2I start_pt = bezierPoints[0]; VECTOR2I start_pt = bezierPoints[0];
for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ ) for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
@ -1381,7 +1382,7 @@ void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
SetBezierC2( aPosition ); SetBezierC2( aPosition );
m_editState = 1; m_editState = 1;
RebuildBezierToSegmentsPointsList( GetWidth() ); RebuildBezierToSegmentsPointsList( GetWidth() / 2 );
break; break;
case SHAPE_T::POLY: case SHAPE_T::POLY:
@ -1463,7 +1464,7 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
case 3: SetBezierC2( aPosition ); break; case 3: SetBezierC2( aPosition ); break;
} }
RebuildBezierToSegmentsPointsList( GetWidth() ); RebuildBezierToSegmentsPointsList( GetWidth() / 2 );
} }
break; break;
@ -1792,7 +1793,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance
std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() }; std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
BEZIER_POLY converter( ctrlPts ); BEZIER_POLY converter( ctrlPts );
std::vector<VECTOR2I> poly; std::vector<VECTOR2I> poly;
converter.GetPoly( poly, GetWidth() ); converter.GetPoly( poly, aError );
for( unsigned ii = 1; ii < poly.size(); ii++ ) for( unsigned ii = 1; ii < poly.size(); ii++ )
TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc ); TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );

View File

@ -111,7 +111,8 @@ int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint,
bezier.push_back( toVector2D( aEndPoint ) ); bezier.push_back( toVector2D( aEndPoint ) );
std::vector<VECTOR2D> result; std::vector<VECTOR2D> result;
decomposer->approximateBezierCurve( result, bezier ); BEZIER_POLY converter( bezier );
converter.GetPoly( result, ADVANCED_CFG::GetCfg().m_FontErrorSize );
for( const VECTOR2D& p : result ) for( const VECTOR2D& p : result )
decomposer->addContourPoint( p ); decomposer->addContourPoint( p );
@ -147,63 +148,6 @@ bool OUTLINE_DECOMPOSER::OutlineToSegments( std::vector<CONTOUR>* aContours )
} }
// use converter in kimath
bool OUTLINE_DECOMPOSER::approximateQuadraticBezierCurve( std::vector<VECTOR2D>& aResult,
const std::vector<VECTOR2D>& aBezier ) const
{
wxASSERT( aBezier.size() == 3 );
// BEZIER_POLY only handles cubic Bezier curves, even though the
// comments say otherwise...
//
// Quadratic to cubic Bezier conversion:
// cpn = Cubic Bezier control points (n = 0..3, 4 in total)
// qpn = Quadratic Bezier control points (n = 0..2, 3 in total)
// cp0 = qp0, cp1 = qp0 + 2/3 * (qp1 - qp0), cp2 = qp2 + 2/3 * (qp1 - qp2), cp3 = qp2
std::vector<VECTOR2D> cubic;
cubic.reserve( 4 );
cubic.push_back( aBezier[0] ); // cp0
cubic.push_back( aBezier[0] + ( ( aBezier[1] - aBezier[0] ) * 2 / 3 ) ); // cp1
cubic.push_back( aBezier[2] + ( ( aBezier[1] - aBezier[2] ) * 2 / 3 ) ); // cp2
cubic.push_back( aBezier[2] ); // cp3
return approximateCubicBezierCurve( aResult, cubic );
}
bool OUTLINE_DECOMPOSER::approximateCubicBezierCurve( std::vector<VECTOR2D>& aResult,
const std::vector<VECTOR2D>& aCubicBezier ) const
{
wxASSERT( aCubicBezier.size() == 4 );
static int minimumSegmentLength = ADVANCED_CFG::GetCfg().m_MinimumSegmentLength;
BEZIER_POLY converter( aCubicBezier );
converter.GetPoly( aResult, minimumSegmentLength );
return true;
}
bool OUTLINE_DECOMPOSER::approximateBezierCurve( std::vector<VECTOR2D>& aResult,
const std::vector<VECTOR2D>& aBezier ) const
{
switch( aBezier.size() )
{
case 4: // cubic
return approximateCubicBezierCurve( aResult, aBezier );
case 3: // quadratic
return approximateQuadraticBezierCurve( aResult, aBezier );
default:
// error, only 3 and 4 are acceptable values
return false;
}
}
int OUTLINE_DECOMPOSER::winding( const std::vector<VECTOR2D>& aContour ) const int OUTLINE_DECOMPOSER::winding( const std::vector<VECTOR2D>& aContour ) const
{ {
// -1 == counterclockwise, 1 == clockwise // -1 == counterclockwise, 1 == clockwise

View File

@ -95,7 +95,7 @@ FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName, int aFaceIndex )
// m_face = handle to face object // m_face = handle to face object
// 0 = char width in 1/64th of points ( 0 = same as char height ) // 0 = char width in 1/64th of points ( 0 = same as char height )
// faceSize() = char height in 1/64th of points // faceSize() = char height in 1/64th of points
// GLYPH_RESOLUTION = horizontal device resolution (288dpi, 4x default) // GLYPH_RESOLUTION = horizontal device resolution (1152dpi, 16x default)
// 0 = vertical device resolution ( 0 = same as horizontal ) // 0 = vertical device resolution ( 0 = same as horizontal )
FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 ); FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 );
} }

View File

@ -266,7 +266,7 @@ EASYEDA_PARSER_BASE::ParseLineChains( const wxString& data, int aArcMinSegLen, b
BEZIER_POLY converter( ctrlPoints ); BEZIER_POLY converter( ctrlPoints );
std::vector<VECTOR2I> bezierPoints; std::vector<VECTOR2I> bezierPoints;
converter.GetPoly( bezierPoints, aArcMinSegLen, 16 ); converter.GetPoly( bezierPoints, aArcMinSegLen );
chain.Append( bezierPoints ); chain.Append( bezierPoints );

View File

@ -244,7 +244,7 @@ void PLOTTER::BezierCurve( const VECTOR2I& aStart, const VECTOR2I& aControl1,
BEZIER_POLY bezier_converter( ctrlPoints ); BEZIER_POLY bezier_converter( ctrlPoints );
std::vector<VECTOR2I> approxPoints; std::vector<VECTOR2I> approxPoints;
bezier_converter.GetPoly( approxPoints, minSegLen ); bezier_converter.GetPoly( approxPoints, aTolerance );
SetCurrentLineWidth( aLineThickness ); SetCurrentLineWidth( aLineThickness );
MoveTo( aStart ); MoveTo( aStart );

View File

@ -217,7 +217,7 @@ void GRAPHICS_IMPORTER_LIB_SYMBOL::AddSpline( const VECTOR2D& aStart,
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) ); spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) ); spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
spline->SetEnd( MapCoordinate( aEnd ) ); spline->SetEnd( MapCoordinate( aEnd ) );
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() ); spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() / 2 );
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if // If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length // null (i.e. very small) length

View File

@ -207,7 +207,7 @@ void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart,
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) ); spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) ); spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
spline->SetEnd( MapCoordinate( aEnd ) ); spline->SetEnd( MapCoordinate( aEnd ) );
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() ); spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() / 2 );
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if // If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length // null (i.e. very small) length

View File

@ -1947,6 +1947,7 @@ void SCH_IO_ALTIUM::ParseBezier( const std::map<wxString, wxString>& aProperties
} }
bezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) ); bezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 );
} }
} }
} }
@ -2350,7 +2351,7 @@ void SCH_IO_ALTIUM::ParseEllipticalArc( const std::map<wxString, wxString>& aPro
schbezier->SetBezierC2( bezier.C2 ); schbezier->SetBezierC2( bezier.C2 );
schbezier->SetEnd( bezier.End ); schbezier->SetEnd( bezier.End );
schbezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) ); schbezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth ); schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 );
currentScreen->Append( schbezier ); currentScreen->Append( schbezier );
} }
@ -2413,7 +2414,7 @@ void SCH_IO_ALTIUM::ParseEllipticalArc( const std::map<wxString, wxString>& aPro
} }
SetLibShapeLine( elem, schbezier, ALTIUM_SCH_RECORD::ELLIPTICAL_ARC ); SetLibShapeLine( elem, schbezier, ALTIUM_SCH_RECORD::ELLIPTICAL_ARC );
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth ); schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 );
} }
} }
} }

View File

@ -1381,7 +1381,7 @@ SCH_SHAPE* SCH_IO_KICAD_LEGACY_LIB_CACHE::loadBezier( LINE_READER& aReader )
pt.y = -schIUScale.MilsToIU( parseInt( aReader, line, &line ) ); pt.y = -schIUScale.MilsToIU( parseInt( aReader, line, &line ) );
bezier->SetEnd( pt ); bezier->SetEnd( pt );
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() ); bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 );
if( *line != 0 ) if( *line != 0 )
bezier->SetFillMode( parseFillMode( aReader, line, &line ) ); bezier->SetFillMode( parseFillMode( aReader, line, &line ) );

View File

@ -1315,7 +1315,7 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSymbolBezier()
} }
} }
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() ); bezier->RebuildBezierToSegmentsPointsList( bezier->GetPenWidth() / 2 );
return bezier.release(); return bezier.release();
} }

View File

@ -739,7 +739,7 @@ void EE_POINT_EDITOR::updateParentItem( bool aSnapToGrid ) const
shape->SetBezierC2( m_editPoints->Point( BEZIER_CTRL_PT2 ).GetPosition() ); shape->SetBezierC2( m_editPoints->Point( BEZIER_CTRL_PT2 ).GetPosition() );
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() ); shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() / 2 );
break; break;
default: default:

View File

@ -509,14 +509,14 @@ public:
double m_PcbSelectionVisibilityRatio; double m_PcbSelectionVisibilityRatio;
/** /**
* Length of the minimum segment for the outline decomposer. This is in IU, so * Deviation between font's bezier curve ideal and the poligonized curve. This
* it is nm in pcbnew and 100nm in eeschema. * is 1/16 of the font's internal units.
* *
* Setting name: "MinimumSegmentLength" * Setting name: "FontErrorSize"
* Valid values: 10 to 1000 * Valid values: 0.01 to 100
* Default value: 50 * Default value: 8
*/ */
int m_MinimumSegmentLength; double m_FontErrorSize;
/** /**
* OCE (STEP/IGES) 3D Plugin Tesselation Linear Deflection * OCE (STEP/IGES) 3D Plugin Tesselation Linear Deflection

View File

@ -306,11 +306,9 @@ public:
* *
* Has meaning only for BEZIER shape. * Has meaning only for BEZIER shape.
* *
* @param aMinSegLen is the min length of segments approximating the bezier. The shape's last * @param aMinSegLen is the max deviation between the polyline and the curve
* segment can be shorter. This parameter avoids having too many very short
* segment in list. Good values are between m_width/2 and m_width.
*/ */
void RebuildBezierToSegmentsPointsList( int aMinSegLen ); void RebuildBezierToSegmentsPointsList( int aMaxError );
/** /**
* Make a set of SHAPE objects representing the EDA_SHAPE. Caller owns the objects. * Make a set of SHAPE objects representing the EDA_SHAPE. Caller owns the objects.
@ -387,7 +385,7 @@ protected:
bool hitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const; bool hitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const;
bool hitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const; bool hitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const;
const std::vector<VECTOR2I> buildBezierToSegmentsPointsList( int aMinSegLen ) const; const std::vector<VECTOR2I> buildBezierToSegmentsPointsList( int aMaxError ) const;
void beginEdit( const VECTOR2I& aStartPoint ); void beginEdit( const VECTOR2I& aStartPoint );
bool continueEdit( const VECTOR2I& aPosition ); bool continueEdit( const VECTOR2I& aPosition );

View File

@ -40,12 +40,6 @@
namespace KIFONT namespace KIFONT
{ {
constexpr int GLYPH_DEFAULT_DPI = 72; ///< FreeType default
// The FreeType default of 72 DPI is not enough for outline decomposition;
// so we'll use something larger than that.
constexpr int GLYPH_RESOLUTION = 288;
constexpr double GLYPH_SIZE_SCALER = GLYPH_DEFAULT_DPI / (double) GLYPH_RESOLUTION;
class GAL_API GLYPH class GAL_API GLYPH
{ {

View File

@ -41,6 +41,13 @@
namespace KIFONT namespace KIFONT
{ {
constexpr int GLYPH_DEFAULT_DPI = 72; ///< FreeType default
// The FreeType default of 72 DPI is not enough for outline decomposition;
// so we'll use something larger than that.
constexpr int GLYPH_RESOLUTION = 1152;
constexpr double GLYPH_SIZE_SCALER = GLYPH_DEFAULT_DPI / (double) GLYPH_RESOLUTION;
struct CONTOUR struct CONTOUR
{ {
std::vector<VECTOR2D> m_Points; std::vector<VECTOR2D> m_Points;
@ -70,13 +77,6 @@ private:
void addContourPoint( const VECTOR2D& p ); void addContourPoint( const VECTOR2D& p );
bool approximateBezierCurve( std::vector<VECTOR2D>& result,
const std::vector<VECTOR2D>& bezier ) const;
bool approximateQuadraticBezierCurve( std::vector<VECTOR2D>& result,
const std::vector<VECTOR2D>& bezier ) const;
bool approximateCubicBezierCurve( std::vector<VECTOR2D>& result,
const std::vector<VECTOR2D>& bezier ) const;
/** /**
* @return 1 if aContour is in clockwise order, -1 if it is in counterclockwise order, * @return 1 if aContour is in clockwise order, -1 if it is in counterclockwise order,
* or 0 if the winding can't be determined. * or 0 if the winding can't be determined.

View File

@ -52,15 +52,32 @@ public:
* Convert a Bezier curve to a polygon. * Convert a Bezier curve to a polygon.
* *
* @param aOutput will be used as an output vector storing polygon points. * @param aOutput will be used as an output vector storing polygon points.
* @param aMinSegLen is the min dist between 2 successive points. * @param aMaxError maximum error in IU between the curve and the polygon.
* It can be used to reduce the number of points.
* (the last point is always generated)
* aMaxSegCount is the max number of segments created
*/ */
void GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen = 0, int aMaxSegCount = 32 ); void GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError = 10 );
void GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen = 0.0, int aMaxSegCount = 32 ); void GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError = 10.0 );
private: private:
void getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError );
void getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError );
int findInflectionPoints( double& aT1, double& aT2 );
int numberOfInflectionPoints();
double thirdControlPointDeviation();
void subdivide( double aT, BEZIER_POLY& aLeft, BEZIER_POLY& aRight );
void recursiveSegmentation( std::vector<VECTOR2D>& aOutput, double aMaxError );
void cubicParabolicApprox( std::vector<VECTOR2D>& aOutput, double aMaxError );
bool isNaN() const;
bool isFlat( double aMaxError ) const;
VECTOR2D eval( double t );
double m_minSegLen; double m_minSegLen;
///< Control points ///< Control points

View File

@ -21,16 +21,69 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
/************************************/ /**********************************************************************************************/
/* routines to handle bezier curves */ /* routines to handle bezier curves */
/************************************/ /* Based on "Fast, Precise Flattening of Cubic Bezier segments offset Curves" by Hain, et. al.*/
/**********************************************************************************************/
// Portions of this code are based draw2d
// Copyright (c) 2010, Laurent Le Goff
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided with the distribution.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Portions of this code are base on BezierKit
// Copyright (c) 2017 Holmes Futrell
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// Portions of this code are based on the spline-research project
// Copyright 2018 Raph Levien
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#include <bezier_curves.h> #include <bezier_curves.h>
#include <geometry/ellipse.h> #include <geometry/ellipse.h>
#include <trigo.h> #include <trigo.h>
#include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2 #include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2
#include <wx/debug.h> // for wxASSERT #include <wx/debug.h> // for wxASSERT
#include <wx/log.h> // for wxLogTrace
#define BEZIER_DBG "bezier"
BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1, BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1,
const VECTOR2I& aCtrl2, const VECTOR2I& aEnd ) const VECTOR2I& aCtrl2, const VECTOR2I& aEnd )
@ -55,62 +108,491 @@ BEZIER_POLY::BEZIER_POLY( const std::vector<VECTOR2I>& aControlPoints )
} }
void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen, int aMaxSegCount ) bool BEZIER_POLY::isNaN() const
{
for( const VECTOR2D& pt : m_ctrlPts )
{
if( std::isnan( pt.x ) || std::isnan( pt.y ) )
return true;
}
return false;
}
bool BEZIER_POLY::isFlat( double aMaxError ) const
{
if( m_ctrlPts.size() == 3 )
{
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
double t = D21.Dot( D31 ) / D31.SquaredEuclideanNorm();
double u = std::min( std::max( t, 0.0 ), 1.0 );
VECTOR2D p = m_ctrlPts[0] + u * D31;
VECTOR2D delta = m_ctrlPts[1] - p;
return 0.5 * delta.EuclideanNorm() <= aMaxError;
}
else if( m_ctrlPts.size() == 4 )
{
VECTOR2D delta = m_ctrlPts[3] - m_ctrlPts[0];
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
double cross1 = delta.Cross( D21 );
double cross2 = delta.Cross( D31 );
double inv_delta_sq = 1.0 / delta.SquaredEuclideanNorm();
double d1 = ( cross1 * cross1 ) * inv_delta_sq;
double d2 = ( cross2 * cross2 ) * inv_delta_sq;
double factor = ( cross1 * cross2 > 0.0 ) ? 3.0 / 4.0 : 4.0 / 9.0;
double f2 = factor * factor;
double tol = aMaxError * aMaxError;
return d1 * f2 <= tol && d2 * f2 <= tol;
}
wxASSERT( false );
return false;
}
void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError )
{ {
aOutput.clear(); aOutput.clear();
std::vector<VECTOR2D> buffer; std::vector<VECTOR2D> buffer;
GetPoly( buffer, double( aMinSegLen ), aMaxSegCount ); GetPoly( buffer, aMaxError );
aOutput.reserve( buffer.size() ); aOutput.reserve( buffer.size() );
for( const VECTOR2D& pt : buffer ) for( const VECTOR2D& pt : buffer )
aOutput.emplace_back( VECTOR2I( (int) pt.x, (int) pt.y ) ); aOutput.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
}
static double approx_int( double x )
{
const double d = 0.6744897501960817;
const double d4 = d * d * d * d;
return x / ( 1.0 - d + std::pow( d4 + x * x * 0.25, 0.25 ) );
}
static constexpr double approx_inv_int( double x )
{
const double p = 0.39538816;
return x * ( 1.0 - p + std::sqrt( p * p + 0.25 * x * x ) );
} }
void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen, int aMaxSegCount ) VECTOR2D BEZIER_POLY::eval( double t )
{ {
wxASSERT( m_ctrlPts.size() == 4 ); double omt = 1.0 - t;
// FIXME Brute force method, use a better (recursive?) algorithm double omt2 = omt * omt;
// with a max error value.
// to optimize the number of segments
double dt = 1.0 / aMaxSegCount;
VECTOR2D::extended_type minSegLen_sq = aMinSegLen * aMinSegLen;
aOutput.clear(); if( m_ctrlPts.size() == 3 )
aOutput.push_back( m_ctrlPts[0] );
// If the Bezier curve is degenerated (straight line), skip intermediate points:
bool degenerated = m_ctrlPts[0] == m_ctrlPts[1] && m_ctrlPts[2] == m_ctrlPts[3];
if( !degenerated )
{ {
for( int ii = 1; ii < aMaxSegCount; ii++ ) return omt2 * m_ctrlPts[0] + 2.0 * omt * t * m_ctrlPts[1] + t * t * m_ctrlPts[2];
}
else if( m_ctrlPts.size() == 4 )
{
double omt3 = omt * omt2;
double t2 = t * t;
double t3 = t * t2;
return omt3 * m_ctrlPts[0] + 3.0 * t * omt2 * m_ctrlPts[1]
+ 3.0 * t2 * omt * m_ctrlPts[2] + t3 * m_ctrlPts[3];
}
else
{
wxASSERT( false );
return VECTOR2D( 0, 0 );
}
}
void BEZIER_POLY::getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
{
double ddx = 2 * m_ctrlPts[1].x - m_ctrlPts[0].x - m_ctrlPts[2].x;
double ddy = 2 * m_ctrlPts[1].y - m_ctrlPts[0].y - m_ctrlPts[2].y;
double u0 =
( m_ctrlPts[1].x - m_ctrlPts[0].x ) * ddx + ( m_ctrlPts[1].y - m_ctrlPts[0].y ) * ddy;
double u2 =
( m_ctrlPts[2].x - m_ctrlPts[1].x ) * ddx + ( m_ctrlPts[2].y - m_ctrlPts[1].y ) * ddy;
double cross =
( m_ctrlPts[2].x - m_ctrlPts[0].x ) * ddy - ( m_ctrlPts[2].y - m_ctrlPts[0].y ) * ddx;
double x0 = u0 / cross;
double x2 = u2 / cross;
double scale = std::abs( cross ) / ( std::hypot( ddx, ddy ) * std::abs( x2 - x0 ) );
double a0 = approx_int( x0 );
double a2 = approx_int( x2 );
int n = std::ceil( 0.5 * std::abs( a2 - a0 ) * std::sqrt( scale / aMaxError ) );
double v0 = approx_inv_int( a0 );
double v2 = approx_inv_int( a2 );
aOutput.emplace_back( m_ctrlPts[0] );
for( int ii = 0; ii < n; ++ii )
{
double u = approx_inv_int( a0 + ( a2 - a0 ) * ii / n );
double t = ( u - v0 ) / ( v2 - v0 );
aOutput.emplace_back( eval( t ) );
}
aOutput.emplace_back( m_ctrlPts[2] );
}
int BEZIER_POLY::numberOfInflectionPoints()
{
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
VECTOR2D D32 = m_ctrlPts[2] - m_ctrlPts[1];
VECTOR2D D43 = m_ctrlPts[3] - m_ctrlPts[2];
double cross1 = D21.Cross( D32 ) * D32.Cross( D43 );
double cross2 = D21.Cross( D32 ) * D21.Cross( D43 );
if( cross1 < 0.0 )
return 1;
else if( cross2 > 0.0 )
return 0;
bool b1 = D21.Dot( D32 ) > 0.0;
bool b2 = D32.Dot( D43 ) > 0.0;
if( b1 ^ b2 )
return 0;
wxLogTrace( BEZIER_DBG, "numberOfInflectionPoints: rare case" );
// These are rare cases where there are potentially 2 or 0 inflection points.
return -1;
}
double BEZIER_POLY::thirdControlPointDeviation()
{
VECTOR2D delta = m_ctrlPts[1] - m_ctrlPts[0];
double len_sq = delta.SquaredEuclideanNorm();
if( len_sq < 1e-6 )
return 0.0;
double len = std::sqrt( len_sq );
double r = ( m_ctrlPts[1].y - m_ctrlPts[0].y ) / len;
double s = ( m_ctrlPts[0].x - m_ctrlPts[1].x ) / len;
double u = ( m_ctrlPts[1].x * m_ctrlPts[0].y - m_ctrlPts[0].x * m_ctrlPts[1].y ) / len;
return std::abs( r * m_ctrlPts[2].x + s * m_ctrlPts[2].y + u );
}
void BEZIER_POLY::subdivide( double aT, BEZIER_POLY& aLeft, BEZIER_POLY& aRight )
{
if( m_ctrlPts.size() == 3 )
{
aLeft.m_ctrlPts[0] = m_ctrlPts[0];
aLeft.m_ctrlPts[1] = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
aLeft.m_ctrlPts[2] = eval( aT );
aRight.m_ctrlPts[2] = m_ctrlPts[2];
aRight.m_ctrlPts[1] = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
aRight.m_ctrlPts[0] = aLeft.m_ctrlPts[2];
}
else if( m_ctrlPts.size() == 4 )
{
VECTOR2D left_ctrl1 = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
VECTOR2D tmp = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
VECTOR2D left_ctrl2 = left_ctrl1 + aT * ( tmp - left_ctrl1 );
VECTOR2D right_ctrl2 = m_ctrlPts[2] + aT * ( m_ctrlPts[3] - m_ctrlPts[2] );
VECTOR2D right_ctrl1 = tmp + aT * ( right_ctrl2 - tmp );
VECTOR2D shared = left_ctrl2 + aT * ( right_ctrl1 - left_ctrl2 );
aLeft.m_ctrlPts[0] = m_ctrlPts[0];
aLeft.m_ctrlPts[1] = left_ctrl1;
aLeft.m_ctrlPts[2] = left_ctrl2;
aLeft.m_ctrlPts[3] = shared;
aRight.m_ctrlPts[3] = m_ctrlPts[3];
aRight.m_ctrlPts[2] = right_ctrl2;
aRight.m_ctrlPts[1] = right_ctrl1;
aRight.m_ctrlPts[0] = shared;
}
else
{
wxASSERT( false );
}
}
void BEZIER_POLY::recursiveSegmentation( std::vector<VECTOR2D>& aOutput, double aThreshhold )
{
wxLogTrace( BEZIER_DBG, "recursiveSegmentation with threshold %f", aThreshhold );
std::vector<BEZIER_POLY> stack;
BEZIER_POLY* bezier = nullptr;
BEZIER_POLY left( std::vector<VECTOR2D>(4) );
BEZIER_POLY right( std::vector<VECTOR2D>(4) );
stack.push_back( *this );
while( !stack.empty() )
{
bezier = &stack.back();
if( bezier->m_ctrlPts[3] == bezier->m_ctrlPts[0] )
{ {
double t = dt * ii; wxLogTrace( BEZIER_DBG, "recursiveSegmentation dropping zero length segment" );
double omt = 1.0 - t; stack.pop_back();
double omt2 = omt * omt; }
double omt3 = omt * omt2; else if( bezier->isFlat( aThreshhold ) )
double t2 = t * t; {
double t3 = t * t2; aOutput.push_back( bezier->m_ctrlPts[3] );
stack.pop_back();
VECTOR2D vertex = omt3 * m_ctrlPts[0] }
+ 3.0 * t * omt2 * m_ctrlPts[1] else
+ 3.0 * t2 * omt * m_ctrlPts[2] {
+ t3 * m_ctrlPts[3]; bezier->subdivide( 0.5, left, right );
*bezier = right;
// a minimal filter on the length of the segment being created: stack.push_back( left );
// The offset from last point:
VECTOR2D delta = vertex - aOutput.back();
VECTOR2D::extended_type dist_sq = delta.SquaredEuclideanNorm();
if( dist_sq > minSegLen_sq )
aOutput.push_back( vertex );
} }
} }
if( aOutput.back() != m_ctrlPts[3] ) wxLogTrace( BEZIER_DBG, "recursiveSegmentation generated %zu points", aOutput.size() );
aOutput.push_back( m_ctrlPts[3] ); }
int BEZIER_POLY::findInflectionPoints( double& aT1, double& aT2 )
{
VECTOR2D A{ ( -m_ctrlPts[0].x + 3 * m_ctrlPts[1].x - 3 * m_ctrlPts[2].x + m_ctrlPts[3].x ),
( -m_ctrlPts[0].y + 3 * m_ctrlPts[1].y - 3 * m_ctrlPts[2].y + m_ctrlPts[3].y ) };
VECTOR2D B{ ( 3 * m_ctrlPts[0].x - 6 * m_ctrlPts[1].x + 3 * m_ctrlPts[2].x ),
( 3 * m_ctrlPts[0].y - 6 * m_ctrlPts[1].y + 3 * m_ctrlPts[2].y ) };
VECTOR2D C{ ( -3 * m_ctrlPts[0].x + 3 * m_ctrlPts[1].x ),
( -3 * m_ctrlPts[0].y + 3 * m_ctrlPts[1].y ) };
double a = 3 * A.Cross( B );
double b = 3 * A.Cross( C );
double c = B.Cross( C );
// Solve the quadratic equation a*t^2 + b*t + c = 0
double r2 = ( b * b - 4 * a * c );
aT1 = 0.0;
aT2 = 0.0;
if( r2 >= 0.0 && a != 0.0 )
{
double r = std::sqrt( r2 );
double t1 = ( ( -b + r ) / ( 2 * a ) );
double t2 = ( ( -b - r ) / ( 2 * a ) );
if( ( t1 > 0.0 && t1 < 1.0 ) && ( t2 > 0.0 && t2 < 1.0 ) )
{
if( t1 > t2 )
{
std::swap( t1, t2 );
}
aT1 = t1;
aT2 = t2;
if( t2 - t1 > 0.00001 )
{
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 2 inflection points at t1 = %f, t2 = %f", t1, t2 );
return 2;
}
else
{
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
return 1;
}
}
else if( t1 > 0.0 && t1 < 1.0 )
{
aT1 = t1;
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
return 1;
}
else if( t2 > 0.0 && t2 < 1.0 )
{
aT1 = t2;
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t2 );
return 1;
}
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
return 0;
}
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
return 0;
}
void BEZIER_POLY::cubicParabolicApprox( std::vector<VECTOR2D>& aOutput, double aMaxError )
{
std::vector<BEZIER_POLY> stack;
stack.push_back( std::vector<VECTOR2D>(4) );
stack.push_back( std::vector<VECTOR2D>(4) );
stack.push_back( std::vector<VECTOR2D>(4) );
stack.push_back( std::vector<VECTOR2D>(4) );
BEZIER_POLY* c = this;
BEZIER_POLY* b1 = &stack[0];
BEZIER_POLY* b2 = &stack[1];
for( ;; )
{
if( c->isNaN() )
{
wxLogDebug( "cubicParabolicApprox: NaN detected" );
break;
}
if( c->isFlat( aMaxError ) )
{
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: General Flatness detected, adding %f %f", c->m_ctrlPts[3].x, c->m_ctrlPts[3].y );
// If the subsegment deviation satisfies the flatness criterion, store the last point and stop
aOutput.push_back( c->m_ctrlPts[3] );
break;
}
// Find the third control point deviation and the t values for subdivision
double d = c->thirdControlPointDeviation();
double t = 2 * std::sqrt( aMaxError / ( 3.0 * d ) ); // Forumla 2 in Hain et al.
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: split point t = %f", t );
if( t > 1.0 )
{
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Invalid t value detected" );
// Case where the t value calculated is invalid, so use recursive subdivision
c->recursiveSegmentation( aOutput, aMaxError );
break;
}
// Valid t value to subdivide at that calculated value
c->subdivide( t, *b1, *b2 );
// First subsegment should have its deviation equal to flatness
if( b1->isFlat( aMaxError ) )
{
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Flatness detected, adding %f %f", b1->m_ctrlPts[3].x, b1->m_ctrlPts[3].y );
aOutput.push_back( b1->m_ctrlPts[3] );
}
else
{
// if not then use segment to handle any mathematical errors
b1->recursiveSegmentation( aOutput, aMaxError );
}
// Repeat the process for the left over subsegment
c = b2;
if( b1 == &stack.front() )
{
b1 = &stack[2];
b2 = &stack[3];
}
else
{
b1 = &stack[0];
b2 = &stack[1];
}
}
}
void BEZIER_POLY::getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
{
aOutput.push_back( m_ctrlPts[0] );
if( numberOfInflectionPoints() == 0 )
{
wxLogTrace( BEZIER_DBG, "getCubicPoly Short circuit to PA" );
// If no inflection points then apply PA on the full Bezier segment.
cubicParabolicApprox( aOutput, aMaxError );
return;
}
// If one or more inflection points then we will have to subdivide the curve
double t1, t2;
int numOfIfP = findInflectionPoints( t1, t2 );
if( numOfIfP == 2 )
{
wxLogTrace( BEZIER_DBG, "getCubicPoly: 2 inflection points" );
// Case when 2 inflection points then divide at the smallest one first
BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
BEZIER_POLY tmp1( std::vector<VECTOR2D>( 4 ) );
BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
BEZIER_POLY sub3( std::vector<VECTOR2D>( 4 ) );
subdivide( t1, sub1, tmp1 );
// Now find the second inflection point in the second curve and subdivide
numOfIfP = tmp1.findInflectionPoints( t1, t2 );
if( numOfIfP == 2 )
tmp1.subdivide( t1, sub2, sub3 );
else if( numOfIfP == 1 )
tmp1.subdivide( t1, sub2, sub3 );
else
{
wxLogTrace( BEZIER_DBG, "getCubicPoly: 2nd inflection point not found" );
return;
}
// Use PA for first subsegment
sub1.cubicParabolicApprox( aOutput, aMaxError );
// Use Segment for the second (middle) subsegment
sub2.recursiveSegmentation( aOutput, aMaxError );
// Use PA for the third curve
sub3.cubicParabolicApprox( aOutput, aMaxError );
}
else if( numOfIfP == 1 )
{
wxLogTrace( BEZIER_DBG, "getCubicPoly: 1 inflection point" );
// Case where there is one inflection point, subdivide once and use PA on both subsegments
BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
subdivide( t1, sub1, sub2 );
sub1.cubicParabolicApprox( aOutput, aMaxError );
sub2.cubicParabolicApprox( aOutput, aMaxError );
}
else
{
wxLogTrace( BEZIER_DBG, "getCubicPoly: Unknown inflection points" );
// Case where there is no inflection, use PA directly
cubicParabolicApprox( aOutput, aMaxError );
}
}
void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
{
if( aMaxError <= 0.0 )
aMaxError = 10.0;
if( m_ctrlPts.size() == 3 )
{
getQuadPoly( aOutput, aMaxError );
}
else if( m_ctrlPts.size() == 4 )
{
getCubicPoly( aOutput, aMaxError );
}
else
{
wxASSERT( false );
}
wxLogTrace( BEZIER_DBG, "GetPoly generated %zu points", aOutput.size() );
} }

View File

@ -395,11 +395,7 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
} }
// Ensure the approximated Bezier shape is built // Ensure the approximated Bezier shape is built
// a good value is between (Bezier curve width / 2) and (Bezier curve width) graphic->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
// ( and at least 0.05 mm to avoid very small segments)
int min_segm_length = std::max( pcbIUScale.mmToIU( 0.05 ),
graphic->GetWidth() );
graphic->RebuildBezierToSegmentsPointsList( min_segm_length );
if( reverse ) if( reverse )
{ {

View File

@ -513,7 +513,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow()
m_item->SetLayer( ToLAYER_ID( layer ) ); m_item->SetLayer( ToLAYER_ID( layer ) );
m_item->RebuildBezierToSegmentsPointsList( m_item->GetWidth() ); m_item->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
if( m_item->IsOnCopperLayer() ) if( m_item->IsOnCopperLayer() )
m_item->SetNetCode( m_netSelector->GetSelectedNetcode() ); m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );

View File

@ -310,7 +310,7 @@ bool DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::Run()
{ {
SHAPE_LINE_CHAIN asPoly; SHAPE_LINE_CHAIN asPoly;
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
for( const VECTOR2I& pt : shape->GetBezierPoints() ) for( const VECTOR2I& pt : shape->GetBezierPoints() )
asPoly.Append( pt ); asPoly.Append( pt );

View File

@ -105,7 +105,7 @@ bool GRAPHICS_CLEANER::isNullShape( PCB_SHAPE* aShape )
return aShape->GetPointCount() == 0; return aShape->GetPointCount() == 0;
case SHAPE_T::BEZIER: case SHAPE_T::BEZIER:
aShape->RebuildBezierToSegmentsPointsList( aShape->GetWidth() ); aShape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
// If the Bezier points list contains 2 points, it is equivalent to a segment // If the Bezier points list contains 2 points, it is equivalent to a segment
if( aShape->GetBezierPoints().size() == 2 ) if( aShape->GetBezierPoints().size() == 2 )

View File

@ -208,7 +208,7 @@ void GRAPHICS_IMPORTER_PCBNEW::AddSpline( const VECTOR2D& aStart, const VECTOR2D
spline->SetBezierC1( MapCoordinate( aBezierControl1 )); spline->SetBezierC1( MapCoordinate( aBezierControl1 ));
spline->SetBezierC2( MapCoordinate( aBezierControl2 )); spline->SetBezierC2( MapCoordinate( aBezierControl2 ));
spline->SetEnd( MapCoordinate( aEnd ) ); spline->SetEnd( MapCoordinate( aEnd ) );
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() ); spline->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if // If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length // null (i.e. very small) length

View File

@ -462,8 +462,6 @@ PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill,
SHAPE_LINE_CHAIN result; SHAPE_LINE_CHAIN result;
VECTOR2D prevPt; VECTOR2D prevPt;
double bezierMinSegLen = polyData.size() < 300 ? aArcAccuracy : aArcAccuracy * 10;
for( int i = 0; i < polyData.size(); i++ ) for( int i = 0; i < polyData.size(); i++ )
{ {
nlohmann::json val = polyData.at( i ); nlohmann::json val = polyData.at( i );
@ -554,7 +552,7 @@ PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill,
BEZIER_POLY converter( ctrlPoints ); BEZIER_POLY converter( ctrlPoints );
std::vector<VECTOR2I> bezierPoints; std::vector<VECTOR2I> bezierPoints;
converter.GetPoly( bezierPoints, bezierMinSegLen, 16 ); converter.GetPoly( bezierPoints, aArcAccuracy );
result.Append( bezierPoints ); result.Append( bezierPoints );

View File

@ -1078,7 +1078,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape
aShape.GetBezierC2(), aShape.GetEnd() }; aShape.GetBezierC2(), aShape.GetEnd() };
BEZIER_POLY converter( ctrlPoints ); BEZIER_POLY converter( ctrlPoints );
std::vector<VECTOR2I> points; std::vector<VECTOR2I> points;
converter.GetPoly( points, aShape.GetStroke().GetWidth() ); converter.GetPoly( points, ARC_HIGH_DEF );
wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" ); wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
addXY( point_node, points[0] ); addXY( point_node, points[0] );

View File

@ -2835,6 +2835,7 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent )
shape->SetBezierC1( parseXY()); shape->SetBezierC1( parseXY());
shape->SetBezierC2( parseXY()); shape->SetBezierC2( parseXY());
shape->SetEnd( parseXY() ); shape->SetEnd( parseXY() );
shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
NeedRIGHT(); NeedRIGHT();
break; break;

View File

@ -1937,22 +1937,27 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
pointCtrl.push_back( aShape->GetEnd() ); pointCtrl.push_back( aShape->GetEnd() );
BEZIER_POLY converter( pointCtrl ); BEZIER_POLY converter( pointCtrl );
converter.GetPoly( output, thickness ); converter.GetPoly( output, m_maxError );
m_gal->DrawSegmentChain( output, thickness ); m_gal->DrawSegmentChain( aShape->GetBezierPoints(), thickness );
} }
else else
{ {
m_gal->SetIsFill( aShape->IsFilled() ); m_gal->SetIsFill( aShape->IsFilled() );
m_gal->SetIsStroke( thickness > 0 ); m_gal->SetIsStroke( thickness > 0 );
m_gal->SetLineWidth( thickness ); m_gal->SetLineWidth( thickness );
// Use thickness as filter value to convert the curve to polyline when the curve if( aShape->GetBezierPoints().size() > 2 )
// is not supported {
m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ), m_gal->DrawPolygon( aShape->GetBezierPoints() );
VECTOR2D( aShape->GetBezierC1() ), }
VECTOR2D( aShape->GetBezierC2() ), else
VECTOR2D( aShape->GetEnd() ), thickness ); {
m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ),
VECTOR2D( aShape->GetBezierC1() ),
VECTOR2D( aShape->GetBezierC2() ),
VECTOR2D( aShape->GetEnd() ), m_maxError );
}
} }
break; break;

View File

@ -267,6 +267,7 @@ bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) ); SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) );
SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) ); SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) );
SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) ); SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) );
RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
} }
return true; return true;
@ -568,7 +569,7 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis )
std::swap( m_start, m_end ); std::swap( m_start, m_end );
if( GetShape() == SHAPE_T::BEZIER ) if( GetShape() == SHAPE_T::BEZIER )
RebuildBezierToSegmentsPointsList( GetWidth() ); RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
break; break;

View File

@ -250,7 +250,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP
std::vector<VECTOR2I> curve_pts; std::vector<VECTOR2I> curve_pts;
curve_pts.reserve( aParams.m_CurveSegCount ); curve_pts.reserve( aParams.m_CurveSegCount );
BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount ); BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, ARC_HIGH_DEF );
for( VECTOR2I& corner: curve_pts ) for( VECTOR2I& corner: curve_pts )
aPoly.push_back( corner ); aPoly.push_back( corner );
@ -258,7 +258,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP
aPoly.push_back( pts[3] ); aPoly.push_back( pts[3] );
curve_pts.clear(); curve_pts.clear();
BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount ); BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, ARC_HIGH_DEF );
for( VECTOR2I& corner: curve_pts ) for( VECTOR2I& corner: curve_pts )
aPoly.push_back( corner ); aPoly.push_back( corner );
@ -319,7 +319,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa
ctrl2.x += bias.x; ctrl2.x += bias.x;
ctrl2.y += bias.y; ctrl2.y += bias.y;
BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount ); BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, ARC_HIGH_DEF );
for( VECTOR2I& corner: curve_pts ) for( VECTOR2I& corner: curve_pts )
aPoly.push_back( corner ); aPoly.push_back( corner );
@ -347,7 +347,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa
ctrl2.x += bias.x; ctrl2.x += bias.x;
ctrl2.y += bias.y; ctrl2.y += bias.y;
BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount ); BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, ARC_HIGH_DEF );
for( VECTOR2I& corner: curve_pts ) for( VECTOR2I& corner: curve_pts )
aPoly.push_back( corner ); aPoly.push_back( corner );

View File

@ -1407,7 +1407,7 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit )
else if( isModified( m_editPoints->Point( BEZIER_END ) ) ) else if( isModified( m_editPoints->Point( BEZIER_END ) ) )
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() ); shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
break; break;
default: // suppress warnings default: // suppress warnings

View File

@ -3,14 +3,17 @@
"3dviewports": [], "3dviewports": [],
"design_settings": { "design_settings": {
"defaults": { "defaults": {
"board_outline_line_width": 0.049999999999999996, "apply_defaults_to_fp_fields": false,
"copper_line_width": 0.19999999999999998, "apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false, "copper_text_italic": false,
"copper_text_size_h": 1.5, "copper_text_size_h": 1.5,
"copper_text_size_v": 1.5, "copper_text_size_v": 1.5,
"copper_text_thickness": 0.3, "copper_text_thickness": 0.3,
"copper_text_upright": true, "copper_text_upright": true,
"courtyard_line_width": 0.049999999999999996, "courtyard_line_width": 0.05,
"dimension_precision": 1, "dimension_precision": 1,
"dimension_units": 2, "dimension_units": 2,
"dimensions": { "dimensions": {
@ -21,13 +24,13 @@
"text_position": 0, "text_position": 0,
"units_format": 1 "units_format": 1
}, },
"fab_line_width": 0.09999999999999999, "fab_line_width": 0.1,
"fab_text_italic": false, "fab_text_italic": false,
"fab_text_size_h": 1.0, "fab_text_size_h": 1.0,
"fab_text_size_v": 1.0, "fab_text_size_v": 1.0,
"fab_text_thickness": 0.15, "fab_text_thickness": 0.15,
"fab_text_upright": true, "fab_text_upright": true,
"other_line_width": 0.09999999999999999, "other_line_width": 0.1,
"other_text_italic": false, "other_text_italic": false,
"other_text_size_h": 1.0, "other_text_size_h": 1.0,
"other_text_size_v": 1.0, "other_text_size_v": 1.0,
@ -46,7 +49,7 @@
"silk_text_upright": true, "silk_text_upright": true,
"zones": { "zones": {
"45_degree_only": false, "45_degree_only": false,
"min_clearance": 0.19999999999999998 "min_clearance": 0.2
} }
}, },
"diff_pair_dimensions": [ "diff_pair_dimensions": [
@ -73,9 +76,12 @@
"duplicate_footprints": "warning", "duplicate_footprints": "warning",
"extra_footprint": "warning", "extra_footprint": "warning",
"footprint": "error", "footprint": "error",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore", "footprint_type_mismatch": "ignore",
"hole_clearance": "error", "hole_clearance": "error",
"hole_near_hole": "error", "hole_near_hole": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error", "invalid_outline": "error",
"isolated_copper": "warning", "isolated_copper": "warning",
"item_on_disabled_layer": "error", "item_on_disabled_layer": "error",
@ -120,17 +126,17 @@
"min_copper_edge_clearance": 0.01, "min_copper_edge_clearance": 0.01,
"min_hole_clearance": 0.0, "min_hole_clearance": 0.0,
"min_hole_to_hole": 0.25, "min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998, "min_microvia_diameter": 0.2,
"min_microvia_drill": 0.09999999999999999, "min_microvia_drill": 0.1,
"min_resolved_spokes": 2, "min_resolved_spokes": 2,
"min_silk_clearance": 0.0, "min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999, "min_text_height": 0.8,
"min_text_thickness": 0.08, "min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3, "min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998, "min_track_width": 0.2,
"min_via_annular_width": 0.049999999999999996, "min_via_annular_width": 0.05,
"min_via_annulus": 0.049999999999999996, "min_via_annulus": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997, "min_via_diameter": 0.4,
"solder_mask_to_copper_clearance": 0.0, "solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true "use_height_for_length_calcs": true
}, },
@ -184,6 +190,32 @@
1.0, 1.0,
2.0 2.0
], ],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [ "via_dimensions": [
{ {
"diameter": 0.85, "diameter": 0.85,
@ -193,6 +225,13 @@
"zones_allow_external_fillets": false, "zones_allow_external_fillets": false,
"zones_use_no_outline": true "zones_use_no_outline": true
}, },
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_presets": [ "layer_presets": [
{ {
"activeLayer": -2, "activeLayer": -2,