154 lines
6.3 KiB
Python
154 lines
6.3 KiB
Python
|
|
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())
|