First attempt at python2 and python3 support in single codebase
4 tests failing on python3 (CQGI, AMF export)
This commit is contained in:
108
cadquery/cq.py
108
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
|
||||||
@ -335,7 +337,8 @@ class CQ(object):
|
|||||||
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:]):
|
||||||
@ -368,7 +371,8 @@ class CQ(object):
|
|||||||
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:
|
||||||
@ -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
|
||||||
@ -751,10 +755,10 @@ class CQ(object):
|
|||||||
:param basePointVector: the base point to mirror about
|
:param basePointVector: the base point to mirror about
|
||||||
:type basePointVector: tuple
|
:type basePointVector: tuple
|
||||||
"""
|
"""
|
||||||
newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
|
newS = self.newObject(
|
||||||
|
[self.objects[0].mirror(mirrorPlane, basePointVector)])
|
||||||
return newS.first()
|
return newS.first()
|
||||||
|
|
||||||
|
|
||||||
def translate(self, vec):
|
def translate(self, vec):
|
||||||
"""
|
"""
|
||||||
Returns a copy of all of the items on the stack moved by the specified translation vector.
|
Returns a copy of all of the items on the stack moved by the specified translation vector.
|
||||||
@ -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
|
||||||
@ -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)
|
||||||
@ -1340,7 +1344,8 @@ class Workplane(CQ):
|
|||||||
# 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)
|
||||||
@ -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)
|
||||||
@ -1788,7 +1792,8 @@ class Workplane(CQ):
|
|||||||
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])
|
||||||
@ -1835,10 +1840,12 @@ class Workplane(CQ):
|
|||||||
"""
|
"""
|
||||||
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
|
||||||
|
|
||||||
@ -1886,7 +1893,8 @@ class Workplane(CQ):
|
|||||||
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)
|
||||||
@ -1934,7 +1942,8 @@ class Workplane(CQ):
|
|||||||
"""
|
"""
|
||||||
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)
|
||||||
@ -1961,9 +1970,11 @@ 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
|
||||||
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)
|
||||||
@ -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):
|
||||||
@ -2016,13 +2028,15 @@ class Workplane(CQ):
|
|||||||
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):
|
||||||
@ -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])
|
||||||
|
|
||||||
@ -2150,7 +2168,8 @@ class Workplane(CQ):
|
|||||||
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)
|
||||||
@ -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])
|
||||||
|
|
||||||
@ -2201,7 +2221,8 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
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
|
||||||
@ -2236,7 +2257,8 @@ class Workplane(CQ):
|
|||||||
|
|
||||||
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])
|
||||||
@ -2298,14 +2320,15 @@ 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 = []
|
||||||
|
|
||||||
# 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.
|
||||||
@ -2334,7 +2357,8 @@ class Workplane(CQ):
|
|||||||
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)
|
||||||
@ -2354,7 +2378,8 @@ 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 = []
|
||||||
@ -2362,7 +2387,8 @@ class Workplane(CQ):
|
|||||||
# 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)
|
||||||
@ -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.
|
||||||
@ -113,13 +114,14 @@ class CQModel(object):
|
|||||||
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,6 +177,7 @@ 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 = {}
|
||||||
|
|
||||||
@ -180,7 +185,7 @@ class ScriptMetadata(object):
|
|||||||
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.
|
||||||
@ -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 = []
|
||||||
@ -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,10 +408,12 @@ 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
|
||||||
|
|
||||||
@ -418,10 +431,11 @@ class ParameterDescriptionFinder(ast.NodeTransformer):
|
|||||||
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):
|
||||||
"""
|
"""
|
||||||
Visits a parse tree, and adds script parameters to the cqModel
|
Visits a parse tree, and adds script parameters to the cqModel
|
||||||
@ -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):
|
||||||
@ -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
|
||||||
|
@ -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,7 +28,7 @@ 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()
|
||||||
|
|
||||||
@ -42,7 +44,6 @@ 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()
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ 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
|
||||||
@ -153,36 +155,39 @@ class AmfWriter(object):
|
|||||||
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),
|
||||||
@ -251,7 +256,8 @@ def getSVG(shape,opts=None):
|
|||||||
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
|
||||||
@ -263,7 +269,8 @@ def getSVG(shape,opts=None):
|
|||||||
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 = ""
|
||||||
@ -307,7 +314,6 @@ def exportSVG(shape, fileName):
|
|||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JSON_TEMPLATE = """\
|
JSON_TEMPLATE = """\
|
||||||
{
|
{
|
||||||
"metadata" :
|
"metadata" :
|
||||||
@ -389,4 +395,3 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
@ -9,9 +9,11 @@ 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"
|
||||||
@ -50,6 +52,8 @@ def importStep(fileName):
|
|||||||
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):
|
def importStepFromURL(url):
|
||||||
# Now read and return the shape
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
@ -68,4 +72,5 @@ def importStepFromURL(url):
|
|||||||
|
|
||||||
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")
|
||||||
|
@ -214,7 +214,8 @@ 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)
|
||||||
@ -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,7 +241,8 @@ 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:]:
|
||||||
@ -430,7 +433,7 @@ class Edge(Shape):
|
|||||||
|
|
||||||
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)
|
||||||
@ -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
|
||||||
@ -608,7 +614,7 @@ class Face(Shape):
|
|||||||
|
|
||||||
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,6 +667,7 @@ 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
|
||||||
@ -679,6 +686,7 @@ class Solid(Shape):
|
|||||||
"""
|
"""
|
||||||
a single solid
|
a single solid
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
A Solid
|
A Solid
|
||||||
|
@ -2,8 +2,13 @@ from OCC.Visualization import Tesselator
|
|||||||
|
|
||||||
import cadquery
|
import cadquery
|
||||||
|
|
||||||
import tempfile, os
|
import tempfile
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.version_info.major == 2:
|
||||||
import cStringIO as StringIO
|
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
|
||||||
@ -25,6 +30,7 @@ except ImportError:
|
|||||||
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"
|
||||||
STEP = "STEP"
|
STEP = "STEP"
|
||||||
@ -55,14 +61,12 @@ 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()
|
||||||
|
|
||||||
@ -107,6 +111,7 @@ 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
|
||||||
@ -179,36 +184,39 @@ class AmfWriter(object):
|
|||||||
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),
|
||||||
@ -235,10 +243,10 @@ def makeSVGedge(e):
|
|||||||
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:
|
||||||
@ -246,6 +254,7 @@ def makeSVGedge(e):
|
|||||||
|
|
||||||
return cs.getvalue()
|
return cs.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def getPaths(visibleShapes, hiddenShapes):
|
def getPaths(visibleShapes, hiddenShapes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -265,7 +274,6 @@ def getPaths(visibleShapes, hiddenShapes):
|
|||||||
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
|
||||||
@ -322,12 +330,14 @@ def getSVG(shape,opts=None):
|
|||||||
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)
|
||||||
|
|
||||||
@ -338,7 +348,8 @@ def getSVG(shape,opts=None):
|
|||||||
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 = ""
|
||||||
@ -382,7 +393,6 @@ def exportSVG(shape, fileName):
|
|||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JSON_TEMPLATE = """\
|
JSON_TEMPLATE = """\
|
||||||
{
|
{
|
||||||
"metadata" :
|
"metadata" :
|
||||||
@ -464,4 +474,3 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||||
|
|
||||||
|
@ -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)
|
||||||
@ -104,16 +105,20 @@ 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)
|
||||||
@ -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()
|
||||||
@ -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)):
|
||||||
@ -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)
|
||||||
@ -604,10 +611,10 @@ class Plane(object):
|
|||||||
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()
|
||||||
@ -692,7 +699,8 @@ class BoundBox(object):
|
|||||||
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)
|
||||||
|
|
||||||
|
@ -9,9 +9,11 @@ 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"
|
||||||
@ -55,6 +57,8 @@ def importStep(fileName):
|
|||||||
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):
|
def importStepFromURL(url):
|
||||||
# Now read and return the shape
|
# Now read and return the shape
|
||||||
try:
|
try:
|
||||||
@ -66,4 +70,5 @@ def importStepFromURL(url):
|
|||||||
|
|
||||||
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")
|
||||||
|
@ -6,7 +6,8 @@ import OCC.GeomAbs as ga #Geometry type enum
|
|||||||
from OCC.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ,
|
from OCC.gp import (gp_Vec, gp_Pnt, gp_Ax1, gp_Ax2, gp_Ax3, gp_Dir, gp_Circ,
|
||||||
gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d)
|
gp_Trsf, gp_Pln, gp_GTrsf, gp_Pnt2d, gp_Dir2d)
|
||||||
|
|
||||||
from OCC.TColgp import TColgp_Array1OfPnt #collection of pints (used for spline construction)
|
# collection of pints (used for spline construction)
|
||||||
|
from OCC.TColgp import TColgp_Array1OfPnt
|
||||||
from OCC.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
|
from OCC.BRepAdaptor import BRepAdaptor_Curve, BRepAdaptor_Surface
|
||||||
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
||||||
BRepBuilderAPI_MakeEdge,
|
BRepBuilderAPI_MakeEdge,
|
||||||
@ -16,7 +17,8 @@ from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeVertex,
|
|||||||
BRepBuilderAPI_Copy,
|
BRepBuilderAPI_Copy,
|
||||||
BRepBuilderAPI_GTransform,
|
BRepBuilderAPI_GTransform,
|
||||||
BRepBuilderAPI_Transform)
|
BRepBuilderAPI_Transform)
|
||||||
from OCC.GProp import GProp_GProps #properties used to store mass calculation result
|
# properties used to store mass calculation result
|
||||||
|
from OCC.GProp import GProp_GProps
|
||||||
from OCC.BRepGProp import BRepGProp_Face, \
|
from OCC.BRepGProp import BRepGProp_Face, \
|
||||||
brepgprop_LinearProperties, \
|
brepgprop_LinearProperties, \
|
||||||
brepgprop_SurfaceProperties, \
|
brepgprop_SurfaceProperties, \
|
||||||
@ -35,7 +37,8 @@ from OCC.BRepPrimAPI import (BRepPrimAPI_MakeBox, #TODO list functions/used for
|
|||||||
from OCC.TopExp import TopExp_Explorer # Toplogy explorer
|
from OCC.TopExp import TopExp_Explorer # Toplogy explorer
|
||||||
from OCC.BRepTools import (BRepTools_WireExplorer, # might be needed for iterating thorugh wires
|
from OCC.BRepTools import (BRepTools_WireExplorer, # might be needed for iterating thorugh wires
|
||||||
breptools_UVBounds)
|
breptools_UVBounds)
|
||||||
from OCC.BRep import BRep_Tool #used for getting underlying geoetry -- is this equvalent to brep adaptor?
|
# used for getting underlying geoetry -- is this equvalent to brep adaptor?
|
||||||
|
from OCC.BRep import BRep_Tool
|
||||||
|
|
||||||
from OCC.TopoDS import (topods_Vertex, # downcasting functions
|
from OCC.TopoDS import (topods_Vertex, # downcasting functions
|
||||||
topods_Edge,
|
topods_Edge,
|
||||||
@ -121,7 +124,7 @@ shape_properties_LUT = \
|
|||||||
ta.TopAbs_SOLID: brepgprop_VolumeProperties,
|
ta.TopAbs_SOLID: brepgprop_VolumeProperties,
|
||||||
ta.TopAbs_COMPOUND: brepgprop_VolumeProperties}
|
ta.TopAbs_COMPOUND: brepgprop_VolumeProperties}
|
||||||
|
|
||||||
inverse_shape_LUT = {v:k for k,v in shape_LUT.iteritems()}
|
inverse_shape_LUT = {v: k for k, v in shape_LUT.items()}
|
||||||
|
|
||||||
downcast_LUT = \
|
downcast_LUT = \
|
||||||
{ta.TopAbs_VERTEX: topods_Vertex,
|
{ta.TopAbs_VERTEX: topods_Vertex,
|
||||||
@ -160,6 +163,7 @@ def downcast(topods_obj):
|
|||||||
|
|
||||||
return downcast_LUT[topods_obj.ShapeType()](topods_obj)
|
return downcast_LUT[topods_obj.ShapeType()](topods_obj)
|
||||||
|
|
||||||
|
|
||||||
class Shape(object):
|
class Shape(object):
|
||||||
"""
|
"""
|
||||||
Represents a shape in the system.
|
Represents a shape in the system.
|
||||||
@ -173,11 +177,11 @@ class Shape(object):
|
|||||||
# 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 clean(self):
|
def clean(self):
|
||||||
"""Experimental clean using ShapeUpgrade"""
|
"""Experimental clean using ShapeUpgrade"""
|
||||||
|
|
||||||
upgrader = ShapeUpgrade_UnifySameDomain(self.wrapped,True,True,False)
|
upgrader = ShapeUpgrade_UnifySameDomain(
|
||||||
|
self.wrapped, True, True, False)
|
||||||
upgrader.Build()
|
upgrader.Build()
|
||||||
|
|
||||||
return self.cast(upgrader.Shape())
|
return self.cast(upgrader.Shape())
|
||||||
@ -201,7 +205,8 @@ class Shape(object):
|
|||||||
ta.TopAbs_COMPOUND: Compound}
|
ta.TopAbs_COMPOUND: Compound}
|
||||||
|
|
||||||
t = obj.ShapeType()
|
t = obj.ShapeType()
|
||||||
tr = constructor_LUT[t](downcast(obj)) #NB downcast is nedded to handly TopoDS_Shape types
|
# NB downcast is nedded to handly TopoDS_Shape types
|
||||||
|
tr = constructor_LUT[t](downcast(obj))
|
||||||
tr.forConstruction = forConstruction
|
tr.forConstruction = forConstruction
|
||||||
# TODO move this to Compound constructor?
|
# TODO move this to Compound constructor?
|
||||||
'''
|
'''
|
||||||
@ -225,7 +230,6 @@ class Shape(object):
|
|||||||
#
|
#
|
||||||
def exportStl(self, fileName, precision=1e-5):
|
def exportStl(self, fileName, precision=1e-5):
|
||||||
|
|
||||||
|
|
||||||
mesh = BRepMesh_IncrementalMesh(self.wrapped, precision, True)
|
mesh = BRepMesh_IncrementalMesh(self.wrapped, precision, True)
|
||||||
mesh.Perform()
|
mesh.Perform()
|
||||||
|
|
||||||
@ -282,7 +286,6 @@ class Shape(object):
|
|||||||
else:
|
else:
|
||||||
return geom_LUT_EDGE_FACE[tr(self.wrapped).GetType()]
|
return geom_LUT_EDGE_FACE[tr(self.wrapped).GetType()]
|
||||||
|
|
||||||
|
|
||||||
def isType(self, obj, strType): # TODO why here?
|
def isType(self, obj, strType): # TODO why here?
|
||||||
"""
|
"""
|
||||||
Returns True if the shape is the specified type, false otherwise
|
Returns True if the shape is the specified type, false otherwise
|
||||||
@ -358,7 +361,8 @@ 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 = [Shape.centerOfMass(o).multiply(Shape.computeMass(o)) for o in objects]
|
weighted_centers = [Shape.centerOfMass(o).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:]:
|
||||||
@ -420,7 +424,6 @@ class Shape(object):
|
|||||||
def ShapeType(self):
|
def ShapeType(self):
|
||||||
return shape_LUT[self.wrapped.ShapeType()]
|
return shape_LUT[self.wrapped.ShapeType()]
|
||||||
|
|
||||||
|
|
||||||
def _entities(self, topo_type):
|
def _entities(self, topo_type):
|
||||||
|
|
||||||
out = {} # using dict to prevent duplicates
|
out = {} # using dict to prevent duplicates
|
||||||
@ -432,7 +435,7 @@ class Shape(object):
|
|||||||
out[item.__hash__()] = item # some implementations use __hash__
|
out[item.__hash__()] = item # some implementations use __hash__
|
||||||
explorer.Next()
|
explorer.Next()
|
||||||
|
|
||||||
return out.values()
|
return list(out.values())
|
||||||
|
|
||||||
def Vertices(self):
|
def Vertices(self):
|
||||||
|
|
||||||
@ -578,6 +581,7 @@ class Shape(object):
|
|||||||
|
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
|
|
||||||
class Vertex(Shape):
|
class Vertex(Shape):
|
||||||
"""
|
"""
|
||||||
A Single Point in Space
|
A Single Point in Space
|
||||||
@ -621,6 +625,7 @@ class Mixin1D(object):
|
|||||||
|
|
||||||
return Properties.Mass()
|
return Properties.Mass()
|
||||||
|
|
||||||
|
|
||||||
class Edge(Shape, Mixin1D):
|
class Edge(Shape, Mixin1D):
|
||||||
"""
|
"""
|
||||||
A trimmed curve that represents the border of a face
|
A trimmed curve that represents the border of a face
|
||||||
@ -632,7 +637,6 @@ class Edge(Shape, Mixin1D):
|
|||||||
"""
|
"""
|
||||||
return BRepAdaptor_Curve(self.wrapped)
|
return BRepAdaptor_Curve(self.wrapped)
|
||||||
|
|
||||||
|
|
||||||
def startPoint(self):
|
def startPoint(self):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -675,7 +679,8 @@ class Edge(Shape, Mixin1D):
|
|||||||
umin, umax = curve.FirstParameter(), curve.LastParameter()
|
umin, umax = curve.FirstParameter(), curve.LastParameter()
|
||||||
umid = 0.5 * (umin + umax)
|
umid = 0.5 * (umin + umax)
|
||||||
|
|
||||||
curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance()) #TODO what are good parameters for those?
|
# TODO what are good parameters for those?
|
||||||
|
curve_props = BRepLProp_CLProps(curve, 2, curve.Tolerance())
|
||||||
curve_props.SetParameter(umid)
|
curve_props.SetParameter(umid)
|
||||||
|
|
||||||
if curve_props.IsTangentDefined():
|
if curve_props.IsTangentDefined():
|
||||||
@ -722,7 +727,8 @@ class Edge(Shape, Mixin1D):
|
|||||||
:return: an Edge
|
:return: an Edge
|
||||||
"""
|
"""
|
||||||
pnts = TColgp_Array1OfPnt(0, len(listOfVector) - 1)
|
pnts = TColgp_Array1OfPnt(0, len(listOfVector) - 1)
|
||||||
for ix,v in enumerate(listOfVector): pnts.SetValue(ix,v.toPnt())
|
for ix, v in enumerate(listOfVector):
|
||||||
|
pnts.SetValue(ix, v.toPnt())
|
||||||
|
|
||||||
spline_geom = GeomAPI_PointsToBSpline(pnts).Curve()
|
spline_geom = GeomAPI_PointsToBSpline(pnts).Curve()
|
||||||
|
|
||||||
@ -810,7 +816,8 @@ class Wire(Shape, Mixin1D):
|
|||||||
# convert list of tuples into Vectors.
|
# convert list of tuples into Vectors.
|
||||||
wire_builder = BRepBuilderAPI_MakePolygon()
|
wire_builder = BRepBuilderAPI_MakePolygon()
|
||||||
|
|
||||||
for v in listOfVertices: wire_builder.Add(v.toPnt())
|
for v in listOfVertices:
|
||||||
|
wire_builder.Add(v.toPnt())
|
||||||
|
|
||||||
w = cls(wire_builder.Wire())
|
w = cls(wire_builder.Wire())
|
||||||
w.forConstruction = forConstruction
|
w.forConstruction = forConstruction
|
||||||
@ -861,6 +868,7 @@ class Wire(Shape, Mixin1D):
|
|||||||
|
|
||||||
return self.__class__(wire_builder.Wire())
|
return self.__class__(wire_builder.Wire())
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@ -898,7 +906,6 @@ class Face(Shape):
|
|||||||
|
|
||||||
u, v = projector.LowerDistanceParameters()
|
u, v = projector.LowerDistanceParameters()
|
||||||
|
|
||||||
|
|
||||||
p = gp_Pnt()
|
p = gp_Pnt()
|
||||||
vn = gp_Vec()
|
vn = gp_Vec()
|
||||||
BRepGProp_Face(self.wrapped).Normal(u, v, p, vn)
|
BRepGProp_Face(self.wrapped).Normal(u, v, p, vn)
|
||||||
@ -959,6 +966,7 @@ class Face(Shape):
|
|||||||
|
|
||||||
return cls(sf.Face())
|
return cls(sf.Face())
|
||||||
|
|
||||||
|
|
||||||
class Shell(Shape):
|
class Shell(Shape):
|
||||||
"""
|
"""
|
||||||
the outer boundary of a surface
|
the outer boundary of a surface
|
||||||
@ -1057,6 +1065,7 @@ class Mixin3D(object):
|
|||||||
|
|
||||||
return self.__class__(shell_builder.Shape())
|
return self.__class__(shell_builder.Shape())
|
||||||
|
|
||||||
|
|
||||||
class Solid(Shape, Mixin3D):
|
class Solid(Shape, Mixin3D):
|
||||||
"""
|
"""
|
||||||
a single solid
|
a single solid
|
||||||
@ -1238,12 +1247,12 @@ class Solid(Shape,Mixin3D):
|
|||||||
comp_builder = TopoDS_Builder()
|
comp_builder = TopoDS_Builder()
|
||||||
comp_builder.MakeCompound(inner_comp) # TODO this could be not needed
|
comp_builder.MakeCompound(inner_comp) # TODO this could be not needed
|
||||||
|
|
||||||
for i in inner_solids: comp_builder.Add(inner_comp,i)
|
for i in inner_solids:
|
||||||
|
comp_builder.Add(inner_comp, i)
|
||||||
|
|
||||||
# subtract from the outer solid
|
# subtract from the outer solid
|
||||||
return cls(BRepAlgoAPI_Cut(outer_solid, inner_comp).Shape())
|
return cls(BRepAlgoAPI_Cut(outer_solid, inner_comp).Shape())
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extrudeLinear(cls, outerWire, innerWires, vecNormal):
|
def extrudeLinear(cls, outerWire, innerWires, vecNormal):
|
||||||
"""
|
"""
|
||||||
@ -1276,7 +1285,8 @@ class Solid(Shape,Mixin3D):
|
|||||||
# FreeCAD allows this in one operation, but others might not
|
# FreeCAD allows this in one operation, but others might not
|
||||||
|
|
||||||
face = Face.makeFromWires(outerWire, innerWires)
|
face = Face.makeFromWires(outerWire, innerWires)
|
||||||
prism_builder = BRepPrimAPI_MakePrism(face.wrapped, vecNormal.wrapped, True)
|
prism_builder = BRepPrimAPI_MakePrism(
|
||||||
|
face.wrapped, vecNormal.wrapped, True)
|
||||||
|
|
||||||
return cls(prism_builder.Shape())
|
return cls(prism_builder.Shape())
|
||||||
|
|
||||||
@ -1347,6 +1357,7 @@ class Solid(Shape,Mixin3D):
|
|||||||
|
|
||||||
return cls(builder.Shape())
|
return cls(builder.Shape())
|
||||||
|
|
||||||
|
|
||||||
class Compound(Shape, Mixin3D):
|
class Compound(Shape, Mixin3D):
|
||||||
"""
|
"""
|
||||||
a collection of disconnected solids
|
a collection of disconnected solids
|
||||||
@ -1361,11 +1372,14 @@ class Compound(Shape,Mixin3D):
|
|||||||
comp_builder = TopoDS_Builder()
|
comp_builder = TopoDS_Builder()
|
||||||
comp_builder.MakeCompound(comp) # TODO this could be not needed
|
comp_builder.MakeCompound(comp) # TODO this could be not needed
|
||||||
|
|
||||||
for s in listOfShapes: comp_builder.Add(comp,s.wrapped)
|
for s in listOfShapes:
|
||||||
|
comp_builder.Add(comp, s.wrapped)
|
||||||
|
|
||||||
return cls(comp)
|
return cls(comp)
|
||||||
|
|
||||||
# TODO this is likely not needed if sing PythonOCC correclty but we will see
|
# TODO this is likely not needed if sing PythonOCC correclty but we will see
|
||||||
|
|
||||||
|
|
||||||
def sortWiresByBuildOrder(wireList, plane, result=[]):
|
def sortWiresByBuildOrder(wireList, plane, result=[]):
|
||||||
"""Tries to determine how wires should be combined into faces.
|
"""Tries to determine how wires should be combined into faces.
|
||||||
|
|
||||||
@ -1397,11 +1411,12 @@ def sortWiresByBuildOrder(wireList, plane, result=[]):
|
|||||||
|
|
||||||
# Iterate through the Inner:Outer Mapping
|
# Iterate through the Inner:Outer Mapping
|
||||||
all_wires = face.Wires()
|
all_wires = face.Wires()
|
||||||
result = {w:outer_inner_map.Find(w.wrapped) for w in all_wires if outer_inner_map.IsBound(w.wrapped)}
|
result = {w: outer_inner_map.Find(
|
||||||
|
w.wrapped) for w in all_wires if outer_inner_map.IsBound(w.wrapped)}
|
||||||
|
|
||||||
# construct the result
|
# construct the result
|
||||||
rv = []
|
rv = []
|
||||||
for k,v in result.iteritems():
|
for k, v in result.items():
|
||||||
tmp = [k, ]
|
tmp = [k, ]
|
||||||
|
|
||||||
iterator = TopTools_ListIteratorOfListOfShape(v)
|
iterator = TopTools_ListIteratorOfListOfShape(v)
|
||||||
|
@ -24,6 +24,7 @@ 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,6 +33,7 @@ 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
|
||||||
@ -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,8 +76,10 @@ 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):
|
||||||
@ -86,6 +91,7 @@ class NearestToPointSelector(Selector):
|
|||||||
|
|
||||||
return [min(objectList, key=dist)]
|
return [min(objectList, key=dist)]
|
||||||
|
|
||||||
|
|
||||||
class BoxSelector(Selector):
|
class BoxSelector(Selector):
|
||||||
"""
|
"""
|
||||||
Selects objects inside the 3D box defined by 2 points.
|
Selects objects inside the 3D box defined by 2 points.
|
||||||
@ -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,11 +137,13 @@ 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
|
||||||
@ -167,6 +176,7 @@ class BaseDirSelector(Selector):
|
|||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
class ParallelDirSelector(BaseDirSelector):
|
class ParallelDirSelector(BaseDirSelector):
|
||||||
"""
|
"""
|
||||||
Selects objects parallel with the provided direction
|
Selects objects parallel with the provided direction
|
||||||
@ -190,6 +200,7 @@ class ParallelDirSelector(BaseDirSelector):
|
|||||||
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
|
||||||
@ -213,6 +224,7 @@ class DirectionSelector(BaseDirSelector):
|
|||||||
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
|
||||||
@ -235,7 +247,8 @@ class PerpendicularDirSelector(BaseDirSelector):
|
|||||||
|
|
||||||
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,6 +272,7 @@ 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()
|
||||||
|
|
||||||
@ -269,6 +283,7 @@ class TypeSelector(Selector):
|
|||||||
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,11 +306,13 @@ 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):
|
||||||
@ -306,17 +323,18 @@ class DirectionMinMaxSelector(Selector):
|
|||||||
# 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,6 +345,7 @@ 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
|
||||||
@ -351,17 +370,19 @@ class DirectionNthSelector(ParallelDirSelector):
|
|||||||
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
|
||||||
|
|
||||||
@ -426,7 +455,7 @@ def _makeGrammar():
|
|||||||
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
|
||||||
@ -463,13 +492,16 @@ def _makeGrammar():
|
|||||||
(other_op('other_op') + direction('dir')) | \
|
(other_op('other_op') + direction('dir')) | \
|
||||||
named_view('named_view')
|
named_view('named_view')
|
||||||
|
|
||||||
|
|
||||||
_grammar = _makeGrammar() # make a grammar instance
|
_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
|
||||||
@ -552,6 +584,7 @@ class _SimpleStringSyntaxSelector(Selector):
|
|||||||
"""
|
"""
|
||||||
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
|
||||||
@ -567,19 +600,23 @@ def _makeExpressionGrammar(atom):
|
|||||||
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
|
||||||
|
items = res.asList()[0][::2]
|
||||||
return reduce(AndSelector, items)
|
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
|
||||||
|
items = res.asList()[0][::2]
|
||||||
return reduce(SumSelector, items)
|
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
|
||||||
|
items = res.asList()[0][::2]
|
||||||
return reduce(SubtractSelector, items)
|
return reduce(SubtractSelector, items)
|
||||||
|
|
||||||
def not_callback(res):
|
def not_callback(res):
|
||||||
@ -595,8 +632,10 @@ def _makeExpressionGrammar(atom):
|
|||||||
|
|
||||||
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
|
||||||
@ -647,6 +686,7 @@ class StringSyntaxSelector(Selector):
|
|||||||
|
|
||||||
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
|
||||||
|
@ -36,12 +36,14 @@ 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)
|
||||||
@ -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(
|
||||||
@ -139,7 +141,7 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
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(
|
||||||
@ -149,7 +151,8 @@ class TestCQGI(BaseTest):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
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(
|
||||||
@ -160,7 +163,8 @@ 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,7 +9,8 @@ __author__ = 'dcowden'
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import unittest,sys
|
import unittest
|
||||||
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
# my modules
|
# my modules
|
||||||
@ -17,23 +18,24 @@ 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
|
||||||
@ -41,18 +43,23 @@ class TestCQSelectors(BaseTest):
|
|||||||
|
|
||||||
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
|
||||||
|
# but selecting all edges still yields all vertices
|
||||||
|
self.assertEqual(4, c.edges().vertices().size())
|
||||||
self.assertEqual(1, c.wires().size()) # just one wire
|
self.assertEqual(1, c.wires().size()) # just one wire
|
||||||
self.assertEqual(0, c.faces().size())
|
self.assertEqual(0, c.faces().size())
|
||||||
self.assertEqual(0,c.vertices().faces().size()) #odd combinations all work but yield no results
|
# 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().faces().size())
|
||||||
self.assertEqual(0, c.edges().vertices().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"
|
||||||
@ -64,7 +71,8 @@ class TestCQSelectors(BaseTest):
|
|||||||
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())
|
||||||
@ -88,8 +96,6 @@ class TestCQSelectors(BaseTest):
|
|||||||
|
|
||||||
self.assertEqual(4, c.faces().last().edges().size())
|
self.assertEqual(4, c.faces().last().edges().size())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def testFaceTypesFilter(self):
|
def testFaceTypesFilter(self):
|
||||||
"Filters by face type"
|
"Filters by face type"
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
@ -123,8 +129,10 @@ class TestCQSelectors(BaseTest):
|
|||||||
# 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())
|
||||||
@ -175,19 +183,23 @@ class TestCQSelectors(BaseTest):
|
|||||||
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(
|
||||||
|
Vector(-1, 0, 0), 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(selectors.DirectionNthSelector(Vector(1,0,0),-2)).val()
|
val = c.faces(selectors.DirectionNthSelector(
|
||||||
|
Vector(1, 0, 0), -2)).val()
|
||||||
self.assertAlmostEqual(val.Center().x, 1.5)
|
self.assertAlmostEqual(val.Center().x, 1.5)
|
||||||
|
|
||||||
# Last face
|
# Last 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, 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)
|
||||||
|
|
||||||
# repeat the test using string based selector
|
# repeat the test using string based selector
|
||||||
|
|
||||||
@ -212,7 +224,8 @@ class TestCQSelectors(BaseTest):
|
|||||||
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')\
|
||||||
@ -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,7 +398,8 @@ 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
|
||||||
@ -453,7 +476,6 @@ class TestCQSelectors(BaseTest):
|
|||||||
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())
|
||||||
@ -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)
|
||||||
|
@ -11,6 +11,7 @@ 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):
|
||||||
@ -33,17 +34,18 @@ 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()
|
||||||
@ -51,16 +53,20 @@ class TestCadObjects(BaseTest):
|
|||||||
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,15 +92,17 @@ 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)
|
||||||
@ -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()
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# system modules
|
# system modules
|
||||||
import math,sys,os.path,time
|
import math
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
|
||||||
# my modules
|
# my modules
|
||||||
from cadquery import *
|
from cadquery import *
|
||||||
@ -61,7 +64,8 @@ class TestCadQuery(BaseTest):
|
|||||||
if os.path.exists(svgFile):
|
if os.path.exists(svgFile):
|
||||||
existingSummary = readFileAsString(SUMMARY_FILE)
|
existingSummary = readFileAsString(SUMMARY_FILE)
|
||||||
svgText = readFileAsString(svgFile)
|
svgText = readFileAsString(svgFile)
|
||||||
svgText = svgText.replace('<?xml version="1.0" encoding="UTF-8" standalone="no"?>',"")
|
svgText = svgText.replace(
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>', "")
|
||||||
|
|
||||||
# now write data into the file
|
# now write data into the file
|
||||||
# the content we are replacing it with also includes the marker, so it can be replaced again
|
# the content we are replacing it with also includes the marker, so it can be replaced again
|
||||||
@ -99,7 +103,8 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
# Make sure that a couple of sections from the SVG output make sense
|
# Make sure that a couple of sections from the SVG output make sense
|
||||||
self.assertTrue(r_str.index('path d="M') > 0)
|
self.assertTrue(r_str.index('path d="M') > 0)
|
||||||
self.assertTrue(r_str.index('line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3"') > 0)
|
self.assertTrue(r_str.index(
|
||||||
|
'line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3"') > 0)
|
||||||
|
|
||||||
def testCubePlugin(self):
|
def testCubePlugin(self):
|
||||||
"""
|
"""
|
||||||
@ -107,6 +112,7 @@ class TestCadQuery(BaseTest):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# make the plugin method
|
# make the plugin method
|
||||||
|
|
||||||
def makeCubes(self, length):
|
def makeCubes(self, length):
|
||||||
# self refers to the CQ or Workplane object
|
# self refers to the CQ or Workplane object
|
||||||
|
|
||||||
@ -124,12 +130,12 @@ class TestCadQuery(BaseTest):
|
|||||||
Workplane.makeCubes = makeCubes
|
Workplane.makeCubes = makeCubes
|
||||||
|
|
||||||
# call it
|
# call it
|
||||||
result = Workplane("XY").box(6.0,8.0,0.5).faces(">Z").rect(4.0,4.0,forConstruction=True).vertices()
|
result = Workplane("XY").box(6.0, 8.0, 0.5).faces(
|
||||||
|
">Z").rect(4.0, 4.0, forConstruction=True).vertices()
|
||||||
result = result.makeCubes(1.0)
|
result = result.makeCubes(1.0)
|
||||||
result = result.combineSolids()
|
result = result.combineSolids()
|
||||||
self.saveModel(result)
|
self.saveModel(result)
|
||||||
self.assertEquals(1,result.solids().size() )
|
self.assertEqual(1, result.solids().size())
|
||||||
|
|
||||||
|
|
||||||
def testCylinderPlugin(self):
|
def testCylinderPlugin(self):
|
||||||
"""
|
"""
|
||||||
@ -154,7 +160,7 @@ class TestCadQuery(BaseTest):
|
|||||||
# 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(Plane(Vector((0, 0, 0)), Vector((1, -1, 0)), Vector((1, 1, 0)))).rect(2.0, 3.0, forConstruction=True).vertices() \
|
s = Workplane(Plane(Vector((0, 0, 0)), Vector((1, -1, 0)), Vector((1, 1, 0)))).rect(2.0, 3.0, forConstruction=True).vertices() \
|
||||||
.cyl(0.25, 0.5)
|
.cyl(0.25, 0.5)
|
||||||
self.assertEquals(4,s.solids().size() )
|
self.assertEqual(4, s.solids().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testPolygonPlugin(self):
|
def testPolygonPlugin(self):
|
||||||
@ -164,6 +170,7 @@ class TestCadQuery(BaseTest):
|
|||||||
Demonstratings using eachpoint to allow working in local coordinates
|
Demonstratings using eachpoint to allow working in local coordinates
|
||||||
to create geometry
|
to create geometry
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def rPoly(self, nSides, diameter):
|
def rPoly(self, nSides, diameter):
|
||||||
|
|
||||||
def _makePolygon(center):
|
def _makePolygon(center):
|
||||||
@ -171,7 +178,8 @@ class TestCadQuery(BaseTest):
|
|||||||
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)),(diameter / 2.0 * math.sin(angle*i)),0))
|
pnts.append(center + Vector((diameter / 2.0 * math.cos(angle * i)),
|
||||||
|
(diameter / 2.0 * math.sin(angle * i)), 0))
|
||||||
return Wire.makePolygon(pnts)
|
return Wire.makePolygon(pnts)
|
||||||
|
|
||||||
return self.eachpoint(_makePolygon, True)
|
return self.eachpoint(_makePolygon, True)
|
||||||
@ -181,17 +189,18 @@ class TestCadQuery(BaseTest):
|
|||||||
s = Workplane("XY").box(4.0, 4.0, 0.25).faces(">Z").workplane().rect(2.0, 2.0, forConstruction=True).vertices()\
|
s = Workplane("XY").box(4.0, 4.0, 0.25).faces(">Z").workplane().rect(2.0, 2.0, forConstruction=True).vertices()\
|
||||||
.rPoly(5, 0.5).cutThruAll()
|
.rPoly(5, 0.5).cutThruAll()
|
||||||
|
|
||||||
self.assertEquals(26,s.faces().size()) #6 base sides, 4 pentagons, 5 sides each = 26
|
# 6 base sides, 4 pentagons, 5 sides each = 26
|
||||||
|
self.assertEqual(26, s.faces().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
|
|
||||||
def testPointList(self):
|
def testPointList(self):
|
||||||
"""
|
"""
|
||||||
Tests adding points and using them
|
Tests adding points and using them
|
||||||
"""
|
"""
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
|
|
||||||
s = c.faces(">Z").workplane().pushPoints([(-0.3, 0.3), (0.3, 0.3), (0, 0)])
|
s = c.faces(">Z").workplane().pushPoints(
|
||||||
|
[(-0.3, 0.3), (0.3, 0.3), (0, 0)])
|
||||||
self.assertEqual(3, s.size())
|
self.assertEqual(3, s.size())
|
||||||
# TODO: is the ability to iterate over points with circle really worth it?
|
# TODO: is the ability to iterate over points with circle really worth it?
|
||||||
# maybe we should just require using all() and a loop for this. the semantics and
|
# maybe we should just require using all() and a loop for this. the semantics and
|
||||||
@ -208,9 +217,9 @@ class TestCadQuery(BaseTest):
|
|||||||
r.objects = []
|
r.objects = []
|
||||||
r.eachpoint(callback_fn)
|
r.eachpoint(callback_fn)
|
||||||
|
|
||||||
|
|
||||||
def testWorkplaneFromFace(self):
|
def testWorkplaneFromFace(self):
|
||||||
s = CQ(makeUnitCube()).faces(">Z").workplane() #make a workplane on the top face
|
# make a workplane on the top face
|
||||||
|
s = CQ(makeUnitCube()).faces(">Z").workplane()
|
||||||
r = s.circle(0.125).cutBlind(-2.0)
|
r = s.circle(0.125).cutBlind(-2.0)
|
||||||
self.saveModel(r)
|
self.saveModel(r)
|
||||||
# the result should have 7 faces
|
# the result should have 7 faces
|
||||||
@ -219,7 +228,8 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(type(r.first().val()), Compound)
|
self.assertEqual(type(r.first().val()), Compound)
|
||||||
|
|
||||||
def testFrontReference(self):
|
def testFrontReference(self):
|
||||||
s = CQ(makeUnitCube()).faces("front").workplane() #make a workplane on the top face
|
# make a workplane on the top face
|
||||||
|
s = CQ(makeUnitCube()).faces("front").workplane()
|
||||||
r = s.circle(0.125).cutBlind(-2.0)
|
r = s.circle(0.125).cutBlind(-2.0)
|
||||||
self.saveModel(r)
|
self.saveModel(r)
|
||||||
# the result should have 7 faces
|
# the result should have 7 faces
|
||||||
@ -231,8 +241,10 @@ class TestCadQuery(BaseTest):
|
|||||||
"""Test solid rotation at the CQ object level."""
|
"""Test solid rotation at the CQ object level."""
|
||||||
box = Workplane("XY").box(1, 1, 5)
|
box = Workplane("XY").box(1, 1, 5)
|
||||||
box.rotate((0, 0, 0), (1, 0, 0), 90)
|
box.rotate((0, 0, 0), (1, 0, 0), 90)
|
||||||
startPoint = box.faces("<Y").edges("<X").first().val().startPoint().toTuple()
|
startPoint = box.faces("<Y").edges(
|
||||||
endPoint = box.faces("<Y").edges("<X").first().val().endPoint().toTuple()
|
"<X").first().val().startPoint().toTuple()
|
||||||
|
endPoint = box.faces("<Y").edges(
|
||||||
|
"<X").first().val().endPoint().toTuple()
|
||||||
|
|
||||||
self.assertEqual(-0.5, startPoint[0])
|
self.assertEqual(-0.5, startPoint[0])
|
||||||
self.assertEqual(-0.5, startPoint[1])
|
self.assertEqual(-0.5, startPoint[1])
|
||||||
@ -241,7 +253,6 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(-0.5, endPoint[1])
|
self.assertEqual(-0.5, endPoint[1])
|
||||||
self.assertEqual(2.5, endPoint[2])
|
self.assertEqual(2.5, endPoint[2])
|
||||||
|
|
||||||
|
|
||||||
def testLoft(self):
|
def testLoft(self):
|
||||||
"""
|
"""
|
||||||
Test making a lofted solid
|
Test making a lofted solid
|
||||||
@ -278,47 +289,56 @@ class TestCadQuery(BaseTest):
|
|||||||
angle_degrees = 360.0
|
angle_degrees = 360.0
|
||||||
|
|
||||||
# Test revolve without any options for making a cylinder
|
# Test revolve without any options for making a cylinder
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length, False).revolve()
|
||||||
self.assertEqual(3, result.faces().size())
|
self.assertEqual(3, result.faces().size())
|
||||||
self.assertEqual(2, result.vertices().size())
|
self.assertEqual(2, result.vertices().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
|
|
||||||
# Test revolve when only setting the angle to revolve through
|
# Test revolve when only setting the angle to revolve through
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length, False).revolve(angle_degrees)
|
||||||
self.assertEqual(3, result.faces().size())
|
self.assertEqual(3, result.faces().size())
|
||||||
self.assertEqual(2, result.vertices().size())
|
self.assertEqual(2, result.vertices().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(270.0)
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length, False).revolve(270.0)
|
||||||
self.assertEqual(5, result.faces().size())
|
self.assertEqual(5, result.faces().size())
|
||||||
self.assertEqual(6, result.vertices().size())
|
self.assertEqual(6, result.vertices().size())
|
||||||
self.assertEqual(9, result.edges().size())
|
self.assertEqual(9, result.edges().size())
|
||||||
|
|
||||||
# Test when passing revolve the angle and the axis of revolution's start point
|
# Test when passing revolve the angle and the axis of revolution's start point
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length).revolve(angle_degrees, (-5, -5))
|
||||||
self.assertEqual(3, result.faces().size())
|
self.assertEqual(3, result.faces().size())
|
||||||
self.assertEqual(2, result.vertices().size())
|
self.assertEqual(2, result.vertices().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5))
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length).revolve(270.0, (-5, -5))
|
||||||
self.assertEqual(5, result.faces().size())
|
self.assertEqual(5, result.faces().size())
|
||||||
self.assertEqual(6, result.vertices().size())
|
self.assertEqual(6, result.vertices().size())
|
||||||
self.assertEqual(9, result.edges().size())
|
self.assertEqual(9, result.edges().size())
|
||||||
|
|
||||||
# Test when passing revolve the angle and both the start and ends of the axis of revolution
|
# Test when passing revolve the angle and both the start and ends of the axis of revolution
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
|
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(
|
||||||
|
angle_degrees, (-5, -5), (-5, 5))
|
||||||
self.assertEqual(3, result.faces().size())
|
self.assertEqual(3, result.faces().size())
|
||||||
self.assertEqual(2, result.vertices().size())
|
self.assertEqual(2, result.vertices().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5, -5),(-5, 5))
|
result = Workplane("XY").rect(
|
||||||
|
rectangle_width, rectangle_length).revolve(270.0, (-5, -5), (-5, 5))
|
||||||
self.assertEqual(5, result.faces().size())
|
self.assertEqual(5, result.faces().size())
|
||||||
self.assertEqual(6, result.vertices().size())
|
self.assertEqual(6, result.vertices().size())
|
||||||
self.assertEqual(9, result.edges().size())
|
self.assertEqual(9, result.edges().size())
|
||||||
|
|
||||||
# Testing all of the above without combine
|
# Testing all of the above without combine
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
|
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(
|
||||||
|
angle_degrees, (-5, -5), (-5, 5), False)
|
||||||
self.assertEqual(3, result.faces().size())
|
self.assertEqual(3, result.faces().size())
|
||||||
self.assertEqual(2, result.vertices().size())
|
self.assertEqual(2, result.vertices().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(270.0,(-5,-5),(-5,5), False)
|
result = Workplane("XY").rect(rectangle_width, rectangle_length).revolve(
|
||||||
|
270.0, (-5, -5), (-5, 5), False)
|
||||||
self.assertEqual(5, result.faces().size())
|
self.assertEqual(5, result.faces().size())
|
||||||
self.assertEqual(6, result.vertices().size())
|
self.assertEqual(6, result.vertices().size())
|
||||||
self.assertEqual(9, result.edges().size())
|
self.assertEqual(9, result.edges().size())
|
||||||
@ -379,7 +399,8 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
|
|
||||||
# Test with makeSolid False and isFrenet True
|
# Test with makeSolid False and isFrenet True
|
||||||
result = Workplane("XY").circle(1.0).sweep(path, makeSolid=False, isFrenet=True)
|
result = Workplane("XY").circle(1.0).sweep(
|
||||||
|
path, makeSolid=False, isFrenet=True)
|
||||||
self.assertEqual(1, result.faces().size())
|
self.assertEqual(1, result.faces().size())
|
||||||
self.assertEqual(3, result.edges().size())
|
self.assertEqual(3, result.edges().size())
|
||||||
|
|
||||||
@ -425,13 +446,16 @@ class TestCadQuery(BaseTest):
|
|||||||
def testRectArray(self):
|
def testRectArray(self):
|
||||||
NUMX = 3
|
NUMX = 3
|
||||||
NUMY = 3
|
NUMY = 3
|
||||||
s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().rarray(8.0,8.0,NUMX,NUMY,True).circle(2.0).extrude(2.0)
|
s = Workplane("XY").box(40, 40, 5, centered=(True, True, True)).faces(
|
||||||
|
">Z").workplane().rarray(8.0, 8.0, NUMX, NUMY, True).circle(2.0).extrude(2.0)
|
||||||
#s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().circle(2.0).extrude(2.0)
|
#s = Workplane("XY").box(40,40,5,centered=(True,True,True)).faces(">Z").workplane().circle(2.0).extrude(2.0)
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
self.assertEqual(6+NUMX*NUMY*2,s.faces().size()) #6 faces for the box, 2 faces for each cylinder
|
# 6 faces for the box, 2 faces for each cylinder
|
||||||
|
self.assertEqual(6 + NUMX * NUMY * 2, s.faces().size())
|
||||||
|
|
||||||
def testNestedCircle(self):
|
def testNestedCircle(self):
|
||||||
s = Workplane("XY").box(40,40,5).pushPoints([(10,0),(0,10)]).circle(4).circle(2).extrude(4)
|
s = Workplane("XY").box(40, 40, 5).pushPoints(
|
||||||
|
[(10, 0), (0, 10)]).circle(4).circle(2).extrude(4)
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
self.assertEqual(14, s.faces().size())
|
self.assertEqual(14, s.faces().size())
|
||||||
|
|
||||||
@ -448,7 +472,8 @@ class TestCadQuery(BaseTest):
|
|||||||
c = 0.1 # clearance on each brick side
|
c = 0.1 # clearance on each brick side
|
||||||
H = 1.2 * P # nominal height of a brick
|
H = 1.2 * P # nominal height of a brick
|
||||||
bumpDiam = 4.8 # the standard bump diameter
|
bumpDiam = 4.8 # the standard bump diameter
|
||||||
t = ( P - ( 2*c) - bumpDiam ) / 2.0 # the nominal thickness of the walls, normally 1.5
|
# the nominal thickness of the walls, normally 1.5
|
||||||
|
t = (P - (2 * c) - bumpDiam) / 2.0
|
||||||
|
|
||||||
postDiam = P - t # works out to 6.5
|
postDiam = P - t # works out to 6.5
|
||||||
total_length = lbumps * P - 2.0 * c
|
total_length = lbumps * P - 2.0 * c
|
||||||
@ -457,18 +482,23 @@ class TestCadQuery(BaseTest):
|
|||||||
# build the brick
|
# build the brick
|
||||||
s = Workplane("XY").box(total_length, total_width, H) # make the base
|
s = Workplane("XY").box(total_length, total_width, H) # make the base
|
||||||
s = s.faces("<Z").shell(-1.0 * t) # shell inwards not outwards
|
s = s.faces("<Z").shell(-1.0 * t) # shell inwards not outwards
|
||||||
s = s.faces(">Z").workplane().rarray(P,P,lbumps,wbumps,True).circle(bumpDiam/2.0).extrude(1.8) # make the bumps on the top
|
s = s.faces(">Z").workplane().rarray(P, P, lbumps, wbumps, True).circle(
|
||||||
|
bumpDiam / 2.0).extrude(1.8) # make the bumps on the top
|
||||||
|
|
||||||
# add posts on the bottom. posts are different diameter depending on geometry
|
# add posts on the bottom. posts are different diameter depending on geometry
|
||||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||||
tmp = s.faces("<Z").workplane(invert=True) #this is cheating a little-- how to select the inner face from the shell?
|
# this is cheating a little-- how to select the inner face from the shell?
|
||||||
|
tmp = s.faces("<Z").workplane(invert=True)
|
||||||
|
|
||||||
if lbumps > 1 and wbumps > 1:
|
if lbumps > 1 and wbumps > 1:
|
||||||
tmp = tmp.rarray(P,P,lbumps - 1,wbumps - 1,center=True).circle(postDiam/2.0).circle(bumpDiam/2.0).extrude(H-t)
|
tmp = tmp.rarray(P, P, lbumps - 1, wbumps - 1, center=True).circle(
|
||||||
|
postDiam / 2.0).circle(bumpDiam / 2.0).extrude(H - t)
|
||||||
elif lbumps > 1:
|
elif lbumps > 1:
|
||||||
tmp = tmp.rarray(P,P,lbumps - 1,1,center=True).circle(t).extrude(H-t)
|
tmp = tmp.rarray(P, P, lbumps - 1, 1,
|
||||||
|
center=True).circle(t).extrude(H - t)
|
||||||
elif wbumps > 1:
|
elif wbumps > 1:
|
||||||
tmp = tmp.rarray(P,P,1,wbumps -1,center=True).circle(t).extrude(H-t)
|
tmp = tmp.rarray(P, P, 1, wbumps - 1,
|
||||||
|
center=True).circle(t).extrude(H - t)
|
||||||
|
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
@ -480,11 +510,13 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
def testTranslateSolid(self):
|
def testTranslateSolid(self):
|
||||||
c = CQ(makeUnitCube())
|
c = CQ(makeUnitCube())
|
||||||
self.assertAlmostEqual(0.0,c.faces("<Z").vertices().item(0).val().Z, 3 )
|
self.assertAlmostEqual(0.0, c.faces(
|
||||||
|
"<Z").vertices().item(0).val().Z, 3)
|
||||||
|
|
||||||
# TODO: it might be nice to provide a version of translate that modifies the existing geometry too
|
# TODO: it might be nice to provide a version of translate that modifies the existing geometry too
|
||||||
d = c.translate(Vector(0, 0, 1.5))
|
d = c.translate(Vector(0, 0, 1.5))
|
||||||
self.assertAlmostEqual(1.5,d.faces("<Z").vertices().item(0).val().Z, 3 )
|
self.assertAlmostEqual(1.5, d.faces(
|
||||||
|
"<Z").vertices().item(0).val().Z, 3)
|
||||||
|
|
||||||
def testTranslateWire(self):
|
def testTranslateWire(self):
|
||||||
c = CQ(makeUnitSquareWire())
|
c = CQ(makeUnitSquareWire())
|
||||||
@ -497,29 +529,37 @@ class TestCadQuery(BaseTest):
|
|||||||
c = CQ(makeUnitCube()) # the cube is the context solid
|
c = CQ(makeUnitCube()) # the cube is the context solid
|
||||||
self.assertEqual(6, c.faces().size()) # cube has six faces
|
self.assertEqual(6, c.faces().size()) # cube has six faces
|
||||||
|
|
||||||
r = c.faces('>Z').workplane().circle(0.125).extrude(0.5,True) #make a boss, not updating the original
|
r = c.faces('>Z').workplane().circle(0.125).extrude(
|
||||||
|
0.5, True) # make a boss, not updating the original
|
||||||
self.assertEqual(8, r.faces().size()) # just the boss faces
|
self.assertEqual(8, r.faces().size()) # just the boss faces
|
||||||
self.assertEqual(8, c.faces().size()) # original is modified too
|
self.assertEqual(8, c.faces().size()) # original is modified too
|
||||||
|
|
||||||
def testSolidReferencesCombineTrue(self):
|
def testSolidReferencesCombineTrue(self):
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
r = s.rect(2.0, 2.0).extrude(0.5)
|
r = s.rect(2.0, 2.0).extrude(0.5)
|
||||||
self.assertEqual(6,r.faces().size() ) #the result of course has 6 faces
|
# the result of course has 6 faces
|
||||||
self.assertEqual(0,s.faces().size() ) # the original workplane does not, because it did not have a solid initially
|
self.assertEqual(6, r.faces().size())
|
||||||
|
# the original workplane does not, because it did not have a solid initially
|
||||||
|
self.assertEqual(0, s.faces().size())
|
||||||
|
|
||||||
t = r.faces(">Z").workplane().rect(0.25, 0.25).extrude(0.5, True)
|
t = r.faces(">Z").workplane().rect(0.25, 0.25).extrude(0.5, True)
|
||||||
self.assertEqual(11,t.faces().size()) #of course the result has 11 faces
|
# of course the result has 11 faces
|
||||||
self.assertEqual(11,r.faces().size()) #r does as well. the context solid for r was updated since combine was true
|
self.assertEqual(11, t.faces().size())
|
||||||
|
# r does as well. the context solid for r was updated since combine was true
|
||||||
|
self.assertEqual(11, r.faces().size())
|
||||||
self.saveModel(r)
|
self.saveModel(r)
|
||||||
|
|
||||||
def testSolidReferenceCombineFalse(self):
|
def testSolidReferenceCombineFalse(self):
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
r = s.rect(2.0, 2.0).extrude(0.5)
|
r = s.rect(2.0, 2.0).extrude(0.5)
|
||||||
self.assertEqual(6,r.faces().size() ) #the result of course has 6 faces
|
# the result of course has 6 faces
|
||||||
self.assertEqual(0,s.faces().size() ) # the original workplane does not, because it did not have a solid initially
|
self.assertEqual(6, r.faces().size())
|
||||||
|
# the original workplane does not, because it did not have a solid initially
|
||||||
|
self.assertEqual(0, s.faces().size())
|
||||||
|
|
||||||
t = r.faces(">Z").workplane().rect(0.25, 0.25).extrude(0.5, False)
|
t = r.faces(">Z").workplane().rect(0.25, 0.25).extrude(0.5, False)
|
||||||
self.assertEqual(6,t.faces().size()) #result has 6 faces, becuase it was not combined with the original
|
# result has 6 faces, becuase it was not combined with the original
|
||||||
|
self.assertEqual(6, t.faces().size())
|
||||||
self.assertEqual(6, r.faces().size()) # original is unmodified as well
|
self.assertEqual(6, r.faces().size()) # original is unmodified as well
|
||||||
# subseuent opertions use that context solid afterwards
|
# subseuent opertions use that context solid afterwards
|
||||||
|
|
||||||
@ -540,7 +580,8 @@ class TestCadQuery(BaseTest):
|
|||||||
Test Creation of workplane from multiple co-planar face
|
Test Creation of workplane from multiple co-planar face
|
||||||
selection.
|
selection.
|
||||||
"""
|
"""
|
||||||
s = Workplane('XY').box(1,1,1).faces('>Z').rect(1,0.5).cutBlind(-0.2)
|
s = Workplane('XY').box(1, 1, 1).faces(
|
||||||
|
'>Z').rect(1, 0.5).cutBlind(-0.2)
|
||||||
|
|
||||||
w = s.faces('>Z').workplane()
|
w = s.faces('>Z').workplane()
|
||||||
o = w.objects[0] # origin of the workplane
|
o = w.objects[0] # origin of the workplane
|
||||||
@ -569,9 +610,11 @@ class TestCadQuery(BaseTest):
|
|||||||
also tests using a workplane plane other than XY
|
also tests using a workplane plane other than XY
|
||||||
"""
|
"""
|
||||||
s = Workplane(Plane.YZ())
|
s = Workplane(Plane.YZ())
|
||||||
r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
r = s.rect(2.0, 2.0).rect(
|
||||||
|
1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
||||||
self.saveModel(r)
|
self.saveModel(r)
|
||||||
self.assertEqual(10,r.faces().size() ) # 10 faces-- 6 plus 4 holes, the vertices of the second rect.
|
# 10 faces-- 6 plus 4 holes, the vertices of the second rect.
|
||||||
|
self.assertEqual(10, r.faces().size())
|
||||||
|
|
||||||
def testTwoWorkplanes(self):
|
def testTwoWorkplanes(self):
|
||||||
"""
|
"""
|
||||||
@ -586,7 +629,8 @@ class TestCadQuery(BaseTest):
|
|||||||
# r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices()
|
# r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices()
|
||||||
# for c in r.all():
|
# for c in r.all():
|
||||||
# c.circle(0.125).extrude(0.5,True)
|
# c.circle(0.125).extrude(0.5,True)
|
||||||
r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
r = s.rect(2.0, 2.0).rect(
|
||||||
|
1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
||||||
|
|
||||||
# side hole, blind deep 1.9
|
# side hole, blind deep 1.9
|
||||||
t = r.faces(">Y").workplane().circle(0.125).cutBlind(-1.9)
|
t = r.faces(">Y").workplane().circle(0.125).cutBlind(-1.9)
|
||||||
@ -648,7 +692,8 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
# base block
|
# base block
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
r = s.rect(2.0, 2.0).rect(
|
||||||
|
1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
||||||
|
|
||||||
# side hole, thru all
|
# side hole, thru all
|
||||||
t = r.faces(">Y").workplane().circle(0.125).cutThruAll()
|
t = r.faces(">Y").workplane().circle(0.125).cutThruAll()
|
||||||
@ -661,12 +706,15 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
# base block
|
# base block
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
r = s.rect(2.0,2.0).rect(1.3,1.3,forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
r = s.rect(2.0, 2.0).rect(
|
||||||
|
1.3, 1.3, forConstruction=True).vertices().circle(0.125).extrude(0.5)
|
||||||
|
|
||||||
# side hole, up to 0.1 from the last face
|
# side hole, up to 0.1 from the last face
|
||||||
try:
|
try:
|
||||||
t = r.faces(">Y").workplane().circle(0.125).cutToOffsetFromFace(r.faces().mminDist(Dir.Y),0.1)
|
t = r.faces(">Y").workplane().circle(
|
||||||
self.assertEqual(10,t.faces().size() ) #should end up being a blind hole
|
0.125).cutToOffsetFromFace(r.faces().mminDist(Dir.Y), 0.1)
|
||||||
|
# should end up being a blind hole
|
||||||
|
self.assertEqual(10, t.faces().size())
|
||||||
t.first().val().exportStep('c:/temp/testCutToFace.STEP')
|
t.first().val().exportStep('c:/temp/testCutToFace.STEP')
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -674,20 +722,20 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
def testWorkplaneOnExistingSolid(self):
|
def testWorkplaneOnExistingSolid(self):
|
||||||
"Tests extruding on an existing solid"
|
"Tests extruding on an existing solid"
|
||||||
c = CQ( makeUnitCube()).faces(">Z").workplane().circle(0.25).circle(0.125).extrude(0.25)
|
c = CQ(makeUnitCube()).faces(">Z").workplane().circle(
|
||||||
|
0.25).circle(0.125).extrude(0.25)
|
||||||
self.saveModel(c)
|
self.saveModel(c)
|
||||||
self.assertEqual(10, c.faces().size())
|
self.assertEqual(10, c.faces().size())
|
||||||
|
|
||||||
|
|
||||||
def testWorkplaneCenterMove(self):
|
def testWorkplaneCenterMove(self):
|
||||||
# this workplane is centered at x=0.5,y=0.5, the center of the upper face
|
# this workplane is centered at x=0.5,y=0.5, the center of the upper face
|
||||||
s = Workplane("XY").box(1,1,1).faces(">Z").workplane().center(-0.5,-0.5) # move the center to the corner
|
s = Workplane("XY").box(1, 1, 1).faces(">Z").workplane(
|
||||||
|
).center(-0.5, -0.5) # move the center to the corner
|
||||||
|
|
||||||
t = s.circle(0.25).extrude(0.2) # make a boss
|
t = s.circle(0.25).extrude(0.2) # make a boss
|
||||||
self.assertEqual(9, t.faces().size())
|
self.assertEqual(9, t.faces().size())
|
||||||
self.saveModel(t)
|
self.saveModel(t)
|
||||||
|
|
||||||
|
|
||||||
def testBasicLines(self):
|
def testBasicLines(self):
|
||||||
"Make a triangluar boss"
|
"Make a triangluar boss"
|
||||||
global OUTDIR
|
global OUTDIR
|
||||||
@ -699,8 +747,10 @@ class TestCadQuery(BaseTest):
|
|||||||
r = s.lineTo(1.0, 0).lineTo(0, 1.0).close().wire().extrude(0.25)
|
r = s.lineTo(1.0, 0).lineTo(0, 1.0).close().wire().extrude(0.25)
|
||||||
r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesStep1.STEP'))
|
r.val().exportStep(os.path.join(OUTDIR, 'testBasicLinesStep1.STEP'))
|
||||||
|
|
||||||
self.assertEqual(0,s.faces().size()) #no faces on the original workplane
|
# no faces on the original workplane
|
||||||
self.assertEqual(5,r.faces().size() ) # 5 faces on newly created object
|
self.assertEqual(0, s.faces().size())
|
||||||
|
# 5 faces on newly created object
|
||||||
|
self.assertEqual(5, r.faces().size())
|
||||||
|
|
||||||
# now add a circle through a side face
|
# now add a circle through a side face
|
||||||
r.faces("+XY").workplane().circle(0.08).cutThruAll()
|
r.faces("+XY").workplane().circle(0.08).cutThruAll()
|
||||||
@ -754,9 +804,10 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(1, r.wire().size())
|
self.assertEqual(1, r.wire().size())
|
||||||
self.assertEqual(4, r.edges().size())
|
self.assertEqual(4, r.edges().size())
|
||||||
self.assertEqual((1.0, 1.0),
|
self.assertEqual((1.0, 1.0),
|
||||||
(r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\
|
(r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))
|
||||||
.first().val().X,
|
.first().val().X,
|
||||||
r.vertices(selectors.NearestToPointSelector((0.0, 0.0, 0.0)))\
|
r.vertices(
|
||||||
|
selectors.NearestToPointSelector((0.0, 0.0, 0.0)))
|
||||||
.first().val().Y))
|
.first().val().Y))
|
||||||
|
|
||||||
def testLargestDimension(self):
|
def testLargestDimension(self):
|
||||||
@ -788,13 +839,13 @@ class TestCadQuery(BaseTest):
|
|||||||
.extrude(30.0, True)
|
.extrude(30.0, True)
|
||||||
|
|
||||||
# make the neck
|
# make the neck
|
||||||
p.faces(">Z").workplane().circle(3.0).extrude(2.0,True) #.edges().fillet(0.05)
|
p.faces(">Z").workplane().circle(3.0).extrude(
|
||||||
|
2.0, True) # .edges().fillet(0.05)
|
||||||
|
|
||||||
# make a shell
|
# make a shell
|
||||||
p.faces(">Z").shell(0.3)
|
p.faces(">Z").shell(0.3)
|
||||||
self.saveModel(p)
|
self.saveModel(p)
|
||||||
|
|
||||||
|
|
||||||
def testSplineShape(self):
|
def testSplineShape(self):
|
||||||
"""
|
"""
|
||||||
Tests making a shape with an edge that is a spline
|
Tests making a shape with an edge that is a spline
|
||||||
@ -819,7 +870,7 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
s = Workplane("XY").lineTo(2, 2).threePointArc((3, 1), (2, 0)) \
|
s = Workplane("XY").lineTo(2, 2).threePointArc((3, 1), (2, 0)) \
|
||||||
.mirrorX().extrude(0.25)
|
.mirrorX().extrude(0.25)
|
||||||
self.assertEquals(6, s.faces().size())
|
self.assertEqual(6, s.faces().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testUnorderedMirror(self):
|
def testUnorderedMirror(self):
|
||||||
@ -844,8 +895,8 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
r = Workplane("XY").polyline(points).mirrorX()
|
r = Workplane("XY").polyline(points).mirrorX()
|
||||||
|
|
||||||
self.assertEquals(1, r.wires().size())
|
self.assertEqual(1, r.wires().size())
|
||||||
self.assertEquals(18, r.edges().size())
|
self.assertEqual(18, r.edges().size())
|
||||||
|
|
||||||
# def testChainedMirror(self):
|
# def testChainedMirror(self):
|
||||||
# """
|
# """
|
||||||
@ -916,7 +967,8 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
Tests filleting edges on a solid
|
Tests filleting edges on a solid
|
||||||
"""
|
"""
|
||||||
c = CQ( makeUnitCube()).faces(">Z").workplane().circle(0.25).extrude(0.25,True).edges("|Z").fillet(0.2)
|
c = CQ(makeUnitCube()).faces(">Z").workplane().circle(
|
||||||
|
0.25).extrude(0.25, True).edges("|Z").fillet(0.2)
|
||||||
self.saveModel(c)
|
self.saveModel(c)
|
||||||
self.assertEqual(12, c.faces().size())
|
self.assertEqual(12, c.faces().size())
|
||||||
|
|
||||||
@ -946,7 +998,8 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
Test chamfer API with a cylinder shape
|
Test chamfer API with a cylinder shape
|
||||||
"""
|
"""
|
||||||
cylinder = Workplane("XY").circle(1).extrude(1).faces(">Z").chamfer(0.1)
|
cylinder = Workplane("XY").circle(
|
||||||
|
1).extrude(1).faces(">Z").chamfer(0.1)
|
||||||
self.saveModel(cylinder)
|
self.saveModel(cylinder)
|
||||||
self.assertEqual(4, cylinder.faces().size())
|
self.assertEqual(4, cylinder.faces().size())
|
||||||
|
|
||||||
@ -958,8 +1011,9 @@ class TestCadQuery(BaseTest):
|
|||||||
pnts = [
|
pnts = [
|
||||||
(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)
|
(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)
|
||||||
]
|
]
|
||||||
c.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25, 0.75)
|
c.faces(">Z").workplane().pushPoints(
|
||||||
self.assertEquals(18, c.faces().size())
|
pnts).cboreHole(0.1, 0.25, 0.25, 0.75)
|
||||||
|
self.assertEqual(18, c.faces().size())
|
||||||
self.saveModel(c)
|
self.saveModel(c)
|
||||||
|
|
||||||
# Tests the case where the depth of the cboreHole is not specified
|
# Tests the case where the depth of the cboreHole is not specified
|
||||||
@ -968,7 +1022,7 @@ class TestCadQuery(BaseTest):
|
|||||||
(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)
|
(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)
|
||||||
]
|
]
|
||||||
c2.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25)
|
c2.faces(">Z").workplane().pushPoints(pnts).cboreHole(0.1, 0.25, 0.25)
|
||||||
self.assertEquals(15, c2.faces().size())
|
self.assertEqual(15, c2.faces().size())
|
||||||
|
|
||||||
def testCounterSinks(self):
|
def testCounterSinks(self):
|
||||||
"""
|
"""
|
||||||
@ -985,7 +1039,8 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# drill a hole in the side
|
# drill a hole in the side
|
||||||
c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll()
|
c = CQ(makeUnitCube()).faces(
|
||||||
|
">Z").workplane().circle(0.25).cutThruAll()
|
||||||
|
|
||||||
self.assertEqual(7, c.faces().size())
|
self.assertEqual(7, c.faces().size())
|
||||||
|
|
||||||
@ -1000,14 +1055,17 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# drill a hole in the side
|
# drill a hole in the side
|
||||||
c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll()
|
c = CQ(makeUnitCube()).faces(
|
||||||
|
">Z").workplane().circle(0.25).cutThruAll()
|
||||||
self.assertEqual(7, c.faces().size())
|
self.assertEqual(7, c.faces().size())
|
||||||
|
|
||||||
# now cut it in half sideways
|
# now cut it in half sideways
|
||||||
result = c.faces(">Y").workplane(-0.5).split(keepTop=True, keepBottom=True)
|
result = c.faces(
|
||||||
|
">Y").workplane(-0.5).split(keepTop=True, keepBottom=True)
|
||||||
|
|
||||||
# stack will have both halves, original will be unchanged
|
# stack will have both halves, original will be unchanged
|
||||||
self.assertEqual(2, result.solids().size()) # two solids are on the stack, eac
|
# two solids are on the stack, eac
|
||||||
|
self.assertEqual(2, result.solids().size())
|
||||||
self.assertEqual(8, result.solids().item(0).faces().size())
|
self.assertEqual(8, result.solids().item(0).faces().size())
|
||||||
self.assertEqual(8, result.solids().item(1).faces().size())
|
self.assertEqual(8, result.solids().item(1).faces().size())
|
||||||
|
|
||||||
@ -1016,14 +1074,17 @@ class TestCadQuery(BaseTest):
|
|||||||
Tests splitting a solid improperly
|
Tests splitting a solid improperly
|
||||||
"""
|
"""
|
||||||
# Drill a hole in the side
|
# Drill a hole in the side
|
||||||
c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll()
|
c = CQ(makeUnitCube()).faces(
|
||||||
|
">Z").workplane().circle(0.25).cutThruAll()
|
||||||
self.assertEqual(7, c.faces().size())
|
self.assertEqual(7, c.faces().size())
|
||||||
|
|
||||||
# Now cut it in half sideways
|
# Now cut it in half sideways
|
||||||
result = c.faces(">Y").workplane(-0.5).split(keepTop=False, keepBottom=True)
|
result = c.faces(
|
||||||
|
">Y").workplane(-0.5).split(keepTop=False, keepBottom=True)
|
||||||
|
|
||||||
# stack will have both halves, original will be unchanged
|
# stack will have both halves, original will be unchanged
|
||||||
self.assertEqual(1, result.solids().size()) # one solid is on the stack
|
# one solid is on the stack
|
||||||
|
self.assertEqual(1, result.solids().size())
|
||||||
self.assertEqual(8, result.solids().item(0).faces().size())
|
self.assertEqual(8, result.solids().item(0).faces().size())
|
||||||
|
|
||||||
def testBoxDefaults(self):
|
def testBoxDefaults(self):
|
||||||
@ -1031,7 +1092,7 @@ class TestCadQuery(BaseTest):
|
|||||||
Tests creating a single box
|
Tests creating a single box
|
||||||
"""
|
"""
|
||||||
s = Workplane("XY").box(2, 3, 4)
|
s = Workplane("XY").box(2, 3, 4)
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testSimpleShell(self):
|
def testSimpleShell(self):
|
||||||
@ -1040,8 +1101,7 @@ class TestCadQuery(BaseTest):
|
|||||||
"""
|
"""
|
||||||
s = Workplane("XY").box(2, 2, 2).faces("+Z").shell(0.05)
|
s = Workplane("XY").box(2, 2, 2).faces("+Z").shell(0.05)
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
self.assertEquals(23, s.faces().size())
|
self.assertEqual(23, s.faces().size())
|
||||||
|
|
||||||
|
|
||||||
def testOpenCornerShell(self):
|
def testOpenCornerShell(self):
|
||||||
s = Workplane("XY").box(1, 1, 1)
|
s = Workplane("XY").box(1, 1, 1)
|
||||||
@ -1059,88 +1119,98 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
def testTopFaceFillet(self):
|
def testTopFaceFillet(self):
|
||||||
s = Workplane("XY").box(1, 1, 1).faces("+Z").edges().fillet(0.1)
|
s = Workplane("XY").box(1, 1, 1).faces("+Z").edges().fillet(0.1)
|
||||||
self.assertEquals(s.faces().size(), 10)
|
self.assertEqual(s.faces().size(), 10)
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testBoxPointList(self):
|
def testBoxPointList(self):
|
||||||
"""
|
"""
|
||||||
Tests creating an array of boxes
|
Tests creating an array of boxes
|
||||||
"""
|
"""
|
||||||
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True)
|
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(
|
||||||
|
0.25, 0.25, 0.25, combine=True)
|
||||||
# 1 object, 4 solids because the object is a compound
|
# 1 object, 4 solids because the object is a compound
|
||||||
self.assertEquals(4, s.solids().size())
|
self.assertEqual(4, s.solids().size())
|
||||||
self.assertEquals(1, s.size())
|
self.assertEqual(1, s.size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=False)
|
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().box(
|
||||||
|
0.25, 0.25, 0.25, combine=False)
|
||||||
# 4 objects, 4 solids, because each is a separate solid
|
# 4 objects, 4 solids, because each is a separate solid
|
||||||
self.assertEquals(4, s.size())
|
self.assertEqual(4, s.size())
|
||||||
self.assertEquals(4, s.solids().size())
|
self.assertEqual(4, s.solids().size())
|
||||||
|
|
||||||
def testBoxCombine(self):
|
def testBoxCombine(self):
|
||||||
s = Workplane("XY").box(4, 4, 0.5).faces(">Z").workplane().rect(3, 3, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True)
|
s = Workplane("XY").box(4, 4, 0.5).faces(">Z").workplane().rect(
|
||||||
|
3, 3, forConstruction=True).vertices().box(0.25, 0.25, 0.25, combine=True)
|
||||||
|
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
self.assertEquals(1, s.solids().size()) # we should have one big solid
|
self.assertEqual(1, s.solids().size()) # we should have one big solid
|
||||||
self.assertEquals(26, s.faces().size()) # should have 26 faces. 6 for the box, and 4x5 for the smaller cubes
|
# should have 26 faces. 6 for the box, and 4x5 for the smaller cubes
|
||||||
|
self.assertEqual(26, s.faces().size())
|
||||||
|
|
||||||
def testSphereDefaults(self):
|
def testSphereDefaults(self):
|
||||||
s = Workplane("XY").sphere(10)
|
s = Workplane("XY").sphere(10)
|
||||||
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(1, s.faces().size())
|
self.assertEqual(1, s.faces().size())
|
||||||
|
|
||||||
def testSphereCustom(self):
|
def testSphereCustom(self):
|
||||||
s = Workplane("XY").sphere(10, angle1=0, angle2=90, angle3=360, centered=(False, False, False))
|
s = Workplane("XY").sphere(10, angle1=0, angle2=90,
|
||||||
|
angle3=360, centered=(False, False, False))
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(2, s.faces().size())
|
self.assertEqual(2, s.faces().size())
|
||||||
|
|
||||||
def testSpherePointList(self):
|
def testSpherePointList(self):
|
||||||
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().sphere(0.25, combine=False)
|
s = Workplane("XY").rect(
|
||||||
|
4.0, 4.0, forConstruction=True).vertices().sphere(0.25, combine=False)
|
||||||
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
||||||
self.assertEquals(4, s.solids().size())
|
self.assertEqual(4, s.solids().size())
|
||||||
self.assertEquals(4, s.faces().size())
|
self.assertEqual(4, s.faces().size())
|
||||||
|
|
||||||
def testSphereCombine(self):
|
def testSphereCombine(self):
|
||||||
s = Workplane("XY").rect(4.0, 4.0, forConstruction=True).vertices().sphere(2.25, combine=True)
|
s = Workplane("XY").rect(
|
||||||
|
4.0, 4.0, forConstruction=True).vertices().sphere(2.25, combine=True)
|
||||||
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
# self.saveModel(s) # Until FreeCAD fixes their sphere operation
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(4, s.faces().size())
|
self.assertEqual(4, s.faces().size())
|
||||||
|
|
||||||
def testQuickStartXY(self):
|
def testQuickStartXY(self):
|
||||||
s = Workplane(Plane.XY()).box(2, 4, 0.5).faces(">Z").workplane().rect(1.5, 3.5, forConstruction=True)\
|
s = Workplane(Plane.XY()).box(2, 4, 0.5).faces(">Z").workplane().rect(1.5, 3.5, forConstruction=True)\
|
||||||
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(14, s.faces().size())
|
self.assertEqual(14, s.faces().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testQuickStartYZ(self):
|
def testQuickStartYZ(self):
|
||||||
s = Workplane(Plane.YZ()).box(2, 4, 0.5).faces(">X").workplane().rect(1.5, 3.5, forConstruction=True)\
|
s = Workplane(Plane.YZ()).box(2, 4, 0.5).faces(">X").workplane().rect(1.5, 3.5, forConstruction=True)\
|
||||||
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(14, s.faces().size())
|
self.assertEqual(14, s.faces().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testQuickStartXZ(self):
|
def testQuickStartXZ(self):
|
||||||
s = Workplane(Plane.XZ()).box(2, 4, 0.5).faces(">Y").workplane().rect(1.5, 3.5, forConstruction=True)\
|
s = Workplane(Plane.XZ()).box(2, 4, 0.5).faces(">Y").workplane().rect(1.5, 3.5, forConstruction=True)\
|
||||||
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
.vertices().cskHole(0.125, 0.25, 82, depth=None)
|
||||||
self.assertEquals(1, s.solids().size())
|
self.assertEqual(1, s.solids().size())
|
||||||
self.assertEquals(14, s.faces().size())
|
self.assertEqual(14, s.faces().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testDoubleTwistedLoft(self):
|
def testDoubleTwistedLoft(self):
|
||||||
s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft()
|
s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed(
|
||||||
s2 = Workplane("XY").polygon(8, 20.0).workplane(offset=-4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft()
|
rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft()
|
||||||
|
s2 = Workplane("XY").polygon(8, 20.0).workplane(
|
||||||
|
offset=-4.0).transformed(rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft()
|
||||||
# self.assertEquals(10,s.faces().size())
|
# self.assertEquals(10,s.faces().size())
|
||||||
# self.assertEquals(1,s.solids().size())
|
# self.assertEquals(1,s.solids().size())
|
||||||
s3 = s.combineSolids(s2)
|
s3 = s.combineSolids(s2)
|
||||||
self.saveModel(s3)
|
self.saveModel(s3)
|
||||||
|
|
||||||
def testTwistedLoft(self):
|
def testTwistedLoft(self):
|
||||||
s = Workplane("XY").polygon(8,20.0).workplane(offset=4.0).transformed(rotate=Vector(0,0,15.0)).polygon(8,20).loft()
|
s = Workplane("XY").polygon(8, 20.0).workplane(offset=4.0).transformed(
|
||||||
self.assertEquals(10,s.faces().size())
|
rotate=Vector(0, 0, 15.0)).polygon(8, 20).loft()
|
||||||
self.assertEquals(1,s.solids().size())
|
self.assertEqual(10, s.faces().size())
|
||||||
|
self.assertEqual(1, s.solids().size())
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
def testUnions(self):
|
def testUnions(self):
|
||||||
@ -1155,7 +1225,7 @@ class TestCadQuery(BaseTest):
|
|||||||
# union stuff
|
# union stuff
|
||||||
for oo in o:
|
for oo in o:
|
||||||
s = s.union(oo)
|
s = s.union(oo)
|
||||||
print "Total time %0.3f" % (time.time() - beginTime)
|
print("Total time %0.3f" % (time.time() - beginTime))
|
||||||
|
|
||||||
# Test unioning a Solid object
|
# Test unioning a Solid object
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
@ -1169,13 +1239,13 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
def testCombine(self):
|
def testCombine(self):
|
||||||
s = Workplane(Plane.XY())
|
s = Workplane(Plane.XY())
|
||||||
objects1 = s.rect(2.0,2.0).extrude(0.5).faces('>Z').rect(1.0,1.0).extrude(0.5)
|
objects1 = s.rect(2.0, 2.0).extrude(0.5).faces(
|
||||||
|
'>Z').rect(1.0, 1.0).extrude(0.5)
|
||||||
|
|
||||||
objects1.combine()
|
objects1.combine()
|
||||||
|
|
||||||
self.assertEqual(11, objects1.faces().size())
|
self.assertEqual(11, objects1.faces().size())
|
||||||
|
|
||||||
|
|
||||||
def testCombineSolidsInLoop(self):
|
def testCombineSolidsInLoop(self):
|
||||||
# duplicates a memory problem of some kind reported when combining lots of objects
|
# duplicates a memory problem of some kind reported when combining lots of objects
|
||||||
s = Workplane("XY").rect(0.5, 0.5).extrude(5.0)
|
s = Workplane("XY").rect(0.5, 0.5).extrude(5.0)
|
||||||
@ -1190,7 +1260,7 @@ class TestCadQuery(BaseTest):
|
|||||||
s.add(oo)
|
s.add(oo)
|
||||||
s = s.combineSolids()
|
s = s.combineSolids()
|
||||||
|
|
||||||
print "Total time %0.3f" % (time.time() - beginTime)
|
print("Total time %0.3f" % (time.time() - beginTime))
|
||||||
|
|
||||||
self.saveModel(s)
|
self.saveModel(s)
|
||||||
|
|
||||||
@ -1207,7 +1277,8 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(6, s.faces().size())
|
self.assertEqual(6, s.faces().size())
|
||||||
|
|
||||||
# test removal of splitter caused by union operation
|
# test removal of splitter caused by union operation
|
||||||
s = Workplane("XY").box(10,10,10).union(Workplane("XY").box(20,10,10))
|
s = Workplane("XY").box(10, 10, 10).union(
|
||||||
|
Workplane("XY").box(20, 10, 10))
|
||||||
|
|
||||||
self.assertEqual(6, s.faces().size())
|
self.assertEqual(6, s.faces().size())
|
||||||
|
|
||||||
@ -1262,7 +1333,6 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertEqual(6, s.faces().size())
|
self.assertEqual(6, s.faces().size())
|
||||||
|
|
||||||
def testCup(self):
|
def testCup(self):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
UOM = "mm"
|
UOM = "mm"
|
||||||
|
|
||||||
@ -1298,11 +1368,11 @@ class TestCadQuery(BaseTest):
|
|||||||
h = 10.0
|
h = 10.0
|
||||||
t = 1.0
|
t = 1.0
|
||||||
s1 = Workplane("XY").circle(bd).workplane(offset=h).circle(td).loft()
|
s1 = Workplane("XY").circle(bd).workplane(offset=h).circle(td).loft()
|
||||||
s2 = Workplane("XY").workplane(offset=t).circle(bd-(2.0*t)).workplane(offset=(h-t)).circle(td-(2.0*t)).loft()
|
s2 = Workplane("XY").workplane(offset=t).circle(
|
||||||
|
bd - (2.0 * t)).workplane(offset=(h - t)).circle(td - (2.0 * t)).loft()
|
||||||
s3 = s1.cut(s2)
|
s3 = s1.cut(s2)
|
||||||
self.saveModel(s3)
|
self.saveModel(s3)
|
||||||
|
|
||||||
|
|
||||||
def testEnclosure(self):
|
def testEnclosure(self):
|
||||||
"""
|
"""
|
||||||
Builds an electronics enclosure
|
Builds an electronics enclosure
|
||||||
@ -1317,21 +1387,30 @@ class TestCadQuery(BaseTest):
|
|||||||
|
|
||||||
p_thickness = 3.0 # Thickness of the box walls
|
p_thickness = 3.0 # Thickness of the box walls
|
||||||
p_sideRadius = 10.0 # Radius for the curves around the sides of the bo
|
p_sideRadius = 10.0 # Radius for the curves around the sides of the bo
|
||||||
p_topAndBottomRadius = 2.0 #Radius for the curves on the top and bottom edges of the box
|
# Radius for the curves on the top and bottom edges of the box
|
||||||
|
p_topAndBottomRadius = 2.0
|
||||||
|
|
||||||
p_screwpostInset = 12.0 #How far in from the edges the screwposts should be place.
|
# How far in from the edges the screwposts should be place.
|
||||||
p_screwpostID = 4.0 #nner Diameter of the screwpost holes, should be roughly screw diameter not including threads
|
p_screwpostInset = 12.0
|
||||||
p_screwpostOD = 10.0 #Outer Diameter of the screwposts.\nDetermines overall thickness of the posts
|
# nner Diameter of the screwpost holes, should be roughly screw diameter not including threads
|
||||||
|
p_screwpostID = 4.0
|
||||||
|
# Outer Diameter of the screwposts.\nDetermines overall thickness of the posts
|
||||||
|
p_screwpostOD = 10.0
|
||||||
|
|
||||||
p_boreDiameter = 8.0 # Diameter of the counterbore hole, if any
|
p_boreDiameter = 8.0 # Diameter of the counterbore hole, if any
|
||||||
p_boreDepth = 1.0 # Depth of the counterbore hole, if
|
p_boreDepth = 1.0 # Depth of the counterbore hole, if
|
||||||
p_countersinkDiameter = 0.0 #Outer diameter of countersink. Should roughly match the outer diameter of the screw head
|
# Outer diameter of countersink. Should roughly match the outer diameter of the screw head
|
||||||
p_countersinkAngle = 90.0 #Countersink angle (complete angle between opposite sides, not from center to one side)
|
p_countersinkDiameter = 0.0
|
||||||
p_flipLid = True #Whether to place the lid with the top facing down or not.
|
# Countersink angle (complete angle between opposite sides, not from center to one side)
|
||||||
p_lipHeight = 1.0 #Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.
|
p_countersinkAngle = 90.0
|
||||||
|
# Whether to place the lid with the top facing down or not.
|
||||||
|
p_flipLid = True
|
||||||
|
# Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.
|
||||||
|
p_lipHeight = 1.0
|
||||||
|
|
||||||
# outer shell
|
# outer shell
|
||||||
oshell = Workplane("XY").rect(p_outerWidth,p_outerLength).extrude(p_outerHeight + p_lipHeight)
|
oshell = Workplane("XY").rect(p_outerWidth, p_outerLength).extrude(
|
||||||
|
p_outerHeight + p_lipHeight)
|
||||||
|
|
||||||
# weird geometry happens if we make the fillets in the wrong order
|
# weird geometry happens if we make the fillets in the wrong order
|
||||||
if p_sideRadius > p_topAndBottomRadius:
|
if p_sideRadius > p_topAndBottomRadius:
|
||||||
@ -1363,20 +1442,25 @@ class TestCadQuery(BaseTest):
|
|||||||
.extrude((-1.0) * (p_outerHeight + p_lipHeight - p_thickness), True)
|
.extrude((-1.0) * (p_outerHeight + p_lipHeight - p_thickness), True)
|
||||||
|
|
||||||
# split lid into top and bottom parts
|
# split lid into top and bottom parts
|
||||||
(lid,bottom) = box.faces(">Z").workplane(-p_thickness -p_lipHeight ).split(keepTop=True,keepBottom=True).all() #splits into two solids
|
(lid, bottom) = box.faces(">Z").workplane(-p_thickness -
|
||||||
|
p_lipHeight).split(keepTop=True, keepBottom=True).all() # splits into two solids
|
||||||
|
|
||||||
# translate the lid, and subtract the bottom from it to produce the lid inset
|
# translate the lid, and subtract the bottom from it to produce the lid inset
|
||||||
lowerLid = lid.translate((0, 0, -p_lipHeight))
|
lowerLid = lid.translate((0, 0, -p_lipHeight))
|
||||||
cutlip = lowerLid.cut(bottom).translate((p_outerWidth + p_thickness ,0,p_thickness - p_outerHeight + p_lipHeight))
|
cutlip = lowerLid.cut(bottom).translate(
|
||||||
|
(p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight))
|
||||||
|
|
||||||
# compute centers for counterbore/countersink or counterbore
|
# compute centers for counterbore/countersink or counterbore
|
||||||
topOfLidCenters = cutlip.faces(">Z").workplane().rect(POSTWIDTH,POSTLENGTH,forConstruction=True).vertices()
|
topOfLidCenters = cutlip.faces(">Z").workplane().rect(
|
||||||
|
POSTWIDTH, POSTLENGTH, forConstruction=True).vertices()
|
||||||
|
|
||||||
# add holes of the desired type
|
# add holes of the desired type
|
||||||
if p_boreDiameter > 0 and p_boreDepth > 0:
|
if p_boreDiameter > 0 and p_boreDepth > 0:
|
||||||
topOfLid = topOfLidCenters.cboreHole(p_screwpostID,p_boreDiameter,p_boreDepth,(2.0)*p_thickness)
|
topOfLid = topOfLidCenters.cboreHole(
|
||||||
|
p_screwpostID, p_boreDiameter, p_boreDepth, (2.0) * p_thickness)
|
||||||
elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
|
elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
|
||||||
topOfLid = topOfLidCenters.cskHole(p_screwpostID,p_countersinkDiameter,p_countersinkAngle,(2.0)*p_thickness)
|
topOfLid = topOfLidCenters.cskHole(
|
||||||
|
p_screwpostID, p_countersinkDiameter, p_countersinkAngle, (2.0) * p_thickness)
|
||||||
else:
|
else:
|
||||||
topOfLid = topOfLidCenters.hole(p_screwpostID, (2.0) * p_thickness)
|
topOfLid = topOfLidCenters.hole(p_screwpostID, (2.0) * p_thickness)
|
||||||
|
|
||||||
@ -1409,6 +1493,3 @@ class TestCadQuery(BaseTest):
|
|||||||
self.assertTupleAlmostEquals(delta.toTuple(),
|
self.assertTupleAlmostEquals(delta.toTuple(),
|
||||||
(0., 0., 2. * h),
|
(0., 0., 2. * h),
|
||||||
decimal_places)
|
decimal_places)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,13 +2,18 @@
|
|||||||
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):
|
||||||
@ -40,4 +45,5 @@ class TestExporters(BaseTest):
|
|||||||
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'])
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
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
|
||||||
@ -39,9 +39,12 @@ class TestImporters(BaseTest):
|
|||||||
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()
|
||||||
|
@ -14,6 +14,7 @@ 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):
|
||||||
@ -22,71 +23,88 @@ class TestWorkplanes(BaseTest):
|
|||||||
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(
|
||||||
self.assertTupleAlmostEquals((11.0,11.0,0.0),p.toWorldCoords((1.0,1.0)).toTuple(),2 )
|
(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((2.0, 2.0), p.toLocalCoords(
|
||||||
|
Vector(12.0, 12.0, 0)).toTuple(), 2)
|
||||||
|
|
||||||
# TODO test these offsets in the other dimensions too
|
# TODO test these offsets in the other dimensions too
|
||||||
p = Plane.YZ(origin=(0, 2, 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(
|
||||||
self.assertTupleAlmostEquals((10,10.0,0.0), p.toLocalCoords(Vector(0.0,12.0,12.0)).toTuple() ,2 )
|
(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)
|
||||||
|
|
||||||
p = Plane.XZ(origin=(2, 0, 2))
|
p = Plane.XZ(origin=(2, 0, 2))
|
||||||
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
r = p.toWorldCoords((1.0, 1.0)).toTuple()
|
||||||
self.assertTupleAlmostEquals((3.0, 0.0, 3.0), r, 2)
|
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 )
|
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,6 +4,7 @@ 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()
|
||||||
@ -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