Source code for qtpyvcp.plugins.positions

"""Smart Axis and Join positions plugin.

Calculates Absolute, Relative, and Distance-To-Go values for each axis.
Configurable to report actual or commanded positions and values in either
machine or program units.

Usage:
    Example syntax to use in Widget Rules Editor::

        position:abs?string&axis=x        # returns X axis absolute position
        position:rel?string&axis=x        # returns X axis relative position
        position:dtg?string&axis=x        # returns X axis DTG value


YAML configuration:

This goes in your config.yml file in the `data_plugins` section.

.. code-block:: yaml

    data_plugins:
      position:
        kwargs:
          # whether to report actual or commanded pos
          report_actual_pos: False
          # whether to report program or machine units
          use_program_units: True
          # format used for metric units
          metric_format: "%9.3f"
          # format used for imperial units
          imperial_format: "%8.4f"

ToDO:
    Add joint positions.
"""

import math

from qtpyvcp.utilities.info import Info
from qtpyvcp.utilities.logger import getLogger
from qtpyvcp.plugins import DataPlugin, DataChannel, getPlugin

STATUS = getPlugin('status')
STAT = STATUS.stat
INFO = Info()

# Set up logging
LOG = getLogger(__name__)

MACHINE_COORDS = INFO.getCoordinates()
MACHINE_UNITS = 2 if INFO.getIsMachineMetric() else 1

# Set the conversions used for changing the DRO units
# Only convert linear axes (XYZUVW), use factor of unity for ABC
if MACHINE_UNITS == 2:
    # List of factors for converting from mm to inches
    CONVERSION_FACTORS = [1.0 / 25.4] * 3 + [1] * 3 + [1.0 / 25.4] * 3
else:
    # List of factors for converting from inches to mm
    CONVERSION_FACTORS = [25.4] * 3 + [1] * 3 + [25.4] * 3


[docs]class Position(DataPlugin): """Positions Plugin""" def __init__(self, report_actual_pos=False, use_program_units=True, metric_format='%9.3f', imperial_format='%8.4f'): super(Position, self).__init__() self._report_actual_pos = False self._use_program_units = use_program_units self._metric_format = metric_format self._imperial_format = imperial_format self._current_format = self._imperial_format self._update() # all these should cause the positions to update STATUS.position.signal.connect(self._update) STATUS.g5x_offset.signal.connect(self._update) STATUS.g92_offset.signal.connect(self._update) STATUS.tool_offset.signal.connect(self._update) STATUS.program_units.signal.connect(self.updateUnits) self.report_actual_pos = report_actual_pos
[docs] def getChannel(self, url): """Get data channel from URL. Args: url (str) : The URL of the channel to get. Returns: tuple : (chan_obj, chan_exp) """ chan, sep, query = url.partition('?') raw_args = query.split('&') args = [] kwargs = {} for arg in [a for a in raw_args if a != '']: if '=' in arg: key, val = arg.split('=') kwargs[key] = val else: args.append(arg) try: chan_obj = self.channels[chan] if 'axis' in kwargs: axis = kwargs.pop('axis') try: kwargs['anum'] = int(axis) except ValueError: kwargs['anum'] = 'xyzabcuvw'.index(str(axis).lower()) if len(args) > 0 and args[0] in ('string', 'text', 'str'): chan_exp = lambda: chan_obj.getString(*args[1:], **kwargs) else: chan_exp = lambda: chan_obj.getValue(*args, **kwargs) except (KeyError, SyntaxError): LOG.exception('Error getting channel') return None, None return chan_obj, chan_exp
def updateUnits(self, canon_units): print(('updating units', canon_units)) if canon_units == 2: self._current_format = self._metric_format else: self._current_format = self._imperial_format self._update() @DataChannel def rel(self, chan, anum=-1): """The current relative axis positions including all offsets To get a single axis pass string and the axis letter:: position:rel?string&axis=x To get a tuple of all the axes pass only string:: position:rel? :returns: current relative axis positions including all offsets :rtype: tuple, str """ if anum == -1: return chan.value return chan.value[anum] @rel.tostring def rel(self, chan, anum): return self._current_format % chan.value[anum] @DataChannel def abs(self, chan, anum=-1): """The current absolute axis positions To get a single axis pass string and the axis letter:: position:abs?string&axis=x To get a tuple of all the axes pass only string:: position:abs? :returns: current absolute axis positions :rtype: tuple, str """ if anum == -1: return chan.value return chan.value[anum] @abs.tostring def abs(self, chan, anum): return self._current_format % chan.value[anum] @DataChannel def dtg(self, chan, anum=-1): """The remaining distance-to-go for the current move To get a single axis pass string and the axis letter:: position:dtg?string&axis=x To get a tuple of all the axes pass only string:: position:dtg? :returns: remaining distance-to-go for the current move :rtype: tuple, str """ if anum == -1: return chan.value return chan.value[anum] @dtg.tostring def dtg(self, chan, anum): return self._current_format % chan.value[anum] # aliases Relative = rel Absolute = abs DistanceToGo = dtg @property def report_actual_pos(self): """Whether to report the actual position. Default True. See the YAML configuration. """ return self._report_actual_pos @report_actual_pos.setter def report_actual_pos(self, report_actual_pos): if report_actual_pos == self._report_actual_pos: return self._report_actual_pos = report_actual_pos if self._report_actual_pos: # disconnect commanded pos update signals STATUS.position.signal.disconnect(self._update) # STATUS.joint_position.signal.disconnect(self._update) # connect actual pos update signals STATUS.actual_position.signal.connect(self._update) # STATUS.joint_actual_position.signal.connect(self.joint._update) else: # disconnect actual pos update signals STATUS.actual_position.signal.disconnect(self._update) # STATUS.joint_actual_position.signal.disconnect(self._update) # connect commanded pos update signals STATUS.position.signal.connect(self._update) # STATUS.joint_position.signal.connect(self._update) def _update(self): if self._report_actual_pos: pos = STAT.actual_position else: pos = STAT.position dtg = STAT.dtg g5x_offset = STAT.g5x_offset g92_offset = STAT.g92_offset tool_offset = STAT.tool_offset rel = [0] * 9 for axis in INFO.AXIS_NUMBER_LIST: rel[axis] = pos[axis] - g5x_offset[axis] - tool_offset[axis] if STAT.rotation_xy != 0: t = math.radians(-STAT.rotation_xy) xr = rel[0] * math.cos(t) - rel[1] * math.sin(t) yr = rel[0] * math.sin(t) + rel[1] * math.cos(t) rel[0] = xr rel[1] = yr for axis in INFO.AXIS_NUMBER_LIST: rel[axis] -= g92_offset[axis] if STAT.program_units != MACHINE_UNITS and self._use_program_units: pos = [pos[anum] * CONVERSION_FACTORS[anum] for anum in range(9)] rel = [rel[anum] * CONVERSION_FACTORS[anum] for anum in range(9)] dtg = [dtg[anum] * CONVERSION_FACTORS[anum] for anum in range(9)] self.rel.setValue(tuple(rel)) self.abs.setValue(tuple(pos)) self.dtg.setValue(tuple(dtg))