Implementing switching of beamline path with a case structure
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user