First attempt at python2 and python3 support in single codebase

4 tests failing on python3 (CQGI, AMF export)
This commit is contained in:
Adam Urbanczyk
2017-09-17 00:57:12 +02:00
parent 231b691b1b
commit 1e05a45f9c
22 changed files with 2068 additions and 1771 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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'])

View File

@ -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()

View File

@ -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')

View File

@ -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']