Cherry-pick commits from Master and about SVG import:
*Fixes in the SVG import polygon postprocessing: - don't drop subsequent polys when a non-filled primitive is imported 'in between' - fix missing holes (also related to the interleaving of stroke and filled shapes, depending on the software that wrote the SVG file) *GRAPHICS_IMPORTER_BUFFER: forgot to store the indices of the paths belonging to a compound shape *pcbnew: GRAPHICS_IMPORTER can now recognize multi-path shapes (and postprocess polygons with holes into Kicad-compatible fractured polysets) *SHAPE_POLY_SET: polysets can now be built from a bunch of arbitraily ordered oriented outlines. Used by the SVG hole support *SVG_IMPORT_PLUGIN: enable postprocessing of polygons with degeneracy and holes *pcbnew: SVG importer support for even-odd fill rule
This commit is contained in:
parent
0fdd1df208
commit
680163f8a8
|
@ -1350,6 +1350,16 @@ public:
|
|||
*/
|
||||
bool IsVertexInHole( int aGlobalIdx );
|
||||
|
||||
/**
|
||||
* Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order.
|
||||
*
|
||||
* @param aPath set of closed outlines forming the polygon. Positive orientation = outline, negative = hole
|
||||
* @param aReverseOrientation inverts the sign of the orientation of aPaths (so negative = outline)
|
||||
* @param aEvenOdd forces the even-off fill rule (default is non zero)
|
||||
* @return the constructed poly set
|
||||
*/
|
||||
static const SHAPE_POLY_SET BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths, bool aReverseOrientation = false, bool aEvenOdd = false );
|
||||
|
||||
private:
|
||||
void fractureSingle( POLYGON& paths );
|
||||
void unfractureSingle ( POLYGON& path );
|
||||
|
|
|
@ -2525,3 +2525,55 @@ SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON()
|
|||
SHAPE_POLY_SET::TRIANGULATED_POLYGON::~TRIANGULATED_POLYGON()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
const SHAPE_POLY_SET
|
||||
SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
|
||||
bool aReverseOrientation, bool aEvenOdd )
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
ClipperLib::PolyTree tree;
|
||||
|
||||
// fixme: do we need aReverseOrientation?
|
||||
|
||||
for( const SHAPE_LINE_CHAIN& path : aPaths )
|
||||
{
|
||||
ClipperLib::Path lc;
|
||||
|
||||
for( int i = 0; i < path.PointCount(); i++ )
|
||||
{
|
||||
lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
|
||||
}
|
||||
|
||||
clipper.AddPath( lc, ClipperLib::ptSubject, true );
|
||||
}
|
||||
|
||||
clipper.StrictlySimple( true );
|
||||
clipper.Execute( ClipperLib::ctUnion, tree,
|
||||
aEvenOdd ? ClipperLib::pftEvenOdd : ClipperLib::pftNonZero,
|
||||
ClipperLib::pftNonZero );
|
||||
SHAPE_POLY_SET result;
|
||||
|
||||
for( ClipperLib::PolyNode* n = tree.GetFirst(); n; n = n->GetNext() )
|
||||
{
|
||||
if( !n->IsHole() )
|
||||
{
|
||||
int outl = result.NewOutline();
|
||||
|
||||
for( unsigned int i = 0; i < n->Contour.size(); i++ )
|
||||
result.Outline( outl ).Append( n->Contour[i].X, n->Contour[i].Y );
|
||||
|
||||
for( unsigned int i = 0; i < n->Childs.size(); i++ )
|
||||
{
|
||||
int outh = result.NewHole( outl );
|
||||
for( unsigned int j = 0; j < n->Childs[i]->Contour.size(); j++ )
|
||||
{
|
||||
result.Hole( outl, outh )
|
||||
.Append( n->Childs[i]->Contour[j].X, n->Childs[i]->Contour[j].Y );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -78,3 +78,9 @@ bool GRAPHICS_IMPORTER::Import( double aScale )
|
|||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void GRAPHICS_IMPORTER::NewShape( POLY_FILL_RULE aFillRule )
|
||||
{
|
||||
m_shapeFillRules.push_back( aFillRule );
|
||||
}
|
||||
|
|
|
@ -45,6 +45,12 @@ class EDA_ITEM;
|
|||
class GRAPHICS_IMPORTER
|
||||
{
|
||||
public:
|
||||
enum POLY_FILL_RULE
|
||||
{
|
||||
PF_NONZERO = 0,
|
||||
PF_EVEN_ODD
|
||||
};
|
||||
|
||||
GRAPHICS_IMPORTER();
|
||||
|
||||
virtual ~GRAPHICS_IMPORTER()
|
||||
|
@ -186,6 +192,8 @@ public:
|
|||
///< Default line thickness (in mm)
|
||||
static constexpr unsigned int DEFAULT_LINE_WIDTH_DFX = 1;
|
||||
|
||||
virtual void NewShape( POLY_FILL_RULE aFillRule = PF_NONZERO );
|
||||
|
||||
/**
|
||||
* Create an object representing a line segment.
|
||||
*
|
||||
|
@ -261,6 +269,8 @@ protected:
|
|||
///< Offset (in mm) for imported coordinates
|
||||
VECTOR2D m_offsetCoordmm;
|
||||
|
||||
std::vector<POLY_FILL_RULE> m_shapeFillRules;
|
||||
|
||||
private:
|
||||
///< List of imported items
|
||||
std::list<std::unique_ptr<EDA_ITEM>> m_items;
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
*/
|
||||
|
||||
#include <eda_item.h>
|
||||
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/shape_poly_set.h>
|
||||
|
||||
#include "graphics_importer_buffer.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -55,6 +59,7 @@ void GRAPHICS_IMPORTER_BUFFER::AddArc( const VECTOR2D& aCenter, const VECTOR2D&
|
|||
void GRAPHICS_IMPORTER_BUFFER::AddPolygon( const std::vector< VECTOR2D >& aVertices, double aWidth )
|
||||
{
|
||||
m_shapes.push_back( make_shape< IMPORTED_POLYGON >( aVertices, aWidth ) );
|
||||
m_shapes.back()->SetParentShapeIndex( m_shapeFillRules.size() - 1 );
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,3 +90,122 @@ void GRAPHICS_IMPORTER_BUFFER::ImportTo( GRAPHICS_IMPORTER& aImporter )
|
|||
for( auto& shape : m_shapes )
|
||||
shape->ImportTo( aImporter );
|
||||
}
|
||||
|
||||
// converts a single SVG-style polygon (multiple outlines, hole detection based on orientation, custom fill rule) to a format that can be digested by KiCad (single outline, fractured)
|
||||
static void convertPolygon( std::list<std::unique_ptr<IMPORTED_SHAPE>>& aShapes,
|
||||
std::vector<IMPORTED_POLYGON*>& aPaths,
|
||||
GRAPHICS_IMPORTER::POLY_FILL_RULE aFillRule,
|
||||
int aWidth )
|
||||
{
|
||||
double minX = std::numeric_limits<double>::max();
|
||||
double minY = minX;
|
||||
double maxX = std::numeric_limits<double>::min();
|
||||
double maxY = maxX;
|
||||
|
||||
// as Clipper/SHAPE_POLY_SET uses ints we first need to upscale to a reasonably large size (in integer coordinates)
|
||||
// to avoid loosing accuracy
|
||||
const double convert_scale = 1000000000.0;
|
||||
|
||||
for( IMPORTED_POLYGON* path : aPaths )
|
||||
{
|
||||
for( VECTOR2D& v : path->Vertices() )
|
||||
{
|
||||
minX = std::min( minX, v.x );
|
||||
minY = std::min( minY, v.y );
|
||||
maxX = std::max( maxX, v.x );
|
||||
maxY = std::max( maxY, v.y );
|
||||
}
|
||||
}
|
||||
|
||||
double origW = ( maxX - minX );
|
||||
double origH = ( maxY - minY );
|
||||
double upscaledW, upscaledH;
|
||||
|
||||
if( origW > origH )
|
||||
{
|
||||
upscaledW = convert_scale;
|
||||
upscaledH = ( origH == 0.0f ? 0.0 : origH * convert_scale / origW );
|
||||
}
|
||||
else
|
||||
{
|
||||
upscaledH = convert_scale;
|
||||
upscaledW = ( origW == 0.0f ? 0.0 : origW * convert_scale / origH );
|
||||
}
|
||||
|
||||
std::vector<SHAPE_LINE_CHAIN> upscaledPaths;
|
||||
|
||||
for( IMPORTED_POLYGON* path : aPaths )
|
||||
{
|
||||
SHAPE_LINE_CHAIN lc;
|
||||
|
||||
for( VECTOR2D& v : path->Vertices() )
|
||||
{
|
||||
int xp = KiROUND( ( v.x - minX ) * ( upscaledW / origW ) );
|
||||
int yp = KiROUND( ( v.y - minY ) * ( upscaledH / origH ) );
|
||||
lc.Append( xp, yp );
|
||||
}
|
||||
lc.SetClosed( true );
|
||||
upscaledPaths.push_back( lc );
|
||||
}
|
||||
|
||||
SHAPE_POLY_SET result = SHAPE_POLY_SET::BuildPolysetFromOrientedPaths(
|
||||
upscaledPaths, false, aFillRule == GRAPHICS_IMPORTER::PF_EVEN_ODD );
|
||||
result.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
||||
|
||||
for( int outl = 0; outl < result.OutlineCount(); outl++ )
|
||||
{
|
||||
const SHAPE_LINE_CHAIN& ro = result.COutline( outl );
|
||||
std::vector<VECTOR2D> pts;
|
||||
for( int i = 0; i < ro.PointCount(); i++ )
|
||||
{
|
||||
double xp = (double) ro.CPoint( i ).x * ( origW / upscaledW ) + minX;
|
||||
double yp = (double) ro.CPoint( i ).y * ( origH / upscaledH ) + minY;
|
||||
pts.emplace_back( VECTOR2D( xp, yp ) );
|
||||
}
|
||||
|
||||
aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( pts, aWidth ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GRAPHICS_IMPORTER_BUFFER::PostprocessNestedPolygons()
|
||||
{
|
||||
int curShapeIdx = -1;
|
||||
int lastWidth = 1;
|
||||
|
||||
std::list<std::unique_ptr<IMPORTED_SHAPE>> newShapes;
|
||||
std::vector<IMPORTED_POLYGON*> polypaths;
|
||||
|
||||
for( auto& shape : m_shapes )
|
||||
{
|
||||
IMPORTED_POLYGON* poly = dynamic_cast<IMPORTED_POLYGON*>( shape.get() );
|
||||
|
||||
if( !poly || poly->GetParentShapeIndex() < 0 )
|
||||
{
|
||||
newShapes.push_back( shape->clone() );
|
||||
continue;
|
||||
}
|
||||
|
||||
lastWidth = poly->GetWidth();
|
||||
int index = poly->GetParentShapeIndex();
|
||||
|
||||
if( curShapeIdx < 0 )
|
||||
index = curShapeIdx;
|
||||
|
||||
if( index == curShapeIdx )
|
||||
{
|
||||
polypaths.push_back( poly );
|
||||
}
|
||||
else if( index >= curShapeIdx + 1 )
|
||||
{
|
||||
convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastWidth );
|
||||
curShapeIdx++;
|
||||
polypaths.clear();
|
||||
polypaths.push_back( poly );
|
||||
}
|
||||
}
|
||||
|
||||
convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastWidth );
|
||||
|
||||
m_shapes.swap( newShapes );
|
||||
}
|
||||
|
|
|
@ -41,6 +41,12 @@ public:
|
|||
|
||||
virtual void Translate( const VECTOR2D& aVec ) = 0;
|
||||
virtual void Scale( double scaleX, double scaleY ) = 0;
|
||||
|
||||
void SetParentShapeIndex( int aIndex ) { m_parentShapeIndex = aIndex; }
|
||||
int GetParentShapeIndex() const { return m_parentShapeIndex; }
|
||||
|
||||
protected:
|
||||
int m_parentShapeIndex = -1;
|
||||
};
|
||||
|
||||
|
||||
|
@ -208,6 +214,10 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<VECTOR2D>& Vertices() { return m_vertices; }
|
||||
|
||||
int GetWidth() const { return m_width; }
|
||||
|
||||
private:
|
||||
std::vector<VECTOR2D> m_vertices;
|
||||
double m_width;
|
||||
|
@ -344,6 +354,8 @@ public:
|
|||
return m_shapes;
|
||||
}
|
||||
|
||||
void PostprocessNestedPolygons();
|
||||
|
||||
protected:
|
||||
///< List of imported shapes
|
||||
std::list< std::unique_ptr< IMPORTED_SHAPE > > m_shapes;
|
||||
|
|
|
@ -76,11 +76,26 @@ bool SVG_IMPORT_PLUGIN::Import()
|
|||
{
|
||||
double lineWidth = shape->strokeWidth;
|
||||
|
||||
GRAPHICS_IMPORTER::POLY_FILL_RULE rule = GRAPHICS_IMPORTER::PF_NONZERO;
|
||||
|
||||
switch( shape->fillRule )
|
||||
{
|
||||
case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
|
||||
case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
m_internalImporter.NewShape( rule );
|
||||
|
||||
for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
|
||||
DrawPath( path->pts, path->npts, path->closed, shape->fill.type == NSVG_PAINT_COLOR,
|
||||
lineWidth );
|
||||
}
|
||||
|
||||
m_internalImporter.PostprocessNestedPolygons();
|
||||
wxCHECK( m_importer, false );
|
||||
m_internalImporter.ImportTo( *m_importer );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -117,7 +132,7 @@ void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aCl
|
|||
if( aNumPoints > 0 )
|
||||
DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
|
||||
|
||||
if( aFilled && aClosedPath )
|
||||
if( aFilled )
|
||||
DrawPolygon( collectedPathPoints, aLineWidth );
|
||||
else
|
||||
DrawLineSegments( collectedPathPoints, aLineWidth );
|
||||
|
@ -157,7 +172,7 @@ void SVG_IMPORT_PLUGIN::DrawCubicBezierCurve( const float* aPoints,
|
|||
|
||||
void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector< VECTOR2D >& aPoints, double aWidth )
|
||||
{
|
||||
m_importer->AddPolygon( aPoints, aWidth );
|
||||
m_internalImporter.AddPolygon( aPoints, aWidth );
|
||||
}
|
||||
|
||||
|
||||
|
@ -166,7 +181,7 @@ void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector< VECTOR2D >& aPoints
|
|||
unsigned int numLineStartPoints = aPoints.size() - 1;
|
||||
|
||||
for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
|
||||
m_importer->AddLine( aPoints[ pointIndex ], aPoints[ pointIndex + 1 ], aWidth );
|
||||
m_internalImporter.AddLine( aPoints[ pointIndex ], aPoints[ pointIndex + 1 ], aWidth );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "nanosvg.h"
|
||||
|
||||
#include "graphics_import_plugin.h"
|
||||
#include "graphics_importer_buffer.h"
|
||||
#include <math/vector2d.h>
|
||||
#include <wildcards_and_files_ext.h>
|
||||
|
||||
|
@ -85,6 +86,8 @@ private:
|
|||
|
||||
wxString m_messages; // messages generated during svg file parsing.
|
||||
// Each message ends by '\n'
|
||||
|
||||
GRAPHICS_IMPORTER_BUFFER m_internalImporter;
|
||||
};
|
||||
|
||||
#endif /* SVG_IMPORT_PLUGIN_H */
|
||||
|
|
Loading…
Reference in New Issue