├── .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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/_static/importexport/box_custom_options_perspective.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/_static/importexport/box_default_options.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | X 35 | 36 | 37 | Y 38 | 39 | 40 | Z 41 | 45 | 46 | 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 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 68 | 72 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_dark_inkscape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | cq 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 68 | 72 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_light_inkscape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | cq 77 | 78 | 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 --------------------------------------------------------------------------------