removed extraneous build directory

This commit is contained in:
Dave Cowden 2015-12-13 08:33:12 -05:00
parent 7384d9b046
commit e7d8a92e0a
20 changed files with 0 additions and 7900 deletions

View File

@ -1,21 +0,0 @@
#these items point to the freecad implementation
from .freecad_impl.geom import Plane,BoundBox,Vector,Matrix,sortWiresByBuildOrder
from .freecad_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound
from .freecad_impl import exporters
from .freecad_impl import importers
#these items are the common implementation
#the order of these matter
from .selectors import *
from .cq import *
__all__ = [
'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
'Shape','Vertex','Edge','Wire','Solid','Shell','Compound','exporters', 'importers',
'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector',
'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins'
]
__version__ = "0.3.0"

View File

@ -1,18 +0,0 @@
"""
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
This file is part of CadQuery.
CadQuery is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CadQuery is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
"""

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +0,0 @@
"""
A special directive for including a cq object.
"""
import traceback
from cadquery import *
from cadquery import cqgi
import StringIO
from docutils.parsers.rst import directives
template = """
.. raw:: html
<div class="cq" style="text-align:%(txt_align)s;float:left;">
%(out_svg)s
</div>
<div style="clear:both;">
</div>
"""
template_content_indent = ' '
def cq_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
# only consider inline snippets
plot_code = '\n'.join(content)
# Since we don't have a filename, use a hash based on the content
# the script must define a variable called 'out', which is expected to
# be a CQ object
out_svg = "Your Script Did not assign call build_output() function!"
try:
_s = StringIO.StringIO()
result = cqgi.parse(plot_code).build()
if result.success:
exporters.exportShape(result.first_result, "SVG", _s)
out_svg = _s.getvalue()
else:
raise result.exception
except Exception:
traceback.print_exc()
out_svg = traceback.format_exc()
# now out
# Now start generating the lines of output
lines = []
# get rid of new lines
out_svg = out_svg.replace('\n', '')
txt_align = "left"
if "align" in options:
txt_align = options['align']
lines.extend((template % locals()).split('\n'))
lines.extend(['::', ''])
lines.extend([' %s' % row.rstrip()
for row in plot_code.split('\n')])
lines.append('')
if len(lines):
state_machine.insert_input(
lines, state_machine.input_lines.source(0))
return []
def setup(app):
setup.app = app
setup.config = app.config
setup.confdir = app.confdir
options = {'height': directives.length_or_unitless,
'width': directives.length_or_percentage_or_unitless,
'align': directives.unchanged
}
app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)

View File

