7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-11 09:00:13 +00:00

SVG import: import beziers as beziers

This maintains a little bit more editability and avoid importing
thousands of tiny line segments when not required.
This commit is contained in:
John Beard 2025-01-01 00:36:37 +08:00
parent 3bd3b8073e
commit 85552c7a4f
7 changed files with 156 additions and 81 deletions

View File

@ -24,9 +24,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <eda_item.h>
#include "graphics_importer.h"
#include <eda_item.h>
#include <eda_shape.h>
#include "graphics_import_plugin.h"
#include <wx/log.h>
GRAPHICS_IMPORTER::GRAPHICS_IMPORTER()
@ -96,3 +100,48 @@ void GRAPHICS_IMPORTER::NewShape( POLY_FILL_RULE aFillRule )
{
m_shapeFillRules.push_back( aFillRule );
}
void GRAPHICS_IMPORTER::addItem( std::unique_ptr<EDA_ITEM> aItem )
{
m_items.emplace_back( std::move( aItem ) );
}
bool GRAPHICS_IMPORTER::setupSplineOrLine( EDA_SHAPE& aSpline, int aAccuracy )
{
aSpline.SetShape( SHAPE_T::BEZIER );
bool degenerate = false;
SEG s_e{ aSpline.GetStart(), aSpline.GetEnd() };
SEG s_c1{ aSpline.GetStart(), aSpline.GetBezierC1() };
SEG e_c2{ aSpline.GetEnd(), aSpline.GetBezierC2() };
if( s_e.ApproxCollinear( s_c1 ) && s_e.ApproxCollinear( e_c2 ) )
degenerate = true;
if( !degenerate )
{
aSpline.RebuildBezierToSegmentsPointsList( aAccuracy );
if( aSpline.GetBezierPoints().size() <= 2 )
{
degenerate = true;
}
}
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length
if( degenerate )
{
aSpline.SetShape( SHAPE_T::SEGMENT );
// segment smaller than MIN_SEG_LEN_ACCEPTABLE_NM nanometers are skipped.
constexpr int MIN_SEG_LEN_ACCEPTABLE_NM = 20;
if( s_e.Length() < MIN_SEG_LEN_ACCEPTABLE_NM )
return false;
}
return true;
}

View File

@ -40,6 +40,7 @@
#include <vector>
class EDA_ITEM;
class EDA_SHAPE;
/**
* A clone of IMPORTED_STROKE, but with floating-point width.
@ -313,10 +314,14 @@ public:
protected:
///< Add an item to the imported shapes list.
void addItem( std::unique_ptr<EDA_ITEM> aItem )
{
m_items.emplace_back( std::move( aItem ) );
}
void addItem( std::unique_ptr<EDA_ITEM> aItem );
/*
* Configure a shape as a spline or a line segment if it's degenerate.
*
* @return false if the shape is near-zero length and should be ignored.
*/
bool setupSplineOrLine( EDA_SHAPE& aShape, int aAccuracy );
///< factor to convert millimeters to Internal Units
double m_millimeterToIu;

View File

