Commit 9b939792 authored by Lars Kruse's avatar Lars Kruse
parents df4e35e1 f9a45628
......@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
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.intersection import intersect_cylinder_point, \
intersect_cylinder_line
......@@ -32,12 +32,12 @@ import uuid
class BaseCutter(IDGenerator):
vertical = Point(0, 0, -1)
vertical = (0, 0, -1)
def __init__(self, radius, location=None, height=None):
super(BaseCutter, self).__init__()
if location is None:
location = Point(0, 0, 0)
location = (0, 0, 0)
if height is None:
height = 10
radius = number(radius)
......@@ -56,22 +56,22 @@ class BaseCutter(IDGenerator):
def get_minx(self, start=None):
if start is None:
start = self.location
return start.x - self.distance_radius
return start[0] - self.distance_radius
def get_maxx(self, start=None):
if start is None:
start = self.location
return start.x + self.distance_radius
return start[0] + self.distance_radius
def get_miny(self, start=None):
if start is None:
start = self.location
return start.y - self.distance_radius
return start[1] - self.distance_radius
def get_maxy(self, start=None):
if start is None:
start = self.location
return start.y + self.distance_radius
return start[1] + self.distance_radius
def update_uuid(self):
self.uuid = uuid.uuid4()
......@@ -105,7 +105,7 @@ class BaseCutter(IDGenerator):
# "moveto" is used for collision detection calculation.
self.location = location
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):
raise NotImplementedError("Inherited class of BaseCutter does not " \
......@@ -126,7 +126,7 @@ class BaseCutter(IDGenerator):
# check bounding circle collision
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 \
* triangle.radius + triangle.radiussq) + epsilon:
return None
......@@ -150,7 +150,7 @@ class BaseCutter(IDGenerator):
start=start)
if cp:
# 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)
if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, cp)
return (cl, l, cp)
......@@ -159,8 +159,8 @@ class BaseCutter(IDGenerator):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_point(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, point)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection
if ccp:
cl = cp.add(start.sub(ccp))
......@@ -172,7 +172,7 @@ class BaseCutter(IDGenerator):
start = self.location
(cl, ccp, cp, l) = self.intersect_cylinder_point(direction, point,
start=start)
if ccp and ccp.z < start.sub(self.location).add(self.center).z:
if ccp and ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None)
return (cl, l, cp)
......@@ -180,11 +180,11 @@ class BaseCutter(IDGenerator):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_line(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, edge)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection
if ccp:
cl = start.add(cp.sub(ccp))
cl = padd(start, psub(cp, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -195,10 +195,10 @@ class BaseCutter(IDGenerator):
start=start)
if not ccp:
return (None, INFINITE, None)
m = cp.sub(edge.p1).dot(edge.dir)
m = pdot(psub(cp, edge.p1), edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, None)
if ccp.z < start.sub(self.location).add(self.center).z:
if ccp[2] < padd(psub(start, self.location), self.center)[2]:
return (None, INFINITE, None)
return (cl, l, cp)
......@@ -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.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.intersection import intersect_circle_plane, \
intersect_circle_point, intersect_circle_line
from pycam.Cutters.BaseCutter import BaseCutter
......@@ -40,7 +40,7 @@ class CylindricalCutter(BaseCutter):
def __init__(self, radius, **kwargs):
BaseCutter.__init__(self, radius, **kwargs)
self.axis = Vector(0, 0, 1)
self.axis = (0, 0, 1, 'v')
def __repr__(self):
return "CylindricalCutter<%s,%s>" % (self.location, self.radius)
......@@ -92,17 +92,17 @@ class CylindricalCutter(BaseCutter):
geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody())
geom_connect = ode_physics.get_parallelepiped_geom(
(Point(-hypotenuse / 2, radius, -diff_z / 2),
Point(hypotenuse / 2, radius, diff_z / 2),
Point(hypotenuse / 2, -radius, diff_z / 2),
Point(-hypotenuse / 2, -radius, -diff_z / 2)),
(Point(-hypotenuse / 2, radius,
((-hypotenuse / 2, radius, -diff_z / 2),
(hypotenuse / 2, radius, diff_z / 2),
(hypotenuse / 2, -radius, diff_z / 2),
(-hypotenuse / 2, -radius, -diff_z / 2)),
((-hypotenuse / 2, radius,
self.height - diff_z / 2),
Point(hypotenuse / 2,
(hypotenuse / 2,
radius, self.height + diff_z / 2),
Point(hypotenuse / 2, -radius,
(hypotenuse / 2, -radius,
self.height + diff_z / 2),
Point(-hypotenuse / 2, -radius,
(-hypotenuse / 2, -radius,
self.height - diff_z / 2)))
geom_connect.setRotation(rot_matrix_box)
geom_connect.setPosition((hypotenuse / 2, 0, radius))
......@@ -119,7 +119,7 @@ class CylindricalCutter(BaseCutter):
if not GL_enabled:
return
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"):
self._cylinder = GLU.gluNewQuadric()
GLU.gluCylinder(self._cylinder, self.radius, self.radius, self.height,
......@@ -131,17 +131,17 @@ class CylindricalCutter(BaseCutter):
def moveto(self, location, **kwargs):
BaseCutter.moveto(self, location, **kwargs)
self.center = Point(location.x, location.y,
location.z - self.get_required_distance())
self.center = (location[0], location[1],
location[2] - self.get_required_distance())
def intersect_circle_plane(self, direction, triangle, start=None):
if start is None:
start = self.location
(ccp, cp, d) = intersect_circle_plane(
start.sub(self.location).add(self.center), self.distance_radius,
direction, triangle)
padd(psub(start, self.location), self.center),
self.distance_radius, direction, triangle)
if ccp and cp:
cl = cp.add(start.sub(ccp))
cl = padd(cp, psub(start, ccp))
return (cl, ccp, cp, d)
return (None, None, None, INFINITE)
......@@ -149,10 +149,10 @@ class CylindricalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_circle_point(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, point)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, point)
if ccp:
cl = cp.add(start.sub(ccp))
cl = padd(cp, psub(start, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -160,10 +160,10 @@ class CylindricalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_circle_line(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, edge)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
if ccp:
cl = cp.add(start.sub(ccp))
cl = padd(cp, psub(start, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -177,9 +177,7 @@ class CylindricalCutter(BaseCutter):
d = d_t
cl = cl_t
cp = cp_t
if cl and (direction.x == 0) and (direction.y == 0):
#print 'circle_triangle:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp
if cl and (direction[0] == 0) and (direction[1] == 0):
return (cl, d, cp)
(cl_e1, d_e1, cp_e1) = self.intersect_circle_edge(direction,
triangle.e1, start=start)
......@@ -191,20 +189,15 @@ class CylindricalCutter(BaseCutter):
d = d_e1
cl = cl_e1
cp = cp_e1
#print 'circle_edge e1:'
if d_e2 < d:
d = d_e2
cl = cl_e2
cp = cp_e2
#print 'circle_edge e2:'
if d_e3 < d:
d = d_e3
cl = cl_e3
cp = cp_e3
#print 'circle_edge e3:'
if cl and (direction.x == 0) and (direction.y == 0):
#print 'circle_edge:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp
if cl and (direction[0] == 0) and (direction[1] == 0):
return (cl, d, cp)
(cl_p1, d_p1, cp_p1) = self.intersect_circle_vertex(direction,
triangle.p1, start=start)
......@@ -216,22 +209,17 @@ class CylindricalCutter(BaseCutter):
d = d_p1
cl = cl_p1
cp = cp_p1
#print 'circle vertex p1:'
if d_p2 < d:
d = d_p2
cl = cl_p2
cp = cp_p2
#print 'circle vertex p2:'
if d_p3 < d:
d = d_p3
cl = cl_p3
cp = cp_p3
#print 'circle vertex p3:'
if cl and (direction.x == 0) and (direction.y == 0):
#print 'circle vertex:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp
if cl and (direction[0] == 0) and (direction[1] == 0):
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,
triangle.p1, start=start)
(cl_p2, d_p2, cp_p2) = self.intersect_cylinder_vertex(direction,
......@@ -242,17 +230,14 @@ class CylindricalCutter(BaseCutter):
d = d_p1
cl = cl_p1
cp = cp_p1
#print 'cyl vertex p1:'
if d_p2 < d:
d = d_p2
cl = cl_p2
cp = cp_p2
#print 'cyl vertex p2:'
if d_p3 < d:
d = d_p3
cl = cl_p3
cp = cp_p3
#print 'cyl vertex p3:'
(cl_e1, d_e1, cp_e1) = self.intersect_cylinder_edge(direction,
triangle.e1, start=start)
(cl_e2, d_e2, cp_e2) = self.intersect_cylinder_edge(direction,
......@@ -263,18 +248,13 @@ class CylindricalCutter(BaseCutter):
d = d_e1
cl = cl_e1
cp = cp_e1
#print 'cyl edge e1:'
if d_e2 < d:
d = d_e2
cl = cl_e2
cp = cp_e2
#print 'cyl edge e2:'
if d_e3 < d:
d = d_e3
cl = cl_e3
cp = cp_e3
#print 'cyl edge e3:'
#print 'cyl:'
#print 'cl is:', cl, 'd is:', d, 'cp is:', cp
return (cl, d, cp)
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
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.intersection import intersect_sphere_plane, \
intersect_sphere_point, intersect_sphere_line
......@@ -41,7 +41,7 @@ class SphericalCutter(BaseCutter):
def __init__(self, radius, **kwargs):
BaseCutter.__init__(self, radius, **kwargs)
self.axis = Vector(0, 0, 1)
self.axis = (0, 0, 1, 'v')
def __repr__(self):
return "SphericalCutter<%s,%s>" % (self.location, self.radius)
......@@ -84,17 +84,17 @@ class SphericalCutter(BaseCutter):
geom_connect_transform = ode.GeomTransform(geom.space)
geom_connect_transform.setBody(geom.getBody())
geom_connect = ode_physics.get_parallelepiped_geom((
Point(-hypotenuse / 2, radius, -diff_z / 2),
Point(hypotenuse / 2, radius, diff_z / 2),
Point(hypotenuse / 2, -radius, diff_z / 2),
Point(-hypotenuse / 2, -radius, -diff_z / 2)),
(Point(-hypotenuse / 2, radius,
(-hypotenuse / 2, radius, -diff_z / 2),
(hypotenuse / 2, radius, diff_z / 2),
(hypotenuse / 2, -radius, diff_z / 2),
(-hypotenuse / 2, -radius, -diff_z / 2)),
((-hypotenuse / 2, radius,
self.height - diff_z / 2),
Point(hypotenuse / 2, radius,
(hypotenuse / 2, radius,
self.height + diff_z / 2),
Point(hypotenuse / 2, -radius,
(hypotenuse / 2, -radius,
self.height + diff_z / 2),
Point(-hypotenuse / 2, -radius,
(-hypotenuse / 2, -radius,
self.height - diff_z / 2)))
geom_connect.setRotation(rot_matrix_box)
geom_connect.setPosition((hypotenuse / 2, 0, radius))
......@@ -129,7 +129,7 @@ class SphericalCutter(BaseCutter):
if not GL_enabled:
return
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"):
self._sphere = GLU.gluNewQuadric()
GLU.gluSphere(self._sphere, self.radius, 10, 10)
......@@ -141,17 +141,17 @@ class SphericalCutter(BaseCutter):
def 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):
if start is None:
start = self.location
(ccp, cp, d) = intersect_sphere_plane(
start.sub(self.location).add(self.center), self.distance_radius,
direction, triangle)
padd(psub(start, self.location), self.center),
self.distance_radius, direction, triangle)
# offset intersection
if ccp:
cl = cp.add(start.sub(ccp))
cl = padd(cp, psub(start, ccp))
return (cl, ccp, cp, d)
return (None, None, None, INFINITE)
......@@ -166,8 +166,8 @@ class SphericalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_sphere_point(
start.sub(self.location).add(self.center), self.distance_radius,
self.distance_radiussq, direction, point)
padd(psub(start, self.location), self.center),
self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection
cl = None
if cp:
......@@ -183,11 +183,11 @@ class SphericalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_sphere_line(
start.sub(self.location).add(self.center), self.distance_radius,
self.distance_radiussq, direction, edge)
padd(psub(start, self.location), self.center),
self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection
if ccp:
cl = cp.sub(ccp.sub(start))
cl = psub(cp, psub(ccp, start))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -196,9 +196,9 @@ class SphericalCutter(BaseCutter):
start=start)
if cp:
# check if the contact point is between the endpoints
d = edge.p2.sub(edge.p1)
m = cp.sub(edge.p1).dot(d)
if (m < -epsilon) or (m > d.normsq + epsilon):
d = psub(edge.p2, edge.p1)
m = pdot(psub(cp, edge.p1), d)
if (m < -epsilon) or (m > pnormsq(d) + epsilon):
return (None, INFINITE, None)
return (cl, l, cp)
......@@ -216,7 +216,7 @@ class SphericalCutter(BaseCutter):
d = d_t
cl = cl_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)
(cl_e1, d_e1, cp_e1) = self.intersect_sphere_edge(direction,
triangle.e1, start=start)
......@@ -254,9 +254,9 @@ class SphericalCutter(BaseCutter):
d = d_p3
cl = cl_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)
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,
triangle.p1, start=start)
(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
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.intersection import intersect_torus_plane, \
intersect_torus_point, intersect_circle_plane, intersect_circle_point, \
......@@ -46,7 +46,7 @@ class ToroidalCutter(BaseCutter):
# we need "minorradius" for "moveto" - thus set it before parent's init
BaseCutter.__init__(self, radius, **kwargs)
self.majorradius = self.radius - minorradius
self.axis = Point(0, 0, 1)
self.axis = (0, 0, 1)
self.majorradiussq = self.majorradius ** 2
self.minorradiussq = self.minorradius ** 2
self.distance_majorradius = self.majorradius \
......@@ -97,7 +97,7 @@ class ToroidalCutter(BaseCutter):
if not GL_enabled:
return
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)
if not hasattr(self, "_cylinder"):
self._cylinder = GLU.gluNewQuadric()
......@@ -105,7 +105,7 @@ class ToroidalCutter(BaseCutter):
10, 20)
GL.glPopMatrix()
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"):
self._disk = GLU.gluNewQuadric()
GLU.gluDisk(self._disk, 0, self.majorradius, 20, 10)
......@@ -113,17 +113,17 @@ class ToroidalCutter(BaseCutter):
def 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):
if start is None:
start = self.location
(ccp, cp, l) = intersect_torus_plane(
start.sub(self.location).add(self.center), self.axis,
self.distance_majorradius, self.distance_minorradius, direction,
padd(psub(start, self.location), self.center),
self.axis, self.distance_majorradius, self.distance_minorradius, direction,
triangle)
if cp:
cl = cp.add(start.sub(ccp))
cl = padd(cp, psub(start, ccp))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -138,12 +138,12 @@ class ToroidalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_torus_point(
start.sub(self.location).add(self.center), self.axis,
self.distance_majorradius, self.distance_minorradius,
padd(psub(start, self.location), self.center),
self.axis, self.distance_majorradius, self.distance_minorradius,
self.distance_majorradiussq, self.distance_minorradiussq,
direction, point)
if ccp:
cl = point.add(start.sub(ccp))
cl = padd(point, psub(start, ccp))
return (cl, ccp, point, l)
return (None, None, None, INFINITE)
......@@ -195,11 +195,11 @@ class ToroidalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_point(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, point)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, point)
# offset intersection
if ccp:
cl = start.add(direction.mul(l))
cl = padd(start, pmul(direction, l))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -207,21 +207,22 @@ class ToroidalCutter(BaseCutter):
if start is None:
start = self.location
(ccp, cp, l) = intersect_cylinder_line(
start.sub(self.location).add(self.center), self.axis,
self.distance_radius, self.distance_radiussq, direction, edge)
padd(psub(start, self.location), self.center),
self.axis, self.distance_radius, self.distance_radiussq, direction, edge)
# offset intersection
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 (None, None, None, INFINITE)
def intersect_cylinder_edge(self, direction, edge, start=None):
(cl, ccp, cp, l) = self.intersect_cylinder_line(direction, edge,
start=start)
if ccp and ccp.z < self.center.z:
if ccp and ccp[2] < self.center[2]:
return (None, INFINITE, None)
if ccp:
m = cp.sub(edge.p1).dot(edge.dir)
m = pdot(psub(cp, edge.p1), edge.dir)
if (m < -epsilon) or (m > edge.len + epsilon):
return (None, INFINITE, None)
return (cl, l, cp)
......@@ -233,7 +234,7 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, direction, triangle)
# offset intersection
if ccp:
cl = cp.sub(ccp.sub(start))
cl = psub(cp, psub(ccp, start))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -244,7 +245,7 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, self.distance_majorradiussq,
direction, point)
if ccp:
cl = cp.sub(ccp.sub(start))
cl = psub(cp, psub(ccp, start))
return (cl, ccp, point, l)
return (None, None, None, INFINITE)
......@@ -255,7 +256,7 @@ class ToroidalCutter(BaseCutter):
self.distance_majorradius, self.distance_majorradiussq,
direction, edge)
if ccp:
cl = cp.sub(ccp.sub(start))
cl = psub(cp, psub(ccp, start))
return (cl, ccp, cp, l)
return (None, None, None, INFINITE)
......@@ -347,7 +348,7 @@ class ToroidalCutter(BaseCutter):
d = d_e3
cl = cl_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,
triangle.p1, start=start)
(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/>.
from pycam.Geometry import TransformableContainer
from pycam.Geometry.Model import ContourModel
from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
TEXT_ALIGN_LEFT = 0
TEXT_ALIGN_CENTER = 1
......@@ -50,9 +50,7 @@ class Letter(TransformableContainer):
def get_positioned_lines(self, base_point, skew=None):
result = []
get_skewed_point = lambda p: \
Point(base_point.x + p.x + (p.y * skew / 100.0),
base_point.y + p.y, base_point.z)
get_skewed_point = lambda p: (base_point[0] + p[0] + (p[1] * skew / 100.0), base_point[1] + p[1], base_point[2])
for line in self.lines:
skewed_p1 = get_skewed_point(line.p1)
skewed_p2 = get_skewed_point(line.p2)
......@@ -107,7 +105,7 @@ class Charset(object):
align=None):
result = ContourModel()
if origin is None:
origin = Point(0, 0, 0)
origin = (0, 0, 0)
if align is None:
align = TEXT_ALIGN_LEFT
base = origin
......@@ -120,7 +118,7 @@ class Charset(object):
line_height = self.default_height
for character in line:
if character == " ":
base = base.add(Point(word_spacing, 0, 0))
base = padd(base, (word_spacing, 0, 0))
elif character in self.letters.keys():
charset_letter = self.letters[character]
new_model = ContourModel()
......@@ -133,13 +131,12 @@ class Charset(object):
# update line height
line_height = max(line_height, charset_letter.maxy())
# shift the base position
base = base.add(Point(
charset_letter.maxx() + letter_spacing, 0, 0))
base = padd(base, (charset_letter.maxx() + letter_spacing, 0, 0))
else:
# unknown character - add a small whitespace
base = base.add(Point(letter_spacing, 0, 0))
base = padd(base, (letter_spacing, 0, 0))
# 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])
if not current_line.maxx is None:
if align == TEXT_ALIGN_CENTER:
current_line.shift(-current_line.maxx / 2, 0, 0)
......
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
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.utils import epsilon, sqrt
# OpenGLTools will be imported later, if necessary
......@@ -48,61 +48,61 @@ class Line(IDGenerator, TransformableContainer):
self.reset_cache()
def copy(self):
return self.__class__(self.p1.copy(), self.p2.copy())
return self.__class__(self.p1, self.p2)
@property
def vector(self):
if self._vector is None:
self._vector = self.p2.sub(self.p1)
self._vector = psub(self.p2, self.p1)
return self._vector
@property
def dir(self):
return self.vector.normalized()
return pnormalized(self.vector)
@property
def len(self):
return self.vector.norm
return pnorm(self.vector)
@property
def minx(self):
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
@property
def maxx(self):
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
@property
def miny(self):
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
@property
def maxy(self):
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
@property
def minz(self):
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
@property
def maxz(self):
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
def __repr__(self):
return "Line<%g,%g,%g>-<%g,%g,%g>" % (self.p1.x, self.p1.y, self.p1.z,
self.p2.x, self.p2.y, self.p2.z)
return "Line<%g,%g,%g>-<%g,%g,%g>" % (self.p1[0], self.p1[1], self.p1[2],
self.p2[0], self.p2[1], self.p2[2])
def __cmp__(self, other):
""" Two lines are equal if both pairs of points are at the same
......@@ -119,10 +119,10 @@ class Line(IDGenerator, TransformableContainer):
return cmp(self.p2, other.p2)
else:
return cmp(str(self), str(other))
def next(self):
yield self.p1
yield self.p2
yield "p1"
yield "p2"
def get_children_count(self):
# a line always contains two points
......@@ -141,23 +141,23 @@ class Line(IDGenerator, TransformableContainer):
return (self.p1, self.p2)
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))
def get_length_line(self, 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)))
def closest_point(self, p):
v = self.dir
if v is None:
# for zero-length lines
return self.p1
l = self.p1.dot(v) - p.dot(v)
return self.p1.sub(v.mul(l))
l = pdot(self.p1, v) - pdot(p, v)
return psub(self.p1, pmul(v, l))
def dist_to_point_sq(self, p):
return p.sub(self.closest_point(p)).normsq
return pnormsq(psub(p, self.closes_point(p)))
def dist_to_point(self, p):
return sqrt(self.dist_to_point_sq(p))
......@@ -166,8 +166,9 @@ class Line(IDGenerator, TransformableContainer):
if (p == self.p1) or (p == self.p2):
# these conditions are not covered by the code below
return True
dir1 = p.sub(self.p1).normalized()
dir2 = self.p2.sub(p).normalized()
dir1 = pnormalized(psub(p, self.p1))
dir2 = pnormalized(psub(self.p2, p))
# True if the two parts of the line have the same direction or if the
# point is self.p1 or self.p2.
return (dir1 == dir2 == self.dir) or (dir1 is None) or (dir2 is None)
......@@ -178,8 +179,8 @@ class Line(IDGenerator, TransformableContainer):
if not color is None:
GL.glColor4f(*color)
GL.glBegin(GL.GL_LINES)
GL.glVertex3f(self.p1.x, self.p1.y, self.p1.z)
GL.glVertex3f(self.p2.x, self.p2.y, self.p2.z)
GL.glVertex3f(self.p1[0], self.p1[1], self.p1[2])
GL.glVertex3f(self.p2[0], self.p2[1], self.p2[2])
GL.glEnd()
# (optional) draw a cone for visualizing the direction of each line
if show_directions and (self.len > 0):
......@@ -196,24 +197,24 @@ class Line(IDGenerator, TransformableContainer):
0 and 1.
"""
x1, x2, x3, x4 = self.p1, self.p2, line.p1, line.p2
a = x2.sub(x1)
b = x4.sub(x3)
c = x3.sub(x1)
a = psub(x2, x1)
b = psub(x4, x3)
c = psub(x3, x1)
# see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
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))
except ZeroDivisionError:
# lines are parallel
# check if they are _one_ line
if a.cross(c).norm != 0:
if pnorm(pcross(a,c)) != 0:
# the lines are parallel with a distance
return None, None
# the lines are on one straight
candidates = []
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):
candidates.append((x4, line.p2.sub(self.p1).norm / a.norm))
candidates.append((x4, pnorm(psub(line.p2, self.p1)) / pnorm(a)))
elif line.is_point_inside(x1):
candidates.append((x1, 0))
elif line.is_point_inside(x2):
......@@ -224,13 +225,13 @@ class Line(IDGenerator, TransformableContainer):
candidates.sort(key=lambda (cp, dist): dist)
return candidates[0]
if infinite_lines or (-epsilon <= factor <= 1 + epsilon):
intersection = x1.add(a.mul(factor))
intersection = padd(x1, pmul(a, factor))
# check if the intersection is between x3 and x4
if infinite_lines:
return intersection, factor
elif (min(x3.x, x4.x) - epsilon <= intersection.x <= max(x3.x, x4.x) + epsilon) \
and (min(x3.y, x4.y) - epsilon <= intersection.y <= max(x3.y, x4.y) + epsilon) \
and (min(x3.z, x4.z) - epsilon <= intersection.z <= max(x3.z, x4.z) + epsilon):
elif (min(x3[0], x4[0]) - epsilon <= intersection[0] <= max(x3[0], x4[0]) + epsilon) \
and (min(x3[1], x4[1]) - epsilon <= intersection[1] <= max(x3[1], x4[1]) + epsilon) \
and (min(x3[2], x4[2]) - epsilon <= intersection[2] <= max(x3[2], x4[2]) + epsilon):
return intersection, factor
else:
# intersection outside of the length of line(x3, x4)
......@@ -247,15 +248,15 @@ class Line(IDGenerator, TransformableContainer):
else:
# the line needs to be cropped
# generate the six planes of the cube for possible intersections
minp = Point(minx, miny, minz)
maxp = Point(maxx, maxy, maxz)
minp = (minx, miny, minz)
maxp = (maxx, maxy, maxz)
planes = [
Plane(minp, Point(1, 0, 0)),
Plane(minp, Point(0, 1, 0)),
Plane(minp, Point(0, 0, 1)),
Plane(maxp, Point(1, 0, 0)),
Plane(maxp, Point(0, 1, 0)),
Plane(maxp, Point(0, 0, 1)),
Plane(minp, (1, 0, 0)),
Plane(minp, (0, 1, 0)),
Plane(minp, (0, 0, 1)),
Plane(maxp, (1, 0, 0)),
Plane(maxp, (0, 1, 0)),
Plane(maxp, (0, 0, 1)),
]
# calculate all intersections
intersections = [plane.intersect_point(self.dir, self.p1)
......
......@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
# 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
import math
......@@ -54,23 +54,6 @@ def get_dot_product(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):
""" calculate the lengt of a 3d vector
......@@ -101,13 +84,10 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
@rtype: tuple(tuple(float))
@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_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:
arcsin = cross_product / (v_orig_length * v_dest_length)
except ZeroDivisionError:
......@@ -121,9 +101,9 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
# calculate the rotation axis
# The rotation axis is equal to the cross product of the original and
# 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[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:
return None
# get the rotation matrix
......@@ -131,15 +111,15 @@ def get_rotation_matrix_from_to(v_orig, v_dest):
c = math.cos(rot_angle)
s = math.sin(rot_angle)
t = 1 - c
return ((t * rot_axis.x * rot_axis.x + c,
t * rot_axis.x * rot_axis.y - s * rot_axis.z,
t * rot_axis.x * rot_axis.z + s * rot_axis.y),
(t * rot_axis.x * rot_axis.y + s * rot_axis.z,
t * rot_axis.y * rot_axis.y + c,
t * rot_axis.y * rot_axis.z - s * rot_axis.x),
(t * rot_axis.x * rot_axis.z - s * rot_axis.y,
t * rot_axis.y * rot_axis.z + s * rot_axis.x,
t * rot_axis.z * rot_axis.z + c))
return ((t * rot_axis[0] * rot_axis[0] + c,
t * rot_axis[0] * rot_axis[1] - s * rot_axis[2],
t * rot_axis[0] * rot_axis[2] + s * rot_axis[1]),
(t * rot_axis[0] * rot_axis[1] + s * rot_axis[2],
t * rot_axis[1] * rot_axis[1] + c,
t * rot_axis[1] * rot_axis[2] - s * rot_axis[0]),
(t * rot_axis[0] * rot_axis[2] - s * rot_axis[1],
t * rot_axis[1] * rot_axis[2] + s * rot_axis[0],
t * rot_axis[2] * rot_axis[2] + c))
def get_rotation_matrix_axis_angle(rot_axis, rot_angle, use_radians=True):
""" calculate rotation matrix for a normalized vector and an angle
......
......@@ -31,7 +31,7 @@ from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane
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.Matrix import TRANSFORMATIONS
from pycam.Toolpath import Bounds
......@@ -109,7 +109,7 @@ class BaseModel(IDGenerator, TransformableContainer):
return sum([len(igroup) for igroup in self._item_groups])
def next(self):
for item_group in self._item_groups:
for item_group in self._item_groups:
for item in item_group:
if isinstance(item, list):
for subitem in item:
......@@ -324,7 +324,7 @@ class Model(BaseModel):
# Find all groups with the same direction (see 'normal') that
# share at least one edge with the current triangle.
touch_groups = []
if t.normal.z == 0:
if t.normal[2] == 0:
# ignore vertical triangles
continue
for group_index, group in enumerate(groups):
......@@ -359,7 +359,7 @@ class ContourModel(BaseModel):
self.name = "contourmodel%d" % self.id
if plane is None:
# 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._line_groups = []
self._item_groups.append(self._line_groups)
......@@ -804,7 +804,7 @@ class PolygonGroup(object):
self.inner = inner_list
self.callback = callback
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:
self.lines.extend(poly.get_lines())
......@@ -836,7 +836,7 @@ class PolygonGroup(object):
# create the backside plane
backside_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))
if self.callback and self.callback():
return None
......@@ -873,7 +873,7 @@ class PolygonGroup(object):
# the contour points of the model will always be at level zero
a[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)
if not 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):
fan_points.append(cp)
final_points.append(cp)
else:
final_points.append(Point(*coords[index]))
final_points.append(coords[index])
# check if the three fan_points are in line
if len(fan_points) == 3:
fan_points.sort()
......@@ -905,7 +905,7 @@ class PolygonGroup(object):
# is hardly possible, anyway.
for index in range(4):
if index in valid_indices:
final_points.append(Point(*coords[index]))
final_points.append(coords[index])
else:
probe_line = get_line(index - 1, index)
cp = self._get_closest_line_collision(probe_line)
......@@ -913,7 +913,7 @@ class PolygonGroup(object):
else:
for index in range(4):
if index in valid_indices:
final_points.append(Point(*coords[index]))
final_points.append(coords[index])
else:
if ((index + 1) % 4) in valid_indices:
other_index = index + 1
......@@ -925,7 +925,7 @@ class PolygonGroup(object):
elif valid_count == 3:
for index in range(4):
if index in valid_indices:
final_points.append(Point(*coords[index]))
final_points.append(coords[index])
else:
# add two points
for other_index in (index - 1, index + 1):
......@@ -933,7 +933,7 @@ class PolygonGroup(object):
cp = self._get_closest_line_collision(probe_line)
final_points.append(cp)
else:
final_points.extend([Point(*coord) for coord in coords])
final_points.extend(coords)
valid_points = []
for p in final_points:
if not (p is None) and not (p in valid_points):
......@@ -970,17 +970,17 @@ class PolygonGroup(object):
return grid
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):
return None
for poly in self.inner:
if poly.is_point_inside(point):
return None
point = Point(x, y, self.outer.minz)
point = (x, y, self.outer.minz)
line_distances = []
for line in self.lines:
cross_product = line.dir.cross(point.sub(line.p1))
if cross_product.z > 0:
cross_product = pcross(line.dir, psub(point, line.p1))
if cross_product[2] > 0:
close_points = []
close_point = line.closest_point(point)
if not line.is_point_inside(close_point):
......@@ -989,8 +989,8 @@ class PolygonGroup(object):
else:
close_points.append(close_point)
for p in close_points:
direction = point.sub(p)
dist = direction.norm
direction = psub(point, p)
dist = pnorm(direction)
line_distances.append(dist)
elif cross_product.z == 0:
# the point is on the line
......@@ -1012,10 +1012,10 @@ class TriangleOptimizer(object):
def append(self, triangle):
# use a simple tuple instead of an object as the dict's key
normal_coords = triangle.normal.x, triangle.normal.y, triangle.normal.z
if not normal_coords in self.groups:
self.groups[normal_coords] = []
self.groups[normal_coords].append(triangle)
normal = triangle.normal
if not normal in self.groups:
self.groups[normal] = []
self.groups[normal].append(triangle)
def optimize(self):
for group in self.groups.values():
......@@ -1068,7 +1068,7 @@ class Rectangle(IDGenerator, TransformableContainer):
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))
for order in orders:
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])
t2 = Triangle(order[2], order[3], order[0])
if t1.normal == t2.normal == normal:
......@@ -1085,22 +1085,22 @@ class Rectangle(IDGenerator, TransformableContainer):
self.reset_cache()
def reset_cache(self):
self.maxx = max([p.x for p in self.get_points()])
self.minx = max([p.x for p in self.get_points()])
self.maxy = max([p.y for p in self.get_points()])
self.miny = max([p.y for p in self.get_points()])
self.maxz = max([p.z for p in self.get_points()])
self.minz = max([p.z for p in self.get_points()])
self.normal = Triangle(self.p1, self.p2, self.p3).normal.normalized()
self.maxx = max([p[0] for p in self.get_points()])
self.minx = max([p[0] for p in self.get_points()])
self.maxy = max([p[1] for p in self.get_points()])
self.miny = max([p[1] for p in self.get_points()])
self.maxz = max([p[2] for p in self.get_points()])
self.minz = max([p[2] for p in self.get_points()])
self.normal = pnormalized(Triangle(self.p1, self.p2, self.p3).normal)
def get_points(self):
return (self.p1, self.p2, self.p3, self.p4)
def next(self):
yield self.p1
yield self.p2
yield self.p3
yield self.p4
yield "p1"
yield "p2"
yield "p3"
yield "p4"
def __repr__(self):
return "Rectangle%d<%s,%s,%s,%s>" % (self.id, self.p1, self.p2,
......@@ -1132,8 +1132,7 @@ class Rectangle(IDGenerator, TransformableContainer):
if len(unique_vertices) != 2:
log.error("Invalid number of vertices: %s" % unique_vertices)
return None
if abs(unique_vertices[0].sub(unique_vertices[1]).norm - \
shared_vertices[0].sub(shared_vertices[1]).norm) < epsilon:
if abs(pnorm(psub(unique_verticies[0], unique_verticies[1])) - pnorm(psub(shared_vertices[0], shared_vertices[1]))) < epsilon:
try:
return Rectangle(unique_vertices[0], unique_vertices[1],
shared_vertices[0], shared_vertices[1],
......
......@@ -22,7 +22,8 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry import TransformableContainer, IDGenerator
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
#from pycam.Geometry.Line import Line
......@@ -34,11 +35,11 @@ class Plane(IDGenerator, TransformableContainer):
def __init__(self, point, normal=None):
super(Plane, self).__init__()
if normal is None:
normal = Vector(0, 0, 1)
normal = (0, 0, 1, 'v')
self.p = point
self.n = normal
if not isinstance(self.n, Vector):
self.n = self.n.get_vector()
if not len(self.n) > 3:
self.n = (self.n[0], self.n[1], self.n[2], 'v')
def __repr__(self):
return "Plane<%s,%s>" % (self.p, self.n)
......@@ -53,11 +54,11 @@ class Plane(IDGenerator, TransformableContainer):
return cmp(str(self), str(other))
def copy(self):
return self.__class__(self.p.copy(), self.n.copy())
return self.__class__(self.p, self.n)
def next(self):
yield self.p
yield self.n
yield "p"
yield "n"
def get_children_count(self):
# a plane always consists of two points
......@@ -65,21 +66,21 @@ class Plane(IDGenerator, TransformableContainer):
def reset_cache(self):
# we need to prevent the "normal" from growing
norm = self.n.normalized()
norm = pnormalized(self.n)
if norm:
self.n = norm
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
direction = direction.normalized()
direction = pnormalized(direction)
if direction is None:
return (None, INFINITE)
denom = self.n.dot(direction)
denom = pdot(self.n, direction)
if denom == 0:
return (None, INFINITE)
l = -(self.n.dot(point) - self.n.dot(self.p)) / denom
cp = point.add(direction.mul(l))
l = -(pdot(self.n, point) - pdot(self.n, self.p)) / denom
cp = padd(point, pmul(direction, l))
return (cp, l)
def intersect_triangle(self, triangle, counter_clockwise=False):
......@@ -101,7 +102,7 @@ class Plane(IDGenerator, TransformableContainer):
# a distance that is lower than the length of the edge.
if (not cp is None) and (-epsilon < l < edge.len - epsilon):
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)
if abs(dist) < epsilon:
# the edge is on the plane
......@@ -116,8 +117,8 @@ class Plane(IDGenerator, TransformableContainer):
# no further calculation, if the line is zero-sized
if collision_line.len == 0:
return collision_line
cross = self.n.cross(collision_line.dir)
if (cross.dot(triangle.normal) < 0) == bool(not counter_clockwise):
cross = pcross(self.n, collision_line.dir)
if (pdot(cross, triangle.normal) < 0) == bool(not counter_clockwise):
# anti-clockwise direction -> revert the direction of the line
collision_line = Line(collision_line.p2, collision_line.p1)
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/>.
"""
from pycam.Geometry.utils import epsilon
from pycam.Geometry.Point import Point
from pycam.Geometry.kdtree import Node, kdtree
......@@ -37,7 +36,7 @@ class PointKdtree(kdtree):
self.tolerance = tolerance
nodes = []
for p in points:
n = Node(p, (p.x, p.y, p.z))
n = Node(p, p)
nodes.append(n)
kdtree.__init__(self, nodes, cutoff, cutoff_distance)
......@@ -48,7 +47,6 @@ class PointKdtree(kdtree):
return dx*dx+dy*dy+dz*dz
def Point(self, x, y, z):
#return Point(x,y,z)
if self._n:
n = self._n
n.bound = (x, y, z)
......@@ -59,7 +57,7 @@ class PointKdtree(kdtree):
self._n = n
return nn.obj
else:
n.obj = Point(x, y, z)
n.obj = (x, y, z)
self._n = None
self.insert(n)
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))
......@@ -21,11 +21,12 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Plane import Plane
from pycam.Geometry import TransformableContainer, IDGenerator, get_bisector
from pycam.Geometry.utils import number, epsilon
import pycam.Utils.log
from pycam.Utils import log
log = log.get_logger()
# import later to avoid circular imports
#from pycam.Geometry.Model import ContourModel
......@@ -39,9 +40,6 @@ except ImportError:
LINE_WIDTH_INNER = 0.7
LINE_WIDTH_OUTER = 1.3
log = pycam.Utils.log.get_logger()
class PolygonInTree(IDGenerator):
""" This class is a wrapper around Polygon objects that is used for sorting.
"""
......@@ -73,7 +71,7 @@ class PolygonInTree(IDGenerator):
pass
def get_cost(self, other):
return other.start.sub(self.end).norm
return pnorm(psub(other.start, self.end))
class PolygonPositionSorter(object):
......@@ -218,7 +216,7 @@ class Polygon(TransformableContainer):
super(Polygon, self).__init__()
if plane is None:
# 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._points = []
self.is_closed = False
......@@ -252,8 +250,7 @@ class Polygon(TransformableContainer):
self._update_limits(line.p2)
elif self._points[-1] == line.p1:
# the new Line can be added to the end of the polygon
if line.dir == self._points[-1].sub(
self._points[-2]).normalized():
if line.dir == pnormalized(psub(self._points[-1], self._points[-2])):
# Remove the last point, if the previous point combination
# is in line with the new Line. This avoids unnecessary
# points on straight lines.
......@@ -267,9 +264,7 @@ class Polygon(TransformableContainer):
self.reset_cache()
else:
# the new Line can be added to the beginning of the polygon
if (len(self._points) > 1) \
and (line.dir == self._points[1].sub(
self._points[0]).normalized()):
if (len(self._points) > 1) and (line.dir == pnormalized(psub(self._points[1], self._points[0]))):
# Avoid points on straight lines - see above.
self._points.pop(0)
if line.p1 != self._points[-1]:
......@@ -325,8 +320,7 @@ class Polygon(TransformableContainer):
return False
def next(self):
for point in self._points:
yield point
yield "_points"
yield self.plane
def get_children_count(self):
......@@ -350,11 +344,11 @@ class Polygon(TransformableContainer):
for index in range(len(self._points)):
p1 = self._points[index]
p2 = self._points[(index + 1) % len(self._points)]
value[0] += p1.y * p2.z - p1.z * p2.y
value[1] += p1.z * p2.x - p1.x * p2.z
value[2] += p1.x * p2.y - p1.y * p2.x
result = self.plane.n.x * value[0] + self.plane.n.y * value[1] \
+ self.plane.n.z * value[2]
value[0] += p1[1] * p2[2] - p1[2] * p2[1]
value[1] += p1[2] * p2[0] - p1[0] * p2[2]
value[2] += p1[0] * p2[1] - p1[1] * p2[0]
result = self.plane.n[0] * value[0] + self.plane.n[1] * value[1] \
+ self.plane.n[2] * value[2]
self._area_cache = result / 2
return self._area_cache
......@@ -368,26 +362,23 @@ class Polygon(TransformableContainer):
for index in range(len(self._points)):
p1 = self._points[index]
p2 = self._points[(index + 1) % len(self._points)]
cxy += (p1.x + p2.x) * (p1.x * p2.y - p1.y * p2.x)
cxz += (p1.x + p2.x) * (p1.x * p2.z - p1.z * p2.x)
cyx += (p1.y + p2.y) * (p1.x * p2.y - p1.y * p2.x)
cyz += (p1.y + p2.y) * (p1.y * p2.z - p1.z * p2.y)
czx += (p1.z + p2.z) * (p1.z * p2.x - p1.x * p2.z)
czy += (p1.z + p2.z) * (p1.y * p2.z - p1.z * p2.y)
cxy += (p1[0] + p2[0]) * (p1[0] * p2[1] - p1[1] * p2[0])
cxz += (p1[0] + p2[0]) * (p1[0] * p2[2] - p1[2] * p2[0])
cyx += (p1[1] + p2[1]) * (p1[0] * p2[1] - p1[1] * p2[0])
cyz += (p1[1] + p2[1]) * (p1[1] * p2[2] - p1[2] * p2[1])
czx += (p1[2] + p2[2]) * (p1[2] * p2[0] - p1[0] * p2[2])
czy += (p1[2] + p2[2]) * (p1[1] * p2[2] - p1[2] * p2[1])
if abs(self.maxz - self.minz) < epsilon:
return Point(cxy / (6 * area), cyx / (6 * area), self.minz)
return (cxy / (6 * area), cyx / (6 * area), self.minz)
elif abs(self.maxy - self.miny) < epsilon:
return Point(cxz / (6 * area), self.miny, czx / (6 * area))
return (cxz / (6 * area), self.miny, czx / (6 * area))
elif abs(self.maxx - self.minx) < epsilon:
return Point(self.minx, cyz / (6 * area), czy / (6 * area))
return (self.minx, cyz / (6 * area), czy / (6 * area))
else:
# calculate area of xy projection
poly_xy = self.get_plane_projection(Plane(Point(0, 0, 0),
Point(0, 0, 1)))
poly_xz = self.get_plane_projection(Plane(Point(0, 0, 0),
Point(0, 1, 0)))
poly_yz = self.get_plane_projection(Plane(Point(0, 0, 0),
Point(1, 0, 0)))
poly_xy = self.get_plane_projection(Plane((0, 0, 0),(0, 0, 1)))
poly_xz = self.get_plane_projection(Plane((0, 0, 0),(0, 1, 0)))
poly_yz = self.get_plane_projection(Plane((0, 0, 0),(1, 0, 0)))
if (poly_xy is None) or (poly_xz is None) or (poly_yz is None):
log.warn("Invalid polygon projection for barycenter: %s" \
% str(self))
......@@ -421,16 +412,14 @@ class Polygon(TransformableContainer):
or (not self.is_closed and index == len(self._points) - 1):
return None
else:
return self._points[index].add(self._points[(index + 1) % \
len(self._points)]).div(2)
return pdiv(padd(self._points[index], self._points[(index + 1) % len(self._points)]), 2)
def get_lengths(self):
result = []
for index in range(len(self._points) - 1):
result.append(self._points[index + 1].sub(
self._points[index]).norm)
result.append(pnorm(psub(self._points[index + 1], self._points[index])))
if self.is_closed:
result.append(self._points[0].sub(self._points[-1]).norm)
result.append(pnorm(psub(self._points[0], self._points[-1])))
return result
def get_max_inside_distance(self):
......@@ -438,12 +427,12 @@ class Polygon(TransformableContainer):
"""
if len(self._points) < 2:
return None
distance = self._points[1].sub(self._points[0]).norm
distance = pnorm(psub(self._points[1], self._points[0]))
for p1 in self._points:
for p2 in self._points:
if p1 is p2:
continue
distance = max(distance, p2.sub(p1).norm)
distance = max(distance, pnorm(psub(p2, p1)))
return distance
def is_outer(self):
......@@ -474,8 +463,7 @@ class Polygon(TransformableContainer):
if not self.is_closed:
return False
# First: check if the point is within the boundary of the polygon.
if not p.is_inside(self.minx, self.maxx, self.miny, self.maxy,
self.minz, self.maxz):
if not pis_inside(p, self.minx, self.maxx, self.miny, self.maxy, self.minz, self.maxz):
# the point is outside the rectangle boundary
return False
# see http://www.alienryderflex.com/polygon/
......@@ -491,14 +479,14 @@ class Polygon(TransformableContainer):
# the y level of the point. This solves the problem of intersections
# through shared vertices or lines that go along the y level of the
# point.
if ((p1.y < p.y) and (p.y <= p2.y)) \
or ((p2.y < p.y) and (p.y <= p1.y)):
part_y = (p.y - p1.y) / (p2.y - p1.y)
intersection_x = p1.x + part_y * (p2.x - p1.x)
if intersection_x < p.x + epsilon:
if ((p1[1] < p[1]) and (p[1] <= p2[1])) \
or ((p2[1] < p[1]) and (p[1] <= p1[1])):
part_y = (p[1] - p1[1]) / (p2[1] - p1[1])
intersection_x = p1[0] + part_y * (p2[0] - p1[0])
if intersection_x < p[0] + epsilon:
# count intersections to the left
intersection_count_left += 1
if intersection_x > p.x - epsilon:
if intersection_x > p[0] - epsilon:
# count intersections to the right
intersection_count_right += 1
# odd intersection count -> inside
......@@ -548,7 +536,7 @@ class Polygon(TransformableContainer):
GL.glLineWidth(LINE_WIDTH_OUTER)
GL.glBegin(GL.GL_LINE_LOOP)
for point in self._points:
GL.glVertex3f(point.x, point.y, point.z)
GL.glVertex3f(point[0], point[1], point[2])
GL.glEnd()
if not is_outer:
GL.glColor(*color)
......@@ -560,19 +548,19 @@ class Polygon(TransformableContainer):
def _update_limits(self, point):
if self.minx is None:
self.minx = point.x
self.maxx = point.x
self.miny = point.y
self.maxy = point.y
self.minz = point.z
self.maxz = point.z
self.minx = point[0]
self.maxx = point[0]
self.miny = point[1]
self.maxy = point[1]
self.minz = point[2]
self.maxz = point[2]
else:
self.minx = min(self.minx, point.x)
self.maxx = max(self.maxx, point.x)
self.miny = min(self.miny, point.y)
self.maxy = max(self.maxy, point.y)
self.minz = min(self.minz, point.z)
self.maxz = max(self.maxz, point.z)
self.minx = min(self.minx, point[0])
self.maxx = max(self.maxx, point[0])
self.miny = min(self.miny, point[1])
self.maxy = max(self.maxy, point[1])
self.minz = min(self.minz, point[2])
self.maxz = max(self.maxz, point[2])
self._lines_cache = None
self._area_cache = None
......@@ -596,12 +584,12 @@ class Polygon(TransformableContainer):
def get_shifted_vertex(index, offset):
p1 = self._points[index]
p2 = self._points[(index + 1) % len(self._points)]
cross_offset = p2.sub(p1).cross(self.plane.n).normalized()
cross_offset = pnormalized(pcross(psub(p2, p1), self.plane.n))
bisector_normalized = self.get_bisector(index)
factor = cross_offset.dot(bisector_normalized)
factor = pdot(cross_offset, bisector_normalized)
if factor != 0:
bisector_sized = bisector_normalized.mul(offset / factor)
return p1.add(bisector_sized)
bisector_sized = pmul(bisector_normalized, offset / factor)
return padd(p1, bisector_sized)
else:
return p2
if offset * 2 >= self.get_max_inside_distance():
......@@ -613,7 +601,7 @@ class Polygon(TransformableContainer):
max_dist = 1000 * epsilon
def test_point_near(p, others):
for o in others:
if p.sub(o).norm < max_dist:
if pnorm(psub(p, o)) < max_dist:
return True
return False
reverse_lines = []
......@@ -622,12 +610,11 @@ class Polygon(TransformableContainer):
next_index = (index + 1) % len(points)
p1 = points[index]
p2 = points[next_index]
diff = p2.sub(p1)
old_dir = self._points[next_index].sub(
self._points[index]).normalized()
if diff.normalized() != old_dir:
diff = psub(p2, p1)
old_dir = pnormalized(psub(self._points[next_index], self._points[index]))
if pnormalized(diff) != old_dir:
# the direction turned around
if diff.norm > max_dist:
if pnorm(diff) > max_dist:
# the offset was too big
return None
else:
......@@ -658,7 +645,7 @@ class Polygon(TransformableContainer):
# no lines are left
print "out 2"
return []
if prev_line.p2.sub(next_line.p1).norm > max_dist:
if pnorm(psub(prev_line.p2, next_line.p1)) > max_dist:
cp, dist = prev_line.get_intersection(next_line)
else:
cp = prev_line.p2
......@@ -705,9 +692,8 @@ class Polygon(TransformableContainer):
# maybe we have been here before
if not cp in split_points:
split_points.append(cp)
elif (cp.sub(line.p1).norm < max_dist) \
or (cp.sub(line.p2).norm < max_dist):
if cp.sub(line.p1).norm < cp.sub(line.p2).norm:
elif (pnorm(psub(cp, line.p1)) < max_dist) or (pnorm(psub(cp, line.p2)) < max_dist):
if pnorm(psub(cp, lines.p1)) < pnorm(psub(cp, line.p2)):
non_reversed[index] = Line(cp, line.p2)
else:
non_reversed[index] = Line(line.p1, cp)
......@@ -783,7 +769,6 @@ class Polygon(TransformableContainer):
if len(group) <= 2:
continue
poly = Polygon(self.plane)
#print "**************************************"
for line in group:
try:
poly.append(line)
......@@ -863,12 +848,12 @@ class Polygon(TransformableContainer):
def get_shifted_vertex(index, offset):
p1 = self._points[index]
p2 = self._points[(index + 1) % len(self._points)]
cross_offset = p2.sub(p1).cross(self.plane.n).normalized()
cross_offset = pnormalized(pcross(psub(p2, p1), self.plane.n))
bisector_normalized = self.get_bisector(index)
factor = cross_offset.dot(bisector_normalized)
factor = pdot(cross_offset, bisector_normalized)
if factor != 0:
bisector_sized = bisector_normalized.mul(offset / factor)
return p1.add(bisector_sized)
bisector_sized = pmul(bisector_normalized, offset / factor)
return padd(p1, bisector_sized)
else:
return p2
def simplify_polygon_intersections(lines):
......@@ -896,8 +881,7 @@ class Polygon(TransformableContainer):
line1 = new_group[index1]
line2 = new_group[index2]
intersection, factor = line1.get_intersection(line2)
if intersection and (intersection != line1.p1) \
and (intersection != line1.p2):
if intersection and (pnorm(psub(intersection, line1.p1)) > epsilon) and (pnorm(psub(intersection, line1.p2)) > epsilon):
del new_group[index1]
new_group.insert(index1,
Line(line1.p1, intersection))
......@@ -911,7 +895,7 @@ class Polygon(TransformableContainer):
if not index1 + 1 in group_starts:
group_starts.append(index1 + 1)
# don't update index2 -> maybe there are other hits
elif intersection and (intersection == line1.p1):
elif intersection and (pnorm(psub(intersection, line1.p1)) < epsilon):
if not index1 in group_starts:
group_starts.append(index1)
index2 += 1
......@@ -929,6 +913,7 @@ class Polygon(TransformableContainer):
for group_start in group_starts:
groups.append(new_group[last_start:group_start])
last_start = group_start
# Add the remaining lines to the first group or as a new
# group.
if groups[0][0].p1 == new_group[-1].p2:
......@@ -979,9 +964,9 @@ class Polygon(TransformableContainer):
for index in range(len(self._points)):
points.append(get_shifted_vertex(index, offset))
new_lines = []
for index in range(len(points)):
for index in range(len(points) - 1):
p1 = points[index]
p2 = points[(index + 1) % len(points)]
p2 = points[(index + 1)]
new_lines.append(Line(p1, p2))
if callback and callback():
return None
......@@ -1003,21 +988,7 @@ class Polygon(TransformableContainer):
group = Polygon(self.plane)
for line in lines:
group.append(line)
if group.is_outer() != self_is_outer:
# We ignore groups that changed the direction. These
# parts of the original group are flipped due to the
# offset.
log.debug("Ignoring reversed polygon: %s / %s" % \
(self.get_area(), group.get_area()))
continue
# Remove polygons that should be inside the original,
# but due to float inaccuracies they are not.
if ((self.is_outer() and (offset < 0)) \
or (not self.is_outer() and (offset > 0))) \
and (not self.is_polygon_inside(group)):
log.debug("Ignoring inaccurate polygon: %s / %s" \
% (self.get_area(), group.get_area()))
continue
groups.append(group)
if not groups:
log.debug("Skipping offset polygon: toggled polygon removed")
......@@ -1044,15 +1015,14 @@ class Polygon(TransformableContainer):
if offset == 0:
return Line(line.p1, line.p2)
else:
cross_offset = line.dir.cross(self.plane.n).normalized().mul(offset)
cross_offset = pmul(pnormalized(pcross(line.dir, self.plane.n)), offset)
# Prolong the line at the beginning and at the end - to allow
# overlaps. Use factor "2" to take care for star-like structure
# where a complete convex triangle would get cropped (two lines
# get lost instead of just one). Use the "abs" value to
# compensate negative offsets.
in_line = line.dir.mul(2 * abs(offset))
return Line(line.p1.add(cross_offset).sub(in_line),
line.p2.add(cross_offset).add(in_line))
in_line = pmul(line.dir, 2 * abs(offset))
return Line(psub(padd(line.p1, cross_offset), in_line), padd(padd(line.p2, cross_offset), in_line))
def do_lines_intersection(l1, l2):
""" calculate the new intersection between two neighbouring lines
"""
......@@ -1064,12 +1034,12 @@ class Polygon(TransformableContainer):
# one line was already marked as obsolete
return
x1, x2, x3, x4 = l2.p1, l2.p2, l1.p1, l1.p2
a = x2.sub(x1)
b = x4.sub(x3)
c = x3.sub(x1)
a = psub(x2, x1)
b = psub(x4, x3)
c = psub(x3, x1)
# see http://mathworld.wolfram.com/Line-LineIntersection.html (24)
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))
except ZeroDivisionError:
l2.p1 = None
return
......@@ -1077,7 +1047,7 @@ class Polygon(TransformableContainer):
# The intersection is always supposed to be within p1 and p2.
l2.p1 = None
else:
intersection = x1.add(a.mul(factor))
intersection = padd(x1, pmul(a, factor))
if Line(l1.p1, intersection).dir != l1.dir:
# Remove lines that would change their direction due to the
# new intersection. These are usually lines that become
......@@ -1320,12 +1290,12 @@ class Polygon(TransformableContainer):
for index in range(len(collisions) - 1):
p1 = collisions[index][0]
p2 = collisions[index + 1][0]
if p1.sub(p2).norm < epsilon:
if pnorm(psub(p1, p2)) < epsilon:
# ignore zero-length lines
continue
# Use the middle between p1 and p2 to check the
# inner/outer state.
p_middle = p1.add(p2).div(2)
p_middle = pdiv(padd(p1, p2), 2)
p_inside = poly2.is_point_inside(p_middle) \
and not poly2.is_point_on_outline(p_middle)
if not p_inside:
......@@ -1365,12 +1335,12 @@ class Polygon(TransformableContainer):
intersections.sort(key=lambda (cp, d): d)
intersections.insert(0, (proj_line.p1, 0))
intersections.append((proj_line.p2, 1))
get_original_point = lambda d: line.p1.add(line.vector.mul(d))
get_original_point = lambda d: padd(line.p1, pmul(line.vector, d))
for index in range(len(intersections) - 1):
p1, d1 = intersections[index]
p2, d2 = intersections[index + 1]
if p1 != p2:
middle = p1.add(p2).div(2)
middle = pdiv(padd(p1, p2), 2)
new_line = Line(get_original_point(d1), get_original_point(d2))
if self.is_point_inside(middle):
inner.append(new_line)
......
......@@ -80,7 +80,7 @@ class PolygonExtractor(object):
print "points=", path.points
i = 0
while i < len(path.points)-1:
if path.points[i].x > path.points[i+1].x:
if path.points[i][0] > path.points[i+1][0]:
if DEBUG_POLYGONEXTRACTOR2:
print "drop point %d:" % path.points[i].id
path.points = path.points[:i] + path.points[i+1:]
......@@ -95,7 +95,7 @@ class PolygonExtractor(object):
print "%d:" % path.id,
print "%d ->" % path.top_join.id
for point in path.points:
print "%d(%g,%g)" % (point.id, point.x, point.y),
print "%d(%g,%g)" % (point.id, point[0], point[1]),
print "->%d" % path.bot_join.id
path_list = []
......@@ -133,7 +133,7 @@ class PolygonExtractor(object):
for path in path_list:
print "path %d(w=%d): " % (path.id, path.winding),
for point in path.points:
print "%d(%g,%g)" % (point.id, point.x, point.y),
print "%d(%g,%g)" % (point.id, point[0], point[1]),
print
if self.current_dir == 0:
......@@ -156,13 +156,13 @@ class PolygonExtractor(object):
self.svg.fill("red")
else:
self.svg.fill("blue")
self.svg.AddDot(p.x, p.y)
self.svg.AddText(p.x, p.y, str(p.id))
self.svg.AddDot(p[0], p[1])
self.svg.AddText(p[0], p[1], str(p.id))
if prev:
self.svg.AddLine(p.x, p.y, prev.x, prev.y)
self.svg.AddLine(p[0], p[1], prev[0], prev[1])
prev = p
p = path.points[0]
self.svg.AddLine(p.x, p.y, prev.x, prev.y)
self.svg.AddLine(p[0], p[1], prev[0], prev[1])
self.svg.close()
self.cont.close()
......@@ -176,8 +176,8 @@ class PolygonExtractor(object):
def append(self, p):
if DEBUG_POLYGONEXTRACTOR3:
p.dir = self.current_dir
self.svg.AddDot(p.x, p.y)
self.svg.AddText(p.x, p.y, str(p.id))
self.svg.AddDot(p[0], p[1])
self.svg.AddText(p[0], p[1], str(p.id))
self.curr_line.append(p)
def end_scanline(self):
......@@ -187,7 +187,7 @@ class PolygonExtractor(object):
if self.policy == PolygonExtractor.CONTOUR and self.hor_path_list:
next_x = -INFINITE
if len(self.curr_line) > 0:
next_x = self.curr_line[0].x
next_x = self.curr_line[0][0]
self.delta_x = next_x - self.last_x
self.last_x = next_x
else:
......@@ -204,7 +204,7 @@ class PolygonExtractor(object):
inside = False
s = ""
for point in scanline:
next_x = point.x
next_x = point[0]
if inside:
s += "*" * int(next_x - last)
else:
......@@ -217,17 +217,17 @@ class PolygonExtractor(object):
print "active paths: ",
for path in self.curr_path_list:
print "%d(%g,%g)" \
% (path.id, path.points[-1].x, path.points[-1].y),
% (path.id, path.points[-1][0], path.points[-1][1]),
print
print "prev points: ",
for point in self.prev_line:
print "(%g,%g)" % (point.x, point.y),
print "(%g,%g)" % (point[0], point[1]),
print
print "active points: ",
for point in scanline:
print "%d(%g,%g)" % (point.id, point.x, point.y),
print "%d(%g,%g)" % (point.id, point[0], point[1]),
print
prev_point = Iterator(self.prev_line)
......@@ -246,13 +246,13 @@ class PolygonExtractor(object):
p0 = Path()
p0.winding = winding + 1
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g)" % (p0.id, c0.x, c0.y)
print "new path %d(%g,%g)" % (p0.id, c0[0], c0[1])
p0.append(c0)
self.curr_path_list.append(p0)
p1 = Path()
p1.winding = winding
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g)" % (p1.id, c1.x, c1.y)
print "new path %d(%g,%g)" % (p1.id, c1[0], c1[1])
p1.append(c1)
self.curr_path_list.append(p1)
p0.top_join = p1
......@@ -282,16 +282,16 @@ class PolygonExtractor(object):
c1 = curr_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "overlap test: p0=%g p1=%g" % (p0.x, p1.x)
print "overlap test: c0=%g c1=%g" % (c0.x, c1.x)
print "overlap test: p0=%g p1=%g" % (p0[0], p1[0])
print "overlap test: c0=%g c1=%g" % (c0[0], c1[0])
if c1.x < p0.x:
if c1[0] < p0[0]:
# new segment is completely to the left
# new path starts
s0 = Path()
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g) w=%d" \
% (s0.id, c0.x, c0.y, winding + 1)
% (s0.id, c0[0], c0[0], winding + 1)
s0.append(c0)
curr_path.insert(s0)
s1 = Path()
......@@ -299,14 +299,14 @@ class PolygonExtractor(object):
s1.winding = winding
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g) w=%d" \
% (s1.id, c1.x, c1.y, winding)
% (s1.id, c1[0], c1[1], winding)
s1.append(c1)
curr_path.insert(s1)
curr_point.next()
curr_point.next()
s0.top_join = s1
s1.top_join = s0
elif c0.x > p1.x:
elif c0[0] > p1[0]:
# new segment is completely to the right
# old path ends
s0 = curr_path.takeNext()
......@@ -342,9 +342,9 @@ class PolygonExtractor(object):
p2 = prev_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "join test: p0=%g p1=%g p2=%g" \
% (p0.x, p1.x, p2.x)
print "join test: c0=%g c1=%g" % (c0.x, c1.x)
if p2.x <= c1.x:
% (p0[0], p1[0], p2[0])
print "join test: c0=%g c1=%g" % (c0[0], c1[0])
if p2[0] <= c1[0]:
overlap_p = True
if self.policy == PolygonExtractor.CONTOUR:
s0 = curr_path.takeNext()
......@@ -384,10 +384,10 @@ class PolygonExtractor(object):
if curr_point.remains()>=2:
c2 = curr_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "split test: p0=%g p1=%g" % (p0.x, p1.x)
print "split test: p0=%g p1=%g" % (p0[0], p1[0])
print "split test: c0=%g c1=%g c2=%g" \
% (c0.x, c1.x, c2.x)
if c2.x <= p1.x:
% (c0[0], c1[0], c2[0])
if c2[0] <= p1[0]:
overlap_c = True
s0 = Path()
s1 = Path()
......@@ -419,14 +419,14 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR:
print "add to path %d(%g,%g)" \
% (left_path.id, left_point.x, left_point.y)
% (left_path.id, left_point[0], left_point[1])
left_path.append(left_point)
right_path.append(right_point)
if right_path == curr_path.peek():
curr_path.next()
if DEBUG_POLYGONEXTRACTOR:
print "add to path %d(%g,%g)" \
% (right_path.id, right_point.x, right_point.y)
% (right_path.id, right_point[0], right_point[1])
winding = right_path.winding
prev_point.next()
curr_point.next()
......@@ -434,8 +434,8 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR:
print "active paths: ",
for path in self.curr_path_list:
print "%d(%g,%g,w=%d)" % (path.id, path.points[-1].x,
path.points[-1].y, path.winding),
print "%d(%g,%g,w=%d)" % (path.id, path.points[-1][0],
path.points[-1][1], path.winding),
print
self.prev_line = scanline
......@@ -448,11 +448,11 @@ class PolygonExtractor(object):
self.cont.fill("red")
else:
self.cont.fill("blue")
self.cont.AddDot(p.x, p.y)
self.cont.AddDot(p[0], p[1])
self.cont.fill("black")
self.cont.AddText(p.x, p.y, str(p.id))
self.cont.AddText(p[0], p[1], str(p.id))
if prev:
self.cont.AddLine(prev.x, prev.y, p.x, p.y)
self.cont.AddLine(prev[0], prev[1], p[0], p[1])
prev = p
if DEBUG_POLYGONEXTRACTOR:
......@@ -460,7 +460,7 @@ class PolygonExtractor(object):
inside = False
s = ""
for point in scanline:
next_y = point.y
next_y = point[1]
if inside:
s += "*" * int(next_y - last)
else:
......@@ -473,17 +473,17 @@ class PolygonExtractor(object):
print "active paths: ",
for path in self.curr_path_list:
print "%d(%g,%g)" \
% (path.id, path.points[-1].x, path.points[-1].y),
% (path.id, path.points[-1][0], path.points[-1][1]),
print
print "prev points: ",
for point in self.prev_line:
print "(%g,%g)" % (point.x, point.y),
print "(%g,%g)" % (point[0], point[1]),
print
print "active points: ",
for point in scanline:
print "%d(%g,%g)" % (point.id, point.x, point.y),
print "%d(%g,%g)" % (point.id, point[0], point[1]),
print
prev_point = Iterator(self.prev_line)
......@@ -502,13 +502,13 @@ class PolygonExtractor(object):
p0 = Path()
p0.winding = winding + 1
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g)" % (p0.id, c0.x, c0.y)
print "new path %d(%g,%g)" % (p0.id, c0[0], c0[1])
p0.append(c0)
self.curr_path_list.append(p0)
p1 = Path()
p1.winding = winding
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g)" % (p1.id, c1.x, c1.y)
print "new path %d(%g,%g)" % (p1.id, c1[0], c1[1])
p1.append(c1)
self.curr_path_list.append(p1)
p0.top_join = p1
......@@ -538,16 +538,16 @@ class PolygonExtractor(object):
c1 = curr_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "overlap test: p0=%g p1=%g" % (p0.x, p1.x)
print "overlap test: c0=%g c1=%g" % (c0.x, c1.x)
print "overlap test: p0=%g p1=%g" % (p0[0], p1[0])
print "overlap test: c0=%g c1=%g" % (c0[0], c1[0])
if c1.y < p0.y:
if c1[1] < p0[1]:
# new segment is completely to the left
# new path starts
s0 = Path()
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g) w=%d" \
% (s0.id, c0.x, c0.y, winding + 1)
% (s0.id, c0[0], c0[1], winding + 1)
s0.append(c0)
curr_path.insert(s0)
s1 = Path()
......@@ -555,14 +555,14 @@ class PolygonExtractor(object):
s1.winding = winding
if DEBUG_POLYGONEXTRACTOR:
print "new path %d(%g,%g) w=%d" \
% (s1.id, c1.x, c1.y, winding)
% (s1.id, c1[0], c1[1], winding)
s1.append(c1)
curr_path.insert(s1)
curr_point.next()
curr_point.next()
s0.top_join = s1
s1.top_join = s0
elif c0.y > p1.y:
elif c0[1] > p1[1]:
# new segment is completely to the right
# old path ends
s0 = curr_path.takeNext()
......@@ -598,9 +598,9 @@ class PolygonExtractor(object):
p2 = prev_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "join test: p0=%g p1=%g p2=%g" \
% (p0.x, p1.x, p2.x)
print "join test: c0=%g c1=%g" % (c0.x, c1.x)
if p2.y <= c1.y:
% (p0[0], p1[0], p2[0])
print "join test: c0=%g c1=%g" % (c0[0], c1[0])
if p2[1] <= c1[1]:
overlap_p = True
if self.policy == PolygonExtractor.CONTOUR:
s0 = curr_path.takeNext()
......@@ -640,10 +640,10 @@ class PolygonExtractor(object):
if curr_point.remains()>=2:
c2 = curr_point.peek(1)
if DEBUG_POLYGONEXTRACTOR:
print "split test: p0=%g p1=%g" % (p0.x, p1.x)
print "split test: p0=%g p1=%g" % (p0[0], p1[0])
print "split test: c0=%g c1=%g c2=%g" \
% (c0.x, c1.x, c2.x)
if c2.y <= p1.y:
% (c0[0], c1[0], c2[0])
if c2[1] <= p1[1]:
overlap_c = True
s0 = Path()
s1 = Path()
......@@ -675,14 +675,14 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR:
print "add to path %d(%g,%g)" \
% (left_path.id, left_point.x, left_point.y)
% (left_path.id, left_point[0], left_point[1])
left_path.append(left_point)
right_path.append(right_point)
if right_path == curr_path.peek():
curr_path.next()
if DEBUG_POLYGONEXTRACTOR:
print "add to path %d(%g,%g)" \
% (right_path.id, right_point.x, right_point.y)
% (right_path.id, right_point[0], right_point[1])
winding = right_path.winding
prev_point.next()
curr_point.next()
......@@ -690,8 +690,8 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR:
print "active paths: ",
for path in self.curr_path_list:
print "%d(%g,%g,w=%d)" % (path.id, path.points[-1].x,
path.points[-1].y, path.winding),
print "%d(%g,%g,w=%d)" % (path.id, path.points[-1][0],
path.points[-1][1], path.winding),
print
self.prev_line = scanline
......@@ -702,26 +702,26 @@ class PolygonExtractor(object):
hor_path_list = []
for s in self.hor_path_list:
allsame = True
miny = s.points[0].y
maxy = s.points[0].y
miny = s.points[0][1]
maxy = s.points[0][1]
for p in s.points:
if not p.x == s.points[0].x:
if not p[0] == s.points[0][0]:
allsame = False
if p.y < miny:
miny = p.y
if p.y > maxy:
maxy = p.y
if p[1] < miny:
miny = p[1]
if p[1] > maxy:
maxy = p[1]
if allsame:
if DEBUG_POLYGONEXTRACTOR2:
print "all same !"
s0 = Path()
for p in s.points:
if p.y == miny:
if p[1] == miny:
s0.append(p)
hor_path_list.append(s0)
s1 = Path()
for p in s.points:
if p.y == maxy:
if p[1] == maxy:
s1.append(p)
hor_path_list.append(s1)
continue
......@@ -729,28 +729,28 @@ class PolygonExtractor(object):
p_iter = CyclicIterator(s.points)
p = s.points[0]
next_p = p_iter.next()
while not ((prev.x >= p.x) and (next_p.x > p.x)):
while not ((prev[0] >= p[0]) and (next_p[0] > p[0])):
p = next_p
next_p = p_iter.next()
count = 0
while count < len(s.points):
s0 = Path()
while next_p.x >= p.x:
while next_p[0] >= p[0]:
s0.append(p)
p = next_p
next_p = p_iter.next()
count += 1
s0.append(p)
while (len(s0.points) > 1) \
and (s0.points[0].x == s0.points[1].x):
and (s0.points[0][0] == s0.points[1][0]):
s0.points = s0.points[1:]
while (len(s0.points) > 1) \
and (s0.points[-2].x == s0.points[-1].x):
and (s0.points[-2][0] == s0.points[-1][0]):
s0.points = s0.points[0:-1]
hor_path_list.append(s0)
s1 = Path()
while next_p.x <= p.x:
while next_p[0] <= p[0]:
s1.append(p)
p = next_p
next_p = p_iter.next()
......@@ -758,13 +758,13 @@ class PolygonExtractor(object):
s1.append(p)
s1.reverse()
while (len(s1.points) > 1) \
and (s1.points[0].x == s1.points[1].x):
and (s1.points[0][0] == s1.points[1][0]):
s1.points = s1.points[1:]
while (len(s1.points) > 1) \
and (s1.points[-2].x == s1.points[-1].x):
and (s1.points[-2][0] == s1.points[-1][0]):
s1.points = s1.points[:-1]
hor_path_list.append(s1)
hor_path_list.sort(cmp=lambda a, b: cmp(a.points[0].x, b.points[0].x))
hor_path_list.sort(cmp=lambda a, b: cmp(a.points[0][0], b.points[0][0]))
if DEBUG_POLYGONEXTRACTOR2:
print "ver_hor_path_list = ", hor_path_list
for s in hor_path_list:
......@@ -785,12 +785,12 @@ class PolygonExtractor(object):
next_x = INFINITE
if self.ver_hor_path_list \
and (self.ver_hor_path_list[0].points[0].x < next_x):
next_x = self.ver_hor_path_list[0].points[0].x
and (self.ver_hor_path_list[0].points[0][0] < next_x):
next_x = self.ver_hor_path_list[0].points[0][0]
if self.act_hor_path_list \
and (self.act_hor_path_list[0].points[0].x < next_x):
next_x = self.act_hor_path_list[0].points[0].x
and (self.act_hor_path_list[0].points[0][0] < next_x):
next_x = self.act_hor_path_list[0].points[0][0]
if next_x >= _next_x:
return
......@@ -801,13 +801,13 @@ class PolygonExtractor(object):
print "next_x =", next_x
if self.ver_hor_path_list \
and (self.ver_hor_path_list[0].points[0].x <= next_x):
and (self.ver_hor_path_list[0].points[0][0] <= next_x):
while self.ver_hor_path_list \
and (self.ver_hor_path_list[0].points[0].x <= next_x):
and (self.ver_hor_path_list[0].points[0][0] <= next_x):
self.act_hor_path_list.append(self.ver_hor_path_list[0])
self.ver_hor_path_list = self.ver_hor_path_list[1:]
self.act_hor_path_list.sort(cmp=lambda a, b:
cmp(a.points[0].x, b.points[0].x))
cmp(a.points[0][0], b.points[0][0]))
scanline = []
i = 0
......@@ -816,7 +816,7 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR2:
print "s =", s
scanline.append(s.points[0])
if s.points[0].x <= next_x:
if s.points[0][0] <= next_x:
if len(s.points) <= 1:
if DEBUG_POLYGONEXTRACTOR2:
print "remove list"
......@@ -830,17 +830,17 @@ class PolygonExtractor(object):
if DEBUG_POLYGONEXTRACTOR2:
print "remove point", s.points[0]
s.points = s.points[1:]
if len(s.points)> 0 and s.points[0].x == next_x:
if len(s.points)> 0 and s.points[0][0] == next_x:
# TODO: the variable "repeat" is never used.
# Any idea?
repeat = True
i += 1
self.act_hor_path_list.sort(cmp=lambda a, b:
cmp(a.points[0].x, b.points[0].x))
cmp(a.points[0][0], b.points[0][0]))
if len(scanline) == 0:
return
scanline.sort(cmp=lambda a, b: cmp(a.y, b.y))
scanline.sort(cmp=lambda a, b: cmp(a[1], b[1]))
if DEBUG_POLYGONEXTRACTOR2:
print "scanline' =", scanline
print "ver_hor_path_list =", self.ver_hor_path_list
......
......@@ -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/>.
"""
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Plane import Plane
from pycam.Geometry.Line import Line
from pycam.Geometry import TransformableContainer, IDGenerator
......@@ -53,55 +53,46 @@ class Triangle(IDGenerator, TransformableContainer):
self.reset_cache()
def reset_cache(self):
self.minx = min(self.p1.x, self.p2.x, self.p3.x)
self.miny = min(self.p1.y, self.p2.y, self.p3.y)
self.minz = min(self.p1.z, self.p2.z, self.p3.z)
self.maxx = max(self.p1.x, self.p2.x, self.p3.x)
self.maxy = max(self.p1.y, self.p2.y, self.p3.y)
self.maxz = max(self.p1.z, self.p2.z, self.p3.z)
self.minx = min(self.p1[0], self.p2[0], self.p3[0])
self.miny = min(self.p1[1], self.p2[1], self.p3[1])
self.minz = min(self.p1[2], self.p2[2], self.p3[2])
self.maxx = max(self.p1[0], self.p2[0], self.p3[0])
self.maxy = max(self.p1[1], self.p2[1], self.p3[1])
self.maxz = max(self.p1[2], self.p2[2], self.p3[2])
self.e1 = Line(self.p1, self.p2)
self.e2 = Line(self.p2, self.p3)
self.e3 = Line(self.p3, self.p1)
# calculate normal, if p1-p2-pe are in clockwise order
if self.normal is None:
self.normal = self.p3.sub(self.p1).cross(self.p2.sub( \
self.p1)).normalized()
if not isinstance(self.normal, Vector):
self.normal = self.normal.get_vector()
# make sure that the normal has always a unit length
self.normal = self.normal.normalized()
self.center = self.p1.add(self.p2).add(self.p3).div(3)
self.normal = pnormalized(pcross(psub(self.p3, self.p1), psub(self.p2, self.p1)))
if not len(self.normal) > 3:
self.normal = (self.normal[0], self.normal[1], self.normal[2], 'v')
self.center = pdiv(padd(padd(self.p1, self.p2), self.p3), 3)
self.plane = Plane(self.center, self.normal)
# calculate circumcircle (resulting in radius and middle)
denom = self.p2.sub(self.p1).cross(self.p3.sub(self.p2)).norm
self.radius = (self.p2.sub(self.p1).norm \
* self.p3.sub(self.p2).norm * self.p3.sub(self.p1).norm) \
/ (2 * denom)
denom = pnorm(pcross(psub(self.p2, self.p1), psub(self.p3, self.p2)))
self.radius = (pnorm(psub(self.p2, self.p1)) * pnorm(psub(self.p3, self.p2)) * pnorm(psub(self.p3, self.p1))) / (2 * denom)
self.radiussq = self.radius ** 2
denom2 = 2 * denom * denom
alpha = self.p3.sub(self.p2).normsq \
* self.p1.sub(self.p2).dot(self.p1.sub(self.p3)) / denom2
beta = self.p1.sub(self.p3).normsq \
* self.p2.sub(self.p1).dot(self.p2.sub(self.p3)) / denom2
gamma = self.p1.sub(self.p2).normsq \
* self.p3.sub(self.p1).dot(self.p3.sub(self.p2)) / denom2
self.middle = Point(
self.p1.x * alpha + self.p2.x * beta + self.p3.x * gamma,
self.p1.y * alpha + self.p2.y * beta + self.p3.y * gamma,
self.p1.z * alpha + self.p2.z * beta + self.p3.z * gamma)
alpha = pnormsq(psub(self.p3, self.p2)) * pdot(psub(self.p1, self.p2), psub(self.p1, self.p3)) / denom2
beta = pnormsq(psub(self.p1, self.p3)) * pdot(psub(self.p2, self.p1), psub(self.p2, self.p3)) / denom2
gamma = pnormsq(psub(self.p1, self.p2)) * pdot(psub(self.p3, self.p1), psub(self.p3, self.p2)) / denom2
self.middle = (self.p1[0] * alpha + self.p2[0] * beta + self.p3[0] * gamma,
self.p1[1] * alpha + self.p2[1] * beta + self.p3[1] * gamma,
self.p1[2] * alpha + self.p2[2] * beta + self.p3[2] * gamma)
def __repr__(self):
return "Triangle%d<%s,%s,%s>" % (self.id, self.p1, self.p2, self.p3)
def copy(self):
return self.__class__(self.p1.copy(), self.p2.copy(), self.p3.copy(),
self.normal.copy())
return self.__class__(self.p1, self.p2, self.p3,
self.normal)
def next(self):
yield self.p1
yield self.p2
yield self.p3
yield self.normal
yield "p1"
yield "p2"
yield "p3"
yield "normal"
def get_points(self):
return (self.p1, self.p2, self.p3)
......@@ -118,25 +109,25 @@ class Triangle(IDGenerator, TransformableContainer):
GL.glBegin(GL.GL_TRIANGLES)
# use normals to improve lighting (contributed by imyrek)
normal_t = self.normal
GL.glNormal3f(normal_t.x, normal_t.y, normal_t.z)
GL.glNormal3f(normal_t[0], normal_t[1], normal_t[2])
# The triangle's points are in clockwise order, but GL expects
# counter-clockwise sorting.
GL.glVertex3f(self.p1.x, self.p1.y, self.p1.z)
GL.glVertex3f(self.p3.x, self.p3.y, self.p3.z)
GL.glVertex3f(self.p2.x, self.p2.y, self.p2.z)
GL.glVertex3f(self.p1[0], self.p1[1], self.p1[2])
GL.glVertex3f(self.p3[0], self.p3[1], self.p3[2])
GL.glVertex3f(self.p2[0], self.p2[1], self.p2[2])
GL.glEnd()
if show_directions: # display surface normals
n = self.normal
c = self.center
d = 0.5
GL.glBegin(GL.GL_LINES)
GL.glVertex3f(c.x, c.y, c.z)
GL.glVertex3f(c.x+n.x*d, c.y+n.y*d, c.z+n.z*d)
GL.glVertex3f(c[0], c[1], c[2])
GL.glVertex3f(c[0]+n[0]*d, c[1]+n[1]*d, c[2]+n[2]*d)
GL.glEnd()
if False: # display bounding sphere
GL.glPushMatrix()
middle = self.middle
GL.glTranslate(middle.x, middle.y, middle.z)
GL.glTranslate(middle[0], middle[1], middle[2])
if not hasattr(self, "_sphere"):
self._sphere = GLU.gluNewQuadric()
GLU.gluSphere(self._sphere, self.radius, 10, 10)
......@@ -144,15 +135,15 @@ class Triangle(IDGenerator, TransformableContainer):
if pycam.Utils.log.is_debug(): # draw triangle id on triangle face
GL.glPushMatrix()
c = self.center
GL.glTranslate(c.x, c.y, c.z)
p12 = self.p1.add(self.p2).mul(0.5)
p3_12 = self.p3.sub(p12).normalized()
p2_1 = self.p1.sub(self.p2).normalized()
pn = p2_1.cross(p3_12)
GL.glMultMatrixf((p2_1.x, p2_1.y, p2_1.z, 0, p3_12.x, p3_12.y,
p3_12.z, 0, pn.x, pn.y, pn.z, 0, 0, 0, 0, 1))
n = self.normal.mul(0.01)
GL.glTranslatef(n.x, n.y, n.z)
GL.glTranslate(c[0], c[1], c[2])
p12 = pmul(padd(self.p1, self.p2), 0.5)
p3_12 = pnormalized(psub(self.p3, p12))
p2_1 = pnormalized(psub(self.p1, self.p2))
pn = pcross(p2_1, p3_12)
GL.glMultMatrixf((p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
n = pmul(self.normal, 0.01)
GL.glTranslatef(n[0], n[1], n[2])
maxdim = max((self.maxx - self.minx), (self.maxy - self.miny),
(self.maxz - self.minz))
factor = 0.001
......@@ -167,18 +158,18 @@ class Triangle(IDGenerator, TransformableContainer):
GL.glPopMatrix()
if False: # draw point id on triangle face
c = self.center
p12 = self.p1.add(self.p2).mul(0.5)
p3_12 = self.p3.sub(p12).normalized()
p2_1 = self.p1.sub(self.p2).normalized()
pn = p2_1.cross(p3_12)
n = self.normal.mul(0.01)
p12 = pmul(padd(self.p1, self.p2), 0.5)
p3_12 = pnormalized(psub(self.p3, p12))
p2_1 = pnormalized(psub(self.p1, self.p2))
pn = pcross(p2_1, p3_12)
n = pmul(self.normal, 0.01)
for p in (self.p1, self.p2, self.p3):
GL.glPushMatrix()
pp = p.sub(p.sub(c).mul(0.3))
GL.glTranslate(pp.x, pp.y, pp.z)
GL.glMultMatrixf((p2_1.x, p2_1.y, p2_1.z, 0, p3_12.x, p3_12.y,
p3_12.z, 0, pn.x, pn.y, pn.z, 0, 0, 0, 0, 1))
GL.glTranslatef(n.x, n.y, n.z)
pp = psub(p, pmul(psub(p, c), 0.3))
GL.glTranslate(pp[0], pp[1], pp[2])
GL.glMultMatrixf((p2_1[0], p2_1[1], p2_1[2], 0, p3_12[0], p3_12[1],
p3_12[2], 0, pn[0], pn[1], pn[2], 0, 0, 0, 0, 1))
GL.glTranslatef(n[0], n[1], n[2])
GL.glScalef(0.001, 0.001, 0.001)
w = 0
for ch in str(p.id):
......@@ -191,15 +182,15 @@ class Triangle(IDGenerator, TransformableContainer):
def is_point_inside(self, p):
# http://www.blackpawn.com/texts/pointinpoly/default.html
# Compute vectors
v0 = self.p3.sub(self.p1)
v1 = self.p2.sub(self.p1)
v2 = p.sub(self.p1)
v0 = psub(self.p3, self.p1)
v1 = psub(self.p2, self.p1)
v2 = psub(p, self.p1)
# Compute dot products
dot00 = v0.dot(v0)
dot01 = v0.dot(v1)
dot02 = v0.dot(v2)
dot11 = v1.dot(v1)
dot12 = v1.dot(v2)
dot00 = pdot(v0, v0)
dot01 = pdot(v0, v1)
dot02 = pdot(v0, v2)
dot11 = pdot(v1, v1)
dot12 = pdot(v1, v2)
# Compute barycentric coordinates
denom = dot00 * dot11 - dot01 * dot01
if denom == 0:
......@@ -218,9 +209,9 @@ class Triangle(IDGenerator, TransformableContainer):
if depth == 0:
sub.append(self)
else:
p4 = self.p1.add(self.p2).div(2)
p5 = self.p2.add(self.p3).div(2)
p6 = self.p3.add(self.p1).div(2)
p4 = pdiv(padd(self.p1, self.p2), 2)
p5 = pdiv(padd(self.p2, self.p3), 2)
p6 = pdiv(padd(self.p3, self.p1), 2)
sub += Triangle(self.p1, p4, p6).subdivide(depth - 1)
sub += Triangle(p6, p5, self.p3).subdivide(depth - 1)
sub += Triangle(p6, p4, p5).subdivide(depth - 1)
......@@ -228,6 +219,6 @@ class Triangle(IDGenerator, TransformableContainer):
return sub
def get_area(self):
cross = self.p2.sub(self.p1).cross(self.p3.sub(self.p1))
return cross.norm / 2
cross = pcross(psub(self.p2, self.p1), psub(self.p3, self.p1))
return pnorm(cross) / 2
......@@ -79,10 +79,10 @@ class TriangleKdtree(kdtree):
def __init__(self, triangles, cutoff=3, cutoff_distance=1.0):
nodes = []
for t in triangles:
n = Node(t, (min(t.p1.x, t.p2.x, t.p3.x),
max(t.p1.x, t.p2.x, t.p3.x),
min(t.p1.y, t.p2.y, t.p3.y),
max(t.p1.y, t.p2.y, t.p3.y)))
n = Node(t, (min(t.p1[0], t.p2[0], t.p3[0]),
max(t.p1[0], t.p2[0], t.p3[0]),
min(t.p1[1], t.p2[1], t.p3[1]),
max(t.p1[1], t.p2[1], t.p3[1])))
nodes.append(n)
super(TriangleKdtree, self).__init__(nodes, cutoff, cutoff_distance)
......
......@@ -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/>.
"""
__all__ = ["utils", "Line", "Model", "Path", "Plane", "Point", "Triangle",
__all__ = ["utils", "Line", "Model", "Path", "Plane", "Triangle",
"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.Utils import log
import types
log = log.get_logger()
import math
......@@ -33,17 +37,17 @@ def get_bisector(p1, p2, p3, up_vector):
""" Calculate the bisector between p1, p2 and p3, whereas p2 is the origin
of the angle.
"""
d1 = p2.sub(p1).normalized()
d2 = p2.sub(p3).normalized()
bisector_dir = d1.add(d2).normalized()
d1 = pnormalized(psub(p2, p1))
d2 = pnormalized(psub(p2, p3))
bisector_dir = pnormalized(padd(d1, d2))
if bisector_dir is None:
# the two vectors pointed to opposite directions
bisector_dir = d1.cross(up_vector).normalized()
bisector_dir = pnormalized(pcross(d1, up_vector))
else:
skel_up_vector = bisector_dir.cross(p2.sub(p1))
if up_vector.dot(skel_up_vector) < 0:
skel_up_vector = pcross(bisector_dir, psub(p2, p1))
if pdot(up_vector, skel_up_vector) < 0:
# reverse the skeleton vector to point outwards
bisector_dir = bisector_dir.mul(-1)
bisector_dir = pmul(bisector_dir, -1)
return bisector_dir
def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False):
......@@ -57,20 +61,19 @@ def get_angle_pi(p1, p2, p3, up_vector, pi_factor=False):
p2--------p1
The result is in a range between 0 and 2*PI.
"""
d1 = p2.sub(p1).normalized()
d2 = p2.sub(p3).normalized()
d1 = pnormalized(psub(p2, p1))
d2 = pnormalized(psub(p2, p3))
if (d1 is None) or (d2 is None):
return 2 * math.pi
angle = math.acos(d1.dot(d2))
angle = math.acos(pdot(d1, d2))
# check the direction of the points (clockwise/anti)
# The code is taken from Polygon.get_area
value = [0, 0, 0]
for (pa, pb) in ((p1, p2), (p2, p3), (p3, p1)):
value[0] += pa.y * pb.z - pa.z * pb.y
value[1] += pa.z * pb.x - pa.x * pb.z
value[2] += pa.x * pb.y - pa.y * pb.x
area = up_vector.x * value[0] + up_vector.y * value[1] \
+ up_vector.z * value[2]
value[0] += pa[1] * pb[2] - pa[2] * pb[1]
value[1] += pa[2] * pb[0] - pa[0] * pb[2]
value[2] += pa[0] * pb[1] - pa[1] * pb[0]
area = up_vector[0] * value[0] + up_vector[1] * value[1] + up_vector[2] * value[2]
if area > 0:
# The points are in anti-clockwise order. Thus the angle is greater
# than 180 degree.
......@@ -113,8 +116,8 @@ def get_points_of_arc(center, radius, a1, a2, plane=None, cords=32):
angle_segment = angle_diff / num_of_segments
points = []
get_angle_point = lambda angle: (
center.x + radius * math.cos(angle),
center.y + radius * math.sin(angle))
center[0] + radius * math.cos(angle),
center[1] + radius * math.sin(angle))
points.append(get_angle_point(a1))
for index in range(num_of_segments):
points.append(get_angle_point(a1 + angle_segment * (index + 1)))
......@@ -131,23 +134,23 @@ def get_bezier_lines(points_with_bulge, segments=32):
if not bulge1 and not bulge2:
# straight line
return [Line.Line(p1, p2)]
straight_dir = p2.sub(p1).normalized()
straight_dir = pnormalized(psub(p2, p1))
#bulge1 = max(-1.0, min(1.0, bulge1))
bulge1 = math.atan(bulge1)
rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1),
-2 * bulge1, use_radians=True)
dir1_mat = Matrix.multiply_vector_matrix((straight_dir.x,
straight_dir.y, straight_dir.z), rot_matrix)
dir1 = Point.Vector(dir1_mat[0], dir1_mat[1], dir1_mat[2])
dir1_mat = Matrix.multiply_vector_matrix((straight_dir[0],
straight_dir[1], straight_dir[2]), rot_matrix)
dir1 = (dir1_mat[0], dir1_mat[1], dir1_mat[2], 'v')
if bulge2 is None:
bulge2 = bulge1
else:
bulge2 = math.atan(bulge2)
rot_matrix = Matrix.get_rotation_matrix_axis_angle((0, 0, 1),
2 * bulge2, use_radians=True)
dir2_mat = Matrix.multiply_vector_matrix((straight_dir.x,
straight_dir.y, straight_dir.z), rot_matrix)
dir2 = Point.Vector(dir2_mat[0], dir2_mat[1], dir2_mat[2])
dir2_mat = Matrix.multiply_vector_matrix((straight_dir[0],
straight_dir[1], straight_dir[2]), rot_matrix)
dir2 = (dir2_mat[0], dir2_mat[1], dir2_mat[2], 'v')
# interpretation of bulge1 and bulge2:
# /// taken from http://paulbourke.net/dataformats/dxf/dxf10.html ///
# The bulge is the tangent of 1/4 the included angle for an arc
......@@ -155,7 +158,7 @@ def get_bezier_lines(points_with_bulge, segments=32):
# point to the end point; a bulge of 0 indicates a straight segment,
# and a bulge of 1 is a semicircle.
alpha = 2 * (abs(bulge1) + abs(bulge2))
dist = p2.sub(p1).norm
dist = pnorm(psub(p2, p1))
# calculate the radius of the circumcircle - avoiding divide-by-zero
if (abs(alpha) < epsilon) or (abs(math.pi - alpha) < epsilon):
radius = dist / 2.0
......@@ -165,16 +168,13 @@ def get_bezier_lines(points_with_bulge, segments=32):
# The calculation of "factor" is based on random guessing - but it
# seems to work well.
factor = 4 * radius * math.tan(alpha / 4.0)
dir1 = dir1.mul(factor)
dir2 = dir2.mul(factor)
dir1 = pmul(dir1, factor)
dir2 = pmul(dir2, factor)
for index in range(segments + 1):
# t: 0..1
t = float(index) / segments
# see: http://en.wikipedia.org/wiki/Cubic_Hermite_spline
p = p1.mul(2 * t ** 3 - 3 * t ** 2 + 1).add(
dir1.mul(t ** 3 - 2 * t ** 2 + t).add(
p2.mul(-2 * t ** 3 + 3 * t ** 2).add(
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)
# create lines
result = []
......@@ -232,16 +232,24 @@ class TransformableContainer(object):
# Use the 'id' builtin to prevent expensive object comparions.
for item in self.next():
if isinstance(item, TransformableContainer):
item.transform_by_matrix(matrix, transformed_list,
callback=callback)
item.transform_by_matrix(matrix, transformed_list,callback=callback)
elif not id(item) in transformed_list:
# non-TransformableContainer do not care to update the
# '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
# not a TransformableContainer. It is not necessary and it
# 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
if callback and callback():
# user requesteded abort
......
......@@ -26,7 +26,7 @@ from pycam.Utils.polynomials import poly4_roots
from pycam.Geometry.utils import INFINITE, sqrt, epsilon
from pycam.Geometry.Plane import Plane
from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
def isNear(a, b):
return abs(a - b) < epsilon
......@@ -60,14 +60,14 @@ def intersect_lines(xl, zl, nxl, nzl, xm, zm, nxm, nzm):
def intersect_cylinder_point(center, axis, radius, radiussq, direction, point):
# take a plane along direction and axis
n = direction.cross(axis).normalized()
n = pnormalized(pcross(direction, axis))
# distance of the point to this plane
d = n.dot(point) - n.dot(center)
d = pdot(n, point) - pdot(n, center)
if abs(d) > radius - epsilon:
return (None, None, INFINITE)
# ccl is on cylinder
d2 = sqrt(radiussq-d*d)
ccl = center.add(n.mul(d)).add(direction.mul(d2))
ccl = padd( padd(center, pmul(n, d)), pmul(direction, d2))
# take plane through ccl and axis
plane = Plane(ccl, direction)
# intersect point with plane
......@@ -77,26 +77,26 @@ def intersect_cylinder_point(center, axis, radius, radiussq, direction, point):
def intersect_cylinder_line(center, axis, radius, radiussq, direction, edge):
d = edge.dir
# take a plane throught the line and along the cylinder axis (1)
n = d.cross(axis)
if n.norm == 0:
n = pcross(d, axis)
if pnorm(n) == 0:
# no contact point, but should check here if cylinder *always*
# intersects line...
return (None, None, INFINITE)
n = n.normalized()
n = pnormalized(n)
# the contact line between the cylinder and this plane (1)
# is where the surface normal is perpendicular to the plane
# so line := ccl + \lambda * axis
if n.dot(direction) < 0:
ccl = center.sub(n.mul(radius))
if pdot(n, direction) < 0:
ccl = psub(center, pmul(n, radius))
else:
ccl = center.add(n.mul(radius))
ccl = padd(center, pmul(n, radius))
# now extrude the contact line along the direction, this is a plane (2)
n2 = direction.cross(axis)
if n2.norm == 0:
n2 = pcross(direction, axis)
if pnorm(n2) == 0:
# no contact point, but should check here if cylinder *always*
# intersects line...
return (None, None, INFINITE)
n2 = n2.normalized()
n2 = pnormalized(n2)
plane1 = Plane(ccl, n2)
# intersect this plane with the line, this gives us the contact point
(cp, l) = plane1.intersect_point(d, edge.p1)
......@@ -108,23 +108,23 @@ def intersect_cylinder_line(center, axis, radius, radiussq, direction, edge):
# the intersection of this plane (3) with the line through the contact point
# gives us the cutter contact point
(ccp, l) = plane2.intersect_point(direction, cp)
cp = ccp.add(direction.mul(-l))
cp = padd(ccp, pmul(direction, -l))
return (ccp, cp, -l)
def intersect_circle_plane(center, radius, direction, triangle):
# let n be the normal to the plane
n = triangle.normal
if n.dot(direction) == 0:
if pdot(n,direction) == 0:
return (None, None, INFINITE)
# project onto z=0
n2 = Point(n.x, n.y, 0)
if n2.norm == 0:
n2 = (n[0], n[1], 0)
if pnorm(n2) == 0:
(cp, d) = triangle.plane.intersect_point(direction, center)
ccp = cp.sub(direction.mul(d))
ccp = psub(cp, pmul(direction, d))
return (ccp, cp, d)
n2 = n2.normalized()
n2 = pnormalized(n2)
# the cutter contact point is on the circle, where the surface normal is n
ccp = center.add(n2.mul(-radius))
ccp = padd(center, pmul(n2, -radius))
# intersect the plane with a line through the contact point
(cp, d) = triangle.plane.intersect_point(direction, ccp)
return (ccp, cp, d)
......@@ -135,45 +135,45 @@ def intersect_circle_point(center, axis, radius, radiussq, direction, point):
# intersect with line gives ccp
(ccp, l) = plane.intersect_point(direction, point)
# check if inside circle
if ccp and (center.sub(ccp).normsq < radiussq - epsilon):
if ccp and (pnormsq(psub(center, ccp)) < radiussq - epsilon):
return (ccp, point, -l)
return (None, None, INFINITE)
def intersect_circle_line(center, axis, radius, radiussq, direction, edge):
# make a plane by sliding the line along the direction (1)
d = edge.dir
if d.dot(axis) == 0:
if direction.dot(axis) == 0:
if pdot(d, axis) == 0:
if pdot(direction, axis) == 0:
return (None, None, INFINITE)
plane = Plane(center, axis)
(p1, l) = plane.intersect_point(direction, edge.p1)
(p2, l) = plane.intersect_point(direction, edge.p2)
pc = Line(p1, p2).closest_point(center)
d_sq = pc.sub(center).normsq
d_sq = pnormsq(psub(pc, center))
if d_sq >= radiussq:
return (None, None, INFINITE)
a = sqrt(radiussq - d_sq)
d1 = p1.sub(pc).dot(d)
d2 = p2.sub(pc).dot(d)
d1 = pdot(psub(p1, pc), d)
d2 = pdot(psub(p2, pc), d)
ccp = None
cp = None
if abs(d1) < a - epsilon:
ccp = p1
cp = p1.sub(direction.mul(l))
cp = psub(p1, pmul(direction, l))
elif abs(d2) < a - epsilon:
ccp = p2
cp = p2.sub(direction.mul(l))
cp = psub(p2, pmul(direction, l))
elif ((d1 < -a + epsilon) and (d2 > a - epsilon)) \
or ((d2 < -a + epsilon) and (d1 > a - epsilon)):
ccp = pc
cp = pc.sub(direction.mul(l))
cp = psub(pc, pmul(direction, l))
return (ccp, cp, -l)
n = d.cross(direction)
if n.norm == 0:
n = pcross(d, direction)
if pnorm(n)== 0:
# no contact point, but should check here if circle *always* intersects
# line...
return (None, None, INFINITE)
n = n.normalized()
n = pnormalized(n)
# take a plane through the base
plane = Plane(center, axis)
# intersect base with line
......@@ -181,39 +181,39 @@ def intersect_circle_line(center, axis, radius, radiussq, direction, edge):
if not lp:
return (None, None, INFINITE)
# intersection of 2 planes: lp + \lambda v
v = axis.cross(n)
if v.norm == 0:
v = pcross(axis, n)
if pnorm(v) == 0:
return (None, None, INFINITE)
v = v.normalized()
v = pnormalized(v)
# take plane through intersection line and parallel to axis
n2 = v.cross(axis)
if n2.norm == 0:
n2 = pcross(v, axis)
if pnorm(n2) == 0:
return (None, None, INFINITE)
n2 = n2.normalized()
n2 = pnormalized(n2)
# distance from center to this plane
dist = n2.dot(center) - n2.dot(lp)
dist = pdot(n2, center) - pdot(n2, lp)
distsq = dist * dist
if distsq > radiussq - epsilon:
return (None, None, INFINITE)
# must be on circle
dist2 = sqrt(radiussq - distsq)
if d.dot(axis) < 0:
if pdot(d, axis) < 0:
dist2 = -dist2
ccp = center.sub(n2.mul(dist)).sub(v.mul(dist2))
plane = Plane(edge.p1, d.cross(direction).cross(d))
ccp = psub(center, psub(pmul(n2, dist), pmul(v, dist2)))
plane = Plane(edge.p1, pcross(pcross(d, direction), d))
(cp, l) = plane.intersect_point(direction, ccp)
return (ccp, cp, l)
def intersect_sphere_plane(center, radius, direction, triangle):
# let n be the normal to the plane
n = triangle.normal
if n.dot(direction) == 0:
if pdot(n, direction) == 0:
return (None, None, INFINITE)
# the cutter contact point is on the sphere, where the surface normal is n
if n.dot(direction) < 0:
ccp = center.sub(n.mul(radius))
if pdot(n, direction) < 0:
ccp = psub(center, pmul(n, radius))
else:
ccp = center.add(n.mul(radius))
ccp = padd(center, pmul(n, radius))
# intersect the plane with a line through the contact point
(cp, d) = triangle.plane.intersect_point(direction, ccp)
return (ccp, cp, d)
......@@ -224,10 +224,10 @@ def intersect_sphere_point(center, radius, radiussq, direction, point):
# sphere equation
# (2) (x-x_0)^2 = R^2
# (1) in (2) gives a quadratic in \lambda
p0_x0 = center.sub(point)
a = direction.normsq
b = 2 * p0_x0.dot(direction)
c = p0_x0.normsq - radiussq
p0_x0 = psub(center, point)
a = pnormsq(direction)
b = 2 * pdot(p0_x0, direction)
c = pnormsq(p0_x0) - radiussq
d = b * b - 4 * a * c
if d < 0:
return (None, None, INFINITE)
......@@ -236,21 +236,21 @@ def intersect_sphere_point(center, radius, radiussq, direction, point):
else:
l = (-b - sqrt(d)) / (2 * a)
# cutter contact point
ccp = point.add(direction.mul(-l))
ccp = padd(point, pmul(direction, -l))
return (ccp, point, l)
def intersect_sphere_line(center, radius, radiussq, direction, edge):
# make a plane by sliding the line along the direction (1)
d = edge.dir
n = d.cross(direction)
if n.norm == 0:
n = pcross(n, direction)
if pnorm(n) == 0:
# no contact point, but should check here if sphere *always* intersects
# line...
return (None, None, INFINITE)
n = n.normalized()
n = pnormalized(n)
# calculate the distance from the sphere center to the plane
dist = - center.dot(n) + edge.p1.dot(n)
dist = - pdot(center, n) + pdot(edge.p1, n)
if abs(dist) > radius - epsilon:
return (None, None, INFINITE)
# this gives us the intersection circle on the sphere
......@@ -259,13 +259,13 @@ def intersect_sphere_line(center, radius, radiussq, direction, edge):
# find the center on the circle closest to this plane
# which means the other component is perpendicular to this plane (2)
n2 = n.cross(d).normalized()
n2 = pnormalized(pcross(n, d))
# the contact point is on a big circle through the sphere...
dist2 = sqrt(radiussq - dist * dist)
# ... and it's on the plane (1)
ccp = center.add(n.mul(dist)).add(n2.mul(dist2))
ccp = padd(center, padd(pmul(n, dist), pmul(n2, dist2)))
# now intersect a line through this point with the plane (2)
plane = Plane(edge.p1, n2)
......@@ -276,19 +276,19 @@ def intersect_torus_plane(center, axis, majorradius, minorradius, direction,
triangle):
# take normal to the plane
n = triangle.normal
if n.dot(direction) == 0:
if pdot(n, direction) == 0:
return (None, None, INFINITE)
if n.dot(axis) == 1:
if pdot(n, axis) == 1:
return (None, None, INFINITE)
# find place on torus where surface normal is n
b = n.mul(-1)
b = pmul(n, -1)
z = axis
a = b.sub(z.mul(z.dot(b)))
a_sq = a.normsq
a = psub(b, pmul(z,pdot(z, b)))
a_sq = pnormsq(a)
if a_sq <= 0:
return (None, None, INFINITE)
a = a.div(sqrt(a_sq))
ccp = center.add(a.mul(majorradius)).add(b.mul(minorradius))
a = pdiv(a, sqrt(a_sq))
ccp = padd(padd(center, pmul(a, majorradius)), pmul(b, minorradius))
# find intersection with plane
(cp, l) = triangle.plane.intersect_point(direction, ccp)
return (ccp, cp, l)
......@@ -296,11 +296,11 @@ def intersect_torus_plane(center, axis, majorradius, minorradius, direction,
def intersect_torus_point(center, axis, majorradius, minorradius, majorradiussq,
minorradiussq, direction, point):
dist = 0
if (direction.x == 0) and (direction.y == 0):
if (direction[0] == 0) and (direction[1] == 0):
# drop
minlsq = (majorradius - minorradius) ** 2
maxlsq = (majorradius + minorradius) ** 2
l_sq = (point.x-center.x) ** 2 + (point.y - center.y) ** 2
l_sq = (point[0]-center[0]) ** 2 + (point[1] - center[1]) ** 2
if (l_sq < minlsq + epsilon) or (l_sq > maxlsq - epsilon):
return (None, None, INFINITE)
l = sqrt(l_sq)
......@@ -308,33 +308,33 @@ def intersect_torus_point(center, axis, majorradius, minorradius, majorradiussq,
if z_sq < 0:
return (None, None, INFINITE)
z = sqrt(z_sq)
ccp = Point(point.x, point.y, center.z - z)
dist = ccp.z - point.z
elif direction.z == 0:
ccp = (point[0], point[1], center[2] - z)
dist = ccp[2] - point[2]
elif direction[2] == 0:
# push
z = point.z - center.z
z = point[2] - center[2]
if abs(z) > minorradius - epsilon:
return (None, None, INFINITE)
l = majorradius + sqrt(minorradiussq - z * z)
n = axis.cross(direction)
d = n.dot(point) - n.dot(center)
n = pcross(axis, direction)
d = pdot(n, point) - pdot(n, center)
if abs(d) > l - epsilon:
return (None, None, INFINITE)
a = sqrt(l * l - d * d)
ccp = center.add(n.mul(d).add(direction.mul(a)))
ccp.z = point.z
dist = point.sub(ccp).dot(direction)
ccp = padd(padd(center, pmul(n, d)), pmul(direction, a))
ccp = (ccp[0], ccp[1], point[2])
dist = pdot(psub(point, ccp), direction)
else:
# general case
x = point.sub(center)
v = direction.mul(-1)
x_x = x.dot(x)
x_v = x.dot(v)
x1 = Point(x.x, x.y, 0)
v1 = Point(v.x, v.y, 0)
x1_x1 = x1.dot(x1)
x1_v1 = x1.dot(v1)
v1_v1 = v1.dot(v1)
x = psub(point, center)
v = pmul(direction, -1)
x_x = pdot(x, x)
x_v = pdot(x, v)
x1 = (x[0], x[1], 0)
v1 = (v[0], v[1], 0)
x1_x1 = pdot(x1, x1)
x1_v1 = pdot(x1, v1)
v1_v1 = pdot(v1, v1)
R2 = majorradiussq
r2 = minorradiussq
a = 1.0
......@@ -347,7 +347,7 @@ def intersect_torus_point(center, axis, majorradius, minorradius, majorradiussq,
return (None, None, INFINITE)
else:
l = min(r)
ccp = point.add(direction.mul(-l))
ccp = padd(point, pmul(direction, -l))
dist = l
return (ccp, point, dist)
......@@ -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/>.
"""
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
from pycam.Geometry.utils import sqrt
# careful import
......@@ -55,38 +55,33 @@ def keep_matrix(func):
@keep_matrix
def draw_direction_cone(p1, p2, position=0.5, precision=12, size=0.1):
# convert p1 and p2 from list/tuple to Point
if not hasattr(p1, "sub"):
p1 = Point(*p1)
if not hasattr(p2, "sub"):
p2 = Point(*p2)
distance = p2.sub(p1)
length = distance.norm
direction = distance.normalized()
distance = psub(p2, p1)
length = pnorm(distance)
direction = pnormalized(distance)
if direction is None:
# zero-length line
return
cone_length = length * size
cone_radius = cone_length / 3.0
# move the cone to the middle of the line
GL.glTranslatef((p1.x + p2.x) * position,
(p1.y + p2.y) * position, (p1.z + p2.z) * position)
GL.glTranslatef((p1[0] + p2[0]) * position,
(p1[1] + p2[1]) * position, (p1[2] + p2[2]) * position)
# rotate the cone according to the line direction
# The cross product is a good rotation axis.
cross = direction.cross(Point(0, 0, -1))
if cross.norm != 0:
cross = pcross(direction, (0, 0, -1))
if pnorm(cross) != 0:
# The line direction is not in line with the z axis.
try:
angle = math.asin(sqrt(direction.x ** 2 + direction.y ** 2))
angle = math.asin(sqrt(direction[0] ** 2 + direction[1] ** 2))
except ValueError:
# invalid angle - just ignore this cone
return
# convert from radians to degree
angle = angle / math.pi * 180
if direction.z < 0:
if direction[2] < 0:
angle = 180 - angle
GL.glRotatef(angle, cross.x, cross.y, cross.z)
elif direction.z == -1:
GL.glRotatef(angle, cross[0], cross[1], cross[2])
elif direction[2] == -1:
# The line goes down the z axis - turn it around.
GL.glRotatef(180, 1, 0, 0)
else:
......
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Letters import Charset
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
import pycam.Utils.log
import pycam.Utils
......@@ -144,13 +144,13 @@ class CXFParser(object):
type_char = line[0].upper()
if (type_def == "L") and (len(coords) == 4):
# line
p1 = Point(coords[0], coords[1], 0)
p2 = Point(coords[2], coords[3], 0)
p1 = (coords[0], coords[1], 0)
p2 = (coords[2], coords[3], 0)
char_definition.append(Line(p1, p2))
elif (type_def in ("A", "AR")) and (len(coords) == 5):
# arc
previous = None
center = Point(coords[0], coords[1], 0)
center = (coords[0], coords[1], 0)
radius = coords[2]
start_angle, end_angle = coords[3], coords[4]
if type_def == "AR":
......@@ -158,7 +158,7 @@ class CXFParser(object):
start_angle, end_angle = end_angle, start_angle
for p in get_points_of_arc(center, radius, start_angle,
end_angle):
current = Point(p[0], p[1], 0)
current = (p[0], p[1], 0)
if not previous is None:
char_definition.append(Line(previous, current))
previous = current
......
......@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Line import Line
import pycam.Geometry.Model
import pycam.Geometry.Matrix
......@@ -142,8 +142,8 @@ class DXFParser(object):
current_group = []
groups.append(current_group)
def get_distance_between_groups(group1, group2):
forward = group1[-1].p2.sub(group2[0].p1).norm
backward = group2[-1].p2.sub(group1[0].p1).norm
forward = pnorm(psub(group1[-1].p2, group2[0].p1))
backward = pnorm(psub(group2[-1].p2, group1[0].p1))
return min(forward, backward)
remaining_groups = groups[:]
ordered_groups = []
......@@ -305,7 +305,7 @@ class DXFParser(object):
"between line %d and %d" % (start_line, end_line))
else:
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):
start_line = self.line_number
......@@ -342,6 +342,8 @@ class DXFParser(object):
next_point = points[index + 1]
if point != next_point:
self.lines.append(Line(point, next_point))
if ("VERTEX_FLAGS" in params) and (params["VERTEX_FLAGS"] == "EXTRA_VERTEX"):
self.lines.append(Line(points[-1], points[0]))
self._open_sequence_items = []
self._open_sequence_params = {}
self._open_sequence = None
......@@ -358,7 +360,7 @@ class DXFParser(object):
"date in line %d: %s" % \
(self.line_number, p_array))
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]
bulge = None
extra_vertex_flag = False
......@@ -755,15 +757,15 @@ class DXFParser(object):
+ "%d and %d" % (start_line, end_line))
else:
# no color height adjustment for 3DFACE
point1 = Point(p1[0], p1[1], p1[2])
point2 = Point(p2[0], p2[1], p2[2])
point3 = Point(p3[0], p3[1], p3[2])
point1 = tuple(p1)
point2 = tuple(p2)
point3 = tuple(p3)
triangles = []
triangles.append((point1, point2, point3))
# DXF specifies, that p3=p4 if triangles (instead of quads) are
# written.
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))
for t in triangles:
if (t[0] != t[1]) and (t[0] != t[2]) and (t[1] != t[2]):
......@@ -811,7 +813,7 @@ class DXFParser(object):
# use the color code as the z coordinate
p1[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:
self.lines.append(line)
else:
......@@ -862,18 +864,17 @@ class DXFParser(object):
if self._color_as_height and (not color is None):
# use the color code as the z coordinate
center[2] = float(color) / 255
center = Point(center[0], center[1], center[2])
xy_point_coords = pycam.Geometry.get_points_of_arc(center, radius,
angle_start, angle_end)
center = tuple(center)
xy_point_coords = pycam.Geometry.get_points_of_arc(center, radius, angle_start, angle_end)
# Somehow the order of points seems to be the opposite of what is
# expected.
xy_point_coords.reverse()
if len(xy_point_coords) > 1:
for index in range(len(xy_point_coords) - 1):
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 = Point(p2[0], p2[1], center.z)
p2 = (p2[0], p2[1], center[2])
if p1 != p2:
self.lines.append(Line(p1, p2))
else:
......
......@@ -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/>.
"""
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.PointKdtree import PointKdtree
from pycam.Geometry.utils import epsilon
......@@ -40,17 +40,17 @@ vertices = 0
edges = 0
kdtree = None
lastUniqueVertex = (None,None,None)
def UniqueVertex(x, y, z):
global vertices
global vertices,lastUniqueVertex
if kdtree:
last = Point.id
p = kdtree.Point(x, y, z)
if p.id == last:
if p == lastUniqueVertex:
vertices += 1
return p
else:
vertices += 1
return Point(x, y, z)
return (x, y, z)
def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
global vertices, edges, kdtree
......@@ -127,7 +127,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
a2 = 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]
v12 = unpack("<f", f.read(4))[0]
......@@ -150,9 +150,9 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
# not used
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)))
if a1 == a2 == a3 == 0:
dotcross = p2.sub(p1).cross(p3.sub(p1)).z
dotcross = pcross(psub(p2, p1), psub(p3,p1))[2]
n = None
if dotcross > 0:
......@@ -209,8 +209,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
if m:
m = normal.match(line)
if m:
n = Vector(float(m.group('x')), float(m.group('y')),
float(m.group('z')))
n = (float(m.group('x')), float(m.group('y')), float(m.group('z')), 'v')
else:
n = None
continue
......@@ -243,7 +242,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
n, p1, p2, p3 = None, None, None, None
continue
if not n:
n = p2.sub(p1).cross(p3.sub(p1)).normalized()
n = pnormalized(pcross(psub(p2, p1), psub(p3, p1)))
# validate the normal
# The three vertices of a triangle in an STL file are supposed
......@@ -254,7 +253,7 @@ def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
dotcross = 0
else:
# 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)))
if dotcross > 0:
# Triangle expects the vertices in clockwise order
t = Triangle(p1, p3, p2, n)
......
......@@ -22,22 +22,21 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Line import Line
from pycam.Geometry.Point import Point
from pycam.Geometry.Model import Model
def get_test_model():
points = []
points.append(Point(-2, 1, 4))
points.append(Point(2, 1, 4))
points.append(Point(0, -2, 4))
points.append(Point(-5, 2, 2))
points.append(Point(-1, 3, 2))
points.append(Point(5, 2, 2))
points.append(Point(4, -1, 2))
points.append(Point(2, -4, 2))
points.append(Point(-2, -4, 2))
points.append(Point(-3, -2, 2))
points.append((-2, 1, 4))
points.append((2, 1, 4))
points.append((0, -2, 4))
points.append((-5, 2, 2))
points.append((-1, 3, 2))
points.append((5, 2, 2))
points.append((4, -1, 2))
points.append((2, -4, 2))
points.append((-2, -4, 2))
points.append((-3, -2, 2))
lines = []
lines.append(Line(points[0], points[1]))
......
......@@ -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:
# 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.Plane import Plane
from pycam.PathGenerators import get_free_paths_ode, get_free_paths_triangles
......@@ -50,7 +50,7 @@ def _process_one_triangle((model, cutter, up_vector, triangle, z)):
# Case 1a
return result, None
# ignore triangles pointing upwards or downwards
if triangle.normal.cross(up_vector).norm == 0:
if pnorm(pcross(triangle.normal, up_vector)) == 0:
# Case 1b
return result, None
edge_collisions = get_collision_waterline_of_triangle(model, cutter,
......@@ -197,7 +197,7 @@ class ContourFollow(object):
def __init__(self, path_processor, physics=None):
self.pa = path_processor
self._up_vector = Vector(0, 0, 1)
self._up_vector = (0, 0, 1, 'v')
self.physics = physics
self._processed_triangles = []
if self.physics:
......@@ -343,13 +343,13 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
for index in range(3):
edge = Line(proj_points[index - 1], proj_points[index])
# the edge should be clockwise around the model
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)
edges.append((edge, proj_points[index - 2]))
outer_edges = []
for edge, other_point in edges:
# 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 pdot(pcross(psub(other_point, edge.p1), edge.dir), up_vector) > 0:
outer_edges.append(edge)
if len(outer_edges) == 0:
# the points seem to be an one line
......@@ -361,14 +361,14 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
outer_edges = [long_edge]
else:
edge = Line(proj_points[0], proj_points[1])
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)
outer_edges = [edge]
else:
# some parts of the triangle are above and some below the cutter level
# Cases (2a), (2b), (3a) and (3b)
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)
if waterline is None:
if len(points_above) == 0:
......@@ -380,7 +380,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
# "triangle.minz >= z" statement above).
outer_edges = []
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
outer_edges = []
else:
......@@ -397,8 +397,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
outer_edges = [waterline]
elif len(points_above) == 1:
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(
up_vector)
dot = pdot(pcross(psub(other_point, waterline.p1), waterline.dir), up_vector)
if dot > 0:
# Case (2b)
outer_edges = [waterline]
......@@ -409,7 +408,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
edges.append(Line(waterline.p2, other_point))
outer_edges = []
for edge in edges:
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))
else:
outer_edges.append(edge)
......@@ -422,15 +421,14 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
edges.append(Line(waterline.p2, other_point))
edges.sort(key=lambda x: x.len)
edge = edges[-1]
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)]
else:
outer_edges = [edge]
else:
# two points above
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(
up_vector)
dot = pdot(pcross(psub(other_point, waterline.p1), waterline.dir), up_vector)
if dot > 0:
# Case (2b)
# the other two points are on the right side
......@@ -438,7 +436,7 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
elif dot < 0:
# Case (3a)
edge = Line(points_above[0], points_above[1])
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)]
else:
outer_edges = [edge]
......@@ -471,21 +469,20 @@ def get_collision_waterline_of_triangle(model, cutter, up_vector, triangle, z):
direction = up_vector.cross(edge.dir).normalized()
if direction is None:
continue
direction = direction.mul(max_length)
edge_dir = edge.p2.sub(edge.p1)
direction = pmul(direction, max_length)
edge_dir = psub(edge.p2, edge.p1)
# 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
# collisions with triangles sharing these vertices.
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))
# We need to use the triangle collision algorithm here - because we
# need the point of collision in the triangle.
collisions = get_free_paths_triangles([model], cutter, start,
start.add(direction), return_triangles=True)
collisions = get_free_paths_triangles([model], cutter, start, padd(start, direction), return_triangles=True)
for index, coll in enumerate(collisions):
if (index % 2 == 0) and (not coll[1] is None) \
and (not coll[2] is None) \
and (coll[0].sub(start).dot(direction) > 0):
and (pdot(psub(coll[0], start), direction) > 0):
cl, hit_t, cp = coll
break
else:
......
......@@ -27,6 +27,7 @@ from pycam.Utils.threading import run_in_parallel
import pycam.Geometry.Model
import pycam.Utils.log
log = pycam.Utils.log.get_logger()
......@@ -52,12 +53,11 @@ class DropCutter(object):
# Transfer the grid (a generator) into a list of lists and count the
# items.
lines = []
# usually there is only one layer - but an xy-grid consists of two
lines = []
for layer in motion_grid:
for line in layer:
lines.append(line)
lines.extend(layer)
num_of_lines = len(lines)
progress_counter = ProgressCounter(len(lines), draw_callback)
current_line = 0
......@@ -66,15 +66,13 @@ class DropCutter(object):
args = []
for one_grid_line in lines:
# simplify the data (useful for remote processing)
xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
args.append((xy_coords, minz, maxz, model, cutter,
self.physics))
args.append(([(x,y) for x,y,z in one_grid_line], minz, maxz, model, cutter, self.physics))
for points in run_in_parallel(_process_one_grid_line, args,
callback=progress_counter.update):
self.pa.new_scanline()
if draw_callback and draw_callback(text="DropCutter: processing " \
+ "line %d/%d" % (current_line + 1, num_of_lines)):
if draw_callback and draw_callback(text="DropCutter: processing line %d/%d"
% (current_line + 1, num_of_lines)):
# cancel requested
quit_requested = True
break
......
......@@ -22,7 +22,6 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.PathProcessors.PathAccumulator
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane
from pycam.Geometry.utils import ceil
......
......@@ -26,6 +26,7 @@ import pycam.PathProcessors
from pycam.Geometry.utils import ceil
from pycam.Utils.threading import run_in_parallel
from pycam.Utils import ProgressCounter
from pycam.Geometry.PointUtils import *
import pycam.Utils.log
import math
......@@ -127,7 +128,7 @@ class PushCutter(object):
for line in layer_grid:
p1, p2 = line
# calculate the required calculation depth (recursion)
distance = p2.sub(p1).norm
distance = pnorm(psub(p2, p1))
# TODO: accessing cutter.radius here is slightly ugly
depth = math.log(accuracy * distance / cutter.radius) / math.log(2)
depth = min(max(ceil(depth), 4), max_depth)
......
......@@ -24,7 +24,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["DropCutter", "PushCutter", "EngraveCutter", "ContourFollow"]
from pycam.Geometry.utils import INFINITE, epsilon, sqrt
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
import pycam.Utils.threading
......@@ -64,15 +64,15 @@ def get_free_paths_triangles(models, cutter, p1, p2, return_triangles=False):
all_results.extend(one_result)
return all_results
backward = p1.sub(p2).normalized()
forward = p2.sub(p1).normalized()
xyz_dist = p2.sub(p1).norm
backward = pnormalized(psub(p1, p2))
forward = pnormalized(psub(p2, p1))
xyz_dist = pnorm(psub(p2, p1))
minx = min(p1.x, p2.x)
maxx = max(p1.x, p2.x)
miny = min(p1.y, p2.y)
maxy = max(p1.y, p2.y)
minz = min(p1.z, p2.z)
minx = min(p1[0], p2[0])
maxx = max(p1[0], p2[0])
miny = min(p1[1], p2[1])
maxy = max(p1[1], p2[1])
minz = min(p1[2], p2[2])
# find all hits along scan line
hits = []
......@@ -161,15 +161,15 @@ def get_free_paths_ode(physics, p1, p2, depth=8):
"""
points = []
# "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.set_drill_position((p1.x, p1.y, p1.z))
physics.extend_drill(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
physics.set_drill_position((p1[0], p1[1], p1[2]))
if physics.check_collision():
# collision detected
if depth > 0:
middle_x = (p1.x + p2.x) / 2
middle_y = (p1.y + p2.y) / 2
middle_z = (p1.z + p2.z) / 2
p_middle = Point(middle_x, middle_y, middle_z)
middle_x = (p1[0] + p2[0]) / 2
middle_y = (p1[1] + p2[1]) / 2
middle_z = (p1[2] + p2[2]) / 2
p_middle = (middle_x, middle_y, middle_z)
group1 = get_free_paths_ode(physics, p1, p_middle, depth - 1)
group2 = get_free_paths_ode(physics, p_middle, p2, depth - 1)
if group1 and group2 and (group1[-1] == group2[0]):
......@@ -222,12 +222,12 @@ def get_max_height_ode(physics, x, y, minz, maxz):
# skip this point (by going up to safety height)
return None
else:
return Point(x, y, safe_z)
return (x, y, safe_z)
def get_max_height_triangles(model, cutter, x, y, minz, maxz):
if model is None:
return Point(x, y, minz)
p = Point(x, y, maxz)
return (x, y, minz)
p = (x, y, maxz)
height_max = None
box_x_min = cutter.get_minx(p)
box_x_max = cutter.get_maxx(p)
......@@ -239,8 +239,8 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz):
box_y_max, box_z_max)
for t in triangles:
cut = cutter.drop(t, start=p)
if cut and ((height_max is None) or (cut.z > height_max)):
height_max = cut.z
if cut and ((height_max is None) or (cut[2] > height_max)):
height_max = cut[2]
# don't do a complete boundary check for the height
# this avoids zero-cuts for models that exceed the bounding box height
if (height_max is None) or (height_max < minz + epsilon):
......@@ -248,18 +248,18 @@ def get_max_height_triangles(model, cutter, x, y, minz, maxz):
if height_max > maxz + epsilon:
return None
else:
return Point(x, y, height_max)
return (x, y, height_max)
def _check_deviance_of_adjacent_points(p1, p2, p3, min_distance):
straight = p3.sub(p1)
added = p2.sub(p1).norm + p3.sub(p2).norm
straight = psub(p3, p1)
added = pnorm(psub(p2, p1)) + pnorm(psub(p3, p2))
# 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
return True
else:
# 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):
max_depth = 8
......@@ -291,11 +291,11 @@ def get_max_height_dynamic(model, cutter, positions, minz, maxz, physics=None):
# distribute the new point two before the middle and one after
if depth_count % 3 != 2:
# 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]))
else:
# 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]))
depth_count += 1
else:
......
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.PathProcessors
from pycam.Geometry.PolygonExtractor import PolygonExtractor
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
from pycam.Toolpath import simplify_toolpath
class ContourCutter(pycam.PathProcessors.BasePathProcessor):
......@@ -33,12 +33,12 @@ class ContourCutter(pycam.PathProcessors.BasePathProcessor):
self.polygon_extractor = None
self.points = []
self.reverse = reverse
self.__forward = Point(1, 1, 0)
self.__forward = (1, 1, 0)
def append(self, point):
# Sort the points in positive x/y direction - otherwise the
# PolygonExtractor breaks.
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)
else:
self.points.append(point)
......
......@@ -41,10 +41,10 @@ class BasePathProcessor(object):
def sort_layered(self, upper_first=True):
if upper_first:
compare_height = lambda path1, path2: \
path1.points[0].z < path2.points[0].z
path1.points[0][2] < path2.points[0][2]
else:
compare_height = lambda path1, path2: \
path1.points[0].z > path2.points[0].z
path1.points[0][2] > path2.points[0][2]
finished = False
while not finished:
index = 0
......
......@@ -253,8 +253,8 @@ class Fonts(pycam.Plugins.PluginBase):
# add the first point again to close the polygon
points.append(points[0])
for point in points:
x = get_virtual_x(point.x)
y = get_virtual_y(point.y)
x = get_virtual_x(point[0])
y = get_virtual_y(point[1])
draw_points.append((x, y))
drawing_area.draw_lines(gc, draw_points)
final_gc = final_drawing_area.new_gc()
......
......@@ -23,7 +23,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins
from pycam.Geometry.Plane import Plane
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
class ModelProjection(pycam.Plugins.PluginBase):
......@@ -87,8 +87,8 @@ class ModelProjection(pycam.Plugins.PluginBase):
("ProjectionModelCustom",
self.gui.get_object("ProjectionZLevel").get_value())):
if self.gui.get_object(objname).get_active():
plane = Plane(Point(0, 0, z_level), Vector(0, 0, 1))
self.log.info("Projecting 3D model at level z=%g" % plane.p.z)
plane = Plane((0, 0, z_level), (0, 0, 1, 'v'))
self.log.info("Projecting 3D model at level z=%g" % plane.p[2])
new_model = model.get_waterline_contour(plane,
callback=progress.update)
if new_model:
......
......@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Plugins
import pycam.Geometry.Point
from pycam.Geometry.PointUtils import *
GTK_COLOR_MAX = 65535.0
......@@ -144,30 +144,27 @@ class OpenGLViewModelTriangle(pycam.Plugins.PluginBase):
model = models[index]
if not hasattr(model, "triangles"):
continue
get_coords = lambda p: (p.x, p.y, p.z)
def calc_normal(main, normals):
suitable = pycam.Geometry.Point.Vector(0, 0, 0)
suitable = (0, 0, 0, 'v')
for normal, weight in normals:
dot = main.dot(normal)
dot = pdot(main, normal)
if dot > 0:
suitable = suitable.add(normal.mul(weight * dot))
return suitable.normalized()
suitable = padd(suitable, pmul(normal, weight * dot))
return pnormalized(suitable)
vertices = {}
for t in model.triangles():
for p in (t.p1, t.p2, t.p3):
coords = get_coords(p)
if not coords in vertices:
vertices[coords] = []
vertices[coords].append((t.normal.normalized(), t.get_area()))
if not p in vertices:
vertices[p] = []
vertices[p].append((pnormalized(t.normal), t.get_area()))
GL.glBegin(GL.GL_TRIANGLES)
for t in model.triangles():
# The triangle's points are in clockwise order, but GL expects
# counter-clockwise sorting.
for p in (t.p1, t.p3, t.p2):
coords = get_coords(p)
normal = calc_normal(t.normal.normalized(), vertices[coords])
GL.glNormal3f(normal.x, normal.y, normal.z)
GL.glVertex3f(p.x, p.y, p.z)
normal = calc_normal(pnormalized(t.normal), vertices[p])
GL.glNormal3f(normal[0], normal[1], normal[2])
GL.glVertex3f(p[0], p[1], p[2])
GL.glEnd()
removal_list.append(index)
# remove all models that we processed
......
......@@ -75,9 +75,38 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase):
def draw_toolpaths(self):
if self._is_visible():
for toolpath in self.core.get("toolpaths").get_visible():
moves = toolpath.get_moves(self.core.get("gcode_safety_height"))
self._draw_toolpath_moves(moves)
moves = toolpath.get_moves_for_opengl(self.core.get("gcode_safety_height"))
self._draw_toolpath_moves2(moves)
#moves = toolpath.get_moves(self.core.get("gcode_safety_height"))
#self._draw_toolpath_moves(moves)
def _draw_toolpath_moves2(self, paths):
GL = self._GL
GL.glDisable(GL.GL_LIGHTING)
color_rapid = self.core.get("color_toolpath_return")
color_cut = self.core.get("color_toolpath_cut")
show_directions = self.core.get("show_directions")
GL.glMatrixMode(GL.GL_MODELVIEW)
GL.glLoadIdentity()
coords = paths[0]
try:
coords.bind()
GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
GL.glVertexPointerf(coords)
for path in paths[1]:
if path[2]:
GL.glColor4f(color_rapid["red"], color_rapid["green"], color_rapid["blue"], color_rapid["alpha"])
else:
GL.glColor4f(color_cut["red"], color_cut["green"], color_cut["blue"], color_cut["alpha"])
if show_directions:
GL.glDisable(GL.GL_CULL_FACE)
GL.glDrawElements(GL.GL_TRIANGLES, len(path[1]), GL.GL_UNSIGNED_INT, path[1])
GL.glEnable(GL.GL_CULL_FACE)
GL.glDrawElements(GL.GL_LINE_STRIP, len(path[0]), GL.GL_UNSIGNED_INT, path[0])
finally:
coords.unbind()
## Simulate still depends on this pathway
def _draw_toolpath_moves(self, moves):
GL = self._GL
GL.glDisable(GL.GL_LIGHTING)
......@@ -102,9 +131,9 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase):
GL.glFinish()
GL.glBegin(GL.GL_LINE_STRIP)
if not last_position is None:
GL.glVertex3f(last_position.x, last_position.y, last_position.z)
GL.glVertex3f(*last_position)
last_rapid = rapid
GL.glVertex3f(position.x, position.y, position.z)
GL.glVertex3f(*position)
last_position = position
GL.glEnd()
if show_directions:
......@@ -112,4 +141,3 @@ class OpenGLViewToolpath(pycam.Plugins.PluginBase):
p1 = moves[index][0]
p2 = moves[index + 1][0]
pycam.Gui.OpenGLTools.draw_direction_cone(p1, p2)
......@@ -34,7 +34,7 @@ import gtk
import math
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
from pycam.Geometry.utils import sqrt, number
import pycam.Plugins
......@@ -815,12 +815,11 @@ class Camera(object):
if (None, None) in low_high:
return
max_dim = max([high - low for low, high in low_high])
distv = Point(v["distance"][0], v["distance"][1],
v["distance"][2]).normalized()
distv = pnormalized((v["distance"][0], v["distance"][1], v["distance"][2]))
# The multiplier "1.25" is based on experiments. 1.414 (sqrt(2)) should
# be roughly sufficient for showing the diagonal of any model.
distv = distv.mul((max_dim * 1.25) / number(math.sin(v["fovy"] / 2)))
self.view["distance"] = (distv.x, distv.y, distv.z)
distv = pmul(distv, (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
# models (e.g. x=1000) are still visible.
self.view["zfar"] = 100 * max_dim
......@@ -976,9 +975,8 @@ class Camera(object):
# Calculate the proportion of each model axis according to the x axis of
# the screen.
distv = self.view["distance"]
distv = Point(distv[0], distv[1], distv[2]).normalized()
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)
distv = pnormalized((distv[0], distv[1], distv[2]))
factors_x = pnormalized(pcross(distv, (v_up[0], v_up[1], v_up[2])))
return (factors_x, factors_y)
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Plane import Plane
import pycam.Gui.ControlsGTK
......@@ -145,7 +145,7 @@ class ToolpathCrop(pycam.Plugins.PluginBase):
polygons.append(poly.copy())
elif hasattr(model, "get_waterline_contour"):
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():
polygons.append(poly.copy())
# add an offset if requested
......
......@@ -24,7 +24,7 @@ import os
import pycam.Plugins
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")),)
......@@ -181,7 +181,7 @@ class ToolpathExport(pycam.Plugins.PluginBase):
pos_x = self.core.get("touch_off_position_x")
pos_y = self.core.get("touch_off_position_y")
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:
touch_off_pos = None
generator = generator_func(destination,
......
......@@ -22,7 +22,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
import pycam.Plugins
import pycam.Geometry.Point
from pycam.Geometry.PointUtils import *
class ToolpathGrid(pycam.Plugins.PluginBase):
......@@ -97,11 +97,10 @@ class ToolpathGrid(pycam.Plugins.PluginBase):
new_paths = []
for x in range(x_count):
for y in range(y_count):
shift = pycam.Geometry.Point.Vector(x * (x_space + x_dim),
y * (y_space + y_dim), 0)
shift = (x * (x_space + x_dim), y * (y_space + y_dim), 0, 'v')
for path in toolpath.paths:
new_path = pycam.Geometry.Path.Path()
new_path.points = [shift.add(p) for p in path.points]
new_path.points = [padd(shift, p) for p in path.points]
new_paths.append(new_path)
if not self.gui.get_object("KeepOriginal").get_active():
toolpath.paths = new_paths
......
......@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Cutters
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
import ode
try:
......@@ -70,25 +70,25 @@ class ODEBlocks(object):
self.y_step_width, self.z_width))
box.setBody(body)
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)
def process_cutter_movement(self, location_start, location_end):
# 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
if (location_start.x > location_end.x) \
or (location_start.y > location_end.y):
if (location_start[0] > location_end[0]) \
or (location_start[1] > location_end[1]):
location_start, location_end = location_end, location_start
cutter_body = ode.Body(self.world)
cutter_shape, cutter_position_func = self.cutter.get_shape("ODE")
self.space.add(cutter_shape)
cutter_shape.space = self.space
cutter_shape.setBody(cutter_body)
cutter_position_func(location_start.x, location_start.y,
location_start.z)
cutter_shape.extend_shape(location_end.x - location_start.x,
location_end.y - location_start.y,
location_end.z - location_start.z)
cutter_position_func(location_start[0], location_start[1],
location_start[2])
cutter_shape.extend_shape(location_end[0] - location_start[0],
location_end[1] - location_start[1],
location_end[2] - location_start[2])
aabb = cutter_shape.getAABB()
cutter_height = aabb[5] - aabb[4]
# add a ray along the drill to work around an ODE bug in v0.11.1
......@@ -112,8 +112,8 @@ class ODEBlocks(object):
aabb = box.getAABB()
end_height, start_height = aabb[-2:]
height_half = (start_height - end_height) / 2.0
x_pos = box.position.x
y_pos = box.position.y
x_pos = box.position[0]
y_pos = box.position[1]
new_z = end_height
box.setPosition((x_pos, y_pos, end_height - height_half))
loops_left = 12
......@@ -137,7 +137,7 @@ class ODEBlocks(object):
z_pos = new_z - new_height / 2.0
new_box = ode.GeomBox(self.space, (aabb[1] - aabb[0], aabb[3] - aabb[2],
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.setPosition((x_pos, y_pos, z_pos))
self.boxes.insert(box_index, new_box)
......@@ -199,30 +199,30 @@ class ODEBlocks(object):
if (0 <= ix < len(height_field)) \
and (0 <= iy < len(height_field[ix])):
point = height_field[ix][iy]
height_sum += point.z
x_positions.append(point.x)
y_positions.append(point.y)
height_sum += point[2]
x_positions.append(point[0])
y_positions.append(point[1])
divisor += 1
# Use the middle between the x positions of two adjacent boxes,
# _if_ there is a neighbour attached to that corner.
if (min(x_positions) < height_field[x][y].x) \
or (max(x_positions) > height_field[x][y].x):
if (min(x_positions) < height_field[x][y][0]) \
or (max(x_positions) > height_field[x][y][0]):
x_value = (min(x_positions) + max(x_positions)) / 2.0
else:
# There is no adjacent box in x direction. Use the step size
# 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
# same as above for y instead of x
if (min(y_positions) < height_field[x][y].y) \
or (max(y_positions) > height_field[x][y].y):
if (min(y_positions) < height_field[x][y][1]) \
or (max(y_positions) > height_field[x][y][1]):
y_value = (min(y_positions) + max(y_positions)) / 2.0
else:
y_value = height_field[x][y].y \
y_value = height_field[x][y][1] \
+ offsets[1] * self.y_step_width / 2.0
# Create a Point instance describing the position and the
# average height.
points.append(Point(x_value, y_value, height_sum / divisor))
points.append((x_value, y_value, height_sum / divisor))
return points
# draw the surface
GL.glBegin(GL.GL_QUADS)
......@@ -233,11 +233,11 @@ class ODEBlocks(object):
points_around = get_box_height_points(x, y)
# Calculate the "normal" of polygon. We picked up three random
# points of this quadrilateral.
n = self._normal(points_around[1].z, points_around[2].z,
points_around[3].z)
n = self._normal(points_around[1][2], points_around[2][2],
points_around[3][2])
GL.glNormal3f(n[0], n[1], n[2])
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
# appropriate corners for the side faces of the material
for condition, i1, i2 in ((x == 0, 3, 0), (y == 0, 0, 1),
......@@ -245,16 +245,16 @@ class ODEBlocks(object):
(y == self.y_steps - 1, 2, 3)):
# check if this point belongs to an edge of the material
if condition:
n = self._normal(points_around[1].z, points_around[2].z,
points_around[3].z)
n = self._normal(points_around[1][2], points_around[2][2],
points_around[3][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)
GL.glVertex3f(points_around[i1].x, points_around[i1].y,
GL.glVertex3f(points_around[i1][0], points_around[i1][1],
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)
GL.glVertex3f(points_around[i2].x, points_around[i2].y,
GL.glVertex3f(points_around[i2][0], points_around[i2][1],
self.z_offset)
GL.glEnd()
......@@ -21,7 +21,7 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
from pycam.Geometry.utils import sqrt
from pycam.Geometry.Point import Point
from pycam.Geometry.PointUtils import *
import ctypes
import math
......@@ -128,12 +128,12 @@ class ZBuffer(object):
py = self.y[y]
for x in range(minx, maxx):
px = self.x[x]
v0x = t.p3.x - t.p1.x
v0y = t.p3.y - t.p1.y
v1x = t.p2.x - t.p1.x
v1y = t.p2.y - t.p1.y
v2x = px - t.p1.x
v2y = py - t.p1.y
v0x = t.p3[0] - t.p1[0]
v0y = t.p3[1] - t.p1[1]
v1x = t.p2[0] - t.p1[0]
v1y = t.p2[1] - t.p1[1]
v2x = px - t.p1[0]
v2y = py - t.p1[1]
dot00 = v0x*v0x + v0y*v0y
dot01 = v0x*v1x + v0y*v1y
dot02 = v0x*v2x + v0y*v2y
......@@ -143,9 +143,9 @@ class ZBuffer(object):
u = (dot11 * dot02 - dot01 * dot12) * invDenom
v = (dot00 * dot12 - dot01 * dot02) * invDenom
if (u >= -EPSILON) and (v >= -EPSILON) and (u + v <= 1-EPSILON):
v0z = t.p3.z - t.p1.z
v1z = t.p2.z - t.p1.z
pz = t.p1.z + v0z * u + v1z * v
v0z = t.p3[2] - t.p1[2]
v1z = t.p2[2] - t.p1[2]
pz = t.p1[2] + v0z * u + v1z * v
if pz > self.buf[y][x].z:
self.buf[y][x].z = pz
self.buf[y+0][x+0].changed = True
......@@ -155,8 +155,8 @@ class ZBuffer(object):
self.changed = True
def add_cutter(self, c):
cx = c.location.x
cy = c.location.y
cx = c.location[0]
cy = c.location[1]
rsq = c.radiussq
minx = int((c.minx - self.minx) / (self.maxx - self.minx) * self.xres) \
- 1
......@@ -182,13 +182,15 @@ class ZBuffer(object):
maxy = self.yres - 1
if miny > self.yres - 1:
miny = self.yres - 1
p = Point(0, 0, 0)
zaxis = Point(0, 0, -1)
p = (0, 0, 0)
zaxis = (0, 0, -1)
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):
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) \
<= rsq + EPSILON:
(cl, ccp, cp, l) = c.intersect_point(zaxis, p)
......
......@@ -159,7 +159,7 @@ def generate_toolpath(model, tool_settings=None,
warning = "The contour model contains colliding line groups. " + \
"This can cause problems with an engraving offset.\n" + \
"A collision was detected at (%.2f, %.2f, %.2f)." % \
(result.x, result.y, result.z)
(result[0], result[1], result[2])
log.warning(warning)
else:
# no collisions and no user interruption
......
......@@ -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/>.
"""
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import epsilon
from pycam.Geometry.Polygon import PolygonSorter
......@@ -91,9 +91,9 @@ def get_fixed_grid_line(start, end, line_pos, z, step_width=None,
else:
steps = floatrange(start, end, inc=step_width)
if grid_direction == GRID_DIRECTION_X:
get_point = lambda pos: Point(pos, line_pos, z)
get_point = lambda pos: (pos, line_pos, z)
else:
get_point = lambda pos: Point(line_pos, pos, z)
get_point = lambda pos: (line_pos, pos, z)
for pos in steps:
yield get_point(pos)
......@@ -208,7 +208,7 @@ def _get_position(minx, maxx, miny, maxy, z, position):
y = miny
else:
y = maxy
return Point(x, y, z)
return (x, y, z)
def get_spiral_layer_lines(minx, maxx, miny, maxy, z, line_distance_x,
line_distance_y, grid_direction, start_position, current_location):
......@@ -255,27 +255,29 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width,
previous = None
for index, (start, end) in enumerate(lines):
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
offset = edge_vector.normalized().mul(radius)
offset = pmul(pnormalized(edge_vector), radius)
#offset = edge_vector.normalized().mul(radius)
if previous:
start = start.add(offset)
center = previous.add(offset)
up_vector = previous.sub(center).cross(start.sub(center)).normalized()
north = center.add(Vector(1.0, 0.0, 0.0))
start = padd(start, offset)
center = padd(previous, offset)
up_vector = pnormalized(pcross(psub(previous, center), psub(start, center)))
north = padd(center, (1.0, 0.0, 0.0, 'v'))
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
# 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
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()
for arc_index in range(len(arc_points) - 1):
p1_coord = arc_points[arc_index]
p2_coord = arc_points[arc_index + 1]
p1 = Point(p1_coord[0], p1_coord[1], z)
p2 = Point(p2_coord[0], p2_coord[1], z)
p1 = (p1_coord[0], p1_coord[1], z)
p2 = (p2_coord[0], p2_coord[1], z)
rounded_lines.append((p1, p2))
if index != len(lines) - 1:
end = end.sub(offset)
......@@ -294,7 +296,7 @@ def get_spiral_layer(minx, maxx, miny, maxy, z, line_distance, step_width,
else:
steps = floatrange(0.0, line.len, inc=step_width)
for step in steps:
next_point = line.p1.add(line.dir.mul(step))
next_point = padd(line.p1, pmul(line.dir, step))
points.append(next_point)
if reverse:
points.reverse()
......@@ -328,7 +330,7 @@ def get_spiral((low, high), layer_distance, line_distance=None,
def get_lines_layer(lines, z, last_z=None, step_width=None,
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 = []
for line in lines:
if (not last_z is None) and (last_z < line.minz):
......@@ -337,9 +339,9 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
elif line.minz < z < line.maxz:
# Split the line at the point at z level and do the calculation
# for both point pairs.
factor = (z - line.p1.z) / (line.p2.z - line.p1.z)
plane_point = line.p1.add(line.vector.mul(factor))
if line.p1.z < z:
factor = (z - line.p1[2]) / (line.p2[2] - line.p1[2])
plane_point = padd(line.p1, pmul(line.vector, factor))
if line.p1[2] < z:
p1 = get_proj_point(line.p1)
p2 = line.p2
else:
......@@ -348,10 +350,10 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
projected_lines.append(Line(p1, plane_point))
yield Line(plane_point, p2)
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]
# 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
else:
p1, p2 = line.p1, cp
......@@ -378,7 +380,7 @@ def get_lines_layer(lines, z, last_z=None, step_width=None,
else:
steps = floatrange(0.0, line.len, inc=step_width)
for step in steps:
next_point = line.p1.add(line.dir.mul(step))
next_point = padd(line.p1, pmul(line.dir, step))
points.append(next_point)
yield points
......
......@@ -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/>.
"""
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Plane import Plane
from pycam.Geometry.Model import Model
......@@ -34,16 +34,16 @@ def _get_triangles_for_face(pts):
return (t1, t2)
def _add_cuboid_to_model(model, start, direction, height, width):
up = Vector(0, 0, 1).mul(height)
ortho_dir = direction.cross(up).normalized()
start1 = start.add(ortho_dir.mul(-width/2))
start2 = start1.add(up)
start3 = start2.add(ortho_dir.mul(width))
start4 = start3.sub(up)
end1 = start1.add(direction)
end2 = start2.add(direction)
end3 = start3.add(direction)
end4 = start4.add(direction)
up = pmul((0, 0, 1, 'v'), height)
ortho_dir = pnormalized(pcross(direction, up))
start1 = padd(start, pmul(ortho_dir, -width/2))
start2 = padd(start1, up)
start3 = padd(start2, pmul(ortho_dir, width))
start4 = psub(start3, up)
end1 = padd(start1, direction)
end2 = padd(start2, direction)
end3 = padd(start3, direction)
end4 = padd(start4, direction)
faces = ((start1, start2, start3, start4), (start1, end1, end2, start2),
(start2, end2, end3, start3), (start3, end3, end4, start4),
(start4, end4, end1, start1), (end4, end3, end2, end1))
......@@ -54,14 +54,14 @@ def _add_cuboid_to_model(model, start, direction, height, width):
def _add_aligned_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz):
points = (
Point(minx, miny, minz),
Point(maxx, miny, minz),
Point(maxx, maxy, minz),
Point(minx, maxy, minz),
Point(minx, miny, maxz),
Point(maxx, miny, maxz),
Point(maxx, maxy, maxz),
Point(minx, maxy, maxz))
(minx, miny, minz),
(maxx, miny, minz),
(maxx, maxy, minz),
(minx, maxy, minz),
(minx, miny, maxz),
(maxx, miny, maxz),
(maxx, maxy, maxz),
(minx, maxy, maxz))
triangles = []
# lower face
triangles.extend(_get_triangles_for_face(
......@@ -159,10 +159,10 @@ def get_support_distributed(model, z_plane, average_distance,
result = Model()
if not hasattr(model, "get_polygons"):
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:
model = model.get_flat_projection(Plane(Point(0, 0, z_plane),
Vector(0, 0, 1)))
model = model.get_flat_projection(Plane((0, 0, z_plane),
(0, 0, 1, 'v')))
if model and bounds:
model = model.get_cropped_model_by_bounds(bounds)
if model:
......@@ -184,24 +184,21 @@ def get_support_distributed(model, z_plane, average_distance,
bridges = bridge_calculator(polygon, z_plane, min_bridges_per_polygon,
average_distance, avoid_distance)
for pos, direction in bridges:
_add_cuboid_to_model(result, pos, direction.mul(length), height,
thickness)
_add_cuboid_to_model(result, pos, pmul(direction, length), height, thickness)
return result
class _BridgeCorner(object):
# 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):
self.location = location
self.position = p2
self.direction = pycam.Geometry.get_bisector(p1, p2, p3,
self.up_vector).normalized()
preferred_direction = p2.sub(barycenter).normalized()
self.direction = pnormalized(pycam.Geometry.get_bisector(p1, p2, p3, self.up_vector))
preferred_direction = pnormalized(psub(p2, barycenter))
# direction_factor: 0..1 (bigger -> better)
direction_factor = (preferred_direction.dot(self.direction) + 1) / 2
angle = pycam.Geometry.get_angle_pi(p1, p2, p3,
self.up_vector, pi_factor=True)
direction_factor = (pdot(preferred_direction, self.direction) + 1) / 2
angle = pycam.Geometry.get_angle_pi(p1, p2, p3, self.up_vector, pi_factor=True)
# angle_factor: 0..1 (bigger -> better)
if angle > 0.5:
# use only angles > 90 degree
......@@ -275,7 +272,7 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
avoid_distance):
def is_near_list(point_list, point, distance):
for p in point_list:
if p.sub(point).norm <= distance:
if pnorm(psub(p, point)) <= distance:
return True
return False
lines = polygon.get_lines()
......@@ -308,8 +305,8 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
if is_near_list(bridge_positions, position, avoid_distance):
line = polygon.get_lines()[line_index]
# calculate two alternative points on the same line
position1 = position.add(line.p1).div(2)
position2 = position.add(line.p2).div(2)
position1 = pdiv(padd(position, line.p1), 2)
position2 = pdiv(padd(position, line.p2), 2)
if is_near_list(bridge_positions, position1, avoid_distance):
if is_near_list(bridge_positions, position2,
avoid_distance):
......@@ -324,9 +321,8 @@ def _get_edge_bridges(polygon, z_plane, min_bridges, average_distance,
# append the original position (ignoring z_plane)
bridge_positions.append(position)
# move the point to z_plane
position = Point(position.x, position.y, z_plane)
bridge_dir = lines[line_index].dir.cross(
polygon.plane.n).normalized()
position = (position[0], position[1], z_plane)
bridge_dir = pnormalized(pcross(lines[line_index].dir, polygon.plane.n))
result.append((position, bridge_dir))
return result
......@@ -22,20 +22,23 @@ along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
__all__ = ["simplify_toolpath", "ToolpathList", "Toolpath", "Generator"]
from pycam.Geometry.Point import Point
import OpenGL.GL as GL
from OpenGL.arrays import vbo
import numpy
from numpy import array
from pycam.Geometry.PointUtils import *
from pycam.Geometry.Path import Path
from pycam.Geometry.Line import Line
from pycam.Geometry.utils import number, epsilon
import pycam.Utils.log
import random
import os
log = pycam.Utils.log.get_logger()
import math
from itertools import groupby
def _check_colinearity(p1, p2, p3):
v1 = p2.sub(p1).normalized()
v2 = p3.sub(p2).normalized()
v1 = pnormalized(psub(p2, p1))
v2 = pnormalized(psub(p3, p2))
# compare if the normalized distances between p1-p2 and p2-p3 are equal
return v1 == v2
......@@ -69,6 +72,22 @@ class Toolpath(object):
self.parameters = parameters
self._max_safe_distance = 2 * parameters.get("tool_radius", 0)
self._feedrate = parameters.get("tool_feedrate", 300)
self.opengl_safety_height = None
self._minx = None
self._maxx = None
self._miny = None
self._maxy = None
self._minz = None
self._maxz = None
def clear_cache(self):
self.opengl_safety_height = None
self._minx = None
self._maxx = None
self._miny = None
self._maxy = None
self._minz = None
self._maxz = None
def get_params(self):
return dict(self.parameters)
......@@ -82,36 +101,48 @@ class Toolpath(object):
new_paths.append(new_path)
return Toolpath(new_paths, parameters=self.get_params())
def _get_limit_generic(self, attr, func):
def _get_limit_generic(self, idx, func):
path_min = []
for path in self.paths:
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)
@property
def minx(self):
return self._get_limit_generic("x", min)
if self._minx == None:
self._minx = self._get_limit_generic(0, min)
return self._minx
@property
def maxx(self):
return self._get_limit_generic("x", max)
if self._maxx == None:
self._maxx = self._get_limit_generic(0, max)
return self._maxx
@property
def miny(self):
return self._get_limit_generic("y", min)
if self._miny == None:
self._miny = self._get_limit_generic(1, min)
return self._miny
@property
def maxy(self):
return self._get_limit_generic("y", max)
if self._maxy == None:
self._maxy = self._get_limit_generic(1, max)
return self._maxy
@property
def minz(self):
return self._get_limit_generic("z", min)
if self._minz == None:
self._minz = self._get_limit_generic(2, min)
return self._minz
@property
def maxz(self):
return self._get_limit_generic("z", max)
if self._maxz == None:
self._maxz = self._get_limit_generic(2, max)
return self._maxz
def get_meta_data(self):
meta = self.toolpath_settings.get_string()
......@@ -137,12 +168,11 @@ class Toolpath(object):
self.last_pos = new_position
return True
else:
distance = new_position.sub(self.last_pos).norm
distance = pnorm(psub(new_position, self.last_pos))
if self.moved_distance + distance > self.max_movement:
partial = (self.max_movement - self.moved_distance) / \
distance
partial_dest = self.last_pos.add(new_position.sub(
self.last_pos).mul(partial))
partial_dest = padd(self.last_pos, pmul(psub(new_position, self.last_pos), partial))
self.moves.append((partial_dest, rapid))
self.last_pos = partial_dest
# we are finished
......@@ -163,21 +193,18 @@ class Toolpath(object):
continue
p_next = path.points[0]
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):
return result.moves
if ((abs(p_last.x - p_next.x) > epsilon) \
or (abs(p_last.y - p_next.y) > epsilon)):
if ((abs(p_last[0] - p_next[0]) > epsilon) or (abs(p_last[1] - p_next[1]) > epsilon)):
# Draw the connection between the last and the next path.
# Respect the safety height.
if (abs(p_last.z - p_next.z) > epsilon) \
or (p_last.sub(p_next).norm > \
self._max_safe_distance + epsilon):
if (abs(p_last[2] - p_next[2]) > epsilon) or (pnorm(psub(p_last, p_next)) > self._max_safe_distance + epsilon):
# The distance between these two points is too far.
# This condition helps to prevent moves up/down for
# adjacent lines.
safety_last = Point(p_last.x, p_last.y, safety_height)
safety_next = Point(p_next.x, p_next.y, safety_height)
safety_last = (p_last[0], p_last[1], safety_height)
safety_next = (p_next[0], p_next[1], safety_height)
if not result.append(safety_last, True):
return result.moves
if not result.append(safety_next, True):
......@@ -187,10 +214,128 @@ class Toolpath(object):
return result.moves
p_last = path.points[-1]
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)
return result.moves
def _rotate_point(self, rp, sp, v, angle):
vx = v[0]
vy = v[1]
vz = v[2]
x = (sp[0] * (vy ** 2 + vz ** 2) - vx * (sp[1] * vy + sp[2] * vz - vx * rp[0] - vy * rp[1] - vz * rp[2])) * (1 - math.cos(angle)) + rp[0] * math.cos(angle) + (-sp[2] * vy + sp[1] * vz - vz * rp[1] + vy * rp[2]) * math.sin(angle)
y = (sp[1] * (vx ** 2 + vz ** 2) - vy * (sp[0] * vx + sp[2] * vz - vx * rp[0] - vy * rp[1] - vz * rp[2])) * (1 - math.cos(angle)) + rp[1] * math.cos(angle) + (sp[2] * vx - sp[0] * vz + vz * rp[0] - vx * rp[2]) * math.sin(angle)
z = (sp[2] * (vx ** 2 + vy ** 2) - vz * (sp[0] * vx + sp[1] * vy - vx * rp[0] - vy * rp[1] - vz * rp[2])) * (1 - math.cos(angle)) + rp[2] * math.cos(angle) + (-sp[1] * vx + sp[0] * vy - vy * rp[0] + vx * rp[1]) * math.sin(angle)
return (x,y,z)
def draw_direction_cone_mesh(self, p1, p2, position=0.5, precision=12, size=0.1):
distance = psub(p2, p1)
length = pnorm(distance)
direction = pnormalized(distance)
if direction is None or length < 0.5:
# zero-length line
return []
cone_length = length * size
cone_radius = cone_length / 3.0
bottom = padd(p1, pmul(psub(p2, p1), position - size/2))
top = padd(p1, pmul(psub(p2, p1), position + size/2))
#generate a a line perpendicular to this line, cross product is good at this
cross = pcross(direction, (0, 0, -1))
conepoints = []
if pnorm(cross) != 0:
# The line direction is not in line with the z axis.
conep1 = padd(bottom, pmul(cross, cone_radius))
conepoints = [ self._rotate_point(conep1, bottom, direction, x) for x in numpy.linspace(0, 2*math.pi, precision)]
else:
# Z axis
# just add cone radius to the x axis and rotate the point
conep1 = (bottom[0] + cone_radius, bottom[1], bottom[2])
conepoints = [ self._rotate_point(conep1, p1, direction, x) for x in numpy.linspace(0, 2*math.pi, precision)]
triangles = [(top, conepoints[idx], conepoints[idx + 1]) for idx in range ( len(conepoints) - 1)]
return triangles
def get_moves_for_opengl(self, safety_height):
if self.opengl_safety_height != safety_height:
self.make_moves_for_opengl(safety_height)
self.make_vbo_for_moves()
return (self.opengl_coords, self.opengl_indices)
# separate vertex coordinates from line definitions and convert to indices
def make_vbo_for_moves(self):
index = 0
output = []
store_vertices = {}
vertices = []
for path in self.opengl_lines:
indices = []
triangles = []
triangle_indices = []
# compress the lines into a centeral array containing all the vertices
# generate a matching index for each line
for idx in range(len(path[0]) - 1):
point = path[0][idx]
if not point in store_vertices:
store_vertices[point] = index
vertices.insert(index, point)
index += 1
indices.append(store_vertices[point])
point2 = path[0][idx + 1]
if not point2 in store_vertices:
store_vertices[point2] = index
vertices.insert(index, point2)
index += 1
triangles.extend(self.draw_direction_cone_mesh(path[0][idx], path[0][idx + 1]))
for t in triangles:
for p in t:
if not p in store_vertices:
store_vertices[p] = index
vertices.insert(index, p)
index += 1
triangle_indices.append(store_vertices[p])
triangle_indices = array(triangle_indices, dtype=numpy.int32)
indices.append(store_vertices[path[0][-1]])
# this list comprehension removes consecutive duplicate points.
indices = array([x[0] for x in groupby(indices)],dtype=numpy.int32)
output.append((indices, triangle_indices, path[1]))
vertices = array(vertices, dtype=numpy.float32)
self.opengl_coords = vbo.VBO(vertices)
self.opengl_indices = output
#convert moves into lines for dispaly with opengl
def make_moves_for_opengl(self, safety_height):
working_path = []
outpaths = []
for path in self.paths:
if not path:
continue
path = path.points
if len(outpaths) != 0:
lastp = outpaths[-1][0][-1]
working_path.append((path[0][0], path[0][1], safety_height))
if ((abs(lastp[0] - path[0][0]) > epsilon) or (abs(lastp[1] - path[0][1]) > epsilon)):
if (abs(lastp[2] - path[0][2]) > epsilon) or (pnorm(psub(lastp, path[0])) > self._max_safe_distance + epsilon):
outpaths.append((tuple([x[0] for x in groupby(working_path)]), True))
else:
working_path.append((0,0,0))
working_path.append((path[0][0], path[0][1], safety_height))
outpaths.append((working_path, True))
# add this move to last move if last move was not rapid
if outpaths[-1][1] == False:
outpaths[-1] = (outpaths[-1][0] + tuple(path), False)
else:
# last move was rapid, so add last point of rapid to beginning of path
outpaths.append((tuple([x[0] for x in groupby((outpaths[-1][0][-1],) + tuple(path))]), False))
working_path = []
working_path.append(path[-1])
working_path.append((path[-1][0], path[-1][1], safety_height))
outpaths.append((tuple([x[0] for x in groupby(working_path)]), True))
self.opengl_safety_height = safety_height
self.opengl_lines = outpaths
def get_machine_time(self, safety_height=0.0):
""" calculate an estimation of the time required for processing the
toolpath with the machine
......@@ -206,7 +351,7 @@ class Toolpath(object):
# go through all points of the path
for new_pos, rapid in self.get_moves(safety_height):
if not current_position is None:
result += new_pos.sub(current_position).norm / self._feedrate
result += pnorm(psub(new_pos, current_position)) / self._feedrate
current_position = new_pos
return result
......@@ -217,7 +362,7 @@ class Toolpath(object):
# go through all points of the path
for new_pos, rapid in self.get_moves(safety_height):
if not current_position is None:
result += new_pos.sub(current_position).norm
result += pnorm(psub(new_pos, current_position))
current_position = new_pos
return result
......@@ -273,6 +418,7 @@ class Toolpath(object):
if current_path.points:
new_paths.append(current_path)
self.paths = new_paths
self.clear_cache()
class Bounds(object):
......
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