From 3b4c4e1199ceca8b5de3f4795899ae181a1748bc Mon Sep 17 00:00:00 2001 From: Dmitrii Kapitan Date: Wed, 31 Jul 2024 10:09:58 +0200 Subject: [PATCH] trampoline classes for abstract c++ classes --- python/helios/__init__.py | 18 +- python/helios/leg.py | 46 +- python/helios/platform.py | 312 +++++----- python/helios/platformsettings.py | 60 +- python/helios/scanner.py | 140 ++--- python/helios/scannersettings.py | 202 +++--- python/helios/scene.py | 74 +-- python/helios/scenepart.py | 272 ++++----- python/helios/survey.py | 258 ++++---- src/binds/GLMTypeCaster.h | 36 ++ src/binds/NoiseSourceWrap.h | 11 + src/binds/ScannerWrap.h | 775 ++++++++++++++++++++++++ src/binds/SimulationCycleCallbackWrap.h | 24 + src/binds/SimulationWrap.h | 31 + src/binds/utils.h | 32 + src/scanner/Scanner.h | 2 +- tests/python/test_leg.py | 22 +- tests/python/test_platform.py | 58 +- tests/python/test_scannersettings.py | 104 ++-- tests/python/test_scene.py | 44 +- tests/python/test_scenepart.py | 224 +++---- 21 files changed, 1827 insertions(+), 918 deletions(-) create mode 100755 src/binds/GLMTypeCaster.h create mode 100755 src/binds/NoiseSourceWrap.h create mode 100755 src/binds/ScannerWrap.h create mode 100755 src/binds/SimulationCycleCallbackWrap.h create mode 100755 src/binds/SimulationWrap.h create mode 100644 src/binds/utils.h mode change 100644 => 100755 tests/python/test_scannersettings.py diff --git a/python/helios/__init__.py b/python/helios/__init__.py index 758473900..87ae8dfac 100755 --- a/python/helios/__init__.py +++ b/python/helios/__init__.py @@ -1,9 +1,9 @@ -from helios.leg import Leg -from helios.platformsettings import PlatformSettings -from helios.scannersettings import ScannerSettings -from helios.platform import Platform -from helios.scanner import Scanner -from helios.survey import Survey -from helios.scenepart import ScenePart -from helios.scene import Scene -from helios.util import * # change this to import only the necessary functions later \ No newline at end of file +# from helios.leg import Leg +# from helios.platformsettings import PlatformSettings +# from helios.scannersettings import ScannerSettings +# from helios.platform import Platform +# from helios.scanner import Scanner +# from helios.survey import Survey +# from helios.scenepart import ScenePart +# from helios.scene import Scene +# #from helios.util import * # change this to import only the necessary functions later \ No newline at end of file diff --git a/python/helios/leg.py b/python/helios/leg.py index 1ae634be1..0a63dca05 100755 --- a/python/helios/leg.py +++ b/python/helios/leg.py @@ -1,26 +1,26 @@ -from pydantic import BaseModel, Field, NonNegativeInt -from typing import Optional, Union -from helios.scannersettings import ScannerSettings -from helios.platformsettings import PlatformSettings +# from pydantic import BaseModel, Field, NonNegativeInt +# from typing import Optional, Union +# from helios.scannersettings import ScannerSettings +# from helios.platformsettings import PlatformSettings -import numpy as np +# import numpy as np -class Leg(BaseModel): - scanner_settings: Optional[ScannerSettings] = Field(default=None) - platform_settings: Optional[PlatformSettings] = Field(default=None) - trajectory_settings: Optional[TrajectorySettings] = Field(default=None) - length: float = Field(default=0.0) - serial_id: NonNegativeInt - strip: Optional[ScanningStrip] = Field(default=None) - was_processed: bool = Field(default=False) - look_at: Optional[Union[np.ndarray, int]] = Field(default=None) # Only for TLS!! Might transfer to utils - position: Optional[np.ndarray] = Field(default=None) - horizontal_fov: Optional[float] = Field(default=None) - vertical_fov: Optional[float] = Field(default=None) - horizontal_resolution: Optional[float] = Field(default=None) - vertical_resolution: Optional[float] = Field(default=None) - is_active: bool = Field(default=True) +# class Leg(BaseModel): +# scanner_settings: Optional[ScannerSettings] = Field(default=None) +# platform_settings: Optional[PlatformSettings] = Field(default=None) +# trajectory_settings: Optional[TrajectorySettings] = Field(default=None) +# length: float = Field(default=0.0) +# serial_id: NonNegativeInt +# strip: Optional[ScanningStrip] = Field(default=None) +# was_processed: bool = Field(default=False) +# look_at: Optional[Union[np.ndarray, int]] = Field(default=None) # Only for TLS!! Might transfer to utils +# position: Optional[np.ndarray] = Field(default=None) +# horizontal_fov: Optional[float] = Field(default=None) +# vertical_fov: Optional[float] = Field(default=None) +# horizontal_resolution: Optional[float] = Field(default=None) +# vertical_resolution: Optional[float] = Field(default=None) +# is_active: bool = Field(default=True) - @property - def belongs_to_strip(self) -> bool: - return self.strip is not None +# @property +# def belongs_to_strip(self) -> bool: +# return self.strip is not None diff --git a/python/helios/platform.py b/python/helios/platform.py index c5dad0bae..baade5e62 100755 --- a/python/helios/platform.py +++ b/python/helios/platform.py @@ -1,169 +1,169 @@ -from typing import Optional -from pydantic import BaseModel -import numpy as np -import math -from helios.scene import Scene -from helios.platformsettings import PlatformSettings - -class Platform(BaseModel): - device_relative_position: np.ndarray = np.array([0.0, 0.0, 0.0]) - device_relative_attitude: Rotation = Rotation() - scene: Optional[Scene] = None - - position_x_noise_source: Optional[NoiseSource] = None - position_y_noise_source: Optional[NoiseSource] = None - position_z_noise_source: Optional[NoiseSource] = None - attitude_x_noise_source: Optional[NoiseSource] = None - attitude_y_noise_source: Optional[NoiseSource] = None - attitude_z_noise_source: Optional[NoiseSource] = None - - settings_speed_m_s: float = 0.0 - _origin_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) - _target_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) - _next_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) - is_on_ground: bool = False - is_stop_and_turn: bool = True - is_smooth_turn: bool = False - is_slowdown_enabled: bool = True - - _position: np.ndarray = np.array([0.0, 0.0, 0.0]) - _attitude: Rotation = Rotation() - #caches - cached_absolute_mount_position: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_absolute_mount_attitude: Rotation = Rotation() - cached_dir_current: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_dir_current_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_array_to_target: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_array_to_target_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_distance_to_target_xy: float = 0.0 - cached_origin_to_target_dir_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_target_to_next_dir_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) - cached_end_target_angle_xy: float = 0.0 - cached_current_angle_xy: float = 0.0 - cached_origin_to_target_angle_xy: float = 0.0 - cached_target_to_next_angle_xy: float = 0.0 - - - #Проверить нужны ли эти декораторы во всех файлах!!!!!! - @property - def absolute_mount_attitude(self) -> Rotation: - return self.cached_absolute_mount_attitude - - @property - def absolute_mount_position(self) -> np.ndarray: - return self.cached_absolute_mount_position +# from typing import Optional +# from pydantic import BaseModel +# import numpy as np +# import math +# from helios.scene import Scene +# from helios.platformsettings import PlatformSettings + +# class Platform(BaseModel): +# device_relative_position: np.ndarray = np.array([0.0, 0.0, 0.0]) +# device_relative_attitude: Rotation = Rotation() +# scene: Optional[Scene] = None + +# position_x_noise_source: Optional[NoiseSource] = None +# position_y_noise_source: Optional[NoiseSource] = None +# position_z_noise_source: Optional[NoiseSource] = None +# attitude_x_noise_source: Optional[NoiseSource] = None +# attitude_y_noise_source: Optional[NoiseSource] = None +# attitude_z_noise_source: Optional[NoiseSource] = None + +# settings_speed_m_s: float = 0.0 +# _origin_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) +# _target_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) +# _next_waypoint: np.ndarray = np.array([0.0, 0.0, 0.0]) +# is_on_ground: bool = False +# is_stop_and_turn: bool = True +# is_smooth_turn: bool = False +# is_slowdown_enabled: bool = True + +# _position: np.ndarray = np.array([0.0, 0.0, 0.0]) +# _attitude: Rotation = Rotation() +# #caches +# cached_absolute_mount_position: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_absolute_mount_attitude: Rotation = Rotation() +# cached_dir_current: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_dir_current_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_array_to_target: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_array_to_target_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_distance_to_target_xy: float = 0.0 +# cached_origin_to_target_dir_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_target_to_next_dir_xy: np.ndarray = np.array([0.0, 0.0, 0.0]) +# cached_end_target_angle_xy: float = 0.0 +# cached_current_angle_xy: float = 0.0 +# cached_origin_to_target_angle_xy: float = 0.0 +# cached_target_to_next_angle_xy: float = 0.0 + + +# #Проверить нужны ли эти декораторы во всех файлах!!!!!! +# @property +# def absolute_mount_attitude(self) -> Rotation: +# return self.cached_absolute_mount_attitude + +# @property +# def absolute_mount_position(self) -> np.ndarray: +# return self.cached_absolute_mount_position - @property - def attitude(self) -> Rotation: - return self._attitude - - @attitude.setter - def attitude(self, value: Rotation): - self._attitude = value - self.cached_absolute_mount_attitude = self._attitude.Rotation.apply_to(self.device_relative_attitude) - - @property - def position(self) -> np.ndarray: - return self._position +# @property +# def attitude(self) -> Rotation: +# return self._attitude + +# @attitude.setter +# def attitude(self, value: Rotation): +# self._attitude = value +# self.cached_absolute_mount_attitude = self._attitude.Rotation.apply_to(self.device_relative_attitude) + +# @property +# def position(self) -> np.ndarray: +# return self._position - @position.setter - def position(self, value: np.ndarray): - self._position = value - self.cached_absolute_mount_position = self._position + self.device_relative_position - self.update_dynamic_cache() - - @property - def origin_waypoint(self) -> np.ndarray: - return self._origin_waypoint +# @position.setter +# def position(self, value: np.ndarray): +# self._position = value +# self.cached_absolute_mount_position = self._position + self.device_relative_position +# self.update_dynamic_cache() + +# @property +# def origin_waypoint(self) -> np.ndarray: +# return self._origin_waypoint - @origin_waypoint.setter - def origin_waypoint(self, value: np.ndarray): - self._origin_waypoint = value - self.update_static_cache() +# @origin_waypoint.setter +# def origin_waypoint(self, value: np.ndarray): +# self._origin_waypoint = value +# self.update_static_cache() - @property - def target_waypoint(self) -> np.ndarray: - return self._target_waypoint +# @property +# def target_waypoint(self) -> np.ndarray: +# return self._target_waypoint - @target_waypoint.setter - def target_waypoint(self, value: np.ndarray): - self._target_waypoint = value - self.update_static_cache() - - @property - def next_waypoint(self) -> np.ndarray: - return self._next_waypoint +# @target_waypoint.setter +# def target_waypoint(self, value: np.ndarray): +# self._target_waypoint = value +# self.update_static_cache() + +# @property +# def next_waypoint(self) -> np.ndarray: +# return self._next_waypoint - @next_waypoint.setter - def next_waypoint(self, value: np.ndarray): - self._next_waypoint = value - self.update_static_cache() - - @property - def current_settings(self) -> PlatformSettings: - current_settings = PlatformSettings() - current_settings.speed_m_s = self.settings_speed_m_s - current_settings.is_on_ground = self.is_on_ground - current_settings.position(self.position) - return self.current_settings +# @next_waypoint.setter +# def next_waypoint(self, value: np.ndarray): +# self._next_waypoint = value +# self.update_static_cache() + +# @property +# def current_settings(self) -> PlatformSettings: +# current_settings = PlatformSettings() +# current_settings.speed_m_s = self.settings_speed_m_s +# current_settings.is_on_ground = self.is_on_ground +# current_settings.position(self.position) +# return self.current_settings - def apply_settings(self, settings: PlatformSettings) -> None: - self.settings_speed_m_s = settings.speed_m_s - self.is_on_ground = settings.is_on_ground - self.position = settings.position +# def apply_settings(self, settings: PlatformSettings) -> None: +# self.settings_speed_m_s = settings.speed_m_s +# self.is_on_ground = settings.is_on_ground +# self.position = settings.position - @property - def current_direction(self) -> np.ndarray: - return self.cached_current_direction +# @property +# def current_direction(self) -> np.ndarray: +# return self.cached_current_direction - @property - def is_interpolated(self) -> bool: - return self.cached_is_interpolating +# @property +# def is_interpolated(self) -> bool: +# return self.cached_is_interpolating - def update_static_cache(self) -> None: - self.cached_origin_to_target_dir_xy = np.array([ - self.target_waypoint[0] - self.origin_waypoint[0], - self.target_waypoint[1] - self.origin_waypoint[1], - 0.0 - ]) - self.cached_target_to_next_dir_xy = np.array([ - self.next_waypoint[0] - self.target_waypoint[0], - self.next_waypoint[1] - self.target_waypoint[1], - 0.0 - ]) - self.cached_end_target_angle_xy = np.arccos( - np.dot(self.cached_origin_to_target_dir_xy, self.cached_target_to_next_dir_xy) / - (np.linalg.norm(self.cached_origin_to_target_dir_xy) * np.linalg.norm(self.cached_target_to_next_dir_xy)) - ) - if np.isnan(self.cached_end_target_angle_xy): - self.cached_end_target_angle_xy = 0.0 - self.cached_origin_to_target_angle_xy = self.direction_to_angle_xy(self.cached_origin_to_target_dir_xy, True) # this function should be moved to a separate class - self.cached_target_to_next_angle_xy = self.direction_to_angle_xy(self.cached_target_to_next_dir_xy, True) # this function should be moved to a separate class - self.update_dynamic_cache() - - def update_dynamic_cache(self) -> None: - self.cached_array_to_target = self.target_waypoint - self.position - self.cached_array_to_target_xy = np.array([self.cached_array_to_target[0], self.cached_array_to_target[1], 0.0]) - self.cached_distance_to_target_xy = np.linalg.norm(self.cached_array_to_target_xy) - self.cached_dir_current = self.current_direction() - self.cached_dir_current_xy = np.array([self.cached_dir_current[0], self.cached_dir_current[1], 0.0]) - self.cached_current_angle_xy = np.arccos( - np.dot(self.cached_dir_current_xy, self.cached_target_to_next_dir_xy) / - (np.linalg.norm(self.cached_dir_current_xy) * np.linalg.norm(self.cached_target_to_next_dir_xy)) - ) - if np.isnan(self.cached_current_angle_xy): - self.cached_current_angle_xy = 0.0 - - - def direction_to_angle_xy(self, direction: np.ndarray, is_normalized: bool) -> float: # this function should be moved to a separate class - angle = np.arctan2(direction[0], direction[1]) - if is_normalized and angle < 0.0: - angle += np.pi * 2 - return angle +# def update_static_cache(self) -> None: +# self.cached_origin_to_target_dir_xy = np.array([ +# self.target_waypoint[0] - self.origin_waypoint[0], +# self.target_waypoint[1] - self.origin_waypoint[1], +# 0.0 +# ]) +# self.cached_target_to_next_dir_xy = np.array([ +# self.next_waypoint[0] - self.target_waypoint[0], +# self.next_waypoint[1] - self.target_waypoint[1], +# 0.0 +# ]) +# self.cached_end_target_angle_xy = np.arccos( +# np.dot(self.cached_origin_to_target_dir_xy, self.cached_target_to_next_dir_xy) / +# (np.linalg.norm(self.cached_origin_to_target_dir_xy) * np.linalg.norm(self.cached_target_to_next_dir_xy)) +# ) +# if np.isnan(self.cached_end_target_angle_xy): +# self.cached_end_target_angle_xy = 0.0 +# self.cached_origin_to_target_angle_xy = self.direction_to_angle_xy(self.cached_origin_to_target_dir_xy, True) # this function should be moved to a separate class +# self.cached_target_to_next_angle_xy = self.direction_to_angle_xy(self.cached_target_to_next_dir_xy, True) # this function should be moved to a separate class +# self.update_dynamic_cache() + +# def update_dynamic_cache(self) -> None: +# self.cached_array_to_target = self.target_waypoint - self.position +# self.cached_array_to_target_xy = np.array([self.cached_array_to_target[0], self.cached_array_to_target[1], 0.0]) +# self.cached_distance_to_target_xy = np.linalg.norm(self.cached_array_to_target_xy) +# self.cached_dir_current = self.current_direction() +# self.cached_dir_current_xy = np.array([self.cached_dir_current[0], self.cached_dir_current[1], 0.0]) +# self.cached_current_angle_xy = np.arccos( +# np.dot(self.cached_dir_current_xy, self.cached_target_to_next_dir_xy) / +# (np.linalg.norm(self.cached_dir_current_xy) * np.linalg.norm(self.cached_target_to_next_dir_xy)) +# ) +# if np.isnan(self.cached_current_angle_xy): +# self.cached_current_angle_xy = 0.0 + + +# def direction_to_angle_xy(self, direction: np.ndarray, is_normalized: bool) -> float: # this function should be moved to a separate class +# angle = np.arctan2(direction[0], direction[1]) +# if is_normalized and angle < 0.0: +# angle += np.pi * 2 +# return angle - def get_by_name(self, name: str) -> Optional['Platform']: - if self.name == name: - return self - return None \ No newline at end of file +# def get_by_name(self, name: str) -> Optional['Platform']: +# if self.name == name: +# return self +# return None \ No newline at end of file diff --git a/python/helios/platformsettings.py b/python/helios/platformsettings.py index 563a7b364..6c17e0d86 100755 --- a/python/helios/platformsettings.py +++ b/python/helios/platformsettings.py @@ -1,38 +1,38 @@ -from pydantic import BaseModel -from typing import Optional, Set +# from pydantic import BaseModel +# from typing import Optional, Set -class PlatformSettings(BaseModel): - name: str = "#nullid#" - basic_template: Optional["PlatformSettings"] = None - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 # combine 3 coordinates into a tuple? - is_yaw_angle_specified: bool = False - yaw_angle: float = 0.0 - is_on_ground: bool = False - is_stop_and_turn: bool = True - is_smooth_turn: bool = False - is_slowdown_enabled: bool = True - speed_m_s: float = 70.0 - altitude: float = 0.0 +# class PlatformSettings(BaseModel): +# name: str = "#nullid#" +# basic_template: Optional["PlatformSettings"] = None +# x: float = 0.0 +# y: float = 0.0 +# z: float = 0.0 # combine 3 coordinates into a tuple? +# is_yaw_angle_specified: bool = False +# yaw_angle: float = 0.0 +# is_on_ground: bool = False +# is_stop_and_turn: bool = True +# is_smooth_turn: bool = False +# is_slowdown_enabled: bool = True +# speed_m_s: float = 70.0 +# altitude: float = 0.0 - def cherry_pick(self, cherries: "PlatformSettings", fields: Set[str], template_fields: Optional[Set[str]] = None) -> "PlatformSettings": - settings = self.model_copy(deep=True) - for field in fields: - if hasattr(cherries, field): - setattr(settings, field, getattr(cherries, field)) - if "basic_template" in fields and cherries.basic_template: - if template_fields: - settings.basic_template = cherries.basic_template.cherry_pick(cherries.basic_template, template_fields) - else: - settings.basic_template = cherries.basic_template.model_copy(deep=True) - return settings +# def cherry_pick(self, cherries: "PlatformSettings", fields: Set[str], template_fields: Optional[Set[str]] = None) -> "PlatformSettings": +# settings = self.model_copy(deep=True) +# for field in fields: +# if hasattr(cherries, field): +# setattr(settings, field, getattr(cherries, field)) +# if "basic_template" in fields and cherries.basic_template: +# if template_fields: +# settings.basic_template = cherries.basic_template.cherry_pick(cherries.basic_template, template_fields) +# else: +# settings.basic_template = cherries.basic_template.model_copy(deep=True) +# return settings - @property - def has_template(self) -> bool: - return self.basic_template is not None +# @property +# def has_template(self) -> bool: +# return self.basic_template is not None diff --git a/python/helios/scanner.py b/python/helios/scanner.py index 5f273f8bc..2b6886ab6 100755 --- a/python/helios/scanner.py +++ b/python/helios/scanner.py @@ -1,79 +1,79 @@ -from typing import Optional, List -from pydantic import BaseModel, Field -import numpy as np -import threading -import json -from xml.etree.ElementTree import Element, SubElement, tostring, ElementTree +# from typing import Optional, List +# from pydantic import BaseModel, Field +# import numpy as np +# import threading +# import json +# from xml.etree.ElementTree import Element, SubElement, tostring, ElementTree -from helios.platform import Platform -from helios.platformsettings import PlatformSettings -from helios.scannersettings import ScannerSettings +# from helios.platform import Platform +# from helios.platformsettings import PlatformSettings +# from helios.scannersettings import ScannerSettings -class Scanner(BaseModel): - name: str = Field(default="SCANNER-ID") - write_waveform: bool = Field(default=False) - write_pulse: bool = Field(default=False) - calc_echowidth: bool = Field(default=False) - full_wave_noise: bool = Field(default=False) - platform_noise_disabled: bool = Field(default=False) - fixed_incidence_angle: bool = Field(default=False) - pulse_frequency: int = Field(default=0) - is_state_active: bool = Field(default=True) - trajectory_time_interval: float = Field(default=0.0) - last_trajectory_time: float = Field(default=0.0) +# class Scanner(BaseModel): +# name: str = Field(default="SCANNER-ID") +# write_waveform: bool = Field(default=False) +# write_pulse: bool = Field(default=False) +# calc_echowidth: bool = Field(default=False) +# full_wave_noise: bool = Field(default=False) +# platform_noise_disabled: bool = Field(default=False) +# fixed_incidence_angle: bool = Field(default=False) +# pulse_frequency: int = Field(default=0) +# is_state_active: bool = Field(default=True) +# trajectory_time_interval: float = Field(default=0.0) +# last_trajectory_time: float = Field(default=0.0) - platform: Optional[Platform] = None - scanning_pulse_process: Optional[ScanningPulseProcess] = None - fms: Optional[FMSFacade] = None - output_paths: Optional[List[str]] = None - all_measurements: Optional[List[Measurement]] = None - all_trajectories: Optional[List[Trajectory]] = None - all_measurements_mutex: Optional[threading.Lock] = None - cycle_measurements: Optional[List[Measurement]] = None - cycle_trajectories: Optional[List[Trajectory]] = None - cycle_measurements_mutex: Optional[threading.Lock] = None - rand_gen1: Optional[RandomnessGenerator] = None - rand_gen2: Optional[RandomnessGenerator] = None - intersection_handling_noise_source: Optional[UniformNoiseSource] = None +# platform: Optional[Platform] = None +# scanning_pulse_process: Optional[ScanningPulseProcess] = None +# fms: Optional[FMSFacade] = None +# output_paths: Optional[List[str]] = None +# all_measurements: Optional[List[Measurement]] = None +# all_trajectories: Optional[List[Trajectory]] = None +# all_measurements_mutex: Optional[threading.Lock] = None +# cycle_measurements: Optional[List[Measurement]] = None +# cycle_trajectories: Optional[List[Trajectory]] = None +# cycle_measurements_mutex: Optional[threading.Lock] = None +# rand_gen1: Optional[RandomnessGenerator] = None +# rand_gen2: Optional[RandomnessGenerator] = None +# intersection_handling_noise_source: Optional[UniformNoiseSource] = None - @property - def current_settings(self) -> ScannerSettings: - current_settings = ScannerSettings( - name=f"{self.name}_settings", - pulse_frequency=self.pulse_frequency, - is_active=self.is_state_active, - beam_divergence_angle=self.beam_divergence_angle(0), - trajectory_time_interval=self.trajectory_time_interval / 1e9, - head_rotation=self.get_scanner_head(0).get_rotate_start(), - rotation_start_angle=self.get_scanner_head(0).get_rotate_current(), - rotation_stop_angle=self.get_scanner_head(0).get_rotate_stop(), - scan_angle=self.get_beam_deflector(0).cfg_setting_scanAngle_rad, - scan_frequency=self.get_beam_deflector(0).cfg_setting_scanFreq_Hz, - min_vertical_angle=self.get_beam_deflector(0).cfg_setting_minVerticalAngle_rad, - max_vertical_angle=self.get_beam_deflector(0).cfg_setting_maxVerticalAngle_rad - ) - return current_settings +# @property +# def current_settings(self) -> ScannerSettings: +# current_settings = ScannerSettings( +# name=f"{self.name}_settings", +# pulse_frequency=self.pulse_frequency, +# is_active=self.is_state_active, +# beam_divergence_angle=self.beam_divergence_angle(0), +# trajectory_time_interval=self.trajectory_time_interval / 1e9, +# head_rotation=self.get_scanner_head(0).get_rotate_start(), +# rotation_start_angle=self.get_scanner_head(0).get_rotate_current(), +# rotation_stop_angle=self.get_scanner_head(0).get_rotate_stop(), +# scan_angle=self.get_beam_deflector(0).cfg_setting_scanAngle_rad, +# scan_frequency=self.get_beam_deflector(0).cfg_setting_scanFreq_Hz, +# min_vertical_angle=self.get_beam_deflector(0).cfg_setting_minVerticalAngle_rad, +# max_vertical_angle=self.get_beam_deflector(0).cfg_setting_maxVerticalAngle_rad +# ) +# return current_settings - def get_scanner_by_name(self, name: str) -> Optional['Scanner']: - if self.name == name: - return self - return None +# def get_scanner_by_name(self, name: str) -> Optional['Scanner']: +# if self.name == name: +# return self +# return None - def to_file(self, filename: str, format: str = 'json') -> None: - if format == 'json': - with open(filename, 'w') as file: - json.dump(self.dict(), file) - elif format == 'xml': - with open(filename, 'w') as file: - file.write(self.to_xml()) - else: - raise ValueError(f"Unsupported format: {format}") +# def to_file(self, filename: str, format: str = 'json') -> None: +# if format == 'json': +# with open(filename, 'w') as file: +# json.dump(self.dict(), file) +# elif format == 'xml': +# with open(filename, 'w') as file: +# file.write(self.to_xml()) +# else: +# raise ValueError(f"Unsupported format: {format}") - def to_xml(self) -> str: - root = Element('Scanner') - for field, value in self.dict().items(): - child = SubElement(root, field) - child.text = str(value) - return tostring(root, encoding='unicode') +# def to_xml(self) -> str: +# root = Element('Scanner') +# for field, value in self.dict().items(): +# child = SubElement(root, field) +# child.text = str(value) +# return tostring(root, encoding='unicode') diff --git a/python/helios/scannersettings.py b/python/helios/scannersettings.py index 69fca954e..e2ce65321 100755 --- a/python/helios/scannersettings.py +++ b/python/helios/scannersettings.py @@ -1,120 +1,120 @@ -from pydantic import BaseModel, Field -from typing import Optional, Set -import numpy as np -import json +# from pydantic import BaseModel, Field +# from typing import Optional, Set +# import numpy as np +# import json -class ScannerSettings(BaseModel): - name: str = Field("#nullid#", description="The name of the scanner settings.") - _basic_template: Optional["ScannerSettings"] = Field(None, description="A template to base settings off of.") - is_active: Optional[bool] = Field(True, description="Whether the scanner is active.") - head_rotation: Optional[float] = Field(0.0, description="The rotation angle of the scanner head.") - rotation_start_angle: Optional[float] = Field(0.0, description="The starting angle for rotation.") - rotation_stop_angle: Optional[float] = Field(0.0, description="The stopping angle for rotation.") - pulse_frequency: Optional[int] = Field(0, description="The frequency of pulses emitted by the scanner.") - scan_angle: Optional[float] = Field(0.0, description="The scanning angle of the scanner.") - min_vertical_angle: Optional[float] = Field(None, description="Minimum vertical scanning angle.") - max_vertical_angle: Optional[float] = Field(None, description="Maximum vertical scanning angle.") - scan_frequency: Optional[float] = Field(0.0, description="The frequency at which the scanner operates.") - beam_divergence_angle: Optional[float] = Field(0.003, description="The divergence angle of the scanner beam.") - trajectory_time_interval: Optional[float] = Field(0.0, description="Time interval for the trajectory.") - vertical_resolution: Optional[float] = Field(0.0, description="Vertical resolution of the scanner.") - horizontal_resolution: Optional[float] = Field(0.0, description="Horizontal resolution of the scanner.") - vertical_fov: Optional[int] = Field(0, description="Vertical field of view.") - horizontal_fov: Optional[int] = Field(0, description="Horizontal field of view.") - optics: Optional[str] = Field(None, description="Type of optics used.") - accuracy: Optional[float] = Field(None, description="Accuracy of the scanner.") - beam_divergence_radius: Optional[float] = Field(None, description="Beam divergence radius.") - max_nor: Optional[float] = Field(None, description="Maximum number of returns.") - max_scan_angle: Optional[float] = Field(None, description="Maximum scan angle.") - max_effective_scan_angle: Optional[float] = Field(None, description="Maximum effective scan angle.") - min_scanner_frequency: Optional[float] = Field(None, description="Minimum scanner frequency.") - max_scanner_frequency: Optional[float] = Field(None, description="Maximum scanner frequency.") - beam_sample_quality: Optional[float] = Field(None, description="Quality of beam sampling.") - beam_origin: Optional[np.ndarray] = Field(None, description="Origin of the scanner beam.") - head_rotation_axis: Optional[np.ndarray] = Field(None, description="Axis of head rotation.") +# class ScannerSettings(BaseModel): +# name: str = Field("#nullid#", description="The name of the scanner settings.") +# _basic_template: Optional["ScannerSettings"] = Field(None, description="A template to base settings off of.") +# is_active: Optional[bool] = Field(True, description="Whether the scanner is active.") +# head_rotation: Optional[float] = Field(0.0, description="The rotation angle of the scanner head.") +# rotation_start_angle: Optional[float] = Field(0.0, description="The starting angle for rotation.") +# rotation_stop_angle: Optional[float] = Field(0.0, description="The stopping angle for rotation.") +# pulse_frequency: Optional[int] = Field(0, description="The frequency of pulses emitted by the scanner.") +# scan_angle: Optional[float] = Field(0.0, description="The scanning angle of the scanner.") +# min_vertical_angle: Optional[float] = Field(None, description="Minimum vertical scanning angle.") +# max_vertical_angle: Optional[float] = Field(None, description="Maximum vertical scanning angle.") +# scan_frequency: Optional[float] = Field(0.0, description="The frequency at which the scanner operates.") +# beam_divergence_angle: Optional[float] = Field(0.003, description="The divergence angle of the scanner beam.") +# trajectory_time_interval: Optional[float] = Field(0.0, description="Time interval for the trajectory.") +# vertical_resolution: Optional[float] = Field(0.0, description="Vertical resolution of the scanner.") +# horizontal_resolution: Optional[float] = Field(0.0, description="Horizontal resolution of the scanner.") +# vertical_fov: Optional[int] = Field(0, description="Vertical field of view.") +# horizontal_fov: Optional[int] = Field(0, description="Horizontal field of view.") +# optics: Optional[str] = Field(None, description="Type of optics used.") +# accuracy: Optional[float] = Field(None, description="Accuracy of the scanner.") +# beam_divergence_radius: Optional[float] = Field(None, description="Beam divergence radius.") +# max_nor: Optional[float] = Field(None, description="Maximum number of returns.") +# max_scan_angle: Optional[float] = Field(None, description="Maximum scan angle.") +# max_effective_scan_angle: Optional[float] = Field(None, description="Maximum effective scan angle.") +# min_scanner_frequency: Optional[float] = Field(None, description="Minimum scanner frequency.") +# max_scanner_frequency: Optional[float] = Field(None, description="Maximum scanner frequency.") +# beam_sample_quality: Optional[float] = Field(None, description="Quality of beam sampling.") +# beam_origin: Optional[np.ndarray] = Field(None, description="Origin of the scanner beam.") +# head_rotation_axis: Optional[np.ndarray] = Field(None, description="Axis of head rotation.") - def cherry_pick(self, cherries: "ScannerSettings", fields: Set[str], template_fields: Optional[Set[str]] = None) -> "ScannerSettings": - settings = self.model_copy(deep=True) - for field in fields: - if hasattr(cherries, field): - setattr(settings, field, getattr(cherries, field)) +# def cherry_pick(self, cherries: "ScannerSettings", fields: Set[str], template_fields: Optional[Set[str]] = None) -> "ScannerSettings": +# settings = self.model_copy(deep=True) +# for field in fields: +# if hasattr(cherries, field): +# setattr(settings, field, getattr(cherries, field)) - if "basic_template" in fields and cherries._basic_template: - if template_fields: - settings._basic_template = cherries._basic_template.cherry_pick(cherries._basic_template, template_fields) - else: - settings._basic_template = cherries._basic_template.copy(deep=True) +# if "basic_template" in fields and cherries._basic_template: +# if template_fields: +# settings._basic_template = cherries._basic_template.cherry_pick(cherries._basic_template, template_fields) +# else: +# settings._basic_template = cherries._basic_template.copy(deep=True) - return settings +# return settings - @property - def has_template(self) -> bool: - """ - Checks if the ScannerSettings object has a basic template. +# @property +# def has_template(self) -> bool: +# """ +# Checks if the ScannerSettings object has a basic template. - Returns: - bool: True if the _basic_template is set, False othyour_moduleerwise. - """ - return self._basic_template is not None +# Returns: +# bool: True if the _basic_template is set, False othyour_moduleerwise. +# """ +# return self._basic_template is not None - @property - def basic_template(self) -> 'ScannerSettings': - """ - Returns the basic template associated with the ScannerSettings object. +# @property +# def basic_template(self) -> 'ScannerSettings': +# """ +# Returns the basic template associated with the ScannerSettings object. - Returns: - ScannerSettings: The basic template. +# Returns: +# ScannerSettings: The basic template. - Raises: - ValueError: If no template is associated. - """ - if self._basic_template is None: - raise ValueError("No template associated with this ScannerSettings.") - return self._basic_template +# Raises: +# ValueError: If no template is associated. +# """ +# if self._basic_template is None: +# raise ValueError("No template associated with this ScannerSettings.") +# return self._basic_template - @property - def has_default_resolution(self) -> bool: - """ - Checks if the ScannerSettings object has default resolution values. +# @property +# def has_default_resolution(self) -> bool: +# """ +# Checks if the ScannerSettings object has default resolution values. - Returns: - bool: True if both vertical and horizontal resolutions are 0.0, False otherwise. - """ - return self.vertical_resolution == 0.0 and self.horizontal_resolution == 0.0 +# Returns: +# bool: True if both vertical and horizontal resolutions are 0.0, False otherwise. +# """ +# return self.vertical_resolution == 0.0 and self.horizontal_resolution == 0.0 - def fit_to_resolution(self, scan_angle_max_rad: float) -> None: - """ - Adjusts the scan frequency and head rotation per second based on the resolution and maximum scan angle. +# def fit_to_resolution(self, scan_angle_max_rad: float) -> None: +# """ +# Adjusts the scan frequency and head rotation per second based on the resolution and maximum scan angle. - Args: - scan_angle_max_rad (float): The maximum scan angle in radians. - """ - self.scan_frequency = (self.pulse_frequency * self.vertical_resolution) / (2.0 * scan_angle_max_rad) - self.head_rotation = self.horizontal_resolution * self.scan_frequency +# Args: +# scan_angle_max_rad (float): The maximum scan angle in radians. +# """ +# self.scan_frequency = (self.pulse_frequency * self.vertical_resolution) / (2.0 * scan_angle_max_rad) +# self.head_rotation = self.horizontal_resolution * self.scan_frequency - @classmethod - def create_preset(cls, name: str, pulse_frequency: int, horizontal_resolution: float, vertical_resolution: float, - horizontal_fov: int, min_vertical_angle: float, max_vertical_angle: float, - save_as: Optional[str] = None) -> 'ScannerSettings': +# @classmethod +# def create_preset(cls, name: str, pulse_frequency: int, horizontal_resolution: float, vertical_resolution: float, +# horizontal_fov: int, min_vertical_angle: float, max_vertical_angle: float, +# save_as: Optional[str] = None) -> 'ScannerSettings': - preset = ScannerSettings(name=name, pulse_frequency=pulse_frequency, - horizontal_resolution=horizontal_resolution, - vertical_resolution=vertical_resolution, - horizontal_fov=horizontal_fov, - min_vertical_angle=min_vertical_angle, - max_vertical_angle=max_vertical_angle) - if save_as: - preset.to_file(save_as) - return preset +# preset = ScannerSettings(name=name, pulse_frequency=pulse_frequency, +# horizontal_resolution=horizontal_resolution, +# vertical_resolution=vertical_resolution, +# horizontal_fov=horizontal_fov, +# min_vertical_angle=min_vertical_angle, +# max_vertical_angle=max_vertical_angle) +# if save_as: +# preset.to_file(save_as) +# return preset - def to_file(self, file_path: str) -> None: - with open(file_path, 'w') as file: - json.dump(self.dict(), file) +# def to_file(self, file_path: str) -> None: +# with open(file_path, 'w') as file: +# json.dump(self.dict(), file) - @staticmethod - def load_preset(file_path: str) -> 'ScannerSettings': - with open(file_path, 'r') as file: - preset_data = json.load(file) - return ScannerSettings(**preset_data) \ No newline at end of file +# @staticmethod +# def load_preset(file_path: str) -> 'ScannerSettings': +# with open(file_path, 'r') as file: +# preset_data = json.load(file) +# return ScannerSettings(**preset_data) \ No newline at end of file diff --git a/python/helios/scene.py b/python/helios/scene.py index bc5e57b73..0b75b3704 100755 --- a/python/helios/scene.py +++ b/python/helios/scene.py @@ -1,49 +1,49 @@ -from typing import Optional, List -from pydantic import BaseModel -import numpy as np -from helios.scenepart import ScenePart, ObjectType +# from typing import Optional, List +# from pydantic import BaseModel +# import numpy as np +# from helios.scenepart import ScenePart, ObjectType -class Scene(BaseModel): - kdgrove_factory: Optional[KDGroveFactory] - kdgrove: Optional[KDGrove] - bbox: Optional[AABB] - bbox_crs: Optional[AABB] - kd_raycaster: Optional[KDGroveRaycaster] - primitives: List[Primitive] - _scene_parts: List[ScenePart] - material: Optional[Material] = None +# class Scene(BaseModel): +# kdgrove_factory: Optional[KDGroveFactory] +# kdgrove: Optional[KDGrove] +# bbox: Optional[AABB] +# bbox_crs: Optional[AABB] +# kd_raycaster: Optional[KDGroveRaycaster] +# primitives: List[Primitive] +# _scene_parts: List[ScenePart] +# material: Optional[Material] = None - @property - def scene_parts(self) -> List[ScenePart]: - return self._scene_parts +# @property +# def scene_parts(self) -> List[ScenePart]: +# return self._scene_parts - @scene_parts.setter - def scene_parts(self, scene_parts: List[ScenePart]): - self._scene_parts = scene_parts +# @scene_parts.setter +# def scene_parts(self, scene_parts: List[ScenePart]): +# self._scene_parts = scene_parts - def has_moving_objects(self) -> bool: - return any(scene_part.object_type == ObjectType.DYN_MOVING_OBJECT for scene_part in self._scene_parts) +# def has_moving_objects(self) -> bool: +# return any(scene_part.object_type == ObjectType.DYN_MOVING_OBJECT for scene_part in self._scene_parts) - def add_scene_part(self, scene_part: ScenePart): - if scene_part not in self._scene_parts: - self._scene_parts.append(scene_part) +# def add_scene_part(self, scene_part: ScenePart): +# if scene_part not in self._scene_parts: +# self._scene_parts.append(scene_part) - def show(self, annotate_ids: bool = False, show_bboxes: bool = False, show_crs: bool = False, show_kd: bool = False, show_kd_raycaster: bool = False): - # visualize scene prior to simulation (e.g. with open3D/PyVista) - pass +# def show(self, annotate_ids: bool = False, show_bboxes: bool = False, show_crs: bool = False, show_kd: bool = False, show_kd_raycaster: bool = False): +# # visualize scene prior to simulation (e.g. with open3D/PyVista) +# pass - def get_scene_part(self, scenepart_id: int) -> ScenePart: - try: - return self._scene_parts[scenepart_id] - except IndexError: - raise ValueError(f"No ScenePart with this id") +# def get_scene_part(self, scenepart_id: int) -> ScenePart: +# try: +# return self._scene_parts[scenepart_id] +# except IndexError: +# raise ValueError(f"No ScenePart with this id") - def delete_scene_part(self, scenepart_id: int)-> bool: - if 0 <= scenepart_id < len(self._scene_parts): - del self._scene_parts[scenepart_id] - return True - return False +# def delete_scene_part(self, scenepart_id: int)-> bool: +# if 0 <= scenepart_id < len(self._scene_parts): +# del self._scene_parts[scenepart_id] +# return True +# return False \ No newline at end of file diff --git a/python/helios/scenepart.py b/python/helios/scenepart.py index 0eb9638a3..cc06ce9d5 100755 --- a/python/helios/scenepart.py +++ b/python/helios/scenepart.py @@ -1,140 +1,140 @@ -from typing import Optional, List -from pydantic import BaseModel, Field -import numpy as np -import os -from pathlib import Path -import open3d as o3d -from enum import Enum - -class PrimitiveType(str, Enum): - NONE = "NONE" - TRIANGLE = "TRIANGLE" - VOXEL = "VOXEL" - -class ObjectType(str, Enum): - STATIC_OBJECT = "STATIC_OBJECT" - DYN_OBJECT = "DYN_OBJECT" - DYN_MOVING_OBJECT = "DYN_MOVING_OBJECT" - -class ScenePart(BaseModel): - name: str = Field(default="") - primitive_type: PrimitiveType = Field(default=PrimitiveType.NONE) - object_type: ObjectType = Field(default=ObjectType.STATIC_OBJECT) - - subpart_borders: List[int] = Field(default_factory=list) - ray_intersection_mode: str = Field(default="") - ray_intersection_argument: bool = Field(default=False) - random_shift: bool = Field(default=False) - origin: np.ndarray = Field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) - rotation: np.ndarray = Field(default_factory=lambda: np.eye(3)) # Assuming rotation is a 3x3 matrix - scale: float = Field(default=1.0) - centroid: np.ndarray = Field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) - - motions: List[dict] = Field(default_factory=list) - is_on_ground: bool = Field(default=True) - time_step_duration: float = Field(default=0.0) - - @classmethod - def from_file(cls, filename: str, file_format: Optional[str] = None) -> 'ScenePart': - if not os.path.exists(filename): - raise FileNotFoundError(f"File {filename} not found.") +# from typing import Optional, List +# from pydantic import BaseModel, Field +# import numpy as np +# import os +# from pathlib import Path +# import open3d as o3d +# from enum import Enum + +# class PrimitiveType(str, Enum): +# NONE = "NONE" +# TRIANGLE = "TRIANGLE" +# VOXEL = "VOXEL" + +# class ObjectType(str, Enum): +# STATIC_OBJECT = "STATIC_OBJECT" +# DYN_OBJECT = "DYN_OBJECT" +# DYN_MOVING_OBJECT = "DYN_MOVING_OBJECT" + +# class ScenePart(BaseModel): +# name: str = Field(default="") +# primitive_type: PrimitiveType = Field(default=PrimitiveType.NONE) +# object_type: ObjectType = Field(default=ObjectType.STATIC_OBJECT) + +# subpart_borders: List[int] = Field(default_factory=list) +# ray_intersection_mode: str = Field(default="") +# ray_intersection_argument: bool = Field(default=False) +# random_shift: bool = Field(default=False) +# origin: np.ndarray = Field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) +# rotation: np.ndarray = Field(default_factory=lambda: np.eye(3)) # Assuming rotation is a 3x3 matrix +# scale: float = Field(default=1.0) +# centroid: np.ndarray = Field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) + +# motions: List[dict] = Field(default_factory=list) +# is_on_ground: bool = Field(default=True) +# time_step_duration: float = Field(default=0.0) + +# @classmethod +# def from_file(cls, filename: str, file_format: Optional[str] = None) -> 'ScenePart': +# if not os.path.exists(filename): +# raise FileNotFoundError(f"File {filename} not found.") - if file_format is None: - file_format = Path(filename).suffix[1:].lower() +# if file_format is None: +# file_format = Path(filename).suffix[1:].lower() - # Logic to handle different file formats - method_name = f"from_{file_format}" - if hasattr(cls, method_name): - method = getattr(cls, method_name) - return method(filename) - else: - raise ValueError(f"Unsupported file format: {file_format}") - - @staticmethod - def from_obj(filename: str) -> 'ScenePart': - # Implement OBJ file processing logic - pass - - @staticmethod - def from_tiff(filename: str) -> 'ScenePart': - # Implement TIFF file processing logic - pass - - @staticmethod - def from_xml(filename: str) -> 'ScenePart': - # Implement XML file processing logic - pass - - @staticmethod - def from_json(filename: str) -> 'ScenePart': - # Implement JSON file processing logic - pass - - @staticmethod - def from_csv(filename: str) -> 'ScenePart': - # Implement CSV file processing logic - pass - - @staticmethod - def from_xyz(filename: str) -> 'ScenePart': - # Implement XYZ file processing logic - pass - - @staticmethod - def from_las(filename: str) -> 'ScenePart': - # Implement LAS file processing logic - pass - - def from_o3d(self, o3d_mesh: o3d.geometry.TriangleMesh): - # Implement processing logic for Open3D mesh - pass - - def transform( - self, - translation: Optional[np.ndarray] = None, - scale: Optional[float] = None, - is_on_ground: Optional[bool] = True, - apply_to_axis: Optional[int] = None - ) -> 'ScenePart': - # Implement transformation logic - pass +# # Logic to handle different file formats +# method_name = f"from_{file_format}" +# if hasattr(cls, method_name): +# method = getattr(cls, method_name) +# return method(filename) +# else: +# raise ValueError(f"Unsupported file format: {file_format}") + +# @staticmethod +# def from_obj(filename: str) -> 'ScenePart': +# # Implement OBJ file processing logic +# pass + +# @staticmethod +# def from_tiff(filename: str) -> 'ScenePart': +# # Implement TIFF file processing logic +# pass + +# @staticmethod +# def from_xml(filename: str) -> 'ScenePart': +# # Implement XML file processing logic +# pass + +# @staticmethod +# def from_json(filename: str) -> 'ScenePart': +# # Implement JSON file processing logic +# pass + +# @staticmethod +# def from_csv(filename: str) -> 'ScenePart': +# # Implement CSV file processing logic +# pass + +# @staticmethod +# def from_xyz(filename: str) -> 'ScenePart': +# # Implement XYZ file processing logic +# pass + +# @staticmethod +# def from_las(filename: str) -> 'ScenePart': +# # Implement LAS file processing logic +# pass + +# def from_o3d(self, o3d_mesh: o3d.geometry.TriangleMesh): +# # Implement processing logic for Open3D mesh +# pass + +# def transform( +# self, +# translation: Optional[np.ndarray] = None, +# scale: Optional[float] = None, +# is_on_ground: Optional[bool] = True, +# apply_to_axis: Optional[int] = None +# ) -> 'ScenePart': +# # Implement transformation logic +# pass - def rotate( - self, - axis: Optional[np.ndarray] = None, - angle: Optional[float] = None, - origin: Optional[np.ndarray] = None, - rotation: Optional[np.ndarray] = None, - matrix: Optional[np.ndarray] = None, - euler_angles: Optional[np.ndarray] = None - ): - # Implement rotation logic - pass +# def rotate( +# self, +# axis: Optional[np.ndarray] = None, +# angle: Optional[float] = None, +# origin: Optional[np.ndarray] = None, +# rotation: Optional[np.ndarray] = None, +# matrix: Optional[np.ndarray] = None, +# euler_angles: Optional[np.ndarray] = None +# ): +# # Implement rotation logic +# pass - def make_motion( - self, - translation: Optional[np.ndarray] = None, - rotation_axis: Optional[np.ndarray] = None, - rotation_angle: Optional[float] = None, - radians: Optional[bool] = True, - rotation_center: Optional[np.ndarray] = None, - loop: Optional[int] = 1, - rotate_around_self: Optional[bool] = False, - auto_crs: Optional[bool] = False - ): - # Implement motion logic - pass - - def make_motion_sequence( - self, - translations: Optional[List[np.ndarray]] = None, - rotation_axes: Optional[List[np.ndarray]] = None, - rotation_angles: Optional[List[float]] = None, - radians: Optional[bool] = True, - rotation_centers: Optional[List[np.ndarray]] = None, - loop: Optional[int] = 1, - rotate_around_self: Optional[bool] = False, - auto_crs: Optional[bool] = False - ): - # Implement motion sequence logic - pass +# def make_motion( +# self, +# translation: Optional[np.ndarray] = None, +# rotation_axis: Optional[np.ndarray] = None, +# rotation_angle: Optional[float] = None, +# radians: Optional[bool] = True, +# rotation_center: Optional[np.ndarray] = None, +# loop: Optional[int] = 1, +# rotate_around_self: Optional[bool] = False, +# auto_crs: Optional[bool] = False +# ): +# # Implement motion logic +# pass + +# def make_motion_sequence( +# self, +# translations: Optional[List[np.ndarray]] = None, +# rotation_axes: Optional[List[np.ndarray]] = None, +# rotation_angles: Optional[List[float]] = None, +# radians: Optional[bool] = True, +# rotation_centers: Optional[List[np.ndarray]] = None, +# loop: Optional[int] = 1, +# rotate_around_self: Optional[bool] = False, +# auto_crs: Optional[bool] = False +# ): +# # Implement motion sequence logic +# pass diff --git a/python/helios/survey.py b/python/helios/survey.py index a39d67080..1987f541a 100755 --- a/python/helios/survey.py +++ b/python/helios/survey.py @@ -1,137 +1,137 @@ -from pydantic import BaseModel -from typing import Optional, Union, List -from helios.scanner import Scanner -from helios.leg import Leg -import math -from helios.scene import Scene -from helios.scannersettings import ScannerSettings -from helios.platform import Platform -from helios.platformsettings import PlatformSettings - -import xml.etree.ElementTree as ET -import numpy as np - -class Survey(BaseModel): - name: str = "Unnamed Survey Playback" - num_runs: int = -1 - scanner: Optional[Scanner] = None - sim_speed_factor: float = 1.0 - legs: List[Leg] = [] - length: float = 0.0 - - scene: Optional[Scene] = None - scanner_settings: Optional[ScannerSettings] = None - platform: Optional[Platform] = None - platform_settings: Optional[PlatformSettings] = None - trajectory_time_interval: float = 0.0 - trajectory: Optional[Trajectory] = None - - fullwave_settings: Optional[dict] = None - run_threads: int = 0 - save_config: bool = False - output_format: Optional[str] = None - output_path: Optional[str] = None - write_waveform: bool = False - calc_echo_width: bool = False - gps_start_time: Optional[str] = None - on_finished_callback: Optional[callable] = None - on_progress_callback: Optional[callable] = None - is_running_flag: bool = False +# from pydantic import BaseModel +# from typing import Optional, Union, List +# from helios.scanner import Scanner +# from helios.leg import Leg +# import math +# from helios.scene import Scene +# from helios.scannersettings import ScannerSettings +# from helios.platform import Platform +# from helios.platformsettings import PlatformSettings + +# import xml.etree.ElementTree as ET +# import numpy as np + +# class Survey(BaseModel): +# name: str = "Unnamed Survey Playback" +# num_runs: int = -1 +# scanner: Optional[Scanner] = None +# sim_speed_factor: float = 1.0 +# legs: List[Leg] = [] +# length: float = 0.0 + +# scene: Optional[Scene] = None +# scanner_settings: Optional[ScannerSettings] = None +# platform: Optional[Platform] = None +# platform_settings: Optional[PlatformSettings] = None +# trajectory_time_interval: float = 0.0 +# trajectory: Optional[Trajectory] = None + +# fullwave_settings: Optional[dict] = None +# run_threads: int = 0 +# save_config: bool = False +# output_format: Optional[str] = None +# output_path: Optional[str] = None +# write_waveform: bool = False +# calc_echo_width: bool = False +# gps_start_time: Optional[str] = None +# on_finished_callback: Optional[callable] = None +# on_progress_callback: Optional[callable] = None +# is_running_flag: bool = False - #instead of propose functionality we first would create a Leg object, and then add it to survey legs - def add_leg(self, leg: Leg): - if self._trajectory is not None: - raise ValueError("Adding legs to a survey with an existing trajectory is not supported. " - "Set the trajectory to None first explicitly.") - else: - if leg not in self.legs: - self.legs.append(leg) - - def add_legs(self, horizontal_fov: float, scan_pattern: Union[ScannerSettings, List[ScannerSettings]], pos: List[List[float]]): - legs = create_legs(horizontal_fov, scan_pattern, pos) - self.legs.extend(legs) - - def remove_leg(self, leg_index: int): - try: - del self.legs[leg_index] - except IndexError: - raise ValueError(f"Leg index {leg_index} is out of range!") - - def calculate_length(self): - self._length = 0 - for i in range(len(self.legs) - 1): - leg_distance = math.dist(self.legs[i].position, self.legs[i + 1].position) - self.legs[i].set_length(leg_distance) - self._length += self.legs[i].get_length() +# #instead of propose functionality we first would create a Leg object, and then add it to survey legs +# def add_leg(self, leg: Leg): +# if self._trajectory is not None: +# raise ValueError("Adding legs to a survey with an existing trajectory is not supported. " +# "Set the trajectory to None first explicitly.") +# else: +# if leg not in self.legs: +# self.legs.append(leg) + +# def add_legs(self, horizontal_fov: float, scan_pattern: Union[ScannerSettings, List[ScannerSettings]], pos: List[List[float]]): +# legs = create_legs(horizontal_fov, scan_pattern, pos) +# self.legs.extend(legs) + +# def remove_leg(self, leg_index: int): +# try: +# del self.legs[leg_index] +# except IndexError: +# raise ValueError(f"Leg index {leg_index} is out of range!") + +# def calculate_length(self): +# self._length = 0 +# for i in range(len(self.legs) - 1): +# leg_distance = math.dist(self.legs[i].position, self.legs[i + 1].position) +# self.legs[i].set_length(leg_distance) +# self._length += self.legs[i].get_length() - def fullwave(self, **kwargs): - self._fullwave_settings = kwargs +# def fullwave(self, **kwargs): +# self._fullwave_settings = kwargs - def output(self): - pass +# def output(self): +# pass - def preview(self): - ''' # some kind of visualization? e.g., 2D map preview, 3D preview with scene and markers for SPs/waypoints, etc. - # survey.preview("2D") - # survey.preview("3D") # similar to scene.show(), but with marker (e.g. diamonds) for leg positions; - # for static platforms also show movement direction and orientation of platform - ''' - pass - - def save_report(self, filename: str): - ''' - Alberto: - Also as this is a research software, I think writing some tables - and/or CSV files with the characteristics of the survey would be nice. - I mean things such as the number of primitives, vertices, the simulation - time (not the execution time), the volume of the bounding box, the - depth for each KDTree in the KDGrove, etc. - Hannah: Yes, so something like a "report" or "summary", see below - ''' - pass - - def set_trajectory(self, trajectory: Union[Trajectory, str]) -> None: - if self.trajectory is not None: - raise ValueError("Overwriting a trajectory in a survey is not supported. Set it to None first explicitly.") - if isinstance(trajectory, str): - self.trajectory = Trajectory.from_file(trajectory) - else: - self.trajectory = trajectory - - - - def run(self,filename: str = None): - #if filename is none, then run the survey without saving the output): - self.is_running = True - callback_counter = 0 - mpoints = [] - tpoints = [] - - while self.is_running: - if len(mpoints) > 0: - # update points in visualization - scene_vis.add_sim_points(mpoints) - scene_vis.add_traj_points(tpoints) - - # + apply colour, refresh gui, etc. - time.sleep(0.1) - - def stop(self): - self.is_running = False +# def preview(self): +# ''' # some kind of visualization? e.g., 2D map preview, 3D preview with scene and markers for SPs/waypoints, etc. +# # survey.preview("2D") +# # survey.preview("3D") # similar to scene.show(), but with marker (e.g. diamonds) for leg positions; +# # for static platforms also show movement direction and orientation of platform +# ''' +# pass + +# def save_report(self, filename: str): +# ''' +# Alberto: +# Also as this is a research software, I think writing some tables +# and/or CSV files with the characteristics of the survey would be nice. +# I mean things such as the number of primitives, vertices, the simulation +# time (not the execution time), the volume of the bounding box, the +# depth for each KDTree in the KDGrove, etc. +# Hannah: Yes, so something like a "report" or "summary", see below +# ''' +# pass + +# def set_trajectory(self, trajectory: Union[Trajectory, str]) -> None: +# if self.trajectory is not None: +# raise ValueError("Overwriting a trajectory in a survey is not supported. Set it to None first explicitly.") +# if isinstance(trajectory, str): +# self.trajectory = Trajectory.from_file(trajectory) +# else: +# self.trajectory = trajectory + + + +# def run(self,filename: str = None): +# #if filename is none, then run the survey without saving the output): +# self.is_running = True +# callback_counter = 0 +# mpoints = [] +# tpoints = [] + +# while self.is_running: +# if len(mpoints) > 0: +# # update points in visualization +# scene_vis.add_sim_points(mpoints) +# scene_vis.add_traj_points(tpoints) + +# # + apply colour, refresh gui, etc. +# time.sleep(0.1) + +# def stop(self): +# self.is_running = False - def is_running(self): - return self.is_running - - -def create_legs(horizontal_fov: float, scan_pattern: Union[ScannerSettings, List[ScannerSettings]], pos: List[List[float]]) -> List[Leg]: - legs = [] - if isinstance(scan_pattern, list): - for p, s in zip(pos, scan_pattern): - legs.append(Leg(horizontal_fov=horizontal_fov, scan_pattern=s, position=p)) - else: - for p in pos: - legs.append(Leg(horizontal_fov=horizontal_fov, scan_pattern=scan_pattern, position=p)) - return legs \ No newline at end of file +# def is_running(self): +# return self.is_running + + +# def create_legs(horizontal_fov: float, scan_pattern: Union[ScannerSettings, List[ScannerSettings]], pos: List[List[float]]) -> List[Leg]: +# legs = [] +# if isinstance(scan_pattern, list): +# for p, s in zip(pos, scan_pattern): +# legs.append(Leg(horizontal_fov=horizontal_fov, scan_pattern=s, position=p)) +# else: +# for p in pos: +# legs.append(Leg(horizontal_fov=horizontal_fov, scan_pattern=scan_pattern, position=p)) +# return legs \ No newline at end of file diff --git a/src/binds/GLMTypeCaster.h b/src/binds/GLMTypeCaster.h new file mode 100755 index 000000000..59c29d6bd --- /dev/null +++ b/src/binds/GLMTypeCaster.h @@ -0,0 +1,36 @@ +#pragma once +#ifndef GLM_TYPE_CASTER_H +#define GLM_TYPE_CASTER_H +#include +#include + +namespace pybind11 { namespace detail { + + template <> struct type_caster { + public: + PYBIND11_TYPE_CASTER(glm::dvec3, _("dvec3")); + + bool load(handle src, bool) { + if (!src) return false; + if (!pybind11::isinstance(src)) return false; + + pybind11::tuple t = pybind11::cast(src); + if (t.size() != 3) return false; + + value = glm::dvec3( + pybind11::cast(t[0]), + pybind11::cast(t[1]), + pybind11::cast(t[2]) + ); + + return true; + } + + static handle cast(const glm::dvec3& src, return_value_policy, handle) { + return pybind11::make_tuple(src.x, src.y, src.z).release(); + } + }; + +} +} +#endif diff --git a/src/binds/NoiseSourceWrap.h b/src/binds/NoiseSourceWrap.h new file mode 100755 index 000000000..0e8eef1b7 --- /dev/null +++ b/src/binds/NoiseSourceWrap.h @@ -0,0 +1,11 @@ + +template +class NoiseSourceWrap : public NoiseSource { +public: + + using NoiseSource::NoiseSource; + + RealType noiseFunction() override { + throw std::runtime_error("Called pure virtual function noiseFunction()"); + } +}; diff --git a/src/binds/ScannerWrap.h b/src/binds/ScannerWrap.h new file mode 100755 index 000000000..fbb0bc95b --- /dev/null +++ b/src/binds/ScannerWrap.h @@ -0,0 +1,775 @@ +#include +#include +#include + +class ScannerWrap : public Scanner { +public: + // using Scanner::Scanner; // Inherit constructors + + ScannerWrap() : Scanner("", std::list()) {} + + ScannerWrap( + std::string const& id, + std::list const& pulseFreqs, + bool writeWaveform=false, + bool writePulse=false, + bool calcEchowidth=false, + bool fullWaveNoise=false, + bool platformNoiseDisabled=false + ) : Scanner(id, pulseFreqs, writeWaveform, writePulse, calcEchowidth, fullWaveNoise, platformNoiseDisabled) {} + + ScannerWrap(Scanner& scanner) : Scanner(scanner) {} + + std::shared_ptr clone() override { + PYBIND11_OVERLOAD_PURE( + std::shared_ptr, + Scanner, + clone + ); + } + + void prepareSimulation(bool const legacyEnergyModel) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + prepareSimulation, + legacyEnergyModel + ); + } + + std::shared_ptr retrieveCurrentSettings(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::shared_ptr, + Scanner, + retrieveCurrentSettings, + idx + ); + } + + + void applySettings(std::shared_ptr settings, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + applySettings, + settings, idx + ); + } + + void applySettingsFWF(FWFSettings fwfSettings, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + applySettingsFWF, + fwfSettings, idx + ); + } + + void doSimStep(unsigned int legIndex, double const currentGpsTime) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + doSimStep, + legIndex, currentGpsTime + ); + } + + void calcRaysNumber(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + calcRaysNumber, + idx + ); + } + + void prepareDiscretization(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + prepareDiscretization, + idx + ); + } + + double calcAtmosphericAttenuation(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + calcAtmosphericAttenuation, + idx + ); + } + + Rotation calcAbsoluteBeamAttitude(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + Rotation, + Scanner, + calcAbsoluteBeamAttitude, + idx + ); + } + + bool checkMaxNOR(int const nor, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + bool, + Scanner, + checkMaxNOR, + nor, idx + ); + } + + void computeSubrays( + std::function&, + std::map&, + std::vector& +#if DATA_ANALYTICS >= 2 + ,bool&, + std::vector& +#endif + )> handleSubray, + NoiseSource& intersectionHandlingNoiseSource, + std::map& reflections, + std::vector& intersects, + size_t const idx +#if DATA_ANALYTICS >= 2 + ,std::shared_ptr pulseRecorder +#endif + ) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + computeSubrays, + handleSubray, intersectionHandlingNoiseSource, reflections, intersects, idx +#if DATA_ANALYTICS >= 2 + ,pulseRecorder +#endif + ); + } + + bool initializeFullWaveform( + double const minHitDist_m, + double const maxHitDist_m, + double& minHitTime_ns, + double& maxHitTime_ns, + double& nsPerBin, + double& distanceThreshold, + int& peakIntensityIndex, + int& numFullwaveBins, + size_t const idx + ) override { + PYBIND11_OVERLOAD_PURE( + bool, + Scanner, + initializeFullWaveform, + minHitDist_m, maxHitDist_m, minHitTime_ns, maxHitTime_ns, nsPerBin, + distanceThreshold, peakIntensityIndex, numFullwaveBins, idx + ); + } + + double calcIntensity( + double const incidenceAngle, + double const targetRange, + Material const& mat, + int const subrayRadiusStep, + size_t const idx +#if DATA_ANALYTICS >= 2 + ,std::vector>& calcIntensityRecords +#endif + ) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + calcIntensity, + incidenceAngle, targetRange, mat, subrayRadiusStep, idx +#if DATA_ANALYTICS >= 2 + ,calcIntensityRecords +#endif + ); + } + + double calcIntensity( + double const targetRange, + double const sigma, + int const subrayRadiusStep, + size_t const idx + ) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + calcIntensity, + targetRange, sigma, subrayRadiusStep, idx + ); + } + + void onLegComplete() override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + onLegComplete, + ); + } + + ScanningDevice& getScanningDevice(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + ScanningDevice&, + Scanner, + getScanningDevice, + idx + ); + } + + int getCurrentPulseNumber(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + int, + Scanner, + getCurrentPulseNumber, + idx + ); + } + + int getNumRays(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + int, + Scanner, + getNumRays, + idx + ); + } + + void setNumRays(int const numRays, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setNumRays, + numRays, idx + ); + } + + double getPulseLength_ns(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getPulseLength_ns, + idx + ); + } + + void setPulseLength_ns(double const pulseLength_ns, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setPulseLength_ns, + pulseLength_ns, idx + ); + } + + bool lastPulseWasHit(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + bool, + Scanner, + lastPulseWasHit, + idx + ); + } + + void setLastPulseWasHit(bool const lastPulseWasHit, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setLastPulseWasHit, + lastPulseWasHit, idx + ); + } + + double getBeamDivergence(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getBeamDivergence, + idx + ); + } + + void setBeamDivergence(double const beamDivergence, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setBeamDivergence, + beamDivergence, idx + ); + } + + double getAveragePower(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getAveragePower, + idx + ); + } + + void setAveragePower(double const averagePower, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setAveragePower, + averagePower, idx + ); + } + + double getBeamQuality(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getBeamQuality, + idx + ); + } + + void setBeamQuality(double const beamQuality, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setBeamQuality, + beamQuality, idx + ); + } + + double getEfficiency(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getEfficiency, + idx + ); + } + + void setEfficiency(double const efficiency, size_t const idx = 0) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setEfficiency, + efficiency, idx + ); + } + + double getReceiverDiameter(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getReceiverDiameter, + idx + ); + } + + void setReceiverDiameter(double const receiverDiameter, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setReceiverDiameter, + receiverDiameter, idx + ); + } + + double getVisibility(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getVisibility, + idx + ); + } + + void setVisibility(double const visibility, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setVisibility, + visibility, idx + ); + } + + double getWavelength(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getWavelength, + idx + ); + } + + void setWavelength(double const wavelength, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setWavelength, + wavelength, idx + ); + } + + double getAtmosphericExtinction(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getAtmosphericExtinction, + idx + ); + } + + void setAtmosphericExtinction(double const atmosphericExtinction, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setAtmosphericExtinction, + atmosphericExtinction, idx + ); + } + + double getBeamWaistRadius(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getBeamWaistRadius, + idx + ); + } + + void setBeamWaistRadius(double const beamWaistRadius, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setBeamWaistRadius, + beamWaistRadius, idx + ); + } + + glm::dvec3 getHeadRelativeEmitterPosition(size_t const idx = 0) const override { + PYBIND11_OVERLOAD_PURE( + glm::dvec3, + Scanner, + getHeadRelativeEmitterPosition, + idx + ); + } + + void setHeadRelativeEmitterPosition(glm::dvec3 const &pos, size_t const idx = 0) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setHeadRelativeEmitterPosition, + pos, idx + ); + } + + glm::dvec3 &getHeadRelativeEmitterPositionByRef(size_t const idx = 0) override { + PYBIND11_OVERLOAD_PURE( + glm::dvec3 &, + Scanner, + getHeadRelativeEmitterPositionByRef, + idx + ); + } + + Rotation getHeadRelativeEmitterAttitude(size_t const idx = 0) const override { + PYBIND11_OVERLOAD_PURE( + Rotation, + Scanner, + getHeadRelativeEmitterAttitude, + idx + ); + } + + void setHeadRelativeEmitterAttitude(Rotation const &attitude, size_t const idx = 0) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setHeadRelativeEmitterAttitude, + attitude, idx + ); + } + + Rotation &getHeadRelativeEmitterAttitudeByRef(size_t const idx = 0) override { + PYBIND11_OVERLOAD_PURE( + Rotation &, + Scanner, + getHeadRelativeEmitterAttitudeByRef, + idx + ); + } + + double getBt2(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getBt2, + idx + ); + } + + void setBt2(double const bt2, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setBt2, + bt2, idx + ); + } + + double getDr2(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getDr2, + idx + ); + } + + void setDr2(double const dr2, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setDr2, + dr2, idx + ); + } + + void setDeviceIndex(size_t const newIdx, size_t const oldIdx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setDeviceIndex, + newIdx, oldIdx + ); + } + + std::string getDeviceId(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + std::string, + Scanner, + getDeviceId, + idx + ); + } + + void setDeviceId(std::string const deviceId, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setDeviceId, + deviceId, idx + ); + } + + size_t getNumDevices() const override { + PYBIND11_OVERLOAD_PURE( + size_t, + Scanner, + getNumDevices, + ); + } + + std::shared_ptr getScannerHead(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::shared_ptr, + Scanner, + getScannerHead, + idx + ); + } + + void setScannerHead(std::shared_ptr scannerHead, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setScannerHead, + scannerHead, idx + ); + } + + std::shared_ptr getBeamDeflector(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::shared_ptr, + Scanner, + getBeamDeflector, + idx + ); + } + + void setBeamDeflector(std::shared_ptr beamDeflector, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setBeamDeflector, + beamDeflector, idx + ); + } + + std::shared_ptr getDetector(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::shared_ptr, + Scanner, + getDetector, + idx + ); + } + + void setDetector(std::shared_ptr detector, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setDetector, + detector, idx + ); + } + + FWFSettings &getFWFSettings(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + FWFSettings &, + Scanner, + getFWFSettings, + idx + ); + } + + void setFWFSettings(FWFSettings const &fwfSettings, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setFWFSettings, + fwfSettings, idx + ); + } + + std::list& getSupportedPulseFreqs_Hz(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::list&, + Scanner, + getSupportedPulseFreqs_Hz, + idx + ); + } + + void setSupportedPulseFreqs_Hz(std::list &supportedPulseFreqs_Hz, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setSupportedPulseFreqs_Hz, + supportedPulseFreqs_Hz, idx + ); + } + + int getMaxNOR(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + int, + Scanner, + getMaxNOR, + idx + ); + } + + void setMaxNOR(int const maxNOR, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setMaxNOR, + maxNOR, idx + ); + } + + int getNumTimeBins(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + int, + Scanner, + getNumTimeBins, + idx + ); + } + + void setNumTimeBins(int const numTimeBins, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setNumTimeBins, + numTimeBins, idx + ); + } + + int getPeakIntensityIndex(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + int, + Scanner, + getPeakIntensityIndex, + idx + ); + } + + void setPeakIntensityIndex(int const pii, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setPeakIntensityIndex, + pii, idx + ); + } + + std::vector& getTimeWave(size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + std::vector&, + Scanner, + getTimeWave, + idx + ); + } + + void setTimeWave(std::vector &timewave, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setTimeWave, + timewave, idx + ); + } + + void setTimeWave(std::vector &&timewave, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setTimeWave, + std::move(timewave), idx + ); + } + + + double getReceivedEnergyMin(size_t const idx) const override { + PYBIND11_OVERLOAD_PURE( + double, + Scanner, + getReceivedEnetgyMin, + idx + ); + } + + void setReceivedEnergyMin(double receivedEnergyMin_W, size_t const idx) override { + PYBIND11_OVERLOAD_PURE( + void, + Scanner, + setReceivedEnetgyMin, + receivedEnergyMin_W, idx + ); + } + + int calcTimePropagation(std::vector &timeWave, int numBins, Scanner &scanner) { + return WaveMaths::calcPropagationTimeLegacy( + timeWave, + numBins, + scanner.getFWFSettings(0).binSize_ns, + scanner.getPulseLength_ns(0), + 7.0 // 3.5 too many ops., 7.0 just one op. + ); + } + +}; diff --git a/src/binds/SimulationCycleCallbackWrap.h b/src/binds/SimulationCycleCallbackWrap.h new file mode 100755 index 000000000..ab02f2ecd --- /dev/null +++ b/src/binds/SimulationCycleCallbackWrap.h @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +namespace py = pybind11; + +class [[gnu::visibility("default")]] SimulationCycleCallbackWrap : public SimulationCycleCallback { +public: + using SimulationCycleCallback::SimulationCycleCallback; + SimulationCycleCallbackWrap(py::object obj) : SimulationCycleCallback(), py_obj(std::move(obj)) {} + + void operator()( + std::vector &measurements, + std::vector &trajectories, + const std::string &outpath) override { + py::gil_scoped_acquire acquire; // Acquire GIL before calling Python code + py_obj(measurements, trajectories, outpath); + } + +private: + py::object py_obj; + +}; diff --git a/src/binds/SimulationWrap.h b/src/binds/SimulationWrap.h new file mode 100755 index 000000000..8cf468fd3 --- /dev/null +++ b/src/binds/SimulationWrap.h @@ -0,0 +1,31 @@ +#include +#include + +class SimulationWrap : public Simulation { +public: + using Simulation::Simulation; + + void onLegComplete() override { + PYBIND11_OVERRIDE_PURE( + void, + Simulation, + onLegComplete, + ); + } + + void doSimLoop() override { + PYBIND11_OVERRIDE( + void, + Simulation, + doSimLoop, + ); + } + + void shutdown() override { + PYBIND11_OVERRIDE( + void, + Simulation, + shutdown, + ); + } +}; \ No newline at end of file diff --git a/src/binds/utils.h b/src/binds/utils.h new file mode 100644 index 000000000..ebff1ef10 --- /dev/null +++ b/src/binds/utils.h @@ -0,0 +1,32 @@ +#include +#include + +namespace pybind11 { +size_t handlePythonIndex(long _index, size_t n) { + size_t index = static_cast(_index); + if (_index < 0) { + index = static_cast(n + _index); + } + if (index >= n) { + std::stringstream ss; + ss << "Index " << _index << " out of range"; + throw pybind11::index_error(ss.str()); + } + return index; +} +} + +int calcTimePropagation(std::vector &timeWave, int numBins, Scanner &scanner) { + return WaveMaths::calcPropagationTimeLegacy( + timeWave, + numBins, + scanner.getFWFSettings(0).binSize_ns, + scanner.getPulseLength_ns(0), + 7.0 // 3.5 too many ops., 7.0 just one op. + ); +} + +template +py::array_t create_numpy_array(T (&arr)[N]) { + return py::array_t(N, arr); +} \ No newline at end of file diff --git a/src/scanner/Scanner.h b/src/scanner/Scanner.h index a0139f266..bbd41024f 100644 --- a/src/scanner/Scanner.h +++ b/src/scanner/Scanner.h @@ -313,7 +313,7 @@ class Scanner : public Asset { * the scanning device */ virtual void calcRaysNumber(size_t const idx) = 0; - /** + /**calcAbsoluteBeamAttitude * @brief Non index version of the Scanner::calcRaysNumber(size_t const) * method * @see Scanner::calcRaysNumber(size_t const) diff --git a/tests/python/test_leg.py b/tests/python/test_leg.py index d06f038ae..05de28e0b 100755 --- a/tests/python/test_leg.py +++ b/tests/python/test_leg.py @@ -1,14 +1,14 @@ -from helios.leg import Leg -import numpy as np -from pydantic import ValidationError +# from helios.leg import Leg +# import numpy as np +# from pydantic import ValidationError -def test_belongs_to_strip(): - leg = Leg(serial_id=1) - assert leg.belongs_to_strip is False - leg.strip = "Some Strip Object" # Replace with an actual ScanningStrip object - assert leg.belongs_to_strip is True +# def test_belongs_to_strip(): +# leg = Leg(serial_id=1) +# assert leg.belongs_to_strip is False +# leg.strip = "Some Strip Object" # Replace with an actual ScanningStrip object +# assert leg.belongs_to_strip is True -def test_invalid_serial_id(): - with pytest.raises(ValidationError): - Leg(serial_id=-1) # NonNegativeInt should prevent negative serial_id \ No newline at end of file +# def test_invalid_serial_id(): +# with pytest.raises(ValidationError): +# Leg(serial_id=-1) # NonNegativeInt should prevent negative serial_id \ No newline at end of file diff --git a/tests/python/test_platform.py b/tests/python/test_platform.py index a1459c9f7..1af78d84a 100755 --- a/tests/python/test_platform.py +++ b/tests/python/test_platform.py @@ -1,38 +1,38 @@ -import numpy as np -from helios.platform import Platform -from helios.platformsettings import PlatformSettings +# import numpy as np +# from helios.platform import Platform +# from helios.platformsettings import PlatformSettings -def test_current_settings(): - platform = Platform() - platform.settings_speed_m_s = 10.0 - platform.is_on_ground = True - platform.position = np.array([100.0, 200.0, 300.0]) +# def test_current_settings(): +# platform = Platform() +# platform.settings_speed_m_s = 10.0 +# platform.is_on_ground = True +# platform.position = np.array([100.0, 200.0, 300.0]) - current_settings = platform.current_settings - import pytest - assert current_settings.speed_m_s == 10.0 - assert current_settings.is_on_ground == True - assert current_settings.position == [100.0, 200.0, 300.0] +# current_settings = platform.current_settings +# import pytest +# assert current_settings.speed_m_s == 10.0 +# assert current_settings.is_on_ground == True +# assert current_settings.position == [100.0, 200.0, 300.0] -def test_apply_settings(): - platform = Platform() - settings = PlatformSettings(speed_m_s=20.0, is_on_ground=False, position=[500.0, 600.0, 700.0]) +# def test_apply_settings(): +# platform = Platform() +# settings = PlatformSettings(speed_m_s=20.0, is_on_ground=False, position=[500.0, 600.0, 700.0]) - platform.apply_settings(settings) +# platform.apply_settings(settings) - assert platform.settings_speed_m_s == 20.0 - assert platform.is_on_ground == False - assert np.array_equal(platform.position, np.array([500.0, 600.0, 700.0])) +# assert platform.settings_speed_m_s == 20.0 +# assert platform.is_on_ground == False +# assert np.array_equal(platform.position, np.array([500.0, 600.0, 700.0])) -def test_update_static_cache(): - platform = Platform() - platform.origin_waypoint = np.array([0.0, 0.0, 0.0]) - platform.target_waypoint = np.array([100.0, 100.0, 0.0]) - platform.next_waypoint = np.array([200.0, 0.0, 0.0]) +# def test_update_static_cache(): +# platform = Platform() +# platform.origin_waypoint = np.array([0.0, 0.0, 0.0]) +# platform.target_waypoint = np.array([100.0, 100.0, 0.0]) +# platform.next_waypoint = np.array([200.0, 0.0, 0.0]) - platform.update_static_cache() +# platform.update_static_cache() - assert np.allclose(platform.cached_origin_to_target_dir_xy, np.array([100.0, 100.0, 0.0])) - assert np.allclose(platform.cached_target_to_next_dir_xy, np.array([100.0, -100.0, 0.0])) - assert platform.cached_end_target_angle_xy == pytest.approx(np.pi / 2) +# assert np.allclose(platform.cached_origin_to_target_dir_xy, np.array([100.0, 100.0, 0.0])) +# assert np.allclose(platform.cached_target_to_next_dir_xy, np.array([100.0, -100.0, 0.0])) +# assert platform.cached_end_target_angle_xy == pytest.approx(np.pi / 2) diff --git a/tests/python/test_scannersettings.py b/tests/python/test_scannersettings.py old mode 100644 new mode 100755 index 8e34d69f7..a7e038c0e --- a/tests/python/test_scannersettings.py +++ b/tests/python/test_scannersettings.py @@ -1,59 +1,59 @@ -from pydantic import ValidationError -from typing import Optional, Set -import numpy as np -from helios.scannersettings import ScannerSettings +# from pydantic import ValidationError +# from typing import Optional, Set +# import numpy as np +# from helios.scannersettings import ScannerSettings -def test_has_template(): - template = ScannerSettings(name="Template") - settings = ScannerSettings(name="Settings", _basic_template=template) - assert settings.has_template is True - assert settings.basic_template == template +# def test_has_template(): +# template = ScannerSettings(name="Template") +# settings = ScannerSettings(name="Settings", _basic_template=template) +# assert settings.has_template is True +# assert settings.basic_template == template -def test_no_template(): - settings = ScannerSettings(name="Settings") - assert settings.has_template is False - with pytest.raises(ValueError): - _ = settings.basic_template +# def test_no_template(): +# settings = ScannerSettings(name="Settings") +# assert settings.has_template is False +# with pytest.raises(ValueError): +# _ = settings.basic_template -def test_has_default_resolution(): - settings = ScannerSettings(name="Settings") - assert settings.has_default_resolution is True - settings.vertical_resolution = 1.0 - assert settings.has_default_resolution is False +# def test_has_default_resolution(): +# settings = ScannerSettings(name="Settings") +# assert settings.has_default_resolution is True +# settings.vertical_resolution = 1.0 +# assert settings.has_default_resolution is False -def test_fit_to_resolution(): - settings = ScannerSettings(name="Settings", pulse_frequency=100, vertical_resolution=1.0, horizontal_resolution=1.0) - settings.fit_to_resolution(np.pi / 2) - assert settings.scan_frequency == pytest.approx(31.831, rel=1e-2) - assert settings.head_rotation == pytest.approx(31.831, rel=1e-2) +# def test_fit_to_resolution(): +# settings = ScannerSettings(name="Settings", pulse_frequency=100, vertical_resolution=1.0, horizontal_resolution=1.0) +# settings.fit_to_resolution(np.pi / 2) +# assert settings.scan_frequency == pytest.approx(31.831, rel=1e-2) +# assert settings.head_rotation == pytest.approx(31.831, rel=1e-2) -def test_create_preset(): - preset = ScannerSettings.create_preset( - name="Preset", - pulse_frequency=1000, - horizontal_resolution=0.5, - vertical_resolution=0.5, - horizontal_fov=90, - min_vertical_angle=-10.0, - max_vertical_angle=10.0 - ) - assert preset.name == "Preset" - assert preset.pulse_frequency == 1000 - assert preset.horizontal_resolution == 0.5 - assert preset.vertical_resolution == 0.5 - assert preset.horizontal_fov == 90 - assert preset.min_vertical_angle == -10.0 - assert preset.max_vertical_angle == 10.0 +# def test_create_preset(): +# preset = ScannerSettings.create_preset( +# name="Preset", +# pulse_frequency=1000, +# horizontal_resolution=0.5, +# vertical_resolution=0.5, +# horizontal_fov=90, +# min_vertical_angle=-10.0, +# max_vertical_angle=10.0 +# ) +# assert preset.name == "Preset" +# assert preset.pulse_frequency == 1000 +# assert preset.horizontal_resolution == 0.5 +# assert preset.vertical_resolution == 0.5 +# assert preset.horizontal_fov == 90 +# assert preset.min_vertical_angle == -10.0 +# assert preset.max_vertical_angle == 10.0 -def test_to_file(tmp_path): - settings = ScannerSettings(name="Settings") - file_path = tmp_path / "settings.json" - settings.to_file(str(file_path)) - assert file_path.exists() +# def test_to_file(tmp_path): +# settings = ScannerSettings(name="Settings") +# file_path = tmp_path / "settings.json" +# settings.to_file(str(file_path)) +# assert file_path.exists() -def test_load_preset(tmp_path): - settings = ScannerSettings(name="Settings") - file_path = tmp_path / "settings.json" - settings.to_file(str(file_path)) - loaded_settings = ScannerSettings.load_preset(str(file_path)) - assert loaded_settings == settings +# def test_load_preset(tmp_path): +# settings = ScannerSettings(name="Settings") +# file_path = tmp_path / "settings.json" +# settings.to_file(str(file_path)) +# loaded_settings = ScannerSettings.load_preset(str(file_path)) +# assert loaded_settings == settings diff --git a/tests/python/test_scene.py b/tests/python/test_scene.py index 2d498477c..570672f2b 100755 --- a/tests/python/test_scene.py +++ b/tests/python/test_scene.py @@ -1,28 +1,28 @@ -from helios.scene import Scene -from helios.scenepart import ScenePart, ObjectType +# from helios.scene import Scene +# from helios.scenepart import ScenePart, ObjectType -def test_add_scene_part(sample_scene): - scene_part = ScenePart(object_type=ObjectType.STATIC_OBJECT) - sample_scene.add_scene_part(scene_part) - assert len(sample_scene.scene_parts) == 1 - assert scene_part in sample_scene.scene_parts +# def test_add_scene_part(sample_scene): +# scene_part = ScenePart(object_type=ObjectType.STATIC_OBJECT) +# sample_scene.add_scene_part(scene_part) +# assert len(sample_scene.scene_parts) == 1 +# assert scene_part in sample_scene.scene_parts -def test_specific_scene_part(sample_scene_with_parts): - scene_part = sample_scene_with_parts.specific_scene_part(1) - assert scene_part is not None - assert scene_part.object_type == ObjectType.DYN_MOVING_OBJECT +# def test_specific_scene_part(sample_scene_with_parts): +# scene_part = sample_scene_with_parts.specific_scene_part(1) +# assert scene_part is not None +# assert scene_part.object_type == ObjectType.DYN_MOVING_OBJECT -def test_specific_scene_part_out_of_range(sample_scene_with_parts): - scene_part = sample_scene_with_parts.specific_scene_part(10) - assert scene_part is None +# def test_specific_scene_part_out_of_range(sample_scene_with_parts): +# scene_part = sample_scene_with_parts.specific_scene_part(10) +# assert scene_part is None -def test_delete_scene_part(sample_scene_with_parts): - initial_len = len(sample_scene_with_parts.scene_parts) - assert sample_scene_with_parts.delete_scene_part(0) is True - assert len(sample_scene_with_parts.scene_parts) == initial_len - 1 +# def test_delete_scene_part(sample_scene_with_parts): +# initial_len = len(sample_scene_with_parts.scene_parts) +# assert sample_scene_with_parts.delete_scene_part(0) is True +# assert len(sample_scene_with_parts.scene_parts) == initial_len - 1 -def test_delete_scene_part_out_of_range(sample_scene_with_parts): - initial_len = len(sample_scene_with_parts.scene_parts) - assert sample_scene_with_parts.delete_scene_part(10) is False - assert len(sample_scene_with_parts.scene_parts) == initial_len \ No newline at end of file +# def test_delete_scene_part_out_of_range(sample_scene_with_parts): +# initial_len = len(sample_scene_with_parts.scene_parts) +# assert sample_scene_with_parts.delete_scene_part(10) is False +# assert len(sample_scene_with_parts.scene_parts) == initial_len \ No newline at end of file diff --git a/tests/python/test_scenepart.py b/tests/python/test_scenepart.py index e002a4ac3..d23f4dc5a 100755 --- a/tests/python/test_scenepart.py +++ b/tests/python/test_scenepart.py @@ -1,114 +1,114 @@ -from helios.scenepart import ScenePart -from helios.scene import Scene -import numpy as np +# from helios.scenepart import ScenePart +# from helios.scene import Scene +# import numpy as np -### different checks for transformation -def test_no_transformation(scene_part): - initial_origin = scene_part.origin.copy() - scene_part.transform() - assert np.allclose(scene_part.origin, initial_origin) - -def test_translation_only(scene_part): - translation_vector = np.array([1.0, 2.0, 3.0]) - scene_part.transform(translation=translation_vector) - assert np.allclose(scene_part.origin, np.array([1.0, 2.0, 3.0])) - -def test_scaling_only(scene_part): - initial_scale = scene_part.scale - scene_part.transform(scale=2.0) - assert scene_part.scale == 2.0 * initial_scale - -def test_translation_and_scaling(scene_part): - translation_vector = np.array([1.0, 2.0, 3.0]) - initial_scale = scene_part.scale - scene_part.transform(translation=translation_vector, scale=2.0) - assert np.allclose(scene_part.origin, np.array([1.0, 2.0, 3.0])) # Check translation - assert scene_part.scale == 2.0 * initial_scale # Check scaling - -def test_not_on_ground(scene_part): - translation_vector = np.array([1.0, 2.0, 3.0]) - scene_part.transform(translation=translation_vector, scale=None, is_on_ground=False) - assert scene_part.is_on_ground is False - -def test_apply_to_specific_axis(scene_part): - scene_part.transform(translation=np.array([1.0, 0.0, 0.0]), apply_to_axis=0) - assert np.allclose(scene_part.origin, np.array([1.0, 0.0, 0.0])) - -### IMPORTANT In general, think about usage of quaternions for rotation in the project!!!!!!!!!!!!! - -def test_rotate_using_axis_and_angle(scene_part): - scene_part.rotate(axis=np.array([0, 0, 1]), angle=90) # Rotate 90 degrees around z-axis - assert np.allclose(scene_part.rotation.quaternion, [0, 0, 0.70710678, 0.70710678]) - - -def test_rotate_with_origin(scene_part): - origin = np.array([1.0, 1.0, 1.0]) - scene_part.rotate(axis=np.array([0, 0, 1]), angle=90, origin=origin) - assert np.allclose(scene_part.origin, [1.0, 1.0, 1.0]) # Origin remains unchanged - -def test_rotate_with_custom_origin(scene_part): - custom_origin = np.array([2.0, 2.0, 2.0]) - scene_part.rotate(axis=np.array([0, 0, 1]), angle=90, origin=custom_origin) - assert np.allclose(scene_part.origin, [2.0, 2.0, 2.0]) # Origin is updated to custom_origin - - -### motion tests -def test_make_motion_translation_only(scene_part): - translation = np.array([0.5, 0, 0]) - scene_part.make_motion(translation=translation, loop=1) - expected_origin = np.array([0.5, 0.0, 0.0]) - assert np.allclose(scene_part.origin, expected_origin) - - -def test_make_motion_translation_and_rotation(scene_part): - translation = np.array([0.5, 0, 0]) - rotation_axis = np.array([0, 0, 1]) - rotation_angle = 90 # degrees - scene_part.make_motion(translation=translation, rotation_axis=rotation_axis, rotation_angle=rotation_angle, radians=False, loop=1) - expected_origin = np.array([0.5, 0.0, 0.0]) - assert np.allclose(scene_part.origin, expected_origin) - -def test_make_motion_with_loop(scene_part): - translation = np.array([0.2, 0, 0]) - loop = 50 - scene_part.make_motion(translation=translation, rotation_axis=None, rotation_angle=None, loop=loop) - expected_origin = np.array([0.2 * loop, 0.0, 0.0]) - assert np.allclose(scene_part.origin, expected_origin) - -def test_make_motion_with_rotation_center(scene_part): - translation = np.array([0.5, 0, 0]) - rotation_axis = np.array([0, 0, 1]) - rotation_angle = 90 # degrees - rotation_center = np.array([1.0, 1.0, 0.0]) - scene_part.make_motion(translation=translation, rotation_axis=rotation_axis, rotation_angle=rotation_angle, radians=False, rotation_center=rotation_center, loop=1) - # Verify the origin and rotation - expected_origin = np.array([1.5, 1.0, 0.0]) - assert np.allclose(scene_part.origin, expected_origin) - - -## test motion_sequence -def test_make_motion_sequence_translations_only(scene_part): - translations = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])] - scene_part.make_motion_sequence(translations=translations, rotation_axes=None, rotation_angles=None) - expected_origin = np.array([1.0, 1.0, 1.0]) - assert np.allclose(scene_part.origin, expected_origin) - - -def test_make_motion_sequence_translation_and_rotation(scene_part): - translations = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])] - rotation_axes = [np.array([0, 0, 1]), np.array([0, 1, 0]), np.array([1, 0, 0])] - rotation_angles = [90, 90, 90] # degrees - scene_part.make_motion_sequence(translations=translations, rotation_axes=rotation_axes, rotation_angles=rotation_angles, radians=False) - expected_origin = np.array([1.0, 1.0, 1.0]) - assert np.allclose(scene_part.origin, expected_origin) - - -def test_make_motion_sequence_with_rotation_centers(scene_part): - translations = [np.array([1.0, 0.0, 0.0])] - rotation_axes = [np.array([0, 0, 1])] - rotation_angles = [90] # degrees - rotation_centers = [np.array([0.0, 0.0, 0.0])] - scene_part.make_motion_sequence(translations=translations, rotation_axes=rotation_axes, rotation_angles=rotation_angles, radians=False, rotation_centers=rotation_centers) - expected_origin = np.array([1.0, 0.0, 0.0]) - assert np.allclose(scene_part.origin, expected_origin) +# ### different checks for transformation +# def test_no_transformation(scene_part): +# initial_origin = scene_part.origin.copy() +# scene_part.transform() +# assert np.allclose(scene_part.origin, initial_origin) + +# def test_translation_only(scene_part): +# translation_vector = np.array([1.0, 2.0, 3.0]) +# scene_part.transform(translation=translation_vector) +# assert np.allclose(scene_part.origin, np.array([1.0, 2.0, 3.0])) + +# def test_scaling_only(scene_part): +# initial_scale = scene_part.scale +# scene_part.transform(scale=2.0) +# assert scene_part.scale == 2.0 * initial_scale + +# def test_translation_and_scaling(scene_part): +# translation_vector = np.array([1.0, 2.0, 3.0]) +# initial_scale = scene_part.scale +# scene_part.transform(translation=translation_vector, scale=2.0) +# assert np.allclose(scene_part.origin, np.array([1.0, 2.0, 3.0])) # Check translation +# assert scene_part.scale == 2.0 * initial_scale # Check scaling + +# def test_not_on_ground(scene_part): +# translation_vector = np.array([1.0, 2.0, 3.0]) +# scene_part.transform(translation=translation_vector, scale=None, is_on_ground=False) +# assert scene_part.is_on_ground is False + +# def test_apply_to_specific_axis(scene_part): +# scene_part.transform(translation=np.array([1.0, 0.0, 0.0]), apply_to_axis=0) +# assert np.allclose(scene_part.origin, np.array([1.0, 0.0, 0.0])) + +# ### IMPORTANT In general, think about usage of quaternions for rotation in the project!!!!!!!!!!!!! + +# def test_rotate_using_axis_and_angle(scene_part): +# scene_part.rotate(axis=np.array([0, 0, 1]), angle=90) # Rotate 90 degrees around z-axis +# assert np.allclose(scene_part.rotation.quaternion, [0, 0, 0.70710678, 0.70710678]) + + +# def test_rotate_with_origin(scene_part): +# origin = np.array([1.0, 1.0, 1.0]) +# scene_part.rotate(axis=np.array([0, 0, 1]), angle=90, origin=origin) +# assert np.allclose(scene_part.origin, [1.0, 1.0, 1.0]) # Origin remains unchanged + +# def test_rotate_with_custom_origin(scene_part): +# custom_origin = np.array([2.0, 2.0, 2.0]) +# scene_part.rotate(axis=np.array([0, 0, 1]), angle=90, origin=custom_origin) +# assert np.allclose(scene_part.origin, [2.0, 2.0, 2.0]) # Origin is updated to custom_origin + + +# ### motion tests +# def test_make_motion_translation_only(scene_part): +# translation = np.array([0.5, 0, 0]) +# scene_part.make_motion(translation=translation, loop=1) +# expected_origin = np.array([0.5, 0.0, 0.0]) +# assert np.allclose(scene_part.origin, expected_origin) + + +# def test_make_motion_translation_and_rotation(scene_part): +# translation = np.array([0.5, 0, 0]) +# rotation_axis = np.array([0, 0, 1]) +# rotation_angle = 90 # degrees +# scene_part.make_motion(translation=translation, rotation_axis=rotation_axis, rotation_angle=rotation_angle, radians=False, loop=1) +# expected_origin = np.array([0.5, 0.0, 0.0]) +# assert np.allclose(scene_part.origin, expected_origin) + +# def test_make_motion_with_loop(scene_part): +# translation = np.array([0.2, 0, 0]) +# loop = 50 +# scene_part.make_motion(translation=translation, rotation_axis=None, rotation_angle=None, loop=loop) +# expected_origin = np.array([0.2 * loop, 0.0, 0.0]) +# assert np.allclose(scene_part.origin, expected_origin) + +# def test_make_motion_with_rotation_center(scene_part): +# translation = np.array([0.5, 0, 0]) +# rotation_axis = np.array([0, 0, 1]) +# rotation_angle = 90 # degrees +# rotation_center = np.array([1.0, 1.0, 0.0]) +# scene_part.make_motion(translation=translation, rotation_axis=rotation_axis, rotation_angle=rotation_angle, radians=False, rotation_center=rotation_center, loop=1) +# # Verify the origin and rotation +# expected_origin = np.array([1.5, 1.0, 0.0]) +# assert np.allclose(scene_part.origin, expected_origin) + + +# ## test motion_sequence +# def test_make_motion_sequence_translations_only(scene_part): +# translations = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])] +# scene_part.make_motion_sequence(translations=translations, rotation_axes=None, rotation_angles=None) +# expected_origin = np.array([1.0, 1.0, 1.0]) +# assert np.allclose(scene_part.origin, expected_origin) + + +# def test_make_motion_sequence_translation_and_rotation(scene_part): +# translations = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])] +# rotation_axes = [np.array([0, 0, 1]), np.array([0, 1, 0]), np.array([1, 0, 0])] +# rotation_angles = [90, 90, 90] # degrees +# scene_part.make_motion_sequence(translations=translations, rotation_axes=rotation_axes, rotation_angles=rotation_angles, radians=False) +# expected_origin = np.array([1.0, 1.0, 1.0]) +# assert np.allclose(scene_part.origin, expected_origin) + + +# def test_make_motion_sequence_with_rotation_centers(scene_part): +# translations = [np.array([1.0, 0.0, 0.0])] +# rotation_axes = [np.array([0, 0, 1])] +# rotation_angles = [90] # degrees +# rotation_centers = [np.array([0.0, 0.0, 0.0])] +# scene_part.make_motion_sequence(translations=translations, rotation_axes=rotation_axes, rotation_angles=rotation_angles, radians=False, rotation_centers=rotation_centers) +# expected_origin = np.array([1.0, 0.0, 0.0]) +# assert np.allclose(scene_part.origin, expected_origin)