cadgen 0.4.1__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 (73) hide show
  1. cadgen-0.4.1/LICENSE +21 -0
  2. cadgen-0.4.1/PKG-INFO +122 -0
  3. cadgen-0.4.1/README.md +95 -0
  4. cadgen-0.4.1/pyproject.toml +48 -0
  5. cadgen-0.4.1/setup.cfg +4 -0
  6. cadgen-0.4.1/src/cadgen/__init__.py +44 -0
  7. cadgen-0.4.1/src/cadgen/_internal/__init__.py +6 -0
  8. cadgen-0.4.1/src/cadgen/_internal/assembly_spec.py +54 -0
  9. cadgen-0.4.1/src/cadgen/_internal/cli_errors.py +107 -0
  10. cadgen-0.4.1/src/cadgen/_internal/cli_locking.py +99 -0
  11. cadgen-0.4.1/src/cadgen/_internal/component_package.py +782 -0
  12. cadgen-0.4.1/src/cadgen/_internal/drawing_package.py +476 -0
  13. cadgen-0.4.1/src/cadgen/_internal/file_metadata.py +61 -0
  14. cadgen-0.4.1/src/cadgen/_internal/generation.py +2412 -0
  15. cadgen-0.4.1/src/cadgen/_internal/glb.py +1163 -0
  16. cadgen-0.4.1/src/cadgen/_internal/glb_mesh_payload.py +699 -0
  17. cadgen-0.4.1/src/cadgen/_internal/glb_topology.py +513 -0
  18. cadgen-0.4.1/src/cadgen/_internal/implicit_package.py +325 -0
  19. cadgen-0.4.1/src/cadgen/_internal/node_resolve_hooks.mjs +49 -0
  20. cadgen-0.4.1/src/cadgen/_internal/node_resolve_register.mjs +10 -0
  21. cadgen-0.4.1/src/cadgen/_internal/node_runtime.py +417 -0
  22. cadgen-0.4.1/src/cadgen/_internal/package_freshness.py +89 -0
  23. cadgen-0.4.1/src/cadgen/_internal/source_hash.py +466 -0
  24. cadgen-0.4.1/src/cadgen/_internal/step_hash.py +12 -0
  25. cadgen-0.4.1/src/cadgen/_internal/step_metadata.py +321 -0
  26. cadgen-0.4.1/src/cadgen/_internal/step_scene.py +2837 -0
  27. cadgen-0.4.1/src/cadgen/_internal/stl.py +31 -0
  28. cadgen-0.4.1/src/cadgen/_internal/threemf.py +563 -0
  29. cadgen-0.4.1/src/cadgen/_internal/validators.py +119 -0
  30. cadgen-0.4.1/src/cadgen/analysis.py +683 -0
  31. cadgen-0.4.1/src/cadgen/api.py +6 -0
  32. cadgen-0.4.1/src/cadgen/assembly.py +436 -0
  33. cadgen-0.4.1/src/cadgen/cad_ref_syntax.py +143 -0
  34. cadgen-0.4.1/src/cadgen/catalog.py +528 -0
  35. cadgen-0.4.1/src/cadgen/cli_logging.py +58 -0
  36. cadgen-0.4.1/src/cadgen/color.py +65 -0
  37. cadgen-0.4.1/src/cadgen/coordination/__init__.py +448 -0
  38. cadgen-0.4.1/src/cadgen/coordination/kinds.py +89 -0
  39. cadgen-0.4.1/src/cadgen/coordination/lock.py +252 -0
  40. cadgen-0.4.1/src/cadgen/coordination/paths.py +76 -0
  41. cadgen-0.4.1/src/cadgen/coordination/phases.py +360 -0
  42. cadgen-0.4.1/src/cadgen/coordination/record.py +194 -0
  43. cadgen-0.4.1/src/cadgen/drawing_checks.py +263 -0
  44. cadgen-0.4.1/src/cadgen/drawing_render.py +464 -0
  45. cadgen-0.4.1/src/cadgen/dxf_artifact.py +210 -0
  46. cadgen-0.4.1/src/cadgen/flatten.py +345 -0
  47. cadgen-0.4.1/src/cadgen/generation.py +18 -0
  48. cadgen-0.4.1/src/cadgen/implicit_artifact.py +225 -0
  49. cadgen-0.4.1/src/cadgen/implicit_export.py +232 -0
  50. cadgen-0.4.1/src/cadgen/instances.py +85 -0
  51. cadgen-0.4.1/src/cadgen/interference.py +247 -0
  52. cadgen-0.4.1/src/cadgen/lookup.py +243 -0
  53. cadgen-0.4.1/src/cadgen/metadata.py +426 -0
  54. cadgen-0.4.1/src/cadgen/py.typed +1 -0
  55. cadgen-0.4.1/src/cadgen/render.py +35 -0
  56. cadgen-0.4.1/src/cadgen/reporting.py +133 -0
  57. cadgen-0.4.1/src/cadgen/selector_types.py +18 -0
  58. cadgen-0.4.1/src/cadgen/snapshot_cli.py +1545 -0
  59. cadgen-0.4.1/src/cadgen/snapshot_core.py +1027 -0
  60. cadgen-0.4.1/src/cadgen/sources.py +34 -0
  61. cadgen-0.4.1/src/cadgen/step_artifact.py +490 -0
  62. cadgen-0.4.1/src/cadgen/step_artifacts.py +513 -0
  63. cadgen-0.4.1/src/cadgen/step_export.py +420 -0
  64. cadgen-0.4.1/src/cadgen/step_export_target.py +401 -0
  65. cadgen-0.4.1/src/cadgen/step_scene.py +23 -0
  66. cadgen-0.4.1/src/cadgen/step_targets.py +519 -0
  67. cadgen-0.4.1/src/cadgen/validity.py +227 -0
  68. cadgen-0.4.1/src/cadgen.egg-info/PKG-INFO +122 -0
  69. cadgen-0.4.1/src/cadgen.egg-info/SOURCES.txt +71 -0
  70. cadgen-0.4.1/src/cadgen.egg-info/dependency_links.txt +1 -0
  71. cadgen-0.4.1/src/cadgen.egg-info/entry_points.txt +2 -0
  72. cadgen-0.4.1/src/cadgen.egg-info/requires.txt +4 -0
  73. cadgen-0.4.1/src/cadgen.egg-info/top_level.txt +1 -0
