diff --git a/README.md b/README.md index 587bb2e..755750c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,24 @@ python -m venv .venv pip install -r requirements.txt ``` -# Usage +After installing, you need to set the pathes to the Sacher Libaray using +the `config.py` file. + +```python + conf = Laser.Config() + conf.save() + #conf.load('./LaserConfig.yaml', as_auto_save=True) + conf.autosave(True, './LaserConfig.yaml') + + # Set the path to the EposCmd64.dll and the SacherMotorControl.pyd + conf.epos_dll.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/EposCmd64.dll')) + + conf.motor_control_pyd.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/' + f'lib/Python312/SacherMotorControl.pyd')) +``` +See (main.py)[./examples/main.py] for an example. Run the main script: diff --git a/examples/CaptDeviceConfig.yaml b/examples/CaptDeviceConfig.yaml index 4fb030c..0d8e819 100644 --- a/examples/CaptDeviceConfig.yaml +++ b/examples/CaptDeviceConfig.yaml @@ -1,4 +1,4 @@ -# - Configuration file stored 2024-08-07 15:54:06.643611 - +# - Configuration file stored 2024-08-08 13:18:18.949366 - CaptDeviceConfig: #!!python/object:controller.CaptDeviceConfig selected_device_index: 0 # Selected device: Selected device from the device list provided by the DreamWaves API. sample_rate: 500 # Sample rate: Sample rate of the device diff --git a/examples/LaserConfig.yaml b/examples/LaserConfig.yaml index 8ce6852..a635991 100644 --- a/examples/LaserConfig.yaml +++ b/examples/LaserConfig.yaml @@ -1,9 +1,11 @@ -# - Configuration file stored 2024-07-31 11:25:23.500548 - +# - Configuration file stored 2024-08-08 13:17:14.134241 - LaserConfig: #!!python/object:controller.LaserConfig + epos_dll: "@Path:" # epos_dll: None + motor_control_pyd: "@Path:" # motor_control_pyd: None wl_sweep_start: 857 # wl_sweep_start: None wl_sweep_stop: 870 # wl_sweep_stop: None - velocity: 10.0 # velocity: None - acceleration: 2.0 # acceleration: None - deceleration: 2.0 # deceleration: None + velocity: 2.0 # velocity: None + acceleration: 1.0 # acceleration: None + deceleration: 1.0 # deceleration: None available_ports: ['USB0', 'USB1', 'USB2', 'USB3', 'USB4', 'USB5', 'USB6', 'USB7', 'USB8', 'USB9'] # available_ports: None port: "USB0" # port: None diff --git a/examples/Main_ADScope.py b/examples/Main_ADScope.py index be5376c..abe36b0 100644 --- a/examples/Main_ADScope.py +++ b/examples/Main_ADScope.py @@ -1,6 +1,7 @@ import logging import sys import os +import pathlib from multiprocessing import Value from rich.logging import RichHandler @@ -32,6 +33,7 @@ #conf_capt_dev.load("CaptDeviceConfig.yaml") conf_capt_dev.autosave() + start_capture_flag = Value('i', 0) capt_dev_model = captdev.Model(conf_capt_dev) @@ -40,6 +42,14 @@ conf = Laser.Config() + # Set the path to the EposCmd64.dll and the SacherMotorControl.pyd + conf.epos_dll.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/EposCmd64.dll')) + + conf.motor_control_pyd.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/' + f'lib/Python312/SacherMotorControl.pyd')) + model = Laser.Model(conf) controller = Laser.Controller(model, start_capture_flag) window = Laser.View(model, controller) diff --git a/examples/main.py b/examples/main.py index d16d0ef..cb3a5ff 100644 --- a/examples/main.py +++ b/examples/main.py @@ -1,4 +1,6 @@ import logging +import os +import pathlib import sys from PySide6.QtWidgets import QApplication from rich.logging import RichHandler @@ -18,15 +20,24 @@ else: app = QApplication.instance() - - conf = Laser.Config() + conf.save() #conf.load('./LaserConfig.yaml', as_auto_save=True) conf.autosave(True, './LaserConfig.yaml') + + # Set the path to the EposCmd64.dll and the SacherMotorControl.pyd + conf.epos_dll.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/EposCmd64.dll')) + + conf.motor_control_pyd.set(pathlib.Path( + f'{Laser.__rootdir__}/libs/SacherLib/PythonMotorControlClass/' + f'lib/Python312/SacherMotorControl.pyd')) + + + conf.module_log_level = logging.DEBUG conf.module_log_enabled = True - model = Laser.Model(conf) controller = Laser.Controller(model, None) controller.internal_log_level = logging.DEBUG @@ -38,8 +49,6 @@ # model.sweep_stop_wavelength, #) - window.show() - - sys.exit(app.exec()) \ No newline at end of file + sys.exit(app.exec()) diff --git a/pyproject.toml b/pyproject.toml index cf9d6b1..2b99527 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ packages = ["src/SacherECLControl"] [project] name = "PySacherECLControl" -version = "1.1.3" +version = "1.1.5" authors = [ { name="Christoph Schmidt", email="cschmidt.fs@gmail.com" }, ] @@ -22,10 +22,10 @@ dependencies = [ 'PySide6', 'rich', 'pyyaml', - 'PyADScopeControl>=1.1.4', + 'PyADScopeControl>=1.1.7', 'PySide6WidgetCollection>=1.0.2', # important, otherwise AboutDialog is not available 'mpPy6', - 'confPy6' + 'confPy6>=1.3.1' ] classifiers = [ "Programming Language :: Python :: 3", diff --git a/requirements.txt b/requirements.txt index f10ce69..003f170 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,8 +8,8 @@ pyyaml # my packages mpPy6 -confPy6 -PyADScopeControl>=1.1.4 +confPy6>=1.3.1 +PyADScopeControl>=1.1.7 PySide6WidgetCollection>=1.0.2, # important, otherwise AboutDialog is not available diff --git a/src/SacherECLControl/Helpers.py b/src/SacherECLControl/Helpers.py new file mode 100644 index 0000000..db2d8b0 --- /dev/null +++ b/src/SacherECLControl/Helpers.py @@ -0,0 +1,58 @@ +# ====================================================================================================================== +# EposCMD64.dll is needed, thus, try to copy it +# ====================================================================================================================== +import os +import pathlib +import shutil + + + +def copyEposDLL(epos_dll="./EposCMD64.dll", logger=None): + if logger is None: + logger = print + else: + logger = logger.info + + dll_dir = pathlib.Path(epos_dll) + dll_name = os.path.basename(epos_dll) + #epos_dll_name = pathlib.Path(epos_dll_name) + logger(f"Copying {dll_dir} to {os.getcwd()}") + + if not dll_dir.exists(): + raise FileNotFoundError(f"Could not find {epos_dll}") + + dll_dir = str(dll_dir.resolve()) + dll_dest = f"{os.getcwd()}/{dll_name}" # Copy the file to the workspace folder + # Copy the file "EposCMD64.dll" to the current dir + if not pathlib.Path(dll_dest).exists(): + shutil.copyfile(dll_dir, f"{os.getcwd()}/EposCMD64.dll") + logger(f"Copied {dll_name} to {os.getcwd()}") + elif pathlib.Path(dll_dest).exists(): + logger(f"{dll_name} already exists in {os.getcwd()}") + + + # Check if the file was copied + if not pathlib.Path(dll_dest).exists(): + raise FileNotFoundError(f"Could not copy {dll_name} to {os.getcwd()}") + +def check_if_git_or_pip(): + # check if a folder .git is present ../../ + git_dir = pathlib.Path(__file__).parent.parent.parent / ".git" + if git_dir.exists(): + print("git_dir:", git_dir) + else: + print("git_dir does not exist") + +def get_pyprojecttoml() -> pathlib.Path: + # is found in ../../pyconfig.toml + pytoml_via_git = pathlib.Path(__file__).parent.parent.parent / "pyproject.toml" + # found in ./pyconfig.toml: Copied to the root dir + pytoml_via_pip = pathlib.Path(__file__).parent / "pyproject.toml" + + if pytoml_via_git.exists(): + #print("pytoml_via_git:", pytoml_via_git) + return pytoml_via_git.resolve().absolute() + elif pytoml_via_pip.exists(): + #print("pytoml_via_pip:", pytoml_via_pip) + return pytoml_via_pip.resolve().absolute() + diff --git a/src/SacherECLControl/LaserConfig.py b/src/SacherECLControl/LaserConfig.py index ccb16f8..4816a6e 100644 --- a/src/SacherECLControl/LaserConfig.py +++ b/src/SacherECLControl/LaserConfig.py @@ -1,4 +1,6 @@ import logging +import os +from pathlib import Path import confPy6 as cfg @@ -7,6 +9,14 @@ class LaserConfig(cfg.ConfigNode): def __init__(self) -> None: super().__init__() + + # Set the path to the EposCmd64.dll + self.epos_dll = cfg.Field(Path("./EposCmd64.dll")) + # Set the path to the SacherMotorControl.pyd + self.motor_control_pyd = cfg.Field( + Path(f"./SacherMotorControl.pyd"), + env_var="MOTOR_CONTROL_PYD") + self.wl_sweep_start = cfg.Field(857) self.wl_sweep_stop = cfg.Field(870) self.velocity = cfg.Field(2.0) diff --git a/src/SacherECLControl/__init__.py b/src/SacherECLControl/__init__.py index bb67f7a..507310a 100644 --- a/src/SacherECLControl/__init__.py +++ b/src/SacherECLControl/__init__.py @@ -4,29 +4,32 @@ import shutil import sys +from WidgetCollection.Tools.PyProjectExtractor import extract_pyproject_info + +from . import Helpers + sys.path.append(os.path.join(os.path.dirname(__file__), '../')) -# from SacherECLControl.controller.LaserCon import LaserCon -dll_dir = pathlib.Path( - f"{os.path.dirname(os.path.realpath(__file__))}/libs/SacherLib/PythonMotorControlClass/EposCMD64.dll") -dll_dir = str(dll_dir.resolve()) -dll_dest = f"{os.getcwd()}/EposCMD64.dll" -# Copy the file "EposCMD64.dll" to the current dir -if not pathlib.Path(dll_dest).exists(): - shutil.copyfile(dll_dir, f"{os.getcwd()}/EposCMD64.dll") -from WidgetCollection.Tools.PyProjectExtractor import extract_pyproject_info -pytoml = pathlib.Path(__file__).parent.parent.parent -if not (pytoml / "pyproject.toml").exists(): - # if installed via pip - pytoml = pathlib.Path(__file__) +# ====================================================================================================================== +# The pyconfig.toml file is needed, to get the metadata. Depending on the installation method (pip or git) the file +# is found in different places. +# ====================================================================================================================== +pytoml = Helpers.get_pyprojecttoml() +def try_and_set(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + print(f"Error reading '{args[1]}' from {pathlib.Path(args[0])}: {e}") + return "unknown" -__version__ = extract_pyproject_info(pytoml, "version") -__author__ = extract_pyproject_info(pytoml, "author") -__description__ = extract_pyproject_info(pytoml, "description") -__license__ = extract_pyproject_info(pytoml, "license") -__url__ = extract_pyproject_info(pytoml, "url") +__rootdir__ = os.path.dirname(os.path.realpath(__file__)) +__version__ = try_and_set(extract_pyproject_info, pytoml.parent, "version") +__author__ = try_and_set(extract_pyproject_info, pytoml.parent, "author") +__description__ = try_and_set(extract_pyproject_info, pytoml.parent, "description") +__license__ = try_and_set(extract_pyproject_info, pytoml.parent, "license") +__url__ = try_and_set(extract_pyproject_info, pytoml.parent, "url") # For correctly display the icon in the taskbar myappid = f'agentsmith29.SacherECLControl.{__version__}' # arbitrary string diff --git a/src/SacherECLControl/controller/LaserDeviceControl.py b/src/SacherECLControl/controller/LaserDeviceControl.py index 2bc5d60..367fda4 100644 --- a/src/SacherECLControl/controller/LaserDeviceControl.py +++ b/src/SacherECLControl/controller/LaserDeviceControl.py @@ -6,7 +6,7 @@ from PySide6.QtCore import Signal from mpPy6.CProcessControl import CProcessControl -from SacherECLControl.controller.multiprocess.MPLaserDevice import MPLaserDevice +from SacherECLControl import Helpers from SacherECLControl.model.LaserControlModel import LaserControlModel @@ -46,6 +46,11 @@ def __init__(self, model: LaserControlModel, start_capture_flag: Value, module_log=True, module_log_level=logging.WARNING): super().__init__(module_log=module_log, module_log_level=module_log_level) + # This is a workaround, otherwise the dll and pyd object import would not work + # Reason: We need assign the correct path beforehand. If we would first try to import the MPLaserDevice class + # the pathes would net be set. + # TODO: Find a better approach than this hacky way? + from SacherECLControl.controller.multiprocess.MPLaserDevice import MPLaserDevice self.model = model self.lock = Lock() @@ -57,7 +62,8 @@ def __init__(self, model: LaserControlModel, self.register_child_process(MPLaserDevice, self._laser_moving_flag, self._laser_finished_flag, - start_capture_flag) + start_capture_flag, + self.model.laser_config.epos_dll.get()) self.connected_changed.connect(self._on_connected_changed) diff --git a/src/SacherECLControl/controller/multiprocess/MPLaserDevice.py b/src/SacherECLControl/controller/multiprocess/MPLaserDevice.py index de6a54c..15e6d8c 100644 --- a/src/SacherECLControl/controller/multiprocess/MPLaserDevice.py +++ b/src/SacherECLControl/controller/multiprocess/MPLaserDevice.py @@ -1,16 +1,25 @@ import os import pathlib +import sys import time from multiprocessing import Value import mpPy6 from mpPy6.CProperty import CProperty +from SacherECLControl import Helpers, __rootdir__ # Copy the dll to the startup directory +try: + spath = str(pathlib.Path(os.getenv("MOTOR_CONTROL_PYD")).absolute().parent.as_posix()) + sys.path.append(spath) + import SacherMotorControl as LaserLib + +except Exception as e: + print(f"Warning: Could not import SacherMotorControl: {e}. Using LaserLibSimulator instead.") + from SacherECLControl.libs import LaserLibSimulator as LaserLib -from SacherECLControl.libs.SacherLib.PythonMotorControlClass.lib.Python312 import SacherMotorControl as LaserLib #from SacherECLControl.libs import LaserLibSimulator as LaserLib @@ -21,12 +30,22 @@ def __init__(self, state_queue, cmd_queue, laser_moving_flag: Value, laser_finished_flag: Value, start_capture_flag: Value, + dll_copy_path, kill_flag: Value, internal_log, internal_log_level, log_file): super().__init__(state_queue, cmd_queue, kill_flag=kill_flag, internal_log=internal_log, internal_log_level=internal_log_level, log_file=log_file) + + self.dll_copy_path = dll_copy_path + #import importlib.util + #import sys + #spec = importlib.util.spec_from_file_location(module_name, module_name_path) + #foo = importlib.util.module_from_spec(spec) + #sys.modules[module_name] = foo + #spec.loader.exec_module(foo) + # if not self.logger.handlers: # self.logger.setLevel(level=logging.DEBUG) # self.logger.disabled = False @@ -50,6 +69,7 @@ def __init__(self, state_queue, cmd_queue, self._wavelength_sweep_running = False def postrun_init(self): + Helpers.copyEposDLL(f"{self.dll_copy_path}", logger=self.logger) self.laser = LaserLib.Motor() # ================================================================================================================== @@ -317,9 +337,6 @@ def get_laser_settings(self, *args, **kwargs): self.get_acceleration() self.get_deceleration() - - - @CProperty def laser_is_moving(self): return self._laser_moving diff --git a/src/SacherECLControl/resources/icons-svg/sacherelccontrol_icon.svg b/src/SacherECLControl/resources/icons-svg/sacherelccontrol_icon.svg index 2f8ca24..082e141 100644 --- a/src/SacherECLControl/resources/icons-svg/sacherelccontrol_icon.svg +++ b/src/SacherECLControl/resources/icons-svg/sacherelccontrol_icon.svg @@ -58,7 +58,10 @@ height="270" x="118.07262" y="134.39658" - ry="30.085812" />