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.
Files changed (53) hide show
  1. pycodecad-1.0.0/.gitignore +14 -0
  2. pycodecad-1.0.0/CHANGELOG.md +5 -0
  3. pycodecad-1.0.0/LICENSE +21 -0
  4. pycodecad-1.0.0/PKG-INFO +117 -0
  5. pycodecad-1.0.0/README.md +94 -0
  6. pycodecad-1.0.0/examples/assembly.py +7 -0
  7. pycodecad-1.0.0/examples/assets/logo.svg +1 -0
  8. pycodecad-1.0.0/examples/assets/pyramid.stl +44 -0
  9. pycodecad-1.0.0/examples/embedded_app.py +77 -0
  10. pycodecad-1.0.0/examples/gear.py +6 -0
  11. pycodecad-1.0.0/examples/gearbox.py +30 -0
  12. pycodecad-1.0.0/examples/gears_turning.py +11 -0
  13. pycodecad-1.0.0/examples/import_files.py +13 -0
  14. pycodecad-1.0.0/examples/parts.py +46 -0
  15. pycodecad-1.0.0/examples/tray.py +28 -0
  16. pycodecad-1.0.0/pyproject.toml +61 -0
  17. pycodecad-1.0.0/src/pycodecad/__init__.py +18 -0
  18. pycodecad-1.0.0/src/pycodecad/__main__.py +3 -0
  19. pycodecad-1.0.0/src/pycodecad/api.py +105 -0
  20. pycodecad-1.0.0/src/pycodecad/app.py +34 -0
  21. pycodecad-1.0.0/src/pycodecad/cad.py +261 -0
  22. pycodecad-1.0.0/src/pycodecad/camera.py +136 -0
  23. pycodecad-1.0.0/src/pycodecad/cli.py +241 -0
  24. pycodecad-1.0.0/src/pycodecad/context.py +178 -0
  25. pycodecad-1.0.0/src/pycodecad/editor.py +256 -0
  26. pycodecad-1.0.0/src/pycodecad/embed.py +16 -0
  27. pycodecad-1.0.0/src/pycodecad/files.py +262 -0
  28. pycodecad-1.0.0/src/pycodecad/icons/LICENSE +43 -0
  29. pycodecad-1.0.0/src/pycodecad/icons/__init__.py +31 -0
  30. pycodecad-1.0.0/src/pycodecad/icons/lucide.ttf +0 -0
  31. pycodecad-1.0.0/src/pycodecad/imgui_backend.py +269 -0
  32. pycodecad-1.0.0/src/pycodecad/params.py +177 -0
  33. pycodecad-1.0.0/src/pycodecad/renderer.py +327 -0
  34. pycodecad-1.0.0/src/pycodecad/runner.py +404 -0
  35. pycodecad-1.0.0/src/pycodecad/sidecar.py +86 -0
  36. pycodecad-1.0.0/src/pycodecad/textedit.py +290 -0
  37. pycodecad-1.0.0/src/pycodecad/ui.py +677 -0
  38. pycodecad-1.0.0/src/pycodecad/viewcube.py +120 -0
  39. pycodecad-1.0.0/src/pycodecad/viewer.py +70 -0
  40. pycodecad-1.0.0/src/pycodecad/window.py +129 -0
  41. pycodecad-1.0.0/src/pycodecad/workspace.py +545 -0
  42. pycodecad-1.0.0/tests/conftest.py +61 -0
  43. pycodecad-1.0.0/tests/test_camera.py +65 -0
  44. pycodecad-1.0.0/tests/test_cli.py +328 -0
  45. pycodecad-1.0.0/tests/test_embed.py +226 -0
  46. pycodecad-1.0.0/tests/test_export_consistency.py +48 -0
  47. pycodecad-1.0.0/tests/test_files.py +157 -0
  48. pycodecad-1.0.0/tests/test_frames.py +104 -0
  49. pycodecad-1.0.0/tests/test_params.py +386 -0
  50. pycodecad-1.0.0/tests/test_runner.py +329 -0
  51. pycodecad-1.0.0/tests/test_textedit.py +88 -0
  52. pycodecad-1.0.0/tests/test_viewcube.py +95 -0
  53. pycodecad-1.0.0/tests/test_workspace.py +719 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ venv/
5
+ .venv/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ *.stl
10
+ *.step
11
+ *.3mf
12
+ !examples/assets/*.stl
13
+ .pycodecad/
14
+ revision.md
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ First release.
@@ -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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/pycodecad)](https://pypi.org/project/pycodecad/)
27
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://pypi.org/project/pycodecad/)
28
+ [![Platform](https://img.shields.io/badge/platform-Linux%20native%20%7C%20Windows%20%26%20macOS%20compatible-lightgrey)](docs/design.md#platforms)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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
+ ![The pycodecad window: the files of the folder, parameters and code on the left, a gear in the 3D view on the right](docs/images/window.png)
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
+ [![PyPI](https://img.shields.io/pypi/v/pycodecad)](https://pypi.org/project/pycodecad/)
4
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://pypi.org/project/pycodecad/)
5
+ [![Platform](https://img.shields.io/badge/platform-Linux%20native%20%7C%20Windows%20%26%20macOS%20compatible-lightgrey)](docs/design.md#platforms)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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
+ ![The pycodecad window: the files of the folder, parameters and code on the left, a gear in the 3D view on the right](docs/images/window.png)
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,7 @@
1
+ """The gearbox of gearbox.py: parts from parts.py composed with normal imports."""
2
+ from pycodecad import show
3
+
4
+ from gearbox import gearbox
5
+
6
+ for name, parts, color in gearbox():
7
+ show(parts, name=name, color=color)
@@ -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,6 @@
1
+ """One loose part made to measure: expose() gives the window a slider per parameter of gear()."""
2
+ from pycodecad import expose, show
3
+
4
+ from parts import gear
5
+
6
+ show(expose(gear), name="gear", color="#F4B02A")
@@ -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)
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())