Commit 3ca79c3c authored by Whitham D. Reeve II's avatar Whitham D. Reeve II

Replace Point class with a 3 position tuple and a collection of helper functions.

PyCam has been spending about 75% of it's time creating point objects. I decided to replace
the pycam Point object with a 3 position tuple, and the pycam Vector object with a 4 position tuple.

All of the point object methods were rewritten to accept and emit tuples. The transform_by_matrix method
was rewritten to accept both 3 and 4 position tuples with behavior dependent on that size. The
normalized method is the only method required to emit the same kind of object that it is called on, and
this has been taken care of with the tuple version.

What does this mean?
All instances of Point(x,y,z) have been converted into (x,y,z)
All instances of Vector(x,y,z) have been converted into (x,y,z,'v')
The notation to access the x,y,z of the Point objects has been been changed
	from p.x,p.y,p.z to p[0],p[1],p[2]
The notation for the point math functions has been completely changed.
Instead of p1.sub(p2) it has been converted to psub(p1,p2)
Instead of p1.sub(p2).mul(0.5) it has been converted to pmul(psub(p1,p2), 0.5)

It is very important to point out that the tuple is an immutable type.
You can not change it once you create it.
t[0] = calculated_x_value # syntax error
You have to replace the reference (t) to the old tuple with a new reference to a new tuple.
t = (calculated_x_value, t[1], t[2]) # works

There was a particularly hairy mutable/immutable barrier in the next() generator present in each class that
inherits TransformableContainer. The TransformableContainer.transform_by_matrix function uses these
generators to collapse polygons and lines and triangles into points where a shift is performed on each.
Now that point is immutable, you can not change the value emitted by the generator.
Geometry/__init__.py transform_by_marix and the associated next() generator in each sub class has been
rewritten to transfer the attr used to store the reference to the tuple.
Geometry/__init__.py transform_by_matrix now sets these attributes which effectively gets around this
limitation.

There could be some old point object code in any of the files not contained in this commit.
It is impossible to know without running the code path and/or careful analysis of each file.

Some list comprehensions that convert a list of point objects into a 3 position tuple have been removed.

