diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..43b60d4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a742e8a..2402ac2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: name: "Code style" runs-on: ubuntu-latest steps: - - uses: ansys/actions/code-style@v7 + - uses: ansys/actions/code-style@v8 with: python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -34,7 +34,7 @@ jobs: name: "Doc style" runs-on: ubuntu-latest steps: - - uses: ansys/actions/doc-style@v7 + - uses: ansys/actions/doc-style@v8 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -51,7 +51,7 @@ jobs: - ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }} steps: - name: Build wheelhouse and perform smoke test - uses: ansys/actions/build-wheelhouse@v7 + uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.PACKAGE_NAME }} library-namespace: ${{ env.PACKAGE_NAMESPACE }} @@ -79,7 +79,7 @@ jobs: run: docker pull ${{ env.ENSIGHT_IMAGE }} - name: Run pytest - uses: ansys/actions/tests-pytest@v7 + uses: ansys/actions/tests-pytest@v8 env: ANSYSLMD_LICENSE_FILE: ${{ format('1055@{0}', secrets.LICENSE_SERVER) }} with: @@ -94,7 +94,7 @@ jobs: needs: [tests] runs-on: ubuntu-latest steps: - - uses: ansys/actions/build-library@v7 + - uses: ansys/actions/build-library@v8 with: library-name: ${{ env.PACKAGE_NAME }} python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -105,21 +105,7 @@ jobs: needs: build-library runs-on: ubuntu-latest steps: - # Upload first to the private PyPi, at least up until we release - - name: Release to the private PyPI repository - uses: ansys/actions/release-pypi-private@v7 - with: - library-name: ${{ env.PACKAGE_NAME }} - twine-token: ${{ secrets.PYANSYS_PYPI_PRIVATE_PAT }} - - - name: Release to the public PyPI repository - uses: ansys/actions/release-pypi-public@v7 - with: - library-name: ${{ env.PACKAGE_NAME }} - twine-username: "__token__" - twine-token: ${{ secrets.PYPI_TOKEN }} - - name: Release to GitHub - uses: ansys/actions/release-github@v7 + uses: ansys/actions/release-github@v8 with: library-name: ${{ env.PACKAGE_NAME }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 127dbd1..38630f8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -26,7 +26,7 @@ jobs: run: docker pull ${{ env.ENSIGHT_IMAGE }} - name: Run pytest - uses: ansys/actions/tests-pytest@v7 + uses: ansys/actions/tests-pytest@v8 env: ANSYSLMD_LICENSE_FILE: ${{ format('1055@{0}', secrets.LICENSE_SERVER) }} with: @@ -42,7 +42,7 @@ jobs: steps: - name: Build library source and wheel artifacts - uses: ansys/actions/build-library@v7 + uses: ansys/actions/build-library@v8 with: library-name: ${{ env.PACKAGE_NAME }} python-version: ${{ env.MAIN_PYTHON_VERSION }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 156c327..c1a63cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,51 +1,57 @@ -repos: - - repo: https://github.com/psf/black - rev: 23.3.0 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! - hooks: - - id: black - - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - - - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 - hooks: - - id: codespell - additional_dependencies: ["tomli"] - - - repo: https://github.com/PyCQA/bandit - rev: 1.7.5 - hooks: - - id: bandit - # args not working with pyproject.toml - args: [ - -lll, - -n, "3", - -r, - -x, "venv/*, tests/*" - ] - -# - repo: https://github.com/pre-commit/mirrors-mypy -# rev: v1.3.0 -# hooks: -# - id: mypy -# # Exclude only works here, not in config files -# exclude: "tests/|doc/|src/ansys/pyensight/core/exts/|exts/" - - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-merge-conflict - - id: debug-statements - - id: check-yaml - - id: trailing-whitespace - - id: check-added-large-files - - id: check-case-conflict +repos: + - repo: https://github.com/psf/black + rev: 24.10.0 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! + hooks: + - id: black + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: ["tomli"] + + - repo: https://github.com/PyCQA/bandit + rev: 1.7.10 + hooks: + - id: bandit + # args not working with pyproject.toml + args: [ + -lll, + -n, "3", + -r, + -x, "venv/*, tests/*" + ] + +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v1.3.0 +# hooks: +# - id: mypy +# # Exclude only works here, not in config files +# exclude: "tests/|doc/|src/ansys/pyensight/core/exts/|exts/" + + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: check-yaml + - id: trailing-whitespace + - id: check-added-large-files + - id: check-case-conflict + + - repo: https://github.com/ansys/pre-commit-hooks + rev: v0.4.3 + hooks: + - id: add-license-headers + files: '(exts|tests)/.*\.(py)' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..613d268 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ANSYS, Inc. and/or its affiliates. + +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. diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index a6cfe0b..26b8ad2 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -16,6 +16,7 @@ autogenerated casemapped circumsphere cmdlang +colorby_rgb compi compj Compute_Per_case @@ -70,6 +71,7 @@ num_points NumPy numpy Oddy +Omniverse Q_criteria partlist peakness @@ -79,6 +81,7 @@ Pitot pitot plist Protobuf +pyensight PyPI Python Radiograph_grid diff --git a/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/__init__.py b/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/__init__.py index 5d37508..92e8600 100644 --- a/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/__init__.py +++ b/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/__init__.py @@ -1 +1,23 @@ +# Copyright (C) 2024 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 .extension import * diff --git a/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/extension.py b/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/extension.py index 2b2f675..98e20ca 100644 --- a/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/extension.py +++ b/exts/ansys.tools.omniverse.core/ansys/tools/omniverse/core/extension.py @@ -1,13 +1,33 @@ +# Copyright (C) 2024 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 json import logging import os import platform -import random -import socket import subprocess import sys import tempfile -from typing import List, Optional +from typing import Optional import uuid import carb.settings @@ -22,7 +42,7 @@ """ -def find_kit_filename() -> Optional[str]: # pragma: no cover +def find_kit_filename() -> Optional[str]: """ Use a combination of the current omniverse application and the information in the local .nvidia-omniverse/config/omniverse.toml file to come up with @@ -68,64 +88,6 @@ def find_kit_filename() -> Optional[str]: # pragma: no cover return None -def find_unused_ports(count: int, avoid: Optional[List[int]] = None) -> Optional[List[int]]: - """Find "count" unused ports on the host system - - A port is considered unused if it does not respond to a "connect" attempt. Walk - the ports from 'start' to 'end' looking for unused ports and avoiding any ports - in the 'avoid' list. Stop once the desired number of ports have been - found. If an insufficient number of ports were found, return None. - - Parameters - ---------- - count: int : - Number of unused ports to find - avoid: Optional[List[int]] : - An optional list of ports not to check - - Returns - ------- - The detected ports or None on failure - - """ - if avoid is None: - avoid = [] - ports = list() - - # pick a starting port number - start = random.randint(1024, 64000) - # We will scan for 65530 ports unless end is specified - port_mod = 65530 - end = start + port_mod - 1 - # walk the "virtual" port range - for base_port in range(start, end + 1): - # Map to physical port range - # There have been some issues with 65534+ so we stop at 65530 - port = base_port % port_mod - # port 0 is special - if port == 0: # pragma: no cover - continue # pragma: no cover - # avoid admin ports - if port < 1024: # pragma: no cover - continue # pragma: no cover - # are we supposed to skip this one? - if port in avoid: # pragma: no cover - continue # pragma: no cover - # is anyone listening? - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = sock.connect_ex(("127.0.0.1", port)) - if result != 0: - ports.append(port) - else: - sock.close() # pragma: no cover - if len(ports) >= count: - return ports - # in case we failed... - if len(ports) < count: # pragma: no cover - return None # pragma: no cover - return ports # pragma: no cover - - class AnsysToolsOmniverseCoreServerExtension(omni.ext.IExt): """ This class is an Omniverse kit. The kit is capable of creating a @@ -157,7 +119,6 @@ def __init__(self, *args, **kwargs) -> None: self._shutdown: bool = False self._server_process = None self._status_filename: str = "" - self._session = None self._interpreter = self._find_ensight_cpython() @property @@ -227,6 +188,15 @@ def time_scale(self) -> float: def time_scale(self, value: float) -> None: self._time_scale = value + @property + def interpreter(self) -> str: + """Fully qualified path to the python.exe or .bat wrapper in which pyensight is installed.""" + return self._interpreter + + @interpreter.setter + def interpreter(self, value: str) -> None: + self._interpreter = value + @classmethod def get_instance(cls) -> Optional["AnsysToolsOmniverseCoreServerExtension"]: return cls._service_instance @@ -294,7 +264,7 @@ def error(self, text: str) -> None: """ self._logger.error(text) - def _find_ensight_cpython(self) -> Optional[str]: # pragma: no cover + def _find_ensight_cpython(self) -> Optional[str]: """ Scan the current system, looking for EnSight installations, specifically, cpython. Check: PYENSIGHT_ANSYS_INSTALLATION, CEI_HOME, AWP_ROOT* in that order @@ -304,6 +274,11 @@ def _find_ensight_cpython(self) -> Optional[str]: # pragma: no cover The first cpython found or None """ + + cpython = "cpython" + if platform.system() == "Windows": + cpython += ".bat" + dirs_to_check = [] if "PYENSIGHT_ANSYS_INSTALLATION" in os.environ: env_inst = os.environ["PYENSIGHT_ANSYS_INSTALLATION"] @@ -313,28 +288,22 @@ def _find_ensight_cpython(self) -> Optional[str]: # pragma: no cover # ways, we'll add that one too, just in case. dirs_to_check.append(os.path.join(env_inst, "CEI")) - if "CEI_HOME" in os.environ: - env_inst = os.environ["CEI_HOME"] - dirs_to_check.append(env_inst) - - # Look for most recent Ansys install + # Look for most recent Ansys install, 25.1 or later awp_roots = [] for env_name in dict(os.environ).keys(): - if env_name.startswith("AWP_ROOT"): + if env_name.startswith("AWP_ROOT") and int(env_name[len("AWP_ROOT") :]) >= 251: awp_roots.append(env_name) awp_roots.sort(reverse=True) for env_name in awp_roots: dirs_to_check.append(os.path.join(os.environ[env_name], "CEI")) # check all the collected locations in order - cpython = "cpython" - if platform.system() == "Windows": - cpython += ".bat" for install_dir in dirs_to_check: launch_file = os.path.join(install_dir, "bin", cpython) if os.path.isfile(launch_file): - return launch_file - return None + if self.validate_interpreter(launch_file): + return launch_file + return "" def on_startup(self, ext_id: str) -> None: """ @@ -357,51 +326,26 @@ def on_shutdown(self) -> None: self.shutdown() AnsysToolsOmniverseCoreServerExtension._service_instance = None - def _docker_command_line_export(self): - ansys_version = self._session._launcher._enshell.ansys_version() - cmd = f"/ansys_inc/v{ansys_version}/CEI/bin/cpython{ansys_version}" - cmd += " -m ansys.pyensight.core.utils.omniverse_cli" - if self.security_token: - cmd += f" --security_token {self.security_token}" - if self.temporal: - cmd += " --temporal true" - if self.vrmode: - cmd += " --include_camera false" - if self.normalize_geometry: - cmd += " --normalize_geometry true" - if self.time_scale != 1.0: - cmd += f" --time_scale {self.time_scale}" - cmd += f" --dsg_uri {self.dsg_uri}" - cmd += " --oneshot true" - cmd += " /home/ensight/dsg_export/" - return cmd - - @property - def session(self): - return self._session - - @session.setter - def session(self, session): - self._session = session - - def _dsg_export_docker(self): - """Utility function to export data from a docker container for EnSight. + def validate_interpreter(self, launch_file: str) -> bool: + if len(launch_file) == 0: + return False + has_ov_module = False + try: + cmd = [launch_file, "-m", "ansys.pyensight.core.utils.omniverse_cli", "-h"] + env_vars = os.environ.copy() + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env_vars) + proc.wait(timeout=60) + has_ov_module = proc.communicate()[0].decode("utf-8").startswith("usage: omniverse_cli") + except Exception as error: + self.warning(f"Exception thrown while testing python {str(launch_file)}: {str(error)}") + has_ov_module = False + return has_ov_module - To be used only in testing environment. Please note, that the way - it is designed, it will wait for the export to finish, - differently from the local install case. + def dsg_export(self) -> None: + """ + Use the oneshot feature of the pyensight omniverse_cli to push the current + EnSight scene to the supplied directory in USD format. """ - result = self._session._launcher._container.exec_run( - self._docker_command_line_export(), - environment={ - "ANSYS_OV_SERVER_STATUS_FILENAME": f"/home/ensight/{self._status_filename}" - }, - ) - if result.exit_code != 0: - output = result.output.decode("utf-8") - self.warning(f"Error during DSG export from docker container {output}") - - def _dsg_export_local(self): # pragma: no cover if self._interpreter is None: self.warning("Unable to determine a kit executable pathname.") return @@ -425,30 +369,14 @@ def _dsg_export_local(self): # pragma: no cover # we are launching the kit from an Omniverse app. In this case, we # inform the kit instance of: # (1) the name of the "server status" file, if any + self._new_status_file() env_vars["ANSYS_OV_SERVER_STATUS_FILENAME"] = self._status_filename try: self.info(f"Running {' '.join(cmd)}") self._server_process = subprocess.Popen(cmd, close_fds=True, env=env_vars) except Exception as error: self.warning(f"Error running translator: {error}") - - def dsg_export(self) -> None: - """ - Use the oneshot feature of the pyensight omniverse_cli to push the current - EnSight scene to the supplied directory in USD format. - """ - self._new_status_file() - if self._session: - self._dsg_export_docker() - else: # pragma: no cover - self._dsg_export_local() self._new_status_file(new=False) - # For the container case, we need to copy the data generated locally to the - # dest folder - if self._session: - dest = os.path.dirname(self.destination) - location = f"file:///{dest}" - self._session.copy_from_session(location, ["/home/ensight/dsg_export/"]) def _new_status_file(self, new=True) -> None: """ @@ -460,23 +388,16 @@ def _new_status_file(self, new=True) -> None: If True, create a new status file. """ if self._status_filename: - if self._session: - self._session._launcher._enshell.start_other(f"rm -rf {self._status_filename}") - else: # pragma: no cover - if os.path.exists(self._status_filename): - try: - os.remove(self._status_filename) - except OSError: - self.warning(f"Unable to delete the status file: {self._status_filename}") + if os.path.exists(self._status_filename): + try: + os.remove(self._status_filename) + except OSError: + self.warning(f"Unable to delete the status file: {self._status_filename}") self._status_filename = "" if new: - if self._session: - # In case of docker, create a status file local to the container - self._status_filename = f"/home/ensight/{uuid.uuid1()}_gs_status.txt" - else: # pragma: no cover - self._status_filename = os.path.join( - tempfile.gettempdir(), str(uuid.uuid1()) + "_gs_status.txt" - ) + self._status_filename = os.path.join( + tempfile.gettempdir(), str(uuid.uuid1()) + "_gs_status.txt" + ) def read_status_file(self) -> dict: """Read the status file and return its contents as a dictionary. @@ -491,18 +412,9 @@ def read_status_file(self) -> dict: """ if not self._status_filename: return {} - if self._session: - result = self._session._launcher._enshell.start_other(f"cat {self._status_filename}") - if result[0] != 0: - self.warning("Couldn't retrieve status file from container") - return {} - try: - return json.loads(result[1]) - except Exception: - return {} - try: # pragma: no cover + try: with open(self._status_filename, "r") as status_file: data = json.load(status_file) - except Exception: # pragma: no cover + except Exception: return {} - return data # pragma: no cover + return data diff --git a/exts/ansys.tools.omniverse.core/config/extension.toml b/exts/ansys.tools.omniverse.core/config/extension.toml index 766b365..bbc8afa 100644 --- a/exts/ansys.tools.omniverse.core/config/extension.toml +++ b/exts/ansys.tools.omniverse.core/config/extension.toml @@ -57,3 +57,5 @@ exts."ansys.tools.omniverse.core".temporal = "0" exts."ansys.tools.omniverse.core".vrmode = "0" exts."ansys.tools.omniverse.core".normalizeGeometry = "0" exts."ansys.tools.omniverse.core".timeScale = "1.0" +exts."ansys.tools.omniverse.core".interpreter = "" + diff --git a/exts/ansys.tools.omniverse.core/docs/CHANGELOG.md b/exts/ansys.tools.omniverse.core/docs/CHANGELOG.md index 8840e7d..58b6769 100644 --- a/exts/ansys.tools.omniverse.core/docs/CHANGELOG.md +++ b/exts/ansys.tools.omniverse.core/docs/CHANGELOG.md @@ -2,6 +2,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.8.12] - 2024-10-17 +- Update to let the path to the python installation be validated and edited + ## [0.8.11] - 2024-09-17 - Update for new pyensight Omniverse interface diff --git a/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/__init__.py b/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/__init__.py index 5d37508..92e8600 100644 --- a/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/__init__.py +++ b/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/__init__.py @@ -1 +1,23 @@ +# Copyright (C) 2024 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 .extension import * diff --git a/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/extension.py b/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/extension.py index 6516329..5799746 100644 --- a/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/extension.py +++ b/exts/ansys.tools.omniverse.dsgui/ansys/tools/omniverse/dsgui/extension.py @@ -1,3 +1,25 @@ +# Copyright (C) 2024 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 logging import threading import time @@ -17,6 +39,7 @@ def __init__(self, *args, **kwargs) -> None: self._grpc = None self._dsg_uri_w = None self._dsg_token_w = None + self._interpreter_w = None self._destination_w = None self._temporal_w = None self._vrmode_w = None @@ -25,6 +48,7 @@ def __init__(self, *args, **kwargs) -> None: self._connect_w = None self._update_w = None self._connected = False + self._error_msg = "" @property def service(self) -> Optional["AnsysToolsOmniverseDSGUIExtension"]: @@ -44,6 +68,7 @@ def start_server(self) -> None: return self.service.dsg_uri = self._dsg_uri_w.model.as_string self.service.security_token = self._dsg_token_w.model.as_string + self.service.interpreter = self._interpreter_w.model.as_string self.service.destination = self._destination_w.model.as_string self.service.temporal = self._temporal_w.model.as_bool self.service.vrmode = self._vrmode_w.model.as_bool @@ -68,7 +93,12 @@ def connect_cb(self) -> None: if self._connected: self.stop_server() else: - self.start_server() + pypath = self._interpreter_w.model.as_string + if not self.service.validate_interpreter(pypath): + self._error_msg = ". Invalid Python path." + else: + self._error_msg = "" + self.start_server() self.update_ui() def update_cb(self) -> None: @@ -104,7 +134,7 @@ def update_ui(self) -> None: self._label_w.text = tmp else: self._connect_w.text = "Connect to DSG Server" - self._label_w.text = "No connected DSG server" + self._label_w.text = "No connected DSG server" + self._error_msg self._update_w.enabled = self._connected and (status.get("status", "idle") == "idle") self._connect_w.enabled = status.get("status", "idle") == "idle" self._temporal_w.enabled = True @@ -113,13 +143,14 @@ def update_ui(self) -> None: self._time_scale_w.enabled = not self._connected self._dsg_uri_w.enabled = not self._connected self._dsg_token_w.enabled = not self._connected + self._interpreter_w.enabled = not self._connected self._destination_w.enabled = not self._connected def build_ui(self) -> None: self._window = ui.Window(f"ANSYS Tools Omniverse DSG ({self.service.version})") with self._window.frame: with ui.VStack(height=0, spacing=5): - self._label_w = ui.Label("No connected DSG server") + self._label_w = ui.Label("No connected DSG server" + self._error_msg) with ui.HStack(spacing=5): ui.Label( @@ -141,7 +172,16 @@ def build_ui(self) -> None: with ui.HStack(spacing=5): ui.Label( - "Omniverse URI:", + "Python path:", + alignment=ui.Alignment.RIGHT_CENTER, + width=0, + ) + self._interpreter_w = ui.StringField() + self._interpreter_w.model.as_string = str(self.service.interpreter) + + with ui.HStack(spacing=5): + ui.Label( + "Export directory:", alignment=ui.Alignment.RIGHT_CENTER, width=0, ) @@ -184,6 +224,7 @@ def on_shutdown(self) -> None: self._label_w = None self._dsg_uri_w = None self._dsg_token_w = None + self._interpreter_w = None self._destination_w = None self._temporal_w = None self._vrmode_w = None diff --git a/exts/ansys.tools.omniverse.dsgui/docs/CHANGELOG.md b/exts/ansys.tools.omniverse.dsgui/docs/CHANGELOG.md index 0605e02..11d717c 100644 --- a/exts/ansys.tools.omniverse.dsgui/docs/CHANGELOG.md +++ b/exts/ansys.tools.omniverse.dsgui/docs/CHANGELOG.md @@ -2,6 +2,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.8.11] - 2024-10-17 +- Add UI to view, edit, and validate the path to the python containing pyensight + ## [0.8.11] - 2024-09-17 - Update for new pyensight Omniverse interface diff --git a/pyproject.toml b/pyproject.toml index f5c4698..889b4e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,9 @@ -[build-system] -requires = ["setuptools >= 42.0.0", "wheel"] -build-backend = "setuptools.build_meta:__legacy__" - [project] name = "ansys-tools-omniverse" version = "1.0.0" description = "A kit extension to send data from Ansys products into Omniverse" readme = "README.rst" -requires-python = "==3.10" +requires-python = ">=3.10,<3.11" license = {file = "LICENSE"} authors = [{name = "ANSYS, Inc.", email = "pyansys.core@ansys.com"}] maintainers = [{name = "ANSYS, Inc.", email = "pyansys.core@ansys.com"}] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b7e54fd..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[egg_info] -egg_base = build/egg-info \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 7019687..0000000 --- a/setup.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Installation file for the ansys-api-pyensight package""" - -from datetime import datetime -import os -import shutil - -import setuptools -import setuptools.command.build -import setuptools.command.build_py -from setuptools.command.build_py import build_py as build_py_orig -import setuptools.command.sdist - -# Get the long description from the README file -HERE = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(HERE, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -with open( - os.path.join(HERE, "src", "ansys", "tools", "omniverse", "VERSION"), - encoding="utf-8", -) as f: - version = f.read().strip() - -package_name = "ansys-tools-omniverse" -description = f"An extension to send data to Omniverse using Ansys EnSight, built on {datetime.now().strftime('%H:%M:%S on %d %B %Y')}" - - -class CustomBuildPy(build_py_orig): - def run(self): - # Copy the exts folder to the desired package location - target_dir = os.path.join(self.build_lib, "ansys", "tools", "omniverse", "exts") - if not os.path.exists(target_dir): - os.makedirs(target_dir) - exts = os.path.join(os.path.dirname(__file__), "exts") - for item in os.listdir(exts): - s = os.path.join(exts, item) - d = os.path.join(target_dir, item) - if os.path.isdir(s): - shutil.copytree(s, d, dirs_exist_ok=True) - else: - shutil.copy2(s, d) - super().run() - - -egg_info_dir = "build/egg-info" -if not os.path.exists(egg_info_dir): - os.makedirs(egg_info_dir) - - -def package_files(directory): - paths = [] - for path, directories, filenames in os.walk(directory): - for filename in filenames: - paths.append(os.path.join(path, filename)) - return paths - - -exts_files = package_files(os.path.join(os.path.dirname(__file__), "exts")) - - -_packages = setuptools.find_namespace_packages("src", include=("ansys.*",)) - -if __name__ == "__main__": - setuptools.setup( - name=package_name, - version=version, - author="ANSYS, Inc.", - author_email="pyansys.core@ansys.com", - maintainer="ANSYS, Inc.", - maintainer_email="pyansys.core@ansys.com", - description=description, - long_description=long_description, - long_description_content_type="text/markdown", - url=f"https://github.com/ansys-internal/{package_name}", - license="MIT", - python_requires="~=3.10", - package_dir={"": "src"}, - packages=_packages, - cmdclass={"build_py": CustomBuildPy}, - include_package_data=True, - package_data={"": ["VERSION"], "ansys.tools.omniverse": exts_files}, - ) diff --git a/src/ansys/tools/omniverse/VERSION b/src/ansys/tools/omniverse/VERSION deleted file mode 100644 index cbaf3b3..0000000 --- a/src/ansys/tools/omniverse/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.0 diff --git a/src/ansys/tools/omniverse/__init__.py b/src/ansys/tools/omniverse/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py index e01741c..86c9881 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,25 @@ +# Copyright (C) 2024 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 ansys.pyensight.core import DockerLauncher, LocalLauncher diff --git a/tests/test_basic.py b/tests/test_basic.py index 5f2858e..f51d622 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,3 +1,25 @@ +# Copyright (C) 2024 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 glob import os from typing import TYPE_CHECKING