ansys-pyensight-core 0.8.7__tar.gz → 0.8.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ansys-pyensight-core might be problematic. Click here for more details.

Files changed (55) hide show
  1. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/PKG-INFO +12 -6
  2. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/pyproject.toml +14 -8
  3. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/dockerlauncher.py +6 -0
  4. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/launcher.py +7 -2
  5. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/locallauncher.py +4 -0
  6. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/session.py +1 -1
  7. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/dsg_server.py +227 -35
  8. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/omniverse.py +84 -24
  9. ansys_pyensight_core-0.8.9/src/ansys/pyensight/core/utils/omniverse_cli.py +481 -0
  10. ansys_pyensight_core-0.8.9/src/ansys/pyensight/core/utils/omniverse_dsg_server.py +690 -0
  11. ansys_pyensight_core-0.8.9/src/ansys/pyensight/core/utils/omniverse_glb_server.py +279 -0
  12. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/__init__.py +0 -1
  13. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/ansys/geometry/service/extension.py +0 -407
  14. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/config/extension.toml +0 -59
  15. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/data/icon.png +0 -0
  16. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/data/preview.png +0 -0
  17. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/docs/CHANGELOG.md +0 -11
  18. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/docs/README.md +0 -13
  19. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.service/docs/index.rst +0 -18
  20. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/__init__.py +0 -1
  21. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/ansys/geometry/serviceui/extension.py +0 -193
  22. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/config/extension.toml +0 -49
  23. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/data/icon.png +0 -0
  24. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/data/preview.png +0 -0
  25. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/CHANGELOG.md +0 -11
  26. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/README.md +0 -13
  27. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/exts/ansys.geometry.serviceui/docs/index.rst +0 -18
  28. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/utils/omniverse_dsg_server.py +0 -880
  29. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_BaseColor.png +0 -0
  30. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_N.png +0 -0
  31. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/utils/resources/Materials/Fieldstone/Fieldstone_ORM.png +0 -0
  32. ansys_pyensight_core-0.8.7/src/ansys/pyensight/core/utils/resources/Materials/Fieldstone.mdl +0 -54
  33. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/LICENSE +0 -0
  34. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/README.rst +0 -0
  35. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/__init__.py +0 -0
  36. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/deep_pixel_view.html +0 -0
  37. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/enscontext.py +0 -0
  38. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/enshell_grpc.py +0 -0
  39. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/ensight_grpc.py +0 -0
  40. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/ensobj.py +0 -0
  41. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/launch_ensight.py +0 -0
  42. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/listobj.py +0 -0
  43. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/py.typed +0 -0
  44. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/renderable.py +0 -0
  45. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/sgeo_poll.html +0 -0
  46. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/__init__.py +0 -0
  47. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/adr.py +0 -0
  48. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/export.py +0 -0
  49. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/parts.py +0 -0
  50. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/query.py +0 -0
  51. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/readers.py +0 -0
  52. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/resources/Materials/000_sky.exr +0 -0
  53. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/support.py +0 -0
  54. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/variables.py +0 -0
  55. {ansys_pyensight_core-0.8.7 → ansys_pyensight_core-0.8.9}/src/ansys/pyensight/core/utils/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ansys-pyensight-core
3
- Version: 0.8.7
3
+ Version: 0.8.9
4
4
  Summary: A python wrapper for Ansys EnSight
5
5
  Author-email: "ANSYS, Inc." <pyansys.core@ansys.com>
6
6
  Maintainer-email: "ANSYS, Inc." <pyansys.core@ansys.com>
@@ -19,7 +19,7 @@ Requires-Dist: importlib-metadata>=4.0; python_version<='3.8'
19
19
  Requires-Dist: ansys-api-pyensight==0.4.1
20
20
  Requires-Dist: requests>=2.28.2
21
21
  Requires-Dist: docker>=6.1.0
22
- Requires-Dist: urllib3<2
22
+ Requires-Dist: urllib3<3.0.0
23
23
  Requires-Dist: numpy>=1.21.0,<2
