ansys-speos-core 0.5.2__py3-none-any.whl → 0.6.1__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.
ansys/speos/core/body.py CHANGED
@@ -29,6 +29,8 @@ from typing import List, Mapping, Optional, Union
29
29
 
30
30
  from ansys.speos.core import proto_message_utils
31
31
  import ansys.speos.core.face as face
32
+ import ansys.speos.core.generic.general_methods as general_methods
33
+ from ansys.speos.core.generic.visualization_methods import _VisualData
32
34
  from ansys.speos.core.geo_ref import GeoRef
33
35
  from ansys.speos.core.kernel.body import ProtoBody
34
36
  from ansys.speos.core.kernel.client import SpeosClient
@@ -72,6 +74,7 @@ class Body:
72
74
  self._parent_part = parent_part
73
75
  self._name = name
74
76
  self.body_link = None
77
+ self._visual_data = _VisualData() if general_methods._GRAPHICS_AVAILABLE else None
75
78
  """Link object for the body in database."""
76
79
 
77
80
  if metadata is None:
@@ -82,6 +85,30 @@ class Body:
82
85
 
83
86
  self._geom_features = []
84
87
 
88
+ @property
89
+ def visual_data(self):
90
+ """Property containing irradiance sensor visualization data.
91
+
92
+ Returns
93
+ -------
94
+ VisualData
95
+ Instance of VisualData Class for pyvista.PolyData of feature faces, coordinate_systems.
96
+
97
+ """
98
+ import numpy as np
99
+
100
+ if self._visual_data.updated is True:
101
+ return self._visual_data
102
+ for feature_face in self._geom_features:
103
+ vertices = np.array(feature_face._face.vertices).reshape(-1, 3)
104
+ facets = np.array(feature_face._face.facets).reshape(-1, 3)
105
+ temp = np.full(facets.shape[0], 3)
106
+ temp = np.vstack(temp)
107
+ facets = np.hstack((temp, facets))
108
+ self._visual_data.add_data_mesh(vertices, facets)
109
+ self._visual_data.updated = True
110
+ return self._visual_data
111
+
85
112
  @property
86
113
  def geo_path(self) -> GeoRef:
87
114
  """Geometry path to be used within other speos objects."""
@@ -159,6 +186,9 @@ class Body:
159
186
  ansys.speos.core.body.Body
160
187
  Body feature.
161
188
  """
189
+ if general_methods._GRAPHICS_AVAILABLE:
190
+ self._visual_data.updated = False
191
+
162
192
  # Commit faces contained in this body
163
193
  for g in self._geom_features:
164
194
  g.commit()
@@ -28,7 +28,7 @@ DEFAULT_HOST: str = "localhost"
28
28
  """Default host used by Speos RPC server and client """
29
29
  DEFAULT_PORT: str = "50098"
30
30
  """Default port used by Speos RPC server and client """
31
- DEFAULT_VERSION: str = "251"
31
+ DEFAULT_VERSION: str = "252"
32
32
  """Latest supported Speos version of the current PySpeos Package"""
33
33
  MAX_SERVER_MESSAGE_LENGTH: int = int(os.environ.get("SPEOS_MAX_MESSAGE_LENGTH", 256 * 1024**2))
34
34
  """Maximum message length value accepted by the Speos RPC server,
@@ -25,6 +25,8 @@
25
25
  this includes decorator and methods
26
26
  """
27
27
 
28
+ from __future__ import annotations
29
+
28
30
  from collections.abc import Collection
29
31
  from functools import lru_cache, wraps
30
32
  import os
@@ -44,6 +46,8 @@ GRAPHICS_ERROR = (
44
46
  "You can install this using `pip install ansys-speos-core[graphics]`."
45
47
  )
46
48
 
49
+ VERSION_ERROR = "The pySpeos feature : {feature_name} needs a Speos Version of {version} or higher."
50
+
47
51
 
48
52
  def deprecate_kwargs(old_arguments: dict, removed_version="0.3.0"):
49
53
  """Issues deprecation warnings for arguments.
