marimo-cad 0.1.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.
- marimo_cad-0.1.0/.github/workflows/publish.yml +56 -0
- marimo_cad-0.1.0/.gitignore +57 -0
- marimo_cad-0.1.0/LICENSE +21 -0
- marimo_cad-0.1.0/PKG-INFO +179 -0
- marimo_cad-0.1.0/README.md +146 -0
- marimo_cad-0.1.0/assets/demo.gif +0 -0
- marimo_cad-0.1.0/js/ARCHITECTURE.md +214 -0
- marimo_cad-0.1.0/js/package-lock.json +1506 -0
- marimo_cad-0.1.0/js/package.json +19 -0
- marimo_cad-0.1.0/js/src/cad-group-factory.js +196 -0
- marimo_cad-0.1.0/js/src/clipping-extension.js +206 -0
- marimo_cad-0.1.0/js/src/constants.js +95 -0
- marimo_cad-0.1.0/js/src/index.js +165 -0
- marimo_cad-0.1.0/js/src/live-viewer.js +339 -0
- marimo_cad-0.1.0/js/src/part-manager.js +365 -0
- marimo_cad-0.1.0/js/src/state-manager.js +164 -0
- marimo_cad-0.1.0/js/src/styles.css +18 -0
- marimo_cad-0.1.0/js/vite.config.js +26 -0
- marimo_cad-0.1.0/notebooks/bookshelf.py +104 -0
- marimo_cad-0.1.0/notebooks/sample_part.step +511 -0
- marimo_cad-0.1.0/notebooks/vase.py +224 -0
- marimo_cad-0.1.0/package-lock.json +79 -0
- marimo_cad-0.1.0/package.json +31 -0
- marimo_cad-0.1.0/playwright.config.js +36 -0
- marimo_cad-0.1.0/pyproject.toml +57 -0
- marimo_cad-0.1.0/scripts/record-demo.js +81 -0
- marimo_cad-0.1.0/src/marimo_cad/__init__.py +20 -0
- marimo_cad-0.1.0/src/marimo_cad/constants.py +68 -0
- marimo_cad-0.1.0/src/marimo_cad/export.py +107 -0
- marimo_cad-0.1.0/src/marimo_cad/static/widget.css +1 -0
- marimo_cad-0.1.0/src/marimo_cad/static/widget.js +50871 -0
- marimo_cad-0.1.0/src/marimo_cad/tessellate.py +311 -0
- marimo_cad-0.1.0/src/marimo_cad/utils.py +78 -0
- marimo_cad-0.1.0/src/marimo_cad/widget.py +175 -0
- marimo_cad-0.1.0/tests/__init__.py +1 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js +288 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/001-initial-render-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/002-dynamic-parts-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/003-selection-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/004-visibility-toggle-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/005-camera-preserved-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/006-clipping-initial-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/e2e/cad-viewer.spec.js-snapshots/007-clipping-dynamic-chromium-darwin.png +0 -0
- marimo_cad-0.1.0/tests/test_export.py +120 -0
- marimo_cad-0.1.0/tests/test_tessellate.py +192 -0
- marimo_cad-0.1.0/tests/test_widget.py +164 -0
- marimo_cad-0.1.0/uv.lock +2094 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Node.js
|
|
23
|
+
uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: "20"
|
|
26
|
+
|
|
27
|
+
- name: Build JS bundle
|
|
28
|
+
run: |
|
|
29
|
+
cd js
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: uv build
|
|
35
|
+
|
|
36
|
+
- name: Upload dist
|
|
37
|
+
uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
publish:
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment: pypi
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write
|
|
48
|
+
steps:
|
|
49
|
+
- name: Download dist
|
|
50
|
+
uses: actions/download-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
ENV/
|
|
25
|
+
|
|
26
|
+
# Node
|
|
27
|
+
node_modules/
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
|
|
32
|
+
# IDE
|
|
33
|
+
.idea/
|
|
34
|
+
.vscode/
|
|
35
|
+
*.swp
|
|
36
|
+
*.swo
|
|
37
|
+
*~
|
|
38
|
+
|
|
39
|
+
# OS
|
|
40
|
+
.DS_Store
|
|
41
|
+
Thumbs.db
|
|
42
|
+
|
|
43
|
+
# Project specific
|
|
44
|
+
src/marimo_cad/static/widget.js
|
|
45
|
+
src/marimo_cad/static/widget.css
|
|
46
|
+
|
|
47
|
+
# Playwright
|
|
48
|
+
test-results/
|
|
49
|
+
playwright-report/
|
|
50
|
+
blob-report/
|
|
51
|
+
.playwright/
|
|
52
|
+
|
|
53
|
+
# Marimo cache
|
|
54
|
+
**/__marimo__/
|
|
55
|
+
|
|
56
|
+
# Keep sample files but ignore generated exports
|
|
57
|
+
!notebooks/sample_part.step
|
marimo_cad-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cemrehan Cavdar
|
|
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,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: marimo-cad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reactive Parametric CAD for marimo notebooks
|
|
5
|
+
Project-URL: Homepage, https://github.com/cemrehancavdar/marimo-cad
|
|
6
|
+
Project-URL: Repository, https://github.com/cemrehancavdar/marimo-cad
|
|
7
|
+
Project-URL: Issues, https://github.com/cemrehancavdar/marimo-cad/issues
|
|
8
|
+
Author-email: Cemrehan Cavdar <cemrehancavdar@hotmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: 3d,anywidget,build123d,cad,marimo,parametric
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: anywidget>=0.9.0
|
|
24
|
+
Requires-Dist: build123d>=0.10.0
|
|
25
|
+
Requires-Dist: marimo>=0.9.0
|
|
26
|
+
Requires-Dist: ocp-tessellate>=3.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
+
Provides-Extra: gears
|
|
31
|
+
Requires-Dist: bd-warehouse>=0.1.0; extra == 'gears'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# marimo-cad
|
|
35
|
+
|
|
36
|
+
**Reactive Parametric CAD** for [marimo](https://marimo.io) notebooks.
|
|
37
|
+
|
|
38
|
+
Build interactive 3D CAD models with sliders that update in real-time without losing your camera position.
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
## Why marimo-cad?
|
|
43
|
+
|
|
44
|
+
| Use Case | Native build123d | marimo-cad |
|
|
45
|
+
|----------|------------------|------------|
|
|
46
|
+
| Quick visualization | Just return the object | Overkill |
|
|
47
|
+
| **Parametric design with sliders** | Camera resets on every change | Camera preserved |
|
|
48
|
+
| Named multi-part assemblies | No | Yes, with tree view |
|
|
49
|
+
| Export (STL/STEP/GLTF) | Manual | Built-in |
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv add marimo-cad
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import marimo as mo
|
|
61
|
+
from build123d import Box
|
|
62
|
+
import marimo_cad as cad
|
|
63
|
+
|
|
64
|
+
size = mo.ui.slider(10, 50, value=20, label="Size")
|
|
65
|
+
viewer = cad.Viewer()
|
|
66
|
+
|
|
67
|
+
box = Box(size.value, size.value, size.value)
|
|
68
|
+
viewer.render(box)
|
|
69
|
+
|
|
70
|
+
mo.vstack([size, viewer])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Examples
|
|
74
|
+
|
|
75
|
+
See [notebooks/](notebooks/) for complete examples:
|
|
76
|
+
- `bookshelf.py` - Parametric bookshelf
|
|
77
|
+
- `vase.py` - Parametric vase with STL export
|
|
78
|
+
|
|
79
|
+
## Viewer Features
|
|
80
|
+
|
|
81
|
+
- **Mouse**: Rotate (drag), pan (right-drag), zoom (scroll)
|
|
82
|
+
- **Tree view**: Toggle part visibility
|
|
83
|
+
- **Clipping planes**: Slice along X/Y/Z
|
|
84
|
+
- **Measurement**: Distance and angles
|
|
85
|
+
|
|
86
|
+
## Limitations
|
|
87
|
+
|
|
88
|
+
- Large models (>100k triangles) may be slow to tessellate
|
|
89
|
+
- Selection events not yet exposed to Python
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uv sync && cd js && npm install && npm run build && cd ..
|
|
95
|
+
uv run pytest tests/ # 34 tests
|
|
96
|
+
uv run ruff check src/ --fix # Lint
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
### Viewer
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
viewer = cad.Viewer(width="100%", height=600)
|
|
107
|
+
viewer.render(shapes)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Parameter | Type | Default | Description |
|
|
111
|
+
|-----------|------|---------|-------------|
|
|
112
|
+
| `width` | `str \| int` | `"100%"` | CSS width or pixels |
|
|
113
|
+
| `height` | `int` | `600` | Height in pixels |
|
|
114
|
+
|
|
115
|
+
**render(shapes)** - Render shapes, preserving camera position.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Single shape
|
|
119
|
+
viewer.render(box)
|
|
120
|
+
|
|
121
|
+
# Multiple shapes
|
|
122
|
+
viewer.render([box, cylinder])
|
|
123
|
+
|
|
124
|
+
# Named parts with colors
|
|
125
|
+
viewer.render([
|
|
126
|
+
{"shape": base, "name": "Base", "color": "blue"},
|
|
127
|
+
{"shape": top, "name": "Top", "color": "red", "alpha": 0.8},
|
|
128
|
+
])
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### PartSpec
|
|
132
|
+
|
|
133
|
+
Dict for specifying parts with metadata:
|
|
134
|
+
|
|
135
|
+
| Key | Type | Required | Description |
|
|
136
|
+
|-----|------|----------|-------------|
|
|
137
|
+
| `shape` | `Shape` | Yes | build123d object |
|
|
138
|
+
| `name` | `str` | No | Display name |
|
|
139
|
+
| `color` | `str` | No | Color name or hex |
|
|
140
|
+
| `alpha` | `float` | No | Opacity 0.0-1.0 |
|
|
141
|
+
|
|
142
|
+
### COLORS
|
|
143
|
+
|
|
144
|
+
Available named colors: `blue`, `red`, `green`, `yellow`, `orange`, `purple`, `cyan`, `pink`, `gray`, `white`, `black`
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
{"shape": box, "color": "blue"} # Named
|
|
148
|
+
{"shape": box, "color": "#ff6600"} # Hex
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Export Functions
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from marimo_cad import export_stl, export_step, export_gltf
|
|
155
|
+
|
|
156
|
+
export_stl(obj, "part.stl") # 3D printing
|
|
157
|
+
export_stl(obj, "fine.stl", tolerance=0.0001) # Higher resolution
|
|
158
|
+
export_step(obj, "part.step") # CAD interchange (lossless)
|
|
159
|
+
export_gltf(obj, "part.glb") # Web viewers
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Function | Parameters | Description |
|
|
163
|
+
|----------|------------|-------------|
|
|
164
|
+
| `export_stl` | `obj, filename, tolerance=0.001, angular_tolerance=0.1` | Tessellated mesh |
|
|
165
|
+
| `export_step` | `obj, filename` | Exact geometry |
|
|
166
|
+
| `export_gltf` | `obj, filename` | Web-ready format |
|
|
167
|
+
|
|
168
|
+
All return `Path` to exported file.
|
|
169
|
+
|
|
170
|
+
### How It Works
|
|
171
|
+
|
|
172
|
+
1. **Tessellate**: build123d → triangle meshes (ocp-tessellate)
|
|
173
|
+
2. **Transport**: Mesh data → JavaScript (anywidget)
|
|
174
|
+
3. **Render**: Three.js via three-cad-viewer
|
|
175
|
+
4. **Update**: `render()` updates geometry, camera preserved
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# marimo-cad
|
|
2
|
+
|
|
3
|
+
**Reactive Parametric CAD** for [marimo](https://marimo.io) notebooks.
|
|
4
|
+
|
|
5
|
+
Build interactive 3D CAD models with sliders that update in real-time without losing your camera position.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Why marimo-cad?
|
|
10
|
+
|
|
11
|
+
| Use Case | Native build123d | marimo-cad |
|
|
12
|
+
|----------|------------------|------------|
|
|
13
|
+
| Quick visualization | Just return the object | Overkill |
|
|
14
|
+
| **Parametric design with sliders** | Camera resets on every change | Camera preserved |
|
|
15
|
+
| Named multi-part assemblies | No | Yes, with tree view |
|
|
16
|
+
| Export (STL/STEP/GLTF) | Manual | Built-in |
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv add marimo-cad
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import marimo as mo
|
|
28
|
+
from build123d import Box
|
|
29
|
+
import marimo_cad as cad
|
|
30
|
+
|
|
31
|
+
size = mo.ui.slider(10, 50, value=20, label="Size")
|
|
32
|
+
viewer = cad.Viewer()
|
|
33
|
+
|
|
34
|
+
box = Box(size.value, size.value, size.value)
|
|
35
|
+
viewer.render(box)
|
|
36
|
+
|
|
37
|
+
mo.vstack([size, viewer])
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
See [notebooks/](notebooks/) for complete examples:
|
|
43
|
+
- `bookshelf.py` - Parametric bookshelf
|
|
44
|
+
- `vase.py` - Parametric vase with STL export
|
|
45
|
+
|
|
46
|
+
## Viewer Features
|
|
47
|
+
|
|
48
|
+
- **Mouse**: Rotate (drag), pan (right-drag), zoom (scroll)
|
|
49
|
+
- **Tree view**: Toggle part visibility
|
|
50
|
+
- **Clipping planes**: Slice along X/Y/Z
|
|
51
|
+
- **Measurement**: Distance and angles
|
|
52
|
+
|
|
53
|
+
## Limitations
|
|
54
|
+
|
|
55
|
+
- Large models (>100k triangles) may be slow to tessellate
|
|
56
|
+
- Selection events not yet exposed to Python
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv sync && cd js && npm install && npm run build && cd ..
|
|
62
|
+
uv run pytest tests/ # 34 tests
|
|
63
|
+
uv run ruff check src/ --fix # Lint
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### Viewer
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
viewer = cad.Viewer(width="100%", height=600)
|
|
74
|
+
viewer.render(shapes)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Parameter | Type | Default | Description |
|
|
78
|
+
|-----------|------|---------|-------------|
|
|
79
|
+
| `width` | `str \| int` | `"100%"` | CSS width or pixels |
|
|
80
|
+
| `height` | `int` | `600` | Height in pixels |
|
|
81
|
+
|
|
82
|
+
**render(shapes)** - Render shapes, preserving camera position.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Single shape
|
|
86
|
+
viewer.render(box)
|
|
87
|
+
|
|
88
|
+
# Multiple shapes
|
|
89
|
+
viewer.render([box, cylinder])
|
|
90
|
+
|
|
91
|
+
# Named parts with colors
|
|
92
|
+
viewer.render([
|
|
93
|
+
{"shape": base, "name": "Base", "color": "blue"},
|
|
94
|
+
{"shape": top, "name": "Top", "color": "red", "alpha": 0.8},
|
|
95
|
+
])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### PartSpec
|
|
99
|
+
|
|
100
|
+
Dict for specifying parts with metadata:
|
|
101
|
+
|
|
102
|
+
| Key | Type | Required | Description |
|
|
103
|
+
|-----|------|----------|-------------|
|
|
104
|
+
| `shape` | `Shape` | Yes | build123d object |
|
|
105
|
+
| `name` | `str` | No | Display name |
|
|
106
|
+
| `color` | `str` | No | Color name or hex |
|
|
107
|
+
| `alpha` | `float` | No | Opacity 0.0-1.0 |
|
|
108
|
+
|
|
109
|
+
### COLORS
|
|
110
|
+
|
|
111
|
+
Available named colors: `blue`, `red`, `green`, `yellow`, `orange`, `purple`, `cyan`, `pink`, `gray`, `white`, `black`
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
{"shape": box, "color": "blue"} # Named
|
|
115
|
+
{"shape": box, "color": "#ff6600"} # Hex
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Export Functions
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from marimo_cad import export_stl, export_step, export_gltf
|
|
122
|
+
|
|
123
|
+
export_stl(obj, "part.stl") # 3D printing
|
|
124
|
+
export_stl(obj, "fine.stl", tolerance=0.0001) # Higher resolution
|
|
125
|
+
export_step(obj, "part.step") # CAD interchange (lossless)
|
|
126
|
+
export_gltf(obj, "part.glb") # Web viewers
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Function | Parameters | Description |
|
|
130
|
+
|----------|------------|-------------|
|
|
131
|
+
| `export_stl` | `obj, filename, tolerance=0.001, angular_tolerance=0.1` | Tessellated mesh |
|
|
132
|
+
| `export_step` | `obj, filename` | Exact geometry |
|
|
133
|
+
| `export_gltf` | `obj, filename` | Web-ready format |
|
|
134
|
+
|
|
135
|
+
All return `Path` to exported file.
|
|
136
|
+
|
|
137
|
+
### How It Works
|
|
138
|
+
|
|
139
|
+
1. **Tessellate**: build123d → triangle meshes (ocp-tessellate)
|
|
140
|
+
2. **Transport**: Mesh data → JavaScript (anywidget)
|
|
141
|
+
3. **Render**: Three.js via three-cad-viewer
|
|
142
|
+
4. **Update**: `render()` updates geometry, camera preserved
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
Binary file
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# JS Architecture: three-cad-viewer Wrapper
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document explains the JavaScript wrapper layer we built over `three-cad-viewer` to enable reactive CAD visualization in marimo notebooks.
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
`three-cad-viewer` is a powerful CAD viewer, but it has several quirks that make reactive updates difficult:
|
|
10
|
+
|
|
11
|
+
1. **Full re-render resets camera** - Calling `viewer.render()` rebuilds everything and resets camera position
|
|
12
|
+
2. **Internal API confusion** - `ObjectGroup` extends `THREE.Group` directly, so position/quaternion are on the group itself (not `group.group`)
|
|
13
|
+
3. **Path-based group keys** - Parts are stored with paths like `/shapes/assembly/Left` not just `Left`
|
|
14
|
+
4. **Tree view coupling** - The tree UI is built during render and doesn't update when parts change
|
|
15
|
+
5. **No exported TreeView** - The `TreeView` class is internal, making tree updates tricky
|
|
16
|
+
|
|
17
|
+
## Solution Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
21
|
+
│ index.js (Widget) │
|
|
22
|
+
│ - Creates Display and LiveViewer │
|
|
23
|
+
│ - Handles model.on('change:shapes_data') events │
|
|
24
|
+
│ - Calls viewer.syncParts() for updates │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
│
|
|
27
|
+
▼
|
|
28
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ LiveViewer (extends Viewer) │
|
|
30
|
+
│ - render() - initial render, builds PartManager │
|
|
31
|
+
│ - syncParts() - smart update (add/remove/update parts) │
|
|
32
|
+
│ - _rebuildTreeView() - updates tree when parts change │
|
|
33
|
+
│ - Exposes parts: PartManager for direct manipulation │
|
|
34
|
+
└─────────────────────────────────────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
38
|
+
│ PartManager │
|
|
39
|
+
│ - get(id) → PartHandle │
|
|
40
|
+
│ - add(partData) → PartHandle │
|
|
41
|
+
│ - remove(id) │
|
|
42
|
+
│ - update(id, partData) │
|
|
43
|
+
│ - sync(partsArray) → { added, updated, removed } │
|
|
44
|
+
│ - buildFromViewer() - maps viewer's groups to handles │
|
|
45
|
+
└─────────────────────────────────────────────────────────────┘
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
49
|
+
│ PartHandle │
|
|
50
|
+
│ - setPosition(x, y, z) │
|
|
51
|
+
│ - setQuaternion(x, y, z, w) │
|
|
52
|
+
│ - setTransform(loc) - from [[pos], [quat]] format │
|
|
53
|
+
│ - updateGeometry(vertices, normals, triangles) │
|
|
54
|
+
│ - updateEdges(edgeData) │
|
|
55
|
+
│ - dispose() │
|
|
56
|
+
└─────────────────────────────────────────────────────────────┘
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ three-cad-viewer (external library) │
|
|
61
|
+
│ - Viewer: base viewer class │
|
|
62
|
+
│ - Display: UI container (tree, toolbar, canvas) │
|
|
63
|
+
│ - THREE.js scene management │
|
|
64
|
+
└─────────────────────────────────────────────────────────────┘
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Key Files
|
|
68
|
+
|
|
69
|
+
### `js/src/part-manager.js`
|
|
70
|
+
|
|
71
|
+
**PartHandle** - Clean interface to a single part:
|
|
72
|
+
- Abstracts away three-cad-viewer's ObjectGroup quirks
|
|
73
|
+
- Provides simple `setPosition()`, `updateGeometry()` methods
|
|
74
|
+
- Handles buffer reuse for efficient geometry updates
|
|
75
|
+
|
|
76
|
+
**PartManager** - Manages all parts:
|
|
77
|
+
- Tracks parts by ID with a Map
|
|
78
|
+
- `sync()` method does intelligent diffing: only adds new, removes old, updates existing
|
|
79
|
+
- `buildFromViewer()` maps three-cad-viewer's internal groups to PartHandles
|
|
80
|
+
- Handles path-based key lookup (finds `Left` in `/shapes/assembly/Left`)
|
|
81
|
+
|
|
82
|
+
### `js/src/live-viewer.js`
|
|
83
|
+
|
|
84
|
+
**LiveViewer** - Extends `three-cad-viewer.Viewer`:
|
|
85
|
+
- Overrides `render()` to initialize PartManager after scene builds
|
|
86
|
+
- `syncParts()` - main update method, preserves camera
|
|
87
|
+
- `_rebuildTreeView()` - reconstructs tree UI when parts change
|
|
88
|
+
- `_buildTreeFromParts()` - creates tree data structure from parts array
|
|
89
|
+
|
|
90
|
+
### `js/src/index.js`
|
|
91
|
+
|
|
92
|
+
**Widget entry point**:
|
|
93
|
+
- Creates Display and LiveViewer
|
|
94
|
+
- Listens for `change:shapes_data` traitlet events
|
|
95
|
+
- First render uses `viewer.render()`, subsequent updates use `viewer.syncParts()`
|
|
96
|
+
|
|
97
|
+
## How It Works
|
|
98
|
+
|
|
99
|
+
### Initial Render
|
|
100
|
+
```javascript
|
|
101
|
+
// First time - full render
|
|
102
|
+
viewer.render(shapesData, renderOptions, viewerOptions);
|
|
103
|
+
// This:
|
|
104
|
+
// 1. Builds THREE.js scene with all parts
|
|
105
|
+
// 2. Creates tree view
|
|
106
|
+
// 3. Sets up camera, lights, controls
|
|
107
|
+
// 4. PartManager.buildFromViewer() maps groups to handles
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Subsequent Updates
|
|
111
|
+
```javascript
|
|
112
|
+
// When slider changes - smart sync
|
|
113
|
+
viewer.syncParts(newShapesData);
|
|
114
|
+
// This:
|
|
115
|
+
// 1. PartManager.sync() diffs old vs new parts
|
|
116
|
+
// 2. Removes parts not in new data
|
|
117
|
+
// 3. Updates existing parts (geometry + transform)
|
|
118
|
+
// 4. Adds new parts
|
|
119
|
+
// 5. If parts added/removed, rebuilds tree view
|
|
120
|
+
// 6. Camera position preserved!
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Tree View Rebuild
|
|
124
|
+
```javascript
|
|
125
|
+
// When parts added or removed
|
|
126
|
+
_rebuildTreeView(partsData) {
|
|
127
|
+
// 1. Build tree structure: { shapes: { "PartName": [1,1], ... } }
|
|
128
|
+
const newTree = this._buildTreeFromParts(partsData);
|
|
129
|
+
|
|
130
|
+
// 2. Update viewer's internal tree properties
|
|
131
|
+
this.tree = this.expandedTree = this.compactTree = newTree;
|
|
132
|
+
|
|
133
|
+
// 3. Get TreeView class from existing instance (not exported)
|
|
134
|
+
const TreeViewClass = this.treeview.constructor;
|
|
135
|
+
|
|
136
|
+
// 4. Dispose old, create new
|
|
137
|
+
this.treeview.dispose();
|
|
138
|
+
this.treeview = new TreeViewClass(this.tree, ...callbacks);
|
|
139
|
+
|
|
140
|
+
// 5. Render new tree
|
|
141
|
+
this.display.clearCadTree();
|
|
142
|
+
this.display.addCadTree(this.treeview.create());
|
|
143
|
+
this.treeview.render();
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## three-cad-viewer Quirks Handled
|
|
148
|
+
|
|
149
|
+
### ObjectGroup is a THREE.Group
|
|
150
|
+
```javascript
|
|
151
|
+
// WRONG - group.group is undefined
|
|
152
|
+
group.group.position.set(x, y, z);
|
|
153
|
+
|
|
154
|
+
// CORRECT - ObjectGroup extends THREE.Group
|
|
155
|
+
group.position.set(x, y, z);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Path-based Keys
|
|
159
|
+
```javascript
|
|
160
|
+
// Groups are stored with paths
|
|
161
|
+
const groups = viewer.nestedGroup.groups;
|
|
162
|
+
// Keys: "/shapes/assembly/Left", "/shapes/assembly/Right", etc.
|
|
163
|
+
|
|
164
|
+
// PartManager handles lookup
|
|
165
|
+
_findViewerGroup(id) {
|
|
166
|
+
for (const [path, group] of Object.entries(groups)) {
|
|
167
|
+
if (path === id || path.endsWith('/' + id)) {
|
|
168
|
+
return { path, group };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Tree Structure Format
|
|
175
|
+
```javascript
|
|
176
|
+
// three-cad-viewer expects this format:
|
|
177
|
+
{
|
|
178
|
+
"shapes": {
|
|
179
|
+
"Left Side": [1, 1], // [shape_visible, edges_visible]
|
|
180
|
+
"Right Side": [1, 1],
|
|
181
|
+
"Top": [1, 1],
|
|
182
|
+
// ...
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Usage Example
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// In marimo widget
|
|
191
|
+
const viewer = new LiveViewer(display, options, callback);
|
|
192
|
+
|
|
193
|
+
// Initial render
|
|
194
|
+
viewer.render(shapesData, renderOptions, viewerOptions);
|
|
195
|
+
|
|
196
|
+
// When data changes (slider moved)
|
|
197
|
+
model.on("change:shapes_data", () => {
|
|
198
|
+
const newData = model.get("shapes_data");
|
|
199
|
+
viewer.syncParts(newData); // Camera preserved, tree updated
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Direct part manipulation (if needed)
|
|
203
|
+
const part = viewer.getPart("Left Side");
|
|
204
|
+
part.setPosition(10, 0, 0);
|
|
205
|
+
viewer.update(true);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Benefits
|
|
209
|
+
|
|
210
|
+
1. **Clean API** - No need to understand three-cad-viewer internals
|
|
211
|
+
2. **Reactive updates** - `syncParts()` handles add/remove/update intelligently
|
|
212
|
+
3. **Camera preserved** - No jarring resets when parameters change
|
|
213
|
+
4. **Tree updates** - Tree view reflects current parts
|
|
214
|
+
5. **Efficient** - Reuses geometry buffers when sizes match
|