File: README.md
CadQuery
Quick Links
Documentation | Cheatsheet | GitHub Discussions | Google Group | GUI Editor
What is CadQuery
CadQuery is an intuitive, easy-to-use Python module for building parametric 3D CAD models. Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized.
CadQuery was built to be used as a Python library without any GUI. This makes it great for use cases such as integration into servers, or creating scientific and engineering scripts. However, it includes internal visualization capabilities.
Additionally, other options for visualization are also available including CQ-Editor and JupyterLab.
For those who are interested, the OCP repository contains the current OCCT wrapper used by CQ.
Key features
- Build 3D models with scripts that are as close as possible to how you would describe the object to a human.
- Create parametric models that can be very easily customized by end users.
- Output high quality (loss-less) CAD formats like STEP and DXF in addition to STL, VRML, AMF and 3MF.
- Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser.
- Offer advanced modeling capabilities such as fillets, curvilinear extrudes, parametric curves and lofts.
- Build nested assemblies out of individual parts and other assemblies.
Getting started
To learn more about designing with CadQuery, visit the documentation, examples, and cheatsheet.
To quickly get started you can use apptainer
apptainer run oras://ghcr.io/cadquery/cadquery-apptainer:master ipython -i -c"from cadquery.func import *; from cadquery.vis import show; show(box(1,1,1))"or docker/podman.
podman run -i -v /tmp:/tmp -e DISPLAY=$DISPLAY --userns=keep-id --user "$(id -u):$(id -g)" --device /dev/dri -t ghcr.io/cadquery/cadquery-docker:master ipython -i -c"from cadquery.func import *; from cadquery.vis import show; show(box(1,1,1))"The CadQuery library (with or without CQ-editor) and its dependencies may be installed using conda/mamba/micromamba/pixi, or pip.
Note that conda-based installation (or the CQ-editor installer) is the better supported option.
See the documentation for detailed CadQuery installation instructions.
CadQuery Installation Via Conda
To first install the Conda package manager see Install the Conda Package Manager, and Miniforge for a minimal installer.
mamba install is recommended over conda install for faster and less memory intensive CadQuery installation.
# Set up a new environment
conda create -n cadquery
# Activate the new environment
conda activate cadquery
# Install the latest released version
mamba install -c conda-forge cadquery
# Or install the dev version to get the latest changes
mamba install -c conda-forge -c cadquery cadquery=masterCadQuery Installation Via Pip
CadQuery has a complex set of dependencies including OCP, which is our set of bindings to the OpenCASCADE CAD kernel. OCP is distributed as binary wheels for Linux, MacOS and Windows. However, there are some limitations. Only Python 3.9 through 3.12 are currently supported, and some older Linux distributions such as Ubuntu 18.04 are not supported. If the pip installation method does not work for your system, you can try the conda installation method.
It is highly recommended that a virtual environment is used when installing CadQuery, although it is not strictly required. Installing CadQuery via pip requires a up-to-date version of pip, which can be obtained with the following command line (or a slight variation thereof).
python3 -m pip install --upgrade pipOnce a current version of pip is installed, CadQuery can be installed using the following command line.
pip install cadqueryIt is also possible to install the very latest changes directly from CadQuery's GitHub repository, with the understanding that sometimes breaking changes can occur. To install from the git repository, run the following command line.
pip install git+https://github.com/CadQuery/cadquery.gitBuilt-in visualization
CadQuery has built in visualization capabilities using trame and vtk.
from cadquery.func import box, fillet
from cadquery.vis import show
b = box(1,1,1)
show(fillet(b, b.edges("|Z"), 0.1))For non-blocking and persistent view one can use.
from cadquery.func import box, fillet
from cadquery.fig import show
b = box(1,1,1)
show(fillet(b, b.edges("|Z"), 0.1))CQ-editor GUI
CQ-editor is an IDE that allows users to edit CadQuery model scripts in a GUI environment. It includes features such as:
- A graphical debugger that allows you to step through your scripts.
- A CadQuery stack inspector.
- Export to various formats, including STEP and STL, directly from the menu.
More on CQ-editor:
Jupyter
CadQuery supports Jupyter out-of-the-box. Run your CadQuery code in the notebook and visualize the model by calling display(<CadQuery object>).
Getting help
You can find the full CadQuery documentation at cadquery.readthedocs.io.
We also have a Google Group to make it easy to get help from other CadQuery users. We want you to feel welcome and encourage you to join the group and introduce yourself. We would also love to hear what you are doing with CadQuery.
GitHub Discussions is a good place to ask general questions that are not tied to a bug report or feature request.
Professional Support
Some of CadQuery's core maintainers are available for consulting engagements involving CadQuery integration, custom feature development, training, code reviews and CAD automation. If you are interested, reach out at [email protected].
Citing
Please use our Zenodo DOI if you use CadQuery for scientific research: https://doi.org/10.5281/zenodo.3955118.
License
CadQuery is licensed under the terms of the Apache Public License, version 2.0.
Contributing
Contributions from the community are welcome and appreciated.
You do not need to be a software developer to have a big impact on this project. Contributions can take many forms including, but not limited to, the following:
- Writing and improving documentation
- Triaging bugs
- Submitting bugs and feature requests
- Creating tutorial videos and blog posts
- Helping other users get started and solve problems
- Telling others about this project
- Helping with translations and internationalization
- Helping with accessibility
- Contributing bug fixes and new features
It is asked that all contributions to this project be made in a respectful and considerate way. Please use the Python Community Code of Conduct's guidelines as a reference.
Contributing code
If you are going to contribute code, make sure to follow this steps:
- Consider opening an issue first to discuss what you have in mind
- Try to keep it as short and simple as possible (if you want to change several
things, start with just one!) - Fork the CadQuery repository, clone your fork and create a new branch to
start working on your changes - Create a conda development environment with something like:
mamba env create -n cq-dev -f environment.yml
- Activate the new conda environment:
conda activate cq-dev
- If desired, install the master branch of cq-editor (Note; a release version may not be compatible with the master branch of cadquery):
mamba install -c cadquery -c conda-forge cq-editor=master
Installing cq-editor adds another instance of cadquery which overrides the clone just added. Fix this by reinstalling cadquery using pip:pip install -e .
- Before making any changes verify that the current tests pass. Run
pytestfrom the root of your cadquery clone, there should be no failures and the output will look similar to this:- ======= 215 passed, 57 warnings in 13.95s =======
- Start with the tests! How should CadQuery behave after your changes? Make
sure to add some tests to the test suite to ensure proper behavior - Make sure your tests have assertions checking all the expected results
- Add a nice docstring to the test indicating what the test is doing; if there
is too much to explain, consider splitting the test in two! - Go ahead and implement the changes
- Add a nice docstring to the functions/methods/classes you implement
describing what they do, what the expected parameters are and what it returns
(if anything) - Update the documentation if there is any change to the public API
- Consider adding an example to the documentation showing your cool new
feature! - Make sure nothing is broken (run the complete test suite with
pytest) - Run
blackfrom our fork to autoformat your code and make sure your code style complies
with CadQuery's - Push the changes to your fork and open a pull-request upstream
- Keep an eye on the automated feedback you will receive from the CI pipelines;
if there is a test failing or some code is not properly formatted, you will
be notified without human intervention - Be prepared for constructive feedback and criticism!
- Be patient and respectful, remember that those reviewing your code are also
working hard (sometimes reviewing changes is harder than implementing them!)
How to Report a Bug
When filing a bug report issue, please be sure to answer these questions:
- What version of the software are you running?
- What operating system are you running the software on?
- What are the steps to reproduce the bug?
How to Suggest a Feature or Enhancement
If you find yourself wishing for a feature that does not exist, you are probably not alone. There are bound to be others out there with similar needs. Open an issue which describes the feature you would like to see, why you need it, and how it should work.
File: conda/web-installer/Readme.md
Readme
This is a script for generating a self installer for cadquery using conda constructor
The installer
- Installs an instance of miniconda
- Adds cadquery / conda-forge to the default channels
- Runs a post install script to download install cadquery.
We need to install cadquery post install due to the file sizes involved with the install of opencascade (around 2Gb)
This installer will not add the installed directory to the Path or try to override the default python (with the default options selected).
Running the constructor
To run
conda install jinja2 constructor
python build.py <github tag version>For Example
build.py 2.2 masterActivation
To Activate the environment
# Under Windows
condabin\activate.bat
# Under Linux / MacOS
source ~/cadquery/bin/activateFile: doc/_static/logo/README.md
CadQuery Logo
This logo uses the Roboto bold font (included), the Inkscape font size is 56, and the color is #2980b9
File: doc/apireference.rst
.. _apireference:
API Reference
The CadQuery API is made up of 4 main objects:
- Sketch -- Construct 2D sketches
- Workplane -- Wraps a topological entity and provides a 2D modelling context.
- Selector -- Filter and select things
- Assembly -- Combine objects into assemblies.
This page lists methods of these objects grouped by functional area
.. seealso::
This page lists api methods grouped by functional area.
Use :ref:classreference to see methods alphabetically by class.
Sketch initialization
.. currentmodule:: cadquery
Creating new sketches.
.. autosummary::
Sketch
Sketch.importDXF
Workplane.sketch
Sketch.finalize
Sketch.copy
Sketch.located
Sketch.moved
Sketch selection
.. currentmodule:: cadquery
Selecting, tagging and manipulating elements.
.. autosummary::
Sketch.tag
Sketch.select
Sketch.reset
Sketch.delete
Sketch.faces
Sketch.edges
Sketch.vertices
Sketching with faces
.. currentmodule:: cadquery
Sketching using the face-based API.
.. autosummary::
Sketch.face
Sketch.rect
Sketch.circle
Sketch.ellipse
Sketch.trapezoid
Sketch.slot
Sketch.regularPolygon
Sketch.polygon
Sketch.rarray
Sketch.parray
Sketch.distribute
Sketch.each
Sketch.push
Sketch.hull
Sketch.offset
Sketch.fillet
Sketch.chamfer
Sketch.clean
Sketching with edges and constraints
.. currentmodule:: cadquery
Sketching using the edge-based API.
.. autosummary::
Sketch.edge
Sketch.segment
Sketch.arc
Sketch.spline
Sketch.close
Sketch.assemble
Sketch.constrain
Sketch.solve
Initialization
.. currentmodule:: cadquery
Creating new workplanes and object chains
.. autosummary::
Workplane
.. _2dOperations:
2D Operations
Creating 2D constructs that can be used to create 3D features.
All 2D operations require a Workplane object to be created.
.. currentmodule:: cadquery
.. autosummary::
Workplane.center
Workplane.lineTo
Workplane.line
Workplane.vLine
Workplane.vLineTo
Workplane.hLine
Workplane.hLineTo
Workplane.polarLine
Workplane.polarLineTo
Workplane.moveTo
Workplane.move
Workplane.spline
Workplane.parametricCurve
Workplane.parametricSurface
Workplane.threePointArc
Workplane.sagittaArc
Workplane.radiusArc
Workplane.tangentArcPoint
Workplane.mirrorY
Workplane.mirrorX
Workplane.wire
Workplane.rect
Workplane.circle
Workplane.ellipse
Workplane.ellipseArc
Workplane.polyline
Workplane.close
Workplane.rarray
Workplane.polarArray
Workplane.slot2D
Workplane.offset2D
Workplane.placeSketch
.. _3doperations:
3D Operations
Some 3D operations also require an active 2D workplane, but some do not.
3D operations that require a 2D workplane to be active:
.. autosummary::
Workplane.cboreHole
Workplane.cskHole
Workplane.hole
Workplane.extrude
Workplane.cut
Workplane.cutBlind
Workplane.cutThruAll
Workplane.box
Workplane.sphere
Workplane.wedge
Workplane.cylinder
Workplane.union
Workplane.combine
Workplane.intersect
Workplane.loft
Workplane.sweep
Workplane.twistExtrude
Workplane.revolve
Workplane.text
3D operations that do NOT require a 2D workplane to be active:
.. autosummary::
Workplane.shell
Workplane.fillet
Workplane.chamfer
Workplane.split
Workplane.rotate
Workplane.rotateAboutCenter
Workplane.translate
Workplane.mirror
File Management and Export
.. autosummary::
Workplane.toSvg
Workplane.exportSvg
.. autosummary::
importers.importStep
importers.importDXF
exporters.export
occ_impl.exporters.dxf.DxfDocument
Iteration Methods
Methods that allow iteration over the stack or objects
.. autosummary::
Workplane.each
Workplane.eachpoint
.. _stackMethods:
Stack and Selector Methods
CadQuery methods that operate on the stack
.. autosummary::
Workplane.all
Workplane.size
Workplane.vals
Workplane.add
Workplane.val
Workplane.first
Workplane.item
Workplane.last
Workplane.end
Workplane.vertices
Workplane.faces
Workplane.edges
Workplane.wires
Workplane.solids
Workplane.shells
Workplane.compounds
.. _selectors:
Selectors
Objects that filter and select CAD objects. Selectors are used to select existing geometry
as a basis for further operations.
.. currentmodule:: cadquery.selectors
.. autosummary::
NearestToPointSelector
BoxSelector
BaseDirSelector
ParallelDirSelector
DirectionSelector
DirectionNthSelector
LengthNthSelector
AreaNthSelector
RadiusNthSelector
PerpendicularDirSelector
TypeSelector
DirectionMinMaxSelector
CenterNthSelector
BinarySelector
AndSelector
SumSelector
SubtractSelector
InverseSelector
StringSyntaxSelector.. _assembly:
Assemblies
Workplane and Shape objects can be connected together into assemblies
.. currentmodule:: cadquery
.. autosummary::
Assembly
Assembly.add
Assembly.save
Assembly.constrain
Assembly.solve
Constraint
ColorFile: doc/assy.rst
.. _assytutorial:
Assemblies
Assembly tutorial
The purpose of this section is to demonstrate how to use the assembly and constraints
functionality to build a realistic model. It will be a enclosure door assembly made out of 20x20 v-slot profiles.
Defining parameters
We want to start with defining the model parameters to allow for easy dimension changes later:
.. code-block:: python
import cadquery as cq
Parameters
H = 400
W = 200
D = 350
PROFILE = cq.importers.importDXF("vslot-2020_1.dxf").wires()
SLOT_D = 5
PANEL_T = 3
HANDLE_D = 20
HANDLE_L = 50
HANDLE_W = 4
It is interesting to note that the v-slot profile is imported from a DXF file.
This way it is very easy to change to other aluminum extrusion type, e.g. Item or Bosch.
Vendors usually provide DXF files.
Defining reusable components
Next we want to define functions generating the assembly components based on the specified parameters.
.. code-block:: python
def make_vslot(l):
return PROFILE.toPending().extrude(l)
def make_connector():
rv = (
cq.Workplane()
.box(20, 20, 20)
.faces("<X")
.workplane()
.cboreHole(6, 15, 18)
.faces("<Z")
.workplane(centerOption="CenterOfMass")
.cboreHole(6, 15, 18)
)
# tag mating faces
rv.faces(">X").tag("X").end()
rv.faces(">Z").tag("Z").end()
return rv def make_panel(w, h, t, cutout):
rv = (
cq.Workplane("XZ")
.rect(w, h)
.extrude(t)
.faces(">Y")
.vertices()
.rect(2 * cutout, 2 * cutout)
.cutThruAll()
.faces("<Y")
.workplane()
.pushPoints([(-w / 3, HANDLE_L / 2), (-w / 3, -HANDLE_L / 2)])
.hole(3)
)
# tag mating edges
rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")
return rv def make_handle(w, h, r):
pts = ((0, 0), (w, 0), (w, h), (0, h))
path = cq.Workplane().polyline(pts)
rv = (
cq.Workplane("YZ")
.rect(r, r)
.sweep(path, transition="round")
.tag("solid")
.faces("<X")
.workplane()
.faces("<X", tag="solid")
.hole(r / 1.5)
)
# tag mating faces
rv.faces("<X").faces(">Y").tag("mate1")
rv.faces("<X").faces("<Y").tag("mate2")
return rvInitial assembly
Next we want to instantiate all the components and add them to the assembly.
.. code-block:: python
define the elements
door = (
cq.Assembly()
.add(make_vslot(H), name="left")
.add(make_vslot(H), name="right")
.add(make_vslot(W), name="top")
.add(make_vslot(W), name="bottom")
.add(make_connector(), name="con_tl", color=cq.Color("black"))
.add(make_connector(), name="con_tr", color=cq.Color("black"))
.add(make_connector(), name="con_bl", color=cq.Color("black"))
.add(make_connector(), name="con_br", color=cq.Color("black"))
.add(
make_panel(W + SLOT_D, H + SLOT_D, PANEL_T, SLOT_D),
name="panel",
color=cq.Color(0, 0, 1, 0.2),
)
.add(
make_handle(HANDLE_D, HANDLE_L, HANDLE_W),
name="handle",
color=cq.Color("yellow"),
)
)
Constraints definition
Then we want to define all the constraints
.. code-block:: python
define the constraints
(
door
# left profile
.constrain("left@faces@<Z", "con_bl?Z", "Plane")
.constrain("left@faces@<X", "con_bl?X", "Axis")
.constrain("left@faces@>Z", "con_tl?Z", "Plane")
.constrain("left@faces@<X", "con_tl?X", "Axis")
# top
.constrain("top@faces@<Z", "con_tl?X", "Plane")
.constrain("top@faces@<Y", "con_tl@faces@>Y", "Axis")
# bottom
.constrain("bottom@faces@<Y", "con_bl@faces@>Y", "Axis")
.constrain("bottom@faces@>Z", "con_bl?X", "Plane")
# right connectors
.constrain("top@faces@>Z", "con_tr@faces@>X", "Plane")
.constrain("bottom@faces@<Z", "con_br@faces@>X", "Plane")
.constrain("left@faces@>Z", "con_tr?Z", "Axis")
.constrain("left@faces@<Z", "con_br?Z", "Axis")
# right profile
.constrain("right@faces@>Z", "con_tr@faces@>Z", "Plane")
.constrain("right@faces@<X", "left@faces@<X", "Axis")
# panel
.constrain("left@faces@>X[-4]", "panel@faces@<X", "Plane")
.constrain("left@faces@>Z", "panel@faces@>Z", "Axis")
# handle
.constrain("panel?hole1", "handle?mate1", "Plane")
.constrain("panel?hole2", "handle?mate2", "Point")
)
Should you need to do something unusual that is not possible with the string
based selectors (e.g. use :py:class:cadquery.selectors.BoxSelector or a user-defined selector class),
it is possible to pass :py:class:cadquery.Shape objects to the :py:meth:cadquery.Assembly.constrain method directly. For example, the above
.. code-block::
.constrain("part1@faces@>Z", "part3@faces@<Z", "Axis")is equivalent to
.. code-block::
.constrain("part1", part1.faces(">z").val(), "part3", part3.faces("<Z").val(), "Axis")This method requires a :py:class:cadquery.Shape object, so remember to use the :py:meth:cadquery.Workplane.val
method to pass a single :py:class:cadquery.Shape and not the whole :py:class:cadquery.Workplane object.
Final result
Below is the complete code including the final solve step.
.. cadquery::
:height: 600px
import cadquery as cq
# Parameters
H = 400
W = 200
D = 350
PROFILE = cq.importers.importDXF("vslot-2020_1.dxf").wires()
SLOT_D = 6
PANEL_T = 3
HANDLE_D = 20
HANDLE_L = 50
HANDLE_W = 4
def make_vslot(l):
return PROFILE.toPending().extrude(l)
def make_connector():
rv = (
cq.Workplane()
.box(20, 20, 20)
.faces("<X")
.workplane()
.cboreHole(6, 15, 18)
.faces("<Z")
.workplane(centerOption="CenterOfMass")
.cboreHole(6, 15, 18)
)
# tag mating faces
rv.faces(">X").tag("X").end()
rv.faces(">Z").tag("Z").end()
return rv
def make_panel(w, h, t, cutout):
rv = (
cq.Workplane("XZ")
.rect(w, h)
.extrude(t)
.faces(">Y")
.vertices()
.rect(2 * cutout, 2 * cutout)
.cutThruAll()
.faces("<Y")
.workplane()
.pushPoints([(-w / 3, HANDLE_L / 2), (-w / 3, -HANDLE_L / 2)])
.hole(3)
)
# tag mating edges
rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")
return rv
def make_handle(w, h, r):
pts = ((0, 0), (w, 0), (w, h), (0, h))
path = cq.Workplane().polyline(pts)
rv = (
cq.Workplane("YZ")
.rect(r, r)
.sweep(path, transition="round")
.tag("solid")
.faces("<X")
.workplane()
.faces("<X", tag="solid")
.hole(r / 1.5)
)
# tag mating faces
rv.faces("<X").faces(">Y").tag("mate1")
rv.faces("<X").faces("<Y").tag("mate2")
return rv
# define the elements
door = (
cq.Assembly()
.add(make_vslot(H), name="left")
.add(make_vslot(H), name="right")
.add(make_vslot(W), name="top")
.add(make_vslot(W), name="bottom")
.add(make_connector(), name="con_tl", color=cq.Color("black"))
.add(make_connector(), name="con_tr", color=cq.Color("black"))
.add(make_connector(), name="con_bl", color=cq.Color("black"))
.add(make_connector(), name="con_br", color=cq.Color("black"))
.add(
make_panel(W + 2 * SLOT_D, H + 2 * SLOT_D, PANEL_T, SLOT_D),
name="panel",
color=cq.Color(0, 0, 1, 0.2),
)
.add(
make_handle(HANDLE_D, HANDLE_L, HANDLE_W),
name="handle",
color=cq.Color("yellow"),
)
)
# define the constraints
(
door
# left profile
.constrain("left@faces@<Z", "con_bl?Z", "Plane")
.constrain("left@faces@<X", "con_bl?X", "Axis")
.constrain("left@faces@>Z", "con_tl?Z", "Plane")
.constrain("left@faces@<X", "con_tl?X", "Axis")
# top
.constrain("top@faces@<Z", "con_tl?X", "Plane")
.constrain("top@faces@<Y", "con_tl@faces@>Y", "Axis")
# bottom
.constrain("bottom@faces@<Y", "con_bl@faces@>Y", "Axis")
.constrain("bottom@faces@>Z", "con_bl?X", "Plane")
# right connectors
.constrain("top@faces@>Z", "con_tr@faces@>X", "Plane")
.constrain("bottom@faces@<Z", "con_br@faces@>X", "Plane")
.constrain("left@faces@>Z", "con_tr?Z", "Axis")
.constrain("left@faces@<Z", "con_br?Z", "Axis")
# right profile
.constrain("right@faces@>Z", "con_tr@faces@>Z", "Plane")
.constrain("right@faces@<X", "left@faces@<X", "Axis")
# panel
.constrain("left@faces@>X[-4]", "panel@faces@<X", "Plane")
.constrain("left@faces@>Z", "panel@faces@>Z", "Axis")
# handle
.constrain("panel?hole1", "handle?mate1", "Plane")
.constrain("panel?hole2", "handle?mate2", "Point")
)
# solve
door.solve()
show_object(door, name="door")Data export
The resulting assembly can be exported as a STEP file or in a internal OCCT XML format.
STEP can be loaded in all CAD tool, e.g. in FreeCAD and the XML be used in other applications using OCCT.
.. code-block:: python
:linenos:
door.export("door.step")
door.export("door.xml")
.. image:: _static/door_assy_freecad.png
Object locations
Objects can be added to an assembly with initial locations supplied, such as:
.. cadquery::
import cadquery as cq
cone = cq.Solid.makeCone(1, 0, 2)
assy = cq.Assembly()
assy.add(
cone,
loc=cq.Location((0, 0, 0), (1, 0, 0), 180),
name="cone0",
color=cq.Color("green"),
)
assy.add(cone, name="cone1", color=cq.Color("blue"))
show_object(assy)As an alternative to the user calculating locations, constraints and the method
:meth:~cadquery.Assembly.solve can be used to position objects in an assembly.
If initial locations and the method :meth:~cadquery.Assembly.solve are used the solver will
overwrite these initial locations with it's solution, however initial locations can still affect the
final solution. In an underconstrained system the solver may not move an object if it does not
contribute to the cost function, or if multiple solutions exist (ie. multiple instances
where the cost function is at a minimum) initial locations can cause the solver to converge on one
particular solution. For very complicated assemblies setting approximately correct initial locations
can also reduce the computational time required.
Constraints
Constraints are often a better representation of the real world relationship the user wants to
model than directly supplying locations. In the above example the real world relationship is that
the bottom face of each cone should touch, which can be modelled with a Plane constraint. When the
user provides explicit locations (instead of constraints) then they are also responsible for updating
them when, for example, the location of cone1 changes.
When at least one constraint is supplied and the method :meth:~cadquery.Assembly.solve is run, an
optimization problem is set up. Each constraint provides a cost function that depends on the
position and orientation (represented by a :class:~cadquery.Location) of the two objects specified
when creating the constraint. The solver varies the location of the assembly's children and attempts
to minimize the sum of all cost functions. Hence by reading the formulae of the cost functions
below, you can understand exactly what each constraint does.
Point
The Point constraint is a frequently used constraint that minimizes the distance between two points.
Some example uses are centering faces or aligning verticies, but it is also useful with dummy
vertices to create offsets between two parts.
The cost function is:
.. math::
( param - \lvert \vec{ c_1 } - \vec{ c_2 } \rvert ) ^2
Where:
- :math:
paramis the parameter of the constraint, which defaults to 0, - :math:
\vec{ c_i }is the center of the ith object, and - :math:
\lvert \vec{ v } \rvertis the modulus of :math:\vec{ v }, ie. the length of
:math:\vec{ v }.
When creating a Point constraint, the param argument can be used to specify a desired offset
between the two centers. This offset does not have a direction associated with it, if you want to
specify an offset in a specific direction then you should use a dummy :class:~cadquery.Vertex.
The Point constraint uses the :meth:~cadquery.Shape.Center to find the center of the
argument. Hence it will work with all subclasses of :class:~cadquery.Shape.
.. cadquery::
import cadquery as cq
# Use the Point constraint to position boxes relative to an arc
line = cq.Edge.makeCircle(radius=10, angle1=0, angle2=90)
box = cq.Workplane().box(1, 1, 1)
assy = cq.Assembly()
assy.add(line, name="line")
# position the red box on the center of the arc
assy.add(box, name="box0", color=cq.Color("red"))
assy.constrain("line", "box0", "Point")
# position the green box at a normalized distance of 0.8 along the arc
position0 = line.positionAt(0.8)
assy.add(box, name="box1", color=cq.Color("green"))
assy.constrain(
"line",
cq.Vertex.makeVertex(*position0.toTuple()),
"box1",
box.val(),
"Point",
)
# position the orange box 2 units in any direction from the green box
assy.add(box, name="box2", color=cq.Color("orange"))
assy.constrain(
"line",
cq.Vertex.makeVertex(*position0.toTuple()),
"box2",
box.val(),
"Point",
param=2,
)
# position the blue box offset 2 units in the x direction from the green box
position1 = position0 + cq.Vector(2, 0, 0)
assy.add(box, name="box3", color=cq.Color("blue"))
assy.constrain(
"line",
cq.Vertex.makeVertex(*position1.toTuple()),
"box3",
box.val(),
"Point",
)
assy.solve()
show_object(assy)Axis
The Axis constraint minimizes the angle between two vectors. It is frequently used to align faces
and control the rotation of an object.
The cost function is:
.. math::
( k_{ dir } \times ( param - \vec{ d_1 } \angle \vec{ d_2 } ) ^2
Where:
- :math:
k_{ dir }is a scaling factor for directional constraints, - :math:
paramis the parameter of the constraint, which defaults to 180 degrees, - :math:
\vec{d_i}is the direction created from the ith object argument as described below, and - :math:
\vec{ d_1 } \angle \vec{ d_2 }is the angle between :math:\vec{ d_1 }and
:math:\vec{ d_2 }.
The argument param defaults to 180 degrees, which sets the two directions opposite
to each other. This represents what is often called a "mate" relationship, where the external faces
of two objects touch.
.. cadquery::
import cadquery as cq
cone = cq.Solid.makeCone(1, 0, 2)
assy = cq.Assembly()
assy.add(cone, name="cone0", color=cq.Color("green"))
assy.add(cone, name="cone1", color=cq.Color("blue"))
assy.constrain("cone0@faces@<Z", "cone1@faces@<Z", "Axis")
assy.solve()
show_object(assy)If the param argument is set to zero, then the two objects will point in the same direction.
This is often used when one object goes through another, such as a pin going into a hole in a plate:
.. cadquery::
import cadquery as cq
plate = cq.Workplane().box(10, 10, 1).faces(">Z").workplane().hole(2)
cone = cq.Solid.makeCone(0.8, 0, 4)
assy = cq.Assembly()
assy.add(plate, name="plate", color=cq.Color("green"))
assy.add(cone, name="cone", color=cq.Color("blue"))
# place the center of the flat face of the cone in the center of the upper face of the plate
assy.constrain("plate@faces@>Z", "cone@faces@<Z", "Point")
# set both the flat face of the cone and the upper face of the plate to point in the same direction
assy.constrain("plate@faces@>Z", "cone@faces@<Z", "Axis", param=0)
assy.solve()
show_object(assy)In creating an Axis constraint, a direction vector is extracted in one of three different ways,
depending on the object's type.
:class:~cadquery.Face:
Using :meth:~cadquery.Face.normalAt
:class:~cadquery.Edge and :meth:~cadquery.Shape.geomType is "CIRCLE":
Using :meth:~cadquery.Mixin1D.normal
:class:~cadquery.Edge and :meth:~cadquery.Shape.geomType is not "CIRCLE":
Using :meth:~cadquery.Mixin1D.tangentAt
Using any other type of object will raise a :exc:ValueError. By far the most common use case
is to define an Axis constraint from a :class:~cadquery.Face.
.. cadquery::
import cadquery as cq
from math import cos, sin, pi
# Create a sinusoidal surface:
surf = cq.Workplane().parametricSurface(
lambda u, v: (u, v, 5 * sin(pi * u / 10) * cos(pi * v / 10)),
N=40,
start=0,
stop=20,
)
# Create a cone with a small, flat tip:
cone = (
cq.Workplane()
.add(cq.Solid.makeCone(1, 0.1, 2))
# tag the tip for easy reference in the constraint:
.faces(">Z")
.tag("tip")
.end()
)
assy = cq.Assembly()
assy.add(surf, name="surf", color=cq.Color("lightgray"))
assy.add(cone, name="cone", color=cq.Color("green"))
# set the Face on the tip of the cone to point in
# the opposite direction of the center of the surface:
assy.constrain("surf", "cone?tip", "Axis")
# to make the example clearer, move the cone to the center of the face:
assy.constrain("surf", "cone?tip", "Point")
assy.solve()
show_object(assy)Plane
The Plane constraint is simply a combination of both the Point and Axis constraints. It is a
convenient shortcut for a commonly used combination of constraints. It can be used to shorten the
previous example from the two constraints to just one:
.. code-block:: diff
assy = cq.Assembly()
assy.add(surf, name="surf", color=cq.Color("lightgray"))
assy.add(cone, name="cone", color=cq.Color("green"))
-# set the Face on the tip of the cone to point in
-# the opposite direction of the center of the surface:
-assy.constrain("surf", "cone?tip", "Axis")
-# to make the example clearer, move the cone to the center of the face:
-assy.constrain("surf", "cone?tip", "Point")
+assy.constrain("surf", "cone?tip", "Plane")
assy.solve()
show_object(assy)The result of this code is identical to the above two constraint example.
For the cost function of Plane, please see the Point and Axis sections. The param argument is applied to Axis and should be left as the default value for a "mate" style
constraint (two surfaces touching) or can be set to 0 for a through surface constraint (see
description in the Axis constraint section).
PointInPlane
PointInPlane positions the center of the first object within the plane defined by the second object.
The cost function is:
.. math::
\operatorname{dist}( \vec{ c }, p_\text{ offset } ) ^2Where:
- :math:
\vec{ c }is the center of the first argument, - :math:
p_\text{ offset }is a plane created from the second object, offset in the plane's normal
direction byparam, and - :math:
\operatorname{dist}( \vec{ a }, b)is the distance between point :math:\vec{ a }and
plane :math:b.
.. cadquery::
import cadquery as cq
# Create an L-shaped object:
bracket = (
cq.Workplane("YZ")
.hLine(1)
.vLine(0.1)
.hLineTo(0.2)
.vLineTo(1)
.hLineTo(0)
.close()
.extrude(1)
# tag some faces for easy reference:
.faces(">Y[1]")
.tag("inner_vert")
.end()
.faces(">Z[1]")
.tag("inner_horiz")
.end()
)
box = cq.Workplane().box(0.5, 0.5, 0.5)
assy = cq.Assembly()
assy.add(bracket, name="bracket", color=cq.Color("gray"))
assy.add(box, name="box", color=cq.Color("green"))
# lock bracket orientation:
assy.constrain("bracket@faces@>Z", "box@faces@>Z", "Axis", param=0)
assy.constrain("bracket@faces@>X", "box@faces@>X", "Axis", param=0)
# constrain the bottom of the box to be on the plane defined by inner_horiz:
assy.constrain("box@faces@<Z", "bracket?inner_horiz", "PointInPlane")
# constrain the side of the box to be 0.2 units from the plane defined by inner_vert
assy.constrain("box@faces@<Y", "bracket?inner_vert", "PointInPlane", param=0.2)
# constrain the end of the box to be 0.1 units inside the end of the bracket
assy.constrain("box@faces@>X", "bracket@faces@>X", "PointInPlane", param=-0.1)
assy.solve()
show_object(assy)PointOnLine
PointOnLine positions the center of the first object on the line defined by the second object.
The cost function is:
.. math::
( param - \operatorname{dist}(\vec{ c }, l ) )^2
Where:
- :math:
\vec{ c }is the center of the first argument, - :math:
lis a line created from the second object - :math:
paramis the parameter of the constraint, which defaults to 0, - :math:
\operatorname{dist}( \vec{ a }, b)is the distance between point :math:\vec{ a }and
line :math:b.
.. cadquery::
import cadquery as cq
b1 = cq.Workplane().box(1, 1, 1)
b2 = cq.Workplane().sphere(0.15)
assy = (
cq.Assembly()
.add(b1, name="b1")
.add(b2, loc=cq.Location((0, 0, 4)), name="b2", color=cq.Color("red"))
)
# fix the position of b1
assy.constrain("b1", "Fixed")
# b2 on one of the edges of b1
assy.constrain("b2", "b1@edges@>>Z and >>Y", "PointOnLine")
# b2 on another of the edges of b1
assy.constrain("b2", "b1@edges@>>Z and >>X", "PointOnLine")
# effectively b2 will be constrained to be on the intersection of the two edges
assy.solve()
show_object(assy)FixedPoint
FixedPoint fixes the position of the given argument to be equal to the given point specified via the parameter of the constraint. This constraint locks all translational degrees of freedom of the argument.
The cost function is:
.. math::
\left\lVert \vec{ c } - \vec{param} \right\rVert ^2
Where:
- :math:
\vec{ c }is the center of the argument, - :math:
paramis the parameter of the constraint - tuple specifying the target position.
.. cadquery::
import cadquery as cq
b1 = cq.Workplane().box(1, 1, 1)
b2 = cq.Workplane().sphere(0.15)
assy = (
cq.Assembly()
.add(b1, name="b1")
.add(b2, loc=cq.Location((0, 0, 4)), name="b2", color=cq.Color("red"))
.add(b1, loc=cq.Location((-2, 0, 0)), name="b3", color=cq.Color("red"))
)
pnt = (0.5, 0.5, 0.5)
# fix the position of b1
assy.constrain("b1", "Fixed")
# fix b2 center at point
assy.constrain("b2", "FixedPoint", pnt)
# fix b3 vertex position at point
assy.constrain("b3@vertices@<X and <Y and <Z", "FixedPoint", pnt)
assy.solve()
show_object(assy)FixedRotation
FixedRotation fixes the rotation of the given argument to be equal to the value specified via the parameter of the constraint.
This constraint locks all rotational degrees of freedom of the argument.
The cost function is:
.. math::
\left\lVert \vec{ R } - \vec{param} \right\rVert ^2
Where:
- :math:
\vec{ R }vector of the rotation angles of the rotation applied to the argument, - :math:
paramis the parameter of the constraint - tuple specifying the target rotation.
.. cadquery::
import cadquery as cq
b1 = cq.Workplane().box(1, 1, 1)
b2 = cq.Workplane().rect(0.1, 0.1).extrude(1, taper=-15)
assy = (
cq.Assembly()
.add(b1, name="b1")
.add(b2, loc=cq.Location((0, 0, 4)), name="b2", color=cq.Color("red"))
)
# fix the position of b1
assy.constrain("b1", "Fixed")
# fix b2 bottom face position (but not rotation)
assy.constrain("b2@faces@<Z", "FixedPoint", (0, 0, 0.5))
# fix b2 rotational degrees of freedom too
assy.constrain("b2", "FixedRotation", (45, 0, 45))
assy.solve()
show_object(assy)FixedAxis
FixedAxis fixes the orientation of the given argument's normal or tangent to be equal to the orientation of the vector specified via the parameter of the constraint. This constraint locks two rotational degrees of freedom of the argument.
The cost function is:
.. math::
( \vec{ a } \angle \vec{ param } ) ^2
Where:
- :math:
\vec{ a }normal or tangent vector of the argument, - :math:
paramis the parameter of the constraint - tuple specifying the target direction.
.. cadquery::
import cadquery as cq
b1 = cq.Workplane().box(1, 1, 1)
b2 = cq.Workplane().rect(0.1, 0.1).extrude(1, taper=-15)
assy = (
cq.Assembly()
.add(b1, name="b1")
.add(b2, loc=cq.Location((0, 0, 4)), name="b2", color=cq.Color("red"))
)
# fix the position of b1
assy.constrain("b1", "Fixed")
# fix b2 bottom face position (but not rotation)
assy.constrain("b2@faces@<Z", "FixedPoint", (0, 0, 0.5))
# fix b2 some rotational degrees of freedom too
assy.constrain("b2@faces@>Z", "FixedAxis", (1, 0, 2))
assy.solve()
show_object(assy)Assembly colors
Aside from RGBA values, the :class:~cadquery.Color class can be instantiated from a text name. Valid names are
listed along with a color sample below:
.. raw:: html
aliceblue
antiquewhite
antiquewhite1
antiquewhite2
antiquewhite3
antiquewhite4
aquamarine1
aquamarine2
aquamarine4
azure
azure2
azure3
azure4
beet
beige
bisque
bisque2
bisque3
bisque4
black
blanchedalmond
blue
blue1
blue2
blue3
blue4
blueviolet
brown
brown1
brown2
brown3
brown4
burlywood
burlywood1
burlywood2
burlywood3
burlywood4
cadetblue
cadetblue1
cadetblue2
cadetblue3
cadetblue4
chartreuse
chartreuse1
chartreuse2
chartreuse3
chartreuse4
chocolate
chocolate1
chocolate2
chocolate3
chocolate4
coral
coral1
coral2
coral3
coral4
cornflowerblue
cornsilk1
cornsilk2
cornsilk3
cornsilk4
cyan
cyan1
cyan2
cyan3
cyan4
darkgoldenrod
darkgoldenrod1
darkgoldenrod2
darkgoldenrod3
darkgoldenrod4
darkgreen
darkkhaki
darkolivegreen
darkolivegreen1
darkolivegreen2
darkolivegreen3
darkolivegreen4
darkorange
darkorange1
darkorange2
darkorange3
darkorange4
darkorchid
darkorchid1
darkorchid2
darkorchid3
darkorchid4
darksalmon
darkseagreen
darkseagreen1
darkseagreen2
darkseagreen3
darkseagreen4
darkslateblue
darkslategray
darkslategray1
darkslategray2
darkslategray3
darkslategray4
darkturquoise
darkviolet
deeppink
deeppink2
deeppink3
deeppink4
deepskyblue1
deepskyblue2
deepskyblue3
deepskyblue4
dodgerblue1
dodgerblue2
dodgerblue3
dodgerblue4
firebrick
firebrick1
firebrick2
firebrick3
firebrick4
floralwhite
forestgreen
gainsboro
ghostwhite
gold
gold1
gold2
gold3
gold4
goldenrod
goldenrod1
goldenrod2
goldenrod3
goldenrod4
gray
gray0
gray1
gray10
gray11
gray12
gray13
gray14
gray15
gray16
gray17
gray18
gray19
gray2
gray20
gray21
gray22
gray23
gray24
gray25
gray26
gray27
gray28
gray29
gray3
gray30
gray31
gray32
gray33
gray34
gray35
gray36
gray37
gray38
gray39
gray4
gray40
gray41
gray42
gray43
gray44
gray45
gray46
gray47
gray48
gray49
gray5
gray50
gray51
gray52
gray53
gray54
gray55
gray56
gray57
gray58
gray59
gray6
gray60
gray61
gray62
gray63
gray64
gray65
gray66
gray67
gray68
gray69
gray7
gray70
gray71
gray72
gray73
gray74
gray75
gray76
gray77
gray78
gray79
gray8
gray80
gray81
gray82
gray83
gray85
gray86
gray87
gray88
gray89
gray9
gray90
gray91
gray92
gray93
gray94
gray95
gray97
gray98
gray99
green
green1
green2
green3
green4
greenyellow
honeydew
honeydew2
honeydew3
honeydew4
hotpink
hotpink1
hotpink2
hotpink3
hotpink4
indianred
indianred1
indianred2
indianred3
indianred4
ivory
ivory2
ivory3
ivory4
khaki
khaki1
khaki2
khaki3
khaki4
lavender
lavenderblush1
lavenderblush2
lavenderblush3
lavenderblush4
lawngreen
lemonchiffon1
lemonchiffon2
lemonchiffon3
lemonchiffon4
lightblue
lightblue1
lightblue2
lightblue3
lightblue4
lightcoral
lightcyan
lightcyan1
lightcyan2
lightcyan3
lightcyan4
lightgoldenrod
lightgoldenrod1
lightgoldenrod2
lightgoldenrod3
lightgoldenrod4
lightgoldenrodyellow
lightgray
lightpink
lightpink1
lightpink2
lightpink3
lightpink4
lightsalmon1
lightsalmon2
lightsalmon3
lightsalmon4
lightseagreen
lightskyblue
lightskyblue1
lightskyblue2
lightskyblue3
lightskyblue4
lightslateblue
lightslategray
lightsteelblue
lightsteelblue1
lightsteelblue2
lightsteelblue3
lightsteelblue4
lightyellow
lightyellow2
lightyellow3
lightyellow4
limegreen
linen
magenta
magenta1
magenta2
magenta3
magenta4
maroon
maroon1
maroon2
maroon3
maroon4
matrablue
matragray
mediumaquamarine
mediumorchid
mediumorchid1
mediumorchid2
mediumorchid3
mediumorchid4
mediumpurple
mediumpurple1
mediumpurple2
mediumpurple3
mediumpurple4
mediumseagreen
mediumslateblue
mediumspringgreen
mediumturquoise
mediumvioletred
midnightblue
mintcream
mistyrose
mistyrose2
mistyrose3
mistyrose4
moccasin
navajowhite1
navajowhite2
navajowhite3
navajowhite4
navyblue
oldlace
olivedrab
olivedrab1
olivedrab2
olivedrab3
olivedrab4
orange
orange1
orange2
orange3
orange4
orangered
orangered1
orangered2
orangered3
orangered4
orchid
orchid1
orchid2
orchid3
orchid4
palegoldenrod
palegreen
palegreen1
palegreen2
palegreen3
palegreen4
paleturquoise
paleturquoise1
paleturquoise2
paleturquoise3
paleturquoise4
palevioletred
palevioletred1
palevioletred2
palevioletred3
palevioletred4
papayawhip
peachpuff
peachpuff2
peachpuff3
peachpuff4
peru
pink
pink1
pink2
pink3
pink4
plum
plum1
plum2
plum3
plum4
powderblue
purple
purple1
purple2
purple3
purple4
red
red1
red2
red3
red4
rosybrown
rosybrown1
rosybrown2
rosybrown3
rosybrown4
royalblue
royalblue1
royalblue2
royalblue3
royalblue4
saddlebrown
salmon
salmon1
salmon2
salmon3
salmon4
sandybrown
seagreen
seagreen1
seagreen2
seagreen3
seagreen4
seashell
seashell2
seashell3
seashell4
sienna
sienna1
sienna2
sienna3
sienna4
skyblue
skyblue1
skyblue2
skyblue3
skyblue4
slateblue
slateblue1
slateblue2
slateblue3
slateblue4
slategray
slategray1
slategray2
slategray3
slategray4
snow
snow2
snow3
snow4
springgreen
springgreen2
springgreen3
springgreen4
steelblue
steelblue1
steelblue2
steelblue3
steelblue4
tan
tan1
tan2
tan3
tan4
teal
thistle
thistle1
thistle2
thistle3
thistle4
tomato
tomato1
tomato2
tomato3
tomato4
turquoise
turquoise1
turquoise2
turquoise3
turquoise4
violet
violetred
violetred1
violetred2
violetred3
violetred4
wheat
wheat1
wheat2
wheat3
wheat4
white
whitesmoke
yellow
yellow1
yellow2
yellow3
yellow4
yellowgreenFile: doc/citing.rst
Citing
Please use our Zenodo DOI if you use CadQuery for scientific research: https://doi.org/10.5281/zenodo.3955118.
File: doc/classreference.rst
.. _classreference:
CadQuery Class Summary
This page documents all of the methods and functions of the CadQuery classes, organized alphabetically.
.. seealso::
For a listing organized by functional area, see the :ref:`apireference`.. currentmodule:: cadquery
Core Classes
.. autosummary::
Sketch
Workplane
Assembly
ConstraintTopological Classes
.. autosummary::
Shape
Vertex
Edge
occ_impl.shapes.Mixin1D
Wire
Face
Shell
occ_impl.shapes.Mixin3D
Solid
Compound
occ_impl.shapes.History
occ_impl.shapes.OpGeometry Classes
.. autosummary::
Vector
Matrix
Plane
LocationSelector Classes
.. currentmodule:: cadquery.selectors
.. autosummary::
Selector
NearestToPointSelector
BoxSelector
BaseDirSelector
ParallelDirSelector
DirectionSelector
PerpendicularDirSelector
TypeSelector
RadiusNthSelector
CenterNthSelector
DirectionMinMaxSelector
DirectionNthSelector
LengthNthSelector
AreaNthSelector
BinarySelector
AndSelector
SumSelector
SubtractSelector
InverseSelector
StringSyntaxSelectorClass Details
.. automodule:: cadquery
:show-inheritance:
:members:
:special-members:
.. automodule:: cadquery.occ_impl.shapes
:show-inheritance:
:members:
.. autoclass:: cadquery.occ_impl.shapes.Mixin1D
:show-inheritance:
:members:
.. autoclass:: cadquery.occ_impl.shapes.Mixin3D
:show-inheritance:
:members:
.. automodule:: cadquery.selectors
:show-inheritance:
:members:
.. automodule:: cadquery.occ_impl.exporters.assembly
:show-inheritance:
:members:
.. automodule:: cadquery.fig
:show-inheritance:
:members:
.. automodule:: cadquery.vis
:show-inheritance:
:members:
.. autofunction:: cadquery.occ_impl.assembly.toJSON
.. autoclass:: cadquery.occ_impl.exporters.dxf.DxfDocument
:members:
.. automethod:: init
File: doc/cqgi.rst
.. _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:
CQ-editor <https://github.com/CadQuery/CQ-editor>_, which runs scripts
inside of a CadQuery IDE, and displays objects in the display window and includes features like debugging.- 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.
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.show_object()method is defined that should be used to export a shape to the execution environment - the :py:meth:
cadquery.cqgi.ScriptCallBack.debug()method is defined, which can be used by scripts to debug model output during execution.
Scripts must call show_object at least once. Invoking show_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 show_object() method.
This CQGI compliant script produces a cube with a circle on top, and displays a workplane as well as an intermediate circle as debug output::
base_cube = cq.Workplane("XY").rect(1.0, 1.0).extrude(1.0)
top_of_cube_plane = base_cube.faces(">Z").workplane()
debug(
top_of_cube_plane,
{
"color": "yellow",
},
)
debug(top_of_cube_plane.center, {"color": "blue"})
circle = top_of_cube_plane.circle(0.5)
debug(circle, {"color": "red"})
show_object(circle.extrude(1.0))Note that importing cadquery is not required.
At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle
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.
The metadatap property of the object contains a cadquery.cqgi.ScriptMetaData object, which can be used to discover the
user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the
model parameters. Typically, after collecting new values, the environment will supply them in the build() method.
This code will return a dictionary of parameter values in the model text SCRIPT::
parameters = cqgi.parse(SCRIPT).metadata.parameters
The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object,
which has a name, type, and default value.
The type is an object which extends ParameterType-- you can use this to determine what kind of widget to render ( checkbox for boolean, for example ).
The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the
describe_parameter() method.
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,
as well as any debug the script produced
If the script failed, the exception property contains the exception object.
If you 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(
build_parameters={"param": 2}, build_options={}
)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.
build_options is used to set server-side settings like timeouts, tessellation tolerances, and other details about
how the model should be built.
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 = 1h, 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.parametersThe 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 enhancements will include a safer sandbox to prevent malicious scripts.
Automating export to STL
A common use-case for the CQGI is the automation of processing cadquery code into geometry, doing so via the CQGI rather than an export line in the script itself leads to a much tidier environment; you may need to do this as part of an automated-workflow, batch-conversion, exporting to another software for assembly, or running stress simulations on resulting bodies.
The below Python script demonstrates how to open, process, and export an STL file from any valid cadquery script::
# Load CQGI
import cadquery.cqgi as cqgi
import cadquery as cq
# load the cadquery script
model = cqgi.parse(open("example.py").read())
# run the script and store the result (from the show_object call in the script)
build_result = model.build()
# test to ensure the process worked.
if build_result.success:
# loop through all the shapes returned and export to STL
for i, result in enumerate(build_result.results):
cq.exporters.export(result.shape, f"example_output{i}.stl")
else:
print(f"BUILD FAILED: {build_result.exception}")Important CQGI Methods
These are the most important Methods and classes of the CQGI
.. currentmodule:: cadquery.cqgi
.. autosummary::
parse
CQModel.build
BuildResult
ScriptCallback.show_object
Complete CQGI API
.. automodule:: cadquery.cqgi
:members:
File: doc/designprinciples.rst
.. _designprinciples:
=========================== Design Principles
Principle 1: Intuitive Construction
CadQuery aims to make building models using python scripting easy and intuitive.
CadQuery strives to allow scripts to read roughly as a human would describe an object verbally.
For example, consider this object:
.. image:: _static/quickstart.png
A human would describe this as:
"A block 80mm square x 30mm thick , with countersunk holes for M2 socket head cap screws
at the corners, and a circular pocket 22mm in diameter in the middle for a bearing"The goal is to have the CadQuery script that produces this object be as close as possible to the English phrase
a human would use.
Principle 2: Capture Design Intent
The features that are not part of the part description above are just as important as those that are. For example, most
humans will assume that:
* The countersunk holes are spaced a uniform distance from the edges
* The circular pocket is in the center of the block, no matter how big the block isIf you have experience with 3D CAD systems, you also know that there is a key design intent built into this object.
After the base block is created, how the hole is located is key. If it is located from one edge, changing the block
size will have a different effect than if the hole is located from the center.
Many scripting languages do not provide a way to capture design intent-- because they require that you always work in
global coordinates. CadQuery is different-- you can locate features relative to others in a relative way-- preserving
the design intent just like a human would when creating a drawing or building an object.
In fact, though many people know how to use 3D CAD systems, few understand how important the way that an object is built
impact its maintainability and resiliency to design changes.
Principle 3: Plugins as first class citizens
Any system for building 3D models will evolve to contain an immense number of libraries and feature builders. It is
important that these can be seamlessly included into the core and used alongside the built in libraries. Plugins
should be easy to install and familiar to use.
Principle 4: CAD models as source code makes sense
It is surprising that the world of 3D CAD is primarily dominated by systems that create opaque binary files.
Just like the world of software, CAD models are very complex.
CAD models have many things in common with software, and would benefit greatly from the use of tools that are standard
in the software industry, such as:
1. Easily re-using features between objects
2. Storing objects using version control systems
3. Computing the differences between objects by using source control tools
4. Share objects on the Internet
5. Automate testing and generation by allowing objects to be built from within librariesCadQuery is designed to make 3D content creation easy enough that the above benefits can be attained without more work
than using existing 'opaque', 'point and click' solutions.
File: doc/examples.rst
.. _examples:
.. currentmodule:: cadquery
Examples
The examples on this page can help you learn how to build objects with CadQuery.
They are organized from simple to complex, so working through them in order is the best way to absorb them.
Each example lists the API elements used in the example for easy reference.
Items introduced in the example are marked with a !
.. note::
We strongly recommend installing `CQ-editor <https://github.com/CadQuery/CQ-editor>`_,
so that you can work along with these examples interactively. See :ref:`installation` for more info.
If you do, make sure to take these steps so that they work:
1. import cadquery as cq
2. add the line ``show_object(result)`` at the end. The samples below are autogenerated, but they use a different
syntax than the models on the website need to be... contents:: List of Examples
:backlinks: entry
Simple Rectangular Plate
Just about the simplest possible example, a rectangular box
.. cadquery::
result = cadquery.Workplane("front").box(2.0, 2.0, 0.5).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane` **!**
* :py:meth:`Workplane.box` **!**Plate with Hole
A rectangular box, but with a hole added.
">Z" selects the top most face of the resulting box. The hole is located in the center because the default origin
of a working plane is the projected origin of the last Workplane, the last Workplane having origin at (0,0,0) the
projection is at the center of the face. The default hole depth is through the entire part.
.. cadquery::
# 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)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.hole` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.box`An extruded prismatic solid
Build a prismatic solid using extrusion. After a drawing operation, the center of the previous object
is placed on the stack, and is the reference for the next operation. So in this case, the rect() is drawn
centered on the previously draw circle.
By default, rectangles and circles are centered around the previous working point.
.. cadquery::
result = cq.Workplane("front").circle(2.0).rect(0.5, 0.75).extrude(0.5).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.circle` **!**
* :py:meth:`Workplane.rect` **!**
* :py:meth:`Workplane.extrude` **!**
* :py:meth:`Workplane`Building Profiles using lines and arcs
Sometimes you need to build complex profiles using lines and arcs. This example builds a prismatic
solid from 2D operations.
2D operations maintain a current point, which is initially at the origin. Use close() to finish a
closed curve.
.. cadquery::
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)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.threePointArc` **!**
* :py:meth:`Workplane.lineTo` **!**
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane`Moving The Current working point
In this example, a closed profile is required, with some interior features as well.
This example also demonstrates using multiple lines of code instead of longer chained commands,
though of course in this case it was possible to do it in one long line as well.
A new work plane center can be established at any point.
.. cadquery::
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).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.center` **!**
* :py:meth:`Workplane`
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.rect`
* :py:meth:`Workplane.extrude`Using Point Lists
Sometimes you need to create a number of features at various locations, and using :py:meth:Workplane.center
is too cumbersome.
You can use a list of points to construct multiple objects at once. Most construction methods,
like :py:meth:Workplane.circle and :py:meth:Workplane.rect, will operate on multiple points if they are on the stack
.. cadquery::
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
.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.pushPoints` **!**
* :py:meth:`Workplane`
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.extrude`Polygons
You can create polygons for each stack point if you would like. Useful in 3d printers whose firmware does not
correct for small hole sizes.
.. cadquery::
result = (
cq.Workplane("front")
.box(3.0, 4.0, 0.25)
.pushPoints([(0, 0.75), (0, -0.75)])
.polygon(6, 1.0)
.cutThruAll()
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.polygon` **!**
* :py:meth:`Workplane.pushPoints`
* :py:meth:`Workplane.box`Polylines
:py:meth:Workplane.polyline allows creating a shape from a large number of chained points connected by lines.
This example uses a polyline to create one half of an i-beam shape, which is mirrored to create the final profile.
.. cadquery::
(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
pts = [
(0, H / 2.0),
(W / 2.0, H / 2.0),
(W / 2.0, (H / 2.0 - t)),
(t / 2.0, (H / 2.0 - t)),
(t / 2.0, (t - H / 2.0)),
(W / 2.0, (t - H / 2.0)),
(W / 2.0, H / -2.0),
(0, H / -2.0),
]
result = cq.Workplane("front").polyline(pts).mirrorY().extrude(L).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.polyline` **!**
* :py:meth:`Workplane`
* :py:meth:`Workplane.mirrorY`
* :py:meth:`Workplane.extrude`Defining an Edge with a Spline
This example defines a side using a spline curve through a collection of points. Useful when you have an edge that
needs a complex profile
.. cadquery::
s = cq.Workplane("XY")
sPnts = [
(2.75, 1.5),
(2.5, 1.75),
(2.0, 1.5),
(1.5, 1.0),
(1.0, 1.25),
(0.5, 1.0),
(0, 1.0),
]
r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts, includeCurrent=True).close()
result = r.extrude(0.5).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.spline` **!**
* :py:meth:`Workplane`
* :py:meth:`Workplane.close`
* :py:meth:`Workplane.lineTo`
* :py:meth:`Workplane.extrude`Mirroring Symmetric Geometry
You can mirror 2D geometry when your shape is symmetric. In this example we also
introduce horizontal and vertical lines, which make for slightly easier coding.
.. cadquery::
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
.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.hLine` **!**
* :py:meth:`Workplane.vLine` **!**
* :py:meth:`Workplane.hLineTo` **!**
* :py:meth:`Workplane.mirrorY` **!**
* :py:meth:`Workplane.mirrorX` **!**
* :py:meth:`Workplane`
* :py:meth:`Workplane.extrude`Mirroring 3D Objects
.. cadquery::
result0 = (
cadquery.Workplane("XY")
.moveTo(10, 0)
.lineTo(5, 0)
.threePointArc((3.9393, 0.4393), (3.5, 1.5))
.threePointArc((3.0607, 2.5607), (2, 3))
.lineTo(1.5, 3)
.threePointArc((0.4393, 3.4393), (0, 4.5))
.lineTo(0, 13.5)
.threePointArc((0.4393, 14.5607), (1.5, 15))
.lineTo(28, 15)
.lineTo(28, 13.5)
.lineTo(24, 13.5)
.lineTo(24, 11.5)
.lineTo(27, 11.5)
.lineTo(27, 10)
.lineTo(22, 10)
.lineTo(22, 13.2)
.lineTo(14.5, 13.2)
.lineTo(14.5, 10)
.lineTo(12.5, 10)
.lineTo(12.5, 13.2)
.lineTo(5.5, 13.2)
.lineTo(5.5, 2)
.threePointArc((5.793, 1.293), (6.5, 1))
.lineTo(10, 1)
.close()
)
result = result0.extrude(100)
result = result.rotate((0, 0, 0), (1, 0, 0), 90)
result = result.translate(result.val().BoundingBox().center.multiply(-1))
mirXY_neg = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, -30))
mirXY_pos = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, 30))
mirZY_neg = result.mirror(mirrorPlane="ZY", basePointVector=(-30, 0, 0))
mirZY_pos = result.mirror(mirrorPlane="ZY", basePointVector=(30, 0, 0))
result = result.union(mirXY_neg).union(mirXY_pos).union(mirZY_neg).union(mirZY_pos).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.moveTo`
* :py:meth:`Workplane.lineTo`
* :py:meth:`Workplane.threePointArc`
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.mirror`
* :py:meth:`Workplane.union`
* :py:meth:`Workplane.rotate`Mirroring From Faces
This example shows how you can mirror about a selected face. It also shows how the resulting mirrored object can be unioned immediately with the referenced mirror geometry.
.. cadquery::
result = cq.Workplane("XY").line(0, 1).line(1, 0).line(0, -0.5).close().extrude(1)
result = result.mirror(result.faces(">X"), union=True).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.line`
* :py:meth:`Workplane.close`
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane.mirror`
* :py:meth:`Workplane.union`Creating Workplanes on Faces
This example shows how to locate a new workplane on the face of a previously created feature.
.. note::
Using workplanes in this way are a key feature of CadQuery. Unlike a typical 3d scripting
language, using work planes frees you from tracking the position of various features in
variables, and allows the model to adjust itself with removing redundant dimensions
The :py:meth:Workplane.faces() method allows you to select the faces of a resulting solid. It
accepts a selector string or object, that allows you to target a single face, and make a workplane
oriented on that face.
Keep in mind that by default the origin of a new workplane is calculated by forming a plane from the
selected face and projecting the previous origin onto that plane. This behaviour can be changed
through the centerOption argument of :py:meth:Workplane.workplane.
.. cadquery::
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.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.faces` **!**
* :py:meth:`StringSyntaxSelector` **!**
* :ref:`selector_reference` **!**
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.box`
* :py:meth:`Workplane`Locating a Workplane on a vertex
Normally, the :py:meth:Workplane.workplane method requires a face to be selected. But if a vertex
is selected immediately after a face, :py:meth:Workplane.workplane with the centerOption
argument set to CenterOfMass will locate the workplane on the face, with the origin at the vertex
instead of at the center of the face
The example also introduces :py:meth:Workplane.cutThruAll, which makes a cut through the entire
part, no matter how deep the part is.
.. cadquery::
result = cq.Workplane("front").box(3, 2, 0.5) # make a basic prism
result = (
result.faces(">Z").vertices("<XY").workplane(centerOption="CenterOfMass")
) # select the lower left vertex and make a workplane
result = result.circle(1.0).cutThruAll() # cut the corner out.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.cutThruAll` **!**
* :ref:`selector_reference` **!**
* :py:meth:`Workplane.vertices` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane`
* :py:meth:`StringSyntaxSelector` **!**Offset Workplanes
Workplanes do not have to lie exactly on a face. When you make a workplane, you can define it at an offset
from an existing face.
This example uses an offset workplane to make a compound object, which is perfectly valid!
.. cadquery::
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.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.extrude`
* :ref:`selector_reference` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane`Copying Workplanes
An existing CQ object can copy a workplane from another CQ object.
.. cadquery::
result = (
cq.Workplane("front")
.circle(1)
.extrude(10) # make a cylinder
# We want to make a second cylinder perpendicular to the first,
# but we have no face to base the workplane off
.copyWorkplane(
# create a temporary object with the required workplane
cq.Workplane("right", origin=(-5, 0, 0))
)
.circle(1)
.extrude(10)
).. topic:: API References
.. hlist::
:columns: 2
* :py:meth:`Workplane.copyWorkplane` **!**
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane`Rotated Workplanes
You can create a rotated work plane by specifying angles of rotation relative to another workplane
.. cadquery::
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)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.transformed` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.rect`
* :py:meth:`Workplane.faces`Using construction Geometry
You can draw shapes to use the vertices as points to locate other features. Features that are used to
locate other features, rather than to create them, are called Construction Geometry
In the example below, a rectangle is drawn, and its vertices are used to locate a set of holes.
.. cadquery::
result = (
cq.Workplane("front")
.box(2, 2, 0.5)
.faces(">Z")
.workplane()
.rect(1.5, 1.5, forConstruction=True)
.vertices()
.hole(0.125)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.rect` (forConstruction=True)
* :ref:`selector_reference`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.hole`
* :py:meth:`Workplane`Shelling To Create Thin features
Shelling converts a solid object into a shell of uniform thickness.
To shell an object and 'hollow out' the inside pass a negative thickness parameter
to the :py:meth:Workplane.shell() method of a shape.
.. cadquery::
result = cq.Workplane("front").box(2, 2, 2).shell(-0.1)A positive thickness parameter wraps an object with filleted outside edges
and the original object will be the 'hollowed out' portion.
.. cadquery::
result = cq.Workplane("front").box(2, 2, 2).shell(0.1)Use face selectors to select a face to be removed from the resulting hollow shape.
.. cadquery::
result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.1)Multiple faces can be removed using more complex selectors.
.. cadquery::
result = cq.Workplane("front").box(2, 2, 2).faces("+Z or -X or +X").shell(0.1)
.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.shell` **!**
* :ref:`selector_reference`
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane`Making Lofts
A loft is a solid swept through a set of wires. This example creates lofted section between a rectangle
and a circular section.
.. cadquery::
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)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.loft` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.rect`Extruding until a given face
Sometimes you will want to extrude a wire until a given face that can be not planar or where you
might not know easily the distance you have to extrude to. In such cases you can use next, last
or even give a :class:~cadquery.Face object for the until argument of
:meth:~cadquery.Workplane.extrude.
.. cadquery::
result = (
cq.Workplane(origin=(20, 0, 0))
.circle(2)
.revolve(180, (-20, 0, 0), (-20, -1, 0))
.center(-20, 0)
.workplane()
.rect(20, 4)
.extrude("next")
)The same behaviour is available with :meth:~cadquery.Workplane.cutBlind and as you can see it is
also possible to work on several :class:~cadquery.Wire objects at a time (the
same is true for :meth:~cadquery.Workplane.extrude).
.. cadquery::
skyscrapers_locations = [(-16, 1), (-8, 0), (7, 0.2), (17, -1.2)]
angles = iter([15, 0, -8, 10])
skyscrapers = (
cq.Workplane()
.pushPoints(skyscrapers_locations)
.eachpoint(
lambda loc: (
cq.Workplane()
.rect(5, 16)
.workplane(offset=10)
.ellipse(3, 8)
.workplane(offset=10)
.slot2D(20, 5, 90)
.loft()
.rotateAboutCenter((0, 0, 1), next(angles))
.val()
.located(loc)
)
)
)
result = (
skyscrapers.transformed((0, -90, 0))
.moveTo(15, 0)
.rect(3, 3, forConstruction=True)
.vertices()
.circle(1)
.cutBlind("last")
)Here is a typical situation where extruding and cuting until a given surface is very handy. It allows us to extrude or cut until a curved surface without overlapping issues.
.. cadquery::
import cadquery as cq
sphere = cq.Workplane().sphere(5)
base = cq.Workplane(origin=(0, 0, -2)).box(12, 12, 10).cut(sphere).edges("|Z").fillet(2)
sphere_face = base.faces(">>X[2] and (not |Z) and (not |Y)").val()
base = base.faces("<Z").workplane().circle(2).extrude(10)
shaft = cq.Workplane().sphere(4.5).circle(1.5).extrude(20)
spherical_joint = (
base.union(shaft)
.faces(">X")
.workplane(centerOption="CenterOfMass")
.move(0, 4)
.slot2D(10, 2, 90)
.cutBlind(sphere_face)
.workplane(offset=10)
.move(0, 2)
.circle(0.9)
.extrude("next")
)
result = spherical_joint.. warning::
If the wire you want to extrude cannot be fully projected on the target surface, the result will
be unpredictable. Furthermore, the algorithm in charge of finding the candidate faces does its search by counting all the faces intersected
by a line created from your wire center along your
extrusion direction. So make sure your wire can be projected on your target face to avoid
unexpected behaviour... topic:: Api References
.. hlist::
:columns: 3
* :py:meth:`Workplane.cutBlind` **!**
* :py:meth:`Workplane.rect`
* :py:meth:`Workplane.ellipse`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.slot2D`
* :py:meth:`Workplane.loft`
* :py:meth:`Workplane.rotateAboutCenter`
* :py:meth:`Workplane.transformed`
* :py:meth:`Workplane.moveTo`
* :py:meth:`Workplane.circle`Making Counter-bored and Counter-sunk Holes
Counterbored and countersunk holes are so common that CadQuery creates macros to create them in a single step.
Similar to :py:meth:Workplane.hole, these functions operate on a list of points as well as a single point.
.. cadquery::
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)
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.cboreHole` **!**
* :py:meth:`Workplane.cskHole` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.rect`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.vertices`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane`Offsetting wires in 2D
Two dimensional wires can be transformed with :py:meth:Workplane.offset2D. They can be offset
inwards or outwards, and with different techniques for extending the corners.
.. cadquery::
original = cq.Workplane().polygon(5, 10).extrude(0.1).translate((0, 0, 2))
arc = cq.Workplane().polygon(5, 10).offset2D(1, "arc").extrude(0.1).translate((0, 0, 1))
intersection = cq.Workplane().polygon(5, 10).offset2D(1, "intersection").extrude(0.1)
result = original.add(arc).add(intersection)Using the forConstruction argument you can do the common task of offsetting a series of bolt holes
from the outline of an object. Here is the counterbore example from above but with the bolt holes
offset from the edges.
.. cadquery::
result = (
cq.Workplane()
.box(4, 2, 0.5)
.faces(">Z")
.edges()
.toPending()
.offset2D(-0.25, forConstruction=True)
.vertices()
.cboreHole(0.125, 0.25, 0.125, depth=None)
)Note that :py:meth:Workplane.edges is for selecting objects. It does not add the selected edges to
pending edges in the modelling context, because this would result in your next extrusion including
everything you had only selected in addition to the lines you had drawn. To specify you want these
edges to be used in :py:meth:Workplane.offset2D, you call :py:meth:Workplane.toPending to
explicitly put them in the list of pending edges.
.. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.offset2D` **!**
* :py:meth:`Workplane.cboreHole`
* :py:meth:`Workplane.cskHole`
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.polygon`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.vertices`
* :py:meth:`Workplane.edges`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane`Rounding Corners with Fillet
Filleting is done by selecting the edges of a solid, and using the fillet function.
Here we fillet all of the edges of a simple plate.
.. cadquery::
result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.fillet` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.edges`
* :py:meth:`Workplane`Tagging objects
The :py:meth:Workplane.tag method can be used to tag a particular object in the chain with a string, so that it can be referred to later in the chain.
The :py:meth:Workplane.workplaneFromTagged method applies :py:meth:Workplane.copyWorkplane to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors.
.. cadquery::
result = (
cq.Workplane("XY")
# create and tag the base workplane
.box(10, 10, 10)
.faces(">Z")
.workplane()
.tag("baseplane")
# extrude a cylinder
.center(-3, 0)
.circle(1)
.extrude(3)
# to reselect the base workplane, simply
.workplaneFromTagged("baseplane")
# extrude a second cylinder
.center(3, 0)
.circle(1)
.extrude(2)
)Tags can also be used with most selectors, including :py:meth:Workplane.vertices, :py:meth:Workplane.faces, :py:meth:Workplane.edges, :py:meth:Workplane.wires, :py:meth:Workplane.shells, :py:meth:Workplane.solids and :py:meth:Workplane.compounds.
.. cadquery::
result = (
cq.Workplane("XY")
# create a triangular prism and tag it
.polygon(3, 5)
.extrude(4)
.tag("prism")
# create a sphere that obscures the prism
.sphere(10)
# create features based on the prism's faces
.faces("<X", tag="prism")
.workplane()
.circle(1)
.cutThruAll()
.faces(">X", tag="prism")
.faces(">Y")
.workplane()
.circle(1)
.cutThruAll()
).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.tag` **!**
* :py:meth:`Workplane.getTagged` **!**
* :py:meth:`Workplane.workplaneFromTagged` **!**
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.cutThruAll`
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane`A Parametric Bearing Pillow Block
Combining a few basic functions, its possible to make a very good parametric bearing pillow block,
with just a few lines of code.
.. cadquery::
(length, height, bearing_diam, thickness, padding) = (30.0, 40.0, 22.0, 10.0, 8.0)
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)
)Splitting an Object
You can split an object using a workplane, and retain either or both halves
.. cadquery::
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).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.split` **!**
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.cutThruAll`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane`The Classic OCC Bottle
CadQuery is based on the OpenCascade.org (OCC) modeling Kernel. Those who are familiar with OCC know about the
famous 'bottle' example. The bottle example in the OCCT online documentation <https://old.opencascade.com/doc/occt-7.5.0/overview/html/occt__tutorial.html>_.
A pythonOCC version is listed here <https://github.com/tpaviot/pythonocc-demos/blob/f3ea9b4f65a9dff482be04b153d4ce5ec2430e13/examples/core_classic_occ_bottle.py>_.
Of course one difference between this sample and the OCC version is the length. This sample is one of the longer
ones at 13 lines, but that's very short compared to the pythonOCC version, which is 10x longer!
.. cadquery::
(L, w, t) = (20.0, 6.0, 3.0)
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)
.threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0))
.vLine(-w / 2.0)
.mirrorX()
.extrude(30.0, True)
)
# Make the neck
p = p.faces(">Z").workplane(centerOption="CenterOfMass").circle(3.0).extrude(2.0, True)
# Make a shell
result = p.faces(">Z").shell(0.3).. topic:: Api References
.. hlist::
:columns: 2
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.mirrorX`
* :py:meth:`Workplane.threePointArc`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.vertices`
* :py:meth:`Workplane.vLine`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane`A Parametric Enclosure
.. cadquery::
:height: 400px
# parameter definitions
p_outerWidth = 100.0 # Outer width of box enclosure
p_outerLength = 150.0 # Outer length of box enclosure
p_outerHeight = 50.0 # Outer height of box enclosure
p_thickness = 3.0 # Thickness of the box walls
p_sideRadius = 10.0 # Radius for the curves around the sides of the box
p_topAndBottomRadius = (
2.0 # Radius for the curves on the top and bottom edges of the box
)
p_screwpostInset = 12.0 # How far in from the edges the screw posts should be place.
p_screwpostID = 4.0 # Inner Diameter of the screw post holes, should be roughly screw diameter not including threads
p_screwpostOD = 10.0 # Outer Diameter of the screw posts.\nDetermines overall thickness of the posts
p_boreDiameter = 8.0 # Diameter of the counterbore hole, if any
p_boreDepth = 1.0 # Depth of the counterbore hole, if
p_countersinkDiameter = 0.0 # Outer diameter of countersink. Should roughly match the outer diameter of the screw head
p_countersinkAngle = 90.0 # Countersink angle (complete angle between opposite sides, not from center to one side)
p_flipLid = True # Whether to place the lid with the top facing down or not.
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 = (
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:
oshell = oshell.edges("|Z").fillet(p_sideRadius)
oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
else:
oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
oshell = oshell.edges("|Z").fillet(p_sideRadius)
# inner shell
ishell = (
oshell.faces("<Z")
.workplane(p_thickness, True)
.rect((p_outerWidth - 2.0 * p_thickness), (p_outerLength - 2.0 * p_thickness))
.extrude(
(p_outerHeight - 2.0 * p_thickness), False
) # set combine false to produce just the new boss
)
ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
# make the box outer box
box = oshell.cut(ishell)
# make the screw posts
POSTWIDTH = p_outerWidth - 2.0 * p_screwpostInset
POSTLENGTH = p_outerLength - 2.0 * p_screwpostInset
box = (
box.faces(">Z")
.workplane(-p_thickness)
.rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
.vertices()
.circle(p_screwpostOD / 2.0)
.circle(p_screwpostID / 2.0)
.extrude(-1.0 * (p_outerHeight + p_lipHeight - p_thickness), True)
)
# split lid into top and bottom parts
(lid, bottom) = (
box.faces(">Z")
.workplane(-p_thickness - p_lipHeight)
.split(keepTop=True, keepBottom=True)
.all()
) # splits into two solids
# translate the lid, and subtract the bottom from it to produce the lid inset
lowerLid = lid.translate((0, 0, -p_lipHeight))
cutlip = lowerLid.cut(bottom).translate(
(p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight)
)
# compute centers for screw holes
topOfLidCenters = (
cutlip.faces(">Z")
.workplane(centerOption="CenterOfMass")
.rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
.vertices()
)
# add holes of the desired type
if p_boreDiameter > 0 and p_boreDepth > 0:
topOfLid = topOfLidCenters.cboreHole(
p_screwpostID, p_boreDiameter, p_boreDepth, 2.0 * p_thickness
)
elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
topOfLid = topOfLidCenters.cskHole(
p_screwpostID, p_countersinkDiameter, p_countersinkAngle, 2.0 * p_thickness
)
else:
topOfLid = topOfLidCenters.hole(p_screwpostID, 2.0 * p_thickness)
# flip lid upside down if desired
if p_flipLid:
topOfLid = topOfLid.rotateAboutCenter((1, 0, 0), 180)
# return the combined result
result = topOfLid.union(bottom).. topic:: Api References
.. hlist::
:columns: 3
* :py:meth:`Workplane.circle`
* :py:meth:`Workplane.rect`
* :py:meth:`Workplane.extrude`
* :py:meth:`Workplane.box`
* :py:meth:`Workplane.all`
* :py:meth:`Workplane.faces`
* :py:meth:`Workplane.vertices`
* :py:meth:`Workplane.edges`
* :py:meth:`Workplane.workplane`
* :py:meth:`Workplane.fillet`
* :py:meth:`Workplane.cut`
* :py:meth:`Workplane.union`
* :py:meth:`Workplane.rotateAboutCenter`
* :py:meth:`Workplane.cboreHole`
* :py:meth:`Workplane.cskHole`
* :py:meth:`Workplane.hole`Lego Brick
This script will produce any size regular rectangular Lego(TM) brick. Its only tricky because of the logic
regarding the underside of the brick.
.. cadquery::
:select: tmp
:height: 400px
#####
# 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 = sBraille Example
.. cadquery::
:height: 400px
from collections import namedtuple
# text_lines is a list of text lines.
# 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
)
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
)
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))
result = make_embossed_plate(text_lines, _cell_geometry)Panel With Various Connector Holes
.. cadquery::
:height: 400px
# 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()
)Cycloidal gear
You can define complex geometries using the parametricCurve functionality.
This specific examples generates a helical cycloidal gear.
.. cadquery::
:height: 400px
import cadquery as cq
from math import sin, cos, pi, floor
# define the generating function
def hypocycloid(t, r1, r2):
return (
(r1 - r2) * cos(t) + r2 * cos(r1 / r2 * t - t),
(r1 - r2) * sin(t) + r2 * sin(-(r1 / r2 * t - t)),
)
def epicycloid(t, r1, r2):
return (
(r1 + r2) * cos(t) - r2 * cos(r1 / r2 * t + t),
(r1 + r2) * sin(t) - r2 * sin(r1 / r2 * t + t),
)
def gear(t, r1=4, r2=1):
if (-1) ** (1 + floor(t / 2 / pi * (r1 / r2))) < 0:
return epicycloid(t, r1, r2)
else:
return hypocycloid(t, r1, r2)
# create the gear profile and extrude it
result = (
cq.Workplane("XY")
.parametricCurve(lambda t: gear(t * 2 * pi, 6, 1))
.twistExtrude(15, 90)
.faces(">Z")
.workplane()
.circle(2)
.cutThruAll()
)