Building up the lattice
This commit is contained in:
@@ -359,6 +359,23 @@ class BeamlineScene(QGraphicsScene):
|
||||
none_act.setEnabled(False)
|
||||
tmpl_menu.addAction(none_act)
|
||||
|
||||
# Align / Distribute — shown when 2+ nodes are selected
|
||||
selected_nodes = [i for i in self.selectedItems()
|
||||
if isinstance(i, BeamlineNode)]
|
||||
if len(selected_nodes) >= 2:
|
||||
menu.addSeparator()
|
||||
align_menu = menu.addMenu("Align && Distribute")
|
||||
align_menu.setStyleSheet(menu.styleSheet())
|
||||
for label, slot in (
|
||||
("Align Horizontally", lambda: self._align(True)),
|
||||
("Align Vertically", lambda: self._align(False)),
|
||||
("Distribute Horizontally", lambda: self._distribute(True)),
|
||||
("Distribute Vertically", lambda: self._distribute(False)),
|
||||
):
|
||||
act = QAction(label, align_menu)
|
||||
act.triggered.connect(slot)
|
||||
align_menu.addAction(act)
|
||||
|
||||
menu.exec_(screen_pos)
|
||||
|
||||
def _ctx_copy(self, node: BeamlineNode):
|
||||
@@ -503,8 +520,116 @@ class BeamlineScene(QGraphicsScene):
|
||||
act_paste.setEnabled(bool(self._clipboard))
|
||||
act_paste.triggered.connect(self.paste)
|
||||
menu.addAction(act_paste)
|
||||
|
||||
# Align / Distribute — only when 2+ nodes are selected
|
||||
selected_nodes = [i for i in self.selectedItems()
|
||||
if isinstance(i, BeamlineNode)]
|
||||
if len(selected_nodes) >= 2:
|
||||
menu.addSeparator()
|
||||
align_menu = menu.addMenu("Align && Distribute")
|
||||
align_menu.setStyleSheet(menu.styleSheet())
|
||||
|
||||
for label, slot in (
|
||||
("Align Horizontally", lambda: self._align(True)),
|
||||
("Align Vertically", lambda: self._align(False)),
|
||||
("Distribute Horizontally", lambda: self._distribute(True)),
|
||||
("Distribute Vertically", lambda: self._distribute(False)),
|
||||
):
|
||||
act = QAction(label, align_menu)
|
||||
act.triggered.connect(slot)
|
||||
align_menu.addAction(act)
|
||||
|
||||
menu.exec_(screen_pos)
|
||||
|
||||
# ── Align / Distribute helpers ────────────────────────────────────────────
|
||||
|
||||
def _selected_nodes(self) -> list[BeamlineNode]:
|
||||
return [i for i in self.selectedItems() if isinstance(i, BeamlineNode)]
|
||||
|
||||
def _node_center(self, node: BeamlineNode) -> QPointF:
|
||||
"""Scene-coordinate centre of a node."""
|
||||
return node.pos() + QPointF(node.W / 2, node.H / 2)
|
||||
|
||||
def _align(self, horizontal: bool):
|
||||
"""
|
||||
Align all selected nodes so their centres share a common axis.
|
||||
|
||||
horizontal=True → align to the mean Y (same horizontal line)
|
||||
horizontal=False → align to the mean X (same vertical line)
|
||||
"""
|
||||
nodes = self._selected_nodes()
|
||||
if len(nodes) < 2:
|
||||
return
|
||||
|
||||
centers = [self._node_center(n) for n in nodes]
|
||||
if horizontal:
|
||||
mean_y = sum(c.y() for c in centers) / len(centers)
|
||||
for node in nodes:
|
||||
cx = self._node_center(node).x()
|
||||
node.setPos(cx - node.W / 2, mean_y - node.H / 2)
|
||||
self.status_changed.emit(
|
||||
f"Aligned {len(nodes)} elements horizontally."
|
||||
)
|
||||
else:
|
||||
mean_x = sum(c.x() for c in centers) / len(centers)
|
||||
for node in nodes:
|
||||
cy = self._node_center(node).y()
|
||||
node.setPos(mean_x - node.W / 2, cy - node.H / 2)
|
||||
self.status_changed.emit(
|
||||
f"Aligned {len(nodes)} elements vertically."
|
||||
)
|
||||
|
||||
# Update all connections attached to moved nodes
|
||||
for node in nodes:
|
||||
for port in node._all_ports():
|
||||
for conn in port.connections:
|
||||
conn.update_path()
|
||||
|
||||
def _distribute(self, horizontal: bool):
|
||||
"""
|
||||
Space all selected nodes evenly along one axis so their centres
|
||||
are equidistant.
|
||||
|
||||
horizontal=True → distribute along X axis
|
||||
horizontal=False → distribute along Y axis
|
||||
"""
|
||||
nodes = self._selected_nodes()
|
||||
if len(nodes) < 3:
|
||||
self.status_changed.emit(
|
||||
"Select at least 3 elements to distribute."
|
||||
)
|
||||
return
|
||||
|
||||
if horizontal:
|
||||
nodes_sorted = sorted(nodes, key=lambda n: self._node_center(n).x())
|
||||
x_min = self._node_center(nodes_sorted[0]).x()
|
||||
x_max = self._node_center(nodes_sorted[-1]).x()
|
||||
step = (x_max - x_min) / (len(nodes_sorted) - 1)
|
||||
for i, node in enumerate(nodes_sorted):
|
||||
cx = x_min + i * step
|
||||
cy = self._node_center(node).y()
|
||||
node.setPos(cx - node.W / 2, cy - node.H / 2)
|
||||
self.status_changed.emit(
|
||||
f"Distributed {len(nodes)} elements horizontally."
|
||||
)
|
||||
else:
|
||||
nodes_sorted = sorted(nodes, key=lambda n: self._node_center(n).y())
|
||||
y_min = self._node_center(nodes_sorted[0]).y()
|
||||
y_max = self._node_center(nodes_sorted[-1]).y()
|
||||
step = (y_max - y_min) / (len(nodes_sorted) - 1)
|
||||
for i, node in enumerate(nodes_sorted):
|
||||
cx = self._node_center(node).x()
|
||||
cy = y_min + i * step
|
||||
node.setPos(cx - node.W / 2, cy - node.H / 2)
|
||||
self.status_changed.emit(
|
||||
f"Distributed {len(nodes)} elements vertically."
|
||||
)
|
||||
|
||||
for node in nodes:
|
||||
for port in node._all_ports():
|
||||
for conn in port.connections:
|
||||
conn.update_path()
|
||||
|
||||
def _ctx_save_template(self, node: BeamlineNode):
|
||||
props = None
|
||||
if self._get_properties:
|
||||
|
||||
@@ -52,8 +52,11 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
self.elementDB={}
|
||||
self.savefile=''
|
||||
self.lines={}
|
||||
self.branchingID={}
|
||||
self.branchingID=[]
|
||||
self.branching={}
|
||||
self.branchingTemp={}
|
||||
self.branchnames={}
|
||||
self.targetReached=False
|
||||
|
||||
self.PL=ProtoListe(0)
|
||||
self.loadFile('Layouts/SFTest2.json')
|
||||
@@ -235,7 +238,7 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
retdict['Position']['Reserved Length'] = float(retdict['Position']['Reserved Length'])
|
||||
return retdict
|
||||
|
||||
def updateProperties(self,node_id: str, data: dict):
|
||||
def updateProperties(self,node_id: str, data: dict):
|
||||
# make sure that the ID is not changed
|
||||
data['Name']['ID']=node_id
|
||||
type = self.editor.getElementType(node_id)
|
||||
@@ -275,17 +278,23 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
self.branchingID.clear()
|
||||
for key in self.elementDB.keys():
|
||||
ele = self.elementDB[key]
|
||||
|
||||
if isinstance(ele,Dipole) and ele.branching:
|
||||
self.branchingID[key] = False
|
||||
self.branchingID.append(key)
|
||||
print('Branching elements:')
|
||||
for key in self.branchingID.keys():
|
||||
print('ID:',key)
|
||||
for key in self.branchingID:
|
||||
print(' ID:',key)
|
||||
|
||||
def flattenSingleLine(self,node_id,branch=0,case_num=1):
|
||||
|
||||
for key in self.branchingID.keys():
|
||||
self.branchingID[key]=(branch == 1)
|
||||
self.targetReached=False
|
||||
self.currentBranch=''
|
||||
self.branching.clear()
|
||||
for ib, id in enumerate(self.branchingID):
|
||||
ibit = 2**ib
|
||||
if branch & ibit > 0:
|
||||
self.branching[id]=True
|
||||
else:
|
||||
self.branching[id]=False
|
||||
type = self.editor.getElementType(node_id)
|
||||
name = 'XXX'
|
||||
if type == 'Line':
|
||||
@@ -295,7 +304,8 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
name = self.elementDB[node_id].Name
|
||||
line = LineContainer(namein=name, Lin=0)
|
||||
line.clear()
|
||||
print('\nUnwrapping beamline...\n')
|
||||
if self.verbose:
|
||||
print('\nUnwrapping beamline...\n')
|
||||
self.branchingTemp.clear()
|
||||
self.unwrap(name, node_id, line, case_num)
|
||||
for key in self.branchingTemp.keys():
|
||||
@@ -305,15 +315,22 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
def flatten(self, node_id, case_num=1 ):
|
||||
print('Flatten beamline for case:', case_num)
|
||||
self.findBranchingDipoles()
|
||||
nbranch = len(self.branchingID.keys())
|
||||
nbranch = len(self.branchingID)
|
||||
print('Possible Number of Branches:', 2**nbranch)
|
||||
self.lines.clear()
|
||||
|
||||
# first write the baseline (all branching off)
|
||||
|
||||
self.lines['1'] = copy.deepcopy(self.flattenSingleLine(node_id,branch=0,case_num=case_num))
|
||||
#self.lines['2'] = copy.deepcopy(self.flattenSingleLine(node_id,branch=1,case_num=case_num))
|
||||
|
||||
# for ib in range(2**nbranch):
|
||||
for ib in range(0,4):
|
||||
line=self.flattenSingleLine(node_id,branch=ib,case_num=case_num)
|
||||
if self.currentBranch=='':
|
||||
self.currentBranch='%d' % ib
|
||||
if not self.currentBranch in self.lines.keys():
|
||||
self.lines[self.currentBranch]=copy.deepcopy(line)
|
||||
else:
|
||||
print('Skipping redundant line:',self.currentBranch)
|
||||
print('Writing Layout for the lines:')
|
||||
for key in self.lines.keys():
|
||||
print(' ',key)
|
||||
self.PL.generateLayout(self.lines)
|
||||
self.plrecs = [self.PL.info[ele] for ele in self.PL.order if len(self.PL.info[ele]['Prefix'])> 5]
|
||||
populate_table(self.UIProtoList,self.plrecs)
|
||||
@@ -328,6 +345,8 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
tree=self.editor.flatten(node_id,case_num)
|
||||
secondBranch = False
|
||||
for ele in tree.path:
|
||||
if self.targetReached:
|
||||
return
|
||||
self.report(ele, name)
|
||||
type = self.editor.getElementType(ele)
|
||||
if type == 'Line':
|
||||
@@ -340,6 +359,8 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
ref = subline.firstElementID
|
||||
if ref in self.elementDB.keys():
|
||||
secondBranch = self.unwrap(name + name1, ref, subline,case_num)
|
||||
if secondBranch:
|
||||
break # do not continue with any other elements
|
||||
else:
|
||||
if self.verbose:
|
||||
print('Undefined reference in Line Container:', name + name1)
|
||||
@@ -352,6 +373,12 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
line.append(self.elementDB[ele],sRef=0,Ref='relative')
|
||||
elif type == 'Case' or type == 'Merge':
|
||||
continue
|
||||
elif type == 'Target':
|
||||
target=self.elementDB[ele].Name
|
||||
if not target in self.lines.keys():
|
||||
print('New Target reached:',self.elementDB[ele].Name)
|
||||
self.targetReached=True
|
||||
self.currentBranch=self.elementDB[ele].Name
|
||||
else:
|
||||
sRef=self.elementDB[ele].OffsetS
|
||||
Ref='absolute'
|
||||
@@ -359,8 +386,9 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
Ref='relative'
|
||||
line.append(self.elementDB[ele],sRef=sRef,Ref=Ref)
|
||||
self.elementDB[ele].Prefix=name
|
||||
if ele in self.branchingID.keys():
|
||||
if self.branchingID[ele] is True:
|
||||
if ele in self.branching.keys():
|
||||
if self.branching[ele] is True:
|
||||
print('Branching at element:', name+'-'+self.elementDB[ele].Name,'with angle:',self.elementDB[ele].design_angle)
|
||||
self.elementDB[ele].angle=self.elementDB[ele].design_angle
|
||||
line.hasBranchPoint=True
|
||||
return True
|
||||
@@ -370,10 +398,12 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
if secondBranch:
|
||||
if tree.secondary:
|
||||
self.unwrap(name, tree.secondary.node_id, line, case_num)
|
||||
else:
|
||||
return secondBranch
|
||||
else:
|
||||
if tree.primary:
|
||||
self.unwrap(name,tree.primary.node_id,line,case_num)
|
||||
return False
|
||||
return secondBranch
|
||||
|
||||
|
||||
|
||||
|
||||
+33
-1
@@ -24,7 +24,6 @@ class ProtoListe(ApplicationTemplate):
|
||||
self.dipole.clear()
|
||||
self.setRef(-0.1, -0.1)
|
||||
#self.iterateLine(line)
|
||||
keys=['1']
|
||||
for key in lines.keys():
|
||||
print('Writing Line for:',key,lines[key].Name)
|
||||
self.setRef(-0.1, -0.1)
|
||||
@@ -258,6 +257,37 @@ class ProtoListe(ApplicationTemplate):
|
||||
|
||||
def writeMarker(self,ele):
|
||||
|
||||
if not ele.Name in self.info.keys() and len(ele.Name) > 4:
|
||||
name = ele.Prefix + '-' + ele.Name
|
||||
domain = self.getDomain(name)
|
||||
align = 'Entrance'
|
||||
angh = np.arctan2(self.ev[0], self.ev[2]) * 180 / np.pi
|
||||
rh = np.sqrt(self.ev[2] * self.ev[2] + self.ev[0] * self.ev[0])
|
||||
angv = np.arctan2(self.ev[1], rh) * 180 / np.pi
|
||||
if ele.Group == 'Start':
|
||||
group = 'Section'
|
||||
bg ='Marker'
|
||||
prefix= ele.Prefix
|
||||
suffix=''
|
||||
index=''
|
||||
elif ele.Group == 'Marker' and 'MREF' in ele.Name:
|
||||
group = 'Marker'
|
||||
bg = 'Reference Point'
|
||||
prefix=ele.Prefix
|
||||
suffix = 'MREF'
|
||||
index=ele.index
|
||||
else:
|
||||
return
|
||||
info = {'Domain': domain, 'Prefix': prefix, 'Suffix': suffix, 'Index': index,
|
||||
's (m)': self.s, 'z (m)': self.z, 'x (m)': self.x, 'y (m)': self.y, 'L (m)': 0,
|
||||
'Reference': 'Start', 'Group': group, 'Baugruppe': bg, 'Variante': 'A', 'Roll': 0,
|
||||
'PS-Ch.': 0, 'P1': self.p1, 'PV': self.p1, 'P2': self.p1, 'EV1': self.ev1,
|
||||
'EV2': self.ev1, 'Yaw': angh, 'Pitch': angv, 'Alignment': align}
|
||||
self.info[ele.Name] = info
|
||||
self.order.append(ele.Name)
|
||||
return
|
||||
|
||||
|
||||
if ele.Group == 'Marker':
|
||||
if 'MREF' in ele.Name:
|
||||
self.write((ele.Name, ele.LengthRes, 'Start', ele.Group, ele.Tag, ele.Baugruppe, 0, 0))
|
||||
@@ -302,6 +332,8 @@ class ProtoListe(ApplicationTemplate):
|
||||
channels=0
|
||||
if 'PSChannels' in ele.__dict__:
|
||||
channels=ele['PSChannels']
|
||||
if ele.Baugruppe=='U15':
|
||||
channels=channels+1
|
||||
if ele.Baugruppe=='QFU':
|
||||
channels=channels-1
|
||||
if ele.Baugruppe=='QFUE':
|
||||
|
||||
+97
-1
@@ -24,7 +24,7 @@ background-color: rgb(13, 15, 26);</string>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
@@ -46,6 +46,102 @@ background-color: rgb(13, 15, 26);</string>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
<string>Settings</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="UIFlattenOffsetFlag">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Offset for Flatten</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="UIFlattenOffset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="lineEdit_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Tolerance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="UITolerances">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1e-5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>1944</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
Reference in New Issue
Block a user