ansys-pyensight-core 0.8.10__py3-none-any.whl → 0.8.12__py3-none-any.whl

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.

@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <link rel="stylesheet" type="text/css" href="/bootstrap.min.cssOPTIONAL_QUERY"/>
5
5
 
6
- <script src='/jquery-3.4.1.min.jsOPTIONAL_QUERY'></script>
6
+ <script src='/jqueryJQUERY_VERSION.min.jsOPTIONAL_QUERY'></script>
7
7
  <script src='/geotiff.jsOPTIONAL_QUERY'></script>
8
8
  <script src='/geotiff_nexus.jsOPTIONAL_QUERY'></script>
9
9
  <script src="/bootstrap.min.jsOPTIONAL_QUERY"></script>
@@ -76,15 +76,15 @@
76
76
  <div style="margin: 0 auto; display:flex; justify-content:center;">
77
77
  <!-- tooltip parent for img --->
78
78
  <div id="probe_display_ITEMID"
79
- data-toggle="tooltip"
80
- data-placement="bottom"
81
- data-fallbackPlacement="['top', 'right']"
82
- data-html="true"
83
- data-container="body"
84
- data-boundary="viewport"
85
- data-animation="false"
86
- data-trigger="manual"
87
- data-title='<span class="f-1r">
79
+ data-BS_PREFIXtoggle="tooltip"
80
+ data-BS_PREFIXplacement="bottom"
81
+ data-BS_PREFIXfallbackPlacement="['top', 'right']"
82
+ data-BS_PREFIXhtml="true"
83
+ data-BS_PREFIXcontainer="body"
84
+ data-BS_PREFIXboundary="viewport"
85
+ data-BS_PREFIXanimation="false"
86
+ data-BS_PREFIXtrigger="manual"
87
+ data-BS_PREFIXtitle='<span class="f-1r">
88
88
  <span>X, Y : <span id="probe_xy_ITEMID">0, 0</span></span>
89
89
  <br>
90
90
  <span id="probe_result_ITEMID"></span>
@@ -63,6 +63,22 @@ warnings.warn(
63
63
  )
64
64
 
65
65
 
66
+ def _build_enum(name: str, pb_enum: Any, flag: bool = False) -> Union[enum.IntEnum, enum.IntFlag]:
67
+ values = {}
68
+ for v in pb_enum:
69
+ values[v[0]] = v[1]
70
+ if flag:
71
+ return enum.IntFlag(name, values)
72
+ return enum.IntEnum(name, values)
73
+
74
+
75
+ ErrorCodes = _build_enum("ErrorCodes", libuserd_pb2.ErrorCodes.items())
76
+ ElementType = _build_enum("ElementType", libuserd_pb2.ElementType.items())
77
+ VariableLocation = _build_enum("VariableLocation", libuserd_pb2.VariableLocation.items())
78
+ VariableType = _build_enum("VariableType", libuserd_pb2.VariableType.items())
79
+ PartHints = _build_enum("PartHints", libuserd_pb2.PartHints.items(), flag=True)
80
+
81
+
66
82
  class LibUserdError(Exception):