@@ -230,3 +234,82 @@ def retrieve_speos_install_dir(
230
234
  if not speos_exec.is_file():
231
235
  error_no_install(speos_rpc_path, int(version))
232
236
  return speos_rpc_path
237
+
238
+
239
+ def wavelength_to_rgb(wavelength: float, gamma: float = 0.8) -> [int, int, int, int]:
240
+ """Convert a given wavelength of light to an approximate RGB color value.
241
+
242
+ The wavelength must be given in nanometers in the range from 380 nm to 750 nm.
243
+ Based on the code from http://www.physics.sfasu.edu/astro/color/spectra.html
244
+
245
+ Parameters
246
+ ----------
247
+ wavelength : float
248
+ Wavelength in nanometer between 380-750 nm
249
+ gamma : float
250
+ Gamma value.
251
+ By default : ``0.8``
252
+ """
253
+ wavelength = float(wavelength)
254
+ if 380 <= wavelength <= 440:
255
+ attenuation = 0.3 + 0.7 * (wavelength - 380) / (440 - 380)
256
+ r = ((-(wavelength - 440) / (440 - 380)) * attenuation) ** gamma
257
+ g = 0.0
258
+ b = (1.0 * attenuation) ** gamma
259
+ elif 440 <= wavelength <= 490:
260
+ r = 0.0
261
+ g = ((wavelength - 440) / (490 - 440)) ** gamma
262
+ b = 1.0
263
+ elif 490 <= wavelength <= 510:
264
+ r = 0.0
265
+ g = 1.0
266
+ b = (-(wavelength - 510) / (510 - 490)) ** gamma
267
+ elif 510 <= wavelength <= 580:
268
+ r = ((wavelength - 510) / (580 - 510)) ** gamma
269
+ g = 1.0
270
+ b = 0.0
271
+ elif 580 <= wavelength <= 645:
272
+ r = 1.0
273
+ g = (-(wavelength - 645) / (645 - 580)) ** gamma
274
+ b = 0.0
275
+ elif 645 <= wavelength <= 750:
276
+ attenuation = 0.3 + 0.7 * (750 - wavelength) / (750 - 645)
277
+ r = (1.0 * attenuation) ** gamma
278
+ g = 0.0
279
+ b = 0.0
280
+ else:
281
+ r = 0.0
282
+ g = 0.0
283
+ b = 0.0
284
+ r *= 255
285
+ g *= 255
286
+ b *= 255
287
+ return [int(r), int(g), int(b), 255]
288
+
289
+
290
+ def min_speos_version(major: int, minor: int, service_pack: int):
291
+ """Raise version warning.
292
+
293
+ Parameters
294
+ ----------
295
+ major : int
296
+ Major release version, e.g. 25
297
+ minor : int
298
+ Minor release version e.g. 1
299
+ service_pack : int
300
+ Service Pack version e.g. 3
301
+ """
302
+ version = f"20{major} R{minor} SP{service_pack}"
303
+
304
+ def decorator(function):
305
+ def wrapper(*args, **kwargs):
306
+ if function.__qualname__.endswith("__init__"):
307
+ name = function.__qualname__[:-9]
308
+ else:
309
+ name = function.__qualname__
310
+ warnings.warn(VERSION_ERROR.format(version=version, feature_name=name), stacklevel=2)
311
+ return function(*args, **kwargs)
312
+
313
+ return wrapper
314
+
315
+ return decorator
@@ -22,7 +22,9 @@
22
22
 
23
23
  """Provides the ``VisualData`` class."""
24
24
 
25
- from typing import TYPE_CHECKING, List
25
+ from typing import TYPE_CHECKING, List, Tuple, Union
26
+
27
+ import numpy as np
26
28
 
27
29
  from ansys.speos.core.generic.general_methods import (
28
30
  graphics_required,
@@ -36,7 +38,7 @@ if TYPE_CHECKING: # pragma: no cover
36
38
 
37
39
  @graphics_required
38
40
  class _VisualCoordinateSystem:
39
- """Visualization data for the coordinate system..
41
+ """Visualization data for the coordinate system.
40
42
 
41
43
  By default, there is empty visualization data.
42
44
 
@@ -52,21 +54,21 @@ class _VisualCoordinateSystem:
52
54
  self.__x_axis = pv.Arrow(
53
55
  start=self.__origin,
54
56
  direction=[1.0, 0.0, 0.0],
55
- scale=10.0,
57
+ scale=1.0,
56
58
  tip_radius=0.05,
57
59
  shaft_radius=0.01,
58
60
  )
59
61
  self.__y_axis = pv.Arrow(
60
62
  start=self.__origin,
61
63
  direction=[0.0, 1.0, 0.0],
62
- scale=10.0,
64
+ scale=1.0,
63
65
  tip_radius=0.05,
64
66
  shaft_radius=0.01,
65
67
  )
66
68
  self.__z_axis = pv.Arrow(
67
69
  start=self.__origin,
68
70
  direction=[0.0, 0.0, 1.0],
69
- scale=10.0,
71
+ scale=1.0,
70
72
  tip_radius=0.05,
71
73
  shaft_radius=0.01,
72
74
  )
@@ -214,6 +216,72 @@ class _VisualCoordinateSystem:
214
216
  )
215
217
 
216
218
 
219
+ @graphics_required
220
+ class _VisualArrow:
221
+ """Visualization data for the line or arrow.
222
+
223
+ By default, there is empty visualization data.
224
+
225
+ Notes
226
+ -----
227
+ **Do not instantiate this class yourself**.
228
+ """
229
+
230
+ def __init__(
231
+ self,
232
+ line_vertices: List[List[float]],
233
+ color: Tuple[float, float, float] = (0.643, 1.0, 0.0),
234
+ arrow: bool = False,
235
+ ) -> None:
236
+ import pyvista as pv
237
+
238
+ if len(line_vertices) != 2 or any(len(point) != 3 for point in line_vertices):
239
+ raise ValueError(
240
+ "line_vertices is expected to be composed of 2 vertices with 3 elements each."
241
+ )
242
+ line_vertices = np.array(line_vertices)
243
+ if arrow:
244
+ self.__data = pv.Arrow(
245
+ start=line_vertices[0],
246
+ direction=line_vertices[1],
247
+ scale=1,
248
+ tip_radius=0.05,
249
+ shaft_radius=0.01,
250
+ )
251
+ else:
252
+ self.__data = pv.Line(line_vertices[0], line_vertices[0] + line_vertices[1])
253
+ if all(value < 1 for value in color):
254
+ self.__color = color + (255,)
255
+ else:
256
+ self.__color = tuple(value / 255 for value in color) + (255,)
257
+
258
+ @property
259
+ def data(self) -> "pv.PolyData":
260
+ """
261
+ Returns the pyvista data of _VisualArrow.
262
+
263
+ Returns
264
+ -------
265
+ pv.PolyData
266
+ The pyvista data of _VisualArrow.
267
+
268
+ """
269
+ return self.__data
270
+
271
+ @property
272
+ def color(self) -> Tuple[float, float, float]:
273
+ """
274
+ Returns the color property of _VisualArrow.
275
+
276
+ Returns
277
+ -------
278
+ Tuple[float, float, float]
279
+ The color tuple of _VisualArrow.
280
+
281
+ """
282
+ return self.__color
283
+
284
+
217
285
  @graphics_required
218
286
  class _VisualData:
219
287
  """Visualization data for the sensor.
@@ -225,25 +293,26 @@ class _VisualData:
225
293
  **Do not instantiate this class yourself**
226
294
  """
227
295
 
228
- def __init__(self, coordinate_system=True):
296
+ def __init__(self, ray: bool = False, coordinate_system: bool = True):
229
297
  import pyvista as pv
230
298
 
231
- self.__data = pv.PolyData()
299
+ self._data = [] if ray else pv.PolyData()
232
300
  self.coordinates = _VisualCoordinateSystem() if coordinate_system else None
233
301
  self.updated = False
234
302
 
235
303
  @property
236
- def data(self) -> "pv.PolyData":
304
+ def data(self) -> Union["pv.PolyData", List[_VisualArrow]]:
237
305
  """
238
- Returns the data of the sensor.
306
+ Returns the pyvista data of _VisualData.
239
307
 
240
308
  Returns
241
309
  -------
242
- pv.PolyData
243
- The data of the surface visualization.
310
+ Union["pv.PolyData", List[_VisualArrow]]
311
+ The data of the surface visualization if surface data,
312
+ else List[_VisualArrow] containing ray info.
244
313
 
245
314
  """
246
- return self.__data
315
+ return self._data
247
316
 
248
317
  def add_data_triangle(self, triangle_vertices: List[List[float]]) -> None:
249
318
  """
@@ -265,7 +334,7 @@ class _VisualData:
265
334
  "triangle_vertices is expected to be composed of 3 vertices with 3 elements each."
266
335
  )
267
336
  faces = [[3, 0, 1, 2]]
268
- self.__data = self.__data.append_polydata(pv.PolyData(triangle_vertices, faces))
337
+ self._data = self._data.append_polydata(pv.PolyData(triangle_vertices, faces))
269
338
 
270
339
  def add_data_rectangle(self, rectangle_vertices: List[List[float]]) -> None:
271
340
  """
@@ -286,4 +355,68 @@ class _VisualData:
286
355
  raise ValueError(
287
356
  "rectangle_vertices is expected to be composed of 3 vertices with 3 elements each."
288
357
  )
289
- self.__data = self.__data.append_polydata(pv.Rectangle(rectangle_vertices))
358
+ self._data = self._data.append_polydata(pv.Rectangle(rectangle_vertices))
359
+
360
+ def add_data_line(self, line: _VisualArrow) -> None:
361
+ """
362
+ Add line data to Visualization data.
363
+
364
+ Parameters
365
+ ----------
366
+ line: _VisualArrow
367
+ _VisualArrow presenting one line
368
+
369
+ Returns
370
+ -------
371
+ None
372
+ """
373
+ self._data.append(line)
374
+
375
+ def add_data_mesh(self, vertices: np.ndarray, facets: np.ndarray) -> None:
376
+ """
377
+ Add mesh data to Visualization data.
378
+
379
+ Parameters
380
+ ----------
381
+ vertices: numpy.ndarray
382
+ The vertices of the mesh.
383
+ facets: numpy.ndarray
384
+ The facets of the mesh.
385
+
386
+ Returns
387
+ -------
388
+ None
389
+ """
390
+ import pyvista as pv
391
+
392
+ if vertices.shape[1] != 3 or facets.shape[1] != 4:
393
+ raise ValueError(
394
+ "mesh vertices is expected to be composed of 3 elements each, and 4 for facets."
395
+ )
396
+ self._data = self._data.append_polydata(pv.PolyData(vertices, facets))
397
+
398
+
399
+ def local2absolute(local_vertice: np.ndarray, coordinates) -> np.ndarray:
400
+ """Convert local coordinate to global coordinate.
401
+
402
+ Parameters
403
+ ----------
404
+ coordinates: list
405
+ local coordinate in shape [1, 9],
406
+ coordinates[3:6] as x-axis,
407
+ coordinates[3:6] as y-axis,
408
+ coordinates[3:6] as z-axis.
409
+ local_vertice: np.ndarray
410
+ numpy array includes x, y, z info.
411
+
412
+ Returns
413
+ -------
414
+ np.ndarray
415
+ numpy array includes x, y, z info
416
+
417
+ """
418
+ global_origin = np.array(coordinates[:3])
419
+ global_x = np.array(coordinates[3:6]) * local_vertice[0]
420
+ global_y = np.array(coordinates[6:9]) * local_vertice[1]
421
+ global_z = np.array(coordinates[9:]) * local_vertice[2]
422
+ return global_origin + global_x + global_y + global_z
@@ -24,10 +24,13 @@
24
24
 
25
25
  from typing import Iterator, List
26
26
 
27
+ from grpc import RpcError
28
+
27
29
  from ansys.api.speos.part.v1 import (
28
30
  face_pb2 as messages,
29
31
  face_pb2_grpc as service,
30
32
  )
33
+ from ansys.speos.core.generic.general_methods import min_speos_version
31
34
  from ansys.speos.core.kernel.crud import CrudItem, CrudStub
32
35
  from ansys.speos.core.kernel.proto_message_utils import protobuf_message_to_str
33
36
 
@@ -102,6 +105,50 @@ class FaceStub(CrudStub):
102
105
  def __init__(self, channel):
103
106
  super().__init__(stub=service.FacesManagerStub(channel=channel))
104
107
  self._actions_stub = service.FaceActionsStub(channel=channel)
108
+ self._is_batch_available = self._check_if_batch_available()
109
+
110
+ def _check_if_batch_available(self) -> bool:
111
+ try:
112
+ for reserve_faces_res in self._actions_stub.ReserveFaces(
113
+ FaceStub._reserve_face_iterator([ProtoFace(name="tmp")])
114
+ ):
115
+ FaceLink(self, reserve_faces_res.guids[0]).delete()
116
+ return True
117
+ except RpcError:
118
+ return False
119
+
120
+ @min_speos_version(25, 2, 0)
121
+ def create_batch(self, message_list: List[ProtoFace]) -> List[FaceLink]:
122
+ """Create new entries.
123
+
124
+ Parameters
125
+ ----------
126
+ message_list : List[face.Face]
127
+ List of datamodels for the new entries.
128
+
129
+ Returns
130
+ -------
131
+ List[ansys.speos.core.kernel.face.FaceLink]
132
+ List pf link objects created.
133
+ """
134
+ if not self._is_batch_available:
135
+ raise NotImplementedError("Please use a Speos Version of 2025 R2 SP0 or higher.")
136
+
137
+ reserve_faces_res = self._actions_stub.ReserveFaces(
138
+ FaceStub._reserve_face_iterator(message_list)
139
+ )
140
+
141
+ guids = []
142
+ for res in reserve_faces_res:
143
+ for guid in res.guids:
144
+ guids.append(guid)
145
+
146
+ chunk_iterator = FaceStub._faces_to_chunks(
147
+ guids=guids, message_list=message_list, nb_items=128 * 1024
148
+ )
149
+ self._actions_stub.Upload(chunk_iterator)
150
+
151
+ return [FaceLink(self, guid) for guid in guids]
105
152
 
106
153
  def create(self, message: ProtoFace) -> FaceLink:
107
154
  """Create a new entry.
@@ -118,13 +165,37 @@ class FaceStub(CrudStub):
118
165
  """
119
166
  resp = CrudStub.create(self, messages.Create_Request(face=ProtoFace(name="tmp")))
120
167
 
121
- chunk_iterator = FaceStub._face_to_chunks(
122
- guid=resp.guid, message=message, nb_items=128 * 1024
168
+ chunk_iterator = FaceStub._faces_to_chunks(
169
+ guids=[resp.guid], message_list=[message], nb_items=128 * 1024
123
170
  )
124
171
  self._actions_stub.Upload(chunk_iterator)
125
-
126
172
  return FaceLink(self, resp.guid)
127
173
 
174
+ @min_speos_version(25, 2, 0)
175
+ def read_batch(self, refs: List[FaceLink]) -> List[ProtoFace]:
176
+ """Get existing entries.
177
+
178
+ Parameters
179
+ ----------
180
+ refs : List[ansys.speos.core.kernel.face.FaceLink]
181
+ List of link objects to read.
182
+
183
+ Returns
184
+ -------
185
+ List[face.Face]
186
+ Datamodels of the entries.
187
+ """
188
+ if not self._is_batch_available:
189
+ raise NotImplementedError("Please use a Speos Version of 2025 R2 SP0 or higher.")
190
+
191
+ for ref in refs:
192
+ if not ref.stub == self:
193
+ raise ValueError("FaceLink is not on current database. Key=" + ref.key)
194
+ chunks = self._actions_stub.Download(
195
+ request=messages.Download_Request(guids=[ref.key for ref in refs])
196
+ )
197
+ return FaceStub._chunks_to_faces(chunks)
198
+
128
199
  def read(self, ref: FaceLink) -> ProtoFace:
129
200
  """Get an existing entry.
130
201
 
@@ -139,9 +210,34 @@ class FaceStub(CrudStub):
139
210
  Datamodel of the entry.
140
211
  """
141
212
  if not ref.stub == self:
142
- raise ValueError("FaceLink is not on current database")
213
+ raise ValueError("FaceLink is not on current database. Key=" + ref.key)
214
+
143
215
  chunks = self._actions_stub.Download(request=messages.Download_Request(guid=ref.key))
144
- return FaceStub._chunks_to_face(chunks)
216
+ return FaceStub._chunks_to_faces(chunks)[0]
217
+
218
+ @min_speos_version(25, 2, 0)
219
+ def update_batch(self, refs: List[FaceLink], data: List[ProtoFace]) -> None:
220
+ """Change existing entries.
221
+
222
+ Parameters
223
+ ----------
224
+ ref : List[ansys.speos.core.kernel.face.FaceLink]
225
+ Link objects to update.
226
+
227
+ data : List[face.Face]
228
+ New datamodels for the entries.
229
+ """
230
+ if not self._is_batch_available:
231
+ raise NotImplementedError("Please use a Speos Version of 2025 R2 SP0 or higher.")
232
+
233
+ for ref in refs:
234
+ if not ref.stub == self:
235
+ raise ValueError("FaceLink is not on current database")
236
+
237
+ chunk_iterator = FaceStub._faces_to_chunks(
238
+ guids=[ref.key for ref in refs], message_list=data, nb_items=128 * 1024
239
+ )
240
+ self._actions_stub.Upload(chunk_iterator)
145
241
 
146
242
  def update(self, ref: FaceLink, data: ProtoFace) -> None:
147
243
  """Change an existing entry.
@@ -157,11 +253,9 @@ class FaceStub(CrudStub):
157
253
  if not ref.stub == self:
158
254
  raise ValueError("FaceLink is not on current database")
159
255
 
160
- CrudStub.update(
161
- self,
162
- messages.Update_Request(guid=ref.key, face=ProtoFace(name="tmp")),
256
+ chunk_iterator = FaceStub._faces_to_chunks(
257
+ guids=[ref.key], message_list=[data], nb_items=128 * 1024
163
258
  )
164
- chunk_iterator = FaceStub._face_to_chunks(guid=ref.key, message=data, nb_items=128 * 1024)
165
259
  self._actions_stub.Upload(chunk_iterator)
166
260
 
167
261
  def delete(self, ref: FaceLink) -> None:
@@ -188,47 +282,63 @@ class FaceStub(CrudStub):
188
282
  return list(map(lambda x: FaceLink(self, x), guids))
189
283
 
190
284
  @staticmethod
191
- def _face_to_chunks(guid: str, message: ProtoFace, nb_items: int) -> Iterator[messages.Chunk]:
192
- for j in range(4):
193
- if j == 0:
194
- chunk_face_header = messages.Chunk(
195
- face_header=messages.Chunk.FaceHeader(
196
- guid=guid,
197
- name=message.name,
198
- description=message.description,
199
- metadata=message.metadata,
200
- sizes=[
201
- len(message.vertices),
202
- len(message.facets),
203
- len(message.texture_coordinates_channels),
204
- ],
205
- )
206
- )
207
- yield chunk_face_header
208
- elif j == 1:
209
- for i in range(0, len(message.vertices), nb_items):
210
- chunk_vertices = messages.Chunk(
211
- vertices=messages.Chunk.Vertices(data=message.vertices[i : i + nb_items])
212
- )
213
- yield chunk_vertices
214
- elif j == 2:
215
- for i in range(0, len(message.facets), nb_items):
216
- chunk_facets = messages.Chunk(
217
- facets=messages.Chunk.Facets(data=message.facets[i : i + nb_items])
218
- )
219
- yield chunk_facets
220
- elif j == 3:
221
- for i in range(0, len(message.normals), nb_items):
222
- chunk_normals = messages.Chunk(
223
- normals=messages.Chunk.Normals(data=message.normals[i : i + nb_items])
285
+ def _reserve_face_iterator(
286
+ message_list: List[ProtoFace],
287
+ ) -> Iterator[messages.ReserveFace_Request]:
288
+ for message in message_list:
289
+ yield messages.ReserveFace_Request(faces=[ProtoFace(name="tmp")])
290
+
291
+ @staticmethod
292
+ def _faces_to_chunks(
293
+ guids: List[str], message_list: List[ProtoFace], nb_items: int
294
+ ) -> Iterator[messages.Chunk]:
295
+ for guid, message in zip(guids, message_list):
296
+ for j in range(4):
297
+ if j == 0:
298
+ chunk_face_header = messages.Chunk(
299
+ face_header=messages.Chunk.FaceHeader(
300
+ guid=guid,
301
+ name=message.name,
302
+ description=message.description,
303
+ metadata=message.metadata,
304
+ sizes=[
305
+ len(message.vertices),
306
+ len(message.facets),
307
+ len(message.vertices_data),
308
+ ],
309
+ )
224
310
  )
225
- yield chunk_normals
311
+ yield chunk_face_header
312
+ elif j == 1:
313
+ for i in range(0, len(message.vertices), nb_items):
314
+ chunk_vertices = messages.Chunk(
315
+ vertices=messages.Chunk.Vertices(
316
+ data=message.vertices[i : i + nb_items]
317
+ )
318
+ )
319
+ yield chunk_vertices
320
+ elif j == 2:
321
+ for i in range(0, len(message.facets), nb_items):
322
+ chunk_facets = messages.Chunk(
323
+ facets=messages.Chunk.Facets(data=message.facets[i : i + nb_items])
324
+ )
325
+ yield chunk_facets
326
+ elif j == 3:
327
+ for i in range(0, len(message.normals), nb_items):
328
+ chunk_normals = messages.Chunk(
329
+ normals=messages.Chunk.Normals(data=message.normals[i : i + nb_items])
330
+ )
331
+ yield chunk_normals
226
332
 
227
333
  @staticmethod
228
- def _chunks_to_face(chunks: messages.Chunk) -> ProtoFace:
334
+ def _chunks_to_faces(chunks: messages.Chunk) -> List[ProtoFace]:
335
+ out_faces = []
229
336
  out_face = ProtoFace()
230
337
  for chunk in chunks:
231
338
  if chunk.HasField("face_header"):
339
+ if out_face != ProtoFace(): # Add face each time a new one starts
340
+ out_faces.append(out_face)
341
+ out_face = ProtoFace()
232
342
  out_face.name = chunk.face_header.name
233
343
  out_face.description = chunk.face_header.description
234
344
  out_face.metadata.update(chunk.face_header.metadata)
@@ -239,4 +349,5 @@ class FaceStub(CrudStub):
239
349
  if chunk.HasField("normals"):
240
350
  out_face.normals.extend(chunk.normals.data)
241
351
 
242
- return out_face
352
+ out_faces.append(out_face) # Don't forget to add last face
353
+ return out_faces
@@ -57,6 +57,7 @@ def protobuf_message_to_str(message: Message, with_full_name: bool = True) -> st
57
57
  including_default_value_fields=True,
58
58
  preserving_proto_field_name=True,
59
59
  indent=4,
60
+ ensure_ascii=False,
60
61
  )
61
62
  else:
62
63
  ret += MessageToJson(
@@ -64,6 +65,7 @@ def protobuf_message_to_str(message: Message, with_full_name: bool = True) -> st
64
65
  always_print_fields_with_no_presence=True,
65
66
  preserving_proto_field_name=True,
66
67
  indent=4,
68
+ ensure_ascii=False,
67
69
  )
68
70
  return ret
69
71