feat: 完善 Face 一级关系编辑和稳定性

This commit is contained in:
2026-08-04 09:35:39 +08:00
parent bb44e3920d
commit 5799d5d813
29 changed files with 6295 additions and 189 deletions
+80 -1
View File
@@ -36,6 +36,13 @@ def _specs(info: dict[str, object]) -> list[dict[str, object]]:
return specs
def _display_specs(info: dict[str, object]) -> list[dict[str, object]]:
probe = _PropertySpecProbe()
probe.selected_kind = "face"
specs = probe._property_editor_specs(info, info)
return probe._sort_property_specs_for_display(specs)
def _scope_mode(specs: list[dict[str, object]], key: str, mode: str) -> dict[str, object]:
for spec in specs:
if spec.get("key") != key:
@@ -145,6 +152,33 @@ def _assert_no_generic_face_leak(keys: set[str], label: str) -> None:
raise SystemExit(f"{label} leaked generic Face edit specs: {leaked}")
def _is_actionable_edit_spec(spec: dict[str, object]) -> bool:
return (
bool(spec.get("editable"))
and bool(spec.get("enabled"))
and bool(spec.get("action"))
and str(spec.get("value_type", "number")) != "command"
)
def _assert_actionable_rows_first(info: dict[str, object], expected_keys: tuple[str, ...], label: str) -> None:
display_specs = _display_specs({**info, "readonly_probe_for_ordering": "readonly"})
seen_non_actionable = False
front_keys: list[str] = []
for spec in display_specs:
key = str(spec.get("key", ""))
if _is_actionable_edit_spec(spec):
if seen_non_actionable:
raise SystemExit(f"{label}: actionable row {key!r} appeared after a read-only/non-action row")
front_keys.append(key)
else:
seen_non_actionable = True
missing = [key for key in expected_keys if key not in front_keys]
if missing:
raise SystemExit(f"{label}: expected editable rows at the front are missing: {missing}; front={front_keys}")
def _collect_legacy_face_terms(value: object, path: str = "specs") -> list[str]:
legacy_terms = ("面内尺寸 1/2", "面内尺寸 1", "面内尺寸 2", "面位置", "偏移距离")
hits: list[str] = []
@@ -292,6 +326,14 @@ def main() -> int:
"plane_origin": (0.0, 0.0, 0.0),
"push_pull_outward_direction": (0.0, 0.0, 1.0),
"normal": (0.0, 0.0, 1.0),
"topology_relation_depth": 1,
"topology_relation_status": "ready",
"same_domain_face_count": 1,
"first_level_boundary_edge_count": 4,
"first_level_boundary_vertex_count": 4,
"first_level_adjacent_face_count": 4,
"first_level_topology_note": "已识别当前 Face 区域 1 个 Face、边界 Edge 4 条、边界 Vertex 4 个、共享边一级相邻 Face 4 个。",
"topology_ignored_relation_note": "当前阶段只传播一级关系;二级、三级拓扑暂不自动递归编辑。",
}
plane_specs = _specs(plane_info)
plane_keys = {str(spec.get("key", "")) for spec in plane_specs}
@@ -309,7 +351,35 @@ def main() -> int:
_assert_label(plane_specs, "local_face_width", "面宽")
_assert_label(plane_specs, "local_face_height", "面高")
_assert_label(plane_specs, "face_target_normal_position", "面偏移")
_assert_actionable_rows_first(
plane_info,
(
"area",
"local_face_width",
"local_face_height",
"face_center_position",
"face_target_normal_position",
),
"plane Face display order",
)
_assert_no_legacy_face_terms(plane_specs)
plane_feature_probe = _PropertySpecProbe()
plane_feature_probe.selected_kind = "feature"
plane_feature_specs, _used = plane_feature_probe._editable_property_specs(plane_info)
plane_feature_rows = plane_feature_probe._feature_property_specs(plane_feature_specs, plane_info)
plane_feature_keys = {str(spec.get("key", "")) for spec in plane_feature_rows}
topology_spec = _spec(plane_feature_rows, "face_first_level_topology")
topology_text = str(topology_spec.get("current_text") or "")
for fragment in ("Face 区域 1 个", "边界 Edge 4 条", "共享边相邻 Face 4 个"):
if fragment not in topology_text:
raise SystemExit(f"plane feature topology row should explain first-level counts, got {topology_spec}")
if "face_target_normal_position" not in plane_feature_keys:
raise SystemExit(
"plane feature mode should expose the current Face offset parameter; "
f"got {sorted(plane_feature_keys)}"
)
if "no_editable_feature_dimensions" in plane_feature_keys:
raise SystemExit("plane feature mode should not fall back to no editable dimensions")
_assert_hard_range(plane_specs, "area", 0.25, 2500.0)
_assert_hard_range(plane_specs, "local_face_width", 0.5, 50.0)
_assert_hard_range(plane_specs, "local_face_height", 0.5, 50.0)
@@ -526,9 +596,18 @@ def main() -> int:
),
)
for label, info, required in analytic_cases:
keys = _spec_keys(info)
specs = _specs(info)
keys = {str(spec.get("key", "")) for spec in specs}
_assert_no_generic_face_leak(keys, label)
_assert_contains(keys, required, label)
if label == "cone feature":
_assert_label(specs, "cone_reference_radius", "参考半径")
_assert_label(specs, "cone_reference_diameter", "参考直径")
_assert_label(specs, "cone_semi_angle_degrees", "圆锥半角")
display_specs = _display_specs(info)
surface_spec = _spec(display_specs, "surface")
if surface_spec.get("current_text") != "圆锥面 / 拔模面":
raise SystemExit(f"cone surface should be displayed in user-facing Chinese, got {surface_spec}")
_assert_target_change_detection()
_assert_holed_plane_local_scopes_disabled()