First attempt at python2 and python3 support in single codebase
4 tests failing on python3 (CQGI, AMF export)
This commit is contained in:
@ -1,21 +1,21 @@
|
|||||||
#these items point to the OCC implementation
|
# these items point to the OCC implementation
|
||||||
from .occ_impl.geom import Plane,BoundBox,Vector,Matrix
|
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
||||||
from .occ_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound,sortWiresByBuildOrder
|
from .occ_impl.shapes import Shape, Vertex, Edge, Face, Wire, Solid, Shell, Compound, sortWiresByBuildOrder
|
||||||
from .occ_impl import exporters
|
from .occ_impl import exporters
|
||||||
from .occ_impl import importers
|
from .occ_impl import importers
|
||||||
|
|
||||||
#these items are the common implementation
|
# these items are the common implementation
|
||||||
|
|
||||||
#the order of these matter
|
# the order of these matter
|
||||||
from .selectors import *
|
from .selectors import *
|
||||||
from .cq import *
|
from .cq import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
|
'CQ', 'Workplane', 'plugins', 'selectors', 'Plane', 'BoundBox', 'Matrix', 'Vector', 'sortWiresByBuildOrder',
|
||||||
'Shape','Vertex','Edge','Wire','Face','Solid','Shell','Compound','exporters', 'importers',
|
'Shape', 'Vertex', 'Edge', 'Wire', 'Face', 'Solid', 'Shell', 'Compound', 'exporters', 'importers',
|
||||||
'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector',
|
'NearestToPointSelector', 'ParallelDirSelector', 'DirectionSelector', 'PerpendicularDirSelector',
|
||||||
'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins'
|
'TypeSelector', 'DirectionMinMaxSelector', 'StringSyntaxSelector', 'Selector', 'plugins'
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
|
342
cadquery/cq.py
342
cadquery/cq.py
@ -31,9 +31,11 @@ class CQContext(object):
|
|||||||
All objects in the same CQ chain share a reference to this same object instance
|
All objects in the same CQ chain share a reference to this same object instance
|
||||||
which allows for shared state when needed,
|
which allows for shared state when needed,
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pendingWires = [] # a list of wires that have been created and need to be extruded
|
self.pendingWires = [] # a list of wires that have been created and need to be extruded
|
||||||
self.pendingEdges = [] # a list of created pending edges that need to be joined into wires
|
# a list of created pending edges that need to be joined into wires
|
||||||
|
self.pendingEdges = []
|
||||||
# a reference to the first point for a set of edges.
|
# a reference to the first point for a set of edges.
|
||||||
# Used to determine how to behave when close() is called
|
# Used to determine how to behave when close() is called
|
||||||
self.firstPoint = None
|
self.firstPoint = None
|
||||||
@ -167,8 +169,8 @@ class CQ(object):
|
|||||||
Most of the time, both objects will contain a single solid, which is
|
Most of the time, both objects will contain a single solid, which is
|
||||||
combined and returned on the stack of the new object.
|
combined and returned on the stack of the new object.
|
||||||
"""
|
"""
|
||||||
#loop through current stack objects, and combine them
|
# loop through current stack objects, and combine them
|
||||||
#TODO: combine other types of objects as well, like edges and wires
|
# TODO: combine other types of objects as well, like edges and wires
|
||||||
toCombine = self.solids().vals()
|
toCombine = self.solids().vals()
|
||||||
|
|
||||||
if otherCQToCombine:
|
if otherCQToCombine:
|
||||||
@ -178,13 +180,13 @@ class CQ(object):
|
|||||||
if len(toCombine) < 1:
|
if len(toCombine) < 1:
|
||||||
raise ValueError("Cannot Combine: at least one solid required!")
|
raise ValueError("Cannot Combine: at least one solid required!")
|
||||||
|
|
||||||
#get context solid and we don't want to find our own objects
|
# get context solid and we don't want to find our own objects
|
||||||
ctxSolid = self.findSolid(searchStack=False, searchParents=True)
|
ctxSolid = self.findSolid(searchStack=False, searchParents=True)
|
||||||
|
|
||||||
if ctxSolid is None:
|
if ctxSolid is None:
|
||||||
ctxSolid = toCombine.pop(0)
|
ctxSolid = toCombine.pop(0)
|
||||||
|
|
||||||
#now combine them all. make sure to save a reference to the ctxSolid pointer!
|
# now combine them all. make sure to save a reference to the ctxSolid pointer!
|
||||||
s = ctxSolid
|
s = ctxSolid
|
||||||
for tc in toCombine:
|
for tc in toCombine:
|
||||||
s = s.fuse(tc)
|
s = s.fuse(tc)
|
||||||
@ -311,9 +313,9 @@ class CQ(object):
|
|||||||
n1 = f1.normalAt()
|
n1 = f1.normalAt()
|
||||||
|
|
||||||
# test normals (direction of planes)
|
# test normals (direction of planes)
|
||||||
if not ((abs(n0.x-n1.x) < self.ctx.tolerance) or
|
if not ((abs(n0.x - n1.x) < self.ctx.tolerance) or
|
||||||
(abs(n0.y-n1.y) < self.ctx.tolerance) or
|
(abs(n0.y - n1.y) < self.ctx.tolerance) or
|
||||||
(abs(n0.z-n1.z) < self.ctx.tolerance)):
|
(abs(n0.z - n1.z) < self.ctx.tolerance)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# test if p1 is on the plane of f0 (offset of planes)
|
# test if p1 is on the plane of f0 (offset of planes)
|
||||||
@ -328,23 +330,24 @@ class CQ(object):
|
|||||||
"""
|
"""
|
||||||
xd = Vector(0, 0, 1).cross(normal)
|
xd = Vector(0, 0, 1).cross(normal)
|
||||||
if xd.Length < self.ctx.tolerance:
|
if xd.Length < self.ctx.tolerance:
|
||||||
#this face is parallel with the x-y plane, so choose x to be in global coordinates
|
# this face is parallel with the x-y plane, so choose x to be in global coordinates
|
||||||
xd = Vector(1, 0, 0)
|
xd = Vector(1, 0, 0)
|
||||||
return xd
|
return xd
|
||||||
|
|
||||||
if len(self.objects) > 1:
|
if len(self.objects) > 1:
|
||||||
# are all objects 'PLANE'?
|
# are all objects 'PLANE'?
|
||||||
if not all(o.geomType() in ('PLANE','CIRCLE') for o in self.objects):
|
if not all(o.geomType() in ('PLANE', 'CIRCLE') for o in self.objects):
|
||||||
raise ValueError("If multiple objects selected, they all must be planar faces.")
|
raise ValueError(
|
||||||
|
"If multiple objects selected, they all must be planar faces.")
|
||||||
|
|
||||||
# are all faces co-planar with each other?
|
# are all faces co-planar with each other?
|
||||||
if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]):
|
if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]):
|
||||||
raise ValueError("Selected faces must be co-planar.")
|
raise ValueError("Selected faces must be co-planar.")
|
||||||
|
|
||||||
if centerOption == 'CenterOfMass':
|
if centerOption == 'CenterOfMass':
|
||||||
center = Shape.CombinedCenter(self.objects)
|
center = Shape.CombinedCenter(self.objects)
|
||||||
elif centerOption == 'CenterOfBoundBox':
|
elif centerOption == 'CenterOfBoundBox':
|
||||||
center = Shape.CombinedCenterOfBoundBox(self.objects)
|
center = Shape.CombinedCenterOfBoundBox(self.objects)
|
||||||
|
|
||||||
normal = self.objects[0].normalAt()
|
normal = self.objects[0].normalAt()
|
||||||
xDir = _computeXdir(normal)
|
xDir = _computeXdir(normal)
|
||||||
@ -353,38 +356,39 @@ class CQ(object):
|
|||||||
obj = self.objects[0]
|
obj = self.objects[0]
|
||||||
|
|
||||||
if isinstance(obj, Face):
|
if isinstance(obj, Face):
|
||||||
if centerOption == 'CenterOfMass':
|
if centerOption == 'CenterOfMass':
|
||||||
center = obj.Center()
|
center = obj.Center()
|
||||||
elif centerOption == 'CenterOfBoundBox':
|
elif centerOption == 'CenterOfBoundBox':
|
||||||
center = obj.CenterOfBoundBox()
|
center = obj.CenterOfBoundBox()
|
||||||
normal = obj.normalAt(center)
|
normal = obj.normalAt(center)
|
||||||
xDir = _computeXdir(normal)
|
xDir = _computeXdir(normal)
|
||||||
else:
|
else:
|
||||||
if hasattr(obj, 'Center'):
|
if hasattr(obj, 'Center'):
|
||||||
if centerOption == 'CenterOfMass':
|
if centerOption == 'CenterOfMass':
|
||||||
center = obj.Center()
|
center = obj.Center()
|
||||||
elif centerOption == 'CenterOfBoundBox':
|
elif centerOption == 'CenterOfBoundBox':
|
||||||
center = obj.CenterOfBoundBox()
|
center = obj.CenterOfBoundBox()
|
||||||
normal = self.plane.zDir
|
normal = self.plane.zDir
|
||||||
xDir = self.plane.xDir
|
xDir = self.plane.xDir
|
||||||
else:
|
else:
|
||||||
raise ValueError("Needs a face or a vertex or point on a work plane")
|
raise ValueError(
|
||||||
|
"Needs a face or a vertex or point on a work plane")
|
||||||
|
|
||||||
#invert if requested
|
# invert if requested
|
||||||
if invert:
|
if invert:
|
||||||
normal = normal.multiply(-1.0)
|
normal = normal.multiply(-1.0)
|
||||||
|
|
||||||
#offset origin if desired
|
# offset origin if desired
|
||||||
offsetVector = normal.normalized().multiply(offset)
|
offsetVector = normal.normalized().multiply(offset)
|
||||||
offsetCenter = center.add(offsetVector)
|
offsetCenter = center.add(offsetVector)
|
||||||
|
|
||||||
#make the new workplane
|
# make the new workplane
|
||||||
plane = Plane(offsetCenter, xDir, normal)
|
plane = Plane(offsetCenter, xDir, normal)
|
||||||
s = Workplane(plane)
|
s = Workplane(plane)
|
||||||
s.parent = self
|
s.parent = self
|
||||||
s.ctx = self.ctx
|
s.ctx = self.ctx
|
||||||
|
|
||||||
#a new workplane has the center of the workplane on the stack
|
# a new workplane has the center of the workplane on the stack
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def first(self):
|
def first(self):
|
||||||
@ -479,7 +483,7 @@ class CQ(object):
|
|||||||
toReturn = self._collectProperty(objType)
|
toReturn = self._collectProperty(objType)
|
||||||
|
|
||||||
if selector is not None:
|
if selector is not None:
|
||||||
if isinstance(selector, str) or isinstance(selector, unicode):
|
if isinstance(selector, str) or isinstance(selector, str):
|
||||||
selectorObj = selectors.StringSyntaxSelector(selector)
|
selectorObj = selectors.StringSyntaxSelector(selector)
|
||||||
else:
|
else:
|
||||||
selectorObj = selector
|
selectorObj = selector
|
||||||
@ -716,7 +720,7 @@ class CQ(object):
|
|||||||
one object, but is not cool for multiple.
|
one object, but is not cool for multiple.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#center point is the first point in the vector
|
# center point is the first point in the vector
|
||||||
endVec = Vector(axisEndPoint)
|
endVec = Vector(axisEndPoint)
|
||||||
|
|
||||||
def _rot(obj):
|
def _rot(obj):
|
||||||
@ -743,17 +747,17 @@ class CQ(object):
|
|||||||
for o in self.objects])
|
for o in self.objects])
|
||||||
|
|
||||||
def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
|
def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
|
||||||
"""
|
"""
|
||||||
Mirror a single CQ object. This operation is the same as in the FreeCAD PartWB's mirroring
|
Mirror a single CQ object. This operation is the same as in the FreeCAD PartWB's mirroring
|
||||||
|
|
||||||
:param mirrorPlane: the plane to mirror about
|
|
||||||
:type mirrorPlane: string, one of "XY", "YX", "XZ", "ZX", "YZ", "ZY" the planes
|
|
||||||
:param basePointVector: the base point to mirror about
|
|
||||||
:type basePointVector: tuple
|
|
||||||
"""
|
|
||||||
newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
|
|
||||||
return newS.first()
|
|
||||||
|
|
||||||
|
:param mirrorPlane: the plane to mirror about
|
||||||
|
:type mirrorPlane: string, one of "XY", "YX", "XZ", "ZX", "YZ", "ZY" the planes
|
||||||
|
:param basePointVector: the base point to mirror about
|
||||||
|
:type basePointVector: tuple
|
||||||
|
"""
|
||||||
|
newS = self.newObject(
|
||||||
|
[self.objects[0].mirror(mirrorPlane, basePointVector)])
|
||||||
|
return newS.first()
|
||||||
|
|
||||||
def translate(self, vec):
|
def translate(self, vec):
|
||||||
"""
|
"""
|
||||||
@ -765,7 +769,6 @@ class CQ(object):
|
|||||||
"""
|
"""
|
||||||
return self.newObject([o.translate(vec) for o in self.objects])
|
return self.newObject([o.translate(vec) for o in self.objects])
|
||||||
|
|
||||||
|
|
||||||
def shell(self, thickness):
|
def shell(self, thickness):
|
||||||
"""
|
"""
|
||||||
Remove the selected faces to create a shell of the specified thickness.
|
Remove the selected faces to create a shell of the specified thickness.
|
||||||
@ -935,7 +938,7 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
if inPlane.__class__.__name__ == 'Plane':
|
if inPlane.__class__.__name__ == 'Plane':
|
||||||
tmpPlane = inPlane
|
tmpPlane = inPlane
|
||||||
elif isinstance(inPlane, str) or isinstance(inPlane, unicode):
|
elif isinstance(inPlane, str) or isinstance(inPlane, str):
|
||||||
tmpPlane = Plane.named(inPlane, origin)
|
tmpPlane = Plane.named(inPlane, origin)
|
||||||
else:
|
else:
|
||||||
tmpPlane = None
|
tmpPlane = None
|
||||||
@ -964,7 +967,7 @@ class Workplane(CQ):
|
|||||||
:return: a new work plane, transformed as requested
|
:return: a new work plane, transformed as requested
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#old api accepted a vector, so we'll check for that.
|
# old api accepted a vector, so we'll check for that.
|
||||||
if rotate.__class__.__name__ == 'Vector':
|
if rotate.__class__.__name__ == 'Vector':
|
||||||
rotate = rotate.toTuple()
|
rotate = rotate.toTuple()
|
||||||
|
|
||||||
@ -990,7 +993,7 @@ class Workplane(CQ):
|
|||||||
:return: a new Workplane object with the current workplane as a parent.
|
:return: a new Workplane object with the current workplane as a parent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#copy the current state to the new object
|
# copy the current state to the new object
|
||||||
ns = Workplane("XY")
|
ns = Workplane("XY")
|
||||||
ns.plane = self.plane
|
ns.plane = self.plane
|
||||||
ns.parent = self
|
ns.parent = self
|
||||||
@ -1026,7 +1029,8 @@ class Workplane(CQ):
|
|||||||
elif isinstance(obj, Vector):
|
elif isinstance(obj, Vector):
|
||||||
p = obj
|
p = obj
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Cannot convert object type '%s' to vector " % type(obj))
|
raise RuntimeError(
|
||||||
|
"Cannot convert object type '%s' to vector " % type(obj))
|
||||||
|
|
||||||
if useLocalCoords:
|
if useLocalCoords:
|
||||||
return self.plane.toLocalCoords(p)
|
return self.plane.toLocalCoords(p)
|
||||||
@ -1055,10 +1059,10 @@ class Workplane(CQ):
|
|||||||
for y in range(yCount):
|
for y in range(yCount):
|
||||||
lpoints.append((xSpacing * x, ySpacing * y))
|
lpoints.append((xSpacing * x, ySpacing * y))
|
||||||
|
|
||||||
#shift points down and left relative to origin if requested
|
# shift points down and left relative to origin if requested
|
||||||
if center:
|
if center:
|
||||||
xc = xSpacing*(xCount-1) * 0.5
|
xc = xSpacing * (xCount - 1) * 0.5
|
||||||
yc = ySpacing*(yCount-1) * 0.5
|
yc = ySpacing * (yCount - 1) * 0.5
|
||||||
cpoints = []
|
cpoints = []
|
||||||
for p in lpoints:
|
for p in lpoints:
|
||||||
cpoints.append((p[0] - xc, p[1] - yc))
|
cpoints.append((p[0] - xc, p[1] - yc))
|
||||||
@ -1204,7 +1208,7 @@ class Workplane(CQ):
|
|||||||
p = self._findFromPoint(True)
|
p = self._findFromPoint(True)
|
||||||
return self.lineTo(xCoord, p.y, forConstruction)
|
return self.lineTo(xCoord, p.y, forConstruction)
|
||||||
|
|
||||||
#absolute move in current plane, not drawing
|
# absolute move in current plane, not drawing
|
||||||
def moveTo(self, x=0, y=0):
|
def moveTo(self, x=0, y=0):
|
||||||
"""
|
"""
|
||||||
Move to the specified point, without drawing.
|
Move to the specified point, without drawing.
|
||||||
@ -1223,7 +1227,7 @@ class Workplane(CQ):
|
|||||||
newCenter = Vector(x, y, 0)
|
newCenter = Vector(x, y, 0)
|
||||||
return self.newObject([self.plane.toWorldCoords(newCenter)])
|
return self.newObject([self.plane.toWorldCoords(newCenter)])
|
||||||
|
|
||||||
#relative move in current plane, not drawing
|
# relative move in current plane, not drawing
|
||||||
def move(self, xDist=0, yDist=0):
|
def move(self, xDist=0, yDist=0):
|
||||||
"""
|
"""
|
||||||
Move the specified distance from the current point, without drawing.
|
Move the specified distance from the current point, without drawing.
|
||||||
@ -1334,19 +1338,20 @@ class Workplane(CQ):
|
|||||||
faster implementation: this one transforms 3 times to accomplish the result
|
faster implementation: this one transforms 3 times to accomplish the result
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#convert edges to a wire, if there are pending edges
|
# convert edges to a wire, if there are pending edges
|
||||||
n = self.wire(forConstruction=False)
|
n = self.wire(forConstruction=False)
|
||||||
|
|
||||||
#attempt to consolidate wires together.
|
# attempt to consolidate wires together.
|
||||||
consolidated = n.consolidateWires()
|
consolidated = n.consolidateWires()
|
||||||
|
|
||||||
rotatedWires = self.plane.rotateShapes(consolidated.wires().vals(), matrix)
|
rotatedWires = self.plane.rotateShapes(
|
||||||
|
consolidated.wires().vals(), matrix)
|
||||||
|
|
||||||
for w in rotatedWires:
|
for w in rotatedWires:
|
||||||
consolidated.objects.append(w)
|
consolidated.objects.append(w)
|
||||||
consolidated._addPendingWire(w)
|
consolidated._addPendingWire(w)
|
||||||
|
|
||||||
#attempt again to consolidate all of the wires
|
# attempt again to consolidate all of the wires
|
||||||
c = consolidated.consolidateWires()
|
c = consolidated.consolidateWires()
|
||||||
|
|
||||||
return c
|
return c
|
||||||
@ -1369,10 +1374,10 @@ class Workplane(CQ):
|
|||||||
Future Enhancements:
|
Future Enhancements:
|
||||||
mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
|
mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
|
||||||
"""
|
"""
|
||||||
#convert edges to a wire, if there are pending edges
|
# convert edges to a wire, if there are pending edges
|
||||||
n = self.wire(forConstruction=False)
|
n = self.wire(forConstruction=False)
|
||||||
|
|
||||||
#attempt to consolidate wires together.
|
# attempt to consolidate wires together.
|
||||||
consolidated = n.consolidateWires()
|
consolidated = n.consolidateWires()
|
||||||
|
|
||||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
||||||
@ -1382,7 +1387,7 @@ class Workplane(CQ):
|
|||||||
consolidated.objects.append(w)
|
consolidated.objects.append(w)
|
||||||
consolidated._addPendingWire(w)
|
consolidated._addPendingWire(w)
|
||||||
|
|
||||||
#attempt again to consolidate all of the wires
|
# attempt again to consolidate all of the wires
|
||||||
return consolidated.consolidateWires()
|
return consolidated.consolidateWires()
|
||||||
|
|
||||||
def mirrorX(self):
|
def mirrorX(self):
|
||||||
@ -1399,10 +1404,10 @@ class Workplane(CQ):
|
|||||||
Future Enhancements:
|
Future Enhancements:
|
||||||
mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
|
mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness
|
||||||
"""
|
"""
|
||||||
#convert edges to a wire, if there are pending edges
|
# convert edges to a wire, if there are pending edges
|
||||||
n = self.wire(forConstruction=False)
|
n = self.wire(forConstruction=False)
|
||||||
|
|
||||||
#attempt to consolidate wires together.
|
# attempt to consolidate wires together.
|
||||||
consolidated = n.consolidateWires()
|
consolidated = n.consolidateWires()
|
||||||
|
|
||||||
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
mirroredWires = self.plane.mirrorInPlane(consolidated.wires().vals(),
|
||||||
@ -1412,7 +1417,7 @@ class Workplane(CQ):
|
|||||||
consolidated.objects.append(w)
|
consolidated.objects.append(w)
|
||||||
consolidated._addPendingWire(w)
|
consolidated._addPendingWire(w)
|
||||||
|
|
||||||
#attempt again to consolidate all of the wires
|
# attempt again to consolidate all of the wires
|
||||||
return consolidated.consolidateWires()
|
return consolidated.consolidateWires()
|
||||||
|
|
||||||
def _addPendingEdge(self, edge):
|
def _addPendingEdge(self, edge):
|
||||||
@ -1458,15 +1463,15 @@ class Workplane(CQ):
|
|||||||
if len(wires) < 2:
|
if len(wires) < 2:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
#TODO: this makes the assumption that either all wires could be combined, or none.
|
# TODO: this makes the assumption that either all wires could be combined, or none.
|
||||||
#in reality trying each combination of wires is probably not reasonable anyway
|
# in reality trying each combination of wires is probably not reasonable anyway
|
||||||
w = Wire.combine(wires)
|
w = Wire.combine(wires)
|
||||||
|
|
||||||
#ok this is a little tricky. if we consolidate wires, we have to actually
|
# ok this is a little tricky. if we consolidate wires, we have to actually
|
||||||
#modify the pendingWires collection to remove the original ones, and replace them
|
# modify the pendingWires collection to remove the original ones, and replace them
|
||||||
#with the consolidate done
|
# with the consolidate done
|
||||||
#since we are already assuming that all wires could be consolidated, its easy, we just
|
# since we are already assuming that all wires could be consolidated, its easy, we just
|
||||||
#clear the pending wire list
|
# clear the pending wire list
|
||||||
r = self.newObject([w])
|
r = self.newObject([w])
|
||||||
r.ctx.pendingWires = []
|
r.ctx.pendingWires = []
|
||||||
r._addPendingWire(w)
|
r._addPendingWire(w)
|
||||||
@ -1495,7 +1500,7 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
edges = self.ctx.pendingEdges
|
edges = self.ctx.pendingEdges
|
||||||
|
|
||||||
#do not consolidate if there are no free edges
|
# do not consolidate if there are no free edges
|
||||||
if len(edges) == 0:
|
if len(edges) == 0:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -1506,7 +1511,6 @@ class Workplane(CQ):
|
|||||||
if type(e) != Edge:
|
if type(e) != Edge:
|
||||||
others.append(e)
|
others.append(e)
|
||||||
|
|
||||||
|
|
||||||
w = Wire.assembleEdges(edges)
|
w = Wire.assembleEdges(edges)
|
||||||
if not forConstruction:
|
if not forConstruction:
|
||||||
self._addPendingWire(w)
|
self._addPendingWire(w)
|
||||||
@ -1549,7 +1553,7 @@ class Workplane(CQ):
|
|||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
|
|
||||||
if useLocalCoordinates:
|
if useLocalCoordinates:
|
||||||
#TODO: this needs to work for all types of objects, not just vectors!
|
# TODO: this needs to work for all types of objects, not just vectors!
|
||||||
r = callBackFunction(self.plane.toLocalCoords(obj))
|
r = callBackFunction(self.plane.toLocalCoords(obj))
|
||||||
r = r.transformShape(self.plane.rG)
|
r = r.transformShape(self.plane.rG)
|
||||||
else:
|
else:
|
||||||
@ -1580,11 +1584,11 @@ class Workplane(CQ):
|
|||||||
If the stack has zero length, a single point is returned, which is the center of the current
|
If the stack has zero length, a single point is returned, which is the center of the current
|
||||||
workplane/coordinate system
|
workplane/coordinate system
|
||||||
"""
|
"""
|
||||||
#convert stack to a list of points
|
# convert stack to a list of points
|
||||||
pnts = []
|
pnts = []
|
||||||
if len(self.objects) == 0:
|
if len(self.objects) == 0:
|
||||||
#nothing on the stack. here, we'll assume we should operate with the
|
# nothing on the stack. here, we'll assume we should operate with the
|
||||||
#origin as the context point
|
# origin as the context point
|
||||||
pnts.append(self.plane.origin)
|
pnts.append(self.plane.origin)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@ -1623,10 +1627,10 @@ class Workplane(CQ):
|
|||||||
# Here pnt is in local coordinates due to useLocalCoords=True
|
# Here pnt is in local coordinates due to useLocalCoords=True
|
||||||
# (xc,yc,zc) = pnt.toTuple()
|
# (xc,yc,zc) = pnt.toTuple()
|
||||||
if centered:
|
if centered:
|
||||||
p1 = pnt.add(Vector(xLen/-2.0, yLen/-2.0, 0))
|
p1 = pnt.add(Vector(xLen / -2.0, yLen / -2.0, 0))
|
||||||
p2 = pnt.add(Vector(xLen/2.0, yLen/-2.0, 0))
|
p2 = pnt.add(Vector(xLen / 2.0, yLen / -2.0, 0))
|
||||||
p3 = pnt.add(Vector(xLen/2.0, yLen/2.0, 0))
|
p3 = pnt.add(Vector(xLen / 2.0, yLen / 2.0, 0))
|
||||||
p4 = pnt.add(Vector(xLen/-2.0, yLen/2.0, 0))
|
p4 = pnt.add(Vector(xLen / -2.0, yLen / 2.0, 0))
|
||||||
else:
|
else:
|
||||||
p1 = pnt
|
p1 = pnt
|
||||||
p2 = pnt.add(Vector(xLen, 0, 0))
|
p2 = pnt.add(Vector(xLen, 0, 0))
|
||||||
@ -1635,11 +1639,11 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
w = Wire.makePolygon([p1, p2, p3, p4, p1], forConstruction)
|
w = Wire.makePolygon([p1, p2, p3, p4, p1], forConstruction)
|
||||||
return w
|
return w
|
||||||
#return Part.makePolygon([p1,p2,p3,p4,p1])
|
# return Part.makePolygon([p1,p2,p3,p4,p1])
|
||||||
|
|
||||||
return self.eachpoint(makeRectangleWire, True)
|
return self.eachpoint(makeRectangleWire, True)
|
||||||
|
|
||||||
#circle from current point
|
# circle from current point
|
||||||
def circle(self, radius, forConstruction=False):
|
def circle(self, radius, forConstruction=False):
|
||||||
"""
|
"""
|
||||||
Make a circle for each item on the stack.
|
Make a circle for each item on the stack.
|
||||||
@ -1688,12 +1692,12 @@ class Workplane(CQ):
|
|||||||
:return: a polygon wire
|
:return: a polygon wire
|
||||||
"""
|
"""
|
||||||
def _makePolygon(center):
|
def _makePolygon(center):
|
||||||
#pnt is a vector in local coordinates
|
# pnt is a vector in local coordinates
|
||||||
angle = 2.0 * math.pi / nSides
|
angle = 2.0 * math.pi / nSides
|
||||||
pnts = []
|
pnts = []
|
||||||
for i in range(nSides+1):
|
for i in range(nSides + 1):
|
||||||
pnts.append(center + Vector((diameter / 2.0 * math.cos(angle*i)),
|
pnts.append(center + Vector((diameter / 2.0 * math.cos(angle * i)),
|
||||||
(diameter / 2.0 * math.sin(angle*i)), 0))
|
(diameter / 2.0 * math.sin(angle * i)), 0))
|
||||||
return Wire.makePolygon(pnts, forConstruction)
|
return Wire.makePolygon(pnts, forConstruction)
|
||||||
|
|
||||||
return self.eachpoint(_makePolygon, True)
|
return self.eachpoint(_makePolygon, True)
|
||||||
@ -1749,7 +1753,7 @@ class Workplane(CQ):
|
|||||||
self.lineTo(self.ctx.firstPoint.x, self.ctx.firstPoint.y)
|
self.lineTo(self.ctx.firstPoint.x, self.ctx.firstPoint.y)
|
||||||
|
|
||||||
# Need to reset the first point after closing a wire
|
# Need to reset the first point after closing a wire
|
||||||
self.ctx.firstPoint=None
|
self.ctx.firstPoint = None
|
||||||
|
|
||||||
return self.wire()
|
return self.wire()
|
||||||
|
|
||||||
@ -1760,8 +1764,8 @@ class Workplane(CQ):
|
|||||||
how long or wide a feature must be to make sure to cut through all of the material
|
how long or wide a feature must be to make sure to cut through all of the material
|
||||||
:return: A value representing the largest dimension of the first solid on the stack
|
:return: A value representing the largest dimension of the first solid on the stack
|
||||||
"""
|
"""
|
||||||
#TODO: this implementation is naive and returns the dims of the first solid... most of
|
# TODO: this implementation is naive and returns the dims of the first solid... most of
|
||||||
#TODO: the time this works. but a stronger implementation would be to search all solids.
|
# TODO: the time this works. but a stronger implementation would be to search all solids.
|
||||||
s = self.findSolid()
|
s = self.findSolid()
|
||||||
if s:
|
if s:
|
||||||
return s.BoundingBox().DiagonalLength * 5.0
|
return s.BoundingBox().DiagonalLength * 5.0
|
||||||
@ -1782,18 +1786,19 @@ class Workplane(CQ):
|
|||||||
if ctxSolid is None:
|
if ctxSolid is None:
|
||||||
raise ValueError("Must have a solid in the chain to cut from!")
|
raise ValueError("Must have a solid in the chain to cut from!")
|
||||||
|
|
||||||
#will contain all of the counterbores as a single compound
|
# will contain all of the counterbores as a single compound
|
||||||
results = self.eachpoint(fcn, useLocalCoords).vals()
|
results = self.eachpoint(fcn, useLocalCoords).vals()
|
||||||
s = ctxSolid
|
s = ctxSolid
|
||||||
for cb in results:
|
for cb in results:
|
||||||
s = s.cut(cb)
|
s = s.cut(cb)
|
||||||
|
|
||||||
if clean: s = s.clean()
|
if clean:
|
||||||
|
s = s.clean()
|
||||||
|
|
||||||
ctxSolid.wrapped = s.wrapped
|
ctxSolid.wrapped = s.wrapped
|
||||||
return self.newObject([s])
|
return self.newObject([s])
|
||||||
|
|
||||||
#but parameter list is different so a simple function pointer wont work
|
# but parameter list is different so a simple function pointer wont work
|
||||||
def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None, clean=True):
|
def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None, clean=True):
|
||||||
"""
|
"""
|
||||||
Makes a counterbored hole for each item on the stack.
|
Makes a counterbored hole for each item on the stack.
|
||||||
@ -1834,18 +1839,20 @@ class Workplane(CQ):
|
|||||||
pnt is in local coordinates
|
pnt is in local coordinates
|
||||||
"""
|
"""
|
||||||
boreDir = Vector(0, 0, -1)
|
boreDir = Vector(0, 0, -1)
|
||||||
#first make the hole
|
# first make the hole
|
||||||
hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coordianates!
|
hole = Solid.makeCylinder(
|
||||||
|
diameter / 2.0, depth, center, boreDir) # local coordianates!
|
||||||
|
|
||||||
#add the counter bore
|
# add the counter bore
|
||||||
cbore = Solid.makeCylinder(cboreDiameter / 2.0, cboreDepth, center, boreDir)
|
cbore = Solid.makeCylinder(
|
||||||
|
cboreDiameter / 2.0, cboreDepth, center, boreDir)
|
||||||
r = hole.fuse(cbore)
|
r = hole.fuse(cbore)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
return self.cutEach(_makeCbore, True, clean)
|
return self.cutEach(_makeCbore, True, clean)
|
||||||
|
|
||||||
#TODO: almost all code duplicated!
|
# TODO: almost all code duplicated!
|
||||||
#but parameter list is different so a simple function pointer wont work
|
# but parameter list is different so a simple function pointer wont work
|
||||||
def cskHole(self, diameter, cskDiameter, cskAngle, depth=None, clean=True):
|
def cskHole(self, diameter, cskDiameter, cskAngle, depth=None, clean=True):
|
||||||
"""
|
"""
|
||||||
Makes a countersunk hole for each item on the stack.
|
Makes a countersunk hole for each item on the stack.
|
||||||
@ -1881,12 +1888,13 @@ class Workplane(CQ):
|
|||||||
depth = self.largestDimension()
|
depth = self.largestDimension()
|
||||||
|
|
||||||
def _makeCsk(center):
|
def _makeCsk(center):
|
||||||
#center is in local coordinates
|
# center is in local coordinates
|
||||||
|
|
||||||
boreDir = Vector(0, 0, -1)
|
boreDir = Vector(0, 0, -1)
|
||||||
|
|
||||||
#first make the hole
|
# first make the hole
|
||||||
hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coords!
|
hole = Solid.makeCylinder(
|
||||||
|
diameter / 2.0, depth, center, boreDir) # local coords!
|
||||||
r = cskDiameter / 2.0
|
r = cskDiameter / 2.0
|
||||||
h = r / math.tan(math.radians(cskAngle / 2.0))
|
h = r / math.tan(math.radians(cskAngle / 2.0))
|
||||||
csk = Solid.makeCone(r, 0.0, h, center, boreDir)
|
csk = Solid.makeCone(r, 0.0, h, center, boreDir)
|
||||||
@ -1895,8 +1903,8 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
return self.cutEach(_makeCsk, True, clean)
|
return self.cutEach(_makeCsk, True, clean)
|
||||||
|
|
||||||
#TODO: almost all code duplicated!
|
# TODO: almost all code duplicated!
|
||||||
#but parameter list is different so a simple function pointer wont work
|
# but parameter list is different so a simple function pointer wont work
|
||||||
def hole(self, diameter, depth=None, clean=True):
|
def hole(self, diameter, depth=None, clean=True):
|
||||||
"""
|
"""
|
||||||
Makes a hole for each item on the stack.
|
Makes a hole for each item on the stack.
|
||||||
@ -1933,13 +1941,14 @@ class Workplane(CQ):
|
|||||||
pnt is in local coordinates
|
pnt is in local coordinates
|
||||||
"""
|
"""
|
||||||
boreDir = Vector(0, 0, -1)
|
boreDir = Vector(0, 0, -1)
|
||||||
#first make the hole
|
# first make the hole
|
||||||
hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates!
|
hole = Solid.makeCylinder(
|
||||||
|
diameter / 2.0, depth, center, boreDir) # local coordinates!
|
||||||
return hole
|
return hole
|
||||||
|
|
||||||
return self.cutEach(_makeHole, True, clean)
|
return self.cutEach(_makeHole, True, clean)
|
||||||
|
|
||||||
#TODO: duplicated code with _extrude and extrude
|
# TODO: duplicated code with _extrude and extrude
|
||||||
def twistExtrude(self, distance, angleDegrees, combine=True, clean=True):
|
def twistExtrude(self, distance, angleDegrees, combine=True, clean=True):
|
||||||
"""
|
"""
|
||||||
Extrudes a wire in the direction normal to the plane, but also twists by the specified
|
Extrudes a wire in the direction normal to the plane, but also twists by the specified
|
||||||
@ -1959,21 +1968,23 @@ class Workplane(CQ):
|
|||||||
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
|
:param boolean clean: call :py:meth:`clean` afterwards to have a clean shape
|
||||||
:return: a CQ object with the resulting solid selected.
|
:return: a CQ object with the resulting solid selected.
|
||||||
"""
|
"""
|
||||||
#group wires together into faces based on which ones are inside the others
|
# group wires together into faces based on which ones are inside the others
|
||||||
#result is a list of lists
|
# result is a list of lists
|
||||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
|
wireSets = sortWiresByBuildOrder(
|
||||||
|
list(self.ctx.pendingWires), self.plane, [])
|
||||||
|
|
||||||
self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
|
# now all of the wires have been used to create an extrusion
|
||||||
|
self.ctx.pendingWires = []
|
||||||
|
|
||||||
#compute extrusion vector and extrude
|
# compute extrusion vector and extrude
|
||||||
eDir = self.plane.zDir.multiply(distance)
|
eDir = self.plane.zDir.multiply(distance)
|
||||||
|
|
||||||
#one would think that fusing faces into a compound and then extruding would work,
|
# one would think that fusing faces into a compound and then extruding would work,
|
||||||
#but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
|
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
|
||||||
#but then cutting it from the main solid fails with BRep_NotDone.
|
# but then cutting it from the main solid fails with BRep_NotDone.
|
||||||
#the work around is to extrude each and then join the resulting solids, which seems to work
|
# the work around is to extrude each and then join the resulting solids, which seems to work
|
||||||
|
|
||||||
#underlying cad kernel can only handle simple bosses-- we'll aggregate them if there
|
# underlying cad kernel can only handle simple bosses-- we'll aggregate them if there
|
||||||
# are multiple sets
|
# are multiple sets
|
||||||
r = None
|
r = None
|
||||||
for ws in wireSets:
|
for ws in wireSets:
|
||||||
@ -1988,7 +1999,8 @@ class Workplane(CQ):
|
|||||||
newS = self._combineWithBase(r)
|
newS = self._combineWithBase(r)
|
||||||
else:
|
else:
|
||||||
newS = self.newObject([r])
|
newS = self.newObject([r])
|
||||||
if clean: newS = newS.clean()
|
if clean:
|
||||||
|
newS = newS.clean()
|
||||||
return newS
|
return newS
|
||||||
|
|
||||||
def extrude(self, distance, combine=True, clean=True, both=False):
|
def extrude(self, distance, combine=True, clean=True, both=False):
|
||||||
@ -2015,14 +2027,16 @@ class Workplane(CQ):
|
|||||||
Support for non-prismatic extrusion ( IE, sweeping along a profile, not just
|
Support for non-prismatic extrusion ( IE, sweeping along a profile, not just
|
||||||
perpendicular to the plane extrude to surface. this is quite tricky since the surface
|
perpendicular to the plane extrude to surface. this is quite tricky since the surface
|
||||||
selected may not be planar
|
selected may not be planar
|
||||||
"""
|
"""
|
||||||
r = self._extrude(distance,both=both) # returns a Solid (or a compound if there were multiple)
|
r = self._extrude(
|
||||||
|
distance, both=both) # returns a Solid (or a compound if there were multiple)
|
||||||
|
|
||||||
if combine:
|
if combine:
|
||||||
newS = self._combineWithBase(r)
|
newS = self._combineWithBase(r)
|
||||||
else:
|
else:
|
||||||
newS = self.newObject([r])
|
newS = self.newObject([r])
|
||||||
if clean: newS = newS.clean()
|
if clean:
|
||||||
|
newS = newS.clean()
|
||||||
return newS
|
return newS
|
||||||
|
|
||||||
def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True):
|
def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True, clean=True):
|
||||||
@ -2047,10 +2061,10 @@ class Workplane(CQ):
|
|||||||
* if combine is true, the value is combined with the context solid if it exists,
|
* if combine is true, the value is combined with the context solid if it exists,
|
||||||
and the resulting solid becomes the new context solid.
|
and the resulting solid becomes the new context solid.
|
||||||
"""
|
"""
|
||||||
#Make sure we account for users specifying angles larger than 360 degrees
|
# Make sure we account for users specifying angles larger than 360 degrees
|
||||||
angleDegrees %= 360.0
|
angleDegrees %= 360.0
|
||||||
|
|
||||||
#Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve
|
# Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve
|
||||||
angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees
|
angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees
|
||||||
|
|
||||||
# The default start point of the vector defining the axis of rotation will be the origin
|
# The default start point of the vector defining the axis of rotation will be the origin
|
||||||
@ -2078,7 +2092,8 @@ class Workplane(CQ):
|
|||||||
newS = self._combineWithBase(r)
|
newS = self._combineWithBase(r)
|
||||||
else:
|
else:
|
||||||
newS = self.newObject([r])
|
newS = self.newObject([r])
|
||||||
if clean: newS = newS.clean()
|
if clean:
|
||||||
|
newS = newS.clean()
|
||||||
return newS
|
return newS
|
||||||
|
|
||||||
def sweep(self, path, makeSolid=True, isFrenet=False, combine=True, clean=True):
|
def sweep(self, path, makeSolid=True, isFrenet=False, combine=True, clean=True):
|
||||||
@ -2091,12 +2106,14 @@ class Workplane(CQ):
|
|||||||
:return: a CQ object with the resulting solid selected.
|
:return: a CQ object with the resulting solid selected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
r = self._sweep(path.wire(), makeSolid, isFrenet) # returns a Solid (or a compound if there were multiple)
|
# returns a Solid (or a compound if there were multiple)
|
||||||
|
r = self._sweep(path.wire(), makeSolid, isFrenet)
|
||||||
if combine:
|
if combine:
|
||||||
newS = self._combineWithBase(r)
|
newS = self._combineWithBase(r)
|
||||||
else:
|
else:
|
||||||
newS = self.newObject([r])
|
newS = self.newObject([r])
|
||||||
if clean: newS = newS.clean()
|
if clean:
|
||||||
|
newS = newS.clean()
|
||||||
return newS
|
return newS
|
||||||
|
|
||||||
def _combineWithBase(self, obj):
|
def _combineWithBase(self, obj):
|
||||||
@ -2128,7 +2145,8 @@ class Workplane(CQ):
|
|||||||
for ss in items:
|
for ss in items:
|
||||||
s = s.fuse(ss)
|
s = s.fuse(ss)
|
||||||
|
|
||||||
if clean: s = s.clean()
|
if clean:
|
||||||
|
s = s.clean()
|
||||||
|
|
||||||
return self.newObject([s])
|
return self.newObject([s])
|
||||||
|
|
||||||
@ -2146,11 +2164,12 @@ class Workplane(CQ):
|
|||||||
:return: a CQ object with the resulting object selected
|
:return: a CQ object with the resulting object selected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#first collect all of the items together
|
# first collect all of the items together
|
||||||
if type(toUnion) == CQ or type(toUnion) == Workplane:
|
if type(toUnion) == CQ or type(toUnion) == Workplane:
|
||||||
solids = toUnion.solids().vals()
|
solids = toUnion.solids().vals()
|
||||||
if len(solids) < 1:
|
if len(solids) < 1:
|
||||||
raise ValueError("CQ object must have at least one solid on the stack to union!")
|
raise ValueError(
|
||||||
|
"CQ object must have at least one solid on the stack to union!")
|
||||||
newS = solids.pop(0)
|
newS = solids.pop(0)
|
||||||
for s in solids:
|
for s in solids:
|
||||||
newS = newS.fuse(s)
|
newS = newS.fuse(s)
|
||||||
@ -2159,7 +2178,7 @@ class Workplane(CQ):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Cannot union type '{}'".format(type(toUnion)))
|
raise ValueError("Cannot union type '{}'".format(type(toUnion)))
|
||||||
|
|
||||||
#now combine with existing solid, if there is one
|
# now combine with existing solid, if there is one
|
||||||
# look for parents to cut from
|
# look for parents to cut from
|
||||||
solidRef = self.findSolid(searchStack=True, searchParents=True)
|
solidRef = self.findSolid(searchStack=True, searchParents=True)
|
||||||
if combine and solidRef is not None:
|
if combine and solidRef is not None:
|
||||||
@ -2168,7 +2187,8 @@ class Workplane(CQ):
|
|||||||
else:
|
else:
|
||||||
r = newS
|
r = newS
|
||||||
|
|
||||||
if clean: r = r.clean()
|
if clean:
|
||||||
|
r = r.clean()
|
||||||
|
|
||||||
return self.newObject([r])
|
return self.newObject([r])
|
||||||
|
|
||||||
@ -2194,14 +2214,15 @@ class Workplane(CQ):
|
|||||||
solidToCut = None
|
solidToCut = None
|
||||||
if type(toCut) == CQ or type(toCut) == Workplane:
|
if type(toCut) == CQ or type(toCut) == Workplane:
|
||||||
solidToCut = toCut.val()
|
solidToCut = toCut.val()
|
||||||
elif type(toCut) in (Solid,Compound):
|
elif type(toCut) in (Solid, Compound):
|
||||||
solidToCut = toCut
|
solidToCut = toCut
|
||||||
else:
|
else:
|
||||||
raise ValueError("Cannot cut type '{}'".format(type(toCut)))
|
raise ValueError("Cannot cut type '{}'".format(type(toCut)))
|
||||||
|
|
||||||
newS = solidRef.cut(solidToCut)
|
newS = solidRef.cut(solidToCut)
|
||||||
|
|
||||||
if clean: newS = newS.clean()
|
if clean:
|
||||||
|
newS = newS.clean()
|
||||||
|
|
||||||
if combine:
|
if combine:
|
||||||
solidRef.wrapped = newS.wrapped
|
solidRef.wrapped = newS.wrapped
|
||||||
@ -2227,16 +2248,17 @@ class Workplane(CQ):
|
|||||||
Future Enhancements:
|
Future Enhancements:
|
||||||
Cut Up to Surface
|
Cut Up to Surface
|
||||||
"""
|
"""
|
||||||
#first, make the object
|
# first, make the object
|
||||||
toCut = self._extrude(distanceToCut)
|
toCut = self._extrude(distanceToCut)
|
||||||
|
|
||||||
#now find a solid in the chain
|
# now find a solid in the chain
|
||||||
|
|
||||||
solidRef = self.findSolid()
|
solidRef = self.findSolid()
|
||||||
|
|
||||||
s = solidRef.cut(toCut)
|
s = solidRef.cut(toCut)
|
||||||
|
|
||||||
if clean: s = s.clean()
|
if clean:
|
||||||
|
s = s.clean()
|
||||||
|
|
||||||
solidRef.wrapped = s.wrapped
|
solidRef.wrapped = s.wrapped
|
||||||
return self.newObject([s])
|
return self.newObject([s])
|
||||||
@ -2295,21 +2317,22 @@ class Workplane(CQ):
|
|||||||
extrude along a profile (sweep)
|
extrude along a profile (sweep)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#group wires together into faces based on which ones are inside the others
|
# group wires together into faces based on which ones are inside the others
|
||||||
#result is a list of lists
|
# result is a list of lists
|
||||||
s = time.time()
|
s = time.time()
|
||||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
|
wireSets = sortWiresByBuildOrder(
|
||||||
#print "sorted wires in %d sec" % ( time.time() - s )
|
list(self.ctx.pendingWires), self.plane, [])
|
||||||
self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
|
# print "sorted wires in %d sec" % ( time.time() - s )
|
||||||
|
# now all of the wires have been used to create an extrusion
|
||||||
|
self.ctx.pendingWires = []
|
||||||
|
|
||||||
#compute extrusion vector and extrude
|
# compute extrusion vector and extrude
|
||||||
eDir = self.plane.zDir.multiply(distance)
|
eDir = self.plane.zDir.multiply(distance)
|
||||||
|
|
||||||
|
# one would think that fusing faces into a compound and then extruding would work,
|
||||||
#one would think that fusing faces into a compound and then extruding would work,
|
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
|
||||||
#but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc)
|
# but then cutting it from the main solid fails with BRep_NotDone.
|
||||||
#but then cutting it from the main solid fails with BRep_NotDone.
|
# the work around is to extrude each and then join the resulting solids, which seems to work
|
||||||
#the work around is to extrude each and then join the resulting solids, which seems to work
|
|
||||||
|
|
||||||
# underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are
|
# underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are
|
||||||
# multiple sets
|
# multiple sets
|
||||||
@ -2332,9 +2355,10 @@ class Workplane(CQ):
|
|||||||
for ws in wireSets:
|
for ws in wireSets:
|
||||||
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
|
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir)
|
||||||
toFuse.append(thisObj)
|
toFuse.append(thisObj)
|
||||||
|
|
||||||
if both:
|
if both:
|
||||||
thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir.multiply(-1.))
|
thisObj = Solid.extrudeLinear(
|
||||||
|
ws[0], ws[1:], eDir.multiply(-1.))
|
||||||
toFuse.append(thisObj)
|
toFuse.append(thisObj)
|
||||||
|
|
||||||
return Compound.makeCompound(toFuse)
|
return Compound.makeCompound(toFuse)
|
||||||
@ -2353,16 +2377,18 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
This method is a utility method, primarily for plugin and internal use.
|
This method is a utility method, primarily for plugin and internal use.
|
||||||
"""
|
"""
|
||||||
#We have to gather the wires to be revolved
|
# We have to gather the wires to be revolved
|
||||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
|
wireSets = sortWiresByBuildOrder(
|
||||||
|
list(self.ctx.pendingWires), self.plane, [])
|
||||||
|
|
||||||
#Mark that all of the wires have been used to create a revolution
|
# Mark that all of the wires have been used to create a revolution
|
||||||
self.ctx.pendingWires = []
|
self.ctx.pendingWires = []
|
||||||
|
|
||||||
#Revolve the wires, make a compound out of them and then fuse them
|
# Revolve the wires, make a compound out of them and then fuse them
|
||||||
toFuse = []
|
toFuse = []
|
||||||
for ws in wireSets:
|
for ws in wireSets:
|
||||||
thisObj = Solid.revolve(ws[0], ws[1:], angleDegrees, axisStart, axisEnd)
|
thisObj = Solid.revolve(
|
||||||
|
ws[0], ws[1:], angleDegrees, axisStart, axisEnd)
|
||||||
toFuse.append(thisObj)
|
toFuse.append(thisObj)
|
||||||
|
|
||||||
return Compound.makeCompound(toFuse)
|
return Compound.makeCompound(toFuse)
|
||||||
@ -2378,13 +2404,16 @@ class Workplane(CQ):
|
|||||||
# group wires together into faces based on which ones are inside the others
|
# group wires together into faces based on which ones are inside the others
|
||||||
# result is a list of lists
|
# result is a list of lists
|
||||||
s = time.time()
|
s = time.time()
|
||||||
wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, [])
|
wireSets = sortWiresByBuildOrder(
|
||||||
|
list(self.ctx.pendingWires), self.plane, [])
|
||||||
# print "sorted wires in %d sec" % ( time.time() - s )
|
# print "sorted wires in %d sec" % ( time.time() - s )
|
||||||
self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion
|
# now all of the wires have been used to create an extrusion
|
||||||
|
self.ctx.pendingWires = []
|
||||||
|
|
||||||
toFuse = []
|
toFuse = []
|
||||||
for ws in wireSets:
|
for ws in wireSets:
|
||||||
thisObj = Solid.sweep(ws[0], ws[1:], path.val(), makeSolid, isFrenet)
|
thisObj = Solid.sweep(
|
||||||
|
ws[0], ws[1:], path.val(), makeSolid, isFrenet)
|
||||||
toFuse.append(thisObj)
|
toFuse.append(thisObj)
|
||||||
|
|
||||||
return Compound.makeCompound(toFuse)
|
return Compound.makeCompound(toFuse)
|
||||||
@ -2447,11 +2476,11 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
boxes = self.eachpoint(_makebox, True)
|
boxes = self.eachpoint(_makebox, True)
|
||||||
|
|
||||||
#if combination is not desired, just return the created boxes
|
# if combination is not desired, just return the created boxes
|
||||||
if not combine:
|
if not combine:
|
||||||
return boxes
|
return boxes
|
||||||
else:
|
else:
|
||||||
#combine everything
|
# combine everything
|
||||||
return self.union(boxes, clean=clean)
|
return self.union(boxes, clean=clean)
|
||||||
|
|
||||||
def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360,
|
def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360,
|
||||||
@ -2547,5 +2576,6 @@ class Workplane(CQ):
|
|||||||
try:
|
try:
|
||||||
cleanObjects = [obj.clean() for obj in self.objects]
|
cleanObjects = [obj.clean() for obj in self.objects]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise AttributeError("%s object doesn't support `clean()` method!" % obj.ShapeType())
|
raise AttributeError(
|
||||||
|
"%s object doesn't support `clean()` method!" % obj.ShapeType())
|
||||||
return self.newObject(cleanObjects)
|
return self.newObject(cleanObjects)
|
||||||
|
@ -6,7 +6,7 @@ A special directive for including a cq object.
|
|||||||
import traceback
|
import traceback
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
from cadquery import cqgi
|
from cadquery import cqgi
|
||||||
import StringIO
|
import io
|
||||||
from docutils.parsers.rst import directives
|
from docutils.parsers.rst import directives
|
||||||
|
|
||||||
template = """
|
template = """
|
||||||
@ -34,7 +34,7 @@ def cq_directive(name, arguments, options, content, lineno,
|
|||||||
out_svg = "Your Script Did not assign call build_output() function!"
|
out_svg = "Your Script Did not assign call build_output() function!"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_s = StringIO.StringIO()
|
_s = io.StringIO()
|
||||||
result = cqgi.parse(plot_code).build()
|
result = cqgi.parse(plot_code).build()
|
||||||
|
|
||||||
if result.success:
|
if result.success:
|
||||||
|
@ -9,6 +9,7 @@ import cadquery
|
|||||||
|
|
||||||
CQSCRIPT = "<cqscript>"
|
CQSCRIPT = "<cqscript>"
|
||||||
|
|
||||||
|
|
||||||
def parse(script_source):
|
def parse(script_source):
|
||||||
"""
|
"""
|
||||||
Parses the script as a model, and returns a model.
|
Parses the script as a model, and returns a model.
|
||||||
@ -48,19 +49,19 @@ class CQModel(object):
|
|||||||
# TODO: pick up other scirpt metadata:
|
# TODO: pick up other scirpt metadata:
|
||||||
# describe
|
# describe
|
||||||
# pick up validation methods
|
# pick up validation methods
|
||||||
self._find_descriptions()
|
self._find_descriptions()
|
||||||
|
|
||||||
def _find_vars(self):
|
def _find_vars(self):
|
||||||
"""
|
"""
|
||||||
Parse the script, and populate variables that appear to be
|
Parse the script, and populate variables that appear to be
|
||||||
overridable.
|
overridable.
|
||||||
"""
|
"""
|
||||||
#assumption here: we assume that variable declarations
|
# assumption here: we assume that variable declarations
|
||||||
#are only at the top level of the script. IE, we'll ignore any
|
# are only at the top level of the script. IE, we'll ignore any
|
||||||
#variable definitions at lower levels of the script
|
# variable definitions at lower levels of the script
|
||||||
|
|
||||||
#we dont want to use the visit interface because here we excplicitly
|
# we dont want to use the visit interface because here we excplicitly
|
||||||
#want to walk only the top level of the tree.
|
# want to walk only the top level of the tree.
|
||||||
assignment_finder = ConstantAssignmentFinder(self.metadata)
|
assignment_finder = ConstantAssignmentFinder(self.metadata)
|
||||||
|
|
||||||
for node in self.ast_tree.body:
|
for node in self.ast_tree.body:
|
||||||
@ -104,22 +105,23 @@ class CQModel(object):
|
|||||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||||
.add_entry("build_object", collector.build_object) \
|
.add_entry("build_object", collector.build_object) \
|
||||||
.add_entry("debug", collector.debug) \
|
.add_entry("debug", collector.debug) \
|
||||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
.add_entry("describe_parameter", collector.describe_parameter) \
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
||||||
exec (c, env)
|
exec(c, env)
|
||||||
result.set_debug(collector.debugObjects )
|
result.set_debug(collector.debugObjects)
|
||||||
if collector.has_results():
|
if collector.has_results():
|
||||||
result.set_success_result(collector.outputObjects)
|
result.set_success_result(collector.outputObjects)
|
||||||
else:
|
else:
|
||||||
raise NoOutputError("Script did not call build_object-- no output available.")
|
raise NoOutputError(
|
||||||
except Exception, ex:
|
"Script did not call build_object-- no output available.")
|
||||||
print "Error Executing Script:"
|
except Exception as ex:
|
||||||
|
print("Error Executing Script:")
|
||||||
result.set_failure_result(ex)
|
result.set_failure_result(ex)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print "Full Text of Script:"
|
print("Full Text of Script:")
|
||||||
print self.script_source
|
print(self.script_source)
|
||||||
|
|
||||||
end = time.clock()
|
end = time.clock()
|
||||||
result.buildTime = end - start
|
result.buildTime = end - start
|
||||||
@ -128,9 +130,10 @@ class CQModel(object):
|
|||||||
def set_param_values(self, params):
|
def set_param_values(self, params):
|
||||||
model_parameters = self.metadata.parameters
|
model_parameters = self.metadata.parameters
|
||||||
|
|
||||||
for k, v in params.iteritems():
|
for k, v in params.items():
|
||||||
if k not in model_parameters:
|
if k not in model_parameters:
|
||||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
raise InvalidParameterError(
|
||||||
|
"Cannot set value '%s': not a parameter of the model." % k)
|
||||||
|
|
||||||
p = model_parameters[k]
|
p = model_parameters[k]
|
||||||
p.set_value(v)
|
p.set_value(v)
|
||||||
@ -147,6 +150,7 @@ class BuildResult(object):
|
|||||||
If unsuccessful, the exception property contains a reference to
|
If unsuccessful, the exception property contains a reference to
|
||||||
the stack trace that occurred.
|
the stack trace that occurred.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.buildTime = None
|
self.buildTime = None
|
||||||
self.results = []
|
self.results = []
|
||||||
@ -173,14 +177,15 @@ class ScriptMetadata(object):
|
|||||||
Defines the metadata for a parsed CQ Script.
|
Defines the metadata for a parsed CQ Script.
|
||||||
the parameters property is a dict of InputParameter objects.
|
the parameters property is a dict of InputParameter objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.parameters = {}
|
self.parameters = {}
|
||||||
|
|
||||||
def add_script_parameter(self, p):
|
def add_script_parameter(self, p):
|
||||||
self.parameters[p.name] = p
|
self.parameters[p.name] = p
|
||||||
|
|
||||||
def add_parameter_description(self,name,description):
|
def add_parameter_description(self, name, description):
|
||||||
print 'Adding Parameter name=%s, desc=%s' % ( name, description )
|
print('Adding Parameter name=%s, desc=%s' % (name, description))
|
||||||
p = self.parameters[name]
|
p = self.parameters[name]
|
||||||
p.desc = description
|
p.desc = description
|
||||||
|
|
||||||
@ -212,6 +217,7 @@ class InputParameter:
|
|||||||
provide additional metadata
|
provide additional metadata
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
#: the default value for the variable.
|
#: the default value for the variable.
|
||||||
@ -251,7 +257,7 @@ class InputParameter:
|
|||||||
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
||||||
raise InvalidParameterError(
|
raise InvalidParameterError(
|
||||||
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
|
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
|
||||||
.format(str(new_value), self.name, str(self.valid_values)))
|
.format(str(new_value), self.name, str(self.valid_values)))
|
||||||
|
|
||||||
if self.varType == NumberParameterType:
|
if self.varType == NumberParameterType:
|
||||||
try:
|
try:
|
||||||
@ -260,7 +266,7 @@ class InputParameter:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidParameterError(
|
raise InvalidParameterError(
|
||||||
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
|
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
|
||||||
.format(str(new_value), self.name))
|
.format(str(new_value), self.name))
|
||||||
|
|
||||||
elif self.varType == StringParameterType:
|
elif self.varType == StringParameterType:
|
||||||
self.ast_node.s = str(new_value)
|
self.ast_node.s = str(new_value)
|
||||||
@ -283,6 +289,7 @@ class ScriptCallback(object):
|
|||||||
the build_object() method is exposed to CQ scripts, to allow them
|
the build_object() method is exposed to CQ scripts, to allow them
|
||||||
to return objects to the execution environment
|
to return objects to the execution environment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.outputObjects = []
|
self.outputObjects = []
|
||||||
self.debugObjects = []
|
self.debugObjects = []
|
||||||
@ -294,13 +301,13 @@ class ScriptCallback(object):
|
|||||||
"""
|
"""
|
||||||
self.outputObjects.append(shape)
|
self.outputObjects.append(shape)
|
||||||
|
|
||||||
def debug(self,obj,args={}):
|
def debug(self, obj, args={}):
|
||||||
"""
|
"""
|
||||||
Debug print/output an object, with optional arguments.
|
Debug print/output an object, with optional arguments.
|
||||||
"""
|
"""
|
||||||
self.debugObjects.append(DebugObject(obj,args))
|
self.debugObjects.append(DebugObject(obj, args))
|
||||||
|
|
||||||
def describe_parameter(self,var_data ):
|
def describe_parameter(self, var_data):
|
||||||
"""
|
"""
|
||||||
Do Nothing-- we parsed the ast ahead of exection to get what we need.
|
Do Nothing-- we parsed the ast ahead of exection to get what we need.
|
||||||
"""
|
"""
|
||||||
@ -315,16 +322,19 @@ class ScriptCallback(object):
|
|||||||
def has_results(self):
|
def has_results(self):
|
||||||
return len(self.outputObjects) > 0
|
return len(self.outputObjects) > 0
|
||||||
|
|
||||||
|
|
||||||
class DebugObject(object):
|
class DebugObject(object):
|
||||||
"""
|
"""
|
||||||
Represents a request to debug an object
|
Represents a request to debug an object
|
||||||
Object is the type of object we want to debug
|
Object is the type of object we want to debug
|
||||||
args are parameters for use during debuging ( for example, color, tranparency )
|
args are parameters for use during debuging ( for example, color, tranparency )
|
||||||
"""
|
"""
|
||||||
def __init__(self,object,args):
|
|
||||||
|
def __init__(self, object, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.object = object
|
self.object = object
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameterError(Exception):
|
class InvalidParameterError(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when an attempt is made to provide a new parameter value
|
Raised when an attempt is made to provide a new parameter value
|
||||||
@ -375,6 +385,7 @@ class EnvironmentBuilder(object):
|
|||||||
The environment includes the builtins, as well as
|
The environment includes the builtins, as well as
|
||||||
the other methods the script will need.
|
the other methods the script will need.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.env = {}
|
self.env = {}
|
||||||
|
|
||||||
@ -397,30 +408,33 @@ class EnvironmentBuilder(object):
|
|||||||
def build(self):
|
def build(self):
|
||||||
return self.env
|
return self.env
|
||||||
|
|
||||||
|
|
||||||
class ParameterDescriptionFinder(ast.NodeTransformer):
|
class ParameterDescriptionFinder(ast.NodeTransformer):
|
||||||
"""
|
"""
|
||||||
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cq_model):
|
def __init__(self, cq_model):
|
||||||
self.cqModel = cq_model
|
self.cqModel = cq_model
|
||||||
|
|
||||||
def visit_Call(self,node):
|
def visit_Call(self, node):
|
||||||
"""
|
"""
|
||||||
Called when we see a function call. Is it describe_parameter?
|
Called when we see a function call. Is it describe_parameter?
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if node.func.id == 'describe_parameter':
|
if node.func.id == 'describe_parameter':
|
||||||
#looks like we have a call to our function.
|
# looks like we have a call to our function.
|
||||||
#first parameter is the variable,
|
# first parameter is the variable,
|
||||||
#second is the description
|
# second is the description
|
||||||
varname = node.args[0].id
|
varname = node.args[0].id
|
||||||
desc = node.args[1].s
|
desc = node.args[1].s
|
||||||
self.cqModel.add_parameter_description(varname,desc)
|
self.cqModel.add_parameter_description(varname, desc)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
print "Unable to handle function call"
|
print("Unable to handle function call")
|
||||||
pass
|
pass
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||||
"""
|
"""
|
||||||
@ -447,7 +461,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||||
except:
|
except:
|
||||||
print "Unable to handle assignment for variable '%s'" % var_name
|
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def visit_Assign(self, node):
|
def visit_Assign(self, node):
|
||||||
@ -455,8 +469,8 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||||||
try:
|
try:
|
||||||
left_side = node.targets[0]
|
left_side = node.targets[0]
|
||||||
|
|
||||||
#do not handle attribute assignments
|
# do not handle attribute assignments
|
||||||
if isinstance(left_side,ast.Attribute):
|
if isinstance(left_side, ast.Attribute):
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||||
@ -467,6 +481,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||||||
self.handle_assignment(n.id, v)
|
self.handle_assignment(n.id, v)
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print "Unable to handle assignment for node '%s'" % ast.dump(left_side)
|
print("Unable to handle assignment for node '%s'" %
|
||||||
|
ast.dump(left_side))
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
@ -37,7 +37,7 @@ def _fc_path():
|
|||||||
"/usr/bin/freecad/lib",
|
"/usr/bin/freecad/lib",
|
||||||
"/usr/lib/freecad",
|
"/usr/lib/freecad",
|
||||||
"/usr/lib64/freecad/lib",
|
"/usr/lib64/freecad/lib",
|
||||||
]:
|
]:
|
||||||
if os.path.exists(_PATH):
|
if os.path.exists(_PATH):
|
||||||
return _PATH
|
return _PATH
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ def _fc_path():
|
|||||||
"c:/apps/FreeCAD 0.15/bin",
|
"c:/apps/FreeCAD 0.15/bin",
|
||||||
"c:/apps/FreeCAD 0.16/bin",
|
"c:/apps/FreeCAD 0.16/bin",
|
||||||
"c:/apps/FreeCAD 0.17/bin",
|
"c:/apps/FreeCAD 0.17/bin",
|
||||||
]:
|
]:
|
||||||
if os.path.exists(_PATH):
|
if os.path.exists(_PATH):
|
||||||
return _PATH
|
return _PATH
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ def _fc_path():
|
|||||||
"/Applications/FreeCAD.app/Contents/lib",
|
"/Applications/FreeCAD.app/Contents/lib",
|
||||||
os.path.join(os.path.expanduser("~"),
|
os.path.join(os.path.expanduser("~"),
|
||||||
"Library/Application Support/FreeCAD/lib"),
|
"Library/Application Support/FreeCAD/lib"),
|
||||||
]:
|
]:
|
||||||
if os.path.exists(_PATH):
|
if os.path.exists(_PATH):
|
||||||
return _PATH
|
return _PATH
|
||||||
|
|
||||||
|
@ -3,7 +3,9 @@ import cadquery
|
|||||||
import FreeCAD
|
import FreeCAD
|
||||||
import Drawing
|
import Drawing
|
||||||
|
|
||||||
import tempfile, os, StringIO
|
import tempfile
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -26,12 +28,12 @@ class UNITS:
|
|||||||
|
|
||||||
|
|
||||||
def toString(shape, exportType, tolerance=0.1):
|
def toString(shape, exportType, tolerance=0.1):
|
||||||
s = StringIO.StringIO()
|
s = io.StringIO()
|
||||||
exportShape(shape, exportType, s, tolerance)
|
exportShape(shape, exportType, s, tolerance)
|
||||||
return s.getvalue()
|
return s.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
def exportShape(shape, exportType, fileLike, tolerance=0.1):
|
||||||
"""
|
"""
|
||||||
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
|
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
|
||||||
object, the first value is exported
|
object, the first value is exported
|
||||||
@ -42,23 +44,22 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
for closing the object
|
for closing the object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isinstance(shape, cadquery.CQ):
|
||||||
if isinstance(shape,cadquery.CQ):
|
|
||||||
shape = shape.val()
|
shape = shape.val()
|
||||||
|
|
||||||
if exportType == ExportTypes.TJS:
|
if exportType == ExportTypes.TJS:
|
||||||
#tessellate the model
|
# tessellate the model
|
||||||
tess = shape.tessellate(tolerance)
|
tess = shape.tessellate(tolerance)
|
||||||
|
|
||||||
mesher = JsonMesh() #warning: needs to be changed to remove buildTime and exportTime!!!
|
mesher = JsonMesh() # warning: needs to be changed to remove buildTime and exportTime!!!
|
||||||
#add vertices
|
# add vertices
|
||||||
for vec in tess[0]:
|
for vec in tess[0]:
|
||||||
mesher.addVertex(vec.x, vec.y, vec.z)
|
mesher.addVertex(vec.x, vec.y, vec.z)
|
||||||
|
|
||||||
#add faces
|
# add faces
|
||||||
for f in tess[1]:
|
for f in tess[1]:
|
||||||
mesher.addTriangleFace(f[0],f[1], f[2])
|
mesher.addTriangleFace(f[0], f[1], f[2])
|
||||||
fileLike.write( mesher.toJson())
|
fileLike.write(mesher.toJson())
|
||||||
elif exportType == ExportTypes.SVG:
|
elif exportType == ExportTypes.SVG:
|
||||||
fileLike.write(getSVG(shape.wrapped))
|
fileLike.write(getSVG(shape.wrapped))
|
||||||
elif exportType == ExportTypes.AMF:
|
elif exportType == ExportTypes.AMF:
|
||||||
@ -66,11 +67,11 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
aw = AmfWriter(tess).writeAmf(fileLike)
|
aw = AmfWriter(tess).writeAmf(fileLike)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
#all these types required writing to a file and then
|
# all these types required writing to a file and then
|
||||||
#re-reading. this is due to the fact that FreeCAD writes these
|
# re-reading. this is due to the fact that FreeCAD writes these
|
||||||
(h, outFileName) = tempfile.mkstemp()
|
(h, outFileName) = tempfile.mkstemp()
|
||||||
#weird, but we need to close this file. the next step is going to write to
|
# weird, but we need to close this file. the next step is going to write to
|
||||||
#it from c code, so it needs to be closed.
|
# it from c code, so it needs to be closed.
|
||||||
os.close(h)
|
os.close(h)
|
||||||
|
|
||||||
if exportType == ExportTypes.STEP:
|
if exportType == ExportTypes.STEP:
|
||||||
@ -83,13 +84,14 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
res = readAndDeleteFile(outFileName)
|
res = readAndDeleteFile(outFileName)
|
||||||
fileLike.write(res)
|
fileLike.write(res)
|
||||||
|
|
||||||
|
|
||||||
def readAndDeleteFile(fileName):
|
def readAndDeleteFile(fileName):
|
||||||
"""
|
"""
|
||||||
read data from file provided, and delete it when done
|
read data from file provided, and delete it when done
|
||||||
return the contents as a string
|
return the contents as a string
|
||||||
"""
|
"""
|
||||||
res = ""
|
res = ""
|
||||||
with open(fileName,'r') as f:
|
with open(fileName, 'r') as f:
|
||||||
res = f.read()
|
res = f.read()
|
||||||
|
|
||||||
os.remove(fileName)
|
os.remove(fileName)
|
||||||
@ -102,16 +104,16 @@ def guessUnitOfMeasure(shape):
|
|||||||
"""
|
"""
|
||||||
bb = shape.BoundBox
|
bb = shape.BoundBox
|
||||||
|
|
||||||
dimList = [ bb.XLength, bb.YLength,bb.ZLength ]
|
dimList = [bb.XLength, bb.YLength, bb.ZLength]
|
||||||
#no real part would likely be bigger than 10 inches on any side
|
# no real part would likely be bigger than 10 inches on any side
|
||||||
if max(dimList) > 10:
|
if max(dimList) > 10:
|
||||||
return UNITS.MM
|
return UNITS.MM
|
||||||
|
|
||||||
#no real part would likely be smaller than 0.1 mm on all dimensions
|
# no real part would likely be smaller than 0.1 mm on all dimensions
|
||||||
if min(dimList) < 0.1:
|
if min(dimList) < 0.1:
|
||||||
return UNITS.IN
|
return UNITS.IN
|
||||||
|
|
||||||
#no real part would have the sum of its dimensions less than about 5mm
|
# no real part would have the sum of its dimensions less than about 5mm
|
||||||
if sum(dimList) < 10:
|
if sum(dimList) < 10:
|
||||||
return UNITS.IN
|
return UNITS.IN
|
||||||
|
|
||||||
@ -119,76 +121,79 @@ def guessUnitOfMeasure(shape):
|
|||||||
|
|
||||||
|
|
||||||
class AmfWriter(object):
|
class AmfWriter(object):
|
||||||
def __init__(self,tessellation):
|
def __init__(self, tessellation):
|
||||||
|
|
||||||
self.units = "mm"
|
self.units = "mm"
|
||||||
self.tessellation = tessellation
|
self.tessellation = tessellation
|
||||||
|
|
||||||
def writeAmf(self,outFile):
|
def writeAmf(self, outFile):
|
||||||
amf = ET.Element('amf',units=self.units)
|
amf = ET.Element('amf', units=self.units)
|
||||||
#TODO: if result is a compound, we need to loop through them
|
# TODO: if result is a compound, we need to loop through them
|
||||||
object = ET.SubElement(amf,'object',id="0")
|
object = ET.SubElement(amf, 'object', id="0")
|
||||||
mesh = ET.SubElement(object,'mesh')
|
mesh = ET.SubElement(object, 'mesh')
|
||||||
vertices = ET.SubElement(mesh,'vertices')
|
vertices = ET.SubElement(mesh, 'vertices')
|
||||||
volume = ET.SubElement(mesh,'volume')
|
volume = ET.SubElement(mesh, 'volume')
|
||||||
|
|
||||||
#add vertices
|
# add vertices
|
||||||
for v in self.tessellation[0]:
|
for v in self.tessellation[0]:
|
||||||
vtx = ET.SubElement(vertices,'vertex')
|
vtx = ET.SubElement(vertices, 'vertex')
|
||||||
coord = ET.SubElement(vtx,'coordinates')
|
coord = ET.SubElement(vtx, 'coordinates')
|
||||||
x = ET.SubElement(coord,'x')
|
x = ET.SubElement(coord, 'x')
|
||||||
x.text = str(v.x)
|
x.text = str(v.x)
|
||||||
y = ET.SubElement(coord,'y')
|
y = ET.SubElement(coord, 'y')
|
||||||
y.text = str(v.y)
|
y.text = str(v.y)
|
||||||
z = ET.SubElement(coord,'z')
|
z = ET.SubElement(coord, 'z')
|
||||||
z.text = str(v.z)
|
z.text = str(v.z)
|
||||||
|
|
||||||
#add triangles
|
# add triangles
|
||||||
for t in self.tessellation[1]:
|
for t in self.tessellation[1]:
|
||||||
triangle = ET.SubElement(volume,'triangle')
|
triangle = ET.SubElement(volume, 'triangle')
|
||||||
v1 = ET.SubElement(triangle,'v1')
|
v1 = ET.SubElement(triangle, 'v1')
|
||||||
v1.text = str(t[0])
|
v1.text = str(t[0])
|
||||||
v2 = ET.SubElement(triangle,'v2')
|
v2 = ET.SubElement(triangle, 'v2')
|
||||||
v2.text = str(t[1])
|
v2.text = str(t[1])
|
||||||
v3 = ET.SubElement(triangle,'v3')
|
v3 = ET.SubElement(triangle, 'v3')
|
||||||
v3.text = str(t[2])
|
v3.text = str(t[2])
|
||||||
|
|
||||||
|
ET.ElementTree(amf).write(outFile, encoding='ISO-8859-1')
|
||||||
|
|
||||||
ET.ElementTree(amf).write(outFile,encoding='ISO-8859-1')
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Objects that represent
|
Objects that represent
|
||||||
three.js JSON object notation
|
three.js JSON object notation
|
||||||
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
|
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class JsonMesh(object):
|
class JsonMesh(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.vertices = [];
|
self.vertices = []
|
||||||
self.faces = [];
|
self.faces = []
|
||||||
self.nVertices = 0;
|
self.nVertices = 0
|
||||||
self.nFaces = 0;
|
self.nFaces = 0
|
||||||
|
|
||||||
def addVertex(self,x,y,z):
|
def addVertex(self, x, y, z):
|
||||||
self.nVertices += 1;
|
self.nVertices += 1
|
||||||
self.vertices.extend([x,y,z]);
|
self.vertices.extend([x, y, z])
|
||||||
|
|
||||||
#add triangle composed of the three provided vertex indices
|
# add triangle composed of the three provided vertex indices
|
||||||
def addTriangleFace(self, i,j,k):
|
def addTriangleFace(self, i, j, k):
|
||||||
#first position means justa simple triangle
|
# first position means justa simple triangle
|
||||||
self.nFaces += 1;
|
self.nFaces += 1
|
||||||
self.faces.extend([0,int(i),int(j),int(k)]);
|
self.faces.extend([0, int(i), int(j), int(k)])
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Get a json model from this model.
|
Get a json model from this model.
|
||||||
For now we'll forget about colors, vertex normals, and all that stuff
|
For now we'll forget about colors, vertex normals, and all that stuff
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def toJson(self):
|
def toJson(self):
|
||||||
return JSON_TEMPLATE % {
|
return JSON_TEMPLATE % {
|
||||||
'vertices' : str(self.vertices),
|
'vertices': str(self.vertices),
|
||||||
'faces' : str(self.faces),
|
'faces': str(self.faces),
|
||||||
'nVertices': self.nVertices,
|
'nVertices': self.nVertices,
|
||||||
'nFaces' : self.nFaces
|
'nFaces': self.nFaces
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -210,62 +215,64 @@ def getPaths(freeCadSVG):
|
|||||||
hiddenPaths = []
|
hiddenPaths = []
|
||||||
visiblePaths = []
|
visiblePaths = []
|
||||||
if len(freeCadSVG) > 0:
|
if len(freeCadSVG) > 0:
|
||||||
#yuk, freecad returns svg fragments. stupid stupid
|
# yuk, freecad returns svg fragments. stupid stupid
|
||||||
fullDoc = "<root>%s</root>" % freeCadSVG
|
fullDoc = "<root>%s</root>" % freeCadSVG
|
||||||
e = ET.ElementTree(ET.fromstring(fullDoc))
|
e = ET.ElementTree(ET.fromstring(fullDoc))
|
||||||
segments = e.findall(".//g")
|
segments = e.findall(".//g")
|
||||||
for s in segments:
|
for s in segments:
|
||||||
paths = s.findall("path")
|
paths = s.findall("path")
|
||||||
|
|
||||||
if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK
|
if s.get("stroke-width") == "0.15": # hidden line HACK HACK HACK
|
||||||
mylist = hiddenPaths
|
mylist = hiddenPaths
|
||||||
else:
|
else:
|
||||||
mylist = visiblePaths
|
mylist = visiblePaths
|
||||||
|
|
||||||
for p in paths:
|
for p in paths:
|
||||||
mylist.append(p.get("d"))
|
mylist.append(p.get("d"))
|
||||||
return (hiddenPaths,visiblePaths)
|
return (hiddenPaths, visiblePaths)
|
||||||
else:
|
else:
|
||||||
return ([],[])
|
return ([], [])
|
||||||
|
|
||||||
|
|
||||||
def getSVG(shape,opts=None):
|
def getSVG(shape, opts=None):
|
||||||
"""
|
"""
|
||||||
Export a shape to SVG
|
Export a shape to SVG
|
||||||
"""
|
"""
|
||||||
|
|
||||||
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
|
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
||||||
|
|
||||||
if opts:
|
if opts:
|
||||||
d.update(opts)
|
d.update(opts)
|
||||||
|
|
||||||
#need to guess the scale and the coordinate center
|
# need to guess the scale and the coordinate center
|
||||||
uom = guessUnitOfMeasure(shape)
|
uom = guessUnitOfMeasure(shape)
|
||||||
|
|
||||||
width=float(d['width'])
|
width = float(d['width'])
|
||||||
height=float(d['height'])
|
height = float(d['height'])
|
||||||
marginLeft=float(d['marginLeft'])
|
marginLeft = float(d['marginLeft'])
|
||||||
marginTop=float(d['marginTop'])
|
marginTop = float(d['marginTop'])
|
||||||
|
|
||||||
#TODO: provide option to give 3 views
|
# TODO: provide option to give 3 views
|
||||||
viewVector = FreeCAD.Base.Vector(-1.75,1.1,5)
|
viewVector = FreeCAD.Base.Vector(-1.75, 1.1, 5)
|
||||||
(visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector)
|
(visibleG0, visibleG1, hiddenG0, hiddenG1) = Drawing.project(shape, viewVector)
|
||||||
|
|
||||||
(hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented!
|
(hiddenPaths, visiblePaths) = getPaths(Drawing.projectToSVG(
|
||||||
|
shape, viewVector, "ShowHiddenLines")) # this param is totally undocumented!
|
||||||
|
|
||||||
#get bounding box -- these are all in 2-d space
|
# get bounding box -- these are all in 2-d space
|
||||||
bb = visibleG0.BoundBox
|
bb = visibleG0.BoundBox
|
||||||
bb.add(visibleG1.BoundBox)
|
bb.add(visibleG1.BoundBox)
|
||||||
bb.add(hiddenG0.BoundBox)
|
bb.add(hiddenG0.BoundBox)
|
||||||
bb.add(hiddenG1.BoundBox)
|
bb.add(hiddenG1.BoundBox)
|
||||||
|
|
||||||
#width pixels for x, height pixesl for y
|
# width pixels for x, height pixesl for y
|
||||||
unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
|
unitScale = min(width / bb.XLength * 0.75, height / bb.YLength * 0.75)
|
||||||
|
|
||||||
#compute amount to translate-- move the top left into view
|
# compute amount to translate-- move the top left into view
|
||||||
(xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale)
|
(xTranslate, yTranslate) = ((0 - bb.XMin) + marginLeft /
|
||||||
|
unitScale, (0 - bb.YMax) - marginTop / unitScale)
|
||||||
|
|
||||||
#compute paths ( again -- had to strip out freecad crap )
|
# compute paths ( again -- had to strip out freecad crap )
|
||||||
hiddenContent = ""
|
hiddenContent = ""
|
||||||
for p in hiddenPaths:
|
for p in hiddenPaths:
|
||||||
hiddenContent += PATHTEMPLATE % p
|
hiddenContent += PATHTEMPLATE % p
|
||||||
@ -274,21 +281,21 @@ def getSVG(shape,opts=None):
|
|||||||
for p in visiblePaths:
|
for p in visiblePaths:
|
||||||
visibleContent += PATHTEMPLATE % p
|
visibleContent += PATHTEMPLATE % p
|
||||||
|
|
||||||
svg = SVG_TEMPLATE % (
|
svg = SVG_TEMPLATE % (
|
||||||
{
|
{
|
||||||
"unitScale" : str(unitScale),
|
"unitScale": str(unitScale),
|
||||||
"strokeWidth" : str(1.0/unitScale),
|
"strokeWidth": str(1.0 / unitScale),
|
||||||
"hiddenContent" : hiddenContent ,
|
"hiddenContent": hiddenContent,
|
||||||
"visibleContent" :visibleContent,
|
"visibleContent": visibleContent,
|
||||||
"xTranslate" : str(xTranslate),
|
"xTranslate": str(xTranslate),
|
||||||
"yTranslate" : str(yTranslate),
|
"yTranslate": str(yTranslate),
|
||||||
"width" : str(width),
|
"width": str(width),
|
||||||
"height" : str(height),
|
"height": str(height),
|
||||||
"textboxY" :str(height - 30),
|
"textboxY": str(height - 30),
|
||||||
"uom" : str(uom)
|
"uom": str(uom)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
#svg = SVG_TEMPLATE % (
|
# svg = SVG_TEMPLATE % (
|
||||||
# {"content": projectedContent}
|
# {"content": projectedContent}
|
||||||
#)
|
#)
|
||||||
return svg
|
return svg
|
||||||
@ -302,13 +309,12 @@ def exportSVG(shape, fileName):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
svg = getSVG(shape.val().wrapped)
|
svg = getSVG(shape.val().wrapped)
|
||||||
f = open(fileName,'w')
|
f = open(fileName, 'w')
|
||||||
f.write(svg)
|
f.write(svg)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
JSON_TEMPLATE = """\
|
||||||
JSON_TEMPLATE= """\
|
|
||||||
{
|
{
|
||||||
"metadata" :
|
"metadata" :
|
||||||
{
|
{
|
||||||
@ -388,5 +394,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||||||
</svg>
|
</svg>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PATHTEMPLATE="\t\t\t<path d=\"%s\" />\n"
|
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ class Vector(object):
|
|||||||
* a 3-tuple
|
* a 3-tuple
|
||||||
* three float values, x, y, and z
|
* three float values, x, y, and z
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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])
|
||||||
@ -82,7 +83,8 @@ class Vector(object):
|
|||||||
elif len(args) == 0:
|
elif len(args) == 0:
|
||||||
fV = FreeCAD.Base.Vector(0, 0, 0)
|
fV = FreeCAD.Base.Vector(0, 0, 0)
|
||||||
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
|
||||||
|
|
||||||
@ -147,16 +149,20 @@ class Vector(object):
|
|||||||
return self.wrapped.getAngle(v.wrapped)
|
return self.wrapped.getAngle(v.wrapped)
|
||||||
|
|
||||||
def distanceToLine(self):
|
def distanceToLine(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def projectToLine(self):
|
def projectToLine(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def distanceToPlane(self):
|
def distanceToPlane(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def projectToPlane(self):
|
def projectToPlane(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def __add__(self, v):
|
def __add__(self, v):
|
||||||
return self.add(v)
|
return self.add(v)
|
||||||
@ -179,6 +185,7 @@ class Matrix:
|
|||||||
|
|
||||||
Used to move geometry in space.
|
Used to move geometry in space.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, matrix=None):
|
def __init__(self, matrix=None):
|
||||||
if matrix is None:
|
if matrix is None:
|
||||||
self.wrapped = FreeCAD.Base.Matrix()
|
self.wrapped = FreeCAD.Base.Matrix()
|
||||||
@ -255,7 +262,7 @@ class Plane(object):
|
|||||||
return namedPlanes[stdName]
|
return namedPlanes[stdName]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError('Supported names are {}'.format(
|
raise ValueError('Supported names are {}'.format(
|
||||||
namedPlanes.keys()))
|
list(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)):
|
||||||
@ -580,6 +587,7 @@ class Plane(object):
|
|||||||
|
|
||||||
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
|
||||||
@ -631,13 +639,13 @@ class BoundBox(object):
|
|||||||
if (fc_bb1.XMin < fc_bb2.XMin and
|
if (fc_bb1.XMin < fc_bb2.XMin and
|
||||||
fc_bb1.XMax > fc_bb2.XMax and
|
fc_bb1.XMax > fc_bb2.XMax and
|
||||||
fc_bb1.YMin < fc_bb2.YMin and
|
fc_bb1.YMin < fc_bb2.YMin and
|
||||||
fc_bb1.YMax > fc_bb2.YMax):
|
fc_bb1.YMax > fc_bb2.YMax):
|
||||||
return b1
|
return b1
|
||||||
|
|
||||||
if (fc_bb2.XMin < fc_bb1.XMin and
|
if (fc_bb2.XMin < fc_bb1.XMin and
|
||||||
fc_bb2.XMax > fc_bb1.XMax and
|
fc_bb2.XMax > fc_bb1.XMax and
|
||||||
fc_bb2.YMin < fc_bb1.YMin and
|
fc_bb2.YMin < fc_bb1.YMin and
|
||||||
fc_bb2.YMax > fc_bb1.YMax):
|
fc_bb2.YMax > fc_bb1.YMax):
|
||||||
return b2
|
return b2
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -8,10 +8,12 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import urllib as urlreader
|
import urllib as urlreader
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
class ImportTypes:
|
class ImportTypes:
|
||||||
STEP = "STEP"
|
STEP = "STEP"
|
||||||
|
|
||||||
|
|
||||||
class UNITS:
|
class UNITS:
|
||||||
MM = "mm"
|
MM = "mm"
|
||||||
IN = "in"
|
IN = "in"
|
||||||
@ -24,23 +26,23 @@ def importShape(importType, fileName):
|
|||||||
:param fileName: THe name of the file that we're importing
|
:param fileName: THe name of the file that we're importing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#Check to see what type of file we're working with
|
# Check to see what type of file we're working with
|
||||||
if importType == ImportTypes.STEP:
|
if importType == ImportTypes.STEP:
|
||||||
return importStep(fileName)
|
return importStep(fileName)
|
||||||
|
|
||||||
|
|
||||||
#Loads a STEP file into a CQ.Workplane object
|
# Loads a STEP file into a CQ.Workplane object
|
||||||
def importStep(fileName):
|
def importStep(fileName):
|
||||||
"""
|
"""
|
||||||
Accepts a file name and loads the STEP file into a cadquery shape
|
Accepts a file name and loads the STEP file into a cadquery shape
|
||||||
:param fileName: The path and name of the STEP file to be imported
|
:param fileName: The path and name of the STEP file to be imported
|
||||||
"""
|
"""
|
||||||
#Now read and return the shape
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
#print fileName
|
# print fileName
|
||||||
rshape = Part.read(fileName)
|
rshape = Part.read(fileName)
|
||||||
|
|
||||||
#Make sure that we extract all the solids
|
# Make sure that we extract all the solids
|
||||||
solids = []
|
solids = []
|
||||||
for solid in rshape.Solids:
|
for solid in rshape.Solids:
|
||||||
solids.append(Shape.cast(solid))
|
solids.append(Shape.cast(solid))
|
||||||
@ -49,23 +51,26 @@ def importStep(fileName):
|
|||||||
except:
|
except:
|
||||||
raise ValueError("STEP File Could not be loaded")
|
raise ValueError("STEP File Could not be loaded")
|
||||||
|
|
||||||
#Loads a STEP file from an URL into a CQ.Workplane object
|
# Loads a STEP file from an URL into a CQ.Workplane object
|
||||||
def importStepFromURL(url):
|
|
||||||
#Now read and return the shape
|
|
||||||
|
def importStepFromURL(url):
|
||||||
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
webFile = urlreader.urlopen(url)
|
webFile = urlreader.urlopen(url)
|
||||||
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
|
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
|
||||||
tempFile.write(webFile.read())
|
tempFile.write(webFile.read())
|
||||||
webFile.close()
|
webFile.close()
|
||||||
tempFile.close()
|
tempFile.close()
|
||||||
|
|
||||||
rshape = Part.read(tempFile.name)
|
rshape = Part.read(tempFile.name)
|
||||||
|
|
||||||
#Make sure that we extract all the solids
|
# Make sure that we extract all the solids
|
||||||
solids = []
|
solids = []
|
||||||
for solid in rshape.Solids:
|
for solid in rshape.Solids:
|
||||||
solids.append(Shape.cast(solid))
|
solids.append(Shape.cast(solid))
|
||||||
|
|
||||||
return cadquery.Workplane("XY").newObject(solids)
|
return cadquery.Workplane("XY").newObject(solids)
|
||||||
except:
|
except:
|
||||||
raise ValueError("STEP File from the URL: " + url + " Could not be loaded")
|
raise ValueError("STEP File from the URL: " +
|
||||||
|
url + " Could not be loaded")
|
||||||
|
@ -89,7 +89,7 @@ class Shape(object):
|
|||||||
elif s == 'Solid':
|
elif s == 'Solid':
|
||||||
tr = Solid(obj)
|
tr = Solid(obj)
|
||||||
elif s == 'Compound':
|
elif s == 'Compound':
|
||||||
#compound of solids, lets return a solid instead
|
# compound of solids, lets return a solid instead
|
||||||
if len(obj.Solids) > 1:
|
if len(obj.Solids) > 1:
|
||||||
tr = Solid(obj)
|
tr = Solid(obj)
|
||||||
elif len(obj.Solids) == 1:
|
elif len(obj.Solids) == 1:
|
||||||
@ -122,7 +122,7 @@ class Shape(object):
|
|||||||
self.wrapped.exportStep(fileName)
|
self.wrapped.exportStep(fileName)
|
||||||
elif fileFormat == ExportFormats.AMF:
|
elif fileFormat == ExportFormats.AMF:
|
||||||
# not built into FreeCAD
|
# not built into FreeCAD
|
||||||
#TODO: user selected tolerance
|
# TODO: user selected tolerance
|
||||||
tess = self.wrapped.tessellate(0.1)
|
tess = self.wrapped.tessellate(0.1)
|
||||||
aw = amfUtils.AMFWriter(tess)
|
aw = amfUtils.AMFWriter(tess)
|
||||||
aw.writeAmf(fileName)
|
aw.writeAmf(fileName)
|
||||||
@ -189,7 +189,7 @@ class Shape(object):
|
|||||||
return BoundBox(self.wrapped.BoundBox)
|
return BoundBox(self.wrapped.BoundBox)
|
||||||
|
|
||||||
def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
|
def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
|
||||||
if mirrorPlane == "XY" or mirrorPlane== "YX":
|
if mirrorPlane == "XY" or mirrorPlane == "YX":
|
||||||
mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 0, 1)
|
mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 0, 1)
|
||||||
elif mirrorPlane == "XZ" or mirrorPlane == "ZX":
|
elif mirrorPlane == "XZ" or mirrorPlane == "ZX":
|
||||||
mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 1, 0)
|
mirrorPlaneNormalVector = FreeCAD.Base.Vector(0, 1, 0)
|
||||||
@ -214,9 +214,10 @@ class Shape(object):
|
|||||||
elif isinstance(self.wrapped, FreeCADPart.Solid):
|
elif isinstance(self.wrapped, FreeCADPart.Solid):
|
||||||
return Vector(self.wrapped.CenterOfMass)
|
return Vector(self.wrapped.CenterOfMass)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped)))
|
raise ValueError("Cannot find the center of %s object type" % str(
|
||||||
|
type(self.Solids()[0].wrapped)))
|
||||||
|
|
||||||
def CenterOfBoundBox(self, tolerance = 0.1):
|
def CenterOfBoundBox(self, tolerance=0.1):
|
||||||
self.wrapped.tessellate(tolerance)
|
self.wrapped.tessellate(tolerance)
|
||||||
if isinstance(self.wrapped, FreeCADPart.Shape):
|
if isinstance(self.wrapped, FreeCADPart.Shape):
|
||||||
# If there are no Solids, we're probably dealing with a Face or something similar
|
# If there are no Solids, we're probably dealing with a Face or something similar
|
||||||
@ -229,7 +230,8 @@ class Shape(object):
|
|||||||
elif isinstance(self.wrapped, FreeCADPart.Solid):
|
elif isinstance(self.wrapped, FreeCADPart.Solid):
|
||||||
return Vector(self.wrapped.BoundBox.Center)
|
return Vector(self.wrapped.BoundBox.Center)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Cannot find the center(BoundBox's) of %s object type" % str(type(self.Solids()[0].wrapped)))
|
raise ValueError("Cannot find the center(BoundBox's) of %s object type" % str(
|
||||||
|
type(self.Solids()[0].wrapped)))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def CombinedCenter(objects):
|
def CombinedCenter(objects):
|
||||||
@ -239,13 +241,14 @@ class Shape(object):
|
|||||||
:param objects: a list of objects with mass
|
:param objects: a list of objects with mass
|
||||||
"""
|
"""
|
||||||
total_mass = sum(Shape.computeMass(o) for o in objects)
|
total_mass = sum(Shape.computeMass(o) for o in objects)
|
||||||
weighted_centers = [o.wrapped.CenterOfMass.multiply(Shape.computeMass(o)) for o in objects]
|
weighted_centers = [o.wrapped.CenterOfMass.multiply(
|
||||||
|
Shape.computeMass(o)) for o in objects]
|
||||||
|
|
||||||
sum_wc = weighted_centers[0]
|
sum_wc = weighted_centers[0]
|
||||||
for wc in weighted_centers[1:] :
|
for wc in weighted_centers[1:]:
|
||||||
sum_wc = sum_wc.add(wc)
|
sum_wc = sum_wc.add(wc)
|
||||||
|
|
||||||
return Vector(sum_wc.multiply(1./total_mass))
|
return Vector(sum_wc.multiply(1. / total_mass))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def computeMass(object):
|
def computeMass(object):
|
||||||
@ -254,12 +257,12 @@ class Shape(object):
|
|||||||
in FreeCAD >=15, faces no longer have mass, but instead have area.
|
in FreeCAD >=15, faces no longer have mass, but instead have area.
|
||||||
"""
|
"""
|
||||||
if object.wrapped.ShapeType == 'Face':
|
if object.wrapped.ShapeType == 'Face':
|
||||||
return object.wrapped.Area
|
return object.wrapped.Area
|
||||||
else:
|
else:
|
||||||
return object.wrapped.Mass
|
return object.wrapped.Mass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def CombinedCenterOfBoundBox(objects, tolerance = 0.1):
|
def CombinedCenterOfBoundBox(objects, tolerance=0.1):
|
||||||
"""
|
"""
|
||||||
Calculates the center of BoundBox of multiple objects.
|
Calculates the center of BoundBox of multiple objects.
|
||||||
|
|
||||||
@ -273,10 +276,10 @@ class Shape(object):
|
|||||||
weighted_centers.append(o.wrapped.BoundBox.Center.multiply(1.0))
|
weighted_centers.append(o.wrapped.BoundBox.Center.multiply(1.0))
|
||||||
|
|
||||||
sum_wc = weighted_centers[0]
|
sum_wc = weighted_centers[0]
|
||||||
for wc in weighted_centers[1:] :
|
for wc in weighted_centers[1:]:
|
||||||
sum_wc = sum_wc.add(wc)
|
sum_wc = sum_wc.add(wc)
|
||||||
|
|
||||||
return Vector(sum_wc.multiply(1./total_mass))
|
return Vector(sum_wc.multiply(1. / total_mass))
|
||||||
|
|
||||||
def Closed(self):
|
def Closed(self):
|
||||||
return self.wrapped.Closed
|
return self.wrapped.Closed
|
||||||
@ -393,7 +396,7 @@ class Vertex(Shape):
|
|||||||
self.Y = obj.Y
|
self.Y = obj.Y
|
||||||
self.Z = obj.Z
|
self.Z = obj.Z
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
def toTuple(self):
|
def toTuple(self):
|
||||||
@ -425,12 +428,12 @@ class Edge(Shape):
|
|||||||
FreeCADPart.Circle: 'CIRCLE'
|
FreeCADPart.Circle: 'CIRCLE'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
def geomType(self):
|
def geomType(self):
|
||||||
t = type(self.wrapped.Curve)
|
t = type(self.wrapped.Curve)
|
||||||
if self.edgetypes.has_key(t):
|
if t in self.edgetypes:
|
||||||
return self.edgetypes[t]
|
return self.edgetypes[t]
|
||||||
else:
|
else:
|
||||||
return "Unknown Edge Curve Type: %s" % str(t)
|
return "Unknown Edge Curve Type: %s" % str(t)
|
||||||
@ -476,7 +479,7 @@ class Edge(Shape):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
|
def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
|
||||||
center = Vector(pnt)
|
center = Vector(pnt)
|
||||||
normal = Vector(dir)
|
normal = Vector(dir)
|
||||||
return Edge(FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped, angle1, angle2))
|
return Edge(FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped, angle1, angle2))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -529,7 +532,7 @@ class Wire(Shape):
|
|||||||
"""
|
"""
|
||||||
self.wrapped = obj
|
self.wrapped = obj
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -565,7 +568,8 @@ class Wire(Shape):
|
|||||||
:param normal: vector representing the direction of the plane the circle should lie in
|
:param normal: vector representing the direction of the plane the circle should lie in
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
w = Wire(FreeCADPart.Wire([FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped)]))
|
w = Wire(FreeCADPart.Wire(
|
||||||
|
[FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped)]))
|
||||||
return w
|
return w
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -588,10 +592,12 @@ class Wire(Shape):
|
|||||||
"""This method is not implemented yet."""
|
"""This method is not implemented yet."""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Face(Shape):
|
class Face(Shape):
|
||||||
"""
|
"""
|
||||||
a bounded surface that represents part of the boundary of a solid
|
a bounded surface that represents part of the boundary of a solid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
|
|
||||||
self.wrapped = obj
|
self.wrapped = obj
|
||||||
@ -603,12 +609,12 @@ class Face(Shape):
|
|||||||
FreeCADPart.Cone: 'CONE'
|
FreeCADPart.Cone: 'CONE'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
def geomType(self):
|
def geomType(self):
|
||||||
t = type(self.wrapped.Surface)
|
t = type(self.wrapped.Surface)
|
||||||
if self.facetypes.has_key(t):
|
if t in self.facetypes:
|
||||||
return self.facetypes[t]
|
return self.facetypes[t]
|
||||||
else:
|
else:
|
||||||
return "Unknown Face Surface Type: %s" % str(t)
|
return "Unknown Face Surface Type: %s" % str(t)
|
||||||
@ -661,13 +667,14 @@ class Shell(Shape):
|
|||||||
"""
|
"""
|
||||||
the outer boundary of a surface
|
the outer boundary of a surface
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, wrapped):
|
def __init__(self, wrapped):
|
||||||
"""
|
"""
|
||||||
A Shell
|
A Shell
|
||||||
"""
|
"""
|
||||||
self.wrapped = wrapped
|
self.wrapped = wrapped
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -679,13 +686,14 @@ class Solid(Shape):
|
|||||||
"""
|
"""
|
||||||
a single solid
|
a single solid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
A Solid
|
A Solid
|
||||||
"""
|
"""
|
||||||
self.wrapped = obj
|
self.wrapped = obj
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -817,11 +825,11 @@ class Solid(Shape):
|
|||||||
rs = FreeCADPart.makeRuledSurface(w1, w2)
|
rs = FreeCADPart.makeRuledSurface(w1, w2)
|
||||||
sides.append(rs)
|
sides.append(rs)
|
||||||
|
|
||||||
#make faces for the top and bottom
|
# make faces for the top and bottom
|
||||||
startFace = FreeCADPart.Face(startWires)
|
startFace = FreeCADPart.Face(startWires)
|
||||||
endFace = FreeCADPart.Face(endWires)
|
endFace = FreeCADPart.Face(endWires)
|
||||||
|
|
||||||
#collect all the faces from the sides
|
# collect all the faces from the sides
|
||||||
faceList = [startFace]
|
faceList = [startFace]
|
||||||
for s in sides:
|
for s in sides:
|
||||||
faceList.extend(s.Faces)
|
faceList.extend(s.Faces)
|
||||||
@ -858,9 +866,9 @@ class Solid(Shape):
|
|||||||
# one would think that fusing faces into a compound and then extruding would work,
|
# one would think that fusing faces into a compound and then extruding would work,
|
||||||
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc),
|
# but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc),
|
||||||
# but then cutting it from the main solid fails with BRep_NotDone.
|
# but then cutting it from the main solid fails with BRep_NotDone.
|
||||||
#the work around is to extrude each and then join the resulting solids, which seems to work
|
# the work around is to extrude each and then join the resulting solids, which seems to work
|
||||||
|
|
||||||
#FreeCAD allows this in one operation, but others might not
|
# FreeCAD allows this in one operation, but others might not
|
||||||
freeCADWires = [outerWire.wrapped]
|
freeCADWires = [outerWire.wrapped]
|
||||||
for w in innerWires:
|
for w in innerWires:
|
||||||
freeCADWires.append(w.wrapped)
|
freeCADWires.append(w.wrapped)
|
||||||
@ -906,10 +914,10 @@ class Solid(Shape):
|
|||||||
rotateCenter = FreeCAD.Base.Vector(axisStart)
|
rotateCenter = FreeCAD.Base.Vector(axisStart)
|
||||||
rotateAxis = FreeCAD.Base.Vector(axisEnd)
|
rotateAxis = FreeCAD.Base.Vector(axisEnd)
|
||||||
|
|
||||||
#Convert our axis end vector into to something FreeCAD will understand (an axis specification vector)
|
# Convert our axis end vector into to something FreeCAD will understand (an axis specification vector)
|
||||||
rotateAxis = rotateCenter.sub(rotateAxis)
|
rotateAxis = rotateCenter.sub(rotateAxis)
|
||||||
|
|
||||||
#FreeCAD wants a rotation center and then an axis to rotate around rather than an axis of rotation
|
# FreeCAD wants a rotation center and then an axis to rotate around rather than an axis of rotation
|
||||||
result = f.revolve(rotateCenter, rotateAxis, angleDegrees)
|
result = f.revolve(rotateCenter, rotateAxis, angleDegrees)
|
||||||
|
|
||||||
return Shape.cast(result)
|
return Shape.cast(result)
|
||||||
@ -1012,7 +1020,7 @@ class Compound(Shape):
|
|||||||
"""
|
"""
|
||||||
self.wrapped = obj
|
self.wrapped = obj
|
||||||
|
|
||||||
# Helps identify this solid through the use of an ID
|
# Helps identify this solid through the use of an ID
|
||||||
self.label = ""
|
self.label = ""
|
||||||
|
|
||||||
def Center(self):
|
def Center(self):
|
||||||
|
@ -2,8 +2,13 @@ from OCC.Visualization import Tesselator
|
|||||||
|
|
||||||
import cadquery
|
import cadquery
|
||||||
|
|
||||||
import tempfile, os
|
import tempfile
|
||||||
import cStringIO as StringIO
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
import cStringIO as StringIO
|
||||||
|
else:
|
||||||
|
import io as StringIO
|
||||||
|
|
||||||
from .shapes import Shape, Compound, TOLERANCE
|
from .shapes import Shape, Compound, TOLERANCE
|
||||||
from .geom import BoundBox
|
from .geom import BoundBox
|
||||||
@ -23,7 +28,8 @@ except ImportError:
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
DISCRETIZATION_TOLERANCE = 1e-3
|
DISCRETIZATION_TOLERANCE = 1e-3
|
||||||
DEFAULT_DIR = gp_Dir(-1.75,1.1,5)
|
DEFAULT_DIR = gp_Dir(-1.75, 1.1, 5)
|
||||||
|
|
||||||
|
|
||||||
class ExportTypes:
|
class ExportTypes:
|
||||||
STL = "STL"
|
STL = "STL"
|
||||||
@ -44,7 +50,7 @@ def toString(shape, exportType, tolerance=0.1):
|
|||||||
return s.getvalue()
|
return s.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
def exportShape(shape, exportType, fileLike, tolerance=0.1):
|
||||||
"""
|
"""
|
||||||
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
|
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
|
||||||
object, the first value is exported
|
object, the first value is exported
|
||||||
@ -55,33 +61,31 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
for closing the object
|
for closing the object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def tessellate(shape):
|
def tessellate(shape):
|
||||||
tess = Tesselator(shape.wrapped)
|
tess = Tesselator(shape.wrapped)
|
||||||
tess.Compute(compute_edges=True, mesh_quality=tolerance)
|
tess.Compute(compute_edges=True, mesh_quality=tolerance)
|
||||||
|
|
||||||
return tess
|
return tess
|
||||||
|
|
||||||
|
if isinstance(shape, cadquery.CQ):
|
||||||
if isinstance(shape,cadquery.CQ):
|
|
||||||
shape = shape.val()
|
shape = shape.val()
|
||||||
|
|
||||||
if exportType == ExportTypes.TJS:
|
if exportType == ExportTypes.TJS:
|
||||||
tess = tessellate(shape)
|
tess = tessellate(shape)
|
||||||
mesher = JsonMesh()
|
mesher = JsonMesh()
|
||||||
|
|
||||||
#add vertices
|
# add vertices
|
||||||
for i_vert in range(tess.ObjGetVertexCount()):
|
for i_vert in range(tess.ObjGetVertexCount()):
|
||||||
v = tess.GetVertex(i_vert)
|
v = tess.GetVertex(i_vert)
|
||||||
mesher.addVertex(*v)
|
mesher.addVertex(*v)
|
||||||
|
|
||||||
#add triangles
|
# add triangles
|
||||||
for i_tr in range(tess.ObjGetTriangleCount()):
|
for i_tr in range(tess.ObjGetTriangleCount()):
|
||||||
t = tess.GetTriangleIndex(i_tr)
|
t = tess.GetTriangleIndex(i_tr)
|
||||||
mesher.addTriangleFace(*t)
|
mesher.addTriangleFace(*t)
|
||||||
|
|
||||||
fileLike.write(mesher.toJson())
|
fileLike.write(mesher.toJson())
|
||||||
|
|
||||||
elif exportType == ExportTypes.SVG:
|
elif exportType == ExportTypes.SVG:
|
||||||
fileLike.write(getSVG(shape))
|
fileLike.write(getSVG(shape))
|
||||||
elif exportType == ExportTypes.AMF:
|
elif exportType == ExportTypes.AMF:
|
||||||
@ -90,11 +94,11 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
aw.writeAmf(fileLike)
|
aw.writeAmf(fileLike)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
#all these types required writing to a file and then
|
# all these types required writing to a file and then
|
||||||
#re-reading. this is due to the fact that FreeCAD writes these
|
# re-reading. this is due to the fact that FreeCAD writes these
|
||||||
(h, outFileName) = tempfile.mkstemp()
|
(h, outFileName) = tempfile.mkstemp()
|
||||||
#weird, but we need to close this file. the next step is going to write to
|
# weird, but we need to close this file. the next step is going to write to
|
||||||
#it from c code, so it needs to be closed.
|
# it from c code, so it needs to be closed.
|
||||||
os.close(h)
|
os.close(h)
|
||||||
|
|
||||||
if exportType == ExportTypes.STEP:
|
if exportType == ExportTypes.STEP:
|
||||||
@ -107,13 +111,14 @@ def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
|||||||
res = readAndDeleteFile(outFileName)
|
res = readAndDeleteFile(outFileName)
|
||||||
fileLike.write(res)
|
fileLike.write(res)
|
||||||
|
|
||||||
|
|
||||||
def readAndDeleteFile(fileName):
|
def readAndDeleteFile(fileName):
|
||||||
"""
|
"""
|
||||||
read data from file provided, and delete it when done
|
read data from file provided, and delete it when done
|
||||||
return the contents as a string
|
return the contents as a string
|
||||||
"""
|
"""
|
||||||
res = ""
|
res = ""
|
||||||
with open(fileName,'r') as f:
|
with open(fileName, 'r') as f:
|
||||||
res = f.read()
|
res = f.read()
|
||||||
|
|
||||||
os.remove(fileName)
|
os.remove(fileName)
|
||||||
@ -126,16 +131,16 @@ def guessUnitOfMeasure(shape):
|
|||||||
"""
|
"""
|
||||||
bb = BoundBox._fromTopoDS(shape.wrapped)
|
bb = BoundBox._fromTopoDS(shape.wrapped)
|
||||||
|
|
||||||
dimList = [ bb.xlen, bb.ylen,bb.zlen ]
|
dimList = [bb.xlen, bb.ylen, bb.zlen]
|
||||||
#no real part would likely be bigger than 10 inches on any side
|
# no real part would likely be bigger than 10 inches on any side
|
||||||
if max(dimList) > 10:
|
if max(dimList) > 10:
|
||||||
return UNITS.MM
|
return UNITS.MM
|
||||||
|
|
||||||
#no real part would likely be smaller than 0.1 mm on all dimensions
|
# no real part would likely be smaller than 0.1 mm on all dimensions
|
||||||
if min(dimList) < 0.1:
|
if min(dimList) < 0.1:
|
||||||
return UNITS.IN
|
return UNITS.IN
|
||||||
|
|
||||||
#no real part would have the sum of its dimensions less than about 5mm
|
# no real part would have the sum of its dimensions less than about 5mm
|
||||||
if sum(dimList) < 10:
|
if sum(dimList) < 10:
|
||||||
return UNITS.IN
|
return UNITS.IN
|
||||||
|
|
||||||
@ -143,109 +148,113 @@ def guessUnitOfMeasure(shape):
|
|||||||
|
|
||||||
|
|
||||||
class AmfWriter(object):
|
class AmfWriter(object):
|
||||||
def __init__(self,tessellation):
|
def __init__(self, tessellation):
|
||||||
|
|
||||||
self.units = "mm"
|
self.units = "mm"
|
||||||
self.tessellation = tessellation
|
self.tessellation = tessellation
|
||||||
|
|
||||||
def writeAmf(self,outFile):
|
def writeAmf(self, outFile):
|
||||||
amf = ET.Element('amf',units=self.units)
|
amf = ET.Element('amf', units=self.units)
|
||||||
#TODO: if result is a compound, we need to loop through them
|
# TODO: if result is a compound, we need to loop through them
|
||||||
object = ET.SubElement(amf,'object',id="0")
|
object = ET.SubElement(amf, 'object', id="0")
|
||||||
mesh = ET.SubElement(object,'mesh')
|
mesh = ET.SubElement(object, 'mesh')
|
||||||
vertices = ET.SubElement(mesh,'vertices')
|
vertices = ET.SubElement(mesh, 'vertices')
|
||||||
volume = ET.SubElement(mesh,'volume')
|
volume = ET.SubElement(mesh, 'volume')
|
||||||
|
|
||||||
#add vertices
|
# add vertices
|
||||||
for i_vert in range(self.tessellation.ObjGetVertexCount()):
|
for i_vert in range(self.tessellation.ObjGetVertexCount()):
|
||||||
v = self.tessellation.GetVertex(i_vert)
|
v = self.tessellation.GetVertex(i_vert)
|
||||||
vtx = ET.SubElement(vertices,'vertex')
|
vtx = ET.SubElement(vertices, 'vertex')
|
||||||
coord = ET.SubElement(vtx,'coordinates')
|
coord = ET.SubElement(vtx, 'coordinates')
|
||||||
x = ET.SubElement(coord,'x')
|
x = ET.SubElement(coord, 'x')
|
||||||
x.text = str(v[0])
|
x.text = str(v[0])
|
||||||
y = ET.SubElement(coord,'y')
|
y = ET.SubElement(coord, 'y')
|
||||||
y.text = str(v[1])
|
y.text = str(v[1])
|
||||||
z = ET.SubElement(coord,'z')
|
z = ET.SubElement(coord, 'z')
|
||||||
z.text = str(v[2])
|
z.text = str(v[2])
|
||||||
|
|
||||||
#add triangles
|
# add triangles
|
||||||
for i_tr in range(self.tessellation.ObjGetTriangleCount()):
|
for i_tr in range(self.tessellation.ObjGetTriangleCount()):
|
||||||
t = self.tessellation.GetTriangleIndex(i_tr)
|
t = self.tessellation.GetTriangleIndex(i_tr)
|
||||||
triangle = ET.SubElement(volume,'triangle')
|
triangle = ET.SubElement(volume, 'triangle')
|
||||||
v1 = ET.SubElement(triangle,'v1')
|
v1 = ET.SubElement(triangle, 'v1')
|
||||||
v1.text = str(t[0])
|
v1.text = str(t[0])
|
||||||
v2 = ET.SubElement(triangle,'v2')
|
v2 = ET.SubElement(triangle, 'v2')
|
||||||
v2.text = str(t[1])
|
v2.text = str(t[1])
|
||||||
v3 = ET.SubElement(triangle,'v3')
|
v3 = ET.SubElement(triangle, 'v3')
|
||||||
v3.text = str(t[2])
|
v3.text = str(t[2])
|
||||||
|
|
||||||
|
ET.ElementTree(amf).write(outFile, encoding='ISO-8859-1')
|
||||||
|
|
||||||
ET.ElementTree(amf).write(outFile,encoding='ISO-8859-1')
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Objects that represent
|
Objects that represent
|
||||||
three.js JSON object notation
|
three.js JSON object notation
|
||||||
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
|
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class JsonMesh(object):
|
class JsonMesh(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.vertices = [];
|
self.vertices = []
|
||||||
self.faces = [];
|
self.faces = []
|
||||||
self.nVertices = 0;
|
self.nVertices = 0
|
||||||
self.nFaces = 0;
|
self.nFaces = 0
|
||||||
|
|
||||||
def addVertex(self,x,y,z):
|
def addVertex(self, x, y, z):
|
||||||
self.nVertices += 1;
|
self.nVertices += 1
|
||||||
self.vertices.extend([x,y,z]);
|
self.vertices.extend([x, y, z])
|
||||||
|
|
||||||
#add triangle composed of the three provided vertex indices
|
# add triangle composed of the three provided vertex indices
|
||||||
def addTriangleFace(self, i,j,k):
|
def addTriangleFace(self, i, j, k):
|
||||||
#first position means justa simple triangle
|
# first position means justa simple triangle
|
||||||
self.nFaces += 1;
|
self.nFaces += 1
|
||||||
self.faces.extend([0,int(i),int(j),int(k)]);
|
self.faces.extend([0, int(i), int(j), int(k)])
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Get a json model from this model.
|
Get a json model from this model.
|
||||||
For now we'll forget about colors, vertex normals, and all that stuff
|
For now we'll forget about colors, vertex normals, and all that stuff
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def toJson(self):
|
def toJson(self):
|
||||||
return JSON_TEMPLATE % {
|
return JSON_TEMPLATE % {
|
||||||
'vertices' : str(self.vertices),
|
'vertices': str(self.vertices),
|
||||||
'faces' : str(self.faces),
|
'faces': str(self.faces),
|
||||||
'nVertices': self.nVertices,
|
'nVertices': self.nVertices,
|
||||||
'nFaces' : self.nFaces
|
'nFaces': self.nFaces
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
def makeSVGedge(e):
|
def makeSVGedge(e):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cs = StringIO.StringIO()
|
cs = StringIO.StringIO()
|
||||||
|
|
||||||
curve = e._geomAdaptor() #adapt the edge into curve
|
curve = e._geomAdaptor() # adapt the edge into curve
|
||||||
start = curve.FirstParameter()
|
start = curve.FirstParameter()
|
||||||
end = curve.LastParameter()
|
end = curve.LastParameter()
|
||||||
|
|
||||||
points = GCPnts_QuasiUniformDeflection(curve,
|
points = GCPnts_QuasiUniformDeflection(curve,
|
||||||
DISCRETIZATION_TOLERANCE,
|
DISCRETIZATION_TOLERANCE,
|
||||||
start,
|
start,
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if points.IsDone():
|
if points.IsDone():
|
||||||
point_it = (points.Value(i+1) for i in \
|
point_it = (points.Value(i + 1) for i in
|
||||||
range(points.NbPoints()))
|
range(points.NbPoints()))
|
||||||
|
|
||||||
p = point_it.next()
|
p = next(point_it)
|
||||||
cs.write('M{},{} '.format(p.X(),p.Y()))
|
cs.write('M{},{} '.format(p.X(), p.Y()))
|
||||||
|
|
||||||
for p in point_it:
|
for p in point_it:
|
||||||
cs.write('L{},{} '.format(p.X(),p.Y()))
|
cs.write('L{},{} '.format(p.X(), p.Y()))
|
||||||
|
|
||||||
return cs.getvalue()
|
return cs.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def getPaths(visibleShapes, hiddenShapes):
|
def getPaths(visibleShapes, hiddenShapes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -257,56 +266,55 @@ def getPaths(visibleShapes, hiddenShapes):
|
|||||||
for s in visibleShapes:
|
for s in visibleShapes:
|
||||||
for e in s.Edges():
|
for e in s.Edges():
|
||||||
visiblePaths.append(makeSVGedge(e))
|
visiblePaths.append(makeSVGedge(e))
|
||||||
|
|
||||||
for s in hiddenShapes:
|
for s in hiddenShapes:
|
||||||
for e in s.Edges():
|
for e in s.Edges():
|
||||||
hiddenPaths.append(makeSVGedge(e))
|
hiddenPaths.append(makeSVGedge(e))
|
||||||
|
|
||||||
return (hiddenPaths,visiblePaths)
|
return (hiddenPaths, visiblePaths)
|
||||||
|
|
||||||
|
|
||||||
|
def getSVG(shape, opts=None):
|
||||||
def getSVG(shape,opts=None):
|
|
||||||
"""
|
"""
|
||||||
Export a shape to SVG
|
Export a shape to SVG
|
||||||
"""
|
"""
|
||||||
|
|
||||||
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
|
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
||||||
|
|
||||||
if opts:
|
if opts:
|
||||||
d.update(opts)
|
d.update(opts)
|
||||||
|
|
||||||
#need to guess the scale and the coordinate center
|
# need to guess the scale and the coordinate center
|
||||||
uom = guessUnitOfMeasure(shape)
|
uom = guessUnitOfMeasure(shape)
|
||||||
|
|
||||||
width=float(d['width'])
|
width = float(d['width'])
|
||||||
height=float(d['height'])
|
height = float(d['height'])
|
||||||
marginLeft=float(d['marginLeft'])
|
marginLeft = float(d['marginLeft'])
|
||||||
marginTop=float(d['marginTop'])
|
marginTop = float(d['marginTop'])
|
||||||
|
|
||||||
hlr = HLRBRep_Algo()
|
hlr = HLRBRep_Algo()
|
||||||
hlr.Add(shape.wrapped)
|
hlr.Add(shape.wrapped)
|
||||||
|
|
||||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
||||||
DEFAULT_DIR)
|
DEFAULT_DIR)
|
||||||
)
|
)
|
||||||
|
|
||||||
hlr.Projector(projector)
|
hlr.Projector(projector)
|
||||||
hlr.Update()
|
hlr.Update()
|
||||||
hlr.Hide()
|
hlr.Hide()
|
||||||
|
|
||||||
hlr_shapes = HLRBRep_HLRToShape(hlr.GetHandle())
|
hlr_shapes = HLRBRep_HLRToShape(hlr.GetHandle())
|
||||||
|
|
||||||
visible = []
|
visible = []
|
||||||
|
|
||||||
visible_sharp_edges = hlr_shapes.VCompound()
|
visible_sharp_edges = hlr_shapes.VCompound()
|
||||||
if not visible_sharp_edges.IsNull():
|
if not visible_sharp_edges.IsNull():
|
||||||
visible.append(visible_sharp_edges)
|
visible.append(visible_sharp_edges)
|
||||||
|
|
||||||
visible_smooth_edges = hlr_shapes.Rg1LineVCompound()
|
visible_smooth_edges = hlr_shapes.Rg1LineVCompound()
|
||||||
if not visible_smooth_edges.IsNull():
|
if not visible_smooth_edges.IsNull():
|
||||||
visible.append(visible_smooth_edges)
|
visible.append(visible_smooth_edges)
|
||||||
|
|
||||||
visible_contour_edges = hlr_shapes.OutLineVCompound()
|
visible_contour_edges = hlr_shapes.OutLineVCompound()
|
||||||
if not visible_contour_edges.IsNull():
|
if not visible_contour_edges.IsNull():
|
||||||
visible.append(visible_contour_edges)
|
visible.append(visible_contour_edges)
|
||||||
@ -316,31 +324,34 @@ def getSVG(shape,opts=None):
|
|||||||
hidden_sharp_edges = hlr_shapes.HCompound()
|
hidden_sharp_edges = hlr_shapes.HCompound()
|
||||||
if not hidden_sharp_edges.IsNull():
|
if not hidden_sharp_edges.IsNull():
|
||||||
hidden.append(hidden_sharp_edges)
|
hidden.append(hidden_sharp_edges)
|
||||||
|
|
||||||
hidden_contour_edges = hlr_shapes.OutLineHCompound()
|
hidden_contour_edges = hlr_shapes.OutLineHCompound()
|
||||||
if not hidden_contour_edges.IsNull():
|
if not hidden_contour_edges.IsNull():
|
||||||
hidden.append(hidden_contour_edges)
|
hidden.append(hidden_contour_edges)
|
||||||
|
|
||||||
#Fix the underlying geometry - otherwise we will get segfaults
|
# Fix the underlying geometry - otherwise we will get segfaults
|
||||||
for el in visible: breplib.BuildCurves3d(el,TOLERANCE)
|
for el in visible:
|
||||||
for el in hidden: breplib.BuildCurves3d(el,TOLERANCE)
|
breplib.BuildCurves3d(el, TOLERANCE)
|
||||||
|
for el in hidden:
|
||||||
|
breplib.BuildCurves3d(el, TOLERANCE)
|
||||||
|
|
||||||
#convert to native CQ objects
|
# convert to native CQ objects
|
||||||
visible = map(Shape,visible)
|
visible = list(map(Shape, visible))
|
||||||
hidden = map(Shape,hidden)
|
hidden = list(map(Shape, hidden))
|
||||||
(hiddenPaths,visiblePaths) = getPaths(visible,
|
(hiddenPaths, visiblePaths) = getPaths(visible,
|
||||||
hidden)
|
hidden)
|
||||||
|
|
||||||
#get bounding box -- these are all in 2-d space
|
# get bounding box -- these are all in 2-d space
|
||||||
bb = Compound.makeCompound(hidden+visible).BoundingBox()
|
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
||||||
|
|
||||||
#width pixels for x, height pixesl for y
|
# width pixels for x, height pixesl for y
|
||||||
unitScale = min( width / bb.xlen * 0.75 , height / bb.ylen * 0.75 )
|
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
||||||
|
|
||||||
#compute amount to translate-- move the top left into view
|
# compute amount to translate-- move the top left into view
|
||||||
(xTranslate,yTranslate) = ( (0 - bb.xmin) + marginLeft/unitScale ,(0- bb.ymax) - marginTop/unitScale)
|
(xTranslate, yTranslate) = ((0 - bb.xmin) + marginLeft /
|
||||||
|
unitScale, (0 - bb.ymax) - marginTop / unitScale)
|
||||||
|
|
||||||
#compute paths ( again -- had to strip out freecad crap )
|
# compute paths ( again -- had to strip out freecad crap )
|
||||||
hiddenContent = ""
|
hiddenContent = ""
|
||||||
for p in hiddenPaths:
|
for p in hiddenPaths:
|
||||||
hiddenContent += PATHTEMPLATE % p
|
hiddenContent += PATHTEMPLATE % p
|
||||||
@ -349,21 +360,21 @@ def getSVG(shape,opts=None):
|
|||||||
for p in visiblePaths:
|
for p in visiblePaths:
|
||||||
visibleContent += PATHTEMPLATE % p
|
visibleContent += PATHTEMPLATE % p
|
||||||
|
|
||||||
svg = SVG_TEMPLATE % (
|
svg = SVG_TEMPLATE % (
|
||||||
{
|
{
|
||||||
"unitScale" : str(unitScale),
|
"unitScale": str(unitScale),
|
||||||
"strokeWidth" : str(1.0/unitScale),
|
"strokeWidth": str(1.0 / unitScale),
|
||||||
"hiddenContent" : hiddenContent ,
|
"hiddenContent": hiddenContent,
|
||||||
"visibleContent" :visibleContent,
|
"visibleContent": visibleContent,
|
||||||
"xTranslate" : str(xTranslate),
|
"xTranslate": str(xTranslate),
|
||||||
"yTranslate" : str(yTranslate),
|
"yTranslate": str(yTranslate),
|
||||||
"width" : str(width),
|
"width": str(width),
|
||||||
"height" : str(height),
|
"height": str(height),
|
||||||
"textboxY" :str(height - 30),
|
"textboxY": str(height - 30),
|
||||||
"uom" : str(uom)
|
"uom": str(uom)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
#svg = SVG_TEMPLATE % (
|
# svg = SVG_TEMPLATE % (
|
||||||
# {"content": projectedContent}
|
# {"content": projectedContent}
|
||||||
#)
|
#)
|
||||||
return svg
|
return svg
|
||||||
@ -377,13 +388,12 @@ def exportSVG(shape, fileName):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
svg = getSVG(shape.val())
|
svg = getSVG(shape.val())
|
||||||
f = open(fileName,'w')
|
f = open(fileName, 'w')
|
||||||
f.write(svg)
|
f.write(svg)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
JSON_TEMPLATE = """\
|
||||||
JSON_TEMPLATE= """\
|
|
||||||
{
|
{
|
||||||
"metadata" :
|
"metadata" :
|
||||||
{
|
{
|
||||||
@ -463,5 +473,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||||||
</svg>
|
</svg>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PATHTEMPLATE="\t\t\t<path d=\"%s\" />\n"
|
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import cadquery
|
|||||||
|
|
||||||
from OCC.gp import gp_Vec, gp_Ax1, gp_Ax3, gp_Pnt, gp_Dir, gp_Trsf, gp, gp_XYZ
|
from OCC.gp import gp_Vec, gp_Ax1, gp_Ax3, gp_Pnt, gp_Dir, gp_Trsf, gp, gp_XYZ
|
||||||
from OCC.Bnd import Bnd_Box
|
from OCC.Bnd import Bnd_Box
|
||||||
from OCC.BRepBndLib import brepbndlib_Add # brepbndlib_AddOptimal
|
from OCC.BRepBndLib import brepbndlib_Add # brepbndlib_AddOptimal
|
||||||
from OCC.BRepMesh import BRepMesh_IncrementalMesh
|
from OCC.BRepMesh import BRepMesh_IncrementalMesh
|
||||||
|
|
||||||
TOL = 1e-2
|
TOL = 1e-2
|
||||||
@ -21,6 +21,7 @@ class Vector(object):
|
|||||||
* a 3-tuple
|
* a 3-tuple
|
||||||
* three float values, x, y, and z
|
* three float values, x, y, and z
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
fV = gp_Vec(*args)
|
fV = gp_Vec(*args)
|
||||||
@ -40,7 +41,7 @@ class Vector(object):
|
|||||||
else:
|
else:
|
||||||
fV = args[0]
|
fV = args[0]
|
||||||
elif len(args) == 0:
|
elif len(args) == 0:
|
||||||
fV = gp_Vec(0,0,0)
|
fV = gp_Vec(0, 0, 0)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Expected three floats, OCC Geom_, or 3-tuple")
|
raise ValueError("Expected three floats, OCC Geom_, or 3-tuple")
|
||||||
|
|
||||||
@ -104,29 +105,33 @@ class Vector(object):
|
|||||||
return self.wrapped.Angle(v.wrapped)
|
return self.wrapped.Angle(v.wrapped)
|
||||||
|
|
||||||
def distanceToLine(self):
|
def distanceToLine(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def projectToLine(self):
|
def projectToLine(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def distanceToPlane(self):
|
def distanceToPlane(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def projectToPlane(self):
|
def projectToPlane(self):
|
||||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
raise NotImplementedError(
|
||||||
|
"Have not needed this yet, but FreeCAD supports it!")
|
||||||
|
|
||||||
def __add__(self, v):
|
def __add__(self, v):
|
||||||
return self.add(v)
|
return self.add(v)
|
||||||
|
|
||||||
def __sub__(self, v):
|
def __sub__(self, v):
|
||||||
return self.sub(v)
|
return self.sub(v)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Vector: ' + str((self.x,self.y,self.z))
|
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Vector: ' + str((self.x,self.y,self.z))
|
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.wrapped == other.wrapped
|
return self.wrapped == other.wrapped
|
||||||
'''
|
'''
|
||||||
@ -134,21 +139,21 @@ class Vector(object):
|
|||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return self.wrapped.__ne__(other)
|
return self.wrapped.__ne__(other)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def toPnt(self):
|
def toPnt(self):
|
||||||
|
|
||||||
return gp_Pnt(self.wrapped.XYZ())
|
return gp_Pnt(self.wrapped.XYZ())
|
||||||
|
|
||||||
def toDir(self):
|
def toDir(self):
|
||||||
|
|
||||||
return gp_Dir(self.wrapped.XYZ())
|
return gp_Dir(self.wrapped.XYZ())
|
||||||
|
|
||||||
def transform(self,T):
|
def transform(self, T):
|
||||||
|
|
||||||
#to gp_Pnt to obey cq transformation convention (in OCC vectors do not translate)
|
# to gp_Pnt to obey cq transformation convention (in OCC vectors do not translate)
|
||||||
pnt = self.toPnt()
|
pnt = self.toPnt()
|
||||||
pnt_t = pnt.Transformed(T.wrapped)
|
pnt_t = pnt.Transformed(T.wrapped)
|
||||||
|
|
||||||
return Vector(gp_Vec(pnt_t.XYZ()))
|
return Vector(gp_Vec(pnt_t.XYZ()))
|
||||||
|
|
||||||
|
|
||||||
@ -157,6 +162,7 @@ class Matrix:
|
|||||||
|
|
||||||
Used to move geometry in space.
|
Used to move geometry in space.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, matrix=None):
|
def __init__(self, matrix=None):
|
||||||
if matrix is None:
|
if matrix is None:
|
||||||
self.wrapped = gp_Trsf()
|
self.wrapped = gp_Trsf()
|
||||||
@ -170,19 +176,19 @@ class Matrix:
|
|||||||
def rotateY(self, angle):
|
def rotateY(self, angle):
|
||||||
self._rotate(gp.OY(),
|
self._rotate(gp.OY(),
|
||||||
angle)
|
angle)
|
||||||
|
|
||||||
def rotateZ(self, angle):
|
def rotateZ(self, angle):
|
||||||
self._rotate(gp.OZ(),
|
self._rotate(gp.OZ(),
|
||||||
angle)
|
angle)
|
||||||
|
|
||||||
def _rotate(self,direction,angle):
|
def _rotate(self, direction, angle):
|
||||||
|
|
||||||
new = gp_Trsf()
|
new = gp_Trsf()
|
||||||
new.SetRotation(direction,
|
new.SetRotation(direction,
|
||||||
angle)
|
angle)
|
||||||
|
|
||||||
self.wrapped = self.wrapped * new
|
self.wrapped = self.wrapped * new
|
||||||
|
|
||||||
def inverse(self):
|
def inverse(self):
|
||||||
return Matrix(self.wrapped.Invert())
|
return Matrix(self.wrapped.Invert())
|
||||||
|
|
||||||
@ -199,7 +205,7 @@ class Plane(object):
|
|||||||
Frequently, it is not necessary to create work planes, as they can be
|
Frequently, it is not necessary to create work planes, as they can be
|
||||||
created automatically from faces.
|
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.
|
||||||
@ -250,7 +256,7 @@ class Plane(object):
|
|||||||
return namedPlanes[stdName]
|
return namedPlanes[stdName]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError('Supported names are {}'.format(
|
raise ValueError('Supported names are {}'.format(
|
||||||
namedPlanes.keys()))
|
list(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)):
|
||||||
@ -340,11 +346,11 @@ class Plane(object):
|
|||||||
zDir = Vector(normal)
|
zDir = Vector(normal)
|
||||||
if (zDir.Length == 0.0):
|
if (zDir.Length == 0.0):
|
||||||
raise ValueError('normal should be non null')
|
raise ValueError('normal should be non null')
|
||||||
|
|
||||||
xDir = Vector(xDir)
|
xDir = Vector(xDir)
|
||||||
if (xDir.Length == 0.0):
|
if (xDir.Length == 0.0):
|
||||||
raise ValueError('xDir should be non null')
|
raise ValueError('xDir should be non null')
|
||||||
|
|
||||||
self.zDir = zDir.normalized()
|
self.zDir = zDir.normalized()
|
||||||
self._setPlaneDir(xDir)
|
self._setPlaneDir(xDir)
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
@ -353,6 +359,7 @@ class Plane(object):
|
|||||||
def origin(self):
|
def origin(self):
|
||||||
return self._origin
|
return self._origin
|
||||||
# TODO is this property rly needed -- why not handle this in the constructor
|
# TODO is this property rly needed -- why not handle this in the constructor
|
||||||
|
|
||||||
@origin.setter
|
@origin.setter
|
||||||
def origin(self, value):
|
def origin(self, value):
|
||||||
self._origin = Vector(value)
|
self._origin = Vector(value)
|
||||||
@ -404,9 +411,9 @@ class Plane(object):
|
|||||||
* Discretizing points along each curve to provide a more reliable
|
* Discretizing points along each curve to provide a more reliable
|
||||||
test.
|
test.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# TODO: also use a set of points along the wire to test as well.
|
# 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
|
# TODO: would it be more efficient to create objects in the local
|
||||||
@ -425,7 +432,7 @@ class Plane(object):
|
|||||||
# know if one is inside the other
|
# know if one is inside the other
|
||||||
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def toLocalCoords(self, obj):
|
def toLocalCoords(self, obj):
|
||||||
"""Project the provided coordinates onto this plane
|
"""Project the provided coordinates onto this plane
|
||||||
|
|
||||||
@ -511,7 +518,7 @@ class Plane(object):
|
|||||||
# - then rotate about x
|
# - then rotate about x
|
||||||
# - then transform back to global coordinates.
|
# - then transform back to global coordinates.
|
||||||
|
|
||||||
#TODO why is it here?
|
# TODO why is it here?
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -542,14 +549,14 @@ class Plane(object):
|
|||||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||||
|
|
||||||
return resultWires'''
|
return resultWires'''
|
||||||
|
|
||||||
def mirrorInPlane(self, listOfShapes, axis = 'X'):
|
def mirrorInPlane(self, listOfShapes, axis='X'):
|
||||||
|
|
||||||
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
||||||
self.zDir.toDir(),
|
self.zDir.toDir(),
|
||||||
self.xDir.toDir())
|
self.xDir.toDir())
|
||||||
T = gp_Trsf()
|
T = gp_Trsf()
|
||||||
|
|
||||||
if axis == 'X':
|
if axis == 'X':
|
||||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||||
local_coord_system.XDirection()))
|
local_coord_system.XDirection()))
|
||||||
@ -558,16 +565,16 @@ class Plane(object):
|
|||||||
local_coord_system.YDirection()))
|
local_coord_system.YDirection()))
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
resultWires = []
|
resultWires = []
|
||||||
for w in listOfShapes:
|
for w in listOfShapes:
|
||||||
mirrored = w.transformShape(Matrix(T))
|
mirrored = w.transformShape(Matrix(T))
|
||||||
|
|
||||||
#attemp stitching of the wires
|
# attemp stitching of the wires
|
||||||
resultWires.append(mirrored)
|
resultWires.append(mirrored)
|
||||||
|
|
||||||
return resultWires
|
return resultWires
|
||||||
|
|
||||||
def _setPlaneDir(self, xDir):
|
def _setPlaneDir(self, xDir):
|
||||||
"""Set the vectors parallel to the plane, i.e. xDir and yDir"""
|
"""Set the vectors parallel to the plane, i.e. xDir and yDir"""
|
||||||
xDir = Vector(xDir)
|
xDir = Vector(xDir)
|
||||||
@ -586,32 +593,32 @@ class Plane(object):
|
|||||||
# the double-inverting is strange, and I don't understand it.
|
# the double-inverting is strange, and I don't understand it.
|
||||||
forward = Matrix()
|
forward = Matrix()
|
||||||
inverse = Matrix()
|
inverse = Matrix()
|
||||||
|
|
||||||
global_coord_system = gp_Ax3()
|
global_coord_system = gp_Ax3()
|
||||||
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
||||||
gp_Dir(*self.zDir.toTuple()),
|
gp_Dir(*self.zDir.toTuple()),
|
||||||
gp_Dir(*self.xDir.toTuple())
|
gp_Dir(*self.xDir.toTuple())
|
||||||
)
|
)
|
||||||
|
|
||||||
forward.wrapped.SetTransformation(global_coord_system,
|
forward.wrapped.SetTransformation(global_coord_system,
|
||||||
local_coord_system)
|
local_coord_system)
|
||||||
|
|
||||||
inverse.wrapped.SetTransformation(local_coord_system,
|
inverse.wrapped.SetTransformation(local_coord_system,
|
||||||
global_coord_system)
|
global_coord_system)
|
||||||
|
|
||||||
#TODO verify if this is OK
|
# TODO verify if this is OK
|
||||||
self.lcs = local_coord_system
|
self.lcs = local_coord_system
|
||||||
self.rG = inverse
|
self.rG = inverse
|
||||||
self.fG = forward
|
self.fG = forward
|
||||||
|
|
||||||
|
|
||||||
class BoundBox(object):
|
class BoundBox(object):
|
||||||
"""A BoundingBox for an object or set of objects. Wraps the OCC one"""
|
"""A BoundingBox for an object or set of objects. Wraps the OCC one"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, bb):
|
def __init__(self, bb):
|
||||||
self.wrapped = bb
|
self.wrapped = bb
|
||||||
XMin, YMin, ZMin, XMax, YMax, ZMax = bb.Get()
|
XMin, YMin, ZMin, XMax, YMax, ZMax = bb.Get()
|
||||||
|
|
||||||
self.xmin = XMin
|
self.xmin = XMin
|
||||||
self.xmax = XMax
|
self.xmax = XMax
|
||||||
self.xlen = XMax - XMin
|
self.xlen = XMax - XMin
|
||||||
@ -621,11 +628,11 @@ class BoundBox(object):
|
|||||||
self.zmin = ZMin
|
self.zmin = ZMin
|
||||||
self.zmax = ZMax
|
self.zmax = ZMax
|
||||||
self.zlen = ZMax - ZMin
|
self.zlen = ZMax - ZMin
|
||||||
|
|
||||||
self.center = Vector((XMax+XMin)/2,
|
self.center = Vector((XMax + XMin) / 2,
|
||||||
(YMax+YMin)/2,
|
(YMax + YMin) / 2,
|
||||||
(ZMax+ZMin)/2)
|
(ZMax + ZMin) / 2)
|
||||||
|
|
||||||
self.DiagonalLength = self.wrapped.SquareExtent()**0.5
|
self.DiagonalLength = self.wrapped.SquareExtent()**0.5
|
||||||
|
|
||||||
def add(self, obj, tol=1e-8):
|
def add(self, obj, tol=1e-8):
|
||||||
@ -639,11 +646,11 @@ class BoundBox(object):
|
|||||||
|
|
||||||
This bounding box is not changed.
|
This bounding box is not changed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tmp = Bnd_Box()
|
tmp = Bnd_Box()
|
||||||
tmp.SetGap(tol)
|
tmp.SetGap(tol)
|
||||||
tmp.Add(self.wrapped)
|
tmp.Add(self.wrapped)
|
||||||
|
|
||||||
if isinstance(obj, tuple):
|
if isinstance(obj, tuple):
|
||||||
tmp.Update(*obj)
|
tmp.Update(*obj)
|
||||||
elif isinstance(obj, Vector):
|
elif isinstance(obj, Vector):
|
||||||
@ -664,23 +671,23 @@ class BoundBox(object):
|
|||||||
doesn't work correctly plus, there was all kinds of rounding error in
|
doesn't work correctly plus, there was all kinds of rounding error in
|
||||||
the built-in implementation i do not understand.
|
the built-in implementation i do not understand.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (bb1.XMin < bb2.XMin and
|
if (bb1.XMin < bb2.XMin and
|
||||||
bb1.XMax > bb2.XMax and
|
bb1.XMax > bb2.XMax and
|
||||||
bb1.YMin < bb2.YMin and
|
bb1.YMin < bb2.YMin and
|
||||||
bb1.YMax > bb2.YMax):
|
bb1.YMax > bb2.YMax):
|
||||||
return bb1
|
return bb1
|
||||||
|
|
||||||
if (bb2.XMin < bb1.XMin and
|
if (bb2.XMin < bb1.XMin and
|
||||||
bb2.XMax > bb1.XMax and
|
bb2.XMax > bb1.XMax and
|
||||||
bb2.YMin < bb1.YMin and
|
bb2.YMin < bb1.YMin and
|
||||||
bb2.YMax > bb1.YMax):
|
bb2.YMax > bb1.YMax):
|
||||||
return bb2
|
return bb2
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fromTopoDS(cls,shape,tol=TOL,optimal=False):
|
def _fromTopoDS(cls, shape, tol=TOL, optimal=False):
|
||||||
'''
|
'''
|
||||||
Constructs a bounnding box from a TopoDS_Shape
|
Constructs a bounnding box from a TopoDS_Shape
|
||||||
'''
|
'''
|
||||||
@ -688,14 +695,15 @@ class BoundBox(object):
|
|||||||
bbox.SetGap(tol)
|
bbox.SetGap(tol)
|
||||||
if optimal:
|
if optimal:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
#brepbndlib_AddOptimal(shape, bbox) #this is 'exact' but expensive - not yet wrapped by PythonOCC
|
# brepbndlib_AddOptimal(shape, bbox) #this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||||
else:
|
else:
|
||||||
mesh = BRepMesh_IncrementalMesh(shape,TOL,True)
|
mesh = BRepMesh_IncrementalMesh(shape, TOL, True)
|
||||||
mesh.Perform()
|
mesh.Perform()
|
||||||
brepbndlib_Add(shape, bbox, True) #this is adds +margin but is faster
|
# this is adds +margin but is faster
|
||||||
|
brepbndlib_Add(shape, bbox, True)
|
||||||
|
|
||||||
return cls(bbox)
|
return cls(bbox)
|
||||||
|
|
||||||
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 not anotherBox.wrapped.IsOut(self.wrapped)
|
return not anotherBox.wrapped.IsOut(self.wrapped)
|
||||||
|
@ -8,10 +8,12 @@ import urllib as urlreader
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from OCC.STEPControl import STEPControl_Reader
|
from OCC.STEPControl import STEPControl_Reader
|
||||||
|
|
||||||
|
|
||||||
class ImportTypes:
|
class ImportTypes:
|
||||||
STEP = "STEP"
|
STEP = "STEP"
|
||||||
|
|
||||||
|
|
||||||
class UNITS:
|
class UNITS:
|
||||||
MM = "mm"
|
MM = "mm"
|
||||||
IN = "in"
|
IN = "in"
|
||||||
@ -24,18 +26,18 @@ def importShape(importType, fileName):
|
|||||||
:param fileName: THe name of the file that we're importing
|
:param fileName: THe name of the file that we're importing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#Check to see what type of file we're working with
|
# Check to see what type of file we're working with
|
||||||
if importType == ImportTypes.STEP:
|
if importType == ImportTypes.STEP:
|
||||||
return importStep(fileName)
|
return importStep(fileName)
|
||||||
|
|
||||||
|
|
||||||
#Loads a STEP file into a CQ.Workplane object
|
# Loads a STEP file into a CQ.Workplane object
|
||||||
def importStep(fileName):
|
def importStep(fileName):
|
||||||
"""
|
"""
|
||||||
Accepts a file name and loads the STEP file into a cadquery shape
|
Accepts a file name and loads the STEP file into a cadquery shape
|
||||||
:param fileName: The path and name of the STEP file to be imported
|
:param fileName: The path and name of the STEP file to be imported
|
||||||
"""
|
"""
|
||||||
#Now read and return the shape
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
reader = STEPControl_Reader()
|
reader = STEPControl_Reader()
|
||||||
reader.ReadFile(fileName)
|
reader.ReadFile(fileName)
|
||||||
@ -43,27 +45,30 @@ def importStep(fileName):
|
|||||||
|
|
||||||
occ_shapes = []
|
occ_shapes = []
|
||||||
for i in range(reader.NbShapes()):
|
for i in range(reader.NbShapes()):
|
||||||
occ_shapes.append(reader.Shape(i+1))
|
occ_shapes.append(reader.Shape(i + 1))
|
||||||
|
|
||||||
#Make sure that we extract all the solids
|
# Make sure that we extract all the solids
|
||||||
solids = []
|
solids = []
|
||||||
for shape in occ_shapes:
|
for shape in occ_shapes:
|
||||||
solids.append(Shape.cast(shape))
|
solids.append(Shape.cast(shape))
|
||||||
|
|
||||||
return cadquery.Workplane("XY").newObject(solids)
|
return cadquery.Workplane("XY").newObject(solids)
|
||||||
except:
|
except:
|
||||||
raise ValueError("STEP File Could not be loaded")
|
raise ValueError("STEP File Could not be loaded")
|
||||||
|
|
||||||
#Loads a STEP file from an URL into a CQ.Workplane object
|
# Loads a STEP file from an URL into a CQ.Workplane object
|
||||||
def importStepFromURL(url):
|
|
||||||
#Now read and return the shape
|
|
||||||
|
def importStepFromURL(url):
|
||||||
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
webFile = urlreader.urlopen(url)
|
webFile = urlreader.urlopen(url)
|
||||||
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
|
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
|
||||||
tempFile.write(webFile.read())
|
tempFile.write(webFile.read())
|
||||||
webFile.close()
|
webFile.close()
|
||||||
tempFile.close()
|
tempFile.close()
|
||||||
|
|
||||||
return importStep(tempFile.name)
|
return importStep(tempFile.name)
|
||||||
except:
|
except:
|
||||||
raise ValueError("STEP File from the URL: " + url + " Could not be loaded")
|
raise ValueError("STEP File from the URL: " +
|
||||||
|
url + " Could not be loaded")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -19,11 +19,12 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
|
from cadquery import Vector, Edge, Vertex, Face, Solid, Shell, Compound
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\
|
from pyparsing import Literal, Word, nums, Optional, Combine, oneOf, upcaseTokens,\
|
||||||
CaselessLiteral,Group,infixNotation,opAssoc,Forward,\
|
CaselessLiteral, Group, infixNotation, opAssoc, Forward,\
|
||||||
ZeroOrMore,Keyword
|
ZeroOrMore, Keyword
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
class Selector(object):
|
class Selector(object):
|
||||||
@ -32,7 +33,8 @@ class Selector(object):
|
|||||||
|
|
||||||
Filters must provide a single method that filters objects.
|
Filters must provide a single method that filters objects.
|
||||||
"""
|
"""
|
||||||
def filter(self,objectList):
|
|
||||||
|
def filter(self, objectList):
|
||||||
"""
|
"""
|
||||||
Filter the provided list
|
Filter the provided list
|
||||||
:param objectList: list to filter
|
:param objectList: list to filter
|
||||||
@ -56,6 +58,7 @@ class Selector(object):
|
|||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
return InverseSelector(self)
|
return InverseSelector(self)
|
||||||
|
|
||||||
|
|
||||||
class NearestToPointSelector(Selector):
|
class NearestToPointSelector(Selector):
|
||||||
"""
|
"""
|
||||||
Selects object nearest the provided point.
|
Selects object nearest the provided point.
|
||||||
@ -73,18 +76,21 @@ class NearestToPointSelector(Selector):
|
|||||||
returns the vertex of the unit cube closest to the point x=0,y=1,z=0
|
returns the vertex of the unit cube closest to the point x=0,y=1,z=0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self,pnt ):
|
|
||||||
|
def __init__(self, pnt):
|
||||||
self.pnt = pnt
|
self.pnt = pnt
|
||||||
def filter(self,objectList):
|
|
||||||
|
def filter(self, objectList):
|
||||||
|
|
||||||
def dist(tShape):
|
def dist(tShape):
|
||||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||||
#if tShape.ShapeType == 'Vertex':
|
# if tShape.ShapeType == 'Vertex':
|
||||||
# return tShape.Point.sub(toVector(self.pnt)).Length
|
# return tShape.Point.sub(toVector(self.pnt)).Length
|
||||||
#else:
|
# else:
|
||||||
# return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
|
# return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
|
||||||
|
|
||||||
return [ min(objectList,key=dist) ]
|
return [min(objectList, key=dist)]
|
||||||
|
|
||||||
|
|
||||||
class BoxSelector(Selector):
|
class BoxSelector(Selector):
|
||||||
"""
|
"""
|
||||||
@ -100,6 +106,7 @@ class BoxSelector(Selector):
|
|||||||
|
|
||||||
CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
|
CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, point0, point1, boundingbox=False):
|
def __init__(self, point0, point1, boundingbox=False):
|
||||||
self.p0 = Vector(*point0)
|
self.p0 = Vector(*point0)
|
||||||
self.p1 = Vector(*point1)
|
self.p1 = Vector(*point1)
|
||||||
@ -130,20 +137,22 @@ class BoxSelector(Selector):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class BaseDirSelector(Selector):
|
class BaseDirSelector(Selector):
|
||||||
"""
|
"""
|
||||||
A selector that handles selection on the basis of a single
|
A selector that handles selection on the basis of a single
|
||||||
direction vector
|
direction vector
|
||||||
"""
|
"""
|
||||||
def __init__(self,vector,tolerance=0.0001 ):
|
|
||||||
|
def __init__(self, vector, tolerance=0.0001):
|
||||||
self.direction = vector
|
self.direction = vector
|
||||||
self.TOLERANCE = tolerance
|
self.TOLERANCE = tolerance
|
||||||
|
|
||||||
def test(self,vec):
|
def test(self, vec):
|
||||||
"Test a specified vector. Subclasses override to provide other implementations"
|
"Test a specified vector. Subclasses override to provide other implementations"
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def filter(self,objectList):
|
def filter(self, objectList):
|
||||||
"""
|
"""
|
||||||
There are lots of kinds of filters, but
|
There are lots of kinds of filters, but
|
||||||
for planes they are always based on the normal of the plane,
|
for planes they are always based on the normal of the plane,
|
||||||
@ -151,7 +160,7 @@ class BaseDirSelector(Selector):
|
|||||||
"""
|
"""
|
||||||
r = []
|
r = []
|
||||||
for o in objectList:
|
for o in objectList:
|
||||||
#no really good way to avoid a switch here, edges and faces are simply different!
|
# no really good way to avoid a switch here, edges and faces are simply different!
|
||||||
|
|
||||||
if type(o) == Face:
|
if type(o) == Face:
|
||||||
# a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
|
# a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
|
||||||
@ -160,13 +169,14 @@ class BaseDirSelector(Selector):
|
|||||||
if self.test(normal):
|
if self.test(normal):
|
||||||
r.append(o)
|
r.append(o)
|
||||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
||||||
#an edge is parallel to a direction if its underlying geometry is plane or line
|
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||||
tangent = o.tangentAt(None)
|
tangent = o.tangentAt(None)
|
||||||
if self.test(tangent):
|
if self.test(tangent):
|
||||||
r.append(o)
|
r.append(o)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
class ParallelDirSelector(BaseDirSelector):
|
class ParallelDirSelector(BaseDirSelector):
|
||||||
"""
|
"""
|
||||||
Selects objects parallel with the provided direction
|
Selects objects parallel with the provided direction
|
||||||
@ -187,9 +197,10 @@ class ParallelDirSelector(BaseDirSelector):
|
|||||||
CQ(aCube).faces("|Z")
|
CQ(aCube).faces("|Z")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test(self,vec):
|
def test(self, vec):
|
||||||
return self.direction.cross(vec).Length < self.TOLERANCE
|
return self.direction.cross(vec).Length < self.TOLERANCE
|
||||||
|
|
||||||
|
|
||||||
class DirectionSelector(BaseDirSelector):
|
class DirectionSelector(BaseDirSelector):
|
||||||
"""
|
"""
|
||||||
Selects objects aligned with the provided direction
|
Selects objects aligned with the provided direction
|
||||||
@ -210,9 +221,10 @@ class DirectionSelector(BaseDirSelector):
|
|||||||
CQ(aCube).faces("+Z")
|
CQ(aCube).faces("+Z")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test(self,vec):
|
def test(self, vec):
|
||||||
return abs(self.direction.getAngle(vec) < self.TOLERANCE)
|
return abs(self.direction.getAngle(vec) < self.TOLERANCE)
|
||||||
|
|
||||||
|
|
||||||
class PerpendicularDirSelector(BaseDirSelector):
|
class PerpendicularDirSelector(BaseDirSelector):
|
||||||
"""
|
"""
|
||||||
Selects objects perpendicular with the provided direction
|
Selects objects perpendicular with the provided direction
|
||||||
@ -233,9 +245,10 @@ class PerpendicularDirSelector(BaseDirSelector):
|
|||||||
CQ(aCube).faces("#Z")
|
CQ(aCube).faces("#Z")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test(self,vec):
|
def test(self, vec):
|
||||||
angle = self.direction.getAngle(vec)
|
angle = self.direction.getAngle(vec)
|
||||||
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE )
|
r = (abs(angle) < self.TOLERANCE) or (
|
||||||
|
abs(angle - math.pi) < self.TOLERANCE)
|
||||||
return not r
|
return not r
|
||||||
|
|
||||||
|
|
||||||
@ -259,16 +272,18 @@ class TypeSelector(Selector):
|
|||||||
CQ(aCube).faces( "%PLANE" )
|
CQ(aCube).faces( "%PLANE" )
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self,typeString):
|
|
||||||
|
def __init__(self, typeString):
|
||||||
self.typeString = typeString.upper()
|
self.typeString = typeString.upper()
|
||||||
|
|
||||||
def filter(self,objectList):
|
def filter(self, objectList):
|
||||||
r = []
|
r = []
|
||||||
for o in objectList:
|
for o in objectList:
|
||||||
if o.geomType() == self.typeString:
|
if o.geomType() == self.typeString:
|
||||||
r.append(o)
|
r.append(o)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
class DirectionMinMaxSelector(Selector):
|
class DirectionMinMaxSelector(Selector):
|
||||||
"""
|
"""
|
||||||
Selects objects closest or farthest in the specified direction
|
Selects objects closest or farthest in the specified direction
|
||||||
@ -291,32 +306,35 @@ class DirectionMinMaxSelector(Selector):
|
|||||||
CQ(aCube).faces( ">Z" )
|
CQ(aCube).faces( ">Z" )
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, vector, directionMax=True, tolerance=0.0001):
|
def __init__(self, vector, directionMax=True, tolerance=0.0001):
|
||||||
self.vector = vector
|
self.vector = vector
|
||||||
self.max = max
|
self.max = max
|
||||||
self.directionMax = directionMax
|
self.directionMax = directionMax
|
||||||
self.TOLERANCE = tolerance
|
self.TOLERANCE = tolerance
|
||||||
def filter(self,objectList):
|
|
||||||
|
def filter(self, objectList):
|
||||||
|
|
||||||
def distance(tShape):
|
def distance(tShape):
|
||||||
return tShape.Center().dot(self.vector)
|
return tShape.Center().dot(self.vector)
|
||||||
|
|
||||||
# import OrderedDict
|
# import OrderedDict
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
#make and distance to object dict
|
# make and distance to object dict
|
||||||
objectDict = {distance(el) : el for el in objectList}
|
objectDict = {distance(el): el for el in objectList}
|
||||||
#transform it into an ordered dict
|
# transform it into an ordered dict
|
||||||
objectDict = OrderedDict(sorted(objectDict.items(),
|
objectDict = OrderedDict(sorted(list(objectDict.items()),
|
||||||
key=lambda x: x[0]))
|
key=lambda x: x[0]))
|
||||||
|
|
||||||
# find out the max/min distance
|
# find out the max/min distance
|
||||||
if self.directionMax:
|
if self.directionMax:
|
||||||
d = objectDict.keys()[-1]
|
d = list(objectDict.keys())[-1]
|
||||||
else:
|
else:
|
||||||
d = objectDict.keys()[0]
|
d = list(objectDict.keys())[0]
|
||||||
|
|
||||||
# return all objects at the max/min distance (within a tolerance)
|
# return all objects at the max/min distance (within a tolerance)
|
||||||
return filter(lambda o: abs(d - distance(o)) < self.TOLERANCE, objectList)
|
return [o for o in objectList if abs(d - distance(o)) < self.TOLERANCE]
|
||||||
|
|
||||||
|
|
||||||
class DirectionNthSelector(ParallelDirSelector):
|
class DirectionNthSelector(ParallelDirSelector):
|
||||||
"""
|
"""
|
||||||
@ -327,41 +345,44 @@ class DirectionNthSelector(ParallelDirSelector):
|
|||||||
Linear Edges
|
Linear Edges
|
||||||
Planar Faces
|
Planar Faces
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, vector, n, directionMax=True, tolerance=0.0001):
|
def __init__(self, vector, n, directionMax=True, tolerance=0.0001):
|
||||||
self.direction = vector
|
self.direction = vector
|
||||||
self.max = max
|
self.max = max
|
||||||
self.directionMax = directionMax
|
self.directionMax = directionMax
|
||||||
self.TOLERANCE = tolerance
|
self.TOLERANCE = tolerance
|
||||||
self.N = n
|
self.N = n
|
||||||
|
|
||||||
def filter(self,objectList):
|
def filter(self, objectList):
|
||||||
#select first the objects that are normal/parallel to a given dir
|
# select first the objects that are normal/parallel to a given dir
|
||||||
objectList = super(DirectionNthSelector,self).filter(objectList)
|
objectList = super(DirectionNthSelector, self).filter(objectList)
|
||||||
|
|
||||||
def distance(tShape):
|
def distance(tShape):
|
||||||
return tShape.Center().dot(self.direction)
|
return tShape.Center().dot(self.direction)
|
||||||
|
|
||||||
#calculate how many digits of precision do we need
|
|
||||||
digits = int(1/self.TOLERANCE)
|
|
||||||
|
|
||||||
#make a distance to object dict
|
# calculate how many digits of precision do we need
|
||||||
#this is one to many mapping so I am using a default dict with list
|
digits = int(1 / self.TOLERANCE)
|
||||||
|
|
||||||
|
# make a distance to object dict
|
||||||
|
# this is one to many mapping so I am using a default dict with list
|
||||||
objectDict = defaultdict(list)
|
objectDict = defaultdict(list)
|
||||||
for el in objectList:
|
for el in objectList:
|
||||||
objectDict[round(distance(el),digits)].append(el)
|
objectDict[round(distance(el), digits)].append(el)
|
||||||
|
|
||||||
# choose the Nth unique rounded distance
|
# choose the Nth unique rounded distance
|
||||||
nth_distance = sorted(objectDict.keys(),
|
nth_distance = sorted(list(objectDict.keys()),
|
||||||
reverse=not self.directionMax)[self.N]
|
reverse=not self.directionMax)[self.N]
|
||||||
|
|
||||||
# map back to original objects and return
|
# map back to original objects and return
|
||||||
return objectDict[nth_distance]
|
return objectDict[nth_distance]
|
||||||
|
|
||||||
|
|
||||||
class BinarySelector(Selector):
|
class BinarySelector(Selector):
|
||||||
"""
|
"""
|
||||||
Base class for selectors that operates with two other
|
Base class for selectors that operates with two other
|
||||||
selectors. Subclass must implement the :filterResults(): method.
|
selectors. Subclass must implement the :filterResults(): method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, left, right):
|
def __init__(self, left, right):
|
||||||
self.left = left
|
self.left = left
|
||||||
self.right = right
|
self.right = right
|
||||||
@ -373,35 +394,43 @@ class BinarySelector(Selector):
|
|||||||
def filterResults(self, r_left, r_right):
|
def filterResults(self, r_left, r_right):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class AndSelector(BinarySelector):
|
class AndSelector(BinarySelector):
|
||||||
"""
|
"""
|
||||||
Intersection selector. Returns objects that is selected by both selectors.
|
Intersection selector. Returns objects that is selected by both selectors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filterResults(self, r_left, r_right):
|
def filterResults(self, r_left, r_right):
|
||||||
# return intersection of lists
|
# return intersection of lists
|
||||||
return list(set(r_left) & set(r_right))
|
return list(set(r_left) & set(r_right))
|
||||||
|
|
||||||
|
|
||||||
class SumSelector(BinarySelector):
|
class SumSelector(BinarySelector):
|
||||||
"""
|
"""
|
||||||
Union selector. Returns the sum of two selectors results.
|
Union selector. Returns the sum of two selectors results.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filterResults(self, r_left, r_right):
|
def filterResults(self, r_left, r_right):
|
||||||
# return the union (no duplicates) of lists
|
# return the union (no duplicates) of lists
|
||||||
return list(set(r_left + r_right))
|
return list(set(r_left + r_right))
|
||||||
|
|
||||||
|
|
||||||
class SubtractSelector(BinarySelector):
|
class SubtractSelector(BinarySelector):
|
||||||
"""
|
"""
|
||||||
Difference selector. Substract results of a selector from another
|
Difference selector. Substract results of a selector from another
|
||||||
selectors results.
|
selectors results.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filterResults(self, r_left, r_right):
|
def filterResults(self, r_left, r_right):
|
||||||
return list(set(r_left) - set(r_right))
|
return list(set(r_left) - set(r_right))
|
||||||
|
|
||||||
|
|
||||||
class InverseSelector(Selector):
|
class InverseSelector(Selector):
|
||||||
"""
|
"""
|
||||||
Inverts the selection of given selector. In other words, selects
|
Inverts the selection of given selector. In other words, selects
|
||||||
all objects that is not selected by given selector.
|
all objects that is not selected by given selector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, selector):
|
def __init__(self, selector):
|
||||||
self.selector = selector
|
self.selector = selector
|
||||||
|
|
||||||
@ -414,189 +443,199 @@ def _makeGrammar():
|
|||||||
"""
|
"""
|
||||||
Define the simple string selector grammar using PyParsing
|
Define the simple string selector grammar using PyParsing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#float definition
|
# float definition
|
||||||
point = Literal('.')
|
point = Literal('.')
|
||||||
plusmin = Literal('+') | Literal('-')
|
plusmin = Literal('+') | Literal('-')
|
||||||
number = Word(nums)
|
number = Word(nums)
|
||||||
integer = Combine(Optional(plusmin) + number)
|
integer = Combine(Optional(plusmin) + number)
|
||||||
floatn = Combine(integer + Optional(point + Optional(number)))
|
floatn = Combine(integer + Optional(point + Optional(number)))
|
||||||
|
|
||||||
#vector definition
|
# vector definition
|
||||||
lbracket = Literal('(')
|
lbracket = Literal('(')
|
||||||
rbracket = Literal(')')
|
rbracket = Literal(')')
|
||||||
comma = Literal(',')
|
comma = Literal(',')
|
||||||
vector = Combine(lbracket + floatn('x') + comma + \
|
vector = Combine(lbracket + floatn('x') + comma +
|
||||||
floatn('y') + comma + floatn('z') + rbracket)
|
floatn('y') + comma + floatn('z') + rbracket)
|
||||||
|
|
||||||
#direction definition
|
# direction definition
|
||||||
simple_dir = oneOf(['X','Y','Z','XY','XZ','YZ'])
|
simple_dir = oneOf(['X', 'Y', 'Z', 'XY', 'XZ', 'YZ'])
|
||||||
direction = simple_dir('simple_dir') | vector('vector_dir')
|
direction = simple_dir('simple_dir') | vector('vector_dir')
|
||||||
|
|
||||||
#CQ type definition
|
# CQ type definition
|
||||||
cqtype = oneOf(['Plane','Cylinder','Sphere','Cone','Line','Circle','Arc'],
|
cqtype = oneOf(['Plane', 'Cylinder', 'Sphere', 'Cone', 'Line', 'Circle', 'Arc'],
|
||||||
caseless=True)
|
caseless=True)
|
||||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||||
|
|
||||||
#type operator
|
# type operator
|
||||||
type_op = Literal('%')
|
type_op = Literal('%')
|
||||||
|
|
||||||
#direction operator
|
# direction operator
|
||||||
direction_op = oneOf(['>','<'])
|
direction_op = oneOf(['>', '<'])
|
||||||
|
|
||||||
#index definition
|
# index definition
|
||||||
ix_number = Group(Optional('-')+Word(nums))
|
ix_number = Group(Optional('-') + Word(nums))
|
||||||
lsqbracket = Literal('[').suppress()
|
lsqbracket = Literal('[').suppress()
|
||||||
rsqbracket = Literal(']').suppress()
|
rsqbracket = Literal(']').suppress()
|
||||||
|
|
||||||
index = lsqbracket + ix_number('index') + rsqbracket
|
|
||||||
|
|
||||||
#other operators
|
|
||||||
other_op = oneOf(['|','#','+','-'])
|
|
||||||
|
|
||||||
#named view
|
|
||||||
named_view = oneOf(['front','back','left','right','top','bottom'])
|
|
||||||
|
|
||||||
return direction('only_dir') | \
|
|
||||||
(type_op('type_op') + cqtype('cq_type')) | \
|
|
||||||
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
|
||||||
(other_op('other_op') + direction('dir')) | \
|
|
||||||
named_view('named_view')
|
|
||||||
|
|
||||||
_grammar = _makeGrammar() #make a grammar instance
|
index = lsqbracket + ix_number('index') + rsqbracket
|
||||||
|
|
||||||
|
# other operators
|
||||||
|
other_op = oneOf(['|', '#', '+', '-'])
|
||||||
|
|
||||||
|
# named view
|
||||||
|
named_view = oneOf(['front', 'back', 'left', 'right', 'top', 'bottom'])
|
||||||
|
|
||||||
|
return direction('only_dir') | \
|
||||||
|
(type_op('type_op') + cqtype('cq_type')) | \
|
||||||
|
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
||||||
|
(other_op('other_op') + direction('dir')) | \
|
||||||
|
named_view('named_view')
|
||||||
|
|
||||||
|
|
||||||
|
_grammar = _makeGrammar() # make a grammar instance
|
||||||
|
|
||||||
|
|
||||||
class _SimpleStringSyntaxSelector(Selector):
|
class _SimpleStringSyntaxSelector(Selector):
|
||||||
"""
|
"""
|
||||||
This is a private class that converts a parseResults object into a simple
|
This is a private class that converts a parseResults object into a simple
|
||||||
selector object
|
selector object
|
||||||
"""
|
"""
|
||||||
def __init__(self,parseResults):
|
|
||||||
|
def __init__(self, parseResults):
|
||||||
#define all token to object mappings
|
|
||||||
|
# define all token to object mappings
|
||||||
self.axes = {
|
self.axes = {
|
||||||
'X': Vector(1,0,0),
|
'X': Vector(1, 0, 0),
|
||||||
'Y': Vector(0,1,0),
|
'Y': Vector(0, 1, 0),
|
||||||
'Z': Vector(0,0,1),
|
'Z': Vector(0, 0, 1),
|
||||||
'XY': Vector(1,1,0),
|
'XY': Vector(1, 1, 0),
|
||||||
'YZ': Vector(0,1,1),
|
'YZ': Vector(0, 1, 1),
|
||||||
'XZ': Vector(1,0,1)
|
'XZ': Vector(1, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.namedViews = {
|
self.namedViews = {
|
||||||
'front' : (Vector(0,0,1),True),
|
'front': (Vector(0, 0, 1), True),
|
||||||
'back' : (Vector(0,0,1),False),
|
'back': (Vector(0, 0, 1), False),
|
||||||
'left' : (Vector(1,0,0),False),
|
'left': (Vector(1, 0, 0), False),
|
||||||
'right' : (Vector(1,0,0),True),
|
'right': (Vector(1, 0, 0), True),
|
||||||
'top' : (Vector(0,1,0),True),
|
'top': (Vector(0, 1, 0), True),
|
||||||
'bottom': (Vector(0,1,0),False)
|
'bottom': (Vector(0, 1, 0), False)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.operatorMinMax = {
|
self.operatorMinMax = {
|
||||||
'>' : True,
|
'>': True,
|
||||||
'<' : False,
|
'<': False,
|
||||||
'+' : True,
|
'+': True,
|
||||||
'-' : False
|
'-': False
|
||||||
}
|
}
|
||||||
|
|
||||||
self.operator = {
|
self.operator = {
|
||||||
'+' : DirectionSelector,
|
'+': DirectionSelector,
|
||||||
'-' : DirectionSelector,
|
'-': DirectionSelector,
|
||||||
'#' : PerpendicularDirSelector,
|
'#': PerpendicularDirSelector,
|
||||||
'|' : ParallelDirSelector}
|
'|': ParallelDirSelector}
|
||||||
|
|
||||||
self.parseResults = parseResults
|
self.parseResults = parseResults
|
||||||
self.mySelector = self._chooseSelector(parseResults)
|
self.mySelector = self._chooseSelector(parseResults)
|
||||||
|
|
||||||
def _chooseSelector(self,pr):
|
def _chooseSelector(self, pr):
|
||||||
"""
|
"""
|
||||||
Sets up the underlying filters accordingly
|
Sets up the underlying filters accordingly
|
||||||
"""
|
"""
|
||||||
if 'only_dir' in pr:
|
if 'only_dir' in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
return DirectionSelector(vec)
|
return DirectionSelector(vec)
|
||||||
|
|
||||||
elif 'type_op' in pr:
|
elif 'type_op' in pr:
|
||||||
return TypeSelector(pr.cq_type)
|
return TypeSelector(pr.cq_type)
|
||||||
|
|
||||||
elif 'dir_op' in pr:
|
elif 'dir_op' in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
minmax = self.operatorMinMax[pr.dir_op]
|
minmax = self.operatorMinMax[pr.dir_op]
|
||||||
|
|
||||||
if 'index' in pr:
|
if 'index' in pr:
|
||||||
return DirectionNthSelector(vec,int(''.join(pr.index.asList())),minmax)
|
return DirectionNthSelector(vec, int(''.join(pr.index.asList())), minmax)
|
||||||
else:
|
else:
|
||||||
return DirectionMinMaxSelector(vec,minmax)
|
return DirectionMinMaxSelector(vec, minmax)
|
||||||
|
|
||||||
elif 'other_op' in pr:
|
elif 'other_op' in pr:
|
||||||
vec = self._getVector(pr)
|
vec = self._getVector(pr)
|
||||||
return self.operator[pr.other_op](vec)
|
return self.operator[pr.other_op](vec)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
args = self.namedViews[pr.named_view]
|
args = self.namedViews[pr.named_view]
|
||||||
return DirectionMinMaxSelector(*args)
|
return DirectionMinMaxSelector(*args)
|
||||||
|
|
||||||
def _getVector(self,pr):
|
def _getVector(self, pr):
|
||||||
"""
|
"""
|
||||||
Translate parsed vector string into a CQ Vector
|
Translate parsed vector string into a CQ Vector
|
||||||
"""
|
"""
|
||||||
if 'vector_dir' in pr:
|
if 'vector_dir' in pr:
|
||||||
vec = pr.vector_dir
|
vec = pr.vector_dir
|
||||||
return Vector(float(vec.x),float(vec.y),float(vec.z))
|
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
||||||
else:
|
else:
|
||||||
return self.axes[pr.simple_dir]
|
return self.axes[pr.simple_dir]
|
||||||
|
|
||||||
def filter(self,objectList):
|
def filter(self, objectList):
|
||||||
"""
|
"""
|
||||||
selects minimum, maximum, positive or negative values relative to a direction
|
selects minimum, maximum, positive or negative values relative to a direction
|
||||||
[+\|-\|<\|>\|] \<X\|Y\|Z>
|
[+\|-\|<\|>\|] \<X\|Y\|Z>
|
||||||
"""
|
"""
|
||||||
return self.mySelector.filter(objectList)
|
return self.mySelector.filter(objectList)
|
||||||
|
|
||||||
|
|
||||||
def _makeExpressionGrammar(atom):
|
def _makeExpressionGrammar(atom):
|
||||||
"""
|
"""
|
||||||
Define the complex string selector grammar using PyParsing (which supports
|
Define the complex string selector grammar using PyParsing (which supports
|
||||||
logical operations and nesting)
|
logical operations and nesting)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#define operators
|
# define operators
|
||||||
and_op = Literal('and')
|
and_op = Literal('and')
|
||||||
or_op = Literal('or')
|
or_op = Literal('or')
|
||||||
delta_op = oneOf(['exc','except'])
|
delta_op = oneOf(['exc', 'except'])
|
||||||
not_op = Literal('not')
|
not_op = Literal('not')
|
||||||
|
|
||||||
def atom_callback(res):
|
def atom_callback(res):
|
||||||
return _SimpleStringSyntaxSelector(res)
|
return _SimpleStringSyntaxSelector(res)
|
||||||
|
|
||||||
atom.setParseAction(atom_callback) #construct a simple selector from every matched
|
# construct a simple selector from every matched
|
||||||
|
atom.setParseAction(atom_callback)
|
||||||
#define callback functions for all operations
|
|
||||||
|
# define callback functions for all operations
|
||||||
def and_callback(res):
|
def and_callback(res):
|
||||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
# take every secend items, i.e. all operands
|
||||||
return reduce(AndSelector,items)
|
items = res.asList()[0][::2]
|
||||||
|
return reduce(AndSelector, items)
|
||||||
|
|
||||||
def or_callback(res):
|
def or_callback(res):
|
||||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
# take every secend items, i.e. all operands
|
||||||
return reduce(SumSelector,items)
|
items = res.asList()[0][::2]
|
||||||
|
return reduce(SumSelector, items)
|
||||||
|
|
||||||
def exc_callback(res):
|
def exc_callback(res):
|
||||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
# take every secend items, i.e. all operands
|
||||||
return reduce(SubtractSelector,items)
|
items = res.asList()[0][::2]
|
||||||
|
return reduce(SubtractSelector, items)
|
||||||
|
|
||||||
def not_callback(res):
|
def not_callback(res):
|
||||||
right = res.asList()[0][1] #take second item, i.e. the operand
|
right = res.asList()[0][1] # take second item, i.e. the operand
|
||||||
return InverseSelector(right)
|
return InverseSelector(right)
|
||||||
|
|
||||||
#construct the final grammar and set all the callbacks
|
# construct the final grammar and set all the callbacks
|
||||||
expr = infixNotation(atom,
|
expr = infixNotation(atom,
|
||||||
[(and_op,2,opAssoc.LEFT,and_callback),
|
[(and_op, 2, opAssoc.LEFT, and_callback),
|
||||||
(or_op,2,opAssoc.LEFT,or_callback),
|
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||||
(delta_op,2,opAssoc.LEFT,exc_callback),
|
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||||
(not_op,1,opAssoc.RIGHT,not_callback)])
|
(not_op, 1, opAssoc.RIGHT, not_callback)])
|
||||||
|
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
||||||
_expression_grammar = _makeExpressionGrammar(_grammar)
|
_expression_grammar = _makeExpressionGrammar(_grammar)
|
||||||
|
|
||||||
|
|
||||||
class StringSyntaxSelector(Selector):
|
class StringSyntaxSelector(Selector):
|
||||||
"""
|
"""
|
||||||
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
|
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
|
||||||
@ -627,10 +666,10 @@ class StringSyntaxSelector(Selector):
|
|||||||
curve/surface type (same as :py:class:`TypeSelector`)
|
curve/surface type (same as :py:class:`TypeSelector`)
|
||||||
|
|
||||||
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ`` or ``(x,y,z)`` which defines an arbitrary direction
|
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ`` or ``(x,y,z)`` which defines an arbitrary direction
|
||||||
|
|
||||||
It is possible to combine simple selectors together using logical operations.
|
It is possible to combine simple selectors together using logical operations.
|
||||||
The following operations are suuported
|
The following operations are suuported
|
||||||
|
|
||||||
:and:
|
:and:
|
||||||
Logical AND, e.g. >X and >Y
|
Logical AND, e.g. >X and >Y
|
||||||
:or:
|
:or:
|
||||||
@ -642,22 +681,23 @@ class StringSyntaxSelector(Selector):
|
|||||||
|
|
||||||
Finally, it is also possible to use even more complex expressions with nesting
|
Finally, it is also possible to use even more complex expressions with nesting
|
||||||
and arbitrary number of terms, e.g.
|
and arbitrary number of terms, e.g.
|
||||||
|
|
||||||
(not >X[0] and #XY) or >XY[0]
|
(not >X[0] and #XY) or >XY[0]
|
||||||
|
|
||||||
Selectors are a complex topic: see :ref:`selector_reference` for more information
|
Selectors are a complex topic: see :ref:`selector_reference` for more information
|
||||||
"""
|
"""
|
||||||
def __init__(self,selectorString):
|
|
||||||
|
def __init__(self, selectorString):
|
||||||
"""
|
"""
|
||||||
Feed the input string through the parser and construct an relevant complex selector object
|
Feed the input string through the parser and construct an relevant complex selector object
|
||||||
"""
|
"""
|
||||||
self.selectorString = selectorString
|
self.selectorString = selectorString
|
||||||
parse_result = _expression_grammar.parseString(selectorString,
|
parse_result = _expression_grammar.parseString(selectorString,
|
||||||
parseAll=True)
|
parseAll=True)
|
||||||
self.mySelector = parse_result.asList()[0]
|
self.mySelector = parse_result.asList()[0]
|
||||||
|
|
||||||
def filter(self,objectList):
|
def filter(self, objectList):
|
||||||
"""
|
"""
|
||||||
Filter give object list through th already constructed complex selector object
|
Filter give object list through th already constructed complex selector object
|
||||||
"""
|
"""
|
||||||
return self.mySelector.filter(objectList)
|
return self.mySelector.filter(objectList)
|
||||||
|
@ -36,22 +36,24 @@ TEST_DEBUG_SCRIPT = textwrap.dedent(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestCQGI(BaseTest):
|
class TestCQGI(BaseTest):
|
||||||
def test_parser(self):
|
def test_parser(self):
|
||||||
model = cqgi.CQModel(TESTSCRIPT)
|
model = cqgi.CQModel(TESTSCRIPT)
|
||||||
metadata = model.metadata
|
metadata = model.metadata
|
||||||
|
|
||||||
self.assertEquals(set(metadata.parameters.keys()), {'height', 'width', 'a', 'b', 'foo'})
|
self.assertEqual(set(metadata.parameters.keys()), {
|
||||||
|
'height', 'width', 'a', 'b', 'foo'})
|
||||||
|
|
||||||
def test_build_with_debug(self):
|
def test_build_with_debug(self):
|
||||||
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
|
||||||
result = model.build()
|
result = model.build()
|
||||||
debugItems = result.debugObjects
|
debugItems = result.debugObjects
|
||||||
self.assertTrue(len(debugItems) == 2)
|
self.assertTrue(len(debugItems) == 2)
|
||||||
self.assertTrue( debugItems[0].object == "bar" )
|
self.assertTrue(debugItems[0].object == "bar")
|
||||||
self.assertTrue( debugItems[0].args == { "color":'yellow' } )
|
self.assertTrue(debugItems[0].args == {"color": 'yellow'})
|
||||||
self.assertTrue( debugItems[1].object == 2.0 )
|
self.assertTrue(debugItems[1].object == 2.0)
|
||||||
self.assertTrue( debugItems[1].args == {} )
|
self.assertTrue(debugItems[1].args == {})
|
||||||
|
|
||||||
def test_build_with_empty_params(self):
|
def test_build_with_empty_params(self):
|
||||||
model = cqgi.CQModel(TESTSCRIPT)
|
model = cqgi.CQModel(TESTSCRIPT)
|
||||||
@ -77,7 +79,7 @@ class TestCQGI(BaseTest):
|
|||||||
a_param = model.metadata.parameters['a']
|
a_param = model.metadata.parameters['a']
|
||||||
self.assertTrue(a_param.default_value == 2.0)
|
self.assertTrue(a_param.default_value == 2.0)
|
||||||
self.assertTrue(a_param.desc == 'FirstLetter')
|
self.assertTrue(a_param.desc == 'FirstLetter')
|
||||||
self.assertTrue(a_param.varType == cqgi.NumberParameterType )
|
self.assertTrue(a_param.varType == cqgi.NumberParameterType)
|
||||||
|
|
||||||
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
def test_describe_parameter_invalid_doesnt_fail_script(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -88,8 +90,8 @@ class TestCQGI(BaseTest):
|
|||||||
)
|
)
|
||||||
model = cqgi.CQModel(script)
|
model = cqgi.CQModel(script)
|
||||||
a_param = model.metadata.parameters['a']
|
a_param = model.metadata.parameters['a']
|
||||||
self.assertTrue(a_param.name == 'a' )
|
self.assertTrue(a_param.name == 'a')
|
||||||
|
|
||||||
def test_build_with_exception(self):
|
def test_build_with_exception(self):
|
||||||
badscript = textwrap.dedent(
|
badscript = textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
@ -127,9 +129,9 @@ class TestCQGI(BaseTest):
|
|||||||
|
|
||||||
model = cqgi.CQModel(script)
|
model = cqgi.CQModel(script)
|
||||||
result = model.build({})
|
result = model.build({})
|
||||||
self.assertEquals(2, len(result.results))
|
self.assertEqual(2, len(result.results))
|
||||||
self.assertEquals(1, result.results[0])
|
self.assertEqual(1, result.results[0])
|
||||||
self.assertEquals(2, result.results[1])
|
self.assertEqual(2, result.results[1])
|
||||||
|
|
||||||
def test_that_assinging_number_to_string_works(self):
|
def test_that_assinging_number_to_string_works(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -138,8 +140,8 @@ class TestCQGI(BaseTest):
|
|||||||
build_object(h)
|
build_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.parse(script).build( {'h': 33.33})
|
result = cqgi.parse(script).build({'h': 33.33})
|
||||||
self.assertEquals(result.results[0], "33.33")
|
self.assertEqual(result.results[0], "33.33")
|
||||||
|
|
||||||
def test_that_assigning_string_to_number_fails(self):
|
def test_that_assigning_string_to_number_fails(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -148,8 +150,9 @@ class TestCQGI(BaseTest):
|
|||||||
build_object(h)
|
build_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.parse(script).build( {'h': "a string"})
|
result = cqgi.parse(script).build({'h': "a string"})
|
||||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
self.assertTrue(isinstance(result.exception,
|
||||||
|
cqgi.InvalidParameterError))
|
||||||
|
|
||||||
def test_that_assigning_unknown_var_fails(self):
|
def test_that_assigning_unknown_var_fails(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -159,8 +162,9 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cqgi.parse(script).build( {'w': "var is not there"})
|
result = cqgi.parse(script).build({'w': "var is not there"})
|
||||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
self.assertTrue(isinstance(result.exception,
|
||||||
|
cqgi.InvalidParameterError))
|
||||||
|
|
||||||
def test_that_not_calling_build_object_raises_error(self):
|
def test_that_not_calling_build_object_raises_error(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -195,7 +199,7 @@ class TestCQGI(BaseTest):
|
|||||||
result = cqgi.parse(script).build({'h': False})
|
result = cqgi.parse(script).build({'h': False})
|
||||||
|
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEquals(result.first_result,'*False*')
|
self.assertEqual(result.first_result, '*False*')
|
||||||
|
|
||||||
def test_that_only_top_level_vars_are_detected(self):
|
def test_that_only_top_level_vars_are_detected(self):
|
||||||
script = textwrap.dedent(
|
script = textwrap.dedent(
|
||||||
@ -213,4 +217,4 @@ class TestCQGI(BaseTest):
|
|||||||
|
|
||||||
model = cqgi.parse(script)
|
model = cqgi.parse(script)
|
||||||
|
|
||||||
self.assertEquals(2, len(model.metadata.parameters))
|
self.assertEqual(2, len(model.metadata.parameters))
|
||||||
|
@ -9,86 +9,92 @@ __author__ = 'dcowden'
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import unittest,sys
|
import unittest
|
||||||
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
#my modules
|
# my modules
|
||||||
from tests import BaseTest,makeUnitCube,makeUnitSquareWire
|
from tests import BaseTest, makeUnitCube, makeUnitSquareWire
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
from cadquery import selectors
|
from cadquery import selectors
|
||||||
|
|
||||||
class TestCQSelectors(BaseTest):
|
|
||||||
|
|
||||||
|
class TestCQSelectors(BaseTest):
|
||||||
|
|
||||||
def testWorkplaneCenter(self):
|
def testWorkplaneCenter(self):
|
||||||
"Test Moving workplane center"
|
"Test Moving workplane center"
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
|
|
||||||
#current point and world point should be equal
|
# current point and world point should be equal
|
||||||
self.assertTupleAlmostEquals((0.0,0.0,0.0),s.plane.origin.toTuple(),3)
|
self.assertTupleAlmostEquals(
|
||||||
|
(0.0, 0.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||||
|
|
||||||
#move origin and confirm center moves
|
# move origin and confirm center moves
|
||||||
s.center(-2.0,-2.0)
|
s.center(-2.0, -2.0)
|
||||||
|
|
||||||
#current point should be 0,0, but
|
# current point should be 0,0, but
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((-2.0,-2.0,0.0),s.plane.origin.toTuple(),3)
|
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(-2.0, -2.0, 0.0), s.plane.origin.toTuple(), 3)
|
||||||
|
|
||||||
def testVertices(self):
|
def testVertices(self):
|
||||||
t = makeUnitSquareWire() # square box
|
t = makeUnitSquareWire() # square box
|
||||||
c = CQ(t)
|
c = CQ(t)
|
||||||
|
|
||||||
self.assertEqual(4,c.vertices().size() )
|
self.assertEqual(4, c.vertices().size())
|
||||||
self.assertEqual(4,c.edges().size() )
|
self.assertEqual(4, c.edges().size())
|
||||||
self.assertEqual(0,c.vertices().edges().size() ) #no edges on any vertices
|
self.assertEqual(0, c.vertices().edges().size()
|
||||||
self.assertEqual(4,c.edges().vertices().size() ) #but selecting all edges still yields all vertices
|
) # no edges on any vertices
|
||||||
self.assertEqual(1,c.wires().size()) #just one wire
|
# but selecting all edges still yields all vertices
|
||||||
self.assertEqual(0,c.faces().size())
|
self.assertEqual(4, c.edges().vertices().size())
|
||||||
self.assertEqual(0,c.vertices().faces().size()) #odd combinations all work but yield no results
|
self.assertEqual(1, c.wires().size()) # just one wire
|
||||||
self.assertEqual(0,c.edges().faces().size())
|
self.assertEqual(0, c.faces().size())
|
||||||
self.assertEqual(0,c.edges().vertices().faces().size())
|
# odd combinations all work but yield no results
|
||||||
|
self.assertEqual(0, c.vertices().faces().size())
|
||||||
|
self.assertEqual(0, c.edges().faces().size())
|
||||||
|
self.assertEqual(0, c.edges().vertices().faces().size())
|
||||||
|
|
||||||
def testEnd(self):
|
def testEnd(self):
|
||||||
c = CQ(makeUnitSquareWire())
|
c = CQ(makeUnitSquareWire())
|
||||||
self.assertEqual(4,c.vertices().size() ) #4 because there are 4 vertices
|
# 4 because there are 4 vertices
|
||||||
self.assertEqual(1,c.vertices().end().size() ) #1 because we started with 1 wire
|
self.assertEqual(4, c.vertices().size())
|
||||||
|
# 1 because we started with 1 wire
|
||||||
|
self.assertEqual(1, c.vertices().end().size())
|
||||||
|
|
||||||
def testAll(self):
|
def testAll(self):
|
||||||
"all returns a list of CQ objects, so that you can iterate over them individually"
|
"all returns a list of CQ objects, so that you can iterate over them individually"
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertEqual(6,c.faces().size())
|
self.assertEqual(6, c.faces().size())
|
||||||
self.assertEqual(6,len(c.faces().all()))
|
self.assertEqual(6, len(c.faces().all()))
|
||||||
self.assertEqual(4,c.faces().all()[0].vertices().size() )
|
self.assertEqual(4, c.faces().all()[0].vertices().size())
|
||||||
|
|
||||||
def testFirst(self):
|
def testFirst(self):
|
||||||
c = CQ( makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertEqual(type(c.vertices().first().val()),Vertex)
|
self.assertEqual(type(c.vertices().first().val()), Vertex)
|
||||||
self.assertEqual(type(c.vertices().first().first().first().val()),Vertex)
|
self.assertEqual(
|
||||||
|
type(c.vertices().first().first().first().val()), Vertex)
|
||||||
|
|
||||||
def testCompounds(self):
|
def testCompounds(self):
|
||||||
c = CQ(makeUnitSquareWire())
|
c = CQ(makeUnitSquareWire())
|
||||||
self.assertEqual(0,c.compounds().size() )
|
self.assertEqual(0, c.compounds().size())
|
||||||
self.assertEqual(0,c.shells().size() )
|
self.assertEqual(0, c.shells().size())
|
||||||
self.assertEqual(0,c.solids().size() )
|
self.assertEqual(0, c.solids().size())
|
||||||
|
|
||||||
def testSolid(self):
|
def testSolid(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
#make sure all the counts are right for a cube
|
# make sure all the counts are right for a cube
|
||||||
self.assertEqual(1,c.solids().size() )
|
self.assertEqual(1, c.solids().size())
|
||||||
self.assertEqual(6,c.faces().size() )
|
self.assertEqual(6, c.faces().size())
|
||||||
self.assertEqual(12,c.edges().size())
|
self.assertEqual(12, c.edges().size())
|
||||||
self.assertEqual(8,c.vertices().size() )
|
self.assertEqual(8, c.vertices().size())
|
||||||
self.assertEqual(0,c.compounds().size())
|
self.assertEqual(0, c.compounds().size())
|
||||||
|
|
||||||
#now any particular face should result in 4 edges and four vertices
|
|
||||||
self.assertEqual(4,c.faces().first().edges().size() )
|
|
||||||
self.assertEqual(1,c.faces().first().size() )
|
|
||||||
self.assertEqual(4,c.faces().first().vertices().size() )
|
|
||||||
|
|
||||||
self.assertEqual(4,c.faces().last().edges().size() )
|
|
||||||
|
|
||||||
|
# now any particular face should result in 4 edges and four vertices
|
||||||
|
self.assertEqual(4, c.faces().first().edges().size())
|
||||||
|
self.assertEqual(1, c.faces().first().size())
|
||||||
|
self.assertEqual(4, c.faces().first().vertices().size())
|
||||||
|
|
||||||
|
self.assertEqual(4, c.faces().last().edges().size())
|
||||||
|
|
||||||
def testFaceTypesFilter(self):
|
def testFaceTypesFilter(self):
|
||||||
"Filters by face type"
|
"Filters by face type"
|
||||||
@ -102,16 +108,16 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testPerpendicularDirFilter(self):
|
def testPerpendicularDirFilter(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
self.assertEqual(8,c.edges("#Z").size() ) #8 edges are perp. to z
|
self.assertEqual(8, c.edges("#Z").size()) # 8 edges are perp. to z
|
||||||
self.assertEqual(4, c.faces("#Z").size()) #4 faces are perp to z too!
|
self.assertEqual(4, c.faces("#Z").size()) # 4 faces are perp to z too!
|
||||||
|
|
||||||
def testFaceDirFilter(self):
|
def testFaceDirFilter(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
#a cube has one face in each direction
|
# a cube has one face in each direction
|
||||||
self.assertEqual(1, c.faces("+Z").size())
|
self.assertEqual(1, c.faces("+Z").size())
|
||||||
self.assertEqual(1, c.faces("-Z").size())
|
self.assertEqual(1, c.faces("-Z").size())
|
||||||
self.assertEqual(1, c.faces("+X").size())
|
self.assertEqual(1, c.faces("+X").size())
|
||||||
self.assertEqual(1, c.faces("X").size()) #should be same as +X
|
self.assertEqual(1, c.faces("X").size()) # should be same as +X
|
||||||
self.assertEqual(1, c.faces("-X").size())
|
self.assertEqual(1, c.faces("-X").size())
|
||||||
self.assertEqual(1, c.faces("+Y").size())
|
self.assertEqual(1, c.faces("+Y").size())
|
||||||
self.assertEqual(1, c.faces("-Y").size())
|
self.assertEqual(1, c.faces("-Y").size())
|
||||||
@ -120,13 +126,15 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testParallelPlaneFaceFilter(self):
|
def testParallelPlaneFaceFilter(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
#faces parallel to Z axis
|
# faces parallel to Z axis
|
||||||
self.assertEqual(2, c.faces("|Z").size())
|
self.assertEqual(2, c.faces("|Z").size())
|
||||||
#TODO: provide short names for ParallelDirSelector
|
# TODO: provide short names for ParallelDirSelector
|
||||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(Vector((0,0,1)))).size()) #same thing as above
|
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||||
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(Vector((0,0,-1)))).size()) #same thing as above
|
Vector((0, 0, 1)))).size()) # same thing as above
|
||||||
|
self.assertEqual(2, c.faces(selectors.ParallelDirSelector(
|
||||||
|
Vector((0, 0, -1)))).size()) # same thing as above
|
||||||
|
|
||||||
#just for fun, vertices on faces parallel to z
|
# just for fun, vertices on faces parallel to z
|
||||||
self.assertEqual(8, c.faces("|Z").vertices().size())
|
self.assertEqual(8, c.faces("|Z").vertices().size())
|
||||||
|
|
||||||
def testParallelEdgeFilter(self):
|
def testParallelEdgeFilter(self):
|
||||||
@ -138,14 +146,14 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testMaxDistance(self):
|
def testMaxDistance(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
#should select the topmost face
|
# should select the topmost face
|
||||||
self.assertEqual(1, c.faces(">Z").size())
|
self.assertEqual(1, c.faces(">Z").size())
|
||||||
self.assertEqual(4, c.faces(">Z").vertices().size())
|
self.assertEqual(4, c.faces(">Z").vertices().size())
|
||||||
|
|
||||||
#vertices should all be at z=1, if this is the top face
|
# vertices should all be at z=1, if this is the top face
|
||||||
self.assertEqual(4, len(c.faces(">Z").vertices().vals() ))
|
self.assertEqual(4, len(c.faces(">Z").vertices().vals()))
|
||||||
for v in c.faces(">Z").vertices().vals():
|
for v in c.faces(">Z").vertices().vals():
|
||||||
self.assertAlmostEqual(1.0,v.Z,3)
|
self.assertAlmostEqual(1.0, v.Z, 3)
|
||||||
|
|
||||||
# test the case of multiple objects at the same distance
|
# test the case of multiple objects at the same distance
|
||||||
el = c.edges("<Z").vals()
|
el = c.edges("<Z").vals()
|
||||||
@ -154,125 +162,130 @@ class TestCQSelectors(BaseTest):
|
|||||||
def testMinDistance(self):
|
def testMinDistance(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
#should select the topmost face
|
# should select the topmost face
|
||||||
self.assertEqual(1, c.faces("<Z").size())
|
self.assertEqual(1, c.faces("<Z").size())
|
||||||
self.assertEqual(4, c.faces("<Z").vertices().size())
|
self.assertEqual(4, c.faces("<Z").vertices().size())
|
||||||
|
|
||||||
#vertices should all be at z=1, if this is the top face
|
# vertices should all be at z=1, if this is the top face
|
||||||
self.assertEqual(4, len(c.faces("<Z").vertices().vals() ))
|
self.assertEqual(4, len(c.faces("<Z").vertices().vals()))
|
||||||
for v in c.faces("<Z").vertices().vals():
|
for v in c.faces("<Z").vertices().vals():
|
||||||
self.assertAlmostEqual(0.0,v.Z,3)
|
self.assertAlmostEqual(0.0, v.Z, 3)
|
||||||
|
|
||||||
# test the case of multiple objects at the same distance
|
# test the case of multiple objects at the same distance
|
||||||
el = c.edges("<Z").vals()
|
el = c.edges("<Z").vals()
|
||||||
self.assertEqual(4, len(el))
|
self.assertEqual(4, len(el))
|
||||||
|
|
||||||
def testNthDistance(self):
|
def testNthDistance(self):
|
||||||
c = Workplane('XY').pushPoints([(-2,0),(2,0)]).box(1,1,1)
|
c = Workplane('XY').pushPoints([(-2, 0), (2, 0)]).box(1, 1, 1)
|
||||||
|
|
||||||
#2nd face
|
# 2nd face
|
||||||
val = c.faces(selectors.DirectionNthSelector(Vector(1,0,0),1)).val()
|
val = c.faces(selectors.DirectionNthSelector(Vector(1, 0, 0), 1)).val()
|
||||||
self.assertAlmostEqual(val.Center().x,-1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
|
|
||||||
#2nd face with inversed selection vector
|
# 2nd face with inversed selection vector
|
||||||
val = c.faces(selectors.DirectionNthSelector(Vector(-1,0,0),1)).val()
|
val = c.faces(selectors.DirectionNthSelector(
|
||||||
self.assertAlmostEqual(val.Center().x,1.5)
|
Vector(-1, 0, 0), 1)).val()
|
||||||
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
#2nd last face
|
|
||||||
val = c.faces(selectors.DirectionNthSelector(Vector(1,0,0),-2)).val()
|
# 2nd last face
|
||||||
self.assertAlmostEqual(val.Center().x,1.5)
|
val = c.faces(selectors.DirectionNthSelector(
|
||||||
|
Vector(1, 0, 0), -2)).val()
|
||||||
#Last face
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
val = c.faces(selectors.DirectionNthSelector(Vector(1,0,0),-1)).val()
|
|
||||||
self.assertAlmostEqual(val.Center().x,2.5)
|
# Last face
|
||||||
|
val = c.faces(selectors.DirectionNthSelector(
|
||||||
#check if the selected face if normal to the specified Vector
|
Vector(1, 0, 0), -1)).val()
|
||||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1,0,0)).Length,0.0)
|
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||||
|
|
||||||
#repeat the test using string based selector
|
# check if the selected face if normal to the specified Vector
|
||||||
|
self.assertAlmostEqual(
|
||||||
#2nd face
|
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||||
|
|
||||||
|
# repeat the test using string based selector
|
||||||
|
|
||||||
|
# 2nd face
|
||||||
val = c.faces('>(1,0,0)[1]').val()
|
val = c.faces('>(1,0,0)[1]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,-1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
val = c.faces('>X[1]').val()
|
val = c.faces('>X[1]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,-1.5)
|
self.assertAlmostEqual(val.Center().x, -1.5)
|
||||||
|
|
||||||
#2nd face with inversed selection vector
|
# 2nd face with inversed selection vector
|
||||||
val = c.faces('>(-1,0,0)[1]').val()
|
val = c.faces('>(-1,0,0)[1]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
val = c.faces('<X[1]').val()
|
val = c.faces('<X[1]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
#2nd last face
|
# 2nd last face
|
||||||
val = c.faces('>X[-2]').val()
|
val = c.faces('>X[-2]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
#Last face
|
# Last face
|
||||||
val = c.faces('>X[-1]').val()
|
val = c.faces('>X[-1]').val()
|
||||||
self.assertAlmostEqual(val.Center().x,2.5)
|
self.assertAlmostEqual(val.Center().x, 2.5)
|
||||||
|
|
||||||
#check if the selected face if normal to the specified Vector
|
# check if the selected face if normal to the specified Vector
|
||||||
self.assertAlmostEqual(val.normalAt().cross(Vector(1,0,0)).Length,0.0)
|
self.assertAlmostEqual(
|
||||||
|
val.normalAt().cross(Vector(1, 0, 0)).Length, 0.0)
|
||||||
#test selection of multiple faces with the same distance
|
|
||||||
|
# test selection of multiple faces with the same distance
|
||||||
c = Workplane('XY')\
|
c = Workplane('XY')\
|
||||||
.box(1,4,1,centered=(False,True,False)).faces('<Z')\
|
.box(1, 4, 1, centered=(False, True, False)).faces('<Z')\
|
||||||
.box(2,2,2,centered=(True,True,False)).faces('>Z')\
|
.box(2, 2, 2, centered=(True, True, False)).faces('>Z')\
|
||||||
.box(1,1,1,centered=(True,True,False))
|
.box(1, 1, 1, centered=(True, True, False))
|
||||||
|
|
||||||
#select 2nd from the bottom (NB python indexing is 0-based)
|
# select 2nd from the bottom (NB python indexing is 0-based)
|
||||||
vals = c.faces('>Z[1]').vals()
|
vals = c.faces('>Z[1]').vals()
|
||||||
self.assertEqual(len(vals),2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('>Z[1]').val()
|
val = c.faces('>Z[1]').val()
|
||||||
self.assertAlmostEqual(val.Center().z,1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
#do the same but by selecting 3rd from the top
|
# do the same but by selecting 3rd from the top
|
||||||
vals = c.faces('<Z[2]').vals()
|
vals = c.faces('<Z[2]').vals()
|
||||||
self.assertEqual(len(vals),2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('<Z[2]').val()
|
val = c.faces('<Z[2]').val()
|
||||||
self.assertAlmostEqual(val.Center().z,1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
#do the same but by selecting 2nd last from the bottom
|
# do the same but by selecting 2nd last from the bottom
|
||||||
vals = c.faces('<Z[-2]').vals()
|
vals = c.faces('<Z[-2]').vals()
|
||||||
self.assertEqual(len(vals),2)
|
self.assertEqual(len(vals), 2)
|
||||||
|
|
||||||
val = c.faces('<Z[-2]').val()
|
val = c.faces('<Z[-2]').val()
|
||||||
self.assertAlmostEqual(val.Center().z,1)
|
self.assertAlmostEqual(val.Center().z, 1)
|
||||||
|
|
||||||
#verify that <Z[-1] is equivalent to <Z
|
# verify that <Z[-1] is equivalent to <Z
|
||||||
val1 = c.faces('<Z[-1]').val()
|
val1 = c.faces('<Z[-1]').val()
|
||||||
val2 = c.faces('<Z').val()
|
val2 = c.faces('<Z').val()
|
||||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||||
val2.Center().toTuple(),
|
val2.Center().toTuple(),
|
||||||
3)
|
3)
|
||||||
|
|
||||||
#verify that >Z[-1] is equivalent to >Z
|
# verify that >Z[-1] is equivalent to >Z
|
||||||
val1 = c.faces('>Z[-1]').val()
|
val1 = c.faces('>Z[-1]').val()
|
||||||
val2 = c.faces('>Z').val()
|
val2 = c.faces('>Z').val()
|
||||||
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
self.assertTupleAlmostEquals(val1.Center().toTuple(),
|
||||||
val2.Center().toTuple(),
|
val2.Center().toTuple(),
|
||||||
3)
|
3)
|
||||||
|
|
||||||
def testNearestTo(self):
|
def testNearestTo(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
#nearest vertex to origin is (0,0,0)
|
# nearest vertex to origin is (0,0,0)
|
||||||
t = (0.1,0.1,0.1)
|
t = (0.1, 0.1, 0.1)
|
||||||
|
|
||||||
v = c.vertices(selectors.NearestToPointSelector(t)).vals()[0]
|
v = c.vertices(selectors.NearestToPointSelector(t)).vals()[0]
|
||||||
self.assertTupleAlmostEquals((0.0,0.0,0.0),(v.X,v.Y,v.Z),3)
|
self.assertTupleAlmostEquals((0.0, 0.0, 0.0), (v.X, v.Y, v.Z), 3)
|
||||||
|
|
||||||
t = (0.1,0.1,0.2)
|
t = (0.1, 0.1, 0.2)
|
||||||
#nearest edge is the vertical side edge, 0,0,0 -> 0,0,1
|
# nearest edge is the vertical side edge, 0,0,0 -> 0,0,1
|
||||||
e = c.edges(selectors.NearestToPointSelector(t)).vals()[0]
|
e = c.edges(selectors.NearestToPointSelector(t)).vals()[0]
|
||||||
v = c.edges(selectors.NearestToPointSelector(t)).vertices().vals()
|
v = c.edges(selectors.NearestToPointSelector(t)).vertices().vals()
|
||||||
self.assertEqual(2,len(v))
|
self.assertEqual(2, len(v))
|
||||||
|
|
||||||
#nearest solid is myself
|
# nearest solid is myself
|
||||||
s = c.solids(selectors.NearestToPointSelector(t)).vals()
|
s = c.solids(selectors.NearestToPointSelector(t)).vals()
|
||||||
self.assertEqual(1,len(s))
|
self.assertEqual(1, len(s))
|
||||||
|
|
||||||
def testBox(self):
|
def testBox(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
@ -303,9 +316,11 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
|
||||||
|
|
||||||
# test multiple vertices selection
|
# test multiple vertices selection
|
||||||
vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, 0.9),(0.1, 1.1, 1.1))).vals()
|
vl = c.vertices(selectors.BoxSelector(
|
||||||
|
(-0.1, -0.1, 0.9), (0.1, 1.1, 1.1))).vals()
|
||||||
self.assertEqual(2, len(vl))
|
self.assertEqual(2, len(vl))
|
||||||
vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, -0.1),(0.1, 1.1, 1.1))).vals()
|
vl = c.vertices(selectors.BoxSelector(
|
||||||
|
(-0.1, -0.1, -0.1), (0.1, 1.1, 1.1))).vals()
|
||||||
self.assertEqual(4, len(vl))
|
self.assertEqual(4, len(vl))
|
||||||
|
|
||||||
# test edge selection
|
# test edge selection
|
||||||
@ -330,9 +345,11 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
|
||||||
|
|
||||||
# test multiple edge selection
|
# test multiple edge selection
|
||||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
el = c.edges(selectors.BoxSelector(
|
||||||
|
(-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
el = c.edges(selectors.BoxSelector(
|
||||||
|
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
# test face selection
|
# test face selection
|
||||||
@ -357,17 +374,22 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
|
||||||
|
|
||||||
# test multiple face selection
|
# test multiple face selection
|
||||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
fl = c.faces(selectors.BoxSelector(
|
||||||
|
(0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
|
||||||
self.assertEqual(2, len(fl))
|
self.assertEqual(2, len(fl))
|
||||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
fl = c.faces(selectors.BoxSelector(
|
||||||
|
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
|
||||||
self.assertEqual(3, len(fl))
|
self.assertEqual(3, len(fl))
|
||||||
|
|
||||||
# test boundingbox option
|
# test boundingbox option
|
||||||
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
|
el = c.edges(selectors.BoxSelector(
|
||||||
|
(-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
|
||||||
self.assertEqual(1, len(el))
|
self.assertEqual(1, len(el))
|
||||||
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
|
fl = c.faces(selectors.BoxSelector(
|
||||||
|
(0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
|
||||||
self.assertEqual(0, len(fl))
|
self.assertEqual(0, len(fl))
|
||||||
fl = c.faces(selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
|
fl = c.faces(selectors.BoxSelector(
|
||||||
|
(-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
|
||||||
self.assertEqual(1, len(fl))
|
self.assertEqual(1, len(fl))
|
||||||
|
|
||||||
def testAndSelector(self):
|
def testAndSelector(self):
|
||||||
@ -376,13 +398,14 @@ class TestCQSelectors(BaseTest):
|
|||||||
S = selectors.StringSyntaxSelector
|
S = selectors.StringSyntaxSelector
|
||||||
BS = selectors.BoxSelector
|
BS = selectors.BoxSelector
|
||||||
|
|
||||||
el = c.edges(selectors.AndSelector(S('|X'), BS((-2,-2,0.1), (2,2,2)))).vals()
|
el = c.edges(selectors.AndSelector(
|
||||||
|
S('|X'), BS((-2, -2, 0.1), (2, 2, 2)))).vals()
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
|
|
||||||
# test 'and' (intersection) operator
|
# test 'and' (intersection) operator
|
||||||
el = c.edges(S('|X') & BS((-2,-2,0.1), (2,2,2))).vals()
|
el = c.edges(S('|X') & BS((-2, -2, 0.1), (2, 2, 2))).vals()
|
||||||
self.assertEqual(2, len(el))
|
self.assertEqual(2, len(el))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
v = c.vertices(">X and >Y").vals()
|
v = c.vertices(">X and >Y").vals()
|
||||||
self.assertEqual(2, len(v))
|
self.assertEqual(2, len(v))
|
||||||
@ -402,7 +425,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertEqual(2, len(fl))
|
self.assertEqual(2, len(fl))
|
||||||
el = c.edges(S("|X") + S("|Y")).vals()
|
el = c.edges(S("|X") + S("|Y")).vals()
|
||||||
self.assertEqual(8, len(el))
|
self.assertEqual(8, len(el))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
fl = c.faces(">Z or <Z").vals()
|
fl = c.faces(">Z or <Z").vals()
|
||||||
self.assertEqual(2, len(fl))
|
self.assertEqual(2, len(fl))
|
||||||
@ -420,7 +443,7 @@ class TestCQSelectors(BaseTest):
|
|||||||
# test the subtract operator
|
# test the subtract operator
|
||||||
fl = c.faces(S("#Z") - S(">X")).vals()
|
fl = c.faces(S("#Z") - S(">X")).vals()
|
||||||
self.assertEqual(3, len(fl))
|
self.assertEqual(3, len(fl))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
fl = c.faces("#Z exc >X").vals()
|
fl = c.faces("#Z exc >X").vals()
|
||||||
self.assertEqual(3, len(fl))
|
self.assertEqual(3, len(fl))
|
||||||
@ -440,43 +463,42 @@ class TestCQSelectors(BaseTest):
|
|||||||
self.assertEqual(5, len(fl))
|
self.assertEqual(5, len(fl))
|
||||||
el = c.faces('>Z').edges(-S('>X')).vals()
|
el = c.faces('>Z').edges(-S('>X')).vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
# test using extended string syntax
|
# test using extended string syntax
|
||||||
fl = c.faces('not >Z').vals()
|
fl = c.faces('not >Z').vals()
|
||||||
self.assertEqual(5, len(fl))
|
self.assertEqual(5, len(fl))
|
||||||
el = c.faces('>Z').edges('not >X').vals()
|
el = c.faces('>Z').edges('not >X').vals()
|
||||||
self.assertEqual(3, len(el))
|
self.assertEqual(3, len(el))
|
||||||
|
|
||||||
def testComplexStringSelector(self):
|
def testComplexStringSelector(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
|
v = c.vertices('(>X and >Y) or (<X and <Y)').vals()
|
||||||
self.assertEqual(4, len(v))
|
self.assertEqual(4, len(v))
|
||||||
|
|
||||||
|
|
||||||
def testFaceCount(self):
|
def testFaceCount(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertEqual( 6, c.faces().size() )
|
self.assertEqual(6, c.faces().size())
|
||||||
self.assertEqual( 2, c.faces("|Z").size() )
|
self.assertEqual(2, c.faces("|Z").size())
|
||||||
|
|
||||||
def testVertexFilter(self):
|
def testVertexFilter(self):
|
||||||
"test selecting vertices on a face"
|
"test selecting vertices on a face"
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
#TODO: filters work ok, but they are in global coordinates which sux. it would be nice
|
# TODO: filters work ok, but they are in global coordinates which sux. it would be nice
|
||||||
#if they were available in coordinates local to the selected face
|
# if they were available in coordinates local to the selected face
|
||||||
|
|
||||||
v2 = c.faces("+Z").vertices("<XY")
|
v2 = c.faces("+Z").vertices("<XY")
|
||||||
self.assertEqual(1,v2.size() ) #another way
|
self.assertEqual(1, v2.size()) # another way
|
||||||
#make sure the vertex is the right one
|
# make sure the vertex is the right one
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), v2.val().toTuple(), 3)
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((0.0,0.0,1.0),v2.val().toTuple() ,3)
|
|
||||||
|
|
||||||
def testGrammar(self):
|
def testGrammar(self):
|
||||||
"""
|
"""
|
||||||
Test if reasonable string selector expressions parse without an error
|
Test if reasonable string selector expressions parse without an error
|
||||||
"""
|
"""
|
||||||
|
|
||||||
gram = selectors._expression_grammar
|
gram = selectors._expression_grammar
|
||||||
|
|
||||||
expressions = ['+X ',
|
expressions = ['+X ',
|
||||||
@ -499,5 +521,5 @@ class TestCQSelectors(BaseTest):
|
|||||||
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)',
|
'(not |(1,1,0) and >(0,0,1)) exc XY and (Z or X)',
|
||||||
'not ( <X or >X or <Y or >Y )']
|
'not ( <X or >X or <Y or >Y )']
|
||||||
|
|
||||||
for e in expressions: gram.parseString(e,parseAll=True)
|
for e in expressions:
|
||||||
|
gram.parseString(e, parseAll=True)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#system modules
|
# system modules
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from tests import BaseTest
|
from tests import BaseTest
|
||||||
@ -6,16 +6,17 @@ from OCC.gp import gp_Vec, gp_Pnt, gp_Ax2, gp_Circ, gp_DZ
|
|||||||
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
||||||
BRepBuilderAPI_MakeEdge,
|
BRepBuilderAPI_MakeEdge,
|
||||||
BRepBuilderAPI_MakeFace)
|
BRepBuilderAPI_MakeFace)
|
||||||
|
|
||||||
from OCC.GC import GC_MakeCircle
|
from OCC.GC import GC_MakeCircle
|
||||||
|
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
|
|
||||||
|
|
||||||
class TestCadObjects(BaseTest):
|
class TestCadObjects(BaseTest):
|
||||||
|
|
||||||
def _make_circle(self):
|
def _make_circle(self):
|
||||||
|
|
||||||
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3),gp_DZ()),
|
circle = gp_Circ(gp_Ax2(gp_Pnt(1, 2, 3), gp_DZ()),
|
||||||
2.)
|
2.)
|
||||||
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
return Shape.cast(BRepBuilderAPI_MakeEdge(circle).Edge())
|
||||||
|
|
||||||
@ -33,34 +34,39 @@ class TestCadObjects(BaseTest):
|
|||||||
"""
|
"""
|
||||||
v = Vertex.makeVertex(1, 1, 1)
|
v = Vertex.makeVertex(1, 1, 1)
|
||||||
self.assertEqual(1, v.X)
|
self.assertEqual(1, v.X)
|
||||||
self.assertEquals(Vector, type(v.Center()))
|
self.assertEqual(Vector, type(v.Center()))
|
||||||
|
|
||||||
def testBasicBoundingBox(self):
|
def testBasicBoundingBox(self):
|
||||||
v = Vertex.makeVertex(1, 1, 1)
|
v = Vertex.makeVertex(1, 1, 1)
|
||||||
v2 = Vertex.makeVertex(2, 2, 2)
|
v2 = Vertex.makeVertex(2, 2, 2)
|
||||||
self.assertEquals(BoundBox, type(v.BoundingBox()))
|
self.assertEqual(BoundBox, type(v.BoundingBox()))
|
||||||
self.assertEquals(BoundBox, type(v2.BoundingBox()))
|
self.assertEqual(BoundBox, type(v2.BoundingBox()))
|
||||||
|
|
||||||
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
bb1 = v.BoundingBox().add(v2.BoundingBox())
|
||||||
|
|
||||||
self.assertAlmostEquals(bb1.xlen, 1.0, 1) #OCC uses some approximations
|
# OCC uses some approximations
|
||||||
|
self.assertAlmostEqual(bb1.xlen, 1.0, 1)
|
||||||
|
|
||||||
def testEdgeWrapperCenter(self):
|
def testEdgeWrapperCenter(self):
|
||||||
e = self._make_circle()
|
e = self._make_circle()
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
|
||||||
|
|
||||||
def testEdgeWrapperMakeCircle(self):
|
def testEdgeWrapperMakeCircle(self):
|
||||||
halfCircleEdge = Edge.makeCircle(radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
|
halfCircleEdge = Edge.makeCircle(radius=10, pnt=(
|
||||||
|
0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
|
||||||
|
|
||||||
#self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
#self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
|
||||||
self.assertTupleAlmostEquals((10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
|
self.assertTupleAlmostEquals(
|
||||||
self.assertTupleAlmostEquals((-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
|
(10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
|
||||||
|
|
||||||
def testFaceWrapperMakePlane(self):
|
def testFaceWrapperMakePlane(self):
|
||||||
mplane = Face.makePlane(10,10)
|
mplane = Face.makePlane(10, 10)
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
self.assertTupleAlmostEquals(
|
||||||
|
(0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
|
||||||
|
|
||||||
def testCenterOfBoundBox(self):
|
def testCenterOfBoundBox(self):
|
||||||
pass
|
pass
|
||||||
@ -72,6 +78,7 @@ class TestCadObjects(BaseTest):
|
|||||||
"""
|
"""
|
||||||
Tests whether or not a proper weighted center can be found for a compound
|
Tests whether or not a proper weighted center can be found for a compound
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def cylinders(self, radius, height):
|
def cylinders(self, radius, height):
|
||||||
def _cyl(pnt):
|
def _cyl(pnt):
|
||||||
# Inner function to build a cylinder
|
# Inner function to build a cylinder
|
||||||
@ -85,22 +92,24 @@ class TestCadObjects(BaseTest):
|
|||||||
Workplane.cyl = cylinders
|
Workplane.cyl = cylinders
|
||||||
|
|
||||||
# Now test. here we want weird workplane to see if the objects are transformed right
|
# Now test. here we want weird workplane to see if the objects are transformed right
|
||||||
s = Workplane("XY").rect(2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
|
s = Workplane("XY").rect(
|
||||||
|
2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
|
||||||
|
|
||||||
self.assertEquals(4, len(s.val().Solids()))
|
self.assertEqual(4, len(s.val().Solids()))
|
||||||
self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
self.assertTupleAlmostEquals(
|
||||||
|
(0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
|
||||||
|
|
||||||
def testDot(self):
|
def testDot(self):
|
||||||
v1 = Vector(2, 2, 2)
|
v1 = Vector(2, 2, 2)
|
||||||
v2 = Vector(1, -1, 1)
|
v2 = Vector(1, -1, 1)
|
||||||
self.assertEquals(2.0, v1.dot(v2))
|
self.assertEqual(2.0, v1.dot(v2))
|
||||||
|
|
||||||
def testVectorAdd(self):
|
def testVectorAdd(self):
|
||||||
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
result = Vector(1, 2, 0) + Vector(0, 0, 3)
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
|
||||||
|
|
||||||
def testTranslate(self):
|
def testTranslate(self):
|
||||||
e = Edge.makeCircle(2,(1,2,3))
|
e = Edge.makeCircle(2, (1, 2, 3))
|
||||||
e2 = e.translate(Vector(0, 0, 1))
|
e2 = e.translate(Vector(0, 0, 1))
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
|
||||||
@ -108,7 +117,8 @@ class TestCadObjects(BaseTest):
|
|||||||
def testVertices(self):
|
def testVertices(self):
|
||||||
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0),
|
e = Shape.cast(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 0),
|
||||||
gp_Pnt(1, 1, 0)).Edge())
|
gp_Pnt(1, 1, 0)).Edge())
|
||||||
self.assertEquals(2, len(e.Vertices()))
|
self.assertEqual(2, len(e.Vertices()))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,43 +1,49 @@
|
|||||||
"""
|
"""
|
||||||
Tests basic workplane functionality
|
Tests basic workplane functionality
|
||||||
"""
|
"""
|
||||||
#core modules
|
# core modules
|
||||||
import StringIO
|
import sys
|
||||||
|
if sys.version_info.major == 2:
|
||||||
|
import cStringIO as StringIO
|
||||||
|
else:
|
||||||
|
import io as StringIO
|
||||||
|
|
||||||
#my modules
|
# my modules
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
from cadquery import exporters
|
from cadquery import exporters
|
||||||
from tests import BaseTest
|
from tests import BaseTest
|
||||||
|
|
||||||
|
|
||||||
class TestExporters(BaseTest):
|
class TestExporters(BaseTest):
|
||||||
|
|
||||||
def _exportBox(self,eType,stringsToFind):
|
def _exportBox(self, eType, stringsToFind):
|
||||||
"""
|
"""
|
||||||
Exports a test object, and then looks for
|
Exports a test object, and then looks for
|
||||||
all of the supplied strings to be in the result
|
all of the supplied strings to be in the result
|
||||||
returns the result in case the case wants to do more checks also
|
returns the result in case the case wants to do more checks also
|
||||||
"""
|
"""
|
||||||
p = Workplane("XY").box(1,2,3)
|
p = Workplane("XY").box(1, 2, 3)
|
||||||
s = StringIO.StringIO()
|
s = StringIO.StringIO()
|
||||||
exporters.exportShape(p,eType,s,0.1)
|
exporters.exportShape(p, eType, s, 0.1)
|
||||||
|
|
||||||
result = s.getvalue()
|
result = s.getvalue()
|
||||||
|
|
||||||
for q in stringsToFind:
|
for q in stringsToFind:
|
||||||
self.assertTrue(result.find(q) > -1 )
|
self.assertTrue(result.find(q) > -1)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def testSTL(self):
|
def testSTL(self):
|
||||||
self._exportBox(exporters.ExportTypes.STL,['facet normal'])
|
self._exportBox(exporters.ExportTypes.STL, ['facet normal'])
|
||||||
|
|
||||||
def testSVG(self):
|
def testSVG(self):
|
||||||
self._exportBox(exporters.ExportTypes.SVG,['<svg','<g transform'])
|
self._exportBox(exporters.ExportTypes.SVG, ['<svg', '<g transform'])
|
||||||
|
|
||||||
def testAMF(self):
|
def testAMF(self):
|
||||||
self._exportBox(exporters.ExportTypes.AMF,['<amf units','</object>'])
|
self._exportBox(exporters.ExportTypes.AMF, ['<amf units', '</object>'])
|
||||||
|
|
||||||
def testSTEP(self):
|
def testSTEP(self):
|
||||||
self._exportBox(exporters.ExportTypes.STEP,['FILE_SCHEMA'])
|
self._exportBox(exporters.ExportTypes.STEP, ['FILE_SCHEMA'])
|
||||||
|
|
||||||
def testTJS(self):
|
def testTJS(self):
|
||||||
self._exportBox(exporters.ExportTypes.TJS,['vertices','formatVersion','faces'])
|
self._exportBox(exporters.ExportTypes.TJS, [
|
||||||
|
'vertices', 'formatVersion', 'faces'])
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Tests file importers such as STEP
|
Tests file importers such as STEP
|
||||||
"""
|
"""
|
||||||
#core modules
|
# core modules
|
||||||
import StringIO
|
import io
|
||||||
|
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
from cadquery import exporters
|
from cadquery import exporters
|
||||||
from cadquery import importers
|
from cadquery import importers
|
||||||
from tests import BaseTest
|
from tests import BaseTest
|
||||||
|
|
||||||
#where unit test output will be saved
|
# where unit test output will be saved
|
||||||
import sys
|
import sys
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
OUTDIR = "c:/temp"
|
OUTDIR = "c:/temp"
|
||||||
@ -24,24 +24,27 @@ class TestImporters(BaseTest):
|
|||||||
:param importType: The type of file we're importing (STEP, STL, etc)
|
:param importType: The type of file we're importing (STEP, STL, etc)
|
||||||
:param fileName: The path and name of the file to write to
|
:param fileName: The path and name of the file to write to
|
||||||
"""
|
"""
|
||||||
#We're importing a STEP file
|
# We're importing a STEP file
|
||||||
if importType == importers.ImportTypes.STEP:
|
if importType == importers.ImportTypes.STEP:
|
||||||
#We first need to build a simple shape to export
|
# We first need to build a simple shape to export
|
||||||
shape = Workplane("XY").box(1, 2, 3).val()
|
shape = Workplane("XY").box(1, 2, 3).val()
|
||||||
|
|
||||||
#Export the shape to a temporary file
|
# Export the shape to a temporary file
|
||||||
shape.exportStep(fileName)
|
shape.exportStep(fileName)
|
||||||
|
|
||||||
# Reimport the shape from the new STEP file
|
# Reimport the shape from the new STEP file
|
||||||
importedShape = importers.importShape(importType,fileName)
|
importedShape = importers.importShape(importType, fileName)
|
||||||
|
|
||||||
#Check to make sure we got a solid back
|
# Check to make sure we got a solid back
|
||||||
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
self.assertTrue(importedShape.val().ShapeType() == "Solid")
|
||||||
|
|
||||||
#Check the number of faces and vertices per face to make sure we have a box shape
|
# Check the number of faces and vertices per face to make sure we have a box shape
|
||||||
self.assertTrue(importedShape.faces("+X").size() == 1 and importedShape.faces("+X").vertices().size() == 4)
|
self.assertTrue(importedShape.faces("+X").size() ==
|
||||||
self.assertTrue(importedShape.faces("+Y").size() == 1 and importedShape.faces("+Y").vertices().size() == 4)
|
1 and importedShape.faces("+X").vertices().size() == 4)
|
||||||
self.assertTrue(importedShape.faces("+Z").size() == 1 and importedShape.faces("+Z").vertices().size() == 4)
|
self.assertTrue(importedShape.faces("+Y").size() ==
|
||||||
|
1 and importedShape.faces("+Y").vertices().size() == 4)
|
||||||
|
self.assertTrue(importedShape.faces("+Z").size() ==
|
||||||
|
1 and importedShape.faces("+Z").vertices().size() == 4)
|
||||||
|
|
||||||
def testSTEP(self):
|
def testSTEP(self):
|
||||||
"""
|
"""
|
||||||
@ -49,6 +52,7 @@ class TestImporters(BaseTest):
|
|||||||
"""
|
"""
|
||||||
self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step")
|
self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import unittest
|
import unittest
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Tests basic workplane functionality
|
Tests basic workplane functionality
|
||||||
"""
|
"""
|
||||||
#core modules
|
# core modules
|
||||||
|
|
||||||
#my modules
|
# my modules
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
from tests import BaseTest,toTuple
|
from tests import BaseTest, toTuple
|
||||||
|
|
||||||
xAxis_ = Vector(1, 0, 0)
|
xAxis_ = Vector(1, 0, 0)
|
||||||
yAxis_ = Vector(0, 1, 0)
|
yAxis_ = Vector(0, 1, 0)
|
||||||
@ -14,79 +14,97 @@ xInvAxis_ = Vector(-1, 0, 0)
|
|||||||
yInvAxis_ = Vector(0, -1, 0)
|
yInvAxis_ = Vector(0, -1, 0)
|
||||||
zInvAxis_ = Vector(0, 0, -1)
|
zInvAxis_ = Vector(0, 0, -1)
|
||||||
|
|
||||||
|
|
||||||
class TestWorkplanes(BaseTest):
|
class TestWorkplanes(BaseTest):
|
||||||
|
|
||||||
def testYZPlaneOrigins(self):
|
def testYZPlaneOrigins(self):
|
||||||
#xy plane-- with origin at x=0.25
|
# xy plane-- with origin at x=0.25
|
||||||
base = Vector(0.25,0,0)
|
base = Vector(0.25, 0, 0)
|
||||||
p = Plane(base, Vector(0,1,0), Vector(1,0,0))
|
p = Plane(base, Vector(0, 1, 0), Vector(1, 0, 0))
|
||||||
|
|
||||||
#origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
|
|
||||||
#(0,0,0) is always the original base in global coordinates
|
#(0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(base.toTuple(), p.toWorldCoords((0,0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
base.toTuple(), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||||
|
|
||||||
def testXYPlaneOrigins(self):
|
def testXYPlaneOrigins(self):
|
||||||
base = Vector(0,0,0.25)
|
base = Vector(0, 0, 0.25)
|
||||||
p = Plane(base, Vector(1,0,0), Vector(0,0,1))
|
p = Plane(base, Vector(1, 0, 0), Vector(0, 0, 1))
|
||||||
|
|
||||||
#origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
|
|
||||||
#(0,0,0) is always the original base in global coordinates
|
#(0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||||
|
|
||||||
def testXZPlaneOrigins(self):
|
def testXZPlaneOrigins(self):
|
||||||
base = Vector(0,0.25,0)
|
base = Vector(0, 0.25, 0)
|
||||||
p = Plane(base, Vector(0,0,1), Vector(0,1,0))
|
p = Plane(base, Vector(0, 0, 1), Vector(0, 1, 0))
|
||||||
|
|
||||||
#(0,0,0) is always the original base in global coordinates
|
#(0,0,0) is always the original base in global coordinates
|
||||||
self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
toTuple(base), p.toWorldCoords((0, 0)).toTuple(), 2)
|
||||||
|
|
||||||
#origin is always (0,0,0) in local coordinates
|
# origin is always (0,0,0) in local coordinates
|
||||||
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, 0, 0), p.toLocalCoords(p.origin).toTuple(), 2)
|
||||||
|
|
||||||
def testPlaneBasics(self):
|
def testPlaneBasics(self):
|
||||||
p = Plane.XY()
|
p = Plane.XY()
|
||||||
#local to world
|
# local to world
|
||||||
self.assertTupleAlmostEquals((1.0,1.0,0),p.toWorldCoords((1,1)).toTuple(),2 )
|
self.assertTupleAlmostEquals(
|
||||||
self.assertTupleAlmostEquals((-1.0,-1.0,0), p.toWorldCoords((-1,-1)).toTuple(),2 )
|
(1.0, 1.0, 0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(-1.0, -1.0, 0), p.toWorldCoords((-1, -1)).toTuple(), 2)
|
||||||
|
|
||||||
#world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals((-1.0,-1.0), p.toLocalCoords(Vector(-1,-1,0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,1,0)).toTuple() ,2 )
|
(-1.0, -1.0), p.toLocalCoords(Vector(-1, -1, 0)).toTuple(), 2)
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(1.0, 1.0), p.toLocalCoords(Vector(1, 1, 0)).toTuple(), 2)
|
||||||
|
|
||||||
p = Plane.YZ()
|
p = Plane.YZ()
|
||||||
self.assertTupleAlmostEquals((0,1.0,1.0),p.toWorldCoords((1,1)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(0, 1.0, 1.0), p.toWorldCoords((1, 1)).toTuple(), 2)
|
||||||
|
|
||||||
#world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(0,1,1)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(1.0, 1.0), p.toLocalCoords(Vector(0, 1, 1)).toTuple(), 2)
|
||||||
|
|
||||||
p = Plane.XZ()
|
p = Plane.XZ()
|
||||||
r = p.toWorldCoords((1,1)).toTuple()
|
r = p.toWorldCoords((1, 1)).toTuple()
|
||||||
self.assertTupleAlmostEquals((1.0,0.0,1.0),r ,2 )
|
self.assertTupleAlmostEquals((1.0, 0.0, 1.0), r, 2)
|
||||||
|
|
||||||
#world to local
|
# world to local
|
||||||
self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,0,1)).toTuple() ,2 )
|
self.assertTupleAlmostEquals(
|
||||||
|
(1.0, 1.0), p.toLocalCoords(Vector(1, 0, 1)).toTuple(), 2)
|
||||||
|
|
||||||
def testOffsetPlanes(self):
|
def testOffsetPlanes(self):
|
||||||
"Tests that a plane offset from the origin works ok too"
|
"Tests that a plane offset from the origin works ok too"
|
||||||
p = Plane.XY(origin=(10.0,10.0,0))
|
p = Plane.XY(origin=(10.0, 10.0, 0))
|
||||||
|
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(11.0, 11.0, 0.0), p.toWorldCoords((1.0, 1.0)).toTuple(), 2)
|
||||||
|
self.assertTupleAlmostEquals((2.0, 2.0), p.toLocalCoords(
|
||||||
|
Vector(12.0, 12.0, 0)).toTuple(), 2)
|
||||||
|
|
||||||
self.assertTupleAlmostEquals((11.0,11.0,0.0),p.toWorldCoords((1.0,1.0)).toTuple(),2 )
|
# TODO test these offsets in the other dimensions too
|
||||||
self.assertTupleAlmostEquals((2.0,2.0), p.toLocalCoords(Vector(12.0,12.0,0)).toTuple() ,2 )
|
p = Plane.YZ(origin=(0, 2, 2))
|
||||||
|
self.assertTupleAlmostEquals(
|
||||||
|
(0.0, 5.0, 5.0), p.toWorldCoords((3.0, 3.0)).toTuple(), 2)
|
||||||
|
self.assertTupleAlmostEquals((10, 10.0, 0.0), p.toLocalCoords(
|
||||||
|
Vector(0.0, 12.0, 12.0)).toTuple(), 2)
|
||||||
|
|
||||||
#TODO test these offsets in the other dimensions too
|
p = Plane.XZ(origin=(2, 0, 2))
|
||||||
p = Plane.YZ(origin=(0,2,2))
|
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
||||||
self.assertTupleAlmostEquals((0.0,5.0,5.0), p.toWorldCoords((3.0,3.0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
||||||
self.assertTupleAlmostEquals((10,10.0,0.0), p.toLocalCoords(Vector(0.0,12.0,12.0)).toTuple() ,2 )
|
self.assertTupleAlmostEquals((10.0, 10.0), p.toLocalCoords(
|
||||||
|
Vector(12.0, 0.0, 12.0)).toTuple(), 2)
|
||||||
p = Plane.XZ(origin=(2,0,2))
|
|
||||||
r = p.toWorldCoords((1.0,1.0)).toTuple()
|
|
||||||
self.assertTupleAlmostEquals((3.0,0.0,3.0),r ,2 )
|
|
||||||
self.assertTupleAlmostEquals((10.0,10.0), p.toLocalCoords(Vector(12.0,0.0,12.0)).toTuple() ,2 )
|
|
||||||
|
|
||||||
def testXYPlaneBasics(self):
|
def testXYPlaneBasics(self):
|
||||||
p = Plane.named('XY')
|
p = Plane.named('XY')
|
||||||
|
@ -4,8 +4,9 @@ import unittest
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def readFileAsString(fileName):
|
def readFileAsString(fileName):
|
||||||
f= open(fileName, 'r')
|
f = open(fileName, 'r')
|
||||||
s = f.read()
|
s = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return s
|
return s
|
||||||
@ -37,13 +38,16 @@ def toTuple(v):
|
|||||||
elif type(v) == Vector:
|
elif type(v) == Vector:
|
||||||
return v.toTuple()
|
return v.toTuple()
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)) )
|
raise RuntimeError(
|
||||||
|
"dont know how to convert type %s to tuple" % str(type(v)))
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(unittest.TestCase):
|
class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
def assertTupleAlmostEquals(self, expected, actual, places):
|
def assertTupleAlmostEquals(self, expected, actual, places):
|
||||||
for i, j in zip(actual, expected):
|
for i, j in zip(actual, expected):
|
||||||
self.assertAlmostEquals(i, j, places)
|
self.assertAlmostEqual(i, j, places)
|
||||||
|
|
||||||
__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes', 'TestExporters', 'TestCQSelectors', 'TestImporters','TestCQGI']
|
|
||||||
|
__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes',
|
||||||
|
'TestExporters', 'TestCQSelectors', 'TestImporters', 'TestCQGI']
|
||||||
|
Reference in New Issue
Block a user