Merge pull request #3978 from mlampert/feature/path-voronoi-vcarve-refactor
Path: Feature/path voronoi vcarve refactor
This commit is contained in:
@@ -58,6 +58,7 @@ namespace Path
|
||||
|
||||
// types
|
||||
typedef double coordinate_type;
|
||||
typedef boost::polygon::voronoi_vertex<double> vertex_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::voronoi_diagram<double> voronoi_diagram_type;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -27,19 +27,18 @@
|
||||
# include <boost/algorithm/string.hpp>
|
||||
#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 <Base/Exception.h>
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/VectorPy.h>
|
||||
|
||||
// files generated out of VoronoiCellPy.xml
|
||||
#include "VoronoiCellPy.cpp"
|
||||
#include "Mod/Path/App/VoronoiCellPy.cpp"
|
||||
|
||||
using namespace Path;
|
||||
|
||||
@@ -75,14 +74,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<VoronoiCellPy*>(lhs)->getVoronoiCellPtr();
|
||||
const VoronoiCell *vr = static_cast<VoronoiCellPy*>(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);
|
||||
|
||||
@@ -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"
|
||||
@@ -100,9 +100,9 @@
|
||||
<UserDocu>Returns true if edge goes through endpoint of the segment site</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="toGeom" Const="true">
|
||||
<Methode Name="toShape" Const="true">
|
||||
<Documentation>
|
||||
<UserDocu>Returns a geom representation of the edge (line segment or arc of parabola)</UserDocu>
|
||||
<UserDocu>Returns a shape for the edge</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="getDistances" Const="true">
|
||||
|
||||
@@ -24,9 +24,17 @@
|
||||
|
||||
|
||||
#ifndef _PreComp_
|
||||
# include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <Geom_Parabola.hxx>
|
||||
#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"
|
||||
@@ -35,19 +43,148 @@
|
||||
#include "Mod/Path/App/VoronoiEdgePy.h"
|
||||
#include "Mod/Path/App/VoronoiVertex.h"
|
||||
#include "Mod/Path/App/VoronoiVertexPy.h"
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#include <Mod/Part/App/LineSegmentPy.h>
|
||||
#include <Mod/Part/App/ArcOfParabolaPy.h>
|
||||
|
||||
// files generated out of VoronoiEdgePy.xml
|
||||
#include "VoronoiEdgePy.cpp"
|
||||
#include "Mod/Path/App/VoronoiEdgePy.cpp"
|
||||
|
||||
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;
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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<typename pt0_type, typename pt1_type>
|
||||
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<typename pt0_type, typename pt1_type>
|
||||
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)));
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) << '>';
|
||||
}
|
||||
|
||||
|
||||
// returns a string which represents the object e.g. when printed in python
|
||||
std::string VoronoiEdgePy::representation(void) const
|
||||
{
|
||||
@@ -92,16 +229,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<VoronoiEdgePy*>(lhs)->getVoronoiEdgePtr();
|
||||
const VoronoiEdge *vr = static_cast<VoronoiEdgePy*>(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);
|
||||
@@ -259,43 +394,16 @@ 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;
|
||||
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) {
|
||||
z1 = z0;
|
||||
}
|
||||
VoronoiEdge *e = getVoronoiEdgePtr();
|
||||
if (e->isBound()) {
|
||||
@@ -305,8 +413,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
|
||||
@@ -334,7 +444,7 @@ PyObject* VoronoiEdgePy::toGeom(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()) {
|
||||
@@ -352,8 +462,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,31 +485,103 @@ PyObject* VoronoiEdgePy::toGeom(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, z));
|
||||
p->setLocation(e->dia->scaledVector(loc, z));
|
||||
p->setAngleXU(atan2(axis.y(), axis.x()));
|
||||
p->setFocal(sqrt(axis.x() * axis.x() + axis.y() * axis.y()) / e->dia->getScale());
|
||||
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, z), param0)) {
|
||||
std::cerr << "closestParameter(v0) failed" << std::endl;
|
||||
}
|
||||
if (!p->closestParameter(e->dia->scaledVector(*v1, z), param1)) {
|
||||
std::cerr << "closestParameter(v0) failed" << std::endl;
|
||||
}
|
||||
a->setRange(param0, param1, false);
|
||||
// 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;
|
||||
}
|
||||
return new Part::ArcOfParabolaPy(a);
|
||||
|
||||
// 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) > fabs(dist1)) {
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
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(arc->handle());
|
||||
BRepBuilderAPI_MakeEdge mkBuilder(h, h->FirstParameter(), h->LastParameter());
|
||||
return new Part::TopoShapeEdgePy(new Part::TopoShape(mkBuilder.Shape()));
|
||||
}
|
||||
}
|
||||
Py_INCREF(Py_None);
|
||||
@@ -405,61 +589,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);
|
||||
@@ -468,22 +597,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);
|
||||
@@ -501,18 +614,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;
|
||||
|
||||
@@ -27,21 +27,20 @@
|
||||
# include <boost/algorithm/string.hpp>
|
||||
#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 <Base/Exception.h>
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -46,9 +46,9 @@
|
||||
</Documentation>
|
||||
<Parameter Name="IncidentEdge" Type="Object"/>
|
||||
</Attribute>
|
||||
<Methode Name="toGeom" Const="true">
|
||||
<Methode Name="toPoint" Const="true">
|
||||
<Documentation>
|
||||
<UserDocu>Returns a Vertex - or None if not possible</UserDocu>
|
||||
<UserDocu>Returns a Vector - or None if not possible</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
</PythonExport>
|
||||
|
||||
@@ -27,20 +27,19 @@
|
||||
# include <boost/algorithm/string.hpp>
|
||||
#endif
|
||||
|
||||
#include "Voronoi.h"
|
||||
#include "VoronoiPy.h"
|
||||
#include "VoronoiEdge.h"
|
||||
#include "VoronoiEdgePy.h"
|
||||
#include "VoronoiVertex.h"
|
||||
#include "VoronoiVertexPy.h"
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/GeometryPyCXX.h>
|
||||
#include <Base/PlacementPy.h>
|
||||
#include <Base/Vector3D.h>
|
||||
#include <Base/VectorPy.h>
|
||||
#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;
|
||||
|
||||
@@ -76,14 +75,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<VoronoiVertexPy*>(lhs)->getVoronoiVertexPtr();
|
||||
const VoronoiVertex *vr = static_cast<VoronoiVertexPy*>(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);
|
||||
@@ -151,7 +150,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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,19 +40,15 @@ from PySide import QtCore
|
||||
|
||||
__doc__ = "Class and implementation of Path Vcarve operation"
|
||||
|
||||
PRIMARY = 0
|
||||
EXTERIOR1 = 1
|
||||
EXTERIOR2 = 4
|
||||
TWIN = 2
|
||||
COLINEAR = 3
|
||||
SECONDARY = 5
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
PRIMARY = 0
|
||||
SECONDARY = 1
|
||||
EXTERIOR1 = 2
|
||||
EXTERIOR2 = 3
|
||||
COLINEAR = 4
|
||||
TWIN = 5
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
@@ -60,7 +56,105 @@ 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 = {}
|
||||
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])
|
||||
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]
|
||||
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)
|
||||
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
|
||||
|
||||
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, l)
|
||||
|
||||
|
||||
begin = {}
|
||||
end = {}
|
||||
|
||||
for i, w in enumerate(wires):
|
||||
begin[i] = w[ 0].Vertices[0].toPoint()
|
||||
end[i] = w[-1].Vertices[1].toPoint()
|
||||
|
||||
result = []
|
||||
while begin:
|
||||
(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 result
|
||||
|
||||
class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
'''Proxy class for Vcarve operation.'''
|
||||
@@ -101,6 +195,30 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
# upgrade ...
|
||||
self.setupAdditionalProperties(obj)
|
||||
|
||||
def _calculate_depth(self, MIC, zStart, zStop, zScale):
|
||||
# given a maximum inscribed circle (MIC) and tool angle,
|
||||
# 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
|
||||
|
||||
def _getPartEdge(self, edge, zStart, zStop, zScale):
|
||||
dist = edge.getDistances()
|
||||
return edge.toShape(self._calculate_depth(dist[0], zStart, zStop, zScale), self._calculate_depth(dist[1], zStart, zStop, zScale))
|
||||
|
||||
def _getPartEdges(self, obj, vWire):
|
||||
# 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(e, zStart, zStop, zScale))
|
||||
return edges
|
||||
|
||||
def buildPathMedial(self, obj, Faces):
|
||||
'''constructs a medial axis path using openvoronoi'''
|
||||
|
||||
@@ -114,115 +232,24 @@ 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))
|
||||
|
||||
return path
|
||||
|
||||
VD.clear()
|
||||
pathlist = []
|
||||
pathlist.append(Path.Command("(starting)"))
|
||||
voronoiWires = []
|
||||
for f in Faces:
|
||||
vd = Path.Voronoi()
|
||||
insert_many_wires(vd, f.Wires)
|
||||
@@ -233,17 +260,27 @@ 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)
|
||||
wires = _collectVoronoiWires(vd);
|
||||
if _sorting != 'global':
|
||||
wires = _sortVoronoiWires(wires)
|
||||
voronoiWires.extend(wires)
|
||||
VD.append((f, vd, wires))
|
||||
|
||||
for wire in getWires(edgelist):
|
||||
pathlist.extend(cutWire(wire))
|
||||
VD.append((f, vd, getWires(edgelist)))
|
||||
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):
|
||||
@@ -283,12 +320,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)
|
||||
|
||||
|
||||
174
src/Mod/Path/PathTests/TestPathVoronoi.py
Normal file
174
src/Mod/Path/PathTests/TestPathVoronoi.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
import sys
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
|
||||
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])
|
||||
|
||||
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.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'''
|
||||
|
||||
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.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'''
|
||||
|
||||
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.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'''
|
||||
|
||||
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()
|
||||
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.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'''
|
||||
|
||||
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)
|
||||
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.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'''
|
||||
|
||||
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)
|
||||
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.assertRoughly(e.valueAt(e.FirstParameter).z, 2.37)
|
||||
self.assertRoughly(e.valueAt(e.LastParameter).z, 5.14)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user