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:
Tomasz Wlostowski 2021-12-30 03:48:44 +01:00 committed by jean-pierre charras
parent 0fdd1df208
commit 680163f8a8
8 changed files with 235 additions and 3 deletions

View File

@ -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 );

View File

@ -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;
}

View File

@ -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 );
}

View File

@ -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;

View File

@ -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 );
}

View File

@ -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;

View File

@ -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 );
}

View File

@ -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 */