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())