"""Headless smoke: the manual re-pick fallback path (prompt -> frame -> pick mode).""" import os import sys os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) import numpy as np from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2 from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut from PySide6.QtWidgets import QApplication, QMessageBox from fluency.ui.main_window import MainWindow from fluency.models.data_model import Body from fluency.geometry_occ.kernel import OCCGeometryObject app = QApplication.instance() or QApplication([]) w = MainWindow() comp = w._current_component body = Body(name="plate") comp.bodies[body.id] = body box = BRepPrimAPI_MakeBox(60.0, 40.0, 5.0).Shape() ax = gp_Ax2(gp_Pnt(10.0, 5.0, 0.0), gp_Dir(0, 0, 1)) box = BRepAlgoAPI_Cut(box, BRepPrimAPI_MakeCylinder(ax, 3.0, 6.0).Shape()).Shape() body.geometry = OCCGeometryObject(box) partner = w._project.add_component() pbody = Body(name="partner") partner.bodies[pbody.id] = pbody pbody.geometry = OCCGeometryObject(BRepPrimAPI_MakeBox(30.0, 30.0, 10.0).Shape()) asm = w._project.get_active_assembly() ac1 = asm.add_component_instance(comp.id, name="A") ac2 = asm.add_component_instance(partner.id, name="B") ac1.geom_cache[body.id] = body.geometry c1 = ac1.add_connector( position=(10.0, 5.0, 2.5), normal=(0, 0, 1), x_dir=(1, 0, 0), source_obj_id=f"asm_{ac1.id}_{body.id}", name="Conn hole", entity_type="cylindrical_face", ) c1.partner_ac_id = ac2.id # Activate the assembly view so the prompt path is taken. w._assembly_view_active = True w._selected_assembly_component_id = ac1.id # Force a Yes from the question dialog, and record that it actually fired. asked = {} def fake_question(parent, title, text, buttons, default): asked["title"] = title return QMessageBox.StandardButton.Yes QMessageBox.question = staticmethod(fake_question) def fake_warning(*a, **k): return QMessageBox.StandardButton.Ok QMessageBox.warning = staticmethod(fake_warning) w._prompt_relocate_unresolved([(asm, ac1, c1)]) assert asked.get("title") == "Connector Position Needed", asked assert w._relocate_pending == [(asm, ac1, c1)], w._relocate_pending assert w._viewer_3d._connector_pick_mode, "pick mode must be active" # A wrong-part click must not consume the pending entry. w._on_relocate_picked((0, 0, 0), (0, 0, 1), (1, 0, 0), "planar_face", f"asm_{ac2.id}_{pbody.id}") assert w._relocate_pending == [(asm, ac1, c1)], "wrong part must not consume the pick" # The right click re-homes and drains. w._on_relocate_picked((40.0, 30.0, 2.5), (0, 0, 1), (1, 0, 0), "cylindrical_face", f"asm_{ac1.id}_{body.id}") assert w._relocate_pending is None assert np.allclose(c1.position, (40.0, 30.0, 2.5)), c1.position assert not w._viewer_3d._connector_pick_mode, "pick mode must be off after completion" # Esc cancel mid-flight clears the state. w._relocate_pending = [(asm, ac1, c1)] w._start_relocate_pick_next() w._on_connector_pick_cancelled() assert w._relocate_pending is None assert not w._viewer_3d._connector_pick_mode print("RELOCATE_PICK_FALLBACK_OK")