cadgen-0.4.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thompson Labs LLC
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.
cadgen-0.4.1/PKG-INFO ADDED
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: cadgen
3
+ Version: 0.4.1
4
+ Summary: STEP-first CAD artifact generation runtime: build123d STEP/GLB/topology generation, validation, and inspection for CAD agent skills.
5
+ Author: earthtojake
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/earthtojake/text-to-cad
8
+ Project-URL: Repository, https://github.com/earthtojake/text-to-cad
9
+ Project-URL: Issues, https://github.com/earthtojake/text-to-cad/issues
10
+ Keywords: cad,step,glb,build123d,parametric,agent-skills
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Manufacturing
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: build123d
23
+ Requires-Dist: cadquery-ocp
24
+ Requires-Dist: ezdxf
25
+ Requires-Dist: shapely
26
+ Dynamic: license-file
27
+
28
+ # cadgen
29
+
30
+ STEP-first CAD artifact generation runtime for CAD agent skills, built on
31
+ [build123d](https://github.com/gumyr/build123d) and OCCT.
32
+
33
+ The package boundary is intentionally narrow: it owns artifact generation,
34
+ validation, selector/topology extraction, mesh settings, source hashing, and the
35
+ `cadgen-step-artifact` CLI. It also includes small generated-script helpers such
36
+ as `cadgen.assembly.AssemblyHelper`, which wraps native build123d labels, joints,
37
+ and compounds without owning skill-specific UX. Prompts, viewer UI, and snapshot
38
+ job orchestration stay in their owning skills.
39
+
40
+ `cadgen` is developed in
41
+ [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad) and was
42
+ previously named `cadpy` inside that repository.
43
+
44
+ ## Public API
45
+
46
+ The supported import surface is the root `cadgen` exports plus the top-level
47
+ `cadgen.*` modules:
48
+
49
+ - Generator-script helpers: root exports (`AssemblyHelper`, `MateRelation`,
50
+ `MateTarget`, `label_text`, `label_shape`, `target`,
51
+ `ensure_step_glb_artifact`, `validate_step_glb_artifact`), `cadgen.assembly`,
52
+ and `cadgen.step_scene` (`import_step`, `load_step_scene`, `located_shape`,
53
+ `occurrence_selector_id`, `scene_occurrence_shape`).
54
+ - Generator-script helpers (2D): `cadgen.sources` (`load_source_module`) and
55
+ `cadgen.flatten` (planar-face projection/unfold, contour emission, kerf
56
+ offsetting) for `.dxf.py` drawing generators.
57
+ - Skill CLI surface: `cadgen.generation` (`generate_step_targets`,
58
+ `generate_dxf_targets`, `targets_include_output_pairs`), `cadgen.catalog`,
59
+ `cadgen.metadata`, `cadgen.analysis`, `cadgen.lookup`, `cadgen.cad_ref_syntax`,
60
+ `cadgen.selector_types`, `cadgen.reporting`, `cadgen.cli_logging`,
61
+ `cadgen.render`, `cadgen.step_artifacts`, `cadgen.step_targets`,
62
+ `cadgen.step_export`, `cadgen.drawing_checks` (DXF drawing validation), and
63
+ `cadgen.drawing_render` (DXF render payload + SVG snapshots).
64
+ - Process entry points: `cadgen-step-artifact`, `python -m cadgen.step_artifact`,
65
+ `python -m cadgen.step_export_target`, and `python -m cadgen.dxf_artifact`.
66
+
67
+ Everything under `cadgen._internal` is private implementation (the STEP scene,
68
+ generation, GLB/topology, and export engines live there) with no import
69
+ stability between releases; `cadgen.generation` and `cadgen.step_scene` are
70
+ thin facades over those engines that re-export only the supported names.
71
+
72
+ ## Install
73
+
74
+ Released versions are published to PyPI by the repository's `Release` workflow;
75
+ the package version always matches the CAD plugin release version:
76
+
77
+ ```bash
78
+ python -m pip install cadgen
79
+ ```
80
+
81
+ Production skill bundles pin the exact release version in their
82
+ `requirements.txt` (for example `cadgen==0.4.0`) and keep a vendored copy of
83
+ this package as an offline fallback:
84
+
85
+ ```bash
86
+ python -m pip install ./scripts/packages/cadgen
87
+ ```
88
+
89
+ ## Local Development
90
+
91
+ Install it editable into the repo CAD runtime when working on the source
92
+ package directly:
93
+
94
+ ```bash
95
+ ./.venv/bin/python -m pip install -e packages/cadgen
96
+ ```
97
+
98
+ After that, changes under `packages/cadgen/src/cadgen` are immediately visible to
99
+ local source checkouts that import the package directly.
100
+
101
+ On `develop`, the CAD skill and root Viewer point at this package through the
102
+ development symlinks `skills/cad/scripts/packages/cadgen` and
103
+ `viewer/packages/cadgen`. Keep those links intact with
104
+ `scripts/dev/setup-symlinks.sh --check`.
105
+
106
+ ## Production Bundling
107
+
108
+ Build a wheel and install it into each skill's bundled Python environment during
109
+ packaging:
110
+
111
+ ```bash
112
+ ./.venv/bin/python -m build packages/cadgen
113
+ python -m pip install packages/cadgen/dist/cadgen-*.whl
114
+ ```
115
+
116
+ The CAD and cad-viewer skills should depend on the package artifact they bundle,
117
+ not on `skills/cad` or the repository root. Production packaging vendors
118
+ installable packages under `skills/cad/scripts/packages/cadgen` and
119
+ `skills/cad-viewer/scripts/viewer/packages/cadgen`; production packaging can also
120
+ set `VIEWER_CAD_PYTHON` to a skill-local Python runtime with this package
121
+ installed. Production skill bundles install `cadgen==<release version>` from
122
+ PyPI first and fall back to the vendored copy when offline.
cadgen-0.4.1/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # cadgen
2
+
3
+ STEP-first CAD artifact generation runtime for CAD agent skills, built on
4
+ [build123d](https://github.com/gumyr/build123d) and OCCT.
5
+
6
+ The package boundary is intentionally narrow: it owns artifact generation,
7
+ validation, selector/topology extraction, mesh settings, source hashing, and the
8
+ `cadgen-step-artifact` CLI. It also includes small generated-script helpers such
9
+ as `cadgen.assembly.AssemblyHelper`, which wraps native build123d labels, joints,
10
+ and compounds without owning skill-specific UX. Prompts, viewer UI, and snapshot
11
+ job orchestration stay in their owning skills.
12
+
13
+ `cadgen` is developed in
14
+ [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad) and was
15
+ previously named `cadpy` inside that repository.
16
+
17
+ ## Public API
18
+
19
+ The supported import surface is the root `cadgen` exports plus the top-level
20
+ `cadgen.*` modules:
21
+
22
+ - Generator-script helpers: root exports (`AssemblyHelper`, `MateRelation`,
23
+ `MateTarget`, `label_text`, `label_shape`, `target`,
24
+ `ensure_step_glb_artifact`, `validate_step_glb_artifact`), `cadgen.assembly`,
25
+ and `cadgen.step_scene` (`import_step`, `load_step_scene`, `located_shape`,
26
+ `occurrence_selector_id`, `scene_occurrence_shape`).
27
+ - Generator-script helpers (2D): `cadgen.sources` (`load_source_module`) and
28
+ `cadgen.flatten` (planar-face projection/unfold, contour emission, kerf
29
+ offsetting) for `.dxf.py` drawing generators.
30
+ - Skill CLI surface: `cadgen.generation` (`generate_step_targets`,
31
+ `generate_dxf_targets`, `targets_include_output_pairs`), `cadgen.catalog`,
32
+ `cadgen.metadata`, `cadgen.analysis`, `cadgen.lookup`, `cadgen.cad_ref_syntax`,
33
+ `cadgen.selector_types`, `cadgen.reporting`, `cadgen.cli_logging`,
34
+ `cadgen.render`, `cadgen.step_artifacts`, `cadgen.step_targets`,
35
+ `cadgen.step_export`, `cadgen.drawing_checks` (DXF drawing validation), and
36
+ `cadgen.drawing_render` (DXF render payload + SVG snapshots).
37
+ - Process entry points: `cadgen-step-artifact`, `python -m cadgen.step_artifact`,
38
+ `python -m cadgen.step_export_target`, and `python -m cadgen.dxf_artifact`.
39
+
40
+ Everything under `cadgen._internal` is private implementation (the STEP scene,
41
+ generation, GLB/topology, and export engines live there) with no import
42
+ stability between releases; `cadgen.generation` and `cadgen.step_scene` are
43
+ thin facades over those engines that re-export only the supported names.
44
+
45
+ ## Install
46
+
47
+ Released versions are published to PyPI by the repository's `Release` workflow;
48
+ the package version always matches the CAD plugin release version:
49
+
50
+ ```bash
51
+ python -m pip install cadgen
52
+ ```
53
+
54
+ Production skill bundles pin the exact release version in their
55
+ `requirements.txt` (for example `cadgen==0.4.0`) and keep a vendored copy of
56
+ this package as an offline fallback:
57
+
58
+ ```bash
59
+ python -m pip install ./scripts/packages/cadgen
60
+ ```
61
+
62
+ ## Local Development
63
+
64
+ Install it editable into the repo CAD runtime when working on the source
65
+ package directly:
66
+
67
+ ```bash
68
+ ./.venv/bin/python -m pip install -e packages/cadgen
69
+ ```
70
+
71
+ After that, changes under `packages/cadgen/src/cadgen` are immediately visible to
72
+ local source checkouts that import the package directly.
73
+
74
+ On `develop`, the CAD skill and root Viewer point at this package through the
75
+ development symlinks `skills/cad/scripts/packages/cadgen` and
76
+ `viewer/packages/cadgen`. Keep those links intact with
77
+ `scripts/dev/setup-symlinks.sh --check`.
78
+
79
+ ## Production Bundling
80
+
81
+ Build a wheel and install it into each skill's bundled Python environment during
82
+ packaging:
83
+
84
+ ```bash
85
+ ./.venv/bin/python -m build packages/cadgen
86
+ python -m pip install packages/cadgen/dist/cadgen-*.whl
87
+ ```
88
+
89
+ The CAD and cad-viewer skills should depend on the package artifact they bundle,
90
+ not on `skills/cad` or the repository root. Production packaging vendors
91
+ installable packages under `skills/cad/scripts/packages/cadgen` and
92
+ `skills/cad-viewer/scripts/viewer/packages/cadgen`; production packaging can also
93
+ set `VIEWER_CAD_PYTHON` to a skill-local Python runtime with this package
94
+ installed. Production skill bundles install `cadgen==<release version>` from
95
+ PyPI first and fall back to the vendored copy when offline.
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cadgen"
7
+ version = "0.4.1"
8
+ description = "STEP-first CAD artifact generation runtime: build123d STEP/GLB/topology generation, validation, and inspection for CAD agent skills."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ authors = [{ name = "earthtojake" }]
14
+ keywords = ["cad", "step", "glb", "build123d", "parametric", "agent-skills"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Manufacturing",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Multimedia :: Graphics :: 3D Modeling",
23
+ "Topic :: Scientific/Engineering",
24
+ ]
25
+ dependencies = [
26
+ "build123d",
27
+ "cadquery-ocp",
28
+ "ezdxf",
29
+ "shapely",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/earthtojake/text-to-cad"
34
+ Repository = "https://github.com/earthtojake/text-to-cad"
35
+ Issues = "https://github.com/earthtojake/text-to-cad/issues"
36
+
37
+ [project.scripts]
38
+ cadgen-step-artifact = "cadgen.step_artifact:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+ include = ["cadgen*"]
43
+
44
+ [tool.setuptools.package-data]
45
+ cadgen = ["py.typed"]
46
+ # The Node builder bridge (cadgen/_internal/node_runtime.py) spawns node with
47
+ # `--import node_resolve_register.mjs`; both .mjs files must ship with the package.
48
+ "cadgen._internal" = ["*.mjs"]
cadgen-0.4.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """Shared CAD artifact generation runtime."""
2
+
3
+ __all__ = [
4
+ "AssemblyHelper",
5
+ "srgb",
6
+ "MateRelation",
7
+ "MateTarget",
8
+ "compound_from_instances",
9
+ "ensure_step_glb_artifact",
10
+ "label_text",
11
+ "label_shape",
12
+ "target",
13
+ "validate_step_glb_artifact",
14
+ ]
15
+
16
+
17
+ def __getattr__(name: str):
18
+ if name in {"ensure_step_glb_artifact", "validate_step_glb_artifact"}:
19
+ from cadgen.api import ensure_step_glb_artifact, validate_step_glb_artifact
20
+
21
+ return {
22
+ "ensure_step_glb_artifact": ensure_step_glb_artifact,
23
+ "validate_step_glb_artifact": validate_step_glb_artifact,
24
+ }[name]
25
+ if name in {"AssemblyHelper", "MateRelation", "MateTarget", "label_shape", "label_text", "target"}:
26
+ from cadgen.assembly import AssemblyHelper, MateRelation, MateTarget, label_shape, label_text, target
27
+
28
+ return {
29
+ "AssemblyHelper": AssemblyHelper,
30
+ "MateRelation": MateRelation,
31
+ "MateTarget": MateTarget,
32
+ "label_text": label_text,
33
+ "label_shape": label_shape,
34
+ "target": target,
35
+ }[name]
36
+ if name in {"srgb", "srgb_to_linear", "linear_to_srgb"}:
37
+ from cadgen import color
38
+
39
+ return getattr(color, name)
40
+ if name == "compound_from_instances":
41
+ from cadgen.instances import compound_from_instances
42
+
43
+ return compound_from_instances
44
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,6 @@
1
+ """Internal cadgen implementation modules.
2
+
3
+ Everything under ``cadgen._internal`` is private: no import stability is
4
+ promised between releases. The supported API is the top-level ``cadgen``
5
+ modules and the root ``cadgen`` exports.
6
+ """
@@ -0,0 +1,54 @@
1
+ """Catalog path utilities and identity-transform constants.
2
+
3
+ Historically this module also parsed Python ``gen_step()`` assembly recipes
4
+ (``instances`` / ``children`` envelopes) into a typed ``AssemblySpec`` tree.
5
+ That contract is gone — ``gen_step()`` is shape-only — and the parser was
6
+ deleted with it. Only the catalog-path helpers and the identity-transform
7
+ constants survive, kept here so the established import paths in
8
+ ``step_artifacts``, ``step_targets``, and ``generation`` continue to resolve.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ from cadgen.catalog import find_source_by_cad_ref
16
+
17
+
18
+ STEP_SUFFIXES = (".step", ".stp")
19
+
20
+ IDENTITY_TRANSFORM: tuple[float, ...] = (
21
+ 1.0, 0.0, 0.0, 0.0,
22
+ 0.0, 1.0, 0.0, 0.0,
23
+ 0.0, 0.0, 1.0, 0.0,
24
+ 0.0, 0.0, 0.0, 1.0,
25
+ )
26
+
27
+
28
+ def find_step_path(cad_ref: str) -> Path | None:
29
+ source = find_source_by_cad_ref(cad_ref)
30
+ if source is not None and source.kind in {"part", "assembly"}:
31
+ return source.step_path.resolve() if source.step_path is not None else None
32
+ return None
33
+
34
+
35
+ def resolve_cad_source_path(cad_ref: str) -> tuple[str, Path] | None:
36
+ source = find_source_by_cad_ref(cad_ref)
37
+ if source is not None:
38
+ if source.kind == "assembly":
39
+ return "assembly", source.source_path
40
+ if source.kind == "part":
41
+ step_path = source.step_path
42
+ return ("part", step_path) if step_path is not None else None
43
+ return None
44
+
45
+
46
+ def multiply_transforms(left: tuple[float, ...], right: tuple[float, ...]) -> tuple[float, ...]:
47
+ product: list[float] = []
48
+ for row in range(4):
49
+ for column in range(4):
50
+ total = 0.0
51
+ for offset in range(4):
52
+ total += left[(row * 4) + offset] * right[(offset * 4) + column]
53
+ product.append(total)
54
+ return tuple(product)
@@ -0,0 +1,107 @@
1
+ """Compact failure reporting for the CAD CLIs.
2
+
3
+ A generator that raises used to reach the interpreter uncaught, so an ordinary authoring
4
+ mistake printed a 62-line, 4.2 KB traceback whose one useful line -- the exception -- was
5
+ last, under ~50 frames of ``runpy``, the launcher, and cadgen internals. A `gen_step()`
6
+ missing its return printed 43 lines to say ``must return one value``.
7
+
8
+ What a caller actually needs is the failure and WHERE IN THEIR MODEL it happened. Those
9
+ frames are already identifiable: :func:`is_first_party_source_file` is the same predicate
10
+ the source-closure capture uses to tell model code from the stdlib, site-packages, and the
11
+ running runtime. So the report keeps the model's own frames and drops everything else.
12
+
13
+ ``--verbose`` still prints the full traceback -- when the fault IS in cadgen, the internal
14
+ frames are the point.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import sys
20
+ import traceback
21
+ from pathlib import Path
22
+ from typing import TextIO
23
+
24
+ # Deep recursion in a generator (a parametric model calling itself) can produce hundreds of
25
+ # identical model frames. The innermost few are where the failure is.
26
+ _MAX_FRAMES = 6
27
+
28
+
29
+ def _display(filename: str) -> str:
30
+ """Cwd-relative where that is meaningful, otherwise the tail.
31
+
32
+ A runtime frame's absolute path runs through the symlinked skill bundle and is longer
33
+ than the line it is annotating; the last few components identify the module just as
34
+ well without burying the message above it.
35
+ """
36
+ try:
37
+ resolved = Path(filename).resolve()
38
+ except (OSError, ValueError):
39
+ return filename
40
+ try:
41
+ return resolved.relative_to(Path.cwd().resolve()).as_posix()
42
+ except ValueError:
43
+ parts = resolved.parts
44
+ return ("…/" + "/".join(parts[-3:])) if len(parts) > 3 else resolved.as_posix()
45
+
46
+
47
+ def _model_frames(exc: BaseException) -> list[traceback.FrameSummary]:
48
+ """The frames in the caller's own CAD sources, outermost first.
49
+
50
+ Everything else -- the launcher, cadgen, build123d/OCP, the stdlib -- is runtime: it is
51
+ the same in every failure and says nothing about which model broke.
52
+ """
53
+ from cadgen._internal.source_hash import is_first_party_source_file
54
+
55
+ frames: list[traceback.FrameSummary] = []
56
+ for frame in traceback.extract_tb(exc.__traceback__):
57
+ try:
58
+ path = Path(frame.filename).resolve()
59
+ except (OSError, ValueError):
60
+ continue
61
+ if is_first_party_source_file(path):
62
+ frames.append(frame)
63
+ return frames
64
+
65
+
66
+ def report_cli_error(
67
+ exc: BaseException,
68
+ *,
69
+ tool: str,
70
+ verbose: bool = False,
71
+ stream: TextIO | None = None,
72
+ ) -> int:
73
+ """Print ``exc`` as a CLI failure and return the exit code (always 1).
74
+
75
+ Under ``verbose`` this is the raw traceback, unchanged -- a cadgen bug needs its own
76
+ frames. Otherwise it is the exception plus the model frames that led to it.
77
+ """
78
+ out = stream if stream is not None else sys.stderr
79
+ if verbose:
80
+ traceback.print_exception(type(exc), exc, exc.__traceback__, file=out)
81
+ return 1
82
+
83
+ def line(text: str) -> None:
84
+ print(f"[{tool}] {text}", file=out)
85
+
86
+ detail = str(exc).strip()
87
+ line(f"FAILED: {type(exc).__name__}: {detail}" if detail else f"FAILED: {type(exc).__name__}")
88
+
89
+ frames = _model_frames(exc)
90
+ if frames:
91
+ if len(frames) > _MAX_FRAMES:
92
+ line(f" ... {len(frames) - _MAX_FRAMES} earlier frame(s) omitted")
93
+ for frame in frames[-_MAX_FRAMES:]:
94
+ line(f" {_display(frame.filename)}:{frame.lineno} in {frame.name}")
95
+ if frame.line:
96
+ line(f" {frame.line.strip()}")
97
+ else:
98
+ # Nothing of the caller's ran -- a validation or resolution error raised before the
99
+ # generator was reached. Name the frame that raised so it is still diagnosable; the
100
+ # message itself usually already names the offending file.
101
+ raised = traceback.extract_tb(exc.__traceback__)
102
+ if raised:
103
+ innermost = raised[-1]
104
+ line(f" raised in {_display(innermost.filename)}:{innermost.lineno}")
105
+
106
+ line("re-run with --verbose for the full traceback")
107
+ return 1
@@ -0,0 +1,99 @@
1
+ """The CLI half of artifact coordination: how a command-line producer waits, reports the
2
+ wait, and answers when it decides not to wait at all.
3
+
4
+ One implementation for every artifact CLI (``step_artifact``, ``dxf_artifact``,
5
+ ``implicit_artifact``, and the ``gen``/``export`` paths in ``_internal.generation``),
6
+ because the three things below have to agree across all of them:
7
+
8
+ * the flag name and its default, since the CAD Viewer passes ``--lock-timeout`` to whichever
9
+ module a given entry maps to and a module that did not accept it would fail argument
10
+ parsing rather than build;
11
+ * the wait notice, which is the only thing distinguishing a contended acquire from a hang --
12
+ cadgen's acquire is blocking by default, so without it a queued build emits nothing on
13
+ either stream for as long as the peer holds the lock;
14
+ * the shape of the "a peer has it" payload, which the viewer branches on.
15
+
16
+ Not in :mod:`cadgen.coordination`: that package is the cross-process protocol shared with
17
+ the viewer's long-lived server, and it has no business knowing about argparse or loggers.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ from pathlib import Path
24
+ from typing import Callable
25
+
26
+ from cadgen.cli_logging import CliLogger, format_elapsed
27
+
28
+ # Waiting is the CLI default on purpose: an agent that asked for a build wants the build,
29
+ # and giving up would leave it with no artifact and nothing to do about it. The viewer is
30
+ # the caller that must not block, and it passes an explicit timeout.
31
+ WAIT_FOREVER = 0.0
32
+
33
+ _LOCK_TIMEOUT_HELP = (
34
+ "Give up after SECONDS if another run holds this model's generation lock, reporting "
35
+ '{"ok":true,"contended":true} instead of building. 0 (the default) waits for the peer; '
36
+ "either way the wait itself is reported on stderr."
37
+ )
38
+
39
+
40
+ def add_lock_timeout_argument(parser: argparse.ArgumentParser) -> None:
41
+ parser.add_argument(
42
+ "--lock-timeout",
43
+ type=float,
44
+ default=WAIT_FOREVER,
45
+ metavar="SECONDS",
46
+ help=_LOCK_TIMEOUT_HELP,
47
+ )
48
+
49
+
50
+ def deadline_ms(lock_timeout_s: float | None) -> float | None:
51
+ """``deadline_ms`` for :func:`cadgen.coordination.artifact_build`. None means block."""
52
+ timeout = float(lock_timeout_s or 0.0)
53
+ return timeout * 1000.0 if timeout > 0 else None
54
+
55
+
56
+ def lock_wait_notice(logger: CliLogger | None, ref: str) -> Callable[[float], None] | None:
57
+ """A callback that says WHY the process is sitting still, while it still is.
58
+
59
+ Repeats on the interval :mod:`cadgen.coordination.lock` applies, so a long wait keeps
60
+ proving it is alive instead of looking wedged."""
61
+ if logger is None:
62
+ return None
63
+
64
+ def notice(elapsed_s: float) -> None:
65
+ logger.info(
66
+ f"waiting for another run to finish building {ref} "
67
+ f"({format_elapsed(elapsed_s)} so far)"
68
+ )
69
+
70
+ return notice
71
+
72
+
73
+ def contended_payload(
74
+ *,
75
+ source_ref: str,
76
+ cad_ref: str = "",
77
+ package_dir: Path | str | None = None,
78
+ **extra: object,
79
+ ) -> dict[str, object]:
80
+ """The answer when a peer holds the lock and this run declined to wait for it.
81
+
82
+ ``ok`` is true because nothing went wrong -- the model is being built, just not here --
83
+ and ``contended`` is what a caller branches on. Deliberately claims no artifact: the
84
+ peer decides what the package ends up containing.
85
+ """
86
+ payload: dict[str, object] = {
87
+ "ok": True,
88
+ "contended": True,
89
+ "skipped": True,
90
+ "sourceRef": source_ref,
91
+ }
92
+ if cad_ref:
93
+ payload["cadPath"] = cad_ref
94
+ if package_dir is not None:
95
+ from cadgen.render import relative_to_cwd
96
+
97
+ payload["packagePath"] = relative_to_cwd(Path(package_dir))
98
+ payload.update({key: value for key, value in extra.items() if value is not None})
99
+ return payload