6 Commits

8 changed files with 796 additions and 5 deletions
@@ -127,6 +127,71 @@ For `max_z`/`min_z` placement intent, use an owner role such as
Use only supplied operations and their exact parameter schemas. Do not invent
parameters, implicit booleans, or substitutes after a capability error.
## Semantic Annotation (meta and intent)
Every document carries semantic annotations for downstream training. They are
purely descriptive: the compiler carries them through verbatim, geometry never
depends on them, and they are never acceptance targets.
Document level — include `meta` with at least one of:
```json
{
"meta": {
"description": "R8 rounded square bushing, 100×100×12, central Ø45 bore",
"function": "Spacer sleeve over a Ø45 shaft; rounded corners for handling"
}
}
```
- `description`: one sentence naming the part with its key specifications
(≤60 characters). `function`: what the part does and where it fits
(≤200 characters). Narratives are written in the request language; keys and
controlled labels are English `snake_case`.
Feature level — attach `intent` to every feature:
```json
{
"intent": {
"label": "shaft_passage",
"summary": "Ø45 central through bore, concentric with the outer contour",
"why": "The fitting face of the sleeve; diameter follows the mating shaft",
"ties_to_requirement": "R2",
"provenance": "authored"
}
}
```
- `label`: one controlled vocabulary term in `snake_case` — for example
`housing_blank`, `shaft_passage`, `fastener_hole`, `bolt_circle`,
`tap_hole`, `counterbore_seat`, `countersink_seat`, `locating_pin_hole`,
`bearing_seat`, `press_fit_boss`, `mounting_boss`, `mounting_foot`,
`lifting_eye`, `slot_adjustment`, `coolant_channel`, `lubrication_gallery`,
`vent_hole`, `drain_port`, `fluid_inlet`, `process_corner_relief`,
`weld_prep`, `machining_setup_tab`, `inspection_access`,
`stress_relief_fillet`, `stiffening_rib`, `weight_relief`,
`mass_saving_pocket`, `load_path_flange`, `wall_thickness_transition`,
`gear_teeth`, `rack_teeth`, `thread_drive`, `cam_track`, `bend_wing`,
`cosmetic_surface`, `datum_plane_feature`, `datum_axis_feature`. The list
is open: an accurate new `snake_case` term is valid, but prefer vocabulary.
- `summary` (required, ≤80 characters): what it is plus the key parameters.
Use parameterized wording (`M8`, `Ø75`, `R8`), never a restatement of the
request prose.
- `why` (optional, ≤400 characters): the functional reason this feature exists.
- `ties_to_requirement` (optional): the requirement id from the task's
requirements contract that this feature implements. Include it whenever the
task carries requirement ids and the feature plainly implements one of them;
omit it when the task has no requirement ids. Never invent an id.
- `provenance`: always `"authored"` when the model writes it.
- Do not invent fields inside `intent`, and never treat a mismatch between an
annotation and geometry as acceptable — the label must match the feature
actually constructed.
- Geometry metrics (`meta.derived_metrics`: volumes, hole counts, per-feature
deltas) are computed and backfilled by the server after rebuild. Never
author or estimate them; if a document already carries `derived_metrics`,
leave it untouched.
## Sketches And Coordinates
Keep a sketch to exactly `workplane` and `profile`. The workplane declares its
@@ -228,6 +293,9 @@ Before returning the document, check every feature against these invariants:
5. Every requested removal intersects its intended material, every repeated
feature has a valid seed reference, and every connected addition uses an
operation whose contract explicitly supports the chosen result mode.
6. Every feature carries an `intent` with a truthful `label`, a parameterized
`summary` of at most 80 characters, and `provenance: "authored"`; the
document carries `meta` with at least one of `description`/`function`.
If any invariant is false, revise the construction before emitting the single
complete `cad.author.v1` document.
+10 -1
View File
@@ -15,6 +15,7 @@ from app.cad_agent.domain.operation_contract import (
validate_operation_contract,
)
from app.cad_agent.ports import AdapterUnavailable
from app.services.derived_metrics import derive_metrics, make_prefix_runner
from app.services.engine_service import load_engine, topology_snapshot, validate_cdsl, validate_cdsl_shape
from app.services.render_bundle import RenderBundleError, render_checkpoint
from app.settings import Settings
@@ -102,7 +103,6 @@ class ProfileCadRuntime:
cdsl_copy = deepcopy(cdsl)
cdsl_copy["part_id"] = task_id
step_path = root / "model.step"
self._write_json(root / "model.cdsl.json", cdsl_copy)
try:
self._validate_finite_tree(cdsl_copy)
validate_cdsl(cdsl_copy, self.engine)
@@ -112,6 +112,15 @@ class ProfileCadRuntime:
health = self._health(engine_result, step_path)
topology = topology_snapshot(engine_result, task_id=task_id, revision_id=revision_id)
self._write_json(root / "model.topology.json", topology)
# Derived metrics are backfilled after validation, so they can
# never influence acceptance; the persisted document carries them
# for training while rebuild behavior stays untouched.
derived = derive_metrics(
cdsl_copy, engine_result, topology,
prefix_runner=make_prefix_runner(self.engine.run_cdsl_only),
)
cdsl_copy.setdefault("meta", {})["derived_metrics"] = derived
self._write_json(root / "model.cdsl.json", cdsl_copy)
report = {"engine_result": engine_result, "preview": {}, "health": health, "render_manifest": {}}
self._write_json(root / "rebuild-report.json", report)
return {"health": health, "topology": topology, "report": report, "render_manifest": {}, "paths": {"cdsl": "model.cdsl.json", "step": "model.step", "topology": "model.topology.json", "report": "rebuild-report.json"}}
@@ -106,10 +106,17 @@ class AuthoringCompiler:
sketch_id = f"sketch_{source_positions[feature.name]:03d}"
sketches.append({"id": sketch_id, **self._runtime_sketch(feature.sketch.model_dump(mode="json"))})
output["sketch_id"] = sketch_id
if feature.intent is not None:
# Semantic annotation only: carried through for training data,
# never consumed by runtime geometry or validation semantics.
output["intent"] = feature.intent.model_dump(mode="json", exclude_none=True)
features.append(output)
document_meta: dict[str, Any] = {"unit": "mm"}
if doc.meta is not None:
document_meta.update(doc.meta.model_dump(mode="json", exclude_none=True))
runtime = {
"schema": "cad.runtime.v1", "schema_version": "1.0.0", "kind": "part",
"part_id": "compiled", "meta": {"unit": "mm"},
"part_id": "compiled", "meta": document_meta,
"bodies": [
{"id": body_ids[body.name], "name": body.name}
for body in doc.bodies
@@ -108,6 +108,33 @@ class SelectorIntent(AuthorModel):
return value
class FeatureIntent(AuthorModel):
"""Feature-level semantic annotation for training data.
Mirrors ``$defs/featureIntent`` in ``cdsl_schema.json``: purely
descriptive, never read by the compiler for geometry decisions.
"""
label: str | None = Field(default=None, pattern=r"^[a-z][a-z0-9_]{2,63}$")
summary: str = Field(min_length=1, max_length=80)
why: str | None = Field(default=None, min_length=1, max_length=400)
ties_to_requirement: str | None = Field(default=None, pattern=r"^[A-Za-z0-9_.:-]{1,80}$")
provenance: Literal["authored", "annotated", "imported"]
class DocumentMeta(AuthorModel):
"""Document-level semantic annotation (part description and function)."""
description: str | None = Field(default=None, min_length=1, max_length=60)
function: str | None = Field(default=None, min_length=1, max_length=200)
@model_validator(mode="after")
def require_at_least_one(self) -> "DocumentMeta":
if self.description is None and self.function is None:
raise ValueError("meta requires at least one of description or function")
return self
class AuthorFeature(AuthorModel):
name: str = Field(pattern=_NAME)
operation: str = Field(pattern=r"^[a-z][a-z0-9_]{0,80}$")
@@ -121,6 +148,10 @@ class AuthorFeature(AuthorModel):
default=None,
description="For sketch operations: exactly {workplane, profile}. Circle profiles use diameter_mm and center_mm.",
)
intent: FeatureIntent | None = Field(
default=None,
description="Optional semantic annotation for training; never consumed by geometry.",
)
class AuthorBody(AuthorModel):
@@ -133,6 +164,10 @@ class AuthoringDocument(AuthorModel):
units: str = Field(default="mm", pattern=r"^mm$")
coordinate_system: str = Field(default="right_handed", pattern=r"^[a-z][a-z0-9_-]{0,40}$")
assumptions: list[str] = Field(default_factory=list, max_length=64)
meta: DocumentMeta | None = Field(
default=None,
description="Optional document-level semantic annotation (description/function).",
)
bodies: list[AuthorBody] = Field(min_length=1, max_length=32)
acceptance_targets: list[dict[str, Any]] = Field(default_factory=list, max_length=128)
@@ -49,7 +49,8 @@ def validate_operation_contract(contract: dict[str, Any]) -> None:
"server_injected_paths", "reference_policy", "semantic_preflight", "candidate_verifiers",
"runtime_capability",
}
if not required.issubset(contract) or set(contract) - required - {"contract_hash", "registry_revision"}:
optional = {"contract_hash", "registry_revision", "nested_selector_policies"}
if not required.issubset(contract) or set(contract) - required - optional:
raise OperationContractError("Operation contract has unknown or missing fields")
if not isinstance(contract["atomic_id"], str) or not contract["atomic_id"]:
raise OperationContractError("Operation contract atomic_id is invalid")
+154
View File
@@ -0,0 +1,154 @@
"""Derived-metrics backfill for CDSL documents (training-grade, server-owned).
The metrics describe the *reconstructed geometry* of a document and are
written exclusively by the server after a successful rebuild. Models never
author them; the fields are excluded from validation ordering (backfilled
after validation) so they can never influence acceptance.
Counting conventions:
- ``hole_count`` : inner cylindrical walls on the final body (cylinder faces
whose ``cylinder_role`` is ``inner``). Countersink/counterbore bores add
their own walls; no coaxial clustering is applied in v1.
- ``fillet_count``: torus faces (rolling-ball blends) on the final body.
- ``face_type_distribution``: final-body faces by ``surface_type``.
- ``per_feature`` : prefix re-execution sampling. Prefix ``i`` re-runs the
first ``i`` features; sampling stops at the first non-executable prefix.
"""
from __future__ import annotations
from collections import Counter
from copy import deepcopy
from pathlib import Path
import tempfile
from typing import Any, Callable
from app.services.engine_service import topology_snapshot
_METRIC_SNAPSHOT_KEYS = (
"volume_mm3", "surface_area_mm2", "bbox_mm", "solid_count",
"face_count", "edge_count", "vertex_count", "hole_count",
"fillet_count", "face_type_distribution",
)
_DELTA_KEYS = ("volume_mm3", "surface_area_mm2", "face_count", "hole_count")
#: Prefix sampling above this feature count degrades to final-only metrics,
#: keeping the O(n^2) re-execution cost bounded on very large documents.
MAX_PER_FEATURE_SAMPLES = 64
def _face_stats(topology: dict[str, Any]) -> dict[str, Any]:
faces = [
record for record in (topology.get("records") or [])
if isinstance(record, dict) and record.get("kind") == "face"
]
distribution: Counter[str] = Counter()
hole_walls = 0
torus = 0
for face in faces:
geometry = face.get("geometry") or {}
surface_type = str(geometry.get("surface_type") or "unknown")
distribution[surface_type] += 1
if surface_type == "cylinder" and geometry.get("cylinder_role") == "inner":
hole_walls += 1
if surface_type == "torus":
torus += 1
return {
"face_type_distribution": dict(sorted(distribution.items())),
"hole_count": hole_walls,
"fillet_count": torus,
}
def _snapshot_from_engine_result(engine_result: dict[str, Any], topology: dict[str, Any]) -> dict[str, Any]:
records = [r for r in (topology.get("records") or []) if isinstance(r, dict)]
faces = [r for r in records if r.get("kind") == "face"]
area = sum(float((f.get("geometry") or {}).get("area_mm2") or 0.0) for f in faces)
snapshot = {
"volume_mm3": float(engine_result.get("volume_mm3") or 0.0),
"surface_area_mm2": area or float(engine_result.get("surface_area_mm2") or 0.0),
"bbox_mm": [float(value) for value in (engine_result.get("bbox_mm") or {}).get("min", [])]
+ [float(value) for value in (engine_result.get("bbox_mm") or {}).get("max", [])],
"solid_count": int(engine_result.get("solid_count") or 0),
"face_count": len(faces),
"edge_count": sum(1 for r in records if r.get("kind") == "edge"),
"vertex_count": sum(1 for r in records if r.get("kind") == "vertex"),
}
snapshot.update(_face_stats(topology))
return {key: snapshot[key] for key in _METRIC_SNAPSHOT_KEYS}
def _delta(after: dict[str, Any], before: dict[str, Any]) -> dict[str, Any]:
return {
"volume_mm3": float(after["volume_mm3"]) - float(before["volume_mm3"]),
"surface_area_mm2": float(after["surface_area_mm2"]) - float(before["surface_area_mm2"]),
"face_count": int(after["face_count"]) - int(before["face_count"]),
"hole_count": int(after["hole_count"]) - int(before["hole_count"]),
}
def derive_metrics(
cdsl: dict[str, Any],
engine_result: dict[str, Any],
topology: dict[str, Any],
*,
engine_build: str = "",
prefix_runner: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""Build the ``meta.derived_metrics`` payload for a successfully rebuilt document.
``prefix_runner`` re-executes a prefix of the document's features and
returns the same shape as ``engine_result``; when omitted, ``per_feature``
degrades to status entries without geometric snapshots.
"""
features = cdsl.get("features") or []
final = _snapshot_from_engine_result(engine_result, topology)
zero = {
"volume_mm3": 0.0, "surface_area_mm2": 0.0,
"face_count": 0, "hole_count": 0,
}
per_feature: list[dict[str, Any]] = []
if prefix_runner is not None and 1 <= len(features) <= MAX_PER_FEATURE_SAMPLES:
previous = zero
for index in range(1, len(features) + 1):
prefix_doc = deepcopy(cdsl)
prefix_doc["features"] = deepcopy(features[:index])
try:
prefix_result = prefix_runner(prefix_doc)
topology_prefix = topology_snapshot(prefix_result)
after = _snapshot_from_engine_result(prefix_result, topology_prefix)
status = str(next(
(fr.get("status") for fr in prefix_result.get("feature_results") or []
if fr.get("feature_id") == features[index - 1].get("id")),
"executed",
))
except Exception:
break # prefix chain broken; keep samples collected so far
per_feature.append({
"feature_id": str(features[index - 1].get("id") or ""),
"atomic_id": str(features[index - 1].get("atomic_id") or ""),
"status": status,
"after": after,
"delta": _delta(after, previous),
})
previous = after
histogram = Counter(str(feature.get("atomic_id") or "") for feature in features)
metrics: dict[str, Any] = {
"engine": str(engine_result.get("engine") or ""),
"final": final,
"per_feature": per_feature,
"atomic_histogram": dict(sorted(histogram.items())),
}
if engine_build:
metrics["engine_build"] = engine_build
return metrics
def make_prefix_runner(run_cdsl_only: Callable[[dict[str, Any], Path], dict[str, Any]]):
"""Wrap an engine ``run_cdsl_only`` entry point for prefix sampling."""
def runner(prefix_doc: dict[str, Any]) -> dict[str, Any]:
with tempfile.TemporaryDirectory() as tmp:
return run_cdsl_only(prefix_doc, Path(tmp) / "prefix.step")
return runner
+76 -2
View File
@@ -9,7 +9,13 @@
"schema_version": {"type": "string"},
"kind": {"type": "string", "minLength": 1},
"part_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{3,80}$"},
"meta": {"type": "object"},
"meta": {
"type": "object",
"properties": {
"derived_metrics": {"$ref": "#/$defs/derivedMetrics"}
},
"additionalProperties": true
},
"bodies": {"type": "array", "items": {"$ref": "#/$defs/runtimeBody"}},
"geometry": {
"type": "object",
@@ -24,6 +30,73 @@
"required": ["schema", "geometry", "features"],
"additionalProperties": false,
"$defs": {
"metricSnapshot": {
"type": "object",
"properties": {
"volume_mm3": {"type": "number"},
"surface_area_mm2": {"type": "number"},
"bbox_mm": {"type": "array", "items": {"type": "number"}, "minItems": 6, "maxItems": 6},
"solid_count": {"type": "integer", "minimum": 0},
"face_count": {"type": "integer", "minimum": 0},
"edge_count": {"type": "integer", "minimum": 0},
"vertex_count": {"type": "integer", "minimum": 0},
"hole_count": {"type": "integer", "minimum": 0},
"fillet_count": {"type": "integer", "minimum": 0},
"face_type_distribution": {"type": "object", "additionalProperties": {"type": "integer", "minimum": 0}}
},
"required": ["volume_mm3", "surface_area_mm2", "bbox_mm", "solid_count", "face_count", "edge_count", "vertex_count", "hole_count", "fillet_count", "face_type_distribution"],
"additionalProperties": false
},
"metricDelta": {
"type": "object",
"properties": {
"volume_mm3": {"type": "number"},
"surface_area_mm2": {"type": "number"},
"face_count": {"type": "integer"},
"hole_count": {"type": "integer"}
},
"required": ["volume_mm3", "surface_area_mm2", "face_count", "hole_count"],
"additionalProperties": false
},
"derivedMetrics": {
"type": "object",
"properties": {
"engine": {"type": "string", "minLength": 1},
"engine_build": {"type": "string", "minLength": 1},
"generated_at": {"type": "string", "minLength": 1},
"final": {"$ref": "#/$defs/metricSnapshot"},
"per_feature": {
"type": "array",
"items": {
"type": "object",
"properties": {
"feature_id": {"type": "string", "minLength": 1},
"atomic_id": {"$ref": "#/$defs/feature_atomic_ids"},
"status": {"type": "string", "minLength": 1},
"after": {"$ref": "#/$defs/metricSnapshot"},
"delta": {"$ref": "#/$defs/metricDelta"}
},
"required": ["feature_id", "atomic_id", "status", "after", "delta"],
"additionalProperties": false
}
},
"atomic_histogram": {"type": "object", "additionalProperties": {"type": "integer", "minimum": 0}}
},
"required": ["engine", "final", "per_feature", "atomic_histogram"],
"additionalProperties": false
},
"featureIntent": {
"type": "object",
"properties": {
"label": {"type": "string", "pattern": "^[a-z][a-z0-9_]{2,63}$"},
"summary": {"type": "string", "minLength": 1, "maxLength": 80},
"why": {"type": "string", "minLength": 1, "maxLength": 400},
"ties_to_requirement": {"type": "string", "pattern": "^[A-Za-z0-9_.:-]{1,80}$"},
"provenance": {"enum": ["authored", "annotated", "imported"]}
},
"required": ["summary", "provenance"],
"additionalProperties": false
},
"number": {"type": "number"},
"positive": {"type": "number", "exclusiveMinimum": 0},
"positiveInteger": {"type": "integer", "minimum": 1},
@@ -1192,7 +1265,8 @@
"params": {"type": "object"},
"execution_status": {"enum": ["supported", "deferred"]},
"selectors": {"type": "array", "items": {"$ref": "#/$defs/selectorRef"}},
"unresolved": {"type": "array", "items": {"type": "string", "minLength": 1}}
"unresolved": {"type": "array", "items": {"type": "string", "minLength": 1}},
"intent": {"$ref": "#/$defs/featureIntent"}
},
"required": ["id", "atomic_id", "depends_on", "params"],
"additionalProperties": false,
@@ -0,0 +1,443 @@
{
"_meta": {
"title": "CDSL 语义词表几何签名(初稿 v0.1)",
"protocol": "cad.cdsl.llm.v1",
"date": "2026-09-14",
"role": "三层关联机制的字典层:label ↔ 几何期望的机器可读绑定。签名是期望(expectation),不是模板(template)。",
"checking_semantics": "签名检查只产生警告与训练数据分级,永不阻塞建模与重建(与 AGENTS.md 一致:不把语义分歧当程序错误)。",
"input_dependency": "metric_expectations 依赖重建管线输出的逐特征指标(derived_metrics per_feature delta)。缺失 delta 时,退化检查为:metric_expectations 中 *_delta 键跳过,其余照查。",
"inheritance": "label 可带 parent,检查时先应用父签名再应用自身(并集)。composite=true 表示该语义典型地由特征组合实现(如打孔+阵列),检查时沿 depends_on 链聚合 delta。",
"out_of_vocabulary": "词表外 label 无签名,程序侧零校验(开放词表的设计使然);训练侧应引导收敛到词表内。",
"known_limitation": "纯用途差异在几何上不可分:coolant_channel 与 lubrication_gallery、cosmetic_surface 与 stress_relief_fillet 的几何签名几乎相同,其区分依赖 intent.why 与上下文,不依赖本签名。",
"cross_field_reference": "param_expectations 的值可以引用另一参数路径(如 {\">\": \"diameter_mm\"}),匹配器解析为跨字段比较。",
"signature_version": "0.1",
"vocabulary_count": 37
},
"signatures": {
"bolt_circle": {
"parent": "fastener_hole",
"composite": true,
"allowed_atomics": ["pattern_circular", "pattern_linear", "hole_wizard", "hole_blind"],
"param_expectations": {
"pattern_count": {">=": 3},
"positions": {">=": 3}
},
"metric_expectations": {
"hole_count_delta": {">=": 3},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"relations": ["instances_same_diameter", "instances_on_common_circle"],
"notes_zh": "一组等径紧固孔沿公共圆周分布。典型实现:hole_wizard(1孔)+pattern_circular 阵列,或一次多 positions。检查时沿 depends_on 聚合打孔与阵列两特征的 delta。"
},
"fastener_hole": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through"],
"param_expectations": {
"end_condition.type": {"in": ["through_all", "through_all_both", "blind"]}
},
"metric_expectations": {
"hole_count_delta": {">=": 1},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "紧固件过孔(光孔)。词表父节点:tap_hole / bolt_circle / 沉头类均为其子型或伴生型。"
},
"tap_hole": {
"parent": "fastener_hole",
"allowed_atomics": ["hole_wizard", "thread_cut"],
"param_expectations": {
"thread": {"required_if_atomic": "hole_wizard"}
},
"metric_expectations": {
"hole_count_delta": {">=": 1},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true,
"creates_thread_helix": false
},
"notes_zh": "螺纹孔。注意:hole_wizard 的 thread 装饰当前被引擎降级为光孔(thread_decoration_ignored 诊断),creates_thread_helix 恒为 false,签名如实反映现状;thread_cut 才产生真实螺旋几何。"
},
"counterbore_seat": {
"parent": "fastener_hole",
"allowed_atomics": ["hole_counterbore", "hole_wizard"],
"param_expectations": {
"counterbore_diameter_mm": {">": "diameter_mm"},
"counterbore_depth_mm": {">": 0}
},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true,
"creates_planar_shoulder": true
},
"notes_zh": "沉头柱坑:主孔 + 同轴大直径浅坑,坑底形成环形平面肩。"
},
"countersink_seat": {
"parent": "fastener_hole",
"allowed_atomics": ["hole_countersink", "hole_wizard"],
"param_expectations": {
"countersink_diameter_mm": {">": "diameter_mm"},
"countersink_angle_rad": {">": 0, "<": 3.141592653589793}
},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cone_face": true
},
"notes_zh": "沉头锥坑:主孔 + 锥形扩口,锥面是与柱面区分的强拓扑指纹。"
},
"locating_pin_hole": {
"allowed_atomics": ["hole_wizard", "hole_blind"],
"param_expectations": {},
"metric_expectations": {
"hole_count_delta": {">=": 1},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "定位销孔。配合公差与双孔位置度是设计要点,但公差不在 CDSL 当前表达能力内,机器只能查到 存在性/数量;配合语义写 why。"
},
"bearing_seat": {
"allowed_atomics": ["extrude_add_blind", "cylinder_add", "revolve_add", "hole_wizard"],
"param_expectations": {},
"metric_expectations": {},
"topology_expectations": {
"creates_cylindrical_fit_surface": true
},
"notes_zh": "轴承位(轴颈或座孔)。功能型语义,几何签名天然宽:内核是圆柱配合面(外圆或内孔),方向(增/减材)与安装形式有关。尺寸配合关系写 why。"
},
"shaft_passage": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through"],
"param_expectations": {
"end_condition.type": {"in": ["through_all", "through_all_both"]}
},
"metric_expectations": {
"hole_count_delta": {">=": 1},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"relations": ["axis_aligned_with_body_center_or_axis"],
"notes_zh": "过轴通孔。贯穿是强约束(签名可查);同心是典型但非必然(签名标注为关系提示)。"
},
"press_fit_boss": {
"allowed_atomics": ["extrude_add_blind", "cylinder_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_outer_cylindrical_face": true
},
"notes_zh": "压配合凸台:增材圆柱特征。过盈量等配合信息写在 why,几何只可查 增材+圆柱面。"
},
"alignment_datum": {
"allowed_atomics": ["reference_plane", "reference_axis", "extrude_add_blind", "hole_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">=": 0}
},
"topology_expectations": {},
"notes_zh": "对中/对位基准结构。可以是参考几何(零材料变化)也可以是小凸台/销孔;签名宽,依赖 why。"
},
"mounting_boss": {
"allowed_atomics": ["extrude_add_blind", "cylinder_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_outer_cylindrical_face": true
},
"notes_zh": "安装凸台:增材圆柱特征,常带后续紧固孔(组合语义)。"
},
"mounting_foot": {
"allowed_atomics": ["extrude_add_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_planar_faces": true
},
"notes_zh": "安装底脚:增材板状特征,形成安装平面。"
},
"lifting_eye": {
"allowed_atomics": ["extrude_add_blind", "revolve_add", "sweep_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "吊环/吊耳:增材结构带贯穿吊孔(吊索穿过)。孔的存在是较强指纹。"
},
"slot_adjustment": {
"allowed_atomics": ["extrude_cut_blind", "extrude_cut_through", "hole_wizard", "hole_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_planar_walls": true
},
"notes_zh": "调整长孔/滑槽:允许位置调节的细长切口。典型实现:长圆 polygon 切除,或两孔+直切。"
},
"coolant_channel": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through", "sweep_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "冷却通道。与 lubrication_gallery 几何签名几乎相同(能力边界案例),区分靠 why 与介质上下文。"
},
"lubrication_gallery": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through", "sweep_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "润滑油路。同上,几何上与冷却通道不可分。"
},
"vent_hole": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through"],
"param_expectations": {},
"metric_expectations": {
"hole_count_delta": {">=": 1},
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "排气孔:小直径贯穿孔,通常贯穿(呼吸/排气的功能要求)。"
},
"drain_port": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "排液口。位置在最低点是设计要点但不在几何签名能力内,写 why。"
},
"fluid_inlet": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through", "extrude_add_blind"],
"param_expectations": {},
"metric_expectations": {},
"topology_expectations": {},
"notes_zh": "进液口。可能是孔(减材)也可能是接管凸台+孔(增减组合),签名宽。"
},
"process_corner_relief": {
"allowed_atomics": ["extrude_cut_blind", "extrude_cut_through", "chamfer", "fillet"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {},
"notes_zh": "工艺让位(避让刀具/相邻件干涉)。材料切除量小是典型特征。"
},
"weld_prep": {
"allowed_atomics": ["chamfer"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_cone_face": true
},
"notes_zh": "焊接坡口:倒角原子直接映射(坡口即倒角),锥面是强指纹。"
},
"machining_setup_tab": {
"allowed_atomics": ["extrude_add_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {},
"notes_zh": "装夹工艺台:临时增材,后续工序去除。生命周期语义写在 why(几何本身与普通小凸台不可分)。"
},
"inspection_access": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_through"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_inner_cylindrical_face": true
},
"notes_zh": "测量/探针可达孔。"
},
"stress_relief_fillet": {
"allowed_atomics": ["fillet"],
"param_expectations": {
"radius_mm": {">": 0}
},
"metric_expectations": {
"volume_delta": {"<": 0},
"face_count_delta": {">": 0}
},
"topology_expectations": {
"creates_torus_face": true
},
"notes_zh": "应力缓解圆角。torus 面(圆角面)+面数增加是修饰操作生效的强指纹;与 cosmetic_surface 的区别是用途而非几何。"
},
"stiffening_rib": {
"allowed_atomics": ["extrude_add_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_planar_faces": true
},
"notes_zh": "加强筋:增材薄壁特征。薄(相对壁厚)是定义的一部分但属跨字段相对约束,初稿不做机器检查。"
},
"weight_relief": {
"allowed_atomics": ["hole_wizard", "hole_blind", "extrude_cut_blind", "extrude_cut_through"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {},
"notes_zh": "减重(孔/槽)。通常多孔或多腔(volume delta 显著为负)。"
},
"mass_saving_pocket": {
"allowed_atomics": ["extrude_cut_blind"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_planar_faces": true
},
"notes_zh": "减重腔:盲切腔体,不留穿。与 weight_relief 的区别是形式(腔 vs 孔阵)。"
},
"load_path_flange": {
"allowed_atomics": ["extrude_add_blind", "revolve_add"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {},
"notes_zh": "承载法兰/接盘。功能型语义,签名宽。"
},
"wall_thickness_transition": {
"allowed_atomics": ["shell", "extrude_add_blind"],
"param_expectations": {},
"metric_expectations": {},
"topology_expectations": {},
"notes_zh": "壁厚过渡。实现多样(抽壳/变截面拉伸),签名宽。"
},
"gear_teeth": {
"allowed_atomics": ["gear_add"],
"param_expectations": {
"module_mm": {">": 0},
"teeth_count": {">=": 8}
},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_involute_or_bspline_faces": true
},
"notes_zh": "轮齿:gear_add 原子专属。渐开线/自由曲面齿面是强指纹(亦可用于检测 gear_add 是否真的生成几何)。"
},
"rack_teeth": {
"allowed_atomics": ["rack_add"],
"param_expectations": {
"module_mm": {">": 0}
},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_involute_or_bspline_faces": true
},
"notes_zh": "齿条齿:rack_add 原子专属。"
},
"thread_drive": {
"allowed_atomics": ["thread_add"],
"param_expectations": {
"pitch_mm": {">": 0}
},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_thread_helix": true
},
"notes_zh": "传动螺纹(外螺纹实体段):thread_add 专属,螺旋面是强指纹。"
},
"cam_track": {
"allowed_atomics": ["sweep_add", "extrude_cut_through", "extrude_cut_blind"],
"param_expectations": {},
"metric_expectations": {},
"topology_expectations": {
"creates_bspline_curve_geometry": true
},
"notes_zh": "凸轮轨道:曲线扫掠或曲线槽,B 样条路径/曲线是其指纹。"
},
"bend_wing": {
"allowed_atomics": ["bend_add"],
"param_expectations": {
"chain": {">=": 1}
},
"metric_expectations": {
"volume_delta": {">": 0}
},
"topology_expectations": {
"creates_cylindrical_bend_face": true
},
"notes_zh": "折弯翼:bend_add 原子专属,折弯内/外圆柱面是指纹。"
},
"cosmetic_surface": {
"allowed_atomics": ["fillet", "chamfer"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"<": 0}
},
"topology_expectations": {
"creates_torus_face": true
},
"notes_zh": "外观修饰(倒圆/倒角)。与 stress_relief_fillet 几何签名几乎相同(能力边界案例),区别写 why。"
},
"datum_plane_feature": {
"allowed_atomics": ["reference_plane"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"==": 0},
"face_count_delta": {"==": 0}
},
"topology_expectations": {},
"notes_zh": "设计基准面:零材料变化 + 零拓扑变化 + 特定原子,是全部签名中最确定的一条(强指纹)。"
},
"datum_axis_feature": {
"allowed_atomics": ["reference_axis"],
"param_expectations": {},
"metric_expectations": {
"volume_delta": {"==": 0},
"face_count_delta": {"==": 0}
},
"topology_expectations": {},
"notes_zh": "设计基准轴:同上,强指纹。"
}
}
}