"""Headless repro: load assemblytest.fluency, compare sketch circle centers vs. saved body hole axes vs. connector positions, then run the real body-update path and check where connectors land. """ 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.BRepAdaptor import BRepAdaptor_Surface from OCP.GeomAbs import GeomAbs_Cylinder from OCP.TopAbs import TopAbs_FACE from OCP.TopExp import TopExp_Explorer from OCP.TopoDS import TopoDS from PySide6.QtWidgets import QApplication from fluency.ui.main_window import MainWindow def etype(e): if isinstance(e, dict): return e.get("type") return getattr(e, "entity_type", None) or getattr(e, "type", None) def egeom(e): if isinstance(e, dict): return e.get("geometry") return getattr(e, "geometry", None) app = QApplication.instance() or QApplication([]) w = MainWindow() path = os.path.join(os.path.dirname(__file__), "assemblytest.fluency") ok = w._open_project_file(path) assert ok, "failed to open demo file" proj = w._project for comp in proj.components.values(): print(f"=== {comp.name} ({comp.id})") for sk in comp.sketches.values(): occ = sk.occ_sketch if occ is None: print(" sketch with no occ_sketch:", sk.id) continue for e in occ._entities.values(): t = etype(e) if t == "circle": g = egeom(e) cid = e.get("id") if isinstance(e, dict) else e.id print(f" circle id={cid} center=({g[0][0]!r}, {g[0][1]!r}) r={g[1]!r}") for body in comp.bodies.values(): if not body.geometry: print(f" body {body.name}: no geometry") continue shape = w._kernel._get_shape(body.geometry) print(f" body {body.name}: extrude len={body.extrude_length}") expl = TopExp_Explorer(shape, TopAbs_FACE) while expl.More(): face = TopoDS.Face_s(expl.Current()) try: adaptor = BRepAdaptor_Surface(face) if adaptor.GetType() == GeomAbs_Cylinder: cyl = adaptor.Cylinder() loc = cyl.Location() d = cyl.Axis().Direction() print( f" cyl axis at ({loc.X()!r}, {loc.Y()!r}) " f"dir=({d.X():.4f},{d.Y():.4f},{d.Z():.4f})" ) except Exception: pass expl.Next() asm = proj.get_active_assembly() print("=== active assembly:", asm.name) for ac in asm.components.values(): comp = proj.get_component_by_id(ac.component_id) print(f" instance '{ac.name}' -> {comp.name}, pos={np.round(ac.position, 4)}") for conn in ac.connectors.values(): print( f" conn '{conn.name}' pos={np.round(conn.position, 6)} " f"normal={np.round(conn.normal, 3)} et={conn.entity_type!r} " f"invalid={conn.is_invalid}" ) # ── Run the real update path: rebuild bodies from sketch, recalc connectors ── # Activate the component with the holes (Component 1). comp1 = None for comp in proj.components.values(): if any( etype(e) == "circle" for sk in comp.sketches.values() for e in (sk.occ_sketch._entities.values() if sk.occ_sketch else []) ): comp1 = comp break assert comp1 is not None w._current_component = comp1 print("=== running _update_bodies_from_sketch()") w._update_bodies_from_sketch() print("=== running _recalculate_connectors()") w._recalculate_connectors() print("=== after update") for comp in proj.components.values(): for body in comp.bodies.values(): if not body.geometry: continue shape = w._kernel._get_shape(body.geometry) expl = TopExp_Explorer(shape, TopAbs_FACE) axes = [] while expl.More(): face = TopoDS.Face_s(expl.Current()) try: adaptor = BRepAdaptor_Surface(face) if adaptor.GetType() == GeomAbs_Cylinder: loc = adaptor.Cylinder().Location() axes.append((round(loc.X(), 9), round(loc.Y(), 9))) except Exception: pass expl.Next() print(f" {comp.name} body axes: {axes}") for sk in comp.sketches.values(): for e in sk.occ_sketch._entities.values(): t = etype(e) if t == "circle": g = egeom(e) print(f" {comp.name} circle: ({g[0][0]!r}, {g[0][1]!r})") asm = proj.get_active_assembly() for ac in asm.components.values(): for conn in ac.connectors.values(): print( f" conn '{conn.name}' pos={np.round(conn.position, 6)} " f"invalid={conn.is_invalid}" ) # Partner alignment check: for each connection, world positions of the pair. for aconn in asm.connections: a1 = asm.components.get(aconn.first_ac_id) a2 = asm.components.get(aconn.second_ac_id) c1 = a1.connectors.get(aconn.first_connector_id) c2 = a2.connectors.get(aconn.second_connector_id) if c1 is None or c2 is None: continue w1 = a1.position + a1.rotation @ np.asarray(c1.position, dtype=float) w2 = a2.position + a2.rotation @ np.asarray(c2.position, dtype=float) print( f" conn {aconn.id[:8]}: w1={np.round(w1, 6)} w2={np.round(w2, 6)} " f"gap={float(np.linalg.norm(w1 - w2))!r}" ) print("DEMO_REPRO_DONE")