From f61655a012462b25700eb4462d06e907c9fb300f Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:02:39 +0530 Subject: [PATCH 01/18] after first patch --- doc/source/getting_started/index.rst | 52 +++++++++ docker/linux/Dockerfile | 4 +- pyproject.toml | 2 +- src/ansys/meshing/prime/core/fileio.py | 2 +- .../meshing/prime/core/mapdlcdbexportutils.py | 32 ++---- src/ansys/meshing/prime/internals/client.py | 79 +++++++++---- src/ansys/meshing/prime/internals/config.py | 5 + .../prime/internals/grpc_communicator.py | 107 +++++++++++------- src/ansys/meshing/prime/internals/launcher.py | 68 +++++++++-- .../meshing/prime/params/primestructs.py | 1 + 10 files changed, 251 insertions(+), 101 deletions(-) diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index ea814d85ae..f098087983 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -65,6 +65,58 @@ To install a basic version of the client, use this command instead: pip install -e . +Connect through gRPC +----------------------- + +PyPrimeMesh uses gRPC to provide secure communications between client and server. +When you run the client and server on the same machine, + +- For Unix OS or Linux OS, PyPrimeMesh uses UDS (Unix Domain Socket) for communication. + +- For Windows OS, server uses interceptor to validate gRPC connections, + ensures the client is running on the same Windows user account as the server and authenticates the client. + +When you launch PyPrimeMesh, gRPC establish connection between the Client and Server +through secure option. Secure is the default option when you use launch_prime(). +You should always prefer secure option to establish secured connection between the client and server. + +When you want to make an insecure connection between the client and server, +you may need to specify the connection type as follows: + +.. code-block:: python + + client = prime.launch_prime(connection_type=prime.internals.config.ConnectionType.GRPC_INSECURE) + +.. note:: + Insecure option is not recommended. + +Connect securely using certificates +-------------------------------------- + +PyPrimeMesh offers secure connection using certificates. For secure connection with mutual TLS (mTLS), you may pass client certificate directory and server certificate directory using client_certs_dir and server_certs_dir respectively to launch_prime(). + +client_certs_dir should contain the following files: + +- client.crt + +- client.key + +- ca.crt + +server_certs_dir should contain the following files: + +- server.crt + +- server.key + +- ca.crt + +.. note:: + - Ensure that ca.crt file is same for client and server. You should not modify the + file names in the client_certs_dir and server_certs_dir respectively. + + - The path of input files must be same for server and client and should be on the shared network. + Dependencies ------------ diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 3e1a6f272f..f3546b0e92 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -6,6 +6,8 @@ FROM rockylinux:8 as builder # Define the working directory WORKDIR /prime +COPY ./certs /prime/certs + COPY ./PyPrimeMeshPackage /prime RUN chmod -R 0755 /prime @@ -37,4 +39,4 @@ ENV AWP_ROOT251="/prime" LABEL org.opencontainers.image.authors="ANSYS Inc." LABEL org.opencontainers.image.vendor="ANSYS Inc." -ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0" ] +ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0" , "--secure", "yes", "--server_cert_dir", "/prime/certs"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 883ff09384..c89d8e385e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "ansys-meshing-prime" -version = "0.8.1" +version = "0.8.2" description = "PyPrimeMesh is a Python client to Ansys Prime Server, which delivers core Ansys meshing technology." readme = "README.md" requires-python = ">=3.10,<4" diff --git a/src/ansys/meshing/prime/core/fileio.py b/src/ansys/meshing/prime/core/fileio.py index f236fe512d..2de8a7bcf3 100644 --- a/src/ansys/meshing/prime/core/fileio.py +++ b/src/ansys/meshing/prime/core/fileio.py @@ -54,9 +54,9 @@ ImportMapdlCdbParams, ImportMapdlCdbResults, ReadSizeFieldParams, - SeparateBlocksFormatType, SizeFieldFileReadResults, WriteSizeFieldParams, + SeparateBlocksFormatType, ) from ansys.meshing.prime.core.model import Model from ansys.meshing.prime.params.primestructs import ErrorCode diff --git a/src/ansys/meshing/prime/core/mapdlcdbexportutils.py b/src/ansys/meshing/prime/core/mapdlcdbexportutils.py index 690900252f..2e45024625 100644 --- a/src/ansys/meshing/prime/core/mapdlcdbexportutils.py +++ b/src/ansys/meshing/prime/core/mapdlcdbexportutils.py @@ -1038,10 +1038,8 @@ def _process_elastic_modulus(self, property_dict, material, mat_id): data = [] if 'Data' in property_dict and property_dict['Data'] is not None: data = property_dict['Data'] - if ( - property_dict["Parameters"]["TYPE"] == "ISOTROPIC" - or property_dict["Parameters"]["TYPE"] == "ISO" - ): + if (property_dict["Parameters"]["TYPE"] == "ISOTROPIC" or + property_dict["Parameters"]["TYPE"] == "ISO"): # self._logger.warning(f"Only isotropic elastic modulus is processed, " # f"Elastic Modulus for the material {material} " # f"is not processed.") @@ -2808,7 +2806,7 @@ def get_static_analysis_data(self, static_data): time_increment = float(data['time_increment']) if 'time_period' in data: time_period = float(data['time_period']) - min_time_increment = time_period * 1e-05 + min_time_increment = time_period*1e-05 if 'min_time_increment' in data: min_time_increment = float(data['min_time_increment']) if 'max_time_increment' in data: @@ -2990,7 +2988,7 @@ def get_dynamic_analysis_data(self, dynamic_data): time_increment = float(data['time_increment']) if 'time_period' in data: time_period = float(data['time_period']) - min_time_increment = time_period * 1e-5 + min_time_increment = time_period*1e-5 if 'min_time_increment' in data: min_time_increment = float(data['min_time_increment']) if 'max_time_increment' in data: @@ -4220,12 +4218,8 @@ class _AxialTempCorrection: ) def __init__( - self, - model: prime.Model, - connector_sections, - connector_behavior, - element_wise_csys=False, - hm_comments=False, + self, model: prime.Model, connector_sections, connector_behavior, + element_wise_csys=False, hm_comments=False ): self._connector_sections = connector_sections self._connector_behavior = connector_behavior @@ -4276,8 +4270,7 @@ def _modify_section_data(self, behavior_data): if "CONNECTOR CONSTITUTIVE REFERENCE" in behavior_data: secdata_string += self._modify_section_type(behavior_data) joint_a_processor = _JointMaterialProcessor( - self._model, self._connector_behavior, self._enable_hm_comments - ) + self._model, self._connector_behavior, self._enable_hm_comments) ref_lens = joint_a_processor._precess_ref_length( behavior_data["CONNECTOR CONSTITUTIVE REFERENCE"] ) @@ -4367,10 +4360,8 @@ def generate_mapdl_commands( return all_mat_cmds, analysis_settings if "Materials" in json_simulation_data and json_simulation_data["Materials"] is not None: mp = _MaterialProcessor( - model, - json_simulation_data["Materials"], - json_simulation_data["Zones"], - params.write_separate_blocks, + model, json_simulation_data["Materials"], + json_simulation_data["Zones"], params.write_separate_blocks ) mat_cmds = mp.get_all_material_commands() all_mat_cmds = mat_cmds @@ -4378,9 +4369,8 @@ def generate_mapdl_commands( "ConnectorBehavior" in json_simulation_data and json_simulation_data["ConnectorBehavior"] is not None ): - jmp = _JointMaterialProcessor( - model, json_simulation_data["ConnectorBehavior"], params.write_separate_blocks - ) + jmp = _JointMaterialProcessor(model, json_simulation_data["ConnectorBehavior"], + params.write_separate_blocks) joint_all_mat_cmds = jmp.get_all_material_commands() all_mat_cmds += joint_all_mat_cmds general_contact_cmds = '' diff --git a/src/ansys/meshing/prime/internals/client.py b/src/ansys/meshing/prime/internals/client.py index 29764709c8..d4267aef39 100644 --- a/src/ansys/meshing/prime/internals/client.py +++ b/src/ansys/meshing/prime/internals/client.py @@ -1,7 +1,6 @@ # Copyright (C) 2024 - 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 @@ -24,6 +23,7 @@ import logging import os +from typing import Optional import ansys.meshing.prime.examples as examples import ansys.meshing.prime.internals.config as config @@ -34,7 +34,6 @@ __all__ = ['Client'] - class Client(object): """Provides the ``Client`` class for PyPrimeMesh. @@ -50,7 +49,8 @@ class Client(object): Maximum time to wait for connection. The default is ``defaults.connection_timeout()``. credentials : Any, optional Credentials to connect to the server. The default is ``None``. - + client_certs_dir : Optional[str] + Directory containing client certificates for mutual TLS. Raises ------ ValueError @@ -65,6 +65,9 @@ def __init__( port: int = defaults.port(), timeout: float = defaults.connection_timeout(), credentials=None, + connection_type: config.ConnectionType = config.ConnectionType.GRPC_SECURE, + uds_file: Optional[str] = None, + client_certs_dir: Optional[str] = None, **kwargs, ): """Initialize the client.""" @@ -72,33 +75,63 @@ def __init__( local = kwargs.get('local', False) if local and server_process is not None: raise ValueError('Local client cannot be instantiated with a server process') + + + if connection_type == config.ConnectionType.GRPC_INSECURE: + print("Warning (Client): Modification of these configurations is not recommended.") + print("Please see the documentation for your installed product for additional information") + self._local = local self._process = server_process self._comm = None if not local: - try: - from ansys.meshing.prime.internals.grpc_communicator import ( - GRPCCommunicator, - ) + if connection_type == config.ConnectionType.GRPC_SECURE or \ + connection_type == config.ConnectionType.GRPC_INSECURE: + try: + from ansys.meshing.prime.internals.grpc_communicator import ( + GRPCCommunicator, + ) - channel = kwargs.get('channel', None) - if channel is not None: - self._comm = GRPCCommunicator(channel=channel, timeout=timeout) - else: - self._comm = GRPCCommunicator( - ip=ip, port=port, timeout=timeout, credentials=credentials + channel = kwargs.get('channel', None) + + if channel is not None: + self._comm = GRPCCommunicator(channel=channel, timeout=timeout) + else: + if os.name == 'nt' or \ + connection_type == config.ConnectionType.GRPC_INSECURE: + if connection_type == config.ConnectionType.GRPC_INSECURE \ + and client_certs_dir is not None: + print("Warning: Ignoring client certificate \ +directory for insecure connections") + client_certs_dir = None + self._comm = GRPCCommunicator( + ip=ip, port=port, timeout=timeout, credentials=credentials, + client_certs_dir=client_certs_dir) + else: + if uds_file is None: + self._comm = GRPCCommunicator( + ip=ip, port=port, + client_certs_dir=client_certs_dir, + timeout=timeout) + else: + self._comm = GRPCCommunicator( + uds_file=uds_file, timeout=timeout, + credentials=credentials) + setattr(self, 'port', port) + except ImportError as err: + logging.getLogger('PyPrimeMesh').error( + f'Failed to load grpc_communicator with message: {err.msg}' ) - setattr(self, 'port', port) - except ImportError as err: - logging.getLogger('PyPrimeMesh').error( - f'Failed to load grpc_communicator with message: {err.msg}' - ) + raise + except ConnectionError: + self.exit() + + logging.getLogger('PyPrimeMesh').error('Failed to connect to PRIME GRPC server') + raise + else: + logging.getLogger('PyPrimeMesh').error(f'Invalid server type: {connection_type}') raise - except ConnectionError: - self.exit() - logging.getLogger('PyPrimeMesh').error('Failed to connect to PRIME GRPC server') - raise else: try: from ansys.meshing.prime.internals.prime_communicator import ( @@ -161,14 +194,12 @@ def exit(self): assert self._local == False terminate_process(self._process) self._process = None - if config.using_container(): container_name = getattr(self, 'container_name') utils.stop_prime_github_container(container_name) elif config.has_pim(): self.remote_instance.delete() self.pim_client.close() - clear_examples = bool(int(os.environ.get('PYPRIMEMESH_CLEAR_EXAMPLES', '1'))) if clear_examples: download_manager = examples.DownloadManager() diff --git a/src/ansys/meshing/prime/internals/config.py b/src/ansys/meshing/prime/internals/config.py index c0a26c1e48..f23490b187 100644 --- a/src/ansys/meshing/prime/internals/config.py +++ b/src/ansys/meshing/prime/internals/config.py @@ -23,6 +23,7 @@ """Configuration utility for PyPrimeMesh.""" from contextlib import contextmanager +from enum import Enum __all__ = [ 'enable_optimizing_numpy_arrays', @@ -44,6 +45,10 @@ from ansys.meshing.prime.internals.logger import PrimeLogger +class ConnectionType(Enum): + GRPC_SECURE = 1, + GRPC_INSECURE = 2, + def _optimize_vectors(): """Get the value of the flag for optimizing vectors.""" diff --git a/src/ansys/meshing/prime/internals/grpc_communicator.py b/src/ansys/meshing/prime/internals/grpc_communicator.py index ffc9110742..17d974275d 100644 --- a/src/ansys/meshing/prime/internals/grpc_communicator.py +++ b/src/ansys/meshing/prime/internals/grpc_communicator.py @@ -1,7 +1,6 @@ # Copyright (C) 2024 - 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 @@ -23,6 +22,7 @@ """Module for communications with the gRPC server.""" __all__ = ['GRPCCommunicator'] from typing import Optional +import os import grpc from ansys.api.meshing.prime.v1 import prime_pb2, prime_pb2_grpc @@ -42,6 +42,49 @@ BUFFER_MESSAGE_LENGTH = defaults.max_message_length() - 100 +def get_secure_channel(client_certs_dir: str, server_host: str, server_port: int): + """Create a secure gRPC channel using the provided TLS files. + + Parameters + ---------- + tls_client_files : list + List of paths to the TLS files. The list should contain: + - client certificate file path + - client key file path + - CA certificate file path + Returns + ------- + grpc.Channel + A secure gRPC channel. + """ + target = f"{server_host}:{server_port}" + + if not os.path.exists(client_certs_dir): + raise FileNotFoundError(f"Client certificates directory does not exist: {client_certs_dir}") + + cert_file = f"{client_certs_dir}/client.crt" + key_file = f"{client_certs_dir}/client.key" + ca_file = f"{client_certs_dir}/ca.crt" + + with open(cert_file, 'rb') as f: + certificate_chain = f.read() + with open(key_file, 'rb') as f: + private_key = f.read() + with open(ca_file, 'rb') as f: + root_certificates = f.read() + + try: + creds = grpc.ssl_channel_credentials( + root_certificates=root_certificates, + private_key=private_key, + certificate_chain=certificate_chain + ) + except Exception as e: + raise RuntimeError(f"Failed to create SSL channel credentials: {e}") + + channel = grpc.secure_channel(target, creds) + return channel + def make_chunks(data, chunk_size): n = max(1, chunk_size) return (data[i : i + n] for i in range(0, len(data), n)) @@ -80,7 +123,10 @@ class GRPCCommunicator(Communicator): Maximum time to wait for connection. The default is ``10.0``. credentials : Any, optional Credentials for connecting to the server. The default is ``None``. - + uds_file : Optional[str], optional + Path to the Unix Domain Socket (UDS) file. The default is ``None``. + client_certs_dir : Optional[str], optional + Directory containing client certificates for mutual TLS. The default is ``None``. Raises ------ ConnectionError @@ -93,18 +139,32 @@ def __init__( port: Optional[int] = None, timeout: float = 10.0, credentials=None, + uds_file: Optional[str] = None, + client_certs_dir: Optional[str] = None, **kwargs, ): """Initialize the server connection.""" import os self._channel = kwargs.get('channel', None) + if self._channel is None and client_certs_dir is not None: + self._channel = get_secure_channel( + client_certs_dir=client_certs_dir, + server_host=ip, + server_port=port + ) + self._models = [] if self._channel is None: ip_addr = f"{ip}:{port}" channel_options = grpc_utils.get_default_channel_args() if credentials is None: - self._channel = grpc.insecure_channel(ip_addr, options=channel_options) + if uds_file is not None: + options = (('grpc.default_authority', 'localhost'),) + self._channel = grpc.insecure_channel(uds_file, options=options) + else: + options = (('grpc.default_authority', 'localhost'),) + self._channel = grpc.insecure_channel(ip_addr, options=options) else: self._channel = grpc.secure_channel(ip_addr, credentials, options=channel_options) @@ -279,47 +339,6 @@ def run_on_server(self, model: Model, recipe: str) -> dict: else: raise RuntimeError("No connection with server") - def server_command(self, command: str, *args) -> dict: - """Run commands on the server. - - Parameters - ---------- - command : str - Commands to run. - - Returns - ------- - dict - Result from the server side. - - Raises - ------ - RuntimeError - Bad response from server. - RuntimeError - Can not connect to server. - """ - if self._stub is not None: - command = {"Command": command} - if len(args) > 0: - command.update({"Args": args[0]}) - - response = self._stub.ServerCommand( - request_iterator( - 0, - json.dumps(command), - prime_pb2.StringMessage, - prime_pb2.Model, - prime_pb2.StringJsonContent, - prime_pb2.MessageCompletionToken, - ) - ) - message = get_response(response, '') - return message - else: - raise RuntimeError("No connection with server") - return {} - def close(self): """Close opened channels.""" self._stub = None diff --git a/src/ansys/meshing/prime/internals/launcher.py b/src/ansys/meshing/prime/internals/launcher.py index 87317c2882..9c3fce5a4e 100644 --- a/src/ansys/meshing/prime/internals/launcher.py +++ b/src/ansys/meshing/prime/internals/launcher.py @@ -1,7 +1,6 @@ # Copyright (C) 2024 - 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 @@ -26,6 +25,7 @@ import subprocess import sys from typing import Optional +import uuid import ansys.meshing.prime.internals.config as config import ansys.meshing.prime.internals.defaults as defaults @@ -73,6 +73,8 @@ def launch_server_process( ip: str = defaults.ip(), port: int = defaults.port(), n_procs: Optional[int] = None, + connection_type: config.ConnectionType = None, + server_certs_dir: Optional[str] = None, **kw, ) -> subprocess.Popen: """Launch a server process for Ansys Prime Server. @@ -90,6 +92,8 @@ def launch_server_process( processes to spawn. The default is ``None``, in which case the server is launched as the only process (normal mode). The process marked as ``Node 0`` hosts the gRPC server. + server_certs_dir : Optional[str] + Directory containing server certificates for mutual TLS. Returns ------- @@ -113,7 +117,7 @@ def launch_server_process( run_prime_script = f'runPrime.{script_ext}' exec_path = os.path.join(prime_root, run_prime_script) - print('Using Ansys Prime Server from {prime_root}'.format(prime_root=prime_root)) + print(f'Launching Ansys Prime Server from {prime_root}') logging.getLogger('PyPrimeMesh').info('Using server from %s', prime_root) if not os.path.isfile(exec_path): raise FileNotFoundError(f'{run_prime_script} not found in {prime_root}') @@ -124,6 +128,7 @@ def launch_server_process( kw = {} enable_python_server = kw.get('server', 'release') + communicator_type = kw.get('communicator_type', 'grpc') scheduler = kw.get('scheduler', None) if not isinstance(enable_python_server, str): @@ -146,6 +151,7 @@ def launch_server_process( if enable_python_server == 'debug': server_args.append('-debug') + server_args.append(f'--type={communicator_type}') server_args.append(f'--ip={ip}') server_args.append(f'--port={port}') if n_procs is not None and isinstance(n_procs, int): @@ -155,6 +161,9 @@ def launch_server_process( server_args.append(f'--scheduler') server_args.append(f'{scheduler}') + if os.name != 'nt' and connection_type == config.ConnectionType.GRPC_SECURE: + server_args.append(f'--uds={kw.get("uds_file", "")}') + kwargs = { 'stdin': subprocess.DEVNULL, } @@ -164,6 +173,14 @@ def launch_server_process( if sys.platform.startswith('win32'): kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP + if connection_type == config.ConnectionType.GRPC_SECURE: + server_args.append("--secure=yes") + else: + server_args.append("--secure=no") + + if server_certs_dir is not None: + server_args.append(f"--server_cert_dir={server_certs_dir}") + logging.getLogger('PyPrimeMesh').info('Launching Ansys Prime Server') server = subprocess.Popen(server_args, **kwargs) return server @@ -178,7 +195,7 @@ def launch_remote_prime( This method creates a file transfer service that is available on Ansys Lab. """ if version is None: - version = '251-sp2' + version = 'latest' pim = pypim.connect() instance = pim.create_instance(product_name='prime', product_version=version) @@ -207,12 +224,14 @@ def launch_remote_prime( return client - def launch_prime( prime_root: Optional[str] = None, ip: str = defaults.ip(), port: int = defaults.port(), timeout: float = defaults.connection_timeout(), + connection_type: config.ConnectionType = config.ConnectionType.GRPC_SECURE, + client_certs_dir : Optional[str] = None, + server_certs_dir : Optional[str] = None, n_procs: Optional[int] = None, version: Optional[str] = None, **kwargs, @@ -235,6 +254,10 @@ def launch_prime( processes to spawn. The default is ``None``, in which case the server is launched as the only process (normal mode). The process marked as ``Node 0`` hosts the gRPC server. + client_certs_dir : Optional[str] + Directory containing client certificates for mutual TLS. + server_certs_dir : Optional[str] + Directory containing server certificates for mutual TLS. Returns ------- @@ -255,19 +278,46 @@ def launch_prime( if ip == defaults.ip(): port = utils.get_available_local_port(port) + channel = None + if ip not in ["127.0.0.1", "localhost"] and \ + connection_type == config.ConnectionType.GRPC_SECURE: + if client_certs_dir is None or server_certs_dir is None: + raise RuntimeError(f"Please provide certificate directory for remote connections.") + missing = [f for f in [f"{client_certs_dir}/client.crt", + f"{client_certs_dir}/client.key", + f"{client_certs_dir}/ca.crt"] + if not os.path.exists(f)] + if missing: + raise RuntimeError(f"Missing required client TLS file(s) for mutual TLS: {', '.join(missing)}") + launch_container = bool(int(os.environ.get('PYPRIMEMESH_LAUNCH_CONTAINER', '0'))) if launch_container: container_name = utils.make_unique_container_name('ansys-prime-server') utils.launch_prime_github_container(port=port, name=container_name, version=version) config.set_using_container(True) - client = Client(port=port, timeout=timeout) + client = Client(port=port, timeout=timeout, client_certs_dir = client_certs_dir) client.container_name = container_name - print('using server from docker : The container name %s', container_name) - logging.getLogger('PyPrimeMesh').info('uses server from container : %s', container_name) + print('using server from docker : The container name ', container_name) return client + uds_file = None + if os.name != 'nt' and client_certs_dir is None: + uds_file = f'unix:/tmp/pyprimemesh-{uuid.uuid4()}.sock' + server = launch_server_process( - prime_root=prime_root, ip=ip, port=port, n_procs=n_procs, **kwargs + prime_root=prime_root, ip=ip, port=port, n_procs=n_procs, + connection_type=connection_type, uds_file=uds_file, + server_certs_dir=server_certs_dir, + **kwargs ) - return Client(server_process=server, ip=ip, port=port, timeout=timeout) + return Client( + server_process=server, + ip=ip, + port=port, + timeout=timeout, + uds_file=uds_file, + connection_type=connection_type, + client_certs_dir=client_certs_dir, + channel=channel + ) diff --git a/src/ansys/meshing/prime/params/primestructs.py b/src/ansys/meshing/prime/params/primestructs.py index 004a5af739..941b925b61 100644 --- a/src/ansys/meshing/prime/params/primestructs.py +++ b/src/ansys/meshing/prime/params/primestructs.py @@ -1,6 +1,7 @@ # Copyright (C) 2024 - 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 From 739fbd17f40d8b125d5d59d756336cd1cdaa54d0 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:03:00 +0530 Subject: [PATCH 02/18] after second patch --- doc/source/getting_started/index.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index f098087983..6667e52cab 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -65,20 +65,20 @@ To install a basic version of the client, use this command instead: pip install -e . -Connect through gRPC +Connecting through gRPC ----------------------- PyPrimeMesh uses gRPC to provide secure communications between client and server. -When you run the client and server on the same machine, +When you run the client and server on the same machine: -- For Unix OS or Linux OS, PyPrimeMesh uses UDS (Unix Domain Socket) for communication. +- For Linux OS, PyPrimeMesh uses UDS (Unix Domain Socket) for communications. -- For Windows OS, server uses interceptor to validate gRPC connections, +- For Windows OS, PyPrimeMesh uses interceptor to validate gRPC connections, ensures the client is running on the same Windows user account as the server and authenticates the client. -When you launch PyPrimeMesh, gRPC establish connection between the Client and Server -through secure option. Secure is the default option when you use launch_prime(). -You should always prefer secure option to establish secured connection between the client and server. +When you launch PyPrimeMesh, gRPC establishes a connection between the Client and Server +through the secure option. **Secure** is the default option when you use launch_prime(). +You should always use the **Secure** option to establish a secured connection between the client and server. When you want to make an insecure connection between the client and server, you may need to specify the connection type as follows: @@ -93,7 +93,7 @@ you may need to specify the connection type as follows: Connect securely using certificates -------------------------------------- -PyPrimeMesh offers secure connection using certificates. For secure connection with mutual TLS (mTLS), you may pass client certificate directory and server certificate directory using client_certs_dir and server_certs_dir respectively to launch_prime(). +PyPrimeMesh offers secure connection using certificates. For secure connection with mutual TLS (mTLS), you may pass a client certificate directory and server certificate directory using client_certs_dir and server_certs_dir respectively to launch_prime(). client_certs_dir should contain the following files: @@ -112,10 +112,10 @@ server_certs_dir should contain the following files: - ca.crt .. note:: - - Ensure that ca.crt file is same for client and server. You should not modify the + - Ensure that ca.crt file is the same for the client and the server. You should not modify the file names in the client_certs_dir and server_certs_dir respectively. - - The path of input files must be same for server and client and should be on the shared network. + - The path of input the files must be the same for server and client and should be on the shared network. Dependencies From 413bddcad7d8ad71c4b7b6e96f73be52a9b4b3c8 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Fri, 28 Nov 2025 05:40:50 +0000 Subject: [PATCH 03/18] chore: adding changelog file 1188.documentation.md [dependabot-skip] --- doc/changelog.d/1188.documentation.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/1188.documentation.md diff --git a/doc/changelog.d/1188.documentation.md b/doc/changelog.d/1188.documentation.md new file mode 100644 index 0000000000..1b05d09373 --- /dev/null +++ b/doc/changelog.d/1188.documentation.md @@ -0,0 +1 @@ +updates to release/0.8 \ No newline at end of file From 43777c7a88b239b847fe238fb1a78d0b0f79c13f Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:33:29 +0530 Subject: [PATCH 04/18] ci_cd file update --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 303d64e66a..b0f9ef500f 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -15,7 +15,7 @@ on: env: DOCKER_IMAGE_NAME: ghcr.io/ansys/prime - DOCKER_IMAGE_TAG: '25.1.2' + DOCKER_IMAGE_TAG: '25.1.4' MAIN_PYTHON_VERSION: '3.12' PACKAGE_NAME: 'ansys-meshing-prime' PACKAGE_NAMESPACE: 'ansys.meshing.prime' From 7608d4c53ef4804dff6e03db39d5d0b495c034b6 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:45:42 +0530 Subject: [PATCH 05/18] accept.txt --- doc/styles/config/vocabularies/ANSYS/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 2187858b64..70833f0aa4 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -6,6 +6,7 @@ automesh Boolean BRep CAD +client_certs_dir conformally [Dd]efeature defeaturing From 4ce4981971d42da25560fef64614d73f80737925 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:30:56 +0530 Subject: [PATCH 06/18] after pre -commit changes --- doc/source/getting_started/index.rst | 4 +- src/ansys/meshing/prime/core/fileio.py | 2 +- .../meshing/prime/core/mapdlcdbexportutils.py | 32 ++++++++---- src/ansys/meshing/prime/internals/client.py | 50 ++++++++++++------- src/ansys/meshing/prime/internals/config.py | 5 +- .../prime/internals/grpc_communicator.py | 13 +++-- src/ansys/meshing/prime/internals/launcher.py | 46 +++++++++++------ 7 files changed, 97 insertions(+), 55 deletions(-) diff --git a/doc/source/getting_started/index.rst b/doc/source/getting_started/index.rst index 6667e52cab..2f5015be99 100644 --- a/doc/source/getting_started/index.rst +++ b/doc/source/getting_started/index.rst @@ -85,7 +85,9 @@ you may need to specify the connection type as follows: .. code-block:: python - client = prime.launch_prime(connection_type=prime.internals.config.ConnectionType.GRPC_INSECURE) + client = prime.launch_prime( + connection_type=prime.internals.config.ConnectionType.GRPC_INSECURE + ) .. note:: Insecure option is not recommended. diff --git a/src/ansys/meshing/prime/core/fileio.py b/src/ansys/meshing/prime/core/fileio.py index 2de8a7bcf3..f236fe512d 100644 --- a/src/ansys/meshing/prime/core/fileio.py +++ b/src/ansys/meshing/prime/core/fileio.py @@ -54,9 +54,9 @@ ImportMapdlCdbParams, ImportMapdlCdbResults, ReadSizeFieldParams, + SeparateBlocksFormatType, SizeFieldFileReadResults, WriteSizeFieldParams, - SeparateBlocksFormatType, ) from ansys.meshing.prime.core.model import Model from ansys.meshing.prime.params.primestructs import ErrorCode diff --git a/src/ansys/meshing/prime/core/mapdlcdbexportutils.py b/src/ansys/meshing/prime/core/mapdlcdbexportutils.py index 2e45024625..690900252f 100644 --- a/src/ansys/meshing/prime/core/mapdlcdbexportutils.py +++ b/src/ansys/meshing/prime/core/mapdlcdbexportutils.py @@ -1038,8 +1038,10 @@ def _process_elastic_modulus(self, property_dict, material, mat_id): data = [] if 'Data' in property_dict and property_dict['Data'] is not None: data = property_dict['Data'] - if (property_dict["Parameters"]["TYPE"] == "ISOTROPIC" or - property_dict["Parameters"]["TYPE"] == "ISO"): + if ( + property_dict["Parameters"]["TYPE"] == "ISOTROPIC" + or property_dict["Parameters"]["TYPE"] == "ISO" + ): # self._logger.warning(f"Only isotropic elastic modulus is processed, " # f"Elastic Modulus for the material {material} " # f"is not processed.") @@ -2806,7 +2808,7 @@ def get_static_analysis_data(self, static_data): time_increment = float(data['time_increment']) if 'time_period' in data: time_period = float(data['time_period']) - min_time_increment = time_period*1e-05 + min_time_increment = time_period * 1e-05 if 'min_time_increment' in data: min_time_increment = float(data['min_time_increment']) if 'max_time_increment' in data: @@ -2988,7 +2990,7 @@ def get_dynamic_analysis_data(self, dynamic_data): time_increment = float(data['time_increment']) if 'time_period' in data: time_period = float(data['time_period']) - min_time_increment = time_period*1e-5 + min_time_increment = time_period * 1e-5 if 'min_time_increment' in data: min_time_increment = float(data['min_time_increment']) if 'max_time_increment' in data: @@ -4218,8 +4220,12 @@ class _AxialTempCorrection: ) def __init__( - self, model: prime.Model, connector_sections, connector_behavior, - element_wise_csys=False, hm_comments=False + self, + model: prime.Model, + connector_sections, + connector_behavior, + element_wise_csys=False, + hm_comments=False, ): self._connector_sections = connector_sections self._connector_behavior = connector_behavior @@ -4270,7 +4276,8 @@ def _modify_section_data(self, behavior_data): if "CONNECTOR CONSTITUTIVE REFERENCE" in behavior_data: secdata_string += self._modify_section_type(behavior_data) joint_a_processor = _JointMaterialProcessor( - self._model, self._connector_behavior, self._enable_hm_comments) + self._model, self._connector_behavior, self._enable_hm_comments + ) ref_lens = joint_a_processor._precess_ref_length( behavior_data["CONNECTOR CONSTITUTIVE REFERENCE"] ) @@ -4360,8 +4367,10 @@ def generate_mapdl_commands( return all_mat_cmds, analysis_settings if "Materials" in json_simulation_data and json_simulation_data["Materials"] is not None: mp = _MaterialProcessor( - model, json_simulation_data["Materials"], - json_simulation_data["Zones"], params.write_separate_blocks + model, + json_simulation_data["Materials"], + json_simulation_data["Zones"], + params.write_separate_blocks, ) mat_cmds = mp.get_all_material_commands() all_mat_cmds = mat_cmds @@ -4369,8 +4378,9 @@ def generate_mapdl_commands( "ConnectorBehavior" in json_simulation_data and json_simulation_data["ConnectorBehavior"] is not None ): - jmp = _JointMaterialProcessor(model, json_simulation_data["ConnectorBehavior"], - params.write_separate_blocks) + jmp = _JointMaterialProcessor( + model, json_simulation_data["ConnectorBehavior"], params.write_separate_blocks + ) joint_all_mat_cmds = jmp.get_all_material_commands() all_mat_cmds += joint_all_mat_cmds general_contact_cmds = '' diff --git a/src/ansys/meshing/prime/internals/client.py b/src/ansys/meshing/prime/internals/client.py index d4267aef39..de5271286e 100644 --- a/src/ansys/meshing/prime/internals/client.py +++ b/src/ansys/meshing/prime/internals/client.py @@ -34,6 +34,7 @@ __all__ = ['Client'] + class Client(object): """Provides the ``Client`` class for PyPrimeMesh. @@ -75,18 +76,21 @@ def __init__( local = kwargs.get('local', False) if local and server_process is not None: raise ValueError('Local client cannot be instantiated with a server process') - - + if connection_type == config.ConnectionType.GRPC_INSECURE: print("Warning (Client): Modification of these configurations is not recommended.") - print("Please see the documentation for your installed product for additional information") + print( + "Please see the documentation for your installed product for additional information" + ) self._local = local self._process = server_process self._comm = None if not local: - if connection_type == config.ConnectionType.GRPC_SECURE or \ - connection_type == config.ConnectionType.GRPC_INSECURE: + if ( + connection_type == config.ConnectionType.GRPC_SECURE + or connection_type == config.ConnectionType.GRPC_INSECURE + ): try: from ansys.meshing.prime.internals.grpc_communicator import ( GRPCCommunicator, @@ -97,26 +101,38 @@ def __init__( if channel is not None: self._comm = GRPCCommunicator(channel=channel, timeout=timeout) else: - if os.name == 'nt' or \ - connection_type == config.ConnectionType.GRPC_INSECURE: - if connection_type == config.ConnectionType.GRPC_INSECURE \ - and client_certs_dir is not None: - print("Warning: Ignoring client certificate \ -directory for insecure connections") + if ( + os.name == 'nt' + or connection_type == config.ConnectionType.GRPC_INSECURE + ): + if ( + connection_type == config.ConnectionType.GRPC_INSECURE + and client_certs_dir is not None + ): + print( + "Warning: Ignoring client certificate \ +directory for insecure connections" + ) client_certs_dir = None self._comm = GRPCCommunicator( - ip=ip, port=port, timeout=timeout, credentials=credentials, - client_certs_dir=client_certs_dir) + ip=ip, + port=port, + timeout=timeout, + credentials=credentials, + client_certs_dir=client_certs_dir, + ) else: if uds_file is None: self._comm = GRPCCommunicator( - ip=ip, port=port, + ip=ip, + port=port, client_certs_dir=client_certs_dir, - timeout=timeout) + timeout=timeout, + ) else: self._comm = GRPCCommunicator( - uds_file=uds_file, timeout=timeout, - credentials=credentials) + uds_file=uds_file, timeout=timeout, credentials=credentials + ) setattr(self, 'port', port) except ImportError as err: logging.getLogger('PyPrimeMesh').error( diff --git a/src/ansys/meshing/prime/internals/config.py b/src/ansys/meshing/prime/internals/config.py index f23490b187..ecd7a2a994 100644 --- a/src/ansys/meshing/prime/internals/config.py +++ b/src/ansys/meshing/prime/internals/config.py @@ -45,9 +45,10 @@ from ansys.meshing.prime.internals.logger import PrimeLogger + class ConnectionType(Enum): - GRPC_SECURE = 1, - GRPC_INSECURE = 2, + GRPC_SECURE = (1,) + GRPC_INSECURE = (2,) def _optimize_vectors(): diff --git a/src/ansys/meshing/prime/internals/grpc_communicator.py b/src/ansys/meshing/prime/internals/grpc_communicator.py index 17d974275d..d9795a1f02 100644 --- a/src/ansys/meshing/prime/internals/grpc_communicator.py +++ b/src/ansys/meshing/prime/internals/grpc_communicator.py @@ -21,8 +21,8 @@ """Module for communications with the gRPC server.""" __all__ = ['GRPCCommunicator'] -from typing import Optional import os +from typing import Optional import grpc from ansys.api.meshing.prime.v1 import prime_pb2, prime_pb2_grpc @@ -61,7 +61,7 @@ def get_secure_channel(client_certs_dir: str, server_host: str, server_port: int if not os.path.exists(client_certs_dir): raise FileNotFoundError(f"Client certificates directory does not exist: {client_certs_dir}") - + cert_file = f"{client_certs_dir}/client.crt" key_file = f"{client_certs_dir}/client.key" ca_file = f"{client_certs_dir}/ca.crt" @@ -77,14 +77,15 @@ def get_secure_channel(client_certs_dir: str, server_host: str, server_port: int creds = grpc.ssl_channel_credentials( root_certificates=root_certificates, private_key=private_key, - certificate_chain=certificate_chain + certificate_chain=certificate_chain, ) except Exception as e: raise RuntimeError(f"Failed to create SSL channel credentials: {e}") - + channel = grpc.secure_channel(target, creds) return channel + def make_chunks(data, chunk_size): n = max(1, chunk_size) return (data[i : i + n] for i in range(0, len(data), n)) @@ -149,9 +150,7 @@ def __init__( self._channel = kwargs.get('channel', None) if self._channel is None and client_certs_dir is not None: self._channel = get_secure_channel( - client_certs_dir=client_certs_dir, - server_host=ip, - server_port=port + client_certs_dir=client_certs_dir, server_host=ip, server_port=port ) self._models = [] diff --git a/src/ansys/meshing/prime/internals/launcher.py b/src/ansys/meshing/prime/internals/launcher.py index 9c3fce5a4e..4b369ab89c 100644 --- a/src/ansys/meshing/prime/internals/launcher.py +++ b/src/ansys/meshing/prime/internals/launcher.py @@ -24,8 +24,8 @@ import os import subprocess import sys -from typing import Optional import uuid +from typing import Optional import ansys.meshing.prime.internals.config as config import ansys.meshing.prime.internals.defaults as defaults @@ -224,14 +224,15 @@ def launch_remote_prime( return client + def launch_prime( prime_root: Optional[str] = None, ip: str = defaults.ip(), port: int = defaults.port(), timeout: float = defaults.connection_timeout(), connection_type: config.ConnectionType = config.ConnectionType.GRPC_SECURE, - client_certs_dir : Optional[str] = None, - server_certs_dir : Optional[str] = None, + client_certs_dir: Optional[str] = None, + server_certs_dir: Optional[str] = None, n_procs: Optional[int] = None, version: Optional[str] = None, **kwargs, @@ -279,23 +280,32 @@ def launch_prime( port = utils.get_available_local_port(port) channel = None - if ip not in ["127.0.0.1", "localhost"] and \ - connection_type == config.ConnectionType.GRPC_SECURE: + if ( + ip not in ["127.0.0.1", "localhost"] + and connection_type == config.ConnectionType.GRPC_SECURE + ): if client_certs_dir is None or server_certs_dir is None: raise RuntimeError(f"Please provide certificate directory for remote connections.") - missing = [f for f in [f"{client_certs_dir}/client.crt", - f"{client_certs_dir}/client.key", - f"{client_certs_dir}/ca.crt"] - if not os.path.exists(f)] + missing = [ + f + for f in [ + f"{client_certs_dir}/client.crt", + f"{client_certs_dir}/client.key", + f"{client_certs_dir}/ca.crt", + ] + if not os.path.exists(f) + ] if missing: - raise RuntimeError(f"Missing required client TLS file(s) for mutual TLS: {', '.join(missing)}") - + raise RuntimeError( + f"Missing required client TLS file(s) for mutual TLS: {', '.join(missing)}" + ) + launch_container = bool(int(os.environ.get('PYPRIMEMESH_LAUNCH_CONTAINER', '0'))) if launch_container: container_name = utils.make_unique_container_name('ansys-prime-server') utils.launch_prime_github_container(port=port, name=container_name, version=version) config.set_using_container(True) - client = Client(port=port, timeout=timeout, client_certs_dir = client_certs_dir) + client = Client(port=port, timeout=timeout, client_certs_dir=client_certs_dir) client.container_name = container_name print('using server from docker : The container name ', container_name) return client @@ -305,10 +315,14 @@ def launch_prime( uds_file = f'unix:/tmp/pyprimemesh-{uuid.uuid4()}.sock' server = launch_server_process( - prime_root=prime_root, ip=ip, port=port, n_procs=n_procs, - connection_type=connection_type, uds_file=uds_file, + prime_root=prime_root, + ip=ip, + port=port, + n_procs=n_procs, + connection_type=connection_type, + uds_file=uds_file, server_certs_dir=server_certs_dir, - **kwargs + **kwargs, ) return Client( @@ -319,5 +333,5 @@ def launch_prime( uds_file=uds_file, connection_type=connection_type, client_certs_dir=client_certs_dir, - channel=channel + channel=channel, ) From 2a70d852b5c817748fa266d02043110ff0c2cc39 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:37:12 +0530 Subject: [PATCH 07/18] updates to the dockerfiles and build files --- docker/build_docker_linux.py | 37 ++++++++++++++++++++++++++++++++++-- docker/linux/Dockerfile | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docker/build_docker_linux.py b/docker/build_docker_linux.py index 15fa381814..5fe560ecca 100644 --- a/docker/build_docker_linux.py +++ b/docker/build_docker_linux.py @@ -143,11 +143,44 @@ def create_docker_image(dest_package_path): # Build the docker image print(">>> Building docker image. This might take some time...") + # Parse version from AWP_ROOT or command line argument + version = "latest" # default + if len(sys.argv) >= 3: + version = sys.argv[2] + else: + # Try to extract version from AWP_ROOT path (e.g., /ansys_inc/v252 -> 25.2) + version_match = re.search(r'v(\d+)(\d)', AWP_ROOT) + if version_match: + major = version_match.group(1) + minor = version_match.group(2) + version = f"{major}.{minor}.0" + + # Get the script directory for robust file path handling + script_dir = os.path.dirname(os.path.abspath(__file__)) + dockerfile_path = os.path.join(script_dir, "linux", "Dockerfile") + + # Verify Dockerfile exists + if not os.path.exists(dockerfile_path): + print(f"XXXXXXX Dockerfile not found at {dockerfile_path}. Exiting process. XXXXXXX") + exit(1) + + # Build Docker image with version tag + image_tag = f"ghcr.io/ansys/prime:{version}" + print(f">>> Building Docker image with tag: {image_tag}") + out = subprocess.run( - ["docker", "build", "-f", "linux/Dockerfile", "-t", "ghcr.io/ansys/prime:latest", "."], - cwd=os.path.dirname(os.path.abspath(__file__)), + ["docker", "build", "-f", dockerfile_path, "-t", image_tag, script_dir], capture_output=True, ) + + # Check if docker build was successful + if out.returncode != 0: + print("XXXXXXX Docker build failed. XXXXXXX") + print("STDOUT:", out.stdout.decode()) + print("STDERR:", out.stderr.decode()) + exit(1) + else: + print(f">>> Docker image built successfully with tag: {image_tag}") # ------------------------------------------------------------------------------- diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index f3546b0e92..0b91677b6d 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -39,4 +39,4 @@ ENV AWP_ROOT251="/prime" LABEL org.opencontainers.image.authors="ANSYS Inc." LABEL org.opencontainers.image.vendor="ANSYS Inc." -ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0" , "--secure", "yes", "--server_cert_dir", "/prime/certs"] \ No newline at end of file +ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0"] \ No newline at end of file From 6c40a2f8f5a240115c4bed1c93521333635fdc43 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:40:34 +0530 Subject: [PATCH 08/18] code style --- docker/build_docker_linux.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/build_docker_linux.py b/docker/build_docker_linux.py index 5fe560ecca..2c46bcacdb 100644 --- a/docker/build_docker_linux.py +++ b/docker/build_docker_linux.py @@ -154,25 +154,25 @@ def create_docker_image(dest_package_path): major = version_match.group(1) minor = version_match.group(2) version = f"{major}.{minor}.0" - + # Get the script directory for robust file path handling script_dir = os.path.dirname(os.path.abspath(__file__)) dockerfile_path = os.path.join(script_dir, "linux", "Dockerfile") - + # Verify Dockerfile exists if not os.path.exists(dockerfile_path): print(f"XXXXXXX Dockerfile not found at {dockerfile_path}. Exiting process. XXXXXXX") exit(1) - + # Build Docker image with version tag image_tag = f"ghcr.io/ansys/prime:{version}" print(f">>> Building Docker image with tag: {image_tag}") - + out = subprocess.run( ["docker", "build", "-f", dockerfile_path, "-t", image_tag, script_dir], capture_output=True, ) - + # Check if docker build was successful if out.returncode != 0: print("XXXXXXX Docker build failed. XXXXXXX") From bc44c89e173d25ba3fc953c2f2e107ed2c2ba2fc Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:51:30 +0530 Subject: [PATCH 09/18] cosmetic --- docker/linux/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 0b91677b6d..5ea991848f 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -6,8 +6,6 @@ FROM rockylinux:8 as builder # Define the working directory WORKDIR /prime -COPY ./certs /prime/certs - COPY ./PyPrimeMeshPackage /prime RUN chmod -R 0755 /prime @@ -39,4 +37,4 @@ ENV AWP_ROOT251="/prime" LABEL org.opencontainers.image.authors="ANSYS Inc." LABEL org.opencontainers.image.vendor="ANSYS Inc." -ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0"] \ No newline at end of file +ENTRYPOINT [ "/prime/meshing/Prime/runPrime.sh", "server", "--ip", "0.0.0.0" ] \ No newline at end of file From 6e77d3aca78a7bf6572164c5e997c1e05b73363d Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:35:42 +0530 Subject: [PATCH 10/18] updates --- src/ansys/meshing/prime/internals/launcher.py | 4 +++- src/ansys/meshing/prime/internals/utils.py | 21 ++++++++++++++++++- tests/conftest.py | 13 ++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/ansys/meshing/prime/internals/launcher.py b/src/ansys/meshing/prime/internals/launcher.py index 4b369ab89c..11d40ef25a 100644 --- a/src/ansys/meshing/prime/internals/launcher.py +++ b/src/ansys/meshing/prime/internals/launcher.py @@ -303,7 +303,9 @@ def launch_prime( launch_container = bool(int(os.environ.get('PYPRIMEMESH_LAUNCH_CONTAINER', '0'))) if launch_container: container_name = utils.make_unique_container_name('ansys-prime-server') - utils.launch_prime_github_container(port=port, name=container_name, version=version) + utils.launch_prime_github_container( + port=port, name=container_name, version=version, connection_type=connection_type + ) config.set_using_container(True) client = Client(port=port, timeout=timeout, client_certs_dir=client_certs_dir) client.container_name = container_name diff --git a/src/ansys/meshing/prime/internals/utils.py b/src/ansys/meshing/prime/internals/utils.py index 238e11ef19..8a3133ccea 100644 --- a/src/ansys/meshing/prime/internals/utils.py +++ b/src/ansys/meshing/prime/internals/utils.py @@ -213,6 +213,7 @@ def launch_prime_github_container( port: int = defaults.port(), name: str = "ansys-prime-server", version: Optional[str] = None, + connection_type: Optional['config.ConnectionType'] = None, ): """Launch a container. @@ -229,6 +230,9 @@ def launch_prime_github_container( Name of the container. The default is ``"ansys-prime-server"``. version : str, optional Version of the container to retrieve. The default is ``None``. + connection_type : config.ConnectionType, optional + Type of connection to use. The default is ``None``, which defaults to + ``config.ConnectionType.GRPC_SECURE``. Raises ------ @@ -254,11 +258,26 @@ def launch_prime_github_container( f'{mount_host}:{mount_image}', '-e', f'ANSYSLMD_LICENSE_FILE={license_file}', + ] + graphics_port = int(os.environ.get('PRIME_GRAPHICS_PORT', '0')) + if graphics_port > 0: + print(f'PyPrimeMesh: using Prime graphics port {graphics_port}') + docker_command += ['-p', f'{graphics_port}:{graphics_port}'] + prime_arguments = [ f'{image_name}:{version}', '--port', f'{port}', ] - subprocess.run(docker_command, stdout=subprocess.DEVNULL) + + # Set default connection type if not provided + if connection_type is None: + connection_type = config.ConnectionType.GRPC_SECURE + + # Handle connection type + if connection_type == config.ConnectionType.GRPC_INSECURE: + prime_arguments.append('--secure=no') + + subprocess.run(docker_command + prime_arguments, stdout=subprocess.DEVNULL) def stop_prime_github_container(name): diff --git a/tests/conftest.py b/tests/conftest.py index 3353fe4f5d..96e52adafe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,10 +57,19 @@ def start_ansys_prime_server(self, prime_root=None, ip='127.0.0.1', port=50055, by default ``1``. """ if n_procs == 1: - self.client = prime.launch_prime(prime_root=prime_root, ip=ip, port=port) + self.client = prime.launch_prime( + prime_root=prime_root, + ip=ip, + port=port, + connection_type=prime.internals.config.ConnectionType.GRPC_INSECURE, + ) else: self.client = prime.launch_prime( - prime_root=prime_root, ip=ip, port=port, n_procs=n_procs + prime_root=prime_root, + ip=ip, + port=port, + n_procs=n_procs, + connection_type=prime.internals.config.ConnectionType.GRPC_INSECURE, ) def start_remote_client(self): From 36c526684b10a2f14b2d5123195eba9e2556ba84 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 12 Dec 2025 10:01:38 +0530 Subject: [PATCH 11/18] changes to build file --- docker/build_docker_linux.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docker/build_docker_linux.py b/docker/build_docker_linux.py index 2c46bcacdb..074a47fbd4 100644 --- a/docker/build_docker_linux.py +++ b/docker/build_docker_linux.py @@ -147,13 +147,6 @@ def create_docker_image(dest_package_path): version = "latest" # default if len(sys.argv) >= 3: version = sys.argv[2] - else: - # Try to extract version from AWP_ROOT path (e.g., /ansys_inc/v252 -> 25.2) - version_match = re.search(r'v(\d+)(\d)', AWP_ROOT) - if version_match: - major = version_match.group(1) - minor = version_match.group(2) - version = f"{major}.{minor}.0" # Get the script directory for robust file path handling script_dir = os.path.dirname(os.path.abspath(__file__)) From 4a9fd546a0041f7106bcc235975b376c9461f567 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:33:40 +0530 Subject: [PATCH 12/18] update to fix the documentation error --- .github/workflows/ci_cd.yml | 1 + doc/source/conf.py | 3 ++- src/ansys/meshing/prime/internals/utils.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index b0f9ef500f..cad0e2d656 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -114,6 +114,7 @@ jobs: PYPRIMEMESH_SPHINX_BUILD: 1 PYPRIMEMESH_IMAGE_TAG: ${{ env.DOCKER_IMAGE_TAG }} ANSYSLMD_LICENSE_FILE: '1055@${{ secrets.LICENSE_SERVER }}' + PRIME_MODE: "GRPC_INSECURE" testing: name: Run Unit Tests diff --git a/doc/source/conf.py b/doc/source/conf.py index ba5436dae6..64a880411b 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -4,12 +4,13 @@ import ansys.tools.visualization_interface as viz_interface import pyvista -from ansys_sphinx_theme import ansys_favicon, get_version_match, pyansys_logo_black from pyvista.plotting.utilities.sphinx_gallery import DynamicScraper from sphinx_gallery.sorting import FileNameSortKey from ansys.meshing.prime import __version__ +from ansys_sphinx_theme import ansys_favicon, get_version_match, pyansys_logo_black +os.environ["PRIME_MODE"] = "GRPC_INSECURE" viz_interface.DOCUMENTATION_BUILD = True # Project information diff --git a/src/ansys/meshing/prime/internals/utils.py b/src/ansys/meshing/prime/internals/utils.py index 8a3133ccea..ec1a9d7b8a 100644 --- a/src/ansys/meshing/prime/internals/utils.py +++ b/src/ansys/meshing/prime/internals/utils.py @@ -274,7 +274,10 @@ def launch_prime_github_container( connection_type = config.ConnectionType.GRPC_SECURE # Handle connection type - if connection_type == config.ConnectionType.GRPC_INSECURE: + if ( + connection_type == config.ConnectionType.GRPC_INSECURE + or os.environ.get('PRIME_MODE', '').upper() == "GRPC_INSECURE" + ): prime_arguments.append('--secure=no') subprocess.run(docker_command + prime_arguments, stdout=subprocess.DEVNULL) From 3d5cf9a5733c79efbb2154dea7ae4cba990966cc Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:09:04 +0530 Subject: [PATCH 13/18] move up --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 64a880411b..fdd5ab0c15 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -4,11 +4,11 @@ import ansys.tools.visualization_interface as viz_interface import pyvista +from ansys_sphinx_theme import ansys_favicon, get_version_match, pyansys_logo_black from pyvista.plotting.utilities.sphinx_gallery import DynamicScraper from sphinx_gallery.sorting import FileNameSortKey from ansys.meshing.prime import __version__ -from ansys_sphinx_theme import ansys_favicon, get_version_match, pyansys_logo_black os.environ["PRIME_MODE"] = "GRPC_INSECURE" viz_interface.DOCUMENTATION_BUILD = True From 8fb00e5760e0f68ad118fa289521fec571555be6 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:08:10 +0530 Subject: [PATCH 14/18] updates to pyvista version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c89d8e385e..5824d323c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,11 +37,11 @@ tests = [ "pytest-cov==6.1.1", "pytest-pyvista==0.1.9", "pytest-xvfb==3.1.1", - "pyvista[trame]==0.44.2" + "pyvista[trame]<=0.45.3" ] doc = [ "ansys-sphinx-theme[autoapi]==1.4.2", - "ansys-tools-visualization-interface==0.8.3", + "ansys-tools-visualization-interface<=0.11.0", "jupyter-sphinx==0.5.3", "numpydoc==1.8.0", "sphinx==8.2.3", From 558841afc1948b0548830b4c7701bdd9f735691a Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:35:52 +0530 Subject: [PATCH 15/18] Update ci_cd.yml --- .github/workflows/ci_cd.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index cad0e2d656..de4cb1611d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -102,6 +102,9 @@ jobs: - name: Setup headless display uses: pyvista/setup-headless-display-action@v4 + - name: Install compatible pyvista and vtk + run: pip install "pyvista==0.39.*" "vtk<9.3" + - name: "Run Ansys documentation building action" uses: ansys/actions/doc-build@v9 with: @@ -138,6 +141,9 @@ jobs: path: tests/graphics/image_cache key: pyvista-image-cache-${{ runner.os }}-v-${{ env.RESET_IMAGE_CACHE }}-${{ hashFiles('pyproject.toml') }} restore-keys: pyvista-image-cache-${{ runner.os }}-v-${{ env.RESET_IMAGE_CACHE }} + + - name: Install compatible pyvista and vtk + run: pip install "pyvista==0.39.*" "vtk<9.3" - name: "Run pytest" uses: ansys/actions/tests-pytest@v9 From 6bbc2ca61debc99482d091654cd9175ca5306f42 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:43:56 +0530 Subject: [PATCH 16/18] version 0.39.1 & 9.2.6 used --- .github/workflows/ci_cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index de4cb1611d..b132b2a972 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -16,7 +16,7 @@ on: env: DOCKER_IMAGE_NAME: ghcr.io/ansys/prime DOCKER_IMAGE_TAG: '25.1.4' - MAIN_PYTHON_VERSION: '3.12' + MAIN_PYTHON_VERSION: '3.10' PACKAGE_NAME: 'ansys-meshing-prime' PACKAGE_NAMESPACE: 'ansys.meshing.prime' DOCUMENTATION_CNAME: 'prime.docs.pyansys.com' @@ -103,7 +103,7 @@ jobs: uses: pyvista/setup-headless-display-action@v4 - name: Install compatible pyvista and vtk - run: pip install "pyvista==0.39.*" "vtk<9.3" + run: pip install "pyvista==0.39.1" "vtk==9.2.6" - name: "Run Ansys documentation building action" uses: ansys/actions/doc-build@v9 @@ -143,7 +143,7 @@ jobs: restore-keys: pyvista-image-cache-${{ runner.os }}-v-${{ env.RESET_IMAGE_CACHE }} - name: Install compatible pyvista and vtk - run: pip install "pyvista==0.39.*" "vtk<9.3" + run: pip install "pyvista==0.39.1" "vtk==9.2.6" - name: "Run pytest" uses: ansys/actions/tests-pytest@v9 From c40d69456120fa20bdf265a8d98b10a1c7efce8a Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:52:15 +0530 Subject: [PATCH 17/18] take the 0.42 and vtk any --- .github/workflows/ci_cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index b132b2a972..39642f894d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -103,7 +103,7 @@ jobs: uses: pyvista/setup-headless-display-action@v4 - name: Install compatible pyvista and vtk - run: pip install "pyvista==0.39.1" "vtk==9.2.6" + run: pip install "pyvista>=0.42" vtk - name: "Run Ansys documentation building action" uses: ansys/actions/doc-build@v9 @@ -143,7 +143,7 @@ jobs: restore-keys: pyvista-image-cache-${{ runner.os }}-v-${{ env.RESET_IMAGE_CACHE }} - name: Install compatible pyvista and vtk - run: pip install "pyvista==0.39.1" "vtk==9.2.6" + run: pip install "pyvista>=0.42" vtk - name: "Run pytest" uses: ansys/actions/tests-pytest@v9 From c2717b2ea79ca78ab7cfddf7db40d4a3a9f6a4d4 Mon Sep 17 00:00:00 2001 From: Subha Nayak <118820636+subhnayak@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:49:42 +0530 Subject: [PATCH 18/18] sphinx-autodoc-typehints --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7a57d7c64e..57e2f08f58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ doc = [ "sphinx>=8.0.0,<8.2.0", "sphinx_design==0.6.1", "pyvista==0.44.2", - "sphinx-autodoc-typehints==3.1.0", + "sphinx-autodoc-typehints==3.0.1", "sphinx-copybutton==0.5.2", "sphinx-gallery==0.19.0", "sphinx-notfound-page==1.1.0",