Skip to content

root folder

This folder contains mainly installation files and the main digital twin program.

digital_twin.bat

File to run the digital twin in MSWindows OS. It is installation specific and is modified by the setup.py script (see below)

    @echo off
    python "C:\Users\blecha\eVectors\03-Digital_twin\digital_twin.py" %*

digital_twin.py

Main python program.

It loads and the input_file.yaml and call the different modules to run the desired analysis.

mkdocs.yaml

Documentation root file used by mkdocs

requirements.txt

Package dependencies for users

    # This is the list of packages required to run the digital twin
    # to install all the packages at once: pip install -r requirements.txt
    numpy >= 1.24.0
    pandas >= 2.0.0
    matplotlib
    scipy >= 1.10.1
    pyyaml
    tk
    pyproj
    pytest
    pytest-benchmark

requirements-docs.txt

Package dependencies for developpers willing to update the documentation

    # Additional required packages to edit the documentation
    # to install, run: pip install -r requirements-docs.txt
    mkdocs
    mkdocs-material
    mkdocs-monorepo-plugin
    mkdocstrings
    mkdocstrings-python
    mkdocs-include-markdown-plugin

setup.py

Installation script.

    import os
    import sys
    import winreg
    import importlib.util
    import ctypes
    from ctypes import wintypes

    def get_script_dir():
        # Absolute path to the directory where setup.py resides
        return os.path.abspath(os.path.dirname(__file__))

    def create_bat_file(script_dir):
        # Path to main.py
        main_py = os.path.join(script_dir, 'digital_twin.py')
        main_py = os.path.normpath(main_py)
        # Path to .bat file (in the same dir)
        bat_path = os.path.join(script_dir, 'digital_twin.bat')
        bat_content = f'@echo off\npython "{main_py}" %*\n'
        with open(bat_path, 'w') as f:
            f.write(bat_content)
        print(f".bat file created in: {bat_path}\n")
        return bat_path

    def get_user_path():
        # Read the user's PATH variable from the registry
        with winreg.OpenKey(
            winreg.HKEY_CURRENT_USER, 
            r'Environment', 
            0, winreg.KEY_READ
        ) as key:
            try:
                value, _ = winreg.QueryValueEx(key, 'Path')
                return value
            except FileNotFoundError:
                return ""

    def set_user_path(new_path):
        # Set the user's PATH variable in the registry
        with winreg.OpenKey(
            winreg.HKEY_CURRENT_USER, 
            r'Environment', 
            0, winreg.KEY_SET_VALUE
        ) as key:
            winreg.SetValueEx(key, 'Path', 0, winreg.REG_EXPAND_SZ, new_path)
        # Broadcast WM_SETTINGCHANGE to notify the system of the change
        HWND_BROADCAST = 0xFFFF
        WM_SETTINGCHANGE = 0x001A
        SMTO_ABORTIFHUNG = 0x0002

        result = ctypes.windll.user32.SendMessageTimeoutW(
            HWND_BROADCAST,
            WM_SETTINGCHANGE,
            0,
            "Environment",
            SMTO_ABORTIFHUNG,
            5000,  # 5 second timeout
            ctypes.byref(wintypes.DWORD())
        )

        # Update the current process's environment immediately
        os.environ['PATH'] = new_path

        return result != 0  # Returns True if broadcast was successful

    def add_to_user_path(script_dir):
        # Add script_dir to user's PATH if not present
        path = get_user_path()
        path_list = [os.path.normcase(os.path.normpath(p)) for p in path.split(';') if p]
        script_dir_norm = os.path.normcase(os.path.normpath(script_dir))
        if script_dir_norm not in path_list:
            new_path = path + (';' if path and not path.endswith(';') else '') + script_dir
            isbroadcasted = set_user_path(new_path)
            print(f"Added '{script_dir}' to the user PATH.\n")
            if isbroadcasted == True:
                print("You may need to restart your terminal.\n")
            else:
                print("You need to restart your terminal or log out and log in for changes to take effect.\n")
        else:
            print(f"'{script_dir}' is already in the user PATH.\n")

    def parse_requirements(requirements_path):
        requirements = []
        if not os.path.isfile(requirements_path):
            print("No requirements.txt found.\n")
            return requirements
        with open(requirements_path, 'r') as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#'):
                    # Only take the package name (strip version info for import check)
                    pkg_name = line.split('==')[0].split('>=')[0].split('<=')[0].strip()
                    requirements.append((pkg_name, line))
        return requirements

    def check_installed(requirements):
        missing = []
        for pkg_name, full_req in requirements:
            # Try to import using importlib.util.find_spec
            # Some packages have different import names!
            # Example: 'pillow' is installed as 'Pillow', import as 'PIL'
            # You may wish to expand this mapping if needed.
            import_name = pkg_name
            if pkg_name.lower() == 'pillow':
                import_name = 'PIL'
            elif pkg_name.lower() == 'pyyaml':
                import_name = 'yaml'
            elif pkg_name.lower() == 'opencv-python':
                import_name = 'cv2'
            elif pkg_name.lower() == 'scikit-learn':
                import_name = 'sklearn'
            elif pkg_name.lower() == 'beautifulsoup4':
                import_name = 'bs4'
            elif pkg_name.lower() == 'python-dateutil':
                import_name = 'dateutil'
            elif pkg_name.lower() == 'pyserial':
                import_name = 'serial'
            elif pkg_name.lower() == 'pyqt5':
                import_name = 'PyQt5'
            elif pkg_name.lower() == 'tk':
                import_name = 'tkinter'

            if importlib.util.find_spec(import_name) is None:
                missing.append(full_req)
        return missing

    def check_requirements(script_dir):
        requirements_path = os.path.join(script_dir, 'requirements.txt')
        requirements = parse_requirements(requirements_path)
        if not requirements:
            return
        missing = check_installed(requirements)
        if missing:
            print("\nThe following Python packages are required but not installed:")
            for pkg in missing:
                print(f"  {pkg}")
            print("\nPlease install them using:")
            print("  python -m pip install -r requirements.txt\n")
        else:
            print("All required Python packages are installed.\n")

    def main():
        if os.name != 'nt':
            print("This script only works on Windows.")
            sys.exit(1)
        print("\nThis is the setup script for the eVector digital twin\n")
        print("This script creates a .bat file and adds it to the user's path\n")
        script_dir = get_script_dir()
        print(f"Script directory: {script_dir}\n")
        bat_path = create_bat_file(script_dir)
        add_to_user_path(script_dir)
        check_requirements(script_dir)
        print("You can know execute eVECTORS digital twin in a consol\n")
        print("Start with the GUI: digital_twin.bat -i yes")
        print("Start with file argument: digital_twin.bat -f input_file.yaml")
        print("Get help: digital_twin.bat -h")
        print("\nDigital Twin setup script finished with grace!\n")
        print("Enjoy your day!")

    if __name__ == '__main__':
        main()