67
83
  """
68
84
  This class is an exception object raised from the libuserd
@@ -179,9 +195,9 @@ class Variable(object):
179
195
  The unit label of this variable, "Pa" for example.
180
196
  unitDims : str
181
197
  The dimensions of this variable, "L/S" for distance per second.
182
- location : "LibUserd.LocationType"
198
+ location : "VariableLocation"
183
199
  The location of this variable.
184
- type : "LibUserd.VariableType"
200
+ type : "VariableType"
185
201
  The type of this variable.
186
202
  timeVarying : bool
187
203
  True if the variable is time-varying.
@@ -200,8 +216,8 @@ class Variable(object):
200
216
  self.name = pb.name
201
217
  self.unitLabel = pb.unitLabel
202
218
  self.unitDims = pb.unitDims
203
- self.location = self._userd.VariableLocation(pb.varLocation)
204
- self.type = self._userd.VariableType(pb.type)
219
+ self.location = VariableLocation(pb.varLocation) # type: ignore
220
+ self.type = VariableType(pb.type) # type: ignore
205
221
  self.timeVarying = pb.timeVarying
206
222
  self.isComplex = pb.isComplex
207
223
  self.interleaveFlag = pb.interleaveFlag
@@ -240,7 +256,7 @@ class Part(object):
240
256
  reader_id : int
241
257
  The id of the Reader this part is associated with.
242
258
  hints : int
243
- See: `LibUserd.PartHints`.
259
+ See: `PartHints`.
244
260
  reader_api_version : float
245
261
  The API version number of the USERD reader this part was read with.
246
262
  metadata : Dict[str, str]
@@ -315,7 +331,7 @@ class Part(object):
315
331
  >>> part = reader.parts()[0]
316
332
  >>> elements = part.elements()
317
333
  >>> for etype, count in elements.items():
318
- ... print(libuserd_instance.ElementType(etype).name, count)
334
+ ... print(libuserd.ElementType(etype).name, count)
319
335
 
320
336
  """
321
337
  self._userd.connect_check()
@@ -350,14 +366,14 @@ class Part(object):
350
366
  --------
351
367
 
352
368
  >>> part = reader.parts()[0]