Whitham D. Reeve II
parent 3b70b70b
...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import IDGenerator from pycam.Geometry import IDGenerator
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import number, INFINITE, epsilon from pycam.Geometry.utils import number, INFINITE, epsilon
from pycam.Geometry.intersection import intersect_cylinder_point, \ from pycam.Geometry.intersection import intersect_cylinder_point, \
intersect_cylinder_line intersect_cylinder_line
...@@ -32,12 +32,12 @@ import uuid ...@@ -32,12 +32,12 @@ import uuid
class BaseCutter(IDGenerator): class BaseCutter(IDGenerator):
vertical = Point(0, 0, -1) vertical = (0, 0, -1)
def __init__(self, radius, location=None, height=None): def __init__(self, radius, location=None, height=None):
super(BaseCutter, self).__init__() super(BaseCutter, self).__init__()
if location is None: if location is None:
location = Point(0, 0, 0) location = (0, 0, 0)
if height is None: if height is None:
height = 10 height = 10
radius = number(radius) radius = number(radius)
...@@ -56,22 +56,22 @@ class BaseCutter(IDGenerator): ...@@ -56,22 +56,22 @@ class BaseCutter(IDGenerator):
def get_minx(self, start=None): def get_minx(self, start=None):
if start is None: if start is None:
start = self.location start = self.location
return start.x - self.distance_radius return start[0] - self.distance_radius
def get_maxx(self, start=None): def get_maxx(self, start=None):
if start is None: if start is None:
start = self.location start = self.location
return start.x + self.distance_radius return start[0] + self.distance_radius
def get_miny(self, start=None): def get_miny(self, start=None):
if start is None: if start is None:
start = self.location start = self.location
return start.y - self.distance_radius return start[1] - self.distance_radius
def get_maxy(self, start=None): def get_maxy(self, start=None):
if start is None: if start is None:
start = self.location start = self.location
return start.y + self.distance_radius return start[1] + self.distance_radius
def update_uuid(self): def update_uuid(self):
self.uuid = uuid.uuid4() self.uuid = uuid.uuid4()
...@@ -105,7 +105,7 @@ class BaseCutter(IDGenerator): ...@@ -105,7 +105,7 @@ class BaseCutter(IDGenerator):
# "moveto" is used for collision detection calculation. # "moveto" is used for collision detection calculation.
self.location = location self.location = location
for shape, set_pos_func in self.shape.values(): for shape, set_pos_func in self.shape.values():
set_pos_func(location.x, location.y, location.z) set_pos_func(location[0], location[1], location[2])
def intersect(self, direction, triangle, start=None): def intersect(self, direction, triangle, start=None):
raise NotImplementedError("Inherited class of BaseCutter does not " \ raise NotImplementedError("Inherited class of BaseCutter does not " \
...@@ -126,7 +126,7 @@ class BaseCutter(IDGenerator): ...@@ -126,7 +126,7 @@ class BaseCutter(IDGenerator):
# check bounding circle collision # check bounding circle collision
c = triangle.middle c = triangle.middle
if (c.x - start.x) ** 2 + (c.y - start.y) ** 2 \ if (c[0] - start[0]) ** 2 + (c[1] - start[1]) ** 2 \
> (self.distance_radiussq + 2 * self.distance_radius \ > (self.distance_radiussq + 2 * self.distance_radius \
* triangle.radius + triangle.radiussq) + epsilon: * triangle.radius + triangle.radiussq) + epsilon:
return None return None
...@@ -150,7 +150,8 @@ class BaseCutter(IDGenerator): ...@@ -150,7 +150,8 @@ class BaseCutter(IDGenerator):
start=start) start=start)
if cp: if cp:
# check if the contact point is between the endpoints # check if the contact point is between the endpoints
m = cp.sub(edge.p1).dot(edge.dir) m = pdot(psub(cp, edge.p1), edge.dir)
#m = cp.sub(edge.p1).dot(edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon): if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, cp) return (None, INFINITE, cp)
return (cl, l, cp) return (cl, l, cp)
...@@ -159,8 +160,8 @@ class BaseCutter(IDGenerator): ...@@ -159,8 +160,8 @@ class BaseCutter(IDGenerator):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_cylinder_point( (ccp, cp, l) = intersect_cylinder_point(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center)
self.distance_radius, self.distance_radiussq, direction, point) self.axis, self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection # offset intersection
if ccp: if ccp:
cl = cp.add(start.sub(ccp)) cl = cp.add(start.sub(ccp))
...@@ -172,7 +173,8 @@ class BaseCutter(IDGenerator): ...@@ -172,7 +173,8 @@ class BaseCutter(IDGenerator):
start = self.location start = self.location
(cl, ccp, cp, l) = self.intersect_cylinder_point(direction, point, (cl, ccp, cp, l) = self.intersect_cylinder_point(direction, point,
start=start) start=start)
if ccp and ccp.z < start.sub(self.location).add(self.center).z: #if ccp and ccp[2] < start.sub(self.location).add(self.center)[2]:
if ccp and ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None) return (None, INFINITE, None)
return (cl, l, cp) return (cl, l, cp)
...@@ -180,11 +182,12 @@ class BaseCutter(IDGenerator): ...@@ -180,11 +182,12 @@ class BaseCutter(IDGenerator):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_cylinder_line( (ccp, cp, l) = intersect_cylinder_line(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
self.distance_radius, self.distance_radiussq, direction, edge) self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection # offset intersection
if ccp: if ccp:
cl = start.add(cp.sub(ccp)) cl = padd(start, psub(cp, ccp))
#cl = start.add(cp.sub(ccp))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -195,10 +198,12 @@ class BaseCutter(IDGenerator): ...@@ -195,10 +198,12 @@ class BaseCutter(IDGenerator):
start=start) start=start)
if not ccp: if not ccp:
return (None, INFINITE, None) return (None, INFINITE, None)
m = cp.sub(edge.p1).dot(edge.dir) m = pdot(psub(cp, edge.p1), edge.dir)
#m = cp.sub(edge.p1).dot(edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon): if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, None) return (None, INFINITE, None)
if ccp.z < start.sub(self.location).add(self.center).z: #if ccp.z < start.sub(self.location).add(self.center)[2]:
if ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None) return (None, INFINITE, None)
return (cl, l, cp) return (cl, l, cp)
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.utils import INFINITE, sqrt from pycam.Geometry.utils import INFINITE, sqrt
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.intersection import intersect_circle_plane, \ from pycam.Geometry.intersection import intersect_circle_plane, \
intersect_circle_point, intersect_circle_line intersect_circle_point, intersect_circle_line
from pycam.Cutters.BaseCutter import BaseCutter from pycam.Cutters.BaseCutter import BaseCutter
...@@ -40,7 +40,7 @@ class CylindricalCutter(BaseCutter): ...@@ -40,7 +40,7 @@ class CylindricalCutter(BaseCutter):
def __init__(self, radius, **kwargs): def __init__(self, radius, **kwargs):
BaseCutter.__init__(self, radius, **kwargs) BaseCutter.__init__(self, radius, **kwargs)
self.axis = Vector(0, 0, 1) self.axis = (0, 0, 1, 'v')
def __repr__(self): def __repr__(self):
return "CylindricalCutter<%s,%s>" % (self.location, self.radius) return "CylindricalCutter<%s,%s>" % (self.location, self.radius)
...@@ -92,17 +92,17 @@ class CylindricalCutter(BaseCutter): ...@@ -92,17 +92,17 @@ class CylindricalCutter(BaseCutter):
geom_connect_transform = ode.GeomTransform(geom.space) geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody()) geom_connect_transform.setBody(geom.getBody())
geom_connect = ode_physics.get_parallelepiped_geom( geom_connect = ode_physics.get_parallelepiped_geom(
(Point(-hypotenuse / 2, radius, -diff_z / 2), ((-hypotenuse / 2, radius, -diff_z / 2),
Point(hypotenuse / 2, radius, diff_z / 2), (hypotenuse / 2, radius, diff_z / 2),
Point(hypotenuse / 2, -radius, diff_z / 2), (hypotenuse / 2, -radius, diff_z / 2),
Point(-hypotenuse / 2, -radius, -diff_z / 2)), (-hypotenuse / 2, -radius, -diff_z / 2)),
(Point(-hypotenuse / 2, radius, ((-hypotenuse / 2, radius,
self.height - diff_z / 2), self.height - diff_z / 2),
Point(hypotenuse / 2, (hypotenuse / 2,
radius, self.height + diff_z / 2), radius, self.height + diff_z / 2),
Point(hypotenuse / 2, -radius, (hypotenuse / 2, -radius,
self.height + diff_z / 2), self.height + diff_z / 2),
Point(-hypotenuse / 2, -radius, (-hypotenuse / 2, -radius,
self.height - diff_z / 2))) self.height - diff_z / 2)))
geom_connect.setRotation(rot_matrix_box) geom_connect.setRotation(rot_matrix_box)
geom_connect.setPosition((hypotenuse / 2, 0, radius)) geom_connect.setPosition((hypotenuse / 2, 0, radius))
...@@ -119,7 +119,7 @@ class CylindricalCutter(BaseCutter): ...@@ -119,7 +119,7 @@ class CylindricalCutter(BaseCutter):
if not GL_enabled: if not GL_enabled:
return return
GL.glPushMatrix() GL.glPushMatrix()
GL.glTranslate(self.center.x, self.center.y, self.center.z) GL.glTranslate(self.center[0], self.center[1], self.center[2])
if not hasattr(self, "_cylinder"): if not hasattr(self, "_cylinder"):
self._cylinder = GLU.gluNewQuadric() self._cylinder = GLU.gluNewQuadric()
GLU.gluCylinder(self._cylinder, self.radius, self.radius, self.height, GLU.gluCylinder(self._cylinder, self.radius, self.radius, self.height,
...@@ -131,17 +131,18 @@ class CylindricalCutter(BaseCutter): ...@@ -131,17 +131,18 @@ class CylindricalCutter(BaseCutter):
def moveto(self, location, **kwargs): def moveto(self, location, **kwargs):
BaseCutter.moveto(self, location, **kwargs) BaseCutter.moveto(self, location, **kwargs)
self.center = Point(location.x, location.y, self.center = (location[0], location[1],
location.z - self.get_required_distance()) location[2] - self.get_required_distance())
def intersect_circle_plane(self, direction, triangle, start=None): def intersect_circle_plane(self, direction, triangle, start=None):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, d) = intersect_circle_plane( (ccp, cp, d) = intersect_circle_plane(
start.sub(self.location).add(self.center), self.distance_radius, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
direction, triangle) self.distance_radius, direction, triangle)
if ccp and cp: if ccp and cp:
cl = cp.add(start.sub(ccp)) cl = padd(cp, psub(start, ccp))
#cl = cp.add(start.sub(ccp))
return (cl, ccp, cp, d) return (cl, ccp, cp, d)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -149,10 +150,11 @@ class CylindricalCutter(BaseCutter): ...@@ -149,10 +150,11 @@ class CylindricalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_circle_point( (ccp, cp, l) = intersect_circle_point(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
self.distance_radius, self.distance_radiussq, direction, point) self.axis, self.distance_radius, self.distance_radiussq, direction, point)
if ccp: if ccp:
cl = cp.add(start.sub(ccp)) cl = padd(cp, psub(start, ccp))
#cl = cp.add(start.sub(ccp))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -160,10 +162,11 @@ class CylindricalCutter(BaseCutter): ...@@ -160,10 +162,11 @@ class CylindricalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_circle_line( (ccp, cp, l) = intersect_circle_line(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
self.distance_radius, self.distance_radiussq, direction, edge) self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
if ccp: if ccp:
cl = cp.add(start.sub(ccp)) cl = padd(cp, psub(start, ccp))
#cl = cp.add(start.sub(ccp))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -177,7 +180,7 @@ class CylindricalCutter(BaseCutter): ...@@ -177,7 +180,7 @@ class CylindricalCutter(BaseCutter):
d = d_t d = d_t
cl = cl_t cl = cl_t
cp = cp_t cp = cp_t
if cl and (direction.x == 0) and (direction.y == 0): if cl and (direction[0] == 0) and (direction[1] == 0):
#print 'circle_triangle:' #print 'circle_triangle:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp #print 'cl is:', cl, 'd is:', d, 'cp is:', cp
return (cl, d, cp) return (cl, d, cp)
...@@ -202,7 +205,7 @@ class CylindricalCutter(BaseCutter): ...@@ -202,7 +205,7 @@ class CylindricalCutter(BaseCutter):
cl = cl_e3 cl = cl_e3
cp = cp_e3 cp = cp_e3
#print 'circle_edge e3:' #print 'circle_edge e3:'
if cl and (direction.x == 0) and (direction.y == 0): if cl and (direction[0] == 0) and (direction[1] == 0):
#print 'circle_edge:' #print 'circle_edge:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp #print 'cl is:', cl, 'd is:', d, 'cp is:', cp
return (cl, d, cp) return (cl, d, cp)
...@@ -227,11 +230,11 @@ class CylindricalCutter(BaseCutter): ...@@ -227,11 +230,11 @@ class CylindricalCutter(BaseCutter):
cl = cl_p3 cl = cl_p3
cp = cp_p3 cp = cp_p3
#print 'circle vertex p3:' #print 'circle vertex p3:'
if cl and (direction.x == 0) and (direction.y == 0): if cl and (direction[0] == 0) and (direction[1] == 0):
#print 'circle vertex:' #print 'circle vertex:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp #print 'cl is:', cl, 'd is:', d, 'cp is:', cp
return (cl, d, cp) return (cl, d, cp)
if (direction.x != 0) or (direction.y != 0): if (direction[0] != 0) or (direction[1] != 0):
(cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction, (cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction,
triangle.p1, start=start) triangle.p1, start=start)
(cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction, (cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction,
......
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry import Matrix from pycam.Geometry import Matrix
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import INFINITE, epsilon, sqrt from pycam.Geometry.utils import INFINITE, epsilon, sqrt
from pycam.Geometry.intersection import intersect_sphere_plane, \ from pycam.Geometry.intersection import intersect_sphere_plane, \
intersect_sphere_point, intersect_sphere_line intersect_sphere_point, intersect_sphere_line
...@@ -41,7 +41,7 @@ class SphericalCutter(BaseCutter): ...@@ -41,7 +41,7 @@ class SphericalCutter(BaseCutter):
def __init__(self, radius, **kwargs): def __init__(self, radius, **kwargs):
BaseCutter.__init__(self, radius, **kwargs) BaseCutter.__init__(self, radius, **kwargs)
self.axis = Vector(0, 0, 1) self.axis = (0, 0, 1, 'v')
def __repr__(self): def __repr__(self):
return "SphericalCutter<%s,%s>" % (self.location, self.radius) return "SphericalCutter<%s,%s>" % (self.location, self.radius)
...@@ -84,17 +84,17 @@ class SphericalCutter(BaseCutter): ...@@ -84,17 +84,17 @@ class SphericalCutter(BaseCutter):
geom_connect_transform = ode.GeomTransform(geom.space) geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody()) geom_connect_transform.setBody(geom.getBody())
geom_connect = ode_physics.get_parallelepiped_geom(( geom_connect = ode_physics.get_parallelepiped_geom((
Point(-hypotenuse / 2, radius, -diff_z / 2), (-hypotenuse / 2, radius, -diff_z / 2),
Point(hypotenuse / 2, radius, diff_z / 2), (hypotenuse / 2, radius, diff_z / 2),
Point(hypotenuse / 2, -radius, diff_z / 2), (hypotenuse / 2, -radius, diff_z / 2),
Point(-hypotenuse / 2, -radius, -diff_z / 2)), (-hypotenuse / 2, -radius, -diff_z / 2)),
(Point(-hypotenuse / 2, radius, ((-hypotenuse / 2, radius,
self.height - diff_z / 2), self.height - diff_z / 2),
Point(hypotenuse / 2, radius, (hypotenuse / 2, radius,
self.height + diff_z / 2), self.height + diff_z / 2),
Point(hypotenuse / 2, -radius, (hypotenuse / 2, -radius,
self.height + diff_z / 2), self.height + diff_z / 2),
Point(-hypotenuse / 2, -radius, (-hypotenuse / 2, -radius,
self.height - diff_z / 2))) self.height - diff_z / 2)))
geom_connect.setRotation(rot_matrix_box) geom_connect.setRotation(rot_matrix_box)
geom_connect.setPosition((hypotenuse / 2, 0, radius)) geom_connect.setPosition((hypotenuse / 2, 0, radius))
...@@ -129,7 +129,7 @@ class SphericalCutter(BaseCutter): ...@@ -129,7 +129,7 @@ class SphericalCutter(BaseCutter):
if not GL_enabled: if not GL_enabled:
return return
GL.glPushMatrix() GL.glPushMatrix()
GL.glTranslate(self.center.x, self.center.y, self.center.z) GL.glTranslate(self.center[0], self.center[1], self.center[2])
if not hasattr(self, "_sphere"): if not hasattr(self, "_sphere"):
self._sphere = GLU.gluNewQuadric() self._sphere = GLU.gluNewQuadric()
GLU.gluSphere(self._sphere, self.radius, 10, 10) GLU.gluSphere(self._sphere, self.radius, 10, 10)
...@@ -141,17 +141,18 @@ class SphericalCutter(BaseCutter): ...@@ -141,17 +141,18 @@ class SphericalCutter(BaseCutter):
def moveto(self, location, **kwargs): def moveto(self, location, **kwargs):
BaseCutter.moveto(self, location, **kwargs) BaseCutter.moveto(self, location, **kwargs)
self.center = Point(location.x, location.y, location.z + self.radius) self.center = (location[0], location[1], location[2] + self.radius)
def intersect_sphere_plane(self, direction, triangle, start=None): def intersect_sphere_plane(self, direction, triangle, start=None):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, d) = intersect_sphere_plane( (ccp, cp, d) = intersect_sphere_plane(
start.sub(self.location).add(self.center), self.distance_radius, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
direction, triangle) self.distance_radius, direction, triangle)
# offset intersection # offset intersection
if ccp: if ccp:
cl = cp.add(start.sub(ccp)) cl = padd(cp, psub(start, ccp))
#cl = cp.add(start.sub(ccp))
return (cl, ccp, cp, d) return (cl, ccp, cp, d)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -166,8 +167,8 @@ class SphericalCutter(BaseCutter): ...@@ -166,8 +167,8 @@ class SphericalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_sphere_point( (ccp, cp, l) = intersect_sphere_point(
start.sub(self.location).add(self.center), self.distance_radius, padd(psub(start, self.location), self.center), #start.sub(self.location).add(self.center),
self.distance_radiussq, direction, point) self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection # offset intersection
cl = None cl = None
if cp: if cp:
...@@ -183,11 +184,12 @@ class SphericalCutter(BaseCutter): ...@@ -183,11 +184,12 @@ class SphericalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_sphere_line( (ccp, cp, l) = intersect_sphere_line(
start.sub(self.location).add(self.center), self.distance_radius, padd(psub(start, self.location), self.center), # start.sub(self.location).add(self.center),
self.distance_radiussq, direction, edge) self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection # offset intersection
if ccp: if ccp:
cl = cp.sub(ccp.sub(start)) cl = psub(cp, psub(ccp, start))
#cl = cp.sub(ccp.sub(start))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -196,9 +198,11 @@ class SphericalCutter(BaseCutter): ...@@ -196,9 +198,11 @@ class SphericalCutter(BaseCutter):
start=start) start=start)
if cp: if cp:
# check if the contact point is between the endpoints # check if the contact point is between the endpoints
d = edge.p2.sub(edge.p1) d = psub(edge.p2, edge.p1)
m = cp.sub(edge.p1).dot(d) #d = edge.p2.sub(edge.p1)
if (m < -epsilon) or (m > d.normsq + epsilon): m = pdot(psub(cp, edge.p1), d)
#m = cp.sub(edge.p1).dot(d)
if (m < -epsilon) or (m > pnormsq(d) + epsilon):
return (None, INFINITE, None) return (None, INFINITE, None)
return (cl, l, cp) return (cl, l, cp)
...@@ -216,7 +220,7 @@ class SphericalCutter(BaseCutter): ...@@ -216,7 +220,7 @@ class SphericalCutter(BaseCutter):
d = d_t d = d_t
cl = cl_t cl = cl_t
cp = cp_t cp = cp_t
if cl and (direction.x == 0) and (direction.y == 0): if cl and (direction[0] == 0) and (direction[1] == 0):
return (cl, d, cp) return (cl, d, cp)
(cl_e1, d_e1, cp_e1) = self.intersect_sphere_edge(direction, (cl_e1, d_e1, cp_e1) = self.intersect_sphere_edge(direction,
triangle.e1, start=start) triangle.e1, start=start)
...@@ -254,9 +258,9 @@ class SphericalCutter(BaseCutter): ...@@ -254,9 +258,9 @@ class SphericalCutter(BaseCutter):
d = d_p3 d = d_p3
cl = cl_p3 cl = cl_p3
cp = cp_p3 cp = cp_p3
if cl and (direction.x == 0) and (direction.y == 0): if cl and (direction[0] == 0) and (direction[1] == 0):
return (cl, d, cp) return (cl, d, cp)
if (direction.x != 0) or (direction.y != 0): if (direction[0] != 0) or (direction[1] != 0):
(cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction, (cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction,
triangle.p1, start=start) triangle.p1, start=start)
(cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction, (cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction,
......
...@@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License ...@@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import INFINITE, number, epsilon from pycam.Geometry.utils import INFINITE, number, epsilon
from pycam.Geometry.intersection import intersect_torus_plane, \ from pycam.Geometry.intersection import intersect_torus_plane, \
intersect_torus_point, intersect_circle_plane, intersect_circle_point, \ intersect_torus_point, intersect_circle_plane, intersect_circle_point, \
...@@ -46,7 +46,7 @@ class ToroidalCutter(BaseCutter): ...@@ -46,7 +46,7 @@ class ToroidalCutter(BaseCutter):
# we need "minorradius" for "moveto" - thus set it before parent's init # we need "minorradius" for "moveto" - thus set it before parent's init
BaseCutter.__init__(self, radius, **kwargs) BaseCutter.__init__(self, radius, **kwargs)
self.majorradius = self.radius - minorradius self.majorradius = self.radius - minorradius
self.axis = Point(0, 0, 1) self.axis = (0, 0, 1)
self.majorradiussq = self.majorradius ** 2 self.majorradiussq = self.majorradius ** 2
self.minorradiussq = self.minorradius ** 2 self.minorradiussq = self.minorradius ** 2
self.distance_majorradius = self.majorradius \ self.distance_majorradius = self.majorradius \
...@@ -97,7 +97,7 @@ class ToroidalCutter(BaseCutter): ...@@ -97,7 +97,7 @@ class ToroidalCutter(BaseCutter):
if not GL_enabled: if not GL_enabled:
return return
GL.glPushMatrix() GL.glPushMatrix()
GL.glTranslate(self.center.x, self.center.y, self.center.z) GL.glTranslate(self.center[0], self.center[1], self.center[2])
GLUT.glutSolidTorus(self.minorradius, self.majorradius, 10, 20) GLUT.glutSolidTorus(self.minorradius, self.majorradius, 10, 20)
if not hasattr(self, "_cylinder"): if not hasattr(self, "_cylinder"):
self._cylinder = GLU.gluNewQuadric() self._cylinder = GLU.gluNewQuadric()
...@@ -105,7 +105,7 @@ class ToroidalCutter(BaseCutter): ...@@ -105,7 +105,7 @@ class ToroidalCutter(BaseCutter):
10, 20) 10, 20)
GL.glPopMatrix() GL.glPopMatrix()
GL.glPushMatrix() GL.glPushMatrix()
GL.glTranslate(self.location.x, self.location.y, self.location.z) GL.glTranslate(self.location[0], self.location[1], self.location[2])
if not hasattr(self, "_disk"): if not hasattr(self, "_disk"):
self._disk = GLU.gluNewQuadric() self._disk = GLU.gluNewQuadric()
GLU.gluDisk(self._disk, 0, self.majorradius, 20, 10) GLU.gluDisk(self._disk, 0, self.majorradius, 20, 10)
...@@ -113,17 +113,18 @@ class ToroidalCutter(BaseCutter): ...@@ -113,17 +113,18 @@ class ToroidalCutter(BaseCutter):
def moveto(self, location, **kwargs): def moveto(self, location, **kwargs):
BaseCutter.moveto(self, location, **kwargs) BaseCutter.moveto(self, location, **kwargs)
self.center = Point(location.x, location.y, location.z+self.minorradius) self.center = (location[0], location[1], location[2]+self.minorradius)
def intersect_torus_plane(self, direction, triangle, start=None): def intersect_torus_plane(self, direction, triangle, start=None):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_torus_plane( (ccp, cp, l) = intersect_torus_plane(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), # start.sub(self.location).add(self.center),
self.distance_majorradius, self.distance_minorradius, direction, self.axis, self.distance_majorradius, self.distance_minorradius, direction,
triangle) triangle)
if cp: if cp:
cl = cp.add(start.sub(ccp)) cl = padd(cp, psub(start, ccp))
#cl = cp.add(start.sub(ccp))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -138,12 +139,13 @@ class ToroidalCutter(BaseCutter): ...@@ -138,12 +139,13 @@ class ToroidalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_torus_point( (ccp, cp, l) = intersect_torus_point(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), # start.sub(self.location).add(self.center),
self.distance_majorradius, self.distance_minorradius, self.axis, self.distance_majorradius, self.distance_minorradius,
self.distance_majorradiussq, self.distance_minorradiussq, self.distance_majorradiussq, self.distance_minorradiussq,
direction, point) direction, point)
if ccp: if ccp:
cl = point.add(start.sub(ccp)) cl = padd(point, psub(start, ccp))
#cl = point.add(start.sub(ccp))
return (cl, ccp, point, l) return (cl, ccp, point, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -195,11 +197,12 @@ class ToroidalCutter(BaseCutter): ...@@ -195,11 +197,12 @@ class ToroidalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_cylinder_point( (ccp, cp, l) = intersect_cylinder_point(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), # start.sub(self.location).add(self.center),
self.distance_radius, self.distance_radiussq, direction, point) self.axis, self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection # offset intersection
if ccp: if ccp:
cl = start.add(direction.mul(l)) cl = padd(start, pmul(direction, l))
#cl = start.add(direction.mul(l))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -207,21 +210,23 @@ class ToroidalCutter(BaseCutter): ...@@ -207,21 +210,23 @@ class ToroidalCutter(BaseCutter):
if start is None: if start is None:
start = self.location start = self.location
(ccp, cp, l) = intersect_cylinder_line( (ccp, cp, l) = intersect_cylinder_line(
start.sub(self.location).add(self.center), self.axis, padd(psub(start, self.location), self.center), # start.sub(self.location).add(self.center),
self.distance_radius, self.distance_radiussq, direction, edge) self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection # offset intersection
if ccp: if ccp:
cl = start.add(cp.sub(ccp)) cl = padd(start, psub(cp, ccp))
#cl = start.add(cp.sub(ccp))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
def intersect_cylinder_edge(self, direction, edge, start=None): def intersect_cylinder_edge(self, direction, edge, start=None):
(cl, ccp, cp, l) = self.intersect_cylinder_line(direction, edge, (cl, ccp, cp, l) = self.intersect_cylinder_line(direction, edge,
start=start) start=start)
if ccp and ccp.z < self.center.z: if ccp and ccp[2] < self.center[2]:
return (None, INFINITE, None) return (None, INFINITE, None)
if ccp: if ccp:
m = cp.sub(edge.p1).dot(edge.dir) m = pdot(psub(cp, edge.p1), edge.dir)
#m = cp.sub(edge.p1).dot(edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon): if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, None) return (None, INFINITE, None)
return (cl, l, cp) return (cl, l, cp)
...@@ -233,7 +238,8 @@ class ToroidalCutter(BaseCutter): ...@@ -233,7 +238,8 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, direction, triangle) self.distance_majorradius, direction, triangle)
# offset intersection # offset intersection
if ccp: if ccp:
cl = cp.sub(ccp.sub(start)) cl = psub(cp, psub(ccp, start))
#cl = cp.sub(ccp.sub(start))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -244,7 +250,8 @@ class ToroidalCutter(BaseCutter): ...@@ -244,7 +250,8 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, self.distance_majorradiussq, self.distance_majorradius, self.distance_majorradiussq,
direction, point) direction, point)
if ccp: if ccp:
cl = cp.sub(ccp.sub(start)) cl = psub(cp, psub(ccp, start))
#cl = cp.sub(ccp.sub(start))
return (cl, ccp, point, l) return (cl, ccp, point, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -255,7 +262,8 @@ class ToroidalCutter(BaseCutter): ...@@ -255,7 +262,8 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, self.distance_majorradiussq, self.distance_majorradius, self.distance_majorradiussq,
direction, edge) direction, edge)
if ccp: if ccp:
cl = cp.sub(ccp.sub(start)) cl = psub(cp, psub(ccp, start))
#cl = cp.sub(ccp.sub(start))
return (cl, ccp, cp, l) return (cl, ccp, cp, l)
return (None, None, None, INFINITE) return (None, None, None, INFINITE)
...@@ -347,7 +355,7 @@ class ToroidalCutter(BaseCutter): ...@@ -347,7 +355,7 @@ class ToroidalCutter(BaseCutter):
d = d_e3 d = d_e3
cl = cl_e3 cl = cl_e3
cp = cp_e3 cp = cp_e3
if direction.x != 0 or direction.y != 0: if direction[0] != 0 or direction[1] != 0:
(cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction, (cl_p1, d_p1, cp_p1) = self.intersect_cylinder_vertex(direction,
triangle.p1, start=start) triangle.p1, start=start)
(cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction, (cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction,
......
...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import TransformableContainer from pycam.Geometry import TransformableContainer
from pycam.Geometry.Model import ContourModel from pycam.Geometry.Model import ContourModel
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
TEXT_ALIGN_LEFT = 0 TEXT_ALIGN_LEFT = 0
TEXT_ALIGN_CENTER = 1 TEXT_ALIGN_CENTER = 1
...@@ -50,9 +50,7 @@ class Letter(TransformableContainer): ...@@ -50,9 +50,7 @@ class Letter(TransformableContainer):
def get_positioned_lines(self, base_point, skew=None): def get_positioned_lines(self, base_point, skew=None):
result = [] result = []
get_skewed_point = lambda p: \ get_skewed_point = lambda p: (base_point[0] + p[0] + (p[1] * skew / 100.0), base_point[1] + p[1], base_point[2])
Point(base_point.x + p.x + (p.y * skew / 100.0),
base_point.y + p.y, base_point.z)
for line in self.lines: for line in self.lines:
skewed_p1 = get_skewed_point(line.p1) skewed_p1 = get_skewed_point(line.p1)
skewed_p2 = get_skewed_point(line.p2) skewed_p2 = get_skewed_point(line.p2)
...@@ -133,13 +131,15 @@ class Charset(object): ...@@ -133,13 +131,15 @@ class Charset(object):
# update line height # update line height
line_height = max(line_height, charset_letter.maxy()) line_height = max(line_height, charset_letter.maxy())
# shift the base position # shift the base position
base = base.add(Point( base = base.add((
charset_letter.maxx() + letter_spacing, 0, 0)) charset_letter.maxx() + letter_spacing, 0, 0))
else: else:
# unknown character - add a small whitespace # unknown character - add a small whitespace
base = base.add(Point(letter_spacing, 0, 0)) base = padd(base, (letter_spacing, 0, 0))
#base = base.add(Point(letter_spacing, 0, 0))
# go to the next line # go to the next line
base = Point(origin.x, base.y - line_height * line_factor, origin.z) base = (origin[0], base[1] - line_height * line_factor, origin[2])
#base = Point(origin.x, base.y - line_height * line_factor, origin.z)
if not current_line.maxx is None: if not current_line.maxx is None:
if align == TEXT_ALIGN_CENTER: if align == TEXT_ALIGN_CENTER:
current_line.shift(-current_line.maxx / 2, 0, 0) current_line.shift(-current_line.maxx / 2, 0, 0)
......
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry import TransformableContainer, IDGenerator from pycam.Geometry import TransformableContainer, IDGenerator
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.utils import epsilon, sqrt from pycam.Geometry.utils import epsilon, sqrt
# OpenGLTools will be imported later, if necessary # OpenGLTools will be imported later, if necessary
...@@ -48,61 +48,64 @@ class Line(IDGenerator, TransformableContainer): ...@@ -48,61 +48,64 @@ class Line(IDGenerator, TransformableContainer):
self.reset_cache() self.reset_cache()
def copy(self): def copy(self):
return self.__class__(self.p1.copy(), self.p2.copy()) return self.__class__(self.p1, self.p2)
@property @property
def vector(self): def vector(self):
if self._vector is None: if self._vector is None:
self._vector = self.p2.sub(self.p1) self._vector = psub(self.p2, self.p1)
#self._vector = self.p2.sub(self.p1)
return self._vector return self._vector
@property @property
def dir(self): def dir(self):
return self.vector.normalized() return pnormalized(self.vector)
#return self.vector.normalized()
@property @property
def len(self): def len(self):
return self.vector.norm return pnorm(self.vector)
#return self.vector.norm
@property @property
def minx(self): def minx(self):
if self._minx is None: if self._minx is None:
self._minx = min(self.p1.x, self.p2.x) self._minx = min(self.p1[0], self.p2[0])
return self._minx return self._minx
@property @property
def maxx(self): def maxx(self):
if self._maxx is None: if self._maxx is None:
self._maxx = max(self.p1.x, self.p2.x) self._maxx = max(self.p1[0], self.p2[0])
return self._maxx return self._maxx
@property @property
def miny(self): def miny(self):
if self._miny is None: if self._miny is None:
self._miny = min(self.p1.y, self.p2.y) self._miny = min(self.p1[1], self.p2[1])
return self._miny return self._miny
@property @property
def maxy(self): def maxy(self):
if self._maxy is None: if self._maxy is None:
self._maxy = max(self.p1.y, self.p2.y) self._maxy = max(self.p1[1], self.p2[1])
return self._maxy return self._maxy
@property @property
def minz(self): def minz(self):
if self._minz is None: if self._minz is None:
self._minz = min(self.p1.z, self.p2.z) self._minz = min(self.p1[2], self.p2[2])
return self._minz return self._minz
@property @property
def maxz(self): def maxz(self):
if self._maxz is None: if self._maxz is None:
self._maxz = max(self.p1.z, self.p2.z) self._maxz = max(self.p1[2], self.p2[2])
return self._maxz return self._maxz
def __repr__(self): def __repr__(self):
return "Line<%g,%g,%g>-<%g,%g,%g>" % (self.p1.x, self.p1.y, self.p1.z, return "Line<%g,%g,%g>-<%g,%g,%g>" % (self.p1[0], self.p1[1], self.p1[2],
self.p2.x, self.p2.y, self.p2.z) self.p2[0], self.p2[1], self.p2[2])
def __cmp__(self, other): def __cmp__(self, other):
""" Two lines are equal if both pairs of points are at the same """ Two lines are equal if both pairs of points are at the same
...@@ -119,10 +122,12 @@ class Line(IDGenerator, TransformableContainer): ...@@ -119,10 +122,12 @@ class Line(IDGenerator, TransformableContainer):
return cmp(self.p2, other.p2) return cmp(self.p2, other.p2)
else: else:
return cmp(str(self), str(other)) return cmp(str(self), str(other))
def next(self): def next(self):
yield self.p1 yield "p1"
yield self.p2 #yield self.p1
#yield lambda x: self.p2 = x
yield "p2"
def get_children_count(self): def get_children_count(self):
# a line always contains two points # a line always contains two points
...@@ -141,23 +146,28 @@ class Line(IDGenerator, TransformableContainer): ...@@ -141,23 +146,28 @@ class Line(IDGenerator, TransformableContainer):
return (self.p1, self.p2) return (self.p1, self.p2)
def point_with_length_multiply(self, l): def point_with_length_multiply(self, l):
return self.p1.add(self.dir.mul(l*self.len)) return padd(self.p1, pmul(self.dir, l*self.len))
#return self.p1.add(self.dir.mul(l*self.len))
def get_length_line(self, length): def get_length_line(self, length):
""" return a line with the same direction and the specified length """ return a line with the same direction and the specified length
""" """
return Line(self.p1, self.p1.add(self.dir.mul(length))) return Line(self.p1, padd(self.p1, pmul(self.dir, length)))
#return Line(self.p1, self.p1.add(self.dir.mul(length)))
def closest_point(self, p): def closest_point(self, p):
v = self.dir v = self.dir
if v is None: if v is None:
# for zero-length lines # for zero-length lines
return self.p1 return self.p1
l = self.p1.dot(v) - p.dot(v) l = pdot(self.p1, v) - pdot(p, v)
return self.p1.sub(v.mul(l)) #l = self.p1.dot(v) - p.dot(v)
return psub(self.p1, pmul(v, l))
#return self.p1.sub(v.mul(l))
def dist_to_point_sq(self, p): def dist_to_point_sq(self, p):
return p.sub(self.closest_point(p)).normsq return pnormsq(psub(p, self.closes_point(p)))
#return p.sub(self.closest_point(p)).normsq
def dist_to_point(self, p): def dist_to_point(self, p):
return sqrt(self.dist_to_point_sq(p)) return sqrt(self.dist_to_point_sq(p))
...@@ -166,8 +176,11 @@ class Line(IDGenerator, TransformableContainer): ...@@ -166,8 +176,11 @@ class Line(IDGenerator, TransformableContainer):
if (p == self.p1) or (p == self.p2): if (p == self.p1) or (p == self.p2):
# these conditions are not covered by the code below # these conditions are not covered by the code below
return True return True
dir1 = p.sub(self.p1).normalized()
dir2 = self.p2.sub(p).normalized() dir1 = pnormalized(psub(p, self.p1))
#dir1 = p.sub(self.p1).normalized()
dir2 = pnormalized(psub(self.p2, p))
#dir2 = self.p2.sub(p).normalized()
# True if the two parts of the line have the same direction or if the # True if the two parts of the line have the same direction or if the
# point is self.p1 or self.p2. # point is self.p1 or self.p2.
return (dir1 == dir2 == self.dir) or (dir1 is None) or (dir2 is None) return (dir1 == dir2 == self.dir) or (dir1 is None) or (dir2 is None)
...@@ -178,8 +191,8 @@ class Line(IDGenerator, TransformableContainer): ...@@ -178,8 +191,8 @@ class Line(IDGenerator, TransformableContainer):
if not color is None: if not color is None:
GL.glColor4f(*color) GL.glColor4f(*color)
GL.glBegin(GL.GL_LINES) GL.glBegin(GL.GL_LINES)
GL.glVertex3f(self.p1.x, self.p1.y, self.p1.z) GL.glVertex3f(self.p1[0], self.p1[1], self.p1[2])
GL.glVertex3f(self.p2.x, self.p2.y, self.p2.z) GL.glVertex3f(self.p2[0], self.p2[1], self.p2[2])
GL.glEnd() GL.glEnd()
# (optional) draw a cone for visualizing the direction of each line # (optional) draw a cone for visualizing the direction of each line
if show_directions and (self.len > 0): if show_directions and (self.len > 0):
...@@ -196,24 +209,30 @@ class Line(IDGenerator, TransformableContainer): ...@@ -196,24 +209,30 @@ class Line(IDGenerator, TransformableContainer):
0 and 1. 0 and 1.
""" """
x1, x2, x3, x4 = self.p1, self.p2, line.p1, line.p2 x1, x2, x3, x4 = self.p1, self.p2, line.p1, line.p2
a = x2.sub(x1) a = psub(x2, x1)
b = x4.sub(x3) #a = x2.sub(x1)
c = x3.sub(x1) b = psub(x4, x3)
#b = x4.sub(x3)
c = psub(x3, x1)
#c = x3.sub(x1)
# see http://mathworld.wolfram.com/Line-LineIntersection.html (24) # see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
try: try:
factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq factor = pdot(pcross(c, b), pcross(a, b)) / pnormsq(pcross(a, b))
#factor = c.cross(b).dot(a.cross(b)) / a.cross(b).normsq
except ZeroDivisionError: except ZeroDivisionError:
# lines are parallel # lines are parallel
# check if they are _one_ line # check if they are _one_ line
if a.cross(c).norm != 0: #if a.cross(c).norm != 0:
if pnorm(pcross(a,c)) != 0:
# the lines are parallel with a distance # the lines are parallel with a distance
return None, None return None, None
# the lines are on one straight # the lines are on one straight
candidates = [] candidates = []
if self.is_point_inside(x3): if self.is_point_inside(x3):
candidates.append((x3, c.norm / a.norm)) candidates.append((x3, pnorm(c) / pnorm(a)))
elif self.is_point_inside(x4): elif self.is_point_inside(x4):
candidates.append((x4, line.p2.sub(self.p1).norm / a.norm)) candidates.append((x4, pnorm(psub(line.p2, self.p1)) / pnorm(a)))
#candidates.append((x4, line.p2.sub(self.p1).norm / a.norm))
elif line.is_point_inside(x1): elif line.is_point_inside(x1):
candidates.append((x1, 0)) candidates.append((x1, 0))
elif line.is_point_inside(x2): elif line.is_point_inside(x2):
...@@ -224,13 +243,14 @@ class Line(IDGenerator, TransformableContainer): ...@@ -224,13 +243,14 @@ class Line(IDGenerator, TransformableContainer):
candidates.sort(key=lambda (cp, dist): dist) candidates.sort(key=lambda (cp, dist): dist)
return candidates[0] return candidates[0]
if infinite_lines or (-epsilon <= factor <= 1 + epsilon): if infinite_lines or (-epsilon <= factor <= 1 + epsilon):
intersection = x1.add(a.mul(factor)) intersection = padd(x1, pmul(a, factor))
#intersection = x1.add(a.mul(factor))
# check if the intersection is between x3 and x4 # check if the intersection is between x3 and x4
if infinite_lines: if infinite_lines:
return intersection, factor return intersection, factor
elif (min(x3.x, x4.x) - epsilon <= intersection.x <= max(x3.x, x4.x) + epsilon) \ elif (min(x3[0], x4[0]) - epsilon <= intersection[0] <= max(x3[0], x4[0]) + epsilon) \
and (min(x3.y, x4.y) - epsilon <= intersection.y <= max(x3.y, x4.y) + epsilon) \ and (min(x3[1], x4[1]) - epsilon <= intersection[1] <= max(x3[1], x4[1]) + epsilon) \
and (min(x3.z, x4.z) - epsilon <= intersection.z <= max(x3.z, x4.z) + epsilon): and (min(x3[2], x4[2]) - epsilon <= intersection[2] <= max(x3[2], x4[2]) + epsilon):
return intersection, factor return intersection, factor
else: else:
# intersection outside of the length of line(x3, x4) # intersection outside of the length of line(x3, x4)
...@@ -247,15 +267,15 @@ class Line(IDGenerator, TransformableContainer): ...@@ -247,15 +267,15 @@ class Line(IDGenerator, TransformableContainer):
else: else:
# the line needs to be cropped # the line needs to be cropped
# generate the six planes of the cube for possible intersections # generate the six planes of the cube for possible intersections
minp = Point(minx, miny, minz) minp = (minx, miny, minz)
maxp = Point(maxx, maxy, maxz) maxp = (maxx, maxy, maxz)
planes = [ planes = [
Plane(minp, Point(1, 0, 0)), Plane(minp, (1, 0, 0)),
Plane(minp, Point(0, 1, 0)), Plane(minp, (0, 1, 0)),
Plane(minp, Point(0, 0, 1)), Plane(minp, (0, 0, 1)),
Plane(maxp, Point(1, 0, 0)), Plane(maxp, (1, 0, 0)),
Plane(maxp, Point(0, 1, 0)), Plane(maxp, (0, 1, 0)),
Plane(maxp, Point(0, 0, 1)), Plane(maxp, (0, 0, 1)),
] ]
# calculate all intersections # calculate all intersections
intersections = [plane.intersect_point(self.dir, self.p1) intersections = [plane.intersect_point(self.dir, self.p1)
......
...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
# various matrix related functions for PyCAM # various matrix related functions for PyCAM
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import sqrt, number, epsilon from pycam.Geometry.utils import sqrt, number, epsilon
import math import math
...@@ -54,23 +54,6 @@ def get_dot_product(a, b): ...@@ -54,23 +54,6 @@ def get_dot_product(a, b):
""" """
return sum(l1 * l2 for l1, l2 in zip(a, b)) return sum(l1 * l2 for l1, l2 in zip(a, b))
def get_cross_product(a, b):
""" calculate the cross product of two 3d vectors
@type a: tuple(float) | list(float) | pycam.Geometry.Point
@value a: the first vector to be multiplied
@type b: tuple(float) | list(float) | pycam.Geometry.Point
@value b: the second vector to be multiplied
@rtype: tuple(float)
@return: the cross product is a 3d vector
"""
if isinstance(a, Point):
a = (a.x, a.y, a.z)
if isinstance(b, Point):
b = (b.x, b.y, b.z)
return (a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0])
def get_length(vector): def get_length(vector):
""" calculate the lengt of a 3d vector """ calculate the lengt of a 3d vector
...@@ -101,13 +84,10 @@ def get_rotation_matrix_from_to(v_orig, v_dest): ...@@ -101,13 +84,10 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
@rtype: tuple(tuple(float)) @rtype: tuple(tuple(float))
@return: the roation matrix (3x3) @return: the roation matrix (3x3)
""" """
if isinstance(v_orig, Point):
v_orig = (v_orig.x, v_orig.y, v_orig.z)
if isinstance(v_dest, Point):
v_dest = (v_dest.x, v_dest.y, v_dest.z)
v_orig_length = get_length(v_orig) v_orig_length = get_length(v_orig)
v_dest_length = get_length(v_dest) v_dest_length = get_length(v_dest)
cross_product = get_length(get_cross_product(v_orig, v_dest)) cross_product = get_length(pcross(v_orig, v_dest))
try: try:
arcsin = cross_product / (v_orig_length * v_dest_length) arcsin = cross_product / (v_orig_length * v_dest_length)
except ZeroDivisionError: except ZeroDivisionError:
...@@ -121,9 +101,9 @@ def get_rotation_matrix_from_to(v_orig, v_dest): ...@@ -121,9 +101,9 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
# calculate the rotation axis # calculate the rotation axis
# The rotation axis is equal to the cross product of the original and # The rotation axis is equal to the cross product of the original and
# destination vectors. # destination vectors.
rot_axis = Point(v_orig[1] * v_dest[2] - v_orig[2] * v_dest[1], rot_axis = pnormalized((v_orig[1] * v_dest[2] - v_orig[2] * v_dest[1],
v_orig[2] * v_dest[0] - v_orig[0] * v_dest[2], v_orig[2] * v_dest[0] - v_orig[0] * v_dest[2],
v_orig[0] * v_dest[1] - v_orig[1] * v_dest[0]).normalized() v_orig[0] * v_dest[1] - v_orig[1] * v_dest[0]))
if not rot_axis: if not rot_axis:
return None return None
# get the rotation matrix # get the rotation matrix
...@@ -131,15 +111,15 @@ def get_rotation_matrix_from_to(v_orig, v_dest): ...@@ -131,15 +111,15 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
c = math.cos(rot_angle) c = math.cos(rot_angle)
s = math.sin(rot_angle) s = math.sin(rot_angle)
t = 1 - c t = 1 - c
return ((t * rot_axis.x * rot_axis.x + c, return ((t * rot_axis[0] * rot_axis[0] + c,
t * rot_axis.x * rot_axis.y - s * rot_axis.z, t * rot_axis[0] * rot_axis[1] - s * rot_axis[2],
t * rot_axis.x * rot_axis.z + s * rot_axis.y), t * rot_axis[0] * rot_axis[2] + s * rot_axis[1]),
(t * rot_axis.x * rot_axis.y + s * rot_axis.z, (t * rot_axis[0] * rot_axis[1] + s * rot_axis[2],
t * rot_axis.y * rot_axis.y + c, t * rot_axis[1] * rot_axis[1] + c,
t * rot_axis.y * rot_axis.z - s * rot_axis.x), t * rot_axis[1] * rot_axis[2] - s * rot_axis[0]),
(t * rot_axis.x * rot_axis.z - s * rot_axis.y, (t * rot_axis[0] * rot_axis[2] - s * rot_axis[1],
t * rot_axis.y * rot_axis.z + s * rot_axis.x, t * rot_axis[1] * rot_axis[2] + s * rot_axis[0],
t * rot_axis.z * rot_axis.z + c)) t * rot_axis[2] * rot_axis[2] + c))
def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True): def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True):
""" calculate rotation matrix for a normalized vector and an angle """ calculate rotation matrix for a normalized vector and an angle
......
...@@ -31,7 +31,7 @@ from pycam.Geometry.Triangle import Triangle ...@@ -31,7 +31,7 @@ from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.Polygon import Polygon from pycam.Geometry.Polygon import Polygon
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.TriangleKdtree import TriangleKdtree from pycam.Geometry.TriangleKdtree import TriangleKdtree
from pycam.Geometry.Matrix import TRANSFORMATIONS from pycam.Geometry.Matrix import TRANSFORMATIONS
from pycam.Toolpath import Bounds from pycam.Toolpath import Bounds
...@@ -109,7 +109,7 @@ class BaseModel(IDGenerator, TransformableContainer): ...@@ -109,7 +109,7 @@ class BaseModel(IDGenerator, TransformableContainer):
return sum([len(igroup) for igroup in self._item_groups]) return sum([len(igroup) for igroup in self._item_groups])
def next(self): def next(self):
for item_group in self._item_groups: for item_group in self._item_groups:
for item in item_group: for item in item_group:
if isinstance(item, list): if isinstance(item, list):
for subitem in item: for subitem in item:
...@@ -324,7 +324,7 @@ class Model(BaseModel): ...@@ -324,7 +324,7 @@ class Model(BaseModel):
# Find all groups with the same direction (see 'normal') that # Find all groups with the same direction (see 'normal') that
# share at least one edge with the current triangle. # share at least one edge with the current triangle.
touch_groups = [] touch_groups = []
if t.normal.z == 0: if t.normal[2] == 0:
# ignore vertical triangles # ignore vertical triangles
continue continue
for group_index, group in enumerate(groups): for group_index, group in enumerate(groups):
...@@ -359,7 +359,7 @@ class ContourModel(BaseModel): ...@@ -359,7 +359,7 @@ class ContourModel(BaseModel):
self.name = "contourmodel%d" % self.id self.name = "contourmodel%d" % self.id
if plane is None: if plane is None:
# the default plane points upwards along the z axis # the default plane points upwards along the z axis
plane = Plane(Point(0, 0, 0), Vector(0, 0, 1)) plane = Plane((0, 0, 0), (0, 0, 1, 'v'))
self._plane = plane self._plane = plane
self._line_groups = [] self._line_groups = []
self._item_groups.append(self._line_groups) self._item_groups.append(self._line_groups)
...@@ -804,7 +804,7 @@ class PolygonGroup(object): ...@@ -804,7 +804,7 @@ class PolygonGroup(object):
self.inner = inner_list self.inner = inner_list
self.callback = callback self.callback = callback
self.lines = outer.get_lines() self.lines = outer.get_lines()
self.z_level = self.lines[0].p1.z self.z_level = self.lines[0].p1[2]
for poly in inner_list: for poly in inner_list:
self.lines.extend(poly.get_lines()) self.lines.extend(poly.get_lines())
...@@ -836,7 +836,7 @@ class PolygonGroup(object): ...@@ -836,7 +836,7 @@ class PolygonGroup(object):
# create the backside plane # create the backside plane
backside_points = [] backside_points = []
for p in item.get_points(): for p in item.get_points():
backside_points.insert(0, Point(p.x, p.y, self.z_level)) backside_points.insert(0, (p[0], p[1], self.z_level))
triangle_optimizer.append(Triangle(*backside_points)) triangle_optimizer.append(Triangle(*backside_points))
if self.callback and self.callback(): if self.callback and self.callback():
return None return None
...@@ -873,7 +873,7 @@ class PolygonGroup(object): ...@@ -873,7 +873,7 @@ class PolygonGroup(object):
# the contour points of the model will always be at level zero # the contour points of the model will always be at level zero
a[2] = self.z_level a[2] = self.z_level
b[2] = self.z_level b[2] = self.z_level
return Line(Point(*a), Point(*b)) return Line(a, b)
valid_indices = [index for index, p in enumerate(coords) valid_indices = [index for index, p in enumerate(coords)
if not p[2] is None] if not p[2] is None]
none_indices = [index for index, p in enumerate(coords) if p[2] is None] none_indices = [index for index, p in enumerate(coords) if p[2] is None]
...@@ -891,7 +891,7 @@ class PolygonGroup(object): ...@@ -891,7 +891,7 @@ class PolygonGroup(object):
fan_points.append(cp) fan_points.append(cp)
final_points.append(cp) final_points.append(cp)
else: else:
final_points.append(Point(*coords[index])) final_points.append(coords[index])
# check if the three fan_points are in line # check if the three fan_points are in line
if len(fan_points) == 3: if len(fan_points) == 3:
fan_points.sort() fan_points.sort()
...@@ -905,7 +905,7 @@ class PolygonGroup(object): ...@@ -905,7 +905,7 @@ class PolygonGroup(object):
# is hardly possible, anyway. # is hardly possible, anyway.
for index in range(4): for index in range(4):
if index in valid_indices: if index in valid_indices:
final_points.append(Point(*coords[index])) final_points.append(coords[index])
else: else:
probe_line = get_line(index - 1, index) probe_line = get_line(index - 1, index)
cp = self._get_closest_line_collision(probe_line) cp = self._get_closest_line_collision(probe_line)
...@@ -913,7 +913,7 @@ class PolygonGroup(object): ...@@ -913,7 +913,7 @@ class PolygonGroup(object):
else: else:
for index in range(4): for index in range(4):
if index in valid_indices: if index in valid_indices:
final_points.append(Point(*coords[index])) final_points.append(coords[index])
else: else:
if ((index + 1) % 4) in valid_indices: if ((index + 1) % 4) in valid_indices:
other_index = index + 1 other_index = index + 1
...@@ -925,7 +925,7 @@ class PolygonGroup(object): ...@@ -925,7 +925,7 @@ class PolygonGroup(object):
elif valid_count == 3: elif valid_count == 3:
for index in range(4): for index in range(4):
if index in valid_indices: if index in valid_indices:
final_points.append(Point(*coords[index])) final_points.append(coords[index])
else: else:
# add two points # add two points
for other_index in (index - 1, index + 1): for other_index in (index - 1, index + 1):
...@@ -933,7 +933,7 @@ class PolygonGroup(object): ...@@ -933,7 +933,7 @@ class PolygonGroup(object):
cp = self._get_closest_line_collision(probe_line) cp = self._get_closest_line_collision(probe_line)
final_points.append(cp) final_points.append(cp)
else: else:
final_points.extend([Point(*coord) for coord in coords]) final_points.extend(coords)
valid_points = [] valid_points = []
for p in final_points: for p in final_points:
if not (p is None) and not (p in valid_points): if not (p is None) and not (p in valid_points):
...@@ -970,17 +970,18 @@ class PolygonGroup(object): ...@@ -970,17 +970,18 @@ class PolygonGroup(object):
return grid return grid
def calculate_point_height(self, x, y, func): def calculate_point_height(self, x, y, func):
point = Point(x, y, self.outer.minz) point = (x, y, self.outer.minz)
if not self.outer.is_point_inside(point): if not self.outer.is_point_inside(point):
return None return None
for poly in self.inner: for poly in self.inner:
if poly.is_point_inside(point): if poly.is_point_inside(point):
return None return None
point = Point(x, y, self.outer.minz) point = (x, y, self.outer.minz)
line_distances = [] line_distances = []
for line in self.lines: for line in self.lines:
cross_product = line.dir.cross(point.sub(line.p1)) cross_product = pcross(line.dir, psub(point, line.p1))
if cross_product.z > 0: #cross_product = line.dir.cross(point.sub(line.p1))
if cross_product[2] > 0:
close_points = [] close_points = []
close_point = line.closest_point(point) close_point = line.closest_point(point)
if not line.is_point_inside(close_point): if not line.is_point_inside(close_point):
...@@ -989,8 +990,10 @@ class PolygonGroup(object): ...@@ -989,8 +990,10 @@ class PolygonGroup(object):
else: else:
close_points.append(close_point) close_points.append(close_point)
for p in close_points: for p in close_points:
direction = point.sub(p) direction = psub(point, p)
dist = direction.norm #direction = point.sub(p)
dist = pnorm(direction)
#dist = direction.norm
line_distances.append(dist) line_distances.append(dist)
elif cross_product.z == 0: elif cross_product.z == 0:
# the point is on the line # the point is on the line
...@@ -1012,10 +1015,10 @@ class TriangleOptimizer(object): ...@@ -1012,10 +1015,10 @@ class TriangleOptimizer(object):
def append(self, triangle): def append(self, triangle):
# use a simple tuple instead of an object as the dict's key # use a simple tuple instead of an object as the dict's key
normal_coords = triangle.normal.x, triangle.normal.y, triangle.normal.z normal = triangle.normal
if not normal_coords in self.groups: if not normal in self.groups:
self.groups[normal_coords] = [] self.groups[normal] = []
self.groups[normal_coords].append(triangle) self.groups[normal].append(triangle)
def optimize(self): def optimize(self):
for group in self.groups.values(): for group in self.groups.values():
...@@ -1068,7 +1071,8 @@ class Rectangle(IDGenerator, TransformableContainer): ...@@ -1068,7 +1071,8 @@ class Rectangle(IDGenerator, TransformableContainer):
orders = ((p1, p2, p3, p4), (p1, p2, p4, p3), (p1, p3, p2, p4), orders = ((p1, p2, p3, p4), (p1, p2, p4, p3), (p1, p3, p2, p4),
(p1, p3, p4, p2), (p1, p4, p2, p3), (p1, p4, p3, p2)) (p1, p3, p4, p2), (p1, p4, p2, p3), (p1, p4, p3, p2))
for order in orders: for order in orders:
if abs(order[0].sub(order[2]).norm - order[1].sub(order[3]).norm) < epsilon: #if abs(order[0].sub(order[2]).norm - order[1].sub(order[3]).norm) < epsilon:
if abs(pnorm(psub(order[0], order[2])) - pnorm(psub(order[1], order[3]))) < epsilon:
t1 = Triangle(order[0], order[1], order[2]) t1 = Triangle(order[0], order[1], order[2])
t2 = Triangle(order[2], order[3], order[0]) t2 = Triangle(order[2], order[3], order[0])
if t1.normal == t2.normal == normal: if t1.normal == t2.normal == normal:
...@@ -1085,22 +1089,26 @@ class Rectangle(IDGenerator, TransformableContainer): ...@@ -1085,22 +1089,26 @@ class Rectangle(IDGenerator, TransformableContainer):
self.reset_cache() self.reset_cache()
def reset_cache(self): def reset_cache(self):
self.maxx = max([p.x for p in self.get_points()]) self.maxx = max([p[0] for p in self.get_points()])
self.minx = max([p.x for p in self.get_points()]) self.minx = max([p[0] for p in self.get_points()])
self.maxy = max([p.y for p in self.get_points()]) self.maxy = max([p[1] for p in self.get_points()])
self.miny = max([p.y for p in self.get_points()]) self.miny = max([p[1] for p in self.get_points()])
self.maxz = max([p.z for p in self.get_points()]) self.maxz = max([p[2] for p in self.get_points()])
self.minz = max([p.z for p in self.get_points()]) self.minz = max([p[2] for p in self.get_points()])
self.normal = Triangle(self.p1, self.p2, self.p3).normal.normalized() self.normal = pnormalized(Triangle(self.p1, self.p2, self.p3).normal)
def get_points(self): def get_points(self):
return (self.p1, self.p2, self.p3, self.p4) return (self.p1, self.p2, self.p3, self.p4)
def next(self): def next(self):
yield self.p1 #yield self.p1
yield self.p2 #yield self.p2
yield self.p3 #yield self.p3
yield self.p4 #yield self.p4
yield "p1"
yield "p2"
yield "p3"
yield "p4"
def __repr__(self): def __repr__(self):
return "Rectangle%d<%s,%s,%s,%s>" % (self.id, self.p1, self.p2, return "Rectangle%d<%s,%s,%s,%s>" % (self.id, self.p1, self.p2,
...@@ -1132,8 +1140,8 @@ class Rectangle(IDGenerator, TransformableContainer): ...@@ -1132,8 +1140,8 @@ class Rectangle(IDGenerator, TransformableContainer):
if len(unique_vertices) != 2: if len(unique_vertices) != 2:
log.error("Invalid number of vertices: %s" % unique_vertices) log.error("Invalid number of vertices: %s" % unique_vertices)
return None return None
if abs(unique_vertices[0].sub(unique_vertices[1]).norm - \ if abs(pnorm(psub(unique_verticies[0], unique_verticies[1])) - pnorm(psub(shared_vertices[0], shared_vertices[1]))) < epsilon:
shared_vertices[0].sub(shared_vertices[1]).norm) < epsilon: #if abs(unique_vertices[0].sub(unique_vertices[1]).norm - shared_vertices[0].sub(shared_vertices[1]).norm) < epsilon:
try: try:
return Rectangle(unique_vertices[0], unique_vertices[1], return Rectangle(unique_vertices[0], unique_vertices[1],
shared_vertices[0], shared_vertices[1], shared_vertices[0], shared_vertices[1],
......
...@@ -22,7 +22,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import TransformableContainer, IDGenerator from pycam.Geometry import TransformableContainer, IDGenerator
from pycam.Geometry.utils import INFINITE, epsilon from pycam.Geometry.utils import INFINITE, epsilon
from pycam.Geometry.Point import Vector from pycam.Geometry.PointUtils import *
# "Line" is imported later to avoid circular imports # "Line" is imported later to avoid circular imports
#from pycam.Geometry.Line import Line #from pycam.Geometry.Line import Line
...@@ -34,11 +35,11 @@ class Plane(IDGenerator, TransformableContainer): ...@@ -34,11 +35,11 @@ class Plane(IDGenerator, TransformableContainer):
def __init__(self, point, normal=None): def __init__(self, point, normal=None):
super(Plane, self).__init__() super(Plane, self).__init__()
if normal is None: if normal is None:
normal = Vector(0, 0, 1) normal = (0, 0, 1, 'v')
self.p = point self.p = point
self.n = normal self.n = normal
if not isinstance(self.n, Vector): if not len(self.n) > 3:
self.n = self.n.get_vector() self.n = (self.n[0], self.n[1], self.n[2], 'v')
def __repr__(self): def __repr__(self):
return "Plane<%s,%s>" % (self.p, self.n) return "Plane<%s,%s>" % (self.p, self.n)
...@@ -56,8 +57,8 @@ class Plane(IDGenerator, TransformableContainer): ...@@ -56,8 +57,8 @@ class Plane(IDGenerator, TransformableContainer):
return self.__class__(self.p.copy(), self.n.copy()) return self.__class__(self.p.copy(), self.n.copy())
def next(self): def next(self):
yield self.p yield "p"
yield self.n yield "n"
def get_children_count(self): def get_children_count(self):
# a plane always consists of two points # a plane always consists of two points
...@@ -65,21 +66,24 @@ class Plane(IDGenerator, TransformableContainer): ...@@ -65,21 +66,24 @@ class Plane(IDGenerator, TransformableContainer):
def reset_cache(self): def reset_cache(self):
# we need to prevent the "normal" from growing # we need to prevent the "normal" from growing
norm = self.n.normalized() norm = pnormalized(self.n)
if norm: if norm:
self.n = norm self.n = norm
def intersect_point(self, direction, point): def intersect_point(self, direction, point):
if (not direction is None) and (direction.norm != 1): if (not direction is None) and (pnorm(direction) != 1):
# calculations will go wrong, if the direction is not a unit vector # calculations will go wrong, if the direction is not a unit vector
direction = direction.normalized() direction = pnormalized(direction)
if direction is None: if direction is None:
return (None, INFINITE) return (None, INFINITE)
denom = self.n.dot(direction) denom = pdot(self.n, direction)
#denom = self.n.dot(direction)
if denom == 0: if denom == 0:
return (None, INFINITE) return (None, INFINITE)
l = -(self.n.dot(point) - self.n.dot(self.p)) / denom l = -(pdot(self.n, point) - pdot(self.n, self.p)) / denom
cp = point.add(direction.mul(l)) #l = -(self.n.dot(point) - self.n.dot(self.p)) / denom
cp = padd(point, pmul(direction, l))
#cp = point.add(direction.mul(l))
return (cp, l) return (cp, l)
def intersect_triangle(self, triangle, counter_clockwise=False): def intersect_triangle(self, triangle, counter_clockwise=False):
...@@ -101,7 +105,7 @@ class Plane(IDGenerator, TransformableContainer): ...@@ -101,7 +105,7 @@ class Plane(IDGenerator, TransformableContainer):
# a distance that is lower than the length of the edge. # a distance that is lower than the length of the edge.
if (not cp is None) and (-epsilon < l < edge.len - epsilon): if (not cp is None) and (-epsilon < l < edge.len - epsilon):
collisions.append(cp) collisions.append(cp)
elif (cp is None) and (self.n.dot(edge.dir) == 0): elif (cp is None) and (pdot(self.n, edge.dir) == 0):
cp, dist = self.intersect_point(self.n, point) cp, dist = self.intersect_point(self.n, point)
if abs(dist) < epsilon: if abs(dist) < epsilon:
# the edge is on the plane # the edge is on the plane
...@@ -116,8 +120,10 @@ class Plane(IDGenerator, TransformableContainer): ...@@ -116,8 +120,10 @@ class Plane(IDGenerator, TransformableContainer):
# no further calculation, if the line is zero-sized # no further calculation, if the line is zero-sized
if collision_line.len == 0: if collision_line.len == 0:
return collision_line return collision_line
cross = self.n.cross(collision_line.dir) cross = pcross(self.n, collision_line.dir)
if (cross.dot(triangle.normal) < 0) == bool(not counter_clockwise): #cross = self.n.cross(collision_line.dir)
#if (cross.dot(triangle.normal) < 0) == bool(not counter_clockwise):
if (pdot(cross, triangle.normal) < 0) == bool(not counter_clockwise):
# anti-clockwise direction -> revert the direction of the line # anti-clockwise direction -> revert the direction of the line
collision_line = Line(collision_line.p2, collision_line.p1) collision_line = Line(collision_line.p2, collision_line.p1)
return collision_line return collision_line
......
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.utils import epsilon, sqrt, number
from pycam.Geometry import IDGenerator
def _is_near(x, y):
return abs(x - y) < epsilon
class Point(IDGenerator):
__slots__ = ["id", "x", "y", "z", "_norm", "_normsq"]
def __init__(self, x, y, z):
super(Point, self).__init__()
self.x = number(x)
self.y = number(y)
self.z = number(z)
self.reset_cache()
@property
def norm(self):
if self._norm is None:
self._norm = sqrt(self.normsq)
return self._norm
@property
def normsq(self):
if self._normsq is None:
self._normsq = self.dot(self)
return self._normsq
def copy(self):
return self.__class__(float(self.x), float(self.y), float(self.z))
def __repr__(self):
return "Point%d<%g,%g,%g>" % (self.id, self.x, self.y, self.z)
def __cmp__(self, other):
""" Two points are equal if all dimensions are identical.
Otherwise the result is based on the individual x/y/z comparisons.
"""
if self.__class__ == other.__class__:
if (self.id == other.id) or \
((_is_near(self.x, other.x)) and \
(_is_near(self.y, other.y)) and \
(_is_near(self.z, other.z))):
return 0
elif not _is_near(self.x, other.x):
return cmp(self.x, other.x)
elif not _is_near(self.y, other.y):
return cmp(self.y, other.y)
else:
return cmp(self.z, other.z)
else:
return cmp(str(self), str(other))
def transform_by_matrix(self, matrix, transformed_list=None, callback=None):
# accept 3x4 matrices as well as 3x3 matrices
offsets = []
for column in matrix:
if len(column) < 4:
offsets.append(0)
else:
offsets.append(column[3])
x = self.x * matrix[0][0] + self.y * matrix[0][1] \
+ self.z * matrix[0][2] + offsets[0]
y = self.x * matrix[1][0] + self.y * matrix[1][1] \
+ self.z * matrix[1][2] + offsets[1]
z = self.x * matrix[2][0] + self.y * matrix[2][1] \
+ self.z * matrix[2][2] + offsets[2]
self.x = x
self.y = y
self.z = z
if callback:
callback()
self.reset_cache()
def reset_cache(self):
self._norm = None
self._normsq = None
def mul(self, c):
c = number(c)
return Point(self.x * c, self.y * c, self.z * c)
def div(self, c):
c = number(c)
return Point(self.x / c, self.y / c, self.z / c)
def add(self, p):
return Point(self.x + p.x, self.y + p.y, self.z + p.z)
def sub(self, p):
return Point(self.x - p.x, self.y - p.y, self.z - p.z)
def dot(self, p):
return self.x * p.x + self.y * p.y + self.z * p.z
def cross(self, p):
return Point(self.y * p.z - p.y * self.z, p.x * self.z - self.x * p.z,
self.x * p.y - p.x * self.y)
def normalized(self):
n = self.norm
if n == 0:
return None
else:
return self.__class__(self.x / n, self.y / n, self.z / n)
def is_inside(self, minx=None, maxx=None, miny=None, maxy=None, minz=None,
maxz=None):
return ((minx is None) or (minx - epsilon <= self.x)) \
and ((maxx is None) or (self.x <= maxx + epsilon)) \
and ((miny is None) or (miny - epsilon <= self.y)) \
and ((maxy is None) or (self.y <= maxy + epsilon)) \
and ((minz is None) or (minz - epsilon <= self.z)) \
and ((maxz is None) or (self.z <= maxz + epsilon))
def get_vector(self):
return Vector(self.x, self.y, self.z)
class Vector(Point):
""" The Vector class is similar to the Point class. The only difference
is that vectors are not shifted during transformations. This feature
is necessary for normals (e.g. of Triangles or Planes).
"""
__slots__ = []
def transform_by_matrix(self, matrix, transformed_list=None, callback=None):
x = self.x * matrix[0][0] + self.y * matrix[0][1] \
+ self.z * matrix[0][2]
y = self.x * matrix[1][0] + self.y * matrix[1][1] \
+ self.z * matrix[1][2]
z = self.x * matrix[2][0] + self.y * matrix[2][1] \
+ self.z * matrix[2][2]
self.x = x
self.y = y
self.z = z
if callback:
callback()
self.reset_cache()
def __repr__(self):
return "Vector%d<%g,%g,%g>" % (self.id, self.x, self.y, self.z)
...@@ -21,7 +21,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,7 +21,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.utils import epsilon from pycam.Geometry.utils import epsilon
from pycam.Geometry.Point import Point
from pycam.Geometry.kdtree import Node, kdtree from pycam.Geometry.kdtree import Node, kdtree
...@@ -37,7 +36,7 @@ class PointKdtree(kdtree): ...@@ -37,7 +36,7 @@ class PointKdtree(kdtree):
self.tolerance = tolerance self.tolerance = tolerance
nodes = [] nodes = []
for p in points: for p in points:
n = Node(p, (p.x, p.y, p.z)) n = Node(p, p)
nodes.append(n) nodes.append(n)
kdtree.__init__(self, nodes, cutoff, cutoff_distance) kdtree.__init__(self, nodes, cutoff, cutoff_distance)
...@@ -59,7 +58,7 @@ class PointKdtree(kdtree): ...@@ -59,7 +58,7 @@ class PointKdtree(kdtree):
self._n = n self._n = n
return nn.obj return nn.obj
else: else:
n.obj = Point(x, y, z) n.obj = (x, y, z)
self._n = None self._n = None
self.insert(n) self.insert(n)
return n.obj return n.obj
......
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.utils import epsilon, sqrt, number
def _is_near(x, y):
return abs(x - y) < epsilon
def pnorm(a):
return sqrt(pdot(a,a))
def pnormsq(a):
return pdot(a,a)
def pcmp(a,b):
""" Two points are equal if all dimensions are identical.
Otherwise the result is based on the individual x/y/z comparisons.
"""
if (_is_near(a[0], b[0]) and _is_near(a[1], b[1]) and _is_near(a[2], b[2])):
return 0
elif not _is_near(a[0], b[0]):
return cmp(a[0], b[0])
elif not _is_near(a[1], b[1]):
return cmp(a[1], b[1])
else:
return cmp(a[2], b[2])
def ptransform_by_matrix(a, matrix, transformed_list=None):
if len(a) > 3:
return (a[0] * matrix[0][0] + a[1] * matrix[0][1] + a[2] * matrix[0][2],
a[0] * matrix[1][0] + a[1] * matrix[1][1] + a[2] * matrix[1][2],
a[0] * matrix[2][0] + a[1] * matrix[2][1] + a[2] * matrix[2][2]) + a[3:]
else:
# accept 3x4 matrices as well as 3x3 matrices
offsets = []
for column in matrix:
if len(column) < 4:
offsets.append(0)
else:
offsets.append(column[3])
return (a[0] * matrix[0][0] + a[1] * matrix[0][1] + a[2] * matrix[0][2] + offsets[0],
a[0] * matrix[1][0] + a[1] * matrix[1][1] + a[2] * matrix[1][2] + offsets[1],
a[0] * matrix[2][0] + a[1] * matrix[2][1] + a[2] * matrix[2][2] + offsets[2])
def pmul(a, c):
c = number(c)
return (a[0] * c, a[1] * c, a[2] * c)
def pdiv(a, c):
c = number(c)
return (a[0] / c, a[0] / c, a[0] / c)
def padd(a, b):
return (a[0] + b[0], a[1] + b[1], a[2] + b[2])
def psub(a, b):
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
def pdot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def pcross(a, b):
return (a[1] * b[2] - b[1] * a[2], b[0] * a[2] - a[0] * b[2], a[0] * b[1] - b[0] * a[1])
def pnormalized(a):
n = pnorm(a)
if n == 0:
return None
else:
return (a[0] / n, a[1] / n, a[2] / n) + a[3:]
def pis_inside(a, minx=None, maxx=None, miny=None, maxy=None, minz=None, maxz=None):
return ((minx is None) or (minx - epsilon <= a[0])) \
and ((maxx is None) or (a[0] <= maxx + epsilon)) \
and ((miny is None) or (miny - epsilon <= a[1])) \
and ((maxy is None) or (a[1] <= maxy + epsilon)) \
and ((minz is None) or (minz - epsilon <= a[2])) \
and ((maxz is None) or (a[2] <= maxz + epsilon))
This diff is collapsed.
This diff is collapsed.
...@@ -79,10 +79,10 @@ class TriangleKdtree(kdtree): ...@@ -79,10 +79,10 @@ class TriangleKdtree(kdtree):
def __init__(self, triangles, cutoff=3, cutoff_distance=1.0): def __init__(self, triangles, cutoff=3, cutoff_distance=1.0):
nodes = [] nodes = []
for t in triangles: for t in triangles:
n = Node(t, (min(t.p1.x, t.p2.x, t.p3.x), n = Node(t, (min(t.p1[0], t.p2[0], t.p3[0]),
max(t.p1.x, t.p2.x, t.p3.x), max(t.p1[0], t.p2[0], t.p3[0]),
min(t.p1.y, t.p2.y, t.p3.y), min(t.p1[1], t.p2[1], t.p3[1]),
max(t.p1.y, t.p2.y, t.p3.y))) max(t.p1[1], t.p2[1], t.p3[1])))
nodes.append(n) nodes.append(n)
super(TriangleKdtree, self).__init__(nodes, cutoff, cutoff_distance) super(TriangleKdtree, self).__init__(nodes, cutoff, cutoff_distance)
......
...@@ -21,11 +21,15 @@ You should have received a copy of the GNU General Public License ...@@ -21,11 +21,15 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
__all__ = ["utils", "Line", "Model", "Path", "Plane", "Point", "Triangle", __all__ = ["utils", "Line", "Model", "Path", "Plane", "Triangle",
"PolygonExtractor", "TriangleKdtree", "intersection", "kdtree", "PolygonExtractor", "TriangleKdtree", "intersection", "kdtree",
"Matrix", "Polygon", "Letters"] "Matrix", "Polygon", "Letters", "PointUtils"]
from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import epsilon, ceil from pycam.Geometry.utils import epsilon, ceil
from pycam.Utils import log
import types
log = log.get_logger()
import math import math
...@@ -33,17 +37,24 @@ def get_bisector(p1, p2, p3, up_vector): ...@@ -33,17 +37,24 @@ def get_bisector(p1, p2, p3, up_vector):
""" Calculate the bisector between p1, p2 and p3, whereas p2 is the origin """ Calculate the bisector between p1, p2 and p3, whereas p2 is the origin
of the angle. of the angle.
""" """
d1 = p2.sub(p1).normalized() d1 = pnormalized(psub(p2, p1))
d2 = p2.sub(p3).normalized() #d1 = p2.sub(p1).normalized()
bisector_dir = d1.add(d2).normalized() d2 = pnormalized(psub(p2, p3))
#d2 = p2.sub(p3).normalized()
bisector_dir = pnormalized(padd(d1, d2))
#bisector_dir = d1.add(d2).normalized()
if bisector_dir is None: if bisector_dir is None:
# the two vectors pointed to opposite directions # the two vectors pointed to opposite directions
bisector_dir = d1.cross(up_vector).normalized() bisector_dir = pnormalized(pcross(d1, up_vector))
#bisector_dir = d1.cross(up_vector).normalized()
else: else:
skel_up_vector = bisector_dir.cross(p2.sub(p1)) skel_up_vector = pcross(bisector_dir, psub(p2, p1))
if up_vector.dot(skel_up_vector) < 0: #skel_up_vector = bisector_dir.cross(p2.sub(p1))
#if up_vector.dot(skel_up_vector) < 0:
if pdot(up_vector, skel_up_vector) < 0:
# reverse the skeleton vector to point outwards # reverse the skeleton vector to point outwards
bisector_dir = bisector_dir.mul(-1) bisector_dir = pmul(bisector_dir, -1)
#bisector_dir = bisector_dir.mul(-1)
return bisector_dir return bisector_dir
def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False): def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False):
...@@ -57,20 +68,22 @@ def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False): ...@@ -57,20 +68,22 @@ def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False):
p2--------p1 p2--------p1
The result is in a range between 0 and 2*PI. The result is in a range between 0 and 2*PI.
""" """
d1 = p2.sub(p1).normalized() d1 = pnormalized(psub(p2, p1))
d2 = p2.sub(p3).normalized() #d1 = p2.sub(p1).normalized()
d2 = pnormalized(psub(p2, p3))
#d2 = p2.sub(p3).normalized()
if (d1 is None) or (d2 is None): if (d1 is None) or (d2 is None):
return 2 * math.pi return 2 * math.pi
angle = math.acos(d1.dot(d2)) angle = math.acos(pdot(d1, d2))
#angle = math.acos(d1.dot(d2))
# check the direction of the points (clockwise/anti) # check the direction of the points (clockwise/anti)
# The code is taken from Polygon.get_area # The code is taken from Polygon.get_area
value = [0, 0, 0] value = [0, 0, 0]
for (pa, pb) in ((p1, p2), (p2, p3), (p3, p1)): for (pa, pb) in ((p1, p2), (p2, p3), (p3, p1)):
value[0] += pa.y * pb.z - pa.z * pb.y value[0] += pa[1] * pb[2] - pa[2] * pb[1]
value[1] += pa.z * pb.x - pa.x * pb.z value[1] += pa[2] * pb[0] - pa[0] * pb[2]
value[2] += pa.x * pb.y - pa.y * pb.x value[2] += pa[0] * pb[1] - pa[1] * pb[0]
area = up_vector.x * value[0] + up_vector.y * value[1] \ area = up_vector[0] * value[0] + up_vector[1] * value[1] + up_vector[2] * value[2]
+ up_vector.z * value[2]
if area > 0: if area > 0:
# The points are in anti-clockwise order. Thus the angle is greater # The points are in anti-clockwise order. Thus the angle is greater
# than 180 degree. # than 180 degree.
...@@ -113,8 +126,8 @@ def get_points_of_arc(center, radius, a1, a2, plane=None, cords=32): ...@@ -113,8 +126,8 @@ def get_points_of_arc(center, radius, a1, a2, plane=None, cords=32):
angle_segment = angle_diff / num_of_segments angle_segment = angle_diff / num_of_segments
points = [] points = []
get_angle_point = lambda angle: ( get_angle_point = lambda angle: (
center.x + radius * math.cos(angle), center[0] + radius * math.cos(angle),
center.y + radius * math.sin(angle)) center[1] + radius * math.sin(angle))
points.append(get_angle_point(a1)) points.append(get_angle_point(a1))
for index in range(num_of_segments): for index in range(num_of_segments):
points.append(get_angle_point(a1 + angle_segment * (index + 1))) points.append(get_angle_point(a1 + angle_segment * (index + 1)))
...@@ -131,23 +144,24 @@ def get_bezier_lines(points_with_bulge, segments=32): ...@@ -131,23 +144,24 @@ def get_bezier_lines(points_with_bulge, segments=32):
if not bulge1 and not bulge2: if not bulge1 and not bulge2:
# straight line # straight line
return [Line.Line(p1, p2)] return [Line.Line(p1, p2)]
straight_dir = p2.sub(p1).normalized() straight_dir = pnormalized(psub(p2, p1))
#straight_dir = p2.sub(p1).normalized()
#bulge1 = max(-1.0, min(1.0, bulge1)) #bulge1 = max(-1.0, min(1.0, bulge1))
bulge1 = math.atan(bulge1) bulge1 = math.atan(bulge1)
rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1), rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1),
-2 * bulge1, use_radians=True) -2 * bulge1, use_radians=True)
dir1_mat = Matrix.multiply_vector_matrix((straight_dir.x, dir1_mat = Matrix.multiply_vector_matrix((straight_dir[0],
straight_dir.y, straight_dir.z), rot_matrix) straight_dir[1], straight_dir[2]), rot_matrix)
dir1 = Point.Vector(dir1_mat[0], dir1_mat[1], dir1_mat[2]) dir1 = (dir1_mat[0], dir1_mat[1], dir1_mat[2], 'v')
if bulge2 is None: if bulge2 is None:
bulge2 = bulge1 bulge2 = bulge1
else: else:
bulge2 = math.atan(bulge2) bulge2 = math.atan(bulge2)
rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1), rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1),
2 * bulge2, use_radians=True) 2 * bulge2, use_radians=True)
dir2_mat = Matrix.multiply_vector_matrix((straight_dir.x, dir2_mat = Matrix.multiply_vector_matrix((straight_dir[0],
straight_dir.y, straight_dir.z), rot_matrix) straight_dir[1], straight_dir[2]), rot_matrix)
dir2 = Point.Vector(dir2_mat[0], dir2_mat[1], dir2_mat[2]) dir2 = (dir2_mat[0], dir2_mat[1], dir2_mat[2], 'v')
# interpretation of bulge1 and bulge2: # interpretation of bulge1 and bulge2:
# /// taken from http://paulbourke.net/dataformats/dxf/dxf10.html /// # /// taken from http://paulbourke.net/dataformats/dxf/dxf10.html ///
# The bulge is the tangent of 1/4 the included angle for an arc # The bulge is the tangent of 1/4 the included angle for an arc
...@@ -155,7 +169,8 @@ def get_bezier_lines(points_with_bulge, segments=32): ...@@ -155,7 +169,8 @@ def get_bezier_lines(points_with_bulge, segments=32):
# point to the end point; a bulge of 0 indicates a straight segment, # point to the end point; a bulge of 0 indicates a straight segment,
# and a bulge of 1 is a semicircle. # and a bulge of 1 is a semicircle.
alpha = 2 * (abs(bulge1) + abs(bulge2)) alpha = 2 * (abs(bulge1) + abs(bulge2))
dist = p2.sub(p1).norm dist = pnorm(psub(p2, p1))
#dist = p2.sub(p1).norm
# calculate the radius of the circumcircle - avoiding divide-by-zero # calculate the radius of the circumcircle - avoiding divide-by-zero
if (abs(alpha) < epsilon) or (abs(math.pi - alpha) < epsilon): if (abs(alpha) < epsilon) or (abs(math.pi - alpha) < epsilon):
radius = dist / 2.0 radius = dist / 2.0
...@@ -165,16 +180,22 @@ def get_bezier_lines(points_with_bulge, segments=32): ...@@ -165,16 +180,22 @@ def get_bezier_lines(points_with_bulge, segments=32):
# The calculation of "factor" is based on random guessing - but it # The calculation of "factor" is based on random guessing - but it
# seems to work well. # seems to work well.
factor = 4 * radius * math.tan(alpha / 4.0) factor = 4 * radius * math.tan(alpha / 4.0)
dir1 = dir1.mul(factor) dir1 = pmul(dir1, factor)
dir2 = dir2.mul(factor) #dir1 = dir1.mul(factor)
dir2 = pmul(dir2, factor)
#dir2 = dir2.mul(factor)
for index in range(segments + 1): for index in range(segments + 1):
# t: 0..1 # t: 0..1
t = float(index) / segments t = float(index) / segments
# see: http://en.wikipedia.org/wiki/Cubic_Hermite_spline # see: http://en.wikipedia.org/wiki/Cubic_Hermite_spline
p = p1.mul(2 * t ** 3 - 3 * t ** 2 + 1).add( #p = p1.mul(2 * t ** 3 - 3 * t ** 2 + 1).add(
dir1.mul(t ** 3 - 2 * t ** 2 + t).add( # dir1.mul(t ** 3 - 2 * t ** 2 + t).add(
p2.mul(-2 * t ** 3 + 3 * t ** 2).add( # p2.mul(-2 * t ** 3 + 3 * t ** 2).add(
dir2.mul(t ** 3 - t ** 2)))) # dir2.mul(t ** 3 - t ** 2)
# )
# )
#)
p = padd( pmul(p1, 2 * t ** 3 - 3 * t ** 2 + 1) ,padd( pmul(dir1, t ** 3 - 2 * t ** 2 + t), padd(pmul(p2, -2 * t ** 3 + 3 * t ** 2) ,pmul(dir2, t ** 3 - t ** 2))))
result_points.append(p) result_points.append(p)
# create lines # create lines
result = [] result = []
...@@ -232,16 +253,24 @@ class TransformableContainer(object): ...@@ -232,16 +253,24 @@ class TransformableContainer(object):
# Use the 'id' builtin to prevent expensive object comparions. # Use the 'id' builtin to prevent expensive object comparions.
for item in self.next(): for item in self.next():
if isinstance(item, TransformableContainer): if isinstance(item, TransformableContainer):
item.transform_by_matrix(matrix, transformed_list, item.transform_by_matrix(matrix, transformed_list,callback=callback)
callback=callback)
elif not id(item) in transformed_list: elif not id(item) in transformed_list:
# non-TransformableContainer do not care to update the # non-TransformableContainer do not care to update the
# 'transformed_list'. Thus we need to do it. # 'transformed_list'. Thus we need to do it.
transformed_list.append(id(item)) #transformed_list.append(id(item))
# Don't transmit the 'transformed_list' if the object is # Don't transmit the 'transformed_list' if the object is
# not a TransformableContainer. It is not necessary and it # not a TransformableContainer. It is not necessary and it
# is hard to understand on the lowest level (e.g. Point). # is hard to understand on the lowest level (e.g. Point).
item.transform_by_matrix(matrix, callback=callback) if isinstance(item, str):
theval = getattr(self, item)
if isinstance(theval, tuple):
setattr(self, item, ptransform_by_matrix(theval, matrix))
elif isinstance(theval, list):
setattr(self, item, [ptransform_by_matrix(x, matrix) for x in theval])
elif isinstance(item, tuple):
log.error("ERROR!! A tuple (Point, Vector) made it into base transform_by_matrix without a back reference. Point/Vector remains unchanged.")
else:
item.transform_by_matrix(matrix, callback=callback)
# run the callback - e.g. for a progress counter # run the callback - e.g. for a progress counter
if callback and callback(): if callback and callback():
# user requesteded abort # user requesteded abort
......
This diff is collapsed.
...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License ...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import sqrt from pycam.Geometry.utils import sqrt
# careful import # careful import
...@@ -56,37 +56,37 @@ def keep_matrix(func): ...@@ -56,37 +56,37 @@ def keep_matrix(func):
@keep_matrix @keep_matrix
def draw_direction_cone(p1, p2, position=0.5, precision=12, size=0.1): def draw_direction_cone(p1, p2, position=0.5, precision=12, size=0.1):
# convert p1 and p2 from list/tuple to Point # convert p1 and p2 from list/tuple to Point
if not hasattr(p1, "sub"): distance = psub(p2, p1)
p1 = Point(*p1) #distance = p2.sub(p1)
if not hasattr(p2, "sub"): length = pnorm(distance)
p2 = Point(*p2) direction = pnormalized(distance)
distance = p2.sub(p1) #direction = distance.normalized()
length = distance.norm
direction = distance.normalized()
if direction is None: if direction is None:
# zero-length line # zero-length line
return return
cone_length = length * size cone_length = length * size
cone_radius = cone_length / 3.0 cone_radius = cone_length / 3.0
# move the cone to the middle of the line # move the cone to the middle of the line
GL.glTranslatef((p1.x + p2.x) * position, GL.glTranslatef((p1[0] + p2[0]) * position,
(p1.y + p2.y) * position, (p1.z + p2.z) * position) (p1[1] + p2[1]) * position, (p1[2] + p2[2]) * position)
# rotate the cone according to the line direction # rotate the cone according to the line direction
# The cross product is a good rotation axis. # The cross product is a good rotation axis.
cross = direction.cross(Point(0, 0, -1)) cross = pcross(direction, (0, 0, -1))
if cross.norm != 0: #cross = direction.cross(Point(0, 0, -1))
#if cross.norm != 0:
if pnorm(cross) != 0:
# The line direction is not in line with the z axis. # The line direction is not in line with the z axis.
try: try:
angle = math.asin(sqrt(direction.x ** 2 + direction.y ** 2)) angle = math.asin(sqrt(direction[0] ** 2 + direction[1] ** 2))
except ValueError: except ValueError:
# invalid angle - just ignore this cone # invalid angle - just ignore this cone
return return
# convert from radians to degree # convert from radians to degree
angle = angle / math.pi * 180 angle = angle / math.pi * 180
if direction.z < 0: if direction[2] < 0:
angle = 180 - angle angle = 180 - angle
GL.glRotatef(angle, cross.x, cross.y, cross.z) GL.glRotatef(angle, cross[0], cross[1], cross[2])
elif direction.z == -1: elif direction[2] == -1:
# The line goes down the z axis - turn it around. # The line goes down the z axis - turn it around.
GL.glRotatef(180, 1, 0, 0) GL.glRotatef(180, 1, 0, 0)
else: else:
......
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Letters import Charset from pycam.Geometry.Letters import Charset
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry import get_points_of_arc from pycam.Geometry import get_points_of_arc
import pycam.Utils.log import pycam.Utils.log
import pycam.Utils import pycam.Utils
...@@ -144,13 +144,13 @@ class CXFParser(object): ...@@ -144,13 +144,13 @@ class CXFParser(object):
type_char = line[0].upper() type_char = line[0].upper()
if (type_def == "L") and (len(coords) == 4): if (type_def == "L") and (len(coords) == 4):
# line # line
p1 = Point(coords[0], coords[1], 0) p1 = (coords[0], coords[1], 0)
p2 = Point(coords[2], coords[3], 0) p2 = (coords[2], coords[3], 0)
char_definition.append(Line(p1, p2)) char_definition.append(Line(p1, p2))
elif (type_def in ("A", "AR")) and (len(coords) == 5): elif (type_def in ("A", "AR")) and (len(coords) == 5):
# arc # arc
previous = None previous = None
center = Point(coords[0], coords[1], 0) center = (coords[0], coords[1], 0)
radius = coords[2] radius = coords[2]
start_angle, end_angle = coords[3], coords[4] start_angle, end_angle = coords[3], coords[4]
if type_def == "AR": if type_def == "AR":
...@@ -158,7 +158,7 @@ class CXFParser(object): ...@@ -158,7 +158,7 @@ class CXFParser(object):
start_angle, end_angle = end_angle, start_angle start_angle, end_angle = end_angle, start_angle
for p in get_points_of_arc(center, radius, start_angle, for p in get_points_of_arc(center, radius, start_angle,
end_angle): end_angle):
current = Point(p[0], p[1], 0) current = (p[0], p[1], 0)
if not previous is None: if not previous is None:
char_definition.append(Line(previous, current)) char_definition.append(Line(previous, current))
previous = current previous = current
......
...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
import pycam.Geometry.Model import pycam.Geometry.Model
import pycam.Geometry.Matrix import pycam.Geometry.Matrix
...@@ -142,8 +142,10 @@ class DXFParser(object): ...@@ -142,8 +142,10 @@ class DXFParser(object):
current_group = [] current_group = []
groups.append(current_group) groups.append(current_group)
def get_distance_between_groups(group1, group2): def get_distance_between_groups(group1, group2):
forward = group1[-1].p2.sub(group2[0].p1).norm forward = pnorm(psub(group1[-1].p2, group2[0].p1))
backward = group2[-1].p2.sub(group1[0].p1).norm #forward = group1[-1].p2.sub(group2[0].p1).norm
backward = pnorm(psub(group2[-1].p2, group1[0].p1))
#backward = group2[-1].p2.sub(group1[0].p1).norm
return min(forward, backward) return min(forward, backward)
remaining_groups = groups[:] remaining_groups = groups[:]
ordered_groups = [] ordered_groups = []
...@@ -305,7 +307,7 @@ class DXFParser(object): ...@@ -305,7 +307,7 @@ class DXFParser(object):
"between line %d and %d" % (start_line, end_line)) "between line %d and %d" % (start_line, end_line))
else: else:
self._open_sequence_items.append( self._open_sequence_items.append(
(Point(point[0], point[1], point[2]), bulge)) ((point[0], point[1], point[2]), bulge))
def parse_polyline(self, init): def parse_polyline(self, init):
start_line = self.line_number start_line = self.line_number
...@@ -358,7 +360,7 @@ class DXFParser(object): ...@@ -358,7 +360,7 @@ class DXFParser(object):
"date in line %d: %s" % \ "date in line %d: %s" % \
(self.line_number, p_array)) (self.line_number, p_array))
p_array[index] = 0 p_array[index] = 0
points.append((Point(p_array[0], p_array[1], p_array[2]), bulge)) points.append(((p_array[0], p_array[1], p_array[2]), bulge))
current_point = [None, None, None] current_point = [None, None, None]
bulge = None bulge = None
extra_vertex_flag = False extra_vertex_flag = False
...@@ -755,15 +757,15 @@ class DXFParser(object): ...@@ -755,15 +757,15 @@ class DXFParser(object):
+ "%d and %d" % (start_line, end_line)) + "%d and %d" % (start_line, end_line))
else: else:
# no color height adjustment for 3DFACE # no color height adjustment for 3DFACE
point1 = Point(p1[0], p1[1], p1[2]) point1 = (p1[0], p1[1], p1[2])
point2 = Point(p2[0], p2[1], p2[2]) point2 = (p2[0], p2[1], p2[2])
point3 = Point(p3[0], p3[1], p3[2]) point3 = (p3[0], p3[1], p3[2])
triangles = [] triangles = []
triangles.append((point1, point2, point3)) triangles.append((point1, point2, point3))
# DXF specifies, that p3=p4 if triangles (instead of quads) are # DXF specifies, that p3=p4 if triangles (instead of quads) are
# written. # written.
if (not None in p4) and (p3 != p4): if (not None in p4) and (p3 != p4):
point4 = Point(p4[0], p4[1], p4[2]) point4 = (p4[0], p4[1], p4[2])
triangles.append((point3, point4, point1)) triangles.append((point3, point4, point1))
for t in triangles: for t in triangles:
if (t[0] != t[1]) and (t[0] != t[2]) and (t[1] != t[2]): if (t[0] != t[1]) and (t[0] != t[2]) and (t[1] != t[2]):
...@@ -811,7 +813,7 @@ class DXFParser(object): ...@@ -811,7 +813,7 @@ class DXFParser(object):
# use the color code as the z coordinate # use the color code as the z coordinate
p1[2] = float(color) / 255 p1[2] = float(color) / 255
p2[2] = float(color) / 255 p2[2] = float(color) / 255
line = Line(Point(p1[0], p1[1], p1[2]), Point(p2[0], p2[1], p2[2])) line = Line((p1[0], p1[1], p1[2]), (p2[0], p2[1], p2[2]))
if line.p1 != line.p2: if line.p1 != line.p2:
self.lines.append(line) self.lines.append(line)
else: else:
...@@ -862,7 +864,7 @@ class DXFParser(object): ...@@ -862,7 +864,7 @@ class DXFParser(object):
if self._color_as_height and (not color is None): if self._color_as_height and (not color is None):
# use the color code as the z coordinate # use the color code as the z coordinate
center[2] = float(color) / 255 center[2] = float(color) / 255
center = Point(center[0], center[1], center[2]) center = (center[0], center[1], center[2])
xy_point_coords = pycam.Geometry.get_points_of_arc(center, radius, xy_point_coords = pycam.Geometry.get_points_of_arc(center, radius,
angle_start, angle_end) angle_start, angle_end)
# Somehow the order of points seems to be the opposite of what is # Somehow the order of points seems to be the opposite of what is
...@@ -871,9 +873,9 @@ class DXFParser(object): ...@@ -871,9 +873,9 @@ class DXFParser(object):
if len(xy_point_coords) > 1: if len(xy_point_coords) > 1:
for index in range(len(xy_point_coords) - 1): for index in range(len(xy_point_coords) - 1):
p1 = xy_point_coords[index] p1 = xy_point_coords[index]
p1 = Point(p1[0], p1[1], center.z) p1 = (p1[0], p1[1], center[2])
p2 = xy_point_coords[index + 1] p2 = xy_point_coords[index + 1]
p2 = Point(p2[0], p2[1], center.z) p2 = (p2[0], p2[1], center[2])
if p1 != p2: if p1 != p2:
self.lines.append(Line(p1, p2)) self.lines.append(Line(p1, p2))
else: else:
......
...@@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License ...@@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.PointKdtree import PointKdtree from pycam.Geometry.PointKdtree import PointKdtree
from pycam.Geometry.utils import epsilon from pycam.Geometry.utils import epsilon
...@@ -40,17 +40,17 @@ vertices = 0 ...@@ -40,17 +40,17 @@ vertices = 0
edges = 0 edges = 0
kdtree = None kdtree = None
lastUniqueVertex = (None,None,None)
def UniqueVertex(x, y, z): def UniqueVertex(x, y, z):
global vertices global vertices,lastUniqueVertex
if kdtree: if kdtree:
last = Point.id
p = kdtree.Point(x, y, z) p = kdtree.Point(x, y, z)
if p.id == last: if p == lastUniqueVertex:
vertices += 1 vertices += 1
return p return p
else: else:
vertices += 1 vertices += 1
return Point(x, y, z) return (x, y, z)
def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
global vertices, edges, kdtree global vertices, edges, kdtree
...@@ -127,7 +127,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): ...@@ -127,7 +127,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
a2 = unpack("<f", f.read(4))[0] a2 = unpack("<f", f.read(4))[0]
a3 = unpack("<f", f.read(4))[0] a3 = unpack("<f", f.read(4))[0]
n = Vector(float(a1), float(a2), float(a3)) n = (float(a1), float(a2), float(a3), 'v')
v11 = unpack("<f", f.read(4))[0] v11 = unpack("<f", f.read(4))[0]
v12 = unpack("<f", f.read(4))[0] v12 = unpack("<f", f.read(4))[0]
...@@ -150,9 +150,11 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): ...@@ -150,9 +150,11 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
# not used # not used
attribs = unpack("<H", f.read(2)) attribs = unpack("<H", f.read(2))
dotcross = n.dot(p2.sub(p1).cross(p3.sub(p1))) dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1)))
#dotcross = n.dot(p2.sub(p1).cross(p3.sub(p1)))
if a1 == a2 == a3 == 0: if a1 == a2 == a3 == 0:
dotcross = p2.sub(p1).cross(p3.sub(p1)).z dotcross = pcross(psub(p2, p1), psub(p3,p1))[2]
#dotcross = p2.sub(p1).cross(p3.sub(p1)).z
n = None n = None
if dotcross > 0: if dotcross > 0:
...@@ -209,8 +211,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): ...@@ -209,8 +211,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
if m: if m:
m = normal.match(line) m = normal.match(line)
if m: if m:
n = Vector(float(m.group('x')), float(m.group('y')), n = (float(m.group('x')), float(m.group('y')), float(m.group('z')), 'v')
float(m.group('z')))
else: else:
n = None n = None
continue continue
...@@ -243,7 +244,8 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): ...@@ -243,7 +244,8 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
n, p1, p2, p3 = None, None, None, None n, p1, p2, p3 = None, None, None, None
continue continue
if not n: if not n:
n = p2.sub(p1).cross(p3.sub(p1)).normalized() n = pnormalized(pcross(psub(p2, p1), psub(p3, p1)))
#n = p2.sub(p1).cross(p3.sub(p1)).normalized()
# validate the normal # validate the normal
# The three vertices of a triangle in an STL file are supposed # The three vertices of a triangle in an STL file are supposed
...@@ -254,7 +256,8 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): ...@@ -254,7 +256,8 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
dotcross = 0 dotcross = 0
else: else:
# make sure the points are in ClockWise order # make sure the points are in ClockWise order
dotcross = n.dot(p2.sub(p1).cross(p3.sub(p1))) dotcross = pdot(n, pcross(psub(p2,p1), psub(p3, p1)))
#dotcross = n.dot(p2.sub(p1).cross(p3.sub(p1)))
if dotcross > 0: if dotcross > 0:
# Triangle expects the vertices in clockwise order # Triangle expects the vertices in clockwise order
t = Triangle(p1, p3, p2, n) t = Triangle(p1, p3, p2, n)
......
...@@ -22,22 +22,21 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,22 +22,21 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point
from pycam.Geometry.Model import Model from pycam.Geometry.Model import Model
def get_test_model(): def get_test_model():
points = [] points = []
points.append(Point(-2, 1, 4)) points.append((-2, 1, 4))
points.append(Point(2, 1, 4)) points.append((2, 1, 4))
points.append(Point(0, -2, 4)) points.append((0, -2, 4))
points.append(Point(-5, 2, 2)) points.append((-5, 2, 2))
points.append(Point(-1, 3, 2)) points.append((-1, 3, 2))
points.append(Point(5, 2, 2)) points.append((5, 2, 2))
points.append(Point(4, -1, 2)) points.append((4, -1, 2))
points.append(Point(2, -4, 2)) points.append((2, -4, 2))
points.append(Point(-2, -4, 2)) points.append((-2, -4, 2))
points.append(Point(-3, -2, 2)) points.append((-3, -2, 2))
lines = [] lines = []
lines.append(Line(points[0], points[1])) lines.append(Line(points[0], points[1]))
......
...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
# take a look at the related blog posting describing this algorithm: # take a look at the related blog posting describing this algorithm:
# http://fab.senselab.org/node/43 # http://fab.senselab.org/node/43
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.PathGenerators import get_free_paths_ode, get_free_paths_triangles from pycam.PathGenerators import get_free_paths_ode, get_free_paths_triangles
...@@ -50,7 +50,8 @@ def _process_one_triangle((model, cutter, up_vector, triangle, z)): ...@@ -50,7 +50,8 @@ def _process_one_triangle((model, cutter, up_vector, triangle, z)):
# Case 1a # Case 1a
return result, None return result, None
# ignore triangles pointing upwards or downwards # ignore triangles pointing upwards or downwards
if triangle.normal.cross(up_vector).norm == 0: #if triangle.normal.cross(up_vector).norm == 0:
if pnorm(pcross(triangle.normal, up_vector)) == 0:
# Case 1b # Case 1b
return result, None return result, None
edge_collisions = get_collision_waterline_of_triangle(model, cutter, edge_collisions = get_collision_waterline_of_triangle(model, cutter,
...@@ -197,7 +198,7 @@ class ContourFollow(object): ...@@ -197,7 +198,7 @@ class ContourFollow(object):
def __init__(self, path_processor, physics=None): def __init__(self, path_processor, physics=None):
self.pa = path_processor self.pa = path_processor
self._up_vector = Vector(0, 0, 1) self._up_vector = (0, 0, 1, 'v')
self.physics = physics self.physics = physics
self._processed_triangles = [] self._processed_triangles = []
if self.physics: if self.physics:
...@@ -343,13 +344,15 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -343,13 +344,15 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
for index in range(3): for index in range(3):
edge = Line(proj_points[index - 1], proj_points[index]) edge = Line(proj_points[index - 1], proj_points[index])
# the edge should be clockwise around the model # the edge should be clockwise around the model
if edge.dir.cross(triangle.normal).dot(up_vector) < 0: #if edge.dir.cross(triangle.normal).dot(up_vector) < 0:
if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
edge = Line(edge.p2, edge.p1) edge = Line(edge.p2, edge.p1)
edges.append((edge, proj_points[index - 2])) edges.append((edge, proj_points[index - 2]))
outer_edges = [] outer_edges = []
for edge, other_point in edges: for edge, other_point in edges:
# pick only edges, where the other point is on the right side # pick only edges, where the other point is on the right side
if other_point.sub(edge.p1).cross(edge.dir).dot(up_vector) > 0: #if other_point.sub(edge.p1).cross(edge.dir).dot(up_vector) > 0:
if pdot(pcross(psub(other_point, edge.p1), edge.dir), up_vector) > 0:
outer_edges.append(edge) outer_edges.append(edge)
if len(outer_edges) == 0: if len(outer_edges) == 0:
# the points seem to be an one line # the points seem to be an one line
...@@ -361,14 +364,15 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -361,14 +364,15 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
outer_edges = [long_edge] outer_edges = [long_edge]
else: else:
edge = Line(proj_points[0], proj_points[1]) edge = Line(proj_points[0], proj_points[1])
if edge.dir.cross(triangle.normal).dot(up_vector) < 0: #if edge.dir.cross(triangle.normal).dot(up_vector) < 0:
if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
edge = Line(edge.p2, edge.p1) edge = Line(edge.p2, edge.p1)
outer_edges = [edge] outer_edges = [edge]
else: else:
# some parts of the triangle are above and some below the cutter level # some parts of the triangle are above and some below the cutter level
# Cases (2a), (2b), (3a) and (3b) # Cases (2a), (2b), (3a) and (3b)
points_above = [plane.get_point_projection(p) points_above = [plane.get_point_projection(p)
for p in triangle.get_points() if p.z > z] for p in triangle.get_points() if p[2] > z]
waterline = plane.intersect_triangle(triangle) waterline = plane.intersect_triangle(triangle)
if waterline is None: if waterline is None:
if len(points_above) == 0: if len(points_above) == 0:
...@@ -380,7 +384,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -380,7 +384,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
# "triangle.minz >= z" statement above). # "triangle.minz >= z" statement above).
outer_edges = [] outer_edges = []
elif not [p for p in triangle.get_points() elif not [p for p in triangle.get_points()
if p.z > z + epsilon]: if p[2] > z + epsilon]:
# same as above: fix for inaccurate floating calculations # same as above: fix for inaccurate floating calculations
outer_edges = [] outer_edges = []
else: else:
...@@ -397,8 +401,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -397,8 +401,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
outer_edges = [waterline] outer_edges = [waterline]
elif len(points_above) == 1: elif len(points_above) == 1:
other_point = points_above[0] other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot( dot = pdot(pcross(psub(other_point, waterline.p1), waterline.dir), up_vector)
up_vector) #dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(up_vector)
if dot > 0: if dot > 0:
# Case (2b) # Case (2b)
outer_edges = [waterline] outer_edges = [waterline]
...@@ -409,7 +413,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -409,7 +413,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
edges.append(Line(waterline.p2, other_point)) edges.append(Line(waterline.p2, other_point))
outer_edges = [] outer_edges = []
for edge in edges: for edge in edges:
if edge.dir.cross(triangle.normal).dot(up_vector) < 0: #if edge.dir.cross(triangle.normal).dot(up_vector) < 0:
if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
outer_edges.append(Line(edge.p2, edge.p1)) outer_edges.append(Line(edge.p2, edge.p1))
else: else:
outer_edges.append(edge) outer_edges.append(edge)
...@@ -422,15 +427,16 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -422,15 +427,16 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
edges.append(Line(waterline.p2, other_point)) edges.append(Line(waterline.p2, other_point))
edges.sort(key=lambda x: x.len) edges.sort(key=lambda x: x.len)
edge = edges[-1] edge = edges[-1]
if edge.dir.cross(triangle.normal).dot(up_vector) < 0: #if edge.dir.cross(triangle.normal).dot(up_vector) < 0:
if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
outer_edges = [Line(edge.p2, edge.p1)] outer_edges = [Line(edge.p2, edge.p1)]
else: else:
outer_edges = [edge] outer_edges = [edge]
else: else:
# two points above # two points above
other_point = points_above[0] other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot( dot = pdot(pcross(psub(other_point, waterline.p1), waterline.dir), up_vector)
up_vector) #dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(up_vector)
if dot > 0: if dot > 0:
# Case (2b) # Case (2b)
# the other two points are on the right side # the other two points are on the right side
...@@ -438,7 +444,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -438,7 +444,8 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
elif dot < 0: elif dot < 0:
# Case (3a) # Case (3a)
edge = Line(points_above[0], points_above[1]) edge = Line(points_above[0], points_above[1])
if edge.dir.cross(triangle.normal).dot(up_vector) < 0: #if edge.dir.cross(triangle.normal).dot(up_vector) < 0:
if pdot(pcross(edge.dir, triangle.normal), up_vector) < 0:
outer_edges = [Line(edge.p2, edge.p1)] outer_edges = [Line(edge.p2, edge.p1)]
else: else:
outer_edges = [edge] outer_edges = [edge]
...@@ -471,21 +478,23 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z): ...@@ -471,21 +478,23 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
direction = up_vector.cross(edge.dir).normalized() direction = up_vector.cross(edge.dir).normalized()
if direction is None: if direction is None:
continue continue
direction = direction.mul(max_length) direction = pmul(direction, max_length)
edge_dir = edge.p2.sub(edge.p1) #direction = direction.mul(max_length)
edge_dir = psub(edge.p2, edge.p1)
#edge_dir = edge.p2.sub(edge.p1)
# TODO: Adapt the number of potential starting positions to the length # TODO: Adapt the number of potential starting positions to the length
# of the line. Don't use 0.0 and 1.0 - this could result in ambiguous # of the line. Don't use 0.0 and 1.0 - this could result in ambiguous
# collisions with triangles sharing these vertices. # collisions with triangles sharing these vertices.
for factor in (0.5, epsilon, 1.0 - epsilon, 0.25, 0.75): for factor in (0.5, epsilon, 1.0 - epsilon, 0.25, 0.75):
start = edge.p1.add(edge_dir.mul(factor)) start = padd(edge.p1, pmul(edge_dir, factor))
#start = edge.p1.add(edge_dir.mul(factor))
# We need to use the triangle collision algorithm here - because we # We need to use the triangle collision algorithm here - because we
# need the point of collision in the triangle. # need the point of collision in the triangle.
collisions = get_free_paths_triangles([model], cutter, start, collisions = get_free_paths_triangles([model], cutter, start, padd(start, direction), return_triangles=True)
start.add(direction), return_triangles=True)
for index, coll in enumerate(collisions): for index, coll in enumerate(collisions):
if (index % 2 == 0) and (not coll[1] is None) \ if (index % 2 == 0) and (not coll[1] is None) \
and (not coll[2] is None) \ and (not coll[2] is None) \
and (coll[0].sub(start).dot(direction) > 0): and (pdot(psub(coll[0], start), direction) > 0): #and (coll[0].sub(start).dot(direction) > 0):
cl, hit_t, cp = coll cl, hit_t, cp = coll
break break
else: else:
......
...@@ -26,6 +26,8 @@ from pycam.Utils import ProgressCounter ...@@ -26,6 +26,8 @@ from pycam.Utils import ProgressCounter
from pycam.Utils.threading import run_in_parallel from pycam.Utils.threading import run_in_parallel
import pycam.Geometry.Model import pycam.Geometry.Model
import pycam.Utils.log import pycam.Utils.log
#import pprint
#pp = pprint.PrettyPrinter(indent=4)
log = pycam.Utils.log.get_logger() log = pycam.Utils.log.get_logger()
...@@ -52,12 +54,11 @@ class DropCutter(object): ...@@ -52,12 +54,11 @@ class DropCutter(object):
# Transfer the grid (a generator) into a list of lists and count the # Transfer the grid (a generator) into a list of lists and count the
# items. # items.
lines = []
# usually there is only one layer - but an xy-grid consists of two # usually there is only one layer - but an xy-grid consists of two
lines = []
for layer in motion_grid: for layer in motion_grid:
for line in layer: lines.extend(layer)
lines.append(line)
num_of_lines = len(lines) num_of_lines = len(lines)
progress_counter = ProgressCounter(len(lines), draw_callback) progress_counter = ProgressCounter(len(lines), draw_callback)
current_line = 0 current_line = 0
...@@ -66,15 +67,13 @@ class DropCutter(object): ...@@ -66,15 +67,13 @@ class DropCutter(object):
args = [] args = []
for one_grid_line in lines: for one_grid_line in lines:
# simplify the data (useful for remote processing) args.append(([(x,y) for x,y,z in one_grid_line], minz, maxz, model, cutter, self.physics))
xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
args.append((xy_coords, minz, maxz, model, cutter,
self.physics))
for points in run_in_parallel(_process_one_grid_line, args, for points in run_in_parallel(_process_one_grid_line, args,
callback=progress_counter.update): callback=progress_counter.update):
self.pa.new_scanline() self.pa.new_scanline()
if draw_callback and draw_callback(text="DropCutter: processing " \ if draw_callback and draw_callback(text="DropCutter: processing line %d/%d"
+ "line %d/%d" % (current_line + 1, num_of_lines)): % (current_line + 1, num_of_lines)):
# cancel requested # cancel requested
quit_requested = True quit_requested = True
break break
......
...@@ -22,7 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
import pycam.PathProcessors.PathAccumulator import pycam.PathProcessors.PathAccumulator
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.utils import ceil from pycam.Geometry.utils import ceil
......
...@@ -26,6 +26,7 @@ import pycam.PathProcessors ...@@ -26,6 +26,7 @@ import pycam.PathProcessors
from pycam.Geometry.utils import ceil from pycam.Geometry.utils import ceil
from pycam.Utils.threading import run_in_parallel from pycam.Utils.threading import run_in_parallel
from pycam.Utils import ProgressCounter from pycam.Utils import ProgressCounter
from pycam.Geometry.PointUtils import *
import pycam.Utils.log import pycam.Utils.log
import math import math
...@@ -127,7 +128,8 @@ class PushCutter(object): ...@@ -127,7 +128,8 @@ class PushCutter(object):
for line in layer_grid: for line in layer_grid:
p1, p2 = line p1, p2 = line
# calculate the required calculation depth (recursion) # calculate the required calculation depth (recursion)
distance = p2.sub(p1).norm distance = pnorm(psub(p2, p1))
#distance = p2.sub(p1).norm
# TODO: accessing cutter.radius here is slightly ugly # TODO: accessing cutter.radius here is slightly ugly
depth = math.log(accuracy * distance / cutter.radius) / math.log(2) depth = math.log(accuracy * distance / cutter.radius) / math.log(2)
depth = min(max(ceil(depth), 4), max_depth) depth = min(max(ceil(depth), 4), max_depth)
......
...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["DropCutter", "PushCutter", "EngraveCutter", "ContourFollow"] __all__ = ["DropCutter", "PushCutter", "EngraveCutter", "ContourFollow"]
from pycam.Geometry.utils import INFINITE, epsilon, sqrt from pycam.Geometry.utils import INFINITE, epsilon, sqrt
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
import pycam.Utils.threading import pycam.Utils.threading
...@@ -64,15 +64,18 @@ def get_free_paths_triangles(models, cutter, p1, p2, return_triangles=False): ...@@ -64,15 +64,18 @@ def get_free_paths_triangles(models, cutter, p1, p2, return_triangles=False):
all_results.extend(one_result) all_results.extend(one_result)
return all_results return all_results
backward = p1.sub(p2).normalized() backward = pnormalized(psub(p1, p2))
forward = p2.sub(p1).normalized() #backward = p1.sub(p2).normalized()
xyz_dist = p2.sub(p1).norm forward = pnormalized(psub(p2, p1))
#forward = p2.sub(p1).normalized()
xyz_dist = pnorm(psub(p2, p1))
#xyz_dist = p2.sub(p1).norm
minx = min(p1.x, p2.x) minx = min(p1[0], p2[0])
maxx = max(p1.x, p2.x) maxx = max(p1[0], p2[0])
miny = min(p1.y, p2.y) miny = min(p1[1], p2[1])
maxy = max(p1.y, p2.y) maxy = max(p1[1], p2[1])
minz = min(p1.z, p2.z) minz = min(p1[2], p2[2])
# find all hits along scan line # find all hits along scan line
hits = [] hits = []
...@@ -161,15 +164,15 @@ def get_free_paths_ode(physics, p1, p2, depth=8): ...@@ -161,15 +164,15 @@ def get_free_paths_ode(physics, p1, p2, depth=8):
""" """
points = [] points = []
# "resize" the drill along the while x/y range and check for a collision # "resize" the drill along the while x/y range and check for a collision
physics.extend_drill(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z) physics.extend_drill(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
physics.set_drill_position((p1.x, p1.y, p1.z)) physics.set_drill_position((p1[0], p1[1], p1[2]))
if physics.check_collision(): if physics.check_collision():
# collision detected # collision detected
if depth > 0: if depth > 0:
middle_x = (p1.x + p2.x) / 2 middle_x = (p1[0] + p2[0]) / 2
middle_y = (p1.y + p2.y) / 2 middle_y = (p1[1] + p2[1]) / 2
middle_z = (p1.z + p2.z) / 2 middle_z = (p1[2] + p2[2]) / 2
p_middle = Point(middle_x, middle_y, middle_z) p_middle = (middle_x, middle_y, middle_z)
group1 = get_free_paths_ode(physics, p1, p_middle, depth - 1) group1 = get_free_paths_ode(physics, p1, p_middle, depth - 1)
group2 = get_free_paths_ode(physics, p_middle, p2, depth - 1) group2 = get_free_paths_ode(physics, p_middle, p2, depth - 1)
if group1 and group2 and (group1[-1] == group2[0]): if group1 and group2 and (group1[-1] == group2[0]):
...@@ -222,12 +225,12 @@ def get_max_height_ode(physics, x, y, minz, maxz): ...@@ -222,12 +225,12 @@ def get_max_height_ode(physics, x, y, minz, maxz):
# skip this point (by going up to safety height) # skip this point (by going up to safety height)
return None return None
else: else:
return Point(x, y, safe_z) return (x, y, safe_z)
def get_max_height_triangles(model, cutter, x, y, minz, maxz): def get_max_height_triangles(model, cutter, x, y, minz, maxz):
if model is None: if model is None:
return Point(x, y, minz) return (x, y, minz)
p = Point(x, y, maxz) p = (x, y, maxz)
height_max = None height_max = None
box_x_min = cutter.get_minx(p) box_x_min = cutter.get_minx(p)
box_x_max = cutter.get_maxx(p) box_x_max = cutter.get_maxx(p)
...@@ -239,8 +242,8 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz): ...@@ -239,8 +242,8 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz):
box_y_max, box_z_max) box_y_max, box_z_max)
for t in triangles: for t in triangles:
cut = cutter.drop(t, start=p) cut = cutter.drop(t, start=p)
if cut and ((height_max is None) or (cut.z > height_max)): if cut and ((height_max is None) or (cut[2] > height_max)):
height_max = cut.z height_max = cut[2]
# don't do a complete boundary check for the height # don't do a complete boundary check for the height
# this avoids zero-cuts for models that exceed the bounding box height # this avoids zero-cuts for models that exceed the bounding box height
if (height_max is None) or (height_max < minz + epsilon): if (height_max is None) or (height_max < minz + epsilon):
...@@ -248,18 +251,20 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz): ...@@ -248,18 +251,20 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz):
if height_max > maxz + epsilon: if height_max > maxz + epsilon:
return None return None
else: else:
return Point(x, y, height_max) return (x, y, height_max)
def _check_deviance_of_adjacent_points(p1, p2, p3, min_distance): def _check_deviance_of_adjacent_points(p1, p2, p3, min_distance):
straight = p3.sub(p1) straight = psub(p3, p1)
added = p2.sub(p1).norm + p3.sub(p2).norm #straight = p3.sub(p1)
added = pnorm(psub(p2, p1)) + pnorm(psub(p3, p2))
#added = p2.sub(p1).norm + p3.sub(p2).norm
# compare only the x/y distance of p1 and p3 with min_distance # compare only the x/y distance of p1 and p3 with min_distance
if straight.x ** 2 + straight.y ** 2 < min_distance ** 2: if straight[0] ** 2 + straight[1] ** 2 < min_distance ** 2:
# the points are too close together # the points are too close together
return True return True
else: else:
# allow 0.1% deviance - this is an angle of around 2 degrees # allow 0.1% deviance - this is an angle of around 2 degrees
return (added / straight.norm) < 1.001 return (added / pnorm(straight)) < 1.001
def get_max_height_dynamic(model, cutter, positions, minz, maxz, physics=None): def get_max_height_dynamic(model, cutter, positions, minz, maxz, physics=None):
max_depth = 8 max_depth = 8
...@@ -291,11 +296,11 @@ def get_max_height_dynamic(model, cutter, positions, minz, maxz, physics=None): ...@@ -291,11 +296,11 @@ def get_max_height_dynamic(model, cutter, positions, minz, maxz, physics=None):
# distribute the new point two before the middle and one after # distribute the new point two before the middle and one after
if depth_count % 3 != 2: if depth_count % 3 != 2:
# insert between the 1st and 2nd point # insert between the 1st and 2nd point
middle = ((p1.x + p2.x) / 2, (p1.y + p2.y) / 2) middle = ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)
result.insert(index + 1, get_max_height(middle[0], middle[1])) result.insert(index + 1, get_max_height(middle[0], middle[1]))
else: else:
# insert between the 2nd and 3rd point # insert between the 2nd and 3rd point
middle = ((p2.x + p3.x) / 2, (p2.y + p3.y) / 2) middle = ((p2[0] + p3[0]) / 2, (p2[1] + p3[1]) / 2)
result.insert(index + 2, get_max_height(middle[0], middle[1])) result.insert(index + 2, get_max_height(middle[0], middle[1]))
depth_count += 1 depth_count += 1
else: else:
......
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.PathProcessors import pycam.PathProcessors
from pycam.Geometry.PolygonExtractor import PolygonExtractor from pycam.Geometry.PolygonExtractor import PolygonExtractor
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Toolpath import simplify_toolpath from pycam.Toolpath import simplify_toolpath
class ContourCutter(pycam.PathProcessors.BasePathProcessor): class ContourCutter(pycam.PathProcessors.BasePathProcessor):
...@@ -33,12 +33,13 @@ class ContourCutter(pycam.PathProcessors.BasePathProcessor): ...@@ -33,12 +33,13 @@ class ContourCutter(pycam.PathProcessors.BasePathProcessor):
self.polygon_extractor = None self.polygon_extractor = None
self.points = [] self.points = []
self.reverse = reverse self.reverse = reverse
self.__forward = Point(1, 1, 0) self.__forward = (1, 1, 0)
def append(self, point): def append(self, point):
# Sort the points in positive x/y direction - otherwise the # Sort the points in positive x/y direction - otherwise the
# PolygonExtractor breaks. # PolygonExtractor breaks.
if self.points and (point.sub(self.points[0]).dot(self.__forward) < 0): #if self.points and (point.sub(self.points[0]).dot(self.__forward) < 0):
if self.points and (pdot(psub(point, self.points[0]), self.__forward) < 0):
self.points.insert(0, point) self.points.insert(0, point)
else: else:
self.points.append(point) self.points.append(point)
......
...@@ -41,10 +41,10 @@ class BasePathProcessor(object): ...@@ -41,10 +41,10 @@ class BasePathProcessor(object):
def sort_layered(self, upper_first=True): def sort_layered(self, upper_first=True):
if upper_first: if upper_first:
compare_height = lambda path1, path2: \ compare_height = lambda path1, path2: \
path1.points[0].z < path2.points[0].z path1.points[0][2] < path2.points[0][2]
else: else:
compare_height = lambda path1, path2: \ compare_height = lambda path1, path2: \
path1.points[0].z > path2.points[0].z path1.points[0][2] > path2.points[0][2]
finished = False finished = False
while not finished: while not finished:
index = 0 index = 0
......
...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins import pycam.Plugins
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
class ModelProjection(pycam.Plugins.PluginBase): class ModelProjection(pycam.Plugins.PluginBase):
...@@ -87,8 +87,8 @@ class ModelProjection(pycam.Plugins.PluginBase): ...@@ -87,8 +87,8 @@ class ModelProjection(pycam.Plugins.PluginBase):
("ProjectionModelCustom", ("ProjectionModelCustom",
self.gui.get_object("ProjectionZLevel").get_value())): self.gui.get_object("ProjectionZLevel").get_value())):
if self.gui.get_object(objname).get_active(): if self.gui.get_object(objname).get_active():
plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1)) plane = Plane((0, 0, z_level), (0, 0, 1, 'v'))
self.log.info("Projecting 3D model at level z=%g" % plane.p.z) self.log.info("Projecting 3D model at level z=%g" % plane.p[2])
new_model = model.get_waterline_contour(plane, new_model = model.get_waterline_contour(plane,
callback=progress.update) callback=progress.update)
if new_model: if new_model:
......
...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
import pycam.Plugins import pycam.Plugins
import pycam.Geometry.Point from pycam.Geometry.PointUtils import *
GTK_COLOR_MAX = 65535.0 GTK_COLOR_MAX = 65535.0
...@@ -144,30 +144,32 @@ class OpenGLViewModelTriangle(pycam.Plugins.PluginBase): ...@@ -144,30 +144,32 @@ class OpenGLViewModelTriangle(pycam.Plugins.PluginBase):
model = models[index] model = models[index]
if not hasattr(model, "triangles"): if not hasattr(model, "triangles"):
continue continue
get_coords = lambda p: (p.x, p.y, p.z)
def calc_normal(main, normals): def calc_normal(main, normals):
suitable = pycam.Geometry.Point.Vector(0, 0, 0) suitable = (0, 0, 0, 'v')
for normal, weight in normals: for normal, weight in normals:
dot = main.dot(normal) dot = pdot(main, normal)
#dot = main.dot(normal)
if dot > 0: if dot > 0:
suitable = suitable.add(normal.mul(weight * dot)) suitable = padd(suitable, pmul(normal, weight * dot))
return suitable.normalized() #suitable = suitable.add(normal.mul(weight * dot))
return pnormalized(suitable)
#return suitable.normalized()
vertices = {} vertices = {}
for t in model.triangles(): for t in model.triangles():
for p in (t.p1, t.p2, t.p3): for p in (t.p1, t.p2, t.p3):
coords = get_coords(p) if not p in vertices:
if not coords in vertices: vertices[p] = []
vertices[coords] = [] vertices[p].append((pnormalized(t.normal), t.get_area()))
vertices[coords].append((t.normal.normalized(), t.get_area())) #vertices[p].append((t.normal.normalized(), t.get_area()))
GL.glBegin(GL.GL_TRIANGLES) GL.glBegin(GL.GL_TRIANGLES)
for t in model.triangles(): for t in model.triangles():
# The triangle's points are in clockwise order, but GL expects # The triangle's points are in clockwise order, but GL expects
# counter-clockwise sorting. # counter-clockwise sorting.
for p in (t.p1, t.p3, t.p2): for p in (t.p1, t.p3, t.p2):
coords = get_coords(p) normal = calc_normal(pnormalized(t.normal), vertices[p])
normal = calc_normal(t.normal.normalized(), vertices[coords]) #normal = calc_normal(t.normal.normalized(), vertices[coords])
GL.glNormal3f(normal.x, normal.y, normal.z) GL.glNormal3f(normal[0], normal[1], normal[2])
GL.glVertex3f(p.x, p.y, p.z) GL.glVertex3f(p[0], p[1], p[2])
GL.glEnd() GL.glEnd()
removal_list.append(index) removal_list.append(index)
# remove all models that we processed # remove all models that we processed
......
...@@ -102,9 +102,9 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase): ...@@ -102,9 +102,9 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase):
GL.glFinish() GL.glFinish()
GL.glBegin(GL.GL_LINE_STRIP) GL.glBegin(GL.GL_LINE_STRIP)
if not last_position is None: if not last_position is None:
GL.glVertex3f(last_position.x, last_position.y, last_position.z) GL.glVertex3f(last_position[0], last_position[1], last_position[2])
last_rapid = rapid last_rapid = rapid
GL.glVertex3f(position.x, position.y, position.z) GL.glVertex3f(position[0], position[1], position[2])
last_position = position last_position = position
GL.glEnd() GL.glEnd()
if show_directions: if show_directions:
......
...@@ -34,7 +34,7 @@ import gtk ...@@ -34,7 +34,7 @@ import gtk
import math import math
from pycam.Gui.OpenGLTools import draw_complete_model_view from pycam.Gui.OpenGLTools import draw_complete_model_view
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
import pycam.Geometry.Matrix as Matrix import pycam.Geometry.Matrix as Matrix
from pycam.Geometry.utils import sqrt, number from pycam.Geometry.utils import sqrt, number
import pycam.Plugins import pycam.Plugins
...@@ -815,12 +815,13 @@ class Camera(object): ...@@ -815,12 +815,13 @@ class Camera(object):
if (None, None) in low_high: if (None, None) in low_high:
return return
max_dim = max([high - low for low, high in low_high]) max_dim = max([high - low for low, high in low_high])
distv = Point(v["distance"][0], v["distance"][1], distv = pnormalized((v["distance"][0], v["distance"][1], v["distance"][2]))
v["distance"][2]).normalized() #distv = Point(v["distance"][0], v["distance"][1],v["distance"][2]).normalized()
# The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should # The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
# be roughly sufficient for showing the diagonal of any model. # be roughly sufficient for showing the diagonal of any model.
distv = distv.mul((max_dim * 1.25) / number(math.sin(v["fovy"] / 2))) distv = pmul(distv, (max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
self.view["distance"] = (distv.x, distv.y, distv.z) #distv = distv.mul((max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
self.view["distance"] = distv
# Adjust the "far" distance for the camera to make sure, that huge # Adjust the "far" distance for the camera to make sure, that huge
# models (e.g. x=1000) are still visible. # models (e.g. x=1000) are still visible.
self.view["zfar"] = 100 * max_dim self.view["zfar"] = 100 * max_dim
...@@ -976,9 +977,10 @@ class Camera(object): ...@@ -976,9 +977,10 @@ class Camera(object):
# Calculate the proportion of each model axis according to the x axis of # Calculate the proportion of each model axis according to the x axis of
# the screen. # the screen.
distv = self.view["distance"] distv = self.view["distance"]
distv = Point(distv[0], distv[1], distv[2]).normalized() distv = pnormalized((distv[0], distv[1], distv[2]))
factors_x = distv.cross(Point(v_up[0], v_up[1], v_up[2])).normalized() factors_x = pnormalized(pcross(distv, (v_up[0], v_up[1], v_up[2])))
factors_x = (factors_x.x, factors_x.y, factors_x.z) #factors_x = distv.cross(Point(v_up[0], v_up[1], v_up[2])).normalized()
#factors_x = (factors_x.x, factors_x.y, factors_x.z)
return (factors_x, factors_y) return (factors_x, factors_y)
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins import pycam.Plugins
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
import pycam.Gui.ControlsGTK import pycam.Gui.ControlsGTK
...@@ -145,7 +145,7 @@ class ToolpathCrop(pycam.Plugins.PluginBase): ...@@ -145,7 +145,7 @@ class ToolpathCrop(pycam.Plugins.PluginBase):
polygons.append(poly.copy()) polygons.append(poly.copy())
elif hasattr(model, "get_waterline_contour"): elif hasattr(model, "get_waterline_contour"):
z_slice = self.gui.get_object("ToolpathCropZSlice").get_value() z_slice = self.gui.get_object("ToolpathCropZSlice").get_value()
plane = Plane(Point(0, 0, z_slice)) plane = Plane((0, 0, z_slice))
for poly in model.get_waterline_contour(plane).get_polygons(): for poly in model.get_waterline_contour(plane).get_polygons():
polygons.append(poly.copy()) polygons.append(poly.copy())
# add an offset if requested # add an offset if requested
......
...@@ -24,7 +24,7 @@ import os ...@@ -24,7 +24,7 @@ import os
import pycam.Plugins import pycam.Plugins
from pycam.Exporters.GCodeExporter import PATH_MODES from pycam.Exporters.GCodeExporter import PATH_MODES
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
FILTER_GCODE = (("GCode files", ("*.ngc", "*.nc", "*.gc", "*.gcode")),) FILTER_GCODE = (("GCode files", ("*.ngc", "*.nc", "*.gc", "*.gcode")),)
...@@ -181,7 +181,7 @@ class ToolpathExport(pycam.Plugins.PluginBase): ...@@ -181,7 +181,7 @@ class ToolpathExport(pycam.Plugins.PluginBase):
pos_x = self.core.get("touch_off_position_x") pos_x = self.core.get("touch_off_position_x")
pos_y = self.core.get("touch_off_position_y") pos_y = self.core.get("touch_off_position_y")
pos_z = self.core.get("touch_off_position_z") pos_z = self.core.get("touch_off_position_z")
touch_off_pos = Point(pos_x, pos_y, pos_z) touch_off_pos = (pos_x, pos_y, pos_z)
else: else:
touch_off_pos = None touch_off_pos = None
generator = generator_func(destination, generator = generator_func(destination,
......
...@@ -22,7 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins import pycam.Plugins
import pycam.Geometry.Point
class ToolpathGrid(pycam.Plugins.PluginBase): class ToolpathGrid(pycam.Plugins.PluginBase):
...@@ -97,8 +96,7 @@ class ToolpathGrid(pycam.Plugins.PluginBase): ...@@ -97,8 +96,7 @@ class ToolpathGrid(pycam.Plugins.PluginBase):
new_paths = [] new_paths = []
for x in range(x_count): for x in range(x_count):
for y in range(y_count): for y in range(y_count):
shift = pycam.Geometry.Point.Vector(x * (x_space + x_dim), shift = (x * (x_space + x_dim), y * (y_space + y_dim), 0, 'v')
y * (y_space + y_dim), 0)
for path in toolpath.paths: for path in toolpath.paths:
new_path = pycam.Geometry.Path.Path() new_path = pycam.Geometry.Path.Path()
new_path.points = [shift.add(p) for p in path.points] new_path.points = [shift.add(p) for p in path.points]
......
...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
import pycam.Cutters import pycam.Cutters
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
import ode import ode
try: try:
...@@ -70,25 +70,25 @@ class ODEBlocks(object): ...@@ -70,25 +70,25 @@ class ODEBlocks(object):
self.y_step_width, self.z_width)) self.y_step_width, self.z_width))
box.setBody(body) box.setBody(body)
box.setPosition((x_pos, y_pos, z_pos)) box.setPosition((x_pos, y_pos, z_pos))
box.position = Point(x_pos, y_pos, z_pos) box.position = (x_pos, y_pos, z_pos)
self.boxes.append(box) self.boxes.append(box)
def process_cutter_movement(self, location_start, location_end): def process_cutter_movement(self, location_start, location_end):
# TODO: fix this workaround in the cutters shape defintions (or in ODE?) # TODO: fix this workaround in the cutters shape defintions (or in ODE?)
# for now we may only move from low x/y values to higher x/y values # for now we may only move from low x/y values to higher x/y values
if (location_start.x > location_end.x) \ if (location_start[0] > location_end[0]) \
or (location_start.y > location_end.y): or (location_start[1] > location_end[1]):
location_start, location_end = location_end, location_start location_start, location_end = location_end, location_start
cutter_body = ode.Body(self.world) cutter_body = ode.Body(self.world)
cutter_shape, cutter_position_func = self.cutter.get_shape("ODE") cutter_shape, cutter_position_func = self.cutter.get_shape("ODE")
self.space.add(cutter_shape) self.space.add(cutter_shape)
cutter_shape.space = self.space cutter_shape.space = self.space
cutter_shape.setBody(cutter_body) cutter_shape.setBody(cutter_body)
cutter_position_func(location_start.x, location_start.y, cutter_position_func(location_start[0], location_start[1],
location_start.z) location_start[2])
cutter_shape.extend_shape(location_end.x - location_start.x, cutter_shape.extend_shape(location_end[0] - location_start[0],
location_end.y - location_start.y, location_end[1] - location_start[1],
location_end.z - location_start.z) location_end[2] - location_start[2])
aabb = cutter_shape.getAABB() aabb = cutter_shape.getAABB()
cutter_height = aabb[5] - aabb[4] cutter_height = aabb[5] - aabb[4]
# add a ray along the drill to work around an ODE bug in v0.11.1 # add a ray along the drill to work around an ODE bug in v0.11.1
...@@ -112,8 +112,8 @@ class ODEBlocks(object): ...@@ -112,8 +112,8 @@ class ODEBlocks(object):
aabb = box.getAABB() aabb = box.getAABB()
end_height, start_height = aabb[-2:] end_height, start_height = aabb[-2:]
height_half = (start_height - end_height) / 2.0 height_half = (start_height - end_height) / 2.0
x_pos = box.position.x x_pos = box.position[0]
y_pos = box.position.y y_pos = box.position[1]
new_z = end_height new_z = end_height
box.setPosition((x_pos, y_pos, end_height - height_half)) box.setPosition((x_pos, y_pos, end_height - height_half))
loops_left = 12 loops_left = 12
...@@ -137,7 +137,7 @@ class ODEBlocks(object): ...@@ -137,7 +137,7 @@ class ODEBlocks(object):
z_pos = new_z - new_height / 2.0 z_pos = new_z - new_height / 2.0
new_box = ode.GeomBox(self.space, (aabb[1] - aabb[0], aabb[3] - aabb[2], new_box = ode.GeomBox(self.space, (aabb[1] - aabb[0], aabb[3] - aabb[2],
new_height)) new_height))
new_box.position = Point(x_pos, y_pos, z_pos) new_box.position = (x_pos, y_pos, z_pos)
new_box.setBody(box.getBody()) new_box.setBody(box.getBody())
new_box.setPosition((x_pos, y_pos, z_pos)) new_box.setPosition((x_pos, y_pos, z_pos))
self.boxes.insert(box_index, new_box) self.boxes.insert(box_index, new_box)
...@@ -199,30 +199,30 @@ class ODEBlocks(object): ...@@ -199,30 +199,30 @@ class ODEBlocks(object):
if (0 <= ix < len(height_field)) \ if (0 <= ix < len(height_field)) \
and (0 <= iy < len(height_field[ix])): and (0 <= iy < len(height_field[ix])):
point = height_field[ix][iy] point = height_field[ix][iy]
height_sum += point.z height_sum += point[2]
x_positions.append(point.x) x_positions.append(point[0])
y_positions.append(point.y) y_positions.append(point[1])
divisor += 1 divisor += 1
# Use the middle between the x positions of two adjacent boxes, # Use the middle between the x positions of two adjacent boxes,
# _if_ there is a neighbour attached to that corner. # _if_ there is a neighbour attached to that corner.
if (min(x_positions) < height_field[x][y].x) \ if (min(x_positions) < height_field[x][y][0]) \
or (max(x_positions) > height_field[x][y].x): or (max(x_positions) > height_field[x][y][0]):
x_value = (min(x_positions) + max(x_positions)) / 2.0 x_value = (min(x_positions) + max(x_positions)) / 2.0
else: else:
# There is no adjacent box in x direction. Use the step size # There is no adjacent box in x direction. Use the step size
# to calculate the x value of this edge. # to calculate the x value of this edge.
x_value = height_field[x][y].x \ x_value = height_field[x][y][0] \
+ offsets[0] * self.x_step_width / 2.0 + offsets[0] * self.x_step_width / 2.0
# same as above for y instead of x # same as above for y instead of x
if (min(y_positions) < height_field[x][y].y) \ if (min(y_positions) < height_field[x][y][1]) \
or (max(y_positions) > height_field[x][y].y): or (max(y_positions) > height_field[x][y][1]):
y_value = (min(y_positions) + max(y_positions)) / 2.0 y_value = (min(y_positions) + max(y_positions)) / 2.0
else: else:
y_value = height_field[x][y].y \ y_value = height_field[x][y][1] \
+ offsets[1] * self.y_step_width / 2.0 + offsets[1] * self.y_step_width / 2.0
# Create a Point instance describing the position and the # Create a Point instance describing the position and the
# average height. # average height.
points.append(Point(x_value, y_value, height_sum / divisor)) points.append((x_value, y_value, height_sum / divisor))
return points return points
# draw the surface # draw the surface
GL.glBegin(GL.GL_QUADS) GL.glBegin(GL.GL_QUADS)
...@@ -233,11 +233,11 @@ class ODEBlocks(object): ...@@ -233,11 +233,11 @@ class ODEBlocks(object):
points_around = get_box_height_points(x, y) points_around = get_box_height_points(x, y)
# Calculate the "normal" of polygon. We picked up three random # Calculate the "normal" of polygon. We picked up three random
# points of this quadrilateral. # points of this quadrilateral.
n = self._normal(points_around[1].z, points_around[2].z, n = self._normal(points_around[1][2], points_around[2][2],
points_around[3].z) points_around[3][2])
GL.glNormal3f(n[0], n[1], n[2]) GL.glNormal3f(n[0], n[1], n[2])
for point in points_around: for point in points_around:
GL.glVertex3f(point.x, point.y, point.z) GL.glVertex3f(point[0], point[1], point[2])
# go through the conditions for an edge box and use the # go through the conditions for an edge box and use the
# appropriate corners for the side faces of the material # appropriate corners for the side faces of the material
for condition, i1, i2 in ((x == 0, 3, 0), (y == 0, 0, 1), for condition, i1, i2 in ((x == 0, 3, 0), (y == 0, 0, 1),
...@@ -245,16 +245,16 @@ class ODEBlocks(object): ...@@ -245,16 +245,16 @@ class ODEBlocks(object):
(y == self.y_steps - 1, 2, 3)): (y == self.y_steps - 1, 2, 3)):
# check if this point belongs to an edge of the material # check if this point belongs to an edge of the material
if condition: if condition:
n = self._normal(points_around[1].z, points_around[2].z, n = self._normal(points_around[1][2], points_around[2][2],
points_around[3].z) points_around[3][2])
GL.glNormal3f(n[0], n[1], n[2]) GL.glNormal3f(n[0], n[1], n[2])
GL.glVertex3f(points_around[i1].x, points_around[i1].y, GL.glVertex3f(points_around[i1][0], points_around[i1][1],
self.z_offset) self.z_offset)
GL.glVertex3f(points_around[i1].x, points_around[i1].y, GL.glVertex3f(points_around[i1][0], points_around[i1][1],
points_around[i1].z) points_around[i1].z)
GL.glVertex3f(points_around[i2].x, points_around[i2].y, GL.glVertex3f(points_around[i2][0], points_around[i2][1],
points_around[i2].z) points_around[i2].z)
GL.glVertex3f(points_around[i2].x, points_around[i2].y, GL.glVertex3f(points_around[i2][0], points_around[i2][1],
self.z_offset) self.z_offset)
GL.glEnd() GL.glEnd()
...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.utils import sqrt from pycam.Geometry.utils import sqrt
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
import ctypes import ctypes
import math import math
...@@ -128,12 +128,12 @@ class ZBuffer(object): ...@@ -128,12 +128,12 @@ class ZBuffer(object):
py = self.y[y] py = self.y[y]
for x in range(minx, maxx): for x in range(minx, maxx):
px = self.x[x] px = self.x[x]
v0x = t.p3.x - t.p1.x v0x = t.p3[0] - t.p1[0]
v0y = t.p3.y - t.p1.y v0y = t.p3[1] - t.p1[1]
v1x = t.p2.x - t.p1.x v1x = t.p2[0] - t.p1[0]
v1y = t.p2.y - t.p1.y v1y = t.p2[1] - t.p1[1]
v2x = px - t.p1.x v2x = px - t.p1[0]
v2y = py - t.p1.y v2y = py - t.p1[1]
dot00 = v0x*v0x + v0y*v0y dot00 = v0x*v0x + v0y*v0y
dot01 = v0x*v1x + v0y*v1y dot01 = v0x*v1x + v0y*v1y
dot02 = v0x*v2x + v0y*v2y dot02 = v0x*v2x + v0y*v2y
...@@ -143,9 +143,9 @@ class ZBuffer(object): ...@@ -143,9 +143,9 @@ class ZBuffer(object):
u = (dot11 * dot02 - dot01 * dot12) * invDenom u = (dot11 * dot02 - dot01 * dot12) * invDenom
v = (dot00 * dot12 - dot01 * dot02) * invDenom v = (dot00 * dot12 - dot01 * dot02) * invDenom
if (u >= -EPSILON) and (v >= -EPSILON) and (u + v <= 1-EPSILON): if (u >= -EPSILON) and (v >= -EPSILON) and (u + v <= 1-EPSILON):
v0z = t.p3.z - t.p1.z v0z = t.p3[2] - t.p1[2]
v1z = t.p2.z - t.p1.z v1z = t.p2[2] - t.p1[2]
pz = t.p1.z + v0z * u + v1z * v pz = t.p1[2] + v0z * u + v1z * v
if pz > self.buf[y][x].z: if pz > self.buf[y][x].z:
self.buf[y][x].z = pz self.buf[y][x].z = pz
self.buf[y+0][x+0].changed = True self.buf[y+0][x+0].changed = True
...@@ -155,8 +155,8 @@ class ZBuffer(object): ...@@ -155,8 +155,8 @@ class ZBuffer(object):
self.changed = True self.changed = True
def add_cutter(self, c): def add_cutter(self, c):
cx = c.location.x cx = c.location[0]
cy = c.location.y cy = c.location[1]
rsq = c.radiussq rsq = c.radiussq
minx = int((c.minx - self.minx) / (self.maxx - self.minx) * self.xres) \ minx = int((c.minx - self.minx) / (self.maxx - self.minx) * self.xres) \
- 1 - 1
...@@ -182,13 +182,15 @@ class ZBuffer(object): ...@@ -182,13 +182,15 @@ class ZBuffer(object):
maxy = self.yres - 1 maxy = self.yres - 1
if miny > self.yres - 1: if miny > self.yres - 1:
miny = self.yres - 1 miny = self.yres - 1
p = Point(0, 0, 0) p = (0, 0, 0)
zaxis = Point(0, 0, -1) zaxis = (0, 0, -1)
for y in range(miny, maxy): for y in range(miny, maxy):
p.y = py = self.y[y] py = self.y[y]
p = (p[0], py, p[2])
for x in range(minx, maxx): for x in range(minx, maxx):
p.x = px = self.x[x] px = self.x[x]
p = (px, p[1], p[2])
if (px - cx) * (px - cx) + (py - cy) * (py - cy) \ if (px - cx) * (px - cx) + (py - cy) * (py - cy) \
<= rsq + EPSILON: <= rsq + EPSILON:
(cl, ccp, cp, l) = c.intersect_point(zaxis, p) (cl, ccp, cp, l) = c.intersect_point(zaxis, p)
......
...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License ...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.utils import epsilon from pycam.Geometry.utils import epsilon
from pycam.Geometry.Polygon import PolygonSorter from pycam.Geometry.Polygon import PolygonSorter
...@@ -91,9 +91,9 @@ def get_fixed_grid_line(start, end, line_pos, z, step_width=None, ...@@ -91,9 +91,9 @@ def get_fixed_grid_line(start, end, line_pos, z, step_width=None,
else: else:
steps = floatrange(start, end, inc=step_width) steps = floatrange(start, end, inc=step_width)
if grid_direction == GRID_DIRECTION_X: if grid_direction == GRID_DIRECTION_X:
get_point = lambda pos: Point(pos, line_pos, z) get_point = lambda pos: (pos, line_pos, z)
else: else:
get_point = lambda pos: Point(line_pos, pos, z) get_point = lambda pos: (line_pos, pos, z)
for pos in steps: for pos in steps:
yield get_point(pos) yield get_point(pos)
...@@ -208,7 +208,7 @@ def _get_position(minx, maxx, miny, maxy, z, position): ...@@ -208,7 +208,7 @@ def _get_position(minx, maxx, miny, maxy, z, position):
y = miny y = miny
else: else:
y = maxy y = maxy
return Point(x, y, z) return (x, y, z)
def get_spiral_layer_lines(minx, maxx, miny, maxy, z, line_distance_x, def get_spiral_layer_lines(minx, maxx, miny, maxy, z, line_distance_x,
line_distance_y, grid_direction, start_position, current_location): line_distance_y, grid_direction, start_position, current_location):
...@@ -255,27 +255,33 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width, ...@@ -255,27 +255,33 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width,
previous = None previous = None
for index, (start, end) in enumerate(lines): for index, (start, end) in enumerate(lines):
radius = 0.5 * min(line_distance_x, line_distance_y) radius = 0.5 * min(line_distance_x, line_distance_y)
edge_vector = end.sub(start) edge_vector = psub(end,start)
#edge_vector = end.sub(start)
# TODO: ellipse would be better than arc # TODO: ellipse would be better than arc
offset = edge_vector.normalized().mul(radius) offset = pmul(pnormalized(edge_vector), radius)
#offset = edge_vector.normalized().mul(radius)
if previous: if previous:
start = start.add(offset) start = padd(start, offset)
center = previous.add(offset) #start = start.add(offset)
up_vector = previous.sub(center).cross(start.sub(center)).normalized() center = padd(previous, offset)
north = center.add(Vector(1.0, 0.0, 0.0)) #center = previous.add(offset)
up_vector = pnormalized(pcross(psub(previous, center), psub(start, center)))
#up_vector = previous.sub(center).cross(start.sub(center)).normalized()
north = padd(center, (1.0, 0.0, 0.0, 'v'))
#north = center.add(Vector(1.0, 0.0, 0.0))
angle_start = pycam.Geometry.get_angle_pi(north, center, previous, up_vector, pi_factor=True) * 180.0 angle_start = pycam.Geometry.get_angle_pi(north, center, previous, up_vector, pi_factor=True) * 180.0
angle_end = pycam.Geometry.get_angle_pi(north, center, start, up_vector, pi_factor=True) * 180.0 angle_end = pycam.Geometry.get_angle_pi(north, center, start, up_vector, pi_factor=True) * 180.0
# TODO: remove these exceptions based on up_vector.z (get_points_of_arc does not respect the plane, yet) # TODO: remove these exceptions based on up_vector.z (get_points_of_arc does not respect the plane, yet)
if up_vector.z < 0: if up_vector[2] < 0:
angle_start, angle_end = -angle_end, -angle_start angle_start, angle_end = -angle_end, -angle_start
arc_points = pycam.Geometry.get_points_of_arc(center, radius, angle_start, angle_end) arc_points = pycam.Geometry.get_points_of_arc(center, radius, angle_start, angle_end)
if up_vector.z < 0: if up_vector[2] < 0:
arc_points.reverse() arc_points.reverse()
for arc_index in range(len(arc_points) - 1): for arc_index in range(len(arc_points) - 1):
p1_coord = arc_points[arc_index] p1_coord = arc_points[arc_index]
p2_coord = arc_points[arc_index + 1] p2_coord = arc_points[arc_index + 1]
p1 = Point(p1_coord[0], p1_coord[1], z) p1 = (p1_coord[0], p1_coord[1], z)
p2 = Point(p2_coord[0], p2_coord[1], z) p2 = (p2_coord[0], p2_coord[1], z)
rounded_lines.append((p1, p2)) rounded_lines.append((p1, p2))
if index != len(lines) - 1: if index != len(lines) - 1:
end = end.sub(offset) end = end.sub(offset)
...@@ -294,7 +300,8 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width, ...@@ -294,7 +300,8 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width,
else: else:
steps = floatrange(0.0, line.len, inc=step_width) steps = floatrange(0.0, line.len, inc=step_width)
for step in steps: for step in steps:
next_point = line.p1.add(line.dir.mul(step)) next_point = padd(line.p1, pmul(line.dir, step))
#next_point = line.p1.add(line.dir.mul(step))
points.append(next_point) points.append(next_point)
if reverse: if reverse:
points.reverse() points.reverse()
...@@ -328,7 +335,7 @@ def get_spiral((low, high), layer_distance, line_distance=None, ...@@ -328,7 +335,7 @@ def get_spiral((low, high), layer_distance, line_distance=None,
def get_lines_layer(lines, z, last_z=None, step_width=None, def get_lines_layer(lines, z, last_z=None, step_width=None,
milling_style=MILLING_STYLE_CONVENTIONAL): milling_style=MILLING_STYLE_CONVENTIONAL):
get_proj_point = lambda proj_point: Point(proj_point.x, proj_point.y, z) get_proj_point = lambda proj_point: (proj_point[0], proj_point[1], z)
projected_lines = [] projected_lines = []
for line in lines: for line in lines:
if (not last_z is None) and (last_z < line.minz): if (not last_z is None) and (last_z < line.minz):
...@@ -337,9 +344,10 @@ def get_lines_layer(lines, z, last_z=None, step_width=None, ...@@ -337,9 +344,10 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
elif line.minz < z < line.maxz: elif line.minz < z < line.maxz:
# Split the line at the point at z level and do the calculation # Split the line at the point at z level and do the calculation
# for both point pairs. # for both point pairs.
factor = (z - line.p1.z) / (line.p2.z - line.p1.z) factor = (z - line.p1[2]) / (line.p2[2] - line.p1[2])
plane_point = line.p1.add(line.vector.mul(factor)) plane_point = padd(line.p1, pmul(line.vector, factor))
if line.p1.z < z: #plane_point = line.p1.add(line.vector.mul(factor))
if line.p1[2] < z:
p1 = get_proj_point(line.p1) p1 = get_proj_point(line.p1)
p2 = line.p2 p2 = line.p2
else: else:
...@@ -348,10 +356,10 @@ def get_lines_layer(lines, z, last_z=None, step_width=None, ...@@ -348,10 +356,10 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
projected_lines.append(Line(p1, plane_point)) projected_lines.append(Line(p1, plane_point))
yield Line(plane_point, p2) yield Line(plane_point, p2)
elif line.minz < last_z < line.maxz: elif line.minz < last_z < line.maxz:
plane = Plane(Point(0, 0, last_z), Vector(0, 0, 1)) plane = Plane((0, 0, last_z), (0, 0, 1, 'v'))
cp = plane.intersect_point(line.dir, line.p1)[0] cp = plane.intersect_point(line.dir, line.p1)[0]
# we can be sure that there is an intersection # we can be sure that there is an intersection
if line.p1.z > last_z: if line.p1[2] > last_z:
p1, p2 = cp, line.p2 p1, p2 = cp, line.p2
else: else:
p1, p2 = line.p1, cp p1, p2 = line.p1, cp
...@@ -378,7 +386,8 @@ def get_lines_layer(lines, z, last_z=None, step_width=None, ...@@ -378,7 +386,8 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
else: else:
steps = floatrange(0.0, line.len, inc=step_width) steps = floatrange(0.0, line.len, inc=step_width)
for step in steps: for step in steps:
next_point = line.p1.add(line.dir.mul(step)) next_point = padd(line.p1, pmul(line.dir, step))
#next_point = line.p1.add(line.dir.mul(step))
points.append(next_point) points.append(next_point)
yield points yield points
......
...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License ...@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Point import Point, Vector from pycam.Geometry.PointUtils import *
from pycam.Geometry.Triangle import Triangle from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Plane import Plane from pycam.Geometry.Plane import Plane
from pycam.Geometry.Model import Model from pycam.Geometry.Model import Model
...@@ -34,16 +34,26 @@ def _get_triangles_for_face(pts): ...@@ -34,16 +34,26 @@ def _get_triangles_for_face(pts):
return (t1, t2) return (t1, t2)
def _add_cuboid_to_model(model, start, direction, height, width): def _add_cuboid_to_model(model, start, direction, height, width):
up = Vector(0, 0, 1).mul(height) up = pmul((0, 0, 1, 'v'), height)
ortho_dir = direction.cross(up).normalized() #up = Vector(0, 0, 1).mul(height)
start1 = start.add(ortho_dir.mul(-width/2)) ortho_dir = pnormalized(pcross(direction, up))
start2 = start1.add(up) #ortho_dir = direction.cross(up).normalized()
start3 = start2.add(ortho_dir.mul(width)) start1 = padd(start, pmul(ortho_dir, -width/2))
start4 = start3.sub(up) #start1 = start.add(ortho_dir.mul(-width/2))
end1 = start1.add(direction) start2 = padd(start1, up)
end2 = start2.add(direction) #start2 = start1.add(up)
end3 = start3.add(direction) start3 = padd(start2, pmul(ortho_dir, width))
end4 = start4.add(direction) #start3 = start2.add(ortho_dir.mul(width))
start4 = psub(start3, up)
#start4 = start3.sub(up)
end1 = padd(start1, direction)
#end1 = start1.add(direction)
end2 = padd(start2, direction)
#end2 = start2.add(direction)
end3 = padd(start3, direction)
#end3 = start3.add(direction)
end4 = padd(start4, direction)
#end4 = start4.add(direction)
faces = ((start1, start2, start3, start4), (start1, end1, end2, start2), faces = ((start1, start2, start3, start4), (start1, end1, end2, start2),
(start2, end2, end3, start3), (start3, end3, end4, start4), (start2, end2, end3, start3), (start3, end3, end4, start4),
(start4, end4, end1, start1), (end4, end3, end2, end1)) (start4, end4, end1, start1), (end4, end3, end2, end1))
...@@ -54,14 +64,14 @@ def _add_cuboid_to_model(model, start, direction, height, width): ...@@ -54,14 +64,14 @@ def _add_cuboid_to_model(model, start, direction, height, width):
def _add_aligned_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz): def _add_aligned_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz):
points = ( points = (
Point(minx, miny, minz), (minx, miny, minz),
Point(maxx, miny, minz), (maxx, miny, minz),
Point(maxx, maxy, minz), (maxx, maxy, minz),
Point(minx, maxy, minz), (minx, maxy, minz),
Point(minx, miny, maxz), (minx, miny, maxz),
Point(maxx, miny, maxz), (maxx, miny, maxz),
Point(maxx, maxy, maxz), (maxx, maxy, maxz),
Point(minx, maxy, maxz)) (minx, maxy, maxz))
triangles = [] triangles = []
# lower face # lower face
triangles.extend(_get_triangles_for_face( triangles.extend(_get_triangles_for_face(
...@@ -159,10 +169,10 @@ def get_support_distributed(model, z_plane, average_distance, ...@@ -159,10 +169,10 @@ def get_support_distributed(model, z_plane, average_distance,
result = Model() result = Model()
if not hasattr(model, "get_polygons"): if not hasattr(model, "get_polygons"):
model = model.get_waterline_contour( model = model.get_waterline_contour(
Plane(Point(0, 0, max(model.minz, z_plane)), Vector(0, 0, 1))) Plane((0, 0, max(model.minz, z_plane)), (0, 0, 1, 'v')))
if model: if model:
model = model.get_flat_projection(Plane(Point(0, 0, z_plane), model = model.get_flat_projection(Plane((0, 0, z_plane),
Vector(0, 0, 1))) (0, 0, 1, 'v')))
if model and bounds: if model and bounds:
model = model.get_cropped_model_by_bounds(bounds) model = model.get_cropped_model_by_bounds(bounds)
if model: if model:
...@@ -184,24 +194,24 @@ def get_support_distributed(model, z_plane, average_distance, ...@@ -184,24 +194,24 @@ def get_support_distributed(model, z_plane, average_distance,
bridges = bridge_calculator(polygon, z_plane, min_bridges_per_polygon, bridges = bridge_calculator(polygon, z_plane, min_bridges_per_polygon,
average_distance, avoid_distance) average_distance, avoid_distance)
for pos, direction in bridges: for pos, direction in bridges:
_add_cuboid_to_model(result, pos, direction.mul(length), height, _add_cuboid_to_model(result, pos, pmul(direction, length), height, thickness)
thickness) #_add_cuboid_to_model(result, pos, direction.mul(length), height, thickness)
return result return result
class _BridgeCorner(object): class _BridgeCorner(object):
# currently we only use the xy plane # currently we only use the xy plane
up_vector = Vector(0, 0, 1) up_vector = (0, 0, 1, 'v')
def __init__(self, barycenter, location, p1, p2, p3): def __init__(self, barycenter, location, p1, p2, p3):
self.location = location self.location = location
self.position = p2 self.position = p2
self.direction = pycam.Geometry.get_bisector(p1, p2, p3, self.direction = pnormalized(pycam.Geometry.get_bisector(p1, p2, p3, self.up_vector))
self.up_vector).normalized() preferred_direction = pnormalized(psub(p2, barycenter))
preferred_direction = p2.sub(barycenter).normalized() #preferred_direction = p2.sub(barycenter).normalized()
# direction_factor: 0..1 (bigger -> better) # direction_factor: 0..1 (bigger -> better)
direction_factor = (preferred_direction.dot(self.direction) + 1) / 2 direction_factor = (pdot(preferred_direction, self.direction) + 1) / 2
angle = pycam.Geometry.get_angle_pi(p1, p2, p3, #direction_factor = (preferred_direction.dot(self.direction) + 1) / 2
self.up_vector, pi_factor=True) angle = pycam.Geometry.get_angle_pi(p1, p2, p3, self.up_vector, pi_factor=True)
# angle_factor: 0..1 (bigger -> better) # angle_factor: 0..1 (bigger -> better)
if angle > 0.5: if angle > 0.5:
# use only angles > 90 degree # use only angles > 90 degree
...@@ -275,7 +285,8 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance, ...@@ -275,7 +285,8 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
avoid_distance): avoid_distance):
def is_near_list(point_list, point, distance): def is_near_list(point_list, point, distance):
for p in point_list: for p in point_list:
if p.sub(point).norm <= distance: #if p.sub(point).norm <= distance:
if pnorm(psub(p, point)) <= distance:
return True return True
return False return False
lines = polygon.get_lines() lines = polygon.get_lines()
...@@ -308,8 +319,10 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance, ...@@ -308,8 +319,10 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
if is_near_list(bridge_positions, position, avoid_distance): if is_near_list(bridge_positions, position, avoid_distance):
line = polygon.get_lines()[line_index] line = polygon.get_lines()[line_index]
# calculate two alternative points on the same line # calculate two alternative points on the same line
position1 = position.add(line.p1).div(2) position1 = pdiv(padd(position, line.p1), 2)
position2 = position.add(line.p2).div(2) #position1 = position.add(line.p1).div(2)
position2 = pdiv(padd(position, line.p2), 2)
#position2 = position.add(line.p2).div(2)
if is_near_list(bridge_positions, position1, avoid_distance): if is_near_list(bridge_positions, position1, avoid_distance):
if is_near_list(bridge_positions, position2, if is_near_list(bridge_positions, position2,
avoid_distance): avoid_distance):
...@@ -324,9 +337,9 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance, ...@@ -324,9 +337,9 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
# append the original position (ignoring z_plane) # append the original position (ignoring z_plane)
bridge_positions.append(position) bridge_positions.append(position)
# move the point to z_plane # move the point to z_plane
position = Point(position.x, position.y, z_plane) position = (position[0], position[1], z_plane)
bridge_dir = lines[line_index].dir.cross( bridge_dir = pnormalized(pcross(lines[line_index].dir, polygon.plane.n))
polygon.plane.n).normalized() #bridge_dir = lines[line_index].dir.cross(polygon.plane.n).normalized()
result.append((position, bridge_dir)) result.append((position, bridge_dir))
return result return result
...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["simplify_toolpath", "ToolpathList", "Toolpath", "Generator"] __all__ = ["simplify_toolpath", "ToolpathList", "Toolpath", "Generator"]
from pycam.Geometry.Point import Point from pycam.Geometry.PointUtils import *
from pycam.Geometry.Path import Path from pycam.Geometry.Path import Path
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
from pycam.Geometry.utils import number, epsilon from pycam.Geometry.utils import number, epsilon
...@@ -34,8 +34,10 @@ log = pycam.Utils.log.get_logger() ...@@ -34,8 +34,10 @@ log = pycam.Utils.log.get_logger()
def _check_colinearity(p1, p2, p3): def _check_colinearity(p1, p2, p3):
v1 = p2.sub(p1).normalized() v1 = pnormalized(psub(p2, p1))
v2 = p3.sub(p2).normalized() #v1 = p2.sub(p1).normalized()
v2 = pnormalized(psub(p3, p2))
#v2 = p3.sub(p2).normalized()
# compare if the normalized distances between p1-p2 and p2-p3 are equal # compare if the normalized distances between p1-p2 and p2-p3 are equal
return v1 == v2 return v1 == v2
...@@ -82,36 +84,36 @@ class Toolpath(object): ...@@ -82,36 +84,36 @@ class Toolpath(object):
new_paths.append(new_path) new_paths.append(new_path)
return Toolpath(new_paths, parameters=self.get_params()) return Toolpath(new_paths, parameters=self.get_params())
def _get_limit_generic(self, attr, func): def _get_limit_generic(self, idx, func):
path_min = [] path_min = []
for path in self.paths: for path in self.paths:
if path.points: if path.points:
path_min.append(func([getattr(p, attr) for p in path.points])) path_min.append(func([ p[idx] for p in path.points]))
return func(path_min) return func(path_min)
@property @property
def minx(self): def minx(self):
return self._get_limit_generic("x", min) return self._get_limit_generic(0, min)
@property @property
def maxx(self): def maxx(self):
return self._get_limit_generic("x", max) return self._get_limit_generic(0, max)
@property @property
def miny(self): def miny(self):
return self._get_limit_generic("y", min) return self._get_limit_generic(1, min)
@property @property
def maxy(self): def maxy(self):
return self._get_limit_generic("y", max) return self._get_limit_generic(1, max)
@property @property
def minz(self): def minz(self):
return self._get_limit_generic("z", min) return self._get_limit_generic(2, min)
@property @property
def maxz(self): def maxz(self):
return self._get_limit_generic("z", max) return self._get_limit_generic(2, max)
def get_meta_data(self): def get_meta_data(self):
meta = self.toolpath_settings.get_string() meta = self.toolpath_settings.get_string()
...@@ -137,12 +139,13 @@ class Toolpath(object): ...@@ -137,12 +139,13 @@ class Toolpath(object):
self.last_pos = new_position self.last_pos = new_position
return True return True
else: else:
distance = new_position.sub(self.last_pos).norm distance = pnorm(psub(new_position, self.last_pos))
#distance = new_position.sub(self.last_pos).norm
if self.moved_distance + distance > self.max_movement: if self.moved_distance + distance > self.max_movement:
partial = (self.max_movement - self.moved_distance) / \ partial = (self.max_movement - self.moved_distance) / \
distance distance
partial_dest = self.last_pos.add(new_position.sub( partial_dest = padd(self.last_pos, pmul(psub(new_position, self.last_pos), partial))
self.last_pos).mul(partial)) #partial_dest = self.last_pos.add(new_position.sub(self.last_pos).mul(partial))
self.moves.append((partial_dest, rapid)) self.moves.append((partial_dest, rapid))
self.last_pos = partial_dest self.last_pos = partial_dest
# we are finished # we are finished
...@@ -163,21 +166,20 @@ class Toolpath(object): ...@@ -163,21 +166,20 @@ class Toolpath(object):
continue continue
p_next = path.points[0] p_next = path.points[0]
if p_last is None: if p_last is None:
p_last = Point(p_next.x, p_next.y, safety_height) p_last = (p_next[0], p_next[1], safety_height)
if not result.append(p_last, True): if not result.append(p_last, True):
return result.moves return result.moves
if ((abs(p_last.x - p_next.x) > epsilon) \ if ((abs(p_last[0] - p_next[0]) > epsilon) \
or (abs(p_last.y - p_next.y) > epsilon)): or (abs(p_last[1] - p_next[1]) > epsilon)):
# Draw the connection between the last and the next path. # Draw the connection between the last and the next path.
# Respect the safety height. # Respect the safety height.
if (abs(p_last.z - p_next.z) > epsilon) \ #if (abs(p_last[2] - p_next[2]) > epsilon) or (p_last.sub(p_next).norm > self._max_safe_distance + epsilon):
or (p_last.sub(p_next).norm > \ if (abs(p_last[2] - p_next[2]) > epsilon) or (pnorm(psub(p_last, p_next)) > self._max_safe_distance + epsilon):
self._max_safe_distance + epsilon):
# The distance between these two points is too far. # The distance between these two points is too far.
# This condition helps to prevent moves up/down for # This condition helps to prevent moves up/down for
# adjacent lines. # adjacent lines.
safety_last = Point(p_last.x, p_last.y, safety_height) safety_last = (p_last[0], p_last[1], safety_height)
safety_next = Point(p_next.x, p_next.y, safety_height) safety_next = (p_next[0], p_next[1], safety_height)
if not result.append(safety_last, True): if not result.append(safety_last, True):
return result.moves return result.moves
if not result.append(safety_next, True): if not result.append(safety_next, True):
...@@ -187,7 +189,7 @@ class Toolpath(object): ...@@ -187,7 +189,7 @@ class Toolpath(object):
return result.moves return result.moves
p_last = path.points[-1] p_last = path.points[-1]
if not p_last is None: if not p_last is None:
p_last_safety = Point(p_last.x, p_last.y, safety_height) p_last_safety = (p_last[0], p_last[1], safety_height)
result.append(p_last_safety, True) result.append(p_last_safety, True)
return result.moves return result.moves
...@@ -206,7 +208,8 @@ class Toolpath(object): ...@@ -206,7 +208,8 @@ class Toolpath(object):
# go through all points of the path # go through all points of the path
for new_pos, rapid in self.get_moves(safety_height): for new_pos, rapid in self.get_moves(safety_height):
if not current_position is None: if not current_position is None:
result += new_pos.sub(current_position).norm / self._feedrate result += pnorm(psub(new_pos, current_position)) / self._feedrate
#result += new_pos.sub(current_position).norm / self._feedrate
current_position = new_pos current_position = new_pos
return result return result
...@@ -217,7 +220,8 @@ class Toolpath(object): ...@@ -217,7 +220,8 @@ class Toolpath(object):
# go through all points of the path # go through all points of the path
for new_pos, rapid in self.get_moves(safety_height): for new_pos, rapid in self.get_moves(safety_height):
if not current_position is None: if not current_position is None:
result += new_pos.sub(current_position).norm result += pnorm(psub(new_pos, current_position))
#result += new_pos.sub(current_position).norm
current_position = new_pos current_position = new_pos
return result return result
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment