nbs-bl 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. nbs_bl/__init__.py +15 -0
  2. nbs_bl/beamline.py +450 -0
  3. nbs_bl/configuration.py +838 -0
  4. nbs_bl/detectors.py +89 -0
  5. nbs_bl/devices/__init__.py +12 -0
  6. nbs_bl/devices/detectors.py +154 -0
  7. nbs_bl/devices/motors.py +242 -0
  8. nbs_bl/devices/sampleholders.py +360 -0
  9. nbs_bl/devices/shutters.py +120 -0
  10. nbs_bl/devices/slits.py +51 -0
  11. nbs_bl/gGrEqns.py +171 -0
  12. nbs_bl/geometry/__init__.py +0 -0
  13. nbs_bl/geometry/affine.py +197 -0
  14. nbs_bl/geometry/bars.py +189 -0
  15. nbs_bl/geometry/frames.py +534 -0
  16. nbs_bl/geometry/linalg.py +138 -0
  17. nbs_bl/geometry/polygons.py +56 -0
  18. nbs_bl/help.py +126 -0
  19. nbs_bl/hw.py +270 -0
  20. nbs_bl/load.py +113 -0
  21. nbs_bl/motors.py +19 -0
  22. nbs_bl/planStatus.py +5 -0
  23. nbs_bl/plans/__init__.py +8 -0
  24. nbs_bl/plans/batches.py +174 -0
  25. nbs_bl/plans/conditions.py +77 -0
  26. nbs_bl/plans/flyscan_base.py +180 -0
  27. nbs_bl/plans/groups.py +55 -0
  28. nbs_bl/plans/maximizers.py +423 -0
  29. nbs_bl/plans/metaplans.py +179 -0
  30. nbs_bl/plans/plan_stubs.py +246 -0
  31. nbs_bl/plans/preprocessors.py +160 -0
  32. nbs_bl/plans/scan_base.py +58 -0
  33. nbs_bl/plans/scan_decorators.py +524 -0
  34. nbs_bl/plans/scans.py +145 -0
  35. nbs_bl/plans/suspenders.py +87 -0
  36. nbs_bl/plans/time_estimation.py +168 -0
  37. nbs_bl/plans/xas.py +123 -0
  38. nbs_bl/printing.py +221 -0
  39. nbs_bl/qt/models/beamline.py +11 -0
  40. nbs_bl/qt/models/energy.py +53 -0
  41. nbs_bl/qt/widgets/energy.py +225 -0
  42. nbs_bl/queueserver.py +249 -0
  43. nbs_bl/redisDevice.py +96 -0
  44. nbs_bl/run_engine.py +63 -0
  45. nbs_bl/samples.py +130 -0
  46. nbs_bl/settings.py +68 -0
  47. nbs_bl/shutters.py +39 -0
  48. nbs_bl/sim/__init__.py +2 -0
  49. nbs_bl/sim/config/polphase.nc +0 -0
  50. nbs_bl/sim/energy.py +403 -0
  51. nbs_bl/sim/manipulator.py +14 -0
  52. nbs_bl/sim/utils.py +36 -0
  53. nbs_bl/startup.py +27 -0
  54. nbs_bl/status.py +114 -0
  55. nbs_bl/tests/__init__.py +0 -0
  56. nbs_bl/tests/modify_regions.py +160 -0
  57. nbs_bl/tests/test_frames.py +99 -0
  58. nbs_bl/tests/test_panels.py +69 -0
  59. nbs_bl/utils.py +235 -0
  60. nbs_bl-0.2.0.dist-info/METADATA +71 -0
  61. nbs_bl-0.2.0.dist-info/RECORD +64 -0
  62. nbs_bl-0.2.0.dist-info/WHEEL +4 -0
  63. nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
  64. nbs_bl-0.2.0.dist-info/licenses/LICENSE +13 -0