@ -1,425 +0,0 @@
"""
The CadQuery Gateway Interface.
Provides classes and tools for executing CadQuery scripts
"""
import ast
import traceback
import time
import cadquery
CQSCRIPT = "<cqscript>"
def parse(script_source):
"""
Parses the script as a model, and returns a model.
If you would prefer to access the underlying model without building it,
for example, to inspect its available parameters, construct a CQModel object.
:param script_source: the script to run. Must be a valid cadquery script
:return: a CQModel object that defines the script and allows execution
"""
model = CQModel(script_source)
return model
class CQModel(object):
"""
Represents a Cadquery Script.
After construction, the metadata property contains
a ScriptMetaData object, which describes the model in more detail,
and can be used to retrive the parameters defined by the model.
the build method can be used to generate a 3d model
"""
def __init__(self, script_source):
"""
Create an object by parsing the supplied python script.
:param script_source: a python script to parse
"""
self.metadata = ScriptMetadata()
self.ast_tree = ast.parse(script_source, CQSCRIPT)
self.script_source = script_source
self._find_vars()
# TODO: pick up other scirpt metadata:
# describe
# pick up validation methods
def _find_vars(self):
"""
Parse the script, and populate variables that appear to be
overridable.
"""
#assumption here: we assume that variable declarations
#are only at the top level of the script. IE, we'll ignore any
#variable definitions at lower levels of the script
#we dont want to use the visit interface because here we excplicitly
#want to walk only the top level of the tree.
assignment_finder = ConstantAssignmentFinder(self.metadata)
for node in self.ast_tree.body:
if isinstance(node, ast.Assign):
assignment_finder.visit_Assign(node)
def validate(self, params):
"""
Determine if the supplied parameters are valid.
NOT IMPLEMENTED YET-- raises NotImplementedError
:param params: a dictionary of parameters
"""
raise NotImplementedError("not yet implemented")
def build(self, build_parameters=None):
"""
Executes the script, using the optional parameters to override those in the model
:param build_parameters: a dictionary of variables. The variables must be
assignable to the underlying variable type.
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
This is the interface so that we can return other information on the result, such as the build time
:return: a BuildResult object, which includes the status of the result, and either
a resulting shape or an exception
"""
if not build_parameters:
build_parameters = {}
start = time.clock()
result = BuildResult()
try:
self.set_param_values(build_parameters)
collector = ScriptCallback()
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
.add_entry("build_object", collector.build_object).build()
c = compile(self.ast_tree, CQSCRIPT, 'exec')
exec (c, env)
if collector.has_results():
result.set_success_result(collector.outputObjects)
else:
raise NoOutputError("Script did not call build_object-- no output available.")
except Exception, ex:
print "Error Executing Script:"
result.set_failure_result(ex)
traceback.print_exc()
print "Full Text of Script:"
print self.script_source
end = time.clock()
result.buildTime = end - start
return result
def set_param_values(self, params):
model_parameters = self.metadata.parameters
for k, v in params.iteritems():
if k not in model_parameters:
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
p = model_parameters[k]
p.set_value(v)
class BuildResult(object):
"""
The result of executing a CadQuery script.
The success property contains whether the exeuction was successful.
If successful, the results property contains a list of all results,
and the first_result property contains the first result.
If unsuccessful, the exception property contains a reference to
the stack trace that occurred.
"""
def __init__(self):
self.buildTime = None
self.results = []
self.first_result = None
self.success = False
self.exception = None
def set_failure_result(self, ex):
self.exception = ex
self.success = False
def set_success_result(self, results):
self.results = results
self.first_result = self.results[0]
self.success = True
class ScriptMetadata(object):
"""
Defines the metadata for a parsed CQ Script.
the parameters property is a dict of InputParameter objects.
"""
def __init__(self):
self.parameters = {}
def add_script_parameter(self, p):
self.parameters[p.name] = p
class ParameterType(object):
pass
class NumberParameterType(ParameterType):
pass
class StringParameterType(ParameterType):
pass
class BooleanParameterType(ParameterType):
pass
class InputParameter:
"""
Defines a parameter that can be supplied when the model is executed.
Name, varType, and default_value are always available, because they are computed
from a variable assignment line of code:
The others are only available if the script has used define_parameter() to
provide additional metadata
"""
def __init__(self):
#: the default value for the variable.
self.default_value = None
#: the name of the parameter.
self.name = None
#: type of the variable: BooleanParameter, StringParameter, NumericParameter
self.varType = None
#: help text describing the variable. Only available if the script used describe_parameter()
self.shortDesc = None
#: valid values for the variable. Only available if the script used describe_parameter()
self.valid_values = []
self.ast_node = None
@staticmethod
def create(ast_node, var_name, var_type, default_value, valid_values=None, short_desc=None):
if valid_values is None:
valid_values = []
p = InputParameter()
p.ast_node = ast_node
p.default_value = default_value
p.name = var_name
if short_desc is None:
p.shortDesc = var_name
else:
p.shortDesc = short_desc
p.varType = var_type
p.valid_values = valid_values
return p
def set_value(self, new_value):
if len(self.valid_values) > 0 and new_value not in self.valid_values:
raise InvalidParameterError(
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
.format(str(new_value), self.name, str(self.valid_values)))
if self.varType == NumberParameterType:
try:
f = float(new_value)
self.ast_node.n = f
except ValueError:
raise InvalidParameterError(
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
.format(str(new_value), self.name))
elif self.varType == StringParameterType:
self.ast_node.s = str(new_value)
elif self.varType == BooleanParameterType:
if new_value:
self.ast_node.id = 'True'
else:
self.ast_node.id = 'False'
else:
raise ValueError("Unknown Type of var: ", str(self.varType))
def __str__(self):
return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
self.name, str(self.varType), str(self.default_value))
class ScriptCallback(object):
"""
Allows a script to communicate with the container
the build_object() method is exposed to CQ scripts, to allow them
to return objects to the execution environment
"""
def __init__(self):
self.outputObjects = []
def build_object(self, shape):
"""
return an object to the executing environment
:param shape: a cadquery object
"""
self.outputObjects.append(shape)
def describe_parameter(self,var, valid_values, short_desc):
"""
Not yet implemented: allows a script to document
extra metadata about the parameters
"""
pass
def add_error(self, param, field_list):
"""
Not implemented yet: allows scripts to indicate that there are problems with inputs
"""
pass
def has_results(self):
return len(self.outputObjects) > 0
class InvalidParameterError(Exception):
"""
Raised when an attempt is made to provide a new parameter value
that cannot be assigned to the model
"""
pass
class NoOutputError(Exception):
"""
Raised when the script does not execute the build_object() method to
return a solid
"""
pass
class ScriptExecutionError(Exception):
"""
Represents a script syntax error.
Useful for helping clients pinpoint issues with the script
interactively
"""
def __init__(self, line=None, message=None):
if line is None:
self.line = 0
else:
self.line = line
if message is None:
self.message = "Unknown Script Error"
else:
self.message = message
def full_message(self):
return self.__repr__()
def __str__(self):
return self.__repr__()
def __repr__(self):
return "ScriptError [Line %s]: %s" % (self.line, self.message)
class EnvironmentBuilder(object):
"""
Builds an execution environment for a cadquery script.
The environment includes the builtins, as well as
the other methods the script will need.
"""
def __init__(self):
self.env = {}
def with_real_builtins(self):
return self.with_builtins(__builtins__)
def with_builtins(self, env_dict):
self.env['__builtins__'] = env_dict
return self
def with_cadquery_objects(self):
self.env['cadquery'] = cadquery
self.env['cq'] = cadquery
return self
def add_entry(self, name, value):
self.env[name] = value
return self
def build(self):
return self.env
class ConstantAssignmentFinder(ast.NodeTransformer):
"""
Visits a parse tree, and adds script parameters to the cqModel
"""
def __init__(self, cq_model):
self.cqModel = cq_model
def handle_assignment(self, var_name, value_node):
try:
if type(value_node) == ast.Num:
self.cqModel.add_script_parameter(
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
elif type(value_node) == ast.Str:
self.cqModel.add_script_parameter(
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
elif type(value_node == ast.Name):
if value_node.id == 'True':
self.cqModel.add_script_parameter(
InputParameter.create(value_node, var_name, BooleanParameterType, True))
elif value_node.id == 'False':
self.cqModel.add_script_parameter(
InputParameter.create(value_node, var_name, BooleanParameterType, True))
except:
print "Unable to handle assignment for variable '%s'" % var_name
pass
def visit_Assign(self, node):
try:
left_side = node.targets[0]
#do not handle attribute assignments
if isinstance(left_side,ast.Attribute):
return
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
self.handle_assignment(left_side.id, node.value)
elif type(node.value) == ast.Tuple:
# we have a multi-value assignment
for n, v in zip(left_side.elts, node.value.elts):
self.handle_assignment(n.id, v)
except:
traceback.print_exc()
print "Unable to handle assignment for node '%s'" % ast.dump(left_side)
return node

View File

@ -1,112 +0,0 @@
"""
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
This file is part of CadQuery.
CadQuery is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CadQuery is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
"""
import os, sys
def _fc_path():
"""Find FreeCAD"""
_PATH = ""
if _PATH:
return _PATH
#look for FREECAD_LIB env variable
if os.environ.has_key('FREECAD_LIB'):
_PATH = os.environ.get('FREECAD_LIB')
if os.path.exists( _PATH):
return _PATH
if sys.platform.startswith('linux'):
#Make some dangerous assumptions...
for _PATH in [
os.path.join(os.path.expanduser("~"), "lib/freecad/lib"),
"/usr/local/lib/freecad/lib",
"/usr/lib/freecad/lib",
"/opt/freecad/lib/",
"/usr/bin/freecad/lib",
"/usr/lib/freecad",
]:
if os.path.exists(_PATH):
return _PATH
elif sys.platform.startswith('win'):
#try all the usual suspects
for _PATH in [
"c:/Program Files/FreeCAD0.12/bin",
"c:/Program Files/FreeCAD0.13/bin",
"c:/Program Files/FreeCAD0.14/bin",
"c:/Program Files/FreeCAD0.15/bin",
"c:/Program Files/FreeCAD0.16/bin",
"c:/Program Files/FreeCAD0.17/bin",
"c:/Program Files (x86)/FreeCAD0.12/bin",
"c:/Program Files (x86)/FreeCAD0.13/bin",
"c:/Program Files (x86)/FreeCAD0.14/bin",
"c:/Program Files (x86)/FreeCAD0.15/bin",
"c:/Program Files (x86)/FreeCAD0.16/bin",
"c:/Program Files (x86)/FreeCAD0.17/bin",
"c:/apps/FreeCAD0.12/bin",
"c:/apps/FreeCAD0.13/bin",
"c:/apps/FreeCAD0.14/bin",
"c:/apps/FreeCAD0.15/bin",
"c:/apps/FreeCAD0.16/bin",
"c:/apps/FreeCAD0.17/bin",
"c:/Program Files/FreeCAD 0.12/bin",
"c:/Program Files/FreeCAD 0.13/bin",
"c:/Program Files/FreeCAD 0.14/bin",
"c:/Program Files/FreeCAD 0.15/bin",
"c:/Program Files/FreeCAD 0.16/bin",
"c:/Program Files/FreeCAD 0.17/bin",
"c:/Program Files (x86)/FreeCAD 0.12/bin",
"c:/Program Files (x86)/FreeCAD 0.13/bin",
"c:/Program Files (x86)/FreeCAD 0.14/bin",
"c:/Program Files (x86)/FreeCAD 0.15/bin",
"c:/Program Files (x86)/FreeCAD 0.16/bin",
"c:/Program Files (x86)/FreeCAD 0.17/bin",
"c:/apps/FreeCAD 0.12/bin",
"c:/apps/FreeCAD 0.13/bin",
"c:/apps/FreeCAD 0.14/bin",
"c:/apps/FreeCAD 0.15/bin",
"c:/apps/FreeCAD 0.16/bin",
"c:/apps/FreeCAD 0.17/bin",
]:
if os.path.exists(_PATH):
return _PATH
elif sys.platform.startswith('darwin'):
#Assume we're dealing with a Mac
for _PATH in [
"/Applications/FreeCAD.app/Contents/lib",
os.path.join(os.path.expanduser("~"), "Library/Application Support/FreeCAD/lib"),
]:
if os.path.exists(_PATH):
return _PATH
#Make sure that the correct FreeCAD path shows up in Python's system path
no_library_path = ImportError('cadquery was unable to determine freecads library path')
try:
import FreeCAD
except ImportError:
path = _fc_path()
if path:
sys.path.insert(0, path)
try:
import FreeCAD
except ImportError:
raise no_library_path
else: raise no_library_path

View File

@ -1,392 +0,0 @@
import cadquery
import FreeCAD
import Drawing
import tempfile, os, StringIO
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
class ExportTypes:
STL = "STL"
STEP = "STEP"
AMF = "AMF"
SVG = "SVG"
TJS = "TJS"
class UNITS:
MM = "mm"
IN = "in"
def toString(shape, exportType, tolerance=0.1):
s = StringIO.StringIO()
exportShape(shape, exportType, s, tolerance)
return s.getvalue()
def exportShape(shape,exportType,fileLike,tolerance=0.1):
"""
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
object, the first value is exported
:param exportFormat: the exportFormat to use
:param tolerance: the tolerance, in model units
:param fileLike: a file like object to which the content will be written.
The object should be already open and ready to write. The caller is responsible
for closing the object
"""
if isinstance(shape,cadquery.CQ):
shape = shape.val()
if exportType == ExportTypes.TJS:
#tessellate the model
tess = shape.tessellate(tolerance)
mesher = JsonMesh() #warning: needs to be changed to remove buildTime and exportTime!!!
#add vertices
for vec in tess[0]:
mesher.addVertex(vec.x, vec.y, vec.z)
#add faces
for f in tess[1]:
mesher.addTriangleFace(f[0],f[1], f[2])
fileLike.write( mesher.toJson())
elif exportType == ExportTypes.SVG:
fileLike.write(getSVG(shape.wrapped))
elif exportType == ExportTypes.AMF:
tess = shape.tessellate(tolerance)
aw = AmfWriter(tess).writeAmf(fileLike)
else:
#all these types required writing to a file and then
#re-reading. this is due to the fact that FreeCAD writes these
(h, outFileName) = tempfile.mkstemp()
#weird, but we need to close this file. the next step is going to write to
#it from c code, so it needs to be closed.
os.close(h)
if exportType == ExportTypes.STEP:
shape.exportStep(outFileName)
elif exportType == ExportTypes.STL:
shape.wrapped.exportStl(outFileName)
else:
raise ValueError("No idea how i got here")
res = readAndDeleteFile(outFileName)
fileLike.write(res)
def readAndDeleteFile(fileName):
"""
read data from file provided, and delete it when done
return the contents as a string
"""
res = ""
with open(fileName,'r') as f:
res = f.read()
os.remove(fileName)
return res
def guessUnitOfMeasure(shape):
"""
Guess the unit of measure of a shape.
"""
bb = shape.BoundBox
dimList = [ bb.XLength, bb.YLength,bb.ZLength ]
#no real part would likely be bigger than 10 inches on any side
if max(dimList) > 10:
return UNITS.MM
#no real part would likely be smaller than 0.1 mm on all dimensions
if min(dimList) < 0.1:
return UNITS.IN
#no real part would have the sum of its dimensions less than about 5mm
if sum(dimList) < 10:
return UNITS.IN
return UNITS.MM
class AmfWriter(object):
def __init__(self,tessellation):
self.units = "mm"
self.tessellation = tessellation
def writeAmf(self,outFile):
amf = ET.Element('amf',units=self.units)
#TODO: if result is a compound, we need to loop through them
object = ET.SubElement(amf,'object',id="0")
mesh = ET.SubElement(object,'mesh')
vertices = ET.SubElement(mesh,'vertices')
volume = ET.SubElement(mesh,'volume')
#add vertices
for v in self.tessellation[0]:
vtx = ET.SubElement(vertices,'vertex')
coord = ET.SubElement(vtx,'coordinates')
x = ET.SubElement(coord,'x')
x.text = str(v.x)
y = ET.SubElement(coord,'y')
y.text = str(v.y)
z = ET.SubElement(coord,'z')
z.text = str(v.z)
#add triangles
for t in self.tessellation[1]:
triangle = ET.SubElement(volume,'triangle')
v1 = ET.SubElement(triangle,'v1')
v1.text = str(t[0])
v2 = ET.SubElement(triangle,'v2')
v2.text = str(t[1])
v3 = ET.SubElement(triangle,'v3')
v3.text = str(t[2])
ET.ElementTree(amf).write(outFile,encoding='ISO-8859-1')
"""
Objects that represent
three.js JSON object notation
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
"""
class JsonMesh(object):
def __init__(self):
self.vertices = [];
self.faces = [];
self.nVertices = 0;
self.nFaces = 0;
def addVertex(self,x,y,z):
self.nVertices += 1;
self.vertices.extend([x,y,z]);
#add triangle composed of the three provided vertex indices
def addTriangleFace(self, i,j,k):
#first position means justa simple triangle
self.nFaces += 1;
self.faces.extend([0,int(i),int(j),int(k)]);
"""
Get a json model from this model.
For now we'll forget about colors, vertex normals, and all that stuff
"""
def toJson(self):
return JSON_TEMPLATE % {
'vertices' : str(self.vertices),
'faces' : str(self.faces),
'nVertices': self.nVertices,
'nFaces' : self.nFaces
};
def getPaths(freeCadSVG):
"""
freeCad svg is worthless-- except for paths, which are fairly useful
this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element
returns two lists-- one list of visible lines, and one list of hidden lines
HACK ALERT!!!!!
FreeCAD does not give a way to determine which lines are hidden and which are not
the only way to tell is that hidden lines are in a <g> with 0.15 stroke and visible are 0.35 stroke.
so we actually look for that as a way to parse.
to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we
cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick!
"""
hiddenPaths = []
visiblePaths = []
if len(freeCadSVG) > 0:
#yuk, freecad returns svg fragments. stupid stupid
fullDoc = "<root>%s</root>" % freeCadSVG
e = ET.ElementTree(ET.fromstring(fullDoc))
segments = e.findall(".//g")
for s in segments:
paths = s.findall("path")
if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK
mylist = hiddenPaths
else:
mylist = visiblePaths
for p in paths:
mylist.append(p.get("d"))
return (hiddenPaths,visiblePaths)
else:
return ([],[])
def getSVG(shape,opts=None):
"""
Export a shape to SVG
"""
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
if opts:
d.update(opts)
#need to guess the scale and the coordinate center
uom = guessUnitOfMeasure(shape)
width=float(d['width'])
height=float(d['height'])
marginLeft=float(d['marginLeft'])
marginTop=float(d['marginTop'])
#TODO: provide option to give 3 views
viewVector = FreeCAD.Base.Vector(-1.75,1.1,5)
(visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector)
(hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented!
#get bounding box -- these are all in 2-d space
bb = visibleG0.BoundBox
bb.add(visibleG1.BoundBox)
bb.add(hiddenG0.BoundBox)
bb.add(hiddenG1.BoundBox)
#width pixels for x, height pixesl for y
unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
#compute amount to translate-- move the top left into view
(xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale)
#compute paths ( again -- had to strip out freecad crap )
hiddenContent = ""
for p in hiddenPaths:
hiddenContent += PATHTEMPLATE % p
visibleContent = ""
for p in visiblePaths:
visibleContent += PATHTEMPLATE % p
svg = SVG_TEMPLATE % (
{
"unitScale" : str(unitScale),
"strokeWidth" : str(1.0/unitScale),
"hiddenContent" : hiddenContent ,
"visibleContent" :visibleContent,
"xTranslate" : str(xTranslate),
"yTranslate" : str(yTranslate),
"width" : str(width),
"height" : str(height),
"textboxY" :str(height - 30),
"uom" : str(uom)
}
)
#svg = SVG_TEMPLATE % (
# {"content": projectedContent}
#)
return svg
def exportSVG(shape, fileName):
"""
accept a cadquery shape, and export it to the provided file
TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
export a view of a part to svg
"""
svg = getSVG(shape.val().wrapped)
f = open(fileName,'w')
f.write(svg)
f.close()
JSON_TEMPLATE= """\
{
"metadata" :
{
"formatVersion" : 3,
"generatedBy" : "ParametricParts",
"vertices" : %(nVertices)d,
"faces" : %(nFaces)d,
"normals" : 0,
"colors" : 0,
"uvs" : 0,
"materials" : 1,
"morphTargets" : 0
},
"scale" : 1.0,
"materials": [ {
"DbgColor" : 15658734,
"DbgIndex" : 0,
"DbgName" : "Material",
"colorAmbient" : [0.0, 0.0, 0.0],
"colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746],
"colorSpecular" : [0.5, 0.5, 0.5],
"shading" : "Lambert",
"specularCoef" : 50,
"transparency" : 1.0,
"vertexColors" : false
}],
"vertices": %(vertices)s,
"morphTargets": [],
"normals": [],
"colors": [],
"uvs": [[]],
"faces": %(faces)s
}
"""
SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="%(width)s"
height="%(height)s"
>
<g transform="scale(%(unitScale)s, -%(unitScale)s) translate(%(xTranslate)s,%(yTranslate)s)" stroke-width="%(strokeWidth)s" fill="none">
<!-- hidden lines -->
<g stroke="rgb(160, 160, 160)" fill="none" stroke-dasharray="%(strokeWidth)s,%(strokeWidth)s" >
%(hiddenContent)s
</g>
<!-- solid lines -->
<g stroke="rgb(0, 0, 0)" fill="none">
%(visibleContent)s
</g>
</g>
<g transform="translate(20,%(textboxY)s)" stroke="rgb(0,0,255)">
<line x1="30" y1="-30" x2="75" y2="-33" stroke-width="3" stroke="#000000" />
<text x="80" y="-30" style="stroke:#000000">X </text>
<line x1="30" y1="-30" x2="30" y2="-75" stroke-width="3" stroke="#000000" />
<text x="25" y="-85" style="stroke:#000000">Y </text>
<line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3" stroke="#000000" />
<text x="65" y="-5" style="stroke:#000000">Z </text>
<!--
<line x1="0" y1="0" x2="%(unitScale)s" y2="0" stroke-width="3" />
<text x="0" y="20" style="stroke:#000000">1 %(uom)s </text>
-->
</g>
</svg>
"""
PATHTEMPLATE="\t\t\t<path d=\"%s\" />\n"

View File

@ -1,647 +0,0 @@
"""
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
This file is part of CadQuery.
CadQuery is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CadQuery is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
"""
import math
import cadquery
import FreeCAD
import Part as FreeCADPart
def sortWiresByBuildOrder(wireList, plane, result=[]):
"""Tries to determine how wires should be combined into faces.
Assume:
The wires make up one or more faces, which could have 'holes'
Outer wires are listed ahead of inner wires
there are no wires inside wires inside wires
( IE, islands -- we can deal with that later on )
none of the wires are construction wires
Compute:
one or more sets of wires, with the outer wire listed first, and inner
ones
Returns, list of lists.
"""
result = []
remainingWires = list(wireList)
while remainingWires:
outerWire = remainingWires.pop(0)
group = [outerWire]
otherWires = list(remainingWires)
for w in otherWires:
if plane.isWireInside(outerWire, w):
group.append(w)
remainingWires.remove(w)
result.append(group)
return result
class Vector(object):
"""Create a 3-dimensional vector
:param args: a 3-d vector, with x-y-z parts.
you can either provide:
* nothing (in which case the null vector is return)
* a FreeCAD vector
* a vector ( in which case it is copied )
* a 3-tuple
* three float values, x, y, and z
"""
def __init__(self, *args):
if len(args) == 3:
fV = FreeCAD.Base.Vector(args[0], args[1], args[2])
elif len(args) == 1:
if isinstance(args[0], Vector):
fV = args[0].wrapped
elif isinstance(args[0], tuple):
fV = FreeCAD.Base.Vector(args[0][0], args[0][1], args[0][2])
elif isinstance(args[0], FreeCAD.Base.Vector):
fV = args[0]
else:
fV = args[0]
elif len(args) == 0:
fV = FreeCAD.Base.Vector(0, 0, 0)
else:
raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple")
self._wrapped = fV
@property
def x(self):
return self.wrapped.x
@property
def y(self):
return self.wrapped.y
@property
def z(self):
return self.wrapped.z
@property
def Length(self):
return self.wrapped.Length
@property
def wrapped(self):
return self._wrapped
def toTuple(self):
return (self.x, self.y, self.z)
# TODO: is it possible to create a dynamic proxy without all this code?
def cross(self, v):
return Vector(self.wrapped.cross(v.wrapped))
def dot(self, v):
return self.wrapped.dot(v.wrapped)
def sub(self, v):
return Vector(self.wrapped.sub(v.wrapped))
def add(self, v):
return Vector(self.wrapped.add(v.wrapped))
def multiply(self, scale):
"""Return a copy multiplied by the provided scalar"""
tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
return Vector(tmp_fc_vector.multiply(scale))
def normalized(self):
"""Return a normalized version of this vector"""
tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
tmp_fc_vector.normalize()
return Vector(tmp_fc_vector)
def Center(self):
"""Return the vector itself
The center of myself is myself.
Provided so that vectors, vertexes, and other shapes all support a
common interface, when Center() is requested for all objects on the
stack.
"""
return self
def getAngle(self, v):
return self.wrapped.getAngle(v.wrapped)
def distanceToLine(self):
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
def projectToLine(self):
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
def distanceToPlane(self):
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
def projectToPlane(self):
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
def __add__(self, v):
return self.add(v)
def __repr__(self):
return self.wrapped.__repr__()
def __str__(self):
return self.wrapped.__str__()
def __ne__(self, other):
return self.wrapped.__ne__(other)
def __eq__(self, other):
return self.wrapped.__eq__(other)
class Matrix:
"""A 3d , 4x4 transformation matrix.
Used to move geometry in space.
"""
def __init__(self, matrix=None):
if matrix is None:
self.wrapped = FreeCAD.Base.Matrix()
else:
self.wrapped = matrix
def rotateX(self, angle):
self.wrapped.rotateX(angle)
def rotateY(self, angle):
self.wrapped.rotateY(angle)
class Plane(object):
"""A 2D coordinate system in space
A 2D coordinate system in space, with the x-y axes on the plane, and a
particular point as the origin.
A plane allows the use of 2-d coordinates, which are later converted to
global, 3d coordinates when the operations are complete.
Frequently, it is not necessary to create work planes, as they can be
created automatically from faces.
"""
@classmethod
def named(cls, stdName, origin=(0, 0, 0)):
"""Create a predefined Plane based on the conventional names.
:param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom)
:type stdName: string
:param origin: the desired origin, specified in global coordinates
:type origin: 3-tuple of the origin of the new plane, in global coorindates.
Available named planes are as follows. Direction references refer to
the global directions.
=========== ======= ======= ======
Name xDir yDir zDir
=========== ======= ======= ======
XY +x +y +z
YZ +y +z +x
ZX +z +x +y
XZ +x +z -y
YX +y +x -z
ZY +z +y -x
front +x +y +z
back -x +y -z
left +z +y -x
right -z +y +x
top +x -z +y
bottom +x +z -y
=========== ======= ======= ======
"""
namedPlanes = {
# origin, xDir, normal
'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
}
try:
return namedPlanes[stdName]
except KeyError:
raise ValueError('Supported names are {}'.format(
namedPlanes.keys()))
@classmethod
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
plane = Plane.named('XY', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
plane = Plane.named('YZ', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
plane = Plane.named('ZX', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
plane = Plane.named('XZ', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
plane = Plane.named('YX', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
plane = Plane.named('ZY', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
plane = Plane.named('front', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
plane = Plane.named('back', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
plane = Plane.named('left', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
plane = Plane.named('right', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
plane = Plane.named('top', origin)
plane._setPlaneDir(xDir)
return plane
@classmethod
def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
plane = Plane.named('bottom', origin)
plane._setPlaneDir(xDir)
return plane
def __init__(self, origin, xDir, normal):
"""Create a Plane with an arbitrary orientation
TODO: project x and y vectors so they work even if not orthogonal
:param origin: the origin
:type origin: a three-tuple of the origin, in global coordinates
:param xDir: a vector representing the xDirection.
:type xDir: a three-tuple representing a vector, or a FreeCAD Vector
:param normal: the normal direction for the new plane
:type normal: a FreeCAD Vector
:raises: ValueError if the specified xDir is not orthogonal to the provided normal.
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
"""
normal = Vector(normal)
if (normal.Length == 0.0):
raise ValueError('normal should be non null')
self.zDir = normal.normalized()
xDir = Vector(xDir)
if (xDir.Length == 0.0):
raise ValueError('xDir should be non null')
self._setPlaneDir(xDir)
self.invZDir = self.zDir.multiply(-1.0)
self.origin = origin
@property
def origin(self):
return self._origin
@origin.setter
def origin(self, value):
self._origin = Vector(value)
self._calcTransforms()
def setOrigin2d(self, x, y):
"""
Set a new origin in the plane itself
Set a new origin in the plane itself. The plane's orientation and
xDrection are unaffected.
:param float x: offset in the x direction
:param float y: offset in the y direction
:return: void
The new coordinates are specified in terms of the current 2-d system.
As an example:
p = Plane.XY()
p.setOrigin2d(2, 2)
p.setOrigin2d(2, 2)
results in a plane with its origin at (x, y) = (4, 4) in global
coordinates. Both operations were relative to local coordinates of the
plane.
"""
self.origin = self.toWorldCoords((x, y))
def isWireInside(self, baseWire, testWire):
"""Determine if testWire is inside baseWire
Determine if testWire is inside baseWire, after both wires are projected
into the current plane.
:param baseWire: a reference wire
:type baseWire: a FreeCAD wire
:param testWire: another wire
:type testWire: a FreeCAD wire
:return: True if testWire is inside baseWire, otherwise False
If either wire does not lie in the current plane, it is projected into
the plane first.
*WARNING*: This method is not 100% reliable. It uses bounding box
tests, but needs more work to check for cases when curves are complex.
Future Enhancements:
* Discretizing points along each curve to provide a more reliable
test.
"""
# TODO: also use a set of points along the wire to test as well.
# TODO: would it be more efficient to create objects in the local
# coordinate system, and then transform to global
# coordinates upon extrusion?
tBaseWire = baseWire.transformGeometry(self.fG)
tTestWire = testWire.transformGeometry(self.fG)
# These bounding boxes will have z=0, since we transformed them into the
# space of the plane.
bb = tBaseWire.BoundingBox()
tb = tTestWire.BoundingBox()
# findOutsideBox actually inspects both ways, here we only want to
# know if one is inside the other
return bb == BoundBox.findOutsideBox2D(bb, tb)
def toLocalCoords(self, obj):
"""Project the provided coordinates onto this plane
:param obj: an object or vector to convert
:type vector: a vector or shape
:return: an object of the same type, but converted to local coordinates
Most of the time, the z-coordinate returned will be zero, because most
operations based on a plane are all 2-d. Occasionally, though, 3-d
points outside of the current plane are transformed. One such example is
:py:meth:`Workplane.box`, where 3-d corners of a box are transformed to
orient the box in space correctly.
"""
if isinstance(obj, Vector):
return Vector(self.fG.multiply(obj.wrapped))
elif isinstance(obj, cadquery.Shape):
return obj.transformShape(self.rG)
else:
raise ValueError(
"Don't know how to convert type {} to local coordinates".format(
type(obj)))
def toWorldCoords(self, tuplePoint):
"""Convert a point in local coordinates to global coordinates
:param tuplePoint: point in local coordinates to convert.
:type tuplePoint: a 2 or three tuple of float. The third value is taken to be zero if not supplied.
:return: a Vector in global coordinates
"""
if isinstance(tuplePoint, Vector):
v = tuplePoint
elif len(tuplePoint) == 2:
v = Vector(tuplePoint[0], tuplePoint[1], 0)
else:
v = Vector(tuplePoint)
return Vector(self.rG.multiply(v.wrapped))
def rotated(self, rotate=(0, 0, 0)):
"""Returns a copy of this plane, rotated about the specified axes
Since the z axis is always normal the plane, rotating around Z will
always produce a plane that is parallel to this one.
The origin of the workplane is unaffected by the rotation.
Rotations are done in order x, y, z. If you need a different order,
manually chain together multiple rotate() commands.
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
:return: a copy of this plane rotated as requested.
"""
rotate = Vector(rotate)
# Convert to radians.
rotate = rotate.multiply(math.pi / 180.0)
# Compute rotation matrix.
m = FreeCAD.Base.Matrix()
m.rotateX(rotate.x)
m.rotateY(rotate.y)
m.rotateZ(rotate.z)
# Compute the new plane.
newXdir = Vector(m.multiply(self.xDir.wrapped))
newZdir = Vector(m.multiply(self.zDir.wrapped))
return Plane(self.origin, newXdir, newZdir)
def rotateShapes(self, listOfShapes, rotationMatrix):
"""Rotate the listOfShapes by the supplied rotationMatrix
@param listOfShapes is a list of shape objects
@param rotationMatrix is a geom.Matrix object.
returns a list of shape objects rotated according to the rotationMatrix.
"""
# Compute rotation matrix (global --> local --> rotate --> global).
# rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
# rm = self.computeTransform(rotationMatrix)
# There might be a better way, but to do this rotation takes 3 steps:
# - transform geometry to local coordinates
# - then rotate about x
# - then transform back to global coordinates.
resultWires = []
for w in listOfShapes:
mirrored = w.transformGeometry(rotationMatrix.wrapped)
# If the first vertex of the second wire is not coincident with the
# first or last vertices of the first wire we have to fix the wire
# so that it will mirror correctly.
if ((mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or
(mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z)):
resultWires.append(mirrored)
else:
# Make sure that our mirrored edges meet up and are ordered
# properly.
aEdges = w.wrapped.Edges
aEdges.extend(mirrored.wrapped.Edges)
comp = FreeCADPart.Compound(aEdges)
mirroredWire = comp.connectEdgesToWires(False).Wires[0]
resultWires.append(cadquery.Shape.cast(mirroredWire))
return resultWires
def _setPlaneDir(self, xDir):
"""Set the vectors parallel to the plane, i.e. xDir and yDir"""
if (self.zDir.dot(xDir) > 1e-5):
raise ValueError('xDir must be parralel to the plane')
xDir = Vector(xDir)
self.xDir = xDir.normalized()
self.yDir = self.zDir.cross(self.xDir).normalized()
def _calcTransforms(self):
"""Computes transformation matrices to convert between coordinates
Computes transformation matrices to convert between local and global
coordinates.
"""
# r is the forward transformation matrix from world to local coordinates
# ok i will be really honest, i cannot understand exactly why this works
# something bout the order of the translation and the rotation.
# the double-inverting is strange, and I don't understand it.
r = FreeCAD.Base.Matrix()
# Forward transform must rotate and adjust for origin.
(r.A11, r.A12, r.A13) = (self.xDir.x, self.xDir.y, self.xDir.z)
(r.A21, r.A22, r.A23) = (self.yDir.x, self.yDir.y, self.yDir.z)
(r.A31, r.A32, r.A33) = (self.zDir.x, self.zDir.y, self.zDir.z)
invR = r.inverse()
invR.A14 = self.origin.x
invR.A24 = self.origin.y
invR.A34 = self.origin.z
self.rG = invR
self.fG = invR.inverse()
def computeTransform(self, tMatrix):
"""Computes the 2-d projection of the supplied matrix"""
return Matrix(self.fG.multiply(tMatrix.wrapped).multiply(self.rG))
class BoundBox(object):
"""A BoundingBox for an object or set of objects. Wraps the FreeCAD one"""
def __init__(self, bb):
self.wrapped = bb
self.xmin = bb.XMin
self.xmax = bb.XMax
self.xlen = bb.XLength
self.ymin = bb.YMin
self.ymax = bb.YMax
self.ylen = bb.YLength
self.zmin = bb.ZMin
self.zmax = bb.ZMax
self.zlen = bb.ZLength
self.center = Vector(bb.Center)
self.DiagonalLength = bb.DiagonalLength
def add(self, obj):
"""Returns a modified (expanded) bounding box
obj can be one of several things:
1. a 3-tuple corresponding to x,y, and z amounts to add
2. a vector, containing the x,y,z values to add
3. another bounding box, where a new box will be created that
encloses both.
This bounding box is not changed.
"""
tmp = FreeCAD.Base.BoundBox(self.wrapped)
if isinstance(obj, tuple):
tmp.add(obj[0], obj[1], obj[2])
elif isinstance(obj, Vector):
tmp.add(obj.fV)
elif isinstance(obj, BoundBox):
tmp.add(obj.wrapped)
return BoundBox(tmp)
@classmethod
def findOutsideBox2D(cls, b1, b2):
"""Compares bounding boxes
Compares bounding boxes. Returns none if neither is inside the other.
Returns the outer one if either is outside the other.
BoundBox.isInside works in 3d, but this is a 2d bounding box, so it
doesn't work correctly plus, there was all kinds of rounding error in
the built-in implementation i do not understand.
"""
fc_bb1 = b1.wrapped
fc_bb2 = b2.wrapped
if (fc_bb1.XMin < fc_bb2.XMin and
fc_bb1.XMax > fc_bb2.XMax and
fc_bb1.YMin < fc_bb2.YMin and
fc_bb1.YMax > fc_bb2.YMax):
return b1
if (fc_bb2.XMin < fc_bb1.XMin and
fc_bb2.XMax > fc_bb1.XMax and
fc_bb2.YMin < fc_bb1.YMin and
fc_bb2.YMax > fc_bb1.YMax):
return b2
return None
def isInside(self, anotherBox):
"""Is the provided bounding box inside this one?"""
return self.wrapped.isInside(anotherBox.wrapped)

View File

@ -1,71 +0,0 @@
import cadquery
from .shapes import Shape
import FreeCAD
import Part
import sys
import os
import urllib as urlreader
import tempfile
class ImportTypes:
STEP = "STEP"
class UNITS:
MM = "mm"
IN = "in"
def importShape(importType, fileName):
"""
Imports a file based on the type (STEP, STL, etc)
:param importType: The type of file that we're importing
:param fileName: THe name of the file that we're importing
"""
#Check to see what type of file we're working with
if importType == ImportTypes.STEP:
return importStep(fileName)
#Loads a STEP file into a CQ.Workplane object
def importStep(fileName):
"""
Accepts a file name and loads the STEP file into a cadquery shape
:param fileName: The path and name of the STEP file to be imported
"""
#Now read and return the shape
try:
#print fileName
rshape = Part.read(fileName)
#Make sure that we extract all the solids
solids = []
for solid in rshape.Solids:
solids.append(Shape.cast(solid))
return cadquery.Workplane("XY").newObject(solids)
except:
raise ValueError("STEP File Could not be loaded")
#Loads a STEP file from an URL into a CQ.Workplane object
def importStepFromURL(url):
#Now read and return the shape
try:
webFile = urlreader.urlopen(url)
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
tempFile.write(webFile.read())
webFile.close()
tempFile.close()
rshape = Part.read(tempFile.name)
#Make sure that we extract all the solids
solids = []
for solid in rshape.Solids:
solids.append(Shape.cast(solid))
return cadquery.Workplane("XY").newObject(solids)
except:
raise ValueError("STEP File from the URL: " + url + " Could not be loaded")

View File

@ -1,982 +0,0 @@
"""
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
This file is part of CadQuery.
CadQuery is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CadQuery is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
Wrapper Classes for FreeCAD
These classes provide a stable interface for 3d objects,
independent of the FreeCAD interface.
Future work might include use of pythonOCC, OCC, or even
another CAD kernel directly, so this interface layer is quite important.
Funny, in java this is one of those few areas where i'd actually spend the time
to make an interface and an implementation, but for new these are just rolled together
This interface layer provides three distinct values:
1. It allows us to avoid changing key api points if we change underlying implementations.
It would be a disaster if script and plugin authors had to change models because we
changed implementations
2. Allow better documentation. One of the reasons FreeCAD is no more popular is because
its docs are terrible. This allows us to provide good documentation via docstrings
for each wrapper
3. Work around bugs. there are a quite a feb bugs in free this layer allows fixing them
4. allows for enhanced functionality. Many objects are missing features we need. For example
we need a 'forConstruction' flag on the Wire object. this allows adding those kinds of things
5. allow changing interfaces when we'd like. there are few cases where the FreeCAD api is not
very user friendly: we like to change those when necessary. As an example, in the FreeCAD api,
all factory methods are on the 'Part' object, but it is very useful to know what kind of
object each one returns, so these are better grouped by the type of object they return.
(who would know that Part.makeCircle() returns an Edge, but Part.makePolygon() returns a Wire ?
"""
from cadquery import Vector, BoundBox
import FreeCAD
import Part as FreeCADPart
class Shape(object):
"""
Represents a shape in the system.
Wrappers the FreeCAD api
"""
def __init__(self, obj):
self.wrapped = obj
self.forConstruction = False
# Helps identify this solid through the use of an ID
self.label = ""
@classmethod
def cast(cls, obj, forConstruction=False):
"Returns the right type of wrapper, given a FreeCAD object"
s = obj.ShapeType
if type(obj) == FreeCAD.Base.Vector:
return Vector(obj)
tr = None
# TODO: there is a clever way to do this i'm sure with a lookup
# but it is not a perfect mapping, because we are trying to hide
# a bit of the complexity of Compounds in FreeCAD.
if s == 'Vertex':
tr = Vertex(obj)
elif s == 'Edge':
tr = Edge(obj)
elif s == 'Wire':
tr = Wire(obj)
elif s == 'Face':
tr = Face(obj)
elif s == 'Shell':
tr = Shell(obj)
elif s == 'Solid':
tr = Solid(obj)
elif s == 'Compound':
#compound of solids, lets return a solid instead
if len(obj.Solids) > 1:
tr = Solid(obj)
elif len(obj.Solids) == 1:
tr = Solid(obj.Solids[0])
elif len(obj.Wires) > 0:
tr = Wire(obj)
else:
tr = Compound(obj)
else:
raise ValueError("cast:unknown shape type %s" % s)
tr.forConstruction = forConstruction
return tr
# TODO: all these should move into the exporters folder.
# we dont need a bunch of exporting code stored in here!
#
def exportStl(self, fileName):
self.wrapped.exportStl(fileName)
def exportStep(self, fileName):
self.wrapped.exportStep(fileName)
def exportShape(self, fileName, fileFormat):
if fileFormat == ExportFormats.STL:
self.wrapped.exportStl(fileName)
elif fileFormat == ExportFormats.BREP:
self.wrapped.exportBrep(fileName)
elif fileFormat == ExportFormats.STEP:
self.wrapped.exportStep(fileName)
elif fileFormat == ExportFormats.AMF:
# not built into FreeCAD
#TODO: user selected tolerance
tess = self.wrapped.tessellate(0.1)
aw = amfUtils.AMFWriter(tess)
aw.writeAmf(fileName)
elif fileFormat == ExportFormats.IGES:
self.wrapped.exportIges(fileName)
else:
raise ValueError("Unknown export format: %s" % format)
def geomType(self):
"""
Gets the underlying geometry type
:return: a string according to the geometry type.
Implementations can return any values desired, but the
values the user uses in type filters should correspond to these.
As an example, if a user does::
CQ(object).faces("%mytype")
The expectation is that the geomType attribute will return 'mytype'
The return values depend on the type of the shape:
Vertex: always 'Vertex'
Edge: LINE, ARC, CIRCLE, SPLINE
Face: PLANE, SPHERE, CONE
Solid: 'Solid'
Shell: 'Shell'
Compound: 'Compound'
Wire: 'Wire'
"""
return self.wrapped.ShapeType
def isType(self, obj, strType):
"""
Returns True if the shape is the specified type, false otherwise
contrast with ShapeType, which will raise an exception
if the provide object is not a shape at all
"""
if hasattr(obj, 'ShapeType'):
return obj.ShapeType == strType
else:
return False
def hashCode(self):
return self.wrapped.hashCode()
def isNull(self):
return self.wrapped.isNull()
def isSame(self, other):
return self.wrapped.isSame(other.wrapped)
def isEqual(self, other):
return self.wrapped.isEqual(other.wrapped)
def isValid(self):
return self.wrapped.isValid()
def BoundingBox(self):
return BoundBox(self.wrapped.BoundBox)
def Center(self):
# A Part.Shape object doesn't have the CenterOfMass function, but it's wrapped Solid(s) does
if isinstance(self.wrapped, FreeCADPart.Shape):
# If there are no Solids, we're probably dealing with a Face or something similar
if len(self.Solids()) == 0:
return Vector(self.wrapped.CenterOfMass)
elif len(self.Solids()) == 1:
return Vector(self.Solids()[0].wrapped.CenterOfMass)
elif len(self.Solids()) > 1:
return self.CombinedCenter(self.Solids())
elif isinstance(self.wrapped, FreeCADPart.Solid):
return Vector(self.wrapped.CenterOfMass)
else:
raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped)))
def CenterOfBoundBox(self):
if isinstance(self.wrapped, FreeCADPart.Shape):
# If there are no Solids, we're probably dealing with a Face or something similar
if len(self.Solids()) == 0:
return Vector(self.wrapped.BoundBox.Center)
elif len(self.Solids()) == 1:
return Vector(self.Solids()[0].wrapped.BoundBox.Center)
elif len(self.Solids()) > 1:
return self.CombinedCenterOfBoundBox(self.Solids())
elif isinstance(self.wrapped, FreeCADPart.Solid):
return Vector(self.wrapped.BoundBox.Center)
else:
raise ValueError("Cannot find the center(BoundBox's) of %s object type" % str(type(self.Solids()[0].wrapped)))
@staticmethod
def CombinedCenter(objects):
"""
Calculates the center of mass of multiple objects.
:param objects: a list of objects with mass
"""
total_mass = sum(o.wrapped.Mass for o in objects)
weighted_centers = [o.wrapped.CenterOfMass.multiply(o.wrapped.Mass) for o in objects]
sum_wc = weighted_centers[0]
for wc in weighted_centers[1:] :
sum_wc = sum_wc.add(wc)
return Vector(sum_wc.multiply(1./total_mass))
@staticmethod
def CombinedCenterOfBoundBox(objects):
"""
Calculates the center of BoundBox of multiple objects.
:param objects: a list of objects with mass 1
"""
total_mass = len(objects)
weighted_centers = [o.wrapped.BoundBox.Center.multiply(1.0) for o in objects]
sum_wc = weighted_centers[0]
for wc in weighted_centers[1:] :
sum_wc = sum_wc.add(wc)
return Vector(sum_wc.multiply(1./total_mass))
def Closed(self):
return self.wrapped.Closed
def ShapeType(self):
return self.wrapped.ShapeType
def Vertices(self):
return [Vertex(i) for i in self.wrapped.Vertexes]
def Edges(self):
return [Edge(i) for i in self.wrapped.Edges]
def Compounds(self):
return [Compound(i) for i in self.wrapped.Compounds]
def Wires(self):
return [Wire(i) for i in self.wrapped.Wires]
def Faces(self):
return [Face(i) for i in self.wrapped.Faces]
def Shells(self):
return [Shell(i) for i in self.wrapped.Shells]
def Solids(self):
return [Solid(i) for i in self.wrapped.Solids]
def Area(self):
return self.wrapped.Area
def Length(self):
return self.wrapped.Length
def rotate(self, startVector, endVector, angleDegrees):
"""
Rotates a shape around an axis
:param startVector: start point of rotation axis either a 3-tuple or a Vector
:param endVector: end point of rotation axis, either a 3-tuple or a Vector
:param angleDegrees: angle to rotate, in degrees
:return: a copy of the shape, rotated
"""
if type(startVector) == tuple:
startVector = Vector(startVector)
if type(endVector) == tuple:
endVector = Vector(endVector)
tmp = self.wrapped.copy()
tmp.rotate(startVector.wrapped, endVector.wrapped, angleDegrees)
return Shape.cast(tmp)
def translate(self, vector):
if type(vector) == tuple:
vector = Vector(vector)
tmp = self.wrapped.copy()
tmp.translate(vector.wrapped)
return Shape.cast(tmp)
def scale(self, factor):
tmp = self.wrapped.copy()
tmp.scale(factor)
return Shape.cast(tmp)
def copy(self):
return Shape.cast(self.wrapped.copy())
def transformShape(self, tMatrix):
"""
tMatrix is a matrix object.
returns a copy of the ojbect, transformed by the provided matrix,
with all objects keeping their type
"""
tmp = self.wrapped.copy()
tmp.transformShape(tMatrix)
r = Shape.cast(tmp)
r.forConstruction = self.forConstruction
return r
def transformGeometry(self, tMatrix):
"""
tMatrix is a matrix object.
returns a copy of the object, but with geometry transformed insetad of just
rotated.
WARNING: transformGeometry will sometimes convert lines and circles to splines,
but it also has the ability to handle skew and stretching transformations.
If your transformation is only translation and rotation, it is safer to use transformShape,
which doesnt change the underlying type of the geometry, but cannot handle skew transformations
"""
tmp = self.wrapped.copy()
tmp = tmp.transformGeometry(tMatrix)
return Shape.cast(tmp)
def __hash__(self):
return self.wrapped.hashCode()
class Vertex(Shape):
"""
A Single Point in Space
"""
def __init__(self, obj, forConstruction=False):
"""
Create a vertex from a FreeCAD Vertex
"""
self.wrapped = obj
self.forConstruction = forConstruction
self.X = obj.X
self.Y = obj.Y
self.Z = obj.Z
# Helps identify this solid through the use of an ID
self.label = ""
def toTuple(self):
return (self.X, self.Y, self.Z)
def Center(self):
"""
The center of a vertex is itself!
"""
return Vector(self.wrapped.Point)
class Edge(Shape):
"""
A trimmed curve that represents the border of a face
"""
def __init__(self, obj):
"""
An Edge
"""
self.wrapped = obj
# self.startPoint = None
# self.endPoint = None
self.edgetypes = {
FreeCADPart.Line: 'LINE',
FreeCADPart.ArcOfCircle: 'ARC',
FreeCADPart.Circle: 'CIRCLE'
}
# Helps identify this solid through the use of an ID
self.label = ""
def geomType(self):
t = type(self.wrapped.Curve)
if self.edgetypes.has_key(t):
return self.edgetypes[t]
else:
return "Unknown Edge Curve Type: %s" % str(t)
def startPoint(self):
"""
:return: a vector representing the start poing of this edge
Note, circles may have the start and end points the same
"""
# work around freecad bug where valueAt is unreliable
curve = self.wrapped.Curve
return Vector(curve.value(self.wrapped.ParameterRange[0]))
def endPoint(self):
"""
:return: a vector representing the end point of this edge.
Note, circles may have the start and end points the same
"""
# warning: easier syntax in freecad of <Edge>.valueAt(<Edge>.ParameterRange[1]) has
# a bug with curves other than arcs, but using the underlying curve directly seems to work
# that's the solution i'm using below
curve = self.wrapped.Curve
v = Vector(curve.value(self.wrapped.ParameterRange[1]))
return v
def tangentAt(self, locationVector=None):
"""
Compute tangent vector at the specified location.
:param locationVector: location to use. Use the center point if None
:return: tangent vector
"""
if locationVector is None:
locationVector = self.Center()
p = self.wrapped.Curve.parameter(locationVector.wrapped)
return Vector(self.wrapped.tangentAt(p))
@classmethod
def makeCircle(cls, radius, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=360.0, angle2=360):
return Edge(FreeCADPart.makeCircle(radius, toVector(pnt), toVector(dir), angle1, angle2))
@classmethod
def makeSpline(cls, listOfVector):
"""
Interpolate a spline through the provided points.
:param cls:
:param listOfVector: a list of Vectors that represent the points
:return: an Edge
"""
vecs = [v.wrapped for v in listOfVector]
spline = FreeCADPart.BSplineCurve()
spline.interpolate(vecs, False)
return Edge(spline.toShape())
@classmethod
def makeThreePointArc(cls, v1, v2, v3):
"""
Makes a three point arc through the provided points
:param cls:
:param v1: start vector
:param v2: middle vector
:param v3: end vector
:return: an edge object through the three points
"""
arc = FreeCADPart.Arc(v1.wrapped, v2.wrapped, v3.wrapped)
e = Edge(arc.toShape())
return e # arcane and undocumented, this creates an Edge object
@classmethod
def makeLine(cls, v1, v2):
"""
Create a line between two points
:param v1: Vector that represents the first point
:param v2: Vector that represents the second point
:return: A linear edge between the two provided points
"""
return Edge(FreeCADPart.makeLine(v1.toTuple(), v2.toTuple()))
class Wire(Shape):
"""
A series of connected, ordered Edges, that typically bounds a Face
"""
def __init__(self, obj):
"""
A Wire
"""
self.wrapped = obj
# Helps identify this solid through the use of an ID
self.label = ""
@classmethod
def combine(cls, listOfWires):
"""
Attempt to combine a list of wires into a new wire.
the wires are returned in a list.
:param cls:
:param listOfWires:
:return:
"""
return Shape.cast(FreeCADPart.Wire([w.wrapped for w in listOfWires]))
@classmethod
def assembleEdges(cls, listOfEdges):
"""
Attempts to build a wire that consists of the edges in the provided list
:param cls:
:param listOfEdges: a list of Edge objects
:return: a wire with the edges assembled
"""
fCEdges = [a.wrapped for a in listOfEdges]
wa = Wire(FreeCADPart.Wire(fCEdges))
return wa
@classmethod
def makeCircle(cls, radius, center, normal):
"""
Makes a Circle centered at the provided point, having normal in the provided direction
:param radius: floating point radius of the circle, must be > 0
:param center: vector representing the center of the circle
:param normal: vector representing the direction of the plane the circle should lie in
:return:
"""
w = Wire(FreeCADPart.Wire([FreeCADPart.makeCircle(radius, center.wrapped, normal.wrapped)]))
return w
@classmethod
def makePolygon(cls, listOfVertices, forConstruction=False):
# convert list of tuples into Vectors.
w = Wire(FreeCADPart.makePolygon([i.wrapped for i in listOfVertices]))
w.forConstruction = forConstruction
return w
@classmethod
def makeHelix(cls, pitch, height, radius, angle=360.0):
"""
Make a helix with a given pitch, height and radius
By default a cylindrical surface is used to create the helix. If
the fourth parameter is set (the apex given in degree) a conical surface is used instead'
"""
return Wire(FreeCADPart.makeHelix(pitch, height, radius, angle))
def clean(self):
"""This method is not implemented yet."""
return self
class Face(Shape):
"""
a bounded surface that represents part of the boundary of a solid
"""
def __init__(self, obj):
self.wrapped = obj
self.facetypes = {
# TODO: bezier,bspline etc
FreeCADPart.Plane: 'PLANE',
FreeCADPart.Sphere: 'SPHERE',
FreeCADPart.Cone: 'CONE'
}
# Helps identify this solid through the use of an ID
self.label = ""
def geomType(self):
t = type(self.wrapped.Surface)
if self.facetypes.has_key(t):
return self.facetypes[t]
else:
return "Unknown Face Surface Type: %s" % str(t)
def normalAt(self, locationVector=None):
"""
Computes the normal vector at the desired location on the face.
:returns: a vector representing the direction
:param locationVector: the location to compute the normal at. If none, the center of the face is used.
:type locationVector: a vector that lies on the surface.
"""
if locationVector == None:
locationVector = self.Center()
(u, v) = self.wrapped.Surface.parameter(locationVector.wrapped)
return Vector(self.wrapped.normalAt(u, v).normalize())
@classmethod
def makePlane(cls, length, width, basePnt=None, dir=None):
return Face(FreeCADPart.makePlan(length, width, toVector(basePnt), toVector(dir)))
@classmethod
def makeRuledSurface(cls, edgeOrWire1, edgeOrWire2, dist=None):
"""
'makeRuledSurface(Edge|Wire,Edge|Wire) -- Make a ruled surface
Create a ruled surface out of two edges or wires. If wires are used then
these must have the same
"""
return Shape.cast(FreeCADPart.makeRuledSurface(edgeOrWire1.obj, edgeOrWire2.obj, dist))
def cut(self, faceToCut):
"Remove a face from another one"
return Shape.cast(self.obj.cut(faceToCut.obj))
def fuse(self, faceToJoin):
return Shape.cast(self.obj.fuse(faceToJoin.obj))
def intersect(self, faceToIntersect):
"""
computes the intersection between the face and the supplied one.
The result could be a face or a compound of faces
"""
return Shape.cast(self.obj.common(faceToIntersect.obj))
class Shell(Shape):
"""
the outer boundary of a surface
"""
def __init__(self, wrapped):
"""
A Shell
"""
self.wrapped = wrapped
# Helps identify this solid through the use of an ID
self.label = ""
@classmethod
def makeShell(cls, listOfFaces):
return Shell(FreeCADPart.makeShell([i.obj for i in listOfFaces]))
class Solid(Shape):
"""
a single solid
"""
def __init__(self, obj):
"""
A Solid
"""
self.wrapped = obj
# Helps identify this solid through the use of an ID
self.label = ""
@classmethod
def isSolid(cls, obj):
"""
Returns true if the object is a FreeCAD solid, false otherwise
"""
if hasattr(obj, 'ShapeType'):
if obj.ShapeType == 'Solid' or \
(obj.ShapeType == 'Compound' and len(obj.Solids) > 0):
return True
return False
@classmethod
def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)):
"""
makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height)
By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
"""
return Shape.cast(FreeCADPart.makeBox(length, width, height, pnt.wrapped, dir.wrapped))
@classmethod
def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
"""
Make a cone with given radii and height
By default pnt=Vector(0,0,0),
dir=Vector(0,0,1) and angle=360'
"""
return Shape.cast(FreeCADPart.makeCone(radius1, radius2, height, pnt.wrapped, dir.wrapped, angleDegrees))
@classmethod
def makeCylinder(cls, radius, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
"""
makeCylinder(radius,height,[pnt,dir,angle]) --
Make a cylinder with a given radius and height
By default pnt=Vector(0,0,0),dir=Vector(0,0,1) and angle=360'
"""
return Shape.cast(FreeCADPart.makeCylinder(radius, height, pnt.wrapped, dir.wrapped, angleDegrees))
@classmethod
def makeTorus(cls, radius1, radius2, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None):
"""
makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) --
Make a torus with agiven radii and angles
By default pnt=Vector(0,0,0),dir=Vector(0,0,1),angle1=0
,angle1=360 and angle=360'
"""
return Shape.cast(FreeCADPart.makeTorus(radius1, radius2, pnt, dir, angleDegrees1, angleDegrees2))
@classmethod
def sweep(cls, profileWire, pathWire):
"""
make a solid by sweeping the profileWire along the specified path
:param cls:
:param profileWire:
:param pathWire:
:return:
"""
# needs to use freecad wire.makePipe or makePipeShell
# needs to allow free-space wires ( those not made from a workplane )
@classmethod
def makeLoft(cls, listOfWire, ruled=False):
"""
makes a loft from a list of wires
The wires will be converted into faces when possible-- it is presumed that nobody ever actually
wants to make an infinitely thin shell for a real FreeCADPart.
"""
# the True flag requests building a solid instead of a shell.
return Shape.cast(FreeCADPart.makeLoft([i.wrapped for i in listOfWire], True, ruled))
@classmethod
def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
"""
Make a wedge located in pnt
By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)
"""
return Shape.cast(
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
@classmethod
def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
"""
Make a sphere with a given radius
By default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360
"""
return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
@classmethod
def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees):
"""
Creates a 'twisted prism' by extruding, while simultaneously rotating around the extrusion vector.
Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the
construction methods used here are different enough that they should be separate.
At a high level, the steps followed are:
(1) accept a set of wires
(2) create another set of wires like this one, but which are transformed and rotated
(3) create a ruledSurface between the sets of wires
(4) create a shell and compute the resulting object
:param outerWire: the outermost wire, a cad.Wire
:param innerWires: a list of inner wires, a list of cad.Wire
:param vecCenter: the center point about which to rotate. the axis of rotation is defined by
vecNormal, located at vecCenter. ( a cad.Vector )
:param vecNormal: a vector along which to extrude the wires ( a cad.Vector )
:param angleDegrees: the angle to rotate through while extruding
:return: a cad.Solid object
"""
# from this point down we are dealing with FreeCAD wires not cad.wires
startWires = [outerWire.wrapped] + [i.wrapped for i in innerWires]
endWires = []
p1 = vecCenter.wrapped
p2 = vecCenter.add(vecNormal).wrapped
# make translated and rotated copy of each wire
for w in startWires:
w2 = w.copy()
w2.translate(vecNormal.wrapped)
w2.rotate(p1, p2, angleDegrees)
endWires.append(w2)
# make a ruled surface for each set of wires
sides = []
for w1, w2 in zip(startWires, endWires):
rs = FreeCADPart.makeRuledSurface(w1, w2)
sides.append(rs)
#make faces for the top and bottom
startFace = FreeCADPart.Face(startWires)
endFace = FreeCADPart.Face(endWires)
#collect all the faces from the sides
faceList = [startFace]
for s in sides:
faceList.extend(s.Faces)
faceList.append(endFace)
shell = FreeCADPart.makeShell(faceList)
solid = FreeCADPart.makeSolid(shell)
return Shape.cast(solid)
@classmethod
def extrudeLinear(cls, outerWire, innerWires, vecNormal):
"""
Attempt to extrude the list of wires into a prismatic solid in the provided direction
:param outerWire: the outermost wire
:param innerWires: a list of inner wires
:param vecNormal: a vector along which to extrude the wires
:return: a Solid object
The wires must not intersect
Extruding wires is very non-trivial. Nested wires imply very different geometry, and
there are many geometries that are invalid. In general, the following conditions must be met:
* all wires must be closed
* there cannot be any intersecting or self-intersecting wires
* wires must be listed from outside in
* more than one levels of nesting is not supported reliably
This method will attempt to sort the wires, but there is much work remaining to make this method
reliable.
"""
# 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 then cutting it from the main solid fails with BRep_NotDone.
#the work around is to extrude each and then join the resulting solids, which seems to work
#FreeCAD allows this in one operation, but others might not
freeCADWires = [outerWire.wrapped]
for w in innerWires:
freeCADWires.append(w.wrapped)
f = FreeCADPart.Face(freeCADWires)
result = f.extrude(vecNormal.wrapped)
return Shape.cast(result)
@classmethod
def revolve(cls, outerWire, innerWires, angleDegrees, axisStart, axisEnd):
"""
Attempt to revolve the list of wires into a solid in the provided direction
:param outerWire: the outermost wire
:param innerWires: a list of inner wires
:param angleDegrees: the angle to revolve through.
:type angleDegrees: float, anything less than 360 degrees will leave the shape open
:param axisStart: the start point of the axis of rotation
:type axisStart: tuple, a two tuple
:param axisEnd: the end point of the axis of rotation
:type axisEnd: tuple, a two tuple
:return: a Solid object
The wires must not intersect
* all wires must be closed
* there cannot be any intersecting or self-intersecting wires
* wires must be listed from outside in
* more than one levels of nesting is not supported reliably
* the wire(s) that you're revolving cannot be centered
This method will attempt to sort the wires, but there is much work remaining to make this method
reliable.
"""
freeCADWires = [outerWire.wrapped]
for w in innerWires:
freeCADWires.append(w.wrapped)
f = FreeCADPart.Face(freeCADWires)
rotateCenter = FreeCAD.Base.Vector(axisStart)
rotateAxis = FreeCAD.Base.Vector(axisEnd)
#Convert our axis end vector into to something FreeCAD will understand (an axis specification vector)
rotateAxis = rotateCenter.sub(rotateAxis)
#FreeCAD wants a rotation center and then an axis to rotate around rather than an axis of rotation
result = f.revolve(rotateCenter, rotateAxis, angleDegrees)
return Shape.cast(result)
def tessellate(self, tolerance):
return self.wrapped.tessellate(tolerance)
def intersect(self, toIntersect):
"""
computes the intersection between this solid and the supplied one
The result could be a face or a compound of faces
"""
return Shape.cast(self.wrapped.common(toIntersect.wrapped))
def cut(self, solidToCut):
"Remove a solid from another one"
return Shape.cast(self.wrapped.cut(solidToCut.wrapped))
def fuse(self, solidToJoin):
return Shape.cast(self.wrapped.fuse(solidToJoin.wrapped))
def clean(self):
"""Clean faces by removing splitter edges."""
r = self.wrapped.removeSplitter()
# removeSplitter() returns a generic Shape type, cast to actual type of object
r = FreeCADPart.cast_to_shape(r)
return Shape.cast(r)
def fillet(self, radius, edgeList):
"""
Fillets the specified edges of this solid.
:param radius: float > 0, the radius of the fillet
:param edgeList: a list of Edge objects, which must belong to this solid
:return: Filleted solid
"""
nativeEdges = [e.wrapped for e in edgeList]
return Shape.cast(self.wrapped.makeFillet(radius, nativeEdges))
def chamfer(self, length, length2, edgeList):
"""
Chamfers the specified edges of this solid.
:param length: length > 0, the length (length) of the chamfer
:param length2: length2 > 0, optional parameter for asymmetrical chamfer. Should be `None` if not required.
:param edgeList: a list of Edge objects, which must belong to this solid
:return: Chamfered solid
"""
nativeEdges = [e.wrapped for e in edgeList]
# note: we prefer 'length' word to 'radius' as opposed to FreeCAD's API
if length2:
return Shape.cast(self.wrapped.makeChamfer(length, length2, nativeEdges))
else:
return Shape.cast(self.wrapped.makeChamfer(length, nativeEdges))
def shell(self, faceList, thickness, tolerance=0.0001):
"""
make a shelled solid of given by removing the list of faces
:param faceList: list of face objects, which must be part of the solid.
:param thickness: floating point thickness. positive shells outwards, negative shells inwards
:param tolerance: modelling tolerance of the method, default=0.0001
:return: a shelled solid
**WARNING** The underlying FreeCAD implementation can very frequently have problems
with shelling complex geometries!
"""
nativeFaces = [f.wrapped for f in faceList]
return Shape.cast(self.wrapped.makeThickness(nativeFaces, thickness, tolerance))
class Compound(Shape):
"""
a collection of disconnected solids
"""
def __init__(self, obj):
"""
An Edge
"""
self.wrapped = obj
# Helps identify this solid through the use of an ID
self.label = ""
def Center(self):
return self.Center()
@classmethod
def makeCompound(cls, listOfShapes):
"""
Create a compound out of a list of shapes
"""
solids = [s.wrapped for s in listOfShapes]
c = FreeCADPart.Compound(solids)
return Shape.cast(c)
def fuse(self, toJoin):
return Shape.cast(self.wrapped.fuse(toJoin.wrapped))
def tessellate(self, tolerance):
return self.wrapped.tessellate(tolerance)
def clean(self):
"""This method is not implemented yet."""
return self

View File

@ -1,18 +0,0 @@
"""
CadQuery
Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""

