cadquery Documentation

A python parametric CAD scripting framework based on OCCT

5,512 stars Python Python 1 file ยท ~29,557 tokens #3d#brep#cad#cadquery#dxf#modeling#occt#opencascade
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy cadquery on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name cadquery --restart always cadquery/cadquery:latest

File: README.md

CadQuery






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

text
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.

text
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.

text
# 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=master

CadQuery 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).

text
python3 -m pip install --upgrade pip

Once a current version of pip is installed, CadQuery can be installed using the following command line.

text
pip install cadquery

It 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.

text
pip install git+https://github.com/CadQuery/cadquery.git

Built-in visualization

CadQuery has built in visualization capabilities using trame and vtk.

text
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.

text
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 pytest from 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 black from 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:

  1. What version of the software are you running?
  2. What operating system are you running the software on?
  3. 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

text
conda install jinja2 constructor
python build.py  <github tag version>

For Example

text
build.py 2.2 master

Activation

To Activate the environment

text
# Under Windows
condabin\activate.bat

# Under Linux / MacOS
source ~/cadquery/bin/activate

File: doc/_static/logo/README.md

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::

text
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::

text
Assembly
Assembly.add
Assembly.save
Assembly.constrain
Assembly.solve
Constraint
Color

File: 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)
)

text
# 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)
)

text
# 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))

text
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

Initial 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::

text
.constrain("part1@faces@>Z", "part3@faces@<Z", "Axis")

is equivalent to

.. code-block::

text
.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

text
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::

text
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:param is 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 } \rvert is 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::

text
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:param is 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::

text
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::

text
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::

text
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

text
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::

text
\operatorname{dist}( \vec{ c }, p_\text{ offset } ) ^2

Where:

  • :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 by param, and
  • :math:\operatorname{dist}( \vec{ a }, b) is the distance between point :math:\vec{ a } and
    plane :math:b.

.. cadquery::

text
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:l is a line created from the second object
  • :math:param is 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::

text
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:param is the parameter of the constraint - tuple specifying the target position.

.. cadquery::

text
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:param is the parameter of the constraint - tuple specifying the target rotation.

.. cadquery::

text
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:param is the parameter of the constraint - tuple specifying the target direction.

.. cadquery::

text
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

text
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
  yellowgreen

File: 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::

text
For a listing organized by functional area, see the :ref:`apireference`

.. currentmodule:: cadquery

Core Classes

.. autosummary::

text
Sketch
Workplane
Assembly
Constraint

Topological Classes

.. autosummary::

text
Shape
Vertex
Edge
occ_impl.shapes.Mixin1D
Wire
Face
Shell
occ_impl.shapes.Mixin3D
Solid
Compound
occ_impl.shapes.History
occ_impl.shapes.Op

Geometry Classes

.. autosummary::

text
Vector
Matrix
Plane
Location

Selector Classes

.. currentmodule:: cadquery.selectors
.. autosummary::

text
Selector
NearestToPointSelector
BoxSelector
BaseDirSelector
ParallelDirSelector
DirectionSelector
PerpendicularDirSelector
TypeSelector
RadiusNthSelector
CenterNthSelector
DirectionMinMaxSelector
DirectionNthSelector
LengthNthSelector
AreaNthSelector
BinarySelector
AndSelector
SumSelector
SubtractSelector
InverseSelector
StringSyntaxSelector

Class 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::

text
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::

text
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::

text
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::

text
h = 1.0
  w = 2.0
  foo = "bar"


  def some_function():
      x = 1

h, w, and foo will be overridable script variables, but x is not.

You can list the variables defined in the model by using the return value of the parse method::

text
model = cqgi.parse(user_script)

   # a dictionary of InputParameter objects
   parameters = model.metadata.parameters

The key of the dictionary is a string , and the value is a :py:class:cadquery.cqgi.InputParameter object
See the CQGI API docs for more details.

Future 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::

text
# 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:

text
"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:

text
* 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 is

If 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:

text
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 libraries

CadQuery 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::

text
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::

text
result = cadquery.Workplane("front").box(2.0, 2.0, 0.5)

.. topic:: Api References

text
.. 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::

text
# 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

text
.. 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::

text
result = cq.Workplane("front").circle(2.0).rect(0.5, 0.75).extrude(0.5)

.. topic:: Api References

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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

text
.. 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::

text
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

text
.. 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::

text
(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

text
.. 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::

text
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

text
.. 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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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::

text
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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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::

text
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::

text
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::

text
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

text
.. 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::

text
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

text
.. 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::

text
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::

text
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

text
.. 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::

text
result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125)

.. topic:: Api References

text
.. 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::

text
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::

text
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

text
.. 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::

text
(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::

text
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

text
.. 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::

text
(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

text
.. 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

text
# 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

text
.. 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

text
#####
# Inputs
######
lbumps = 6  # number of bumps long
wbumps = 2  # number of bumps wide
thin = True  # True for thin, False for thick

#
# Lego Brick Constants-- these make a Lego brick a Lego :)
#
pitch = 8.0
clearance = 0.1
bumpDiam = 4.8
bumpHeight = 1.8
if thin:
    height = 3.2
else:
    height = 9.6

t = (pitch - (2 * clearance) - bumpDiam) / 2.0
postDiam = pitch - t  # works out to 6.5
total_length = lbumps * pitch - 2.0 * clearance
total_width = wbumps * pitch - 2.0 * clearance

# make the base
s = cq.Workplane("XY").box(total_length, total_width, height)

# shell inwards not outwards
s = s.faces("<Z").shell(-1.0 * t)

# make the bumps on the top
s = (
    s.faces(">Z")
    .workplane()
    .rarray(pitch, pitch, lbumps, wbumps, True)
    .circle(bumpDiam / 2.0)
    .extrude(bumpHeight)
)

# add posts on the bottom. posts are different diameter depending on geometry
# solid studs for 1 bump, tubes for multiple, none for 1x1
tmp = s.faces("<Z").workplane(invert=True)

if lbumps > 1 and wbumps > 1:
    tmp = (
        tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True)
        .circle(postDiam / 2.0)
        .circle(bumpDiam / 2.0)
        .extrude(height - t)
    )
elif lbumps > 1:
    tmp = (
        tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True)
        .circle(t)
        .extrude(height - t)
    )
elif wbumps > 1:
    tmp = (
        tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True)
        .circle(t)
        .extrude(height - t)
    )
else:
    tmp = s

Braille Example

.. cadquery::
:height: 400px

text
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

text
# 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

text
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()
)