@@ -0,0 +1,87 @@
1
+ from ..utils import merge_func
2
+ from importlib.metadata import entry_points
3
+ from bluesky.preprocessors import finalize_wrapper
4
+ from bluesky.suspenders import SuspenderBase
5
+ from bluesky import Msg
6
+ from ..beamline import GLOBAL_BEAMLINE
7
+
8
+
9
+ def get_suspender_entrypoints():
10
+ """
11
+ Get suspender entrypoints from beamline configuration.
12
+
13
+ Returns
14
+ -------
15
+ list
16
+ List of decorator functions loaded from entrypoints
17
+ """
18
+ config = GLOBAL_BEAMLINE.config
19
+ suspender_entrypoints = config.get("settings", {}).get("suspenders", [])
20
+ print(f"Suspender entrypoints: {suspender_entrypoints}")
21
+ suspenders = []
22
+ if suspender_entrypoints:
23
+ eps = entry_points()
24
+
25
+ for ep_name in suspender_entrypoints:
26
+ try:
27
+ # Look for entrypoint in nbs_bl.suspenders group
28
+ matches = eps.select(group="nbs_bl.suspenders", name=ep_name)
29
+ for match in matches:
30
+ print(f"Loading suspender {match.name}")
31
+ suspender = match.load()
32
+
33
+ # Handle both single suspender and list of suspenders
34
+ if isinstance(suspender, (list, tuple)):
35
+ suspenders.extend(suspender)
36
+ else:
37
+ suspenders.append(suspender)
38
+ except Exception as e:
39
+ print(f"Failed to load suspender {ep_name}: {e}")
40
+
41
+ return suspenders
42
+
43
+
44
+ def dynamic_suspenders(func):
45
+ # Load suspenders from entrypoints at decoration time
46
+ suspenders = get_suspender_entrypoints()
47
+
48
+ @merge_func(func)
49
+ def wrapper(*args, skip_suspenders=False, **kwargs):
50
+ """
51
+ Parameters
52
+ ----------
53
+ skip_suspenders : bool, optional
54
+ If True, do not install suspenders
55
+ """
56
+ # Check if suspenders should be skipped
57
+
58
+ if skip_suspenders or not suspenders:
59
+ return (yield from func(*args, **kwargs))
60
+ else:
61
+ suspender_list = []
62
+ for sus in suspenders:
63
+ if callable(sus) and not isinstance(sus, SuspenderBase):
64
+ sus = sus()
65
+ if isinstance(sus, (list, tuple)):
66
+ suspender_list.extend(sus)
67
+ else:
68
+ suspender_list.append(sus)
69
+
70
+ def _install():
71
+ for susp in suspender_list:
72
+ yield Msg("install_suspender", None, susp)
73
+
74
+ def _remove():
75
+ for susp in suspender_list:
76
+ yield Msg("remove_suspender", None, susp)
77
+
78
+ @merge_func(func)
79
+ def _inner_plan(*args, **kwargs):
80
+ yield from _install()
81
+ return (yield from func(*args, **kwargs))
82
+
83
+ return (
84
+ yield from finalize_wrapper(_inner_plan(*args, **kwargs), _remove())
85
+ )
86
+
87
+ return wrapper
@@ -0,0 +1,168 @@
1
+ import numpy as np
2
+ from .scan_base import _make_gscan_points
3
+
4
+ """
5
+ Time estimation functions for plan execution time calculation.
6
+
7
+ Usage Examples:
8
+ ---------------
9
+ 1. Basic time estimation:
10
+ estimation_dict = {
11
+ "estimator": "generic_estimate",
12
+ "fixed": 10,
13
+ "overhead": 0.5,
14
+ "dwell": "dwell_time"
15
+ }
16
+ plan_args = {"dwell_time": 2.0, "points": 100}
17
+ time = generic_estimate("my_plan", plan_args, estimation_dict)
18
+
19
+ 2. With repeat functionality:
20
+ estimation_dict = {
21
+ "estimator": "generic_estimate",
22
+ "fixed": 10,
23
+ "overhead": 0.5,
24
+ "dwell": "dwell_time",
25
+ "reset": 5.0 # 5 seconds between repeats
26
+ }
27
+ plan_args = {"dwell_time": 2.0, "points": 100, "repeat": 3}
28
+ time = generic_estimate("my_plan", plan_args, estimation_dict)
29
+ # Result: (base_time * 3) + (5.0 * 2) = base_time * 3 + 10.0
30
+ """
31
+
32
+
33
+ def with_repeat(estimator_func):
34
+ """
35
+ Decorator to add repeat functionality to time estimators.
36
+
37
+ This decorator handles the "repeat" parameter in plan_args and "reset" parameter
38
+ in estimation_dict. It calculates the base plan time, multiplies by repeats,
39
+ and adds reset time for each repeat except the last one.
40
+
41
+ Parameters
42
+ ----------
43
+ estimator_func : callable
44
+ The original time estimator function
45
+
46
+ Returns
47
+ -------
48
+ callable
49
+ Wrapped estimator function that handles repeats
50
+ """
51
+
52
+ def wrapper(plan_name, plan_args, estimation_dict):
53
+ # Get repeat count and remove from plan_args to avoid confusion
54
+ repeat_count = plan_args.get("repeat", 1)
55
+
56
+ # Get reset time from estimation parameters
57
+ reset_time = estimation_dict.get("reset_time", 0)
58
+
59
+ # Calculate base plan time using original estimator
60
+ base_time = estimator_func(plan_name, plan_args, estimation_dict)
61
+
62
+ if base_time is None:
63
+ return None
64
+
65
+ # Calculate total time: base_time * repeats + reset_time * (repeats - 1)
66
+ total_time = base_time * repeat_count + reset_time * (repeat_count - 1)
67
+
68
+ return total_time
69
+
70
+ return wrapper
71
+
72
+
73
+ def get_dwell(plan_args, estimation_dict):
74
+ dwell = estimation_dict.get("dwell", "dwell")
75
+ if isinstance(dwell, str):
76
+ return plan_args.get(dwell, 0)
77
+ else:
78
+ return dwell
79
+
80
+
81
+ @with_repeat
82
+ def generic_estimate(plan_name, plan_args, estimation_dict):
83
+ a = estimation_dict.get("fixed", 0)
84
+ b = estimation_dict.get("overhead", 0)
85
+ c = get_dwell(plan_args, estimation_dict)
86
+ if "points" in estimation_dict:
87
+ points = estimation_dict.get("points")
88
+ if isinstance(points, str):
89
+ if points == "args":
90
+ n = len(plan_args.get("args", []))
91
+ else:
92
+ n = plan_args.get(points, 0)
93
+ else:
94
+ n = points
95
+ else:
96
+ n = 0
97
+ return a + b * n + c * n
98
+
99
+
100
+ @with_repeat
101
+ def list_scan_estimate(plan_name, plan_args, estimation_dict):
102
+ a = estimation_dict.get("fixed", 0)
103
+ b = estimation_dict.get("overhead", 0)
104
+ c = get_dwell(plan_args, estimation_dict)
105
+
106
+ n = len(plan_args.get("args", ["", []])[1])
107
+ return a + b * n + c * n
108
+
109
+
110
+ @with_repeat
111
+ def grid_scan_estimate(plan_name, plan_args, estimation_dict):
112
+ a = estimation_dict.get("fixed", 0)
113
+ b = estimation_dict.get("overhead", 0)
114
+ c = get_dwell(plan_args, estimation_dict)
115
+
116
+ args = plan_args.get("args")
117
+ n_axes = len(args) // 4
118
+ n_points = 1
119
+ for i in range(n_axes):
120
+ n_points *= args[i * 4 + 3]
121
+ return a + b * n_points + c * n_points
122
+
123
+
124
+ @with_repeat
125
+ def list_grid_scan_estimate(plan_name, plan_args, estimation_dict):
126
+ a = estimation_dict.get("fixed", 0)
127
+ b = estimation_dict.get("overhead", 0)
128
+ c = get_dwell(plan_args, estimation_dict)
129
+
130
+ args = plan_args.get("args")
131
+ n_axes = len(args) // 2
132
+ n_points = 1
133
+ for i in range(n_axes):
134
+ n_points *= len(args[i * 2 + 1])
135
+ return a + b * n_points + c * n_points
136
+
137
+
138
+ @with_repeat
139
+ def fly_scan_estimate(plan_name, plan_args, estimation_dict):
140
+ if "args" in plan_args:
141
+ args = plan_args["args"]
142
+ if len(args) % 2 == 0:
143
+ args = args[1:]
144
+ start = args[0]
145
+ stop = args[1]
146
+ speed = args[2]
147
+ else:
148
+ start = plan_args.get("start", 0)
149
+ stop = plan_args.get("stop", 0)
150
+ speed = plan_args.get("speed", 1)
151
+ a = estimation_dict.get("fixed", 0)
152
+ return a + np.abs((stop - start) / speed)
153
+
154
+
155
+ @with_repeat
156
+ def gscan_estimate(plan_name, plan_args, estimation_dict):
157
+ if "region" in estimation_dict:
158
+ region = estimation_dict.get("region")
159
+ else:
160
+ region = plan_args.get("args")
161
+ if len(region) % 2 == 0:
162
+ region = region[1:] # First argument is motor name
163
+ points = len(_make_gscan_points(*region))
164
+ a = estimation_dict.get("fixed", 0)
165
+ b = estimation_dict.get("overhead", 0)
166
+ c = get_dwell(plan_args, estimation_dict)
167
+
168
+ return a + b * points + c * points
nbs_bl/plans/xas.py ADDED
@@ -0,0 +1,123 @@
1
+ # from ..settings import settings
2
+ from ..plans.scans import nbs_gscan
3
+ from ..utils import merge_func
4
+ from ..plans.preprocessors import wrap_metadata
5
+ from .plan_stubs import set_roi, clear_one_roi
6
+ from ..help import _add_to_import_list, add_to_func_list, add_to_plan_time_dict
7
+ from ..queueserver import GLOBAL_USER_STATUS
8
+ from ..status import StatusDict
9
+ from ..beamline import GLOBAL_BEAMLINE
10
+ from .scan_base import _make_gscan_points
11
+
12
+ # from ..settings import GLOBAL_SETTINGS as settings
13
+ from os.path import join
14
+
15
+ try:
16
+ import tomllib
17
+ except ModuleNotFoundError:
18
+ import tomli as tomllib
19
+
20
+ GLOBAL_XAS_PLANS = GLOBAL_USER_STATUS.request_status_dict("XAS_PLANS", use_redis=True)
21
+ GLOBAL_XAS_PLANS.clear()
22
+
23
+
24
+ def add_to_xas_list(f, key, **plan_info):
25
+ """
26
+ A function decorator that will add the plan to the built-in list
27
+ """
28
+ _add_to_import_list(f, "xas")
29
+ GLOBAL_XAS_PLANS[key] = {}
30
+ GLOBAL_XAS_PLANS[key].update(plan_info)
31
+ return f
32
+
33
+
34
+ def _wrap_xas(element, edge):
35
+ def decorator(func):
36
+ return wrap_metadata({"element": element, "edge": edge, "scantype": "xas"})(
37
+ func
38
+ )
39
+
40
+ return decorator
41
+
42
+
43
+ # Needs to have Element, Edge, Ref Element,
44
+ def _xas_factory(energy_grid, element, edge, key):
45
+ @_wrap_xas(element, edge)
46
+ @wrap_metadata({"plan_name": key})
47
+ @merge_func(nbs_gscan, omit_params=["motor", "start", "stop", "step", "args"])
48
+ def inner(**kwargs):
49
+ """Parameters
50
+ ----------
51
+ repeat : int
52
+ Number of times to repeat the scan
53
+ **kwargs :
54
+ Arguments to be passed to tes_gscan
55
+
56
+ """
57
+ eref_sample = kwargs.pop("eref_sample", None)
58
+ if eref_sample is None:
59
+ eref_sample = element
60
+ yield from set_roi("pfy", energy_grid[0], energy_grid[-2])
61
+ yield from nbs_gscan(
62
+ GLOBAL_BEAMLINE.energy, *energy_grid, eref_sample=eref_sample, **kwargs
63
+ )
64
+ yield from clear_one_roi("pfy")
65
+
66
+ d = f"Perform an in-place xas scan for {element} with energy pattern {energy_grid} \n"
67
+ inner.__doc__ = d + inner.__doc__
68
+
69
+ inner.__qualname__ = key
70
+ inner.__name__ = key
71
+ inner._edge = edge
72
+ inner._short_doc = (
73
+ f"Do XAS for {element} from {energy_grid[0]} to {energy_grid[-2]}"
74
+ )
75
+ return inner
76
+
77
+
78
+ @add_to_func_list
79
+ def load_xas(filename):
80
+ """Load XAS plans from a TOML file and inject them into the IPython user namespace.
81
+
82
+ Parameters
83
+ ----------
84
+ filename : str
85
+ Path to the TOML file containing XAS plan definitions
86
+ """
87
+ try:
88
+ # Get IPython's user namespace
89
+ ip = get_ipython()
90
+ user_ns = ip.user_ns
91
+ except (NameError, AttributeError):
92
+ # Not running in IPython, just return the generated functions
93
+ user_ns = None
94
+
95
+ generated_plans = {}
96
+ with open(filename, "rb") as f:
97
+ regions = tomllib.load(f)
98
+ for key, value in regions.items():
99
+ name = value.get("name", key)
100
+ region = value.get("region")
101
+ element = value.get("element", "")
102
+ edge = value.get("edge", "")
103
+ xas_func = _xas_factory(region, element, edge, key)
104
+ add_to_xas_list(
105
+ xas_func, key, name=name, element=element, edge=edge, region=region
106
+ )
107
+ add_to_plan_time_dict(
108
+ xas_func,
109
+ "gscan_estimate",
110
+ fixed=5,
111
+ overhead=0.5,
112
+ dwell="dwell",
113
+ region=region,
114
+ )
115
+ # Store the function
116
+ generated_plans[key] = xas_func
117
+
118
+ # If we're in IPython, inject into user namespace
119
+ if user_ns is not None:
120
+ user_ns[key] = xas_func
121
+
122
+ # Return the generated plans dictionary in case it's needed
123
+ return generated_plans
nbs_bl/printing.py ADDED
@@ -0,0 +1,221 @@
1
+ from IPython.utils.coloransi import TermColors as color
2
+ from rich import print
3
+ import sys
4
+ import re
5
+ from select import select
6
+
7
+
8
+ def _strip_ansi(text):
9
+ """
10
+ Remove ANSI escape sequences from text.
11
+
12
+ Parameters
13
+ ----------
14
+ text : str
15
+ Text containing ANSI escape sequences
16
+
17
+ Returns
18
+ -------
19
+ str
20
+ Text with ANSI sequences removed
21
+ """
22
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
23
+ return ansi_escape.sub("", text)
24
+
25
+
26
+ def _ansilen(text):
27
+ """
28
+ Get visible length of text containing ANSI escape sequences.
29
+
30
+ Parameters
31
+ ----------
32
+ text : str
33
+ Text containing ANSI escape sequences
34
+
35
+ Returns
36
+ -------
37
+ int
38
+ Visual length of text
39
+ """
40
+ return len(_strip_ansi(text))
41
+
42
+
43
+ def _wrap_text(text, width, subsequent_indent=""):
44
+ """
45
+ Wrap text while preserving ANSI escape sequences.
46
+
47
+ Parameters
48
+ ----------
49
+ text : str
50
+ Text to wrap
51
+ width : int
52
+ Maximum line width
53
+ subsequent_indent : str
54
+ Indentation for lines after the first
55
+
56
+ Returns
57
+ -------
58
+ str
59
+ Wrapped text with preserved ANSI sequences
60
+ """
61
+ words = text.split()
62
+ lines = []
63
+ current_line = []
64
+ current_length = 0
65
+
66
+ for word in words:
67
+ word_length = _ansilen(word)
68
+ if current_length + word_length + (1 if current_line else 0) <= width:
69
+ current_line.append(word)
70
+ current_length += word_length + (1 if current_line else 0)
71
+ else:
72
+ if current_line:
73
+ lines.append(" ".join(current_line))
74
+ current_line = [word]
75
+ current_length = word_length
76
+
77
+ if current_line:
78
+ lines.append(" ".join(current_line))
79
+
80
+ # Add indentation to subsequent lines
81
+ if subsequent_indent and len(lines) > 1:
82
+ lines[1:] = [subsequent_indent + line for line in lines[1:]]
83
+
84
+ return "\n".join(lines)
85
+
86
+
87
+ def colored(text, tint="white", attrs=[]):
88
+ """
89
+ A simple wrapper around IPython's interface to TermColors
90
+ """
91
+ tint = tint.lower()
92
+ if "dark" in tint:
93
+ tint = tint[4:].lower()
94
+ elif "light" in tint:
95
+ tint = "bright_" + tint[5:].lower()
96
+ elif "blink" in tint:
97
+ tint = "bold " + tint[5:].lower()
98
+ elif "no" in tint:
99
+ tint = "reset"
100
+ else:
101
+ tint = tint.lower()
102
+ return "[{0}]{1}[/{0}]".format(tint, str(text))
103
+
104
+
105
+ # def colored(text, tint="white", attrs=[]):
106
+ # """
107
+ # A simple wrapper around IPython's interface to TermColors
108
+ # """
109
+ # tint = tint.lower()
110
+ # if "dark" in tint:
111
+ # tint = "Dark" + tint[4:].capitalize()
112
+ # elif "light" in tint:
113
+ # tint = "Light" + tint[5:].capitalize()
114
+ # elif "blink" in tint:
115
+ # tint = "Blink" + tint[5:].capitalize()
116
+ # elif "no" in tint:
117
+ # tint = "Normal"
118
+ # else:
119
+ # tint = tint.capitalize()
120
+ # return "{0}{1}{2}".format(getattr(color, tint), str(text), color.Normal)
121
+
122
+ def read_input(message, default, timeout, secs):
123
+ print(message)
124
+ rlist, _, _ = select([sys.stdin], [], [], secs)
125
+ if rlist:
126
+ s = sys.stdin.readline()
127
+ if ord(s[0]) == 13 or ord(s[0]) == 10:
128
+ return default
129
+ else:
130
+ return s.rstrip(chr(10) + chr(13))
131
+ else:
132
+ return timeout
133
+
134
+
135
+ def run_report(thisfile):
136
+ """
137
+ Noisily proclaim to be importing a file of python code.
138
+ """
139
+ print(colored("Importing %s ..." % thisfile.split("/")[-1], "lightcyan"))
140
+
141
+
142
+ def boxed_text(title, text, tint, width=82, shrink=False):
143
+ """
144
+ Put text in a lovely unicode block element box.
145
+
146
+ Parameters
147
+ ----------
148
+ title : str
149
+ Title to display at top of box
150
+ text : str or list
151
+ Text content to display in box
152
+ tint : str
153
+ Color to use for box borders
154
+ width : int
155
+ Maximum width of box
156
+ shrink : bool
157
+ Whether to shrink box to content width
158
+ """
159
+ if isinstance(text, str):
160
+ textlines = text.splitlines()
161
+ else:
162
+ textlines = text
163
+
164
+ if shrink:
165
+ width = min(width, max((_ansilen(line) for line in textlines)))
166
+
167
+ remainder = width - 5 - len(title)
168
+ ul = "\u250C"
169
+ ur = "\u2510"
170
+ ll = "\u2514"
171
+ lr = "\u2518"
172
+ bar = "\u2500"
173
+ strut = "\u2502"
174
+
175
+ print("")
176
+ print(colored("".join([ul, bar * 3, " ", title, " ", bar * remainder, ur]), tint))
177
+
178
+ for line1 in textlines:
179
+ lines = _wrap_text(line1, width, subsequent_indent=" " * 26)
180
+ for line in lines.splitlines():
181
+ line = line.rstrip()
182
+ add = " " * (width - _ansilen(line))
183
+ print("".join([colored(strut, tint), line, add, colored(strut, tint)]))
184
+
185
+ print(colored("".join([ll, bar * width, lr]), tint))
186
+
187
+
188
+ def error_msg(text):
189
+ return colored(text, "lightred")
190
+
191
+
192
+ def warning_msg(text):
193
+ return colored(text, "yellow")
194
+
195
+
196
+ def url_msg(text):
197
+ return text
198
+
199
+
200
+ def bold_msg(text):
201
+ return colored(text, "white")
202
+
203
+
204
+ def verbosebold_msg(text):
205
+ return colored(text, "lightcyan")
206
+
207
+
208
+ def list_msg(text):
209
+ return colored(text, "cyan")
210
+
211
+
212
+ def disconnected_msg(text):
213
+ return colored(text, "purple")
214
+
215
+
216
+ def info_msg(text):
217
+ return colored(text, "brown")
218
+
219
+
220
+ def whisper(text):
221
+ return colored(text, "darkgray")
@@ -0,0 +1,11 @@
1
+ from nbs_gui.models.beamline import GUIBeamlineModel
2
+
3
+
4
+ class SSTBeamlineModel(GUIBeamlineModel):
5
+ def __init__(self, *args, **kwargs):
6
+ super().__init__(*args, **kwargs)
7
+ try:
8
+ self.energy.obj.rotation_motor = self.primary_sampleholder.obj.r
9
+ except AttributeError as e:
10
+ print(f"Problem loading energy model: {e}")
11
+ print("Finished loading BeamlineModel")
@@ -0,0 +1,53 @@
1
+ from ..widgets.energy import EnergyControl, EnergyMonitor
2
+ from nbs_gui.models import PseudoPositionerModel, PVPositionerModel, PVModel
3
+
4
+
5
+ # Copied from ucal as an example
6
+ class EnergyModel:
7
+ default_controller = EnergyControl
8
+ default_monitor = EnergyMonitor
9
+
10
+ def __init__(
11
+ self,
12
+ name,
13
+ obj,
14
+ group,
15
+ long_name,
16
+ **kwargs,
17
+ ):
18
+ print("Initializing Energy")
19
+ self.name = name
20
+ self.obj = obj
21
+ self.energy = PseudoPositionerModel(name, obj, group, name)
22
+ self.grating_motor = PVPositionerModel(
23
+ name=obj.monoen.gratingx.name,
24
+ obj=obj.monoen.gratingx,
25
+ group=group,
26
+ long_name=f"{name} Grating",
27
+ )
28
+ self.cff = PVModel(
29
+ obj.monoen.cff.name, obj.monoen.cff, group=group, long_name=f"{name} CFF"
30
+ )
31
+ self.real_motors = self.energy.real_motors
32
+ self.group = group
33
+ self.label = long_name
34
+ for key, value in kwargs.items():
35
+ setattr(self, key, value)
36
+ print("Done Initializing Energy")
37
+
38
+ def iter_models(self):
39
+ """
40
+ Yield contained energy-related models for traversal.
41
+
42
+ Yields
43
+ ------
44
+ BaseModel
45
+ Contained models.
46
+ """
47
+ yield from (
48
+ self.energy,
49
+ self.grating_motor,
50
+ self.cff,
51
+ )
52
+ for motor in self.real_motors:
53
+ yield motor