优化skill,添加特征树、编辑窗口

This commit is contained in:
2026-08-07 17:44:12 +08:00
parent 35e18ca956
commit 5fbcf2c0b8
70 changed files with 13076 additions and 465 deletions
+11 -6
View File
@@ -15,7 +15,11 @@ Choose a backend before modeling, keep its source as the editable authority, and
only its promoted schema `2.0` generalized library.
Never read `cad-experience-plugin/parser/input`, `parser/output`, or private
case JSON during creation, modification, or reconstruction.
2. Run the deterministic router from this skill directory:
2. Run the deterministic router from this skill directory. For a new model it
matches only the requested primary part against the SimpleCADAPI standard
part catalog in `scripts/capabilities.json`. A catalog match selects
SimpleCADAPI; every unmatched request selects text-to-cad/build123d. It does
not read capability documents or let an LLM choose the backend:
```bash
python3 scripts/route.py "<request>" --explain
@@ -46,9 +50,9 @@ Choose a backend before modeling, keep its source as the editable authority, and
geometry acceptance, parameter perturbation acceptance, and artifact
publication. A route decision alone is not a completed reconstruction.
3. Follow the selected route unless a hard environmental constraint makes it unavailable. If overriding it, record the reason in the task manifest.
4. Load only the selected backend reference from `references/capabilities.md` and the matching installed Skill:
- `build123d`: use `$cad`; prefer it for custom brackets, housings, shafts, flanges, fixtures, source-level assemblies, and freeform/custom surface work.
- `simplecadapi`: use an installed SimpleCADAPI 2.0.2 Skill/runtime; prefer it when a standard factory exists, or when replayable ModelResult graphs, semantic lineage, physical units, tolerance chains, scene packages, or CAD translators materially improve the result. Its standard library includes gears, racks, ring and bevel gears, bearings, bolts, nuts, roller-chain sprockets, cycloidal parts, and reducer families.
4. Use the selected backend directly:
- `build123d`: use `$cad` for custom brackets, housings, shafts, flanges, fixtures, source-level assemblies, and every new part outside the catalog.
- `simplecadapi`: use the installed SimpleCADAPI 2.0.2 runtime only for catalogued standard primary parts: gears, ring gears, racks, cycloidal discs, ball bearings, roller-chain sprockets, bolts, and nuts.
5. When the route reports `requirement_refinement` or `visual_repair`, refine the brief, generate, execute, inspect multiple views, and repair no more than three visual mismatch rounds before asking the user.
6. Generate into a task-owned directory and start CAD Viewer with that task directory as `--dir`; do not expose the repository-wide fixture library for a task review. Write one DesignIR 3.0 `*.designir.json` per part and treat it as the editable geometry contract; keep `cad-task.json` as the execution record. Read `references/designir-3.0.md` for both semantic authoring and uploaded STEP reconstruction. `references/designir-2.0.md` is migration-only. Never change backend during an edit unless conversion is explicitly requested; modify the recorded authoritative layer.
For a supplied STEP/STP, use `scripts/route.py --execute`; do not manually
@@ -90,8 +94,9 @@ Choose a backend before modeling, keep its source as the editable authority, and
- Prefer an explicit user backend when it can produce the editable STEP contract.
- Preserve the backend of an explicitly supplied DesignIR or Python generator unless conversion is requested.
- Do not assign a permanent default backend. Score native-source continuity, factory coverage, semantic/replay needs, geometry vocabulary, assembly needs, and validation needs.
- Prefer SimpleCADAPI when a supported standard mechanical factory or its model graph, semantic topology, unit/tolerance, scene, or translator capabilities materially improve editability and verification.
- Prefer build123d for custom machined parts, source-driven assemblies, and freeform or surface-heavy work not covered by a SimpleCADAPI factory or tested operation vocabulary.
- For a new model, do not use semantic, graph, tolerance, assembly, or generic mechanical keywords to choose a backend. Use the standard-part catalog only.
- Select SimpleCADAPI only when the primary requested part matches an entry in `scripts/capabilities.json`. Standard-part words used only as features, such as bolt holes or bearing seats, are excluded from that match.
- Select build123d for every unmatched new request, including custom machined parts, source-driven assemblies, and freeform or surface-heavy work.
- For uploaded STEP, report SurfaceIR as the effective reconstruction engine. The selected authoring backend describes future semantic repair/edit work; it is not allowed to read the teacher STEP during reconstruction.
- Prefer source quality and future modification over the shortest first generation.
@@ -0,0 +1,90 @@
# text-to-cad / build123d Agent Capability Guide
This document is the required backend capability brief for CAD Agent Studio
when considering the `build123d` route through text-to-cad. It is intentionally
an Agent-facing decision document, not a geometry template.
## Role
Use this route when the Agent will author backend-native `build123d` Python
source and the server will only execute, validate, normalize to DesignIR 3.0,
and publish artifacts.
text-to-cad owns orchestration, artifact contracts, DesignIR normalization,
SurfaceIR reconstruction for uploaded STEP, validation, repair workflow, and
Viewer handoff. For direct new model generation, `build123d` source is the
editable source of truth.
## Studio Documentation Discipline
For CAD Agent Studio backend selection, this document is sufficient. Decide
from the capability boundary here: what text-to-cad/build123d is strong at,
what it is weak at, and whether the primary requested object matches those
strengths.
Do not read build123d API manuals, skill internals, or implementation guides
during ordinary backend selection. The selection task only needs to know what
the tool can generate, not the exact API signatures.
## Strong Fits
- Custom mechanical parts: brackets, flanges, wheel hub adapters, housings,
plates, fixtures, covers, shafts, bushings, levers, cranks, connecting rods.
- Feature-rich machined parts with cylinders, boxes, revolve-like stacks,
holes, through cuts, counterbores, pockets, bosses, ribs, shells, chamfers,
fillets, lofts, sweeps, and source-level construction logic.
- Requests where words such as bolt, nut, bearing, gear, or sprocket describe
holes, mounts, clearances, interfaces, or surrounding features rather than
asking for those standard parts as the primary object.
- Geometry where future edits should preserve readable Python parameters and
explicit construction order.
## Weak Fits
- The requested primary object is a standard mechanical component already
covered by a SimpleCADAPI standard factory, such as a gear, bearing, bolt,
nut, roller-chain sprocket, rack, or reducer.
- The request specifically requires SimpleCADAPI model graph replay, semantic
topology lineage, unit/tolerance chains, translator workflows, or strict
B-Rep comparison.
## Agent Source Contract
When choosing this backend, submit raw Python source to `generate_cad` with:
- `selectedBackend = "build123d"`
- `sourceKind = "build123d_python"`
- A script that accepts `--step` and `--metadata`
- STEP export written exactly to the `--step` path
- Metadata JSON written exactly to the `--metadata` path
- stdout JSON is optional diagnostic output; file artifacts are the source of
truth for execution success
If exposing editable parameters, include a top-level block:
```python
# CAD_AGENT_PARAMETERS_START
PARAMETERS = {
"example": 1.0
}
# CAD_AGENT_PARAMETERS_END
```
Each editable metadata parameter must include `name`, `value`, `unit`,
`editable`, `binding_kind`, `parameter_path`, and `regenerate_adapter`.
Use `binding_kind = "python_constant"` and `regenerate_adapter = "build123d"`
for parameters bound to the `PARAMETERS` block.
Do not submit diagnostic, API-probing, topology-probing, radius-sweep, or smoke
test scripts as `nativeSource`. The source must be the final model generator
for the user's part and must write the requested file artifacts in one
execution. If this compact reference is insufficient, report the missing
reference instead of using `generate_cad` as an exploration tool.
## Decision Rule
Choose build123d when the full requested part is a custom feature model and no
SimpleCADAPI standard factory directly represents the primary object. Do not
route to another backend merely because a feature name contains a standard part
word. For example, bolt holes or nut clearances in a flange remain features of
the flange, not a request to generate a bolt or nut.
@@ -16,6 +16,78 @@
"simplecadapi_brep_compare"
]
},
"routing_catalog": {
"selection_rule": "For a new model, choose SimpleCADAPI only when the requested primary object matches one catalog entry. Feature-context matches are excluded. Every other request chooses build123d.",
"source": "SimpleCADAPI/src/simplecadapi/std/{gear,bearing,chain,fastener}.py",
"simplecadapi_standard_part_types": [
{
"type": "spur_gear",
"factory": "simplecadapi.std.gear.make_spur_gear_rsolid",
"aliases": ["齿轮", "直齿轮", "正齿轮", "spur gear"],
"feature_context_exclusions": ["齿轮孔", "齿轮安装", "齿轮座", "齿轮箱", "gear hole", "gear mount", "gear housing", "gearbox"]
},
{
"type": "straight_bevel_gear",
"factory": "simplecadapi.std.gear.make_straight_bevel_gear_rsolid",
"aliases": ["直齿锥齿轮", "锥齿轮", "straight bevel gear", "bevel gear"],
"feature_context_exclusions": ["锥齿轮箱", "bevel gearbox", "锥齿轮座"]
},
{
"type": "helical_gear",
"factory": "simplecadapi.std.gear.make_helical_gear_rsolid",
"aliases": ["斜齿轮", "helical gear"],
"feature_context_exclusions": ["斜齿轮箱", "helical gearbox", "斜齿轮座"]
},
{
"type": "herringbone_gear",
"factory": "simplecadapi.std.gear.make_herringbone_gear_rsolid",
"aliases": ["人字齿轮", "herringbone gear"],
"feature_context_exclusions": ["人字齿轮箱", "herringbone gearbox", "人字齿轮座"]
},
{
"type": "ring_gear",
"factory": "simplecadapi.std.gear.make_spur_ring_gear_rsolid",
"aliases": ["内齿圈", "齿圈", "ring gear"],
"feature_context_exclusions": ["齿圈座", "ring gear housing", "ring gear mount"]
},
{
"type": "gear_rack",
"factory": "simplecadapi.std.gear.make_spur_rack_rsolid",
"aliases": ["齿条", "直齿条", "斜齿条", "人字齿条", "gear rack", "rack gear", "spur rack", "helical rack", "herringbone rack"],
"feature_context_exclusions": ["齿条安装", "齿条座", "rack mount", "rack housing"]
},
{
"type": "cycloidal_disc",
"factory": "simplecadapi.std.gear.make_cycloidal_disc_rsolid",
"aliases": ["摆线盘", "摆线轮", "cycloidal disc"],
"feature_context_exclusions": ["摆线减速器", "cycloidal reducer", "摆线针轮减速器"]
},
{
"type": "ball_bearing",
"factory": "simplecadapi.std.bearing.make_ball_bearing_rassembly",
"aliases": ["轴承", "滚动轴承", "球轴承", "深沟球轴承", "ball bearing"],
"feature_context_exclusions": ["轴承座", "轴承壳", "轴承孔", "轴承安装", "bearing housing", "bearing seat", "bearing hole", "bearing mount"]
},
{
"type": "roller_chain_sprocket",
"factory": "simplecadapi.std.chain.make_roller_chain_sprocket_rsolid",
"aliases": ["滚子链轮", "链轮", "roller chain sprocket", "sprocket"],
"feature_context_exclusions": ["链轮座", "链轮箱", "链轮安装", "sprocket housing", "sprocket mount"]
},
{
"type": "bolt",
"factory": "simplecadapi.std.fastener.make_bolt_rsolid",
"aliases": ["螺栓", "六角螺栓", "内六角螺栓", "螺栓本体", "bolt", "hex bolt", "socket head bolt"],
"feature_context_exclusions": ["螺栓孔", "螺栓圆", "螺栓阵列", "bolt hole", "bolt circle", "bolt pattern"]
},
{
"type": "nut",
"factory": "simplecadapi.std.fastener.make_nut_rsolid",
"aliases": ["螺母", "六角螺母", "螺母本体", "nut", "hex nut"],
"feature_context_exclusions": ["螺母槽", "螺母孔", "螺母避让", "nut pocket", "nut hole", "nut clearance"]
}
]
},
"backends": {
"build123d": {
"project": "text-to-cad",
+177 -10
View File
@@ -181,6 +181,49 @@ def contains_any(text: str, terms: tuple[str, ...]) -> bool:
return any(term in text for term in terms)
def simplecadapi_standard_part_match(
request: str,
registry: dict[str, object],
) -> dict[str, object] | None:
"""Match only a requested *primary* standard part, never a feature word.
The catalog is maintained beside the router from the actual SimpleCADAPI
``simplecadapi.std`` factories. This is intentionally deterministic: an
unmatched or ambiguous request is a build123d request.
"""
catalog = registry.get("routing_catalog", {})
rules = (
catalog.get("simplecadapi_standard_part_types", [])
if isinstance(catalog, dict)
else []
)
text = request.casefold()
best_match: tuple[int, dict[str, object], list[str]] | None = None
for raw_rule in rules:
if not isinstance(raw_rule, dict):
continue
aliases = [str(value).casefold() for value in raw_rule.get("aliases", [])]
exclusions = [str(value).casefold() for value in raw_rule.get("feature_context_exclusions", [])]
matched_aliases = [alias for alias in aliases if alias and alias in text]
if not matched_aliases:
continue
if any(exclusion and exclusion in text for exclusion in exclusions):
continue
# Generic terms such as "gear" must never hide a more specific
# standard-part type such as "herringbone gear".
specificity = max(len(alias) for alias in matched_aliases)
if best_match is None or specificity > best_match[0]:
best_match = (specificity, raw_rule, matched_aliases)
if best_match is None:
return None
_, rule, matched_aliases = best_match
return {
"type": rule.get("type"),
"factory": rule.get("factory"),
"matched_aliases": matched_aliases,
}
def infer_experience_query(request: str) -> tuple[str | None, list[str]]:
text = request.lower()
family = next(
@@ -559,6 +602,127 @@ def route(args: argparse.Namespace) -> dict[str, object]:
assembly=args.assembly,
source_backend=source_backend,
)
if not edit_context and args.backend == "auto":
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
standard_part = simplecadapi_standard_part_match(request, registry)
if not standard_part:
requirements = [
requirement
for requirement in requirements
if requirement != "standard_mechanical_factory"
]
agent_experiences = (
experience_context.get("experiences", [])
if isinstance(experience_context, dict)
else []
)
agent_generalized_methods = [
{
key: item[key]
for key in (
"id",
"kind",
"guidance",
"check",
"repair",
"confidence",
"reconstruction_grammar",
)
if isinstance(item, dict) and key in item
}
for item in agent_experiences
if isinstance(item, dict)
]
agent_reconstruction_grammars = [
item["reconstruction_grammar"]
for item in agent_experiences
if isinstance(item, dict)
and item.get("kind") == "reconstruction_grammar"
and "reconstruction_grammar" in item
]
selected_name = "simplecadapi" if standard_part else "build123d"
backend_info = registry["backends"][selected_name]
source_of_truth = (
"simplecadapi model graph with native Python source"
if selected_name == "simplecadapi"
else "build123d native Python source"
)
selection_reason = (
"matched SimpleCADAPI standard-part catalog entry "
f"{standard_part['type']} ({standard_part['factory']})"
if standard_part
else "no SimpleCADAPI standard-part catalog entry matched; use the general text-to-cad/build123d backend"
)
return {
"schema_version": "1.0",
"request": request,
"selected_backend": selected_name,
"runner_skill": backend_info["runner_skill"],
"project": backend_info["project"],
"adapter": None,
"confidence": "high",
"routing_policy": {
"version": "4.0",
"selection_scope": "new_model_primary_part_type",
"requirements": requirements,
"decision_authority": "cad_router_standard_part_catalog",
"keyword_backend_scoring": True,
"fallback_policy": "unmatched_requests_use_build123d",
"standard_part_match": standard_part,
"server_side_template_generation": False,
"effective_execution_engine": selected_name,
"validation_engines": ["backend_geometry_validation"],
},
"backend_scores": [
{
"backend": selected_name,
"score": 100,
"reasons": [selection_reason],
},
{
"backend": "build123d" if selected_name == "simplecadapi" else "simplecadapi",
"score": 0,
"reasons": ["not selected by the standard-part catalog"],
},
],
"fallback_order": ["build123d"] if selected_name == "simplecadapi" else [],
"workflow_profiles": [],
"artifact_contract": {
"primary_format": "step",
"editable_contract": "DesignIR 3.0 normalized contract",
"editable_contract_role": "contract_after_backend_generation",
"authoritative_geometry_source": source_of_truth,
},
"source_of_truth": source_of_truth,
"experience_context": experience_context,
"design_plan": {
"plan_kind": "deterministic_standard_part_route",
"operation": "create",
"part_family": experience_family or "unclassified",
"requested_feature_roles": sorted(set(experience_features)),
"selected_backend": selected_name,
"source_of_truth": source_of_truth,
"standard_part_match": standard_part,
"generalized_methods": agent_generalized_methods,
"reconstruction_grammars": agent_reconstruction_grammars,
"experience_status": (
"matched"
if experience_context and experience_context.get("experiences")
else "no_promoted_method_matched"
),
"execution_steps": [
"CAD Router selects the backend from the standard-part catalog.",
"Agent writes backend-native Python source for the full request.",
"Server executes the submitted source and rejects missing artifacts.",
"Server normalizes backend output into DesignIR 3.0.",
],
},
"project_contributions": {
"text-to-cad": "default backend for every request outside the SimpleCADAPI standard-part catalog",
"SimpleCADAPI": "backend for the catalogued standard mechanical part types only",
},
"availability": None,
}
scores = {
"build123d": BackendScore(
"build123d",
@@ -692,12 +856,13 @@ def route(args: argparse.Namespace) -> dict[str, object]:
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
backend_info = registry["backends"][selected.name]
editable_contract = "DesignIR 3.0"
source_of_truth = (
"per-part DesignIR 3.0 with an isolated SurfaceIR compiler"
if teacher_step_reconstruction
else "per-part semantic DesignIR 3.0 with an isolated backend compiler"
)
editable_contract = "DesignIR 3.0 normalized contract"
if teacher_step_reconstruction:
source_of_truth = "surfaceir_designir"
elif selected.name == "simplecadapi":
source_of_truth = "simplecadapi model graph with native Python source"
else:
source_of_truth = "build123d native Python source"
gap = selected.score - max((scores[name].score for name in scores if name != selected.name), default=0)
confidence = "high" if gap >= 40 else "medium" if gap >= 15 else "low"
result: dict[str, object] = {
@@ -740,6 +905,8 @@ def route(args: argparse.Namespace) -> dict[str, object]:
"artifact_contract": {
"primary_format": "step",
"editable_contract": editable_contract,
"editable_contract_role": "contract_after_backend_generation",
"authoritative_geometry_source": source_of_truth,
},
"source_of_truth": source_of_truth,
"experience_context": experience_context,
@@ -831,10 +998,10 @@ def route(args: argparse.Namespace) -> dict[str, object]:
if edit_context
else [
"Resolve explicit user dimensions and preserve them as authoritative parameters.",
"Instantiate matched reconstruction grammars as DesignIR parameters, datums, constraints, and canonical feature stages; never copy teacher geometry.",
"Translate requested feature roles and matched generalized methods into backend operations.",
"Generate the editable source of truth and STEP artifact.",
"Validate topology, dimensions, feature relationships, surface mix, and export integrity against the grammar validation roles.",
"Translate requested feature roles and matched generalized methods into the selected backend's native operations.",
"Generate backend-native source or model graph and export the primary STEP artifact.",
"Normalize backend metadata into DesignIR 3.0 for parameters, feature tree, validation, and future edit bindings.",
"Validate topology, dimensions, feature relationships, surface mix, and export integrity against the brief.",
]
),
},