24
24
  Requires-Dist: Pillow>=9.3.0
25
25
  Requires-Dist: pypng>=0.0.20
@@ -29,26 +29,32 @@ Requires-Dist: bump2version>=1.0.1 ; extra == "dev"
29
29
  Requires-Dist: ipdb>=0.9.4 ; extra == "dev"
30
30
  Requires-Dist: dill>=0.3.5.1 ; extra == "dev"
31
31
  Requires-Dist: pre-commit>=3.3.3 ; extra == "dev"
32
- Requires-Dist: Sphinx==7.2.6 ; extra == "doc"
32
+ Requires-Dist: usd-core>=24.8 ; extra == "dev"
33
+ Requires-Dist: pygltflib>=1.16.2 ; extra == "dev"
34
+ Requires-Dist: Sphinx==8.0.2 ; extra == "doc"
33
35
  Requires-Dist: numpydoc==1.5.0 ; extra == "doc"
34
36
  Requires-Dist: ansys-sphinx-theme==0.9.9 ; extra == "doc"
35
37
  Requires-Dist: sphinx-copybutton==0.5.2 ; extra == "doc"
36
38
  Requires-Dist: sphinx-gallery==0.13.0 ; extra == "doc"
37
39
  Requires-Dist: sphinxcontrib-mermaid==0.9.2 ; extra == "doc"
38
40
  Requires-Dist: docker>=6.1.0 ; extra == "doc"
39
- Requires-Dist: matplotlib==3.7.2 ; extra == "doc"
41
+ Requires-Dist: matplotlib==3.9.1.post1 ; extra == "doc"
40
42
  Requires-Dist: requests>=2.28.2 ; extra == "doc"
41
43
  Requires-Dist: sphinxcontrib.jquery==4.1 ; extra == "doc"
42
44
  Requires-Dist: coverage-badge==1.1.0 ; extra == "doc"
43
45
  Requires-Dist: sphinxcontrib-openapi==0.8.1 ; extra == "doc"
44
46
  Requires-Dist: sphinxcontrib-video==0.2.0 ; extra == "doc"
45
- Requires-Dist: pytest==7.1.2 ; extra == "tests"
47
+ Requires-Dist: usd-core>=24.8 ; extra == "doc"
48
+ Requires-Dist: pygltflib>=1.16.2 ; extra == "doc"
49
+ Requires-Dist: pytest==8.3.2 ; extra == "tests"
46
50
  Requires-Dist: pytest-cov==4.1.0 ; extra == "tests"
47
51
  Requires-Dist: dill>=0.3.5.1 ; extra == "tests"
48
52
  Requires-Dist: pytest-mock==3.10.0 ; extra == "tests"
49
- Requires-Dist: urllib3==1.26.10 ; extra == "tests"
53
+ Requires-Dist: urllib3==2.2.2 ; extra == "tests"
50
54
  Requires-Dist: requests>=2.28.2 ; extra == "tests"
51
55
  Requires-Dist: docker>=6.1.0 ; extra == "tests"
56
+ Requires-Dist: usd-core>=24.8 ; extra == "tests"
57
+ Requires-Dist: pygltflib>=1.16.2 ; extra == "tests"
52
58
  Project-URL: Changelog, https://github.com/ansys/pyensight/blob/main/CHANGELOG.rst
53
59
  Project-URL: Documentation, https://ensight.docs.pyansys.com/
54
60
  Project-URL: Homepage, https://github.com/ansys/pyensight
@@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi"
6
6
 
7
7
  [project]
8
8
  name = "ansys-pyensight-core"
9
- version = "0.8.7"
9
+ version = "0.8.9"
10
10
  description = "A python wrapper for Ansys EnSight"
11
11
  readme = "README.rst"
12
12
  requires-python = ">=3.9,<4"
