feat: 完善 Face 一级编辑与隔离计算

This commit is contained in:
2026-08-05 15:08:16 +08:00
parent a76282d7dd
commit ed1faf51d8
28 changed files with 3740 additions and 876 deletions
+103 -9
View File
@@ -9,7 +9,7 @@ if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from step_editor.window_state import WindowStateMixin
from step_editor.ui_helpers import _format_value
from step_editor.ui_helpers import INFO_LABELS, _format_value
class _PropertySpecProbe(WindowStateMixin):
@@ -181,7 +181,20 @@ def _assert_actionable_rows_first(info: dict[str, object], expected_keys: tuple[
def _collect_legacy_face_terms(value: object, path: str = "specs") -> list[str]:
legacy_terms = ("面内尺寸 1/2", "面内尺寸 1", "面内尺寸 2", "面位置", "偏移距离")
legacy_terms = (
"面内尺寸 1/2",
"面内尺寸 1",
"面内尺寸 2",
"面位置",
"偏移距离",
"面偏移",
"面宽",
"面高",
"推拉当前面",
"只改当前面",
"调整整个特征",
"移动整个特征",
)
hits: list[str] = []
if isinstance(value, dict):
for key, child in value.items():
@@ -229,6 +242,13 @@ def _assert_no_legacy_face_source_terms() -> None:
re.compile(r"面内尺寸 2"),
re.compile(r"(?<![端平底])面位置"),
re.compile(r"偏移距离"),
re.compile(r"面偏移"),
re.compile(r"面宽"),
re.compile(r"(?<!侧)面高(?!度)"),
re.compile(r"推拉当前面"),
re.compile(r"只改当前面"),
re.compile(r"调整整个特征"),
re.compile(r"移动整个特征"),
)
hits: list[str] = []
for file_path in files:
@@ -309,13 +329,19 @@ def _assert_holed_plane_local_scopes_disabled() -> None:
raise SystemExit("Face plane-offset push/pull scope should remain available for a holed planar Face")
semantics = _spec(specs, "face_edit_semantics")
if "不能只改当前面" not in str(semantics.get("current_text") or ""):
if "不能局部重建" not in str(semantics.get("current_text") or ""):
raise SystemExit(f"Face edit semantics should summarize the local blocker: {semantics}")
if "has inner boundary" not in str(semantics.get("disabled_tip") or ""):
raise SystemExit(f"Face edit semantics tip should include the blocker: {semantics}")
def main() -> int:
if INFO_LABELS.get("face_id") != "当前拓扑 Face ID":
raise SystemExit("raw face_id label should make clear that it is the current topological Face ID")
for key in ("selection_title", "selection_display_id", "selection_topological_face_id"):
if key not in INFO_LABELS:
raise SystemExit(f"{key} should have a user-facing label")
relation_depths_text = _format_value(("second-level", "third-level", "deeper"))
if "second-level" not in relation_depths_text or "deeper" not in relation_depths_text:
raise SystemExit(f"relation-depth tuple should render as text: {relation_depths_text!r}")
@@ -353,9 +379,9 @@ def main() -> int:
},
"plane Face",
)
_assert_label(plane_specs, "local_face_width", "面宽")
_assert_label(plane_specs, "local_face_height", "面高")
_assert_label(plane_specs, "face_target_normal_position", "偏移")
_assert_label(plane_specs, "local_face_width", "U向尺寸")
_assert_label(plane_specs, "local_face_height", "V向尺寸")
_assert_label(plane_specs, "face_target_normal_position", "偏移曲面")
_assert_actionable_rows_first(
plane_info,
(
@@ -378,9 +404,18 @@ def main() -> int:
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:
expected_plane_feature_keys = {
"area",
"local_face_width",
"local_face_height",
"face_center_position",
"face_target_normal_position",
}
missing_plane_feature_keys = expected_plane_feature_keys - plane_feature_keys
if missing_plane_feature_keys:
raise SystemExit(
"plane feature mode should expose the current Face offset parameter; "
"plain plane feature mode should expose the same first-level editable Face parameters; "
f"missing {sorted(missing_plane_feature_keys)}, "
f"got {sorted(plane_feature_keys)}"
)
if "no_editable_feature_dimensions" in plane_feature_keys:
@@ -470,6 +505,12 @@ def main() -> int:
mode,
("5%", "5 倍", "会被阻止"),
)
shell_local = _scope_mode(shell_specs, "shell_thickness_estimate", "local")
if shell_local.get("label") != "拉伸/切除":
raise SystemExit(f"shell thickness local scope should expose an edit semantic, got {shell_local}")
shell_owning = _scope_mode(shell_specs, "shell_thickness_estimate", "owning")
if shell_owning.get("label") != "缩放特征":
raise SystemExit(f"shell thickness owning scope should expose an edit semantic, got {shell_owning}")
cylinder_keys = _spec_keys(
{
@@ -549,9 +590,62 @@ def main() -> int:
raise SystemExit(f"generic cylinder height should default to owning-axis resize: {generic_height}")
if generic_height.get("action") != "resize_cylindrical_height_owning_scale":
raise SystemExit(f"generic cylinder height should use owning-axis action by default: {generic_height}")
if generic_height.get("scope_text") != "调整整个特征":
if generic_height.get("scope_text") != "缩放特征":
raise SystemExit(f"generic cylinder height should show owning scope by default: {generic_height}")
split_full_cylinder_specs = _specs(
{
"surface": "cylinder",
"feature_guess": "",
"diameter": 6.0,
"radius": 3.0,
"angular_span": 3.141592653589793,
"same_domain_angular_span": 6.283185307179586,
"is_full_cylinder": True,
"height_estimate": 11.0,
"same_domain_height_estimate": 11.0,
"area": 207.0,
"area_center": (0.0, 0.0, 5.5),
"bbox_center": (0.0, 0.0, 5.5),
"axis": (0.0, 0.0, 1.0),
"axis_point": (0.0, 0.0, 0.0),
"axis_center": (0.0, 0.0, 5.5),
}
)
split_full_height_local = _scope_mode(split_full_cylinder_specs, "cylinder_height", "local")
if not bool(split_full_height_local.get("enabled")):
raise SystemExit(
"split same-domain full cylinder should keep local height push/pull enabled: "
f"{split_full_height_local}"
)
split_full_hole_keys = _spec_keys(
{
"surface": "cylinder",
"feature_guess": "hole/groove candidate",
"diameter": 6.0,
"radius": 3.0,
"angular_span": 3.141592653589793,
"same_domain_angular_span": 6.283185307179586,
"is_full_cylinder": True,
"height_estimate": 11.0,
"same_domain_height_estimate": 11.0,
"area": 188.0,
"area_center": (0.0, 0.0, 5.5),
"bbox_center": (0.0, 0.0, 5.5),
"axis": (0.0, 0.0, 1.0),
"axis_point": (0.0, 0.0, 0.0),
"axis_center": (0.0, 0.0, 5.5),
"slot_chord_width_estimate": 6.0,
"slot_sagitta_depth_estimate": 3.0,
"slot_arc_length_estimate": 9.42477796076938,
"feature_bottom_face_ids": (),
}
)
_assert_contains(split_full_hole_keys, {"diameter", "hole_cylinder_radius"}, "split full cylindrical hole")
if "slot_chord_width_estimate" in split_full_hole_keys:
raise SystemExit(f"split full cylindrical hole should not be shown as a slot: {split_full_hole_keys}")
slot_keys = _spec_keys(
{
"surface": "cylinder",