From 35da5c890ce524f7e8f80c9c2aaf318a8b05419d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Oct 2020 19:36:06 -0700 Subject: [PATCH] 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) +