├── .dockerignore ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── RELEASE.md ├── apt.txt ├── doc ├── clipping.md ├── cqparts.md ├── faces-edges-vertices.md ├── features.md ├── jupyter-cadquery.png ├── occ.md ├── replay.md └── splash.png ├── docker ├── Dockerfile └── run.sh ├── environment.yml.bak ├── examples ├── 1-cadquery.ipynb ├── 2-cadquery-examples.ipynb ├── 3-occ.ipynb ├── 4-sketches.ipynb ├── 5-build123d.ipynb ├── 6-show_object.ipynb ├── 7-show.ipynb ├── 8-show_all.ipynb ├── 9-set_viewer_config.ipynb └── assemblies │ ├── 1-disk-arm.ipynb │ ├── 1-disk-arm.png │ ├── 2-hexapod.ipynb │ ├── 2-hexapod.png │ ├── 3-jansen-linkage-l.png │ ├── 3-jansen-linkage.ipynb │ ├── 3-jansen-linkage.png │ ├── 4-bearing.ipynb │ ├── 5-door.ipynb │ └── 6-nested-assemblies.ipynb ├── jupyter-config └── jupyter_server_config.d │ └── jupyter_cadquery.json ├── jupyter_cadquery ├── __init__.py ├── _version.py ├── app.py ├── comms.py ├── config.py ├── logo.py ├── replay.py ├── show.py └── tools.py ├── notebooks ├── Icon Factory.ipynb ├── Juypter CadQuery Logo.ipynb └── icons │ ├── dark │ ├── bottom.png │ ├── edges.png │ ├── empty.png │ ├── empty_mesh.png │ ├── empty_shape.png │ ├── fit.png │ ├── front.png │ ├── isometric.png │ ├── left.png │ ├── mesh.png │ ├── mix_mesh.png │ ├── mix_shape.png │ ├── no_mesh.png │ ├── no_shape.png │ ├── plane.png │ ├── rear.png │ ├── reset.png │ ├── right.png │ ├── shape.png │ └── top.png │ └── light │ ├── bottom.png │ ├── edges.png │ ├── empty.png │ ├── empty_mesh.png │ ├── empty_shape.png │ ├── fit.png │ ├── front.png │ ├── isometric.png │ ├── left.png │ ├── mesh.png │ ├── mix_mesh.png │ ├── mix_shape.png │ ├── no_mesh.png │ ├── no_shape.png │ ├── plane.png │ ├── rear.png │ ├── reset.png │ ├── right.png │ ├── shape.png │ └── top.png ├── pyproject.toml ├── requirements.txt ├── runtime.txt ├── screenshots ├── 0_intro.png ├── debugging.gif ├── explode.gif ├── hexapod-crawling.gif ├── hexapod.png ├── jupyter-cadquery.png ├── measure.gif ├── replay.gif ├── sidecar.png └── viewer-locations.png ├── tests ├── compare_edges_output.png ├── compare_output.png ├── compare_rotate.png └── testlib.py └── validate_nb.py /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | .git 4 | .gitignore 5 | README.md 6 | RELEASE.md 7 | screenshots 8 | doc 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | .ipynb_checkpoints/ 3 | dist/ 4 | build/ 5 | *.py[cod] 6 | node_modules/ 7 | .mypy_cache 8 | yarn.lock 9 | scratch 10 | labextensions.txt.bak 11 | Untitled.ipynb 12 | TODO 13 | vslot-2020_1.dxf 14 | step.py 15 | .vscode/configurationCache.log 16 | .vscode/dryrun.log 17 | .vscode/targets.log 18 | 19 | # Compiled javascript 20 | # jupyter_cadquery/static/ 21 | 22 | # OS X 23 | .DS_Store 24 | .virtual_documents/ 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Aktuelle Datei", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "ms-python.black-formatter", 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "source.organizeImports": "always" 7 | }, 8 | }, 9 | "isort.args":["--profile", "black"], 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Release v3.5.2 (03.01.2023) 4 | 5 | ### Changes 6 | - Default python now is 3.10 7 | - Add support for `Compound`s with mixed shape types 8 | - Aligned `show_object` with `CQ-Editor` (e.g. support `options` dict) 9 | - Improved [build123d](https://github.com/gumyr/build123d) support 10 | - Add support for my private `Alg123d` library (a thin facade on top of `build123d` to remove all implicit behavior and give control back to the user) 11 | 12 | ### Fixes 13 | - OCCT bug with helix: If height = 2 * pitch, `GCPnts_QuasiUniformDeflection` returns 2 points only. Jupyter CadQuery detects this and uses `GCPnts_QuasiUniformAbscissa` instead 14 | 15 | 16 | ## Release v3.4.0 (18.10.2022) 17 | 18 | Support for [build123d](https://github.com/gumyr/build123d) (experimental). 19 | 20 | ## Release v3.3.0 (18.09.2022) 21 | 22 | This version changes the default view angle, hence the change of the minor version number. If you want to keep the old view behaviour of _Jupyter CadQuery_ for existing models, use `up="L"` (L as in legacy) as `show` parameter directly or via `set_defaults`. 23 | 24 | ### Changes: 25 | 26 | - Changed view button orientation behaviour: 27 | - up="Z" now works like FreeCAD, Onshape, ... with isometric view changed and buttons adapted (e.g. front is now defined differently!) 28 | - up="Y" works like "Fusion 360" in "y up" mode 29 | - up="L" works like the old z up mode of Jupyter CadQuery 30 | - Logo and hexapod example adapted to new view behaviour 31 | 32 | ### Fixes: 33 | 34 | - Fixed default parameters of `exportSTL` 35 | 36 | ## Release v3.2.2 (21.08.2022) 37 | 38 | No feature change, change dependency to cad-viewer-widget 1.3.5 which fixes using ipywidgets 7.7.2 39 | 40 | ## Release v3.2.1 (20.08.2022) 41 | 42 | No feature change, just re-released 3.2.0 since a deployment error happened with 3.2.0 to Pypi 43 | 44 | ## Release v3.2.0 (13.08.2022) 45 | 46 | ### New features: 47 | 48 | - Support of **y-axis as camera up** axis like in Fusion 360 49 | - Support for **alpha channel for colors**. 50 | **Note:** Transparent objects in WebGL are tricky and sometimes don't render at the right depth of the object. 51 | Jupyter CadQuery uses the following algorithm: 52 | - First draw all opaque objects with the correct depth information 53 | - Then draw all transparent objects. 54 | Unfortunately, WebGL does not support depth info for transparent objects, see https://stackoverflow.com/a/37651610 55 | Impact: Transparent objects might be fully or parts drawn at a wrong depth level. 56 | Nevertheless, I decided to support alpha channel 57 | 58 | ### Fixes: 59 | 60 | - Top level bounding box returned numpy values which broke export to HTML 61 | 62 | ## Release v3.1.0 (08.07.2022) 63 | 64 | ### New features: 65 | 66 | - **Performance** 67 | 68 | - Change exchange of shapes and tracks from Python to Javascript to binary mode 69 | - Introduced LRU cache for tessellation results (128MB default) 70 | - Introduced LRU cache for bounding box calculation 71 | - Introduced multiprocessing for large assemblies (10s to 100s objects) 72 | 73 | - **Step reader** 74 | 75 | - Added import function for STEP files into CadQuery assemblies preserving names and colors (for colors, best effort only, since Jupyter CadQuery does not support colored faces) 76 | - Added save_assembly/load_assembly to quickly save and load parsed STEP files in a binary BRep 77 | 78 | - **Animation system** 79 | 80 | - Introduced slider for animation 81 | - Added animated explode mode for CadQuery assemblies based on Animation system 82 | 83 | - **Bounding Box** 84 | 85 | - Removed OCCT bounding box algorithm and created a fast and precise top level bounding box after tessellation via numpy 86 | - Show bounding box (AABB) on tree click or cad view double click 87 | 88 | - **CAD view** 89 | 90 | - Element isolation 91 | - Added feature to isolate elements (shift double click or shift click on navigation tree) 92 | - Isolated objects are centered around the center of elements bounding box 93 | - Added highlighting of tree nodes when element picked 94 | - Added remove elements via navigation tree (meta click) 95 | 96 | - **UI** 97 | - Introduced light progress bar for assemblies 98 | - Parameters cad_width, tree_width and height can be changed after view is opened 99 | - Introduce glass mode 100 | - Hide checkbox options behind a 'More' menu for small CAD viewers 101 | - Enable auto-dark mode according to browser setting (added 'browser' mode to theme keyword) 102 | - Added highlighting for the most recent selected view button 103 | - Added tree collapsing/expanding buttons 104 | - Extend help for new features 105 | 106 | ### Fixes: 107 | 108 | - Change radio button behaviour to standard behaviour 109 | - Send notifications for changed "target" parameter 110 | - Fixed slider color for Safari 111 | - Fixed scrollbar for Firefox 112 | - Fixed initial zoom for views wider than high 113 | - Fixed get_pick to support cq.Assembly 114 | 115 | ## Release v3.0.0 (24.02.2022) 116 | 117 | ### New features 118 | 119 | - **Performance** 120 | 121 | - By removing the back and forth communication from pythreejs (Python) to Javascript (threejs), the new version is significantly faster in showing multi object assemblies. 122 | 123 | - **CadQuery feature support** 124 | 125 | - Supports the latest **CadQuery Sketch class**. 126 | 127 | - **New CAD View Controller** 128 | 129 | - Besides the _orbit_ controller (with z-axis being restricted to show up) it now also supports a **trackball controller** with full freedom of moving the CAD objects. The trackball controller uses the holroyd algorithm (see e.g. [here](https://www.mattkeeter.com/projects/rotation/)) to have better control of movements and avoid the usual trackball tumbling. 130 | 131 | - **A full re-implementation of Sidecar** 132 | 133 | - Sidecars will be **reused** based on name of the sidecar 134 | - Supports **different anchors** (_right_, _split-right_, _split-left_, _split-top_, _split-bottom_). 135 | - Sidecars opening with anchor _right_ will adapt the size to the the size of the CAD view 136 | 137 | - **WebGL contexts** 138 | 139 | - In a browser only a limited number of WebGL context can be shown at the same time (e.g. 16 in Chrome on my Mac). Hence, _Jupyter-CadQuery_ now thoroughly tracks WebGL contexts, i.e. **releases WebGL context** when sidecar gets closed. 140 | 141 | - **Replay mode** 142 | 143 | - Supports **CadQuery Sketch class**. 144 | - Replay mode now can **show bounding box** instead of result to compare step with result. 145 | 146 | - **New features** 147 | 148 | - _Jupyter-CadQuery_ now allows to show **all three grids** (xy, xz, yz). 149 | - `show_bbox` additionally shows the bounding box. 150 | - CAD viewer icons are scalable svg icons. 151 | - Clipping supports an **intersection mode**. 152 | - The animation controller is now part of the Javascript component. 153 | - export_html exports the whole view (with tools) as a HTML page 154 | - export_png export the CAD view (without tools) as a PNG 155 | 156 | - **Fixes** 157 | 158 | - more than I can remember (or am willing to read out of git log) ... 159 | 160 | ## Release v2.2.1 (07.10.2021) 161 | 162 | - **New features** 163 | 164 | - The docker container now supports Viewer mode (added new flags `-v` and `-d`) 165 | 166 | - **Fixes** 167 | 168 | - Fix [#47](https://github.com/bernhard-42/jupyter-cadquery/issues/47) Unable to see cadquery.Assembly when top level object of an Assembly is empty 169 | - Fix [#52](https://github.com/bernhard-42/jupyter-cadquery/issues/52) add `zoom` to ignored attributes for `reset_camera=False` 170 | - Fix [#53](https://github.com/bernhard-42/jupyter-cadquery/issues/53) Replaced `scipy` with `pyquaternion` for less heavyweight dependencies (and since CadQuery dropped `scipy`) 171 | 172 | ## Release v2.2.0 (28.06.2021) 173 | 174 | - **New features** 175 | 176 | - A new Viewer component based on [`voilà`](https://github.com/voila-dashboards/voila) allows to use _Jupyter-CadQuery_ as viewer for any IDE 177 | - Dark theme support 178 | - Tessellation normals can be rendered now for inspection 179 | - _Jupyter-CadQuery_ now has a logo, which is show as 3D objects when CAD viewer starts in (both sidecar and new Viewer) 180 | - `set_sidecar` can now immediatly start the viewer (parameter `init`) 181 | 182 | - **Changes** 183 | 184 | - `show` has new parameters 185 | - `ambient_intensity`: set ambient light intensity 186 | - `direct_intensity`: set direct light intensity 187 | - `default_edgecolor`: set default edge color 188 | - `render_normals`: render normals 189 | - During tessellation, normals are normalized 190 | - Defaults system now lives in `jupyter_cadquery.defaults` and is more consistent 191 | - Lean scrollbars are now default (`mac_scrollbar` parameter) 192 | - Rendering timer restructured with finer granular selection 193 | 194 | - **Fixes** 195 | - Hidden edges are now visible in transparent view 196 | - Fixed reset camera logic between different calls to `show` 197 | - Optimized bounding box calculation 198 | - Double scrollbars removed 199 | - Fix html export (including OrbitControls fix) 200 | 201 | ## Release v2.0.0 (06.03.2021) 202 | 203 | - **New features** 204 | 205 | - _Jupyter-CadQuery_ supports the latest _CadQuery 2.1_ with _OCP_ (note, it will not run with the _FreeCAD_ version of _CadQuery_). 206 | - Uses JupyterLab 3.0 which has a new extension deployment system which simplifies the installation of `Jupyter-CadQuery` drastically (see below) 207 | - It supports the new [CadQuery Assemblies](https://cadquery.readthedocs.io/en/latest/assy.html) 208 | - Splits UI and shape rendering and shows a progress bar during rendering, especially useful for large assembblies 209 | - If you install `cadquery-massembly` (see below) then the class `MAssembly` (meaning "Mate base Assembly") is available, which is derived from `cadquery.Assembly` but similar to `cqparts` or FreeCad's `Assembly4` works with mates to manually connect instead of constraints and a numerical solver. 210 | - Comes with an animation system to simulate models built with `MAssembly` 211 | 212 | - **Changes** 213 | - Deprecates _Jupyter-CadQuery_'s `Assembly` (too many assemblies in the meantime) and has renamed it to `PartGroup` (no semantic change). `Assembly` can still be used with warnings at the moment. 214 | - Does not test or change the `cqparts` support since the project doesn't seem to be active any more 215 | 216 | ## Relase v2.1.0 "Performance Release" (07.04.2021) 217 | 218 | - **New features** 219 | 220 | - Complete new tessellator class. Significantly faster (for a 15MB STEP file it reduced the rendering time from 3 min to <10 sec) 221 | - Mesh quality is calculated as in FreeCad (sum of bounding box x-, y-, z-widths divided by 300 times deviation parameter) 222 | 223 | - **Changes** 224 | - Pan speed is adapted to object size sum of bounding box x-, y-, z-widths divided by 300) 225 | - Replay warnings can be suppressed now (`replay(warning=False)`) 226 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Bernhard Walter 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include jupyter_cadquery/icons * 2 | recursive-include jupyter_cadquery/viewer viewer.ipynb 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean_notebooks wheel install tests check_version dist check_dist upload_test upload bump release create-release docker docker_upload 2 | 3 | PYCACHE := $(shell find . -name '__pycache__') 4 | EGGS := $(wildcard *.egg-info) 5 | CURRENT_VERSION := $(shell awk '/current_version = / {print $$3}' pyproject.toml) 6 | 7 | # https://github.com/jupyter/nbconvert/issues/637 8 | 9 | JQ_RULES := '(.cells[] | select(has("outputs")) | .outputs) = [] \ 10 | | (.cells[] | select(has("execution_count")) | .execution_count) = null \ 11 | | .metadata = { \ 12 | "language_info": {"name":"python", "pygments_lexer": "ipython3"}, \ 13 | "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"} \ 14 | } \ 15 | | .cells[].metadata = {}' 16 | 17 | clean_notebooks: ./examples/*.ipynb ./examples/assemblies/*.ipynb 18 | @for file in $^ ; do \ 19 | echo "$${file}" ; \ 20 | jq --indent 1 $(JQ_RULES) "$${file}" > "$${file}_clean"; \ 21 | mv "$${file}_clean" "$${file}"; \ 22 | python validate_nb.py "$${file}"; \ 23 | done 24 | 25 | clean: clean_notebooks 26 | @echo "=> Cleaning" 27 | @rm -fr build dist $(EGGS) $(PYCACHE) 28 | 29 | prepare: clean 30 | git add . 31 | git status 32 | git commit -m "cleanup before release" 33 | 34 | # Version commands 35 | 36 | bump: 37 | @echo Current version: $(CURRENT_VERSION) 38 | ifdef part 39 | bump-my-version bump $(part) --allow-dirty && grep current pyproject.toml 40 | else ifdef version 41 | bump-my-version bump --allow-dirty --new-version $(version) && grep current pyproject.toml 42 | else 43 | @echo "Provide part=major|minor|patch|release|build and optionally version=x.y.z..." 44 | exit 1 45 | endif 46 | 47 | # Dist commands 48 | 49 | dist: 50 | @rm -f dist/* 51 | @python -m build 52 | 53 | release: 54 | git add . 55 | git status 56 | git diff-index --quiet HEAD || git commit -m "Latest release: $(CURRENT_VERSION)" 57 | git tag -a v$(CURRENT_VERSION) -m "Latest release: $(CURRENT_VERSION)" 58 | 59 | create-release: 60 | @github-release release -u bernhard-42 -r jupyter-cadquery -t v$(CURRENT_VERSION) -n jupyter-cadquery-$(CURRENT_VERSION) 61 | @sleep 2 62 | @github-release upload -u bernhard-42 -r jupyter-cadquery -t v$(CURRENT_VERSION) -n jupyter_cadquery-$(CURRENT_VERSION).tar.gz -f dist/jupyter_cadquery-$(CURRENT_VERSION).tar.gz 63 | @github-release upload -u bernhard-42 -r jupyter-cadquery -t v$(CURRENT_VERSION) -n jupyter_cadquery-$(CURRENT_VERSION)-py3-none-any.whl -f dist/jupyter_cadquery-$(CURRENT_VERSION)-py3-none-any.whl 64 | 65 | install: dist 66 | @echo "=> Installing jupyter_cadquery" 67 | @pip install --upgrade . 68 | 69 | check_dist: 70 | @twine check dist/* 71 | 72 | upload: 73 | @twine upload dist/* 74 | 75 | docker: 76 | @rm -fr docker/examples docker/requirements.txt 77 | @cp -R examples requirements.txt docker/ 78 | @cd docker && docker build -t bwalter42/jupyter_cadquery:$(CURRENT_VERSION) . 79 | @rm -fr docker/examples docker/requirements.txt 80 | 81 | upload_docker: 82 | @docker push bwalter42/jupyter_cadquery:$(CURRENT_VERSION) 83 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release documentation 2 | 3 | ## Release process 4 | 5 | In case the jupyter labextions and/or the python code has been changed: 6 | 7 | 1. Run tests 8 | 9 | ```bash 10 | make tests 11 | ``` 12 | 13 | 2. Clean environment 14 | 15 | ```bash 16 | make clean # delete all temp files 17 | make prepare # commit deletions 18 | ``` 19 | 20 | 3. Bump version of jupyter_cadquery 21 | 22 | - A new release candidate with rc0 23 | 24 | ```bash 25 | make bump part=major|minor|patch 26 | ``` 27 | 28 | - A new build 29 | 30 | ```bash 31 | make bump part=build 32 | ``` 33 | 34 | - A new release 35 | 36 | ```bash 37 | make bump part=release 38 | ``` 39 | 40 | - A new release without release candidate 41 | 42 | ```bash 43 | make bump part=major|minor|patch version=major.minor.patch 44 | ``` 45 | 46 | 4. Create distribution 47 | 48 | ```bash 49 | make dist 50 | ``` 51 | 52 | 5. Create and tag release 53 | 54 | ```bash 55 | make release 56 | ``` 57 | 58 | 6. Deploy to pypi 59 | 60 | ```bash 61 | make upload 62 | ``` 63 | 64 | 7. Create Docker container 65 | 66 | ```bash 67 | make docker 68 | ``` 69 | 70 | 7. Upload Docker container 71 | 72 | ```bash 73 | make upload_docker 74 | ``` 75 | 76 | ### 3 Push changes 77 | 78 | 1. Push repo and tag 79 | 80 | ```bash 81 | git push 82 | git push origin --tags 83 | ``` 84 | -------------------------------------------------------------------------------- /apt.txt: -------------------------------------------------------------------------------- 1 | libgl1-mesa-glx 2 | libglu1-mesa -------------------------------------------------------------------------------- /doc/clipping.md: -------------------------------------------------------------------------------- 1 | ## Clipping 2 | 3 | ![Overview](../screenshots/clipping.gif) 4 | -------------------------------------------------------------------------------- /doc/cqparts.md: -------------------------------------------------------------------------------- 1 | ## CQParts support 2 | 3 | ![Overview](../screenshots/cqparts.gif) -------------------------------------------------------------------------------- /doc/faces-edges-vertices.md: -------------------------------------------------------------------------------- 1 | ## Faces Edges Vertices 2 | 3 | ![Overview](../screenshots/faces-edges-vertices.gif) -------------------------------------------------------------------------------- /doc/features.md: -------------------------------------------------------------------------------- 1 | ## Viewing Features 2 | 3 | ![Overview](../screenshots/overview.gif) -------------------------------------------------------------------------------- /doc/jupyter-cadquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/doc/jupyter-cadquery.png -------------------------------------------------------------------------------- /doc/occ.md: -------------------------------------------------------------------------------- 1 | ## OCC support 2 | 3 | ![Overview](../screenshots/occ.gif) 4 | -------------------------------------------------------------------------------- /doc/replay.md: -------------------------------------------------------------------------------- 1 | ## Replay (*experimental*) 2 | 3 | ![Overview](../screenshots/replay.gif) -------------------------------------------------------------------------------- /doc/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/doc/splash.png -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | RUN apt-get -qq update && \ 4 | apt-get -qq install --yes --no-install-recommends libgl1-mesa-glx libglu1-mesa libxrender-dev gettext-base less unzip git > /dev/null && \ 5 | apt-get -qq purge && \ 6 | apt-get -qq clean && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | COPY requirements.txt /tmp 10 | RUN pip install --no-cache-dir -r /tmp/requirements.txt 11 | 12 | RUN useradd -d /home/workdir -s /bin/bash cq 13 | RUN mkdir /home/examples-read-only 14 | 15 | VOLUME /home/workdir/ 16 | WORKDIR /home/ 17 | EXPOSE 8888 18 | 19 | USER cq 20 | 21 | ADD --chown=cq:cq examples /home/examples-read-only 22 | ADD --chown=cq:cq run.sh /tmp 23 | RUN chmod +x /tmp/run.sh 24 | 25 | ENTRYPOINT ["/tmp/run.sh"] -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VIEWER=0 4 | export THEME=light 5 | 6 | while getopts "dvgw:h:" o; do 7 | case "${o}" in 8 | g) 9 | export GLASS_MODE=1 10 | ;; 11 | w) 12 | export CAD_WIDTH=${OPTARG} 13 | ;; 14 | h) 15 | export CAD_HEIGHT=${OPTARG} 16 | ;; 17 | d) 18 | export THEME=dark 19 | ;; 20 | esac 21 | done 22 | 23 | echo "Starting in JupyterLab mode: http://localhost:8888/lab" 24 | jupyter lab --ip=0.0.0.0 --no-browser --NotebookApp.token='' --NotebookApp.allow_origin='*' 25 | -------------------------------------------------------------------------------- /environment.yml.bak: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | - cadquery 4 | dependencies: 5 | - python=3.12 6 | - matplotlib=3.10.1 7 | - pip=25.0.1 8 | - pip: 9 | - git+https://github.com/cadquery/cadquery 10 | - git+https://github.com/gumyr/build123d 11 | - jupyter-cadquery==4.0.2 12 | - cadquery-massembly==1.0.0 13 | -------------------------------------------------------------------------------- /examples/3-occ.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from jupyter_cadquery import show, open_viewer\n", 10 | "open_viewer(\"OCC\")" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# OCC bottle (ported over to OCP)" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import math\n", 27 | "\n", 28 | "from OCP.gp import gp_Pnt, gp_Vec, gp_Trsf, gp_Ax2, gp_Ax3, gp_Pnt2d, gp_Dir2d, gp_Ax2d, gp, gp_Pln\n", 29 | "from OCP.GC import GC_MakeArcOfCircle, GC_MakeSegment\n", 30 | "from OCP.GCE2d import GCE2d_MakeSegment\n", 31 | "from OCP.Geom import Geom_Plane, Geom_CylindricalSurface\n", 32 | "from OCP.Geom2d import Geom2d_Ellipse, Geom2d_TrimmedCurve\n", 33 | "from OCP.GeomAbs import GeomAbs_Plane\n", 34 | "from OCP.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire,\n", 35 | " BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform)\n", 36 | "from OCP.BRepPrimAPI import BRepPrimAPI_MakePrism, BRepPrimAPI_MakeCylinder\n", 37 | "from OCP.BRepFilletAPI import BRepFilletAPI_MakeFillet\n", 38 | "from OCP.BRepAlgoAPI import BRepAlgoAPI_Fuse\n", 39 | "from OCP.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid, BRepOffsetAPI_ThruSections\n", 40 | "from OCP.BRepAdaptor import BRepAdaptor_Surface\n", 41 | "from OCP.BRepLib import BRepLib\n", 42 | "from OCP.BRep import BRep_Tool\n", 43 | "from OCP.BRep import BRep_Builder\n", 44 | "from OCP.TopoDS import TopoDS, TopoDS_Compound, TopoDS_Builder, TopoDS_Face\n", 45 | "from OCP.TopExp import TopExp_Explorer\n", 46 | "from OCP.TopAbs import TopAbs_EDGE, TopAbs_FACE\n", 47 | "from OCP.TopTools import TopTools_ListOfShape\n", 48 | "\n", 49 | "\n", 50 | "def face_is_plane(face: TopoDS_Face) -> bool:\n", 51 | " \"\"\"\n", 52 | " Returns True if the TopoDS_Face is a plane, False otherwise\n", 53 | " \"\"\"\n", 54 | " surf = BRepAdaptor_Surface(face, True)\n", 55 | " surf_type = surf.GetType()\n", 56 | " return surf_type == GeomAbs_Plane\n", 57 | "\n", 58 | "\n", 59 | "def geom_plane_from_face(aFace: TopoDS_Face) -> gp_Pln:\n", 60 | " \"\"\"\n", 61 | " Returns the geometric plane entity from a planar surface\n", 62 | " \"\"\"\n", 63 | " return BRepAdaptor_Surface(aFace, True).Plane()\n", 64 | "\n", 65 | "\n", 66 | "height = 70\n", 67 | "width = 50\n", 68 | "thickness = 30\n", 69 | "\n", 70 | "print(\"creating bottle\")\n", 71 | "# The points we'll use to create the profile of the bottle's body\n", 72 | "aPnt1 = gp_Pnt(-width / 2.0, 0, 0)\n", 73 | "aPnt2 = gp_Pnt(-width / 2.0, -thickness / 4.0, 0)\n", 74 | "aPnt3 = gp_Pnt(0, -thickness / 2.0, 0)\n", 75 | "aPnt4 = gp_Pnt(width / 2.0, -thickness / 4.0, 0)\n", 76 | "aPnt5 = gp_Pnt(width / 2.0, 0, 0)\n", 77 | "\n", 78 | "aArcOfCircle = GC_MakeArcOfCircle(aPnt2, aPnt3, aPnt4)\n", 79 | "aSegment1 = GC_MakeSegment(aPnt1, aPnt2)\n", 80 | "aSegment2 = GC_MakeSegment(aPnt4, aPnt5)\n", 81 | "\n", 82 | "# Could also construct the line edges directly using the points instead of the resulting line\n", 83 | "aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1.Value())\n", 84 | "aEdge2 = BRepBuilderAPI_MakeEdge(aArcOfCircle.Value())\n", 85 | "aEdge3 = BRepBuilderAPI_MakeEdge(aSegment2.Value())\n", 86 | "\n", 87 | "# Create a wire out of the edges\n", 88 | "aWire = BRepBuilderAPI_MakeWire(aEdge1.Edge(), aEdge2.Edge(), aEdge3.Edge())\n", 89 | "\n", 90 | "# Quick way to specify the X axis\n", 91 | "xAxis = gp.OX_s()\n", 92 | "\n", 93 | "# Set up the mirror\n", 94 | "aTrsf = gp_Trsf()\n", 95 | "aTrsf.SetMirror(xAxis)\n", 96 | "\n", 97 | "# Apply the mirror transformation\n", 98 | "aBRespTrsf = BRepBuilderAPI_Transform(aWire.Wire(), aTrsf)\n", 99 | "\n", 100 | "# Get the mirrored shape back out of the transformation and convert back to a wire\n", 101 | "aMirroredShape = aBRespTrsf.Shape()\n", 102 | "\n", 103 | "# A wire instead of a generic shape now\n", 104 | "aMirroredWire = TopoDS.Wire_s(aMirroredShape)\n", 105 | "\n", 106 | "# Combine the two constituent wires\n", 107 | "mkWire = BRepBuilderAPI_MakeWire()\n", 108 | "mkWire.Add(aWire.Wire())\n", 109 | "mkWire.Add(aMirroredWire)\n", 110 | "myWireProfile = mkWire.Wire()\n", 111 | "\n", 112 | "# The face that we'll sweep to make the prism\n", 113 | "myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile)\n", 114 | "\n", 115 | "# We want to sweep the face along the Z axis to the height\n", 116 | "aPrismVec = gp_Vec(0, 0, height)\n", 117 | "myBody_step1 = BRepPrimAPI_MakePrism(myFaceProfile.Face(), aPrismVec)\n", 118 | "\n", 119 | "# Add fillets to all edges through the explorer\n", 120 | "mkFillet = BRepFilletAPI_MakeFillet(myBody_step1.Shape())\n", 121 | "anEdgeExplorer = TopExp_Explorer(myBody_step1.Shape(), TopAbs_EDGE)\n", 122 | "\n", 123 | "while anEdgeExplorer.More():\n", 124 | " anEdge = TopoDS.Edge_s(anEdgeExplorer.Current())\n", 125 | " mkFillet.Add(thickness / 12.0, anEdge)\n", 126 | "\n", 127 | " anEdgeExplorer.Next()\n", 128 | "\n", 129 | "# Create the neck of the bottle\n", 130 | "neckLocation = gp_Pnt(0, 0, height)\n", 131 | "neckAxis = gp.DZ_s()\n", 132 | "neckAx2 = gp_Ax2(neckLocation, neckAxis)\n", 133 | "\n", 134 | "myNeckRadius = thickness / 4.0\n", 135 | "myNeckHeight = height / 10.0\n", 136 | "\n", 137 | "mkCylinder = BRepPrimAPI_MakeCylinder(neckAx2, myNeckRadius, myNeckHeight)\n", 138 | "\n", 139 | "myBody_step2 = BRepAlgoAPI_Fuse(mkFillet.Shape(), mkCylinder.Shape())\n", 140 | "\n", 141 | "# Our goal is to find the highest Z face and remove it\n", 142 | "zMax = -1.0\n", 143 | "\n", 144 | "# We have to work our way through all the faces to find the highest Z face so we can remove it for the shell\n", 145 | "aFaceExplorer = TopExp_Explorer(myBody_step2.Shape(), TopAbs_FACE)\n", 146 | "while aFaceExplorer.More():\n", 147 | " aFace = TopoDS.Face_s(aFaceExplorer.Current())\n", 148 | " if face_is_plane(aFace):\n", 149 | " aPlane = geom_plane_from_face(aFace)\n", 150 | "\n", 151 | " # We want the highest Z face, so compare this to the previous faces\n", 152 | " aPntLoc = aPlane.Location()\n", 153 | " aZ = aPntLoc.Z()\n", 154 | " if aZ > zMax:\n", 155 | " zMax = aZ\n", 156 | " aFaceExplorer.Next()\n", 157 | "\n", 158 | "facesToRemove = TopTools_ListOfShape()\n", 159 | "facesToRemove.Append(aFace)\n", 160 | "\n", 161 | "mk_thick_solid = BRepOffsetAPI_MakeThickSolid()\n", 162 | "mk_thick_solid.MakeThickSolidByJoin(\n", 163 | " myBody_step2.Shape(), facesToRemove, -thickness / 50.0, 0.001\n", 164 | ")\n", 165 | "mk_thick_solid.Build()\n", 166 | "myBody_step3 = mk_thick_solid.Shape()\n", 167 | "\n", 168 | "# Set up our surfaces for the threading on the neck\n", 169 | "neckAx2_Ax3 = gp_Ax3(neckLocation, gp.DZ_s())\n", 170 | "aCyl1 = Geom_CylindricalSurface(neckAx2_Ax3, myNeckRadius * 0.99)\n", 171 | "aCyl2 = Geom_CylindricalSurface(neckAx2_Ax3, myNeckRadius * 1.05)\n", 172 | "\n", 173 | "# Set up the curves for the threads on the bottle's neck\n", 174 | "aPnt = gp_Pnt2d(2.0 * math.pi, myNeckHeight / 2.0)\n", 175 | "aDir = gp_Dir2d(2.0 * math.pi, myNeckHeight / 4.0)\n", 176 | "anAx2d = gp_Ax2d(aPnt, aDir)\n", 177 | "\n", 178 | "aMajor = 2.0 * math.pi\n", 179 | "aMinor = myNeckHeight / 10.0\n", 180 | "\n", 181 | "anEllipse1 = Geom2d_Ellipse(anAx2d, aMajor, aMinor)\n", 182 | "anEllipse2 = Geom2d_Ellipse(anAx2d, aMajor, aMinor / 4.0)\n", 183 | "\n", 184 | "anArc1 = Geom2d_TrimmedCurve(anEllipse1, 0, math.pi)\n", 185 | "anArc2 = Geom2d_TrimmedCurve(anEllipse2, 0, math.pi)\n", 186 | "\n", 187 | "anEllipsePnt1 = anEllipse1.Value(0)\n", 188 | "anEllipsePnt2 = anEllipse1.Value(math.pi)\n", 189 | "\n", 190 | "aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2)\n", 191 | "\n", 192 | "# Build edges and wires for threading\n", 193 | "anEdge1OnSurf1 = BRepBuilderAPI_MakeEdge(anArc1, aCyl1)\n", 194 | "anEdge2OnSurf1 = BRepBuilderAPI_MakeEdge(aSegment.Value(), aCyl1)\n", 195 | "anEdge1OnSurf2 = BRepBuilderAPI_MakeEdge(anArc2, aCyl2)\n", 196 | "anEdge2OnSurf2 = BRepBuilderAPI_MakeEdge(aSegment.Value(), aCyl2)\n", 197 | "\n", 198 | "threadingWire1 = BRepBuilderAPI_MakeWire(anEdge1OnSurf1.Edge(), anEdge2OnSurf1.Edge())\n", 199 | "threadingWire2 = BRepBuilderAPI_MakeWire(anEdge1OnSurf2.Edge(), anEdge2OnSurf2.Edge())\n", 200 | "\n", 201 | "# Compute the 3D representations of the edges/wires\n", 202 | "BRepLib.BuildCurves3d_s(threadingWire1.Shape())\n", 203 | "BRepLib.BuildCurves3d_s(threadingWire2.Shape())\n", 204 | "\n", 205 | "# Create the surfaces of the threading\n", 206 | "aTool = BRepOffsetAPI_ThruSections(True)\n", 207 | "aTool.AddWire(threadingWire1.Wire())\n", 208 | "aTool.AddWire(threadingWire2.Wire())\n", 209 | "aTool.CheckCompatibility(False)\n", 210 | "myThreading = aTool.Shape()\n", 211 | "\n", 212 | "# Build the resulting compound\n", 213 | "bottle = TopoDS_Compound()\n", 214 | "aBuilder = BRep_Builder()\n", 215 | "aBuilder.MakeCompound(bottle)\n", 216 | "aBuilder.Add(bottle, myBody_step3)\n", 217 | "aBuilder.Add(bottle, myThreading)\n", 218 | "print(\"bottle finished\")" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "show(bottle, colors=[\"aliceblue\"], ambient_intensity=0.6, direct_intensity=0.18)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": null, 233 | "metadata": {}, 234 | "outputs": [], 235 | "source": [] 236 | } 237 | ], 238 | "metadata": { 239 | "language_info": { 240 | "name": "python", 241 | "pygments_lexer": "ipython3" 242 | }, 243 | "kernelspec": { 244 | "display_name": "Python 3", 245 | "language": "python", 246 | "name": "python3" 247 | } 248 | }, 249 | "nbformat": 4, 250 | "nbformat_minor": 4 251 | } 252 | -------------------------------------------------------------------------------- /examples/4-sketches.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import cadquery as cq" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from jupyter_cadquery import show, set_defaults, open_viewer, Camera\n", 19 | "from jupyter_cadquery.replay import enable_replay, disable_replay, reset_replay, get_context, replay, Replay, _CTX\n", 20 | "from ocp_tessellate.convert import to_assembly\n", 21 | "\n", 22 | "cv = open_viewer(\"Box\", cad_width=780, height=525)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "set_defaults(reset_camera=Camera.RESET, show_parent=False, axes=True, axes0=True)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "# Sketch" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "use_replay = True\n", 48 | "\n", 49 | "if use_replay:\n", 50 | " enable_replay(show_bbox=False, warning=False)\n", 51 | " reset_replay()\n", 52 | " show_object = replay\n", 53 | "else:\n", 54 | " disable_replay()\n", 55 | " show_object = show" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "result = (\n", 65 | " cq.Sketch()\n", 66 | " .segment((0.,0),(2.,0.))\n", 67 | " .segment((0.,2))\n", 68 | " .close()\n", 69 | " .arc((.6,.6),0.4,0.,360.)\n", 70 | " .assemble(tag='face')\n", 71 | " .edges('%LINE',tag='face')\n", 72 | " .vertices()\n", 73 | " .chamfer(0.2)\n", 74 | " .reset()\n", 75 | ")\n", 76 | "r = show_object(result, show_bbox=False)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "result = (\n", 86 | " cq.Workplane()\n", 87 | " .transformed((0,90,90),(2,0,0))\n", 88 | " .box(5,5,1)\n", 89 | " .faces('>X')\n", 90 | " .sketch()\n", 91 | " .regularPolygon(2,3,tag='outer')\n", 92 | " .regularPolygon(1.5,3,mode='s')\n", 93 | " .vertices(tag='outer')\n", 94 | " .fillet(.2)\n", 95 | " .reset()\n", 96 | " .finalize()\n", 97 | " .extrude(0.5)\n", 98 | ")\n", 99 | "r = show_object(result, show_bbox=True) # , show_result=False)" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": null, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "result = (cq\n", 109 | " .Sketch()\n", 110 | " .trapezoid(4, 3, 90)\n", 111 | " .vertices()\n", 112 | " .circle(0.5, mode='s')\n", 113 | " .reset()\n", 114 | " .vertices()\n", 115 | " .fillet(0.25)\n", 116 | " .reset()\n", 117 | " .rarray(0.6, 1, 5, 1)\n", 118 | " .slot(01.5, 0.4, mode='s', angle=90)\n", 119 | " .reset()\n", 120 | ")\n", 121 | "show_object(result)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "result = (\n", 131 | " cq.Workplane()\n", 132 | " .box(5,5,1)\n", 133 | " .faces('>Z')\n", 134 | " .workplane()\n", 135 | " .rarray(2,2,2,2)\n", 136 | " .rect(1.5,1.5)\n", 137 | " .extrude(.5)\n", 138 | " .faces('>Z')\n", 139 | " .sketch()\n", 140 | " .circle(0.4)\n", 141 | " .wires()\n", 142 | " .distribute(6)\n", 143 | " .circle(0.1,mode='a')\n", 144 | " .clean()\n", 145 | " .finalize()\n", 146 | " .cutBlind(-0.5,taper=10)\n", 147 | ")\n", 148 | "\n", 149 | "# show(result,reset_camera=False)\n", 150 | "show_object(result, show_bbox=True)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "s = (\n", 160 | " cq.Sketch()\n", 161 | " .trapezoid(3,1,110)\n", 162 | " .vertices()\n", 163 | " .fillet(0.2)\n", 164 | ")\n", 165 | "\n", 166 | "result = (\n", 167 | " cq.Workplane()\n", 168 | " .box(5,5,5)\n", 169 | " .faces('>X')\n", 170 | " .workplane()\n", 171 | " .transformed((0, 0, -90))\n", 172 | " .placeSketch(s)\n", 173 | " .cutThruAll()\n", 174 | ")\n", 175 | "\n", 176 | "show_object(result, show_bbox=True)" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "s1 = (\n", 186 | " cq.Sketch()\n", 187 | " .trapezoid(3, 1, 110)\n", 188 | " .vertices()\n", 189 | " .fillet(0.2)\n", 190 | " .reset()\n", 191 | ")\n", 192 | "\n", 193 | "s2 = (\n", 194 | " cq.Sketch()\n", 195 | " .rect(2,1)\n", 196 | " .vertices()\n", 197 | " .fillet(0.2)\n", 198 | " .reset()\n", 199 | " .moved(cq.Location(cq.Vector(0, 0, 3)))\n", 200 | ")\n", 201 | "\n", 202 | "result = (\n", 203 | " cq.Workplane()\n", 204 | " .placeSketch(s1, s2)\n", 205 | " .loft()\n", 206 | ")\n", 207 | "\n", 208 | "r = show_object(result, show_bbox=True)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": {}, 215 | "outputs": [], 216 | "source": [ 217 | "result = (\n", 218 | " cq.Sketch()\n", 219 | " .rect(1, 2, mode='c', tag='base')\n", 220 | " .vertices(tag='base')\n", 221 | " .circle(.7)\n", 222 | " .reset()\n", 223 | " .edges('|Y', tag='base')\n", 224 | " .ellipse(1.2, 1, mode='i')\n", 225 | " .reset()\n", 226 | " .rect(2, 2, mode='i')\n", 227 | " .clean()\n", 228 | ")\n", 229 | "\n", 230 | "r = show_object(result, debug=False)" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "a = cq.Sketch().rect(1, 2, mode='c', tag='base')\n", 240 | "a._faces" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "result = (\n", 250 | " cq.Sketch()\n", 251 | " .segment((0,0), (0,3.),\"s1\")\n", 252 | " .arc((0.,3.), (1.5,1.5), (0.,0.),\"a1\")\n", 253 | " .constrain(\"s1\",\"Fixed\",None)\n", 254 | " .constrain(\"s1\", \"a1\",\"Coincident\",None)\n", 255 | " .constrain(\"a1\", \"s1\",\"Coincident\",None)\n", 256 | " .constrain(\"s1\",'a1', \"Angle\", 45)\n", 257 | " .solve()\n", 258 | " .assemble()\n", 259 | ")\n", 260 | "replay(result)" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "result = (\n", 270 | " cq.Sketch()\n", 271 | " .arc((0,0),1.,0.,360.)\n", 272 | " .arc((1,1.5),0.5,0.,360.)\n", 273 | " .segment((0.,2),(-1,3.))\n", 274 | " .hull()\n", 275 | ")\n", 276 | "replay(result)" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [] 285 | } 286 | ], 287 | "metadata": { 288 | "language_info": { 289 | "name": "python", 290 | "pygments_lexer": "ipython3" 291 | }, 292 | "kernelspec": { 293 | "display_name": "Python 3", 294 | "language": "python", 295 | "name": "python3" 296 | } 297 | }, 298 | "nbformat": 4, 299 | "nbformat_minor": 4 300 | } 301 | -------------------------------------------------------------------------------- /examples/5-build123d.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "11d86549-d553-4483-be2d-24ec757d2630", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from jupyter_cadquery import show, open_viewer, set_defaults\n", 11 | "from build123d import *\n", 12 | "\n", 13 | "cv = open_viewer(\"Build123d\", cad_width=780, glass=True)\n", 14 | "\n", 15 | "set_defaults(edge_accuracy=0.0001)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "id": "abe2d029-0b01-46ca-aa08-eb22221fbd16", 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "with BuildPart() as b:\n", 26 | " Box(1, 1, 1)\n", 27 | " CounterBoreHole(0.2, 0.3, 0.1)\n", 28 | " Box(1, 0.2, 1, mode=Mode.SUBTRACT)\n", 29 | " Box(0.2, 1, 1, mode=Mode.SUBTRACT)\n", 30 | "\n", 31 | "with BuildSketch() as s:\n", 32 | " with BuildLine() as line:\n", 33 | " l = Line((0, 0), (1, 0))\n", 34 | " Line(l @ 1, (1, 1))\n", 35 | " offset(line.line.edges(), amount=1)\n", 36 | " make_face()\n", 37 | "\n", 38 | "with BuildLine() as l:\n", 39 | " l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\n", 40 | " l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\n", 41 | " RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\n", 42 | " l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\n", 43 | " TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\n", 44 | " l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\n", 45 | " SagittaArc(l3 @ 1, l4 @ 0, 0.003)\n", 46 | " l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\n", 47 | " ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\n", 48 | " l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\n", 49 | " Spline(l5 @ 1, l6 @ 0, tangents=(l5 % 1, l6 % 0), tangent_scalars=(2, 2))\n", 50 | " l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\n", 51 | " TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n", 52 | " mirror(l.edges(), about=Plane.YZ)\n" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "id": "e97cf194-caf2-4549-9607-c985e1fcc9e1", 58 | "metadata": {}, 59 | "source": [ 60 | "## BuildPart, BuildSketch, BuildLine" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "id": "968f720a-2e00-414d-9404-f7ac6516bd3b", 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "show(b)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "id": "a2568530-bd57-4364-baae-d16e49ff69fc", 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [ 80 | "show(s)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "id": "c8a9da63-e685-4c43-9989-b8fcde637535", 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "show(l)" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "5696f436-1d19-42fe-af89-ccefc397d325", 96 | "metadata": {}, 97 | "source": [ 98 | "## BuildPart.part, BuildSketch.sketch, BuildLine.line" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "id": "c79c3236-f8a6-4819-b01b-5de97ec2bf93", 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "show(b.part)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "id": "8e2d9e05-e924-46cf-b24a-e0dfc162000b", 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "show(s.sketch)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "id": "85226f6b-331a-4cbf-a84c-a816a19b255a", 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "show(l.line)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "id": "c3011fd1-f339-4c50-8e34-7cbc7a2d4093", 134 | "metadata": {}, 135 | "source": [ 136 | "## BuildPart.part.wrapped, BuildSketch.sketch.wrapped, BuildLine.line.wrapped" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "id": "231d821f-4fa4-4fc2-bfba-6d466dca5ae9", 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [ 146 | "show(b.part.wrapped)" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "id": "7d7ca0b5-3e42-4d4f-83b0-05353e4b8940", 153 | "metadata": {}, 154 | "outputs": [], 155 | "source": [ 156 | "show(s.sketch.wrapped)" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "id": "3bc418d9-99d5-4c5e-bf56-f122b8d6a502", 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "show(l.line.wrapped)" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "id": "453c177d-659d-4440-9ddb-07b8ed06ed0f", 172 | "metadata": {}, 173 | "source": [ 174 | "## ShapeLists" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "id": "605786fc-e1b6-4abe-859b-6942968c1857", 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "show(b.solids()[0:2])" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "id": "7b72766c-2ec7-42fc-bbb5-f1485d237795", 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "show(b, b.faces()[0:2], transparent=True)" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "id": "b9c46806-c793-4292-9405-a99c00e2cdbc", 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "show(b, b.wires()[0:2], transparent=True)" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "id": "ad7fca69-e566-4cc6-beee-f2cba8c2ff58", 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "show(b, b.edges()[0:2], transparent=True)" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "id": "09ebb2f9-5c59-4fb6-ba47-6b04a026a6e2", 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "show(b, b.vertices()[0:2], transparent=True)" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "2333e981-8542-4717-9e1a-2b7c3c5ac1e3", 230 | "metadata": {}, 231 | "source": [ 232 | "## Direct API" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": null, 238 | "id": "ae1b5db7-52a8-4f8f-8081-42a7e5f77569", 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "show(b.solids().sort_by(Axis.X)[-1])" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "id": "e0389b26-73b4-42e4-9a9d-5b9f60ac7c06", 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "show(b, b.faces().sort_by(Axis.Z)[-1], transparent=True)" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": null, 258 | "id": "a3569ef9-6ce2-4fbd-a80b-146d5021617f", 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [ 262 | "show(b, b.wires().sort_by(Axis.Z)[-1], transparent=True)" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "id": "25a94934-f007-4e8f-adf6-0ef6a43a0a48", 269 | "metadata": {}, 270 | "outputs": [], 271 | "source": [ 272 | "show(b, b.edges().sort_by(Axis.Z)[-1], transparent=True)" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "id": "0b556940-5bfd-4341-ad4a-9ab33f4ceabd", 279 | "metadata": {}, 280 | "outputs": [], 281 | "source": [ 282 | "show(b, b.vertices().sort_by(Axis.Z)[-1], transparent=True)" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": null, 288 | "id": "8ea8ac91-745b-4e74-8e45-2b1cd99d0e3c", 289 | "metadata": {}, 290 | "outputs": [], 291 | "source": [ 292 | "show(b.solids().sort_by(Axis.X)[-1].wrapped)" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": null, 298 | "id": "3c59d354-fbf9-4ad0-944a-1c2619f9fbdf", 299 | "metadata": {}, 300 | "outputs": [], 301 | "source": [ 302 | "show(b, b.faces().sort_by(Axis.Z)[-1].wrapped, transparent=True)" 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "execution_count": null, 308 | "id": "5264c499-df8c-4a4f-a01d-0209249541a4", 309 | "metadata": {}, 310 | "outputs": [], 311 | "source": [ 312 | "show(b, b.wires().sort_by(Axis.Z)[-1].wrapped, transparent=True)" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": null, 318 | "id": "d7a77e19-73de-4008-aeb7-4fdb667c7dc7", 319 | "metadata": {}, 320 | "outputs": [], 321 | "source": [ 322 | "show(b, b.edges().sort_by(Axis.Z)[-1].wrapped, transparent=True)" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "id": "8d06bbea-655c-4e3d-9b8a-89b74252da84", 329 | "metadata": {}, 330 | "outputs": [], 331 | "source": [ 332 | "show(b, b.vertices().sort_by(Axis.Z)[-1].wrapped, transparent=True)" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "id": "e0ff1d59-0aa5-4c4f-a776-7b207cc20101", 338 | "metadata": {}, 339 | "source": [ 340 | "## Tutorial" 341 | ] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "id": "131f03b6-536e-449e-a9d8-30ab94b99161", 346 | "metadata": {}, 347 | "source": [ 348 | "### Tutorial 1" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": null, 354 | "id": "15f76e5a-8ea2-452d-bdd8-594e456a4786", 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "with BuildPart() as example:\n", 359 | " Cylinder(radius=10, height=3)\n", 360 | " with BuildSketch(example.faces().sort_by()[-1]):\n", 361 | " RegularPolygon(radius=7, side_count=6)\n", 362 | " Circle(radius=4, mode=Mode.SUBTRACT)\n", 363 | " extrude(amount=-2, mode=Mode.SUBTRACT)\n", 364 | " fillet(\n", 365 | " example\n", 366 | " .edges()\n", 367 | " .filter_by(GeomType.CIRCLE)\n", 368 | " .sort_by(SortBy.RADIUS)[-2:]\n", 369 | " .sort_by(Axis.Z), \n", 370 | " radius=1\n", 371 | " )\n", 372 | "show(example)" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "id": "d2ee056e-264d-4ecf-b75a-aee23aec5016", 378 | "metadata": {}, 379 | "source": [ 380 | "### Tutorial 2" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "id": "46b35391-9f26-4f20-a787-b5eaf3b4c017", 387 | "metadata": {}, 388 | "outputs": [], 389 | "source": [ 390 | "pip_count = 6\n", 391 | "\n", 392 | "lego_unit_size = 8\n", 393 | "pip_height = 1.8\n", 394 | "pip_diameter = 4.8\n", 395 | "block_length = lego_unit_size * pip_count\n", 396 | "block_width = 16\n", 397 | "base_height = 9.6\n", 398 | "block_height = base_height + pip_height\n", 399 | "support_outer_diameter = 6.5\n", 400 | "support_inner_diameter = 4.8\n", 401 | "ridge_width = 0.6\n", 402 | "ridge_depth = 0.3\n", 403 | "wall_thickness = 1.2" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": null, 409 | "id": "55ca47f2-ec5a-4e7c-b646-9cb50f0671d8", 410 | "metadata": {}, 411 | "outputs": [], 412 | "source": [ 413 | "with BuildPart() as lego:\n", 414 | " with BuildSketch():\n", 415 | " perimeter = Rectangle(block_length, block_width)\n", 416 | " offset(\n", 417 | " perimeter,\n", 418 | " amount=-wall_thickness,\n", 419 | " kind=Kind.INTERSECTION,\n", 420 | " mode=Mode.SUBTRACT,\n", 421 | " )\n", 422 | " with GridLocations(0, lego_unit_size, 1, 2):\n", 423 | " Rectangle(block_length, ridge_width)\n", 424 | " with GridLocations(lego_unit_size, 0, pip_count, 1):\n", 425 | " Rectangle(ridge_width, block_width)\n", 426 | " Rectangle(\n", 427 | " block_length - 2 * (wall_thickness + ridge_depth),\n", 428 | " block_width - 2 * (wall_thickness + ridge_depth),\n", 429 | " mode=Mode.SUBTRACT,\n", 430 | " )\n", 431 | " with GridLocations(lego_unit_size, 0, pip_count - 1, 1):\n", 432 | " Circle(support_outer_diameter / 2)\n", 433 | " Circle(support_inner_diameter / 2, mode=Mode.SUBTRACT)\n", 434 | " extrude(amount=base_height - wall_thickness)\n", 435 | " with Locations(\n", 436 | " Plane(origin=(0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z), z_dir=(0, 0, 1)).location\n", 437 | " ):\n", 438 | " Box(\n", 439 | " block_length,\n", 440 | " block_width,\n", 441 | " wall_thickness,\n", 442 | " align=(Align.CENTER, Align.CENTER, Align.MIN),\n", 443 | " )\n", 444 | " with Locations(lego.faces().sort_by(Axis.Z)[-1]):\n", 445 | " with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):\n", 446 | " Cylinder(\n", 447 | " radius=pip_diameter / 2, height=pip_height, align=(Align.CENTER, Align.CENTER, Align.MIN)\n", 448 | " )\n" 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": null, 454 | "id": "ef340ca8-ece8-491d-8dfd-8c806704678d", 455 | "metadata": {}, 456 | "outputs": [], 457 | "source": [ 458 | "show(lego)" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "id": "98ef16b2-22fa-458e-8f0c-8451e1eef82a", 465 | "metadata": {}, 466 | "outputs": [], 467 | "source": [] 468 | } 469 | ], 470 | "metadata": { 471 | "language_info": { 472 | "name": "python", 473 | "pygments_lexer": "ipython3" 474 | }, 475 | "kernelspec": { 476 | "display_name": "Python 3", 477 | "language": "python", 478 | "name": "python3" 479 | } 480 | }, 481 | "nbformat": 4, 482 | "nbformat_minor": 5 483 | } 484 | -------------------------------------------------------------------------------- /examples/6-show_object.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "d14a2665-5d01-4537-bbd4-a7f623ebaa8d", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import cadquery as cq\n", 11 | "from jupyter_cadquery import show, show_object, open_viewer, set_defaults\n", 12 | "from build123d import *\n", 13 | "\n", 14 | "cv = open_viewer(\"Test\")\n", 15 | "\n", 16 | "set_defaults(show_parent=False)\n", 17 | "cv = open_viewer(\"Test\", cad_width=1000, aspect_ratio=1)" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "5f11600f-2e57-481c-97ee-410a495e6f4f", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "box = cq.Workplane().box(1,2,3).edges().chamfer(0.1)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "id": "56699f81-a851-432a-9322-cce4ab739ef6", 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "show_object(box.faces(\">X\"), name=\"green\", options={\"color\":\"green\", \"alpha\":0.2}, clear=True)\n", 38 | "show_object(box.faces(\"Z\"), name=\"blue\", options={\"color\":\"blue\"})\n", 40 | "show_object(box.faces(\">>Z[-2]\"), name=\"default\", options={\"alpha\":0.5}, axes=True)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "id": "da1b7214-d1f7-4065-b5eb-83e233cb3a66", 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "show_object(box.wires(\">X\"), name=\"green\", options={\"color\":\"green\"}, clear=True)\n", 51 | "show_object(box.wires(\"Z\"), name=\"blue\", options={\"color\":\"blue\"}, grid=(True, False, False))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "id": "d01a2a71-9b0d-4c9a-834d-2cacdfcaa535", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "show_object(box.edges(\"Y\"), name=\"red\", options={\"color\":\"red\"})\n", 64 | "show_object(box.edges(\"Y\"), name=\"red\", options={\"color\":\"red\"})\n", 76 | "show_object(box.vertices(\"Z[-2]\").wires(cq.NearestToPointSelector((dist_pivot + r_disk, 0))).tag(\"mate\")\n", 173 | "\n", 174 | "slot = (cq.Workplane()\n", 175 | " .rect(2*r_disk, 2*nr)\n", 176 | " .extrude(thickness)\n", 177 | " .union(nipple.translate((-r_disk, 0, 0)))\n", 178 | " .union(nipple.translate((r_disk, 0, 0)))\n", 179 | " .translate((dist_pivot, 0, 0))\n", 180 | ")\n", 181 | " \n", 182 | "arm = (\n", 183 | " cq.Workplane()\n", 184 | " .rect(4 * nr + (r_disk + dist_pivot), 4 * nr)\n", 185 | " .extrude(thickness)\n", 186 | " .edges(\"|Z\").fillet(3) \n", 187 | " .translate(((r_disk + dist_pivot) / 2, 0, 0))\n", 188 | " .cut(nipple)\n", 189 | " .cut(slot)\n", 190 | ")\n", 191 | "arm.faces(\">Z\").wires(cq.NearestToPointSelector((0,0))).tag(\"mate\")\n", 192 | "\n", 193 | "show(\n", 194 | " disk,\n", 195 | " base.translate((0, -1.5 * r_disk, 0)),\n", 196 | " arm.translate((0, 1.5 * r_disk, 0)),\n", 197 | ")" 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "metadata": {}, 203 | "source": [ 204 | "## Define assembly" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "import webcolors\n", 214 | "\n", 215 | "def create_disk_arm():\n", 216 | " L = lambda *args: cq.Location(cq.Vector(*args))\n", 217 | "\n", 218 | " return (MAssembly(base, name=\"base\", color=\"silver\", loc=L(-dist_pivot/2, 0, 0))\n", 219 | " .add(disk, name=\"disk\", color=\"MediumAquaMarine\", loc=L(r_disk, -1.5 * r_disk, 0))\n", 220 | " .add(arm, name=\"arm\", color=\"orange\", loc=L(0, 10*nr, 0))\n", 221 | " )" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "metadata": {}, 227 | "source": [ 228 | "## Define mates" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "metadata": {}, 235 | "outputs": [], 236 | "source": [ 237 | "from collections import OrderedDict as odict\n", 238 | "\n", 239 | "disk_arm = create_disk_arm()\n", 240 | "\n", 241 | "disk_arm.mate(\"base?mate\", name=\"disk_pivot\", origin=True, transforms=odict(rz=180))\n", 242 | "disk_arm.mate(\"base@faces@>Z\", name=\"arm_pivot\")\n", 243 | "disk_arm.mate(\"disk@faces@>Z[-2]\", name=\"disk\", origin=True)\n", 244 | "disk_arm.mate(\"arm?mate\", name=\"arm\", origin=True)\n", 245 | "\n", 246 | "show(disk_arm, render_mates=True)" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "## Relocate and assemble" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [ 262 | "# ensure all parts are relocated so that the origin mates is the part origin \n", 263 | "disk_arm.relocate()\n", 264 | "\n", 265 | "# assemble each part\n", 266 | "disk_arm.assemble(\"arm\", \"arm_pivot\")\n", 267 | "disk_arm.assemble(\"disk\", \"disk_pivot\")\n", 268 | "\n", 269 | "d = show(disk_arm, render_mates=True, axes=False)" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "# Animate" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "from jupyter_cadquery import AnimationTrack\n", 286 | "\n", 287 | "times = np.linspace(0, 5, 181)\n", 288 | "disk_angles = np.linspace(0, 360, 181)\n", 289 | "arm_angles = [angle_arm(d) for d in disk_angles]\n", 290 | "\n", 291 | "# move disk\n", 292 | "# Note, the selector must follow the path in the CAD view navigation hierarchy\n", 293 | "d.add_track(AnimationTrack(f\"/base/disk\", \"rz\", times, disk_angles))\n", 294 | "\n", 295 | "# move arm\n", 296 | "d.add_track(AnimationTrack(f\"/base/arm\", \"rz\", times, arm_angles))\n", 297 | "\n", 298 | "d.animate(speed=2)" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "metadata": {}, 305 | "outputs": [], 306 | "source": [] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": null, 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": null, 318 | "metadata": {}, 319 | "outputs": [], 320 | "source": [] 321 | } 322 | ], 323 | "metadata": { 324 | "language_info": { 325 | "name": "python", 326 | "pygments_lexer": "ipython3" 327 | }, 328 | "kernelspec": { 329 | "display_name": "Python 3", 330 | "language": "python", 331 | "name": "python3" 332 | } 333 | }, 334 | "nbformat": 4, 335 | "nbformat_minor": 4 336 | } 337 | -------------------------------------------------------------------------------- /examples/assemblies/1-disk-arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/examples/assemblies/1-disk-arm.png -------------------------------------------------------------------------------- /examples/assemblies/2-hexapod.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import cadquery as cq\n", 10 | "from jupyter_cadquery import (\n", 11 | " open_viewer, show,\n", 12 | " set_defaults, get_defaults\n", 13 | ")\n", 14 | "from cadquery_massembly import Mate, MAssembly, relocate\n", 15 | "\n", 16 | "cv = open_viewer(\"Hexapod\", cad_width=800, height=600, theme=\"light\")" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "set_defaults(axes=False, axes0=True, mate_scale=5, transparent=False)" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "# Hexapod " 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "![2-hexapod.png](2-hexapod.png)" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "import numpy as np\n", 49 | "horizontal_angle = 25\n", 50 | "\n", 51 | "def intervals(count):\n", 52 | " r = [ min(180, (90 + i*(360 // count)) % 360) for i in range(count)]\n", 53 | " return r \n", 54 | "\n", 55 | "def times(end, count):\n", 56 | " return np.linspace(0, end, count+1)\n", 57 | " \n", 58 | "def vertical(count, end, offset, reverse):\n", 59 | " ints = intervals(count)\n", 60 | " heights = [round(35 * np.sin(np.deg2rad(x)) - 15, 1) for x in ints]\n", 61 | " heights.append(heights[0])\n", 62 | " return times(end, count), heights[offset:] + heights[1:offset+1]\n", 63 | "\n", 64 | "def horizontal(end, reverse):\n", 65 | " factor = 1 if reverse else -1\n", 66 | " return times(end, 4), [0, factor * horizontal_angle, 0, -factor * horizontal_angle, 0]\n", 67 | "\n", 68 | "print(\"Leg group 1 (transparent)\")\n", 69 | "print(\"horizontal movement \", horizontal(4, True))\n", 70 | "print(\"vertical heights (left) \", vertical(8, 4, 0, True))\n", 71 | "print(\"vertical heights (right)\", vertical(8, 4, 0, False))\n", 72 | "\n", 73 | "print(\"\\nLeg group 1 (filled)\")\n", 74 | "print(\"horizontal movement\", horizontal(4, False))\n", 75 | "print(\"vertical heights (left) \", vertical(8, 4, 4, True))\n", 76 | "print(\"vertical heights (right)\", vertical(8, 4, 4, False))\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "# Assembly" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "## Parts" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "thickness = 2\n", 100 | "height = 40\n", 101 | "width = 65\n", 102 | "length = 100\n", 103 | "diam = 4\n", 104 | "tol = 0.05" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "def create_base():\n", 114 | " x1, x2 = 0.63, 0.87\n", 115 | " base_holes = {\n", 116 | " \"right_front\": (-x1*width, -x1*length), \"right_middle\": (-x2*width, 0), \"right_back\": ( -x1*width, x1*length ),\n", 117 | " \"left_front\": ( x1*width, -x1*length), \"left_middle\": ( x2*width, 0), \"left_back\": ( x1*width, x1*length ),\n", 118 | " }\n", 119 | " stand_holes = {\"front_stand\": (0, -0.75 * length), \"back_stand\": (0, 0.8 * length)}\n", 120 | "\n", 121 | " workplane = cq.Workplane()\n", 122 | "\n", 123 | " base = (workplane\n", 124 | " .ellipseArc(width, length, 295, 245, startAtCurrent=False).close()\n", 125 | " .pushPoints(list(base_holes.values())).circle(diam / 2 + tol)\n", 126 | " .moveTo(*stand_holes[\"back_stand\" ]).rect(width / 2 + 2 * tol, thickness + 2 * tol)\n", 127 | " .moveTo(*stand_holes[\"front_stand\"]).rect(width / 2 + 2 * tol, thickness + 2 * tol)\n", 128 | " .extrude(thickness)\n", 129 | " )\n", 130 | " base\n", 131 | "\n", 132 | "\n", 133 | " base.faces(\"Z\").tag(\"top\")\n", 135 | "\n", 136 | " for name, hole in base_holes.items():\n", 137 | " base.faces(\"Z\").edges(cq.NearestToPointSelector(upper_leg_hole)).tag(\"top\")\n", 194 | " upper_leg.faces(\"Z\").edges(cq.NearestToPointSelector(lower_leg_hole)).tag(\"top\"),\n", 212 | " lower_leg.faces(\" r0 + r1) or (d < abs(r1 - r0)) or ((d == 0) and (r0 == r1)):\n", 68 | " return None\n", 69 | " \n", 70 | " a = (r0**2 - r1**2 + d**2) / (2 * d)\n", 71 | " h = np.sqrt(r0**2 - a**2)\n", 72 | " p2 = p0 + (a / d) * p10\n", 73 | " r = Vec(-p10[1], p10[0]) * (h / d)\n", 74 | "\n", 75 | " return (p2 - r, p2 + r)\n", 76 | "\n", 77 | "\n", 78 | "def link_loc(name, joints, links):\n", 79 | " p1_index, p2_index = name.split(\"_\")[1:]\n", 80 | " p1 = joints[int(p1_index)]\n", 81 | " p2 = joints[int(p2_index)]\n", 82 | " a = math.degrees(math.atan2(p1[1] - p2[1], p1[0] - p2[0]))\n", 83 | " return (np.array((links[name][\"lev\"], *p1)), a)\n", 84 | "\n", 85 | " \n", 86 | "def linkage(alpha, x, y, links):\n", 87 | " \"\"\"For a given angle return the 2d location of each joint\"\"\"\n", 88 | " p0 = Vec(0, 0)\n", 89 | " p1 = Vec(x, y)\n", 90 | " p2 = p1 + links[\"link_1_2\"][\"len\"] * Vec(np.cos(np.deg2rad(alpha)), np.sin(np.deg2rad(alpha)))\n", 91 | " p3 = intersect(p0, links[\"link_0_3\"][\"len\"], p2, links[\"link_2_3\"][\"len\"])[1]\n", 92 | " p4 = intersect(p0, links[\"link_4_0\"][\"len\"], p3, links[\"link_3_4\"][\"len\"])[1]\n", 93 | " p5 = intersect(p0, links[\"link_0_5\"][\"len\"], p2, links[\"link_2_5\"][\"len\"])[0]\n", 94 | " p6 = intersect(p4, links[\"link_4_6\"][\"len\"], p5, links[\"link_5_6\"][\"len\"])[0]\n", 95 | " p7 = intersect(p5, links[\"link_7_5\"][\"len\"], p6, links[\"link_6_7\"][\"len\"])[1]\n", 96 | " return (p0, p1, p2, p3, p4, p5, p6, p7)\n", 97 | "\n", 98 | "height = 2\n", 99 | "x = 38.0\n", 100 | "y = 7.8\n", 101 | "\n", 102 | "links = {}\n", 103 | "links[\"link_1_2\"] = {\"len\": 15.0, \"lev\": 3 * height, \"col\": \"DarkBlue\"}\n", 104 | "links[\"link_2_3\"] = {\"len\": 50.0, \"lev\": 4 * height, \"col\": \"DarkGreen\"}\n", 105 | "links[\"link_3_4\"] = {\"len\": 55.8, \"lev\": 3 * height, \"col\": \"Red\"}\n", 106 | "links[\"link_4_0\"] = {\"len\": 40.1, \"lev\": 1 * height, \"col\": \"Red\"}\n", 107 | "links[\"link_0_3\"] = {\"len\": 41.5, \"lev\": 2 * height, \"col\": \"Red\"}\n", 108 | "links[\"link_4_6\"] = {\"len\": 39.4, \"lev\": 2 * height, \"col\": \"Purple\"}\n", 109 | "links[\"link_0_5\"] = {\"len\": 39.3, \"lev\": 3 * height, \"col\": \"OliveDrab\"}\n", 110 | "links[\"link_2_5\"] = {\"len\": 61.9, \"lev\": 1 * height, \"col\": \"Orange\"}\n", 111 | "links[\"link_5_6\"] = {\"len\": 36.7, \"lev\": 0 * height, \"col\": \"RoyalBlue\"}\n", 112 | "links[\"link_6_7\"] = {\"len\": 65.7, \"lev\": 1 * height, \"col\": \"RoyalBlue\"}\n", 113 | "links[\"link_7_5\"] = {\"len\": 49.0, \"lev\": 2 * height, \"col\": \"RoyalBlue\"}\n", 114 | "\n", 115 | "link_list = list(links.keys())" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "## Visualisation" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "import matplotlib.pyplot as plt\n", 132 | "import matplotlib.gridspec as gridspec\n", 133 | "%matplotlib inline\n", 134 | "\n", 135 | "def c(a,b):\n", 136 | " return links[f\"link_{a}_{b}\"][\"col\"].replace(\"Blue4\", \"blue\")\n", 137 | "\n", 138 | "def plot(ax, joints):\n", 139 | " p0, p1, p2, p3, p4, p5, p6, p7 = joints\n", 140 | " lines = (\n", 141 | " (p1, p2, c(1,2)), (p2, p5, c(2,5)), (p2, p3, c(2,3)), (p0, p3, c(0,3)), (p4, p0, c(4,0)), (p3, p4, c(3,4)), \n", 142 | " (p4, p6, c(4,6)), (p0, p5, c(0,5)), (p5, p6, c(5,6)), (p7, p5, c(7,5)), (p6, p7, c(6,7))\n", 143 | " )\n", 144 | " ax.scatter((p0[0], p1[0]), (p0[1], p1[1]))\n", 145 | " for a, b, col in lines:\n", 146 | " ax.plot((a[0], b[0]), (a[1], b[1]), color=col)\n", 147 | "\n", 148 | "fig = plt.figure(constrained_layout=True)\n", 149 | "fig.set_size_inches(15, 5)\n", 150 | "spec2 = gridspec.GridSpec(ncols=6, nrows=2, figure=fig)\n", 151 | "\n", 152 | "for i, alpha in enumerate(range(0,360, 30)):\n", 153 | " joints = linkage(alpha, x, y, links)\n", 154 | " ax = fig.add_subplot(spec2[i//6, i%6])\n", 155 | " ax.set_xlim(-70, 60)\n", 156 | " ax.set_ylim(-90, 50)\n", 157 | " plot(ax, joints)" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "# Assembly" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "## Parts" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "def make_link(length, width=2, height=1):\n", 181 | " link = (\n", 182 | " cq.Workplane(\"YZ\").rect(length + 4, width + 2)\n", 183 | " .pushPoints(((-length/2, 0), (length/2, 0))).circle(1)\n", 184 | " .extrude(height).edges(\"|X\").fillet(1.99)\n", 185 | " )\n", 186 | " link.faces(\">X\").wires(cq.NearestToPointSelector((0, length/2))).tag(\"mate\")\n", 187 | " return link\n", 188 | "\n", 189 | "parts = {name: make_link(links[name][\"len\"], height=(2 * height if name == \"link_1_2\" else height)) \n", 190 | " for name in link_list}" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "## Define Assembly" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "def create_leg(x, y):\n", 207 | " L = lambda *args: cq.Location(cq.Vector(*args))\n", 208 | "\n", 209 | " leg = MAssembly(cq.Workplane(\"YZ\").polyline([(0,0), (x, 0),(x,y)]), name=\"base\", color=\"Gray\")\n", 210 | " for i, name in enumerate(link_list):\n", 211 | " leg.add(parts[name], name=name, color=links[name][\"col\"], loc=L(0, 0, i*10 - 50))\n", 212 | " return leg\n", 213 | "\n", 214 | "leg = create_leg(x, y)\n", 215 | "d = show(leg, axes=False)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "## Define Mates" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": null, 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "leg = create_leg(x, y)\n", 232 | "\n", 233 | "for name in link_list:\n", 234 | " leg.mate(f\"{name}?mate\", name=name, origin=True)\n", 235 | " \n", 236 | "d = show(leg, render_mates=True, axes=False)" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "metadata": {}, 242 | "source": [ 243 | "## Relocate and assemble" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "leg.relocate()\n", 253 | " \n", 254 | "d = show(leg, render_mates=True)" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "alpha = 0\n", 264 | "joints = linkage(alpha, x, y, links)\n", 265 | "\n", 266 | "for name in link_list:\n", 267 | " v, a = link_loc(name, joints, links)\n", 268 | " abs_loc = cq.Location(cq.Workplane(\"YZ\").plane.rotated((0,0,a)), cq.Vector(*v)) # calculate the absolute location ...\n", 269 | " loc = abs_loc * leg.mates[name].mate.loc.inverse # ... and center the mate of the link first\n", 270 | " leg.assemble(name, loc)\n", 271 | "\n", 272 | "cv = show(leg, render_mates=True)" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "# Animation" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": null, 285 | "metadata": {}, 286 | "outputs": [], 287 | "source": [ 288 | "from jupyter_cadquery import AnimationTrack\n", 289 | "\n", 290 | "alphas = {name: [] for name in link_list}\n", 291 | "positions = {name: [] for name in link_list}\n", 292 | "\n", 293 | "for alpha in range(0, -375, -15):\n", 294 | " for name in link_list:\n", 295 | " p, a = link_loc(name, linkage(alpha, x, y, links), links) \n", 296 | " alphas[name].append(a)\n", 297 | " positions[name].append(p)\n", 298 | "\n", 299 | "time = np.linspace(0, 4, 25)\n", 300 | "\n", 301 | "for name in link_list:\n", 302 | " cv.add_track(AnimationTrack(f\"/base/{name}\", \"t\", time, [(p - positions[name][0]).tolist() for p in positions[name]]))\n", 303 | " cv.add_track(AnimationTrack(f\"/base/{name}\", \"rz\", time, [a - alphas[name][0] for a in alphas[name]]))\n", 304 | "\n", 305 | "cv.animate(2)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": null, 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [ 314 | "# close_viewers()" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "metadata": {}, 321 | "outputs": [], 322 | "source": [] 323 | } 324 | ], 325 | "metadata": { 326 | "language_info": { 327 | "name": "python", 328 | "pygments_lexer": "ipython3" 329 | }, 330 | "kernelspec": { 331 | "display_name": "Python 3", 332 | "language": "python", 333 | "name": "python3" 334 | } 335 | }, 336 | "nbformat": 4, 337 | "nbformat_minor": 4 338 | } 339 | -------------------------------------------------------------------------------- /examples/assemblies/3-jansen-linkage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/examples/assemblies/3-jansen-linkage.png -------------------------------------------------------------------------------- /examples/assemblies/4-bearing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import cadquery as cq\n", 10 | "from jupyter_cadquery import show, set_defaults, open_viewer\n", 11 | "from cadquery_massembly import Mate, MAssembly\n", 12 | "from jupyter_cadquery import AnimationTrack\n", 13 | "\n", 14 | "# Avoid clean error\n", 15 | "cq.occ_impl.shapes.Shape.clean = lambda x: x\n", 16 | "\n", 17 | "open_viewer(\"Bearing\")\n", 18 | "set_defaults(axes=False, axes0=True, edge_accuracy=0.01, mate_scale=1)" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "# Bearing" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "## Parts" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "def ring(inner_radius, outer_radius, width):\n", 42 | " ring = (cq.Workplane(origin=(0, 0, -width / 2))\n", 43 | " .circle(outer_radius).circle(inner_radius)\n", 44 | " .extrude(width)\n", 45 | " )\n", 46 | " return ring\n", 47 | "\n", 48 | "tol = 0.05\n", 49 | "ball_diam = 5\n", 50 | "\n", 51 | "r1, r2, r3, r4 = 4, 6, 8, 10\n", 52 | "r5 = (r3 + r2) / 2\n", 53 | "inner_ring = ring(r1, r2, ball_diam)\n", 54 | "outer_ring = ring(r3, r4, ball_diam)\n", 55 | "\n", 56 | "torus = cq.CQ(cq.Solid.makeTorus(r5, ball_diam / 2 + tol))\n", 57 | "ball = cq.Workplane().sphere(ball_diam / 2)\n", 58 | "\n", 59 | "inner = inner_ring.cut(torus)\n", 60 | "outer = outer_ring.cut(torus)\n", 61 | "\n", 62 | "show(ball, inner, outer, names=[\"ball\", \"inner\", \"outer\"])\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "## Assembly" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "number_balls = 6\n", 79 | "balls = [\"ball_%d\" % i for i in range(number_balls)]\n", 80 | "\n", 81 | "def create_bearing():\n", 82 | " L = lambda *args: cq.Location(cq.Vector(*args))\n", 83 | " C = lambda name: web_color(name)\n", 84 | " \n", 85 | " assy = MAssembly(outer, loc=L(0, 0, ball_diam/2), name=\"outer\", color=\"orange\")\n", 86 | " assy.add(inner, loc=L(20, 0, 0), name=\"inner\", color=\"orange\")\n", 87 | " for i in range(number_balls):\n", 88 | " assy.add(ball, loc=L(6*i, 20, 0), name=balls[i], color=\"black\")\n", 89 | "\n", 90 | " return assy" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "bearing = create_bearing()\n", 100 | "show(bearing)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "### Mates" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "from collections import OrderedDict as odict\n", 117 | "\n", 118 | "bearing.mate(\"outer@faces@X\").tag(\"X\").end()\n", 82 | " rv.faces(\">Z\").tag(\"Z\").end()\n", 83 | "\n", 84 | " return rv\n", 85 | "\n", 86 | "\n", 87 | "def make_panel(w, h, t, cutout):\n", 88 | "\n", 89 | " rv = (\n", 90 | " cq.Workplane(\"XZ\")\n", 91 | " .rect(w, h)\n", 92 | " .extrude(t)\n", 93 | " .faces(\">Y\")\n", 94 | " .vertices()\n", 95 | " .rect(2*cutout,2*cutout)\n", 96 | " .cutThruAll()\n", 97 | " .faces(\"Z\").tag(\"hole1\")\n", 105 | " rv.faces(\"Y\").tag(\"mate1\")\n", 129 | " rv.faces(\"Z\", name=f\"{v}_0\", transforms=odict(rx=0))\n", 204 | " door.mate(f\"{v}@faces@X[-4]\", name=\"panel_1\")\n", 214 | "\n", 215 | "# add mates to handle and one hole\n", 216 | "door.mate(\"handle?mate1\", name=\"handle_0\", transforms=odict(rx=180))\n", 217 | "door.mate(\"panel?hole2\", name=\"handle_1\", transforms=odict(rx=180))\n", 218 | "\n", 219 | "show(door, render_mates=True)" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "# manually assemble as you would in reality\n", 229 | "\n", 230 | "door.assemble(\"bottom_0\", \"con_bl_0\") # add bottom vslot to bottom-left connector\n", 231 | "door.assemble(\"con_br_1\", \"bottom_1\") # add bottom-right connector to bottom vslot\n", 232 | "door.assemble(\"left_1\", \"con_bl_1\") # add left vslot to bottom-left connector\n", 233 | "door.assemble(\"right_0\", \"con_br_0\") # add right vslot to bottom-right connector\n", 234 | "door.assemble(\"panel_0\", \"panel_1\") # add panel\n", 235 | "door.assemble(\"con_tl_0\", \"left_0\") # add top-left connector to left vslot\n", 236 | "door.assemble(\"con_tr_1\", \"right_1\") # add top-right connector to right vslot\n", 237 | "door.assemble(\"top_1\", \"con_tl_1\") # add top vslot to top-left connector\n", 238 | "door.assemble(\"handle_0\", \"handle_1\") # add handle\n", 239 | "\n", 240 | "show(door, render_mates=False)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": {}, 246 | "source": [ 247 | "# The Solver based tutorial" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "cv2 = open_viewer(\"Door2\")" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": null, 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [ 265 | "import cadquery as cq\n", 266 | "\n", 267 | "# Parameters\n", 268 | "H = 400\n", 269 | "W = 200\n", 270 | "D = 350\n", 271 | "\n", 272 | "PROFILE = cq.importers.importDXF(\"./vslot-2020_1.dxf\").wires()\n", 273 | "\n", 274 | "SLOT_D = 6\n", 275 | "PANEL_T = 3\n", 276 | "\n", 277 | "HANDLE_D = 20\n", 278 | "HANDLE_L = 50\n", 279 | "HANDLE_W = 4\n", 280 | "\n", 281 | "\n", 282 | "def make_vslot(l):\n", 283 | "\n", 284 | " return PROFILE.toPending().extrude(l)\n", 285 | "\n", 286 | "\n", 287 | "def make_connector():\n", 288 | "\n", 289 | " rv = (\n", 290 | " cq.Workplane()\n", 291 | " .box(20, 20, 20)\n", 292 | " .faces(\"X\").tag(\"X\").end()\n", 302 | " rv.faces(\">Z\").tag(\"Z\").end()\n", 303 | "\n", 304 | " return rv\n", 305 | "\n", 306 | "\n", 307 | "def make_panel(w, h, t, cutout):\n", 308 | "\n", 309 | " rv = (\n", 310 | " cq.Workplane(\"XZ\")\n", 311 | " .rect(w, h)\n", 312 | " .extrude(t)\n", 313 | " .faces(\">Y\")\n", 314 | " .vertices()\n", 315 | " .rect(2*cutout,2*cutout)\n", 316 | " .cutThruAll()\n", 317 | " .faces(\"Y\").edges(\"%CIRCLE\").edges(\">Z\").tag(\"hole1\")\n", 325 | " rv.faces(\">Y\").edges(\"%CIRCLE\").edges(\"Y\").tag(\"mate1\")\n", 349 | " rv.faces(\"Z\", \"con_tl?Z\", \"Plane\")\n", 384 | " .constrain(\"left@faces@Y\", \"Axis\")\n", 388 | " # bottom\n", 389 | " .constrain(\"bottom@faces@Y\", \"Axis\")\n", 390 | " .constrain(\"bottom@faces@>Z\", \"con_bl?X\", \"Plane\")\n", 391 | " # right connectors\n", 392 | " .constrain(\"top@faces@>Z\", \"con_tr@faces@>X\", \"Plane\")\n", 393 | " .constrain(\"bottom@faces@X\", \"Plane\")\n", 394 | " .constrain(\"left@faces@>Z\", \"con_tr?Z\", \"Axis\")\n", 395 | " .constrain(\"left@faces@Z\", \"con_tr@faces@>Z\", \"Plane\")\n", 398 | " .constrain(\"right@faces@X[-4]\", \"panel@faces@Z\", \"panel@faces@>Z\", \"Axis\")\n", 402 | " # handle\n", 403 | " .constrain(\"panel?hole1\", \"handle?mate1\", \"Plane\")\n", 404 | " .constrain(\"panel?hole2\", \"handle?mate2\", \"Point\")\n", 405 | ")\n", 406 | "\n", 407 | "# solve\n", 408 | "door.solve()\n", 409 | "\n", 410 | "show_object(door,name='door')" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": null, 416 | "metadata": {}, 417 | "outputs": [], 418 | "source": [] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": null, 423 | "metadata": {}, 424 | "outputs": [], 425 | "source": [] 426 | } 427 | ], 428 | "metadata": { 429 | "language_info": { 430 | "name": "python", 431 | "pygments_lexer": "ipython3" 432 | }, 433 | "kernelspec": { 434 | "display_name": "Python 3", 435 | "language": "python", 436 | "name": "python3" 437 | } 438 | }, 439 | "nbformat": 4, 440 | "nbformat_minor": 4 441 | } 442 | -------------------------------------------------------------------------------- /examples/assemblies/6-nested-assemblies.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import cadquery as cq\n", 10 | "from jupyter_cadquery import (\n", 11 | " open_viewer, show,\n", 12 | " set_defaults, get_defaults, \n", 13 | ")\n", 14 | "from cadquery_massembly import Mate, MAssembly, relocate\n", 15 | "\n", 16 | "cv = open_viewer(\"Test\")" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "set_defaults(axes=True, axes0=True, mate_scale=2.5)" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "box0 = cq.Workplane(\"XY\").box(10,20,10)\n", 35 | "box1 = cq.Workplane(\"XZ\").box(10,20,10)\n", 36 | "box2 = cq.Workplane(\"YX\").box(10,20,10)\n", 37 | "box3 = cq.Workplane(\"YZ\").box(10,20,10)\n", 38 | "\n", 39 | "for box, name, dirs in (\n", 40 | " (box0, \"box0\", (\"Y\", \"X\")),\n", 41 | " (box1, \"box1\", (\"Z\", \"X\")),\n", 42 | " (box2, \"box2\", (\"X\", \"Y\")),\n", 43 | " (box3, \"box3\", (\"Z\", \"Y\")),\n", 44 | "):\n", 45 | " for i, direction in enumerate(dirs):\n", 46 | " box.faces(f\">{direction}\").tag(f\"{name}_m{i}\")\n", 47 | "\n", 48 | "show(box0, box1, box2, box3)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "cyl1 = cq.Workplane(\"XY\").circle(2).extrude(10)\n", 58 | "cyl2 = cq.Workplane(\"XZ\").circle(2).extrude(10)\n", 59 | "cyl3 = cq.Workplane(\"YZ\").circle(2).extrude(10)\n", 60 | "\n", 61 | "for cyl, name, ax in (\n", 62 | " (cyl1, \"cyl1\", \"Z\"), \n", 63 | " (cyl2, \"cyl2\", \"Y\"), \n", 64 | " (cyl3, \"cyl3\", \"X\"),\n", 65 | "):\n", 66 | " cyl.faces(f\">{ax}\").tag(f\"{name}_m0\")\n", 67 | " cyl.faces(f\"<{ax}\").tag(f\"{name}_m1\")\n", 68 | " \n", 69 | "show(cyl1, cyl2, cyl3)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "def create():\n", 79 | " L = lambda *args: cq.Location(cq.Vector(*args))\n", 80 | " C = lambda *args: [int(args[i]*255) for i in range(3)]\n", 81 | "\n", 82 | " a = (MAssembly(cyl3, name=\"cyl3\", color=C(1,0,0), loc=L(-20, -10, 20))\n", 83 | " .add(box3, name=\"box3\", color=C(1,0,0), loc=L(20,10,0))\n", 84 | " )\n", 85 | " b = (MAssembly(cyl2, name=\"cyl2\", color=C(0,0.5, 0.25), loc=L(0, -20, 20))\n", 86 | " .add(box2, name=\"box2\", color=C(0,0.5,0.25), loc=L(0, 20, 20))\n", 87 | " .add(a, name=\"a\")\n", 88 | " )\n", 89 | " c = (MAssembly(cyl1, name=\"cyl1\", color=C(0,0,1), loc=L(10,0,-10))\n", 90 | " .add(box1, name=\"box1\", color=C(0,0,1), loc=L(10, 0,10))\n", 91 | " .add(b, name=\"b\")\n", 92 | " )\n", 93 | " d = (MAssembly(box0, name=\"box0\", color=C(0.5,0.5,0.5), loc=L(30,30,30))\n", 94 | " .add(c, name=\"c\")\n", 95 | " )\n", 96 | " return d\n", 97 | "\n", 98 | "assy = create()\n", 99 | "show(assy)" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": null, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "from collections import OrderedDict as odict\n", 109 | "\n", 110 | "assy = create()\n", 111 | "for obj, name in (\n", 112 | " (\"box0\", \"box0\"), (\"c/box1\", \"box1\"), (\"c/b/box2\", \"box2\"), (\"c/b/a/box3\", \"box3\"), \n", 113 | " (\"c\", \"cyl1\"), (\"c/b\", \"cyl2\"), (\"c/b/a\", \"cyl3\")\n", 114 | "):\n", 115 | " assy.mate(f\"{obj}?{name}_m0\", name=f\"{name}_m0\", transforms=odict(rx=180 if \"c\" in name else 0), origin=True)\n", 116 | " assy.mate(f\"{obj}?{name}_m1\", name=f\"{name}_m1\", transforms=odict(rx=0 if \"b\" in name else 180))\n", 117 | "\n", 118 | "show(assy, render_mates=True)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "assy.relocate()\n", 128 | "show(assy, render_mates=True)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "assy.assemble(\"cyl1_m0\", \"box0_m0\")\n", 138 | "assy.assemble(\"box1_m1\", \"cyl1_m1\")\n", 139 | "assy.assemble(\"cyl2_m0\", \"box1_m0\")\n", 140 | "assy.assemble(\"box2_m1\", \"cyl2_m1\")\n", 141 | "assy.assemble(\"cyl3_m0\", \"box2_m0\")\n", 142 | "assy.assemble(\"box3_m1\", \"cyl3_m1\")\n", 143 | "\n", 144 | "d = show(assy, render_mates=True)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "import numpy as np\n", 154 | "from jupyter_cadquery import AnimationTrack\n", 155 | "\n", 156 | "d.add_track(AnimationTrack(f\"/box0/c\", \"rz\", np.linspace(0,6,13), np.linspace(0, 360, 13)))\n", 157 | "d.add_track(AnimationTrack(f\"/box0/c/b\", \"rz\", np.linspace(0,6,13), np.linspace(0, 360, 13)))\n", 158 | "d.add_track(AnimationTrack(f\"/box0/c/b/a\", \"rz\", np.linspace(0,6,13), np.linspace(0, 360, 13)))\n", 159 | " \n", 160 | "d.animate(speed=3)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [] 176 | } 177 | ], 178 | "metadata": { 179 | "language_info": { 180 | "name": "python", 181 | "pygments_lexer": "ipython3" 182 | }, 183 | "kernelspec": { 184 | "display_name": "Python 3", 185 | "language": "python", 186 | "name": "python3" 187 | } 188 | }, 189 | "nbformat": 4, 190 | "nbformat_minor": 4 191 | } 192 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_server_config.d/jupyter_cadquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "jupyter_cadquery": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /jupyter_cadquery/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Bernhard Walter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | import os 19 | 20 | os.environ["JUPYTER_CADQUERY"] = "1" 21 | 22 | from cad_viewer_widget import ( 23 | AnimationTrack, 24 | close_sidecar as close_viewer, 25 | close_sidecars as close_viewers, 26 | get_sidecar as get_viewer, 27 | get_sidecars as get_viewers, 28 | get_default_sidecar as get_default_viewer, 29 | set_default_sidecar as set_default_viewer, 30 | get_viewer_by_id, 31 | get_viewers_by_id, 32 | ) 33 | 34 | from ocp_vscode.colors import * 35 | from ocp_vscode.config import ( 36 | Camera, 37 | Collapse, 38 | combined_config, 39 | get_changed_config, 40 | set_viewer_config, 41 | get_default, 42 | get_defaults, 43 | reset_defaults, 44 | set_defaults, 45 | status, 46 | workspace_config, 47 | ) 48 | 49 | # Inject Collapse enum. Import in cad_viewer_widget would lead to circular import 50 | from cad_viewer_widget.widget import _set_collapse 51 | 52 | _set_collapse( 53 | {"R": Collapse.ROOT, "C": Collapse.ALL, "E": Collapse.NONE, "1": Collapse.LEAVES} 54 | ) 55 | del _set_collapse 56 | 57 | from ocp_vscode.show import show_all, reset_show, show_clear 58 | 59 | 60 | from .app import JupyterCadqueryBackend 61 | from .config import get_user_defaults, save_user_defaults 62 | from ._version import __version__ 63 | from .tools import auto_show, get_pick 64 | from .show import * 65 | 66 | try: 67 | from ocp_tessellate.tessellator import ( 68 | disable_native_tessellator, 69 | enable_native_tessellator, 70 | is_native_tessellator_enabled, 71 | ) 72 | 73 | if os.environ.get("NATIVE_TESSELLATOR") == "0": 74 | disable_native_tessellator() 75 | else: 76 | enable_native_tessellator() 77 | 78 | print( 79 | "Found and enabled native tessellator.\n" 80 | "To disable, call `disable_native_tessellator()`\n" 81 | "To enable, call `enable_native_tessellator()`\n" 82 | ) 83 | except: 84 | pass 85 | 86 | from cad_viewer_widget._version import __version__ as cvw_version 87 | from ocp_tessellate.ocp_utils import Color, occt_version 88 | 89 | 90 | def versions(): 91 | # do not add to global namesapce 92 | from ._version import __version__ as jcq_version 93 | from ._version import __version_info__ as jcq_version_info 94 | 95 | print() 96 | print("Versions:") 97 | print("- jupyter_cadquery ", jcq_version) 98 | print("- cad_viewer_widget", cvw_version) 99 | print("- open cascade ", occt_version()) 100 | print() 101 | 102 | 103 | def _jupyter_server_extension_points(): 104 | return [{"module": "jupyter_cadquery.app", "app": JupyterCadqueryBackend}] 105 | 106 | 107 | def _load_jupyter_server_extension(server_app): 108 | JupyterCadqueryBackend.load_jupyter_server_extension(server_app) 109 | 110 | 111 | try: 112 | from IPython import get_ipython 113 | 114 | shell_name = get_ipython().__class__.__name__ 115 | if shell_name == "ZMQInteractiveShell": 116 | auto_show() 117 | except Exception as ex: 118 | ... 119 | 120 | get_user_defaults() 121 | -------------------------------------------------------------------------------- /jupyter_cadquery/_version.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Bernhard Walter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | import re 19 | from collections import namedtuple 20 | 21 | VersionInfo = namedtuple("VersionInfo", ["major", "minor", "patch", "release", "build"]) 22 | 23 | 24 | def get_version(version): 25 | r = re.compile( 26 | r"(?P\d+)\.(?P\d+)\.(?P\d+)\-{0,1}(?P\D*)(?P\d*)" 27 | ) 28 | major, minor, patch, release, build = r.match(version).groups() 29 | return VersionInfo(major, minor, patch, release, build) 30 | 31 | 32 | __version__ = "4.0.2" # DO NOT EDIT THIS DIRECTLY! It is managed by bumpversion 33 | __version_info__ = get_version(__version__) 34 | -------------------------------------------------------------------------------- /jupyter_cadquery/app.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Bernhard Walter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import os 18 | import secrets 19 | 20 | import orjson 21 | from jupyter_server.base.handlers import JupyterHandler 22 | from jupyter_server.extension.application import ExtensionApp 23 | from jupyter_server.extension.handler import ExtensionHandlerMixin 24 | 25 | # ensure the Jupyter Cadquery comms routines will be loaded 26 | os.environ["JUPYTER_CADQUERY"] = "1" 27 | from ocp_vscode.backend import ViewerBackend 28 | from ocp_vscode.comms import MessageType 29 | 30 | BACKENDS = {} 31 | 32 | API_KEY = secrets.token_urlsafe(32) 33 | 34 | 35 | class MeasureHandler(ExtensionHandlerMixin, JupyterHandler): 36 | def post(self): 37 | viewer = self.get_body_argument("viewer") 38 | message = orjson.loads(self.get_body_argument("data")) 39 | 40 | apikey = self.get_body_argument("apikey") 41 | if apikey != API_KEY: 42 | self.log.error("Invalid API key") 43 | self.set_status(401, reason="Invalid API key") 44 | self.finish(orjson.dumps({"error": "Invalid API key"})) 45 | return 46 | 47 | if viewer is None: 48 | self.log.error("Unknown viewer") 49 | self.finish(orjson.dumps({"error": "Unknown viewer"})) 50 | elif message is None: 51 | self.log.error("Missing message") 52 | self.finish(orjson.dumps({"error": "Missing shape ID(s) or active Tool"})) 53 | else: 54 | backend = BACKENDS.get(viewer) 55 | if backend is None: 56 | self.log.error("Unknown viewer") 57 | self.finish(orjson.dumps({"error": "Unknown viewer"})) 58 | else: 59 | self.log.info(f"Identifiers received {message} for viewer {viewer}") 60 | result = backend.handle_event(message, MessageType.UPDATES) 61 | self.finish(orjson.dumps({"success": result})) 62 | 63 | 64 | class ObjectsHandler(ExtensionHandlerMixin, JupyterHandler): 65 | def post(self): 66 | viewer = self.get_body_argument("viewer") 67 | data = orjson.loads(self.get_body_argument("data")) 68 | 69 | apikey = self.get_body_argument("apikey") 70 | if apikey != API_KEY: 71 | self.log.error("Invalid API key") 72 | self.set_status(401, reason="Invalid API key") 73 | self.finish(orjson.dumps({"error": "Invalid API key"})) 74 | return 75 | 76 | if viewer is None: 77 | self.log.error("Unknown viewer") 78 | self.finish(orjson.dumps({"error": "Unknown viewer"})) 79 | elif data is None: 80 | self.log.error("Missing objects") 81 | self.finish(orjson.dumps({"error": "Missing objects"})) 82 | else: 83 | BACKENDS[viewer] = ViewerBackend(port=0, jcv_id=viewer) 84 | BACKENDS[viewer].load_model(data["model"]) 85 | self.log.info(f"Objects received for viewer {viewer}") 86 | self.finish( 87 | orjson.dumps({"success": f"Objects received for viewer {viewer}"}) 88 | ) 89 | 90 | 91 | def wrapper(self, init_http): 92 | def init_httpserver(): 93 | init_http() 94 | port = self.port 95 | os.environ["JUPYTER_PORT"] = str(port) 96 | os.environ["JUPYTER_CADQUERY_API_KEY"] = API_KEY 97 | self.log.info(f"JUPYTER_PORT={port}") 98 | self.log.info(f"JUPYTER_CADQUERY_API_KEY={API_KEY}") 99 | 100 | return init_httpserver 101 | 102 | 103 | class JupyterCadqueryBackend(ExtensionApp): 104 | name = "jupyter_cadquery" 105 | static_paths = [] 106 | template_paths = [] 107 | 108 | def initialize_handlers(self): 109 | self.handlers.append((r"/measure", MeasureHandler)) 110 | self.handlers.append((r"/objects", ObjectsHandler)) 111 | 112 | init_http = self.serverapp.init_httpserver 113 | self.serverapp.init_httpserver = wrapper(self.serverapp, init_http) 114 | 115 | def initialize_settings(self): 116 | pass 117 | 118 | def initialize_templates(self): 119 | pass 120 | -------------------------------------------------------------------------------- /jupyter_cadquery/comms.py: -------------------------------------------------------------------------------- 1 | """Communication with the viewer""" 2 | 3 | # 4 | # Copyright 2025 Bernhard Walter 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | 20 | from enum import Enum 21 | import os 22 | 23 | import orjson 24 | import requests 25 | from cad_viewer_widget import get_default_sidecar, get_sidecar, show 26 | from cad_viewer_widget.utils import display_args, viewer_args 27 | from ocp_vscode.comms import default as json_default 28 | 29 | from .config import get_user_defaults 30 | 31 | __all__ = [ 32 | "set_jupyter_port", 33 | "get_jupyter_port", 34 | "init_session", 35 | "send_data", 36 | "send_command", 37 | "send_backend", 38 | "send_measure_request", 39 | "send_config", 40 | ] 41 | 42 | SESSION = None 43 | 44 | def init_session(url): 45 | global SESSION 46 | session = requests.Session() 47 | session.get(url) 48 | SESSION = session 49 | 50 | 51 | def send_data(data, port=None, timeit=False): 52 | """ 53 | Send data to the viewer 54 | 55 | Called by ocp_vscode.show.show() to send model and config to viewer 56 | """ 57 | 58 | collapse_mapping = ["E", "1", "C", "R"] # show needs the string 59 | 60 | config = data["config"] 61 | type_ = data["type"] 62 | if type_ != "data": 63 | raise TypeError(f"Wrong data type {type_}") 64 | # count = data["count"] 65 | data = data["data"] 66 | 67 | if config.get("collapse") is not None: 68 | if isinstance(config["collapse"], Enum): 69 | config["collapse"] = collapse_mapping[config["collapse"].value] 70 | else: 71 | config["collapse"] = collapse_mapping[config["collapse"]] 72 | 73 | if config.get("reset_camera") is not None: 74 | if isinstance(config["reset_camera"], Enum): 75 | config["reset_camera"] = config["reset_camera"].value 76 | 77 | if config.get("orbit_control") is not None: 78 | config["control"] = "orbit" if config["orbit_control"] else "trackball" 79 | 80 | all_args = viewer_args(config) 81 | all_args.update(display_args(config)) 82 | viewer = show( 83 | data, 84 | title=config.get("viewer"), 85 | anchor=config.get("anchor"), 86 | **all_args, 87 | ) 88 | viewer.widget.measure_callback = send_measure_request 89 | return viewer 90 | 91 | 92 | def send_command(data, port=None, title=None, timeit=False): 93 | """ 94 | Send command to the viewer. 95 | 96 | With data == "config" called by called by ocp_vscode.config.workspace_config() 97 | With data == "status" called by called by ocp_vscode.config.status() 98 | """ 99 | if data == "config": 100 | config = get_user_defaults() 101 | viewer = None 102 | if title is None: 103 | title = get_default_sidecar() 104 | if title is not None: 105 | viewer = get_sidecar(title) 106 | else: 107 | viewer = get_sidecar(title) 108 | 109 | if viewer is not None: 110 | config["_splash"] = viewer._splash 111 | return config 112 | 113 | elif data == "status": 114 | viewer = get_sidecar(title) 115 | return {} if viewer is None else viewer.status() 116 | 117 | else: 118 | raise ValueError("Unknown data for send_data") 119 | 120 | 121 | def send_backend(data, port=None, jcv_id=None, timeit=False): 122 | """ 123 | Send data to the viewer 124 | 125 | Called by ocp_vscode.show.show() to send model to backend 126 | """ 127 | port = os.environ.get("JUPYTER_PORT", "8888") 128 | url = f"http://localhost:{port}" 129 | 130 | if SESSION is None: 131 | init_session(url) 132 | 133 | message = { 134 | "_xsrf": SESSION.cookies.get("_xsrf"), 135 | "apikey": os.environ.get("JUPYTER_CADQUERY_API_KEY"), 136 | "viewer": jcv_id, 137 | "data": orjson.dumps(data, default=json_default).decode("utf-8"), 138 | } 139 | response = SESSION.post(f"{url}/objects", data=message) 140 | return response.status_code 141 | 142 | 143 | def send_measure_request(jcv_id, shape_ids): 144 | """ 145 | Retrieve the measurement for a given viewer and shape ids from the backend 146 | 147 | Called as callbacks by cad_viewer_widget.widget.CadViewerWidget.active_tool and 148 | cad_viewer_widget.widget.CadViewerWidget.selected_shape_ids to retrieve measurements 149 | """ 150 | port = os.environ.get("JUPYTER_PORT", "8888") 151 | url = f"http://localhost:{port}" 152 | 153 | if SESSION is None: 154 | init_session(url) 155 | 156 | message = { 157 | "_xsrf": SESSION.cookies.get("_xsrf"), 158 | "apikey": os.environ.get("JUPYTER_CADQUERY_API_KEY"), 159 | "viewer": jcv_id, 160 | "data": orjson.dumps(shape_ids).decode("utf-8"), 161 | } 162 | response = SESSION.post(f"{url}/measure", data=message) 163 | return response.status_code, response.text 164 | 165 | 166 | def send_config(config, port=None, title=None, timeit=False): 167 | """ 168 | Send config to the viewer 169 | 170 | Called by ocp_vscode.config.set_viewer_config() to set attributes in the viewer 171 | """ 172 | title = config["config"].get("title") 173 | 174 | if title is None: 175 | title = get_default_sidecar() 176 | if title is None: 177 | return 178 | 179 | cv = get_sidecar(title) 180 | if cv is None: 181 | return 182 | 183 | for k, v in config["config"].items(): 184 | if v is not None: 185 | if not k in ["port", "title"]: 186 | setattr(cv, k, v) 187 | -------------------------------------------------------------------------------- /jupyter_cadquery/config.py: -------------------------------------------------------------------------------- 1 | """Configuration of the viewer""" 2 | 3 | # 4 | # Copyright 2025 Bernhard Walter 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | from pathlib import Path 20 | 21 | from yaml import safe_dump, safe_load 22 | 23 | WORKSPACE_DEFAULTS = None 24 | 25 | __all__ = ["get_user_defaults", "save_user_defaults"] 26 | 27 | 28 | def workspace_defaults(): 29 | return { 30 | "_splash": False, 31 | "ambient_intensity": 1, 32 | "angular_tolerance": 0.2, 33 | "axes": False, 34 | "axes0": True, 35 | "black_edges": False, 36 | "center_grid": False, 37 | "collapse": "1", 38 | "control": "trackball", 39 | "default_color": "#e8b024", 40 | "default_edgecolor": "#707070", 41 | "default_facecolor": "Violet", 42 | "default_opacity": 0.5, 43 | "default_thickedgecolor": "MediumOrchid", 44 | "default_vertexcolor": "MediumOrchid", 45 | "deviation": 0.1, 46 | "direct_intensity": 1.1, 47 | "explode": False, 48 | "glass": True, 49 | "grid": [False, False, False], 50 | "metalness": 0.3, 51 | "modifier_keys": { 52 | "shift": "shiftKey", 53 | "ctrl": "ctrlKey", 54 | "meta": "metaKey", 55 | }, 56 | "new_tree_behavior": True, 57 | "ortho": True, 58 | "pan_speed": 1, 59 | "reset_camera": "reset", 60 | "rotate_speed": 1, 61 | "roughness": 0.65, 62 | "ticks": 10, 63 | "tools": True, 64 | "transparent": False, 65 | "tree_width": 240, 66 | "up": "Z", 67 | "zoom_speed": 1, 68 | } 69 | 70 | 71 | def get_user_defaults(): 72 | global WORKSPACE_DEFAULTS 73 | if WORKSPACE_DEFAULTS is None: 74 | path = Path("~/.jcq_config").expanduser() 75 | if path.exists(): 76 | with open(path, "r") as fd: 77 | WORKSPACE_DEFAULTS = workspace_defaults() 78 | try: 79 | config = safe_load(fd) 80 | WORKSPACE_DEFAULTS.update(config) 81 | except: 82 | print(f"Error: Cannot parse {path}") 83 | WORKSPACE_DEFAULTS = workspace_defaults() 84 | else: 85 | WORKSPACE_DEFAULTS = workspace_defaults() 86 | return dict(WORKSPACE_DEFAULTS) 87 | 88 | 89 | def save_user_defaults(): 90 | path = Path("~/.jcq_config").expanduser() 91 | config = dict(WORKSPACE_DEFAULTS) 92 | 93 | del config["_splash"] 94 | with open(path, "w") as fd: 95 | fd.write(safe_dump(config)) 96 | print(f"Wrote config {path}") 97 | -------------------------------------------------------------------------------- /jupyter_cadquery/tools.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2025 Bernhard Walter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | import numpy as np 19 | 20 | # from ocp_tessellate.cad_objects import Edges, Faces, Part, PartGroup, Vertices 21 | from ocp_tessellate.convert import tessellate_group, to_assembly 22 | from ocp_tessellate.ocp_utils import is_build123d_assembly, is_cadquery_assembly 23 | from ocp_tessellate.utils import numpy_to_json 24 | from ocp_vscode import show 25 | 26 | try: 27 | import cadquery as cq 28 | 29 | HAS_CADQUERY = True 30 | except ImportError: 31 | HAS_CADQUERY = False 32 | 33 | try: 34 | import build123d as bd 35 | 36 | HAS_BUILD123D = True 37 | except ImportError: 38 | HAS_BUILD123D = False 39 | 40 | 41 | # pylint: disable=protected-access 42 | # pylint: disable=unnecessary-lambda 43 | def auto_show(): 44 | if HAS_CADQUERY: 45 | try: 46 | del cq.Workplane._repr_html_ # pylint: disable=no-member 47 | del cq.Shape._repr_html_ # pylint: disable=no-member 48 | except: # pylint: disable=bare-except 49 | pass 50 | cq.Workplane._ipython_display_ = lambda cad_obj: show(cad_obj) 51 | cq.Shape._ipython_display_ = lambda cad_obj: show(cad_obj) 52 | cq.Assembly._ipython_display_ = lambda cad_obj: show(cad_obj) 53 | cq.Sketch._ipython_display_ = lambda cad_obj: show(cad_obj) 54 | print("Overwriting auto display for cadquery Workplane and Shape") 55 | 56 | if HAS_BUILD123D: 57 | bd.BuildPart._ipython_display_ = lambda cad_obj: show(cad_obj) 58 | bd.BuildSketch._ipython_display_ = lambda cad_obj: show(cad_obj) 59 | bd.BuildLine._ipython_display_ = lambda cad_obj: show(cad_obj) 60 | bd.ShapeList._ipython_display_ = lambda cad_obj: show(cad_obj) 61 | bd.Shape._ipython_display_ = lambda cad_obj: show(cad_obj) 62 | try: 63 | del bd.Shape._repr_javascript_ 64 | except: 65 | pass 66 | print( 67 | "Overwriting auto display for build123d BuildPart, BuildSketch, BuildLine, ShapeList" 68 | ) 69 | 70 | 71 | def get_pick(assembly, pick): 72 | if pick == {}: 73 | print("First double click on an object in the CAD viewer") 74 | return None 75 | 76 | path = pick["path"] 77 | name = pick["name"] 78 | id_ = "/".join([path, name]) 79 | 80 | if is_cadquery_assembly(assembly): 81 | short_path = "/".join(id_.split("/")[2:]) 82 | if assembly.objects.get(short_path) is not None: 83 | return assembly.objects[short_path] 84 | else: 85 | short_path = "/".join(id_.split("/")[2:-1]) 86 | if assembly.objects.get(short_path) is not None: 87 | return assembly.objects[short_path] 88 | else: 89 | print("Currently only CadQuery is supported") 90 | -------------------------------------------------------------------------------- /notebooks/Juypter CadQuery Logo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "da4c78fb-3926-4a12-bc07-af1e2a07ddcb", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import re\n", 11 | "import sys\n", 12 | "import numpy as np\n", 13 | "\n", 14 | "import cadquery as cq\n", 15 | "from cadquery.occ_impl.shapes import Wire\n", 16 | "\n", 17 | "from OCP.TColgp import TColgp_Array1OfPnt\n", 18 | "from OCP.gp import gp_Pnt\n", 19 | "from OCP.Geom import Geom_BezierCurve\n", 20 | "from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire\n", 21 | "\n", 22 | "from jupyter_cadquery import show, open_viewer\n", 23 | "from jupyter_cadquery.viewer.client import _convert\n", 24 | "from jupyter_cadquery import set_defaults\n", 25 | "from jupyter_cadquery import Part, PartGroup\n", 26 | "\n", 27 | "\n", 28 | "np.set_printoptions(threshold=sys.maxsize)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "id": "67ce430c-9d34-4016-84a5-7e3b18b5978f", 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "svg1 = \"M 18.6962 7.12238C 10.6836 7.12238 3.64131 4.24672 0 0C 1.41284 3.82041 3.96215 7.1163 7.30479 9.44404C 10.6474 11.7718 14.623 13.0196 18.6962 13.0196C 22.7695 13.0196 26.745 11.7718 30.0877 9.44404C 33.4303 7.1163 35.9796 3.82041 37.3925 4.0486e-13C 33.7601 4.24672 26.7445 7.12238 18.6962 7.12238Z\"\n", 39 | "svg2 = \"M 18.6962 5.89725C 26.7089 5.89725 33.7512 8.77291 37.3925 13.0196C 35.9796 9.19922 33.4303 5.90333 30.0877 3.57559C 26.745 1.24785 22.7695 4.0486e-13 18.6962 0C 14.623 4.0486e-13 10.6474 1.24785 7.30479 3.57559C 3.96215 5.90333 1.41284 9.19922 0 13.0196C 3.64131 8.76401 10.648 5.89725 18.6962 5.89725Z\"\n", 40 | "dist = 2457.88 - 2484.27\n", 41 | "color = \"#F37726\"" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "44701f4f-72b0-4d83-b8cb-e5c80fb9817e", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "class Move:\n", 52 | " def __init__(self, coords):\n", 53 | " self.x, self.y = [float(x) for x in coords.strip().split(\" \")]\n", 54 | "\n", 55 | " def __repr__(self):\n", 56 | " return f\"Move p=({self.x}, {self.y})\"\n", 57 | "\n", 58 | " def convert(self):\n", 59 | " return None, gp_Pnt(self.x, self.y, 0.0)\n", 60 | " \n", 61 | "class Curve:\n", 62 | " def __init__(self, coords):\n", 63 | " self.x1, self.y1, self.x2, self.y2, self.x, self.y = [float(x) for x in coords.strip().split(\" \")]\n", 64 | " \n", 65 | " def convert(self, start):\n", 66 | " pole1 = gp_Pnt(self.x1, self.y1, 0.0)\n", 67 | " pole2 = gp_Pnt(self.x2, self.y2, 0.0)\n", 68 | " end = gp_Pnt(self.x, self.y, 0.0)\n", 69 | " array = TColgp_Array1OfPnt(1, 4)\n", 70 | " array.SetValue(1, start)\n", 71 | " array.SetValue(2, pole1)\n", 72 | " array.SetValue(3, pole2)\n", 73 | " array.SetValue(4, end)\n", 74 | " curve = Geom_BezierCurve(array)\n", 75 | " curve = BRepBuilderAPI_MakeEdge(curve).Edge()\n", 76 | " return curve, end\n", 77 | " \n", 78 | " def __repr__(self):\n", 79 | " return f\"Curve p1=({self.x1}, {self.y1}), p2=({self.x2}, {self.y2}), p=({self.x}, {self.y})\"\n", 80 | "\n", 81 | "class Close:\n", 82 | " def __init__(self, coords):\n", 83 | " ...\n", 84 | " \n", 85 | " def convert(self, start):\n", 86 | " return start\n", 87 | " \n", 88 | " def __repr__(self):\n", 89 | " return \"Close\"\n", 90 | " \n", 91 | "command_map = {\n", 92 | " \"M\": Move, \"C\": Curve, \"Z\": Close\n", 93 | "}\n", 94 | "\n", 95 | "def parse(svg):\n", 96 | " commands = re.split(r\"([MCZ])\", svg)\n", 97 | " if commands[-2] == \"Z\":\n", 98 | " commands = commands[1:]\n", 99 | " else:\n", 100 | " commands = commands[1:-1]\n", 101 | " commands = [command_map[c[0]](c[1]) for c in np.asarray(commands).reshape(-1,2)]\n", 102 | " return commands\n", 103 | "\n", 104 | "def convert(curve):\n", 105 | " edges = []\n", 106 | " close = False\n", 107 | " for c in curve:\n", 108 | " if isinstance(c, Move):\n", 109 | " e, p = c.convert()\n", 110 | " start = e\n", 111 | " elif isinstance(c, Curve):\n", 112 | " e, p = c.convert(p)\n", 113 | " edges.append(e)\n", 114 | " elif isinstance(c, Close):\n", 115 | " close = True\n", 116 | "\n", 117 | " wire_builder = BRepBuilderAPI_MakeWire()\n", 118 | "\n", 119 | " for e in edges:\n", 120 | " wire_builder.Add(e)\n", 121 | "\n", 122 | " wire_builder.Build()\n", 123 | "\n", 124 | " if not wire_builder.IsDone():\n", 125 | " print(\"error\") \n", 126 | " else:\n", 127 | " wire = Wire(wire_builder.Wire())\n", 128 | " if close:\n", 129 | " return wire\n", 130 | " else:\n", 131 | " return wire\n", 132 | "\n", 133 | "curve1 = parse(svg1)\n", 134 | "edges1 = convert(curve1)\n", 135 | "curve2 = parse(svg2)\n", 136 | "edges2 = convert(curve2)\n", 137 | "edges2 = edges2.translate((0, dist, 0))\n", 138 | "\n", 139 | "top = cq.Workplane(edges1)\n", 140 | "top.ctx.pendingWires.append(edges1)\n", 141 | "top = top.extrude(2)\n", 142 | "\n", 143 | "bottom = cq.Workplane(edges2)\n", 144 | "bottom.ctx.pendingWires.append(edges2)\n", 145 | "bottom = bottom.extrude(2)\n", 146 | "\n", 147 | "jupyter = top.union(bottom).rotate((0,0,0), (1,0,0), 90)\n", 148 | "c = jupyter.val().Center().toTuple()\n", 149 | "jupyter = jupyter.translate((-c[0], -c[1], -c[2]))\n", 150 | "\n", 151 | "cadquery = (\n", 152 | " cq.Workplane(\"XZ\")\n", 153 | " .text(\"cq\", 24, 2, combine='cut', halign=\"center\", valign=\"top\", font=\"roboto\")\n", 154 | " .translate((-c[0]+17, -c[1], 5.2-c[2]))\n", 155 | "\n", 156 | ")\n", 157 | "\n", 158 | "logo = PartGroup([\n", 159 | " Part(jupyter, name=\"jupyter\", color=\"#2980b9\"),\n", 160 | " Part(cadquery, name=\"cadquery\", color=\"#ffffff\"),\n", 161 | "], name=\"jupyter cadquery\")\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "a319f032-2fe5-44ac-a205-3ad75c256a9a", 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "params = dict(\n", 172 | "# position=[80.7, -144.2, 39.4], \n", 173 | "# quaternion=[0.5993, 0.1576, 0.1964, 0.7599],\n", 174 | " position=[88.3, -118.6, 28.7], \n", 175 | " quaternion=[0.6019, 0.2058, 0.2358, 0.7347],\n", 176 | " grid=(True, False, False), \n", 177 | " ticks=3, \n", 178 | " ortho=False,\n", 179 | " default_edge_color=\"#bbb\",\n", 180 | ")" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "3a348944-c0b1-4fba-b989-dc154c462fa9", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "d = show(\n", 191 | " logo, \n", 192 | " theme=\"light\",\n", 193 | " **params\n", 194 | ")" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "id": "a402cedb-c1f2-402e-a764-e640fabb93d5", 201 | "metadata": {}, 202 | "outputs": [], 203 | "source": [ 204 | "import pickle\n", 205 | "import json\n", 206 | "import base64\n", 207 | "from jupyter_cadquery.utils import Color\n", 208 | "\n", 209 | "data = _convert(\n", 210 | " logo,\n", 211 | " **params\n", 212 | ")\n", 213 | "\n", 214 | "del data[\"config\"][\"default_color\"]\n", 215 | "\n", 216 | "msg = pickle.dumps(data, 4)\n", 217 | "# msg = json.dumps(data, default=default)\n", 218 | "\n", 219 | "with open(\"../jupyter_cadquery/logo.py\", \"w\") as fd:\n", 220 | " fd.write(\"LOGO_DATA = '%s'\" % base64.b64encode(msg).decode())\n", 221 | " # fd.write(\"LOGO_DATA = '%s'\" % msg)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": null, 227 | "id": "74ee9ecf-9a4f-40a3-9552-ddbea8f9cb61", 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "# import numpy as np\n", 232 | "# \n", 233 | "# def default(obj):\n", 234 | "# if type(obj).__module__ == np.__name__:\n", 235 | "# if isinstance(obj, np.ndarray):\n", 236 | "# return obj.tolist()\n", 237 | "# else:\n", 238 | "# return obj.item()\n", 239 | "# raise TypeError('Unknown type:', type(obj))\n", 240 | "# " 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "id": "1e48e3c5-5b95-4cd9-9b87-25e2081f4d10", 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "# def round6(obj):\n", 251 | "# if isinstance(obj, (np.ndarray, list, tuple)):\n", 252 | "# return [round6(tup) for tup in obj]\n", 253 | "# elif isinstance(obj, np.int32):\n", 254 | "# return obj.item()\n", 255 | "# else:\n", 256 | "# if isinstance(obj, (np.float32, np.float64)):\n", 257 | "# return round(obj.item(),6)\n", 258 | "# else:\n", 259 | "# return round(obj, 6)\n", 260 | "# \n", 261 | "# for part in data[\"data\"][\"shapes\"][\"parts\"]:\n", 262 | "# for array in [\"vertices\", \"normals\", \"triangles\", \"edges\"]:\n", 263 | "# part[\"shape\"][array] = [round6(x) for x in part[\"shape\"][array]]\n", 264 | "# \n", 265 | "# #json.dumps(data, default=default)\n", 266 | "# with open(\"../jupyter_cadquery/logo2.py\", \"w\") as fd:\n", 267 | "# fd.write(\"LOGO_DATA = '%s'\" % json.dumps(data, default=default))" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "id": "12512e4e-a30b-45db-8930-8e354b43b8a7", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [] 277 | } 278 | ], 279 | "metadata": { 280 | "kernelspec": { 281 | "display_name": "Python 3 (ipykernel)", 282 | "language": "python", 283 | "name": "python3" 284 | }, 285 | "language_info": { 286 | "codemirror_mode": { 287 | "name": "ipython", 288 | "version": 3 289 | }, 290 | "file_extension": ".py", 291 | "mimetype": "text/x-python", 292 | "name": "python", 293 | "nbconvert_exporter": "python", 294 | "pygments_lexer": "ipython3", 295 | "version": "3.9.12" 296 | }, 297 | "vscode": { 298 | "interpreter": { 299 | "hash": "3852a12778ea076f548ed5ee712095683d7085d4bce64a988aa4a97a2b0cc207" 300 | } 301 | } 302 | }, 303 | "nbformat": 4, 304 | "nbformat_minor": 5 305 | } 306 | -------------------------------------------------------------------------------- /notebooks/icons/dark/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/bottom.png -------------------------------------------------------------------------------- /notebooks/icons/dark/edges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/edges.png -------------------------------------------------------------------------------- /notebooks/icons/dark/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/empty.png -------------------------------------------------------------------------------- /notebooks/icons/dark/empty_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/empty_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/dark/empty_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/empty_shape.png -------------------------------------------------------------------------------- /notebooks/icons/dark/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/fit.png -------------------------------------------------------------------------------- /notebooks/icons/dark/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/front.png -------------------------------------------------------------------------------- /notebooks/icons/dark/isometric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/isometric.png -------------------------------------------------------------------------------- /notebooks/icons/dark/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/left.png -------------------------------------------------------------------------------- /notebooks/icons/dark/mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/mesh.png -------------------------------------------------------------------------------- /notebooks/icons/dark/mix_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/mix_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/dark/mix_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/mix_shape.png -------------------------------------------------------------------------------- /notebooks/icons/dark/no_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/no_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/dark/no_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/no_shape.png -------------------------------------------------------------------------------- /notebooks/icons/dark/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/plane.png -------------------------------------------------------------------------------- /notebooks/icons/dark/rear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/rear.png -------------------------------------------------------------------------------- /notebooks/icons/dark/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/reset.png -------------------------------------------------------------------------------- /notebooks/icons/dark/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/right.png -------------------------------------------------------------------------------- /notebooks/icons/dark/shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/shape.png -------------------------------------------------------------------------------- /notebooks/icons/dark/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/dark/top.png -------------------------------------------------------------------------------- /notebooks/icons/light/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/bottom.png -------------------------------------------------------------------------------- /notebooks/icons/light/edges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/edges.png -------------------------------------------------------------------------------- /notebooks/icons/light/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/empty.png -------------------------------------------------------------------------------- /notebooks/icons/light/empty_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/empty_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/light/empty_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/empty_shape.png -------------------------------------------------------------------------------- /notebooks/icons/light/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/fit.png -------------------------------------------------------------------------------- /notebooks/icons/light/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/front.png -------------------------------------------------------------------------------- /notebooks/icons/light/isometric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/isometric.png -------------------------------------------------------------------------------- /notebooks/icons/light/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/left.png -------------------------------------------------------------------------------- /notebooks/icons/light/mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/mesh.png -------------------------------------------------------------------------------- /notebooks/icons/light/mix_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/mix_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/light/mix_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/mix_shape.png -------------------------------------------------------------------------------- /notebooks/icons/light/no_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/no_mesh.png -------------------------------------------------------------------------------- /notebooks/icons/light/no_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/no_shape.png -------------------------------------------------------------------------------- /notebooks/icons/light/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/plane.png -------------------------------------------------------------------------------- /notebooks/icons/light/rear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/rear.png -------------------------------------------------------------------------------- /notebooks/icons/light/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/reset.png -------------------------------------------------------------------------------- /notebooks/icons/light/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/right.png -------------------------------------------------------------------------------- /notebooks/icons/light/shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/shape.png -------------------------------------------------------------------------------- /notebooks/icons/light/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/notebooks/icons/light/top.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-jupyter-builder"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "jupyter_cadquery" 7 | version = "4.0.2" 8 | description = "An extension to render cadquery objects in JupyterLab via pythreejs" 9 | readme = "README.md" 10 | authors = [{ name = "Bernhard Walter", email = "b_walter@arcor.de" }] 11 | license = { file = "LICENSE" } 12 | requires-python = ">=3.8" 13 | keywords = ["ipython", "jupyter", "widgets", "CAD", "cadquery"] 14 | classifiers = [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Framework :: IPython", 17 | "Intended Audience :: Developers", 18 | "Intended Audience :: Science/Research", 19 | "Topic :: Multimedia :: Graphics", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.8", 22 | "Programming Language :: Python :: 3.9", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | "Programming Language :: Python :: 3.12", 26 | ] 27 | dependencies = [ 28 | "jupyterlab>=4.3.6,<5", 29 | "webcolors~=24.8.0", 30 | "cad-viewer-widget~=3.0.2", 31 | "cachetools~=5.5.0", 32 | "orjson~=3.10.16", 33 | "ocp_vscode~=2.7.0", 34 | ] 35 | 36 | [project.optional-dependencies] 37 | dev = [ 38 | "jupyter-packaging", 39 | "cookiecutter", 40 | "twine", 41 | "bumpversion", 42 | "black", 43 | "pylint", 44 | "pyYaml", 45 | ] 46 | 47 | [tool.hatch.metadata] 48 | allow-direct-references = true 49 | 50 | [tool.hatch.build.targets.wheel] 51 | include = [ 52 | "jupyter_cadquery/*.py", 53 | "jupyter-config/jupyter_server_config.d/jupyter_cadquery.json", 54 | ] 55 | 56 | [tool.hatch.build.targets.wheel.shared-data] 57 | "jupyter-config/jupyter_server_config.d" = "etc/jupyter/jupyter_server_config.d" 58 | 59 | [tool.hatch.envs.default.scripts] 60 | jupyter-cadquery = "jupyter_cadquery.app:JupyterCadqueryBackend.launch_instance" 61 | 62 | 63 | # = = = = = = = = = = = = = = 64 | # bump version 65 | # = = = = = = = = = = = = = = 66 | 67 | 68 | [tool.bumpversion] 69 | current_version = "4.0.2" 70 | commit = false 71 | tag = false 72 | parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)(?P\\d*)(?P\\d*)" 73 | serialize = ["{major}.{minor}.{patch}"] 74 | allow_dirty = true 75 | message = "Bump version: {current_version} → {new_version}" 76 | search = "{current_version}" 77 | replace = "{new_version}" 78 | regex = false 79 | ignore_missing_version = false 80 | ignore_missing_files = false 81 | sign_tags = false 82 | commit_args = "" 83 | setup_hooks = [] 84 | pre_commit_hooks = [] 85 | post_commit_hooks = [] 86 | 87 | [[tool.bumpversion.files]] 88 | filename = "jupyter_cadquery/_version.py" 89 | search = '__version__ = "{current_version}"' 90 | replace = '__version__ = "{new_version}"' 91 | 92 | [[tool.bumpversion.files]] 93 | filename = "README.md" 94 | search = 'jupyter-cadquery=={current_version}' 95 | replace = 'jupyter-cadquery=={new_version}' 96 | 97 | [[tool.bumpversion.files]] 98 | filename = "README.md" 99 | search = 'Current version: **v{current_version}**' 100 | replace = 'Current version: **v{new_version}**' 101 | 102 | [[tool.bumpversion.files]] 103 | filename = "README.md" 104 | search = 'jupyter_cadquery {current_version} OK' 105 | replace = 'jupyter_cadquery {new_version} OK' 106 | 107 | [[tool.bumpversion.files]] 108 | filename = "environment.yml" 109 | search = 'jupyter-cadquery=={current_version}' 110 | replace = 'jupyter-cadquery=={new_version}' 111 | 112 | [[tool.bumpversion.files]] 113 | filename = "docker/Dockerfile" 114 | search = 'jupyter-cadquery=={current_version}' 115 | replace = 'jupyter-cadquery=={new_version}' 116 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | notebook~=7.4.0 2 | matplotlib~=3.10.1 3 | cadquery @ git+https://github.com/cadquery/cadquery 4 | build123d @ git+https://github.com/gumyr/build123d 5 | cadquery-massembly==1.0.0 6 | jupyter-cadquery @ git+https://github.com/bernhard-42/jupyter-cadquery -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.12 2 | -------------------------------------------------------------------------------- /screenshots/0_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/0_intro.png -------------------------------------------------------------------------------- /screenshots/debugging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/debugging.gif -------------------------------------------------------------------------------- /screenshots/explode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/explode.gif -------------------------------------------------------------------------------- /screenshots/hexapod-crawling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/hexapod-crawling.gif -------------------------------------------------------------------------------- /screenshots/hexapod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/hexapod.png -------------------------------------------------------------------------------- /screenshots/jupyter-cadquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/jupyter-cadquery.png -------------------------------------------------------------------------------- /screenshots/measure.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/measure.gif -------------------------------------------------------------------------------- /screenshots/replay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/replay.gif -------------------------------------------------------------------------------- /screenshots/sidecar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/sidecar.png -------------------------------------------------------------------------------- /screenshots/viewer-locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/screenshots/viewer-locations.png -------------------------------------------------------------------------------- /tests/compare_edges_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/tests/compare_edges_output.png -------------------------------------------------------------------------------- /tests/compare_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/tests/compare_output.png -------------------------------------------------------------------------------- /tests/compare_rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhard-42/jupyter-cadquery/3a4156030eebec7dd0164e4637d0dec4160e3243/tests/compare_rotate.png -------------------------------------------------------------------------------- /tests/testlib.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import time 3 | from PIL import Image, ImageChops 4 | import numpy as np 5 | import io 6 | import sys 7 | 8 | import ipywidgets 9 | from IPython import get_ipython 10 | from IPython.display import display 11 | 12 | def list_approx(l1, l2): 13 | assert len(l1) == len(l2) 14 | for i, (x1,x2) in enumerate(zip(l1, l2)): 15 | assert abs((x1 - x2) / x2) < 0.01, f"Element {i} differs: {x1}, {x2}" 16 | 17 | 18 | def save_test(cv, name): 19 | filename = f'{name.replace(" ", "_").lower()}.png' 20 | print(filename) 21 | cv.widget.test_func = None 22 | cv.export_png(filename) 23 | 24 | 25 | class Test(object): 26 | def __init__(self, name, out, cv, compare=False): 27 | self.name = name 28 | self.out = out 29 | self.cv = cv 30 | self.compare = compare 31 | self.filename = f'{name.replace(" ", "_").lower()}.png' 32 | 33 | def __enter__(self): 34 | with self.out: 35 | print(f"Test '{self.name}'") 36 | 37 | if compare: 38 | self.cv.widget.test_func = compare(self.filename, self.out, self.cv) 39 | 40 | 41 | def __exit__(self, type, value, traceback): 42 | ... 43 | 44 | 45 | def compare(filename, output, cv): 46 | def inner(data): 47 | 48 | img1 = Image.open(io.BytesIO(data)) 49 | img2 = Image.open(filename) 50 | 51 | diff = ImageChops.difference(img1, img2).convert("RGB") 52 | 53 | values = np.array(diff) 54 | w,h,c = values.shape 55 | result = sum(values.flatten()) / (w*h*c) 56 | 57 | cv.widget.test_func = None 58 | 59 | with output: 60 | assert result < 0.1, f"Image diff is {result}" 61 | 62 | return inner 63 | 64 | # Notebook execution stop 65 | 66 | class NotebookExit(BaseException): 67 | pass 68 | 69 | ipython = get_ipython() 70 | _showtraceback = ipython._showtraceback 71 | 72 | def _exception_handler(exception_type, exception, traceback): 73 | if exception_type == NotebookExit: 74 | print("Notebook paused: %s" % (exception), file=sys.stderr) 75 | else: 76 | _showtraceback(exception_type, exception, traceback) 77 | 78 | ipython._showtraceback = _exception_handler 79 | 80 | def stop(msg): 81 | raise NotebookExit(msg) 82 | 83 | # Log output 84 | 85 | out = ipywidgets.Output() 86 | 87 | display(out) 88 | 89 | with out: 90 | print("Test log:") -------------------------------------------------------------------------------- /validate_nb.py: -------------------------------------------------------------------------------- 1 | import nbformat 2 | import json 3 | import sys 4 | 5 | with open(sys.argv[1], "r") as fd: 6 | nb = json.load(fd) 7 | 8 | try: 9 | nbformat.validate(nb) 10 | print("==> OK") 11 | except: 12 | print("==> ERROR") 13 | --------------------------------------------------------------------------------