diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index bf981aeef4..d0378b87dc 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -248,8 +248,11 @@ set( COMMON_SRCS tool/context_menu.cpp geometry/seg.cpp + geometry/shape.cpp geometry/shape_line_chain.cpp + geometry/shape_poly_set.cpp geometry/shape_collisions.cpp + geometry/shape_file_io.cpp ) add_library( common STATIC ${COMMON_SRCS} ) add_dependencies( common lib-dependencies ) diff --git a/common/geometry/shape.cpp b/common/geometry/shape.cpp new file mode 100644 index 0000000000..0e312c56ae --- /dev/null +++ b/common/geometry/shape.cpp @@ -0,0 +1,38 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski + * + * 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 + +bool SHAPE::Parse( std::stringstream& aStream ) +{ + assert ( false ); + return false; +}; + +const std::string SHAPE::Format( ) const +{ + assert ( false ); + return std::string(""); +}; diff --git a/common/geometry/shape_file_io.cpp b/common/geometry/shape_file_io.cpp new file mode 100644 index 0000000000..59e0b0caf8 --- /dev/null +++ b/common/geometry/shape_file_io.cpp @@ -0,0 +1,87 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski + * + * 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 + +SHAPE_FILE_IO::SHAPE_FILE_IO( const std::string& aFilename, bool aAppend ) +{ + m_groupActive = false; + m_file = fopen ( aFilename.c_str(), aAppend ? "ab" : "wb" ); + + // fixme: exceptions +} + +SHAPE_FILE_IO::~SHAPE_FILE_IO() +{ + if(!m_file) + return; + + if (m_groupActive) + fprintf(m_file,"endgroup\n"); + + fclose(m_file); +} + +SHAPE *SHAPE_FILE_IO::Read() +{ + assert(false); + return NULL; +} + +void SHAPE_FILE_IO::BeginGroup( const std::string aName ) +{ + if(!m_file) + return; + fprintf(m_file, "group %s\n", aName.c_str()); + m_groupActive = true; +} + +void SHAPE_FILE_IO::EndGroup() +{ + if(!m_file || !m_groupActive) + return; + + fprintf(m_file, "endgroup\n"); + m_groupActive = false; +} + +void SHAPE_FILE_IO::Write( const SHAPE *aShape, const std::string aName ) +{ + printf("write %p f %p\n", aShape, m_file ); + + if(!m_file) + return; + + if(!m_groupActive) + fprintf(m_file,"group default\n"); + + std::string sh = aShape->Format(); + + fprintf(m_file,"shape %d %s %s\n", aShape->Type(), aName.c_str(), sh.c_str() ); + fflush(m_file); +} \ No newline at end of file diff --git a/common/geometry/shape_poly_set.cpp b/common/geometry/shape_poly_set.cpp new file mode 100644 index 0000000000..b6bad11f1a --- /dev/null +++ b/common/geometry/shape_poly_set.cpp @@ -0,0 +1,580 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski + * + * 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 +#include +#include +#include + +#include + +#include "geometry/shape_poly_set.h" + +using namespace ClipperLib; + +int SHAPE_POLY_SET::NewOutline () +{ + Path empty_path; + Paths poly; + poly.push_back(empty_path); + m_polys.push_back(poly); + return m_polys.size() - 1; +} + +int SHAPE_POLY_SET::NewHole( int aOutline ) +{ + assert(false); + return -1; +} + +int SHAPE_POLY_SET::AppendVertex ( int x, int y, int aOutline, int aHole ) +{ + if(aOutline < 0) + aOutline += m_polys.size(); + + int idx; + + if(aHole < 0) + idx = 0; + else + idx = aHole + 1; + + assert ( aOutline < (int)m_polys.size() ); + assert ( idx < (int)m_polys[aOutline].size() ); + + m_polys[aOutline][idx].push_back( IntPoint( x, y ) ); + + return m_polys[aOutline][idx].size(); +} + +int SHAPE_POLY_SET::VertexCount ( int aOutline , int aHole ) const +{ + if(aOutline < 0) + aOutline += m_polys.size(); + + int idx; + + if(aHole < 0) + idx = 0; + else + idx = aHole + 1; + + assert ( aOutline < (int)m_polys.size() ); + assert ( idx < (int)m_polys[aOutline].size() ); + + return m_polys[aOutline][idx].size(); +} + +const VECTOR2I SHAPE_POLY_SET::GetVertex ( int index, int aOutline , int aHole ) const +{ + if(aOutline < 0) + aOutline += m_polys.size(); + + int idx; + + if(aHole < 0) + idx = 0; + else + idx = aHole + 1; + + assert ( aOutline < (int)m_polys.size() ); + assert ( idx < (int)m_polys[aOutline].size() ); + + IntPoint p = m_polys[aOutline][idx][index]; + return VECTOR2I (p.X, p.Y); +} + +int SHAPE_POLY_SET::AddOutline( const SHAPE_LINE_CHAIN& aOutline ) +{ + assert ( aOutline.IsClosed() ); + + Path p = convert ( aOutline ); + Paths poly; + + if( !Orientation( p ) ) + ReversePath(p); // outlines are always CW + + poly.push_back( p ); + + m_polys.push_back( poly ); + + return m_polys.size() - 1; +} + +int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline ) +{ + assert ( m_polys.size() ); + if(aOutline < 0) + aOutline += m_polys.size(); + + Paths& poly = m_polys[ aOutline ]; + + assert ( poly.size() ); + + Path p = convert ( aHole ); + if( Orientation( p ) ) + ReversePath(p); // holes are always CCW + + poly.push_back( p ); + + return poly.size() - 1; +} + +const ClipperLib::Path SHAPE_POLY_SET::convert( const SHAPE_LINE_CHAIN& aPath ) +{ + Path c_path; + + for(int i = 0; i < aPath.PointCount(); i++) + { + const VECTOR2I& vertex = aPath.CPoint(i); + c_path.push_back(ClipperLib::IntPoint ( vertex.x, vertex.y ) ); + } + + return c_path; +} + +void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType type, const SHAPE_POLY_SET& b ) +{ + Clipper c; + + c.StrictlySimple( true ); + + BOOST_FOREACH ( Paths& subject, m_polys ) + { + c.AddPaths(subject, ptSubject, true); + } + + BOOST_FOREACH ( const Paths& clip, b.m_polys ) + { + c.AddPaths(clip, ptClip, true); + } + + PolyTree solution; + + c.Execute(type, solution, pftNonZero, pftNonZero); + + importTree(&solution); +} + +void SHAPE_POLY_SET::Add( const SHAPE_POLY_SET& b ) +{ + booleanOp( ctUnion, b ); +} + +void SHAPE_POLY_SET::Subtract( const SHAPE_POLY_SET& b ) +{ + booleanOp( ctDifference, b ); +} + + +void SHAPE_POLY_SET::Erode ( int aFactor ) +{ + ClipperOffset c; + + BOOST_FOREACH( Paths& p, m_polys ) + c.AddPaths(p, jtRound, etClosedPolygon ); + + PolyTree solution; + + c.Execute ( solution, aFactor ); + + m_polys.clear(); + + for (PolyNode *n = solution.GetFirst(); n; n = n->GetNext() ) + { + Paths ps; + ps.push_back(n->Contour); + m_polys.push_back(ps); + } +} + +void SHAPE_POLY_SET::importTree ( ClipperLib::PolyTree* tree) +{ + m_polys.clear(); + + for (PolyNode *n = tree->GetFirst(); n; n = n->GetNext() ) + { + if( !n->IsHole() ) + { + Paths paths; + paths.push_back(n->Contour); + + for (unsigned i = 0; i < n->Childs.size(); i++) + paths.push_back(n->Childs[i]->Contour); + m_polys.push_back(paths); + } + } +} + +// Polygon fracturing code. Work in progress. + +struct FractureEdge +{ + FractureEdge(bool connected, Path* owner, int index) : + m_connected(connected), + m_owner(owner), + m_next(NULL) + { + m_p1 = (*owner)[index]; + m_p2 = (*owner)[(index + 1) % owner->size()]; + } + + FractureEdge(int64_t y = 0) : + m_connected(false), + m_owner(NULL), + m_next(NULL) + { + m_p1.Y = m_p2.Y = y; + } + + FractureEdge(bool connected, const IntPoint& p1, const IntPoint& p2) : + m_connected(connected), + m_owner(NULL), + m_p1(p1), + m_p2(p2), + m_next(NULL) + { + + } + + bool matches ( int y ) const + { + int y_min = std::min(m_p1.Y, m_p2.Y); + int y_max = std::max(m_p1.Y, m_p2.Y); + + return (y >= y_min) && (y <= y_max); + } + + bool m_connected; + Path* m_owner; + IntPoint m_p1, m_p2; + FractureEdge *m_next; +}; + +struct CompareEdges +{ + bool operator()(const FractureEdge *a, const FractureEdge *b) const + { + if( std::min(a->m_p1.Y, a->m_p2.Y) < std::min(b->m_p1.Y, b->m_p2.Y) ) + return true; + return false; + } +}; + +typedef std::multiset FractureEdgeSet; + +static int processEdge ( FractureEdgeSet& edges, FractureEdge* edge ) +{ + int n = 0; + int64_t x = edge->m_p1.X; + int64_t y = edge->m_p1.Y; + + + int64_t min_dist_l = std::numeric_limits::max(); + int64_t min_dist_r = std::numeric_limits::max(); + int64_t x_nearest_l = 0, x_nearest_r = 0, x_nearest; + + // fixme: search edges in sorted multiset + // FractureEdge comp_min( std::min(edge->m_p1.Y, edge->m_p2.Y) ); + // FractureEdgeSet::iterator e_begin = edges.lower_bound ( &comp_min ); + + FractureEdgeSet::iterator e_nearest_l = edges.end(), e_nearest_r = edges.end(), e_nearest; + + + for(FractureEdgeSet::iterator i = edges.begin() ; i != edges.end(); ++i) + { + n++; + if( (*i)->matches(y) ) + { + int64_t x_intersect; + if( (*i)->m_p1.Y == (*i)->m_p2.Y ) // horizontal edge + x_intersect = std::max ( (*i)->m_p1.X, (*i)->m_p2.X ); + else + x_intersect = (*i)->m_p1.X + rescale((*i)->m_p2.X - (*i)->m_p1.X, y - (*i)->m_p1.Y, (*i)->m_p2.Y - (*i)->m_p1.Y ); + + int64_t dist = (x - x_intersect); + + if(dist > 0 && dist < min_dist_l) + { + min_dist_l = dist; + x_nearest_l = x_intersect; + e_nearest_l = i; + } + + if(dist <= 0 && (-dist) < min_dist_r) + { + min_dist_r = -dist; + x_nearest_r = x_intersect; + e_nearest_r = i; + } + } + } + + if(e_nearest_l != edges.end() || e_nearest_r != edges.end()) + { + if( e_nearest_l !=edges.end() && ( (*e_nearest_l)->m_connected || ((*e_nearest_l) ->m_owner != edge->m_owner ))) + { + e_nearest = e_nearest_l; + x_nearest = x_nearest_l; + } + else if( e_nearest_r !=edges.end() && ( (*e_nearest_r)->m_connected || ((*e_nearest_r) ->m_owner != edge->m_owner ) )) { + e_nearest = e_nearest_r; + x_nearest = x_nearest_r; + } + else + return 0; + + FractureEdge split_1 ( true, (*e_nearest)->m_p1, IntPoint(x_nearest, y) ); + FractureEdge *lead1 = new FractureEdge(true, IntPoint(x_nearest, y), IntPoint(x, y) ); + FractureEdge *lead2 = new FractureEdge(true, IntPoint(x, y), IntPoint(x_nearest, y) ); + FractureEdge *split_2 = new FractureEdge ( true, IntPoint(x_nearest, y), (*e_nearest)->m_p2 ); + + edges.insert(split_2); + edges.insert(lead1); + edges.insert(lead2); + + FractureEdge* link = (*e_nearest)->m_next; + + (*e_nearest)->m_p1 = split_1.m_p1; + (*e_nearest)->m_p2 = IntPoint(x_nearest, y); + (*e_nearest)->m_connected = true;//split_1->m_connected; + (*e_nearest)->m_next = lead1; + lead1->m_next = edge; + + FractureEdge *last; + for(last = edge; last->m_next != edge; last = last->m_next) + { + last->m_connected = true; + last->m_owner = NULL; + } + + last->m_owner = NULL; + last->m_connected = true; + last->m_next = lead2; + lead2->m_next = split_2; + split_2->m_next = link; + + return 1; + } + + return 0; +} + +void SHAPE_POLY_SET::fractureSingle( ClipperLib::Paths& paths ) +{ + FractureEdgeSet edges; + FractureEdge *root = NULL; + + bool first = true; + + if(paths.size() == 1) + return; + + int num_unconnected = 0; + + BOOST_FOREACH(Path& path, paths) + { + int index = 0; + + FractureEdge *prev = NULL, *first_edge = NULL; + for(unsigned i = 0; i < path.size(); i++) + { + FractureEdge *fe = new FractureEdge ( first, &path, index++ ); + + if(!root) + root = fe; + + if(!first_edge) + first_edge = fe; + if(prev) + prev->m_next = fe; + + if(i == path.size() - 1) + fe->m_next = first_edge; + + prev = fe; + edges.insert ( fe ); + + if(!fe->m_connected) + num_unconnected++; + } + + first = false; // first path is always the outline + } + + while(1) + { + bool found = false; + + for(FractureEdgeSet::iterator i = edges.begin(); i != edges.end(); ++i ) + { + if(!(*i)->m_connected) + { + if (processEdge ( edges, *i ) > 0) + found = true; + } + } + if(!found) + break; + } + + IntPoint prev = root->m_p1; + paths.clear(); + Path newPath; + newPath.push_back(prev); + FractureEdge *e; + IntPoint p; + + for( e = root; e->m_next != root; e=e->m_next) + { + p = e->m_p1; + newPath.push_back(p); + prev = p; + delete e; + } + + p = e->m_p1; + + delete e; + newPath.push_back(p); + paths.push_back(newPath); +} + +void SHAPE_POLY_SET::Fracture () +{ + BOOST_FOREACH(Paths& paths, m_polys) + { + fractureSingle( paths ); + } +} + +void SHAPE_POLY_SET::Simplify() +{ + for (unsigned i = 0; i < m_polys.size(); i++) + { + Paths out; + SimplifyPolygons(m_polys[i], out, pftNonZero); + m_polys[i] = out; + } +} + +const std::string SHAPE_POLY_SET::Format() const +{ + std::stringstream ss; + + ss << "polyset " << m_polys.size() << "\n"; + + for( unsigned i = 0; i < m_polys.size(); i++ ) + { + ss << "poly " << m_polys[i].size() << "\n"; + for( unsigned j = 0; j < m_polys[i].size(); j++) + { + ss << m_polys[i][j].size() << "\n"; + for( unsigned v = 0; v < m_polys[i][j].size(); v++) + ss << m_polys[i][j][v].X << " " << m_polys[i][j][v].Y << "\n"; + } + ss << "\n"; + } + + return ss.str(); +} + +bool SHAPE_POLY_SET::Parse( std::stringstream& aStream ) +{ + std::string tmp; + + aStream >> tmp; + + if(tmp != "polyset") + return false; + + aStream >> tmp; + + unsigned int n_polys = atoi( tmp.c_str() ); + if(n_polys < 0) + return false; + + for( unsigned i = 0; i < n_polys; i++ ) + { + ClipperLib::Paths paths; + + aStream >> tmp; + if(tmp != "poly") + return false; + + aStream >> tmp; + unsigned int n_outlines = atoi( tmp.c_str() ); + if(n_outlines < 0) + return false; + + for( unsigned j = 0; j < n_outlines; j++) + { + ClipperLib::Path outline; + + aStream >> tmp; + unsigned int n_vertices = atoi( tmp.c_str() ); + for( unsigned v = 0; v < n_vertices; v++) + { + ClipperLib::IntPoint p; + + aStream >> tmp; p.X = atoi ( tmp.c_str() ); + aStream >> tmp; p.Y = atoi ( tmp.c_str() ); + outline.push_back(p); + } + paths.push_back(outline); + } + m_polys.push_back(paths); + } + return true; +} + +const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const +{ + BOX2I bb; + bool first = true; + + for( unsigned i = 0; i < m_polys.size(); i++ ) + { + for( unsigned j = 0; j < m_polys[i].size(); j++) + { + for( unsigned v = 0; v < m_polys[i][j].size(); v++) + { + VECTOR2I p( m_polys[i][j][v].X, m_polys[i][j][v].Y ); + if(first) + bb = BOX2I(p, VECTOR2I(0, 0)); + else + bb.Merge (p); + + first = false; + } + } + } + + bb.Inflate( aClearance ); + return bb; +} diff --git a/include/geometry/shape.h b/include/geometry/shape.h index 89ec3ef991..300de610f8 100644 --- a/include/geometry/shape.h +++ b/include/geometry/shape.h @@ -25,6 +25,9 @@ #ifndef __SHAPE_H #define __SHAPE_H +#include +#include + #include #include @@ -42,7 +45,7 @@ enum SHAPE_TYPE SH_LINE_CHAIN, ///> line chain (polyline) SH_CIRCLE, ///> circle SH_CONVEX, ///> convex polygon - SH_POLYGON, ///> any polygon (with holes, etc.) + SH_POLY_SET, ///> any polygon (with holes, etc.) SH_COMPOUND ///> compound shape, consisting of multiple simple shapes }; @@ -153,6 +156,10 @@ public: virtual bool IsSolid() const = 0; + virtual bool Parse( std::stringstream& aStream ); + + virtual const std::string Format( ) const; + protected: ///> type of our shape SHAPE_TYPE m_type; diff --git a/include/geometry/shape_file_io.h b/include/geometry/shape_file_io.h new file mode 100644 index 0000000000..5df105aa64 --- /dev/null +++ b/include/geometry/shape_file_io.h @@ -0,0 +1,61 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski + * + * 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 + */ + + +#ifndef __SHAPE_FILE_IO_H +#define __SHAPE_FILE_IO_H + +#include + +class SHAPE; + +/** + * Class SHAPE_FILE_IO + * + * Helper class for saving/loading shapes from a file. + */ +class SHAPE_FILE_IO +{ + public: + SHAPE_FILE_IO ( const std::string& aFilename, bool aAppend = false ); + ~SHAPE_FILE_IO ( ); + + void BeginGroup( const std::string aName = ""); + void EndGroup( ); + + SHAPE *Read(); + + void Write ( const SHAPE *aShape, const std::string aName = ""); + + void Write ( const SHAPE &aShape, const std::string aName = "") + { + Write( &aShape, aName ); + }; + + private: + FILE *m_file; + bool m_groupActive; +}; + +#endif diff --git a/include/geometry/shape_poly_set.h b/include/geometry/shape_poly_set.h new file mode 100644 index 0000000000..4988752c65 --- /dev/null +++ b/include/geometry/shape_poly_set.h @@ -0,0 +1,132 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski + * + * 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 + */ + +#ifndef __SHAPE_POLY_SET_H +#define __SHAPE_POLY_SET_H + +#include +#include +#include +#include + +#include "clipper.hpp" + +/** + * Class SHAPE_POLY_SET + * + * Represents a set of closed polygons. Polygons may be nonconvex, self-intersecting + * and have holes. Provides boolean operations (using Clipper library as the backend). + * + * TODO: document, derive from class SHAPE, add convex partitioning & spatial index + */ +class SHAPE_POLY_SET : public SHAPE +{ + public: + SHAPE_POLY_SET() : SHAPE (SH_POLY_SET) {}; + ~SHAPE_POLY_SET() {}; + + ///> Creates a new empty polygon in the set and returns its index + int NewOutline (); + + ///> Cretes a new empty hole in the given outline (default: last one) and returns its index + int NewHole( int aOutline = -1); + + ///> Adds a new outline to the set and returns its index + int AddOutline ( const SHAPE_LINE_CHAIN& aOutline ); + + ///> Adds a new hole to the given outline (default: last) and returns its index + int AddHole ( const SHAPE_LINE_CHAIN& aHole, int aOutline = -1 ); + + ///> Appends a vertex at the end of the given outline/hole (default: last hole in the last outline) + int AppendVertex ( int x, int y, int aOutline = -1, int aHole = -1 ); + + ///> Returns the index-th vertex in a given hole outline within a given outline + const VECTOR2I GetVertex ( int index, int aOutline = -1, int aHole = -1) const; + + ///> Returns true if any of the outlines is self-intersecting + bool IsSelfIntersecting(); + + ///> Returns the number of outlines in the set + int OutlineCount() const { return m_polys.size(); } + + ///> Returns the number of vertices in a given outline/hole + int VertexCount ( int aOutline = -1, int aHole = -1 ) const; + + ///> Returns the internal representation (ClipperLib) of a given polygon (outline + holes) + const ClipperLib::Paths& GetPoly ( int aIndex ) const + { + return m_polys[aIndex]; + } + + ///> Performs boolean polyset difference + void Subtract( const SHAPE_POLY_SET& b ); + + ///> Performs boolean polyset union + void Add( const SHAPE_POLY_SET& b ); + + ///> Performs smooth outline inflation (Minkowski sum of the outline and a circle of a given radius) + void SmoothInflate ( int aFactor ); + + ///> Performs outline erosion/shrinking + void Erode ( int aFactor ); + + ///> Converts a set of polygons with holes to a singe outline with 'slits'/'fractures' connecting the outer ring + ///> to the inner holes + void Fracture (); + + ///> Simplifies the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) + void Simplify (); + + /// @copydoc SHAPE::Format() + const std::string Format() const; + + /// @copydoc SHAPE::Parse() + bool Parse( std::stringstream& aStream ); + + void Move( const VECTOR2I& aVector ) { assert(false ); }; + + bool IsSolid() const + { + return true; + } + + const BOX2I BBox( int aClearance = 0 ) const; + + bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const { return false; } + bool Collide( const SEG& aSeg, int aClearance = 0 ) const { return false; } + + private: + + void fractureSingle( ClipperLib::Paths& paths ); + void importTree ( ClipperLib::PolyTree* tree); + void booleanOp( ClipperLib::ClipType type, const SHAPE_POLY_SET& b ); + + const ClipperLib::Path convert( const SHAPE_LINE_CHAIN& aPath ); + + typedef std::vector Polyset; + + Polyset m_polys; +}; + +#endif