diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml
index 5e07429a14a..2bd399e3bc3 100644
--- a/.github/workflows/ci_cd.yml
+++ b/.github/workflows/ci_cd.yml
@@ -150,6 +150,53 @@ jobs:
python -c "import ansys.aedt.core; from ansys.aedt.core import __version__"
+
+ build-application-windows:
+ name: "Build SAM Bot - Windows"
+ runs-on: windows-latest
+ needs: [pr-title]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.MAIN_PYTHON_VERSION }}
+
+ - name: Install Dependencies
+ run: pip install .[freeze]
+
+ - name: Extract version
+ run: |
+ python src/ansys/aedt/core/extensions/pyaedt_bot/installer/extract_version.py
+
+ - name: Freeze application
+ run: pyinstaller src/ansys/aedt/core/extensions/pyaedt_bot/installer/frozen.spec
+
+ - name: Install NSIS
+ run: choco install nsis -y
+
+ - name: Run NSIS
+ shell: pwsh
+ if: always()
+ run: |
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = "Stop"
+
+ $setupPath = "src/ansys/aedt/core/extensions/pyaedt_bot/installer/setup.nsi"
+
+ if (!(Test-Path -Path $setupPath)) {
+ Write-Error "NSIS script not found at $setupPath"
+ }
+ & makensis $setupPath
+
+# - name: List output
+# run: ls -R dist
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: SAM-Bot-Installer-windows
+ path: dist/pyaedt_bot/SAM-Bot-Installer-windows.exe
+
unit-tests:
name: Running unit tests
needs: [smoke-tests]
diff --git a/codecov.yml b/codecov.yml
index 05ba2e94749..4fcf6777674 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -4,6 +4,7 @@ ignore:
- "src/ansys/aedt/core/visualization/advanced/sbrplus/hdm_utils.py"
- "src/ansys/aedt/core/extensions/installer"
- "src/ansys/aedt/core/extensions/templates"
+ - "src/ansys/aedt/core/extensions/pyaedt_bot"
- "src/ansys/aedt/core/common_rpc.py"
- "src/ansys/aedt/core/internal/grpc_plugin_dll_class.py"
- "src/ansys/aedt/core/edb.py"
diff --git a/doc/changelog.d/6145.added.md b/doc/changelog.d/6145.added.md
new file mode 100644
index 00000000000..395269c030d
--- /dev/null
+++ b/doc/changelog.d/6145.added.md
@@ -0,0 +1 @@
+SAM Bot
\ No newline at end of file
diff --git a/doc/source/Resources/pyaedt_installer_from_aedt.py b/doc/source/Resources/pyaedt_installer_from_aedt.py
index b4eb277dfdb..e80df1f483b 100644
--- a/doc/source/Resources/pyaedt_installer_from_aedt.py
+++ b/doc/source/Resources/pyaedt_installer_from_aedt.py
@@ -37,7 +37,6 @@
is_linux = os.name == "posix"
is_windows = not is_linux
-
VENV_DIR_PREFIX = ".pyaedt_env"
"""
@@ -52,7 +51,6 @@
else:
VENV_DIR = os.path.join(os.environ["HOME"], VENV_DIR_PREFIX)
-
DISCLAIMER = (
"This script will download and install certain third-party software and/or "
"open-source software (collectively, 'Third-Party Software'). Such Third-Party "
@@ -274,9 +272,11 @@ def install_pyaedt():
subprocess.run([str(python_exe), "-m", "pip", "install", "--upgrade", "pip"], check=True) # nosec
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "wheel"], check=True) # nosec
if args.version <= "231":
- subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "pyaedt[all]=='0.9.0'"], check=True) # nosec
+ subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "pyaedt[all]=='0.9.0'"],
+ check=True) # nosec
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "jupyterlab"], check=True) # nosec
- subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipython", "-U"], check=True) # nosec
+ subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipython", "-U"],
+ check=True) # nosec
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipyvtklink"], check=True) # nosec
else:
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "pyaedt[all]"], check=True) # nosec
@@ -308,7 +308,8 @@ def install_pyaedt():
if args.version <= "231":
subprocess.run([str(pip_exe), "pip=1000", "install", "pyaedt[all]=='0.9.0'"], check=True) # nosec
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "jupyterlab"], check=True) # nosec
- subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipython", "-U"], check=True) # nosec
+ subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipython", "-U"],
+ check=True) # nosec
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "ipyvtklink"], check=True) # nosec
else:
subprocess.run([str(pip_exe), "--default-timeout=1000", "install", "pyaedt[all]"], check=True) # nosec
diff --git a/doc/source/User_guide/extensions.rst b/doc/source/User_guide/extensions.rst
index e989e0d8352..c6d2b1d09a5 100644
--- a/doc/source/User_guide/extensions.rst
+++ b/doc/source/User_guide/extensions.rst
@@ -13,6 +13,7 @@ The following sections provide further clarification.
You can launch extensions in standalone mode from the console or a Python script.
+
Pre-installed extensions
------------------------
@@ -151,15 +152,6 @@ They are small automated workflows with a simple GUI.
Shielding effectiveness automated workflow HFSS.
-
- .. grid-item-card:: Point cloud generator
- :link: pyaedt_extensions_doc/hfss/point_cloud_generator
- :link-type: doc
- :margin: 2 2 0 0
-
- Generate object and surface conforming point clouds.
-
-
.. grid-item-card:: Move it
:link: pyaedt_extensions_doc/hfss/move_it
:link-type: doc
@@ -200,6 +192,14 @@ They are small automated workflows with a simple GUI.
Import different schematic files (ACS, SP, CIR, QCV) into Circuit.
+ .. grid-item-card:: Circuit configuration
+ :link: pyaedt_extensions_doc/circuit/circuit_configuration
+ :link-type: doc
+ :margin: 2 2 0 0
+
+ Apply simulation configuration to a Circuit design.
+
+
Twin Builder extensions
~~~~~~~~~~~~~~~~~~~~~~~
@@ -330,3 +330,45 @@ The Python script requires a common initial part to define the port and the vers
aedtapp.modeler.create_sphere([0, 0, 0], 20)
app.release_desktop(False, False)
+
+
+SAM Bot
+-------
+
+SAM Bot (Smart AEDT Manager) is a standalone utility that provides a floating GUI for launching PyAEDT-based automation scripts quickly and easily.
+It enables a drag-and-drop experience and a right-click context menu for executing extensions defined in a TOML configuration file.
+
+**Key features:**
+
+- Launch PyAEDT extensions directly without opening AEDT manually.
+- Use a TOML file to define and organize multiple automation scripts.
+- Drag the bot across the screen or dock it in a convenient location.
+- Access the context menu by right-clicking on the bot icon.
+
+.. image:: ../_static/sam_bot_example.png
+ :width: 300
+ :alt: SAM Bot Interface
+
+**Installation:**
+
+You can download and install SAM Bot by visiting the following link:
+
+`Download SAM Bot `_
+
+After downloading the `.exe` file, you can run it directly.
+To customize the configuration, set the `PYAEDT_BOT_CONFIG` environment variable to the path of your TOML file.
+
+**Example TOML file:**
+
+.. code:: toml
+
+ interpreter = "Your_Python_Interpreter"
+
+ [Project]
+ extension_1 = { name = "Import Nastran/STL", script = "project/import_nastran.py" }
+
+ [NewSection]
+ extension_1 = { name = "Custom extension", script = "custom_script.py" }
+ extension_2 = { name = "Choke Designer", script = "hfss/choke_designer.py" }
+
+**Note:** SAM Bot is compatible with Python 3.10 and is typically used with a virtual environment managed by PyAEDT.
diff --git a/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst
index 8195221c5d3..3f58bebd911 100644
--- a/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst
+++ b/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst
@@ -9,3 +9,11 @@ Circuit extensions
:margin: 2 2 0 0
Import different schematic files (ACS, SP, CIR, QCV) into Circuit.
+
+
+ .. grid-item-card:: Circuit configuration
+ :link: circuit_configuration
+ :link-type: doc
+ :margin: 2 2 0 0
+
+ Apply simulation configuration to a Circuit design.
diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst
index 7a9d9bbfb86..54d9f8845ae 100644
--- a/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst
+++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst
@@ -17,7 +17,6 @@ HFSS extensions
Edit a source from file data in HFSS.
-
.. grid-item-card:: Shielding effectiveness
:link: shielding
:link-type: doc
@@ -25,7 +24,6 @@ HFSS extensions
Shielding effectiveness workflow in HFSS.
-
.. grid-item-card:: Move it
:link: move_it
:link-type: doc
diff --git a/doc/source/_static/sam_bot_example.png b/doc/source/_static/sam_bot_example.png
new file mode 100644
index 00000000000..8adbebbab7d
Binary files /dev/null and b/doc/source/_static/sam_bot_example.png differ
diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt
index 4817b2a5578..68797d4f5e3 100644
--- a/doc/styles/config/vocabularies/ANSYS/accept.txt
+++ b/doc/styles/config/vocabularies/ANSYS/accept.txt
@@ -21,6 +21,7 @@ COM interface
[Cc]omponents
Conda
CPython
+datas
DesignXploration
docstring
[Dd]ocstrings
diff --git a/pyproject.toml b/pyproject.toml
index ff639ce718c..ceb3f26cadf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -130,6 +130,10 @@ examples = [
"plotly>=6.0,<6.1",
"scikit-rf>=0.30.0,<1.8",
]
+freeze = [
+ "pyaedt[all]",
+ "pyinstaller"
+]
[tool.setuptools.dynamic]
version = {attr = "ansys.aedt.core.__version__"}
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.ico b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.ico
new file mode 100644
index 00000000000..a3db9f2f06a
Binary files /dev/null and b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.ico differ
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.png b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.png
new file mode 100644
index 00000000000..62815904b90
Binary files /dev/null and b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/bot.png differ
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/assets/config.toml b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/config.toml
new file mode 100644
index 00000000000..b4e41fa1c38
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/assets/config.toml
@@ -0,0 +1,46 @@
+interpreter = ""
+
+[Project]
+extension_1 = { name = "Import Nastran/STL", script = "project/import_nastran.py" }
+extension_2 = { name = "Generate report", script = "circuit/create_report.py" }
+extension_3 = { name = "Configure layout", script = "circuit/configure_edb.py" }
+extension_4 = { name = "Advanced Fields Calculator", script = "circuit/advanced_fields_calculator.py" }
+extension_5 = { name = "Point Cloud Generator", script = "circuit/points_cloud.py" }
+
+[Circuit]
+extension_1 = { name = "Import Schematic", script = "circuit/import_schematic.py" }
+extension_2 = { name = "Circuit Configuration", script = "circuit/circuit_configuration.py" }
+
+[HFSS]
+extension_1 = { name = "Choke Designer", script = "hfss/choke_designer.py" }
+extension_2 = { name = "Shielding Effectiveness", script = "hfss/shielding_effectiveness.py" }
+extension_3 = { name = "Move IT", script = "hfss/move_it.py" }
+extension_4 = { name = "Push from Transient", script = "hfss/push_excitation_from_file.py" }
+
+[HFSS3DLayout]
+extension_1 = { name = "Export to 3D", script = "hfss3dlayout/export_to_3d.py" }
+extension_2 = { name = "Push from Transient", script = "hfss3dlayout/push_excitation_from_file_3dl.py" }
+extension_3 = { name = "Export Layout info", script = "hfss3dlayout/export_layout.py" }
+extension_4 = { name = "Advanced Cutout", script = "hfss3dlayout/export_layout.py" }
+extension_5 = { name = "Parametrize Layout", script = "hfss3dlayout/parametrize_edb.py" }
+extension_6 = { name = "Arbitrary Waveports", script = "hfss3dlayout/generate_arbitrary_wave_ports.py" }
+extension_7 = { name = "Via merging", script = "hfss3dlayout/via_clustering_extension.py" }
+extension_8 = { name = "Post Layout Design", script = "hfss3dlayout/post_layout_design_toolkit.py" }
+
+[Maxwell3D]
+extension_1 = { name = "Fields distribution", script = "maxwell3d/fields_distribution.py" }
+
+[Maxwell2D]
+extension_1 = { name = "Fields distribution", script = "maxwell3d/fields_distribution.py" }
+
+[Icepak]
+extension_1 = { name = "Create Power map", script = "icepak/power_map_from_csv.py" }
+
+[TwinBuilder]
+extension_1 = { name = "Export to Circuit", script = "twinbuilder/convert_to_circuit.py" }
+
+[EMIT]
+
+[Q3D]
+
+[Q2D]
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/extract_version.py b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/extract_version.py
new file mode 100644
index 00000000000..b58c2e27a0d
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/extract_version.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
+# SPDX-License-Identifier: MIT
+#
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+import re
+
+try:
+ THIS_PATH = os.path.dirname(__file__)
+except NameError:
+ THIS_PATH = os.getcwd()
+
+with open("src/ansys/aedt/core/__init__.py", "r") as f:
+ content = f.read()
+
+match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
+if match:
+ with open(os.path.join(THIS_PATH, "VERSION"), "w") as v:
+ v.write(match.group(1))
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/frozen.spec b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/frozen.spec
new file mode 100644
index 00000000000..6c87f31bfc3
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/frozen.spec
@@ -0,0 +1,79 @@
+import glob
+import os
+import sys
+from PyInstaller.utils.hooks import collect_all, collect_submodules, copy_metadata
+from PyInstaller import __main__
+
+block_cipher = None
+
+# Path where this script is located
+try:
+ THIS_PATH = os.path.dirname(__file__)
+except NameError:
+ THIS_PATH = os.getcwd()
+
+OUT_PATH = 'pyaedt_bot'
+APP_NAME = 'SAM Bot'
+
+# Update paths based on the folder structure
+CODE_PATH = os.path.join(THIS_PATH, "src", "ansys", "aedt", "core", "extensions", "pyaedt_bot")
+INSTALLER_PATH = os.path.join(CODE_PATH, 'installer')
+ASSETS_PATH = os.path.join(CODE_PATH, 'assets')
+ICON_FILE = os.path.join(ASSETS_PATH, 'bot.ico')
+HOOKS_DIR = os.path.join(INSTALLER_PATH, 'hooks')
+
+# Path to the main entry file (pyaedt_bot.py)
+main_py = os.path.join(CODE_PATH, 'pyaedt_bot.py')
+
+if not os.path.isfile(main_py):
+ raise FileNotFoundError(f'Unable to locate main entrypoint at {main_py}')
+
+# Additional data files to include
+added_files = [
+ (os.path.join(ASSETS_PATH, 'bot.png'), 'assets'),
+ (os.path.join(ASSETS_PATH, 'bot.ico'), 'assets'),
+ (os.path.join(ASSETS_PATH, 'config.toml'), 'assets'),
+ (os.path.join(INSTALLER_PATH, 'VERSION'), '.'),
+]
+
+# Copy required package metadata
+added_files += copy_metadata('ansys-tools-visualization_interface')
+
+# PyInstaller analysis
+a = Analysis([main_py],
+ pathex=[],
+ binaries=[],
+ datas=added_files,
+ hiddenimports=[],
+ hookspath=[HOOKS_DIR],
+ runtime_hooks=[],
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False)
+
+# Create the Python archive
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+# Create the executable
+exe = EXE(pyz,
+ a.scripts,
+ [],
+ exclude_binaries=True,
+ name=APP_NAME,
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ console=False,
+ icon=ICON_FILE)
+
+# Collect all necessary files into the final output folder
+coll = COLLECT(exe,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ strip=False,
+ upx=True,
+ name=OUT_PATH)
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.aedt.core.py b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.aedt.core.py
new file mode 100644
index 00000000000..c5e59b983f1
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.aedt.core.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from PyInstaller.utils.hooks import collect_data_files
+
+datas = collect_data_files("ansys.aedt.core")
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.tools.visualization_interface.py b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.tools.visualization_interface.py
new file mode 100644
index 00000000000..93df05cbe6b
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/hooks/hook-ansys.tools.visualization_interface.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from PyInstaller.utils.hooks import collect_all
+
+datas, binaries, hiddenimports = collect_all("ansys.tools.visualization_interface")
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/setup.nsi b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/setup.nsi
new file mode 100644
index 00000000000..639fba7285d
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/setup.nsi
@@ -0,0 +1,87 @@
+; NSIS script for SAM Bot installer
+
+!define PRODUCT_NAME "SAM Bot"
+!define OUTFILE_NAME "SAM-Bot-Installer-windows.exe"
+
+; Define relative root paths from this script's location
+!define ROOT_DIR "..\..\..\..\..\..\.."
+!define DIST_DIR "${ROOT_DIR}\dist\pyaedt_bot"
+!define ICON_FILE "${DIST_DIR}\_internal\assets\bot.ico"
+!define LICENSE_FILE "${ROOT_DIR}\LICENSE"
+!define VERSION_FILE "VERSION"
+!define /file PRODUCT_VERSION "${VERSION_FILE}"
+
+Name "${PRODUCT_NAME}"
+OutFile "${DIST_DIR}\${OUTFILE_NAME}"
+VIProductVersion "${PRODUCT_VERSION}"
+
+!define MULTIUSER_EXECUTIONLEVEL Highest
+!define MULTIUSER_MUI
+!define MULTIUSER_INSTALLMODE_COMMANDLINE
+!include MultiUser.nsh
+!include MUI2.nsh
+!include InstallOptions.nsh
+
+!define MUI_PAGE_CUSTOMFUNCTION_PRE oneclickpre
+!insertmacro MULTIUSER_PAGE_INSTALLMODE
+!insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}"
+!insertmacro MUI_PAGE_INSTFILES
+!include "uninstall.nsi"
+
+Function CreateDesktopShortCut
+ CreateShortCut "$desktop\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
+FunctionEnd
+
+!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
+!define MUI_FINISHPAGE_SHOWREADME
+!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut"
+!define MUI_FINISHPAGE_SHOWREADME_FUNCTION "CreateDesktopShortCut"
+!insertmacro MUI_PAGE_FINISH
+
+Function .onInit
+ !insertmacro MULTIUSER_INIT
+FunctionEnd
+
+Function un.onInit
+ !insertmacro MULTIUSER_UNINIT
+FunctionEnd
+
+Section "${PRODUCT_NAME}" SEC01
+ SetOutPath "$PROGRAMFILES64\ANSYS Inc\${PRODUCT_NAME}"
+
+ File /r "${DIST_DIR}\*"
+
+ CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
+ CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
+
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME}"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayIcon" "$\"$INSTDIR\${PRODUCT_NAME}.exe$\""
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "ANSYS Inc"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Version" "${PRODUCT_VERSION}"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${PRODUCT_VERSION}"
+
+ WriteUninstaller "$INSTDIR\uninstall.exe"
+SectionEnd
+
+Section "Uninstall" SEC02
+ Delete "$PROGRAMFILES64\${PRODUCT_NAME}\*.*"
+ RMDir "$PROGRAMFILES64\${PRODUCT_NAME}"
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
+ Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
+ RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
+ Delete "$desktop\${PRODUCT_NAME}.lnk"
+SectionEnd
+
+Icon "${ICON_FILE}"
+InstallDir "$PROGRAMFILES64\ANSYS Inc\${PRODUCT_NAME}"
+
+InstProgressFlags smooth
+Function oneclickpre
+ !insertmacro MUI_HEADER_TEXT "Installing ${PRODUCT_NAME}" "Please wait while the installation completes."
+ HideWindow
+FunctionEnd
+
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_LANGUAGE English
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/installer/uninstall.nsi b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/uninstall.nsi
new file mode 100644
index 00000000000..180d4895567
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/installer/uninstall.nsi
@@ -0,0 +1,42 @@
+Var DeleteUserData
+Var DeleteLogs
+
+Section "Uninstall"
+ MessageBox MB_YESNO|MB_ICONQUESTION "Do you want to uninstall ${PRODUCT_NAME} ${PRODUCT_VERSION}?" IDYES checkDeleteUserData
+ Abort
+
+checkDeleteUserData:
+ MessageBox MB_YESNO|MB_ICONQUESTION "Delete application user data?" IDYES deleteUserData
+ StrCpy $DeleteUserData 0
+ Goto checkDeleteLogs
+
+deleteUserData:
+ StrCpy $DeleteUserData 1
+
+checkDeleteLogs:
+ MessageBox MB_YESNO|MB_ICONQUESTION "Delete logs?" IDYES deleteLogs
+ StrCpy $DeleteLogs 0
+ Goto doneAsking
+
+deleteLogs:
+ StrCpy $DeleteLogs 1
+
+doneAsking:
+
+ ${If} $DeleteUserData == 1
+ RMDir /r "$PROFILE\.${PRODUCT_NAME}"
+ ${EndIf}
+
+ ${If} $DeleteLogs == 1
+ RMDir /r "$PROFILE\.${PRODUCT_NAME}_logs"
+ ${EndIf}
+
+ Delete "$INSTDIR\*.*"
+ RMDir /r "$INSTDIR"
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
+ Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
+ RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
+ Delete "$desktop\${PRODUCT_NAME}.lnk"
+
+ MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} has been uninstalled."
+SectionEnd
\ No newline at end of file
diff --git a/src/ansys/aedt/core/extensions/pyaedt_bot/pyaedt_bot.py b/src/ansys/aedt/core/extensions/pyaedt_bot/pyaedt_bot.py
new file mode 100644
index 00000000000..285eb26ef88
--- /dev/null
+++ b/src/ansys/aedt/core/extensions/pyaedt_bot/pyaedt_bot.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
+# SPDX-License-Identifier: MIT
+#
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tkinter as tk
+from tkinter import Menu
+
+from ansys.aedt.core.aedt_logger import pyaedt_logger as logger
+from ansys.aedt.core.generic.file_utils import read_toml
+
+
+def get_base_dir():
+ """Determine the base directory for resources depending on whether running as PyInstaller executable or as a
+ script."""
+ if getattr(sys, "frozen", False):
+ # Running from PyInstaller bundle
+ return Path(sys._MEIPASS)
+ else:
+ # Running from source
+ return Path(__file__).resolve().parent
+
+
+VENV_DIR_PREFIX = ".pyaedt_env"
+
+
+class PyAEDTBot(tk.Tk):
+ def __init__(self):
+ super().__init__()
+
+ self.base_dir = get_base_dir()
+
+ # Load icon for the bot
+ icon_path = self.base_dir / "assets" / "bot.png"
+ self.icon_image = tk.PhotoImage(file=icon_path)
+
+ # Load config file from environment or default
+ self.config_path = os.environ.get("PyAEDT_BOT_CONFIG")
+ if self.config_path is None or not Path(self.config_path).is_file():
+ self.config_path = self.base_dir / "assets" / "config.toml"
+ else:
+ self.config_path = Path(self.config_path)
+
+ if self.config_path.is_file():
+ self.config = read_toml(self.config_path)
+ else:
+ raise FileNotFoundError(f"{self.config_path} not found.")
+
+ default_interpreter = Path(os.environ["APPDATA"]) / VENV_DIR_PREFIX / "3_10" / "Scripts" / "python.exe"
+
+ self.python_interpreter = Path(sys.executable)
+
+ if "python.exe" not in str(self.python_interpreter):
+ self.python_interpreter = default_interpreter
+
+ local_interpreter = self.config.get("interpreter", None)
+ if local_interpreter:
+ self.python_interpreter = Path(local_interpreter)
+
+ self.extensions_dir = (
+ self.python_interpreter.parent.parent / "Lib" / "site-packages" / "ansys" / "aedt" / "core" / "extensions"
+ )
+
+ # Window setup
+ self.overrideredirect(True)
+ self.geometry("+100+100")
+ self.configure(bg="white")
+ self.attributes("-topmost", True)
+ self.wm_attributes("-transparentcolor", "white")
+
+ self._offset_x = 0
+ self._offset_y = 0
+
+ # Display icon as draggable bot
+ self.icon_label = tk.Label(self, image=self.icon_image, bg="white", bd=0)
+ self.icon_label.pack()
+ self.icon_label.bind("", self.start_move)
+ self.icon_label.bind("", self.move_icon)
+
+ # Right-click context menu
+ self.menu = Menu(self, tearoff=0)
+ self.load_menu()
+ self.icon_label.bind("", self.show_menu)
+
+ def start_move(self, event):
+ """Start moving the window."""
+ self._offset_x = event.x
+ self._offset_y = event.y
+
+ def move_icon(self, event):
+ """Move the icon according to mouse motion."""
+ x = self.winfo_pointerx() - self._offset_x
+ y = self.winfo_pointery() - self._offset_y
+ self.geometry(f"+{x}+{y}")
+
+ def show_menu(self, event):
+ """Display the context menu on right click."""
+ self.menu.tk_popup(event.x_root, event.y_root)
+
+ def load_menu(self):
+ """Load the context menu from the TOML configuration."""
+
+ for extension_type, extensions in self.config.items():
+ if isinstance(extensions, dict):
+ submenu = Menu(self.menu, tearoff=0)
+ for key, extension_data in extensions.items():
+ name = extension_data.get("name", key)
+ script = extension_data.get("script")
+ if not script:
+ continue
+
+ script_path = Path(script)
+ if not script_path.is_file():
+ script_path = self.extensions_dir / script_path
+
+ submenu.add_command(label=name, command=lambda s=script_path: self.launch_extension(s))
+ self.menu.add_cascade(label=extension_type, menu=submenu)
+
+ self.menu.add_separator()
+ self.menu.add_command(label="Help", command=self.show_help)
+ self.menu.add_command(label="Quit", command=self.destroy)
+
+ def show_help(self):
+ """Display a help message describing the bot."""
+ help_window = tk.Toplevel(self)
+ help_window.title("About SAM Bot")
+ help_window.configure(bg="white")
+ help_window.resizable(False, False)
+
+ # Set the custom icon (top-left corner)
+ help_window.iconphoto(False, self.icon_image)
+
+ message = (
+ "SAM Bot - Smart AEDT Manager\n\n"
+ "SAM Bot provides a floating menu for launching PyAEDT automation scripts with ease.\n\n"
+ "Features:\n"
+ "- Right-click the bot icon to access configured extensions.\n"
+ "- Drag the bot by clicking and holding the icon.\n"
+ "- Customize panels using the 'PYAEDT_BOT_CONFIG' environment variable.\n"
+ "- Edit the TOML configuration file to define your own extensions.\n"
+ "- You can also use a custom Python virtual environment (Python 3.10 required).\n\n"
+ f"You can use this configuration file as a template: {self.config_path}."
+ )
+
+ # Text label
+ label = tk.Label(help_window, text=message, justify="left", bg="white", font=("Segoe UI", 10), padx=10, pady=10)
+ label.pack()
+
+ # Close button
+ close_button = tk.Button(help_window, text="Close", command=help_window.destroy)
+ close_button.pack(pady=(0, 10))
+
+ def launch_extension(self, script_path):
+ """Launch an extension script using the Python interpreter."""
+ logger.info(f"Launching {script_path}")
+ logger.info(f"Using interpreter: {self.python_interpreter}")
+ if not script_path.is_file():
+ logger.error(f"{script_path} not found.")
+ raise FileNotFoundError(f"{script_path} not found.")
+ subprocess.Popen([self.python_interpreter, str(script_path)], shell=True)
+ logger.info(f"Finished launching {script_path}.")
+
+
+if __name__ == "__main__":
+ app = PyAEDTBot()
+ app.mainloop()
diff --git a/src/ansys/aedt/core/extensions/templates/template_get_started.py b/src/ansys/aedt/core/extensions/templates/template_get_started.py
index 60c6a8e9c41..6a5e973e525 100644
--- a/src/ansys/aedt/core/extensions/templates/template_get_started.py
+++ b/src/ansys/aedt/core/extensions/templates/template_get_started.py
@@ -55,7 +55,7 @@ def frontend():
app = ansys.aedt.core.Desktop(
new_desktop=False,
- specified_version=version,
+ version=version,
port=port,
aedt_process_id=aedt_process_id,
student_version=is_student,