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
|
||||
from .selectors import *
|
||||
from .CQ import *
|
||||
from .cq import *
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -35,7 +35,7 @@ def cq_directive(name, arguments, options, content, lineno,
|
|||
|
||||
try:
|
||||
_s = StringIO.StringIO()
|
||||
result = cqgi.execute(plot_code)
|
||||
result = cqgi.parse(plot_code).build()
|
||||
|
||||
if result.success:
|
||||
exporters.exportShape(result.first_result, "SVG", _s)
|
||||
|
|
257
cadquery/cqgi.py
257
cadquery/cqgi.py
|
@ -1,70 +1,99 @@
|
|||
"""
|
||||
|
||||
The CadQuery Container Environment.
|
||||
The CadQuery Gateway Interface.
|
||||
Provides classes and tools for executing CadQuery scripts
|
||||
"""
|
||||
import ast
|
||||
import traceback
|
||||
import re
|
||||
import time
|
||||
import cadquery
|
||||
|
||||
CQSCRIPT = "<cqscript>"
|
||||
|
||||
|
||||
def execute(script_source, build_parameters=None):
|
||||
def parse(script_source):
|
||||
"""
|
||||
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,
|
||||
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 build_parameters: a dictionary of variables. The variables must be
|
||||
assignable to the underlying variable type.
|
||||
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
|
||||
This is the interface so that we can return other information onthe result, such as the build time
|
||||
:return: a BuildResult object, which includes the status of the result, and either
|
||||
a resulting shape or an exception
|
||||
:return: a CQModel object that defines the script and allows execution
|
||||
|
||||
"""
|
||||
model = CQModel(script_source)
|
||||
return model.build(build_parameters)
|
||||
return model
|
||||
|
||||
|
||||
class CQModel(object):
|
||||
"""
|
||||
Object that provides a nice interface to a cq script that
|
||||
is following the cce model
|
||||
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)
|
||||
|
||||
ConstantAssignmentFinder(self.metadata).visit(self.ast_tree)
|
||||
|
||||
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, params=None):
|
||||
def build(self, build_parameters=None):
|
||||
"""
|
||||
|
||||
:param params: dictionary of parameter values to build with
|
||||
:return:
|
||||
Executes the script, using the optional parameters to override those in the model
|
||||
:param build_parameters: a dictionary of variables. The variables must be
|
||||
assignable to the underlying variable type.
|
||||
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
|
||||
This is the interface so that we can return other information onthe result, such as the build time
|
||||
:return: a BuildResult object, which includes the status of the result, and either
|
||||
a resulting shape or an exception
|
||||
"""
|
||||
if not params:
|
||||
params = {}
|
||||
if not build_parameters:
|
||||
build_parameters = {}
|
||||
|
||||
start = time.clock()
|
||||
result = BuildResult()
|
||||
|
||||
try:
|
||||
self.set_param_values(params)
|
||||
collector = BuildObjectCollector()
|
||||
self.set_param_values(build_parameters)
|
||||
collector = ScriptCallback()
|
||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||
.add_entry("build_object", collector.build_object).build()
|
||||
|
||||
|
@ -75,7 +104,11 @@ class CQModel(object):
|
|||
else:
|
||||
raise NoOutputError("Script did not call build_object-- no output available.")
|
||||
except Exception, ex:
|
||||
print "Error Executing Script:"
|
||||
result.set_failure_result(ex)
|
||||
traceback.print_exc()
|
||||
print "Full Text of Script:"
|
||||
print self.script_source
|
||||
|
||||
end = time.clock()
|
||||
result.buildTime = end - start
|
||||
|
@ -93,6 +126,16 @@ class CQModel(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):
|
||||
self.buildTime = None
|
||||
self.results = []
|
||||
|
@ -111,6 +154,10 @@ class BuildResult(object):
|
|||
|
||||
|
||||
class ScriptMetadata(object):
|
||||
"""
|
||||
Defines the metadata for a parsed CQ Script.
|
||||
the parameters property is a dict of InputParameter objects.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.parameters = {}
|
||||
|
||||
|
@ -135,12 +182,37 @@ class BooleanParameterType(ParameterType):
|
|||
|
||||
|
||||
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):
|
||||
self.name = None
|
||||
self.shortDesc = None
|
||||
self.varType = None
|
||||
self.valid_values = []
|
||||
|
||||
#: the default value for the variable.
|
||||
self.default_value = None
|
||||
|
||||
#: the name of the parameter.
|
||||
self.name = None
|
||||
|
||||
#: type of the variable: BooleanParameter, StringParameter, NumericParameter
|
||||
self.varType = None
|
||||
|
||||
#: help text describing the variable. Only available if the script used describe_parameter()
|
||||
self.shortDesc = None
|
||||
|
||||
|
||||
|
||||
#: valid values for the variable. Only available if the script used describe_parameter()
|
||||
self.valid_values = []
|
||||
|
||||
|
||||
|
||||
self.ast_node = None
|
||||
|
||||
@staticmethod
|
||||
|
@ -181,9 +253,9 @@ class InputParameter:
|
|||
self.ast_node.s = str(new_value)
|
||||
elif self.varType == BooleanParameterType:
|
||||
if new_value:
|
||||
self.ast_node.value.id = 'True'
|
||||
self.ast_node.id = 'True'
|
||||
else:
|
||||
self.ast_node.value.id = 'False'
|
||||
self.ast_node.id = 'False'
|
||||
else:
|
||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||
|
||||
|
@ -192,56 +264,54 @@ class InputParameter:
|
|||
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):
|
||||
self.outputObjects = []
|
||||
|
||||
def build_object(self, shape):
|
||||
"""
|
||||
return an object to the executing environment
|
||||
:param shape: a cadquery object
|
||||
"""
|
||||
self.outputObjects.append(shape)
|
||||
|
||||
def describe_parameter(self,var, valid_values, short_desc):
|
||||
"""
|
||||
Not yet implemented: allows a script to document
|
||||
extra metadata about the parameters
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_error(self, param, field_list):
|
||||
"""
|
||||
Not implemented yet: allows scripts to indicate that there are problems with inputs
|
||||
"""
|
||||
pass
|
||||
|
||||
def has_results(self):
|
||||
return len(self.outputObjects) > 0
|
||||
|
||||
|
||||
class 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):
|
||||
"""
|
||||
Raised when an attempt is made to provide a new parameter value
|
||||
that cannot be assigned to the model
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NoOutputError(Exception):
|
||||
"""
|
||||
Raised when the script does not execute the build_output() method to
|
||||
return a solid
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -274,6 +344,11 @@ class ScriptExecutionError(Exception):
|
|||
|
||||
|
||||
class EnvironmentBuilder(object):
|
||||
"""
|
||||
Builds an execution environment for a cadquery script.
|
||||
The environment includes the builtins, as well as
|
||||
the other methods the script will need.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
|
@ -286,6 +361,7 @@ class EnvironmentBuilder(object):
|
|||
|
||||
def with_cadquery_objects(self):
|
||||
self.env['cadquery'] = cadquery
|
||||
self.env['cq'] = cadquery
|
||||
return self
|
||||
|
||||
def add_entry(self, name, value):
|
||||
|
@ -305,26 +381,45 @@ class ConstantAssignmentFinder(ast.NodeTransformer):
|
|||
self.cqModel = cq_model
|
||||
|
||||
def handle_assignment(self, var_name, value_node):
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
elif type(value_node) == ast.Str:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
|
||||
elif type(value_node == ast.Name):
|
||||
if value_node.value.Id == 'True':
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.value.Id == 'False':
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
elif type(value_node) == ast.Str:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
|
||||
elif type(value_node == ast.Name):
|
||||
if value_node.id == 'True':
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.id == 'False':
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
except:
|
||||
print "Unable to handle assignment for variable '%s'" % var_name
|
||||
pass
|
||||
|
||||
def visit_Assign(self, node):
|
||||
left_side = node.targets[0]
|
||||
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||
self.handle_assignment(left_side.id, node.value)
|
||||
elif type(node.value) == ast.Tuple:
|
||||
# we have a multi-value assignment
|
||||
for n, v in zip(left_side.elts, node.value.elts):
|
||||
self.handle_assignment(n.id, v)
|
||||
|
||||
try:
|
||||
left_side = node.targets[0]
|
||||
|
||||
#do not handle attribute assignments
|
||||
if isinstance(left_side,ast.Attribute):
|
||||
return
|
||||
|
||||
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||
self.handle_assignment(left_side.id, node.value)
|
||||
elif type(node.value) == ast.Tuple:
|
||||
# we have a multi-value assignment
|
||||
for n, v in zip(left_side.elts, node.value.elts):
|
||||
self.handle_assignment(n.id, v)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
print "Unable to handle assignment for node '%s'" % ast.dump(left_side)
|
||||
|
||||
return node
|
||||
|
|
|
@ -32,9 +32,11 @@ def sortWiresByBuildOrder(wireList, plane, result=[]):
|
|||
there are no wires inside wires inside wires
|
||||
( IE, islands -- we can deal with that later on )
|
||||
none of the wires are construction wires
|
||||
|
||||
Compute:
|
||||
one or more sets of wires, with the outer wire listed first, and inner
|
||||
ones
|
||||
|
||||
Returns, list of lists.
|
||||
"""
|
||||
result = []
|
||||
|
@ -56,7 +58,7 @@ def sortWiresByBuildOrder(wireList, plane, result=[]):
|
|||
class Vector(object):
|
||||
"""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:
|
||||
* nothing (in which case the null vector is return)
|
||||
|
@ -375,9 +377,10 @@ class Plane(object):
|
|||
|
||||
The new coordinates are specified in terms of the current 2-d system.
|
||||
As an example:
|
||||
p = Plane.XY()
|
||||
p.setOrigin2d(2, 2)
|
||||
p.setOrigin2d(2, 2)
|
||||
|
||||
p = Plane.XY()
|
||||
p.setOrigin2d(2, 2)
|
||||
p.setOrigin2d(2, 2)
|
||||
|
||||
results in a plane with its origin at (x, y) = (4, 4) in global
|
||||
coordinates. Both operations were relative to local coordinates of the
|
||||
|
|
|
@ -433,7 +433,7 @@ class Edge(Shape):
|
|||
|
||||
def tangentAt(self, locationVector=None):
|
||||
"""
|
||||
Compute tangent vector at the specified location.
|
||||
Compute tangent vector at the specified location.
|
||||
:param locationVector: location to use. Use the center point if None
|
||||
:return: tangent vector
|
||||
"""
|
||||
|
@ -668,16 +668,16 @@ class Solid(Shape):
|
|||
@classmethod
|
||||
def makeBox(cls, length, width, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1)):
|
||||
"""
|
||||
makeBox(length,width,height,[pnt,dir]) -- Make a box located\nin pnt with the d
|
||||
imensions (length,width,height)\nBy default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
|
||||
makeBox(length,width,height,[pnt,dir]) -- Make a box located in pnt with the dimensions (length,width,height)
|
||||
By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)'
|
||||
"""
|
||||
return Shape.cast(FreeCADPart.makeBox(length, width, height, pnt.wrapped, dir.wrapped))
|
||||
|
||||
@classmethod
|
||||
def makeCone(cls, radius1, radius2, height, pnt=Vector(0, 0, 0), dir=Vector(0, 0, 1), angleDegrees=360):
|
||||
"""
|
||||
'makeCone(radius1,radius2,height,[pnt,dir,angle]) --
|
||||
Make a cone with given radii and height\nBy default pnt=Vector(0,0,0),
|
||||
Make a cone with given radii and height
|
||||
By default pnt=Vector(0,0,0),
|
||||
dir=Vector(0,0,1) and angle=360'
|
||||
"""
|
||||
return Shape.cast(FreeCADPart.makeCone(radius1, radius2, height, pnt.wrapped, dir.wrapped, angleDegrees))
|
||||
|
@ -727,10 +727,8 @@ class Solid(Shape):
|
|||
@classmethod
|
||||
def makeWedge(cls, xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt=None, dir=None):
|
||||
"""
|
||||
'makeWedge(xmin, ymin, zmin, z2min, x2min,
|
||||
xmax, ymax, zmax, z2max, x2max,[pnt, dir])
|
||||
Make a wedge located in pnt\nBy default pnt=Vector(0,0,0) and dir=Vec
|
||||
tor(0,0,1)'
|
||||
Make a wedge located in pnt
|
||||
By default pnt=Vector(0,0,0) and dir=Vector(0,0,1)
|
||||
"""
|
||||
return Shape.cast(
|
||||
FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir))
|
||||
|
@ -738,9 +736,8 @@ class Solid(Shape):
|
|||
@classmethod
|
||||
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 giv
|
||||
en radius\nBy default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360'
|
||||
Make a sphere with a given radius
|
||||
By default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360
|
||||
"""
|
||||
return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3))
|
||||
|
||||
|
@ -752,11 +749,11 @@ class Solid(Shape):
|
|||
Though the signature may appear to be similar enough to extrudeLinear to merit combining them, the
|
||||
construction methods used here are different enough that they should be separate.
|
||||
|
||||
At a high level, the steps followed ar:
|
||||
(1) accept a set of wires
|
||||
(2) create another set of wires like this one, but which are transformed and rotated
|
||||
(3) create a ruledSurface between the sets of wires
|
||||
(40 create a shell and compute the resulting object
|
||||
At a high level, the steps followed are:
|
||||
(1) accept a set of wires
|
||||
(2) create another set of wires like this one, but which are transformed and rotated
|
||||
(3) create a ruledSurface between the sets of wires
|
||||
(4) create a shell and compute the resulting object
|
||||
|
||||
:param outerWire: the outermost wire, a cad.Wire
|
||||
:param innerWires: a list of inner wires, a list of cad.Wire
|
||||
|
|
|
@ -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`
|
||||
|
||||
.. module:: cadquery
|
||||
.. currentmodule:: cadquery
|
||||
|
||||
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:
|
||||
|
||||
.. automodule:: cadquery
|
||||
|
||||
===========================
|
||||
CadQuery Design Principles
|
||||
|
|
381
doc/examples.rst
381
doc/examples.rst
|
@ -4,7 +4,6 @@
|
|||
CadQuery Examples
|
||||
*********************************
|
||||
|
||||
.. automodule:: 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::
|
||||
|
||||
result = Workplane("front").box(2.0,2.0,0.5).faces(">Z").hole(0.5)
|
||||
build_output(result)
|
||||
# The dimensions of the box. These can be modified rather than changing the
|
||||
# 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
|
||||
|
||||
|
@ -88,7 +96,8 @@ By default, rectangles and circles are centered around the previous working poin
|
|||
|
||||
.. 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
|
||||
|
||||
|
@ -112,8 +121,9 @@ closed curve.
|
|||
|
||||
.. 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)
|
||||
build_object(result)
|
||||
|
||||
|
||||
.. topic:: Api References
|
||||
|
@ -138,13 +148,14 @@ A new work plane center can be established at any point.
|
|||
|
||||
.. 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,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!
|
||||
|
||||
result = result.extrude(0.25)
|
||||
build_object(result)
|
||||
|
||||
|
||||
.. topic:: Api References
|
||||
|
@ -169,10 +180,11 @@ like :py:meth:`Workplane.circle` and :py:meth:`Workplane.rect`, will operate on
|
|||
|
||||
.. 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.circle( 0.25 ) # circle will operate on all four points
|
||||
result = r.extrude(0.125 ) # make prism
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -192,8 +204,9 @@ correct for small hole sizes.
|
|||
|
||||
.. 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()
|
||||
build_object(result)
|
||||
|
||||
.. 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),
|
||||
(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
|
||||
|
||||
|
@ -246,7 +260,7 @@ needs a complex profile
|
|||
|
||||
.. cq_plot::
|
||||
|
||||
s = Workplane("XY")
|
||||
s = cq.Workplane("XY")
|
||||
sPnts = [
|
||||
(2.75,1.5),
|
||||
(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()
|
||||
result = r.extrude(0.5)
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -279,9 +294,10 @@ introduce horizontal and vertical lines, which make for slightly easier coding.
|
|||
|
||||
.. 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
|
||||
result =r.mirrorY().extrude(0.25 ) # mirror the geometry and extrude
|
||||
build_object(result)
|
||||
|
||||
.. 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::
|
||||
|
||||
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
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -342,9 +359,10 @@ how deep the part is
|
|||
|
||||
.. 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.circle(1.0).cutThruAll() #cut the corner out
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -369,9 +387,10 @@ This example uses an offset workplane to make a compound object, which is perfec
|
|||
|
||||
.. 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.circle(1.0).extrude(0.5) #disc
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -390,9 +409,10 @@ You can create a rotated work plane by specifying angles of rotation relative to
|
|||
|
||||
.. cq_plot::
|
||||
|
||||
result = 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)) \
|
||||
result = cq.Workplane("front").box(4.0,4.0,0.25).faces(">Z").workplane() \
|
||||
.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)
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -414,8 +434,9 @@ In the example below, a rectangle is drawn, and its vertices are used to locate
|
|||
|
||||
.. 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 )
|
||||
build_object(result)
|
||||
|
||||
.. 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::
|
||||
|
||||
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
|
||||
|
||||
|
@ -458,9 +480,11 @@ and a circular section.
|
|||
|
||||
.. 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)
|
||||
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
.. hlist::
|
||||
|
@ -481,9 +505,11 @@ Similar to :py:meth:`Workplane.hole` , these functions operate on a list of poin
|
|||
|
||||
.. 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)
|
||||
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
.. hlist::
|
||||
|
@ -507,7 +533,8 @@ Here we fillet all of the edges of a simple plate.
|
|||
|
||||
.. 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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
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() \
|
||||
.rect(length-padding,height-padding,forConstruction=True) \
|
||||
.vertices().cboreHole(2.4,4.4,2.1)
|
||||
|
||||
build_output(result)
|
||||
build_object(result)
|
||||
|
||||
|
||||
Splitting an Object
|
||||
|
@ -544,10 +571,11 @@ You can split an object using a workplane, and retain either or both halves
|
|||
|
||||
.. 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
|
||||
result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
|
@ -577,7 +605,7 @@ ones at 13 lines, but that's very short compared to the pythonOCC version, which
|
|||
.. cq_plot::
|
||||
|
||||
(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
|
||||
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
|
||||
result = p.faces(">Z").shell(0.3)
|
||||
build_object(result)
|
||||
|
||||
.. 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.
|
||||
|
||||
#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
|
||||
if p_sideRadius > p_topAndBottomRadius:
|
||||
|
@ -687,6 +716,8 @@ A Parametric Enclosure
|
|||
#return the combined result
|
||||
result =topOfLid.combineSolids(bottom)
|
||||
|
||||
build_object(result)
|
||||
|
||||
.. topic:: Api References
|
||||
|
||||
.. hlist::
|
||||
|
@ -709,9 +740,299 @@ A Parametric Enclosure
|
|||
* :py:meth:`Workplane.cskHole`
|
||||
* :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>`_,
|
||||
there are > 20 examples available that you can interact with
|
||||
.. cq_plot::
|
||||
: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
|
||||
======================
|
||||
|
||||
.. module:: cadquery
|
||||
|
||||
If you find that CadQuery doesnt suit your needs, you can easily extend it. CadQuery provides several extension
|
||||
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
|
||||
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::
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
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,
|
||||
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:
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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 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
|
||||
|
||||
* :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
|
||||
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.
|
||||
|
||||
* :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
|
||||
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:
|
||||
* :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
|
||||
|
@ -138,7 +134,7 @@ To install it, simply attach it to the CadQuery or Workplane object, like this::
|
|||
do stuff
|
||||
return whatever_you_want
|
||||
|
||||
Workplane.yourPlugin = _yourFunction
|
||||
cq.Workplane.yourPlugin = _yourFunction
|
||||
|
||||
That's it!
|
||||
|
||||
|
@ -147,8 +143,8 @@ CadQueryExample Plugins
|
|||
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:
|
||||
|
||||
* :py:meth:`cadquery.CQ.Workplane.polygon`
|
||||
* :py:meth:`cadquery.CQ.Workplane.cboreHole`
|
||||
* :py:meth:`cadquery.Workplane.polygon`
|
||||
* :py:meth:`cadquery.Workplane.cboreHole`
|
||||
|
||||
|
||||
Plugin Example
|
||||
|
@ -167,18 +163,18 @@ This ultra simple plugin makes cubes of the specified size for each stack point.
|
|||
def _singleCube(pnt):
|
||||
#pnt is a location in local coordinates
|
||||
#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
|
||||
#method, and convert to/from local coordinates.
|
||||
return self.eachpoint(_singleCube,True)
|
||||
|
||||
#link the plugin into cadQuery
|
||||
Workplane.makeCubes = makeCubes
|
||||
cq.Workplane.makeCubes = makeCubes
|
||||
|
||||
#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() \
|
||||
.makeCubes(1.0).combineSolids()
|
||||
|
||||
build_object(result)
|
||||
|
||||
|
|
|
@ -3,40 +3,23 @@
|
|||
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:
|
||||
|
||||
* Variable Assignments
|
||||
* cadquery and other python code
|
||||
* 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
|
||||
selectors.rst
|
||||
classreference.rst
|
||||
cqgi.rst
|
||||
extending.rst
|
||||
roadmap.rst
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.. _3d_cad_primer:
|
||||
|
||||
.. module:: cadquery
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
|
||||
.. module:: 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
|
||||
|
||||
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
|
||||
:linenos:
|
||||
|
||||
import cadquery
|
||||
from Helpers import show
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
thickness = 10.0
|
||||
|
||||
# make the base
|
||||
result = cadquery.Workplane("XY").box(height, width, thickness)
|
||||
result = cq.Workplane("XY").box(height, width, thickness)
|
||||
|
||||
# Render the solid
|
||||
show(result)
|
||||
build_object(result)
|
||||
|
||||
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
|
||||
:linenos:
|
||||
:emphasize-lines: 7,11
|
||||
|
||||
import cadquery
|
||||
from Helpers import show
|
||||
:emphasize-lines: 4,8
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
|
@ -94,11 +87,11 @@ This modification will do the trick:
|
|||
diameter = 22.0
|
||||
|
||||
# make the base
|
||||
result = cadquery.Workplane("XY").box(height, width, thickness)\
|
||||
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||
.faces(">Z").workplane().hole(diameter)
|
||||
|
||||
# Render the solid
|
||||
show(result)
|
||||
build_object(result)
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
:py:meth:`cadquery.CQ.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
|
||||
**Line 8**, we're adding the hole.
|
||||
:py:meth:`cadquery.CQ.faces` selects the top-most face in the Z direction, and then
|
||||
: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.
|
||||
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::
|
||||
|
||||
|
@ -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
|
||||
:linenos:
|
||||
:emphasize-lines: 8,13-15
|
||||
|
||||
import cadquery
|
||||
from Helpers import show
|
||||
:emphasize-lines: 5,10-13
|
||||
|
||||
height = 60.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
|
||||
|
||||
# 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() \
|
||||
.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)
|
||||
|
||||
# Render the solid
|
||||
show(result)
|
||||
build_object(result)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
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,
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
||||
**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
|
||||
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.
|
||||
The :py:meth:`cadquery.CQ.CQ.vertices` function selects the corners of the rectangle
|
||||
**Line 12** selects the vertices of the rectangle, which we will use for the centers of the holes.
|
||||
The :py:meth:`cadquery.CQ.vertices` function selects the corners of the rectangle
|
||||
|
||||
**Line 16** 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,
|
||||
**Line 13** uses the cboreHole function to draw the holes.
|
||||
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
|
||||
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.
|
||||
|
@ -208,10 +198,7 @@ We can do that using the preset dictionaries in the parameter definition:
|
|||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 16
|
||||
|
||||
import cadquery
|
||||
from Helpers import show
|
||||
:emphasize-lines: 13
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
|
@ -220,7 +207,7 @@ We can do that using the preset dictionaries in the parameter definition:
|
|||
padding = 12.0
|
||||
|
||||
# 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() \
|
||||
.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)
|
||||
|
||||
# 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.
|
||||
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"),
|
||||
**Line 13** fillets the edges using the :py:meth:`cadquery.CQ.fillet` method.
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
String Selectors Reference
|
||||
=============================
|
||||
|
||||
.. module:: cadquery
|
||||
|
||||
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.
|
||||
|
@ -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 patterns in, CadQuery will automatically use the associated selector object.
|
||||
|
||||
* :py:meth:`cadquery.CQ.CQ.faces`
|
||||
* :py:meth:`cadquery.CQ.CQ.edges`
|
||||
* :py:meth:`cadquery.CQ.CQ.vertices`
|
||||
* :py:meth:`cadquery.CQ.CQ.solids`
|
||||
* :py:meth:`cadquery.CQ.CQ.shells`
|
||||
* :py:meth:`cadquery.CQ.faces`
|
||||
* :py:meth:`cadquery.CQ.edges`
|
||||
* :py:meth:`cadquery.CQ.vertices`
|
||||
* :py:meth:`cadquery.CQ.solids`
|
||||
* :py:meth:`cadquery.CQ.shells`
|
||||
|
||||
.. 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
|
||||
========= ====================================== ======================================================= ==========================
|
||||
+Z Faces with normal in +z direction :py:class:`cadquery.selectors.DirectionSelector` 0 or 1
|
||||
\|Z Faces parallel to xy plane :py:class:`cadquery.selectors.ParallelDirSelector` 0..many
|
||||
-X Faces with normal in neg x direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
||||
#Z Faces perpendicular to z direction :py:class:`cadquery.selectors.PerpendicularDirSelector` 0..many
|
||||
%Plane Faces of type plane :py:class:`cadquery.selectors.TypeSelector` 0..many
|
||||
>Y Face farthest in the positive y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
||||
<Y Face farthest in the negative y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 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.ParallelDirSelector` 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.PerpendicularDirSelector` 0..many
|
||||
%Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many
|
||||
>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.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
|
||||
========= ==================================== ======================================================= ==========================
|
||||
+Z Edges aligned in the Z direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
||||
\|Z Edges parallel to z direction :py:class:`cadquery.selectors.ParallelDirSelector` 0..many
|
||||
-X Edges aligned in neg x direction :py:class:`cadquery.selectors.DirectionSelector` 0..many
|
||||
#Z Edges perpendicular to z direction :py:class:`cadquery.selectors.PerpendicularDirSelector` 0..many
|
||||
%Line Edges of type line :py:class:`cadquery.selectors.TypeSelector` 0..many
|
||||
>Y Edges farthest in the positive y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
||||
<Y Edges farthest in the negative y dir :py:class:`cadquery.selectors.DirectionMinMaxSelector` 0 or 1
|
||||
+Z Edges aligned in the Z direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
\|Z Edges parallel to z direction :py:class:`cadquery.ParallelDirSelector` 0..many
|
||||
-X Edges aligned in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
#Z Edges perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
|
||||
%Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many
|
||||
>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.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
|
||||
========= ======================================= ======================================================= ==========================
|
||||
>Y Vertices farthest in the positive y dir :py:class:`cadquery.selectors.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 positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||
<Y Vertices farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0 or 1
|
||||
========= ======================================= ======================================================= ==========================
|
||||
|
||||
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(TestExporters.TestExporters))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestImporters.TestImporters))
|
||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCQGI.TestCQGI))
|
||||
unittest.TextTestRunner().run(suite)
|
||||
|
|
|
@ -92,7 +92,7 @@ class TestCQGI(BaseTest):
|
|||
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")
|
||||
|
||||
def test_that_assigning_string_to_number_fails(self):
|
||||
|
@ -102,7 +102,7 @@ class TestCQGI(BaseTest):
|
|||
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))
|
||||
|
||||
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))
|
||||
|
||||
def test_that_not_calling_build_object_raises_error(self):
|
||||
|
@ -122,7 +122,7 @@ class TestCQGI(BaseTest):
|
|||
h = 20.0
|
||||
"""
|
||||
)
|
||||
result = cqgi.execute(script)
|
||||
result = cqgi.parse(script).build()
|
||||
self.assertTrue(isinstance(result.exception, cqgi.NoOutputError))
|
||||
|
||||
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.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