From 27238c3813e78a56e6a5ba6877da4101a5c555b1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 2 Sep 2020 19:23:11 -0700 Subject: [PATCH 01/33] Added python bindings for voronoi diagram construction and accessing the resulting vertices. --- src/Mod/Path/App/AppPath.cpp | 8 ++ src/Mod/Path/App/CMakeLists.txt | 10 ++ src/Mod/Path/App/Voronoi.cpp | 106 +++++++++++++++ src/Mod/Path/App/Voronoi.h | 76 +++++++++++ src/Mod/Path/App/VoronoiPy.xml | 69 ++++++++++ src/Mod/Path/App/VoronoiPyImp.cpp | 164 ++++++++++++++++++++++++ src/Mod/Path/App/VoronoiVertex.cpp | 52 ++++++++ src/Mod/Path/App/VoronoiVertex.h | 52 ++++++++ src/Mod/Path/App/VoronoiVertexPy.xml | 46 +++++++ src/Mod/Path/App/VoronoiVertexPyImp.cpp | 151 ++++++++++++++++++++++ 10 files changed, 734 insertions(+) create mode 100644 src/Mod/Path/App/Voronoi.cpp create mode 100644 src/Mod/Path/App/Voronoi.h create mode 100644 src/Mod/Path/App/VoronoiPy.xml create mode 100644 src/Mod/Path/App/VoronoiPyImp.cpp create mode 100644 src/Mod/Path/App/VoronoiVertex.cpp create mode 100644 src/Mod/Path/App/VoronoiVertex.h create mode 100644 src/Mod/Path/App/VoronoiVertexPy.xml create mode 100644 src/Mod/Path/App/VoronoiVertexPyImp.cpp diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index 3e08c68046..e5de2bb0ad 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -46,6 +46,10 @@ #include "FeaturePathShape.h" #include "AreaPy.h" #include "FeatureArea.h" +#include "Voronoi.h" +#include "VoronoiPy.h" +#include "VoronoiVertex.h" +#include "VoronoiVertexPy.h" namespace Path { extern PyObject* initModule(); @@ -72,6 +76,8 @@ PyMOD_INIT_FUNC(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::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 +100,8 @@ PyMOD_INIT_FUNC(Path) Path::FeatureAreaPython ::init(); Path::FeatureAreaView ::init(); Path::FeatureAreaViewPython ::init(); + Path::Voronoi ::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..0ede609c42 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -36,6 +36,8 @@ generate_from_xml(TooltablePy) generate_from_xml(FeaturePathCompoundPy) generate_from_xml(AreaPy) generate_from_xml(FeatureAreaPy) +generate_from_xml(VoronoiPy) +generate_from_xml(VoronoiVertexPy) SET(Python_SRCS CommandPy.xml @@ -52,6 +54,10 @@ SET(Python_SRCS AreaPyImp.cpp FeatureAreaPy.xml FeatureAreaPyImp.cpp + VoronoiPy.xml + VoronoiPyImp.cpp + VoronoiVertexPy.xml + VoronoiVertexPyImp.cpp ) SET(Mod_SRCS @@ -90,6 +96,10 @@ SET(Path_SRCS FeatureArea.h PathSegmentWalker.h PathSegmentWalker.cpp + Voronoi.cpp + Voronoi.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..19558034c9 --- /dev/null +++ b/src/Mod/Path/App/Voronoi.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * 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 + +#if 0 +static const std::size_t EXTERNAL_COLOR = 1; + +static void color_exterior(const Voronoi::diagram_type::edge_type *edge) { + if (edge->color() == EXTERNAL_COLOR) { + // end recursion + return; + } + edge->color(EXTERNAL_COLOR); + edge->twin()->color(EXTERNAL_COLOR); + auto v = edge->vertex1(); + if (v == NULL || !edge->is_primary()) { + return; + } + v->color(EXTERNAL_COLOR); + auto e = v->incident_edge(); + do { + color_exterior(e); + e = e->rot_next(); + } while (e != v->incident_edge()); +} +#endif + +// Constructors & destructors + +Voronoi::Voronoi() + :vd(new diagram_type) +{ +} + +Voronoi::~Voronoi() +{ +} + + +void Voronoi::addPoint(const Voronoi::point_type &p) { + points.push_back(p); +} + +void Voronoi::addSegment(const Voronoi::segment_type &s) { + segments.push_back(s); +} + + +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(points.begin(), points.end(), segments.begin(), segments.end(), (voronoi_diagram_type*)vd); +} diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h new file mode 100644 index 0000000000..716256db68 --- /dev/null +++ b/src/Mod/Path/App/Voronoi.h @@ -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 * + * * + ***************************************************************************/ +#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(); + + // 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 + { }; + + // specific methods + void addPoint(const point_type &p); + void addSegment(const segment_type &p); + + void construct(); + long numCells() const; + long numEdges() const; + long numVertices() const; + + // attributes + std::vector points; + std::vector segments; + Base::Reference vd; + }; + +} //namespace Path + +#endif // PATH_VORONOI_H diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml new file mode 100644 index 0000000000..f6e46a37de --- /dev/null +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -0,0 +1,69 @@ + + + + + + Voronoi([segments]): Create voronoi for given collection of line segments + + + + + 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 + + + + diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp new file mode 100644 index 0000000000..95d4dd2902 --- /dev/null +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + * 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/VoronoiVertex.h" +#include +#include +#include +#include + +// files generated out of VoronoiPy.xml +#include "VoronoiPy.h" +#include "VoronoiPy.cpp" +#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()->segments.size() << ", " << getVoronoiPtr()->points.size() << "}" + << " -> " + << "{" << 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*/) +{ + if (!PyArg_ParseTuple(args, "")) { + PyErr_SetString(PyExc_RuntimeError, "no arguments accepted"); + return -1; + } + 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()->points.push_back(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()->segments.push_back(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(new VoronoiVertex(getVoronoiPtr()->vd, i)))); + } + return list; +} + +// 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..ce1f69164c --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertex.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * 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) +{ +} + +VoronoiVertex::~VoronoiVertex() { +} diff --git a/src/Mod/Path/App/VoronoiVertex.h b/src/Mod/Path/App/VoronoiVertex.h new file mode 100644 index 0000000000..ab9083dd67 --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertex.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * 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 = INT_MAX); + ~VoronoiVertex(); + + bool isBound(void) const { return dia.isValid() && index != INT_MAX; } + + Base::Reference dia; + long index; +}; + +} +#endif diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml new file mode 100644 index 0000000000..515d849b32 --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -0,0 +1,46 @@ + + + + + + Vertex of a Voronoi diagram + + + + Assigned color of the receiver. + + + + + + X position + + + + + + Y position + + + + + + diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp new file mode 100644 index 0000000000..1dc3c71751 --- /dev/null +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -0,0 +1,151 @@ +/*************************************************************************** + * 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/VoronoiVertex.h" +#include +#include +#include +#include +#include + +// files generated out of VoronoiVertexPy.xml +#include "VoronoiPy.h" +#include "VoronoiVertexPy.h" +#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()) { + Voronoi::diagram_type::vertex_type pt = v->dia->vertices()[v->index]; + ss << "[" << pt.x() << ", " << pt.y() << "]"; + } + 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->dia->vertices()[self->index]; + } + if (throwIfNotBound) { + throw Py::TypeError("Vertex not bound to voronoi diagram"); + } + return 0; +} + +Py::Int VoronoiVertexPy::getColor(void) const { + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + return Py::Int(v->dia->vertices()[v->index].color()); + } + return Py::Int(0); +} + +void VoronoiVertexPy::setColor(Py::Int color) { + getVertexFromPy(this)->color(int(color) & 0x0FFFFFFF); +} + +Py::Float VoronoiVertexPy::getX(void) const +{ + VoronoiVertex *v = getVoronoiVertexPtr(); + if (!v->isBound()) { + throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); + } + return Py::Float(v->dia->vertices()[v->index].x()); +} + +Py::Float VoronoiVertexPy::getY(void) const +{ + VoronoiVertex *v = getVoronoiVertexPtr(); + if (!v->isBound()) { + throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); + } + return Py::Float(v->dia->vertices()[v->index].y()); +} + +#if 0 +Py::Object VoronoiVertexPy::getIncidentEdge() const { + Py_INCREF(Py_None); + return Py_None; +} +#endif + +// custom attributes get/set + +PyObject* VoronoiVertexPy::getCustomAttributes(const char* /*attr*/) const +{ + return 0; +} + +int VoronoiVertexPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + From 6fc2af2adce617e3ea8d775ecc91f1382949094c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 2 Sep 2020 22:54:33 -0700 Subject: [PATCH 02/33] Added support for voronoi edges. --- src/Mod/Path/App/AppPath.cpp | 18 +- src/Mod/Path/App/CMakeLists.txt | 5 + src/Mod/Path/App/Voronoi.cpp | 43 ++++ src/Mod/Path/App/Voronoi.h | 20 +- src/Mod/Path/App/VoronoiEdge.cpp | 76 +++++++ src/Mod/Path/App/VoronoiEdge.h | 54 +++++ src/Mod/Path/App/VoronoiEdgePy.xml | 101 ++++++++++ src/Mod/Path/App/VoronoiEdgePyImp.cpp | 254 ++++++++++++++++++++++++ src/Mod/Path/App/VoronoiPy.xml | 2 +- src/Mod/Path/App/VoronoiPyImp.cpp | 10 + src/Mod/Path/App/VoronoiVertex.cpp | 24 +++ src/Mod/Path/App/VoronoiVertex.h | 6 +- src/Mod/Path/App/VoronoiVertexPy.xml | 2 - src/Mod/Path/App/VoronoiVertexPyImp.cpp | 32 +-- 14 files changed, 619 insertions(+), 28 deletions(-) create mode 100644 src/Mod/Path/App/VoronoiEdge.cpp create mode 100644 src/Mod/Path/App/VoronoiEdge.h create mode 100644 src/Mod/Path/App/VoronoiEdgePy.xml create mode 100644 src/Mod/Path/App/VoronoiEdgePyImp.cpp diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index e5de2bb0ad..e89849f4b2 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -47,6 +47,8 @@ #include "AreaPy.h" #include "FeatureArea.h" #include "Voronoi.h" +#include "VoronoiEdge.h" +#include "VoronoiEdgePy.h" #include "VoronoiPy.h" #include "VoronoiVertex.h" #include "VoronoiVertexPy.h" @@ -71,13 +73,14 @@ 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::VoronoiPy ::Type, pathModule, "Voronoi"); - Base::Interpreter().addType(&Path::VoronoiVertexPy ::Type, pathModule, "VoronoiVertex"); + 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::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. @@ -101,6 +104,7 @@ PyMOD_INIT_FUNC(Path) Path::FeatureAreaView ::init(); Path::FeatureAreaViewPython ::init(); Path::Voronoi ::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 0ede609c42..0196889289 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -37,6 +37,7 @@ generate_from_xml(FeaturePathCompoundPy) generate_from_xml(AreaPy) generate_from_xml(FeatureAreaPy) generate_from_xml(VoronoiPy) +generate_from_xml(VoronoiEdgePy) generate_from_xml(VoronoiVertexPy) SET(Python_SRCS @@ -56,6 +57,8 @@ SET(Python_SRCS FeatureAreaPyImp.cpp VoronoiPy.xml VoronoiPyImp.cpp + VoronoiEdgePy.xml + VoronoiEdgePyImp.cpp VoronoiVertexPy.xml VoronoiVertexPyImp.cpp ) @@ -98,6 +101,8 @@ SET(Path_SRCS PathSegmentWalker.cpp Voronoi.cpp Voronoi.h + VoronoiEdge.cpp + VoronoiEdge.h VoronoiVertex.cpp VoronoiVertex.h ${Mod_SRCS} diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index 19558034c9..23b4895290 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -68,6 +68,48 @@ static void color_exterior(const Voronoi::diagram_type::edge_type *edge) { // Constructors & destructors +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::Voronoi() :vd(new diagram_type) { @@ -103,4 +145,5 @@ void Voronoi::construct() { vd->clear(); construct_voronoi(points.begin(), points.end(), segments.begin(), segments.end(), (voronoi_diagram_type*)vd); + vd->reIndex(); } diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 716256db68..76bdd9d506 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -46,6 +46,7 @@ namespace Path Voronoi(); ~Voronoi(); + static const int InvalidIndex = INT_MAX; // types typedef double coordinate_type; typedef boost::polygon::point_data point_type; @@ -54,7 +55,24 @@ namespace Path class diagram_type : public voronoi_diagram_type , public Base::Handled - { }; + { + public: + + 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(); + + private: + cell_map_type cell_index; + edge_map_type edge_index; + vertex_map_type vertex_index; + }; // specific methods void addPoint(const point_type &p); 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..4d6ade8d59 --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -0,0 +1,101 @@ + + + + + + Edge of a Voronoi diagram + + + + Assigned color of the receiver. + + + + + + + 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 + + + --> + + diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp new file mode 100644 index 0000000000..323630fcc0 --- /dev/null +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -0,0 +1,254 @@ +/*************************************************************************** + * 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/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 + +// 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() << ", " << v0->y() << "]"; + } else { + ss << "[~]"; + } + ss << ", "; + if (v1) { + ss << "[" << v1->x() << ", " << v1->y() << "]"; + } 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::Int VoronoiEdgePy::getColor(void) const { + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + return Py::Int(e->dia->edges()[e->index].color()); + } + return Py::Int(0); +} + +void VoronoiEdgePy::setColor(Py::Int color) { + getEdgeFromPy(this)->color(int(color) & 0x0FFFFFFF); +} + +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::getRotatedNext(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->rot_next()))); +} + +Py::Object VoronoiEdgePy::getRotatedPrev(void) const +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this); + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->rot_prev()))); +} + +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; +} + + +// 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 index f6e46a37de..5542fc8084 100644 --- a/src/Mod/Path/App/VoronoiPy.xml +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -22,13 +22,13 @@ + --> List of all edges of the voronoi diagram - --> List of all vertices of the voronoi diagram diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 95d4dd2902..85d744d8d4 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -28,6 +28,7 @@ #endif #include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/VoronoiEdge.h" #include "Mod/Path/App/VoronoiVertex.h" #include #include @@ -37,6 +38,7 @@ // files generated out of VoronoiPy.xml #include "VoronoiPy.h" #include "VoronoiPy.cpp" +#include "VoronoiEdgePy.h" #include "VoronoiVertexPy.h" using namespace Path; @@ -149,6 +151,14 @@ Py::List VoronoiPy::getVertices(void) const { return list; } +Py::List VoronoiPy::getEdges(void) const { + Py::List list; + for (int i=0; inumEdges(); ++i) { + list.append(Py::asObject(new VoronoiEdgePy(new VoronoiEdge(getVoronoiPtr()->vd, i)))); + } + return list; +} + // custom attributes get/set PyObject *VoronoiPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Path/App/VoronoiVertex.cpp b/src/Mod/Path/App/VoronoiVertex.cpp index ce1f69164c..c91935b5ca 100644 --- a/src/Mod/Path/App/VoronoiVertex.cpp +++ b/src/Mod/Path/App/VoronoiVertex.cpp @@ -45,8 +45,32 @@ 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 index ab9083dd67..9393f331cc 100644 --- a/src/Mod/Path/App/VoronoiVertex.h +++ b/src/Mod/Path/App/VoronoiVertex.h @@ -39,13 +39,15 @@ class PathExport VoronoiVertex TYPESYSTEM_HEADER(); public: - VoronoiVertex(Voronoi::diagram_type* dia = 0, long index = INT_MAX); + 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 { return dia.isValid() && index != INT_MAX; } + bool isBound(void) const; Base::Reference dia; long index; + mutable const Voronoi::diagram_type::vertex_type *ptr; }; } diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index 515d849b32..3f90b8b9fa 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -34,13 +34,11 @@ - diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 1dc3c71751..711b18f246 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -27,8 +27,12 @@ # include #endif -#include "Mod/Path/App/Voronoi.h" -#include "Mod/Path/App/VoronoiVertex.h" +#include "Voronoi.h" +#include "VoronoiPy.h" +#include "VoronoiEdge.h" +#include "VoronoiEdgePy.h" +#include "VoronoiVertex.h" +#include "VoronoiVertexPy.h" #include #include #include @@ -36,8 +40,6 @@ #include // files generated out of VoronoiVertexPy.xml -#include "VoronoiPy.h" -#include "VoronoiVertexPy.h" #include "VoronoiVertexPy.cpp" using namespace Path; @@ -50,8 +52,7 @@ std::string VoronoiVertexPy::representation(void) const ss << "VoronoiVertex("; VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - Voronoi::diagram_type::vertex_type pt = v->dia->vertices()[v->index]; - ss << "[" << pt.x() << ", " << pt.y() << "]"; + ss << "[" << v->ptr->x() << ", " << v->ptr->y() << "]"; } ss << ")"; return ss.str(); @@ -81,7 +82,7 @@ PyObject* VoronoiVertexPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { && 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)) { + if (vl->index == vr->index && vl->dia == vr->dia) { cmp = Py_True; } } @@ -92,7 +93,7 @@ PyObject* VoronoiVertexPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { const Voronoi::voronoi_diagram_type::vertex_type* getVertexFromPy(VoronoiVertexPy *v, bool throwIfNotBound = true) { auto self = v->getVoronoiVertexPtr(); if (self->isBound()) { - return &self->dia->vertices()[self->index]; + return self->ptr; } if (throwIfNotBound) { throw Py::TypeError("Vertex not bound to voronoi diagram"); @@ -103,7 +104,7 @@ const Voronoi::voronoi_diagram_type::vertex_type* getVertexFromPy(VoronoiVertexP Py::Int VoronoiVertexPy::getColor(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - return Py::Int(v->dia->vertices()[v->index].color()); + return Py::Int(v->ptr->color()); } return Py::Int(0); } @@ -118,7 +119,7 @@ Py::Float VoronoiVertexPy::getX(void) const if (!v->isBound()) { throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); } - return Py::Float(v->dia->vertices()[v->index].x()); + return Py::Float(v->ptr->x()); } Py::Float VoronoiVertexPy::getY(void) const @@ -127,15 +128,16 @@ Py::Float VoronoiVertexPy::getY(void) const if (!v->isBound()) { throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); } - return Py::Float(v->dia->vertices()[v->index].y()); + return Py::Float(v->ptr->y()); } -#if 0 Py::Object VoronoiVertexPy::getIncidentEdge() const { - Py_INCREF(Py_None); - return Py_None; + VoronoiVertex *v = getVoronoiVertexPtr(); + if (!v->isBound()) { + throw Py::TypeError("Vertex not bound to voronoi diagram"); + } + return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); } -#endif // custom attributes get/set From 9ba881738254da2d1d23c0750c8e99e3f31e56d3 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 3 Sep 2020 18:51:33 -0700 Subject: [PATCH 03/33] Added support for voronoi cells --- src/Mod/Path/App/AppPath.cpp | 4 + src/Mod/Path/App/CMakeLists.txt | 5 + src/Mod/Path/App/VoronoiCell.cpp | 76 ++++++++++ src/Mod/Path/App/VoronoiCell.h | 54 +++++++ src/Mod/Path/App/VoronoiCellPy.xml | 59 ++++++++ src/Mod/Path/App/VoronoiCellPyImp.cpp | 181 ++++++++++++++++++++++++ src/Mod/Path/App/VoronoiEdgePy.xml | 2 - src/Mod/Path/App/VoronoiEdgePyImp.cpp | 9 ++ src/Mod/Path/App/VoronoiPy.xml | 2 - src/Mod/Path/App/VoronoiPyImp.cpp | 10 ++ src/Mod/Path/App/VoronoiVertexPyImp.cpp | 29 ++-- 11 files changed, 413 insertions(+), 18 deletions(-) create mode 100644 src/Mod/Path/App/VoronoiCell.cpp create mode 100644 src/Mod/Path/App/VoronoiCell.h create mode 100644 src/Mod/Path/App/VoronoiCellPy.xml create mode 100644 src/Mod/Path/App/VoronoiCellPyImp.cpp diff --git a/src/Mod/Path/App/AppPath.cpp b/src/Mod/Path/App/AppPath.cpp index e89849f4b2..2e984c5769 100644 --- a/src/Mod/Path/App/AppPath.cpp +++ b/src/Mod/Path/App/AppPath.cpp @@ -47,6 +47,8 @@ #include "AreaPy.h" #include "FeatureArea.h" #include "Voronoi.h" +#include "VoronoiCell.h" +#include "VoronoiCellPy.h" #include "VoronoiEdge.h" #include "VoronoiEdgePy.h" #include "VoronoiPy.h" @@ -79,6 +81,7 @@ PyMOD_INIT_FUNC(Path) 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"); @@ -104,6 +107,7 @@ PyMOD_INIT_FUNC(Path) Path::FeatureAreaView ::init(); Path::FeatureAreaViewPython ::init(); Path::Voronoi ::init(); + Path::VoronoiCell ::init(); Path::VoronoiEdge ::init(); Path::VoronoiVertex ::init(); diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 0196889289..5177dcf8f8 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -37,6 +37,7 @@ 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) @@ -57,6 +58,8 @@ SET(Python_SRCS FeatureAreaPyImp.cpp VoronoiPy.xml VoronoiPyImp.cpp + VoronoiCellPy.xml + VoronoiCellPyImp.cpp VoronoiEdgePy.xml VoronoiEdgePyImp.cpp VoronoiVertexPy.xml @@ -101,6 +104,8 @@ SET(Path_SRCS PathSegmentWalker.cpp Voronoi.cpp Voronoi.h + VoronoiCell.cpp + VoronoiCell.h VoronoiEdge.cpp VoronoiEdge.h VoronoiVertex.cpp diff --git a/src/Mod/Path/App/VoronoiCell.cpp b/src/Mod/Path/App/VoronoiCell.cpp new file mode 100644 index 0000000000..37a2760737 --- /dev/null +++ b/src/Mod/Path/App/VoronoiCell.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 "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; +} diff --git a/src/Mod/Path/App/VoronoiCell.h b/src/Mod/Path/App/VoronoiCell.h new file mode 100644 index 0000000000..d45cf4c6bd --- /dev/null +++ b/src/Mod/Path/App/VoronoiCell.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_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; + + 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..7da58f3fdd --- /dev/null +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -0,0 +1,59 @@ + + + + + + Cell of a Voronoi diagram + + + + 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 + + + + diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp new file mode 100644 index 0000000000..99dc6263f3 --- /dev/null +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -0,0 +1,181 @@ +/*************************************************************************** + * 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 +#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::Int VoronoiCellPy::getColor(void) const { + VoronoiCell *c = getVoronoiCellPtr(); + if (c->isBound()) { + return Py::Int(c->dia->cells()[c->index].color()); + } + return Py::Int(0); +} + +void VoronoiCellPy::setColor(Py::Int color) { + getCellFromPy(this)->color(int(color) & 0x0FFFFFFF); +} + +Py::Int VoronoiCellPy::getSourceIndex(void) const +{ + VoronoiCell *c = getVoronoiCellFromPy(this); + return Py::Int(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; +} + + +// 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/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index 4d6ade8d59..f6496e8edb 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -22,14 +22,12 @@ - Begin and End voronoi vertex diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 323630fcc0..73ae0b65a1 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -29,6 +29,8 @@ #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" @@ -191,6 +193,13 @@ Py::Object VoronoiEdgePy::getRotatedPrev(void) const 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); diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml index 5542fc8084..b38bf6872b 100644 --- a/src/Mod/Path/App/VoronoiPy.xml +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -15,14 +15,12 @@ Voronoi([segments]): Create voronoi for given collection of line segments - List of all edges of the voronoi diagram diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 85d744d8d4..d90b4c7257 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -28,6 +28,7 @@ #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 @@ -38,6 +39,7 @@ // files generated out of VoronoiPy.xml #include "VoronoiPy.h" #include "VoronoiPy.cpp" +#include "VoronoiCellPy.h" #include "VoronoiEdgePy.h" #include "VoronoiVertexPy.h" @@ -159,6 +161,14 @@ Py::List VoronoiPy::getEdges(void) const { return list; } +Py::List VoronoiPy::getCells(void) const { + Py::List list; + for (int i=0; inumCells(); ++i) { + list.append(Py::asObject(new VoronoiCellPy(new VoronoiCell(getVoronoiPtr()->vd, i)))); + } + return list; +} + // custom attributes get/set PyObject *VoronoiPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 711b18f246..a26ab482fb 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -101,6 +101,18 @@ const Voronoi::voronoi_diagram_type::vertex_type* getVertexFromPy(VoronoiVertexP 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::Int VoronoiVertexPy::getColor(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { @@ -115,27 +127,16 @@ void VoronoiVertexPy::setColor(Py::Int color) { Py::Float VoronoiVertexPy::getX(void) const { - VoronoiVertex *v = getVoronoiVertexPtr(); - if (!v->isBound()) { - throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); - } - return Py::Float(v->ptr->x()); + return Py::Float(getVoronoiVertexFromPy(this)->ptr->x()); } Py::Float VoronoiVertexPy::getY(void) const { - VoronoiVertex *v = getVoronoiVertexPtr(); - if (!v->isBound()) { - throw Py::FloatingPointError("Cannot get coordinates of unbound voronoi vertex"); - } - return Py::Float(v->ptr->y()); + return Py::Float(getVoronoiVertexFromPy(this)->ptr->y()); } Py::Object VoronoiVertexPy::getIncidentEdge() const { - VoronoiVertex *v = getVoronoiVertexPtr(); - if (!v->isBound()) { - throw Py::TypeError("Vertex not bound to voronoi diagram"); - } + VoronoiVertex *v = getVoronoiVertexFromPy(this); return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); } From 10e5fce11ce378ebb524739761270ba7ec8e8cff Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 3 Sep 2020 19:35:21 -0700 Subject: [PATCH 04/33] Added geometries for vertices and linear edges. --- src/Mod/Path/App/Voronoi.cpp | 6 +- src/Mod/Path/App/Voronoi.h | 6 +- src/Mod/Path/App/VoronoiCell.cpp | 18 +++++ src/Mod/Path/App/VoronoiCell.h | 3 + src/Mod/Path/App/VoronoiEdgePy.xml | 6 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 91 +++++++++++++++++++++++++ src/Mod/Path/App/VoronoiPyImp.cpp | 6 +- src/Mod/Path/App/VoronoiVertexPy.xml | 5 ++ src/Mod/Path/App/VoronoiVertexPyImp.cpp | 14 ++++ 9 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index 23b4895290..b24191d401 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -121,11 +121,11 @@ Voronoi::~Voronoi() void Voronoi::addPoint(const Voronoi::point_type &p) { - points.push_back(p); + vd->points.push_back(p); } void Voronoi::addSegment(const Voronoi::segment_type &s) { - segments.push_back(s); + vd->segments.push_back(s); } @@ -144,6 +144,6 @@ long Voronoi::numVertices() const { void Voronoi::construct() { vd->clear(); - construct_voronoi(points.begin(), points.end(), segments.begin(), segments.end(), (voronoi_diagram_type*)vd); + construct_voronoi(vd->points.begin(), vd->points.end(), vd->segments.begin(), vd->segments.end(), (voronoi_diagram_type*)vd); vd->reIndex(); } diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 76bdd9d506..4824948475 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -52,6 +52,7 @@ namespace Path 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 @@ -68,6 +69,9 @@ namespace Path void reIndex(); + std::vector points; + std::vector segments; + private: cell_map_type cell_index; edge_map_type edge_index; @@ -84,8 +88,6 @@ namespace Path long numVertices() const; // attributes - std::vector points; - std::vector segments; Base::Reference vd; }; diff --git a/src/Mod/Path/App/VoronoiCell.cpp b/src/Mod/Path/App/VoronoiCell.cpp index 37a2760737..9fef6eca9b 100644 --- a/src/Mod/Path/App/VoronoiCell.cpp +++ b/src/Mod/Path/App/VoronoiCell.cpp @@ -74,3 +74,21 @@ bool VoronoiCell::isBound(void) const { 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 index d45cf4c6bd..743538c71c 100644 --- a/src/Mod/Path/App/VoronoiCell.h +++ b/src/Mod/Path/App/VoronoiCell.h @@ -45,6 +45,9 @@ public: 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; diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index f6496e8edb..a43d0257c8 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -94,6 +94,10 @@ Returns true if edge goes through endpoint of the segment site - --> + + + Returns true if edge goes through endpoint of the segment site + + diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 73ae0b65a1..4b4a6867bc 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -40,6 +40,7 @@ #include #include #include +#include // files generated out of VoronoiEdgePy.xml #include "VoronoiEdgePy.cpp" @@ -248,6 +249,96 @@ PyObject* VoronoiEdgePy::isSecondary(PyObject *args) return chk; } +namespace { + Voronoi::point_type retrievPoint(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { + 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 dia->points[index]; + } + index -= dia->points.size(); + if (category == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + return low(dia->segments[index]); + } else { + return high(dia->segments[index]); + } + } + + Voronoi::segment_type retrieveSegment(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { + Voronoi::diagram_type::cell_type::source_index_type index = cell->source_index() - dia->points.size(); + return dia->segments[index]; + } +} + +PyObject* VoronoiEdgePy::getGeom(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(Base::Vector3d(v0->x(), v0->y(), z), Base::Vector3d(v1->x(), v1->y(), 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 = retrievPoint(e->dia, c0); + Voronoi::point_type p1 = retrievPoint(e->dia, 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() ? retrievPoint(e->dia, c1) : retrievPoint(e->dia, c0); + Voronoi::segment_type segment = c0->contains_segment() ? retrieveSegment(e->dia, c0) : retrieveSegment(e->dia, 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; + 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(Base::Vector3d(begin.x(), begin.y(), z), Base::Vector3d(end.x(), end.y())); + return new Part::LineSegmentPy(p); + } + } + } + Py_INCREF(Py_None); + return Py_None; +} // custom attributes get/set diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index d90b4c7257..403961fc2e 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -51,7 +51,7 @@ std::string VoronoiPy::representation(void) const std::stringstream ss; ss.precision(5); ss << "Voronoi(" - << "{" << getVoronoiPtr()->segments.size() << ", " << getVoronoiPtr()->points.size() << "}" + << "{" << getVoronoiPtr()->vd->segments.size() << ", " << getVoronoiPtr()->vd->points.size() << "}" << " -> " << "{" << getVoronoiPtr()->numCells() << ", " << getVoronoiPtr()->numEdges() << ", " << getVoronoiPtr()->numVertices() << "}" << ")"; @@ -92,7 +92,7 @@ Voronoi::point_type getPointFromPy(PyObject *obj) { PyObject* VoronoiPy::addPoint(PyObject *args) { PyObject *obj = 0; if (PyArg_ParseTuple(args, "O", &obj)) { - getVoronoiPtr()->points.push_back(getPointFromPy(obj)); + getVoronoiPtr()->vd->points.push_back(getPointFromPy(obj)); } Py_INCREF(Py_None); return Py_None; @@ -105,7 +105,7 @@ PyObject* VoronoiPy::addSegment(PyObject *args) { if (PyArg_ParseTuple(args, "OO", &objBegin, &objEnd)) { auto p0 = getPointFromPy(objBegin); auto p1 = getPointFromPy(objEnd); - getVoronoiPtr()->segments.push_back(Voronoi::segment_type(p0, p1)); + getVoronoiPtr()->vd->segments.push_back(Voronoi::segment_type(p0, p1)); } Py_INCREF(Py_None); return Py_None; diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index 3f90b8b9fa..fa6917c4c5 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -40,5 +40,10 @@ + + + Returns a Vertex - or None if not possible + + diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index a26ab482fb..1d9f307735 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -140,6 +140,20 @@ Py::Object VoronoiVertexPy::getIncidentEdge() const { return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); } +PyObject* VoronoiVertexPy::getGeom(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->ptr->y(), z)); + } + Py_INCREF(Py_None); + return Py_None; +} + // custom attributes get/set PyObject* VoronoiVertexPy::getCustomAttributes(const char* /*attr*/) const From 5ad3bb17fd2384bba7719699d0d453ee8fe5221d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 4 Sep 2020 14:30:09 -0700 Subject: [PATCH 05/33] Added toGeom for curved edges. --- src/Mod/Path/App/Voronoi.h | 2 + src/Mod/Path/App/VoronoiCellPyImp.cpp | 4 +- src/Mod/Path/App/VoronoiEdgePy.xml | 2 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 87 ++++++++++++++++++++++--- src/Mod/Path/App/VoronoiVertexPy.xml | 2 +- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 6 +- 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 4824948475..5be8018170 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -47,6 +47,8 @@ namespace Path ~Voronoi(); static const int InvalidIndex = INT_MAX; + static const int ColorMask = 0x07FFFFFF; // top 5 bits reserved internally + // types typedef double coordinate_type; typedef boost::polygon::point_data point_type; diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index 99dc6263f3..bf7e9e9353 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -115,13 +115,13 @@ VoronoiCell* getVoronoiCellFromPy(const VoronoiCellPy *c, PyObject *args = 0) { Py::Int VoronoiCellPy::getColor(void) const { VoronoiCell *c = getVoronoiCellPtr(); if (c->isBound()) { - return Py::Int(c->dia->cells()[c->index].color()); + return Py::Int(c->ptr->color() & Voronoi::ColorMask); } return Py::Int(0); } void VoronoiCellPy::setColor(Py::Int color) { - getCellFromPy(this)->color(int(color) & 0x0FFFFFFF); + getCellFromPy(this)->color(int(color) & Voronoi::ColorMask); } Py::Int VoronoiCellPy::getSourceIndex(void) const diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index a43d0257c8..ab55c6ced0 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -94,7 +94,7 @@ Returns true if edge goes through endpoint of the segment site - + Returns true if edge goes through endpoint of the segment site diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 4b4a6867bc..cd2013384a 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -41,6 +41,7 @@ #include #include #include +#include // files generated out of VoronoiEdgePy.xml #include "VoronoiEdgePy.cpp" @@ -132,13 +133,13 @@ VoronoiEdge* getVoronoiEdgeFromPy(const VoronoiEdgePy *e, PyObject *args = 0) { Py::Int VoronoiEdgePy::getColor(void) const { VoronoiEdge *e = getVoronoiEdgePtr(); if (e->isBound()) { - return Py::Int(e->dia->edges()[e->index].color()); + return Py::Int(e->ptr->color() & Voronoi::ColorMask); } return Py::Int(0); } void VoronoiEdgePy::setColor(Py::Int color) { - getEdgeFromPy(this)->color(int(color) & 0x0FFFFFFF); + getEdgeFromPy(this)->color(int(color) & Voronoi::ColorMask); } Py::List VoronoiEdgePy::getVertices(void) const @@ -250,7 +251,7 @@ PyObject* VoronoiEdgePy::isSecondary(PyObject *args) } namespace { - Voronoi::point_type retrievPoint(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { + Voronoi::point_type retrievePoint(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { 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) { @@ -270,7 +271,7 @@ namespace { } } -PyObject* VoronoiEdgePy::getGeom(PyObject *args) +PyObject* VoronoiEdgePy::toGeom(PyObject *args) { double z = 0.0; if (!PyArg_ParseTuple(args, "|d", &z)) { @@ -283,7 +284,7 @@ PyObject* VoronoiEdgePy::getGeom(PyObject *args) auto v0 = e->ptr->vertex0(); auto v1 = e->ptr->vertex1(); if (v0 && v1) { - auto p = new Part::GeomLineSegment(); + auto p = new Part::GeomLineSegment; p->setPoints(Base::Vector3d(v0->x(), v0->y(), z), Base::Vector3d(v1->x(), v1->y(), z)); return new Part::LineSegmentPy(p); } @@ -294,14 +295,14 @@ PyObject* VoronoiEdgePy::getGeom(PyObject *args) Voronoi::point_type origin; Voronoi::point_type direction; if (c0->contains_point() && c1->contains_point()) { - Voronoi::point_type p0 = retrievPoint(e->dia, c0); - Voronoi::point_type p1 = retrievPoint(e->dia, c1); + Voronoi::point_type p0 = retrievePoint(e->dia, c0); + Voronoi::point_type p1 = retrievePoint(e->dia, 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() ? retrievPoint(e->dia, c1) : retrievPoint(e->dia, c0); + origin = c0->contains_segment() ? retrievePoint(e->dia, c1) : retrievePoint(e->dia, c0); Voronoi::segment_type segment = c0->contains_segment() ? retrieveSegment(e->dia, c0) : retrieveSegment(e->dia, c1); Voronoi::coordinate_type dx = high(segment).x() - low(segment).x(); Voronoi::coordinate_type dy = high(segment).y() - low(segment).y(); @@ -330,10 +331,78 @@ PyObject* VoronoiEdgePy::getGeom(PyObject *args) end.x(origin.x() + direction.x() * k); end.y(origin.y() + direction.y() * k); } - auto p = new Part::GeomLineSegment(); + auto p = new Part::GeomLineSegment; p->setPoints(Base::Vector3d(begin.x(), begin.y(), z), Base::Vector3d(end.x(), end.y())); 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() ? retrievePoint(e->dia, e->ptr->cell()) : retrievePoint(e->dia, e->ptr->twin()->cell()); + Voronoi::segment_type segment = e->ptr->cell()->contains_point() ? retrieveSegment(e->dia, e->ptr->twin()->cell()) : retrieveSegment(e->dia, 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; + { + // 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) + double proj = (p.x() * s.x() + p.y() * s.y()) / (s.x() * s.x() + s.y() * s.y()); + Voronoi::point_type p1; + { + p1.x(proj * s.x()); + p1.y(proj * s.y()); + } + // finally ... + // the location is the mid point between the projection on the segment and the point + loc.x(((offset.x() + p1.x()) + point.x()) / 2); + loc.y(((offset.y() + p1.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(Base::Vector3d(point.x(), point.y(), z)); + p->setLocation(Base::Vector3d(loc.x(), loc.y(), z)); + p->setAngleXU(atan2(axis.y(), axis.x())); + p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y())); + } + 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(Base::Vector3d(v0->x(), v0->y(), z), param0)) { + std::cerr << "closestParameter(v0) failed" << std::endl; + } + if (!p->closestParameter(Base::Vector3d(v1->x(), v1->y(), z), param1)) { + std::cerr << "closestParameter(v0) failed" << std::endl; + } + a->setRange(param0, param1, false); + } + return new Part::ArcOfParabolaPy(a); } } Py_INCREF(Py_None); diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index fa6917c4c5..bffc51c33d 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -40,7 +40,7 @@ - + Returns a Vertex - or None if not possible diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 1d9f307735..7c14477fab 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -116,13 +116,13 @@ VoronoiVertex* getVoronoiVertexFromPy(const VoronoiVertexPy *v, PyObject *args = Py::Int VoronoiVertexPy::getColor(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - return Py::Int(v->ptr->color()); + return Py::Int(v->ptr->color() & Voronoi::ColorMask); } return Py::Int(0); } void VoronoiVertexPy::setColor(Py::Int color) { - getVertexFromPy(this)->color(int(color) & 0x0FFFFFFF); + getVertexFromPy(this)->color(int(color) & Voronoi::ColorMask); } Py::Float VoronoiVertexPy::getX(void) const @@ -140,7 +140,7 @@ Py::Object VoronoiVertexPy::getIncidentEdge() const { return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); } -PyObject* VoronoiVertexPy::getGeom(PyObject *args) +PyObject* VoronoiVertexPy::toGeom(PyObject *args) { double z = 0.0; if (!PyArg_ParseTuple(args, "|d", &z)) { From 926d254849ed3a22358b95cc71fbba90d44ca329 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 4 Sep 2020 16:56:32 -0700 Subject: [PATCH 06/33] Added api calls for coloring all twins and coloring exterior edges and vertices. --- src/Mod/Path/App/Voronoi.cpp | 35 ++++++++++++++++++++++--------- src/Mod/Path/App/Voronoi.h | 5 +++++ src/Mod/Path/App/VoronoiPy.xml | 10 +++++++++ src/Mod/Path/App/VoronoiPyImp.cpp | 22 +++++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index b24191d401..96442ede5e 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -43,28 +43,24 @@ TYPESYSTEM_SOURCE(Path::Voronoi , Base::BaseClass); // Helpers -#if 0 -static const std::size_t EXTERNAL_COLOR = 1; - -static void color_exterior(const Voronoi::diagram_type::edge_type *edge) { - if (edge->color() == EXTERNAL_COLOR) { +static void colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue) { + if (edge->color() == colorValue) { // end recursion return; } - edge->color(EXTERNAL_COLOR); - edge->twin()->color(EXTERNAL_COLOR); + edge->color(colorValue); + edge->twin()->color(colorValue); auto v = edge->vertex1(); if (v == NULL || !edge->is_primary()) { return; } - v->color(EXTERNAL_COLOR); + v->color(colorValue); auto e = v->incident_edge(); do { - color_exterior(e); + colorExterior(e, colorValue); e = e->rot_next(); } while (e != v->incident_edge()); } -#endif // Constructors & destructors @@ -147,3 +143,22 @@ void Voronoi::construct() construct_voronoi(vd->points.begin(), vd->points.end(), vd->segments.begin(), vd->segments.end(), (voronoi_diagram_type*)vd); vd->reIndex(); } + +void Voronoi::colorExterior(int color) { + for (diagram_type::const_edge_iterator it = vd->edges().begin(); it != vd->edges().end(); ++it) { + if (!it->is_finite()) { + ::colorExterior(&(*it), color); + } + } +} + +void Voronoi::colorTwins(int 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); + } + } + } +} diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 5be8018170..586d8e0b23 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -89,8 +89,13 @@ namespace Path long numEdges() const; long numVertices() const; + void colorExterior(int color); + void colorTwins(int color); + + private: // attributes Base::Reference vd; + friend class VoronoiPy; }; } //namespace Path diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml index b38bf6872b..b6227aaf01 100644 --- a/src/Mod/Path/App/VoronoiPy.xml +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -63,5 +63,15 @@ 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) + + diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 403961fc2e..6c0bd9eafd 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -169,6 +169,28 @@ Py::List VoronoiPy::getCells(void) const { return list; } +PyObject* VoronoiPy::colorExterior(PyObject *args) { + int color = 0; + if (!PyArg_ParseTuple(args, "i", &color)) { + throw Py::RuntimeError("colorExterior requires an integer (color) argument"); + } + getVoronoiPtr()->colorExterior(color); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* VoronoiPy::colorTwins(PyObject *args) { + int color = 0; + if (!PyArg_ParseTuple(args, "i", &color)) { + throw Py::RuntimeError("colorTwins requires an integer (color) argument"); + } + getVoronoiPtr()->colorTwins(color); + + Py_INCREF(Py_None); + return Py_None; +} + // custom attributes get/set PyObject *VoronoiPy::getCustomAttributes(const char* /*attr*/) const From 38ff7fcdaef1e88b068cb3542942c176cb2bcdbc Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 4 Sep 2020 19:08:38 -0700 Subject: [PATCH 07/33] Added support for distances of edge end points. --- src/Mod/Path/App/VoronoiEdgePy.xml | 7 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 127 ++++++++++++++++++++------ 2 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index ab55c6ced0..c05f273e52 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -96,7 +96,12 @@ - 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 diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index cd2013384a..0a3e062db5 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -269,6 +269,36 @@ namespace { Voronoi::diagram_type::cell_type::source_index_type index = cell->source_index() - dia->points.size(); return dia->segments[index]; } + + 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) @@ -314,7 +344,7 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) direction.y(dx); } } - double k = 10.0; + double k = 10.0; // <-- need something smarter here Voronoi::point_type begin; Voronoi::point_type end; if (e->ptr->vertex0()) { @@ -343,35 +373,10 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) // this is only the mid point of the segment if the parabola is symmetric Voronoi::point_type loc; { - // 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) - double proj = (p.x() * s.x() + p.y() * s.y()) / (s.x() * s.x() + s.y() * s.y()); - Voronoi::point_type p1; - { - p1.x(proj * s.x()); - p1.y(proj * s.y()); - } - // finally ... + Voronoi::point_type proj = orthognalProjection(point, segment); // the location is the mid point between the projection on the segment and the point - loc.x(((offset.x() + p1.x()) + point.x()) / 2); - loc.y(((offset.y() + p1.y()) + point.y()) / 2); + loc.x((proj.x() + point.x()) / 2); + loc.y((proj.y() + point.y()) / 2); } Voronoi::point_type axis; { @@ -409,6 +414,70 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) return Py_None; } + +namespace { + + double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const Voronoi::point_type &p1) { + double x = v0.x() - p1.x(); + double y = v0.y() - p1.y(); + return sqrt(x * x + y * y); + } + + void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list) { + if (v0) { + list->append(Py::Float(distanceBetween(*v0, p1))); + } 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) { + 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))); + } else { + Py_INCREF(Py_None); + list->append(Py::asObject(Py_None)); + } + } + + bool addDistancesToPoint(const VoronoiEdge *edge, Voronoi::point_type p, Py::List *list) { + addDistanceBetween(edge->ptr->vertex0(), p, list); + addDistanceBetween(edge->ptr->vertex1(), p, list); + 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, retrievePoint(edge->dia, c0), list); + } + const Voronoi::diagram_type::cell_type *c1 = edge->ptr->twin()->cell(); + if (c1->contains_point()) { + return addDistancesToPoint(edge, retrievePoint(edge->dia, c1), list); + } + // at this point both cells are sourced from segments and it does not matter which one we use + Voronoi::segment_type segment = retrieveSegment(edge->dia, c0); + addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list); + addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list); + return false; + } +} + +PyObject* VoronoiEdgePy::getDistances(PyObject *args) +{ + VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); + Py::List list; + retrieveDistances(e, &list); + return Py::new_reference_to(list); +} + // custom attributes get/set PyObject* VoronoiEdgePy::getCustomAttributes(const char* /*attr*/) const From 94a44bcbfea585e4db32ce11c7d3e2b1a552c19d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 5 Sep 2020 23:18:09 -0700 Subject: [PATCH 08/33] Added scaling to micro meters --- src/Mod/Path/App/Voronoi.cpp | 50 +++++++++++++++++++++++-- src/Mod/Path/App/Voronoi.h | 21 +++++++++-- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 44 +++++++++++----------- src/Mod/Path/App/VoronoiPyImp.cpp | 12 +++--- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 10 +++-- 5 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index 96442ede5e..ddde461c7d 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -62,7 +62,33 @@ static void colorExterior(const Voronoi::diagram_type::edge_type *edge, std::siz } while (e != v->incident_edge()); } -// Constructors & destructors +// 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)); @@ -106,6 +132,8 @@ void Voronoi::diagram_type::reIndex() { } } +// Voronoi + Voronoi::Voronoi() :vd(new diagram_type) { @@ -117,11 +145,27 @@ Voronoi::~Voronoi() void Voronoi::addPoint(const Voronoi::point_type &p) { - vd->points.push_back(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) { - vd->segments.push_back(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(); } diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 586d8e0b23..7dec32604f 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -53,13 +53,21 @@ namespace Path 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; + 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; @@ -75,14 +83,16 @@ namespace Path std::vector segments; private: + double scale; cell_map_type cell_index; edge_map_type edge_index; vertex_map_type vertex_index; }; - // specific methods void addPoint(const point_type &p); void addSegment(const segment_type &p); + long numPoints() const; + long numSegments() const; void construct(); long numCells() const; @@ -92,10 +102,13 @@ namespace Path void colorExterior(int color); void colorTwins(int color); + template + T* create(int index) { + return new T(vd, index); + } + private: - // attributes Base::Reference vd; - friend class VoronoiPy; }; } //namespace Path diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 0a3e062db5..4b3a2449a1 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -59,13 +59,13 @@ std::string VoronoiEdgePy::representation(void) const 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() << ", " << v0->y() << "]"; + ss << "[" << (v0->x() / e->dia->getScale()) << ", " << (v0->y() / e->dia->getScale()) << "]"; } else { ss << "[~]"; } ss << ", "; if (v1) { - ss << "[" << v1->x() << ", " << v1->y() << "]"; + ss << "[" << (v1->x() / e->dia->getScale()) << ", " << (v1->y() / e->dia->getScale()) << "]"; } else { ss << "[~]"; } @@ -315,7 +315,7 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) auto v1 = e->ptr->vertex1(); if (v0 && v1) { auto p = new Part::GeomLineSegment; - p->setPoints(Base::Vector3d(v0->x(), v0->y(), z), Base::Vector3d(v1->x(), v1->y(), z)); + p->setPoints(e->dia->scaledVector(*v0, z), e->dia->scaledVector(*v1, z)); return new Part::LineSegmentPy(p); } } else { @@ -362,7 +362,7 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) end.y(origin.y() + direction.y() * k); } auto p = new Part::GeomLineSegment; - p->setPoints(Base::Vector3d(begin.x(), begin.y(), z), Base::Vector3d(end.x(), end.y())); + p->setPoints(e->dia->scaledVector(begin, z), e->dia->scaledVector(end, z)); return new Part::LineSegmentPy(p); } } else { @@ -385,10 +385,10 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) } auto p = new Part::GeomParabola; { - p->setCenter(Base::Vector3d(point.x(), point.y(), z)); - p->setLocation(Base::Vector3d(loc.x(), loc.y(), z)); + 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())); + p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y()) / e->dia->getScale()); } auto a = new Part::GeomArcOfParabola; { @@ -399,10 +399,10 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) auto v1 = e->ptr->vertex1(); double param0 = 0; double param1 = 0; - if (!p->closestParameter(Base::Vector3d(v0->x(), v0->y(), z), param0)) { + if (!p->closestParameter(e->dia->scaledVector(*v0, z), param0)) { std::cerr << "closestParameter(v0) failed" << std::endl; } - if (!p->closestParameter(Base::Vector3d(v1->x(), v1->y(), z), param1)) { + if (!p->closestParameter(e->dia->scaledVector(*v1, z), param1)) { std::cerr << "closestParameter(v0) failed" << std::endl; } a->setRange(param0, param1, false); @@ -417,22 +417,22 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) namespace { - double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const Voronoi::point_type &p1) { + 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); + return sqrt(x * x + y * y) / scale; } - void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list) { + 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))); + 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) { + 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; { @@ -440,32 +440,32 @@ namespace { p0.y(v0->y()); } Voronoi::point_type p1 = orthognalProjection(p0, segment); - list->append(Py::Float(distanceBetween(*v0, p1))); + 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) { - addDistanceBetween(edge->ptr->vertex0(), p, list); - addDistanceBetween(edge->ptr->vertex1(), p, list); + 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, retrievePoint(edge->dia, c0), list); + return addDistancesToPoint(edge, retrievePoint(edge->dia, c0), list, edge->dia->getScale()); } const Voronoi::diagram_type::cell_type *c1 = edge->ptr->twin()->cell(); if (c1->contains_point()) { - return addDistancesToPoint(edge, retrievePoint(edge->dia, c1), list); + return addDistancesToPoint(edge, retrievePoint(edge->dia, 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 = retrieveSegment(edge->dia, c0); - addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list); - addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list); + addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list, edge->dia->getScale()); + addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list, edge->dia->getScale()); return false; } } diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 6c0bd9eafd..9ee460fdc8 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -51,7 +51,7 @@ std::string VoronoiPy::representation(void) const std::stringstream ss; ss.precision(5); ss << "Voronoi(" - << "{" << getVoronoiPtr()->vd->segments.size() << ", " << getVoronoiPtr()->vd->points.size() << "}" + << "{" << getVoronoiPtr()->numSegments() << ", " << getVoronoiPtr()->numPoints() << "}" << " -> " << "{" << getVoronoiPtr()->numCells() << ", " << getVoronoiPtr()->numEdges() << ", " << getVoronoiPtr()->numVertices() << "}" << ")"; @@ -92,7 +92,7 @@ Voronoi::point_type getPointFromPy(PyObject *obj) { PyObject* VoronoiPy::addPoint(PyObject *args) { PyObject *obj = 0; if (PyArg_ParseTuple(args, "O", &obj)) { - getVoronoiPtr()->vd->points.push_back(getPointFromPy(obj)); + getVoronoiPtr()->addPoint(getPointFromPy(obj)); } Py_INCREF(Py_None); return Py_None; @@ -105,7 +105,7 @@ PyObject* VoronoiPy::addSegment(PyObject *args) { if (PyArg_ParseTuple(args, "OO", &objBegin, &objEnd)) { auto p0 = getPointFromPy(objBegin); auto p1 = getPointFromPy(objEnd); - getVoronoiPtr()->vd->segments.push_back(Voronoi::segment_type(p0, p1)); + getVoronoiPtr()->addSegment(Voronoi::segment_type(p0, p1)); } Py_INCREF(Py_None); return Py_None; @@ -148,7 +148,7 @@ PyObject* VoronoiPy::numVertices(PyObject *args) Py::List VoronoiPy::getVertices(void) const { Py::List list; for (int i=0; inumVertices(); ++i) { - list.append(Py::asObject(new VoronoiVertexPy(new VoronoiVertex(getVoronoiPtr()->vd, i)))); + list.append(Py::asObject(new VoronoiVertexPy(getVoronoiPtr()->create(i)))); } return list; } @@ -156,7 +156,7 @@ Py::List VoronoiPy::getVertices(void) const { Py::List VoronoiPy::getEdges(void) const { Py::List list; for (int i=0; inumEdges(); ++i) { - list.append(Py::asObject(new VoronoiEdgePy(new VoronoiEdge(getVoronoiPtr()->vd, i)))); + list.append(Py::asObject(new VoronoiEdgePy(getVoronoiPtr()->create(i)))); } return list; } @@ -164,7 +164,7 @@ Py::List VoronoiPy::getEdges(void) const { Py::List VoronoiPy::getCells(void) const { Py::List list; for (int i=0; inumCells(); ++i) { - list.append(Py::asObject(new VoronoiCellPy(new VoronoiCell(getVoronoiPtr()->vd, i)))); + list.append(Py::asObject(new VoronoiCellPy(getVoronoiPtr()->create(i)))); } return list; } diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 7c14477fab..edd99a0d50 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -52,7 +52,7 @@ std::string VoronoiVertexPy::representation(void) const ss << "VoronoiVertex("; VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - ss << "[" << v->ptr->x() << ", " << v->ptr->y() << "]"; + ss << "[" << (v->ptr->x() / v->dia->getScale()) << ", " << (v->ptr->y() / v->dia->getScale()) << "]"; } ss << ")"; return ss.str(); @@ -127,12 +127,14 @@ void VoronoiVertexPy::setColor(Py::Int color) { Py::Float VoronoiVertexPy::getX(void) const { - return Py::Float(getVoronoiVertexFromPy(this)->ptr->x()); + VoronoiVertex *v = getVoronoiVertexFromPy(this); + return Py::Float(v->ptr->x() / v->dia->getScale()); } Py::Float VoronoiVertexPy::getY(void) const { - return Py::Float(getVoronoiVertexFromPy(this)->ptr->y()); + VoronoiVertex *v = getVoronoiVertexFromPy(this); + return Py::Float(v->ptr->y() / v->dia->getScale()); } Py::Object VoronoiVertexPy::getIncidentEdge() const { @@ -148,7 +150,7 @@ PyObject* VoronoiVertexPy::toGeom(PyObject *args) } VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - return new Base::VectorPy(new Base::Vector3d(v->ptr->x(), v->ptr->y(), z)); + 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; From f86033965855fd8a1c30cc7a0801e26713c470a2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 5 Sep 2020 23:36:14 -0700 Subject: [PATCH 09/33] Added optional scaling factor to Voronoi construction. --- src/Mod/Path/App/Voronoi.h | 3 +++ src/Mod/Path/App/VoronoiPyImp.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 7dec32604f..2aa786b058 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -107,6 +107,9 @@ namespace Path return new T(vd, index); } + double getScale() const { return vd->getScale(); } + void setScale(double scale) { vd->setScale(scale); } + private: Base::Reference vd; }; diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 9ee460fdc8..e536232c54 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -68,10 +68,13 @@ PyObject *VoronoiPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Py // constructor int VoronoiPy::PyInit(PyObject* args, PyObject* /*kwds*/) { - if (!PyArg_ParseTuple(args, "")) { - PyErr_SetString(PyExc_RuntimeError, "no arguments accepted"); + 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; } From 952c94cb50ef09e29cd722906318af5a0af1d86f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 7 Sep 2020 18:12:26 -0700 Subject: [PATCH 10/33] Added colorColinear and resetColor algorithms --- src/Mod/Path/App/Voronoi.cpp | 106 +++++++++++++++++++++++- src/Mod/Path/App/Voronoi.h | 18 +++- src/Mod/Path/App/VoronoiCellPy.xml | 5 ++ src/Mod/Path/App/VoronoiCellPyImp.cpp | 30 +++++-- src/Mod/Path/App/VoronoiEdgePy.xml | 5 ++ src/Mod/Path/App/VoronoiEdgePyImp.cpp | 95 ++++++++++++++------- src/Mod/Path/App/VoronoiPy.xml | 10 +++ src/Mod/Path/App/VoronoiPyImp.cpp | 32 ++++++- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 8 +- 9 files changed, 258 insertions(+), 51 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index ddde461c7d..abd4ca6450 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -132,6 +132,26 @@ void Voronoi::diagram_type::reIndex() { } } +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() @@ -188,7 +208,7 @@ void Voronoi::construct() vd->reIndex(); } -void Voronoi::colorExterior(int color) { +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_finite()) { ::colorExterior(&(*it), color); @@ -196,7 +216,7 @@ void Voronoi::colorExterior(int color) { } } -void Voronoi::colorTwins(int 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(); @@ -206,3 +226,85 @@ void Voronoi::colorTwins(int 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 (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 index 2aa786b058..272a81321b 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -46,8 +46,9 @@ namespace Path Voronoi(); ~Voronoi(); - static const int InvalidIndex = INT_MAX; - static const int ColorMask = 0x07FFFFFF; // top 5 bits reserved internally + 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; @@ -82,6 +83,13 @@ namespace Path 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; @@ -99,8 +107,10 @@ namespace Path long numEdges() const; long numVertices() const; - void colorExterior(int color); - void colorTwins(int color); + 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) { diff --git a/src/Mod/Path/App/VoronoiCellPy.xml b/src/Mod/Path/App/VoronoiCellPy.xml index 7da58f3fdd..48601d0847 100644 --- a/src/Mod/Path/App/VoronoiCellPy.xml +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -55,5 +55,10 @@ 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 index bf7e9e9353..9a74a18447 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -27,7 +27,6 @@ # 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" @@ -112,15 +111,15 @@ VoronoiCell* getVoronoiCellFromPy(const VoronoiCellPy *c, PyObject *args = 0) { return self; } -Py::Int VoronoiCellPy::getColor(void) const { +Py::Long VoronoiCellPy::getColor(void) const { VoronoiCell *c = getVoronoiCellPtr(); if (c->isBound()) { - return Py::Int(c->ptr->color() & Voronoi::ColorMask); + return Py::Long(c->ptr->color() & Voronoi::ColorMask); } - return Py::Int(0); + return Py::Long(0); } -void VoronoiCellPy::setColor(Py::Int color) { +void VoronoiCellPy::setColor(Py::Long color) { getCellFromPy(this)->color(int(color) & Voronoi::ColorMask); } @@ -166,6 +165,27 @@ PyObject* VoronoiCellPy::isDegenerate(PyObject *args) 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 diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index c05f273e52..ddefc4586f 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -104,5 +104,10 @@ 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 index 4b3a2449a1..0c6fb6c688 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -130,15 +130,15 @@ VoronoiEdge* getVoronoiEdgeFromPy(const VoronoiEdgePy *e, PyObject *args = 0) { return self; } -Py::Int VoronoiEdgePy::getColor(void) const { +Py::Long VoronoiEdgePy::getColor(void) const { VoronoiEdge *e = getVoronoiEdgePtr(); if (e->isBound()) { - return Py::Int(e->ptr->color() & Voronoi::ColorMask); + return Py::Long(e->ptr->color() & Voronoi::ColorMask); } - return Py::Int(0); + return Py::Long(0); } -void VoronoiEdgePy::setColor(Py::Int color) { +void VoronoiEdgePy::setColor(Py::Long color) { getEdgeFromPy(this)->color(int(color) & Voronoi::ColorMask); } @@ -251,25 +251,6 @@ PyObject* VoronoiEdgePy::isSecondary(PyObject *args) } namespace { - Voronoi::point_type retrievePoint(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { - 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 dia->points[index]; - } - index -= dia->points.size(); - if (category == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { - return low(dia->segments[index]); - } else { - return high(dia->segments[index]); - } - } - - Voronoi::segment_type retrieveSegment(Voronoi::diagram_type *dia, const Voronoi::diagram_type::cell_type *cell) { - Voronoi::diagram_type::cell_type::source_index_type index = cell->source_index() - dia->points.size(); - return dia->segments[index]; - } - 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; @@ -325,15 +306,15 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) Voronoi::point_type origin; Voronoi::point_type direction; if (c0->contains_point() && c1->contains_point()) { - Voronoi::point_type p0 = retrievePoint(e->dia, c0); - Voronoi::point_type p1 = retrievePoint(e->dia, c1); + 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() ? retrievePoint(e->dia, c1) : retrievePoint(e->dia, c0); - Voronoi::segment_type segment = c0->contains_segment() ? retrieveSegment(e->dia, c0) : retrieveSegment(e->dia, c1); + 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()) { @@ -367,8 +348,8 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) } } else { // parabolic curve, which is always formed by a point and an edge - Voronoi::point_type point = e->ptr->cell()->contains_point() ? retrievePoint(e->dia, e->ptr->cell()) : retrievePoint(e->dia, e->ptr->twin()->cell()); - Voronoi::segment_type segment = e->ptr->cell()->contains_point() ? retrieveSegment(e->dia, e->ptr->twin()->cell()) : retrieveSegment(e->dia, e->ptr->cell()); + 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; @@ -456,14 +437,14 @@ namespace { 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, retrievePoint(edge->dia, c0), list, edge->dia->getScale()); + 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, retrievePoint(edge->dia, c1), list, edge->dia->getScale()); + 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 = retrieveSegment(edge->dia, c0); + 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; @@ -478,6 +459,56 @@ PyObject* VoronoiEdgePy::getDistances(PyObject *args) 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 diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml index b6227aaf01..30c0576b92 100644 --- a/src/Mod/Path/App/VoronoiPy.xml +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -73,5 +73,15 @@ 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 + + diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index e536232c54..ed3724483f 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -173,8 +173,8 @@ Py::List VoronoiPy::getCells(void) const { } PyObject* VoronoiPy::colorExterior(PyObject *args) { - int color = 0; - if (!PyArg_ParseTuple(args, "i", &color)) { + Voronoi::color_type color = 0; + if (!PyArg_ParseTuple(args, "k", &color)) { throw Py::RuntimeError("colorExterior requires an integer (color) argument"); } getVoronoiPtr()->colorExterior(color); @@ -184,8 +184,8 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { } PyObject* VoronoiPy::colorTwins(PyObject *args) { - int color = 0; - if (!PyArg_ParseTuple(args, "i", &color)) { + Voronoi::color_type color = 0; + if (!PyArg_ParseTuple(args, "k", &color)) { throw Py::RuntimeError("colorTwins requires an integer (color) argument"); } getVoronoiPtr()->colorTwins(color); @@ -194,6 +194,30 @@ PyObject* VoronoiPy::colorTwins(PyObject *args) { 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; +} + // custom attributes get/set PyObject *VoronoiPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index edd99a0d50..0a8b7a92ba 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -113,15 +113,15 @@ VoronoiVertex* getVoronoiVertexFromPy(const VoronoiVertexPy *v, PyObject *args = } -Py::Int VoronoiVertexPy::getColor(void) const { +Py::Long VoronoiVertexPy::getColor(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - return Py::Int(v->ptr->color() & Voronoi::ColorMask); + return Py::Long(v->ptr->color() & Voronoi::ColorMask); } - return Py::Int(0); + return Py::Long(0); } -void VoronoiVertexPy::setColor(Py::Int color) { +void VoronoiVertexPy::setColor(Py::Long color) { getVertexFromPy(this)->color(int(color) & Voronoi::ColorMask); } From 07f3e6c5211bf1d6749f585dc48dc670130419e8 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 7 Sep 2020 23:49:24 -0700 Subject: [PATCH 11/33] Added callback to colorExterior to also color 'interior' --- src/Mod/Path/App/Voronoi.cpp | 40 +++++++++++++++---------------- src/Mod/Path/App/Voronoi.h | 2 ++ src/Mod/Path/App/VoronoiPyImp.cpp | 37 ++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index abd4ca6450..f06e06e6d7 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -43,25 +43,6 @@ TYPESYSTEM_SOURCE(Path::Voronoi , Base::BaseClass); // Helpers -static void colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue) { - if (edge->color() == colorValue) { - // 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()); -} - // Voronoi::diagram_type Voronoi::diagram_type::diagram_type() @@ -208,10 +189,29 @@ void Voronoi::construct() vd->reIndex(); } +void Voronoi::colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue) { + if (edge->color() == colorValue) { + // 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_finite()) { - ::colorExterior(&(*it), color); + colorExterior(&(*it), color); } } } diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 272a81321b..0634ef3c91 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -122,6 +122,8 @@ namespace Path private: Base::Reference vd; + friend class VoronoiPy; + void colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue); }; } //namespace Path diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index ed3724483f..64b736b0ac 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -172,12 +172,45 @@ Py::List VoronoiPy::getCells(void) const { return list; } +static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &isExterior) { + if (!isExterior) { + 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) { + return false; + } + isExterior = result == Py_True; + Py_DECREF(result); + } + return true; +} + PyObject* VoronoiPy::colorExterior(PyObject *args) { Voronoi::color_type color = 0; - if (!PyArg_ParseTuple(args, "k", &color)) { + PyObject *callback = 0; + if (!PyArg_ParseTuple(args, "k|O", &color, &callback)) { throw Py::RuntimeError("colorExterior requires an integer (color) argument"); } - getVoronoiPtr()->colorExterior(color); + Voronoi *vo = getVoronoiPtr(); + vo->colorExterior(color); + if (callback) { + 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 isExterior = false; + if (!callbackWithVertex(vo->vd, callback, v0, isExterior) || !callbackWithVertex(vo->vd, callback, v1, isExterior)) { + return NULL; + } + if (isExterior) { + vo->colorExterior(&(*e), color); + } + } + } + } Py_INCREF(Py_None); return Py_None; From d68b5064cf843d4e2a66223029a75b531c863f2b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 8 Sep 2020 21:02:17 -0700 Subject: [PATCH 12/33] Some algorithm optimisations --- src/Mod/Path/App/Voronoi.cpp | 4 ++-- src/Mod/Path/App/VoronoiPyImp.cpp | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index f06e06e6d7..f7e36b8425 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -190,7 +190,7 @@ void Voronoi::construct() } void Voronoi::colorExterior(const Voronoi::diagram_type::edge_type *edge, std::size_t colorValue) { - if (edge->color() == colorValue) { + if (edge->color()) { // end recursion return; } @@ -210,7 +210,7 @@ void Voronoi::colorExterior(const Voronoi::diagram_type::edge_type *edge, std::s 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_finite()) { + if (it->is_infinite()) { colorExterior(&(*it), color); } } diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 64b736b0ac..5062e9f4e2 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -173,7 +173,7 @@ Py::List VoronoiPy::getCells(void) const { } static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &isExterior) { - if (!isExterior) { + if (!isExterior && v->color() == 0) { PyObject *vx = new VoronoiVertexPy(new VoronoiVertex(dia, v)); PyObject *arglist = Py_BuildValue("(O)", vx); PyObject *result = PyEval_CallObject(callback, arglist); @@ -201,11 +201,12 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { 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 isExterior = false; - if (!callbackWithVertex(vo->vd, callback, v0, isExterior) || !callbackWithVertex(vo->vd, callback, v1, isExterior)) { + bool is0 = false; + bool is1 = false; + if (!callbackWithVertex(vo->vd, callback, v0, is0) || !callbackWithVertex(vo->vd, callback, v1, is1)) { return NULL; } - if (isExterior) { + if (is0 && is1) { vo->colorExterior(&(*e), color); } } From 1109691393ba124c877d5911b17563d45cd0b92a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 8 Sep 2020 23:24:46 -0700 Subject: [PATCH 13/33] Added color -1 to mean resetting all color. --- src/Mod/Path/App/Voronoi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index f7e36b8425..b124bb52d2 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -293,7 +293,7 @@ void Voronoi::colorColinear(Voronoi::color_type color, double degree) { void Voronoi::resetColor(Voronoi::color_type color) { for (auto it = vd->cells().begin(); it != vd->cells().end(); ++it) { - if (it->color() == color) { + if (color == -1 || it->color() == color) { it->color(0); } } From a95ecd69a9cd8a681e29418a67b506e6e9f6fc7c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 8 Sep 2020 23:26:42 -0700 Subject: [PATCH 14/33] Simplified and further optimised colinear colorisation --- src/Mod/Path/App/VoronoiPyImp.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 5062e9f4e2..f9afffa33f 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -172,20 +172,22 @@ Py::List VoronoiPy::getCells(void) const { return list; } -static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &isExterior) { - if (!isExterior && v->color() == 0) { +static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &bail) { + bool rc = false; + if (!bail && v->color() == 0) { 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) { - return false; + bail = true; + } else { + rc = result == Py_True; + Py_DECREF(result); } - isExterior = result == Py_True; - Py_DECREF(result); } - return true; + return rc; } PyObject* VoronoiPy::colorExterior(PyObject *args) { @@ -201,14 +203,13 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { 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 is0 = false; - bool is1 = false; - if (!callbackWithVertex(vo->vd, callback, v0, is0) || !callbackWithVertex(vo->vd, callback, v1, is1)) { - return NULL; - } - if (is0 && is1) { + bool bail = false; + if (callbackWithVertex(vo->vd, callback, v0, bail) && callbackWithVertex(vo->vd, callback, v1, bail)) { vo->colorExterior(&(*e), color); } + if (bail) { + return NULL; + } } } } From 6d43d750284869282523ee5b0591be4436642d09 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 8 Sep 2020 23:27:19 -0700 Subject: [PATCH 15/33] Added Index as attribute of all voronoi objects for easier debugging --- src/Mod/Path/App/VoronoiCellPy.xml | 6 ++++++ src/Mod/Path/App/VoronoiCellPyImp.cpp | 8 ++++++++ src/Mod/Path/App/VoronoiEdgePy.xml | 14 ++++++++++---- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 12 ++++++++++-- src/Mod/Path/App/VoronoiVertexPy.xml | 6 ++++++ src/Mod/Path/App/VoronoiVertexPyImp.cpp | 8 ++++++++ 6 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/VoronoiCellPy.xml b/src/Mod/Path/App/VoronoiCellPy.xml index 48601d0847..ffc4b57a2b 100644 --- a/src/Mod/Path/App/VoronoiCellPy.xml +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -16,6 +16,12 @@ Cell of a Voronoi diagram + + + Internal id of the element. + + + Assigned color of the receiver. diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index 9a74a18447..95c8328866 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -111,6 +111,14 @@ VoronoiCell* getVoronoiCellFromPy(const VoronoiCellPy *c, PyObject *args = 0) { return self; } +Py::Int VoronoiCellPy::getIndex(void) const { + VoronoiCell *c = getVoronoiCellPtr(); + if (c->isBound()) { + return Py::Int(c->dia->index(c->ptr)); + } + return Py::Int(-1); +} + Py::Long VoronoiCellPy::getColor(void) const { VoronoiCell *c = getVoronoiCellPtr(); if (c->isBound()) { diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index ddefc4586f..daf107b7a2 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -16,6 +16,12 @@ Edge of a Voronoi diagram + + + Internal id of the element. + + + Assigned color of the receiver. @@ -46,17 +52,17 @@ - + Rotated CCW next edge whithin voronoi cell - + - + Rotated CCW previous edge whithin voronoi cell - + diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 0c6fb6c688..7ca2f85251 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -130,6 +130,14 @@ VoronoiEdge* getVoronoiEdgeFromPy(const VoronoiEdgePy *e, PyObject *args = 0) { return self; } +Py::Int VoronoiEdgePy::getIndex(void) const { + VoronoiEdge *e = getVoronoiEdgePtr(); + if (e->isBound()) { + return Py::Int(e->dia->index(e->ptr)); + } + return Py::Int(-1); +} + Py::Long VoronoiEdgePy::getColor(void) const { VoronoiEdge *e = getVoronoiEdgePtr(); if (e->isBound()) { @@ -183,13 +191,13 @@ Py::Object VoronoiEdgePy::getPrev(void) const return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->prev()))); } -Py::Object VoronoiEdgePy::getRotatedNext(void) const +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::getRotatedPrev(void) const +Py::Object VoronoiEdgePy::getRotPrev(void) const { VoronoiEdge *e = getVoronoiEdgeFromPy(this); return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(e->dia, e->ptr->rot_prev()))); diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index bffc51c33d..ef5eca069f 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -16,6 +16,12 @@ Vertex of a Voronoi diagram + + + Internal id of the element. + + + Assigned color of the receiver. diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 0a8b7a92ba..beef937908 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -113,6 +113,14 @@ VoronoiVertex* getVoronoiVertexFromPy(const VoronoiVertexPy *v, PyObject *args = } +Py::Int VoronoiVertexPy::getIndex(void) const { + VoronoiVertex *v = getVoronoiVertexPtr(); + if (v->isBound()) { + return Py::Int(v->dia->index(v->ptr)); + } + return Py::Int(-1); +} + Py::Long VoronoiVertexPy::getColor(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { From 7777e0f26531610a194df062d4e62086c443fba5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 9 Sep 2020 21:30:45 -0700 Subject: [PATCH 16/33] Added retrieval of points and segments for voronoi diagram --- src/Mod/Path/App/Voronoi.cpp | 2 +- src/Mod/Path/App/VoronoiPy.xml | 20 +++++++++++++ src/Mod/Path/App/VoronoiPyImp.cpp | 48 +++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/App/Voronoi.cpp b/src/Mod/Path/App/Voronoi.cpp index b124bb52d2..4a3382306b 100644 --- a/src/Mod/Path/App/Voronoi.cpp +++ b/src/Mod/Path/App/Voronoi.cpp @@ -293,7 +293,7 @@ void Voronoi::colorColinear(Voronoi::color_type color, double degree) { void Voronoi::resetColor(Voronoi::color_type color) { for (auto it = vd->cells().begin(); it != vd->cells().end(); ++it) { - if (color == -1 || it->color() == color) { + if (color == 0 || it->color() == color) { it->color(0); } } diff --git a/src/Mod/Path/App/VoronoiPy.xml b/src/Mod/Path/App/VoronoiPy.xml index 30c0576b92..d330d6ccca 100644 --- a/src/Mod/Path/App/VoronoiPy.xml +++ b/src/Mod/Path/App/VoronoiPy.xml @@ -83,5 +83,25 @@ 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 index f9afffa33f..77dcb76f15 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -253,6 +253,54 @@ PyObject* VoronoiPy::resetColor(PyObject *args) { 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 From d9f09b1716a850ad3bb51e5eb86e378cde8c16c2 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 9 Sep 2020 22:19:45 -0700 Subject: [PATCH 17/33] Added vertex cache to voronoi colorExterior callback processing - cuts execution time in half. --- src/Mod/Path/App/VoronoiPyImp.cpp | 39 ++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 77dcb76f15..119408b67e 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -172,20 +172,36 @@ Py::List VoronoiPy::getCells(void) const { return list; } -static bool callbackWithVertex(Voronoi::diagram_type *dia, PyObject *callback, const Voronoi::diagram_type::vertex_type *v, bool &bail) { +typedef std::map exterior_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) { - 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; +#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 = result == Py_True; - Py_DECREF(result); + rc = it->second; } +#else + (void)cache; +#endif } return rc; } @@ -199,12 +215,13 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { Voronoi *vo = getVoronoiPtr(); vo->colorExterior(color); if (callback) { + exterior_map_t cache; 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) && callbackWithVertex(vo->vd, callback, v1, bail)) { + if (callbackWithVertex(vo->vd, callback, v0, bail, cache) && callbackWithVertex(vo->vd, callback, v1, bail, cache)) { vo->colorExterior(&(*e), color); } if (bail) { From 33beb4110af6159063176bfc60264a8b0837a8d1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 9 Sep 2020 23:37:34 -0700 Subject: [PATCH 18/33] Added voronoi colorExterior case where one external vertex coincides with a point of an input segment. --- src/Mod/Path/App/VoronoiPyImp.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 119408b67e..474d0e7da1 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -173,6 +173,7 @@ Py::List VoronoiPy::getCells(void) const { } typedef std::map exterior_map_t; +typedef std::map > coordinate_map_t; #define VORONOI_USE_EXTERIOR_CACHE 1 @@ -215,7 +216,8 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { Voronoi *vo = getVoronoiPtr(); vo->colorExterior(color); if (callback) { - exterior_map_t cache; + 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(); @@ -223,6 +225,17 @@ PyObject* VoronoiPy::colorExterior(PyObject *args) { 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; From c141f4188798bb776d8e8f32cd3fc23087b51ea9 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 11 Jun 2019 22:05:06 -0500 Subject: [PATCH 19/33] Path: add vcarve operation using openvoronoi --- src/Mod/Path/CMakeLists.txt | 2 + src/Mod/Path/Gui/Resources/Path.qrc | 2 + .../Path/Gui/Resources/icons/Path-Vcarve.svg | 664 ++++++++++++++++++ .../Gui/Resources/panels/PageOpVcarveEdit.ui | 104 +++ src/Mod/Path/InitGui.py | 1 + src/Mod/Path/PathScripts/PathGuiInit.py | 2 +- src/Mod/Path/PathScripts/PathSelection.py | 8 + src/Mod/Path/PathScripts/PathVcarve.py | 266 +++++++ src/Mod/Path/PathScripts/PathVcarveGui.py | 148 ++++ 9 files changed, 1196 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg create mode 100644 src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui create mode 100644 src/Mod/Path/PathScripts/PathVcarve.py create mode 100644 src/Mod/Path/PathScripts/PathVcarveGui.py 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..44b066cd0c 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..0cf0716a94 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..fd5fa66346 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -0,0 +1,104 @@ + + + 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.001000000000000 + + + 0.010000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 6b8b545bae..b630732886 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -113,6 +113,7 @@ class PathWorkbench (Workbench): threedcmdgroup = threedopcmdlist if PathPreferences.experimentalFeaturesEnabled(): projcmdlist.append("Path_Sanity") + engravecmdlist.append("Path_Vcarve") prepcmdlist.append("Path_Shape") extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) 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..797cb8ac00 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -47,6 +47,9 @@ 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): + class ENGRAVEGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument @@ -300,6 +303,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 +335,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..3fd0c8ba95 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2014 Yorik van Havre * +# * * +# * 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 ArchPanel +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 traceback +import time +import PathScripts.PathGeom as pg +from PathScripts.PathOpTools import orientWire + + +from PySide import QtCore + +__doc__ = "Class and implementation of Path Vcarve operation" + +if False: + 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) + +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.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | 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")) + self.setupAdditionalProperties(obj) + + def opOnDocumentRestored(self, obj): + # upgrade ... + self.setupAdditionalProperties(obj) + + + def buildPathMedial(self, obj, Faces, zDepths, unitcircle): + '''constructs a medial axis path using openvoronoi''' + import openvoronoi as ovd + + def insert_wire_points(vd, wire): + pts=[] + for p in wire.Vertexes: + pts.append( ovd.Point( p.X, p.Y ) ) + print('p1 = FreeCAD.Vector(X:{} Y:{}'.format(p.X, p.Y)) + id_list = [] + print("inserting ",len(pts)," point-sites:") + for p in pts: + id_list.append( vd.addVertexSite( p ) ) + return id_list + + def insert_wire_segments(vd,id_list): + print('insert_polygon-segments') + print('inserting {} segments'.format(len(id_list))) + for n in range(len(id_list)): + n_nxt = n+1 + if n==(len(id_list)-1): + n_nxt=0 + vd.addLineSite( id_list[n], id_list[n_nxt]) + + def insert_many_wires(vd, wires): + # print('inserting {} wires'.format(len(obj.Wires))) + polygon_ids =[] + t_before = time.time() + for idx, wire in enumerate(wires): + d = obj.Discretize + print('discretize: {}'.format(d)) + d = 0.008 + pointList = wire.discretize(Deflection=d) + segwire = Part.Wire([Part.makeLine(p[0],p[1]) for p in zip(pointList, pointList[1:] )]) + + if idx == 0: + segwire = orientWire(segwire, forward=False) + else: + segwire = orientWire(segwire, forward=True) + + poly_id = insert_wire_points(vd,segwire) + polygon_ids.append(poly_id) + t_after = time.time() + pt_time = t_after-t_before + + t_before = time.time() + for ids in polygon_ids: + insert_wire_segments(vd,ids) + t_after = time.time() + seg_time = t_after-t_before + return [pt_time, seg_time] + + + def buildMedial(vd): + safeheight = 3.0 + path = [] + maw = ovd.MedialAxisWalk( vd.getGraph() ) + toolpath = maw.walk() + for chain in toolpath: + path.append(Path.Command("G0 Z{}".format(safeheight))) + p = chain[0][0][0] + z = -(chain[0][0][1]) + + path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, safeheight))) + + for step in chain: + for point in step: + p = point[0] + z = -(point[1]) + path.append(Path.Command("G1 X{} Y{} Z{}".format(p.x, p.y, z))) + + path.append(Path.Command("G0 Z{}".format(safeheight))) + + return path + + pathlist = [] + bins = 120 # int bins = number of bins for grid-search (affects performance, should not affect correctness) + for f in Faces: + #unitcircle = f.BoundBox.DiagonalLength/2 + print('unitcircle: {}'.format(unitcircle)) + vd = ovd.VoronoiDiagram(200, bins) + vd.set_silent(True) # suppress Warnings! + wires = f.Wires + insert_many_wires(vd, wires) + pi = ovd.PolygonInterior( True ) + vd.filter_graph(pi) + ma = ovd.MedialAxis() + vd.filter_graph(ma) + pathlist.extend(buildMedial( vd )) # the actual cutting g-code + + self.commandlist = pathlist + + + + def opExecute(self, obj): + '''opExecute(obj) ... process engraving operation''' + PathLog.track() + # Openvoronoi must be installed + try: + import openvoronoi as ovd + except: + FreeCAD.Console.PrintError( + translate("Path_Vcarve", "This operation requires OpenVoronoi to be installed.") + "\n") + return + + + job = PathUtils.findParentJob(obj) + + jobshapes = [] + zValues = self.getZValues(obj) + + + try: + if len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ + self.model[0].isDerivedFrom('Part::Part2DObject'): + PathLog.track() + + # self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + + # we only consider the outer wire if this is a Face + modelshape = self.model[0].Shape + self.buildPathMedial(obj, modelshape.Faces, zValues, modelshape.BoundBox.DiagonalLength/2) + # self.wires = wires + + # elif obj.Base: + # PathLog.track() + # wires = [] + # for base, subs in obj.Base: + # edges = [] + # basewires = [] + # for feature in subs: + # sub = base.Shape.getElement(feature) + # if type(sub) == Part.Edge: + # edges.append(sub) + # elif sub.Wires: + # basewires.extend(sub.Wires) + # else: + # basewires.append(Part.Wire(sub.Edges)) + + # for edgelist in Part.sortEdges(edges): + # basewires.append(Part.Wire(edgelist)) + + # wires.extend(basewires) + # self.buildpathocc(obj, wires, zValues) + # self.wires = wires + # elif not obj.BaseShapes: + # PathLog.track() + # if not obj.Base and not obj.BaseShapes: + # for base in self.model: + # PathLog.track(base.Label) + # if base.isDerivedFrom('Part::Part2DObject'): + # jobshapes.append(base) + + # if not jobshapes: + # raise ValueError(translate('PathVcarve', "Unknown baseobject type for engraving (%s)") % (obj.Base)) + + # if obj.BaseShapes or jobshapes: + # PathLog.track() + # wires = [] + # for shape in obj.BaseShapes + jobshapes: + # PathLog.track(shape.Label) + # shapeWires = shape.Shape.Wires + # self.buildpathocc(obj, shapeWires, zValues) + # wires.extend(shapeWires) + # self.wires = wires + # # the last command is a move to clearance, which is automatically added by PathOp + # if self.commandlist: + # self.commandlist.pop() + + 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) + proxy = 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..21c53066e7 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -0,0 +1,148 @@ +# -*- 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.PathSelection as PathSelection +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.StartVertex != self.form.startVertex.value(): + # obj.StartVertex = self.form.startVertex.value() + self.updateToolController(obj, self.form.toolController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + # self.form.startVertex.setValue(obj.StartVertex) + self.setupToolController(obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + # signals.append(self.form.startVertex.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") From 0b7eec6b798e109b48ed719054ff913f6e472e08 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 11 Jun 2019 22:05:20 -0500 Subject: [PATCH 20/33] Path: increasing the discretize precision --- src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui | 2 +- src/Mod/Path/PathScripts/PathVcarve.py | 5 +---- src/Mod/Path/PathScripts/PathVcarveGui.py | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui index fd5fa66346..479c7bf074 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -65,7 +65,7 @@ <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 + 4 0.001000000000000 diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 3fd0c8ba95..82408cd5f8 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -104,10 +104,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): polygon_ids =[] t_before = time.time() for idx, wire in enumerate(wires): - d = obj.Discretize - print('discretize: {}'.format(d)) - d = 0.008 - pointList = wire.discretize(Deflection=d) + pointList = wire.discretize(Deflection=obj.Discretize) segwire = Part.Wire([Part.makeLine(p[0],p[1]) for p in zip(pointList, pointList[1:] )]) if idx == 0: diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index 21c53066e7..0ece507d45 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -117,19 +117,19 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - # if obj.StartVertex != self.form.startVertex.value(): - # obj.StartVertex = self.form.startVertex.value() + if obj.Discretize != self.form.discretize.value(): + obj.Discretize = self.form.discretize.value() self.updateToolController(obj, self.form.toolController) def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - # self.form.startVertex.setValue(obj.StartVertex) + self.form.discretize.setValue(obj.Discretize) self.setupToolController(obj, self.form.toolController) def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] - # signals.append(self.form.startVertex.editingFinished) + signals.append(self.form.discretize.editingFinished) signals.append(self.form.toolController.currentIndexChanged) return signals From 5df1f176e631d06da4103ad6df2f4bb687889aef Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 11 Jun 2019 21:59:35 -0500 Subject: [PATCH 21/33] path: vcarve requires engraver with proper angle vcarve calculates depth with MIC now --- .../Gui/Resources/panels/PageOpVcarveEdit.ui | 4 ++-- src/Mod/Path/PathScripts/PathVcarve.py | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui index 479c7bf074..c514bad1d8 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -68,13 +68,13 @@ 4 - 0.001000000000000 + 0.000100000000000 1.000000000000000 - 0.001000000000000 + 0.000100000000000 0.010000000000000 diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 82408cd5f8..cf9d80cf9d 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -34,7 +34,7 @@ import traceback import time import PathScripts.PathGeom as pg from PathScripts.PathOpTools import orientWire - +import math from PySide import QtCore @@ -104,6 +104,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): polygon_ids =[] t_before = time.time() for idx, wire in enumerate(wires): + print('discretize: {}'.format(obj.Discretize)) pointList = wire.discretize(Deflection=obj.Discretize) segwire = Part.Wire([Part.makeLine(p[0],p[1]) for p in zip(pointList, pointList[1:] )]) @@ -124,9 +125,14 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): seg_time = t_after-t_before return [pt_time, seg_time] + def calculate_depth(MIC): + # given a maximum inscribed circle (MIC) and tool angle, + # return depth of cut. + toolangle = obj.ToolController.Tool.CuttingEdgeAngle + return MIC / math.tan(math.radians(toolangle/2)) def buildMedial(vd): - safeheight = 3.0 + safeheight = obj.SafeHeight.Value path = [] maw = ovd.MedialAxisWalk( vd.getGraph() ) toolpath = maw.walk() @@ -140,7 +146,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for step in chain: for point in step: p = point[0] - z = -(point[1]) + z = calculate_depth(-(point[1])) path.append(Path.Command("G1 X{} Y{} Z{}".format(p.x, p.y, z))) path.append(Path.Command("G0 Z{}".format(safeheight))) @@ -183,7 +189,15 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): jobshapes = [] zValues = self.getZValues(obj) + if obj.ToolController.Tool.ToolType != 'Engraver': + FreeCAD.Console.PrintError( + translate("Path_Vcarve", "This operation requires an engraver tool.") + "\n") + return + 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 len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ self.model[0].isDerivedFrom('Part::Part2DObject'): From d9d4387b1de6a1d16df7d6b6cfda2bab3921e851 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 11 Jun 2019 21:59:47 -0500 Subject: [PATCH 22/33] Path: vcarve add feed rate --- src/Mod/Path/PathScripts/PathVcarve.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index cf9d80cf9d..6209f90377 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -147,7 +147,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for point in step: p = point[0] z = calculate_depth(-(point[1])) - path.append(Path.Command("G1 X{} Y{} Z{}".format(p.x, p.y, z))) + path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, z, obj.ToolController.HorizFeed.Value))) path.append(Path.Command("G0 Z{}".format(safeheight))) @@ -207,6 +207,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): # we only consider the outer wire if this is a Face modelshape = self.model[0].Shape + modelshape.tessellate(0.01) self.buildPathMedial(obj, modelshape.Faces, zValues, modelshape.BoundBox.DiagonalLength/2) # self.wires = wires From f63d0fadcb7e0b19528e2829aa122c5aa2c23f5c Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 11 Jun 2019 21:59:54 -0500 Subject: [PATCH 23/33] Path: Vcarve - Added threshold property to remove unwanted segments code cleanup & debug --- .../Gui/Resources/panels/PageOpVcarveEdit.ui | 40 +- src/Mod/Path/PathScripts/PathSelection.py | 17 + src/Mod/Path/PathScripts/PathVcarve.py | 373 +++++++++++------- src/Mod/Path/PathScripts/PathVcarveGui.py | 22 +- 4 files changed, 291 insertions(+), 161 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui index c514bad1d8..b10575eea4 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -48,7 +48,7 @@ - + @@ -65,22 +65,54 @@ <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> - 4 + 3 - 0.000100000000000 + 0.001000000000000 1.000000000000000 - 0.000100000000000 + 0.010000000000000 0.010000000000000 + + + + <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> + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.100000000000000 + + + 0.800000000000000 + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Threshold + + + diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 797cb8ac00..657f4519ce 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -49,6 +49,23 @@ class MESHGate(PathBaseGate): 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 == 'Edge': + return True + + if sub: + subShape = shape.getElement(sub) + if subShape.ShapeType == 'Edge': + return True + + return False class ENGRAVEGate(PathBaseGate): diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 6209f90377..55ca61d6c9 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -22,7 +22,6 @@ # * * # *************************************************************************** -import ArchPanel import FreeCAD import Part import Path @@ -30,9 +29,9 @@ 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 traceback import time -import PathScripts.PathGeom as pg from PathScripts.PathOpTools import orientWire import math @@ -46,148 +45,264 @@ if False: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + 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.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseFaces; + 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 + 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 + 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 threshold for removing extraneous segments (0-1.0). default=0.8. Larger numbers remove less.")) + obj.Threshold = 0.8 + obj.Discretize = 0.01 self.setupAdditionalProperties(obj) def opOnDocumentRestored(self, obj): # upgrade ... self.setupAdditionalProperties(obj) - - def buildPathMedial(self, obj, Faces, zDepths, unitcircle): + def buildPathMedial(self, obj, Faces): '''constructs a medial axis path using openvoronoi''' - import openvoronoi as ovd + #import openvoronoi as ovd - def insert_wire_points(vd, wire): - pts=[] - for p in wire.Vertexes: - pts.append( ovd.Point( p.X, p.Y ) ) - print('p1 = FreeCAD.Vector(X:{} Y:{}'.format(p.X, p.Y)) - id_list = [] - print("inserting ",len(pts)," point-sites:") - for p in pts: - id_list.append( vd.addVertexSite( p ) ) - return id_list + # def insert_wire_points(vd, wire): + # pts = [] + # for p in wire.Vertexes: + # pts.append(ovd.Point(p.X, p.Y)) + # PathLog.debug('ovd.Point( {} ,{} )'.format(p.X, p.Y)) + # id_list = [] + # PathLog.debug("inserting {} openvoronoi point-sites".format(len(pts))) + # for p in pts: + # id_list.append(vd.addVertexSite(p)) + # return id_list - def insert_wire_segments(vd,id_list): - print('insert_polygon-segments') - print('inserting {} segments'.format(len(id_list))) - for n in range(len(id_list)): - n_nxt = n+1 - if n==(len(id_list)-1): - n_nxt=0 - vd.addLineSite( id_list[n], id_list[n_nxt]) + # def insert_wire_segments(vd, id_list): + # PathLog.debug('inserting {} segments into the voronoi diagram'.format(len(id_list))) + # for n in range(len(id_list)): + # n_nxt = n + 1 + # if n == (len(id_list) - 1): + # n_nxt = 0 + # vd.addLineSite(id_list[n], id_list[n_nxt]) def insert_many_wires(vd, wires): - # print('inserting {} wires'.format(len(obj.Wires))) - polygon_ids =[] - t_before = time.time() - for idx, wire in enumerate(wires): - print('discretize: {}'.format(obj.Discretize)) - pointList = wire.discretize(Deflection=obj.Discretize) - segwire = Part.Wire([Part.makeLine(p[0],p[1]) for p in zip(pointList, pointList[1:] )]) + #polygon_ids = [] + #t_before = time.time() + 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]) - if idx == 0: - segwire = orientWire(segwire, forward=False) - else: - segwire = orientWire(segwire, forward=True) + for i in range(len(pts)): + vd.addSegment(ptv[i], ptv[i+1]) - poly_id = insert_wire_points(vd,segwire) - polygon_ids.append(poly_id) - t_after = time.time() - pt_time = t_after-t_before + # segwire = Part.Wire([Part.makeLine(p[0], p[1]) for p in zip(pointList, pointList[1:])]) - t_before = time.time() - for ids in polygon_ids: - insert_wire_segments(vd,ids) - t_after = time.time() - seg_time = t_after-t_before - return [pt_time, seg_time] + # if idx == 0: + # segwire = orientWire(segwire, forward=False) + # else: + # segwire = orientWire(segwire, forward=True) + + # poly_id = insert_wire_points(vd, segwire) + # polygon_ids.append(poly_id) + # t_after = time.time() + # pt_time = t_after - t_before + + # t_before = time.time() + # for ids in polygon_ids: + # insert_wire_segments(vd, ids) + # t_after = time.time() + # seg_time = t_after - t_before + # return [pt_time, seg_time] def calculate_depth(MIC): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut. + maxdepth = obj.ToolController.Tool.CuttingEdgeHeight toolangle = obj.ToolController.Tool.CuttingEdgeAngle - return MIC / math.tan(math.radians(toolangle/2)) + d = MIC / math.tan(math.radians(toolangle / 2)) + return d if d <= maxdepth else maxdepth - def buildMedial(vd): - safeheight = obj.SafeHeight.Value + # def buildMedial(vd): + # safeheight = obj.SafeHeight.Value + # path = [] + # maw = ovd.MedialAxisWalk(vd.getGraph()) + # toolpath = maw.walk() + # for chain in toolpath: + + # path.append(Path.Command("G0 Z{}".format(safeheight))) + # p = chain[0][0][0] + # z = -(chain[0][0][1]) + + # path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, safeheight))) + + # for step in chain: + # for point in step: + # p = point[0] + # z = calculate_depth(-(point[1])) + # path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, z, obj.ToolController.HorizFeed.Value))) +# path.append(Path.Command("G0 Z{}".format(safeheight))) return path pathlist = [] + def getEdges(vd, color=[0]): + if type(color) == int: + color = [color] + geomList = [] + for e in vd.Edges: + if e.Color not in color: + continue + # geom = e.toGeom(8) + if e.toGeom(8) 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])) + geomList.append(Part.LineSegment(p1, p2)) + # if individualEdges: + # name = "e%04d" % i + # Part.show(Part.Edge(geom), name) + #geomList.append(Part.Edge(geom)) + if geomList: + return geomList + + def areConnected(seg1, seg2): + ''' + Checks if two segments share an endpoint. + returns a new linesegment if connected or original seg1 if not + ''' + l1 = [seg1.StartPoint, seg1.EndPoint] + l2 = [seg2.StartPoint, seg2.EndPoint] + l3 = [v1 for v1 in l1 for v2 in l2 if PathGeom.pointsCoincide (v1, v2, error=0.01)] + # for v1 in l1: + # for v2 in l2: + # if PathGeom.pointsCoincide(v1, v2): + # l3.append(v1) + #l3 = [value for value in l1 if value in l2] + print('l1: {} l2: {} l3: {}'.format(l1,l2,l3)) + if len(l3) == 0: # no connection + print('no connection') + return seg1 + elif len(l3) == 1: # extend chain + print('one vert') + p1 = l1[0] if l1[0] == l3[0] else l1[1] + p2 = l2[0] if l2[0] == l3[0] else l2[1] + return Part.LineSegment(p1, p2) + else: # loop + print('loop') + return None + + def chains(seglist): + ''' + iterates through segements and builds a list of chains + ''' + + chains = [] + while len(seglist) > 0: + cur_seg = seglist.pop(0) + cur_chain = [cur_seg] + remaining = [] + tempseg = cur_seg # tempseg is a linesegment from first vertex to last in curchain + for i, seg in enumerate(seglist): + + ac = areConnected(tempseg, seg) + if ac != tempseg: + cur_chain.append(seg) + if ac is None: + remaining.extend(seglist[i+1:]) + break + else: + tempseg = ac + + #print("c: {}".format(cur_chain)) + + chains.append(cur_chain) + seglist = remaining + + return chains + + def cutWire(w): path = [] - maw = ovd.MedialAxisWalk( vd.getGraph() ) - toolpath = maw.walk() - for chain in toolpath: - path.append(Path.Command("G0 Z{}".format(safeheight))) - p = chain[0][0][0] - z = -(chain[0][0][1]) - - path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, safeheight))) - - for step in chain: - for point in step: - p = point[0] - z = calculate_depth(-(point[1])) - path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, z, obj.ToolController.HorizFeed.Value))) - - path.append(Path.Command("G0 Z{}".format(safeheight))) + p = w.Vertexes[0] + path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) + path.append(Path.Command("G0 X{} Y{} Z{}".format(p.X, p.Y, obj.SafeHeight.Value))) + # print('\/ \/ \/') + # print(p.Point) + c = Path.Command("G1 X{} Y{} Z{} F{}".format(p.X, p.Y, p.Z, obj.ToolController.HorizFeed.Value)) + # print(c) + # print('/\ /\ /\ ') + path.append(c) + #path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.X, p.Y, p.Z, obj.ToolController.HorizFeed.Value))) + for vert in w.Vertexes[1:]: + path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(vert.X, vert.Y, vert.Z, obj.ToolController.HorizFeed.Value))) + path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) return path pathlist = [] - bins = 120 # int bins = number of bins for grid-search (affects performance, should not affect correctness) + pathlist.append(Path.Command("(starting)")) for f in Faces: - #unitcircle = f.BoundBox.DiagonalLength/2 - print('unitcircle: {}'.format(unitcircle)) - vd = ovd.VoronoiDiagram(200, bins) - vd.set_silent(True) # suppress Warnings! - wires = f.Wires - insert_many_wires(vd, wires) - pi = ovd.PolygonInterior( True ) - vd.filter_graph(pi) - ma = ovd.MedialAxis() - vd.filter_graph(ma) - pathlist.extend(buildMedial( vd )) # the actual cutting g-code + vd = Path.Voronoi() + insert_many_wires(vd, f.Wires) + + vd.construct() + # vd.colorExterior(1) + # vd.colorTwins(2) + + for e in vd.Edges: + e.Color = 0 if e.isPrimary() else 5 + vd.colorExterior(1) + vd.colorExterior(4, lambda v: not f.isInside(v.toGeom(), 0.01, True)) # should derive tolerance from geometry + vd.colorColinear(3) + vd.colorTwins(2) + + edgelist = getEdges(vd) + # for e in edgelist: + # Part.show(e.toShape()) + + # for e in [e_ for e_ in vd.Edges if e_.Color == 2]: + # print(e.getDistances()) + # p1 = e.Vertices[0].toGeom(calculate_depth(0-e.getDistances()[0])) + # p2 = e.Vertices[-1].toGeom(calculate_depth(0-e.getDistances()[-1])) + # edgelist.append(Part.makeLine(p1, p2)) + + # vlist = [] + # for v, r in zip(e.Vertices, e.getDistances()): + # p = v.toGeom() + # p.z = calculate_depth(r) + # vlist.append(p) + # l = Part.makeLine(vlist[0], vlist[-1]) + # edgelist.append(l) + + # for s in Part.sortEdges(edgelist): + # pathlist.extend(cutWire(Part.Wire(s))) + + for chain in chains(edgelist): + print('chain length {}'.format(len(chain))) + print(chain) + Part.show(Part.Wire([e.toShape() for e in chain])) + + #pathlist.extend(sortedWires) # the actual cutting g-code self.commandlist = pathlist - - def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() # Openvoronoi must be installed - try: - import openvoronoi as ovd - except: - FreeCAD.Console.PrintError( - translate("Path_Vcarve", "This operation requires OpenVoronoi to be installed.") + "\n") - return - - - job = PathUtils.findParentJob(obj) - - jobshapes = [] - zValues = self.getZValues(obj) if obj.ToolController.Tool.ToolType != 'Engraver': FreeCAD.Console.PrintError( @@ -199,62 +314,23 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") return try: - if len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \ + 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() - # self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) - - # we only consider the outer wire if this is a Face modelshape = self.model[0].Shape - modelshape.tessellate(0.01) - self.buildPathMedial(obj, modelshape.Faces, zValues, modelshape.BoundBox.DiagonalLength/2) - # self.wires = wires - - # elif obj.Base: - # PathLog.track() - # wires = [] - # for base, subs in obj.Base: - # edges = [] - # basewires = [] - # for feature in subs: - # sub = base.Shape.getElement(feature) - # if type(sub) == Part.Edge: - # edges.append(sub) - # elif sub.Wires: - # basewires.extend(sub.Wires) - # else: - # basewires.append(Part.Wire(sub.Edges)) - - # for edgelist in Part.sortEdges(edges): - # basewires.append(Part.Wire(edgelist)) - - # wires.extend(basewires) - # self.buildpathocc(obj, wires, zValues) - # self.wires = wires - # elif not obj.BaseShapes: - # PathLog.track() - # if not obj.Base and not obj.BaseShapes: - # for base in self.model: - # PathLog.track(base.Label) - # if base.isDerivedFrom('Part::Part2DObject'): - # jobshapes.append(base) - - # if not jobshapes: - # raise ValueError(translate('PathVcarve', "Unknown baseobject type for engraving (%s)") % (obj.Base)) - - # if obj.BaseShapes or jobshapes: - # PathLog.track() - # wires = [] - # for shape in obj.BaseShapes + jobshapes: - # PathLog.track(shape.Label) - # shapeWires = shape.Shape.Wires - # self.buildpathocc(obj, shapeWires, zValues) - # wires.extend(shapeWires) - # self.wires = wires - # # the last command is a move to clearance, which is automatically added by PathOp - # if self.commandlist: - # self.commandlist.pop() + self.buildPathMedial(obj, modelshape.Faces) except Exception as e: PathLog.error(e) @@ -266,13 +342,14 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): job = PathUtils.findParentJob(obj) self.opSetDefaultValues(obj, job) -def SetupProperties(): - return [ "Discretize" ] -def Create(name, obj = None): +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) proxy = ObjectVcarve(obj, name) return obj - diff --git a/src/Mod/Path/PathScripts/PathVcarveGui.py b/src/Mod/Path/PathScripts/PathVcarveGui.py index 0ece507d45..34e2073002 100644 --- a/src/Mod/Path/PathScripts/PathVcarveGui.py +++ b/src/Mod/Path/PathScripts/PathVcarveGui.py @@ -27,7 +27,6 @@ import FreeCADGui import PathScripts.PathVcarve as PathVcarve import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui -import PathScripts.PathSelection as PathSelection import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui @@ -43,9 +42,11 @@ if False: 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.''' @@ -59,10 +60,10 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): 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)) + 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)) + PathLog.notice((translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label)) continue if base.isDerivedFrom('Part::Part2DObject'): if sel.HasSubObjects: @@ -74,7 +75,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): 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] + self.obj.Base = [(p, el) for p, el in self.obj.Base if p != base] shapes.append(base) self.obj.BaseShapes = shapes added = True @@ -108,6 +109,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): self.obj.BaseShapes = shapes return self.super().updateBase() + class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Vcarve operation.''' @@ -119,17 +121,21 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''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 @@ -137,11 +143,9 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''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"), + +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) From a1852d1106195a46deea88b7d3bb07c2b5db5dbc Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 15 Sep 2020 13:23:17 -0500 Subject: [PATCH 24/33] Rewrite for boost voronoi --- src/Mod/Path/PathScripts/PathVcarve.py | 265 +++++++++---------------- 1 file changed, 98 insertions(+), 167 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 55ca61d6c9..e4f30fe78c 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -2,7 +2,7 @@ # *************************************************************************** # * * -# * Copyright (c) 2014 Yorik van Havre * +# * 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) * @@ -30,15 +30,17 @@ 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 time -from PathScripts.PathOpTools import orientWire + import math from PySide import QtCore __doc__ = "Class and implementation of Path Vcarve operation" + if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) @@ -60,18 +62,30 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): 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.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.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 threshold for removing extraneous segments (0-1.0). default=0.8. Larger numbers remove less.")) - obj.Threshold = 0.8 + 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): @@ -80,30 +94,8 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): def buildPathMedial(self, obj, Faces): '''constructs a medial axis path using openvoronoi''' - #import openvoronoi as ovd - - # def insert_wire_points(vd, wire): - # pts = [] - # for p in wire.Vertexes: - # pts.append(ovd.Point(p.X, p.Y)) - # PathLog.debug('ovd.Point( {} ,{} )'.format(p.X, p.Y)) - # id_list = [] - # PathLog.debug("inserting {} openvoronoi point-sites".format(len(pts))) - # for p in pts: - # id_list.append(vd.addVertexSite(p)) - # return id_list - - # def insert_wire_segments(vd, id_list): - # PathLog.debug('inserting {} segments into the voronoi diagram'.format(len(id_list))) - # for n in range(len(id_list)): - # n_nxt = n + 1 - # if n == (len(id_list) - 1): - # n_nxt = 0 - # vd.addLineSite(id_list[n], id_list[n_nxt]) def insert_many_wires(vd, wires): - #polygon_ids = [] - #t_before = time.time() for wire in wires: PathLog.debug('discretize value: {}'.format(obj.Discretize)) pts = wire.discretize(QuasiDeflection=obj.Discretize) @@ -113,52 +105,14 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for i in range(len(pts)): vd.addSegment(ptv[i], ptv[i+1]) - # segwire = Part.Wire([Part.makeLine(p[0], p[1]) for p in zip(pointList, pointList[1:])]) - - # if idx == 0: - # segwire = orientWire(segwire, forward=False) - # else: - # segwire = orientWire(segwire, forward=True) - - # poly_id = insert_wire_points(vd, segwire) - # polygon_ids.append(poly_id) - # t_after = time.time() - # pt_time = t_after - t_before - - # t_before = time.time() - # for ids in polygon_ids: - # insert_wire_segments(vd, ids) - # t_after = time.time() - # seg_time = t_after - t_before - # return [pt_time, seg_time] - def calculate_depth(MIC): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut. maxdepth = obj.ToolController.Tool.CuttingEdgeHeight toolangle = obj.ToolController.Tool.CuttingEdgeAngle - d = MIC / math.tan(math.radians(toolangle / 2)) + d = round(MIC / math.tan(math.radians(toolangle / 2)), 4) return d if d <= maxdepth else maxdepth - # def buildMedial(vd): - # safeheight = obj.SafeHeight.Value - # path = [] - # maw = ovd.MedialAxisWalk(vd.getGraph()) - # toolpath = maw.walk() - # for chain in toolpath: - - # path.append(Path.Command("G0 Z{}".format(safeheight))) - # p = chain[0][0][0] - # z = -(chain[0][0][1]) - - # path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, safeheight))) - - # for step in chain: - # for point in step: - # p = point[0] - # z = calculate_depth(-(point[1])) - # path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, z, obj.ToolController.HorizFeed.Value))) -# path.append(Path.Command("G0 Z{}".format(safeheight))) return path pathlist = [] def getEdges(vd, color=[0]): if type(color) == int: color = [color] @@ -166,90 +120,87 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for e in vd.Edges: if e.Color not in color: continue - # geom = e.toGeom(8) if e.toGeom(8) 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])) - geomList.append(Part.LineSegment(p1, p2)) - # if individualEdges: - # name = "e%04d" % i - # Part.show(Part.Edge(geom), name) - #geomList.append(Part.Edge(geom)) + newedge = Part.Edge(Part.Vertex(p1), Part.Vertex(p2)) + + newedge.fixTolerance(obj.Tolerance, Part.Vertex) + geomList.append(newedge) + if geomList: return geomList - def areConnected(seg1, seg2): - ''' - Checks if two segments share an endpoint. - returns a new linesegment if connected or original seg1 if not - ''' - l1 = [seg1.StartPoint, seg1.EndPoint] - l2 = [seg2.StartPoint, seg2.EndPoint] - l3 = [v1 for v1 in l1 for v2 in l2 if PathGeom.pointsCoincide (v1, v2, error=0.01)] - # for v1 in l1: - # for v2 in l2: - # if PathGeom.pointsCoincide(v1, v2): - # l3.append(v1) - #l3 = [value for value in l1 if value in l2] - print('l1: {} l2: {} l3: {}'.format(l1,l2,l3)) - if len(l3) == 0: # no connection - print('no connection') - return seg1 - elif len(l3) == 1: # extend chain - print('one vert') - p1 = l1[0] if l1[0] == l3[0] else l1[1] - p2 = l2[0] if l2[0] == l3[0] else l2[1] - return Part.LineSegment(p1, p2) - else: # loop - print('loop') - return None + def sortEm(mywire, unmatched): + remaining = [] + wireGrowing = False - def chains(seglist): - ''' - iterates through segements and builds a list of chains - ''' + # 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): + if candidate.Length < obj.Tolerance: + continue + + # end points of candidate edge + cverts = [candidate.Edges[0].valueAt(candidate.Edges[0].FirstParameter), + candidate.Edges[-1].valueAt(candidate.Edges[-1].LastParameter)] + + # 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(seglist) > 0: - cur_seg = seglist.pop(0) - cur_chain = [cur_seg] - remaining = [] - tempseg = cur_seg # tempseg is a linesegment from first vertex to last in curchain - for i, seg in enumerate(seglist): + while len(candidateList) > 0: + cur_wire = Part.Wire(candidateList.pop(0)) - ac = areConnected(tempseg, seg) - if ac != tempseg: - cur_chain.append(seg) - if ac is None: - remaining.extend(seglist[i+1:]) - break - else: - tempseg = ac + wireGrowing = True + while wireGrowing: + cur_wire, candidateList, wireGrowing = sortEm(cur_wire, + candidateList) - #print("c: {}".format(cur_chain)) - - chains.append(cur_chain) - seglist = remaining + chains.append(cur_wire) return chains def cutWire(w): path = [] - p = w.Vertexes[0] path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) - path.append(Path.Command("G0 X{} Y{} Z{}".format(p.X, p.Y, obj.SafeHeight.Value))) - # print('\/ \/ \/') - # print(p.Point) - c = Path.Command("G1 X{} Y{} Z{} F{}".format(p.X, p.Y, p.Z, obj.ToolController.HorizFeed.Value)) - # print(c) - # print('/\ /\ /\ ') + 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) - #path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.X, p.Y, p.Z, obj.ToolController.HorizFeed.Value))) - for vert in w.Vertexes[1:]: - path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(vert.X, vert.Y, vert.Z, obj.ToolController.HorizFeed.Value))) + for e in w.Edges: + path.extend(PathGeom.cmdsForEdge(e, + hSpeed=obj.ToolController.HorizFeed.Value)) - path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) return path pathlist = [] @@ -259,59 +210,36 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): insert_many_wires(vd, f.Wires) vd.construct() - # vd.colorExterior(1) - # vd.colorTwins(2) for e in vd.Edges: e.Color = 0 if e.isPrimary() else 5 vd.colorExterior(1) - vd.colorExterior(4, lambda v: not f.isInside(v.toGeom(), 0.01, True)) # should derive tolerance from geometry - vd.colorColinear(3) + vd.colorExterior(4, lambda v: not f.isInside(v.toGeom(), + obj.Tolerance, True)) + vd.colorColinear(3, obj.Threshold) vd.colorTwins(2) edgelist = getEdges(vd) - # for e in edgelist: - # Part.show(e.toShape()) - # for e in [e_ for e_ in vd.Edges if e_.Color == 2]: - # print(e.getDistances()) - # p1 = e.Vertices[0].toGeom(calculate_depth(0-e.getDistances()[0])) - # p2 = e.Vertices[-1].toGeom(calculate_depth(0-e.getDistances()[-1])) - # edgelist.append(Part.makeLine(p1, p2)) - - # vlist = [] - # for v, r in zip(e.Vertices, e.getDistances()): - # p = v.toGeom() - # p.z = calculate_depth(r) - # vlist.append(p) - # l = Part.makeLine(vlist[0], vlist[-1]) - # edgelist.append(l) - - # for s in Part.sortEdges(edgelist): - # pathlist.extend(cutWire(Part.Wire(s))) - - for chain in chains(edgelist): - print('chain length {}'.format(len(chain))) - print(chain) - Part.show(Part.Wire([e.toShape() for e in chain])) - - #pathlist.extend(sortedWires) # the actual cutting g-code + for wire in getWires(edgelist): + pathlist.extend(cutWire(wire)) self.commandlist = pathlist def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() - # Openvoronoi must be installed if obj.ToolController.Tool.ToolType != 'Engraver': FreeCAD.Console.PrintError( - translate("Path_Vcarve", "This operation requires an engraver tool.") + "\n") + translate("Path_Vcarve", + "This operation requires an engraver tool.") + "\n") return if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: FreeCAD.Console.PrintError( - translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") + translate("Path_Vcarve", + "Engraver Cutting Edge Angle must be < 180 degrees.") + "\n") return try: if obj.Base: @@ -335,10 +263,13 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): 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.')) + 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''' + '''updateDepths(obj) ... engraving is always done at \ + the top most z-value''' job = PathUtils.findParentJob(obj) self.opSetDefaultValues(obj, job) From b1e5b5a902072b21c5085bbced3a682a319c13ff Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 19 Sep 2020 10:03:09 -0500 Subject: [PATCH 25/33] Path: Vcarve fixes formatting fixes remove reference to old ToolType --- src/Mod/Path/Gui/Resources/Path.qrc | 4 +-- .../Path/Gui/Resources/icons/Path-Vcarve.svg | 28 ++++++++++++++----- src/Mod/Path/PathScripts/PathVcarve.py | 6 ---- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 44b066cd0c..2e0873a2be 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -66,7 +66,7 @@ icons/Path-ToolController.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg - icons/Path-Vcarve.svg + icons/Path-Vcarve.svg icons/Path-Waterline.svg icons/arrow-ccw.svg icons/arrow-cw.svg @@ -112,7 +112,7 @@ panels/PageOpSlotEdit.ui panels/PageOpSurfaceEdit.ui panels/PageOpWaterlineEdit.ui - panels/PageOpVcarveEdit.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 index 0cf0716a94..87788da135 100644 --- a/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg +++ b/src/Mod/Path/Gui/Resources/icons/Path-Vcarve.svg @@ -14,7 +14,7 @@ height="64px" id="svg2816" version="1.1" - inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + inkscape:version="0.92.3 (2405546, 2018-03-11)" sodipodi:docname="Path-Vcarve.svg"> @@ -566,10 +566,10 @@ inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:object-nodes="true" - inkscape:window-width="1165" - inkscape:window-height="1047" - inkscape:window-x="1829" - inkscape:window-y="1050" + inkscape:window-width="1076" + inkscape:window-height="605" + inkscape:window-x="0" + inkscape:window-y="1287" inkscape:window-maximized="0" showguides="true" inkscape:guide-bbox="true" @@ -595,7 +595,7 @@ image/svg+xml - + Path-Engrave 2016-02-24 http://www.freecadweb.org/wiki/index.php?title=Artwork @@ -610,13 +610,27 @@ FreeCAD LGPL2+ - https://www.gnu.org/copyleft/lesser.html + https://www.gnu.org/copyleft/lesser.html [agryson] Alexander Gryson + + + + + + + = 180.0: FreeCAD.Console.PrintError( translate("Path_Vcarve", From 8758cfce9f443ee90ab0c08da76aa994dad79f8d Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 21 Sep 2020 17:02:23 -0500 Subject: [PATCH 26/33] Resolve UI issues taskpanel threshold value add constants to PathVoronoi calls check for tool properties set correct z height coloring exterior lines --- .../Gui/Resources/panels/PageOpVcarveEdit.ui | 35 +++++++------------ src/Mod/Path/PathScripts/PathVcarve.py | 32 +++++++++++------ 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui index b10575eea4..21316a699c 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpVcarveEdit.ui @@ -81,28 +81,6 @@ - - - - <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> - - - 2 - - - 0.000000000000000 - - - 1.000000000000000 - - - 0.100000000000000 - - - 0.800000000000000 - - - @@ -113,6 +91,19 @@ + + + + <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 + + + diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 29e81523ed..0273c8e190 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -40,6 +40,12 @@ from PySide import QtCore __doc__ = "Class and implementation of Path Vcarve operation" +PRIMARY = 0 +EXTERIOR1 = 1 +EXTERIOR2 = 4 +TWIN = 2 +COLINEAR = 3 +OTHER = 5 if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -113,14 +119,14 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): d = round(MIC / math.tan(math.radians(toolangle / 2)), 4) return d if d <= maxdepth else maxdepth - def getEdges(vd, color=[0]): + 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(8) is None: + 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])) @@ -129,8 +135,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): newedge.fixTolerance(obj.Tolerance, Part.Vertex) geomList.append(newedge) - if geomList: - return geomList + return geomList def sortEm(mywire, unmatched): remaining = [] @@ -212,12 +217,13 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): vd.construct() for e in vd.Edges: - e.Color = 0 if e.isPrimary() else 5 - vd.colorExterior(1) - vd.colorExterior(4, lambda v: not f.isInside(v.toGeom(), + e.Color = PRIMARY if e.isPrimary() else OTHER + vd.colorExterior(EXTERIOR1) + vd.colorExterior(EXTERIOR2, + lambda v: not f.isInside(v.toGeom(f.BoundBox.ZMin), obj.Tolerance, True)) - vd.colorColinear(3, obj.Threshold) - vd.colorTwins(2) + vd.colorColinear(COLINEAR, obj.Threshold) + vd.colorTwins(TWIN) edgelist = getEdges(vd) @@ -230,6 +236,12 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): '''opExecute(obj) ... process engraving operation''' PathLog.track() + if not (hasattr(obj.ToolController.Tool, "CuttingEdgeAngle") and + hasattr(obj.ToolController.Tool, "CuttingEdgeHeight")): + FreeCAD.Console.PrintError( + translate("Path_Vcarve", "VCarve requires an engraving \ + cutter with CuttingEdgeAngle and CuttingEdgeHeight") + "\n") + if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: FreeCAD.Console.PrintError( translate("Path_Vcarve", @@ -276,5 +288,5 @@ def Create(name, obj=None): '''Create(name) ... Creates and returns a Vcarve operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - proxy = ObjectVcarve(obj, name) + ObjectVcarve(obj, name) return obj From d22bfecdadbf8634e01d64df4aa6281a5d4664b1 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 22 Sep 2020 16:54:36 -0500 Subject: [PATCH 27/33] improve selection and avoid border case with ultra short segments --- src/Mod/Path/PathScripts/PathSelection.py | 12 ++++++++++-- src/Mod/Path/PathScripts/PathVcarve.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 657f4519ce..f6483f8505 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -57,13 +57,21 @@ class VCARVEGate: if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0: return True - if shape.ShapeType == 'Edge': + 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 True + return False return False diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 0273c8e190..fb4b04969c 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -47,7 +47,7 @@ TWIN = 2 COLINEAR = 3 OTHER = 5 -if False: +if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: @@ -146,13 +146,15 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): mywire.Edges[-1].valueAt(mywire.Edges[-1].LastParameter)] for i, candidate in enumerate(unmatched): - if candidate.Length < obj.Tolerance: - continue # 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. @@ -165,8 +167,10 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): wireGrowing = True elist = mywire.Edges otherIndex = int(not(idx)) + newedge = Part.Edge(Part.Vertex(wvert), - Part.Vertex(cverts[otherIndex])) + Part.Vertex(cverts[otherIndex])) + elist.append(newedge) mywire = Part.Wire(Part.__sortEdges__(elist)) remaining.extend(unmatched[i+1:]) From 1492aa01b506fd4b9282c5c80f6e8ea488f6e24c Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 23 Sep 2020 14:34:57 -0500 Subject: [PATCH 28/33] remove need for cuttingedgeheight Calculating from diameter and cuttingedgeangle --- src/Mod/Path/PathScripts/PathVcarve.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index fb4b04969c..6501cdc802 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -114,8 +114,11 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): def calculate_depth(MIC): # given a maximum inscribed circle (MIC) and tool angle, # return depth of cut. - maxdepth = obj.ToolController.Tool.CuttingEdgeHeight + + 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 @@ -240,11 +243,10 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): '''opExecute(obj) ... process engraving operation''' PathLog.track() - if not (hasattr(obj.ToolController.Tool, "CuttingEdgeAngle") and - hasattr(obj.ToolController.Tool, "CuttingEdgeHeight")): + if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): FreeCAD.Console.PrintError( translate("Path_Vcarve", "VCarve requires an engraving \ - cutter with CuttingEdgeAngle and CuttingEdgeHeight") + "\n") + cutter with CuttingEdgeAngle") + "\n") if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: FreeCAD.Console.PrintError( From 81dc724b4acefc7f46cce1928d41f705af04516b Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 23 Sep 2020 14:38:41 -0500 Subject: [PATCH 29/33] move vcarve out of experimental --- src/Mod/Path/InitGui.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index b630732886..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", @@ -113,7 +113,6 @@ class PathWorkbench (Workbench): threedcmdgroup = threedopcmdlist if PathPreferences.experimentalFeaturesEnabled(): projcmdlist.append("Path_Sanity") - engravecmdlist.append("Path_Vcarve") prepcmdlist.append("Path_Shape") extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) From af1dacf57f365a4d40f5535081c430259e8caac3 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 28 Sep 2020 12:12:51 -0500 Subject: [PATCH 30/33] rename OTHER --- src/Mod/Path/PathScripts/PathVcarve.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 6501cdc802..7a6298d653 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -45,7 +45,7 @@ EXTERIOR1 = 1 EXTERIOR2 = 4 TWIN = 2 COLINEAR = 3 -OTHER = 5 +SECONDARY = 5 if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -58,6 +58,7 @@ else: def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) +VD = [] class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' @@ -215,6 +216,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): return path + VD.clear() pathlist = [] pathlist.append(Path.Command("(starting)")) for f in Faces: @@ -224,7 +226,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): vd.construct() for e in vd.Edges: - e.Color = PRIMARY if e.isPrimary() else OTHER + e.Color = PRIMARY if e.isPrimary() else SECONDARY vd.colorExterior(EXTERIOR1) vd.colorExterior(EXTERIOR2, lambda v: not f.isInside(v.toGeom(f.BoundBox.ZMin), @@ -236,6 +238,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for wire in getWires(edgelist): pathlist.extend(cutWire(wire)) + VD.append((f, vd, getWires(edgelist))) self.commandlist = pathlist From ebae99c9e04b531ab8e9bbb658c57bbbdb8215f7 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 29 Sep 2020 14:03:37 -0500 Subject: [PATCH 31/33] whitespace --- src/Mod/Path/PathScripts/PathVcarve.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 7a6298d653..2ac5491d88 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -60,6 +60,7 @@ def translate(context, text, disambig=None): VD = [] + class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' From e3c476af218212c25e082dca29c13eaeb34fc1b6 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Sep 2020 12:54:57 -0700 Subject: [PATCH 32/33] Unified voronoi indices for python to be of type long --- src/Mod/Path/App/VoronoiCellPy.xml | 6 +++--- src/Mod/Path/App/VoronoiCellPyImp.cpp | 10 +++++----- src/Mod/Path/App/VoronoiEdgePy.xml | 4 ++-- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 6 +++--- src/Mod/Path/App/VoronoiVertexPy.xml | 4 ++-- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Mod/Path/App/VoronoiCellPy.xml b/src/Mod/Path/App/VoronoiCellPy.xml index ffc4b57a2b..6bce1f0b0c 100644 --- a/src/Mod/Path/App/VoronoiCellPy.xml +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -20,19 +20,19 @@ Internal id of the element. - + Assigned color of the receiver. - + Returns the index of the cell's source - + diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index 95c8328866..bf4e998aff 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -111,12 +111,12 @@ VoronoiCell* getVoronoiCellFromPy(const VoronoiCellPy *c, PyObject *args = 0) { return self; } -Py::Int VoronoiCellPy::getIndex(void) const { +Py::Long VoronoiCellPy::getIndex(void) const { VoronoiCell *c = getVoronoiCellPtr(); if (c->isBound()) { - return Py::Int(c->dia->index(c->ptr)); + return Py::Long(c->dia->index(c->ptr)); } - return Py::Int(-1); + return Py::Long(-1); } Py::Long VoronoiCellPy::getColor(void) const { @@ -131,10 +131,10 @@ void VoronoiCellPy::setColor(Py::Long color) { getCellFromPy(this)->color(int(color) & Voronoi::ColorMask); } -Py::Int VoronoiCellPy::getSourceIndex(void) const +Py::Long VoronoiCellPy::getSourceIndex(void) const { VoronoiCell *c = getVoronoiCellFromPy(this); - return Py::Int(c->ptr->source_index()); + return Py::Long(c->ptr->source_index()); } Py::Int VoronoiCellPy::getSourceCategory(void) const diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index daf107b7a2..08324ff2ff 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -20,13 +20,13 @@ Internal id of the element. - + Assigned color of the receiver. - + diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 7ca2f85251..91a3d50268 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -130,12 +130,12 @@ VoronoiEdge* getVoronoiEdgeFromPy(const VoronoiEdgePy *e, PyObject *args = 0) { return self; } -Py::Int VoronoiEdgePy::getIndex(void) const { +Py::Long VoronoiEdgePy::getIndex(void) const { VoronoiEdge *e = getVoronoiEdgePtr(); if (e->isBound()) { - return Py::Int(e->dia->index(e->ptr)); + return Py::Long(e->dia->index(e->ptr)); } - return Py::Int(-1); + return Py::Long(-1); } Py::Long VoronoiEdgePy::getColor(void) const { diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index ef5eca069f..7cdd517b8b 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -20,13 +20,13 @@ Internal id of the element. - + Assigned color of the receiver. - + diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index beef937908..c03fa30b55 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -113,12 +113,12 @@ VoronoiVertex* getVoronoiVertexFromPy(const VoronoiVertexPy *v, PyObject *args = } -Py::Int VoronoiVertexPy::getIndex(void) const { +Py::Long VoronoiVertexPy::getIndex(void) const { VoronoiVertex *v = getVoronoiVertexPtr(); if (v->isBound()) { - return Py::Int(v->dia->index(v->ptr)); + return Py::Long(v->dia->index(v->ptr)); } - return Py::Int(-1); + return Py::Long(-1); } Py::Long VoronoiVertexPy::getColor(void) const { From e97f7a8a5481c195b1dc99a35a877eec192e1491 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Sep 2020 17:02:57 -0700 Subject: [PATCH 33/33] More integer type cleanup for the voronoi python interface --- src/Mod/Path/App/VoronoiCellPyImp.cpp | 2 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 2 +- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index bf4e998aff..8e780f2d57 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -128,7 +128,7 @@ Py::Long VoronoiCellPy::getColor(void) const { } void VoronoiCellPy::setColor(Py::Long color) { - getCellFromPy(this)->color(int(color) & Voronoi::ColorMask); + getCellFromPy(this)->color(long(color) & Voronoi::ColorMask); } Py::Long VoronoiCellPy::getSourceIndex(void) const diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 91a3d50268..6043754b60 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -147,7 +147,7 @@ Py::Long VoronoiEdgePy::getColor(void) const { } void VoronoiEdgePy::setColor(Py::Long color) { - getEdgeFromPy(this)->color(int(color) & Voronoi::ColorMask); + getEdgeFromPy(this)->color(long(color) & Voronoi::ColorMask); } Py::List VoronoiEdgePy::getVertices(void) const diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index c03fa30b55..837c2efea4 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -130,7 +130,7 @@ Py::Long VoronoiVertexPy::getColor(void) const { } void VoronoiVertexPy::setColor(Py::Long color) { - getVertexFromPy(this)->color(int(color) & Voronoi::ColorMask); + getVertexFromPy(this)->color(long(color) & Voronoi::ColorMask); } Py::Float VoronoiVertexPy::getX(void) const