From d3615f7e541997bc5465c4a3728a119229108dd0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 13 Oct 2020 20:34:03 -0700 Subject: [PATCH 01/12] Added equal/not equal comparison to voronoi python objects with unit tests. --- src/Mod/Path/App/VoronoiCellPyImp.cpp | 6 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 10 +-- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 6 +- src/Mod/Path/PathTests/TestPathVoronoi.py | 89 +++++++++++++++++++++++ src/Mod/Path/TestPathApp.py | 2 + 5 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 src/Mod/Path/PathTests/TestPathVoronoi.py diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index 113f248c0b..9bae03a385 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -75,14 +75,14 @@ int VoronoiCellPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyObject* VoronoiCellPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { - PyObject *cmp = Py_False; + PyObject *cmp = (op == Py_EQ) ? Py_False : Py_True; if ( PyObject_TypeCheck(lhs, &VoronoiCellPy::Type) && PyObject_TypeCheck(rhs, &VoronoiCellPy::Type) - && op == Py_EQ) { + && (op == Py_EQ || op == Py_NE)) { 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; + cmp = (op == Py_EQ) ? Py_True : Py_False; } } Py_INCREF(cmp); diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 451eca0220..8da24deab6 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -92,16 +92,14 @@ int VoronoiEdgePy::PyInit(PyObject* args, PyObject* /*kwd*/) PyObject* VoronoiEdgePy::richCompare(PyObject *lhs, PyObject *rhs, int op) { - PyObject *cmp = Py_False; + PyObject *cmp = (op == Py_EQ) ? Py_False : Py_True; if ( PyObject_TypeCheck(lhs, &VoronoiEdgePy::Type) && PyObject_TypeCheck(rhs, &VoronoiEdgePy::Type) - && op == Py_EQ) { + && (op == Py_EQ || op == Py_NE)) { 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; + if (vl->dia == vr->dia && vl->index == vr->index) { + cmp = (op == Py_EQ) ? Py_True : Py_False; } } Py_INCREF(cmp); diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 45e74a04b2..02b1c393d1 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -76,14 +76,14 @@ int VoronoiVertexPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyObject* VoronoiVertexPy::richCompare(PyObject *lhs, PyObject *rhs, int op) { - PyObject *cmp = Py_False; + PyObject *cmp = (op == Py_EQ) ? Py_False : Py_True; if ( PyObject_TypeCheck(lhs, &VoronoiVertexPy::Type) && PyObject_TypeCheck(rhs, &VoronoiVertexPy::Type) - && op == Py_EQ) { + && (op == Py_EQ || op == Py_NE)) { 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; + cmp = (op == Py_EQ) ? Py_True : Py_False; } } Py_INCREF(cmp); diff --git a/src/Mod/Path/PathTests/TestPathVoronoi.py b/src/Mod/Path/PathTests/TestPathVoronoi.py new file mode 100644 index 0000000000..a06c8437c1 --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathVoronoi.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2020 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import Path +import PathTests.PathTestUtils as PathTestUtils + +vd = None + +def initVD(): + global vd + if vd is None: + pts = [(0,0), (3.5,0), (3.5,1), (1,1), (1,2), (2.5,2), (2.5,3), (1,3), (1,4), (3.5, 4), (3.5,5), (0,5)] + ptv = [FreeCAD.Vector(p[0], p[1]) for p in pts] + ptv.append(ptv[0]) + + vd = Path.Voronoi() + for i in range(len(pts)): + vd.addSegment(ptv[i], ptv[i+1]) + + vd.construct() + + for e in vd.Edges: + e.Color = 0 if e.isPrimary() else 1; + vd.colorExterior(2) + vd.colorColinear(3) + vd.colorTwins(4) + + +class TestPathVoronoi(PathTestUtils.PathTestBase): + + def setUp(self): + initVD() + + def test00(self): + '''Check vertex comparison''' + + self.assertTrue( vd.Vertices[0] == vd.Vertices[0]) + self.assertTrue( vd.Vertices[1] == vd.Vertices[1]) + self.assertTrue( vd.Vertices[0] != vd.Vertices[1]) + self.assertEqual( vd.Vertices[0], vd.Vertices[0]) + self.assertEqual( vd.Vertices[1], vd.Vertices[1]) + self.assertNotEqual(vd.Vertices[0], vd.Vertices[1]) + self.assertNotEqual(vd.Vertices[1], vd.Vertices[0]) + + def test10(self): + '''Check edge comparison''' + + self.assertTrue( vd.Edges[0] == vd.Edges[0]) + self.assertTrue( vd.Edges[1] == vd.Edges[1]) + self.assertTrue( vd.Edges[0] != vd.Edges[1]) + self.assertEqual( vd.Edges[0], vd.Edges[0]) + self.assertEqual( vd.Edges[1], vd.Edges[1]) + self.assertNotEqual(vd.Edges[0], vd.Edges[1]) + self.assertNotEqual(vd.Edges[1], vd.Edges[0]) + + + def test20(self): + '''Check cell comparison''' + + self.assertTrue( vd.Cells[0] == vd.Cells[0]) + self.assertTrue( vd.Cells[1] == vd.Cells[1]) + self.assertTrue( vd.Cells[0] != vd.Cells[1]) + self.assertEqual( vd.Cells[0], vd.Cells[0]) + self.assertEqual( vd.Cells[1], vd.Cells[1]) + self.assertNotEqual(vd.Cells[0], vd.Cells[1]) + self.assertNotEqual(vd.Cells[1], vd.Cells[0]) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index b5ac4e215c..147cf9a169 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -42,6 +42,7 @@ from PathTests.TestPathToolController import TestPathToolController from PathTests.TestPathSetupSheet import TestPathSetupSheet from PathTests.TestPathDeburr import TestPathDeburr from PathTests.TestPathHelix import TestPathHelix +from PathTests.TestPathVoronoi import TestPathVoronoi # dummy usage to get flake8 and lgtm quiet False if TestApp.__name__ else True @@ -62,4 +63,5 @@ False if TestPathDeburr.__name__ else True False if TestPathHelix.__name__ else True False if TestPathPreferences.__name__ else True False if TestPathToolBit.__name__ else True +False if TestPathVoronoi.__name__ else True From 35da5c890ce524f7e8f80c9c2aaf318a8b05419d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Oct 2020 19:36:06 -0700 Subject: [PATCH 02/12] Replaced toGeom with toPoint for VoronoiVertex and toShape for VoronoiEdge - results in arbitrary orientation of parabola for multiple z --- src/Mod/Path/App/VoronoiEdgePy.xml | 4 +- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 298 ++++++++++++++-------- src/Mod/Path/App/VoronoiVertexPy.xml | 4 +- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 2 +- src/Mod/Path/PathTests/TestPathVoronoi.py | 83 ++++++ 5 files changed, 282 insertions(+), 109 deletions(-) diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index f6ade1eac4..2da04541f4 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -100,9 +100,9 @@ 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 a shape for the edge diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 8da24deab6..8879f061d1 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -24,9 +24,12 @@ #ifndef _PreComp_ -# include -#endif - +#include +#include "BRepLib.hxx" +#include "BRepTools.hxx" +#include "BRepBuilderAPI_MakeEdge.hxx" +#include "BRepBuilderAPI_MakeFace.hxx" +#include "BRepProj_Projection.hxx" #include "Mod/Path/App/Voronoi.h" #include "Mod/Path/App/Voronoi.h" #include "Mod/Path/App/VoronoiCell.h" @@ -40,14 +43,109 @@ #include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // files generated out of VoronoiEdgePy.xml #include "VoronoiEdgePy.cpp" using namespace Path; +namespace { + + Voronoi::point_type orthognalProjection(const Voronoi::point_type &point, const Voronoi::segment_type &segment) { + // move segment so it goes through the origin (s) + Voronoi::point_type offset; + { + offset.x(low(segment).x()); + offset.y(low(segment).y()); + } + Voronoi::point_type s; + { + s.x(high(segment).x() - offset.x()); + s.y(high(segment).y() - offset.y()); + } + // move point accordingly so it maintains it's relation to s (p) + Voronoi::point_type p; + { + p.x(point.x() - offset.x()); + p.y(point.y() - offset.y()); + } + // calculate the orthogonal projection of p onto s + // ((p dot s) / (s dot s)) * s (https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line) + // and it back by original offset to get the projected point + double proj = (p.x() * s.x() + p.y() * s.y()) / (s.x() * s.x() + s.y() * s.y()); + Voronoi::point_type pt; + { + pt.x(offset.x() + proj * s.x()); + pt.y(offset.y() + proj * s.y()); + } + return pt; + } + + template + double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const pt_type &p1, double scale) { + double x = v0.x() - p1.x(); + double y = v0.y() - p1.y(); + return sqrt(x * x + y * y) / scale; + } + + void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list, double scale) { + if (v0) { + list->append(Py::Float(distanceBetween(*v0, p1, scale))); + } else { + Py_INCREF(Py_None); + list->append(Py::asObject(Py_None)); + } + } + + void addProjectedDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::segment_type &segment, Py::List *list, double scale) { + if (v0) { + Voronoi::point_type p0; + { + p0.x(v0->x()); + p0.y(v0->y()); + } + Voronoi::point_type p1 = orthognalProjection(p0, segment); + list->append(Py::Float(distanceBetween(*v0, p1, scale))); + } else { + Py_INCREF(Py_None); + list->append(Py::asObject(Py_None)); + } + } + + bool addDistancesToPoint(const VoronoiEdge *edge, Voronoi::point_type p, Py::List *list, double scale) { + addDistanceBetween(edge->ptr->vertex0(), p, list, scale); + addDistanceBetween(edge->ptr->vertex1(), p, list, scale); + return true; + } + + bool retrieveDistances(const VoronoiEdge *edge, Py::List *list) { + const Voronoi::diagram_type::cell_type *c0 = edge->ptr->cell(); + if (c0->contains_point()) { + return addDistancesToPoint(edge, edge->dia->retrievePoint(c0), list, edge->dia->getScale()); + } + const Voronoi::diagram_type::cell_type *c1 = edge->ptr->twin()->cell(); + if (c1->contains_point()) { + return addDistancesToPoint(edge, edge->dia->retrievePoint(c1), list, edge->dia->getScale()); + } + // at this point both cells are sourced from segments and it does not matter which one we use + Voronoi::segment_type segment = edge->dia->retrieveSegment(c0); + addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list, edge->dia->getScale()); + addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list, edge->dia->getScale()); + return false; + } +} + + // returns a string which represents the object e.g. when printed in python std::string VoronoiEdgePy::representation(void) const { @@ -257,43 +355,15 @@ PyObject* VoronoiEdgePy::isSecondary(PyObject *args) return chk; } -namespace { - Voronoi::point_type orthognalProjection(const Voronoi::point_type &point, const Voronoi::segment_type &segment) { - // move segment so it goes through the origin (s) - Voronoi::point_type offset; - { - offset.x(low(segment).x()); - offset.y(low(segment).y()); - } - Voronoi::point_type s; - { - s.x(high(segment).x() - offset.x()); - s.y(high(segment).y() - offset.y()); - } - // move point accordingly so it maintains it's relation to s (p) - Voronoi::point_type p; - { - p.x(point.x() - offset.x()); - p.y(point.y() - offset.y()); - } - // calculate the orthogonal projection of p onto s - // ((p dot s) / (s dot s)) * s (https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line) - // and it back by original offset to get the projected point - double proj = (p.x() * s.x() + p.y() * s.y()) / (s.x() * s.x() + s.y() * s.y()); - Voronoi::point_type pt; - { - pt.x(offset.x() + proj * s.x()); - pt.y(offset.y() + proj * s.y()); - } - return pt; - } -} - -PyObject* VoronoiEdgePy::toGeom(PyObject *args) +PyObject* VoronoiEdgePy::toShape(PyObject *args) { - double z = 0.0; - if (!PyArg_ParseTuple(args, "|d", &z)) { - throw Py::RuntimeError("single argument of type double accepted"); + double z0 = 0.0; + double z1 = DBL_MAX; + if (!PyArg_ParseTuple(args, "|dd", &z0, &z1)) { + throw Py::RuntimeError("no, one or two arguments of type double accepted"); + } + if (z1 == DBL_MAX) { + z1 = z0; } VoronoiEdge *e = getVoronoiEdgePtr(); if (e->isBound()) { @@ -303,8 +373,10 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) auto v1 = e->ptr->vertex1(); if (v0 && v1) { auto p = new Part::GeomLineSegment; - p->setPoints(e->dia->scaledVector(*v0, z), e->dia->scaledVector(*v1, z)); - return new Part::LineSegmentPy(p); + p->setPoints(e->dia->scaledVector(*v0, z0), e->dia->scaledVector(*v1, z1)); + Handle(Geom_Curve) h = Handle(Geom_Curve)::DownCast(p->handle()); + BRepBuilderAPI_MakeEdge mkBuilder(h, h->FirstParameter(), h->LastParameter()); + return new Part::TopoShapeEdgePy(new Part::TopoShape(mkBuilder.Shape())); } } else { // infinite linear, need to clip somehow @@ -350,8 +422,10 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) end.y(origin.y() + direction.y() * k); } auto p = new Part::GeomLineSegment; - p->setPoints(e->dia->scaledVector(begin, z), e->dia->scaledVector(end, z)); - return new Part::LineSegmentPy(p); + p->setPoints(e->dia->scaledVector(begin, z0), e->dia->scaledVector(end, z1)); + Handle(Geom_Curve) h = Handle(Geom_Curve)::DownCast(p->handle()); + BRepBuilderAPI_MakeEdge mkBuilder(h, h->FirstParameter(), h->LastParameter()); + return new Part::TopoShapeEdgePy(new Part::TopoShape(mkBuilder.Shape())); } } else { // parabolic curve, which is always formed by a point and an edge @@ -373,8 +447,8 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) } auto p = new Part::GeomParabola; { - p->setCenter(e->dia->scaledVector(point, z)); - p->setLocation(e->dia->scaledVector(loc, z)); + p->setCenter(e->dia->scaledVector(point, z0)); + p->setLocation(e->dia->scaledVector(loc, z0)); p->setAngleXU(atan2(axis.y(), axis.x())); p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y()) / e->dia->getScale()); } @@ -387,15 +461,86 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) auto v1 = e->ptr->vertex1(); double param0 = 0; double param1 = 0; - if (!p->closestParameter(e->dia->scaledVector(*v0, z), param0)) { + if (!p->closestParameter(e->dia->scaledVector(*v0, z0), param0)) { std::cerr << "closestParameter(v0) failed" << std::endl; } - if (!p->closestParameter(e->dia->scaledVector(*v1, z), param1)) { + if (!p->closestParameter(e->dia->scaledVector(*v1, z0), param1)) { std::cerr << "closestParameter(v0) failed" << std::endl; } a->setRange(param0, param1, false); + std::cerr << "range(" << param0 << ", " << param1 << ")" << std::endl; + + if (z0 != z1) { + // two points of the plane are the end points of the parabola at the correct z level + auto p0 = e->dia->scaledVector(*v0, z0); + auto p1 = e->dia->scaledVector(*v1, z1); + // we get a third point by moving p0 along the axis of the parabola + auto p0_ = e->dia->scaledVector(v0->x() + axis.x(), v0->y() + axis.y(), z0); + // normal of the plane defined by those 3 points + auto norm = ((p1 - p0).Cross(p0_ - p0)).Normalize(); + + if (true) { + double r = Distance(p0, p1) * 10; + + // construct a face we can project the parabola on + Handle(Geom_Plane) plane = new Geom_Plane(gp_Pnt(p0.x, p0.y, p0.z), gp_Dir(norm.x, norm.y, norm.z)); + BRepBuilderAPI_MakeFace mkFace(plane, -r, r, -r, r +#if OCC_VERSION_HEX >= 0x060502 + , Precision::Confusion() +#endif + ); + + // get a shape for the parabola arc + Handle(Geom_Curve) arc = Handle(Geom_Curve)::DownCast(a->handle()); + BRepBuilderAPI_MakeEdge parab(arc, arc->FirstParameter(), arc->LastParameter()); + + // get projection of parabola onto the plane + BRepProj_Projection projection(parab.Shape(), mkFace.Shape(), gp::DZ()); + TopoDS_Shape shape = projection.Shape(); + + // what we get is a compound shape - but we're pretty sure there's only a single edge + // in there. if that's the case - return just that single edge + TopTools_IndexedMapOfShape map; + for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + map.Add(it.Current()); + } + if (map.Extent() == 1) { + // there's indeed just a single edge in the compound. Unfortunately the edge + // can end up being oriented the wrong way. + TopoDS_Shape edge = map(1); + edge.Orientation((edge.Orientation() == TopAbs_REVERSED) ? TopAbs_FORWARD : TopAbs_REVERSED); + return new Part::TopoShapeEdgePy(new Part::TopoShape(edge)); + } + return new Part::TopoShapePy(new Part::TopoShape(shape)); + } else { + std::cerr << "hugo" << std::endl; + double scale = Distance(p0, p1) / distanceBetween(*v0, *v1, e->dia->getScale()); + double kz = param0 / fabs(param0 - param1); + double zc = z0 + (z0 - z1) * kz; + + auto center = e->dia->scaledVector(point, zc); + auto location = e->dia->scaledVector(loc, zc); + //p->setCenter(center); + //p->setLocation(location); + p->setFocal(p->getFocal() * scale * scale); + + Handle(Geom_TrimmedCurve) trim = Handle(Geom_TrimmedCurve)::DownCast(a->handle()); + Handle(Geom_Parabola) parabola = Handle(Geom_Parabola)::DownCast(trim->BasisCurve()); + gp_Ax1 axis; + axis.SetLocation(gp_Pnt(location.x, location.y, location.z)); + axis.SetDirection(gp_Dir(norm.x, norm.y, norm.z)); + parabola->SetAxis(axis); + parabola->SetFocal(p->getFocal() / scale); + + a->setRange(param0 * scale, param1 * scale, false); + } + } } - return new Part::ArcOfParabolaPy(a); + + // get a shape for the parabola arc + Handle(Geom_Curve) h = Handle(Geom_Curve)::DownCast(a->handle()); + BRepBuilderAPI_MakeEdge mkBuilder(h, h->FirstParameter(), h->LastParameter()); + return new Part::TopoShapeEdgePy(new Part::TopoShape(mkBuilder.Shape())); } } Py_INCREF(Py_None); @@ -403,61 +548,6 @@ PyObject* VoronoiEdgePy::toGeom(PyObject *args) } -namespace { - - double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const Voronoi::point_type &p1, double scale) { - double x = v0.x() - p1.x(); - double y = v0.y() - p1.y(); - return sqrt(x * x + y * y) / scale; - } - - void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list, double scale) { - if (v0) { - list->append(Py::Float(distanceBetween(*v0, p1, scale))); - } else { - Py_INCREF(Py_None); - list->append(Py::asObject(Py_None)); - } - } - - void addProjectedDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::segment_type &segment, Py::List *list, double scale) { - if (v0) { - Voronoi::point_type p0; - { - p0.x(v0->x()); - p0.y(v0->y()); - } - Voronoi::point_type p1 = orthognalProjection(p0, segment); - list->append(Py::Float(distanceBetween(*v0, p1, scale))); - } else { - Py_INCREF(Py_None); - list->append(Py::asObject(Py_None)); - } - } - - bool addDistancesToPoint(const VoronoiEdge *edge, Voronoi::point_type p, Py::List *list, double scale) { - addDistanceBetween(edge->ptr->vertex0(), p, list, scale); - addDistanceBetween(edge->ptr->vertex1(), p, list, scale); - return true; - } - - bool retrieveDistances(const VoronoiEdge *edge, Py::List *list) { - const Voronoi::diagram_type::cell_type *c0 = edge->ptr->cell(); - if (c0->contains_point()) { - return addDistancesToPoint(edge, edge->dia->retrievePoint(c0), list, edge->dia->getScale()); - } - const Voronoi::diagram_type::cell_type *c1 = edge->ptr->twin()->cell(); - if (c1->contains_point()) { - return addDistancesToPoint(edge, edge->dia->retrievePoint(c1), list, edge->dia->getScale()); - } - // at this point both cells are sourced from segments and it does not matter which one we use - Voronoi::segment_type segment = edge->dia->retrieveSegment(c0); - addProjectedDistanceBetween(edge->ptr->vertex0(), segment, list, edge->dia->getScale()); - addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list, edge->dia->getScale()); - return false; - } -} - PyObject* VoronoiEdgePy::getDistances(PyObject *args) { VoronoiEdge *e = getVoronoiEdgeFromPy(this, args); diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index 7cdd517b8b..1c61e275fc 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -46,9 +46,9 @@ - + - Returns a Vertex - or None if not possible + Returns a Vector - or None if not possible diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 02b1c393d1..1c01d24076 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -151,7 +151,7 @@ Py::Object VoronoiVertexPy::getIncidentEdge() const { return Py::asObject(new VoronoiEdgePy(new VoronoiEdge(v->dia, v->ptr->incident_edge()))); } -PyObject* VoronoiVertexPy::toGeom(PyObject *args) +PyObject* VoronoiVertexPy::toPoint(PyObject *args) { double z = 0.0; if (!PyArg_ParseTuple(args, "|d", &z)) { diff --git a/src/Mod/Path/PathTests/TestPathVoronoi.py b/src/Mod/Path/PathTests/TestPathVoronoi.py index a06c8437c1..f696b8abd3 100644 --- a/src/Mod/Path/PathTests/TestPathVoronoi.py +++ b/src/Mod/Path/PathTests/TestPathVoronoi.py @@ -23,7 +23,9 @@ # *************************************************************************** import FreeCAD +import Part import Path +import PathScripts.PathGeom as PathGeom import PathTests.PathTestUtils as PathTestUtils vd = None @@ -87,3 +89,84 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): self.assertNotEqual(vd.Cells[0], vd.Cells[1]) self.assertNotEqual(vd.Cells[1], vd.Cells[0]) + def test50(self): + '''Check toShape for linear edges''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isLinear()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape() + self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 0) + self.assertEqual(e.valueAt(e.LastParameter).z, 0) + + def test51(self): + '''Check toShape for linear edges with set z''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isLinear()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape(13.7) + self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 13.7) + self.assertEqual(e.valueAt(e.LastParameter).z, 13.7) + + def test52(self): + '''Check toShape for linear edges with varying z''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isLinear()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape(2.37, 5.14) + self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 2.37) + self.assertEqual(e.valueAt(e.LastParameter).z, 5.14) + + def test60(self): + '''Check toShape for curved edges''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isCurved()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape() + print(type(e.Curve)) + self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 0) + self.assertEqual(e.valueAt(e.LastParameter).z, 0) + + def test61(self): + '''Check toShape for curved edges with set z''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isCurved()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape(13.7) + print(type(e.Curve)) + self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 13.7) + self.assertEqual(e.valueAt(e.LastParameter).z, 13.7) + + def test62(self): + '''Check toShape for curved edges with varying z''' + + edges = [e for e in vd.Edges if e.Color == 0 and e.isCurved()] + self.assertNotEqual(len(edges), 0) + e0 = edges[0] + + e = e0.toShape(2.37, 5.14) + print(type(e.Curve)) + self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) + self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) + self.assertEqual(e.valueAt(e.FirstParameter).z, 2.37) + self.assertEqual(e.valueAt(e.LastParameter).z, 5.14) + From 0d747d7abd7af0332451867801312b8abe6a606c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 17 Oct 2020 23:42:14 -0700 Subject: [PATCH 03/12] Fixed voronoi parabola creation with correct orientation. --- src/Mod/Path/App/Voronoi.h | 1 + src/Mod/Path/App/VoronoiEdgePyImp.cpp | 282 +++++++++++----------- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/PathTests/TestPathVoronoi.py | 27 +-- 4 files changed, 158 insertions(+), 153 deletions(-) diff --git a/src/Mod/Path/App/Voronoi.h b/src/Mod/Path/App/Voronoi.h index 7be3cac3d7..d30651fbb1 100644 --- a/src/Mod/Path/App/Voronoi.h +++ b/src/Mod/Path/App/Voronoi.h @@ -58,6 +58,7 @@ namespace Path // types typedef double coordinate_type; + typedef boost::polygon::voronoi_vertex vertex_type; typedef boost::polygon::point_data point_type; typedef boost::polygon::segment_data segment_type; typedef boost::polygon::voronoi_diagram voronoi_diagram_type; diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 8879f061d1..ab8ac8f985 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -25,42 +25,37 @@ #ifndef _PreComp_ #include -#include "BRepLib.hxx" -#include "BRepTools.hxx" #include "BRepBuilderAPI_MakeEdge.hxx" -#include "BRepBuilderAPI_MakeFace.hxx" -#include "BRepProj_Projection.hxx" #include "Mod/Path/App/Voronoi.h" #include "Mod/Path/App/Voronoi.h" #include "Mod/Path/App/VoronoiCell.h" -#include "Mod/Path/App/VoronoiCellPy.h" #include "Mod/Path/App/VoronoiEdge.h" -#include "Mod/Path/App/VoronoiEdgePy.h" #include "Mod/Path/App/VoronoiVertex.h" -#include "Mod/Path/App/VoronoiVertexPy.h" #include -#include -#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include #endif -// files generated out of VoronoiEdgePy.xml -#include "VoronoiEdgePy.cpp" +#include "Mod/Path/App/VoronoiCellPy.h" +#include "Mod/Path/App/VoronoiEdgePy.h" +#include "Mod/Path/App/VoronoiEdgePy.cpp" +#include "Mod/Path/App/VoronoiVertexPy.h" using namespace Path; namespace { + Voronoi::point_type pointFromVertex(const Voronoi::vertex_type v) { + Voronoi::point_type pt; + pt.x(v.x()); + pt.y(v.y()); + return pt; + } + 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; @@ -91,13 +86,43 @@ namespace { return pt; } - template - double distanceBetween(const Voronoi::diagram_type::vertex_type &v0, const pt_type &p1, double scale) { - double x = v0.x() - p1.x(); - double y = v0.y() - p1.y(); - return sqrt(x * x + y * y) / scale; + double length(const Voronoi::point_type &p) { + return sqrt(p.x() * p.x() + p.y() * p.y()); } + int sideOf(const Voronoi::point_type &p, const Voronoi::segment_type &s) { + Voronoi::coordinate_type dxp = p.x() - low(s).x(); + Voronoi::coordinate_type dyp = p.y() - low(s).y(); + Voronoi::coordinate_type dxs = high(s).x() - low(s).x(); + Voronoi::coordinate_type dys = high(s).y() - low(s).y(); + + double d = -dxs * dyp + dys * dxp; + if (d < 0) { + return -1; + } + if (d > 0) { + return +1; + } + return 0; + } + + template + double distanceBetween(const pt0_type &p0, const pt1_type &p1, double scale) { + Voronoi::point_type dist; + dist.x(p0.x() - p1.x()); + dist.y(p0.y() - p1.y()); + return length(dist) / scale; + } + + template + double signedDistanceBetween(const pt0_type &p0, const pt1_type &p1, double scale) { + if (length(p0) > length(p1)) { + return -distanceBetween(p0, p1, scale); + } + return distanceBetween(p0, p1, scale); + } + + void addDistanceBetween(const Voronoi::diagram_type::vertex_type *v0, const Voronoi::point_type &p1, Py::List *list, double scale) { if (v0) { list->append(Py::Float(distanceBetween(*v0, p1, scale))); @@ -143,6 +168,19 @@ namespace { addProjectedDistanceBetween(edge->ptr->vertex1(), segment, list, edge->dia->getScale()); return false; } + +} + +std::ostream& operator<<(std::ostream& os, const Voronoi::vertex_type &v) { + return os << '(' << v.x() << ", " << v.y() << ')'; +} + +std::ostream& operator<<(std::ostream& os, const Voronoi::point_type &p) { + return os << '(' << p.x() << ", " << p.y() << ')'; +} + +std::ostream& operator<<(std::ostream& os, const Voronoi::segment_type &s) { + return os << '<' << low(s) << ", " << high(s) << '>'; } @@ -404,7 +442,7 @@ PyObject* VoronoiEdgePy::toShape(PyObject *args) direction.y(dx); } } - double k = 10.0; // <-- need something smarter here + double k = 2.5; // <-- need something smarter here Voronoi::point_type begin; Voronoi::point_type end; if (e->ptr->vertex0()) { @@ -445,100 +483,95 @@ PyObject* VoronoiEdgePy::toShape(PyObject *args) axis.x(point.x() - loc.x()); axis.y(point.y() - loc.y()); } - auto p = new Part::GeomParabola; + Voronoi::segment_type xaxis; { - p->setCenter(e->dia->scaledVector(point, z0)); - p->setLocation(e->dia->scaledVector(loc, z0)); - p->setAngleXU(atan2(axis.y(), axis.x())); - p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y()) / e->dia->getScale()); + xaxis.low(point); + xaxis.high(loc); } - auto a = new Part::GeomArcOfParabola; - { - a->setHandle(Handle(Geom_Parabola)::DownCast(p->handle())); - // figure out the arc parameters - auto v0 = e->ptr->vertex0(); - auto v1 = e->ptr->vertex1(); - double param0 = 0; - double param1 = 0; - if (!p->closestParameter(e->dia->scaledVector(*v0, z0), param0)) { - std::cerr << "closestParameter(v0) failed" << std::endl; - } - if (!p->closestParameter(e->dia->scaledVector(*v1, z0), param1)) { - std::cerr << "closestParameter(v0) failed" << std::endl; - } - a->setRange(param0, param1, false); - std::cerr << "range(" << param0 << ", " << param1 << ")" << std::endl; - - if (z0 != z1) { - // two points of the plane are the end points of the parabola at the correct z level - auto p0 = e->dia->scaledVector(*v0, z0); - auto p1 = e->dia->scaledVector(*v1, z1); - // we get a third point by moving p0 along the axis of the parabola - auto p0_ = e->dia->scaledVector(v0->x() + axis.x(), v0->y() + axis.y(), z0); - // normal of the plane defined by those 3 points - auto norm = ((p1 - p0).Cross(p0_ - p0)).Normalize(); - - if (true) { - double r = Distance(p0, p1) * 10; - - // construct a face we can project the parabola on - Handle(Geom_Plane) plane = new Geom_Plane(gp_Pnt(p0.x, p0.y, p0.z), gp_Dir(norm.x, norm.y, norm.z)); - BRepBuilderAPI_MakeFace mkFace(plane, -r, r, -r, r -#if OCC_VERSION_HEX >= 0x060502 - , Precision::Confusion() -#endif - ); - - // get a shape for the parabola arc - Handle(Geom_Curve) arc = Handle(Geom_Curve)::DownCast(a->handle()); - BRepBuilderAPI_MakeEdge parab(arc, arc->FirstParameter(), arc->LastParameter()); - - // get projection of parabola onto the plane - BRepProj_Projection projection(parab.Shape(), mkFace.Shape(), gp::DZ()); - TopoDS_Shape shape = projection.Shape(); - - // what we get is a compound shape - but we're pretty sure there's only a single edge - // in there. if that's the case - return just that single edge - TopTools_IndexedMapOfShape map; - for (TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { - map.Add(it.Current()); - } - if (map.Extent() == 1) { - // there's indeed just a single edge in the compound. Unfortunately the edge - // can end up being oriented the wrong way. - TopoDS_Shape edge = map(1); - edge.Orientation((edge.Orientation() == TopAbs_REVERSED) ? TopAbs_FORWARD : TopAbs_REVERSED); - return new Part::TopoShapeEdgePy(new Part::TopoShape(edge)); - } - return new Part::TopoShapePy(new Part::TopoShape(shape)); - } else { - std::cerr << "hugo" << std::endl; - double scale = Distance(p0, p1) / distanceBetween(*v0, *v1, e->dia->getScale()); - double kz = param0 / fabs(param0 - param1); - double zc = z0 + (z0 - z1) * kz; - - auto center = e->dia->scaledVector(point, zc); - auto location = e->dia->scaledVector(loc, zc); - //p->setCenter(center); - //p->setLocation(location); - p->setFocal(p->getFocal() * scale * scale); - - Handle(Geom_TrimmedCurve) trim = Handle(Geom_TrimmedCurve)::DownCast(a->handle()); - Handle(Geom_Parabola) parabola = Handle(Geom_Parabola)::DownCast(trim->BasisCurve()); - gp_Ax1 axis; - axis.SetLocation(gp_Pnt(location.x, location.y, location.z)); - axis.SetDirection(gp_Dir(norm.x, norm.y, norm.z)); - parabola->SetAxis(axis); - parabola->SetFocal(p->getFocal() / scale); - - a->setRange(param0 * scale, param1 * scale, false); - } - } + // determine distances of the end points from the x-axis, those are the parameters for + // the arc of the parabola in the horizontal plane + auto pt0 = pointFromVertex(*e->ptr->vertex0()); + auto pt1 = pointFromVertex(*e->ptr->vertex1()); + Voronoi::point_type pt0x = orthognalProjection(pt0, xaxis); + Voronoi::point_type pt1x = orthognalProjection(pt1, xaxis); + double dist0 = distanceBetween(pt0, pt0x, e->dia->getScale()) * sideOf(pt0, xaxis); + double dist1 = distanceBetween(pt1, pt1x, e->dia->getScale()) * sideOf(pt1, xaxis); + if (dist1 < dist0) { + // if the parabola is traversed in the revere direction we need to use the points + // on the other side of the parabola - beauty of symmetric geometries + dist0 = -dist0; + dist1 = -dist1; } + // at this point we have the direction of the x-axis and the two end points p0 and p1 + // which means we know the plane of the parabola + auto p0 = e->dia->scaledVector(pt0, z0); + auto p1 = e->dia->scaledVector(pt1, z1); + // we get a third point by moving p0 along the axis of the parabola + auto p_ = p0 + e->dia->scaledVector(axis, 0); + + // normal of the plane defined by those 3 points + auto norm = ((p_ - p0).Cross(p1 - p0)).Normalize(); + + // the next thing to figure out is the z level of the x-axis, + double zx = z0 - (dist0 / (dist0 - dist1)) * (z0 - z1); + + auto locn = e->dia->scaledVector(loc, zx); + auto xdir = e->dia->scaledVector(axis, zx); + + double focal; + if (z0 == z1) { + // focal length if parabola in the xy-plane is simply half the distance between the + // point and segment - aka the distance between point and location, aka the length of axis + focal = length(axis) / e->dia->getScale(); + } else { + // if the parabola is not in the xy-plane we need to find the + // (x,y) coordinates of a point on the parabola in the parabola's + // coordinate system. + // see: http://amsi.org.au/ESA_Senior_Years/SeniorTopic2/2a/2a_2content_10.html + // note that above website uses Y as the symmetry axis of the parabola whereas + // OCC uses X as the symmetry axis. The math below is in the website's system. + // We already know 2 points on the parabola (p0 and p1), we know their X values + // (dist0 and dist1) if the parabola is in the xy-plane, and we know their orthogonal + // projection onto the parabola's symmetry axis Y (pt0x and pt1x). The resulting Y + // values are the distance between the parabola's location (loc) and the respective + // orthogonal projection. Pythagoras gives us the X values, using the X from the + // xy-plane and the difference in z. + // Note that this calculation also gives correct results if the parabola is in + // the xy-plane (z0 == z1), it's just that above calculation is so much simpler. + double flenX0 = sqrt(dist0 * dist0 + (z0 - zx) * (z0 - zx)); + double flenX1 = sqrt(dist1 * dist1 + (zx - z1) * (zx - z1)); + double flenX; + double flenY; + // if one of the points is the location, we have to use the other to get sensible values + if (fabs(dist0) > 0.001) { + flenX = flenX0; + flenY = distanceBetween(loc, pt0x, e->dia->getScale()); + } else { + flenX = flenX1; + flenY = distanceBetween(loc, pt1x, e->dia->getScale()); + } + // parabola: (x - p)^2 = 4*focal*(y - q) | (p,q) ... location of parabola + focal = (flenX * flenX) / (4 * fabs(flenY)); + // use new X values to set the parameters + dist0 = dist0 >= 0 ? flenX0 : -flenX0; + dist1 = dist1 >= 0 ? flenX1 : -flenX1; + } + + gp_Pnt pbLocn(locn.x, locn.y, locn.z); + gp_Dir pbNorm(norm.x, norm.y, norm.z); + gp_Dir pbXdir(xdir.x, xdir.y, 0); + + gp_Ax2 pb(pbLocn, pbNorm, pbXdir); + Handle(Geom_Parabola) parabola = new Geom_Parabola(pb, focal); + + auto arc = new Part::GeomArcOfParabola; + arc->setHandle(parabola); + arc->setRange(dist0, dist1, false); + // get a shape for the parabola arc - Handle(Geom_Curve) h = Handle(Geom_Curve)::DownCast(a->handle()); + Handle(Geom_Curve) h = Handle(Geom_Curve)::DownCast(arc->handle()); BRepBuilderAPI_MakeEdge mkBuilder(h, h->FirstParameter(), h->LastParameter()); return new Part::TopoShapeEdgePy(new Part::TopoShape(mkBuilder.Shape())); } @@ -556,22 +589,6 @@ 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); @@ -589,18 +606,7 @@ PyObject* VoronoiEdgePy::getSegmentAngle(PyObject *args) 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; diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 451226ae21..5b88d574de 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -198,6 +198,7 @@ SET(PathTests_SRCS PathTests/TestPathToolController.py PathTests/TestPathTooltable.py PathTests/TestPathUtil.py + PathTests/TestPathVoronoi.py PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd diff --git a/src/Mod/Path/PathTests/TestPathVoronoi.py b/src/Mod/Path/PathTests/TestPathVoronoi.py index f696b8abd3..a1ad7af9b9 100644 --- a/src/Mod/Path/PathTests/TestPathVoronoi.py +++ b/src/Mod/Path/PathTests/TestPathVoronoi.py @@ -99,8 +99,8 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape() self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 0) - self.assertEqual(e.valueAt(e.LastParameter).z, 0) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 0) + self.assertRoughly(e.valueAt(e.LastParameter).z, 0) def test51(self): '''Check toShape for linear edges with set z''' @@ -112,8 +112,8 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape(13.7) self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 13.7) - self.assertEqual(e.valueAt(e.LastParameter).z, 13.7) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 13.7) + self.assertRoughly(e.valueAt(e.LastParameter).z, 13.7) def test52(self): '''Check toShape for linear edges with varying z''' @@ -125,8 +125,8 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e = e0.toShape(2.37, 5.14) self.assertTrue(type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 2.37) - self.assertEqual(e.valueAt(e.LastParameter).z, 5.14) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 2.37) + self.assertRoughly(e.valueAt(e.LastParameter).z, 5.14) def test60(self): '''Check toShape for curved edges''' @@ -136,11 +136,10 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e0 = edges[0] e = e0.toShape() - print(type(e.Curve)) self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 0) - self.assertEqual(e.valueAt(e.LastParameter).z, 0) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 0) + self.assertRoughly(e.valueAt(e.LastParameter).z, 0) def test61(self): '''Check toShape for curved edges with set z''' @@ -150,11 +149,10 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e0 = edges[0] e = e0.toShape(13.7) - print(type(e.Curve)) self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 13.7) - self.assertEqual(e.valueAt(e.LastParameter).z, 13.7) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 13.7) + self.assertRoughly(e.valueAt(e.LastParameter).z, 13.7) def test62(self): '''Check toShape for curved edges with varying z''' @@ -164,9 +162,8 @@ class TestPathVoronoi(PathTestUtils.PathTestBase): e0 = edges[0] e = e0.toShape(2.37, 5.14) - print(type(e.Curve)) self.assertTrue(type(e.Curve) == Part.Parabola or type(e.Curve) == Part.BSplineCurve) self.assertFalse(PathGeom.pointsCoincide(e.valueAt(e.FirstParameter), e.valueAt(e.LastParameter))) - self.assertEqual(e.valueAt(e.FirstParameter).z, 2.37) - self.assertEqual(e.valueAt(e.LastParameter).z, 5.14) + self.assertRoughly(e.valueAt(e.FirstParameter).z, 2.37) + self.assertRoughly(e.valueAt(e.LastParameter).z, 5.14) From 3c4bccbf6c0fbe8e2e7eb034a83076862a2cdbdc Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 18 Oct 2020 16:36:24 -0700 Subject: [PATCH 04/12] Fixed parabola calculation if vornonoi edge starts close to its location. --- src/Mod/Path/App/VoronoiEdgePyImp.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index ab8ac8f985..7c1b59c774 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -397,7 +397,8 @@ PyObject* VoronoiEdgePy::toShape(PyObject *args) { double z0 = 0.0; double z1 = DBL_MAX; - if (!PyArg_ParseTuple(args, "|dd", &z0, &z1)) { + int dbg = 0; + if (!PyArg_ParseTuple(args, "|ddp", &z0, &z1, &dbg)) { throw Py::RuntimeError("no, one or two arguments of type double accepted"); } if (z1 == DBL_MAX) { @@ -545,7 +546,7 @@ PyObject* VoronoiEdgePy::toShape(PyObject *args) double flenX; double flenY; // if one of the points is the location, we have to use the other to get sensible values - if (fabs(dist0) > 0.001) { + if (fabs(dist0) > fabs(dist1)) { flenX = flenX0; flenY = distanceBetween(loc, pt0x, e->dia->getScale()); } else { @@ -554,6 +555,12 @@ PyObject* VoronoiEdgePy::toShape(PyObject *args) } // parabola: (x - p)^2 = 4*focal*(y - q) | (p,q) ... location of parabola focal = (flenX * flenX) / (4 * fabs(flenY)); + if (dbg) { + std::cerr << "segement" << segment << ", point" << point << std::endl; + std::cerr << " loc" << loc << ", axis" << axis << std::endl; + std::cerr << " dist0(" << dist0 << " : " << flenX0 << ", dist1(" << dist1 << " : " << flenX1 << ")" << std::endl; + std::cerr << " z(" << z0 << ", " << zx << ", " << z1 << ")" << std::endl; + } // use new X values to set the parameters dist0 = dist0 >= 0 ? flenX0 : -flenX0; dist1 = dist1 >= 0 ? flenX1 : -flenX1; From e35a0221326fab8180f841f61d7668f109eee4aa Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 18 Oct 2020 16:56:36 -0700 Subject: [PATCH 05/12] New vcarve wire detection algorithm using the new z-values of toShape --- src/Mod/Path/PathScripts/PathVcarve.py | 210 ++++++++++++------------- 1 file changed, 101 insertions(+), 109 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 4c049c1cc0..95b7dc40d8 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -47,12 +47,8 @@ TWIN = 2 COLINEAR = 3 SECONDARY = 5 -if False: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +#PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): @@ -60,7 +56,65 @@ def translate(context, text, disambig=None): VD = [] +Vertex = {} +def _getVoronoiWires(vd): + edges = [e for e in vd.Edges if e.Color == PRIMARY] + vertex = {} + for e in edges: + for v in e.Vertices: + i = v.Index + j = vertex.get(i, []) + j.append(e) + vertex[i] = j + Vertex.clear() + for v in vertex: + Vertex[v] = vertex[v] + + # knots are the start and end points of a wire + knots = [i for i in vertex if len(vertex[i]) == 1] + knots.extend([i for i in vertex if len(vertex[i]) > 2]) + + def consume(v, edge): + vertex[v] = [e for e in vertex[v] if e.Index != edge.Index] + return len(vertex[v]) == 0 + + def traverse(vStart, edge, edges): + if vStart == edge.Vertices[0].Index: + vEnd = edge.Vertices[1].Index + edges.append(edge) + else: + vEnd = edge.Vertices[0].Index + edges.append(edge.Twin) + + consume(vStart, edge) + if consume(vEnd, edge): + return None + return vEnd + + wires = [] + while knots: + we = [] + vFirst = knots[0] + vStart = vFirst + vLast = vFirst + if len(vertex[vStart]): + while not vStart is None: + vLast = vStart + edges = vertex[vStart] + if len(edges) > 0: + edge = edges[0] + vStart = traverse(vStart, edge, we) + else: + vStart = None + wires.append(we) + print("knots %s - (%s, %s)" % (knots, vFirst, vLast)) + # The first and last edge are knots, check if they still have more edges attached + if len(vertex[vFirst]) == 0: + knots = [v for v in knots if v != vFirst] + if len(vertex[vLast]) == 0: + knots = [v for v in knots if v != vLast] + return wires class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' @@ -101,6 +155,29 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): # upgrade ... self.setupAdditionalProperties(obj) + def _calculate_depth(self, obj, MIC, baselevel=0): + # given a maximum inscribed circle (MIC) and tool angle, + # return depth of cut relative to baselevel. + + r = float(obj.ToolController.Tool.Diameter) / 2 + toolangle = obj.ToolController.Tool.CuttingEdgeAngle + maxdepth = baselevel - r / math.tan(math.radians(toolangle/2)) + + d = baselevel - round(MIC / math.tan(math.radians(toolangle / 2)), 4) + PathLog.debug('baselevel value: {} depth: {}'.format(baselevel, d)) + return d if d > maxdepth else maxdepth + + def _getPartEdge(self, obj, edge, bblevel): + dist = edge.getDistances() + return edge.toShape(self._calculate_depth(obj, dist[0]), self._calculate_depth(obj, dist[1])) + + def _getPartEdges(self, obj, vWire): + bblevel = self.model[0].Shape.BoundBox.ZMin + edges = [] + for e in vWire: + edges.append(self._getPartEdge(obj, e, bblevel)) + return edges + def buildPathMedial(self, obj, Faces): '''constructs a medial axis path using openvoronoi''' @@ -114,107 +191,17 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): for i in range(len(pts)): vd.addSegment(ptv[i], ptv[i+1]) - def calculate_depth(MIC, baselevel=0): - # given a maximum inscribed circle (MIC) and tool angle, - # return depth of cut relative to baselevel. - - r = obj.ToolController.Tool.Diameter / 2 - toolangle = obj.ToolController.Tool.CuttingEdgeAngle - maxdepth = baselevel - r / math.tan(math.radians(toolangle/2)) - - d = baselevel - round(MIC / math.tan(math.radians(toolangle / 2)), 4) - PathLog.debug('baselevel value: {} depth: {}'.format(baselevel, d)) - return d if d <= maxdepth else maxdepth - - def getEdges(vd, color=[PRIMARY]): - if type(color) == int: - color = [color] - geomList = [] - bblevel = self.model[0].Shape.BoundBox.ZMin - for e in vd.Edges: - if e.Color not in color: - continue - if e.toGeom() is None: - continue - p1 = e.Vertices[0].toGeom(calculate_depth(e.getDistances()[0], bblevel)) - p2 = e.Vertices[-1].toGeom(calculate_depth(e.getDistances()[-1], bblevel)) - newedge = Part.Edge(Part.Vertex(p1), Part.Vertex(p2)) - - newedge.fixTolerance(obj.Tolerance, Part.Vertex) - geomList.append(newedge) - - return geomList - - def sortEm(mywire, unmatched): - remaining = [] - wireGrowing = False - - # end points of existing wire - wireverts = [mywire.Edges[0].valueAt(mywire.Edges[0].FirstParameter), - mywire.Edges[-1].valueAt(mywire.Edges[-1].LastParameter)] - - for i, candidate in enumerate(unmatched): - - # end points of candidate edge - cverts = [candidate.Edges[0].valueAt(candidate.Edges[0].FirstParameter), - candidate.Edges[-1].valueAt(candidate.Edges[-1].LastParameter)] - - # ignore short segments below tolerance level - if PathGeom.pointsCoincide(cverts[0], cverts[1], obj.Tolerance): - continue - - # iterate the combination of endpoints. If a match is found, - # make an edge from the common endpoint to the other end of - # the candidate wire. Add the edge to the wire and return it. - - # This generates a new edge rather than using the candidate to - # avoid vertexes with close but different vectors - for wvert in wireverts: - for idx, cvert in enumerate(cverts): - if PathGeom.pointsCoincide(wvert, cvert, obj.Tolerance): - wireGrowing = True - elist = mywire.Edges - otherIndex = int(not(idx)) - - newedge = Part.Edge(Part.Vertex(wvert), - Part.Vertex(cverts[otherIndex])) - - elist.append(newedge) - mywire = Part.Wire(Part.__sortEdges__(elist)) - remaining.extend(unmatched[i+1:]) - return mywire, remaining, wireGrowing - - # if not matched, add to remaining list to test later - remaining.append(candidate) - - return mywire, remaining, wireGrowing - - def getWires(candidateList): - - chains = [] - while len(candidateList) > 0: - cur_wire = Part.Wire(candidateList.pop(0)) - - wireGrowing = True - while wireGrowing: - cur_wire, candidateList, wireGrowing = sortEm(cur_wire, - candidateList) - - chains.append(cur_wire) - - return chains - - def cutWire(w): + def cutWire(edges): path = [] path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value))) - e = w.Edges[0] + e = edges[0] p = e.valueAt(e.FirstParameter) path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value))) c = Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, obj.ToolController.HorizFeed.Value)) path.append(c) - for e in w.Edges: + for e in edges: path.extend(PathGeom.cmdsForEdge(e, hSpeed=obj.ToolController.HorizFeed.Value)) @@ -233,16 +220,21 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): e.Color = PRIMARY if e.isPrimary() else SECONDARY vd.colorExterior(EXTERIOR1) vd.colorExterior(EXTERIOR2, - lambda v: not f.isInside(v.toGeom(f.BoundBox.ZMin), + lambda v: not f.isInside(v.toPoint(f.BoundBox.ZMin), obj.Tolerance, True)) vd.colorColinear(COLINEAR, obj.Threshold) vd.colorTwins(TWIN) - edgelist = getEdges(vd) - - for wire in getWires(edgelist): - pathlist.extend(cutWire(wire)) - VD.append((f, vd, getWires(edgelist))) + if True: + wires = [] + for vWire in _getVoronoiWires(vd): + pWire = self._getPartEdges(obj, vWire) + if pWire: + wires.append(pWire) + pathlist.extend(cutWire(pWire)) + VD.append((f, vd, wires)) + else: + VD.append((f, vd)) self.commandlist = pathlist @@ -283,12 +275,12 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): PathLog.error(e) traceback.print_exc() PathLog.error(translate('PathVcarve', 'The Job Base Object has \ - no engraveable element. Engraving \ - operation will produce no output.')) +no engraveable element. Engraving \ +operation will produce no output.')) + raise e 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 6e0e8541a5711788b544fedb87562a5d32c95277 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 18 Oct 2020 17:10:00 -0700 Subject: [PATCH 06/12] Added sorting of voronoi wires to minimize rapid moves --- src/Mod/Path/PathScripts/PathVcarve.py | 46 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 95b7dc40d8..d718b5f772 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -58,7 +58,7 @@ def translate(context, text, disambig=None): VD = [] Vertex = {} -def _getVoronoiWires(vd): +def _collectVoronoiWires(vd): edges = [e for e in vd.Edges if e.Color == PRIMARY] vertex = {} for e in edges: @@ -116,6 +116,33 @@ def _getVoronoiWires(vd): knots = [v for v in knots if v != vLast] return wires +def _sortVoronoiWires(wires, start = FreeCAD.Vector(0, 0, 0)): + def closestTo(start, point): + p = None + l = None + for i in point: + if l is None or l > start.distanceToPoint(point[i]): + l = start.distanceToPoint(point[i]) + p = i + return p + + + begin = {} + end = {} + + for i, w in enumerate(wires): + begin[i] = w[ 0].Vertices[0].toPoint() + end[i] = w[-1].Vertices[1].toPoint() + + index = [] + while begin: + idx = closestTo(start, begin) + index.append(idx) + del begin[idx] + start = end[idx] + + return [wires[i] for i in index] + class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' @@ -225,16 +252,13 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): vd.colorColinear(COLINEAR, obj.Threshold) vd.colorTwins(TWIN) - if True: - wires = [] - for vWire in _getVoronoiWires(vd): - pWire = self._getPartEdges(obj, vWire) - if pWire: - wires.append(pWire) - pathlist.extend(cutWire(pWire)) - VD.append((f, vd, wires)) - else: - VD.append((f, vd)) + wires = [] + for vWire in _sortVoronoiWires(_collectVoronoiWires(vd)): + pWire = self._getPartEdges(obj, vWire) + if pWire: + wires.append(pWire) + pathlist.extend(cutWire(pWire)) + VD.append((f, vd, wires)) self.commandlist = pathlist From c581602bf8ce313becf17e4fc9a64aa15424b2e4 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 18 Oct 2020 17:36:48 -0700 Subject: [PATCH 07/12] Added sorting over all wires, not just the ones of a single face --- src/Mod/Path/PathScripts/PathVcarve.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index d718b5f772..a15d900fdc 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -58,6 +58,8 @@ def translate(context, text, disambig=None): VD = [] Vertex = {} +_sorting = 'global' + def _collectVoronoiWires(vd): edges = [e for e in vd.Edges if e.Color == PRIMARY] vertex = {} @@ -235,8 +237,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): return path VD.clear() - pathlist = [] - pathlist.append(Path.Command("(starting)")) + voronoiWires = [] for f in Faces: vd = Path.Voronoi() insert_many_wires(vd, f.Wires) @@ -252,14 +253,22 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): vd.colorColinear(COLINEAR, obj.Threshold) vd.colorTwins(TWIN) - wires = [] - for vWire in _sortVoronoiWires(_collectVoronoiWires(vd)): - pWire = self._getPartEdges(obj, vWire) - if pWire: - wires.append(pWire) - pathlist.extend(cutWire(pWire)) + wires = _collectVoronoiWires(vd); + if _sorting != 'global': + wires = _sortVoronoiWires(wires) + voronoiWires.extend(wires) VD.append((f, vd, wires)) + if _sorting == 'global': + voronoiWires = _sortVoronoiWires(voronoiWires) + + pathlist = [] + pathlist.append(Path.Command("(starting)")) + for w in voronoiWires: + pWire = self._getPartEdges(obj, w) + if pWire: + wires.append(pWire) + pathlist.extend(cutWire(pWire)) self.commandlist = pathlist def opExecute(self, obj): From 204ab7e826e4421f2bfd0cf9e8e7998ad68114de Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 18 Oct 2020 17:40:03 -0700 Subject: [PATCH 08/12] Allow voronoi edges to be traversed in any direction and use that to sort the sequence of milling the wires. --- src/Mod/Path/PathScripts/PathVcarve.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index a15d900fdc..95fa570445 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -110,8 +110,6 @@ def _collectVoronoiWires(vd): else: vStart = None wires.append(we) - print("knots %s - (%s, %s)" % (knots, vFirst, vLast)) - # The first and last edge are knots, check if they still have more edges attached if len(vertex[vFirst]) == 0: knots = [v for v in knots if v != vFirst] if len(vertex[vLast]) == 0: @@ -126,7 +124,7 @@ def _sortVoronoiWires(wires, start = FreeCAD.Vector(0, 0, 0)): if l is None or l > start.distanceToPoint(point[i]): l = start.distanceToPoint(point[i]) p = i - return p + return (p, l) begin = {} @@ -136,14 +134,22 @@ def _sortVoronoiWires(wires, start = FreeCAD.Vector(0, 0, 0)): begin[i] = w[ 0].Vertices[0].toPoint() end[i] = w[-1].Vertices[1].toPoint() - index = [] + result = [] while begin: - idx = closestTo(start, begin) - index.append(idx) - del begin[idx] - start = end[idx] + (bIdx, bLen) = closestTo(start, begin) + (eIdx, eLen) = closestTo(start, end) + if bLen < eLen: + result.append(wires[bIdx]) + start = end[bIdx] + del begin[bIdx] + del end[bIdx] + else: + result.append([e.Twin for e in reversed(wires[eIdx])]) + start = begin[eIdx] + del begin[eIdx] + del end[eIdx] - return [wires[i] for i in index] + return result class ObjectVcarve(PathEngraveBase.ObjectOp): '''Proxy class for Vcarve operation.''' From 85418c48dfe79bec3a014e412e2529ff4a6b833d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 19 Oct 2020 18:16:48 -0700 Subject: [PATCH 09/12] Fixed 'o' and depth issue --- src/Mod/Path/PathScripts/PathVcarve.py | 46 +++++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 95fa570445..ea8f5660b8 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -40,12 +40,12 @@ from PySide import QtCore __doc__ = "Class and implementation of Path Vcarve operation" -PRIMARY = 0 -EXTERIOR1 = 1 -EXTERIOR2 = 4 -TWIN = 2 -COLINEAR = 3 -SECONDARY = 5 +PRIMARY = 0 +SECONDARY = 1 +EXTERIOR1 = 2 +EXTERIOR2 = 3 +COLINEAR = 4 +TWIN = 5 PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) #PathLog.trackModule(PathLog.thisModule()) @@ -76,6 +76,11 @@ def _collectVoronoiWires(vd): # knots are the start and end points of a wire knots = [i for i in vertex if len(vertex[i]) == 1] knots.extend([i for i in vertex if len(vertex[i]) > 2]) + if len(knots) == 0: + for i in vertex: + if len(vertex[i]) > 0: + knots.append(i) + break def consume(v, edge): vertex[v] = [e for e in vertex[v] if e.Index != edge.Index] @@ -190,27 +195,28 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): # upgrade ... self.setupAdditionalProperties(obj) - def _calculate_depth(self, obj, MIC, baselevel=0): + def _calculate_depth(self, MIC, zStart, zStop, zScale): # given a maximum inscribed circle (MIC) and tool angle, - # return depth of cut relative to baselevel. + # return depth of cut relative to zStart. + depth = zStart - round(MIC / zScale, 4) + PathLog.debug('zStart value: {} depth: {}'.format(zStart, depth)) + return depth if depth > zStop else zStop - r = float(obj.ToolController.Tool.Diameter) / 2 - toolangle = obj.ToolController.Tool.CuttingEdgeAngle - maxdepth = baselevel - r / math.tan(math.radians(toolangle/2)) - - d = baselevel - round(MIC / math.tan(math.radians(toolangle / 2)), 4) - PathLog.debug('baselevel value: {} depth: {}'.format(baselevel, d)) - return d if d > maxdepth else maxdepth - - def _getPartEdge(self, obj, edge, bblevel): + def _getPartEdge(self, edge, zStart, zStop, zScale): dist = edge.getDistances() - return edge.toShape(self._calculate_depth(obj, dist[0]), self._calculate_depth(obj, dist[1])) + return edge.toShape(self._calculate_depth(dist[0], zStart, zStop, zScale), self._calculate_depth(dist[1], zStart, zStop, zScale)) def _getPartEdges(self, obj, vWire): - bblevel = self.model[0].Shape.BoundBox.ZMin + # pre-calculate the depth limits - pre-mature optimisation ;) + r = float(obj.ToolController.Tool.Diameter) / 2 + toolangle = obj.ToolController.Tool.CuttingEdgeAngle + zStart = self.model[0].Shape.BoundBox.ZMin + zStop = zStart - r / math.tan(math.radians(toolangle/2)) + zScale = 1.0 / math.tan(math.radians(toolangle / 2)) + edges = [] for e in vWire: - edges.append(self._getPartEdge(obj, e, bblevel)) + edges.append(self._getPartEdge(e, zStart, zStop, zScale)) return edges def buildPathMedial(self, obj, Faces): From 318ad0fb65738656777cc656289b5dfb6e8dd512 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 23 Oct 2020 22:19:12 -0700 Subject: [PATCH 10/12] Include proper model headers for python files. --- src/Mod/Path/App/VoronoiCellPy.xml | 2 +- src/Mod/Path/App/VoronoiEdgePy.xml | 2 +- src/Mod/Path/App/VoronoiVertexPy.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/App/VoronoiCellPy.xml b/src/Mod/Path/App/VoronoiCellPy.xml index 6bce1f0b0c..dd57e44332 100644 --- a/src/Mod/Path/App/VoronoiCellPy.xml +++ b/src/Mod/Path/App/VoronoiCellPy.xml @@ -5,7 +5,7 @@ Name="VoronoiCellPy" Twin="VoronoiCell" TwinPointer="VoronoiCell" - Include="Mod/Path/App/Voronoi.h" + Include="Mod/Path/App/VoronoiCell.h" FatherInclude="Base/BaseClassPy.h" Namespace="Path" FatherNamespace="Base" diff --git a/src/Mod/Path/App/VoronoiEdgePy.xml b/src/Mod/Path/App/VoronoiEdgePy.xml index 2da04541f4..bce837524d 100644 --- a/src/Mod/Path/App/VoronoiEdgePy.xml +++ b/src/Mod/Path/App/VoronoiEdgePy.xml @@ -5,7 +5,7 @@ Name="VoronoiEdgePy" Twin="VoronoiEdge" TwinPointer="VoronoiEdge" - Include="Mod/Path/App/Voronoi.h" + Include="Mod/Path/App/VoronoiEdge.h" FatherInclude="Base/BaseClassPy.h" Namespace="Path" FatherNamespace="Base" diff --git a/src/Mod/Path/App/VoronoiVertexPy.xml b/src/Mod/Path/App/VoronoiVertexPy.xml index 1c61e275fc..b4e647b746 100644 --- a/src/Mod/Path/App/VoronoiVertexPy.xml +++ b/src/Mod/Path/App/VoronoiVertexPy.xml @@ -5,7 +5,7 @@ Name="VoronoiVertexPy" Twin="VoronoiVertex" TwinPointer="VoronoiVertex" - Include="Mod/Path/App/Voronoi.h" + Include="Mod/Path/App/VoronoiVertex.h" FatherInclude="Base/BaseClassPy.h" Namespace="Path" FatherNamespace="Base" From 982656babe4cee49c2e440f3adfda6cda0f197f0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 24 Oct 2020 14:03:14 -0700 Subject: [PATCH 11/12] Remove app include files from precompile guard --- src/Mod/Path/App/VoronoiCellPyImp.cpp | 13 ++++++------ src/Mod/Path/App/VoronoiEdgePyImp.cpp | 27 +++++++++++++------------ src/Mod/Path/App/VoronoiPyImp.cpp | 19 +++++++++-------- src/Mod/Path/App/VoronoiVertexPyImp.cpp | 25 +++++++++++------------ 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/Mod/Path/App/VoronoiCellPyImp.cpp b/src/Mod/Path/App/VoronoiCellPyImp.cpp index 9bae03a385..f64b722a24 100644 --- a/src/Mod/Path/App/VoronoiCellPyImp.cpp +++ b/src/Mod/Path/App/VoronoiCellPyImp.cpp @@ -27,19 +27,18 @@ # include #endif +#include "Base/Exception.h" +#include "Base/GeometryPyCXX.h" +#include "Base/PlacementPy.h" +#include "Base/Vector3D.h" +#include "Base/VectorPy.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" +#include "Mod/Path/App/VoronoiCellPy.cpp" using namespace Path; diff --git a/src/Mod/Path/App/VoronoiEdgePyImp.cpp b/src/Mod/Path/App/VoronoiEdgePyImp.cpp index 7c1b59c774..cd347eee65 100644 --- a/src/Mod/Path/App/VoronoiEdgePyImp.cpp +++ b/src/Mod/Path/App/VoronoiEdgePyImp.cpp @@ -25,26 +25,27 @@ #ifndef _PreComp_ #include -#include "BRepBuilderAPI_MakeEdge.hxx" -#include "Mod/Path/App/Voronoi.h" -#include "Mod/Path/App/Voronoi.h" -#include "Mod/Path/App/VoronoiCell.h" -#include "Mod/Path/App/VoronoiEdge.h" -#include "Mod/Path/App/VoronoiVertex.h" -#include -#include -#include -#include -#include -#include +#include #include #endif +#include "Base/Exception.h" +#include "Base/Vector3D.h" +#include "Base/VectorPy.h" +#include "Mod/Part/App/ArcOfParabolaPy.h" +#include "Mod/Part/App/LineSegmentPy.h" +#include "Mod/Part/App/TopoShapeEdgePy.h" +#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/VoronoiEdgePy.cpp" +#include "Mod/Path/App/VoronoiVertex.h" #include "Mod/Path/App/VoronoiVertexPy.h" +#include "Mod/Path/App/VoronoiEdgePy.cpp" + using namespace Path; namespace { diff --git a/src/Mod/Path/App/VoronoiPyImp.cpp b/src/Mod/Path/App/VoronoiPyImp.cpp index 474d0e7da1..ed898be265 100644 --- a/src/Mod/Path/App/VoronoiPyImp.cpp +++ b/src/Mod/Path/App/VoronoiPyImp.cpp @@ -27,21 +27,20 @@ # include #endif +#include "Base/Exception.h" +#include "Base/GeometryPyCXX.h" +#include "Base/Vector3D.h" +#include "Base/VectorPy.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/VoronoiPy.h" #include "Mod/Path/App/VoronoiVertex.h" -#include -#include -#include -#include +#include "Mod/Path/App/VoronoiVertexPy.h" -// files generated out of VoronoiPy.xml -#include "VoronoiPy.h" -#include "VoronoiPy.cpp" -#include "VoronoiCellPy.h" -#include "VoronoiEdgePy.h" -#include "VoronoiVertexPy.h" +#include "Mod/Path/App/VoronoiPy.cpp" using namespace Path; diff --git a/src/Mod/Path/App/VoronoiVertexPyImp.cpp b/src/Mod/Path/App/VoronoiVertexPyImp.cpp index 1c01d24076..15d18f22ba 100644 --- a/src/Mod/Path/App/VoronoiVertexPyImp.cpp +++ b/src/Mod/Path/App/VoronoiVertexPyImp.cpp @@ -27,20 +27,19 @@ # include #endif -#include "Voronoi.h" -#include "VoronoiPy.h" -#include "VoronoiEdge.h" -#include "VoronoiEdgePy.h" -#include "VoronoiVertex.h" -#include "VoronoiVertexPy.h" -#include -#include -#include -#include -#include +#include "Base/Exception.h" +#include "Base/GeometryPyCXX.h" +#include "Base/PlacementPy.h" +#include "Base/Vector3D.h" +#include "Base/VectorPy.h" +#include "Mod/Path/App/Voronoi.h" +#include "Mod/Path/App/VoronoiEdge.h" +#include "Mod/Path/App/VoronoiEdgePy.h" +#include "Mod/Path/App/VoronoiPy.h" +#include "Mod/Path/App/VoronoiVertex.h" +#include "Mod/Path/App/VoronoiVertexPy.h" -// files generated out of VoronoiVertexPy.xml -#include "VoronoiVertexPy.cpp" +#include "Mod/Path/App/VoronoiVertexPy.cpp" using namespace Path; From 236f8605cda666e4ea18acf2b57bd6c98fc93f04 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 25 Oct 2020 18:56:47 -0700 Subject: [PATCH 12/12] Special provisions for py2 - not understanding why though --- src/Mod/Path/PathTests/TestPathVoronoi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathTests/TestPathVoronoi.py b/src/Mod/Path/PathTests/TestPathVoronoi.py index a1ad7af9b9..451637d149 100644 --- a/src/Mod/Path/PathTests/TestPathVoronoi.py +++ b/src/Mod/Path/PathTests/TestPathVoronoi.py @@ -27,6 +27,7 @@ import Part import Path import PathScripts.PathGeom as PathGeom import PathTests.PathTestUtils as PathTestUtils +import sys vd = None @@ -44,7 +45,11 @@ def initVD(): vd.construct() for e in vd.Edges: - e.Color = 0 if e.isPrimary() else 1; + if sys.version_info.major > 2: + e.Color = 0 if e.isPrimary() else 1 + else: + e.Color = long(0) if e.isPrimary() else long(1) + vd.colorExterior(2) vd.colorColinear(3) vd.colorTwins(4)