Implementing switching of beamline path with a case structure

This commit is contained in:
2026-07-09 15:30:56 +02:00
parent ba3a514920
commit a60f4133b5
6 changed files with 123 additions and 19 deletions
+51 -2
View File
@@ -378,13 +378,26 @@ class BeamlineEditorWidget(QWidget):
nodes_data = []
for item in self.scene.items():
if isinstance(item, BeamlineNode):
nodes_data.append({
nd = {
"id": item.node_id,
"element_type": item.element_type,
"display_name": item.display_name(),
"x": item.pos().x(),
"y": item.pos().y(),
})
}
# Persist collapse state for Start markers
if item.element_type == "Start":
nd["collapsed"] = item.node_id in self.scene._collapsed_starts
# Save relative offsets so the layout is restored correctly
# even if the file is loaded with the Start node in a new position.
# Offsets are stored as {downstream_node_id: [dx, dy]}.
offsets = self.scene._collapse_offsets.get(item.node_id, {})
if offsets:
nd["collapse_offsets"] = {
nid: [pt.x(), pt.y()]
for nid, pt in offsets.items()
}
nodes_data.append(nd)
conns_data = []
for item in self.scene.items():
@@ -565,4 +578,40 @@ class BeamlineEditorWidget(QWidget):
conn = Connection(src_port, dst_port)
self.scene.addItem(conn)
# ── Pass 3: restore collapse state for Start markers ──────────────────
# Must run after connections are built so _downstream_items() works.
for nd in payload.get("nodes", []):
if not nd.get("collapsed", False):
continue
canvas_id = remap.get(nd["id"], nd["id"])
start_node = node_lookup.get(canvas_id)
if start_node is None:
continue
# Restore the saved relative offsets (remapping downstream IDs too)
raw_offsets = nd.get("collapse_offsets", {})
offsets: dict[str, QPointF] = {}
for file_nid, (dx, dy) in raw_offsets.items():
canvas_nid = remap.get(file_nid, file_nid)
offsets[canvas_nid] = QPointF(dx, dy)
# If no offsets were saved, compute them now from current positions
if not offsets:
start_pos = start_node.pos()
down_nodes, _ = self.scene._downstream_items(start_node)
for dn in down_nodes:
offsets[dn.node_id] = dn.pos() - start_pos
self.scene._collapse_offsets[canvas_id] = offsets
# Apply the collapse (hide nodes/wires, update label)
self.scene._collapsed_starts.add(canvas_id)
down_nodes, down_conns = self.scene._downstream_items(start_node)
for n in down_nodes:
n.setVisible(False)
for c in down_conns:
c.setVisible(False)
if hasattr(start_node, "set_collapsed_label_visible"):
start_node.set_collapsed_label_visible(True)
return remap
+49 -5
View File
@@ -447,16 +447,60 @@ class AlignmentNode(BeamlineNode):
p.drawLine(QPointF(cx, cy), QPointF(cx, cy + dy))
class StartNode(BeamlineNode):
"""Start marker: no in-port, 1 out."""
def __init__(self, pos): super().__init__("Start", pos)
"""
Start marker: no in-port, 1 out.
Arrow-head sits flush against the right edge (near the out-port).
A second label row shows "(collapsed)" when collapsed.
"""
def __init__(self, pos):
super().__init__("Start", pos)
# Second-row label for collapsed state — hidden by default
self._collapsed_label = QGraphicsTextItem("(collapsed)", self)
self._collapsed_label.setDefaultTextColor(QColor("#A5D6A7"))
self._collapsed_label.setFont(QFont("Segoe UI", 6))
self._collapsed_label.setZValue(6)
self._collapsed_label.setVisible(False)
self._layout_labels()
def _layout_labels(self):
"""Stack name and (collapsed) label vertically, centred in the node."""
br1 = self._label.boundingRect()
br2 = self._collapsed_label.boundingRect()
if self._collapsed_label.isVisible():
gap = 1
total = br1.height() + gap + br2.height()
y1 = (self.H - total) / 2
y2 = y1 + br1.height() + gap
else:
y1 = (self.H - br1.height()) / 2
y2 = y1
self._label.setPos((self.W - br1.width()) / 2, y1)
self._collapsed_label.setPos((self.W - br2.width()) / 2, y2)
def set_display_name(self, name: str):
"""Override to re-run the two-row layout after the name changes."""
super().set_display_name(name)
self._layout_labels()
def set_collapsed_label_visible(self, visible: bool):
self._collapsed_label.setVisible(visible)
self._layout_labels()
self.update()
def _draw_body(self, p):
p.setBrush(QBrush(self._gradient()))
p.setPen(QPen(QColor("#00897B"), 1.5))
p.drawRoundedRect(QRectF(0, 0, self.W, self.H), 6, 6)
cx, cy = self.W / 2, self.H / 2
# Arrow-head at the right edge, pointing right toward the out-port
cy = self.H / 2
tip_x = self.W - 4 # flush with right edge
base_x = tip_x - 12
half_h = 8
pts = QPolygonF([
QPointF(cx - 10, cy - 10), QPointF(cx + 4, cy),
QPointF(cx - 10, cy + 10), QPointF(cx - 6, cy),
QPointF(base_x, cy - half_h),
QPointF(tip_x, cy),
QPointF(base_x, cy + half_h),
QPointF(base_x + 4, cy),
])
p.setBrush(QBrush(QColor("#A5D6A7")))
p.setPen(Qt.NoPen)
+9 -8
View File
@@ -332,10 +332,9 @@ class BeamlineScene(QGraphicsScene):
for c in conns:
c.setVisible(True)
c.update_path()
# revert label (strip suffix added at collapse time)
start_node.set_display_name(
start_node.display_name().replace(" (collapsed)", "")
)
# hide second-row collapsed label
if hasattr(start_node, "set_collapsed_label_visible"):
start_node.set_collapsed_label_visible(False)
self.status_changed.emit(
f"Uncollapsed downstream of {start_node.display_name()}"
)
@@ -350,10 +349,12 @@ class BeamlineScene(QGraphicsScene):
self._collapse_offsets[start_node.node_id] = offsets
for c in conns:
c.setVisible(False)
name = start_node.display_name()
if "(collapsed)" not in name:
start_node.set_display_name(name + " (collapsed)")
self.status_changed.emit(f"Collapsed downstream of {name}")
# show second-row "(collapsed)" label
if hasattr(start_node, "set_collapsed_label_visible"):
start_node.set_collapsed_label_visible(True)
self.status_changed.emit(
f"Collapsed downstream of {start_node.display_name()}"
)
def _downstream_items(
self,
+1 -2
View File
@@ -171,7 +171,6 @@ class LineContainer:
seq += ele.writeLattice(app)
elif isinstance(ele, VariableContainer):
dL = ele.getResLength()
print('Adding Drift:',dL)
app.writeDrift(dL)
Last +=dL
else:
@@ -183,7 +182,7 @@ class LineContainer:
# if name<>0: # not valid in python3.x
if name != 0:
seq.append(ele)
print('Extend Cell',self.getResLength(), Last)
#print('Extend Cell',self.getResLength(), Last)
app.writeDrift(self.getResLength() - Last)
app.writeLine(self,seq) # indicate the app that a sequence is done
+13 -1
View File
@@ -46,6 +46,7 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
self.elementDB={}
self.savefile=''
self.lines={}
self.branchingID=[]
self.PL=ProtoListe(0)
self.loadFile('Layouts/SFTest2.json')
@@ -205,8 +206,19 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
return
def findBranchingDipoles(self):
self.branchingID.clear()
for key in self.elementDB.keys():
ele = self.elementDB[key]
if ele.branching:
self.branchingID.append(key)
print('Branching elements')
print(self.branchingID)
def flatten(self, node_id, case_num=1 ):
print('Flatten case:', case_num)
print('Flatten beamline for case:', case_num)
self.findBranchingDipoles()
self.lines.clear()
type = self.editor.getElementType(node_id)
name='XXX'
-1
View File
@@ -223,7 +223,6 @@ class ProtoListe(ApplicationTemplate):
def writeLine(self,ele,seq):
if not ele.Name in self.info.keys() and len(ele.Name) > 1:
print('Empty Cell encountered:', ele.Name)
domain = self.getDomain(ele.Name)
align = 'Entrance'
angh = np.arctan2(self.ev[0], self.ev[2]) * 180 / np.pi