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.
- bevel_cad-0.1.0/.gitignore +12 -0
- bevel_cad-0.1.0/CHANGELOG.md +39 -0
- bevel_cad-0.1.0/LICENSE +21 -0
- bevel_cad-0.1.0/PKG-INFO +153 -0
- bevel_cad-0.1.0/README.md +114 -0
- bevel_cad-0.1.0/docs/cli.md +37 -0
- bevel_cad-0.1.0/docs/config.md +92 -0
- bevel_cad-0.1.0/docs/mcp.md +36 -0
- bevel_cad-0.1.0/docs/parts.md +39 -0
- bevel_cad-0.1.0/docs/project-layout.md +19 -0
- bevel_cad-0.1.0/docs/releasing.md +99 -0
- bevel_cad-0.1.0/docs/skills.md +18 -0
- bevel_cad-0.1.0/docs/viewer.md +24 -0
- bevel_cad-0.1.0/examples/README.md +23 -0
- bevel_cad-0.1.0/examples/bevel.yaml +25 -0
- bevel_cad-0.1.0/examples/configs/button_label.yaml +4 -0
- bevel_cad-0.1.0/examples/configs/button_label_custom.yaml +9 -0
- bevel_cad-0.1.0/examples/configs/spacer_washer.yaml +9 -0
- bevel_cad-0.1.0/examples/src/button_label.py +226 -0
- bevel_cad-0.1.0/examples/src/spacer_washer.py +57 -0
- bevel_cad-0.1.0/pyproject.toml +118 -0
- bevel_cad-0.1.0/src/bevel_cad/__init__.py +31 -0
- bevel_cad-0.1.0/src/bevel_cad/__main__.py +3 -0
- bevel_cad-0.1.0/src/bevel_cad/_version.py +24 -0
- bevel_cad-0.1.0/src/bevel_cad/cli/__init__.py +5 -0
- bevel_cad-0.1.0/src/bevel_cad/cli/create.py +142 -0
- bevel_cad-0.1.0/src/bevel_cad/cli/main.py +342 -0
- bevel_cad-0.1.0/src/bevel_cad/cli/mcp.py +36 -0
- bevel_cad-0.1.0/src/bevel_cad/cli/skills.py +60 -0
- bevel_cad-0.1.0/src/bevel_cad/commands.py +416 -0
- bevel_cad-0.1.0/src/bevel_cad/config/__init__.py +54 -0
- bevel_cad-0.1.0/src/bevel_cad/config/defaults.yaml +58 -0
- bevel_cad-0.1.0/src/bevel_cad/config/loader.py +280 -0
- bevel_cad-0.1.0/src/bevel_cad/config/paths.py +105 -0
- bevel_cad-0.1.0/src/bevel_cad/config/schema.py +159 -0
- bevel_cad-0.1.0/src/bevel_cad/geom/__init__.py +1 -0
- bevel_cad-0.1.0/src/bevel_cad/geom/text_plate.py +170 -0
- bevel_cad-0.1.0/src/bevel_cad/mcp/__init__.py +5 -0
- bevel_cad-0.1.0/src/bevel_cad/mcp/server.py +376 -0
- bevel_cad-0.1.0/src/bevel_cad/mesh/__init__.py +1 -0
- bevel_cad-0.1.0/src/bevel_cad/mesh/convert.py +61 -0
- bevel_cad-0.1.0/src/bevel_cad/mesh/fuse.py +150 -0
- bevel_cad-0.1.0/src/bevel_cad/mesh/inspect.py +165 -0
- bevel_cad-0.1.0/src/bevel_cad/mesh/validate.py +38 -0
- bevel_cad-0.1.0/src/bevel_cad/parts.py +322 -0
- bevel_cad-0.1.0/src/bevel_cad/py.typed +0 -0
- bevel_cad-0.1.0/src/bevel_cad/render/__init__.py +46 -0
- bevel_cad-0.1.0/src/bevel_cad/render/bundle.py +65 -0
- bevel_cad-0.1.0/src/bevel_cad/render/colors.py +87 -0
- bevel_cad-0.1.0/src/bevel_cad/render/logbuffer.py +73 -0
- bevel_cad-0.1.0/src/bevel_cad/render/naming.py +44 -0
- bevel_cad-0.1.0/src/bevel_cad/render/pipeline.py +498 -0
- bevel_cad-0.1.0/src/bevel_cad/render/planner.py +224 -0
- bevel_cad-0.1.0/src/bevel_cad/render/preview.py +391 -0
- bevel_cad-0.1.0/src/bevel_cad/render/stats.py +143 -0
- bevel_cad-0.1.0/src/bevel_cad/scaffold.py +246 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-mcp-workflow/SKILL.md +59 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-model-iteration/SKILL.md +63 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/SKILL.md +94 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/references/cadquery-pitfalls.md +48 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-part-authoring/references/part-skeleton.py +28 -0
- bevel_cad-0.1.0/src/bevel_cad/skills/bevel-render-verify/SKILL.md +77 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/basic/config.yaml.tmpl +9 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/basic/part.py.tmpl +42 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/basic/template.yaml +3 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/label/config.yaml.tmpl +16 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/label/part.py.tmpl +49 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/label/template.yaml +6 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/project/README.md.tmpl +30 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/project/bevel.yaml.tmpl +29 -0
- bevel_cad-0.1.0/src/bevel_cad/templates/project/gitignore.tmpl +5 -0
- bevel_cad-0.1.0/src/bevel_cad/viewer.py +176 -0
- bevel_cad-0.1.0/tests/__init__.py +0 -0
- bevel_cad-0.1.0/tests/conftest.py +71 -0
- bevel_cad-0.1.0/tests/fixtures/box_part.py +8 -0
- bevel_cad-0.1.0/tests/test_bundle.py +47 -0
- bevel_cad-0.1.0/tests/test_button_label.py +352 -0
- bevel_cad-0.1.0/tests/test_cli.py +178 -0
- bevel_cad-0.1.0/tests/test_color_palette.py +78 -0
- bevel_cad-0.1.0/tests/test_config.py +127 -0
- bevel_cad-0.1.0/tests/test_examples_project.py +53 -0
- bevel_cad-0.1.0/tests/test_fuse_utils.py +79 -0
- bevel_cad-0.1.0/tests/test_logbuffer.py +89 -0
- bevel_cad-0.1.0/tests/test_mcp.py +165 -0
- bevel_cad-0.1.0/tests/test_naming.py +33 -0
- bevel_cad-0.1.0/tests/test_parts.py +72 -0
- bevel_cad-0.1.0/tests/test_pipeline.py +146 -0
- bevel_cad-0.1.0/tests/test_planner.py +84 -0
- bevel_cad-0.1.0/tests/test_render_stats.py +70 -0
- bevel_cad-0.1.0/tests/test_scaffold.py +115 -0
|
@@ -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
|
bevel_cad-0.1.0/LICENSE
ADDED
|
@@ -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.
|
bevel_cad-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/bevel-cad/)
|
|
43
|
+
[](https://pypi.org/project/bevel-cad/)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
[](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
|
+
[](https://pypi.org/project/bevel-cad/)
|
|
4
|
+
[](https://pypi.org/project/bevel-cad/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](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).
|