feat: 完善 STEP/B-Rep 一级关系参数化编辑

This commit is contained in:
2026-08-07 18:08:32 +08:00
parent eef9efcc1e
commit 12250603dd
26 changed files with 4254 additions and 1270 deletions
+121 -49
View File
@@ -46,6 +46,15 @@ def _edge_specs(info: dict[str, object]) -> list[dict[str, object]]:
return specs
def _edge_display_specs(info: dict[str, object]) -> list[dict[str, object]]:
probe = _PropertySpecProbe()
probe.selected_kind = "edge"
probe.selected_face_id = None
probe.selected_edge_id = 7
specs = probe._property_editor_specs(info, info)
return probe._sort_property_specs_for_display(specs)
def _display_specs(info: dict[str, object]) -> list[dict[str, object]]:
probe = _PropertySpecProbe()
probe.selected_kind = "face"
@@ -105,7 +114,7 @@ def _assert_current_text_contains(
def _assert_spans_value_columns(specs: list[dict[str, object]], key: str) -> None:
spec = _spec(specs, key)
if not bool(spec.get("span_value_columns")):
raise SystemExit(f"{key} should span the value/intent/target/action columns: {spec}")
raise SystemExit(f"{key} should span the value/intent/target columns: {spec}")
def _assert_hard_range(specs: list[dict[str, object]], key: str, low: float, high: float) -> None:
@@ -195,8 +204,6 @@ def _assert_actionable_rows_first(info: dict[str, object], expected_keys: tuple[
seen_non_actionable = False
front_keys: list[str] = []
for spec in display_specs:
if bool(spec.get("pin_top")):
continue
key = str(spec.get("key", ""))
if _is_actionable_edit_spec(spec):
if seen_non_actionable:
@@ -210,6 +217,13 @@ def _assert_actionable_rows_first(info: dict[str, object], expected_keys: tuple[
raise SystemExit(f"{label}: expected editable rows at the front are missing: {missing}; front={front_keys}")
def _assert_keys_absent(specs: list[dict[str, object]], forbidden_keys: tuple[str, ...], label: str) -> None:
keys = {str(spec.get("key", "")) for spec in specs}
leaked = [key for key in forbidden_keys if key in keys]
if leaked:
raise SystemExit(f"{label} should not show diagnostic/non-editable rows in the parameter table: {leaked}")
def _collect_legacy_face_terms(value: object, path: str = "specs") -> list[str]:
legacy_terms = (
"面内尺寸 1/2",
@@ -296,7 +310,7 @@ def _assert_target_change_detection() -> None:
probe = _PropertySpecProbe()
number_spec = {
"label": "",
"label": "内长度",
"current_raw": 100.0,
"current_text": "100",
"value_type": "positive",
@@ -321,19 +335,17 @@ def _assert_target_change_detection() -> None:
def _assert_property_table_column_widths() -> None:
for width in (320, 340, 360, 400, 520):
columns = _property_table_column_widths(width)
if len(columns) != 5:
raise SystemExit(f"property table should have five column widths, got {columns}")
if len(columns) != 4:
raise SystemExit(f"property table should have four column widths, got {columns}")
if sum(columns) != width:
raise SystemExit(f"property table widths should fill viewport {width}, got {columns} sum={sum(columns)}")
label_width, current_width, scope_width, target_width, action_width = columns
if current_width < label_width + 40:
raise SystemExit(f"current value column should be wider than parameter-name column at {width}: {columns}")
if action_width < 70:
raise SystemExit(f"operation column should keep row buttons visible at {width}: {columns}")
label_width, current_width, scope_width, target_width = columns
if current_width < 96:
raise SystemExit(f"current value column should stay readable at {width}: {columns}")
if target_width < 56:
raise SystemExit(f"target value column should stay usable at {width}: {columns}")
if scope_width > label_width + 8:
raise SystemExit(f"modeling-intent column should not take space from current values at {width}: {columns}")
if scope_width < 44:
raise SystemExit(f"modeling-intent column should stay usable at {width}: {columns}")
def _assert_holed_plane_local_scopes_disabled() -> None:
@@ -355,7 +367,7 @@ def _assert_holed_plane_local_scopes_disabled() -> None:
"local_face_deform_blocker": "has inner boundary",
}
)
for key in ("area", "local_face_width", "local_face_height", "face_center_position"):
for key in ("local_face_width", "local_face_height", "face_center_position"):
local_mode = _scope_mode(specs, key, "local")
if bool(local_mode.get("enabled", True)):
raise SystemExit(f"{key} local Face scope should be disabled for a holed planar Face")
@@ -429,26 +441,44 @@ def main() -> int:
_assert_current_text_contains(
plane_specs,
"cad_modeling_form",
("柔性建模", "拉伸切除", "偏移变换"),
("柔性建模", "拉伸切除", "偏移"),
"plane Face",
)
_assert_hint_fragments(
_spec(plane_specs, "cad_modeling_form").get("disabled_tip"),
("CAD 语义判断", "这东西像什么建模对象"),
"CAD modeling form tooltip",
)
_assert_label(plane_specs, "cad_recommended_operation", "推荐操作")
_assert_spans_value_columns(plane_specs, "cad_recommended_operation")
_assert_current_text_contains(
plane_specs,
"cad_recommended_operation",
("优先改偏移变换", "拉伸/切除"),
("优先改偏移", "拉伸/切除"),
"plane Face",
)
_assert_hint_fragments(
_spec(plane_specs, "cad_recommended_operation").get("disabled_tip"),
("相对安全的改法建议", "建议你怎么改"),
"recommended operation tooltip",
)
plane_display_specs = _display_specs(plane_info)
if str(plane_display_specs[0].get("key", "")) != "cad_modeling_form":
raise SystemExit(f"plane Face should show CAD modeling form first: {plane_display_specs[0]}")
if str(plane_display_specs[1].get("key", "")) != "cad_recommended_operation":
raise SystemExit(f"plane Face should show recommended operation second: {plane_display_specs[1]}")
if any(str(spec.get("key", "")) == "area" for spec in plane_display_specs):
raise SystemExit("Face property table should keep area in diagnostics, not in the parameter table")
_assert_keys_absent(
plane_display_specs,
(
"cad_modeling_form",
"cad_recommended_operation",
"face_first_level_topology",
"face_edit_semantics",
"feature_context_note",
),
"plane Face display specs",
)
_assert_contains(
plane_keys,
{
"area",
"local_face_width",
"local_face_height",
"face_center_position",
@@ -456,13 +486,14 @@ def main() -> int:
},
"plane Face",
)
_assert_label(plane_specs, "local_face_width", "U向尺寸")
_assert_label(plane_specs, "local_face_height", "V向尺寸")
_assert_label(plane_specs, "face_target_normal_position", "偏移变换")
if "area" in plane_keys:
raise SystemExit("plane Face should not expose area as a modifiable parameter")
_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",
@@ -476,22 +507,18 @@ def main() -> int:
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")
_assert_spans_value_columns(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}")
_assert_label(plane_feature_rows, "face_edit_semantics", "建模意图")
_assert_spans_value_columns(plane_feature_rows, "face_edit_semantics")
_assert_label(plane_feature_rows, "cad_modeling_form", "建模形式")
_assert_label(plane_feature_rows, "cad_recommended_operation", "推荐操作")
if str(plane_feature_rows[0].get("key", "")) != "cad_modeling_form":
raise SystemExit(f"feature mode should keep CAD modeling form first: {plane_feature_rows[0]}")
if str(plane_feature_rows[1].get("key", "")) != "cad_recommended_operation":
raise SystemExit(f"feature mode should keep recommended operation second: {plane_feature_rows[1]}")
_assert_keys_absent(
plane_feature_rows,
(
"cad_modeling_form",
"cad_recommended_operation",
"face_first_level_topology",
"face_edit_semantics",
"feature_context_note",
),
"plane feature mode display specs",
)
expected_plane_feature_keys = {
"area",
"local_face_width",
"local_face_height",
"face_center_position",
@@ -506,7 +533,6 @@ def main() -> int:
)
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)
_assert_hard_range(plane_specs, "face_target_normal_position", -70.7106781185, 70.7106781185)
@@ -514,8 +540,6 @@ def main() -> int:
_assert_validation_error(probe, _spec(plane_specs, "local_face_width"), "0.49", True, "Face width below hard range")
_assert_validation_error(probe, _spec(plane_specs, "local_face_width"), "0.5", False, "Face width lower boundary")
_assert_validation_error(probe, _spec(plane_specs, "local_face_width"), "50.1", True, "Face width above hard range")
_assert_validation_error(probe, _spec(plane_specs, "area"), "0.2", True, "Face area below hard range")
_assert_validation_error(probe, _spec(plane_specs, "area"), "2500", False, "Face area upper boundary")
_assert_validation_error(
probe,
_spec(plane_specs, "face_target_normal_position"),
@@ -543,7 +567,7 @@ def main() -> int:
)
if "push_pull_distance" in plane_keys:
raise SystemExit("plane Face should not expose a separate push_pull_distance row")
for key in ("area", "local_face_width", "local_face_height"):
for key in ("local_face_width", "local_face_height"):
for mode in ("local", "owning"):
_assert_scoped_hint_fragments(
plane_specs,
@@ -664,7 +688,18 @@ def main() -> int:
cylinder_feature_probe = _PropertySpecProbe()
cylinder_feature_specs, _used = cylinder_feature_probe._editable_property_specs(cylinder_topology_info)
cylinder_feature_rows = cylinder_feature_probe._feature_property_specs(cylinder_feature_specs, cylinder_topology_info)
_spec(cylinder_feature_rows, "cylindrical_feature_first_level_topology")
_assert_keys_absent(
cylinder_feature_rows,
(
"cad_modeling_form",
"cad_recommended_operation",
"cylindrical_feature_first_level_topology",
"hole_edit_semantics",
"slot_edit_semantics",
"feature_context_note",
),
"cylindrical feature mode display specs",
)
generic_cylinder_specs = _specs(
{
@@ -820,6 +855,8 @@ def main() -> int:
"radius": 1.0,
"angular_span": 1.5707963267948966,
"existing_fillet_radius": 1.0,
"existing_fillet_arc_length_estimate": 1.5707963267948966,
"existing_fillet_angular_span": 1.5707963267948966,
"feature_existing_fillet_support_face_ids": (1, 2),
"area": 3.0,
"area_center": (0.0, 0.0, 0.0),
@@ -830,7 +867,11 @@ def main() -> int:
fillet_specs = _specs(fillet_info)
fillet_keys = {str(spec.get("key", "")) for spec in fillet_specs}
_assert_no_generic_face_leak(fillet_keys, "existing fillet feature")
_assert_contains(fillet_keys, {"existing_fillet_radius_estimate"}, "existing fillet feature")
_assert_contains(
fillet_keys,
{"existing_fillet_radius_estimate", "existing_fillet_arc_length_estimate"},
"existing fillet feature",
)
_assert_current_text_contains(
fillet_specs,
"cad_modeling_form",
@@ -914,9 +955,7 @@ def main() -> int:
_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_keys_absent(display_specs, ("surface",), "cone feature display specs")
edge_specs = _edge_specs(
{
@@ -963,6 +1002,39 @@ def main() -> int:
{"length", "edge_first_level_topology"},
"line Edge",
)
_assert_contains(
{str(spec.get("key", "")) for spec in _edge_display_specs({"curve": "line", "length": 10.0})},
{"length", "edge_length_anchor_mode"},
"line Edge display",
)
complex_edge_specs = _edge_specs({"curve": "b-spline curve", "length": 12.0})
complex_length_spec = _spec(complex_edge_specs, "length")
if complex_length_spec.get("enabled"):
raise SystemExit(f"complex curve Edge length should not be enabled in the property specs: {complex_length_spec}")
complex_length_tip = str(complex_length_spec.get("disabled_tip") or "")
if "复杂曲线Edge暂未实现" not in complex_length_tip:
raise SystemExit(f"complex curve Edge length tip should explain unsupported editing: {complex_length_tip}")
complex_edge_keys = {str(spec.get("key", "")) for spec in _edge_display_specs({"curve": "b-spline curve", "length": 12.0})}
if "length" in complex_edge_keys or "edge_length_anchor_mode" in complex_edge_keys:
raise SystemExit(f"complex curve Edge display should hide length editing rows: {complex_edge_keys}")
ellipse_display_specs = _edge_display_specs(
{
"curve": "ellipse",
"length": 23.0,
"major_radius": 5.0,
"minor_radius": 2.0,
}
)
ellipse_display_keys = {str(spec.get("key", "")) for spec in ellipse_display_specs}
_assert_contains(
ellipse_display_keys,
{"ellipse_edge_major_radius", "ellipse_edge_minor_radius"},
"ellipse Edge display",
)
if "length" in ellipse_display_keys or "edge_length_anchor_mode" in ellipse_display_keys:
raise SystemExit(f"ellipse Edge display should prefer explicit axis radii over generic length: {ellipse_display_keys}")
low_recognition_specs = _specs(
{