Source code for pzp_hardware.thorlabs.apt_piezo

# This file is a part of pzp-hardware, a library of laboratory hardware support Pieces
# for the puzzlepiece GUI & automation framework. Check out https://pzp-hardware.readthedocs.io
# Licensed under the Apache License 2.0 - https://github.com/jdranczewski/pzp-hardware/blob/main/LICENSE

r"""
:module_title:`ThorLabs APT piezo inertia actuator`

Pieces for interacting with
`ThorLabs piezo inertia actuator controllers <https://www.thorlabs.com/newgrouppage9.cfm?objectgroup_id=9790>`__
through the `APT interface <https://www.thorlabs.com/newgrouppage9.cfm?objectgroup_id=9019>`__
using the `puzzlepiece <https://puzzlepiece.readthedocs.io>`__ framework.

Example usage (see :ref:`getting-started` for more details on using Pieces in general)::

    import puzzlepiece as pzp
    from pzp_hardware.thorlabs import apt_piezo

    app = pzp.QApp()
    puzzle = pzp.Puzzle(debug=False)
    puzzle.add_piece("stage", apt_piezo.Piece, row=0, column=0)
    puzzle.show()
    app.exec()

.. automodule:: pzp_hardware.thorlabs._apt_base
   :no-index:

Available Pieces
----------------
"""

import puzzlepiece as pzp
from puzzlepiece.extras import hardware_tools as pht
from qtpy import QtCore

from pzp_hardware.thorlabs import _apt_base


#MARK: Base Piece
[docs] class Base(_apt_base.APTBase): """ .. image:: ../images/pzp_hardware.thorlabs.apt_piezo.Base.png Base Piece that doesn't define any channels, and should be inherited to provide a Piece matching your piezo configuration. Two examples are included: :class:`~pzp_hardware.thorlabs.apt_piezo.Piece` and :class:`~pzp_hardware.thorlabs.apt_piezo.DoublePiece`. """ def define_params(self): super().define_params() @pzp.param.connect(self) def connect(): if self.puzzle.debug: return 1 self._ensure_apt() _lib = self.puzzle.globals['apt'].core._lib c = pht.c _lib.PZMOT_GetPositionSteps.argtypes = [c.c_long, c.POINTER(c.c_long)] _lib.PZMOT_MoveAbsoluteStepsEx.argtypes = [c.c_long, c.c_long, c.c_bool] _lib.PZMOT_SetChannel.argtypes = [c.c_long, c.c_long] # Left here for compatibility with apt_stage stuff self.motor = True err_code = self.puzzle.globals['apt'].core._lib.InitHWDevice(int(self.params['serial'].get_value())) if (err_code != 0): message = self.puzzle.globals['apt'].core._get_error_text(err_code) raise Exception( f"Failed to connect to piezo: {message}" ) return 1
[docs] def make_channel(self, name, i): """ Create a named param for a channel number with a setter and getter. Should be called within ``define_params`` to create params for your piezo controllers x, y, z, etc axes:: def define_params(self): super().define_params() for i, name in zip((0, 1), "xy"): self.make_channel(name, i) :param name: name of the channel/param to create :param i: index of the channel on the piezo controller :rtype: puzzlepiece.param.ParamInt """ def set_channel(): err_code = self.puzzle.globals['apt'].core._lib.PZMOT_SetChannel( int(self.params['serial'].get_value()), i, ) if (err_code != 0): message = self.puzzle.globals['apt'].core._get_error_text(err_code) raise Exception( f"Failed to select channel: {message}" ) @pzp.param.spinbox(self, name, 0) @self._ensure def set_value(value): if self.puzzle.debug: return value if self[name].value == value: # APT crashes if you try to set a value that is already set... return set_channel() err_code = self.puzzle.globals['apt'].core._lib.PZMOT_MoveAbsoluteStepsEx( int(self.params['serial'].get_value()), value, True ) if (err_code != 0): message = self.puzzle.globals['apt'].core._get_error_text(err_code) raise Exception( f"Failed to move the piezo: {message}" ) @set_value.set_getter(self) @self._ensure def get_value(): if self.puzzle.debug: return self[name].value or 0 set_channel() pos = self.puzzle.globals['apt'].core.ctypes.c_long() err_code = self.puzzle.globals['apt'].core._lib.PZMOT_GetPositionSteps( int(self.params['serial'].get_value()), self.puzzle.globals['apt'].core.ctypes.byref(pos), ) if (err_code != 0): message = self.puzzle.globals['apt'].core._get_error_text(err_code) raise Exception( f"Failed to get the piezo value: {message}" ) return pos.value return set_value
#MARK: Main Pieces
[docs] class Piece(Base): """ A Piece that defines two piezo channels, ``x`` and ``y``, for example to control a mirror. .. image:: ../images/pzp_hardware.thorlabs.apt_piezo.Piece.png """ def define_params(self): super().define_params() for i, name in zip((0, 1), "xy"): self.make_channel(name, i)
[docs] class DoublePiece(Base): """ A Piece that defines four piezo channels, an ``x`` and ``y`` times two, for example to control two mirrors. .. image:: ../images/pzp_hardware.thorlabs.apt_piezo.DoublePiece.png """ def define_params(self): super().define_params() for i, name in zip((0, 1, 2, 3), ("x1", "y1", "x2", "y2")): self.make_channel(name, i)
if __name__ == "__main__": app = pzp.QApp() puzzle = pzp.Puzzle(name="APT piezo", debug=pht.debug_prompt()) puzzle.add_piece("piezo", DoublePiece, 0, 0) puzzle.show() app.exec()