View File

@ -1,474 +0,0 @@
"""
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
This file is part of CadQuery.
CadQuery is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CadQuery is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>
"""
import re
import math
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
class Selector(object):
"""
Filters a list of objects
Filters must provide a single method that filters objects.
"""
def filter(self,objectList):
"""
Filter the provided list
:param objectList: list to filter
:type objectList: list of FreeCAD primatives
:return: filtered list
The default implementation returns the original list unfiltered
"""
return objectList
def __and__(self, other):
return AndSelector(self, other)
def __add__(self, other):
return SumSelector(self, other)
def __sub__(self, other):
return SubtractSelector(self, other)
def __neg__(self):
return InverseSelector(self)
class NearestToPointSelector(Selector):
"""
Selects object nearest the provided point.
If the object is a vertex or point, the distance
is used. For other kinds of shapes, the center of mass
is used to to compute which is closest.
Applicability: All Types of Shapes
Example::
CQ(aCube).vertices(NearestToPointSelector((0,1,0))
returns the vertex of the unit cube closest to the point x=0,y=1,z=0
"""
def __init__(self,pnt ):
self.pnt = pnt
def filter(self,objectList):
def dist(tShape):
return tShape.Center().sub(Vector(*self.pnt)).Length
#if tShape.ShapeType == 'Vertex':
# return tShape.Point.sub(toVector(self.pnt)).Length
#else:
# return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
return [ min(objectList,key=dist) ]
class BoxSelector(Selector):
"""
Selects objects inside the 3D box defined by 2 points.
If `boundingbox` is True only the objects that have their bounding
box inside the given box is selected. Otherwise only center point
of the object is tested.
Applicability: all types of shapes
Example::
CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
"""
def __init__(self, point0, point1, boundingbox=False):
self.p0 = Vector(*point0)
self.p1 = Vector(*point1)
self.test_boundingbox = boundingbox
def filter(self, objectList):
result = []
x0, y0, z0 = self.p0.toTuple()
x1, y1, z1 = self.p1.toTuple()
def isInsideBox(p):
# using XOR for checking if x/y/z is in between regardless
# of order of x/y/z0 and x/y/z1
return ((p.x < x0) ^ (p.x < x1)) and \
((p.y < y0) ^ (p.y < y1)) and \
((p.z < z0) ^ (p.z < z1))
for o in objectList:
if self.test_boundingbox:
bb = o.BoundingBox()
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
result.append(o)
else:
if isInsideBox(o.Center()):
result.append(o)
return result
class BaseDirSelector(Selector):
"""
A selector that handles selection on the basis of a single
direction vector
"""
def __init__(self,vector,tolerance=0.0001 ):
self.direction = vector
self.TOLERANCE = tolerance
def test(self,vec):
"Test a specified vector. Subclasses override to provide other implementations"
return True
def filter(self,objectList):
"""
There are lots of kinds of filters, but
for planes they are always based on the normal of the plane,
and for edges on the tangent vector along the edge
"""
r = []
for o in objectList:
#no really good way to avoid a switch here, edges and faces are simply different!
if type(o) == Face:
# a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
normal = o.normalAt(None)
if self.test(normal):
r.append(o)
elif type(o) == Edge and o.geomType() == 'LINE':
#an edge is parallel to a direction if it is a line, and the line is parallel to the dir
tangent = o.tangentAt(None)
if self.test(tangent):
r.append(o)
return r
class ParallelDirSelector(BaseDirSelector):
"""
Selects objects parallel with the provided direction
Applicability:
Linear Edges
Planar Faces
Use the string syntax shortcut \|(X|Y|Z) if you want to select
based on a cardinal direction.
Example::
CQ(aCube).faces(ParallelDirSelector((0,0,1))
selects faces with a normals in the z direction, and is equivalent to::
CQ(aCube).faces("|Z")
"""
def test(self,vec):
return self.direction.cross(vec).Length < self.TOLERANCE
class DirectionSelector(BaseDirSelector):
"""
Selects objects aligned with the provided direction
Applicability:
Linear Edges
Planar Faces
Use the string syntax shortcut +/-(X|Y|Z) if you want to select
based on a cardinal direction.
Example::
CQ(aCube).faces(DirectionSelector((0,0,1))
selects faces with a normals in the z direction, and is equivalent to::
CQ(aCube).faces("+Z")
"""
def test(self,vec):
return abs(self.direction.getAngle(vec) < self.TOLERANCE)
class PerpendicularDirSelector(BaseDirSelector):
"""
Selects objects perpendicular with the provided direction
Applicability:
Linear Edges
Planar Faces
Use the string syntax shortcut #(X|Y|Z) if you want to select
based on a cardinal direction.
Example::
CQ(aCube).faces(PerpendicularDirSelector((0,0,1))
selects faces with a normals perpendicular to the z direction, and is equivalent to::
CQ(aCube).faces("#Z")
"""
def test(self,vec):
angle = self.direction.getAngle(vec)
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE )
return not r
class TypeSelector(Selector):
"""
Selects objects of the prescribed topological type.
Applicability:
Faces: Plane,Cylinder,Sphere
Edges: Line,Circle,Arc
You can use the shortcut selector %(PLANE|SPHERE|CONE) for faces,
and %(LINE|ARC|CIRCLE) for edges.
For example this::
CQ(aCube).faces ( TypeSelector("PLANE") )
will select 6 faces, and is equivalent to::
CQ(aCube).faces( "%PLANE" )
"""
def __init__(self,typeString):
self.typeString = typeString.upper()
def filter(self,objectList):
r = []
for o in objectList:
if o.geomType() == self.typeString:
r.append(o)
return r
class DirectionMinMaxSelector(Selector):
"""
Selects objects closest or farthest in the specified direction
Used for faces, points, and edges
Applicability:
All object types. for a vertex, its point is used. for all other kinds
of objects, the center of mass of the object is used.
You can use the string shortcuts >(X|Y|Z) or <(X|Y|Z) if you want to
select based on a cardinal direction.
For example this::
CQ(aCube).faces ( DirectionMinMaxSelector((0,0,1),True )
Means to select the face having the center of mass farthest in the positive z direction,
and is the same as:
CQ(aCube).faces( ">Z" )
Future Enhancements:
provide a nicer way to select in arbitrary directions. IE, a bit more code could
allow '>(0,0,1)' to work.
"""
def __init__(self, vector, directionMax=True, tolerance=0.0001):
self.vector = vector
self.max = max
self.directionMax = directionMax
self.TOLERANCE = tolerance
def filter(self,objectList):
def distance(tShape):
return tShape.Center().dot(self.vector)
#if tShape.ShapeType == 'Vertex':
# pnt = tShape.Point
#else:
# pnt = tShape.Center()
#return pnt.dot(self.vector)
# find out the max/min distance
if self.directionMax:
d = max(map(distance, objectList))
else:
d = min(map(distance, objectList))
# return all objects at the max/min distance (within a tolerance)
return filter(lambda o: abs(d - distance(o)) < self.TOLERANCE, objectList)
class BinarySelector(Selector):
"""
Base class for selectors that operates with two other
selectors. Subclass must implement the :filterResults(): method.
"""
def __init__(self, left, right):
self.left = left
self.right = right
def filter(self, objectList):
return self.filterResults(self.left.filter(objectList),
self.right.filter(objectList))
def filterResults(self, r_left, r_right):
raise NotImplementedError
class AndSelector(BinarySelector):
"""
Intersection selector. Returns objects that is selected by both selectors.
"""
def filterResults(self, r_left, r_right):
# return intersection of lists
return list(set(r_left) & set(r_right))
class SumSelector(BinarySelector):
"""
Union selector. Returns the sum of two selectors results.
"""
def filterResults(self, r_left, r_right):
# return the union (no duplicates) of lists
return list(set(r_left + r_right))
class SubtractSelector(BinarySelector):
"""
Difference selector. Substract results of a selector from another
selectors results.
"""
def filterResults(self, r_left, r_right):
return list(set(r_left) - set(r_right))
class InverseSelector(Selector):
"""
Inverts the selection of given selector. In other words, selects
all objects that is not selected by given selector.
"""
def __init__(self, selector):
self.selector = selector
def filter(self, objectList):
# note that Selector() selects everything
return SubtractSelector(Selector(), self.selector).filter(objectList)
class StringSyntaxSelector(Selector):
"""
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
are also available ( usually with more functionality ) through the creation of full-fledged
selector objects. see :py:class:`Selector` and its subclasses
Filtering works differently depending on the type of object list being filtered.
:param selectorString: A two-part selector string, [selector][axis]
:return: objects that match the specified selector
***Modfiers*** are ``('|','+','-','<','>','%')``
:\|:
parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
:#:
perpendicular to (same as :py:class:`PerpendicularDirSelector` )
:+:
positive direction (same as :py:class:`DirectionSelector` )
:-:
negative direction (same as :py:class:`DirectionSelector` )
:>:
maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
:<:
minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
:%:
curve/surface type (same as :py:class:`TypeSelector`)
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ``
Selectors are a complex topic: see :ref:`selector_reference` for more information
"""
def __init__(self,selectorString):
self.axes = {
'X': Vector(1,0,0),
'Y': Vector(0,1,0),
'Z': Vector(0,0,1),
'XY': Vector(1,1,0),
'YZ': Vector(0,1,1),
'XZ': Vector(1,0,1)
}
namedViews = {
'front': ('>','Z' ),
'back': ('<','Z'),
'left':('<', 'X'),
'right': ('>', 'X'),
'top': ('>','Y'),
'bottom': ('<','Y')
}
self.selectorString = selectorString
r = re.compile("\s*([-\+<>\|\%#])*\s*(\w+)\s*",re.IGNORECASE)
m = r.match(selectorString)
if m != None:
if namedViews.has_key(selectorString):
(a,b) = namedViews[selectorString]
self.mySelector = self._chooseSelector(a,b )
else:
self.mySelector = self._chooseSelector(m.groups()[0],m.groups()[1])
else:
raise ValueError ("Selector String format must be [-+<>|#%] X|Y|Z ")
def _chooseSelector(self,selType,selAxis):
"""Sets up the underlying filters accordingly"""
if selType == "%":
return TypeSelector(selAxis)
#all other types need to select axis as a vector
#get the axis vector first, will throw an except if an unknown axis is used
try:
vec = self.axes[selAxis]
except KeyError:
raise ValueError ("Axis value %s not allowed: must be one of %s" % (selAxis, str(self.axes)))
if selType in (None, "+"):
#use direction filter
return DirectionSelector(vec)
elif selType == '-':
#just use the reverse of the direction vector
return DirectionSelector(vec.multiply(-1.0))
elif selType == "|":
return ParallelDirSelector(vec)
elif selType == ">":
return DirectionMinMaxSelector(vec,True)
elif selType == "<":
return DirectionMinMaxSelector(vec,False)
elif selType == '#':
return PerpendicularDirSelector(vec)
else:
raise ValueError ("Selector String format must be [-+<>|] X|Y|Z ")
def filter(self,objectList):
"""
selects minimum, maximum, positive or negative values relative to a direction
[+\|-\|<\|>\|] \<X\|Y\|Z>
"""
return self.mySelector.filter(objectList)

