wip: digital twin
CI for debye_bec / test (push) Failing after 1m0s
CI for debye_bec / test (pull_request) Failing after 1m1s

This commit is contained in:
x01da
2026-05-06 16:12:25 +02:00
parent b0a7d6905c
commit 6da7e665b3
5 changed files with 285 additions and 197 deletions
@@ -0,0 +1,173 @@
import sys
import re
import numpy as np
from scipy.interpolate import UnivariateSpline
from xrt.backends.raycing.physconsts import CHeVcm, AVOGADRO
from bec_lib import bec_logger
# pylint: disable=E0611
from qtpy.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QApplication,
QLayout,
)
# pylint: disable=E0611
from qtpy.QtCore import (
Qt,
QTimer,
)
from qtpy.QtGui import (
QColor,
QBrush,
)
import pyqtgraph as pg
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeSlot
from debye_bec.bec_widgets.widgets.qt_widgets import (
InputNumberField,
ComboBox,
Group,
NumberIndicator,
Mover,
)
from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions
from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview
from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
logger = bec_logger.logger
def sldi_gap_to_acc(sldi_gapx, sldi_gapy):
d1 = bl.feSlits.center1[1]
h_acc = np.tan(sldi_gapx / (2 * d1))
v_acc = np.tan(sldi_gapy / (2 * d1))
return h_acc, v_acc
def cm_trx_to_stripe(cm_trx):
cm_stripe = None
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
if low <= cm_trx <= high:
cm_stripe = name
return cm_stripe
def fm_trx_to_stripe(fm_trx):
fm_stripe = None
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
if low <= fm_trx <= high:
fm_stripe = name + ' (flat)'
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
if low <= fm_trx <= high:
fm_stripe = name + ' (toroid)'
return fm_stripe
def mo1_energy_resolution(xtal, energy):
index = bl.mo1.xtal.index(xtal)
crystal = bl.mo1.material1[index]
dtheta = np.linspace(-30, 90, 601)
theta = crystal.get_Bragg_angle(energy) + dtheta * 1e-6
refl = np.abs(crystal.get_amplitude(energy, np.sin(theta))[0])**2 # single crystal
refl2 = refl**2 # DCM with parallel crystals
# FWHM of the DCM curve
spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0)
r1, r2 = spline.roots()
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
# Energy resolution
theta_B = crystal.get_Bragg_angle(energy)
dE_over_E = fwhm_rad / np.tan(theta_B)
dE = dE_over_E * energy
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
return dE
def cm_reflectivity(cm_stripe, cm_pitch, energy):
index = bl.cm.surface.index(cm_stripe)
rs, rp = bl.cm.material[index].get_amplitude(
energy,
np.sin(cm_pitch)
)[0:2]
refl = abs(rs)**2
return refl
def fm_reflectivity(fm_stripe, fm_pitch, energy):
if fm_stripe in ('Rh (toroid)', 'Pt (toroid)'):
surface = bl.fm.surfaceToroid
material = bl.fm.materialToroid
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
index = surface.index(stripe)
else:
surface = bl.fm.surfaceFlat
material = bl.fm.materialFlat
stripe = re.sub(r'\s*\(.*?\)', '', fm_stripe).strip()
index = surface.index(stripe)
rs, rp = material[index].get_amplitude(
energy,
np.sin(fm_pitch)
)[0:2]
refl = abs(rs)**2
return refl
def mo1_bragg_angle(mo_mode, d_spacing, energy, cm_pitch):
H = 6.62606957E-34
E = 1.602176634E-19
C = 299792458
wl = C * H / (E * energy)
val = wl / (2 * d_spacing * 1e-10)
bragg_angle = 0
if val > -1 and val < 1:
bragg_angle = np.asin(val)
if mo_mode in 'Monochromatic':
# Add 2x CM pitch to the bragg angle
bragg_angle_cor = ((2 * cm_pitch) + bragg_angle)
elif mo_mode in 'Pinkbeam':
# Align xtal surfaces parallel to beam
bragg_angle_cor = (2 * cm_pitch)
return bragg_angle, bragg_angle_cor
def fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc=None, sldi_vacc=None, fm_focx=None, fm_focy=None):
p = bl.fm.center[1] # posFM
q = smpl - bl.fm.center[1] # dist posFM to posEX
if fm_focus in 'Defocused':
a = 2 * np.tan(sldi_hacc) * bl.fm.center[1] # Beam width at focusing mirror
b = 2 * np.tan(sldi_vacc) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam)
x = fm_focx
y = fm_focy
qx = q + x * p / a
qy = q + y * p / b
f = (p * qx) / (p + qx) # focal length
else: # Calculate for focused beam on sample in "manual" and "focused" mode
qy = None
f = (p * q) / (p + q) # focal length
pitch = 0
if 'Rh' in fm_stripe:
pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM
if 'Pt' in fm_stripe:
pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM
return pitch, qy
def cm_critical_angle(cm_stripe, energy):
if cm_stripe in 'Si':
stripe = bl.stripeSi
elif cm_stripe in 'Pt':
stripe = bl.stripePt
elif cm_stripe in 'Rh':
stripe = bl.stripeRh
else:
raise Exception(f'Stripe {stripe} not found in beamline parameters!')
w = CHeVcm/100/energy # convert energy [eV] to wavelength [m]
# Calculate critical angle for mirror
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(energy))
numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3)
criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi)
return criticalAngle
@@ -38,9 +38,20 @@ from debye_bec.bec_widgets.widgets.qt_widgets import (
NumberIndicator,
Mover,
)
from debye_bec.bec_widgets.widgets.digital_twin.calculate_positions import calc_positions
from debye_bec.bec_widgets.widgets.digital_twin.calculate_sideview import calc_sideview
from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions
from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview
from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces
from debye_bec.bec_widgets.widgets.digital_twin.calc_varia import (
sldi_gap_to_acc,
cm_trx_to_stripe,
fm_trx_to_stripe,
mo1_energy_resolution,
cm_reflectivity,
fm_reflectivity,
mo1_bragg_angle,
fm_ideal_pitch,
cm_critical_angle,
)
import debye_bec.bec_widgets.widgets.x01da_parameters as bl
@@ -104,9 +115,12 @@ class DigitalTwin(BECWidget, QWidget):
self._timer = QTimer(self)
self._timer.setInterval(1000)
self._timer.timeout.connect(self.calc_reality)
# TODO: Check if I need to stop the timer if the widget is closed?
self._timer.start()
def apply_theme(self, theme):
self.sideview_plot.apply_theme(theme)
self.surface_plots.apply_theme(theme)
@SafeSlot()
def calc_assistant(self, *args, **kwargs):
identifier = kwargs['identifier']
@@ -156,105 +170,6 @@ class DigitalTwin(BECWidget, QWidget):
self.calc_assistant_sideview()
self.calc_assistant_surfaces()
def update_fm_mode(self):
fm_focus = self.input.fm_focus.currentText()
if fm_focus in 'Manual':
self.input.fm_pitch.setVisible(True)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
self.input.fm_focy.setVisible(False)
elif fm_focus in 'Focused':
self.input.fm_pitch.setVisible(False)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
self.input.fm_focy.setVisible(False)
else: # Defocused
self.input.fm_pitch.setVisible(False)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(True)
self.input.fm_focy.setVisible(True)
@SafeSlot()
def calc_reality(self):
config = self.get_reality_config()
beam = calc_sideview(config)
data = {'x': beam['Z'], 'y': beam['Y']}
self.sideview_plot.update_curves('reality', data)
surfaces = calc_surfaces(config)
self.surface_plots.update_surfaces(scene='reality', data=surfaces)
def calc_mo1_energy_resolution(self, *args, **kwargs):
xtal = self.input.mo1_xtal.currentText().translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
index = bl.mo1.xtal.index(xtal)
crystal = bl.mo1.material1[index]
E = self.input.energy.value()
dtheta = np.linspace(-30, 90, 601)
theta = crystal.get_Bragg_angle(E) + dtheta * 1e-6
refl = np.abs(crystal.get_amplitude(E, np.sin(theta))[0])**2 # single crystal
refl2 = refl**2 # DCM with parallel crystals
# FWHM of the DCM curve
spline = UnivariateSpline(dtheta, refl2 - refl2.max()/2, s=0)
r1, r2 = spline.roots()
fwhm_rad = (r2 - r1) * 1e-6 # µrad → rad
# Energy resolution
theta_B = crystal.get_Bragg_angle(E)
dE_over_E = fwhm_rad / np.tan(theta_B)
dE = dE_over_E * E
# logger.info(f"DCM FWHM : {r2-r1:.2f} µrad")
# logger.info(f"ΔE/E : {dE_over_E:.2e}")
# logger.info(f"ΔE : {dE:.3f} eV at {E} eV")
self.input.mo1_eres.setValue(dE)
def calc_cm_reflectivity(self):
index = bl.cm.surface.index(self.input.cm_stripe.currentText())
rs, rp = bl.cm.material[index].get_amplitude(
self.input.energy.value(),
np.sin(-self.input.cm_pitch.value() * 1e-3)
)[0:2]
self.input.cm_refl.setValue(100 * abs(rs)**2)
self.input.cm_refl.setLabel(f"Reflectivity at \n{self.input.energy.value():.0f} eV")
rs, rp = bl.cm.material[index].get_amplitude(
2 * self.input.energy.value(),
np.sin(-self.input.cm_pitch.value() * 1e-3)
)[0:2]
self.input.cm_refl_harm.setValue(100 * abs(rs)**2)
self.input.cm_refl_harm.setLabel(f"Reflectivity at \n{3 * self.input.energy.value():.0f} eV")
def calc_fm_reflectivity(self):
if self.input.fm_stripe.currentText() in ('Rh (toroid)', 'Pt (toroid)'):
surface = bl.fm.surfaceToroid
material = bl.fm.materialToroid
stripe = re.sub(r'\s*\(.*?\)', '', self.input.fm_stripe.currentText()).strip()
index = surface.index(stripe)
else:
surface = bl.fm.surfaceFlat
material = bl.fm.materialFlat
stripe = re.sub(r'\s*\(.*?\)', '', self.input.fm_stripe.currentText()).strip()
index = surface.index(stripe)
rs, rp = material[index].get_amplitude(
self.input.energy.value(),
np.sin(-self.input.fm_pitch.value() * 1e-3)
)[0:2]
self.input.fm_refl.setValue(100 * abs(rs)**2)
self.input.fm_refl.setLabel(f"Reflectivity at \n{self.input.energy.value():.0f} eV")
rs, rp = material[index].get_amplitude(
2 * self.input.energy.value(),
np.sin(-self.input.fm_pitch.value() * 1e-3)
)[0:2]
self.input.fm_refl_harm.setValue(100 * abs(rs)**2)
self.input.fm_refl_harm.setLabel(f"Reflectivity at \n{3 * self.input.energy.value():.0f} eV")
def calc_cm_fm_harm_suppr(self):
harm_suppr = (self.input.cm_refl.value() * self.input.fm_refl.value()) / (self.input.cm_refl_harm.value() * self.input.fm_refl_harm.value())
self.input.cm_fm_harm_suppr.setValue(harm_suppr)
self.input.cm_fm_harm_suppr.setLabel(f"Total Suppression Factor at {3 * self.input.energy.value():.0f} eV")
def get_assistant_config(self):
fm_focus = self.input.fm_focus.currentText()
@@ -296,23 +211,12 @@ class DigitalTwin(BECWidget, QWidget):
mo1_bragg = self.dev.mo1_bragg.read()
sldi_gapx = self.dev.sldi_gapx.read()['sldi_gapx']['value']
sldi_gapy = self.dev.sldi_gapy.read()['sldi_gapy']['value']
d1 = bl.feSlits.center1[1]
h_acc = np.tan(sldi_gapx / (2 * d1))
v_acc = np.tan(sldi_gapy / (2 * d1))
h_acc, v_acc = sldi_gap_to_acc(sldi_gapx, sldi_gapy)
cm_trx = -self.dev.cm_trx.read()['cm_trx']['value']
cm_stripe = None
for name, low, high in zip(bl.cm.surface, bl.cm.limOptX[0], bl.cm.limOptX[1]):
if low <= cm_trx <= high:
cm_stripe = name
cm_stripe = cm_trx_to_stripe(cm_trx)
cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3
fm_trx = -self.dev.fm_trx.read()['fm_trx']['value']
fm_stripe = None
for name, low, high in zip(bl.fm.surfaceFlat, bl.fm.limOptXFlat[1], bl.fm.limOptXFlat[0]):
if low <= fm_trx <= high:
fm_stripe = name + ' (flat)'
for name, low, high in zip(bl.fm.surfaceToroid, bl.fm.limOptXToroid[1], bl.fm.limOptXToroid[0]):
if low <= fm_trx <= high:
fm_stripe = name + ' (toroid)'
fm_stripe = fm_trx_to_stripe(fm_trx)
fm_pitch = -self.dev.fm_rotx.read()['fm_rotx']['value'] * 1e-3
fm_pitch_real = 2 * cm_pitch - fm_pitch
config = { # Config in SI units!
@@ -334,21 +238,75 @@ class DigitalTwin(BECWidget, QWidget):
# logger.info(f'Config created: {config}')
return config
@SafeSlot()
def update_fm_mode(self):
fm_focus = self.input.fm_focus.currentText()
if fm_focus in 'Manual':
self.input.fm_pitch.setVisible(True)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
self.input.fm_focy.setVisible(False)
elif fm_focus in 'Focused':
self.input.fm_pitch.setVisible(False)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
self.input.fm_focy.setVisible(False)
else: # Defocused
self.input.fm_pitch.setVisible(False)
self.input.fm_pitch_ideal.setVisible(True)
self.input.fm_focx.setVisible(True)
self.input.fm_focy.setVisible(True)
def calc_reality(self):
config = self.get_reality_config()
beam = calc_sideview(config)
data = {'x': beam['Z'], 'y': beam['Y']} # TODO: Refactor sideview calculator to match data format
self.sideview_plot.update_curves('reality', data)
surfaces = calc_surfaces(config)
self.surface_plots.update_surfaces(scene='reality', data=surfaces)
def calc_mo1_energy_resolution(self):
xtal = self.input.mo1_xtal.currentText().translate(str.maketrans('', '', '()')) # Remove brackets from xtal name to conform with parameters
energy = self.input.energy.value()
self.input.mo1_eres.setValue(mo1_energy_resolution(xtal, energy))
def calc_cm_reflectivity(self):
cm_stripe = self.input.cm_stripe.currentText()
cm_pitch = -self.input.cm_pitch.value() * 1e-3
energy = self.input.energy.value()
self.input.cm_refl.setValue(100 * cm_reflectivity(cm_stripe, cm_pitch, energy))
self.input.cm_refl.setLabel(f"Reflectivity at \n{energy:.0f} eV")
self.input.cm_refl_harm.setValue(100 * cm_reflectivity(cm_stripe, cm_pitch, 3*energy))
self.input.cm_refl_harm.setLabel(f"Reflectivity at \n{3*energy:.0f} eV")
def calc_fm_reflectivity(self):
fm_stripe = self.input.fm_stripe.currentText()
fm_focus = self.input.fm_focus.currentText()
if fm_focus in 'Manual':
fm_pitch = -self.input.fm_pitch.value() * 1e-3
else:
fm_pitch = -self.input.fm_pitch_ideal.value() * 1e-3
energy = self.input.energy.value()
self.input.fm_refl.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, energy))
self.input.fm_refl.setLabel(f"Reflectivity at \n{energy:.0f} eV")
self.input.fm_refl_harm.setValue(100 * fm_reflectivity(fm_stripe, fm_pitch, 3*energy))
self.input.fm_refl_harm.setLabel(f"Reflectivity at \n{3*energy:.0f} eV")
def calc_cm_fm_harm_suppr(self):
harm_suppr = (self.input.cm_refl.value() * self.input.fm_refl.value()) / (self.input.cm_refl_harm.value() * self.input.fm_refl_harm.value())
self.input.cm_fm_harm_suppr.setValue(harm_suppr)
self.input.cm_fm_harm_suppr.setLabel(f"Total Suppression Factor at {3 * self.input.energy.value():.0f} eV")
def calc_assistant_sideview(self):
beam = calc_sideview(self.get_assistant_config())
data = {'x': beam['Z'], 'y': beam['Y']}
self.sideview_plot.update_curves('assistant', data)
@SafeSlot()
def calc_assistant_surfaces(self):
surfaces = calc_surfaces(self.get_assistant_config())
self.surface_plots.update_surfaces(scene='assistant', data=surfaces)
@SafeSlot()
def calc_positions(self):
out = calc_positions(self.get_assistant_config())
self.positions.sldi_gapx.setValue(out['sldi_gapx']['value'])
self.positions.sldi_gapy.setValue(out['sldi_gapy']['value'])
self.positions.cm_trx.setValue(out['cm_trx']['value'])
@@ -370,7 +328,6 @@ class DigitalTwin(BECWidget, QWidget):
self.positions.ot_rotx.setValue(out['ot_rotx']['value'])
self.positions.ot_es1_trz.setValue(out['ot_es1_trz']['value'])
@SafeSlot()
def calc_mo1_bragg_angle(self):
"""
Calculates bragg angle in rad
@@ -382,29 +339,13 @@ class DigitalTwin(BECWidget, QWidget):
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.get()
else:
raise Exception(f'Invalid xtal selection: {xtal}')
H = 6.62606957E-34
E = 1.602176634E-19
C = 299792458
wl = C * H / (E * self.input.energy.value())
val = wl / (2 * d_spacing * 1e-10)
self.bragg_angle = 0
if val > -1 and val < 1:
self.bragg_angle = np.asin(val)
cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3
if self.input.mo1_mode.currentText() in 'Monochromatic':
# Add 2x CM pitch to the bragg angle
bragg_angle_cor = ((2 * cm_pitch) + self.bragg_angle) / np.pi * 180
elif self.input.mo1_mode.currentText() in 'Pinkbeam':
# Align xtal surfaces parallel to beam
bragg_angle_cor = (2 * cm_pitch) / np.pi * 180
mo1_mode = self.input.mo1_mode.currentText()
energy = self.input.energy.value()
theta, theta_cor = mo1_bragg_angle(mo1_mode, d_spacing, energy, cm_pitch)
self.bragg_angle = theta
self.input.mo1_bragg_angle.setValue(theta_cor / np.pi * 180)
self.input.mo1_bragg_angle.setValue(bragg_angle_cor)
# self.calc_positions()
@SafeSlot()
def update_mo1_mode(self):
if self.input.mo1_mode.currentText() in 'Monochromatic':
self.input.mo1_xtal.setVisible(True)
@@ -415,45 +356,22 @@ class DigitalTwin(BECWidget, QWidget):
self.input.mo1_bragg_angle.setVisible(False)
self.input.mo1_eres.setVisible(False)
@SafeSlot()
def calc_fm_ideal_pitch(self):
p = bl.fm.center[1] # posFM
q = self.input.smpl.value() - bl.fm.center[1] # dist posFM to posEX
if self.input.fm_focus.currentText() in 'Defocused':
a = 2 * np.tan(self.input.sldi_hacc.value() * 1e-3) * bl.fm.center[1] # Beam width at focusing mirror
b = 2 * np.tan(self.input.sldi_vacc.value() * 1e-3) * bl.cm.center[1] # Beam height at focusing mirror (collimated beam)
x = self.input.fm_focx.value()
y = self.input.fm_focy.value()
qx = q + x * p / a
self.qy = q + y * p / b
f = (p * qx) / (p + qx) # focal length
else: # Calculate for focused beam on sample in "manual" and "focused" mode
f = (p * q) / (p + q) # focal length
pitch = 0
if 'Rh' in self.input.fm_stripe.currentText():
pitch = np.arcsin(bl.fm.r[0]/(2*f))# ideal pitch for FM
if 'Pt' in self.input.fm_stripe.currentText():
pitch = np.arcsin(bl.fm.r[1]/(2*f)) # ideal pitch for FM
self.input.fm_pitch_ideal.setValue(-pitch * 1e3)
def calc_fm_ideal_pitch(self): # TODO: What happens if the flats are selected?
fm_focus = self.input.fm_focus.currentText()
fm_stripe = self.input.fm_stripe.currentText()
smpl = self.input.smpl.value()
sldi_hacc = self.input.sldi_hacc.value() * 1e-3
sldi_vacc = self.input.sldi_vacc.value() * 1e-3
fm_focx = self.input.fm_focx.value()
fm_focy = self.input.fm_focy.value()
fm_pitch, qy = fm_ideal_pitch(fm_focus, fm_stripe, smpl, sldi_hacc, sldi_vacc, fm_focx, fm_focy)
self.qy = qy
self.input.fm_pitch_ideal.setValue(-fm_pitch * 1e3)
@SafeSlot()
def calc_cm_crit_pitch(self):
stripe = self.input.cm_stripe.currentText()
# Config Mirror
if stripe in 'Si':
stripe = bl.stripeSi
elif stripe in 'Pt':
stripe = bl.stripePt
elif stripe in 'Rh':
stripe = bl.stripeRh
else:
raise Exception(f'Stripe {stripe} not found in beamline parameters!')
w = CHeVcm/100/self.input.energy.value() # convert energy [eV] to wavelength [m]
# Calculate critical angle for mirror
f1 = stripe.elements[0].Z + np.real(stripe.elements[0].get_f1f2(self.input.energy.value()))
numberDensity = stripe.rho*1e3*AVOGADRO/(stripe.elements[0].mass/1e3)
criticalAngle = np.sqrt(numberDensity*2.8179e-15*w**2*f1/np.pi)
self.input.cm_pitch_critical.setValue(-criticalAngle * 1e3)
cm_stripe = self.input.cm_stripe.currentText()
energy = self.input.energy.value()
self.input.cm_pitch_critical.setValue(-cm_critical_angle(cm_stripe, energy) * 1e3)
class InputPanel(QWidget):
"""Right-side control panel: input field, indicator, send, recording."""
@@ -720,11 +638,11 @@ class MoverPanel(QWidget):
self._layout .addWidget(self.mover_group)
self._layout .addStretch()
class SurfacePlots(BECWidget, QWidget):
class SurfacePlots(QWidget):
"""Plot widget with two curves and legend."""
def __init__(self, parent=None, *arg, **kwargs):
super().__init__(parent=parent, theme_update=True, *arg, **kwargs)
def __init__(self, parent=None):
super().__init__(parent=parent)
self._layout = QHBoxLayout(self)
self.surfaces = {
@@ -776,7 +694,7 @@ class SurfacePlots(BECWidget, QWidget):
# Create surfaces
for idx, scene in enumerate(self.surfaces):
for name, device in self.surfaces[scene].items():
for name, _ in self.surfaces[scene].items():
if scene in 'assistant':
brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern)
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine)
@@ -830,7 +748,7 @@ class SurfacePlots(BECWidget, QWidget):
self.text_color = (0, 0, 0)
for idx, scene in enumerate(self.surfaces):
for name, device in self.surfaces[scene].items():
for name, _ in self.surfaces[scene].items():
if scene in 'assistant':
brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern)
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine)
@@ -860,7 +778,7 @@ class SurfacePlots(BECWidget, QWidget):
rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101
rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
widget.addItem(rect)
text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) # TODO: CHange color according to theme
text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5))
widget.addItem(text)
text.setPos((hx+lx)/2, (hy+ly)/2)
text.setZValue(10)
@@ -868,17 +786,17 @@ class SurfacePlots(BECWidget, QWidget):
self.texts.append(text)
def plot_mono_surface(widget, xtal, xtalWidth, xtalOffsetX, xtalLength):
for sf, w, offx, len in zip(xtal, xtalWidth, xtalOffsetX, xtalLength):
for sf, w, offx, length in zip(xtal, xtalWidth, xtalOffsetX, xtalLength):
rect = pg.QtWidgets.QGraphicsRectItem( # pylint: disable=E1101
offx - w/2,
-len/2,
-length/2,
w,
len,
length,
)
rect.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101
rect.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
widget.addItem(rect)
text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5)) # TODO: CHange color according to theme
text = pg.TextItem(sf, color=self.text_color, anchor=(0.5, 0.5))
widget.addItem(text)
text.setPos(offx, 0)
text.setZValue(10)
@@ -897,7 +815,7 @@ class SurfacePlots(BECWidget, QWidget):
plot_mirror_stripe(plot['widget'], bl.fm.surfaceToroid, bl.fm.limOptXToroid, bl.fm.limOptYToroid)
else:
raise Exception(f'Plot {name} not found!')
for name, plot in self.plots.items():
for name, plot in self.plots.items():
plot['widget'].disableAutoRange()
def update_surfaces(self, scene, data):
@@ -908,11 +826,11 @@ class SurfacePlots(BECWidget, QWidget):
y = np.array(device['y'] + [device['y'][0]]) if len(device['y']) != 0 else np.array([])
plot.setData(x=x, y=y)
class SideviewPlot(BECWidget, QWidget):
class SideviewPlot(QWidget):
"""Plot widget with two curves and legend."""
def __init__(self, parent=None, *arg, **kwargs):
super().__init__(parent=parent, theme_update=True, *arg, **kwargs)
def __init__(self, parent=None):
super().__init__(parent=parent)
self._layout = QVBoxLayout(self)
# self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
@@ -1045,7 +963,6 @@ class SideviewPlot(BECWidget, QWidget):
if __name__ == "__main__":
from qtpy.QtWidgets import QApplication
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.colors import apply_theme
@@ -1053,7 +970,5 @@ if __name__ == "__main__":
apply_theme("light")
dispatcher = BECDispatcher(gui_id="digital_twin")
win = DigitalTwin()
# win.resize(1000, 800)
win.show()
sys.exit(app.exec_())
@@ -139,7 +139,7 @@ cm = collimatingMirror(
material=(stripeSi, stripePt, stripeRh),
limPhysX=(-34, 34),
limPhysY=(-600, 600),
limOptX=((-27, -3.5, 15), (-11, 6.5, 25)),
limOptX=((-21, -7, 14), (-11, 11, 23)),
limOptY=((-500, -500, -500), (500, 500, 500)),
R=[3e6, 15e6],
pitch=[-5.0e-3, -0.0e-3],