9abeb6266a
- assembly forward proagation
159 lines
5.5 KiB
Python
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")
|