View File

@ -1,170 +0,0 @@
"""
Tests CQGI functionality
Currently, this includes:
Parsing a script, and detecting its available variables
Altering the values at runtime
defining a build_object function to return results
"""
from cadquery import cqgi
from tests import BaseTest
import textwrap
TESTSCRIPT = textwrap.dedent(
"""
height=2.0
width=3.0
(a,b) = (1.0,1.0)
foo="bar"
result = "%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) )
build_object(result)
"""
)
class TestCQGI(BaseTest):
def test_parser(self):
model = cqgi.CQModel(TESTSCRIPT)
metadata = model.metadata
self.assertEquals(set(metadata.parameters.keys()), {'height', 'width', 'a', 'b', 'foo'})
def test_build_with_empty_params(self):
model = cqgi.CQModel(TESTSCRIPT)
result = model.build()
self.assertTrue(result.success)
self.assertTrue(len(result.results) == 1)
self.assertTrue(result.results[0] == "2.0|3.0|bar|1.0")
def test_build_with_different_params(self):
model = cqgi.CQModel(TESTSCRIPT)
result = model.build({'height': 3.0})
self.assertTrue(result.results[0] == "3.0|3.0|bar|1.0")
def test_build_with_exception(self):
badscript = textwrap.dedent(
"""
raise ValueError("ERROR")
"""
)
model = cqgi.CQModel(badscript)
result = model.build({})
self.assertFalse(result.success)
self.assertIsNotNone(result.exception)
self.assertTrue(result.exception.message == "ERROR")
def test_that_invalid_syntax_in_script_fails_immediately(self):
badscript = textwrap.dedent(
"""
this doesnt even compile
"""
)
with self.assertRaises(Exception) as context:
model = cqgi.CQModel(badscript)
self.assertTrue('invalid syntax' in context.exception)
def test_that_two_results_are_returned(self):
script = textwrap.dedent(
"""
h = 1
build_object(h)
h = 2
build_object(h)
"""
)
model = cqgi.CQModel(script)
result = model.build({})
self.assertEquals(2, len(result.results))
self.assertEquals(1, result.results[0])
self.assertEquals(2, result.results[1])
def test_that_assinging_number_to_string_works(self):
script = textwrap.dedent(
"""
h = "this is a string"
build_object(h)
"""
)
result = cqgi.parse(script).build( {'h': 33.33})
self.assertEquals(result.results[0], "33.33")
def test_that_assigning_string_to_number_fails(self):
script = textwrap.dedent(
"""
h = 20.0
build_object(h)
"""
)
result = cqgi.parse(script).build( {'h': "a string"})
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
def test_that_assigning_unknown_var_fails(self):
script = textwrap.dedent(
"""
h = 20.0
build_object(h)
"""
)
result = cqgi.parse(script).build( {'w': "var is not there"})
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
def test_that_not_calling_build_object_raises_error(self):
script = textwrap.dedent(
"""
h = 20.0
"""
)
result = cqgi.parse(script).build()
self.assertTrue(isinstance(result.exception, cqgi.NoOutputError))
def test_that_cq_objects_are_visible(self):
script = textwrap.dedent(
"""
r = cadquery.Workplane('XY').box(1,2,3)
build_object(r)
"""
)
result = cqgi.parse(script).build()
self.assertTrue(result.success)
self.assertIsNotNone(result.first_result)
def test_setting_boolean_variable(self):
script = textwrap.dedent(
"""
h = True
build_object( "*%s*" % str(h) )
"""
)
#result = cqgi.execute(script)
result = cqgi.parse(script).build({'h': False})
self.assertTrue(result.success)
self.assertEquals(result.first_result,'*False*')
def test_that_only_top_level_vars_are_detected(self):
script = textwrap.dedent(
"""
h = 1.0
w = 2.0
def do_stuff():
x = 1
y = 2
build_object( "result" )
"""
)
model = cqgi.parse(script)
self.assertEquals(2, len(model.metadata.parameters))

