diff --git a/pcbnew/import_gfx/graphics_importer.cpp b/pcbnew/import_gfx/graphics_importer.cpp index d51bcced3b..a8359447e3 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 2c332d0431..fa388368e3 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 df8d034ceb..d8468d0b80 100644 --- a/pcbnew/import_gfx/graphics_importer_buffer.cpp +++ b/pcbnew/import_gfx/graphics_importer_buffer.cpp @@ -86,3 +86,119 @@ void GRAPHICS_IMPORTER_BUFFER::ImportTo( GRAPHICS_IMPORTER& aImporter ) for( std::unique_ptr& 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 = 0; + 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( 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 f9153b9551..82b6b16b53 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;