Merge branch 'master' into OCP
This commit is contained in:
@ -1,25 +1,65 @@
|
||||
# these items point to the OCC implementation
|
||||
from .occ_impl.geom import Plane, BoundBox, Vector, Matrix
|
||||
from .occ_impl.shapes import (Shape, Vertex, Edge, Face, Wire, Solid, Shell,
|
||||
Compound, sortWiresByBuildOrder)
|
||||
from .occ_impl.shapes import (
|
||||
Shape,
|
||||
Vertex,
|
||||
Edge,
|
||||
Face,
|
||||
Wire,
|
||||
Solid,
|
||||
Shell,
|
||||
Compound,
|
||||
sortWiresByBuildOrder,
|
||||
)
|
||||
from .occ_impl import exporters
|
||||
from .occ_impl import importers
|
||||
|
||||
# these items are the common implementation
|
||||
|
||||
# the order of these matter
|
||||
from .selectors import (NearestToPointSelector, ParallelDirSelector,
|
||||
DirectionSelector, PerpendicularDirSelector, TypeSelector,
|
||||
DirectionMinMaxSelector, StringSyntaxSelector, Selector)
|
||||
from .selectors import (
|
||||
NearestToPointSelector,
|
||||
ParallelDirSelector,
|
||||
DirectionSelector,
|
||||
PerpendicularDirSelector,
|
||||
TypeSelector,
|
||||
DirectionMinMaxSelector,
|
||||
StringSyntaxSelector,
|
||||
Selector,
|
||||
)
|
||||
from .cq import CQ, Workplane, selectors
|
||||
from . import plugins
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CQ', 'Workplane', 'plugins', 'selectors', 'Plane', 'BoundBox', 'Matrix', 'Vector', 'sortWiresByBuildOrder',
|
||||
'Shape', 'Vertex', 'Edge', 'Wire', 'Face', 'Solid', 'Shell', 'Compound', 'exporters', 'importers',
|
||||
'NearestToPointSelector', 'ParallelDirSelector', 'DirectionSelector', 'PerpendicularDirSelector',
|
||||
'TypeSelector', 'DirectionMinMaxSelector', 'StringSyntaxSelector', 'Selector', 'plugins'
|
||||
"CQ",
|
||||
"Workplane",
|
||||
"plugins",
|
||||
"selectors",
|
||||
"Plane",
|
||||
"BoundBox",
|
||||
"Matrix",
|
||||
"Vector",
|
||||
"sortWiresByBuildOrder",
|
||||
"Shape",
|
||||
"Vertex",
|
||||
"Edge",
|
||||
"Wire",
|
||||
"Face",
|
||||
"Solid",
|
||||
"Shell",
|
||||
"Compound",
|
||||
"exporters",
|
||||
"importers",
|
||||
"NearestToPointSelector",
|
||||
"ParallelDirSelector",
|
||||
"DirectionSelector",
|
||||
"PerpendicularDirSelector",
|
||||
"TypeSelector",
|
||||
"DirectionMinMaxSelector",
|
||||
"StringSyntaxSelector",
|
||||
"Selector",
|
||||
"plugins",
|
||||
]
|
||||
|
||||
__version__ = "2.0.0dev"
|
||||
__version__ = "2.0RC1"
|
||||
|
790
cadquery/cq.py
790
cadquery/cq.py
File diff suppressed because it is too large
Load Diff
@ -20,13 +20,22 @@ template = """
|
||||
</div>
|
||||
|
||||
"""
|
||||
template_content_indent = ' '
|
||||
template_content_indent = " "
|
||||
|
||||
|
||||
def cq_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
def cq_directive(
|
||||
name,
|
||||
arguments,
|
||||
options,
|
||||
content,
|
||||
lineno,
|
||||
content_offset,
|
||||
block_text,
|
||||
state,
|
||||
state_machine,
|
||||
):
|
||||
# only consider inline snippets
|
||||
plot_code = '\n'.join(content)
|
||||
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
|
||||
@ -52,22 +61,20 @@ def cq_directive(name, arguments, options, content, lineno,
|
||||
lines = []
|
||||
|
||||
# get rid of new lines
|
||||
out_svg = out_svg.replace('\n', '')
|
||||
out_svg = out_svg.replace("\n", "")
|
||||
|
||||
txt_align = "left"
|
||||
if "align" in options:
|
||||
txt_align = options['align']
|
||||
txt_align = options["align"]
|
||||
|
||||
lines.extend((template % locals()).split('\n'))
|
||||
lines.extend((template % locals()).split("\n"))
|
||||
|
||||
lines.extend(['::', ''])
|
||||
lines.extend([' %s' % row.rstrip()
|
||||
for row in plot_code.split('\n')])
|
||||
lines.append('')
|
||||
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))
|
||||
state_machine.insert_input(lines, state_machine.input_lines.source(0))
|
||||
|
||||
return []
|
||||
|
||||
@ -77,9 +84,10 @@ def setup(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
|
||||
}
|
||||
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)
|
||||
app.add_directive("cq_plot", cq_directive, True, (0, 2, 0), **options)
|
||||
|
142
cadquery/cqgi.py
142
cadquery/cqgi.py
@ -9,6 +9,7 @@ import cadquery
|
||||
|
||||
CQSCRIPT = "<cqscript>"
|
||||
|
||||
|
||||
def parse(script_source):
|
||||
"""
|
||||
Parses the script as a model, and returns a model.
|
||||
@ -34,6 +35,7 @@ class CQModel(object):
|
||||
|
||||
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.
|
||||
@ -100,16 +102,20 @@ class CQModel(object):
|
||||
try:
|
||||
self.set_param_values(build_parameters)
|
||||
collector = ScriptCallback()
|
||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||
.add_entry("__name__", "__cqgi__") \
|
||||
.add_entry("show_object", collector.show_object) \
|
||||
.add_entry("debug", collector.debug) \
|
||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
||||
env = (
|
||||
EnvironmentBuilder()
|
||||
.with_real_builtins()
|
||||
.with_cadquery_objects()
|
||||
.add_entry("__name__", "__cqgi__")
|
||||
.add_entry("show_object", collector.show_object)
|
||||
.add_entry("debug", collector.debug)
|
||||
.add_entry("describe_parameter", collector.describe_parameter)
|
||||
.build()
|
||||
)
|
||||
|
||||
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
||||
exec (c, env)
|
||||
result.set_debug(collector.debugObjects )
|
||||
c = compile(self.ast_tree, CQSCRIPT, "exec")
|
||||
exec(c, env)
|
||||
result.set_debug(collector.debugObjects)
|
||||
result.set_success_result(collector.outputObjects)
|
||||
|
||||
except Exception as ex:
|
||||
@ -124,7 +130,9 @@ class CQModel(object):
|
||||
|
||||
for k, v in params.items():
|
||||
if k not in model_parameters:
|
||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '%s': not a parameter of the model." % k
|
||||
)
|
||||
|
||||
p = model_parameters[k]
|
||||
p.set_value(v)
|
||||
@ -134,10 +142,12 @@ class ShapeResult(object):
|
||||
"""
|
||||
An object created by a build, including the user parameters provided
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.shape = None
|
||||
self.options = None
|
||||
|
||||
|
||||
class BuildResult(object):
|
||||
"""
|
||||
The result of executing a CadQuery script.
|
||||
@ -149,10 +159,11 @@ class BuildResult(object):
|
||||
If unsuccessful, the exception property contains a reference to
|
||||
the stack trace that occurred.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.buildTime = None
|
||||
self.results = [] #list of ShapeResult
|
||||
self.debugObjects = [] #list of ShapeResult
|
||||
self.results = [] # list of ShapeResult
|
||||
self.debugObjects = [] # list of ShapeResult
|
||||
self.first_result = None
|
||||
self.success = False
|
||||
self.exception = None
|
||||
@ -176,13 +187,14 @@ 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
|
||||
|
||||
def add_parameter_description(self,name,description):
|
||||
def add_parameter_description(self, name, description):
|
||||
p = self.parameters[name]
|
||||
p.desc = description
|
||||
|
||||
@ -214,6 +226,7 @@ class InputParameter:
|
||||
provide additional metadata
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
#: the default value for the variable.
|
||||
@ -234,7 +247,9 @@ class InputParameter:
|
||||
self.ast_node = None
|
||||
|
||||
@staticmethod
|
||||
def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
|
||||
def create(
|
||||
ast_node, var_name, var_type, default_value, valid_values=None, desc=None
|
||||
):
|
||||
|
||||
if valid_values is None:
|
||||
valid_values = []
|
||||
@ -251,8 +266,10 @@ class InputParameter:
|
||||
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)))
|
||||
"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:
|
||||
@ -265,28 +282,33 @@ class InputParameter:
|
||||
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))
|
||||
"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:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = True
|
||||
else:
|
||||
self.ast_node.id = 'True'
|
||||
self.ast_node.id = "True"
|
||||
else:
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
self.ast_node.value = False
|
||||
else:
|
||||
self.ast_node.id = 'False'
|
||||
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))
|
||||
self.name,
|
||||
str(self.varType),
|
||||
str(self.default_value),
|
||||
)
|
||||
|
||||
|
||||
class ScriptCallback(object):
|
||||
@ -295,22 +317,23 @@ class ScriptCallback(object):
|
||||
the show_object() method is exposed to CQ scripts, to allow them
|
||||
to return objects to the execution environment
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.outputObjects = []
|
||||
self.debugObjects = []
|
||||
|
||||
def show_object(self, shape,options={}):
|
||||
def show_object(self, shape, options={}):
|
||||
"""
|
||||
return an object to the executing environment, with options
|
||||
:param shape: a cadquery object
|
||||
:param options: a dictionary of options that will be made available to the executing environment
|
||||
"""
|
||||
o = ShapeResult()
|
||||
o.options=options
|
||||
o.options = options
|
||||
o.shape = shape
|
||||
self.outputObjects.append(o)
|
||||
|
||||
def debug(self,obj,args={}):
|
||||
def debug(self, obj, args={}):
|
||||
"""
|
||||
Debug print/output an object, with optional arguments.
|
||||
"""
|
||||
@ -319,7 +342,7 @@ class ScriptCallback(object):
|
||||
s.options = args
|
||||
self.debugObjects.append(s)
|
||||
|
||||
def describe_parameter(self,var_data ):
|
||||
def describe_parameter(self, var_data):
|
||||
"""
|
||||
Do Nothing-- we parsed the ast ahead of execution to get what we need.
|
||||
"""
|
||||
@ -335,12 +358,12 @@ class ScriptCallback(object):
|
||||
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
|
||||
|
||||
|
||||
@ -349,6 +372,7 @@ class NoOutputError(Exception):
|
||||
Raised when the script does not execute the show_object() method to
|
||||
return a solid
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -386,6 +410,7 @@ class EnvironmentBuilder(object):
|
||||
The environment includes the builtins, as well as
|
||||
the other methods the script will need.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
@ -393,12 +418,12 @@ class EnvironmentBuilder(object):
|
||||
return self.with_builtins(__builtins__)
|
||||
|
||||
def with_builtins(self, env_dict):
|
||||
self.env['__builtins__'] = env_dict
|
||||
self.env["__builtins__"] = env_dict
|
||||
return self
|
||||
|
||||
def with_cadquery_objects(self):
|
||||
self.env['cadquery'] = cadquery
|
||||
self.env['cq'] = cadquery
|
||||
self.env["cadquery"] = cadquery
|
||||
self.env["cq"] = cadquery
|
||||
return self
|
||||
|
||||
def add_entry(self, name, value):
|
||||
@ -408,30 +433,33 @@ class EnvironmentBuilder(object):
|
||||
def build(self):
|
||||
return self.env
|
||||
|
||||
|
||||
class ParameterDescriptionFinder(ast.NodeTransformer):
|
||||
"""
|
||||
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
||||
"""
|
||||
|
||||
def __init__(self, cq_model):
|
||||
self.cqModel = cq_model
|
||||
|
||||
def visit_Call(self,node):
|
||||
"""
|
||||
def visit_Call(self, node):
|
||||
"""
|
||||
Called when we see a function call. Is it describe_parameter?
|
||||
"""
|
||||
try:
|
||||
if node.func.id == 'describe_parameter':
|
||||
try:
|
||||
if node.func.id == "describe_parameter":
|
||||
# looks like we have a call to our function.
|
||||
# first parameter is the variable,
|
||||
# second is the description
|
||||
varname = node.args[0].id
|
||||
desc = node.args[1].s
|
||||
self.cqModel.add_parameter_description(varname,desc)
|
||||
self.cqModel.add_parameter_description(varname, desc)
|
||||
|
||||
except:
|
||||
#print "Unable to handle function call"
|
||||
except:
|
||||
# print "Unable to handle function call"
|
||||
pass
|
||||
return node
|
||||
return node
|
||||
|
||||
|
||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
"""
|
||||
@ -446,24 +474,42 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
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))
|
||||
InputParameter.create(
|
||||
value_node, var_name, StringParameterType, value_node.s
|
||||
)
|
||||
)
|
||||
elif type(value_node) == ast.Name:
|
||||
if value_node.id == 'True':
|
||||
if value_node.id == "True":
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.id == 'False':
|
||||
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, False))
|
||||
elif hasattr(ast, 'NameConstant') and type(value_node) == ast.NameConstant:
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
elif hasattr(ast, "NameConstant") and type(value_node) == ast.NameConstant:
|
||||
if value_node.value == True:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, True
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, False))
|
||||
InputParameter.create(
|
||||
value_node, var_name, BooleanParameterType, False
|
||||
)
|
||||
)
|
||||
except:
|
||||
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||
pass
|
||||
@ -479,7 +525,7 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
|
||||
# Handle the NamedConstant type that is only present in Python 3
|
||||
astTypes = [ast.Num, ast.Str, ast.Name]
|
||||
if hasattr(ast, 'NameConstant'):
|
||||
if hasattr(ast, "NameConstant"):
|
||||
astTypes.append(ast.NameConstant)
|
||||
|
||||
if type(node.value) in astTypes:
|
||||
|
@ -1,9 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
#from OCP.Visualization import Tesselator
|
||||
|
||||
# from OCP.Visualization import Tesselator
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
import cStringIO as StringIO
|
||||
else:
|
||||
@ -56,7 +58,7 @@ def exportShape(shape, exportType, fileLike, tolerance=0.1):
|
||||
The object should be already open and ready to write. The caller is responsible
|
||||
for closing the object
|
||||
"""
|
||||
|
||||
|
||||
from ..cq import CQ
|
||||
|
||||
def tessellate(shape):
|
||||
@ -72,7 +74,7 @@ def exportShape(shape, exportType, fileLike, tolerance=0.1):
|
||||
|
||||
# add vertices
|
||||
for v in tess[0]:
|
||||
mesher.addVertex(v.x,v.y,v.z)
|
||||
mesher.addVertex(v.x, v.y, v.z)
|
||||
|
||||
# add triangles
|
||||
for t in tess[1]:
|
||||
@ -112,7 +114,7 @@ def readAndDeleteFile(fileName):
|
||||
return the contents as a string
|
||||
"""
|
||||
res = ""
|
||||
with open(fileName, 'r') as f:
|
||||
with open(fileName, "r") as f:
|
||||
res = "{}".format(f.read())
|
||||
|
||||
os.remove(fileName)
|
||||
@ -148,32 +150,32 @@ class AmfWriter(object):
|
||||
self.tessellation = tessellation
|
||||
|
||||
def writeAmf(self, outFile):
|
||||
amf = ET.Element('amf', units=self.units)
|
||||
amf = ET.Element("amf", units=self.units)
|
||||
# TODO: if result is a compound, we need to loop through them
|
||||
object = ET.SubElement(amf, 'object', id="0")
|
||||
mesh = ET.SubElement(object, 'mesh')
|
||||
vertices = ET.SubElement(mesh, 'vertices')
|
||||
volume = ET.SubElement(mesh, 'volume')
|
||||
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')
|
||||
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 = ET.SubElement(coord, "y")
|
||||
y.text = str(v.y)
|
||||
z = ET.SubElement(coord, 'z')
|
||||
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')
|
||||
triangle = ET.SubElement(volume, "triangle")
|
||||
v1 = ET.SubElement(triangle, "v1")
|
||||
v1.text = str(t[0])
|
||||
v2 = ET.SubElement(triangle, 'v2')
|
||||
v2 = ET.SubElement(triangle, "v2")
|
||||
v2.text = str(t[1])
|
||||
v3 = ET.SubElement(triangle, 'v3')
|
||||
v3 = ET.SubElement(triangle, "v3")
|
||||
v3.text = str(t[2])
|
||||
|
||||
amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
|
||||
@ -211,11 +213,11 @@ class JsonMesh(object):
|
||||
|
||||
def toJson(self):
|
||||
return JSON_TEMPLATE % {
|
||||
'vertices': str(self.vertices),
|
||||
'faces': str(self.faces),
|
||||
'nVertices': self.nVertices,
|
||||
'nFaces': self.nFaces
|
||||
};
|
||||
"vertices": str(self.vertices),
|
||||
"faces": str(self.faces),
|
||||
"nVertices": self.nVertices,
|
||||
"nFaces": self.nFaces,
|
||||
}
|
||||
|
||||
|
||||
def makeSVGedge(e):
|
||||
@ -229,20 +231,16 @@ def makeSVGedge(e):
|
||||
start = curve.FirstParameter()
|
||||
end = curve.LastParameter()
|
||||
|
||||
points = GCPnts_QuasiUniformDeflection(curve,
|
||||
DISCRETIZATION_TOLERANCE,
|
||||
start,
|
||||
end)
|
||||
points = GCPnts_QuasiUniformDeflection(curve, DISCRETIZATION_TOLERANCE, start, end)
|
||||
|
||||
if points.IsDone():
|
||||
point_it = (points.Value(i + 1) for i in
|
||||
range(points.NbPoints()))
|
||||
point_it = (points.Value(i + 1) for i in range(points.NbPoints()))
|
||||
|
||||
p = next(point_it)
|
||||
cs.write('M{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("M{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
for p in point_it:
|
||||
cs.write('L{},{} '.format(p.X(), p.Y()))
|
||||
cs.write("L{},{} ".format(p.X(), p.Y()))
|
||||
|
||||
return cs.getvalue()
|
||||
|
||||
@ -271,7 +269,7 @@ def getSVG(shape, opts=None):
|
||||
Export a shape to SVG
|
||||
"""
|
||||
|
||||
d = {'width': 800, 'height': 240, 'marginLeft': 200, 'marginTop': 20}
|
||||
d = {"width": 800, "height": 240, "marginLeft": 200, "marginTop": 20}
|
||||
|
||||
if opts:
|
||||
d.update(opts)
|
||||
@ -279,17 +277,15 @@ def getSVG(shape, opts=None):
|
||||
# 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'])
|
||||
width = float(d["width"])
|
||||
height = float(d["height"])
|
||||
marginLeft = float(d["marginLeft"])
|
||||
marginTop = float(d["marginTop"])
|
||||
|
||||
hlr = HLRBRep_Algo()
|
||||
hlr.Add(shape.wrapped)
|
||||
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(),
|
||||
DEFAULT_DIR)
|
||||
)
|
||||
projector = HLRAlgo_Projector(gp_Ax2(gp_Pnt(), DEFAULT_DIR))
|
||||
|
||||
hlr.Projector(projector)
|
||||
hlr.Update()
|
||||
@ -330,8 +326,7 @@ def getSVG(shape, opts=None):
|
||||
# convert to native CQ objects
|
||||
visible = list(map(Shape, visible))
|
||||
hidden = list(map(Shape, hidden))
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible,
|
||||
hidden)
|
||||
(hiddenPaths, visiblePaths) = getPaths(visible, hidden)
|
||||
|
||||
# get bounding box -- these are all in 2-d space
|
||||
bb = Compound.makeCompound(hidden + visible).BoundingBox()
|
||||
@ -340,8 +335,10 @@ def getSVG(shape, opts=None):
|
||||
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75)
|
||||
|
||||
# compute amount to translate-- move the top left into view
|
||||
(xTranslate, yTranslate) = ((0 - bb.xmin) + marginLeft /
|
||||
unitScale, (0 - bb.ymax) - marginTop / unitScale)
|
||||
(xTranslate, yTranslate) = (
|
||||
(0 - bb.xmin) + marginLeft / unitScale,
|
||||
(0 - bb.ymax) - marginTop / unitScale,
|
||||
)
|
||||
|
||||
# compute paths ( again -- had to strip out freecad crap )
|
||||
hiddenContent = ""
|
||||
@ -356,19 +353,19 @@ def getSVG(shape, opts=None):
|
||||
{
|
||||
"unitScale": str(unitScale),
|
||||
"strokeWidth": str(1.0 / unitScale),
|
||||
"hiddenContent": hiddenContent,
|
||||
"hiddenContent": hiddenContent,
|
||||
"visibleContent": visibleContent,
|
||||
"xTranslate": str(xTranslate),
|
||||
"yTranslate": str(yTranslate),
|
||||
"width": str(width),
|
||||
"height": str(height),
|
||||
"textboxY": str(height - 30),
|
||||
"uom": str(uom)
|
||||
"uom": str(uom),
|
||||
}
|
||||
)
|
||||
# svg = SVG_TEMPLATE % (
|
||||
# {"content": projectedContent}
|
||||
#)
|
||||
# )
|
||||
return svg
|
||||
|
||||
|
||||
@ -380,7 +377,7 @@ def exportSVG(shape, fileName):
|
||||
"""
|
||||
|
||||
svg = getSVG(shape.val())
|
||||
f = open(fileName, 'w')
|
||||
f = open(fileName, "w")
|
||||
f.write(svg)
|
||||
f.close()
|
||||
|
||||
@ -465,4 +462,4 @@ SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
PATHTEMPLATE = "\t\t\t<path d=\"%s\" />\n"
|
||||
PATHTEMPLATE = '\t\t\t<path d="%s" />\n'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import math
|
||||
|
||||
|
||||
from OCP.gp import gp_Vec, gp_Ax1, gp_Ax3, gp_Pnt, gp_Dir, gp_Trsf, gp_GTrsf, gp, gp_XYZ
|
||||
from OCP.Bnd import Bnd_Box
|
||||
from OCP.BRepBndLib import BRepBndLib
|
||||
@ -27,16 +27,16 @@ class Vector(object):
|
||||
if len(args) == 3:
|
||||
fV = gp_Vec(*args)
|
||||
elif len(args) == 2:
|
||||
fV = gp_Vec(*args,0)
|
||||
fV = gp_Vec(*args, 0)
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], Vector):
|
||||
fV = gp_Vec(args[0].wrapped.XYZ())
|
||||
elif isinstance(args[0], (tuple, list)):
|
||||
arg = args[0]
|
||||
if len(arg)==3:
|
||||
if len(arg) == 3:
|
||||
fV = gp_Vec(*arg)
|
||||
elif len(arg)==2:
|
||||
fV = gp_Vec(*arg,0)
|
||||
elif len(arg) == 2:
|
||||
fV = gp_Vec(*arg, 0)
|
||||
elif isinstance(args[0], (gp_Vec, gp_Pnt, gp_Dir)):
|
||||
fV = gp_Vec(args[0].XYZ())
|
||||
elif isinstance(args[0], gp_XYZ):
|
||||
@ -53,25 +53,25 @@ class Vector(object):
|
||||
@property
|
||||
def x(self):
|
||||
return self.wrapped.X()
|
||||
|
||||
|
||||
@x.setter
|
||||
def x(self,value):
|
||||
def x(self, value):
|
||||
self.wrapped.SetX(value)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self.wrapped.Y()
|
||||
|
||||
|
||||
@y.setter
|
||||
def y(self,value):
|
||||
def y(self, value):
|
||||
self.wrapped.SetY(value)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
return self.wrapped.Z()
|
||||
|
||||
|
||||
@z.setter
|
||||
def z(self,value):
|
||||
def z(self, value):
|
||||
self.wrapped.SetZ(value)
|
||||
|
||||
@property
|
||||
@ -132,16 +132,13 @@ class Vector(object):
|
||||
return self.wrapped.Angle(v.wrapped)
|
||||
|
||||
def distanceToLine(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def projectToLine(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def distanceToPlane(self):
|
||||
raise NotImplementedError(
|
||||
"Have not needed this yet, but FreeCAD supports it!")
|
||||
raise NotImplementedError("Have not needed this yet, but OCCT supports it!")
|
||||
|
||||
def projectToPlane(self, plane):
|
||||
"""
|
||||
@ -154,7 +151,7 @@ class Vector(object):
|
||||
base = plane.origin
|
||||
normal = plane.zDir
|
||||
|
||||
return self-normal*(((self-base).dot(normal))/normal.Length**2)
|
||||
return self - normal * (((self - base).dot(normal)) / normal.Length ** 2)
|
||||
|
||||
def __neg__(self):
|
||||
return self * -1
|
||||
@ -163,18 +160,19 @@ class Vector(object):
|
||||
return self.Length
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __str__(self):
|
||||
return 'Vector: ' + str((self.x, self.y, self.z))
|
||||
return "Vector: " + str((self.x, self.y, self.z))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.wrapped.IsEqual(other.wrapped, 0.00001, 0.00001)
|
||||
'''
|
||||
|
||||
"""
|
||||
is not implemented in OCC
|
||||
def __ne__(self, other):
|
||||
return self.wrapped.__ne__(other)
|
||||
'''
|
||||
"""
|
||||
|
||||
def toPnt(self):
|
||||
|
||||
@ -222,44 +220,48 @@ class Matrix:
|
||||
elif isinstance(matrix, (list, tuple)):
|
||||
# Validate matrix size & 4x4 last row value
|
||||
valid_sizes = all(
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4))
|
||||
for row in matrix
|
||||
(isinstance(row, (list, tuple)) and (len(row) == 4)) for row in matrix
|
||||
) and len(matrix) in (3, 4)
|
||||
if not valid_sizes:
|
||||
raise TypeError("Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(matrix))
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0,0,0,1)):
|
||||
raise ValueError("Expected the last row to be [0,0,0,1], but got: {!r}".format(matrix[3]))
|
||||
raise TypeError(
|
||||
"Matrix constructor requires 2d list of 4x3 or 4x4, but got: {!r}".format(
|
||||
matrix
|
||||
)
|
||||
)
|
||||
elif (len(matrix) == 4) and (tuple(matrix[3]) != (0, 0, 0, 1)):
|
||||
raise ValueError(
|
||||
"Expected the last row to be [0,0,0,1], but got: {!r}".format(
|
||||
matrix[3]
|
||||
)
|
||||
)
|
||||
|
||||
# Assign values to matrix
|
||||
self.wrapped = gp_GTrsf()
|
||||
[self.wrapped.SetValue(i+1,j+1,e)
|
||||
for i,row in enumerate(matrix[:3])
|
||||
for j,e in enumerate(row)]
|
||||
|
||||
[
|
||||
self.wrapped.SetValue(i + 1, j + 1, e)
|
||||
for i, row in enumerate(matrix[:3])
|
||||
for j, e in enumerate(row)
|
||||
]
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid param to matrix constructor: {}".format(matrix))
|
||||
raise TypeError("Invalid param to matrix constructor: {}".format(matrix))
|
||||
|
||||
def rotateX(self, angle):
|
||||
|
||||
self._rotate(gp.OX_s(),
|
||||
angle)
|
||||
self._rotate(gp.OX_s(), angle)
|
||||
|
||||
def rotateY(self, angle):
|
||||
|
||||
self._rotate(gp.OY_s(),
|
||||
angle)
|
||||
self._rotate(gp.OY_s(), angle)
|
||||
|
||||
def rotateZ(self, angle):
|
||||
|
||||
self._rotate(gp.OZ_s(),
|
||||
angle)
|
||||
self._rotate(gp.OZ_s(), angle)
|
||||
|
||||
def _rotate(self, direction, angle):
|
||||
|
||||
new = gp_Trsf()
|
||||
new.SetRotation(direction,
|
||||
angle)
|
||||
new.SetRotation(direction, angle)
|
||||
|
||||
self.wrapped = self.wrapped * gp_GTrsf(new)
|
||||
|
||||
@ -277,11 +279,12 @@ class Matrix:
|
||||
def transposed_list(self):
|
||||
"""Needed by the cqparts gltf exporter
|
||||
"""
|
||||
|
||||
|
||||
trsf = self.wrapped
|
||||
data = [[trsf.Value(i,j) for j in range(1,5)] for i in range(1,4)] + \
|
||||
[[0.,0.,0.,1.]]
|
||||
|
||||
data = [[trsf.Value(i, j) for j in range(1, 5)] for i in range(1, 4)] + [
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
]
|
||||
|
||||
return [data[j][i] for i in range(4) for j in range(4)]
|
||||
|
||||
def __getitem__(self, rc):
|
||||
@ -298,7 +301,7 @@ class Matrix:
|
||||
else:
|
||||
# gp_GTrsf doesn't provide access to the 4th row because it has
|
||||
# an implied value as below:
|
||||
return [0., 0., 0., 1.][c]
|
||||
return [0.0, 0.0, 0.0, 1.0][c]
|
||||
else:
|
||||
raise IndexError("Out of bounds access into 4x4 matrix: {!r}".format(rc))
|
||||
|
||||
@ -352,95 +355,94 @@ class Plane(object):
|
||||
|
||||
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))
|
||||
"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(
|
||||
list(namedPlanes.keys())))
|
||||
raise ValueError("Supported names are {}".format(list(namedPlanes.keys())))
|
||||
|
||||
@classmethod
|
||||
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XY', origin)
|
||||
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = Plane.named("bottom", origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@ -458,12 +460,12 @@ class Plane(object):
|
||||
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
||||
"""
|
||||
zDir = Vector(normal)
|
||||
if (zDir.Length == 0.0):
|
||||
raise ValueError('normal should be non null')
|
||||
if zDir.Length == 0.0:
|
||||
raise ValueError("normal should be non null")
|
||||
|
||||
xDir = Vector(xDir)
|
||||
if (xDir.Length == 0.0):
|
||||
raise ValueError('xDir should be non null')
|
||||
if xDir.Length == 0.0:
|
||||
raise ValueError("xDir should be non null")
|
||||
|
||||
self.zDir = zDir.normalized()
|
||||
self._setPlaneDir(xDir)
|
||||
@ -489,7 +491,8 @@ class Plane(object):
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
# TODO is this property rly needed -- why not handle this in the constructor
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
@ -545,7 +548,7 @@ class Plane(object):
|
||||
|
||||
pass
|
||||
|
||||
'''
|
||||
"""
|
||||
# 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
|
||||
@ -562,7 +565,7 @@ class Plane(object):
|
||||
# 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
|
||||
@ -580,7 +583,7 @@ class Plane(object):
|
||||
|
||||
"""
|
||||
from .shapes import Shape
|
||||
|
||||
|
||||
if isinstance(obj, Vector):
|
||||
return obj.transform(self.fG)
|
||||
elif isinstance(obj, Shape):
|
||||
@ -588,7 +591,9 @@ class Plane(object):
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know how to convert type {} to local coordinates".format(
|
||||
type(obj)))
|
||||
type(obj)
|
||||
)
|
||||
)
|
||||
|
||||
def toWorldCoords(self, tuplePoint):
|
||||
"""Convert a point in local coordinates to global coordinates
|
||||
@ -619,19 +624,29 @@ class Plane(object):
|
||||
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
|
||||
:return: a copy of this plane rotated as requested.
|
||||
"""
|
||||
# NB: this is not a geometric Vector
|
||||
rotate = Vector(rotate)
|
||||
# Convert to radians.
|
||||
rotate = rotate.multiply(math.pi / 180.0)
|
||||
|
||||
# Compute rotation matrix.
|
||||
m = Matrix()
|
||||
m.rotateX(rotate.x)
|
||||
m.rotateY(rotate.y)
|
||||
m.rotateZ(rotate.z)
|
||||
T1 = gp_Trsf()
|
||||
T1.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.xDir.toTuple())), rotate.x
|
||||
)
|
||||
T2 = gp_Trsf()
|
||||
T2.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.yDir.toTuple())), rotate.y
|
||||
)
|
||||
T3 = gp_Trsf()
|
||||
T3.SetRotation(
|
||||
gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.zDir.toTuple())), rotate.z
|
||||
)
|
||||
T = Matrix(gp_GTrsf(T1 * T2 * T3))
|
||||
|
||||
# Compute the new plane.
|
||||
newXdir = self.xDir.transform(m)
|
||||
newZdir = self.zDir.transform(m)
|
||||
newXdir = self.xDir.transform(T)
|
||||
newZdir = self.zDir.transform(T)
|
||||
|
||||
return Plane(self.origin, newXdir, newZdir)
|
||||
|
||||
@ -655,7 +670,7 @@ class Plane(object):
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
'''
|
||||
"""
|
||||
resultWires = []
|
||||
for w in listOfShapes:
|
||||
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
||||
@ -681,21 +696,19 @@ class Plane(object):
|
||||
|
||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||
|
||||
return resultWires'''
|
||||
return resultWires"""
|
||||
|
||||
def mirrorInPlane(self, listOfShapes, axis='X'):
|
||||
def mirrorInPlane(self, listOfShapes, axis="X"):
|
||||
|
||||
local_coord_system = gp_Ax3(self.origin.toPnt(),
|
||||
self.zDir.toDir(),
|
||||
self.xDir.toDir())
|
||||
local_coord_system = gp_Ax3(
|
||||
self.origin.toPnt(), self.zDir.toDir(), self.xDir.toDir()
|
||||
)
|
||||
T = gp_Trsf()
|
||||
|
||||
if axis == 'X':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.XDirection()))
|
||||
elif axis == 'Y':
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(),
|
||||
local_coord_system.YDirection()))
|
||||
if axis == "X":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.XDirection()))
|
||||
elif axis == "Y":
|
||||
T.SetMirror(gp_Ax1(self.origin.toPnt(), local_coord_system.YDirection()))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -726,22 +739,21 @@ class Plane(object):
|
||||
# the double-inverting is strange, and I don't understand it.
|
||||
forward = Matrix()
|
||||
inverse = Matrix()
|
||||
|
||||
|
||||
forwardT = gp_Trsf()
|
||||
inverseT = gp_Trsf()
|
||||
|
||||
global_coord_system = gp_Ax3()
|
||||
local_coord_system = gp_Ax3(gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple())
|
||||
)
|
||||
local_coord_system = gp_Ax3(
|
||||
gp_Pnt(*self.origin.toTuple()),
|
||||
gp_Dir(*self.zDir.toTuple()),
|
||||
gp_Dir(*self.xDir.toTuple()),
|
||||
)
|
||||
|
||||
forwardT.SetTransformation(global_coord_system,
|
||||
local_coord_system)
|
||||
forwardT.SetTransformation(global_coord_system, local_coord_system)
|
||||
forward.wrapped = gp_GTrsf(forwardT)
|
||||
|
||||
inverseT.SetTransformation(local_coord_system,
|
||||
global_coord_system)
|
||||
|
||||
inverseT.SetTransformation(local_coord_system, global_coord_system)
|
||||
inverse.wrapped = gp_GTrsf(inverseT)
|
||||
|
||||
# TODO verify if this is OK
|
||||
@ -751,7 +763,7 @@ class Plane(object):
|
||||
|
||||
|
||||
class BoundBox(object):
|
||||
"""A BoundingBox for an object or set of objects. Wraps the OCP.one"""
|
||||
"""A BoundingBox for an object or set of objects. Wraps the OCP one"""
|
||||
|
||||
def __init__(self, bb):
|
||||
self.wrapped = bb
|
||||
@ -767,11 +779,9 @@ class BoundBox(object):
|
||||
self.zmax = ZMax
|
||||
self.zlen = ZMax - ZMin
|
||||
|
||||
self.center = Vector((XMax + XMin) / 2,
|
||||
(YMax + YMin) / 2,
|
||||
(ZMax + ZMin) / 2)
|
||||
self.center = Vector((XMax + XMin) / 2, (YMax + YMin) / 2, (ZMax + ZMin) / 2)
|
||||
|
||||
self.DiagonalLength = self.wrapped.SquareExtent()**0.5
|
||||
self.DiagonalLength = self.wrapped.SquareExtent() ** 0.5
|
||||
|
||||
def add(self, obj, tol=1e-8):
|
||||
"""Returns a modified (expanded) bounding box
|
||||
@ -810,30 +820,36 @@ class BoundBox(object):
|
||||
the built-in implementation i do not understand.
|
||||
"""
|
||||
|
||||
if (bb1.XMin < bb2.XMin and
|
||||
bb1.XMax > bb2.XMax and
|
||||
bb1.YMin < bb2.YMin and
|
||||
bb1.YMax > bb2.YMax):
|
||||
if (
|
||||
bb1.XMin < bb2.XMin
|
||||
and bb1.XMax > bb2.XMax
|
||||
and bb1.YMin < bb2.YMin
|
||||
and bb1.YMax > bb2.YMax
|
||||
):
|
||||
return bb1
|
||||
|
||||
if (bb2.XMin < bb1.XMin and
|
||||
bb2.XMax > bb1.XMax and
|
||||
bb2.YMin < bb1.YMin and
|
||||
bb2.YMax > bb1.YMax):
|
||||
if (
|
||||
bb2.XMin < bb1.XMin
|
||||
and bb2.XMax > bb1.XMax
|
||||
and bb2.YMin < bb1.YMin
|
||||
and bb2.YMax > bb1.YMax
|
||||
):
|
||||
return bb2
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _fromTopoDS(cls, shape, tol=None, optimal=True):
|
||||
'''
|
||||
"""
|
||||
Constructs a bounding box from a TopoDS_Shape
|
||||
'''
|
||||
"""
|
||||
tol = TOL if tol is None else tol # tol = TOL (by default)
|
||||
bbox = Bnd_Box()
|
||||
|
||||
|
||||
if optimal:
|
||||
BRepBndLib.AddOptimal_s(shape, bbox) #this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||
BRepBndLib.AddOptimal_s(
|
||||
shape, bbox
|
||||
) # this is 'exact' but expensive - not yet wrapped by PythonOCC
|
||||
else:
|
||||
mesh = BRepMesh_IncrementalMesh(shape, tol, True)
|
||||
mesh.Perform()
|
||||
@ -844,12 +860,14 @@ class BoundBox(object):
|
||||
|
||||
def isInside(self, b2):
|
||||
"""Is the provided bounding box inside this one?"""
|
||||
if (b2.xmin > self.xmin and
|
||||
b2.ymin > self.ymin and
|
||||
b2.zmin > self.zmin and
|
||||
b2.xmax < self.xmax and
|
||||
b2.ymax < self.ymax and
|
||||
b2.zmax < self.zmax):
|
||||
if (
|
||||
b2.xmin > self.xmin
|
||||
and b2.ymin > self.ymin
|
||||
and b2.zmin > self.zmin
|
||||
and b2.xmax < self.xmax
|
||||
and b2.ymax < self.ymax
|
||||
and b2.zmax < self.zmax
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -34,14 +34,14 @@ 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
|
||||
reader = STEPControl_Reader()
|
||||
readStatus = reader.ReadFile(fileName)
|
||||
if readStatus != OCP.IFSelect.IFSelect_RetDone:
|
||||
raise ValueError("STEP File could not be loaded")
|
||||
for i in range(reader.NbRootsForTransfer()):
|
||||
reader.TransferRoot(i+1)
|
||||
reader.TransferRoot(i + 1)
|
||||
|
||||
occ_shapes = []
|
||||
for i in range(reader.NbShapes()):
|
||||
|
@ -1,103 +1,8 @@
|
||||
from OCC.Display.WebGl.x3dom_renderer import X3DExporter
|
||||
from OCC.Core.gp import gp_Quaternion, gp_Vec
|
||||
from uuid import uuid4
|
||||
from math import tan
|
||||
from xml.etree import ElementTree
|
||||
from IPython.display import SVG
|
||||
|
||||
from .geom import BoundBox
|
||||
from .exporters import toString, ExportTypes
|
||||
|
||||
BOILERPLATE = \
|
||||
'''
|
||||
<link rel='stylesheet' type='text/css' href='http://www.x3dom.org/download/x3dom.css'></link>
|
||||
<div style='height: {height}px; width: 100%;' width='100%' height='{height}px'>
|
||||
<x3d style='height: {height}px; width: 100%;' id='{id}' width='100%' height='{height}px'>
|
||||
<scene>
|
||||
<Viewpoint position='{x},{y},{z}' centerOfRotation='{x0} {y0} {z0}' orientation='{rot}' fieldOfView='{fov}'></Viewpoint>
|
||||
{src}
|
||||
</scene>
|
||||
</x3d>
|
||||
</div>
|
||||
<script>
|
||||
if (document.getElementById('X3DOM_JS_MODULE') == null){{
|
||||
var scr = document.createElement('script');
|
||||
head = document.head || document.getElementsByTagName('head')[0];
|
||||
scr.src = 'http://www.x3dom.org/download/x3dom.js';
|
||||
scr.async = false;
|
||||
scr.id = 'X3DOM_JS_MODULE';
|
||||
scr.onload = function () {{
|
||||
x3dom.reload();
|
||||
}}
|
||||
head.insertBefore(scr, head.lastChild);
|
||||
}}
|
||||
else if (typeof x3dom != 'undefined') {{ //call reload only if x3dom already loaded
|
||||
x3dom.reload();
|
||||
}}
|
||||
|
||||
//document.getElementById('{id}').runtime.fitAll()
|
||||
</script>
|
||||
'''
|
||||
def display(shape):
|
||||
|
||||
#https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
|
||||
#better if else
|
||||
|
||||
ROT = (0.77,0.3,0.55,1.28)
|
||||
ROT = (0.,0,0,1.)
|
||||
FOV = 0.2
|
||||
|
||||
def add_x3d_boilerplate(src, height=400, center=(0,0,0), d=(0,0,15), fov=FOV, rot='{} {} {} {} '.format(*ROT)):
|
||||
|
||||
return BOILERPLATE.format(src=src,
|
||||
id=uuid4(),
|
||||
height=height,
|
||||
x=d[0],
|
||||
y=d[1],
|
||||
z=d[2],
|
||||
x0=center[0],
|
||||
y0=center[1],
|
||||
z0=center[2],
|
||||
fov=fov,
|
||||
rot=rot)
|
||||
|
||||
def x3d_display(shape,
|
||||
vertex_shader=None,
|
||||
fragment_shader=None,
|
||||
export_edges=True,
|
||||
color=(1,1,0),
|
||||
specular_color=(1,1,1),
|
||||
shininess=0.4,
|
||||
transparency=0.4,
|
||||
line_color=(0,0,0),
|
||||
line_width=2.,
|
||||
mesh_quality=.3):
|
||||
|
||||
# Export to XML <Scene> tag
|
||||
exporter = X3DExporter(shape,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
export_edges,
|
||||
color,
|
||||
specular_color,
|
||||
shininess,
|
||||
transparency,
|
||||
line_color,
|
||||
line_width,
|
||||
mesh_quality)
|
||||
|
||||
exporter.compute()
|
||||
x3d_str = exporter.to_x3dfile_string(shape_id=0)
|
||||
xml_et = ElementTree.fromstring(x3d_str)
|
||||
scene_tag = xml_et.find('./Scene')
|
||||
|
||||
# Viewport Parameters
|
||||
bb = BoundBox._fromTopoDS(shape)
|
||||
d = max(bb.xlen,bb.ylen,bb.zlen)
|
||||
c = bb.center
|
||||
|
||||
vec = gp_Vec(0,0,d/1.5/tan(FOV/2))
|
||||
quat = gp_Quaternion(*ROT)
|
||||
vec = quat*(vec) + c.wrapped
|
||||
|
||||
# return boilerplate + Scene
|
||||
return add_x3d_boilerplate(ElementTree.tostring(scene_tag).decode('utf-8'),
|
||||
d=(vec.X(),vec.Y(),vec.Z()),
|
||||
center=(c.x,c.y,c.z))
|
||||
return SVG(toString(shape, ExportTypes.SVG))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,22 @@ import re
|
||||
import math
|
||||
from cadquery import Vector, Edge, Vertex, Face, Solid, Shell, Compound
|
||||
from collections import defaultdict
|
||||
from pyparsing import Literal, Word, nums, Optional, Combine, oneOf, upcaseTokens,\
|
||||
CaselessLiteral, Group, infixNotation, opAssoc, Forward,\
|
||||
ZeroOrMore, Keyword
|
||||
from pyparsing import (
|
||||
Literal,
|
||||
Word,
|
||||
nums,
|
||||
Optional,
|
||||
Combine,
|
||||
oneOf,
|
||||
upcaseTokens,
|
||||
CaselessLiteral,
|
||||
Group,
|
||||
infixNotation,
|
||||
opAssoc,
|
||||
Forward,
|
||||
ZeroOrMore,
|
||||
Keyword,
|
||||
)
|
||||
from functools import reduce
|
||||
|
||||
|
||||
@ -81,7 +94,6 @@ class NearestToPointSelector(Selector):
|
||||
self.pnt = pnt
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
def dist(tShape):
|
||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||
# if tShape.ShapeType == 'Vertex':
|
||||
@ -121,15 +133,18 @@ class BoxSelector(Selector):
|
||||
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))
|
||||
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)):
|
||||
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()):
|
||||
@ -168,7 +183,9 @@ class BaseDirSelector(Selector):
|
||||
|
||||
if self.test(normal):
|
||||
r.append(o)
|
||||
elif type(o) == Edge and (o.geomType() == 'LINE' or o.geomType() == 'PLANE'):
|
||||
elif type(o) == Edge and (
|
||||
o.geomType() == "LINE" or o.geomType() == "PLANE"
|
||||
):
|
||||
# an edge is parallel to a direction if its underlying geometry is plane or line
|
||||
tangent = o.tangentAt()
|
||||
if self.test(tangent):
|
||||
@ -247,8 +264,7 @@ class PerpendicularDirSelector(BaseDirSelector):
|
||||
|
||||
def test(self, vec):
|
||||
angle = self.direction.getAngle(vec)
|
||||
r = (abs(angle) < self.TOLERANCE) or (
|
||||
abs(angle - math.pi) < self.TOLERANCE)
|
||||
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE)
|
||||
return not r
|
||||
|
||||
|
||||
@ -314,17 +330,16 @@ class DirectionMinMaxSelector(Selector):
|
||||
self.TOLERANCE = tolerance
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
def distance(tShape):
|
||||
return tShape.Center().dot(self.vector)
|
||||
|
||||
# import OrderedDict
|
||||
from collections import OrderedDict
|
||||
|
||||
# make and distance to object dict
|
||||
objectDict = {distance(el): el for el in objectList}
|
||||
# transform it into an ordered dict
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()),
|
||||
key=lambda x: x[0]))
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()), key=lambda x: x[0]))
|
||||
|
||||
# find out the max/min distance
|
||||
if self.directionMax:
|
||||
@ -370,8 +385,9 @@ class DirectionNthSelector(ParallelDirSelector):
|
||||
objectDict[round(distance(el), digits)].append(el)
|
||||
|
||||
# choose the Nth unique rounded distance
|
||||
nth_distance = sorted(list(objectDict.keys()),
|
||||
reverse=not self.directionMax)[self.N]
|
||||
nth_distance = sorted(list(objectDict.keys()), reverse=not self.directionMax)[
|
||||
self.N
|
||||
]
|
||||
|
||||
# map back to original objects and return
|
||||
return objectDict[nth_distance]
|
||||
@ -388,8 +404,9 @@ class BinarySelector(Selector):
|
||||
self.right = right
|
||||
|
||||
def filter(self, objectList):
|
||||
return self.filterResults(self.left.filter(objectList),
|
||||
self.right.filter(objectList))
|
||||
return self.filterResults(
|
||||
self.left.filter(objectList), self.right.filter(objectList)
|
||||
)
|
||||
|
||||
def filterResults(self, r_left, r_right):
|
||||
raise NotImplementedError
|
||||
@ -445,52 +462,56 @@ def _makeGrammar():
|
||||
"""
|
||||
|
||||
# float definition
|
||||
point = Literal('.')
|
||||
plusmin = Literal('+') | Literal('-')
|
||||
point = Literal(".")
|
||||
plusmin = Literal("+") | Literal("-")
|
||||
number = Word(nums)
|
||||
integer = Combine(Optional(plusmin) + number)
|
||||
floatn = Combine(integer + Optional(point + Optional(number)))
|
||||
|
||||
# vector definition
|
||||
lbracket = Literal('(')
|
||||
rbracket = Literal(')')
|
||||
comma = Literal(',')
|
||||
vector = Combine(lbracket + floatn('x') + comma +
|
||||
floatn('y') + comma + floatn('z') + rbracket)
|
||||
lbracket = Literal("(")
|
||||
rbracket = Literal(")")
|
||||
comma = Literal(",")
|
||||
vector = Combine(
|
||||
lbracket + floatn("x") + comma + floatn("y") + comma + floatn("z") + rbracket
|
||||
)
|
||||
|
||||
# direction definition
|
||||
simple_dir = oneOf(['X', 'Y', 'Z', 'XY', 'XZ', 'YZ'])
|
||||
direction = simple_dir('simple_dir') | vector('vector_dir')
|
||||
simple_dir = oneOf(["X", "Y", "Z", "XY", "XZ", "YZ"])
|
||||
direction = simple_dir("simple_dir") | vector("vector_dir")
|
||||
|
||||
# CQ type definition
|
||||
cqtype = oneOf(['Plane', 'Cylinder', 'Sphere', 'Cone', 'Line', 'Circle', 'Arc'],
|
||||
caseless=True)
|
||||
cqtype = oneOf(
|
||||
["Plane", "Cylinder", "Sphere", "Cone", "Line", "Circle", "Arc"], caseless=True
|
||||
)
|
||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||
|
||||
# type operator
|
||||
type_op = Literal('%')
|
||||
type_op = Literal("%")
|
||||
|
||||
# direction operator
|
||||
direction_op = oneOf(['>', '<'])
|
||||
direction_op = oneOf([">", "<"])
|
||||
|
||||
# index definition
|
||||
ix_number = Group(Optional('-') + Word(nums))
|
||||
lsqbracket = Literal('[').suppress()
|
||||
rsqbracket = Literal(']').suppress()
|
||||
ix_number = Group(Optional("-") + Word(nums))
|
||||
lsqbracket = Literal("[").suppress()
|
||||
rsqbracket = Literal("]").suppress()
|
||||
|
||||
index = lsqbracket + ix_number('index') + rsqbracket
|
||||
index = lsqbracket + ix_number("index") + rsqbracket
|
||||
|
||||
# other operators
|
||||
other_op = oneOf(['|', '#', '+', '-'])
|
||||
other_op = oneOf(["|", "#", "+", "-"])
|
||||
|
||||
# named view
|
||||
named_view = oneOf(['front', 'back', 'left', 'right', 'top', 'bottom'])
|
||||
named_view = oneOf(["front", "back", "left", "right", "top", "bottom"])
|
||||
|
||||
return direction('only_dir') | \
|
||||
(type_op('type_op') + cqtype('cq_type')) | \
|
||||
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
||||
(other_op('other_op') + direction('dir')) | \
|
||||
named_view('named_view')
|
||||
return (
|
||||
direction("only_dir")
|
||||
| (type_op("type_op") + cqtype("cq_type"))
|
||||
| (direction_op("dir_op") + direction("dir") + Optional(index))
|
||||
| (other_op("other_op") + direction("dir"))
|
||||
| named_view("named_view")
|
||||
)
|
||||
|
||||
|
||||
_grammar = _makeGrammar() # make a grammar instance
|
||||
@ -506,33 +527,34 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
|
||||
# define all token to object mappings
|
||||
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)
|
||||
"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),
|
||||
}
|
||||
|
||||
self.namedViews = {
|
||||
'front': (Vector(0, 0, 1), True),
|
||||
'back': (Vector(0, 0, 1), False),
|
||||
'left': (Vector(1, 0, 0), False),
|
||||
'right': (Vector(1, 0, 0), True),
|
||||
'top': (Vector(0, 1, 0), True),
|
||||
'bottom': (Vector(0, 1, 0), False)
|
||||
"front": (Vector(0, 0, 1), True),
|
||||
"back": (Vector(0, 0, 1), False),
|
||||
"left": (Vector(1, 0, 0), False),
|
||||
"right": (Vector(1, 0, 0), True),
|
||||
"top": (Vector(0, 1, 0), True),
|
||||
"bottom": (Vector(0, 1, 0), False),
|
||||
}
|
||||
|
||||
self.operatorMinMax = {
|
||||
'>': True,
|
||||
'<': False,
|
||||
">": True,
|
||||
"<": False,
|
||||
}
|
||||
|
||||
self.operator = {
|
||||
'+': DirectionSelector,
|
||||
'-': lambda v: DirectionSelector(-v),
|
||||
'#': PerpendicularDirSelector,
|
||||
'|': ParallelDirSelector}
|
||||
"+": DirectionSelector,
|
||||
"-": lambda v: DirectionSelector(-v),
|
||||
"#": PerpendicularDirSelector,
|
||||
"|": ParallelDirSelector,
|
||||
}
|
||||
|
||||
self.parseResults = parseResults
|
||||
self.mySelector = self._chooseSelector(parseResults)
|
||||
@ -541,23 +563,25 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Sets up the underlying filters accordingly
|
||||
"""
|
||||
if 'only_dir' in pr:
|
||||
if "only_dir" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return DirectionSelector(vec)
|
||||
|
||||
elif 'type_op' in pr:
|
||||
elif "type_op" in pr:
|
||||
return TypeSelector(pr.cq_type)
|
||||
|
||||
elif 'dir_op' in pr:
|
||||
elif "dir_op" in pr:
|
||||
vec = self._getVector(pr)
|
||||
minmax = self.operatorMinMax[pr.dir_op]
|
||||
|
||||
if 'index' in pr:
|
||||
return DirectionNthSelector(vec, int(''.join(pr.index.asList())), minmax)
|
||||
if "index" in pr:
|
||||
return DirectionNthSelector(
|
||||
vec, int("".join(pr.index.asList())), minmax
|
||||
)
|
||||
else:
|
||||
return DirectionMinMaxSelector(vec, minmax)
|
||||
|
||||
elif 'other_op' in pr:
|
||||
elif "other_op" in pr:
|
||||
vec = self._getVector(pr)
|
||||
return self.operator[pr.other_op](vec)
|
||||
|
||||
@ -569,7 +593,7 @@ class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
Translate parsed vector string into a CQ Vector
|
||||
"""
|
||||
if 'vector_dir' in pr:
|
||||
if "vector_dir" in pr:
|
||||
vec = pr.vector_dir
|
||||
return Vector(float(vec.x), float(vec.y), float(vec.z))
|
||||
else:
|
||||
@ -590,10 +614,10 @@ def _makeExpressionGrammar(atom):
|
||||
"""
|
||||
|
||||
# define operators
|
||||
and_op = Literal('and')
|
||||
or_op = Literal('or')
|
||||
delta_op = oneOf(['exc', 'except'])
|
||||
not_op = Literal('not')
|
||||
and_op = Literal("and")
|
||||
or_op = Literal("or")
|
||||
delta_op = oneOf(["exc", "except"])
|
||||
not_op = Literal("not")
|
||||
|
||||
def atom_callback(res):
|
||||
return _SimpleStringSyntaxSelector(res)
|
||||
@ -622,11 +646,15 @@ def _makeExpressionGrammar(atom):
|
||||
return InverseSelector(right)
|
||||
|
||||
# construct the final grammar and set all the callbacks
|
||||
expr = infixNotation(atom,
|
||||
[(and_op, 2, opAssoc.LEFT, and_callback),
|
||||
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||
(not_op, 1, opAssoc.RIGHT, not_callback)])
|
||||
expr = infixNotation(
|
||||
atom,
|
||||
[
|
||||
(and_op, 2, opAssoc.LEFT, and_callback),
|
||||
(or_op, 2, opAssoc.LEFT, or_callback),
|
||||
(delta_op, 2, opAssoc.LEFT, exc_callback),
|
||||
(not_op, 1, opAssoc.RIGHT, not_callback),
|
||||
],
|
||||
)
|
||||
|
||||
return expr
|
||||
|
||||
@ -690,8 +718,7 @@ class StringSyntaxSelector(Selector):
|
||||
Feed the input string through the parser and construct an relevant complex selector object
|
||||
"""
|
||||
self.selectorString = selectorString
|
||||
parse_result = _expression_grammar.parseString(selectorString,
|
||||
parseAll=True)
|
||||
parse_result = _expression_grammar.parseString(selectorString, parseAll=True)
|
||||
self.mySelector = parse_result.asList()[0]
|
||||
|
||||
def filter(self, objectList):
|
||||
|
Reference in New Issue
Block a user