优化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
@@ -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.",
]
),
},