Files
fluencyCAD/.test_demo_repro.py
T
bklronin 9abeb6266a - Assembly instaiated operations
- assembly forward proagation
2026-08-19 20:14:14 +02:00

159 lines
5.5 KiB
Python

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