bevel-cad 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. bevel_cad-0.1.0/.gitignore +12 -0
  2. bevel_cad-0.1.0/CHANGELOG.md +39 -0
  3. bevel_cad-0.1.0/LICENSE +21 -0
  4. bevel_cad-0.1.0/PKG-INFO +153 -0
  5. bevel_cad-0.1.0/README.md +114 -0
  6. bevel_cad-0.1.0/docs/cli.md +37 -0
  7. bevel_cad-0.1.0/docs/config.md +92 -0
  8. bevel_cad-0.1.0/docs/mcp.md +36 -0
  9. bevel_cad-0.1.0/docs/parts.md +39 -0
  10. bevel_cad-0.1.0/docs/project-layout.md +19 -0
  11. bevel_cad-0.1.0/docs/releasing.md +99 -0
  12. bevel_cad-0.1.0/docs/skills.md +18 -0
  13. bevel_cad-0.1.0/docs/viewer.md +24 -0
  14. bevel_cad-0.1.0/examples/README.md +23 -0
  15. bevel_cad-0.1.0/examples/bevel.yaml +25 -0
  16. bevel_cad-0.1.0/examples/configs/button_label.yaml +4 -0
  17. bevel_cad-0.1.0/examples/configs/button_label_custom.yaml +9 -0
  18. bevel_cad-0.1.0/examples/configs/spacer_washer.yaml +9 -0
  19. bevel_cad-0.1.0/examples/src/button_label.py +226 -0
  20. bevel_cad-0.1.0/examples/src/spacer_washer.py +57 -0
  21. bevel_cad-0.1.0/pyproject.toml +118 -0
  22. bevel_cad-0.1.0/src/bevel_cad/__init__.py +31 -0
  23. bevel_cad-0.1.0/src/bevel_cad/__main__.py +3 -0
  24. bevel_cad-0.1.0/src/bevel_cad/_version.py +24 -0
  25. bevel_cad-0.1.0/src/bevel_cad/cli/__init__.py +5 -0
  26. bevel_cad-0.1.0/src/bevel_cad/cli/create.py +142 -0
  27. bevel_cad-0.1.0/src/bevel_cad/cli/main.py +342 -0
  28. bevel_cad-0.1.0/src/bevel_cad/cli/mcp.py +36 -0
  29. bevel_cad-0.1.0/src/bevel_cad/cli/skills.py +60 -0
  30. bevel_cad-0.1.0/src/bevel_cad/commands.py +416 -0
  31. bevel_cad-0.1.0/src/bevel_cad/config/__init__.py +54 -0
  32. bevel_cad-0.1.0/src/bevel_cad/config/defaults.yaml +58 -0
  33. bevel_cad-0.1.0/src/bevel_cad/config/loader.py +280 -0
  34. bevel_cad-0.1.0/src/bevel_cad/config/paths.py +105 -0
  35. bevel_cad-0.1.0/src/bevel_cad/config/schema.py +159 -0
  36. bevel_cad-0.1.0/src/bevel_cad/geom/__init__.py +1 -0
  37. bevel_cad-0.1.0/src/bevel_cad/geom/text_plate.py +170 -0
  38. bevel_cad-0.1.0/src/bevel_cad/mcp/__init__.py +5 -0
  39. bevel_cad-0.1.0/src/bevel_cad/mcp/server.py +376 -0
  40. bevel_cad-0.1.0/src/bevel_cad/mesh/__init__.py +1 -0
  41. bevel_cad-0.1.0/src/bevel_cad/mesh/convert.py +61 -0
  42. bevel_cad-0.1.0/src/bevel_cad/mesh/fuse.py +150 -0
  43. bevel_cad-0.1.0/src/bevel_cad/mesh/inspect.py +165 -0
  44. bevel_cad-0.1.0/src/bevel_cad/mesh/validate.py +38 -0
  45. bevel_cad-0.1.0/src/bevel_cad/parts.py +322 -0
  46. bevel_cad-0.1.0/src/bevel_cad/py.typed +0 -0
  47. bevel_cad-0.1.0/src/bevel_cad/render/__init__.py +46 -0
  48. bevel_cad-0.1.0/src/bevel_cad/render/bundle.py +65 -0
  49. bevel_cad-0.1.0/src/bevel_cad/render/colors.py +87 -0
  50. bevel_cad-0.1.0/src/bevel_cad/render/logbuffer.py +73 -0
  51. bevel_cad-0.1.0/src/bevel_cad/render/naming.py +44 -0
  52. bevel_cad-0.1.0/src/bevel_cad/render/pipeline.py +498 -0
  53. bevel_cad-0.1.0/src/bevel_cad/render/planner.py +224 -0
  54. bevel_cad-0.1.0/src/bevel_cad/render/preview.py +391 -0
  55. bevel_cad-0.1.0/src/bevel_cad/render/stats.py +143 -0
  56. bevel_cad-0.1.0/src/bevel_cad/scaffold.py +246 -0
  57. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-mcp-workflow/SKILL.md +59 -0
  58. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-model-iteration/SKILL.md +63 -0
  59. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/SKILL.md +94 -0
  60. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/references/cadquery-pitfalls.md +48 -0
  61. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/references/part-skeleton.py +28 -0
  62. bevel_cad-0.1.0/src/bevel_cad/skills/bevel-render-verify/SKILL.md +77 -0
  63. bevel_cad-0.1.0/src/bevel_cad/templates/basic/config.yaml.tmpl +9 -0
  64. bevel_cad-0.1.0/src/bevel_cad/templates/basic/part.py.tmpl +42 -0
  65. bevel_cad-0.1.0/src/bevel_cad/templates/basic/template.yaml +3 -0
  66. bevel_cad-0.1.0/src/bevel_cad/templates/label/config.yaml.tmpl +16 -0
  67. bevel_cad-0.1.0/src/bevel_cad/templates/label/part.py.tmpl +49 -0
  68. bevel_cad-0.1.0/src/bevel_cad/templates/label/template.yaml +6 -0
  69. bevel_cad-0.1.0/src/bevel_cad/templates/project/README.md.tmpl +30 -0
  70. bevel_cad-0.1.0/src/bevel_cad/templates/project/bevel.yaml.tmpl +29 -0
  71. bevel_cad-0.1.0/src/bevel_cad/templates/project/gitignore.tmpl +5 -0
  72. bevel_cad-0.1.0/src/bevel_cad/viewer.py +176 -0
  73. bevel_cad-0.1.0/tests/__init__.py +0 -0
  74. bevel_cad-0.1.0/tests/conftest.py +71 -0
  75. bevel_cad-0.1.0/tests/fixtures/box_part.py +8 -0
  76. bevel_cad-0.1.0/tests/test_bundle.py +47 -0
  77. bevel_cad-0.1.0/tests/test_button_label.py +352 -0
  78. bevel_cad-0.1.0/tests/test_cli.py +178 -0
  79. bevel_cad-0.1.0/tests/test_color_palette.py +78 -0
  80. bevel_cad-0.1.0/tests/test_config.py +127 -0
  81. bevel_cad-0.1.0/tests/test_examples_project.py +53 -0
  82. bevel_cad-0.1.0/tests/test_fuse_utils.py +79 -0
  83. bevel_cad-0.1.0/tests/test_logbuffer.py +89 -0
  84. bevel_cad-0.1.0/tests/test_mcp.py +165 -0
  85. bevel_cad-0.1.0/tests/test_naming.py +33 -0
  86. bevel_cad-0.1.0/tests/test_parts.py +72 -0
  87. bevel_cad-0.1.0/tests/test_pipeline.py +146 -0
  88. bevel_cad-0.1.0/tests/test_planner.py +84 -0
  89. bevel_cad-0.1.0/tests/test_render_stats.py +70 -0
  90. bevel_cad-0.1.0/tests/test_scaffold.py +115 -0
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ **/renders/*
8
+ !**/renders/.gitkeep
9
+ bevel.local.yaml
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ src/bevel_cad/_version.py
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-09-14
10
+
11
+ ### Added
12
+
13
+ - Render pipeline: `build(cfg)` to a timestamped bundle with STL / STEP / 3MF / GLB / GLTF / OBJ
14
+ exports, preview PNG, config snapshot, stats CSV, and log.
15
+ - Layered OmegaConf configuration (`bevel.yaml`, `bevel.local.yaml`, `configs/<part>.yaml`,
16
+ `-c FILE`, `KEY=VALUE` overrides) with typed `project` / `rendering` / `viewer` blocks.
17
+ - CLI: `render`, `config`, `list`, `describe`, `renders`, `show`, `inspect`, `upload`, `create`,
18
+ `add`, `templates`, `skills`, `mcp`.
19
+ - MCP server (`bevel mcp`) exposing the CLI to AI agents; bundled agent skills.
20
+ - Project scaffolding templates (`basic`, `label`) and a self-contained `examples/` project.
21
+ - Entry points `bevel_cad.parts` / `bevel_cad.providers` for third-party part registries.
22
+
23
+ ### Changed
24
+
25
+ - `requires-python` is now `>=3.11,<3.13` and `cadquery>=2.8` (the code uses
26
+ `cadquery.func.fillet2D`, which first shipped in 2.8; 2.8 needs Python 3.11).
27
+ - The `viewer` extra is removed until `cadquery-web-viewer` 2.2 is published; the current
28
+ 2.1.x release pins an OCP version that cannot coexist with cadquery 2.8.
29
+
30
+ ### CI / packaging
31
+
32
+ - Version is derived from git tags via hatch-vcs; `bevel --version` reports it.
33
+ - GitHub Actions: build + `twine check`, test matrix (Linux 3.11/3.12, macOS 3.12, Windows 3.12),
34
+ ruff, pyright, clean-environment wheel smoke test, weekly CodeQL, Dependabot, Renovate.
35
+ - Releases publish to PyPI with Trusted Publishing (PEP 740 attestations) and attach a
36
+ GitHub build-provenance attestation plus the wheel and sdist to the GitHub Release.
37
+
38
+ [Unreleased]: https://github.com/jimcortez/bevel-cad/compare/v0.1.0...HEAD
39
+ [0.1.0]: https://github.com/jimcortez/bevel-cad/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jim Cortez
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,153 @@
1
+ Metadata-Version: 2.5
2
+ Name: bevel-cad
3
+ Version: 0.1.0
4
+ Summary: Render, export, and configure CadQuery parts: layered YAML config, timestamped render bundles, previews, web viewer, CLI and MCP server.
5
+ Project-URL: Homepage, https://github.com/jimcortez/bevel-cad
6
+ Project-URL: Repository, https://github.com/jimcortez/bevel-cad
7
+ Project-URL: Issues, https://github.com/jimcortez/bevel-cad/issues
8
+ Project-URL: Changelog, https://github.com/jimcortez/bevel-cad/blob/main/CHANGELOG.md
9
+ Author-email: Jim Cortez <jim@jimcortez.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: 3d-printing,cad,cadquery,mcp,step,stl
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Manufacturing
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
21
+ Requires-Python: <3.13,>=3.11
22
+ Requires-Dist: cadquery>=2.8
23
+ Requires-Dist: httpx<1,>=0.28
24
+ Requires-Dist: numpy>=1.26
25
+ Requires-Dist: omegaconf>=2.3
26
+ Requires-Dist: pillow>=10
27
+ Requires-Dist: pyrender>=0.1.45
28
+ Requires-Dist: pyyaml>=6
29
+ Requires-Dist: trimesh>=4.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mcp[cli]<3,>=2; extra == 'dev'
32
+ Requires-Dist: pyright>=1.1.409; extra == 'dev'
33
+ Requires-Dist: pytest-cov; extra == 'dev'
34
+ Requires-Dist: pytest>=8; extra == 'dev'
35
+ Requires-Dist: ruff>=0.15; extra == 'dev'
36
+ Provides-Extra: mcp
37
+ Requires-Dist: mcp<3,>=2; extra == 'mcp'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # bevel-cad
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/bevel-cad.svg)](https://pypi.org/project/bevel-cad/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/bevel-cad.svg)](https://pypi.org/project/bevel-cad/)
44
+ [![License](https://img.shields.io/pypi/l/bevel-cad.svg)](LICENSE)
45
+ [![CI](https://github.com/jimcortez/bevel-cad/actions/workflows/ci.yml/badge.svg)](https://github.com/jimcortez/bevel-cad/actions/workflows/ci.yml)
46
+
47
+ *WARNING: project under active development and not stable at all*
48
+
49
+ Render, export, and configure [CadQuery](https://cadquery.readthedocs.io/) parts.
50
+
51
+ `bevel` turns a `build(cfg)` function into a **render bundle** — a timestamped folder with
52
+ STL / STEP / 3MF / GLB / GLTF / OBJ exports, a preview PNG, the exact config that produced
53
+ it, timings, and a log — and can push the geometry to
54
+ [cadquery-web-viewer](https://github.com/jimcortez/cadquery-web-viewer). Configuration is
55
+ layered YAML (OmegaConf) with `key.path=value` overrides; a project is a `bevel.yaml` plus
56
+ `configs/` and `src/` folders; everything is available as a CLI, a Python API, and an MCP
57
+ server so AI assistants can drive it.
58
+
59
+ ```bash
60
+ pip install "bevel-cad[mcp]" # or: uv add "bevel-cad[mcp]"
61
+
62
+ bevel create my_block --template basic # scaffold a project (interactive if args omitted)
63
+ cd my_block
64
+ bevel render my_block # -> renders/my_block_<YYYYMMDD-HHMMSS>/
65
+ bevel render my_block my_block.cylinder_depth=null --skip preview # override + faster loop
66
+ bevel inspect renders/*/my_block_*.stl # watertight? components? open edges?
67
+ bevel render my_block --viewer # push to a running cadquery-web-viewer
68
+ bevel mcp # expose all of the above to an AI agent
69
+ ```
70
+
71
+ ## A part
72
+
73
+ ```python
74
+ import cadquery as cq
75
+ import bevel_cad
76
+
77
+ @bevel_cad.part(defaults={"widget": {"width": 40.0, "hole_d": 6.0}})
78
+ def build(cfg) -> cq.Workplane:
79
+ p = cfg.widget
80
+ return cq.Workplane("XY").box(p.width, p.width, 5).faces(">Z").workplane().hole(p.hole_d)
81
+ ```
82
+
83
+ Return an `Assembly` with named children to get one STL per body (and per-body colours in
84
+ the viewer). Anything with `.wrapped` (build123d) or a `trimesh.Trimesh` works too.
85
+
86
+ ## Python API
87
+
88
+ ```python
89
+ from bevel_cad import load_config, render_part
90
+
91
+ cfg = load_config(files=["configs/widget.yaml"], dotlist=["widget.hole_d=8"])
92
+ result = render_part(build(cfg), cfg, name="widget-8mm")
93
+ print(result.bundle_dir, result.path_for("stl"), result.extra_paths)
94
+ ```
95
+
96
+ ## Project layout
97
+
98
+ ```
99
+ bevel.yaml project config: how to render (formats, tolerances, viewer)
100
+ bevel.local.yaml personal overrides (git-ignored)
101
+ configs/<part>.yaml per-part config: `part:` + the part's block
102
+ src/<part>.py build(cfg) -> geometry
103
+ renders/<slug>_<ts>/ bundles
104
+ ```
105
+
106
+ Config layers, low to high: built-in defaults → `~/.config/bevel/config.yaml` →
107
+ `bevel.yaml` → `bevel.local.yaml` → part `defaults=` → `-c FILE …` → `KEY=VALUE`.
108
+ The `project`, `rendering`, and `viewer` blocks are typed and validated; everything else is
109
+ free-form. Missing keys raise (no silent `None`); use `cfg.get("key", default)` for optional ones.
110
+
111
+ ## Commands
112
+
113
+ | command | |
114
+ |---|---|
115
+ | `bevel render [TARGET] [-c FILE]… [KEY=VALUE]… [--name N] [--out DIR] [--only F] [--skip F] [--viewer]` | build + bundle |
116
+ | `bevel config [TARGET] …` | print the merged config |
117
+ | `bevel list` / `bevel describe NAME` | discoverable parts and their defaults |
118
+ | `bevel renders` / `bevel show BUNDLE` | previous bundles; files, snapshot, stats, log |
119
+ | `bevel inspect MESH…` | watertight, components, boundary/non-manifold edges, volume |
120
+ | `bevel upload BUNDLE` | re-push a bundle to the viewer |
121
+ | `bevel create` / `bevel add` / `bevel templates` | scaffolding (`basic`, `label`) |
122
+ | `bevel skills list\|install` | agent skills for building/verifying parts |
123
+ | `bevel mcp [--transport stdio\|streamable-http]` | MCP server |
124
+
125
+ Every command takes `--root DIR` and `--json`. `TARGET` is a `.py`/`.yaml` path, a project part
126
+ name, `pkg.module[:fn]`, or a registered name (`bevel_cad.parts` / `bevel_cad.providers` entry points).
127
+ A ready-made project lives in [examples/](examples/).
128
+
129
+ ## Docs
130
+
131
+ [docs/config.md](docs/config.md) · [docs/cli.md](docs/cli.md) · [docs/parts.md](docs/parts.md) ·
132
+ [docs/project-layout.md](docs/project-layout.md) · [docs/viewer.md](docs/viewer.md) ·
133
+ [docs/mcp.md](docs/mcp.md) · [docs/skills.md](docs/skills.md) ·
134
+ [docs/releasing.md](docs/releasing.md)
135
+
136
+ ## Extending
137
+
138
+ Other packages register parts via entry points and can add typed config blocks / CLI flags:
139
+
140
+ ```toml
141
+ [project.entry-points."bevel_cad.parts"]
142
+ clamp = "mypkg.parts.clamp"
143
+ [project.entry-points."bevel_cad.providers"]
144
+ mypkg = "mypkg.registry:bevel_parts" # -> {name: "module.path", ...}
145
+ ```
146
+
147
+ ```python
148
+ from bevel_cad.cli import CliHooks, main
149
+ main(hooks=CliHooks(schema=MySchema, add_render_flags=add_my_flags, prepare_config=wrap_cfg))
150
+ ```
151
+
152
+ MIT licensed. Python 3.11–3.12 (cadquery 2.8 needs 3.11; the viewer pins `<3.13`).
153
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev loop and [docs/releasing.md](docs/releasing.md) for releases.
@@ -0,0 +1,114 @@
1
+ # bevel-cad
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/bevel-cad.svg)](https://pypi.org/project/bevel-cad/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/bevel-cad.svg)](https://pypi.org/project/bevel-cad/)
5
+ [![License](https://img.shields.io/pypi/l/bevel-cad.svg)](LICENSE)
6
+ [![CI](https://github.com/jimcortez/bevel-cad/actions/workflows/ci.yml/badge.svg)](https://github.com/jimcortez/bevel-cad/actions/workflows/ci.yml)
7
+
8
+ *WARNING: project under active development and not stable at all*
9
+
10
+ Render, export, and configure [CadQuery](https://cadquery.readthedocs.io/) parts.
11
+
12
+ `bevel` turns a `build(cfg)` function into a **render bundle** — a timestamped folder with
13
+ STL / STEP / 3MF / GLB / GLTF / OBJ exports, a preview PNG, the exact config that produced
14
+ it, timings, and a log — and can push the geometry to
15
+ [cadquery-web-viewer](https://github.com/jimcortez/cadquery-web-viewer). Configuration is
16
+ layered YAML (OmegaConf) with `key.path=value` overrides; a project is a `bevel.yaml` plus
17
+ `configs/` and `src/` folders; everything is available as a CLI, a Python API, and an MCP
18
+ server so AI assistants can drive it.
19
+
20
+ ```bash
21
+ pip install "bevel-cad[mcp]" # or: uv add "bevel-cad[mcp]"
22
+
23
+ bevel create my_block --template basic # scaffold a project (interactive if args omitted)
24
+ cd my_block
25
+ bevel render my_block # -> renders/my_block_<YYYYMMDD-HHMMSS>/
26
+ bevel render my_block my_block.cylinder_depth=null --skip preview # override + faster loop
27
+ bevel inspect renders/*/my_block_*.stl # watertight? components? open edges?
28
+ bevel render my_block --viewer # push to a running cadquery-web-viewer
29
+ bevel mcp # expose all of the above to an AI agent
30
+ ```
31
+
32
+ ## A part
33
+
34
+ ```python
35
+ import cadquery as cq
36
+ import bevel_cad
37
+
38
+ @bevel_cad.part(defaults={"widget": {"width": 40.0, "hole_d": 6.0}})
39
+ def build(cfg) -> cq.Workplane:
40
+ p = cfg.widget
41
+ return cq.Workplane("XY").box(p.width, p.width, 5).faces(">Z").workplane().hole(p.hole_d)
42
+ ```
43
+
44
+ Return an `Assembly` with named children to get one STL per body (and per-body colours in
45
+ the viewer). Anything with `.wrapped` (build123d) or a `trimesh.Trimesh` works too.
46
+
47
+ ## Python API
48
+
49
+ ```python
50
+ from bevel_cad import load_config, render_part
51
+
52
+ cfg = load_config(files=["configs/widget.yaml"], dotlist=["widget.hole_d=8"])
53
+ result = render_part(build(cfg), cfg, name="widget-8mm")
54
+ print(result.bundle_dir, result.path_for("stl"), result.extra_paths)
55
+ ```
56
+
57
+ ## Project layout
58
+
59
+ ```
60
+ bevel.yaml project config: how to render (formats, tolerances, viewer)
61
+ bevel.local.yaml personal overrides (git-ignored)
62
+ configs/<part>.yaml per-part config: `part:` + the part's block
63
+ src/<part>.py build(cfg) -> geometry
64
+ renders/<slug>_<ts>/ bundles
65
+ ```
66
+
67
+ Config layers, low to high: built-in defaults → `~/.config/bevel/config.yaml` →
68
+ `bevel.yaml` → `bevel.local.yaml` → part `defaults=` → `-c FILE …` → `KEY=VALUE`.
69
+ The `project`, `rendering`, and `viewer` blocks are typed and validated; everything else is
70
+ free-form. Missing keys raise (no silent `None`); use `cfg.get("key", default)` for optional ones.
71
+
72
+ ## Commands
73
+
74
+ | command | |
75
+ |---|---|
76
+ | `bevel render [TARGET] [-c FILE]… [KEY=VALUE]… [--name N] [--out DIR] [--only F] [--skip F] [--viewer]` | build + bundle |
77
+ | `bevel config [TARGET] …` | print the merged config |
78
+ | `bevel list` / `bevel describe NAME` | discoverable parts and their defaults |
79
+ | `bevel renders` / `bevel show BUNDLE` | previous bundles; files, snapshot, stats, log |
80
+ | `bevel inspect MESH…` | watertight, components, boundary/non-manifold edges, volume |
81
+ | `bevel upload BUNDLE` | re-push a bundle to the viewer |
82
+ | `bevel create` / `bevel add` / `bevel templates` | scaffolding (`basic`, `label`) |
83
+ | `bevel skills list\|install` | agent skills for building/verifying parts |
84
+ | `bevel mcp [--transport stdio\|streamable-http]` | MCP server |
85
+
86
+ Every command takes `--root DIR` and `--json`. `TARGET` is a `.py`/`.yaml` path, a project part
87
+ name, `pkg.module[:fn]`, or a registered name (`bevel_cad.parts` / `bevel_cad.providers` entry points).
88
+ A ready-made project lives in [examples/](examples/).
89
+
90
+ ## Docs
91
+
92
+ [docs/config.md](docs/config.md) · [docs/cli.md](docs/cli.md) · [docs/parts.md](docs/parts.md) ·
93
+ [docs/project-layout.md](docs/project-layout.md) · [docs/viewer.md](docs/viewer.md) ·
94
+ [docs/mcp.md](docs/mcp.md) · [docs/skills.md](docs/skills.md) ·
95
+ [docs/releasing.md](docs/releasing.md)
96
+
97
+ ## Extending
98
+
99
+ Other packages register parts via entry points and can add typed config blocks / CLI flags:
100
+
101
+ ```toml
102
+ [project.entry-points."bevel_cad.parts"]
103
+ clamp = "mypkg.parts.clamp"
104
+ [project.entry-points."bevel_cad.providers"]
105
+ mypkg = "mypkg.registry:bevel_parts" # -> {name: "module.path", ...}
106
+ ```
107
+
108
+ ```python
109
+ from bevel_cad.cli import CliHooks, main
110
+ main(hooks=CliHooks(schema=MySchema, add_render_flags=add_my_flags, prepare_config=wrap_cfg))
111
+ ```
112
+
113
+ MIT licensed. Python 3.11–3.12 (cadquery 2.8 needs 3.11; the viewer pins `<3.13`).
114
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev loop and [docs/releasing.md](docs/releasing.md) for releases.
@@ -0,0 +1,37 @@
1
+ # CLI
2
+
3
+ All commands accept `--root DIR` (project root; default: nearest `bevel.yaml`), `--json`, `-v`.
4
+ Exit codes: `0` ok, `1` user-facing failure (unknown part, invalid config, viewer down,
5
+ export error), `2` usage.
6
+
7
+ ```
8
+ bevel render [TARGET] [KEY=VALUE]... [-c FILE]... [--name N] [--out DIR]
9
+ [--only FMT[,FMT]] [--skip FMT[,FMT]] [--viewer | --no-viewer]
10
+ bevel config [TARGET] [KEY=VALUE]... [-c FILE]...
11
+ bevel upload BUNDLE [KEY=VALUE]... [-c FILE]... [--name N]
12
+ bevel list bevel describe NAME [--source]
13
+ bevel renders [--limit N] bevel show BUNDLE
14
+ bevel inspect MESH... bevel project
15
+ bevel create [NAME] [--description D] [--format stl|step|3mf|glb] [--template basic|label]
16
+ [--dir PATH] [--param KEY=VALUE] [-y] [--no-skills] [--force]
17
+ bevel add NAME [--template T] [--param KEY=VALUE] [-y] [--force]
18
+ bevel templates bevel skills list|install|path [--project|--user|--to DIR] [--force]
19
+ bevel mcp [--transport stdio|streamable-http] [--host H] [--port P]
20
+ ```
21
+
22
+ ## TARGET resolution
23
+
24
+ 1. an existing `.py` file, or a `.yaml` file (used as the last config layer; its `part:` key names the code);
25
+ 2. `<configs_dir>/<name>.yaml` (+ `part:`), then `<src_dir>/<name>.py` in the project;
26
+ 3. `package.module[:callable]`;
27
+ 4. a registered name (`bevel_cad.parts` / `bevel_cad.providers` entry points, bundled examples).
28
+
29
+ Positionals after the target that contain `=` are dotlist overrides.
30
+
31
+ `--only` enables exactly the listed formats (plus `config` and `stats`); `--skip` disables the
32
+ listed ones. Both accept format names (`stl`) or job names (`iso`).
33
+
34
+ ## `--json`
35
+
36
+ Every command prints a JSON object/array with `--json` — the same data the MCP server returns.
37
+ `render` gives `{run_name, stem, bundle_dir, files{job: path}, extra_files[], viewer_names[], stats{}}`.
@@ -0,0 +1,92 @@
1
+ # Configuration
2
+
3
+ bevel uses [OmegaConf](https://omegaconf.readthedocs.io/) to merge YAML layers into one
4
+ config object with attribute access (`cfg.rendering.exports.stl.enabled`).
5
+
6
+ ## Layers (low → high precedence)
7
+
8
+ | # | layer | where |
9
+ |---|---|---|
10
+ | 1 | built-in defaults | `bevel_cad/config/defaults.yaml` |
11
+ | 2 | user | `$XDG_CONFIG_HOME/bevel/config.yaml` (`~/.config/bevel/config.yaml`) |
12
+ | 3 | project | `bevel.yaml`, found by walking up from cwd (`$BEVEL_ROOT` / `--root` override) |
13
+ | 4 | project local | `bevel.local.yaml` next to it (git-ignored) |
14
+ | 5 | part defaults | `@bevel_cad.part(defaults=...)` |
15
+ | 6 | `-c FILE …` | in the order given; a `.yaml` render target or `configs/<name>.yaml` is appended here |
16
+ | 7 | CLI dotlist | `key.path=value` (values parsed as YAML: `true`, `null`, `3.5`, `[a,b]`) |
17
+ | 8 | programmatic | `load_layers(overrides=...)` |
18
+
19
+ `bevel config [TARGET]` prints the merged result; `<stem>.yaml` in every bundle is the
20
+ snapshot that produced that render (re-run with `bevel render -c <stem>.yaml`).
21
+
22
+ ## Typed blocks
23
+
24
+ ```yaml
25
+ project:
26
+ name: my_project
27
+ description: ...
28
+ configs_dir: configs # or a list
29
+ src_dir: src
30
+
31
+ rendering:
32
+ name: null # run name; falls back to the part name
33
+ output_dir: renders # relative to the project root (cwd without a project)
34
+ tolerance: 0.001 # tessellation (mm)
35
+ angular_tolerance: 0.05
36
+ exports: # mapping keyed by job name
37
+ stl: {enabled: true, stl_ascii: false}
38
+ step: {enabled: false, write_pcurves: true, precision_mode: 0}
39
+ "3mf": {enabled: false}
40
+ glb: {enabled: true}
41
+ gltf: {enabled: false}
42
+ obj: {enabled: false, unit_scale_mm_to_m: true, target_face_count: null, watertight_required: false}
43
+ preview: {enabled: true, image_width: 800, image_height: 600, elevation: 30, azimuth: 45, roll: 0,
44
+ light_azimuth: 225, light_elevation: 45, color: "#b3b3b3", background: "#1a1a2e", opacity: 1.0}
45
+ config: {enabled: true}
46
+ stats: {enabled: true}
47
+ iso: {format: preview, filename: "{name}-iso.png", azimuth: 135} # extra jobs: any name
48
+
49
+ viewer:
50
+ enabled: false # or --viewer
51
+ host: localhost
52
+ port: 32323
53
+ upload_timeout: 300.0
54
+ post_timeout: 60.0
55
+ tolerance: 0.05 # coarser tessellation for the viewer
56
+ angular_tolerance: 0.1
57
+ style: {protocol: null, texture: null, color_faces: null, color_edges: null, color_vertices: null}
58
+ ```
59
+
60
+ Export jobs: `format` defaults to the key, `filename` to `{name}.<ext>` where `{name}` is the
61
+ bundle stem and `{run_name}` the raw run name. Jobs run in a fixed order
62
+ (step, stl, 3mf, glb, gltf, obj, preview, config, stats); `preview`/`obj`/`gltf` synthesise a
63
+ disabled GLB job when GLB is off. Two jobs resolving to the same file is an error.
64
+
65
+ Typed blocks reject unknown keys and wrong types (`viewer.port=abc` fails at load). All other
66
+ top-level keys are free-form for parts. The merged config is **struct**: a missing attribute
67
+ raises `ConfigAttributeError` (an `AttributeError`) instead of returning `None`; use
68
+ `cfg.get("key", default)`.
69
+
70
+ ## Extending with a schema
71
+
72
+ ```python
73
+ from dataclasses import dataclass, field
74
+ from bevel_cad import BevelSchema
75
+
76
+ @dataclass
77
+ class Bounds: width: float = 100.0; height: float = 100.0
78
+
79
+ @dataclass
80
+ class MySchema(BevelSchema):
81
+ output_bounds: Bounds = field(default_factory=Bounds)
82
+
83
+ cfg = load_config(schema=MySchema, files=["configs/x.yaml"])
84
+ ```
85
+
86
+ Validation in `__post_init__` runs when you call `OmegaConf.to_object(cfg.output_bounds)`.
87
+
88
+ ## Legacy shapes
89
+
90
+ A `server:` block (`server.viewer.host`, `server.color_faces`, …) and list-form
91
+ `rendering.exports` (`- format: stl`) are converted on load with a `DeprecationWarning`, so
92
+ older snapshots stay usable.
@@ -0,0 +1,36 @@
1
+ # MCP server
2
+
3
+ `bevel mcp` exposes every command as typed MCP tools (official `mcp` Python SDK v2).
4
+
5
+ ```bash
6
+ pip install "bevel-cad[mcp]"
7
+ bevel mcp --root /path/to/project # stdio (Claude Code / Desktop)
8
+ bevel mcp --transport streamable-http --port 8765 # HTTP
9
+ uv run mcp dev src/bevel_cad/mcp/server.py # MCP Inspector
10
+ ```
11
+
12
+ Claude Code:
13
+
14
+ ```bash
15
+ claude mcp add bevel -- uv run --directory /path/to/project bevel mcp --root .
16
+ ```
17
+
18
+ ## Tools
19
+
20
+ `project_info`, `list_parts`, `describe_part(name)`, `read_part_source(name)`,
21
+ `resolve_config(target, configs, overrides)`, `render(target, configs, overrides, name, only,
22
+ skip, viewer, out)`, `get_preview(bundle)` → image, `inspect_mesh(path)`, `list_renders(limit)`,
23
+ `describe_render(bundle)`, `upload(bundle, name)`, `list_templates`, `create_project(...)`,
24
+ `add_part(...)`, `list_skills`, `read_skill(name)`.
25
+
26
+ `render` runs in a subprocess (`bevel render --json`), so OCC crashes cannot take the server
27
+ down; log lines are forwarded as progress. Results are the same JSON the CLI prints with `--json`.
28
+
29
+ ## Resources
30
+
31
+ `bevel://project`, `bevel://parts/{name}`, `bevel://renders/{stem}/config|stats|log`,
32
+ `bevel://skills/{name}`.
33
+
34
+ ## Prompts
35
+
36
+ `build_part`, `verify_render`, `iterate_model` return the matching skill text.
@@ -0,0 +1,39 @@
1
+ # Parts
2
+
3
+ A part is a module exposing `build(cfg)` that **returns** geometry. See the bundled
4
+ skill `bevel-part-authoring` (`bevel skills path`) for the full guide and pitfalls.
5
+
6
+ ```python
7
+ import bevel_cad
8
+
9
+ @bevel_cad.part(name=None, defaults={...}, schema=None, description="...")
10
+ def build(cfg): ...
11
+ ```
12
+
13
+ - `defaults` — merged as the *part* config layer (below `-c` files and CLI overrides).
14
+ - `schema` — optional dataclass extending `BevelSchema` for strict validation of your blocks.
15
+ - Accepted return types: `cq.Assembly` (named children → per-body STLs and viewer colours),
16
+ `cq.Shape`, `cq.Workplane`, `trimesh.Trimesh`, anything with `.wrapped`.
17
+ - Returning `None` means "the part rendered itself" (legacy contract); nothing is written.
18
+
19
+ ## Discovery
20
+
21
+ `bevel list` shows, in precedence order: project `src/` files, `bevel_cad.parts` entry points,
22
+ `bevel_cad.providers` (a callable returning `{name: "module.path"}`), bundled examples.
23
+
24
+ Standalone files are imported with their directory on `sys.path`, so sibling imports work.
25
+
26
+ ## Example project
27
+
28
+ `examples/` in the bevel-cad repo is a self-contained bevel project (`bevel.yaml`, `configs/`,
29
+ `src/`, `renders/`) with `button_label` (engraved plate with a button hole, two bodies) and
30
+ `spacer_washer` (inch-dimensioned filleted washer):
31
+
32
+ ```bash
33
+ cd examples
34
+ bevel list
35
+ bevel render button_label_custom # configs/button_label_custom.yaml -> src/button_label.py
36
+ bevel render spacer_washer spacer_washer.outer_diameter_in=2.0
37
+ ```
38
+
39
+ Copy the folder anywhere; nothing in it depends on being inside the repository.
@@ -0,0 +1,19 @@
1
+ # Project layout
2
+
3
+ ```
4
+ my_project/
5
+ bevel.yaml project config (typed blocks project/rendering/viewer + anything shared)
6
+ bevel.local.yaml personal overrides, git-ignored
7
+ configs/<part>.yaml per-part: `part: <source>` + the part's own block
8
+ src/<part>.py part sources (src/ is put on sys.path)
9
+ renders/<slug>_<YYYYMMDD-HHMMSS>/
10
+ <stem>.stl <stem>_<body>.stl <stem>.glb <stem>.png <stem>.yaml <stem>.csv <stem>.log
11
+ [<stem>.step <stem>.3mf <stem>.gltf <stem>.obj]
12
+ .claude/skills/bevel-*/ agent skills (bevel skills install --project)
13
+ ```
14
+
15
+ - Root discovery: walk up from cwd for `bevel.yaml`; `$BEVEL_ROOT` or `--root` override.
16
+ - `project.configs_dir` may be a list (e.g. `[knot_configs, part_configs]`).
17
+ - `rendering.output_dir` resolves against the root (against cwd when there is no project).
18
+ - `bevel create NAME` scaffolds all of this plus a first part from a template;
19
+ `bevel add NAME` adds another config/source pair.
@@ -0,0 +1,99 @@
1
+ # Releasing bevel-cad
2
+
3
+ Releases are fully automated from a git tag. No API tokens or secrets are stored in the
4
+ repository or in GitHub: PyPI publishing and artifact signing use GitHub's OIDC identity
5
+ ("Trusted Publishing").
6
+
7
+ ## One-time setup
8
+
9
+ Do these once, in order. Nothing here is a secret; there is nothing to paste into GitHub.
10
+
11
+ ### 1. PyPI: add a *pending* trusted publisher
12
+
13
+ `bevel-cad` does not exist on PyPI yet, so the publisher must be registered as *pending*
14
+ (it becomes a normal publisher on the first upload, which also creates the project).
15
+
16
+ 1. Log in at <https://pypi.org>, verify your email and enable 2FA if not already done.
17
+ 2. Go to **Your account → Publishing** (<https://pypi.org/manage/account/publishing/>).
18
+ 3. Under **Add a new pending publisher → GitHub**, enter exactly:
19
+
20
+ | Field | Value |
21
+ |---|---|
22
+ | PyPI project name | `bevel-cad` |
23
+ | Owner | `jimcortez` |
24
+ | Repository name | `bevel-cad` |
25
+ | Workflow name | `release.yml` |
26
+ | Environment name | `pypi` |
27
+
28
+ The environment name must match; the workflow declares `environment: pypi`.
29
+
30
+ ### 2. GitHub: create the `pypi` environment
31
+
32
+ 1. Repo **Settings → Environments → New environment**, name it `pypi`.
33
+ 2. Under **Deployment branches and tags** choose **Selected branches and tags** and add a
34
+ tag rule `v*`. This stops any non-tag workflow from ever obtaining the publishing identity.
35
+ 3. Optional: add yourself as a **Required reviewer** for a manual approval gate before each
36
+ PyPI upload. The workflow will pause at the `publish` job until approved.
37
+
38
+ ### 3. GitHub: Actions and security settings
39
+
40
+ - **Settings → Actions → General → Workflow permissions** can stay at *Read repository contents
41
+ and packages permissions*. Each job escalates only what it needs (`contents: write`,
42
+ `id-token: write`, `attestations: write` on the publish job).
43
+ - **Settings → Code security**: enable *Dependabot alerts* and *Dependabot security updates*.
44
+ Do **not** enable CodeQL *Default setup*; the repo runs CodeQL through
45
+ `.github/workflows/codeql.yml` (advanced setup) and the two conflict.
46
+
47
+ ### 4. Renovate
48
+
49
+ Install the Mend Renovate GitHub App on the `jimcortez/bevel-cad` repository from
50
+ <https://github.com/apps/renovate>. Configuration lives in `.github/renovate.json5`
51
+ (weekend schedule, branch automerge, lock-file maintenance). Dependabot handles GitHub Actions.
52
+
53
+ ### 5. Local `gh` CLI
54
+
55
+ `gh auth login -h github.com` on the machine you release from, so you can watch runs
56
+ (`gh run watch`) and verify attestations (`gh attestation verify`).
57
+
58
+ ## Cutting a release
59
+
60
+ 1. Make sure `main` is green in CI and `CHANGELOG.md` has the version's entry with a date.
61
+ 2. Tag the commit on `main` and push the tag:
62
+
63
+ ```bash
64
+ git switch main && git pull
65
+ git tag -a v0.1.0 -m "v0.1.0"
66
+ git push origin v0.1.0
67
+ ```
68
+
69
+ 3. `release.yml` then:
70
+ - checks the tag is a valid PEP 440 version and is reachable from `main`;
71
+ - re-runs the full build + test matrix + clean-install smoke test on the tagged commit;
72
+ - signs a build-provenance attestation for the wheel and sdist;
73
+ - publishes to PyPI with PEP 740 attestations;
74
+ - creates a GitHub Release with auto-generated notes and attaches the wheel and sdist.
75
+
76
+ The package version comes from the tag (hatch-vcs). `v0.1.0` → `bevel-cad 0.1.0`. Untagged
77
+ builds are `0.1.1.devN`. There is no version string to bump in the tree.
78
+
79
+ ## Verifying a release
80
+
81
+ ```bash
82
+ # Provenance attestation stored on GitHub
83
+ gh attestation verify bevel_cad-0.1.0-py3-none-any.whl --owner jimcortez
84
+
85
+ # PyPI attestation and a clean install
86
+ uv venv /tmp/bevel-check && VIRTUAL_ENV=/tmp/bevel-check uv pip install bevel-cad==0.1.0
87
+ /tmp/bevel-check/bin/bevel --version
88
+ ```
89
+
90
+ The PyPI project page shows "Verified details" with the publishing workflow and commit.
91
+
92
+ ## If a release fails
93
+
94
+ - `check-tag` fails: the tag is not on `main` or is not PEP 440. Delete and re-tag.
95
+ - `build` fails: fix on `main`, then move the tag (`git tag -f`, `git push -f origin v0.1.0`)
96
+ only if nothing was published yet. Once PyPI has the version, cut a new patch version instead;
97
+ PyPI file names can never be reused.
98
+ - `publish` fails at PyPI with an OIDC error: re-check the trusted publisher fields above
99
+ (repository, workflow file name and environment must match exactly).