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:
parent
3bd3b8073e
commit
85552c7a4f
common/import_gfx
eeschema
pcbnew/import_gfx
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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 )
|
||||
|
@ -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.
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user