From a2cf1e3e71593d57398d2ce9a36545c52498a713 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Thu, 2 Jan 2025 15:00:19 -0500 Subject: [PATCH 1/9] save --- .../drivers/flex_stacker/__init__.py | 4 +- .../drivers/flex_stacker/abstract.py | 6 +- .../opentrons/drivers/flex_stacker/driver.py | 13 +- .../drivers/flex_stacker/simulator.py | 4 +- .../opentrons/drivers/flex_stacker/types.py | 10 +- .../hardware_control/modules/__init__.py | 2 + .../hardware_control/modules/flex_stacker.py | 191 ++++++++++++++++++ .../hardware_control/modules/types.py | 12 ++ .../opentrons_shared_data/module/types.py | 4 + 9 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 api/src/opentrons/hardware_control/modules/flex_stacker.py diff --git a/api/src/opentrons/drivers/flex_stacker/__init__.py b/api/src/opentrons/drivers/flex_stacker/__init__.py index cd4866c179a..268694fdaa1 100644 --- a/api/src/opentrons/drivers/flex_stacker/__init__.py +++ b/api/src/opentrons/drivers/flex_stacker/__init__.py @@ -1,9 +1,9 @@ -from .abstract import AbstractStackerDriver +from .abstract import AbstractFlexStackerDriver from .driver import FlexStackerDriver from .simulator import SimulatingDriver __all__ = [ - "AbstractStackerDriver", + "AbstractFlexStackerDriver", "FlexStackerDriver", "SimulatingDriver", ] diff --git a/api/src/opentrons/drivers/flex_stacker/abstract.py b/api/src/opentrons/drivers/flex_stacker/abstract.py index 5ba3cdcb026..943ebcec485 100644 --- a/api/src/opentrons/drivers/flex_stacker/abstract.py +++ b/api/src/opentrons/drivers/flex_stacker/abstract.py @@ -10,7 +10,7 @@ ) -class AbstractStackerDriver(Protocol): +class AbstractFlexStackerDriver(Protocol): """Protocol for the Stacker driver.""" async def connect(self) -> None: @@ -87,3 +87,7 @@ async def set_led( ) -> bool: """Set LED color of status bar.""" ... + + async def enter_programming_mode(self) -> None: + """Reboot into programming mode""" + ... diff --git a/api/src/opentrons/drivers/flex_stacker/driver.py b/api/src/opentrons/drivers/flex_stacker/driver.py index 83671023772..7c4dfb3761b 100644 --- a/api/src/opentrons/drivers/flex_stacker/driver.py +++ b/api/src/opentrons/drivers/flex_stacker/driver.py @@ -5,7 +5,7 @@ from opentrons.drivers.command_builder import CommandBuilder from opentrons.drivers.asyncio.communication import AsyncResponseSerialConnection -from .abstract import AbstractStackerDriver +from .abstract import AbstractFlexStackerDriver from .types import ( GCODE, StackerAxis, @@ -28,7 +28,7 @@ GCODE_ROUNDING_PRECISION = 2 -class FlexStackerDriver(AbstractStackerDriver): +class FlexStackerDriver(AbstractFlexStackerDriver): """FLEX Stacker driver.""" @classmethod @@ -254,7 +254,8 @@ async def set_led( raise ValueError(f"Incorrect Response for set led: {resp}") return True - async def update_firmware(self, firmware_file_path: str) -> None: - """Updates the firmware on the device.""" - # TODO: Implement firmware update - pass + async def enter_programming_mode(self) -> None: + """Reboot into programming mode""" + command = GCODE.ENTER_BOOTLOADER.build_command() + await self._connection.send_dfu_command(command) + await self._connection.close() diff --git a/api/src/opentrons/drivers/flex_stacker/simulator.py b/api/src/opentrons/drivers/flex_stacker/simulator.py index 1e0b59b19de..4eef1d797dd 100644 --- a/api/src/opentrons/drivers/flex_stacker/simulator.py +++ b/api/src/opentrons/drivers/flex_stacker/simulator.py @@ -2,7 +2,7 @@ from opentrons.util.async_helpers import ensure_yield -from .abstract import AbstractStackerDriver +from .abstract import AbstractFlexStackerDriver from .types import ( StackerAxis, PlatformStatus, @@ -14,7 +14,7 @@ ) -class SimulatingDriver(AbstractStackerDriver): +class SimulatingDriver(AbstractFlexStackerDriver): """FLEX Stacker driver simulator.""" def __init__(self, serial_number: Optional[str] = None) -> None: diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index 4035aaaa755..63c977ae656 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -1,6 +1,6 @@ from enum import Enum from dataclasses import dataclass, fields -from typing import List +from typing import List, Dict from opentrons.drivers.command_builder import CommandBuilder @@ -45,6 +45,14 @@ class StackerInfo: hw: HardwareRevision sn: str + def to_dict(self) -> Dict[str, str]: + """Build command.""" + return { + "serial": self.sn, + "version": self.fw, + "model": self.hw.value, + } + class StackerAxis(Enum): """Stacker Axis.""" diff --git a/api/src/opentrons/hardware_control/modules/__init__.py b/api/src/opentrons/hardware_control/modules/__init__.py index 67a6c442f39..305ccfdc855 100644 --- a/api/src/opentrons/hardware_control/modules/__init__.py +++ b/api/src/opentrons/hardware_control/modules/__init__.py @@ -4,6 +4,7 @@ from .thermocycler import Thermocycler from .heater_shaker import HeaterShaker from .absorbance_reader import AbsorbanceReader +from .flex_stacker import FlexStacker from .update import update_firmware from .utils import MODULE_TYPE_BY_NAME, build from .types import ( @@ -55,4 +56,5 @@ "AbsorbanceReaderStatus", "AbsorbanceReaderDisconnectedError", "ModuleDisconnectedCallback", + "FlexStacker", ] diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py new file mode 100644 index 00000000000..1f077e6fb6a --- /dev/null +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import asyncio +import logging +from typing import Optional, Mapping + +from opentrons.drivers.rpi_drivers.types import USBPort +from opentrons.drivers.flex_stacker.driver import FlexStackerDriver +from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver +from opentrons.drivers.flex_stacker.simulator import SimulatingDriver +from opentrons.hardware_control.execution_manager import ExecutionManager +from opentrons.hardware_control.poller import Reader, Poller +from opentrons.hardware_control.modules import mod_abc, update +from opentrons.hardware_control.modules.types import ( + ModuleDisconnectedCallback, + ModuleType, + UploadFunction, + LiveData, +) + +log = logging.getLogger(__name__) + +POLL_PERIOD = 1.0 +SIMULATING_POLL_PERIOD = POLL_PERIOD / 20.0 + +DFU_PID = "df11" + + +class FlexStacker(mod_abc.AbstractModule): + """Hardware control interface for an attached Flex-Stacker module.""" + + MODULE_TYPE = ModuleType.FLEX_STACKER + + @classmethod + async def build( + cls, + port: str, + usb_port: USBPort, + execution_manager: ExecutionManager, + hw_control_loop: asyncio.AbstractEventLoop, + poll_interval_seconds: Optional[float] = None, + simulating: bool = False, + sim_model: Optional[str] = None, + sim_serial_number: Optional[str] = None, + disconnected_callback: ModuleDisconnectedCallback = None, + ) -> "FlexStacker": + """ + Build a FlexStacker + + Args: + port: The port to connect to + usb_port: USB Port + execution_manager: Execution manager. + hw_control_loop: The event loop running in the hardware control thread. + poll_interval_seconds: Poll interval override. + simulating: whether to build a simulating driver + loop: Loop + sim_model: The model name used by simulator + disconnected_callback: Callback to inform the module controller that the device was disconnected + + Returns: + FlexStacker instance + """ + driver: AbstractFlexStackerDriver + if not simulating: + driver = await FlexStackerDriver.create(port=port, loop=hw_control_loop) + poll_interval_seconds = poll_interval_seconds or POLL_PERIOD + else: + driver = SimulatingDriver(serial_number=sim_serial_number) + poll_interval_seconds = poll_interval_seconds or SIMULATING_POLL_PERIOD + + reader = FlexStackerReader(driver=driver) + poller = Poller(reader=reader, interval=poll_interval_seconds) + module = cls( + port=port, + usb_port=usb_port, + driver=driver, + reader=reader, + poller=poller, + device_info=(await driver.get_device_info()).to_dict(), + hw_control_loop=hw_control_loop, + execution_manager=execution_manager, + disconnected_callback=disconnected_callback, + ) + + try: + await poller.start() + except Exception: + log.exception(f"First read of Flex-Stacker on port {port} failed") + + return module + + def __init__( + self, + port: str, + usb_port: USBPort, + execution_manager: ExecutionManager, + driver: AbstractFlexStackerDriver, + reader: FlexStackerReader, + poller: Poller, + device_info: Mapping[str, str], + hw_control_loop: asyncio.AbstractEventLoop, + disconnected_callback: ModuleDisconnectedCallback = None, + ): + super().__init__( + port=port, + usb_port=usb_port, + hw_control_loop=hw_control_loop, + execution_manager=execution_manager, + disconnected_callback=disconnected_callback, + ) + self._device_info = device_info + self._driver = driver + self._reader = reader + self._poller = poller + + async def cleanup(self) -> None: + """Stop the poller task""" + await self._poller.stop() + await self._driver.disconnect() + + @classmethod + def name(cls) -> str: + """Used for picking up serial port symlinks""" + return "flexstacker" + + def firmware_prefix(self) -> str: + """The prefix used for looking up firmware""" + return "flex-stacker" + + @staticmethod + def _model_from_revision(revision: Optional[str]) -> str: + """Defines the revision -> model mapping""" + return "flexStackerV1" + + def model(self) -> str: + return self._model_from_revision(self._device_info.get("model")) + + def bootloader(self) -> UploadFunction: + return update.upload_via_dfu + + @property + def device_info(self) -> Mapping[str, str]: + return self._device_info + + @property + def live_data(self) -> LiveData: + return { + "status": self.status, + "data": { + "errorDetails": self._reader.error, + }, + } + + @property + def status(self) -> str: + """Module status or error state details.""" + return "idle" + + @property + def is_simulated(self) -> bool: + return isinstance(self._driver, SimulatingDriver) + + async def prep_for_update(self) -> str: + await self._poller.stop() + await self._driver.enter_programming_mode() + dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=2) + return dfu_info + + +class FlexStackerReader(Reader): + error: Optional[str] + + def __init__(self, driver: AbstractFlexStackerDriver) -> None: + self.error: Optional[str] = None + self._driver = driver + + async def read(self) -> None: + self._set_error(None) + + def on_error(self, exception: Exception) -> None: + self._set_error(exception) + + def _set_error(self, exception: Optional[Exception]) -> None: + if exception is None: + self.error = None + else: + try: + self.error = str(exception.args[0]) + except Exception: + self.error = repr(exception) diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index 9b7c33058d4..b680809b4bc 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -27,6 +27,7 @@ HeaterShakerModuleType, MagneticBlockType, AbsorbanceReaderType, + FlexStackerType, ) @@ -62,6 +63,7 @@ class ModuleType(str, Enum): HEATER_SHAKER: HeaterShakerModuleType = "heaterShakerModuleType" MAGNETIC_BLOCK: MagneticBlockType = "magneticBlockType" ABSORBANCE_READER: AbsorbanceReaderType = "absorbanceReaderType" + FLEX_STACKER: FlexStackerType = "flexStackerType" @classmethod def from_model(cls, model: ModuleModel) -> ModuleType: @@ -77,6 +79,8 @@ def from_model(cls, model: ModuleModel) -> ModuleType: return cls.MAGNETIC_BLOCK if isinstance(model, AbsorbanceReaderModel): return cls.ABSORBANCE_READER + if isinstance(model, FlexStackerModel): + return cls.FLEX_STACKER @classmethod def to_module_fixture_id(cls, module_type: ModuleType) -> str: @@ -91,6 +95,8 @@ def to_module_fixture_id(cls, module_type: ModuleType) -> str: return "magneticBlockV1" if module_type == ModuleType.ABSORBANCE_READER: return "absorbanceReaderV1" + if module_type == ModuleType.FLEX_STACKER: + return "flexStackerV1" else: raise ValueError( f"Module Type {module_type} does not have a related fixture ID." @@ -124,6 +130,10 @@ class AbsorbanceReaderModel(str, Enum): ABSORBANCE_READER_V1: str = "absorbanceReaderV1" +class FlexStackerModel(str, Enum): + FLEX_STACKER_V1: str = "flexStackerV1" + + def module_model_from_string(model_string: str) -> ModuleModel: for model_enum in { MagneticModuleModel, @@ -132,6 +142,7 @@ def module_model_from_string(model_string: str) -> ModuleModel: HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, + FlexStackerModel, }: try: return cast(ModuleModel, model_enum(model_string)) @@ -184,6 +195,7 @@ class ModuleInfo(NamedTuple): HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, + FlexStackerModel, ] diff --git a/shared-data/python/opentrons_shared_data/module/types.py b/shared-data/python/opentrons_shared_data/module/types.py index ab9465b04f6..87b49613167 100644 --- a/shared-data/python/opentrons_shared_data/module/types.py +++ b/shared-data/python/opentrons_shared_data/module/types.py @@ -19,6 +19,7 @@ HeaterShakerModuleType = Literal["heaterShakerModuleType"] MagneticBlockType = Literal["magneticBlockType"] AbsorbanceReaderType = Literal["absorbanceReaderType"] +FlexStackerType = Literal["flexStackerType"] ModuleType = Union[ MagneticModuleType, @@ -27,6 +28,7 @@ HeaterShakerModuleType, MagneticBlockType, AbsorbanceReaderType, + FlexStackerType, ] MagneticModuleModel = Literal["magneticModuleV1", "magneticModuleV2"] @@ -35,6 +37,7 @@ HeaterShakerModuleModel = Literal["heaterShakerModuleV1"] MagneticBlockModel = Literal["magneticBlockV1"] AbsorbanceReaderModel = Literal["absorbanceReaderV1"] +FlexStackerModel = Literal["flexStackerV1"] ModuleModel = Union[ MagneticModuleModel, @@ -43,6 +46,7 @@ HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, + FlexStackerModel, ] ModuleSlotTransform = TypedDict( From d26ee40b81a28c4cce34a7ad797156d90d666c9d Mon Sep 17 00:00:00 2001 From: vegano1 Date: Sat, 4 Jan 2025 09:42:54 -0500 Subject: [PATCH 2/9] feat(api,robot-server,shared-data): add FlexStacker module to the hardware controller and robot server. --- .../drivers/flex_stacker/abstract.py | 4 - .../drivers/flex_stacker/simulator.py | 28 +- .../backends/ot3controller.py | 8 +- .../hardware_control/modules/flex_stacker.py | 5 +- .../hardware_control/modules/types.py | 16 +- .../hardware_control/modules/utils.py | 3 + api/src/opentrons/protocol_engine/types.py | 9 + .../modules/module_data_mapper.py | 7 + .../robot_server/modules/module_models.py | 28 +- .../definitions/3/flexStackerModuleV1.json | 524 ++++++++++++++++++ .../opentrons_shared_data/module/types.py | 8 +- 11 files changed, 613 insertions(+), 27 deletions(-) create mode 100644 shared-data/module/definitions/3/flexStackerModuleV1.json diff --git a/api/src/opentrons/drivers/flex_stacker/abstract.py b/api/src/opentrons/drivers/flex_stacker/abstract.py index 943ebcec485..f92039f9564 100644 --- a/api/src/opentrons/drivers/flex_stacker/abstract.py +++ b/api/src/opentrons/drivers/flex_stacker/abstract.py @@ -25,10 +25,6 @@ async def is_connected(self) -> bool: """Check connection to stacker.""" ... - async def update_firmware(self, firmware_file_path: str) -> None: - """Updates the firmware on the device.""" - ... - async def get_device_info(self) -> StackerInfo: """Get Device Info.""" ... diff --git a/api/src/opentrons/drivers/flex_stacker/simulator.py b/api/src/opentrons/drivers/flex_stacker/simulator.py index 4eef1d797dd..e1db708f279 100644 --- a/api/src/opentrons/drivers/flex_stacker/simulator.py +++ b/api/src/opentrons/drivers/flex_stacker/simulator.py @@ -4,6 +4,7 @@ from .abstract import AbstractFlexStackerDriver from .types import ( + LEDColor, StackerAxis, PlatformStatus, Direction, @@ -61,8 +62,8 @@ async def set_serial_number(self, sn: str) -> bool: return True @ensure_yield - async def stop_motor(self) -> bool: - """Stop motor movement.""" + async def stop_motors(self) -> bool: + """Stop all motor movement.""" return True @ensure_yield @@ -78,12 +79,15 @@ async def get_limit_switches_status(self) -> LimitSwitchStatus: """Get limit switch statuses for all axes.""" return self._limit_switch_status - @ensure_yield - async def get_platform_sensor_status(self) -> PlatformStatus: + async def get_platform_sensor(self, direction: Direction) -> bool: """Get platform sensor status. - :return: True if platform is detected, False otherwise + :return: True if platform is present, False otherwise """ + return True + + async def get_platform_status(self) -> PlatformStatus: + """Get platform status.""" return self._platform_sensor_status @ensure_yield @@ -107,3 +111,17 @@ async def move_to_limit_switch( ) -> bool: """Move until limit switch is triggered.""" return True + + async def home_axis(self, axis: StackerAxis, direction: Direction) -> bool: + """Home axis.""" + return True + + async def set_led( + self, power: float, color: LEDColor | None = None, external: bool | None = None + ) -> bool: + """Set LED color.""" + return True + + async def enter_programming_mode(self) -> None: + """Reboot into programming mode""" + pass diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 84ffbffd8da..985077de269 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -482,7 +482,7 @@ def _build_system_hardware( ) -> SystemDrivers: gpio = OT3GPIO("hardware_control") eeprom_driver = eeprom_driver or EEPROMDriver(gpio) - eeprom_driver.setup() + # eeprom_driver.setup() gpio_dev: Union[OT3GPIO, RemoteOT3GPIO] = gpio usb_messenger: Optional[BinaryMessenger] = None if usb_driver: @@ -529,9 +529,9 @@ def update_feature_flags(self, feature_flags: HardwareFeatureFlags) -> None: async def update_motor_status(self) -> None: """Retreieve motor and encoder status and position from all present nodes""" motor_nodes = self._motor_nodes() - assert len(motor_nodes) - response = await get_motor_position(self._messenger, motor_nodes) - self._handle_motor_status_response(response) + if motor_nodes: + response = await get_motor_position(self._messenger, motor_nodes) + self._handle_motor_status_response(response) async def update_motor_estimation(self, axes: Sequence[Axis]) -> None: """Update motor position estimation for commanded nodes, and update cache of data.""" diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py index 1f077e6fb6a..e55f0472b0a 100644 --- a/api/src/opentrons/hardware_control/modules/flex_stacker.py +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -131,7 +131,7 @@ def firmware_prefix(self) -> str: @staticmethod def _model_from_revision(revision: Optional[str]) -> str: """Defines the revision -> model mapping""" - return "flexStackerV1" + return "flexStackerModuleV1" def model(self) -> str: return self._model_from_revision(self._device_info.get("model")) @@ -167,6 +167,9 @@ async def prep_for_update(self) -> str: dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=2) return dfu_info + async def deactivate(self, must_be_running: bool = True) -> None: + pass + class FlexStackerReader(Reader): error: Optional[str] diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index b680809b4bc..beb67473138 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -27,7 +27,7 @@ HeaterShakerModuleType, MagneticBlockType, AbsorbanceReaderType, - FlexStackerType, + FlexStackerModuleType, ) @@ -63,7 +63,7 @@ class ModuleType(str, Enum): HEATER_SHAKER: HeaterShakerModuleType = "heaterShakerModuleType" MAGNETIC_BLOCK: MagneticBlockType = "magneticBlockType" ABSORBANCE_READER: AbsorbanceReaderType = "absorbanceReaderType" - FLEX_STACKER: FlexStackerType = "flexStackerType" + FLEX_STACKER: FlexStackerModuleType = "flexStackerModuleType" @classmethod def from_model(cls, model: ModuleModel) -> ModuleType: @@ -79,7 +79,7 @@ def from_model(cls, model: ModuleModel) -> ModuleType: return cls.MAGNETIC_BLOCK if isinstance(model, AbsorbanceReaderModel): return cls.ABSORBANCE_READER - if isinstance(model, FlexStackerModel): + if isinstance(model, FlexStackerModuleModel): return cls.FLEX_STACKER @classmethod @@ -96,7 +96,7 @@ def to_module_fixture_id(cls, module_type: ModuleType) -> str: if module_type == ModuleType.ABSORBANCE_READER: return "absorbanceReaderV1" if module_type == ModuleType.FLEX_STACKER: - return "flexStackerV1" + return "flexStackerModuleV1" else: raise ValueError( f"Module Type {module_type} does not have a related fixture ID." @@ -130,8 +130,8 @@ class AbsorbanceReaderModel(str, Enum): ABSORBANCE_READER_V1: str = "absorbanceReaderV1" -class FlexStackerModel(str, Enum): - FLEX_STACKER_V1: str = "flexStackerV1" +class FlexStackerModuleModel(str, Enum): + FLEX_STACKER_V1: str = "flexStackerModuleV1" def module_model_from_string(model_string: str) -> ModuleModel: @@ -142,7 +142,7 @@ def module_model_from_string(model_string: str) -> ModuleModel: HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, - FlexStackerModel, + FlexStackerModuleModel, }: try: return cast(ModuleModel, model_enum(model_string)) @@ -195,7 +195,7 @@ class ModuleInfo(NamedTuple): HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, - FlexStackerModel, + FlexStackerModuleModel, ] diff --git a/api/src/opentrons/hardware_control/modules/utils.py b/api/src/opentrons/hardware_control/modules/utils.py index 296c89a1311..481d0366324 100644 --- a/api/src/opentrons/hardware_control/modules/utils.py +++ b/api/src/opentrons/hardware_control/modules/utils.py @@ -13,6 +13,7 @@ from .thermocycler import Thermocycler from .heater_shaker import HeaterShaker from .absorbance_reader import AbsorbanceReader +from .flex_stacker import FlexStacker log = logging.getLogger(__name__) @@ -26,6 +27,7 @@ Thermocycler.name(): Thermocycler.MODULE_TYPE, HeaterShaker.name(): HeaterShaker.MODULE_TYPE, AbsorbanceReader.name(): AbsorbanceReader.MODULE_TYPE, + FlexStacker.name(): FlexStacker.MODULE_TYPE, } _MODULE_CLS_BY_TYPE: Dict[ModuleType, Type[AbstractModule]] = { @@ -34,6 +36,7 @@ Thermocycler.MODULE_TYPE: Thermocycler, HeaterShaker.MODULE_TYPE: HeaterShaker, AbsorbanceReader.MODULE_TYPE: AbsorbanceReader, + FlexStacker.MODULE_TYPE: FlexStacker, } diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index b1388d58212..3383ca5967a 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -476,6 +476,7 @@ class ModuleModel(str, Enum): HEATER_SHAKER_MODULE_V1 = "heaterShakerModuleV1" MAGNETIC_BLOCK_V1 = "magneticBlockV1" ABSORBANCE_READER_V1 = "absorbanceReaderV1" + FLEX_STACKER_MODULE_V1 = "flexStackerModuleV1" def as_type(self) -> ModuleType: """Get the ModuleType of this model.""" @@ -491,6 +492,8 @@ def as_type(self) -> ModuleType: return ModuleType.MAGNETIC_BLOCK elif ModuleModel.is_absorbance_reader(self): return ModuleType.ABSORBANCE_READER + elif ModuleModel.is_flex_stacker(self): + return ModuleType.FLEX_STACKER assert False, f"Invalid ModuleModel {self}" @@ -534,6 +537,11 @@ def is_absorbance_reader( """Whether a given model is an Absorbance Plate Reader.""" return model == cls.ABSORBANCE_READER_V1 + @classmethod + def is_flex_stacker(cls, model: ModuleModel) -> TypeGuard[AbsorbanceReaderModel]: + """Whether a given model is a Flex Stacker..""" + return model == cls.FLEX_STACKER_MODULE_V1 + TemperatureModuleModel = Literal[ ModuleModel.TEMPERATURE_MODULE_V1, ModuleModel.TEMPERATURE_MODULE_V2 @@ -547,6 +555,7 @@ def is_absorbance_reader( HeaterShakerModuleModel = Literal[ModuleModel.HEATER_SHAKER_MODULE_V1] MagneticBlockModel = Literal[ModuleModel.MAGNETIC_BLOCK_V1] AbsorbanceReaderModel = Literal[ModuleModel.ABSORBANCE_READER_V1] +FlexStackerModuleModel = Literal[ModuleModel.FLEX_STACKER_MODULE_V1] class ModuleDimensions(BaseModel): diff --git a/robot-server/robot_server/modules/module_data_mapper.py b/robot-server/robot_server/modules/module_data_mapper.py index f0c623fdab3..58a6302ba84 100644 --- a/robot-server/robot_server/modules/module_data_mapper.py +++ b/robot-server/robot_server/modules/module_data_mapper.py @@ -28,6 +28,8 @@ from .module_models import ( AttachedModule, AttachedModuleData, + FlexStackerModule, + FlexStackerModuleData, MagneticModule, MagneticModuleData, ModuleCalibrationData, @@ -155,6 +157,11 @@ def map_data( int, live_data["data"].get("referenceWavelength") ), ) + elif module_type == ModuleType.FLEX_STACKER: + module_cls = FlexStackerModule + module_data = FlexStackerModuleData( + status=live_data["status"], + ) else: assert False, f"Invalid module type {module_type}" diff --git a/robot-server/robot_server/modules/module_models.py b/robot-server/robot_server/modules/module_models.py index 7b6da4925ac..1c8a1b14e93 100644 --- a/robot-server/robot_server/modules/module_models.py +++ b/robot-server/robot_server/modules/module_models.py @@ -343,19 +343,44 @@ class AbsorbanceReaderModule( AbsorbanceReaderModuleData, ] ): - """An attached Heater-Shaker Module.""" + """An attached Absorbance Reader Module.""" moduleType: Literal[ModuleType.ABSORBANCE_READER] moduleModel: Literal[ModuleModel.ABSORBANCE_READER_V1] data: AbsorbanceReaderModuleData +class FlexStackerModuleData(BaseModel): + """Live data from a Flex Stacker module.""" + + # TODO: add rest of data + status: str = Field( + ..., + description="Overall status of the module.", + ) + + +class FlexStackerModule( + _GenericModule[ + Literal[ModuleType.FLEX_STACKER], + Literal[ModuleModel.FLEX_STACKER_MODULE_V1], + FlexStackerModuleData, + ] +): + """An attached Flex Stacker Module.""" + + moduleType: Literal[ModuleType.FLEX_STACKER] + moduleModel: Literal[ModuleModel.FLEX_STACKER_MODULE_V1] + data: FlexStackerModuleData + + AttachedModule = Union[ TemperatureModule, MagneticModule, ThermocyclerModule, HeaterShakerModule, AbsorbanceReaderModule, + FlexStackerModule, ] @@ -365,4 +390,5 @@ class AbsorbanceReaderModule( ThermocyclerModuleData, HeaterShakerModuleData, AbsorbanceReaderModuleData, + FlexStackerModuleData, ] diff --git a/shared-data/module/definitions/3/flexStackerModuleV1.json b/shared-data/module/definitions/3/flexStackerModuleV1.json new file mode 100644 index 00000000000..fa6f7db3494 --- /dev/null +++ b/shared-data/module/definitions/3/flexStackerModuleV1.json @@ -0,0 +1,524 @@ +{ + "$otSharedSchema": "module/schemas/3", + "moduleType": "flexStackerModuleType", + "model": "flexStackerModuleV1", + "labwareOffset": { + "x": -0.125, + "y": 1.125, + "z": 68.275 + }, + "dimensions": { + "bareOverallHeight": 82, + "overLabwareHeight": 0, + "xDimension": 156.25, + "yDimension": 91.75, + "footprintXDimension": 128, + "footprintYDimension": 86, + "labwareInterfaceXDimension": 128, + "labwareInterfaceYDimension": 86 + }, + "cornerOffsetFromSlot": { + "x": -18, + "y": -1.8, + "z": 0 + }, + "calibrationPoint": { + "x": 12.0, + "y": 8.75, + "z": 68.275 + }, + "gripperOffsets": { + "default": { + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + }, + "dropOffset": { + "x": 0, + "y": 0, + "z": 1.0 + } + } + }, + "displayName": "Flex Stacker Module GEN1", + "quirks": [], + "slotTransforms": { + "ot2_standard": { + "3": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "6": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "9": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + } + }, + "ot2_short_trash": { + "3": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "6": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "9": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + } + }, + "ot3_standard": { + "D1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "C1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "B1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "A1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "D3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "C3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "B3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "A3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + } + } + }, + "compatibleWith": [], + "incompatibleWithDecks": [], + "twoDimensionalRendering": { + "name": "svg", + "type": "element", + "value": "", + "attributes": { + "version": "1.1", + "id": "flexstacker", + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "x": "0px", + "y": "0px", + "viewBox": "0 0 148.6 91.8", + "style": "enable-background:new 0 0 148.6 91.8;", + "xml:space": "preserve" + }, + "children": [ + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "moduleBaseFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#E6E6E6;", + "d": "M24.4,88.6c0.9,0,1.1-0.7,1.1-1.4v-3.4c0-0.2-0.2-0.5-0.5-0.5h-5.2c-0.2,0-0.5-0.2-0.5-0.5v-4.5\n\t\tc0-0.2-0.2-0.5-0.5-0.5h-5.7c-1,0-1.9-0.8-1.9-1.9V15.6c0-1,0.8-1.9,1.9-1.9h5.7c0.2,0,0.5-0.2,0.5-0.5V8.8c0-0.2,0.2-0.5,0.5-0.5\n\t\tH25c0.2,0,0.5-0.2,0.5-0.5V4.5c0-0.8-0.4-1.4-1.1-1.4s-23.9,0-23.9,0v85.5C0.5,88.6,23.5,88.6,24.4,88.6z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "moduleOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M147.9,1.1v89.6H1.1V1.1H147.9 M148.9,0H0v91.7h148.9V0L148.9,0z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "moduleBaseInnerWalls" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M148.4,89.1H146v-1.4H26c-0.2,1.2-1.2,1.4-1.6,1.4H0.5v-1h23.8c0.3,0,0.7-0.1,0.7-0.9v-0.5h122v1.4h1.4V89.1z" + }, + "children": [] + }, + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M147,5H25V4.5c0-0.8-0.3-0.9-0.7-0.9H0.5v-1h23.8c0.4,0,1.4,0.1,1.6,1.4h120V2.6h2.4v1H147V5z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "statusFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#FFFFFF;", + "d": "M4.8,40.3c0-1.1,0.9-2,2-2s2,0.9,2,2v11.2c0,1.1-0.9,2-2,2s-2-0.9-2-2V40.3z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "statusOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M6.8,53.7H6.7c-1.2,0-2.2-1-2.2-2.2V40.2c0-1.2,1-2.2,2.2-2.2h0.1C8,38,9,39,9,40.2v11.3C9,52.7,8,53.7,6.8,53.7z\n\t\t M6.7,38.5c-0.9,0-1.7,0.8-1.7,1.7v11.3c0,1,0.8,1.7,1.7,1.7h0.1c1,0,1.7-0.8,1.7-1.7V40.2c0-1-0.8-1.7-1.7-1.7H6.7z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "moduleMidFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#E6E6E6;", + "d": "M12.1,91.2L8.6,86c-0.1-0.1-0.1-0.2-0.1-0.3V6c0-0.1,0-0.2,0.1-0.3l3.5-5.2h134.2l3.5,5.2\n\t\tc0.1,0.1,0.1,0.2,0.1,0.3v79.7c0,0.1,0,0.2-0.1,0.3l-3.5,5.2H12.1" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "moduleMidOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M146,1.1l3.3,5v79.7l-3.3,5H12.3l-3.3-5V6l3.3-4.9L146,1.1 M146.5,0H11.8L8.2,5.4C8.1,5.6,8,5.8,8,6v79.7\n\t\tc0,0.2,0.1,0.4,0.2,0.6l3.6,5.4h134.8l3.6-5.4c0.1-0.2,0.2-0.4,0.2-0.6V6c0-0.2-0.1-0.4-0.2-0.6L146.5,0L146.5,0z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "plateBottomFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#FFFFFF;", + "d": "M49.1,6.5c-0.9,0-1.7,0.6-1.9,1.6c-0.4,1.7-1.9,2.9-3.7,2.9s-3.3-1.2-3.7-2.9\n\t\tc-0.2-1-1.1-1.6-1.9-1.6H24.4c-1.6,0-2.9,1.3-2.9,2.9v72.9c0,1.6,1.3,2.9,2.9,2.9h13.4c0.8,0,1.6-0.6,1.9-1.6\n\t\tc0.4-1.7,1.9-2.9,3.7-2.9s3.3,1.2,3.7,2.9c0.2,1,1,1.6,1.9,1.6h60.2c0.9,0,1.7-0.6,1.9-1.6c0.4-1.7,1.9-2.9,3.7-2.9\n\t\tc1.8,0,3.3,1.2,3.7,2.9c0.2,1,1.1,1.6,1.9,1.6H134c1.6,0,2.9-1.3,2.9-2.9V9.4c0-1.6-1.3-2.9-2.9-2.9h-13.4c-0.8,0-1.6,0.6-1.9,1.6\n\t\tc-0.4,1.7-1.9,2.9-3.7,2.9c-1.8,0-3.3-1.2-3.7-2.9c-0.2-1-1-1.6-1.9-1.6L49.1,6.5L49.1,6.5z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "plateBottomOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M134,85.5h-13.4c-1,0-1.8-0.7-2.1-1.7c-0.4-1.6-1.8-2.7-3.5-2.7s-3.1,1.1-3.5,2.7c-0.3,1.1-1.1,1.7-2.1,1.7H49.1\n\t\tc-1,0-1.9-0.7-2.1-1.7c-0.4-1.6-1.8-2.7-3.5-2.7s-3.1,1.1-3.5,2.7c-0.3,1-1.1,1.7-2.1,1.7H24.4c-1.8,0-3.2-1.4-3.2-3.2V9.4\n\t\tc0-1.8,1.4-3.2,3.2-3.2h13.4c1,0,1.8,0.7,2.1,1.7c0.4,1.6,1.8,2.7,3.5,2.7s3.1-1.1,3.5-2.7c0.3-1,1-1.6,1.9-1.7l0,0h60.4\n\t\tc1,0,1.9,0.7,2.1,1.7c0.4,1.6,1.8,2.7,3.5,2.7s3.1-1.1,3.5-2.7c0.3-1,1.1-1.7,2.1-1.7H134c1.8,0,3.2,1.4,3.2,3.2v72.9\n\t\tC137.1,84.1,135.7,85.5,134,85.5z M114.9,80.6c1.9,0,3.5,1.3,4,3.1c0.2,0.8,0.9,1.4,1.6,1.4H134c1.5,0,2.7-1.2,2.7-2.7v-73\n\t\tc0-1.5-1.2-2.7-2.7-2.7h-13.4c-0.7,0-1.4,0.6-1.6,1.4c-0.5,1.8-2.1,3.1-4,3.1s-3.5-1.3-4-3.1c-0.2-0.8-0.9-1.4-1.7-1.4h-60h-0.2\n\t\tc-0.8,0-1.4,0.5-1.7,1.4c-0.5,1.8-2.1,3.1-4,3.1s-3.5-1.3-4-3.1c-0.2-0.8-0.9-1.4-1.6-1.4H24.4c-1.5,0-2.7,1.2-2.7,2.7v72.9\n\t\tc0,1.5,1.2,2.7,2.7,2.7h13.4c0.7,0,1.4-0.6,1.6-1.4c0.5-1.8,2.1-3.1,4-3.1s3.5,1.3,4,3.1c0.2,0.8,0.9,1.4,1.7,1.4h60.2\n\t\tc0.8,0,1.4-0.5,1.7-1.4C111.4,81.8,113,80.6,114.9,80.6z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "plateHolder" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M114.9,3c-2.3,0-4.1,1.8-4.1,4.1s1.8,4.1,4.1,4.1s4.1-1.8,4.1-4.1S117.2,3,114.9,3z M111.3,7.1c0-0.4,0.1-0.8,0.2-1.1h6.8\n\t\tc0.1,0.4,0.2,0.7,0.2,1.1c0,0.2,0,0.4-0.1,0.6h-7.1C111.4,7.5,111.3,7.3,111.3,7.1z M114.9,3.5c1.4,0,2.6,0.8,3.2,2h-6.4\n\t\tC112.3,4.3,113.5,3.5,114.9,3.5z M114.9,10.7c-1.6,0-2.9-1.1-3.4-2.5h6.8C117.9,9.6,116.5,10.7,114.9,10.7z" + }, + "children": [] + }, + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M43.4,3c-2.3,0-4.1,1.8-4.1,4.1s1.8,4.1,4.1,4.1s4.1-1.8,4.1-4.1S45.7,3,43.4,3z M39.8,7.1c0-0.4,0.1-0.8,0.2-1.1h6.8\n\t\tC47,6.3,47,6.7,47,7.1c0,0.2,0,0.4-0.1,0.6h-7.1C39.9,7.5,39.8,7.3,39.8,7.1z M43.4,3.5c1.4,0,2.6,0.8,3.2,2h-6.4\n\t\tC40.9,4.3,42,3.5,43.4,3.5z M43.4,10.7c-1.6,0-2.9-1.1-3.4-2.5h6.8C46.4,9.6,45,10.7,43.4,10.7z" + }, + "children": [] + }, + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M43.4,80.5c-2.3,0-4.1,1.8-4.1,4.1s1.8,4.1,4.1,4.1s4.1-1.8,4.1-4.1S45.7,80.5,43.4,80.5z M43.4,81c1.6,0,3,1.1,3.4,2.5H40\n\t\tC40.5,82.1,41.8,81,43.4,81z M47,84.6c0,0.4-0.1,0.8-0.2,1.2H40c-0.1-0.4-0.2-0.8-0.2-1.2c0-0.2,0-0.4,0.1-0.6H47\n\t\tC47,84.2,47,84.4,47,84.6z M43.4,88.2c-1.4,0-2.6-0.8-3.2-1.9h6.3C46,87.4,44.8,88.2,43.4,88.2z" + }, + "children": [] + }, + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M114.9,80.5c-2.3,0-4.1,1.8-4.1,4.1s1.8,4.1,4.1,4.1s4.1-1.8,4.1-4.1S117.2,80.5,114.9,80.5z M114.9,81\n\t\tc1.6,0,3,1.1,3.4,2.5h-6.8C112,82.1,113.3,81,114.9,81z M118.5,84.6c0,0.4-0.1,0.8-0.2,1.2h-6.8c-0.1-0.4-0.2-0.8-0.2-1.2\n\t\tc0-0.2,0-0.4,0.1-0.6h7.1C118.5,84.2,118.5,84.4,118.5,84.6z M114.9,88.2c-1.4,0-2.6-0.8-3.2-1.9h6.3\n\t\tC117.5,87.4,116.3,88.2,114.9,88.2z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "adapterScrew" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#707075; opacity: 50%;", + "d": "M79.2,50.5c-2.6,0-4.7-2.1-4.7-4.7s2.1-4.7,4.7-4.7s4.7,2.1,4.7,4.7S81.7,50.5,79.2,50.5z M79.2,41.6\n\t\tc-2.3,0-4.2,1.9-4.2,4.2s1.9,4.2,4.2,4.2c2.3,0,4.2-1.9,4.2-4.2C83.4,43.5,81.5,41.6,79.2,41.6z" + }, + "children": [] + }, + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#707075; opacity: 50%;", + "d": "M79.2,49c-1.8,0-3.2-1.4-3.2-3.2s1.4-3.2,3.2-3.2s3.2,1.4,3.2,3.2C82.3,47.6,80.9,49,79.2,49z M79.2,43.2\n\t\tc-1.5,0-2.7,1.2-2.7,2.7s1.2,2.7,2.7,2.7s2.7-1.2,2.7-2.7C81.8,44.4,80.6,43.2,79.2,43.2z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "leftLatchFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#E6E6E6;", + "d": "M14.1,6.6c0-0.4,0.1-0.8,0.4-1.2L18.8,0h-7L8.2,5.4C8.1,5.6,8,5.8,8,6v79.7c0,0.2,0.1,0.4,0.2,0.6\n\t\tl3.6,5.4h7l-4.3-5.4c-0.2-0.4-0.4-0.7-0.4-1.2L14.1,6.6L14.1,6.6z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "leftLatchOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M16.6,1.1l-2.9,3.7c-0.4,0.5-0.6,1.2-0.6,1.8v78.5c0,0.7,0.2,1.3,0.6,1.8l2.9,3.7h-4.3l-3.3-5V6l3.3-4.9L16.6,1.1 M18.8,0\n\t\th-7L8.2,5.4C8.1,5.6,8,5.8,8,6v79.7c0,0.2,0.1,0.4,0.2,0.6l3.6,5.4h7l-4.3-5.4c-0.2-0.4-0.4-0.7-0.4-1.2V6.6c0-0.4,0.1-0.8,0.4-1.2\n\t\tL18.8,0L18.8,0z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "rightLatchFill" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "style": "fill:#E6E6E6;", + "d": "M145.3,6.6c0-0.4-0.1-0.8-0.4-1.2L140.6,0h7l3.6,5.4c0.1,0.2,0.2,0.4,0.2,0.6v79.7\n\t\tc0,0.2-0.1,0.4-0.2,0.6l-3.6,5.4h-7l4.3-5.4c0.2-0.4,0.4-0.7,0.4-1.2V6.6z" + }, + "children": [] + } + ] + }, + { + "name": "g", + "type": "element", + "value": "", + "attributes": { + "id": "rightLatchOutline" + }, + "children": [ + { + "name": "path", + "type": "element", + "value": "", + "attributes": { + "d": "M147.1,1.1l3.3,5v79.7l-3.3,5h-4.3l2.9-3.7c0.4-0.5,0.6-1.2,0.6-1.8V6.6c0-0.7-0.2-1.3-0.6-1.8l-2.9-3.7L147.1,1.1\n\t\t M147.6,0h-7l4.3,5.4c0.2,0.4,0.4,0.7,0.4,1.2v78.5c0,0.4-0.1,0.8-0.4,1.2l-4.3,5.4h7l3.6-5.4c0.1-0.2,0.2-0.4,0.2-0.6V5.9\n\t\tc0-0.2-0.1-0.4-0.2-0.6L147.6,0L147.6,0z" + }, + "children": [] + } + ] + } + ] + } +} diff --git a/shared-data/python/opentrons_shared_data/module/types.py b/shared-data/python/opentrons_shared_data/module/types.py index 87b49613167..64907930396 100644 --- a/shared-data/python/opentrons_shared_data/module/types.py +++ b/shared-data/python/opentrons_shared_data/module/types.py @@ -19,7 +19,7 @@ HeaterShakerModuleType = Literal["heaterShakerModuleType"] MagneticBlockType = Literal["magneticBlockType"] AbsorbanceReaderType = Literal["absorbanceReaderType"] -FlexStackerType = Literal["flexStackerType"] +FlexStackerModuleType = Literal["flexStackerModuleType"] ModuleType = Union[ MagneticModuleType, @@ -28,7 +28,7 @@ HeaterShakerModuleType, MagneticBlockType, AbsorbanceReaderType, - FlexStackerType, + FlexStackerModuleType, ] MagneticModuleModel = Literal["magneticModuleV1", "magneticModuleV2"] @@ -37,7 +37,7 @@ HeaterShakerModuleModel = Literal["heaterShakerModuleV1"] MagneticBlockModel = Literal["magneticBlockV1"] AbsorbanceReaderModel = Literal["absorbanceReaderV1"] -FlexStackerModel = Literal["flexStackerV1"] +FlexStackerModuleModel = Literal["flexStackerModuleV1"] ModuleModel = Union[ MagneticModuleModel, @@ -46,7 +46,7 @@ HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, - FlexStackerModel, + FlexStackerModuleModel, ] ModuleSlotTransform = TypedDict( From be1a2784e553af776d44071b2d6efe11ee023f8f Mon Sep 17 00:00:00 2001 From: vegano1 Date: Sat, 4 Jan 2025 15:08:28 -0500 Subject: [PATCH 3/9] add poller status getters --- .../drivers/flex_stacker/abstract.py | 5 ++ .../opentrons/drivers/flex_stacker/types.py | 66 +++++++++++++++++-- .../hardware_control/modules/flex_stacker.py | 56 +++++++++++++++- .../modules/module_data_mapper.py | 5 ++ .../robot_server/modules/module_models.py | 7 ++ 5 files changed, 130 insertions(+), 9 deletions(-) diff --git a/api/src/opentrons/drivers/flex_stacker/abstract.py b/api/src/opentrons/drivers/flex_stacker/abstract.py index f92039f9564..e27055edf00 100644 --- a/api/src/opentrons/drivers/flex_stacker/abstract.py +++ b/api/src/opentrons/drivers/flex_stacker/abstract.py @@ -1,6 +1,7 @@ from typing import Protocol from .types import ( + LimitSwitchStatus, StackerAxis, PlatformStatus, Direction, @@ -44,6 +45,10 @@ async def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> boo """ ... + async def get_limit_switches_status(self) -> LimitSwitchStatus: + """Get limit switch statuses for all axes.""" + ... + async def get_platform_sensor(self, direction: Direction) -> bool: """Get platform sensor status. diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index 63c977ae656..88a47078fdd 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -98,11 +98,11 @@ def distance(self, distance: float) -> float: class LimitSwitchStatus: """Stacker Limit Switch Statuses.""" - XE: bool - XR: bool - ZE: bool - ZR: bool - LR: bool + XE: bool = False + XR: bool = False + ZE: bool = False + ZR: bool = False + LR: bool = False @classmethod def get_fields(cls) -> List[str]: @@ -124,8 +124,8 @@ def get(self, axis: StackerAxis, direction: Direction) -> bool: class PlatformStatus: """Stacker Platform Statuses.""" - E: bool - R: bool + E: bool = False + R: bool = False @classmethod def get_fields(cls) -> List[str]: @@ -136,6 +136,58 @@ def get(self, direction: Direction) -> bool: """Get platform status.""" return self.E if direction == Direction.EXTENT else self.R + def to_dict(self) -> Dict[str, bool]: + """Dict of the data.""" + return { + "extent": self.E, + "retract": self.R, + } + + +class PlatformState(Enum): + UNKNOWN = "unknown" + EXTENDED = "extended" + RETRACTED = "retracted" + + @classmethod + def from_status(cls, status: PlatformStatus) -> "PlatformState": + """Get the state from the platform status.""" + if status.E and not status.R: + return PlatformState.EXTENDED + if status.R and not status.E: + return PlatformState.RETRACTED + return PlatformState.UNKNOWN + + +class StackerAxisState(Enum): + UNKNOWN = "unknown" + EXTENDED = "extended" + RETRACTED = "retracted" + + @classmethod + def from_status( + cls, status: LimitSwitchStatus, axis: StackerAxis + ) -> "StackerAxisState": + """Get the axis state from the limit switch status.""" + match axis: + case StackerAxis.X: + if status.XE and not status.XR: + return StackerAxisState.EXTENDED + if status.XR and not status.XE: + return StackerAxisState.RETRACTED + case StackerAxis.Z: + if status.ZE and not status.ZR: + return StackerAxisState.EXTENDED + if status.ZR and not status.ZE: + return StackerAxisState.RETRACTED + case StackerAxis.L: + return ( + StackerAxisState.EXTENDED + if status.LR + else StackerAxisState.RETRACTED + ) + return StackerAxisState.UNKNOWN + @dataclass class MoveParams: diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py index e55f0472b0a..786375ebd2a 100644 --- a/api/src/opentrons/hardware_control/modules/flex_stacker.py +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -2,8 +2,13 @@ import asyncio import logging -from typing import Optional, Mapping +from typing import Dict, Optional, Mapping +from opentrons.drivers.flex_stacker.types import ( + PlatformState, + StackerAxis, + StackerAxisState, +) from opentrons.drivers.rpi_drivers.types import USBPort from opentrons.drivers.flex_stacker.driver import FlexStackerDriver from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver @@ -139,6 +144,21 @@ def model(self) -> str: def bootloader(self) -> UploadFunction: return update.upload_via_dfu + @property + def platform_state(self) -> PlatformState: + """The state of the platform.""" + return self._reader.platform_state + + @property + def limit_switch_status(self) -> Dict[StackerAxis, StackerAxisState]: + """The status of the Limit switches.""" + return self._reader.limit_switch_status + + @property + def hopper_door_closed(self) -> bool: + """The status of the hopper door.""" + return self._reader.hopper_door_closed + @property def device_info(self) -> Mapping[str, str]: return self._device_info @@ -148,6 +168,11 @@ def live_data(self) -> LiveData: return { "status": self.status, "data": { + "platformState": self.platform_state.value, + "axisStateX": self.limit_switch_status[StackerAxis.X].value, + "axisStateZ": self.limit_switch_status[StackerAxis.Z].value, + "axisStateL": self.limit_switch_status[StackerAxis.L].value, + "hopperDoorClosed": self.hopper_door_closed, "errorDetails": self._reader.error, }, } @@ -163,12 +188,13 @@ def is_simulated(self) -> bool: async def prep_for_update(self) -> str: await self._poller.stop() + await self._driver.stop_motors() await self._driver.enter_programming_mode() dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=2) return dfu_info async def deactivate(self, must_be_running: bool = True) -> None: - pass + await self._driver.stop_motors() class FlexStackerReader(Reader): @@ -177,10 +203,36 @@ class FlexStackerReader(Reader): def __init__(self, driver: AbstractFlexStackerDriver) -> None: self.error: Optional[str] = None self._driver = driver + self.limit_switch_status = { + axis: StackerAxisState.UNKNOWN for axis in StackerAxis + } + self.platform_state = PlatformState.UNKNOWN + self.hopper_door_closed = False async def read(self) -> None: + await self.get_limit_switch_status() + await self.get_platform_sensor_state() + await self.get_door_closed() self._set_error(None) + async def get_limit_switch_status(self) -> None: + """Get the limit switch status.""" + status = await self._driver.get_limit_switches_status() + self.limit_switch_status = { + StackerAxis.X: StackerAxisState.from_status(status, StackerAxis.X), + StackerAxis.Z: StackerAxisState.from_status(status, StackerAxis.Z), + StackerAxis.L: StackerAxisState.from_status(status, StackerAxis.L), + } + + async def get_platform_sensor_state(self) -> None: + """Get the platform state.""" + status = await self._driver.get_platform_status() + self.platform_state = PlatformState.from_status(status) + + async def get_door_closed(self) -> None: + """Check if the hopper door is closed.""" + self.hopper_door_closed = await self._driver.get_hopper_door_closed() + def on_error(self, exception: Exception) -> None: self._set_error(exception) diff --git a/robot-server/robot_server/modules/module_data_mapper.py b/robot-server/robot_server/modules/module_data_mapper.py index 58a6302ba84..c15d2ffdf25 100644 --- a/robot-server/robot_server/modules/module_data_mapper.py +++ b/robot-server/robot_server/modules/module_data_mapper.py @@ -161,6 +161,11 @@ def map_data( module_cls = FlexStackerModule module_data = FlexStackerModuleData( status=live_data["status"], + plarformState=cast(str, live_data.get("platformState")), + axisStateX=cast(str, live_data.get("axisStateX")), + axisStateZ=cast(str, live_data.get("axisStateZ")), + axisStateL=cast(str, live_data.get("axisStateL")), + hopperDoorClosed=cast(bool, live_data.get("hopperDoorClosed")), ) else: assert False, f"Invalid module type {module_type}" diff --git a/robot-server/robot_server/modules/module_models.py b/robot-server/robot_server/modules/module_models.py index 1c8a1b14e93..04f3547193f 100644 --- a/robot-server/robot_server/modules/module_models.py +++ b/robot-server/robot_server/modules/module_models.py @@ -358,6 +358,13 @@ class FlexStackerModuleData(BaseModel): ..., description="Overall status of the module.", ) + plarformState: str = Field(..., description="The state of the platform.") + axisStateX: str = Field(..., description="The state of the X axis limit switches.") + axisStateZ: str = Field(..., description="The state of the Z axis limit switches.") + axisStateL: str = Field(..., description="The state of the L axis limit switches.") + hopperDoorClosed: bool = Field( + ..., description="Whether the hopper door is closed." + ) class FlexStackerModule( From 2cf4b169de939887cbff78f400fa5c84bac37e56 Mon Sep 17 00:00:00 2001 From: Brayan Almonte Date: Mon, 6 Jan 2025 10:48:01 -0500 Subject: [PATCH 4/9] Update ot3controller.py revert debug changes --- .../opentrons/hardware_control/backends/ot3controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 985077de269..84ffbffd8da 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -482,7 +482,7 @@ def _build_system_hardware( ) -> SystemDrivers: gpio = OT3GPIO("hardware_control") eeprom_driver = eeprom_driver or EEPROMDriver(gpio) - # eeprom_driver.setup() + eeprom_driver.setup() gpio_dev: Union[OT3GPIO, RemoteOT3GPIO] = gpio usb_messenger: Optional[BinaryMessenger] = None if usb_driver: @@ -529,9 +529,9 @@ def update_feature_flags(self, feature_flags: HardwareFeatureFlags) -> None: async def update_motor_status(self) -> None: """Retreieve motor and encoder status and position from all present nodes""" motor_nodes = self._motor_nodes() - if motor_nodes: - response = await get_motor_position(self._messenger, motor_nodes) - self._handle_motor_status_response(response) + assert len(motor_nodes) + response = await get_motor_position(self._messenger, motor_nodes) + self._handle_motor_status_response(response) async def update_motor_estimation(self, axes: Sequence[Axis]) -> None: """Update motor position estimation for commanded nodes, and update cache of data.""" From ce2b674e66a65483c809859f4531c0c0f0cc2396 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Tue, 7 Jan 2025 16:56:53 -0500 Subject: [PATCH 5/9] fixes to robot-server and api --- .../drivers/flex_stacker/__init__.py | 2 + .../opentrons/drivers/flex_stacker/types.py | 45 ---------------- .../hardware_control/modules/__init__.py | 6 +++ .../hardware_control/modules/flex_stacker.py | 32 +++++------ .../hardware_control/modules/types.py | 53 ++++++++++++++++++ .../modules/test_hc_flexstacker.py | 52 ++++++++++++++++++ .../hardware_control/test_modules.py | 54 ++++++++++++++++++- .../modules/module_data_mapper.py | 17 +++--- .../robot_server/modules/module_models.py | 16 ++++-- shared-data/module/schemas/3.json | 5 +- 10 files changed, 209 insertions(+), 73 deletions(-) create mode 100644 api/tests/opentrons/hardware_control/modules/test_hc_flexstacker.py diff --git a/api/src/opentrons/drivers/flex_stacker/__init__.py b/api/src/opentrons/drivers/flex_stacker/__init__.py index 268694fdaa1..4c8d50a7069 100644 --- a/api/src/opentrons/drivers/flex_stacker/__init__.py +++ b/api/src/opentrons/drivers/flex_stacker/__init__.py @@ -1,9 +1,11 @@ from .abstract import AbstractFlexStackerDriver from .driver import FlexStackerDriver from .simulator import SimulatingDriver +from . import types as FlexStackerTypes __all__ = [ "AbstractFlexStackerDriver", "FlexStackerDriver", "SimulatingDriver", + "FlexStackerTypes", ] diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index 88a47078fdd..dbedab901a4 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -144,51 +144,6 @@ def to_dict(self) -> Dict[str, bool]: } -class PlatformState(Enum): - UNKNOWN = "unknown" - EXTENDED = "extended" - RETRACTED = "retracted" - - @classmethod - def from_status(cls, status: PlatformStatus) -> "PlatformState": - """Get the state from the platform status.""" - if status.E and not status.R: - return PlatformState.EXTENDED - if status.R and not status.E: - return PlatformState.RETRACTED - return PlatformState.UNKNOWN - - -class StackerAxisState(Enum): - UNKNOWN = "unknown" - EXTENDED = "extended" - RETRACTED = "retracted" - - @classmethod - def from_status( - cls, status: LimitSwitchStatus, axis: StackerAxis - ) -> "StackerAxisState": - """Get the axis state from the limit switch status.""" - match axis: - case StackerAxis.X: - if status.XE and not status.XR: - return StackerAxisState.EXTENDED - if status.XR and not status.XE: - return StackerAxisState.RETRACTED - case StackerAxis.Z: - if status.ZE and not status.ZR: - return StackerAxisState.EXTENDED - if status.ZR and not status.ZE: - return StackerAxisState.RETRACTED - case StackerAxis.L: - return ( - StackerAxisState.EXTENDED - if status.LR - else StackerAxisState.RETRACTED - ) - return StackerAxisState.UNKNOWN - - @dataclass class MoveParams: """Move Parameters.""" diff --git a/api/src/opentrons/hardware_control/modules/__init__.py b/api/src/opentrons/hardware_control/modules/__init__.py index 305ccfdc855..35ae63eacbe 100644 --- a/api/src/opentrons/hardware_control/modules/__init__.py +++ b/api/src/opentrons/hardware_control/modules/__init__.py @@ -20,6 +20,9 @@ MagneticStatus, HeaterShakerStatus, AbsorbanceReaderStatus, + PlatformState, + StackerAxisState, + FlexStackerStatus, SpeedStatus, LiveData, ) @@ -57,4 +60,7 @@ "AbsorbanceReaderDisconnectedError", "ModuleDisconnectedCallback", "FlexStacker", + "FlexStackerStatus", + "PlatformState", + "StackerAxisState", ] diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py index 786375ebd2a..d66a754627e 100644 --- a/api/src/opentrons/hardware_control/modules/flex_stacker.py +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -5,9 +5,7 @@ from typing import Dict, Optional, Mapping from opentrons.drivers.flex_stacker.types import ( - PlatformState, StackerAxis, - StackerAxisState, ) from opentrons.drivers.rpi_drivers.types import USBPort from opentrons.drivers.flex_stacker.driver import FlexStackerDriver @@ -17,8 +15,11 @@ from opentrons.hardware_control.poller import Reader, Poller from opentrons.hardware_control.modules import mod_abc, update from opentrons.hardware_control.modules.types import ( + FlexStackerStatus, ModuleDisconnectedCallback, ModuleType, + PlatformState, + StackerAxisState, UploadFunction, LiveData, ) @@ -141,9 +142,6 @@ def _model_from_revision(revision: Optional[str]) -> str: def model(self) -> str: return self._model_from_revision(self._device_info.get("model")) - def bootloader(self) -> UploadFunction: - return update.upload_via_dfu - @property def platform_state(self) -> PlatformState: """The state of the platform.""" @@ -163,10 +161,20 @@ def hopper_door_closed(self) -> bool: def device_info(self) -> Mapping[str, str]: return self._device_info + @property + def status(self) -> FlexStackerStatus: + """Module status or error state details.""" + # TODO: Implement getting device status from the stacker + return FlexStackerStatus.IDLE + + @property + def is_simulated(self) -> bool: + return isinstance(self._driver, SimulatingDriver) + @property def live_data(self) -> LiveData: return { - "status": self.status, + "status": self.status.value, "data": { "platformState": self.platform_state.value, "axisStateX": self.limit_switch_status[StackerAxis.X].value, @@ -177,15 +185,6 @@ def live_data(self) -> LiveData: }, } - @property - def status(self) -> str: - """Module status or error state details.""" - return "idle" - - @property - def is_simulated(self) -> bool: - return isinstance(self._driver, SimulatingDriver) - async def prep_for_update(self) -> str: await self._poller.stop() await self._driver.stop_motors() @@ -193,6 +192,9 @@ async def prep_for_update(self) -> str: dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=2) return dfu_info + def bootloader(self) -> UploadFunction: + return update.upload_via_dfu + async def deactivate(self, must_be_running: bool = True) -> None: await self._driver.stop_motors() diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index beb67473138..e9bf6dfa2ad 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -17,6 +17,7 @@ from typing_extensions import TypedDict from pathlib import Path +from opentrons.drivers.flex_stacker.types import LimitSwitchStatus, PlatformStatus, StackerAxis from opentrons.drivers.rpi_drivers.types import USBPort if TYPE_CHECKING: @@ -237,3 +238,55 @@ class LidStatus(str, Enum): OFF = "off" UNKNOWN = "unknown" ERROR = "error" + + +class FlexStackerStatus(str, Enum): + IDLE = "idle" + RUNNING = "running" + ERROR = "error" + + +class PlatformState(str, Enum): + UNKNOWN = "unknown" + EXTENDED = "extended" + RETRACTED = "retracted" + + @classmethod + def from_status(cls, status: PlatformStatus) -> "PlatformState": + """Get the state from the platform status.""" + if status.E and not status.R: + return PlatformState.EXTENDED + if status.R and not status.E: + return PlatformState.RETRACTED + return PlatformState.UNKNOWN + + +class StackerAxisState(str, Enum): + UNKNOWN = "unknown" + EXTENDED = "extended" + RETRACTED = "retracted" + + @classmethod + def from_status( + cls, status: LimitSwitchStatus, axis: StackerAxis + ) -> "StackerAxisState": + """Get the axis state from the limit switch status.""" + match axis: + case StackerAxis.X: + if status.XE and not status.XR: + return StackerAxisState.EXTENDED + if status.XR and not status.XE: + return StackerAxisState.RETRACTED + case StackerAxis.Z: + if status.ZE and not status.ZR: + return StackerAxisState.EXTENDED + if status.ZR and not status.ZE: + return StackerAxisState.RETRACTED + case StackerAxis.L: + return ( + StackerAxisState.EXTENDED + if status.LR + else StackerAxisState.RETRACTED + ) + return StackerAxisState.UNKNOWN + diff --git a/api/tests/opentrons/hardware_control/modules/test_hc_flexstacker.py b/api/tests/opentrons/hardware_control/modules/test_hc_flexstacker.py new file mode 100644 index 00000000000..0a6d43e441f --- /dev/null +++ b/api/tests/opentrons/hardware_control/modules/test_hc_flexstacker.py @@ -0,0 +1,52 @@ +import asyncio +import pytest +import mock +from typing import AsyncGenerator +from opentrons.hardware_control import modules, ExecutionManager +from opentrons.drivers.rpi_drivers.types import USBPort + + +@pytest.fixture +def usb_port() -> USBPort: + return USBPort( + name="", + port_number=0, + device_path="/dev/ot_module_sim_flexstacker0", + ) + + +@pytest.fixture +async def simulating_module( + usb_port: USBPort, +) -> AsyncGenerator[modules.AbstractModule, None]: + module = await modules.build( + port=usb_port.device_path, + usb_port=usb_port, + type=modules.ModuleType["FLEX_STACKER"], + simulating=True, + hw_control_loop=asyncio.get_running_loop(), + execution_manager=ExecutionManager(), + ) + assert isinstance(module, modules.AbstractModule) + try: + yield module + finally: + await module.cleanup() + + +@pytest.fixture +async def simulating_module_driver_patched( + simulating_module: modules.FlexStacker, +) -> AsyncGenerator[modules.AbstractModule, None]: + driver_mock = mock.MagicMock() + with mock.patch.object( + simulating_module, "_driver", driver_mock + ), mock.patch.object(simulating_module._reader, "_driver", driver_mock): + yield simulating_module + + +async def test_sim_state(simulating_module: modules.FlexStacker) -> None: + status = simulating_module.device_info + assert status["serial"] == "dummySerialFS" + assert status["model"] == "a1" + assert status["version"] == "stacker-fw" diff --git a/api/tests/opentrons/hardware_control/test_modules.py b/api/tests/opentrons/hardware_control/test_modules.py index 5df0b142e07..8906a799cfe 100644 --- a/api/tests/opentrons/hardware_control/test_modules.py +++ b/api/tests/opentrons/hardware_control/test_modules.py @@ -8,6 +8,7 @@ from opentrons.hardware_control import ExecutionManager from opentrons.hardware_control.modules import ModuleAtPort +from opentrons.hardware_control.modules.flex_stacker import FlexStacker from opentrons.hardware_control.modules.types import ( BundledFirmware, ModuleModel, @@ -16,6 +17,7 @@ HeaterShakerModuleModel, ThermocyclerModuleModel, AbsorbanceReaderModel, + FlexStackerModuleModel, ModuleType, ) from opentrons.hardware_control.modules import ( @@ -49,6 +51,9 @@ async def test_get_modules_simulating() -> None: "absorbancereader": [ SimulatingModule(serial_number="555", model="absorbanceReaderV1") ], + "flexstacker": [ + SimulatingModule(serial_number="656", model="flexStackerModuleV1") + ], } api = await hardware_control.API.build_hardware_simulator(attached_modules=mods) await asyncio.sleep(0.05) @@ -110,6 +115,7 @@ async def test_module_caching() -> None: (ThermocyclerModuleModel.THERMOCYCLER_V1, Thermocycler), (HeaterShakerModuleModel.HEATER_SHAKER_V1, HeaterShaker), (AbsorbanceReaderModel.ABSORBANCE_READER_V1, AbsorbanceReader), + (FlexStackerModuleModel.FLEX_STACKER_V1, FlexStacker), ], ) async def test_create_simulating_module( @@ -259,7 +265,28 @@ async def mod_absorbancereader() -> AsyncIterator[AbstractModule]: await absorbancereader.cleanup() -async def test_module_update_integration( +@pytest.fixture +async def mod_flexstacker() -> AsyncIterator[AbstractModule]: + usb_port = USBPort( + name="", + hub=False, + port_number=0, + device_path="/dev/ot_module_sim_flexstacker0", + ) + + flexstacker = await build_module( + port="/dev/ot_module_sim_flexstacker0", + usb_port=usb_port, + type=ModuleType.FLEX_STACKER, + simulating=True, + hw_control_loop=asyncio.get_running_loop(), + execution_manager=ExecutionManager(), + ) + yield flexstacker + await flexstacker.cleanup() + + +async def test_module_update_integration( # noqa: C901 monkeypatch: pytest.MonkeyPatch, mod_tempdeck: AbstractModule, mod_magdeck: AbstractModule, @@ -267,6 +294,7 @@ async def test_module_update_integration( mod_heatershaker: AbstractModule, mod_thermocycler_gen2: AbstractModule, mod_absorbancereader: AbstractModule, + mod_flexstacker: AbstractModule, ) -> None: from opentrons.hardware_control import modules @@ -362,6 +390,7 @@ async def mock_find_dfu_device_tc2(pid: str, expected_device_count: int) -> str: upload_via_dfu_mock.assert_called_once_with( "df11", "fake_fw_file_path", bootloader_kwargs ) + upload_via_dfu_mock.reset_mock() # Test absorbancereader update with byonoy library bootloader_kwargs["module"] = mod_absorbancereader @@ -373,6 +402,20 @@ async def mock_find_dfu_device_tc2(pid: str, expected_device_count: int) -> str: byonoy_update_firmware_mock.assert_called_once_with("fake_fw_file_path") assert not mod_absorbancereader.updating + # test flex stacker update with dfu bootloader + async def mock_find_dfu_device_fs2(pid: str, expected_device_count: int) -> str: + if expected_device_count == 2: + return "df11" + return "none" + + monkeypatch.setattr(modules.update, "find_dfu_device", mock_find_dfu_device_fs2) + + bootloader_kwargs["module"] = mod_flexstacker + await modules.update_firmware(mod_flexstacker, "fake_fw_file_path") + upload_via_dfu_mock.assert_called_once_with( + "df11", "fake_fw_file_path", bootloader_kwargs + ) + async def test_get_bundled_fw(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> None: from opentrons.hardware_control import modules @@ -392,6 +435,9 @@ async def test_get_bundled_fw(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> dummy_abs_file = Path(tmpdir) / "absorbance-96@v1.0.2.byoup" dummy_abs_file.write_text("hello") + dummy_fs_file = Path(tmpdir) / "flex-stacker@v7.0.0.bin" + dummy_fs_file.write_text("hello") + dummy_bogus_file = Path(tmpdir) / "thermoshaker@v6.6.6.bin" dummy_bogus_file.write_text("hello") @@ -414,6 +460,9 @@ async def test_get_bundled_fw(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> "absorbancereader": [ SimulatingModule(serial_number="555", model="absorbanceReaderV1") ], + "flexstacker": [ + SimulatingModule(serial_number="656", model="flexStackerModuleV1") + ], } api = await API.build_hardware_simulator(attached_modules=mods) @@ -434,6 +483,9 @@ async def test_get_bundled_fw(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> assert api.attached_modules[4].bundled_fw == BundledFirmware( version="1.0.2", path=dummy_abs_file ) + assert api.attached_modules[5].bundled_fw == BundledFirmware( + version="7.0.0", path=dummy_fs_file + ) for m in api.attached_modules: await m.cleanup() diff --git a/robot-server/robot_server/modules/module_data_mapper.py b/robot-server/robot_server/modules/module_data_mapper.py index c15d2ffdf25..6e61cff6f14 100644 --- a/robot-server/robot_server/modules/module_data_mapper.py +++ b/robot-server/robot_server/modules/module_data_mapper.py @@ -12,6 +12,9 @@ HeaterShakerStatus, SpeedStatus, AbsorbanceReaderStatus, + PlatformState, + StackerAxisState, + FlexStackerStatus, ) from opentrons.hardware_control.modules.magdeck import OFFSET_TO_LABWARE_BOTTOM from opentrons.drivers.types import ( @@ -160,12 +163,14 @@ def map_data( elif module_type == ModuleType.FLEX_STACKER: module_cls = FlexStackerModule module_data = FlexStackerModuleData( - status=live_data["status"], - plarformState=cast(str, live_data.get("platformState")), - axisStateX=cast(str, live_data.get("axisStateX")), - axisStateZ=cast(str, live_data.get("axisStateZ")), - axisStateL=cast(str, live_data.get("axisStateL")), - hopperDoorClosed=cast(bool, live_data.get("hopperDoorClosed")), + status=FlexStackerStatus(live_data["status"]), + platformState=cast( + PlatformState, live_data["data"].get("platformState") + ), + axisStateX=cast(StackerAxisState, live_data["data"].get("axisStateX")), + axisStateZ=cast(StackerAxisState, live_data["data"].get("axisStateZ")), + axisStateL=cast(StackerAxisState, live_data["data"].get("axisStateL")), + hopperDoorClosed=cast(bool, live_data["data"].get("hopperDoorClosed")), ) else: assert False, f"Invalid module type {module_type}" diff --git a/robot-server/robot_server/modules/module_models.py b/robot-server/robot_server/modules/module_models.py index 04f3547193f..44fd524b471 100644 --- a/robot-server/robot_server/modules/module_models.py +++ b/robot-server/robot_server/modules/module_models.py @@ -12,6 +12,8 @@ HeaterShakerStatus, SpeedStatus, AbsorbanceReaderStatus, + PlatformState, + StackerAxisState, ) from opentrons.drivers.types import ( ThermocyclerLidStatus, @@ -358,10 +360,16 @@ class FlexStackerModuleData(BaseModel): ..., description="Overall status of the module.", ) - plarformState: str = Field(..., description="The state of the platform.") - axisStateX: str = Field(..., description="The state of the X axis limit switches.") - axisStateZ: str = Field(..., description="The state of the Z axis limit switches.") - axisStateL: str = Field(..., description="The state of the L axis limit switches.") + platformState: PlatformState = Field(..., description="The state of the platform.") + axisStateX: StackerAxisState = Field( + ..., description="The state of the X axis limit switches." + ) + axisStateZ: StackerAxisState = Field( + ..., description="The state of the Z axis limit switches." + ) + axisStateL: StackerAxisState = Field( + ..., description="The state of the L axis limit switches." + ) hopperDoorClosed: bool = Field( ..., description="Whether the hopper door is closed." ) diff --git a/shared-data/module/schemas/3.json b/shared-data/module/schemas/3.json index 9bc24d9adef..09504bbe494 100644 --- a/shared-data/module/schemas/3.json +++ b/shared-data/module/schemas/3.json @@ -83,13 +83,14 @@ "thermocyclerModuleType", "heaterShakerModuleType", "magneticBlockType", - "absorbanceReaderType" + "absorbanceReaderType", + "flexStackerModuleType" ], "type": "string" }, "model": { "type": "string", - "pattern": "^(temperatureModule|magneticModule|thermocyclerModule|heaterShakerModule|magneticBlock|absorbanceReader)V[0-9]+$" + "pattern": "^(temperatureModule|magneticModule|thermocyclerModule|heaterShakerModule|magneticBlock|absorbanceReader|flexStackerModule)V[0-9]+$" }, "labwareOffset": { "$ref": "#/definitions/coordinates" }, "dimensions": { From f1b0e698d63952ae16e0dbf808354bd19cc0d339 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Wed, 8 Jan 2025 11:38:28 -0500 Subject: [PATCH 6/9] add get motion params (M120) and enable motors (M17) to FlexStackerDriver --- .../drivers/flex_stacker/abstract.py | 10 ++++- .../opentrons/drivers/flex_stacker/driver.py | 40 +++++++++++++++++- .../drivers/flex_stacker/simulator.py | 10 ++++- .../opentrons/drivers/flex_stacker/types.py | 10 ++++- .../hardware_control/modules/types.py | 7 +++- .../drivers/flex_stacker/test_driver.py | 42 +++++++++++++++++++ 6 files changed, 113 insertions(+), 6 deletions(-) diff --git a/api/src/opentrons/drivers/flex_stacker/abstract.py b/api/src/opentrons/drivers/flex_stacker/abstract.py index e27055edf00..9e2fbc1a449 100644 --- a/api/src/opentrons/drivers/flex_stacker/abstract.py +++ b/api/src/opentrons/drivers/flex_stacker/abstract.py @@ -1,4 +1,4 @@ -from typing import Protocol +from typing import List, Protocol from .types import ( LimitSwitchStatus, @@ -34,10 +34,18 @@ async def set_serial_number(self, sn: str) -> bool: """Set Serial Number.""" ... + async def enable_motors(self, axis: List[StackerAxis]) -> bool: + """Enables the axis motor if present, disables it otherwise.""" + ... + async def stop_motors(self) -> bool: """Stop all motor movement.""" ... + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: + """Get the motion parameters used by the given axis motor.""" + ... + async def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> bool: """Get limit switch status. diff --git a/api/src/opentrons/drivers/flex_stacker/driver.py b/api/src/opentrons/drivers/flex_stacker/driver.py index 7c4dfb3761b..3b3b17bd701 100644 --- a/api/src/opentrons/drivers/flex_stacker/driver.py +++ b/api/src/opentrons/drivers/flex_stacker/driver.py @@ -1,6 +1,6 @@ import asyncio import re -from typing import Optional +from typing import List, Optional from opentrons.drivers.command_builder import CommandBuilder from opentrons.drivers.asyncio.communication import AsyncResponseSerialConnection @@ -76,6 +76,27 @@ def parse_door_closed(cls, response: str) -> bool: raise ValueError(f"Incorrect Response for door closed: {response}") return bool(int(match.group(1))) + @classmethod + def parse_move_params(cls, response: str) -> MoveParams: + """Parse move params.""" + field_names = MoveParams.get_fields() + pattern = r"\s".join( + [ + rf"{f}:(?P<{f}>(\d*\.)?\d+)" if f != "M" else rf"{f}:(?P<{f}>[X,Z,L])" + for f in field_names + ] + ) + _RE = re.compile(f"^{GCODE.GET_MOVE_PARAMS} {pattern}$") + m = _RE.match(response) + if not m: + raise ValueError(f"Incorrect Response for move params: {response}") + return MoveParams( + axis=StackerAxis(m.group("M")), + max_speed=float(m.group("V")), + acceleration=float(m.group("A")), + max_speed_discont=float(m.group("D")), + ) + @classmethod def append_move_params( cls, command: CommandBuilder, params: MoveParams | None @@ -148,6 +169,16 @@ async def set_serial_number(self, sn: str) -> bool: raise ValueError(f"Incorrect Response for set serial number: {resp}") return True + async def enable_motors(self, axis: List[StackerAxis]) -> bool: + """Enables the axis motor if present, disables it otherwise.""" + command = GCODE.ENABLE_MOTORS.build_command() + for a in axis: + command.add_element(a.name) + resp = await self._connection.send_command(command) + if not re.match(rf"^{GCODE.ENABLE_MOTORS}$", resp): + raise ValueError(f"Incorrect Response for enable motors: {resp}") + return True + async def stop_motors(self) -> bool: """Stop all motor movement.""" resp = await self._connection.send_command(GCODE.STOP_MOTORS.build_command()) @@ -155,6 +186,13 @@ async def stop_motors(self) -> bool: raise ValueError(f"Incorrect Response for stop motors: {resp}") return True + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: + """Get the motion parameters used by the given axis motor.""" + response = await self._connection.send_command( + GCODE.GET_MOVE_PARAMS.build_command().add_element(axis.name) + ) + return self.parse_move_params(response) + async def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> bool: """Get limit switch status. diff --git a/api/src/opentrons/drivers/flex_stacker/simulator.py b/api/src/opentrons/drivers/flex_stacker/simulator.py index e1db708f279..84247c90256 100644 --- a/api/src/opentrons/drivers/flex_stacker/simulator.py +++ b/api/src/opentrons/drivers/flex_stacker/simulator.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from opentrons.util.async_helpers import ensure_yield @@ -61,11 +61,19 @@ async def set_serial_number(self, sn: str) -> bool: """Set Serial Number.""" return True + async def enable_motors(self, axis: List[StackerAxis]) -> bool: + """Enables the axis motor if present, disables it otherwise.""" + return True + @ensure_yield async def stop_motors(self) -> bool: """Stop all motor movement.""" return True + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: + """Get the motion parameters used by the given axis motor.""" + return MoveParams(axis, 1, 1, 1) + @ensure_yield async def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> bool: """Get limit switch status. diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index dbedab901a4..6ba9f5a0c5e 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -11,12 +11,14 @@ class GCODE(str, Enum): MOVE_TO_SWITCH = "G5" HOME_AXIS = "G28" STOP_MOTORS = "M0" + ENABLE_MOTORS = "M17" GET_RESET_REASON = "M114" DEVICE_INFO = "M115" GET_LIMIT_SWITCH = "M119" - SET_LED = "M200" + GET_MOVE_PARAMS = "M120" GET_PLATFORM_SENSOR = "M121" GET_DOOR_SWITCH = "M122" + SET_LED = "M200" SET_SERIAL_NUMBER = "M996" ENTER_BOOTLOADER = "dfu" @@ -148,6 +150,12 @@ def to_dict(self) -> Dict[str, bool]: class MoveParams: """Move Parameters.""" + axis: StackerAxis | None = None max_speed: float | None = None acceleration: float | None = None max_speed_discont: float | None = None + + @classmethod + def get_fields(cls) -> List[str]: + """Get parsing fields.""" + return ["M", "V", "A", "D"] diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index e9bf6dfa2ad..52aaefd2312 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -17,7 +17,11 @@ from typing_extensions import TypedDict from pathlib import Path -from opentrons.drivers.flex_stacker.types import LimitSwitchStatus, PlatformStatus, StackerAxis +from opentrons.drivers.flex_stacker.types import ( + LimitSwitchStatus, + PlatformStatus, + StackerAxis, +) from opentrons.drivers.rpi_drivers.types import USBPort if TYPE_CHECKING: @@ -289,4 +293,3 @@ def from_status( else StackerAxisState.RETRACTED ) return StackerAxisState.UNKNOWN - diff --git a/api/tests/opentrons/drivers/flex_stacker/test_driver.py b/api/tests/opentrons/drivers/flex_stacker/test_driver.py index aea2492cf9e..143826c4550 100644 --- a/api/tests/opentrons/drivers/flex_stacker/test_driver.py +++ b/api/tests/opentrons/drivers/flex_stacker/test_driver.py @@ -66,6 +66,25 @@ async def test_stop_motors(subject: FlexStackerDriver, connection: AsyncMock) -> await subject.get_device_info() +async def test_get_motion_params( + subject: FlexStackerDriver, connection: AsyncMock +) -> None: + """It should send a get motion params command.""" + connection.send_command.return_value = "M120 M:X V:200.000 A:1500.000 D:5.000" + response = await subject.get_motion_params(types.StackerAxis.X) + assert response == types.MoveParams( + axis=types.StackerAxis.X, + acceleration=1500.0, + max_speed=200.0, + max_speed_discont=5.0, + ) + + command = types.GCODE.GET_MOVE_PARAMS.build_command().add_element(types.StackerAxis.X.name) + response = await connection.send_command(command) + connection.send_command.assert_any_call(command) + connection.reset_mock() + + async def test_set_serial_number( subject: FlexStackerDriver, connection: AsyncMock ) -> None: @@ -94,6 +113,29 @@ async def test_set_serial_number( connection.reset_mock() +async def test_enable_motors(subject: FlexStackerDriver, connection: AsyncMock) -> None: + """It should send a enable motors command""" + connection.send_command.return_value = "M17" + response = await subject.enable_motors([types.StackerAxis.X]) + assert response + + move_to = types.GCODE.ENABLE_MOTORS.build_command().add_element(types.StackerAxis.X.value) + connection.send_command.assert_any_call(move_to) + connection.reset_mock() + + # Test no arg to disable all motors + response = await subject.enable_motors(list(types.StackerAxis)) + assert response + + move_to = types.GCODE.ENABLE_MOTORS.build_command() + move_to.add_element(types.StackerAxis.X.value) + move_to.add_element(types.StackerAxis.Z.value) + move_to.add_element(types.StackerAxis.L.value) + + print("MOVE TO",move_to) + connection.send_command.assert_any_call(move_to) + connection.reset_mock() + async def test_get_limit_switch( subject: FlexStackerDriver, connection: AsyncMock ) -> None: From 18bdd626c5176171c3a7cdc728f1f56cb49afc26 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Wed, 8 Jan 2025 15:31:42 -0500 Subject: [PATCH 7/9] - add enable_motors to driver and module - add set_run_current and set_ihold_current to driver - add get_motion_params to driver - add current to MoveParams to simplify interface - add STACKER_MOTION_CONFIG to keep track of default move parameters used for homing or moving an axis. - move_in_mm and move_to_limit_switch now return MoveResult - added move_axis and home_axis methods to the FlexStacker module - added close_latch and open_latch methods to the FlexStacker module --- .../drivers/flex_stacker/__init__.py | 3 +- .../drivers/flex_stacker/abstract.py | 15 +++- .../opentrons/drivers/flex_stacker/driver.py | 83 ++++++++++++++++-- .../drivers/flex_stacker/simulator.py | 19 ++-- .../opentrons/drivers/flex_stacker/types.py | 21 +++-- .../hardware_control/modules/flex_stacker.py | 87 ++++++++++++++++++- .../drivers/flex_stacker/test_driver.py | 13 ++- 7 files changed, 217 insertions(+), 24 deletions(-) diff --git a/api/src/opentrons/drivers/flex_stacker/__init__.py b/api/src/opentrons/drivers/flex_stacker/__init__.py index 4c8d50a7069..66b4cda546b 100644 --- a/api/src/opentrons/drivers/flex_stacker/__init__.py +++ b/api/src/opentrons/drivers/flex_stacker/__init__.py @@ -1,5 +1,5 @@ from .abstract import AbstractFlexStackerDriver -from .driver import FlexStackerDriver +from .driver import FlexStackerDriver, STACKER_MOTION_CONFIG from .simulator import SimulatingDriver from . import types as FlexStackerTypes @@ -8,4 +8,5 @@ "FlexStackerDriver", "SimulatingDriver", "FlexStackerTypes", + "STACKER_MOTION_CONFIG", ] diff --git a/api/src/opentrons/drivers/flex_stacker/abstract.py b/api/src/opentrons/drivers/flex_stacker/abstract.py index 9e2fbc1a449..222e6715086 100644 --- a/api/src/opentrons/drivers/flex_stacker/abstract.py +++ b/api/src/opentrons/drivers/flex_stacker/abstract.py @@ -2,6 +2,7 @@ from .types import ( LimitSwitchStatus, + MoveResult, StackerAxis, PlatformStatus, Direction, @@ -42,6 +43,14 @@ async def stop_motors(self) -> bool: """Stop all motor movement.""" ... + async def set_run_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis peak run current in amps.""" + ... + + async def set_ihold_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis hold current in amps.""" + ... + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: """Get the motion parameters used by the given axis motor.""" ... @@ -77,13 +86,13 @@ async def get_hopper_door_closed(self) -> bool: async def move_in_mm( self, axis: StackerAxis, distance: float, params: MoveParams | None = None - ) -> bool: - """Move axis.""" + ) -> MoveResult: + """Move axis by the given distance in mm.""" ... async def move_to_limit_switch( self, axis: StackerAxis, direction: Direction, params: MoveParams | None = None - ) -> bool: + ) -> MoveResult: """Move until limit switch is triggered.""" ... diff --git a/api/src/opentrons/drivers/flex_stacker/driver.py b/api/src/opentrons/drivers/flex_stacker/driver.py index 3b3b17bd701..366ea08b5f5 100644 --- a/api/src/opentrons/drivers/flex_stacker/driver.py +++ b/api/src/opentrons/drivers/flex_stacker/driver.py @@ -8,6 +8,7 @@ from .abstract import AbstractFlexStackerDriver from .types import ( GCODE, + MoveResult, StackerAxis, PlatformStatus, Direction, @@ -28,6 +29,58 @@ GCODE_ROUNDING_PRECISION = 2 +STACKER_MOTION_CONFIG = { + StackerAxis.X: { + "home": MoveParams( + StackerAxis.X, + max_speed=10.0, + acceleration=100.0, + max_speed_discont=40, + current=1.5, + ), + "move": MoveParams( + StackerAxis.X, + max_speed=200.0, + acceleration=1500.0, + max_speed_discont=40, + current=1.0, + ), + }, + StackerAxis.Z: { + "home": MoveParams( + StackerAxis.Z, + max_speed=10.0, + acceleration=100.0, + max_speed_discont=40, + current=1.5, + ), + "move": MoveParams( + StackerAxis.Z, + max_speed=200.0, + acceleration=500.0, + max_speed_discont=40, + current=1.5, + ), + }, + StackerAxis.L: { + "home": MoveParams( + StackerAxis.L, + max_speed=100.0, + acceleration=800.0, + max_speed_discont=40, + current=0.8, + ), + "move": MoveParams( + StackerAxis.L, + max_speed=100.0, + acceleration=800.0, + max_speed_discont=40, + current=0.6, + ), + }, +} + + class FlexStackerDriver(AbstractFlexStackerDriver): """FLEX Stacker driver.""" @@ -186,6 +239,24 @@ async def stop_motors(self) -> bool: raise ValueError(f"Incorrect Response for stop motors: {resp}") return True + async def set_run_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis peak run current in amps.""" + resp = await self._connection.send_command( + GCODE.SET_RUN_CURRENT.build_command().add_float(axis.name, current) + ) + if not re.match(rf"^{GCODE.SET_RUN_CURRENT}$", resp): + raise ValueError(f"Incorrect Response for set run current: {resp}") + return True + + async def set_ihold_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis hold current in amps.""" + resp = await self._connection.send_command( + GCODE.SET_IHOLD_CURRENT.build_command().add_float(axis.name, current) + ) + if not re.match(rf"^{GCODE.SET_IHOLD_CURRENT}$", resp): + raise ValueError(f"Incorrect Response for set ihold current: {resp}") + return True + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: """Get the motion parameters used by the given axis motor.""" response = await self._connection.send_command( @@ -235,8 +306,8 @@ async def get_hopper_door_closed(self) -> bool: async def move_in_mm( self, axis: StackerAxis, distance: float, params: MoveParams | None = None - ) -> bool: - """Move axis.""" + ) -> MoveResult: + """Move axis by the given distance in mm.""" command = self.append_move_params( GCODE.MOVE_TO.build_command().add_float( axis.name, distance, GCODE_ROUNDING_PRECISION @@ -246,11 +317,12 @@ async def move_in_mm( resp = await self._connection.send_command(command) if not re.match(rf"^{GCODE.MOVE_TO}$", resp): raise ValueError(f"Incorrect Response for move to: {resp}") - return True + # TODO: handle STALL_ERROR + return MoveResult.NO_ERROR async def move_to_limit_switch( self, axis: StackerAxis, direction: Direction, params: MoveParams | None = None - ) -> bool: + ) -> MoveResult: """Move until limit switch is triggered.""" command = self.append_move_params( GCODE.MOVE_TO_SWITCH.build_command().add_int(axis.name, direction.value), @@ -259,7 +331,8 @@ async def move_to_limit_switch( resp = await self._connection.send_command(command) if not re.match(rf"^{GCODE.MOVE_TO_SWITCH}$", resp): raise ValueError(f"Incorrect Response for move to switch: {resp}") - return True + # TODO: handle STALL_ERROR + return MoveResult.NO_ERROR async def home_axis(self, axis: StackerAxis, direction: Direction) -> bool: """Home axis.""" diff --git a/api/src/opentrons/drivers/flex_stacker/simulator.py b/api/src/opentrons/drivers/flex_stacker/simulator.py index 84247c90256..1ceedabf146 100644 --- a/api/src/opentrons/drivers/flex_stacker/simulator.py +++ b/api/src/opentrons/drivers/flex_stacker/simulator.py @@ -5,6 +5,7 @@ from .abstract import AbstractFlexStackerDriver from .types import ( LEDColor, + MoveResult, StackerAxis, PlatformStatus, Direction, @@ -70,6 +71,14 @@ async def stop_motors(self) -> bool: """Stop all motor movement.""" return True + async def set_run_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis peak run current in amps.""" + return True + + async def set_ihold_current(self, axis: StackerAxis, current: float) -> bool: + """Set axis hold current in amps.""" + return True + async def get_motion_params(self, axis: StackerAxis) -> MoveParams: """Get the motion parameters used by the given axis motor.""" return MoveParams(axis, 1, 1, 1) @@ -109,16 +118,16 @@ async def get_hopper_door_closed(self) -> bool: @ensure_yield async def move_in_mm( self, axis: StackerAxis, distance: float, params: MoveParams | None = None - ) -> bool: - """Move axis.""" - return True + ) -> MoveResult: + """Move axis by the given distance in mm.""" + return MoveResult.NO_ERROR @ensure_yield async def move_to_limit_switch( self, axis: StackerAxis, direction: Direction, params: MoveParams | None = None - ) -> bool: + ) -> MoveResult: """Move until limit switch is triggered.""" - return True + return MoveResult.NO_ERROR async def home_axis(self, axis: StackerAxis, direction: Direction) -> bool: """Home axis.""" diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index 6ba9f5a0c5e..2e5befd3085 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -1,6 +1,6 @@ from enum import Enum from dataclasses import dataclass, fields -from typing import List, Dict +from typing import List, Dict, Optional from opentrons.drivers.command_builder import CommandBuilder @@ -20,6 +20,8 @@ class GCODE(str, Enum): GET_DOOR_SWITCH = "M122" SET_LED = "M200" SET_SERIAL_NUMBER = "M996" + SET_RUN_CURRENT = "M906" + SET_IHOLD_CURRENT = "M907" ENTER_BOOTLOADER = "dfu" def build_command(self) -> CommandBuilder: @@ -150,12 +152,21 @@ def to_dict(self) -> Dict[str, bool]: class MoveParams: """Move Parameters.""" - axis: StackerAxis | None = None - max_speed: float | None = None - acceleration: float | None = None - max_speed_discont: float | None = None + axis: Optional[StackerAxis] = None + max_speed: Optional[float] = None + acceleration: Optional[float] = None + max_speed_discont: Optional[float] = None + current: Optional[float] = 0 @classmethod def get_fields(cls) -> List[str]: """Get parsing fields.""" return ["M", "V", "A", "D"] + + +class MoveResult(str, Enum): + """The result of a move command.""" + + NO_ERROR = "ok" + STALL_ERROR = "stall" + UNKNOWN_ERROR = "unknown" diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py index d66a754627e..380e671d26c 100644 --- a/api/src/opentrons/hardware_control/modules/flex_stacker.py +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -5,10 +5,16 @@ from typing import Dict, Optional, Mapping from opentrons.drivers.flex_stacker.types import ( + Direction, + MoveParams, + MoveResult, StackerAxis, ) from opentrons.drivers.rpi_drivers.types import USBPort -from opentrons.drivers.flex_stacker.driver import FlexStackerDriver +from opentrons.drivers.flex_stacker.driver import ( + STACKER_MOTION_CONFIG, + FlexStackerDriver, +) from opentrons.drivers.flex_stacker.abstract import AbstractFlexStackerDriver from opentrons.drivers.flex_stacker.simulator import SimulatingDriver from opentrons.hardware_control.execution_manager import ExecutionManager @@ -198,6 +204,74 @@ def bootloader(self) -> UploadFunction: async def deactivate(self, must_be_running: bool = True) -> None: await self._driver.stop_motors() + async def move_axis( + self, + axis: StackerAxis, + direction: Direction, + distance: float, + speed: Optional[float] = None, + acceleration: Optional[float] = None, + current: Optional[float] = None, + ) -> bool: + """Move the axis in a direction by the given distance in mm.""" + motion_params = STACKER_MOTION_CONFIG[axis]["move"] + await self._driver.set_run_current(axis, current or motion_params.current or 0) + if any([speed, acceleration]): + motion_params.max_speed = speed or motion_params.max_speed + motion_params.acceleration = acceleration or motion_params.acceleration + distance = direction.distance(distance) + success = await self._driver.move_in_mm(axis, distance, params=motion_params) + # TODO: This can return a stall, handle that here + return success == MoveResult.NO_ERROR + + async def home_axis( + self, + axis: StackerAxis, + direction: Direction, + speed: Optional[float] = None, + acceleration: Optional[float] = None, + current: Optional[float] = None, + ) -> bool: + motion_params = STACKER_MOTION_CONFIG[axis]["home"] + await self._driver.set_run_current(axis, current or motion_params.current or 0) + # Set the max hold current for the Z axis + if axis == StackerAxis.Z: + await self._driver.set_ihold_current(axis, 1.8) + if any([speed, acceleration]): + motion_params.max_speed = speed or motion_params.max_speed + motion_params.acceleration = acceleration or motion_params.acceleration + success = await self._driver.move_to_limit_switch( + axis=axis, direction=direction, params=motion_params + ) + # TODO: This can return a stall, handle that here + return success == MoveResult.NO_ERROR + + async def close_latch( + self, + velocity: Optional[float] = None, + acceleration: Optional[float] = None, + ) -> bool: + """Close the latch, dropping any labware its holding.""" + motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"] + speed = velocity or motion_params.max_speed + accel = acceleration or motion_params.acceleration + return await self.home_axis( + StackerAxis.L, Direction.RETRACT, speed=speed, acceleration=accel + ) + + async def open_latch( + self, + velocity: Optional[float] = None, + acceleration: Optional[float] = None, + ) -> bool: + """Open the latch.""" + motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"] + speed = velocity or motion_params.max_speed + accel = acceleration or motion_params.acceleration + return await self.home_axis( + StackerAxis.L, Direction.EXTENT, speed=speed, acceleration=accel + ) + class FlexStackerReader(Reader): error: Optional[str] @@ -210,11 +284,16 @@ def __init__(self, driver: AbstractFlexStackerDriver) -> None: } self.platform_state = PlatformState.UNKNOWN self.hopper_door_closed = False + self.motion_params = {axis: MoveParams(axis=axis) for axis in StackerAxis} + self.get_config = True async def read(self) -> None: await self.get_limit_switch_status() await self.get_platform_sensor_state() await self.get_door_closed() + if self.get_config: + await self.get_motion_parameters() + self.get_config = False self._set_error(None) async def get_limit_switch_status(self) -> None: @@ -226,6 +305,12 @@ async def get_limit_switch_status(self) -> None: StackerAxis.L: StackerAxisState.from_status(status, StackerAxis.L), } + async def get_motion_parameters(self) -> None: + """Get the motion parameters used by the axis motors.""" + self.move_params = { + axis: self._driver.get_motion_params(axis) for axis in StackerAxis + } + async def get_platform_sensor_state(self) -> None: """Get the platform state.""" status = await self._driver.get_platform_status() diff --git a/api/tests/opentrons/drivers/flex_stacker/test_driver.py b/api/tests/opentrons/drivers/flex_stacker/test_driver.py index 143826c4550..1de13c569cb 100644 --- a/api/tests/opentrons/drivers/flex_stacker/test_driver.py +++ b/api/tests/opentrons/drivers/flex_stacker/test_driver.py @@ -78,8 +78,10 @@ async def test_get_motion_params( max_speed=200.0, max_speed_discont=5.0, ) - - command = types.GCODE.GET_MOVE_PARAMS.build_command().add_element(types.StackerAxis.X.name) + + command = types.GCODE.GET_MOVE_PARAMS.build_command().add_element( + types.StackerAxis.X.name + ) response = await connection.send_command(command) connection.send_command.assert_any_call(command) connection.reset_mock() @@ -119,7 +121,9 @@ async def test_enable_motors(subject: FlexStackerDriver, connection: AsyncMock) response = await subject.enable_motors([types.StackerAxis.X]) assert response - move_to = types.GCODE.ENABLE_MOTORS.build_command().add_element(types.StackerAxis.X.value) + move_to = types.GCODE.ENABLE_MOTORS.build_command().add_element( + types.StackerAxis.X.value + ) connection.send_command.assert_any_call(move_to) connection.reset_mock() @@ -132,10 +136,11 @@ async def test_enable_motors(subject: FlexStackerDriver, connection: AsyncMock) move_to.add_element(types.StackerAxis.Z.value) move_to.add_element(types.StackerAxis.L.value) - print("MOVE TO",move_to) + print("MOVE TO", move_to) connection.send_command.assert_any_call(move_to) connection.reset_mock() + async def test_get_limit_switch( subject: FlexStackerDriver, connection: AsyncMock ) -> None: From a6703e24a50ec820cad2dc278167feee2c08e003 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Thu, 9 Jan 2025 13:36:35 -0500 Subject: [PATCH 8/9] - remove defaults from LimitSwitchStaus and PlatformStatus - added HopperDoorState and LatchState to FlexStacker module - replaced hopperDoorClosed with hopperDoorState in live data - replaced axisStateL with LatchState in live data - use move_axis instead of home_axis for open_latch and close_latch - set find_dfu_device expected_device_count to 3 - cleanup --- .../opentrons/drivers/flex_stacker/types.py | 14 ++-- .../hardware_control/modules/flex_stacker.py | 74 +++++++++++++++---- .../hardware_control/modules/types.py | 45 +++++++---- .../modules/module_data_mapper.py | 7 +- .../robot_server/modules/module_models.py | 12 ++- 5 files changed, 108 insertions(+), 44 deletions(-) diff --git a/api/src/opentrons/drivers/flex_stacker/types.py b/api/src/opentrons/drivers/flex_stacker/types.py index 2e5befd3085..9f8e8825b93 100644 --- a/api/src/opentrons/drivers/flex_stacker/types.py +++ b/api/src/opentrons/drivers/flex_stacker/types.py @@ -102,11 +102,11 @@ def distance(self, distance: float) -> float: class LimitSwitchStatus: """Stacker Limit Switch Statuses.""" - XE: bool = False - XR: bool = False - ZE: bool = False - ZR: bool = False - LR: bool = False + XE: bool + XR: bool + ZE: bool + ZR: bool + LR: bool @classmethod def get_fields(cls) -> List[str]: @@ -128,8 +128,8 @@ def get(self, axis: StackerAxis, direction: Direction) -> bool: class PlatformStatus: """Stacker Platform Statuses.""" - E: bool = False - R: bool = False + E: bool + R: bool @classmethod def get_fields(cls) -> List[str]: diff --git a/api/src/opentrons/hardware_control/modules/flex_stacker.py b/api/src/opentrons/hardware_control/modules/flex_stacker.py index 380e671d26c..5ded3b391b3 100644 --- a/api/src/opentrons/hardware_control/modules/flex_stacker.py +++ b/api/src/opentrons/hardware_control/modules/flex_stacker.py @@ -22,6 +22,8 @@ from opentrons.hardware_control.modules import mod_abc, update from opentrons.hardware_control.modules.types import ( FlexStackerStatus, + HopperDoorState, + LatchState, ModuleDisconnectedCallback, ModuleType, PlatformState, @@ -37,6 +39,9 @@ DFU_PID = "df11" +# Distance in mm the latch can travel to open/close +LATCH_TRAVEL = 25.0 + class FlexStacker(mod_abc.AbstractModule): """Hardware control interface for an attached Flex-Stacker module.""" @@ -125,6 +130,7 @@ def __init__( self._driver = driver self._reader = reader self._poller = poller + self._stacker_status = FlexStackerStatus.IDLE async def cleanup(self) -> None: """Stop the poller task""" @@ -148,21 +154,26 @@ def _model_from_revision(revision: Optional[str]) -> str: def model(self) -> str: return self._model_from_revision(self._device_info.get("model")) + @property + def latch_state(self) -> LatchState: + """The state of the latch.""" + return LatchState.from_state(self.limit_switch_status[StackerAxis.L]) + @property def platform_state(self) -> PlatformState: """The state of the platform.""" return self._reader.platform_state + @property + def hopper_door_state(self) -> HopperDoorState: + """The status of the hopper door.""" + return HopperDoorState.from_state(self._reader.hopper_door_closed) + @property def limit_switch_status(self) -> Dict[StackerAxis, StackerAxisState]: """The status of the Limit switches.""" return self._reader.limit_switch_status - @property - def hopper_door_closed(self) -> bool: - """The status of the hopper door.""" - return self._reader.hopper_door_closed - @property def device_info(self) -> Mapping[str, str]: return self._device_info @@ -170,8 +181,7 @@ def device_info(self) -> Mapping[str, str]: @property def status(self) -> FlexStackerStatus: """Module status or error state details.""" - # TODO: Implement getting device status from the stacker - return FlexStackerStatus.IDLE + return self._stacker_status @property def is_simulated(self) -> bool: @@ -182,11 +192,11 @@ def live_data(self) -> LiveData: return { "status": self.status.value, "data": { + "latchState": self.latch_state.value, "platformState": self.platform_state.value, + "hopperDoorState": self.hopper_door_state.value, "axisStateX": self.limit_switch_status[StackerAxis.X].value, "axisStateZ": self.limit_switch_status[StackerAxis.Z].value, - "axisStateL": self.limit_switch_status[StackerAxis.L].value, - "hopperDoorClosed": self.hopper_door_closed, "errorDetails": self._reader.error, }, } @@ -195,7 +205,8 @@ async def prep_for_update(self) -> str: await self._poller.stop() await self._driver.stop_motors() await self._driver.enter_programming_mode() - dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=2) + # flex stacker has three unique "devices" over DFU + dfu_info = await update.find_dfu_device(pid=DFU_PID, expected_device_count=3) return dfu_info def bootloader(self) -> UploadFunction: @@ -252,11 +263,24 @@ async def close_latch( acceleration: Optional[float] = None, ) -> bool: """Close the latch, dropping any labware its holding.""" + # Dont move the latch if its already closed. + if self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED: + return True motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"] speed = velocity or motion_params.max_speed accel = acceleration or motion_params.acceleration - return await self.home_axis( - StackerAxis.L, Direction.RETRACT, speed=speed, acceleration=accel + success = await self.move_axis( + StackerAxis.L, + Direction.RETRACT, + distance=LATCH_TRAVEL, + speed=speed, + acceleration=accel, + ) + # Check that the latch is closed. + await self._reader.get_limit_switch_status() + return ( + success + and self.limit_switch_status[StackerAxis.L] == StackerAxisState.EXTENDED ) async def open_latch( @@ -265,12 +289,34 @@ async def open_latch( acceleration: Optional[float] = None, ) -> bool: """Open the latch.""" + # Dont move the latch if its already opened. + if self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED: + return True motion_params = STACKER_MOTION_CONFIG[StackerAxis.L]["move"] speed = velocity or motion_params.max_speed accel = acceleration or motion_params.acceleration - return await self.home_axis( - StackerAxis.L, Direction.EXTENT, speed=speed, acceleration=accel + success = await self.move_axis( + StackerAxis.L, + Direction.EXTENT, + distance=LATCH_TRAVEL, + speed=speed, + acceleration=accel, ) + # Check that the latch is opened. + await self._reader.get_limit_switch_status() + return ( + success + and self.limit_switch_status[StackerAxis.L] == StackerAxisState.RETRACTED + ) + + # NOTE: We are defining the interface, will implement in seperate pr. + async def dispense(self) -> bool: + """Dispenses the next labware in the stacker.""" + return True + + async def store(self) -> bool: + """Stores a labware in the stacker.""" + return True class FlexStackerReader(Reader): diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index 52aaefd2312..a43c7046a6b 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -246,7 +246,8 @@ class LidStatus(str, Enum): class FlexStackerStatus(str, Enum): IDLE = "idle" - RUNNING = "running" + DISPENSING = "dispensing" + STORING = "storing" ERROR = "error" @@ -259,10 +260,10 @@ class PlatformState(str, Enum): def from_status(cls, status: PlatformStatus) -> "PlatformState": """Get the state from the platform status.""" if status.E and not status.R: - return PlatformState.EXTENDED + return cls.EXTENDED if status.R and not status.E: - return PlatformState.RETRACTED - return PlatformState.UNKNOWN + return cls.RETRACTED + return cls.UNKNOWN class StackerAxisState(str, Enum): @@ -278,18 +279,34 @@ def from_status( match axis: case StackerAxis.X: if status.XE and not status.XR: - return StackerAxisState.EXTENDED + return cls.EXTENDED if status.XR and not status.XE: - return StackerAxisState.RETRACTED + return cls.RETRACTED case StackerAxis.Z: if status.ZE and not status.ZR: - return StackerAxisState.EXTENDED + return cls.EXTENDED if status.ZR and not status.ZE: - return StackerAxisState.RETRACTED + return cls.RETRACTED case StackerAxis.L: - return ( - StackerAxisState.EXTENDED - if status.LR - else StackerAxisState.RETRACTED - ) - return StackerAxisState.UNKNOWN + return cls.EXTENDED if status.LR else cls.RETRACTED + return cls.UNKNOWN + + +class LatchState(str, Enum): + CLOSED = "closed" + OPENED = "opened" + + @classmethod + def from_state(cls, state: StackerAxisState) -> "LatchState": + """Get the latch state from the axis state.""" + return cls.CLOSED if state == StackerAxisState.EXTENDED else cls.OPENED + + +class HopperDoorState(str, Enum): + CLOSED = "closed" + OPENED = "opened" + + @classmethod + def from_state(cls, state: bool) -> "HopperDoorState": + """Get the hopper door state from the door state boolean.""" + return cls.CLOSED if state else cls.OPENED diff --git a/robot-server/robot_server/modules/module_data_mapper.py b/robot-server/robot_server/modules/module_data_mapper.py index 6e61cff6f14..b425c547c22 100644 --- a/robot-server/robot_server/modules/module_data_mapper.py +++ b/robot-server/robot_server/modules/module_data_mapper.py @@ -25,6 +25,7 @@ ) from opentrons.drivers.rpi_drivers.types import USBPort as HardwareUSBPort +from opentrons.hardware_control.modules.types import HopperDoorState, LatchState from opentrons.protocol_engine import ModuleModel, DeckType from .module_identifier import ModuleIdentity @@ -164,13 +165,15 @@ def map_data( module_cls = FlexStackerModule module_data = FlexStackerModuleData( status=FlexStackerStatus(live_data["status"]), + latchState=cast(LatchState, live_data["data"].get("latchState")), platformState=cast( PlatformState, live_data["data"].get("platformState") ), + hopperDoorState=cast( + HopperDoorState, live_data["data"].get("hopperDoorState") + ), axisStateX=cast(StackerAxisState, live_data["data"].get("axisStateX")), axisStateZ=cast(StackerAxisState, live_data["data"].get("axisStateZ")), - axisStateL=cast(StackerAxisState, live_data["data"].get("axisStateL")), - hopperDoorClosed=cast(bool, live_data["data"].get("hopperDoorClosed")), ) else: assert False, f"Invalid module type {module_type}" diff --git a/robot-server/robot_server/modules/module_models.py b/robot-server/robot_server/modules/module_models.py index 44fd524b471..fb56e9e3fbf 100644 --- a/robot-server/robot_server/modules/module_models.py +++ b/robot-server/robot_server/modules/module_models.py @@ -21,6 +21,7 @@ AbsorbanceReaderLidStatus, AbsorbanceReaderPlatePresence, ) +from opentrons.hardware_control.modules.types import HopperDoorState, LatchState from opentrons.protocol_engine import ModuleModel from opentrons.protocol_engine.types import Vec3f @@ -355,24 +356,21 @@ class AbsorbanceReaderModule( class FlexStackerModuleData(BaseModel): """Live data from a Flex Stacker module.""" - # TODO: add rest of data status: str = Field( ..., description="Overall status of the module.", ) + latchState: LatchState = Field(..., description="The state of the labware latch.") platformState: PlatformState = Field(..., description="The state of the platform.") + hopperDoorState: HopperDoorState = Field( + ..., description="The state of the hopper door." + ) axisStateX: StackerAxisState = Field( ..., description="The state of the X axis limit switches." ) axisStateZ: StackerAxisState = Field( ..., description="The state of the Z axis limit switches." ) - axisStateL: StackerAxisState = Field( - ..., description="The state of the L axis limit switches." - ) - hopperDoorClosed: bool = Field( - ..., description="Whether the hopper door is closed." - ) class FlexStackerModule( From dc016e3c96beae6bafc8145c90c7e7bcbd6ad8b3 Mon Sep 17 00:00:00 2001 From: vegano1 Date: Thu, 9 Jan 2025 13:52:54 -0500 Subject: [PATCH 9/9] fix unit tests --- .../hardware_control/test_modules.py | 2 +- .../module/definitions/3/flexStackerV1.json | 184 ------------------ 2 files changed, 1 insertion(+), 185 deletions(-) delete mode 100644 shared-data/module/definitions/3/flexStackerV1.json diff --git a/api/tests/opentrons/hardware_control/test_modules.py b/api/tests/opentrons/hardware_control/test_modules.py index 8906a799cfe..f5fa0e69336 100644 --- a/api/tests/opentrons/hardware_control/test_modules.py +++ b/api/tests/opentrons/hardware_control/test_modules.py @@ -404,7 +404,7 @@ async def mock_find_dfu_device_tc2(pid: str, expected_device_count: int) -> str: # test flex stacker update with dfu bootloader async def mock_find_dfu_device_fs2(pid: str, expected_device_count: int) -> str: - if expected_device_count == 2: + if expected_device_count == 3: return "df11" return "none" diff --git a/shared-data/module/definitions/3/flexStackerV1.json b/shared-data/module/definitions/3/flexStackerV1.json deleted file mode 100644 index 8ded657f59b..00000000000 --- a/shared-data/module/definitions/3/flexStackerV1.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "$otSharedSchema": "module/schemas/3", - "moduleType": "flexStackerType", - "model": "flexStackerV1", - "labwareOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "dimensions": { - "bareOverallHeight": 18.5, - "overLabwareHeight": 0.0, - "lidHeight": 60.0, - "xDimension": 155.3, - "yDimension": 95.5, - "labwareInterfaceXDimension": 127.8, - "labwareInterfaceYDimension": 85.5 - }, - "cornerOffsetFromSlot": { - "x": -4.875, - "y": -4.875, - "z": 0 - }, - "calibrationPoint": { - "x": 14.4, - "y": 64.93, - "z": 97.8 - }, - "displayName": "FLEX Stacker Module GEN1", - "quirks": [], - "slotTransforms": { - "ot2_standard": {}, - "ot2_short_trash": {}, - "ot3_standard": {} - }, - "compatibleWith": [], - "incompatibleWithDecks": ["ot2_standard", "ot2_short_trash"], - "twoDimensionalRendering": { - "name": "svg", - "type": "element", - "value": "", - "attributes": { - "version": "1.1", - "id": "flexStacker", - "xmlns": "http://www.w3.org/2000/svg", - "xmlns:xlink": "http://www.w3.org/1999/xlink", - "width": "134", - "height": "96", - "viewBox": "0 0 134 96", - "style": "enable-background:new 0 0 148.6 91.8;", - "xml:space": "preserve" - }, - "children": [ - { - "name": "defs", - "type": "element", - "value": "", - "attributes": {}, - "children": [ - { - "name": "style", - "type": "element", - "value": "", - "attributes": {}, - "children": [ - { - "name": "", - "type": "text", - "value": "\n\t\t.cls-1{fill:none;}.cls-1,.cls-2{stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:.71px;}.cls-2{fill:#e6e6e6;}", - "attributes": {}, - "children": [] - } - ] - } - ] - }, - { - "name": "g", - "type": "element", - "value": "", - "attributes": { - "id": "Foot_Print" - }, - "children": [ - { - "name": "rect", - "type": "element", - "value": "", - "attributes": { - "class": "cls-2", - "x": ".35", - "y": ".36", - "width": "136", - "height": "94", - "rx": "5", - "ry": "5" - }, - "children": [] - } - ] - }, - { - "name": "g", - "type": "element", - "value": "", - "attributes": { - "id": "Slot_Clips" - }, - "children": [ - { - "name": "path", - "type": "element", - "value": "", - "attributes": { - "class": "cls-1", - "d": "m17.37.36v2.6m-2-.93V.36m2,2.6c0-.52-.9-.93-2-.93m104,.93V.36m2,1.67c-1.1,0-2,.42-2,.93m2-2.6v1.67m-2,92.33v-2.6m2,.93v1.67m-2-2.6c0,.52.9.93,2,.93M5.37,2.03v1.99m.18.12c-.06-.05-.12-.08-.18-.12m.18.12c.18.14.4.22.63.22m11.19,0H6.18m11.19-1.4v1.4M5.37,2.03h10M6.18,90.35h11.19m-11.19,0c-.23,0-.45.08-.63.22m-.18.12c.06-.03.13-.07.18-.12m-.18,2.11v-1.99m10,1.99H5.37m10,0c1.1,0,2-.42,2-.93m0-1.4v1.4m-15.33-2.4h1.99m.12-.18c-.05.06-.08.12-.12.18m.12-.18c.14-.18.22-.4.22-.63m0-11.19v11.19m-1.4-11.19h1.4m-1.4,0c-.52,0-.93.9-.93,2m0,10v-10m130.33,9.19v-11.19m0,11.19c0,.23.08.45.22.63m.12.18c-.03-.06-.07-.13-.12-.18m2.11.18h-1.99m1.99-10v10m0-10c0-1.1-.42-2-.93-2m-1.4,0h1.4m-2.4,15.33v-1.99m-.18-.12c.06.05.12.08.18.12m-.18-.12c-.18-.14-.4-.22-.63-.22m-11.19,0h11.19m-11.19,1.4v-1.4m12,2.33h-10m9.19-88.33h-11.19m11.19,0c.23,0,.45-.08.63-.22m.18-.12c-.06.03-.13.07-.18.12m.18-2.11v1.99m-10-1.99h10m-12,2.33v-1.4m15.33,2.4h-1.99m-.12.18c.05-.06.08-.12.12-.18m-.12.18c-.14.18-.22.4-.22.63m0,11.19V6.18m1.4,11.19h-1.4m1.4,0c.52,0,.93-.9.93-2m0-10v10M4.37,6.18v11.19m0-11.19c0-.23-.08-.45-.22-.63m-.12-.18c.03.06.07.13.12.18m-2.11-.19h1.99m-1.99,10V5.36m0,10c0,1.1.42,2,.93,2m1.4,0h-1.4m133.4,0h-2.6m.93-2h1.67m-2.6,62h2.6m0,2h-1.67M2.97,17.37H.36m0-2h1.67m15.33,76.4v2.6m-2,0v-1.67M.36,77.35h2.6m-.93,2H.36M134.68,5.36c0-1.84-1.49-3.33-3.33-3.33m0,1.99c.81-.44,1.78.53,1.34,1.34M2.04,89.37c0,1.84,1.49,3.33,3.33,3.33M5.36,2.04c-1.84,0-3.33,1.49-3.33,3.33m129.33,87.33c1.84,0,3.33-1.49,3.33-3.33m-1.99,0c.44.81-.53,1.78-1.34,1.34m-126,0c-.81.44-1.78-.53-1.34-1.34M4.03,5.37c-.44-.81.53-1.78,1.34-1.34m112,90.34v-4m0,0h2M4.37,19.37H.36m4-2v2M.36,75.35h4m0,0v2M136.36,19.37h-4m0,0v-2m0,58h4m-4,2v-2M117.35,4.37V.36m2,4h-2M19.37.36v4m0,0h-2m2,86v4m-2-4h2" - }, - "children": [] - } - ] - }, - { - "name": "g", - "type": "element", - "value": "", - "attributes": { - "id": "Module_Title" - }, - "children": [ - { - "name": "path", - "type": "element", - "value": "", - "attributes": { - "class": "cls-1", - "d": "m73.92,6.86h-.49m0,.5h.49m-.54-.04s.02.04.04.04m2.44-2.71s.04,0,.04-.01m-.36-.03c.11,0,.24.02.32.04m-.78.68c0-.55.1-.72.46-.72m0,1.48c-.35,0-.46-.17-.46-.77m.79.73c-.08.02-.21.04-.33.04m.37-.03s-.01-.02-.04-.02m.05.38v-.36m-.02.39s.02,0,.02-.03m-.48.09c.2,0,.37-.02.46-.06m-1.4-1.13c0,.73.24,1.19.94,1.19m0-2.35c-.69,0-.94.41-.94,1.16m1.42-1.1c-.12-.04-.3-.06-.47-.06m1.83,2.92h.91m-.95-.04s.02.04.04.04m1.48-1.41c.21-.08.41-.25.41-.7m-.06,1.39c0-.39-.16-.5-.32-.6m-.6,1.32c.57,0,.92-.17.92-.72m1.44-2.16c-.09-.02-.19-.04-.32-.04m-24.18,1.46c0,1.45-1.04,2.65-2.42,2.89m-4.39-2.89c0,1.9,1.37,3.48,3.18,3.8m.69-7.67c-2.14,0-3.87,1.73-3.87,3.87m5.13.3h0m31.38-.73v-.95m.07.99c-.06,0-.07-.01-.07-.04m1.4-.95s.01-.05-.05-.05m-.72,1.17c.18-.25.67-.98.77-1.13m-1.41,1.41s.03-.04.08-.04m-1.08.43s-.01-.02-.04-.02m.05.38l-.02-.36m.01-1.83c-.12-.04-.3-.06-.47-.06m-1.67,2.13c.36-.33.35-1.57,0-1.9m-1.45,1.9c.23.3,1.22.3,1.45,0m-22.12-.81l.47,1.56m-.55-1.56s.02-.06.04-.06m-.49,1.61l.45-1.56m-.5,1.6s.04-.02.05-.04m-.8.04h.75m-.79-.04s.02.04.04.04m-.04-2.84v2.79m.04-2.84s-.04.02-.04.04m.52-.04h-.47m.52.04s-.02-.04-.04-.04m0,2.19l.03-2.15m0,2.22s-.02-.01-.02-.06m.07,0s-.02.06-.04.06m.47-1.54l-.43,1.49m.52-1.55s-.06,0-.08.06m.48-.06h-.4m.98,1.61s-.03-.02-.04-.06m.06,0s0,.05-.02.05m0-2.22l.02,2.17m.02-2.21s-.04.02-.04.04m.52-.04h-.48m.52.04s-.02-.04-.04-.04m.04,2.84v-2.79m-.04,2.84s.04-.02.04-.04m-.75.04h.71m-.77-.04s.03.04.06.04m2.52-1.33c0,.25-.12.31-.45.31m.45-.48v.17m-.02-.19l.02.02m-.52-.09l.5.06m-.23-1.4c-.76,0-.99.14-.99.63m1.73-.55c-.15-.05-.45-.08-.74-.08m.8.14s-.01-.05-.06-.06m.06,1.6v-1.53m-1.58,2.13c.16.05.46.08.74.08m-.77-.14s0,.05.02.06m0-.38l-.03.32m.09-.35s-.06,0-.06.02m.59.02c-.15,0-.39-.02-.53-.04m3.92.4l.04-.04m-.82.13c.24,0,.62-.02.79-.09m-1.88-1.04c0,.8.29,1.13,1.09,1.13m-.11-2.17c-.72,0-.98.23-.98,1.03m1.34-1.01c-.11-.01-.23-.02-.36-.02m.39-.01s0,.03-.02.03m-.51-.5c.5,0,.53.13.53.47m-1.19-.39c.16-.04.45-.08.66-.08m.06-.42c-.19,0-.62.05-.78.11m1.8.7c0-.67-.4-.81-1.02-.81m3.45.76h-.47m.51.04s-.02-.04-.04-.04m.04,1.5v-1.46m-.94,2.26c.74,0,.94-.19.94-.81m-1.82.72c.23.04.61.09.87.09m-.92-.13s.02.04.04.04m-.04-2.17v2.13m.04-2.17s-.04.02-.04.04m.51-.04h-.47m.51.04s-.02-.04-.04-.04m.04,1.81v-1.77m.04,1.81s-.04-.02-.04-.04m.34.07c-.13,0-.23,0-.3-.02m.71-.38c0,.32-.07.41-.41.41m.41-1.84v1.43m.04-1.47s-.04.02-.04.04m2.9.08s0-.03-.03-.04m0,.37l.03-.33m-.08.35l.05-.02m-.64-.08c.22,0,.47.06.6.1m-1.21.47c0-.46.22-.57.61-.57m-.59.59l-.02-.02m1.22.02h-1.2m1.31.36c0-.29-.01-.36-.11-.36m-.77,1.34c.67,0,.88-.37.88-.98m-1.89-.2c0,.76.32,1.18,1.01,1.18m.07-2.35c-.83,0-1.09.45-1.09,1.17m1.81-1.04c-.17-.08-.43-.12-.72-.12m2.37.38v-.29m-.04.33s.04,0,.04-.04m-.17.04h.13m-.31.18c0-.14.05-.18.18-.18m-.18,1.43v-1.26m.42,1.32s-.02-.04-.04-.04m.04.39v-.34m-.04.38s.04-.02.04-.04m-.4.04h.35m-.38.02l.02-.02m-.03.55v-.53m-.04.57s.04-.02.04-.04m-.51.04h.47m-.51-.04s.02.04.04.04m-.04-.57v.53m-.02-.55l.02.02m-.32-.02h.29m-.34-.04s.02.04.04.04m-.04-.38v.34m.04-.38s-.04.02-.04.04m.34-.05h-.29m.32-.02l-.02.02m.02-1.41v1.39m.51-1.85c-.34,0-.51.16-.51.47m.85-.43c-.09-.02-.22-.04-.34-.04m.83,2.72v.42m-.77-1.27l-.02-.02m1.35.41v-2.18m-.55,2.18s.02.04.04.04m0-2.26s-.04.02-.04.04m-8.66-.33s.01.03.04.02m-.09-.36l.05.33m-.02-.36l-.03.04m19.59.58c-.69,0-.94.41-.94,1.16m2.41-1.08s-.02-.04-.04-.04m-23.41.59c0,.43.1.62.72.7m24.09-1.29h-.54m-23.33,2.31c.53,0,.84-.18.84-.67m21.74-.21l.08.02m-24.74.77l-.47-1.48m-7.07,3.59l.16.91m19.16-5.17s-.02-.05-.04-.06m9.71.19c-.25-.31-1.22-.31-1.46,0m-.95,2.92s.04-.02.04-.05m-.02-3.1c-.35,0-.53.12-.53.52m-19.25.75s.03.01.04.06m26.08-1.28s-.04,0-.06.04m-7.73,1.52s-.05-.04-.05-.06m-18.04-.78c-.02-.06-.02-.07-.07-.07m15.29-.23l.02-.36m-23.52,5.17l.17-.91m32.19-4.25l-.02-.04m-.01.4l.02-.36m-.07.38s.04,0,.04-.01m-.36-.03c.11,0,.24.02.32.04m-.78.68c0-.55.1-.72.46-.72m0,1.48c-.35,0-.46-.17-.46-.77m.79.73c-.08.02-.21.04-.33.04m.36.36s.02,0,.02-.03m.98.85v-1.61m-.04,1.65s.04-.02.04-.04m-.5.04h.46m-.5-.04s.02.04.04.04m-.04-3.07v3.03m.04-3.07s-.04.02-.04.04m.5-.04h-.46m-11.84.04v2.18m12.52-1.24s-.06.04-.11.04m.54.2s.01-.04.04-.06m-.01.11l-.02-.05m.77.99c-.07-.1-.49-.64-.75-.94m.7.98s.06-.01.05-.04m-.61.04h.56m-.62-.04s.03.04.06.04m-.61-.83c.18.23.43.6.54.79m-6.2-1.68c0-.13.05-.18.18-.18m-4.49-.32l-.02-.04m-22.82,3.56l1.26-1.86m-2.53,0h0m-.29-.89c0,.33.11.64.29.89m1.26-2.44c-.85,0-1.55.69-1.55,1.55m3.09,0c0-.85-.69-1.55-1.55-1.55m1.26,2.44c.18-.25.28-.56.28-.89m-1.55-2.34c1.62,0,2.94,1.32,2.94,2.94m17.89,1.72s.04-.02.04-.04m7.19-2.91c-.36.32-.38,1.58,0,1.9m-24.19-.66c0-2.14-1.73-3.87-3.87-3.87m27.49,2.49s-.02-.05-.04-.05m.05.33v-.28m-.04.32s.04,0,.04-.04m-.35,2.73v-2.51m.18-.18h.13m-.82,2.74h.47m3.13-1.99c0,.73.24,1.19.94,1.19m-7.04-2.26v2.79m9.28-2.8c-.14.25-.44.72-.59.95m-12.49,1.65s-.04.02-.04.04m4.92-2.68h-1.04m1.89.77c0-.58-.43-.77-.85-.77m.42,1.5s0-.03.02-.04m-12.23.71v-2.12m-13.94,4.27c-1.38-.25-2.42-1.45-2.42-2.89m23.8,1.26s-.02-.04-.04-.04m-22.09-.92l1.26,1.86m20.81-3.57h-.47m.51.04s-.02-.04-.04-.04m0,2.26s.04-.02.04-.04m-.51.04h.47m10.36.04c.2,0,.37-.02.46-.06m-5.04.82s.02.05.04.05m-.04-2.64v2.59m-29.53-1.64c0-1.62,1.32-2.94,2.94-2.94m20.87,4.62v-.42m3.31-2.68s-.04.02-.04.04m-4.25,1.79h-.35m-18.84,3.39c1.81-.32,3.18-1.9,3.18-3.8m13.54.5c.32,0,.4-.17.4-.49m0,0c0-.12-.01-.12-.05-.12m0,0h-.78m0,0l-.02.03m0,0c0,.33.07.59.45.59m7.59.55c.35,0,.45-.11.45-.38m-.43-.45h-.27m-.01.83h.27m-.3-.8v.77m.04-.8s-.04,0-.04.04m.74.42c0-.35-.15-.44-.43-.45m-.31.8s0,.03.03.03m-14.28-2.11s0-.02-.03-.03m0,0c-.06-.01-.17-.02-.27-.02m0,.69h.28m0,0l.02-.02m0,0v-.62m-.71.28c0,.3.1.36.4.36m0-.69c-.33,0-.41.04-.41.34m2.34.55c0,.59.11.7.5.7m.3-.07v-1.25m0,0s0-.03-.03-.04m0,0s-.13-.02-.25-.02m0,0c-.45,0-.52.11-.52.68m.77.67l.02-.04m-.3.07c.12,0,.21-.01.27-.04m-11.92-.31c.16-.2.25-.45.25-.72m0,0c0-.65-.53-1.17-1.17-1.17m.35,1.94c.22.21.4.19.57-.05m-2.09-.72c.79,0,1.14.43,1.52.77m-.35-1.94c-.65,0-1.17.53-1.17,1.17m25.9.46h.26m-.3-.84v.8m0,0s.01.04.04.04m.75-.43c0-.35-.12-.43-.53-.43m0,0h-.24m.26.87c.29,0,.5-.08.5-.44m-.77-.43s-.03,0-.03.03m4.4.03c-.05-.12-.57-.12-.63,0m0,0c-.17.17-.18,1.15,0,1.33m.63,0c.17-.17.16-1.16,0-1.33m-.63,1.33c.05.12.58.12.63,0m0,0c.17-.17.16-1.16,0-1.33m-.63,0c-.17.17-.18,1.15,0,1.33m1.04.28c.36-.33.35-1.57,0-1.9m-1.45,0c-.36.32-.38,1.58,0,1.9" - }, - "children": [] - } - ] - }, - { - "name": "g", - "type": "element", - "value": "", - "attributes": { - "id": "Well_Labels" - }, - "children": [ - { - "name": "path", - "type": "element", - "value": "", - "attributes": { - "class": "cls-1", - "d": "m82.38,87.7c.27-.2.27-.71,0-.9m0,0c-.13-.11-.31-.16-.53-.16m0,0c-.22,0-.39.06-.53.16m0,0c-.28.2-.27.7,0,.9m0,0c.26.23.8.22,1.06,0m8.84.01c.12-.08.21-.19.28-.33m0,0c.07-.14.1-.3.1-.49m0,0c0-.12-.04-.23-.11-.33m0,0c-.07-.1-.17-.18-.29-.24m0,0c-.12-.06-.24-.09-.37-.09m0,0c-.67-.04-.9.7-.65,1.21m0,0c.06.12.15.22.26.29m0,0c.23.14.58.14.8-.02m10.22-.23c.26-.39.27-1.83,0-2.22m0,0c-.13-.23-.32-.34-.58-.34m0,0c-.25,0-.45.11-.59.34m0,0c-.27.38-.27,1.83,0,2.21m0,0c.24.46.92.46,1.16,0m-82.52.68h.33m-.41-.24c.05.06.08.14.08.24m-.34-.38c.12.03.2.08.25.14m-.06-.49h-.69m.69-2.84v2.84m.47-2.84h-.47m-6.54-43.11v-.41m-.23,1.6h-1.61m1.61.4v-.4m-1.61,1.57v-1.16m-.46,1.57h2.25m-2.25-3.57v3.57m2.11-10.97h-1.57m0,1.58v-1.18m1.75,1.18h-1.75m0-3.17h-.47m19.08,52.92v-.4m-1.79.83c-.07-.1-.1-.22-.1-.35m.37.62c-.11-.08-.2-.17-.27-.26m1.66,1.3c-.07-.13-.16-.25-.27-.34m.37.81c0-.18-.04-.34-.1-.47m-.04,1.01c.1-.15.14-.33.14-.53m-2.3.45c.1.17.24.31.43.41m-.13-.97h-.45m1.77.56c-.13.11-.3.17-.52.17m.71-.62c0,.18-.06.33-.19.44m-.96-1.53c.13.08.27.16.4.23m-.77-.5c.11.1.24.19.37.27m-.64-.62c.07.13.16.25.27.35m2.02-1.25h-2.4M10.03,15.72v-1.65m1.93,1.65h-1.93m1.93,1.93h.46m-.46-1.52v1.52m-1.93-1.52h1.93m-2.38,1.52h.46m-.46-3.57v3.57m.46-3.57h-.46m2.82,9h-.35m.35,1.84v-1.84m-1.34,1.85h1.34m-.56-.93c.04.1.06.19.07.28m-.24-.55c.07.08.13.17.17.27m-1.25-.47c.13-.05.26-.08.41-.08m-.75.33c.1-.11.21-.2.34-.25m-.56.69c.05-.18.13-.33.22-.44m-.17,1.9c-.09-.21-.13-.48-.13-.81m1.04,1.43c-.21,0-.39-.05-.54-.15m.95.05c-.12.07-.26.1-.41.1m.71-.37c-.08.11-.18.2-.3.27m.53.16c.12-.11.22-.23.29-.37m-.71.62c.16-.06.3-.15.42-.26m-.93.35c.18,0,.35-.03.51-.1m-1.32-.12c.23.14.5.22.82.22m-1.35-.85c.12.28.3.49.53.63m-.72-1.63c0,.39.06.72.19.99m0-1.98c-.12.27-.18.6-.18.98m1.52-1.82c-.31,0-.59.07-.81.22m2.08,27.64c-.14-.27-.35-.47-.63-.61m.83,1.61c0-.4-.07-.73-.2-.99m-1.9,2.76h.25m-.51,0h.26m-.58-3.57v3.57m.78-3.57h-.78m2.29,9.31c-.11-.11-.24-.2-.41-.26m.79,1.08c-.02-.15-.06-.3-.12-.44m-.37.44h.49m-.64-.38c.07.12.13.25.15.38m-.44-.67c.12.07.22.16.29.28m-.73-.39c.17,0,.31.04.43.11m-.98,2.62c-.15-.1-.26-.26-.34-.47m.88.62c-.22,0-.4-.05-.55-.15m.98.05c-.12.07-.26.1-.43.1m.73-.37c-.07.11-.17.2-.29.27m.44-.65c-.03.14-.08.27-.15.38m.63-.38h-.49m.28.58c.12-.17.19-.37.21-.58m-1.35,1.15c.25,0,.48-.05.67-.16m-1.49-.06c.23.14.5.22.82.22m-1.34-.84c.12.27.29.48.52.62m-.69-1.61c0,.39.06.72.18.99m0-1.98c-.12.28-.18.61-.18.99m2.77,8.45c.02-.09.04-.19.04-.29m-.16.53c.06-.07.1-.16.13-.25m-.36.44c.1-.06.17-.12.23-.2m-.26,1.84c.22-.16.34-.39.34-.69m-1.32.93c.44,0,.77-.08.99-.24m-2.13.24h1.15m-1.15-3.57v3.57m1.19-3.57h-1.19m.46,9.97l-.35-.97m1.82.97h-1.46m1.81-.97l-.35.97m.81-.97h-.47m-.83,3.57l1.29-3.57m-1.81,3.57h.52m-1.81-3.57l1.29,3.57m-.83-3.57h-.47m28.63,8.07c-.07-.13-.17-.24-.29-.33m.39.77c0-.16-.04-.31-.1-.44m0,.85c.06-.12.1-.26.1-.41m-.37.71c.12-.08.21-.18.27-.3m-.68.49c.16-.04.29-.11.41-.19m.11,1.5c.11-.14.16-.3.16-.5m-.6.81c.19-.07.34-.18.44-.31m-1.1.42c.24,0,.46-.04.65-.11m-1.27-.02c.19.08.39.13.62.13m-1.23-1.09c0,.23.06.43.17.59m.29-.59h-.46m.58.4c-.07-.1-.12-.24-.12-.4m.41.63c-.12-.05-.22-.13-.29-.23m.66.31c-.13,0-.25-.02-.37-.07m.79,0c-.11.05-.25.08-.42.08m.68-.28c-.06.08-.15.16-.26.2m.35-.48c0,.1-.03.19-.09.28m0-.59c.07.09.11.19.11.31m-.4-.53c.12.06.22.13.29.23m-1.02-.32h.31m-.31-.39v.39m.32-.4h-.32m.78-.08c-.13.05-.29.08-.46.08m.9-.67c0,.14-.04.26-.12.36m0-.71c.07.1.11.21.11.34m-.41-.57c.13.06.23.13.3.23m-.76-.31c.17,0,.32.02.46.08m-1.01.09c.14-.12.33-.18.55-.18m-.82.69c.04-.22.12-.39.27-.51m-.73.51h.46m-.29-.57c-.11.16-.17.35-.17.57m.61-.95c-.19.09-.34.21-.44.37m1.09-.51c-.25,0-.46.05-.65.13m1.2-.07c-.17-.05-.35-.07-.55-.07m9.86,3.63v-2.26m-.47,2.26h.47m-2.13-2.24l1.67,2.24m9.84-3.05c-.11-.18-.26-.32-.45-.43m-1.04,2.16c.14.05.29.07.46.07m-.85-.31c.12.11.25.19.39.24m-.3.92l-.09-1.15m.23-.21c-.08-.05-.17-.13-.26-.23m.51.32c-.08-.02-.17-.05-.25-.1m-.48-.79c.06-.13.13-.25.22-.36m1.46-.57c-.19-.1-.42-.15-.68-.15m10.1.59c-.1-.18-.24-.33-.43-.43m-1.45,2.44c-.07-.23-.09-.51-.07-.85m.37,1.37c-.14-.12-.24-.29-.31-.52m1.37.97c.17-.07.31-.18.42-.32m-.96.43c.2,0,.38-.04.55-.11m8.36-2.97c-.06-.2-.1-.38-.11-.53m.34,1.19c-.09-.23-.16-.45-.22-.66m1.25,3.04v-.4m-2.51.4h2.51m-2.51-.4v.4m2.01-.4h-2.01m11.3-1.02c-.1-.13-.24-.23-.4-.3m-1.68,1.32c.35.48,1.14.54,1.66.33m-1.66-1.35c-.22.26-.2.76,0,1.02m.41-1.32c-.16.07-.3.17-.4.3m.06-.47c.11.09.23.14.35.17m-.55-1.43c-.19.26-.23.65-.06.94m.54-1.29c-.2.08-.36.2-.48.36m1.19-.48c-.28,0-.51.04-.72.13m10.86.74c-.11-.28-.27-.5-.48-.65m-.71,3.45c.28,0,.52-.07.72-.22m-1.37.06c.18.1.4.16.66.16m.37-2.29c-.13-.05-.26-.08-.42-.08m7.97,2.32v-3.57m-.32,3.57h.32m-.41-.24c.05.06.08.14.08.24m-.15-.73h-.69m.69-2.84v2.84m.47-2.84h-.47m1.67.4c-.4.47-.44,1.77-.18,2.39m1.11-2.85c-.39,0-.7.16-.93.46m1.86,0c-.23-.31-.54-.46-.93-.46m.93,3.21c.46-.55.46-2.19,0-2.74m6.04,2.92c.05.06.08.14.08.24m-.34-.38c.12.03.2.08.25.14m10.37-2.85v-.07m.1.43c-.07-.1-.1-.22-.1-.35m.37.62c-.11-.08-.2-.17-.27-.26m1.66,1.3c-.07-.13-.16-.25-.27-.34m.37.81c0-.18-.04-.34-.1-.47m-.04,1.01c.1-.15.14-.33.14-.53m-.55.89c.18-.08.31-.2.41-.36m-1.03.48c.24,0,.44-.04.62-.13m-1.32-.02c.19.1.42.15.69.15m-1.12-.56c.1.17.24.31.43.41m-.58-.97c0,.21.05.4.15.57m1.1.16c-.24,0-.42-.07-.56-.2m1.08.03c-.13.11-.3.17-.52.17m.71-.62c0,.18-.06.33-.19.44m-.55-1.3c.13.07.26.15.37.23m-.77-.46c.13.08.27.16.4.23m-.77-.5c.11.1.24.19.37.27m-.64-.62c.07.13.16.25.27.35m-.37-1.25v.42m2.39-.42h-2.39m-.92,3.57v-3.57m-.33,3.57h.33m-.41-.24c.05.06.08.14.08.24m-.34-.38c.12.03.2.08.25.14m-.75-.19c.21,0,.38.01.5.05m-.5-.35v.3m.69-.3h-.69m.69-2.84v2.84m.47-2.84h-.47m-6.05,3.57v-3.57m-.32,3.57h.32m-.41-.24c.05.06.08.14.08.24m-.34-.38c.12.03.2.08.25.14m-.75-.49v.3m.69-.3h-.69m.69-2.84v2.84m.47-2.84h-.47m-82.58,3.47c.19.1.42.15.69.15m79.19-.48c.21,0,.38.01.5.05M12.14,70.23c-.1-.13-.27-.24-.52-.33m.66.82c0-.19-.05-.35-.15-.49m44.07,15.54c0-.25-.06-.46-.16-.65M10.48,33.67v-1.59m1.84,27.69c-.06-.14-.15-.27-.26-.38m88.14,28.62c.48.35,1.25.25,1.6-.26M10.44,26.14c-.16-.1-.28-.25-.36-.47m45.9,62.1h-1.73M10.32,59.78c.17-.23.42-.35.74-.35m-1,1.44c0-.5.08-.86.25-1.09m-.14,1.91c-.08-.21-.12-.48-.12-.81m88,26.91c.12.03.2.08.25.14m-.75-.19c.21,0,.38.01.5.05m-.5-.35v.3M10.4,43.08h1.61m-.25,19.46c.19-.11.35-.25.47-.42m96.87,25.62c.21,0,.38.01.5.05m-55.46-2.52c.09-.11.2-.19.32-.25M10.27,59.25c-.23.14-.4.36-.52.63m1.34-.85c-.32,0-.6.07-.82.22m1.38-.13c-.16-.06-.35-.1-.56-.1m81.08,27.41c0-.4-.05-.74-.16-1.02m0,1.98c.11-.26.17-.59.17-.96m-.63,1.57c.2-.14.36-.35.47-.61m-73.91.05v.3m71.64-.11c.1.18.24.33.42.43m.62-2.21c-.23,0-.44.05-.62.14M11.61,50.28c-.28-.14-.63-.2-1.07-.2m80.96,36.08c-.09-.1-.2-.17-.32-.22m27.76-.02c-.13-.07-.26-.14-.37-.22m.76.44c-.13-.08-.26-.15-.4-.22m.76.48c-.11-.1-.23-.18-.36-.26M10.59,53.64c.44,0,.79-.08,1.07-.22m78.61,31.65c.13-.1.29-.15.47-.15M9.86,53.65h.22m79.7,31.33c-.12.14-.18.3-.21.5M9.76,53.65h.11m80.89,30.9c-.2,0-.38.04-.55.11M11.82,23.33c-.1-.09-.21-.16-.35-.22m26.45,63.71c-.11-.14-.29-.25-.52-.34m80.68.61h-.46m.69.53c-.14-.13-.22-.31-.24-.53M12.37,69.04c0-.32-.12-.56-.35-.72m107.46,18.53c.07.1.1.22.1.36m-.37-.63c.11.08.2.17.27.27M11.91,24.59h-.87m.85-.31v.31m-.52-1.07c.11.05.2.12.28.2m106.03,61.3c0,.18.04.34.1.47M10.99,23.44c.15,0,.28.02.39.08m71.11,62.94c.26-.05.49-.26.62-.49M9.95,24.87c0-.25.02-.47.08-.65m72.87,63.56c.2-.26.22-.76,0-1.02M10.48,34.07h1.57m68.55,51.89c.07.13.16.24.27.32M11.85,25.55c-.02.13-.07.25-.15.37m.64-.37h-.49m.37.43c.07-.14.11-.28.12-.43m70.24,59.12c-.2-.08-.44-.13-.72-.13m1.2.48c-.11-.15-.27-.27-.48-.36m-9.83-.07h-.5M10.4,42.67v-1.19m0,0h1.84m-2.08-18.25c-.23.14-.4.35-.52.62m2.55,20.78v-.4m42.68,40.68c.25,0,.46.07.61.22M11.97,14.08v1.65m.46-1.65h-.46m.46,3.57v-3.57m59.83,70.52c.02.16.07.36.14.6M10.03,17.65v-1.52m16.66,68.9c0,.18.04.34.1.47m-.18,1.59c0,.21.05.4.15.57m38.39-1.89c0-.24-.05-.45-.15-.63m-1.04,2.71c-.21,0-.38-.06-.53-.18m-8.53-1.12c-.1,0-.19,0-.27-.02m10.28,1.28c.11-.14.18-.3.2-.5m-37.25.53c-.24,0-.42-.07-.56-.2m80.6.55h.33M12.05,23.08v.54m51.15,64.36c.21.16.46.23.76.23m-37.26-3.62v.42m1.2,3.2c.24,0,.44-.04.62-.13m8.26-1.43c.16,0,.3.04.42.09m62.56.64c.11.28.25.48.44.62m-9.99-3.35c-.17.08-.31.18-.43.32M11.47,23.12c-.14-.06-.3-.08-.49-.08m43.48,61.99c.12-.06.26-.09.42-.09m-1.36.53l.4.17m-35.82,2.11c.21,0,.38.01.5.05m9.93.31c.18-.08.31-.2.41-.36m18.34-2.23h-.59m73.42-.51v-.4m-1.88.4h1.88m-75.55.51v.42M11.65,53.42c.27-.14.47-.34.6-.59m25.54,31.98c-.12-.09-.27-.16-.44-.2M12.05,23.62c-.06-.1-.14-.2-.24-.29m26.27,63.96c0-.18-.06-.34-.17-.48m-10.6.8c-.14-.13-.22-.31-.24-.53m8.69.63c.11.16.26.29.45.37m36.86-1.21c.16.3.34.6.54.9m-36.05-1.82c-.08.1-.19.17-.32.23m17.76.75c.22,0,.41-.04.6-.13m26.87,1.31c.18-.08.32-.19.43-.33M12.05,34.07v-.4m60.62,52.32c.11.29.24.58.4.89m-18.96-.69h-.02m53,1.25v.3m-51.36-1.99c0,.16-.04.3-.11.41m18.51,1.61c-.13-.17-.25-.37-.38-.59m-18.25-2.04c.15.14.23.35.23.6m.27,2.42v-.4m-44.36-17.88c.14-.03.26-.07.35-.13m60.42,15.43c.07.24.16.5.27.79M12.23,41.08h-2.3m43.92,47.09h2.13m34.16-2.18c-.18.09-.32.22-.42.4m-62.53-1.31v-.07m19.47.91h.59m-20.06-.91h1.89m62.61,1.49c-.04-.12-.11-.23-.19-.33m-28.24.45c.09.09.2.16.32.21m1.54.47h-.43M12.26,52.84c.13-.26.19-.58.19-.95m51.47,32.66c-.28,0-.52.07-.72.22m-7.6,2.04c.18-.09.33-.22.44-.39M11.05,24.59v.34m96.72,59.68v2.84m.47-2.84h-.47m-61.55,0v.91m18.36-.81c-.19-.1-.41-.16-.66-.16m19.2,1.42c.17-.29.13-.68-.06-.94m-27.45,1.14c-.07.12-.17.21-.29.28M12.23,35.65v-.4m41.85,50.94l-.38.12m19.68.19c-.11-.24-.21-.48-.3-.71m-19.38.52l.16,1.86m9.73-1.35c.12.05.25.07.4.07m-9.86-.71h-.02m35.46-.71h.44m-25.57,2.22c-.13.1-.28.15-.47.15m1.05-1.47c.1-.17.15-.37.15-.6m-45.91,2.41v-3.57m71.49.32c.21,0,.39.06.54.17m-28.08-.33c-.2.14-.36.35-.47.61m11.01,1.81c-.12-.22-.24-.45-.35-.69M12.19,44.25h-1.79m81.12,40.52c-.21-.15-.47-.22-.77-.22m-34.72,1.86c.11-.17.17-.38.17-.64m6.52,1.56c.11.29.26.5.47.66m28.08-2.9c.15.11.26.28.34.51m-28.89-.23c-.11.27-.17.59-.17.96m-16.34-.82h-1.68m10.37-.97c-.22,0-.43.04-.61.12m1.02,1.78c-.12.07-.26.1-.41.1m-7.65-.63v-.4M10.01,32.08v3.57m43.81,49.34c-.13.14-.23.3-.3.48m36.49.01c.04-.16.13-.29.26-.4m-.54,1.31c-.1.17-.15.37-.15.61m-42.91-1.49v-.91m-34.64-16.28c-.23-.16-.58-.24-1.03-.24m17.7,18.31c-.11-.1-.23-.18-.36-.26m34.73.16c.05.12.11.23.2.32m-.71-.28c0,.38.05.72.16,1m1.26-.43c.24,0,.44-.05.62-.14m-36.76-.42c.13.07.26.15.37.23m80.03,1.6v-3.57m-.47,2.84h-.69m-78.75-1.31c-.13-.08-.26-.15-.4-.22m61.64,1.09c0,.24.05.45.15.63m-25.13-.87c.18-.09.31-.22.41-.39m-36.8.2c.11.08.2.17.27.27m25.81-2.18c-.19.08-.35.19-.48.32m-7.14-.38h-.45M10.01,35.65h2.22m79.39,49.95c.07.23.1.53.08.89m-27,.8c-.05.17-.13.3-.26.4m-35.95-.85c.07.1.1.22.1.36m-.65-1.29c-.13-.07-.26-.14-.37-.22m55.05.23c.18-.25.13-.64-.13-.83m-.17,1.06c.13-.06.23-.14.3-.24m-.13-.82c-.16-.12-.37-.18-.63-.18m0,0c-.59-.05-1.1.48-.76,1.01m0,0c.24.36.85.4,1.22.24M11.05,69.69c.16,0,.3-.02.43-.06m.43-.57c0-.2-.08-.35-.23-.45m0,0c-.16-.1-.38-.15-.68-.15m-.72,0v1.23m0,0h.78m-.05-1.23h-.72m1.52.96c.08-.09.12-.22.12-.36m-.43.57c.13-.04.23-.11.31-.2m-.19,1.7c.16-.1.24-.25.24-.46m0,0c0-.11-.02-.2-.07-.28m-.72-.3h-.78m1.31.12c-.08-.05-.16-.08-.25-.1m-.34,1.16c.25,0,.46-.05.62-.15m-.28-1.01c-.09-.02-.18-.03-.28-.03m-.78,0v1.19m1.5-.89c-.05-.08-.11-.13-.19-.18m-1.31,1.07h.71m-.6,7.2l.61,1.73m0,0l.61-1.73m0,0h-1.22m35.82,7.44h-1.23m1.23,1.66v-1.66m-1.23,0l1.23,1.66m18.37-1.41c.12-.23.12-.62-.01-.85m0,0c-.06-.12-.16-.22-.27-.29m-.8.01c-.12.08-.21.19-.28.33m1.1,1.06c.11-.06.2-.15.26-.27m-.67-1.24c-.16,0-.29.04-.41.12m.8-.02c-.12-.07-.24-.1-.39-.1m-.68.45c-.07.14-.1.3-.1.48m.4.58c.24.13.58.12.79,0m-1.2-.58c0,.25.19.46.41.58M11.8,52.7c.11-.2.17-.47.17-.82m-1.4,1.38c.29,0,.55-.04.75-.13m-.78-2.66h-.31m1.08.14c-.22-.09-.47-.14-.76-.14m1.25.58c-.12-.2-.28-.35-.49-.44m.66,1.27c0-.35-.06-.62-.17-.83m-.47,2.07c.21-.08.37-.23.48-.43m-1.57.56h.34m-.34-2.79v2.79m71.1,33.55c-.28.2-.27.7,0,.9" - }, - "children": [] - } - ] - }, - { - "name": "g", - "type": "element", - "value": "", - "attributes": { - "id": "Wells" - }, - "children": [ - { - "name": "path", - "type": "element", - "value": "", - "attributes": { - "class": "cls-1", - "d": "m122.09,79.84l.1-.09m-1.63,2.61l.04-.13m-104.49,0l.04.13m-1.63-2.61l.1.09m0-64.98l-.1.1m1.63-2.61l-.04.13m104.49,0l-.04-.13m1.63,2.61l-.1-.1m-14.23,5.68v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69c1.64-.38,2.93-1.67,3.31-3.31m-5.31,3.69v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m21.31-3.31v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-77.69-3.31v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69c1.64-.38,2.93-1.67,3.31-3.31m9.38,61h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m0-7c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0v-.38m0-8.62v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m43-18v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m16,18v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m0-7h.38m-3.69,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0-16c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m3.31,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-12.31v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m21.31,14.69v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m-3.69,3.31v.38m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-23.69-48.31v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-7,0v.38m3.31-3.69h.38m30.31,12.69v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m12.69,48.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,0h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m7-9v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m-3.69,3.31v.38M62.87,20.55v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69,3.31v.38m0-.38c1.64-.38,2.93-1.67,3.31-3.31m5.69,3.31v.38m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m0-7c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m21.31,3.69v-.38m-3.69-3.31h.38m0,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31M23.55,61.85c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m3.69-3.31v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m7-36v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m0-.38c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-.38-9h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-7,0v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m21.69,3.69v-.38m-3.69-3.31h.38m-3.69,3.31v.38m0-.38c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m5.31-3.31h.38m-3.69,3.31v.38m3.69,3.31h-.38m3.69-3.31v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m30.31,32.69v-.38m-3.69-3.31h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-7,0v.38m27,17.62v.38m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m3.31,3.69v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31M50.55,25.87c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69,3.31v.38m-14.69,41.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m0-16c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m-3.31-3.69v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0-16c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,0h.38m0,7h-.38m-3.31-3.69v.38m7,0v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m7-9v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m0,0c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-21.69c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m-3.31-3.69v.38m7,0v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-12.69,48.31v.38m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m18.38-45c.38,1.64,1.67,2.93,3.31,3.31m-7,0v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m5.69,14.69c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m-3.31-3.69v.38m7,0v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-12.69c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m7,0v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m5.31,12.31c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31v-.38m-3.69-3.31h.38m0,0c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m3.69-12.69c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m9,17.62c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m0-7h.38m0,0c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69-12.69c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m0-7h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-7-9c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m-3.31-3.69v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-3.69-3.31h.38m0,0c.38,1.64,1.67,2.93,3.31,3.31m0-8.62v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m0,7h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m12.69,32.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m7,0v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m18,9c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m3.69-3.31v-.38m-7,0v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m0-9c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m-3.31-3.69v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m0-9.38c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m-3.31-3.69v.38m3.31-3.69h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-3.31-12.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m0-7h.38m3.31,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m0-25c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m-3.31-3.69v.38m7,0v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m8.62,2h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m-3.31-3.69v.38m7,0v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m0,.38c-1.64.38-2.93,1.67-3.31,3.31m8.62-16h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m7,0v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-36,38c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m-3.31-3.69v.38m3.31-3.69h.38m3.31,3.69v-.38m2,0c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m0-7h.38m3.31,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0-52c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-3.69-3.31h.38m17.62,34c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-3.69-5.69c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m0-7h.38m-3.69,3.31v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-84.31-12.31c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-3.31,3.69h-.38m0-7h.38m26.62,18h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m0-7c.38,1.64,1.67,2.93,3.31,3.31m-7,0v.38m0-.38c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m5.69-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m0,0h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-7,9c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m3.31,3.69v-.38m-7,0v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m-27-34c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m0,0c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m-3.69,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m38-9c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m3.31,3.69v-.38m-7,0v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m-57.31,14.31v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m3.69-3.31v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m27,27h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m0,0h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m0,.38c-1.64.38-2.93,1.67-3.31,3.31m3.31-3.31v-.38m-7,0v.38m45-36.38c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m0-7h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-39.31-5.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m7,0v-.38m-39.31,23.69c.38,1.64,1.67,2.93,3.31,3.31m-7,0v.38m3.69,3.31h-.38m3.69-3.31v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m27,8.62c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m0-7h.38m3.31,3.69v-.38m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m30.31-23.69c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.69,3.31h-.38m.38-7c.38,1.64,1.67,2.93,3.31,3.31m-3.69-3.31h.38m3.31,3.69v-.38m-39.31,5.69c.38,1.64,1.67,2.93,3.31,3.31m-7,0c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69v-.38m-3.69-3.31h.38m-3.69,3.31v.38m7,0c-1.64.38-2.93,1.67-3.31,3.31m0,0h-.38m0,0c-.38-1.64-1.67-2.93-3.31-3.31m21.69,30.31h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-3.69-3.31h.38m-.38,7c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m-18-36.38c1.64-.38,2.93-1.67,3.31-3.31m0,7c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m3.31,3.69c-1.64.38-2.93,1.67-3.31,3.31m0-7c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m7,0v-.38m23.69,23.69c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m0,0h.38m0,7h-.38m3.69-3.31c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m0-.38v.38m-27-9.38c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m0-7h.38m3.31,3.69v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m30.69,14.31c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m3.31-3.69h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m-.38,0c-.38-1.64-1.67-2.93-3.31-3.31m7,0v-.38m14.69-39.31c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-3.69-3.31h.38m0,7h-.38m-3.31-3.69c1.64-.38,2.93-1.67,3.31-3.31m-3.31,3.31v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m30.31-3.31v-.38m-3.31-3.31c.38,1.64,1.67,2.93,3.31,3.31m-3.31,3.69h-.38m-3.31-3.69v.38m3.31,3.31c-.38-1.64-1.67-2.93-3.31-3.31m3.31-3.69h.38m-3.69,3.31c1.64-.38,2.93-1.67,3.31-3.31m3.69,3.69c-1.64.38-2.93,1.67-3.31,3.31m-3.69,14.31v.38m3.69-3.69c.38,1.64,1.67,2.93,3.31,3.31m0,.38v-.38m-7,0c1.64-.38,2.93-1.67,3.31-3.31m.38,7h-.38m0,0c-.38-1.64-1.67-2.93-3.31-3.31m7,0c-1.64.38-2.93,1.67-3.31,3.31m-.38-7h.38m3.31-5.31v-.38m-3.69,3.69c-.38-1.64-1.67-2.93-3.31-3.31m0-.38c1.64-.38,2.93-1.67,3.31-3.31m.38,0c.38,1.64,1.67,2.93,3.31,3.31m0,.38c-1.64.38-2.93,1.67-3.31,3.31m-3.69-3.69v.38m3.69,3.31h-.38m0-7h.38m8.65-28.24l.08.06m-2.32-3.72l.02.1M14.52,79.1l-.08-.06m2.32,3.72l-.02-.1m0-70.59l.02-.1m-2.32,3.72l.08-.06m105.46,67.04l-.02.1m2.32-3.72l-.08.06m-78.79-27.24c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm14.75-9c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm34.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-75.25-9c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm25.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.45-9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm34.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-82.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm70.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-28.45,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-46.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm79.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-21.25,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm25.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-84.25-9c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-12.25,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0ZM14.61,42.85c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm5.75,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm59.75,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-1.45-9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0ZM25.71,24.87c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm25.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.45,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.85,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm25.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0ZM43.71,24.87c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm23.75,9c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.55-9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0ZM14.61,15.87c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm61.55,63c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0ZM50.6,15.87c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm25.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-55.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.55,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-1.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-73.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,27c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm34.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-55.15,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm88.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.45,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-46.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm34.55,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm70.85,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-46.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm61.85-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-82.15,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-3.25,18c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.85-18c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm52.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-19.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-37.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-28.15,27c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm79.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-37.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm23.74,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-57.25,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm50.75,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-66.25-18c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm1.9,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.3,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.45,27c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-37.15-9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.85,9c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15-27c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm16.85,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-57.25,18c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm.33,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm43.55,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm7.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-82.45-9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm79.55,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-10.15,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-.3,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-64.45,0c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm-.1,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.2,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm.03,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-1.45,9c-.05-3.18,4.94-3.18,4.9,0,.05,3.18-4.94,3.18-4.9,0Zm-1.8,0c-.08-5.52,8.58-5.52,8.5,0,.08,5.51-8.58,5.51-8.5,0Zm2.1,0c-.04-2.79,4.34-2.79,4.3,0,.04,2.79-4.34,2.79-4.3,0Zm-2.2,0c-.08-5.64,8.78-5.64,8.7,0,.08,5.64-8.78,5.64-8.7,0Zm2.23,0c-.04-2.75,4.28-2.75,4.24,0,.04,2.75-4.28,2.75-4.24,0Zm1.09,0c-.02-1.34,2.08-1.34,2.06,0,.02,1.34-2.08,1.34-2.06,0Zm.03,0c-.02-1.3,2.02-1.3,2,0,.02,1.3-2.02,1.3-2,0Zm-9.93-12.99c-3.28,1.48-3.3,6.49,0,7.97m0-34.97c-3.28,1.48-3.3,6.49,0,7.97m0-16.97c-3.28,1.48-3.3,6.49,0,7.97m0-16.97c-3.28,1.48-3.3,6.49,0,7.97m14.92-14.92h1.03m7.97,0h1.03m7.97,0c-1.48-3.28-6.49-3.3-7.97,0m7.97,0h1.03m7.97,0c-1.48-3.28-6.49-3.3-7.97,0m34.97,0h1.03m7.97,0c-1.48-3.28-6.49-3.3-7.97,0m7.97,0h1.03m7.97,0c-1.48-3.28-6.49-3.3-7.97,0m7.97,0h1.03m14.92,5.92v1.02m0,7.97c3.28-1.48,3.3-6.49,0-7.97m0,16.98c3.28-1.48,3.3-6.49,0-7.97m0,16.97c3.28-1.48,3.3-6.49,0-7.97m0,7.97v1.03m0,7.97c3.28-1.48,3.3-6.49,0-7.97m0,7.98v1.02m0,7.97c3.28-1.48,3.3-6.49,0-7.97m0,7.98v1.02m0,7.97c3.28-1.48,3.3-6.49,0-7.97m-14.92,14.92h-1.03m-25.97,0c1.48,3.28,6.49,3.3,7.97,0m-16.97,0c1.48,3.28,6.49,3.3,7.97,0m-7.98,0h-1.02m-7.98,0h-1.02m-7.98,0h-1.03m-16.97,0c1.48,3.28,6.49,3.3,7.97,0m-16.97,0c1.48,3.28,6.49,3.3,7.97,0m-14.92-5.92c-6.95,3.72,2.2,12.87,5.92,5.92m-5.92-32.92v-1.03m96.94,33.94h-1.02m-25.97,0c1.48,3.28,6.49,3.3,7.97,0m24.94-42.94v1.03m0,34.97v1.03M76.84,13.94h1.02m-60.94,51.94v-1.02m0-7.98v-1.02m0-34.98v-1.02m50.92-5.92c-1.48-3.28-6.49-3.3-7.97,0m-19.02,0c-1.48-3.28-6.49-3.3-7.97,0m27,66.86c1.48,3.28,6.49,3.3,7.97,0m46.03,0c3.72,6.95,12.87-2.2,5.92-5.92m-41.92,5.92h-1.02m10.03,0h-1.03m-9-66.86c-1.48-3.28-6.49-3.3-7.97,0m-51.94,24.94v-1.03m33.95,42.94c1.48,3.28,6.49,3.3,7.97,0m60.94-51.94v1.03m-60.94-15.94h1.02m7.98,0h1.02M16.93,47.88c-3.28,1.48-3.3,6.49,0,7.97M112.84,13.94c-1.48-3.28-6.49-3.3-7.97,0M16.93,74.88v-1.03m15.95,6.94h-1.03m-7.98,0h-1.02m0-66.86c-3.72-6.95-12.87,2.2-5.92,5.92m87.95,60.94c1.48,3.28,6.49,3.3,7.97,0M22.85,13.94h1.02m-6.94,42.94c-3.28,1.48-3.3,6.49,0,7.97M112.84,13.94h1.02m-18,66.86c1.48,3.28,6.49,3.3,7.97,0M85.84,13.94c-1.48-3.28-6.49-3.3-7.97,0m-36,66.86h-1.03m1.03,0c1.48,3.28,6.49,3.3,7.97,0M119.78,19.85c6.95-3.72-2.2-12.87-5.92-5.92m-18,66.86h-1.03M16.93,29.88v-1.03m14.92-14.92c-1.48-3.28-6.49-3.3-7.97,0" - }, - "children": [] - } - ] - } - ] - } -}