feat: 接入 SCDM 优先编辑闭环并支持阵列局部间距

接入 SCDM probe/edit/cache/校验链路,增强孔组、阵列、关系式和参数表交互。

支持阵列相邻段间距、移动意图切换、结果回滚校验,并补充对应回归脚本。
This commit is contained in:
2026-08-19 18:02:47 +08:00
parent a3eb7e2476
commit 4e7877e05c
21 changed files with 5646 additions and 491 deletions
+42 -3
View File
@@ -42,6 +42,7 @@ from .relation_formulas import (
parse_relation_formula,
relation_value_to_text,
rewrite_relation_formula_ids,
validate_relation_formula_graph,
)
from .scdm_backend import resolve_scdm_backend
from .scdm_edit_runner import run_scdm_edit_job
@@ -3100,6 +3101,7 @@ class WindowStateMixin:
text = self.relation_formula_input.text().strip() if hasattr(self, "relation_formula_input") else ""
try:
formula = parse_relation_formula(text)
self._validate_relation_formula_graph(formula)
if not replay_active:
self._validate_relation_formula_references(formula)
except RelationFormulaError as exc:
@@ -3453,6 +3455,18 @@ class WindowStateMixin:
for ref in formula.references:
self._relation_value_for_ref(ref)
def _validate_relation_formula_graph(self, new_formula) -> None:
formulas = []
for item in getattr(self, "relation_formula_items", []) or []:
if not bool(item.get("enabled", True)):
continue
try:
formulas.append(parse_relation_formula(str(item.get("text") or "")))
except RelationFormulaError:
continue
formulas.append(new_formula)
validate_relation_formula_graph(formulas)
def _relation_parameter_supported(self, ref: ObjectParameterRef, *, target: bool = False) -> None:
if target and self._relation_visible_spec_for_ref(ref) is not None:
return
@@ -4565,7 +4579,7 @@ class WindowStateMixin:
return inner_wires > 0 or boundary_edges >= 16
def _scdm_selection_status_message(self, scdm_specs: list[dict[str, object]]) -> str:
if self.selected_kind not in {"feature", "face", "edge"}:
if self.selected_kind not in {"feature", "face", "edge", "solid", "part"}:
return ""
if self.model is None:
return ""
@@ -8915,8 +8929,12 @@ class WindowStateMixin:
def _scdm_property_target_value(self, spec: dict[str, object], text: str) -> object:
value_type = str(spec.get("value_type") or "number")
unit_scale = _float_or_none(spec.get("scdm_unit_scale"))
if unit_scale is None or unit_scale <= 0:
unit_scale = 1.0
if value_type == "vector3":
return list(self._parse_property_vector3(text))
values = list(self._parse_property_vector3(text))
return [value * unit_scale for value in values] if self._scdm_property_uses_length_units(spec) else values
if value_type in {"number", "positive"}:
try:
value = float(text)
@@ -8924,11 +8942,32 @@ class WindowStateMixin:
raise ValueError("请输入数字形式的目标值。") from exc
if value_type == "positive" and value <= 0:
raise ValueError("请输入大于 0 的目标值。")
return value
return value * unit_scale if self._scdm_property_uses_length_units(spec) else value
if value_type == "command":
return True
return text
@staticmethod
def _scdm_property_uses_length_units(spec: dict[str, object]) -> bool:
key = str(spec.get("scdm_capability_key") or spec.get("key") or "")
value_type = str(spec.get("value_type") or "")
if value_type == "vector3":
return True
suffixes = (
".diameter",
".radius",
".offset",
".width",
".depth",
".height",
".distance",
".thickness",
".spacing",
".segment_spacing",
".position",
)
return key.endswith(suffixes)
@Slot(object)
def _finish_scdm_edit_action(self, result: object) -> None:
if hasattr(self, "_reroute_to_ui_thread") and self._reroute_to_ui_thread(lambda result=result: self._finish_scdm_edit_action(result)):