@@ -30,7 +30,7 @@ dependencies = [
30
30
  "ansys-api-pyensight==0.4.1",
31
31
  "requests>=2.28.2",
32
32
  "docker>=6.1.0",
33
- "urllib3<2",
33
+ "urllib3<3.0.0",
34
34
  "numpy>=1.21.0,<2",
35
35
  "Pillow>=9.3.0",
36
36
  "pypng>=0.0.20",
@@ -44,30 +44,36 @@ dev = [
44
44
  "ipdb>=0.9.4",
45
45
  "dill>=0.3.5.1",
46
46
  "pre-commit>=3.3.3",
47
+ "usd-core>=24.8",
48
+ "pygltflib>=1.16.2",
47
49
  ]
48
50
  tests = [
49
- "pytest==7.1.2",
51
+ "pytest==8.3.2",
50
52
  "pytest-cov==4.1.0",
51
53
  "dill>=0.3.5.1",
52
54
  "pytest-mock==3.10.0",
53
- "urllib3==1.26.10",
55
+ "urllib3==2.2.2",
54
56
  "requests>=2.28.2",
55
57
  "docker>=6.1.0",
58
+ "usd-core>=24.8",
59
+ "pygltflib>=1.16.2",
56
60
  ]
57
61
  doc = [
58
- "Sphinx==7.2.6",
62
+ "Sphinx==8.0.2",
59
63
  "numpydoc==1.5.0",
60
64
  "ansys-sphinx-theme==0.9.9",
61
65
  "sphinx-copybutton==0.5.2",
62
66
  "sphinx-gallery==0.13.0",
63
67
  "sphinxcontrib-mermaid==0.9.2",
64
68
  "docker>=6.1.0",
65
- "matplotlib==3.7.2",
69
+ "matplotlib==3.9.1.post1",
66
70
  "requests>=2.28.2",
67
71
  "sphinxcontrib.jquery==4.1",
68
72
  "coverage-badge==1.1.0",
69
73
  "sphinxcontrib-openapi==0.8.1",
70
74
  "sphinxcontrib-video==0.2.0",
75
+ "usd-core>=24.8",
76
+ "pygltflib>=1.16.2",
71
77
  ]
72
78
 
73
79
  [project.urls]
@@ -88,8 +94,8 @@ omit = [
88
94
  "*/omniverse*.py",
89
95
  "*/dsg_server.py",
90
96
  "*/readers.py",
91
- "*/service/*.py",
92
- "*/serviceui/*.py",
97
+ "*/omniverse/core/*.py",
98
+ "*/omniverse/dsgui/*.py",
93
99
  ]
94
100
 
95
101
  [tool.coverage.report]
@@ -87,6 +87,9 @@ class DockerLauncher(Launcher):
87
87
  Number of EnSight servers to use for SOS (Server of Server) mode.
88
88
  This parameter is defined on the parent ``Launcher`` class, where
89
89
  the default is ``None``, in which case SOS mode is not used.
90
+ additional_command_line_options: list, optional
91
+ Additional command line options to be used to launch EnSight.
92
+ Arguments that contain spaces are not supported.
90
93
 
91
94
  Examples
92
95
  --------
@@ -504,6 +507,9 @@ class DockerLauncher(Launcher):
504
507
 
505
508
  vnc_url = "vnc://%%3Frfb_port=1999%%26use_auth=0"
506
509
  ensight_args += " -vnc " + vnc_url
510
+ if self._additional_command_line_options:
511
+ ensight_args += " "
512
+ ensight_args += " ".join(self._additional_command_line_options)
507
513
 
508
514
  logging.debug(f"Starting EnSight with args: {ensight_args}\n")
509
515
  ret = self._enshell.start_ensight(ensight_args, ensight_env_vars)
@@ -59,7 +59,10 @@ class Launcher:
59
59
  enable_rest_api : bool, optional
60
60
  Whether to enable the EnSight REST API. The default is ``False``.
61
61
  This parameter is supported in EnSight 2024 R1 and later.
62
-
62
+ additional_command_line_options: list, optional
63
+ Additional command line options to be used to launch EnSight.
64
+ Please note, when using DockerLauncher, arguments that contain spaces
65
+ are not supported.
63
66
  """
64
67
 
65
68
  def __init__(
@@ -68,6 +71,7 @@ class Launcher:
68
71
  use_egl: bool = False,
69
72
  use_sos: Optional[int] = None,
70
73
  enable_rest_api: bool = False,
74
+ additional_command_line_options: Optional[List] = None,
71
75
  ) -> None:
72
76
  self._timeout = timeout
73
77
  self._use_egl_param_val: bool = use_egl
@@ -87,6 +91,7 @@ class Launcher:
87
91
  self._egl_env_val = False
88
92
  # a dict of any optional launcher specific query parameters for URLs
89
93
  self._query_parameters: Dict[str, str] = {}
94
+ self._additional_command_line_options = additional_command_line_options
90
95
 
91
96
  @property
92
97
  def session_directory(self) -> str:
@@ -180,7 +185,7 @@ class Launcher:
180
185
  for process in psutil.process_iter():
181
186
  try:
182
187
  process_cmdline = process.cmdline()
183
- except (psutil.AccessDenied, psutil.ZombieProcess):
188
+ except (psutil.AccessDenied, psutil.ZombieProcess, OSError):
184
189
  continue
185
190
  if not process_cmdline:
186
191
  continue
@@ -56,6 +56,8 @@ class LocalLauncher(Launcher):
56
56
  Number of EnSight servers to use for SOS (Server of Server) mode.
57
57
  This parameter is defined on the parent ``Launcher`` class, where
58
58
  the default is ``None``, in which case SOS mode is not used.
59
+ additional_command_line_options: list, optional
60
+ Additional command line options to be used to launch EnSight.
59
61
 
60
62
  Examples
61
63
  --------
@@ -154,6 +156,8 @@ class LocalLauncher(Launcher):
154
156
  vnc_url = f"vnc://%%3Frfb_port={self._ports[1]}%%26use_auth=0"
155
157
  cmd.extend(["-vnc", vnc_url])
156
158
  cmd.extend(["-ports", str(self._ports[4])])
159
+ if self._additional_command_line_options:
160
+ cmd.extend(self._additional_command_line_options)
157
161
 
158
162
  use_egl = self._use_egl()
159
163
 
@@ -1064,7 +1064,7 @@ class Session:
1064
1064
  onlyfiles = [f for f in listdir(_utils_dir) if os.path.isfile(os.path.join(_utils_dir, f))]
1065
1065
  for _basename in onlyfiles:
1066
1066
  # skip over any files with the "_server" in their names
1067
- if "_server" in _basename:
1067
+ if "_server" in _basename or "_cli" in _basename:
1068
1068
  continue
1069
1069
  _filename = os.path.join(_utils_dir, _basename)
1070
1070
  try:
@@ -1,8 +1,11 @@
1
+ import hashlib
2
+ import json
1
3
  import logging
2
4
  import os
3
5
  import queue
4
6
  import sys
5
7
  import threading
8
+ import time
6
9
  from typing import Any, Dict, List, Optional
7
10
 
8
11
  from ansys.api.pyensight.v0 import dynamic_scene_graph_pb2
@@ -32,9 +35,10 @@ class Part(object):
32
35
  self.normals = numpy.array([], dtype="float32")
33
36
  self.normals_elem = False
34
37
  self.tcoords = numpy.array([], dtype="float32")
35
- self.tcoords_var_id: Optional[int] = None
36
38
  self.tcoords_elem = False
39
+ self.node_sizes = numpy.array([], dtype="float32")
37
40
  self.cmd: Optional[Any] = None
41
+ self.hash = hashlib.new("sha256")
38
42
  self.reset()
39
43
 
40
44
  def reset(self, cmd: Any = None) -> None:
@@ -46,6 +50,10 @@ class Part(object):
46
50
  self.tcoords = numpy.array([], dtype="float32")
47
51
  self.tcoords_var_id = None
48
52
  self.tcoords_elem = False
53
+ self.node_sizes = numpy.array([], dtype="float32")
54
+ self.hash = hashlib.new("sha256")
55
+ if cmd is not None:
56
+ self.hash.update(cmd.hash.encode("utf-8"))
49
57
  self.cmd = cmd
50
58
 
51
59
  def update_geom(self, cmd: dynamic_scene_graph_pb2.UpdateGeom) -> None:
@@ -83,17 +91,25 @@ class Part(object):
83
91
  ):
84
92
  # Get the variable definition
85
93
  if cmd.variable_id in self.session.variables:
86
- self.tcoords_var_id = cmd.variable_id
87
- self.tcoords_elem = (
88
- cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_VARIABLE
89
- )
90
- if self.tcoords.size != cmd.total_array_size:
91
- self.tcoords = numpy.resize(self.tcoords, cmd.total_array_size)
92
- self.tcoords[
93
- cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)
94
- ] = cmd.flt_array
95
- else:
96
- self.tcoords_var_id = None
94
+ if self.cmd.color_variableid == cmd.variable_id: # type: ignore
95
+ # Receive the colorby var values
96
+ self.tcoords_elem = (
97
+ cmd.payload_type == dynamic_scene_graph_pb2.UpdateGeom.ELEM_VARIABLE
98
+ )
99
+ if self.tcoords.size != cmd.total_array_size:
100
+ self.tcoords = numpy.resize(self.tcoords, cmd.total_array_size)
101
+ self.tcoords[
102
+ cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)
103
+ ] = cmd.flt_array
104
+ if self.cmd.node_size_variableid == cmd.variable_id: # type: ignore
105
+ # Receive the node size var values
106
+ if self.node_sizes.size != cmd.total_array_size:
107
+ self.node_sizes = numpy.resize(self.node_sizes, cmd.total_array_size)
108
+ self.node_sizes[
109
+ cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)
110
+ ] = cmd.flt_array
111
+ # Combine the hashes for the UpdatePart and all UpdateGeom messages
112
+ self.hash.update(cmd.hash.encode("utf-8"))
97
113
 
98
114
  def nodal_surface_rep(self):
99
115
  """
@@ -120,26 +136,7 @@ class Part(object):
120
136
  self.session.log(f"Note: part '{self.cmd.name}' contains no triangles.")
121
137
  return None, None, None, None, None, None
122
138
  verts = self.coords
123
- if self.session.normalize_geometry and self.session.scene_bounds is not None:
124
- midx = (self.session.scene_bounds[3] + self.session.scene_bounds[0]) * 0.5
125
- midy = (self.session.scene_bounds[4] + self.session.scene_bounds[1]) * 0.5
126
- midz = (self.session.scene_bounds[5] + self.session.scene_bounds[2]) * 0.5
127
- dx = self.session.scene_bounds[3] - self.session.scene_bounds[0]
128
- dy = self.session.scene_bounds[4] - self.session.scene_bounds[1]
129
- dz = self.session.scene_bounds[5] - self.session.scene_bounds[2]
130
- s = dx
131
- if dy > s:
132
- s = dy
133
- if dz > s:
134
- s = dz
135
- if s == 0:
136
- s = 1.0
137
- num_verts = int(verts.size / 3)
138
- for i in range(num_verts):
139
- j = i * 3
140
- verts[j + 0] = (verts[j + 0] - midx) / s
141
- verts[j + 1] = (verts[j + 1] - midy) / s
142
- verts[j + 2] = (verts[j + 2] - midz) / s
139
+ self.normalize_verts(verts)
143
140
 
144
141
  conn = self.conn_tris
145
142
  normals = self.normals
@@ -242,6 +239,141 @@ class Part(object):
242
239
 
243
240
  return command, verts, conn, normals, tcoords, var_cmd
244
241
 
242
+ def normalize_verts(self, verts: numpy.ndarray):
243
+ """
244
+ This function scales and translates vertices, so the longest axis in the scene is of
245
+ length 1.0, and data is centered at the origin
246
+
247
+ Returns the scale factor
248
+ """
249
+ s = 1.0
250
+ if self.session.normalize_geometry and self.session.scene_bounds is not None:
251
+ num_verts = int(verts.size / 3)
252
+ midx = (self.session.scene_bounds[3] + self.session.scene_bounds[0]) * 0.5
253
+ midy = (self.session.scene_bounds[4] + self.session.scene_bounds[1]) * 0.5
254
+ midz = (self.session.scene_bounds[5] + self.session.scene_bounds[2]) * 0.5
255
+ dx = self.session.scene_bounds[3] - self.session.scene_bounds[0]
256
+ dy = self.session.scene_bounds[4] - self.session.scene_bounds[1]
257
+ dz = self.session.scene_bounds[5] - self.session.scene_bounds[2]
258
+ s = dx
259
+ if dy > s:
260
+ s = dy
261
+ if dz > s:
262
+ s = dz
263
+ if s == 0:
264
+ s = 1.0
265
+ for i in range(num_verts):
266
+ j = i * 3
267
+ verts[j + 0] = (verts[j + 0] - midx) / s
268
+ verts[j + 1] = (verts[j + 1] - midy) / s
269
+ verts[j + 2] = (verts[j + 2] - midz) / s
270
+ return 1.0 / s
271
+
272
+ def point_rep(self):
273
+ """
274
+ This function processes the geometry arrays and returns values to represent point data
275
+
276
+ Returns
277
+ -------
278
+ On failure, the method returns None for the first return value. The returned tuple is:
279
+
280
+ (part_command, vertices, sizes, colors, var_command)
281
+
282
+ part_command: UPDATE_PART command object
283
+ vertices: numpy array of per-node coordinates
284
+ sizes: numpy array of per-node radii
285
+ colors: numpy array of per-node rgb colors
286
+ var_command: UPDATE_VARIABLE command object for the variable the colors correspond to, if any
287
+ """
288
+ if self.cmd is None:
289
+ return None, None, None, None, None
290
+ if self.cmd.render != self.cmd.NODES:
291
+ # Early out. Rendering type for this object is a surface rep, not a point rep
292
+ return None, None, None, None, None
293
+ verts = self.coords
294
+ num_verts = int(verts.size / 3)
295
+ norm_scale = self.normalize_verts(verts)
296
+
297
+ # Convert var values in self.tcoords to RGB colors
298
+ # For now, look up RGB colors. Planned USD enhancements should allow tex coords instead.
299
+ colors = None
300
+ var_cmd = None
301
+
302
+ if self.tcoords.size and self.tcoords.size == num_verts:
303
+ var_dsg_id = self.cmd.color_variableid
304
+ var_cmd = self.session.variables[var_dsg_id]
305
+ if len(var_cmd.levels) == 0:
306
+ self.session.log(
307
+ f"Note: Node rep not created for part '{self.cmd.name}'. It has var values, but a palette with 0 levels."
308
+ )
309
+ return None, None, None, None, None
310
+
311
+ p_min = None
312
+ p_max = None
313
+ for lvl in var_cmd.levels:
314
+ if (p_min is None) or (p_min > lvl.value):
315
+ p_min = lvl.value
316
+ if (p_max is None) or (p_max < lvl.value):
317
+ p_max = lvl.value
318
+
319
+ num_texels = int(len(var_cmd.texture) / 4)
320
+
321
+ colors = numpy.ndarray((num_verts * 3,), dtype="float32")
322
+ low_color = [c / 255.0 for c in var_cmd.texture[0:3]]
323
+ high_color = [
324
+ c / 255.0 for c in var_cmd.texture[4 * (num_texels - 1) : 4 * (num_texels - 1) + 3]
325
+ ]
326
+ if p_min == p_max:
327
+ # Special case where palette min == palette max
328
+ mid_color = var_cmd[4 * (num_texels // 2) : 4 * (num_texels // 2) + 3]
329
+ for idx in range(num_verts):
330
+ val = self.tcoords[idx]
331
+ if val == p_min:
332
+ colors[idx * 3 : idx * 3 + 3] = mid_color
333
+ elif val < p_min:
334
+ colors[idx * 3 : idx * 3 + 3] = low_color
335
+ elif val > p_min:
336
+ colors[idx * 3 : idx * 3 + 3] = high_color
337
+ else:
338
+ for idx in range(num_verts):
339
+ val = self.tcoords[idx]
340
+ if val <= p_min:
341
+ colors[idx * 3 : idx * 3 + 3] = low_color
342
+ else:
343
+ pal_pos = (num_texels - 1) * (val - p_min) / (p_max - p_min)
344
+ pal_idx, pal_sub = divmod(pal_pos, 1)
345
+ pal_idx = int(pal_idx)
346
+
347
+ if pal_idx >= num_texels - 1:
348
+ colors[idx * 3 : idx * 3 + 3] = high_color
349
+ else:
350
+ col0 = var_cmd.texture[pal_idx * 4 : pal_idx * 4 + 3]
351
+ col1 = var_cmd.texture[4 + pal_idx * 4 : 4 + pal_idx * 4 + 3]
352
+ for ii in range(0, 3):
353
+ colors[idx * 3 + ii] = (
354
+ col0[ii] * pal_sub + col1[ii] * (1.0 - pal_sub)
355
+ ) / 255.0
356
+ self.session.log(f"Part '{self.cmd.name}' defined: {self.coords.size/3} points.")
357
+
358
+ node_sizes = None
359
+ if self.node_sizes.size and self.node_sizes.size == num_verts:
360
+ # Pass out the node sizes if there is a size-by variable
361
+ node_size_default = self.cmd.node_size_default * norm_scale
362
+ node_sizes = numpy.ndarray((num_verts,), dtype="float32")
363
+ for ii in range(0, num_verts):
364
+ node_sizes[ii] = self.node_sizes[ii] * node_size_default
365
+ elif norm_scale != 1.0:
366
+ # Pass out the node sizes if the model is normalized to fit in a unit cube
367
+ node_size_default = self.cmd.node_size_default * norm_scale
368
+ node_sizes = numpy.ndarray((num_verts,), dtype="float32")
369
+ for ii in range(0, num_verts):
370
+ node_sizes[ii] = node_size_default
371
+
372
+ self.session.log(f"Part '{self.cmd.name}' defined: {self.coords.size/3} points.")
373
+ command = self.cmd
374
+
375
+ return command, verts, node_sizes, colors, var_cmd
376
+
245
377
 
246
378
  class UpdateHandler(object):
247
379
  """
@@ -410,6 +542,9 @@ class DSGSession(object):
410
542
  self._scene_bounds: Optional[List] = None
411
543
  self._cur_timeline: List = [0.0, 0.0] # Start/End time for current update
412
544
  self._callback_handler.session = self
545
+ # log any status changes to this file. external apps will be monitoring
546
+ self._status_file = os.environ.get("ANSYS_OV_SERVER_STATUS_FILENAME", "")
547
+ self._status = dict(status="idle", start_time=0.0, processed_buffers=0, total_buffers=0)
413
548
 
414
549
  @property
415
550
  def scene_bounds(self) -> Optional[List]:
@@ -474,6 +609,15 @@ class DSGSession(object):
474
609
  if level < self._verbose:
475
610
  logging.info(s)
476
611
 
612
+ @staticmethod
613
+ def warn(s: str) -> None:
614
+ """Issue a warning to the logging system
615
+
616
+ The logging message is mapped to "warn" and cannot be blocked via verbosity
617
+ checks.
618
+ """
619
+ logging.warning(s)
620
+
477
621
  def start(self) -> int:
478
622
  """Start a gRPC connection to an EnSight instance
479
623
 
@@ -518,6 +662,38 @@ class DSGSession(object):
518
662
  """Check the service shutdown request status"""
519
663
  return self._shutdown
520
664
 
665
+ def _update_status_file(self, timed: bool = False):
666
+ """
667
+ Update the status file contents. The status file will contain the
668
+ following json object, stored as: self._status
669
+
670
+ {
671
+ 'status' : "working|idle",
672
+ 'start_time' : timestamp_of_update_begin,
673
+ 'processed_buffers' : number_of_protobuffers_processed,
674
+ 'total_buffers' : number_of_protobuffers_total,
675
+ }
676
+
677
+ Parameters
678
+ ----------
679
+ timed : bool, optional:
680
+ if True, only update every second.
681
+
682
+ """
683
+ if self._status_file:
684
+ current_time = time.time()
685
+ if timed:
686
+ last_time = self._status.get("last_time", 0.0)
687
+ if current_time - last_time < 1.0: # type: ignore
688
+ return
689
+ self._status["last_time"] = current_time
690
+ try:
691
+ message = json.dumps(self._status)
692
+ with open(self._status_file, "w") as status_file:
693
+ status_file.write(message)
694
+ except IOError:
695
+ pass # Note failure is expected here in some cases
696
+
521
697
  def request_an_update(self, animation: bool = False, allow_spontaneous: bool = True) -> None:
522
698
  """Start a DSG update
523
699
  Send a command to the DSG protocol to "init" an update.
@@ -590,12 +766,21 @@ class DSGSession(object):
590
766
  self._mesh_block_count = 0 # reset when a new group shows up
591
767
  self._callback_handler.begin_update()
592
768
 
769
+ # Update our status
770
+ self._status = dict(
771
+ status="working", start_time=time.time(), processed_buffers=1, total_buffers=1
772
+ )
773
+ self._update_status_file()
774
+
593
775
  # handle the various commands until UPDATE_SCENE_END
594
776
  cmd = self._get_next_message()
595
777
  while (cmd is not None) and (
596
778
  cmd.command_type != dynamic_scene_graph_pb2.SceneUpdateCommand.UPDATE_SCENE_END
597
779
  ):
598
780
  self._handle_update_command(cmd)
781
+ self._status["processed_buffers"] += 1 # type: ignore
782
+ self._status["total_buffers"] = self._status["processed_buffers"] + self._message_queue.qsize() # type: ignore
783
+ self._update_status_file(timed=True)
599
784
  cmd = self._get_next_message()
600
785
 
601
786
  # Flush the last part
@@ -603,6 +788,10 @@ class DSGSession(object):
603
788
 
604
789
  self._callback_handler.end_update()
605
790
 
791
+ # Update our status
792
+ self._status = dict(status="idle", start_time=0.0, processed_buffers=0, total_buffers=0)
793
+ self._update_status_file()
794
+
606
795
  def _handle_update_command(self, cmd: dynamic_scene_graph_pb2.SceneUpdateCommand) -> None:
607
796
  """Dispatch out a scene update command to the proper handler
608
797
 
@@ -647,10 +836,13 @@ class DSGSession(object):
647
836
  There is always a part being modified. This method completes the current part, committing
648
837
  it to the handler.
649
838
  """
650
- self._callback_handler.finalize_part(self.part)
839
+ try:
840
+ self._callback_handler.finalize_part(self.part)
841
+ except Exception as e:
842
+ self.warn(f"Error encountered while finalizing part geometry: {str(e)}")
651
843
  self._mesh_block_count += 1
652
844
 
653
- def _handle_part(self, part: Any) -> None:
845
+ def _handle_part(self, part_cmd: Any) -> None:
654
846
  """Handle a DSG UPDATE_PART command
655
847
 
656
848
  Finish the current part and set up the next part.
@@ -661,7 +853,7 @@ class DSGSession(object):
661
853
  The command coming from the EnSight stream.
662
854
  """
663
855
  self._finish_part()
664
- self._part.reset(part)
856
+ self._part.reset(part_cmd)
665
857
 
666
858
  def _handle_group(self, group: Any) -> None:
667
859
  """Handle a DSG UPDATE_GROUP command