353
- >>> conn = part.element_conn(libuserd_instance.ElementType.HEX08)
354
- >>> nodes_per_elem = libuserd_instance.nodes_per_element(libuserd_instance.ElementType.HEX08)
369
+ >>> conn = part.element_conn(libuserd.ElementType.HEX08)
370
+ >>> nodes_per_elem = libuserd_instance.nodes_per_element(libuserd.ElementType.HEX08)
355
371
  >>> conn.shape = (len(conn)//nodes_per_elem, nodes_per_elem)
356
372
  >>> for element in conn:
357
373
  ... print(element)
358
374
 
359
375
  """
360
- if elem_type >= self._userd.ElementType.NSIDED:
376
+ if elem_type >= ElementType.NSIDED: # type: ignore
361
377
  raise RuntimeError(f"Element type {elem_type} is not valid for this call")
362
378
  pb = libuserd_pb2.Part_element_connRequest()
363
379
  pb.part_id = self.id
@@ -375,7 +391,7 @@ class Part(object):
375
391
  error = self._userd.libuserd_exception(e)
376
392
  # if we get an "UNKNOWN" error, then return an empty array
377
393
  if isinstance(error, LibUserdError):
378
- if error.code == self._userd.ErrorCodes.UNKNOWN: # type: ignore
394
+ if error.code == ErrorCodes.UNKNOWN: # type: ignore
379
395
  return numpy.empty(0, dtype=numpy.uint32)
380
396
  raise error
381
397
  return conn
@@ -601,6 +617,79 @@ class Reader(object):
601
617
  for key in pb.metadata.keys():
602
618
  self.metadata[key] = pb.metadata[key]
603
619
  self.raw_metadata = pb.raw_metadata
620
+ self._timesets: List["numpy.array"] = []
621
+ self._update_timesets()
622
+
623
+ def _update_timesets(self) -> None:
624
+ """
625
+ To simplify the interface to time, the timesets are all queried and
626
+ cached. Additionally, a "common timeset" is generated that combines
627
+ all the timevalues from all timesets into a single timeset which is
628
+ saved as "timeset 0".
629
+
630
+ This method reads all the timesets and generates the common timeset.
631
+ """
632
+ if len(self._timesets):
633
+ return
634
+ num_timesets = self.get_number_of_time_sets()
635
+ # The common timeset
636
+ common = set()
637
+ self._timesets = [numpy.array([], dtype="float32")]
638
+ # The other timesets
639
+ for ts in range(1, num_timesets + 1):
640
+ v = self.timevalues(timeset=ts)
641
+ # merge into the common timeset
642
+ common.update(v)
643
+ self._timesets.append(v)
644
+ self._timesets[0] = numpy.array(sorted(list(common)))
645
+
646
+ def _common_set_step(self, s: int) -> None:
647
+ """
648
+ When the common timeset is used in a ``set_timestep()`` call, this
649
+ method selects the time value from the common timeset and then calls
650
+ ``_common_set_time()`` to change the current simulation time.
651
+
652
+ Parameters
653
+ ----------
654
+ s : int
655
+ The index (timestep) in the common timeset to change the current time to.
656
+
657
+ Raises
658
+ ------
659
+ RuntimeError
660
+ If the timestep index is invalid.
661
+
662
+ """
663
+ try:
664
+ v = self._timesets[0][s]
665
+ self._common_set_time(v)
666
+ except IndexError:
667
+ raise RuntimeError(f"Invalid step number {s}.") from None
668
+
669
+ def _common_set_time(self, t: float) -> None:
670
+ """
671
+ Change the current time value to the passed time value. This method
672
+ walks all the timesets. It selects the largest time value in each timeset
673
+ that is less than or equal to the specified time value. It then sets
674
+ the time value for each timeset accordingly.
675
+
676
+ Parameters
677
+ ----------
678
+ t : float
679
+ The time value (in the common timeset) to change the reader simulation time to.
680
+
681
+ """
682
+ # given the time float from the common timeline,
683
+ # change the timestep in all the timesets to match.
684
+ for timeset in range(1, len(self._timesets)):
685
+ # check for perfect match first (avoids rounding)
686
+ where = numpy.where(self._timesets[timeset] == t)
687
+ if len(where[0]):
688
+ timestep = where[0][0]
689
+ else:
690
+ timestep = numpy.searchsorted(self._timesets[timeset], t)
691
+ timestep = min(timestep, len(self._timesets[timeset]) - 1)
692
+ self.set_timestep(timestep, timeset=timeset)
604
693
 
605
694
  def parts(self) -> List[Part]:
606
695
  """
@@ -671,6 +760,8 @@ class Reader(object):
671
760
  int
672
761
  The number of timesets.
673
762
  """
763
+ if len(self._timesets):
764
+ return len(self._timesets) - 1
674
765
  self._userd.connect_check()
675
766
  pb = libuserd_pb2.Reader_get_number_of_time_setsRequest()
676
767
  try:
@@ -681,20 +772,27 @@ class Reader(object):
681
772
  raise self._userd.libuserd_exception(e)
682
773
  return reply.numberOfTimeSets
683
774
 
684
- def timevalues(self, timeset: int = 1) -> List[float]:
775
+ def timevalues(self, timeset: int = 0) -> List[float]:
685
776
  """
686
- Get a list of the time step values in this dataset.
777
+ Get a list of the time step values in this dataset for the specified timeset.
778
+ The default timeset is ``0`` which is a special, "common" timeset formed by
779
+ merging all the timesets in the data into a single timeset.
687
780
 
688
781
  Parameters
689
782
  ----------
690
783
  timeset : int, optional
691
- The timestep to query (default is 1)
784
+ The timestep to query (default is 0)
692
785
 
693
786
  Returns
694
787
  -------
695
- List[float]
696
- A list of time floats.
788
+ numpy.array
789
+ The simulation time value floats.
697
790
  """
791
+ if timeset == 0:
792
+ try:
793
+ return self._timesets[timeset]
794
+ except IndexError:
795
+ return numpy.array([], dtype="float32")
698
796
  self._userd.connect_check()
699
797
  pb = libuserd_pb2.Reader_timevaluesRequest()
700
798
  pb.timeSetNumber = timeset
@@ -704,18 +802,43 @@ class Reader(object):
704
802
  raise self._userd.libuserd_exception(e)
705
803
  return numpy.array(timevalues.timeValues)
706
804
 
707
- def set_timevalue(self, timevalue: float, timeset: int = 1) -> None:
805
+ def set_timevalue(self, timevalue: float, timeset: int = 0) -> None:
708
806
  """
709
- Change the current time to the specified value. This value should ideally
710
- be on of the values returned by `timevalues`
807
+ Change the current time within the selected timeset to the specified value.
808
+ The default timeset selected is the merged "common" timeset ``0``. If the
809
+ "common" timeset is used, the appropriate time value will be set for
810
+ all timesets by this method.
711
811
 
712
812
  Parameters
713
813
  ----------
714
814
  timevalue : float
715
815
  The time value to change the timestep closest to.
716
816
  timeset : int, optional
717
- The timestep to query (default is 1)
718
- """
817
+ The timeset to change (default is 0)
818
+
819
+ Examples
820
+ --------
821
+ >>> from ansys.pyensight.core import libuserd
822
+ >>> import numpy
823
+ >>> s = libuserd.LibUserd()
824
+ >>> s.initialize()
825
+ >>> opt = {'Long names': 0, 'Number of timesteps': 5, 'Number of scalars': 3,
826
+ ... 'Number of spheres': 2, 'Number of cubes': 2}
827
+ >>> d = s.load_data("foo", file_format="Synthetic", reader_options=opt)
828
+ >>> parts = d.parts()
829
+ >>> for t in d.timevalues():
830
+ ... d.set_timevalue(t)
831
+ ... for p in parts:
832
+ ... nodes = p.nodes()
833
+ ... nodes.shape = (len(nodes)//3, 3)
834
+ ... centroid = numpy.average(nodes, 0)
835
+ ... print(f"Time: {t} Part: {p.name} Centroid: {centroid}")
836
+ >>> s.shutdown()
837
+
838
+ """
839
+ if timeset == 0:
840
+ self._common_set_time(timevalue)
841
+ return
719
842
  self._userd.connect_check()
720
843
  pb = libuserd_pb2.Reader_set_timevalueRequest()
721
844
  pb.timesetNumber = timeset
@@ -725,18 +848,24 @@ class Reader(object):
725
848
  except grpc.RpcError as e:
726
849
  raise self._userd.libuserd_exception(e)
727
850
 
728
- def set_timestep(self, timestep: int, timeset: int = 1) -> None:
851
+ def set_timestep(self, timestep: int, timeset: int = 0) -> None:
729
852
  """
730
853
  Change the current time to the specified timestep. This call is the same as:
731
854
  ``reader.set_timevalue(reader.timevalues()[timestep])``.
855
+ The default timeset selected is the merged "common" timeset ``0``. If the
856
+ "common" timeset is used, the appropriate time value will be set for
857
+ all timesets by this method.
732
858
 
733
859
  Parameters
734
860
  ----------
735
861
  timestep : int
736
862
  The timestep to change to.
737
863
  timeset : int, optional
738
- The timestep to query (default is 1)
864
+ The timeset to change (default is 0)
739
865
  """
866
+ if timeset == 0:
867
+ self._common_set_step(timestep)
868
+ return
740
869
  self._userd.connect_check()
741
870
  pb = libuserd_pb2.Reader_set_timestepRequest()
742
871
  pb.timeSetNumber = timeset
@@ -1034,27 +1163,12 @@ class LibUserd(object):
1034
1163
  raise RuntimeError(f"Unable to start the gRPC server ({str(self.server_pathname)})")
1035
1164
 
1036
1165
  def _build_enums(self) -> None:
1037
- """Build the enums values."""
1038
- values = {}
1039
- for v in libuserd_pb2.ErrorCodes.items():
1040
- values[v[0]] = v[1]
1041
- self.ErrorCodes = enum.IntEnum("ErrorCodes", values) # type: ignore
1042
- values = {}
1043
- for v in libuserd_pb2.ElementType.items():
1044
- values[v[0]] = v[1]
1045
- self.ElementType = enum.IntEnum("ElementType", values) # type: ignore
1046
- values = {}
1047
- for v in libuserd_pb2.VariableLocation.items():
1048
- values[v[0]] = v[1]
1049
- self.VariableLocation = enum.IntEnum("VariableLocation", values) # type: ignore
1050
- values = {}
1051
- for v in libuserd_pb2.VariableType.items():
1052
- values[v[0]] = v[1]
1053
- self.VariableType = enum.IntEnum("VariableType", values) # type: ignore
1054
- values = {}
1055
- for v in libuserd_pb2.PartHints.items():
1056
- values[v[0]] = v[1]
1057
- self.PartHints = enum.IntEnum("PartHints", values) # type: ignore
1166
+ # retained for backward compatibility
1167
+ self.ErrorCodes = ErrorCodes
1168
+ self.ElementType = ElementType
1169
+ self.VariableLocation = VariableLocation
1170
+ self.VariableType = VariableType
1171
+ self.PartHints = PartHints
1058
1172
 
1059
1173
  def _pull_docker_image(self) -> None:
1060
1174
  """Pull the docker image if not available"""
@@ -1216,6 +1330,13 @@ class LibUserd(object):
1216
1330
  # ways, we'll add that one too, just in case.
1217
1331
  dirs_to_check.append(os.path.join(env_inst, "CEI"))
1218
1332
 
1333
+ try:
1334
+ import enve
1335
+
1336
+ dirs_to_check.append(enve.home())
1337
+ except ModuleNotFoundError:
1338
+ pass
1339
+
1219
1340
  if "CEI_HOME" in os.environ:
1220
1341
  env_inst = os.environ["CEI_HOME"]
1221
1342
  dirs_to_check.append(env_inst)
@@ -1464,7 +1585,7 @@ class LibUserd(object):
1464
1585
  Parameters
1465
1586
  ----------
1466
1587
  element_type
1467
- The element type: LibUserd.ElementType enum value
1588
+ The element type: ElementType enum value
1468
1589
 
1469
1590
  Returns
1470
1591
  -------
@@ -1489,7 +1610,7 @@ class LibUserd(object):
1489
1610
  Parameters
1490
1611
  ----------
1491
1612
  element_type
1492
- The element type: LibUserd.ElementType enum value
1613
+ The element type: ElementType enum value
1493
1614
 
1494
1615
  Returns
1495
1616
  -------
@@ -1512,7 +1633,7 @@ class LibUserd(object):
1512
1633
  Parameters
1513
1634
  ----------
1514
1635
  element_type
1515
- The element type: LibUserd.ElementType enum value
1636
+ The element type: ElementType enum value
1516
1637
 
1517
1638
  Returns
1518
1639
  -------
@@ -1535,7 +1656,7 @@ class LibUserd(object):
1535
1656
  Parameters
1536
1657
  ----------
1537
1658
  element_type
1538
- The element type: LibUserd.ElementType enum value
1659
+ The element type: ElementType enum value
1539
1660
 
1540
1661
  Returns
1541
1662
  -------
@@ -1558,7 +1679,7 @@ class LibUserd(object):
1558
1679
  Parameters
1559
1680
  ----------
1560
1681
  element_type
1561
- The element type: LibUserd.ElementType enum value
1682
+ The element type: ElementType enum value
1562
1683
 
1563
1684
  Returns
1564
1685
  -------
@@ -1580,7 +1701,7 @@ class LibUserd(object):
1580
1701
  Part.element_conn() method. This function returns the number of those elements
1581
1702
  and may be useful in common element type handling code.
1582
1703
 
1583
- Note: The value is effectively int(LibUserd.ElementType.NSIDED).
1704
+ Note: The value is effectively int(ElementType.NSIDED).
1584
1705
 
1585
1706
  Returns
1586
1707
  -------
@@ -1733,7 +1854,7 @@ class LibUserd(object):
1733
1854
  try:
1734
1855
  output = the_reader.read_dataset(data_file, result_file)
1735
1856
  except Exception:
1736
- raise RuntimeError("Unable to open the specified dataset.")
1857
+ raise RuntimeError("Unable to open the specified dataset.") from None
1737
1858
 
1738
1859
  return output
1739
1860
 
@@ -314,6 +314,8 @@ class LocalLauncher(Launcher):
314
314
  return
315
315
  except PermissionError:
316
316
  pass
317
+ except FileNotFoundError:
318
+ pass
317
319
  except Exception:
318
320
  raise
319
321
  raise RuntimeError(f"Unable to remove {self.session_directory} in {maximum_wait_secs}s")
@@ -389,10 +389,17 @@ class RenderableDeepPixel(Renderable):
389
389
  for script in ["geotiff.js", "geotiff_nexus.js", "bootstrap.min.js"]:
390
390
  name = base_name + f"'{script}')"
391
391
  cmd += f'shutil.copy({name}, r"""{self._session.launcher.session_directory}""")\n'
392
+ name = "os.path.join(enve.home(), f'nexus{ceiversion.nexus_suffix}', 'django', "
393
+ name += "'website', 'static', 'website', 'content', 'bootstrap.min.css')"
394
+ cmd += f'shutil.copy({name}, r"""{self._session.launcher.session_directory}""")\n'
395
+ self._session.cmd(cmd, do_eval=False)
396
+ # With Bootstrap 5 (2025 R1), class names have '-bs-' in them, e.g. 'data-bs-toggle' vs 'data-toggle'
397
+ bs_prefix = "bs-"
398
+ jquery_version = ""
392
399
  if int(self._session._cei_suffix) < 251:
393
- jquery = "jquery-3.4.1.min.js"
394
- else:
395
- jquery = "jquery.min.js"
400
+ bs_prefix = ""
401
+ jquery_version = "-3.4.1"
402
+ jquery = f"jquery{jquery_version}.min.js"
396
403
  cmd = "import shutil, enve, ceiversion, os.path\n"
397
404
  name = base_name + f"'{jquery}')"
398
405
  cmd += "try:"
@@ -409,6 +416,8 @@ class RenderableDeepPixel(Renderable):
409
416
  html = html.replace("TIFF_URL", tiff_url)
410
417
  html = html.replace("ITEMID", self._guid)
411
418
  html = html.replace("OPTIONAL_QUERY", optional_query)
419
+ html = html.replace("JQUERY_VERSION", jquery_version)
420
+ html = html.replace("BS_PREFIX", bs_prefix)
412
421
  # refresh the remote HTML
413
422
  self._save_remote_html_page(html)
414
423
  super().update()
@@ -101,6 +101,12 @@ class Part(object):
101
101
  self.tcoords[
102
102
  cmd.chunk_offset : cmd.chunk_offset + len(cmd.flt_array)
103
103
  ] = cmd.flt_array
104
+
105
+ # Add the variable hash to the Part's hash, to pick up palette changes
106
+ var_cmd = self.session.variables.get(cmd.variable_id, None)
107
+ if var_cmd is not None:
108
+ self.hash.update(var_cmd.hash.encode("utf-8"))
109
+
104
110
  if self.cmd.node_size_variableid == cmd.variable_id: # type: ignore
105
111
  # Receive the node size var values
106
112
  if self.node_sizes.size != cmd.total_array_size:
@@ -618,6 +624,15 @@ class DSGSession(object):
618
624
  """
619
625
  logging.warning(s)
620
626
 
627
+ @staticmethod
628
+ def error(s: str) -> None:
629
+ """Issue an error to the logging system
630
+
631
+ The logging message is mapped to "error" and cannot be blocked via verbosity
632
+ checks.
633
+ """
634
+ logging.error(s)
635
+
621
636
  def start(self) -> int:
622
637
  """Start a gRPC connection to an EnSight instance
623
638
 
@@ -741,6 +756,13 @@ class DSGSession(object):
741
756
  except queue.Empty:
742
757
  return None
743
758
 
759
+ def _reset(self):
760
+ self._variables = {}
761
+ self._groups = {}
762
+ self._part = Part(self)
763
+ self._scene_bounds = None
764
+ self._mesh_block_count = 0 # reset when a new group shows up
765
+
744
766
  def handle_one_update(self) -> None:
745
767
  """Monitor the DSG stream and handle a single update operation
746
768
 
@@ -759,11 +781,7 @@ class DSGSession(object):
759
781
  cmd = self._get_next_message()
760
782
 
761
783
  # Start anew
762
- self._variables = {}
763
- self._groups = {}
764
- self._part = Part(self)
765
- self._scene_bounds = None
766
- self._mesh_block_count = 0 # reset when a new group shows up
784
+ self._reset()
767
785
  self._callback_handler.begin_update()
768
786
 
769
787
  # Update our status
@@ -839,7 +857,11 @@ class DSGSession(object):
839
857
  try:
840
858
  self._callback_handler.finalize_part(self.part)
841
859
  except Exception as e:
860
+ import traceback
861
+
842
862
  self.warn(f"Error encountered while finalizing part geometry: {str(e)}")
863
+ traceback_str = "".join(traceback.format_tb(e.__traceback__))
864
+ logging.debug(f"Traceback: {traceback_str}")
843
865
  self._mesh_block_count += 1
844
866
 
845
867
  def _handle_part(self, part_cmd: Any) -> None:
@@ -44,7 +44,7 @@ class Omniverse:
44
44
  >>> from ansys.pyensight.core import LocalLauncher
45
45
  >>> session = LocalLauncher().start()
46
46
  >>> ov = session.ensight.utils.omniverse
47
- >>> ov.create_connection()
47
+ >>> ov.create_connection(r"D:\Omniverse\Example")
48
48
  >>> ov.update()
49
49
  >>> ov.close_connection()
50
50
 
@@ -202,8 +202,8 @@ class Omniverse:
202
202
  Parameters
203
203
  ----------
204
204
  omniverse_path : str
205
- The URI to the Omniverse server. It will look like this:
206
- "omniverse://localhost/Users/test"
205
+ The directory name where the USD files should be saved. For example:
206
+ "C:/Users/test/OV/usdfiles"
207
207
  include_camera : bool
208
208
  If True, apply the EnSight camera to the Omniverse scene. This option
209
209
  should be used if the target viewer is in AR/VR mode. Defaults to False.
@@ -276,17 +276,16 @@ class OmniverseGeometryServer(object):
276
276
  update_handler = ov_dsg_server.OmniverseUpdateHandler(omni_link)
277
277
 
278
278
  # Link it to the GLB file monitoring service
279
- glb_link = ov_glb_server.GLBSession(
280
- verbose=1,
281
- handler=update_handler,
282
- )
279
+ glb_link = ov_glb_server.GLBSession(verbose=1, handler=update_handler, vrmode=self.vrmode)
283
280
  if single_file_upload:
284
281
  start_time = time.time()
285
282
  logging.info(f"Uploading file: {the_dir}.")
286
283
  try:
284
+ glb_link.start_uploads([0.0, 0.0])
287
285
  glb_link.upload_file(the_dir)
286
+ glb_link.end_uploads()
288
287
  except Exception as error:
289
- logging.warning(f"Error uploading file: {the_dir}: {error}")
288
+ logging.error(f"Unable to upload file: {the_dir}: {error}")
290
289
  logging.info(f"Uploaded in {(time.time() - start_time):.2f}")
291
290
  else:
292
291
  logging.info(f"Starting file monitoring for {the_dir}.")
@@ -297,7 +296,7 @@ class OmniverseGeometryServer(object):
297
296
  while not os.path.exists(stop_file):
298
297
  loop_time = time.time()
299
298
  files_to_remove = []
300
- for filename in glob.glob(os.path.join(the_dir, "*", "*.upload")):
299
+ for filename in glob.glob(os.path.join(the_dir, "*.upload")):
301
300
  # reset to the launch URI/directory
302
301
  omni_link.destination = orig_destination
303
302
  # Keep track of the files and time values
@@ -317,7 +316,7 @@ class OmniverseGeometryServer(object):
317
316
  with open(filename, "r") as fp:
318
317
  glb_info = json.load(fp)
319
318
  except Exception:
320
- logging.warning(f"Error reading file: {filename}")
319
+ logging.error(f"Unable to read file: {filename}")
321
320
  continue
322
321
  # if specified, set the URI/directory target
323
322
  omni_link.destination = glb_info.get("destination", orig_destination)
@@ -326,28 +325,35 @@ class OmniverseGeometryServer(object):
326
325
  files_to_remove.extend(the_files)
327
326
  # Times not used for now, but parse them anyway
328
327
  the_times = glb_info.get("times", [0.0] * len(the_files))
328
+ file_timestamps.extend(the_times)
329
329
  # Validate a few things
330
330
  if len(the_files) != len(the_times):
331
- logging.warning(
331
+ logging.error(
332
332
  f"Number of times and files are not the same in: {filename}"
333
333
  )
334
334
  continue
335
- if len(set(the_times)) != 1:
336
- logging.warning("Time values not currently supported.")
337
- if len(the_files) > 1:
338
- logging.warning("Multiple glb files not currently fully supported.")
339
335
  files_to_process.extend(the_files)
340
- # Upload the files
341
- for glb_file in files_to_process:
342
- start_time = time.time()
343
- logging.info(
344
- f"Uploading file: {glb_file} to {omni_link.destination}."
345
- )
346
- try:
347
- glb_link.upload_file(glb_file)
348
- except Exception as error:
349
- logging.warning(f"Error uploading file: {glb_file}: {error}")
350
- logging.info(f"Uploaded in {(time.time() - start_time):%.2f}")
336
+ # manage time
337
+ timeline = sorted(set(file_timestamps))
338
+ if len(timeline) != 1:
339
+ logging.warning("Time values not currently supported.")
340
+ if len(files_to_process) > 1:
341
+ logging.warning("Multiple glb files not currently fully supported.")
342
+ # Upload the files
343
+ glb_link.start_uploads([timeline[0], timeline[-1]])
344
+ for glb_file, timestamp in zip(files_to_process, file_timestamps):
345
+ start_time = time.time()
346
+ logging.info(f"Uploading file: {glb_file} to {omni_link.destination}.")
347
+ try:
348
+ time_idx = timeline.index(timestamp) + 1
349
+ if time_idx == len(timeline):
350
+ time_idx -= 1
351
+ limits = [timestamp, timeline[time_idx]]
352
+ glb_link.upload_file(glb_file, timeline=limits)
353
+ except Exception as error:
354
+ logging.error(f"Unable to upload file: {glb_file}: {error}")
355
+ logging.info(f"Uploaded in {(time.time() - start_time):.2f}s")
356
+ glb_link.end_uploads()
351
357
  for filename in files_to_remove:
352
358
  try:
353
359
  # Only delete the file if it is in the_dir_path
@@ -361,7 +367,10 @@ class OmniverseGeometryServer(object):
361
367
  except Exception as error:
362
368
  logging.error(f"Error encountered while monitoring: {error}")
363
369
  logging.info("Stopping file monitoring.")
364
- os.remove(stop_file)
370
+ try:
371
+ os.remove(stop_file)
372
+ except IOError:
373
+ logging.error("Unable to remove 'shutdown' file.")
365
374
 
366
375
  omni_link.shutdown()
367
376