pycodecad 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycodecad-1.0.0/.gitignore +14 -0
- pycodecad-1.0.0/CHANGELOG.md +5 -0
- pycodecad-1.0.0/LICENSE +21 -0
- pycodecad-1.0.0/PKG-INFO +117 -0
- pycodecad-1.0.0/README.md +94 -0
- pycodecad-1.0.0/examples/assembly.py +7 -0
- pycodecad-1.0.0/examples/assets/logo.svg +1 -0
- pycodecad-1.0.0/examples/assets/pyramid.stl +44 -0
- pycodecad-1.0.0/examples/embedded_app.py +77 -0
- pycodecad-1.0.0/examples/gear.py +6 -0
- pycodecad-1.0.0/examples/gearbox.py +30 -0
- pycodecad-1.0.0/examples/gears_turning.py +11 -0
- pycodecad-1.0.0/examples/import_files.py +13 -0
- pycodecad-1.0.0/examples/parts.py +46 -0
- pycodecad-1.0.0/examples/tray.py +28 -0
- pycodecad-1.0.0/pyproject.toml +61 -0
- pycodecad-1.0.0/src/pycodecad/__init__.py +18 -0
- pycodecad-1.0.0/src/pycodecad/__main__.py +3 -0
- pycodecad-1.0.0/src/pycodecad/api.py +105 -0
- pycodecad-1.0.0/src/pycodecad/app.py +34 -0
- pycodecad-1.0.0/src/pycodecad/cad.py +261 -0
- pycodecad-1.0.0/src/pycodecad/camera.py +136 -0
- pycodecad-1.0.0/src/pycodecad/cli.py +241 -0
- pycodecad-1.0.0/src/pycodecad/context.py +178 -0
- pycodecad-1.0.0/src/pycodecad/editor.py +256 -0
- pycodecad-1.0.0/src/pycodecad/embed.py +16 -0
- pycodecad-1.0.0/src/pycodecad/files.py +262 -0
- pycodecad-1.0.0/src/pycodecad/icons/LICENSE +43 -0
- pycodecad-1.0.0/src/pycodecad/icons/__init__.py +31 -0
- pycodecad-1.0.0/src/pycodecad/icons/lucide.ttf +0 -0
- pycodecad-1.0.0/src/pycodecad/imgui_backend.py +269 -0
- pycodecad-1.0.0/src/pycodecad/params.py +177 -0
- pycodecad-1.0.0/src/pycodecad/renderer.py +327 -0
- pycodecad-1.0.0/src/pycodecad/runner.py +404 -0
- pycodecad-1.0.0/src/pycodecad/sidecar.py +86 -0
- pycodecad-1.0.0/src/pycodecad/textedit.py +290 -0
- pycodecad-1.0.0/src/pycodecad/ui.py +677 -0
- pycodecad-1.0.0/src/pycodecad/viewcube.py +120 -0
- pycodecad-1.0.0/src/pycodecad/viewer.py +70 -0
- pycodecad-1.0.0/src/pycodecad/window.py +129 -0
- pycodecad-1.0.0/src/pycodecad/workspace.py +545 -0
- pycodecad-1.0.0/tests/conftest.py +61 -0
- pycodecad-1.0.0/tests/test_camera.py +65 -0
- pycodecad-1.0.0/tests/test_cli.py +328 -0
- pycodecad-1.0.0/tests/test_embed.py +226 -0
- pycodecad-1.0.0/tests/test_export_consistency.py +48 -0
- pycodecad-1.0.0/tests/test_files.py +157 -0
- pycodecad-1.0.0/tests/test_frames.py +104 -0
- pycodecad-1.0.0/tests/test_params.py +386 -0
- pycodecad-1.0.0/tests/test_runner.py +329 -0
- pycodecad-1.0.0/tests/test_textedit.py +88 -0
- pycodecad-1.0.0/tests/test_viewcube.py +95 -0
- pycodecad-1.0.0/tests/test_workspace.py +719 -0
pycodecad-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 offerrall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pycodecad-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pycodecad
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Code-CAD with build123d: a desktop window that shows and exports what your Python script builds.
|
|
5
|
+
Project-URL: Repository, https://github.com/offerrall/pycodecad
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: 3d,3d-printing,build123d,cad,parametric
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: build123d<0.14,>=0.13
|
|
17
|
+
Requires-Dist: glfw<3,>=2.7
|
|
18
|
+
Requires-Dist: moderngl<6,>=5.10
|
|
19
|
+
Requires-Dist: numpy<3,>=1.26
|
|
20
|
+
Requires-Dist: pytypehint==1.2.0
|
|
21
|
+
Requires-Dist: slimgui<0.9,>=0.8.3
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# pycodecad
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/pycodecad/)
|
|
27
|
+
[](https://pypi.org/project/pycodecad/)
|
|
28
|
+
[](docs/design.md#platforms)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
|
|
31
|
+
Code-CAD with [build123d](https://github.com/gumyr/build123d): write a Python script, see the part,
|
|
32
|
+
export it for 3D printing.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from build123d import Box, Cylinder
|
|
38
|
+
from pycodecad import show
|
|
39
|
+
|
|
40
|
+
plate = Box(40, 30, 8) - Cylinder(5, 8)
|
|
41
|
+
show(plate, name="plate", color="#F4B02A")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pycodecad # Python 3.12+, OpenGL 3.3
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pycodecad examples # copy the examples to ./pycodecad-examples and open them
|
|
54
|
+
pycodecad part.py # a window on part.py (created from an example if missing)
|
|
55
|
+
pycodecad parts/gear/ # a window on a folder: a part in several files
|
|
56
|
+
pycodecad check part.py # run it without a window, print the result as JSON
|
|
57
|
+
pycodecad export part.py part.stl # also .3mf .step .glb .brep
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## What you get
|
|
61
|
+
|
|
62
|
+
- **A window** on a part: its files, the code and the part side by side. It runs the part once when
|
|
63
|
+
it opens, then only when you press Run, and writes only when you press Save.
|
|
64
|
+
- **Parameters**: `expose(fn)` turns the arguments of a function into sliders, number boxes and
|
|
65
|
+
checkboxes.
|
|
66
|
+
- **Animations**: `frame()` saves the scene as a frame; the window plays them. Parts built once and
|
|
67
|
+
moved with `Pos` are not computed again, so mechanisms play in real time.
|
|
68
|
+
- **A command line**: `check`, `render` and `export` run the same script without a window.
|
|
69
|
+
- **An AI workflow**: an assistant edits the file and checks its own work with `check` and
|
|
70
|
+
`render`.
|
|
71
|
+
- **Parts for your own app** (experimental): the window's code, parameters and 3D view as Dear
|
|
72
|
+
ImGui components, in `pycodecad.embed`.
|
|
73
|
+
|
|
74
|
+
<table>
|
|
75
|
+
<tr>
|
|
76
|
+
<td width="33%"><img src="docs/images/parameters.png" alt="The Parameters panel: sliders, a number box and a checkbox made by expose()"></td>
|
|
77
|
+
<td width="33%"><img src="docs/images/error.png" alt="A failed run: the line marked in the editor and the traceback under it"></td>
|
|
78
|
+
<td width="33%"><img src="docs/images/render-grid.png" alt="pycodecad render: a gear in iso, front, top and right views"></td>
|
|
79
|
+
</tr>
|
|
80
|
+
<tr>
|
|
81
|
+
<td align="center">Parameters from <code>expose()</code></td>
|
|
82
|
+
<td align="center">Errors point at the line</td>
|
|
83
|
+
<td align="center"><code>pycodecad render</code>: what an AI sees</td>
|
|
84
|
+
</tr>
|
|
85
|
+
</table>
|
|
86
|
+
|
|
87
|
+
pycodecad is small on purpose, readable in an afternoon: see [Design](docs/design.md#small-on-purpose).
|
|
88
|
+
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
- [Getting started](docs/getting-started.md): install, a first part, run, export.
|
|
92
|
+
- [The window](docs/window.md): layout, Run and Save, Reload, read-only mode, shortcuts.
|
|
93
|
+
- [Parameters](docs/parameters.md): `expose()`, its controls and `--set`.
|
|
94
|
+
- [Scripts](docs/scripts.md): `show`, `clear`, `frame` (animations), `import_mesh`, colors, paths, errors.
|
|
95
|
+
- [Command line](docs/cli.md): `check`, `render`, `export`, `context`, exit codes.
|
|
96
|
+
- [Working with an AI assistant](docs/ai.md): the AI context and how an assistant checks its work.
|
|
97
|
+
- [Files](docs/files.md): everything pycodecad writes, and where.
|
|
98
|
+
- [Embedding](docs/embedding.md): the window's parts in your own app (experimental).
|
|
99
|
+
- [Design](docs/design.md): principles, platforms and plans.
|
|
100
|
+
- [Development](docs/development.md): tests, type checks and releases.
|
|
101
|
+
|
|
102
|
+
Examples: [examples/](examples/)
|
|
103
|
+
|
|
104
|
+
## Credits
|
|
105
|
+
|
|
106
|
+
- [build123d](https://github.com/gumyr/build123d): the modeling language
|
|
107
|
+
- [Open CASCADE](https://dev.opencascade.org/) and [OCP](https://github.com/CadQuery/OCP): the geometry kernel
|
|
108
|
+
- [Dear ImGui](https://github.com/ocornut/imgui) and [slimgui](https://github.com/nurpax/slimgui): the interface
|
|
109
|
+
- [ModernGL](https://github.com/moderngl/moderngl), [GLFW](https://www.glfw.org/) and
|
|
110
|
+
[pyGLFW](https://github.com/FlorianRhiem/pyGLFW): rendering and windows
|
|
111
|
+
- [NumPy](https://numpy.org/): meshes
|
|
112
|
+
- [pytypehint](https://github.com/offerrall/pytypehint): the parameters of `expose()`
|
|
113
|
+
- [Lucide](https://lucide.dev): icons (ISC)
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# pycodecad
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pycodecad/)
|
|
4
|
+
[](https://pypi.org/project/pycodecad/)
|
|
5
|
+
[](docs/design.md#platforms)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Code-CAD with [build123d](https://github.com/gumyr/build123d): write a Python script, see the part,
|
|
9
|
+
export it for 3D printing.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from build123d import Box, Cylinder
|
|
15
|
+
from pycodecad import show
|
|
16
|
+
|
|
17
|
+
plate = Box(40, 30, 8) - Cylinder(5, 8)
|
|
18
|
+
show(plate, name="plate", color="#F4B02A")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install pycodecad # Python 3.12+, OpenGL 3.3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pycodecad examples # copy the examples to ./pycodecad-examples and open them
|
|
31
|
+
pycodecad part.py # a window on part.py (created from an example if missing)
|
|
32
|
+
pycodecad parts/gear/ # a window on a folder: a part in several files
|
|
33
|
+
pycodecad check part.py # run it without a window, print the result as JSON
|
|
34
|
+
pycodecad export part.py part.stl # also .3mf .step .glb .brep
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What you get
|
|
38
|
+
|
|
39
|
+
- **A window** on a part: its files, the code and the part side by side. It runs the part once when
|
|
40
|
+
it opens, then only when you press Run, and writes only when you press Save.
|
|
41
|
+
- **Parameters**: `expose(fn)` turns the arguments of a function into sliders, number boxes and
|
|
42
|
+
checkboxes.
|
|
43
|
+
- **Animations**: `frame()` saves the scene as a frame; the window plays them. Parts built once and
|
|
44
|
+
moved with `Pos` are not computed again, so mechanisms play in real time.
|
|
45
|
+
- **A command line**: `check`, `render` and `export` run the same script without a window.
|
|
46
|
+
- **An AI workflow**: an assistant edits the file and checks its own work with `check` and
|
|
47
|
+
`render`.
|
|
48
|
+
- **Parts for your own app** (experimental): the window's code, parameters and 3D view as Dear
|
|
49
|
+
ImGui components, in `pycodecad.embed`.
|
|
50
|
+
|
|
51
|
+
<table>
|
|
52
|
+
<tr>
|
|
53
|
+
<td width="33%"><img src="docs/images/parameters.png" alt="The Parameters panel: sliders, a number box and a checkbox made by expose()"></td>
|
|
54
|
+
<td width="33%"><img src="docs/images/error.png" alt="A failed run: the line marked in the editor and the traceback under it"></td>
|
|
55
|
+
<td width="33%"><img src="docs/images/render-grid.png" alt="pycodecad render: a gear in iso, front, top and right views"></td>
|
|
56
|
+
</tr>
|
|
57
|
+
<tr>
|
|
58
|
+
<td align="center">Parameters from <code>expose()</code></td>
|
|
59
|
+
<td align="center">Errors point at the line</td>
|
|
60
|
+
<td align="center"><code>pycodecad render</code>: what an AI sees</td>
|
|
61
|
+
</tr>
|
|
62
|
+
</table>
|
|
63
|
+
|
|
64
|
+
pycodecad is small on purpose, readable in an afternoon: see [Design](docs/design.md#small-on-purpose).
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
- [Getting started](docs/getting-started.md): install, a first part, run, export.
|
|
69
|
+
- [The window](docs/window.md): layout, Run and Save, Reload, read-only mode, shortcuts.
|
|
70
|
+
- [Parameters](docs/parameters.md): `expose()`, its controls and `--set`.
|
|
71
|
+
- [Scripts](docs/scripts.md): `show`, `clear`, `frame` (animations), `import_mesh`, colors, paths, errors.
|
|
72
|
+
- [Command line](docs/cli.md): `check`, `render`, `export`, `context`, exit codes.
|
|
73
|
+
- [Working with an AI assistant](docs/ai.md): the AI context and how an assistant checks its work.
|
|
74
|
+
- [Files](docs/files.md): everything pycodecad writes, and where.
|
|
75
|
+
- [Embedding](docs/embedding.md): the window's parts in your own app (experimental).
|
|
76
|
+
- [Design](docs/design.md): principles, platforms and plans.
|
|
77
|
+
- [Development](docs/development.md): tests, type checks and releases.
|
|
78
|
+
|
|
79
|
+
Examples: [examples/](examples/)
|
|
80
|
+
|
|
81
|
+
## Credits
|
|
82
|
+
|
|
83
|
+
- [build123d](https://github.com/gumyr/build123d): the modeling language
|
|
84
|
+
- [Open CASCADE](https://dev.opencascade.org/) and [OCP](https://github.com/CadQuery/OCP): the geometry kernel
|
|
85
|
+
- [Dear ImGui](https://github.com/ocornut/imgui) and [slimgui](https://github.com/nurpax/slimgui): the interface
|
|
86
|
+
- [ModernGL](https://github.com/moderngl/moderngl), [GLFW](https://www.glfw.org/) and
|
|
87
|
+
[pyGLFW](https://github.com/FlorianRhiem/pyGLFW): rendering and windows
|
|
88
|
+
- [NumPy](https://numpy.org/): meshes
|
|
89
|
+
- [pytypehint](https://github.com/offerrall/pytypehint): the parameters of `expose()`
|
|
90
|
+
- [Lucide](https://lucide.dev): icons (ISC)
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20mm" height="20mm" viewBox="0 0 20 20"><path d="M0 0 H20 V5 H5 V20 H0 Z"/></svg>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
solid pyramid
|
|
2
|
+
facet normal 0 0 -1
|
|
3
|
+
outer loop
|
|
4
|
+
vertex 30 -10 0
|
|
5
|
+
vertex 50 10 0
|
|
6
|
+
vertex 50 -10 0
|
|
7
|
+
endloop
|
|
8
|
+
endfacet
|
|
9
|
+
facet normal 0 0 -1
|
|
10
|
+
outer loop
|
|
11
|
+
vertex 30 -10 0
|
|
12
|
+
vertex 30 10 0
|
|
13
|
+
vertex 50 10 0
|
|
14
|
+
endloop
|
|
15
|
+
endfacet
|
|
16
|
+
facet normal 0 -0.83205 0.5547
|
|
17
|
+
outer loop
|
|
18
|
+
vertex 30 -10 0
|
|
19
|
+
vertex 50 -10 0
|
|
20
|
+
vertex 40 0 15
|
|
21
|
+
endloop
|
|
22
|
+
endfacet
|
|
23
|
+
facet normal 0.83205 -0 0.5547
|
|
24
|
+
outer loop
|
|
25
|
+
vertex 50 -10 0
|
|
26
|
+
vertex 50 10 0
|
|
27
|
+
vertex 40 0 15
|
|
28
|
+
endloop
|
|
29
|
+
endfacet
|
|
30
|
+
facet normal 0 0.83205 0.5547
|
|
31
|
+
outer loop
|
|
32
|
+
vertex 50 10 0
|
|
33
|
+
vertex 30 10 0
|
|
34
|
+
vertex 40 0 15
|
|
35
|
+
endloop
|
|
36
|
+
endfacet
|
|
37
|
+
facet normal -0.83205 0 0.5547
|
|
38
|
+
outer loop
|
|
39
|
+
vertex 30 10 0
|
|
40
|
+
vertex 30 -10 0
|
|
41
|
+
vertex 40 0 15
|
|
42
|
+
endloop
|
|
43
|
+
endfacet
|
|
44
|
+
endsolid pyramid
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""An order desk built on pycodecad's ImGui components (pycodecad.embed, experimental in 1.0): pick an
|
|
2
|
+
order and the tray of tray.py is rebuilt to its measures; export it for the printer.
|
|
3
|
+
|
|
4
|
+
python examples/embedded_app.py # the app
|
|
5
|
+
python examples/embedded_app.py shot.png # hidden: save a picture after the first run, then quit
|
|
6
|
+
|
|
7
|
+
tray.py opens read only (a template): the orders never change it. Exports are written next
|
|
8
|
+
to it, one file per order (order_1041.stl ...).
|
|
9
|
+
"""
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from slimgui import imgui
|
|
14
|
+
|
|
15
|
+
import pycodecad.embed as p3
|
|
16
|
+
|
|
17
|
+
ORDERS = ( # number, customer, width, depth, height (mm), rounded corners
|
|
18
|
+
("1041", "Cafe Norte: cutlery", 120.0, 80.0, 30.0, True),
|
|
19
|
+
("1042", "Workshop: screws", 60.0, 40.0, 20.0, False),
|
|
20
|
+
("1043", "Studio: pencils", 180.0, 50.0, 25.0, True),
|
|
21
|
+
("1044", "Kitchen: spices", 150.0, 150.0, 60.0, True),
|
|
22
|
+
)
|
|
23
|
+
FIRST_USE = imgui.Cond.FIRST_USE_EVER
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def place(title: str, x: float, y: float, width: float, height: float) -> None:
|
|
27
|
+
imgui.set_next_window_pos((x, y), FIRST_USE)
|
|
28
|
+
imgui.set_next_window_size((width, height), FIRST_USE)
|
|
29
|
+
imgui.begin(title)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(screenshot: str | None = None) -> None:
|
|
33
|
+
window = p3.create_window("Tray orders", size=(1400, 860), visible=screenshot is None)
|
|
34
|
+
part = p3.Workspace(Path(__file__).with_name("tray.py"), read_only=True, run=False)
|
|
35
|
+
part.show_files = False # only the template, not the other files of its folder
|
|
36
|
+
preview = p3.Viewer() # the same tray from the front, with a camera of its own
|
|
37
|
+
preview.look_from((0.0, -1.0, 0.0))
|
|
38
|
+
chosen = None
|
|
39
|
+
while window.frame(keep_open=part.dirty()):
|
|
40
|
+
place("Template", 350, 10, 1040, 840) # first: draw() updates it, what follows sees this frame's state
|
|
41
|
+
part.draw()
|
|
42
|
+
part.close_prompt(window)
|
|
43
|
+
imgui.end()
|
|
44
|
+
|
|
45
|
+
place("Orders", 10, 10, 330, 400)
|
|
46
|
+
for index, (number, customer, width, depth, height, rounded) in enumerate(ORDERS):
|
|
47
|
+
if imgui.selectable(f"{number} {customer}", chosen == index)[0] or (screenshot and chosen is None):
|
|
48
|
+
chosen = index
|
|
49
|
+
part.values.update({"tray.width": width, "tray.depth": depth, "tray.height": height,
|
|
50
|
+
"tray.rounded": rounded})
|
|
51
|
+
part.run() # explicit, as always
|
|
52
|
+
if chosen is not None:
|
|
53
|
+
number, _, width, depth, height, _ = ORDERS[chosen]
|
|
54
|
+
imgui.separator()
|
|
55
|
+
imgui.text(f"Order {number}: {width:g} x {depth:g} x {height:g} mm")
|
|
56
|
+
imgui.begin_disabled(part.running() or part.error is not None)
|
|
57
|
+
if imgui.button("Export STL"):
|
|
58
|
+
part.export("stl", target=f"order_{number}.stl")
|
|
59
|
+
imgui.same_line()
|
|
60
|
+
if imgui.button("Export 3MF (Bambu)"):
|
|
61
|
+
part.export("3mf", "bambu", target=f"order_{number}.3mf")
|
|
62
|
+
imgui.end_disabled()
|
|
63
|
+
imgui.text_wrapped("Running..." if part.running() else part.error or part.message)
|
|
64
|
+
imgui.end()
|
|
65
|
+
|
|
66
|
+
place("Preview", 10, 420, 330, 430)
|
|
67
|
+
preview.draw(part.shown)
|
|
68
|
+
imgui.end()
|
|
69
|
+
|
|
70
|
+
if screenshot and part.ran() and not part.running():
|
|
71
|
+
window.screenshot(screenshot)
|
|
72
|
+
window.request_close()
|
|
73
|
+
window.close()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
main(*sys.argv[1:2])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""The parts composed into a gearbox: two meshing gears on shafts, on a plate held by brackets.
|
|
2
|
+
Each part is built once here; gearbox() only places them, so an animation of it is cheap."""
|
|
3
|
+
from build123d import Pos, Rot
|
|
4
|
+
from build123d.topology import Shape
|
|
5
|
+
|
|
6
|
+
from parts import bracket, gear, pitch_radius, plate, shaft
|
|
7
|
+
|
|
8
|
+
SMALL, LARGE, MODULE = 12, 24, 2.0
|
|
9
|
+
BACKLASH = 0.3
|
|
10
|
+
DISTANCE = pitch_radius(SMALL, MODULE) + pitch_radius(LARGE, MODULE) + BACKLASH
|
|
11
|
+
AXES = [(-DISTANCE / 2, 0.0), (DISTANCE / 2, 0.0)]
|
|
12
|
+
PLATE = 5.0
|
|
13
|
+
|
|
14
|
+
PINION, WHEEL = gear(SMALL, MODULE), gear(LARGE, MODULE)
|
|
15
|
+
SHAFT, BRACKET = shaft(6.0, 30.0), bracket(40.0, 4.0)
|
|
16
|
+
WIDTH = DISTANCE + 80
|
|
17
|
+
BASE = plate(WIDTH, 70, PLATE, AXES)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def gearbox(angle: float = 0.0) -> list[tuple[str, Shape | list[Shape], str]]:
|
|
21
|
+
"""(name, parts, color) with the pinion turned by angle degrees (the wheel follows)."""
|
|
22
|
+
(x0, y0), (x1, y1) = AXES
|
|
23
|
+
half_tooth = 180 / LARGE
|
|
24
|
+
return [
|
|
25
|
+
("plate", BASE, "#9AA3AB"),
|
|
26
|
+
("shaft", [Pos(x, y, PLATE) * SHAFT for x, y in AXES], "#D0D4D8"),
|
|
27
|
+
("pinion", Pos(x0, y0, PLATE + 12) * Rot(0, 0, angle) * PINION, "#F4B02A"),
|
|
28
|
+
("wheel", Pos(x1, y1, PLATE + 12) * Rot(0, 0, half_tooth - angle * SMALL / LARGE) * WHEEL, "#6947AE"),
|
|
29
|
+
("bracket", [Pos(-WIDTH / 2, 0, 0) * Rot(0, 0, 180) * BRACKET, Pos(WIDTH / 2, 0, 0) * BRACKET], "#48B5A4"),
|
|
30
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""An animation with frame(): the gearbox turning. The parts are built once (in gearbox.py) and
|
|
2
|
+
only placed in each frame, so 90 frames cost little more than one."""
|
|
3
|
+
from pycodecad import clear, frame, show
|
|
4
|
+
|
|
5
|
+
from gearbox import gearbox
|
|
6
|
+
|
|
7
|
+
for step in range(90):
|
|
8
|
+
clear()
|
|
9
|
+
for name, parts, color in gearbox(angle=360 * step / 90):
|
|
10
|
+
show(parts, name=name, color=color)
|
|
11
|
+
frame()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Extruded SVG next to a reference STL. The paths start from this file's folder, so the script
|
|
2
|
+
also works with plain `python examples/import_files.py` from any directory."""
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from build123d import extrude, import_svg
|
|
6
|
+
from pycodecad import import_mesh, show
|
|
7
|
+
|
|
8
|
+
assets = Path(__file__).parent / "assets"
|
|
9
|
+
height = 3.0
|
|
10
|
+
|
|
11
|
+
logo = extrude(import_svg(assets / "logo.svg"), amount=height) # pyright: ignore[reportArgumentType]
|
|
12
|
+
show(logo, name="logo", color="gold")
|
|
13
|
+
show(import_mesh(assets / "pyramid.stl"), name="reference", color="blue")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""The loose parts: one function per part, no show(). The other scripts compose them."""
|
|
2
|
+
from math import cos, pi, sin
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from build123d import Align, Box, Cylinder, Polygon, Pos, extrude
|
|
6
|
+
from build123d.topology import Shape
|
|
7
|
+
from pytypehint import Label, Max, Min, Slider
|
|
8
|
+
|
|
9
|
+
BOTTOM = (Align.CENTER, Align.CENTER, Align.MIN)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def pitch_radius(teeth: int, module: float) -> float:
|
|
13
|
+
return module * teeth / 2
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gear(teeth: Annotated[int, Min(8), Max(60), Slider(), Label("Teeth")] = 20,
|
|
17
|
+
module: Annotated[float, Min(0.5), Max(4.0), Slider(), Label("Module")] = 2.0,
|
|
18
|
+
thickness: Annotated[float, Min(2.0), Max(30.0), Slider(), Label("Thickness")] = 8.0,
|
|
19
|
+
bore: Annotated[float, Min(2.0), Max(20.0), Label("Bore")] = 6.0) -> Shape:
|
|
20
|
+
"""Spur gear with straight flanks and a hub, centred on the origin, standing on z=0."""
|
|
21
|
+
root, tip = pitch_radius(teeth, module) - 1.25 * module, pitch_radius(teeth, module) + module
|
|
22
|
+
outline = [(r * cos(a), r * sin(a))
|
|
23
|
+
for i in range(teeth)
|
|
24
|
+
for r, a in ((root, (2 * i - 0.62) * pi / teeth), (tip, (2 * i - 0.3) * pi / teeth),
|
|
25
|
+
(tip, (2 * i + 0.3) * pi / teeth), (root, (2 * i + 0.62) * pi / teeth))]
|
|
26
|
+
body = extrude(Polygon(*outline, align=None), thickness)
|
|
27
|
+
body += Pos(0, 0, thickness) * Cylinder(bore, thickness / 2, align=BOTTOM)
|
|
28
|
+
return body - Cylinder(bore / 2, thickness * 4)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def shaft(diameter: float = 6.0, length: float = 30.0) -> Shape:
|
|
32
|
+
return Cylinder(diameter / 2, length, align=BOTTOM)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def plate(width: float, depth: float, thickness: float, holes: list[tuple[float, float]]) -> Shape:
|
|
36
|
+
body = Box(width, depth, thickness, align=BOTTOM)
|
|
37
|
+
for x, y in holes:
|
|
38
|
+
body -= Pos(x, y, 0) * Cylinder(3.2, thickness, align=BOTTOM)
|
|
39
|
+
return body
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def bracket(length: float = 40.0, thickness: float = 4.0) -> Shape:
|
|
43
|
+
"""L bracket with a mounting hole, its corner on the origin."""
|
|
44
|
+
base = Pos(length / 2, 0, thickness / 2) * Box(length, 20, thickness)
|
|
45
|
+
wall = Pos(thickness / 2, 0, 12) * Box(thickness, 20, 24)
|
|
46
|
+
return base + wall - Pos(length * 0.6, 0, thickness / 2) * Cylinder(3, thickness)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Open tray made to measure: expose() gives the window a control per parameter (units: mm).
|
|
2
|
+
|
|
3
|
+
Change the values in the window and press Run (Ctrl+R); outside the window the defaults are used,
|
|
4
|
+
or `--set` on the command line: pycodecad export tray.py tray.stl --set width=120
|
|
5
|
+
"""
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
from build123d import Axis, Box, Pos, fillet
|
|
9
|
+
from pytypehint import Description, Label, Max, Min, Slider
|
|
10
|
+
from pycodecad import expose, show
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def tray(width: Annotated[float, Min(20.0), Max(200.0), Slider(), Label("Width")] = 80.0,
|
|
14
|
+
depth: Annotated[float, Min(20.0), Max(200.0), Slider(), Label("Depth")] = 60.0,
|
|
15
|
+
height: Annotated[float, Min(5.0), Max(100.0), Slider(), Label("Height")] = 25.0,
|
|
16
|
+
wall: Annotated[float, Min(0.8), Max(5.0), Label("Wall"),
|
|
17
|
+
Description("Also the thickness of the floor")] = 2.0,
|
|
18
|
+
rounded: Annotated[bool, Label("Rounded corners")] = True):
|
|
19
|
+
"""A box without lid: the outside measures width x depth x height, the floor is one wall thick."""
|
|
20
|
+
outside = Pos(0, 0, height / 2) * Box(width, depth, height)
|
|
21
|
+
inside = Pos(0, 0, wall + height / 2) * Box(width - 2 * wall, depth - 2 * wall, height)
|
|
22
|
+
if rounded:
|
|
23
|
+
outside = fillet(outside.edges().filter_by(Axis.Z), radius=min(width, depth) / 8)
|
|
24
|
+
inside = fillet(inside.edges().filter_by(Axis.Z), radius=max(min(width, depth) / 8 - wall, 0.5))
|
|
25
|
+
return outside - inside
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
show(expose(tray), name="tray", color="#4C9BE8")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pycodecad"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Code-CAD with build123d: a desktop window that shows and exports what your Python script builds."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
keywords = ["cad", "build123d", "3d", "parametric", "3d-printing"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 5 - Production/Stable",
|
|
12
|
+
"Operating System :: POSIX :: Linux",
|
|
13
|
+
"Operating System :: Microsoft :: Windows",
|
|
14
|
+
"Operating System :: MacOS",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"build123d>=0.13,<0.14",
|
|
20
|
+
"slimgui>=0.8.3,<0.9",
|
|
21
|
+
"glfw>=2.7,<3",
|
|
22
|
+
"moderngl>=5.10,<6",
|
|
23
|
+
"numpy>=1.26,<3",
|
|
24
|
+
"pytypehint==1.2.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
pycodecad = "pycodecad.cli:main"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = ["pytest>=8"]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
|
|
40
|
+
[tool.pyright]
|
|
41
|
+
include = ["src", "tests", "examples"]
|
|
42
|
+
pythonVersion = "3.12"
|
|
43
|
+
venvPath = "."
|
|
44
|
+
venv = "venv"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Repository = "https://github.com/offerrall/pycodecad"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.version]
|
|
50
|
+
path = "src/pycodecad/__init__.py"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/pycodecad"]
|
|
54
|
+
exclude = ["**/__pycache__", "**/.pycodecad"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
57
|
+
"examples" = "pycodecad/examples"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.sdist]
|
|
60
|
+
include = ["/src/pycodecad", "/tests", "/examples", "/README.md", "/CHANGELOG.md", "/pyproject.toml"]
|
|
61
|
+
exclude = ["**/__pycache__", "**/*.pyc"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Code-CAD with build123d: write a Python script, see the part, export it."""
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
__version__ = "1.0.0"
|
|
5
|
+
|
|
6
|
+
__all__ = ["show", "clear", "frame", "import_mesh", "expose"]
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .api import clear, expose, frame, import_mesh, show
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name: str):
|
|
13
|
+
# Lazy, so `import pycodecad` (and the CLI) start instantly.
|
|
14
|
+
if name in __all__:
|
|
15
|
+
from . import api
|
|
16
|
+
|
|
17
|
+
return getattr(api, name)
|
|
18
|
+
raise AttributeError(name)
|