scad123d 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.
- scad123d-0.1.0/.claude/launch.json +11 -0
- scad123d-0.1.0/.github/workflows/ci.yml +87 -0
- scad123d-0.1.0/.gitignore +13 -0
- scad123d-0.1.0/.python-version +1 -0
- scad123d-0.1.0/CONVERSATION.md +491 -0
- scad123d-0.1.0/LICENSE +21 -0
- scad123d-0.1.0/PKG-INFO +318 -0
- scad123d-0.1.0/README.md +295 -0
- scad123d-0.1.0/ROADMAP.md +178 -0
- scad123d-0.1.0/docs/REFERENCE.md +357 -0
- scad123d-0.1.0/docs/generate_images.py +190 -0
- scad123d-0.1.0/docs/images/bosl2_tube_filleted.png +0 -0
- scad123d-0.1.0/docs/images/hull_analytic.png +0 -0
- scad123d-0.1.0/docs/images/hull_fallback.png +0 -0
- scad123d-0.1.0/docs/images/mcad_gear.png +0 -0
- scad123d-0.1.0/docs/images/minkowski_after.png +0 -0
- scad123d-0.1.0/docs/images/minkowski_before.png +0 -0
- scad123d-0.1.0/docs/images/stl_vs_step_step.png +0 -0
- scad123d-0.1.0/docs/images/stl_vs_step_stl.png +0 -0
- scad123d-0.1.0/docs/models/.gitignore +4 -0
- scad123d-0.1.0/docs/models/bosl2_tube.bin +0 -0
- scad123d-0.1.0/docs/models/bosl2_tube.edges.json +1 -0
- scad123d-0.1.0/docs/models/bosl2_tube.glb +1 -0
- scad123d-0.1.0/docs/models/boss_step.bin +0 -0
- scad123d-0.1.0/docs/models/boss_step.edges.json +1 -0
- scad123d-0.1.0/docs/models/boss_step.glb +1 -0
- scad123d-0.1.0/docs/models/hull_fallback.bin +0 -0
- scad123d-0.1.0/docs/models/hull_fallback.glb +1 -0
- scad123d-0.1.0/docs/models/hull_ok.bin +0 -0
- scad123d-0.1.0/docs/models/hull_ok.glb +1 -0
- scad123d-0.1.0/docs/models/mcad_gear.bin +0 -0
- scad123d-0.1.0/docs/models/mcad_gear.glb +1 -0
- scad123d-0.1.0/docs/models/minkowski_after.bin +0 -0
- scad123d-0.1.0/docs/models/minkowski_after.glb +1 -0
- scad123d-0.1.0/docs/models/minkowski_before.bin +0 -0
- scad123d-0.1.0/docs/models/minkowski_before.glb +1 -0
- scad123d-0.1.0/docs/scad/boss.scad +4 -0
- scad123d-0.1.0/docs/scad/hull_corners.scad +10 -0
- scad123d-0.1.0/docs/scad/hull_fallback.scad +5 -0
- scad123d-0.1.0/docs/scad/mcad_gear.scad +2 -0
- scad123d-0.1.0/docs/scad/round_box.scad +4 -0
- scad123d-0.1.0/docs/scad/tube_example.scad +3 -0
- scad123d-0.1.0/docs/viewer.html +220 -0
- scad123d-0.1.0/justfile +17 -0
- scad123d-0.1.0/pyproject.toml +51 -0
- scad123d-0.1.0/src/scad123d/__init__.py +128 -0
- scad123d-0.1.0/src/scad123d/build.py +256 -0
- scad123d-0.1.0/src/scad123d/cli.py +134 -0
- scad123d-0.1.0/src/scad123d/csg.lark +37 -0
- scad123d-0.1.0/src/scad123d/emit.py +36 -0
- scad123d-0.1.0/src/scad123d/errors.py +31 -0
- scad123d-0.1.0/src/scad123d/facets.py +74 -0
- scad123d-0.1.0/src/scad123d/hull.py +336 -0
- scad123d-0.1.0/src/scad123d/mesh.py +49 -0
- scad123d-0.1.0/src/scad123d/minkowski.py +126 -0
- scad123d-0.1.0/src/scad123d/module_import.py +374 -0
- scad123d-0.1.0/src/scad123d/nodes.py +55 -0
- scad123d-0.1.0/src/scad123d/openscad.py +207 -0
- scad123d-0.1.0/src/scad123d/parser.py +95 -0
- scad123d-0.1.0/src/scad123d/scad_declarations.py +451 -0
- scad123d-0.1.0/src/scad123d/solids.py +202 -0
- scad123d-0.1.0/tests/__init__.py +0 -0
- scad123d-0.1.0/tests/conftest.py +154 -0
- scad123d-0.1.0/tests/fixtures/booleans.csg +17 -0
- scad123d-0.1.0/tests/fixtures/extrusions.csg +28 -0
- scad123d-0.1.0/tests/fixtures/facets.csg +16 -0
- scad123d-0.1.0/tests/fixtures/hull_capsule.csg +9 -0
- scad123d-0.1.0/tests/fixtures/hull_cylinders.csg +15 -0
- scad123d-0.1.0/tests/fixtures/hull_spheres.csg +27 -0
- scad123d-0.1.0/tests/fixtures/metrics.json +185 -0
- scad123d-0.1.0/tests/fixtures/minkowski_polyhedron.csg +5 -0
- scad123d-0.1.0/tests/fixtures/minkowski_sphere.csg +14 -0
- scad123d-0.1.0/tests/fixtures/modules/module_lib.scad +8 -0
- scad123d-0.1.0/tests/fixtures/params.csg +15 -0
- scad123d-0.1.0/tests/fixtures/polyhedron.csg +5 -0
- scad123d-0.1.0/tests/fixtures/primitives.csg +17 -0
- scad123d-0.1.0/tests/fixtures/scad/booleans.scad +12 -0
- scad123d-0.1.0/tests/fixtures/scad/extrusions.scad +5 -0
- scad123d-0.1.0/tests/fixtures/scad/facets.scad +7 -0
- scad123d-0.1.0/tests/fixtures/scad/hull_capsule.scad +5 -0
- scad123d-0.1.0/tests/fixtures/scad/hull_cylinders.scad +9 -0
- scad123d-0.1.0/tests/fixtures/scad/hull_spheres.scad +12 -0
- scad123d-0.1.0/tests/fixtures/scad/minkowski_polyhedron.scad +396 -0
- scad123d-0.1.0/tests/fixtures/scad/minkowski_sphere.scad +6 -0
- scad123d-0.1.0/tests/fixtures/scad/params.scad +10 -0
- scad123d-0.1.0/tests/fixtures/scad/polyhedron.scad +8 -0
- scad123d-0.1.0/tests/fixtures/scad/primitives.scad +6 -0
- scad123d-0.1.0/tests/fixtures/scad/transforms.scad +7 -0
- scad123d-0.1.0/tests/fixtures/scad/twod.scad +10 -0
- scad123d-0.1.0/tests/fixtures/transforms.csg +29 -0
- scad123d-0.1.0/tests/fixtures/twod.csg +14 -0
- scad123d-0.1.0/tests/regen_fixtures.py +49 -0
- scad123d-0.1.0/tests/test_build.py +300 -0
- scad123d-0.1.0/tests/test_cli.py +110 -0
- scad123d-0.1.0/tests/test_differential.py +280 -0
- scad123d-0.1.0/tests/test_module_import.py +310 -0
- scad123d-0.1.0/tests/test_openscad_discovery.py +200 -0
- scad123d-0.1.0/tests/test_parser.py +98 -0
- scad123d-0.1.0/tests/test_scad_declarations.py +227 -0
- scad123d-0.1.0/uv.lock +1653 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
# Tier 1: parses committed .csg fixtures and checks geometry against
|
|
10
|
+
# committed reference metrics. Needs no OpenSCAD binary, so it runs anywhere.
|
|
11
|
+
fixtures:
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
+
python: ["3.10", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python }}
|
|
23
|
+
- run: uv sync --all-extras --dev
|
|
24
|
+
- run: uv run pytest -q -m "not needs_openscad"
|
|
25
|
+
|
|
26
|
+
# Tier 2: differential comparison against OpenSCAD itself. Ubuntu's packaged
|
|
27
|
+
# OpenSCAD is typically older than the version the fixtures were generated
|
|
28
|
+
# with; the handful of tests that need byte/tessellation-exact agreement
|
|
29
|
+
# (not the usual convergence-based comparison) detect that mismatch via
|
|
30
|
+
# require_fixture_openscad_version() and skip themselves rather than fail,
|
|
31
|
+
# so this job's pass/fail is real signal -- no continue-on-error needed.
|
|
32
|
+
differential:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.13"
|
|
39
|
+
- name: Install OpenSCAD (AppImage)
|
|
40
|
+
# apt-get install openscad pulls OpenSCAD's whole dependency chain
|
|
41
|
+
# (Qt, X11/GL, CGAL, Boost -- dozens of packages) from Ubuntu's
|
|
42
|
+
# external mirrors on every single run; that's what a previous run of
|
|
43
|
+
# this job once hung on for 15+ minutes. The AppImage is a single,
|
|
44
|
+
# self-contained file with no dependency resolution.
|
|
45
|
+
#
|
|
46
|
+
# It's pinned to a specific snapshot for reproducibility, but
|
|
47
|
+
# files.openscad.org/snapshots/ only retains a rolling window of
|
|
48
|
+
# recent builds, so this pin WILL eventually 404 as it rotates out.
|
|
49
|
+
# If so: pick a current filename from
|
|
50
|
+
# https://files.openscad.org/snapshots/ and update SNAPSHOT below.
|
|
51
|
+
# The fallback keeps CI working meanwhile by grabbing whatever's
|
|
52
|
+
# newest, at the cost of a floating (not reproducible) version.
|
|
53
|
+
run: |
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
# The AppImage's own bundled binary is dynamically linked against
|
|
56
|
+
# libEGL/libOpenGL even for headless CSG export -- it fails to even
|
|
57
|
+
# start without them (confirmed via `ldd`, no other libraries
|
|
58
|
+
# missing). Two small packages, not OpenSCAD's whole dependency
|
|
59
|
+
# chain -- Qt/CGAL/Boost/the app itself all still come bundled
|
|
60
|
+
# inside the AppImage, unaffected.
|
|
61
|
+
sudo apt-get update -qq
|
|
62
|
+
sudo apt-get install -y -qq libegl1 libopengl0
|
|
63
|
+
SNAPSHOT="OpenSCAD-2026.08.13-x86_64.AppImage"
|
|
64
|
+
URL="https://files.openscad.org/snapshots/$SNAPSHOT"
|
|
65
|
+
if ! curl -fsSL -o openscad.AppImage "$URL"; then
|
|
66
|
+
echo "::warning::$SNAPSHOT is gone from files.openscad.org/snapshots/ (rotated out) -- falling back to the latest available snapshot. Update SNAPSHOT in this workflow step."
|
|
67
|
+
LATEST=$(curl -fsSL https://files.openscad.org/snapshots/ | grep -oE 'href="[^"]*x86_64\.AppImage"' | sed 's/href="//;s/"//' | sort | tail -1)
|
|
68
|
+
curl -fsSL -o openscad.AppImage "https://files.openscad.org/snapshots/$LATEST"
|
|
69
|
+
fi
|
|
70
|
+
chmod +x openscad.AppImage
|
|
71
|
+
# Extract once here rather than run the AppImage directly: mounting
|
|
72
|
+
# an AppImage needs FUSE, which GitHub-hosted runners don't
|
|
73
|
+
# reliably have. Extraction needs no FUSE, and leaves a plain
|
|
74
|
+
# directory scad123d's own subprocess calls can invoke normally.
|
|
75
|
+
./openscad.AppImage --appimage-extract || true
|
|
76
|
+
echo "--- missing shared libraries (diagnostic) ---"
|
|
77
|
+
if [ -f squashfs-root/usr/bin/openscad ]; then
|
|
78
|
+
ldd squashfs-root/usr/bin/openscad | grep "not found" || echo "(none missing)"
|
|
79
|
+
else
|
|
80
|
+
echo "extraction did not produce squashfs-root/usr/bin/openscad"
|
|
81
|
+
find squashfs-root -maxdepth 3 || true
|
|
82
|
+
fi
|
|
83
|
+
sudo mv squashfs-root /opt/openscad
|
|
84
|
+
sudo ln -s /opt/openscad/AppRun /usr/local/bin/openscad
|
|
85
|
+
openscad --version
|
|
86
|
+
- run: uv sync --all-extras --dev
|
|
87
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# scad123d — Conversation Log
|
|
2
|
+
|
|
3
|
+
## 2026-08-18 — Initial brief
|
|
4
|
+
|
|
5
|
+
### User
|
|
6
|
+
|
|
7
|
+
> You might look at ~/Projects/solid123d. That project allowed an import-only
|
|
8
|
+
> substitution to use SolidPython code to generate Build123d geometry. I'd like
|
|
9
|
+
> to make `scad123d` to allow us to import OpenSCAD code and generate Build123D
|
|
10
|
+
> geometry from it. I don't think we need to allow users to write OpenSCAD code
|
|
11
|
+
> in a Python file but we'd like to import openSCAD code and use it in a Python
|
|
12
|
+
> file. So it could be that this module would contain only one importable
|
|
13
|
+
> function, `scad123d.import_scad(path:pathlib.Path) -> build123d.Shape:` Or
|
|
14
|
+
> maybe there are others? I think the thing to begin with is probably a
|
|
15
|
+
> pyparsing (or other? there are a lot of parser builders in Python) parser for
|
|
16
|
+
> OpenSCAD, and then a translation from that AST to Build123d primitives. It
|
|
17
|
+
> might be easiest to generate SolidPython code using the OpenSCAD AST and then
|
|
18
|
+
> using solid123d to convert that, or it might be good to go straight to
|
|
19
|
+
> Build123d. Is that a clear brief, and do you have any questions?
|
|
20
|
+
|
|
21
|
+
### Assistant
|
|
22
|
+
|
|
23
|
+
Read the prior art: `/Users/jonese/Dropbox/Projects/3D_geometry/scad_2_build123d`
|
|
24
|
+
(package name `solid123d`, on PyPI). ~1000 lines, clean separation into
|
|
25
|
+
primitives/transforms/booleans/extrusions/render.
|
|
26
|
+
|
|
27
|
+
Brief is clear. One correction to the framing, then questions.
|
|
28
|
+
|
|
29
|
+
#### The framing issue
|
|
30
|
+
|
|
31
|
+
"pyparsing parser -> AST -> translate to build123d" understates it. OpenSCAD is a
|
|
32
|
+
full programming language; a `.scad` file is a *program* and geometry is its
|
|
33
|
+
output. You need three layers, not two:
|
|
34
|
+
|
|
35
|
+
1. **Parser** -> AST
|
|
36
|
+
2. **Evaluator** — the actual hard part: lexical scoping, `module`/`function`
|
|
37
|
+
definitions with defaults, `children()`/`$children`, `for`/`intersection_for`/
|
|
38
|
+
`if`/`let`, list comprehensions (`for`/`if`/`each`/`let` inside `[]`),
|
|
39
|
+
`include`/`use`, `$`-variable *dynamic* scoping, and OpenSCAD's
|
|
40
|
+
non-sequential variable semantics (within a scope the last assignment wins for
|
|
41
|
+
the whole scope — `x=1; echo(x); x=2;` prints `2`)
|
|
42
|
+
3. **Geometry backend** -> build123d
|
|
43
|
+
|
|
44
|
+
Layer 3 is essentially solid123d already.
|
|
45
|
+
|
|
46
|
+
#### Don't route through SolidPython source
|
|
47
|
+
|
|
48
|
+
Generating SolidPython code doesn't save you layer 2 — SolidPython has no
|
|
49
|
+
representation for `for`, `if`, or `module`, so you'd have to evaluate those
|
|
50
|
+
first anyway. You'd get a codegen step and two debugging hops for no reduction in
|
|
51
|
+
work.
|
|
52
|
+
|
|
53
|
+
But solid123d is still the right thing to reuse — as a **library, not a codegen
|
|
54
|
+
target**. The evaluator calls `solid123d.cube(...)`, `solid123d.translate(v)(...)`
|
|
55
|
+
directly; those already return `build123d.Shape`. See
|
|
56
|
+
`@/Users/jonese/Dropbox/Projects/3D_geometry/scad_2_build123d/src/solid123d/primitives.py:22`.
|
|
57
|
+
|
|
58
|
+
#### Parser choice
|
|
59
|
+
|
|
60
|
+
**Lark** over pyparsing: grammar in EBNF in its own file, Earley for the ambiguous
|
|
61
|
+
corners and LALR when you want speed, far better error messages, pure Python. A
|
|
62
|
+
C-like expression grammar with precedence is exactly where pyparsing is least
|
|
63
|
+
pleasant. `tree-sitter-openscad` also exists and is battle-tested, but adds a
|
|
64
|
+
native dep and hands you a CST to walk.
|
|
65
|
+
|
|
66
|
+
#### Where fidelity actually breaks
|
|
67
|
+
|
|
68
|
+
- **`$fn`** — the sneaky one. `circle(r=10, $fn=6)` *is a hexagon* in OpenSCAD,
|
|
69
|
+
not an approximation of a circle. solid123d ignores `$fn` (exact BRep curves),
|
|
70
|
+
which is correct for a migration bridge but silently wrong for an importer
|
|
71
|
+
whose job is fidelity.
|
|
72
|
+
- **`hull()`** — common in real files, no OCCT equivalent. Either hard-fail or
|
|
73
|
+
approximate via `scipy.spatial.ConvexHull` over tessellated vertices ->
|
|
74
|
+
polyhedron (exact for polyhedral input, wrong for spheres).
|
|
75
|
+
- **`minkowski()`** — also common (rounded boxes). Sometimes expressible as
|
|
76
|
+
`offset`; generally not.
|
|
77
|
+
- **`polyhedron()`, `import()` (STL/DXF/SVG), `surface()`, `projection()`,
|
|
78
|
+
`linear_extrude(twist=)`** — each needs its own call. Mesh imports especially:
|
|
79
|
+
OCCT booleans on imported triangle soup are miserable.
|
|
80
|
+
- **Modifiers** `*` `!` `#` `%` — `*`/`!` are cheap and genuinely useful; `#`/`%`
|
|
81
|
+
map to color/discard.
|
|
82
|
+
|
|
83
|
+
#### API surface
|
|
84
|
+
|
|
85
|
+
`import_scad(path) -> Shape` covers "give me the model", but most `.scad` files
|
|
86
|
+
worth importing are *libraries of modules*. Proposed:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
lib = scad123d.import_scad(path, **overrides) # -> ScadModule namespace
|
|
90
|
+
nut = lib.hex_nut(size=5) # modules as Python callables -> Shape
|
|
91
|
+
part = lib.geometry # top-level geometry, implicitly unioned
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`**overrides` sets top-level variables (Customizer-style) — nearly free once the
|
|
95
|
+
evaluator exists, and very valuable.
|
|
96
|
+
|
|
97
|
+
#### Testing
|
|
98
|
+
|
|
99
|
+
OpenSCAD.app is installed on this machine, so differential testing is practical:
|
|
100
|
+
render each fixture with `openscad -o out.stl`, compare volume / bbox / centroid
|
|
101
|
+
against the build123d result within tolerance. That's the only way to trust this.
|
|
102
|
+
Worth building the harness early rather than bolting it on.
|
|
103
|
+
|
|
104
|
+
#### Questions
|
|
105
|
+
|
|
106
|
+
1. **Target corpus** — what do you actually want to import? This decides
|
|
107
|
+
everything. Hand-written single files? Thingiverse downloads? Files built on
|
|
108
|
+
BOSL2/MCAD/dotSCAD? (BOSL2 is a project unto itself: heavy list-comprehension
|
|
109
|
+
math, function literals, `$`-var conventions.) There are ~40 `.scad` files
|
|
110
|
+
under `~/Dropbox/Projects/sp_scratch` — is that representative?
|
|
111
|
+
2. **`$fn` fidelity** — honor it (faceted, matches OpenSCAD exactly), ignore it
|
|
112
|
+
(exact BRep, prettier and filletable), or honor-when-explicitly-set?
|
|
113
|
+
3. **`hull`/`minkowski`** — hard fail, or best-effort approximation with a
|
|
114
|
+
warning?
|
|
115
|
+
4. **solid123d dependency** — depend on the PyPI package, or vendor it so
|
|
116
|
+
scad123d stands alone?
|
|
117
|
+
5. **`include`/`use` resolution** — full `OPENSCADPATH` + library-dir search, or
|
|
118
|
+
restricted to paths relative to the imported file? (`include` executes
|
|
119
|
+
arbitrary code from arbitrary paths, so this is a sandboxing question too.)
|
|
120
|
+
6. **Depth vs. breadth first** — MVP is primitives + transforms + booleans +
|
|
121
|
+
variables + `for`/`if` + modules + `include`. Full language adds list
|
|
122
|
+
comprehensions, function literals, `assert`/`echo`, `intersection_for`,
|
|
123
|
+
ranges, string ops. Ship the MVP first, or get the language right before
|
|
124
|
+
widening geometry coverage?
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 2026-08-18 — Decisions, and the CSG-export discovery
|
|
129
|
+
|
|
130
|
+
### User
|
|
131
|
+
|
|
132
|
+
> BOSL2 & MCAD are good goals. For our purposes, let's pick something like 10
|
|
133
|
+
> modules from BOSL2 and get fidelity between those two? We'll ignore
|
|
134
|
+
> `sp_scratch`; that's just scratch code I've worked on in the past.
|
|
135
|
+
>
|
|
136
|
+
> When $fn is set globally, it's usually there as a compilation-complexity
|
|
137
|
+
> switch; I use something like $fn=48 for development work and $fn=128 for
|
|
138
|
+
> exports. When $fn is set explicitly in a function call, (like your hexagon
|
|
139
|
+
> example) that's usually intentional. I'd say let's honor explicit $fn
|
|
140
|
+
> invocations, and ignore global usage for any $fn >= 20, with explicit notes in
|
|
141
|
+
> the README describing behavior and workarounds.
|
|
142
|
+
>
|
|
143
|
+
> 3. Let's do a hard fail on hull and minkowski for now. We might look to include
|
|
144
|
+
> more universal approximations at a later stage, but for now let's throw a
|
|
145
|
+
> NotImplementedError or something and make similar explicit notes in the README
|
|
146
|
+
> 4. Do we actually need solid123d? If so, let's depend on PyPI. If not, let's not
|
|
147
|
+
> do it at all. Definitely no vendoring.
|
|
148
|
+
> 5. Yes, let's include OPENSCADPATH logic to mimic OpenSCAD's. There's probably a
|
|
149
|
+
> fair amount of testing to make sure we resolve to the same paths as OpenSCAD.
|
|
150
|
+
> Your point about sandboxing is a good one -- worth including a security note
|
|
151
|
+
> in the docs
|
|
152
|
+
> 6. Let's get the language right first. The primitives are the easy part.
|
|
153
|
+
> Interpreting all the language constructs correctly is a bigger job.
|
|
154
|
+
>
|
|
155
|
+
> *UNLESS* we want to replace OpenSCAD calls with calls to the OpenSCAD executable
|
|
156
|
+
> to create STLs, then import those STLs normally. That's a much much smaller
|
|
157
|
+
> project and still gets us mostly similar results. Can you tell me some pros and
|
|
158
|
+
> cons of this approach? I suspect translating all the code in an OpenSCAD
|
|
159
|
+
> repository to Build123d code is a big job. Letting OpenSCAD do its thing and
|
|
160
|
+
> then using the output seems like a reasonable approach even if it means we don't
|
|
161
|
+
> get e.g. per-face awareness from the OpenSCAD objects.
|
|
162
|
+
|
|
163
|
+
### Assistant
|
|
164
|
+
|
|
165
|
+
Ran experiments against OpenSCAD 2025.07.18 (installed at
|
|
166
|
+
`/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD`). BOSL2 is already present
|
|
167
|
+
in `~/Documents/OpenSCAD/libraries/`.
|
|
168
|
+
|
|
169
|
+
#### Finding: `openscad -o out.csg` gives a fully-evaluated tree of *analytic primitives*
|
|
170
|
+
|
|
171
|
+
OpenSCAD's CSG export runs the entire language and emits a flattened tree.
|
|
172
|
+
Verified resolved: modules inlined with defaults, `if` decided per-instance,
|
|
173
|
+
`for` unrolled, list comprehensions evaluated, `children()` spliced, transforms
|
|
174
|
+
collapsed to 4x4 `multmatrix`, `include`/`use`/`OPENSCADPATH` resolved, `*`
|
|
175
|
+
disable-modifier stripped.
|
|
176
|
+
|
|
177
|
+
Two key properties:
|
|
178
|
+
- **Primitives stay analytic** — `cylinder(h=8, r1=3, r2=1.5)`, not triangles.
|
|
179
|
+
The BRep survives.
|
|
180
|
+
- **`$fn` is emitted per-primitive and distinguishes global-inherited from
|
|
181
|
+
call-site** (`$fn=48` from global vs `$fn=6` explicit). The agreed $fn policy
|
|
182
|
+
falls out for free. `$fn=0` means unset (use `$fa`/`$fs`).
|
|
183
|
+
|
|
184
|
+
**BOSL2 works.** `include <BOSL2/std.scad> + gears.scad` with `cuboid(rounding=3)`,
|
|
185
|
+
`spur_gear()`, `prismoid(chamfer=2)` exported in **0.33s**. Full node vocabulary
|
|
186
|
+
of that output:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
945 group 65 multmatrix 9 polygon 9 linear_extrude
|
|
190
|
+
8 intersection 8 cylinder 4 union 1 polyhedron 1 hull
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Across all probes the CSG vocabulary is ~24 node types with no expressions, no
|
|
194
|
+
variables, no scoping — a ~100-line Lark grammar plus a tree walk, not an
|
|
195
|
+
interpreter.
|
|
196
|
+
|
|
197
|
+
#### Three options
|
|
198
|
+
|
|
199
|
+
| | A: full interpreter | B: shell -> STL | C: shell -> CSG |
|
|
200
|
+
|---|---|---|---|
|
|
201
|
+
| Effort | months | ~1 week | ~2-3 weeks |
|
|
202
|
+
| BOSL2/MCAD day one | no | yes | yes |
|
|
203
|
+
| Geometry out | BRep | **mesh** | **BRep** |
|
|
204
|
+
| `fillet`/`chamfer` after import | yes | **no** | yes |
|
|
205
|
+
| Face/edge selectors | yes | triangles only | yes |
|
|
206
|
+
| Real STEP export | yes | mesh-as-STEP | yes |
|
|
207
|
+
| `hull`/`minkowski` | no | **yes** | no (mesh fallback) |
|
|
208
|
+
| Needs OpenSCAD binary | no | yes | yes |
|
|
209
|
+
|
|
210
|
+
#### Pros/cons of the STL route
|
|
211
|
+
|
|
212
|
+
**Pros:** perfect semantic fidelity by construction including `hull`,
|
|
213
|
+
`minkowski`, `projection`, `surface`. Smallest codebase. Zero language-tracking
|
|
214
|
+
maintenance.
|
|
215
|
+
|
|
216
|
+
**Cons** (the mesh consequence is bigger than "no per-face awareness"):
|
|
217
|
+
- `fillet()`/`chamfer()` effectively dead — filleting a 128-facet cylinder rim
|
|
218
|
+
gives 128 tiny facet-edge fillets, not a rounded rim.
|
|
219
|
+
- Selectors return triangles; `.faces().sort_by(Axis.Z)[-1]` is one triangle.
|
|
220
|
+
No `Plane(face)` workplane placement.
|
|
221
|
+
- OCCT booleans on triangle soup: much slower, much more failure-prone.
|
|
222
|
+
- STEP export is mesh-as-STEP; downstream CAD imports a mesh body, not a solid.
|
|
223
|
+
- Faceting baked at import time; changing `$fn` means re-running.
|
|
224
|
+
- STL is unsealed triangle soup, needs sewing/healing. 3MF is the better
|
|
225
|
+
container if going this route.
|
|
226
|
+
|
|
227
|
+
Decisive point: B's only real advantage over C is that
|
|
228
|
+
`hull`/`minkowski`/`projection`/`surface` get evaluated. Everything else B
|
|
229
|
+
offers, C offers too — plus analytic geometry. Both need the same binary, so B
|
|
230
|
+
buys nothing on the dependency front.
|
|
231
|
+
|
|
232
|
+
#### Recommendation: C, with B as a localized fallback
|
|
233
|
+
|
|
234
|
+
Take the CSG route. When the walker hits a node with no BRep equivalent
|
|
235
|
+
(`hull`, `minkowski`, `projection`, `surface`, mesh `import`), write *that
|
|
236
|
+
subtree alone* back out (CSG syntax is valid OpenSCAD input), re-invoke OpenSCAD
|
|
237
|
+
to mesh just it, splice the mesh in with a warning. Analytic everywhere
|
|
238
|
+
possible; mesh confined to the smallest subtree that needs it.
|
|
239
|
+
|
|
240
|
+
**This bears on decision #3.** `cuboid([20,15,10], rounding=3, edges="Z")`
|
|
241
|
+
compiled to `hull()` over eight thin cylinders — BOSL2's rounded boxes *are*
|
|
242
|
+
`hull()`. A hard `NotImplementedError` would reject one of the most common
|
|
243
|
+
BOSL2 idioms. Recommend the mesh fallback instead of a hard fail.
|
|
244
|
+
|
|
245
|
+
#### Revised answers to the open questions
|
|
246
|
+
|
|
247
|
+
- **#4 solid123d** — yes, emphatically; it becomes the entire backend. The CSG
|
|
248
|
+
node set is nearly 1:1 with its public API. Depend on the PyPI package. Needs
|
|
249
|
+
two additions: `polyhedron()` and a `multmatrix()` applying a raw 4x4.
|
|
250
|
+
- **#5 OPENSCADPATH** — moot; OpenSCAD resolves includes itself. Security note
|
|
251
|
+
gets *stronger*: we execute OpenSCAD on user-supplied `.scad`, which can
|
|
252
|
+
`include`/`import()` arbitrary paths. Prominent docs warning warranted.
|
|
253
|
+
- **#6 language first** — moot; OpenSCAD is the interpreter.
|
|
254
|
+
- **#2 `$fn`** — implementable exactly as specified; CSG gives per-primitive `$fn`.
|
|
255
|
+
|
|
256
|
+
Caveats stated: C is not an independent reimplementation — it hard-depends on
|
|
257
|
+
the OpenSCAD binary, and a pure-Python no-binary scad123d remains project A.
|
|
258
|
+
Verified on OpenSCAD 2025.07.18 against a moderate BOSL2 sample, not proof it
|
|
259
|
+
holds across all of BOSL2.
|
|
260
|
+
|
|
261
|
+
**Open question:** does depending on the OpenSCAD binary kill it? If scad123d
|
|
262
|
+
must work without OpenSCAD installed, C is off the table. If acceptable,
|
|
263
|
+
suggested a fidelity suite spanning mapping paths rather than 10 arbitrary
|
|
264
|
+
modules: `cuboid(rounding=)` / `cyl(rounding=)` (hull path), `spur_gear` /
|
|
265
|
+
`star` (linear_extrude+polygon), `prismoid(chamfer=)` / `rounded_prism` /
|
|
266
|
+
`path_sweep` (polyhedron), `torus` (rotate_extrude), `tube` (booleans),
|
|
267
|
+
`text3d` (fonts).
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 2026-08-18 — Scaffolded v1 (rungs 0 and 1)
|
|
272
|
+
|
|
273
|
+
### User
|
|
274
|
+
|
|
275
|
+
> Yes, scaffold it with rungs 0 and 1. Please add docs regarding the tradeoffs
|
|
276
|
+
> involved, but since 95% of OpenSCAD Minkowski use is just adding rounds, this
|
|
277
|
+
> will cover most of our bases and improve on existing solution.
|
|
278
|
+
|
|
279
|
+
### Assistant
|
|
280
|
+
|
|
281
|
+
Built the project. 76 tests pass, 1 skipped.
|
|
282
|
+
|
|
283
|
+
#### Layout
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
src/scad123d/
|
|
287
|
+
csg.lark grammar for OpenSCAD's flattened .csg format (~24 nodes, no expressions)
|
|
288
|
+
parser.py .csg text -> CsgNode tree
|
|
289
|
+
nodes.py CsgNode; UNSUPPORTED set
|
|
290
|
+
emit.py CsgNode -> .csg text (enables the subtree fallback)
|
|
291
|
+
openscad.py binary discovery (macOS/Windows/Linux/flatpak/snap) + invocation
|
|
292
|
+
build.py the walker; delegates primitives to solid123d
|
|
293
|
+
solids.py polyhedron() and multmatrix (no solid123d equivalent)
|
|
294
|
+
facets.py the $fn policy + analytic faceted circle/cylinder
|
|
295
|
+
minkowski.py rung 1
|
|
296
|
+
mesh.py rung 0
|
|
297
|
+
errors.py
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Public API: `import_scad(path, **overrides)`, `import_csg(tree|text|path)`,
|
|
301
|
+
`to_csg(path)`, plus `find_openscad`/`openscad_version`.
|
|
302
|
+
|
|
303
|
+
#### Verified results
|
|
304
|
+
|
|
305
|
+
- **BOSL2 works end to end.** `tube(h=20, or=15, ir=10)` imports as 2 planes +
|
|
306
|
+
**2 analytic CYLINDER faces**, volume exact to 1.16e-16.
|
|
307
|
+
`torus(r_maj=20, r_min=5)` imports as a **single TORUS face**, exact to
|
|
308
|
+
1.84e-16. `prismoid(chamfer=2)` and `spur_gear(teeth=16)` build exactly via
|
|
309
|
+
the polyhedron and linear_extrude paths. `cuboid(rounding=4)` takes the hull
|
|
310
|
+
mesh path as predicted.
|
|
311
|
+
- **The payoff claim holds.** Filleting an imported BOSL2 tube selects 2
|
|
312
|
+
circular edges (not triangles) and produces 2 TORUS fillet surfaces, valid
|
|
313
|
+
solid, 14KB analytic STEP.
|
|
314
|
+
- **Rung 1 is exact.** minkowski(cube, sphere) matches the Steiner formula to
|
|
315
|
+
6.7e-11, returning 6 planes + 12 cylinders + 8 spheres.
|
|
316
|
+
- **Faceted primitives are exact.** Hexagon + octagon + pentagonal frustum +
|
|
317
|
+
square pyramid + exact cylinder summed to 6315.27 by hand vs 6315.2881 built.
|
|
318
|
+
- **CI tier runs with no binary**: 44 tests in 0.24s, 33 deselected.
|
|
319
|
+
|
|
320
|
+
#### Four non-obvious things found while building
|
|
321
|
+
|
|
322
|
+
1. **OpenSCAD writes CSG matrices at only 6 significant figures**, so a rotation
|
|
323
|
+
arrives orthonormal to just ~3e-7 (cos 45 deg is emitted as `0.707107`). That
|
|
324
|
+
residual is enough to make OCCT report the transformed shape invalid. Fixed
|
|
325
|
+
by decomposing and re-orthonormalizing via Gram-Schmidt.
|
|
326
|
+
2. **A `gp_Trsf` carrying any scale factor other than 1 produces a shape that
|
|
327
|
+
fails `BRepCheck`** — the geometry scales but its tolerances do not. Scaling
|
|
328
|
+
must be applied separately, never folded into the rigid transform.
|
|
329
|
+
3. **`build123d.transform_shape()` is unusable** for this: it routes through
|
|
330
|
+
`gp_GTrsf.Trsf()`, and OCCT never sets the form flag on an element-wise
|
|
331
|
+
GTrsf, so even a pure translation raises "non-orthogonal GTrsf".
|
|
332
|
+
4. **`build123d.scale()` defaults to scaling about the object's location, not
|
|
333
|
+
the origin.** This is an upstream bug in solid123d, which calls it without
|
|
334
|
+
`about=` — OpenSCAD's `scale()` is origin-based, so bridged solid123d code
|
|
335
|
+
with an off-origin object gets the wrong result. Recorded in ROADMAP.md.
|
|
336
|
+
|
|
337
|
+
Also corrected an earlier claim of mine: I had said the CSG output distinguishes
|
|
338
|
+
a global `$fn` from a call-site one. It does not — it records only the effective
|
|
339
|
+
value per node. The agreed policy is implemented as a magnitude threshold
|
|
340
|
+
(`facet_threshold`, default 20), which matches the intent; documented as a
|
|
341
|
+
heuristic in the README with workarounds.
|
|
342
|
+
|
|
343
|
+
One test-design error found and fixed: I first asserted BRep volume >= OpenSCAD
|
|
344
|
+
mesh volume. That flips when a curved feature is *subtracted* — an exact
|
|
345
|
+
cylindrical hole removes more material than a faceted one. Replaced with a
|
|
346
|
+
direction-agnostic convergence assertion (raising $fn must move OpenSCAD's
|
|
347
|
+
volume toward ours).
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 2026-08-18 — Committed; patched solid123d's minkowski() upstream
|
|
352
|
+
|
|
353
|
+
### User
|
|
354
|
+
|
|
355
|
+
> Yes, commit this and do rung 2
|
|
356
|
+
> [Request interrupted by user]
|
|
357
|
+
> Let's not do rung 2 yet, but let's commit. Also, can you tell me how
|
|
358
|
+
> solid123d handles hull & minkowski? It seems like there could be a problem
|
|
359
|
+
> with those calls that our rung 1 solution would resolve for most use cases
|
|
360
|
+
|
|
361
|
+
### Assistant
|
|
362
|
+
|
|
363
|
+
Committed scad123d's v1 scaffold (`fb5b59d`).
|
|
364
|
+
|
|
365
|
+
Checked `src/solid123d/booleans.py`: `hull()` and `minkowski()` both raised
|
|
366
|
+
`NotImplementedError` **unconditionally**, regardless of arguments -- so
|
|
367
|
+
`minkowski()(box, sphere(3))`, the single most common real-world Minkowski
|
|
368
|
+
call, hard-failed for every SolidPython-bridge user. Confirmed this was a
|
|
369
|
+
better fit to fix upstream than to route around: the analytic-offset trick
|
|
370
|
+
needs no OpenSCAD binary (pure `build123d.offset`), so it's a zero-dependency,
|
|
371
|
+
strictly backward-compatible addition to solid123d itself.
|
|
372
|
+
|
|
373
|
+
Patched `~/Dropbox/Projects/3D_geometry/scad_2_build123d` directly (with the
|
|
374
|
+
user's go-ahead): `minkowski()` now detects a bare, untransformed
|
|
375
|
+
`sphere()`/`circle()` at the origin as either argument (`BRepAdaptor_Surface`/
|
|
376
|
+
`BRepAdaptor_Curve`, checking `GeomType` and that the center is within 1e-6 of
|
|
377
|
+
the origin) and computes the sum as `offset(A, r)`. Verified exact against the
|
|
378
|
+
3D and 2D Steiner formulas, commutative in either argument order, and correct
|
|
379
|
+
on `minkowski(sphere(2), sphere(3))` -> radius-5 sphere. Everything else still
|
|
380
|
+
raises `NotImplementedError` exactly as before. Added 6 tests to
|
|
381
|
+
`tests/test_solid123d.py`; all 50 pass. Committed as `5ba9785`, with a
|
|
382
|
+
CONVERSATION.md entry in that repo per its own house convention (`9037aa0`).
|
|
383
|
+
|
|
384
|
+
**Note for later**: this fix lives in the local solid123d repo, not yet
|
|
385
|
+
released. scad123d depends on `solid123d>=0.1.0` from PyPI but does not
|
|
386
|
+
actually call `solid123d.minkowski()` -- scad123d's own rung 1
|
|
387
|
+
(`src/scad123d/minkowski.py`) is an independent implementation that the CSG
|
|
388
|
+
walker (`build.py`) calls directly, so scad123d is unaffected either way.
|
|
389
|
+
The solid123d fix only reaches solid123d's own (SolidPython-bridge) users once
|
|
390
|
+
it's released as a new version to PyPI -- that's a separate decision for the
|
|
391
|
+
user to make, not bundled into this session.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 2026-08-18 (cont.) — Rung 2, and a real fix upstream instead
|
|
396
|
+
|
|
397
|
+
### User
|
|
398
|
+
|
|
399
|
+
> Yes, let's do rung 2.
|
|
400
|
+
|
|
401
|
+
### Assistant
|
|
402
|
+
|
|
403
|
+
Before writing code, verified rung 2's premise against the real BOSL2 calls it
|
|
404
|
+
was supposed to fix.
|
|
405
|
+
|
|
406
|
+
#### The premise was half wrong
|
|
407
|
+
|
|
408
|
+
Traced `cuboid([20,15,10], rounding=3, edges="Z")`'s actual CSG output. The
|
|
409
|
+
`hull()` node found during the original design exploration turns out to be
|
|
410
|
+
inert: all 8 of its children resolve to *empty* geometry (two intersection
|
|
411
|
+
operands with non-overlapping bounding boxes — `z in [-0.005,0.005]` vs
|
|
412
|
+
`z in [-0.165,-0.055]`), apparently anchor/attachment bookkeeping. The real
|
|
413
|
+
rounded profile is a many-vertex 2D polygon, pre-faceted by BOSL2 itself at
|
|
414
|
+
`$fn` resolution, fed straight into `linear_extrude` — already handled
|
|
415
|
+
natively by scad123d, no hull involved. Rung 2 as scoped would not have
|
|
416
|
+
touched this call at all.
|
|
417
|
+
|
|
418
|
+
`cuboid([20,15,10], rounding=3)` (every edge, not just `Z`) doesn't use
|
|
419
|
+
`hull()` either — it's `minkowski(cube, polyhedron)`, where the polyhedron has
|
|
420
|
+
258 vertices all at radius 3.000000 +/- 4e-6 from a shared centroid. A sphere,
|
|
421
|
+
expressed as an explicit facet mesh rather than a `sphere()` node. Rung 1 only
|
|
422
|
+
recognized a literal `sphere`/`circle`, so this took the mesh path too.
|
|
423
|
+
|
|
424
|
+
Surfaced this to the user with three options (original rung 2 only /
|
|
425
|
+
polyhedron-ball extension only / both) rather than silently redefining scope.
|
|
426
|
+
User chose both.
|
|
427
|
+
|
|
428
|
+
#### Built, in order
|
|
429
|
+
|
|
430
|
+
**Rung 1.5 — Minkowski with a polyhedron/polygon ball.** Redesigned
|
|
431
|
+
`minkowski.py`'s classification to operate on the *already-built* Shape for
|
|
432
|
+
each operand rather than the raw CSG node -- mirrors the redesign that made
|
|
433
|
+
rung 2 itself work on real code (see below), and was necessary here too: a
|
|
434
|
+
raw-node classifier stalled on a 2-child `group()` partway down the real
|
|
435
|
+
BOSL2 tree (one inert sibling, one real), which the existing walker's
|
|
436
|
+
`_union()` already resolves correctly by filtering empties. Added detection
|
|
437
|
+
of a polyhedron/polygon whose vertices are all equidistant from a centroid,
|
|
438
|
+
gated at >=24 vertices (above all 5 Platonic solids' vertex counts -- a
|
|
439
|
+
cube's 8 corners are *also* equidistant from centroid, so count is what
|
|
440
|
+
distinguishes a genuine tessellation from a deliberate few-sided kernel).
|
|
441
|
+
Verified against the real BOSL2 file: `cuboid(rounding=3)` now imports as 6
|
|
442
|
+
planes + 12 cylinders + 8 spheres, Steiner formula to ~4e-7 (limited by
|
|
443
|
+
BOSL2's own kernel precision).
|
|
444
|
+
|
|
445
|
+
**Rung 2 — hull of equal-radius spheres and parallel cylinders.** Classifies
|
|
446
|
+
each hull() child's *built* shape (single-face SPHERE, or 1-cylinder+2-plane
|
|
447
|
+
solid) rather than the raw tree, for the same real-code-robustness reason.
|
|
448
|
+
Sphere case: qhull `ConvexHull` of centers -> `offset(polyhedron, r, ARC)`,
|
|
449
|
+
verified exact (~1e-9) against a box-of-8-corners and a tetrahedron,
|
|
450
|
+
reproducing the original design-phase numbers (953.14152, 8285.4424) as
|
|
451
|
+
regression fixtures. Cylinder case: project onto the plane perpendicular to
|
|
452
|
+
the shared axis, 2D-hull, offset, extrude -- verified exact against a 4-post
|
|
453
|
+
rounded-rectangle case and an arbitrarily-tilted 2-cylinder case. A fully
|
|
454
|
+
collinear point set (2+ spheres on one line) builds an explicit capsule
|
|
455
|
+
instead of a degenerate hull -- also just the common 2-post slot idiom.
|
|
456
|
+
|
|
457
|
+
Two implementation bugs found and fixed along the way:
|
|
458
|
+
- Passing qhull's raw triangulated simplices into `offset_3d` made OCCT raise
|
|
459
|
+
`Null TopoDS_Shape object` -- a box face comes back as 2 triangles sharing a
|
|
460
|
+
plane equation, and offset needs merged planar faces, not adjacent coplanar
|
|
461
|
+
triangles. Fixed by grouping simplices via `hull.equations` and re-deriving
|
|
462
|
+
each merged face as the 2D hull of its own projected vertices.
|
|
463
|
+
- The first stadium (2D capsule) construction, built as a union of a
|
|
464
|
+
rectangle and two circles, left 12 lateral faces after extrusion instead of
|
|
465
|
+
4 -- OCCT's fuse doesn't merge the collinear segments the union introduces
|
|
466
|
+
at tangent points. Rebuilt as one closed wire (2 lines + 2 tangent arcs).
|
|
467
|
+
|
|
468
|
+
Also fixed, orthogonally, discovered while tracing the real BOSL2 tree: an
|
|
469
|
+
intersection producing empty geometry (legitimate -- non-overlapping
|
|
470
|
+
operands) would later crash `apply_matrix`/`moved()` with "Cannot move an
|
|
471
|
+
empty shape" instead of just vanishing from the tree the way OpenSCAD treats
|
|
472
|
+
it. `_children()` now filters `shape._wrapped is None` the same as it already
|
|
473
|
+
filtered `None` itself.
|
|
474
|
+
|
|
475
|
+
**What rung 2 deliberately does not cover** (falls back to mesh, does not
|
|
476
|
+
crash): coplanar-but-not-collinear sphere hulls (qhull raises on degenerate
|
|
477
|
+
input); cylinders not sharing one axial span; mixed sphere+cylinder or
|
|
478
|
+
unequal-radius hulls. Documented in ROADMAP.md as Rung 2.5 and folded into
|
|
479
|
+
Rung 4/5's existing scope notes.
|
|
480
|
+
|
|
481
|
+
76 tests before this session; 104 after (58 more in the local run counting
|
|
482
|
+
fixture-parametrized cases), all passing across two clean full runs.
|
|
483
|
+
|
|
484
|
+
#### Note on solid123d, unaffected either way
|
|
485
|
+
|
|
486
|
+
scad123d's rung 1/1.5/2 are independent implementations the walker calls
|
|
487
|
+
directly, not routed through solid123d, so none of this session's work
|
|
488
|
+
touches or depends on it. For the record: solid123d's own `minkowski()`
|
|
489
|
+
already got the plain-ball fix in the prior session (2026-08-18 earlier,
|
|
490
|
+
`5ba9785`); its `hull()` still raises `NotImplementedError` unconditionally,
|
|
491
|
+
and the polyhedron-ball extension built here has not been ported upstream.
|
scad123d-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Evan Jones
|
|
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.
|