Files
pythonocc-step-editor/scripts/verify_selection_identity_ui.py

256 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from step_editor.info_panel import InfoPanelMixin
from step_editor.ui_helpers import INFO_LABELS
from step_editor.window_state import WindowStateMixin
class _SelectionProbe(InfoPanelMixin, WindowStateMixin):
pass
class _LogicalFaceModel:
def __init__(self, logical_id: int) -> None:
self.logical_id = logical_id
def face_region_logical_id(self, _face_id: int) -> int:
return self.logical_id
def face_logical_id(self, _face_id: int) -> int:
return self.logical_id
class _FeatureContextModel(_LogicalFaceModel):
faces = [object()] * 800
face_part_ids = [1] * 800
face_solid_ids = [0] * 800
def quick_face_info(self, face_id: int) -> dict[str, object]:
return {
"kind": "face",
"face_id": face_id,
"topological_face_id": face_id,
"logical_face_id": self.logical_id,
"surface": "plane",
"part_id": 1,
"solid_id": 0,
"area": 100.0,
"area_center": (5.0, 5.0, 0.0),
"local_face_width": 10.0,
"local_face_height": 10.0,
"feature_source_face_id": face_id,
"feature_highlight_face_ids": (face_id,),
}
def cached_feature_info(self, _face_id: int) -> dict[str, object] | None:
return None
def face_first_level_topology(self, face_id: int) -> dict[str, object]:
return {
"topology_relation_depth": 1,
"topology_relation_model": "STEP/B-Rep shared-edge first-level",
"topology_relation_scope": "selected same-domain region + direct shared-edge adjacent Faces",
"topology_relation_boundary": "shared-edge",
"topology_ignored_relation_depths": ("second-level", "third-level", "deeper"),
"topology_ignored_relation_note": "当前阶段只传播一级关系;二级、三级拓扑暂不自动递归编辑。",
"same_domain_face_ids": (face_id,),
"same_domain_face_count": 1,
"same_domain_region_kind": "single-face",
"first_level_boundary_edge_ids": (1, 2, 3, 4),
"first_level_boundary_edge_count": 4,
"first_level_boundary_vertex_count": 4,
"first_level_adjacent_face_ids": (10, 11, 12, 13),
"first_level_adjacent_face_count": 4,
"first_level_face_ids": (face_id, 10, 11, 12, 13),
"first_level_face_count": 5,
"first_level_topology_note": "已识别当前 Face 区域 1 个 Face、边界 Edge 4 条、边界 Vertex 4 个、共享边一级相邻 Face 4 个。",
}
def face_first_level_facts(self, face_id: int, scope: str = "auto") -> dict[str, object]:
return {
"first_level_fact_model": "STEP/B-Rep first-level fact graph",
"first_level_fact_status": "ready",
"first_level_fact_relation_depth": 1,
"first_level_fact_scope": scope,
"first_level_fact_relation_boundary": "shared-edge",
"first_level_fact_subject_face_ids": (face_id,),
"first_level_fact_subject_face_count": 1,
"first_level_fact_boundary_edge_count": 4,
"first_level_fact_boundary_vertex_count": 4,
"first_level_fact_adjacent_face_ids": (10, 11, 12, 13),
"first_level_fact_adjacent_face_count": 4,
"first_level_fact_included_face_count": 5,
"first_level_fact_ignored_relation_depths": ("second-level", "third-level", "deeper"),
"first_level_fact_summary": "一级事实=当前 Face、边界和共享边相邻 Face。",
}
def _recognition_summary_fields(self, _info: dict[str, object]) -> dict[str, object]:
return {"recognition_summary": "识别摘要包含一级事实。"}
class _FeatureRefreshProbe(WindowStateMixin):
def __init__(self) -> None:
self.model = _FeatureContextModel(594)
self.selected_kind = "feature"
self.selected_face_id = 637
self.selected_edge_id = None
self.selected_part_id = 1
self.selected_solid_id = 0
self.selected_pick_position = (1.0, 2.0, 3.0)
self.feature_detection_level = "current-only"
self.set_info_payload: dict[str, object] | None = None
self.highlighted_face_ids: tuple[int, ...] = ()
self.synced_id: tuple[str, int] | None = None
def set_info(self, info: dict[str, object]) -> None:
self.set_info_payload = dict(info)
self.current_info_values = dict(info)
def _highlight_faces(self, face_ids: list[int] | tuple[int, ...] | None = None, **_kwargs) -> None:
self.highlighted_face_ids = tuple(int(item) for item in (face_ids or ()))
def _update_selected_object_title(self) -> None:
return
def _sync_id_picker(self, kind: str, target_id: int) -> None:
self.synced_id = (kind, int(target_id))
def _probe(kind: str, selected_face_id: int, info: dict[str, object]) -> _SelectionProbe:
probe = object.__new__(_SelectionProbe)
probe.model = None
probe.selected_kind = kind
probe.selected_part_id = None
probe.selected_solid_id = None
probe.selected_face_id = selected_face_id
probe.selected_edge_id = None
probe.current_info_values = dict(info)
return probe
def _assert(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def main() -> int:
for key in ("selection_title", "selection_display_id", "selection_topological_face_id"):
_assert(key in INFO_LABELS, f"{key} should have a readable UI label")
_assert(INFO_LABELS.get("face_id") == "当前拓扑 Face ID", "raw face_id should be labelled as topological")
info = {
"feature_type": "可拉伸/切除平面候选",
"selection_display_id": 594,
"selection_topological_face_id": 637,
"face_region_logical_id": 594,
"logical_face_id": 594,
}
feature_probe = _probe("feature", 637, info)
_assert(
feature_probe._selected_id_text() == "face 594",
f"feature copy ID should prefer logical Face ID, got {feature_probe._selected_id_text()!r}",
)
feature_title = feature_probe._selected_object_title_suffix()
_assert(
"Face 594" in feature_title and "拓扑 637" in feature_title,
f"feature title should show logical and topological IDs: {feature_title}",
)
_assert("候选" not in feature_title, f"feature title should use a user-facing label, got: {feature_title}")
_assert("可拉伸/切除平面" in feature_title, f"feature title should keep the editable feature name: {feature_title}")
face_probe = _probe("face", 637, info)
_assert(
face_probe._selected_id_text() == "face 594",
f"Face copy ID should prefer logical Face ID, got {face_probe._selected_id_text()!r}",
)
face_title = face_probe._selected_object_title_suffix()
_assert(
"Face 594" in face_title and "拓扑 637" in face_title,
f"Face title should show logical and topological IDs: {face_title}",
)
_assert("候选" not in face_title, f"Face title should not expose candidate wording: {face_title}")
fallback_probe = _probe("feature", 637, {"face_region_logical_id": 594})
_assert(fallback_probe._selected_id_text() == "face 594", "logical Face ID fallback should be copied")
selection_only_probe = _probe("feature", 637, {"selection_display_id": 594})
_assert(
selection_only_probe._selected_id_text() == "face 594",
"selection_display_id should be enough for copied Face ID",
)
selection_only_title = selection_only_probe._selected_object_title_suffix()
_assert(
"Face 594" in selection_only_title and "拓扑 637" in selection_only_title,
f"selection_display_id should be enough for feature title: {selection_only_title}",
)
face_selection_only_probe = _probe("face", 637, {"selection_display_id": 594})
face_selection_only_title = face_selection_only_probe._selected_object_title_suffix()
_assert(
"Face 594" in face_selection_only_title and "拓扑 637" in face_selection_only_title,
f"selection_display_id should be enough for Face title: {face_selection_only_title}",
)
raw_probe = _probe("feature", 637, {})
_assert(raw_probe._selected_id_text() == "face 637", "topological Face ID fallback should still work")
helper_probe = _probe("feature", 637, {})
helper_probe.model = _LogicalFaceModel(594)
fields = helper_probe._selection_identity_fields(637, "特征来源 Face")
_assert(fields.get("selection_display_id") == 594, f"helper should expose logical Face ID: {fields}")
_assert(
fields.get("selection_topological_face_id") == 637,
f"helper should keep current topological Face ID: {fields}",
)
_assert(
fields.get("selection_title") == "特征来源 Face 594(当前拓扑 Face 637",
f"helper title should explain both IDs: {fields}",
)
refresh_probe = _FeatureRefreshProbe()
context = refresh_probe._feature_context_info(637)
_assert(context.get("selection_display_id") == 594, f"feature context should include logical ID: {context}")
_assert(
context.get("selection_topological_face_id") == 637,
f"feature context should include topological ID: {context}",
)
refresh_probe._on_feature_detection_level_changed()
payload = refresh_probe.set_info_payload or {}
_assert(payload.get("selection_display_id") == 594, f"detection refresh should call set_info with identity: {payload}")
_assert(payload.get("pick_position") == (1.0, 2.0, 3.0), f"detection refresh should preserve pick: {payload}")
_assert(refresh_probe.highlighted_face_ids == (637,), f"detection refresh should rehighlight source Face")
source_probe = _FeatureRefreshProbe()
source_probe.selected_face_id = 100
source_probe.selected_pick_position = (9.0, 9.0, 9.0)
source_probe._activate_property_source_feature(
{
"source_face_id": 637,
"source_feature_info": {
"feature_type": "可拉伸/切除平面候选",
"part_id": 1,
"solid_id": 0,
"feature_highlight_face_ids": (637,),
},
}
)
source_payload = source_probe.set_info_payload or {}
_assert(source_probe.synced_id == ("Feature", 594), f"associated jump should sync logical ID: {source_probe.synced_id}")
_assert(source_payload.get("selection_display_id") == 594, f"associated jump should keep logical ID: {source_payload}")
_assert("pick_position" not in source_payload, f"associated jump should not reuse stale pick position: {source_payload}")
print("selection identity UI ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())