Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2603962
Student version
Samuelopez-ansys Jul 15, 2026
8c352ca
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Jul 15, 2026
7c24b83
chore: adding changelog file 7918.fixed.md [dependabot-skip]
pyansys-ci-bot Jul 15, 2026
3a848b6
Student version
Samuelopez-ansys Jul 15, 2026
2a85972
Active sessions
Samuelopez-ansys Jul 15, 2026
dcb2185
Fixed port assignment
maxcapodi78 Jul 15, 2026
1f7b6cd
Assign port
Samuelopez-ansys Jul 15, 2026
68f7182
Fixed port assignment
maxcapodi78 Jul 15, 2026
394d8be
Assign port
Samuelopez-ansys Jul 15, 2026
3c63d24
Fixed port assignment
maxcapodi78 Jul 15, 2026
82be8b1
Fixed port assignment
maxcapodi78 Jul 15, 2026
0ee530f
Fixed port assignment
maxcapodi78 Jul 15, 2026
448dafe
Add unit test
Samuelopez-ansys Jul 16, 2026
e53bce6
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Jul 16, 2026
feeeb7b
Fix port None
Samuelopez-ansys Jul 16, 2026
41658c8
Fix issue with version
Samuelopez-ansys Jul 17, 2026
125d13a
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Jul 17, 2026
ed69209
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 10, 2026
8267faa
Apply suggestions from code review
Samuelopez-ansys Aug 10, 2026
ac65acb
Merge remote-tracking branch 'origin/fix/issue_7891' into fix/issue_7891
Samuelopez-ansys Aug 10, 2026
87b2ae8
Update docstring
Samuelopez-ansys Aug 10, 2026
6588e9d
Port set internally directly with __port
Samuelopez-ansys Aug 10, 2026
ca2b564
Split tests
Samuelopez-ansys Aug 10, 2026
b256d56
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 11, 2026
9496017
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 12, 2026
50b8deb
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 13, 2026
18b8e5b
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 21, 2026
0e78f9d
Skip EMIT tests
Samuelopez-ansys Aug 27, 2026
b43998d
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 27, 2026
fbec6df
Skip EMIT tests
Samuelopez-ansys Aug 27, 2026
910abe0
Skip EMIT tests
Samuelopez-ansys Aug 27, 2026
340ee8e
Apply suggestions from code review
Samuelopez-ansys Aug 27, 2026
005acd2
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Aug 27, 2026
0511768
Fix suggestions
Samuelopez-ansys Aug 27, 2026
dfafbd8
Fix issue
Samuelopez-ansys Aug 27, 2026
9e00d02
Fix patch
Samuelopez-ansys Aug 27, 2026
687904f
Merge branch 'main' into fix/issue_7891
Samuelopez-ansys Aug 27, 2026
39a7361
Fix patch
Samuelopez-ansys Aug 27, 2026
e05ac11
Fix patch
Samuelopez-ansys Aug 27, 2026
7df4ac9
Fix patch
Samuelopez-ansys Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/7918.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Connect to existing student version
34 changes: 33 additions & 1 deletion doc/source/User_guide/desktop_sessions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ If needed, you can still override the default behavior explicitly:
# The AEDT session remains open here.

Use ``Desktop`` class directly
--------------------
------------------------------

When ``Desktop`` is used directly, the default behavior depends on whether PyAEDT starts
or attaches to AEDT. You can also release the desktop explicitly for finer control:
Expand All @@ -108,3 +108,35 @@ Recommendations
- Use ``with Desktop(...)`` when you want predictable cleanup.
- Use direct ``Desktop(...)`` construction when you need more manual control over the AEDT session lifecycle.
- When attaching to an existing AEDT session, consider leaving ``close_on_exit`` unset or setting it explicitly to ``True`` if you do want PyAEDT to close that session.


Session selection precedence
----------------------------

When ``Desktop`` decides whether to connect to an existing AEDT session or to start a new one,
PyAEDT evaluates several inputs in a specific order. The following list describes the exact
checks performed by the library (this order matches the implementation in
``ansys.aedt.core.desktop._validate_port`` and related initialization logic):

