From 5de6a0d974325c01b427b6938f03ef98988e6d1a Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 27 Apr 2024 10:19:49 +0200 Subject: [PATCH 01/18] Update min and max position values for ChainAxis class in hardware.py --- config/u3_config_rb27/hardware.py | 2 ++ field_friend/automations/puncher.py | 2 +- field_friend/hardware/chain_axis.py | 12 ++++++++---- field_friend/hardware/field_friend.py | 2 +- field_friend/hardware/field_friend_hardware.py | 2 ++ .../interface/components/hardware_control.py | 4 ++-- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/config/u3_config_rb27/hardware.py b/config/u3_config_rb27/hardware.py index 54fe25ee..183c540b 100644 --- a/config/u3_config_rb27/hardware.py +++ b/config/u3_config_rb27/hardware.py @@ -11,6 +11,8 @@ 'y_axis': { 'version': 'chain_axis', 'name': 'chain_axis', + 'min_position': -0.10, + 'max_position': 0.10, 'motor_on_expander': False, 'end_stops_on_expander': True, 'step_pin': 13, diff --git a/field_friend/automations/puncher.py b/field_friend/automations/puncher.py index 5e7fc3d8..48293bc8 100644 --- a/field_friend/automations/puncher.py +++ b/field_friend/automations/puncher.py @@ -72,7 +72,7 @@ async def punch(self, y: float, *, depth: float = 0.01, angle: float = 180) -> N raise PuncherException('homing failed') await rosys.sleep(0.5) if isinstance(self.field_friend.y_axis, ChainAxis): - if not self.field_friend.y_axis.MIN_POSITION+self.field_friend.y_axis.WORK_OFFSET <= y <= self.field_friend.y_axis.MAX_POSITION-self.field_friend.y_axis.WORK_OFFSET: + if not self.field_friend.y_axis.min_position <= y <= self.field_friend.y_axis.max_position: rosys.notify('y position out of range', type='negative') raise PuncherException('y position out of range') elif isinstance(self.field_friend.y_axis, YAxis): diff --git a/field_friend/hardware/chain_axis.py b/field_friend/hardware/chain_axis.py index 91c94c07..8907189a 100644 --- a/field_friend/hardware/chain_axis.py +++ b/field_friend/hardware/chain_axis.py @@ -16,13 +16,14 @@ class ChainAxis(rosys.hardware.Module, abc.ABC): MAX_POSITION = 0.14235 CHAIN_RADIUS = 0.05185 RADIUS_STEPS = 8400 # TODO: make configurable (U2=16000, U3 = 8400) - WORK_OFFSET = 0.04 REF_OFFSET = 1500 POSITION_OFFSET = CHAIN_RADIUS / RADIUS_STEPS * REF_OFFSET TOP_DOWN_FACTOR = 1.0589 - def __init__(self, **kwargs) -> None: + def __init__(self, min_position: float = -0.10, max_position: float = 0.10, **kwargs) -> None: super().__init__(**kwargs) + self.min_position = min_position + self.max_position = max_position self.steps: int = 0 self.alarm: bool = False @@ -76,7 +77,7 @@ async def move_to(self, position: float, speed: int | None = None) -> None: speed = self.DEFAULT_SPEED if not self.is_referenced: raise RuntimeError('yaxis is not referenced, reference first') - if not self.MIN_POSITION + self.WORK_OFFSET <= position <= self.MAX_POSITION - self.WORK_OFFSET: + if not self.min_position <= position <= self.max_position: raise RuntimeError(f'target yaxis {position} is out of range') if not self.ref_t: raise RuntimeError('yaxis is not at top reference, move to top reference first') @@ -128,6 +129,8 @@ class ChainAxisHardware(ChainAxis, rosys.hardware.ModuleHardware): def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, name: str = 'chain_axis', expander: Optional[rosys.hardware.ExpanderHardware], + min_position: float = -0.10, + max_position: float = 0.10, step_pin: int = 5, dir_pin: int = 4, alarm_pin: int = 13, @@ -196,7 +199,8 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, f'{name}_alarm.level', f'{name}_ref_t.level', ] - super().__init__(robot_brain=robot_brain, lizard_code=lizard_code, core_message_fields=core_message_fields) + super().__init__(min_position=min_position, max_position=max_position, robot_brain=robot_brain, + lizard_code=lizard_code, core_message_fields=core_message_fields) async def stop(self) -> None: await super().stop() diff --git a/field_friend/hardware/field_friend.py b/field_friend/hardware/field_friend.py index 57eaf511..3b69772e 100644 --- a/field_friend/hardware/field_friend.py +++ b/field_friend/hardware/field_friend.py @@ -76,6 +76,6 @@ def can_reach(self, local_point: rosys.geometry.Point, second_tool: bool = False work_x = self.WORK_X_DRILL tool_radius = self.DRILL_RADIUS return work_x - tool_radius <= local_point.x <= work_x + tool_radius \ - and self.y_axis.MIN_POSITION+self.y_axis.WORK_OFFSET <= local_point.y <= self.y_axis.MAX_POSITION-self.y_axis.WORK_OFFSET + and self.y_axis.min_position <= local_point.y <= self.y_axis.max_position else: raise NotImplementedError(f'Tool {self.tool} is not implemented for reachability check') diff --git a/field_friend/hardware/field_friend_hardware.py b/field_friend/hardware/field_friend_hardware.py index 353f5d03..36e3a6cb 100644 --- a/field_friend/hardware/field_friend_hardware.py +++ b/field_friend/hardware/field_friend_hardware.py @@ -97,6 +97,8 @@ def __init__(self) -> None: y_axis = ChainAxisHardware(robot_brain, expander=expander, name=config_hardware['y_axis']['name'], + min_position=config_hardware['y_axis']['min_position'], + max_position=config_hardware['y_axis']['max_position'], step_pin=config_hardware['y_axis']['step_pin'], dir_pin=config_hardware['y_axis']['dir_pin'], alarm_pin=config_hardware['y_axis']['alarm_pin'], diff --git a/field_friend/interface/components/hardware_control.py b/field_friend/interface/components/hardware_control.py index 29d2f04f..957b9dba 100644 --- a/field_friend/interface/components/hardware_control.py +++ b/field_friend/interface/components/hardware_control.py @@ -124,10 +124,10 @@ async def toggle_flashlight(e: ValueChangeEventArguments) -> None: with ui.row(): if isinstance(field_friend.y_axis, ChainAxis): ui.button(on_click=lambda: automator.start( - puncher.punch(field_friend.y_axis.MAX_POSITION-field_friend.y_axis.WORK_OFFSET, depth=depth.value))) + puncher.punch(field_friend.y_axis.min_position, depth=depth.value))) ui.button(on_click=lambda: automator.start(puncher.punch(0, depth=depth.value))) ui.button(on_click=lambda: automator.start( - puncher.punch(field_friend.y_axis.MIN_POSITION+field_friend.y_axis.WORK_OFFSET, depth=depth.value))) + puncher.punch(field_friend.y_axis.max_position, depth=depth.value))) elif isinstance(field_friend.y_axis, YAxis) and isinstance(field_friend.z_axis, Tornado): ui.button(on_click=lambda: automator.start( puncher.punch(field_friend.y_axis.min_position, angle=angle.value))) From 937434eb7337e6224650743d831220fa53122036 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 27 Apr 2024 10:20:15 +0200 Subject: [PATCH 02/18] Update docker-compose.jetson.orin.yml detector to plant detection --- docker-compose.jetson.orin.yml | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docker-compose.jetson.orin.yml b/docker-compose.jetson.orin.yml index 11bcc738..4c72d07f 100644 --- a/docker-compose.jetson.orin.yml +++ b/docker-compose.jetson.orin.yml @@ -5,20 +5,6 @@ services: restart: always privileged: true - # detector: - # image: "zauberzeug/yolov5-detector:nlv0.8.8-35.4.1" - # restart: always - # ports: - # - "8004:80" - # hostname: ${ROBOT_ID} - # environment: - # - HOST=learning-loop.ai - # - ORGANIZATION=zuckerruebe - # - PROJECT=uckerbots - # - NVIDIA_VISIBLE_DEVICES=all - # volumes: - # - ~/data_plants:/data - detector: image: "zauberzeug/yolov5-detector:nlv0.8.8-35.4.1" restart: always @@ -27,13 +13,27 @@ services: hostname: ${ROBOT_ID} environment: - HOST=learning-loop.ai - - ORGANIZATION=zauberzeug - - PROJECT=coins + - ORGANIZATION=zuckerruebe + - PROJECT=uckerbots - NVIDIA_VISIBLE_DEVICES=all volumes: - - ~/data_coins:/data + - ~/data_plants:/data + + # detector: + # image: "zauberzeug/yolov5-detector:nlv0.8.8-35.4.1" + # restart: always + # ports: + # - "8004:80" + # hostname: ${ROBOT_ID} + # environment: + # - HOST=learning-loop.ai + # - ORGANIZATION=zauberzeug + # - PROJECT=coins + # - NVIDIA_VISIBLE_DEVICES=all + # volumes: + # - ~/data_coins:/data - circle_sight: + monitoring: image: "zauberzeug/yolov5-detector:nlv0.8.8-35.4.1" restart: always ports: From 71f12d29a7ba8723e49112333121c8aec2a6487e Mon Sep 17 00:00:00 2001 From: Miguel Date: Sun, 28 Apr 2024 00:18:26 +0200 Subject: [PATCH 03/18] Add KPI tracking for weeds removed and chops --- field_friend/automations/kpi_provider.py | 2 ++ field_friend/automations/puncher.py | 1 + field_friend/automations/weeding.py | 1 + field_friend/kpi_generator.py | 8 +++++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/field_friend/automations/kpi_provider.py b/field_friend/automations/kpi_provider.py index 716c2fe5..83b0849f 100644 --- a/field_friend/automations/kpi_provider.py +++ b/field_friend/automations/kpi_provider.py @@ -26,7 +26,9 @@ class Weeding_KPIs(KPIs): rows_weeded: int = 0 crops_detected: int = 0 weeds_detected: int = 0 + weeds_removed: int = 0 punches: int = 0 + chops: int = 0 weeding_completed: bool = False diff --git a/field_friend/automations/puncher.py b/field_friend/automations/puncher.py index 48293bc8..dda31560 100644 --- a/field_friend/automations/puncher.py +++ b/field_friend/automations/puncher.py @@ -127,6 +127,7 @@ async def chop(self) -> None: else: await self.field_friend.y_axis.move_dw_to_r_ref() await self.field_friend.y_axis.stop() + self.kpi_provider.increment_weeding_kpi('chops') async def tornado_drill(self, angle: float = 180) -> None: self.log.info(f'Drilling with tornado at {angle}...') diff --git a/field_friend/automations/weeding.py b/field_friend/automations/weeding.py index 1815b467..c82105a0 100644 --- a/field_friend/automations/weeding.py +++ b/field_friend/automations/weeding.py @@ -383,6 +383,7 @@ async def _weed_with_plan(self): turn_path = self.turn_paths[i] await self.system.driver.drive_path(turn_path) await rosys.sleep(1) + self.kpi_provider.increment_weeding_kpi('rows_weeded') self.system.automation_watcher.stop_field_watch() self.system.automation_watcher.gnss_watch_active = False diff --git a/field_friend/kpi_generator.py b/field_friend/kpi_generator.py index e9f74589..31c15dad 100644 --- a/field_friend/kpi_generator.py +++ b/field_friend/kpi_generator.py @@ -1,8 +1,10 @@ from datetime import datetime, timedelta from random import randint -from .automations import KpiProvider + from rosys.analysis import Day, Month, date_to_str +from .automations import KpiProvider + def generate_kpis(kpi_provider: KpiProvider) -> None: kpi_provider.days = [ @@ -23,7 +25,9 @@ def generate_kpis(kpi_provider: KpiProvider) -> None: 'mowing_completed': randint(0, 10), 'crops_detected': randint(0, 100), 'weeds_detected': randint(0, 500), + 'weeds_removed': randint(0, 500), 'punches': randint(0, 200), + 'chops': randint(0, 200), }, ) for i in range(7 * 12) @@ -49,7 +53,9 @@ def generate_kpis(kpi_provider: KpiProvider) -> None: 'mowing_completed': randint(0, 10) * 7 * 4, 'crops_detected': randint(0, 100) * 7 * 4, 'weeds_detected': randint(0, 500) * 7 * 4, + 'weeds_removed': randint(0, 500) * 7 * 4, 'punches': randint(0, 200) * 7 * 4, + 'chops': randint(0, 200) * 7 * 4, }, ) for i in range(4, 10) From c92b2ad084273dc61f4c4b3a2f9cf347c0963a94 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sun, 28 Apr 2024 00:18:36 +0200 Subject: [PATCH 04/18] Add area and worked_area properties to Field class --- field_friend/automations/field_provider.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index 96b88e13..5ba3fa65 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -84,6 +84,20 @@ def outline(self) -> list[Point]: else: return [] + @property + def area(self) -> float: + if len(self.outline_wgs84) > 0: + polygon = Polygon(self.outline_wgs84) + return polygon.area + else: + return 0.0 + + def worked_area(self, worked_rows: int) -> float: + worked_area = 0.0 + if self.area > 0: + worked_area = worked_rows * self.area / len(self.rows) + return worked_area + class Active_object(TypedDict): object_type: Literal["Obstacles", "Rows", "Outline"] From 27b440a6bff07283b2791a3a93b0d6ffbb79bdd8 Mon Sep 17 00:00:00 2001 From: Miguel Belo <99266854+angelom93@users.noreply.github.com> Date: Sun, 28 Apr 2024 00:19:34 +0200 Subject: [PATCH 05/18] Double mechanic workflow (#31) * Update WORK_X_DRILL to WORK_X in hardware and automations files * Update reachability check for FieldFriend class in field_friend.py * implement dual_mechanism workflow and improve weed_screw workflow * Use default speed values for axis simulation * further refactor and improvement of weeding * Refactor variable names for dual_mechanism in field_friend.py and field_friend_simulation.py * Refactor dual_mechanism_workflow function name in weeding.py * Fix WORK_X_DRILL to WORK_X in field_friend_object.py * Update WORK_X_DRILL to WORK_X in field_friend.py * Add backwards_allowed parameter to drive_and_punch method in puncher.py * Refactor variable names for dual_mechanism in field_friend.py and field_friend_simulation.py * catch drills and chops behind robot --- field_friend/automations/puncher.py | 12 +- field_friend/automations/weeding.py | 303 +++++++++++------- field_friend/hardware/field_friend.py | 20 +- .../hardware/field_friend_hardware.py | 2 +- .../hardware/field_friend_simulation.py | 12 +- .../components/field_friend_object.py | 4 +- field_friend/system.py | 2 +- 7 files changed, 196 insertions(+), 159 deletions(-) diff --git a/field_friend/automations/puncher.py b/field_friend/automations/puncher.py index dda31560..d9ea564b 100644 --- a/field_friend/automations/puncher.py +++ b/field_friend/automations/puncher.py @@ -44,10 +44,7 @@ async def drive_to_punch(self, local_target_x: float) -> None: rosys.notify('no y or z axis', 'negative') return self.log.info(f'Driving to punch at {local_target_x}...') - if self.field_friend.tool in ['tornado', 'weed_screw', 'none']: - work_x = self.field_friend.WORK_X - elif self.field_friend.tool in ['dual_mechanism']: - work_x = self.field_friend.WORK_X_DRILL + work_x = self.field_friend.WORK_X if local_target_x < work_x: self.log.info(f'Target: {local_target_x} is behind') axis_distance = local_target_x - work_x @@ -108,14 +105,17 @@ async def clear_view(self) -> None: await self.field_friend.y_axis.move_to(y, speed=self.field_friend.y_axis.max_speed) await self.field_friend.y_axis.stop() - async def drive_and_punch(self, x: float, y: float, depth: float = 0.05, angle: float = 180) -> None: + async def drive_and_punch(self, x: float, y: float, depth: float = 0.05, angle: float = 180, backwards_allowed: bool = True) -> None: if self.field_friend.y_axis is None or self.field_friend.z_axis is None: rosys.notify('no y or z axis', 'negative') return try: + work_x = self.field_friend.WORK_X + if x < work_x and not backwards_allowed: + self.log.warning(f'target x: {x} is behind') + return await self.drive_to_punch(x) await self.punch(y, depth=depth, angle=angle) - await self.clear_view() except Exception as e: raise PuncherException('drive and punch failed') from e diff --git a/field_friend/automations/weeding.py b/field_friend/automations/weeding.py index c82105a0..8e6f8b9c 100644 --- a/field_friend/automations/weeding.py +++ b/field_friend/automations/weeding.py @@ -1,4 +1,6 @@ import logging +from copy import deepcopy +from random import randint from typing import TYPE_CHECKING, Any, Optional import numpy as np @@ -21,6 +23,9 @@ class WorkflowException(Exception): class Weeding(rosys.persistence.PersistentModule): + WORKING_DISTANCE = 0.06 + DRIVE_DISTANCE = 0.04 + def __init__(self, system: 'System') -> None: super().__init__() self.PATH_PLANNED = rosys.event.Event() @@ -59,6 +64,7 @@ def __init__(self, system: 'System') -> None: self.row_segment_completed: bool = False self.crops_to_handle: dict[str, Point] = {} self.weeds_to_handle: dict[str, Point] = {} + self.last_chop_position = None def backup(self) -> dict: return { @@ -208,16 +214,15 @@ def _make_plan(self) -> Optional[list[list[rosys.driving.PathSegment]]]: minimum_row_distance = int( np.ceil(self.minimum_turning_radius * 2 / rows_distance)) - self.log.info(f'Minimum turning distance: {minimum_row_distance}') + self.log.info(f'Minimum row distance: {minimum_row_distance}') if minimum_row_distance > 1: sequence = find_sequence(len(rows), minimum_distance=minimum_row_distance) if not sequence: self.log.warning('No sequence found') - return None - # sequence = list(range(len(rows))) + sequence = list(range(len(rows))) else: sequence = list(range(len(rows))) - self.log.info(f'Sequence: {sequence}') + self.log.info(f'Row sequence: {sequence}') paths = [] switch_first_row = False @@ -359,6 +364,7 @@ async def _weed_with_plan(self): self.current_segment = segment self.invalidate() if not self.system.is_real: + self.system.detector.simulated_objects = [] self._create_simulated_plants() self.log.info(f'Driving row {i + 1}/{len(self.weeding_plan)} and segment {j + 1}/{len(path)}...') self.row_segment_completed = False @@ -374,6 +380,8 @@ async def _weed_with_plan(self): await self._handle_plants() self.crops_to_handle = {} self.weeds_to_handle = {} + if self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x < 0.01: + self.row_segment_completed = True await self.system.field_friend.flashlight.turn_off() self.system.plant_locator.pause() @@ -407,12 +415,12 @@ async def _weed_planless(self): await rosys.sleep(2) self.system.plant_locator.resume() await rosys.sleep(0.5) - await self._get_upcoming_crops() + await self._get_upcoming_plants() while self.crops_to_handle or self.weeds_to_handle: await self._handle_plants() already_explored_count = 0 await rosys.sleep(0.2) - await self._get_upcoming_crops() + await self._get_upcoming_plants() if not self.crops_to_handle and already_explored_count != 5: self.log.info('No crops found, advancing a bit to ensure there are really no more crops') target = self.system.odometer.prediction.transform(Point(x=0.10, y=0)) @@ -430,15 +438,16 @@ async def _drive_segment(self): async def _check_for_plants(self): self.log.info('Checking for plants...') while True: - await self._get_upcoming_crops() - if self.system.field_friend.tool == 'tornado': + await self._get_upcoming_plants() + if self.system.field_friend.tool in ['tornado', 'none'] or self.use_monitor_workflow: if self.crops_to_handle: return - if self.weeds_to_handle or self.crops_to_handle: - return + else: + if self.weeds_to_handle or self.crops_to_handle: + return await rosys.sleep(0.2) - async def _get_upcoming_crops(self): + async def _get_upcoming_plants(self): relative_crop_positions = { c.id: self.system.odometer.prediction.relative_point(c.position) for c in self.system.plant_provider.crops if c.position.distance(self.system.odometer.prediction.point) < 0.5 @@ -448,13 +457,12 @@ async def _get_upcoming_crops(self): # Correctly filter to get upcoming crops based on their x position upcoming_crop_positions = { c: pos for c, pos in relative_crop_positions.items() - if self.system.field_friend.WORK_X + self.system.field_friend.DRILL_RADIUS < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x + if self.system.field_friend.WORK_X < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x } - self.log.info(f'Upcoming crops in segment: {upcoming_crop_positions}') else: upcoming_crop_positions = { c: pos for c, pos in relative_crop_positions.items() - if self.system.field_friend.WORK_X + self.system.field_friend.DRILL_RADIUS < pos.x + if self.system.field_friend.WORK_X < pos.x < 0.4 } # Sort the upcoming_crop_positions dictionary by the .x attribute of its values @@ -470,12 +478,12 @@ async def _get_upcoming_crops(self): # Filter to get upcoming weeds based on their .x position upcoming_weed_positions = { w: pos for w, pos in relative_weed_positions.items() - if self.system.field_friend.WORK_X + self.system.field_friend.DRILL_RADIUS < pos.x <= self.current_segment.spline.end.x + if self.system.field_friend.WORK_X < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x } else: upcoming_weed_positions = { w: pos for w, pos in relative_weed_positions.items() - if self.system.field_friend.WORK_X + self.system.field_friend.DRILL_RADIUS < pos.x + if self.system.field_friend.WORK_X < pos.x < 0.4 } # Sort the upcoming_weed_positions dictionary by the .x attribute of its values @@ -490,20 +498,20 @@ async def _handle_plants(self) -> None: if self.system.field_friend.tool == 'tornado' and self.crops_to_handle and not self.use_monitor_workflow: await self._tornado_workflow() - elif self.system.field_friend.tool == 'weed_screw': + elif self.system.field_friend.tool == 'weed_screw' and not self.use_monitor_workflow: await self._weed_screw_workflow() + elif self.system.field_friend.tool == 'dual_mechanism' and not self.use_monitor_workflow: + await self._dual_mechanism_workflow() elif self.system.field_friend.tool == 'none' or self.use_monitor_workflow: await self._monitor_workflow() - # ToDo: implement workflow of other tools - async def _tornado_workflow(self) -> None: self.log.info('Starting Tornado Workflow..') try: closest_crop_position = list(self.crops_to_handle.values())[0] self.log.info(f'Closest crop position: {closest_crop_position}') # fist check if the closest crop is in the working area - if closest_crop_position.x < self.system.field_friend.WORK_X + 0.05: + if closest_crop_position.x < self.system.field_friend.WORK_X + self.WORKING_DISTANCE: self.log.info(f'target next crop at {closest_crop_position}') # do not steer while advancing on a crop drive_distance = closest_crop_position.x - self.system.field_friend.WORK_X @@ -525,20 +533,8 @@ async def _tornado_workflow(self) -> None: if not self.only_monitoring: # punch in the middle position with closed knives await self.system.puncher.punch(0, angle=180) - - self.system.plant_locator.resume() else: - self.log.info('follow line of crops') - farthest_crop = list(self.crops_to_handle.values())[-1] - self.log.info(f'Farthest crop: {farthest_crop}') - upcoming_world_position = self.system.odometer.prediction.transform(farthest_crop) - yaw = self.system.odometer.prediction.point.direction(upcoming_world_position) - # only apply minimal yaw corrections to avoid oversteering - yaw = eliminate_2pi(self.system.odometer.prediction.yaw) * 0.85 + eliminate_2pi(yaw) * 0.15 - target = self.system.odometer.prediction.point.polar(0.04, yaw) - - self.log.info(f'current world position: {self.system.odometer.prediction} target next crop at {target}') - await self.system.driver.drive_to(target) + await self._follow_line_of_crops() await rosys.sleep(0.2) self.log.info('workflow completed') except Exception as e: @@ -550,7 +546,7 @@ async def _monitor_workflow(self) -> None: closest_crop_position = list(self.crops_to_handle.values())[0] self.log.info(f'Closest crop position: {closest_crop_position}') # fist check if the closest crop is in the working area - if closest_crop_position.x < 0.05: + if closest_crop_position.x < self.WORKING_DISTANCE: self.log.info(f'target next crop at {closest_crop_position}') # do not steer while advancing on a crop target = self.system.odometer.prediction.transform(Point(x=closest_crop_position.x, y=0)) @@ -558,18 +554,9 @@ async def _monitor_workflow(self) -> None: self.system.plant_locator.resume() else: if self.crops_to_handle: - self.log.info('follow line of crops') - farthest_crop = list(self.crops_to_handle.values())[-1] - self.log.info(f'Farthest crop: {farthest_crop}') - upcoming_world_position = self.system.odometer.prediction.transform(farthest_crop) - yaw = self.system.odometer.prediction.point.direction(upcoming_world_position) - # only apply minimal yaw corrections to avoid oversteering - yaw = eliminate_2pi(self.system.odometer.prediction.yaw) * 0.85 + eliminate_2pi(yaw) * 0.15 - target = self.system.odometer.prediction.point.polar(0.04, yaw) + await self._follow_line_of_crops() else: - target = self.system.odometer.prediction.point.polar(0.04, self.system.odometer.prediction.yaw) - self.log.info(f'current world position: {self.system.odometer.prediction} target next crop at {target}') - await self.system.driver.drive_to(target) + await self._driving_a_bit_forward() await rosys.sleep(0.2) self.log.info('workflow completed') except Exception as e: @@ -578,80 +565,165 @@ async def _monitor_workflow(self) -> None: async def _weed_screw_workflow(self) -> None: self.log.info('Starting Weed Screw Workflow...') try: + starting_position = deepcopy(self.system.odometer.prediction) self._keep_crops_safe() - next_weed_position = None - if self.weeds_to_handle: - next_weed_position = list(self.weeds_to_handle.values())[0] - self.log.info(f'Next weed position: {next_weed_position}') - - if next_weed_position and next_weed_position.x < self.system.field_friend.WORK_X + 0.06: - self.log.info(f'target next weed at {next_weed_position}') - drive_distance = next_weed_position.x - self.system.field_friend.WORK_X - target = self.system.odometer.prediction.transform( - Point(x=drive_distance, y=0)) - await self.system.driver.drive_to(target) - corrected_positions = [Point(x=position.x - drive_distance, y=position.y) - for _, position in self.weeds_to_handle.items()] - for index, position in enumerate(corrected_positions): - if self.system.field_friend.can_reach(position): - if not self.only_monitoring: - await self.system.puncher.punch(position.y, depth=self.weed_screw_depth) - corrected_positions.pop(index) - self.log.info(f'Punched weed at {position}') - for index, other_position in enumerate(corrected_positions): - if position.distance(other_position) < self.system.field_friend.DRILL_RADIUS: - corrected_positions.pop(index) - self.log.info(f'Punched weed at {other_position}') - else: - self.log.info(f'Could not reach weed at {position}') - self.system.plant_locator.resume() + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items( + ) if position.x < self.system.field_friend.WORK_X + self.WORKING_DISTANCE and self.system.field_friend.can_reach(position)} + if weeds_in_range: + self.log.info(f'Weeds in range {len(weeds_in_range)}') + while weeds_in_range: + next_weed_position = list(weeds_in_range.values())[0] + weed_world_position = starting_position.transform(next_weed_position) + corrected_relative_weed_position = self.system.odometer.prediction.relative_point( + weed_world_position) + self.log.info(f'Targeting weed at {next_weed_position}') + if not self.only_monitoring: + await self.system.puncher.drive_and_punch( + corrected_relative_weed_position.x, corrected_relative_weed_position.y, depth=self.weed_screw_depth, backwards_allowed=False) + punched_weeds = [weed_id for weed_id, position in weeds_in_range.items( + ) if position.distance(next_weed_position) <= self.system.field_friend.DRILL_RADIUS] + for weed_id in punched_weeds: + self.system.plant_provider.remove_weed(weed_id) + if weed_id in weeds_in_range: + del weeds_in_range[weed_id] + elif self.crops_to_handle: - self.log.info('follow line of crops') - farthest_crop = list(self.crops_to_handle.values())[-1] - self.log.info(f'Farthest crop: {farthest_crop}') - upcoming_world_position = self.system.odometer.prediction.transform(farthest_crop) - yaw = self.system.odometer.prediction.point.direction(upcoming_world_position) - # only apply minimal yaw corrections to avoid oversteering - yaw = eliminate_2pi(self.system.odometer.prediction.yaw) * 0.85 + eliminate_2pi(yaw) * 0.15 - target = self.system.odometer.prediction.point.polar(0.04, yaw) - - self.log.info(f'current world position: {self.system.odometer.prediction} target next crop at {target}') - await self.system.driver.drive_to(target) + await self._follow_line_of_crops() else: - self.log.info('No crops and no weeeds in range, driving forward a bit...') - target = self.system.odometer.prediction.point.polar(0.04, self.system.odometer.prediction.yaw) - self.log.info(f'current world position: {self.system.odometer.prediction} target: {target}') - await self.system.driver.drive_to(target) - + await self._driving_a_bit_forward() await rosys.sleep(0.2) - self.log.info('workflow completed') + self.log.info('Workflow completed') except Exception as e: raise WorkflowException(f'Error while Weed Screw Workflow: {e}') from e + async def _dual_mechanism_workflow(self) -> None: + self.log.info('Starting dual mechanism workflow...') + try: + moved = False + starting_position = deepcopy(self.system.odometer.prediction) + if self.crops_to_handle: + next_crop_position = list(self.crops_to_handle.values())[0] + # first: check if weeds near crop + self._keep_crops_safe() + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if next_crop_position.x - self.system.field_friend.DRILL_RADIUS*2 + < position.x < next_crop_position.x + self.system.field_friend.DRILL_RADIUS*2 and self.system.field_friend.can_reach(position)} + self.log.info(f'weed_position in range: {weeds_in_range.items()}') + if weeds_in_range: + self.log.info(f' {len(weeds_in_range)} Weeds in range for drilling') + while weeds_in_range: + next_weed_position = list(weeds_in_range.values())[0] + self.log.info(f'Next weed position: {next_weed_position}') + weed_world_position = starting_position.transform(next_weed_position) + corrected_relative_weed_position = self.system.odometer.prediction.relative_point( + weed_world_position) + self.log.info(f'corrected relative weed position: {corrected_relative_weed_position}') + moved = True + if not self.only_monitoring: + await self.system.puncher.drive_and_punch( + corrected_relative_weed_position.x, next_weed_position.y, depth=self.weed_screw_depth, backwards_allowed=False) + punched_weeds = [weed_id for weed_id, position in weeds_in_range.items( + ) if position.distance(next_weed_position) < self.system.field_friend.DRILL_RADIUS] + for weed_id in punched_weeds: + self.system.plant_provider.remove_weed(weed_id) + if weed_id in weeds_in_range: + del weeds_in_range[weed_id] + await self.system.puncher.clear_view() + # second: check if weed before crop + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if position.x < next_crop_position.x - ( + self.system.field_friend.DRILL_RADIUS) and self.system.field_friend.can_reach(position, second_tool=True)} + if weeds_in_range: + self.log.info('Weeds in range for chopping before crop') + crop_world_position = starting_position.transform(next_crop_position) + corrected_relative_crop_position = self.system.odometer.prediction.relative_point( + crop_world_position) + target_position = corrected_relative_crop_position.x - \ + self.system.field_friend.DRILL_RADIUS - self.system.field_friend.CHOP_RADIUS + axis_distance = target_position - self.system.field_friend.WORK_X_CHOP + if axis_distance >= 0: + local_target = Point(x=axis_distance, y=0) + world_target = self.system.driver.prediction.transform(local_target) + moved = True + await self.system.driver.drive_to(world_target) + if not self.only_monitoring: + await self.system.puncher.chop() + choped_weeds = [weed_id for weed_id, position in self.weeds_to_handle.items( + ) if target_position - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < target_position + self.system.field_friend.CHOP_RADIUS] + for weed_id in choped_weeds: + self.system.plant_provider.remove_weed(weed_id) + else: + self.log.warning(f'Weed position {next_weed_position} is behind field friend') + if not moved: + await self._follow_line_of_crops() + moved = True + elif self.weeds_to_handle: + self.log.info('No crops in range, checking for weeds...') + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if self.system.field_friend.WORK_X_CHOP < + position.x < self.system.field_friend.WORK_X + self.WORKING_DISTANCE and self.system.field_friend.can_reach(position)} + if weeds_in_range: + next_weed_position = list(weeds_in_range.values())[0] + axis_distance = next_weed_position.x - self.system.field_friend.WORK_X_CHOP + self.system.field_friend.CHOP_RADIUS + if axis_distance >= 0: + local_target = Point(x=axis_distance, y=0) + self.log.info(f'Next weed position: {next_weed_position}') + self.log.info(f'Axis distance: {axis_distance}') + world_target = self.system.driver.prediction.transform(local_target) + moved = True + await self.system.driver.drive_to(world_target) + if not self.only_monitoring: + await self.system.puncher.chop() + choped_weeds = [weed_id for weed_id, position in self.weeds_to_handle.items( + ) if axis_distance - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < axis_distance + self.system.field_friend.CHOP_RADIUS] + for weed_id in choped_weeds: + self.system.plant_provider.remove_weed(weed_id) + else: + self.log.warning(f'Weed position {next_weed_position} is behind field friend') + if not moved: + await self._driving_a_bit_forward() + await rosys.sleep(0.2) + self.log.info('Workflow completed') + except Exception as e: + raise WorkflowException(f'Error while double mechanism Workflow: {e}') from e + + async def _follow_line_of_crops(self): + self.log.info('Following line of crops...') + farthest_crop = list(self.crops_to_handle.values())[-1] + self.log.info(f'Farthest crop: {farthest_crop}') + upcoming_world_position = self.system.odometer.prediction.transform(farthest_crop) + yaw = self.system.odometer.prediction.point.direction(upcoming_world_position) + # only apply minimal yaw corrections to avoid oversteering + yaw = eliminate_2pi(self.system.odometer.prediction.yaw) * 0.85 + eliminate_2pi(yaw) * 0.15 + target = self.system.odometer.prediction.point.polar(self.DRIVE_DISTANCE, yaw) + self.log.info(f'Current world position: {self.system.odometer.prediction} Target next crop at {target}') + await self.system.driver.drive_to(target) + + async def _driving_a_bit_forward(self): + self.log.info('No crops and no weeds in range, driving forward a bit...') + target = self.system.odometer.prediction.point.polar(self.DRIVE_DISTANCE, self.system.odometer.prediction.yaw) + self.log.info(f'Current world position: {self.system.odometer.prediction} Target: {target}') + await self.system.driver.drive_to(target) + def _keep_crops_safe(self) -> None: self.log.info('Keeping crops safe...') for crop, crop_position in self.crops_to_handle.items(): - if crop_position.x > 0.06: - continue for weed, weed_position in self.weeds_to_handle.items(): - if weed_position.x > 0.06: - continue offset = self.system.field_friend.DRILL_RADIUS + \ self.crop_safety_distance - crop_position.distance(weed_position) if offset > 0: safe_weed_position = weed_position.polar(offset, crop_position.direction(weed_position)) self.weeds_to_handle[weed] = safe_weed_position - self.log.info(f'Moved weed {weed} to {safe_weed_position} to safe {crop} at {crop_position}') + self.log.info( + f'Moved weed {weed} from {weed_position} to {safe_weed_position} by {offset} to safe {crop} at {crop_position}') def _safe_crop_to_row(self, crop_id: str) -> None: if self.current_row is None: return + self.log.info(f'Saving crop {crop_id} to row {self.current_row.name}...') crop = next((c for c in self.system.plant_provider.crops if c.id == crop_id), None) if crop is None: self.log.error(f'Error in crop saving: Crop with id {crop_id} not found') return for c in self.current_row.crops: - if c.position.distance(crop.position) < 0.08 and c.type == crop.type: + if c.position.distance(crop.position) < 0.05 and c.type == crop.type: if c.confidence >= crop.confidence: return self.log.info('Updating crop with higher confidence') @@ -664,60 +736,43 @@ def _safe_crop_to_row(self, crop_id: str) -> None: def _create_simulated_plants(self): self.log.info('Creating simulated plants...') if self.current_segment: - first_point = self.current_segment.spline.start - last_point = self.current_segment.spline.end + self.log.info('Creating simulated plants for current segment') distance = self.current_segment.spline.start.distance(self.current_segment.spline.end) for i in range(1, int(distance/0.20)): self.system.plant_provider.add_crop(Plant( id=str(i), type='beet', - position=first_point.polar(0.20*i, first_point.direction(last_point)), + position=self.system.odometer.prediction.point.polar( + 0.20*i, self.system.odometer.prediction.yaw).polar(randint(-2, 2)*0.01, self.system.odometer.prediction.yaw+np.pi/2), detection_time=rosys.time(), confidence=0.9, )) - for j in range(0, 2): + for j in range(1, 7): self.system.plant_provider.add_weed(Plant( - id=str(i), + id=f'{i}_{j}', type='weed', - position=first_point.polar(0.20*i, first_point.direction(last_point) - ).polar(0.04*j, first_point.direction(last_point) + 1.57), - detection_time=rosys.time(), - confidence=0.9, - )) - for j in range(0, 2): - self.system.plant_provider.add_weed(Plant( - id=str(i), - type='weed', - position=first_point.polar(0.20*i, first_point.direction(last_point) - ).polar(0.02*j, first_point.direction(last_point) - 1.57), + position=self.system.odometer.prediction.point.polar( + 0.20*i+randint(-5, 5)*0.01, self.system.odometer.prediction.yaw).polar(randint(-15, 15)*0.01, self.system.odometer.prediction.yaw + np.pi/2), detection_time=rosys.time(), confidence=0.9, )) else: + self.log.info('Creating simulated plants for whole row') for i in range(0, 30): self.system.plant_provider.add_crop(Plant( id=str(i), type='beet', - position=self.system.odometer.prediction.point.polar(0.20*i, self.system.odometer.prediction.yaw), + position=self.system.odometer.prediction.point.polar( + 0.20*i, self.system.odometer.prediction.yaw).polar(randint(-2, 2)*0.01, self.system.odometer.prediction.yaw+np.pi/2), detection_time=rosys.time(), confidence=0.9, )) - for i in range(0, 30): - for j in range(0, 2): - self.system.plant_provider.add_weed(Plant( - id=str(i), - type='weed', - position=self.system.odometer.prediction.point.polar( - 0.20*i, self.system.odometer.prediction.yaw).polar(0.02*j, self.system.odometer.prediction.yaw + 1.57), - detection_time=rosys.time(), - confidence=0.9, - )) - for j in range(0, 2): + for j in range(1, 7): self.system.plant_provider.add_weed(Plant( - id=str(i), + id=f'{i}_{j}', type='weed', position=self.system.odometer.prediction.point.polar( - 0.20*i, self.system.odometer.prediction.yaw).polar(0.02*j, self.system.odometer.prediction.yaw - 1.57), + 0.20*i+randint(-5, 5)*0.01, self.system.odometer.prediction.yaw).polar(randint(-15, 15)*0.01, self.system.odometer.prediction.yaw + np.pi/2), detection_time=rosys.time(), confidence=0.9, )) diff --git a/field_friend/hardware/field_friend.py b/field_friend/hardware/field_friend.py index 3b69772e..002cb39b 100644 --- a/field_friend/hardware/field_friend.py +++ b/field_friend/hardware/field_friend.py @@ -21,7 +21,6 @@ class FieldFriend(rosys.hardware.Robot): DRILL_RADIUS = 0.025 CHOP_RADIUS = 0.07 WORK_X_CHOP = 0.04 - WORK_X_DRILL = 0.175 def __init__( self, *, @@ -61,21 +60,12 @@ def can_reach(self, local_point: rosys.geometry.Point, second_tool: bool = False The point is given in local coordinates, i.e. the origin is the center of the tool. """ - if self.tool in ['weed_screw'] and isinstance(self.y_axis, YAxis): - return self.WORK_X - self.DRILL_RADIUS <= local_point.x <= self.WORK_X + self.DRILL_RADIUS \ - and self.y_axis.min_position <= local_point.y <= self.y_axis.max_position - elif self.tool in ['tornado'] and isinstance(self.y_axis, YAxis): + if self.tool in ['weed_screw', 'tornado'] and isinstance(self.y_axis, YAxis): return self.y_axis.min_position <= local_point.y <= self.y_axis.max_position - elif self.tool in ['double_mechanism'] and isinstance(self.y_axis, ChainAxis): - if not second_tool: - work_x = self.WORK_X_CHOP - tool_radius = self.CHOP_RADIUS - return work_x - tool_radius <= local_point.x <= work_x + tool_radius \ - and self.y_axis.MIN_POSITION <= local_point.y <= self.y_axis.MAX_POSITION + elif self.tool in ['dual_mechanism'] and isinstance(self.y_axis, ChainAxis): + if second_tool: + return self.y_axis.MIN_POSITION <= local_point.y <= self.y_axis.MAX_POSITION else: - work_x = self.WORK_X_DRILL - tool_radius = self.DRILL_RADIUS - return work_x - tool_radius <= local_point.x <= work_x + tool_radius \ - and self.y_axis.min_position <= local_point.y <= self.y_axis.max_position + return self.y_axis.min_position <= local_point.y <= self.y_axis.max_position else: raise NotImplementedError(f'Tool {self.tool} is not implemented for reachability check') diff --git a/field_friend/hardware/field_friend_hardware.py b/field_friend/hardware/field_friend_hardware.py index 36e3a6cb..3c3e562a 100644 --- a/field_friend/hardware/field_friend_hardware.py +++ b/field_friend/hardware/field_friend_hardware.py @@ -43,7 +43,7 @@ def __init__(self) -> None: self.DRILL_RADIUS: float = config_params['drill_radius'] elif tool in ['dual_mechanism']: self.WORK_X_CHOP: float = config_params['work_x_chop'] - self.WORK_X_DRILL: float = config_params['work_x_drill'] + self.WORK_X: float = config_params['work_x_drill'] self.DRILL_RADIUS = config_params['drill_radius'] self.CHOP_RADIUS: float = config_params['chop_radius'] else: diff --git a/field_friend/hardware/field_friend_simulation.py b/field_friend/hardware/field_friend_simulation.py index 189fe90e..61c32ffa 100644 --- a/field_friend/hardware/field_friend_simulation.py +++ b/field_friend/hardware/field_friend_simulation.py @@ -30,9 +30,9 @@ def __init__(self, robot_id) -> None: if tool in ['tornado', 'weed_screw', 'none']: self.WORK_X = config_params['work_x'] self.DRILL_RADIUS = config_params['drill_radius'] - elif tool in ['double_mechanism']: + elif tool in ['dual_mechanism']: self.WORK_X_CHOP = config_params['work_x_chop'] - self.WORK_X_DRILL = config_params['work_x_drill'] + self.WORK_X = config_params['work_x_drill'] self.DRILL_RADIUS = config_params['drill_radius'] self.CHOP_RADIUS = config_params['chop_radius'] else: @@ -44,13 +44,9 @@ def __init__(self, robot_id) -> None: y_axis = ChainAxisSimulation() elif config_hardware['y_axis']['version'] in ['y_axis_stepper', 'y_axis_canopen']: y_axis = YAxisSimulation( - max_speed=config_hardware['y_axis']['max_speed'], - reference_speed=config_hardware['y_axis']['reference_speed'], min_position=config_hardware['y_axis']['min_position'], max_position=config_hardware['y_axis']['max_position'], axis_offset=config_hardware['y_axis']['axis_offset'], - steps_per_m=config_hardware['y_axis']['steps_per_m'], - reversed_direction=config_hardware['y_axis']['reversed_direction'] ) elif config_hardware['y_axis']['version'] == 'none': y_axis = None @@ -60,13 +56,9 @@ def __init__(self, robot_id) -> None: z_axis: ZAxisSimulation | TornadoSimulation | None if config_hardware['z_axis']['version'] in ['z_axis_stepper', 'z_axis_canopen']: z_axis = ZAxisSimulation( - max_speed=config_hardware['z_axis']['max_speed'], - reference_speed=config_hardware['z_axis']['reference_speed'], min_position=config_hardware['z_axis']['min_position'], max_position=config_hardware['z_axis']['max_position'], axis_offset=config_hardware['z_axis']['axis_offset'], - steps_per_m=config_hardware['z_axis']['steps_per_m'], - reversed_direction=config_hardware['z_axis']['reversed_direction'] ) elif config_hardware['z_axis']['version'] == 'tornado': diff --git a/field_friend/interface/components/field_friend_object.py b/field_friend/interface/components/field_friend_object.py index c1e607e3..a7a3520f 100644 --- a/field_friend/interface/components/field_friend_object.py +++ b/field_friend/interface/components/field_friend_object.py @@ -43,12 +43,12 @@ def update(self) -> None: elif isinstance(self.robot.y_axis, ChainAxis): if self.robot.y_axis.MIN_POSITION <= self.robot.y_axis.position <= self.robot.y_axis.MAX_POSITION: self.tool.move(x=self.robot.WORK_X_CHOP, y=self.robot.y_axis.position) - self.second_tool.move(x=self.robot.WORK_X_DRILL, y=self.robot.y_axis.position, + self.second_tool.move(x=self.robot.WORK_X, y=self.robot.y_axis.position, z=self.robot.z_axis.position) elif self.robot.y_axis.position > self.robot.y_axis.MAX_POSITION: difference = self.robot.y_axis.position - self.robot.y_axis.MAX_POSITION self.tool.move(x=self.robot.WORK_X_CHOP, y=self.robot.y_axis.MAX_POSITION - difference) - self.second_tool.move(x=self.robot.WORK_X_DRILL, y=self.robot.y_axis.MAX_POSITION - difference) + self.second_tool.move(x=self.robot.WORK_X, y=self.robot.y_axis.MAX_POSITION - difference) else: difference = self.robot.y_axis.MIN_POSITION - self.robot.y_axis.position self.tool.move(x=self.robot.WORK_X_CHOP, y=self.robot.y_axis.MIN_POSITION + difference) diff --git a/field_friend/system.py b/field_friend/system.py index d3551091..3b7415d3 100644 --- a/field_friend/system.py +++ b/field_friend/system.py @@ -32,7 +32,7 @@ def __init__(self) -> None: self.monitoring_detector = rosys.vision.DetectorHardware(port=8005) self.camera_configurator = CameraConfigurator(self.usb_camera_provider) else: - version = 'rb33' # insert here your field friend version to be simulated + version = 'rb27' # insert here your field friend version to be simulated self.field_friend = FieldFriendSimulation(robot_id=version) self.usb_camera_provider = SimulatedCamProvider() self.usb_camera_provider.remove_all_cameras() From a913d3e09ebabd35d2bd70fd3128c3a985d99995 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sun, 28 Apr 2024 01:33:44 +0200 Subject: [PATCH 06/18] Add KPI tracking for weeds removed and chops in status_dev.py --- field_friend/automations/weeding.py | 33 +++++++++++++++++-- .../interface/components/status_dev.py | 25 +++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/field_friend/automations/weeding.py b/field_friend/automations/weeding.py index 8e6f8b9c..e13baa23 100644 --- a/field_friend/automations/weeding.py +++ b/field_friend/automations/weeding.py @@ -56,6 +56,10 @@ def __init__(self, system: 'System') -> None: # driver settings + self.state: str = 'idle' + self.start_time: Optional[float] = None + self.last_pose: Optional[Pose] = None + self.drived_distance: float = 0.0 self.sorted_weeding_rows: list = [] self.weeding_plan: Optional[list[list[PathSegment]]] = None self.turn_paths: list[list[PathSegment]] = [] @@ -64,7 +68,26 @@ def __init__(self, system: 'System') -> None: self.row_segment_completed: bool = False self.crops_to_handle: dict[str, Point] = {} self.weeds_to_handle: dict[str, Point] = {} - self.last_chop_position = None + + rosys.on_repeat(self._update_time_and_distance, 0.1) + + def _update_time_and_distance(self): + if self.state == 'idle': + return + if self.start_time is None: + self.start_time = rosys.time() + if self.last_pose is None: + self.last_pose = self.system.odometer.prediction + self.drived_distance = 0.0 + self.drived_distance += self.system.odometer.prediction.distance(self.last_pose) + if self.drived_distance > 1: + self.kpi_provider.increment_weeding_kpi('distance') + self.drived_distance -= 1 + self.last_pose = self.system.odometer.prediction + passed_time = rosys.time() - self.start_time + if passed_time > 1: + self.kpi_provider.increment_weeding_kpi('time') + self.start_time = rosys.time() def backup(self) -> dict: return { @@ -305,7 +328,9 @@ async def _generate_turn_paths(self) -> list[list[PathSegment]]: async def _weeding(self): self.log.info('Starting driving...') await rosys.sleep(0.5) + self.state = 'running' try: + if self.weeding_plan: await self._weed_with_plan() self.log.info('Weeding with plan completed') @@ -314,7 +339,7 @@ async def _weeding(self): self.log.info('Planless weeding completed') except WorkflowException as e: - self.kpi_provider.increment('automation_stopped') + self.kpi_provider.increment_weeding_kpi('automation_stopped') self.log.error(f'WorkflowException: {e}') finally: self.kpi_provider.increment_weeding_kpi('weeding_completed') @@ -586,6 +611,7 @@ async def _weed_screw_workflow(self) -> None: self.system.plant_provider.remove_weed(weed_id) if weed_id in weeds_in_range: del weeds_in_range[weed_id] + self.kpi_provider.increment_weeding_kpi('weeds_removed') elif self.crops_to_handle: await self._follow_line_of_crops() @@ -627,6 +653,7 @@ async def _dual_mechanism_workflow(self) -> None: self.system.plant_provider.remove_weed(weed_id) if weed_id in weeds_in_range: del weeds_in_range[weed_id] + self.kpi_provider.increment_weeding_kpi('weeds_removed') await self.system.puncher.clear_view() # second: check if weed before crop weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if position.x < next_crop_position.x - ( @@ -650,6 +677,7 @@ async def _dual_mechanism_workflow(self) -> None: ) if target_position - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < target_position + self.system.field_friend.CHOP_RADIUS] for weed_id in choped_weeds: self.system.plant_provider.remove_weed(weed_id) + self.kpi_provider.increment_weeding_kpi('weeds_removed') else: self.log.warning(f'Weed position {next_weed_position} is behind field friend') if not moved: @@ -675,6 +703,7 @@ async def _dual_mechanism_workflow(self) -> None: ) if axis_distance - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < axis_distance + self.system.field_friend.CHOP_RADIUS] for weed_id in choped_weeds: self.system.plant_provider.remove_weed(weed_id) + self.kpi_provider.increment_weeding_kpi('weeds_removed') else: self.log.warning(f'Weed position {next_weed_position} is behind field friend') if not moved: diff --git a/field_friend/interface/components/status_dev.py b/field_friend/interface/components/status_dev.py index 58c462df..70a09485 100644 --- a/field_friend/interface/components/status_dev.py +++ b/field_friend/interface/components/status_dev.py @@ -115,6 +115,9 @@ def status_dev_page(robot: FieldFriend, system: 'System'): with ui.row().classes('place-items-center'): ui.markdown('**Current Field:**').style('color: #EDF4FB') current_field_label = ui.label() + with ui.row().classes('place-items-center'): + ui.markdown('**Worked Area:**').style('color: #EDF4FB') + worked_area_label = ui.label() with ui.row().classes('place-items-center'): ui.markdown('**Current Row:**').style('color: #EDF4FB') current_row_label = ui.label() @@ -133,9 +136,16 @@ def status_dev_page(robot: FieldFriend, system: 'System'): with ui.row().classes('place-items-center'): ui.markdown('**Weeds Detected:**').style('color: #EDF4FB') kpi_weeds_detected_label = ui.label() + with ui.row().classes('place-items-center'): + ui.markdown('**Weeds Removed:**').style('color: #EDF4FB') + kpi_weeds_removed_label = ui.label() with ui.row().classes('place-items-center'): ui.markdown('**Punches:**').style('color: #EDF4FB') kpi_punches_label = ui.label() + if robot.tool == 'dual_mechanism': + with ui.row().classes('place-items-center'): + ui.markdown('**Chops:**').style('color: #EDF4FB') + kpi_chops_label = ui.label() with ui.card().style('background-color: #3E63A6; color: white;'): ui.markdown('**Positioning**').style('color: #6E93D6').classes('w-full text-center') @@ -255,19 +265,26 @@ def get_jetson_cpu_temperature(): if system.automator.is_running: if system.field_provider.active_field is not None: current_field_label.text = system.field_provider.active_field.name - kpi_fieldtime_label.text = system.kpi_provider.current_weeding_kpis.time - kpi_distance_label.text = system.kpi_provider.current_weeding_kpis.distance + kpi_fieldtime_label.text = f'{system.kpi_provider.current_weeding_kpis.time:.2f}s' + kpi_distance_label.text = f'{system.kpi_provider.current_weeding_kpis.distance:.0f}m' current_automation = next(key for key, value in system.automations.items() if value == system.automator.default_automation) if current_automation == 'weeding' or current_automation == 'monitoring': - if system.field_provider.active_object is not None and system.field_provider.active_object['object'] is not None: - current_row_label.text = system.field_provider.active_object['object'].name + if current_automation == 'weeding': + current_row_label.text = system.weeding.current_row.name if system.weeding.current_row is not None else 'No row' + worked_area_label.text = f'{system.weeding.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.weeding.field.area:.2f}m²' if system.weeding.field is not None else 'No field' + elif current_automation == 'monitoring': + current_row_label.text = system.monitoring.current_row.name if system.monitoring.current_row is not None else 'No row' + worked_area_label.text = f'{system.monitoring.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.monitoring.field.area:.2f}m²' if system.monitoring.field is not None else 'No field' kpi_weeds_detected_label.text = system.kpi_provider.current_weeding_kpis.weeds_detected kpi_crops_detected_label.text = system.kpi_provider.current_weeding_kpis.crops_detected + kpi_weeds_removed_label.text = system.kpi_provider.current_weeding_kpis.weeds_removed kpi_rows_weeded_label.text = system.kpi_provider.current_weeding_kpis.rows_weeded if current_automation == 'weeding': kpi_punches_label.text = system.kpi_provider.current_weeding_kpis.punches + if robot.tool == 'dual_mechanism': + kpi_chops_label.text = system.kpi_provider.current_weeding_kpis.chops gnss_device_label.text = 'No connection' if system.gnss.device is None else 'Connected' reference_position_label.text = 'No reference' if system.gnss.reference_lat is None else 'Set' From 49d3e5b8aff658d620354ca845a48fc1999ffb33 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sun, 28 Apr 2024 01:34:01 +0200 Subject: [PATCH 07/18] Refactor field_planner.py, geodata_picker.py, and leaflet_map.py to use field names with incremental numbers --- field_friend/automations/field_provider.py | 4 ++-- field_friend/interface/components/field_planner.py | 6 +++--- .../interface/components/geodata_picker.py | 2 +- field_friend/interface/components/leaflet_map.py | 14 ++++++++------ 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index 5ba3fa65..1360bac1 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -86,8 +86,8 @@ def outline(self) -> list[Point]: @property def area(self) -> float: - if len(self.outline_wgs84) > 0: - polygon = Polygon(self.outline_wgs84) + if len(self.outline) > 0: + polygon = Polygon([(p.x, p.y) for p in self.outline]) return polygon.area else: return 0.0 diff --git a/field_friend/interface/components/field_planner.py b/field_friend/interface/components/field_planner.py index aacffda7..e9bcbefb 100644 --- a/field_friend/interface/components/field_planner.py +++ b/field_friend/interface/components/field_planner.py @@ -343,7 +343,7 @@ def remove_point(self, field: Field, point: Optional[list] = None) -> None: def add_field(self) -> None: new_id = str(uuid.uuid4()) - field = Field(id=f'{new_id}', name=f'{new_id}', outline_wgs84=[]) + field = Field(id=f'{new_id}', name=f'field_{len(self.field_provider.fields)+1}', outline_wgs84=[]) self.field_provider.add_field(field) def delete_field(self, field: Field) -> None: @@ -353,7 +353,7 @@ def clear_fields(self) -> None: self.field_provider.clear_fields() def add_obstacle(self, field: Field) -> None: - obstacle = FieldObstacle(id=f'{str(uuid.uuid4())}', name=f'{str(uuid.uuid4())}', points_wgs84=[]) + obstacle = FieldObstacle(id=f'{str(uuid.uuid4())}', name=f'obstacle_{len(field.obstacles)+1}', points_wgs84=[]) self.field_provider.add_obstacle(field, obstacle) async def add_obstacle_point(self, field: Field, obstacle: FieldObstacle, point: Optional[list] = None, new_point: Optional[list] = None) -> None: @@ -399,7 +399,7 @@ def remove_obstacle_point(self, obstacle: FieldObstacle, point: Optional[list] = self.field_provider.invalidate() def add_row(self, field: Field) -> None: - row = Row(id=f'{str(uuid.uuid4())}', name=f'{str(uuid.uuid4())}', points_wgs84=[]) + row = Row(id=f'{str(uuid.uuid4())}', name=f'row_{len(field.rows)+1}', points_wgs84=[]) self.field_provider.add_row(field, row) def add_row_point(self, field: Field, row: Row, point: Optional[list] = None, new_point: Optional[list] = None) -> None: diff --git a/field_friend/interface/components/geodata_picker.py b/field_friend/interface/components/geodata_picker.py index d783c063..cf15375b 100644 --- a/field_friend/interface/components/geodata_picker.py +++ b/field_friend/interface/components/geodata_picker.py @@ -97,7 +97,7 @@ async def restore_from_file(self, e: events.UploadEventArguments) -> None: coordinates.pop() reference_point = coordinates[0] new_id = str(uuid.uuid4()) - field = Field(id=f'{new_id}', name=f'{new_id}', outline_wgs84=coordinates, + field = Field(id=f'{new_id}', name=f'field_{len(self.field_provider.fields)+1}', outline_wgs84=coordinates, reference_lat=reference_point[0], reference_lon=reference_point[1]) self.field_provider.add_field(field) return diff --git a/field_friend/interface/components/leaflet_map.py b/field_friend/interface/components/leaflet_map.py index f904e1ec..f5799206 100644 --- a/field_friend/interface/components/leaflet_map.py +++ b/field_friend/interface/components/leaflet_map.py @@ -1,13 +1,15 @@ import logging -from typing import TYPE_CHECKING, Dict, List, Union import uuid -import rosys.geometry -from nicegui import events, ui, elements +from typing import TYPE_CHECKING, Dict, List, Union + +import numpy as np import rosys -from .key_controls import KeyControls +import rosys.geometry +from nicegui import elements, events, ui + from ...automations import Field -import numpy as np +from .key_controls import KeyControls if TYPE_CHECKING: from field_friend.system import System @@ -91,7 +93,7 @@ def handle_draw(e: events.GenericEventArguments): point_list = [] for point in coordinates[0]: point_list.append([point['lat'], point['lng']]) - field = Field(id=f'{str(uuid.uuid4())}', name=f'{str(uuid.uuid4())}', + field = Field(id=f'{str(uuid.uuid4())}', name=f'field_{len(self.field_provider.fields)+1}', outline_wgs84=point_list, reference_lat=point_list[0][0], reference_lon=point_list[0][1]) self.field_provider.add_field(field) From 85d30b7fe025f5fb18e399b2cc5f41e391c23635 Mon Sep 17 00:00:00 2001 From: Miguel Date: Mon, 29 Apr 2024 10:44:19 +0200 Subject: [PATCH 08/18] Refactor weeding.py to add new tornado drill options --- field_friend/automations/weeding.py | 32 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/field_friend/automations/weeding.py b/field_friend/automations/weeding.py index e13baa23..f4ec28cf 100644 --- a/field_friend/automations/weeding.py +++ b/field_friend/automations/weeding.py @@ -48,6 +48,8 @@ def __init__(self, system: 'System') -> None: # workflow settings self.only_monitoring: bool = False + self.drill_with_open_tornado: bool = False + self.drill_between_crops: bool = False # tool settings self.tornado_angle: float = 110.0 @@ -539,25 +541,32 @@ async def _tornado_workflow(self) -> None: if closest_crop_position.x < self.system.field_friend.WORK_X + self.WORKING_DISTANCE: self.log.info(f'target next crop at {closest_crop_position}') # do not steer while advancing on a crop - drive_distance = closest_crop_position.x - self.system.field_friend.WORK_X - target = self.system.odometer.prediction.transform(Point(x=drive_distance, y=0)) - await self.system.driver.drive_to(target) + if not self.only_monitoring and self.system.field_friend.can_reach(closest_crop_position): - await self.system.puncher.punch(closest_crop_position.y, angle=self.tornado_angle) - if len(self.crops_to_handle) > 1: + await self.system.puncher.drive_and_punch(closest_crop_position.x, closest_crop_position.y, angle=self.tornado_angle) + if self.drill_with_open_tornado: + await self.system.puncher.punch(closest_crop_position.y, angle=0) + else: + drive_distance = closest_crop_position.x - self.system.field_friend.WORK_X + target = self.system.odometer.prediction.transform(Point(x=drive_distance, y=0)) + await self.system.driver.drive_to(target) + + if len(self.crops_to_handle) > 1 and self.drill_between_crops: self.log.info('checking for second closest crop') second_closest_crop_position = list(self.crops_to_handle.values())[1] distance_to_next_crop = closest_crop_position.distance(second_closest_crop_position) if distance_to_next_crop > 0.15: # get the target of half the distance between the two crops + target = closest_crop_position.x + distance_to_next_crop / 2 self.log.info(f'driving to position between two crops: {target}') - target = self.system.odometer.prediction.transform( - Point(x=closest_crop_position.x + distance_to_next_crop / 2 - self.system.field_friend.WORK_X, y=0)) - await self.system.driver.drive_to(target) - self.log.info(f'target between two crops at {target}') if not self.only_monitoring: # punch in the middle position with closed knives - await self.system.puncher.punch(0, angle=180) + await self.system.puncher.drive_and_punch(target, 0, angle=180) + else: + drive_distance = target - self.system.field_friend.WORK_X + target = self.system.odometer.prediction.transform(Point(x=drive_distance, y=0)) + await self.system.driver.drive_to(target) + else: await self._follow_line_of_crops() await rosys.sleep(0.2) @@ -574,7 +583,8 @@ async def _monitor_workflow(self) -> None: if closest_crop_position.x < self.WORKING_DISTANCE: self.log.info(f'target next crop at {closest_crop_position}') # do not steer while advancing on a crop - target = self.system.odometer.prediction.transform(Point(x=closest_crop_position.x, y=0)) + drive_distance = closest_crop_position.x - self.system.field_friend.WORK_X + target = self.system.odometer.prediction.transform(Point(x=drive_distance, y=0)) await self.system.driver.drive_to(target) self.system.plant_locator.resume() else: From 8238232f405404ebeaae922254036ee2aa71327d Mon Sep 17 00:00:00 2001 From: Johannes-Thiel Date: Mon, 29 Apr 2024 10:58:05 +0200 Subject: [PATCH 09/18] Update field_friend_object.py to support Tornado hardware in addition to YAxis --- .../interface/components/field_friend_object.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/field_friend/interface/components/field_friend_object.py b/field_friend/interface/components/field_friend_object.py index a7a3520f..d9357fe9 100644 --- a/field_friend/interface/components/field_friend_object.py +++ b/field_friend/interface/components/field_friend_object.py @@ -4,7 +4,7 @@ from rosys.geometry import Prism from rosys.vision import CameraProjector, CameraProvider, camera_objects -from ...hardware import ChainAxis, FieldFriend, YAxis +from ...hardware import ChainAxis, FieldFriend, Tornado, YAxis, ZAxis class field_friend_object(robot_object): @@ -34,12 +34,18 @@ def __init__(self, odometer: Odometer, camera_provider: CameraProvider, def update(self) -> None: super().update() - if isinstance(self.robot.y_axis, YAxis): + if isinstance(self.robot.y_axis, YAxis) and isinstance(self.robot.z_axis, ZAxis): self.tool.move(x=self.robot.WORK_X, y=self.robot.y_axis.position, z=self.robot.z_axis.position) if self.robot.y_axis.position > self.robot.y_axis.max_position or self.robot.y_axis.position < self.robot.y_axis.min_position: self.tool.material('red') else: self.tool.material('#4488ff') + elif isinstance(self.robot.y_axis, YAxis) and isinstance(self.robot.z_axis, Tornado): + self.tool.move(x=self.robot.WORK_X, y=self.robot.y_axis.position, z=self.robot.z_axis.position_z) + if self.robot.y_axis.position > self.robot.y_axis.max_position or self.robot.y_axis.position < self.robot.y_axis.min_position: + self.tool.material('red') + else: + self.tool.material('#4488ff') elif isinstance(self.robot.y_axis, ChainAxis): if self.robot.y_axis.MIN_POSITION <= self.robot.y_axis.position <= self.robot.y_axis.MAX_POSITION: self.tool.move(x=self.robot.WORK_X_CHOP, y=self.robot.y_axis.position) From f4d0a9b56cbd64811e702827bfe2133f74222d8d Mon Sep 17 00:00:00 2001 From: Miguel Belo <99266854+angelom93@users.noreply.github.com> Date: Mon, 6 May 2024 08:18:45 +0200 Subject: [PATCH 10/18] Tornado control flow refactor (#27) * implement turn_by and turn_knives_to methods * use new methods in puncher class * implement reference speeds * Update hardware configuration for ff12_config_rb32 and u4_config_rb28 eith tornado reference speeds * Fix turn_knives_to method in TornadoHardware class * Fix reference to knive stop in CoinCollecting class * rename ref_t and ref_b to ref_knife_stop and ref_knive_ground * typo * Add turns parameter to punch method in Puncher class * Refactor field_friend_hardware.py to update knife stop and ground pin names * Update hardware.py and puncher.py for speed adjustments * backup rb28 * Update camera crop settings in config/u4_config_rb28/camera.py * Fix typo in Field class area method * Fix error handling in PlantLocator class * tmp remove of rows as obstacles * fix binding of minimum_turning_radius * Fix typo in worked_area_label text formatting * remove buggy persistence of gnss * Refactor prune method in PlantProvider class to use separate variables for max age of weeds and crops * backup rb28 * Add settings for dual mechanism workflow * add settings to operation.py * change rendering of plant objects in plant_objects.py * implement gnss correction by antenna offset * Fix error handling in PlantLocator class * Add driver settings to operation.py * Update max age of weeds in prune method of PlantProvider class * Implement antenna offset for GNSS correction * Refactor obstacle creation in Weeding class to include row polygons * Refactor plant_provider.py to use async/await for adding weeds and crops * Refactor driver settings in Weeding class * Refactor field_planner.py to remove unused imports and dependencies * Add driver settings for weeding automation in operation.py * fix field_friend_object.py to update second_tool move coordinates * backup rb28 * Update antenna offset for GNSS correction in params.py * backup for u3 * backup rb27 * Refactor operation.py to add driver settings for weeding automation * Refactor operation.py to add driver settings for weeding automation * Update healthcheck configuration in docker-compose.yml * provide antenna offset for u5 and u6 * Update antenna offset for GNSS correction in params.py for U5 --------- Co-authored-by: Johannes-Thiel Co-authored-by: Rodja Trappe --- ...eld_friend.automations.field_provider.json | 420 + ...field_friend.automations.kpi_provider.json | 65 + .../rb27/field_friend.automations.mowing.json | 7 + ...ield_friend.automations.path_provider.json | 3 + .../field_friend.automations.weeding.json | 26 + backup/rb27/field_friend.navigation.gnss.json | 13 + ...sion.calibratable_usb_camera_provider.json | 93 + .../field_friend.vision.usb_cam_provider.json | 93 + backup/rb27/rosys.config.json | 3 + .../rb27/rosys.pathplanning.path_planner.json | 29 + ...on.mjpeg_camera.mjpeg_camera_provider.json | 3 + ...eld_friend.automations.field_provider.json | 291 +- .../field_friend.automations.followme.json | 14 + ...field_friend.automations.kpi_provider.json | 168 + .../rb28/field_friend.automations.mowing.json | 8306 +++++++++++++---- .../field_friend.automations.weeding.json | 26 + backup/rb28/field_friend.navigation.gnss.json | 13 +- ...sion.calibratable_usb_camera_provider.json | 93 + .../field_friend.vision.usb_cam_provider.json | 89 +- .../rb28/rosys.pathplanning.path_planner.json | 128 +- ...on.mjpeg_camera.mjpeg_camera_provider.json | 36 + ...eld_friend.automations.field_provider.json | 420 + ...field_friend.automations.kpi_provider.json | 64 + .../field_friend.automations.mowing.json | 7 + ...ield_friend.automations.path_provider.json | 3 + .../field_friend.automations.weeding.json | 25 + .../field_friend.navigation.gnss.json | 13 + ...sion.calibratable_usb_camera_provider.json | 93 + .../field_friend.vision.usb_cam_provider.json | 93 + .../zauberzeug@192.168.42.2/rosys.config.json | 3 + .../rosys.pathplanning.path_planner.json | 114 + ...on.mjpeg_camera.mjpeg_camera_provider.json | 3 + config/ff12_config_rb32/hardware.py | 6 +- config/u3_config_rb27/params.py | 1 + config/u4_config_rb28/camera.py | 8 +- config/u4_config_rb28/hardware.py | 8 +- config/u4_config_rb28/params.py | 1 + config/u5_config_rb33/params.py | 1 + config/u6_config_rb34/params.py | 1 + docker-compose.yml | 12 +- field_friend/automations/coin_collecting.py | 8 +- field_friend/automations/field_provider.py | 5 +- field_friend/automations/plant_locator.py | 8 +- field_friend/automations/plant_provider.py | 43 +- field_friend/automations/puncher.py | 25 +- field_friend/automations/weeding.py | 229 +- .../hardware/field_friend_hardware.py | 7 +- field_friend/hardware/safety.py | 4 +- field_friend/hardware/tornado.py | 127 +- .../components/field_friend_object.py | 2 +- .../interface/components/field_planner.py | 3 - .../interface/components/operation.py | 94 +- .../interface/components/plant_object.py | 13 +- .../interface/components/status_dev.py | 8 +- .../interface/components/status_drawer.py | 8 +- field_friend/navigation/gnss.py | 47 +- .../navigation/point_transformation.py | 21 +- field_friend/system.py | 14 +- 58 files changed, 9120 insertions(+), 2341 deletions(-) create mode 100644 backup/rb27/field_friend.automations.field_provider.json create mode 100644 backup/rb27/field_friend.automations.kpi_provider.json create mode 100644 backup/rb27/field_friend.automations.mowing.json create mode 100644 backup/rb27/field_friend.automations.path_provider.json create mode 100644 backup/rb27/field_friend.automations.weeding.json create mode 100644 backup/rb27/field_friend.navigation.gnss.json create mode 100644 backup/rb27/field_friend.vision.calibratable_usb_camera_provider.json create mode 100644 backup/rb27/field_friend.vision.usb_cam_provider.json create mode 100644 backup/rb27/rosys.config.json create mode 100644 backup/rb27/rosys.pathplanning.path_planner.json create mode 100644 backup/rb27/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json create mode 100644 backup/rb28/field_friend.automations.followme.json create mode 100644 backup/rb28/field_friend.automations.kpi_provider.json create mode 100644 backup/rb28/field_friend.automations.weeding.json create mode 100644 backup/rb28/field_friend.vision.calibratable_usb_camera_provider.json create mode 100644 backup/rb28/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.automations.field_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.automations.kpi_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.automations.mowing.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.automations.path_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.automations.weeding.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.navigation.gnss.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.vision.calibratable_usb_camera_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/field_friend.vision.usb_cam_provider.json create mode 100644 backup/zauberzeug@192.168.42.2/rosys.config.json create mode 100644 backup/zauberzeug@192.168.42.2/rosys.pathplanning.path_planner.json create mode 100644 backup/zauberzeug@192.168.42.2/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json diff --git a/backup/rb27/field_friend.automations.field_provider.json b/backup/rb27/field_friend.automations.field_provider.json new file mode 100644 index 00000000..53148270 --- /dev/null +++ b/backup/rb27/field_friend.automations.field_provider.json @@ -0,0 +1,420 @@ +{ + "fields": [ + { + "id": "5a8a2850-89d8-4c31-8998-58f95c4bf12b", + "name": "Test", + "outline_wgs84": [ + [ + 53.11086450840848, + 13.91407542875983 + ], + [ + 53.110900665, + 13.914103491666667 + ], + [ + 53.11083908809259, + 13.914568552126127 + ], + [ + 53.11080833166667, + 13.914556051666667 + ] + ], + "reference_lat": 53.11065129259277, + "reference_lon": 13.91371250152588, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "a2a8d7a7-1d09-42f3-9a38-d2a35d2f6440", + "name": "row_1", + "points_wgs84": [ + [ + 53.11086900379518, + 13.914155492406122 + ], + [ + 53.11082100387979, + 13.914524203377239 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "dbfad64c-0180-4593-b6ff-a9ab87b8024a", + "name": "row_2", + "points_wgs84": [ + [ + 53.11087289058598, + 13.91415661849706 + ], + [ + 53.11082537116117, + 13.914524852764124 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "58729d6f-268b-4a37-8d7a-770a4d7f7546", + "name": "row_4", + "points_wgs84": [ + [ + 53.11088111899296, + 13.9141582500688 + ], + [ + 53.11083298032453, + 13.91452758242803 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "8d83be1e-40f1-433c-803b-19352b339c77", + "name": "row_5", + "points_wgs84": [ + [ + 53.11088460915779, + 13.914162049853772 + ], + [ + 53.1108366940985, + 13.914525808114977 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "f275d15c-b65c-462f-a18b-a8576f888a54", + "name": "field_Bohren", + "outline_wgs84": [ + [ + 53.11088575363881, + 13.914051403518485 + ], + [ + 53.110692736924065, + 13.913986660077686 + ], + [ + 53.11061942665463, + 13.914525650421513 + ], + [ + 53.1108193510101, + 13.914618566551765 + ] + ], + "reference_lat": 53.110881499529384, + "reference_lon": 13.914047767487654, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "bc19d73e-6516-4be4-9d5d-9c7f82905791", + "name": "row_1", + "points_wgs84": [ + [ + 53.1108103725628, + 13.914533857646589 + ], + [ + 53.11086585435494, + 13.914110043957198 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "cac9f4be-7487-4c72-adb3-93a2f7ba9266", + "name": "row_2", + "points_wgs84": [ + [ + 53.11079158358294, + 13.914522718556901 + ], + [ + 53.11084545526309, + 13.914113096249709 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "7e1c954c-1c3d-4751-8b5c-5310db183d02", + "name": "row_3", + "points_wgs84": [ + [ + 53.110771634262846, + 13.914515257349777 + ], + [ + 53.110825714596324, + 13.914102179170046 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "72390a9f-2f89-41ae-8285-494e5dc1c9fd", + "name": "row_4", + "points_wgs84": [ + [ + 53.11075179138159, + 13.914505245488035 + ], + [ + 53.110806123173724, + 13.914098907293617 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "4228284a-f2b5-446e-a2a2-85281edd0764", + "name": "row_5", + "points_wgs84": [ + [ + 53.11073251919884, + 13.914499494997212 + ], + [ + 53.11078634220459, + 13.914090071936823 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "38d0b78e-73ff-481e-a9fd-ad0b4f0bc383", + "name": "field_Pendelhacke", + "outline_wgs84": [ + [ + 53.110885761849865, + 13.91405137425968 + ], + [ + 53.110692719323964, + 13.913986645203941 + ], + [ + 53.11061944584767, + 13.914525645292146 + ], + [ + 53.11081932732413, + 13.91461852074301 + ] + ], + "reference_lat": 53.11088148947587, + "reference_lon": 13.914047757052812, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "a1b944a9-2f32-4863-9ed1-5b7d3e3905a9", + "name": "row_1", + "points_wgs84": [ + [ + 53.11080748389169, + 13.914528449056974 + ], + [ + 53.1108616244314, + 13.914115170279958 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "06485080-96a3-4387-bd98-ae13c5959378", + "name": "row_2", + "points_wgs84": [ + [ + 53.11078736188202, + 13.91452134589732 + ], + [ + 53.110841041222685, + 13.914112616811344 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "7c403dd6-76da-4a44-a793-4fb8814f910e", + "name": "row_3", + "points_wgs84": [ + [ + 53.11076732735035, + 13.914514540695514 + ], + [ + 53.11082206421793, + 13.914099414601182 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "66a73378-a6c4-463c-a71a-699779bea36f", + "name": "row_4", + "points_wgs84": [ + [ + 53.11074767905439, + 13.914503905811971 + ], + [ + 53.1108022193147, + 13.914094882137139 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "6948c407-cf9b-4638-a7a8-dc7e0dcfb1ba", + "name": "row_5", + "points_wgs84": [ + [ + 53.11072939628047, + 13.91449482199291 + ], + [ + 53.110782272589596, + 13.91408976451268 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "e51659c4-d7aa-412a-9f1f-81c2fc1aa0b8", + "name": "field_DoppelMechanik", + "outline_wgs84": [ + [ + 53.11088572490662, + 13.914051421541666 + ], + [ + 53.11069272815888, + 13.913986684849567 + ], + [ + 53.11061941486577, + 13.914525678599288 + ], + [ + 53.11081934782822, + 13.914618532493373 + ] + ], + "reference_lat": 53.11088150880825, + "reference_lon": 13.91404779326869, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "afbafd10-dcf6-4c4f-a462-241f7d48c6a1", + "name": "row_1", + "points_wgs84": [ + [ + 53.11079956288332, + 13.914524099085297 + ], + [ + 53.11085311243897, + 13.91411221946047 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "870744db-13ce-432a-96dd-a9e23e502836", + "name": "row_2", + "points_wgs84": [ + [ + 53.110780072773125, + 13.914515952501517 + ], + [ + 53.110833534926016, + 13.91410706581986 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "620063b9-a870-4000-8be3-e2a6669ae4fd", + "name": "row_3", + "points_wgs84": [ + [ + 53.1107598941182, + 13.914508513187641 + ], + [ + 53.11081359893513, + 13.914103616913216 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "bcc78e8f-b5c3-444d-b657-33d7984082c8", + "name": "row_4", + "points_wgs84": [ + [ + 53.11074016554936, + 13.914501518993957 + ], + [ + 53.11079387469022, + 13.914094245038237 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "50bd98c7-2f2b-4ec1-9c11-5582f4932f01", + "name": "row_5", + "points_wgs84": [ + [ + 53.11072112793958, + 13.914493604773137 + ], + [ + 53.11077420287833, + 13.91408888452689 + ] + ], + "reverse": false, + "crops": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/backup/rb27/field_friend.automations.kpi_provider.json b/backup/rb27/field_friend.automations.kpi_provider.json new file mode 100644 index 00000000..c755f78a --- /dev/null +++ b/backup/rb27/field_friend.automations.kpi_provider.json @@ -0,0 +1,65 @@ +{ + "current_weeding_kpis": { + "distance": 7, + "time": 250, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "rows_weeded": 0, + "crops_detected": 0, + "weeds_detected": 0, + "weeds_removed": 0, + "punches": 0, + "chops": 0, + "weeding_completed": 1 + }, + "current_mowing_kpis": { + "distance": 0, + "time": 0, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "mowing_completed": false + }, + "days": [ + { + "date": "2024-04-24", + "incidents": { + "low_battery": 3 + } + }, + { + "date": "2024-04-26", + "incidents": { + "low_battery": 9, + "punches": 4 + } + }, + { + "date": "2024-04-30", + "incidents": { + "time": 5641, + "distance": 593, + "weeds_detected": 1720, + "crops_detected": 123, + "rows_weeded": 3, + "weeding_completed": 13, + "punches": 33, + "weeds_removed": 55, + "chops": 19, + "low_battery": 14 + } + } + ], + "months": [] +} \ No newline at end of file diff --git a/backup/rb27/field_friend.automations.mowing.json b/backup/rb27/field_friend.automations.mowing.json new file mode 100644 index 00000000..4e1a52f1 --- /dev/null +++ b/backup/rb27/field_friend.automations.mowing.json @@ -0,0 +1,7 @@ +{ + "padding": 1.0, + "lane_distance": 0.5, + "paths": [], + "current_path": [], + "current_path_segment": null +} \ No newline at end of file diff --git a/backup/rb27/field_friend.automations.path_provider.json b/backup/rb27/field_friend.automations.path_provider.json new file mode 100644 index 00000000..f05625f8 --- /dev/null +++ b/backup/rb27/field_friend.automations.path_provider.json @@ -0,0 +1,3 @@ +{ + "paths": [] +} \ No newline at end of file diff --git a/backup/rb27/field_friend.automations.weeding.json b/backup/rb27/field_friend.automations.weeding.json new file mode 100644 index 00000000..220a6126 --- /dev/null +++ b/backup/rb27/field_friend.automations.weeding.json @@ -0,0 +1,26 @@ +{ + "use_field_planning": true, + "field": null, + "start_row_id": null, + "end_row_id": null, + "minimum_turning_radius": 0.5, + "turn_offset": 0.4, + "only_monitoring": false, + "drill_with_open_tornado": false, + "drill_between_crops": false, + "with_drilling": false, + "with_chopping": false, + "chop_if_no_crops": false, + "tornado_angle": 110.0, + "weed_screw_depth": 0.15, + "crop_safety_distance": 0.01, + "linear_speed_on_row": 0.08, + "angular_speed_on_row": 0.4, + "linear_speed_between_rows": 0.3, + "angular_speed_between_rows": 0.8, + "sorted_weeding_rows": [], + "weeding_plan": [], + "turn_paths": [], + "current_row": null, + "current_segment": null +} \ No newline at end of file diff --git a/backup/rb27/field_friend.navigation.gnss.json b/backup/rb27/field_friend.navigation.gnss.json new file mode 100644 index 00000000..005f4bb1 --- /dev/null +++ b/backup/rb27/field_friend.navigation.gnss.json @@ -0,0 +1,13 @@ +{ + "record": { + "timestamp": null, + "latitude": 0.0, + "longitude": 0.0, + "mode": "NNNN", + "gps_qual": 0, + "altitude": 0.0, + "separation": 0.0, + "heading": 0.0, + "speed_kmh": 0.0 + } +} \ No newline at end of file diff --git a/backup/rb27/field_friend.vision.calibratable_usb_camera_provider.json b/backup/rb27/field_friend.vision.calibratable_usb_camera_provider.json new file mode 100644 index 00000000..c5dfb39b --- /dev/null +++ b/backup/rb27/field_friend.vision.calibratable_usb_camera_provider.json @@ -0,0 +1,93 @@ +{ + "cameras": { + "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { + "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "connect_after_init": true, + "streaming": true, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 1433.6721216716353, + 0.0, + 347.7277694468386 + ], + [ + 0.0, + 1370.2101675446997, + 239.1060666171041 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.01557127473024067, + -56.25674323172758, + 0.23577466596211383, + 0.011342764817018949, + 421.8140030391272 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 720, + "height": 560 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.005087957942356058, + 0.9070537937934018, + -0.42098399951657745 + ], + [ + 0.9967070483093687, + -0.02946986293924958, + -0.075541955420596 + ], + [ + -0.08092695802019419, + -0.419982073835704, + -0.9039169680464345 + ] + ] + }, + "translation": [ + 0.5757624106417404, + 0.0740620510871811, + 1.1604844656286453 + ] + } + }, + "auto_exposure": true, + "exposure": false, + "width": 1280, + "height": 720, + "fps": 9 + } + } +} \ No newline at end of file diff --git a/backup/rb27/field_friend.vision.usb_cam_provider.json b/backup/rb27/field_friend.vision.usb_cam_provider.json new file mode 100644 index 00000000..d226b1ae --- /dev/null +++ b/backup/rb27/field_friend.vision.usb_cam_provider.json @@ -0,0 +1,93 @@ +{ + "cameras": { + "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { + "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "connect_after_init": true, + "streaming": true, + "auto_exposure": true, + "exposure": false, + "width": 1920, + "height": 1080, + "fps": 6, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 1433.6721216716353, + 0.0, + 347.7277694468386 + ], + [ + 0.0, + 1370.2101675446997, + 239.1060666171041 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.01557127473024067, + -56.25674323172758, + 0.23577466596211383, + 0.011342764817018949, + 421.8140030391272 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 720, + "height": 560 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.005087957942356058, + 0.9070537937934018, + -0.42098399951657745 + ], + [ + 0.9967070483093687, + -0.02946986293924958, + -0.075541955420596 + ], + [ + -0.08092695802019419, + -0.419982073835704, + -0.9039169680464345 + ] + ] + }, + "translation": [ + 0.5757624106417404, + 0.0740620510871811, + 1.1604844656286453 + ] + } + } + } + } +} \ No newline at end of file diff --git a/backup/rb27/rosys.config.json b/backup/rb27/rosys.config.json new file mode 100644 index 00000000..07b402b0 --- /dev/null +++ b/backup/rb27/rosys.config.json @@ -0,0 +1,3 @@ +{ + "simulation_speed": 1.0 +} \ No newline at end of file diff --git a/backup/rb27/rosys.pathplanning.path_planner.json b/backup/rb27/rosys.pathplanning.path_planner.json new file mode 100644 index 00000000..47655302 --- /dev/null +++ b/backup/rb27/rosys.pathplanning.path_planner.json @@ -0,0 +1,29 @@ +{ + "obstacles": {}, + "areas": { + "38d0b78e-73ff-481e-a9fd-ad0b4f0bc383": { + "outline": [ + { + "x": 0.4754654857812373, + "y": -0.24222659678731243 + }, + { + "x": -21.007917850278513, + "y": 4.09237901275715 + }, + { + "x": -29.16229728702451, + "y": -32.002028438879016 + }, + { + "x": -6.917771396089687, + "y": -38.22130290308707 + } + ], + "id": "38d0b78e-73ff-481e-a9fd-ad0b4f0bc383", + "type": null, + "color": "green", + "closed": true + } + } +} \ No newline at end of file diff --git a/backup/rb27/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json b/backup/rb27/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json new file mode 100644 index 00000000..cbd18b8a --- /dev/null +++ b/backup/rb27/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json @@ -0,0 +1,3 @@ +{ + "cameras": {} +} \ No newline at end of file diff --git a/backup/rb28/field_friend.automations.field_provider.json b/backup/rb28/field_friend.automations.field_provider.json index 6056075a..9d7ed992 100644 --- a/backup/rb28/field_friend.automations.field_provider.json +++ b/backup/rb28/field_friend.automations.field_provider.json @@ -1,33 +1,296 @@ { "fields": [ { - "id": "2a621665-b0f0-443c-8252-3bd42c997845", - "outline": [ + "id": "5a8a2850-89d8-4c31-8998-58f95c4bf12b", + "name": "Test", + "outline_wgs84": [ + [ + 53.11086450840848, + 13.91407542875983 + ], + [ + 53.110900665, + 13.914103491666667 + ], + [ + 53.11083908809259, + 13.914568552126127 + ], + [ + 53.11080833166667, + 13.914556051666667 + ] + ], + "reference_lat": 53.11065129259277, + "reference_lon": 13.91371250152588, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "a2a8d7a7-1d09-42f3-9a38-d2a35d2f6440", + "name": "row_1", + "points_wgs84": [ + [ + 53.11086900379518, + 13.914155492406122 + ], + [ + 53.11082100387979, + 13.914524203377239 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "dbfad64c-0180-4593-b6ff-a9ab87b8024a", + "name": "row_2", + "points_wgs84": [ + [ + 53.11087289058598, + 13.91415661849706 + ], + [ + 53.11082537116117, + 13.914524852764124 + ] + ], + "reverse": false, + "crops": [] + }, { - "x": 4.14008820351158, - "y": -16.529322089550206 + "id": "3b4a2163-f3ff-4598-8272-3210f7d680cc", + "name": "row_3", + "points_wgs84": [ + [ + 53.1108764614488, + 13.914158682027072 + ], + [ + 53.110828752312116, + 13.914525133180984 + ] + ], + "reverse": false, + "crops": [] }, { - "x": -12.634128191514506, - "y": -24.040097374894728 + "id": "58729d6f-268b-4a37-8d7a-770a4d7f7546", + "name": "row_4", + "points_wgs84": [ + [ + 53.11088111899296, + 13.9141582500688 + ], + [ + 53.11083298032453, + 13.91452758242803 + ] + ], + "reverse": false, + "crops": [] }, { - "x": -18.930759085830115, - "y": -16.78108433237046 + "id": "8d83be1e-40f1-433c-803b-19352b339c77", + "name": "row_5", + "points_wgs84": [ + [ + 53.11088460915779, + 13.914162049853772 + ], + [ + 53.1108366940985, + 13.914525808114977 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "21ec7d8d-dd94-470b-856b-fbf99348c9a0", + "name": "field_2", + "outline_wgs84": [ + [ + 53.110884221600415, + 13.914226577669016 + ], + [ + 53.110903025069895, + 13.914107201931566 + ], + [ + 53.11086720461946, + 13.914098982160908 + ], + [ + 53.1108632950714, + 13.914202533999706 + ] + ], + "reference_lat": 53.110884221600415, + "reference_lon": 13.914226577669016, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "9c07c145-6483-409d-a5d8-e3d1103d295f", + "name": "row_1", + "points_wgs84": [ + [ + 53.11087392461309, + 13.91415514363712 + ], + [ + 53.110876969031665, + 13.914130932744571 + ] + ], + "reverse": false, + "crops": [] }, { - "x": 0.5057610077650115, - "y": -10.85220402722948 + "id": "a8aff49b-891d-4203-bf1d-cfb5fd463ba4", + "name": "row_2", + "points_wgs84": [ + [ + 53.110884664792806, + 13.914165015553325 + ], + [ + 53.110888652810765, + 13.914132289955367 + ] + ], + "reverse": false, + "crops": [] } + ] + }, + { + "id": "c94823e6-1738-4d8a-995b-72e021fd7951", + "name": "field_Tornado", + "outline_wgs84": [ + [ + 53.110879, + 13.91404991093148 + ], + [ + 53.11068696745771, + 13.913990223201246 + ], + [ + 53.110619, + 13.914525293968063 + ], + [ + 53.11081300514498, + 13.914601447086936 + ] ], - "reference_lat": 51.983140848333335, - "reference_lon": 7.434131408333333, + "reference_lat": 53.11087948216796, + "reference_lon": 13.91404991093148, "visualized": false, "obstacles": [], "rows": [ { - "id": "c8b2f2b7-4d46-461f-b7d3-bed58bc52b95", - "points": [], + "id": "bb0bf3d5-419d-49f0-b8ef-598f48b25f02", + "name": "row_1", + "points_wgs84": [ + [ + 53.110801817152485, + 13.914540262102923 + ], + [ + 53.110822640312556, + 13.914386299598647 + ], + [ + 53.110857462682276, + 13.91411825541417 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "04939b09-604c-470e-bcbc-a04786f36229", + "name": "row_2", + "points_wgs84": [ + [ + 53.110782992884424, + 13.914518846076097 + ], + [ + 53.110818046879785, + 13.91425563877989 + ], + [ + 53.110837330722084, + 13.914113336121375 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "2f32bcec-e3d5-49bb-bdda-05aa36e7acd8", + "name": "row_3", + "points_wgs84": [ + [ + 53.110763055542854, + 13.914509412317527 + ], + [ + 53.1107923826172, + 13.914293436033752 + ], + [ + 53.11081891556159, + 13.914093666088247 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "d7745165-3fb1-4b0d-8b6d-1f01c0037a80", + "name": "row_4", + "points_wgs84": [ + [ + 53.110742861303926, + 13.914510906326248 + ], + [ + 53.11077224689231, + 13.914296073769421 + ], + [ + 53.110798502365064, + 13.914100238657408 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "71a7bbfa-5d58-43c2-97bd-e8c09fbd94e4", + "name": "row_5", + "points_wgs84": [ + [ + 53.11072440969969, + 13.914501259190327 + ], + [ + 53.11075244107501, + 13.914289856723581 + ], + [ + 53.11077917021907, + 13.914089400488287 + ] + ], "reverse": false, "crops": [] } diff --git a/backup/rb28/field_friend.automations.followme.json b/backup/rb28/field_friend.automations.followme.json new file mode 100644 index 00000000..fdd9a507 --- /dev/null +++ b/backup/rb28/field_friend.automations.followme.json @@ -0,0 +1,14 @@ +{ + "stop_distance": 200.0, + "max_y_distance": 414.0, + "max_matching_distance": 270.0, + "max_first_matching_distance": 250.0, + "yaw_min": 0.05, + "yaw_max": 0.4, + "linear_speed": 0.2, + "angular_speed": 0.2, + "projection_factor": 0.24, + "confidence": 0.4, + "target_timeout": 6.0, + "drive": true +} \ No newline at end of file diff --git a/backup/rb28/field_friend.automations.kpi_provider.json b/backup/rb28/field_friend.automations.kpi_provider.json new file mode 100644 index 00000000..5bdef5ba --- /dev/null +++ b/backup/rb28/field_friend.automations.kpi_provider.json @@ -0,0 +1,168 @@ +{ + "current_weeding_kpis": { + "distance": 248, + "time": 2260, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "rows_weeded": 4, + "crops_detected": 118, + "weeds_detected": 3605, + "weeds_removed": 0, + "punches": 0, + "chops": 0, + "weeding_completed": 1 + }, + "current_mowing_kpis": { + "distance": 0, + "time": 0, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "mowing_completed": false + }, + "days": [ + { + "date": "2024-04-04", + "incidents": { + "low_battery": 9, + "bumps": 1 + } + }, + { + "date": "2024-04-05", + "incidents": { + "punches": 16, + "bumps": 3, + "crops_detected": 40, + "weeds_detected": 4 + } + }, + { + "date": "2024-04-08", + "incidents": { + "crops_detected": 50, + "punches": 7, + "weeds_detected": 195, + "weeding_completed": 4, + "bumps": 1 + } + }, + { + "date": "2024-04-09", + "incidents": { + "bumps": 6 + } + }, + { + "date": "2024-04-10", + "incidents": { + "followme_completed": 4 + } + }, + { + "date": "2024-04-11", + "incidents": { + "low_battery": 2 + } + }, + { + "date": "2024-04-12", + "incidents": { + "bumps": 25 + } + }, + { + "date": "2024-04-15", + "incidents": { + "followme_completed": 3, + "bumps": 12 + } + }, + { + "date": "2024-04-17", + "incidents": { + "followme_completed": 15, + "bumps": 7 + } + }, + { + "date": "2024-04-22", + "incidents": { + "weeds_detected": 150, + "crops_detected": 30, + "punches": 13, + "coin_collecting_completed": 4, + "bumps": 8, + "followme_completed": 4 + } + }, + { + "date": "2024-04-23", + "incidents": { + "bumps": 4, + "followme_completed": 3, + "low_battery": 1 + } + }, + { + "date": "2024-04-24", + "incidents": { + "followme_completed": 3, + "low_battery": 3, + "bumps": 1 + } + }, + { + "date": "2024-04-25", + "incidents": { + "bumps": 4, + "followme_completed": 1 + } + }, + { + "date": "2024-04-26", + "incidents": { + "bumps": 7 + } + }, + { + "date": "2024-04-29", + "incidents": { + "punches": 21, + "bumps": 7, + "time": 6996, + "distance": 729, + "weeds_detected": 10613, + "weeding_completed": 16, + "crops_detected": 15, + "rows_weeded": 5, + "coin_collecting_completed": 1 + } + }, + { + "date": "2024-04-30", + "incidents": { + "time": 12658, + "distance": 1391, + "weeds_detected": 9827, + "crops_detected": 205, + "rows_weeded": 26, + "weeding_completed": 31, + "bumps": 6, + "punches": 35 + } + } + ], + "months": [] +} \ No newline at end of file diff --git a/backup/rb28/field_friend.automations.mowing.json b/backup/rb28/field_friend.automations.mowing.json index ee2420e6..878c86cf 100644 --- a/backup/rb28/field_friend.automations.mowing.json +++ b/backup/rb28/field_friend.automations.mowing.json @@ -1,1195 +1,1195 @@ { - "padding": 1.3399999999999999, - "lane_distance": 0.6812249086628424, + "padding": 1.1400000000000001, + "lane_distance": 0.4794285796269571, "paths": [ [ { "spline": { "start": { - "x": 1.9904484806479574, - "y": -16.02364468382114 + "x": -3.3110376044163314, + "y": -17.350505281830728 }, "control1": { - "x": -5.050386505404345, - "y": -19.176228873513086 + "x": -3.716498434380511, + "y": -15.412266830741077 }, "control2": { - "x": -5.050386505404345, - "y": -19.176228873513082 + "x": -3.7164984343805108, + "y": -15.412266830741078 }, "end": { - "x": -12.091221491456647, - "y": -22.32881306320503 - }, - "a": 1.9904484806479574, - "b": -5.050386505404345, - "c": -5.050386505404345, - "d": -12.091221491456647, - "e": -16.02364468382114, - "f": -19.176228873513086, - "g": -19.176228873513082, - "h": -22.32881306320503, - "m": -14.081669972104605, - "n": 7.040834986052302, - "o": -7.040834986052302, - "p": -6.3051683793839075, - "q": 3.15258418969195, - "r": -3.1525841896919466 + "x": -4.1219592643446905, + "y": -13.474028379651427 + }, + "a": -3.3110376044163314, + "b": -3.716498434380511, + "c": -3.7164984343805108, + "d": -4.1219592643446905, + "e": -17.350505281830728, + "f": -15.412266830741077, + "g": -15.412266830741078, + "h": -13.474028379651427, + "m": -0.8109216599283608, + "n": 0.4054608299641802, + "o": -0.40546082996417976, + "p": 3.8764769021793057, + "q": -1.9382384510896529, + "r": 1.938238451089651 }, "backward": false }, { "spline": { "start": { - "x": -12.091221491456647, - "y": -22.32881306320503 + "x": -4.1219592643446905, + "y": -13.474028379651427 }, "control1": { - "x": -12.177876510763841, - "y": -22.367613467470502 + "x": -4.141187934962031, + "y": -13.382108899396924 }, "control2": { - "x": -12.187425779127148, - "y": -22.36858771862414 + "x": -4.139539492544631, + "y": -13.37078859953336 }, "end": { - "x": -12.280130986665883, - "y": -22.348086298246653 - }, - "a": -12.091221491456647, - "b": -12.177876510763841, - "c": -12.187425779127148, - "d": -12.280130986665883, - "e": -22.32881306320503, - "f": -22.367613467470502, - "g": -22.36858771862414, - "h": -22.348086298246653, - "m": -0.16026169011931657, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.01635048158070518, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "x": -4.094894923133487, + "y": -13.28817022962653 + }, + "a": -4.1219592643446905, + "b": -4.141187934962031, + "c": -4.139539492544631, + "d": -4.094894923133487, + "e": -13.474028379651427, + "f": -13.382108899396924, + "g": -13.37078859953336, + "h": -13.28817022962653, + "m": 0.022119013959001066, + "n": 0.02087711303474027, + "o": -0.01922867061734035, + "p": 0.15189725043420843, + "q": -0.08059918039094072, + "r": 0.09191948025450358 }, "backward": false }, { "spline": { "start": { - "x": -12.280130986665883, - "y": -22.348086298246653 + "x": -4.094894923133487, + "y": -13.28817022962653 }, "control1": { - "x": -12.354809913076233, - "y": -22.331571324553956 + "x": -4.060578306574829, + "y": -13.224664567948308 }, "control2": { - "x": -12.354693841811876, - "y": -22.317240040049903 + "x": -4.043918635265363, + "y": -13.231600583556528 }, "end": { - "x": -12.40480998472941, - "y": -22.259464105397644 - }, - "a": -12.280130986665883, - "b": -12.354809913076233, - "c": -12.354693841811876, - "d": -12.40480998472941, - "e": -22.348086298246653, - "f": -22.331571324553956, - "g": -22.317240040049903, - "h": -22.259464105397644, - "m": -0.1250272118566027, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.045628339336840185, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "x": -3.976717134225542, + "y": -13.205246124821471 + }, + "a": -4.094894923133487, + "b": -4.060578306574829, + "c": -4.043918635265363, + "d": -3.976717134225542, + "e": -13.28817022962653, + "f": -13.224664567948308, + "g": -13.231600583556528, + "h": -13.205246124821471, + "m": 0.06819877497954518, + "n": -0.01765694524919148, + "o": 0.03431661655865792, + "p": 0.10373215162972116, + "q": -0.07044167728644268, + "r": 0.06350566167822258 }, "backward": false }, { "spline": { "start": { - "x": -12.40480998472941, - "y": -22.259464105397644 + "x": -3.976717134225542, + "y": -13.205246124821471 }, "control1": { - "x": -14.420419836961068, - "y": -19.935786816452737 + "x": 2.2764603049283063, + "y": -10.752933241430384 }, "control2": { - "x": -14.42041983696107, - "y": -19.93578681645274 + "x": 2.276460304928305, + "y": -10.752933241430384 }, "end": { - "x": -16.43602968919273, - "y": -17.612109527507833 - }, - "a": -12.40480998472941, - "b": -14.420419836961068, - "c": -14.42041983696107, - "d": -16.43602968919273, - "e": -22.259464105397644, - "f": -19.935786816452737, - "g": -19.93578681645274, - "h": -17.612109527507833, - "m": -4.03121970446332, - "n": 2.0156098522316572, - "o": -2.015609852231659, - "p": 4.647354577889818, - "q": -2.3236772889449107, - "r": 2.323677288944907 + "x": 8.529637744082153, + "y": -8.300620358039296 + }, + "a": -3.976717134225542, + "b": 2.2764603049283063, + "c": 2.276460304928305, + "d": 8.529637744082153, + "e": -13.205246124821471, + "f": -10.752933241430384, + "g": -10.752933241430384, + "h": -8.300620358039296, + "m": 12.506354878307699, + "n": -6.25317743915385, + "o": 6.253177439153848, + "p": 4.904625766782175, + "q": -2.4523128833910874, + "r": 2.4523128833910874 }, "backward": false }, { "spline": { "start": { - "x": -16.43602968919273, - "y": -17.612109527507833 + "x": 8.529637744082153, + "y": -8.300620358039296 }, "control1": { - "x": -16.49361810904551, - "y": -17.5457192470823 + "x": 8.616788825148694, + "y": -8.266442258828048 }, "control2": { - "x": -16.498206624660558, - "y": -17.52492779540708 + "x": 8.628745152649351, + "y": -8.266269951951177 }, "end": { - "x": -16.473910085467818, - "y": -17.440466125879556 - }, - "a": -16.43602968919273, - "b": -16.49361810904551, - "c": -16.498206624660558, - "d": -16.473910085467818, - "e": -17.612109527507833, - "f": -17.5457192470823, - "g": -17.52492779540708, - "h": -17.440466125879556, - "m": -0.024114849429942353, - "n": 0.052999904237733375, - "o": -0.05758841985278096, - "p": 0.10926904660262693, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "x": 8.716844941786379, + "y": -8.297922448705336 + }, + "a": 8.529637744082153, + "b": 8.616788825148694, + "c": 8.628745152649351, + "d": 8.716844941786379, + "e": -8.300620358039296, + "f": -8.266442258828048, + "g": -8.266269951951177, + "h": -8.297922448705336, + "m": 0.15133821520224977, + "n": -0.07519475356588323, + "o": 0.08715108106654057, + "p": 0.0021809887033477082, + "q": -0.034005792334376395, + "r": 0.034178099211247925 }, "backward": false }, { "spline": { "start": { - "x": -16.473910085467818, - "y": -17.440466125879556 + "x": 8.716844941786379, + "y": -8.297922448705336 }, "control1": { - "x": -16.459375225005935, - "y": -17.389938823566187 + "x": 8.783659984830374, + "y": -8.321927760792855 }, "control2": { - "x": -16.426071759980577, - "y": -17.41801504143529 + "x": 8.77849499336912, + "y": -8.340362260260314 }, "end": { - "x": -16.375783038651115, - "y": -17.402675062466564 - }, - "a": -16.473910085467818, - "b": -16.459375225005935, - "c": -16.426071759980577, - "d": -16.375783038651115, - "e": -17.440466125879556, - "f": -17.389938823566187, - "g": -17.41801504143529, - "h": -17.402675062466564, - "m": -0.0017833482593694328, - "n": 0.018768604563476288, - "o": 0.01453486046188246, - "p": 0.12201971702029724, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "x": 8.812981572560112, + "y": -8.402420143249453 + }, + "a": 8.716844941786379, + "b": 8.783659984830374, + "c": 8.77849499336912, + "d": 8.812981572560112, + "e": -8.297922448705336, + "f": -8.321927760792855, + "g": -8.340362260260314, + "h": -8.402420143249453, + "m": 0.1116316051574966, + "n": -0.07198003450524837, + "o": 0.0668150430439951, + "p": -0.04919419614174103, + "q": 0.00557081262006065, + "r": -0.024005312087519215 }, "backward": false }, { "spline": { "start": { - "x": -16.375783038651115, - "y": -17.402675062466564 + "x": 8.812981572560112, + "y": -8.402420143249453 }, "control1": { - "x": -8.321056452272451, - "y": -14.945676093962286 + "x": 9.673989682642553, + "y": -9.951786354733635 }, "control2": { - "x": -8.321056452272451, - "y": -14.945676093962286 + "x": 9.673989682642551, + "y": -9.951786354733635 }, "end": { - "x": -0.2663298658937867, - "y": -12.488677125458008 - }, - "a": -16.375783038651115, - "b": -8.321056452272451, - "c": -8.321056452272451, - "d": -0.2663298658937867, - "e": -17.402675062466564, - "f": -14.945676093962286, - "g": -14.945676093962286, - "h": -12.488677125458008, - "m": 16.109453172757327, - "n": -8.054726586378663, - "o": 8.054726586378663, - "p": 4.913997937008553, - "q": -2.456998968504278, - "r": 2.456998968504278 + "x": 10.534997792724992, + "y": -11.501152566217817 + }, + "a": 8.812981572560112, + "b": 9.673989682642553, + "c": 9.673989682642551, + "d": 10.534997792724992, + "e": -8.402420143249453, + "f": -9.951786354733635, + "g": -9.951786354733635, + "h": -11.501152566217817, + "m": 1.7220162201648854, + "n": -0.8610081100824427, + "o": 0.8610081100824409, + "p": -3.098732422968366, + "q": 1.5493662114841822, + "r": -1.5493662114841822 }, "backward": false }, { "spline": { "start": { - "x": -0.2663298658937867, - "y": -12.488677125458008 + "x": 10.534997792724992, + "y": -11.501152566217817 }, "control1": { - "x": -0.17566847189296741, - "y": -12.461021940746974 + "x": 10.579358670737165, + "y": -11.580979044852553 }, "control2": { - "x": -0.1657873086812938, - "y": -12.461301514270977 + "x": 10.58074503418681, + "y": -11.596764086769838 }, "end": { - "x": -0.07683462917804214, - "y": -12.494038624906537 - }, - "a": -0.2663298658937867, - "b": -0.17566847189296741, - "c": -0.1657873086812938, - "d": -0.07683462917804214, - "e": -12.488677125458008, - "f": -12.461021940746974, - "g": -12.461301514270977, - "h": -12.494038624906537, - "m": 0.15985174708072375, - "n": -0.08078023078914567, - "o": 0.0906613940008193, - "p": -0.004522778876523859, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "x": 10.550977904573669, + "y": -11.683101055582897 + }, + "a": 10.534997792724992, + "b": 10.579358670737165, + "c": 10.58074503418681, + "d": 10.550977904573669, + "e": -11.501152566217817, + "f": -11.580979044852553, + "g": -11.596764086769838, + "h": -11.683101055582897, + "m": 0.011821021499743622, + "n": -0.04297451456252688, + "o": 0.04436087801217248, + "p": -0.13459336361322194, + "q": 0.0640414367174511, + "r": -0.07982647863473602 }, "backward": false }, { "spline": { "start": { - "x": -0.07683462917804214, - "y": -12.494038624906537 + "x": 10.550977904573669, + "y": -11.683101055582897 }, "control1": { - "x": -0.005691927185520265, - "y": -12.5202211583507 + "x": 10.530609752900592, + "y": -11.742177106872767 }, "control2": { - "x": -0.008072901222325228, - "y": -12.534919434322207 + "x": 10.505467173581247, + "y": -11.729463712150888 }, "end": { - "x": 0.03279927747010902, - "y": -12.598765139841728 - }, - "a": -0.07683462917804214, - "b": -0.005691927185520265, - "c": -0.008072901222325228, - "d": 0.03279927747010902, - "e": -12.494038624906537, - "f": -12.5202211583507, - "g": -12.534919434322207, - "h": -12.598765139841728, - "m": 0.11677682875856604, - "n": -0.07352367602932683, - "o": 0.07114270199252187, - "p": -0.060631687020670455, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "x": 10.447900146027525, + "y": -11.753771710365672 + }, + "a": 10.550977904573669, + "b": 10.530609752900592, + "c": 10.505467173581247, + "d": 10.447900146027525, + "e": -11.683101055582897, + "f": -11.742177106872767, + "g": -11.729463712150888, + "h": -11.753771710365672, + "m": -0.02765002058811028, + "n": -0.004774427646268009, + "o": -0.02036815167307715, + "p": -0.10881083894840948, + "q": 0.07178944601174742, + "r": -0.05907605128986937 }, "backward": false }, { "spline": { "start": { - "x": 0.03279927747010902, - "y": -12.598765139841728 + "x": 10.447900146027525, + "y": -11.753771710365672 }, "control1": { - "x": 1.0489768792842749, - "y": -14.186118151022631 + "x": 3.6810309433337354, + "y": -14.61111994404433 }, "control2": { - "x": 1.0489768792842744, - "y": -14.186118151022631 + "x": 3.6810309433337345, + "y": -14.611119944044326 }, "end": { - "x": 2.0651544810984404, - "y": -15.773471162203535 - }, - "a": 0.03279927747010902, - "b": 1.0489768792842749, - "c": 1.0489768792842744, - "d": 2.0651544810984404, - "e": -12.598765139841728, - "f": -14.186118151022631, - "g": -14.186118151022631, - "h": -15.773471162203535, - "m": 2.0323552036283328, - "n": -1.0161776018141664, - "o": 1.016177601814166, - "p": -3.174706022361807, - "q": 1.5873530111809036, - "r": -1.5873530111809036 + "x": -3.085838259360055, + "y": -17.468468177722983 + }, + "a": 10.447900146027525, + "b": 3.6810309433337354, + "c": 3.6810309433337345, + "d": -3.085838259360055, + "e": -11.753771710365672, + "f": -14.61111994404433, + "g": -14.611119944044326, + "h": -17.468468177722983, + "m": -13.533738405387577, + "n": 6.766869202693789, + "o": -6.7668692026937896, + "p": -5.714696467357317, + "q": 2.8573482336786604, + "r": -2.857348233678657 }, "backward": false }, { "spline": { "start": { - "x": 2.0651544810984404, - "y": -15.773471162203535 + "x": -3.085838259360055, + "y": -17.468468177722983 }, "control1": { - "x": 2.0899632936716346, - "y": -15.812224568371489 + "x": -3.1305613958215615, + "y": -17.487352771343026 }, "control2": { - "x": 2.065139022555776, - "y": -15.855260400063573 + "x": -3.169893880666062, + "y": -17.45655125284964 }, "end": { - "x": 2.0191715439702316, - "y": -15.853188062398566 - }, - "a": 2.0651544810984404, - "b": 2.0899632936716346, - "c": 2.065139022555776, - "d": 2.0191715439702316, - "e": -15.773471162203535, - "f": -15.812224568371489, - "g": -15.855260400063573, - "h": -15.853188062398566, - "m": 0.028489876219366383, - "n": -0.049633083689052615, - "o": 0.024808812573194228, - "p": 0.049390594881224104, - "q": -0.004282425524129607, - "r": -0.038753406167954196 + "x": -3.1622814070037357, + "y": -17.408605063680017 + }, + "a": -3.085838259360055, + "b": -3.1305613958215615, + "c": -3.169893880666062, + "d": -3.1622814070037357, + "e": -17.468468177722983, + "f": -17.487352771343026, + "g": -17.45655125284964, + "h": -17.408605063680017, + "m": 0.04155430688982076, + "n": 0.005390651617005915, + "o": -0.044723136461506385, + "p": -0.03254144143718918, + "q": 0.04968611211343088, + "r": -0.018884593620043688 }, "backward": false }, { "spline": { "start": { - "x": 2.0191715439702316, - "y": -15.853188062398566 + "x": -3.1622814070037357, + "y": -17.408605063680017 }, "control1": { - "x": 1.5013446687688368, - "y": -15.829843033033173 + "x": -3.103189753707147, + "y": -17.0364238630638 }, "control2": { - "x": 1.4635125473892967, - "y": -15.513188272615256 + "x": -2.8838539886315604, + "y": -17.051147868003277 }, "end": { - "x": 0.9904191949100383, - "y": -15.725019202644013 - }, - "a": 2.0191715439702316, - "b": 1.5013446687688368, - "c": 1.4635125473892967, - "d": 0.9904191949100383, - "e": -15.853188062398566, - "f": -15.829843033033173, - "g": -15.513188272615256, - "h": -15.725019202644013, - "m": -0.9152559849215738, - "n": 0.47999475382185475, - "o": -0.5178268752013948, - "p": -0.8217954214991963, - "q": 0.2933097310525241, - "r": 0.023345029365392733 + "x": -2.961015679406676, + "y": -16.682289164624628 + }, + "a": -3.1622814070037357, + "b": -3.103189753707147, + "c": -2.8838539886315604, + "d": -2.961015679406676, + "e": -17.408605063680017, + "f": -17.0364238630638, + "g": -17.051147868003277, + "h": -16.682289164624628, + "m": -0.45674156762970064, + "n": 0.16024411177899767, + "o": 0.05909165329658883, + "p": 0.7704879138738256, + "q": -0.38690520555569563, + "r": 0.3721812006162182 }, "backward": false }, { "spline": { "start": { - "x": 0.9904191949100383, - "y": -15.725019202644013 + "x": -2.961015679406676, + "y": -16.682289164624628 }, "control1": { - "x": -5.458799399697785, - "y": -18.612702924461736 + "x": -3.265372127860259, + "y": -15.227363490837753 }, "control2": { - "x": -5.458799399697786, - "y": -18.612702924461736 + "x": -3.2653721278602585, + "y": -15.227363490837753 }, "end": { - "x": -11.90801799430561, - "y": -21.500386646279456 - }, - "a": 0.9904191949100383, - "b": -5.458799399697785, - "c": -5.458799399697786, - "d": -11.90801799430561, - "e": -15.725019202644013, - "f": -18.612702924461736, - "g": -18.612702924461736, - "h": -21.500386646279456, - "m": -12.898437189215644, - "n": 6.449218594607823, - "o": -6.4492185946078235, - "p": -5.775367443635446, - "q": 2.887683721817723, - "r": -2.887683721817723 + "x": -3.5697285763138416, + "y": -13.77243781705088 + }, + "a": -2.961015679406676, + "b": -3.265372127860259, + "c": -3.2653721278602585, + "d": -3.5697285763138416, + "e": -16.682289164624628, + "f": -15.227363490837753, + "g": -15.227363490837753, + "h": -13.77243781705088, + "m": -0.6087128969071673, + "n": 0.30435644845358345, + "o": -0.304356448453583, + "p": 2.909851347573749, + "q": -1.4549256737868745, + "r": 1.4549256737868745 }, "backward": false }, { "spline": { "start": { - "x": -11.90801799430561, - "y": -21.500386646279456 + "x": -3.5697285763138416, + "y": -13.77243781705088 }, "control1": { - "x": -11.994673013612804, - "y": -21.53918705054493 + "x": -3.5889572469311823, + "y": -13.680518336796377 }, "control2": { - "x": -12.00422228197611, - "y": -21.540161301698568 + "x": -3.587308804513782, + "y": -13.669198036932814 }, "end": { - "x": -12.096927489514846, - "y": -21.51965988132108 - }, - "a": -11.90801799430561, - "b": -11.994673013612804, - "c": -12.00422228197611, - "d": -12.096927489514846, - "e": -21.500386646279456, - "f": -21.53918705054493, - "g": -21.540161301698568, - "h": -21.51965988132108, - "m": -0.16026169011932012, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.016350481580698073, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "x": -3.5426642351026376, + "y": -13.586579667025983 + }, + "a": -3.5697285763138416, + "b": -3.5889572469311823, + "c": -3.587308804513782, + "d": -3.5426642351026376, + "e": -13.77243781705088, + "f": -13.680518336796377, + "g": -13.669198036932814, + "h": -13.586579667025983, + "m": 0.022119013959001954, + "n": 0.020877113034741157, + "o": -0.019228670617340793, + "p": 0.15189725043420843, + "q": -0.08059918039094072, + "r": 0.09191948025450358 }, "backward": false }, { "spline": { "start": { - "x": -12.096927489514846, - "y": -21.51965988132108 + "x": -3.5426642351026376, + "y": -13.586579667025983 }, "control1": { - "x": -12.171606415925195, - "y": -21.503144907628382 + "x": -3.50834761854398, + "y": -13.52307400534776 }, "control2": { - "x": -12.171490344660839, - "y": -21.48881362312433 + "x": -3.4916879472345133, + "y": -13.53001002095598 }, "end": { - "x": -12.221606487578372, - "y": -21.43103768847207 - }, - "a": -12.096927489514846, - "b": -12.171606415925195, - "c": -12.171490344660839, - "d": -12.221606487578372, - "e": -21.51965988132108, - "f": -21.503144907628382, - "g": -21.48881362312433, - "h": -21.43103768847207, - "m": -0.12502721185659205, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.04562833933684729, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "x": -3.424486446194693, + "y": -13.503655562220924 + }, + "a": -3.5426642351026376, + "b": -3.50834761854398, + "c": -3.4916879472345133, + "d": -3.424486446194693, + "e": -13.586579667025983, + "f": -13.52307400534776, + "g": -13.53001002095598, + "h": -13.503655562220924, + "m": 0.06819877497954208, + "n": -0.017656945249190592, + "o": 0.034316616558657476, + "p": 0.10373215162971405, + "q": -0.07044167728644268, + "r": 0.06350566167822258 }, "backward": false }, { "spline": { "start": { - "x": -12.221606487578372, - "y": -21.43103768847207 + "x": -3.424486446194693, + "y": -13.503655562220924 }, "control1": { - "x": -13.727998538515276, - "y": -19.69440746058662 + "x": 2.4448925072044294, + "y": -11.201857194114352 }, "control2": { - "x": -13.72799853851528, - "y": -19.694407460586625 + "x": 2.444892507204427, + "y": -11.201857194114352 }, "end": { - "x": -15.234390589452184, - "y": -17.957777232701176 - }, - "a": -12.221606487578372, - "b": -13.727998538515276, - "c": -13.72799853851528, - "d": -15.234390589452184, - "e": -21.43103768847207, - "f": -19.69440746058662, - "g": -19.694407460586625, - "h": -17.957777232701176, - "m": -3.0127841018737964, - "n": 1.5063920509369009, - "o": -1.5063920509369044, - "p": 3.4732604557709017, - "q": -1.7366302278854526, - "r": 1.736630227885449 + "x": 8.31427146060355, + "y": -8.900058826007779 + }, + "a": -3.424486446194693, + "b": 2.4448925072044294, + "c": 2.444892507204427, + "d": 8.31427146060355, + "e": -13.503655562220924, + "f": -11.201857194114352, + "g": -11.201857194114352, + "h": -8.900058826007779, + "m": 11.73875790679825, + "n": -5.869378953399124, + "o": 5.8693789533991225, + "p": 4.603596736213145, + "q": -2.3017983681065726, + "r": 2.3017983681065726 }, "backward": false }, { "spline": { "start": { - "x": -15.234390589452184, - "y": -17.957777232701176 + "x": 8.31427146060355, + "y": -8.900058826007779 }, "control1": { - "x": -15.291979009304963, - "y": -17.891386952275642 + "x": 8.40142254167009, + "y": -8.865880726796531 }, "control2": { - "x": -15.296567524920015, - "y": -17.870595500600423 + "x": 8.413378869170748, + "y": -8.86570841991966 }, "end": { - "x": -15.272270985727273, - "y": -17.7861338310729 - }, - "a": -15.234390589452184, - "b": -15.291979009304963, - "c": -15.296567524920015, - "d": -15.272270985727273, - "e": -17.957777232701176, - "f": -17.891386952275642, - "g": -17.870595500600423, - "h": -17.7861338310729, - "m": -0.02411484942993347, - "n": 0.052999904237728046, - "o": -0.05758841985277918, - "p": 0.10926904660261982, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "x": 8.501478658307775, + "y": -8.897360916673819 + }, + "a": 8.31427146060355, + "b": 8.40142254167009, + "c": 8.413378869170748, + "d": 8.501478658307775, + "e": -8.900058826007779, + "f": -8.865880726796531, + "g": -8.86570841991966, + "h": -8.897360916673819, + "m": 0.1513382152022551, + "n": -0.07519475356588323, + "o": 0.08715108106654057, + "p": 0.0021809887033477082, + "q": -0.034005792334376395, + "r": 0.034178099211247925 }, "backward": false }, { "spline": { "start": { - "x": -15.272270985727273, - "y": -17.7861338310729 + "x": 8.501478658307775, + "y": -8.897360916673819 }, "control1": { - "x": -15.257736125265389, - "y": -17.73560652875953 + "x": 8.56829370135177, + "y": -8.921366228761338 }, "control2": { - "x": -15.22443266024003, - "y": -17.763682746628632 + "x": 8.563128709890517, + "y": -8.939800728228796 }, "end": { - "x": -15.174143938910568, - "y": -17.748342767659906 - }, - "a": -15.272270985727273, - "b": -15.257736125265389, - "c": -15.22443266024003, - "d": -15.174143938910568, - "e": -17.7861338310729, - "f": -17.73560652875953, - "g": -17.763682746628632, - "h": -17.748342767659906, - "m": -0.0017833482593676564, - "n": 0.01876860456347451, - "o": 0.014534860461884236, - "p": 0.12201971702030434, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "x": 8.597615289081508, + "y": -9.001858611217935 + }, + "a": 8.501478658307775, + "b": 8.56829370135177, + "c": 8.563128709890517, + "d": 8.597615289081508, + "e": -8.897360916673819, + "f": -8.921366228761338, + "g": -8.939800728228796, + "h": -9.001858611217935, + "m": 0.11163160515749126, + "n": -0.07198003450524837, + "o": 0.0668150430439951, + "p": -0.04919419614174103, + "q": 0.00557081262006065, + "r": -0.024005312087519215 }, "backward": false }, { "spline": { "start": { - "x": -15.174143938910568, - "y": -17.748342767659906 + "x": 8.597615289081508, + "y": -9.001858611217935 }, "control1": { - "x": -7.86786772485169, - "y": -15.51964972277024 + "x": 9.227066450649238, + "y": -10.134542903145373 }, "control2": { - "x": -7.86786772485169, - "y": -15.51964972277024 + "x": 9.227066450649236, + "y": -10.134542903145373 }, "end": { - "x": -0.5615915107928121, - "y": -13.290956677880574 - }, - "a": -15.174143938910568, - "b": -7.86786772485169, - "c": -7.86786772485169, - "d": -0.5615915107928121, - "e": -17.748342767659906, - "f": -15.51964972277024, - "g": -15.51964972277024, - "h": -13.290956677880574, - "m": 14.612552428117755, - "n": -7.306276214058878, - "o": 7.306276214058878, - "p": 4.457386089779334, - "q": -2.228693044889667, - "r": 2.228693044889667 + "x": 9.856517612216965, + "y": -11.267227195072811 + }, + "a": 8.597615289081508, + "b": 9.227066450649238, + "c": 9.227066450649236, + "d": 9.856517612216965, + "e": -9.001858611217935, + "f": -10.134542903145373, + "g": -10.134542903145373, + "h": -11.267227195072811, + "m": 1.2589023231354606, + "n": -0.6294511615677312, + "o": 0.6294511615677294, + "p": -2.265368583854878, + "q": 1.132684291927438, + "r": -1.132684291927438 }, "backward": false }, { "spline": { "start": { - "x": -0.5615915107928121, - "y": -13.290956677880574 + "x": 9.856517612216965, + "y": -11.267227195072811 }, "control1": { - "x": -0.4709301167919928, - "y": -13.263301493169541 + "x": 9.900878490229138, + "y": -11.347053673707547 }, "control2": { - "x": -0.46104895358031917, - "y": -13.263581066693543 + "x": 9.902264853678783, + "y": -11.362838715624832 }, "end": { - "x": -0.3720962740770675, - "y": -13.296318177329104 - }, - "a": -0.5615915107928121, - "b": -0.4709301167919928, - "c": -0.46104895358031917, - "d": -0.3720962740770675, - "e": -13.290956677880574, - "f": -13.263301493169541, - "g": -13.263581066693543, - "h": -13.296318177329104, - "m": 0.1598517470807238, - "n": -0.08078023078914565, - "o": 0.0906613940008193, - "p": -0.004522778876525635, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "x": 9.872497724065642, + "y": -11.449175684437892 + }, + "a": 9.856517612216965, + "b": 9.900878490229138, + "c": 9.902264853678783, + "d": 9.872497724065642, + "e": -11.267227195072811, + "f": -11.347053673707547, + "g": -11.362838715624832, + "h": -11.449175684437892, + "m": 0.011821021499738293, + "n": -0.04297451456252688, + "o": 0.04436087801217248, + "p": -0.13459336361322194, + "q": 0.0640414367174511, + "r": -0.07982647863473602 }, "backward": false }, { "spline": { "start": { - "x": -0.3720962740770675, - "y": -13.296318177329104 + "x": 9.872497724065642, + "y": -11.449175684437892 }, "control1": { - "x": -0.30095357208454565, - "y": -13.322500710773268 + "x": 9.852129572392565, + "y": -11.508251735727761 }, "control2": { - "x": -0.3033345461213507, - "y": -13.337198986744774 + "x": 9.82698699307322, + "y": -11.495538341005883 }, "end": { - "x": -0.26246236742891643, - "y": -13.401044692264295 - }, - "a": -0.3720962740770675, - "b": -0.30095357208454565, - "c": -0.3033345461213507, - "d": -0.26246236742891643, - "e": -13.296318177329104, - "f": -13.322500710773268, - "g": -13.337198986744774, - "h": -13.401044692264295, - "m": 0.11677682875856615, - "n": -0.07352367602932691, - "o": 0.07114270199252187, - "p": -0.06063168702066868, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "x": 9.769419965519498, + "y": -11.519846339220667 + }, + "a": 9.872497724065642, + "b": 9.852129572392565, + "c": 9.82698699307322, + "d": 9.769419965519498, + "e": -11.449175684437892, + "f": -11.508251735727761, + "g": -11.495538341005883, + "h": -11.519846339220667, + "m": -0.027650020588108504, + "n": -0.004774427646268009, + "o": -0.02036815167307715, + "p": -0.10881083894840948, + "q": 0.07178944601174742, + "r": -0.05907605128986937 }, "backward": false }, { "spline": { "start": { - "x": -0.26246236742891643, - "y": -13.401044692264295 + "x": 9.769419965519498, + "y": -11.519846339220667 }, "control1": { - "x": 0.4013314139658026, - "y": -14.43794518664535 + "x": 3.51680181558455, + "y": -14.160049199868777 }, "control2": { - "x": 0.4013314139658022, - "y": -14.43794518664535 + "x": 3.5168018155845484, + "y": -14.160049199868773 }, "end": { - "x": 1.0651251953605212, - "y": -15.474845681026409 - }, - "a": -0.26246236742891643, - "b": 0.4013314139658026, - "c": 0.4013314139658022, - "d": 1.0651251953605212, - "e": -13.401044692264295, - "f": -14.43794518664535, - "g": -14.43794518664535, - "h": -15.474845681026409, - "m": 1.3275875627894387, - "n": -0.6637937813947195, - "o": 0.663793781394719, - "p": -2.0738009887621143, - "q": 1.0369004943810562, - "r": -1.0369004943810562 + "x": -2.7358163343503996, + "y": -16.800252060516883 + }, + "a": 9.769419965519498, + "b": 3.51680181558455, + "c": 3.5168018155845484, + "d": -2.7358163343503996, + "e": -11.519846339220667, + "f": -14.160049199868777, + "g": -14.160049199868773, + "h": -16.800252060516883, + "m": -12.505236299869892, + "n": 6.252618149934946, + "o": -6.252618149934948, + "p": -5.280405721296223, + "q": 2.640202860648113, + "r": -2.6402028606481096 }, "backward": false }, { "spline": { "start": { - "x": 1.0651251953605212, - "y": -15.474845681026409 + "x": -2.7358163343503996, + "y": -16.800252060516883 }, "control1": { - "x": 1.0899340079337152, - "y": -15.513599087194363 + "x": -2.780539470811906, + "y": -16.819136654136926 }, "control2": { - "x": 1.0651097368178575, - "y": -15.556634918886447 + "x": -2.819871955656407, + "y": -16.78833513564354 }, "end": { - "x": 1.0191422582323126, - "y": -15.55456258122144 - }, - "a": 1.0651251953605212, - "b": 1.0899340079337152, - "c": 1.0651097368178575, - "d": 1.0191422582323126, - "e": -15.474845681026409, - "f": -15.513599087194363, - "g": -15.556634918886447, - "h": -15.55456258122144, - "m": 0.028489876219364607, - "n": -0.04963308368905173, - "o": 0.024808812573194006, - "p": 0.049390594881224104, - "q": -0.004282425524129607, - "r": -0.038753406167954196 + "x": -2.8122594819940807, + "y": -16.740388946473917 + }, + "a": -2.7358163343503996, + "b": -2.780539470811906, + "c": -2.819871955656407, + "d": -2.8122594819940807, + "e": -16.800252060516883, + "f": -16.819136654136926, + "g": -16.78833513564354, + "h": -16.740388946473917, + "m": 0.041554306889821646, + "n": 0.005390651617005471, + "o": -0.044723136461506385, + "p": -0.03254144143718918, + "q": 0.04968611211343088, + "r": -0.018884593620043688 }, "backward": false }, { "spline": { "start": { - "x": 1.0191422582323126, - "y": -15.55456258122144 + "x": -2.8122594819940807, + "y": -16.740388946473917 }, "control1": { - "x": 0.501315383030917, - "y": -15.531217551856047 + "x": -2.7531678286974928, + "y": -16.3682077458577 }, "control2": { - "x": 0.4634832616513772, - "y": -15.214562791438128 + "x": -2.5338320636219067, + "y": -16.382931750797177 }, "end": { - "x": -0.009610090827881917, - "y": -15.426393721466885 - }, - "a": 1.0191422582323126, - "b": 0.501315383030917, - "c": 0.4634832616513772, - "d": -0.009610090827881917, - "e": -15.55456258122144, - "f": -15.531217551856047, - "g": -15.214562791438128, - "h": -15.426393721466885, - "m": -0.9152559849215753, - "n": 0.47999475382185575, - "o": -0.5178268752013956, - "p": -0.8217954214992034, - "q": 0.2933097310525259, - "r": 0.023345029365392733 + "x": -2.6109937543970227, + "y": -16.014073047418524 + }, + "a": -2.8122594819940807, + "b": -2.7531678286974928, + "c": -2.5338320636219067, + "d": -2.6109937543970227, + "e": -16.740388946473917, + "f": -16.3682077458577, + "g": -16.382931750797177, + "h": -16.014073047418524, + "m": -0.4567415676297002, + "n": 0.1602441117789981, + "o": 0.05909165329658794, + "p": 0.7704879138738256, + "q": -0.38690520555569563, + "r": 0.3721812006162182 }, "backward": false }, { "spline": { "start": { - "x": -0.009610090827881917, - "y": -15.426393721466885 + "x": -2.6109937543970227, + "y": -16.014073047418524 }, "control1": { - "x": -5.867212293991229, - "y": -18.049176975410383 + "x": -2.8142458213400086, + "y": -15.042460150934428 }, "control2": { - "x": -5.86721229399123, - "y": -18.049176975410383 + "x": -2.814245821340008, + "y": -15.042460150934428 }, "end": { - "x": -11.724814497154577, - "y": -20.671960229353882 - }, - "a": -0.009610090827881917, - "b": -5.867212293991229, - "c": -5.86721229399123, - "d": -11.724814497154577, - "e": -15.426393721466885, - "f": -18.049176975410383, - "g": -18.049176975410383, - "h": -20.671960229353882, - "m": -11.715204406326691, - "n": 5.857602203163347, - "o": -5.8576022031633475, - "p": -5.245566507886993, - "q": 2.6227832539434974, - "r": -2.6227832539434974 + "x": -3.017497888282994, + "y": -14.070847254450332 + }, + "a": -2.6109937543970227, + "b": -2.8142458213400086, + "c": -2.814245821340008, + "d": -3.017497888282994, + "e": -16.014073047418524, + "f": -15.042460150934428, + "g": -15.042460150934428, + "h": -14.070847254450332, + "m": -0.4065041338859734, + "n": 0.20325206694298625, + "o": -0.2032520669429858, + "p": 1.9432257929681924, + "q": -0.9716128964840962, + "r": 0.9716128964840962 }, "backward": false }, { "spline": { "start": { - "x": -11.724814497154577, - "y": -20.671960229353882 + "x": -3.017497888282994, + "y": -14.070847254450332 }, "control1": { - "x": -11.811469516461772, - "y": -20.710760633619355 + "x": -3.0367265589003347, + "y": -13.978927774195828 }, "control2": { - "x": -11.821018784825078, - "y": -20.711734884772994 + "x": -3.0350781164829344, + "y": -13.967607474332265 }, "end": { - "x": -11.913723992363813, - "y": -20.691233464395506 - }, - "a": -11.724814497154577, - "b": -11.811469516461772, - "c": -11.821018784825078, - "d": -11.913723992363813, - "e": -20.671960229353882, - "f": -20.710760633619355, - "g": -20.711734884772994, - "h": -20.691233464395506, - "m": -0.16026169011931835, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.01635048158070518, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "x": -2.99043354707179, + "y": -13.884989104425435 + }, + "a": -3.017497888282994, + "b": -3.0367265589003347, + "c": -3.0350781164829344, + "d": -2.99043354707179, + "e": -14.070847254450332, + "f": -13.978927774195828, + "g": -13.967607474332265, + "h": -13.884989104425435, + "m": 0.022119013959002398, + "n": 0.020877113034741157, + "o": -0.019228670617340793, + "p": 0.15189725043419955, + "q": -0.08059918039094072, + "r": 0.09191948025450358 }, "backward": false }, { "spline": { "start": { - "x": -11.913723992363813, - "y": -20.691233464395506 + "x": -2.99043354707179, + "y": -13.884989104425435 }, "control1": { - "x": -11.988402918774163, - "y": -20.67471849070281 + "x": -2.9561169305131325, + "y": -13.821483442747212 }, "control2": { - "x": -11.988286847509807, - "y": -20.660387206198756 + "x": -2.9394572592036656, + "y": -13.828419458355432 }, "end": { - "x": -12.03840299042734, - "y": -20.602611271546497 - }, - "a": -11.913723992363813, - "b": -11.988402918774163, - "c": -11.988286847509807, - "d": -12.03840299042734, - "e": -20.691233464395506, - "f": -20.67471849070281, - "g": -20.660387206198756, - "h": -20.602611271546497, - "m": -0.12502721185659382, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.04562833933684729, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "x": -2.8722557581638455, + "y": -13.802064999620375 + }, + "a": -2.99043354707179, + "b": -2.9561169305131325, + "c": -2.9394572592036656, + "d": -2.8722557581638455, + "e": -13.884989104425435, + "f": -13.821483442747212, + "g": -13.828419458355432, + "h": -13.802064999620375, + "m": 0.0681987749795443, + "n": -0.017656945249190592, + "o": 0.034316616558657476, + "p": 0.10373215162971228, + "q": -0.07044167728644268, + "r": 0.06350566167822258 }, "backward": false }, { "spline": { "start": { - "x": -12.03840299042734, - "y": -20.602611271546497 + "x": -2.8722557581638455, + "y": -13.802064999620375 }, "control1": { - "x": -13.035577240069488, - "y": -19.453028104720506 + "x": 2.6133247094805503, + "y": -11.650781146798318 }, "control2": { - "x": -13.035577240069491, - "y": -19.453028104720506 + "x": 2.6133247094805485, + "y": -11.650781146798318 }, "end": { - "x": -14.03275148971164, - "y": -18.303444937894515 - }, - "a": -12.03840299042734, - "b": -13.035577240069488, - "c": -13.035577240069491, - "d": -14.03275148971164, - "e": -20.602611271546497, - "f": -19.453028104720506, - "g": -19.453028104720506, - "h": -18.303444937894515, - "m": -1.9943484992842926, - "n": 0.9971742496421445, - "o": -0.9971742496421481, - "p": 2.2991663336519856, - "q": -1.149583166825991, - "r": 1.149583166825991 + "x": 8.098905177124944, + "y": -9.49949729397626 + }, + "a": -2.8722557581638455, + "b": 2.6133247094805503, + "c": 2.6133247094805485, + "d": 8.098905177124944, + "e": -13.802064999620375, + "f": -11.650781146798318, + "g": -11.650781146798318, + "h": -9.49949729397626, + "m": 10.971160935288795, + "n": -5.4855804676443976, + "o": 5.485580467644396, + "p": 4.302567705644114, + "q": -2.1512838528220577, + "r": 2.1512838528220577 }, "backward": false }, { "spline": { "start": { - "x": -14.03275148971164, - "y": -18.303444937894515 + "x": 8.098905177124944, + "y": -9.49949729397626 }, "control1": { - "x": -14.090339909564419, - "y": -18.23705465746898 + "x": 8.186056258191485, + "y": -9.465319194765012 }, "control2": { - "x": -14.09492842517947, - "y": -18.216263205793762 + "x": 8.198012585692142, + "y": -9.46514688788814 }, "end": { - "x": -14.070631885986728, - "y": -18.131801536266238 - }, - "a": -14.03275148971164, - "b": -14.090339909564419, - "c": -14.09492842517947, - "d": -14.070631885986728, - "e": -18.303444937894515, - "f": -18.23705465746898, - "g": -18.216263205793762, - "h": -18.131801536266238, - "m": -0.024114849429931695, - "n": 0.052999904237728046, - "o": -0.05758841985277918, - "p": 0.10926904660261627, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "x": 8.28611237482917, + "y": -9.4967993846423 + }, + "a": 8.098905177124944, + "b": 8.186056258191485, + "c": 8.198012585692142, + "d": 8.28611237482917, + "e": -9.49949729397626, + "f": -9.465319194765012, + "g": -9.46514688788814, + "h": -9.4967993846423, + "m": 0.15133821520225155, + "n": -0.07519475356588323, + "o": 0.08715108106654057, + "p": 0.002180988703345932, + "q": -0.034005792334376395, + "r": 0.034178099211247925 }, "backward": false }, { "spline": { "start": { - "x": -14.070631885986728, - "y": -18.131801536266238 + "x": 8.28611237482917, + "y": -9.4967993846423 }, "control1": { - "x": -14.056097025524844, - "y": -18.08127423395287 + "x": 8.352927417873165, + "y": -9.520804696729819 }, "control2": { - "x": -14.022793560499485, - "y": -18.10935045182197 + "x": 8.347762426411911, + "y": -9.539239196197277 }, "end": { - "x": -13.972504839170023, - "y": -18.094010472853245 - }, - "a": -14.070631885986728, - "b": -14.056097025524844, - "c": -14.022793560499485, - "d": -13.972504839170023, - "e": -18.131801536266238, - "f": -18.08127423395287, - "g": -18.10935045182197, - "h": -18.094010472853245, - "m": -0.0017833482593729855, - "n": 0.01876860456347451, - "o": 0.014534860461884236, - "p": 0.1220197170203079, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "x": 8.382249005602903, + "y": -9.601297079186416 + }, + "a": 8.28611237482917, + "b": 8.352927417873165, + "c": 8.347762426411911, + "d": 8.382249005602903, + "e": -9.4967993846423, + "f": -9.520804696729819, + "g": -9.539239196197277, + "h": -9.601297079186416, + "m": 0.11163160515749126, + "n": -0.07198003450524837, + "o": 0.0668150430439951, + "p": -0.04919419614174281, + "q": 0.00557081262006065, + "r": -0.024005312087519215 }, "backward": false }, { "spline": { "start": { - "x": -13.972504839170023, - "y": -18.094010472853245 + "x": 8.382249005602903, + "y": -9.601297079186416 }, "control1": { - "x": -7.414678997430931, - "y": -16.093623351578195 + "x": 8.78014321865592, + "y": -10.317299451557108 }, "control2": { - "x": -7.41467899743093, - "y": -16.093623351578195 + "x": 8.78014321865592, + "y": -10.31729945155711 }, "end": { - "x": -0.8568531556918383, - "y": -14.093236230303141 - }, - "a": -13.972504839170023, - "b": -7.414678997430931, - "c": -7.41467899743093, - "d": -0.8568531556918383, - "e": -18.094010472853245, - "f": -16.093623351578195, - "g": -16.093623351578195, - "h": -14.093236230303141, - "m": 13.115651683478186, - "n": -6.557825841739091, - "o": 6.557825841739092, - "p": 4.000774242550104, - "q": -2.0003871212750504, - "r": 2.0003871212750504 + "x": 9.178037431708939, + "y": -11.033301823927802 + }, + "a": 8.382249005602903, + "b": 8.78014321865592, + "c": 8.78014321865592, + "d": 9.178037431708939, + "e": -9.601297079186416, + "f": -10.317299451557108, + "g": -10.31729945155711, + "h": -11.033301823927802, + "m": 0.795788426106034, + "n": -0.39789421305301786, + "o": 0.39789421305301786, + "p": -1.4320047447413806, + "q": 0.7160023723706903, + "r": -0.7160023723706921 }, "backward": false }, { "spline": { "start": { - "x": -0.8568531556918383, - "y": -14.093236230303141 + "x": 9.178037431708939, + "y": -11.033301823927802 }, "control1": { - "x": -0.766191761691019, - "y": -14.065581045592108 + "x": 9.222398309721111, + "y": -11.113128302562538 }, "control2": { - "x": -0.7563105984793453, - "y": -14.06586061911611 + "x": 9.223784673170757, + "y": -11.128913344479823 }, "end": { - "x": -0.6673579189760938, - "y": -14.09859772975167 - }, - "a": -0.8568531556918383, - "b": -0.766191761691019, - "c": -0.7563105984793453, - "d": -0.6673579189760938, - "e": -14.093236230303141, - "f": -14.065581045592108, - "g": -14.06586061911611, - "h": -14.09859772975167, - "m": 0.15985174708072336, - "n": -0.08078023078914565, - "o": 0.0906613940008193, - "p": -0.004522778876516753, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "x": 9.194017543557615, + "y": -11.215250313292882 + }, + "a": 9.178037431708939, + "b": 9.222398309721111, + "c": 9.223784673170757, + "d": 9.194017543557615, + "e": -11.033301823927802, + "f": -11.113128302562538, + "g": -11.128913344479823, + "h": -11.215250313292882, + "m": 0.011821021499736517, + "n": -0.04297451456252688, + "o": 0.04436087801217248, + "p": -0.1345933636132326, + "q": 0.0640414367174511, + "r": -0.07982647863473602 }, "backward": false }, { "spline": { "start": { - "x": -0.6673579189760938, - "y": -14.09859772975167 + "x": 9.194017543557615, + "y": -11.215250313292882 }, "control1": { - "x": -0.5962152169835718, - "y": -14.124780263195834 + "x": 9.173649391884538, + "y": -11.274326364582752 }, "control2": { - "x": -0.5985961910203768, - "y": -14.13947853916734 + "x": 9.148506812565193, + "y": -11.261612969860874 }, "end": { - "x": -0.5577240123279424, - "y": -14.203324244686861 - }, - "a": -0.6673579189760938, - "b": -0.5962152169835718, - "c": -0.5985961910203768, - "d": -0.5577240123279424, - "e": -14.09859772975167, - "f": -14.124780263195834, - "g": -14.13947853916734, - "h": -14.203324244686861, - "m": 0.11677682875856632, - "n": -0.07352367602932686, - "o": 0.07114270199252193, - "p": -0.06063168702067756, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "x": 9.090939785011471, + "y": -11.285920968075658 + }, + "a": 9.194017543557615, + "b": 9.173649391884538, + "c": 9.148506812565193, + "d": 9.090939785011471, + "e": -11.215250313292882, + "f": -11.274326364582752, + "g": -11.261612969860874, + "h": -11.285920968075658, + "m": -0.027650020588106727, + "n": -0.004774427646268009, + "o": -0.02036815167307715, + "p": -0.10881083894841659, + "q": 0.07178944601174742, + "r": -0.05907605128986937 }, "backward": false }, { "spline": { "start": { - "x": -0.5577240123279424, - "y": -14.203324244686861 + "x": 9.090939785011471, + "y": -11.285920968075658 }, "control1": { - "x": -0.24631405135267054, - "y": -14.68977222226807 + "x": 3.3525726878353623, + "y": -13.708978455693217 }, "control2": { - "x": -0.24631405135267095, - "y": -14.689772222268072 + "x": 3.3525726878353628, + "y": -13.70897845569322 }, "end": { - "x": 0.06509590962260095, - "y": -15.176220199849281 - }, - "a": -0.5577240123279424, - "b": -0.24631405135267054, - "c": -0.24631405135267095, - "d": 0.06509590962260095, - "e": -14.203324244686861, - "f": -14.68977222226807, - "g": -14.689772222268072, - "h": -15.176220199849281, - "m": 0.6228199219505446, - "n": -0.3114099609752723, - "o": 0.3114099609752719, - "p": -0.9728959551624214, - "q": 0.48644797758120717, - "r": -0.48644797758120895 + "x": -2.3857944093407464, + "y": -16.13203594331078 + }, + "a": 9.090939785011471, + "b": 3.3525726878353623, + "c": 3.3525726878353628, + "d": -2.3857944093407464, + "e": -11.285920968075658, + "f": -13.708978455693217, + "g": -13.70897845569322, + "h": -16.13203594331078, + "m": -11.47673419435222, + "n": 5.738367097176109, + "o": -5.738367097176109, + "p": -4.846114975235114, + "q": 2.4230574876175552, + "r": -2.423057487617559 }, "backward": false } @@ -1198,770 +1198,3080 @@ { "spline": { "start": { - "x": -11.16085925302996, - "y": -15.916473261222762 + "x": 10.040576977764365, + "y": -11.817216671012797 }, "control1": { - "x": -13.542611646920335, - "y": -16.98291991927031 + "x": 9.872857166860037, + "y": -11.015459848946962 }, "control2": { - "x": -13.542611646920335, - "y": -16.982919919270312 + "x": 9.872857166860035, + "y": -11.015459848946962 }, "end": { - "x": -15.92436404081071, - "y": -18.04936657731786 - }, - "a": -11.16085925302996, - "b": -13.542611646920335, - "c": -13.542611646920335, - "d": -15.92436404081071, - "e": -15.916473261222762, - "f": -16.98291991927031, - "g": -16.982919919270312, - "h": -18.04936657731786, - "m": -4.763504787780748, - "n": 2.381752393890375, - "o": -2.381752393890375, - "p": -2.1328933160950854, - "q": 1.0664466580475427, - "r": -1.0664466580475462 + "x": 9.705137355955706, + "y": -10.213703026881127 + }, + "a": 10.040576977764365, + "b": 9.872857166860037, + "c": 9.872857166860035, + "d": 9.705137355955706, + "e": -11.817216671012797, + "f": -11.015459848946962, + "g": -11.015459848946962, + "h": -10.213703026881127, + "m": -0.3354396218086517, + "n": 0.16771981090432675, + "o": -0.16771981090432853, + "p": 1.6035136441316702, + "q": -0.8017568220658351, + "r": 0.8017568220658351 }, "backward": false }, { "spline": { "start": { - "x": -15.92436404081071, - "y": -18.04936657731786 + "x": 9.705137355955706, + "y": -10.213703026881127 }, "control1": { - "x": -15.978768937226278, - "y": -18.07372675854939 + "x": 9.684892999619743, + "y": -10.116928229001411 }, "control2": { - "x": -15.989166600633256, - "y": -18.120743868891662 + "x": 9.68411244066312, + "y": -10.114833677421275 }, "end": { - "x": -15.950106984749256, - "y": -18.165773387994314 - }, - "a": -15.92436404081071, - "b": -15.978768937226278, - "c": -15.989166600633256, - "d": -15.950106984749256, - "e": -18.04936657731786, - "f": -18.07372675854939, - "g": -18.120743868891662, - "h": -18.165773387994314, - "m": 0.005450046282392762, - "n": 0.044007233008589, - "o": -0.05440489641556745, - "p": 0.02464452035035336, - "q": -0.022656929110738133, - "r": -0.024360181231532607 + "x": 9.636086506776302, + "y": -10.028412006861071 + }, + "a": 9.705137355955706, + "b": 9.684892999619743, + "c": 9.68411244066312, + "d": 9.636086506776302, + "e": -10.213703026881127, + "f": -10.116928229001411, + "g": -10.114833677421275, + "h": -10.028412006861071, + "m": -0.06670917230952966, + "n": 0.019463797379339454, + "o": -0.02024435633596333, + "p": 0.17900736527964867, + "q": -0.09468024629958016, + "r": 0.09677479787971599 }, "backward": false }, { "spline": { "start": { - "x": -15.950106984749256, - "y": -18.165773387994314 + "x": 9.636086506776302, + "y": -10.028412006861071 }, "control1": { - "x": -15.737898567613128, - "y": -18.410415910889412 + "x": 9.290748473559788, + "y": -9.40698338854662 }, "control2": { - "x": -15.753619330875859, - "y": -18.719310154659887 + "x": 8.774075136077252, + "y": -8.104352094647044 }, "end": { - "x": -15.458040977338284, - "y": -18.586962834203764 + "x": 8.919645365222031, + "y": -8.800226503741671 + }, + "a": 9.636086506776302, + "b": 9.290748473559788, + "c": 8.774075136077252, + "d": 8.919645365222031, + "e": -10.028412006861071, + "f": -9.40698338854662, + "g": -8.104352094647044, + "h": -8.800226503741671, + "m": 0.8335788708933336, + "n": -0.17133530426602128, + "o": -0.3453380332165139, + "p": -2.6797083785793223, + "q": 0.6812026755851228, + "r": 0.621428618314452 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.919645365222031, + "y": -8.800226503741671 + }, + "control1": { + "x": 9.25508498703069, + "y": -10.40374014787334 }, - "a": -15.950106984749256, - "b": -15.737898567613128, - "c": -15.753619330875859, - "d": -15.458040977338284, - "e": -18.165773387994314, - "f": -18.410415910889412, - "g": -18.719310154659887, - "h": -18.586962834203764, - "m": 0.539228297199168, - "n": -0.2279291803988599, - "o": 0.2122084171361287, - "p": 0.50549328510197, - "q": -0.0642517208753759, - "r": -0.24464252289509858 + "control2": { + "x": 9.25508498703069, + "y": -10.40374014787334 + }, + "end": { + "x": 9.590524608839349, + "y": -12.007253792005008 + }, + "a": 8.919645365222031, + "b": 9.25508498703069, + "c": 9.25508498703069, + "d": 9.590524608839349, + "e": -8.800226503741671, + "f": -10.40374014787334, + "g": -10.40374014787334, + "h": -12.007253792005008, + "m": 0.6708792436173177, + "n": -0.3354396218086588, + "o": 0.3354396218086588, + "p": -3.207027288263335, + "q": 1.6035136441316684, + "r": -1.6035136441316684 }, "backward": false }, { "spline": { "start": { - "x": -15.458040977338284, - "y": -18.586962834203764 + "x": 9.590524608839349, + "y": -12.007253792005008 }, "control1": { - "x": -10.694536189557535, - "y": -16.454069518108664 + "x": 9.606332981708317, + "y": -12.08282310445007 }, "control2": { - "x": -10.694536189557535, - "y": -16.454069518108664 + "x": 9.59000087466324, + "y": -12.114002228886791 }, "end": { - "x": -5.931031401776787, - "y": -14.321176202013566 + "x": 9.518876571100726, + "y": -12.144034862453148 + }, + "a": 9.590524608839349, + "b": 9.606332981708317, + "c": 9.59000087466324, + "d": 9.518876571100726, + "e": -12.007253792005008, + "f": -12.08282310445007, + "g": -12.114002228886791, + "h": -12.144034862453148, + "m": -0.022651716603391492, + "n": -0.03214047991404456, + "o": 0.015808372868967524, + "p": -0.04324369713796905, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.518876571100726, + "y": -12.144034862453148 + }, + "control1": { + "x": 9.342858495259119, + "y": -12.218359475954074 + }, + "control2": { + "x": 9.179594723161383, + "y": -12.384309474909207 }, - "a": -15.458040977338284, - "b": -10.694536189557535, - "c": -10.694536189557535, - "d": -5.931031401776787, - "e": -18.586962834203764, - "f": -16.454069518108664, - "g": -16.454069518108664, - "h": -14.321176202013566, - "m": 9.527009575561499, - "n": -4.763504787780748, - "o": 4.763504787780748, - "p": 4.265786632190196, - "q": -2.1328933160950996, - "r": 2.1328933160950996 + "end": { + "x": 9.140472239914333, + "y": -12.19729091299722 + }, + "a": 9.518876571100726, + "b": 9.342858495259119, + "c": 9.179594723161383, + "d": 9.140472239914333, + "e": -12.144034862453148, + "f": -12.218359475954074, + "g": -12.384309474909207, + "h": -12.19729091299722, + "m": 0.11138698510681522, + "n": 0.012754303743871986, + "o": -0.17601807584160767, + "p": 0.4445939463213282, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -5.931031401776787, - "y": -14.321176202013566 + "x": 9.140472239914333, + "y": -12.19729091299722 }, "control1": { - "x": -5.839940764336174, - "y": -14.280389718025074 + "x": 8.751027792687397, + "y": -10.335616131131644 }, "control2": { - "x": -5.839576307662656, - "y": -14.280253146868457 + "x": 8.751027792687399, + "y": -10.335616131131644 }, "end": { - "x": -5.7441138298695975, - "y": -14.251133448396624 + "x": 8.361583345460463, + "y": -8.47394134926607 + }, + "a": 9.140472239914333, + "b": 8.751027792687397, + "c": 8.751027792687399, + "d": 8.361583345460463, + "e": -12.19729091299722, + "f": -10.335616131131644, + "g": -10.335616131131644, + "h": -8.47394134926607, + "m": -0.778888894453873, + "n": 0.3894444472269374, + "o": -0.3894444472269356, + "p": 3.7233495637311496, + "q": -1.8616747818655757, + "r": 1.8616747818655757 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.361583345460463, + "y": -8.47394134926607 }, - "a": -5.931031401776787, - "b": -5.839940764336174, - "c": -5.839576307662656, - "d": -5.7441138298695975, - "e": -14.321176202013566, - "f": -14.280389718025074, - "g": -14.280253146868457, - "h": -14.251133448396624, - "m": 0.18582420188663384, - "n": -0.09072618076709471, - "o": 0.09109063744061263, - "p": 0.06963304014709415, - "q": -0.04064991283187602, - "r": 0.040786483988492606 + "control1": { + "x": 8.34836684143731, + "y": -8.410762037249858 + }, + "control2": { + "x": 8.308101791072355, + "y": -8.389003972573823 + }, + "end": { + "x": 8.248010645611375, + "y": -8.412569958286781 + }, + "a": 8.361583345460463, + "b": 8.34836684143731, + "c": 8.308101791072355, + "d": 8.248010645611375, + "e": -8.47394134926607, + "f": -8.410762037249858, + "g": -8.389003972573823, + "h": -8.412569958286781, + "m": 0.007222451245770145, + "n": -0.027048546341800872, + "o": -0.013216504023153774, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -5.7441138298695975, - "y": -14.251133448396624 + "x": 8.248010645611375, + "y": -8.412569958286781 }, "control1": { - "x": -3.22446920155933, - "y": -13.482545689846898 + "x": 8.05492832198582, + "y": -8.488291185517157 }, "control2": { - "x": 1.7030503576363891, - "y": -11.649357270912006 + "x": 7.866446622056128, + "y": -8.448460168768857 }, "end": { - "x": -0.7012035505236112, - "y": -12.725879142804372 + "x": 7.908913333040266, + "y": -8.651465258380197 + }, + "a": 8.248010645611375, + "b": 8.05492832198582, + "c": 7.866446622056128, + "d": 7.908913333040266, + "e": -8.412569958286781, + "f": -8.488291185517157, + "g": -8.448460168768857, + "h": -8.651465258380197, + "m": 0.22634778721796422, + "n": 0.004600623695862893, + "o": -0.19308232362555522, + "p": -0.3583883503383163, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.908913333040266, + "y": -8.651465258380197 + }, + "control1": { + "x": 8.29966660201479, + "y": -10.519396646184813 + }, + "control2": { + "x": 8.299666602014792, + "y": -10.519396646184815 }, - "a": -5.7441138298695975, - "b": -3.22446920155933, - "c": 1.7030503576363891, - "d": -0.7012035505236112, - "e": -14.251133448396624, - "f": -13.482545689846898, - "g": -11.649357270912006, - "h": -12.725879142804372, - "m": -9.739648398241172, - "n": 2.407874930885452, - "o": 2.5196446283102674, - "p": -3.9743109512124217, - "q": 1.0646006603851657, - "r": 0.7685877585497263 + "end": { + "x": 8.690419870989317, + "y": -12.38732803398943 + }, + "a": 7.908913333040266, + "b": 8.29966660201479, + "c": 8.299666602014792, + "d": 8.690419870989317, + "e": -8.651465258380197, + "f": -10.519396646184813, + "g": -10.519396646184815, + "h": -12.38732803398943, + "m": 0.7815065379490473, + "n": -0.3907532689745228, + "o": 0.39075326897452456, + "p": -3.7358627756092293, + "q": 1.8679313878046138, + "r": -1.8679313878046155 }, "backward": false }, { "spline": { "start": { - "x": -0.7012035505236112, - "y": -12.725879142804372 + "x": 8.690419870989317, + "y": -12.38732803398943 }, "control1": { - "x": -7.846460732194735, - "y": -15.92521911694702 + "x": 8.706228243858284, + "y": -12.462897346434492 }, "control2": { - "x": -7.846460732194734, - "y": -15.92521911694702 + "x": 8.689896136813207, + "y": -12.494076470871214 }, "end": { - "x": -14.991717913865857, - "y": -19.124559091089665 + "x": 8.618771833250694, + "y": -12.52410910443757 + }, + "a": 8.690419870989317, + "b": 8.706228243858284, + "c": 8.689896136813207, + "d": 8.618771833250694, + "e": -12.38732803398943, + "f": -12.462897346434492, + "g": -12.494076470871214, + "h": -12.52410910443757, + "m": -0.022651716603391492, + "n": -0.03214047991404456, + "o": 0.015808372868967524, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.618771833250694, + "y": -12.52410910443757 + }, + "control1": { + "x": 8.442753757409086, + "y": -12.598433717938496 }, - "a": -0.7012035505236112, - "b": -7.846460732194735, - "c": -7.846460732194734, - "d": -14.991717913865857, - "e": -12.725879142804372, - "f": -15.92521911694702, - "g": -15.92521911694702, - "h": -19.124559091089665, - "m": -14.29051436334225, - "n": 7.145257181671124, - "o": -7.1452571816711234, - "p": -6.398679948285293, - "q": 3.1993399741426476, - "r": -3.1993399741426476 + "control2": { + "x": 8.27948998531135, + "y": -12.76438371689363 + }, + "end": { + "x": 8.2403675020643, + "y": -12.577365154981642 + }, + "a": 8.618771833250694, + "b": 8.442753757409086, + "c": 8.27948998531135, + "d": 8.2403675020643, + "e": -12.52410910443757, + "f": -12.598433717938496, + "g": -12.76438371689363, + "h": -12.577365154981642, + "m": 0.11138698510681522, + "n": 0.012754303743871986, + "o": -0.17601807584160767, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -14.991717913865857, - "y": -19.124559091089665 + "x": 8.2403675020643, + "y": -12.577365154981642 }, "control1": { - "x": -15.046122810281425, - "y": -19.148919272321198 + "x": 7.848305411342184, + "y": -10.703177161237981 }, "control2": { - "x": -15.056520473688403, - "y": -19.19593638266347 + "x": 7.848305411342184, + "y": -10.703177161237981 }, "end": { - "x": -15.017460857804403, - "y": -19.24096590176612 + "x": 7.456243320620068, + "y": -8.828989167494322 + }, + "a": 8.2403675020643, + "b": 7.848305411342184, + "c": 7.848305411342184, + "d": 7.456243320620068, + "e": -12.577365154981642, + "f": -10.703177161237981, + "g": -10.703177161237981, + "h": -8.828989167494322, + "m": -0.7841241814442323, + "n": 0.3920620907221162, + "o": -0.3920620907221162, + "p": 3.7483759874873215, + "q": -1.8741879937436607, + "r": 1.8741879937436607 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.456243320620068, + "y": -8.828989167494322 + }, + "control1": { + "x": 7.443026816596913, + "y": -8.765809855478109 + }, + "control2": { + "x": 7.402761766231959, + "y": -8.744051790802075 }, - "a": -14.991717913865857, - "b": -15.046122810281425, - "c": -15.056520473688403, - "d": -15.017460857804403, - "e": -19.124559091089665, - "f": -19.148919272321198, - "g": -19.19593638266347, - "h": -19.24096590176612, - "m": 0.005450046282392762, - "n": 0.044007233008589, - "o": -0.05440489641556745, - "p": 0.024644520350349808, - "q": -0.022656929110738133, - "r": -0.024360181231532607 + "end": { + "x": 7.342670620770979, + "y": -8.767617776515033 + }, + "a": 7.456243320620068, + "b": 7.443026816596913, + "c": 7.402761766231959, + "d": 7.342670620770979, + "e": -8.828989167494322, + "f": -8.765809855478109, + "g": -8.744051790802075, + "h": -8.767617776515033, + "m": 0.007222451245773698, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.003902803048807968, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -15.017460857804403, - "y": -19.24096590176612 + "x": 7.342670620770979, + "y": -8.767617776515033 }, "control1": { - "x": -14.805252440668278, - "y": -19.485608424661216 + "x": 7.149588297145424, + "y": -8.84333900374541 }, "control2": { - "x": -14.82097320393101, - "y": -19.794502668431686 + "x": 6.961106597215733, + "y": -8.803507986997111 }, "end": { - "x": -14.525394850393438, - "y": -19.662155347975567 + "x": 7.00357330819987, + "y": -9.00651307660845 + }, + "a": 7.342670620770979, + "b": 7.149588297145424, + "c": 6.961106597215733, + "d": 7.00357330819987, + "e": -8.767617776515033, + "f": -8.84333900374541, + "g": -8.803507986997111, + "h": -9.00651307660845, + "m": 0.2263477872179651, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.00357330819987, + "y": -9.00651307660845 + }, + "control1": { + "x": 7.396944220669577, + "y": -10.886957676291152 + }, + "control2": { + "x": 7.396944220669577, + "y": -10.886957676291152 }, - "a": -15.017460857804403, - "b": -14.805252440668278, - "c": -14.82097320393101, - "d": -14.525394850393438, - "e": -19.24096590176612, - "f": -19.485608424661216, - "g": -19.794502668431686, - "h": -19.662155347975567, - "m": 0.5392282971991538, - "n": -0.22792918039885635, - "o": 0.21220841713612515, - "p": 0.5054932851019593, - "q": -0.0642517208753759, - "r": -0.24464252289509503 + "end": { + "x": 7.790315133139284, + "y": -12.767402275973854 + }, + "a": 7.00357330819987, + "b": 7.396944220669577, + "c": 7.396944220669577, + "d": 7.790315133139284, + "e": -9.00651307660845, + "f": -10.886957676291152, + "g": -10.886957676291152, + "h": -12.767402275973854, + "m": 0.7867418249394138, + "n": -0.3933709124697069, + "o": 0.3933709124697069, + "p": -3.760889199365405, + "q": 1.8804445996827024, + "r": -1.8804445996827024 }, "backward": false }, { "spline": { "start": { - "x": -14.525394850393438, - "y": -19.662155347975567 + "x": 7.790315133139284, + "y": -12.767402275973854 }, "control1": { - "x": -7.156892723372116, - "y": -16.36285585389901 + "x": 7.8061235060082526, + "y": -12.842971588418916 }, "control2": { - "x": -7.156892723372115, - "y": -16.36285585389901 + "x": 7.789791398963174, + "y": -12.874150712855638 }, "end": { - "x": 0.21160940364920622, - "y": -13.063556359822453 + "x": 7.718667095400661, + "y": -12.904183346421995 + }, + "a": 7.790315133139284, + "b": 7.8061235060082526, + "c": 7.789791398963174, + "d": 7.718667095400661, + "e": -12.767402275973854, + "f": -12.842971588418916, + "g": -12.874150712855638, + "h": -12.904183346421995, + "m": -0.022651716603384386, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.718667095400661, + "y": -12.904183346421995 + }, + "control1": { + "x": 7.542649019559054, + "y": -12.97850795992292 + }, + "control2": { + "x": 7.379385247461317, + "y": -13.144457958878053 }, - "a": -14.525394850393438, - "b": -7.156892723372116, - "c": -7.156892723372115, - "d": 0.21160940364920622, - "e": -19.662155347975567, - "f": -16.36285585389901, - "g": -16.36285585389901, - "h": -13.063556359822453, - "m": 14.73700425404264, - "n": -7.368502127021321, - "o": 7.368502127021322, - "p": 6.59859898815311, - "q": -3.299299494076557, - "r": 3.299299494076557 + "end": { + "x": 7.340262764214268, + "y": -12.957439396966066 + }, + "a": 7.718667095400661, + "b": 7.542649019559054, + "c": 7.379385247461317, + "d": 7.340262764214268, + "e": -12.904183346421995, + "f": -12.97850795992292, + "g": -13.144457958878053, + "h": -12.957439396966066, + "m": 0.11138698510681788, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": 0.21160940364920622, - "y": -13.063556359822453 + "x": 7.340262764214268, + "y": -12.957439396966066 }, "control1": { - "x": 0.2807541078058382, - "y": -13.032596324321416 + "x": 6.94558302999697, + "y": -11.070738191344322 }, "control2": { - "x": 0.3159473658054553, - "y": -13.043105620153257 + "x": 6.94558302999697, + "y": -11.070738191344322 }, "end": { - "x": 0.3567935939988915, - "y": -13.106910788858492 + "x": 6.550903295779673, + "y": -9.184036985722576 + }, + "a": 7.340262764214268, + "b": 6.94558302999697, + "c": 6.94558302999697, + "d": 6.550903295779673, + "e": -12.957439396966066, + "f": -11.070738191344322, + "g": -11.070738191344322, + "h": -9.184036985722576, + "m": -0.7893594684345953, + "n": 0.39467973421729763, + "o": -0.39467973421729763, + "p": 3.773402411243488, + "q": -1.886701205621744, + "r": 1.886701205621744 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.550903295779673, + "y": -9.184036985722576 + }, + "control1": { + "x": 6.537686791756518, + "y": -9.120857673706363 }, - "a": 0.21160940364920622, - "b": 0.2807541078058382, - "c": 0.3159473658054553, - "d": 0.3567935939988915, - "e": -13.063556359822453, - "f": -13.032596324321416, - "g": -13.043105620153257, - "h": -13.106910788858492, - "m": 0.039604416350834004, - "n": -0.0339514461570149, - "o": 0.06914470415663199, - "p": -0.011826541540511215, - "q": -0.04146933133287867, - "r": 0.030960035501037098 + "control2": { + "x": 6.497421741391563, + "y": -9.099099609030329 + }, + "end": { + "x": 6.437330595930583, + "y": -9.122665594743287 + }, + "a": 6.550903295779673, + "b": 6.537686791756518, + "c": 6.497421741391563, + "d": 6.437330595930583, + "e": -9.184036985722576, + "f": -9.120857673706363, + "g": -9.099099609030329, + "h": -9.122665594743287, + "m": 0.007222451245773698, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.0039028030488115206, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": 0.3567935939988915, - "y": -13.106910788858492 + "x": 6.437330595930583, + "y": -9.122665594743287 }, "control1": { - "x": 0.5138141145254159, - "y": -13.35218975941442 + "x": 6.244248272305028, + "y": -9.198386821973664 }, "control2": { - "x": 0.848785379649943, - "y": -13.52465200394487 + "x": 6.055766572375336, + "y": -9.158555805225363 }, "end": { - "x": 0.5829802387955523, - "y": -13.64366815561671 + "x": 6.098233283359473, + "y": -9.361560894836703 + }, + "a": 6.437330595930583, + "b": 6.244248272305028, + "c": 6.055766572375336, + "d": 6.098233283359473, + "e": -9.122665594743287, + "f": -9.198386821973664, + "g": -9.158555805225363, + "h": -9.361560894836703, + "m": 0.2263477872179669, + "n": 0.004600623695862893, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.098233283359473, + "y": -9.361560894836703 + }, + "control1": { + "x": 6.4942218393243625, + "y": -11.25451870639749 + }, + "control2": { + "x": 6.4942218393243625, + "y": -11.25451870639749 }, - "a": 0.3567935939988915, - "b": 0.5138141145254159, - "c": 0.848785379649943, - "d": 0.5829802387955523, - "e": -13.106910788858492, - "f": -13.35218975941442, - "g": -13.52465200394487, - "h": -13.64366815561671, - "m": -0.7787271505769207, - "n": 0.1779507445980027, - "o": 0.15702052052652443, - "p": -0.019370633166865048, - "q": 0.07281672602547751, - "r": -0.24527897055592796 + "end": { + "x": 6.890210395289252, + "y": -13.147476517958278 + }, + "a": 6.098233283359473, + "b": 6.4942218393243625, + "c": 6.4942218393243625, + "d": 6.890210395289252, + "e": -9.361560894836703, + "f": -11.25451870639749, + "g": -11.25451870639749, + "h": -13.147476517958278, + "m": 0.7919771119297785, + "n": -0.39598855596488924, + "o": 0.39598855596488924, + "p": -3.785915623121575, + "q": 1.8929578115607875, + "r": -1.8929578115607875 }, "backward": false }, { "spline": { "start": { - "x": 0.5829802387955523, - "y": -13.64366815561671 + "x": 6.890210395289252, + "y": -13.147476517958278 }, "control1": { - "x": -6.738045774062729, - "y": -16.921709880239092 + "x": 6.90601876815822, + "y": -13.22304583040334 }, "control2": { - "x": -6.73804577406273, - "y": -16.921709880239092 + "x": 6.889686661113141, + "y": -13.254224954840062 }, "end": { - "x": -14.059071786921011, - "y": -20.199751604861472 + "x": 6.818562357550628, + "y": -13.284257588406419 + }, + "a": 6.890210395289252, + "b": 6.90601876815822, + "c": 6.889686661113141, + "d": 6.818562357550628, + "e": -13.147476517958278, + "f": -13.22304583040334, + "g": -13.254224954840062, + "h": -13.284257588406419, + "m": -0.022651716603384386, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.818562357550628, + "y": -13.284257588406419 }, - "a": 0.5829802387955523, - "b": -6.738045774062729, - "c": -6.73804577406273, - "d": -14.059071786921011, - "e": -13.64366815561671, - "f": -16.921709880239092, - "g": -16.921709880239092, - "h": -20.199751604861472, - "m": -14.64205202571656, - "n": 7.3210260128582805, - "o": -7.321026012858281, - "p": -6.5560834492447615, - "q": 3.2780417246223816, - "r": -3.2780417246223816 + "control1": { + "x": 6.642544281709022, + "y": -13.358582201907344 + }, + "control2": { + "x": 6.479280509611287, + "y": -13.524532200862474 + }, + "end": { + "x": 6.440158026364237, + "y": -13.337513638950488 + }, + "a": 6.818562357550628, + "b": 6.642544281709022, + "c": 6.479280509611287, + "d": 6.440158026364237, + "e": -13.284257588406419, + "f": -13.358582201907344, + "g": -13.524532200862474, + "h": -13.337513638950488, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.1760180758416059, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -14.059071786921011, - "y": -20.199751604861472 + "x": 6.440158026364237, + "y": -13.337513638950488 }, "control1": { - "x": -14.113476683336579, - "y": -20.224111786093005 + "x": 6.042860648651756, + "y": -11.43829922145066 }, "control2": { - "x": -14.123874346743555, - "y": -20.271128896435275 + "x": 6.042860648651756, + "y": -11.438299221450658 }, "end": { - "x": -14.084814730859556, - "y": -20.316158415537927 + "x": 5.645563270939276, + "y": -9.539084803950828 + }, + "a": 6.440158026364237, + "b": 6.042860648651756, + "c": 6.042860648651756, + "d": 5.645563270939276, + "e": -13.337513638950488, + "f": -11.43829922145066, + "g": -11.438299221450658, + "h": -9.539084803950828, + "m": -0.7945947554249617, + "n": 0.39729737771248086, + "o": -0.39729737771248086, + "p": 3.79842883499966, + "q": -1.8992144174998273, + "r": 1.899214417499829 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.645563270939276, + "y": -9.539084803950828 + }, + "control1": { + "x": 5.632346766916121, + "y": -9.475905491934615 + }, + "control2": { + "x": 5.592081716551166, + "y": -9.454147427258581 }, - "a": -14.059071786921011, - "b": -14.113476683336579, - "c": -14.123874346743555, - "d": -14.084814730859556, - "e": -20.199751604861472, - "f": -20.224111786093005, - "g": -20.271128896435275, - "h": -20.316158415537927, - "m": 0.005450046282385657, - "n": 0.044007233008590774, - "o": -0.05440489641556745, - "p": 0.024644520350360466, - "q": -0.022656929110738133, - "r": -0.024360181231532607 + "end": { + "x": 5.531990571090186, + "y": -9.47771341297154 + }, + "a": 5.645563270939276, + "b": 5.632346766916121, + "c": 5.592081716551166, + "d": 5.531990571090186, + "e": -9.539084803950828, + "f": -9.475905491934615, + "g": -9.454147427258581, + "h": -9.47771341297154, + "m": 0.007222451245775474, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.003902803048813297, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -14.084814730859556, - "y": -20.316158415537927 + "x": 5.531990571090186, + "y": -9.47771341297154 }, "control1": { - "x": -13.872606313723427, - "y": -20.560800938433022 + "x": 5.338908247464632, + "y": -9.553434640201916 }, "control2": { - "x": -13.888327076986158, - "y": -20.869695182203493 + "x": 5.1504265475349404, + "y": -9.513603623453617 }, "end": { - "x": -13.592748723448585, - "y": -20.737347861747374 + "x": 5.192893258519078, + "y": -9.716608713064955 + }, + "a": 5.531990571090186, + "b": 5.338908247464632, + "c": 5.1504265475349404, + "d": 5.192893258519078, + "e": -9.47771341297154, + "f": -9.553434640201916, + "g": -9.513603623453617, + "h": -9.716608713064955, + "m": 0.22634778721796867, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.3583883503383163, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.192893258519078, + "y": -9.716608713064955 + }, + "control1": { + "x": 5.5914994579791495, + "y": -11.622079736503828 }, - "a": -14.084814730859556, - "b": -13.872606313723427, - "c": -13.888327076986158, - "d": -13.592748723448585, - "e": -20.316158415537927, - "f": -20.560800938433022, - "g": -20.869695182203493, - "h": -20.737347861747374, - "m": 0.5392282971991591, - "n": -0.2279291803988599, - "o": 0.2122084171361287, - "p": 0.5054932851019629, - "q": -0.0642517208753759, - "r": -0.24464252289509503 + "control2": { + "x": 5.5914994579791495, + "y": -11.622079736503828 + }, + "end": { + "x": 5.990105657439221, + "y": -13.5275507599427 + }, + "a": 5.192893258519078, + "b": 5.5914994579791495, + "c": 5.5914994579791495, + "d": 5.990105657439221, + "e": -9.716608713064955, + "f": -11.622079736503828, + "g": -11.622079736503828, + "h": -13.5275507599427, + "m": 0.7972123989201432, + "n": -0.3986061994600716, + "o": 0.3986061994600716, + "p": -3.8109420468777433, + "q": 1.9054710234388725, + "r": -1.9054710234388725 }, "backward": false }, { "spline": { "start": { - "x": -13.592748723448585, - "y": -20.737347861747374 + "x": 5.990105657439221, + "y": -13.5275507599427 }, "control1": { - "x": -6.319198824753343, - "y": -17.48056390657917 + "x": 6.0059140303081895, + "y": -13.60312007238776 }, "control2": { - "x": -6.319198824753344, - "y": -17.48056390657917 + "x": 5.98958192326311, + "y": -13.634299196824482 }, "end": { - "x": 0.9543510739418979, - "y": -14.223779951410968 + "x": 5.918457619700598, + "y": -13.66433183039084 + }, + "a": 5.990105657439221, + "b": 6.0059140303081895, + "c": 5.98958192326311, + "d": 5.918457619700598, + "e": -13.5275507599427, + "f": -13.60312007238776, + "g": -13.634299196824482, + "h": -13.66433183039084, + "m": -0.022651716603386163, + "n": -0.032140479914048115, + "o": 0.015808372868968412, + "p": -0.043243697137976156, + "q": 0.044390188008337716, + "r": -0.07556931244505982 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.918457619700598, + "y": -13.66433183039084 + }, + "control1": { + "x": 5.742439543858991, + "y": -13.738656443891765 + }, + "control2": { + "x": 5.579175771761254, + "y": -13.904606442846898 }, - "a": -13.592748723448585, - "b": -6.319198824753343, - "c": -6.319198824753344, - "d": 0.9543510739418979, - "e": -20.737347861747374, - "f": -17.48056390657917, - "g": -17.48056390657917, - "h": -14.223779951410968, - "m": 14.547099797390485, - "n": -7.273549898695243, - "o": 7.273549898695242, - "p": 6.5135679103364055, - "q": -3.2567839551682027, - "r": 3.2567839551682027 + "end": { + "x": 5.540053288514205, + "y": -13.71758788093491 + }, + "a": 5.918457619700598, + "b": 5.742439543858991, + "c": 5.579175771761254, + "d": 5.540053288514205, + "e": -13.66433183039084, + "f": -13.738656443891765, + "g": -13.904606442846898, + "h": -13.71758788093491, + "m": 0.11138698510681433, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": 0.9543510739418979, - "y": -14.223779951410968 + "x": 5.540053288514205, + "y": -13.71758788093491 }, "control1": { - "x": 1.0234957780985299, - "y": -14.192819915909931 + "x": 5.1401382673065426, + "y": -11.805860251556997 }, "control2": { - "x": 1.058689036098147, - "y": -14.203329211741773 + "x": 5.1401382673065426, + "y": -11.805860251556997 }, "end": { - "x": 1.099535264291583, - "y": -14.267134380447008 + "x": 4.74022324609888, + "y": -9.894132622179082 + }, + "a": 5.540053288514205, + "b": 5.1401382673065426, + "c": 5.1401382673065426, + "d": 4.74022324609888, + "e": -13.71758788093491, + "f": -11.805860251556997, + "g": -11.805860251556997, + "h": -9.894132622179082, + "m": -0.7998300424153246, + "n": 0.3999150212076623, + "o": -0.3999150212076623, + "p": 3.8234552587558284, + "q": -1.9117276293779142, + "r": 1.9117276293779142 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 4.74022324609888, + "y": -9.894132622179082 + }, + "control1": { + "x": 4.727006742075726, + "y": -9.830953310162869 + }, + "control2": { + "x": 4.686741691710771, + "y": -9.809195245486835 }, - "a": 0.9543510739418979, - "b": 1.0234957780985299, - "c": 1.058689036098147, - "d": 1.099535264291583, - "e": -14.223779951410968, - "f": -14.192819915909931, - "g": -14.203329211741773, - "h": -14.267134380447008, - "m": 0.03960441635083356, - "n": -0.03395144615701495, - "o": 0.06914470415663199, - "p": -0.01182654154051832, - "q": -0.04146933133287867, - "r": 0.030960035501037098 + "end": { + "x": 4.626650546249791, + "y": -9.832761231199793 + }, + "a": 4.74022324609888, + "b": 4.727006742075726, + "c": 4.686741691710771, + "d": 4.626650546249791, + "e": -9.894132622179082, + "f": -9.830953310162869, + "g": -9.809195245486835, + "h": -9.832761231199793, + "m": 0.007222451245775474, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.0039028030488097443, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": 1.099535264291583, - "y": -14.267134380447008 + "x": 4.626650546249791, + "y": -9.832761231199793 }, "control1": { - "x": 1.2565557848181077, - "y": -14.512413351002937 + "x": 4.4335682226242366, + "y": -9.90848245843017 }, "control2": { - "x": 1.5915270499426355, - "y": -14.684875595533388 + "x": 4.245086522694545, + "y": -9.868651441681871 }, "end": { - "x": 1.325721909088244, - "y": -14.803891747205228 + "x": 4.287553233678683, + "y": -10.07165653129321 + }, + "a": 4.626650546249791, + "b": 4.4335682226242366, + "c": 4.245086522694545, + "d": 4.287553233678683, + "e": -9.832761231199793, + "f": -9.90848245843017, + "g": -9.868651441681871, + "h": -10.07165653129321, + "m": 0.2263477872179669, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.35838835033830563, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 4.287553233678683, + "y": -10.07165653129321 + }, + "control1": { + "x": 4.688777076633936, + "y": -11.989640766610165 + }, + "control2": { + "x": 4.688777076633936, + "y": -11.989640766610165 }, - "a": 1.099535264291583, - "b": 1.2565557848181077, - "c": 1.5915270499426355, - "d": 1.325721909088244, - "e": -14.267134380447008, - "f": -14.512413351002937, - "g": -14.684875595533388, - "h": -14.803891747205228, - "m": -0.7787271505769222, - "n": 0.17795074459800309, - "o": 0.1570205205265247, - "p": -0.0193706331668686, - "q": 0.07281672602547928, - "r": -0.24527897055592973 + "end": { + "x": 5.090000919589189, + "y": -13.907625001927123 + }, + "a": 4.287553233678683, + "b": 4.688777076633936, + "c": 4.688777076633936, + "d": 5.090000919589189, + "e": -10.07165653129321, + "f": -11.989640766610165, + "g": -11.989640766610165, + "h": -13.907625001927123, + "m": 0.8024476859105061, + "n": -0.40122384295525304, + "o": 0.40122384295525304, + "p": -3.8359684706339134, + "q": 1.9179842353169558, + "r": -1.9179842353169558 }, "backward": false }, { "spline": { "start": { - "x": 1.325721909088244, - "y": -14.803891747205228 + "x": 5.090000919589189, + "y": -13.907625001927123 }, "control1": { - "x": -5.900351875443958, - "y": -18.039417932919253 + "x": 5.105809292458157, + "y": -13.983194314372184 }, "control2": { - "x": -5.900351875443956, - "y": -18.03941793291925 + "x": 5.089477185413078, + "y": -14.014373438808907 }, "end": { - "x": -13.126425659976158, - "y": -21.274944118633275 + "x": 5.018352881850565, + "y": -14.044406072375264 + }, + "a": 5.090000919589189, + "b": 5.105809292458157, + "c": 5.089477185413078, + "d": 5.018352881850565, + "e": -13.907625001927123, + "f": -13.983194314372184, + "g": -14.014373438808907, + "h": -14.044406072375264, + "m": -0.022651716603389715, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.018352881850565, + "y": -14.044406072375264 + }, + "control1": { + "x": 4.842334806008958, + "y": -14.118730685876189 }, - "a": 1.325721909088244, - "b": -5.900351875443958, - "c": -5.900351875443956, - "d": -13.126425659976158, - "e": -14.803891747205228, - "f": -18.039417932919253, - "g": -18.03941793291925, - "h": -21.274944118633275, - "m": -14.452147569064406, - "n": 7.226073784532204, - "o": -7.226073784532202, - "p": -6.471052371428058, - "q": 3.235526185714029, - "r": -3.2355261857140256 + "control2": { + "x": 4.679071033911222, + "y": -14.284680684831322 + }, + "end": { + "x": 4.6399485506641724, + "y": -14.097662122919335 + }, + "a": 5.018352881850565, + "b": 4.842334806008958, + "c": 4.679071033911222, + "d": 4.6399485506641724, + "e": -14.044406072375264, + "f": -14.118730685876189, + "g": -14.284680684831322, + "h": -14.097662122919335, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -13.126425659976158, - "y": -21.274944118633275 + "x": 4.6399485506641724, + "y": -14.097662122919335 }, "control1": { - "x": -13.180830556391726, - "y": -21.299304299864808 + "x": 4.237415885961329, + "y": -12.173421281663334 }, "control2": { - "x": -13.191228219798703, - "y": -21.34632141020708 + "x": 4.237415885961329, + "y": -12.173421281663334 }, "end": { - "x": -13.152168603914703, - "y": -21.39135092930973 + "x": 3.834883221258485, + "y": -10.249180440407335 + }, + "a": 4.6399485506641724, + "b": 4.237415885961329, + "c": 4.237415885961329, + "d": 3.834883221258485, + "e": -14.097662122919335, + "f": -12.173421281663334, + "g": -12.173421281663334, + "h": -10.249180440407335, + "m": -0.8050653294056875, + "n": 0.40253266470284377, + "o": -0.40253266470284377, + "p": 3.8484816825119985, + "q": -1.924240841256001, + "r": 1.924240841256001 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.834883221258485, + "y": -10.249180440407335 + }, + "control1": { + "x": 3.8216667172353302, + "y": -10.186001128391121 + }, + "control2": { + "x": 3.781401666870376, + "y": -10.164243063715087 }, - "a": -13.126425659976158, - "b": -13.180830556391726, - "c": -13.191228219798703, - "d": -13.152168603914703, - "e": -21.274944118633275, - "f": -21.299304299864808, - "g": -21.34632141020708, - "h": -21.39135092930973, - "m": 0.005450046282385657, - "n": 0.044007233008590774, - "o": -0.05440489641556745, - "p": 0.02464452035035336, - "q": -0.022656929110738133, - "r": -0.024360181231532607 + "end": { + "x": 3.7213105214093956, + "y": -10.187809049428045 + }, + "a": 3.834883221258485, + "b": 3.8216667172353302, + "c": 3.781401666870376, + "d": 3.7213105214093956, + "e": -10.249180440407335, + "f": -10.186001128391121, + "g": -10.164243063715087, + "h": -10.187809049428045, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488115206, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -13.152168603914703, - "y": -21.39135092930973 + "x": 3.7213105214093956, + "y": -10.187809049428045 }, "control1": { - "x": -12.939960186778574, - "y": -21.635993452204826 + "x": 3.528228197783841, + "y": -10.263530276658422 }, "control2": { - "x": -12.955680950041305, - "y": -21.944887695975297 + "x": 3.3397464978541502, + "y": -10.223699259910124 }, "end": { - "x": -12.660102596503732, - "y": -21.812540375519177 + "x": 3.3822132088382872, + "y": -10.426704349521462 + }, + "a": 3.7213105214093956, + "b": 3.528228197783841, + "c": 3.3397464978541502, + "d": 3.3822132088382872, + "e": -10.187809049428045, + "f": -10.263530276658422, + "g": -10.223699259910124, + "h": -10.426704349521462, + "m": 0.22634778721796334, + "n": 0.0046006236958642255, + "o": -0.19308232362555477, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.3822132088382872, + "y": -10.426704349521462 }, - "a": -13.152168603914703, - "b": -12.939960186778574, - "c": -12.955680950041305, - "d": -12.660102596503732, - "e": -21.39135092930973, - "f": -21.635993452204826, - "g": -21.944887695975297, - "h": -21.812540375519177, - "m": 0.5392282971991662, - "n": -0.2279291803988599, - "o": 0.2122084171361287, - "p": 0.50549328510197, - "q": -0.0642517208753759, - "r": -0.24464252289509503 + "control1": { + "x": 3.7860546952887217, + "y": -12.357201796716504 + }, + "control2": { + "x": 3.7860546952887217, + "y": -12.357201796716504 + }, + "end": { + "x": 4.189896181739156, + "y": -14.287699243911547 + }, + "a": 3.3822132088382872, + "b": 3.7860546952887217, + "c": 3.7860546952887217, + "d": 4.189896181739156, + "e": -10.426704349521462, + "f": -12.357201796716504, + "g": -12.357201796716504, + "h": -14.287699243911547, + "m": 0.807682972900869, + "n": -0.4038414864504345, + "o": 0.4038414864504345, + "p": -3.8609948943900854, + "q": 1.9304974471950427, + "r": -1.9304974471950427 }, "backward": false }, { "spline": { "start": { - "x": -12.660102596503732, - "y": -21.812540375519177 + "x": 4.189896181739156, + "y": -14.287699243911547 }, "control1": { - "x": -5.481504926134571, - "y": -18.598271959259332 + "x": 4.205704554608125, + "y": -14.363268556356608 }, "control2": { - "x": -5.481504926134572, - "y": -18.598271959259332 + "x": 4.189372447563046, + "y": -14.39444768079333 }, "end": { - "x": 1.6970927442345891, - "y": -15.384003542999485 + "x": 4.118248144000533, + "y": -14.424480314359688 + }, + "a": 4.189896181739156, + "b": 4.205704554608125, + "c": 4.189372447563046, + "d": 4.118248144000533, + "e": -14.287699243911547, + "f": -14.363268556356608, + "g": -14.39444768079333, + "h": -14.424480314359688, + "m": -0.022651716603389715, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 4.118248144000533, + "y": -14.424480314359688 + }, + "control1": { + "x": 3.942230068158926, + "y": -14.498804927860613 + }, + "control2": { + "x": 3.7789662960611894, + "y": -14.664754926815744 }, - "a": -12.660102596503732, - "b": -5.481504926134571, - "c": -5.481504926134572, - "d": 1.6970927442345891, - "e": -21.812540375519177, - "f": -18.598271959259332, - "g": -18.598271959259332, - "h": -15.384003542999485, - "m": 14.35719534073832, - "n": -7.178597670369162, - "o": 7.178597670369161, - "p": 6.42853683251969, - "q": -3.214268416259845, - "r": 3.214268416259845 + "end": { + "x": 3.73984381281414, + "y": -14.477736364903757 + }, + "a": 4.118248144000533, + "b": 3.942230068158926, + "c": 3.7789662960611894, + "d": 3.73984381281414, + "e": -14.424480314359688, + "f": -14.498804927860613, + "g": -14.664754926815744, + "h": -14.477736364903757, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213229, + "q": -0.09162538545420595, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.73984381281414, + "y": -14.477736364903757 + }, + "control1": { + "x": 3.3346935046161144, + "y": -12.540982311769673 + }, + "control2": { + "x": 3.3346935046161144, + "y": -12.540982311769673 + }, + "end": { + "x": 2.9295431964180887, + "y": -10.604228258635588 + }, + "a": 3.73984381281414, + "b": 3.3346935046161144, + "c": 3.3346935046161144, + "d": 2.9295431964180887, + "e": -14.477736364903757, + "f": -12.540982311769673, + "g": -12.540982311769673, + "h": -10.604228258635588, + "m": -0.8103006163960513, + "n": 0.40515030819802567, + "o": -0.40515030819802567, + "p": 3.8735081062681704, + "q": -1.9367540531340843, + "r": 1.9367540531340843 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.9295431964180887, + "y": -10.604228258635588 + }, + "control1": { + "x": 2.916326692394934, + "y": -10.541048946619375 + }, + "control2": { + "x": 2.87606164202998, + "y": -10.519290881943341 + }, + "end": { + "x": 2.8159704965689993, + "y": -10.5428568676563 + }, + "a": 2.9295431964180887, + "b": 2.916326692394934, + "c": 2.87606164202998, + "d": 2.8159704965689993, + "e": -10.604228258635588, + "f": -10.541048946619375, + "g": -10.519290881943341, + "h": -10.5428568676563, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.003902803048807968, + "q": -0.041421247340180045, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.8159704965689993, + "y": -10.5428568676563 + }, + "control1": { + "x": 2.6228881729434446, + "y": -10.618578094886676 + }, + "control2": { + "x": 2.4344064730137536, + "y": -10.578747078138377 + }, + "end": { + "x": 2.476873183997891, + "y": -10.781752167749715 + }, + "a": 2.8159704965689993, + "b": 2.6228881729434446, + "c": 2.4344064730137536, + "d": 2.476873183997891, + "e": -10.5428568676563, + "f": -10.618578094886676, + "g": -10.578747078138377, + "h": -10.781752167749715, + "m": 0.2263477872179651, + "n": 0.004600623695863781, + "o": -0.19308232362555477, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.476873183997891, + "y": -10.781752167749715 + }, + "control1": { + "x": 2.8833323139435074, + "y": -12.724762826822843 + }, + "control2": { + "x": 2.8833323139435074, + "y": -12.724762826822841 + }, + "end": { + "x": 3.289791443889124, + "y": -14.66777348589597 + }, + "a": 2.476873183997891, + "b": 2.8833323139435074, + "c": 2.8833323139435074, + "d": 3.289791443889124, + "e": -10.781752167749715, + "f": -12.724762826822843, + "g": -12.724762826822841, + "h": -14.66777348589597, + "m": 0.8129182598912328, + "n": -0.4064591299456164, + "o": 0.4064591299456164, + "p": -3.8860213181462626, + "q": 1.9430106590731295, + "r": -1.9430106590731278 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.289791443889124, + "y": -14.66777348589597 + }, + "control1": { + "x": 3.3055998167580922, + "y": -14.743342798341029 + }, + "control2": { + "x": 3.289267709713013, + "y": -14.774521922777751 + }, + "end": { + "x": 3.2181434061505003, + "y": -14.804554556344108 + }, + "a": 3.289791443889124, + "b": 3.3055998167580922, + "c": 3.289267709713013, + "d": 3.2181434061505003, + "e": -14.66777348589597, + "f": -14.743342798341029, + "g": -14.774521922777751, + "h": -14.804554556344108, + "m": -0.02265171660338705, + "n": -0.03214047991404767, + "o": 0.015808372868968412, + "p": -0.04324369713796905, + "q": 0.044390188008337716, + "r": -0.07556931244505982 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.2181434061505003, + "y": -14.804554556344108 + }, + "control1": { + "x": 3.042125330308893, + "y": -14.878879169845034 + }, + "control2": { + "x": 2.878861558211156, + "y": -15.044829168800167 + }, + "end": { + "x": 2.8397390749641067, + "y": -14.85781060688818 + }, + "a": 3.2181434061505003, + "b": 3.042125330308893, + "c": 2.878861558211156, + "d": 2.8397390749641067, + "e": -14.804554556344108, + "f": -14.878879169845034, + "g": -15.044829168800167, + "h": -14.85781060688818, + "m": 0.111386985106817, + "n": 0.01275430374387021, + "o": -0.17601807584160722, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.8397390749641067, + "y": -14.85781060688818 + }, + "control1": { + "x": 2.4319711232709, + "y": -12.908543341876012 + }, + "control2": { + "x": 2.431971123270899, + "y": -12.90854334187601 + }, + "end": { + "x": 2.0242031715776925, + "y": -10.959276076863842 + }, + "a": 2.8397390749641067, + "b": 2.4319711232709, + "c": 2.431971123270899, + "d": 2.0242031715776925, + "e": -14.85781060688818, + "f": -12.908543341876012, + "g": -12.90854334187601, + "h": -10.959276076863842, + "m": -0.8155359033864116, + "n": 0.4077679516932058, + "o": -0.4077679516932067, + "p": 3.898534530024328, + "q": -1.9492672650121659, + "r": 1.9492672650121676 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.0242031715776925, + "y": -10.959276076863842 + }, + "control1": { + "x": 2.010986667554538, + "y": -10.89609676484763 + }, + "control2": { + "x": 1.9707216171895836, + "y": -10.874338700171595 + }, + "end": { + "x": 1.9106304717286033, + "y": -10.897904685884553 + }, + "a": 2.0242031715776925, + "b": 2.010986667554538, + "c": 1.9707216171895836, + "d": 1.9106304717286033, + "e": -10.959276076863842, + "f": -10.89609676484763, + "g": -10.874338700171595, + "h": -10.897904685884553, + "m": 0.007222451245773698, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.003902803048818626, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.9106304717286033, + "y": -10.897904685884553 + }, + "control1": { + "x": 1.7175481481030481, + "y": -10.97362591311493 + }, + "control2": { + "x": 1.5290664481733567, + "y": -10.93379489636663 + }, + "end": { + "x": 1.571533159157494, + "y": -11.13679998597797 + }, + "a": 1.9106304717286033, + "b": 1.7175481481030481, + "c": 1.5290664481733567, + "d": 1.571533159157494, + "e": -10.897904685884553, + "f": -10.97362591311493, + "g": -10.93379489636663, + "h": -11.13679998597797, + "m": 0.2263477872179649, + "n": 0.004600623695863781, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.571533159157494, + "y": -11.13679998597797 + }, + "control1": { + "x": 1.9806099325982918, + "y": -13.09232385692918 + }, + "control2": { + "x": 1.9806099325982918, + "y": -13.09232385692918 + }, + "end": { + "x": 2.3896867060390896, + "y": -15.047847727880393 + }, + "a": 1.571533159157494, + "b": 1.9806099325982918, + "c": 1.9806099325982918, + "d": 2.3896867060390896, + "e": -11.13679998597797, + "f": -13.09232385692918, + "g": -13.09232385692918, + "h": -15.047847727880393, + "m": 0.8181535468815957, + "n": -0.40907677344079785, + "o": 0.40907677344079785, + "p": -3.9110477419024257, + "q": 1.955523870951211, + "r": -1.955523870951211 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.3896867060390896, + "y": -15.047847727880393 + }, + "control1": { + "x": 2.405495078908058, + "y": -15.123417040325455 + }, + "control2": { + "x": 2.3891629718629797, + "y": -15.154596164762177 + }, + "end": { + "x": 2.3180386683004666, + "y": -15.184628798328534 + }, + "a": 2.3896867060390896, + "b": 2.405495078908058, + "c": 2.3891629718629797, + "d": 2.3180386683004666, + "e": -15.047847727880393, + "f": -15.123417040325455, + "g": -15.154596164762177, + "h": -15.184628798328534, + "m": -0.02265171660338705, + "n": -0.03214047991404678, + "o": 0.015808372868968412, + "p": -0.04324369713796905, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.3180386683004666, + "y": -15.184628798328534 + }, + "control1": { + "x": 2.1420205924588593, + "y": -15.25895341182946 + }, + "control2": { + "x": 1.9787568203611219, + "y": -15.424903410784593 + }, + "end": { + "x": 1.9396343371140725, + "y": -15.237884848872605 + }, + "a": 2.3180386683004666, + "b": 2.1420205924588593, + "c": 1.9787568203611219, + "d": 1.9396343371140725, + "e": -15.184628798328534, + "f": -15.25895341182946, + "g": -15.424903410784593, + "h": -15.237884848872605, + "m": 0.11138698510681833, + "n": 0.012754303743869766, + "o": -0.17601807584160722, + "p": 0.4445939463213282, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.9396343371140725, + "y": -15.237884848872605 + }, + "control1": { + "x": 1.529248741925684, + "y": -13.27610437198235 + }, + "control2": { + "x": 1.529248741925684, + "y": -13.27610437198235 + }, + "end": { + "x": 1.1188631467372954, + "y": -11.314323895092096 + }, + "a": 1.9396343371140725, + "b": 1.529248741925684, + "c": 1.529248741925684, + "d": 1.1188631467372954, + "e": -15.237884848872605, + "f": -13.27610437198235, + "g": -13.27610437198235, + "h": -11.314323895092096, + "m": -0.8207711903767771, + "n": 0.4103855951883886, + "o": -0.4103855951883886, + "p": 3.9235609537805107, + "q": -1.9617804768902545, + "r": 1.9617804768902545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.1188631467372954, + "y": -11.314323895092096 + }, + "control1": { + "x": 1.1056466427141407, + "y": -11.251144583075884 + }, + "control2": { + "x": 1.0653815923491867, + "y": -11.22938651839985 + }, + "end": { + "x": 1.0052904468882065, + "y": -11.252952504112807 + }, + "a": 1.1188631467372954, + "b": 1.1056466427141407, + "c": 1.0653815923491867, + "d": 1.0052904468882065, + "e": -11.314323895092096, + "f": -11.251144583075884, + "g": -11.22938651839985, + "h": -11.252952504112807, + "m": 0.007222451245773254, + "n": -0.027048546341799318, + "o": -0.013216504023154663, + "p": -0.0039028030488150733, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.0052904468882065, + "y": -11.252952504112807 + }, + "control1": { + "x": 0.8122081232626508, + "y": -11.328673731343184 + }, + "control2": { + "x": 0.6237264233329585, + "y": -11.288842714594884 + }, + "end": { + "x": 0.6661931343170959, + "y": -11.491847804206223 + }, + "a": 1.0052904468882065, + "b": 0.8122081232626508, + "c": 0.6237264233329585, + "d": 0.6661931343170959, + "e": -11.252952504112807, + "f": -11.328673731343184, + "g": -11.288842714594884, + "h": -11.491847804206223, + "m": 0.22634778721796645, + "n": 0.004600623695863337, + "o": -0.19308232362555566, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.6661931343170959, + "y": -11.491847804206223 + }, + "control1": { + "x": 1.0778875512530761, + "y": -13.45988488703552 + }, + "control2": { + "x": 1.0778875512530761, + "y": -13.45988488703552 + }, + "end": { + "x": 1.4895819681890563, + "y": -15.427921969864816 + }, + "a": 0.6661931343170959, + "b": 1.0778875512530761, + "c": 1.0778875512530761, + "d": 1.4895819681890563, + "e": -11.491847804206223, + "f": -13.45988488703552, + "g": -13.45988488703552, + "h": -15.427921969864816, + "m": 0.8233888338719604, + "n": -0.4116944169359802, + "o": 0.4116944169359802, + "p": -3.9360741656585922, + "q": 1.9680370828292961, + "r": -1.9680370828292961 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.4895819681890563, + "y": -15.427921969864816 + }, + "control1": { + "x": 1.5053903410580247, + "y": -15.503491282309877 + }, + "control2": { + "x": 1.4890582340129461, + "y": -15.5346704067466 + }, + "end": { + "x": 1.417933930450433, + "y": -15.564703040312956 + }, + "a": 1.4895819681890563, + "b": 1.5053903410580247, + "c": 1.4890582340129461, + "d": 1.417933930450433, + "e": -15.427921969864816, + "f": -15.503491282309877, + "g": -15.5346704067466, + "h": -15.564703040312956, + "m": -0.02265171660338794, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.417933930450433, + "y": -15.564703040312956 + }, + "control1": { + "x": 1.241915854608826, + "y": -15.639027653813882 + }, + "control2": { + "x": 1.0786520825110888, + "y": -15.804977652769015 + }, + "end": { + "x": 1.0395295992640392, + "y": -15.617959090857028 + }, + "a": 1.417933930450433, + "b": 1.241915854608826, + "c": 1.0786520825110888, + "d": 1.0395295992640392, + "e": -15.564703040312956, + "f": -15.639027653813882, + "g": -15.804977652769015, + "h": -15.617959090857028, + "m": 0.11138698510681766, + "n": 0.012754303743869766, + "o": -0.176018075841607, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.0395295992640392, + "y": -15.617959090857028 + }, + "control1": { + "x": 0.6265263605804682, + "y": -13.643665402088688 + }, + "control2": { + "x": 0.6265263605804684, + "y": -13.643665402088688 + }, + "end": { + "x": 0.21352312189689737, + "y": -11.669371713320349 + }, + "a": 1.0395295992640392, + "b": 0.6265263605804682, + "c": 0.6265263605804684, + "d": 0.21352312189689737, + "e": -15.617959090857028, + "f": -13.643665402088688, + "g": -13.643665402088688, + "h": -11.669371713320349, + "m": -0.8260064773671427, + "n": 0.41300323868357125, + "o": -0.41300323868357103, + "p": 3.948587377536679, + "q": -1.9742936887683395, + "r": 1.9742936887683395 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.21352312189689737, + "y": -11.669371713320349 + }, + "control1": { + "x": 0.20030661787374268, + "y": -11.606192401304137 + }, + "control2": { + "x": 0.1600415675087885, + "y": -11.584434336628101 + }, + "end": { + "x": 0.09995042204780843, + "y": -11.60800032234106 + }, + "a": 0.21352312189689737, + "b": 0.20030661787374268, + "c": 0.1600415675087885, + "d": 0.09995042204780843, + "e": -11.669371713320349, + "f": -11.606192401304137, + "g": -11.584434336628101, + "h": -11.60800032234106, + "m": 0.007222451245773587, + "n": -0.027048546341799484, + "o": -0.01321650402315469, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.09995042204780843, + "y": -11.60800032234106 + }, + "control1": { + "x": -0.093131901577747, + "y": -11.683721549571437 + }, + "control2": { + "x": -0.28161360150743864, + "y": -11.643890532823137 + }, + "end": { + "x": -0.23914689052330118, + "y": -11.846895622434477 + }, + "a": 0.09995042204780843, + "b": -0.093131901577747, + "c": -0.28161360150743864, + "d": -0.23914689052330118, + "e": -11.60800032234106, + "f": -11.683721549571437, + "g": -11.643890532823137, + "h": -11.846895622434477, + "m": 0.22634778721796525, + "n": 0.004600623695863809, + "o": -0.19308232362555544, + "p": -0.3583883503383163, + "q": 0.11555224397867825, + "r": -0.07572122723037822 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.23914689052330118, + "y": -11.846895622434477 + }, + "control1": { + "x": 0.1751651699078604, + "y": -13.827445917141858 + }, + "control2": { + "x": 0.17516516990786052, + "y": -13.827445917141858 + }, + "end": { + "x": 0.5894772303390221, + "y": -15.80799621184924 + }, + "a": -0.23914689052330118, + "b": 0.1751651699078604, + "c": 0.17516516990786052, + "d": 0.5894772303390221, + "e": -11.846895622434477, + "f": -13.827445917141858, + "g": -13.827445917141858, + "h": -15.80799621184924, + "m": 0.8286241208623228, + "n": -0.4143120604311615, + "o": 0.4143120604311616, + "p": -3.9611005894147624, + "q": 1.9805502947073812, + "r": -1.9805502947073812 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.5894772303390221, + "y": -15.80799621184924 + }, + "control1": { + "x": 0.6052856032079905, + "y": -15.883565524294301 + }, + "control2": { + "x": 0.5889534961629119, + "y": -15.914744648731023 + }, + "end": { + "x": 0.5178291926003988, + "y": -15.94477728229738 + }, + "a": 0.5894772303390221, + "b": 0.6052856032079905, + "c": 0.5889534961629119, + "d": 0.5178291926003988, + "e": -15.80799621184924, + "f": -15.883565524294301, + "g": -15.914744648731023, + "h": -15.94477728229738, + "m": -0.022651716603387495, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.5178291926003988, + "y": -15.94477728229738 + }, + "control1": { + "x": 0.34181111675879183, + "y": -16.019101895798308 + }, + "control2": { + "x": 0.17854734466105446, + "y": -16.18505189475344 + }, + "end": { + "x": 0.139424861414005, + "y": -15.998033332841452 + }, + "a": 0.5178291926003988, + "b": 0.34181111675879183, + "c": 0.17854734466105446, + "d": 0.139424861414005, + "e": -15.94477728229738, + "f": -16.019101895798308, + "g": -16.18505189475344, + "h": -15.998033332841452, + "m": 0.11138698510681833, + "n": 0.012754303743869655, + "o": -0.176018075841607, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092723 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.139424861414005, + "y": -15.998033332841452 + }, + "control1": { + "x": -0.2761960207647475, + "y": -14.011226432195027 + }, + "control2": { + "x": -0.27619602076474725, + "y": -14.011226432195027 + }, + "end": { + "x": -0.6918169029434997, + "y": -12.024419531548602 + }, + "a": 0.139424861414005, + "b": -0.2761960207647475, + "c": -0.27619602076474725, + "d": -0.6918169029434997, + "e": -15.998033332841452, + "f": -14.011226432195027, + "g": -14.011226432195027, + "h": -12.024419531548602, + "m": -0.8312417643575054, + "n": 0.4156208821787527, + "o": -0.4156208821787525, + "p": 3.9736138012928492, + "q": -1.9868069006464246, + "r": 1.9868069006464246 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.6918169029434997, + "y": -12.024419531548602 + }, + "control1": { + "x": -0.7050334069666544, + "y": -11.96124021953239 + }, + "control2": { + "x": -0.7452984573316086, + "y": -11.939482154856355 + }, + "end": { + "x": -0.8053896027925886, + "y": -11.963048140569313 + }, + "a": -0.6918169029434997, + "b": -0.7050334069666544, + "c": -0.7452984573316086, + "d": -0.8053896027925886, + "e": -12.024419531548602, + "f": -11.96124021953239, + "g": -11.939482154856355, + "h": -11.963048140569313, + "m": 0.007222451245773698, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.8053896027925886, + "y": -11.963048140569313 + }, + "control1": { + "x": -0.9984719264181443, + "y": -12.038769367799691 + }, + "control2": { + "x": -1.1869536263478362, + "y": -11.998938351051391 + }, + "end": { + "x": -1.1444869153636987, + "y": -12.201943440662731 + }, + "a": -0.8053896027925886, + "b": -0.9984719264181443, + "c": -1.1869536263478362, + "d": -1.1444869153636987, + "e": -11.963048140569313, + "f": -12.038769367799691, + "g": -11.998938351051391, + "h": -12.201943440662731, + "m": 0.22634778721796556, + "n": 0.004600623695863781, + "o": -0.19308232362555566, + "p": -0.3583883503383234, + "q": 0.11555224397867825, + "r": -0.07572122723037822 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.1444869153636987, + "y": -12.201943440662731 + }, + "control1": { + "x": -0.7275572114373554, + "y": -14.195006947248197 + }, + "control2": { + "x": -0.7275572114373554, + "y": -14.195006947248197 + }, + "end": { + "x": -0.3106275075110121, + "y": -16.188070453833664 + }, + "a": -1.1444869153636987, + "b": -0.7275572114373554, + "c": -0.7275572114373554, + "d": -0.3106275075110121, + "e": -12.201943440662731, + "f": -14.195006947248197, + "g": -14.195006947248197, + "h": -16.188070453833664, + "m": 0.8338594078526866, + "n": -0.4169297039263433, + "o": 0.4169297039263433, + "p": -3.9861270131709325, + "q": 1.9930635065854663, + "r": -1.9930635065854663 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.3106275075110121, + "y": -16.188070453833664 + }, + "control1": { + "x": -0.29481913464204373, + "y": -16.263639766278725 + }, + "control2": { + "x": -0.3111512416871223, + "y": -16.294818890715447 + }, + "end": { + "x": -0.38227554524963536, + "y": -16.324851524281804 + }, + "a": -0.3106275075110121, + "b": -0.29481913464204373, + "c": -0.3111512416871223, + "d": -0.38227554524963536, + "e": -16.188070453833664, + "f": -16.263639766278725, + "g": -16.294818890715447, + "h": -16.324851524281804, + "m": -0.022651716603387495, + "n": -0.03214047991404695, + "o": 0.015808372868968357, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.38227554524963536, + "y": -16.324851524281804 + }, + "control1": { + "x": -0.5582936210912419, + "y": -16.399176137782728 + }, + "control2": { + "x": -0.7215573931889789, + "y": -16.565126136737863 + }, + "end": { + "x": -0.7606798764360283, + "y": -16.378107574825876 + }, + "a": -0.38227554524963536, + "b": -0.5582936210912419, + "c": -0.7215573931889789, + "d": -0.7606798764360283, + "e": -16.324851524281804, + "f": -16.399176137782728, + "g": -16.565126136737863, + "h": -16.378107574825876, + "m": 0.1113869851068181, + "n": 0.012754303743869544, + "o": -0.17601807584160656, + "p": 0.44459394632134064, + "q": -0.09162538545421128, + "r": -0.07432461350092368 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.7606798764360283, + "y": -16.378107574825876 + }, + "control1": { + "x": -1.1789184021099626, + "y": -14.378787462301366 + }, + "control2": { + "x": -1.1789184021099626, + "y": -14.378787462301366 + }, + "end": { + "x": -1.5971569277838968, + "y": -12.379467349776856 + }, + "a": -0.7606798764360283, + "b": -1.1789184021099626, + "c": -1.1789184021099626, + "d": -1.5971569277838968, + "e": -16.378107574825876, + "f": -14.378787462301366, + "g": -14.378787462301366, + "h": -12.379467349776856, + "m": -0.8364770513478685, + "n": 0.41823852567393427, + "o": -0.41823852567393427, + "p": 3.9986402250490194, + "q": -1.9993201125245097, + "r": 1.9993201125245097 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.5971569277838968, + "y": -12.379467349776856 + }, + "control1": { + "x": -1.6103734318070515, + "y": -12.316288037760645 + }, + "control2": { + "x": -1.6506384821720057, + "y": -12.29452997308461 + }, + "end": { + "x": -1.710729627632986, + "y": -12.318095958797567 + }, + "a": -1.5971569277838968, + "b": -1.6103734318070515, + "c": -1.6506384821720057, + "d": -1.710729627632986, + "e": -12.379467349776856, + "f": -12.316288037760645, + "g": -12.29452997308461, + "h": -12.318095958797567, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.710729627632986, + "y": -12.318095958797567 + }, + "control1": { + "x": -1.903811951258541, + "y": -12.393817186027944 + }, + "control2": { + "x": -2.0922936511882324, + "y": -12.353986169279645 + }, + "end": { + "x": -2.049826940204095, + "y": -12.556991258890983 + }, + "a": -1.710729627632986, + "b": -1.903811951258541, + "c": -2.0922936511882324, + "d": -2.049826940204095, + "e": -12.318095958797567, + "f": -12.393817186027944, + "g": -12.353986169279645, + "h": -12.556991258890983, + "m": 0.22634778721796578, + "n": 0.004600623695863559, + "o": -0.193082323625555, + "p": -0.35838835033830563, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.049826940204095, + "y": -12.556991258890983 + }, + "control1": { + "x": -1.63027959278257, + "y": -14.562567977354536 + }, + "control2": { + "x": -1.63027959278257, + "y": -14.562567977354536 + }, + "end": { + "x": -1.210732245361045, + "y": -16.568144695818088 + }, + "a": -2.049826940204095, + "b": -1.63027959278257, + "c": -1.63027959278257, + "d": -1.210732245361045, + "e": -12.556991258890983, + "f": -14.562567977354536, + "g": -14.562567977354536, + "h": -16.568144695818088, + "m": 0.83909469484305, + "n": -0.419547347421525, + "o": 0.419547347421525, + "p": -4.0111534369271045, + "q": 2.005576718463553, + "r": -2.005576718463553 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.210732245361045, + "y": -16.568144695818088 + }, + "control1": { + "x": -1.1949238724920765, + "y": -16.64371400826315 + }, + "control2": { + "x": -1.2112559795371551, + "y": -16.67489313269987 + }, + "end": { + "x": -1.2823802830996682, + "y": -16.70492576626623 + }, + "a": -1.210732245361045, + "b": -1.1949238724920765, + "c": -1.2112559795371551, + "d": -1.2823802830996682, + "e": -16.568144695818088, + "f": -16.64371400826315, + "g": -16.67489313269987, + "h": -16.70492576626623, + "m": -0.02265171660338705, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.2823802830996682, + "y": -16.70492576626623 + }, + "control1": { + "x": -1.458398358941275, + "y": -16.779250379767156 + }, + "control2": { + "x": -1.6216621310390122, + "y": -16.945200378722287 + }, + "end": { + "x": -1.6607846142860616, + "y": -16.7581818168103 + }, + "a": -1.2823802830996682, + "b": -1.458398358941275, + "c": -1.6216621310390122, + "d": -1.6607846142860616, + "e": -16.70492576626623, + "f": -16.779250379767156, + "g": -16.945200378722287, + "h": -16.7581818168103, + "m": 0.1113869851068181, + "n": 0.012754303743869544, + "o": -0.17601807584160678, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092723 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.6607846142860616, + "y": -16.7581818168103 + }, + "control1": { + "x": -2.0816407834551773, + "y": -14.746348492407705 + }, + "control2": { + "x": -2.0816407834551773, + "y": -14.746348492407705 + }, + "end": { + "x": -2.502496952624293, + "y": -12.73451516800511 + }, + "a": -1.6607846142860616, + "b": -2.0816407834551773, + "c": -2.0816407834551773, + "d": -2.502496952624293, + "e": -16.7581818168103, + "f": -14.746348492407705, + "g": -14.746348492407705, + "h": -12.73451516800511, + "m": -0.8417123383382314, + "n": 0.4208561691691157, + "o": -0.4208561691691157, + "p": 4.0236666488051895, + "q": -2.0118333244025948, + "r": 2.0118333244025948 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.502496952624293, + "y": -12.73451516800511 + }, + "control1": { + "x": -2.5157134566474477, + "y": -12.671335855988897 + }, + "control2": { + "x": -2.5559785070124015, + "y": -12.649577791312861 + }, + "end": { + "x": -2.6160696524733824, + "y": -12.67314377702582 + }, + "a": -2.502496952624293, + "b": -2.5157134566474477, + "c": -2.5559785070124015, + "d": -2.6160696524733824, + "e": -12.73451516800511, + "f": -12.671335855988897, + "g": -12.649577791312861, + "h": -12.67314377702582, + "m": 0.0072224512457719214, + "n": -0.027048546341799096, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017827, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.6160696524733824, + "y": -12.67314377702582 + }, + "control1": { + "x": -2.8091519760989376, + "y": -12.748865004256196 + }, + "control2": { + "x": -2.997633676028629, + "y": -12.709033987507897 + }, + "end": { + "x": -2.9551669650444916, + "y": -12.912039077119235 + }, + "a": -2.6160696524733824, + "b": -2.8091519760989376, + "c": -2.997633676028629, + "d": -2.9551669650444916, + "e": -12.67314377702582, + "f": -12.748865004256196, + "g": -12.709033987507897, + "h": -12.912039077119235, + "m": 0.226347787217966, + "n": 0.004600623695863781, + "o": -0.19308232362555522, + "p": -0.3583883503383074, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.9551669650444916, + "y": -12.912039077119235 + }, + "control1": { + "x": -2.5330019741277847, + "y": -14.930129007460874 + }, + "control2": { + "x": -2.5330019741277847, + "y": -14.930129007460874 + }, + "end": { + "x": -2.110836983211078, + "y": -16.948218937802512 + }, + "a": -2.9551669650444916, + "b": -2.5330019741277847, + "c": -2.5330019741277847, + "d": -2.110836983211078, + "e": -12.912039077119235, + "f": -14.930129007460874, + "g": -14.930129007460874, + "h": -16.948218937802512, + "m": 0.8443299818334138, + "n": -0.4221649909167069, + "o": 0.4221649909167069, + "p": -4.036179860683276, + "q": 2.018089930341638, + "r": -2.018089930341638 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.110836983211078, + "y": -16.948218937802512 + }, + "control1": { + "x": -2.0950286103421094, + "y": -17.023788250247573 + }, + "control2": { + "x": -2.111360717387188, + "y": -17.054967374684296 + }, + "end": { + "x": -2.182485020949701, + "y": -17.085000008250653 + }, + "a": -2.110836983211078, + "b": -2.0950286103421094, + "c": -2.111360717387188, + "d": -2.182485020949701, + "e": -16.948218937802512, + "f": -17.023788250247573, + "g": -17.054967374684296, + "h": -17.085000008250653, + "m": -0.02265171660338705, + "n": -0.03214047991404678, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.182485020949701, + "y": -17.085000008250653 + }, + "control1": { + "x": -2.358503096791308, + "y": -17.15932462175158 + }, + "control2": { + "x": -2.5217668688890456, + "y": -17.32527462070671 + }, + "end": { + "x": -2.560889352136095, + "y": -17.138256058794724 + }, + "a": -2.182485020949701, + "b": -2.358503096791308, + "c": -2.5217668688890456, + "d": -2.560889352136095, + "e": -17.085000008250653, + "f": -17.15932462175158, + "g": -17.32527462070671, + "h": -17.138256058794724, + "m": 0.11138698510681833, + "n": 0.012754303743869766, + "o": -0.17601807584160722, + "p": 0.4445939463213264, + "q": -0.09162538545420418, + "r": -0.07432461350092723 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.560889352136095, + "y": -17.138256058794724 + }, + "control1": { + "x": -2.9843631648003925, + "y": -15.113909522514044 + }, + "control2": { + "x": -2.9843631648003925, + "y": -15.113909522514044 + }, + "end": { + "x": -3.40783697746469, + "y": -13.089562986233364 + }, + "a": -2.560889352136095, + "b": -2.9843631648003925, + "c": -2.9843631648003925, + "d": -3.40783697746469, + "e": -17.138256058794724, + "f": -15.113909522514044, + "g": -15.113909522514044, + "h": -13.089562986233364, + "m": -0.8469476253285952, + "n": 0.4234738126642976, + "o": -0.4234738126642976, + "p": 4.048693072561363, + "q": -2.02434653628068, + "r": 2.02434653628068 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -3.40783697746469, + "y": -13.089562986233364 + }, + "control1": { + "x": -3.421053481487845, + "y": -13.02638367421715 + }, + "control2": { + "x": -3.4613185318527986, + "y": -13.004625609541115 + }, + "end": { + "x": -3.5214096773137795, + "y": -13.028191595254073 + }, + "a": -3.40783697746469, + "b": -3.421053481487845, + "c": -3.4613185318527986, + "d": -3.5214096773137795, + "e": -13.089562986233364, + "f": -13.02638367421715, + "g": -13.004625609541115, + "h": -13.028191595254073, + "m": 0.00722245124577281, + "n": -0.027048546341799096, + "o": -0.013216504023154663, + "p": -0.003902803048813297, + "q": -0.04142124734017827, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -3.5214096773137795, + "y": -13.028191595254073 + }, + "control1": { + "x": -3.7144920009393347, + "y": -13.10391282248445 + }, + "control2": { + "x": -3.9029737008690257, + "y": -13.064081805736151 + }, + "end": { + "x": -3.8605069898848883, + "y": -13.26708689534749 + }, + "a": -3.5214096773137795, + "b": -3.7144920009393347, + "c": -3.9029737008690257, + "d": -3.8605069898848883, + "e": -13.028191595254073, + "f": -13.10391282248445, + "g": -13.064081805736151, + "h": -13.26708689534749, + "m": 0.22634778721796334, + "n": 0.0046006236958642255, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -3.8605069898848883, + "y": -13.26708689534749 + }, + "control1": { + "x": -3.4357243554729995, + "y": -15.297690037567211 + }, + "control2": { + "x": -3.435724355473, + "y": -15.297690037567211 + }, + "end": { + "x": -3.010941721061111, + "y": -17.328293179786932 + }, + "a": -3.8605069898848883, + "b": -3.4357243554729995, + "c": -3.435724355473, + "d": -3.010941721061111, + "e": -13.26708689534749, + "f": -15.297690037567211, + "g": -15.297690037567211, + "h": -17.328293179786932, + "m": 0.8495652688237789, + "n": -0.42478263441188924, + "o": 0.4247826344118888, + "p": -4.061206284439443, + "q": 2.0306031422197215, + "r": -2.0306031422197215 }, "backward": false } @@ -1971,1190 +4281,3080 @@ { "spline": { "start": { - "x": 1.9904484806479574, - "y": -16.02364468382114 + "x": 10.040576977764365, + "y": -11.817216671012797 + }, + "control1": { + "x": 9.872857166860037, + "y": -11.015459848946962 + }, + "control2": { + "x": 9.872857166860035, + "y": -11.015459848946962 + }, + "end": { + "x": 9.705137355955706, + "y": -10.213703026881127 + }, + "a": 10.040576977764365, + "b": 9.872857166860037, + "c": 9.872857166860035, + "d": 9.705137355955706, + "e": -11.817216671012797, + "f": -11.015459848946962, + "g": -11.015459848946962, + "h": -10.213703026881127, + "m": -0.3354396218086517, + "n": 0.16771981090432675, + "o": -0.16771981090432853, + "p": 1.6035136441316702, + "q": -0.8017568220658351, + "r": 0.8017568220658351 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.705137355955706, + "y": -10.213703026881127 + }, + "control1": { + "x": 9.684892999619743, + "y": -10.116928229001411 + }, + "control2": { + "x": 9.68411244066312, + "y": -10.114833677421275 + }, + "end": { + "x": 9.636086506776302, + "y": -10.028412006861071 + }, + "a": 9.705137355955706, + "b": 9.684892999619743, + "c": 9.68411244066312, + "d": 9.636086506776302, + "e": -10.213703026881127, + "f": -10.116928229001411, + "g": -10.114833677421275, + "h": -10.028412006861071, + "m": -0.06670917230952966, + "n": 0.019463797379339454, + "o": -0.02024435633596333, + "p": 0.17900736527964867, + "q": -0.09468024629958016, + "r": 0.09677479787971599 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.636086506776302, + "y": -10.028412006861071 + }, + "control1": { + "x": 9.290748473559788, + "y": -9.40698338854662 + }, + "control2": { + "x": 8.774075136077252, + "y": -8.104352094647044 + }, + "end": { + "x": 8.919645365222031, + "y": -8.800226503741671 + }, + "a": 9.636086506776302, + "b": 9.290748473559788, + "c": 8.774075136077252, + "d": 8.919645365222031, + "e": -10.028412006861071, + "f": -9.40698338854662, + "g": -8.104352094647044, + "h": -8.800226503741671, + "m": 0.8335788708933336, + "n": -0.17133530426602128, + "o": -0.3453380332165139, + "p": -2.6797083785793223, + "q": 0.6812026755851228, + "r": 0.621428618314452 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.919645365222031, + "y": -8.800226503741671 + }, + "control1": { + "x": 9.25508498703069, + "y": -10.40374014787334 + }, + "control2": { + "x": 9.25508498703069, + "y": -10.40374014787334 + }, + "end": { + "x": 9.590524608839349, + "y": -12.007253792005008 + }, + "a": 8.919645365222031, + "b": 9.25508498703069, + "c": 9.25508498703069, + "d": 9.590524608839349, + "e": -8.800226503741671, + "f": -10.40374014787334, + "g": -10.40374014787334, + "h": -12.007253792005008, + "m": 0.6708792436173177, + "n": -0.3354396218086588, + "o": 0.3354396218086588, + "p": -3.207027288263335, + "q": 1.6035136441316684, + "r": -1.6035136441316684 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.590524608839349, + "y": -12.007253792005008 + }, + "control1": { + "x": 9.606332981708317, + "y": -12.08282310445007 + }, + "control2": { + "x": 9.59000087466324, + "y": -12.114002228886791 + }, + "end": { + "x": 9.518876571100726, + "y": -12.144034862453148 + }, + "a": 9.590524608839349, + "b": 9.606332981708317, + "c": 9.59000087466324, + "d": 9.518876571100726, + "e": -12.007253792005008, + "f": -12.08282310445007, + "g": -12.114002228886791, + "h": -12.144034862453148, + "m": -0.022651716603391492, + "n": -0.03214047991404456, + "o": 0.015808372868967524, + "p": -0.04324369713796905, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.518876571100726, + "y": -12.144034862453148 + }, + "control1": { + "x": 9.342858495259119, + "y": -12.218359475954074 + }, + "control2": { + "x": 9.179594723161383, + "y": -12.384309474909207 + }, + "end": { + "x": 9.140472239914333, + "y": -12.19729091299722 + }, + "a": 9.518876571100726, + "b": 9.342858495259119, + "c": 9.179594723161383, + "d": 9.140472239914333, + "e": -12.144034862453148, + "f": -12.218359475954074, + "g": -12.384309474909207, + "h": -12.19729091299722, + "m": 0.11138698510681522, + "n": 0.012754303743871986, + "o": -0.17601807584160767, + "p": 0.4445939463213282, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 9.140472239914333, + "y": -12.19729091299722 + }, + "control1": { + "x": 8.751027792687397, + "y": -10.335616131131644 + }, + "control2": { + "x": 8.751027792687399, + "y": -10.335616131131644 + }, + "end": { + "x": 8.361583345460463, + "y": -8.47394134926607 + }, + "a": 9.140472239914333, + "b": 8.751027792687397, + "c": 8.751027792687399, + "d": 8.361583345460463, + "e": -12.19729091299722, + "f": -10.335616131131644, + "g": -10.335616131131644, + "h": -8.47394134926607, + "m": -0.778888894453873, + "n": 0.3894444472269374, + "o": -0.3894444472269356, + "p": 3.7233495637311496, + "q": -1.8616747818655757, + "r": 1.8616747818655757 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.361583345460463, + "y": -8.47394134926607 + }, + "control1": { + "x": 8.34836684143731, + "y": -8.410762037249858 + }, + "control2": { + "x": 8.308101791072355, + "y": -8.389003972573823 + }, + "end": { + "x": 8.248010645611375, + "y": -8.412569958286781 + }, + "a": 8.361583345460463, + "b": 8.34836684143731, + "c": 8.308101791072355, + "d": 8.248010645611375, + "e": -8.47394134926607, + "f": -8.410762037249858, + "g": -8.389003972573823, + "h": -8.412569958286781, + "m": 0.007222451245770145, + "n": -0.027048546341800872, + "o": -0.013216504023153774, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.248010645611375, + "y": -8.412569958286781 + }, + "control1": { + "x": 8.05492832198582, + "y": -8.488291185517157 + }, + "control2": { + "x": 7.866446622056128, + "y": -8.448460168768857 + }, + "end": { + "x": 7.908913333040266, + "y": -8.651465258380197 + }, + "a": 8.248010645611375, + "b": 8.05492832198582, + "c": 7.866446622056128, + "d": 7.908913333040266, + "e": -8.412569958286781, + "f": -8.488291185517157, + "g": -8.448460168768857, + "h": -8.651465258380197, + "m": 0.22634778721796422, + "n": 0.004600623695862893, + "o": -0.19308232362555522, + "p": -0.3583883503383163, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.908913333040266, + "y": -8.651465258380197 + }, + "control1": { + "x": 8.29966660201479, + "y": -10.519396646184813 + }, + "control2": { + "x": 8.299666602014792, + "y": -10.519396646184815 + }, + "end": { + "x": 8.690419870989317, + "y": -12.38732803398943 + }, + "a": 7.908913333040266, + "b": 8.29966660201479, + "c": 8.299666602014792, + "d": 8.690419870989317, + "e": -8.651465258380197, + "f": -10.519396646184813, + "g": -10.519396646184815, + "h": -12.38732803398943, + "m": 0.7815065379490473, + "n": -0.3907532689745228, + "o": 0.39075326897452456, + "p": -3.7358627756092293, + "q": 1.8679313878046138, + "r": -1.8679313878046155 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.690419870989317, + "y": -12.38732803398943 + }, + "control1": { + "x": 8.706228243858284, + "y": -12.462897346434492 + }, + "control2": { + "x": 8.689896136813207, + "y": -12.494076470871214 + }, + "end": { + "x": 8.618771833250694, + "y": -12.52410910443757 + }, + "a": 8.690419870989317, + "b": 8.706228243858284, + "c": 8.689896136813207, + "d": 8.618771833250694, + "e": -12.38732803398943, + "f": -12.462897346434492, + "g": -12.494076470871214, + "h": -12.52410910443757, + "m": -0.022651716603391492, + "n": -0.03214047991404456, + "o": 0.015808372868967524, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.618771833250694, + "y": -12.52410910443757 + }, + "control1": { + "x": 8.442753757409086, + "y": -12.598433717938496 + }, + "control2": { + "x": 8.27948998531135, + "y": -12.76438371689363 + }, + "end": { + "x": 8.2403675020643, + "y": -12.577365154981642 + }, + "a": 8.618771833250694, + "b": 8.442753757409086, + "c": 8.27948998531135, + "d": 8.2403675020643, + "e": -12.52410910443757, + "f": -12.598433717938496, + "g": -12.76438371689363, + "h": -12.577365154981642, + "m": 0.11138698510681522, + "n": 0.012754303743871986, + "o": -0.17601807584160767, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 8.2403675020643, + "y": -12.577365154981642 + }, + "control1": { + "x": 7.848305411342184, + "y": -10.703177161237981 + }, + "control2": { + "x": 7.848305411342184, + "y": -10.703177161237981 + }, + "end": { + "x": 7.456243320620068, + "y": -8.828989167494322 + }, + "a": 8.2403675020643, + "b": 7.848305411342184, + "c": 7.848305411342184, + "d": 7.456243320620068, + "e": -12.577365154981642, + "f": -10.703177161237981, + "g": -10.703177161237981, + "h": -8.828989167494322, + "m": -0.7841241814442323, + "n": 0.3920620907221162, + "o": -0.3920620907221162, + "p": 3.7483759874873215, + "q": -1.8741879937436607, + "r": 1.8741879937436607 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.456243320620068, + "y": -8.828989167494322 + }, + "control1": { + "x": 7.443026816596913, + "y": -8.765809855478109 + }, + "control2": { + "x": 7.402761766231959, + "y": -8.744051790802075 + }, + "end": { + "x": 7.342670620770979, + "y": -8.767617776515033 + }, + "a": 7.456243320620068, + "b": 7.443026816596913, + "c": 7.402761766231959, + "d": 7.342670620770979, + "e": -8.828989167494322, + "f": -8.765809855478109, + "g": -8.744051790802075, + "h": -8.767617776515033, + "m": 0.007222451245773698, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.003902803048807968, + "q": -0.041421247340180045, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.342670620770979, + "y": -8.767617776515033 + }, + "control1": { + "x": 7.149588297145424, + "y": -8.84333900374541 + }, + "control2": { + "x": 6.961106597215733, + "y": -8.803507986997111 + }, + "end": { + "x": 7.00357330819987, + "y": -9.00651307660845 + }, + "a": 7.342670620770979, + "b": 7.149588297145424, + "c": 6.961106597215733, + "d": 7.00357330819987, + "e": -8.767617776515033, + "f": -8.84333900374541, + "g": -8.803507986997111, + "h": -9.00651307660845, + "m": 0.2263477872179651, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.00357330819987, + "y": -9.00651307660845 + }, + "control1": { + "x": 7.396944220669577, + "y": -10.886957676291152 + }, + "control2": { + "x": 7.396944220669577, + "y": -10.886957676291152 + }, + "end": { + "x": 7.790315133139284, + "y": -12.767402275973854 + }, + "a": 7.00357330819987, + "b": 7.396944220669577, + "c": 7.396944220669577, + "d": 7.790315133139284, + "e": -9.00651307660845, + "f": -10.886957676291152, + "g": -10.886957676291152, + "h": -12.767402275973854, + "m": 0.7867418249394138, + "n": -0.3933709124697069, + "o": 0.3933709124697069, + "p": -3.760889199365405, + "q": 1.8804445996827024, + "r": -1.8804445996827024 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.790315133139284, + "y": -12.767402275973854 + }, + "control1": { + "x": 7.8061235060082526, + "y": -12.842971588418916 + }, + "control2": { + "x": 7.789791398963174, + "y": -12.874150712855638 + }, + "end": { + "x": 7.718667095400661, + "y": -12.904183346421995 + }, + "a": 7.790315133139284, + "b": 7.8061235060082526, + "c": 7.789791398963174, + "d": 7.718667095400661, + "e": -12.767402275973854, + "f": -12.842971588418916, + "g": -12.874150712855638, + "h": -12.904183346421995, + "m": -0.022651716603384386, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.718667095400661, + "y": -12.904183346421995 + }, + "control1": { + "x": 7.542649019559054, + "y": -12.97850795992292 + }, + "control2": { + "x": 7.379385247461317, + "y": -13.144457958878053 + }, + "end": { + "x": 7.340262764214268, + "y": -12.957439396966066 + }, + "a": 7.718667095400661, + "b": 7.542649019559054, + "c": 7.379385247461317, + "d": 7.340262764214268, + "e": -12.904183346421995, + "f": -12.97850795992292, + "g": -13.144457958878053, + "h": -12.957439396966066, + "m": 0.11138698510681788, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 7.340262764214268, + "y": -12.957439396966066 + }, + "control1": { + "x": 6.94558302999697, + "y": -11.070738191344322 + }, + "control2": { + "x": 6.94558302999697, + "y": -11.070738191344322 + }, + "end": { + "x": 6.550903295779673, + "y": -9.184036985722576 + }, + "a": 7.340262764214268, + "b": 6.94558302999697, + "c": 6.94558302999697, + "d": 6.550903295779673, + "e": -12.957439396966066, + "f": -11.070738191344322, + "g": -11.070738191344322, + "h": -9.184036985722576, + "m": -0.7893594684345953, + "n": 0.39467973421729763, + "o": -0.39467973421729763, + "p": 3.773402411243488, + "q": -1.886701205621744, + "r": 1.886701205621744 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.550903295779673, + "y": -9.184036985722576 + }, + "control1": { + "x": 6.537686791756518, + "y": -9.120857673706363 + }, + "control2": { + "x": 6.497421741391563, + "y": -9.099099609030329 + }, + "end": { + "x": 6.437330595930583, + "y": -9.122665594743287 + }, + "a": 6.550903295779673, + "b": 6.537686791756518, + "c": 6.497421741391563, + "d": 6.437330595930583, + "e": -9.184036985722576, + "f": -9.120857673706363, + "g": -9.099099609030329, + "h": -9.122665594743287, + "m": 0.007222451245773698, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.0039028030488115206, + "q": -0.041421247340180045, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.437330595930583, + "y": -9.122665594743287 + }, + "control1": { + "x": 6.244248272305028, + "y": -9.198386821973664 + }, + "control2": { + "x": 6.055766572375336, + "y": -9.158555805225363 + }, + "end": { + "x": 6.098233283359473, + "y": -9.361560894836703 + }, + "a": 6.437330595930583, + "b": 6.244248272305028, + "c": 6.055766572375336, + "d": 6.098233283359473, + "e": -9.122665594743287, + "f": -9.198386821973664, + "g": -9.158555805225363, + "h": -9.361560894836703, + "m": 0.2263477872179669, + "n": 0.004600623695862893, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.098233283359473, + "y": -9.361560894836703 + }, + "control1": { + "x": 6.4942218393243625, + "y": -11.25451870639749 + }, + "control2": { + "x": 6.4942218393243625, + "y": -11.25451870639749 + }, + "end": { + "x": 6.890210395289252, + "y": -13.147476517958278 + }, + "a": 6.098233283359473, + "b": 6.4942218393243625, + "c": 6.4942218393243625, + "d": 6.890210395289252, + "e": -9.361560894836703, + "f": -11.25451870639749, + "g": -11.25451870639749, + "h": -13.147476517958278, + "m": 0.7919771119297785, + "n": -0.39598855596488924, + "o": 0.39598855596488924, + "p": -3.785915623121575, + "q": 1.8929578115607875, + "r": -1.8929578115607875 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.890210395289252, + "y": -13.147476517958278 + }, + "control1": { + "x": 6.90601876815822, + "y": -13.22304583040334 + }, + "control2": { + "x": 6.889686661113141, + "y": -13.254224954840062 + }, + "end": { + "x": 6.818562357550628, + "y": -13.284257588406419 + }, + "a": 6.890210395289252, + "b": 6.90601876815822, + "c": 6.889686661113141, + "d": 6.818562357550628, + "e": -13.147476517958278, + "f": -13.22304583040334, + "g": -13.254224954840062, + "h": -13.284257588406419, + "m": -0.022651716603384386, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.818562357550628, + "y": -13.284257588406419 + }, + "control1": { + "x": 6.642544281709022, + "y": -13.358582201907344 + }, + "control2": { + "x": 6.479280509611287, + "y": -13.524532200862474 + }, + "end": { + "x": 6.440158026364237, + "y": -13.337513638950488 + }, + "a": 6.818562357550628, + "b": 6.642544281709022, + "c": 6.479280509611287, + "d": 6.440158026364237, + "e": -13.284257588406419, + "f": -13.358582201907344, + "g": -13.524532200862474, + "h": -13.337513638950488, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.1760180758416059, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 6.440158026364237, + "y": -13.337513638950488 + }, + "control1": { + "x": 6.042860648651756, + "y": -11.43829922145066 + }, + "control2": { + "x": 6.042860648651756, + "y": -11.438299221450658 + }, + "end": { + "x": 5.645563270939276, + "y": -9.539084803950828 + }, + "a": 6.440158026364237, + "b": 6.042860648651756, + "c": 6.042860648651756, + "d": 5.645563270939276, + "e": -13.337513638950488, + "f": -11.43829922145066, + "g": -11.438299221450658, + "h": -9.539084803950828, + "m": -0.7945947554249617, + "n": 0.39729737771248086, + "o": -0.39729737771248086, + "p": 3.79842883499966, + "q": -1.8992144174998273, + "r": 1.899214417499829 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.645563270939276, + "y": -9.539084803950828 + }, + "control1": { + "x": 5.632346766916121, + "y": -9.475905491934615 + }, + "control2": { + "x": 5.592081716551166, + "y": -9.454147427258581 + }, + "end": { + "x": 5.531990571090186, + "y": -9.47771341297154 + }, + "a": 5.645563270939276, + "b": 5.632346766916121, + "c": 5.592081716551166, + "d": 5.531990571090186, + "e": -9.539084803950828, + "f": -9.475905491934615, + "g": -9.454147427258581, + "h": -9.47771341297154, + "m": 0.007222451245775474, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.003902803048813297, + "q": -0.041421247340180045, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.531990571090186, + "y": -9.47771341297154 + }, + "control1": { + "x": 5.338908247464632, + "y": -9.553434640201916 + }, + "control2": { + "x": 5.1504265475349404, + "y": -9.513603623453617 + }, + "end": { + "x": 5.192893258519078, + "y": -9.716608713064955 + }, + "a": 5.531990571090186, + "b": 5.338908247464632, + "c": 5.1504265475349404, + "d": 5.192893258519078, + "e": -9.47771341297154, + "f": -9.553434640201916, + "g": -9.513603623453617, + "h": -9.716608713064955, + "m": 0.22634778721796867, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.3583883503383163, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.192893258519078, + "y": -9.716608713064955 + }, + "control1": { + "x": 5.5914994579791495, + "y": -11.622079736503828 + }, + "control2": { + "x": 5.5914994579791495, + "y": -11.622079736503828 + }, + "end": { + "x": 5.990105657439221, + "y": -13.5275507599427 + }, + "a": 5.192893258519078, + "b": 5.5914994579791495, + "c": 5.5914994579791495, + "d": 5.990105657439221, + "e": -9.716608713064955, + "f": -11.622079736503828, + "g": -11.622079736503828, + "h": -13.5275507599427, + "m": 0.7972123989201432, + "n": -0.3986061994600716, + "o": 0.3986061994600716, + "p": -3.8109420468777433, + "q": 1.9054710234388725, + "r": -1.9054710234388725 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.990105657439221, + "y": -13.5275507599427 + }, + "control1": { + "x": 6.0059140303081895, + "y": -13.60312007238776 + }, + "control2": { + "x": 5.98958192326311, + "y": -13.634299196824482 + }, + "end": { + "x": 5.918457619700598, + "y": -13.66433183039084 + }, + "a": 5.990105657439221, + "b": 6.0059140303081895, + "c": 5.98958192326311, + "d": 5.918457619700598, + "e": -13.5275507599427, + "f": -13.60312007238776, + "g": -13.634299196824482, + "h": -13.66433183039084, + "m": -0.022651716603386163, + "n": -0.032140479914048115, + "o": 0.015808372868968412, + "p": -0.043243697137976156, + "q": 0.044390188008337716, + "r": -0.07556931244505982 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.918457619700598, + "y": -13.66433183039084 + }, + "control1": { + "x": 5.742439543858991, + "y": -13.738656443891765 + }, + "control2": { + "x": 5.579175771761254, + "y": -13.904606442846898 + }, + "end": { + "x": 5.540053288514205, + "y": -13.71758788093491 + }, + "a": 5.918457619700598, + "b": 5.742439543858991, + "c": 5.579175771761254, + "d": 5.540053288514205, + "e": -13.66433183039084, + "f": -13.738656443891765, + "g": -13.904606442846898, + "h": -13.71758788093491, + "m": 0.11138698510681433, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 5.540053288514205, + "y": -13.71758788093491 + }, + "control1": { + "x": 5.1401382673065426, + "y": -11.805860251556997 + }, + "control2": { + "x": 5.1401382673065426, + "y": -11.805860251556997 + }, + "end": { + "x": 4.74022324609888, + "y": -9.894132622179082 + }, + "a": 5.540053288514205, + "b": 5.1401382673065426, + "c": 5.1401382673065426, + "d": 4.74022324609888, + "e": -13.71758788093491, + "f": -11.805860251556997, + "g": -11.805860251556997, + "h": -9.894132622179082, + "m": -0.7998300424153246, + "n": 0.3999150212076623, + "o": -0.3999150212076623, + "p": 3.8234552587558284, + "q": -1.9117276293779142, + "r": 1.9117276293779142 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 4.74022324609888, + "y": -9.894132622179082 + }, + "control1": { + "x": 4.727006742075726, + "y": -9.830953310162869 + }, + "control2": { + "x": 4.686741691710771, + "y": -9.809195245486835 + }, + "end": { + "x": 4.626650546249791, + "y": -9.832761231199793 + }, + "a": 4.74022324609888, + "b": 4.727006742075726, + "c": 4.686741691710771, + "d": 4.626650546249791, + "e": -9.894132622179082, + "f": -9.830953310162869, + "g": -9.809195245486835, + "h": -9.832761231199793, + "m": 0.007222451245775474, + "n": -0.027048546341799984, + "o": -0.013216504023154663, + "p": -0.0039028030488097443, + "q": -0.041421247340180045, + "r": 0.06317931201621363 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 4.626650546249791, + "y": -9.832761231199793 }, "control1": { - "x": -5.050386505404345, - "y": -19.176228873513086 + "x": 4.4335682226242366, + "y": -9.90848245843017 }, "control2": { - "x": -5.050386505404345, - "y": -19.176228873513082 + "x": 4.245086522694545, + "y": -9.868651441681871 }, "end": { - "x": -12.091221491456647, - "y": -22.32881306320503 - }, - "a": 1.9904484806479574, - "b": -5.050386505404345, - "c": -5.050386505404345, - "d": -12.091221491456647, - "e": -16.02364468382114, - "f": -19.176228873513086, - "g": -19.176228873513082, - "h": -22.32881306320503, - "m": -14.081669972104605, - "n": 7.040834986052302, - "o": -7.040834986052302, - "p": -6.3051683793839075, - "q": 3.15258418969195, - "r": -3.1525841896919466 + "x": 4.287553233678683, + "y": -10.07165653129321 + }, + "a": 4.626650546249791, + "b": 4.4335682226242366, + "c": 4.245086522694545, + "d": 4.287553233678683, + "e": -9.832761231199793, + "f": -9.90848245843017, + "g": -9.868651441681871, + "h": -10.07165653129321, + "m": 0.2263477872179669, + "n": 0.004600623695862893, + "o": -0.19308232362555433, + "p": -0.35838835033830563, + "q": 0.1155522439786747, + "r": -0.07572122723037644 }, "backward": false }, { "spline": { "start": { - "x": -12.091221491456647, - "y": -22.32881306320503 + "x": 4.287553233678683, + "y": -10.07165653129321 }, "control1": { - "x": -12.177876510763841, - "y": -22.367613467470502 + "x": 4.688777076633936, + "y": -11.989640766610165 }, "control2": { - "x": -12.187425779127148, - "y": -22.36858771862414 + "x": 4.688777076633936, + "y": -11.989640766610165 }, "end": { - "x": -12.280130986665883, - "y": -22.348086298246653 - }, - "a": -12.091221491456647, - "b": -12.177876510763841, - "c": -12.187425779127148, - "d": -12.280130986665883, - "e": -22.32881306320503, - "f": -22.367613467470502, - "g": -22.36858771862414, - "h": -22.348086298246653, - "m": -0.16026169011931657, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.01635048158070518, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "x": 5.090000919589189, + "y": -13.907625001927123 + }, + "a": 4.287553233678683, + "b": 4.688777076633936, + "c": 4.688777076633936, + "d": 5.090000919589189, + "e": -10.07165653129321, + "f": -11.989640766610165, + "g": -11.989640766610165, + "h": -13.907625001927123, + "m": 0.8024476859105061, + "n": -0.40122384295525304, + "o": 0.40122384295525304, + "p": -3.8359684706339134, + "q": 1.9179842353169558, + "r": -1.9179842353169558 }, "backward": false }, { "spline": { "start": { - "x": -12.280130986665883, - "y": -22.348086298246653 + "x": 5.090000919589189, + "y": -13.907625001927123 }, "control1": { - "x": -12.354809913076233, - "y": -22.331571324553956 + "x": 5.105809292458157, + "y": -13.983194314372184 }, "control2": { - "x": -12.354693841811876, - "y": -22.317240040049903 + "x": 5.089477185413078, + "y": -14.014373438808907 }, "end": { - "x": -12.40480998472941, - "y": -22.259464105397644 - }, - "a": -12.280130986665883, - "b": -12.354809913076233, - "c": -12.354693841811876, - "d": -12.40480998472941, - "e": -22.348086298246653, - "f": -22.331571324553956, - "g": -22.317240040049903, - "h": -22.259464105397644, - "m": -0.1250272118566027, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.045628339336840185, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "x": 5.018352881850565, + "y": -14.044406072375264 + }, + "a": 5.090000919589189, + "b": 5.105809292458157, + "c": 5.089477185413078, + "d": 5.018352881850565, + "e": -13.907625001927123, + "f": -13.983194314372184, + "g": -14.014373438808907, + "h": -14.044406072375264, + "m": -0.022651716603389715, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 }, "backward": false }, { "spline": { "start": { - "x": -12.40480998472941, - "y": -22.259464105397644 + "x": 5.018352881850565, + "y": -14.044406072375264 }, "control1": { - "x": -14.420419836961068, - "y": -19.935786816452737 + "x": 4.842334806008958, + "y": -14.118730685876189 }, "control2": { - "x": -14.42041983696107, - "y": -19.93578681645274 + "x": 4.679071033911222, + "y": -14.284680684831322 }, "end": { - "x": -16.43602968919273, - "y": -17.612109527507833 - }, - "a": -12.40480998472941, - "b": -14.420419836961068, - "c": -14.42041983696107, - "d": -16.43602968919273, - "e": -22.259464105397644, - "f": -19.935786816452737, - "g": -19.93578681645274, - "h": -17.612109527507833, - "m": -4.03121970446332, - "n": 2.0156098522316572, - "o": -2.015609852231659, - "p": 4.647354577889818, - "q": -2.3236772889449107, - "r": 2.323677288944907 + "x": 4.6399485506641724, + "y": -14.097662122919335 + }, + "a": 5.018352881850565, + "b": 4.842334806008958, + "c": 4.679071033911222, + "d": 4.6399485506641724, + "e": -14.044406072375264, + "f": -14.118730685876189, + "g": -14.284680684831322, + "h": -14.097662122919335, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213264, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -16.43602968919273, - "y": -17.612109527507833 + "x": 4.6399485506641724, + "y": -14.097662122919335 }, "control1": { - "x": -16.49361810904551, - "y": -17.5457192470823 + "x": 4.237415885961329, + "y": -12.173421281663334 }, "control2": { - "x": -16.498206624660558, - "y": -17.52492779540708 + "x": 4.237415885961329, + "y": -12.173421281663334 }, "end": { - "x": -16.473910085467818, - "y": -17.440466125879556 - }, - "a": -16.43602968919273, - "b": -16.49361810904551, - "c": -16.498206624660558, - "d": -16.473910085467818, - "e": -17.612109527507833, - "f": -17.5457192470823, - "g": -17.52492779540708, - "h": -17.440466125879556, - "m": -0.024114849429942353, - "n": 0.052999904237733375, - "o": -0.05758841985278096, - "p": 0.10926904660262693, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "x": 3.834883221258485, + "y": -10.249180440407335 + }, + "a": 4.6399485506641724, + "b": 4.237415885961329, + "c": 4.237415885961329, + "d": 3.834883221258485, + "e": -14.097662122919335, + "f": -12.173421281663334, + "g": -12.173421281663334, + "h": -10.249180440407335, + "m": -0.8050653294056875, + "n": 0.40253266470284377, + "o": -0.40253266470284377, + "p": 3.8484816825119985, + "q": -1.924240841256001, + "r": 1.924240841256001 }, "backward": false }, { "spline": { "start": { - "x": -16.473910085467818, - "y": -17.440466125879556 + "x": 3.834883221258485, + "y": -10.249180440407335 }, "control1": { - "x": -16.459375225005935, - "y": -17.389938823566187 + "x": 3.8216667172353302, + "y": -10.186001128391121 }, "control2": { - "x": -16.426071759980577, - "y": -17.41801504143529 + "x": 3.781401666870376, + "y": -10.164243063715087 }, "end": { - "x": -16.375783038651115, - "y": -17.402675062466564 - }, - "a": -16.473910085467818, - "b": -16.459375225005935, - "c": -16.426071759980577, - "d": -16.375783038651115, - "e": -17.440466125879556, - "f": -17.389938823566187, - "g": -17.41801504143529, - "h": -17.402675062466564, - "m": -0.0017833482593694328, - "n": 0.018768604563476288, - "o": 0.01453486046188246, - "p": 0.12201971702029724, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "x": 3.7213105214093956, + "y": -10.187809049428045 + }, + "a": 3.834883221258485, + "b": 3.8216667172353302, + "c": 3.781401666870376, + "d": 3.7213105214093956, + "e": -10.249180440407335, + "f": -10.186001128391121, + "g": -10.164243063715087, + "h": -10.187809049428045, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488115206, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -16.375783038651115, - "y": -17.402675062466564 + "x": 3.7213105214093956, + "y": -10.187809049428045 }, "control1": { - "x": -8.321056452272451, - "y": -14.945676093962286 + "x": 3.528228197783841, + "y": -10.263530276658422 }, "control2": { - "x": -8.321056452272451, - "y": -14.945676093962286 + "x": 3.3397464978541502, + "y": -10.223699259910124 }, "end": { - "x": -0.2663298658937867, - "y": -12.488677125458008 - }, - "a": -16.375783038651115, - "b": -8.321056452272451, - "c": -8.321056452272451, - "d": -0.2663298658937867, - "e": -17.402675062466564, - "f": -14.945676093962286, - "g": -14.945676093962286, - "h": -12.488677125458008, - "m": 16.109453172757327, - "n": -8.054726586378663, - "o": 8.054726586378663, - "p": 4.913997937008553, - "q": -2.456998968504278, - "r": 2.456998968504278 + "x": 3.3822132088382872, + "y": -10.426704349521462 + }, + "a": 3.7213105214093956, + "b": 3.528228197783841, + "c": 3.3397464978541502, + "d": 3.3822132088382872, + "e": -10.187809049428045, + "f": -10.263530276658422, + "g": -10.223699259910124, + "h": -10.426704349521462, + "m": 0.22634778721796334, + "n": 0.0046006236958642255, + "o": -0.19308232362555477, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 }, "backward": false }, { "spline": { "start": { - "x": -0.2663298658937867, - "y": -12.488677125458008 + "x": 3.3822132088382872, + "y": -10.426704349521462 }, "control1": { - "x": -0.17566847189296741, - "y": -12.461021940746974 + "x": 3.7860546952887217, + "y": -12.357201796716504 }, "control2": { - "x": -0.1657873086812938, - "y": -12.461301514270977 + "x": 3.7860546952887217, + "y": -12.357201796716504 }, "end": { - "x": -0.07683462917804214, - "y": -12.494038624906537 - }, - "a": -0.2663298658937867, - "b": -0.17566847189296741, - "c": -0.1657873086812938, - "d": -0.07683462917804214, - "e": -12.488677125458008, - "f": -12.461021940746974, - "g": -12.461301514270977, - "h": -12.494038624906537, - "m": 0.15985174708072375, - "n": -0.08078023078914567, - "o": 0.0906613940008193, - "p": -0.004522778876523859, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "x": 4.189896181739156, + "y": -14.287699243911547 + }, + "a": 3.3822132088382872, + "b": 3.7860546952887217, + "c": 3.7860546952887217, + "d": 4.189896181739156, + "e": -10.426704349521462, + "f": -12.357201796716504, + "g": -12.357201796716504, + "h": -14.287699243911547, + "m": 0.807682972900869, + "n": -0.4038414864504345, + "o": 0.4038414864504345, + "p": -3.8609948943900854, + "q": 1.9304974471950427, + "r": -1.9304974471950427 }, "backward": false }, { "spline": { "start": { - "x": -0.07683462917804214, - "y": -12.494038624906537 + "x": 4.189896181739156, + "y": -14.287699243911547 }, "control1": { - "x": -0.005691927185520265, - "y": -12.5202211583507 + "x": 4.205704554608125, + "y": -14.363268556356608 }, "control2": { - "x": -0.008072901222325228, - "y": -12.534919434322207 + "x": 4.189372447563046, + "y": -14.39444768079333 }, "end": { - "x": 0.03279927747010902, - "y": -12.598765139841728 - }, - "a": -0.07683462917804214, - "b": -0.005691927185520265, - "c": -0.008072901222325228, - "d": 0.03279927747010902, - "e": -12.494038624906537, - "f": -12.5202211583507, - "g": -12.534919434322207, - "h": -12.598765139841728, - "m": 0.11677682875856604, - "n": -0.07352367602932683, - "o": 0.07114270199252187, - "p": -0.060631687020670455, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "x": 4.118248144000533, + "y": -14.424480314359688 + }, + "a": 4.189896181739156, + "b": 4.205704554608125, + "c": 4.189372447563046, + "d": 4.118248144000533, + "e": -14.287699243911547, + "f": -14.363268556356608, + "g": -14.39444768079333, + "h": -14.424480314359688, + "m": -0.022651716603389715, + "n": -0.032140479914047226, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 }, "backward": false }, { "spline": { "start": { - "x": 0.03279927747010902, - "y": -12.598765139841728 + "x": 4.118248144000533, + "y": -14.424480314359688 }, "control1": { - "x": 1.0489768792842749, - "y": -14.186118151022631 + "x": 3.942230068158926, + "y": -14.498804927860613 }, "control2": { - "x": 1.0489768792842744, - "y": -14.186118151022631 + "x": 3.7789662960611894, + "y": -14.664754926815744 }, "end": { - "x": 2.0651544810984404, - "y": -15.773471162203535 - }, - "a": 0.03279927747010902, - "b": 1.0489768792842749, - "c": 1.0489768792842744, - "d": 2.0651544810984404, - "e": -12.598765139841728, - "f": -14.186118151022631, - "g": -14.186118151022631, - "h": -15.773471162203535, - "m": 2.0323552036283328, - "n": -1.0161776018141664, - "o": 1.016177601814166, - "p": -3.174706022361807, - "q": 1.5873530111809036, - "r": -1.5873530111809036 + "x": 3.73984381281414, + "y": -14.477736364903757 + }, + "a": 4.118248144000533, + "b": 3.942230068158926, + "c": 3.7789662960611894, + "d": 3.73984381281414, + "e": -14.424480314359688, + "f": -14.498804927860613, + "g": -14.664754926815744, + "h": -14.477736364903757, + "m": 0.1113869851068161, + "n": 0.01275430374387021, + "o": -0.17601807584160678, + "p": 0.4445939463213229, + "q": -0.09162538545420595, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": 2.0651544810984404, - "y": -15.773471162203535 + "x": 3.73984381281414, + "y": -14.477736364903757 }, "control1": { - "x": 2.0899632936716346, - "y": -15.812224568371489 + "x": 3.3346935046161144, + "y": -12.540982311769673 }, "control2": { - "x": 2.065139022555776, - "y": -15.855260400063573 + "x": 3.3346935046161144, + "y": -12.540982311769673 }, "end": { - "x": 2.0191715439702316, - "y": -15.853188062398566 - }, - "a": 2.0651544810984404, - "b": 2.0899632936716346, - "c": 2.065139022555776, - "d": 2.0191715439702316, - "e": -15.773471162203535, - "f": -15.812224568371489, - "g": -15.855260400063573, - "h": -15.853188062398566, - "m": 0.028489876219366383, - "n": -0.049633083689052615, - "o": 0.024808812573194228, - "p": 0.049390594881224104, - "q": -0.004282425524129607, - "r": -0.038753406167954196 + "x": 2.9295431964180887, + "y": -10.604228258635588 + }, + "a": 3.73984381281414, + "b": 3.3346935046161144, + "c": 3.3346935046161144, + "d": 2.9295431964180887, + "e": -14.477736364903757, + "f": -12.540982311769673, + "g": -12.540982311769673, + "h": -10.604228258635588, + "m": -0.8103006163960513, + "n": 0.40515030819802567, + "o": -0.40515030819802567, + "p": 3.8735081062681704, + "q": -1.9367540531340843, + "r": 1.9367540531340843 }, "backward": false }, { "spline": { "start": { - "x": 2.0191715439702316, - "y": -15.853188062398566 + "x": 2.9295431964180887, + "y": -10.604228258635588 }, "control1": { - "x": 1.5013446687688368, - "y": -15.829843033033173 + "x": 2.916326692394934, + "y": -10.541048946619375 }, "control2": { - "x": 1.4635125473892967, - "y": -15.513188272615256 + "x": 2.87606164202998, + "y": -10.519290881943341 }, "end": { - "x": 0.9904191949100383, - "y": -15.725019202644013 - }, - "a": 2.0191715439702316, - "b": 1.5013446687688368, - "c": 1.4635125473892967, - "d": 0.9904191949100383, - "e": -15.853188062398566, - "f": -15.829843033033173, - "g": -15.513188272615256, - "h": -15.725019202644013, - "m": -0.9152559849215738, - "n": 0.47999475382185475, - "o": -0.5178268752013948, - "p": -0.8217954214991963, - "q": 0.2933097310525241, - "r": 0.023345029365392733 + "x": 2.8159704965689993, + "y": -10.5428568676563 + }, + "a": 2.9295431964180887, + "b": 2.916326692394934, + "c": 2.87606164202998, + "d": 2.8159704965689993, + "e": -10.604228258635588, + "f": -10.541048946619375, + "g": -10.519290881943341, + "h": -10.5428568676563, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.003902803048807968, + "q": -0.041421247340180045, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": 0.9904191949100383, - "y": -15.725019202644013 + "x": 2.8159704965689993, + "y": -10.5428568676563 }, "control1": { - "x": -5.458799399697785, - "y": -18.612702924461736 + "x": 2.6228881729434446, + "y": -10.618578094886676 }, "control2": { - "x": -5.458799399697786, - "y": -18.612702924461736 + "x": 2.4344064730137536, + "y": -10.578747078138377 }, "end": { - "x": -11.90801799430561, - "y": -21.500386646279456 + "x": 2.476873183997891, + "y": -10.781752167749715 + }, + "a": 2.8159704965689993, + "b": 2.6228881729434446, + "c": 2.4344064730137536, + "d": 2.476873183997891, + "e": -10.5428568676563, + "f": -10.618578094886676, + "g": -10.578747078138377, + "h": -10.781752167749715, + "m": 0.2263477872179651, + "n": 0.004600623695863781, + "o": -0.19308232362555477, + "p": -0.35838835033831096, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.476873183997891, + "y": -10.781752167749715 + }, + "control1": { + "x": 2.8833323139435074, + "y": -12.724762826822843 + }, + "control2": { + "x": 2.8833323139435074, + "y": -12.724762826822841 }, - "a": 0.9904191949100383, - "b": -5.458799399697785, - "c": -5.458799399697786, - "d": -11.90801799430561, - "e": -15.725019202644013, - "f": -18.612702924461736, - "g": -18.612702924461736, - "h": -21.500386646279456, - "m": -12.898437189215644, - "n": 6.449218594607823, - "o": -6.4492185946078235, - "p": -5.775367443635446, - "q": 2.887683721817723, - "r": -2.887683721817723 + "end": { + "x": 3.289791443889124, + "y": -14.66777348589597 + }, + "a": 2.476873183997891, + "b": 2.8833323139435074, + "c": 2.8833323139435074, + "d": 3.289791443889124, + "e": -10.781752167749715, + "f": -12.724762826822843, + "g": -12.724762826822841, + "h": -14.66777348589597, + "m": 0.8129182598912328, + "n": -0.4064591299456164, + "o": 0.4064591299456164, + "p": -3.8860213181462626, + "q": 1.9430106590731295, + "r": -1.9430106590731278 }, "backward": false }, { "spline": { "start": { - "x": -11.90801799430561, - "y": -21.500386646279456 + "x": 3.289791443889124, + "y": -14.66777348589597 }, "control1": { - "x": -11.994673013612804, - "y": -21.53918705054493 + "x": 3.3055998167580922, + "y": -14.743342798341029 }, "control2": { - "x": -12.00422228197611, - "y": -21.540161301698568 + "x": 3.289267709713013, + "y": -14.774521922777751 }, "end": { - "x": -12.096927489514846, - "y": -21.51965988132108 + "x": 3.2181434061505003, + "y": -14.804554556344108 + }, + "a": 3.289791443889124, + "b": 3.3055998167580922, + "c": 3.289267709713013, + "d": 3.2181434061505003, + "e": -14.66777348589597, + "f": -14.743342798341029, + "g": -14.774521922777751, + "h": -14.804554556344108, + "m": -0.02265171660338705, + "n": -0.03214047991404767, + "o": 0.015808372868968412, + "p": -0.04324369713796905, + "q": 0.044390188008337716, + "r": -0.07556931244505982 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 3.2181434061505003, + "y": -14.804554556344108 + }, + "control1": { + "x": 3.042125330308893, + "y": -14.878879169845034 }, - "a": -11.90801799430561, - "b": -11.994673013612804, - "c": -12.00422228197611, - "d": -12.096927489514846, - "e": -21.500386646279456, - "f": -21.53918705054493, - "g": -21.540161301698568, - "h": -21.51965988132108, - "m": -0.16026169011932012, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.016350481580698073, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "control2": { + "x": 2.878861558211156, + "y": -15.044829168800167 + }, + "end": { + "x": 2.8397390749641067, + "y": -14.85781060688818 + }, + "a": 3.2181434061505003, + "b": 3.042125330308893, + "c": 2.878861558211156, + "d": 2.8397390749641067, + "e": -14.804554556344108, + "f": -14.878879169845034, + "g": -15.044829168800167, + "h": -14.85781060688818, + "m": 0.111386985106817, + "n": 0.01275430374387021, + "o": -0.17601807584160722, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -12.096927489514846, - "y": -21.51965988132108 + "x": 2.8397390749641067, + "y": -14.85781060688818 }, "control1": { - "x": -12.171606415925195, - "y": -21.503144907628382 + "x": 2.4319711232709, + "y": -12.908543341876012 }, "control2": { - "x": -12.171490344660839, - "y": -21.48881362312433 + "x": 2.431971123270899, + "y": -12.90854334187601 }, "end": { - "x": -12.221606487578372, - "y": -21.43103768847207 + "x": 2.0242031715776925, + "y": -10.959276076863842 + }, + "a": 2.8397390749641067, + "b": 2.4319711232709, + "c": 2.431971123270899, + "d": 2.0242031715776925, + "e": -14.85781060688818, + "f": -12.908543341876012, + "g": -12.90854334187601, + "h": -10.959276076863842, + "m": -0.8155359033864116, + "n": 0.4077679516932058, + "o": -0.4077679516932067, + "p": 3.898534530024328, + "q": -1.9492672650121659, + "r": 1.9492672650121676 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.0242031715776925, + "y": -10.959276076863842 + }, + "control1": { + "x": 2.010986667554538, + "y": -10.89609676484763 + }, + "control2": { + "x": 1.9707216171895836, + "y": -10.874338700171595 }, - "a": -12.096927489514846, - "b": -12.171606415925195, - "c": -12.171490344660839, - "d": -12.221606487578372, - "e": -21.51965988132108, - "f": -21.503144907628382, - "g": -21.48881362312433, - "h": -21.43103768847207, - "m": -0.12502721185659205, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.04562833933684729, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "end": { + "x": 1.9106304717286033, + "y": -10.897904685884553 + }, + "a": 2.0242031715776925, + "b": 2.010986667554538, + "c": 1.9707216171895836, + "d": 1.9106304717286033, + "e": -10.959276076863842, + "f": -10.89609676484763, + "g": -10.874338700171595, + "h": -10.897904685884553, + "m": 0.007222451245773698, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.003902803048818626, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -12.221606487578372, - "y": -21.43103768847207 + "x": 1.9106304717286033, + "y": -10.897904685884553 }, "control1": { - "x": -13.727998538515276, - "y": -19.69440746058662 + "x": 1.7175481481030481, + "y": -10.97362591311493 }, "control2": { - "x": -13.72799853851528, - "y": -19.694407460586625 + "x": 1.5290664481733567, + "y": -10.93379489636663 }, "end": { - "x": -15.234390589452184, - "y": -17.957777232701176 + "x": 1.571533159157494, + "y": -11.13679998597797 + }, + "a": 1.9106304717286033, + "b": 1.7175481481030481, + "c": 1.5290664481733567, + "d": 1.571533159157494, + "e": -10.897904685884553, + "f": -10.97362591311493, + "g": -10.93379489636663, + "h": -11.13679998597797, + "m": 0.2263477872179649, + "n": 0.004600623695863781, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.571533159157494, + "y": -11.13679998597797 + }, + "control1": { + "x": 1.9806099325982918, + "y": -13.09232385692918 + }, + "control2": { + "x": 1.9806099325982918, + "y": -13.09232385692918 }, - "a": -12.221606487578372, - "b": -13.727998538515276, - "c": -13.72799853851528, - "d": -15.234390589452184, - "e": -21.43103768847207, - "f": -19.69440746058662, - "g": -19.694407460586625, - "h": -17.957777232701176, - "m": -3.0127841018737964, - "n": 1.5063920509369009, - "o": -1.5063920509369044, - "p": 3.4732604557709017, - "q": -1.7366302278854526, - "r": 1.736630227885449 + "end": { + "x": 2.3896867060390896, + "y": -15.047847727880393 + }, + "a": 1.571533159157494, + "b": 1.9806099325982918, + "c": 1.9806099325982918, + "d": 2.3896867060390896, + "e": -11.13679998597797, + "f": -13.09232385692918, + "g": -13.09232385692918, + "h": -15.047847727880393, + "m": 0.8181535468815957, + "n": -0.40907677344079785, + "o": 0.40907677344079785, + "p": -3.9110477419024257, + "q": 1.955523870951211, + "r": -1.955523870951211 }, "backward": false }, { "spline": { "start": { - "x": -15.234390589452184, - "y": -17.957777232701176 + "x": 2.3896867060390896, + "y": -15.047847727880393 }, "control1": { - "x": -15.291979009304963, - "y": -17.891386952275642 + "x": 2.405495078908058, + "y": -15.123417040325455 }, "control2": { - "x": -15.296567524920015, - "y": -17.870595500600423 + "x": 2.3891629718629797, + "y": -15.154596164762177 }, "end": { - "x": -15.272270985727273, - "y": -17.7861338310729 + "x": 2.3180386683004666, + "y": -15.184628798328534 + }, + "a": 2.3896867060390896, + "b": 2.405495078908058, + "c": 2.3891629718629797, + "d": 2.3180386683004666, + "e": -15.047847727880393, + "f": -15.123417040325455, + "g": -15.154596164762177, + "h": -15.184628798328534, + "m": -0.02265171660338705, + "n": -0.03214047991404678, + "o": 0.015808372868968412, + "p": -0.04324369713796905, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 2.3180386683004666, + "y": -15.184628798328534 + }, + "control1": { + "x": 2.1420205924588593, + "y": -15.25895341182946 + }, + "control2": { + "x": 1.9787568203611219, + "y": -15.424903410784593 }, - "a": -15.234390589452184, - "b": -15.291979009304963, - "c": -15.296567524920015, - "d": -15.272270985727273, - "e": -17.957777232701176, - "f": -17.891386952275642, - "g": -17.870595500600423, - "h": -17.7861338310729, - "m": -0.02411484942993347, - "n": 0.052999904237728046, - "o": -0.05758841985277918, - "p": 0.10926904660261982, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "end": { + "x": 1.9396343371140725, + "y": -15.237884848872605 + }, + "a": 2.3180386683004666, + "b": 2.1420205924588593, + "c": 1.9787568203611219, + "d": 1.9396343371140725, + "e": -15.184628798328534, + "f": -15.25895341182946, + "g": -15.424903410784593, + "h": -15.237884848872605, + "m": 0.11138698510681833, + "n": 0.012754303743869766, + "o": -0.17601807584160722, + "p": 0.4445939463213282, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -15.272270985727273, - "y": -17.7861338310729 + "x": 1.9396343371140725, + "y": -15.237884848872605 }, "control1": { - "x": -15.257736125265389, - "y": -17.73560652875953 + "x": 1.529248741925684, + "y": -13.27610437198235 }, "control2": { - "x": -15.22443266024003, - "y": -17.763682746628632 + "x": 1.529248741925684, + "y": -13.27610437198235 }, "end": { - "x": -15.174143938910568, - "y": -17.748342767659906 + "x": 1.1188631467372954, + "y": -11.314323895092096 + }, + "a": 1.9396343371140725, + "b": 1.529248741925684, + "c": 1.529248741925684, + "d": 1.1188631467372954, + "e": -15.237884848872605, + "f": -13.27610437198235, + "g": -13.27610437198235, + "h": -11.314323895092096, + "m": -0.8207711903767771, + "n": 0.4103855951883886, + "o": -0.4103855951883886, + "p": 3.9235609537805107, + "q": -1.9617804768902545, + "r": 1.9617804768902545 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.1188631467372954, + "y": -11.314323895092096 + }, + "control1": { + "x": 1.1056466427141407, + "y": -11.251144583075884 }, - "a": -15.272270985727273, - "b": -15.257736125265389, - "c": -15.22443266024003, - "d": -15.174143938910568, - "e": -17.7861338310729, - "f": -17.73560652875953, - "g": -17.763682746628632, - "h": -17.748342767659906, - "m": -0.0017833482593676564, - "n": 0.01876860456347451, - "o": 0.014534860461884236, - "p": 0.12201971702030434, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "control2": { + "x": 1.0653815923491867, + "y": -11.22938651839985 + }, + "end": { + "x": 1.0052904468882065, + "y": -11.252952504112807 + }, + "a": 1.1188631467372954, + "b": 1.1056466427141407, + "c": 1.0653815923491867, + "d": 1.0052904468882065, + "e": -11.314323895092096, + "f": -11.251144583075884, + "g": -11.22938651839985, + "h": -11.252952504112807, + "m": 0.007222451245773254, + "n": -0.027048546341799318, + "o": -0.013216504023154663, + "p": -0.0039028030488150733, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -15.174143938910568, - "y": -17.748342767659906 + "x": 1.0052904468882065, + "y": -11.252952504112807 }, "control1": { - "x": -7.86786772485169, - "y": -15.51964972277024 + "x": 0.8122081232626508, + "y": -11.328673731343184 }, "control2": { - "x": -7.86786772485169, - "y": -15.51964972277024 + "x": 0.6237264233329585, + "y": -11.288842714594884 }, "end": { - "x": -0.5615915107928121, - "y": -13.290956677880574 + "x": 0.6661931343170959, + "y": -11.491847804206223 + }, + "a": 1.0052904468882065, + "b": 0.8122081232626508, + "c": 0.6237264233329585, + "d": 0.6661931343170959, + "e": -11.252952504112807, + "f": -11.328673731343184, + "g": -11.288842714594884, + "h": -11.491847804206223, + "m": 0.22634778721796645, + "n": 0.004600623695863337, + "o": -0.19308232362555566, + "p": -0.3583883503383145, + "q": 0.11555224397867647, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.6661931343170959, + "y": -11.491847804206223 + }, + "control1": { + "x": 1.0778875512530761, + "y": -13.45988488703552 + }, + "control2": { + "x": 1.0778875512530761, + "y": -13.45988488703552 }, - "a": -15.174143938910568, - "b": -7.86786772485169, - "c": -7.86786772485169, - "d": -0.5615915107928121, - "e": -17.748342767659906, - "f": -15.51964972277024, - "g": -15.51964972277024, - "h": -13.290956677880574, - "m": 14.612552428117755, - "n": -7.306276214058878, - "o": 7.306276214058878, - "p": 4.457386089779334, - "q": -2.228693044889667, - "r": 2.228693044889667 + "end": { + "x": 1.4895819681890563, + "y": -15.427921969864816 + }, + "a": 0.6661931343170959, + "b": 1.0778875512530761, + "c": 1.0778875512530761, + "d": 1.4895819681890563, + "e": -11.491847804206223, + "f": -13.45988488703552, + "g": -13.45988488703552, + "h": -15.427921969864816, + "m": 0.8233888338719604, + "n": -0.4116944169359802, + "o": 0.4116944169359802, + "p": -3.9360741656585922, + "q": 1.9680370828292961, + "r": -1.9680370828292961 }, "backward": false }, { "spline": { "start": { - "x": -0.5615915107928121, - "y": -13.290956677880574 + "x": 1.4895819681890563, + "y": -15.427921969864816 }, "control1": { - "x": -0.4709301167919928, - "y": -13.263301493169541 + "x": 1.5053903410580247, + "y": -15.503491282309877 }, "control2": { - "x": -0.46104895358031917, - "y": -13.263581066693543 + "x": 1.4890582340129461, + "y": -15.5346704067466 }, "end": { - "x": -0.3720962740770675, - "y": -13.296318177329104 + "x": 1.417933930450433, + "y": -15.564703040312956 + }, + "a": 1.4895819681890563, + "b": 1.5053903410580247, + "c": 1.4890582340129461, + "d": 1.417933930450433, + "e": -15.427921969864816, + "f": -15.503491282309877, + "g": -15.5346704067466, + "h": -15.564703040312956, + "m": -0.02265171660338794, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 1.417933930450433, + "y": -15.564703040312956 }, - "a": -0.5615915107928121, - "b": -0.4709301167919928, - "c": -0.46104895358031917, - "d": -0.3720962740770675, - "e": -13.290956677880574, - "f": -13.263301493169541, - "g": -13.263581066693543, - "h": -13.296318177329104, - "m": 0.1598517470807238, - "n": -0.08078023078914565, - "o": 0.0906613940008193, - "p": -0.004522778876525635, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "control1": { + "x": 1.241915854608826, + "y": -15.639027653813882 + }, + "control2": { + "x": 1.0786520825110888, + "y": -15.804977652769015 + }, + "end": { + "x": 1.0395295992640392, + "y": -15.617959090857028 + }, + "a": 1.417933930450433, + "b": 1.241915854608826, + "c": 1.0786520825110888, + "d": 1.0395295992640392, + "e": -15.564703040312956, + "f": -15.639027653813882, + "g": -15.804977652769015, + "h": -15.617959090857028, + "m": 0.11138698510681766, + "n": 0.012754303743869766, + "o": -0.176018075841607, + "p": 0.44459394632133353, + "q": -0.09162538545420773, + "r": -0.07432461350092545 }, "backward": false }, { "spline": { "start": { - "x": -0.3720962740770675, - "y": -13.296318177329104 + "x": 1.0395295992640392, + "y": -15.617959090857028 }, "control1": { - "x": -0.30095357208454565, - "y": -13.322500710773268 + "x": 0.6265263605804682, + "y": -13.643665402088688 }, "control2": { - "x": -0.3033345461213507, - "y": -13.337198986744774 + "x": 0.6265263605804684, + "y": -13.643665402088688 }, "end": { - "x": -0.26246236742891643, - "y": -13.401044692264295 + "x": 0.21352312189689737, + "y": -11.669371713320349 + }, + "a": 1.0395295992640392, + "b": 0.6265263605804682, + "c": 0.6265263605804684, + "d": 0.21352312189689737, + "e": -15.617959090857028, + "f": -13.643665402088688, + "g": -13.643665402088688, + "h": -11.669371713320349, + "m": -0.8260064773671427, + "n": 0.41300323868357125, + "o": -0.41300323868357103, + "p": 3.948587377536679, + "q": -1.9742936887683395, + "r": 1.9742936887683395 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.21352312189689737, + "y": -11.669371713320349 + }, + "control1": { + "x": 0.20030661787374268, + "y": -11.606192401304137 + }, + "control2": { + "x": 0.1600415675087885, + "y": -11.584434336628101 }, - "a": -0.3720962740770675, - "b": -0.30095357208454565, - "c": -0.3033345461213507, - "d": -0.26246236742891643, - "e": -13.296318177329104, - "f": -13.322500710773268, - "g": -13.337198986744774, - "h": -13.401044692264295, - "m": 0.11677682875856615, - "n": -0.07352367602932691, - "o": 0.07114270199252187, - "p": -0.06063168702066868, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "end": { + "x": 0.09995042204780843, + "y": -11.60800032234106 + }, + "a": 0.21352312189689737, + "b": 0.20030661787374268, + "c": 0.1600415675087885, + "d": 0.09995042204780843, + "e": -11.669371713320349, + "f": -11.606192401304137, + "g": -11.584434336628101, + "h": -11.60800032234106, + "m": 0.007222451245773587, + "n": -0.027048546341799484, + "o": -0.01321650402315469, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -0.26246236742891643, - "y": -13.401044692264295 + "x": 0.09995042204780843, + "y": -11.60800032234106 }, "control1": { - "x": 0.4013314139658026, - "y": -14.43794518664535 + "x": -0.093131901577747, + "y": -11.683721549571437 }, "control2": { - "x": 0.4013314139658022, - "y": -14.43794518664535 + "x": -0.28161360150743864, + "y": -11.643890532823137 }, "end": { - "x": 1.0651251953605212, - "y": -15.474845681026409 + "x": -0.23914689052330118, + "y": -11.846895622434477 + }, + "a": 0.09995042204780843, + "b": -0.093131901577747, + "c": -0.28161360150743864, + "d": -0.23914689052330118, + "e": -11.60800032234106, + "f": -11.683721549571437, + "g": -11.643890532823137, + "h": -11.846895622434477, + "m": 0.22634778721796525, + "n": 0.004600623695863809, + "o": -0.19308232362555544, + "p": -0.3583883503383163, + "q": 0.11555224397867825, + "r": -0.07572122723037822 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.23914689052330118, + "y": -11.846895622434477 + }, + "control1": { + "x": 0.1751651699078604, + "y": -13.827445917141858 }, - "a": -0.26246236742891643, - "b": 0.4013314139658026, - "c": 0.4013314139658022, - "d": 1.0651251953605212, - "e": -13.401044692264295, - "f": -14.43794518664535, - "g": -14.43794518664535, - "h": -15.474845681026409, - "m": 1.3275875627894387, - "n": -0.6637937813947195, - "o": 0.663793781394719, - "p": -2.0738009887621143, - "q": 1.0369004943810562, - "r": -1.0369004943810562 + "control2": { + "x": 0.17516516990786052, + "y": -13.827445917141858 + }, + "end": { + "x": 0.5894772303390221, + "y": -15.80799621184924 + }, + "a": -0.23914689052330118, + "b": 0.1751651699078604, + "c": 0.17516516990786052, + "d": 0.5894772303390221, + "e": -11.846895622434477, + "f": -13.827445917141858, + "g": -13.827445917141858, + "h": -15.80799621184924, + "m": 0.8286241208623228, + "n": -0.4143120604311615, + "o": 0.4143120604311616, + "p": -3.9611005894147624, + "q": 1.9805502947073812, + "r": -1.9805502947073812 }, "backward": false }, { "spline": { "start": { - "x": 1.0651251953605212, - "y": -15.474845681026409 + "x": 0.5894772303390221, + "y": -15.80799621184924 }, "control1": { - "x": 1.0899340079337152, - "y": -15.513599087194363 + "x": 0.6052856032079905, + "y": -15.883565524294301 }, "control2": { - "x": 1.0651097368178575, - "y": -15.556634918886447 + "x": 0.5889534961629119, + "y": -15.914744648731023 }, "end": { - "x": 1.0191422582323126, - "y": -15.55456258122144 + "x": 0.5178291926003988, + "y": -15.94477728229738 + }, + "a": 0.5894772303390221, + "b": 0.6052856032079905, + "c": 0.5889534961629119, + "d": 0.5178291926003988, + "e": -15.80799621184924, + "f": -15.883565524294301, + "g": -15.914744648731023, + "h": -15.94477728229738, + "m": -0.022651716603387495, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": 0.5178291926003988, + "y": -15.94477728229738 + }, + "control1": { + "x": 0.34181111675879183, + "y": -16.019101895798308 + }, + "control2": { + "x": 0.17854734466105446, + "y": -16.18505189475344 }, - "a": 1.0651251953605212, - "b": 1.0899340079337152, - "c": 1.0651097368178575, - "d": 1.0191422582323126, - "e": -15.474845681026409, - "f": -15.513599087194363, - "g": -15.556634918886447, - "h": -15.55456258122144, - "m": 0.028489876219364607, - "n": -0.04963308368905173, - "o": 0.024808812573194006, - "p": 0.049390594881224104, - "q": -0.004282425524129607, - "r": -0.038753406167954196 + "end": { + "x": 0.139424861414005, + "y": -15.998033332841452 + }, + "a": 0.5178291926003988, + "b": 0.34181111675879183, + "c": 0.17854734466105446, + "d": 0.139424861414005, + "e": -15.94477728229738, + "f": -16.019101895798308, + "g": -16.18505189475344, + "h": -15.998033332841452, + "m": 0.11138698510681833, + "n": 0.012754303743869655, + "o": -0.176018075841607, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092723 }, "backward": false }, { "spline": { "start": { - "x": 1.0191422582323126, - "y": -15.55456258122144 + "x": 0.139424861414005, + "y": -15.998033332841452 }, "control1": { - "x": 0.501315383030917, - "y": -15.531217551856047 + "x": -0.2761960207647475, + "y": -14.011226432195027 }, "control2": { - "x": 0.4634832616513772, - "y": -15.214562791438128 + "x": -0.27619602076474725, + "y": -14.011226432195027 }, "end": { - "x": -0.009610090827881917, - "y": -15.426393721466885 + "x": -0.6918169029434997, + "y": -12.024419531548602 + }, + "a": 0.139424861414005, + "b": -0.2761960207647475, + "c": -0.27619602076474725, + "d": -0.6918169029434997, + "e": -15.998033332841452, + "f": -14.011226432195027, + "g": -14.011226432195027, + "h": -12.024419531548602, + "m": -0.8312417643575054, + "n": 0.4156208821787527, + "o": -0.4156208821787525, + "p": 3.9736138012928492, + "q": -1.9868069006464246, + "r": 1.9868069006464246 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.6918169029434997, + "y": -12.024419531548602 + }, + "control1": { + "x": -0.7050334069666544, + "y": -11.96124021953239 + }, + "control2": { + "x": -0.7452984573316086, + "y": -11.939482154856355 }, - "a": 1.0191422582323126, - "b": 0.501315383030917, - "c": 0.4634832616513772, - "d": -0.009610090827881917, - "e": -15.55456258122144, - "f": -15.531217551856047, - "g": -15.214562791438128, - "h": -15.426393721466885, - "m": -0.9152559849215753, - "n": 0.47999475382185575, - "o": -0.5178268752013956, - "p": -0.8217954214992034, - "q": 0.2933097310525259, - "r": 0.023345029365392733 + "end": { + "x": -0.8053896027925886, + "y": -11.963048140569313 + }, + "a": -0.6918169029434997, + "b": -0.7050334069666544, + "c": -0.7452984573316086, + "d": -0.8053896027925886, + "e": -12.024419531548602, + "f": -11.96124021953239, + "g": -11.939482154856355, + "h": -11.963048140569313, + "m": 0.007222451245773698, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -0.009610090827881917, - "y": -15.426393721466885 + "x": -0.8053896027925886, + "y": -11.963048140569313 }, "control1": { - "x": -5.867212293991229, - "y": -18.049176975410383 + "x": -0.9984719264181443, + "y": -12.038769367799691 }, "control2": { - "x": -5.86721229399123, - "y": -18.049176975410383 + "x": -1.1869536263478362, + "y": -11.998938351051391 }, "end": { - "x": -11.724814497154577, - "y": -20.671960229353882 + "x": -1.1444869153636987, + "y": -12.201943440662731 + }, + "a": -0.8053896027925886, + "b": -0.9984719264181443, + "c": -1.1869536263478362, + "d": -1.1444869153636987, + "e": -11.963048140569313, + "f": -12.038769367799691, + "g": -11.998938351051391, + "h": -12.201943440662731, + "m": 0.22634778721796556, + "n": 0.004600623695863781, + "o": -0.19308232362555566, + "p": -0.3583883503383234, + "q": 0.11555224397867825, + "r": -0.07572122723037822 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.1444869153636987, + "y": -12.201943440662731 + }, + "control1": { + "x": -0.7275572114373554, + "y": -14.195006947248197 + }, + "control2": { + "x": -0.7275572114373554, + "y": -14.195006947248197 }, - "a": -0.009610090827881917, - "b": -5.867212293991229, - "c": -5.86721229399123, - "d": -11.724814497154577, - "e": -15.426393721466885, - "f": -18.049176975410383, - "g": -18.049176975410383, - "h": -20.671960229353882, - "m": -11.715204406326691, - "n": 5.857602203163347, - "o": -5.8576022031633475, - "p": -5.245566507886993, - "q": 2.6227832539434974, - "r": -2.6227832539434974 + "end": { + "x": -0.3106275075110121, + "y": -16.188070453833664 + }, + "a": -1.1444869153636987, + "b": -0.7275572114373554, + "c": -0.7275572114373554, + "d": -0.3106275075110121, + "e": -12.201943440662731, + "f": -14.195006947248197, + "g": -14.195006947248197, + "h": -16.188070453833664, + "m": 0.8338594078526866, + "n": -0.4169297039263433, + "o": 0.4169297039263433, + "p": -3.9861270131709325, + "q": 1.9930635065854663, + "r": -1.9930635065854663 }, "backward": false }, { "spline": { "start": { - "x": -11.724814497154577, - "y": -20.671960229353882 + "x": -0.3106275075110121, + "y": -16.188070453833664 }, "control1": { - "x": -11.811469516461772, - "y": -20.710760633619355 + "x": -0.29481913464204373, + "y": -16.263639766278725 }, "control2": { - "x": -11.821018784825078, - "y": -20.711734884772994 + "x": -0.3111512416871223, + "y": -16.294818890715447 }, "end": { - "x": -11.913723992363813, - "y": -20.691233464395506 + "x": -0.38227554524963536, + "y": -16.324851524281804 + }, + "a": -0.3106275075110121, + "b": -0.29481913464204373, + "c": -0.3111512416871223, + "d": -0.38227554524963536, + "e": -16.188070453833664, + "f": -16.263639766278725, + "g": -16.294818890715447, + "h": -16.324851524281804, + "m": -0.022651716603387495, + "n": -0.03214047991404695, + "o": 0.015808372868968357, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -0.38227554524963536, + "y": -16.324851524281804 + }, + "control1": { + "x": -0.5582936210912419, + "y": -16.399176137782728 }, - "a": -11.724814497154577, - "b": -11.811469516461772, - "c": -11.821018784825078, - "d": -11.913723992363813, - "e": -20.671960229353882, - "f": -20.710760633619355, - "g": -20.711734884772994, - "h": -20.691233464395506, - "m": -0.16026169011931835, - "n": 0.07710575094388794, - "o": -0.08665501930719444, - "p": -0.01635048158070518, - "q": 0.03782615311183335, - "r": -0.03880040426547282 + "control2": { + "x": -0.7215573931889789, + "y": -16.565126136737863 + }, + "end": { + "x": -0.7606798764360283, + "y": -16.378107574825876 + }, + "a": -0.38227554524963536, + "b": -0.5582936210912419, + "c": -0.7215573931889789, + "d": -0.7606798764360283, + "e": -16.324851524281804, + "f": -16.399176137782728, + "g": -16.565126136737863, + "h": -16.378107574825876, + "m": 0.1113869851068181, + "n": 0.012754303743869544, + "o": -0.17601807584160656, + "p": 0.44459394632134064, + "q": -0.09162538545421128, + "r": -0.07432461350092368 }, "backward": false }, { "spline": { "start": { - "x": -11.913723992363813, - "y": -20.691233464395506 + "x": -0.7606798764360283, + "y": -16.378107574825876 }, "control1": { - "x": -11.988402918774163, - "y": -20.67471849070281 + "x": -1.1789184021099626, + "y": -14.378787462301366 }, "control2": { - "x": -11.988286847509807, - "y": -20.660387206198756 + "x": -1.1789184021099626, + "y": -14.378787462301366 }, "end": { - "x": -12.03840299042734, - "y": -20.602611271546497 + "x": -1.5971569277838968, + "y": -12.379467349776856 + }, + "a": -0.7606798764360283, + "b": -1.1789184021099626, + "c": -1.1789184021099626, + "d": -1.5971569277838968, + "e": -16.378107574825876, + "f": -14.378787462301366, + "g": -14.378787462301366, + "h": -12.379467349776856, + "m": -0.8364770513478685, + "n": 0.41823852567393427, + "o": -0.41823852567393427, + "p": 3.9986402250490194, + "q": -1.9993201125245097, + "r": 1.9993201125245097 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.5971569277838968, + "y": -12.379467349776856 + }, + "control1": { + "x": -1.6103734318070515, + "y": -12.316288037760645 + }, + "control2": { + "x": -1.6506384821720057, + "y": -12.29452997308461 }, - "a": -11.913723992363813, - "b": -11.988402918774163, - "c": -11.988286847509807, - "d": -12.03840299042734, - "e": -20.691233464395506, - "f": -20.67471849070281, - "g": -20.660387206198756, - "h": -20.602611271546497, - "m": -0.12502721185659382, - "n": 0.07479499767470621, - "o": -0.07467892641034979, - "p": 0.04562833933684729, - "q": -0.002183689188644422, - "r": 0.016514973692697055 + "end": { + "x": -1.710729627632986, + "y": -12.318095958797567 + }, + "a": -1.5971569277838968, + "b": -1.6103734318070515, + "c": -1.6506384821720057, + "d": -1.710729627632986, + "e": -12.379467349776856, + "f": -12.316288037760645, + "g": -12.29452997308461, + "h": -12.318095958797567, + "m": 0.00722245124577281, + "n": -0.02704854634179954, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017649, + "r": 0.06317931201621185 }, "backward": false }, { "spline": { "start": { - "x": -12.03840299042734, - "y": -20.602611271546497 + "x": -1.710729627632986, + "y": -12.318095958797567 }, "control1": { - "x": -13.035577240069488, - "y": -19.453028104720506 + "x": -1.903811951258541, + "y": -12.393817186027944 }, "control2": { - "x": -13.035577240069491, - "y": -19.453028104720506 + "x": -2.0922936511882324, + "y": -12.353986169279645 }, "end": { - "x": -14.03275148971164, - "y": -18.303444937894515 + "x": -2.049826940204095, + "y": -12.556991258890983 + }, + "a": -1.710729627632986, + "b": -1.903811951258541, + "c": -2.0922936511882324, + "d": -2.049826940204095, + "e": -12.318095958797567, + "f": -12.393817186027944, + "g": -12.353986169279645, + "h": -12.556991258890983, + "m": 0.22634778721796578, + "n": 0.004600623695863559, + "o": -0.193082323625555, + "p": -0.35838835033830563, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.049826940204095, + "y": -12.556991258890983 }, - "a": -12.03840299042734, - "b": -13.035577240069488, - "c": -13.035577240069491, - "d": -14.03275148971164, - "e": -20.602611271546497, - "f": -19.453028104720506, - "g": -19.453028104720506, - "h": -18.303444937894515, - "m": -1.9943484992842926, - "n": 0.9971742496421445, - "o": -0.9971742496421481, - "p": 2.2991663336519856, - "q": -1.149583166825991, - "r": 1.149583166825991 + "control1": { + "x": -1.63027959278257, + "y": -14.562567977354536 + }, + "control2": { + "x": -1.63027959278257, + "y": -14.562567977354536 + }, + "end": { + "x": -1.210732245361045, + "y": -16.568144695818088 + }, + "a": -2.049826940204095, + "b": -1.63027959278257, + "c": -1.63027959278257, + "d": -1.210732245361045, + "e": -12.556991258890983, + "f": -14.562567977354536, + "g": -14.562567977354536, + "h": -16.568144695818088, + "m": 0.83909469484305, + "n": -0.419547347421525, + "o": 0.419547347421525, + "p": -4.0111534369271045, + "q": 2.005576718463553, + "r": -2.005576718463553 }, "backward": false }, { "spline": { "start": { - "x": -14.03275148971164, - "y": -18.303444937894515 + "x": -1.210732245361045, + "y": -16.568144695818088 }, "control1": { - "x": -14.090339909564419, - "y": -18.23705465746898 + "x": -1.1949238724920765, + "y": -16.64371400826315 }, "control2": { - "x": -14.09492842517947, - "y": -18.216263205793762 + "x": -1.2112559795371551, + "y": -16.67489313269987 }, "end": { - "x": -14.070631885986728, - "y": -18.131801536266238 + "x": -1.2823802830996682, + "y": -16.70492576626623 + }, + "a": -1.210732245361045, + "b": -1.1949238724920765, + "c": -1.2112559795371551, + "d": -1.2823802830996682, + "e": -16.568144695818088, + "f": -16.64371400826315, + "g": -16.67489313269987, + "h": -16.70492576626623, + "m": -0.02265171660338705, + "n": -0.032140479914047004, + "o": 0.015808372868968412, + "p": -0.04324369713797793, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -1.2823802830996682, + "y": -16.70492576626623 + }, + "control1": { + "x": -1.458398358941275, + "y": -16.779250379767156 + }, + "control2": { + "x": -1.6216621310390122, + "y": -16.945200378722287 }, - "a": -14.03275148971164, - "b": -14.090339909564419, - "c": -14.09492842517947, - "d": -14.070631885986728, - "e": -18.303444937894515, - "f": -18.23705465746898, - "g": -18.216263205793762, - "h": -18.131801536266238, - "m": -0.024114849429931695, - "n": 0.052999904237728046, - "o": -0.05758841985277918, - "p": 0.10926904660261627, - "q": -0.045598828750314624, - "r": 0.06639028042553363 + "end": { + "x": -1.6607846142860616, + "y": -16.7581818168103 + }, + "a": -1.2823802830996682, + "b": -1.458398358941275, + "c": -1.6216621310390122, + "d": -1.6607846142860616, + "e": -16.70492576626623, + "f": -16.779250379767156, + "g": -16.945200378722287, + "h": -16.7581818168103, + "m": 0.1113869851068181, + "n": 0.012754303743869544, + "o": -0.17601807584160678, + "p": 0.4445939463213193, + "q": -0.09162538545420418, + "r": -0.07432461350092723 }, "backward": false }, { "spline": { "start": { - "x": -14.070631885986728, - "y": -18.131801536266238 + "x": -1.6607846142860616, + "y": -16.7581818168103 }, "control1": { - "x": -14.056097025524844, - "y": -18.08127423395287 + "x": -2.0816407834551773, + "y": -14.746348492407705 }, "control2": { - "x": -14.022793560499485, - "y": -18.10935045182197 + "x": -2.0816407834551773, + "y": -14.746348492407705 }, "end": { - "x": -13.972504839170023, - "y": -18.094010472853245 + "x": -2.502496952624293, + "y": -12.73451516800511 + }, + "a": -1.6607846142860616, + "b": -2.0816407834551773, + "c": -2.0816407834551773, + "d": -2.502496952624293, + "e": -16.7581818168103, + "f": -14.746348492407705, + "g": -14.746348492407705, + "h": -12.73451516800511, + "m": -0.8417123383382314, + "n": 0.4208561691691157, + "o": -0.4208561691691157, + "p": 4.0236666488051895, + "q": -2.0118333244025948, + "r": 2.0118333244025948 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.502496952624293, + "y": -12.73451516800511 + }, + "control1": { + "x": -2.5157134566474477, + "y": -12.671335855988897 }, - "a": -14.070631885986728, - "b": -14.056097025524844, - "c": -14.022793560499485, - "d": -13.972504839170023, - "e": -18.131801536266238, - "f": -18.08127423395287, - "g": -18.10935045182197, - "h": -18.094010472853245, - "m": -0.0017833482593729855, - "n": 0.01876860456347451, - "o": 0.014534860461884236, - "p": 0.1220197170203079, - "q": -0.07860352018247241, - "r": 0.05052730231336966 + "control2": { + "x": -2.5559785070124015, + "y": -12.649577791312861 + }, + "end": { + "x": -2.6160696524733824, + "y": -12.67314377702582 + }, + "a": -2.502496952624293, + "b": -2.5157134566474477, + "c": -2.5559785070124015, + "d": -2.6160696524733824, + "e": -12.73451516800511, + "f": -12.671335855988897, + "g": -12.649577791312861, + "h": -12.67314377702582, + "m": 0.0072224512457719214, + "n": -0.027048546341799096, + "o": -0.013216504023154663, + "p": -0.0039028030488168497, + "q": -0.04142124734017827, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -13.972504839170023, - "y": -18.094010472853245 + "x": -2.6160696524733824, + "y": -12.67314377702582 }, "control1": { - "x": -7.414678997430931, - "y": -16.093623351578195 + "x": -2.8091519760989376, + "y": -12.748865004256196 }, "control2": { - "x": -7.41467899743093, - "y": -16.093623351578195 + "x": -2.997633676028629, + "y": -12.709033987507897 }, "end": { - "x": -0.8568531556918383, - "y": -14.093236230303141 + "x": -2.9551669650444916, + "y": -12.912039077119235 + }, + "a": -2.6160696524733824, + "b": -2.8091519760989376, + "c": -2.997633676028629, + "d": -2.9551669650444916, + "e": -12.67314377702582, + "f": -12.748865004256196, + "g": -12.709033987507897, + "h": -12.912039077119235, + "m": 0.226347787217966, + "n": 0.004600623695863781, + "o": -0.19308232362555522, + "p": -0.3583883503383074, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.9551669650444916, + "y": -12.912039077119235 + }, + "control1": { + "x": -2.5330019741277847, + "y": -14.930129007460874 + }, + "control2": { + "x": -2.5330019741277847, + "y": -14.930129007460874 }, - "a": -13.972504839170023, - "b": -7.414678997430931, - "c": -7.41467899743093, - "d": -0.8568531556918383, - "e": -18.094010472853245, - "f": -16.093623351578195, - "g": -16.093623351578195, - "h": -14.093236230303141, - "m": 13.115651683478186, - "n": -6.557825841739091, - "o": 6.557825841739092, - "p": 4.000774242550104, - "q": -2.0003871212750504, - "r": 2.0003871212750504 + "end": { + "x": -2.110836983211078, + "y": -16.948218937802512 + }, + "a": -2.9551669650444916, + "b": -2.5330019741277847, + "c": -2.5330019741277847, + "d": -2.110836983211078, + "e": -12.912039077119235, + "f": -14.930129007460874, + "g": -14.930129007460874, + "h": -16.948218937802512, + "m": 0.8443299818334138, + "n": -0.4221649909167069, + "o": 0.4221649909167069, + "p": -4.036179860683276, + "q": 2.018089930341638, + "r": -2.018089930341638 }, "backward": false }, { "spline": { "start": { - "x": -0.8568531556918383, - "y": -14.093236230303141 + "x": -2.110836983211078, + "y": -16.948218937802512 }, "control1": { - "x": -0.766191761691019, - "y": -14.065581045592108 + "x": -2.0950286103421094, + "y": -17.023788250247573 }, "control2": { - "x": -0.7563105984793453, - "y": -14.06586061911611 + "x": -2.111360717387188, + "y": -17.054967374684296 }, "end": { - "x": -0.6673579189760938, - "y": -14.09859772975167 + "x": -2.182485020949701, + "y": -17.085000008250653 + }, + "a": -2.110836983211078, + "b": -2.0950286103421094, + "c": -2.111360717387188, + "d": -2.182485020949701, + "e": -16.948218937802512, + "f": -17.023788250247573, + "g": -17.054967374684296, + "h": -17.085000008250653, + "m": -0.02265171660338705, + "n": -0.03214047991404678, + "o": 0.015808372868968412, + "p": -0.04324369713797083, + "q": 0.04439018800833949, + "r": -0.0755693124450616 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -2.182485020949701, + "y": -17.085000008250653 + }, + "control1": { + "x": -2.358503096791308, + "y": -17.15932462175158 + }, + "control2": { + "x": -2.5217668688890456, + "y": -17.32527462070671 }, - "a": -0.8568531556918383, - "b": -0.766191761691019, - "c": -0.7563105984793453, - "d": -0.6673579189760938, - "e": -14.093236230303141, - "f": -14.065581045592108, - "g": -14.06586061911611, - "h": -14.09859772975167, - "m": 0.15985174708072336, - "n": -0.08078023078914565, - "o": 0.0906613940008193, - "p": -0.004522778876516753, - "q": -0.027934758235035773, - "r": 0.027655184711033343 + "end": { + "x": -2.560889352136095, + "y": -17.138256058794724 + }, + "a": -2.182485020949701, + "b": -2.358503096791308, + "c": -2.5217668688890456, + "d": -2.560889352136095, + "e": -17.085000008250653, + "f": -17.15932462175158, + "g": -17.32527462070671, + "h": -17.138256058794724, + "m": 0.11138698510681833, + "n": 0.012754303743869766, + "o": -0.17601807584160722, + "p": 0.4445939463213264, + "q": -0.09162538545420418, + "r": -0.07432461350092723 }, "backward": false }, { "spline": { "start": { - "x": -0.6673579189760938, - "y": -14.09859772975167 + "x": -2.560889352136095, + "y": -17.138256058794724 }, "control1": { - "x": -0.5962152169835718, - "y": -14.124780263195834 + "x": -2.9843631648003925, + "y": -15.113909522514044 }, "control2": { - "x": -0.5985961910203768, - "y": -14.13947853916734 + "x": -2.9843631648003925, + "y": -15.113909522514044 }, "end": { - "x": -0.5577240123279424, - "y": -14.203324244686861 + "x": -3.40783697746469, + "y": -13.089562986233364 + }, + "a": -2.560889352136095, + "b": -2.9843631648003925, + "c": -2.9843631648003925, + "d": -3.40783697746469, + "e": -17.138256058794724, + "f": -15.113909522514044, + "g": -15.113909522514044, + "h": -13.089562986233364, + "m": -0.8469476253285952, + "n": 0.4234738126642976, + "o": -0.4234738126642976, + "p": 4.048693072561363, + "q": -2.02434653628068, + "r": 2.02434653628068 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -3.40783697746469, + "y": -13.089562986233364 + }, + "control1": { + "x": -3.421053481487845, + "y": -13.02638367421715 + }, + "control2": { + "x": -3.4613185318527986, + "y": -13.004625609541115 }, - "a": -0.6673579189760938, - "b": -0.5962152169835718, - "c": -0.5985961910203768, - "d": -0.5577240123279424, - "e": -14.09859772975167, - "f": -14.124780263195834, - "g": -14.13947853916734, - "h": -14.203324244686861, - "m": 0.11677682875856632, - "n": -0.07352367602932686, - "o": 0.07114270199252193, - "p": -0.06063168702067756, - "q": 0.011484257472657688, - "r": -0.02618253344416388 + "end": { + "x": -3.5214096773137795, + "y": -13.028191595254073 + }, + "a": -3.40783697746469, + "b": -3.421053481487845, + "c": -3.4613185318527986, + "d": -3.5214096773137795, + "e": -13.089562986233364, + "f": -13.02638367421715, + "g": -13.004625609541115, + "h": -13.028191595254073, + "m": 0.00722245124577281, + "n": -0.027048546341799096, + "o": -0.013216504023154663, + "p": -0.003902803048813297, + "q": -0.04142124734017827, + "r": 0.06317931201621363 }, "backward": false }, { "spline": { "start": { - "x": -0.5577240123279424, - "y": -14.203324244686861 + "x": -3.5214096773137795, + "y": -13.028191595254073 }, "control1": { - "x": -0.24631405135267054, - "y": -14.68977222226807 + "x": -3.7144920009393347, + "y": -13.10391282248445 }, "control2": { - "x": -0.24631405135267095, - "y": -14.689772222268072 + "x": -3.9029737008690257, + "y": -13.064081805736151 }, "end": { - "x": 0.06509590962260095, - "y": -15.176220199849281 + "x": -3.8605069898848883, + "y": -13.26708689534749 + }, + "a": -3.5214096773137795, + "b": -3.7144920009393347, + "c": -3.9029737008690257, + "d": -3.8605069898848883, + "e": -13.028191595254073, + "f": -13.10391282248445, + "g": -13.064081805736151, + "h": -13.26708689534749, + "m": 0.22634778721796334, + "n": 0.0046006236958642255, + "o": -0.19308232362555522, + "p": -0.3583883503383145, + "q": 0.1155522439786747, + "r": -0.07572122723037644 + }, + "backward": false + }, + { + "spline": { + "start": { + "x": -3.8605069898848883, + "y": -13.26708689534749 + }, + "control1": { + "x": -3.4357243554729995, + "y": -15.297690037567211 }, - "a": -0.5577240123279424, - "b": -0.24631405135267054, - "c": -0.24631405135267095, - "d": 0.06509590962260095, - "e": -14.203324244686861, - "f": -14.68977222226807, - "g": -14.689772222268072, - "h": -15.176220199849281, - "m": 0.6228199219505446, - "n": -0.3114099609752723, - "o": 0.3114099609752719, - "p": -0.9728959551624214, - "q": 0.48644797758120717, - "r": -0.48644797758120895 + "control2": { + "x": -3.435724355473, + "y": -15.297690037567211 + }, + "end": { + "x": -3.010941721061111, + "y": -17.328293179786932 + }, + "a": -3.8605069898848883, + "b": -3.4357243554729995, + "c": -3.435724355473, + "d": -3.010941721061111, + "e": -13.26708689534749, + "f": -15.297690037567211, + "g": -15.297690037567211, + "h": -17.328293179786932, + "m": 0.8495652688237789, + "n": -0.42478263441188924, + "o": 0.4247826344118888, + "p": -4.061206284439443, + "q": 2.0306031422197215, + "r": -2.0306031422197215 }, "backward": false } @@ -3162,35 +7362,35 @@ "current_path_segment": { "spline": { "start": { - "x": -13.972504839170023, - "y": -18.094010472853245 + "x": 0.6661931343170959, + "y": -11.491847804206223 }, "control1": { - "x": -7.414678997430931, - "y": -16.093623351578195 + "x": 1.0778875512530761, + "y": -13.45988488703552 }, "control2": { - "x": -7.41467899743093, - "y": -16.093623351578195 + "x": 1.0778875512530761, + "y": -13.45988488703552 }, "end": { - "x": -0.8568531556918383, - "y": -14.093236230303141 - }, - "a": -13.972504839170023, - "b": -7.414678997430931, - "c": -7.41467899743093, - "d": -0.8568531556918383, - "e": -18.094010472853245, - "f": -16.093623351578195, - "g": -16.093623351578195, - "h": -14.093236230303141, - "m": 13.115651683478186, - "n": -6.557825841739091, - "o": 6.557825841739092, - "p": 4.000774242550104, - "q": -2.0003871212750504, - "r": 2.0003871212750504 + "x": 1.4895819681890563, + "y": -15.427921969864816 + }, + "a": 0.6661931343170959, + "b": 1.0778875512530761, + "c": 1.0778875512530761, + "d": 1.4895819681890563, + "e": -11.491847804206223, + "f": -13.45988488703552, + "g": -13.45988488703552, + "h": -15.427921969864816, + "m": 0.8233888338719604, + "n": -0.4116944169359802, + "o": 0.4116944169359802, + "p": -3.9360741656585922, + "q": 1.9680370828292961, + "r": -1.9680370828292961 }, "backward": false } diff --git a/backup/rb28/field_friend.automations.weeding.json b/backup/rb28/field_friend.automations.weeding.json new file mode 100644 index 00000000..efd3fd67 --- /dev/null +++ b/backup/rb28/field_friend.automations.weeding.json @@ -0,0 +1,26 @@ +{ + "use_field_planning": true, + "field": null, + "start_row_id": null, + "end_row_id": null, + "minimum_turning_radius": 0.5, + "turn_offset": 0.4, + "only_monitoring": false, + "drill_with_open_tornado": false, + "drill_between_crops": false, + "only_drilling": false, + "only_chopping": false, + "chop_if_no_crops": false, + "tornado_angle": 110.0, + "weed_screw_depth": 0.15, + "crop_safety_distance": 0.01, + "linear_speed_on_row": 0.5, + "angular_speed_on_row": 0.5, + "linear_speed_between_rows": 0.5, + "angular_speed_between_rows": 0.5, + "sorted_weeding_rows": [], + "weeding_plan": [], + "turn_paths": [], + "current_row": null, + "current_segment": null +} \ No newline at end of file diff --git a/backup/rb28/field_friend.navigation.gnss.json b/backup/rb28/field_friend.navigation.gnss.json index dc185d46..407aec13 100644 --- a/backup/rb28/field_friend.navigation.gnss.json +++ b/backup/rb28/field_friend.navigation.gnss.json @@ -1,4 +1,13 @@ { - "reference_lat": 51.983140848333335, - "reference_lon": 7.434131408333333 + "record": { + "timestamp": 1714383598.7, + "latitude": 53.110880316666666, + "longitude": 13.91411435, + "mode": "DAAA", + "gps_qual": 2, + "altitude": 62.1027, + "separation": "43.2568", + "heading": 0.0, + "speed_kmh": 0.0 + } } \ No newline at end of file diff --git a/backup/rb28/field_friend.vision.calibratable_usb_camera_provider.json b/backup/rb28/field_friend.vision.calibratable_usb_camera_provider.json new file mode 100644 index 00000000..ecfc8049 --- /dev/null +++ b/backup/rb28/field_friend.vision.calibratable_usb_camera_provider.json @@ -0,0 +1,93 @@ +{ + "cameras": { + "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { + "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "connect_after_init": true, + "streaming": true, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 730.672004356113, + 0.0, + 349.4579020458706 + ], + [ + 0.0, + 747.3229371982118, + 306.47658848091226 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.4433783071214983, + 0.26015075527427883, + 0.0038723050893755035, + -0.000710634350529248, + -0.09040485124205291 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 780, + "height": 720 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.01439018074828502, + 0.7644481697651403, + -0.6445245677557675 + ], + [ + 0.9997806209878847, + 0.0011890510028333967, + -0.02091162487212134 + ], + [ + -0.015219480776667554, + -0.6446840946544593, + -0.7642975765396935 + ] + ] + }, + "translation": [ + 0.3088200185097347, + 0.004181817389632946, + 0.31627615299671547 + ] + } + }, + "auto_exposure": true, + "exposure": false, + "width": 1280, + "height": 720, + "fps": 9 + } + } +} \ No newline at end of file diff --git a/backup/rb28/field_friend.vision.usb_cam_provider.json b/backup/rb28/field_friend.vision.usb_cam_provider.json index be58249f..d61eb546 100644 --- a/backup/rb28/field_friend.vision.usb_cam_provider.json +++ b/backup/rb28/field_friend.vision.usb_cam_provider.json @@ -1,21 +1,88 @@ { "cameras": { - "HD_USB_Camera_HD_USB_Camera": { - "id": "HD_USB_Camera_HD_USB_Camera", - "name": "HD_USB_Camera_HD_USB_Camera", - "connect_after_init": true, - "streaming": true, - "auto_exposure": true, - "exposure": false, - "width": 1920, - "height": 1080, - "fps": 6 - }, "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", "connect_after_init": true, "streaming": true, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 1079.9508535164803, + 0.0, + 544.1826309856062 + ], + [ + 0.0, + 1101.1475820425799, + 403.07410538903343 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.3826870922741008, + 0.01375680101360712, + 0.004251100356821879, + 0.0006867591075843546, + 0.18994139003680205 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 1220, + "height": 980 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.018691479240049325, + 0.7690563101689658, + -0.6389076775193069 + ], + [ + 0.999686091558257, + 0.0037122194868790537, + -0.024777767685117014 + ], + [ + -0.016683733059336223, + -0.6391702521361403, + -0.7688842837745014 + ] + ] + }, + "translation": [ + 0.3047716549397838, + 0.00466836222855056, + 0.3154736356456431 + ] + } + }, "auto_exposure": true, "exposure": false, "width": 1920, diff --git a/backup/rb28/rosys.pathplanning.path_planner.json b/backup/rb28/rosys.pathplanning.path_planner.json index 30fe16ff..f33b9a7f 100644 --- a/backup/rb28/rosys.pathplanning.path_planner.json +++ b/backup/rb28/rosys.pathplanning.path_planner.json @@ -1,26 +1,132 @@ { - "obstacles": {}, + "obstacles": { + "row_bb0bf3d5-419d-49f0-b8ef-598f48b25f02": { + "id": "row_bb0bf3d5-419d-49f0-b8ef-598f48b25f02", + "outline": [ + { + "x": -8.653099477350798, + "y": -32.84647608886028 + }, + { + "x": -8.653099477350798, + "y": -4.566697692851725 + }, + { + "x": -2.440510181265307, + "y": -4.566697692851725 + }, + { + "x": -2.440510181265307, + "y": -32.84647608886028 + } + ] + }, + "row_d7745165-3fb1-4b0d-8b6d-1f01c0037a80": { + "id": "row_d7745165-3fb1-4b0d-8b6d-1f01c0037a80", + "outline": [ + { + "x": -15.15901450292266, + "y": -30.805428384420015 + }, + { + "x": -15.15901450292266, + "y": -3.2768422375404103 + }, + { + "x": -9.085392068597827, + "y": -3.2768422375404103 + }, + { + "x": -9.085392068597827, + "y": -30.805428384420015 + } + ] + }, + "row_04939b09-604c-470e-bcbc-a04786f36229": { + "id": "row_04939b09-604c-470e-bcbc-a04786f36229", + "outline": [ + { + "x": -10.748030900121053, + "y": -31.412360766999765 + }, + { + "x": -10.748030900121053, + "y": -4.237278556612357 + }, + { + "x": -4.680963230413845, + "y": -4.237278556612357 + }, + { + "x": -4.680963230413845, + "y": -31.412360766999765 + } + ] + }, + "row_71a7bbfa-5d58-43c2-97bd-e8c09fbd94e4": { + "id": "row_71a7bbfa-5d58-43c2-97bd-e8c09fbd94e4", + "outline": [ + { + "x": -17.267664661646293, + "y": -30.234691581604128 + }, + { + "x": -17.267664661646293, + "y": -2.634428410873591 + }, + { + "x": -11.153551086545443, + "y": -2.634428410873591 + }, + { + "x": -11.153551086545443, + "y": -30.234691581604128 + } + ] + }, + "row_2f32bcec-e3d5-49bb-bdda-05aa36e7acd8": { + "id": "row_2f32bcec-e3d5-49bb-bdda-05aa36e7acd8", + "outline": [ + { + "x": -12.928431467082303, + "y": -31.136975020580866 + }, + { + "x": -12.928431467082303, + "y": -3.6884484944825418 + }, + { + "x": -6.791170875092781, + "y": -3.6884484944825418 + }, + { + "x": -6.791170875092781, + "y": -31.136975020580866 + } + ] + } + }, "areas": { - "2a621665-b0f0-443c-8252-3bd42c997845": { + "c94823e6-1738-4d8a-995b-72e021fd7951": { "outline": [ { - "x": 4.14008820351158, - "y": -16.529322089550206 + "x": -0.05365968084014772, + "y": -6.5714156384155456e-18 }, { - "x": -12.634128191514506, - "y": -24.040097374894728 + "x": -21.424643655025765, + "y": 3.99701288123126 }, { - "x": -18.930759085830115, - "y": -16.78108433237046 + "x": -28.98852605656782, + "y": -31.834266595067685 }, { - "x": 0.5057610077650115, - "y": -10.85220402722948 + "x": -7.397976402045103, + "y": -36.933732669956406 } ], - "id": "2a621665-b0f0-443c-8252-3bd42c997845", + "id": "c94823e6-1738-4d8a-995b-72e021fd7951", "type": null, "color": "green", "closed": true diff --git a/backup/rb28/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json b/backup/rb28/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json new file mode 100644 index 00000000..ee111faa --- /dev/null +++ b/backup/rb28/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json @@ -0,0 +1,36 @@ +{ + "cameras": { + "b8:a4:4f:8f:8b:77-1": { + "id": "b8:a4:4f:8f:8b:77-1", + "name": "b8:a4:4f:8f:8b:77-1", + "connect_after_init": false, + "streaming": true, + "username": "root", + "password": "zauberzg!" + }, + "b8:a4:4f:8f:8b:77-2": { + "id": "b8:a4:4f:8f:8b:77-2", + "name": "b8:a4:4f:8f:8b:77-2", + "connect_after_init": false, + "streaming": true, + "username": "root", + "password": "zauberzg!" + }, + "b8:a4:4f:8f:8b:77-3": { + "id": "b8:a4:4f:8f:8b:77-3", + "name": "b8:a4:4f:8f:8b:77-3", + "connect_after_init": false, + "streaming": true, + "username": "root", + "password": "zauberzg!" + }, + "b8:a4:4f:8f:8b:77-4": { + "id": "b8:a4:4f:8f:8b:77-4", + "name": "b8:a4:4f:8f:8b:77-4", + "connect_after_init": false, + "streaming": true, + "username": "root", + "password": "zauberzg!" + } + } +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.automations.field_provider.json b/backup/zauberzeug@192.168.42.2/field_friend.automations.field_provider.json new file mode 100644 index 00000000..1f837775 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.automations.field_provider.json @@ -0,0 +1,420 @@ +{ + "fields": [ + { + "id": "5a8a2850-89d8-4c31-8998-58f95c4bf12b", + "name": "Test", + "outline_wgs84": [ + [ + 53.11086450840848, + 13.91407542875983 + ], + [ + 53.110900665, + 13.914103491666667 + ], + [ + 53.11083908809259, + 13.914568552126127 + ], + [ + 53.11080833166667, + 13.914556051666667 + ] + ], + "reference_lat": 53.11065129259277, + "reference_lon": 13.91371250152588, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "a2a8d7a7-1d09-42f3-9a38-d2a35d2f6440", + "name": "row_1", + "points_wgs84": [ + [ + 53.11086900379518, + 13.914155492406122 + ], + [ + 53.11082100387979, + 13.914524203377239 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "dbfad64c-0180-4593-b6ff-a9ab87b8024a", + "name": "row_2", + "points_wgs84": [ + [ + 53.11087289058598, + 13.91415661849706 + ], + [ + 53.11082537116117, + 13.914524852764124 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "58729d6f-268b-4a37-8d7a-770a4d7f7546", + "name": "row_4", + "points_wgs84": [ + [ + 53.11088111899296, + 13.9141582500688 + ], + [ + 53.11083298032453, + 13.91452758242803 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "8d83be1e-40f1-433c-803b-19352b339c77", + "name": "row_5", + "points_wgs84": [ + [ + 53.11088460915779, + 13.914162049853772 + ], + [ + 53.1108366940985, + 13.914525808114977 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "f275d15c-b65c-462f-a18b-a8576f888a54", + "name": "field_Bohren", + "outline_wgs84": [ + [ + 53.110881503838094, + 13.914047766613132 + ], + [ + 53.110692736924065, + 13.913986660077686 + ], + [ + 53.11061942665463, + 13.914525650421513 + ], + [ + 53.110815801795376, + 13.91460211337871 + ] + ], + "reference_lat": 53.110881499529384, + "reference_lon": 13.914047767487654, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "bc19d73e-6516-4be4-9d5d-9c7f82905791", + "name": "row_1", + "points_wgs84": [ + [ + 53.1108103725628, + 13.914533857646589 + ], + [ + 53.11086579154916, + 13.914106568851572 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "cac9f4be-7487-4c72-adb3-93a2f7ba9266", + "name": "row_2", + "points_wgs84": [ + [ + 53.11079158358294, + 13.914522718556901 + ], + [ + 53.11084545526309, + 13.914113096249709 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "7e1c954c-1c3d-4751-8b5c-5310db183d02", + "name": "row_3", + "points_wgs84": [ + [ + 53.110771634262846, + 13.914515257349777 + ], + [ + 53.110825714596324, + 13.914102179170046 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "72390a9f-2f89-41ae-8285-494e5dc1c9fd", + "name": "row_4", + "points_wgs84": [ + [ + 53.11075179138159, + 13.914505245488035 + ], + [ + 53.110806123173724, + 13.914098907293617 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "4228284a-f2b5-446e-a2a2-85281edd0764", + "name": "row_5", + "points_wgs84": [ + [ + 53.11073251919884, + 13.914499494997212 + ], + [ + 53.11078634220459, + 13.914090071936823 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "38d0b78e-73ff-481e-a9fd-ad0b4f0bc383", + "name": "field_Pendelhacke", + "outline_wgs84": [ + [ + 53.1108814932072, + 13.91404776075555 + ], + [ + 53.110692719323964, + 13.913986645203941 + ], + [ + 53.11061944584767, + 13.914525645292146 + ], + [ + 53.110814933096734, + 13.914602167478126 + ] + ], + "reference_lat": 53.11088148947587, + "reference_lon": 13.914047757052812, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "a1b944a9-2f32-4863-9ed1-5b7d3e3905a9", + "name": "row_1", + "points_wgs84": [ + [ + 53.11080748389169, + 13.914528449056974 + ], + [ + 53.1108616244314, + 13.914115170279958 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "06485080-96a3-4387-bd98-ae13c5959378", + "name": "row_2", + "points_wgs84": [ + [ + 53.11078736188202, + 13.91452134589732 + ], + [ + 53.110841041222685, + 13.914112616811344 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "7c403dd6-76da-4a44-a793-4fb8814f910e", + "name": "row_3", + "points_wgs84": [ + [ + 53.11076732735035, + 13.914514540695514 + ], + [ + 53.11082206421793, + 13.914099414601182 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "66a73378-a6c4-463c-a71a-699779bea36f", + "name": "row_4", + "points_wgs84": [ + [ + 53.11074767905439, + 13.914503905811971 + ], + [ + 53.1108022193147, + 13.914094882137139 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "6948c407-cf9b-4638-a7a8-dc7e0dcfb1ba", + "name": "row_5", + "points_wgs84": [ + [ + 53.11072939628047, + 13.91449482199291 + ], + [ + 53.110782272589596, + 13.91408976451268 + ] + ], + "reverse": false, + "crops": [] + } + ] + }, + { + "id": "e51659c4-d7aa-412a-9f1f-81c2fc1aa0b8", + "name": "field_DoppelMechanik", + "outline_wgs84": [ + [ + 53.11088151081755, + 13.914047789228333 + ], + [ + 53.11069272815888, + 13.913986684849567 + ], + [ + 53.11061941486577, + 13.914525678599288 + ], + [ + 53.11081493838742, + 13.914602171888317 + ] + ], + "reference_lat": 53.11088150880825, + "reference_lon": 13.91404779326869, + "visualized": false, + "obstacles": [], + "rows": [ + { + "id": "afbafd10-dcf6-4c4f-a462-241f7d48c6a1", + "name": "row_1", + "points_wgs84": [ + [ + 53.11079956288332, + 13.914524099085297 + ], + [ + 53.11085311243897, + 13.91411221946047 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "870744db-13ce-432a-96dd-a9e23e502836", + "name": "row_2", + "points_wgs84": [ + [ + 53.110780072773125, + 13.914515952501517 + ], + [ + 53.110833534926016, + 13.91410706581986 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "620063b9-a870-4000-8be3-e2a6669ae4fd", + "name": "row_3", + "points_wgs84": [ + [ + 53.1107598941182, + 13.914508513187641 + ], + [ + 53.11081359893513, + 13.914103616913216 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "bcc78e8f-b5c3-444d-b657-33d7984082c8", + "name": "row_4", + "points_wgs84": [ + [ + 53.11074016554936, + 13.914501518993957 + ], + [ + 53.11079387469022, + 13.914094245038237 + ] + ], + "reverse": false, + "crops": [] + }, + { + "id": "50bd98c7-2f2b-4ec1-9c11-5582f4932f01", + "name": "row_5", + "points_wgs84": [ + [ + 53.11072112793958, + 13.914493604773137 + ], + [ + 53.11077420287833, + 13.91408888452689 + ] + ], + "reverse": false, + "crops": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.automations.kpi_provider.json b/backup/zauberzeug@192.168.42.2/field_friend.automations.kpi_provider.json new file mode 100644 index 00000000..ba018716 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.automations.kpi_provider.json @@ -0,0 +1,64 @@ +{ + "current_weeding_kpis": { + "distance": 0, + "time": 0, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "rows_weeded": 0, + "crops_detected": 0, + "weeds_detected": 0, + "weeds_removed": 0, + "punches": 0, + "chops": 0, + "weeding_completed": false + }, + "current_mowing_kpis": { + "distance": 0, + "time": 0, + "bumps": 0, + "low_battery": 0, + "can_failure": 0, + "automation_stopped": 0, + "e_stop_triggered": 0, + "soft_e_stop_triggered": 0, + "imu_rolling_detected": 0, + "gnss_connection_lost": 0, + "mowing_completed": false + }, + "days": [ + { + "date": "2024-04-24", + "incidents": { + "low_battery": 3 + } + }, + { + "date": "2024-04-26", + "incidents": { + "low_battery": 9, + "punches": 4 + } + }, + { + "date": "2024-04-30", + "incidents": { + "time": 3817, + "distance": 277, + "weeds_detected": 1497, + "crops_detected": 94, + "rows_weeded": 3, + "weeding_completed": 4, + "punches": 33, + "weeds_removed": 55, + "chops": 19 + } + } + ], + "months": [] +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.automations.mowing.json b/backup/zauberzeug@192.168.42.2/field_friend.automations.mowing.json new file mode 100644 index 00000000..4e1a52f1 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.automations.mowing.json @@ -0,0 +1,7 @@ +{ + "padding": 1.0, + "lane_distance": 0.5, + "paths": [], + "current_path": [], + "current_path_segment": null +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.automations.path_provider.json b/backup/zauberzeug@192.168.42.2/field_friend.automations.path_provider.json new file mode 100644 index 00000000..f05625f8 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.automations.path_provider.json @@ -0,0 +1,3 @@ +{ + "paths": [] +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.automations.weeding.json b/backup/zauberzeug@192.168.42.2/field_friend.automations.weeding.json new file mode 100644 index 00000000..b5668515 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.automations.weeding.json @@ -0,0 +1,25 @@ +{ + "use_field_planning": true, + "field": null, + "start_row_id": null, + "end_row_id": null, + "minimum_turning_radius": 0.5, + "only_monitoring": false, + "drill_with_open_tornado": false, + "drill_between_crops": false, + "only_drilling": false, + "only_chopping": false, + "chop_if_no_crops": false, + "tornado_angle": 110.0, + "weed_screw_depth": 0.15, + "crop_safety_distance": 0.01, + "linear_speed_on_row": 0.08, + "angular_speed_on_row": 0.4, + "linear_speed_between_rows": 0.3, + "angular_speed_between_rows": 0.8, + "sorted_weeding_rows": [], + "weeding_plan": [], + "turn_paths": [], + "current_row": null, + "current_segment": null +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.navigation.gnss.json b/backup/zauberzeug@192.168.42.2/field_friend.navigation.gnss.json new file mode 100644 index 00000000..005f4bb1 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.navigation.gnss.json @@ -0,0 +1,13 @@ +{ + "record": { + "timestamp": null, + "latitude": 0.0, + "longitude": 0.0, + "mode": "NNNN", + "gps_qual": 0, + "altitude": 0.0, + "separation": 0.0, + "heading": 0.0, + "speed_kmh": 0.0 + } +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.vision.calibratable_usb_camera_provider.json b/backup/zauberzeug@192.168.42.2/field_friend.vision.calibratable_usb_camera_provider.json new file mode 100644 index 00000000..c5dfb39b --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.vision.calibratable_usb_camera_provider.json @@ -0,0 +1,93 @@ +{ + "cameras": { + "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { + "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "connect_after_init": true, + "streaming": true, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 1433.6721216716353, + 0.0, + 347.7277694468386 + ], + [ + 0.0, + 1370.2101675446997, + 239.1060666171041 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.01557127473024067, + -56.25674323172758, + 0.23577466596211383, + 0.011342764817018949, + 421.8140030391272 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 720, + "height": 560 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.005087957942356058, + 0.9070537937934018, + -0.42098399951657745 + ], + [ + 0.9967070483093687, + -0.02946986293924958, + -0.075541955420596 + ], + [ + -0.08092695802019419, + -0.419982073835704, + -0.9039169680464345 + ] + ] + }, + "translation": [ + 0.5757624106417404, + 0.0740620510871811, + 1.1604844656286453 + ] + } + }, + "auto_exposure": true, + "exposure": false, + "width": 1280, + "height": 720, + "fps": 9 + } + } +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/field_friend.vision.usb_cam_provider.json b/backup/zauberzeug@192.168.42.2/field_friend.vision.usb_cam_provider.json new file mode 100644 index 00000000..d226b1ae --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/field_friend.vision.usb_cam_provider.json @@ -0,0 +1,93 @@ +{ + "cameras": { + "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0": { + "id": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "name": "32e4-9230-platform-3610000.xhci-usb-0:2.1:1.0", + "connect_after_init": true, + "streaming": true, + "auto_exposure": true, + "exposure": false, + "width": 1920, + "height": 1080, + "fps": 6, + "focal_length": 1830, + "calibration": { + "intrinsics": { + "matrix": [ + [ + 1433.6721216716353, + 0.0, + 347.7277694468386 + ], + [ + 0.0, + 1370.2101675446997, + 239.1060666171041 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ], + "distortion": [ + -0.01557127473024067, + -56.25674323172758, + 0.23577466596211383, + 0.011342764817018949, + 421.8140030391272 + ], + "rotation": { + "R": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0 + ] + ] + }, + "size": { + "width": 720, + "height": 560 + } + }, + "extrinsics": { + "rotation": { + "R": [ + [ + -0.005087957942356058, + 0.9070537937934018, + -0.42098399951657745 + ], + [ + 0.9967070483093687, + -0.02946986293924958, + -0.075541955420596 + ], + [ + -0.08092695802019419, + -0.419982073835704, + -0.9039169680464345 + ] + ] + }, + "translation": [ + 0.5757624106417404, + 0.0740620510871811, + 1.1604844656286453 + ] + } + } + } + } +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/rosys.config.json b/backup/zauberzeug@192.168.42.2/rosys.config.json new file mode 100644 index 00000000..07b402b0 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/rosys.config.json @@ -0,0 +1,3 @@ +{ + "simulation_speed": 1.0 +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/rosys.pathplanning.path_planner.json b/backup/zauberzeug@192.168.42.2/rosys.pathplanning.path_planner.json new file mode 100644 index 00000000..e3fa2fa6 --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/rosys.pathplanning.path_planner.json @@ -0,0 +1,114 @@ +{ + "obstacles": { + "row_a2a8d7a7-1d09-42f3-9a38-d2a35d2f6440": { + "id": "row_a2a8d7a7-1d09-42f3-9a38-d2a35d2f6440", + "outline": [ + { + "x": 24.218813097300302, + "y": -29.674937338763694 + }, + { + "x": 24.218813097300302, + "y": -54.34577219385262 + }, + { + "x": 18.89719766451805, + "y": -54.34577219385262 + }, + { + "x": 18.89719766451805, + "y": -29.674937338763694 + } + ] + }, + "row_dbfad64c-0180-4593-b6ff-a9ab87b8024a": { + "id": "row_dbfad64c-0180-4593-b6ff-a9ab87b8024a", + "outline": [ + { + "x": 24.65136812285677, + "y": -29.750343471273254 + }, + { + "x": 24.65136812285677, + "y": -54.38925300182701 + }, + { + "x": 19.38322571902191, + "y": -54.38925300182701 + }, + { + "x": 19.38322571902191, + "y": -29.750343471273254 + } + ] + }, + "row_58729d6f-268b-4a37-8d7a-770a4d7f7546": { + "id": "row_58729d6f-268b-4a37-8d7a-770a4d7f7546", + "outline": [ + { + "x": 25.567094688912526, + "y": -29.859596160954577 + }, + { + "x": 25.567094688912526, + "y": -54.5720358214478 + }, + { + "x": 20.23003907146769, + "y": -54.5720358214478 + }, + { + "x": 20.23003907146769, + "y": -29.859596160954577 + } + ] + }, + "row_8d83be1e-40f1-433c-803b-19352b339c77": { + "id": "row_8d83be1e-40f1-433c-803b-19352b339c77", + "outline": [ + { + "x": 25.95551098654778, + "y": -30.11404670703774 + }, + { + "x": 25.95551098654778, + "y": -54.45321394411896 + }, + { + "x": 20.64333751816373, + "y": -54.45321394411896 + }, + { + "x": 20.64333751816373, + "y": -30.11404670703774 + } + ] + } + }, + "areas": { + "5a8a2850-89d8-4c31-8998-58f95c4bf12b": { + "outline": [ + { + "x": 23.72849874434108, + "y": -24.303468212230452 + }, + { + "x": 27.752316275418853, + "y": -26.182682363470505 + }, + { + "x": 20.899794534151816, + "y": -57.32557073067396 + }, + { + "x": 17.476952549804945, + "y": -56.48851568957107 + } + ], + "id": "5a8a2850-89d8-4c31-8998-58f95c4bf12b", + "type": null, + "color": "green", + "closed": true + } + } +} \ No newline at end of file diff --git a/backup/zauberzeug@192.168.42.2/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json b/backup/zauberzeug@192.168.42.2/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json new file mode 100644 index 00000000..cbd18b8a --- /dev/null +++ b/backup/zauberzeug@192.168.42.2/rosys.vision.mjpeg_camera.mjpeg_camera_provider.json @@ -0,0 +1,3 @@ +{ + "cameras": {} +} \ No newline at end of file diff --git a/config/ff12_config_rb32/hardware.py b/config/ff12_config_rb32/hardware.py index a44039cc..a628ce60 100644 --- a/config/ff12_config_rb32/hardware.py +++ b/config/ff12_config_rb32/hardware.py @@ -36,8 +36,8 @@ 'end_bottom_pin': 5, 'ref_motor_pin': 35, 'ref_gear_pin': 18, - 'ref_t_pin': 4, - 'ref_b_pin': 33, + 'ref_knife_stop_pin': 4, + 'ref_knife_ground_pin': 33, 'motors_on_expander': False, 'end_stops_on_expander': True, 'is_z_reversed': True, @@ -45,6 +45,8 @@ 'speed_limit': 1.5, 'turn_speed_limit': 1.5, 'current_limit': 30, + 'z_reference_speed': 0.0075, + 'turn_reference_speed': 0.25, }, 'flashlight': { 'version': 'flashlight_pwm_v2', diff --git a/config/u3_config_rb27/params.py b/config/u3_config_rb27/params.py index 14384c98..2e4f0d4c 100644 --- a/config/u3_config_rb27/params.py +++ b/config/u3_config_rb27/params.py @@ -8,4 +8,5 @@ 'drill_radius': 0.025, 'chop_radius': 0.07, 'tool': 'dual_mechanism', + 'antenna_offset': 0.195, } diff --git a/config/u4_config_rb28/camera.py b/config/u4_config_rb28/camera.py index 3e799d61..923bedee 100644 --- a/config/u4_config_rb28/camera.py +++ b/config/u4_config_rb28/camera.py @@ -5,9 +5,9 @@ 'auto_exposure': True, }, 'crop': { - 'left': 350, - 'right': 350, - 'up': 50, - 'down': 50, + 'left': 250, + 'right': 250, + 'up': 0, + 'down': 0, }, } diff --git a/config/u4_config_rb28/hardware.py b/config/u4_config_rb28/hardware.py index c7249826..bd17102e 100644 --- a/config/u4_config_rb28/hardware.py +++ b/config/u4_config_rb28/hardware.py @@ -13,7 +13,7 @@ 'version': 'y_axis_stepper', 'name': 'y_axis', 'max_speed': 60_000, - 'reference_speed': 20_000, + 'reference_speed': 10_000, 'min_position': -0.068, 'max_position': 0.068, 'axis_offset': 0.075, @@ -38,8 +38,8 @@ 'end_bottom_pin': 5, 'ref_motor_pin': 33, 'ref_gear_pin': 4, - 'ref_t_pin': 35, - 'ref_b_pin': 18, + 'ref_knife_stop_pin': 35, + 'ref_knife_ground_pin': 18, 'motors_on_expander': False, 'end_stops_on_expander': True, 'is_z_reversed': True, @@ -47,6 +47,8 @@ 'speed_limit': 1.5, 'turn_speed_limit': 1.3, 'current_limit': 30, + 'z_reference_speed': 0.0075, + 'turn_reference_speed': 0.25, }, 'flashlight': { 'version': 'flashlight_pwm', diff --git a/config/u4_config_rb28/params.py b/config/u4_config_rb28/params.py index afcc88e5..3f53a21f 100644 --- a/config/u4_config_rb28/params.py +++ b/config/u4_config_rb28/params.py @@ -5,5 +5,6 @@ 'wheel_distance': 0.47, 'work_x': -0.0105, 'drill_radius': 0.025, + 'antenna_offset': 0.205, 'tool': 'tornado', } diff --git a/config/u5_config_rb33/params.py b/config/u5_config_rb33/params.py index 0b95e8a6..9f3c75a8 100644 --- a/config/u5_config_rb33/params.py +++ b/config/u5_config_rb33/params.py @@ -5,5 +5,6 @@ 'wheel_distance': 0.47, 'work_x': 0.093, 'drill_radius': 0.025, + 'antenna_offset': 0.205, 'tool': 'weed_screw', } diff --git a/config/u6_config_rb34/params.py b/config/u6_config_rb34/params.py index 0b95e8a6..9f3c75a8 100644 --- a/config/u6_config_rb34/params.py +++ b/config/u6_config_rb34/params.py @@ -5,5 +5,6 @@ 'wheel_distance': 0.47, 'work_x': 0.093, 'drill_radius': 0.025, + 'antenna_offset': 0.205, 'tool': 'weed_screw', } diff --git a/docker-compose.yml b/docker-compose.yml index b4f5accf..1e417596 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,12 +25,12 @@ services: max-size: "200m" max-file: "100" init: true - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/"] - interval: 60s - timeout: 20s - retries: 3 - start_period: 60s + # healthcheck: + # test: curl --fail http://localhost/status || exit 1 + # interval: 20s + # retries: 3 + # start_period: 1h + # timeout: 10s autoheal: # restart containers which turn unhealthy image: willfarrell/autoheal diff --git a/field_friend/automations/coin_collecting.py b/field_friend/automations/coin_collecting.py index 10e72a63..6a39fac5 100644 --- a/field_friend/automations/coin_collecting.py +++ b/field_friend/automations/coin_collecting.py @@ -40,12 +40,12 @@ async def start(self) -> None: rosys.notify('Y-Axis is in alarm, aborting', 'negative') self.log.error('Y-Axis is in alarm, aborting') return - if self.system.field_friend.z_axis.ref_t: - rosys.notify('Tornado is in top ref', 'negative') - self.log.error('Tornado is in top ref') + if self.system.field_friend.z_axis.ref_knife_stop: + rosys.notify('Tornado is in knife stop ref', 'negative') + self.log.error('Tornado is in knife stop ref') return if not await self.system.puncher.try_home(): - rosys.notify('Puncher homing failed, aborting', 'error') + rosys.notify('Puncher homing failed, aborting', 'negative') self.log.error('Puncher homing failed, aborting') self.work_x = self.system.field_friend.WORK_X self.system.odometer.reset() diff --git a/field_friend/automations/field_provider.py b/field_friend/automations/field_provider.py index 1360bac1..d50f5fb0 100644 --- a/field_friend/automations/field_provider.py +++ b/field_friend/automations/field_provider.py @@ -84,7 +84,6 @@ def outline(self) -> list[Point]: else: return [] - @property def area(self) -> float: if len(self.outline) > 0: polygon = Polygon([(p.x, p.y) for p in self.outline]) @@ -94,8 +93,8 @@ def area(self) -> float: def worked_area(self, worked_rows: int) -> float: worked_area = 0.0 - if self.area > 0: - worked_area = worked_rows * self.area / len(self.rows) + if self.area() > 0: + worked_area = worked_rows * self.area() / len(self.rows) return worked_area diff --git a/field_friend/automations/plant_locator.py b/field_friend/automations/plant_locator.py index 855c7153..f1072810 100644 --- a/field_friend/automations/plant_locator.py +++ b/field_friend/automations/plant_locator.py @@ -42,8 +42,10 @@ async def _detect_plants(self) -> None: t = rosys.time() camera = next((camera for camera in self.camera_provider.cameras.values() if camera.is_connected), None) if not camera: - raise DetectorError() + self.log.error('no connected camera found') + return if camera.calibration is None: + self.log.error('no calibration found') raise DetectorError() new_image = camera.latest_captured_image if new_image is None or new_image.detections: @@ -67,7 +69,7 @@ async def _detect_plants(self) -> None: continue world_point = self.odometer.prediction.transform(floor_point.projection()) weed = Plant(position=world_point, type=d.category_name, detection_time=rosys.time()) - self.plant_provider.add_weed(weed) + await self.plant_provider.add_weed(weed) elif d.category_name in self.crop_category_names and d.confidence >= self.minimum_crop_confidence: # self.log.info('crop found') image_point = rosys.geometry.Point(x=d.cx, y=d.cy) @@ -78,7 +80,7 @@ async def _detect_plants(self) -> None: world_point = self.odometer.prediction.transform(floor_point.projection()) crop = Plant(position=world_point, type=d.category_name, detection_time=rosys.time(), confidence=d.confidence) - self.plant_provider.add_crop(crop) + await self.plant_provider.add_crop(crop) elif d.category_name not in self.crop_category_names and d.category_name not in self.weed_category_names: self.log.info(f'{d.category_name} not in categories') # else: diff --git a/field_friend/automations/plant_provider.py b/field_friend/automations/plant_provider.py index 629d57b7..bde9b08b 100644 --- a/field_friend/automations/plant_provider.py +++ b/field_friend/automations/plant_provider.py @@ -20,6 +20,17 @@ def __post_init__(self) -> None: self.id = str(uuid.uuid4()) +def check_if_plant_exists(plant: Plant, plants: list[Plant], distance: float) -> bool: + for p in plants: + if p.position.distance(plant.position) < distance and p.type == plant.type: + if p.confidence > plant.confidence: + return True + p.position = plant.position + p.confidence = plant.confidence + return True + return False + + class PlantProvider: def __init__(self) -> None: @@ -38,20 +49,16 @@ def __init__(self) -> None: rosys.on_repeat(self.prune, 10.0) - def prune(self, max_age: float = 10 * 60.0) -> None: - self.weeds[:] = [weed for weed in self.weeds if weed.detection_time > rosys.time() - max_age] - self.crops[:] = [crop for crop in self.crops if crop.detection_time > rosys.time() - max_age] + def prune(self) -> None: + weeds_max_age = 10.0 + crops_max_age = 60.0 * 60.0 + self.weeds[:] = [weed for weed in self.weeds if weed.detection_time > rosys.time() - weeds_max_age] + self.crops[:] = [crop for crop in self.crops if crop.detection_time > rosys.time() - crops_max_age] self.PLANTS_CHANGED.emit() - def add_weed(self, weed: Plant) -> None: - for w in self.weeds: - if w.position.distance(weed.position) < 0.02 and w.type == weed.type: - if w.confidence > weed.confidence: - return - w.position = weed.position - w.confidence = weed.confidence - self.PLANTS_CHANGED.emit() - return + async def add_weed(self, weed: Plant) -> None: + if await rosys.run.cpu_bound(check_if_plant_exists, weed, self.weeds, 0.05): + return self.weeds.append(weed) self.PLANTS_CHANGED.emit() self.ADDED_NEW_WEED.emit() @@ -64,15 +71,9 @@ def clear_weeds(self) -> None: self.weeds.clear() self.PLANTS_CHANGED.emit() - def add_crop(self, crop: Plant) -> None: - for c in self.crops: - if c.position.distance(crop.position) < 0.05 and c.type == crop.type: - if c.confidence > crop.confidence: - return - c.position = crop.position - c.confidence = crop.confidence - self.PLANTS_CHANGED.emit() - return + async def add_crop(self, crop: Plant) -> None: + if await rosys.run.cpu_bound(check_if_plant_exists, crop, self.crops, 0.05): + return self.crops.append(crop) self.PLANTS_CHANGED.emit() self.ADDED_NEW_CROP.emit() diff --git a/field_friend/automations/puncher.py b/field_friend/automations/puncher.py index d9ea564b..dad19f62 100644 --- a/field_friend/automations/puncher.py +++ b/field_friend/automations/puncher.py @@ -52,7 +52,7 @@ async def drive_to_punch(self, local_target_x: float) -> None: world_target = self.driver.prediction.transform(local_target) await self.driver.drive_to(world_target, backward=axis_distance < 0) - async def punch(self, y: float, *, depth: float = 0.01, angle: float = 180) -> None: + async def punch(self, y: float, *, depth: float = 0.01, angle: float = 180, turns: float = 2.0) -> None: self.log.info(f'Punching at {y} with depth {depth}...') if self.field_friend.y_axis is None or self.field_friend.z_axis is None: rosys.notify('no y or z axis', 'negative') @@ -79,7 +79,7 @@ async def punch(self, y: float, *, depth: float = 0.01, angle: float = 180) -> N if isinstance(self.field_friend.z_axis, Tornado): await self.field_friend.y_axis.move_to(y) - await self.tornado_drill(angle=angle) + await self.tornado_drill(angle=angle, turns=turns) elif isinstance(self.field_friend.z_axis, ZAxis): await self.field_friend.y_axis.move_to(y) await self.field_friend.z_axis.move_to(-depth) @@ -105,7 +105,7 @@ async def clear_view(self) -> None: await self.field_friend.y_axis.move_to(y, speed=self.field_friend.y_axis.max_speed) await self.field_friend.y_axis.stop() - async def drive_and_punch(self, x: float, y: float, depth: float = 0.05, angle: float = 180, backwards_allowed: bool = True) -> None: + async def drive_and_punch(self, x: float, y: float, depth: float = 0.05, angle: float = 180, turns: float = 2.0, backwards_allowed: bool = True) -> None: if self.field_friend.y_axis is None or self.field_friend.z_axis is None: rosys.notify('no y or z axis', 'negative') return @@ -115,7 +115,8 @@ async def drive_and_punch(self, x: float, y: float, depth: float = 0.05, angle: self.log.warning(f'target x: {x} is behind') return await self.drive_to_punch(x) - await self.punch(y, depth=depth, angle=angle) + await self.punch(y, depth=depth, angle=angle, turns=turns) + # await self.clear_view() except Exception as e: raise PuncherException('drive and punch failed') from e @@ -129,7 +130,7 @@ async def chop(self) -> None: await self.field_friend.y_axis.stop() self.kpi_provider.increment_weeding_kpi('chops') - async def tornado_drill(self, angle: float = 180) -> None: + async def tornado_drill(self, angle: float = 180, turns: float = 2) -> None: self.log.info(f'Drilling with tornado at {angle}...') if not isinstance(self.field_friend.z_axis, Tornado): raise PuncherException('tornado drill is only available for tornado axis') @@ -143,17 +144,15 @@ async def tornado_drill(self, angle: float = 180) -> None: await rosys.sleep(0.5) await self.field_friend.z_axis.move_down_until_reference() - current_angle = self.field_friend.z_axis.position_turn - await self.field_friend.z_axis.turn_by(current_angle-angle) - await rosys.sleep(3) - current_angle = self.field_friend.z_axis.position_turn - await self.field_friend.z_axis.turn_by(current_angle+700) - await rosys.sleep(3) + await self.field_friend.z_axis.turn_knifes_to(angle) + await rosys.sleep(2) + await self.field_friend.z_axis.turn_by(turns) + await rosys.sleep(2) await self.field_friend.z_axis.return_to_reference() await rosys.sleep(0.5) - if not await self.field_friend.z_axis.try_reference_turn(): - raise PuncherException('tornado reference failed') + await self.field_friend.z_axis.turn_knifes_to(0) + await rosys.sleep(0.5) except Exception as e: raise PuncherException(f'tornado drill failed because of: {e}') from e finally: diff --git a/field_friend/automations/weeding.py b/field_friend/automations/weeding.py index f4ec28cf..329f6cbf 100644 --- a/field_friend/automations/weeding.py +++ b/field_friend/automations/weeding.py @@ -35,7 +35,7 @@ def __init__(self, system: 'System') -> None: self.system = system self.kpi_provider = system.kpi_provider - # default settings + # general settings self.continue_canceled_weeding: bool = False self.use_monitor_workflow: bool = False @@ -45,11 +45,17 @@ def __init__(self, system: 'System') -> None: self.start_row_id: Optional[str] = None self.end_row_id: Optional[str] = None self.minimum_turning_radius: float = 0.5 + self.turn_offset: float = 0.4 # workflow settings self.only_monitoring: bool = False + # tornado self.drill_with_open_tornado: bool = False self.drill_between_crops: bool = False + # dual mechanism + self.with_drilling: bool = False + self.with_chopping: bool = False + self.chop_if_no_crops: bool = False # tool settings self.tornado_angle: float = 110.0 @@ -57,6 +63,10 @@ def __init__(self, system: 'System') -> None: self.crop_safety_distance: float = 0.01 # driver settings + self.linear_speed_on_row: float = 0.08 + self.angular_speed_on_row: float = 0.4 + self.linear_speed_between_rows: float = 0.3 + self.angular_speed_between_rows: float = 0.8 self.state: str = 'idle' self.start_time: Optional[float] = None @@ -92,27 +102,55 @@ def _update_time_and_distance(self): self.start_time = rosys.time() def backup(self) -> dict: - return { + dict = { 'use_field_planning': self.use_field_planning, + 'field': rosys.persistence.to_dict(self.field) if self.field else None, 'start_row_id': self.start_row_id, 'end_row_id': self.end_row_id, - 'tornado_angle': self.tornado_angle, 'minimum_turning_radius': self.minimum_turning_radius, + 'turn_offset': self.turn_offset, 'only_monitoring': self.only_monitoring, + 'drill_with_open_tornado': self.drill_with_open_tornado, + 'drill_between_crops': self.drill_between_crops, + 'with_drilling': self.with_drilling, + 'with_chopping': self.with_chopping, + 'chop_if_no_crops': self.chop_if_no_crops, + 'tornado_angle': self.tornado_angle, + 'weed_screw_depth': self.weed_screw_depth, + 'crop_safety_distance': self.crop_safety_distance, + 'linear_speed_on_row': self.linear_speed_on_row, + 'angular_speed_on_row': self.angular_speed_on_row, + 'linear_speed_between_rows': self.linear_speed_between_rows, + 'angular_speed_between_rows': self.angular_speed_between_rows, 'sorted_weeding_rows': self.sorted_weeding_rows, 'weeding_plan': [[rosys.persistence.to_dict(segment) for segment in row] for row in self.weeding_plan] if self.weeding_plan else [], 'turn_paths': [rosys.persistence.to_dict(segment) for segment in self.turn_paths], 'current_row': rosys.persistence.to_dict(self.current_row) if self.current_row else None, 'current_segment': rosys.persistence.to_dict(self.current_segment) if self.current_segment else None, } + self.log.info(f'backing up: {dict}') + return dict def restore(self, data: dict[str, Any]) -> None: - self.use_field_planning = data.get('use_field_planning', False) - self.start_row_id = data.get('start_row_id') - self.end_row_id = data.get('end_row_id') - self.tornado_angle = data.get('tornado_angle', 110.0) - self.minimum_turning_radius = data.get('minimum_turning_radius', 0.5) - self.only_monitoring = data.get('only_monitoring', False) + self.use_field_planning = data.get('use_field_planning', self.use_field_planning) + self.field = rosys.persistence.from_dict(Field, data['field']) if data['field'] else None + self.start_row_id = data.get('start_row_id', self.start_row_id) + self.end_row_id = data.get('end_row_id', self.end_row_id) + self.minimum_turning_radius = data.get('minimum_turning_radius', self.minimum_turning_radius) + self.turn_offset = data.get('turn_offset', self.turn_offset) + self.only_monitoring = data.get('only_monitoring', self.only_monitoring) + self.drill_with_open_tornado = data.get('drill_with_open_tornado', self.drill_with_open_tornado) + self.drill_between_crops = data.get('drill_between_crops', self.drill_between_crops) + self.with_drilling = data.get('with_drilling', self.with_drilling) + self.with_chopping = data.get('with_chopping', self.with_chopping) + self.chop_if_no_crops = data.get('chop_if_no_crops', self.chop_if_no_crops) + self.tornado_angle = data.get('tornado_angle', self.tornado_angle) + self.weed_screw_depth = data.get('weed_screw_depth', self.weed_screw_depth) + self.crop_safety_distance = data.get('crop_safety_distance', self.crop_safety_distance) + self.linear_speed_on_row = data.get('linear_speed_on_row', self.linear_speed_on_row) + self.angular_speed_on_row = data.get('angular_speed_on_row', self.angular_speed_on_row) + self.linear_speed_between_rows = data.get('linear_speed_between_rows', self.linear_speed_between_rows) + self.angular_speed_between_rows = data.get('angular_speed_between_rows', self.angular_speed_between_rows) self.sorted_weeding_rows = data.get('sorted_weeding_rows', []) self.weeding_plan = [ [rosys.persistence.from_dict(PathSegment, segment_data) @@ -126,6 +164,7 @@ def restore(self, data: dict[str, Any]) -> None: 'current_segment']) if data['current_segment'] else None def invalidate(self) -> None: + self.log.info('backing up...') self.request_backup() async def start(self): @@ -136,6 +175,7 @@ async def start(self): if self.use_field_planning and not await self._field_planning(): rosys.notify('Field planning failed', 'negative') return + self.invalidate() await self._weeding() async def _check_hardware_ready(self) -> bool: @@ -191,10 +231,6 @@ async def _field_planning(self) -> bool: self.turn_paths = await self._generate_turn_paths() if not self.turn_paths: self.log.error('No turn paths available') - return False - self.log.info(f'Turn paths: {self.turn_paths}') - self.log.info( - f'Planned path: {[path_segment for path in self.weeding_plan for path_segment in path] + self.turn_paths}') paths = [path_segment for path in self.weeding_plan for path_segment in path] turn_paths = [path_segment for path in self.turn_paths for path_segment in path] self.PATH_PLANNED.emit(paths + turn_paths) @@ -232,14 +268,17 @@ def _make_plan(self) -> Optional[list[list[rosys.driving.PathSegment]]]: distance_to_last_row = min([point.distance(robot_position) for point in rows[-1].points(reference)]) if distance_to_first_row > distance_to_last_row: rows = list(reversed(rows)) - minimum_row_distance = 1 # 1 = no row needs to be skippen when turning + minimum_row_distance = 1 # 1 = no row needs to be skipped when turning if len(rows) > 1: rows_distance = rows[0].points(reference)[0].distance(rows[1].points(reference)[0]) + self.log.info(f'Rows distance: {rows_distance}') + self.log.info(f'Minimum turning radius: {self.minimum_turning_radius}') if self.minimum_turning_radius * 2 > rows_distance: + self.log.info('Rows distance is smaller than minimum turning radius * 2') minimum_row_distance = int( np.ceil(self.minimum_turning_radius * 2 / rows_distance)) - self.log.info(f'Minimum row distance: {minimum_row_distance}') + self.log.info(f'Minimum row distance: {minimum_row_distance} need to skip {minimum_row_distance - 1} rows') if minimum_row_distance > 1: sequence = find_sequence(len(rows), minimum_distance=minimum_row_distance) if not sequence: @@ -294,10 +333,9 @@ async def _generate_turn_paths(self) -> list[list[PathSegment]]: row_points = row.points([self.field.reference_lat, self.field.reference_lon]) # create a small polygon around the row to avoid the robot driving through the row row_polygon = [ - Point(x=row_points[0].x - 0.01, y=row_points[0].y - 0.01), - Point(x=row_points[0].x - 0.01, y=row_points[-1].y + 0.01), - Point(x=row_points[-1].x + 0.01, y=row_points[-1].y + 0.01), - Point(x=row_points[-1].x + 0.01, y=row_points[0].y - 0.01), + Point(x=row_points[0].x, y=row_points[0].y), + Point(x=row_points[0].x - 0.01, y=row_points[0].y + 0.01), + Point(x=row_points[-1].x, y=row_points[-1].y), ] self.system.path_planner.obstacles[f'row_{row.id}'] = rosys.pathplanning.Obstacle( id=f'row_{row.id}', outline=row_polygon) @@ -305,17 +343,23 @@ async def _generate_turn_paths(self) -> list[list[PathSegment]]: area = rosys.pathplanning.Area(id=f'{self.field.id}', outline=self.field.outline) self.system.path_planner.areas = {area.id: area} for i in range(len(self.weeding_plan) - 1): - # remove this rows from obstacles to allow starting in it an insert it afterwards again + # remove the current and the rows from obstacles to allow starting in it an insert it afterwards again start_row = self.sorted_weeding_rows[i] end_row = self.sorted_weeding_rows[i + 1] temp_removed_start_row = self.system.path_planner.obstacles.pop(f'row_{start_row.id}') temp_removed_end_row = self.system.path_planner.obstacles.pop(f'row_{end_row.id}') - start_pose = Pose(x=self.weeding_plan[i][-1].spline.end.x, - y=self.weeding_plan[i][-1].spline.end.y, - yaw=self.weeding_plan[i][-1].spline.start.direction(self.weeding_plan[i][-1].spline.end)) - end_pose = Pose(x=self.weeding_plan[i + 1][0].spline.start.x, - y=self.weeding_plan[i + 1][0].spline.start.y, - yaw=self.weeding_plan[i + 1][0].spline.start.direction(self.weeding_plan[i + 1][0].spline.end)) + start_point = Point(x=self.weeding_plan[i][-1].spline.end.x, + y=self.weeding_plan[i][-1].spline.end.y + ) + yaw = self.weeding_plan[i][-1].spline.start.direction(self.weeding_plan[i][-1].spline.end) + offset_start_point = start_point.polar(self.turn_offset, yaw) + + start_pose = Pose(x=offset_start_point.x, y=offset_start_point.y, yaw=yaw) + end_point = Point(x=self.weeding_plan[i + 1][0].spline.start.x, + y=self.weeding_plan[i + 1][0].spline.start.y) + end_yaw = self.weeding_plan[i + 1][0].spline.start.direction(self.weeding_plan[i + 1][0].spline.end) + offset_end_point = end_point.polar(0.5, yaw) + end_pose = Pose(x=offset_end_point.x, y=offset_end_point.y, yaw=end_yaw) self.log.info(f'Searching path from row {i} to row {i + 1}...') turn_path = await self.system.path_planner.search(start=start_pose, goal=end_pose, timeout=120) if turn_path: @@ -325,6 +369,9 @@ async def _generate_turn_paths(self) -> list[list[PathSegment]]: return [] self.system.path_planner.obstacles[f'row_{start_row.id}'] = temp_removed_start_row self.system.path_planner.obstacles[f'row_{end_row.id}'] = temp_removed_end_row + # # clear all row obstacles + # for row in self.field.rows: + # self.system.path_planner.obstacles.pop(f'row_{row.id}') return turn_paths async def _weeding(self): @@ -373,7 +420,8 @@ async def _weed_with_plan(self): if self.continue_canceled_weeding and self.current_row != self.sorted_weeding_rows[i]: continue self.system.driver.parameters.can_drive_backwards = False - self.system.driver.parameters.minimum_turning_radius = 0.02 + self.system.driver.parameters.linear_speed_limit = self.linear_speed_on_row + self.system.driver.parameters.angular_speed_limit = self.angular_speed_on_row self.current_row = self.sorted_weeding_rows[i] self.system.plant_locator.pause() self.system.plant_provider.clear() @@ -414,6 +462,8 @@ async def _weed_with_plan(self): self.system.plant_locator.pause() if i < len(self.weeding_plan) - 1: self.system.driver.parameters.can_drive_backwards = True + self.system.driver.parameters.linear_speed_limit = self.linear_speed_between_rows + self.system.driver.parameters.angular_speed_limit = self.angular_speed_between_rows self.log.info('Driving to next row...') turn_path = self.turn_paths[i] await self.system.driver.drive_path(turn_path) @@ -469,6 +519,11 @@ async def _check_for_plants(self): if self.system.field_friend.tool in ['tornado', 'none'] or self.use_monitor_workflow: if self.crops_to_handle: return + elif self.system.field_friend.tool == 'dual_mechanism': + if (self.with_drilling and not self.only_monitoring) and (self.crops_to_handle or self.weeds_to_handle): + return + elif self.crops_to_handle: + return else: if self.weeds_to_handle or self.crops_to_handle: return @@ -484,7 +539,7 @@ async def _get_upcoming_plants(self): # Correctly filter to get upcoming crops based on their x position upcoming_crop_positions = { c: pos for c, pos in relative_crop_positions.items() - if self.system.field_friend.WORK_X < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x + if self.system.field_friend.WORK_X+self.system.field_friend.DRILL_RADIUS < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x } else: upcoming_crop_positions = { @@ -505,7 +560,7 @@ async def _get_upcoming_plants(self): # Filter to get upcoming weeds based on their .x position upcoming_weed_positions = { w: pos for w, pos in relative_weed_positions.items() - if self.system.field_friend.WORK_X < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x + if self.system.field_friend.WORK_X+self.system.field_friend.DRILL_RADIUS < pos.x <= self.system.odometer.prediction.relative_point(self.current_segment.spline.end).x } else: upcoming_weed_positions = { @@ -527,7 +582,9 @@ async def _handle_plants(self) -> None: await self._tornado_workflow() elif self.system.field_friend.tool == 'weed_screw' and not self.use_monitor_workflow: await self._weed_screw_workflow() - elif self.system.field_friend.tool == 'dual_mechanism' and not self.use_monitor_workflow: + elif self.system.field_friend.tool == 'dual_mechanism' and not self.use_monitor_workflow and self.crops_to_handle: + await self._dual_mechanism_workflow() + elif self.system.field_friend.tool == 'dual_mechanism' and not self.use_monitor_workflow and self.with_drilling and not self.only_monitoring: await self._dual_mechanism_workflow() elif self.system.field_friend.tool == 'none' or self.use_monitor_workflow: await self._monitor_workflow() @@ -560,7 +617,7 @@ async def _tornado_workflow(self) -> None: target = closest_crop_position.x + distance_to_next_crop / 2 self.log.info(f'driving to position between two crops: {target}') if not self.only_monitoring: - # punch in the middle position with closed knives + # punch in the middle position with closed knifes await self.system.puncher.drive_and_punch(target, 0, angle=180) else: drive_distance = target - self.system.field_friend.WORK_X @@ -640,60 +697,64 @@ async def _dual_mechanism_workflow(self) -> None: if self.crops_to_handle: next_crop_position = list(self.crops_to_handle.values())[0] # first: check if weeds near crop - self._keep_crops_safe() - weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if next_crop_position.x - self.system.field_friend.DRILL_RADIUS*2 - < position.x < next_crop_position.x + self.system.field_friend.DRILL_RADIUS*2 and self.system.field_friend.can_reach(position)} - self.log.info(f'weed_position in range: {weeds_in_range.items()}') - if weeds_in_range: - self.log.info(f' {len(weeds_in_range)} Weeds in range for drilling') - while weeds_in_range: - next_weed_position = list(weeds_in_range.values())[0] - self.log.info(f'Next weed position: {next_weed_position}') - weed_world_position = starting_position.transform(next_weed_position) - corrected_relative_weed_position = self.system.odometer.prediction.relative_point( - weed_world_position) - self.log.info(f'corrected relative weed position: {corrected_relative_weed_position}') - moved = True - if not self.only_monitoring: - await self.system.puncher.drive_and_punch( - corrected_relative_weed_position.x, next_weed_position.y, depth=self.weed_screw_depth, backwards_allowed=False) - punched_weeds = [weed_id for weed_id, position in weeds_in_range.items( - ) if position.distance(next_weed_position) < self.system.field_friend.DRILL_RADIUS] - for weed_id in punched_weeds: - self.system.plant_provider.remove_weed(weed_id) - if weed_id in weeds_in_range: - del weeds_in_range[weed_id] - self.kpi_provider.increment_weeding_kpi('weeds_removed') - await self.system.puncher.clear_view() - # second: check if weed before crop - weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if position.x < next_crop_position.x - ( - self.system.field_friend.DRILL_RADIUS) and self.system.field_friend.can_reach(position, second_tool=True)} - if weeds_in_range: - self.log.info('Weeds in range for chopping before crop') - crop_world_position = starting_position.transform(next_crop_position) - corrected_relative_crop_position = self.system.odometer.prediction.relative_point( - crop_world_position) - target_position = corrected_relative_crop_position.x - \ - self.system.field_friend.DRILL_RADIUS - self.system.field_friend.CHOP_RADIUS - axis_distance = target_position - self.system.field_friend.WORK_X_CHOP - if axis_distance >= 0: - local_target = Point(x=axis_distance, y=0) - world_target = self.system.driver.prediction.transform(local_target) + if self.with_chopping: + self.log.info(f'Drilling allowed: only chopping is {self.with_chopping}') + self._keep_crops_safe() + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if next_crop_position.x - self.system.field_friend.DRILL_RADIUS*2 + < position.x < next_crop_position.x + self.system.field_friend.DRILL_RADIUS*2 and self.system.field_friend.can_reach(position)} + self.log.info(f'weed_position in range: {weeds_in_range.items()}') + if weeds_in_range: + self.log.info(f' {len(weeds_in_range)} Weeds in range for drilling') + while weeds_in_range: + next_weed_position = list(weeds_in_range.values())[0] + self.log.info(f'Next weed position: {next_weed_position}') + weed_world_position = starting_position.transform(next_weed_position) + corrected_relative_weed_position = self.system.odometer.prediction.relative_point( + weed_world_position) + self.log.info(f'corrected relative weed position: {corrected_relative_weed_position}') + moved = True + if not self.only_monitoring: + await self.system.puncher.drive_and_punch( + corrected_relative_weed_position.x, next_weed_position.y, depth=self.weed_screw_depth, backwards_allowed=False) + punched_weeds = [weed_id for weed_id, position in weeds_in_range.items( + ) if position.distance(next_weed_position) < self.system.field_friend.DRILL_RADIUS] + for weed_id in punched_weeds: + self.system.plant_provider.remove_weed(weed_id) + if weed_id in weeds_in_range: + del weeds_in_range[weed_id] + self.kpi_provider.increment_weeding_kpi('weeds_removed') + await self.system.puncher.clear_view() + # second: check if weed before crop to chop + if self.with_drilling: + self.log.info(f'Chopping allowed: only drilling is {self.with_drilling}') + weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if position.x < next_crop_position.x - ( + self.system.field_friend.DRILL_RADIUS) and self.system.field_friend.can_reach(position, second_tool=True)} + if weeds_in_range: + self.log.info('Weeds in range for chopping before crop') + crop_world_position = starting_position.transform(next_crop_position) + corrected_relative_crop_position = self.system.odometer.prediction.relative_point( + crop_world_position) + target_position = corrected_relative_crop_position.x - \ + self.system.field_friend.DRILL_RADIUS - self.system.field_friend.CHOP_RADIUS + axis_distance = target_position - self.system.field_friend.WORK_X_CHOP + if axis_distance >= 0: + local_target = Point(x=axis_distance, y=0) + world_target = self.system.driver.prediction.transform(local_target) + moved = True + await self.system.driver.drive_to(world_target) + if not self.only_monitoring: + await self.system.puncher.chop() + choped_weeds = [weed_id for weed_id, position in self.weeds_to_handle.items( + ) if target_position - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < target_position + self.system.field_friend.CHOP_RADIUS] + for weed_id in choped_weeds: + self.system.plant_provider.remove_weed(weed_id) + self.kpi_provider.increment_weeding_kpi('weeds_removed') + else: + self.log.warning(f'Weed position {next_weed_position} is behind field friend') + if not moved: + await self._follow_line_of_crops() moved = True - await self.system.driver.drive_to(world_target) - if not self.only_monitoring: - await self.system.puncher.chop() - choped_weeds = [weed_id for weed_id, position in self.weeds_to_handle.items( - ) if target_position - self.system.field_friend.CHOP_RADIUS < self.system.odometer.prediction.relative_point(starting_position.transform(position)).x < target_position + self.system.field_friend.CHOP_RADIUS] - for weed_id in choped_weeds: - self.system.plant_provider.remove_weed(weed_id) - self.kpi_provider.increment_weeding_kpi('weeds_removed') - else: - self.log.warning(f'Weed position {next_weed_position} is behind field friend') - if not moved: - await self._follow_line_of_crops() - moved = True - elif self.weeds_to_handle: + elif self.weeds_to_handle and self.chop_if_no_crops: self.log.info('No crops in range, checking for weeds...') weeds_in_range = {weed_id: position for weed_id, position in self.weeds_to_handle.items() if self.system.field_friend.WORK_X_CHOP < position.x < self.system.field_friend.WORK_X + self.WORKING_DISTANCE and self.system.field_friend.can_reach(position)} diff --git a/field_friend/hardware/field_friend_hardware.py b/field_friend/hardware/field_friend_hardware.py index 3c3e562a..b5bfe267 100644 --- a/field_friend/hardware/field_friend_hardware.py +++ b/field_friend/hardware/field_friend_hardware.py @@ -37,6 +37,7 @@ def __init__(self) -> None: self.WHEEL_DIAMETER: float = self.THOOTH_COUNT * self.PITCH / np.pi self.M_PER_TICK: float = self.WHEEL_DIAMETER * np.pi / self.MOTOR_GEAR_RATIO self.WHEEL_DISTANCE: float = config_params['wheel_distance'] + self.ANTENNA_OFFSET: float = config_params['antenna_offset'] tool: str = config_params['tool'] if tool in ['tornado', 'weed_screw', 'none']: self.WORK_X: float = config_params['work_x'] @@ -181,8 +182,8 @@ def __init__(self) -> None: end_bottom_pin=config_hardware['z_axis']['end_bottom_pin'], ref_motor_pin=config_hardware['z_axis']['ref_motor_pin'], ref_gear_pin=config_hardware['z_axis']['ref_gear_pin'], - ref_t_pin=config_hardware['z_axis']['ref_t_pin'], - ref_b_pin=config_hardware['z_axis']['ref_b_pin'], + ref_knife_stop_pin=config_hardware['z_axis']['ref_knife_stop_pin'], + ref_knife_ground_pin=config_hardware['z_axis']['ref_knife_ground_pin'], motors_on_expander=config_hardware['z_axis']['motors_on_expander'], end_stops_on_expander=config_hardware['z_axis']['end_stops_on_expander'], is_z_reversed=config_hardware['z_axis']['is_z_reversed'], @@ -190,6 +191,8 @@ def __init__(self) -> None: speed_limit=config_hardware['z_axis']['speed_limit'], turn_speed_limit=config_hardware['z_axis']['turn_speed_limit'], current_limit=config_hardware['z_axis']['current_limit'], + z_reference_speed=config_hardware['z_axis']['z_reference_speed'], + turn_reference_speed=config_hardware['z_axis']['turn_reference_speed'], ) elif config_hardware['z_axis']['version'] == 'z_axis_canopen': z_axis = ZAxisCanOpenHardware(robot_brain, diff --git a/field_friend/hardware/safety.py b/field_friend/hardware/safety.py index f1fdd69f..4ae67015 100644 --- a/field_friend/hardware/safety.py +++ b/field_friend/hardware/safety.py @@ -84,9 +84,9 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, lizard_code += f'when {y_axis.name}_ref_t.level == 1 then {wheels.name}.speed(0, 0); end\n' if isinstance(z_axis, TornadoHardware): if isinstance(y_axis, YAxisStepperHardware): - lizard_code += f'when {z_axis.name}_ref_knive_ground.level == 1 then {wheels.name}.speed(0, 0); {y_axis.name}.stop(); end\n' + lizard_code += f'when {z_axis.name}_ref_knife_ground.level == 1 then {wheels.name}.speed(0, 0); {y_axis.name}.stop(); end\n' elif isinstance(y_axis, YAxisCanOpenHardware): - lizard_code += f'when {z_axis.name}_ref_knive_ground.level == 1 then {wheels.name}.speed(0, 0); {y_axis.name}_motor.set_ctrl_enable(false); end\n' + lizard_code += f'when {z_axis.name}_ref_knife_ground.level == 1 then {wheels.name}.speed(0, 0); {y_axis.name}_motor.set_ctrl_enable(false); end\n' # implement watchdog for rosys modules lizard_code += f'when core.last_message_age > 1000 then {wheels.name}.speed(0, 0); end\n' diff --git a/field_friend/hardware/tornado.py b/field_friend/hardware/tornado.py index 2411db28..3e9a7faf 100644 --- a/field_friend/hardware/tornado.py +++ b/field_friend/hardware/tornado.py @@ -14,6 +14,8 @@ def __init__(self, min_position, **kwargs) -> None: self.position_z: float = 0.0 self.position_turn: float = 0.0 + self.last_angle: float = 0.0 + self.is_referenced: bool = False self.z_is_referenced: bool = False self.turn_is_referenced: bool = False @@ -23,8 +25,8 @@ def __init__(self, min_position, **kwargs) -> None: self.ref_motor: bool = False self.ref_gear: bool = False - self.ref_t: bool = False - self.ref_b: bool = False + self.ref_knife_stop: bool = False + self.ref_knife_ground: bool = False rosys.on_shutdown(self.stop) @@ -53,7 +55,12 @@ async def move_down_until_reference(self) -> None: raise RuntimeError('zaxis is not referenced, reference first') @abc.abstractmethod - async def turn_by(self, angle: float) -> None: + async def turn_by(self, turns: float) -> None: + if not self.turn_is_referenced: + raise RuntimeError('zaxis is not referenced, reference first') + + @abc.abstractmethod + async def turn_knifes_to(self, angle: float) -> None: if not self.turn_is_referenced: raise RuntimeError('zaxis is not referenced, reference first') @@ -99,8 +106,8 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, end_bottom_pin: int = 5, ref_motor_pin: int = 33, ref_gear_pin: int = 4, - ref_t_pin: int = 35, - ref_b_pin: int = 18, + ref_knife_stop_pin: int = 35, + ref_knife_ground_pin: int = 18, motors_on_expander: bool = False, end_stops_on_expander: bool = True, is_z_reversed: bool = False, @@ -108,12 +115,16 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, speed_limit: int = 1, turn_speed_limit: int = 1, current_limit: int = 20, + z_reference_speed: float = 0.0075, + turn_reference_speed: float = 0.25, ) -> None: self.name = name self.expander = expander self.speed_limit = speed_limit self.turn_speed_limit = turn_speed_limit self.current_limit = current_limit + self.z_reference_speed = z_reference_speed + self.turn_reference_speed = turn_reference_speed lizard_code = remove_indentation(f''' {name}_z = {expander.name + "." if motors_on_expander and expander else ""}ODriveMotor({can.name}, {z_can_address}) @@ -128,8 +139,8 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, {name}_end_bottom = {expander.name + "." if end_stops_on_expander and expander else ""}Input({end_bottom_pin}) {name}_ref_motor = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_motor_pin}) {name}_ref_gear = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_gear_pin}) - {name}_ref_t = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_t_pin}) - {name}_ref_b = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_b_pin}) + {name}_ref_knife_stop = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_knife_stop_pin}) + {name}_ref_knife_ground = {expander.name + "." if end_stops_on_expander and expander else ""}Input({ref_knife_ground_pin}) bool {name}_is_referencing = false; bool {name}_end_top_enabled = true; bool {name}_end_bottom_enabled = true; @@ -150,14 +161,14 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, when {name}_ref_gear_enabled and {name}_is_referencing and {name}_ref_gear.level == 1 then {name}_turn.speed(0); end - bool {name}_knive_ground_enabled = false; - bool {name}_knive_stop_enabled = false; - when {name}_knive_ground_enabled and {name}_ref_b.level == 1 then + bool {name}_knife_ground_enabled = false; + bool {name}_knife_stop_enabled = false; + when {name}_knife_ground_enabled and {name}_ref_knife_ground.level == 1 then {name}_z.off(); end - when {name}_knive_stop_enabled and {name}_ref_t.level == 1 then + when {name}_knife_stop_enabled and {name}_ref_knife_stop.level == 1 then en3.off(); - {name}_knive_stop_enabled = false; + {name}_knife_stop_enabled = false; end ''') core_message_fields = [ @@ -165,8 +176,8 @@ def __init__(self, robot_brain: rosys.hardware.RobotBrain, *, f'{name}_end_bottom.level', f'{name}_ref_motor.level', f'{name}_ref_gear.level', - f'{name}_ref_t.level', - f'{name}_ref_b.level', + f'{name}_ref_knife_stop.level', + f'{name}_ref_knife_ground.level', f'{name}_z.position:3', f'{name}_turn.position:3', ] @@ -191,6 +202,8 @@ async def move_to(self, position: float) -> None: except RuntimeError as e: raise Exception(e) from e await self.robot_brain.send(f'{self.name}_z.position({position});') + while not position - 0.005 < self.position_z < position + 0.005: + await rosys.sleep(0.1) async def move_down_until_reference(self) -> None: try: @@ -200,24 +213,24 @@ async def move_down_until_reference(self) -> None: try: self.log.info('moving z axis down') await self.robot_brain.send( - f'{self.name}_knive_stop_enabled = true;' - f'{self.name}_knive_ground_enabled = true;' + f'{self.name}_knife_stop_enabled = true;' + f'{self.name}_knife_ground_enabled = true;' ) await rosys.sleep(0.5) await self.robot_brain.send( f'{self.name}_z.position({self.min_position});' ) await rosys.sleep(0.5) - while self.ref_b and not self.ref_t: + while self.ref_knife_ground and not self.ref_knife_stop: if self.min_position - 0.005 <= self.position_z <= self.min_position + 0.005: self.log.info('minimum position reached') break self.log.info('moving z axis down') await rosys.sleep(0.1) - if self.ref_t: - raise Exception('Error while moving z axis down: Ref Top is active') - if not self.ref_b: - self.log.info('bottom ref reached') + if self.ref_knife_stop: + raise Exception('Error while moving z axis down: Ref knifes stop triggered') + if not self.ref_knife_ground: + self.log.info('Ref ground triggered: Bottom ground reached') except Exception as e: self.log.error(f'error while moving z axis down: {e}') self.is_referenced = False @@ -229,16 +242,35 @@ async def move_down_until_reference(self) -> None: self.log.info('finalizing moving z axis down') await self.robot_brain.send( f'{self.name}_z.speed(0);' - f'{self.name}_knive_stop_enabled = false;' - f'{self.name}_knive_ground_enabled = false;' + f'{self.name}_knife_stop_enabled = false;' + f'{self.name}_knife_ground_enabled = false;' ) - async def turn_by(self, angle: float) -> None: + async def turn_by(self, turns: float) -> None: try: - await super().turn_by(angle) + await super().turn_by(turns) + except RuntimeError as e: + raise Exception(e) + target = self.position_turn/360 + turns + await self.robot_brain.send(f'{self.name}_turn.position({target});') + while not target - 0.01 < self.position_turn/360 < target + 0.01: + await rosys.sleep(0.1) + + async def turn_knifes_to(self, angle: float) -> None: + try: + await super().turn_knifes_to(angle) except RuntimeError as e: raise Exception(e) from e - await self.robot_brain.send(f'{self.name}_turn.position({angle/360});') + target_angle = angle - self.last_angle + if target_angle == 0: + return + elif target_angle < 0: + target_angle = 360 - self.last_angle + angle + target = (self.position_turn - target_angle)/360 + await self.robot_brain.send(f'{self.name}_turn.position({target});') + while not target - 0.01 < self.position_turn/360 < target + 0.01: + await rosys.sleep(0.1) + self.last_angle = angle async def try_reference_z(self) -> bool: if not await super().try_reference_z(): @@ -264,11 +296,11 @@ async def try_reference_z(self) -> bool: await self.robot_brain.send(f'{self.name}_end_top_enabled = true;') await rosys.sleep(1) await self.robot_brain.send( - f'{self.name}_z.speed({0.005*self.speed_limit});' + f'{self.name}_z.speed({self.z_reference_speed});' ) await rosys.sleep(0.2) while not self.end_top: - await self.robot_brain.send(f'{self.name}_z.speed({0.005*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({self.z_reference_speed});') await rosys.sleep(0.2) await self.robot_brain.send(f'{self.name}_z.speed(0);') @@ -276,10 +308,10 @@ async def try_reference_z(self) -> bool: self.log.info('moving out of end top') await self.robot_brain.send(f'{self.name}_end_top_enabled = false;') await rosys.sleep(1) - await self.robot_brain.send(f'{self.name}_z.speed({-0.005*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({-self.z_reference_speed});') await rosys.sleep(0.2) while self.end_top: - await self.robot_brain.send(f'{self.name}_z.speed({-0.005*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({-self.z_reference_speed});') await rosys.sleep(0.2) await self.robot_brain.send(f'{self.name}_z.speed(0);') @@ -287,10 +319,10 @@ async def try_reference_z(self) -> bool: self.log.info('moving slowly to end top') await self.robot_brain.send(f'{self.name}_end_top_enabled = true;') await rosys.sleep(1) - await self.robot_brain.send(f'{self.name}_z.speed({0.001*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({self.z_reference_speed/5});') await rosys.sleep(0.2) while not self.end_top: - await self.robot_brain.send(f'{self.name}_z.speed({0.001*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({self.z_reference_speed/5});') await rosys.sleep(0.2) await self.robot_brain.send(f'{self.name}_z.speed(0);') @@ -298,10 +330,10 @@ async def try_reference_z(self) -> bool: self.log.info('moving slowly out of end top') await self.robot_brain.send(f'{self.name}_end_top_enabled = false;') await rosys.sleep(1) - await self.robot_brain.send(f'{self.name}_z.speed({-0.001*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({-self.z_reference_speed/5});') await rosys.sleep(0.2) while self.end_top: - await self.robot_brain.send(f'{self.name}_z.speed({-0.001*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_z.speed({-self.z_reference_speed/5});') await rosys.sleep(0.2) await self.robot_brain.send(f'{self.name}_z.speed(0);') @@ -342,21 +374,21 @@ async def try_reference_turn(self) -> bool: await self.robot_brain.send(f'{self.name}_ref_gear_enabled = true;') await rosys.sleep(1) await self.robot_brain.send( - f'{self.name}_turn.speed({0.18*self.speed_limit});' + f'{self.name}_turn.speed({self.turn_reference_speed});' ) await rosys.sleep(0.2) while not self.ref_gear: - await self.robot_brain.send(f'{self.name}_turn.speed({0.18*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_turn.speed({self.turn_reference_speed});') await rosys.sleep(0.1) await self.robot_brain.send(f'{self.name}_turn.speed(0);') # drive to motor ref await self.robot_brain.send(f'{self.name}_ref_motor_enabled = true;') await rosys.sleep(1) - await self.robot_brain.send(f'{self.name}_turn.move({-0.18*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_turn.move({-self.turn_reference_speed});') await rosys.sleep(0.2) while not self.ref_motor: - await self.robot_brain.send(f'{self.name}_turn.speed({-0.18*self.speed_limit});') + await self.robot_brain.send(f'{self.name}_turn.speed({-self.turn_reference_speed});') await rosys.sleep(0.1) await self.robot_brain.send(f'{self.name}_turn.speed(0);') @@ -370,6 +402,7 @@ async def try_reference_turn(self) -> bool: await rosys.sleep(0.2) self.log.info('referencing tornado turn position done') self.turn_is_referenced = True + self.last_angle = 0 return True except Exception as e: self.log.error(f'error while referencing tornado turn position: {e}') @@ -388,8 +421,8 @@ def handle_core_output(self, time: float, words: list[str]) -> None: self.is_referenced = False self.ref_motor = int(words.pop(0)) == 0 self.ref_gear = int(words.pop(0)) == 1 - self.ref_t = int(words.pop(0)) == 1 - self.ref_b = int(words.pop(0)) == 0 + self.ref_knife_stop = int(words.pop(0)) == 1 + self.ref_knife_ground = int(words.pop(0)) == 0 self.position_z = float(words.pop(0)) self.position_turn = float(words.pop(0)) * 360 @@ -436,6 +469,20 @@ async def turn_by(self, angle: float) -> None: raise Exception(e) from e self.position_turn = angle + async def turn_knifes_to(self, angle: float) -> None: + try: + await super().turn_knifes_to(angle) + except RuntimeError as e: + raise Exception(e) from e + self.position_turn = angle + + async def turn_knifes_to(self, angle: float) -> None: + try: + await super().turn_knifes_to(angle) + except RuntimeError as e: + raise Exception(e) + self.position_turn = angle + async def try_reference_z(self) -> bool: if not await super().try_reference_z(): return False diff --git a/field_friend/interface/components/field_friend_object.py b/field_friend/interface/components/field_friend_object.py index d9357fe9..0aa0e080 100644 --- a/field_friend/interface/components/field_friend_object.py +++ b/field_friend/interface/components/field_friend_object.py @@ -58,4 +58,4 @@ def update(self) -> None: else: difference = self.robot.y_axis.MIN_POSITION - self.robot.y_axis.position self.tool.move(x=self.robot.WORK_X_CHOP, y=self.robot.y_axis.MIN_POSITION + difference) - self.second_tool.move(x=self.robot.WORK_X_DRILL, y=self.robot.y_axis.MIN_POSITION + difference) + self.second_tool.move(x=self.robot.WORK_X, y=self.robot.y_axis.MIN_POSITION + difference) diff --git a/field_friend/interface/components/field_planner.py b/field_friend/interface/components/field_planner.py index e9bcbefb..e80a623c 100644 --- a/field_friend/interface/components/field_planner.py +++ b/field_friend/interface/components/field_planner.py @@ -1,4 +1,3 @@ -import json import logging import uuid from typing import Literal, Optional @@ -6,8 +5,6 @@ import rosys from nicegui import events, ui -from field_friend.navigation.point_transformation import cartesian_to_wgs84, wgs84_to_cartesian - from ...automations import Field, FieldObstacle, FieldProvider, Row from ...navigation import Gnss from .geodata_picker import geodata_picker diff --git a/field_friend/interface/components/operation.py b/field_friend/interface/components/operation.py index bbb5d210..2f91efb2 100644 --- a/field_friend/interface/components/operation.py +++ b/field_friend/interface/components/operation.py @@ -71,31 +71,103 @@ def show_field_selection() -> None: 'w-24').bind_value(system.mowing, 'lane_distance').tooltip('Set the lane distance for the system. automation') ui.number('Number of outer lanes', value=3, step=1, min=3, format='%.0f').props('dense outlined').classes( 'w-28').bind_value(system.mowing, 'number_of_outer_lanes').tooltip('Set the number of outer lanes for the mowing automation') - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.1, min=0.1, max=1.0).props( + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.1, max=2.0).props( 'dense outlined suffix=m').classes('w-32').bind_value( - self.system.mowing, 'turning_radius').tooltip( + self.system.mowing, 'minimum_turning_radius').tooltip( 'Set the turning radius for the mowing automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='weeding'): - with ui.column(): + ui.separator() + ui.markdown('Field settings').style('color: #6E93D6') + with ui.row(): self.with_field_planning = ui.checkbox('Use field planning', value=True).bind_value( self.system.weeding, 'use_field_planning').tooltip('Set the weeding automation to use the field planning with GNSS') with ui.row().bind_visibility_from(self.with_field_planning, 'value', value=True): self.show_start_row() self.show_end_row() - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.1, min=0.1, max=1.0).props( + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0).props( 'dense outlined suffix=m').classes('w-30').bind_value( - self.system.weeding, 'turning_radius').tooltip( + self.system.weeding, 'minimum_turning_radius').tooltip( 'Set the turning radius for the weeding automation') - with ui.row(): + ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0).props( + 'dense outlined suffix=m').classes('w-30').bind_value( + self.system.weeding, 'turn_offset').tooltip( + 'Set the turning offset for the weeding automation') + ui.separator() + ui.markdown('Detector settings').style('color: #6E93D6') + with ui.row(): + ui.number('Min. weed confidence', format='%.2f', value=0.8, step=0.05, min=0.0, max=1.0).props( + 'dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_weed_confidence').tooltip( + 'Set the minimum weed confidence for the weeding automation') + ui.number('Min. crop confidence', format='%.2f', value=0.4, step=0.05, min=0.0, max=1.0).props( + 'dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_crop_confidence').tooltip( + 'Set the minimum crop confidence for the weeding automation') + ui.separator() + ui.markdown('Tool settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': ui.number('Tornado angle', format='%.0f', value=180, step=1, min=1, max=180).props( 'dense outlined suffix=°').classes('w-24').bind_value( self.system.weeding, 'tornado_angle').tooltip( 'Set the angle for the tornado drill') - ui.checkbox('Only monitoring').bind_value( - self.system.weeding, 'only_monitoring').tooltip( - 'Set the weeding automation to only monitor the field') + elif self.system.field_friend.tool in ['weed_screw', 'dual_mechanism']: + ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1).props( + 'dense outlined suffix=°').classes('w-24').bind_value( + self.system.weeding, 'weed_screw_depth').tooltip( + 'Set the drill depth for the weeding automation') + ui.number('Crop safety distance', value=0.01, step=0.01, min=0.0, max=0.05, format='%.2f').props( + 'dense outlined suffix=m').classes('w-24').bind_value( + self.system.weeding, 'crop_safety_distance').tooltip( + 'Set the crop safety distance for the weeding automation') + + ui.separator() + ui.markdown('Workflow settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': + ui.checkbox('Drill 2x with open torando', value=False, on_change=system.weeding.invalidate).bind_value( + self.system.weeding, 'drill_with_open_tornado').tooltip( + 'Set the weeding automation to drill a second time with open tornado') + ui.checkbox('Drill between crops', value=False).bind_value( + self.system.weeding, 'drill_between_crops').tooltip( + 'Set the weeding automation to drill between crops') + elif self.system.field_friend.tool == 'dual_mechanism': + + ui.checkbox('Drilling', value=False).bind_value( + self.system.weeding, 'with_drilling').tooltip( + 'Set the weeding automation to with drill') + ui.checkbox('Chopping', value=False).bind_value( + self.system.weeding, 'with_chopping').tooltip( + 'Set the weeding automation to with chop') + + ui.checkbox('Chop if no crops', value=False).bind_value( + self.system.weeding, 'chop_if_no_crops').tooltip( + 'Set the weeding automation to chop also if no crops seen') + + ui.checkbox('Only monitoring').bind_value( + self.system.weeding, 'only_monitoring').tooltip( + 'Set the weeding automation to only monitor the field') + ui.separator() + ui.markdown('**Driver settings**').style('color: #6E93D6') + with ui.row(): + ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f').props( + 'dense outlined suffix=m/s').classes('w-24').bind_value( + self.system.weeding, 'linear_speed_on_row').tooltip( + 'Set the linear speed on row for the weeding automation') + ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f').props( + 'dense outlined suffix=m/s').classes('w-24').bind_value( + self.system.weeding, 'linear_speed_between_rows').tooltip( + 'Set the linear speed between rows for the weeding automation') + ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f').props( + 'dense outlined suffix=°/s').classes('w-24').bind_value( + self.system.weeding, 'angular_speed_on_row').tooltip( + 'Set the angular speed on row for the weeding automation') + ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f').props( + 'dense outlined suffix=°/s').classes('w-24').bind_value( + self.system.weeding, 'angular_speed_between_rows').tooltip( + 'Set the angular speed between rows for the weeding automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='monitoring'): with ui.column(): @@ -105,9 +177,9 @@ def show_field_selection() -> None: with ui.row().bind_visibility_from(self.with_field_planning_monitor, 'value', value=True): self.show_start_row() self.show_end_row() - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.1, min=0.1, max=1.0).props( + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0).props( 'dense outlined suffix=m').classes('w-30').bind_value( - self.system.monitoring, 'turning_radius').tooltip( + self.system.monitoring, 'minimum_turning_radius').tooltip( 'Set the turning radius for the monitoring automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='collecting (demo)'): diff --git a/field_friend/interface/components/plant_object.py b/field_friend/interface/components/plant_object.py index 4f2c02bd..2dc78bc2 100644 --- a/field_friend/interface/components/plant_object.py +++ b/field_friend/interface/components/plant_object.py @@ -1,8 +1,10 @@ import logging + from nicegui.elements.scene_objects import Group, Sphere from ...automations import PlantProvider + class plant_objects(Group): def __init__(self, plant_provider: PlantProvider, weed_category_names: list[str]) -> None: @@ -23,6 +25,11 @@ def update(self) -> None: obj.delete() for id, plant in in_world.items(): if id not in rendered: - Sphere(0.02).with_name(f'plant_{plant.type}:{id}') \ - .material('#ef1208' if plant.type in self.weed_category_names else '#11ede3') \ - .move(plant.position.x, plant.position.y, 0.02) + if plant.type in self.weed_category_names: + Sphere(0.02).with_name(f'plant_{plant.type}:{id}') \ + .material('#ef1208') \ + .move(plant.position.x, plant.position.y, 0.02) + else: + Sphere(0.035).with_name(f'plant_{plant.type}:{id}') \ + .material('#11ede3') \ + .move(plant.position.x, plant.position.y, 0.035) diff --git a/field_friend/interface/components/status_dev.py b/field_friend/interface/components/status_dev.py index 70a09485..0e7e068a 100644 --- a/field_friend/interface/components/status_dev.py +++ b/field_friend/interface/components/status_dev.py @@ -215,8 +215,8 @@ def update_status() -> None: 'end_bottom' if robot.z_axis.end_bottom else '', 'ref_motor' if robot.z_axis.ref_motor else '', 'ref_gear' if robot.z_axis.ref_gear else '', - 'ref_t' if robot.z_axis.ref_t else '', - 'ref_b' if robot.z_axis.ref_b else '', + 'ref_knife_stop' if robot.z_axis.ref_knife_stop else '', + 'ref_knife_ground' if robot.z_axis.ref_knife_ground else '', f'{robot.z_axis.position_z:.2f}m' if robot.z_axis.z_is_referenced else '', f'{robot.z_axis.position_turn:.2f}°' if robot.z_axis.turn_is_referenced else '', ] @@ -273,10 +273,10 @@ def get_jetson_cpu_temperature(): if current_automation == 'weeding' or current_automation == 'monitoring': if current_automation == 'weeding': current_row_label.text = system.weeding.current_row.name if system.weeding.current_row is not None else 'No row' - worked_area_label.text = f'{system.weeding.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.weeding.field.area:.2f}m²' if system.weeding.field is not None else 'No field' + worked_area_label.text = f'{system.weeding.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.weeding.field.area():.2f}m²' if system.weeding.field is not None else 'No field' elif current_automation == 'monitoring': current_row_label.text = system.monitoring.current_row.name if system.monitoring.current_row is not None else 'No row' - worked_area_label.text = f'{system.monitoring.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.monitoring.field.area:.2f}m²' if system.monitoring.field is not None else 'No field' + worked_area_label.text = f'{system.monitoring.field.worked_area(system.kpi_provider.current_weeding_kpis.rows_weeded):.2f}m²/{system.monitoring.field.area():.2f}m²' if system.monitoring.field is not None else 'No field' kpi_weeds_detected_label.text = system.kpi_provider.current_weeding_kpis.weeds_detected kpi_crops_detected_label.text = system.kpi_provider.current_weeding_kpis.crops_detected kpi_weeds_removed_label.text = system.kpi_provider.current_weeding_kpis.weeds_removed diff --git a/field_friend/interface/components/status_drawer.py b/field_friend/interface/components/status_drawer.py index b9c2e781..a7868cfe 100644 --- a/field_friend/interface/components/status_drawer.py +++ b/field_friend/interface/components/status_drawer.py @@ -179,12 +179,12 @@ def update_status() -> None: '' if robot.z_axis.is_referenced else 'not referenced', '' if robot.z_axis.z_is_referenced else 'z not referenced', '' if robot.z_axis.turn_is_referenced else 'turn not referenced', - 'end_top' if robot.z_axis.end_top else '', - 'end_bottom' if robot.z_axis.end_bottom else '', + 'top' if robot.z_axis.end_top else '', + 'bottom' if robot.z_axis.end_bottom else '', 'ref_motor' if robot.z_axis.ref_motor else '', 'ref_gear' if robot.z_axis.ref_gear else '', - 'ref_t' if robot.z_axis.ref_t else '', - 'ref_b' if robot.z_axis.ref_b else '', + 'knife_stop' if robot.z_axis.ref_knife_stop else '', + 'knife_ground' if robot.z_axis.ref_knife_ground else '', f'{robot.z_axis.position_z:.2f}m' if robot.z_axis.z_is_referenced else '', f'{robot.z_axis.position_turn:.2f}°' if robot.z_axis.turn_is_referenced else '', ] diff --git a/field_friend/navigation/gnss.py b/field_friend/navigation/gnss.py index 457595ee..4478a769 100644 --- a/field_friend/navigation/gnss.py +++ b/field_friend/navigation/gnss.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from copy import deepcopy from dataclasses import dataclass -from typing import Any, Optional, Protocol +from typing import Optional, Protocol import numpy as np import pynmea2 @@ -14,7 +14,7 @@ from geographiclib.geodesic import Geodesic from serial.tools import list_ports -from field_friend.navigation.point_transformation import cartesian_to_wgs84, wgs84_to_cartesian +from field_friend.navigation.point_transformation import cartesian_to_wgs84, get_new_position, wgs84_to_cartesian @dataclass @@ -30,7 +30,7 @@ class GNSSRecord: speed_kmh: float = 0.0 -class Gnss(rosys.persistence.PersistentModule, ABC): +class Gnss(ABC): def __init__(self) -> None: super().__init__() @@ -58,26 +58,6 @@ def __init__(self) -> None: rosys.on_repeat(self.update, 0.01) rosys.on_repeat(self.try_connection, 3.0) - def backup(self) -> dict: - return { - 'record': rosys.persistence.to_dict(self.record) - } - - def restore(self, data: dict[str, Any]) -> None: - record = data.get('record') - if not isinstance(record, dict): - self.log.error('No record data found') - return - self.record.timestamp = record["timestamp"] - self.record.latitude = record["latitude"] - self.record.longitude = record["longitude"] - self.record.mode = record["mode"] - self.record.gps_qual = record["gps_qual"] - self.record.altitude = record["altitude"] - self.record.separation = record["separation"] - self.record.heading = record["heading"] - self.record.speed_kmh = record["speed_kmh"] - @abstractmethod async def update(self) -> None: pass @@ -93,7 +73,6 @@ def set_reference(self, lat: float, lon: float) -> None: def clear_reference(self) -> None: self.reference_lat = None self.reference_lon = None - self.request_backup() def get_reference(self) -> tuple[Optional[float], Optional[float]]: return self.reference_lat, self.reference_lon @@ -109,9 +88,10 @@ class GnssHardware(Gnss): PORT = '/dev/cu.usbmodem36307295' TYPES_NEEDED = {'GGA', 'GNS', 'HDT'} - def __init__(self, odometer: rosys.driving.Odometer) -> None: + def __init__(self, odometer: rosys.driving.Odometer, antenna_offset: float) -> None: super().__init__() self.odometer = odometer + self.antenna_offset = antenna_offset def __del__(self) -> None: if self.ser is not None: @@ -218,13 +198,18 @@ async def update(self) -> None: self.log.info(f'GNSS reference set to {record.latitude}, {record.longitude}') self.set_reference(record.latitude, record.longitude) else: - cartesian_coordinates = wgs84_to_cartesian([self.reference_lat, self.reference_lon], [ - record.latitude, record.longitude]) if has_heading: yaw = np.deg2rad(float(-record.heading)) else: yaw = self.odometer.get_pose(time=record.timestamp).yaw # TODO: Better INS implementation if no heading provided by GNSS + # correct the gnss coordinat by antenna offset + corrected_coordinates = get_new_position([ + record.latitude, record.longitude], self.antenna_offset, yaw+np.pi/2) + self.record.latitude = deepcopy(corrected_coordinates[0]) + self.record.longitude = deepcopy(corrected_coordinates[1]) + cartesian_coordinates = wgs84_to_cartesian([self.reference_lat, self.reference_lon], [ + self.record.latitude, self.record.longitude]) pose = rosys.geometry.Pose( x=cartesian_coordinates[0], y=cartesian_coordinates[1], @@ -244,7 +229,6 @@ async def update(self) -> None: def set_reference(self, lat: float, lon: float) -> None: self.reference_lat = lat self.reference_lon = lon - self.request_backup() class PoseProvider(Protocol): @@ -268,7 +252,11 @@ async def update(self) -> None: if self.reference_lon is None: self.reference_lon = 7.434212 pose = deepcopy(self.pose_provider.pose) - pose.time = rosys.time() + try: + pose.time = rosys.time() + except Exception: + self.log.error('Pose provider has no time attribute') + return current_position = cartesian_to_wgs84([self.reference_lat, self.reference_lon], [pose.x, pose.y]) self.record.timestamp = pose.time @@ -288,4 +276,3 @@ def set_reference(self, lat: float, lon: float) -> None: self.record.latitude = lat self.record.longitude = lon self.ROBOT_POSITION_LOCATED.emit() - self.request_backup() diff --git a/field_friend/navigation/point_transformation.py b/field_friend/navigation/point_transformation.py index d29c73af..21a6fbd0 100644 --- a/field_friend/navigation/point_transformation.py +++ b/field_friend/navigation/point_transformation.py @@ -1,6 +1,7 @@ +from typing import List + import numpy as np from geographiclib.geodesic import Geodesic -from typing import List def wgs84_to_cartesian(reference, point) -> List[float]: @@ -18,3 +19,21 @@ def cartesian_to_wgs84(reference, point) -> List[float]: r = Geodesic.WGS84.Direct(r['lat2'], r['lon2'], 0.0, point[1]) wgs84_coords = [r['lat2'], r['lon2']] return wgs84_coords + + +def get_new_position(reference, distance, yaw): + """ + Calculate a new position given a reference point, distance, and yaw (direction in radians). + + Parameters: + - reference: Tuple containing the latitude and longitude of the reference point (lat, lon). + - distance: Distance to move from the reference point in meters. + - yaw: Direction of movement in radians from the north. + + Returns: + - Tuple containing the latitude and longitude of the new position (lat, lon). + """ + azimuth_deg = np.degrees(-yaw) + result = Geodesic.WGS84.Direct(reference[0], reference[1], azimuth_deg, distance) + new_position = [result['lat2'], result['lon2']] + return new_position diff --git a/field_friend/system.py b/field_friend/system.py index 3b7415d3..650c47c5 100644 --- a/field_friend/system.py +++ b/field_friend/system.py @@ -49,17 +49,17 @@ def __init__(self) -> None: self.odometer = rosys.driving.Odometer(self.field_friend.wheels) self.gnss: GnssHardware | GnssSimulation if self.is_real: - self.gnss = GnssHardware(self.odometer) + self.gnss = GnssHardware(self.odometer, self.field_friend.ANTENNA_OFFSET) else: self.gnss = GnssSimulation(self.field_friend.wheels) self.gnss.ROBOT_POSE_LOCATED.register(self.odometer.handle_detection) self.driver = rosys.driving.Driver(self.field_friend.wheels, self.odometer) - self.driver.parameters.linear_speed_limit = 0.1 - self.driver.parameters.angular_speed_limit = 0.5 + self.driver.parameters.linear_speed_limit = 0.3 + self.driver.parameters.angular_speed_limit = 0.8 self.driver.parameters.can_drive_backwards = True - self.driver.parameters.minimum_turning_radius = 0.02 - self.driver.parameters.hook_offset = 0.6 - self.driver.parameters.carrot_distance = 0.2 + self.driver.parameters.minimum_turning_radius = 0.01 + self.driver.parameters.hook_offset = 0.45 + self.driver.parameters.carrot_distance = 0.15 self.driver.parameters.carrot_offset = self.driver.parameters.hook_offset + self.driver.parameters.carrot_distance self.kpi_provider = KpiProvider(self.plant_provider) @@ -80,6 +80,8 @@ def watch_robot() -> None: self.plant_provider, self.odometer) self.plant_locator.weed_category_names = self.big_weed_category_names + self.small_weed_category_names self.plant_locator.crop_category_names = self.crop_category_names + self.plant_locator.minimum_weed_confidence = 0.8 + self.plant_locator.minimum_crop_confidence = 0.40 rosys.on_repeat(watch_robot, 1.0) From 4d148387c28ae0a15bb1d60442dcea83779d3947 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:19:35 +0200 Subject: [PATCH 11/18] somehow wrapping pynmea2.parse in run.cpu_bound produced random rosys shutdowns (#44) --- field_friend/navigation/gnss.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/field_friend/navigation/gnss.py b/field_friend/navigation/gnss.py index 4478a769..0f466b44 100644 --- a/field_friend/navigation/gnss.py +++ b/field_friend/navigation/gnss.py @@ -151,7 +151,7 @@ async def update(self) -> None: self.log.debug('No data received') return try: - msg = await rosys.run.cpu_bound(pynmea2.parse, line) + msg = pynmea2.parse(line) if not hasattr(msg, 'sentence_type'): self.log.debug(f'No sentence type: {msg}') return @@ -186,10 +186,10 @@ async def update(self) -> None: self.device = None return if self.record.gps_qual > 0 and record.gps_qual == 0: - self.log.info('GNSS lost') + self.log.warning('GNSS lost') self.GNSS_CONNECTION_LOST.emit() if self.record.gps_qual == 4 and record.gps_qual != 4: - self.log.info('GNSS RTK fix lost') + self.log.warning('GNSS RTK fix lost') self.RTK_FIX_LOST.emit() self.record = deepcopy(record) if has_location: From 45b5e1ca3b54925dae552fa00be03506f1dd4cc4 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:20:32 +0200 Subject: [PATCH 12/18] correctly await can_start handler in automation controls (#43) --- field_friend/interface/components/automation_controls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/field_friend/interface/components/automation_controls.py b/field_friend/interface/components/automation_controls.py index ed36d5db..5c56f237 100644 --- a/field_friend/interface/components/automation_controls.py +++ b/field_friend/interface/components/automation_controls.py @@ -21,7 +21,8 @@ def __init__( async def start() -> None: if callable(can_start): # Directly handle both synchronous and asynchronous can_start - proceed = can_start() if not isinstance(can_start(), Awaitable) else await can_start() + result = can_start() + proceed = result if not isinstance(result, Awaitable) else await result if not proceed: return From ce968c2f1258f5b9d8ea24b4b70ba3ed653d0fe4 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:25:04 +0200 Subject: [PATCH 13/18] remove non-functional restart via keyboard shortcut (#39) --- field_friend/interface/components/key_controls.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/field_friend/interface/components/key_controls.py b/field_friend/interface/components/key_controls.py index 6b622b88..aec8db35 100644 --- a/field_friend/interface/components/key_controls.py +++ b/field_friend/interface/components/key_controls.py @@ -27,9 +27,6 @@ def handle_keys(self, e: KeyEventArguments) -> None: if e.key == '!': self.automator.start(self.puncher.try_home()) - if e.action.keydown and e.key == 'r' and e.modifiers.shift: - self.system.restart() - if e.action.keydown and e.key == 's': if self.automator.is_running: self.automator.stop(because='stop button was pressed') From 3fd0c63e2e74ac169896fd400f6cb2f8c33df86a Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:31:26 +0200 Subject: [PATCH 14/18] ensure we have a float; not non-serializable decimal for gnss heading (#37) --- field_friend/navigation/gnss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/field_friend/navigation/gnss.py b/field_friend/navigation/gnss.py index 0f466b44..a78542ac 100644 --- a/field_friend/navigation/gnss.py +++ b/field_friend/navigation/gnss.py @@ -176,7 +176,7 @@ async def update(self) -> None: # print(f'The GNSS message: {msg.mode_indicator}') has_location = True if msg.sentence_type == 'HDT' and getattr(msg, 'heading', None): - record.heading = msg.heading + record.heading = float(msg.heading) has_heading = True except pynmea2.ParseError as e: self.log.info(f'Parse error: {e}') @@ -199,7 +199,7 @@ async def update(self) -> None: self.set_reference(record.latitude, record.longitude) else: if has_heading: - yaw = np.deg2rad(float(-record.heading)) + yaw = np.deg2rad(-record.heading) else: yaw = self.odometer.get_pose(time=record.timestamp).yaw # TODO: Better INS implementation if no heading provided by GNSS From e523ef464cd81e8a9def81d968f54d6a50a62ca2 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:43:36 +0200 Subject: [PATCH 15/18] General code review (#34) * formatting and logging * fix persistence restore issues * add forgotten input * add more categories for weed and crop * better line breaks * cleanup * revert change because in real robots we want persistence to overwrite the settings (see https://github.com/zauberzeug/rosys/discussions/106) * formatting of ui.run * fix name --- .../automations/automation_watcher.py | 5 +++-- field_friend/automations/plant_locator.py | 4 ++-- .../interface/components/monitoring.py | 13 +++++++----- .../interface/components/robot_scene.py | 20 ++++++++++--------- field_friend/interface/pages/monitor_page.py | 14 ++++++++----- field_friend/system.py | 8 +++++--- field_friend/vision/camera_configurator.py | 5 +++-- main.py | 8 ++++++-- 8 files changed, 47 insertions(+), 30 deletions(-) diff --git a/field_friend/automations/automation_watcher.py b/field_friend/automations/automation_watcher.py index 17f14604..8f7aa55a 100644 --- a/field_friend/automations/automation_watcher.py +++ b/field_friend/automations/automation_watcher.py @@ -53,8 +53,9 @@ def pause(self, reason: str) -> None: if self.path_recorder.state == 'recording': return else: - self.log.info(f'pausing automation because {reason}') - self.automator.pause(because=f'{reason})') + if self.automator.is_running: + self.log.info(f'pausing automation because {reason}') + self.automator.pause(because=f'{reason})') return if reason.startswith('GNSS') and not self.gnss_watch_active: return diff --git a/field_friend/automations/plant_locator.py b/field_friend/automations/plant_locator.py index f1072810..4439b97d 100644 --- a/field_friend/automations/plant_locator.py +++ b/field_friend/automations/plant_locator.py @@ -5,8 +5,8 @@ from .plant_provider import Plant, PlantProvider -WEED_CATEGORY_NAME = ['coin', 'weed'] -CROP_CATEGORY_NAME = ['coin_with_hole', 'crop'] +WEED_CATEGORY_NAME = ['coin', 'weed', 'big_weed', 'thistle', 'orache', 'weedy_area', ] +CROP_CATEGORY_NAME = ['coin_with_hole', 'crop', 'sugar_beet', 'onion', 'garlic', ] MINIMUM_WEED_CONFIDENCE = 0.8 MINIMUM_CROP_CONFIDENCE = 0.75 diff --git a/field_friend/interface/components/monitoring.py b/field_friend/interface/components/monitoring.py index fc899662..2eace261 100644 --- a/field_friend/interface/components/monitoring.py +++ b/field_friend/interface/components/monitoring.py @@ -31,7 +31,8 @@ def __init__(self, field_friend: FieldFriend, system: 'System', *, - shrink_factor: int = 1) -> None: + shrink_factor: int = 1, + ) -> None: self.log = logging.getLogger('field_friend.monitoring') self.usb_camera_provider = usb_camera_provider self.mjpg_camera_provider = mjpeg_camera_provider @@ -69,10 +70,12 @@ def __init__(self, ui.label('Animal count:').classes('text-2xl text-bold').bind_text_from(self, 'animal_count', backward=lambda x: f'Animal count: {x}') ui.space() - ui.switch('Person detection').bind_value( - self, 'monitoring_active').bind_enabled_from(self.automator, 'is_running', backward=lambda x: not x) - ui.switch('Plant detection').bind_value(self.plant_locator, 'is_paused', forward=lambda x: not x, - backward=lambda x: not x).bind_enabled_from(self.automator, 'is_running', backward=lambda x: not x) + ui.switch('Person detection') \ + .bind_value(self, 'monitoring_active') \ + .bind_enabled_from(self.automator, 'is_running', backward=lambda x: not x) + ui.switch('Plant detection') \ + .bind_value(self.plant_locator, 'is_paused', forward=lambda x: not x, backward=lambda x: not x) \ + .bind_enabled_from(self.automator, 'is_running', backward=lambda x: not x) with ui.row().classes('w-full items-stretch gap-0'): column_classes = 'w-1/3 items-center mt-[50px]' diff --git a/field_friend/interface/components/robot_scene.py b/field_friend/interface/components/robot_scene.py index 5fec61b2..e01c893b 100644 --- a/field_friend/interface/components/robot_scene.py +++ b/field_friend/interface/components/robot_scene.py @@ -1,4 +1,6 @@ import logging +from typing import TYPE_CHECKING + import rosys from nicegui import events, ui @@ -7,7 +9,6 @@ from .plant_object import plant_objects from .visualizer_object import visualizer_object -from typing import TYPE_CHECKING if TYPE_CHECKING: from field_friend.system import System @@ -24,18 +25,19 @@ def __init__(self, system: 'System'): with self.scene_card.tight().classes('w-full place-items-center').style('max-width: 100%; overflow: hidden;'): def toggle_lock(): self.locked_view = not self.locked_view - self.lock_view_button.props( - f'flat color={"primary" if self.locked_view else "grey"}') - self.lock_view_button = ui.button(icon='sym_o_visibility_lock', on_click=toggle_lock).props('flat color=primary').style( - 'position: absolute; left: 1px; top: 1px; z-index: 500;').tooltip('Lock view to robot') + self.lock_view_button.props(f'flat color={"primary" if self.locked_view else "grey"}') + self.lock_view_button = ui.button(icon='sym_o_visibility_lock', on_click=toggle_lock).props('flat color=primary') \ + .style('position: absolute; left: 1px; top: 1px; z-index: 500;').tooltip('Lock view to robot') with ui.scene(200, 200, on_click=self.handle_click, grid=False).classes('w-full') as self.scene: field_friend_object(self.system.odometer, self.system.usb_camera_provider, self.system.field_friend) rosys.driving.driver_object(self.system.driver) - plant_objects(self.system.plant_provider, self.system.big_weed_category_names + - self.system.small_weed_category_names) - visualizer_object(self.system.automator, self.system.path_provider, - self.system.mowing, self.system.weeding) + plant_objects(self.system.plant_provider, + self.system.big_weed_category_names + self.system.small_weed_category_names) + visualizer_object(self.system.automator, + self.system.path_provider, + self.system.mowing, + self.system.weeding) field_object(self.system.field_provider) self.scene.move_camera(-0.5, -1, 2) diff --git a/field_friend/interface/pages/monitor_page.py b/field_friend/interface/pages/monitor_page.py index d4eb6b19..388e73ec 100644 --- a/field_friend/interface/pages/monitor_page.py +++ b/field_friend/interface/pages/monitor_page.py @@ -20,9 +20,13 @@ def page() -> None: self.content() def content(self) -> None: - # ui.query('body').classes('bg-black text-white') - # ui.query('.nicegui-content').classes('p-0 h-screen') ui.colors(primary='#6E93D6', secondary='#53B689', accent='#111B1E', positive='#53B689') - if self.system.is_real: - monitoring(self.system.usb_camera_provider, self.system.mjpeg_camera_provider, - self.system.detector, self.system.monitoring_detector, self.system.plant_locator, self.system.automator, self.system.field_friend, self.system) + monitoring(self.system.usb_camera_provider, + self.system.mjpeg_camera_provider, + self.system.detector, + self.system.monitoring_detector, + self.system.plant_locator, + self.system.automator, + self.system.field_friend, + self.system, + ) diff --git a/field_friend/system.py b/field_friend/system.py index 650c47c5..87a02401 100644 --- a/field_friend/system.py +++ b/field_friend/system.py @@ -43,7 +43,6 @@ def __init__(self) -> None: yaw=np.deg2rad(90))) self.detector = rosys.vision.DetectorSimulation(self.usb_camera_provider) self.camera_configurator = CameraConfigurator(self.usb_camera_provider, robot_id=version) - # self.circle_sight = None self.plant_provider = PlantProvider() self.steerer = rosys.driving.Steerer(self.field_friend.wheels, speed_scaling=0.25) self.odometer = rosys.driving.Odometer(self.field_friend.wheels) @@ -76,8 +75,11 @@ def watch_robot() -> None: self.big_weed_category_names = ['thistle', 'big_weed', 'orache'] self.small_weed_category_names = ['weed', 'coin'] self.crop_category_names = ['sugar_beet', 'crop', 'coin_with_hole'] - self.plant_locator = PlantLocator(self.usb_camera_provider, self.detector, - self.plant_provider, self.odometer) + self.plant_locator = PlantLocator(self.usb_camera_provider, + self.detector, + self.plant_provider, + self.odometer, + ) self.plant_locator.weed_category_names = self.big_weed_category_names + self.small_weed_category_names self.plant_locator.crop_category_names = self.crop_category_names self.plant_locator.minimum_weed_confidence = 0.8 diff --git a/field_friend/vision/camera_configurator.py b/field_friend/vision/camera_configurator.py index 91a36b0f..ac2cafe0 100644 --- a/field_friend/vision/camera_configurator.py +++ b/field_friend/vision/camera_configurator.py @@ -12,7 +12,8 @@ class CameraConfigurator: def __init__(self, camera_provider: rosys.vision.CameraProvider, - robot_id: str | None = None): + robot_id: str | None = None, + ): self.log = logging.getLogger('field_friend.camera_configurator') self.camera_provider = camera_provider if not robot_id: @@ -23,7 +24,7 @@ def __init__(self, async def update_camera_config(self): await rosys.sleep(15) - self.log.info(f'updating camera config') + self.log.info('updating camera config') camera = None start_time = rosys.time() while not camera: diff --git a/main.py b/main.py index aab6933c..c19bfdff 100755 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import rosys from nicegui import app, ui from rosys.analysis import logging_page @@ -46,6 +47,9 @@ def status(): app.on_startup(startup) -ui.run(title='Field Friend', port=80, favicon='assets/favicon.ico', +ui.run(title='Field Friend', + port=80, + favicon='assets/favicon.ico', binding_refresh_interval=0.3, - reconnect_timeout=10) + reconnect_timeout=10, + ) From 34ecd513cd7d9f02ca766987a825849a18571f33 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:44:38 +0200 Subject: [PATCH 16/18] more compact routes documentation (#33) --- main.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index c19bfdff..c2e570fe 100755 --- a/main.py +++ b/main.py @@ -21,28 +21,19 @@ def page_wrapper() -> None: header_bar(system, drawer) system_bar() - # / - interface.pages.main_page(page_wrapper, system) - # /field - interface.pages.field_planner_page(page_wrapper, system) - # /path - interface.pages.path_planner_page(page_wrapper, system) - # /dev - interface.pages.dev_page(page_wrapper, system) - # /test - interface.pages.test_page(page_wrapper, system) - # /kpis - interface.pages.kpi_page(page_wrapper, system) - # /monitor - interface.pages.monitor_page(page_wrapper, system) - # /status - - @app.get('/status') + interface.pages.main_page(page_wrapper, system) # / + interface.pages.field_planner_page(page_wrapper, system) # /field + interface.pages.path_planner_page(page_wrapper, system) # /path + interface.pages.dev_page(page_wrapper, system) # /dev + interface.pages.test_page(page_wrapper, system) # /test + interface.pages.kpi_page(page_wrapper, system) # /kpis + interface.pages.monitor_page(page_wrapper, system) # /monitor + + @app.get('/status') # /status def status(): return {'status': 'ok'} - # /logging - logging_page(['field_friend', 'rosys']) + logging_page(['field_friend', 'rosys']) # /logging app.on_startup(startup) From 306241bde9c4c107a8b14eb044a24349f6ee8f81 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 08:53:10 +0200 Subject: [PATCH 17/18] formatting of operation.py (#40) * implement turn_by and turn_knives_to methods * use new methods in puncher class * implement reference speeds * Update hardware configuration for ff12_config_rb32 and u4_config_rb28 eith tornado reference speeds * Fix turn_knives_to method in TornadoHardware class * Fix reference to knive stop in CoinCollecting class * rename ref_t and ref_b to ref_knife_stop and ref_knive_ground * typo * Add turns parameter to punch method in Puncher class * Refactor field_friend_hardware.py to update knife stop and ground pin names * Update hardware.py and puncher.py for speed adjustments * backup rb28 * Update camera crop settings in config/u4_config_rb28/camera.py * Fix typo in Field class area method * Fix error handling in PlantLocator class * tmp remove of rows as obstacles * fix binding of minimum_turning_radius * Fix typo in worked_area_label text formatting * remove buggy persistence of gnss * Refactor prune method in PlantProvider class to use separate variables for max age of weeds and crops * backup rb28 * Add settings for dual mechanism workflow * add settings to operation.py * change rendering of plant objects in plant_objects.py * implement gnss correction by antenna offset * Fix error handling in PlantLocator class * Add driver settings to operation.py * Update max age of weeds in prune method of PlantProvider class * Implement antenna offset for GNSS correction * Refactor obstacle creation in Weeding class to include row polygons * Refactor plant_provider.py to use async/await for adding weeds and crops * Refactor driver settings in Weeding class * Refactor field_planner.py to remove unused imports and dependencies * Add driver settings for weeding automation in operation.py * fix field_friend_object.py to update second_tool move coordinates * backup rb28 * Update antenna offset for GNSS correction in params.py * backup for u3 * backup rb27 * Refactor operation.py to add driver settings for weeding automation * Refactor operation.py to add driver settings for weeding automation * Update healthcheck configuration in docker-compose.yml * provide antenna offset for u5 and u6 * Update antenna offset for GNSS correction in params.py for U5 * formatting of operation.py --------- Co-authored-by: Miguel Co-authored-by: Johannes-Thiel --- .../interface/components/operation.py | 158 ++++++++++-------- 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/field_friend/interface/components/operation.py b/field_friend/interface/components/operation.py index 2f91efb2..00208945 100644 --- a/field_friend/interface/components/operation.py +++ b/field_friend/interface/components/operation.py @@ -30,8 +30,8 @@ def __init__(self, system: 'System', leaflet_map: leaflet_map) -> None: @ui.refreshable def center_map_button() -> None: if self.field_provider.active_field is not None and len(self.field_provider.active_field.outline_wgs84) > 0: - ui.button(on_click=lambda: self.leaflet_map.m.set_center(self.field_provider.active_field.outline_wgs84[0])).props( - 'icon=place color=primary fab-mini flat').tooltip('center map on point').classes('ml-0') + ui.button(on_click=lambda: self.leaflet_map.m.set_center(self.field_provider.active_field.outline_wgs84[0])) \ + .props('icon=place color=primary fab-mini flat').tooltip('center map on point').classes('ml-0') else: ui.icon('place').props('size=sm color=grey').classes('ml-2') center_map_button() @@ -48,35 +48,45 @@ def center_map_button() -> None: def show_field_selection() -> None: self.field_selection = ui.select( field_selection_dict, - with_input=True, on_change=self.set_field, label='Field', value=self.initial_value).tooltip( - 'Select the field to work on').classes('w-24') + with_input=True, on_change=self.set_field, label='Field', value=self.initial_value)\ + .tooltip('Select the field to work on').classes('w-24') show_field_selection() self.field_provider.FIELDS_CHANGED.register(show_field_selection.refresh) ui.separator() with ui.row(): - ui.label("Automation").classes('text-xl') + ui.label('Automation').classes('text-xl') with ui.row().classes('w-full'): - self.automations_toggle = ui.select( - [key for key in self.system.automations.keys()], - value='weeding').bind_value( - self.system.automator, 'default_automation', forward=lambda key: self.system.automations[key], - backward=lambda automation: next( - key for key, value in self.system.automations.items() if value == automation)).classes('w-full border pl-2').style('border: 2px solid #6E93D6; border-radius: 5px; background-color: #EEF4FA') + self.automations_toggle = ui.select([key for key in self.system.automations.keys()], value='weeding') \ + .bind_value(self.system.automator, + 'default_automation', + forward=lambda key: self.system.automations[key], + backward=lambda automation: next(key for key, value in self.system.automations.items() if value == automation)) \ + .classes('w-full border pl-2').style('border: 2px solid #6E93D6; border-radius: 5px; background-color: #EEF4FA') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='mowing'): with ui.row(): - ui.number('Padding', value=0.5, step=0.1, min=0.0, format='%.1f').props('dense outlined suffix=m').classes( - 'w-24').bind_value(system.mowing, 'padding').tooltip('Set the padding for the mowing automation') - ui.number('Lane distance', value=0.5, step=0.1, min=0.0, format='%.1f').props('dense outlined suffix=m').classes( - 'w-24').bind_value(system.mowing, 'lane_distance').tooltip('Set the lane distance for the system. automation') - ui.number('Number of outer lanes', value=3, step=1, min=3, format='%.0f').props('dense outlined').classes( - 'w-28').bind_value(system.mowing, 'number_of_outer_lanes').tooltip('Set the number of outer lanes for the mowing automation') - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.1, max=2.0).props( - 'dense outlined suffix=m').classes('w-32').bind_value( - self.system.mowing, 'minimum_turning_radius').tooltip( - 'Set the turning radius for the mowing automation') + ui.number('Padding', value=0.5, step=0.1, min=0.0, format='%.1f') \ + .props('dense outlined suffix=m').classes('w-24') \ + .bind_value(system.mowing, 'padding') \ + .tooltip('Set the padding for the mowing automation') + ui.number('Lane distance', value=0.5, step=0.1, min=0.0, format='%.1f') \ + .props('dense outlined suffix=m') \ + .classes('w-24').bind_value(system.mowing, 'lane_distance') \ + .tooltip('Set the lane distance for the system. automation') + ui.number('Number of outer lanes', value=3, step=1, min=3, format='%.0f') \ + .props('dense outlined').classes('w-28') \ + .bind_value(system.mowing, 'number_of_outer_lanes') \ + .tooltip('Set the number of outer lanes for the mowing automation') + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.1, max=2.0) \ + .props('dense outlined suffix=m') \ + .classes('w-32') \ + .bind_value(self.system.mowing, 'minimum_turning_radius') \ + .tooltip('Set the turning radius for the mowing automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='weeding'): + ui.separator() + ui.markdown('Field settings').style('color: #6E93D6') + with ui.row(): ui.separator() ui.markdown('Field settings').style('color: #6E93D6') with ui.row(): @@ -86,23 +96,23 @@ def show_field_selection() -> None: with ui.row().bind_visibility_from(self.with_field_planning, 'value', value=True): self.show_start_row() self.show_end_row() - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0).props( - 'dense outlined suffix=m').classes('w-30').bind_value( + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0) \ + .props('dense outlined suffix=m').classes('w-30').bind_value( self.system.weeding, 'minimum_turning_radius').tooltip( 'Set the turning radius for the weeding automation') - ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0).props( - 'dense outlined suffix=m').classes('w-30').bind_value( + ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0) \ + .props('dense outlined suffix=m').classes('w-30').bind_value( self.system.weeding, 'turn_offset').tooltip( 'Set the turning offset for the weeding automation') ui.separator() ui.markdown('Detector settings').style('color: #6E93D6') with ui.row(): - ui.number('Min. weed confidence', format='%.2f', value=0.8, step=0.05, min=0.0, max=1.0).props( - 'dense outlined').classes('w-24').bind_value( + ui.number('Min. weed confidence', format='%.2f', value=0.8, step=0.05, min=0.0, max=1.0) \ + .props('dense outlined').classes('w-24').bind_value( self.system.plant_locator, 'minimum_weed_confidence').tooltip( 'Set the minimum weed confidence for the weeding automation') - ui.number('Min. crop confidence', format='%.2f', value=0.4, step=0.05, min=0.0, max=1.0).props( - 'dense outlined').classes('w-24').bind_value( + ui.number('Min. crop confidence', format='%.2f', value=0.4, step=0.05, min=0.0, max=1.0) \ + .props('dense outlined').classes('w-24').bind_value( self.system.plant_locator, 'minimum_crop_confidence').tooltip( 'Set the minimum crop confidence for the weeding automation') ui.separator() @@ -114,8 +124,9 @@ def show_field_selection() -> None: self.system.weeding, 'tornado_angle').tooltip( 'Set the angle for the tornado drill') elif self.system.field_friend.tool in ['weed_screw', 'dual_mechanism']: - ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1).props( - 'dense outlined suffix=°').classes('w-24').bind_value( + ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, + min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1) \ + .props('dense outlined suffix=°').classes('w-24').bind_value( self.system.weeding, 'weed_screw_depth').tooltip( 'Set the drill depth for the weeding automation') ui.number('Crop safety distance', value=0.01, step=0.01, min=0.0, max=0.05, format='%.2f').props( @@ -152,59 +163,58 @@ def show_field_selection() -> None: ui.separator() ui.markdown('**Driver settings**').style('color: #6E93D6') with ui.row(): - ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f').props( - 'dense outlined suffix=m/s').classes('w-24').bind_value( - self.system.weeding, 'linear_speed_on_row').tooltip( - 'Set the linear speed on row for the weeding automation') - ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f').props( - 'dense outlined suffix=m/s').classes('w-24').bind_value( - self.system.weeding, 'linear_speed_between_rows').tooltip( - 'Set the linear speed between rows for the weeding automation') - ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f').props( - 'dense outlined suffix=°/s').classes('w-24').bind_value( - self.system.weeding, 'angular_speed_on_row').tooltip( - 'Set the angular speed on row for the weeding automation') - ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f').props( - 'dense outlined suffix=°/s').classes('w-24').bind_value( - self.system.weeding, 'angular_speed_between_rows').tooltip( - 'Set the angular speed between rows for the weeding automation') + ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') \ + .props('dense outlined suffix=m/s').classes('w-24') \ + .bind_value(self.system.weeding, 'linear_speed_on_row') \ + .tooltip('Set the linear speed on row for the weeding automation') + ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') \ + .props('dense outlined suffix=m/s').classes('w-24') \ + .bind_value(self.system.weeding, 'linear_speed_between_rows') \ + .tooltip('Set the linear speed between rows for the weeding automation') + ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') \ + .props('dense outlined suffix=°/s').classes('w-24') \ + .bind_value(self.system.weeding, 'angular_speed_on_row') \ + .tooltip('Set the angular speed on row for the weeding automation') + ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') \ + .props('dense outlined suffix=°/s').classes('w-24') \ + .bind_value(self.system.weeding, 'angular_speed_between_rows') \ + .tooltip('Set the angular speed between rows for the weeding automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='monitoring'): with ui.column(): - self.with_field_planning_monitor = ui.checkbox('Use field planning', value=True).bind_value( - self.system.monitoring, 'use_field_planning').tooltip('Set the monitoring automation to use the field planning with GNSS') + self.with_field_planning_monitor = ui.checkbox('Use field planning', value=True) \ + .bind_value(self.system.monitoring, 'use_field_planning').tooltip('Set the monitoring automation to use the field planning with GNSS') with ui.row().bind_visibility_from(self.with_field_planning_monitor, 'value', value=True): self.show_start_row() self.show_end_row() - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0).props( - 'dense outlined suffix=m').classes('w-30').bind_value( - self.system.monitoring, 'minimum_turning_radius').tooltip( - 'Set the turning radius for the monitoring automation') + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0) \ + .props('dense outlined suffix=m').classes('w-30') \ + .bind_value(self.system.monitoring, 'minimum_turning_radius') \ + .tooltip('Set the turning radius for the monitoring automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='collecting (demo)'): with ui.row(): ui.number( - 'Drill angle', format='%.0f', value=100, step=1, min=1, max=180).props( - 'dense outlined suffix=°').classes('w-24').bind_value( - self.system.coin_collecting, 'angle').tooltip( - 'Set the drill depth for the weeding automation') - ui.checkbox('with drilling', value=True).bind_value( - self.system.coin_collecting, 'with_drilling') + 'Drill angle', format='%.0f', value=100, step=1, min=1, max=180) \ + .props('dense outlined suffix=°').classes('w-24') \ + .bind_value(self.system.coin_collecting, 'angle') \ + .tooltip('Set the drill depth for the weeding automation') + ui.checkbox('with drilling', value=True) \ + .bind_value(self.system.coin_collecting, 'with_drilling') ui.space() with ui.row().style("margin: 1rem; width: calc(100% - 2rem);"): with ui.column(): - ui.button('emergency stop', on_click=lambda: system.field_friend.estop.set_soft_estop(True)).props('color=red').classes( - 'py-3 px-6 text-lg').bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=False) - ui.button('emergency reset', on_click=lambda: system.field_friend.estop.set_soft_estop(False)).props( - 'color=red-700 outline').classes('py-3 px-6 text-lg').bind_visibility_from(system.field_friend.estop, - 'is_soft_estop_active', value=True) + ui.button('emergency stop', on_click=lambda: system.field_friend.estop.set_soft_estop(True)).props('color=red') \ + .classes('py-3 px-6 text-lg').bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=False) + ui.button('emergency reset', on_click=lambda: system.field_friend.estop.set_soft_estop(False)) \ + .props('color=red-700 outline').classes('py-3 px-6 text-lg') \ + .bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=True) ui.space() with ui.row(): automation_controls(self.system, can_start=self.ensure_start) with ui.dialog() as self.dialog, ui.card(): - self.dialog_label = ui.label(f'Do you want to continue the canceled automation').classes( - 'text-lg') + self.dialog_label = ui.label('Do you want to continue the canceled automation').classes('text-lg') with ui.row(): ui.button('Yes', on_click=lambda: self.dialog.submit('Yes')) ui.button('No', on_click=lambda: self.dialog.submit('No')) @@ -213,20 +223,20 @@ def show_field_selection() -> None: @ui.refreshable def show_start_row(self) -> None: if self.field_provider.active_field is not None: - ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='Start row').bind_value(self.system.weeding, 'start_row_id').classes( - 'w-24').tooltip('Select the row to start on') + ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='Start row') \ + .bind_value(self.system.weeding, 'start_row_id').classes('w-24').tooltip('Select the row to start on') else: - ui.select([None], label='Start row').bind_value(self.system.weeding, 'start_row').classes( - 'w-24').tooltip('Select the row to start on') + ui.select([None], label='Start row')\ + .bind_value(self.system.weeding, 'start_row').classes('w-24').tooltip('Select the row to start on') @ui.refreshable def show_end_row(self) -> None: if self.field_provider.active_field is not None: - ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='End row').bind_value(self.system.weeding, 'end_row_id').classes( - 'w-24').tooltip('Select the row to end on') + ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='End row') \ + .bind_value(self.system.weeding, 'end_row_id').classes('w-24').tooltip('Select the row to end on') else: - ui.select([None], label='End row').bind_value(self.system.weeding, 'end_row').classes( - 'w-24').tooltip('Select the row to end on') + ui.select([None], label='End row') \ + .bind_value(self.system.weeding, 'end_row').classes('w-24').tooltip('Select the row to end on') def set_field(self) -> None: for field in self.system.field_provider.fields: From daf235fa584c8c2b94dadf7f47378113606c4073 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Mon, 6 May 2024 09:00:34 +0200 Subject: [PATCH 18/18] Use app.storage.user to preselect last automation and field (#41) * implement turn_by and turn_knives_to methods * use new methods in puncher class * implement reference speeds * Update hardware configuration for ff12_config_rb32 and u4_config_rb28 eith tornado reference speeds * Fix turn_knives_to method in TornadoHardware class * Fix reference to knive stop in CoinCollecting class * rename ref_t and ref_b to ref_knife_stop and ref_knive_ground * typo * Add turns parameter to punch method in Puncher class * formatting and logging * fix persistence restore issues * add forgotten input * add more categories for weed and crop * better line breaks * cleanup * Refactor field_friend_hardware.py to update knife stop and ground pin names * Update hardware.py and puncher.py for speed adjustments * backup rb28 * Update camera crop settings in config/u4_config_rb28/camera.py * Fix typo in Field class area method * Fix error handling in PlantLocator class * tmp remove of rows as obstacles * fix binding of minimum_turning_radius * Fix typo in worked_area_label text formatting * remove buggy persistence of gnss * Refactor prune method in PlantProvider class to use separate variables for max age of weeds and crops * backup rb28 * Add settings for dual mechanism workflow * add settings to operation.py * change rendering of plant objects in plant_objects.py * implement gnss correction by antenna offset * Fix error handling in PlantLocator class * Add driver settings to operation.py * Update max age of weeds in prune method of PlantProvider class * Implement antenna offset for GNSS correction * Refactor obstacle creation in Weeding class to include row polygons * Refactor plant_provider.py to use async/await for adding weeds and crops * Refactor driver settings in Weeding class * Refactor field_planner.py to remove unused imports and dependencies * Add driver settings for weeding automation in operation.py * fix field_friend_object.py to update second_tool move coordinates * backup rb28 * Update antenna offset for GNSS correction in params.py * backup for u3 * backup rb27 * Refactor operation.py to add driver settings for weeding automation * Refactor operation.py to add driver settings for weeding automation * Update healthcheck configuration in docker-compose.yml * provide antenna offset for u5 and u6 * Update antenna offset for GNSS correction in params.py for U5 * revert change because in real robots we want persistence to overwrite the settings (see https://github.com/zauberzeug/rosys/discussions/106) * formatting of ui.run * fix name * formatting of operation.py * use app.storage.user to preselect last automation and field * provide storage secret * Provide map buttons to center on robot and toggle basemap (#42) * provide map buttons to center on robot and toggle basemap * improved max zoom --------- Co-authored-by: Miguel Co-authored-by: Johannes-Thiel --- .gitignore | 3 +- .syncignore | 1 + .../interface/components/leaflet_map.py | 81 ++-- .../interface/components/operation.py | 358 ++++++++++++------ .../interface/pages/field_planner_page.py | 2 + field_friend/interface/pages/main_page.py | 11 +- main.py | 4 +- 7 files changed, 316 insertions(+), 144 deletions(-) diff --git a/.gitignore b/.gitignore index 234f9b66..5d8885ee 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ __pycache__/ detector/ *.env *.swp -site/ \ No newline at end of file +site/ +.nicegui/ diff --git a/.syncignore b/.syncignore index 504aa03f..414f759e 100644 --- a/.syncignore +++ b/.syncignore @@ -11,3 +11,4 @@ config.hjson sync.sh nicegui rosys +.nicegui \ No newline at end of file diff --git a/field_friend/interface/components/leaflet_map.py b/field_friend/interface/components/leaflet_map.py index f5799206..45b990ba 100644 --- a/field_friend/interface/components/leaflet_map.py +++ b/field_friend/interface/components/leaflet_map.py @@ -1,14 +1,13 @@ import logging import uuid -from typing import TYPE_CHECKING, Dict, List, Union +from typing import TYPE_CHECKING -import numpy as np import rosys import rosys.geometry -from nicegui import elements, events, ui +from nicegui import app, events, ui +from nicegui.elements.leaflet_layers import TileLayer -from ...automations import Field from .key_controls import KeyControls if TYPE_CHECKING: @@ -34,28 +33,22 @@ def __init__(self, system: 'System', draw_tools: bool) -> None: }, 'edit': False, } - self.center_point = [51.983159, 7.434212] + center_point = [51.983159, 7.434212] if self.field_provider.active_field is None: - self.center_point = [51.983159, 7.434212] + center_point = [51.983159, 7.434212] else: if len(self.field_provider.active_field.outline_wgs84) > 0: - self.center_point = self.field_provider.active_field.outline_wgs84[0] + center_point = self.field_provider.active_field.outline_wgs84[0] + self.m: ui.leaflet if draw_tools: - self.m = ui.leaflet(center=(self.center_point[0], self.center_point[1]), + self.m = ui.leaflet(center=(center_point[0], center_point[1]), zoom=13, draw_control=self.draw_control) else: - self.m = ui.leaflet(center=(self.center_point[0], self.center_point[1]), + self.m = ui.leaflet(center=(center_point[0], center_point[1]), zoom=13) - self.m.clear_layers() - self.m.tile_layer( - url_template=r'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - options={ - 'maxZoom': 21, - 'attribution': '© OpenStreetMap contributors' - }, - ) - + self.current_basemap: TileLayer | None = None + self.toggle_basemap() self.field_layers: list[list] = [] self.robot_marker = None self.drawn_marker = None @@ -101,6 +94,14 @@ def handle_draw(e: events.GenericEventArguments): m.on('draw:created', handle_draw) self.gnss.ROBOT_POSITION_LOCATED.register(self.update_robot_position) + def buttons(self) -> None: + """Builds additional buttons to interact with the map.""" + ui.button(icon='my_location', on_click=self.zoom_to_robot).props('dense flat') + ui.button(icon='satellite', on_click=self.toggle_basemap).props('dense flat') \ + .bind_visibility_from(self, 'current_basemap', lambda x: x is not None and 'openstreetmap' not in x.url_template) + ui.button(icon='map', on_click=self.toggle_basemap).props('dense flat') \ + .bind_visibility_from(self, 'current_basemap', lambda x: x is not None and 'openstreetmap' in x.url_template) + def set_simulated_reference(self, latlon, dialog): dialog.close() self.m.remove_layer(self.drawn_marker) @@ -167,11 +168,39 @@ def update_robot_position(self) -> None: self.robot_marker.run_method(':setIcon', icon) self.robot_marker.move(self.gnss.record.latitude, self.gnss.record.longitude) - def change_basemap(self) -> None: - return - # TODO: add a button in leaflet map to change basemap layer and implement the functionality here - - # this is the ESRI satellite image as free satellite image - # Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { - # attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' - # }) + def zoom_to_robot(self) -> None: + self.m.center = (self.gnss.record.latitude, self.gnss.record.longitude) + self.m.set_zoom(self.current_basemap.options['maxZoom'] - 1) + + def toggle_basemap(self) -> None: + use_satellite = app.storage.user.get('use_satellite', False) + if self.current_basemap is not None: + self.m.remove_layer(self.current_basemap) + use_satellite = not use_satellite + app.storage.user['use_satellite'] = use_satellite + if use_satellite: + # ESRI satellite image provides free usage + self.current_basemap = self.m.tile_layer( + url_template='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', + options={ + 'maxZoom': 21, + 'attribution': 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' + }) + # self.current_basemap = self.m.tile_layer( + # url_template=r'http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', + # options={ + # 'maxZoom': 20, + # 'subdomains': ['mt0', 'mt1', 'mt2', 'mt3'], + # 'attribution': '© Google Maps' + # }, + # ) + else: + self.current_basemap = self.m.tile_layer( + url_template=r'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + options={ + 'maxZoom': 20, + 'attribution': '© OpenStreetMap contributors' + }, + ) + if self.current_basemap.options['maxZoom'] - 1 < self.m.zoom: + self.m.set_zoom(self.current_basemap.options['maxZoom'] - 1) diff --git a/field_friend/interface/components/operation.py b/field_friend/interface/components/operation.py index 00208945..9919ba85 100644 --- a/field_friend/interface/components/operation.py +++ b/field_friend/interface/components/operation.py @@ -2,7 +2,7 @@ import logging from typing import TYPE_CHECKING -from nicegui import ui +from nicegui import app, events, ui from .automation_controls import automation_controls from .key_controls import KeyControls @@ -32,6 +32,8 @@ def center_map_button() -> None: if self.field_provider.active_field is not None and len(self.field_provider.active_field.outline_wgs84) > 0: ui.button(on_click=lambda: self.leaflet_map.m.set_center(self.field_provider.active_field.outline_wgs84[0])) \ .props('icon=place color=primary fab-mini flat').tooltip('center map on point').classes('ml-0') + ui.button(on_click=lambda: self.leaflet_map.m.set_center(self.field_provider.active_field.outline_wgs84[0])) \ + .props('icon=place color=primary fab-mini flat').tooltip('center map on point').classes('ml-0') else: ui.icon('place').props('size=sm color=grey').classes('ml-2') center_map_button() @@ -41,28 +43,30 @@ def center_map_button() -> None: if self.field_provider.fields is not None and len(self.field_provider.fields) > 0: for field in self.system.field_provider.fields: field_selection_dict[field.id] = field.name - self.initial_value = None if self.field_provider.active_field is None else self.field_provider.active_field.id + active = self.field_provider.active_field + self.initial_value = app.storage.user.get('field') if active is None else active.id self.field_selection = None @ui.refreshable def show_field_selection() -> None: self.field_selection = ui.select( field_selection_dict, - with_input=True, on_change=self.set_field, label='Field', value=self.initial_value)\ + with_input=True, + on_change=self.set_field, + label='Field')\ .tooltip('Select the field to work on').classes('w-24') + self.field_selection.value = self.initial_value show_field_selection() self.field_provider.FIELDS_CHANGED.register(show_field_selection.refresh) ui.separator() with ui.row(): ui.label('Automation').classes('text-xl') + ui.label('Automation').classes('text-xl') with ui.row().classes('w-full'): - self.automations_toggle = ui.select([key for key in self.system.automations.keys()], value='weeding') \ - .bind_value(self.system.automator, - 'default_automation', - forward=lambda key: self.system.automations[key], - backward=lambda automation: next(key for key, value in self.system.automations.items() if value == automation)) \ + self.automations_toggle = ui.select([key for key in self.system.automations.keys()], + on_change=self.handle_automation_changed) \ .classes('w-full border pl-2').style('border: 2px solid #6E93D6; border-radius: 5px; background-color: #EEF4FA') - + self.automations_toggle.value = app.storage.user.get('automation', 'weeding') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='mowing'): with ui.row(): ui.number('Padding', value=0.5, step=0.1, min=0.0, format='%.1f') \ @@ -82,11 +86,25 @@ def show_field_selection() -> None: .classes('w-32') \ .bind_value(self.system.mowing, 'minimum_turning_radius') \ .tooltip('Set the turning radius for the mowing automation') + ui.number('Padding', value=0.5, step=0.1, min=0.0, format='%.1f') \ + .props('dense outlined suffix=m').classes('w-24') \ + .bind_value(system.mowing, 'padding') \ + .tooltip('Set the padding for the mowing automation') + ui.number('Lane distance', value=0.5, step=0.1, min=0.0, format='%.1f') \ + .props('dense outlined suffix=m') \ + .classes('w-24').bind_value(system.mowing, 'lane_distance') \ + .tooltip('Set the lane distance for the system. automation') + ui.number('Number of outer lanes', value=3, step=1, min=3, format='%.0f') \ + .props('dense outlined').classes('w-28') \ + .bind_value(system.mowing, 'number_of_outer_lanes') \ + .tooltip('Set the number of outer lanes for the mowing automation') + ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.1, max=2.0) \ + .props('dense outlined suffix=m') \ + .classes('w-32') \ + .bind_value(self.system.mowing, 'minimum_turning_radius') \ + .tooltip('Set the turning radius for the mowing automation') with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='weeding'): - ui.separator() - ui.markdown('Field settings').style('color: #6E93D6') - with ui.row(): ui.separator() ui.markdown('Field settings').style('color: #6E93D6') with ui.row(): @@ -99,107 +117,204 @@ def show_field_selection() -> None: ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0) \ .props('dense outlined suffix=m').classes('w-30').bind_value( self.system.weeding, 'minimum_turning_radius').tooltip( - 'Set the turning radius for the weeding automation') - ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0) \ + ui.number('Min. turning radius', format='%.2f', + value=0.5, step=0.05, min=0.05, max=2.0) .props('dense outlined suffix=m').classes('w-30').bind_value( - self.system.weeding, 'turn_offset').tooltip( - 'Set the turning offset for the weeding automation') - ui.separator() - ui.markdown('Detector settings').style('color: #6E93D6') - with ui.row(): - ui.number('Min. weed confidence', format='%.2f', value=0.8, step=0.05, min=0.0, max=1.0) \ - .props('dense outlined').classes('w-24').bind_value( - self.system.plant_locator, 'minimum_weed_confidence').tooltip( - 'Set the minimum weed confidence for the weeding automation') - ui.number('Min. crop confidence', format='%.2f', value=0.4, step=0.05, min=0.0, max=1.0) \ - .props('dense outlined').classes('w-24').bind_value( - self.system.plant_locator, 'minimum_crop_confidence').tooltip( - 'Set the minimum crop confidence for the weeding automation') - ui.separator() - ui.markdown('Tool settings').style('color: #6E93D6') - with ui.row(): - if self.system.field_friend.tool == 'tornado': - ui.number('Tornado angle', format='%.0f', value=180, step=1, min=1, max=180).props( - 'dense outlined suffix=°').classes('w-24').bind_value( - self.system.weeding, 'tornado_angle').tooltip( - 'Set the angle for the tornado drill') - elif self.system.field_friend.tool in ['weed_screw', 'dual_mechanism']: - ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, - min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1) \ + self.system.weeding, 'minimum_turning_radius').tooltip( + 'Set the turning radius for the weeding automation') + ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0) + .props('dense outlined suffix=m').classes('w-30').bind_value( + self.system.weeding, 'turn_offset').tooltip( + 'Set the turning offset for the weeding automation') + ui.separator() + ui.markdown('Detector settings').style('color: #6E93D6') + with ui.row(): + ui.number('Min. weed confidence', format='%.2f', + value=0.8, step=0.05, min=0.0, max=1.0) + .props('dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_weed_confidence').tooltip( + 'Set the minimum weed confidence for the weeding automation') + ui.number('Min. crop confidence', format='%.2f', + value=0.4, step=0.05, min=0.0, max=1.0) + .props('dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_crop_confidence').tooltip( + 'Set the minimum crop confidence for the weeding automation') + ui.separator() + ui.markdown('Tool settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': + ui.number('turn_offset', format='%.2f', value=0.4, step=0.05, min=0.05, max=2.0) + .props('dense outlined suffix=m').classes('w-30').bind_value( + self.system.weeding, 'turn_offset').tooltip( + 'Set the turning offset for the weeding automation') + ui.separator() + ui.markdown('Detector settings').style('color: #6E93D6') + with ui.row(): + ui.number('Min. weed confidence', format='%.2f', + value=0.8, step=0.05, min=0.0, max=1.0) + .props('dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_weed_confidence').tooltip( + 'Set the minimum weed confidence for the weeding automation') + ui.number('Min. crop confidence', format='%.2f', + value=0.4, step=0.05, min=0.0, max=1.0) + .props('dense outlined').classes('w-24').bind_value( + self.system.plant_locator, 'minimum_crop_confidence').tooltip( + 'Set the minimum crop confidence for the weeding automation') + ui.separator() + ui.markdown('Tool settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': + ui.number('Tornado angle', format='%.0f', value=180, step=1, min=1, max=180).props( + 'dense outlined suffix=°').classes('w-24').bind_value( + self.system.weeding, 'tornado_angle').tooltip( + 'Set the angle for the tornado drill') + elif self.system.field_friend.tool in ['weed_screw', 'dual_mechanism']: + ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, + min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1) .props('dense outlined suffix=°').classes('w-24').bind_value( - self.system.weeding, 'weed_screw_depth').tooltip( - 'Set the drill depth for the weeding automation') - ui.number('Crop safety distance', value=0.01, step=0.01, min=0.0, max=0.05, format='%.2f').props( - 'dense outlined suffix=m').classes('w-24').bind_value( - self.system.weeding, 'crop_safety_distance').tooltip( - 'Set the crop safety distance for the weeding automation') + self.system.weeding, 'weed_screw_depth').tooltip( + 'Set the drill depth for the weeding automation') + ui.number('Crop safety distance', value=0.01, step=0.01, min=0.0, max=0.05, format='%.2f').props( + 'dense outlined suffix=m').classes('w-24').bind_value( + self.system.weeding, 'crop_safety_distance').tooltip( + 'Set the crop safety distance for the weeding automation') - ui.separator() - ui.markdown('Workflow settings').style('color: #6E93D6') - with ui.row(): - if self.system.field_friend.tool == 'tornado': - ui.checkbox('Drill 2x with open torando', value=False, on_change=system.weeding.invalidate).bind_value( - self.system.weeding, 'drill_with_open_tornado').tooltip( - 'Set the weeding automation to drill a second time with open tornado') - ui.checkbox('Drill between crops', value=False).bind_value( - self.system.weeding, 'drill_between_crops').tooltip( - 'Set the weeding automation to drill between crops') - elif self.system.field_friend.tool == 'dual_mechanism': + ui.separator() + ui.markdown('Workflow settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': + ui.checkbox('Drill 2x with open torando', value=False, on_change=system.weeding.invalidate).bind_value( + self.system.weeding, 'drill_with_open_tornado').tooltip( + 'Set the weeding automation to drill a second time with open tornado') + ui.checkbox('Drill between crops', value=False).bind_value( + self.system.weeding, 'drill_between_crops').tooltip( + 'Set the weeding automation to drill between crops') + elif self.system.field_friend.tool == 'dual_mechanism': - ui.checkbox('Drilling', value=False).bind_value( - self.system.weeding, 'with_drilling').tooltip( - 'Set the weeding automation to with drill') - ui.checkbox('Chopping', value=False).bind_value( - self.system.weeding, 'with_chopping').tooltip( - 'Set the weeding automation to with chop') + ui.checkbox('Drilling', value=False).bind_value( + self.system.weeding, 'with_drilling').tooltip( + 'Set the weeding automation to with drill') + ui.checkbox('Chopping', value=False).bind_value( + self.system.weeding, 'with_chopping').tooltip( + 'Set the weeding automation to with chop') - ui.checkbox('Chop if no crops', value=False).bind_value( - self.system.weeding, 'chop_if_no_crops').tooltip( - 'Set the weeding automation to chop also if no crops seen') + ui.checkbox('Chop if no crops', value=False).bind_value( + self.system.weeding, 'chop_if_no_crops').tooltip( + 'Set the weeding automation to chop also if no crops seen') - ui.checkbox('Only monitoring').bind_value( - self.system.weeding, 'only_monitoring').tooltip( - 'Set the weeding automation to only monitor the field') - ui.separator() - ui.markdown('**Driver settings**').style('color: #6E93D6') - with ui.row(): - ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') \ - .props('dense outlined suffix=m/s').classes('w-24') \ - .bind_value(self.system.weeding, 'linear_speed_on_row') \ - .tooltip('Set the linear speed on row for the weeding automation') - ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') \ - .props('dense outlined suffix=m/s').classes('w-24') \ - .bind_value(self.system.weeding, 'linear_speed_between_rows') \ - .tooltip('Set the linear speed between rows for the weeding automation') - ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') \ - .props('dense outlined suffix=°/s').classes('w-24') \ - .bind_value(self.system.weeding, 'angular_speed_on_row') \ - .tooltip('Set the angular speed on row for the weeding automation') - ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') \ - .props('dense outlined suffix=°/s').classes('w-24') \ - .bind_value(self.system.weeding, 'angular_speed_between_rows') \ - .tooltip('Set the angular speed between rows for the weeding automation') + ui.checkbox('Only monitoring').bind_value( + self.system.weeding, 'only_monitoring').tooltip( + 'Set the weeding automation to only monitor the field') + ui.separator() + ui.markdown('**Driver settings**').style('color: #6E93D6') + with ui.row(): + ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=m/s').classes('w-24') + .bind_value(self.system.weeding, 'linear_speed_on_row') + .tooltip('Set the linear speed on row for the weeding automation') + ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=m/s').classes('w-24') + .bind_value(self.system.weeding, 'linear_speed_between_rows') + .tooltip('Set the linear speed between rows for the weeding automation') + ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=°/s').classes('w-24') + .bind_value(self.system.weeding, 'angular_speed_on_row') + .tooltip('Set the angular speed on row for the weeding automation') + ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=°/s').classes('w-24') + .bind_value(self.system.weeding, 'angular_speed_between_rows') + .tooltip('Set the angular speed between rows for the weeding automation') + elif self.system.field_friend.tool in ['weed_screw', 'dual_mechanism']: + ui.number('Drill depth', value=0.02, format='%.2f', step=0.01, + min=self.system.field_friend.z_axis.max_position, max=self.system.field_friend.z_axis.min_position*-1) + .props('dense outlined suffix=°').classes('w-24').bind_value( + self.system.weeding, 'weed_screw_depth').tooltip( + 'Set the drill depth for the weeding automation') + ui.number('Crop safety distance', value=0.01, step=0.01, min=0.0, max=0.05, format='%.2f').props( + 'dense outlined suffix=m').classes('w-24').bind_value( + self.system.weeding, 'crop_safety_distance').tooltip( + 'Set the crop safety distance for the weeding automation') - with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='monitoring'): - with ui.column(): - self.with_field_planning_monitor = ui.checkbox('Use field planning', value=True) \ - .bind_value(self.system.monitoring, 'use_field_planning').tooltip('Set the monitoring automation to use the field planning with GNSS') + ui.separator() + ui.markdown('Workflow settings').style('color: #6E93D6') + with ui.row(): + if self.system.field_friend.tool == 'tornado': + ui.checkbox('Drill 2x with open torando', value=False, on_change=system.weeding.invalidate).bind_value( + self.system.weeding, 'drill_with_open_tornado').tooltip( + 'Set the weeding automation to drill a second time with open tornado') + ui.checkbox('Drill between crops', value=False).bind_value( + self.system.weeding, 'drill_between_crops').tooltip( + 'Set the weeding automation to drill between crops') + elif self.system.field_friend.tool == 'dual_mechanism': - with ui.row().bind_visibility_from(self.with_field_planning_monitor, 'value', value=True): - self.show_start_row() - self.show_end_row() - ui.number('Min. turning radius', format='%.2f', value=0.5, step=0.05, min=0.05, max=2.0) \ - .props('dense outlined suffix=m').classes('w-30') \ - .bind_value(self.system.monitoring, 'minimum_turning_radius') \ + ui.checkbox('Drilling', value=False).bind_value( + self.system.weeding, 'with_drilling').tooltip( + 'Set the weeding automation to with drill') + ui.checkbox('Chopping', value=False).bind_value( + self.system.weeding, 'with_chopping').tooltip( + 'Set the weeding automation to with chop') + + ui.checkbox('Chop if no crops', value=False).bind_value( + self.system.weeding, 'chop_if_no_crops').tooltip( + 'Set the weeding automation to chop also if no crops seen') + + ui.checkbox('Only monitoring').bind_value( + self.system.weeding, 'only_monitoring').tooltip( + 'Set the weeding automation to only monitor the field') + ui.separator() + ui.markdown('**Driver settings**').style('color: #6E93D6') + with ui.row(): + ui.number('linear_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=m/s').classes('w-24') + .bind_value(self.system.weeding, 'linear_speed_on_row') + .tooltip('Set the linear speed on row for the weeding automation') + ui.number('linear_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=m/s').classes('w-24') + .bind_value(self.system.weeding, 'linear_speed_between_rows') + .tooltip('Set the linear speed between rows for the weeding automation') + ui.number('angular_speed_on_row', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=°/s').classes('w-24') + .bind_value(self.system.weeding, 'angular_speed_on_row') + .tooltip('Set the angular speed on row for the weeding automation') + ui.number('angular_speed_between_rows', value=0.5, step=0.1, min=0.1, format='%.1f') + .props('dense outlined suffix=°/s').classes('w-24') + .bind_value(self.system.weeding, 'angular_speed_between_rows') + .tooltip('Set the angular speed between rows for the weeding automation') + + with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='monitoring'): + with ui.column(): + self.with_field_planning_monitor=ui.checkbox('Use field planning', value=True) + .bind_value(self.system.monitoring, 'use_field_planning').tooltip('Set the monitoring automation to use the field planning with GNSS') + self.with_field_planning_monitor=ui.checkbox('Use field planning', value=True) + .bind_value(self.system.monitoring, 'use_field_planning').tooltip('Set the monitoring automation to use the field planning with GNSS') + + with ui.row().bind_visibility_from(self.with_field_planning_monitor, 'value', value=True): + self.show_start_row() + self.show_end_row() + ui.number('Min. turning radius', format='%.2f', + value=0.5, step=0.05, min=0.05, max=2.0) + .props('dense outlined suffix=m').classes('w-30') + .bind_value(self.system.monitoring, 'minimum_turning_radius') + .tooltip('Set the turning radius for the monitoring automation') + ui.number('Min. turning radius', format='%.2f', + value=0.5, step=0.05, min=0.05, max=2.0) + .props('dense outlined suffix=m').classes('w-30') + .bind_value(self.system.monitoring, 'minimum_turning_radius') .tooltip('Set the turning radius for the monitoring automation') - with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='collecting (demo)'): - with ui.row(): - ui.number( - 'Drill angle', format='%.0f', value=100, step=1, min=1, max=180) \ - .props('dense outlined suffix=°').classes('w-24') \ - .bind_value(self.system.coin_collecting, 'angle') \ - .tooltip('Set the drill depth for the weeding automation') + with ui.column().bind_visibility_from(self.automations_toggle, 'value', value='collecting (demo)'): + with ui.row(): + ui.number( + 'Drill angle', format='%.0f', value=100, step=1, min=1, max=180) + .props('dense outlined suffix=°').classes('w-24') + .bind_value(self.system.coin_collecting, 'angle') + .tooltip('Set the drill depth for the weeding automation') + ui.checkbox('with drilling', value=True) + .bind_value(self.system.coin_collecting, 'with_drilling') + 'Drill angle', format='%.0f', value=100, step=1, min=1, max=180) \ + .props('dense outlined suffix=°').classes('w-24') \ + .bind_value(self.system.coin_collecting, 'angle') \ + .tooltip('Set the drill depth for the weeding automation') ui.checkbox('with drilling', value=True) \ .bind_value(self.system.coin_collecting, 'with_drilling') ui.space() @@ -210,10 +325,16 @@ def show_field_selection() -> None: ui.button('emergency reset', on_click=lambda: system.field_friend.estop.set_soft_estop(False)) \ .props('color=red-700 outline').classes('py-3 px-6 text-lg') \ .bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=True) + ui.button('emergency stop', on_click=lambda: system.field_friend.estop.set_soft_estop(True)).props('color=red') \ + .classes('py-3 px-6 text-lg').bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=False) + ui.button('emergency reset', on_click=lambda: system.field_friend.estop.set_soft_estop(False)) \ + .props('color=red-700 outline').classes('py-3 px-6 text-lg') \ + .bind_visibility_from(system.field_friend.estop, 'is_soft_estop_active', value=True) ui.space() with ui.row(): - automation_controls(self.system, can_start=self.ensure_start) + automation_controls(self.system, can_start=self.can_start) with ui.dialog() as self.dialog, ui.card(): + self.dialog_label = ui.label('Do you want to continue the canceled automation').classes('text-lg') self.dialog_label = ui.label('Do you want to continue the canceled automation').classes('text-lg') with ui.row(): ui.button('Yes', on_click=lambda: self.dialog.submit('Yes')) @@ -225,23 +346,32 @@ def show_start_row(self) -> None: if self.field_provider.active_field is not None: ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='Start row') \ .bind_value(self.system.weeding, 'start_row_id').classes('w-24').tooltip('Select the row to start on') + ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='Start row') \ + .bind_value(self.system.weeding, 'start_row_id').classes('w-24').tooltip('Select the row to start on') else: ui.select([None], label='Start row')\ .bind_value(self.system.weeding, 'start_row').classes('w-24').tooltip('Select the row to start on') + ui.select([None], label='Start row')\ + .bind_value(self.system.weeding, 'start_row').classes('w-24').tooltip('Select the row to start on') @ui.refreshable def show_end_row(self) -> None: if self.field_provider.active_field is not None: ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='End row') \ .bind_value(self.system.weeding, 'end_row_id').classes('w-24').tooltip('Select the row to end on') + ui.select({row.id: row.name for row in self.field_provider.active_field.rows}, label='End row') \ + .bind_value(self.system.weeding, 'end_row_id').classes('w-24').tooltip('Select the row to end on') else: ui.select([None], label='End row') \ .bind_value(self.system.weeding, 'end_row').classes('w-24').tooltip('Select the row to end on') + ui.select([None], label='End row') \ + .bind_value(self.system.weeding, 'end_row').classes('w-24').tooltip('Select the row to end on') def set_field(self) -> None: for field in self.system.field_provider.fields: if field.id == self.field_selection.value: self.field_provider.select_field(field) + app.storage.user['field'] = field.id if len(field.outline_wgs84) > 0: self.system.gnss.set_reference(field.outline_wgs84[0][0], field.outline_wgs84[0][1]) # TODO das hier noch auf das active field umbauen, damit auch diese werte im weeding auf das active field registriert sind @@ -250,16 +380,16 @@ def set_field(self) -> None: self.show_start_row.refresh() self.show_end_row.refresh() - async def ensure_start(self) -> bool: - self.log.info('Ensuring start of automation') + async def can_start(self) -> bool: + self.log.info('Checking if automation can be started') if self.automations_toggle.value == 'mowing': - return await self.ensure_mowing_start() + return await self.can_mowing_start() elif self.automations_toggle.value == 'weeding': - return await self.ensure_weeding_start() + return await self.can_weeding_start() return True - async def ensure_mowing_start(self) -> bool: - self.log.info('Ensuring start of mowing automation') + async def can_mowing_start(self) -> bool: + self.log.info('Checking mowing automation') if self.system.mowing.current_path_segment is None: self.system.mowing.continue_mowing = False return True @@ -273,8 +403,8 @@ async def ensure_mowing_start(self) -> bool: return False return True - async def ensure_weeding_start(self) -> bool: - self.log.info('Ensuring start of weeding automation') + async def can_weeding_start(self) -> bool: + self.log.info('Checking weeding automation') if not self.system.weeding.current_row or not self.system.weeding.current_segment: self.system.weeding.continue_canceled_weeding = False return True @@ -287,3 +417,7 @@ async def ensure_weeding_start(self) -> bool: elif result == 'Cancel': return False return True + + def handle_automation_changed(self, e: events.ValueChangeEventArguments) -> None: + app.storage.user.update({'automation': e.value}) + self.system.automator.default_automation = self.system.automations[e.value] diff --git a/field_friend/interface/pages/field_planner_page.py b/field_friend/interface/pages/field_planner_page.py index 4501f99c..feec9881 100644 --- a/field_friend/interface/pages/field_planner_page.py +++ b/field_friend/interface/pages/field_planner_page.py @@ -24,5 +24,7 @@ def content(self) -> None: with ui.row().classes('items-stretch justify-items-stretch').style('flex-wrap:nowrap; height:40%; max-height:40%;'): leaflet_map_field = leaflet_map(self.system, True) leaflet_map_field.m.style('height: 100%; max-height:100%;') + with ui.column(): + leaflet_map_field.buttons() with ui.row().classes('items-stretch justify-items-stretch').style('flex-wrap:nowrap; height: 60%; max-height:60%;'): field_planner(self.system.field_provider, self.system.odometer, self.system.gnss, leaflet_map_field) diff --git a/field_friend/interface/pages/main_page.py b/field_friend/interface/pages/main_page.py index 5148fae5..058d6779 100644 --- a/field_friend/interface/pages/main_page.py +++ b/field_friend/interface/pages/main_page.py @@ -26,8 +26,9 @@ def content(self, devmode) -> None: with splitter.before: with ui.column().classes('h-full p-2').style('width: 100%;'): leaflet_map_landing = leaflet_map(self.system, False) - leaflet_map_landing.m.classes( - 'h-full w-full') + leaflet_map_landing.m.classes('h-full w-full') + with ui.row(): + leaflet_map_landing.buttons() with splitter.after: with ui.row().classes('h-full ml-2 m-2').style('width: calc(100% - 1rem)'): with ui.column().style('width: 55%; height: 100%; flex-wrap: nowrap;'): @@ -36,7 +37,11 @@ def content(self, devmode) -> None: with ui.card().classes('w-full h-full p-0').style('margin-bottom: 10px;'): with ui.scroll_area().classes('w-full h-full'): with ui.card().classes('w-full'): - camera_card(self.system.usb_camera_provider, self.system.automator, self.system.detector, self.system.plant_locator, self.system.field_friend, + camera_card(self.system.usb_camera_provider, + self.system.automator, + self.system.detector, + self.system.plant_locator, + self.system.field_friend, self.system.puncher) with ui.card().classes('w-full'): robot_scene(self.system) diff --git a/main.py b/main.py index c2e570fe..96a4ec9a 100755 --- a/main.py +++ b/main.py @@ -16,8 +16,7 @@ def startup() -> None: system = System() def page_wrapper() -> None: - drawer = status_drawer(system, system.field_friend, system.gnss, - system.odometer, system.automator) + drawer = status_drawer(system, system.field_friend, system.gnss, system.odometer, system.automator) header_bar(system, drawer) system_bar() @@ -40,6 +39,7 @@ def status(): ui.run(title='Field Friend', port=80, + storage_secret='feldfreund', favicon='assets/favicon.ico', binding_refresh_interval=0.3, reconnect_timeout=10,