workable version of cqgi version
This commit is contained in:
parent
190980d4a1
commit
142a5c88d8
|
@ -8,7 +8,7 @@ from .freecad_impl import importers
|
||||||
|
|
||||||
#the order of these matter
|
#the order of these matter
|
||||||
from .selectors import *
|
from .selectors import *
|
||||||
from .CQ import *
|
from .cq import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -35,7 +35,7 @@ def cq_directive(name, arguments, options, content, lineno,
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_s = StringIO.StringIO()
|
_s = StringIO.StringIO()
|
||||||
result = cqgi.execute(plot_code)
|
result = cqgi.parse(plot_code).build()
|
||||||
|
|
||||||
if result.success:
|
if result.success:
|
||||||
exporters.exportShape(result.first_result, "SVG", _s)
|
exporters.exportShape(result.first_result, "SVG", _s)
|
||||||
|
|
251
cadquery/cqgi.py
251
cadquery/cqgi.py
|
@ -1,25 +1,83 @@
|
||||||
"""
|
"""
|
||||||
|
The CadQuery Gateway Interface.
|
||||||
The CadQuery Container Environment.
|
|
||||||
Provides classes and tools for executing CadQuery scripts
|
Provides classes and tools for executing CadQuery scripts
|
||||||
"""
|
"""
|
||||||
import ast
|
import ast
|
||||||
import traceback
|
import traceback
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
import cadquery
|
import cadquery
|
||||||
|
|
||||||
CQSCRIPT = "<cqscript>"
|
CQSCRIPT = "<cqscript>"
|
||||||
|
|
||||||
|
def parse(script_source):
|
||||||
def execute(script_source, build_parameters=None):
|
|
||||||
"""
|
"""
|
||||||
Executes the provided model, using the specified variables.
|
Parses the script as a model, and returns a model.
|
||||||
|
|
||||||
If you would prefer to access the underlying model without building it,
|
If you would prefer to access the underlying model without building it,
|
||||||
for example, to inspect its available parameters, construct a CQModel object.
|
for example, to inspect its available parameters, construct a CQModel object.
|
||||||
|
|
||||||
:param script_source: the script to run. Must be a valid cadquery script
|
: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
|
:param build_parameters: a dictionary of variables. The variables must be
|
||||||
assignable to the underlying variable type.
|
assignable to the underlying variable type.
|
||||||
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
|
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
|
||||||
|
@ -27,44 +85,15 @@ def execute(script_source, build_parameters=None):
|
||||||
:return: a BuildResult object, which includes the status of the result, and either
|
:return: a BuildResult object, which includes the status of the result, and either
|
||||||
a resulting shape or an exception
|
a resulting shape or an exception
|
||||||
"""
|
"""
|
||||||
model = CQModel(script_source)
|
if not build_parameters:
|
||||||
return model.build(build_parameters)
|
build_parameters = {}
|
||||||
|
|
||||||
|
|
||||||
class CQModel(object):
|
|
||||||
"""
|
|
||||||
Object that provides a nice interface to a cq script that
|
|
||||||
is following the cce model
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, script_source):
|
|
||||||
self.metadata = ScriptMetadata()
|
|
||||||
self.ast_tree = ast.parse(script_source, CQSCRIPT)
|
|
||||||
|
|
||||||
ConstantAssignmentFinder(self.metadata).visit(self.ast_tree)
|
|
||||||
|
|
||||||
# TODO: pick up other scirpt metadata:
|
|
||||||
# describe
|
|
||||||
# pick up validation methods
|
|
||||||
|
|
||||||
def validate(self, params):
|
|
||||||
raise NotImplementedError("not yet implemented")
|
|
||||||
|
|
||||||
def build(self, params=None):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param params: dictionary of parameter values to build with
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if not params:
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
start = time.clock()
|
start = time.clock()
|
||||||
result = BuildResult()
|
result = BuildResult()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.set_param_values(params)
|
self.set_param_values(build_parameters)
|
||||||
collector = BuildObjectCollector()
|
collector = ScriptCallback()
|
||||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||||
.add_entry("build_object", collector.build_object).build()
|
.add_entry("build_object", collector.build_object).build()
|
||||||
|
|
||||||
|
@ -75,7 +104,11 @@ class CQModel(object):
|
||||||
else:
|
else:
|
||||||
raise NoOutputError("Script did not call build_object-- no output available.")
|
raise NoOutputError("Script did not call build_object-- no output available.")
|
||||||
except Exception, ex:
|
except Exception, ex:
|
||||||
|
print "Error Executing Script:"
|
||||||
result.set_failure_result(ex)
|
result.set_failure_result(ex)
|
||||||
|
traceback.print_exc()
|
||||||
|
print "Full Text of Script:"
|
||||||
|
print self.script_source
|
||||||
|
|
||||||
end = time.clock()
|
end = time.clock()
|
||||||
result.buildTime = end - start
|
result.buildTime = end - start
|
||||||
|
@ -93,6 +126,16 @@ class CQModel(object):
|
||||||
|
|
||||||
|
|
||||||
class BuildResult(object):
|
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):
|
def __init__(self):
|
||||||
self.buildTime = None
|
self.buildTime = None
|
||||||
self.results = []
|
self.results = []
|
||||||
|
@ -111,6 +154,10 @@ class BuildResult(object):
|
||||||
|
|
||||||
|
|
||||||
class ScriptMetadata(object):
|
class ScriptMetadata(object):
|
||||||
|
"""
|
||||||
|
Defines the metadata for a parsed CQ Script.
|
||||||
|
the parameters property is a dict of InputParameter objects.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.parameters = {}
|
self.parameters = {}
|
||||||
|
|
||||||
|
@ -135,12 +182,37 @@ class BooleanParameterType(ParameterType):
|
||||||
|
|
||||||
|
|
||||||
class InputParameter:
|
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):
|
def __init__(self):
|
||||||
self.name = None
|
|
||||||
self.shortDesc = None
|
#: the default value for the variable.
|
||||||
self.varType = None
|
|
||||||
self.valid_values = []
|
|
||||||
self.default_value = None
|
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
|
self.ast_node = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -181,9 +253,9 @@ class InputParameter:
|
||||||
self.ast_node.s = str(new_value)
|
self.ast_node.s = str(new_value)
|
||||||
elif self.varType == BooleanParameterType:
|
elif self.varType == BooleanParameterType:
|
||||||
if new_value:
|
if new_value:
|
||||||
self.ast_node.value.id = 'True'
|
self.ast_node.id = 'True'
|
||||||
else:
|
else:
|
||||||
self.ast_node.value.id = 'False'
|
self.ast_node.id = 'False'
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||||
|
|
||||||
|
@ -192,56 +264,54 @@ class InputParameter:
|
||||||
self.name, str(self.varType), str(self.default_value))
|
self.name, str(self.varType), str(self.default_value))
|
||||||
|
|
||||||
|
|
||||||
class BuildObjectCollector(object):
|
class ScriptCallback(object):
|
||||||
"""
|
"""
|
||||||
Allows a script to provide output objects
|
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):
|
def __init__(self):
|
||||||
self.outputObjects = []
|
self.outputObjects = []
|
||||||
|
|
||||||
def build_object(self, shape):
|
def build_object(self, shape):
|
||||||
|
"""
|
||||||
|
return an object to the executing environment
|
||||||
|
:param shape: a cadquery object
|
||||||
|
"""
|
||||||
self.outputObjects.append(shape)
|
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):
|
def has_results(self):
|
||||||
return len(self.outputObjects) > 0
|
return len(self.outputObjects) > 0
|
||||||
|
|
||||||
|
|
||||||
class ScriptExecutor(object):
|
|
||||||
"""
|
|
||||||
executes a script in a given environment.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, environment, ast_tree):
|
|
||||||
|
|
||||||
try:
|
|
||||||
exec ast_tree in environment
|
|
||||||
except Exception, ex:
|
|
||||||
|
|
||||||
# an error here means there was a problem compiling the script
|
|
||||||
# try to figure out what line the error was on
|
|
||||||
traceback.print_exc()
|
|
||||||
formatted_lines = traceback.format_exc().splitlines()
|
|
||||||
line_text = ""
|
|
||||||
for f in formatted_lines:
|
|
||||||
if f.find(CQSCRIPT) > -1:
|
|
||||||
m = re.search("line\\s+(\\d+)", f, re.IGNORECASE)
|
|
||||||
if m and m.group(1):
|
|
||||||
line_text = m.group(1)
|
|
||||||
else:
|
|
||||||
line_text = 0
|
|
||||||
|
|
||||||
sse = ScriptExecutionError()
|
|
||||||
sse.line = int(line_text)
|
|
||||||
sse.message = str(ex)
|
|
||||||
raise sse
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameterError(Exception):
|
class InvalidParameterError(Exception):
|
||||||
|
"""
|
||||||
|
Raised when an attempt is made to provide a new parameter value
|
||||||
|
that cannot be assigned to the model
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoOutputError(Exception):
|
class NoOutputError(Exception):
|
||||||
|
"""
|
||||||
|
Raised when the script does not execute the build_output() method to
|
||||||
|
return a solid
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,6 +344,11 @@ class ScriptExecutionError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentBuilder(object):
|
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):
|
def __init__(self):
|
||||||
self.env = {}
|
self.env = {}
|
||||||
|
|
||||||
|
@ -286,6 +361,7 @@ class EnvironmentBuilder(object):
|
||||||
|
|
||||||
def with_cadquery_objects(self):
|
def with_cadquery_objects(self):
|
||||||
self.env['cadquery'] = cadquery
|
self.env['cadquery'] = cadquery
|
||||||
|
self.env['cq'] = cadquery
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_entry(self, name, value):
|
def add_entry(self, name, value):
|
||||||
|
@ -305,6 +381,11 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||||
self.cqModel = cq_model
|
self.cqModel = cq_model
|
||||||
|
|
||||||
def handle_assignment(self, var_name, value_node):
|
def handle_assignment(self, var_name, value_node):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
if type(value_node) == ast.Num:
|
if type(value_node) == ast.Num:
|
||||||
self.cqModel.add_script_parameter(
|
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))
|
||||||
|
@ -312,19 +393,33 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||||
self.cqModel.add_script_parameter(
|
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):
|
elif type(value_node == ast.Name):
|
||||||
if value_node.value.Id == 'True':
|
if value_node.id == 'True':
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||||
elif value_node.value.Id == 'False':
|
elif value_node.id == 'False':
|
||||||
self.cqModel.add_script_parameter(
|
self.cqModel.add_script_parameter(
|
||||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||||
|
except:
|
||||||
|
print "Unable to handle assignment for variable '%s'" % var_name
|
||||||
|
pass
|
||||||
|
|
||||||
def visit_Assign(self, node):
|
def visit_Assign(self, node):
|
||||||
|
|
||||||
|
try:
|
||||||
left_side = node.targets[0]
|
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]:
|
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||||
self.handle_assignment(left_side.id, node.value)
|
self.handle_assignment(left_side.id, node.value)
|
||||||
elif type(node.value) == ast.Tuple:
|
elif type(node.value) == ast.Tuple:
|
||||||
# we have a multi-value assignment
|
# we have a multi-value assignment
|
||||||
for n, v in zip(left_side.elts, node.value.elts):
|
for n, v in zip(left_side.elts, node.value.elts):
|
||||||
self.handle_assignment(n.id, v)
|
self.handle_assignment(n.id, v)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
print "Unable to handle assignment for node '%s'" % ast.dump(left_side)
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
|
@ -32,9 +32,11 @@ def sortWiresByBuildOrder(wireList, plane, result=[]):
|
||||||
there are no wires inside wires inside wires
|
there are no wires inside wires inside wires
|
||||||
( IE, islands -- we can deal with that later on )
|
( IE, islands -- we can deal with that later on )
|
||||||
none of the wires are construction wires
|
none of the wires are construction wires
|
||||||
|
|
||||||
Compute:
|
Compute:
|
||||||
one or more sets of wires, with the outer wire listed first, and inner
|
one or more sets of wires, with the outer wire listed first, and inner
|
||||||
ones
|
ones
|
||||||
|
|
||||||
Returns, list of lists.
|
Returns, list of lists.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
|
@ -56,7 +58,7 @@ def sortWiresByBuildOrder(wireList, plane, result=[]):
|
||||||
class Vector(object):
|
class Vector(object):
|
||||||
"""Create a 3-dimensional vector
|
"""Create a 3-dimensional vector
|
||||||
|
|
||||||
:param *args: a 3-d vector, with x-y-z parts.
|
:param args: a 3-d vector, with x-y-z parts.
|
||||||
|
|
||||||
you can either provide:
|
you can either provide:
|
||||||
* nothing (in which case the null vector is return)
|
* nothing (in which case the null vector is return)
|
||||||
|
@ -375,6 +377,7 @@ class Plane(object):
|
||||||
|
|
||||||
The new coordinates are specified in terms of the current 2-d system.
|
The new coordinates are specified in terms of the current 2-d system.
|
||||||
As an example:
|
As an example:
|
||||||
|
|
||||||
p = Plane.XY()
|
p = Plane.XY()
|
||||||
p.setOrigin2d(2, 2)
|
p.setOrigin2d(2, 2)
|
||||||
p.setOrigin2d(2, 2)
|
p.setOrigin2d(2, 2)
|
||||||
|
|
|
@ -668,16 +668,16 @@ class Solid(Shape):
|
||||||
@classmethod
|
@classmethod
|
||||||
def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)):
|
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\nin pnt with the d
|
makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height)
|
||||||
imensions (length,width,height)\nBy default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
|
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))
|
return Shape.cast(FreeCADPart.makeBox(length, width, height, pnt.wrapped, dir.wrapped))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
|
def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
|
||||||
"""
|
"""
|
||||||
'makeCone(radius1,radius2,height,[pnt,dir,angle]) --
|
Make a cone with given radii and height
|
||||||
Make a cone with given radii and height\nBy default pnt=Vector(0,0,0),
|
By default pnt=Vector(0,0,0),
|
||||||
dir=Vector(0,0,1) and angle=360'
|
dir=Vector(0,0,1) and angle=360'
|
||||||
"""
|
"""
|
||||||
return Shape.cast(FreeCADPart.makeCone(radius1, radius2, height, pnt.wrapped, dir.wrapped, angleDegrees))
|
return Shape.cast(FreeCADPart.makeCone(radius1, radius2, height, pnt.wrapped, dir.wrapped, angleDegrees))
|
||||||
|
@ -727,10 +727,8 @@ class Solid(Shape):
|
||||||
@classmethod
|
@classmethod
|
||||||
def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
|
def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
|
||||||
"""
|
"""
|
||||||
'makeWedge(xmin, ymin, zmin, z2min, x2min,
|
Make a wedge located in pnt
|
||||||
xmax, ymax, zmax, z2max, x2max,[pnt, dir])
|
By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)
|
||||||
Make a wedge located in pnt\nBy default pnt=Vector(0,0,0) and dir=Vec
|
|
||||||
tor(0,0,1)'
|
|
||||||
"""
|
"""
|
||||||
return Shape.cast(
|
return Shape.cast(
|
||||||
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
|
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
|
||||||
|
@ -738,9 +736,8 @@ class Solid(Shape):
|
||||||
@classmethod
|
@classmethod
|
||||||
def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
|
def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None):
|
||||||
"""
|
"""
|
||||||
'makeSphere(radius,[pnt, dir, angle1,angle2,angle3]) --
|
Make a sphere with a given radius
|
||||||
Make a sphere with a giv
|
By default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360
|
||||||
en radius\nBy 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))
|
return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
|
||||||
|
|
||||||
|
@ -752,11 +749,11 @@ class Solid(Shape):
|
||||||
Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the
|
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.
|
construction methods used here are different enough that they should be separate.
|
||||||
|
|
||||||
At a high level, the steps followed ar:
|
At a high level, the steps followed are:
|
||||||
(1) accept a set of wires
|
(1) accept a set of wires
|
||||||
(2) create another set of wires like this one, but which are transformed and rotated
|
(2) create another set of wires like this one, but which are transformed and rotated
|
||||||
(3) create a ruledSurface between the sets of wires
|
(3) create a ruledSurface between the sets of wires
|
||||||
(40 create a shell and compute the resulting object
|
(4) create a shell and compute the resulting object
|
||||||
|
|
||||||
:param outerWire: the outermost wire, a cad.Wire
|
:param outerWire: the outermost wire, a cad.Wire
|
||||||
:param innerWires: a list of inner wires, a list of cad.Wire
|
:param innerWires: a list of inner wires, a list of cad.Wire
|
||||||
|
|
|
@ -10,7 +10,7 @@ This page documents all of the methods and functions of the CadQuery classes, or
|
||||||
|
|
||||||
For a listing organized by functional area, see the :ref:`apireference`
|
For a listing organized by functional area, see the :ref:`apireference`
|
||||||
|
|
||||||
.. module:: cadquery
|
.. currentmodule:: cadquery
|
||||||
|
|
||||||
Core Classes
|
Core Classes
|
||||||
---------------------
|
---------------------
|
||||||
|
|
125
doc/cqgi.rst
Normal file
125
doc/cqgi.rst
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
.. _cqgi:
|
||||||
|
|
||||||
|
The CadQuery Gateway Interface
|
||||||
|
====================================
|
||||||
|
|
||||||
|
|
||||||
|
CadQuery is first and foremost designed as a library, which can be used as a part of any project.
|
||||||
|
In this context, there is no need for a standard script format or gateway api.
|
||||||
|
|
||||||
|
Though the embedded use case is the most common, several tools have been created which run
|
||||||
|
cadquery scripts on behalf of the user, and then render the result of the script visually.
|
||||||
|
|
||||||
|
These execution environments (EE) generally accept a script and user input values for
|
||||||
|
script parameters, and then display the resulting objects visually to the user.
|
||||||
|
|
||||||
|
Today, three execution environments exist:
|
||||||
|
|
||||||
|
* `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_, which runs scripts
|
||||||
|
inside of the FreeCAD IDE, and displays objects in the display window
|
||||||
|
* the cq-directive, which is used to execute scripts inside of sphinx-doc,
|
||||||
|
producing documented examples that include both a script and an SVG representation of the object that results
|
||||||
|
* `ParametricParts.com <https://www.parametricparts.com>`_, which provides a web-based way to prompt user input for
|
||||||
|
variables, and then display the result output in a web page.
|
||||||
|
|
||||||
|
The CQGI is distributed with cadquery, and standardizes the interface between execution environments and cadquery scripts.
|
||||||
|
|
||||||
|
|
||||||
|
The Script Side
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
CQGI compliant containers provide an execution environment for scripts. The environment includes:
|
||||||
|
|
||||||
|
* the cadquery library is automatically imported as 'cq'.
|
||||||
|
* the :py:meth:`cadquery.cqgi.ScriptCallback.build_object()` method is defined that should be used to export a shape to the execution environment
|
||||||
|
|
||||||
|
Scripts must call build_output at least once. Invoking build_object more than once will send multiple objects to
|
||||||
|
the container. An error will occur if the script does not return an object using the build_object() method.
|
||||||
|
|
||||||
|
Future enhancements will include several other methods, used to provide more metadata for the execution environment:
|
||||||
|
* :py:meth:`cadquery.cqgi.ScriptCallback.add_error()`, indicates an error with an input parameter
|
||||||
|
* :py:meth:`cadquery.cqgi.ScriptCallback.describe_parameter()`, provides extra information about a parameter in the script,
|
||||||
|
|
||||||
|
|
||||||
|
The execution environment side
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
CQGI makes it easy to run cadquery scripts in a standard way. To run a script from an execution environment,
|
||||||
|
run code like this::
|
||||||
|
|
||||||
|
from cadquery import cqgi
|
||||||
|
|
||||||
|
user_script = ...
|
||||||
|
build_result = cqgi.parse(user_script).build()
|
||||||
|
|
||||||
|
The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object.
|
||||||
|
|
||||||
|
Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object,
|
||||||
|
,which includes the script execution time, and a success flag.
|
||||||
|
|
||||||
|
If the script was successful, the results property will include a list of results returned by the script.
|
||||||
|
|
||||||
|
If the script failed, the exception property contains the exception object.
|
||||||
|
|
||||||
|
If your have a way to get inputs from a user, you can override any of the constants defined in the user script
|
||||||
|
with new values::
|
||||||
|
|
||||||
|
from cadquery import cqgi
|
||||||
|
|
||||||
|
user_script = ...
|
||||||
|
build_result = cqgi.parse(user_script).build({ 'param': 2 } )
|
||||||
|
|
||||||
|
If a parameter called 'param' is defined in the model, it will be assigned the value 2 before the script runs.
|
||||||
|
An error will occur if a value is provided that is not defined in the model, or if the value provided cannot
|
||||||
|
be assigned to a variable with the given name.
|
||||||
|
|
||||||
|
More about script variables
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
CQGI uses the following rules to find input variables for a script:
|
||||||
|
|
||||||
|
* only top-level statements are considered
|
||||||
|
* only assignments of constant values to a local name are considered.
|
||||||
|
|
||||||
|
For example, in the following script::
|
||||||
|
|
||||||
|
h = 1.0
|
||||||
|
w = 2.0
|
||||||
|
foo = 'bar'
|
||||||
|
|
||||||
|
def some_function():
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
h, w, and foo will be overridable script variables, but x is not.
|
||||||
|
|
||||||
|
You can list the variables defined in the model by using the return value of the parse method::
|
||||||
|
|
||||||
|
model = cqgi.parse(user_script)
|
||||||
|
|
||||||
|
//a dictionary of InputParameter objects
|
||||||
|
parameters = model.metadata.parameters
|
||||||
|
|
||||||
|
The key of the dictionary is a string , and the value is a :py:class:`cadquery.cqgi.InputParameter` object
|
||||||
|
See the CQGI API docs for more details.
|
||||||
|
|
||||||
|
Future enhancments will include a safer sandbox to prevent malicious scripts.
|
||||||
|
|
||||||
|
Important CQGI Methods
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
These are the most important Methods and classes of the CQGI
|
||||||
|
|
||||||
|
.. currentmodule:: cadquery.cqgi
|
||||||
|
|
||||||
|
.. autosummary::
|
||||||
|
parse
|
||||||
|
CQModel.build
|
||||||
|
BuildResult
|
||||||
|
ScriptCallback.build_object
|
||||||
|
|
||||||
|
Complete CQGI api
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. automodule:: cadquery.cqgi
|
||||||
|
:members:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.. _designprinciples:
|
.. _designprinciples:
|
||||||
|
|
||||||
.. automodule:: cadquery
|
|
||||||
|
|
||||||
===========================
|
===========================
|
||||||
CadQuery Design Principles
|
CadQuery Design Principles
|
||||||
|
|
381
doc/examples.rst
381
doc/examples.rst
|
@ -4,7 +4,6 @@
|
||||||
CadQuery Examples
|
CadQuery Examples
|
||||||
*********************************
|
*********************************
|
||||||
|
|
||||||
.. automodule:: cadquery
|
|
||||||
|
|
||||||
|
|
||||||
The examples on this page can help you learn how to build objects with CadQuery.
|
The examples on this page can help you learn how to build objects with CadQuery.
|
||||||
|
@ -64,9 +63,18 @@ of a working plane is at the center of the face. The default hole depth is thro
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(2.0,2.0,0.5).faces(">Z").hole(0.5)
|
# The dimensions of the box. These can be modified rather than changing the
|
||||||
build_output(result)
|
# object's code directly.
|
||||||
|
length = 80.0
|
||||||
|
height = 60.0
|
||||||
|
thickness = 10.0
|
||||||
|
center_hole_dia = 22.0
|
||||||
|
|
||||||
|
# Create a box based on the dimensions above and add a 22mm center hole
|
||||||
|
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||||
|
.faces(">Z").workplane().hole(center_hole_dia)
|
||||||
|
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -88,7 +96,8 @@ By default, rectangles and circles are centered around the previous working poin
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").circle(2.0).rect(0.5,0.75).extrude(0.5)
|
result = cq.Workplane("front").circle(2.0).rect(0.5,0.75).extrude(0.5)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -112,8 +121,9 @@ closed curve.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").lineTo(2.0,0).lineTo(2.0,1.0).threePointArc((1.0,1.5),(0.0,1.0))\
|
result = cq.Workplane("front").lineTo(2.0,0).lineTo(2.0,1.0).threePointArc((1.0,1.5),(0.0,1.0))\
|
||||||
.close().extrude(0.25)
|
.close().extrude(0.25)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
@ -138,13 +148,14 @@ A new work plane center can be established at any point.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").circle(3.0) #current point is the center of the circle, at (0,0)
|
result = cq.Workplane("front").circle(3.0) #current point is the center of the circle, at (0,0)
|
||||||
result = result.center(1.5,0.0).rect(0.5,0.5) # new work center is (1.5,0.0)
|
result = result.center(1.5,0.0).rect(0.5,0.5) # new work center is (1.5,0.0)
|
||||||
|
|
||||||
result = result.center(-1.5,1.5).circle(0.25) # new work center is ( 0.0,1.5).
|
result = result.center(-1.5,1.5).circle(0.25) # new work center is ( 0.0,1.5).
|
||||||
#the new center is specified relative to the previous center, not global coordinates!
|
#the new center is specified relative to the previous center, not global coordinates!
|
||||||
|
|
||||||
result = result.extrude(0.25)
|
result = result.extrude(0.25)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
@ -169,10 +180,11 @@ like :py:meth:`Workplane.circle` and :py:meth:`Workplane.rect`, will operate on
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
r = Workplane("front").circle(2.0) # make base
|
r = cq.Workplane("front").circle(2.0) # make base
|
||||||
r = r.pushPoints( [ (1.5,0),(0,1.5),(-1.5,0),(0,-1.5) ] ) # now four points are on the stack
|
r = r.pushPoints( [ (1.5,0),(0,1.5),(-1.5,0),(0,-1.5) ] ) # now four points are on the stack
|
||||||
r = r.circle( 0.25 ) # circle will operate on all four points
|
r = r.circle( 0.25 ) # circle will operate on all four points
|
||||||
result = r.extrude(0.125 ) # make prism
|
result = r.extrude(0.125 ) # make prism
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -192,8 +204,9 @@ correct for small hole sizes.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(3.0,4.0,0.25).pushPoints ( [ ( 0,0.75 ),(0,-0.75) ]) \
|
result = cq.Workplane("front").box(3.0,4.0,0.25).pushPoints ( [ ( 0,0.75 ),(0,-0.75) ]) \
|
||||||
.polygon(6,1.0).cutThruAll()
|
.polygon(6,1.0).cutThruAll()
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -224,7 +237,8 @@ This example uses a polyline to create one half of an i-beam shape, which is mir
|
||||||
(W/2.0,H/-2.0),
|
(W/2.0,H/-2.0),
|
||||||
(0,H/-2.0)
|
(0,H/-2.0)
|
||||||
]
|
]
|
||||||
result = Workplane("front").polyline(pts).mirrorY().extrude(L)
|
result = cq.Workplane("front").polyline(pts).mirrorY().extrude(L)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -246,7 +260,7 @@ needs a complex profile
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
s = Workplane("XY")
|
s = cq.Workplane("XY")
|
||||||
sPnts = [
|
sPnts = [
|
||||||
(2.75,1.5),
|
(2.75,1.5),
|
||||||
(2.5,1.75),
|
(2.5,1.75),
|
||||||
|
@ -258,6 +272,7 @@ needs a complex profile
|
||||||
]
|
]
|
||||||
r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close()
|
r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close()
|
||||||
result = r.extrude(0.5)
|
result = r.extrude(0.5)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -279,9 +294,10 @@ introduce horizontal and vertical lines, which make for slightly easier coding.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
r = Workplane("front").hLine(1.0) # 1.0 is the distance, not coordinate
|
r = cq.Workplane("front").hLine(1.0) # 1.0 is the distance, not coordinate
|
||||||
r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0) # hLineTo allows using xCoordinate not distance
|
r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0) # hLineTo allows using xCoordinate not distance
|
||||||
result =r.mirrorY().extrude(0.25 ) # mirror the geometry and extrude
|
result =r.mirrorY().extrude(0.25 ) # mirror the geometry and extrude
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -315,8 +331,9 @@ Keep in mind that the origin of new workplanes are located at the center of a fa
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(2,3,0.5) #make a basic prism
|
result = cq.Workplane("front").box(2,3,0.5) #make a basic prism
|
||||||
result = result.faces(">Z").workplane().hole(0.5) #find the top-most face and make a hole
|
result = result.faces(">Z").workplane().hole(0.5) #find the top-most face and make a hole
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -342,9 +359,10 @@ how deep the part is
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(3,2,0.5) #make a basic prism
|
result = cq.Workplane("front").box(3,2,0.5) #make a basic prism
|
||||||
result = result.faces(">Z").vertices("<XY").workplane() #select the lower left vertex and make a workplane
|
result = result.faces(">Z").vertices("<XY").workplane() #select the lower left vertex and make a workplane
|
||||||
result = result.circle(1.0).cutThruAll() #cut the corner out
|
result = result.circle(1.0).cutThruAll() #cut the corner out
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -369,9 +387,10 @@ This example uses an offset workplane to make a compound object, which is perfec
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(3,2,0.5) #make a basic prism
|
result = cq.Workplane("front").box(3,2,0.5) #make a basic prism
|
||||||
result = result.faces("<X").workplane(offset=0.75) #workplane is offset from the object surface
|
result = result.faces("<X").workplane(offset=0.75) #workplane is offset from the object surface
|
||||||
result = result.circle(1.0).extrude(0.5) #disc
|
result = result.circle(1.0).extrude(0.5) #disc
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -390,9 +409,10 @@ You can create a rotated work plane by specifying angles of rotation relative to
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(4.0,4.0,0.25).faces(">Z").workplane() \
|
result = cq.Workplane("front").box(4.0,4.0,0.25).faces(">Z").workplane() \
|
||||||
.transformed(offset=Vector(0,-1.5,1.0),rotate=Vector(60,0,0)) \
|
.transformed(offset=cq.Vector(0,-1.5,1.0),rotate=cq.Vector(60,0,0)) \
|
||||||
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25)
|
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.25)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -414,8 +434,9 @@ In the example below, a rectangle is drawn, and its vertices are used to locate
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(2,2,0.5).faces(">Z").workplane() \
|
result = cq.Workplane("front").box(2,2,0.5).faces(">Z").workplane() \
|
||||||
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.125 )
|
.rect(1.5,1.5,forConstruction=True).vertices().hole(0.125 )
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -438,7 +459,8 @@ are removed, and then the inside of the solid is 'hollowed out' to make the shel
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(2,2,2).faces("+Z").shell(0.05)
|
result = cq.Workplane("front").box(2,2,2).faces("+Z").shell(0.05)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -458,9 +480,11 @@ and a circular section.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("front").box(4.0,4.0,0.25).faces(">Z").circle(1.5) \
|
result = cq.Workplane("front").box(4.0,4.0,0.25).faces(">Z").circle(1.5) \
|
||||||
.workplane(offset=3.0).rect(0.75,0.5).loft(combine=True)
|
.workplane(offset=3.0).rect(0.75,0.5).loft(combine=True)
|
||||||
|
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
.. hlist::
|
.. hlist::
|
||||||
|
@ -481,9 +505,11 @@ Similar to :py:meth:`Workplane.hole` , these functions operate on a list of poin
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane(Plane.XY()).box(4,2,0.5).faces(">Z").workplane().rect(3.5,1.5,forConstruction=True)\
|
result = cq.Workplane(cq.Plane.XY()).box(4,2,0.5).faces(">Z").workplane().rect(3.5,1.5,forConstruction=True)\
|
||||||
.vertices().cboreHole(0.125, 0.25,0.125,depth=None)
|
.vertices().cboreHole(0.125, 0.25,0.125,depth=None)
|
||||||
|
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
.. hlist::
|
.. hlist::
|
||||||
|
@ -507,7 +533,8 @@ Here we fillet all of the edges of a simple plate.
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
result = Workplane("XY" ).box(3,3,0.5).edges("|Z").fillet(0.125)
|
result = cq.Workplane("XY" ).box(3,3,0.5).edges("|Z").fillet(0.125)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -529,12 +556,12 @@ with just a few lines of code.
|
||||||
|
|
||||||
(length,height,bearing_diam, thickness,padding) = ( 30.0,40.0,22.0,10.0,8.0)
|
(length,height,bearing_diam, thickness,padding) = ( 30.0,40.0,22.0,10.0,8.0)
|
||||||
|
|
||||||
result = Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam) \
|
result = cq.Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(bearing_diam) \
|
||||||
.faces(">Z").workplane() \
|
.faces(">Z").workplane() \
|
||||||
.rect(length-padding,height-padding,forConstruction=True) \
|
.rect(length-padding,height-padding,forConstruction=True) \
|
||||||
.vertices().cboreHole(2.4,4.4,2.1)
|
.vertices().cboreHole(2.4,4.4,2.1)
|
||||||
|
|
||||||
build_output(result)
|
build_object(result)
|
||||||
|
|
||||||
|
|
||||||
Splitting an Object
|
Splitting an Object
|
||||||
|
@ -544,10 +571,11 @@ You can split an object using a workplane, and retain either or both halves
|
||||||
|
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
c = Workplane("XY").box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
c = cq.Workplane("XY").box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
||||||
|
|
||||||
#now cut it in half sideways
|
#now cut it in half sideways
|
||||||
result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
|
result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -577,7 +605,7 @@ ones at 13 lines, but that's very short compared to the pythonOCC version, which
|
||||||
.. cq_plot::
|
.. cq_plot::
|
||||||
|
|
||||||
(L,w,t) = (20.0,6.0,3.0)
|
(L,w,t) = (20.0,6.0,3.0)
|
||||||
s = Workplane("XY")
|
s = cq.Workplane("XY")
|
||||||
|
|
||||||
#draw half the profile of the bottle and extrude it
|
#draw half the profile of the bottle and extrude it
|
||||||
p = s.center(-L/2.0,0).vLine(w/2.0) \
|
p = s.center(-L/2.0,0).vLine(w/2.0) \
|
||||||
|
@ -589,6 +617,7 @@ ones at 13 lines, but that's very short compared to the pythonOCC version, which
|
||||||
|
|
||||||
#make a shell
|
#make a shell
|
||||||
result = p.faces(">Z").shell(0.3)
|
result = p.faces(">Z").shell(0.3)
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
|
@ -631,7 +660,7 @@ A Parametric Enclosure
|
||||||
p_lipHeight = 1.0 #Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.
|
p_lipHeight = 1.0 #Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.
|
||||||
|
|
||||||
#outer shell
|
#outer shell
|
||||||
oshell = Workplane("XY").rect(p_outerWidth,p_outerLength).extrude(p_outerHeight + p_lipHeight)
|
oshell = cq.Workplane("XY").rect(p_outerWidth,p_outerLength).extrude(p_outerHeight + p_lipHeight)
|
||||||
|
|
||||||
#weird geometry happens if we make the fillets in the wrong order
|
#weird geometry happens if we make the fillets in the wrong order
|
||||||
if p_sideRadius > p_topAndBottomRadius:
|
if p_sideRadius > p_topAndBottomRadius:
|
||||||
|
@ -687,6 +716,8 @@ A Parametric Enclosure
|
||||||
#return the combined result
|
#return the combined result
|
||||||
result =topOfLid.combineSolids(bottom)
|
result =topOfLid.combineSolids(bottom)
|
||||||
|
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
.. topic:: Api References
|
.. topic:: Api References
|
||||||
|
|
||||||
.. hlist::
|
.. hlist::
|
||||||
|
@ -709,9 +740,299 @@ A Parametric Enclosure
|
||||||
* :py:meth:`Workplane.cskHole`
|
* :py:meth:`Workplane.cskHole`
|
||||||
* :py:meth:`Workplane.hole`
|
* :py:meth:`Workplane.hole`
|
||||||
|
|
||||||
|
Lego Brick
|
||||||
|
-------------------
|
||||||
|
|
||||||
More Examples available in cadquery-freecad-modules
|
This script will produce any size regular rectangular Lego(TM) brick. Its only tricky because of the logic
|
||||||
----------------------------------------------------
|
regarding the underside of the brick.
|
||||||
|
|
||||||
If you have installed the `cadquery-freecad-module <https://github.com/jmwright/cadquery-freecad-module>`_,
|
.. cq_plot::
|
||||||
there are > 20 examples available that you can interact with
|
:height: 400
|
||||||
|
|
||||||
|
#####
|
||||||
|
# Inputs
|
||||||
|
######
|
||||||
|
lbumps = 6 # number of bumps long
|
||||||
|
wbumps = 2 # number of bumps wide
|
||||||
|
thin = True # True for thin, False for thick
|
||||||
|
|
||||||
|
#
|
||||||
|
# Lego Brick Constants-- these make a lego brick a lego :)
|
||||||
|
#
|
||||||
|
pitch = 8.0
|
||||||
|
clearance = 0.1
|
||||||
|
bumpDiam = 4.8
|
||||||
|
bumpHeight = 1.8
|
||||||
|
if thin:
|
||||||
|
height = 3.2
|
||||||
|
else:
|
||||||
|
height = 9.6
|
||||||
|
|
||||||
|
t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||||
|
postDiam = pitch - t # works out to 6.5
|
||||||
|
total_length = lbumps*pitch - 2.0*clearance
|
||||||
|
total_width = wbumps*pitch - 2.0*clearance
|
||||||
|
|
||||||
|
# make the base
|
||||||
|
s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||||
|
|
||||||
|
# shell inwards not outwards
|
||||||
|
s = s.faces("<Z").shell(-1.0 * t)
|
||||||
|
|
||||||
|
# make the bumps on the top
|
||||||
|
s = s.faces(">Z").workplane(). \
|
||||||
|
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
||||||
|
.extrude(bumpHeight)
|
||||||
|
|
||||||
|
# add posts on the bottom. posts are different diameter depending on geometry
|
||||||
|
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||||
|
tmp = s.faces("<Z").workplane(invert=True)
|
||||||
|
|
||||||
|
if lbumps > 1 and wbumps > 1:
|
||||||
|
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
||||||
|
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
||||||
|
elif lbumps > 1:
|
||||||
|
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
||||||
|
circle(t).extrude(height - t)
|
||||||
|
elif wbumps > 1:
|
||||||
|
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
||||||
|
circle(t).extrude(height - t)
|
||||||
|
else:
|
||||||
|
tmp = s
|
||||||
|
|
||||||
|
# Render the solid
|
||||||
|
build_object(tmp)
|
||||||
|
|
||||||
|
|
||||||
|
Braille Example
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. cq_plot::
|
||||||
|
:height: 400
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, division
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
# text_lines is a list of text lines.
|
||||||
|
# FreeCAD in braille (converted with braille-converter:
|
||||||
|
# https://github.com/jpaugh/braille-converter.git).
|
||||||
|
text_lines = ['⠠ ⠋ ⠗ ⠑ ⠑ ⠠ ⠉ ⠠ ⠁ ⠠ ⠙']
|
||||||
|
# See http://www.tiresias.org/research/reports/braille_cell.htm for examples
|
||||||
|
# of braille cell geometry.
|
||||||
|
horizontal_interdot = 2.5
|
||||||
|
vertical_interdot = 2.5
|
||||||
|
horizontal_intercell = 6
|
||||||
|
vertical_interline = 10
|
||||||
|
dot_height = 0.5
|
||||||
|
dot_diameter = 1.3
|
||||||
|
|
||||||
|
base_thickness = 1.5
|
||||||
|
|
||||||
|
# End of configuration.
|
||||||
|
BrailleCellGeometry = namedtuple('BrailleCellGeometry',
|
||||||
|
('horizontal_interdot',
|
||||||
|
'vertical_interdot',
|
||||||
|
'intercell',
|
||||||
|
'interline',
|
||||||
|
'dot_height',
|
||||||
|
'dot_diameter'))
|
||||||
|
|
||||||
|
|
||||||
|
class Point(object):
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return Point(self.x + other.x, self.y + other.y)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return (self.x, self.y)[index]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '({}, {})'.format(self.x, self.y)
|
||||||
|
|
||||||
|
|
||||||
|
def brailleToPoints(text, cell_geometry):
|
||||||
|
# Unicode bit pattern (cf. https://en.wikipedia.org/wiki/Braille_Patterns).
|
||||||
|
mask1 = 0b00000001
|
||||||
|
mask2 = 0b00000010
|
||||||
|
mask3 = 0b00000100
|
||||||
|
mask4 = 0b00001000
|
||||||
|
mask5 = 0b00010000
|
||||||
|
mask6 = 0b00100000
|
||||||
|
mask7 = 0b01000000
|
||||||
|
mask8 = 0b10000000
|
||||||
|
masks = (mask1, mask2, mask3, mask4, mask5, mask6, mask7, mask8)
|
||||||
|
|
||||||
|
# Corresponding dot position
|
||||||
|
w = cell_geometry.horizontal_interdot
|
||||||
|
h = cell_geometry.vertical_interdot
|
||||||
|
pos1 = Point(0, 2 * h)
|
||||||
|
pos2 = Point(0, h)
|
||||||
|
pos3 = Point(0, 0)
|
||||||
|
pos4 = Point(w, 2 * h)
|
||||||
|
pos5 = Point(w, h)
|
||||||
|
pos6 = Point(w, 0)
|
||||||
|
pos7 = Point(0, -h)
|
||||||
|
pos8 = Point(w, -h)
|
||||||
|
pos = (pos1, pos2, pos3, pos4, pos5, pos6, pos7, pos8)
|
||||||
|
|
||||||
|
# Braille blank pattern (u'\u2800').
|
||||||
|
blank = '⠀'
|
||||||
|
points = []
|
||||||
|
# Position of dot1 along the x-axis (horizontal).
|
||||||
|
character_origin = 0
|
||||||
|
for c in text:
|
||||||
|
for m, p in zip(masks, pos):
|
||||||
|
delta_to_blank = ord(c) - ord(blank)
|
||||||
|
if (m & delta_to_blank):
|
||||||
|
points.append(p + Point(character_origin, 0))
|
||||||
|
character_origin += cell_geometry.intercell
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def get_plate_height(text_lines, cell_geometry):
|
||||||
|
# cell_geometry.vertical_interdot is also used as space between base
|
||||||
|
# borders and characters.
|
||||||
|
return (2 * cell_geometry.vertical_interdot +
|
||||||
|
2 * cell_geometry.vertical_interdot +
|
||||||
|
(len(text_lines) - 1) * cell_geometry.interline)
|
||||||
|
|
||||||
|
|
||||||
|
def get_plate_width(text_lines, cell_geometry):
|
||||||
|
# cell_geometry.horizontal_interdot is also used as space between base
|
||||||
|
# borders and characters.
|
||||||
|
max_len = max([len(t) for t in text_lines])
|
||||||
|
return (2 * cell_geometry.horizontal_interdot +
|
||||||
|
cell_geometry.horizontal_interdot +
|
||||||
|
(max_len - 1) * cell_geometry.intercell)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cylinder_radius(cell_geometry):
|
||||||
|
"""Return the radius the cylinder should have
|
||||||
|
The cylinder have the same radius as the half-sphere make the dots (the
|
||||||
|
hidden and the shown part of the dots).
|
||||||
|
The radius is such that the spherical cap with diameter
|
||||||
|
cell_geometry.dot_diameter has a height of cell_geometry.dot_height.
|
||||||
|
"""
|
||||||
|
h = cell_geometry.dot_height
|
||||||
|
r = cell_geometry.dot_diameter / 2
|
||||||
|
return (r ** 2 + h ** 2) / 2 / h
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_plate_thickness(plate_thickness, cell_geometry):
|
||||||
|
"""Return the height on which the half spheres will sit"""
|
||||||
|
return (plate_thickness +
|
||||||
|
get_cylinder_radius(cell_geometry) -
|
||||||
|
cell_geometry.dot_height)
|
||||||
|
|
||||||
|
|
||||||
|
def make_base(text_lines, cell_geometry, plate_thickness):
|
||||||
|
base_width = get_plate_width(text_lines, cell_geometry)
|
||||||
|
base_height = get_plate_height(text_lines, cell_geometry)
|
||||||
|
base_thickness = get_base_plate_thickness(plate_thickness, cell_geometry)
|
||||||
|
base = cq.Workplane('XY').box(base_width, base_height, base_thickness,
|
||||||
|
centered=(False, False, False))
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def make_embossed_plate(text_lines, cell_geometry):
|
||||||
|
"""Make an embossed plate with dots as spherical caps
|
||||||
|
Method:
|
||||||
|
- make a thin plate on which sit cylinders
|
||||||
|
- fillet the upper edge of the cylinders so to get pseudo half-spheres
|
||||||
|
- make the union with a thicker plate so that only the sphere caps stay
|
||||||
|
"visible".
|
||||||
|
"""
|
||||||
|
base = make_base(text_lines, cell_geometry, base_thickness)
|
||||||
|
|
||||||
|
dot_pos = []
|
||||||
|
base_width = get_plate_width(text_lines, cell_geometry)
|
||||||
|
base_height = get_plate_height(text_lines, cell_geometry)
|
||||||
|
y = base_height - 3 * cell_geometry.vertical_interdot
|
||||||
|
line_start_pos = Point(cell_geometry.horizontal_interdot, y)
|
||||||
|
for text in text_lines:
|
||||||
|
dots = brailleToPoints(text, cell_geometry)
|
||||||
|
dots = [p + line_start_pos for p in dots]
|
||||||
|
dot_pos += dots
|
||||||
|
line_start_pos += Point(0, -cell_geometry.interline)
|
||||||
|
|
||||||
|
r = get_cylinder_radius(cell_geometry)
|
||||||
|
base = base.faces('>Z').vertices('<XY').workplane() \
|
||||||
|
.pushPoints(dot_pos).circle(r) \
|
||||||
|
.extrude(r)
|
||||||
|
# Make a fillet almost the same radius to get a pseudo spherical cap.
|
||||||
|
base = base.faces('>Z').edges() \
|
||||||
|
.fillet(r - 0.001)
|
||||||
|
hidding_box = cq.Workplane('XY').box(
|
||||||
|
base_width, base_height, base_thickness, centered=(False, False, False))
|
||||||
|
result = hidding_box.union(base)
|
||||||
|
return result
|
||||||
|
|
||||||
|
_cell_geometry = BrailleCellGeometry(
|
||||||
|
horizontal_interdot,
|
||||||
|
vertical_interdot,
|
||||||
|
horizontal_intercell,
|
||||||
|
vertical_interline,
|
||||||
|
dot_height,
|
||||||
|
dot_diameter)
|
||||||
|
|
||||||
|
if base_thickness < get_cylinder_radius(_cell_geometry):
|
||||||
|
raise ValueError('Base thickness should be at least {}'.format(dot_height))
|
||||||
|
|
||||||
|
build_object(make_embossed_plate(text_lines, _cell_geometry))
|
||||||
|
|
||||||
|
Panel With Various Connector Holes
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
.. cq_plot::
|
||||||
|
:height: 400
|
||||||
|
|
||||||
|
# The dimensions of the model. These can be modified rather than changing the
|
||||||
|
# object's code directly.
|
||||||
|
width = 400
|
||||||
|
height = 500
|
||||||
|
thickness = 2
|
||||||
|
|
||||||
|
# Create a plate with two polygons cut through it
|
||||||
|
result = cq.Workplane("front").box(width, height, thickness)
|
||||||
|
|
||||||
|
h_sep = 60
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(157,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(157,-30-idx*h_sep).moveTo(-16.65,0).circle(1.6).moveTo(16.65,0).circle(1.6).moveTo(-10.1889,-5.7).threePointArc((-12.59306,-4.70416),(-13.5889,-2.3)).lineTo(-14.4,2.3).threePointArc((-13.40416,4.70416),(-11,5.7)).lineTo(11,5.7).threePointArc((13.40416,4.70416),(14.4,2.3)).lineTo(13.5889,-2.3).threePointArc((12.59306,-4.70416),(10.1889,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
h_sep4DB9 = 30
|
||||||
|
for idx in range(8):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(91,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(25,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(25,-30-idx*h_sep).moveTo(-16.65,0).circle(1.6).moveTo(16.65,0).circle(1.6).moveTo(-10.1889,-5.7).threePointArc((-12.59306,-4.70416),(-13.5889,-2.3)).lineTo(-14.4,2.3).threePointArc((-13.40416,4.70416),(-11,5.7)).lineTo(11,5.7).threePointArc((13.40416,4.70416),(14.4,2.3)).lineTo(13.5889,-2.3).threePointArc((12.59306,-4.70416),(10.1889,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(8):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-41,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-107,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-107,-30-idx*h_sep).circle(14).rect(24.7487,24.7487, forConstruction=True).vertices().hole(3.2).cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(8):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-173,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll()
|
||||||
|
|
||||||
|
for idx in range(4):
|
||||||
|
result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-173,-30-idx*h_sep).moveTo(-2.9176,-5.3).threePointArc((-6.05,0),(-2.9176,5.3)).lineTo(2.9176,5.3).threePointArc((6.05,0),(2.9176,-5.3)).close().cutThruAll()
|
||||||
|
|
||||||
|
# Render the solid
|
||||||
|
build_object(result)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
Extending CadQuery
|
Extending CadQuery
|
||||||
======================
|
======================
|
||||||
|
|
||||||
.. module:: cadquery
|
|
||||||
|
|
||||||
If you find that CadQuery doesnt suit your needs, you can easily extend it. CadQuery provides several extension
|
If you find that CadQuery doesnt suit your needs, you can easily extend it. CadQuery provides several extension
|
||||||
methods:
|
methods:
|
||||||
|
@ -19,7 +18,7 @@ Using FreeCAD Script
|
||||||
The easiest way to extend CadQuery is to simply use FreeCAD script inside of your build method. Just about
|
The easiest way to extend CadQuery is to simply use FreeCAD script inside of your build method. Just about
|
||||||
any valid FreeCAD script will execute just fine. For example, this simple CadQuery script::
|
any valid FreeCAD script will execute just fine. For example, this simple CadQuery script::
|
||||||
|
|
||||||
return Workplane("XY").box(1.0,2.0,3.0).val()
|
return cq.Workplane("XY").box(1.0,2.0,3.0).val()
|
||||||
|
|
||||||
is actually equivalent to::
|
is actually equivalent to::
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ a lot of the complexity of the FreeCAD api.
|
||||||
|
|
||||||
You can get the best of both worlds by wrapping your freecad script into a CadQuery plugin.
|
You can get the best of both worlds by wrapping your freecad script into a CadQuery plugin.
|
||||||
|
|
||||||
A CadQuery plugin is simply a function that is attached to the CadQuery :py:meth:`cadquery.CQ.CQ` or :py:meth:`cadquery.CQ.Workplane` class.
|
A CadQuery plugin is simply a function that is attached to the CadQuery :py:meth:`cadquery.CQ` or :py:meth:`cadquery.Workplane` class.
|
||||||
When connected, your plugin can be used in the chain just like the built-in functions.
|
When connected, your plugin can be used in the chain just like the built-in functions.
|
||||||
|
|
||||||
There are a few key concepts important to understand when building a plugin
|
There are a few key concepts important to understand when building a plugin
|
||||||
|
@ -68,14 +67,14 @@ Preserving the Chain
|
||||||
|
|
||||||
CadQuery's fluent api relies on the ability to chain calls together one after another. For this to work,
|
CadQuery's fluent api relies on the ability to chain calls together one after another. For this to work,
|
||||||
you must return a valid CadQuery object as a return value. If you choose not to return a CadQuery object,
|
you must return a valid CadQuery object as a return value. If you choose not to return a CadQuery object,
|
||||||
then your plugin will end the chain. Sometimes this is desired for example :py:meth:`cadquery.CQ.CQ.size`
|
then your plugin will end the chain. Sometimes this is desired for example :py:meth:`cadquery.CQ.size`
|
||||||
|
|
||||||
There are two ways you can safely continue the chain:
|
There are two ways you can safely continue the chain:
|
||||||
|
|
||||||
1. **return self** If you simply wish to modify the stack contents, you can simply return a reference to
|
1. **return self** If you simply wish to modify the stack contents, you can simply return a reference to
|
||||||
self. This approach is destructive, because the contents of the stack are modified, but it is also the
|
self. This approach is destructive, because the contents of the stack are modified, but it is also the
|
||||||
simplest.
|
simplest.
|
||||||
2. :py:meth:`cadquery.CQ.CQ.newObject` Most of the time, you will want to return a new object. Using newObject will
|
2. :py:meth:`cadquery.CQ.newObject` Most of the time, you will want to return a new object. Using newObject will
|
||||||
return a new CQ or Workplane object having the stack you specify, and will link this object to the
|
return a new CQ or Workplane object having the stack you specify, and will link this object to the
|
||||||
previous one. This preserves the original object and its stack.
|
previous one. This preserves the original object and its stack.
|
||||||
|
|
||||||
|
@ -87,29 +86,26 @@ When you implement a CadQuery plugin, you are extending CadQuery's base objects.
|
||||||
CadQuery or Workplane methods from inside of your extension. You can also call a number of internal methods that
|
CadQuery or Workplane methods from inside of your extension. You can also call a number of internal methods that
|
||||||
are designed to aid in plugin creation:
|
are designed to aid in plugin creation:
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane._pointsOnStack` returns a FreeCAD Vector ( a point ) for each item on the stack. Useful if you
|
|
||||||
are writing a plugin that you'd like to operate on all values on the stack, like :py:meth:`cadquery.CQ.Workplane.circle` and
|
|
||||||
most other built-ins do
|
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane._makeWireAtPoints` will invoke a factory function you supply for all points on the stack,
|
* :py:meth:`cadquery.Workplane._makeWireAtPoints` will invoke a factory function you supply for all points on the stack,
|
||||||
and return a properly constructed cadquery object. This function takes care of registering wires for you
|
and return a properly constructed cadquery object. This function takes care of registering wires for you
|
||||||
and everything like that
|
and everything like that
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane.newObject` returns a new Workplane object with the provided stack, and with its parent set
|
* :py:meth:`cadquery.Workplane.newObject` returns a new Workplane object with the provided stack, and with its parent set
|
||||||
to the current object. The preferred way to continue the chain
|
to the current object. The preferred way to continue the chain
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane.findSolid` returns the first Solid found in the chain, working from the current object upwards
|
* :py:meth:`cadquery.CQ.findSolid` returns the first Solid found in the chain, working from the current object upwards
|
||||||
in the chain. commonly used when your plugin will modify an existing solid, or needs to create objects and
|
in the chain. commonly used when your plugin will modify an existing solid, or needs to create objects and
|
||||||
then combine them onto the 'main' part that is in progress
|
then combine them onto the 'main' part that is in progress
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane._addWire` must be called if you add a wire. This allows the base class to track all the wires
|
* :py:meth:`cadquery.Workplane._addPendingWire` must be called if you add a wire. This allows the base class to track all the wires
|
||||||
that are created, so that they can be managed when extrusion occurs.
|
that are created, so that they can be managed when extrusion occurs.
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane.wire` gathers up all of the edges that have been drawn ( eg, by line, vline, etc ), and
|
* :py:meth:`cadquery.Workplane.wire` gathers up all of the edges that have been drawn ( eg, by line, vline, etc ), and
|
||||||
attempts to combine them into a single wire, which is returned. This should be used when your plugin creates
|
attempts to combine them into a single wire, which is returned. This should be used when your plugin creates
|
||||||
2-d edges, and you know it is time to collect them into a single wire.
|
2-d edges, and you know it is time to collect them into a single wire.
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane.plane` provides a reference to the workplane, which allows you to convert between workplane
|
* :py:meth:`cadquery.Workplane.plane` provides a reference to the workplane, which allows you to convert between workplane
|
||||||
coordinates and global coordinates:
|
coordinates and global coordinates:
|
||||||
* :py:meth:`cadquery.freecad_impl.geom.Plane.toWorldCoords` will convert local coordinates to global ones
|
* :py:meth:`cadquery.freecad_impl.geom.Plane.toWorldCoords` will convert local coordinates to global ones
|
||||||
* :py:meth:`cadquery.freecad_impl.geom.Plane.toLocalCoords` will convet from global coordinates to local coordinates
|
* :py:meth:`cadquery.freecad_impl.geom.Plane.toLocalCoords` will convet from global coordinates to local coordinates
|
||||||
|
@ -138,7 +134,7 @@ To install it, simply attach it to the CadQuery or Workplane object, like this::
|
||||||
do stuff
|
do stuff
|
||||||
return whatever_you_want
|
return whatever_you_want
|
||||||
|
|
||||||
Workplane.yourPlugin = _yourFunction
|
cq.Workplane.yourPlugin = _yourFunction
|
||||||
|
|
||||||
That's it!
|
That's it!
|
||||||
|
|
||||||
|
@ -147,8 +143,8 @@ CadQueryExample Plugins
|
||||||
Some core cadquery code is intentionally written exactly like a plugin.
|
Some core cadquery code is intentionally written exactly like a plugin.
|
||||||
If you are writing your own plugins, have a look at these methods for inspiration:
|
If you are writing your own plugins, have a look at these methods for inspiration:
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.Workplane.polygon`
|
* :py:meth:`cadquery.Workplane.polygon`
|
||||||
* :py:meth:`cadquery.CQ.Workplane.cboreHole`
|
* :py:meth:`cadquery.Workplane.cboreHole`
|
||||||
|
|
||||||
|
|
||||||
Plugin Example
|
Plugin Example
|
||||||
|
@ -167,18 +163,18 @@ This ultra simple plugin makes cubes of the specified size for each stack point.
|
||||||
def _singleCube(pnt):
|
def _singleCube(pnt):
|
||||||
#pnt is a location in local coordinates
|
#pnt is a location in local coordinates
|
||||||
#since we're using eachpoint with useLocalCoordinates=True
|
#since we're using eachpoint with useLocalCoordinates=True
|
||||||
return Solid.makeBox(length,length,length,pnt)
|
return cq.Solid.makeBox(length,length,length,pnt)
|
||||||
|
|
||||||
#use CQ utility method to iterate over the stack, call our
|
#use CQ utility method to iterate over the stack, call our
|
||||||
#method, and convert to/from local coordinates.
|
#method, and convert to/from local coordinates.
|
||||||
return self.eachpoint(_singleCube,True)
|
return self.eachpoint(_singleCube,True)
|
||||||
|
|
||||||
#link the plugin into cadQuery
|
#link the plugin into cadQuery
|
||||||
Workplane.makeCubes = makeCubes
|
cq.Workplane.makeCubes = makeCubes
|
||||||
|
|
||||||
#use the plugin
|
#use the plugin
|
||||||
result = Workplane("XY").box(6.0,8.0,0.5).faces(">Z")\
|
result = cq.Workplane("XY").box(6.0,8.0,0.5).faces(">Z")\
|
||||||
.rect(4.0,4.0,forConstruction=True).vertices() \
|
.rect(4.0,4.0,forConstruction=True).vertices() \
|
||||||
.makeCubes(1.0).combineSolids()
|
.makeCubes(1.0).combineSolids()
|
||||||
|
build_object(result)
|
||||||
|
|
||||||
|
|
|
@ -3,40 +3,23 @@
|
||||||
CadQuery Scripts and Object Output
|
CadQuery Scripts and Object Output
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
CadQuery scripts are pure python scripts that follow a standard format.
|
CadQuery scripts are pure python scripts, that may follow a few conventions.
|
||||||
|
|
||||||
|
If you are using cadquery as a library, there are no constraints.
|
||||||
|
|
||||||
|
If you are using cadquery scripts inside of a caduquer execution environment,
|
||||||
|
like `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_ or
|
||||||
|
`parametricParts.com <https://www.parametricparts.com>`_, there are a few conventions you need to be aware of:
|
||||||
|
|
||||||
|
* cadquery is already imported as 'cq'
|
||||||
|
* to return an object to the container, you need to call the build_object() method.
|
||||||
|
|
||||||
|
See the
|
||||||
Each script generally has three sections:
|
Each script generally has three sections:
|
||||||
|
|
||||||
* Variable Assignments
|
* Variable Assignments
|
||||||
* cadquery and other python code
|
* cadquery and other python code
|
||||||
* object exports, via the export_object() function
|
* object exports, via the export_object() function
|
||||||
|
|
||||||
Execution Environments
|
|
||||||
-----------------------
|
|
||||||
When your script runs, the container does not know which objects you wish to yeild for output.
|
|
||||||
Further, what 'output' means is different depending on the execution environment.
|
|
||||||
|
|
||||||
Most containers supply an export_object() method that allows you to export an object.
|
|
||||||
|
|
||||||
There are three execution environments:
|
|
||||||
|
|
||||||
1. **Native Library**. In this context, there is no execution environment. Your scripts will only generate output
|
|
||||||
when you manually invoke a method to save your object to disk, for example using the exporters library
|
|
||||||
|
|
||||||
1. **cadquery-freecad-module**. In this context, exporting an object means displaying it on the screen, and
|
|
||||||
registering it with FreeCAD for further manipulation.
|
|
||||||
|
|
||||||
2. **parametricparts.com** In this context, exporting an object means exporting it into a format chosen by the
|
|
||||||
user executing the script.
|
|
||||||
|
|
||||||
Variable Substitution
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
When a cadquery script runs, the values of the variables assume their hard-coded values.
|
|
||||||
|
|
||||||
Some execution environments, such as the `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_
|
|
||||||
or `parametricParts.com <https://www.parametricparts.com>`_ , may subsitute other values supplied by a user of your script.
|
|
||||||
|
|
||||||
When this happens, your script will not know the difference: variables will appear to have been initialized the same
|
|
||||||
as they had be before.
|
|
||||||
|
|
||||||
|
see the :ref:`cqgi` section for more details.
|
|
@ -43,6 +43,7 @@ Table Of Contents
|
||||||
apireference.rst
|
apireference.rst
|
||||||
selectors.rst
|
selectors.rst
|
||||||
classreference.rst
|
classreference.rst
|
||||||
|
cqgi.rst
|
||||||
extending.rst
|
extending.rst
|
||||||
roadmap.rst
|
roadmap.rst
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.. _3d_cad_primer:
|
.. _3d_cad_primer:
|
||||||
|
|
||||||
.. module:: cadquery
|
|
||||||
|
|
||||||
CadQuery Concepts
|
CadQuery Concepts
|
||||||
===================================
|
===================================
|
||||||
|
@ -49,7 +48,7 @@ in space, or relative to other planes using offsets or rotations.
|
||||||
The most powerful feature of workplanes is that they allow you to work in 2D space in the coordinate system of the
|
The most powerful feature of workplanes is that they allow you to work in 2D space in the coordinate system of the
|
||||||
workplane, and then build 3D features based on local coordinates. This makes scripts much easier to create and maintain.
|
workplane, and then build 3D features based on local coordinates. This makes scripts much easier to create and maintain.
|
||||||
|
|
||||||
See :py:class:`cadquery.CQ.Workplane` to learn more
|
See :py:class:`cadquery.Workplane` to learn more
|
||||||
|
|
||||||
|
|
||||||
2D Construction
|
2D Construction
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
|
||||||
.. module:: cadquery
|
|
||||||
|
|
||||||
.. _quickstart:
|
.. _quickstart:
|
||||||
|
|
||||||
***********************
|
***********************
|
||||||
CadQuery QuickStart
|
CadQuery QuickStart
|
||||||
***********************
|
***********************
|
||||||
|
|
||||||
|
.. module:: cadquery
|
||||||
|
|
||||||
Want a quick glimpse of what CadQuery can do? This quickstart will demonstrate the basics of cadQuery using a simple example
|
Want a quick glimpse of what CadQuery can do? This quickstart will demonstrate the basics of cadQuery using a simple example
|
||||||
|
|
||||||
Prerequisites: FreeCAD + cadQuery-freeCAD-module in FreeCAD
|
Prerequisites: FreeCAD + cadQuery-freeCAD-module in FreeCAD
|
||||||
|
@ -55,18 +54,15 @@ with place-holders for the dimensions. Paste this into the CodeWindow:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
import cadquery
|
|
||||||
from Helpers import show
|
|
||||||
|
|
||||||
height = 60.0
|
height = 60.0
|
||||||
width = 80.0
|
width = 80.0
|
||||||
thickness = 10.0
|
thickness = 10.0
|
||||||
|
|
||||||
# make the base
|
# make the base
|
||||||
result = cadquery.Workplane("XY").box(height, width, thickness)
|
result = cq.Workplane("XY").box(height, width, thickness)
|
||||||
|
|
||||||
# Render the solid
|
# Render the solid
|
||||||
show(result)
|
build_object(result)
|
||||||
|
|
||||||
Press F2 to run the script. You should see Our basic base.
|
Press F2 to run the script. You should see Our basic base.
|
||||||
|
|
||||||
|
@ -83,10 +79,7 @@ This modification will do the trick:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:linenos:
|
:linenos:
|
||||||
:emphasize-lines: 7,11
|
:emphasize-lines: 4,8
|
||||||
|
|
||||||
import cadquery
|
|
||||||
from Helpers import show
|
|
||||||
|
|
||||||
height = 60.0
|
height = 60.0
|
||||||
width = 80.0
|
width = 80.0
|
||||||
|
@ -94,11 +87,11 @@ This modification will do the trick:
|
||||||
diameter = 22.0
|
diameter = 22.0
|
||||||
|
|
||||||
# make the base
|
# make the base
|
||||||
result = cadquery.Workplane("XY").box(height, width, thickness)\
|
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||||
.faces(">Z").workplane().hole(diameter)
|
.faces(">Z").workplane().hole(diameter)
|
||||||
|
|
||||||
# Render the solid
|
# Render the solid
|
||||||
show(result)
|
build_object(result)
|
||||||
|
|
||||||
Rebuild your model by pressing F2. Your block should look like this:
|
Rebuild your model by pressing F2. Your block should look like this:
|
||||||
|
|
||||||
|
@ -107,13 +100,13 @@ Rebuild your model by pressing F2. Your block should look like this:
|
||||||
|
|
||||||
The code is pretty compact, lets step through it.
|
The code is pretty compact, lets step through it.
|
||||||
|
|
||||||
**Line 7** adds a new parameter, diameter, for the diamter of the hole
|
**Line 4** adds a new parameter, diameter, for the diamter of the hole
|
||||||
|
|
||||||
**Line 11**, we're adding the hole.
|
**Line 8**, we're adding the hole.
|
||||||
:py:meth:`cadquery.CQ.CQ.faces` selects the top-most face in the Z direction, and then
|
:py:meth:`cadquery.CQ.faces` selects the top-most face in the Z direction, and then
|
||||||
:py:meth:`cadquery.CQ.CQ.workplane` begins a new workplane located on this face. The center of this workplane
|
:py:meth:`cadquery.CQ.workplane` begins a new workplane located on this face. The center of this workplane
|
||||||
is located at the geometric center of the shape, which in this case is the center of the plate.
|
is located at the geometric center of the shape, which in this case is the center of the plate.
|
||||||
Finally, :py:meth:`cadquery.CQ.Workplane.hole` drills a hole through the part 22mm in diamter
|
Finally, :py:meth:`cadquery.Workplane.hole` drills a hole through the part 22mm in diamter
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -139,10 +132,7 @@ Good news!-- we can get the job done with just two lines of code. Here's the cod
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:linenos:
|
:linenos:
|
||||||
:emphasize-lines: 8,13-15
|
:emphasize-lines: 5,10-13
|
||||||
|
|
||||||
import cadquery
|
|
||||||
from Helpers import show
|
|
||||||
|
|
||||||
height = 60.0
|
height = 60.0
|
||||||
width = 80.0
|
width = 80.0
|
||||||
|
@ -151,7 +141,7 @@ Good news!-- we can get the job done with just two lines of code. Here's the cod
|
||||||
padding = 12.0
|
padding = 12.0
|
||||||
|
|
||||||
# make the base
|
# make the base
|
||||||
result = cadquery.Workplane("XY").box(height, width, thickness)\
|
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||||
.faces(">Z").workplane().hole(diameter)\
|
.faces(">Z").workplane().hole(diameter)\
|
||||||
.faces(">Z").workplane() \
|
.faces(">Z").workplane() \
|
||||||
.rect(height - padding,width - padding,forConstruction=True)\
|
.rect(height - padding,width - padding,forConstruction=True)\
|
||||||
|
@ -159,7 +149,7 @@ Good news!-- we can get the job done with just two lines of code. Here's the cod
|
||||||
.cboreHole(2.4, 4.4, 2.1)
|
.cboreHole(2.4, 4.4, 2.1)
|
||||||
|
|
||||||
# Render the solid
|
# Render the solid
|
||||||
show(result)
|
build_object(result)
|
||||||
|
|
||||||
|
|
||||||
After pressing F2 to re-execute the model, you should see something like this:
|
After pressing F2 to re-execute the model, you should see something like this:
|
||||||
|
@ -169,14 +159,14 @@ After pressing F2 to re-execute the model, you should see something like this:
|
||||||
|
|
||||||
There is quite a bit going on here, so lets break it down a bit.
|
There is quite a bit going on here, so lets break it down a bit.
|
||||||
|
|
||||||
**Line 8** creates a new padding parameter that decides how far the holes are from the edges of the plate.
|
**Line 5** creates a new padding parameter that decides how far the holes are from the edges of the plate.
|
||||||
|
|
||||||
**Line 13** selects the top-most face of the block, and creates a workplane on the top that face, which we'll use to
|
**Line 10** selects the top-most face of the block, and creates a workplane on the top that face, which we'll use to
|
||||||
define the centers of the holes in the corners.
|
define the centers of the holes in the corners.
|
||||||
|
|
||||||
There are a couple of things to note about this line:
|
There are a couple of things to note about this line:
|
||||||
|
|
||||||
1. The :py:meth:`cadquery.CQ.Workplane.rect` function draws a rectangle. **forConstruction=True**
|
1. The :py:meth:`cadquery.Workplane.rect` function draws a rectangle. **forConstruction=True**
|
||||||
tells CadQuery that this rectangle will not form a part of the solid,
|
tells CadQuery that this rectangle will not form a part of the solid,
|
||||||
but we are just using it to help define some other geometry.
|
but we are just using it to help define some other geometry.
|
||||||
2. The center point of a workplane on a face is always at the center of the face, which works well here
|
2. The center point of a workplane on a face is always at the center of the face, which works well here
|
||||||
|
@ -184,15 +174,15 @@ There are a couple of things to note about this line:
|
||||||
this case, the center of the top face of the block. So this rectangle will be centered on the face
|
this case, the center of the top face of the block. So this rectangle will be centered on the face
|
||||||
|
|
||||||
|
|
||||||
**Line 14** draws a rectangle 8mm smaller than the overall length and width of the block,which we will use to
|
**Line 11** draws a rectangle 8mm smaller than the overall length and width of the block,which we will use to
|
||||||
locate the corner holes. We'll use the vertices ( corners ) of this rectangle to locate the holes. The rectangle's
|
locate the corner holes. We'll use the vertices ( corners ) of this rectangle to locate the holes. The rectangle's
|
||||||
center is at the center of the workplane, which in this case co-incides with the center of the bearing hole.
|
center is at the center of the workplane, which in this case co-incides with the center of the bearing hole.
|
||||||
|
|
||||||
**Line 15** selects the vertices of the rectangle, which we will use for the centers of the holes.
|
**Line 12** selects the vertices of the rectangle, which we will use for the centers of the holes.
|
||||||
The :py:meth:`cadquery.CQ.CQ.vertices` function selects the corners of the rectangle
|
The :py:meth:`cadquery.CQ.vertices` function selects the corners of the rectangle
|
||||||
|
|
||||||
**Line 16** uses the cboreHole function to draw the holes.
|
**Line 13** uses the cboreHole function to draw the holes.
|
||||||
The :py:meth:`cadquery.CQ.Workplane.cboreHole` function is a handy CadQuery function that makes a counterbored hole,
|
The :py:meth:`cadquery.Workplane.cboreHole` function is a handy CadQuery function that makes a counterbored hole,
|
||||||
like most other CadQuery functions, operate on the values on the stack. In this case, since we
|
like most other CadQuery functions, operate on the values on the stack. In this case, since we
|
||||||
selected the four vertices before calling the function, the function operates on each of the four points--
|
selected the four vertices before calling the function, the function operates on each of the four points--
|
||||||
which results in a counterbore hole at the corners.
|
which results in a counterbore hole at the corners.
|
||||||
|
@ -208,10 +198,7 @@ We can do that using the preset dictionaries in the parameter definition:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:linenos:
|
:linenos:
|
||||||
:emphasize-lines: 16
|
:emphasize-lines: 13
|
||||||
|
|
||||||
import cadquery
|
|
||||||
from Helpers import show
|
|
||||||
|
|
||||||
height = 60.0
|
height = 60.0
|
||||||
width = 80.0
|
width = 80.0
|
||||||
|
@ -220,7 +207,7 @@ We can do that using the preset dictionaries in the parameter definition:
|
||||||
padding = 12.0
|
padding = 12.0
|
||||||
|
|
||||||
# make the base
|
# make the base
|
||||||
result = cadquery.Workplane("XY").box(height, width, thickness)\
|
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||||
.faces(">Z").workplane().hole(diameter)\
|
.faces(">Z").workplane().hole(diameter)\
|
||||||
.faces(">Z").workplane() \
|
.faces(">Z").workplane() \
|
||||||
.rect(height - padding, width - padding, forConstruction=True)\
|
.rect(height - padding, width - padding, forConstruction=True)\
|
||||||
|
@ -228,11 +215,12 @@ We can do that using the preset dictionaries in the parameter definition:
|
||||||
.edges("|Z").fillet(2.0)
|
.edges("|Z").fillet(2.0)
|
||||||
|
|
||||||
# Render the solid
|
# Render the solid
|
||||||
show(result)
|
build_object(result)
|
||||||
|
|
||||||
On **Line 16**, we're filleting the edges using the :py:meth:`cadquery.CQ.CQ.fillet` method.
|
**Line 13** fillets the edges using the :py:meth:`cadquery.CQ.fillet` method.
|
||||||
To grab the right edges, the :py:meth:`cadquery.CQ.CQ.edges`
|
|
||||||
selects all of the edges that are parallel to the Z axis ("|Z"),
|
To grab the right edges, the :py:meth:`cadquery.CQ.edges` selects all of the
|
||||||
|
edges that are parallel to the Z axis ("\|Z"),
|
||||||
|
|
||||||
The finished product looks like this:
|
The finished product looks like this:
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
String Selectors Reference
|
String Selectors Reference
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
.. module:: cadquery
|
|
||||||
|
|
||||||
CadQuery selector strings allow filtering various types of object lists. Most commonly, Edges, Faces, and Vertices are
|
CadQuery selector strings allow filtering various types of object lists. Most commonly, Edges, Faces, and Vertices are
|
||||||
used, but all objects types can be filtered.
|
used, but all objects types can be filtered.
|
||||||
|
@ -11,11 +10,11 @@ used, but all objects types can be filtered.
|
||||||
String selectors are simply shortcuts for using the full object equivalents. If you pass one of the
|
String selectors are simply shortcuts for using the full object equivalents. If you pass one of the
|
||||||
string patterns in, CadQuery will automatically use the associated selector object.
|
string patterns in, CadQuery will automatically use the associated selector object.
|
||||||
|
|
||||||
* :py:meth:`cadquery.CQ.CQ.faces`
|
* :py:meth:`cadquery.CQ.faces`
|
||||||
* :py:meth:`cadquery.CQ.CQ.edges`
|
* :py:meth:`cadquery.CQ.edges`
|
||||||
* :py:meth:`cadquery.CQ.CQ.vertices`
|
* :py:meth:`cadquery.CQ.vertices`
|
||||||
* :py:meth:`cadquery.CQ.CQ.solids`
|
* :py:meth:`cadquery.CQ.solids`
|
||||||
* :py:meth:`cadquery.CQ.CQ.shells`
|
* :py:meth:`cadquery.CQ.shells`
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -45,13 +44,13 @@ The axis used in the listing below are for illustration: any axis would work sim
|
||||||
========= ====================================== ======================================================= ==========================
|
========= ====================================== ======================================================= ==========================
|
||||||
Selector Selects Selector Class # objects returned
|
Selector Selects Selector Class # objects returned
|
||||||
========= ====================================== ======================================================= ==========================
|
========= ====================================== ======================================================= ==========================
|
||||||
+Z Faces with normal in +z direction :py:class:`cadquery.selectors.DirectionSelector` 0 or 1
|
+Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0 or 1
|
||||||
\|Z Faces parallel to xy plane :py:class:`cadquery.selectors.ParallelDirSelector` 0..many
|
\|Z Faces parallel to xy plane :py:class:`cadquery.ParallelDirSelector` 0..many
|
||||||
-X Faces with normal in neg x direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
-X Faces with normal in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||||
#Z Faces perpendicular to z direction :py:class:`cadquery.selectors.PerpendicularDirSelector` 0..many
|
#Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
|
||||||
%Plane Faces of type plane :py:class:`cadquery.selectors.TypeSelector` 0..many
|
%Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many
|
||||||
>Y Face farthest in the positive y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
>Y Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
<Y Face farthest in the negative y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
<Y Face farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
========= ====================================== ======================================================= ==========================
|
========= ====================================== ======================================================= ==========================
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,13 +72,13 @@ The axis used in the listing below are for illustration: any axis would work sim
|
||||||
========= ==================================== ======================================================= ==========================
|
========= ==================================== ======================================================= ==========================
|
||||||
Selector Selects Selector Class # objects returned
|
Selector Selects Selector Class # objects returned
|
||||||
========= ==================================== ======================================================= ==========================
|
========= ==================================== ======================================================= ==========================
|
||||||
+Z Edges aligned in the Z direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
+Z Edges aligned in the Z direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||||
\|Z Edges parallel to z direction :py:class:`cadquery.selectors.ParallelDirSelector` 0..many
|
\|Z Edges parallel to z direction :py:class:`cadquery.ParallelDirSelector` 0..many
|
||||||
-X Edges aligned in neg x direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
-X Edges aligned in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||||
#Z Edges perpendicular to z direction :py:class:`cadquery.selectors.PerpendicularDirSelector` 0..many
|
#Z Edges perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
|
||||||
%Line Edges of type line :py:class:`cadquery.selectors.TypeSelector` 0..many
|
%Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many
|
||||||
>Y Edges farthest in the positive y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
>Y Edges farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
<Y Edges farthest in the negative y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
<Y Edges farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
========= ==================================== ======================================================= ==========================
|
========= ==================================== ======================================================= ==========================
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,8 +92,8 @@ Only a few of the filter types apply to vertices. The location of the vertex is
|
||||||
========= ======================================= ======================================================= ==========================
|
========= ======================================= ======================================================= ==========================
|
||||||
Selector Selects Selector Class # objects returned
|
Selector Selects Selector Class # objects returned
|
||||||
========= ======================================= ======================================================= ==========================
|
========= ======================================= ======================================================= ==========================
|
||||||
>Y Vertices farthest in the positive y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
>Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
<Y Vertices farthest in the negative y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
<Y Vertices farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||||
========= ======================================= ======================================================= ==========================
|
========= ======================================= ======================================================= ==========================
|
||||||
|
|
||||||
Future Enhancements
|
Future Enhancements
|
||||||
|
|
|
@ -14,4 +14,5 @@ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQSelectors.TestC
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
|
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCadQuery.TestCadQuery))
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
|
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestExporters.TestExporters))
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters))
|
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters))
|
||||||
|
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI))
|
||||||
unittest.TextTestRunner().run(suite)
|
unittest.TextTestRunner().run(suite)
|
||||||
|
|
|
@ -92,7 +92,7 @@ class TestCQGI(BaseTest):
|
||||||
build_object(h)
|
build_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.execute(script, {'h': 33.33})
|
result = cqgi.parse(script).build( {'h': 33.33})
|
||||||
self.assertEquals(result.results[0], "33.33")
|
self.assertEquals(result.results[0], "33.33")
|
||||||
|
|
||||||
def test_that_assigning_string_to_number_fails(self):
|
def test_that_assigning_string_to_number_fails(self):
|
||||||
|
@ -102,7 +102,7 @@ class TestCQGI(BaseTest):
|
||||||
build_object(h)
|
build_object(h)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.execute(script, {'h': "a string"})
|
result = cqgi.parse(script).build( {'h': "a string"})
|
||||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||||
|
|
||||||
def test_that_assigning_unknown_var_fails(self):
|
def test_that_assigning_unknown_var_fails(self):
|
||||||
|
@ -113,7 +113,7 @@ class TestCQGI(BaseTest):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cqgi.execute(script, {'w': "var is not there"})
|
result = cqgi.parse(script).build( {'w': "var is not there"})
|
||||||
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
|
||||||
|
|
||||||
def test_that_not_calling_build_object_raises_error(self):
|
def test_that_not_calling_build_object_raises_error(self):
|
||||||
|
@ -122,7 +122,7 @@ class TestCQGI(BaseTest):
|
||||||
h = 20.0
|
h = 20.0
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = cqgi.execute(script)
|
result = cqgi.parse(script).build()
|
||||||
self.assertTrue(isinstance(result.exception, cqgi.NoOutputError))
|
self.assertTrue(isinstance(result.exception, cqgi.NoOutputError))
|
||||||
|
|
||||||
def test_that_cq_objects_are_visible(self):
|
def test_that_cq_objects_are_visible(self):
|
||||||
|
@ -133,6 +133,38 @@ class TestCQGI(BaseTest):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
result = cqgi.execute(script)
|
result = cqgi.parse(script).build()
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertIsNotNone(result.first_result)
|
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))
|
Loading…
Reference in New Issue
Block a user