View File

@ -1,358 +0,0 @@
__author__ = 'dcowden'
"""
Tests for CadQuery Selectors
These tests do not construct any solids, they test only selectors that query
an existing solid
"""
import math
import unittest,sys
import os.path
#my modules
from tests import BaseTest,makeUnitCube,makeUnitSquareWire
from cadquery import *
from cadquery import selectors
class TestCQSelectors(BaseTest):
def testWorkplaneCenter(self):
"Test Moving workplane center"
s = Workplane(Plane.XY())
#current point and world point should be equal
self.assertTupleAlmostEquals((0.0,0.0,0.0),s.plane.origin.toTuple(),3)
#move origin and confirm center moves
s.center(-2.0,-2.0)
#current point should be 0,0, but
self.assertTupleAlmostEquals((-2.0,-2.0,0.0),s.plane.origin.toTuple(),3)
def testVertices(self):
t = makeUnitSquareWire() # square box
c = CQ(t)
self.assertEqual(4,c.vertices().size() )
self.assertEqual(4,c.edges().size() )
self.assertEqual(0,c.vertices().edges().size() ) #no edges on any vertices
self.assertEqual(4,c.edges().vertices().size() ) #but selecting all edges still yields all vertices
self.assertEqual(1,c.wires().size()) #just one wire
self.assertEqual(0,c.faces().size())
self.assertEqual(0,c.vertices().faces().size()) #odd combinations all work but yield no results
self.assertEqual(0,c.edges().faces().size())
self.assertEqual(0,c.edges().vertices().faces().size())
def testEnd(self):
c = CQ(makeUnitSquareWire())
self.assertEqual(4,c.vertices().size() ) #4 because there are 4 vertices
self.assertEqual(1,c.vertices().end().size() ) #1 because we started with 1 wire
def testAll(self):
"all returns a list of CQ objects, so that you can iterate over them individually"
c = CQ(makeUnitCube())
self.assertEqual(6,c.faces().size())
self.assertEqual(6,len(c.faces().all()))
self.assertEqual(4,c.faces().all()[0].vertices().size() )
def testFirst(self):
c = CQ( makeUnitCube())
self.assertEqual(type(c.vertices().first().val()),Vertex)
self.assertEqual(type(c.vertices().first().first().first().val()),Vertex)
def testCompounds(self):
c = CQ(makeUnitSquareWire())
self.assertEqual(0,c.compounds().size() )
self.assertEqual(0,c.shells().size() )
self.assertEqual(0,c.solids().size() )
def testSolid(self):
c = CQ(makeUnitCube())
#make sure all the counts are right for a cube
self.assertEqual(1,c.solids().size() )
self.assertEqual(6,c.faces().size() )
self.assertEqual(12,c.edges().size())
self.assertEqual(8,c.vertices().size() )
self.assertEqual(0,c.compounds().size())
#now any particular face should result in 4 edges and four vertices
self.assertEqual(4,c.faces().first().edges().size() )
self.assertEqual(1,c.faces().first().size() )
self.assertEqual(4,c.faces().first().vertices().size() )
self.assertEqual(4,c.faces().last().edges().size() )
def testFaceTypesFilter(self):
"Filters by face type"
c = CQ(makeUnitCube())
self.assertEqual(c.faces().size(), c.faces('%PLANE').size())
self.assertEqual(c.faces().size(), c.faces('%plane').size())
self.assertEqual(0, c.faces('%sphere').size())
self.assertEqual(0, c.faces('%cone').size())
self.assertEqual(0, c.faces('%SPHERE').size())
def testPerpendicularDirFilter(self):
c = CQ(makeUnitCube())
self.assertEqual(8,c.edges("#Z").size() ) #8 edges are perp. to z
self.assertEqual(4, c.faces("#Z").size()) #4 faces are perp to z too!
def testFaceDirFilter(self):
c = CQ(makeUnitCube())
#a cube has one face in each direction
self.assertEqual(1, c.faces("+Z").size())
self.assertEqual(1, c.faces("-Z").size())
self.assertEqual(1, c.faces("+X").size())
self.assertEqual(1, c.faces("X").size()) #should be same as +X
self.assertEqual(1, c.faces("-X").size())
self.assertEqual(1, c.faces("+Y").size())
self.assertEqual(1, c.faces("-Y").size())
self.assertEqual(0, c.faces("XY").size())
def testParallelPlaneFaceFilter(self):
c = CQ(makeUnitCube())
#faces parallel to Z axis
self.assertEqual(2, c.faces("|Z").size())
#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(Vector((0,0,-1)))).size()) #same thing as above
#just for fun, vertices on faces parallel to z
self.assertEqual(8, c.faces("|Z").vertices().size())
def testParallelEdgeFilter(self):
c = CQ(makeUnitCube())
self.assertEqual(4, c.edges("|Z").size())
self.assertEqual(4, c.edges("|X").size())
self.assertEqual(4, c.edges("|Y").size())
def testMaxDistance(self):
c = CQ(makeUnitCube())
#should select the topmost face
self.assertEqual(1, c.faces(">Z").size())
self.assertEqual(4, c.faces(">Z").vertices().size())
#vertices should all be at z=1, if this is the top face
self.assertEqual(4, len(c.faces(">Z").vertices().vals() ))
for v in c.faces(">Z").vertices().vals():
self.assertAlmostEqual(1.0,v.Z,3)
# test the case of multiple objects at the same distance
el = c.edges("<Z").vals()
self.assertEqual(4, len(el))
def testMinDistance(self):
c = CQ(makeUnitCube())
#should select the topmost face
self.assertEqual(1, c.faces("<Z").size())
self.assertEqual(4, c.faces("<Z").vertices().size())
#vertices should all be at z=1, if this is the top face
self.assertEqual(4, len(c.faces("<Z").vertices().vals() ))
for v in c.faces("<Z").vertices().vals():
self.assertAlmostEqual(0.0,v.Z,3)
# test the case of multiple objects at the same distance
el = c.edges("<Z").vals()
self.assertEqual(4, len(el))
def testNearestTo(self):
c = CQ(makeUnitCube())
#nearest vertex to origin is (0,0,0)
t = (0.1,0.1,0.1)
v = c.vertices(selectors.NearestToPointSelector(t)).vals()[0]
self.assertTupleAlmostEquals((0.0,0.0,0.0),(v.X,v.Y,v.Z),3)
t = (0.1,0.1,0.2)
#nearest edge is the vertical side edge, 0,0,0 -> 0,0,1
e = c.edges(selectors.NearestToPointSelector(t)).vals()[0]
v = c.edges(selectors.NearestToPointSelector(t)).vertices().vals()
self.assertEqual(2,len(v))
#nearest solid is myself
s = c.solids(selectors.NearestToPointSelector(t)).vals()
self.assertEqual(1,len(s))
def testBox(self):
c = CQ(makeUnitCube())
# test vertice selection
test_data_vertices = [
# box point0, box point1, selected vertice
((0.9, 0.9, 0.9), (1.1, 1.1, 1.1), (1.0, 1.0, 1.0)),
((-0.1, 0.9, 0.9), (0.9, 1.1, 1.1), (0.0, 1.0, 1.0)),
((-0.1, -0.1, 0.9), (0.1, 0.1, 1.1), (0.0, 0.0, 1.0)),
((-0.1, -0.1, -0.1), (0.1, 0.1, 0.1), (0.0, 0.0, 0.0)),
((0.9, -0.1, -0.1), (1.1, 0.1, 0.1), (1.0, 0.0, 0.0)),
((0.9, 0.9, -0.1), (1.1, 1.1, 0.1), (1.0, 1.0, 0.0)),
((-0.1, 0.9, -0.1), (0.1, 1.1, 0.1), (0.0, 1.0, 0.0)),
((0.9, -0.1, 0.9), (1.1, 0.1, 1.1), (1.0, 0.0, 1.0))
]
for d in test_data_vertices:
vl = c.vertices(selectors.BoxSelector(d[0], d[1])).vals()
self.assertEqual(1, len(vl))
v = vl[0]
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
# this time box points are swapped
vl = c.vertices(selectors.BoxSelector(d[1], d[0])).vals()
self.assertEqual(1, len(vl))
v = vl[0]
self.assertTupleAlmostEquals(d[2], (v.X, v.Y, v.Z), 3)
# test multiple vertices selection
vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, 0.9),(0.1, 1.1, 1.1))).vals()
self.assertEqual(2, len(vl))
vl = c.vertices(selectors.BoxSelector((-0.1, -0.1, -0.1),(0.1, 1.1, 1.1))).vals()
self.assertEqual(4, len(vl))
# test edge selection
test_data_edges = [
# box point0, box point1, edge center
((0.4, -0.1, -0.1), (0.6, 0.1, 0.1), (0.5, 0.0, 0.0)),
((-0.1, -0.1, 0.4), (0.1, 0.1, 0.6), (0.0, 0.0, 0.5)),
((0.9, 0.9, 0.4), (1.1, 1.1, 0.6), (1.0, 1.0, 0.5)),
((0.4, 0.9, 0.9), (0.6, 1.1, 1.1,), (0.5, 1.0, 1.0))
]
for d in test_data_edges:
el = c.edges(selectors.BoxSelector(d[0], d[1])).vals()
self.assertEqual(1, len(el))
ec = el[0].Center()
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
# test again by swapping box points
el = c.edges(selectors.BoxSelector(d[1], d[0])).vals()
self.assertEqual(1, len(el))
ec = el[0].Center()
self.assertTupleAlmostEquals(d[2], (ec.x, ec.y, ec.z), 3)
# test multiple edge selection
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (0.6, 0.1, 0.6))).vals()
self.assertEqual(2, len(el))
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6))).vals()
self.assertEqual(3, len(el))
# test face selection
test_data_faces = [
# box point0, box point1, face center
((0.4, -0.1, 0.4), (0.6, 0.1, 0.6), (0.5, 0.0, 0.5)),
((0.9, 0.4, 0.4), (1.1, 0.6, 0.6), (1.0, 0.5, 0.5)),
((0.4, 0.4, 0.9), (0.6, 0.6, 1.1), (0.5, 0.5, 1.0)),
((0.4, 0.4, -0.1), (0.6, 0.6, 0.1), (0.5, 0.5, 0.0))
]
for d in test_data_faces:
fl = c.faces(selectors.BoxSelector(d[0], d[1])).vals()
self.assertEqual(1, len(fl))
fc = fl[0].Center()
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
# test again by swapping box points
fl = c.faces(selectors.BoxSelector(d[1], d[0])).vals()
self.assertEqual(1, len(fl))
fc = fl[0].Center()
self.assertTupleAlmostEquals(d[2], (fc.x, fc.y, fc.z), 3)
# test multiple face selection
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (0.6, 1.1, 1.1))).vals()
self.assertEqual(2, len(fl))
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1))).vals()
self.assertEqual(3, len(fl))
# test boundingbox option
el = c.edges(selectors.BoxSelector((-0.1, -0.1, -0.1), (1.1, 0.1, 0.6), True)).vals()
self.assertEqual(1, len(el))
fl = c.faces(selectors.BoxSelector((0.4, 0.4, 0.4), (1.1, 1.1, 1.1), True)).vals()
self.assertEqual(0, len(fl))
fl = c.faces(selectors.BoxSelector((-0.1, 0.4, -0.1), (1.1, 1.1, 1.1), True)).vals()
self.assertEqual(1, len(fl))
def testAndSelector(self):
c = CQ(makeUnitCube())
S = selectors.StringSyntaxSelector
BS = selectors.BoxSelector
el = c.edges(selectors.AndSelector(S('|X'), BS((-2,-2,0.1), (2,2,2)))).vals()
self.assertEqual(2, len(el))
# test 'and' (intersection) operator
el = c.edges(S('|X') & BS((-2,-2,0.1), (2,2,2))).vals()
self.assertEqual(2, len(el))
def testSumSelector(self):
c = CQ(makeUnitCube())
S = selectors.StringSyntaxSelector
fl = c.faces(selectors.SumSelector(S(">Z"), S("<Z"))).vals()
self.assertEqual(2, len(fl))
el = c.edges(selectors.SumSelector(S("|X"), S("|Y"))).vals()
self.assertEqual(8, len(el))
# test the sum operator
fl = c.faces(S(">Z") + S("<Z")).vals()
self.assertEqual(2, len(fl))
el = c.edges(S("|X") + S("|Y")).vals()
self.assertEqual(8, len(el))
def testSubtractSelector(self):
c = CQ(makeUnitCube())
S = selectors.StringSyntaxSelector
fl = c.faces(selectors.SubtractSelector(S("#Z"), S(">X"))).vals()
self.assertEqual(3, len(fl))
# test the subtract operator
fl = c.faces(S("#Z") - S(">X")).vals()
self.assertEqual(3, len(fl))
def testInverseSelector(self):
c = CQ(makeUnitCube())
S = selectors.StringSyntaxSelector
fl = c.faces(selectors.InverseSelector(S('>Z'))).vals()
self.assertEqual(5, len(fl))
el = c.faces('>Z').edges(selectors.InverseSelector(S('>X'))).vals()
self.assertEqual(3, len(el))
# test invert operator
fl = c.faces(-S('>Z')).vals()
self.assertEqual(5, len(fl))
el = c.faces('>Z').edges(-S('>X')).vals()
self.assertEqual(3, len(el))
def testFaceCount(self):
c = CQ(makeUnitCube())
self.assertEqual( 6, c.faces().size() )
self.assertEqual( 2, c.faces("|Z").size() )
def testVertexFilter(self):
"test selecting vertices on a face"
c = CQ(makeUnitCube())
#TODO: filters work ok, but they are in global coordinates which sux. it would be nice
#if they were available in coordinates local to the selected face
v2 = c.faces("+Z").vertices("<XY")
self.assertEqual(1,v2.size() ) #another way
#make sure the vertex is the right one
self.assertTupleAlmostEquals((0.0,0.0,1.0),v2.val().toTuple() ,3)

