3d-review 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.
Files changed (37) hide show
  1. 3d_review-0.1.0/LICENSE +21 -0
  2. 3d_review-0.1.0/PKG-INFO +134 -0
  3. 3d_review-0.1.0/README.md +110 -0
  4. 3d_review-0.1.0/pyproject.toml +31 -0
  5. 3d_review-0.1.0/setup.cfg +4 -0
  6. 3d_review-0.1.0/src/3d_review.egg-info/PKG-INFO +134 -0
  7. 3d_review-0.1.0/src/3d_review.egg-info/SOURCES.txt +35 -0
  8. 3d_review-0.1.0/src/3d_review.egg-info/dependency_links.txt +1 -0
  9. 3d_review-0.1.0/src/3d_review.egg-info/entry_points.txt +2 -0
  10. 3d_review-0.1.0/src/3d_review.egg-info/requires.txt +13 -0
  11. 3d_review-0.1.0/src/3d_review.egg-info/top_level.txt +1 -0
  12. 3d_review-0.1.0/src/review3d/__init__.py +2 -0
  13. 3d_review-0.1.0/src/review3d/bench/__init__.py +10 -0
  14. 3d_review-0.1.0/src/review3d/bench/bench.example.toml +6 -0
  15. 3d_review-0.1.0/src/review3d/bench/notify.py +22 -0
  16. 3d_review-0.1.0/src/review3d/bench/server.py +256 -0
  17. 3d_review-0.1.0/src/review3d/bench/settings.py +32 -0
  18. 3d_review-0.1.0/src/review3d/bench/static/threads.html +195 -0
  19. 3d_review-0.1.0/src/review3d/bench/static/twist.html +652 -0
  20. 3d_review-0.1.0/src/review3d/bench/static/vendor/LICENSE-three.js.txt +23 -0
  21. 3d_review-0.1.0/src/review3d/bench/static/vendor/jsm/controls/OrbitControls.js +1523 -0
  22. 3d_review-0.1.0/src/review3d/bench/static/vendor/jsm/controls/TransformControls.js +1608 -0
  23. 3d_review-0.1.0/src/review3d/bench/static/vendor/jsm/loaders/GLTFLoader.js +4725 -0
  24. 3d_review-0.1.0/src/review3d/bench/static/vendor/jsm/utils/BufferGeometryUtils.js +1373 -0
  25. 3d_review-0.1.0/src/review3d/bench/static/vendor/three.module.js +54155 -0
  26. 3d_review-0.1.0/src/review3d/bench/thread.py +211 -0
  27. 3d_review-0.1.0/src/review3d/board.py +935 -0
  28. 3d_review-0.1.0/src/review3d/cli.py +87 -0
  29. 3d_review-0.1.0/src/review3d/ledger.py +251 -0
  30. 3d_review-0.1.0/src/review3d/penpot.py +763 -0
  31. 3d_review-0.1.0/src/review3d/skill/SKILL.md +312 -0
  32. 3d_review-0.1.0/src/review3d/stages.py +131 -0
  33. 3d_review-0.1.0/tests/test_bench.py +155 -0
  34. 3d_review-0.1.0/tests/test_board.py +322 -0
  35. 3d_review-0.1.0/tests/test_cli.py +65 -0
  36. 3d_review-0.1.0/tests/test_ledger.py +131 -0
  37. 3d_review-0.1.0/tests/test_penpot.py +494 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konstantinos Georgiou
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,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: 3d-review
3
+ Version: 0.1.0
4
+ Summary: Review a 3D design with an assistant: a web bench where a person marks what is wrong on the model, a ledger of which build step moved what, a requirement board whose rows are proven able to fail, and a Penpot bridge
5
+ Author: Konstantinos Georgiou
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drkostas/3d-review
8
+ Keywords: 3d,design review,3d-printing,trimesh,penpot,assistant
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: 3d-base>=0.2.0
15
+ Requires-Dist: numpy>=1.24
16
+ Requires-Dist: trimesh>=4.0
17
+ Requires-Dist: scipy>=1.10
18
+ Requires-Dist: tomli>=2; python_version < "3.11"
19
+ Provides-Extra: penpot
20
+ Requires-Dist: shapely>=2.0; extra == "penpot"
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8; extra == "test"
23
+ Dynamic: license-file
24
+
25
+ # 3d-review
26
+
27
+ 3d-review is what I used to review a 3D design together with an AI assistant, while designing a 3D-printed toy. The person shows what is wrong on the model itself, the assistant answers with pictures, and the assistant has to prove that what the person asked for is still true after every rebuild.
28
+
29
+ ![The review bench with three parts loaded](docs/bench.png)
30
+
31
+ It has four parts, and each one can be used alone.
32
+
33
+ - **The bench** is a web page that shows the model's stages. The person picks a part, moves or turns a ghost of it, marks a spot, writes what is wrong and submits. Each submission keeps the before and after pictures, the exact transform and the words, and becomes a thread the assistant answers. Replies are drawn from the person's own camera. "Publish all" notifies the assistant once for everything new.
34
+ - **The ledger** records which step of a build pipeline moved or rebuilt a part the person cares about, including code that runs between two steps. A placement the person locked stays locked, or the ledger says which line moved it.
35
+ - **The board** has one row per thing the person asked for, each with a measurement and a bar. A planted fault proves each row can go red, so a green board is not a green check over a hole.
36
+ - **The Penpot bridge** pushes 2D outlines in millimetres into a self-hosted Penpot file and reads the person's edits back.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install 3d-review
42
+ pip install "3d-review[penpot]" # adds shapely, for cutting outlines in the Penpot bridge
43
+ ```
44
+
45
+ In Python it is imported as `review3d`. It uses [3d-base](https://github.com/drkostas/3d-base) for pictures.
46
+
47
+ ## The bench
48
+
49
+ ```bash
50
+ 3d-review stages body.stl arm.stl wheel.stl -o model # writes model/stages.json and model/stages/parts.glb
51
+ cp "$(python -c 'import review3d,os;print(os.path.dirname(review3d.__file__))')/bench/bench.example.toml" model/bench.toml
52
+ 3d-review bench --config model/bench.toml --port 8766
53
+ ```
54
+
55
+ Then open `http://localhost:8766/twist.html`. `threads.html` lists every thread. From Python, `review3d.stages.write_stages` writes any number of stages (for example "parts", "assembled" and "printed") from `{stage: {part: trimesh.Trimesh}}`, and `review3d.bench.server.Bench` takes your own notifier, rebuild hook and freshness check. The default notifier appends a line to a file, so the assistant can watch that file.
56
+
57
+ The bench never changes the model. A submission is a request, and applying it is a separate step.
58
+
59
+ ## The ledger
60
+
61
+ ```python
62
+ from review3d import ledger
63
+ import mybuild
64
+
65
+ ledger.install(mybuild, watch={"arm", "wheel"}, pipeline="build")
66
+ mybuild.build()
67
+ print(ledger.missing(mybuild, "build")) # steps the pipeline calls that the ledger does not wrap
68
+ ledger.write("ledger.json")
69
+ ```
70
+
71
+ The steps are read from the pipeline's own source, so a step added later is never invisible to it. Moves are measured exactly (the rigid transform between the vertices before and after), and a part whose vertex count changed is reported as rebuilt.
72
+
73
+ ## The board
74
+
75
+ ```python
76
+ import types
77
+ import trimesh
78
+ from review3d.board import Board, Row, at_most, mirror_asymmetry
79
+
80
+ design = types.SimpleNamespace(body=trimesh.load("part.stl")) # any object your rows can read
81
+
82
+ board = Board([Row("symmetric", "the two sides match", lambda d: mirror_asymmetry(d.body, normal=(1, 0, 0)), at_most(0.2), "mm")])
83
+
84
+ @board.fault("symmetric")
85
+ def bump(design):
86
+ lump = trimesh.creation.box(extents=(2, 2, 2))
87
+ lump.apply_translation([6, 0, 0])
88
+ design.body = trimesh.util.concatenate([design.body, lump])
89
+
90
+ board.report(design)
91
+ print(board.proof_table(board.prove(design)))
92
+ ```
93
+
94
+ A row whose measurement fails or has no input reads unknown, never green. A row with no fault reads unproven. The measurements include mirror symmetry, sphericity, surface roughness, gap and penetration between parts, tipping, first-layer area, overhang share, length along an axis, axis angle, bow, out of round, tunnels, and the brightness of a rendered view.
95
+
96
+ ## The Penpot bridge
97
+
98
+ ```python
99
+ from review3d.penpot import PenpotClient, PenpotSettings
100
+
101
+ client = PenpotClient(PenpotSettings.load("penpot.toml")) # or PENPOT_* environment variables
102
+ result = client.push({"plate": [(0, 0), (60, 0), (60, 20), (0, 20)]})
103
+ edited = client.pull(file_id=result.file_id)
104
+ ```
105
+
106
+ One Penpot unit is one millimetre. Keep credentials in the settings file or the environment, never in code.
107
+
108
+ ## Claude Code skill
109
+
110
+ ```bash
111
+ 3d-review skill # copies it to ~/.claude/skills/3d-review
112
+ ```
113
+
114
+ The skill is the review loop as we ran it. It covers writing the person's requirements as rows before building, proving each row, answering threads with pictures, keeping locks through rebuilds, asking questions with pictures on the bench instead of in words, and the failures that taught each rule.
115
+
116
+ ## Limits
117
+
118
+ - The bench is meant for one person and one assistant on a trusted network. It has no login, so keep it on `127.0.0.1` or behind a private network.
119
+ - The board's measurements are general. The rows that matter for your design are yours to write.
120
+ - The bench includes three.js r169 (MIT, licence in `bench/static/vendor`).
121
+ - Reading through Penpot's exporter depends on the exporter service working. The default `pull` reads the stored geometry and does not need it.
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ python -m venv .venv && .venv/bin/pip install -e '.[test,penpot]'
127
+ .venv/bin/pytest
128
+ ```
129
+
130
+ The Penpot tests use a fake server. Set `PENPOT_URL` and a token to run the live round trip too.
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,110 @@
1
+ # 3d-review
2
+
3
+ 3d-review is what I used to review a 3D design together with an AI assistant, while designing a 3D-printed toy. The person shows what is wrong on the model itself, the assistant answers with pictures, and the assistant has to prove that what the person asked for is still true after every rebuild.
4
+
5
+ ![The review bench with three parts loaded](docs/bench.png)
6
+
7
+ It has four parts, and each one can be used alone.
8
+
9
+ - **The bench** is a web page that shows the model's stages. The person picks a part, moves or turns a ghost of it, marks a spot, writes what is wrong and submits. Each submission keeps the before and after pictures, the exact transform and the words, and becomes a thread the assistant answers. Replies are drawn from the person's own camera. "Publish all" notifies the assistant once for everything new.
10
+ - **The ledger** records which step of a build pipeline moved or rebuilt a part the person cares about, including code that runs between two steps. A placement the person locked stays locked, or the ledger says which line moved it.
11
+ - **The board** has one row per thing the person asked for, each with a measurement and a bar. A planted fault proves each row can go red, so a green board is not a green check over a hole.
12
+ - **The Penpot bridge** pushes 2D outlines in millimetres into a self-hosted Penpot file and reads the person's edits back.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install 3d-review
18
+ pip install "3d-review[penpot]" # adds shapely, for cutting outlines in the Penpot bridge
19
+ ```
20
+
21
+ In Python it is imported as `review3d`. It uses [3d-base](https://github.com/drkostas/3d-base) for pictures.
22
+
23
+ ## The bench
24
+
25
+ ```bash
26
+ 3d-review stages body.stl arm.stl wheel.stl -o model # writes model/stages.json and model/stages/parts.glb
27
+ cp "$(python -c 'import review3d,os;print(os.path.dirname(review3d.__file__))')/bench/bench.example.toml" model/bench.toml
28
+ 3d-review bench --config model/bench.toml --port 8766
29
+ ```
30
+
31
+ Then open `http://localhost:8766/twist.html`. `threads.html` lists every thread. From Python, `review3d.stages.write_stages` writes any number of stages (for example "parts", "assembled" and "printed") from `{stage: {part: trimesh.Trimesh}}`, and `review3d.bench.server.Bench` takes your own notifier, rebuild hook and freshness check. The default notifier appends a line to a file, so the assistant can watch that file.
32
+
33
+ The bench never changes the model. A submission is a request, and applying it is a separate step.
34
+
35
+ ## The ledger
36
+
37
+ ```python
38
+ from review3d import ledger
39
+ import mybuild
40
+
41
+ ledger.install(mybuild, watch={"arm", "wheel"}, pipeline="build")
42
+ mybuild.build()
43
+ print(ledger.missing(mybuild, "build")) # steps the pipeline calls that the ledger does not wrap
44
+ ledger.write("ledger.json")
45
+ ```
46
+
47
+ The steps are read from the pipeline's own source, so a step added later is never invisible to it. Moves are measured exactly (the rigid transform between the vertices before and after), and a part whose vertex count changed is reported as rebuilt.
48
+
49
+ ## The board
50
+
51
+ ```python
52
+ import types
53
+ import trimesh
54
+ from review3d.board import Board, Row, at_most, mirror_asymmetry
55
+
56
+ design = types.SimpleNamespace(body=trimesh.load("part.stl")) # any object your rows can read
57
+
58
+ board = Board([Row("symmetric", "the two sides match", lambda d: mirror_asymmetry(d.body, normal=(1, 0, 0)), at_most(0.2), "mm")])
59
+
60
+ @board.fault("symmetric")
61
+ def bump(design):
62
+ lump = trimesh.creation.box(extents=(2, 2, 2))
63
+ lump.apply_translation([6, 0, 0])
64
+ design.body = trimesh.util.concatenate([design.body, lump])
65
+
66
+ board.report(design)
67
+ print(board.proof_table(board.prove(design)))
68
+ ```
69
+
70
+ A row whose measurement fails or has no input reads unknown, never green. A row with no fault reads unproven. The measurements include mirror symmetry, sphericity, surface roughness, gap and penetration between parts, tipping, first-layer area, overhang share, length along an axis, axis angle, bow, out of round, tunnels, and the brightness of a rendered view.
71
+
72
+ ## The Penpot bridge
73
+
74
+ ```python
75
+ from review3d.penpot import PenpotClient, PenpotSettings
76
+
77
+ client = PenpotClient(PenpotSettings.load("penpot.toml")) # or PENPOT_* environment variables
78
+ result = client.push({"plate": [(0, 0), (60, 0), (60, 20), (0, 20)]})
79
+ edited = client.pull(file_id=result.file_id)
80
+ ```
81
+
82
+ One Penpot unit is one millimetre. Keep credentials in the settings file or the environment, never in code.
83
+
84
+ ## Claude Code skill
85
+
86
+ ```bash
87
+ 3d-review skill # copies it to ~/.claude/skills/3d-review
88
+ ```
89
+
90
+ The skill is the review loop as we ran it. It covers writing the person's requirements as rows before building, proving each row, answering threads with pictures, keeping locks through rebuilds, asking questions with pictures on the bench instead of in words, and the failures that taught each rule.
91
+
92
+ ## Limits
93
+
94
+ - The bench is meant for one person and one assistant on a trusted network. It has no login, so keep it on `127.0.0.1` or behind a private network.
95
+ - The board's measurements are general. The rows that matter for your design are yours to write.
96
+ - The bench includes three.js r169 (MIT, licence in `bench/static/vendor`).
97
+ - Reading through Penpot's exporter depends on the exporter service working. The default `pull` reads the stored geometry and does not need it.
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ python -m venv .venv && .venv/bin/pip install -e '.[test,penpot]'
103
+ .venv/bin/pytest
104
+ ```
105
+
106
+ The Penpot tests use a fake server. Set `PENPOT_URL` and a token to run the live round trip too.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "3d-review"
7
+ version = "0.1.0"
8
+ description = "Review a 3D design with an assistant: a web bench where a person marks what is wrong on the model, a ledger of which build step moved what, a requirement board whose rows are proven able to fail, and a Penpot bridge"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "Konstantinos Georgiou" }]
13
+ dependencies = ["3d-base>=0.2.0", "numpy>=1.24", "trimesh>=4.0", "scipy>=1.10", "tomli>=2; python_version < '3.11'"]
14
+ keywords = ["3d", "design review", "3d-printing", "trimesh", "penpot", "assistant"]
15
+ classifiers = ["Programming Language :: Python :: 3", "Topic :: Multimedia :: Graphics :: 3D Modeling"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/drkostas/3d-review"
19
+
20
+ [project.scripts]
21
+ 3d-review = "review3d.cli:main"
22
+
23
+ [project.optional-dependencies]
24
+ penpot = ["shapely>=2.0"]
25
+ test = ["pytest>=8"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.setuptools.package-data]
31
+ review3d = ["skill/SKILL.md", "bench/static/*.html", "bench/static/vendor/**/*", "bench/bench.example.toml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: 3d-review
3
+ Version: 0.1.0
4
+ Summary: Review a 3D design with an assistant: a web bench where a person marks what is wrong on the model, a ledger of which build step moved what, a requirement board whose rows are proven able to fail, and a Penpot bridge
5
+ Author: Konstantinos Georgiou
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drkostas/3d-review
8
+ Keywords: 3d,design review,3d-printing,trimesh,penpot,assistant
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: 3d-base>=0.2.0
15
+ Requires-Dist: numpy>=1.24
16
+ Requires-Dist: trimesh>=4.0
17
+ Requires-Dist: scipy>=1.10
18
+ Requires-Dist: tomli>=2; python_version < "3.11"
19
+ Provides-Extra: penpot
20
+ Requires-Dist: shapely>=2.0; extra == "penpot"
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8; extra == "test"
23
+ Dynamic: license-file
24
+
25
+ # 3d-review
26
+
27
+ 3d-review is what I used to review a 3D design together with an AI assistant, while designing a 3D-printed toy. The person shows what is wrong on the model itself, the assistant answers with pictures, and the assistant has to prove that what the person asked for is still true after every rebuild.
28
+
29
+ ![The review bench with three parts loaded](docs/bench.png)
30
+
31
+ It has four parts, and each one can be used alone.
32
+
33
+ - **The bench** is a web page that shows the model's stages. The person picks a part, moves or turns a ghost of it, marks a spot, writes what is wrong and submits. Each submission keeps the before and after pictures, the exact transform and the words, and becomes a thread the assistant answers. Replies are drawn from the person's own camera. "Publish all" notifies the assistant once for everything new.
34
+ - **The ledger** records which step of a build pipeline moved or rebuilt a part the person cares about, including code that runs between two steps. A placement the person locked stays locked, or the ledger says which line moved it.
35
+ - **The board** has one row per thing the person asked for, each with a measurement and a bar. A planted fault proves each row can go red, so a green board is not a green check over a hole.
36
+ - **The Penpot bridge** pushes 2D outlines in millimetres into a self-hosted Penpot file and reads the person's edits back.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install 3d-review
42
+ pip install "3d-review[penpot]" # adds shapely, for cutting outlines in the Penpot bridge
43
+ ```
44
+
45
+ In Python it is imported as `review3d`. It uses [3d-base](https://github.com/drkostas/3d-base) for pictures.
46
+
47
+ ## The bench
48
+
49
+ ```bash
50
+ 3d-review stages body.stl arm.stl wheel.stl -o model # writes model/stages.json and model/stages/parts.glb
51
+ cp "$(python -c 'import review3d,os;print(os.path.dirname(review3d.__file__))')/bench/bench.example.toml" model/bench.toml
52
+ 3d-review bench --config model/bench.toml --port 8766
53
+ ```
54
+
55
+ Then open `http://localhost:8766/twist.html`. `threads.html` lists every thread. From Python, `review3d.stages.write_stages` writes any number of stages (for example "parts", "assembled" and "printed") from `{stage: {part: trimesh.Trimesh}}`, and `review3d.bench.server.Bench` takes your own notifier, rebuild hook and freshness check. The default notifier appends a line to a file, so the assistant can watch that file.
56
+
57
+ The bench never changes the model. A submission is a request, and applying it is a separate step.
58
+
59
+ ## The ledger
60
+
61
+ ```python
62
+ from review3d import ledger
63
+ import mybuild
64
+
65
+ ledger.install(mybuild, watch={"arm", "wheel"}, pipeline="build")
66
+ mybuild.build()
67
+ print(ledger.missing(mybuild, "build")) # steps the pipeline calls that the ledger does not wrap
68
+ ledger.write("ledger.json")
69
+ ```
70
+
71
+ The steps are read from the pipeline's own source, so a step added later is never invisible to it. Moves are measured exactly (the rigid transform between the vertices before and after), and a part whose vertex count changed is reported as rebuilt.
72
+
73
+ ## The board
74
+
75
+ ```python
76
+ import types
77
+ import trimesh
78
+ from review3d.board import Board, Row, at_most, mirror_asymmetry
79
+
80
+ design = types.SimpleNamespace(body=trimesh.load("part.stl")) # any object your rows can read
81
+
82
+ board = Board([Row("symmetric", "the two sides match", lambda d: mirror_asymmetry(d.body, normal=(1, 0, 0)), at_most(0.2), "mm")])
83
+
84
+ @board.fault("symmetric")
85
+ def bump(design):
86
+ lump = trimesh.creation.box(extents=(2, 2, 2))
87
+ lump.apply_translation([6, 0, 0])
88
+ design.body = trimesh.util.concatenate([design.body, lump])
89
+
90
+ board.report(design)
91
+ print(board.proof_table(board.prove(design)))
92
+ ```
93
+
94
+ A row whose measurement fails or has no input reads unknown, never green. A row with no fault reads unproven. The measurements include mirror symmetry, sphericity, surface roughness, gap and penetration between parts, tipping, first-layer area, overhang share, length along an axis, axis angle, bow, out of round, tunnels, and the brightness of a rendered view.
95
+
96
+ ## The Penpot bridge
97
+
98
+ ```python
99
+ from review3d.penpot import PenpotClient, PenpotSettings
100
+
101
+ client = PenpotClient(PenpotSettings.load("penpot.toml")) # or PENPOT_* environment variables
102
+ result = client.push({"plate": [(0, 0), (60, 0), (60, 20), (0, 20)]})
103
+ edited = client.pull(file_id=result.file_id)
104
+ ```
105
+
106
+ One Penpot unit is one millimetre. Keep credentials in the settings file or the environment, never in code.
107
+
108
+ ## Claude Code skill
109
+
110
+ ```bash
111
+ 3d-review skill # copies it to ~/.claude/skills/3d-review
112
+ ```
113
+
114
+ The skill is the review loop as we ran it. It covers writing the person's requirements as rows before building, proving each row, answering threads with pictures, keeping locks through rebuilds, asking questions with pictures on the bench instead of in words, and the failures that taught each rule.
115
+
116
+ ## Limits
117
+
118
+ - The bench is meant for one person and one assistant on a trusted network. It has no login, so keep it on `127.0.0.1` or behind a private network.
119
+ - The board's measurements are general. The rows that matter for your design are yours to write.
120
+ - The bench includes three.js r169 (MIT, licence in `bench/static/vendor`).
121
+ - Reading through Penpot's exporter depends on the exporter service working. The default `pull` reads the stored geometry and does not need it.
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ python -m venv .venv && .venv/bin/pip install -e '.[test,penpot]'
127
+ .venv/bin/pytest
128
+ ```
129
+
130
+ The Penpot tests use a fake server. Set `PENPOT_URL` and a token to run the live round trip too.
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,35 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/3d_review.egg-info/PKG-INFO
5
+ src/3d_review.egg-info/SOURCES.txt
6
+ src/3d_review.egg-info/dependency_links.txt
7
+ src/3d_review.egg-info/entry_points.txt
8
+ src/3d_review.egg-info/requires.txt
9
+ src/3d_review.egg-info/top_level.txt
10
+ src/review3d/__init__.py
11
+ src/review3d/board.py
12
+ src/review3d/cli.py
13
+ src/review3d/ledger.py
14
+ src/review3d/penpot.py
15
+ src/review3d/stages.py
16
+ src/review3d/bench/__init__.py
17
+ src/review3d/bench/bench.example.toml
18
+ src/review3d/bench/notify.py
19
+ src/review3d/bench/server.py
20
+ src/review3d/bench/settings.py
21
+ src/review3d/bench/thread.py
22
+ src/review3d/bench/static/threads.html
23
+ src/review3d/bench/static/twist.html
24
+ src/review3d/bench/static/vendor/LICENSE-three.js.txt
25
+ src/review3d/bench/static/vendor/three.module.js
26
+ src/review3d/bench/static/vendor/jsm/controls/OrbitControls.js
27
+ src/review3d/bench/static/vendor/jsm/controls/TransformControls.js
28
+ src/review3d/bench/static/vendor/jsm/loaders/GLTFLoader.js
29
+ src/review3d/bench/static/vendor/jsm/utils/BufferGeometryUtils.js
30
+ src/review3d/skill/SKILL.md
31
+ tests/test_bench.py
32
+ tests/test_board.py
33
+ tests/test_cli.py
34
+ tests/test_ledger.py
35
+ tests/test_penpot.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ 3d-review = review3d.cli:main
@@ -0,0 +1,13 @@
1
+ 3d-base>=0.2.0
2
+ numpy>=1.24
3
+ trimesh>=4.0
4
+ scipy>=1.10
5
+
6
+ [:python_version < "3.11"]
7
+ tomli>=2
8
+
9
+ [penpot]
10
+ shapely>=2.0
11
+
12
+ [test]
13
+ pytest>=8
@@ -0,0 +1 @@
1
+ review3d
@@ -0,0 +1,2 @@
1
+ """Review a 3D design with an assistant."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,10 @@
1
+ """The review bench, a web page for reviewing a 3D model.
2
+
3
+ A reviewer opens the page, picks a stage of the model, circles a spot or moves a ghost copy of a
4
+ part, writes what is wrong, and submits. Each submission is stored as a comment thread that the
5
+ assistant answers. Replies can carry a picture rendered from the reviewer's own camera.
6
+
7
+ `server.Bench` holds the plug-in points. They are the folders that hold the stage files, the
8
+ folder that holds the threads, the notifier used when the reviewer presses "Publish all", and the
9
+ optional rebuild and freshness hooks.
10
+ """
@@ -0,0 +1,6 @@
1
+ # Copy this file into your project as bench.toml.
2
+ port = 8766
3
+ host = "127.0.0.1"
4
+ model_dirs = ["."]
5
+ subs_dir = "submissions"
6
+ stages_dir = "stages"
@@ -0,0 +1,22 @@
1
+ """Notifiers, which tell the assistant that the reviewer pressed "Publish all".
2
+
3
+ A notifier is any object with a method `notify(text)` that returns `(ok, detail)`. The default
4
+ appends a line to a log file, which works on every machine and needs nothing installed. A project
5
+ with a better channel (a chat it can write into, a message queue) passes its own notifier.
6
+ """
7
+ import datetime as _dt
8
+ import pathlib
9
+
10
+
11
+ class FileNotifier:
12
+ """Append each notification to a log file, one line per notification, with a timestamp."""
13
+
14
+ def __init__(self, path):
15
+ self.path = pathlib.Path(path)
16
+
17
+ def notify(self, text):
18
+ self.path.parent.mkdir(parents=True, exist_ok=True)
19
+ stamp = _dt.datetime.now().astimezone().isoformat(timespec="seconds")
20
+ with self.path.open("a", encoding="utf-8") as f:
21
+ f.write(f"{stamp} {text}\n")
22
+ return True, f"written to {self.path}"