├── .coveragerc
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── 00-generic-issue.md
│ ├── config.yml
│ ├── exception_crash_report.md
│ ├── modelling-question.md
│ └── occt-feature-request.md
├── .gitignore
├── .readthedocs.yaml
├── CITATION.cff
├── LICENSE
├── MANIFEST.in
├── README.md
├── appveyor.yml
├── azure-pipelines.yml
├── build-docs.sh
├── cadquery
├── README.txt
├── __init__.py
├── assembly.py
├── contrib
│ └── __init__.py
├── cq.py
├── cq_directive.py
├── cqgi.py
├── func.py
├── hull.py
├── occ_impl
│ ├── README.txt
│ ├── __init__.py
│ ├── assembly.py
│ ├── exporters
│ │ ├── __init__.py
│ │ ├── amf.py
│ │ ├── assembly.py
│ │ ├── dxf.py
│ │ ├── json.py
│ │ ├── svg.py
│ │ ├── threemf.py
│ │ └── vtk.py
│ ├── geom.py
│ ├── importers
│ │ ├── __init__.py
│ │ └── dxf.py
│ ├── jupyter_tools.py
│ ├── shape_protocols.py
│ ├── shapes.py
│ ├── sketch_solver.py
│ └── solver.py
├── plugins
│ └── __init__.py
├── py.typed
├── selectors.py
├── sketch.py
├── types.py
├── units.py
├── utils.py
└── vis.py
├── changes.md
├── conda
├── meta.yaml
└── web-installer
│ ├── .gitignore
│ ├── Readme.md
│ ├── build.py
│ ├── construct.yaml.jinja2
│ ├── post-install.bat.jinja2
│ └── post-install.sh.jinja2
├── conda_build.bat
├── conda_build.sh
├── conda_build_config.yaml
├── doc
├── README
├── _static
│ ├── ParametricPulley.PNG
│ ├── assy.png
│ ├── block.png
│ ├── cadquery_cheatsheet.html
│ ├── ctrl_pts.png
│ ├── door_assy.png
│ ├── door_assy_freecad.png
│ ├── hyOzd-cablefix.png
│ ├── hyOzd-finished.jpg
│ ├── importexport
│ │ ├── box_custom_options.svg
│ │ ├── box_custom_options_perspective.svg
│ │ └── box_default_options.svg
│ ├── logo
│ │ ├── README.md
│ │ ├── Roboto_Font
│ │ │ ├── LICENSE.txt
│ │ │ └── Roboto-Bold.ttf
│ │ ├── cadquery_logo_dark.svg
│ │ ├── cadquery_logo_dark_inkscape.svg
│ │ ├── cadquery_logo_light.svg
│ │ └── cadquery_logo_light_inkscape.svg
│ ├── new_badge.png
│ ├── parametric-cup-screencap.PNG
│ ├── parametric-pillowblock-screencap.png
│ ├── pillowblock.png
│ ├── quickstart-1.png
│ ├── quickstart-2.png
│ ├── quickstart-3.png
│ ├── quickstart-4.png
│ ├── quickstart-5.png
│ ├── quickstart.png
│ ├── quickstart
│ │ ├── 000.png
│ │ ├── 001.png
│ │ ├── 002.png
│ │ ├── 003.png
│ │ ├── 004.png
│ │ └── 005.png
│ ├── show.PNG
│ ├── show_camera_position.png
│ ├── show_demo.PNG
│ ├── show_jupyter.PNG
│ ├── show_styling.png
│ ├── show_vtk.PNG
│ ├── simple_assy.png
│ ├── simpleblock.png
│ ├── tables.css
│ └── vtk.js
├── apireference.rst
├── assy.rst
├── citing.rst
├── classreference.rst
├── conf.py
├── cqgi.rst
├── designprinciples.rst
├── examples.rst
├── ext
│ └── sphinx_autodoc_multimethod.py
├── extending.rst
├── fileformat.rst
├── free-func.rst
├── gen_colors.py
├── importexport.rst
├── index.rst
├── installation.rst
├── intro.rst
├── primer.rst
├── quickstart.rst
├── roadmap.rst
├── selectors.rst
├── sketch.rst
├── vis.rst
├── vslot-2020_1.dxf
└── workplane.rst
├── environment.yml
├── examples
├── CQ examples.ipynb
├── Ex001_Simple_Block.py
├── Ex002_Block_With_Bored_Center_Hole.py
├── Ex003_Pillow_Block_With_Counterbored_Holes.py
├── Ex004_Extruded_Cylindrical_Plate.py
├── Ex005_Extruded_Lines_and_Arcs.py
├── Ex006_Moving_the_Current_Working_Point.py
├── Ex007_Using_Point_Lists.py
├── Ex008_Polygon_Creation.py
├── Ex009_Polylines.py
├── Ex010_Defining_an_Edge_with_a_Spline.py
├── Ex011_Mirroring_Symmetric_Geometry.py
├── Ex012_Creating_Workplanes_on_Faces.py
├── Ex013_Locating_a_Workplane_on_a_Vertex.py
├── Ex014_Offset_Workplanes.py
├── Ex015_Rotated_Workplanes.py
├── Ex016_Using_Construction_Geometry.py
├── Ex017_Shelling_to_Create_Thin_Features.py
├── Ex018_Making_Lofts.py
├── Ex019_Counter_Sunk_Holes.py
├── Ex020_Rounding_Corners_with_Fillets.py
├── Ex021_Splitting_an_Object.py
├── Ex022_Revolution.py
├── Ex023_Sweep.py
├── Ex024_Sweep_With_Multiple_Sections.py
├── Ex025_Swept_Helix.py
├── Ex026_Case_Seam_Lip.py
├── Ex100_Lego_Brick.py
└── Ex101_InterpPlate.py
├── mypy.ini
├── mypy
└── cadquery-plugin.py
├── partcad.yaml
├── setup.cfg
├── setup.py
└── tests
├── README.txt
├── __init__.py
├── naca.py
├── test_assembly.py
├── test_cad_objects.py
├── test_cadquery.py
├── test_cqgi.py
├── test_examples.py
├── test_exporters.py
├── test_free_functions.py
├── test_hull.py
├── test_importers.py
├── test_jupyter.py
├── test_pickle.py
├── test_selectors.py
├── test_shapes.py
├── test_sketch.py
├── test_utils.py
├── test_vis.py
├── test_workplanes.py
└── testdata
├── 1001.dxf
├── MC 12x31.dxf
├── OpenSans-Regular.ttf
├── gear.dxf
├── genshi.dxf
├── rational_spline.dxf
├── red_cube_blue_cylinder.step
├── spline.dxf
└── three_layers.dxf
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | omit =
4 | cadquery/utils.py
5 | cadquery/cq_directive.py
6 | tests/*
7 |
8 | [report]
9 | exclude_lines =
10 | # Ignore stub body
11 | \.\.\.
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't display text diffs for files that are not human readable
2 | # Git should not modify line endings in these test files
3 | tests/testdata/* -diff -text
4 | *.svg -diff -text
5 |
6 | # Jupyter notebooks should be classified as documentation
7 | *.ipynb linguist-documentation -diff -text
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/00-generic-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Generic issue
3 | about: Use this if none of the more specific templates fit your issue
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Online documentation
4 | url: https://cadquery.readthedocs.io/
5 | about: CadQuery's extensive online documentation
6 | - name: GitHub Discussions
7 | url: https://github.com/CadQuery/cadquery/discussions
8 | about: For more casual discussion and help
9 | - name: Google group
10 | url: https://groups.google.com/forum/#!forum/cadquery
11 | about: Mailing list for CadQuery
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/exception_crash_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Exception/crash report
3 | about: You have a specific exception or crash to report
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 |
13 | ## To Reproduce
14 |
15 |
16 | ```python
17 | ```
18 |
19 |
20 |
21 | ## Backtrace
22 |
23 |
24 | ```
25 | ```
26 |
27 | ## Environment
28 |
29 | OS:
30 |
31 | Was CadQuery installed using Conda?:
32 | Output of `conda list` from your active Conda environment:
33 | ```
34 | ```
35 |
36 | Using:
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/modelling-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Modelling question
3 | about: You want to know how to perform a modelling operation
4 | title: ''
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 |
20 |
21 |
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/occt-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: OCCT feature request
3 | about: Suggest an implementation of an existing OCCT feature
4 | title: ''
5 | labels: OCC feature, enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 | Links to OCCT documentation:
13 | *
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.pyc
3 | doc/_build/*
4 | dist/*
5 | .idea/*
6 | cadquery.egg-info/
7 | target/*
8 | .eggs/
9 | .vscode
10 | MANIFEST
11 | out.*
12 | res?.dxf
13 | limit?.dxf
14 | .~*
15 | .*.swp
16 | assy.wrl
17 | assy.xml
18 | assy.zip
19 | nested.step
20 | simple.caf
21 | simple.step
22 | simple.stp
23 | simple.xml
24 | test.brep
25 | nested.caf
26 | nested.glb
27 | nested.stp
28 | nested.wrl
29 | nested.xml
30 | nested.zip
31 | nested.stl
32 | nested.bin
33 | nested_*.bin
34 | nested_*.gltf
35 | nested_*.glb
36 | nested_*.stl
37 | out1.3mf
38 | out2.3mf
39 | out3.3mf
40 | orig.dxf
41 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | sphinx:
4 | # Path to your Sphinx configuration file.
5 | configuration: doc/conf.py
6 |
7 |
8 | build:
9 | os: "ubuntu-20.04"
10 | tools:
11 | python: "mambaforge-4.10"
12 |
13 | conda:
14 | environment: environment.yml
15 |
16 | formats: all
17 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | doi: 10.5281/zenodo.10513848
3 | license: "Apache 2.0"
4 | url: https://github.com/CadQuery/cadquery
5 | title: "CadQuery"
6 | message: "If you use this software, please cite it using these metadata."
7 | authors:
8 | - name: "CadQuery contributors"
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include cadquery/py.typed
3 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | shallow_clone: true
2 |
3 | platform:
4 | - x64
5 |
6 | environment:
7 | matrix:
8 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
9 | MINICONDA_DIRNAME: C:\Miniforge
10 | - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204
11 | OS: Linux
12 | - APPVEYOR_BUILD_WORKER_IMAGE: macOS
13 | OS: MacOSX
14 |
15 | ANACONDA_TOKEN:
16 | secure: $(anaconda_token)
17 |
18 | init:
19 | - cmd: curl -fsSLo Miniforge.exe https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Windows-x86_64.exe
20 | - cmd: Miniforge.exe /InstallationType=JustMe /RegisterPython=0 /S /D=%MINICONDA_DIRNAME%
21 | - cmd: set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
22 | - cmd: activate
23 | - cmd: set MAMBA_ROOT_PREFIX=C:/Miniforge/Library
24 | - sh: curl -sL https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$OS-x86_64.sh > miniconda.sh
25 | - sh: bash miniconda.sh -b -p $HOME/miniconda;
26 | - sh: export PATH="$HOME/miniconda/bin:$HOME/miniconda/lib:$PATH";
27 | - sh: source $HOME/miniconda/bin/activate
28 | - sh: export MAMBA_ROOT_PREFIX=$HOME/miniconda
29 |
30 | install:
31 | - conda config --set always_yes yes
32 | - mamba env create -f environment.yml
33 | - mamba list -n cadquery
34 |
35 | build: false
36 |
37 | test_script:
38 | - mamba run -n cadquery black . --diff --check
39 | - mamba run -n cadquery mypy cadquery
40 | - mamba run -n cadquery pytest -v --cov
41 |
42 | on_success:
43 | - mamba run -n cadquery codecov
44 |
45 | #on_finish:
46 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
47 | # - sh: export APPVEYOR_SSH_BLOCK=true
48 | # - sh: curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
49 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | branches:
3 | include:
4 | - master
5 | - refs/tags/*
6 |
7 | pr:
8 | - master
9 |
10 | resources:
11 | repositories:
12 | - repository: templates
13 | type: github
14 | name: CadQuery/conda-packages
15 | endpoint: CadQuery
16 |
17 | parameters:
18 | - name: minor
19 | type: object
20 | default:
21 | - 13
22 |
23 | jobs:
24 | - ${{ each minor in parameters.minor }}:
25 | - template: conda-build.yml@templates
26 | parameters:
27 | name: Windows
28 | vmImage: 'windows-latest'
29 | py_maj: 3
30 | py_min: ${{minor}}
31 | conda_bld: 3.21.6
32 |
--------------------------------------------------------------------------------
/build-docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | (cd doc && sphinx-build -b html . ../target/docs)
3 |
--------------------------------------------------------------------------------
/cadquery/README.txt:
--------------------------------------------------------------------------------
1 | ***
2 | Core CadQuery implementation.
3 |
4 | No files should depend on or import PythonOCC, or other CAD Kernel libraries!!!
5 | Dependencies should be on the classes provided by implementation packages, which in turn
6 | can depend on CAD libraries.
7 |
8 | ***
--------------------------------------------------------------------------------
/cadquery/__init__.py:
--------------------------------------------------------------------------------
1 | from importlib.metadata import version, PackageNotFoundError
2 |
3 | try:
4 | __version__ = version("cadquery")
5 | except PackageNotFoundError:
6 | # package is not installed
7 | __version__ = "2.6-dev"
8 |
9 | # these items point to the OCC implementation
10 | from .occ_impl.geom import Plane, BoundBox, Vector, Matrix, Location
11 | from .occ_impl.shapes import (
12 | Shape,
13 | Vertex,
14 | Edge,
15 | Face,
16 | Wire,
17 | Solid,
18 | Shell,
19 | Compound,
20 | sortWiresByBuildOrder,
21 | )
22 | from .occ_impl import exporters
23 | from .occ_impl import importers
24 |
25 | # these items are the common implementation
26 |
27 | # the order of these matter
28 | from .selectors import (
29 | NearestToPointSelector,
30 | ParallelDirSelector,
31 | DirectionSelector,
32 | PerpendicularDirSelector,
33 | TypeSelector,
34 | DirectionMinMaxSelector,
35 | StringSyntaxSelector,
36 | Selector,
37 | )
38 | from .sketch import Sketch
39 | from .cq import CQ, Workplane
40 | from .assembly import Assembly, Color, Constraint
41 | from . import selectors
42 | from . import plugins
43 |
44 |
45 | __all__ = [
46 | "CQ",
47 | "Workplane",
48 | "Assembly",
49 | "Color",
50 | "Constraint",
51 | "plugins",
52 | "selectors",
53 | "Plane",
54 | "BoundBox",
55 | "Matrix",
56 | "Vector",
57 | "Location",
58 | "sortWiresByBuildOrder",
59 | "Shape",
60 | "Vertex",
61 | "Edge",
62 | "Wire",
63 | "Face",
64 | "Solid",
65 | "Shell",
66 | "Compound",
67 | "exporters",
68 | "importers",
69 | "NearestToPointSelector",
70 | "ParallelDirSelector",
71 | "DirectionSelector",
72 | "PerpendicularDirSelector",
73 | "TypeSelector",
74 | "DirectionMinMaxSelector",
75 | "StringSyntaxSelector",
76 | "Selector",
77 | "plugins",
78 | "Sketch",
79 | ]
80 |
--------------------------------------------------------------------------------
/cadquery/contrib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/cadquery/contrib/__init__.py
--------------------------------------------------------------------------------
/cadquery/func.py:
--------------------------------------------------------------------------------
1 | from .occ_impl.geom import Vector, Plane, Location
2 | from .occ_impl.shapes import (
3 | Shape,
4 | Vertex,
5 | Edge,
6 | Wire,
7 | Face,
8 | Shell,
9 | Solid,
10 | CompSolid,
11 | Compound,
12 | edgeOn,
13 | wireOn,
14 | wire,
15 | face,
16 | shell,
17 | solid,
18 | compound,
19 | vertex,
20 | segment,
21 | polyline,
22 | polygon,
23 | rect,
24 | spline,
25 | circle,
26 | ellipse,
27 | plane,
28 | box,
29 | cylinder,
30 | sphere,
31 | torus,
32 | cone,
33 | text,
34 | fuse,
35 | cut,
36 | intersect,
37 | imprint,
38 | split,
39 | fill,
40 | clean,
41 | cap,
42 | fillet,
43 | chamfer,
44 | extrude,
45 | revolve,
46 | offset,
47 | sweep,
48 | loft,
49 | check,
50 | closest,
51 | setThreads,
52 | project,
53 | faceOn,
54 | )
55 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/README.txt:
--------------------------------------------------------------------------------
1 | CadQuery implementation based on OCP
2 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/cadquery/occ_impl/__init__.py
--------------------------------------------------------------------------------
/cadquery/occ_impl/exporters/__init__.py:
--------------------------------------------------------------------------------
1 | import tempfile
2 | import os
3 | import io as StringIO
4 |
5 | from typing import IO, Optional, Union, cast, Dict, Any, Iterable
6 | from typing_extensions import Literal
7 |
8 | from OCP.VrmlAPI import VrmlAPI
9 |
10 | from ...utils import deprecate
11 | from ..shapes import Shape, compound
12 |
13 | from .svg import getSVG
14 | from .json import JsonMesh
15 | from .amf import AmfWriter
16 | from .threemf import ThreeMFWriter
17 | from .dxf import exportDXF, DxfDocument
18 | from .vtk import exportVTP
19 |
20 |
21 | class ExportTypes:
22 | STL = "STL"
23 | STEP = "STEP"
24 | AMF = "AMF"
25 | SVG = "SVG"
26 | TJS = "TJS"
27 | DXF = "DXF"
28 | VRML = "VRML"
29 | VTP = "VTP"
30 | THREEMF = "3MF"
31 | BREP = "BREP"
32 | BIN = "BIN"
33 |
34 |
35 | ExportLiterals = Literal[
36 | "STL", "STEP", "AMF", "SVG", "TJS", "DXF", "VRML", "VTP", "3MF", "BREP", "BIN"
37 | ]
38 |
39 |
40 | def export(
41 | w: Union[Shape, Iterable[Shape]],
42 | fname: str,
43 | exportType: Optional[ExportLiterals] = None,
44 | tolerance: float = 0.1,
45 | angularTolerance: float = 0.1,
46 | opt: Optional[Dict[str, Any]] = None,
47 | ):
48 |
49 | """
50 | Export Workplane or Shape to file. Multiple entities are converted to compound.
51 |
52 | :param w: Shape or Iterable[Shape] (e.g. Workplane) to be exported.
53 | :param fname: output filename.
54 | :param exportType: the exportFormat to use. If None will be inferred from the extension. Default: None.
55 | :param tolerance: the deflection tolerance, in model units. Default 0.1.
56 | :param angularTolerance: the angular tolerance, in radians. Default 0.1.
57 | :param opt: additional options passed to the specific exporter. Default None.
58 | """
59 |
60 | shape: Shape
61 | f: IO
62 |
63 | if not opt:
64 | opt = {}
65 |
66 | if isinstance(w, Shape):
67 | shape = w
68 | else:
69 | shape = compound(*w)
70 |
71 | if exportType is None:
72 | t = fname.split(".")[-1].upper()
73 | if t in ExportTypes.__dict__.values():
74 | exportType = cast(ExportLiterals, t)
75 | else:
76 | raise ValueError("Unknown extensions, specify export type explicitly")
77 |
78 | if exportType == ExportTypes.TJS:
79 | tess = shape.tessellate(tolerance, angularTolerance)
80 | mesher = JsonMesh()
81 |
82 | # add vertices
83 | for v in tess[0]:
84 | mesher.addVertex(v.x, v.y, v.z)
85 |
86 | # add triangles
87 | for ixs in tess[1]:
88 | mesher.addTriangleFace(*ixs)
89 |
90 | with open(fname, "w") as f:
91 | f.write(mesher.toJson())
92 |
93 | elif exportType == ExportTypes.SVG:
94 | with open(fname, "w") as f:
95 | f.write(getSVG(shape, opt))
96 |
97 | elif exportType == ExportTypes.AMF:
98 | tess = shape.tessellate(tolerance, angularTolerance)
99 | aw = AmfWriter(tess)
100 | with open(fname, "wb") as f:
101 | aw.writeAmf(f)
102 |
103 | elif exportType == ExportTypes.THREEMF:
104 | tmfw = ThreeMFWriter(shape, tolerance, angularTolerance, **opt)
105 | with open(fname, "wb") as f:
106 | tmfw.write3mf(f)
107 |
108 | elif exportType == ExportTypes.DXF:
109 | exportDXF(w, fname, **opt)
110 |
111 | elif exportType == ExportTypes.STEP:
112 | shape.exportStep(fname, **opt)
113 |
114 | elif exportType == ExportTypes.STL:
115 | if opt:
116 | useascii = opt.get("ascii", False) or opt.get("ASCII", False)
117 | else:
118 | useascii = False
119 |
120 | shape.exportStl(fname, tolerance, angularTolerance, useascii)
121 |
122 | elif exportType == ExportTypes.VRML:
123 | shape.mesh(tolerance, angularTolerance)
124 | VrmlAPI.Write_s(shape.wrapped, fname)
125 |
126 | elif exportType == ExportTypes.VTP:
127 | exportVTP(shape, fname, tolerance, angularTolerance)
128 |
129 | elif exportType == ExportTypes.BREP:
130 | shape.exportBrep(fname)
131 |
132 | elif exportType == ExportTypes.BIN:
133 | shape.exportBin(fname)
134 |
135 | else:
136 | raise ValueError("Unknown export type")
137 |
138 |
139 | @deprecate()
140 | def toString(shape, exportType, tolerance=0.1, angularTolerance=0.05):
141 | s = StringIO.StringIO()
142 | exportShape(shape, exportType, s, tolerance, angularTolerance)
143 | return s.getvalue()
144 |
145 |
146 | @deprecate()
147 | def exportShape(
148 | w: Union[Shape, Iterable[Shape]],
149 | exportType: ExportLiterals,
150 | fileLike: IO,
151 | tolerance: float = 0.1,
152 | angularTolerance: float = 0.1,
153 | ):
154 | """
155 | :param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
156 | object, the first value is exported
157 | :param exportType: the exportFormat to use
158 | :param fileLike: a file like object to which the content will be written.
159 | The object should be already open and ready to write. The caller is responsible
160 | for closing the object
161 | :param tolerance: the linear tolerance, in model units. Default 0.1.
162 | :param angularTolerance: the angular tolerance, in radians. Default 0.1.
163 | """
164 |
165 | def tessellate(shape, angularTolerance):
166 |
167 | return shape.tessellate(tolerance, angularTolerance)
168 |
169 | shape: Shape
170 | if isinstance(w, Shape):
171 | shape = w
172 | else:
173 | shape = compound(*w)
174 |
175 | if exportType == ExportTypes.TJS:
176 | tess = tessellate(shape, angularTolerance)
177 | mesher = JsonMesh()
178 |
179 | # add vertices
180 | for v in tess[0]:
181 | mesher.addVertex(v.x, v.y, v.z)
182 |
183 | # add triangles
184 | for t in tess[1]:
185 | mesher.addTriangleFace(*t)
186 |
187 | fileLike.write(mesher.toJson())
188 |
189 | elif exportType == ExportTypes.SVG:
190 | fileLike.write(getSVG(shape))
191 | elif exportType == ExportTypes.AMF:
192 | tess = tessellate(shape, angularTolerance)
193 | aw = AmfWriter(tess)
194 | aw.writeAmf(fileLike)
195 | elif exportType == ExportTypes.THREEMF:
196 | tmfw = ThreeMFWriter(shape, tolerance, angularTolerance)
197 | tmfw.write3mf(fileLike)
198 | else:
199 |
200 | # all these types required writing to a file and then
201 | # re-reading. this is due to the fact that FreeCAD writes these
202 | (h, outFileName) = tempfile.mkstemp()
203 | # weird, but we need to close this file. the next step is going to write to
204 | # it from c code, so it needs to be closed.
205 | os.close(h)
206 |
207 | if exportType == ExportTypes.STEP:
208 | shape.exportStep(outFileName)
209 | elif exportType == ExportTypes.STL:
210 | shape.exportStl(outFileName, tolerance, angularTolerance, True)
211 | else:
212 | raise ValueError("No idea how i got here")
213 |
214 | res = readAndDeleteFile(outFileName)
215 | fileLike.write(res)
216 |
217 |
218 | @deprecate()
219 | def readAndDeleteFile(fileName):
220 | """
221 | Read data from file provided, and delete it when done
222 | return the contents as a string
223 | """
224 | res = ""
225 | with open(fileName, "r") as f:
226 | res = "{}".format(f.read())
227 |
228 | os.remove(fileName)
229 | return res
230 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/exporters/amf.py:
--------------------------------------------------------------------------------
1 | import xml.etree.cElementTree as ET
2 |
3 |
4 | class AmfWriter(object):
5 | def __init__(self, tessellation):
6 |
7 | self.units = "mm"
8 | self.tessellation = tessellation
9 |
10 | def writeAmf(self, outFile):
11 | amf = ET.Element("amf", units=self.units)
12 | # TODO: if result is a compound, we need to loop through them
13 | object = ET.SubElement(amf, "object", id="0")
14 | mesh = ET.SubElement(object, "mesh")
15 | vertices = ET.SubElement(mesh, "vertices")
16 | volume = ET.SubElement(mesh, "volume")
17 |
18 | # add vertices
19 | for v in self.tessellation[0]:
20 | vtx = ET.SubElement(vertices, "vertex")
21 | coord = ET.SubElement(vtx, "coordinates")
22 | x = ET.SubElement(coord, "x")
23 | x.text = str(v.x)
24 | y = ET.SubElement(coord, "y")
25 | y.text = str(v.y)
26 | z = ET.SubElement(coord, "z")
27 | z.text = str(v.z)
28 |
29 | # add triangles
30 | for t in self.tessellation[1]:
31 | triangle = ET.SubElement(volume, "triangle")
32 | v1 = ET.SubElement(triangle, "v1")
33 | v1.text = str(t[0])
34 | v2 = ET.SubElement(triangle, "v2")
35 | v2.text = str(t[1])
36 | v3 = ET.SubElement(triangle, "v3")
37 | v3.text = str(t[2])
38 |
39 | amf = ET.ElementTree(amf).write(outFile, xml_declaration=True)
40 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/exporters/json.py:
--------------------------------------------------------------------------------
1 | """
2 | Objects that represent
3 | three.js JSON object notation
4 | https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
5 | """
6 |
7 | JSON_TEMPLATE = """\
8 | {
9 | "metadata" :
10 | {
11 | "formatVersion" : 3,
12 | "generatedBy" : "cadquery",
13 | "vertices" : %(nVertices)d,
14 | "faces" : %(nFaces)d,
15 | "normals" : 0,
16 | "colors" : 0,
17 | "uvs" : 0,
18 | "materials" : 1,
19 | "morphTargets" : 0
20 | },
21 |
22 | "scale" : 1.0,
23 |
24 | "materials": [ {
25 | "DbgColor" : 15658734,
26 | "DbgIndex" : 0,
27 | "DbgName" : "Material",
28 | "colorAmbient" : [0.0, 0.0, 0.0],
29 | "colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746],
30 | "colorSpecular" : [0.5, 0.5, 0.5],
31 | "shading" : "Lambert",
32 | "specularCoef" : 50,
33 | "transparency" : 1.0,
34 | "vertexColors" : false
35 | }],
36 |
37 | "vertices": %(vertices)s,
38 |
39 | "morphTargets": [],
40 |
41 | "normals": [],
42 |
43 | "colors": [],
44 |
45 | "uvs": [[]],
46 |
47 | "faces": %(faces)s
48 | }
49 | """
50 |
51 |
52 | class JsonMesh(object):
53 | def __init__(self):
54 |
55 | self.vertices = []
56 | self.faces = []
57 | self.nVertices = 0
58 | self.nFaces = 0
59 |
60 | def addVertex(self, x, y, z):
61 | self.nVertices += 1
62 | self.vertices.extend([x, y, z])
63 |
64 | # add triangle composed of the three provided vertex indices
65 | def addTriangleFace(self, i, j, k):
66 | # first position means justa simple triangle
67 | self.nFaces += 1
68 | self.faces.extend([0, int(i), int(j), int(k)])
69 |
70 | """
71 | Get a json model from this model.
72 | For now we'll forget about colors, vertex normals, and all that stuff
73 | """
74 |
75 | def toJson(self):
76 | return JSON_TEMPLATE % {
77 | "vertices": str(self.vertices),
78 | "faces": str(self.faces),
79 | "nVertices": self.nVertices,
80 | "nFaces": self.nFaces,
81 | }
82 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/exporters/threemf.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from os import PathLike
3 | import xml.etree.cElementTree as ET
4 | from typing import IO, List, Literal, Tuple, Union
5 | from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
6 |
7 | from ..shapes import Compound, Shape, Vector
8 |
9 |
10 | class CONTENT_TYPES(object):
11 | MODEL = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"
12 | RELATION = "application/vnd.openxmlformats-package.relationships+xml"
13 |
14 |
15 | class SCHEMAS(object):
16 | CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"
17 | RELATION = "http://schemas.openxmlformats.org/package/2006/relationships"
18 | CORE = "http://schemas.microsoft.com/3dmanufacturing/core/2015/02"
19 | MODEL = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
20 |
21 |
22 | Unit = Literal["micron", "millimeter", "centimeter", "meter", "inch", "foot"]
23 |
24 |
25 | class ThreeMFWriter(object):
26 | def __init__(
27 | self,
28 | shape: Shape,
29 | tolerance: float,
30 | angularTolerance: float,
31 | unit: Unit = "millimeter",
32 | ):
33 | """
34 | Initialize the writer.
35 | Used to write the given Shape to a 3MF file.
36 | """
37 | self.unit = unit
38 |
39 | if isinstance(shape, Compound):
40 | shapes = list(shape)
41 | else:
42 | shapes = [shape]
43 |
44 | tessellations = [s.tessellate(tolerance, angularTolerance) for s in shapes]
45 | # Remove shapes that did not tesselate
46 | self.tessellations = [t for t in tessellations if all(t)]
47 |
48 | def write3mf(
49 | self, outfile: Union[PathLike, str, IO[bytes]],
50 | ):
51 | """
52 | Write to the given file.
53 | """
54 |
55 | try:
56 | import zlib
57 |
58 | compression = ZIP_DEFLATED
59 | except ImportError:
60 | compression = ZIP_STORED
61 |
62 | with ZipFile(outfile, "w", compression) as zf:
63 | zf.writestr("_rels/.rels", self._write_relationships())
64 | zf.writestr("[Content_Types].xml", self._write_content_types())
65 | zf.writestr("3D/3dmodel.model", self._write_3d())
66 |
67 | def _write_3d(self) -> str:
68 |
69 | no_meshes = len(self.tessellations)
70 |
71 | model = ET.Element(
72 | "model", {"xml:lang": "en-US", "xmlns": SCHEMAS.CORE,}, unit=self.unit,
73 | )
74 |
75 | # Add meta data
76 | ET.SubElement(
77 | model, "metadata", name="Application"
78 | ).text = "CadQuery 3MF Exporter"
79 | ET.SubElement(
80 | model, "metadata", name="CreationDate"
81 | ).text = datetime.now().isoformat()
82 |
83 | resources = ET.SubElement(model, "resources")
84 |
85 | # Add all meshes to resources
86 | for i, tessellation in enumerate(self.tessellations):
87 | self._add_mesh(resources, str(i), tessellation)
88 |
89 | # Create a component of all meshes
90 | comp_object = ET.SubElement(
91 | resources,
92 | "object",
93 | id=str(no_meshes),
94 | name=f"CadQuery Component",
95 | type="model",
96 | )
97 | components = ET.SubElement(comp_object, "components")
98 |
99 | # Add all meshes to the component
100 | for i in range(no_meshes):
101 | ET.SubElement(
102 | components, "component", objectid=str(i),
103 | )
104 |
105 | # Add the component to the build
106 | build = ET.SubElement(model, "build")
107 | ET.SubElement(build, "item", objectid=str(no_meshes))
108 |
109 | return ET.tostring(model, xml_declaration=True, encoding="utf-8")
110 |
111 | def _add_mesh(
112 | self,
113 | to: ET.Element,
114 | id: str,
115 | tessellation: Tuple[List[Vector], List[Tuple[int, int, int]]],
116 | ):
117 | object = ET.SubElement(
118 | to, "object", id=id, name=f"CadQuery Shape {id}", type="model"
119 | )
120 | mesh = ET.SubElement(object, "mesh")
121 |
122 | # add vertices
123 | vertices = ET.SubElement(mesh, "vertices")
124 | for v in tessellation[0]:
125 | ET.SubElement(vertices, "vertex", x=str(v.x), y=str(v.y), z=str(v.z))
126 |
127 | # add triangles
128 | volume = ET.SubElement(mesh, "triangles")
129 | for t in tessellation[1]:
130 | ET.SubElement(volume, "triangle", v1=str(t[0]), v2=str(t[1]), v3=str(t[2]))
131 |
132 | def _write_content_types(self) -> str:
133 |
134 | root = ET.Element("Types")
135 | root.set("xmlns", SCHEMAS.CONTENT_TYPES)
136 | ET.SubElement(
137 | root,
138 | "Override",
139 | PartName="/3D/3dmodel.model",
140 | ContentType=CONTENT_TYPES.MODEL,
141 | )
142 | ET.SubElement(
143 | root,
144 | "Override",
145 | PartName="/_rels/.rels",
146 | ContentType=CONTENT_TYPES.RELATION,
147 | )
148 |
149 | return ET.tostring(root, xml_declaration=True, encoding="utf-8")
150 |
151 | def _write_relationships(self) -> str:
152 |
153 | root = ET.Element("Relationships")
154 | root.set("xmlns", SCHEMAS.RELATION)
155 | ET.SubElement(
156 | root,
157 | "Relationship",
158 | Target="/3D/3dmodel.model",
159 | Id="rel-1",
160 | Type=SCHEMAS.MODEL,
161 | TargetMode="Internal",
162 | )
163 |
164 | return ET.tostring(root, xml_declaration=True, encoding="utf-8")
165 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/exporters/vtk.py:
--------------------------------------------------------------------------------
1 | from vtkmodules.vtkIOXML import vtkXMLPolyDataWriter
2 | from ..shapes import Shape
3 |
4 |
5 | def exportVTP(
6 | shape: Shape, fname: str, tolerance: float = 0.1, angularTolerance: float = 0.1
7 | ):
8 |
9 | writer = vtkXMLPolyDataWriter()
10 | writer.SetFileName(fname)
11 | writer.SetInputData(shape.toVtkPolyData(tolerance, angularTolerance))
12 | writer.Write()
13 |
14 |
15 | def toString(
16 | shape: Shape, tolerance: float = 1e-3, angularTolerance: float = 0.1
17 | ) -> str:
18 |
19 | writer = vtkXMLPolyDataWriter()
20 | writer.SetWriteToOutputString(True)
21 | writer.SetInputData(shape.toVtkPolyData(tolerance, angularTolerance, True))
22 | writer.Write()
23 |
24 | return writer.GetOutputString()
25 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/importers/__init__.py:
--------------------------------------------------------------------------------
1 | from math import pi
2 | from typing import List, Literal
3 |
4 | import OCP.IFSelect
5 | from OCP.STEPControl import STEPControl_Reader
6 |
7 | from ... import cq
8 | from ..shapes import Shape
9 | from .dxf import _importDXF
10 |
11 | RAD2DEG = 360.0 / (2 * pi)
12 |
13 |
14 | class ImportTypes:
15 | STEP = "STEP"
16 | DXF = "DXF"
17 | BREP = "BREP"
18 | BIN = "BIN"
19 |
20 |
21 | class UNITS:
22 | MM = "mm"
23 | IN = "in"
24 |
25 |
26 | def importShape(
27 | importType: Literal["STEP", "DXF", "BREP", "BIN"], fileName: str, *args, **kwargs
28 | ) -> "cq.Workplane":
29 | """
30 | Imports a file based on the type (STEP, STL, etc)
31 |
32 | :param importType: The type of file that we're importing
33 | :param fileName: The name of the file that we're importing
34 | """
35 |
36 | # Check to see what type of file we're working with
37 | if importType == ImportTypes.STEP:
38 | return importStep(fileName)
39 | elif importType == ImportTypes.DXF:
40 | return importDXF(fileName, *args, **kwargs)
41 | elif importType == ImportTypes.BREP:
42 | return importBrep(fileName)
43 | elif importType == ImportTypes.BIN:
44 | return importBin(fileName)
45 | else:
46 | raise RuntimeError("Unsupported import type: {!r}".format(importType))
47 |
48 |
49 | def importBrep(fileName: str) -> "cq.Workplane":
50 | """
51 | Loads the BREP file as a single shape into a cadquery Workplane.
52 |
53 | :param fileName: The path and name of the BREP file to be imported
54 |
55 | """
56 | shape = Shape.importBrep(fileName)
57 |
58 | # We know a single shape is returned. Sending it as a list prevents
59 | # newObject from decomposing the part into its constituent parts. If the
60 | # shape is a compound, it will be stored as a compound on the workplane. In
61 | # some cases it may be desirable for the compound to be broken into its
62 | # constituent solids. To do this, use list(shape) or shape.Solids().
63 | return cq.Workplane("XY").newObject([shape])
64 |
65 |
66 | def importBin(fileName: str) -> "cq.Workplane":
67 | """
68 | Loads the binary BREP file as a single shape into a cadquery Workplane.
69 |
70 | :param fileName: The path and name of the BREP file to be imported
71 |
72 | """
73 | shape = Shape.importBin(fileName)
74 |
75 | return cq.Workplane("XY").newObject([shape])
76 |
77 |
78 | # Loads a STEP file into a CQ.Workplane object
79 | def importStep(fileName: str) -> "cq.Workplane":
80 | """
81 | Accepts a file name and loads the STEP file into a cadquery Workplane
82 |
83 | :param fileName: The path and name of the STEP file to be imported
84 | """
85 |
86 | # Now read and return the shape
87 | reader = STEPControl_Reader()
88 | readStatus = reader.ReadFile(fileName)
89 | if readStatus != OCP.IFSelect.IFSelect_RetDone:
90 | raise ValueError("STEP File could not be loaded")
91 | for i in range(reader.NbRootsForTransfer()):
92 | reader.TransferRoot(i + 1)
93 |
94 | occ_shapes = []
95 | for i in range(reader.NbShapes()):
96 | occ_shapes.append(reader.Shape(i + 1))
97 |
98 | # Make sure that we extract all the solids
99 | solids = []
100 | for shape in occ_shapes:
101 | solids.append(Shape.cast(shape))
102 |
103 | return cq.Workplane("XY").newObject(solids)
104 |
105 |
106 | def importDXF(
107 | filename: str, tol: float = 1e-6, exclude: List[str] = [], include: List[str] = []
108 | ) -> "cq.Workplane":
109 | """
110 | Loads a DXF file into a Workplane.
111 |
112 | All layers are imported by default. Provide a layer include or exclude list
113 | to select layers. Layer names are handled as case-insensitive.
114 |
115 | :param filename: The path and name of the DXF file to be imported
116 | :param tol: The tolerance used for merging edges into wires
117 | :param exclude: a list of layer names not to import
118 | :param include: a list of layer names to import
119 | """
120 |
121 | faces = _importDXF(filename, tol, exclude, include)
122 |
123 | return cq.Workplane("XY").newObject(faces)
124 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/importers/dxf.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 | from math import pi
3 | from typing import List
4 |
5 | from ... import cq
6 | from ..geom import Vector
7 | from ..shapes import Shape, Edge, Face, sortWiresByBuildOrder
8 |
9 | import ezdxf
10 |
11 | from OCP.ShapeAnalysis import ShapeAnalysis_FreeBounds
12 | from OCP.TopTools import TopTools_HSequenceOfShape
13 | from OCP.gp import gp_Pnt
14 | from OCP.Geom import Geom_BSplineCurve
15 | from OCP.TColgp import TColgp_Array1OfPnt
16 | from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger
17 | from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
18 |
19 |
20 | RAD2DEG = 360.0 / (2 * pi)
21 |
22 |
23 | def _dxf_line(el):
24 |
25 | try:
26 | return (Edge.makeLine(Vector(el.dxf.start.xyz), Vector(el.dxf.end.xyz)),)
27 | except Exception:
28 | return ()
29 |
30 |
31 | def _dxf_circle(el):
32 |
33 | try:
34 | return (Edge.makeCircle(el.dxf.radius, Vector(el.dxf.center.xyz)),)
35 | except Exception:
36 | return ()
37 |
38 |
39 | def _dxf_arc(el):
40 |
41 | try:
42 | return (
43 | Edge.makeCircle(
44 | el.dxf.radius,
45 | Vector(el.dxf.center.xyz),
46 | angle1=el.dxf.start_angle,
47 | angle2=el.dxf.end_angle,
48 | ),
49 | )
50 | except Exception:
51 | return ()
52 |
53 |
54 | def _dxf_polyline(el):
55 |
56 | rv = (DXF_CONVERTERS[e.dxf.dxftype](e) for e in el.virtual_entities())
57 |
58 | return (e[0] for e in rv if e)
59 |
60 |
61 | def _dxf_spline(el):
62 |
63 | try:
64 | degree = el.dxf.degree
65 | periodic = el.closed
66 | rational = False
67 |
68 | knots_unique = OrderedDict()
69 | for k in el.knots:
70 | if k in knots_unique:
71 | knots_unique[k] += 1
72 | else:
73 | knots_unique[k] = 1
74 |
75 | # assmble knots
76 | knots = TColStd_Array1OfReal(1, len(knots_unique))
77 | multiplicities = TColStd_Array1OfInteger(1, len(knots_unique))
78 | for i, (k, m) in enumerate(knots_unique.items()):
79 | knots.SetValue(i + 1, k)
80 | multiplicities.SetValue(i + 1, m)
81 |
82 | # assemble weights if present:
83 | if el.weights:
84 | rational = True
85 |
86 | weights = TColStd_Array1OfReal(1, len(el.weights))
87 | for i, w in enumerate(el.weights):
88 | weights.SetValue(i + 1, w)
89 |
90 | # assemble control points
91 | pts = TColgp_Array1OfPnt(1, len(el.control_points))
92 | for i, p in enumerate(el.control_points):
93 | pts.SetValue(i + 1, gp_Pnt(*p))
94 |
95 | if rational:
96 | spline = Geom_BSplineCurve(
97 | pts, weights, knots, multiplicities, degree, periodic
98 | )
99 | else:
100 | spline = Geom_BSplineCurve(pts, knots, multiplicities, degree, periodic)
101 |
102 | return (Edge(BRepBuilderAPI_MakeEdge(spline).Edge()),)
103 |
104 | except Exception:
105 | return ()
106 |
107 |
108 | def _dxf_ellipse(el):
109 |
110 | try:
111 |
112 | return (
113 | Edge.makeEllipse(
114 | el.dxf.major_axis.magnitude,
115 | el.minor_axis.magnitude,
116 | pnt=Vector(el.dxf.center.xyz),
117 | dir=el.dxf.extrusion.xyz,
118 | xdir=Vector(el.dxf.major_axis.xyz),
119 | angle1=el.dxf.start_param * RAD2DEG,
120 | angle2=el.dxf.end_param * RAD2DEG,
121 | ),
122 | )
123 | except Exception:
124 | return ()
125 |
126 |
127 | DXF_CONVERTERS = {
128 | "LINE": _dxf_line,
129 | "CIRCLE": _dxf_circle,
130 | "ARC": _dxf_arc,
131 | "POLYLINE": _dxf_polyline,
132 | "LWPOLYLINE": _dxf_polyline,
133 | "SPLINE": _dxf_spline,
134 | "ELLIPSE": _dxf_ellipse,
135 | }
136 |
137 |
138 | def _dxf_convert(elements, tol):
139 |
140 | rv = []
141 | edges = []
142 |
143 | for el in elements:
144 | conv = DXF_CONVERTERS.get(el.dxf.dxftype)
145 | if conv:
146 | edges.extend(conv(el))
147 |
148 | if edges:
149 | edges_in = TopTools_HSequenceOfShape()
150 | wires_out = TopTools_HSequenceOfShape()
151 |
152 | for e in edges:
153 | edges_in.Append(e.wrapped)
154 | ShapeAnalysis_FreeBounds.ConnectEdgesToWires_s(edges_in, tol, False, wires_out)
155 |
156 | rv = [Shape.cast(el) for el in wires_out]
157 |
158 | return rv
159 |
160 |
161 | def _importDXF(
162 | filename: str, tol: float = 1e-6, exclude: List[str] = [], include: List[str] = [],
163 | ) -> List[Face]:
164 | """
165 | Loads a DXF file into a list of faces.
166 |
167 | :param fileName: The path and name of the DXF file to be imported
168 | :param tol: The tolerance used for merging edges into wires
169 | :param exclude: a list of layer names not to import
170 | :param include: a list of layer names to import
171 | """
172 |
173 | if exclude and include:
174 | raise ValueError("you may specify either 'include' or 'exclude' but not both")
175 |
176 | dxf = ezdxf.readfile(filename)
177 | faces = []
178 |
179 | layers = dxf.modelspace().groupby(dxfattrib="layer")
180 |
181 | # normalize layer names to conform the DXF spec
182 | names = set([name.lower() for name in layers.keys()])
183 |
184 | if include:
185 | selected = names & set([name.lower() for name in include])
186 | elif exclude:
187 | selected = names - set([name.lower() for name in exclude])
188 | else:
189 | selected = names
190 |
191 | if not selected:
192 | raise ValueError("no DXF layers selected")
193 |
194 | for name, layer in layers.items():
195 | if name.lower() in selected:
196 | res = _dxf_convert(layers[name], tol)
197 | wire_sets = sortWiresByBuildOrder(res)
198 | for wire_set in wire_sets:
199 | faces.append(Face.makeFromWires(wire_set[0], wire_set[1:]))
200 |
201 | return faces
202 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/jupyter_tools.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any, List
2 | from json import dumps
3 |
4 | from IPython.display import Javascript
5 |
6 |
7 | from .exporters.vtk import toString
8 | from .shapes import Shape
9 | from ..assembly import Assembly
10 | from .assembly import toJSON
11 | from ..vis import DEFAULT_COLOR
12 |
13 | TEMPLATE_RENDER = """
14 |
15 | function render(data, parent_element, ratio){{
16 |
17 | // Initial setup
18 | const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
19 | const renderer = vtk.Rendering.Core.vtkRenderer.newInstance({{ background: [1, 1, 1 ] }});
20 | renderWindow.addRenderer(renderer);
21 |
22 | // iterate over all children children
23 | for (var el of data){{
24 | var trans = el.position;
25 | var rot = el.orientation;
26 | var rgba = el.color;
27 | var shape = el.shape;
28 |
29 | // load the inline data
30 | var reader = vtk.IO.XML.vtkXMLPolyDataReader.newInstance();
31 | const textEncoder = new TextEncoder();
32 | reader.parseAsArrayBuffer(textEncoder.encode(shape));
33 |
34 | // setup actor,mapper and add
35 | const mapper = vtk.Rendering.Core.vtkMapper.newInstance();
36 | mapper.setInputConnection(reader.getOutputPort());
37 | mapper.setResolveCoincidentTopologyToPolygonOffset();
38 | mapper.setResolveCoincidentTopologyPolygonOffsetParameters(0.9,20);
39 |
40 | const actor = vtk.Rendering.Core.vtkActor.newInstance();
41 | actor.setMapper(mapper);
42 |
43 | // set color and position
44 | actor.getProperty().setColor(rgba.slice(0,3));
45 | actor.getProperty().setOpacity(rgba[3]);
46 |
47 | actor.rotateZ(rot[2]*180/Math.PI);
48 | actor.rotateY(rot[1]*180/Math.PI);
49 | actor.rotateX(rot[0]*180/Math.PI);
50 |
51 | actor.setPosition(trans);
52 |
53 | renderer.addActor(actor);
54 |
55 | }};
56 |
57 | renderer.resetCamera();
58 |
59 | const openglRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
60 | renderWindow.addView(openglRenderWindow);
61 |
62 | // Add output to the "parent element"
63 | var container;
64 | var dims;
65 |
66 | if(typeof(parent_element.appendChild) !== "undefined"){{
67 | container = document.createElement("div");
68 | parent_element.appendChild(container);
69 | dims = parent_element.getBoundingClientRect();
70 | }}else{{
71 | container = parent_element.append("
").children("div:last-child").get(0);
72 | dims = parent_element.get(0).getBoundingClientRect();
73 | }};
74 |
75 | openglRenderWindow.setContainer(container);
76 |
77 | // handle size
78 | if (ratio){{
79 | openglRenderWindow.setSize(dims.width, dims.width*ratio);
80 | }}else{{
81 | openglRenderWindow.setSize(dims.width, dims.height);
82 | }};
83 |
84 | // Interaction setup
85 | const interact_style = vtk.Interaction.Style.vtkInteractorStyleManipulator.newInstance();
86 |
87 | const manips = {{
88 | rot: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRotateManipulator.newInstance(),
89 | pan: vtk.Interaction.Manipulators.vtkMouseCameraTrackballPanManipulator.newInstance(),
90 | zoom1: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
91 | zoom2: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
92 | roll: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRollManipulator.newInstance(),
93 | }};
94 |
95 | manips.zoom1.setControl(true);
96 | manips.zoom2.setScrollEnabled(true);
97 | manips.roll.setShift(true);
98 | manips.pan.setButton(2);
99 |
100 | for (var k in manips){{
101 | interact_style.addMouseManipulator(manips[k]);
102 | }};
103 |
104 | const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
105 | interactor.setView(openglRenderWindow);
106 | interactor.initialize();
107 | interactor.bindEvents(container);
108 | interactor.setInteractorStyle(interact_style);
109 |
110 | // Orientation marker
111 |
112 | const axes = vtk.Rendering.Core.vtkAnnotatedCubeActor.newInstance();
113 | axes.setXPlusFaceProperty({{text: '+X'}});
114 | axes.setXMinusFaceProperty({{text: '-X'}});
115 | axes.setYPlusFaceProperty({{text: '+Y'}});
116 | axes.setYMinusFaceProperty({{text: '-Y'}});
117 | axes.setZPlusFaceProperty({{text: '+Z'}});
118 | axes.setZMinusFaceProperty({{text: '-Z'}});
119 |
120 | const orientationWidget = vtk.Interaction.Widgets.vtkOrientationMarkerWidget.newInstance({{
121 | actor: axes,
122 | interactor: interactor }});
123 | orientationWidget.setEnabled(true);
124 | orientationWidget.setViewportCorner(vtk.Interaction.Widgets.vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT);
125 | orientationWidget.setViewportSize(0.2);
126 |
127 | }};
128 | """
129 |
130 | TEMPLATE = (
131 | TEMPLATE_RENDER
132 | + """
133 |
134 | function load_and_render(parent_element)
135 | {{
136 | new Promise(
137 | function(resolve, reject)
138 | {{
139 | if (typeof(require) !== "undefined" ){{
140 | require.config({{
141 | "paths": {{"vtk": "https://unpkg.com/vtk"}},
142 | }});
143 | require(["vtk"], resolve, reject);
144 | }} else if ( typeof(vtk) === "undefined" ){{
145 | var script = document.createElement("script");
146 | script.onload = resolve;
147 | script.onerror = reject;
148 | script.src = "https://unpkg.com/vtk.js";
149 | document.head.appendChild(script);
150 | }} else {{ resolve() }};
151 | }}
152 | ).then(() => {{
153 | var data = {data};
154 | render(data, parent_element, {ratio});
155 | }});
156 | }}
157 |
158 | load_and_render({element});
159 | """
160 | )
161 |
162 |
163 | def display(shape):
164 |
165 | payload: List[Dict[str, Any]] = []
166 |
167 | if isinstance(shape, Shape):
168 | payload.append(
169 | dict(
170 | shape=toString(shape),
171 | color=DEFAULT_COLOR,
172 | position=[0, 0, 0],
173 | orientation=[0, 0, 0],
174 | )
175 | )
176 | elif isinstance(shape, Assembly):
177 | payload = toJSON(shape)
178 | else:
179 | raise ValueError(f"Type {type(shape)} is not supported")
180 |
181 | code = TEMPLATE.format(data=dumps(payload), element="element", ratio=0.5)
182 |
183 | return Javascript(code)
184 |
--------------------------------------------------------------------------------
/cadquery/occ_impl/shape_protocols.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Literal, Sequence
2 | from typing_extensions import Protocol, Self
3 |
4 | from .geom import Vector, BoundBox
5 |
6 | import OCP.GeomAbs as ga
7 |
8 | geom_LUT_FACE = {
9 | ga.GeomAbs_Plane: "PLANE",
10 | ga.GeomAbs_Cylinder: "CYLINDER",
11 | ga.GeomAbs_Cone: "CONE",
12 | ga.GeomAbs_Sphere: "SPHERE",
13 | ga.GeomAbs_Torus: "TORUS",
14 | ga.GeomAbs_BezierSurface: "BEZIER",
15 | ga.GeomAbs_BSplineSurface: "BSPLINE",
16 | ga.GeomAbs_SurfaceOfRevolution: "REVOLUTION",
17 | ga.GeomAbs_SurfaceOfExtrusion: "EXTRUSION",
18 | ga.GeomAbs_OffsetSurface: "OFFSET",
19 | ga.GeomAbs_OtherSurface: "OTHER",
20 | }
21 |
22 | geom_LUT_EDGE = {
23 | ga.GeomAbs_Line: "LINE",
24 | ga.GeomAbs_Circle: "CIRCLE",
25 | ga.GeomAbs_Ellipse: "ELLIPSE",
26 | ga.GeomAbs_Hyperbola: "HYPERBOLA",
27 | ga.GeomAbs_Parabola: "PARABOLA",
28 | ga.GeomAbs_BezierCurve: "BEZIER",
29 | ga.GeomAbs_BSplineCurve: "BSPLINE",
30 | ga.GeomAbs_OffsetCurve: "OFFSET",
31 | ga.GeomAbs_OtherCurve: "OTHER",
32 | }
33 |
34 | Shapes = Literal[
35 | "Vertex", "Edge", "Wire", "Face", "Shell", "Solid", "CompSolid", "Compound"
36 | ]
37 |
38 | Geoms = Literal[
39 | "Vertex",
40 | "Wire",
41 | "Shell",
42 | "Solid",
43 | "Compound",
44 | "PLANE",
45 | "CYLINDER",
46 | "CONE",
47 | "SPHERE",
48 | "TORUS",
49 | "BEZIER",
50 | "BSPLINE",
51 | "REVOLUTION",
52 | "EXTRUSION",
53 | "OFFSET",
54 | "OTHER",
55 | "LINE",
56 | "CIRCLE",
57 | "ELLIPSE",
58 | "HYPERBOLA",
59 | "PARABOLA",
60 | ]
61 |
62 |
63 | class ShapeProtocol(Protocol):
64 | def ShapeType(self) -> Shapes:
65 | ...
66 |
67 | def geomType(self) -> Geoms:
68 | ...
69 |
70 | def Center(self) -> Vector:
71 | ...
72 |
73 | def Area(self) -> float:
74 | ...
75 |
76 | def BoundingBox(self, tolerance: Optional[float] = None) -> BoundBox:
77 | ...
78 |
79 |
80 | class Shape1DProtocol(ShapeProtocol, Protocol):
81 | def tangentAt(
82 | self, p: float = 0.5, mode: Literal["length", "parameter"] = "length"
83 | ) -> Vector:
84 | ...
85 |
86 | def radius(self) -> float:
87 | ...
88 |
89 | def Length(self) -> float:
90 | ...
91 |
92 |
93 | class FaceProtocol(ShapeProtocol, Protocol):
94 | def normalAt(self, v: Optional[Vector] = None) -> Vector:
95 | ...
96 |
97 | @classmethod
98 | def makeFromWires(cls, w: ShapeProtocol, ws: Sequence[ShapeProtocol]) -> Self:
99 | ...
100 |
--------------------------------------------------------------------------------
/cadquery/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/cadquery/plugins/__init__.py
--------------------------------------------------------------------------------
/cadquery/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/cadquery/py.typed
--------------------------------------------------------------------------------
/cadquery/types.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | Real = Union[int, float]
4 |
--------------------------------------------------------------------------------
/cadquery/units.py:
--------------------------------------------------------------------------------
1 | from math import pi
2 |
3 | RAD2DEG = 180 / pi
4 | DEG2RAD = pi / 180
5 |
--------------------------------------------------------------------------------
/cadquery/utils.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 | from inspect import signature, isbuiltin
3 | from typing import TypeVar, Callable, cast
4 | from warnings import warn
5 |
6 | from multimethod import multimethod, DispatchError
7 |
8 | TCallable = TypeVar("TCallable", bound=Callable)
9 |
10 |
11 | class deprecate_kwarg:
12 | def __init__(self, name, new_value):
13 |
14 | self.name = name
15 | self.new_value = new_value
16 |
17 | def __call__(self, f: TCallable) -> TCallable:
18 | @wraps(f)
19 | def wrapped(*args, **kwargs):
20 |
21 | f_sig_bound = signature(f).bind(*args, **kwargs)
22 |
23 | if self.name not in f_sig_bound.kwargs:
24 | warn(
25 | f"Default value of {self.name} will change in the next release to {self.new_value}",
26 | FutureWarning,
27 | )
28 |
29 | return f(*args, **kwargs)
30 |
31 | return cast(TCallable, wrapped)
32 |
33 |
34 | class deprecate:
35 | def __call__(self, f):
36 | @wraps(f)
37 | def wrapped(*args, **kwargs):
38 |
39 | warn(f"{f.__name__} will be removed in the next release.", FutureWarning)
40 |
41 | return f(*args, **kwargs)
42 |
43 | return wrapped
44 |
45 |
46 | class cqmultimethod(multimethod):
47 | def __call__(self, *args, **kwargs):
48 |
49 | try:
50 | return super().__call__(*args, **kwargs)
51 | except DispatchError:
52 | return next(iter(self.values()))(*args, **kwargs)
53 |
54 |
55 | class deprecate_kwarg_name:
56 | def __init__(self, name, new_name):
57 |
58 | self.name = name
59 | self.new_name = new_name
60 |
61 | def __call__(self, f: TCallable) -> TCallable:
62 | @wraps(f)
63 | def wrapped(*args, **kwargs):
64 |
65 | if self.name in kwargs:
66 | warn(
67 | f"Kwarg <{self.name}> will be removed. Please use <{self.new_name}>",
68 | FutureWarning,
69 | )
70 |
71 | return f(*args, **kwargs)
72 |
73 | return cast(TCallable, wrapped)
74 |
75 |
76 | def get_arity(f: TCallable) -> int:
77 |
78 | if isbuiltin(f):
79 | rv = 0 # assume 0 arity for builtins; they cannot be introspected
80 | else:
81 | # NB: this is not understood by mypy
82 | n_defaults = len(f.__defaults__) if f.__defaults__ else 0
83 | rv = f.__code__.co_argcount - n_defaults
84 |
85 | return rv
86 |
--------------------------------------------------------------------------------
/conda/meta.yaml:
--------------------------------------------------------------------------------
1 | package:
2 | name: cadquery
3 | version: {{ environ.get('PACKAGE_VERSION') }}
4 |
5 | source:
6 | path: ..
7 |
8 | build:
9 | string: {{ GIT_DESCRIBE_TAG }}_{{ GIT_BUILD_STR }}
10 | noarch: python
11 | script: python setup.py install --single-version-externally-managed --record=record.txt
12 |
13 | requirements:
14 | build:
15 | - python >=3.9
16 | - setuptools
17 | run:
18 | - python >=3.9
19 | - ocp=7.8.1
20 | - vtk=*=qt*
21 | - pyparsing >=2.1.9
22 | - ezdxf>=1.3.0
23 | - ipython
24 | - typing_extensions
25 | - nlopt
26 | - multimethod >=1.11,<2.0
27 | - casadi
28 | - typish
29 |
30 | test:
31 | requires:
32 | - pytest
33 | - docutils
34 | - path
35 | source_files:
36 | - tests/
37 | commands:
38 | - pytest -v
39 |
40 | about:
41 | summary: CadQuery - scripted CAD based on OCCT
42 |
--------------------------------------------------------------------------------
/conda/web-installer/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore files generated from templates
2 | construct.yaml
3 | post-install.bat
4 | post-install.sh
5 | build/
6 |
7 |
--------------------------------------------------------------------------------
/conda/web-installer/Readme.md:
--------------------------------------------------------------------------------
1 | # Readme
2 |
3 | This is a script for generating a self installer for cadquery using conda constructor
4 |
5 | * https://github.com/conda/constructor
6 |
7 | The installer
8 |
9 | * Installs an instance of miniconda
10 | * Adds cadquery / conda-forge to the default channels
11 | * Runs a post install script to download install cadquery.
12 |
13 | We need to install cadquery post install due to the file sizes involved with the install of opencascade (around 2Gb)
14 | This installer will not add the installed directory to the Path or try to override the default python (with the default options selected).
15 |
16 | ## Running the constructor
17 |
18 | To run
19 | ```
20 | conda install jinja2 constructor
21 | python build.py
22 | ```
23 |
24 | For Example
25 | ```
26 | build.py 2.2 master
27 | ```
28 |
29 | ## Activation
30 |
31 | To Activate the environment
32 | ```
33 | # Under Windows
34 | condabin\activate.bat
35 |
36 | # Under Linux / MacOS
37 | source ~/cadquery/bin/activate
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/conda/web-installer/build.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import subprocess
4 | from jinja2 import Environment, select_autoescape, FileSystemLoader
5 |
6 |
7 | def usage():
8 | print("Web installer build script")
9 | print("build.py ")
10 | print(
11 | "The installer verison is the version number used within the conda constructor script"
12 | )
13 | print("The tag verison is the version of cadquery that will be pulled from github")
14 |
15 |
16 | def write_file(destpath, contents):
17 | with open(destpath, "w") as destfile:
18 | destfile.write(contents)
19 |
20 |
21 | def run_cmd(cmdarray, workingdir, captureout=False):
22 | stdout = stderr = None
23 | if captureout:
24 | stdout = stderr = subprocess.PIPE
25 | proc = subprocess.Popen(
26 | cmdarray, cwd=workingdir, stdout=stdout, stderr=stderr, universal_newlines=True
27 | )
28 | proc_out, proc_err = proc.communicate()
29 | if proc.returncode != 0:
30 | raise RuntimeError("Failure to run command")
31 | return stdout, stderr
32 |
33 |
34 | def generate_templates(installer_version, tag_version):
35 | print("Generating Scripts")
36 | env = Environment(loader=FileSystemLoader("."), autoescape=select_autoescape())
37 |
38 | template = env.get_template("construct.yaml.jinja2")
39 | output = template.render(installer_version=installer_version)
40 | write_file("construct.yaml", output)
41 |
42 | template = env.get_template("post-install.bat.jinja2")
43 | output = template.render(tag_version=tag_version)
44 | write_file("post-install.bat", output)
45 |
46 | template = env.get_template("post-install.sh.jinja2")
47 | output = template.render(tag_version=tag_version)
48 | write_file("post-install.sh", output)
49 |
50 |
51 | def run_constructor():
52 | print("Running constructor")
53 | scriptdir = os.path.dirname(os.path.realpath(__file__))
54 | builddir = os.path.join(scriptdir, "build")
55 | if not os.path.exists(builddir):
56 | os.makedirs(builddir)
57 | run_cmd(["constructor", scriptdir], builddir)
58 |
59 |
60 | def main():
61 | if len(sys.argv) < 2:
62 | usage()
63 | return
64 | installer_version = sys.argv[1]
65 | tag_version = sys.argv[2]
66 | generate_templates(installer_version, tag_version)
67 | run_constructor()
68 |
69 |
70 | main()
71 |
--------------------------------------------------------------------------------
/conda/web-installer/construct.yaml.jinja2:
--------------------------------------------------------------------------------
1 | name: cadquery
2 | version: {{ installer_version }}
3 | installer_type: all
4 |
5 | license_file: ../../LICENSE
6 |
7 | # Don't add to the system Path
8 | initialize_by_default: false
9 | # Don't set as the default python
10 | register_python_default: false
11 |
12 | channels:
13 | - http://repo.anaconda.com/pkgs/main/
14 | - conda-forge
15 | - cadquery
16 |
17 | write_condarc: true
18 | conda_default_channels:
19 | - conda-forge
20 | - cadquery
21 |
22 | specs:
23 | - python 3.10*
24 | - conda
25 | # Occt is too big to bundle so install during post_install instead
26 | # - cadquery
27 |
28 | post_install: post-install.sh # [unix]
29 | post_install: post-install.bat # [win]
30 |
--------------------------------------------------------------------------------
/conda/web-installer/post-install.bat.jinja2:
--------------------------------------------------------------------------------
1 | echo Entering Conda Environment
2 | call %PREFIX%\condabin\activate.bat
3 |
4 | echo Installing CadQuery
5 | call conda install -c conda-forge -c cadquery -y cadquery={{ tag_version }}
6 |
7 | echo Cleaning Packages
8 | call conda clean -a -y
9 | call conda deactivate
10 |
--------------------------------------------------------------------------------
/conda/web-installer/post-install.sh.jinja2:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo Entering Conda Environment
4 | source $PREFIX/bin/activate
5 |
6 | echo Installing CadQuery
7 | conda install -c conda-forge -c cadquery -y cadquery={{ tag_version }}
8 |
9 | echo Cleaning Packages
10 | conda clean -a -y
11 |
12 | echo To activate run
13 | echo source ~/cadquery/bin/activate
14 |
--------------------------------------------------------------------------------
/conda_build.bat:
--------------------------------------------------------------------------------
1 | conda build -c cadquery -c conda-forge --output-folder . conda/meta.yaml
2 |
--------------------------------------------------------------------------------
/conda_build.sh:
--------------------------------------------------------------------------------
1 | conda build conda/meta.yaml -c cadquery -c conda-forge --croot /tmp/cbld
2 |
--------------------------------------------------------------------------------
/conda_build_config.yaml:
--------------------------------------------------------------------------------
1 | python:
2 | - 3.6
--------------------------------------------------------------------------------
/doc/README:
--------------------------------------------------------------------------------
1 | This documentation should be generated with sphinxdoc.
2 | see ../build-docs.sh
3 |
--------------------------------------------------------------------------------
/doc/_static/ParametricPulley.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/ParametricPulley.PNG
--------------------------------------------------------------------------------
/doc/_static/assy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/assy.png
--------------------------------------------------------------------------------
/doc/_static/block.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/block.png
--------------------------------------------------------------------------------
/doc/_static/ctrl_pts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/ctrl_pts.png
--------------------------------------------------------------------------------
/doc/_static/door_assy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/door_assy.png
--------------------------------------------------------------------------------
/doc/_static/door_assy_freecad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/door_assy_freecad.png
--------------------------------------------------------------------------------
/doc/_static/hyOzd-cablefix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/hyOzd-cablefix.png
--------------------------------------------------------------------------------
/doc/_static/hyOzd-finished.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/hyOzd-finished.jpg
--------------------------------------------------------------------------------
/doc/_static/importexport/box_custom_options.svg:
--------------------------------------------------------------------------------
1 |
2 |
34 |
--------------------------------------------------------------------------------
/doc/_static/importexport/box_custom_options_perspective.svg:
--------------------------------------------------------------------------------
1 |
2 |
34 |
--------------------------------------------------------------------------------
/doc/_static/importexport/box_default_options.svg:
--------------------------------------------------------------------------------
1 |
2 |
47 |
--------------------------------------------------------------------------------
/doc/_static/logo/README.md:
--------------------------------------------------------------------------------
1 | ## CadQuery Logo
2 |
3 | This logo uses the Roboto bold font (included), the Inkscape font size is 56, and the color is #2980b9
4 |
--------------------------------------------------------------------------------
/doc/_static/logo/Roboto_Font/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/logo/Roboto_Font/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/doc/_static/logo/cadquery_logo_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/doc/_static/logo/cadquery_logo_dark_inkscape.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/doc/_static/logo/cadquery_logo_light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/doc/_static/logo/cadquery_logo_light_inkscape.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
79 |
--------------------------------------------------------------------------------
/doc/_static/new_badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/new_badge.png
--------------------------------------------------------------------------------
/doc/_static/parametric-cup-screencap.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/parametric-cup-screencap.PNG
--------------------------------------------------------------------------------
/doc/_static/parametric-pillowblock-screencap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/parametric-pillowblock-screencap.png
--------------------------------------------------------------------------------
/doc/_static/pillowblock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/pillowblock.png
--------------------------------------------------------------------------------
/doc/_static/quickstart-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart-1.png
--------------------------------------------------------------------------------
/doc/_static/quickstart-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart-2.png
--------------------------------------------------------------------------------
/doc/_static/quickstart-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart-3.png
--------------------------------------------------------------------------------
/doc/_static/quickstart-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart-4.png
--------------------------------------------------------------------------------
/doc/_static/quickstart-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart-5.png
--------------------------------------------------------------------------------
/doc/_static/quickstart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/000.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/001.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/002.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/003.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/004.png
--------------------------------------------------------------------------------
/doc/_static/quickstart/005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/quickstart/005.png
--------------------------------------------------------------------------------
/doc/_static/show.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show.PNG
--------------------------------------------------------------------------------
/doc/_static/show_camera_position.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show_camera_position.png
--------------------------------------------------------------------------------
/doc/_static/show_demo.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show_demo.PNG
--------------------------------------------------------------------------------
/doc/_static/show_jupyter.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show_jupyter.PNG
--------------------------------------------------------------------------------
/doc/_static/show_styling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show_styling.png
--------------------------------------------------------------------------------
/doc/_static/show_vtk.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/show_vtk.PNG
--------------------------------------------------------------------------------
/doc/_static/simple_assy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/simple_assy.png
--------------------------------------------------------------------------------
/doc/_static/simpleblock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/doc/_static/simpleblock.png
--------------------------------------------------------------------------------
/doc/_static/tables.css:
--------------------------------------------------------------------------------
1 | .wy-table-responsive table td, .wy-table-responsive table th {
2 | white-space: normal;
3 | }
4 | .wy-table-responsive {
5 | overflow: visible;
6 | }
7 |
--------------------------------------------------------------------------------
/doc/apireference.rst:
--------------------------------------------------------------------------------
1 | .. _apireference:
2 |
3 | ***********************
4 | API Reference
5 | ***********************
6 |
7 | The CadQuery API is made up of 4 main objects:
8 |
9 | * **Sketch** -- Construct 2D sketches
10 | * **Workplane** -- Wraps a topological entity and provides a 2D modelling context.
11 | * **Selector** -- Filter and select things
12 | * **Assembly** -- Combine objects into assemblies.
13 |
14 | This page lists methods of these objects grouped by **functional area**
15 |
16 | .. seealso::
17 | This page lists api methods grouped by functional area.
18 | Use :ref:`classreference` to see methods alphabetically by class.
19 |
20 |
21 | Sketch initialization
22 | ---------------------
23 |
24 | .. currentmodule:: cadquery
25 |
26 | Creating new sketches.
27 |
28 | .. autosummary::
29 | Sketch
30 | Sketch.importDXF
31 | Workplane.sketch
32 | Sketch.finalize
33 | Sketch.copy
34 | Sketch.located
35 | Sketch.moved
36 |
37 | Sketch selection
38 | ----------------
39 |
40 | .. currentmodule:: cadquery
41 |
42 | Selecting, tagging and manipulating elements.
43 |
44 | .. autosummary::
45 | Sketch.tag
46 | Sketch.select
47 | Sketch.reset
48 | Sketch.delete
49 | Sketch.faces
50 | Sketch.edges
51 | Sketch.vertices
52 |
53 | Sketching with faces
54 | --------------------
55 |
56 | .. currentmodule:: cadquery
57 |
58 | Sketching using the face-based API.
59 |
60 | .. autosummary::
61 | Sketch.face
62 | Sketch.rect
63 | Sketch.circle
64 | Sketch.ellipse
65 | Sketch.trapezoid
66 | Sketch.slot
67 | Sketch.regularPolygon
68 | Sketch.polygon
69 | Sketch.rarray
70 | Sketch.parray
71 | Sketch.distribute
72 | Sketch.each
73 | Sketch.push
74 | Sketch.hull
75 | Sketch.offset
76 | Sketch.fillet
77 | Sketch.chamfer
78 | Sketch.clean
79 |
80 | Sketching with edges and constraints
81 | ------------------------------------
82 |
83 | .. currentmodule:: cadquery
84 |
85 | Sketching using the edge-based API.
86 |
87 | .. autosummary::
88 | Sketch.edge
89 | Sketch.segment
90 | Sketch.arc
91 | Sketch.spline
92 | Sketch.close
93 | Sketch.assemble
94 | Sketch.constrain
95 | Sketch.solve
96 |
97 |
98 | Initialization
99 | --------------
100 |
101 | .. currentmodule:: cadquery
102 |
103 | Creating new workplanes and object chains
104 |
105 | .. autosummary::
106 | Workplane
107 |
108 |
109 | .. _2dOperations:
110 |
111 | 2D Operations
112 | -------------
113 |
114 | Creating 2D constructs that can be used to create 3D features.
115 |
116 | All 2D operations require a **Workplane** object to be created.
117 |
118 | .. currentmodule:: cadquery
119 |
120 | .. autosummary::
121 | Workplane.center
122 | Workplane.lineTo
123 | Workplane.line
124 | Workplane.vLine
125 | Workplane.vLineTo
126 | Workplane.hLine
127 | Workplane.hLineTo
128 | Workplane.polarLine
129 | Workplane.polarLineTo
130 | Workplane.moveTo
131 | Workplane.move
132 | Workplane.spline
133 | Workplane.parametricCurve
134 | Workplane.parametricSurface
135 | Workplane.threePointArc
136 | Workplane.sagittaArc
137 | Workplane.radiusArc
138 | Workplane.tangentArcPoint
139 | Workplane.mirrorY
140 | Workplane.mirrorX
141 | Workplane.wire
142 | Workplane.rect
143 | Workplane.circle
144 | Workplane.ellipse
145 | Workplane.ellipseArc
146 | Workplane.polyline
147 | Workplane.close
148 | Workplane.rarray
149 | Workplane.polarArray
150 | Workplane.slot2D
151 | Workplane.offset2D
152 | Workplane.placeSketch
153 |
154 | .. _3doperations:
155 |
156 | 3D Operations
157 | -----------------
158 |
159 | Some 3D operations also require an active 2D workplane, but some do not.
160 |
161 | 3D operations that require a 2D workplane to be active:
162 |
163 | .. autosummary::
164 | Workplane.cboreHole
165 | Workplane.cskHole
166 | Workplane.hole
167 | Workplane.extrude
168 | Workplane.cut
169 | Workplane.cutBlind
170 | Workplane.cutThruAll
171 | Workplane.box
172 | Workplane.sphere
173 | Workplane.wedge
174 | Workplane.cylinder
175 | Workplane.union
176 | Workplane.combine
177 | Workplane.intersect
178 | Workplane.loft
179 | Workplane.sweep
180 | Workplane.twistExtrude
181 | Workplane.revolve
182 | Workplane.text
183 |
184 |
185 | 3D operations that do NOT require a 2D workplane to be active:
186 |
187 | .. autosummary::
188 | Workplane.shell
189 | Workplane.fillet
190 | Workplane.chamfer
191 | Workplane.split
192 | Workplane.rotate
193 | Workplane.rotateAboutCenter
194 | Workplane.translate
195 | Workplane.mirror
196 |
197 | File Management and Export
198 | ---------------------------------
199 |
200 | .. autosummary::
201 | Workplane.toSvg
202 | Workplane.exportSvg
203 |
204 |
205 | .. autosummary::
206 | importers.importStep
207 | importers.importDXF
208 | exporters.export
209 | occ_impl.exporters.dxf.DxfDocument
210 |
211 |
212 | Iteration Methods
213 | ------------------
214 |
215 | Methods that allow iteration over the stack or objects
216 |
217 | .. autosummary::
218 | Workplane.each
219 | Workplane.eachpoint
220 |
221 |
222 | .. _stackMethods:
223 |
224 | Stack and Selector Methods
225 | ------------------------------
226 |
227 | CadQuery methods that operate on the stack
228 |
229 | .. autosummary::
230 | Workplane.all
231 | Workplane.size
232 | Workplane.vals
233 | Workplane.add
234 | Workplane.val
235 | Workplane.first
236 | Workplane.item
237 | Workplane.last
238 | Workplane.end
239 | Workplane.vertices
240 | Workplane.faces
241 | Workplane.edges
242 | Workplane.wires
243 | Workplane.solids
244 | Workplane.shells
245 | Workplane.compounds
246 |
247 | .. _selectors:
248 |
249 | Selectors
250 | ------------------------
251 |
252 | Objects that filter and select CAD objects. Selectors are used to select existing geometry
253 | as a basis for further operations.
254 |
255 | .. currentmodule:: cadquery.selectors
256 | .. autosummary::
257 |
258 | NearestToPointSelector
259 | BoxSelector
260 | BaseDirSelector
261 | ParallelDirSelector
262 | DirectionSelector
263 | DirectionNthSelector
264 | LengthNthSelector
265 | AreaNthSelector
266 | RadiusNthSelector
267 | PerpendicularDirSelector
268 | TypeSelector
269 | DirectionMinMaxSelector
270 | CenterNthSelector
271 | BinarySelector
272 | AndSelector
273 | SumSelector
274 | SubtractSelector
275 | InverseSelector
276 | StringSyntaxSelector
277 |
278 | .. _assembly:
279 |
280 | Assemblies
281 | ----------
282 |
283 | Workplane and Shape objects can be connected together into assemblies
284 |
285 | .. currentmodule:: cadquery
286 | .. autosummary::
287 |
288 | Assembly
289 | Assembly.add
290 | Assembly.save
291 | Assembly.constrain
292 | Assembly.solve
293 | Constraint
294 | Color
295 |
--------------------------------------------------------------------------------
/doc/citing.rst:
--------------------------------------------------------------------------------
1 | Citing
2 | ======
3 |
4 | Please use our Zenodo DOI if you use CadQuery for scientific research: https://doi.org/10.5281/zenodo.3955118.
5 |
--------------------------------------------------------------------------------
/doc/classreference.rst:
--------------------------------------------------------------------------------
1 | .. _classreference:
2 |
3 | *************************
4 | CadQuery Class Summary
5 | *************************
6 |
7 | This page documents all of the methods and functions of the CadQuery classes, organized alphabetically.
8 |
9 | .. seealso::
10 |
11 | For a listing organized by functional area, see the :ref:`apireference`
12 |
13 | .. currentmodule:: cadquery
14 |
15 | Core Classes
16 | ---------------------
17 |
18 | .. autosummary::
19 |
20 | Sketch
21 | Workplane
22 | Assembly
23 | Constraint
24 |
25 | Topological Classes
26 | ----------------------
27 |
28 | .. autosummary::
29 |
30 | Shape
31 | Vertex
32 | Edge
33 | cadquery.occ_impl.shapes.Mixin1D
34 | Wire
35 | Face
36 | Shell
37 | cadquery.occ_impl.shapes.Mixin3D
38 | Solid
39 | Compound
40 |
41 | Geometry Classes
42 | ------------------
43 |
44 | .. autosummary::
45 |
46 | Vector
47 | Matrix
48 | Plane
49 | Location
50 |
51 | Selector Classes
52 | ---------------------
53 |
54 | .. currentmodule:: cadquery.selectors
55 | .. autosummary::
56 |
57 | Selector
58 | NearestToPointSelector
59 | BoxSelector
60 | BaseDirSelector
61 | ParallelDirSelector
62 | DirectionSelector
63 | PerpendicularDirSelector
64 | TypeSelector
65 | RadiusNthSelector
66 | CenterNthSelector
67 | DirectionMinMaxSelector
68 | DirectionNthSelector
69 | LengthNthSelector
70 | AreaNthSelector
71 | BinarySelector
72 | AndSelector
73 | SumSelector
74 | SubtractSelector
75 | InverseSelector
76 | StringSyntaxSelector
77 |
78 |
79 | Class Details
80 | ---------------
81 |
82 | .. automodule:: cadquery
83 | :show-inheritance:
84 | :members:
85 | :special-members:
86 |
87 | .. automodule:: cadquery.occ_impl.shapes
88 | :show-inheritance:
89 | :members:
90 |
91 | .. autoclass:: cadquery.occ_impl.shapes.Mixin1D
92 | :show-inheritance:
93 | :members:
94 |
95 | .. autoclass:: cadquery.occ_impl.shapes.Mixin3D
96 | :show-inheritance:
97 | :members:
98 |
99 | .. automodule:: cadquery.selectors
100 | :show-inheritance:
101 | :members:
102 |
103 | .. automodule:: cadquery.occ_impl.exporters.assembly
104 | :show-inheritance:
105 | :members:
106 |
107 | .. autofunction:: cadquery.occ_impl.assembly.toJSON
108 |
109 | .. autoclass:: cadquery.occ_impl.exporters.dxf.DxfDocument
110 | :members:
111 |
112 | .. automethod:: __init__
113 |
--------------------------------------------------------------------------------
/doc/cqgi.rst:
--------------------------------------------------------------------------------
1 | .. _cqgi:
2 |
3 | The CadQuery Gateway Interface
4 | ====================================
5 |
6 |
7 | CadQuery is first and foremost designed as a library, which can be used as a part of any project.
8 | In this context, there is no need for a standard script format or gateway API.
9 |
10 | Though the embedded use case is the most common, several tools have been created which run
11 | cadquery scripts on behalf of the user, and then render the result of the script visually.
12 |
13 | These execution environments (EE) generally accept a script and user input values for
14 | script parameters, and then display the resulting objects visually to the user.
15 |
16 | Today, three execution environments exist:
17 |
18 | * `CQ-editor `_, which runs scripts
19 | inside of a CadQuery IDE, and displays objects in the display window and includes features like debugging.
20 | * The cq-directive, which is used to execute scripts inside of sphinx-doc,
21 | producing documented examples that include both a script and an SVG representation of the object that results.
22 |
23 | The CQGI is distributed with CadQuery, and standardizes the interface between execution environments and CadQuery scripts.
24 |
25 |
26 | The Script Side
27 | -----------------
28 |
29 | CQGI compliant containers provide an execution environment for scripts. The environment includes:
30 |
31 | * the cadquery library is automatically imported as 'cq'.
32 | * the :py:meth:`cadquery.cqgi.ScriptCallback.show_object()` method is defined that should be used to export a shape to the execution environment
33 | * the :py:meth:`cadquery.cqgi.ScriptCallBack.debug()` method is defined, which can be used by scripts to debug model output during execution.
34 |
35 | Scripts must call show_object at least once. Invoking show_object more than once will send multiple objects to
36 | the container. An error will occur if the script does not return an object using the show_object() method.
37 |
38 | 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::
39 |
40 | base_cube = cq.Workplane("XY").rect(1.0, 1.0).extrude(1.0)
41 | top_of_cube_plane = base_cube.faces(">Z").workplane()
42 | debug(
43 | top_of_cube_plane,
44 | {
45 | "color": "yellow",
46 | },
47 | )
48 | debug(top_of_cube_plane.center, {"color": "blue"})
49 |
50 | circle = top_of_cube_plane.circle(0.5)
51 | debug(circle, {"color": "red"})
52 |
53 | show_object(circle.extrude(1.0))
54 |
55 | Note that importing cadquery is not required.
56 | At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle
57 |
58 | Future enhancements will include several other methods, used to provide more metadata for the execution environment:
59 | * :py:meth:`cadquery.cqgi.ScriptCallback.add_error()`, indicates an error with an input parameter
60 | * :py:meth:`cadquery.cqgi.ScriptCallback.describe_parameter()`, provides extra information about a parameter in the script,
61 |
62 |
63 | The execution environment side
64 | -------------------------------
65 |
66 | CQGI makes it easy to run cadquery scripts in a standard way. To run a script from an execution environment,
67 | run code like this::
68 |
69 | from cadquery import cqgi
70 |
71 | user_script = ...
72 | build_result = cqgi.parse(user_script).build()
73 |
74 | The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object.
75 |
76 | The `metadata`p property of the object contains a `cadquery.cqgi.ScriptMetaData` object, which can be used to discover the
77 | user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the
78 | model parameters. Typically, after collecting new values, the environment will supply them in the build() method.
79 |
80 | This code will return a dictionary of parameter values in the model text SCRIPT::
81 | parameters = cqgi.parse(SCRIPT).metadata.parameters
82 |
83 | The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object,
84 | which has a name, type, and default value.
85 |
86 | 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 ).
87 |
88 | The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the
89 | describe_parameter() method.
90 |
91 |
92 |
93 | Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object,
94 | ,which includes the script execution time, and a success flag.
95 |
96 | If the script was successful, the results property will include a list of results returned by the script,
97 | as well as any debug the script produced
98 |
99 | If the script failed, the exception property contains the exception object.
100 |
101 | If you have a way to get inputs from a user, you can override any of the constants defined in the user script
102 | with new values::
103 |
104 | from cadquery import cqgi
105 |
106 | user_script = ...
107 | build_result = cqgi.parse(user_script).build(
108 | build_parameters={"param": 2}, build_options={}
109 | )
110 |
111 | If a parameter called 'param' is defined in the model, it will be assigned the value 2 before the script runs.
112 | An error will occur if a value is provided that is not defined in the model, or if the value provided cannot
113 | be assigned to a variable with the given name.
114 |
115 | build_options is used to set server-side settings like timeouts, tessellation tolerances, and other details about
116 | how the model should be built.
117 |
118 |
119 | More about script variables
120 | -----------------------------
121 |
122 | CQGI uses the following rules to find input variables for a script:
123 |
124 | * only top-level statements are considered
125 | * only assignments of constant values to a local name are considered.
126 |
127 | For example, in the following script::
128 |
129 | h = 1.0
130 | w = 2.0
131 | foo = "bar"
132 |
133 |
134 | def some_function():
135 | x = 1
136 |
137 | h, w, and foo will be overridable script variables, but x is not.
138 |
139 | You can list the variables defined in the model by using the return value of the parse method::
140 |
141 | model = cqgi.parse(user_script)
142 |
143 | # a dictionary of InputParameter objects
144 | parameters = model.metadata.parameters
145 |
146 | The key of the dictionary is a string , and the value is a :py:class:`cadquery.cqgi.InputParameter` object
147 | See the CQGI API docs for more details.
148 |
149 | Future enhancements will include a safer sandbox to prevent malicious scripts.
150 |
151 | Automating export to STL
152 | -------------------------
153 | 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.
154 |
155 | The below Python script demonstrates how to open, process, and export an STL file from any valid cadquery script::
156 |
157 | # Load CQGI
158 | import cadquery.cqgi as cqgi
159 | import cadquery as cq
160 |
161 | # load the cadquery script
162 | model = cqgi.parse(open("example.py").read())
163 |
164 | # run the script and store the result (from the show_object call in the script)
165 | build_result = model.build()
166 |
167 | # test to ensure the process worked.
168 | if build_result.success:
169 | # loop through all the shapes returned and export to STL
170 | for i, result in enumerate(build_result.results):
171 | cq.exporters.export(result.shape, f"example_output{i}.stl")
172 | else:
173 | print(f"BUILD FAILED: {build_result.exception}")
174 |
175 |
176 |
177 | Important CQGI Methods
178 | -------------------------
179 |
180 | These are the most important Methods and classes of the CQGI
181 |
182 | .. currentmodule:: cadquery.cqgi
183 |
184 | .. autosummary::
185 | parse
186 | CQModel.build
187 | BuildResult
188 | ScriptCallback.show_object
189 |
190 | Complete CQGI API
191 | -----------------
192 |
193 | .. automodule:: cadquery.cqgi
194 | :members:
195 |
196 |
--------------------------------------------------------------------------------
/doc/designprinciples.rst:
--------------------------------------------------------------------------------
1 | .. _designprinciples:
2 |
3 |
4 | ===========================
5 | Design Principles
6 | ===========================
7 |
8 |
9 | Principle 1: Intuitive Construction
10 | ====================================
11 |
12 | CadQuery aims to make building models using python scripting easy and intuitive.
13 | CadQuery strives to allow scripts to read roughly as a human would describe an object verbally.
14 |
15 | For example, consider this object:
16 |
17 | .. image:: _static/quickstart.png
18 |
19 | A human would describe this as:
20 |
21 | "A block 80mm square x 30mm thick , with countersunk holes for M2 socket head cap screws
22 | at the corners, and a circular pocket 22mm in diameter in the middle for a bearing"
23 |
24 | The goal is to have the CadQuery script that produces this object be as close as possible to the English phrase
25 | a human would use.
26 |
27 |
28 | Principle 2: Capture Design Intent
29 | ====================================
30 |
31 | The features that are **not** part of the part description above are just as important as those that are. For example, most
32 | humans will assume that:
33 |
34 | * The countersunk holes are spaced a uniform distance from the edges
35 | * The circular pocket is in the center of the block, no matter how big the block is
36 |
37 | If you have experience with 3D CAD systems, you also know that there is a key design intent built into this object.
38 | After the base block is created, how the hole is located is key. If it is located from one edge, changing the block
39 | size will have a different effect than if the hole is located from the center.
40 |
41 | Many scripting languages do not provide a way to capture design intent-- because they require that you always work in
42 | global coordinates. CadQuery is different-- you can locate features relative to others in a relative way-- preserving
43 | the design intent just like a human would when creating a drawing or building an object.
44 |
45 | In fact, though many people know how to use 3D CAD systems, few understand how important the way that an object is built
46 | impact its maintainability and resiliency to design changes.
47 |
48 |
49 | Principle 3: Plugins as first class citizens
50 | ============================================
51 |
52 | Any system for building 3D models will evolve to contain an immense number of libraries and feature builders. It is
53 | important that these can be seamlessly included into the core and used alongside the built in libraries. Plugins
54 | should be easy to install and familiar to use.
55 |
56 |
57 | Principle 4: CAD models as source code makes sense
58 | ==================================================================
59 |
60 | It is surprising that the world of 3D CAD is primarily dominated by systems that create opaque binary files.
61 | Just like the world of software, CAD models are very complex.
62 |
63 | CAD models have many things in common with software, and would benefit greatly from the use of tools that are standard
64 | in the software industry, such as:
65 |
66 | 1. Easily re-using features between objects
67 | 2. Storing objects using version control systems
68 | 3. Computing the differences between objects by using source control tools
69 | 4. Share objects on the Internet
70 | 5. Automate testing and generation by allowing objects to be built from within libraries
71 |
72 | CadQuery is designed to make 3D content creation easy enough that the above benefits can be attained without more work
73 | than using existing 'opaque', 'point and click' solutions.
74 |
75 |
--------------------------------------------------------------------------------
/doc/fileformat.rst:
--------------------------------------------------------------------------------
1 | .. _cadquery_reference:
2 |
3 | CadQuery Scripts and Object Output
4 | ======================================
5 |
6 | CadQuery scripts are pure Python scripts, that may follow a few conventions.
7 |
8 | If you are using CadQuery as a library, there are no constraints.
9 |
10 | If you are using CadQuery scripts inside of a CadQuery execution environment
11 | like `CQ-editor `_, there are a few conventions you need to be aware of:
12 |
13 | * cadquery is usually imported as 'cq' at the top of a script
14 | * to return an object to the execution environment (like CQ-editor) for rendering, you need to call the show_object() method
15 |
16 | Each script generally has three sections:
17 |
18 | * Variable Assignments and metadata definitions
19 | * CadQuery and other Python code
20 | * object export or rendering, via the show_object() function
21 |
22 |
23 | see the :ref:`cqgi` section for more details.
--------------------------------------------------------------------------------
/doc/gen_colors.py:
--------------------------------------------------------------------------------
1 | """
2 | A script to generate RST (HTML only) for displaying all the colours supported
3 | by OCP. Used in the file assy.rst.
4 | """
5 |
6 | from OCP import Quantity
7 | import cadquery as cq
8 | from typing import Dict
9 | from itertools import chain
10 |
11 |
12 | OCP_COLOR_LEADER, SEP = "Quantity_NOC", "_"
13 |
14 | TEMPLATE = """\
15 | {color_name}
\
16 | """
17 |
18 |
19 | def color_to_rgba_str(c: cq.Color) -> str:
20 | """ Convert a Color object to a string for the HTML/CSS template.
21 | """
22 | t = c.toTuple()
23 | vals = [int(v * 255) for v in t[:3]]
24 | return ",".join([str(v) for v in chain(vals, [t[3]])])
25 |
26 |
27 | def calc_text_color(c: cq.Color) -> str:
28 | """ Calculate required overlay text color from background color.
29 | """
30 | val = sum(c.toTuple()[:3]) / 3
31 | if val < 0.5:
32 | rv = "255,255,255"
33 | else:
34 | rv = "0,0,0"
35 |
36 | return rv
37 |
38 |
39 | def get_colors() -> Dict[str, cq.Color]:
40 | """ Scan OCP for colors and output to a dict.
41 | """
42 | colors = {}
43 | for name in dir(Quantity):
44 | splitted = name.rsplit(SEP, 1)
45 | if splitted[0] == OCP_COLOR_LEADER:
46 | colors.update({splitted[1].lower(): cq.Color(splitted[1])})
47 |
48 | return colors
49 |
50 |
51 | def rst():
52 | """ Produce the text for a Sphinx directive.
53 | """
54 | lines = [
55 | ".. raw:: html",
56 | "",
57 | ' ',
58 | ]
59 | colors = get_colors()
60 | for name, c in colors.items():
61 | lines += [
62 | TEMPLATE.format(
63 | background_color=color_to_rgba_str(c),
64 | text_color=calc_text_color(c),
65 | color_name=name,
66 | )
67 | ]
68 |
69 | lines.append("
")
70 | return "\n".join(lines)
71 |
72 |
73 | if __name__ == "__main__":
74 | print(rst())
75 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 | CadQuery 2 Documentation
4 | ===================================
5 |
6 | CadQuery is an intuitive, easy-to-use Python library for building parametric 3D CAD models. It has several goals:
7 |
8 | * Build models with scripts that are as close as possible to how you'd describe the object to a human,
9 | using a standard, already established programming language
10 |
11 | * Create parametric models that can be very easily customized by end users
12 |
13 | * Output high quality CAD formats like STEP, AMF and 3MF in addition to traditional STL
14 |
15 | * Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
16 |
17 | See CadQuery in Action
18 | -------------------------
19 |
20 | This `Getting Started Video `_ will show you what CadQuery can do. Please note that the video has not been updated for CadQuery 2 and still shows CadQuery use within FreeCAD.
21 |
22 |
23 | Quick Links
24 | ------------------
25 |
26 | * :ref:`quickstart`
27 | * `CadQuery CheatSheet <_static/cadquery_cheatsheet.html>`_
28 | * :ref:`apireference`
29 |
30 | Table Of Contents
31 | -------------------
32 |
33 | .. toctree::
34 | :maxdepth: 2
35 |
36 | intro.rst
37 | installation.rst
38 | quickstart.rst
39 | designprinciples.rst
40 | primer.rst
41 | workplane.rst
42 | sketch.rst
43 | assy.rst
44 | free-func.rst
45 | vis.rst
46 | fileformat.rst
47 | examples.rst
48 | apireference.rst
49 | API Cheatsheet
50 | selectors.rst
51 | classreference.rst
52 | importexport.rst
53 | cqgi.rst
54 | extending.rst
55 | citing.rst
56 |
57 |
58 |
59 | Indices and tables
60 | -------------------
61 |
62 | * :ref:`genindex`
63 | * :ref:`modindex`
64 | * :ref:`search`
65 |
66 |
--------------------------------------------------------------------------------
/doc/intro.rst:
--------------------------------------------------------------------------------
1 | .. _what_is_cadquery:
2 |
3 | *********************
4 | Introduction
5 | *********************
6 |
7 | What is CadQuery
8 | ========================================
9 |
10 | CadQuery is an intuitive, easy-to-use Python library for building parametric 3D CAD models. It has several goals:
11 |
12 | * Build models with scripts that are as close as possible to how you'd describe the object to a human,
13 | using a standard, already established programming language
14 |
15 | * Create parametric models that can be very easily customized by end users
16 |
17 | * Output high quality CAD formats like STEP, AMF and 3MF in addition to traditional STL
18 |
19 | * Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
20 |
21 | CadQuery 2 is based on
22 | `OCP `_,
23 | which is a set of Python bindings for the open-source `OpenCascade `_ modelling kernel.
24 |
25 | Using CadQuery, you can build fully parametric models with a very small amount of code. For example, this simple script
26 | produces a flat plate with a hole in the middle::
27 |
28 | thickness = 0.5
29 | width = 2.0
30 | result = Workplane("front").box(width, width, thickness).faces(">Z").hole(thickness)
31 |
32 | .. image:: _static/simpleblock.png
33 |
34 | That's a bit of a dixie-cup example. But it is pretty similar to a more useful part: a parametric pillow block for a
35 | standard 608-size ball bearing::
36 |
37 | (length, height, diam, thickness, padding) = (30.0, 40.0, 22.0, 10.0, 8.0)
38 |
39 | result = (
40 | Workplane("XY")
41 | .box(length, height, thickness)
42 | .faces(">Z")
43 | .workplane()
44 | .hole(diam)
45 | .faces(">Z")
46 | .workplane()
47 | .rect(length - padding, height - padding, forConstruction=True)
48 | .vertices()
49 | .cboreHole(2.4, 4.4, 2.1)
50 | )
51 |
52 | .. image:: _static/pillowblock.png
53 |
54 | Lots more examples are available in the :ref:`examples`
55 |
56 | CadQuery is a library, GUIs are separate
57 | ==============================================
58 |
59 | CadQuery is a library, that's intentionally designed to be usable as a GUI-less library. This enables
60 | its use in a variety of engineering and scientific applications that create 3D models programmatically.
61 |
62 | If you'd like a GUI, you have a couple of options:
63 |
64 | * The Qt-based GUI `CQ-editor `_
65 | * As a Jupyter extension `jupyter-cadquery `_
66 |
67 |
68 | Why CadQuery instead of OpenSCAD?
69 | ============================================
70 |
71 | Like OpenSCAD, CadQuery is an open-source, script based, parametric model generator. But CadQuery has several key advantages:
72 |
73 | 1. **The scripts use a standard programming language**, Python, and thus can benefit from the associated infrastructure.
74 | This includes many standard libraries and IDEs
75 |
76 | 2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
77 | by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
78 | in addition to the standard CSG operations supported by CGAL
79 |
80 | 3. **Ability to import/export STEP and DXF** We think the ability to begin with a STEP model, created in a CAD package,
81 | and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
82 |
83 | 4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
84 | features based on the position of other features, workplanes, vertices, etc.
85 |
86 | 5. **Better Performance** CadQuery scripts can build STL, STEP, AMF and 3MF faster than OpenSCAD.
87 |
88 | Where does the name CadQuery come from?
89 | ========================================
90 |
91 | CadQuery is inspired by `jQuery `_, a popular framework that
92 | revolutionized web development involving JavaScript.
93 |
94 | CadQuery is for 3D CAD what jQuery is for JavaScript.
95 | If you are familiar with how jQuery works, you will probably recognize several jQuery features that CadQuery uses:
96 |
97 | * A fluent API to create clean, easy to read code
98 |
99 | * Ability to use the library along side other Python libraries
100 |
101 | * Clear and complete documentation, with plenty of samples.
102 |
103 |
104 |
--------------------------------------------------------------------------------
/doc/roadmap.rst:
--------------------------------------------------------------------------------
1 | .. _roadmap:
2 |
3 |
4 | RoadMap: Planned Features
5 | ==============================
6 |
7 | **CadQuery is not even close to finished!!!**
8 |
9 | Many features are planned for later versions. This page tracks them. If you find that you need features
10 | not listed here, let us know!
11 |
12 |
13 | Workplanes
14 | --------------------
15 |
16 | rotated workplanes
17 | support creation of workplanes at an angle to another plane or face
18 |
19 | workplane local rotations
20 | rotate the coordinate system of a workplane by an angle.
21 |
22 | make a workplane from a wire
23 | useful to select outer wire and then operate from there, to allow offsets
24 |
25 | Assemblies
26 | ----------
27 |
28 | implement more constraints
29 | in plane, on axis, parallel to vector
30 |
31 |
32 | 2D operations
33 | -------------------
34 |
35 | arc construction using relative measures
36 | instead of forcing use of absolute workplane coordinates
37 |
38 | tangent arcs
39 | after a line
40 |
41 | centerpoint arcs
42 | including portions of arcs as well as with end points specified
43 |
44 | trimming
45 | ability to use construction geometry to trim other entities
46 |
47 | construction lines
48 | especially centerlines
49 |
50 | 2D fillets
51 | for a rectangle, or for consecutive selected lines
52 |
53 | 2D chamfers
54 | based on rectangles, polygons, polylines, or adjacent selected lines
55 |
56 | mirror around centerline
57 | using centerline construction geometry
58 |
59 | midpoint selection
60 | select midpoints of lines, arcs
61 |
62 | face center
63 | explicit selection of face center
64 |
65 | manipulate spline control points
66 | so that the shape of a spline can be more accurately controlled
67 |
68 | feature snap
69 | project geometry in the rest of the part into the work plane, so that
70 | they can be selected and used as references for other features.
71 |
72 | polyline edges
73 | allow polyline to be combined with other edges/curves
74 |
75 | 3D operations
76 | ---------------------
77 |
78 | rotation/transform that return a copy
79 | The current rotateAboutCenter and translate method modify the object, rather than returning a copy
80 |
81 | primitive creation
82 | Need primitive creation for:
83 | * cone
84 | * torus
85 | * wedge
86 |
--------------------------------------------------------------------------------
/doc/vis.rst:
--------------------------------------------------------------------------------
1 | .. _vis:
2 |
3 | ===========================
4 | Visualization
5 | ===========================
6 |
7 |
8 | Pure Python
9 | ===========
10 |
11 | Since version 2.4 CadQuery supports visualization without any external tools. Those facilities are based on the VTK library
12 | and are not tied to any external tool.
13 |
14 | .. code-block:: python
15 |
16 | from cadquery import *
17 | from cadquery.vis import show
18 |
19 | w = Workplane().sphere(1).split(keepBottom=True) - Workplane().sphere(0.5)
20 | r = w.faces('>Z').fillet(0.1)
21 |
22 | # Show the result
23 | show(r, alpha=0.5)
24 |
25 |
26 | .. image:: _static/show.PNG
27 |
28 |
29 | One can visualize objects of type :class:`~cadquery.Workplane`, :class:`~cadquery.Sketch`, :class:`~cadquery.Assembly`, :class:`~cadquery.Shape`,
30 | :class:`~cadquery.Vector`, :class:`~cadquery.Location` and lists thereof.
31 |
32 |
33 | .. code-block:: python
34 |
35 | from cadquery import *
36 | from cadquery.func import *
37 | from cadquery.vis import show
38 |
39 | w = Workplane().sphere(0.5).split(keepTop=True)
40 | sk = Sketch().rect(1.5, 1.5)
41 | sh = torus(5, 0.5)
42 |
43 | r = rect(2, 2)
44 | c = circle(2)
45 |
46 | N = 50
47 | params = [i/N for i in range(N)]
48 |
49 | vecs = r.positions(params)
50 | locs = c.locations(params)
51 |
52 | # Render the solid
53 | show(w, sk, sh, vecs, locs)
54 |
55 |
56 | .. image:: _static/show_demo.PNG
57 |
58 |
59 | Additionally it is possible to integrate with other libraries using VTK and display any `vtkProp` object.
60 |
61 |
62 | .. code-block:: python
63 |
64 | from cadquery.vis import show
65 | from cadquery.func import torus
66 |
67 | from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor
68 |
69 |
70 | a = vtkAnnotatedCubeActor()
71 | t = torus(5,1)
72 |
73 | show(t, a)
74 |
75 | .. image:: _static/show_vtk.PNG
76 |
77 |
78 | Note that currently the show function is blocking.
79 |
80 | Screenshots
81 | ===========
82 |
83 | :meth:`~cadquery.vis.show` allows additionally to take screenshots in `png` format. One can specify zoom,
84 | camera position and windows size.
85 |
86 | .. code-block:: python
87 |
88 | from cadquery.vis import show
89 | from cadquery.func import box
90 |
91 | b = box(1,1,1)
92 |
93 | show(b, width=800, height=800, screenshot='img.png', zoom=2, roll=-20, elevation=-30, interact=False)
94 |
95 |
96 | .. warning::
97 | Intermittent issues were observed with this functionality, please submit detailed bug reports in case
98 | of problems.
99 |
100 | Sometimes it is desirable to control the camera position precisely. This can be achieved as follows.
101 |
102 | .. code-block:: python
103 |
104 | from cadquery.vis import show
105 | from cadquery.func import torus
106 |
107 | R = 10
108 | r = 1
109 | h = 2
110 |
111 | t = torus(R, r)
112 |
113 | show(t, position=(R, -R, R/h), roll=-45, zoom=0.9)
114 |
115 |
116 | .. image:: _static/show_camera_position.png
117 |
118 |
119 | Control points
120 | ==============
121 |
122 | :meth:`~cadquery.vis.ctrlPts` allows to visualize control points of surfaces and curves.
123 |
124 | .. code-block:: python
125 |
126 | from cadquery.func import *
127 | from cadquery.vis import *
128 |
129 | c = circle(1).toSplines()
130 | spine = spline([(0, 0, 0), (-3, -3, 5)], tgts=[(0, 0, 1), (0, -1, 0)])
131 | f = sweep(c, spine)
132 |
133 | show(
134 | f,
135 | ctrlPts(f),
136 | spine.moved(x=7),
137 | ctrlPts(spine.moved(x=7), color="green"),
138 | alpha=0.0,
139 | )
140 |
141 | .. image:: _static/ctrl_pts.png
142 |
143 | Note that for some geometries explicit conversion to spline representation might be needed.
144 | :meth:`~cadquery.Shape.toSplines` performs approximate conversion and :meth:`~cadquery.Shape.toNURBS`
145 | performs exact one.
146 |
147 |
148 | Styling
149 | =======
150 |
151 | Fine-grained control of the appearance of every item can be achieved using :meth:`~cadquery.vis.style`.
152 |
153 | .. code-block:: python
154 |
155 | from cadquery.vis import *
156 | from cadquery.func import *
157 |
158 | show(
159 | style(
160 | torus(10, 2),
161 | color="crimson",
162 | tubes=True,
163 | linewidth=5,
164 | mesh=True,
165 | meshcolor="blue",
166 | tolerance=0.1,
167 | ),
168 | style(box(3, 3, 3), color="green", markersize=0.1, alpha=0.5),
169 | )
170 |
171 |
172 | .. image:: _static/show_styling.png
173 |
174 |
175 | Jupyter/JupterLab
176 | =================
177 |
178 | There is also more limited support for displaying :class:`~cadquery.Workplane`, :class:`~cadquery.Sketch`, :class:`~cadquery.Assembly`,
179 | :class:`~cadquery.Shape` in Jupyter and JupyterLab. This functionality is implemented using VTK.js.
180 |
181 | .. code-block:: python
182 |
183 | from cadquery import *
184 |
185 | Workplane().sphere(1).split(keepTop=True)
186 |
187 | .. image:: _static/show_jupyter.PNG
188 |
189 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: cadquery
2 | channels:
3 | - conda-forge
4 | dependencies:
5 | - python>=3.9,<=3.12
6 | - ipython
7 | - ocp=7.8.1
8 | - vtk=*=qt*
9 | - pyparsing>=2.1.9
10 | - sphinx=8.1.3
11 | - sphinx_rtd_theme
12 | - mypy
13 | - codecov
14 | - pytest
15 | - pytest-cov
16 | - ezdxf>=1.3.0
17 | - typing_extensions
18 | - nlopt
19 | - path
20 | - casadi
21 | - typish
22 | - multimethod >=1.11,<2.0
23 | - typed-ast
24 | - regex
25 | - pathspec
26 | - click
27 | - appdirs
28 | - pip
29 | - pip:
30 | - --editable=.
31 | - git+https://github.com/cadquery/black.git@cq
32 |
--------------------------------------------------------------------------------
/examples/Ex001_Simple_Block.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | length = 80.0 # Length of the block
5 | height = 60.0 # Height of the block
6 | thickness = 10.0 # Thickness of the block
7 |
8 | # Create a 3D block based on the dimension variables above.
9 | # 1. Establishes a workplane that an object can be built on.
10 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
11 | # positive Z direction is "up", and the negative Z direction is "down".
12 | result = cq.Workplane("XY").box(length, height, thickness)
13 |
14 | # The following method is now outdated, but can still be used to display the
15 | # results of the script if you want
16 | # from Helpers import show
17 | # show(result) # Render the result of this script
18 |
19 | show_object(result)
20 |
--------------------------------------------------------------------------------
/examples/Ex002_Block_With_Bored_Center_Hole.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | length = 80.0 # Length of the block
5 | height = 60.0 # Height of the block
6 | thickness = 10.0 # Thickness of the block
7 | center_hole_dia = 22.0 # Diameter of center hole in block
8 |
9 | # Create a block based on the dimensions above and add a 22mm center hole.
10 | # 1. Establishes a workplane that an object can be built on.
11 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
12 | # positive Z direction is "up", and the negative Z direction is "down".
13 | # 2. The highest (max) Z face is selected and a new workplane is created on it.
14 | # 3. The new workplane is used to drill a hole through the block.
15 | # 3a. The hole is automatically centered in the workplane.
16 | result = (
17 | cq.Workplane("XY")
18 | .box(length, height, thickness)
19 | .faces(">Z")
20 | .workplane()
21 | .hole(center_hole_dia)
22 | )
23 |
24 | # Displays the result of this script
25 | show_object(result)
26 |
--------------------------------------------------------------------------------
/examples/Ex003_Pillow_Block_With_Counterbored_Holes.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | length = 80.0 # Length of the block
5 | width = 100.0 # Width of the block
6 | thickness = 10.0 # Thickness of the block
7 | center_hole_dia = 22.0 # Diameter of center hole in block
8 | cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter
9 | cbore_inset = 12.0 # How far from the edge the cbored holes are set
10 | cbore_diameter = 4.4 # Bolt head pocket hole diameter
11 | cbore_depth = 2.1 # Bolt head pocket hole depth
12 |
13 | # Create a 3D block based on the dimensions above and add a 22mm center hold
14 | # and 4 counterbored holes for bolts
15 | # 1. Establishes a workplane that an object can be built on.
16 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
17 | # positive Z direction is "up", and the negative Z direction is "down".
18 | # 2. The highest(max) Z face is selected and a new workplane is created on it.
19 | # 3. The new workplane is used to drill a hole through the block.
20 | # 3a. The hole is automatically centered in the workplane.
21 | # 4. The highest(max) Z face is selected and a new workplane is created on it.
22 | # 5. A for-construction rectangle is created on the workplane based on the
23 | # block's overall dimensions.
24 | # 5a. For-construction objects are used only to place other geometry, they
25 | # do not show up in the final displayed geometry.
26 | # 6. The vertices of the rectangle (corners) are selected, and a counter-bored
27 | # hole is placed at each of the vertices (all 4 of them at once).
28 | result = (
29 | cq.Workplane("XY")
30 | .box(length, width, thickness)
31 | .faces(">Z")
32 | .workplane()
33 | .hole(center_hole_dia)
34 | .faces(">Z")
35 | .workplane()
36 | .rect(length - cbore_inset, width - cbore_inset, forConstruction=True)
37 | .vertices()
38 | .cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
39 | .edges("|Z")
40 | .fillet(2.0)
41 | )
42 |
43 | # Displays the result of this script
44 | show_object(result)
45 |
--------------------------------------------------------------------------------
/examples/Ex004_Extruded_Cylindrical_Plate.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | circle_radius = 50.0 # Radius of the plate
5 | thickness = 13.0 # Thickness of the plate
6 | rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate
7 | rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
8 |
9 | # Extrude a cylindrical plate with a rectangular hole in the middle of it.
10 | # 1. Establishes a workplane that an object can be built on.
11 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
12 | # that the positive Z direction is "up", and the negative Z direction
13 | # is "down".
14 | # 2. The 2D geometry for the outer circle is created at the same time as the
15 | # rectangle that will create the hole in the center.
16 | # 2a. The circle and the rectangle will be automatically centered on the
17 | # workplane.
18 | # 2b. Unlike some other functions like the hole(), circle() takes
19 | # a radius and not a diameter.
20 | # 3. The circle and rectangle are extruded together, creating a cylindrical
21 | # plate with a rectangular hole in the center.
22 | # 3a. circle() and rect() could be changed to any other shape to completely
23 | # change the resulting plate and/or the hole in it.
24 | result = (
25 | cq.Workplane("front")
26 | .circle(circle_radius)
27 | .rect(rectangle_width, rectangle_length)
28 | .extrude(thickness)
29 | )
30 |
31 | # Displays the result of this script
32 | show_object(result)
33 |
--------------------------------------------------------------------------------
/examples/Ex005_Extruded_Lines_and_Arcs.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | width = 2.0 # Overall width of the plate
5 | thickness = 0.25 # Thickness of the plate
6 |
7 | # Extrude a plate outline made of lines and an arc
8 | # 1. Establishes a workplane that an object can be built on.
9 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
10 | # that the positive Z direction is "up", and the negative Z direction
11 | # is "down".
12 | # 2. Draws a line from the origin to an X position of the plate's width.
13 | # 2a. The starting point of a 2D drawing like this will be at the center of the
14 | # workplane (0, 0) unless the moveTo() function moves the starting point.
15 | # 3. A line is drawn from the last position straight up in the Y direction
16 | # 1.0 millimeters.
17 | # 4. An arc is drawn from the last point, through point (1.0, 1.5) which is
18 | # half-way back to the origin in the X direction and 0.5 mm above where
19 | # the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm
20 | # above (in the Y direction) where our first line started from.
21 | # 5. An arc is drawn from the last point that ends on (-0.5, 1.0), the sag of
22 | # the curve 0.2 determines that the curve is concave with the midpoint 0.1 mm
23 | # from the arc baseline. If the sag was -0.2 the arc would be convex.
24 | # This convention is valid when the profile is drawn counterclockwise.
25 | # The reverse is true if the profile is drawn clockwise.
26 | # Clockwise: +sag => convex, -sag => concave
27 | # Counterclockwise: +sag => concave, -sag => convex
28 | # 6. An arc is drawn from the last point that ends on (-0.7, -0.2), the arc is
29 | # determined by the radius of -1.5 mm.
30 | # Clockwise: +radius => convex, -radius => concave
31 | # Counterclockwise: +radius => concave, -radius => convex
32 | # 7. close() is called to automatically draw the last line for us and close
33 | # the sketch so that it can be extruded.
34 | # 7a. Without the close(), the 2D sketch will be left open and the extrude
35 | # operation will provide unpredictable results.
36 | # 8. The 2D sketch is extruded into a solid object of the specified thickness.
37 | result = (
38 | cq.Workplane("front")
39 | .lineTo(width, 0)
40 | .lineTo(width, 1.0)
41 | .threePointArc((1.0, 1.5), (0.0, 1.0))
42 | .sagittaArc((-0.5, 1.0), 0.2)
43 | .radiusArc((-0.7, -0.2), -1.5)
44 | .close()
45 | .extrude(thickness)
46 | )
47 |
48 | # Displays the result of this script
49 | show_object(result)
50 |
--------------------------------------------------------------------------------
/examples/Ex006_Moving_the_Current_Working_Point.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | circle_radius = 3.0 # The outside radius of the plate
5 | thickness = 0.25 # The thickness of the plate
6 |
7 | # Make a plate with two cutouts in it by moving the workplane center point
8 | # 1. Establishes a workplane that an object can be built on.
9 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
10 | # that the positive Z direction is "up", and the negative Z direction
11 | # is "down".
12 | # 1b. The initial workplane center point is the center of the circle, at (0,0).
13 | # 2. A circle is created at the center of the workplane
14 | # 2a. Notice that circle() takes a radius and not a diameter
15 | result = cq.Workplane("front").circle(circle_radius)
16 |
17 | # 3. The work center is movide to (1.5, 0.0) by calling center().
18 | # 3a. The new center is specified relative to the previous center,not
19 | # relative to global coordinates.
20 | # 4. A 0.5mm x 0.5mm 2D square is drawn inside the circle.
21 | # 4a. The plate has not been extruded yet, only 2D geometry is being created.
22 | result = result.center(1.5, 0.0).rect(0.5, 0.5)
23 |
24 | # 5. The work center is moved again, this time to (-1.5, 1.5).
25 | # 6. A 2D circle is created at that new center with a radius of 0.25mm.
26 | result = result.center(-1.5, 1.5).circle(0.25)
27 |
28 | # 7. All 2D geometry is extruded to the specified thickness of the plate.
29 | # 7a. The small circle and the square are enclosed in the outer circle of the
30 | # plate and so it is assumed that we want them to be cut out of the plate.
31 | # A separate cut operation is not needed.
32 | result = result.extrude(thickness)
33 |
34 | # Displays the result of this script
35 | show_object(result)
36 |
--------------------------------------------------------------------------------
/examples/Ex007_Using_Point_Lists.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | plate_radius = 2.0 # The radius of the plate that will be extruded
5 | hole_pattern_radius = 0.25 # Radius of circle where the holes will be placed
6 | thickness = 0.125 # The thickness of the plate that will be extruded
7 |
8 | # Make a plate with 4 holes in it at various points in a polar arrangement from
9 | # the center of the workplane.
10 | # 1. Establishes a workplane that an object can be built on.
11 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
12 | # that the positive Z direction is "up", and the negative Z direction
13 | # is "down".
14 | # 2. A 2D circle is drawn that will become though outer profile of the plate.
15 | r = cq.Workplane("front").circle(plate_radius)
16 |
17 | # 3. Push 4 points on the stack that will be used as the center points of the
18 | # holes.
19 | r = r.pushPoints([(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)])
20 |
21 | # 4. This circle() call will operate on all four points, putting a circle at
22 | # each one.
23 | r = r.circle(hole_pattern_radius)
24 |
25 | # 5. All 2D geometry is extruded to the specified thickness of the plate.
26 | # 5a. The small hole circles are enclosed in the outer circle of the plate and
27 | # so it is assumed that we want them to be cut out of the plate. A
28 | # separate cut operation is not needed.
29 | result = r.extrude(thickness)
30 |
31 | # Displays the result of this script
32 | show_object(result)
33 |
--------------------------------------------------------------------------------
/examples/Ex008_Polygon_Creation.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | width = 3.0 # The width of the plate
5 | height = 4.0 # The height of the plate
6 | thickness = 0.25 # The thickness of the plate
7 | polygon_sides = 6 # The number of sides that the polygonal holes should have
8 | polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
9 |
10 | # Create a plate with two polygons cut through it
11 | # 1. Establishes a workplane that an object can be built on.
12 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
13 | # that the positive Z direction is "up", and the negative Z direction
14 | # is "down".
15 | # 2. A 3D box is created in one box() operation to represent the plate.
16 | # 2a. The box is centered around the origin, which creates a result that may
17 | # be unituitive when the polygon cuts are made.
18 | # 3. 2 points are pushed onto the stack and will be used as centers for the
19 | # polygonal holes.
20 | # 4. The two polygons are created, on for each point, with one call to
21 | # polygon() using the number of sides and the circle that bounds the
22 | # polygon.
23 | # 5. The polygons are cut thru all objects that are in the line of extrusion.
24 | # 5a. A face was not selected, and so the polygons are created on the
25 | # workplane. Since the box was centered around the origin, the polygons end
26 | # up being in the center of the box. This makes them cut from the center to
27 | # the outside along the normal (positive direction).
28 | # 6. The polygons are cut through all objects, starting at the center of the
29 | # box/plate and going "downward" (opposite of normal) direction. Functions
30 | # like cutBlind() assume a positive cut direction, but cutThruAll() assumes
31 | # instead that the cut is made from a max direction and cuts downward from
32 | # that max through all objects.
33 | result = (
34 | cq.Workplane("front")
35 | .box(width, height, thickness)
36 | .pushPoints([(0, 0.75), (0, -0.75)])
37 | .polygon(polygon_sides, polygon_dia)
38 | .cutThruAll()
39 | )
40 |
41 | # Displays the result of this script
42 | show_object(result)
43 |
--------------------------------------------------------------------------------
/examples/Ex009_Polylines.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # These can be modified rather than hardcoding values for each dimension.
4 | # Define up our Length, Height, Width, and thickness of the beam
5 | (L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
6 |
7 | # Define the points that the polyline will be drawn to/thru
8 | pts = [
9 | (0, H / 2.0),
10 | (W / 2.0, H / 2.0),
11 | (W / 2.0, (H / 2.0 - t)),
12 | (t / 2.0, (H / 2.0 - t)),
13 | (t / 2.0, (t - H / 2.0)),
14 | (W / 2.0, (t - H / 2.0)),
15 | (W / 2.0, H / -2.0),
16 | (0, H / -2.0),
17 | ]
18 |
19 | # We generate half of the I-beam outline and then mirror it to create the full
20 | # I-beam.
21 | # 1. Establishes a workplane that an object can be built on.
22 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
23 | # that the positive Z direction is "up", and the negative Z direction
24 | # is "down".
25 | # 2. moveTo() is used to move the first point from the origin (0, 0) to
26 | # (0, 10.0), with 10.0 being half the height (H/2.0). If this is not done
27 | # the first line will start from the origin, creating an extra segment that
28 | # will cause the extrude to have an invalid shape.
29 | # 3. The polyline function takes a list of points and generates the lines
30 | # through all the points at once.
31 | # 3. Only half of the I-beam profile has been drawn so far. That half is
32 | # mirrored around the Y-axis to create the complete I-beam profile.
33 | # 4. The I-beam profile is extruded to the final length of the beam.
34 | result = cq.Workplane("front").polyline(pts).mirrorY().extrude(L)
35 |
36 | # Displays the result of this script
37 | show_object(result)
38 |
--------------------------------------------------------------------------------
/examples/Ex010_Defining_an_Edge_with_a_Spline.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # 1. Establishes a workplane to create the spline on to extrude.
4 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
5 | # positive Z direction is "up", and the negative Z direction is "down".
6 | s = cq.Workplane("XY")
7 |
8 | # The points that the spline will pass through
9 | sPnts = [
10 | (2.75, 1.5),
11 | (2.5, 1.75),
12 | (2.0, 1.5),
13 | (1.5, 1.0),
14 | (1.0, 1.25),
15 | (0.5, 1.0),
16 | (0, 1.0),
17 | ]
18 |
19 | # 2. Generate our plate with the spline feature and make sure it is a
20 | # closed entity
21 | r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts, includeCurrent=True).close()
22 |
23 | # 3. Extrude to turn the wire into a plate
24 | result = r.extrude(0.5)
25 |
26 | # Displays the result of this script
27 | show_object(result)
28 |
--------------------------------------------------------------------------------
/examples/Ex011_Mirroring_Symmetric_Geometry.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # 1. Establishes a workplane that an object can be built on.
4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
5 | # that the positive Z direction is "up", and the negative Z direction
6 | # is "down".
7 | # 2. A horizontal line is drawn on the workplane with the hLine function.
8 | # 2a. 1.0 is the distance, not coordinate. hLineTo allows using xCoordinate
9 | # not distance.
10 | r = cq.Workplane("front").hLine(1.0)
11 |
12 | # 3. Draw a series of vertical and horizontal lines with the vLine and hLine
13 | # functions.
14 | r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0)
15 |
16 | # 4. Mirror the geometry about the Y axis and extrude it into a 3D object.
17 | result = r.mirrorY().extrude(0.25)
18 |
19 | # Displays the result of this script
20 | show_object(result)
21 |
--------------------------------------------------------------------------------
/examples/Ex012_Creating_Workplanes_on_Faces.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # 1. Establishes a workplane that an object can be built on.
4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
5 | # that the positive Z direction is "up", and the negative Z direction
6 | # is "down".
7 | # 2. Creates a 3D box that will have a hole placed in it later.
8 | result = cq.Workplane("front").box(2, 3, 0.5)
9 |
10 | # 3. Find the top-most face with the >Z max selector.
11 | # 3a. Establish a new workplane to build geometry on.
12 | # 3b. Create a hole down into the box.
13 | result = result.faces(">Z").workplane().hole(0.5)
14 |
15 | # Displays the result of this script
16 | show_object(result)
17 |
--------------------------------------------------------------------------------
/examples/Ex013_Locating_a_Workplane_on_a_Vertex.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # 1. Establishes a workplane that an object can be built on.
4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
5 | # that the positive Z direction is "up", and the negative Z direction
6 | # is "down".
7 | # 2. Creates a 3D box that will have a hole placed in it later.
8 | result = cq.Workplane("front").box(3, 2, 0.5)
9 |
10 | # 3. Select the lower left vertex and make a workplane.
11 | # 3a. The top-most Z face is selected using the >Z selector.
12 | # 3b. The lower-left vertex of the faces is selected with the Z").vertices("Z")
20 | .workplane()
21 | .transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0))
22 | .rect(1.5, 1.5, forConstruction=True)
23 | .vertices()
24 | .hole(0.25)
25 | )
26 |
27 | # Displays the result of this script
28 | show_object(result)
29 |
--------------------------------------------------------------------------------
/examples/Ex016_Using_Construction_Geometry.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a block with holes in each corner of a rectangle on that workplane.
4 | # 1. Establishes a workplane that an object can be built on.
5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
6 | # that the positive Z direction is "up", and the negative Z direction
7 | # is "down".
8 | # 2. Creates a plain box to base future geometry on with the box() function.
9 | # 3. Selects the top-most Z face of the box.
10 | # 4. Creates a new workplane to build new geometry on.
11 | # 5. Creates a for-construction rectangle that only exists to use for placing
12 | # other geometry.
13 | # 6. Selects the vertices of the for-construction rectangle.
14 | # 7. Places holes at the center of each selected vertex.
15 | result = (
16 | cq.Workplane("front")
17 | .box(2, 2, 0.5)
18 | .faces(">Z")
19 | .workplane()
20 | .rect(1.5, 1.5, forConstruction=True)
21 | .vertices()
22 | .hole(0.125)
23 | )
24 |
25 | # Displays the result of this script
26 | show_object(result)
27 |
--------------------------------------------------------------------------------
/examples/Ex017_Shelling_to_Create_Thin_Features.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a hollow box that's open on both ends with a thin wall.
4 | # 1. Establishes a workplane that an object can be built on.
5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
6 | # that the positive Z direction is "up", and the negative Z direction
7 | # is "down".
8 | # 2. Creates a plain box to base future geometry on with the box() function.
9 | # 3. Selects faces with normal in +z direction.
10 | # 4. Create a shell by cutting out the top-most Z face.
11 | result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.05)
12 |
13 | # Displays the result of this script
14 | show_object(result)
15 |
--------------------------------------------------------------------------------
/examples/Ex018_Making_Lofts.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a lofted section between a rectangle and a circular section.
4 | # 1. Establishes a workplane that an object can be built on.
5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning
6 | # that the positive Z direction is "up", and the negative Z direction
7 | # is "down".
8 | # 2. Creates a plain box to base future geometry on with the box() function.
9 | # 3. Selects the top-most Z face of the box.
10 | # 4. Draws a 2D circle at the center of the the top-most face of the box.
11 | # 5. Creates a workplane 3 mm above the face the circle was drawn on.
12 | # 6. Draws a 2D circle on the new, offset workplane.
13 | # 7. Creates a loft between the circle and the rectangle.
14 | result = (
15 | cq.Workplane("front")
16 | .box(4.0, 4.0, 0.25)
17 | .faces(">Z")
18 | .circle(1.5)
19 | .workplane(offset=3.0)
20 | .rect(0.75, 0.5)
21 | .loft(combine=True)
22 | )
23 |
24 | # Displays the result of this script
25 | show_object(result)
26 |
--------------------------------------------------------------------------------
/examples/Ex019_Counter_Sunk_Holes.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a plate with 4 counter-sunk holes in it.
4 | # 1. Establishes a workplane using an XY object instead of a named plane.
5 | # 2. Creates a plain box to base future geometry on with the box() function.
6 | # 3. Selects the top-most face of the box and established a workplane on that.
7 | # 4. Draws a for-construction rectangle on the workplane which only exists for
8 | # placing other geometry.
9 | # 5. Selects the corner vertices of the rectangle and places a counter-sink
10 | # hole, using each vertex as the center of a hole using the cskHole()
11 | # function.
12 | # 5a. When the depth of the counter-sink hole is set to None, the hole will be
13 | # cut through.
14 | result = (
15 | cq.Workplane(cq.Plane.XY())
16 | .box(4, 2, 0.5)
17 | .faces(">Z")
18 | .workplane()
19 | .rect(3.5, 1.5, forConstruction=True)
20 | .vertices()
21 | .cskHole(0.125, 0.25, 82.0, depth=None)
22 | )
23 |
24 | # Displays the result of this script
25 | show_object(result)
26 |
--------------------------------------------------------------------------------
/examples/Ex020_Rounding_Corners_with_Fillets.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a plate with 4 rounded corners in the Z-axis.
4 | # 1. Establishes a workplane that an object can be built on.
5 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
6 | # positive Z direction is "up", and the negative Z direction is "down".
7 | # 2. Creates a plain box to base future geometry on with the box() function.
8 | # 3. Selects all edges that are parallel to the Z axis.
9 | # 4. Creates fillets on each of the selected edges with the specified radius.
10 | result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125)
11 |
12 | # Displays the result of this script
13 | show_object(result)
14 |
--------------------------------------------------------------------------------
/examples/Ex021_Splitting_an_Object.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Create a simple block with a hole through it that we can split.
4 | # 1. Establishes a workplane that an object can be built on.
5 | # 1a. Uses the X and Y origins to define the workplane, meaning that the
6 | # positive Z direction is "up", and the negative Z direction is "down".
7 | # 2. Creates a plain box to base future geometry on with the box() function.
8 | # 3. Selects the top-most face of the box and establishes a workplane on it
9 | # that new geometry can be built on.
10 | # 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
11 | # all the way through the box.
12 | c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()
13 |
14 | # 5. Selects the face furthest away from the origin in the +Y axis direction.
15 | # 6. Creates an offset workplane that is set in the center of the object.
16 | # 6a. One possible improvement to this script would be to make the dimensions
17 | # of the box variables, and then divide the Y-axis dimension by 2.0 and
18 | # use that to create the offset workplane.
19 | # 7. Uses the embedded workplane to split the object, keeping only the "top"
20 | # portion.
21 | result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
22 |
23 | # Displays the result of this script
24 | show_object(result)
25 |
--------------------------------------------------------------------------------
/examples/Ex022_Revolution.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # The dimensions of the model. These can be modified rather than changing the
4 | # shape's code directly.
5 | rectangle_width = 10.0
6 | rectangle_length = 10.0
7 | angle_degrees = 360.0
8 |
9 | # Revolve a cylinder from a rectangle
10 | # Switch comments around in this section to try the revolve operation with different parameters
11 | result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
12 | # result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
13 | # result = cq.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
14 | # result = cq.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
15 | # result = cq.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
16 |
17 | # Revolve a donut with square walls
18 | # result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
19 |
20 | # Displays the result of this script
21 | show_object(result)
22 |
--------------------------------------------------------------------------------
/examples/Ex023_Sweep.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # Points we will use to create spline and polyline paths to sweep over
4 | pts = [(0, 1), (1, 2), (2, 4)]
5 |
6 | # Spline path generated from our list of points (tuples)
7 | path = cq.Workplane("XZ").spline(pts)
8 |
9 | # Sweep a circle with a diameter of 1.0 units along the spline path we just created
10 | defaultSweep = cq.Workplane("XY").circle(1.0).sweep(path)
11 |
12 | # Sweep defaults to making a solid and not generating a Frenet solid. Setting Frenet to True helps prevent creep in
13 | # the orientation of the profile as it is being swept
14 | frenetShell = cq.Workplane("XY").circle(1.0).sweep(path, makeSolid=True, isFrenet=True)
15 |
16 | # We can sweep shapes other than circles
17 | defaultRect = cq.Workplane("XY").rect(1.0, 1.0).sweep(path)
18 |
19 | # Switch to a polyline path, but have it use the same points as the spline
20 | path = cq.Workplane("XZ").polyline(pts, includeCurrent=True)
21 |
22 | # Using a polyline path leads to the resulting solid having segments rather than a single swept outer face
23 | plineSweep = cq.Workplane("XY").circle(1.0).sweep(path)
24 |
25 | # Switch to an arc for the path
26 | path = cq.Workplane("XZ").threePointArc((1.0, 1.5), (0.0, 1.0))
27 |
28 | # Use a smaller circle section so that the resulting solid looks a little nicer
29 | arcSweep = cq.Workplane("XY").circle(0.5).sweep(path)
30 |
31 | # Translate the resulting solids so that they do not overlap and display them left to right
32 | show_object(defaultSweep)
33 | show_object(frenetShell.translate((5, 0, 0)))
34 | show_object(defaultRect.translate((10, 0, 0)))
35 | show_object(plineSweep)
36 | show_object(arcSweep.translate((20, 0, 0)))
37 |
--------------------------------------------------------------------------------
/examples/Ex024_Sweep_With_Multiple_Sections.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | # X axis line length 20.0
4 | path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0)
5 |
6 | # Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0
7 | defaultSweep = (
8 | cq.Workplane("YZ")
9 | .workplane(offset=-10.0)
10 | .circle(2.0)
11 | .workplane(offset=10.0)
12 | .circle(1.0)
13 | .workplane(offset=10.0)
14 | .circle(2.0)
15 | .sweep(path, multisection=True)
16 | )
17 |
18 | # We can sweep through different shapes
19 | recttocircleSweep = (
20 | cq.Workplane("YZ")
21 | .workplane(offset=-10.0)
22 | .rect(2.0, 2.0)
23 | .workplane(offset=8.0)
24 | .circle(1.0)
25 | .workplane(offset=4.0)
26 | .circle(1.0)
27 | .workplane(offset=8.0)
28 | .rect(2.0, 2.0)
29 | .sweep(path, multisection=True)
30 | )
31 |
32 | circletorectSweep = (
33 | cq.Workplane("YZ")
34 | .workplane(offset=-10.0)
35 | .circle(1.0)
36 | .workplane(offset=7.0)
37 | .rect(2.0, 2.0)
38 | .workplane(offset=6.0)
39 | .rect(2.0, 2.0)
40 | .workplane(offset=7.0)
41 | .circle(1.0)
42 | .sweep(path, multisection=True)
43 | )
44 |
45 |
46 | # Placement of the Shape is important otherwise could produce unexpected shape
47 | specialSweep = (
48 | cq.Workplane("YZ")
49 | .circle(1.0)
50 | .workplane(offset=10.0)
51 | .rect(2.0, 2.0)
52 | .sweep(path, multisection=True)
53 | )
54 |
55 | # Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0
56 | path = (
57 | cq.Workplane("XZ")
58 | .moveTo(-5, 4)
59 | .lineTo(0, 4)
60 | .threePointArc((4, 0), (0, -4))
61 | .lineTo(-5, -4)
62 | )
63 |
64 | # Placement of different shapes should follow the path
65 | # cylinder r=1.5 along first line
66 | # then sweep along arc from r=1.5 to r=1.0
67 | # then cylinder r=1.0 along last line
68 | arcSweep = (
69 | cq.Workplane("YZ")
70 | .workplane(offset=-5)
71 | .moveTo(0, 4)
72 | .circle(1.5)
73 | .workplane(offset=5, centerOption="CenterOfMass")
74 | .circle(1.5)
75 | .moveTo(0, -8)
76 | .circle(1.0)
77 | .workplane(offset=-5, centerOption="CenterOfMass")
78 | .circle(1.0)
79 | .sweep(path, multisection=True)
80 | )
81 |
82 |
83 | # Translate the resulting solids so that they do not overlap and display them left to right
84 | show_object(defaultSweep)
85 | show_object(circletorectSweep.translate((0, 5, 0)))
86 | show_object(recttocircleSweep.translate((0, 10, 0)))
87 | show_object(specialSweep.translate((0, 15, 0)))
88 | show_object(arcSweep.translate((0, -5, 0)))
89 |
--------------------------------------------------------------------------------
/examples/Ex025_Swept_Helix.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 |
3 | r = 0.5 # Radius of the helix
4 | p = 0.4 # Pitch of the helix - vertical distance between loops
5 | h = 2.4 # Height of the helix - total height
6 |
7 | # Helix
8 | wire = cq.Wire.makeHelix(pitch=p, height=h, radius=r)
9 | helix = cq.Workplane(obj=wire)
10 |
11 | # Final result: A 2D shape swept along a helix.
12 | result = (
13 | cq.Workplane("XZ") # helix is moving up the Z axis
14 | .center(r, 0) # offset isosceles trapezoid
15 | .polyline(((-0.15, 0.1), (0.0, 0.05), (0, 0.35), (-0.15, 0.3)))
16 | .close() # make edges a wire
17 | .sweep(helix, isFrenet=True) # Frenet keeps orientation as expected
18 | )
19 |
20 | show_object(result)
21 |
--------------------------------------------------------------------------------
/examples/Ex026_Case_Seam_Lip.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | from cadquery.selectors import AreaNthSelector
3 |
4 | case_bottom = (
5 | cq.Workplane("XY")
6 | .rect(20, 20)
7 | .extrude(10) # solid 20x20x10 box
8 | .edges("|Z or Z")
11 | .shell(2) # shell of thickness 2 with top face open
12 | .faces(">Z")
13 | .wires(AreaNthSelector(-1)) # selecting top outer wire
14 | .toPending()
15 | .workplane()
16 | .offset2D(-1) # creating centerline wire of case seam face
17 | .extrude(1) # covering the sell with temporary "lid"
18 | .faces(">Z[-2]")
19 | .wires(AreaNthSelector(0)) # selecting case crossection wire
20 | .toPending()
21 | .workplane()
22 | .cutBlind(2) # cutting through the "lid" leaving a lip on case seam surface
23 | )
24 |
25 | # similar process repeated for the top part
26 | # but instead of "growing" an inner lip
27 | # material is removed inside case seam centerline
28 | # to create an outer lip
29 | case_top = (
30 | cq.Workplane("XY")
31 | .move(25)
32 | .rect(20, 20)
33 | .extrude(5)
34 | .edges("|Z or >Z")
35 | .fillet(2)
36 | .faces("Z")
37 | .workplane()
38 | .rarray(pitch, pitch, lbumps, wbumps, True)
39 | .circle(bumpDiam / 2.0)
40 | .extrude(bumpHeight)
41 | )
42 |
43 | # add posts on the bottom. posts are different diameter depending on geometry
44 | # solid studs for 1 bump, tubes for multiple, none for 1x1
45 | tmp = s.faces(" 1 and wbumps > 1:
48 | tmp = (
49 | tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True)
50 | .circle(postDiam / 2.0)
51 | .circle(bumpDiam / 2.0)
52 | .extrude(height - t)
53 | )
54 | elif lbumps > 1:
55 | tmp = (
56 | tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True)
57 | .circle(t)
58 | .extrude(height - t)
59 | )
60 | elif wbumps > 1:
61 | tmp = (
62 | tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True)
63 | .circle(t)
64 | .extrude(height - t)
65 | )
66 | else:
67 | tmp = s
68 |
69 | # Render the solid
70 | show_object(tmp)
71 |
--------------------------------------------------------------------------------
/examples/Ex101_InterpPlate.py:
--------------------------------------------------------------------------------
1 | from math import sin, cos, pi, sqrt
2 | import cadquery as cq
3 |
4 | # TEST_1
5 | # example from PythonOCC core_geometry_geomplate.py, use of thickness = 0 returns 2D surface.
6 | thickness = 0
7 | edge_points = [(0.0, 0.0, 0.0), (0.0, 10.0, 0.0), (0.0, 10.0, 10.0), (0.0, 0.0, 10.0)]
8 | surface_points = [(5.0, 5.0, 5.0)]
9 | plate_0 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness)
10 | print("plate_0.val().Volume() = ", plate_0.val().Volume())
11 | plate_0 = plate_0.translate((0, 6 * 12, 0))
12 | show_object(plate_0)
13 |
14 | # EXAMPLE 1
15 | # Plate with 5 sides and 2 bumps, one side is not co-planar with the other sides
16 | thickness = 0.1
17 | edge_points = [
18 | (-7.0, -7.0, 0.0),
19 | (-3.0, -10.0, 3.0),
20 | (7.0, -7.0, 0.0),
21 | (7.0, 7.0, 0.0),
22 | (-7.0, 7.0, 0.0),
23 | ]
24 | edge_wire = cq.Workplane("XY").polyline(
25 | [(-7.0, -7.0), (7.0, -7.0), (7.0, 7.0), (-7.0, 7.0)]
26 | )
27 | # edge_wire = edge_wire.add(cq.Workplane("YZ").workplane().transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0)).polyline([(-7.,0.), (3,-3), (7.,0.)]))
28 | # In CadQuery Sept-2019 it worked with rotate=cq.Vector(0, 45, 0). In CadQuery Dec-2019 rotate=cq.Vector(45, 0, 0) only closes the wire.
29 | edge_wire = edge_wire.add(
30 | cq.Workplane("YZ")
31 | .workplane()
32 | .transformed(offset=cq.Vector(0, 0, -7), rotate=cq.Vector(45, 0, 0))
33 | .spline([(-7.0, 0.0), (3, -3), (7.0, 0.0)])
34 | )
35 | surface_points = [(-3.0, -3.0, -3.0), (3.0, 3.0, 3.0)]
36 | plate_1 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
37 | # plate_1 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness) # list of (x,y,z) points instead of wires for edges
38 | print("plate_1.val().Volume() = ", plate_1.val().Volume())
39 | show_object(plate_1)
40 |
41 | # EXAMPLE 2
42 | # Embossed star, need to change optional parameters to obtain nice looking result.
43 | r1 = 3.0
44 | r2 = 10.0
45 | fn = 6
46 | thickness = 0.1
47 | edge_points = [
48 | (r1 * cos(i * pi / fn), r1 * sin(i * pi / fn))
49 | if i % 2 == 0
50 | else (r2 * cos(i * pi / fn), r2 * sin(i * pi / fn))
51 | for i in range(2 * fn + 1)
52 | ]
53 | edge_wire = cq.Workplane("XY").polyline(edge_points)
54 | r2 = 4.5
55 | surface_points = [
56 | (r2 * cos(i * pi / fn), r2 * sin(i * pi / fn), 1.0) for i in range(2 * fn)
57 | ] + [(0.0, 0.0, -2.0)]
58 | plate_2 = cq.Workplane("XY").interpPlate(
59 | edge_wire,
60 | surface_points,
61 | thickness,
62 | combine=True,
63 | clean=True,
64 | degree=3,
65 | nbPtsOnCur=15,
66 | nbIter=2,
67 | anisotropy=False,
68 | tol2d=0.00001,
69 | tol3d=0.0001,
70 | tolAng=0.01,
71 | tolCurv=0.1,
72 | maxDeg=8,
73 | maxSegments=49,
74 | )
75 | # plate_2 = cq.Workplane("XY").interpPlate(edge_points, surface_points, thickness, combine=True, clean=True, Degree=3, NbPtsOnCur=15, NbIter=2, Anisotropie=False, Tol2d=0.00001, Tol3d=0.0001, TolAng=0.01, TolCurv=0.1, MaxDeg=8, MaxSegments=49) # list of (x,y,z) points instead of wires for edges
76 | print("plate_2.val().Volume() = ", plate_2.val().Volume())
77 | plate_2 = plate_2.translate((0, 2 * 12, 0))
78 | show_object(plate_2)
79 |
80 | # EXAMPLE 3
81 | # Points on hexagonal pattern coordinates, use of pushpoints.
82 | r1 = 1.0
83 | N = 3
84 | ca = cos(30.0 * pi / 180.0)
85 | sa = sin(30.0 * pi / 180.0)
86 | # EVEN ROWS
87 | pts = [
88 | (-3.0, -3.0),
89 | (-1.267949, -3.0),
90 | (0.464102, -3.0),
91 | (2.196152, -3.0),
92 | (-3.0, 0.0),
93 | (-1.267949, 0.0),
94 | (0.464102, 0.0),
95 | (2.196152, 0.0),
96 | (-2.133974, -1.5),
97 | (-0.401923, -1.5),
98 | (1.330127, -1.5),
99 | (3.062178, -1.5),
100 | (-2.133975, 1.5),
101 | (-0.401924, 1.5),
102 | (1.330127, 1.5),
103 | (3.062178, 1.5),
104 | ]
105 | # Spike surface
106 | thickness = 0.1
107 | fn = 6
108 | edge_points = [
109 | (
110 | r1 * cos(i * 2 * pi / fn + 30 * pi / 180),
111 | r1 * sin(i * 2 * pi / fn + 30 * pi / 180),
112 | )
113 | for i in range(fn + 1)
114 | ]
115 | surface_points = [
116 | (
117 | r1 / 4 * cos(i * 2 * pi / fn + 30 * pi / 180),
118 | r1 / 4 * sin(i * 2 * pi / fn + 30 * pi / 180),
119 | 0.75,
120 | )
121 | for i in range(fn + 1)
122 | ] + [(0, 0, 2)]
123 | edge_wire = cq.Workplane("XY").polyline(edge_points)
124 | plate_3 = (
125 | cq.Workplane("XY")
126 | .pushPoints(pts)
127 | .interpPlate(
128 | edge_wire,
129 | surface_points,
130 | thickness,
131 | combine=False,
132 | clean=False,
133 | degree=2,
134 | nbPtsOnCur=20,
135 | nbIter=2,
136 | anisotropy=False,
137 | tol2d=0.00001,
138 | tol3d=0.0001,
139 | tolAng=0.01,
140 | tolCurv=0.1,
141 | maxDeg=8,
142 | maxSegments=9,
143 | )
144 | )
145 | print("plate_3.val().Volume() = ", plate_3.val().Volume())
146 | plate_3 = plate_3.translate((0, 4 * 11, 0))
147 | show_object(plate_3)
148 |
149 | # EXAMPLE 4
150 | # Gyroïd, all edges are splines on different workplanes.
151 | thickness = 0.1
152 | edge_points = [
153 | [[3.54, 3.54], [1.77, 0.0], [3.54, -3.54]],
154 | [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
155 | [[-3.54, -3.54], [0.0, -1.77], [3.54, -3.54]],
156 | [[-3.54, -3.54], [-1.77, 0.0], [-3.54, 3.54]],
157 | [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
158 | [[3.54, 3.54], [0.0, 1.77], [-3.54, 3.54]],
159 | ]
160 | plane_list = ["XZ", "XY", "YZ", "XZ", "YZ", "XY"]
161 | offset_list = [-3.54, 3.54, 3.54, 3.54, -3.54, -3.54]
162 | edge_wire = (
163 | cq.Workplane(plane_list[0]).workplane(offset=-offset_list[0]).spline(edge_points[0])
164 | )
165 | for i in range(len(edge_points) - 1):
166 | edge_wire = edge_wire.add(
167 | cq.Workplane(plane_list[i + 1])
168 | .workplane(offset=-offset_list[i + 1])
169 | .spline(edge_points[i + 1])
170 | )
171 | surface_points = [(0, 0, 0)]
172 | plate_4 = cq.Workplane("XY").interpPlate(edge_wire, surface_points, thickness)
173 | print("plate_4.val().Volume() = ", plate_4.val().Volume())
174 | plate_4 = plate_4.translate((0, 5 * 12, 0))
175 | show_object(plate_4)
176 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | ignore_missing_imports = False
3 | disable_error_code = no-redef
4 | plugins = mypy/cadquery-plugin.py
5 |
6 | [mypy-ezdxf.*]
7 | ignore_missing_imports = True
8 | no_implicit_optional = False
9 |
10 | [mypy-pyparsing.*]
11 | ignore_missing_imports = True
12 |
13 | [mypy-IPython.*]
14 | ignore_missing_imports = True
15 |
16 | [mypy-scipy.*]
17 | ignore_missing_imports = True
18 |
19 | [mypy-numpy.*]
20 | ignore_missing_imports = True
21 |
22 | [mypy-nptyping.*]
23 | ignore_missing_imports = True
24 |
25 | [mypy-nlopt.*]
26 | ignore_missing_imports = True
27 |
28 | [mypy-vtkmodules.*]
29 | ignore_missing_imports = True
30 |
31 | [mypy-docutils.*]
32 | ignore_missing_imports = True
33 |
34 | [mypy-typish.*]
35 | ignore_missing_imports = True
36 |
37 | [mypy-casadi.*]
38 | ignore_missing_imports = True
39 |
40 |
--------------------------------------------------------------------------------
/mypy/cadquery-plugin.py:
--------------------------------------------------------------------------------
1 | from mypy.plugin import Plugin, FunctionContext
2 | from mypy.types import Type, UnionType
3 |
4 |
5 | class CadqueryPlugin(Plugin):
6 | def get_function_hook(self, fullname: str):
7 |
8 | if fullname == "cadquery.occ_impl.shapes._get":
9 |
10 | return hook__get
11 |
12 | elif fullname == "cadquery.occ_impl.shapes._get_one":
13 |
14 | return hook__get_one
15 |
16 | elif fullname == "cadquery.occ_impl.shapes._get_edges":
17 |
18 | return hook__get_edges
19 |
20 | elif fullname == "cadquery.occ_impl.shapes._get_wires":
21 |
22 | return hook__get_wires
23 |
24 | return None
25 |
26 |
27 | def hook__get(ctx: FunctionContext) -> Type:
28 | """
29 | Hook for cq.occ_impl.shapes._get
30 |
31 | Based on the second argument values it adjusts return type to an Iterator of specific subclasses of Shape.
32 | """
33 |
34 | if hasattr(ctx.args[1][0], "items"):
35 | return_type_names = [el.value for el in ctx.args[1][0].items]
36 | else:
37 | return_type_names = [ctx.args[1][0].value]
38 |
39 | return_types = UnionType([ctx.api.named_type(n) for n in return_type_names])
40 |
41 | return ctx.api.named_generic_type("typing.Iterable", [return_types])
42 |
43 |
44 | def hook__get_one(ctx: FunctionContext) -> Type:
45 | """
46 | Hook for cq.occ_impl.shapes._get_one
47 |
48 | Based on the second argument values it adjusts return type to a Union of specific subclasses of Shape.
49 | """
50 |
51 | if hasattr(ctx.args[1][0], "items"):
52 | return_type_names = [el.value for el in ctx.args[1][0].items]
53 | else:
54 | return_type_names = [ctx.args[1][0].value]
55 |
56 | return UnionType([ctx.api.named_type(n) for n in return_type_names])
57 |
58 |
59 | def hook__get_wires(ctx: FunctionContext) -> Type:
60 | """
61 | Hook for cq.occ_impl.shapes._get_wires
62 | """
63 |
64 | return_type = ctx.api.named_type("Wire")
65 |
66 | return ctx.api.named_generic_type("typing.Iterable", [return_type])
67 |
68 |
69 | def hook__get_edges(ctx: FunctionContext) -> Type:
70 | """
71 | Hook for cq.occ_impl.shapes._get_edges
72 | """
73 |
74 | return_type = ctx.api.named_type("Edge")
75 |
76 | return ctx.api.named_generic_type("typing.Iterable", [return_type])
77 |
78 |
79 | def plugin(version: str):
80 |
81 | return CadqueryPlugin
82 |
--------------------------------------------------------------------------------
/partcad.yaml:
--------------------------------------------------------------------------------
1 | name: /pub/examples/script/cadquery
2 | desc: CadQuery examples
3 | url: https://github.com/CadQuery/cadquery
4 |
5 | parts:
6 | examples/Ex001_Simple_Block:
7 | type: cadquery
8 | examples/Ex002_Block_With_Bored_Center_Hole:
9 | type: cadquery
10 | examples/Ex003_Pillow_Block_With_Counterbored_Holes:
11 | type: cadquery
12 | examples/Ex004_Extruded_Cylindrical_Plate:
13 | type: cadquery
14 | examples/Ex005_Extruded_Lines_and_Arcs:
15 | type: cadquery
16 | examples/Ex006_Moving_the_Current_Working_Point:
17 | type: cadquery
18 | examples/Ex007_Using_Point_Lists:
19 | type: cadquery
20 | examples/Ex008_Polygon_Creation:
21 | type: cadquery
22 | examples/Ex009_Polylines:
23 | type: cadquery
24 | examples/Ex010_Defining_an_Edge_with_a_Spline:
25 | type: cadquery
26 | examples/Ex011_Mirroring_Symmetric_Geometry:
27 | type: cadquery
28 | examples/Ex012_Creating_Workplanes_on_Faces:
29 | type: cadquery
30 | examples/Ex013_Locating_a_Workplane_on_a_Vertex:
31 | type: cadquery
32 | examples/Ex014_Offset_Workplanes:
33 | type: cadquery
34 | examples/Ex015_Rotated_Workplanes:
35 | type: cadquery
36 | examples/Ex016_Using_Construction_Geometry:
37 | type: cadquery
38 | examples/Ex017_Shelling_to_Create_Thin_Features:
39 | type: cadquery
40 | examples/Ex018_Making_Lofts:
41 | type: cadquery
42 | examples/Ex019_Counter_Sunk_Holes:
43 | type: cadquery
44 | examples/Ex020_Rounding_Corners_with_Fillets:
45 | type: cadquery
46 | examples/Ex021_Splitting_an_Object:
47 | type: cadquery
48 | examples/Ex022_Revolution:
49 | type: cadquery
50 | examples/Ex023_Sweep:
51 | type: cadquery
52 | examples/Ex024_Sweep_With_Multiple_Sections:
53 | type: cadquery
54 | examples/Ex025_Swept_Helix:
55 | type: cadquery
56 | examples/Ex026_Case_Seam_Lip:
57 | type: cadquery
58 | examples/Ex100_Lego_Brick:
59 | type: cadquery
60 | # examples/Ex101_InterpPlate uses `show_object()` multiple times which is not supported by PartCAD yet
61 | #examples/Ex101_InterpPlate:
62 | # type: cadquery
63 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/setup.cfg
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) CadQuery Development Team.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | import os
15 | from setuptools import setup, find_packages
16 |
17 | reqs = []
18 | setup_reqs = []
19 |
20 | # ReadTheDocs, AppVeyor and Azure builds will break when trying to instal pip deps in a conda env
21 | is_rtd = "READTHEDOCS" in os.environ
22 | is_appveyor = "APPVEYOR" in os.environ
23 | is_azure = "CONDA_PY" in os.environ
24 | is_conda = "CONDA_PREFIX" in os.environ
25 |
26 | # Only include the installation dependencies if we are not running on RTD or AppVeyor or in a conda env
27 | if not is_rtd and not is_appveyor and not is_azure and not is_conda:
28 | reqs = [
29 | "cadquery-ocp>=7.8.1,<7.9",
30 | "ezdxf>=1.3.0",
31 | "multimethod>=1.11,<2.0",
32 | "nlopt>=2.9.0,<3.0",
33 | "typish",
34 | "casadi",
35 | "path",
36 | ]
37 |
38 |
39 | setup(
40 | name="cadquery",
41 | version="2.6-dev", # Update this for the next release
42 | url="https://github.com/CadQuery/cadquery",
43 | license="Apache Public License 2.0",
44 | author="David Cowden",
45 | author_email="dave.cowden@gmail.com",
46 | description="CadQuery is a parametric scripting language for creating and traversing CAD models",
47 | long_description=open("README.md").read(),
48 | long_description_content_type="text/markdown",
49 | packages=find_packages(exclude=("tests",)),
50 | python_requires=">=3.9",
51 | setup_requires=setup_reqs,
52 | install_requires=reqs,
53 | extras_require={
54 | "dev": [
55 | "docutils",
56 | "ipython",
57 | "pytest",
58 | # "black@git+https://github.com/cadquery/black.git@cq",
59 | ],
60 | "ipython": ["ipython",],
61 | },
62 | include_package_data=True,
63 | zip_safe=False,
64 | platforms="any",
65 | test_suite="tests",
66 | classifiers=[
67 | "Development Status :: 5 - Production/Stable",
68 | #'Development Status :: 6 - Mature',
69 | #'Development Status :: 7 - Inactive',
70 | "Intended Audience :: Developers",
71 | "Intended Audience :: End Users/Desktop",
72 | "Intended Audience :: Information Technology",
73 | "Intended Audience :: Science/Research",
74 | "Intended Audience :: System Administrators",
75 | "License :: OSI Approved :: Apache Software License",
76 | "Operating System :: POSIX",
77 | "Operating System :: MacOS",
78 | "Operating System :: Unix",
79 | "Programming Language :: Python",
80 | "Topic :: Software Development :: Libraries :: Python Modules",
81 | "Topic :: Internet",
82 | "Topic :: Scientific/Engineering",
83 | ],
84 | )
85 |
--------------------------------------------------------------------------------
/tests/README.txt:
--------------------------------------------------------------------------------
1 | It is OK for tests to import implementations like FreeCAD directly.
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from cadquery import *
2 | from OCP.gp import gp_Vec
3 | import unittest
4 | import sys
5 | import os
6 |
7 |
8 | def readFileAsString(fileName):
9 | f = open(fileName, "r")
10 | s = f.read()
11 | f.close()
12 | return s
13 |
14 |
15 | def writeStringToFile(strToWrite, fileName):
16 | f = open(fileName, "w")
17 | f.write(strToWrite)
18 | f.close()
19 |
20 |
21 | def makeUnitSquareWire():
22 | V = Vector
23 | return Wire.makePolygon(
24 | [V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)]
25 | )
26 |
27 |
28 | def makeUnitCube(centered=True):
29 | return makeCube(1.0, centered)
30 |
31 |
32 | def makeCube(size, xycentered=True):
33 | if xycentered:
34 | return Workplane().rect(size, size).extrude(size).val()
35 | else:
36 | return Solid.makeBox(size, size, size)
37 |
38 |
39 | def toTuple(v):
40 | """convert a vector or a vertex to a 3-tuple: x,y,z"""
41 | if type(v) == gp_Vec:
42 | return (v.X(), v.Y(), v.Z())
43 | elif type(v) == Vector:
44 | return v.toTuple()
45 | else:
46 | raise RuntimeError("dont know how to convert type %s to tuple" % str(type(v)))
47 |
48 |
49 | class BaseTest(unittest.TestCase):
50 | def assertTupleAlmostEquals(self, expected, actual, places, msg=None):
51 | for i, j in zip(actual, expected):
52 | self.assertAlmostEqual(i, j, places, msg=msg)
53 |
54 |
55 | __all__ = [
56 | "TestCadObjects",
57 | "TestCadQuery",
58 | "TestCQGI",
59 | "TestCQSelectors",
60 | "TestCQSelectors",
61 | "TestExporters",
62 | "TestImporters",
63 | "TestJupyter",
64 | "TestWorkplanes",
65 | ]
66 |
--------------------------------------------------------------------------------
/tests/naca.py:
--------------------------------------------------------------------------------
1 | naca5305 = [
2 | [1.000074, 0.000520],
3 | [0.998545, 0.000829],
4 | [0.993968, 0.001750],
5 | [0.986369, 0.003267],
6 | [0.975792, 0.005351],
7 | [0.962301, 0.007964],
8 | [0.945974, 0.011059],
9 | [0.926909, 0.014580],
10 | [0.905218, 0.018465],
11 | [0.881032, 0.022648],
12 | [0.854494, 0.027058],
13 | [0.825764, 0.031621],
14 | [0.795017, 0.036263],
15 | [0.762437, 0.040911],
16 | [0.728224, 0.045493],
17 | [0.692585, 0.049938],
18 | [0.655739, 0.054180],
19 | [0.617912, 0.058158],
20 | [0.579336, 0.061813],
21 | [0.540252, 0.065095],
22 | [0.500900, 0.067958],
23 | [0.461525, 0.070367],
24 | [0.422374, 0.072291],
25 | [0.383692, 0.073709],
26 | [0.345722, 0.074611],
27 | [0.308702, 0.074992],
28 | [0.272257, 0.074521],
29 | [0.237079, 0.072481],
30 | [0.203612, 0.069020],
31 | [0.172090, 0.064350],
32 | [0.142727, 0.058704],
33 | [0.115719, 0.052328],
34 | [0.091240, 0.045475],
35 | [0.069442, 0.038397],
36 | [0.050454, 0.031335],
37 | [0.034383, 0.024516],
38 | [0.021313, 0.018141],
39 | [0.011308, 0.012382],
40 | [0.004410, 0.007379],
41 | [0.000639, 0.003232],
42 | [0.000000, 0.000000],
43 | [0.002443, -0.002207],
44 | [0.007902, -0.003318],
45 | [0.016322, -0.003385],
46 | [0.027630, -0.002492],
47 | [0.041737, -0.000752],
48 | [0.058539, 0.001696],
49 | [0.077918, 0.004691],
50 | [0.099743, 0.008055],
51 | [0.123875, 0.011591],
52 | [0.150167, 0.015098],
53 | [0.178462, 0.018365],
54 | [0.208603, 0.021185],
55 | [0.240422, 0.023351],
56 | [0.273752, 0.024670],
57 | [0.308614, 0.024992],
58 | [0.345261, 0.024967],
59 | [0.382862, 0.024875],
60 | [0.421191, 0.024682],
61 | [0.460016, 0.024358],
62 | [0.499100, 0.023878],
63 | [0.538207, 0.023226],
64 | [0.577098, 0.022390],
65 | [0.615534, 0.021370],
66 | [0.653278, 0.020171],
67 | [0.690099, 0.018807],
68 | [0.725767, 0.017298],
69 | [0.760061, 0.015670],
70 | [0.792768, 0.013955],
71 | [0.823684, 0.012188],
72 | [0.852613, 0.010407],
73 | [0.879374, 0.008651],
74 | [0.903799, 0.006957],
75 | [0.925731, 0.005364],
76 | [0.945032, 0.003906],
77 | [0.961578, 0.002615],
78 | [0.975264, 0.001519],
79 | [0.986001, 0.000641],
80 | [0.993720, 0.000001],
81 | [0.998372, -0.000389],
82 | [0.999926, -0.000520],
83 | ]
84 |
--------------------------------------------------------------------------------
/tests/test_cqgi.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests CQGI functionality
3 |
4 | Currently, this includes:
5 | Parsing a script, and detecting its available variables
6 | Altering the values at runtime
7 | defining a build_object function to return results
8 | """
9 |
10 | import pytest
11 | from cadquery import cqgi
12 | from tests import BaseTest
13 | import textwrap
14 |
15 | TESTSCRIPT = textwrap.dedent(
16 | """
17 | height=2.0
18 | width=3.0
19 | (a,b) = (1.0,1.0)
20 | o = (2, 2, 0)
21 | foo="bar"
22 |
23 | result = "%s|%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) , str(o) )
24 | show_object(result)
25 | """
26 | )
27 |
28 | TEST_DEBUG_SCRIPT = textwrap.dedent(
29 | """
30 | height=2.0
31 | width=3.0
32 | (a,b) = (1.0,1.0)
33 | o = (2, 2, 0)
34 | foo="bar"
35 | debug(foo, { "color": 'yellow' } )
36 | result = "%s|%s|%s|%s|%s" % ( str(height) , str(width) , foo , str(a) , str(o) )
37 | show_object(result)
38 | debug(height )
39 | """
40 | )
41 |
42 |
43 | class TestCQGI(BaseTest):
44 | def test_parser(self):
45 | model = cqgi.CQModel(TESTSCRIPT)
46 | metadata = model.metadata
47 | self.assertEqual(
48 | set(metadata.parameters.keys()), {"height", "width", "a", "b", "foo", "o"}
49 | )
50 |
51 | def test_build_with_debug(self):
52 | model = cqgi.CQModel(TEST_DEBUG_SCRIPT)
53 | result = model.build()
54 | debugItems = result.debugObjects
55 | self.assertTrue(len(debugItems) == 2)
56 | self.assertTrue(debugItems[0].shape == "bar")
57 | self.assertTrue(debugItems[0].options == {"color": "yellow"})
58 | self.assertTrue(debugItems[1].shape == 2.0)
59 | self.assertTrue(debugItems[1].options == {})
60 |
61 | def test_build_with_empty_params(self):
62 | model = cqgi.CQModel(TESTSCRIPT)
63 | result = model.build()
64 |
65 | self.assertTrue(result.success)
66 | self.assertTrue(len(result.results) == 1)
67 | self.assertTrue(result.results[0].shape == "2.0|3.0|bar|1.0|(2, 2, 0)")
68 |
69 | def test_build_with_different_params(self):
70 | model = cqgi.CQModel(TESTSCRIPT)
71 | result = model.build({"height": 3.0, "o": (3, 3)})
72 | print(result.results[0].shape)
73 | self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0|(3, 3)")
74 |
75 | def test_build_with_nested_tuple_params(self):
76 | model = cqgi.CQModel(TESTSCRIPT)
77 | result = model.build({"height": 3.0, "o": ((2, 2), (3, 3))})
78 | print(result.results[0].shape)
79 | self.assertTrue(result.results[0].shape == "3.0|3.0|bar|1.0|((2, 2), (3, 3))")
80 |
81 | def test_describe_parameters(self):
82 | script = textwrap.dedent(
83 | """
84 | a = 2.0
85 | describe_parameter(a,'FirstLetter')
86 | """
87 | )
88 | model = cqgi.CQModel(script)
89 | a_param = model.metadata.parameters["a"]
90 | self.assertTrue(a_param.default_value == 2.0)
91 | self.assertTrue(a_param.desc == "FirstLetter")
92 | self.assertTrue(a_param.varType == cqgi.NumberParameterType)
93 |
94 | def test_describe_parameter_invalid_doesnt_fail_script(self):
95 | script = textwrap.dedent(
96 | """
97 | a = 2.0
98 | describe_parameter(a, 2 - 1 )
99 | """
100 | )
101 | model = cqgi.CQModel(script)
102 | a_param = model.metadata.parameters["a"]
103 | self.assertTrue(a_param.name == "a")
104 |
105 | def test_build_with_exception(self):
106 | badscript = textwrap.dedent(
107 | """
108 | raise ValueError("ERROR")
109 | """
110 | )
111 |
112 | model = cqgi.CQModel(badscript)
113 | result = model.build({})
114 | self.assertFalse(result.success)
115 | self.assertIsNotNone(result.exception)
116 | self.assertTrue(result.exception.args[0] == "ERROR")
117 |
118 | def test_that_invalid_syntax_in_script_fails_immediately(self):
119 | badscript = textwrap.dedent(
120 | """
121 | this doesn't even compile
122 | """
123 | )
124 |
125 | exception = None
126 | try:
127 | cqgi.CQModel(badscript)
128 | except Exception as e:
129 | exception = e
130 |
131 | self.assertIsInstance(exception, SyntaxError)
132 |
133 | def test_that_two_results_are_returned(self):
134 | script = textwrap.dedent(
135 | """
136 | h = 1
137 | show_object(h)
138 | h = 2
139 | show_object(h)
140 | """
141 | )
142 |
143 | model = cqgi.CQModel(script)
144 | result = model.build({})
145 | self.assertEqual(2, len(result.results))
146 | self.assertEqual(1, result.results[0].shape)
147 | self.assertEqual(2, result.results[1].shape)
148 |
149 | def test_that_assinging_number_to_string_works(self):
150 | script = textwrap.dedent(
151 | """
152 | h = "this is a string"
153 | show_object(h)
154 | """
155 | )
156 | result = cqgi.parse(script).build({"h": 33.33})
157 | self.assertEqual(result.results[0].shape, "33.33")
158 |
159 | def test_that_assigning_string_to_number_fails(self):
160 | script = textwrap.dedent(
161 | """
162 | h = 20.0
163 | show_object(h)
164 | """
165 | )
166 | result = cqgi.parse(script).build({"h": "a string"})
167 | self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
168 |
169 | def test_that_assigning_unknown_var_fails(self):
170 | script = textwrap.dedent(
171 | """
172 | h = 20.0
173 | show_object(h)
174 | """
175 | )
176 |
177 | result = cqgi.parse(script).build({"w": "var is not there"})
178 | self.assertTrue(isinstance(result.exception, cqgi.InvalidParameterError))
179 |
180 | def test_that_cq_objects_are_visible(self):
181 | script = textwrap.dedent(
182 | """
183 | r = cadquery.Workplane('XY').box(1,2,3)
184 | show_object(r)
185 | """
186 | )
187 |
188 | result = cqgi.parse(script).build()
189 | self.assertTrue(result.success)
190 | self.assertIsNotNone(result.first_result)
191 |
192 | def test_that_options_can_be_passed(self):
193 | script = textwrap.dedent(
194 | """
195 | r = cadquery.Workplane('XY').box(1,2,3)
196 | show_object(r, options={"rgba":(128, 255, 128, 0.0)})
197 | """
198 | )
199 |
200 | result = cqgi.parse(script).build()
201 | self.assertTrue(result.success)
202 | self.assertIsNotNone(result.first_result.options)
203 |
204 | def test_setting_boolean_variable(self):
205 | script = textwrap.dedent(
206 | """
207 | h = True
208 | show_object( "*%s*" % str(h) )
209 | """
210 | )
211 |
212 | result = cqgi.parse(script).build({"h": False})
213 |
214 | self.assertTrue(result.success)
215 | self.assertEqual(result.first_result.shape, "*False*")
216 |
217 | def test_that_only_top_level_vars_are_detected(self):
218 | script = textwrap.dedent(
219 | """
220 | h = 1.0
221 | w = 2.0
222 |
223 | def do_stuff():
224 | x = 1
225 | y = 2
226 |
227 | show_object( "result" )
228 | """
229 | )
230 |
231 | model = cqgi.parse(script)
232 |
233 | self.assertEqual(2, len(model.metadata.parameters))
234 |
235 | def test_invalid_parameter_type(self):
236 | """Contrived test in case a parameter type that is not valid for InputParameter sneaks through."""
237 |
238 | # Made up parameter class
239 | class UnknowParameter:
240 | def __init__():
241 | return 1
242 |
243 | # Set up the most basic InputParameter object that is possible
244 | p = cqgi.InputParameter()
245 | p.varType = UnknowParameter
246 |
247 | # Setting the parameter should throw an unknown parameter type error
248 | with pytest.raises(ValueError) as info:
249 | p.set_value(2)
250 |
--------------------------------------------------------------------------------
/tests/test_examples.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from glob import glob
4 | from itertools import chain, count
5 |
6 | from path import Path
7 |
8 | from docutils.parsers.rst import directives
9 | from docutils.core import publish_doctree
10 | from docutils.utils import Reporter
11 |
12 | import cadquery as cq
13 | from cadquery import cqgi
14 | from cadquery.cq_directive import cq_directive
15 |
16 |
17 | def find_examples(pattern="examples/*.py", path=Path("examples")):
18 |
19 | for p in glob(pattern):
20 | with open(p, encoding="UTF-8") as f:
21 | code = f.read()
22 |
23 | yield code, path
24 |
25 |
26 | def find_examples_in_docs(pattern="doc/*.rst", path=Path("doc")):
27 |
28 | # dummy CQ directive for code
29 | class dummy_cq_directive(cq_directive):
30 |
31 | codes = []
32 |
33 | def run(self):
34 |
35 | self.codes.append("\n".join(self.content))
36 |
37 | return []
38 |
39 | directives.register_directive("cadquery", dummy_cq_directive)
40 |
41 | # read and parse all rst files
42 | for p in glob(pattern):
43 | with open(p, encoding="UTF-8") as f:
44 | doc = f.read()
45 |
46 | publish_doctree(
47 | doc, settings_overrides={"report_level": Reporter.SEVERE_LEVEL + 1}
48 | )
49 |
50 | # yield all code snippets
51 | for c in dummy_cq_directive.codes:
52 |
53 | yield c, path
54 |
55 |
56 | @pytest.mark.parametrize(
57 | "code, path", chain(find_examples(), find_examples_in_docs()), ids=count(0)
58 | )
59 | def test_example(code, path):
60 |
61 | # build
62 | with path:
63 | res = cqgi.parse(code).build()
64 |
65 | assert res.exception is None
66 |
67 | # check if the resulting objects are valid
68 | for r in res.results:
69 | r = r.shape
70 | if isinstance(r, cq.Workplane):
71 | for v in r.vals():
72 | if isinstance(v, cq.Shape):
73 | assert v.isValid()
74 | elif isinstance(r, cq.Shape):
75 | assert r.isValid()
76 |
--------------------------------------------------------------------------------
/tests/test_hull.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | import cadquery as cq
4 | from cadquery import hull
5 |
6 |
7 | def test_hull():
8 |
9 | c1 = cq.Edge.makeCircle(0.5, (-1.5, 0.5, 0))
10 | c2 = cq.Edge.makeCircle(0.5, (1.9, 0.0, 0))
11 | c3 = cq.Edge.makeCircle(0.2, (0.3, 1.5, 0))
12 | c4 = cq.Edge.makeCircle(0.2, (1.0, 1.5, 0))
13 | c5 = cq.Edge.makeCircle(0.1, (0.0, 0.0, 0.0))
14 | e1 = cq.Edge.makeLine(cq.Vector(0, -0.5), cq.Vector(-0.5, 1.5))
15 | e2 = cq.Edge.makeLine(cq.Vector(2.1, 1.5), cq.Vector(2.6, 1.5))
16 |
17 | edges = [c1, c2, c3, c4, c5, e1, e2]
18 |
19 | h = hull.find_hull(edges)
20 |
21 | assert len(h.Vertices()) == 11
22 | assert h.IsClosed()
23 | assert h.isValid()
24 |
25 |
26 | def test_validation():
27 |
28 | with pytest.raises(ValueError):
29 |
30 | e1 = cq.Edge.makeEllipse(2, 1)
31 | c1 = cq.Edge.makeCircle(0.5, (-1.5, 0.5, 0))
32 | hull.find_hull([c1, e1])
33 |
--------------------------------------------------------------------------------
/tests/test_jupyter.py:
--------------------------------------------------------------------------------
1 | from tests import BaseTest
2 |
3 | import cadquery as cq
4 | from cadquery.occ_impl.jupyter_tools import display
5 |
6 |
7 | class TestJupyter(BaseTest):
8 | def test_repr_javascript(self):
9 | cube = cq.Workplane("XY").box(1, 1, 1)
10 | assy = cq.Assembly().add(cube)
11 | shape = cube.val()
12 |
13 | self.assertIsInstance(shape, cq.occ_impl.shapes.Solid)
14 |
15 | # Test no exception on rendering to js
16 | js1 = shape._repr_javascript_()
17 | js2 = cube._repr_javascript_()
18 | js3 = assy._repr_javascript_()
19 |
20 | assert "function render" in js1
21 | assert "function render" in js2
22 | assert "function render" in js3
23 |
24 | def test_display_error(self):
25 |
26 | with self.assertRaises(ValueError):
27 | display(cq.Vector())
28 |
--------------------------------------------------------------------------------
/tests/test_pickle.py:
--------------------------------------------------------------------------------
1 | from pickle import loads, dumps
2 |
3 | from cadquery import (
4 | Vector,
5 | Matrix,
6 | Plane,
7 | Location,
8 | Shape,
9 | Sketch,
10 | Assembly,
11 | Color,
12 | Workplane,
13 | )
14 | from cadquery.func import box
15 |
16 | from pytest import mark
17 |
18 |
19 | @mark.parametrize(
20 | "obj",
21 | [
22 | Vector(2, 3, 4),
23 | Matrix(),
24 | Plane((-2, 1, 1)),
25 | Location(1, 2, 4),
26 | Sketch().rect(1, 1),
27 | Color("red"),
28 | Workplane().sphere(1),
29 | ],
30 | )
31 | def test_simple(obj):
32 |
33 | assert isinstance(loads(dumps(obj)), type(obj))
34 |
35 |
36 | def test_shape():
37 |
38 | s = Shape(box(1, 1, 1).wrapped)
39 |
40 | assert isinstance(loads(dumps(s)), Shape)
41 |
42 |
43 | def test_assy():
44 |
45 | assy = Assembly().add(box(1, 1, 1), color=Color("blue")).add(box(2, 2, 2))
46 |
47 | assert isinstance(loads(dumps(assy)), Assembly)
48 |
--------------------------------------------------------------------------------
/tests/test_shapes.py:
--------------------------------------------------------------------------------
1 | from cadquery.occ_impl.shapes import (
2 | Vector,
3 | Shape,
4 | Solid,
5 | wire,
6 | segment,
7 | polyline,
8 | box,
9 | compound,
10 | circle,
11 | plane,
12 | torus,
13 | cylinder,
14 | ellipse,
15 | spline,
16 | sweep,
17 | polygon,
18 | wireOn,
19 | )
20 |
21 | from pytest import approx, raises
22 |
23 | from math import pi
24 |
25 |
26 | def test_edge_paramAt():
27 |
28 | # paramAt for a segment
29 | e = segment((0, 0), (0, 1))
30 |
31 | p1 = e.paramAt(Vector(0, 0))
32 | p2 = e.paramAt(Vector(-1, 0))
33 | p3 = e.paramAt(Vector(0, 1))
34 |
35 | assert p1 == approx(p2)
36 | assert p1 == approx(0)
37 | assert p3 == approx(e.paramAt(1))
38 |
39 | # paramAt for a simple wire
40 | w1 = wire(e)
41 |
42 | p4 = w1.paramAt(Vector(0, 0))
43 | p5 = w1.paramAt(Vector(0, 1))
44 |
45 | assert p4 == approx(p1)
46 | assert p5 == approx(p3)
47 |
48 | # paramAt for a complex wire
49 | w2 = polyline((0, 0), (0, 1), (1, 1))
50 |
51 | p6 = w2.paramAt(Vector(0, 0))
52 | p7 = w2.paramAt(Vector(0, 1))
53 | p8 = w2.paramAt(Vector(0.1, 0.1))
54 |
55 | assert p6 == approx(w2.paramAt(0))
56 | assert p7 == approx(w2.paramAt(0.5))
57 | assert p8 == approx(w2.paramAt(0.1 / 2))
58 |
59 |
60 | def test_face_paramAt():
61 |
62 | f = plane(1, 1)
63 |
64 | u, v = f.paramAt((0.5, 0))
65 |
66 | assert u == approx(0.5)
67 | assert v == approx(0.0)
68 |
69 |
70 | def test_face_params():
71 |
72 | f = plane(1, 1)
73 |
74 | us, vs = f.params([(0.49, 0.0), (0.5, 0)])
75 |
76 | u1, u2 = us
77 | v1, v2 = vs
78 |
79 | assert u1 == approx(0.49)
80 | assert v1 == approx(0.0)
81 |
82 | assert u2 == approx(0.5)
83 | assert v2 == approx(0.0)
84 |
85 |
86 | def test_face_positionAt():
87 |
88 | f = plane(1, 1)
89 |
90 | p = f.positionAt(0.5, 0.5)
91 |
92 | assert p.x == approx(0.5)
93 | assert p.y == approx(0.5)
94 | assert p.z == approx(0)
95 |
96 |
97 | def test_face_positions():
98 |
99 | f = plane(1, 1)
100 |
101 | ps = f.positions([(0, 0), (0.5, 0.5)])
102 |
103 | p1, p2 = ps
104 |
105 | assert p1.x == approx(0)
106 | assert p1.y == approx(0)
107 | assert p1.z == approx(0)
108 |
109 | assert p2.x == approx(0.5)
110 | assert p2.y == approx(0.5)
111 | assert p2.z == approx(0)
112 |
113 |
114 | def test_edge_params():
115 |
116 | e = spline([(0, 0), (1, 0), (1, 1), (2, 0), (2, -1)], periodic=True)
117 | N = 5
118 |
119 | pts_orig = e.sample(N)[0]
120 | pts = [pt + Vector(0, 0, 1e-1) for pt in pts_orig]
121 |
122 | ps = e.params(pts)
123 |
124 | for i in range(N):
125 | assert (e.positionAt(ps[i], mode="parameter") - pts_orig[i]).Length == approx(0)
126 |
127 |
128 | def test_edge_tangents():
129 |
130 | e = circle(1)
131 |
132 | tgts = e.tangents([0, 1], mode="length")
133 |
134 | assert (tgts[0] - Vector(0, 1, 0)).Length == approx(0)
135 | assert (tgts[0] - tgts[1]).Length == approx(0)
136 |
137 | tgts = e.tangents([0, pi], mode="parameter")
138 |
139 | assert (tgts[1] - Vector(0, -1, 0)).Length == approx(0)
140 | assert (tgts[0] - tgts[1]).Length == approx(2)
141 |
142 |
143 | def test_isSolid():
144 |
145 | s = box(1, 1, 1)
146 |
147 | assert Solid.isSolid(s)
148 | assert Solid.isSolid(compound(s))
149 | assert not Solid.isSolid(s.faces())
150 |
151 |
152 | def test_shells():
153 |
154 | s = box(2, 2, 2) - box(1, 1, 1).moved(z=0.5)
155 |
156 | assert s.outerShell().Area() == approx(6 * 4)
157 | assert len(s.innerShells()) == 1
158 | assert s.innerShells()[0].Area() == approx(6 * 1)
159 |
160 |
161 | def test_curvature():
162 |
163 | r = 10
164 |
165 | c = circle(r)
166 | w = polyline((0, 0), (1, 0), (1, 1))
167 |
168 | assert c.curvatureAt(0) == approx(1 / r)
169 |
170 | curvatures = c.curvatures([0, 0.5])
171 |
172 | assert approx(curvatures[0]) == curvatures[1]
173 |
174 | assert w.curvatureAt(0) == approx(w.curvatureAt(0.5))
175 |
176 |
177 | def test_normals():
178 |
179 | r1 = 10
180 | r2 = 1
181 |
182 | t = torus(2 * r1, 2 * r2).faces()
183 |
184 | n1 = t.normalAt((r1, 0, r2))
185 | n2 = t.normalAt((r1 + r2, 0))
186 |
187 | assert n1.toTuple() == approx((0, 0, 1))
188 | assert n2.toTuple() == approx((1, 0, 0))
189 |
190 | n3, p3 = t.normalAt(0, 0)
191 |
192 | assert n3.toTuple() == approx((1, 0, 0))
193 | assert p3.toTuple() == approx((r1 + r2, 0, 0))
194 |
195 | (n4, n5), _ = t.normals((0, 0), (0, pi / 2))
196 |
197 | assert n4.toTuple() == approx((1, 0, 0))
198 | assert n5.toTuple() == approx((0, 0, 1))
199 |
200 |
201 | def test_trimming():
202 |
203 | e = segment((0, 0), (0, 1))
204 | f = plane(1, 1)
205 |
206 | # edge trim
207 | assert e.trim(0, 0.5).Length() == approx(e.Length() / 2)
208 |
209 | # face trim
210 | assert f.trim(0, 0.5, -0.5, 0.5).Area() == approx(f.Area() / 2)
211 |
212 | # face trim using wires
213 | assert f.trim(
214 | wireOn(f, polygon((0, -0.5), (0.5, -0.5), (0.5, 0.5), (0, 0.5)))
215 | ).Area() == approx(f.Area() / 2)
216 |
217 | # face trim using wires - single edge case
218 | assert f.trim(wireOn(f, circle(1))).isValid()
219 |
220 | # face trim using points
221 | assert f.trim((0, -0.5), (0.5, -0.5), (0.5, 0.5), (0, 0.5)).Area() == approx(
222 | f.Area() / 2
223 | )
224 |
225 |
226 | def test_bin_import_export():
227 |
228 | b = box(1, 1, 1)
229 |
230 | from io import BytesIO
231 |
232 | bio = BytesIO()
233 |
234 | b.exportBin(bio)
235 | bio.seek(0)
236 |
237 | r = Shape.importBin(bio)
238 |
239 | assert r.isValid()
240 | assert r.Volume() == approx(1)
241 |
242 | with raises(Exception):
243 | Shape.importBin(BytesIO())
244 |
245 |
246 | def test_sample():
247 |
248 | e = ellipse(10, 1)
249 | s = segment((0, 0), (1, 0))
250 |
251 | pts1, params1 = e.sample(10) # equidistant
252 | pts2, params2 = e.sample(0.1) # deflection based
253 | pts3, params3 = s.sample(10) # equidistant, open
254 |
255 | assert len(pts1) == len(params1)
256 | assert len(pts1) == 10 # e is closed
257 |
258 | assert len(pts2) == len(params2)
259 | assert len(pts2) == 16
260 |
261 | assert len(pts3) == len(params3)
262 | assert len(pts3) == 10 # s is open
263 |
264 |
265 | def test_isolines():
266 |
267 | c = cylinder(1, 2).faces("%CYLINDER")
268 |
269 | isos_v = c.isolines([0, 1])
270 | isos_u = c.isolines([0, 1], "u")
271 |
272 | assert len(isos_u) == 2
273 | assert len(isos_v) == 2
274 |
275 | assert isos_u[0].Length() == approx(2)
276 | assert isos_v[0].Length() == approx(pi)
277 |
278 |
279 | def test_extend():
280 |
281 | f = sweep(spline((0, 0), (0, 1), (2, 0)), spline((0, 0, 0), (0, 1, 1), (0, 1, 5)))
282 | f_ext = f.extend(1)
283 |
284 | assert f_ext.Area() > f.Area()
285 |
286 |
287 | def test_remove():
288 |
289 | b = box(2, 2, 2) - box(1, 1, 1).moved(z=0.5)
290 |
291 | assert len(b.Faces()) == 12
292 |
293 | br = b.remove(*b.innerShells())
294 |
295 | assert len(br.Faces()) == 6
296 | assert br.isValid()
297 |
298 |
299 | def test_addCavity():
300 |
301 | b1 = box(2, 2, 2)
302 | b2 = box(1, 1, 1).moved(z=0.5)
303 |
304 | br = b1.addCavity(b2)
305 |
306 | assert len(br.Faces()) == 12
307 | assert len(br.Shells()) == 2
308 | assert br.isValid()
309 |
310 |
311 | def test_replace():
312 |
313 | b = box(1, 1, 1)
314 | f_top = b.faces(">Z")
315 | f_top_split = f_top / plane(0.5, 0.5).moved(f_top.Center())
316 |
317 | br1 = b.replace(f_top, f_top_split)
318 |
319 | assert len(br1.Faces()) == len(b.Faces()) + 1
320 | assert br1.isValid()
321 |
322 | br2 = b.replace(f_top, *f_top_split) # invoke with individual faces
323 |
324 | assert len(br2.Faces()) == len(b.Faces()) + 1
325 | assert br2.isValid()
326 |
327 |
328 | def test_addHole():
329 |
330 | f = plane(1, 1)
331 | c = circle(0.1)
332 |
333 | f1 = f.addHole(c)
334 |
335 | assert len(f1.innerWires()) == 1
336 | assert f1.isValid()
337 |
338 | f2 = f.addHole(wire(c))
339 |
340 | assert len(f2.innerWires()) == 1
341 | assert f2.isValid()
342 |
343 | f3 = f.addHole(*c.moved((-0.3, 0), (0.3, 0)))
344 |
345 | assert len(f3.innerWires()) == 2
346 | assert f3.isValid()
347 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | from cadquery.utils import cqmultimethod as multimethod
2 |
3 | from pytest import raises
4 |
5 |
6 | def test_multimethod():
7 | class A:
8 | @multimethod
9 | def f(self, a: int, c: str = "s"):
10 | return 1
11 |
12 | @f.register
13 | def f(self, a: int, b: int, c: str = "b"):
14 | return 2
15 |
16 | assert A().f(0, "s") == 1
17 | assert A().f(0, c="s") == 1
18 | assert A().f(0) == 1
19 |
20 | assert A().f(0, 1, c="s") == 2
21 | assert A().f(0, 1, "s") == 2
22 | assert A().f(0, 1) == 2
23 |
24 | assert A().f(a=0, c="s") == 1
25 |
26 | with raises(TypeError):
27 | A().f(a=0, b=1, c="s")
28 |
--------------------------------------------------------------------------------
/tests/test_vis.py:
--------------------------------------------------------------------------------
1 | from cadquery import Workplane, Assembly, Sketch, Location, Vector
2 | from cadquery.func import circle, sweep, spline, plane, torus
3 | from cadquery.vis import show, show_object, vtkAxesActor, ctrlPts, style
4 |
5 | import cadquery.vis as vis
6 |
7 | from vtkmodules.vtkRenderingCore import (
8 | vtkRenderWindow,
9 | vtkRenderWindowInteractor,
10 | vtkWindowToImageFilter,
11 | vtkActor,
12 | vtkAssembly,
13 | )
14 | from vtkmodules.vtkRenderingAnnotation import vtkAnnotatedCubeActor
15 | from vtkmodules.vtkIOImage import vtkPNGWriter
16 |
17 | from pytest import fixture, raises
18 | from path import Path
19 |
20 |
21 | @fixture(scope="module")
22 | def tmpdir(tmp_path_factory):
23 | return Path(tmp_path_factory.mktemp("screenshots"))
24 |
25 |
26 | @fixture
27 | def wp():
28 |
29 | return Workplane().box(1, 1, 1)
30 |
31 |
32 | @fixture
33 | def assy(wp):
34 |
35 | return Assembly().add(wp)
36 |
37 |
38 | @fixture
39 | def sk():
40 |
41 | return Sketch().circle(1.0)
42 |
43 |
44 | class FakeInteractor(vtkRenderWindowInteractor):
45 | def Start(self):
46 |
47 | pass
48 |
49 | def Initialize(self):
50 |
51 | pass
52 |
53 |
54 | class FakeWindow(vtkRenderWindow):
55 | def Render(*args):
56 |
57 | pass
58 |
59 | def SetSize(*args):
60 |
61 | pass
62 |
63 | def GetScreenSize(*args):
64 |
65 | return 1, 1
66 |
67 | def SetPosition(*args):
68 |
69 | pass
70 |
71 | def SetOffScreenRendering(*args):
72 |
73 | pass
74 |
75 |
76 | class FakeWin2Img(vtkWindowToImageFilter):
77 | def Update(*args):
78 |
79 | pass
80 |
81 |
82 | class FakePNGWriter(vtkPNGWriter):
83 | def Write(*args):
84 |
85 | pass
86 |
87 |
88 | @fixture
89 | def patch_vtk(monkeypatch):
90 | """
91 | Fixture needed to not show anything during testing / prevent crashes in CI.
92 | """
93 |
94 | # use some dummy vtk objects
95 | monkeypatch.setattr(vis, "vtkRenderWindowInteractor", FakeInteractor)
96 | monkeypatch.setattr(vis, "vtkRenderWindow", FakeWindow)
97 | monkeypatch.setattr(vis, "vtkWindowToImageFilter", FakeWin2Img)
98 | monkeypatch.setattr(vis, "vtkPNGWriter", FakePNGWriter)
99 |
100 |
101 | def test_show(wp, assy, sk, patch_vtk):
102 |
103 | # simple smoke test
104 | show(wp)
105 | show(wp.val())
106 | show(wp.val().wrapped)
107 | show(assy)
108 | show(sk)
109 | show(wp, sk, assy, wp.val())
110 | show(Vector())
111 | show(Location())
112 | show([Vector, Vector, Location])
113 | show([wp, assy])
114 | show()
115 |
116 | # show with edges
117 | show(wp, edges=True)
118 |
119 | show_object(wp)
120 | show_object(wp.val())
121 | show_object(assy)
122 | show_object(sk)
123 | show_object(wp, sk, assy, wp.val())
124 | show_object()
125 |
126 | # for compatibility with CQ-editor
127 | show_object(wp, "a")
128 |
129 | # for now a workaround to be compatible with more complicated CQ-editor invocations
130 | show(1)
131 |
132 | # show a raw vtkProp
133 | show(vtkAxesActor(), [vtkAnnotatedCubeActor()])
134 |
135 |
136 | def test_screenshot(wp, tmpdir, patch_vtk):
137 |
138 | # smoke test for now
139 | with tmpdir:
140 | show(wp, interact=False, screenshot="img.png", trihedron=False, gradient=False)
141 |
142 |
143 | def test_ctrlPts():
144 |
145 | c = circle(1)
146 |
147 | # non-NURBS objects throw
148 | with raises(ValueError):
149 | ctrlPts(c)
150 |
151 | # control points of a curve
152 | a1 = ctrlPts(c.toNURBS())
153 | assert isinstance(a1, vtkActor)
154 |
155 | # control points of a non-periodic curve
156 | a2 = ctrlPts(c.trim(0, 1).toNURBS())
157 | assert isinstance(a2, vtkActor)
158 |
159 | # non-NURBS objects throw
160 | with raises(ValueError):
161 | ctrlPts(plane(1, 1))
162 |
163 | # control points of a surface
164 | a3 = ctrlPts(sweep(c.trim(0, 1), spline((0, 0, 0), (0, 0, 1))))
165 | assert isinstance(a3, vtkActor)
166 |
167 | # control points of a u,v periodic surface
168 | a4 = ctrlPts(torus(5, 1).faces().toNURBS())
169 | assert isinstance(a4, vtkActor)
170 |
171 |
172 | def test_style(wp, assy):
173 |
174 | t = torus(10, 1)
175 | e = t.Edges()[0]
176 | pts = e.sample(10)[0]
177 | locs = e.locations([0, 0.5, 0.75])
178 |
179 | # Shape
180 | act = style(t, color="red", alpha=0.5, tubes=True, spheres=True)
181 | assert isinstance(act, (vtkActor, vtkAssembly))
182 |
183 | # Assy
184 | act = style(assy, color="red", alpha=0.5, tubes=True, spheres=True)
185 | assert isinstance(act, (vtkActor, vtkAssembly))
186 |
187 | # Workplane
188 | act = style(wp, color="red", alpha=0.5, tubes=True, spheres=True)
189 | assert isinstance(act, (vtkActor, vtkAssembly))
190 |
191 | # Shape
192 | act = style(e)
193 | assert isinstance(act, (vtkActor, vtkAssembly))
194 |
195 | # Sketch
196 | act = style(Sketch().circle(1))
197 | assert isinstance(act, (vtkActor, vtkAssembly))
198 |
199 | # list[Vector]
200 | act = style(pts)
201 | assert isinstance(act, (vtkActor, vtkAssembly))
202 |
203 | # list[Location]
204 | act = style(locs)
205 | assert isinstance(act, (vtkActor, vtkAssembly))
206 |
207 | # vtkAssembly
208 | act = style(style(t))
209 | assert isinstance(act, (vtkActor, vtkAssembly))
210 |
211 | # vtkActor
212 | act = style(ctrlPts(e.toNURBS()))
213 | assert isinstance(act, (vtkActor, vtkAssembly))
214 |
215 |
216 | def test_camera_position(wp, patch_vtk):
217 |
218 | show(wp, position=(0, 0, 1), focus=(0, 0.1, 0))
219 | show(wp, focus=(0, 0.1, 0))
220 | show(wp, position=(0, 0, 1))
221 |
--------------------------------------------------------------------------------
/tests/testdata/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CadQuery/cadquery/f7544696bdc3f2ea2f55f5241aef1bc1b8afdddc/tests/testdata/OpenSans-Regular.ttf
--------------------------------------------------------------------------------