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)
+