Clean up geom.py

Remove Plane.setOrigin3d() and replace with Plane.origin = ...
This commit is contained in:
Gaël Ecorchard 2015-10-08 22:40:59 +02:00
parent cc82031e51
commit 37890d132b
2 changed files with 333 additions and 330 deletions

View File

@ -943,7 +943,7 @@ class Workplane(CQ):
offset = offset.toTuple() offset = offset.toTuple()
p = self.plane.rotated(rotate) p = self.plane.rotated(rotate)
p.setOrigin3d(self.plane.toWorldCoords(offset)) p.origin = self.plane.toWorldCoords(offset)
ns = self.newObject([p.origin]) ns = self.newObject([p.origin])
ns.plane = p ns.plane = p

View File

@ -22,17 +22,20 @@ import cadquery
import FreeCAD import FreeCAD
import Part as FreeCADPart import Part as FreeCADPart
def sortWiresByBuildOrder(wireList,plane,result=[]):
""" def sortWiresByBuildOrder(wireList, plane, result=[]):
Tries to determine how wires should be combined into faces. """Tries to determine how wires should be combined into faces.
Assume:
The wires make up one or more faces, which could have 'holes' Assume:
Outer wires are listed ahead of inner wires The wires make up one or more faces, which could have 'holes'
there are no wires inside wires inside wires ( IE, islands -- we can deal with that later on ) Outer wires are listed ahead of inner wires
none of the wires are construction wires there are no wires inside wires inside wires
Compute: ( IE, islands -- we can deal with that later on )
one or more sets of wires, with the outer wire listed first, and inner ones none of the wires are construction wires
Returns, list of lists. Compute:
one or more sets of wires, with the outer wire listed first, and inner
ones
Returns, list of lists.
""" """
result = [] result = []
@ -42,13 +45,14 @@ def sortWiresByBuildOrder(wireList,plane,result=[]):
group = [outerWire] group = [outerWire]
otherWires = list(remainingWires) otherWires = list(remainingWires)
for w in otherWires: for w in otherWires:
if plane.isWireInside(outerWire,w): if plane.isWireInside(outerWire, w):
group.append(w) group.append(w)
remainingWires.remove(w) remainingWires.remove(w)
result.append(group) result.append(group)
return result return result
class Vector(object): class Vector(object):
"""Create a 3-dimensional vector """Create a 3-dimensional vector
@ -60,22 +64,15 @@ class Vector(object):
* a vector ( in which case it is copied ) * a vector ( in which case it is copied )
* a 3-tuple * a 3-tuple
* three float values, x, y, and z * three float values, x, y, and z
FreeCAD's vector implementation has a dumb
implementation for multiply and add-- they modify the existing
value and return a copy as well.
This vector is immutable-- all mutations return a copy!
""" """
def __init__(self,*args): def __init__(self, *args):
if len(args) == 3: if len(args) == 3:
fV = FreeCAD.Base.Vector(args[0],args[1],args[2]) fV = FreeCAD.Base.Vector(args[0], args[1], args[2])
elif len(args) == 1: elif len(args) == 1:
if isinstance(args[0], Vector): if isinstance(args[0], Vector):
fV = args[0].wrapped fV = args[0].wrapped
elif isinstance(args[0], tuple): elif isinstance(args[0], tuple):
fV = FreeCAD.Base.Vector(args[0][0],args[0][1],args[0][2]) fV = FreeCAD.Base.Vector(args[0][0], args[0][1], args[0][2])
elif isinstance(args[0], FreeCAD.Base.Vector): elif isinstance(args[0], FreeCAD.Base.Vector):
fV = args[0] fV = args[0]
else: else:
@ -85,58 +82,66 @@ class Vector(object):
else: else:
raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple") raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple")
self.wrapped = fV self._wrapped = fV
self.Length = fV.Length
self.x = fV.x @property
self.y = fV.y def x(self):
self.z = fV.z return self.wrapped.x
@property
def y(self):
return self.wrapped.y
@property
def z(self):
return self.wrapped.z
@property
def Length(self):
return self.wrapped.Length
@property
def wrapped(self):
return self._wrapped
def toTuple(self): def toTuple(self):
return (self.x,self.y,self.z) return (self.x, self.y, self.z)
#TODO: is it possible to create a dynamic proxy without all this code? # TODO: is it possible to create a dynamic proxy without all this code?
def cross(self,v): def cross(self, v):
return Vector( self.wrapped.cross(v.wrapped)) return Vector(self.wrapped.cross(v.wrapped))
def dot(self,v): def dot(self, v):
return self.wrapped.dot(v.wrapped) return self.wrapped.dot(v.wrapped)
def sub(self,v): def sub(self, v):
return Vector(self.wrapped.sub(v.wrapped)) return Vector(self.wrapped.sub(v.wrapped))
def add(self,v): def add(self, v):
return Vector( self.wrapped.add(v.wrapped)) return Vector(self.wrapped.add(v.wrapped))
def multiply(self,scale): def multiply(self, scale):
""" """Return a copy multiplied by the provided scalar"""
Return self multiplied by the provided scalar tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
return Vector(tmp_fc_vector.multiply(scale))
Note: FreeCAD has a bug here, where the
base is also modified
"""
tmp = FreeCAD.Base.Vector(self.wrapped)
return Vector( tmp.multiply(scale))
def normalize(self): def normalize(self):
""" """Return a normalized version of this vector"""
Return normalized version this vector. tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
tmp_fc_vector.normalize()
Note: FreeCAD has a bug here, where the return Vector(tmp_fc_vector)
base is also modified
"""
tmp = FreeCAD.Base.Vector(self.wrapped)
tmp.normalize()
return Vector( tmp )
def Center(self): def Center(self):
""" """Return the vector itself
The center of myself is myself. The center of myself is myself.
Provided so that vectors, vertexes, and other shapes all support a common interface, Provided so that vectors, vertexes, and other shapes all support a
when Center() is requested for all objects on the stack common interface, when Center() is requested for all objects on the
stack.
""" """
return self return self
def getAngle(self,v): def getAngle(self, v):
return self.wrapped.getAngle(v.wrapped) return self.wrapped.getAngle(v.wrapped)
def distanceToLine(self): def distanceToLine(self):
@ -154,128 +159,116 @@ class Vector(object):
def __hash__(self): def __hash__(self):
return self.wrapped.__hash__() return self.wrapped.__hash__()
def __add__(self,v): def __add__(self, v):
return self.add(v) return self.add(v)
def __len__(self):
return self.Length
def __repr__(self): def __repr__(self):
return self.wrapped.__repr__() return self.wrapped.__repr__()
def __str__(self): def __str__(self):
return self.wrapped.__str__() return self.wrapped.__str__()
def __len__(self,other): def __ne__(self, other):
return self.wrapped.__len__(other)
def __lt__(self,other):
return self.wrapped.__lt__(other)
def __gt__(self,other):
return self.wrapped.__gt__(other)
def __ne__(self,other):
return self.wrapped.__ne__(other) return self.wrapped.__ne__(other)
def __le__(self,other): def __eq__(self, other):
return self.wrapped.__le__(other)
def __ge__(self,other):
return self.wrapped.__ge__(other)
def __eq__(self,other):
return self.wrapped.__eq__(other) return self.wrapped.__eq__(other)
class Matrix:
"""
A 3d , 4x4 transformation matrix.
Used to move geometry in space. class Matrix:
"""A 3d , 4x4 transformation matrix.
Used to move geometry in space.
""" """
def __init__(self,matrix=None): def __init__(self, matrix=None):
if matrix == None: if matrix is None:
self.wrapped = FreeCAD.Base.Matrix() self.wrapped = FreeCAD.Base.Matrix()
else: else:
self.wrapped = matrix self.wrapped = matrix
def rotateX(self,angle): def rotateX(self, angle):
self.wrapped.rotateX(angle) self.wrapped.rotateX(angle)
def rotateY(self,angle): def rotateY(self, angle):
self.wrapped.rotateY(angle) self.wrapped.rotateY(angle)
class Plane: class Plane(object):
""" """A 2D coordinate system in space
A 2d coordinate system in space, with the x-y axes on the a plane, and a particular point as the origin.
A plane allows the use of 2-d coordinates, which are later converted to global, 3d coordinates when A 2D coordinate system in space, with the x-y axes on the plane, and a
the operations are complete. particular point as the origin.
Frequently, it is not necessary to create work planes, as they can be created automatically from faces. A plane allows the use of 2-d coordinates, which are later converted to
global, 3d coordinates when the operations are complete.
Frequently, it is not necessary to create work planes, as they can be
created automatically from faces.
""" """
@classmethod @classmethod
def named(cls,stdName,origin=(0,0,0)): def named(cls, stdName, origin=(0, 0, 0)):
""" """Create a predefined Plane based on the conventional names.
Create a predefined Plane based on the conventional names.
:param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom :param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom)
:type stdName: string :type stdName: string
:param origin: the desired origin, specified in global coordinates :param origin: the desired origin, specified in global coordinates
:type origin: 3-tuple of the origin of the new plane, in global coorindates. :type origin: 3-tuple of the origin of the new plane, in global coorindates.
Available named planes are as follows. Direction references refer to the global Available named planes are as follows. Direction references refer to
directions the global directions.
=========== ======= ======= ====== =========== ======= ======= ======
Name xDir yDir zDir Name xDir yDir zDir
=========== ======= ======= ====== =========== ======= ======= ======
XY +x +y +z XY +x +y +z
YZ +y +z +x YZ +y +z +x
ZX +z +x +y ZX +z +x +y
XZ +x +z -y XZ +x +z -y
YX +y +x -z YX +y +x -z
ZY +z +y -x ZY +z +y -x
front +x +y +z front +x +y +z
back -x +y -z back -x +y -z
left +z +y -x left +z +y -x
right -z +y +x right -z +y +x
top +x -z +y top +x -z +y
bottom +x +z -y bottom +x +z -y
=========== ======= ======= ====== =========== ======= ======= ======
""" """
namedPlanes = { namedPlanes = {
#origin, xDir, normal # origin, xDir, normal
'XY' : Plane(Vector(origin),Vector((1,0,0)),Vector((0,0,1))), 'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
'YZ' : Plane(Vector(origin),Vector((0,1,0)),Vector((1,0,0))), 'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)), 'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
'XZ' : Plane(Vector(origin),Vector((1,0,0)),Vector((0,-1,0))), 'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
'YX': Plane(origin, (0, 1, 0), (0, 0, -1)), 'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)), 'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
'front': Plane(Vector(origin),Vector((1,0,0)),Vector((0,0,1))), 'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
'back': Plane(Vector(origin),Vector((-1,0,0)),Vector((0,0,-1))), 'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
'left': Plane(Vector(origin),Vector((0,0,1)),Vector((-1,0,0))), 'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
'right': Plane(Vector(origin),Vector((0,0,-1)),Vector((1,0,0))), 'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
'top': Plane(Vector(origin),Vector((1,0,0)),Vector((0,1,0))), 'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
'bottom': Plane(Vector(origin),Vector((1,0,0)),Vector((0,-1,0))) 'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
} }
if namedPlanes.has_key(stdName): try:
return namedPlanes[stdName] return namedPlanes[stdName]
else: except KeyError:
raise ValueError("Supported names are %s " % str(namedPlanes.keys()) ) raise ValueError('Supported names are {}'.format(
namedPlanes.keys()))
@classmethod @classmethod
def XY(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
return Plane.named('XY',origin) plane = Plane.named('XY', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def YZ(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
return Plane.named('YZ',origin) plane = Plane.named('YZ', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)): def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
@ -284,8 +277,10 @@ class Plane:
return plane return plane
@classmethod @classmethod
def XZ(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
return Plane.named('XZ',origin) plane = Plane.named('XZ', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)): def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
@ -300,43 +295,53 @@ class Plane:
return plane return plane
@classmethod @classmethod
def front(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
return Plane.named('front',origin) plane = Plane.named('front', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def back(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
return Plane.named('back',origin) plane = Plane.named('back', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def left(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
return Plane.named('left',origin) plane = Plane.named('left', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def right(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
return Plane.named('right',origin) plane = Plane.named('right', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def top(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
return Plane.named('top',origin) plane = Plane.named('top', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod @classmethod
def bottom(cls,origin=(0,0,0),xDir=Vector(1,0,0)): def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
return Plane.named('bottom',origin) plane = Plane.named('bottom', origin)
plane._setPlaneDir(xDir)
return plane
def __init__(self, origin, xDir, normal ): def __init__(self, origin, xDir, normal):
""" """Create a Plane with an arbitrary orientation
Create a Plane with an arbitrary orientation
TODO: project x and y vectors so they work even if not orthogonal
:param origin: the origin
:type origin: a three-tuple of the origin, in global coordinates
:param xDir: a vector representing the xDirection.
:type xDir: a three-tuple representing a vector, or a FreeCAD Vector
:param normal: the normal direction for the new plane
:type normal: a FreeCAD Vector
:raises: ValueError if the specified xDir is not orthogonal to the provided normal.
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
TODO: project x and y vectors so they work even if not orthogonal
:param origin: the origin
:type origin: a three-tuple of the origin, in global coordinates
:param xDir: a vector representing the xDirection.
:type xDir: a three-tuple representing a vector, or a FreeCAD Vector
:param normal: the normal direction for the new plane
:type normal: a FreeCAD Vector
:raises: ValueError if the specified xDir is not orthogonal to the provided normal.
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
""" """
normal = Vector(normal) normal = Vector(normal)
if (normal.Length == 0.0): if (normal.Length == 0.0):
@ -347,90 +352,92 @@ class Plane:
raise ValueError('xDir should be non null') raise ValueError('xDir should be non null')
self._setPlaneDir(xDir) self._setPlaneDir(xDir)
#stupid freeCAD!!!!! multiply has a bug that changes the original also!
self.invZDir = self.zDir.multiply(-1.0) self.invZDir = self.zDir.multiply(-1.0)
self.setOrigin3d(origin) self.origin = origin
@property
def origin(self):
return self._origin
def setOrigin3d(self,originVector): @origin.setter
""" def origin(self, value):
Move the origin of the plane, leaving its orientation and xDirection unchanged. self._origin = Vector(value)
:param originVector: the new center of the plane, *global* coordinates
:type originVector: a FreeCAD Vector.
:return: void
"""
self.origin = Vector(originVector)
self._calcTransforms() self._calcTransforms()
def setOrigin2d(self,x,y): def setOrigin2d(self, x, y):
"""Set a new origin in the plane itself
Set a new origin in the plane itself. The plane's orientation and
xDrection are unaffected.
:param float x: offset in the x direction
:param float y: offset in the y direction
:return: void
The new coordinates are specified in terms of the current 2-d system.
As an example:
p = Plane.XY()
p.setOrigin2d(2, 2)
p.setOrigin2d(2, 2)
results in a plane with its origin at (x, y) = (4, 4) in global
coordinates. Both operations were relative to local coordinates of the
plane.
""" """
Set a new origin based of the plane. The plane's orientation and xDrection are unaffected. self.origin = self.toWorldCoords((x, y))
:param float x: offset in the x direction def isWireInside(self, baseWire, testWire):
:param float y: offset in the y direction """Determine if testWire is inside baseWire
:return: void
the new coordinates are specified in terms of the current 2-d system. As an example:: Determine if testWire is inside baseWire, after both wires are projected
p = Plane.XY() into the current plane.
p.setOrigin2d(2,2)
p.setOrigin2d(2,2)
results in a plane with its origin at (x,y)=(4,4) in global coordinates. The both operations were relative to :param baseWire: a reference wire
local coordinates of the plane. :type baseWire: a FreeCAD wire
:param testWire: another wire
:type testWire: a FreeCAD wire
:return: True if testWire is inside baseWire, otherwise False
If either wire does not lie in the current plane, it is projected into
the plane first.
*WARNING*: This method is not 100% reliable. It uses bounding box
tests, but needs more work to check for cases when curves are complex.
Future Enhancements:
* Discretizing points along each curve to provide a more reliable
test.
""" """
self.setOrigin3d(self.toWorldCoords((x,y))) # TODO: also use a set of points along the wire to test as well.
# TODO: would it be more efficient to create objects in the local
def isWireInside(self,baseWire,testWire): # coordinate system, and then transform to global
""" # coordinates upon extrusion?
Determine if testWire is inside baseWire, after both wires are projected into the current plane
:param baseWire: a reference wire
:type baseWire: a FreeCAD wire
:param testWire: another wire
:type testWire: a FreeCAD wire
:return: True if testWire is inside baseWire, otherwise False
If either wire does not lie in the current plane, it is projected into the plane first.
*WARNING*: This method is not 100% reliable. It uses bounding box tests, but needs
more work to check for cases when curves are complex.
Future Enhancements:
* Discretizing points along each curve to provide a more reliable test
"""
#TODO: also use a set of points along the wire to test as well.
#TODO: would it be more efficient to create objects in the local coordinate system, and then transform to global
#coordinates upon extrusion?
tBaseWire = baseWire.transformGeometry(self.fG) tBaseWire = baseWire.transformGeometry(self.fG)
tTestWire = testWire.transformGeometry(self.fG) tTestWire = testWire.transformGeometry(self.fG)
#these bounding boxes will have z=0, since we transformed them into the space of the plane # These bounding boxes will have z=0, since we transformed them into the
# space of the plane.
bb = tBaseWire.BoundingBox() bb = tBaseWire.BoundingBox()
tb = tTestWire.BoundingBox() tb = tTestWire.BoundingBox()
#findOutsideBox actually inspects both ways, here we only want to # findOutsideBox actually inspects both ways, here we only want to
#know if one is inside the other # know if one is inside the other
x = BoundBox.findOutsideBox2D(bb,tb) return bb == BoundBox.findOutsideBox2D(bb, tb)
return x == bb
def toLocalCoords(self,obj): def toLocalCoords(self, obj):
""" """Project the provided coordinates onto this plane
Project the provided coordinates onto this plane.
:param obj: an object or vector to convert :param obj: an object or vector to convert
:type vector: a vector or shape :type vector: a vector or shape
:return: an object of the same type as the input, but converted to local coordinates :return: an object of the same type, but converted to local coordinates
Most of the time, the z-coordinate returned will be zero, because most operations Most of the time, the z-coordinate returned will be zero, because most
based on a plane are all 2-d. Occasionally, though, 3-d points outside of the current plane are transformed. operations based on a plane are all 2-d. Occasionally, though, 3-d
One such example is :py:meth:`Workplane.box`, where 3-d corners of a box are transformed to orient the box in space points outside of the current plane are transformed. One such example is
correctly. :py:meth:`Workplane.box`, where 3-d corners of a box are transformed to
orient the box in space correctly.
""" """
if isinstance(obj, Vector): if isinstance(obj, Vector):
@ -438,96 +445,89 @@ class Plane:
elif isinstance(obj, cadquery.Shape): elif isinstance(obj, cadquery.Shape):
return obj.transformShape(self.rG) return obj.transformShape(self.rG)
else: else:
raise ValueError("Dont know how to convert type %s to local coordinates" % str(type(obj))) raise ValueError(
"Don't know how to convert type {} to local coordinates".format(
type(obj)))
def toWorldCoords(self, tuplePoint): def toWorldCoords(self, tuplePoint):
""" """Convert a point in local coordinates to global coordinates
Convert a point in local coordinates to global coordinates.
:param tuplePoint: point in local coordinates to convert
:type tuplePoint: a 2 or three tuple of float. the third value is taken to be zero if not supplied
:return: a Vector in global coordinates
:param tuplePoint: point in local coordinates to convert.
:type tuplePoint: a 2 or three tuple of float. The third value is taken to be zero if not supplied.
:return: a Vector in global coordinates
""" """
if isinstance(tuplePoint, Vector): if isinstance(tuplePoint, Vector):
v = tuplePoint v = tuplePoint
elif len(tuplePoint) == 2: elif len(tuplePoint) == 2:
v = Vector(tuplePoint[0], tuplePoint[1], 0) v = Vector(tuplePoint[0], tuplePoint[1], 0)
else: else:
v = Vector(tuplePoint[0],tuplePoint[1],tuplePoint[2]) v = Vector(tuplePoint)
return Vector(self.rG.multiply(v.wrapped)) return Vector(self.rG.multiply(v.wrapped))
def rotated(self, rotate=(0, 0, 0)):
"""Returns a copy of this plane, rotated about the specified axes
def rotated(self,rotate=(0,0,0)): Since the z axis is always normal the plane, rotating around Z will
always produce a plane that is parallel to this one.
The origin of the workplane is unaffected by the rotation.
Rotations are done in order x, y, z. If you need a different order,
manually chain together multiple rotate() commands.
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
:return: a copy of this plane rotated as requested.
""" """
returns a copy of this plane, rotated about the specified axes, as measured from horizontal rotate = Vector(rotate)
# Convert to radians.
rotate = rotate.multiply(math.pi / 180.0)
Since the z axis is always normal the plane, rotating around Z will always produce a plane # Compute rotation matrix.
that is parallel to this one
the origin of the workplane is unaffected by the rotation.
rotations are done in order x,y,z. if you need a different order, manually chain together multiple .rotate()
commands
:param rotate: Vector [xDegrees,yDegrees,zDegrees]
:return: a copy of this plane rotated as requested
"""
if rotate.__class__.__name__ != 'Vector':
rotate = Vector(rotate)
#convert to radians
rotate = rotate.multiply(math.pi / 180.0 )
#compute rotation matrix
m = FreeCAD.Base.Matrix() m = FreeCAD.Base.Matrix()
m.rotateX(rotate.x) m.rotateX(rotate.x)
m.rotateY(rotate.y) m.rotateY(rotate.y)
m.rotateZ(rotate.z) m.rotateZ(rotate.z)
#compute the new plane # Compute the new plane.
newXdir = Vector(m.multiply(self.xDir.wrapped)) newXdir = Vector(m.multiply(self.xDir.wrapped))
newZdir = Vector(m.multiply(self.zDir.wrapped)) newZdir = Vector(m.multiply(self.zDir.wrapped))
newP= Plane(self.origin,newXdir,newZdir) return Plane(self.origin, newXdir, newZdir)
return newP
def rotateShapes(self, listOfShapes, rotationMatrix): def rotateShapes(self, listOfShapes, rotationMatrix):
"""Rotate the listOfShapes by the supplied rotationMatrix
@param listOfShapes is a list of shape objects
@param rotationMatrix is a geom.Matrix object.
returns a list of shape objects rotated according to the rotationMatrix.
""" """
rotate the listOfShapes by the rotationMatrix supplied. # Compute rotation matrix (global --> local --> rotate --> global).
@param listOfShapes is a list of shape objects # rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
@param rotationMatrix is a geom.Matrix object. # rm = self.computeTransform(rotationMatrix)
returns a list of shape objects rotated according to the rotationMatrix
"""
#compute rotation matrix ( global --> local --> rotate --> global ) # There might be a better way, but to do this rotation takes 3 steps:
#rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG) # - transform geometry to local coordinates
rm = self.computeTransform(rotationMatrix) # - then rotate about x
# - then transform back to global coordinates.
#There might be a better way, but to do this rotation takes 3 steps
#transform geometry to local coordinates
#then rotate about x
#then transform back to global coordinates
resultWires = [] resultWires = []
for w in listOfShapes: for w in listOfShapes:
mirrored = w.transformGeometry(rotationMatrix.wrapped) mirrored = w.transformGeometry(rotationMatrix.wrapped)
# If the first vertex of the second wire is not coincident with the first or last vertices of the first wire # If the first vertex of the second wire is not coincident with the
# we have to fix the wire so that it will mirror correctly # first or last vertices of the first wire we have to fix the wire
if (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and # so that it will mirror correctly.
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and if ((mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or \ mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or
(mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and (mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z): mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z)):
resultWires.append(mirrored) resultWires.append(mirrored)
else: else:
# Make sure that our mirrored edges meet up and are ordered properly # Make sure that our mirrored edges meet up and are ordered
# properly.
aEdges = w.wrapped.Edges aEdges = w.wrapped.Edges
aEdges.extend(mirrored.wrapped.Edges) aEdges.extend(mirrored.wrapped.Edges)
comp = FreeCADPart.Compound(aEdges) comp = FreeCADPart.Compound(aEdges)
@ -541,40 +541,44 @@ class Plane:
"""Set the vectors parallel to the plane, i.e. xDir and yDir""" """Set the vectors parallel to the plane, i.e. xDir and yDir"""
if (self.zDir.dot(xDir) > 1e-5): if (self.zDir.dot(xDir) > 1e-5):
raise ValueError('xDir must be parralel to the plane') raise ValueError('xDir must be parralel to the plane')
xDir = Vector(xDir)
self.xDir = xDir.normalize() self.xDir = xDir.normalize()
self.yDir = self.zDir.cross(self.xDir).normalize() self.yDir = self.zDir.cross(self.xDir).normalize()
def _calcTransforms(self): def _calcTransforms(self):
"""Computes transformation matrices to convert between coordinates
Computes transformation matrices to convert between local and global
coordinates.
""" """
Computes transformation martrices to convert between local and global coordinates # r is the forward transformation matrix from world to local coordinates
""" # ok i will be really honest, i cannot understand exactly why this works
#r is the forward transformation matrix from world to local coordinates # something bout the order of the translation and the rotation.
#ok i will be really honest-- i cannot understand exactly why this works
#something bout the order of the translation and the rotation.
# the double-inverting is strange, and I don't understand it. # the double-inverting is strange, and I don't understand it.
r = FreeCAD.Base.Matrix() r = FreeCAD.Base.Matrix()
#forward transform must rotate and adjust for origin # Forward transform must rotate and adjust for origin.
(r.A11, r.A12, r.A13 ) = (self.xDir.x, self.xDir.y, self.xDir.z ) (r.A11, r.A12, r.A13) = (self.xDir.x, self.xDir.y, self.xDir.z)
(r.A21, r.A22, r.A23 ) = (self.yDir.x, self.yDir.y, self.yDir.z ) (r.A21, r.A22, r.A23) = (self.yDir.x, self.yDir.y, self.yDir.z)
(r.A31, r.A32, r.A33 ) = (self.zDir.x, self.zDir.y, self.zDir.z ) (r.A31, r.A32, r.A33) = (self.zDir.x, self.zDir.y, self.zDir.z)
invR = r.inverse() invR = r.inverse()
(invR.A14,invR.A24,invR.A34) = (self.origin.x,self.origin.y,self.origin.z) invR.A14 = self.origin.x
invR.A24 = self.origin.y
invR.A34 = self.origin.z
( self.rG,self.fG ) = ( invR,invR.inverse() ) self.rG = invR
self.fG = invR.inverse()
def computeTransform(self,tMatrix): def computeTransform(self, tMatrix):
""" """Computes the 2-d projection of the supplied matrix"""
Computes the 2-d projection of the supplied matrix
""" return Matrix(self.fG.multiply(tMatrix.wrapped).multiply(self.rG))
rm = self.fG.multiply(tMatrix.wrapped).multiply(self.rG)
return Matrix(rm)
class BoundBox(object): class BoundBox(object):
"A BoundingBox for an object or set of objects. Wraps the FreeCAD one" """A BoundingBox for an object or set of objects. Wraps the FreeCAD one"""
def __init__(self,bb): def __init__(self, bb):
self.wrapped = bb self.wrapped = bb
self.xmin = bb.XMin self.xmin = bb.XMin
self.xmax = bb.XMax self.xmax = bb.XMax
@ -588,55 +592,54 @@ class BoundBox(object):
self.center = Vector(bb.Center) self.center = Vector(bb.Center)
self.DiagonalLength = bb.DiagonalLength self.DiagonalLength = bb.DiagonalLength
def add(self,obj): def add(self, obj):
""" """Returns a modified (expanded) bounding box
returns a modified (expanded) bounding box
obj can be one of several things: obj can be one of several things:
1. a 3-tuple corresponding to x,y, and z amounts to add 1. a 3-tuple corresponding to x,y, and z amounts to add
2. a vector, containing the x,y,z values to add 2. a vector, containing the x,y,z values to add
3. another bounding box, where a new box will be created that encloses both 3. another bounding box, where a new box will be created that
encloses both.
this bounding box is not changed This bounding box is not changed.
""" """
tmp = FreeCAD.Base.BoundBox(self.wrapped) tmp = FreeCAD.Base.BoundBox(self.wrapped)
if type(obj) is tuple: if isinstance(obj, tuple):
tmp.add(obj[0],obj[1],obj[2]) tmp.add(obj[0], obj[1], obj[2])
elif type(obj) is Vector: elif isinstance(obj, Vector):
tmp.add(obj.fV) tmp.add(obj.fV)
elif type(obj) is BoundBox: elif isinstance(obj, BoundBox):
tmp.add(obj.wrapped) tmp.add(obj.wrapped)
return BoundBox(tmp) return BoundBox(tmp)
@classmethod @classmethod
def findOutsideBox2D(cls,b1, b2): def findOutsideBox2D(cls, b1, b2):
""" """Compares bounding boxes
compares bounding boxes. returns none if neither is inside the other. returns
the outer one if either is outside the other
BoundBox.isInside works in 3d, but this is a 2d bounding box, so it doesnt work correctly Compares bounding boxes. Returns none if neither is inside the other.
plus, there was all kinds of rounding error in the built-in implementation i do not understand. Returns the outer one if either is outside the other.
Here we assume that the b
BoundBox.isInside works in 3d, but this is a 2d bounding box, so it
doesn't work correctly plus, there was all kinds of rounding error in
the built-in implementation i do not understand.
""" """
bb1 = b1.wrapped fc_bb1 = b1.wrapped
bb2 = b2.wrapped fc_bb2 = b2.wrapped
if bb1.XMin < bb2.XMin and\ if (fc_bb1.XMin < fc_bb2.XMin and
bb1.XMax > bb2.XMax and\ fc_bb1.XMax > fc_bb2.XMax and
bb1.YMin < bb2.YMin and\ fc_bb1.YMin < fc_bb2.YMin and
bb1.YMax > bb2.YMax: fc_bb1.YMax > fc_bb2.YMax):
return b1 return b1
if bb2.XMin < bb1.XMin and\ if (fc_bb2.XMin < fc_bb1.XMin and
bb2.XMax > bb1.XMax and\ fc_bb2.XMax > fc_bb1.XMax and
bb2.YMin < bb1.YMin and\ fc_bb2.YMin < fc_bb1.YMin and
bb2.YMax > bb1.YMax: fc_bb2.YMax > fc_bb1.YMax):
return b2 return b2
return None return None
def isInside(self,anotherBox): def isInside(self, anotherBox):
""" """Is the provided bounding box inside this one?"""
is the provided bounding box inside this one?
"""
return self.wrapped.isInside(anotherBox.wrapped) return self.wrapped.isInside(anotherBox.wrapped)