Files
pythonocc-step-editor/scripts/verify_edge_first_level_topology.py
T

154 lines
6.3 KiB
Python
Raw Normal View History

from __future__ import annotations
from pathlib import Path
import sys
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.model import StepModel
from step_editor.ui_helpers import INFO_LABELS
from step_editor.window_state import WindowStateMixin
DEFAULT_MODEL = PROJECT_ROOT / "assets" / "models" / "cube_10mm.step"
TOLERANCE = 1e-5
class _EdgeProbe(WindowStateMixin):
def __init__(self, model: StepModel) -> None:
self.model = model
def _assert(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def _line_edge_ids_near_length(model: StepModel, length: float, tolerance: float) -> list[int]:
edge_ids: list[int] = []
for edge_id in range(len(model.edges)):
info = model.edge_info(edge_id)
if info.get("curve") != "line":
continue
if abs(float(info.get("length", 0.0)) - length) <= tolerance:
edge_ids.append(edge_id)
return edge_ids
def _edge_under_test(model: StepModel) -> int:
edge_ids = _line_edge_ids_near_length(model, 10.0, TOLERANCE)
if not edge_ids:
raise AssertionError("No 10mm line Edge found in cube model.")
return edge_ids[0]
def _point(value: object) -> tuple[float, float, float]:
if not isinstance(value, (tuple, list)) or len(value) != 3:
raise AssertionError(f"Expected 3D point, got {value!r}")
return float(value[0]), float(value[1]), float(value[2])
def _assert_edge_topology(model: StepModel, edge_id: int) -> dict[str, object]:
topology = model.edge_first_level_topology(edge_id)
_assert(topology.get("topology_relation_depth") == 1, f"bad Edge topology depth: {topology}")
_assert(
topology.get("topology_relation_boundary") == "shared-vertex/shared-face",
f"bad Edge relation boundary: {topology}",
)
_assert(int(topology.get("selected_edge_count", 0) or 0) == 1, f"bad selected Edge count: {topology}")
_assert(int(topology.get("first_level_vertex_count", 0) or 0) == 2, f"bad endpoint count: {topology}")
_assert(
int(topology.get("first_level_adjacent_face_count", 0) or 0) == 2,
f"cube Edge should have 2 incident Faces: {topology}",
)
_assert(
int(topology.get("first_level_adjacent_edge_count", 0) or 0) == 4,
f"cube Edge should have 4 shared-endpoint neighbor Edges: {topology}",
)
_assert(
int(topology.get("first_level_edge_count", 0) or 0) == 5,
f"cube Edge first-level Edge set should contain selected Edge + 4 neighbors: {topology}",
)
ignored = tuple(topology.get("topology_ignored_relation_depths", ()) or ())
_assert("second-level" in ignored and "third-level" in ignored, f"missing ignored depths: {topology}")
return topology
def _assert_edge_facts(facts: dict[str, object], label: str) -> None:
_assert(facts.get("first_level_fact_model") == "STEP/B-Rep first-level fact graph", f"{label}: bad model")
_assert(facts.get("first_level_fact_source_model") == "edge", f"{label}: bad source")
_assert(facts.get("first_level_fact_status") == "ready", f"{label}: bad status {facts}")
_assert(facts.get("first_level_fact_scope") == "edge", f"{label}: bad scope {facts}")
_assert(facts.get("first_level_fact_relation_depth") == 1, f"{label}: bad depth {facts}")
_assert(
facts.get("first_level_fact_relation_boundary") == "shared-vertex/shared-face",
f"{label}: bad boundary {facts}",
)
_assert(int(facts.get("first_level_fact_subject_edge_count", 0) or 0) == 1, f"{label}: bad subject")
_assert(int(facts.get("first_level_fact_boundary_vertex_count", 0) or 0) == 2, f"{label}: bad vertices")
_assert(int(facts.get("first_level_fact_adjacent_edge_count", 0) or 0) == 4, f"{label}: bad adjacent Edges")
_assert(int(facts.get("first_level_fact_adjacent_face_count", 0) or 0) == 2, f"{label}: bad adjacent Faces")
_assert(int(facts.get("first_level_fact_included_edge_count", 0) or 0) == 5, f"{label}: bad included Edges")
_assert(str(facts.get("first_level_fact_summary") or ""), f"{label}: missing summary")
def _assert_plan_facts(plan: dict[str, object], label: str) -> None:
_assert_edge_facts(plan, label)
_assert("resize_strategy" in plan or "chamfer_mode" in plan, f"{label}: plan has no strategy marker")
def main() -> int:
for key in (
"first_level_fact_subject_edge_ids",
"first_level_fact_subject_edge_count",
"first_level_fact_adjacent_edge_ids",
"first_level_fact_adjacent_edge_count",
"first_level_fact_included_edge_ids",
"first_level_fact_included_edge_count",
"first_level_vertex_count",
"first_level_adjacent_edge_count",
):
_assert(key in INFO_LABELS, f"{key} should have a user-facing label")
model = StepModel.load(DEFAULT_MODEL)
edge_id = _edge_under_test(model)
info = model.edge_info(edge_id)
start = _point(info.get("start_point"))
end = _point(info.get("end_point"))
center = _point(info.get("length_center"))
topology = _assert_edge_topology(model, edge_id)
facts = model.edge_first_level_facts(edge_id)
_assert_edge_facts(facts, "Edge first-level facts")
fields = _EdgeProbe(model)._edge_first_level_selection_fields(edge_id)
_assert_edge_facts(fields, "Edge selection fields")
endpoint_target = (end[0] + 2.0, end[1], end[2])
center_target = (center[0], center[1], center[2] + 1.0)
plans = (
("Edge length", model.general_edge_length_plan(edge_id, 15.0, anchor_mode="keep-start")),
("Edge endpoint", model.edge_endpoint_move_plan(edge_id, "end", endpoint_target)),
("Edge center", model.edge_center_move_plan(edge_id, center_target)),
("Edge fillet", model.edge_fillet_plan(edge_id, 0.5)),
("Edge chamfer", model.edge_chamfer_plan(edge_id, 0.4)),
("Edge asymmetric chamfer", model.edge_asymmetric_chamfer_plan(edge_id, 0.3, 0.4)),
("Edge distance-angle chamfer", model.edge_distance_angle_chamfer_plan(edge_id, 0.3, 45.0)),
)
for label, plan in plans:
_assert_plan_facts(plan, label)
print(f"model={DEFAULT_MODEL}")
print(f"edge_id={edge_id}")
print(f"start={start} end={end}")
print(f"topology={topology}")
print("edge first-level topology ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())