- If ``port`` is ``0``: a concrete port is assigned (``_assign_port``) and used.
- If ``new_desktop`` is ``True``: PyAEDT prefers to start a new AEDT instance. If the
requested port is already in use by any active session, PyAEDT chooses a different
free port (``_find_free_port``) so the new session can be started.
- If a remote RPyC RPC connection is configured (``settings.remote_rpc_session``): PyAEDT
uses the remote session and short-circuits further local port checks (``new_desktop`` is
set to ``False`` and the requested port is used).
- If there is an active session for the same AEDT version and the requested port is used by
that session, PyAEDT connects to it (reuse).
- If there is an active session for the same AEDT version but the opposite display mode
(graphical vs non-graphical) using the requested port, PyAEDT flips the ``non_graphical``
flag and connect to that session.
Comment thread
Samuelopez-ansys marked this conversation as resolved.
Outdated
- If the requested port is in use by a different AEDT version, PyAEDT treats this as a
conflict and (to avoid attaching to the wrong version) select a new free port and start
a new session (``new_desktop`` becomes ``True``).
- If none of the above conditions apply, PyAEDT uses the requested port and start a
new AEDT session.

This precedence ensures predictable behavior: version and the desire to force a new
session (``new_desktop``) govern whether PyAEDT attaches or starts, while port and display
mode determine whether an existing session can be reused or whether a new one must be
created.
89 changes: 56 additions & 33 deletions src/ansys/aedt/core/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from ansys.aedt.core.generic.general_methods import _is_version_format_valid
from ansys.aedt.core.generic.general_methods import _normalize_version_to_string
from ansys.aedt.core.generic.general_methods import active_sessions
from ansys.aedt.core.generic.general_methods import all_active_sessions
from ansys.aedt.core.generic.general_methods import com_active_sessions
from ansys.aedt.core.generic.general_methods import grpc_active_sessions
from ansys.aedt.core.generic.general_methods import inside_desktop_ironpython_console
Expand Down Expand Up @@ -439,10 +440,7 @@ def launch_aedt(
timeout = settings.desktop_launch_timeout
start = time.time()
while timeout > 0:
if is_grpc_session_active(
port,
host,
):
if is_grpc_session_active(port, host, student_version):
break
timeout -= 1
time.sleep(1)
Expand Down Expand Up @@ -801,7 +799,7 @@ def __init__(
)
self.__close_on_exit_arg = close_on_exit
self.__machine = machine if machine else None
self.__port = port
self.__port = port if port is not None else 0
Comment thread
Samuelopez-ansys marked this conversation as resolved.
self.__is_grpc_api = True
self.__student_version = False
self.__aedt_version_string = ""
Expand Down Expand Up @@ -1032,8 +1030,6 @@ def port(self) -> int:
>>> d.port

"""
if not self.__port:
self._assign_port()
return self.__port

@port.setter
Expand Down Expand Up @@ -1139,7 +1135,7 @@ def check_starting_mode(self) -> None:
self.logger.debug(f"Available sessions: {sessions}")
if self.aedt_process_id in sessions:
if sessions[self.aedt_process_id] != -1:
self.port = sessions[self.aedt_process_id]
self.__port = sessions[self.aedt_process_id]
self.__starting_mode = "grpc"
else:
self.__starting_mode = "com"
Expand Down Expand Up @@ -3072,7 +3068,7 @@ def __initialize(self, new_desktop_required=None):
oapp = self.grpc_plugin.CreateAedtApplication(
server_args.client_machine, self.port, self.non_graphical, new_desktop_required
)
self.port = self.grpc_plugin.port
self.__port = self.grpc_plugin.port
self.aedt_process_id = self.odesktop.GetProcessID()

return oapp
Expand All @@ -3094,23 +3090,59 @@ def _check_machine(self) -> None:
self.machine = "127.0.0.1"

@pyaedt_function_handler()
def _validate_port(self, port, machine=None):
def _validate_port(
Comment thread
Samuelopez-ansys marked this conversation as resolved.
self,
):
"""Validate the specified gRPC port.

On top of checking the port, this method also determines if a new AEDT session
needs to be launched.
"""
self.logger.debug(f"Validating specified gRPC port: {port}")
if port == 0:
return port
active_ports = is_grpc_session_active(port, machine)
if self.new_desktop and active_ports:
self.logger.warning(f"Port {port} is already in use. Finding a new free port.")
return _find_free_port()
elif not settings.remote_rpc_session and not self.new_desktop and not active_ports:
self.logger.warning(f"No active AEDT gRPC session found on port {port}. Opening a new AEDT session.")
self.logger.debug(f"Validating specified gRPC port: {self.port}")

if self.port == 0: # Checking if available session is there or eventually assign new port
self._assign_port()
return self.port
all_sessions = all_active_sessions()
version = self.aedt_version_id[2:4] + self.aedt_version_id[5]
version += "_nongraphical" if self.non_graphical else "_graphical"
version += "_student" if self.student_version else ""

version_neg = self.aedt_version_id[2:4] + self.aedt_version_id[5]
version_neg += "_nongraphical" if not self.non_graphical else "_graphical"
version_neg += "_student" if self.student_version else ""
Comment thread
Samuelopez-ansys marked this conversation as resolved.
Outdated

if self.new_desktop:
for el in all_sessions.values():
if self.port in el.values():
self.logger.warning(f"Port {self.port} is already in use. Finding a new free port.")
self.__port = _find_free_port()
break
return self.port
elif settings.remote_rpc_session: # remote session -> no port check
self.logger.warning(f"Remote session found on port {self.port}. Using it.")
Comment thread
Samuelopez-ansys marked this conversation as resolved.
Outdated
self.new_desktop = False
return self.port
elif version in all_sessions and self.port in all_sessions[version].values():
self.logger.info(f"Port {self.port} session has been found.")
return self.port
elif version_neg in all_sessions and self.port in all_sessions[version_neg].values():
mode = "graphical" if self.non_graphical else "non_graphical"
Comment thread
Samuelopez-ansys marked this conversation as resolved.
self.logger.warning(f"Port {self.port} is already in use in {mode} mode. Using it.")
self.non_graphical = not self.non_graphical
return self.port
else:
for el in all_sessions.values():
if self.port in el.values():
self.logger.warning(
f"Port {self.port} is already in use by another AEDT version. Finding a new free port."
)
self.new_desktop = True
self.__port = _find_free_port()
return self.port
# No active sessions found, open a new AEDT session
self.new_desktop = True
return port
return self.port

@pyaedt_function_handler()
def _assign_port(self):
Expand All @@ -3121,11 +3153,11 @@ def _assign_port(self):
)
try:
self.__port = settings.remote_rpc_session.port
except Exception:
except Exception: # pragma: no cover
self.logger.debug("Failed to retrieve port from RPyC connection")
raise Exception("Failed to retrieve port from RPyC connection")

if settings.use_multi_desktop or self.new_desktop:
elif settings.use_multi_desktop or self.new_desktop:
self.__port = _find_free_port()
self.logger.info(f"New AEDT session is starting on gRPC port {self.port}.")

Expand Down Expand Up @@ -3294,23 +3326,14 @@ def __init_grpc(self):
lock_file = self._on_ci_generate_lock_file()

# Validate port availability/compatibility
try:
self.__port = self._validate_port(self.port)
except Exception:
# NOTE: When we can't validate the port and are not in a
# remote RPC session, we try to launch a new instance by default.
self.logger.warning(f"Could not validate port {self.port}")
if not settings.remote_rpc_session:
self.logger.info("Opening a new AEDT session.")
self.new_desktop = True
self._validate_port()

self.__port = self._validate_port(self.port, self.machine)
is_launched = True
# Launch new AEDT instance if needed
if self.new_desktop:
self.logger.info(f"Starting new AEDT gRPC session on port {self.port}.")
# Spawn AEDT process with gRPC server arguments
is_launched, self.port = launch_aedt(
is_launched, self.__port = launch_aedt(
installer, self.non_graphical, self.port, self.student_version, host=self.machine
)
if not is_launched:
Expand Down
Loading
Loading