@ -191,9 +191,9 @@ bool SVG_IMPORT_PLUGIN::Import()
{
if( filled && !path->closed )
{
// KiCad doesn't support a single object representing a filled shape that is not closed
// KiCad doesn't support a single object representing a filled shape that is *not* closed
// so create a filled, closed shape for the fill, and an unfilled, open shape for the outline
IMPORTED_STROKE noStroke( 0, LINE_STYLE::SOLID, COLOR4D::UNSPECIFIED );
static IMPORTED_STROKE noStroke( -1, LINE_STYLE::SOLID, COLOR4D::UNSPECIFIED );
DrawPath( path->pts, path->npts, true, noStroke, true, fillColor );
DrawPath( path->pts, path->npts, false, stroke, false, COLOR4D::UNSPECIFIED );
}
@ -201,6 +201,8 @@ bool SVG_IMPORT_PLUGIN::Import()
{
// Either the shape has fill and no stroke, so we implicitly close it (for no difference),
// or it's really closed
// We could choose to import a not-filled, closed outline as splines to keep the
// original editability and control points, but currently we don't.
const bool closed = path->closed || filled;
DrawPath( path->pts, path->npts, closed, stroke, filled, fillColor );
@ -265,42 +267,8 @@ BOX2D SVG_IMPORT_PLUGIN::GetImageBBox() const
}
void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
const IMPORTED_STROKE& aStroke, bool aFilled,
const COLOR4D& aFillColor )
{
std::vector<VECTOR2D> collectedPathPoints;
if( aNumPoints > 0 )
DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
if( aClosedPath && collectedPathPoints.size() > 2 )
DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
else
DrawLineSegments( collectedPathPoints, aStroke );
}
void SVG_IMPORT_PLUGIN::DrawCubicBezierPath( const float* aPoints, int aNumPoints,
std::vector<VECTOR2D>& aGeneratedPoints )
{
const int pointsPerSegment = 4;
const int curveSpecificPointsPerSegment = 3;
const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
const float* currentPoints = aPoints;
int remainingPoints = aNumPoints;
while( remainingPoints >= pointsPerSegment )
{
DrawCubicBezierCurve( currentPoints, aGeneratedPoints );
currentPoints += curveSpecificCoordinatesPerSegment;
remainingPoints -= curveSpecificPointsPerSegment;
}
}
void SVG_IMPORT_PLUGIN::DrawCubicBezierCurve( const float* aPoints,
std::vector<VECTOR2D>& aGeneratedPoints )
static void GatherInterpolatedCubicBezierCurve( const float* aPoints,
std::vector<VECTOR2D>& aGeneratedPoints )
{
auto start = getBezierPoint( aPoints, 0.0f );
auto end = getBezierPoint( aPoints, 1.0f );
@ -314,6 +282,79 @@ void SVG_IMPORT_PLUGIN::DrawCubicBezierCurve( const float* aPoints,
}
static void GatherInterpolatedCubicBezierPath( const float* aPoints, int aNumPoints,
std::vector<VECTOR2D>& aGeneratedPoints )
{
const int pointsPerSegment = 4;
const int curveSpecificPointsPerSegment = 3;
const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
const float* currentPoints = aPoints;
int remainingPoints = aNumPoints;
while( remainingPoints >= pointsPerSegment )
{
GatherInterpolatedCubicBezierCurve( currentPoints, aGeneratedPoints );
currentPoints += curveSpecificCoordinatesPerSegment;
remainingPoints -= curveSpecificPointsPerSegment;
}
}
void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
const IMPORTED_STROKE& aStroke, bool aFilled,
const COLOR4D& aFillColor )
{
bool drewPolygon = false;
if( aClosedPath )
{
// Closed paths are always polygons, which mean they need to be interpolated
std::vector<VECTOR2D> collectedPathPoints;
if( aNumPoints > 0 )
GatherInterpolatedCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
if( collectedPathPoints.size() > 2 )
{
DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
drewPolygon = true;
}
}
if( !drewPolygon )
{
DrawSplinePath( aPoints, aNumPoints, aStroke );
}
}
void SVG_IMPORT_PLUGIN::DrawSplinePath( const float* aCoords, int aNumPoints,
const IMPORTED_STROKE& aStroke )
{
// NanoSVG just gives us the points of the Bezier curves, so we have to
// decide whether to draw lines or splines based on the points we have.
const int pointsPerSegment = 4;
const int curveSpecificPointsPerSegment = 3;
const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
const float* currentCoords = aCoords;
int remainingPoints = aNumPoints;
while( remainingPoints >= pointsPerSegment )
{
VECTOR2D start = getPoint( currentCoords );
VECTOR2D c1 = getPoint( currentCoords + 2 );
VECTOR2D c2 = getPoint( currentCoords + 4 );
VECTOR2D end = getPoint( currentCoords + 6 );
// Add as a spline and the importer will decide whether to draw it as a spline or as lines
m_internalImporter.AddSpline( start, c1, c2, end, aStroke );
currentCoords += curveSpecificCoordinatesPerSegment;
remainingPoints -= curveSpecificPointsPerSegment;
}
}
void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector<VECTOR2D>& aPoints,
const IMPORTED_STROKE& aStroke, bool aFilled,
const COLOR4D& aFillColor )

View File

