## File: README.md

# CadQuery

[](https://ci.appveyor.com/project/jmwright/cadquery-o18bh/branch/master)
[](https://dev.azure.com/cadquery/conda-packages/_build/latest?definitionId=2&branchName=master)
[](https://codecov.io/gh/CadQuery/cadquery)
[](https://cadquery.readthedocs.io/en/latest/?badge=latest)
[](https://doi.org/10.5281/zenodo.3955118)

---

### Quick Links
[***Documentation***](https://cadquery.readthedocs.io/en/latest/) | [***Cheatsheet***](https://cadquery.readthedocs.io/en/latest/_static/cadquery_cheatsheet.html) | [***GitHub Discussions***](https://github.com/CadQuery/cadquery/discussions) | [***Google Group***](https://groups.google.com/g/cadquery) | [***GUI Editor***](https://github.com/CadQuery/CQ-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](https://github.com/CadQuery/OCP) 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](https://cadquery.readthedocs.io/en/latest/intro.html), [examples](https://cadquery.readthedocs.io/en/latest/examples.html), and [cheatsheet](https://cadquery.readthedocs.io/en/latest/_static/cadquery_cheatsheet.html).

To quickly get started you can use apptainer

```
apptainer run oras://ghcr.io/cadquery/cadquery-apptainer:master ipython -i -c"from cadquery.func import *; from cadquery.vis import show; show(box(1,1,1))"
```

or docker/podman.

```
podman run -i -v /tmp:/tmp -e DISPLAY=$DISPLAY  --userns=keep-id --user "$(id -u):$(id -g)" --device /dev/dri -t ghcr.io/cadquery/cadquery-docker:master ipython -i  -c"from cadquery.func import *; from cadquery.vis import show; show(box(1,1,1))"
```

The CadQuery library (with or without CQ-editor) and its dependencies may be installed using conda/mamba/micromamba/pixi, or pip.
Note that conda-based installation (or the CQ-editor installer) is the better supported option.

See the documentation for detailed CadQuery [installation instructions](https://cadquery.readthedocs.io/en/latest/installation.html).

### CadQuery Installation Via Conda

To first install the Conda package manager see [Install the Conda Package Manager](https://cadquery.readthedocs.io/en/latest/installation.html#install-the-conda-package-manager), and [Miniforge](https://github.com/conda-forge/miniforge) for a minimal installer.

``mamba install`` is recommended over ``conda install`` for faster and less memory intensive CadQuery installation.

```
# Set up a new environment
conda create -n cadquery

# Activate the new environment
conda activate cadquery

# Install the latest released version
mamba install -c conda-forge cadquery

# Or install the dev version to get the latest changes
mamba install -c conda-forge -c cadquery cadquery=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).
```
python3 -m pip install --upgrade pip
```
Once a current version of pip is installed, CadQuery can be installed using the following command line.
```
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.
```
pip install git+https://github.com/CadQuery/cadquery.git
```

### Built-in visualization

CadQuery has built in visualization capabilities using trame and vtk.

```
from cadquery.func import box, fillet
from cadquery.vis import show

b = box(1,1,1)
show(fillet(b, b.edges("|Z"), 0.1))

```

For non-blocking and persistent view one can use.

```
from cadquery.func import box, fillet
from cadquery.fig import show

b = box(1,1,1)
show(fillet(b, b.edges("|Z"), 0.1))

```

### CQ-editor GUI

CQ-editor is an IDE that allows users to edit CadQuery model scripts in a GUI environment. It includes features such as:

* A graphical debugger that allows you to step through your scripts.
* A CadQuery stack inspector.
* Export to various formats, including STEP and STL, directly from the menu.

More on CQ-editor:
* [CQ-editor README](https://github.com/CadQuery/CQ-editor/blob/master/README.md)
* [Installation](https://cadquery.readthedocs.io/en/latest/installation.html#adding-a-nicer-gui-via-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>)```.

 * [JupyterLab installation](https://cadquery.readthedocs.io/en/latest/installation.html#jupyter).


## Getting help

You can find the full CadQuery documentation at [cadquery.readthedocs.io](https://cadquery.readthedocs.io/).

We also have a [Google Group](https://groups.google.com/forum/#!forum/cadquery) 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](https://github.com/CadQuery/cadquery/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 **info@cadquery.org**.

## 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](http://www.apache.org/licenses/LICENSE-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](https://www.python.org/psf/codeofconduct/) 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](https://github.com/CadQuery/black) 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](https://github.com/CadQuery/cadquery/issues), 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](https://github.com/CadQuery/cadquery/issues) 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

  * https://github.com/conda/constructor

The installer

  * Installs an instance of miniconda
  * Adds cadquery / conda-forge to the default channels
  * Runs a post install script to download install cadquery.

We need to install cadquery post install due to the file sizes involved with the install of opencascade (around 2Gb)
This installer will not add the installed directory to the Path or try to override the default python (with the default options selected).

## Running the constructor

To run
```
conda install jinja2 constructor
python build.py  <github tag version>
```

For Example
```
build.py 2.2 master
```

## Activation

To Activate the environment
```
# Under Windows
condabin\activate.bat

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

---

## File: doc/_static/logo/README.md

## CadQuery Logo

This logo uses the Roboto bold font (included), the Inkscape font size is 56, and the color is #2980b9

---

## File: doc/apireference.rst

.. _apireference:

***********************
API Reference
***********************

The CadQuery API is made up of 4 main objects:

* **Sketch** -- Construct 2D sketches
* **Workplane** -- Wraps a topological entity and provides a 2D modelling context.
* **Selector** -- Filter and select things
* **Assembly** -- Combine objects into assemblies.

This page lists  methods of these objects grouped by **functional area**

.. seealso::
  This page lists api methods grouped by functional area.
  Use :ref:`classreference` to see methods alphabetically by class.


Sketch initialization
---------------------

.. currentmodule:: cadquery

Creating new sketches.

.. autosummary::
    Sketch
    Sketch.importDXF
    Workplane.sketch
    Sketch.finalize
    Sketch.copy
    Sketch.located
    Sketch.moved

Sketch selection
----------------

.. currentmodule:: cadquery

Selecting, tagging and manipulating elements.

.. autosummary::
    Sketch.tag
    Sketch.select
    Sketch.reset
    Sketch.delete
    Sketch.faces
    Sketch.edges
    Sketch.vertices

Sketching with faces
--------------------

.. currentmodule:: cadquery

Sketching using the face-based API.

.. autosummary::
    Sketch.face
    Sketch.rect
    Sketch.circle
    Sketch.ellipse
    Sketch.trapezoid
    Sketch.slot
    Sketch.regularPolygon
    Sketch.polygon
    Sketch.rarray
    Sketch.parray
    Sketch.distribute
    Sketch.each
    Sketch.push
    Sketch.hull
    Sketch.offset
    Sketch.fillet
    Sketch.chamfer
    Sketch.clean

Sketching with edges and constraints
------------------------------------

.. currentmodule:: cadquery

Sketching using the edge-based API.

.. autosummary::
    Sketch.edge
    Sketch.segment
    Sketch.arc
    Sketch.spline
    Sketch.close
    Sketch.assemble
    Sketch.constrain
    Sketch.solve


Initialization
--------------

.. currentmodule:: cadquery

Creating new workplanes and object chains

.. autosummary::
    Workplane


.. _2dOperations:

2D Operations
-------------

Creating 2D constructs that can be used to create 3D features.

All 2D operations require a **Workplane** object to be created.

.. currentmodule:: cadquery

.. autosummary::
    Workplane.center
	Workplane.lineTo
	Workplane.line
	Workplane.vLine
	Workplane.vLineTo
	Workplane.hLine
	Workplane.hLineTo
	Workplane.polarLine
	Workplane.polarLineTo
	Workplane.moveTo
	Workplane.move
	Workplane.spline
	Workplane.parametricCurve
	Workplane.parametricSurface
	Workplane.threePointArc
	Workplane.sagittaArc
	Workplane.radiusArc
    Workplane.tangentArcPoint
	Workplane.mirrorY
	Workplane.mirrorX
	Workplane.wire
	Workplane.rect
	Workplane.circle
	Workplane.ellipse
	Workplane.ellipseArc
	Workplane.polyline
	Workplane.close
	Workplane.rarray
	Workplane.polarArray
	Workplane.slot2D
	Workplane.offset2D
	Workplane.placeSketch

.. _3doperations:

3D Operations
-----------------

Some 3D operations also require an active 2D workplane, but some do not.

3D operations that require a 2D workplane to be active:

.. autosummary::
	Workplane.cboreHole
	Workplane.cskHole
	Workplane.hole
	Workplane.extrude
	Workplane.cut
	Workplane.cutBlind
	Workplane.cutThruAll
	Workplane.box
	Workplane.sphere
	Workplane.wedge
	Workplane.cylinder
	Workplane.union
	Workplane.combine
	Workplane.intersect
	Workplane.loft
	Workplane.sweep
	Workplane.twistExtrude
	Workplane.revolve
	Workplane.text


3D operations that do NOT require a 2D workplane to be active:

.. autosummary::
	Workplane.shell
	Workplane.fillet
	Workplane.chamfer
	Workplane.split
	Workplane.rotate
	Workplane.rotateAboutCenter
	Workplane.translate
	Workplane.mirror

File Management and Export
---------------------------------

.. autosummary::
    Workplane.toSvg
    Workplane.exportSvg


.. autosummary::
    importers.importStep
    importers.importDXF
    exporters.export
    occ_impl.exporters.dxf.DxfDocument


Iteration Methods
------------------

Methods that allow iteration over the stack or objects

.. autosummary::
    Workplane.each
    Workplane.eachpoint


.. _stackMethods:

Stack and Selector Methods
------------------------------

CadQuery methods that operate on the stack

.. autosummary::
	Workplane.all
	Workplane.size
	Workplane.vals
	Workplane.add
	Workplane.val
	Workplane.first
	Workplane.item
	Workplane.last
	Workplane.end
	Workplane.vertices
	Workplane.faces
	Workplane.edges
	Workplane.wires
	Workplane.solids
	Workplane.shells
	Workplane.compounds

.. _selectors:

Selectors
------------------------

Objects that filter and select CAD objects. Selectors are used to select existing geometry
as a basis for further operations.

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

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

.. _assembly:

Assemblies
----------

Workplane and Shape objects can be connected together into assemblies

.. currentmodule:: cadquery
.. autosummary::

    Assembly
    Assembly.add
    Assembly.save
    Assembly.constrain
    Assembly.solve
    Constraint
    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)
       )

       # 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

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

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

is equivalent to

.. code-block::

    .constrain("part1", part1.faces(">z").val(), "part3", part3.faces("<Z").val(), "Axis")

This method requires a :py:class:`cadquery.Shape` object, so remember to use the :py:meth:`cadquery.Workplane.val`
method to pass a single :py:class:`cadquery.Shape` and not the whole :py:class:`cadquery.Workplane` object.

Final result
============

Below is the complete code including the final solve step.

.. cadquery::
    :height: 600px

    import cadquery as cq

    # Parameters
    H = 400
    W = 200
    D = 350

    PROFILE = cq.importers.importDXF("vslot-2020_1.dxf").wires()

    SLOT_D = 6
    PANEL_T = 3

    HANDLE_D = 20
    HANDLE_L = 50
    HANDLE_W = 4


    def make_vslot(l):
        return PROFILE.toPending().extrude(l)


    def make_connector():
        rv = (
            cq.Workplane()
            .box(20, 20, 20)
            .faces("<X")
            .workplane()
            .cboreHole(6, 15, 18)
            .faces("<Z")
            .workplane(centerOption="CenterOfMass")
            .cboreHole(6, 15, 18)
        )

        # tag mating faces
        rv.faces(">X").tag("X").end()
        rv.faces(">Z").tag("Z").end()

        return rv


    def make_panel(w, h, t, cutout):
        rv = (
            cq.Workplane("XZ")
            .rect(w, h)
            .extrude(t)
            .faces(">Y")
            .vertices()
            .rect(2 * cutout, 2 * cutout)
            .cutThruAll()
            .faces("<Y")
            .workplane()
            .pushPoints([(-w / 3, HANDLE_L / 2), (-w / 3, -HANDLE_L / 2)])
            .hole(3)
        )

        # tag mating edges
        rv.faces(">Y").edges("%CIRCLE").edges(">Z").tag("hole1")
        rv.faces(">Y").edges("%CIRCLE").edges("<Z").tag("hole2")

        return rv


    def make_handle(w, h, r):
        pts = ((0, 0), (w, 0), (w, h), (0, h))

        path = cq.Workplane().polyline(pts)

        rv = (
            cq.Workplane("YZ")
            .rect(r, r)
            .sweep(path, transition="round")
            .tag("solid")
            .faces("<X")
            .workplane()
            .faces("<X", tag="solid")
            .hole(r / 1.5)
        )

        # tag mating faces
        rv.faces("<X").faces(">Y").tag("mate1")
        rv.faces("<X").faces("<Y").tag("mate2")

        return rv


    # define the elements
    door = (
        cq.Assembly()
        .add(make_vslot(H), name="left")
        .add(make_vslot(H), name="right")
        .add(make_vslot(W), name="top")
        .add(make_vslot(W), name="bottom")
        .add(make_connector(), name="con_tl", color=cq.Color("black"))
        .add(make_connector(), name="con_tr", color=cq.Color("black"))
        .add(make_connector(), name="con_bl", color=cq.Color("black"))
        .add(make_connector(), name="con_br", color=cq.Color("black"))
        .add(
            make_panel(W + 2 * SLOT_D, H + 2 * SLOT_D, PANEL_T, SLOT_D),
            name="panel",
            color=cq.Color(0, 0, 1, 0.2),
        )
        .add(
            make_handle(HANDLE_D, HANDLE_L, HANDLE_W),
            name="handle",
            color=cq.Color("yellow"),
        )
    )

    # define the constraints
    (
        door
        # left profile
        .constrain("left@faces@<Z", "con_bl?Z", "Plane")
        .constrain("left@faces@<X", "con_bl?X", "Axis")
        .constrain("left@faces@>Z", "con_tl?Z", "Plane")
        .constrain("left@faces@<X", "con_tl?X", "Axis")
        # top
        .constrain("top@faces@<Z", "con_tl?X", "Plane")
        .constrain("top@faces@<Y", "con_tl@faces@>Y", "Axis")
        # bottom
        .constrain("bottom@faces@<Y", "con_bl@faces@>Y", "Axis")
        .constrain("bottom@faces@>Z", "con_bl?X", "Plane")
        # right connectors
        .constrain("top@faces@>Z", "con_tr@faces@>X", "Plane")
        .constrain("bottom@faces@<Z", "con_br@faces@>X", "Plane")
        .constrain("left@faces@>Z", "con_tr?Z", "Axis")
        .constrain("left@faces@<Z", "con_br?Z", "Axis")
        # right profile
        .constrain("right@faces@>Z", "con_tr@faces@>Z", "Plane")
        .constrain("right@faces@<X", "left@faces@<X", "Axis")
        # panel
        .constrain("left@faces@>X[-4]", "panel@faces@<X", "Plane")
        .constrain("left@faces@>Z", "panel@faces@>Z", "Axis")
        # handle
        .constrain("panel?hole1", "handle?mate1", "Plane")
        .constrain("panel?hole2", "handle?mate2", "Point")
    )

    # solve
    door.solve()

    show_object(door, name="door")


Data export
===========

The resulting assembly can be exported as a STEP file or in a internal OCCT XML format.


STEP can be loaded in all CAD tool, e.g. in FreeCAD and the XML be used in other applications using OCCT.

.. code-block:: python
   :linenos:

   door.export("door.step")
   door.export("door.xml")

.. image:: _static/door_assy_freecad.png


Object locations
----------------

Objects can be added to an assembly with initial locations supplied, such as:

.. cadquery::

    import cadquery as cq

    cone = cq.Solid.makeCone(1, 0, 2)

    assy = cq.Assembly()
    assy.add(
        cone,
        loc=cq.Location((0, 0, 0), (1, 0, 0), 180),
        name="cone0",
        color=cq.Color("green"),
    )
    assy.add(cone, name="cone1", color=cq.Color("blue"))

    show_object(assy)


As an alternative to the user calculating locations, constraints and the method
:meth:`~cadquery.Assembly.solve` can be used to position objects in an assembly.

If initial locations and the method :meth:`~cadquery.Assembly.solve` are used the solver will
overwrite these initial locations with it's solution, however initial locations can still affect the
final solution. In an underconstrained system the solver may not move an object if it does not
contribute to the cost function, or if multiple solutions exist (ie. multiple instances
where the cost function is at a minimum) initial locations can cause the solver to converge on one
particular solution. For very complicated assemblies setting approximately correct initial locations
can also reduce the computational time required.


Constraints
-----------

Constraints are often a better representation of the real world relationship the user wants to
model than directly supplying locations. In the above example the real world relationship is that
the bottom face of each cone should touch, which can be modelled with a Plane constraint. When the
user provides explicit locations (instead of constraints) then they are also responsible for updating
them when, for example, the location of ``cone1`` changes.

When at least one constraint is supplied and the method :meth:`~cadquery.Assembly.solve` is run, an
optimization problem is set up. Each constraint provides a cost function that depends on the
position and orientation (represented by a :class:`~cadquery.Location`) of the two objects specified
when creating the constraint. The solver varies the location of the assembly's children and attempts
to minimize the sum of all cost functions. Hence by reading the formulae of the cost functions
below, you can understand exactly what each constraint does.


Point
=====

The Point constraint is a frequently used constraint that minimizes the distance between two points.
Some example uses are centering faces or aligning verticies, but it is also useful with dummy
vertices to create offsets between two parts.

The cost function is:

.. math::

  ( param - \lvert \vec{ c_1 } - \vec{ c_2 } \rvert ) ^2

Where:

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

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

    import cadquery as cq

    cone = cq.Solid.makeCone(1, 0, 2)

    assy = cq.Assembly()
    assy.add(cone, name="cone0", color=cq.Color("green"))
    assy.add(cone, name="cone1", color=cq.Color("blue"))
    assy.constrain("cone0@faces@<Z", "cone1@faces@<Z", "Axis")

    assy.solve()
    show_object(assy)


If the ``param`` argument is set to zero, then the two objects will point in the same direction.
This is often used when one object goes through another, such as a pin going into a hole in a plate:


.. cadquery::

    import cadquery as cq

    plate = cq.Workplane().box(10, 10, 1).faces(">Z").workplane().hole(2)
    cone = cq.Solid.makeCone(0.8, 0, 4)

    assy = cq.Assembly()
    assy.add(plate, name="plate", color=cq.Color("green"))
    assy.add(cone, name="cone", color=cq.Color("blue"))
    # place the center of the flat face of the cone in the center of the upper face of the plate
    assy.constrain("plate@faces@>Z", "cone@faces@<Z", "Point")

    # set both the flat face of the cone and the upper face of the plate to point in the same direction
    assy.constrain("plate@faces@>Z", "cone@faces@<Z", "Axis", param=0)

    assy.solve()
    show_object(assy)


In creating an Axis constraint, a direction vector is extracted in one of three different ways,
depending on the object's type.

:class:`~cadquery.Face`:
  Using :meth:`~cadquery.Face.normalAt`

:class:`~cadquery.Edge` and :meth:`~cadquery.Shape.geomType` is ``"CIRCLE"``:
  Using :meth:`~cadquery.Mixin1D.normal`

:class:`~cadquery.Edge` and :meth:`~cadquery.Shape.geomType` is not ``"CIRCLE"``:
  Using :meth:`~cadquery.Mixin1D.tangentAt`

Using any other type of object will raise a :exc:`ValueError`. By far the most common use case
is to define an Axis constraint from a :class:`~cadquery.Face`.


.. cadquery::

    import cadquery as cq
    from math import cos, sin, pi

    # Create a sinusoidal surface:
    surf = cq.Workplane().parametricSurface(
        lambda u, v: (u, v, 5 * sin(pi * u / 10) * cos(pi * v / 10)),
        N=40,
        start=0,
        stop=20,
    )

    # Create a cone with a small, flat tip:
    cone = (
        cq.Workplane()
        .add(cq.Solid.makeCone(1, 0.1, 2))
        # tag the tip for easy reference in the constraint:
        .faces(">Z")
        .tag("tip")
        .end()
    )

    assy = cq.Assembly()
    assy.add(surf, name="surf", color=cq.Color("lightgray"))
    assy.add(cone, name="cone", color=cq.Color("green"))
    # set the Face on the tip of the cone to point in
    # the opposite direction of the center of the surface:
    assy.constrain("surf", "cone?tip", "Axis")
    # to make the example clearer, move the cone to the center of the face:
    assy.constrain("surf", "cone?tip", "Point")
    assy.solve()

    show_object(assy)


Plane
=====

The Plane constraint is simply a combination of both the Point and Axis constraints. It is a
convenient shortcut for a commonly used combination of constraints. It can be used to shorten the
previous example from the two constraints to just one:

.. code-block:: diff

    assy = cq.Assembly()
    assy.add(surf, name="surf", color=cq.Color("lightgray"))
    assy.add(cone, name="cone", color=cq.Color("green"))
    -# set the Face on the tip of the cone to point in
    -# the opposite direction of the center of the surface:
    -assy.constrain("surf", "cone?tip", "Axis")
    -# to make the example clearer, move the cone to the center of the face:
    -assy.constrain("surf", "cone?tip", "Point")
    +assy.constrain("surf", "cone?tip", "Plane")
    assy.solve()

    show_object(assy)


The result of this code is identical to the above two constraint example.

For the cost function of Plane, please see the Point and Axis sections. The ``param`` argument is applied to Axis and should be left as the default value for a "mate" style
constraint (two surfaces touching) or can be set to ``0`` for a through surface constraint (see
description in the Axis constraint section).


PointInPlane
============

PointInPlane positions the center of the first object within the plane defined by the second object.
The cost function is:

.. math::

    \operatorname{dist}( \vec{ c }, p_\text{ offset } ) ^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::

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

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

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

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

    import cadquery as cq

    b1 = cq.Workplane().box(1, 1, 1)
    b2 = cq.Workplane().rect(0.1, 0.1).extrude(1, taper=-15)

    assy = (
        cq.Assembly()
        .add(b1, name="b1")
        .add(b2, loc=cq.Location((0, 0, 4)), name="b2", color=cq.Color("red"))
    )

    # fix the position of b1
    assy.constrain("b1", "Fixed")
    # fix b2 bottom face position (but not rotation)
    assy.constrain("b2@faces@<Z", "FixedPoint", (0, 0, 0.5))
    # fix b2 some rotational degrees of freedom too
    assy.constrain("b2@faces@>Z", "FixedAxis", (1, 0, 2))

    assy.solve()
    show_object(assy)


Assembly colors
---------------

Aside from RGBA values, the :class:`~cadquery.Color` class can be instantiated from a text name. Valid names are
listed along with a color sample below:

.. raw:: html

    
      aliceblue
      antiquewhite
      antiquewhite1
      antiquewhite2
      antiquewhite3
      antiquewhite4
      aquamarine1
      aquamarine2
      aquamarine4
      azure
      azure2
      azure3
      azure4
      beet
      beige
      bisque
      bisque2
      bisque3
      bisque4
      black
      blanchedalmond
      blue
      blue1
      blue2
      blue3
      blue4
      blueviolet
      brown
      brown1
      brown2
      brown3
      brown4
      burlywood
      burlywood1
      burlywood2
      burlywood3
      burlywood4
      cadetblue
      cadetblue1
      cadetblue2
      cadetblue3
      cadetblue4
      chartreuse
      chartreuse1
      chartreuse2
      chartreuse3
      chartreuse4
      chocolate
      chocolate1
      chocolate2
      chocolate3
      chocolate4
      coral
      coral1
      coral2
      coral3
      coral4
      cornflowerblue
      cornsilk1
      cornsilk2
      cornsilk3
      cornsilk4
      cyan
      cyan1
      cyan2
      cyan3
      cyan4
      darkgoldenrod
      darkgoldenrod1
      darkgoldenrod2
      darkgoldenrod3
      darkgoldenrod4
      darkgreen
      darkkhaki
      darkolivegreen
      darkolivegreen1
      darkolivegreen2
      darkolivegreen3
      darkolivegreen4
      darkorange
      darkorange1
      darkorange2
      darkorange3
      darkorange4
      darkorchid
      darkorchid1
      darkorchid2
      darkorchid3
      darkorchid4
      darksalmon
      darkseagreen
      darkseagreen1
      darkseagreen2
      darkseagreen3
      darkseagreen4
      darkslateblue
      darkslategray
      darkslategray1
      darkslategray2
      darkslategray3
      darkslategray4
      darkturquoise
      darkviolet
      deeppink
      deeppink2
      deeppink3
      deeppink4
      deepskyblue1
      deepskyblue2
      deepskyblue3
      deepskyblue4
      dodgerblue1
      dodgerblue2
      dodgerblue3
      dodgerblue4
      firebrick
      firebrick1
      firebrick2
      firebrick3
      firebrick4
      floralwhite
      forestgreen
      gainsboro
      ghostwhite
      gold
      gold1
      gold2
      gold3
      gold4
      goldenrod
      goldenrod1
      goldenrod2
      goldenrod3
      goldenrod4
      gray
      gray0
      gray1
      gray10
      gray11
      gray12
      gray13
      gray14
      gray15
      gray16
      gray17
      gray18
      gray19
      gray2
      gray20
      gray21
      gray22
      gray23
      gray24
      gray25
      gray26
      gray27
      gray28
      gray29
      gray3
      gray30
      gray31
      gray32
      gray33
      gray34
      gray35
      gray36
      gray37
      gray38
      gray39
      gray4
      gray40
      gray41
      gray42
      gray43
      gray44
      gray45
      gray46
      gray47
      gray48
      gray49
      gray5
      gray50
      gray51
      gray52
      gray53
      gray54
      gray55
      gray56
      gray57
      gray58
      gray59
      gray6
      gray60
      gray61
      gray62
      gray63
      gray64
      gray65
      gray66
      gray67
      gray68
      gray69
      gray7
      gray70
      gray71
      gray72
      gray73
      gray74
      gray75
      gray76
      gray77
      gray78
      gray79
      gray8
      gray80
      gray81
      gray82
      gray83
      gray85
      gray86
      gray87
      gray88
      gray89
      gray9
      gray90
      gray91
      gray92
      gray93
      gray94
      gray95
      gray97
      gray98
      gray99
      green
      green1
      green2
      green3
      green4
      greenyellow
      honeydew
      honeydew2
      honeydew3
      honeydew4
      hotpink
      hotpink1
      hotpink2
      hotpink3
      hotpink4
      indianred
      indianred1
      indianred2
      indianred3
      indianred4
      ivory
      ivory2
      ivory3
      ivory4
      khaki
      khaki1
      khaki2
      khaki3
      khaki4
      lavender
      lavenderblush1
      lavenderblush2
      lavenderblush3
      lavenderblush4
      lawngreen
      lemonchiffon1
      lemonchiffon2
      lemonchiffon3
      lemonchiffon4
      lightblue
      lightblue1
      lightblue2
      lightblue3
      lightblue4
      lightcoral
      lightcyan
      lightcyan1
      lightcyan2
      lightcyan3
      lightcyan4
      lightgoldenrod
      lightgoldenrod1
      lightgoldenrod2
      lightgoldenrod3
      lightgoldenrod4
      lightgoldenrodyellow
      lightgray
      lightpink
      lightpink1
      lightpink2
      lightpink3
      lightpink4
      lightsalmon1
      lightsalmon2
      lightsalmon3
      lightsalmon4
      lightseagreen
      lightskyblue
      lightskyblue1
      lightskyblue2
      lightskyblue3
      lightskyblue4
      lightslateblue
      lightslategray
      lightsteelblue
      lightsteelblue1
      lightsteelblue2
      lightsteelblue3
      lightsteelblue4
      lightyellow
      lightyellow2
      lightyellow3
      lightyellow4
      limegreen
      linen
      magenta
      magenta1
      magenta2
      magenta3
      magenta4
      maroon
      maroon1
      maroon2
      maroon3
      maroon4
      matrablue
      matragray
      mediumaquamarine
      mediumorchid
      mediumorchid1
      mediumorchid2
      mediumorchid3
      mediumorchid4
      mediumpurple
      mediumpurple1
      mediumpurple2
      mediumpurple3
      mediumpurple4
      mediumseagreen
      mediumslateblue
      mediumspringgreen
      mediumturquoise
      mediumvioletred
      midnightblue
      mintcream
      mistyrose
      mistyrose2
      mistyrose3
      mistyrose4
      moccasin
      navajowhite1
      navajowhite2
      navajowhite3
      navajowhite4
      navyblue
      oldlace
      olivedrab
      olivedrab1
      olivedrab2
      olivedrab3
      olivedrab4
      orange
      orange1
      orange2
      orange3
      orange4
      orangered
      orangered1
      orangered2
      orangered3
      orangered4
      orchid
      orchid1
      orchid2
      orchid3
      orchid4
      palegoldenrod
      palegreen
      palegreen1
      palegreen2
      palegreen3
      palegreen4
      paleturquoise
      paleturquoise1
      paleturquoise2
      paleturquoise3
      paleturquoise4
      palevioletred
      palevioletred1
      palevioletred2
      palevioletred3
      palevioletred4
      papayawhip
      peachpuff
      peachpuff2
      peachpuff3
      peachpuff4
      peru
      pink
      pink1
      pink2
      pink3
      pink4
      plum
      plum1
      plum2
      plum3
      plum4
      powderblue
      purple
      purple1
      purple2
      purple3
      purple4
      red
      red1
      red2
      red3
      red4
      rosybrown
      rosybrown1
      rosybrown2
      rosybrown3
      rosybrown4
      royalblue
      royalblue1
      royalblue2
      royalblue3
      royalblue4
      saddlebrown
      salmon
      salmon1
      salmon2
      salmon3
      salmon4
      sandybrown
      seagreen
      seagreen1
      seagreen2
      seagreen3
      seagreen4
      seashell
      seashell2
      seashell3
      seashell4
      sienna
      sienna1
      sienna2
      sienna3
      sienna4
      skyblue
      skyblue1
      skyblue2
      skyblue3
      skyblue4
      slateblue
      slateblue1
      slateblue2
      slateblue3
      slateblue4
      slategray
      slategray1
      slategray2
      slategray3
      slategray4
      snow
      snow2
      snow3
      snow4
      springgreen
      springgreen2
      springgreen3
      springgreen4
      steelblue
      steelblue1
      steelblue2
      steelblue3
      steelblue4
      tan
      tan1
      tan2
      tan3
      tan4
      teal
      thistle
      thistle1
      thistle2
      thistle3
      thistle4
      tomato
      tomato1
      tomato2
      tomato3
      tomato4
      turquoise
      turquoise1
      turquoise2
      turquoise3
      turquoise4
      violet
      violetred
      violetred1
      violetred2
      violetred3
      violetred4
      wheat
      wheat1
      wheat2
      wheat3
      wheat4
      white
      whitesmoke
      yellow
      yellow1
      yellow2
      yellow3
      yellow4
      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::

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

.. currentmodule:: cadquery

Core Classes
---------------------

.. autosummary::

    Sketch
    Workplane
    Assembly
    Constraint

Topological Classes
----------------------

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

Geometry Classes
------------------

.. autosummary::

    Vector
    Matrix
    Plane
    Location

Selector Classes
---------------------

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

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

    base_cube = cq.Workplane("XY").rect(1.0, 1.0).extrude(1.0)
    top_of_cube_plane = base_cube.faces(">Z").workplane()
    debug(
        top_of_cube_plane,
        {
            "color": "yellow",
        },
    )
    debug(top_of_cube_plane.center, {"color": "blue"})

    circle = top_of_cube_plane.circle(0.5)
    debug(circle, {"color": "red"})

    show_object(circle.extrude(1.0))

Note that importing cadquery is not required. 
At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle

Future enhancements will include several other methods, used to provide more metadata for the execution environment:
  * :py:meth:`cadquery.cqgi.ScriptCallback.add_error()`, indicates an error with an input parameter
  * :py:meth:`cadquery.cqgi.ScriptCallback.describe_parameter()`, provides extra information about a parameter in the script,


The execution environment side
-------------------------------

CQGI makes it easy to run cadquery scripts in a standard way. To run a script from an execution environment,
run code like this::

    from cadquery import cqgi

    user_script = ...
    build_result = cqgi.parse(user_script).build()

The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object.

The `metadata`p property of the object contains a `cadquery.cqgi.ScriptMetaData` object, which can be used to discover the
user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the 
model parameters.  Typically, after collecting new values, the environment will supply them in the build() method.

This code will return a dictionary of parameter values in the model text SCRIPT::
     parameters = cqgi.parse(SCRIPT).metadata.parameters

The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object, 
which has a name, type, and default value. 

The type is an object which extends ParameterType-- you can use this to determine what kind of widget to render ( checkbox for boolean, for example ).

The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the
describe_parameter() method.



Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object,
,which includes the script execution time, and a success flag.

If the script was successful, the results property will include a list of results returned by the script,
as well as any debug the script produced

If the script failed, the exception property contains the exception object.

If you have a way to get inputs from a user, you can override any of the constants defined in the user script
with new values::

    from cadquery import cqgi

    user_script = ...
    build_result = cqgi.parse(user_script).build(
        build_parameters={"param": 2}, build_options={}
    )

If a parameter called 'param' is defined in the model, it will be assigned the value 2 before the script runs.
An error will occur if a value is provided that is not defined in the model, or if the value provided cannot
be assigned to a variable with the given name.

build_options is used to set server-side settings like timeouts, tessellation tolerances, and other details about
how the model should be built.


More about script variables
-----------------------------

CQGI uses the following rules to find input variables for a script:

  * only top-level statements are considered
  * only assignments of constant values to a local name are considered.

For example, in the following script::

      h = 1.0
      w = 2.0
      foo = "bar"


      def some_function():
          x = 1

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

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

       model = cqgi.parse(user_script)

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

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

Future enhancements will include a safer sandbox to prevent malicious scripts.

Automating export to STL
-------------------------
A common use-case for the CQGI is the automation of processing cadquery code into geometry, doing so via the CQGI rather than an export line in the script itself leads to a much tidier environment; you may need to do this as part of an automated-workflow, batch-conversion, exporting to another software for assembly, or running stress simulations on resulting bodies.

The below Python script demonstrates how to open, process, and export an STL file from any valid cadquery script::

      # Load CQGI
      import cadquery.cqgi as cqgi
      import cadquery as cq

      # load the cadquery script
      model = cqgi.parse(open("example.py").read())

      # run the script and store the result (from the show_object call in the script)
      build_result = model.build()

      # test to ensure the process worked.
      if build_result.success:
          # loop through all the shapes returned and export to STL
          for i, result in enumerate(build_result.results):
              cq.exporters.export(result.shape, f"example_output{i}.stl")
      else:
          print(f"BUILD FAILED: {build_result.exception}")



Important CQGI Methods
-------------------------

These are the most important Methods and classes of the CQGI

.. currentmodule:: cadquery.cqgi

.. autosummary::
    parse
    CQModel.build
    BuildResult
    ScriptCallback.show_object

Complete CQGI API
-----------------

.. automodule:: cadquery.cqgi
   :members:

---

## File: doc/designprinciples.rst

.. _designprinciples:


===========================
Design Principles
===========================


Principle 1: Intuitive Construction
====================================

CadQuery aims to make building models using python scripting easy and intuitive.
CadQuery strives to allow scripts to read roughly as a human would describe an object verbally.

For example, consider this object:

.. image:: _static/quickstart.png

A human would describe this as:

     "A block 80mm square x 30mm thick , with countersunk holes for M2 socket head cap screws
     at the corners, and a circular pocket 22mm in diameter in the middle for a bearing"

The goal is to have the CadQuery script that produces this object be as close as possible to the English phrase
a human would use.


Principle 2: Capture Design Intent
====================================

The features that are **not** part of the part description above are just as important as those that are.  For example, most
humans will assume that:

    * The countersunk holes are spaced a uniform distance from the edges
    * The circular pocket is in the center of the block, no matter how big the block 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:

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

    We strongly recommend installing `CQ-editor <https://github.com/CadQuery/CQ-editor>`_,
    so that you can work along with these examples interactively. See :ref:`installation` for more info.

    If you do, make sure to take these steps so that they work:

       1. import cadquery as cq
       2. add the line ``show_object(result)``  at the end. The samples below are autogenerated, but they use a different
          syntax than the models on the website need to be.

.. contents:: List of Examples
    :backlinks: entry


Simple Rectangular Plate
------------------------

Just about the simplest possible example, a rectangular box

.. cadquery::

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


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane` **!**
        * :py:meth:`Workplane.box` **!**

Plate with Hole
------------------------

A rectangular box, but with a hole added.

"\>Z" selects the top most face of the resulting box. The hole is located in the center because the default origin
of a working plane is the projected origin of the last Workplane, the last Workplane having origin at (0,0,0) the
projection is at the center of the face. The default hole depth is through the entire part.


.. cadquery::

        # The dimensions of the box. These can be modified rather than changing the
        # object's code directly.
        length = 80.0
        height = 60.0
        thickness = 10.0
        center_hole_dia = 22.0

        # Create a box based on the dimensions above and add a 22mm center hole
        result = (
            cq.Workplane("XY")
            .box(length, height, thickness)
            .faces(">Z")
            .workplane()
            .hole(center_hole_dia)
        )

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.hole` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.box`

An extruded prismatic solid
-------------------------------

Build a prismatic solid using extrusion. After a drawing operation, the center of the previous object
is placed on the stack, and is the reference for the next operation. So in this case, the rect() is drawn
centered on the previously draw circle.

By default, rectangles and circles are centered around the previous working point.

.. cadquery::

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

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.circle` **!**
        * :py:meth:`Workplane.rect` **!**
        * :py:meth:`Workplane.extrude` **!**
        * :py:meth:`Workplane`

Building Profiles using lines and arcs
--------------------------------------

Sometimes you need to build complex profiles using lines and arcs. This example builds a prismatic
solid from 2D operations.

2D operations maintain a current point, which is initially at the origin. Use close() to finish a
closed curve.


.. cadquery::

    result = (
        cq.Workplane("front")
        .lineTo(2.0, 0)
        .lineTo(2.0, 1.0)
        .threePointArc((1.0, 1.5), (0.0, 1.0))
        .close()
        .extrude(0.25)
    )


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.threePointArc` **!**
        * :py:meth:`Workplane.lineTo` **!**
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane`

Moving The Current working point
---------------------------------

In this example, a closed profile is required, with some interior features as well.

This example also demonstrates using multiple lines of code instead of longer chained commands,
though of course in this case it was possible to do it in one long line as well.

A new work plane center can be established at any point.

.. cadquery::

    result = cq.Workplane("front").circle(
        3.0
    )  # current point is the center of the circle, at (0, 0)
    result = result.center(1.5, 0.0).rect(0.5, 0.5)  # new work center is (1.5, 0.0)

    result = result.center(-1.5, 1.5).circle(0.25)  # new work center is (0.0, 1.5).
    # The new center is specified relative to the previous center, not global coordinates!

    result = result.extrude(0.25)


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.center` **!**
        * :py:meth:`Workplane`
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.rect`
        * :py:meth:`Workplane.extrude`

Using Point Lists
---------------------------

Sometimes you need to create a number of features at various locations, and using :py:meth:`Workplane.center`
is too cumbersome.

You can use a list of points to construct multiple objects at once. Most construction methods,
like :py:meth:`Workplane.circle` and :py:meth:`Workplane.rect`, will operate on multiple points if they are on the stack

.. cadquery::

   r = cq.Workplane("front").circle(2.0)  # make base
   r = r.pushPoints(
       [(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)]
   )  # now four points are on the stack
   r = r.circle(0.25)  # circle will operate on all four points
   result = r.extrude(0.125)  # make prism

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.pushPoints` **!**
        * :py:meth:`Workplane`
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.extrude`

Polygons
-------------------------

You can create polygons for each stack point if you would like. Useful in 3d printers whose firmware does not
correct for small hole sizes.

.. cadquery::

    result = (
        cq.Workplane("front")
        .box(3.0, 4.0, 0.25)
        .pushPoints([(0, 0.75), (0, -0.75)])
        .polygon(6, 1.0)
        .cutThruAll()
    )

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.polygon` **!**
        * :py:meth:`Workplane.pushPoints`
        * :py:meth:`Workplane.box`

Polylines
-------------------------

:py:meth:`Workplane.polyline` allows creating a shape from a large number of chained points connected by lines.

This example uses a polyline to create one half of an i-beam shape, which is mirrored to create the final profile.

.. cadquery::

    (L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
    pts = [
        (0, H / 2.0),
        (W / 2.0, H / 2.0),
        (W / 2.0, (H / 2.0 - t)),
        (t / 2.0, (H / 2.0 - t)),
        (t / 2.0, (t - H / 2.0)),
        (W / 2.0, (t - H / 2.0)),
        (W / 2.0, H / -2.0),
        (0, H / -2.0),
    ]
    result = cq.Workplane("front").polyline(pts).mirrorY().extrude(L)

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.polyline` **!**
        * :py:meth:`Workplane`
        * :py:meth:`Workplane.mirrorY`
        * :py:meth:`Workplane.extrude`



Defining an Edge with a Spline
------------------------------

This example defines a side using a spline curve through a collection of points. Useful when you have an edge that
needs a complex profile

.. cadquery::

    s = cq.Workplane("XY")
    sPnts = [
        (2.75, 1.5),
        (2.5, 1.75),
        (2.0, 1.5),
        (1.5, 1.0),
        (1.0, 1.25),
        (0.5, 1.0),
        (0, 1.0),
    ]
    r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts, includeCurrent=True).close()
    result = r.extrude(0.5)

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.spline` **!**
        * :py:meth:`Workplane`
        * :py:meth:`Workplane.close`
        * :py:meth:`Workplane.lineTo`
        * :py:meth:`Workplane.extrude`

Mirroring Symmetric Geometry
-----------------------------

You can mirror 2D geometry when your shape is symmetric. In this example we also
introduce horizontal and vertical lines, which make for slightly easier coding.


.. cadquery::

   r = cq.Workplane("front").hLine(1.0)  # 1.0 is the distance, not coordinate
   r = (
       r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0)
   )  # hLineTo allows using xCoordinate not distance
   result = r.mirrorY().extrude(0.25)  # mirror the geometry and extrude

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.hLine` **!**
        * :py:meth:`Workplane.vLine` **!**
        * :py:meth:`Workplane.hLineTo` **!**
        * :py:meth:`Workplane.mirrorY` **!**
        * :py:meth:`Workplane.mirrorX` **!**
        * :py:meth:`Workplane`
        * :py:meth:`Workplane.extrude`

Mirroring 3D Objects
-----------------------------

.. cadquery::

    result0 = (
        cadquery.Workplane("XY")
        .moveTo(10, 0)
        .lineTo(5, 0)
        .threePointArc((3.9393, 0.4393), (3.5, 1.5))
        .threePointArc((3.0607, 2.5607), (2, 3))
        .lineTo(1.5, 3)
        .threePointArc((0.4393, 3.4393), (0, 4.5))
        .lineTo(0, 13.5)
        .threePointArc((0.4393, 14.5607), (1.5, 15))
        .lineTo(28, 15)
        .lineTo(28, 13.5)
        .lineTo(24, 13.5)
        .lineTo(24, 11.5)
        .lineTo(27, 11.5)
        .lineTo(27, 10)
        .lineTo(22, 10)
        .lineTo(22, 13.2)
        .lineTo(14.5, 13.2)
        .lineTo(14.5, 10)
        .lineTo(12.5, 10)
        .lineTo(12.5, 13.2)
        .lineTo(5.5, 13.2)
        .lineTo(5.5, 2)
        .threePointArc((5.793, 1.293), (6.5, 1))
        .lineTo(10, 1)
        .close()
    )
    result = result0.extrude(100)

    result = result.rotate((0, 0, 0), (1, 0, 0), 90)

    result = result.translate(result.val().BoundingBox().center.multiply(-1))

    mirXY_neg = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, -30))
    mirXY_pos = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, 30))
    mirZY_neg = result.mirror(mirrorPlane="ZY", basePointVector=(-30, 0, 0))
    mirZY_pos = result.mirror(mirrorPlane="ZY", basePointVector=(30, 0, 0))

    result = result.union(mirXY_neg).union(mirXY_pos).union(mirZY_neg).union(mirZY_pos)


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.moveTo`
        * :py:meth:`Workplane.lineTo`
        * :py:meth:`Workplane.threePointArc`
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane.mirror`
        * :py:meth:`Workplane.union`
        * :py:meth:`Workplane.rotate`


Mirroring From Faces
-----------------------------

This example shows how you can mirror about a selected face. It also shows how the resulting mirrored object can be unioned immediately with the referenced mirror geometry.

.. cadquery::

    result = cq.Workplane("XY").line(0, 1).line(1, 0).line(0, -0.5).close().extrude(1)

    result = result.mirror(result.faces(">X"), union=True)


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.line`
        * :py:meth:`Workplane.close`
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane.mirror`
        * :py:meth:`Workplane.union`

Creating Workplanes on Faces
-----------------------------

This example shows how to locate a new workplane on the face of a previously created feature.

.. note::
    Using workplanes in this way are a key feature of CadQuery. Unlike a typical 3d scripting
    language, using work planes frees you from tracking the position of various features in
    variables, and allows the model to adjust itself with removing redundant dimensions

The :py:meth:`Workplane.faces()` method allows you to select the faces of a resulting solid. It
accepts a selector string or object, that allows you to target a single face, and make a workplane
oriented on that face.

Keep in mind that by default the origin of a new workplane is calculated by forming a plane from the
selected face and projecting the previous origin onto that plane. This behaviour can be changed
through the centerOption argument of :py:meth:`Workplane.workplane`.

.. cadquery::

    result = cq.Workplane("front").box(2, 3, 0.5)  # make a basic prism
    result = (
        result.faces(">Z").workplane().hole(0.5)
    )  # find the top-most face and make a hole

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.faces` **!**
        * :py:meth:`StringSyntaxSelector` **!**
        * :ref:`selector_reference` **!**
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane`

Locating a Workplane on a vertex
---------------------------------

Normally, the :py:meth:`Workplane.workplane` method requires a face to be selected. But if a vertex
is selected **immediately after a face**, :py:meth:`Workplane.workplane` with the centerOption
argument set to CenterOfMass will locate the workplane on the face, with the origin at the vertex
instead of at the center of the face

The example also introduces :py:meth:`Workplane.cutThruAll`, which makes a cut through the entire
part, no matter how deep the part is.

.. cadquery::

    result = cq.Workplane("front").box(3, 2, 0.5)  # make a basic prism
    result = (
        result.faces(">Z").vertices("<XY").workplane(centerOption="CenterOfMass")
    )  # select the lower left vertex and make a workplane
    result = result.circle(1.0).cutThruAll()  # cut the corner out

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.cutThruAll` **!**

        * :ref:`selector_reference` **!**
        * :py:meth:`Workplane.vertices` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane`
        * :py:meth:`StringSyntaxSelector` **!**

Offset Workplanes
--------------------------

Workplanes do not have to lie exactly on a face. When you make a workplane, you can define it at an offset
from an existing face.

This example uses an offset workplane to make a compound object, which is perfectly valid!

.. cadquery::

    result = cq.Workplane("front").box(3, 2, 0.5)  # make a basic prism
    result = result.faces("<X").workplane(
        offset=0.75
    )  # workplane is offset from the object surface
    result = result.circle(1.0).extrude(0.5)  # disc

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.extrude`
        * :ref:`selector_reference` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane`

Copying Workplanes
--------------------------

An existing CQ object can copy a workplane from another CQ object.

.. cadquery::

    result = (
        cq.Workplane("front")
        .circle(1)
        .extrude(10)  # make a cylinder
        # We want to make a second cylinder perpendicular to the first,
        # but we have no face to base the workplane off
        .copyWorkplane(
            # create a temporary object with the required workplane
            cq.Workplane("right", origin=(-5, 0, 0))
        )
        .circle(1)
        .extrude(10)
    )

.. topic:: API References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.copyWorkplane` **!**
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane`

Rotated Workplanes
--------------------------

You can create a rotated work plane by specifying angles of rotation relative to another workplane

.. cadquery::

    result = (
        cq.Workplane("front")
        .box(4.0, 4.0, 0.25)
        .faces(">Z")
        .workplane()
        .transformed(offset=cq.Vector(0, -1.5, 1.0), rotate=cq.Vector(60, 0, 0))
        .rect(1.5, 1.5, forConstruction=True)
        .vertices()
        .hole(0.25)
    )

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.transformed` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.rect`
        * :py:meth:`Workplane.faces`

Using construction Geometry
---------------------------

You can draw shapes to use the vertices as points to locate other features. Features that are used to
locate other features, rather than to create them, are called ``Construction Geometry``

In the example below, a rectangle is drawn, and its vertices are used to locate a set of holes.

.. cadquery::

    result = (
        cq.Workplane("front")
        .box(2, 2, 0.5)
        .faces(">Z")
        .workplane()
        .rect(1.5, 1.5, forConstruction=True)
        .vertices()
        .hole(0.125)
    )

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.rect` (forConstruction=True)
        * :ref:`selector_reference`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.hole`
        * :py:meth:`Workplane`

Shelling To Create Thin features
--------------------------------

Shelling converts a solid object into a shell of uniform thickness.

To shell an object and 'hollow out' the inside pass a negative thickness parameter
to the :py:meth:`Workplane.shell()` method of a shape.

.. cadquery::

    result = cq.Workplane("front").box(2, 2, 2).shell(-0.1)

A positive thickness parameter wraps an object with filleted outside edges
and the original object will be the 'hollowed out' portion.

.. cadquery::

    result = cq.Workplane("front").box(2, 2, 2).shell(0.1)

Use face selectors to select a face to be removed from the resulting hollow shape.

.. cadquery::

    result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.1)

Multiple faces can be removed using more complex selectors.

.. cadquery::

   result = cq.Workplane("front").box(2, 2, 2).faces("+Z or -X or +X").shell(0.1)

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.shell` **!**
        * :ref:`selector_reference`
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane`

Making Lofts
--------------------------------------------

A loft is a solid swept through a set of wires. This example creates lofted section between a rectangle
and a circular section.

.. cadquery::

    result = (
        cq.Workplane("front")
        .box(4.0, 4.0, 0.25)
        .faces(">Z")
        .circle(1.5)
        .workplane(offset=3.0)
        .rect(0.75, 0.5)
        .loft(combine=True)
    )


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.loft` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.rect`

Extruding until a given face
--------------------------------------------

Sometimes you will want to extrude a wire until a given face that can be not planar or where you
might not know easily the distance you have to extrude to. In such cases you can use `next`, `last`
or even give a :class:`~cadquery.Face` object for the `until` argument of
:meth:`~cadquery.Workplane.extrude`.


.. cadquery::

    result = (
        cq.Workplane(origin=(20, 0, 0))
        .circle(2)
        .revolve(180, (-20, 0, 0), (-20, -1, 0))
        .center(-20, 0)
        .workplane()
        .rect(20, 4)
        .extrude("next")
    )

The same behaviour is available with :meth:`~cadquery.Workplane.cutBlind` and as you can see it is
also possible to work on several :class:`~cadquery.Wire` objects at a time (the
same is true for :meth:`~cadquery.Workplane.extrude`).

.. cadquery::

    skyscrapers_locations = [(-16, 1), (-8, 0), (7, 0.2), (17, -1.2)]
    angles = iter([15, 0, -8, 10])
    skyscrapers = (
        cq.Workplane()
        .pushPoints(skyscrapers_locations)
        .eachpoint(
            lambda loc: (
                cq.Workplane()
                .rect(5, 16)
                .workplane(offset=10)
                .ellipse(3, 8)
                .workplane(offset=10)
                .slot2D(20, 5, 90)
                .loft()
                .rotateAboutCenter((0, 0, 1), next(angles))
                .val()
                .located(loc)
            )
        )
    )

    result = (
        skyscrapers.transformed((0, -90, 0))
        .moveTo(15, 0)
        .rect(3, 3, forConstruction=True)
        .vertices()
        .circle(1)
        .cutBlind("last")
    )

Here is a typical situation where extruding and cuting until a given surface is very handy. It allows us to extrude or cut until a curved surface without overlapping issues.

.. cadquery::

    import cadquery as cq

    sphere = cq.Workplane().sphere(5)
    base = cq.Workplane(origin=(0, 0, -2)).box(12, 12, 10).cut(sphere).edges("|Z").fillet(2)
    sphere_face = base.faces(">>X[2] and (not |Z) and (not |Y)").val()
    base = base.faces("<Z").workplane().circle(2).extrude(10)

    shaft = cq.Workplane().sphere(4.5).circle(1.5).extrude(20)

    spherical_joint = (
        base.union(shaft)
        .faces(">X")
        .workplane(centerOption="CenterOfMass")
        .move(0, 4)
        .slot2D(10, 2, 90)
        .cutBlind(sphere_face)
        .workplane(offset=10)
        .move(0, 2)
        .circle(0.9)
        .extrude("next")
    )

    result = spherical_joint

.. warning::

    If the wire you want to extrude cannot be fully projected on the target surface, the result will
    be unpredictable. Furthermore, the algorithm in charge of finding the candidate faces does its search by counting all the faces intersected
    by a line created from your wire center along your
    extrusion direction. So make sure your wire can be projected on your target face to avoid
    unexpected behaviour.

.. topic:: Api References

    .. hlist::
        :columns: 3

        * :py:meth:`Workplane.cutBlind` **!**
        * :py:meth:`Workplane.rect`
        * :py:meth:`Workplane.ellipse`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.slot2D`
        * :py:meth:`Workplane.loft`
        * :py:meth:`Workplane.rotateAboutCenter`
        * :py:meth:`Workplane.transformed`
        * :py:meth:`Workplane.moveTo`
        * :py:meth:`Workplane.circle`


Making Counter-bored and Counter-sunk Holes
----------------------------------------------

Counterbored and countersunk holes are so common that CadQuery creates macros to create them in a single step.

Similar to :py:meth:`Workplane.hole`, these functions operate on a list of points as well as a single point.

.. cadquery::

    result = (
        cq.Workplane(cq.Plane.XY())
        .box(4, 2, 0.5)
        .faces(">Z")
        .workplane()
        .rect(3.5, 1.5, forConstruction=True)
        .vertices()
        .cboreHole(0.125, 0.25, 0.125, depth=None)
    )


.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.cboreHole` **!**
        * :py:meth:`Workplane.cskHole` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.rect`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.vertices`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane`

Offsetting wires in 2D
----------------------

Two dimensional wires can be transformed with :py:meth:`Workplane.offset2D`. They can be offset
inwards or outwards, and with different techniques for extending the corners.

.. cadquery::

    original = cq.Workplane().polygon(5, 10).extrude(0.1).translate((0, 0, 2))
    arc = cq.Workplane().polygon(5, 10).offset2D(1, "arc").extrude(0.1).translate((0, 0, 1))
    intersection = cq.Workplane().polygon(5, 10).offset2D(1, "intersection").extrude(0.1)
    result = original.add(arc).add(intersection)


Using the forConstruction argument you can do the common task of offsetting a series of bolt holes
from the outline of an object. Here is the counterbore example from above but with the bolt holes
offset from the edges.

.. cadquery::

    result = (
        cq.Workplane()
        .box(4, 2, 0.5)
        .faces(">Z")
        .edges()
        .toPending()
        .offset2D(-0.25, forConstruction=True)
        .vertices()
        .cboreHole(0.125, 0.25, 0.125, depth=None)
    )


Note that :py:meth:`Workplane.edges` is for selecting objects. It does not add the selected edges to
pending edges in the modelling context, because this would result in your next extrusion including
everything you had only selected in addition to the lines you had drawn. To specify you want these
edges to be used in :py:meth:`Workplane.offset2D`, you call :py:meth:`Workplane.toPending` to
explicitly put them in the list of pending edges.

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.offset2D` **!**
        * :py:meth:`Workplane.cboreHole`
        * :py:meth:`Workplane.cskHole`
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.polygon`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.vertices`
        * :py:meth:`Workplane.edges`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane`


Rounding Corners with Fillet
-----------------------------

Filleting is done by selecting the edges of a solid, and using the fillet function.

Here we fillet all of the edges of a simple plate.

.. cadquery::

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

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.fillet` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.edges`
        * :py:meth:`Workplane`

Tagging objects
----------------

The :py:meth:`Workplane.tag` method can be used to tag a particular object in the chain with a string, so that it can be referred to later in the chain.

The :py:meth:`Workplane.workplaneFromTagged` method applies :py:meth:`Workplane.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors.

.. cadquery::

    result = (
        cq.Workplane("XY")
        # create and tag the base workplane
        .box(10, 10, 10)
        .faces(">Z")
        .workplane()
        .tag("baseplane")
        # extrude a cylinder
        .center(-3, 0)
        .circle(1)
        .extrude(3)
        # to reselect the base workplane, simply
        .workplaneFromTagged("baseplane")
        # extrude a second cylinder
        .center(3, 0)
        .circle(1)
        .extrude(2)
    )


Tags can also be used with most selectors, including :py:meth:`Workplane.vertices`, :py:meth:`Workplane.faces`, :py:meth:`Workplane.edges`, :py:meth:`Workplane.wires`, :py:meth:`Workplane.shells`, :py:meth:`Workplane.solids` and :py:meth:`Workplane.compounds`.

.. cadquery::

    result = (
        cq.Workplane("XY")
        # create a triangular prism and tag it
        .polygon(3, 5)
        .extrude(4)
        .tag("prism")
        # create a sphere that obscures the prism
        .sphere(10)
        # create features based on the prism's faces
        .faces("<X", tag="prism")
        .workplane()
        .circle(1)
        .cutThruAll()
        .faces(">X", tag="prism")
        .faces(">Y")
        .workplane()
        .circle(1)
        .cutThruAll()
    )

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.tag` **!**
        * :py:meth:`Workplane.getTagged` **!**
        * :py:meth:`Workplane.workplaneFromTagged` **!**
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane.cutThruAll`
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane`

A Parametric Bearing Pillow Block
------------------------------------

Combining a few basic functions, its possible to make a very good parametric bearing pillow block,
with just a few lines of code.

.. cadquery::

        (length, height, bearing_diam, thickness, padding) = (30.0, 40.0, 22.0, 10.0, 8.0)

        result = (
            cq.Workplane("XY")
            .box(length, height, thickness)
            .faces(">Z")
            .workplane()
            .hole(bearing_diam)
            .faces(">Z")
            .workplane()
            .rect(length - padding, height - padding, forConstruction=True)
            .vertices()
            .cboreHole(2.4, 4.4, 2.1)
        )


Splitting an Object
---------------------

You can split an object using a workplane, and retain either or both halves

.. cadquery::

        c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()

        # now cut it in half sideways
        result = c.faces(">Y").workplane(-0.5).split(keepTop=True)

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.split` **!**
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.cutThruAll`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane`

The Classic OCC Bottle
----------------------

CadQuery is based on the OpenCascade.org (OCC) modeling Kernel. Those who are familiar with OCC know about the
famous 'bottle' example. `The bottle example in the OCCT online documentation <https://old.opencascade.com/doc/occt-7.5.0/overview/html/occt__tutorial.html>`_.

A pythonOCC version is listed `here <https://github.com/tpaviot/pythonocc-demos/blob/f3ea9b4f65a9dff482be04b153d4ce5ec2430e13/examples/core_classic_occ_bottle.py>`_.

Of course one difference between this sample and the OCC version is the length. This sample is one of the longer
ones at 13 lines, but that's very short compared to the pythonOCC version, which is 10x longer!


.. cadquery::

    (L, w, t) = (20.0, 6.0, 3.0)
    s = cq.Workplane("XY")

    # Draw half the profile of the bottle and extrude it
    p = (
        s.center(-L / 2.0, 0)
        .vLine(w / 2.0)
        .threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0))
        .vLine(-w / 2.0)
        .mirrorX()
        .extrude(30.0, True)
    )

    # Make the neck
    p = p.faces(">Z").workplane(centerOption="CenterOfMass").circle(3.0).extrude(2.0, True)

    # Make a shell
    result = p.faces(">Z").shell(0.3)

.. topic:: Api References

    .. hlist::
        :columns: 2

        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane.mirrorX`
        * :py:meth:`Workplane.threePointArc`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.vertices`
        * :py:meth:`Workplane.vLine`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane`

A Parametric Enclosure
-----------------------

.. cadquery::
    :height: 400px

    # parameter definitions
    p_outerWidth = 100.0  # Outer width of box enclosure
    p_outerLength = 150.0  # Outer length of box enclosure
    p_outerHeight = 50.0  # Outer height of box enclosure

    p_thickness = 3.0  # Thickness of the box walls
    p_sideRadius = 10.0  # Radius for the curves around the sides of the box
    p_topAndBottomRadius = (
        2.0  # Radius for the curves on the top and bottom edges of the box
    )

    p_screwpostInset = 12.0  # How far in from the edges the screw posts should be place.
    p_screwpostID = 4.0  # Inner Diameter of the screw post holes, should be roughly screw diameter not including threads
    p_screwpostOD = 10.0  # Outer Diameter of the screw posts.\nDetermines overall thickness of the posts

    p_boreDiameter = 8.0  # Diameter of the counterbore hole, if any
    p_boreDepth = 1.0  # Depth of the counterbore hole, if
    p_countersinkDiameter = 0.0  # Outer diameter of countersink. Should roughly match the outer diameter of the screw head
    p_countersinkAngle = 90.0  # Countersink angle (complete angle between opposite sides, not from center to one side)
    p_flipLid = True  # Whether to place the lid with the top facing down or not.
    p_lipHeight = 1.0  # Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.

    # outer shell
    oshell = (
        cq.Workplane("XY")
        .rect(p_outerWidth, p_outerLength)
        .extrude(p_outerHeight + p_lipHeight)
    )

    # weird geometry happens if we make the fillets in the wrong order
    if p_sideRadius > p_topAndBottomRadius:
        oshell = oshell.edges("|Z").fillet(p_sideRadius)
        oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
    else:
        oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
        oshell = oshell.edges("|Z").fillet(p_sideRadius)

    # inner shell
    ishell = (
        oshell.faces("<Z")
        .workplane(p_thickness, True)
        .rect((p_outerWidth - 2.0 * p_thickness), (p_outerLength - 2.0 * p_thickness))
        .extrude(
            (p_outerHeight - 2.0 * p_thickness), False
        )  # set combine false to produce just the new boss
    )
    ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)

    # make the box outer box
    box = oshell.cut(ishell)

    # make the screw posts
    POSTWIDTH = p_outerWidth - 2.0 * p_screwpostInset
    POSTLENGTH = p_outerLength - 2.0 * p_screwpostInset

    box = (
        box.faces(">Z")
        .workplane(-p_thickness)
        .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
        .vertices()
        .circle(p_screwpostOD / 2.0)
        .circle(p_screwpostID / 2.0)
        .extrude(-1.0 * (p_outerHeight + p_lipHeight - p_thickness), True)
    )

    # split lid into top and bottom parts
    (lid, bottom) = (
        box.faces(">Z")
        .workplane(-p_thickness - p_lipHeight)
        .split(keepTop=True, keepBottom=True)
        .all()
    )  # splits into two solids

    # translate the lid, and subtract the bottom from it to produce the lid inset
    lowerLid = lid.translate((0, 0, -p_lipHeight))
    cutlip = lowerLid.cut(bottom).translate(
        (p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight)
    )

    # compute centers for screw holes
    topOfLidCenters = (
        cutlip.faces(">Z")
        .workplane(centerOption="CenterOfMass")
        .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
        .vertices()
    )

    # add holes of the desired type
    if p_boreDiameter > 0 and p_boreDepth > 0:
        topOfLid = topOfLidCenters.cboreHole(
            p_screwpostID, p_boreDiameter, p_boreDepth, 2.0 * p_thickness
        )
    elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
        topOfLid = topOfLidCenters.cskHole(
            p_screwpostID, p_countersinkDiameter, p_countersinkAngle, 2.0 * p_thickness
        )
    else:
        topOfLid = topOfLidCenters.hole(p_screwpostID, 2.0 * p_thickness)

    # flip lid upside down if desired
    if p_flipLid:
        topOfLid = topOfLid.rotateAboutCenter((1, 0, 0), 180)

    # return the combined result
    result = topOfLid.union(bottom)


.. topic:: Api References

    .. hlist::
        :columns: 3

        * :py:meth:`Workplane.circle`
        * :py:meth:`Workplane.rect`
        * :py:meth:`Workplane.extrude`
        * :py:meth:`Workplane.box`
        * :py:meth:`Workplane.all`
        * :py:meth:`Workplane.faces`
        * :py:meth:`Workplane.vertices`
        * :py:meth:`Workplane.edges`
        * :py:meth:`Workplane.workplane`
        * :py:meth:`Workplane.fillet`
        * :py:meth:`Workplane.cut`
        * :py:meth:`Workplane.union`
        * :py:meth:`Workplane.rotateAboutCenter`
        * :py:meth:`Workplane.cboreHole`
        * :py:meth:`Workplane.cskHole`
        * :py:meth:`Workplane.hole`

Lego Brick
-------------------

This script will produce any size regular rectangular Lego(TM) brick. Its only tricky because of the logic
regarding the underside of the brick.

.. cadquery::
    :select: tmp
    :height: 400px

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

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

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

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

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

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

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

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


Braille Example
---------------------

.. cadquery::
    :height: 400px

    from collections import namedtuple


    # text_lines is a list of text lines.
    # Braille (converted with braille-converter:
    # https://github.com/jpaugh/braille-converter.git).
    text_lines = ["⠠ ⠋ ⠗ ⠑ ⠑ ⠠ ⠉ ⠠ ⠁ ⠠ ⠙"]
    # See http://www.tiresias.org/research/reports/braille_cell.htm for examples
    # of braille cell geometry.
    horizontal_interdot = 2.5
    vertical_interdot = 2.5
    horizontal_intercell = 6
    vertical_interline = 10
    dot_height = 0.5
    dot_diameter = 1.3

    base_thickness = 1.5

    # End of configuration.
    BrailleCellGeometry = namedtuple(
        "BrailleCellGeometry",
        (
            "horizontal_interdot",
            "vertical_interdot",
            "intercell",
            "interline",
            "dot_height",
            "dot_diameter",
        ),
    )


    class Point(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y

        def __add__(self, other):
            return Point(self.x + other.x, self.y + other.y)

        def __len__(self):
            return 2

        def __getitem__(self, index):
            return (self.x, self.y)[index]

        def __str__(self):
            return "({}, {})".format(self.x, self.y)


    def brailleToPoints(text, cell_geometry):
        # Unicode bit pattern (cf. https://en.wikipedia.org/wiki/Braille_Patterns).
        mask1 = 0b00000001
        mask2 = 0b00000010
        mask3 = 0b00000100
        mask4 = 0b00001000
        mask5 = 0b00010000
        mask6 = 0b00100000
        mask7 = 0b01000000
        mask8 = 0b10000000
        masks = (mask1, mask2, mask3, mask4, mask5, mask6, mask7, mask8)

        # Corresponding dot position
        w = cell_geometry.horizontal_interdot
        h = cell_geometry.vertical_interdot
        pos1 = Point(0, 2 * h)
        pos2 = Point(0, h)
        pos3 = Point(0, 0)
        pos4 = Point(w, 2 * h)
        pos5 = Point(w, h)
        pos6 = Point(w, 0)
        pos7 = Point(0, -h)
        pos8 = Point(w, -h)
        pos = (pos1, pos2, pos3, pos4, pos5, pos6, pos7, pos8)

        # Braille blank pattern (u'\u2800').
        blank = "⠀"
        points = []
        # Position of dot1 along the x-axis (horizontal).
        character_origin = 0
        for c in text:
            for m, p in zip(masks, pos):
                delta_to_blank = ord(c) - ord(blank)
                if m & delta_to_blank:
                    points.append(p + Point(character_origin, 0))
            character_origin += cell_geometry.intercell
        return points


    def get_plate_height(text_lines, cell_geometry):
        # cell_geometry.vertical_interdot is also used as space between base
        # borders and characters.
        return (
            2 * cell_geometry.vertical_interdot
            + 2 * cell_geometry.vertical_interdot
            + (len(text_lines) - 1) * cell_geometry.interline
        )


    def get_plate_width(text_lines, cell_geometry):
        # cell_geometry.horizontal_interdot is also used as space between base
        # borders and characters.
        max_len = max([len(t) for t in text_lines])
        return (
            2 * cell_geometry.horizontal_interdot
            + cell_geometry.horizontal_interdot
            + (max_len - 1) * cell_geometry.intercell
        )


    def get_cylinder_radius(cell_geometry):
        """Return the radius the cylinder should have
        The cylinder have the same radius as the half-sphere make the dots (the
        hidden and the shown part of the dots).
        The radius is such that the spherical cap with diameter
        cell_geometry.dot_diameter has a height of cell_geometry.dot_height.
        """
        h = cell_geometry.dot_height
        r = cell_geometry.dot_diameter / 2
        return (r**2 + h**2) / 2 / h


    def get_base_plate_thickness(plate_thickness, cell_geometry):
        """Return the height on which the half spheres will sit"""
        return (
            plate_thickness + get_cylinder_radius(cell_geometry) - cell_geometry.dot_height
        )


    def make_base(text_lines, cell_geometry, plate_thickness):
        base_width = get_plate_width(text_lines, cell_geometry)
        base_height = get_plate_height(text_lines, cell_geometry)
        base_thickness = get_base_plate_thickness(plate_thickness, cell_geometry)
        base = cq.Workplane("XY").box(
            base_width, base_height, base_thickness, centered=False
        )
        return base


    def make_embossed_plate(text_lines, cell_geometry):
        """Make an embossed plate with dots as spherical caps
        Method:
            - make a thin plate on which sit cylinders
            - fillet the upper edge of the cylinders so to get pseudo half-spheres
            - make the union with a thicker plate so that only the sphere caps stay
              "visible".
        """
        base = make_base(text_lines, cell_geometry, base_thickness)

        dot_pos = []
        base_width = get_plate_width(text_lines, cell_geometry)
        base_height = get_plate_height(text_lines, cell_geometry)
        y = base_height - 3 * cell_geometry.vertical_interdot
        line_start_pos = Point(cell_geometry.horizontal_interdot, y)
        for text in text_lines:
            dots = brailleToPoints(text, cell_geometry)
            dots = [p + line_start_pos for p in dots]
            dot_pos += dots
            line_start_pos += Point(0, -cell_geometry.interline)

        r = get_cylinder_radius(cell_geometry)
        base = (
            base.faces(">Z")
            .vertices("<XY")
            .workplane()
            .pushPoints(dot_pos)
            .circle(r)
            .extrude(r)
        )
        # Make a fillet almost the same radius to get a pseudo spherical cap.
        base = base.faces(">Z").edges().fillet(r - 0.001)
        hidding_box = cq.Workplane("XY").box(
            base_width, base_height, base_thickness, centered=False
        )
        result = hidding_box.union(base)
        return result


    _cell_geometry = BrailleCellGeometry(
        horizontal_interdot,
        vertical_interdot,
        horizontal_intercell,
        vertical_interline,
        dot_height,
        dot_diameter,
    )

    if base_thickness < get_cylinder_radius(_cell_geometry):
        raise ValueError("Base thickness should be at least {}".format(dot_height))

    result = make_embossed_plate(text_lines, _cell_geometry)

Panel With Various Connector Holes
-----------------------------------

.. cadquery::
    :height: 400px

    # The dimensions of the model. These can be modified rather than changing the
    # object's code directly.
    width = 400
    height = 500
    thickness = 2

    # Create a plate with two polygons cut through it
    result = cq.Workplane("front").box(width, height, thickness)

    h_sep = 60
    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(157, 210 - idx * h_sep)
            .moveTo(-23.5, 0)
            .circle(1.6)
            .moveTo(23.5, 0)
            .circle(1.6)
            .moveTo(-17.038896, -5.7)
            .threePointArc((-19.44306, -4.70416), (-20.438896, -2.3))
            .lineTo(-21.25, 2.3)
            .threePointArc((-20.25416, 4.70416), (-17.85, 5.7))
            .lineTo(17.85, 5.7)
            .threePointArc((20.25416, 4.70416), (21.25, 2.3))
            .lineTo(20.438896, -2.3)
            .threePointArc((19.44306, -4.70416), (17.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(157, -30 - idx * h_sep)
            .moveTo(-16.65, 0)
            .circle(1.6)
            .moveTo(16.65, 0)
            .circle(1.6)
            .moveTo(-10.1889, -5.7)
            .threePointArc((-12.59306, -4.70416), (-13.5889, -2.3))
            .lineTo(-14.4, 2.3)
            .threePointArc((-13.40416, 4.70416), (-11, 5.7))
            .lineTo(11, 5.7)
            .threePointArc((13.40416, 4.70416), (14.4, 2.3))
            .lineTo(13.5889, -2.3)
            .threePointArc((12.59306, -4.70416), (10.1889, -5.7))
            .close()
            .cutThruAll()
        )

    h_sep4DB9 = 30
    for idx in range(8):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(91, 225 - idx * h_sep4DB9)
            .moveTo(-12.5, 0)
            .circle(1.6)
            .moveTo(12.5, 0)
            .circle(1.6)
            .moveTo(-6.038896, -5.7)
            .threePointArc((-8.44306, -4.70416), (-9.438896, -2.3))
            .lineTo(-10.25, 2.3)
            .threePointArc((-9.25416, 4.70416), (-6.85, 5.7))
            .lineTo(6.85, 5.7)
            .threePointArc((9.25416, 4.70416), (10.25, 2.3))
            .lineTo(9.438896, -2.3)
            .threePointArc((8.44306, -4.70416), (6.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(25, 210 - idx * h_sep)
            .moveTo(-23.5, 0)
            .circle(1.6)
            .moveTo(23.5, 0)
            .circle(1.6)
            .moveTo(-17.038896, -5.7)
            .threePointArc((-19.44306, -4.70416), (-20.438896, -2.3))
            .lineTo(-21.25, 2.3)
            .threePointArc((-20.25416, 4.70416), (-17.85, 5.7))
            .lineTo(17.85, 5.7)
            .threePointArc((20.25416, 4.70416), (21.25, 2.3))
            .lineTo(20.438896, -2.3)
            .threePointArc((19.44306, -4.70416), (17.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(25, -30 - idx * h_sep)
            .moveTo(-16.65, 0)
            .circle(1.6)
            .moveTo(16.65, 0)
            .circle(1.6)
            .moveTo(-10.1889, -5.7)
            .threePointArc((-12.59306, -4.70416), (-13.5889, -2.3))
            .lineTo(-14.4, 2.3)
            .threePointArc((-13.40416, 4.70416), (-11, 5.7))
            .lineTo(11, 5.7)
            .threePointArc((13.40416, 4.70416), (14.4, 2.3))
            .lineTo(13.5889, -2.3)
            .threePointArc((12.59306, -4.70416), (10.1889, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(8):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(-41, 225 - idx * h_sep4DB9)
            .moveTo(-12.5, 0)
            .circle(1.6)
            .moveTo(12.5, 0)
            .circle(1.6)
            .moveTo(-6.038896, -5.7)
            .threePointArc((-8.44306, -4.70416), (-9.438896, -2.3))
            .lineTo(-10.25, 2.3)
            .threePointArc((-9.25416, 4.70416), (-6.85, 5.7))
            .lineTo(6.85, 5.7)
            .threePointArc((9.25416, 4.70416), (10.25, 2.3))
            .lineTo(9.438896, -2.3)
            .threePointArc((8.44306, -4.70416), (6.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(-107, 210 - idx * h_sep)
            .moveTo(-23.5, 0)
            .circle(1.6)
            .moveTo(23.5, 0)
            .circle(1.6)
            .moveTo(-17.038896, -5.7)
            .threePointArc((-19.44306, -4.70416), (-20.438896, -2.3))
            .lineTo(-21.25, 2.3)
            .threePointArc((-20.25416, 4.70416), (-17.85, 5.7))
            .lineTo(17.85, 5.7)
            .threePointArc((20.25416, 4.70416), (21.25, 2.3))
            .lineTo(20.438896, -2.3)
            .threePointArc((19.44306, -4.70416), (17.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(-107, -30 - idx * h_sep)
            .circle(14)
            .rect(24.7487, 24.7487, forConstruction=True)
            .vertices()
            .hole(3.2)
            .cutThruAll()
        )

    for idx in range(8):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(-173, 225 - idx * h_sep4DB9)
            .moveTo(-12.5, 0)
            .circle(1.6)
            .moveTo(12.5, 0)
            .circle(1.6)
            .moveTo(-6.038896, -5.7)
            .threePointArc((-8.44306, -4.70416), (-9.438896, -2.3))
            .lineTo(-10.25, 2.3)
            .threePointArc((-9.25416, 4.70416), (-6.85, 5.7))
            .lineTo(6.85, 5.7)
            .threePointArc((9.25416, 4.70416), (10.25, 2.3))
            .lineTo(9.438896, -2.3)
            .threePointArc((8.44306, -4.70416), (6.038896, -5.7))
            .close()
            .cutThruAll()
        )

    for idx in range(4):
        result = (
            result.workplane(offset=1, centerOption="CenterOfBoundBox")
            .center(-173, -30 - idx * h_sep)
            .moveTo(-2.9176, -5.3)
            .threePointArc((-6.05, 0), (-2.9176, 5.3))
            .lineTo(2.9176, 5.3)
            .threePointArc((6.05, 0), (2.9176, -5.3))
            .close()
            .cutThruAll()
        )


Cycloidal gear
--------------

You can define complex geometries using the parametricCurve functionality.
This specific examples generates a helical cycloidal gear.

.. cadquery::
    :height: 400px

    import cadquery as cq
    from math import sin, cos, pi, floor


    # define the generating function
    def hypocycloid(t, r1, r2):
        return (
            (r1 - r2) * cos(t) + r2 * cos(r1 / r2 * t - t),
            (r1 - r2) * sin(t) + r2 * sin(-(r1 / r2 * t - t)),
        )


    def epicycloid(t, r1, r2):
        return (
            (r1 + r2) * cos(t) - r2 * cos(r1 / r2 * t + t),
            (r1 + r2) * sin(t) - r2 * sin(r1 / r2 * t + t),
        )


    def gear(t, r1=4, r2=1):
        if (-1) ** (1 + floor(t / 2 / pi * (r1 / r2))) < 0:
            return epicycloid(t, r1, r2)
        else:
            return hypocycloid(t, r1, r2)


    # create the gear profile and extrude it
    result = (
        cq.Workplane("XY")
        .parametricCurve(lambda t: gear(t * 2 * pi, 6, 1))
        .twistExtrude(15, 90)
        .faces(">Z")
        .workplane()
        .circle(2)
        .cutThruAll()
    )