bapsf-motion 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. bapsf_motion/__init__.py +59 -0
  2. bapsf_motion/actors/__init__.py +18 -0
  3. bapsf_motion/actors/axis_.py +347 -0
  4. bapsf_motion/actors/base.py +319 -0
  5. bapsf_motion/actors/demos/__init__.py +0 -0
  6. bapsf_motion/actors/demos/drive_timed_run_1D.py +80 -0
  7. bapsf_motion/actors/demos/drive_timed_run_2D.py +98 -0
  8. bapsf_motion/actors/demos/mgroup_timed_run_2D.py +103 -0
  9. bapsf_motion/actors/drive_.py +369 -0
  10. bapsf_motion/actors/manager_.py +437 -0
  11. bapsf_motion/actors/motion_group_.py +1117 -0
  12. bapsf_motion/actors/motor_.py +1870 -0
  13. bapsf_motion/actors/tests/__init__.py +0 -0
  14. bapsf_motion/examples/20240627_2D_test_setup.toml +52 -0
  15. bapsf_motion/examples/20241004_2D_test_setup.toml +57 -0
  16. bapsf_motion/examples/bapsf_motion.toml +85 -0
  17. bapsf_motion/examples/benchtop_motion_group.toml +27 -0
  18. bapsf_motion/examples/benchtop_run.toml +42 -0
  19. bapsf_motion/examples/mg_1d.toml +24 -0
  20. bapsf_motion/examples/mg_2d_strb_test_setup.toml +37 -0
  21. bapsf_motion/examples/motion_group.toml +18 -0
  22. bapsf_motion/gui/__init__.py +4 -0
  23. bapsf_motion/gui/configure/__init__.py +47 -0
  24. bapsf_motion/gui/configure/bases.py +132 -0
  25. bapsf_motion/gui/configure/configure_.py +605 -0
  26. bapsf_motion/gui/configure/drive_overlay.py +716 -0
  27. bapsf_motion/gui/configure/helpers.py +48 -0
  28. bapsf_motion/gui/configure/motion_builder_overlay.py +1161 -0
  29. bapsf_motion/gui/configure/motion_group_widget.py +1890 -0
  30. bapsf_motion/gui/configure/transform_overlay.py +313 -0
  31. bapsf_motion/gui/motor.py +313 -0
  32. bapsf_motion/gui/widgets/__init__.py +32 -0
  33. bapsf_motion/gui/widgets/buttons.py +290 -0
  34. bapsf_motion/gui/widgets/logging.py +305 -0
  35. bapsf_motion/gui/widgets/misc.py +101 -0
  36. bapsf_motion/motion_builder/__init__.py +12 -0
  37. bapsf_motion/motion_builder/core.py +473 -0
  38. bapsf_motion/motion_builder/exclusions/__init__.py +41 -0
  39. bapsf_motion/motion_builder/exclusions/base.py +246 -0
  40. bapsf_motion/motion_builder/exclusions/circular.py +169 -0
  41. bapsf_motion/motion_builder/exclusions/divider.py +228 -0
  42. bapsf_motion/motion_builder/exclusions/helpers.py +176 -0
  43. bapsf_motion/motion_builder/exclusions/lapd.py +413 -0
  44. bapsf_motion/motion_builder/exclusions/shadow.py +691 -0
  45. bapsf_motion/motion_builder/item.py +175 -0
  46. bapsf_motion/motion_builder/layers/__init__.py +27 -0
  47. bapsf_motion/motion_builder/layers/base.py +191 -0
  48. bapsf_motion/motion_builder/layers/helpers.py +178 -0
  49. bapsf_motion/motion_builder/layers/regular_grid.py +210 -0
  50. bapsf_motion/transform/__init__.py +19 -0
  51. bapsf_motion/transform/base.py +506 -0
  52. bapsf_motion/transform/helpers.py +153 -0
  53. bapsf_motion/transform/identity.py +80 -0
  54. bapsf_motion/transform/lapd.py +481 -0
  55. bapsf_motion/transform/lapd_droop.py +540 -0
  56. bapsf_motion/utils/__init__.py +162 -0
  57. bapsf_motion/utils/exceptions.py +13 -0
  58. bapsf_motion/utils/toml.py +55 -0
  59. bapsf_motion/utils/units_.py +30 -0
  60. bapsf_motion-0.2.0.dist-info/METADATA +141 -0
  61. bapsf_motion-0.2.0.dist-info/RECORD +64 -0
  62. bapsf_motion-0.2.0.dist-info/WHEEL +5 -0
  63. bapsf_motion-0.2.0.dist-info/dependency_links.txt +1 -0
  64. bapsf_motion-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ """`bapsf_motion`"""