@ -76,16 +76,16 @@ private:
void DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
const IMPORTED_STROKE& aStroke, bool aFilled, const COLOR4D& aFillColor );
void DrawCubicBezierPath( const float* aPoints, int aNumPoints,
std::vector<VECTOR2D>& aGeneratedPoints );
void DrawCubicBezierCurve( const float* aPoints, std::vector<VECTOR2D>& aGeneratedPoints );
void DrawPolygon( const std::vector<VECTOR2D>& aPoints, const IMPORTED_STROKE& aStroke,
bool aFilled, const COLOR4D& aFillColor );
void DrawLineSegments( const std::vector<VECTOR2D>& aPoints, const IMPORTED_STROKE& aStroke );
/**
* Draw a path made up of cubic Bezier curves, adding them as real bezier curves.
*/
void DrawSplinePath( const float* aPoints, int aNumPoints, const IMPORTED_STROKE& aStroke );
struct NSVGimage* m_parsedImage;
wxString m_messages; // messages generated during svg file parsing.

View File

@ -196,10 +196,9 @@ void GRAPHICS_IMPORTER_SCH::AddText( const VECTOR2D& aOrigin, const wxString& aT
}
void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart,
const VECTOR2D& aBezierControl1,
const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd,
const IMPORTED_STROKE& aStroke )
void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1,
const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd,
const IMPORTED_STROKE& aStroke )
{
std::unique_ptr<SCH_SHAPE> spline = std::make_unique<SCH_SHAPE>( SHAPE_T::BEZIER );
spline->SetStroke( MapStrokeParams( aStroke ) );
@ -207,20 +206,13 @@ void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart,
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
spline->SetEnd( MapCoordinate( aEnd ) );
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() / 2 );
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length
if( spline->GetBezierPoints().size() <= 2 )
if( setupSplineOrLine( *spline, aStroke.GetWidth() / 2 ) )
{
spline->SetShape( SHAPE_T::SEGMENT );
int dist = VECTOR2I( spline->GetStart() - spline->GetEnd() ).EuclideanNorm();
// segment smaller than MIN_SEG_LEN_ACCEPTABLE_NM nanometers are skipped.
#define MIN_SEG_LEN_ACCEPTABLE_NM 20
if( dist < MIN_SEG_LEN_ACCEPTABLE_NM )
return;
// SCH_LINES aren't SCH_SHAPES
if( spline->GetShape() == SHAPE_T::SEGMENT )
AddLine( aStart, aEnd, aStroke );
else
addItem( std::move( spline ) );
}
addItem( std::move( spline ) );
}

View File

@ -1186,7 +1186,7 @@ int SCH_DRAWING_TOOLS::ImportGraphics( const TOOL_EVENT& aEvent )
for( std::unique_ptr<EDA_ITEM>& ptr : list )
{
SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( ptr.get() );
wxCHECK2( item, continue );
wxCHECK2_MSG( item, continue, wxString::Format( "Bad item type: ", ptr->Type() ) );
newItems.push_back( item );
selectedItems.push_back( item );

View File

@ -201,28 +201,16 @@ void GRAPHICS_IMPORTER_PCBNEW::AddSpline( const VECTOR2D& aStart, const VECTOR2D
const IMPORTED_STROKE& aStroke )
{
std::unique_ptr<PCB_SHAPE> spline = std::make_unique<PCB_SHAPE>( m_parent );
spline->SetShape( SHAPE_T::BEZIER );
spline->SetLayer( GetLayer() );
spline->SetStroke( MapStrokeParams( aStroke ) );
spline->SetStart( MapCoordinate( aStart ) );
spline->SetBezierC1( MapCoordinate( aBezierControl1 ));
spline->SetBezierC2( MapCoordinate( aBezierControl2 ));
spline->SetEnd( MapCoordinate( aEnd ) );
spline->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
// null (i.e. very small) length
if( spline->GetBezierPoints().size() <= 2 )
if( setupSplineOrLine( *spline, ARC_HIGH_DEF ) )
{
spline->SetShape( SHAPE_T::SEGMENT );
int dist = VECTOR2I(spline->GetStart()- spline->GetEnd()).EuclideanNorm();
// segment smaller than MIN_SEG_LEN_ACCEPTABLE_NM nanometers are skipped.
#define MIN_SEG_LEN_ACCEPTABLE_NM 20
if( dist < MIN_SEG_LEN_ACCEPTABLE_NM )
return;
addItem( std::move( spline ) );
}
addItem( std::move( spline ) );
}