/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 2017 CERN * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors. * @author Janito Vaqueiro Ferreira Filho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include "graphics_importer_buffer.h" using namespace std; template static std::unique_ptr make_shape( const Args&... aArguments ) { return std::make_unique( aArguments... ); } void GRAPHICS_IMPORTER_BUFFER::AddLine( const VECTOR2D& aStart, const VECTOR2D& aEnd, const IMPORTED_STROKE& aStroke ) { m_shapes.push_back( make_shape( aStart, aEnd, aStroke ) ); } void GRAPHICS_IMPORTER_BUFFER::AddCircle( const VECTOR2D& aCenter, double aRadius, const IMPORTED_STROKE& aStroke, bool aFilled, const COLOR4D& aFillColor ) { m_shapes.push_back( make_shape( aCenter, aRadius, aStroke, aFilled, aFillColor ) ); } void GRAPHICS_IMPORTER_BUFFER::AddArc( const VECTOR2D& aCenter, const VECTOR2D& aStart, const EDA_ANGLE& aAngle, const IMPORTED_STROKE& aStroke ) { m_shapes.push_back( make_shape( aCenter, aStart, aAngle, aStroke ) ); } void GRAPHICS_IMPORTER_BUFFER::AddPolygon( const std::vector& aVertices, const IMPORTED_STROKE& aStroke, bool aFilled, const COLOR4D& aFillColor ) { m_shapes.push_back( make_shape( aVertices, aStroke, aFilled, aFillColor ) ); m_shapes.back()->SetParentShapeIndex( m_shapeFillRules.size() - 1 ); } void GRAPHICS_IMPORTER_BUFFER::AddText( const VECTOR2D& aOrigin, const wxString& aText, double aHeight, double aWidth, double aThickness, double aOrientation, GR_TEXT_H_ALIGN_T aHJustify, GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D& aColor ) { m_shapes.push_back( make_shape( aOrigin, aText, aHeight, aWidth, aThickness, aOrientation, aHJustify, aVJustify, aColor ) ); } void GRAPHICS_IMPORTER_BUFFER::AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1, const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd, const IMPORTED_STROKE& aStroke ) { m_shapes.push_back( make_shape( aStart, aBezierControl1, aBezierControl2, aEnd, aStroke ) ); } void GRAPHICS_IMPORTER_BUFFER::AddShape( std::unique_ptr& aShape ) { m_shapes.push_back( std::move( aShape ) ); } 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, const IMPORTED_STROKE& aStroke, bool aFilled, const COLOR4D& aFillColor ) { 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 losing 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 openPaths; std::vector upscaledPaths; for( IMPORTED_POLYGON* path : aPaths ) { if( path->Vertices().size() < 3 ) { openPaths.push_back( path ); continue; } 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, aStroke, aFilled, aFillColor ) ); } for( IMPORTED_POLYGON* openPath : openPaths ) aShapes.push_back( std::make_unique( *openPath ) ); } void GRAPHICS_IMPORTER_BUFFER::PostprocessNestedPolygons() { int curShapeIdx = -1; IMPORTED_STROKE lastStroke; bool lastFilled = false; COLOR4D lastFillColor = COLOR4D::UNSPECIFIED; std::list> newShapes; std::vector polypaths; for( std::unique_ptr& shape : m_shapes ) { IMPORTED_POLYGON* poly = dynamic_cast( shape.get() ); if( !poly || poly->GetParentShapeIndex() < 0 ) { newShapes.push_back( shape->clone() ); continue; } int index = poly->GetParentShapeIndex(); if( index != curShapeIdx && curShapeIdx >= 0 ) { convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled, lastFillColor ); polypaths.clear(); } curShapeIdx = index; lastStroke = poly->GetStroke(); lastFilled = poly->IsFilled(); lastFillColor = poly->GetFillColor(); polypaths.push_back( poly ); } if( curShapeIdx >= 0 ) { convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled, lastFillColor ); } m_shapes.swap( newShapes ); }