solid123d 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.
- solid123d-0.1.0/.github/workflows/ci.yml +28 -0
- solid123d-0.1.0/.gitignore +19 -0
- solid123d-0.1.0/.python-version +1 -0
- solid123d-0.1.0/CONVERSATION.md +100 -0
- solid123d-0.1.0/LICENSE +21 -0
- solid123d-0.1.0/PKG-INFO +93 -0
- solid123d-0.1.0/README.md +68 -0
- solid123d-0.1.0/justfile +9 -0
- solid123d-0.1.0/pyproject.toml +39 -0
- solid123d-0.1.0/src/solid123d/__init__.py +63 -0
- solid123d-0.1.0/src/solid123d/_common.py +41 -0
- solid123d-0.1.0/src/solid123d/booleans.py +64 -0
- solid123d-0.1.0/src/solid123d/extrusions.py +61 -0
- solid123d-0.1.0/src/solid123d/fonts.py +100 -0
- solid123d-0.1.0/src/solid123d/primitives.py +133 -0
- solid123d-0.1.0/src/solid123d/render.py +53 -0
- solid123d-0.1.0/src/solid123d/transforms.py +128 -0
- solid123d-0.1.0/src/solid123d/utils.py +33 -0
- solid123d-0.1.0/tests/test_fonts.py +75 -0
- solid123d-0.1.0/tests/test_solid123d.py +236 -0
- solid123d-0.1.0/uv.lock +1463 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, macos-latest]
|
|
14
|
+
python-version: ["3.10", "3.13"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync --dev
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: uv run pytest -v
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Conversation Log
|
|
2
|
+
|
|
3
|
+
## 2026-08-13
|
|
4
|
+
|
|
5
|
+
**User**: Wants to transition from OpenSCAD/SolidPython to build123d. Asked
|
|
6
|
+
for a small bridge letting existing SolidPython code build build123d objects
|
|
7
|
+
by only changing imports, e.g. `from solid import translate, cube,
|
|
8
|
+
scad_render_to_file` → same API, build123d underneath.
|
|
9
|
+
|
|
10
|
+
**Assistant**: Built the `solid123d` package (uv project, build123d 0.11.1,
|
|
11
|
+
Python 3.13, branch `feature/solidpython-bridge`).
|
|
12
|
+
|
|
13
|
+
Key design decisions:
|
|
14
|
+
- Primitives return **native build123d `Shape` objects** (no wrapper class),
|
|
15
|
+
so bridged code interoperates directly with build123d and migration can
|
|
16
|
+
proceed file-by-file. Trade-off: SolidPython's `a * b` intersection
|
|
17
|
+
operator is unavailable (`a & b` / `intersection()(a, b)` instead).
|
|
18
|
+
- Transforms are closures: `translate(v)` returns a callable applying
|
|
19
|
+
`Pos * shape`; multiple children are implicitly unioned as in OpenSCAD.
|
|
20
|
+
- `rotate([x,y,z])` applies sequential global-axis rotations X→Y→Z to match
|
|
21
|
+
OpenSCAD's fixed-axis order exactly; axis-angle and scalar forms supported.
|
|
22
|
+
- OpenSCAD alignment semantics preserved: `cube`/`square` corner at origin
|
|
23
|
+
unless `center=True`; `cylinder` XY-centered sitting on z=0.
|
|
24
|
+
- `scad_render_to_file` exports real geometry: `.scad` filenames rewritten
|
|
25
|
+
to `.step` with a `UserWarning`; `.stl`/`.step`/`.stp` honored as-is.
|
|
26
|
+
`scad_render()` raises `NotImplementedError` (no SCAD source exists).
|
|
27
|
+
- `hull`, `minkowski`, `linear_extrude(twist=)` raise `NotImplementedError`
|
|
28
|
+
(no BRep equivalent); `segments`/`$fn` accepted and ignored.
|
|
29
|
+
- `linear_extrude(scale=)` implemented via loft between base and scaled top.
|
|
30
|
+
- `rotate_extrude` maps the XY profile onto Plane.XZ then `revolve`s about Z.
|
|
31
|
+
- `solid123d.utils` provides `up/down/left/right/forward/back`.
|
|
32
|
+
|
|
33
|
+
Verification: 32 pytest tests pass (volumes/bboxes vs analytic values, e.g.
|
|
34
|
+
torus volume for `rotate_extrude`); user's exact example script runs with
|
|
35
|
+
only the import changed and emits a valid STEP file.
|
|
36
|
+
|
|
37
|
+
## 2026-08-13 (later)
|
|
38
|
+
|
|
39
|
+
**User**: OpenSCAD found "Academy Engraved LET" by name but build123d
|
|
40
|
+
couldn't. How does font identification work on both systems?
|
|
41
|
+
|
|
42
|
+
**Assistant**: Diagnosed on-machine: OpenSCAD uses fontconfig, which matches
|
|
43
|
+
the family name (nameID 1) and treats subfamily (nameID 2) as a style with
|
|
44
|
+
fuzzy best-match fallback. build123d uses OCCT `Font_FontMgr`, which only
|
|
45
|
+
folds standard subfamilies (Regular/Bold/Italic/Bold Italic) into font
|
|
46
|
+
aspects; this font's subfamily is "Plain", so OCCT registers it as
|
|
47
|
+
"Academy Engraved LET Plain" and a lookup by family name warns on stderr and
|
|
48
|
+
silently substitutes Arial. Verified: `Text(font="Academy Engraved LET
|
|
49
|
+
Plain")` and `font_path=` both render correctly.
|
|
50
|
+
|
|
51
|
+
**User**: Make `text()` resolve fonts by family name.
|
|
52
|
+
|
|
53
|
+
**Assistant**: Added `solid123d/fonts.py`: scans platform font dirs
|
|
54
|
+
(macOS/Linux/Windows) with fontTools (~0.06 s, `lru_cache`d), indexes
|
|
55
|
+
family -> {subfamily -> path} incl. .ttc collections, parses OpenSCAD's
|
|
56
|
+
`"Family:style=Style"` syntax, resolves to a concrete file path passed to
|
|
57
|
+
build123d as `font_path`. Unknown families fall through to build123d's own
|
|
58
|
+
name lookup (with standard styles mapped to `FontStyle`). Default style
|
|
59
|
+
preference: regular/plain/normal/book/roman/medium, else first face.
|
|
60
|
+
10 new tests (parse, resolution, end-to-end area check vs Arial fallback);
|
|
61
|
+
42 total pass.
|
|
62
|
+
|
|
63
|
+
## 2026-08-13 (later still)
|
|
64
|
+
|
|
65
|
+
**User**: Reported unions/differences "not applying" in sollie_token.py;
|
|
66
|
+
suspected linear_extrude without twist.
|
|
67
|
+
|
|
68
|
+
**Assistant**: linear_extrude is exact; real bug was solid123d `scale()`
|
|
69
|
+
padding a 2-vector with 0 instead of 1 (OpenSCAD identity), flattening
|
|
70
|
+
extruded text to degenerate solids so engraving subtractions no-op'd or
|
|
71
|
+
nulled. Fixed via `vec3(v, default=1.0)` + regression test (43 pass);
|
|
72
|
+
user's script verified end-to-end (embeds seat exactly into pockets).
|
|
73
|
+
|
|
74
|
+
**User**: Declined adding color-aware compound export to the bridge
|
|
75
|
+
(OpenSCAD exports colorless STL anyway). Asked how to make one STEP with
|
|
76
|
+
per-shape colors from two build123d Shapes.
|
|
77
|
+
|
|
78
|
+
**Assistant**: Recipe: set `.color` (and optionally `.label`) on each
|
|
79
|
+
Shape, group with `Compound(children=[...])` (grouping, not fusing), then
|
|
80
|
+
`export_step`. Verified two-color output in combined_colored.step. Gotcha
|
|
81
|
+
documented: the 8 predefined STEP colours (yellow, red, ...) are written
|
|
82
|
+
as DRAUGHTING_PRE_DEFINED_COLOUR('name'), not COLOUR_RGB — grep both when
|
|
83
|
+
inspecting files.
|
|
84
|
+
|
|
85
|
+
**User**: Wants a typing alias in solid123d (aliasing build123d.Shape) so
|
|
86
|
+
old SolidPython return-type annotations stay correct. (First asked for
|
|
87
|
+
`SolidPythonObject`, then corrected: that name was a mistake.)
|
|
88
|
+
|
|
89
|
+
**Assistant**: Exported `OpenSCADObject` (SolidPython's real class name)
|
|
90
|
+
and `OpenSCADObjectPlus` (solid2's) as aliases of `build123d.Shape`.
|
|
91
|
+
Test added; 44 pass.
|
|
92
|
+
|
|
93
|
+
**User**: Chose the name `solid123d` (over solid-build123d etc., all
|
|
94
|
+
verified available on PyPI); asked to replace every "solidbd" occurrence.
|
|
95
|
+
|
|
96
|
+
**Assistant**: Renamed src/solidbd -> src/solid123d and
|
|
97
|
+
tests/test_solidbd.py -> tests/test_solid123d.py; updated pyproject.toml,
|
|
98
|
+
README, tests, sources, and CONVERSATION.md; re-locked and synced with uv;
|
|
99
|
+
also updated the imports in sollie_token_aug_2026/sollie_token.py. Grep
|
|
100
|
+
confirms zero remaining occurrences; 44 tests pass.
|
solid123d-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.
|
solid123d-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: solid123d
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in bridge from SolidPython to build123d: change one import, get native BRep geometry
|
|
5
|
+
Project-URL: Homepage, https://github.com/etjones/solid123d
|
|
6
|
+
Project-URL: Repository, https://github.com/etjones/solid123d
|
|
7
|
+
Project-URL: Issues, https://github.com/etjones/solid123d/issues
|
|
8
|
+
Author-email: Evan Jones <evan_t_jones@mac.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: 3d,brep,build123d,cad,openscad,solidpython,step
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Manufacturing
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: build123d>=0.11.1
|
|
23
|
+
Requires-Dist: fonttools>=4.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# solid123d
|
|
27
|
+
|
|
28
|
+
A bridge that runs [SolidPython](https://github.com/SolidCode/SolidPython)
|
|
29
|
+
(OpenSCAD-style) code on the [build123d](https://build123d.readthedocs.io/)
|
|
30
|
+
BRep kernel. Change one import and existing code produces native build123d
|
|
31
|
+
shapes instead of OpenSCAD source.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
# before
|
|
35
|
+
from solid import translate, cube, scad_render_to_file
|
|
36
|
+
# after
|
|
37
|
+
from solid123d import translate, cube, scad_render_to_file
|
|
38
|
+
|
|
39
|
+
c = translate([10, 0, 0])(cube(10, center=True))
|
|
40
|
+
scad_render_to_file(c, filename="c.scad") # writes c.step (real BRep geometry)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Every object returned is a plain build123d `Shape`, so bridged code mixes
|
|
44
|
+
freely with native build123d — the intended migration path:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from build123d import fillet
|
|
48
|
+
from solid123d import cube
|
|
49
|
+
|
|
50
|
+
part = cube(20, center=True)
|
|
51
|
+
part = fillet(part.edges(), radius=2) # native build123d from here on
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Supported
|
|
55
|
+
|
|
56
|
+
- **Primitives**: `cube`, `sphere`, `cylinder` (incl. `r1`/`r2` cones,
|
|
57
|
+
`d`/`d1`/`d2`), `square`, `circle`, `polygon` (incl. `paths` holes), `text`
|
|
58
|
+
- **Font resolution**: `text(font=...)` matches fonts by family name the way
|
|
59
|
+
fontconfig/OpenSCAD does (incl. `"Family:style=Style"` syntax), by scanning
|
|
60
|
+
system font directories with fontTools and handing build123d the resolved
|
|
61
|
+
file path. This fixes fonts with nonstandard subfamilies (e.g.
|
|
62
|
+
`"Academy Engraved LET"`, whose subfamily "Plain" makes OCCT's own lookup
|
|
63
|
+
fall back to Arial).
|
|
64
|
+
- **Transforms** (callable style, `translate(v)(obj, ...)`): `translate`,
|
|
65
|
+
`rotate` (vector, scalar, and axis-angle forms with OpenSCAD ordering),
|
|
66
|
+
`scale`, `mirror`, `resize`, `color`, `offset`
|
|
67
|
+
- **Booleans**: `union()`, `difference()`, `intersection()` — plus native
|
|
68
|
+
operators `a + b`, `a - b`, `a & b`
|
|
69
|
+
- **2D → 3D**: `linear_extrude` (incl. `center`, `scale`; no `twist`),
|
|
70
|
+
`rotate_extrude` (incl. partial `angle`)
|
|
71
|
+
- **Export**: `scad_render_to_file` writes `.step`/`.stl`; a `.scad`
|
|
72
|
+
filename is rewritten to `.step` with a warning
|
|
73
|
+
- `solid123d.utils`: `up`, `down`, `left`, `right`, `forward`, `back`
|
|
74
|
+
- **Typing aliases**: `OpenSCADObject` and `OpenSCADObjectPlus` are
|
|
75
|
+
aliases of `build123d.Shape`, so existing
|
|
76
|
+
annotations like `def some_obj() -> OpenSCADObject:` remain correct
|
|
77
|
+
|
|
78
|
+
## Known differences
|
|
79
|
+
|
|
80
|
+
- `a * b` intersection is not overloaded; use `a & b` or `intersection()(a, b)`.
|
|
81
|
+
- `hull()` and `minkowski()` raise `NotImplementedError` (no BRep equivalent);
|
|
82
|
+
in build123d these are usually a `loft`, `sweep`, `offset`, or fillet.
|
|
83
|
+
- `linear_extrude(twist=...)` raises `NotImplementedError`.
|
|
84
|
+
- `$fn`/`segments` arguments are accepted and ignored — BRep curves are exact.
|
|
85
|
+
- `scad_render()` (source string) raises `NotImplementedError`.
|
|
86
|
+
- OpenSCAD modifiers (`#`, `%`, `!`) and `import()`/`surface()`/`projection()`
|
|
87
|
+
are not implemented.
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
just test # or: uv run pytest
|
|
93
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# solid123d
|
|
2
|
+
|
|
3
|
+
A bridge that runs [SolidPython](https://github.com/SolidCode/SolidPython)
|
|
4
|
+
(OpenSCAD-style) code on the [build123d](https://build123d.readthedocs.io/)
|
|
5
|
+
BRep kernel. Change one import and existing code produces native build123d
|
|
6
|
+
shapes instead of OpenSCAD source.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
# before
|
|
10
|
+
from solid import translate, cube, scad_render_to_file
|
|
11
|
+
# after
|
|
12
|
+
from solid123d import translate, cube, scad_render_to_file
|
|
13
|
+
|
|
14
|
+
c = translate([10, 0, 0])(cube(10, center=True))
|
|
15
|
+
scad_render_to_file(c, filename="c.scad") # writes c.step (real BRep geometry)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Every object returned is a plain build123d `Shape`, so bridged code mixes
|
|
19
|
+
freely with native build123d — the intended migration path:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from build123d import fillet
|
|
23
|
+
from solid123d import cube
|
|
24
|
+
|
|
25
|
+
part = cube(20, center=True)
|
|
26
|
+
part = fillet(part.edges(), radius=2) # native build123d from here on
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Supported
|
|
30
|
+
|
|
31
|
+
- **Primitives**: `cube`, `sphere`, `cylinder` (incl. `r1`/`r2` cones,
|
|
32
|
+
`d`/`d1`/`d2`), `square`, `circle`, `polygon` (incl. `paths` holes), `text`
|
|
33
|
+
- **Font resolution**: `text(font=...)` matches fonts by family name the way
|
|
34
|
+
fontconfig/OpenSCAD does (incl. `"Family:style=Style"` syntax), by scanning
|
|
35
|
+
system font directories with fontTools and handing build123d the resolved
|
|
36
|
+
file path. This fixes fonts with nonstandard subfamilies (e.g.
|
|
37
|
+
`"Academy Engraved LET"`, whose subfamily "Plain" makes OCCT's own lookup
|
|
38
|
+
fall back to Arial).
|
|
39
|
+
- **Transforms** (callable style, `translate(v)(obj, ...)`): `translate`,
|
|
40
|
+
`rotate` (vector, scalar, and axis-angle forms with OpenSCAD ordering),
|
|
41
|
+
`scale`, `mirror`, `resize`, `color`, `offset`
|
|
42
|
+
- **Booleans**: `union()`, `difference()`, `intersection()` — plus native
|
|
43
|
+
operators `a + b`, `a - b`, `a & b`
|
|
44
|
+
- **2D → 3D**: `linear_extrude` (incl. `center`, `scale`; no `twist`),
|
|
45
|
+
`rotate_extrude` (incl. partial `angle`)
|
|
46
|
+
- **Export**: `scad_render_to_file` writes `.step`/`.stl`; a `.scad`
|
|
47
|
+
filename is rewritten to `.step` with a warning
|
|
48
|
+
- `solid123d.utils`: `up`, `down`, `left`, `right`, `forward`, `back`
|
|
49
|
+
- **Typing aliases**: `OpenSCADObject` and `OpenSCADObjectPlus` are
|
|
50
|
+
aliases of `build123d.Shape`, so existing
|
|
51
|
+
annotations like `def some_obj() -> OpenSCADObject:` remain correct
|
|
52
|
+
|
|
53
|
+
## Known differences
|
|
54
|
+
|
|
55
|
+
- `a * b` intersection is not overloaded; use `a & b` or `intersection()(a, b)`.
|
|
56
|
+
- `hull()` and `minkowski()` raise `NotImplementedError` (no BRep equivalent);
|
|
57
|
+
in build123d these are usually a `loft`, `sweep`, `offset`, or fillet.
|
|
58
|
+
- `linear_extrude(twist=...)` raises `NotImplementedError`.
|
|
59
|
+
- `$fn`/`segments` arguments are accepted and ignored — BRep curves are exact.
|
|
60
|
+
- `scad_render()` (source string) raises `NotImplementedError`.
|
|
61
|
+
- OpenSCAD modifiers (`#`, `%`, `!`) and `import()`/`surface()`/`projection()`
|
|
62
|
+
are not implemented.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
just test # or: uv run pytest
|
|
68
|
+
```
|
solid123d-0.1.0/justfile
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "solid123d"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Drop-in bridge from SolidPython to build123d: change one import, get native BRep geometry"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
authors = [{ name = "Evan Jones", email = "evan_t_jones@mac.com" }]
|
|
10
|
+
keywords = ["openscad", "solidpython", "build123d", "cad", "3d", "step", "brep"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Manufacturing",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"build123d>=0.11.1",
|
|
24
|
+
"fonttools>=4.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/etjones/solid123d"
|
|
29
|
+
Repository = "https://github.com/etjones/solid123d"
|
|
30
|
+
Issues = "https://github.com/etjones/solid123d/issues"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=9.1.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["hatchling"]
|
|
39
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""solid123d: run SolidPython code on the build123d kernel.
|
|
2
|
+
|
|
3
|
+
Change ``from solid import ...`` to ``from solid123d import ...`` and the
|
|
4
|
+
same code produces native build123d shapes, which can be mixed freely
|
|
5
|
+
with regular build123d code and exported to STEP/STL.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from build123d import Shape
|
|
9
|
+
|
|
10
|
+
from .booleans import difference, hull, intersection, minkowski, union
|
|
11
|
+
from .extrusions import linear_extrude, rotate_extrude
|
|
12
|
+
from .primitives import (
|
|
13
|
+
circle,
|
|
14
|
+
cube,
|
|
15
|
+
cylinder,
|
|
16
|
+
polygon,
|
|
17
|
+
sphere,
|
|
18
|
+
square,
|
|
19
|
+
text,
|
|
20
|
+
)
|
|
21
|
+
from .render import scad_render, scad_render_to_file
|
|
22
|
+
from .transforms import (
|
|
23
|
+
color,
|
|
24
|
+
mirror,
|
|
25
|
+
offset,
|
|
26
|
+
resize,
|
|
27
|
+
rotate,
|
|
28
|
+
scale,
|
|
29
|
+
translate,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Typing aliases: SolidPython signatures like `-> OpenSCADObject` stay
|
|
33
|
+
# correct, since every solid123d function returns a build123d Shape.
|
|
34
|
+
OpenSCADObject = Shape
|
|
35
|
+
OpenSCADObjectPlus = Shape
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"OpenSCADObject",
|
|
39
|
+
"OpenSCADObjectPlus",
|
|
40
|
+
"circle",
|
|
41
|
+
"color",
|
|
42
|
+
"cube",
|
|
43
|
+
"cylinder",
|
|
44
|
+
"difference",
|
|
45
|
+
"hull",
|
|
46
|
+
"intersection",
|
|
47
|
+
"linear_extrude",
|
|
48
|
+
"minkowski",
|
|
49
|
+
"mirror",
|
|
50
|
+
"offset",
|
|
51
|
+
"polygon",
|
|
52
|
+
"resize",
|
|
53
|
+
"rotate",
|
|
54
|
+
"rotate_extrude",
|
|
55
|
+
"scad_render",
|
|
56
|
+
"scad_render_to_file",
|
|
57
|
+
"scale",
|
|
58
|
+
"sphere",
|
|
59
|
+
"square",
|
|
60
|
+
"text",
|
|
61
|
+
"translate",
|
|
62
|
+
"union",
|
|
63
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared helpers for the SolidPython -> build123d bridge."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Sequence
|
|
4
|
+
from functools import reduce
|
|
5
|
+
from operator import add
|
|
6
|
+
|
|
7
|
+
from build123d import Shape
|
|
8
|
+
|
|
9
|
+
Vec3 = tuple[float, float, float]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def vec3(v: float | Sequence[float], default: float = 0.0) -> Vec3:
|
|
13
|
+
"""Expand an OpenSCAD-style scalar or vector into an (x, y, z) tuple."""
|
|
14
|
+
if isinstance(v, (int, float)):
|
|
15
|
+
return (float(v), float(v), float(v))
|
|
16
|
+
vals = [float(x) for x in v]
|
|
17
|
+
while len(vals) < 3:
|
|
18
|
+
vals.append(default)
|
|
19
|
+
return (vals[0], vals[1], vals[2])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def flatten(children: Iterable[object]) -> list[Shape]:
|
|
23
|
+
"""Flatten nested lists/tuples of shapes (SolidPython allows both
|
|
24
|
+
``op()(a, b)`` and ``op()([a, b])``)."""
|
|
25
|
+
out: list[Shape] = []
|
|
26
|
+
for child in children:
|
|
27
|
+
if isinstance(child, (list, tuple)):
|
|
28
|
+
out.extend(flatten(child))
|
|
29
|
+
elif child is not None:
|
|
30
|
+
out.append(child)
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def group(children: Iterable[object]) -> Shape:
|
|
35
|
+
"""Combine children the way an OpenSCAD block does: implicit union."""
|
|
36
|
+
shapes = flatten(children)
|
|
37
|
+
if not shapes:
|
|
38
|
+
raise ValueError("expected at least one shape")
|
|
39
|
+
if len(shapes) == 1:
|
|
40
|
+
return shapes[0]
|
|
41
|
+
return reduce(add, shapes)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""OpenSCAD boolean operations: ``union()(a, b)``, ``difference()(a, b)``.
|
|
2
|
+
|
|
3
|
+
Note that because primitives are native build123d shapes, the algebra
|
|
4
|
+
operators also work directly: ``a + b`` (union), ``a - b`` (difference),
|
|
5
|
+
``a & b`` (intersection). SolidPython's ``a * b`` intersection operator
|
|
6
|
+
is NOT available — use ``a & b`` or ``intersection()(a, b)``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from functools import reduce
|
|
11
|
+
from operator import and_, sub
|
|
12
|
+
|
|
13
|
+
from build123d import Shape
|
|
14
|
+
|
|
15
|
+
from ._common import flatten, group
|
|
16
|
+
|
|
17
|
+
Applier = Callable[..., Shape]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def union() -> Applier:
|
|
21
|
+
def apply(*children: Shape) -> Shape:
|
|
22
|
+
return group(children)
|
|
23
|
+
|
|
24
|
+
return apply
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def difference() -> Applier:
|
|
28
|
+
def apply(*children: Shape) -> Shape:
|
|
29
|
+
shapes = flatten(children)
|
|
30
|
+
if not shapes:
|
|
31
|
+
raise ValueError("difference() requires at least one shape")
|
|
32
|
+
return reduce(sub, shapes)
|
|
33
|
+
|
|
34
|
+
return apply
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def intersection() -> Applier:
|
|
38
|
+
def apply(*children: Shape) -> Shape:
|
|
39
|
+
shapes = flatten(children)
|
|
40
|
+
if not shapes:
|
|
41
|
+
raise ValueError("intersection() requires at least one shape")
|
|
42
|
+
return reduce(and_, shapes)
|
|
43
|
+
|
|
44
|
+
return apply
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def hull() -> Applier:
|
|
48
|
+
def apply(*children: Shape) -> Shape:
|
|
49
|
+
raise NotImplementedError(
|
|
50
|
+
"hull() has no direct build123d equivalent; model the shape "
|
|
51
|
+
"explicitly (e.g. loft/sweep) instead"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return apply
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def minkowski() -> Applier:
|
|
58
|
+
def apply(*children: Shape) -> Shape:
|
|
59
|
+
raise NotImplementedError(
|
|
60
|
+
"minkowski() has no build123d equivalent; use offset() or "
|
|
61
|
+
"fillet/chamfer on the build123d object instead"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return apply
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""OpenSCAD 2D -> 3D operations: linear_extrude and rotate_extrude."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from build123d import Axis, Plane, Pos, Shape
|
|
6
|
+
from build123d import extrude as _bd_extrude
|
|
7
|
+
from build123d import loft as _bd_loft
|
|
8
|
+
from build123d import revolve as _bd_revolve
|
|
9
|
+
from build123d import scale as _bd_scale
|
|
10
|
+
|
|
11
|
+
from ._common import group, vec3
|
|
12
|
+
|
|
13
|
+
Applier = Callable[..., Shape]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def linear_extrude(
|
|
17
|
+
height: float = 100,
|
|
18
|
+
center: bool = False,
|
|
19
|
+
convexity: int | None = None,
|
|
20
|
+
twist: float = 0,
|
|
21
|
+
slices: int | None = None,
|
|
22
|
+
scale: float | tuple[float, float] = 1.0,
|
|
23
|
+
segments: int | None = None,
|
|
24
|
+
) -> Applier:
|
|
25
|
+
if twist:
|
|
26
|
+
raise NotImplementedError(
|
|
27
|
+
"linear_extrude(twist=...) is not supported; use build123d "
|
|
28
|
+
"sweep() along a helix instead"
|
|
29
|
+
)
|
|
30
|
+
scale_xy = (
|
|
31
|
+
(float(scale), float(scale))
|
|
32
|
+
if isinstance(scale, (int, float))
|
|
33
|
+
else (float(scale[0]), float(scale[1]))
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def apply(*children: Shape) -> Shape:
|
|
37
|
+
face = group(children)
|
|
38
|
+
if scale_xy == (1.0, 1.0):
|
|
39
|
+
solid = _bd_extrude(face, amount=height)
|
|
40
|
+
else:
|
|
41
|
+
top = _bd_scale(face, by=(scale_xy[0], scale_xy[1], 1.0))
|
|
42
|
+
solid = _bd_loft([face, Pos(0, 0, height) * top])
|
|
43
|
+
if center:
|
|
44
|
+
solid = Pos(0, 0, -height / 2) * solid
|
|
45
|
+
return solid
|
|
46
|
+
|
|
47
|
+
return apply
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def rotate_extrude(
|
|
51
|
+
angle: float = 360,
|
|
52
|
+
convexity: int | None = None,
|
|
53
|
+
segments: int | None = None,
|
|
54
|
+
) -> Applier:
|
|
55
|
+
def apply(*children: Shape) -> Shape:
|
|
56
|
+
# OpenSCAD takes an XY profile (x >= 0) and spins it about Z;
|
|
57
|
+
# map the profile onto the XZ plane, then revolve.
|
|
58
|
+
profile = Plane.XZ * group(children)
|
|
59
|
+
return _bd_revolve(profile, axis=Axis.Z, revolution_arc=angle)
|
|
60
|
+
|
|
61
|
+
return apply
|