diff --git a/libs/kimath/include/geometry/shape_poly_set.h b/libs/kimath/include/geometry/shape_poly_set.h index 1cd9cd056b..4c9c7e40ec 100644 --- a/libs/kimath/include/geometry/shape_poly_set.h +++ b/libs/kimath/include/geometry/shape_poly_set.h @@ -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& aPaths, bool aReverseOrientation = false, bool aEvenOdd = false ); + private: void fractureSingle( POLYGON& paths ); void unfractureSingle ( POLYGON& path ); diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp index 58225b6969..49ff0f9de9 100644 --- a/libs/kimath/src/geometry/shape_poly_set.cpp +++ b/libs/kimath/src/geometry/shape_poly_set.cpp @@ -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& 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; +} diff --git a/pcbnew/import_gfx/graphics_importer.cpp b/pcbnew/import_gfx/graphics_importer.cpp index 5e435ea48d..6158b1dff8 100644 --- a/pcbnew/import_gfx/graphics_importer.cpp +++ b/pcbnew/import_gfx/graphics_importer.cpp @@ -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 ); +} diff --git a/pcbnew/import_gfx/graphics_importer.h b/pcbnew/import_gfx/graphics_importer.h index b14c2dd19e..97fadf6dc7 100644 --- a/pcbnew/import_gfx/graphics_importer.h +++ b/pcbnew/import_gfx/graphics_importer.h @@ -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 m_shapeFillRules; + private: ///< List of imported items std::list> m_items; diff --git a/pcbnew/import_gfx/graphics_importer_buffer.cpp b/pcbnew/import_gfx/graphics_importer_buffer.cpp index 6be5c61b2f..5f12184be5 100644 --- a/pcbnew/import_gfx/graphics_importer_buffer.cpp +++ b/pcbnew/import_gfx/graphics_importer_buffer.cpp @@ -23,6 +23,10 @@ */ #include + +#include +#include + #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>& aShapes, + std::vector& aPaths, + GRAPHICS_IMPORTER::POLY_FILL_RULE aFillRule, + int aWidth ) +{ + double minX = std::numeric_limits::max(); + double minY = minX; + double maxX = std::numeric_limits::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 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 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( pts, aWidth ) ); + } +} + + +void GRAPHICS_IMPORTER_BUFFER::PostprocessNestedPolygons() +{ + int curShapeIdx = -1; + int lastWidth = 1; + + std::list> newShapes; + std::vector polypaths; + + for( auto& shape : m_shapes ) + { + IMPORTED_POLYGON* poly = dynamic_cast( 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 ); +} diff --git a/pcbnew/import_gfx/graphics_importer_buffer.h b/pcbnew/import_gfx/graphics_importer_buffer.h index c539037976..4294bf7560 100644 --- a/pcbnew/import_gfx/graphics_importer_buffer.h +++ b/pcbnew/import_gfx/graphics_importer_buffer.h @@ -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& Vertices() { return m_vertices; } + + int GetWidth() const { return m_width; } + private: std::vector 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; diff --git a/pcbnew/import_gfx/svg_import_plugin.cpp b/pcbnew/import_gfx/svg_import_plugin.cpp index d8bad79ff3..d7a5423ec9 100644 --- a/pcbnew/import_gfx/svg_import_plugin.cpp +++ b/pcbnew/import_gfx/svg_import_plugin.cpp @@ -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 ); } diff --git a/pcbnew/import_gfx/svg_import_plugin.h b/pcbnew/import_gfx/svg_import_plugin.h index 7030837bb8..1f917fb355 100644 --- a/pcbnew/import_gfx/svg_import_plugin.h +++ b/pcbnew/import_gfx/svg_import_plugin.h @@ -29,6 +29,7 @@ #include "nanosvg.h" #include "graphics_import_plugin.h" +#include "graphics_importer_buffer.h" #include #include @@ -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 */