View File

@ -1,86 +0,0 @@
#system modules
import sys
import unittest
from tests import BaseTest
import FreeCAD
import Part
from cadquery import *
class TestCadObjects(BaseTest):
def testVectorConstructors(self):
v1 = Vector(1, 2, 3)
v2 = Vector((1, 2, 3))
v3 = Vector(FreeCAD.Base.Vector(1, 2, 3))
for v in [v1, v2, v3]:
self.assertTupleAlmostEquals((1, 2, 3), v.toTuple(), 4)
def testVertex(self):
"""
Tests basic vertex functions
"""
v = Vertex(Part.Vertex(1, 1, 1))
self.assertEqual(1, v.X)
self.assertEquals(Vector, type(v.Center()))
def testBasicBoundingBox(self):
v = Vertex(Part.Vertex(1, 1, 1))
v2 = Vertex(Part.Vertex(2, 2, 2))
self.assertEquals(BoundBox, type(v.BoundingBox()))
self.assertEquals(BoundBox, type(v2.BoundingBox()))
bb1 = v.BoundingBox().add(v2.BoundingBox())
self.assertEquals(bb1.xlen, 1.0)
def testEdgeWrapperCenter(self):
e = Edge(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
def testCompoundCenter(self):
"""
Tests whether or not a proper weighted center can be found for a compound
"""
def cylinders(self, radius, height):
def _cyl(pnt):
# Inner function to build a cylinder
return Solid.makeCylinder(radius, height, pnt)
# Combine all the cylinders into a single compound
r = self.eachpoint(_cyl, True).combineSolids()
return r
Workplane.cyl = cylinders
# 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)
self.assertEquals(4, len(s.val().Solids()))
self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
def testDot(self):
v1 = Vector(2, 2, 2)
v2 = Vector(1, -1, 1)
self.assertEquals(2.0, v1.dot(v2))
def testVectorAdd(self):
result = Vector(1, 2, 0) + Vector(0, 0, 3)
self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
def testTranslate(self):
e = Shape.cast(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
e2 = e.translate(Vector(0, 0, 1))
self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
def testVertices(self):
e = Shape.cast(Part.makeLine((0, 0, 0), (1, 1, 0)))
self.assertEquals(2, len(e.Vertices()))
if __name__ == '__main__':
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
"""
Tests basic workplane functionality
"""
#core modules
import StringIO
#my modules
from cadquery import *
from cadquery import exporters
from tests import BaseTest
class TestExporters(BaseTest):
def _exportBox(self,eType,stringsToFind):
"""
Exports a test object, and then looks for
all of the supplied strings to be in the result
returns the result in case the case wants to do more checks also
"""
p = Workplane("XY").box(1,2,3)
s = StringIO.StringIO()
exporters.exportShape(p,eType,s,0.1)
result = s.getvalue()
#print result
for q in stringsToFind:
self.assertTrue(result.find(q) > -1 )
return result
def testSTL(self):
self._exportBox(exporters.ExportTypes.STL,['facet normal'])
def testSVG(self):
self._exportBox(exporters.ExportTypes.SVG,['<svg','<g transform'])
def testAMF(self):
self._exportBox(exporters.ExportTypes.AMF,['<amf units','</object>'])
def testSTEP(self):
self._exportBox(exporters.ExportTypes.STEP,['FILE_SCHEMA'])
def testTJS(self):
self._exportBox(exporters.ExportTypes.TJS,['vertices','formatVersion','faces'])

View File

@ -1,54 +0,0 @@
"""
Tests file importers such as STEP
"""
#core modules
import StringIO
from cadquery import *
from cadquery import exporters
from cadquery import importers
from tests import BaseTest
#where unit test output will be saved
import sys
if sys.platform.startswith("win"):
OUTDIR = "c:/temp"
else:
OUTDIR = "/tmp"
class TestImporters(BaseTest):
def importBox(self, importType, fileName):
"""
Exports a simple box to a STEP file and then imports it again
:param importType: The type of file we're importing (STEP, STL, etc)
:param fileName: The path and name of the file to write to
"""
#We're importing a STEP file
if importType == importers.ImportTypes.STEP:
#We first need to build a simple shape to export
shape = Workplane("XY").box(1, 2, 3).val()
#Export the shape to a temporary file
shape.exportStep(fileName)
# Reimport the shape from the new STEP file
importedShape = importers.importShape(importType,fileName)
#Check to make sure we got a solid back
self.assertTrue(importedShape.val().ShapeType() == "Solid")
#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("+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):
"""
Tests STEP file import
"""
self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step")
if __name__ == '__main__':
import unittest
unittest.main()

View File

@ -1,125 +0,0 @@
"""
Tests basic workplane functionality
"""
#core modules
#my modules
from cadquery import *
from tests import BaseTest,toTuple
xAxis_ = Vector(1, 0, 0)
yAxis_ = Vector(0, 1, 0)
zAxis_ = Vector(0, 0, 1)
xInvAxis_ = Vector(-1, 0, 0)
yInvAxis_ = Vector(0, -1, 0)
zInvAxis_ = Vector(0, 0, -1)
class TestWorkplanes(BaseTest):
def testYZPlaneOrigins(self):
#xy plane-- with origin at x=0.25
base = Vector(0.25,0,0)
p = Plane(base, Vector(0,1,0), Vector(1,0,0))
#origin is always (0,0,0) in local coordinates
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
#(0,0,0) is always the original base in global coordinates
self.assertTupleAlmostEquals(base.toTuple(), p.toWorldCoords((0,0)).toTuple() ,2 )
def testXYPlaneOrigins(self):
base = Vector(0,0,0.25)
p = Plane(base, Vector(1,0,0), Vector(0,0,1))
#origin is always (0,0,0) in local coordinates
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
#(0,0,0) is always the original base in global coordinates
self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 )
def testXZPlaneOrigins(self):
base = Vector(0,0.25,0)
p = Plane(base, Vector(0,0,1), Vector(0,1,0))
#(0,0,0) is always the original base in global coordinates
self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 )
#origin is always (0,0,0) in local coordinates
self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 )
def testPlaneBasics(self):
p = Plane.XY()
#local to world
self.assertTupleAlmostEquals((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
self.assertTupleAlmostEquals((-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()
self.assertTupleAlmostEquals((0,1.0,1.0),p.toWorldCoords((1,1)).toTuple() ,2 )
#world to local
self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(0,1,1)).toTuple() ,2 )
p = Plane.XZ()
r = p.toWorldCoords((1,1)).toTuple()
self.assertTupleAlmostEquals((1.0,0.0,1.0),r ,2 )
#world to local
self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,0,1)).toTuple() ,2 )
def testOffsetPlanes(self):
"Tests that a plane offset from the origin works ok too"
p = Plane.XY(origin=(10.0,10.0,0))
self.assertTupleAlmostEquals((11.0,11.0,0.0),p.toWorldCoords((1.0,1.0)).toTuple(),2 )
self.assertTupleAlmostEquals((2.0,2.0), p.toLocalCoords(Vector(12.0,12.0,0)).toTuple() ,2 )
#TODO test these offsets in the other dimensions too
p = Plane.YZ(origin=(0,2,2))
self.assertTupleAlmostEquals((0.0,5.0,5.0), p.toWorldCoords((3.0,3.0)).toTuple() ,2 )
self.assertTupleAlmostEquals((10,10.0,0.0), p.toLocalCoords(Vector(0.0,12.0,12.0)).toTuple() ,2 )
p = Plane.XZ(origin=(2,0,2))
r = p.toWorldCoords((1.0,1.0)).toTuple()
self.assertTupleAlmostEquals((3.0,0.0,3.0),r ,2 )
self.assertTupleAlmostEquals((10.0,10.0), p.toLocalCoords(Vector(12.0,0.0,12.0)).toTuple() ,2 )
def testXYPlaneBasics(self):
p = Plane.named('XY')
self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)
def testYZPlaneBasics(self):
p = Plane.named('YZ')
self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
def testZXPlaneBasics(self):
p = Plane.named('ZX')
self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
def testXZPlaneBasics(self):
p = Plane.named('XZ')
self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4)
def testYXPlaneBasics(self):
p = Plane.named('YX')
self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4)
def testZYPlaneBasics(self):
p = Plane.named('ZY')
self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4)
self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4)

View File

@ -1,54 +0,0 @@
from cadquery import *
import unittest
import sys
import os
import FreeCAD
import Part as P
from FreeCAD import Vector as V
def readFileAsString(fileName):
f= open(fileName, 'r')
s = f.read()
f.close()
return s
def writeStringToFile(strToWrite, fileName):
f = open(fileName, 'w')
f.write(strToWrite)
f.close()
def makeUnitSquareWire():
return Solid.cast(P.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)]))
def makeUnitCube():
return makeCube(1.0)
def makeCube(size):
return Solid.makeBox(size, size, size)
def toTuple(v):
"""convert a vector or a vertex to a 3-tuple: x,y,z"""
pnt = v
if type(v) == FreeCAD.Base.Vector:
return (v.Point.x, v.Point.y, v.Point.z)
elif type(v) == Vector:
return v.toTuple()
else:
raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)) )
class BaseTest(unittest.TestCase):
def assertTupleAlmostEquals(self, expected, actual, places):
for i, j in zip(actual, expected):
self.assertAlmostEquals(i, j, places)
__all__ = ['TestCadObjects', 'TestCadQuery', 'TestCQSelectors', 'TestWorkplanes', 'TestExporters', 'TestCQSelectors', 'TestImporters','TestCQGI']