优化skill,添加特征树、编辑窗口
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Normalize backend-native CAD generation artifacts into DesignIR 3.0.
|
||||
|
||||
This script intentionally treats backend-native source or model graph files as
|
||||
the editable authority. DesignIR records the contract, parameters, validation
|
||||
evidence, and a non-authoritative SurfaceIR snapshot compiled from the generated
|
||||
STEP.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
from designir_codec import write_designir
|
||||
|
||||
import surfaceir_pipeline
|
||||
|
||||
|
||||
def _safe_id(value: str, fallback: str) -> str:
|
||||
token = re.sub(r"[^a-zA-Z0-9_.-]+", "_", value.strip()).strip("._-")
|
||||
return token or fallback
|
||||
|
||||
|
||||
def _read_json(path: Path | None) -> dict[str, Any]:
|
||||
if path is None:
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
if not isinstance(payload, dict):
|
||||
return {}
|
||||
return payload
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _parameter_record(
|
||||
item: dict[str, Any],
|
||||
*,
|
||||
backend: str,
|
||||
source_path: str | None,
|
||||
graph_path: str | None,
|
||||
) -> tuple[str, dict[str, Any]] | None:
|
||||
name = str(item.get("name") or item.get("id") or "").strip()
|
||||
if not name:
|
||||
return None
|
||||
try:
|
||||
value = float(item.get("value"))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
binding_kind = str(item.get("binding_kind") or "python_constant")
|
||||
binding_source = str(item.get("source_path") or source_path or "")
|
||||
if binding_kind == "model_graph_parameter" and graph_path:
|
||||
binding_source = str(item.get("source_path") or graph_path)
|
||||
minimum = item.get("min", item.get("minimum"))
|
||||
maximum = item.get("max", item.get("maximum"))
|
||||
record: dict[str, Any] = {
|
||||
"value": value,
|
||||
"unit": str(item.get("unit") or "mm"),
|
||||
"editable": bool(item.get("editable", True)),
|
||||
"semantic_role": str(item.get("semantic_role") or name),
|
||||
"display_name": item.get("display_name") or item.get("label") or name,
|
||||
"description": item.get("description") or "",
|
||||
"edit_state": str(item.get("edit_state") or "backend_bound_parameter"),
|
||||
"backend_binding": {
|
||||
"backend": backend,
|
||||
"source_path": binding_source,
|
||||
"binding_kind": binding_kind,
|
||||
"parameter_path": str(item.get("parameter_path") or f"PARAMETERS.{name}"),
|
||||
"regenerate_adapter": str(item.get("regenerate_adapter") or backend),
|
||||
},
|
||||
}
|
||||
if minimum is not None:
|
||||
record["min"] = minimum
|
||||
if maximum is not None:
|
||||
record["max"] = maximum
|
||||
for key in ("step", "precision", "group", "validated_range"):
|
||||
if key in item:
|
||||
record[key] = item[key]
|
||||
return name, record
|
||||
|
||||
|
||||
def normalize_backend_result(
|
||||
*,
|
||||
request: str,
|
||||
backend: str,
|
||||
source_of_truth: str,
|
||||
step_path: Path,
|
||||
output_path: Path,
|
||||
model_id: str,
|
||||
family: str,
|
||||
native_source_path: str | None,
|
||||
backend_graph_path: str | None,
|
||||
metadata_path: Path | None,
|
||||
validation_paths: list[str],
|
||||
) -> dict[str, Any]:
|
||||
metadata = _read_json(metadata_path)
|
||||
parameters: dict[str, Any] = {}
|
||||
for item in metadata.get("parameters", []):
|
||||
if isinstance(item, dict):
|
||||
parsed = _parameter_record(
|
||||
item,
|
||||
backend=backend,
|
||||
source_path=native_source_path,
|
||||
graph_path=backend_graph_path,
|
||||
)
|
||||
if parsed:
|
||||
name, record = parsed
|
||||
parameters[name] = record
|
||||
|
||||
features = []
|
||||
for index, item in enumerate(metadata.get("features", []), start=1):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
feature_id = str(item.get("id") or f"backend_feature_{index}")
|
||||
features.append(
|
||||
{
|
||||
**item,
|
||||
"id": feature_id,
|
||||
"operation": str(item.get("operation") or "backend_native_feature"),
|
||||
"backend": backend,
|
||||
"original_history_recovered": False,
|
||||
"source_of_truth": source_of_truth,
|
||||
}
|
||||
)
|
||||
if not features:
|
||||
features.append(
|
||||
{
|
||||
"id": "backend_native_model",
|
||||
"operation": "backend_native_feature",
|
||||
"backend": backend,
|
||||
"source_of_truth": source_of_truth,
|
||||
"original_history_recovered": False,
|
||||
"description": "Model was authored by the selected backend's native generation flow.",
|
||||
}
|
||||
)
|
||||
|
||||
surface_payload: dict[str, Any] | None = None
|
||||
surface_snapshot: dict[str, Any] = {
|
||||
"status": "materialized",
|
||||
"source": "generated_step",
|
||||
}
|
||||
try:
|
||||
surface_payload = surfaceir_pipeline.extract_surfaceir(step_path)
|
||||
except Exception as exc:
|
||||
# SurfaceIR is a non-authoritative snapshot for backend-native models.
|
||||
# Unsupported STEP surface vocabulary must not discard a valid STEP,
|
||||
# native source, or replayable model graph.
|
||||
surface_snapshot = {
|
||||
"status": "unavailable",
|
||||
"source": "generated_step",
|
||||
"error": str(exc),
|
||||
}
|
||||
has_surface_snapshot = surface_payload is not None
|
||||
reconstruction_mode = (
|
||||
"hybrid_semantic_surface_parametric"
|
||||
if has_surface_snapshot
|
||||
else "fully_semantic_parametric"
|
||||
)
|
||||
designir: dict[str, Any] = {
|
||||
"schema_version": "3.0",
|
||||
"designir_kind": "independent_parametric_cad",
|
||||
"model_id": model_id,
|
||||
"family": family,
|
||||
"units": str(metadata.get("units") or "mm"),
|
||||
"document_status": (
|
||||
surface_payload.get("document_status", "geometry_present")
|
||||
if surface_payload
|
||||
else "geometry_present"
|
||||
),
|
||||
"reconstruction_mode": reconstruction_mode,
|
||||
"authoring_mode": "semantic_feature_program",
|
||||
"backend_hint": backend,
|
||||
"source_of_truth": {
|
||||
"kind": source_of_truth,
|
||||
"backend": backend,
|
||||
"native_source_path": native_source_path,
|
||||
"backend_graph_path": backend_graph_path,
|
||||
"primary_step_path": str(step_path),
|
||||
"primary_step_sha256": _sha256(step_path),
|
||||
},
|
||||
"semantic_layer": {
|
||||
"reconstruction_status": "ready" if has_surface_snapshot else "partial",
|
||||
"coordinate_system": {
|
||||
"origin": [0, 0, 0],
|
||||
"x_axis": [1, 0, 0],
|
||||
"y_axis": [0, 1, 0],
|
||||
"z_axis": [0, 0, 1],
|
||||
},
|
||||
"datums": {
|
||||
"primary_axis": {"kind": "axis", "axis": "z"},
|
||||
},
|
||||
"parameters": parameters,
|
||||
"expressions": {},
|
||||
"sketches": [],
|
||||
"constraints": [],
|
||||
"features": features,
|
||||
"patterns": [],
|
||||
"attachments": [
|
||||
{
|
||||
"id": "backend_native_authority",
|
||||
"kind": "source_of_truth",
|
||||
"backend": backend,
|
||||
"source_of_truth": source_of_truth,
|
||||
"native_source_path": native_source_path,
|
||||
"backend_graph_path": backend_graph_path,
|
||||
}
|
||||
],
|
||||
"construction_stages": [
|
||||
{
|
||||
"id": "backend_native_generation",
|
||||
"kind": "backend_generation",
|
||||
"backend": backend,
|
||||
"validation_artifacts": validation_paths,
|
||||
},
|
||||
{
|
||||
"id": "surfaceir_snapshot",
|
||||
"kind": "non_authoritative_surface_snapshot",
|
||||
**surface_snapshot,
|
||||
},
|
||||
],
|
||||
},
|
||||
"edit_interface": {
|
||||
"semantic_parameters": [
|
||||
name
|
||||
for name, record in parameters.items()
|
||||
if record.get("editable") is True and record.get("backend_binding")
|
||||
],
|
||||
"surface_parameter_groups": (
|
||||
surface_payload.get("edit_interface", {}).get(
|
||||
"surface_parameter_groups", []
|
||||
)
|
||||
if surface_payload
|
||||
else []
|
||||
),
|
||||
"modification_levels": ["backend_native_parameter", "semantic_feature"],
|
||||
"preserved_interfaces": [],
|
||||
},
|
||||
"validation_contract": {
|
||||
"source_independence": True,
|
||||
"geometry_checks": (
|
||||
["backend_native_generation", "step_surfaceir_materialization"]
|
||||
if has_surface_snapshot
|
||||
else ["backend_native_generation"]
|
||||
),
|
||||
"edit_checks": ["backend_bound_parameter_regeneration"],
|
||||
"invariants": [],
|
||||
"perturbations": [],
|
||||
"thresholds": {},
|
||||
"backend_validation_artifacts": validation_paths,
|
||||
"surface_snapshot": surface_snapshot,
|
||||
},
|
||||
}
|
||||
if surface_payload:
|
||||
designir["surface_layer"] = surface_payload["surface_layer"]
|
||||
designir["reconstruction_strategy"] = surface_payload.get(
|
||||
"reconstruction_strategy",
|
||||
{"boundary_strategy": "exact_3d", "selection_status": "default"},
|
||||
)
|
||||
designir["compiled_surface_provenance"] = {
|
||||
"source": "generated_step",
|
||||
"teacher_geometry_used": False,
|
||||
"semantic_layer_authoritative": True,
|
||||
}
|
||||
write_designir(output_path, designir)
|
||||
return {
|
||||
"valid": True,
|
||||
"output": str(output_path.expanduser().resolve()),
|
||||
"schema_version": "3.0",
|
||||
"reconstruction_mode": designir["reconstruction_mode"],
|
||||
"backend": backend,
|
||||
"source_of_truth": source_of_truth,
|
||||
"parameter_count": len(parameters),
|
||||
"feature_count": len(features),
|
||||
"surface_snapshot": surface_snapshot,
|
||||
}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--request", required=True)
|
||||
parser.add_argument("--backend", choices=("build123d", "simplecadapi", "surfaceir"), required=True)
|
||||
parser.add_argument(
|
||||
"--source-of-truth",
|
||||
choices=("native_source", "model_graph", "surfaceir_designir"),
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument("--step", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--model-id", required=True)
|
||||
parser.add_argument("--family", default="backend_native_model")
|
||||
parser.add_argument("--native-source-path")
|
||||
parser.add_argument("--backend-graph-path")
|
||||
parser.add_argument("--metadata", type=Path)
|
||||
parser.add_argument("--validation-artifact", action="append", default=[])
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
try:
|
||||
result = normalize_backend_result(
|
||||
request=args.request,
|
||||
backend=args.backend,
|
||||
source_of_truth=args.source_of_truth,
|
||||
step_path=args.step.expanduser().resolve(),
|
||||
output_path=args.output.expanduser().resolve(),
|
||||
model_id=_safe_id(args.model_id, "backend_native_model"),
|
||||
family=args.family,
|
||||
native_source_path=args.native_source_path,
|
||||
backend_graph_path=args.backend_graph_path,
|
||||
metadata_path=args.metadata,
|
||||
validation_paths=args.validation_artifact,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(json.dumps({"valid": False, "error": str(exc)}, ensure_ascii=False, indent=2))
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user