diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 3e08c68046..2e984c5769 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -46,6 +46,14 @@ #include "FeaturePathShape.h" #include "AreaPy.h" #include "FeatureArea.h" +#include "Voronoi.h" +#include "VoronoiCell.h" +#include "VoronoiCellPy.h" +#include "VoronoiEdge.h" +#include "VoronoiEdgePy.h" +#include "VoronoiPy.h" +#include "VoronoiVertex.h" +#include "VoronoiVertexPy.h" namespace Path { extern PyObject* initModule(); @@ -67,11 +75,15 @@ PyMOD_INIT_FUNC(Path) Base::Console().Log("Loading Path module... done\n"); // Add Types to module - Base::Interpreter().addType(&Path::CommandPy ::Type, pathModule, "Command"); - Base::Interpreter().addType(&Path::PathPy ::Type, pathModule, "Path"); - Base::Interpreter().addType(&Path::ToolPy ::Type, pathModule, "Tool"); - Base::Interpreter().addType(&Path::TooltablePy ::Type, pathModule, "Tooltable"); - Base::Interpreter().addType(&Path::AreaPy ::Type, pathModule, "Area"); + Base::Interpreter().addType(&Path::CommandPy ::Type, pathModule, "Command"); + Base::Interpreter().addType(&Path::PathPy ::Type, pathModule, "Path"); + Base::Interpreter().addType(&Path::ToolPy ::Type, pathModule, "Tool"); + Base::Interpreter().addType(&Path::TooltablePy ::Type, pathModule, "Tooltable"); + Base::Interpreter().addType(&Path::AreaPy ::Type, pathModule, "Area"); + Base::Interpreter().addType(&Path::VoronoiPy ::Type, pathModule, "Voronoi"); + Base::Interpreter().addType(&Path::VoronoiCellPy ::Type, pathModule, "VoronoiCell"); + Base::Interpreter().addType(&Path::VoronoiEdgePy ::Type, pathModule, "VoronoiEdge"); + Base::Interpreter().addType(&Path::VoronoiVertexPy ::Type, pathModule, "VoronoiVertex"); // NOTE: To finish the initialization of our own type objects we must // call PyType_Ready, otherwise we run into a segmentation fault, later on. @@ -94,6 +106,10 @@ PyMOD_INIT_FUNC(Path) Path::FeatureAreaPython ::init(); Path::FeatureAreaView ::init(); Path::FeatureAreaViewPython ::init(); + Path::Voronoi ::init(); + Path::VoronoiCell ::init(); + Path::VoronoiEdge ::init(); + Path::VoronoiVertex ::init(); PyMOD_Return(pathModule); } diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 2cee9a3a3a..5177dcf8f8 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -36,6 +36,10 @@ generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) generate_from_xml(AreaPy) generate_from_xml(FeatureAreaPy) +generate_from_xml(VoronoiPy) +generate_from_xml(VoronoiCellPy) +generate_from_xml(VoronoiEdgePy) +generate_from_xml(VoronoiVertexPy) SET(Python_SRCS CommandPy.xml @@ -52,6 +56,14 @@ SET(Python_SRCS AreaPyImp.cpp FeatureAreaPy.xml FeatureAreaPyImp.cpp + VoronoiPy.xml + VoronoiPyImp.cpp + VoronoiCellPy.xml + VoronoiCellPyImp.cpp + VoronoiEdgePy.xml + VoronoiEdgePyImp.cpp + VoronoiVertexPy.xml + VoronoiVertexPyImp.cpp ) SET(Mod_SRCS @@ -90,6 +102,14 @@ SET(Path_SRCS FeatureArea.h PathSegmentWalker.h PathSegmentWalker.cpp + Voronoi.cpp + Voronoi.h + VoronoiCell.cpp + VoronoiCell.h + VoronoiEdge.cpp + VoronoiEdge.h + VoronoiVertex.cpp + VoronoiVertex.h ${Mod_SRCS} ${Python_SRCS} ) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp new file mode 100644 index 0000000000..4a3382306b --- /dev/null +++ b/src/Mod/Path/App/Voronoi.cpp @@ -0,0 +1,310 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include "Voronoi.h" + +using namespace Base; +using namespace Path; + +TYPESYSTEM_SOURCE(Path::Voronoi , Base::BaseClass); + +// Helpers + +// Voronoi::diagram_type + +Voronoi::diagram_type::diagram_type() + :scale(1000) +{ +} + +double Voronoi::diagram_type::getScale() const { + return scale; +} + +void Voronoi::diagram_type::setScale(double s) { + scale = s; +} + +Base::Vector3d Voronoi::diagram_type::scaledVector(double x, double y, double z) const { + return Base::Vector3d(x / scale, y / scale, z); +} + +Base::Vector3d Voronoi::diagram_type::scaledVector(const point_type &p, double z) const { + return scaledVector(p.x(), p.y(), z); +} + +Base::Vector3d Voronoi::diagram_type::scaledVector(const vertex_type &v, double z) const { + return scaledVector(v.x(), v.y(), z); +} + + +int Voronoi::diagram_type::index(const Voronoi::diagram_type::cell_type *cell) const { + auto it = cell_index.find(intptr_t(cell)); + if (it == cell_index.end()) { + return Voronoi::InvalidIndex; + } + return it->second; +} +int Voronoi::diagram_type::index(const Voronoi::diagram_type::edge_type *edge) const { + auto it = edge_index.find(intptr_t(edge)); + if (it == edge_index.end()) { + return Voronoi::InvalidIndex; + } + return it->second; +} +int Voronoi::diagram_type::index(const Voronoi::diagram_type::vertex_type *vertex) const { + auto it = vertex_index.find(intptr_t(vertex)); + if (it == vertex_index.end()) { + return Voronoi::InvalidIndex; + } + return it->second; +} + +void Voronoi::diagram_type::reIndex() { + int idx = 0; + cell_index.clear(); + edge_index.clear(); + vertex_index.clear(); + + idx = 0; + for (auto it = cells().begin(); it != cells().end(); ++it, ++idx) { + cell_index[intptr_t(&(*it))] = idx; + } + idx = 0; + for (auto it = edges().begin(); it != edges().end(); ++it, ++idx) { + edge_index[intptr_t(&(*it))] = idx; + } + idx = 0; + for (auto it = vertices().begin(); it != vertices().end(); ++it, ++idx) { + vertex_index[intptr_t(&(*it))] = idx; + } +} + +Voronoi::point_type Voronoi::diagram_type::retrievePoint(const Voronoi::diagram_type::cell_type *cell) const { + Voronoi::diagram_type::cell_type::source_index_type index = cell->source_index(); + Voronoi::diagram_type::cell_type::source_category_type category = cell->source_category(); + if (category == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + return points[index]; + } + index -= points.size(); + if (category == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + return low(segments[index]); + } else { + return high(segments[index]); + } +} + +Voronoi::segment_type Voronoi::diagram_type::retrieveSegment(const Voronoi::diagram_type::cell_type *cell) const { + Voronoi::diagram_type::cell_type::source_index_type index = cell->source_index() - points.size(); + return segments[index]; +} + + +// Voronoi + +Voronoi::Voronoi() + :vd(new diagram_type) +{ +} + +Voronoi::~Voronoi() +{ +} + + +void Voronoi::addPoint(const Voronoi::point_type &p) { + Voronoi::point_type pi; + pi.x(p.x() * vd->getScale()); + pi.y(p.y() * vd->getScale()); + vd->points.push_back(pi); +} + +void Voronoi::addSegment(const Voronoi::segment_type &s) { + Voronoi::point_type pil, pih; + pil.x(low(s).x() * vd->getScale()); + pil.y(low(s).y() * vd->getScale()); + pih.x(high(s).x() * vd->getScale()); + pih.y(high(s).y() * vd->getScale()); + vd->segments.push_back(segment_type(pil, pih)); +} + +long Voronoi::numPoints() const { + return vd->points.size(); +} + +long Voronoi::numSegments() const { + return vd->segments.size(); +} + + +long Voronoi::numCells() const { + return vd->num_cells(); +} + +long Voronoi::numEdges() const { + return vd->num_edges(); +} + +long Voronoi::numVertices() const { + return vd->num_vertices(); +} + +void Voronoi::construct() +{ + vd->clear(); + construct_voronoi(vd->points.begin(), vd->points.end(), vd->segments.begin(), vd->segments.end(), (voronoi_diagram_type*)vd); + vd->reIndex(); +} + +void Voronoi::colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue) { + if (edge->color()) { + // end recursion + return; + } + edge->color(colorValue); + edge->twin()->color(colorValue); + auto v = edge->vertex1(); + if (v == NULL || !edge->is_primary()) { + return; + } + v->color(colorValue); + auto e = v->incident_edge(); + do { + colorExterior(e, colorValue); + e = e->rot_next(); + } while (e != v->incident_edge()); +} + +void Voronoi::colorExterior(Voronoi::color_type color) { + for (diagram_type::const_edge_iterator it = vd->edges().begin(); it != vd->edges().end(); ++it) { + if (it->is_infinite()) { + colorExterior(&(*it), color); + } + } +} + +void Voronoi::colorTwins(Voronoi::color_type color) { + for (diagram_type::const_edge_iterator it = vd->edges().begin(); it != vd->edges().end(); ++it) { + if (!it->color()) { + auto twin = it->twin(); + if (!twin->color()) { + twin->color(color); + } + } + } +} + +double Voronoi::diagram_type::angleOfSegment(int i, Voronoi::diagram_type::angle_map_t *angle) const { + Voronoi::diagram_type::angle_map_t::const_iterator a = angle ? angle->find(i) : Voronoi::diagram_type::angle_map_t::const_iterator(); + if (!angle || a == angle->end()) { + Voronoi::point_type p0 = low(segments[i]); + Voronoi::point_type p1 = high(segments[i]); + double ang = 0; + if (p0.x() == p1.x()) { + if ((p0.y() > 0 && p1.y() > 0) || (p0.y() > 0 && p1.y() > 0)) { + ang = M_PI_2; + } else { + ang = -M_PI_2; + } + } else { + ang = atan((p0.y() - p1.y()) / (p0.x() - p1.x())); + } + if (angle) { + angle->insert(angle_map_t::value_type(i, ang)); + } + return ang; + } + return a->second; +} + +static bool pointsMatch(const Voronoi::point_type &p0, const Voronoi::point_type &p1) { + return long(p0.x()) == long(p1.x()) && long(p0.y()) == long(p1.y()); +} + +bool Voronoi::diagram_type::segmentsAreConnected(int i, int j) const { + return + pointsMatch(low(segments[i]), low(segments[j])) + || pointsMatch(low(segments[i]), high(segments[j])) + || pointsMatch(high(segments[i]), low(segments[j])) + || pointsMatch(high(segments[i]), high(segments[j])); +} + +void Voronoi::colorColinear(Voronoi::color_type color, double degree) { + double rad = degree * M_PI / 180; + + Voronoi::diagram_type::angle_map_t angle; + int psize = vd->points.size(); + + for (diagram_type::const_edge_iterator it = vd->edges().begin(); it != vd->edges().end(); ++it) { + int i0 = it->cell()->source_index() - psize; + int i1 = it->twin()->cell()->source_index() - psize; + if (it->color() == 0 + && it->cell()->contains_segment() + && it->twin()->cell()->contains_segment() + && vd->segmentsAreConnected(i0, i1)) { + double a0 = vd->angleOfSegment(i0, &angle); + double a1 = vd->angleOfSegment(i1, &angle); + double a = a0 - a1; + if (a > M_PI_2) { + a -= M_PI; + } else if (a < -M_PI_2) { + a += M_PI; + } + if (fabs(a) < rad) { + it->color(color); + it->twin()->color(color); + } + } + } +} + +void Voronoi::resetColor(Voronoi::color_type color) { + for (auto it = vd->cells().begin(); it != vd->cells().end(); ++it) { + if (color == 0 || it->color() == color) { + it->color(0); + } + } + for (auto it = vd->edges().begin(); it != vd->edges().end(); ++it) { + if (it->color() == color) { + it->color(0); + } + } + for (auto it = vd->vertices().begin(); it != vd->vertices().end(); ++it) { + if (it->color() == color) { + it->color(0); + } + } +} diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h new file mode 100644 index 0000000000..0634ef3c91 --- /dev/null +++ b/src/Mod/Path/App/Voronoi.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ +#ifndef PATH_VORONOI_H +#define PATH_VORONOI_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Path +{ + class PathExport Voronoi + : public Base::BaseClass + { + TYPESYSTEM_HEADER(); + + public: + //constructors + Voronoi(); + ~Voronoi(); + + typedef std::size_t color_type; + static const int InvalidIndex = INT_MAX; + static const color_type ColorMask = 0x07FFFFFFFFFFFFFFul; // top 5 bits reserved internally + + // types + typedef double coordinate_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::voronoi_diagram voronoi_diagram_type; + + class diagram_type + : public voronoi_diagram_type + , public Base::Handled + { + public: + diagram_type(); + + double getScale() const; + void setScale(double s); + + Base::Vector3d scaledVector(double x, double y, double z) const; + Base::Vector3d scaledVector(const point_type &p, double z) const; + Base::Vector3d scaledVector(const vertex_type &v, double z) const; + + typedef std::map cell_map_type; + typedef std::map edge_map_type; + typedef std::map vertex_map_type; + + int index(const cell_type *cell) const; + int index(const edge_type *edge) const; + int index(const vertex_type *vertex) const; + + void reIndex(); + + std::vector points; + std::vector segments; + + point_type retrievePoint(const cell_type *cell) const; + segment_type retrieveSegment(const cell_type *cell) const; + + typedef std::map angle_map_t; + double angleOfSegment(int i, angle_map_t *angle = 0) const; + bool segmentsAreConnected(int i, int j) const; + + private: + double scale; + cell_map_type cell_index; + edge_map_type edge_index; + vertex_map_type vertex_index; + }; + + void addPoint(const point_type &p); + void addSegment(const segment_type &p); + long numPoints() const; + long numSegments() const; + + void construct(); + long numCells() const; + long numEdges() const; + long numVertices() const; + + void resetColor(color_type color); + void colorExterior(color_type color); + void colorTwins(color_type color); + void colorColinear(color_type color, double degree); + + template + T* create(int index) { + return new T(vd, index); + } + + double getScale() const { return vd->getScale(); } + void setScale(double scale) { vd->setScale(scale); } + + private: + Base::Reference vd; + friend class VoronoiPy; + void colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue); + }; + +} //namespace Path + +#endif // PATH_VORONOI_H diff --git a/src/Mod/Path/App/VoronoiCell.cpp b/src/Mod/Path/App/VoronoiCell.cpp new file mode 100644 index 0000000000..9fef6eca9b --- /dev/null +++ b/src/Mod/Path/App/VoronoiCell.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include "Voronoi.h" +#include "VoronoiCell.h" + +using namespace Base; +using namespace Path; + +TYPESYSTEM_SOURCE(Path::VoronoiCell , Base::Persistence) + +VoronoiCell::VoronoiCell(Voronoi::diagram_type *d, long index) + : dia(d) + , index(index) + , ptr(0) +{ + if (dia && long(dia->num_cells()) > index) { + ptr = &(dia->cells()[index]); + } +} + +VoronoiCell::VoronoiCell(Voronoi::diagram_type *d, const Voronoi::diagram_type::cell_type *e) + : dia(d) + , index(Voronoi::InvalidIndex) + , ptr(e) +{ + if (d && e) { + index = dia->index(e); + } +} + +VoronoiCell::~VoronoiCell() { +} + +bool VoronoiCell::isBound(void) const { + if (ptr != 0 && dia.isValid() && index != Voronoi::InvalidIndex) { + if (&(dia->cells()[index]) == ptr) { + return true; + } + } + ptr = 0; + return false; +} + +Voronoi::point_type VoronoiCell::sourcePoint() const { + int index = ptr->source_index(); + int category = ptr->source_category(); + if (category == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + return dia->points[index]; + } + if (category == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + return low(dia->segments[index - dia->points.size()]); + } else { + return high(dia->segments[index - dia->points.size()]); + } +} + +Voronoi::segment_type VoronoiCell::sourceSegment() const { + return dia->segments[ptr->source_index() - dia->points.size()]; +} + diff --git a/src/Mod/Path/App/VoronoiCell.h b/src/Mod/Path/App/VoronoiCell.h new file mode 100644 index 0000000000..743538c71c --- /dev/null +++ b/src/Mod/Path/App/VoronoiCell.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ +#ifndef PATH_VORONOICELL_H +#define PATH_VORONOICELL_H + +#include +#include +#include +#include +#include "Voronoi.h" + +namespace Path +{ + +class Voronoi; + +class PathExport VoronoiCell + : public Base::BaseClass +{ + TYPESYSTEM_HEADER(); +public: + + VoronoiCell(Voronoi::diagram_type *dia = 0, long index = Voronoi::InvalidIndex); + VoronoiCell(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell); + ~VoronoiCell(); + + bool isBound(void) const; + + Voronoi::point_type sourcePoint() const; + Voronoi::segment_type sourceSegment() const; + + Base::Reference dia; + long index; + mutable const Voronoi::diagram_type::cell_type *ptr; +}; + +} +#endif diff --git a/src/Mod/Path/App/VoronoiCellPy.xml b/src/Mod/Path/App/VoronoiCellPy.xml new file mode 100644 index 0000000000..6bce1f0b0c --- /dev/null +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -0,0 +1,70 @@ + + + + + + Cell of a Voronoi diagram + + + + Internal id of the element. + + + + + + Assigned color of the receiver. + + + + + + Returns the index of the cell's source + + + + + + Returns the index of the cell's source + + + + + + Incident edge of the cell - if exists + + + + + + Returns true if the cell contains a point site + + + + + Returns true if the cell contains a segment site + + + + + Returns true if the cell doesn't have an incident edge + + + + + Returns the Source for the cell + + + + diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp new file mode 100644 index 0000000000..8e780f2d57 --- /dev/null +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -0,0 +1,209 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + + +#ifndef _PreComp_ +# include +#endif + +#include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/VoronoiCell.h" +#include "Mod/Path/App/VoronoiCellPy.h" +#include "Mod/Path/App/VoronoiEdge.h" +#include "Mod/Path/App/VoronoiEdgePy.h" +#include +#include +#include +#include +#include + +// files generated out of VoronoiCellPy.xml +#include "VoronoiCellPy.cpp" + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string VoronoiCellPy::representation(void) const +{ + std::stringstream ss; + ss.precision(5); + ss << "VoronoiCell("; + VoronoiCell *c = getVoronoiCellPtr(); + if (c->isBound()) { + ss << c->ptr->source_category() << ":" << c->ptr->source_index(); + } + ss << ")"; + return ss.str(); +} + +PyObject *VoronoiCellPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of VoronoiCellPy and the Twin object + return new VoronoiCellPy(new VoronoiCell); +} + +// constructor method +int VoronoiCellPy::PyInit(PyObject* args, PyObject* /*kwd*/) +{ + if (!PyArg_ParseTuple(args, "")) { + PyErr_SetString(PyExc_RuntimeError, "no arguments accepted"); + return -1; + } + return 0; +} + + +PyObject* VoronoiCellPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { + PyObject *cmp = Py_False; + if ( PyObject_TypeCheck(lhs, &VoronoiCellPy::Type) + && PyObject_TypeCheck(rhs, &VoronoiCellPy::Type) + && op == Py_EQ) { + const VoronoiCell *vl = static_cast(lhs)->getVoronoiCellPtr(); + const VoronoiCell *vr = static_cast(rhs)->getVoronoiCellPtr(); + if (vl->index == vr->index && vl->dia == vr->dia) { + cmp = Py_True; + } + } + Py_INCREF(cmp); + return cmp; +} + +const Voronoi::voronoi_diagram_type::cell_type* getCellFromPy(VoronoiCellPy *c, bool throwIfNotBound = true) { + auto self = c->getVoronoiCellPtr(); + if (self->isBound()) { + return self->ptr; + } + if (throwIfNotBound) { + throw Py::TypeError("Cell not bound to voronoi diagram"); + } + return 0; +} + +VoronoiCell* getVoronoiCellFromPy(const VoronoiCellPy *c, PyObject *args = 0) { + VoronoiCell *self = c->getVoronoiCellPtr(); + if (!self->isBound()) { + throw Py::TypeError("Cell not bound to voronoi diagram"); + } + if (args && !PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("No arguments accepted"); + } + return self; +} + +Py::Long VoronoiCellPy::getIndex(void) const { + VoronoiCell *c = getVoronoiCellPtr(); + if (c->isBound()) { + return Py::Long(c->dia->index(c->ptr)); + } + return Py::Long(-1); +} + +Py::Long VoronoiCellPy::getColor(void) const { + VoronoiCell *c = getVoronoiCellPtr(); + if (c->isBound()) { + return Py::Long(c->ptr->color() & Voronoi::ColorMask); + } + return Py::Long(0); +} + +void VoronoiCellPy::setColor(Py::Long color) { + getCellFromPy(this)->color(long(color) & Voronoi::ColorMask); +} + +Py::Long VoronoiCellPy::getSourceIndex(void) const +{ + VoronoiCell *c = getVoronoiCellFromPy(this); + return Py::Long(c->ptr->source_index()); +} + +Py::Int VoronoiCellPy::getSourceCategory(void) const +{ + VoronoiCell *c = getVoronoiCellFromPy(this); + return Py::Int(c->ptr->source_category()); +} + +Py::Object VoronoiCellPy::getIncidentEdge(void) const +{ + VoronoiCell *c = getVoronoiCellFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(c->dia, c->ptr->incident_edge()))); +} + +PyObject* VoronoiCellPy::containsPoint(PyObject *args) +{ + VoronoiCell *c = getVoronoiCellFromPy(this, args); + PyObject *chk = c->ptr->contains_point() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiCellPy::containsSegment(PyObject *args) +{ + VoronoiCell *c = getVoronoiCellFromPy(this, args); + PyObject *chk = c->ptr->contains_segment() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiCellPy::isDegenerate(PyObject *args) +{ + VoronoiCell *c = getVoronoiCellFromPy(this, args); + PyObject *chk = c->ptr->is_degenerate() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiCellPy::getSource(PyObject *args) +{ + double z = 0; + if (!PyArg_ParseTuple(args, "|d", &z)) { + throw Py::TypeError("Optional z argument (double) accepted"); + } + + VoronoiCell *c = getVoronoiCellFromPy(this); + if (c->ptr->contains_point()) { + Base::Vector3d v = c->dia->scaledVector(c->dia->retrievePoint(c->ptr), z); + return new Base::VectorPy(new Base::Vector3d(v)); + } + Voronoi::segment_type s = c->dia->retrieveSegment(c->ptr); + Base::Vector3d v0 = c->dia->scaledVector(low(s), z); + Base::Vector3d v1 = c->dia->scaledVector(high(s), z); + Py::List list; + list.append(Py::asObject(new Base::VectorPy(new Base::Vector3d(v0)))); + list.append(Py::asObject(new Base::VectorPy(new Base::Vector3d(v1)))); + return Py::new_reference_to(list); +} + + +// custom attributes get/set + +PyObject* VoronoiCellPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int VoronoiCellPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + diff --git a/src/Mod/Path/App/VoronoiEdge.cpp b/src/Mod/Path/App/VoronoiEdge.cpp new file mode 100644 index 0000000000..a0649d7af3 --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdge.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include "Voronoi.h" +#include "VoronoiEdge.h" + +using namespace Base; +using namespace Path; + +TYPESYSTEM_SOURCE(Path::VoronoiEdge , Base::Persistence) + +VoronoiEdge::VoronoiEdge(Voronoi::diagram_type *d, long index) + : dia(d) + , index(index) + , ptr(0) +{ + if (dia && long(dia->num_edges()) > index) { + ptr = &(dia->edges()[index]); + } +} + +VoronoiEdge::VoronoiEdge(Voronoi::diagram_type *d, const Voronoi::diagram_type::edge_type *e) + : dia(d) + , index(Voronoi::InvalidIndex) + , ptr(e) +{ + if (d && e) { + index = dia->index(e); + } +} + +VoronoiEdge::~VoronoiEdge() { +} + +bool VoronoiEdge::isBound(void) const { + if (ptr != 0 && dia.isValid() && index != Voronoi::InvalidIndex) { + if (&(dia->edges()[index]) == ptr) { + return true; + } + } + ptr = 0; + return false; +} diff --git a/src/Mod/Path/App/VoronoiEdge.h b/src/Mod/Path/App/VoronoiEdge.h new file mode 100644 index 0000000000..756d7c6542 --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdge.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ +#ifndef PATH_VORONOIEDGE_H +#define PATH_VORONOIEDGE_H + +#include +#include +#include +#include +#include "Voronoi.h" + +namespace Path +{ + +class Voronoi; + +class PathExport VoronoiEdge + : public Base::BaseClass +{ + TYPESYSTEM_HEADER(); +public: + + VoronoiEdge(Voronoi::diagram_type *dia = 0, long index = Voronoi::InvalidIndex); + VoronoiEdge(Voronoi::diagram_type *dia, const Voronoi::diagram_type::edge_type *edge); + ~VoronoiEdge(); + + bool isBound(void) const; + + Base::Reference dia; + long index; + mutable const Voronoi::diagram_type::edge_type *ptr; +}; + +} +#endif diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml new file mode 100644 index 0000000000..08324ff2ff --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -0,0 +1,119 @@ + + + + + + Edge of a Voronoi diagram + + + + Internal id of the element. + + + + + + Assigned color of the receiver. + + + + + + cell the edge belongs to + + + + + + Begin and End voronoi vertex + + + + + + CCW next edge whithin voronoi cell + + + + + + CCW previous edge whithin voronoi cell + + + + + + Rotated CCW next edge whithin voronoi cell + + + + + + Rotated CCW previous edge whithin voronoi cell + + + + + + Twin edge + + + + + + Returns true if both vertices are finite + + + + + Returns true if the end vertex is infinite + + + + + Returns true if edge is straight + + + + + Returns true if edge is curved + + + + + Returns false if edge goes through endpoint of the segment site + + + + + Returns true if edge goes through endpoint of the segment site + + + + + Returns a geom representation of the edge (line segment or arc of parabola) + + + + + Returns the distance of the vertices to the input source + + + + + Returns the angle (in degree) of the segments if the edge was formed by two segments + + + + diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp new file mode 100644 index 0000000000..6043754b60 --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -0,0 +1,531 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + + +#ifndef _PreComp_ +# include +#endif + +#include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/VoronoiCell.h" +#include "Mod/Path/App/VoronoiCellPy.h" +#include "Mod/Path/App/VoronoiEdge.h" +#include "Mod/Path/App/VoronoiEdgePy.h" +#include "Mod/Path/App/VoronoiVertex.h" +#include "Mod/Path/App/VoronoiVertexPy.h" +#include +#include +#include +#include +#include +#include +#include + +// files generated out of VoronoiEdgePy.xml +#include "VoronoiEdgePy.cpp" + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string VoronoiEdgePy::representation(void) const +{ + std::stringstream ss; + ss.precision(5); + ss << "VoronoiEdge("; + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + const Voronoi::diagram_type::vertex_type *v0 = e->ptr->vertex0(); + const Voronoi::diagram_type::vertex_type *v1 = e->ptr->vertex1(); + if (v0) { + ss << "[" << (v0->x() / e->dia->getScale()) << ", " << (v0->y() / e->dia->getScale()) << "]"; + } else { + ss << "[~]"; + } + ss << ", "; + if (v1) { + ss << "[" << (v1->x() / e->dia->getScale()) << ", " << (v1->y() / e->dia->getScale()) << "]"; + } else { + ss << "[~]"; + } + } + ss << ")"; + return ss.str(); +} + +PyObject *VoronoiEdgePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of VoronoiEdgePy and the Twin object + return new VoronoiEdgePy(new VoronoiEdge); +} + +// constructor method +int VoronoiEdgePy::PyInit(PyObject* args, PyObject* /*kwd*/) +{ + if (!PyArg_ParseTuple(args, "")) { + PyErr_SetString(PyExc_RuntimeError, "no arguments accepted"); + return -1; + } + return 0; +} + + +PyObject* VoronoiEdgePy::richCompare(PyObject *lhs, PyObject *rhs, int op) { + PyObject *cmp = Py_False; + if ( PyObject_TypeCheck(lhs, &VoronoiEdgePy::Type) + && PyObject_TypeCheck(rhs, &VoronoiEdgePy::Type) + && op == Py_EQ) { + const VoronoiEdge *vl = static_cast(lhs)->getVoronoiEdgePtr(); + const VoronoiEdge *vr = static_cast(rhs)->getVoronoiEdgePtr(); + if (vl->index == vr->index && vl->dia == vr->dia) { + cmp = Py_True; + } else { + std::cerr << "VoronoiEdge==(" << vl->index << " != " << vr->index << " || " << (vl->dia == vr->dia) << ")" << std::endl; + } + } + Py_INCREF(cmp); + return cmp; +} + +const Voronoi::voronoi_diagram_type::edge_type* getEdgeFromPy(VoronoiEdgePy *e, bool throwIfNotBound = true) { + auto self = e->getVoronoiEdgePtr(); + if (self->isBound()) { + return self->ptr; + } + if (throwIfNotBound) { + throw Py::TypeError("Edge not bound to voronoi diagram"); + } + return 0; +} + +VoronoiEdge* getVoronoiEdgeFromPy(const VoronoiEdgePy *e, PyObject *args = 0) { + VoronoiEdge *self = e->getVoronoiEdgePtr(); + if (!self->isBound()) { + throw Py::TypeError("Edge not bound to voronoi diagram"); + } + if (args && !PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("No arguments accepted"); + } + return self; +} + +Py::Long VoronoiEdgePy::getIndex(void) const { + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + return Py::Long(e->dia->index(e->ptr)); + } + return Py::Long(-1); +} + +Py::Long VoronoiEdgePy::getColor(void) const { + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + return Py::Long(e->ptr->color() & Voronoi::ColorMask); + } + return Py::Long(0); +} + +void VoronoiEdgePy::setColor(Py::Long color) { + getEdgeFromPy(this)->color(long(color) & Voronoi::ColorMask); +} + +Py::List VoronoiEdgePy::getVertices(void) const +{ + Py::List list; + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + auto v0 = e->ptr->vertex0(); + auto v1 = e->ptr->vertex1(); + if (v0) { + list.append(Py::asObject(new VoronoiVertexPy(new VoronoiVertex(e->dia, v0)))); + } else { + Py_INCREF(Py_None); + list.append(Py::asObject(Py_None)); + } + if (v1) { + list.append(Py::asObject(new VoronoiVertexPy(new VoronoiVertex(e->dia, v1)))); + } else { + Py_INCREF(Py_None); + list.append(Py::asObject(Py_None)); + } + } + return list; +} + +Py::Object VoronoiEdgePy::getTwin(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->twin()))); +} + +Py::Object VoronoiEdgePy::getNext(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->next()))); +} + +Py::Object VoronoiEdgePy::getPrev(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->prev()))); +} + +Py::Object VoronoiEdgePy::getRotNext(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->rot_next()))); +} + +Py::Object VoronoiEdgePy::getRotPrev(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->rot_prev()))); +} + +Py::Object VoronoiEdgePy::getCell(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiCellPy(new VoronoiCell(e->dia, e->ptr->cell()))); +} + + +PyObject* VoronoiEdgePy::isFinite(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_finite() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiEdgePy::isInfinite(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_infinite() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiEdgePy::isLinear(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_linear() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiEdgePy::isCurved(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_curved() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiEdgePy::isPrimary(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_primary() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +PyObject* VoronoiEdgePy::isSecondary(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + PyObject *chk = e->ptr->is_secondary() ? Py_True : Py_False; + Py_INCREF(chk); + return chk; +} + +namespace { + Voronoi::point_type orthognalProjection(const Voronoi::point_type &point, const Voronoi::segment_type &segment) { + // move segment so it goes through the origin (s) + Voronoi::point_type offset; + { + offset.x(low(segment).x()); + offset.y(low(segment).y()); + } + Voronoi::point_type s; + { + s.x(high(segment).x() - offset.x()); + s.y(high(segment).y() - offset.y()); + } + // move point accordingly so it maintains it's relation to s (p) + Voronoi::point_type p; + { + p.x(point.x() - offset.x()); + p.y(point.y() - offset.y()); + } + // calculate the orthogonal projection of p onto s + // ((p dot s) / (s dot s)) * s (https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line) + // and it back by original offset to get the projected point + double proj = (p.x() * s.x() + p.y() * s.y()) / (s.x() * s.x() + s.y() * s.y()); + Voronoi::point_type pt; + { + pt.x(offset.x() + proj * s.x()); + pt.y(offset.y() + proj * s.y()); + } + return pt; + } +} + +PyObject* VoronoiEdgePy::toGeom(PyObject *args) +{ + double z = 0.0; + if (!PyArg_ParseTuple(args, "|d", &z)) { + throw Py::RuntimeError("single argument of type double accepted"); + } + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + if (e->ptr->is_linear()) { + if (e->ptr->is_finite()) { + auto v0 = e->ptr->vertex0(); + auto v1 = e->ptr->vertex1(); + if (v0 && v1) { + auto p = new Part::GeomLineSegment; + p->setPoints(e->dia->scaledVector(*v0, z), e->dia->scaledVector(*v1, z)); + return new Part::LineSegmentPy(p); + } + } else { + // infinite linear, need to clip somehow + const Voronoi::diagram_type::cell_type *c0 = e->ptr->cell(); + const Voronoi::diagram_type::cell_type *c1 = e->ptr->twin()->cell(); + Voronoi::point_type origin; + Voronoi::point_type direction; + if (c0->contains_point() && c1->contains_point()) { + Voronoi::point_type p0 = e->dia->retrievePoint(c0); + Voronoi::point_type p1 = e->dia->retrievePoint(c1); + origin.x((p0.x() + p1.x()) / 2.); + origin.y((p0.y() + p1.y()) / 2.); + direction.x(p0.y() - p1.y()); + direction.y(p1.x() - p0.x()); + } else { + origin = c0->contains_segment() ? e->dia->retrievePoint(c1) : e->dia->retrievePoint(c0); + Voronoi::segment_type segment = c0->contains_segment() ? e->dia->retrieveSegment(c0) : e->dia->retrieveSegment(c1); + Voronoi::coordinate_type dx = high(segment).x() - low(segment).x(); + Voronoi::coordinate_type dy = high(segment).y() - low(segment).y(); + if ((low(segment) == origin) ^ c0->contains_point()) { + direction.x(dy); + direction.y(-dx); + } else { + direction.x(-dy); + direction.y(dx); + } + } + double k = 10.0; // <-- need something smarter here + Voronoi::point_type begin; + Voronoi::point_type end; + if (e->ptr->vertex0()) { + begin.x(e->ptr->vertex0()->x()); + begin.y(e->ptr->vertex0()->y()); + } else { + begin.x(origin.x() - direction.x() * k); + begin.y(origin.y() - direction.y() * k); + } + if (e->ptr->vertex1()) { + end.x(e->ptr->vertex1()->x()); + end.y(e->ptr->vertex1()->y()); + } else { + end.x(origin.x() + direction.x() * k); + end.y(origin.y() + direction.y() * k); + } + auto p = new Part::GeomLineSegment; + p->setPoints(e->dia->scaledVector(begin, z), e->dia->scaledVector(end, z)); + return new Part::LineSegmentPy(p); + } + } else { + // parabolic curve, which is always formed by a point and an edge + Voronoi::point_type point = e->ptr->cell()->contains_point() ? e->dia->retrievePoint(e->ptr->cell()) : e->dia->retrievePoint(e->ptr->twin()->cell()); + Voronoi::segment_type segment = e->ptr->cell()->contains_point() ? e->dia->retrieveSegment(e->ptr->twin()->cell()) : e->dia->retrieveSegment(e->ptr->cell()); + // the location is the mid point betwenn the normal on the segment through point + // this is only the mid point of the segment if the parabola is symmetric + Voronoi::point_type loc; + { + Voronoi::point_type proj = orthognalProjection(point, segment); + // the location is the mid point between the projection on the segment and the point + loc.x((proj.x() + point.x()) / 2); + loc.y((proj.y() + point.y()) / 2); + } + Voronoi::point_type axis; + { + axis.x(point.x() - loc.x()); + axis.y(point.y() - loc.y()); + } + auto p = new Part::GeomParabola; + { + p->setCenter(e->dia->scaledVector(point, z)); + p->setLocation(e->dia->scaledVector(loc, z)); + p->setAngleXU(atan2(axis.y(), axis.x())); + p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y()) / e->dia->getScale()); + } + auto a = new Part::GeomArcOfParabola; + { + a->setHandle(Handle(Geom_Parabola)::DownCast(p->handle())); + + // figure out the arc parameters + auto v0 = e->ptr->vertex0(); + auto v1 = e->ptr->vertex1(); + double param0 = 0; + double param1 = 0; + if (!p->closestParameter(e->dia->scaledVector(*v0, z), param0)) { + std::cerr << "closestParameter(v0) failed" << std::endl; + } + if (!p->closestParameter(e->dia->scaledVector(*v1, z), param1)) { + std::cerr << "closestParameter(v0) failed" << std::endl; + } + a->setRange(param0, param1, false); + } + return new Part::ArcOfParabolaPy(a); + } + } + Py_INCREF(Py_None); + return Py_None; +} + + +namespace { + + double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const Voronoi::point_type &p1, double scale) { + double x = v0.x() - p1.x(); + double y = v0.y() - p1.y(); + return sqrt(x * x + y * y) / scale; + } + + void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list, double scale) { + if (v0) { + list->append(Py::Float(distanceBetween(*v0, p1, scale))); + } else { + Py_INCREF(Py_None); + list->append(Py::asObject(Py_None)); + } + } + + void addProjectedDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::segment_type &segment, Py::List *list, double scale) { + if (v0) { + Voronoi::point_type p0; + { + p0.x(v0->x()); + p0.y(v0->y()); + } + Voronoi::point_type p1 = orthognalProjection(p0, segment); + list->append(Py::Float(distanceBetween(*v0, p1, scale))); + } else { + Py_INCREF(Py_None); + list->append(Py::asObject(Py_None)); + } + } + + bool addDistancesToPoint(const VoronoiEdge *edge, Voronoi::point_type p, Py::List *list, double scale) { + addDistanceBetween(edge->ptr->vertex0(), p, list, scale); + addDistanceBetween(edge->ptr->vertex1(), p, list, scale); + return true; + } + + bool retrieveDistances(const VoronoiEdge *edge, Py::List *list) { + const Voronoi::diagram_type::cell_type *c0 = edge->ptr->cell(); + if (c0->contains_point()) { + return addDistancesToPoint(edge, edge->dia->retrievePoint(c0), list, edge->dia->getScale()); + } + const Voronoi::diagram_type::cell_type *c1 = edge->ptr->twin()->cell(); + if (c1->contains_point()) { + return addDistancesToPoint(edge, edge->dia->retrievePoint(c1), list, edge->dia->getScale()); + } + // at this point both cells are sourced from segments and it does not matter which one we use + Voronoi::segment_type segment = edge->dia->retrieveSegment(c0); + addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list, edge->dia->getScale()); + addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list, edge->dia->getScale()); + return false; + } +} + +PyObject* VoronoiEdgePy::getDistances(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + Py::List list; + retrieveDistances(e, &list); + return Py::new_reference_to(list); +} + +std::ostream& operator<<(std::ostream &str, const Voronoi::point_type &p) { + return str << "[" << int(p.x()) << ", " << int(p.y()) << "]"; +} + +std::ostream& operator<<(std::ostream &str, const Voronoi::segment_type &s) { + return str << '<' << low(s) << '-' << high(s) << '>'; +} + +static bool pointsMatch(const Voronoi::point_type &p0, const Voronoi::point_type &p1) { + return long(p0.x()) == long(p1.x()) && long(p0.y()) == long(p1.y()); +} + +static void printCompare(const char *label, const Voronoi::point_type &p0, const Voronoi::point_type &p1) { + std::cerr << " " << label <<": " << pointsMatch(p1, p0) << pointsMatch(p0, p1) << " " << p0 << ' ' << p1 << std::endl; +} + +PyObject* VoronoiEdgePy::getSegmentAngle(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + + if (e->ptr->cell()->contains_segment() && e->ptr->twin()->cell()->contains_segment()) { + int i0 = e->ptr->cell()->source_index() - e->dia->points.size(); + int i1 = e->ptr->twin()->cell()->source_index() - e->dia->points.size(); + if (e->dia->segmentsAreConnected(i0, i1)) { + double a0 = e->dia->angleOfSegment(i0); + double a1 = e->dia->angleOfSegment(i1); + double a = a0 - a1; + if (a > M_PI_2) { + a -= M_PI; + } else if (a < -M_PI_2) { + a += M_PI; + } + return Py::new_reference_to(Py::Float(a)); + } else { + std::cerr << "indices: " << std::endl; + std::cerr << " " << e->dia->segments[i0] << std::endl; + std::cerr << " " << e->dia->segments[i1] << std::endl; + std::cerr << " connected: " << e->dia->segmentsAreConnected(i0, i1) << std::endl; + printCompare("l/l", low(e->dia->segments[i0]), low(e->dia->segments[i1])); + printCompare("l/h", low(e->dia->segments[i0]), high(e->dia->segments[i1])); + printCompare("h/l", high(e->dia->segments[i0]), low(e->dia->segments[i1])); + printCompare("h/h", high(e->dia->segments[i0]), high(e->dia->segments[i1])); + } + } else { + std::cerr << "constains_segment(" << e->ptr->cell()->contains_segment() << ", " << e->ptr->twin()->cell()->contains_segment() << ")" << std::endl; + } + Py_INCREF(Py_None); + return Py_None; +} + +// custom attributes get/set + +PyObject* VoronoiEdgePy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int VoronoiEdgePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml new file mode 100644 index 0000000000..d330d6ccca --- /dev/null +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -0,0 +1,107 @@ + + + + + + Voronoi([segments]): Create voronoi for given collection of line segments + + + + List of all cells of the voronoi diagram + + + + + + List of all edges of the voronoi diagram + + + + + + List of all vertices of the voronoi diagram + + + + + + Return number of cells + + + + + Return number of edges + + + + + Return number of vertices + + + + + addPoint(vector|vector2d) add given point to input collection + + + + + addSegment(vector|vector2d, vector|vector2d) add given segment to input collection + + + + + constructs the voronoi diagram from the input collections + + + + + assign given color to all exterior edges and vertices + + + + + assign given color to all twins of edges (which one is considered a twin is arbitrary) + + + + + assign given color to all edges sourced by two segments almost in line with each other (optional angle in degrees) + + + + + assign color 0 to all elements with the given color + + + + + Get list of all input points. + + + + + Return number of input points + + + + + Get list of all input segments. + + + + + Return number of input segments + + + + diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp new file mode 100644 index 0000000000..474d0e7da1 --- /dev/null +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -0,0 +1,346 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + + +#ifndef _PreComp_ +# include +#endif + +#include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/VoronoiCell.h" +#include "Mod/Path/App/VoronoiEdge.h" +#include "Mod/Path/App/VoronoiVertex.h" +#include +#include +#include +#include + +// files generated out of VoronoiPy.xml +#include "VoronoiPy.h" +#include "VoronoiPy.cpp" +#include "VoronoiCellPy.h" +#include "VoronoiEdgePy.h" +#include "VoronoiVertexPy.h" + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string VoronoiPy::representation(void) const +{ + std::stringstream ss; + ss.precision(5); + ss << "Voronoi(" + << "{" << getVoronoiPtr()->numSegments() << ", " << getVoronoiPtr()->numPoints() << "}" + << " -> " + << "{" << getVoronoiPtr()->numCells() << ", " << getVoronoiPtr()->numEdges() << ", " << getVoronoiPtr()->numVertices() << "}" + << ")"; + return ss.str(); +} + + +PyObject *VoronoiPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of VoronoiPy and its twin object + return new VoronoiPy(new Voronoi); +} + +// constructor +int VoronoiPy::PyInit(PyObject* args, PyObject* /*kwds*/) +{ + Voronoi *vo = getVoronoiPtr(); + double scale = vo->getScale(); + if (!PyArg_ParseTuple(args, "|d", &scale)) { + PyErr_SetString(PyExc_RuntimeError, "scale argument (double) accepted, default = 1000"); + return -1; + } + vo->setScale(scale); + return 0; +} + +Voronoi::point_type getPointFromPy(PyObject *obj) { + if (obj) { + if (PyObject_TypeCheck(obj, &Base::VectorPy::Type)) { + Base::Vector3d *vect = (static_cast(obj))->getVectorPtr(); + return Voronoi::point_type(vect->x, vect->y); + } else if (PyObject_TypeCheck(obj, Base::Vector2dPy::type_object())) { + Base::Vector2d vect = Py::toVector2d(obj); + return Voronoi::point_type(vect.x, vect.y); + } + } + throw Py::TypeError("Points must be Base::Vector or Base::Vector2d"); + return Voronoi::point_type(); +} + +PyObject* VoronoiPy::addPoint(PyObject *args) { + PyObject *obj = 0; + if (PyArg_ParseTuple(args, "O", &obj)) { + getVoronoiPtr()->addPoint(getPointFromPy(obj)); + } + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::addSegment(PyObject *args) { + PyObject *objBegin = 0; + PyObject *objEnd = 0; + + if (PyArg_ParseTuple(args, "OO", &objBegin, &objEnd)) { + auto p0 = getPointFromPy(objBegin); + auto p1 = getPointFromPy(objEnd); + getVoronoiPtr()->addSegment(Voronoi::segment_type(p0, p1)); + } + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::construct(PyObject *args) { + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + getVoronoiPtr()->construct(); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::numCells(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + return PyLong_FromLong(getVoronoiPtr()->numCells()); +} + +PyObject* VoronoiPy::numEdges(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + return PyLong_FromLong(getVoronoiPtr()->numEdges()); +} + +PyObject* VoronoiPy::numVertices(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + return PyLong_FromLong(getVoronoiPtr()->numVertices()); +} + +Py::List VoronoiPy::getVertices(void) const { + Py::List list; + for (int i=0; inumVertices(); ++i) { + list.append(Py::asObject(new VoronoiVertexPy(getVoronoiPtr()->create(i)))); + } + return list; +} + +Py::List VoronoiPy::getEdges(void) const { + Py::List list; + for (int i=0; inumEdges(); ++i) { + list.append(Py::asObject(new VoronoiEdgePy(getVoronoiPtr()->create(i)))); + } + return list; +} + +Py::List VoronoiPy::getCells(void) const { + Py::List list; + for (int i=0; inumCells(); ++i) { + list.append(Py::asObject(new VoronoiCellPy(getVoronoiPtr()->create(i)))); + } + return list; +} + +typedef std::map exterior_map_t; +typedef std::map > coordinate_map_t; + +#define VORONOI_USE_EXTERIOR_CACHE 1 + +static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &bail, exterior_map_t &cache) { + bool rc = false; + if (!bail && v->color() == 0) { +#if VORONOI_USE_EXTERIOR_CACHE + auto it = cache.find(uintptr_t(v)); + if (it == cache.end()) { +#endif + PyObject *vx = new VoronoiVertexPy(new VoronoiVertex(dia, v)); + PyObject *arglist = Py_BuildValue("(O)", vx); + PyObject *result = PyEval_CallObject(callback, arglist); + Py_DECREF(arglist); + Py_DECREF(vx); + if (result == NULL) { + bail = true; + } else { + rc = result == Py_True; + Py_DECREF(result); + cache.insert(exterior_map_t::value_type(uintptr_t(v), rc)); + } +#if VORONOI_USE_EXTERIOR_CACHE + } else { + rc = it->second; + } +#else + (void)cache; +#endif + } + return rc; +} + +PyObject* VoronoiPy::colorExterior(PyObject *args) { + Voronoi::color_type color = 0; + PyObject *callback = 0; + if (!PyArg_ParseTuple(args, "k|O", &color, &callback)) { + throw Py::RuntimeError("colorExterior requires an integer (color) argument"); + } + Voronoi *vo = getVoronoiPtr(); + vo->colorExterior(color); + if (callback) { + exterior_map_t cache; + coordinate_map_t pts; + for (auto e = vo->vd->edges().begin(); e != vo->vd->edges().end(); ++e) { + if (e->is_finite() && e->color() == 0) { + const Voronoi::diagram_type::vertex_type *v0 = e->vertex0(); + const Voronoi::diagram_type::vertex_type *v1 = e->vertex1(); + bool bail = false; + if (callbackWithVertex(vo->vd, callback, v0, bail, cache) && callbackWithVertex(vo->vd, callback, v1, bail, cache)) { + vo->colorExterior(&(*e), color); + } else if (!bail && callbackWithVertex(vo->vd, callback, v1, bail, cache)) { + if (pts.empty()) { + for (auto s = vo->vd->segments.begin(); s != vo->vd->segments.end(); ++s) { + pts[low(*s).x()].insert(low(*s).y()); + pts[high(*s).x()].insert(high(*s).y()); + } + } + auto ys = pts.find(int32_t(v0->x())); + if (ys != pts.end() && ys->second.find(v0->y()) != ys->second.end()) { + vo->colorExterior(&(*e), color); + } + } + if (bail) { + return NULL; + } + } + } + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::colorTwins(PyObject *args) { + Voronoi::color_type color = 0; + if (!PyArg_ParseTuple(args, "k", &color)) { + throw Py::RuntimeError("colorTwins requires an integer (color) argument"); + } + getVoronoiPtr()->colorTwins(color); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::colorColinear(PyObject *args) { + Voronoi::color_type color = 0; + double degree = 10.; + if (!PyArg_ParseTuple(args, "k|d", &color, °ree)) { + throw Py::RuntimeError("colorColinear requires an integer (color) and optionally a derivation in degrees argument (default 10)"); + } + getVoronoiPtr()->colorColinear(color, degree); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::resetColor(PyObject *args) { + Voronoi::color_type color = 0; + if (!PyArg_ParseTuple(args, "k", &color)) { + throw Py::RuntimeError("clearColor requires an integer (color) argument"); + } + + getVoronoiPtr()->resetColor(color); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::getPoints(PyObject *args) { + double z = 0; + if (!PyArg_ParseTuple(args, "|d", &z)) { + throw Py::RuntimeError("Optional z argument (double) accepted"); + } + Voronoi *vo = getVoronoiPtr(); + Py::List list; + for (auto it = vo->vd->points.begin(); it != vo->vd->points.end(); ++it) { + list.append(Py::asObject(new Base::VectorPy(new Base::Vector3d(vo->vd->scaledVector(*it, z))))); + } + return Py::new_reference_to(list); +} + +PyObject* VoronoiPy::getSegments(PyObject *args) { + double z = 0; + if (!PyArg_ParseTuple(args, "|d", &z)) { + throw Py::RuntimeError("Optional z argument (double) accepted"); + } + Voronoi *vo = getVoronoiPtr(); + Py::List list; + for (auto it = vo->vd->segments.begin(); it != vo->vd->segments.end(); ++it) { + PyObject *p0 = new Base::VectorPy(new Base::Vector3d(vo->vd->scaledVector(low(*it), z))); + PyObject *p1 = new Base::VectorPy(new Base::Vector3d(vo->vd->scaledVector(high(*it), z))); + PyObject *tp = PyTuple_New(2); + PyTuple_SetItem(tp, 0, p0); + PyTuple_SetItem(tp, 1, p1); + list.append(Py::asObject(tp)); + } + return Py::new_reference_to(list); +} + +PyObject* VoronoiPy::numPoints(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + return PyLong_FromLong(getVoronoiPtr()->vd->points.size()); +} + +PyObject* VoronoiPy::numSegments(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("no arguments accepted"); + } + return PyLong_FromLong(getVoronoiPtr()->vd->segments.size()); +} + + +// custom attributes get/set + +PyObject *VoronoiPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int VoronoiPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + + diff --git a/src/Mod/Path/App/VoronoiVertex.cpp b/src/Mod/Path/App/VoronoiVertex.cpp new file mode 100644 index 0000000000..c91935b5ca --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertex.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include "Voronoi.h" +#include "VoronoiVertex.h" + +using namespace Base; +using namespace Path; + +TYPESYSTEM_SOURCE(Path::VoronoiVertex , Base::Persistence) + +VoronoiVertex::VoronoiVertex(Voronoi::diagram_type *d, long index) + : dia(d) + , index(index) + , ptr(0) +{ + if (dia && long(dia->num_vertices()) > index) { + ptr = &(dia->vertices()[index]); + } +} + +VoronoiVertex::VoronoiVertex(Voronoi::diagram_type *d, const Voronoi::diagram_type::vertex_type *v) + : dia(d) + , index(Voronoi::InvalidIndex) + , ptr(v) +{ + if (dia && v) { + index = dia->index(v); + } +} + +VoronoiVertex::~VoronoiVertex() { +} + +bool VoronoiVertex::isBound(void) const { + if (ptr != 0 && dia.isValid() && index != Voronoi::InvalidIndex) { + if (&(dia->vertices()[index]) == ptr) { + return true; + } + } + ptr = 0; + return false; +} diff --git a/src/Mod/Path/App/VoronoiVertex.h b/src/Mod/Path/App/VoronoiVertex.h new file mode 100644 index 0000000000..9393f331cc --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertex.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ +#ifndef PATH_VORONOIVERTEX_H +#define PATH_VORONOIVERTEX_H + +#include +#include +#include +#include +#include "Voronoi.h" + +namespace Path +{ + +class Voronoi; + +class PathExport VoronoiVertex + : public Base::BaseClass +{ + TYPESYSTEM_HEADER(); +public: + + VoronoiVertex(Voronoi::diagram_type *dia = 0, long index = Voronoi::InvalidIndex); + VoronoiVertex(Voronoi::diagram_type *dia, const Voronoi::diagram_type::vertex_type *v); + ~VoronoiVertex(); + + bool isBound(void) const; + + Base::Reference dia; + long index; + mutable const Voronoi::diagram_type::vertex_type *ptr; +}; + +} +#endif diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml new file mode 100644 index 0000000000..7cdd517b8b --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -0,0 +1,55 @@ + + + + + + Vertex of a Voronoi diagram + + + + Internal id of the element. + + + + + + Assigned color of the receiver. + + + + + + X position + + + + + + Y position + + + + + + Y position + + + + + + Returns a Vertex - or None if not possible + + + + diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp new file mode 100644 index 0000000000..837c2efea4 --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + * Copyright (c) sliptonic (shopinthewoods@gmail.com) 2020 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + + +#ifndef _PreComp_ +# include +#endif + +#include "Voronoi.h" +#include "VoronoiPy.h" +#include "VoronoiEdge.h" +#include "VoronoiEdgePy.h" +#include "VoronoiVertex.h" +#include "VoronoiVertexPy.h" +#include +#include +#include +#include +#include + +// files generated out of VoronoiVertexPy.xml +#include "VoronoiVertexPy.cpp" + +using namespace Path; + +// returns a string which represents the object e.g. when printed in python +std::string VoronoiVertexPy::representation(void) const +{ + std::stringstream ss; + ss.precision(5); + ss << "VoronoiVertex("; + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + ss << "[" << (v->ptr->x() / v->dia->getScale()) << ", " << (v->ptr->y() / v->dia->getScale()) << "]"; + } + ss << ")"; + return ss.str(); +} + +PyObject *VoronoiVertexPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of VoronoiVertexPy and the Twin object + return new VoronoiVertexPy(new VoronoiVertex); +} + +// constructor method +int VoronoiVertexPy::PyInit(PyObject* args, PyObject* /*kwd*/) +{ + if (!PyArg_ParseTuple(args, "")) { + PyErr_SetString(PyExc_RuntimeError, "no arguments accepted"); + return -1; + } + return 0; +} + + +PyObject* VoronoiVertexPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { + PyObject *cmp = Py_False; + if ( PyObject_TypeCheck(lhs, &VoronoiVertexPy::Type) + && PyObject_TypeCheck(rhs, &VoronoiVertexPy::Type) + && op == Py_EQ) { + const VoronoiVertex *vl = static_cast(lhs)->getVoronoiVertexPtr(); + const VoronoiVertex *vr = static_cast(rhs)->getVoronoiVertexPtr(); + if (vl->index == vr->index && vl->dia == vr->dia) { + cmp = Py_True; + } + } + Py_INCREF(cmp); + return cmp; +} + +const Voronoi::voronoi_diagram_type::vertex_type* getVertexFromPy(VoronoiVertexPy *v, bool throwIfNotBound = true) { + auto self = v->getVoronoiVertexPtr(); + if (self->isBound()) { + return self->ptr; + } + if (throwIfNotBound) { + throw Py::TypeError("Vertex not bound to voronoi diagram"); + } + return 0; +} + +VoronoiVertex* getVoronoiVertexFromPy(const VoronoiVertexPy *v, PyObject *args = 0) { + VoronoiVertex *self = v->getVoronoiVertexPtr(); + if (!self->isBound()) { + throw Py::TypeError("Vertex not bound to voronoi diagram"); + } + if (args && !PyArg_ParseTuple(args, "")) { + throw Py::RuntimeError("No arguments accepted"); + } + return self; +} + + +Py::Long VoronoiVertexPy::getIndex(void) const { + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + return Py::Long(v->dia->index(v->ptr)); + } + return Py::Long(-1); +} + +Py::Long VoronoiVertexPy::getColor(void) const { + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + return Py::Long(v->ptr->color() & Voronoi::ColorMask); + } + return Py::Long(0); +} + +void VoronoiVertexPy::setColor(Py::Long color) { + getVertexFromPy(this)->color(long(color) & Voronoi::ColorMask); +} + +Py::Float VoronoiVertexPy::getX(void) const +{ + VoronoiVertex *v = getVoronoiVertexFromPy(this); + return Py::Float(v->ptr->x() / v->dia->getScale()); +} + +Py::Float VoronoiVertexPy::getY(void) const +{ + VoronoiVertex *v = getVoronoiVertexFromPy(this); + return Py::Float(v->ptr->y() / v->dia->getScale()); +} + +Py::Object VoronoiVertexPy::getIncidentEdge() const { + VoronoiVertex *v = getVoronoiVertexFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); +} + +PyObject* VoronoiVertexPy::toGeom(PyObject *args) +{ + double z = 0.0; + if (!PyArg_ParseTuple(args, "|d", &z)) { + throw Py::RuntimeError("single argument of type double accepted"); + } + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + return new Base::VectorPy(new Base::Vector3d(v->ptr->x() / v->dia->getScale(), v->ptr->y() / v->dia->getScale(), z)); + } + Py_INCREF(Py_None); + return Py_None; +} + +// custom attributes get/set + +PyObject* VoronoiVertexPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int VoronoiVertexPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index a532b2da04..451226ae21 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -125,6 +125,8 @@ SET(PathScripts_SRCS PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py + PathScripts/PathVcarve.py + PathScripts/PathVcarveGui.py PathScripts/PathWaterline.py PathScripts/PathWaterlineGui.py PathScripts/PostUtils.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 1e67d1a92f..2e0873a2be 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -66,6 +66,7 @@ icons/Path-ToolController.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg + icons/Path-Vcarve.svg icons/Path-Waterline.svg icons/arrow-ccw.svg icons/arrow-cw.svg @@ -111,6 +112,7 @@ panels/PageOpSlotEdit.ui panels/PageOpSurfaceEdit.ui panels/PageOpWaterlineEdit.ui + panels/PageOpVcarveEdit.ui panels/PathEdit.ui panels/PointEdit.ui panels/SetupGlobal.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg b/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg new file mode 100644 index 0000000000..87788da135 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-Engrave + 2016-02-24 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Engrave.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui new file mode 100644 index 0000000000..21316a699c --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -0,0 +1,127 @@ + + + Form + + + + 0 + 0 + 400 + 140 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Discretization Deflection + + + + + + + <html><head/><body><p>This value is used in discretizing arcs into segments. Smaller values will result in larger gcode. Larger values may cause unwanted segments in the medial line path.</p></body></html> + + + 3 + + + 0.001000000000000 + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.010000000000000 + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Threshold + + + + + + + <html><head/><body><p>Threshold is used by the medial axis filter to remove unwanted segments. If the resulting path contains unwanted segments, decrease this value. </p><p>Valid values are 0.0 - 1.0</p><p>Default = 0.8</p><p>1.0 will remove nothing.</p></body></html> + + + 180 + + + 10 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 6b8b545bae..86678e97fd 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -97,7 +97,7 @@ class PathWorkbench (Workbench): "Path_MillFace", "Path_Helix", "Path_Adaptive", "Path_Slot"] threedopcmdlist = ["Path_Pocket_3D"] - engravecmdlist = ["Path_Engrave", "Path_Deburr"] + engravecmdlist = ["Path_Engrave", "Path_Deburr", "Path_Vcarve"] modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"] dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index ad39fbea0c..060d1b3fe5 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -35,7 +35,6 @@ else: Processed = False - def Startup(): global Processed # pylint: disable=global-statement if not Processed: @@ -82,6 +81,7 @@ def Startup(): from PathScripts import PathToolLibraryEditor from PathScripts import PathUtilsGui # from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency + from PathScripts import PathVcarveGui Processed = True else: PathLog.debug('Skipping PathGui initialisation') diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 95456b6759..f6483f8505 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -47,6 +47,34 @@ class MESHGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument return obj.TypeId[0:4] == 'Mesh' +class VCARVEGate: + def allow(self, doc, obj, sub): + try: + shape = obj.Shape + except Exception: # pylint: disable=broad-except + return False + + if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0: + return True + + if shape.ShapeType == 'Face': + return True + + elif shape.ShapeType == 'Solid': + if sub and sub[0:4] == 'Face': + return True + + elif shape.ShapeType == 'Compound': + if sub and sub[0:4] == 'Face': + return True + + if sub: + subShape = shape.getElement(sub) + if subShape.ShapeType == 'Edge': + return False + + return False + class ENGRAVEGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument @@ -300,6 +328,10 @@ def surfaceselect(): FreeCADGui.Selection.addSelectionGate(gate) FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") +def vcarveselect(): + FreeCADGui.Selection.addSelectionGate(VCARVEGate()) + FreeCAD.Console.PrintWarning("Vcarve Select Mode\n") + def probeselect(): FreeCADGui.Selection.addSelectionGate(PROBEGate()) @@ -328,6 +360,7 @@ def select(op): opsel['Surface'] = surfaceselect opsel['Waterline'] = surfaceselect opsel['Adaptive'] = adaptiveselect + opsel['Vcarve'] = vcarveselect opsel['Probe'] = probeselect opsel['Custom'] = customselect return opsel[op] diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py new file mode 100644 index 0000000000..2ac5491d88 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * 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 Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import Part +import Path +import PathScripts.PathEngraveBase as PathEngraveBase +import PathScripts.PathLog as PathLog +import PathScripts.PathOp as PathOp +import PathScripts.PathUtils as PathUtils +import PathScripts.PathGeom as PathGeom +import PathScripts.PathPreferences as PathPreferences + +import traceback + +import math + +from PySide import QtCore + +__doc__ = "Class and implementation of Path Vcarve operation" + +PRIMARY = 0 +EXTERIOR1 = 1 +EXTERIOR2 = 4 +TWIN = 2 +COLINEAR = 3 +SECONDARY = 5 + +if True: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +VD = [] + + +class ObjectVcarve(PathEngraveBase.ObjectOp): + '''Proxy class for Vcarve operation.''' + + def opFeatures(self, obj): + '''opFeatures(obj) ... return all standard features and edges based geomtries''' + return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureBaseFaces + + def setupAdditionalProperties(self, obj): + if not hasattr(obj, 'BaseShapes'): + obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", + "Additional base objects to be engraved")) + obj.setEditorMode('BaseShapes', 2) # hide + if not hasattr(obj, 'BaseObject'): + obj.addProperty("App::PropertyLink", "BaseObject", "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", + "Additional base objects to be engraved")) + obj.setEditorMode('BaseObject', 2) # hide + + def initOperation(self, obj): + '''initOperation(obj) ... create vcarve specific properties.''' + obj.addProperty("App::PropertyFloat", "Discretize", "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", + "The deflection value for discretizing arcs")) + obj.addProperty("App::PropertyFloat", "Threshold", "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", + "cutoff for removing colinear segments (degrees). \ + default=10.0.")) + obj.addProperty("App::PropertyFloat", "Tolerance", "Path", + QtCore.QT_TRANSLATE_NOOP("PathVcarve", "")) + obj.Threshold = 10.0 + obj.Discretize = 0.01 + obj.Tolerance = PathPreferences.defaultGeometryTolerance() + self.setupAdditionalProperties(obj) + + def opOnDocumentRestored(self, obj): + # upgrade ... + self.setupAdditionalProperties(obj) + + def buildPathMedial(self, obj, Faces): + '''constructs a medial axis path using openvoronoi''' + + def insert_many_wires(vd, wires): + for wire in wires: + PathLog.debug('discretize value: {}'.format(obj.Discretize)) + pts = wire.discretize(QuasiDeflection=obj.Discretize) + ptv = [FreeCAD.Vector(p[0], p[1]) for p in pts] + ptv.append(ptv[0]) + + for i in range(len(pts)): + vd.addSegment(ptv[i], ptv[i+1]) + + def calculate_depth(MIC): + # given a maximum inscribed circle (MIC) and tool angle, + # return depth of cut. + + r = obj.ToolController.Tool.Diameter / 2 + toolangle = obj.ToolController.Tool.CuttingEdgeAngle + maxdepth = r / math.tan(math.radians(toolangle/2)) + + d = round(MIC / math.tan(math.radians(toolangle / 2)), 4) + return d if d <= maxdepth else maxdepth + + def getEdges(vd, color=[PRIMARY]): + if type(color) == int: + color = [color] + geomList = [] + for e in vd.Edges: + if e.Color not in color: + continue + if e.toGeom() is None: + continue + p1 = e.Vertices[0].toGeom(calculate_depth(0-e.getDistances()[0])) + p2 = e.Vertices[-1].toGeom(calculate_depth(0-e.getDistances()[-1])) + newedge = Part.Edge(Part.Vertex(p1), Part.Vertex(p2)) + + newedge.fixTolerance(obj.Tolerance, Part.Vertex) + geomList.append(newedge) + + return geomList + + def sortEm(mywire, unmatched): + remaining = [] + wireGrowing = False + + # end points of existing wire + wireverts = [mywire.Edges[0].valueAt(mywire.Edges[0].FirstParameter), + mywire.Edges[-1].valueAt(mywire.Edges[-1].LastParameter)] + + for i, candidate in enumerate(unmatched): + + # end points of candidate edge + cverts = [candidate.Edges[0].valueAt(candidate.Edges[0].FirstParameter), + candidate.Edges[-1].valueAt(candidate.Edges[-1].LastParameter)] + + # ignore short segments below tolerance level + if PathGeom.pointsCoincide(cverts[0], cverts[1], obj.Tolerance): + continue + + # iterate the combination of endpoints. If a match is found, + # make an edge from the common endpoint to the other end of + # the candidate wire. Add the edge to the wire and return it. + + # This generates a new edge rather than using the candidate to + # avoid vertexes with close but different vectors + for wvert in wireverts: + for idx, cvert in enumerate(cverts): + if PathGeom.pointsCoincide(wvert, cvert, obj.Tolerance): + wireGrowing = True + elist = mywire.Edges + otherIndex = int(not(idx)) + + newedge = Part.Edge(Part.Vertex(wvert), + Part.Vertex(cverts[otherIndex])) + + elist.append(newedge) + mywire = Part.Wire(Part.__sortEdges__(elist)) + remaining.extend(unmatched[i+1:]) + return mywire, remaining, wireGrowing + + # if not matched, add to remaining list to test later + remaining.append(candidate) + + return mywire, remaining, wireGrowing + + def getWires(candidateList): + + chains = [] + while len(candidateList) > 0: + cur_wire = Part.Wire(candidateList.pop(0)) + + wireGrowing = True + while wireGrowing: + cur_wire, candidateList, wireGrowing = sortEm(cur_wire, + candidateList) + + chains.append(cur_wire) + + return chains + + def cutWire(w): + path = [] + path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) + e = w.Edges[0] + p = e.valueAt(e.FirstParameter) + path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, + obj.SafeHeight.Value))) + c = Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, + obj.ToolController.HorizFeed.Value)) + path.append(c) + for e in w.Edges: + path.extend(PathGeom.cmdsForEdge(e, + hSpeed=obj.ToolController.HorizFeed.Value)) + + return path + + VD.clear() + pathlist = [] + pathlist.append(Path.Command("(starting)")) + for f in Faces: + vd = Path.Voronoi() + insert_many_wires(vd, f.Wires) + + vd.construct() + + for e in vd.Edges: + e.Color = PRIMARY if e.isPrimary() else SECONDARY + vd.colorExterior(EXTERIOR1) + vd.colorExterior(EXTERIOR2, + lambda v: not f.isInside(v.toGeom(f.BoundBox.ZMin), + obj.Tolerance, True)) + vd.colorColinear(COLINEAR, obj.Threshold) + vd.colorTwins(TWIN) + + edgelist = getEdges(vd) + + for wire in getWires(edgelist): + pathlist.extend(cutWire(wire)) + VD.append((f, vd, getWires(edgelist))) + + self.commandlist = pathlist + + def opExecute(self, obj): + '''opExecute(obj) ... process engraving operation''' + PathLog.track() + + if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): + FreeCAD.Console.PrintError( + translate("Path_Vcarve", "VCarve requires an engraving \ + cutter with CuttingEdgeAngle") + "\n") + + if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: + FreeCAD.Console.PrintError( + translate("Path_Vcarve", + "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") + return + try: + if obj.Base: + PathLog.track() + for base in obj.Base: + faces = [] + for sub in base[1]: + shape = getattr(base[0].Shape, sub) + if isinstance(shape, Part.Face): + faces.append(shape) + + modelshape = Part.makeCompound(faces) + + elif len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ + self.model[0].isDerivedFrom('Part::Part2DObject'): + PathLog.track() + + modelshape = self.model[0].Shape + self.buildPathMedial(obj, modelshape.Faces) + + except Exception as e: + PathLog.error(e) + traceback.print_exc() + PathLog.error(translate('PathVcarve', + 'The Job Base Object has no engraveable element.\ + Engraving operation will produce no output.')) + + def opUpdateDepths(self, obj, ignoreErrors=False): + '''updateDepths(obj) ... engraving is always done at \ + the top most z-value''' + job = PathUtils.findParentJob(obj) + self.opSetDefaultValues(obj, job) + + +def SetupProperties(): + return ["Discretize"] + + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Vcarve operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + ObjectVcarve(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py new file mode 100644 index 0000000000..34e2073002 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * 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 Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathVcarve as PathVcarve +import PathScripts.PathLog as PathLog +import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathUtils as PathUtils + +from PySide import QtCore, QtGui + +__title__ = "Path Vcarve Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Vcarve operation page controller and command implementation." + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): + '''Enhanced base geometry page to also allow special base objects.''' + + def super(self): + return super(TaskPanelBaseGeometryPage, self) + + def addBaseGeometry(self, selection): + added = False + shapes = self.obj.BaseShapes + for sel in selection: + job = PathUtils.findParentJob(self.obj) + base = job.Proxy.resourceClone(job, sel.Object) + if not base: + PathLog.notice((translate("Path", "%s is not a Base Model object of the job %s") + "\n") % (sel.Object.Label, job.Label)) + continue + if base in shapes: + PathLog.notice((translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label)) + continue + if base.isDerivedFrom('Part::Part2DObject'): + if sel.HasSubObjects: + # selectively add some elements of the drawing to the Base + for sub in sel.SubElementNames: + if 'Vertex' in sub: + PathLog.info(translate("Path", "Ignoring vertex")) + else: + self.obj.Proxy.addBase(self.obj, base, sub) + else: + # when adding an entire shape to BaseShapes we can take its sub shapes out of Base + self.obj.Base = [(p, el) for p, el in self.obj.Base if p != base] + shapes.append(base) + self.obj.BaseShapes = shapes + added = True + else: + # user wants us to engrave an edge of face of a base model + base = self.super().addBaseGeometry(selection) + added = added or base + + return added + + def setFields(self, obj): + self.super().setFields(obj) + self.form.baseList.blockSignals(True) + for shape in self.obj.BaseShapes: + item = QtGui.QListWidgetItem(shape.Label) + item.setData(self.super().DataObject, shape) + item.setData(self.super().DataObjectSub, None) + self.form.baseList.addItem(item) + self.form.baseList.blockSignals(False) + + def updateBase(self): + PathLog.track() + shapes = [] + for i in range(self.form.baseList.count()): + item = self.form.baseList.item(i) + obj = item.data(self.super().DataObject) + sub = item.data(self.super().DataObjectSub) + if not sub: + shapes.append(obj) + PathLog.debug("Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes)) + self.obj.BaseShapes = shapes + return self.super().updateBase() + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Vcarve operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + if obj.Discretize != self.form.discretize.value(): + obj.Discretize = self.form.discretize.value() + if obj.Threshold != self.form.threshold.value(): + obj.Threshold = self.form.threshold.value() + self.updateToolController(obj, self.form.toolController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + self.form.discretize.setValue(obj.Discretize) + self.form.threshold.setValue(obj.Threshold) + self.setupToolController(obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + signals.append(self.form.discretize.editingFinished) + signals.append(self.form.threshold.editingFinished) + signals.append(self.form.toolController.currentIndexChanged) + return signals + + def taskPanelBaseGeometryPage(self, obj, features): + '''taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.''' + return TaskPanelBaseGeometryPage(obj, features) + + +Command = PathOpGui.SetupOperation('Vcarve', PathVcarve.Create, TaskPanelOpPage, + 'Path-Vcarve', QtCore.QT_TRANSLATE_NOOP("PathVcarve", "Vcarve"), + QtCore.QT_TRANSLATE_NOOP("PathVcarve", "Creates a medial line engraving path"), + PathVcarve.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathVcarveGui... done\n")