2
+ __all__ = ["__version__"]
3
+
4
+ # Enforce Python version check during package import.
5
+ # This is the same check as the one at the top of setup.py
6
+ import sys
7
+
8
+ if sys.version_info < (3, 7): # coverage: ignore
9
+ raise ImportError("bapsf_motion does not support Python < 3.7")
10
+
11
+ if sys.version_info >= (3, 8):
12
+ from importlib.metadata import version, PackageNotFoundError
13
+ else:
14
+ from importlib_metadata import version, PackageNotFoundError
15
+
16
+ from bapsf_motion import actors, motion_builder, transform, utils
17
+
18
+ # define version
19
+ try:
20
+ # this places a runtime dependency on setuptools
21
+ #
22
+ # note: if there's any distribution metadata in your source files, then this
23
+ # will find a version based on those files. Keep distribution metadata
24
+ # out of your repository unless you've intentionally installed the package
25
+ # as editable (e.g. `pip install -e {bapsf_motion_directory_root}`),
26
+ # but then __version__ will not be updated with each commit, it is
27
+ # frozen to the version at time of install.
28
+ #
29
+ #: bapsf_motion version string
30
+ __version__ = version("bapsf_motion")
31
+ except PackageNotFoundError:
32
+ # package is not installed
33
+ fallback_version = "unknown"
34
+ try:
35
+ # code most likely being used from source
36
+ # if setuptools_scm is installed then generate a version
37
+ from setuptools_scm import get_version
38
+
39
+ __version__ = get_version(
40
+ root="../", relative_to=__file__, fallback_version=fallback_version
41
+ )
42
+ del get_version
43
+ warn_add = "setuptools_scm failed to detect the version"
44
+ except ModuleNotFoundError:
45
+ # setuptools_scm is not installed
46
+ __version__ = fallback_version
47
+ warn_add = "setuptools_scm is not installed"
48
+
49
+ if __version__ == fallback_version:
50
+ from warnings import warn
51
+
52
+ warn(
53
+ f"bapsf_motion.__version__ not generated (set to 'unknown'), "
54
+ f"bapsf_motion is not an installed package and {warn_add}.",
55
+ RuntimeWarning,
56
+ )
57
+
58
+ del warn
59
+ del fallback_version, warn_add
@@ -0,0 +1,18 @@
1
+ __all__ = []
2
+ __actors__ = [
3
+ "Axis",
4
+ "BaseActor",
5
+ "Drive",
6
+ "EventActor",
7
+ "RunManager",
8
+ "MotionGroup",
9
+ "Motor",
10
+ ]
11
+ __all__ += __actors__
12
+
13
+ from bapsf_motion.actors.axis_ import Axis
14
+ from bapsf_motion.actors.base import BaseActor, EventActor
15
+ from bapsf_motion.actors.drive_ import Drive
16
+ from bapsf_motion.actors.manager_ import RunManager, RunManagerConfig
17
+ from bapsf_motion.actors.motion_group_ import MotionGroup, MotionGroupConfig
18
+ from bapsf_motion.actors.motor_ import Motor
@@ -0,0 +1,347 @@
1
+ """
2
+ Module for functionality focused around the
3
+ `~bapsf_motion.actors.axis_.Axis` actor class.
4
+ """
5
+ __all__ = ["Axis"]
6
+ __actors__ = ["Axis"]
7
+
8
+ import asyncio
9
+ import logging
10
+
11
+ from typing import Any, Dict, Optional, Union
12
+
13
+ from bapsf_motion.actors.base import EventActor
14
+ from bapsf_motion.actors.motor_ import Motor
15
+ from bapsf_motion.utils import units as u
16
+
17
+
18
+ class Axis(EventActor):
19
+ """
20
+ The `Axis` actor is the next level actor above the |Motor| actor.
21
+ This actor is ignorant of how it is situated in a probe drive, but
22
+ is fully aware of the entire physical axis that defines it and the
23
+ motor that moves the axis. This actor operates in physical units
24
+ and will handle all the necessary unit converstion to communicate
25
+ with the |Motor| actor.
26
+
27
+ Parameters
28
+ ----------
29
+ ip: str
30
+ IPv4 address for the motor driving the axis
31
+
32
+ units: str
33
+ Physical units the axis operates in (e.g. ``'cm'``)
34
+
35
+ units_per_rev: float
36
+ The number of ``units`` traversed per motor revolution.
37
+
38
+ name: str
39
+ Name the axis. (DEFAULT: ``'Axis'``)
40
+
41
+ logger: `~logging.Logger`, optional
42
+ An instance of `~logging.Logger` that the Actor will record
43
+ events and status updates to. If `None`, then a logger will
44
+ automatically be generated. (DEFUALT: `None`)
45
+
46
+ loop: `asyncio.AbstractEventLoop`, optional
47
+ Instance of an `asyncio` `event loop`_. Communication with the
48
+ motor will happen primaritly through the evenet loop. If
49
+ `None`, then an `event loop`_ will be auto-generated.
50
+ (DEFAULT: `None`)
51
+
52
+ auto_run: bool, optional
53
+ If `True`, then the `event loop`_ will be placed in a separate
54
+ thread and started. This is all done via the :meth:`run`
55
+ method. (DEFAULT: `False`)
56
+
57
+ Examples
58
+ --------
59
+
60
+ >>> from bapsf_motion.actors import Axis
61
+ >>> import logging
62
+ >>> import sys
63
+ >>> logging.basicConfig(stream=sys.stdout, level=logging.NOTSET)
64
+ >>> ax = Axis(
65
+ ... ip="192.168.6.104",
66
+ ... units="cm",
67
+ ... units_per_rev=0.1*2.54, # acme rod with .1 in pitch
68
+ ... name="WALL-E",
69
+ ... auto_run=True,
70
+ ... )
71
+
72
+ """
73
+ # TODO: better handle naming of the Axis and child Motor
74
+
75
+ def __init__(
76
+ self,
77
+ *,
78
+ ip: str,
79
+ units: str,
80
+ units_per_rev: float,
81
+ name: str = "Axis",
82
+ logger: logging.Logger = None,
83
+ loop: asyncio.AbstractEventLoop = None,
84
+ auto_run: bool = False,
85
+ parent: Optional["EventActor"] = None,
86
+ ):
87
+ # TODO: update units so inches can be used
88
+ self._motor = None
89
+ self._units = u.Unit(units)
90
+ self._units_per_rev = units_per_rev * self._units / u.rev
91
+
92
+ super().__init__(
93
+ name=name,
94
+ logger=logger,
95
+ loop=loop,
96
+ auto_run=False,
97
+ parent=parent,
98
+ )
99
+
100
+ self._motor = None
101
+ self._spawn_motor(ip=ip)
102
+
103
+ if isinstance(self._motor, Motor) and self._motor.terminated:
104
+ # terminate self if Motor is terminated
105
+ self.terminate(delay_loop_stop=True)
106
+ else:
107
+ self.run(auto_run=auto_run)
108
+
109
+ def _configure_before_run(self):
110
+ return
111
+
112
+ def _initialize_tasks(self):
113
+ return
114
+
115
+ def run(self, auto_run=True):
116
+ if self.terminated:
117
+ # we are restarting
118
+ self._terminated = False
119
+ self._spawn_motor(ip=self.config["ip"])
120
+
121
+ super().run(auto_run=auto_run)
122
+
123
+ if self.motor is None:
124
+ return
125
+
126
+ self.motor.run(auto_run=auto_run)
127
+
128
+ def terminate(self, delay_loop_stop=False):
129
+ self.motor.terminate(delay_loop_stop=True)
130
+ super().terminate(delay_loop_stop=delay_loop_stop)
131
+
132
+ def _spawn_motor(self, ip):
133
+ if isinstance(self.motor, Motor) and not self.terminated:
134
+ self.motor.terminate(delay_loop_stop=True)
135
+
136
+ self._motor = Motor(
137
+ ip=ip,
138
+ name="motor",
139
+ logger=self.logger,
140
+ loop=self.loop,
141
+ auto_run=False,
142
+ parent=self,
143
+ )
144
+
145
+ @property
146
+ def config(self) -> Dict[str, Any]:
147
+ """Dictionary of the axis configuration parameters."""
148
+ return {
149
+ "name": self.name,
150
+ "ip": self.motor.ip,
151
+ "units": str(self.units),
152
+ "units_per_rev": self.units_per_rev.value.item()
153
+ }
154
+ config.__doc__ = EventActor.config.__doc__
155
+
156
+ @property
157
+ def motor(self) -> Motor:
158
+ """Instance of the |Motor| object that belongs to |Axis|."""
159
+ return self._motor
160
+
161
+ @property
162
+ def ip(self):
163
+ """IPv4 address for the Axis' motor"""
164
+ return self.motor.ip
165
+
166
+ @property
167
+ def is_moving(self) -> bool:
168
+ """
169
+ `True` or `False` indicating if the axis is currently moving.
170
+ """
171
+ return self.motor.is_moving
172
+
173
+ @property
174
+ def position(self):
175
+ """
176
+ Current axis position in units defined by the :attr:`units`
177
+ attribute.
178
+ """
179
+ pos = self.motor.position
180
+ return pos.to(self.units, equivalencies=self.equivalencies)
181
+
182
+ @property
183
+ def steps_per_rev(self):
184
+ """Number of motor steps for a full revolution."""
185
+ return self.motor.steps_per_rev
186
+
187
+ @property
188
+ def units(self) -> u.Unit:
189
+ """
190
+ The unit of measure for the `Axis` physical parameters like
191
+ position, speed, etc.
192
+ """
193
+ return self._units
194
+
195
+ @units.setter
196
+ def units(self, new_units: u.Unit):
197
+ """Set the units of measure."""
198
+ if self.units.physical_type != new_units.physical_type:
199
+ raise ValueError
200
+
201
+ self._units_per_rev = self.units_per_rev.to(new_units / u.rev)
202
+ self._units = new_units
203
+
204
+ @property
205
+ def units_per_rev(self) -> u.Quantity:
206
+ """
207
+ The number of units (:attr:`units`) translated per full
208
+ revolution of the motor (:attr:`motor`).
209
+ """
210
+ return self._units_per_rev
211
+
212
+ @units_per_rev.setter
213
+ def units_per_rev(self, value: Union[float, u.Quantity]):
214
+ """
215
+ Update the number of units translated per full revolution of the
216
+ motor.
217
+ """
218
+ if isinstance(value, float) and value > 0.0:
219
+ self._units_per_rev = value * self.units / u.rev
220
+ elif (
221
+ isinstance(value, u.Quantity)
222
+ and value.unit == self.units / u.rev
223
+ and value > 0.0
224
+ ):
225
+ self._units_per_rev = value
226
+
227
+ @property
228
+ def equivalencies(self):
229
+ """
230
+ List of unit equivalencies to convert back-and-forth between
231
+ the axis physical units and the motor units.
232
+ """
233
+ steps_per_rev = self.steps_per_rev.value
234
+ units_per_rev = self.units_per_rev.value
235
+
236
+ equivs = [
237
+ (
238
+ u.rev,
239
+ u.steps,
240
+ lambda x: int(x * steps_per_rev),
241
+ lambda x: x / steps_per_rev,
242
+ ),
243
+ (
244
+ u.rev,
245
+ self.units,
246
+ lambda x: x * units_per_rev,
247
+ lambda x: x / units_per_rev,
248
+ ),
249
+ (
250
+ u.steps,
251
+ self.units,
252
+ lambda x: x * units_per_rev / steps_per_rev,
253
+ lambda x: int(x * steps_per_rev / units_per_rev),
254
+ ),
255
+ ]
256
+ for equiv in equivs.copy():
257
+ equivs.extend(
258
+ [
259
+ (equiv[0] / u.s, equiv[1] / u.s, equiv[2], equiv[3]),
260
+ (equiv[0] / u.s / u.s, equiv[1] / u.s / u.s, equiv[2], equiv[3]),
261
+ ]
262
+ )
263
+
264
+ return equivs
265
+
266
+ @property
267
+ def conversion_pairs(self):
268
+ """
269
+ List of conversion pairs between motor units and physical
270
+ units. For example, ``[(u.steps, self.units), ...]``.
271
+ """
272
+ return [
273
+ (u.steps, self.units),
274
+ (u.steps / u.s, self.units / u.s),
275
+ (u.steps / u.s / u.s, self.units / u.s / u.s),
276
+ (u.rev / u.s, self.units / u.s),
277
+ (u.rev / u.s / u.s, self.units / u.s / u.s),
278
+ ]
279
+
280
+ def send_command(self, command, *args):
281
+ """
282
+ Send ``command`` to the motor, and receive its response. If the
283
+ `event loop`_ is running, then the command will be sent as
284
+ a threadsafe coroutine_ in the loop. Otherwise, the command
285
+ will be sent directly to the motor.
286
+
287
+ Parameters
288
+ ----------
289
+ command: str
290
+ The desired command to be sent to the motor.
291
+ *args:
292
+ Any arguments to the ``command`` that will be sent with the
293
+ motor command.
294
+ """
295
+ cmd_entry = self.motor._commands[command]
296
+ motor_unit = cmd_entry["units"] # type: u.Unit
297
+
298
+ # TODO: put this into a separate convert() method that can handle both
299
+ # the send and recv unit conversion
300
+ if motor_unit is not None and len(args):
301
+ axis_unit = None
302
+ for motor_u, axis_u in self.conversion_pairs:
303
+ if motor_unit == motor_u:
304
+ axis_unit = axis_u
305
+ break
306
+
307
+ if axis_unit is not None:
308
+ args = list(args)
309
+ args[0] = args[0] * axis_unit.to(
310
+ motor_unit, equivalencies=self.equivalencies
311
+ )
312
+
313
+ # TODO: There should be a cleaner way of enforcing this
314
+ # int conversion...maybe add it to the Motor class,
315
+ # but I [Erik] currently feel the conversion should
316
+ # happen outside the Motor class
317
+ if motor_unit is u.steps:
318
+ args[0] = int(args[0])
319
+
320
+ rtn = self.motor.send_command(command, *args)
321
+
322
+ # TODO: see detailing todo above
323
+ if hasattr(rtn, "unit"):
324
+ axis_unit = None
325
+ for motor_u, axis_u in self.conversion_pairs:
326
+ if rtn.unit == motor_u:
327
+ axis_unit = axis_u
328
+ break
329
+
330
+ if axis_unit is not None:
331
+ rtn = rtn.to(axis_unit, equivalencies=self.equivalencies)
332
+
333
+ return rtn
334
+
335
+ def move_to(self, *args):
336
+ """
337
+ Quick access command for ``send_command("move_to", *args)``.
338
+ """
339
+ return self.send_command("move_to", *args)
340
+
341
+ def stop(self):
342
+ """
343
+ Quick access command for ``send_command("stop")``.
344
+ """
345
+ # not sending STOP command through send_command() since using
346
+ # motor.stop() should result in faster execution
347
+ return self.motor.stop()