fixed setup.py issue
This commit is contained in:
parent
3a7ff7eaf1
commit
7384d9b046
21
build/lib.linux-i686-2.7/cadquery/__init__.py
Normal file
21
build/lib.linux-i686-2.7/cadquery/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
#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"
|
18
build/lib.linux-i686-2.7/cadquery/contrib/__init__.py
Normal file
18
build/lib.linux-i686-2.7/cadquery/contrib/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
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/>
|
||||
"""
|
2467
build/lib.linux-i686-2.7/cadquery/cq.py
Normal file
2467
build/lib.linux-i686-2.7/cadquery/cq.py
Normal file
File diff suppressed because it is too large
Load Diff
85
build/lib.linux-i686-2.7/cadquery/cq_directive.py
Normal file
85
build/lib.linux-i686-2.7/cadquery/cq_directive.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
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)
|
425
build/lib.linux-i686-2.7/cadquery/cqgi.py
Normal file
425
build/lib.linux-i686-2.7/cadquery/cqgi.py
Normal file
|
@ -0,0 +1,425 @@
|
|||
"""
|
||||
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
|
112
build/lib.linux-i686-2.7/cadquery/freecad_impl/__init__.py
Normal file
112
build/lib.linux-i686-2.7/cadquery/freecad_impl/__init__.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
"""
|
||||
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
|
392
build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py
Normal file
392
build/lib.linux-i686-2.7/cadquery/freecad_impl/exporters.py
Normal file
|
@ -0,0 +1,392 @@
|
|||
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"
|
||||
|
647
build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py
Normal file
647
build/lib.linux-i686-2.7/cadquery/freecad_impl/geom.py
Normal file
|
@ -0,0 +1,647 @@
|
|||
"""
|
||||
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)
|
71
build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py
Normal file
71
build/lib.linux-i686-2.7/cadquery/freecad_impl/importers.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
|
||||
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")
|
982
build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py
Normal file
982
build/lib.linux-i686-2.7/cadquery/freecad_impl/shapes.py
Normal file
|
@ -0,0 +1,982 @@
|
|||
"""
|
||||
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
|
18
build/lib.linux-i686-2.7/cadquery/plugins/__init__.py
Normal file
18
build/lib.linux-i686-2.7/cadquery/plugins/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
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
|
||||
"""
|
474
build/lib.linux-i686-2.7/cadquery/selectors.py
Normal file
474
build/lib.linux-i686-2.7/cadquery/selectors.py
Normal file
|
@ -0,0 +1,474 @@
|
|||
"""
|
||||
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)
|
170
build/lib.linux-i686-2.7/tests/TestCQGI.py
Normal file
170
build/lib.linux-i686-2.7/tests/TestCQGI.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
"""
|
||||
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))
|
358
build/lib.linux-i686-2.7/tests/TestCQSelectors.py
Normal file
358
build/lib.linux-i686-2.7/tests/TestCQSelectors.py
Normal file
|
@ -0,0 +1,358 @@
|
|||
__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)
|
86
build/lib.linux-i686-2.7/tests/TestCadObjects.py
Normal file
86
build/lib.linux-i686-2.7/tests/TestCadObjects.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
#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()
|
1298
build/lib.linux-i686-2.7/tests/TestCadQuery.py
Normal file
1298
build/lib.linux-i686-2.7/tests/TestCadQuery.py
Normal file
File diff suppressed because it is too large
Load Diff
43
build/lib.linux-i686-2.7/tests/TestExporters.py
Normal file
43
build/lib.linux-i686-2.7/tests/TestExporters.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
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'])
|
54
build/lib.linux-i686-2.7/tests/TestImporters.py
Normal file
54
build/lib.linux-i686-2.7/tests/TestImporters.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
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()
|
125
build/lib.linux-i686-2.7/tests/TestWorkplanes.py
Normal file
125
build/lib.linux-i686-2.7/tests/TestWorkplanes.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
"""
|
||||
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)
|
54
build/lib.linux-i686-2.7/tests/__init__.py
Normal file
54
build/lib.linux-i686-2.7/tests/__init__.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
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']
|
|
@ -1,14 +1,19 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: cadquery
|
||||
Version: 0.3.0
|
||||
Version: 0.4.0
|
||||
Summary: CadQuery is a parametric scripting language for creating and traversing CAD models
|
||||
Home-page: https://github.com/dcowden/cadquery
|
||||
Author: David Cowden
|
||||
Author-email: dave.cowden@gmail.com
|
||||
License: Apache Public License
|
||||
License: Apache Public License 2.0
|
||||
Description: What is a CadQuery?
|
||||
========================================
|
||||
|
||||
[](https://travis-ci.org/dcowden/cadquery)
|
||||
[](https://coveralls.io/r/dcowden/cadquery)
|
||||
[](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
|
||||
[](https://github.com/dcowden/cadquery/blob/master/LICENSE)
|
||||
|
||||
CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
|
||||
|
||||
CadQuery has several goals:
|
||||
|
@ -20,18 +25,24 @@ Description: What is a CadQuery?
|
|||
|
||||
Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized.
|
||||
|
||||
Full Documentation
|
||||
============================
|
||||
You can find the full cadquery documentation at http://dcowden.github.io/cadquery
|
||||
|
||||
|
||||
Getting Started With CadQuery
|
||||
========================================
|
||||
|
||||
The easiest way to get started with CadQuery is to Install FreeCAD ( version 14 recommended ) (http://www.freecadweb.org/) , and then to use our CadQuery-FreeCAD plugin here:
|
||||
|
||||
https://github.com/jmwright/cadquery-freecad-module
|
||||
The easiest way to get started with CadQuery is to Install FreeCAD (version 14+) (http://www.freecadweb.org/), and then to use our great CadQuery-FreeCAD plugin here: https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
|
||||
It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
|
||||
|
||||
It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
|
||||
|
||||
We also have a Google Group to make it easy to get help from other CadQuery users. Please join the group and introduce yourself, and we would also love to hear what you are doing with CadQuery. https://groups.google.com/forum/#!forum/cadquery
|
||||
|
||||
|
||||
|
||||
Why CadQuery instead of OpenSCAD?
|
||||
========================================
|
||||
|
@ -55,12 +66,13 @@ Description: What is a CadQuery?
|
|||
4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
|
||||
features based on the position of other features, workplanes, vertices, etc.
|
||||
|
||||
5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
|
||||
5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
CadQuery is licensed under the terms of the Apache Public License, v 2.0 http://www.apache.org/licenses/LICENSE-2.0
|
||||
CadQuery is licensed under the terms of the Apache Public License, version 2.0.
|
||||
A copy of the license can be found at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Where is the GUI?
|
||||
==================
|
||||
|
@ -74,22 +86,22 @@ Description: What is a CadQuery?
|
|||
|
||||
Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
|
||||
|
||||
1. install FreeCAD, version 0.14 or greater for your platform. http://sourceforge.net/projects/free-cad/.
|
||||
1. install FreeCAD, version 0.12 or greater for your platform. http://sourceforge.net/projects/free-cad/.
|
||||
|
||||
2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
|
||||
2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
|
||||
preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
|
||||
lib directory to your path. On (*Nix)::
|
||||
|
||||
|
||||
import sys
|
||||
sys.path.append('/usr/lib/freecad/lib')
|
||||
|
||||
|
||||
or on Windows::
|
||||
|
||||
|
||||
import sys
|
||||
sys.path.append('/c/apps/FreeCAD/bin')
|
||||
|
||||
|
||||
*NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
|
||||
|
||||
|
||||
3. install cadquery::
|
||||
|
||||
pip install cadquery
|
||||
|
@ -99,9 +111,9 @@ Description: What is a CadQuery?
|
|||
from cadquery import *
|
||||
box = Workplane("XY").box(1,2,3)
|
||||
exporters.toString(box,'STL')
|
||||
|
||||
|
||||
You're up and running!
|
||||
|
||||
|
||||
Installing -- Using CadQuery from Inside FreeCAD
|
||||
=================================================
|
||||
|
||||
|
@ -120,7 +132,7 @@ Description: What is a CadQuery?
|
|||
|
||||
* A fluent api to create clean, easy to read code
|
||||
* Language features that make selection and iteration incredibly easy
|
||||
*
|
||||
*
|
||||
* Ability to use the library along side other python libraries
|
||||
* Clear and complete documentation, with plenty of samples.
|
||||
|
||||
|
@ -132,7 +144,7 @@ Classifier: Intended Audience :: End Users/Desktop
|
|||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: Intended Audience :: Science/Research
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: MacOS
|
||||
Classifier: Operating System :: Unix
|
||||
|
|
|
@ -2,9 +2,10 @@ MANIFEST.in
|
|||
README.txt
|
||||
setup.cfg
|
||||
setup.py
|
||||
cadquery/CQ.py
|
||||
cadquery/__init__.py
|
||||
cadquery/cq.py
|
||||
cadquery/cq_directive.py
|
||||
cadquery/cqgi.py
|
||||
cadquery/selectors.py
|
||||
cadquery.egg-info/PKG-INFO
|
||||
cadquery.egg-info/SOURCES.txt
|
||||
|
@ -18,6 +19,7 @@ cadquery/freecad_impl/geom.py
|
|||
cadquery/freecad_impl/importers.py
|
||||
cadquery/freecad_impl/shapes.py
|
||||
cadquery/plugins/__init__.py
|
||||
tests/TestCQGI.py
|
||||
tests/TestCQSelectors.py
|
||||
tests/TestCadObjects.py
|
||||
tests/TestCadQuery.py
|
||||
|
|
2
setup.py
2
setup.py
|
@ -23,7 +23,7 @@ setup(
|
|||
author_email='dave.cowden@gmail.com',
|
||||
description='CadQuery is a parametric scripting language for creating and traversing CAD models',
|
||||
long_description=open('README.md').read(),
|
||||
packages=['cadquery','cadquery.contrib','cadquery.freecad_impl','cadquery.plugins','cadquery.cqgi','tests'],
|
||||
packages=['cadquery','cadquery.contrib','cadquery.freecad_impl','cadquery.plugins','tests'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
|
|
Loading…
Reference in New Issue
Block a user