From bbeb54db47e530e2925d8272c4c2e204316c5884 Mon Sep 17 00:00:00 2001 From: Randy Frank Date: Thu, 11 Jul 2024 20:02:23 -0400 Subject: [PATCH] Add time scaling to temporal export Improve error handling Add "time_scale" mechanism to allow timeline adjustment during export. --- .../ansys/geometry/service/extension.py | 53 ++++++++++++++++--- .../config/extension.toml | 3 +- .../ansys/geometry/serviceui/extension.py | 25 ++++++++- .../config/extension.toml | 2 +- .../ansys/geometry/service/extension.py | 53 ++++++++++++++++--- .../config/extension.toml | 3 +- .../ansys/geometry/serviceui/extension.py | 25 ++++++++- .../config/extension.toml | 2 +- src/ansys/pyensight/core/utils/dsg_server.py | 7 +++ 9 files changed, 155 insertions(+), 18 deletions(-) diff --git a/exts/ansys.geometry.service/ansys/geometry/service/extension.py b/exts/ansys.geometry.service/ansys/geometry/service/extension.py index c384ea01a5e..f991ea49176 100644 --- a/exts/ansys.geometry.service/ansys/geometry/service/extension.py +++ b/exts/ansys.geometry.service/ansys/geometry/service/extension.py @@ -13,6 +13,8 @@ try: import ansys.pyensight.core + import ansys.pyensight.core.utils.dsg_server as tmp_dsg_server # noqa: F401 + import ansys.pyensight.core.utils.omniverse_dsg_server as tmp_ov_dsg_server # noqa: F401 except ModuleNotFoundError: logging.warning("ansys.geometry.server - Installing ansys-pyensight-core") omni.kit.pipapi.install("ansys-pyensight-core") @@ -82,6 +84,11 @@ def __init__(self, *args, **kwargs) -> None: self._security_token = self._setting("securityCode", "ENSIGHT_SECURITY_TOKEN") self._temporal = self._setting("temporal") != "0" self._vrmode = self._setting("vrmode") != "0" + try: + scale = float(self._setting("timeScale")) + except ValueError: + scale = 1.0 + self._time_scale = scale self._normalize_geometry = self._setting("normalizeGeometry") != "0" self._version = "unknown" self._shutdown = False @@ -141,6 +148,15 @@ def normalize_geometry(self) -> bool: def normalize_geometry(self, val: bool) -> None: self._normalize_geometry = val + @property + def time_scale(self) -> float: + """Value to multiply DSG time values by before passing to Omniverse""" + return self._time_scale + + @time_scale.setter + def time_scale(self, value: float) -> None: + self._time_scale = value + @classmethod def get_instance(cls) -> Optional["AnsysGeometryServiceServerExtension"]: return cls._service_instance @@ -260,18 +276,40 @@ def help(self) -> None: self.warning( f" If non-zero, remap the geometry to the domain [-1,-1,-1]-[1,1,1]. (default: {self.normalize_geometry})" ) + self.warning(" --/exts/ansys.geometry.service/timeScale=FLOAT") + self.warning( + f" Multiply all DSG time values by this value. (default: {self.time_scale})" + ) + + def is_server_running(self) -> bool: + """ + Returns True if the server is running. + + Returns + ------- + bool + True if the server is running. + """ + if self._server_process: + if psutil.pid_exists(self._server_process.pid): + return True + return False def stop_server(self) -> None: """ If a DSG server connection has been started, stop it. It could be in process or a subprocess. """ - self._shutdown = True - if self._server_process: - for child in psutil.Process(self._server_process.pid).children(recursive=True): - child.kill() - self._server_process.kill() - self._server_process = None + try: + self._shutdown = True + if self._server_process: + for child in psutil.Process(self._server_process.pid).children(recursive=True): + child.kill() + self._server_process.kill() + except psutil.NoSuchProcess: + pass + self._server_process = None + self._shutdown = False def launch_server(self) -> None: """ @@ -300,6 +338,8 @@ def launch_server(self) -> None: cmd.append("--/exts/ansys.geometry.service/vrmode=1") if self.normalize_geometry: cmd.append("--/exts/ansys.geometry.service/normalizeGeometry=1") + if self.time_scale != 1.0: + cmd.append(f"--/exts/ansys.geometry.service/timeScale={self.time_scale}") cmd.append(f"--/exts/ansys.geometry.service/omniUrl={self.omni_uri}") cmd.append(f"--/exts/ansys.geometry.service/dsgUrl={self.dsg_uri}") cmd.append("--/exts/ansys.geometry.service/run=1") @@ -344,6 +384,7 @@ def run_server(self) -> None: security_code=self.security_token, verbose=1, normalize_geometry=self.normalize_geometry, + time_scale=self.time_scale, handler=update_handler, ) diff --git a/exts/ansys.geometry.service/config/extension.toml b/exts/ansys.geometry.service/config/extension.toml index d4b5851e573..0fe8d34a340 100644 --- a/exts/ansys.geometry.service/config/extension.toml +++ b/exts/ansys.geometry.service/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.8.5" +version = "0.8.6" # Lists people or organizations that are considered the "authors" of the package. authors = ["ANSYS"] @@ -56,3 +56,4 @@ exts."ansys.geometry.service".securityCode = "" exts."ansys.geometry.service".temporal = "0" exts."ansys.geometry.service".vrmode = "0" exts."ansys.geometry.service".normalizeGeometry = "0" +exts."ansys.geometry.service".timeScale = "1.0" diff --git a/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py b/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py index 2b54b85faf0..f1f8acb2db7 100644 --- a/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +++ b/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py @@ -20,6 +20,7 @@ def __init__(self, *args, **kwargs) -> None: self._temporal_w = None self._vrmode_w = None self._normalize_w = None + self._time_scale_w = None self._connect_w = None self._update_w = None self._connected = False @@ -38,7 +39,7 @@ def error(self, text: str) -> None: self._logger.error(text) def launch_server(self) -> None: - if self._connected: + if self.service.is_server_running(): return self.service.dsg_uri = self._dsg_uri_w.model.as_string self.service.security_token = self._dsg_token_w.model.as_string @@ -46,7 +47,14 @@ def launch_server(self) -> None: self.service.temporal = self._temporal_w.model.as_bool self.service.vrmode = self._vrmode_w.model.as_bool self.service.normalize_geometry = self._normalize_w.model.as_bool + scale = self._time_scale_w.model.as_float + if scale <= 0.0: + scale = 1.0 + self.service.time_scale = scale self.service.launch_server() + if not self.service.is_server_running(): + self.error("Failed to launch omniverse service.") + return # parse the DSG USI parsed = urlparse(self.service.dsg_uri) @@ -60,6 +68,9 @@ def launch_server(self) -> None: host=host, port=port, secret_key=self.service.security_token ) self._grpc.connect() + if not self._grpc.is_connected(): + self.error(f"Failed to connect to DSG service {host}:{port}") + return self.info("Connected to DSG service") self._connected = True @@ -69,6 +80,7 @@ def stop_server(self) -> None: return self.service.stop_server() self._grpc.shutdown() + self._grpc = None self.info("Disconnect from DSG service") self._connected = False @@ -104,12 +116,15 @@ def on_startup(self, ext_id: str) -> None: def update_ui(self) -> None: if self._connected: self._connect_w.text = "Disconnect from DSG Server" + self._label_w.text = f"Connected to: {self.service.dsg_uri}" else: self._connect_w.text = "Connect to DSG Server" + self._label_w.text = "No connected DSG server" self._update_w.enabled = self._connected self._temporal_w.enabled = True self._vrmode_w.enabled = not self._connected self._normalize_w.enabled = not self._connected + 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._omni_uri_w.enabled = not self._connected @@ -151,6 +166,13 @@ def build_ui(self) -> None: self._normalize_w.model.set_value(self.service.normalize_geometry) ui.Label("Normalize", alignment=ui.Alignment.LEFT_CENTER) + with ui.HStack(spacing=5): + ui.Label( + "Temporal scaling factor:", alignment=ui.Alignment.RIGHT_CENTER, width=0 + ) + self._time_scale_w = ui.FloatField() + self._time_scale_w.model.as_float = self.service.time_scale + with ui.HStack(): self._connect_w = ui.Button("Connect to DSG Server", clicked_fn=self.connect_cb) self._update_w = ui.Button("Request Update", clicked_fn=self.update_cb) @@ -166,5 +188,6 @@ def on_shutdown(self) -> None: self._temporal_w = None self._vrmode_w = None self._normalize_w = None + self._time_scale_w = None self._connect_w = None self._update_w = None diff --git a/exts/ansys.geometry.serviceui/config/extension.toml b/exts/ansys.geometry.serviceui/config/extension.toml index 069ca66a52f..b4805b13f3b 100644 --- a/exts/ansys.geometry.serviceui/config/extension.toml +++ b/exts/ansys.geometry.serviceui/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.8.5" +version = "0.8.6" # Lists people or organizations that are considered the "authors" of the package. authors = ["ANSYS"] diff --git a/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py b/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py index c384ea01a5e..f991ea49176 100644 --- a/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +++ b/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py @@ -13,6 +13,8 @@ try: import ansys.pyensight.core + import ansys.pyensight.core.utils.dsg_server as tmp_dsg_server # noqa: F401 + import ansys.pyensight.core.utils.omniverse_dsg_server as tmp_ov_dsg_server # noqa: F401 except ModuleNotFoundError: logging.warning("ansys.geometry.server - Installing ansys-pyensight-core") omni.kit.pipapi.install("ansys-pyensight-core") @@ -82,6 +84,11 @@ def __init__(self, *args, **kwargs) -> None: self._security_token = self._setting("securityCode", "ENSIGHT_SECURITY_TOKEN") self._temporal = self._setting("temporal") != "0" self._vrmode = self._setting("vrmode") != "0" + try: + scale = float(self._setting("timeScale")) + except ValueError: + scale = 1.0 + self._time_scale = scale self._normalize_geometry = self._setting("normalizeGeometry") != "0" self._version = "unknown" self._shutdown = False @@ -141,6 +148,15 @@ def normalize_geometry(self) -> bool: def normalize_geometry(self, val: bool) -> None: self._normalize_geometry = val + @property + def time_scale(self) -> float: + """Value to multiply DSG time values by before passing to Omniverse""" + return self._time_scale + + @time_scale.setter + def time_scale(self, value: float) -> None: + self._time_scale = value + @classmethod def get_instance(cls) -> Optional["AnsysGeometryServiceServerExtension"]: return cls._service_instance @@ -260,18 +276,40 @@ def help(self) -> None: self.warning( f" If non-zero, remap the geometry to the domain [-1,-1,-1]-[1,1,1]. (default: {self.normalize_geometry})" ) + self.warning(" --/exts/ansys.geometry.service/timeScale=FLOAT") + self.warning( + f" Multiply all DSG time values by this value. (default: {self.time_scale})" + ) + + def is_server_running(self) -> bool: + """ + Returns True if the server is running. + + Returns + ------- + bool + True if the server is running. + """ + if self._server_process: + if psutil.pid_exists(self._server_process.pid): + return True + return False def stop_server(self) -> None: """ If a DSG server connection has been started, stop it. It could be in process or a subprocess. """ - self._shutdown = True - if self._server_process: - for child in psutil.Process(self._server_process.pid).children(recursive=True): - child.kill() - self._server_process.kill() - self._server_process = None + try: + self._shutdown = True + if self._server_process: + for child in psutil.Process(self._server_process.pid).children(recursive=True): + child.kill() + self._server_process.kill() + except psutil.NoSuchProcess: + pass + self._server_process = None + self._shutdown = False def launch_server(self) -> None: """ @@ -300,6 +338,8 @@ def launch_server(self) -> None: cmd.append("--/exts/ansys.geometry.service/vrmode=1") if self.normalize_geometry: cmd.append("--/exts/ansys.geometry.service/normalizeGeometry=1") + if self.time_scale != 1.0: + cmd.append(f"--/exts/ansys.geometry.service/timeScale={self.time_scale}") cmd.append(f"--/exts/ansys.geometry.service/omniUrl={self.omni_uri}") cmd.append(f"--/exts/ansys.geometry.service/dsgUrl={self.dsg_uri}") cmd.append("--/exts/ansys.geometry.service/run=1") @@ -344,6 +384,7 @@ def run_server(self) -> None: security_code=self.security_token, verbose=1, normalize_geometry=self.normalize_geometry, + time_scale=self.time_scale, handler=update_handler, ) diff --git a/src/ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml b/src/ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml index d4b5851e573..0fe8d34a340 100644 --- a/src/ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +++ b/src/ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.8.5" +version = "0.8.6" # Lists people or organizations that are considered the "authors" of the package. authors = ["ANSYS"] @@ -56,3 +56,4 @@ exts."ansys.geometry.service".securityCode = "" exts."ansys.geometry.service".temporal = "0" exts."ansys.geometry.service".vrmode = "0" exts."ansys.geometry.service".normalizeGeometry = "0" +exts."ansys.geometry.service".timeScale = "1.0" diff --git a/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py b/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py index 2b54b85faf0..f1f8acb2db7 100644 --- a/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +++ b/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py @@ -20,6 +20,7 @@ def __init__(self, *args, **kwargs) -> None: self._temporal_w = None self._vrmode_w = None self._normalize_w = None + self._time_scale_w = None self._connect_w = None self._update_w = None self._connected = False @@ -38,7 +39,7 @@ def error(self, text: str) -> None: self._logger.error(text) def launch_server(self) -> None: - if self._connected: + if self.service.is_server_running(): return self.service.dsg_uri = self._dsg_uri_w.model.as_string self.service.security_token = self._dsg_token_w.model.as_string @@ -46,7 +47,14 @@ def launch_server(self) -> None: self.service.temporal = self._temporal_w.model.as_bool self.service.vrmode = self._vrmode_w.model.as_bool self.service.normalize_geometry = self._normalize_w.model.as_bool + scale = self._time_scale_w.model.as_float + if scale <= 0.0: + scale = 1.0 + self.service.time_scale = scale self.service.launch_server() + if not self.service.is_server_running(): + self.error("Failed to launch omniverse service.") + return # parse the DSG USI parsed = urlparse(self.service.dsg_uri) @@ -60,6 +68,9 @@ def launch_server(self) -> None: host=host, port=port, secret_key=self.service.security_token ) self._grpc.connect() + if not self._grpc.is_connected(): + self.error(f"Failed to connect to DSG service {host}:{port}") + return self.info("Connected to DSG service") self._connected = True @@ -69,6 +80,7 @@ def stop_server(self) -> None: return self.service.stop_server() self._grpc.shutdown() + self._grpc = None self.info("Disconnect from DSG service") self._connected = False @@ -104,12 +116,15 @@ def on_startup(self, ext_id: str) -> None: def update_ui(self) -> None: if self._connected: self._connect_w.text = "Disconnect from DSG Server" + self._label_w.text = f"Connected to: {self.service.dsg_uri}" else: self._connect_w.text = "Connect to DSG Server" + self._label_w.text = "No connected DSG server" self._update_w.enabled = self._connected self._temporal_w.enabled = True self._vrmode_w.enabled = not self._connected self._normalize_w.enabled = not self._connected + 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._omni_uri_w.enabled = not self._connected @@ -151,6 +166,13 @@ def build_ui(self) -> None: self._normalize_w.model.set_value(self.service.normalize_geometry) ui.Label("Normalize", alignment=ui.Alignment.LEFT_CENTER) + with ui.HStack(spacing=5): + ui.Label( + "Temporal scaling factor:", alignment=ui.Alignment.RIGHT_CENTER, width=0 + ) + self._time_scale_w = ui.FloatField() + self._time_scale_w.model.as_float = self.service.time_scale + with ui.HStack(): self._connect_w = ui.Button("Connect to DSG Server", clicked_fn=self.connect_cb) self._update_w = ui.Button("Request Update", clicked_fn=self.update_cb) @@ -166,5 +188,6 @@ def on_shutdown(self) -> None: self._temporal_w = None self._vrmode_w = None self._normalize_w = None + self._time_scale_w = None self._connect_w = None self._update_w = None diff --git a/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml b/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml index 069ca66a52f..b4805b13f3b 100644 --- a/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +++ b/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.8.5" +version = "0.8.6" # Lists people or organizations that are considered the "authors" of the package. authors = ["ANSYS"] diff --git a/src/ansys/pyensight/core/utils/dsg_server.py b/src/ansys/pyensight/core/utils/dsg_server.py index 09945f24266..4924aae7906 100644 --- a/src/ansys/pyensight/core/utils/dsg_server.py +++ b/src/ansys/pyensight/core/utils/dsg_server.py @@ -349,6 +349,7 @@ def __init__( verbose: int = 0, normalize_geometry: bool = False, vrmode: bool = False, + time_scale: float = 1.0, handler: UpdateHandler = UpdateHandler(), ): """ @@ -378,6 +379,9 @@ def __init__( vrmode : bool If True, do not include the EnSight camera in the generated view group. The default is to include the EnSight view in the scene transformations. + time_scale : float + All DSG protobuffers time values will be multiplied by this factor after + being received. The default is ``1.0``. handler : UpdateHandler This is an UpdateHandler subclass that is called back when the state of a scene transfer changes. For example, methods are called when the @@ -394,6 +398,7 @@ def __init__( self._dsg = None self._normalize_geometry = normalize_geometry self._vrmode = vrmode + self._time_scale = time_scale self._time_limits = [ sys.float_info.max, -sys.float_info.max, @@ -709,5 +714,7 @@ def _handle_view(self, view: Any) -> None: self._scene_bounds = None self._groups[view.id] = view if len(view.timeline) == 2: + view.timeline[0] *= self._time_scale + view.timeline[1] *= self._time_scale self.cur_timeline = [view.timeline[0], view.timeline[1]] self._callback_handler.add_group(view.id, view=True)