2from dataclasses
import dataclass
3from typing
import List, Optional, Union
8from .wrappers
import UContextWrapper
as context_wrapper
9from .wrappers.DataTypes
import vec2, vec3, vec4, int2, int3, int4, SphericalCoord, RGBcolor, RGBAcolor, PrimitiveType, Date, Time, Location
10from .plugins.loader
import LibraryLoadError, validate_library, get_library_info
11from .plugins.registry
import get_plugin_registry
12from .validation.geometry
import (
13 validate_patch_params, validate_triangle_params, validate_sphere_params,
14 validate_tube_params, validate_box_params
21 Physical properties and geometry information for a primitive.
22 This is separate from primitive data (user-defined key-value pairs).
25 primitive_type: PrimitiveType
30 centroid: Optional[vec3] =
None
31 texture_file: Optional[str] =
None
32 texture_uv: Optional[List[vec2]] =
None
33 solid_fraction: Optional[float] =
None
36 """Calculate centroid from vertices if not provided."""
39 total_x = sum(v.x
for v
in self.
vertices)
40 total_y = sum(v.y
for v
in self.
vertices)
41 total_z = sum(v.z
for v
in self.
vertices)
43 self.
centroid =
vec3(total_x / count, total_y / count, total_z / count)
48 Central simulation environment for PyHelios that manages 3D primitives and their data.
50 The Context class provides methods for:
51 - Creating geometric primitives (patches, triangles)
52 - Creating compound geometry (tiles, spheres, tubes, boxes)
53 - Loading 3D models from files (PLY, OBJ, XML)
54 - Managing primitive data (flexible key-value storage)
55 - Querying primitive properties and collections
56 - Batch operations on multiple primitives
59 - UUID-based primitive tracking
60 - Comprehensive primitive data system with auto-type detection
61 - Efficient array-based data retrieval via getPrimitiveDataArray()
62 - Cross-platform compatibility with mock mode support
63 - Context manager protocol for resource cleanup
66 >>> with Context() as context:
67 ... # Create primitives
68 ... patch_uuid = context.addPatch(center=vec3(0, 0, 0))
69 ... triangle_uuid = context.addTriangle(vec3(0,0,0), vec3(1,0,0), vec3(0.5,1,0))
71 ... # Set primitive data
72 ... context.setPrimitiveDataFloat(patch_uuid, "temperature", 25.5)
73 ... context.setPrimitiveDataFloat(triangle_uuid, "temperature", 30.2)
75 ... # Get data efficiently as NumPy array
76 ... temps = context.getPrimitiveDataArray([patch_uuid, triangle_uuid], "temperature")
77 ... print(temps) # [25.5 30.2]
88 library_info = get_library_info()
89 if library_info.get(
'is_mock',
False):
91 print(
"Warning: PyHelios running in development mock mode - functionality is limited")
92 print(
"Available plugins: None (mock mode)")
99 if not validate_library():
100 raise LibraryLoadError(
101 "Native Helios library validation failed. Some required functions are missing. "
102 "Try rebuilding the native library: build_scripts/build_helios"
104 except LibraryLoadError:
106 except Exception
as e:
107 raise LibraryLoadError(
108 f
"Failed to validate native Helios library: {e}. "
109 f
"To enable development mode without native libraries, set PYHELIOS_DEV_MODE=1"
114 self.
context = context_wrapper.createContext()
117 raise LibraryLoadError(
118 "Failed to create Helios context. Native library may not be functioning correctly."
123 except Exception
as e:
125 raise LibraryLoadError(
126 f
"Failed to create Helios context: {e}. "
127 f
"Ensure native libraries are built and accessible."
131 """Helper method to check if context is available with detailed error messages."""
136 "Context is in mock mode - native functionality not available.\n"
137 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
141 "Context has been cleaned up and is no longer usable.\n"
142 "This usually means you're trying to use a Context outside its 'with' statement scope.\n"
144 "Fix: Ensure all Context usage is inside the 'with Context() as context:' block:\n"
145 " with Context() as context:\n"
146 " # All context operations must be here\n"
147 " with SomePlugin(context) as plugin:\n"
148 " plugin.do_something()\n"
149 " with Visualizer() as vis:\n"
150 " vis.buildContextGeometry(context) # Still inside Context scope\n"
151 " # Context is cleaned up here - cannot use context after this point"
155 "Context creation failed - native functionality not available.\n"
156 "Build native libraries with 'python build_scripts/build_helios.py'"
161 f
"Context is not available (state: {self._lifecycle_state}).\n"
162 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
166 """Validate that a UUID exists in this context.
169 uuid: The UUID to validate
172 RuntimeError: If UUID is invalid or doesn't exist in context
175 if not isinstance(uuid, int)
or uuid < 0:
176 raise RuntimeError(f
"Invalid UUID: {uuid}. UUIDs must be non-negative integers.")
181 if uuid
not in valid_uuids:
182 raise RuntimeError(f
"UUID {uuid} does not exist in context. Valid UUIDs: {valid_uuids[:10]}{'...' if len(valid_uuids) > 10 else ''}")
192 def _validate_file_path(self, filename: str, expected_extensions: List[str] =
None) -> str:
193 """Validate and normalize file path for security.
196 filename: File path to validate
197 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
200 Normalized absolute path
204 ValueError: If path is invalid or potentially dangerous
205 FileNotFoundError: If file does not exist
210 abs_path = os.path.abspath(filename)
214 normalized_path = os.path.normpath(abs_path)
215 if abs_path != normalized_path:
216 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
219 if expected_extensions:
220 file_ext = os.path.splitext(abs_path)[1].lower()
221 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
222 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
225 if not os.path.exists(abs_path):
226 raise FileNotFoundError(f
"File not found: {abs_path}")
229 if not os.path.isfile(abs_path):
230 raise ValueError(f
"Path is not a file: {abs_path}")
235 """Validate and normalize output file path for security.
238 filename: Output file path to validate
239 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
242 Normalized absolute path
245 ValueError: If path is invalid or potentially dangerous
246 PermissionError: If output directory is not writable
251 if not filename
or not filename.strip():
252 raise ValueError(
"Filename cannot be empty")
255 abs_path = os.path.abspath(filename)
258 normalized_path = os.path.normpath(abs_path)
259 if abs_path != normalized_path:
260 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
263 if expected_extensions:
264 file_ext = os.path.splitext(abs_path)[1].lower()
265 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
266 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
269 output_dir = os.path.dirname(abs_path)
270 if not os.path.exists(output_dir):
271 raise ValueError(f
"Output directory does not exist: {output_dir}")
272 if not os.access(output_dir, os.W_OK):
273 raise PermissionError(f
"Output directory is not writable: {output_dir}")
280 def __exit__(self, exc_type, exc_value, traceback):
282 context_wrapper.destroyContext(self.
context)
287 """Destructor to ensure C++ resources freed even without 'with' statement."""
288 if hasattr(self,
'context')
and self.
context is not None:
290 context_wrapper.destroyContext(self.
context)
293 except Exception
as e:
295 warnings.warn(f
"Error in Context.__del__: {e}")
303 context_wrapper.markGeometryClean(self.
context)
307 context_wrapper.markGeometryDirty(self.
context)
312 return context_wrapper.isGeometryDirty(self.
context)
316 Seed the random number generator for reproducible stochastic results.
319 seed: Integer seed value for random number generation
322 This is critical for reproducible results in stochastic simulations
323 (e.g., LiDAR scans with beam divergence, random perturbations).
326 context_wrapper.helios_lib.seedRandomGenerator(self.
context, seed)
328 @validate_patch_params
329 def addPatch(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1), rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None) -> int:
334 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
335 return context_wrapper.addPatchWithCenterSizeRotationAndColor(self.
context, center.to_list(), size.to_list(), rotation_list, color.to_list())
338 rotation: Optional[SphericalCoord] =
None,
339 uv_center: Optional[vec2] =
None,
340 uv_size: Optional[vec2] =
None) -> int:
341 """Add a textured patch primitive to the context.
343 Creates a rectangular patch with a texture image mapped to its surface.
346 center: 3D position of the patch center
347 size: Width and height of the patch
348 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
349 rotation: Optional spherical rotation (defaults to no rotation)
350 uv_center: Optional UV center of texture map (required if uv_size is provided)
351 uv_size: Optional UV size of texture map (required if uv_center is provided)
354 UUID of the created textured patch primitive
357 ValueError: If arguments have wrong types or UV params are partially specified
358 FileNotFoundError: If texture file doesn't exist
359 RuntimeError: If context is in mock mode
362 >>> context = Context()
363 >>> uuid = context.addPatchTextured(
364 ... center=vec3(0, 0, 0),
366 ... texture_file="texture.png"
371 if not isinstance(center, vec3):
372 raise ValueError(f
"center must be a vec3, got {type(center).__name__}")
373 if not isinstance(size, vec2):
374 raise ValueError(f
"size must be a vec2, got {type(size).__name__}")
375 if not isinstance(texture_file, str):
376 raise ValueError(f
"texture_file must be a str, got {type(texture_file).__name__}")
377 if rotation
is not None and not isinstance(rotation, SphericalCoord):
378 raise ValueError(f
"rotation must be a SphericalCoord, got {type(rotation).__name__}")
380 if (uv_center
is None) != (uv_size
is None):
381 raise ValueError(
"uv_center and uv_size must both be provided or both omitted")
382 if uv_center
is not None and not isinstance(uv_center, vec2):
383 raise ValueError(f
"uv_center must be a vec2, got {type(uv_center).__name__}")
384 if uv_size
is not None and not isinstance(uv_size, vec2):
385 raise ValueError(f
"uv_size must be a vec2, got {type(uv_size).__name__}")
388 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
391 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
393 if uv_center
is not None:
394 return context_wrapper.addPatchWithTextureAndUV(
395 self.
context, center.to_list(), size.to_list(), rotation_list,
396 validated_texture_file, uv_center.to_list(), uv_size.to_list()
399 return context_wrapper.addPatchWithTexture(
400 self.
context, center.to_list(), size.to_list(), rotation_list,
401 validated_texture_file
404 @validate_triangle_params
405 def addTriangle(self, vertex0: vec3, vertex1: vec3, vertex2: vec3, color: Optional[RGBcolor] =
None) -> int:
406 """Add a triangle primitive to the context
409 vertex0: First vertex of the triangle
410 vertex1: Second vertex of the triangle
411 vertex2: Third vertex of the triangle
412 color: Optional triangle color (defaults to white)
415 UUID of the created triangle primitive
419 return context_wrapper.addTriangle(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list())
421 return context_wrapper.addTriangleWithColor(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list(), color.to_list())
424 texture_file: str, uv0: vec2, uv1: vec2, uv2: vec2) -> int:
425 """Add a textured triangle primitive to the context
427 Creates a triangle with texture mapping. The texture image is mapped to the triangle
428 surface using UV coordinates, where (0,0) represents the top-left corner of the image
429 and (1,1) represents the bottom-right corner.
432 vertex0: First vertex of the triangle
433 vertex1: Second vertex of the triangle
434 vertex2: Third vertex of the triangle
435 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
436 uv0: UV texture coordinates for first vertex
437 uv1: UV texture coordinates for second vertex
438 uv2: UV texture coordinates for third vertex
441 UUID of the created textured triangle primitive
444 ValueError: If texture file path is invalid
445 FileNotFoundError: If texture file doesn't exist
446 RuntimeError: If context is in mock mode
449 >>> context = Context()
450 >>> # Create a textured triangle
451 >>> vertex0 = vec3(0, 0, 0)
452 >>> vertex1 = vec3(1, 0, 0)
453 >>> vertex2 = vec3(0.5, 1, 0)
454 >>> uv0 = vec2(0, 0) # Bottom-left of texture
455 >>> uv1 = vec2(1, 0) # Bottom-right of texture
456 >>> uv2 = vec2(0.5, 1) # Top-center of texture
457 >>> uuid = context.addTriangleTextured(vertex0, vertex1, vertex2,
458 ... "texture.png", uv0, uv1, uv2)
463 for name, val
in [(
"vertex0", vertex0), (
"vertex1", vertex1), (
"vertex2", vertex2)]:
464 if not isinstance(val, vec3):
465 raise ValueError(f
"{name} must be a vec3, got {type(val).__name__}")
466 for name, val
in [(
"uv0", uv0), (
"uv1", uv1), (
"uv2", uv2)]:
467 if not isinstance(val, vec2):
468 raise ValueError(f
"{name} must be a vec2, got {type(val).__name__}")
472 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
475 return context_wrapper.addTriangleWithTexture(
477 vertex0.to_list(), vertex1.to_list(), vertex2.to_list(),
478 validated_texture_file,
479 uv0.to_list(), uv1.to_list(), uv2.to_list()
483 """Get the type of a primitive or multiple primitives.
486 uuid: Single UUID (int) or list of UUIDs
489 PrimitiveType for single UUID, or np.ndarray of shape (N,) uint32 for list
492 if isinstance(uuid, (list, tuple)):
494 return np.empty((0,), dtype=np.uint32)
495 ptr, size = context_wrapper.getBatchPrimitiveTypes(self.
context, uuid)
496 if size == 0
or not ptr:
497 return np.empty((0,), dtype=np.uint32)
498 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
499 primitive_type = context_wrapper.getPrimitiveType(self.
context, uuid)
503 """Get the area of a primitive or multiple primitives.
506 uuid: Single UUID (int) or list of UUIDs
509 float for single UUID, or np.ndarray of shape (N,) for list
512 if isinstance(uuid, (list, tuple)):
514 return np.empty((0,), dtype=np.float32)
515 ptr, size = context_wrapper.getBatchPrimitiveAreas(self.
context, uuid)
516 if size == 0
or not ptr:
517 return np.empty((0,), dtype=np.float32)
518 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
519 return context_wrapper.getPrimitiveArea(self.
context, uuid)
522 """Get the normal vector of a primitive or multiple primitives.
525 uuid: Single UUID (int) or list of UUIDs
528 vec3 for single UUID, or np.ndarray of shape (N, 3) for list
531 if isinstance(uuid, (list, tuple)):
533 return np.empty((0, 3), dtype=np.float32)
534 ptr, size = context_wrapper.getBatchPrimitiveNormals(self.
context, uuid)
535 if size == 0
or not ptr:
536 return np.empty((0, 3), dtype=np.float32)
537 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
538 normal_ptr = context_wrapper.getPrimitiveNormal(self.
context, uuid)
539 return vec3(normal_ptr[0], normal_ptr[1], normal_ptr[2])
542 """Get vertices of a primitive or multiple primitives.
545 uuid: Single UUID (int) or list of UUIDs
548 List[vec3] for single UUID, or tuple of (flat_data, offsets) for list
549 where flat_data is a float32 ndarray and offsets is a uint32 ndarray
550 of length N+1. Vertices for primitive i are at
551 flat_data[offsets[i]:offsets[i+1]].
554 if isinstance(uuid, (list, tuple)):
556 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
557 ptr, offsets, total = context_wrapper.getBatchPrimitiveVertices(self.
context, uuid)
558 offsets_arr = np.array(offsets, dtype=np.uint32)
559 if total == 0
or not ptr:
560 return (np.empty((0,), dtype=np.float32), offsets_arr)
561 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
562 return (data, offsets_arr)
563 size = ctypes.c_uint()
564 vertices_ptr = context_wrapper.getPrimitiveVertices(self.
context, uuid, ctypes.byref(size))
566 vertices_list = ctypes.cast(vertices_ptr, ctypes.POINTER(ctypes.c_float * size.value)).contents
567 vertices = [
vec3(vertices_list[i], vertices_list[i+1], vertices_list[i+2])
for i
in range(0, size.value, 3)]
571 """Get the color of a primitive or multiple primitives.
574 uuid: Single UUID (int) or list of UUIDs
577 RGBcolor for single UUID, or np.ndarray of shape (N, 3) for list
580 if isinstance(uuid, (list, tuple)):
582 return np.empty((0, 3), dtype=np.float32)
583 ptr, size = context_wrapper.getBatchPrimitiveColors(self.
context, uuid)
584 if size == 0
or not ptr:
585 return np.empty((0, 3), dtype=np.float32)
586 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
587 color_ptr = context_wrapper.getPrimitiveColor(self.
context, uuid)
588 return RGBcolor(color_ptr[0], color_ptr[1], color_ptr[2])
592 return context_wrapper.getPrimitiveCount(self.
context)
595 """Check if a primitive exists for a given UUID or list of UUIDs.
598 uuid: A single UUID (int) or a list of UUIDs.
601 True if the primitive(s) exist, False otherwise.
602 For a list, returns True only if ALL primitives exist.
605 if isinstance(uuid, (list, tuple)):
606 arr = (ctypes.c_uint * len(uuid))(*uuid)
607 return context_wrapper.doesPrimitiveExistBatch(self.
context, arr, len(uuid))
608 return context_wrapper.doesPrimitiveExist(self.
context, uuid)
612 size = ctypes.c_uint()
613 uuids_ptr = context_wrapper.getAllUUIDs(self.
context, ctypes.byref(size))
614 return list(uuids_ptr[:size.value])
618 return context_wrapper.getObjectCount(self.
context)
622 size = ctypes.c_uint()
623 objectids_ptr = context_wrapper.getAllObjectIDs(self.
context, ctypes.byref(size))
624 return list(objectids_ptr[:size.value])
628 Get physical properties and geometry information for a single primitive.
631 uuid: UUID of the primitive
634 PrimitiveInfo object containing physical properties and geometry
644 solid_fraction =
None
658 primitive_type=primitive_type,
663 texture_file=texture_file,
664 texture_uv=texture_uv,
665 solid_fraction=solid_fraction,
670 Get physical properties and geometry information for all primitives in the context.
673 List of PrimitiveInfo objects for all primitives
680 Get physical properties and geometry information for all primitives belonging to a specific object.
683 object_id: ID of the object
686 List of PrimitiveInfo objects for primitives in the object
688 object_uuids = context_wrapper.getObjectPrimitiveUUIDs(self.
context, object_id)
692 def addTile(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
693 rotation: Optional[SphericalCoord] =
None, subdiv: int2 =
int2(1, 1),
694 color: Optional[RGBcolor] =
None) -> List[int]:
696 Add a subdivided patch (tile) to the context.
698 A tile is a patch subdivided into a regular grid of smaller patches,
699 useful for creating detailed surfaces or terrain.
702 center: 3D coordinates of tile center (default: origin)
703 size: Width and height of the tile (default: 1x1)
704 rotation: Orientation of the tile (default: no rotation)
705 subdiv: Number of subdivisions in x and y directions (default: 1x1)
706 color: Color of the tile (default: white)
709 List of UUIDs for all patches created in the tile
712 >>> context = Context()
713 >>> # Create a 2x2 meter tile subdivided into 4x4 patches
714 >>> tile_uuids = context.addTile(
715 ... center=vec3(0, 0, 1),
717 ... subdiv=int2(4, 4),
718 ... color=RGBcolor(0.5, 0.8, 0.2)
720 >>> print(f"Created {len(tile_uuids)} patches")
725 if not isinstance(center, vec3):
726 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
727 if not isinstance(size, vec2):
728 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
729 if rotation
is not None and not isinstance(rotation, SphericalCoord):
730 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
731 if not isinstance(subdiv, int2):
732 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
733 if color
is not None and not isinstance(color, RGBcolor):
734 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
737 if any(s <= 0
for s
in size.to_list()):
738 raise ValueError(
"All size dimensions must be positive")
739 if any(s <= 0
for s
in subdiv.to_list()):
740 raise ValueError(
"All subdivision counts must be positive")
746 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
748 if color
and not (color.r == 1.0
and color.g == 1.0
and color.b == 1.0):
749 return context_wrapper.addTileWithColor(
750 self.
context, center.to_list(), size.to_list(),
751 rotation_list, subdiv.to_list(), color.to_list()
754 return context_wrapper.addTile(
755 self.
context, center.to_list(), size.to_list(),
756 rotation_list, subdiv.to_list()
759 @validate_sphere_params
760 def addSphere(self, center: vec3 =
vec3(0, 0, 0), radius: float = 1.0,
761 ndivs: int = 10, color: Optional[RGBcolor] =
None) -> List[int]:
763 Add a sphere to the context.
765 The sphere is tessellated into triangular faces based on the specified
769 center: 3D coordinates of sphere center (default: origin)
770 radius: Radius of the sphere (default: 1.0)
771 ndivs: Number of divisions for tessellation (default: 10)
772 Higher values create smoother spheres but more triangles
773 color: Color of the sphere (default: white)
776 List of UUIDs for all triangles created in the sphere
779 >>> context = Context()
780 >>> # Create a red sphere at (1, 2, 3) with radius 0.5
781 >>> sphere_uuids = context.addSphere(
782 ... center=vec3(1, 2, 3),
785 ... color=RGBcolor(1, 0, 0)
787 >>> print(f"Created sphere with {len(sphere_uuids)} triangles")
792 if not isinstance(center, vec3):
793 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
794 if not isinstance(radius, (int, float)):
795 raise ValueError(f
"Radius must be a number, got {type(radius).__name__}")
796 if not isinstance(ndivs, int):
797 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
798 if color
is not None and not isinstance(color, RGBcolor):
799 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
803 raise ValueError(
"Sphere radius must be positive")
805 raise ValueError(
"Number of divisions must be at least 3")
808 return context_wrapper.addSphereWithColor(
809 self.
context, ndivs, center.to_list(), radius, color.to_list()
812 return context_wrapper.addSphere(
813 self.
context, ndivs, center.to_list(), radius
816 @validate_tube_params
817 def addTube(self, nodes: List[vec3], radii: Union[float, List[float]],
818 ndivs: int = 6, colors: Optional[Union[RGBcolor, List[RGBcolor]]] =
None) -> List[int]:
820 Add a tube (pipe/cylinder) to the context.
822 The tube is defined by a series of nodes (path) with radius at each node.
823 It's tessellated into triangular faces based on the number of radial divisions.
826 nodes: List of 3D points defining the tube path (at least 2 nodes)
827 radii: Radius at each node. Can be:
828 - Single float: constant radius for all nodes
829 - List of floats: radius for each node (must match nodes length)
830 ndivs: Number of radial divisions (default: 6)
831 Higher values create smoother tubes but more triangles
832 colors: Colors at each node. Can be:
834 - Single RGBcolor: constant color for all nodes
835 - List of RGBcolor: color for each node (must match nodes length)
838 List of UUIDs for all triangles created in the tube
841 >>> context = Context()
842 >>> # Create a curved tube with varying radius
843 >>> nodes = [vec3(0, 0, 0), vec3(1, 0, 0), vec3(2, 1, 0)]
844 >>> radii = [0.1, 0.2, 0.1]
845 >>> colors = [RGBcolor(1, 0, 0), RGBcolor(0, 1, 0), RGBcolor(0, 0, 1)]
846 >>> tube_uuids = context.addTube(nodes, radii, ndivs=8, colors=colors)
847 >>> print(f"Created tube with {len(tube_uuids)} triangles")
852 if not isinstance(nodes, (list, tuple)):
853 raise ValueError(f
"Nodes must be a list or tuple, got {type(nodes).__name__}")
854 if not isinstance(ndivs, int):
855 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
856 if colors
is not None and not isinstance(colors, (RGBcolor, list, tuple)):
857 raise ValueError(f
"Colors must be RGBcolor, list, tuple, or None, got {type(colors).__name__}")
861 raise ValueError(
"Tube requires at least 2 nodes")
863 raise ValueError(
"Number of radial divisions must be at least 3")
866 if isinstance(radii, (int, float)):
867 radii_list = [float(radii)] * len(nodes)
869 radii_list = [float(r)
for r
in radii]
870 if len(radii_list) != len(nodes):
871 raise ValueError(f
"Number of radii ({len(radii_list)}) must match number of nodes ({len(nodes)})")
874 if any(r <= 0
for r
in radii_list):
875 raise ValueError(
"All radii must be positive")
880 nodes_flat.extend(node.to_list())
884 return context_wrapper.addTube(self.
context, ndivs, nodes_flat, radii_list)
885 elif isinstance(colors, RGBcolor):
887 colors_flat = colors.to_list() * len(nodes)
890 if len(colors) != len(nodes):
891 raise ValueError(f
"Number of colors ({len(colors)}) must match number of nodes ({len(nodes)})")
894 colors_flat.extend(color.to_list())
896 return context_wrapper.addTubeWithColor(self.
context, ndivs, nodes_flat, radii_list, colors_flat)
899 def addBox(self, center: vec3 =
vec3(0, 0, 0), size: vec3 =
vec3(1, 1, 1),
900 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None) -> List[int]:
902 Add a rectangular box to the context.
904 The box is subdivided into patches on each face based on the specified
908 center: 3D coordinates of box center (default: origin)
909 size: Width, height, and depth of the box (default: 1x1x1)
910 subdiv: Number of subdivisions in x, y, and z directions (default: 1x1x1)
911 Higher values create more detailed surfaces
912 color: Color of the box (default: white)
915 List of UUIDs for all patches created on the box faces
918 >>> context = Context()
919 >>> # Create a blue box subdivided for detail
920 >>> box_uuids = context.addBox(
921 ... center=vec3(0, 0, 2),
922 ... size=vec3(2, 1, 0.5),
923 ... subdiv=int3(4, 2, 1),
924 ... color=RGBcolor(0, 0, 1)
926 >>> print(f"Created box with {len(box_uuids)} patches")
931 if not isinstance(center, vec3):
932 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
933 if not isinstance(size, vec3):
934 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
935 if not isinstance(subdiv, int3):
936 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
937 if color
is not None and not isinstance(color, RGBcolor):
938 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
941 if any(s <= 0
for s
in size.to_list()):
942 raise ValueError(
"All box dimensions must be positive")
943 if any(s < 1
for s
in subdiv.to_list()):
944 raise ValueError(
"All subdivision counts must be at least 1")
947 return context_wrapper.addBoxWithColor(
948 self.
context, center.to_list(), size.to_list(),
949 subdiv.to_list(), color.to_list()
952 return context_wrapper.addBox(
953 self.
context, center.to_list(), size.to_list(), subdiv.to_list()
956 def addDisk(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
957 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
958 color: Optional[Union[RGBcolor, RGBAcolor]] =
None) -> List[int]:
960 Add a disk (circular or elliptical surface) to the context.
962 A disk is a flat circular or elliptical surface tessellated into
963 triangular faces. Supports both uniform radial subdivisions and
964 separate radial/azimuthal subdivisions for finer control.
967 center: 3D coordinates of disk center (default: origin)
968 size: Semi-major and semi-minor radii of the disk (default: 1x1 circle)
969 ndivs: Number of radial divisions (int) or [radial, azimuthal] divisions (int2)
970 (default: 20). Higher values create smoother circles but more triangles.
971 rotation: Orientation of the disk (default: horizontal, normal = +z)
972 color: Color of the disk (default: white). Can be RGBcolor or RGBAcolor for transparency.
975 List of UUIDs for all triangles created in the disk
978 >>> context = Context()
979 >>> # Create a red disk at (0, 0, 1) with radius 0.5
980 >>> disk_uuids = context.addDisk(
981 ... center=vec3(0, 0, 1),
982 ... size=vec2(0.5, 0.5),
984 ... color=RGBcolor(1, 0, 0)
986 >>> print(f"Created disk with {len(disk_uuids)} triangles")
988 >>> # Create a semi-transparent blue elliptical disk
989 >>> disk_uuids = context.addDisk(
990 ... center=vec3(0, 0, 2),
991 ... size=vec2(1.0, 0.5),
993 ... rotation=SphericalCoord(1, 0.5, 0),
994 ... color=RGBAcolor(0, 0, 1, 0.5)
997 >>> # Create disk with polar/radial subdivisions for finer control
998 >>> disk_uuids = context.addDisk(
999 ... center=vec3(0, 0, 3),
1000 ... size=vec2(1, 1),
1001 ... ndivs=int2(10, 20), # 10 radial, 20 azimuthal divisions
1002 ... color=RGBcolor(0, 1, 0)
1008 if not isinstance(center, vec3):
1009 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1010 if not isinstance(size, vec2):
1011 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1012 if not isinstance(ndivs, (int, int2)):
1013 raise ValueError(f
"Ndivs must be an int or int2, got {type(ndivs).__name__}")
1014 if rotation
is not None and not isinstance(rotation, SphericalCoord):
1015 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
1016 if color
is not None and not isinstance(color, (RGBcolor, RGBAcolor)):
1017 raise ValueError(f
"Color must be an RGBcolor, RGBAcolor, or None, got {type(color).__name__}")
1020 if any(s <= 0
for s
in size.to_list()):
1021 raise ValueError(
"Disk size must be positive")
1024 if isinstance(ndivs, int):
1026 raise ValueError(
"Number of divisions must be at least 3")
1028 if any(n < 1
for n
in ndivs.to_list()):
1029 raise ValueError(
"Radial and angular divisions must be at least 1")
1032 if rotation
is None:
1037 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1040 if isinstance(ndivs, int2):
1043 if isinstance(color, RGBAcolor):
1044 return context_wrapper.addDiskPolarSubdivisionsRGBA(
1045 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1046 rotation_list, color.to_list()
1050 return context_wrapper.addDiskPolarSubdivisions(
1051 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1052 rotation_list, color.to_list()
1056 color_list = [1.0, 1.0, 1.0]
1057 return context_wrapper.addDiskPolarSubdivisions(
1058 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1059 rotation_list, color_list
1064 if isinstance(color, RGBAcolor):
1066 return context_wrapper.addDiskWithRGBAColor(
1067 self.
context, ndivs, center.to_list(), size.to_list(),
1068 rotation_list, color.to_list()
1072 return context_wrapper.addDiskWithColor(
1073 self.
context, ndivs, center.to_list(), size.to_list(),
1074 rotation_list, color.to_list()
1078 return context_wrapper.addDiskWithRotation(
1079 self.
context, ndivs, center.to_list(), size.to_list(),
1083 def addCone(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1084 ndivs: int = 20, color: Optional[RGBcolor] =
None) -> List[int]:
1086 Add a cone (or cylinder/frustum) to the context.
1088 A cone is a 3D shape connecting two circular cross-sections with
1089 potentially different radii. When radii are equal, creates a cylinder.
1090 When one radius is zero, creates a true cone.
1093 node0: 3D coordinates of the base center
1094 node1: 3D coordinates of the apex center
1095 radius0: Radius at base (node0). Use 0 for pointed end.
1096 radius1: Radius at apex (node1). Use 0 for pointed end.
1097 ndivs: Number of radial divisions for tessellation (default: 20)
1098 color: Color of the cone (default: white)
1101 List of UUIDs for all triangles created in the cone
1104 >>> context = Context()
1105 >>> # Create a cylinder (equal radii)
1106 >>> cylinder_uuids = context.addCone(
1107 ... node0=vec3(0, 0, 0),
1108 ... node1=vec3(0, 0, 2),
1114 >>> # Create a true cone (one radius = 0)
1115 >>> cone_uuids = context.addCone(
1116 ... node0=vec3(1, 0, 0),
1117 ... node1=vec3(1, 0, 1.5),
1121 ... color=RGBcolor(1, 0, 0)
1124 >>> # Create a frustum (different radii)
1125 >>> frustum_uuids = context.addCone(
1126 ... node0=vec3(2, 0, 0),
1127 ... node1=vec3(2, 0, 1),
1136 if not isinstance(node0, vec3):
1137 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1138 if not isinstance(node1, vec3):
1139 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1140 if not isinstance(ndivs, int):
1141 raise ValueError(f
"ndivs must be an int, got {type(ndivs).__name__}")
1142 if color
is not None and not isinstance(color, RGBcolor):
1143 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1146 if radius0 < 0
or radius1 < 0:
1147 raise ValueError(
"Radii must be non-negative")
1149 raise ValueError(
"Number of radial divisions must be at least 3")
1153 return context_wrapper.addConeWithColor(
1154 self.
context, ndivs, node0.to_list(), node1.to_list(),
1155 radius0, radius1, color.to_list()
1158 return context_wrapper.addCone(
1159 self.
context, ndivs, node0.to_list(), node1.to_list(),
1164 radius: Union[float, vec3] = 1.0, ndivs: int = 20,
1165 color: Optional[RGBcolor] =
None,
1166 texturefile: Optional[str] =
None) -> int:
1168 Add a spherical or ellipsoidal compound object to the context.
1170 Creates a sphere or ellipsoid as a compound object with a trackable object ID.
1171 Primitives within the object are registered as children of the object.
1174 center: Center position of sphere/ellipsoid (default: origin)
1175 radius: Radius as float (sphere) or vec3 (ellipsoid) (default: 1.0)
1176 ndivs: Number of tessellation divisions (default: 20)
1177 color: Optional RGB color
1178 texturefile: Optional texture image file path
1181 Object ID of the created compound object
1184 ValueError: If parameters are invalid
1185 NotImplementedError: If object-returning functions unavailable
1188 >>> # Create a basic sphere at origin
1189 >>> obj_id = ctx.addSphereObject()
1191 >>> # Create a colored sphere
1192 >>> obj_id = ctx.addSphereObject(
1193 ... center=vec3(0, 0, 5),
1195 ... color=RGBcolor(1, 0, 0)
1198 >>> # Create an ellipsoid (stretched sphere)
1199 >>> obj_id = ctx.addSphereObject(
1200 ... center=vec3(10, 0, 0),
1201 ... radius=vec3(2, 1, 1), # Elongated in x-direction
1208 if not isinstance(center, vec3):
1209 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1210 if not isinstance(radius, (int, float, vec3)):
1211 raise ValueError(f
"Radius must be a number or vec3, got {type(radius).__name__}")
1212 if color
is not None and not isinstance(color, RGBcolor):
1213 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1217 raise ValueError(
"Number of divisions must be at least 3")
1220 is_ellipsoid = isinstance(radius, vec3)
1226 return context_wrapper.addSphereObject_ellipsoid_texture(
1227 self.
context, ndivs, center.to_list(), radius.to_list(), texturefile
1230 return context_wrapper.addSphereObject_ellipsoid_color(
1231 self.
context, ndivs, center.to_list(), radius.to_list(), color.to_list()
1234 return context_wrapper.addSphereObject_ellipsoid(
1235 self.
context, ndivs, center.to_list(), radius.to_list()
1240 return context_wrapper.addSphereObject_texture(
1241 self.
context, ndivs, center.to_list(), radius, texturefile
1244 return context_wrapper.addSphereObject_color(
1245 self.
context, ndivs, center.to_list(), radius, color.to_list()
1248 return context_wrapper.addSphereObject_basic(
1249 self.
context, ndivs, center.to_list(), radius
1254 subdiv: int2 =
int2(1, 1),
1255 color: Optional[RGBcolor] =
None,
1256 texturefile: Optional[str] =
None,
1257 texture_repeat: Optional[int2] =
None) -> int:
1259 Add a tiled patch (subdivided patch) as a compound object to the context.
1261 Creates a rectangular patch subdivided into a grid of smaller patches,
1262 registered as a compound object with a trackable object ID.
1265 center: Center position of tile (default: origin)
1266 size: Size in x and y directions (default: 1x1)
1267 rotation: Spherical rotation (default: no rotation)
1268 subdiv: Number of subdivisions in x and y (default: 1x1)
1269 color: Optional RGB color
1270 texturefile: Optional texture image file path
1271 texture_repeat: Optional texture repetitions in x and y
1274 Object ID of the created compound object
1277 ValueError: If parameters are invalid
1278 NotImplementedError: If object-returning functions unavailable
1281 >>> # Create a basic 2x2 tile
1282 >>> obj_id = ctx.addTileObject(
1283 ... center=vec3(0, 0, 0),
1284 ... size=vec2(10, 10),
1285 ... subdiv=int2(2, 2)
1288 >>> # Create a colored tile with rotation
1289 >>> obj_id = ctx.addTileObject(
1290 ... center=vec3(5, 0, 0),
1291 ... size=vec2(10, 5),
1292 ... rotation=SphericalCoord(1, 0, 45),
1293 ... subdiv=int2(4, 2),
1294 ... color=RGBcolor(0, 1, 0)
1300 if not isinstance(center, vec3):
1301 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1302 if not isinstance(size, vec2):
1303 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1304 if not isinstance(rotation, SphericalCoord):
1305 raise ValueError(f
"Rotation must be a SphericalCoord, got {type(rotation).__name__}")
1306 if not isinstance(subdiv, int2):
1307 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
1308 if color
is not None and not isinstance(color, RGBcolor):
1309 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1310 if texture_repeat
is not None and not isinstance(texture_repeat, int2):
1311 raise ValueError(f
"texture_repeat must be an int2 or None, got {type(texture_repeat).__name__}")
1314 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1317 if texture_repeat
is not None:
1318 if texturefile
is None:
1319 raise ValueError(
"texture_repeat requires texturefile")
1320 return context_wrapper.addTileObject_texture_repeat(
1321 self.
context, center.to_list(), size.to_list(), rotation_list,
1322 subdiv.to_list(), texturefile, texture_repeat.to_list()
1325 return context_wrapper.addTileObject_texture(
1326 self.
context, center.to_list(), size.to_list(), rotation_list,
1327 subdiv.to_list(), texturefile
1330 return context_wrapper.addTileObject_color(
1331 self.
context, center.to_list(), size.to_list(), rotation_list,
1332 subdiv.to_list(), color.to_list()
1335 return context_wrapper.addTileObject_basic(
1336 self.
context, center.to_list(), size.to_list(), rotation_list,
1341 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None,
1342 texturefile: Optional[str] =
None, reverse_normals: bool =
False) -> int:
1344 Add a rectangular box (prism) as a compound object to the context.
1347 center: Center position (default: origin)
1348 size: Size in x, y, z directions (default: 1x1x1)
1349 subdiv: Subdivisions in x, y, z (default: 1x1x1)
1350 color: Optional RGB color
1351 texturefile: Optional texture file path
1352 reverse_normals: Reverse normal directions (default: False)
1355 Object ID of the created compound object
1360 if not isinstance(center, vec3):
1361 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1362 if not isinstance(size, vec3):
1363 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
1364 if not isinstance(subdiv, int3):
1365 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
1366 if color
is not None and not isinstance(color, RGBcolor):
1367 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1371 return context_wrapper.addBoxObject_texture_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile, reverse_normals)
1373 return context_wrapper.addBoxObject_color_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list(), reverse_normals)
1375 raise ValueError(
"reverse_normals requires either color or texturefile")
1377 return context_wrapper.addBoxObject_texture(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile)
1379 return context_wrapper.addBoxObject_color(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list())
1381 return context_wrapper.addBoxObject_basic(self.
context, center.to_list(), size.to_list(), subdiv.to_list())
1383 def addConeObject(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1384 ndivs: int = 20, color: Optional[RGBcolor] =
None,
1385 texturefile: Optional[str] =
None) -> int:
1387 Add a cone/cylinder/frustum as a compound object to the context.
1390 node0: Base position
1392 radius0: Radius at base
1393 radius1: Radius at top
1394 ndivs: Number of radial divisions (default: 20)
1395 color: Optional RGB color
1396 texturefile: Optional texture file path
1399 Object ID of the created compound object
1404 if not isinstance(node0, vec3):
1405 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1406 if not isinstance(node1, vec3):
1407 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1408 if not isinstance(radius0, (int, float)):
1409 raise ValueError(f
"radius0 must be a number, got {type(radius0).__name__}")
1410 if not isinstance(radius1, (int, float)):
1411 raise ValueError(f
"radius1 must be a number, got {type(radius1).__name__}")
1412 if color
is not None and not isinstance(color, RGBcolor):
1413 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1416 return context_wrapper.addConeObject_texture(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, texturefile)
1418 return context_wrapper.addConeObject_color(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, color.to_list())
1420 return context_wrapper.addConeObject_basic(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1)
1423 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
1424 color: Optional[Union[RGBcolor, RGBAcolor]] =
None,
1425 texturefile: Optional[str] =
None) -> int:
1427 Add a disk as a compound object to the context.
1430 center: Center position (default: origin)
1431 size: Semi-major and semi-minor radii (default: 1x1)
1432 ndivs: int (uniform) or int2 (polar/radial subdivisions) (default: 20)
1433 rotation: Optional spherical rotation
1434 color: Optional RGB or RGBA color
1435 texturefile: Optional texture file path
1438 Object ID of the created compound object
1442 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
if rotation
else [1, 0, 0]
1443 is_polar = isinstance(ndivs, int2)
1447 return context_wrapper.addDiskObject_polar_texture(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, texturefile)
1449 if isinstance(color, RGBAcolor):
1450 return context_wrapper.addDiskObject_polar_rgba(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1452 return context_wrapper.addDiskObject_polar_color(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1454 return context_wrapper.addDiskObject_polar_color(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list,
RGBcolor(0.5, 0.5, 0.5).to_list())
1457 return context_wrapper.addDiskObject_texture(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, texturefile)
1459 if isinstance(color, RGBAcolor):
1460 return context_wrapper.addDiskObject_rgba(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1462 return context_wrapper.addDiskObject_color(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1464 return context_wrapper.addDiskObject_rotation(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list)
1466 return context_wrapper.addDiskObject_basic(self.
context, ndivs, center.to_list(), size.to_list())
1468 def addTubeObject(self, ndivs: int, nodes: List[vec3], radii: List[float],
1469 colors: Optional[List[RGBcolor]] =
None,
1470 texturefile: Optional[str] =
None,
1471 texture_uv: Optional[List[float]] =
None) -> int:
1473 Add a tube as a compound object to the context.
1476 ndivs: Number of radial subdivisions
1477 nodes: List of vec3 positions defining tube segments
1478 radii: List of radii at each node
1479 colors: Optional list of RGB colors for each segment
1480 texturefile: Optional texture file path
1481 texture_uv: Optional UV coordinates for texture mapping
1484 Object ID of the created compound object
1489 if not isinstance(nodes, (list, tuple)):
1490 raise ValueError(f
"Nodes must be a list, got {type(nodes).__name__}")
1491 for i, node
in enumerate(nodes):
1492 if not isinstance(node, vec3):
1493 raise ValueError(f
"nodes[{i}] must be a vec3, got {type(node).__name__}")
1494 if not isinstance(radii, (list, tuple)):
1495 raise ValueError(f
"Radii must be a list, got {type(radii).__name__}")
1496 if colors
is not None:
1497 if not isinstance(colors, (list, tuple)):
1498 raise ValueError(f
"Colors must be a list or None, got {type(colors).__name__}")
1499 for i, c
in enumerate(colors):
1500 if not isinstance(c, RGBcolor):
1501 raise ValueError(f
"colors[{i}] must be an RGBcolor, got {type(c).__name__}")
1504 raise ValueError(
"Tube requires at least 2 nodes")
1505 if len(radii) != len(nodes):
1506 raise ValueError(
"Number of radii must match number of nodes")
1508 nodes_flat = [coord
for node
in nodes
for coord
in node.to_list()]
1510 if texture_uv
is not None:
1511 if texturefile
is None:
1512 raise ValueError(
"texture_uv requires texturefile")
1513 return context_wrapper.addTubeObject_texture_uv(self.
context, ndivs, nodes_flat, radii, texturefile, texture_uv)
1515 return context_wrapper.addTubeObject_texture(self.
context, ndivs, nodes_flat, radii, texturefile)
1517 if len(colors) != len(nodes):
1518 raise ValueError(
"Number of colors must match number of nodes")
1519 colors_flat = [c
for color
in colors
for c
in color.to_list()]
1520 return context_wrapper.addTubeObject_color(self.
context, ndivs, nodes_flat, radii, colors_flat)
1522 return context_wrapper.addTubeObject_basic(self.
context, ndivs, nodes_flat, radii)
1524 def copyPrimitive(self, UUID: Union[int, List[int]]) -> Union[int, List[int]]:
1526 Copy one or more primitives.
1528 Creates a duplicate of the specified primitive(s) with all associated data.
1529 The copy is placed at the same location as the original.
1532 UUID: Single primitive UUID or list of UUIDs to copy
1535 Single UUID of copied primitive (if UUID is int) or
1536 List of UUIDs of copied primitives (if UUID is list)
1539 >>> context = Context()
1540 >>> original_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1541 >>> # Copy single primitive
1542 >>> copied_uuid = context.copyPrimitive(original_uuid)
1543 >>> # Copy multiple primitives
1544 >>> copied_uuids = context.copyPrimitive([uuid1, uuid2, uuid3])
1548 if isinstance(UUID, int):
1549 return context_wrapper.copyPrimitive(self.
context, UUID)
1550 elif isinstance(UUID, list):
1551 return context_wrapper.copyPrimitives(self.
context, UUID)
1553 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1557 Copy all primitive data from source to destination primitive.
1559 Copies all associated data (primitive data fields) from the source
1560 primitive to the destination primitive. Both primitives must already exist.
1563 sourceUUID: UUID of the source primitive
1564 destinationUUID: UUID of the destination primitive
1567 >>> context = Context()
1568 >>> source_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1569 >>> dest_uuid = context.addPatch(center=vec3(1, 0, 0), size=vec2(1, 1))
1570 >>> context.setPrimitiveData(source_uuid, "temperature", 25.5)
1571 >>> context.copyPrimitiveData(source_uuid, dest_uuid)
1572 >>> # dest_uuid now has temperature data
1576 if not isinstance(sourceUUID, int):
1577 raise ValueError(f
"sourceUUID must be int, got {type(sourceUUID).__name__}")
1578 if not isinstance(destinationUUID, int):
1579 raise ValueError(f
"destinationUUID must be int, got {type(destinationUUID).__name__}")
1581 context_wrapper.copyPrimitiveData(self.
context, sourceUUID, destinationUUID)
1583 def copyObject(self, ObjID: Union[int, List[int]]) -> Union[int, List[int]]:
1585 Copy one or more compound objects.
1587 Creates a duplicate of the specified compound object(s) with all
1588 associated primitives and data. The copy is placed at the same location
1592 ObjID: Single object ID or list of object IDs to copy
1595 Single object ID of copied object (if ObjID is int) or
1596 List of object IDs of copied objects (if ObjID is list)
1599 >>> context = Context()
1600 >>> original_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1601 >>> # Copy single object
1602 >>> copied_obj = context.copyObject(original_obj)
1603 >>> # Copy multiple objects
1604 >>> copied_objs = context.copyObject([obj1, obj2, obj3])
1608 if isinstance(ObjID, int):
1609 return context_wrapper.copyObject(self.
context, ObjID)
1610 elif isinstance(ObjID, list):
1611 return context_wrapper.copyObjects(self.
context, ObjID)
1613 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1615 def copyObjectData(self, source_objID: int, destination_objID: int) ->
None:
1617 Copy all object data from source to destination compound object.
1619 Copies all associated data (object data fields) from the source
1620 compound object to the destination object. Both objects must already exist.
1623 source_objID: Object ID of the source compound object
1624 destination_objID: Object ID of the destination compound object
1627 >>> context = Context()
1628 >>> source_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1629 >>> dest_obj = context.addTile(center=vec3(2, 0, 0), size=vec2(2, 2))
1630 >>> context.setObjectData(source_obj, "material", "wood")
1631 >>> context.copyObjectData(source_obj, dest_obj)
1632 >>> # dest_obj now has material data
1636 if not isinstance(source_objID, int):
1637 raise ValueError(f
"source_objID must be int, got {type(source_objID).__name__}")
1638 if not isinstance(destination_objID, int):
1639 raise ValueError(f
"destination_objID must be int, got {type(destination_objID).__name__}")
1641 context_wrapper.copyObjectData(self.
context, source_objID, destination_objID)
1645 Translate one or more primitives by a shift vector.
1647 Moves the specified primitive(s) by the given shift vector without
1648 changing their orientation or size.
1651 UUID: Single primitive UUID or list of UUIDs to translate
1652 shift: 3D vector representing the translation [x, y, z]
1655 >>> context = Context()
1656 >>> patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1657 >>> # Translate single primitive
1658 >>> context.translatePrimitive(patch_uuid, vec3(1, 0, 0)) # Move 1 unit in x
1659 >>> # Translate multiple primitives
1660 >>> context.translatePrimitive([uuid1, uuid2, uuid3], vec3(0, 0, 1)) # Move 1 unit in z
1665 if not isinstance(shift, vec3):
1666 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1668 if isinstance(UUID, int):
1669 context_wrapper.translatePrimitive(self.
context, UUID, shift.to_list())
1670 elif isinstance(UUID, list):
1671 context_wrapper.translatePrimitives(self.
context, UUID, shift.to_list())
1673 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1675 def translateObject(self, ObjID: Union[int, List[int]], shift: vec3) ->
None:
1677 Translate one or more compound objects by a shift vector.
1679 Moves the specified compound object(s) and all their constituent
1680 primitives by the given shift vector without changing orientation or size.
1683 ObjID: Single object ID or list of object IDs to translate
1684 shift: 3D vector representing the translation [x, y, z]
1687 >>> context = Context()
1688 >>> tile_uuids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1689 >>> obj_id = context.getPrimitiveParentObjectID(tile_uuids[0]) # Get object ID
1690 >>> # Translate single object
1691 >>> context.translateObject(obj_id, vec3(5, 0, 0)) # Move 5 units in x
1692 >>> # Translate multiple objects
1693 >>> context.translateObject([obj1, obj2, obj3], vec3(0, 2, 0)) # Move 2 units in y
1698 if not isinstance(shift, vec3):
1699 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1701 if isinstance(ObjID, int):
1702 context_wrapper.translateObject(self.
context, ObjID, shift.to_list())
1703 elif isinstance(ObjID, list):
1704 context_wrapper.translateObjects(self.
context, ObjID, shift.to_list())
1706 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1709 axis: Union[str, vec3], origin: Optional[vec3] =
None) ->
None:
1711 Rotate one or more primitives.
1714 UUID: Single UUID or list of UUIDs to rotate
1715 angle: Rotation angle in radians
1716 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1717 origin: Optional rotation origin point. If None, rotates about primitive center.
1718 If provided with string axis, raises ValueError.
1721 ValueError: If axis is invalid or if origin is provided with string axis
1726 if isinstance(axis, str):
1727 if axis
not in (
'x',
'y',
'z'):
1728 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1729 if origin
is not None:
1730 raise ValueError(
"origin parameter cannot be used with string axis")
1733 if isinstance(UUID, int):
1734 context_wrapper.rotatePrimitive_axisString(self.
context, UUID, angle, axis)
1735 elif isinstance(UUID, list):
1736 context_wrapper.rotatePrimitives_axisString(self.
context, UUID, angle, axis)
1738 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1740 elif isinstance(axis, vec3):
1741 axis_list = axis.to_list()
1744 if all(abs(v) < 1e-10
for v
in axis_list):
1745 raise ValueError(
"axis vector cannot be zero")
1749 if isinstance(UUID, int):
1750 context_wrapper.rotatePrimitive_axisVector(self.
context, UUID, angle, axis_list)
1751 elif isinstance(UUID, list):
1752 context_wrapper.rotatePrimitives_axisVector(self.
context, UUID, angle, axis_list)
1754 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1757 if not isinstance(origin, vec3):
1758 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1760 origin_list = origin.to_list()
1761 if isinstance(UUID, int):
1762 context_wrapper.rotatePrimitive_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1763 elif isinstance(UUID, list):
1764 context_wrapper.rotatePrimitives_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1766 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1768 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1770 def rotateObject(self, ObjID: Union[int, List[int]], angle: float,
1771 axis: Union[str, vec3], origin: Optional[vec3] =
None,
1772 about_origin: bool =
False) ->
None:
1774 Rotate one or more objects.
1777 ObjID: Single object ID or list of object IDs to rotate
1778 angle: Rotation angle in radians
1779 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1780 origin: Optional rotation origin point. If None, rotates about object center.
1781 If provided with string axis, raises ValueError.
1782 about_origin: If True, rotate about global origin (0,0,0). Cannot be used with origin parameter.
1785 ValueError: If axis is invalid or if origin and about_origin are both specified
1790 if origin
is not None and about_origin:
1791 raise ValueError(
"Cannot specify both origin and about_origin")
1794 if isinstance(axis, str):
1795 if axis
not in (
'x',
'y',
'z'):
1796 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1797 if origin
is not None:
1798 raise ValueError(
"origin parameter cannot be used with string axis")
1800 raise ValueError(
"about_origin parameter cannot be used with string axis")
1803 if isinstance(ObjID, int):
1804 context_wrapper.rotateObject_axisString(self.
context, ObjID, angle, axis)
1805 elif isinstance(ObjID, list):
1806 context_wrapper.rotateObjects_axisString(self.
context, ObjID, angle, axis)
1808 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1810 elif isinstance(axis, vec3):
1811 axis_list = axis.to_list()
1814 if all(abs(v) < 1e-10
for v
in axis_list):
1815 raise ValueError(
"axis vector cannot be zero")
1819 if isinstance(ObjID, int):
1820 context_wrapper.rotateObjectAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1821 elif isinstance(ObjID, list):
1822 context_wrapper.rotateObjectsAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1824 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1825 elif origin
is None:
1827 if isinstance(ObjID, int):
1828 context_wrapper.rotateObject_axisVector(self.
context, ObjID, angle, axis_list)
1829 elif isinstance(ObjID, list):
1830 context_wrapper.rotateObjects_axisVector(self.
context, ObjID, angle, axis_list)
1832 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1835 if not isinstance(origin, vec3):
1836 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1838 origin_list = origin.to_list()
1839 if isinstance(ObjID, int):
1840 context_wrapper.rotateObject_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1841 elif isinstance(ObjID, list):
1842 context_wrapper.rotateObjects_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1844 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1846 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1848 def scalePrimitive(self, UUID: Union[int, List[int]], scale: vec3, point: Optional[vec3] =
None) ->
None:
1850 Scale one or more primitives.
1853 UUID: Single UUID or list of UUIDs to scale
1854 scale: Scale factors as vec3(x, y, z)
1855 point: Optional point to scale about. If None, scales about primitive center.
1858 ValueError: If scale or point parameters are invalid
1862 if not isinstance(scale, vec3):
1863 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1865 scale_list = scale.to_list()
1869 if isinstance(UUID, int):
1870 context_wrapper.scalePrimitive(self.
context, UUID, scale_list)
1871 elif isinstance(UUID, list):
1872 context_wrapper.scalePrimitives(self.
context, UUID, scale_list)
1874 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1877 if not isinstance(point, vec3):
1878 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1880 point_list = point.to_list()
1881 if isinstance(UUID, int):
1882 context_wrapper.scalePrimitiveAboutPoint(self.
context, UUID, scale_list, point_list)
1883 elif isinstance(UUID, list):
1884 context_wrapper.scalePrimitivesAboutPoint(self.
context, UUID, scale_list, point_list)
1886 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1888 def scaleObject(self, ObjID: Union[int, List[int]], scale: vec3,
1889 point: Optional[vec3] =
None, about_center: bool =
False,
1890 about_origin: bool =
False) ->
None:
1892 Scale one or more objects.
1895 ObjID: Single object ID or list of object IDs to scale
1896 scale: Scale factors as vec3(x, y, z)
1897 point: Optional point to scale about
1898 about_center: If True, scale about object center (default behavior)
1899 about_origin: If True, scale about global origin (0,0,0)
1902 ValueError: If parameters are invalid or conflicting options specified
1907 options_count = sum([point
is not None, about_center, about_origin])
1908 if options_count > 1:
1909 raise ValueError(
"Cannot specify multiple scaling options (point, about_center, about_origin)")
1911 if not isinstance(scale, vec3):
1912 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1914 scale_list = scale.to_list()
1918 if isinstance(ObjID, int):
1919 context_wrapper.scaleObjectAboutOrigin(self.
context, ObjID, scale_list)
1920 elif isinstance(ObjID, list):
1921 context_wrapper.scaleObjectsAboutOrigin(self.
context, ObjID, scale_list)
1923 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1926 if isinstance(ObjID, int):
1927 context_wrapper.scaleObjectAboutCenter(self.
context, ObjID, scale_list)
1928 elif isinstance(ObjID, list):
1929 context_wrapper.scaleObjectsAboutCenter(self.
context, ObjID, scale_list)
1931 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1932 elif point
is not None:
1934 if not isinstance(point, vec3):
1935 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1937 point_list = point.to_list()
1938 if isinstance(ObjID, int):
1939 context_wrapper.scaleObjectAboutPoint(self.
context, ObjID, scale_list, point_list)
1940 elif isinstance(ObjID, list):
1941 context_wrapper.scaleObjectsAboutPoint(self.
context, ObjID, scale_list, point_list)
1943 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1946 if isinstance(ObjID, int):
1947 context_wrapper.scaleObject(self.
context, ObjID, scale_list)
1948 elif isinstance(ObjID, list):
1949 context_wrapper.scaleObjects(self.
context, ObjID, scale_list)
1951 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1955 Scale the length of a Cone object by scaling the distance between its two nodes.
1958 ObjID: Object ID of the Cone to scale
1959 scale_factor: Factor by which to scale the cone length (e.g., 2.0 doubles length)
1962 ValueError: If ObjID is not an integer or scale_factor is invalid
1963 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
1966 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
1967 method, enforcing better encapsulation.
1970 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
1971 >>> context.scaleConeObjectLength(cone_id, 1.5) # Make cone 50% longer
1973 if not isinstance(ObjID, int):
1974 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
1975 if not isinstance(scale_factor, (int, float)):
1976 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
1977 if scale_factor <= 0:
1978 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
1980 context_wrapper.scaleConeObjectLength(self.
context, ObjID, float(scale_factor))
1984 Scale the girth of a Cone object by scaling the radii at both nodes.
1987 ObjID: Object ID of the Cone to scale
1988 scale_factor: Factor by which to scale the cone girth (e.g., 2.0 doubles girth)
1991 ValueError: If ObjID is not an integer or scale_factor is invalid
1992 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
1995 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
1996 method, enforcing better encapsulation.
1999 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
2000 >>> context.scaleConeObjectGirth(cone_id, 2.0) # Double the cone girth
2002 if not isinstance(ObjID, int):
2003 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
2004 if not isinstance(scale_factor, (int, float)):
2005 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
2006 if scale_factor <= 0:
2007 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
2009 context_wrapper.scaleConeObjectGirth(self.
context, ObjID, float(scale_factor))
2011 def loadPLY(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2012 rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None,
2013 upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2015 Load geometry from a PLY (Stanford Polygon) file.
2018 filename: Path to the PLY file to load
2019 origin: Origin point for positioning the geometry (optional)
2020 height: Height scaling factor (optional)
2021 rotation: Rotation to apply to the geometry (optional)
2022 color: Default color for geometry without color data (optional)
2023 upaxis: Up axis orientation ("YUP" or "ZUP")
2024 silent: If True, suppress loading output messages
2027 List of UUIDs for the loaded primitives
2032 if origin
is not None and not isinstance(origin, vec3):
2033 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2034 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2035 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2036 if color
is not None and not isinstance(color, RGBcolor):
2037 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2042 if origin
is None and height
is None and rotation
is None and color
is None:
2044 return context_wrapper.loadPLY(self.
context, validated_filename, silent)
2046 elif origin
is not None and height
is not None and rotation
is None and color
is None:
2048 return context_wrapper.loadPLYWithOriginHeight(self.
context, validated_filename, origin.to_list(), height, upaxis, silent)
2050 elif origin
is not None and height
is not None and rotation
is not None and color
is None:
2052 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2053 return context_wrapper.loadPLYWithOriginHeightRotation(self.
context, validated_filename, origin.to_list(), height, rotation_list, upaxis, silent)
2055 elif origin
is not None and height
is not None and rotation
is None and color
is not None:
2057 return context_wrapper.loadPLYWithOriginHeightColor(self.
context, validated_filename, origin.to_list(), height, color.to_list(), upaxis, silent)
2059 elif origin
is not None and height
is not None and rotation
is not None and color
is not None:
2061 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2062 return context_wrapper.loadPLYWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation_list, color.to_list(), upaxis, silent)
2065 raise ValueError(
"Invalid parameter combination. When using transformations, both origin and height are required.")
2067 def loadOBJ(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2068 scale: Optional[vec3] =
None, rotation: Optional[SphericalCoord] =
None,
2069 color: Optional[RGBcolor] =
None, upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2071 Load geometry from an OBJ (Wavefront) file.
2074 filename: Path to the OBJ file to load
2075 origin: Origin point for positioning the geometry (optional)
2076 height: Height scaling factor (optional, alternative to scale)
2077 scale: Scale factor for all dimensions (optional, alternative to height)
2078 rotation: Rotation to apply to the geometry (optional)
2079 color: Default color for geometry without color data (optional)
2080 upaxis: Up axis orientation ("YUP" or "ZUP")
2081 silent: If True, suppress loading output messages
2084 List of UUIDs for the loaded primitives
2089 if origin
is not None and not isinstance(origin, vec3):
2090 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2091 if scale
is not None and not isinstance(scale, vec3):
2092 raise ValueError(f
"Scale must be a vec3 or None, got {type(scale).__name__}")
2093 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2094 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2095 if color
is not None and not isinstance(color, RGBcolor):
2096 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2101 if origin
is None and height
is None and scale
is None and rotation
is None and color
is None:
2103 return context_wrapper.loadOBJ(self.
context, validated_filename, silent)
2105 elif origin
is not None and height
is not None and scale
is None and rotation
is not None and color
is not None:
2107 return context_wrapper.loadOBJWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), silent)
2109 elif origin
is not None and height
is not None and scale
is None and rotation
is not None and color
is not None and upaxis !=
"YUP":
2111 return context_wrapper.loadOBJWithOriginHeightRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), upaxis, silent)
2113 elif origin
is not None and scale
is not None and rotation
is not None and color
is not None:
2115 return context_wrapper.loadOBJWithOriginScaleRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), scale.to_list(), rotation.to_list(), color.to_list(), upaxis, silent)
2118 raise ValueError(
"Invalid parameter combination. For OBJ loading, you must provide either: " +
2119 "1) No parameters (simple load), " +
2120 "2) origin + height + rotation + color, " +
2121 "3) origin + height + rotation + color + upaxis, or " +
2122 "4) origin + scale + rotation + color + upaxis")
2124 def loadXML(self, filename: str, quiet: bool =
False) -> List[int]:
2126 Load geometry from a Helios XML file.
2129 filename: Path to the XML file to load
2130 quiet: If True, suppress loading output messages
2133 List of UUIDs for the loaded primitives
2139 return context_wrapper.loadXML(self.
context, validated_filename, quiet)
2141 def writePLY(self, filename: str, UUIDs: Optional[List[int]] =
None) ->
None:
2143 Write geometry to a PLY (Stanford Polygon) file.
2146 filename: Path to the output PLY file
2147 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2150 ValueError: If filename is invalid or UUIDs are invalid
2151 PermissionError: If output directory is not writable
2152 FileNotFoundError: If UUIDs do not exist in context
2153 RuntimeError: If Context is in mock mode
2156 >>> context.writePLY("output.ply") # Export all primitives
2157 >>> context.writePLY("subset.ply", [uuid1, uuid2]) # Export specific primitives
2166 context_wrapper.writePLY(self.
context, validated_filename)
2170 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2177 context_wrapper.writePLYWithUUIDs(self.
context, validated_filename, UUIDs)
2179 def writeOBJ(self, filename: str, UUIDs: Optional[List[int]] =
None,
2180 primitive_data_fields: Optional[List[str]] =
None,
2181 write_normals: bool =
False, silent: bool =
False) ->
None:
2183 Write geometry to an OBJ (Wavefront) file.
2186 filename: Path to the output OBJ file
2187 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2188 primitive_data_fields: Optional list of primitive data field names to export
2189 write_normals: Whether to include vertex normals in the output
2190 silent: Whether to suppress output messages during export
2193 ValueError: If filename is invalid, UUIDs are invalid, or data fields don't exist
2194 PermissionError: If output directory is not writable
2195 FileNotFoundError: If UUIDs do not exist in context
2196 RuntimeError: If Context is in mock mode
2199 >>> context.writeOBJ("output.obj") # Export all primitives
2200 >>> context.writeOBJ("subset.obj", [uuid1, uuid2]) # Export specific primitives
2201 >>> context.writeOBJ("with_data.obj", [uuid1], ["temperature", "area"]) # Export with data
2210 context_wrapper.writeOBJ(self.
context, validated_filename, write_normals, silent)
2211 elif primitive_data_fields
is None:
2214 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2220 context_wrapper.writeOBJWithUUIDs(self.
context, validated_filename, UUIDs, write_normals, silent)
2224 raise ValueError(
"UUIDs list cannot be empty when exporting primitive data")
2225 if not primitive_data_fields:
2226 raise ValueError(
"primitive_data_fields list cannot be empty")
2235 context_wrapper.writeOBJWithPrimitiveData(self.
context, validated_filename, UUIDs, primitive_data_fields, write_normals, silent)
2238 UUIDs: Optional[List[int]] =
None,
2239 print_header: bool =
False) ->
None:
2241 Write primitive data to an ASCII text file.
2243 Outputs a space-separated text file where each row corresponds to a primitive
2244 and each column corresponds to a primitive data label.
2247 filename: Path to the output file
2248 column_labels: List of primitive data labels to include as columns.
2249 Use "UUID" to include primitive UUIDs as a column.
2250 The order determines the column order in the output file.
2251 UUIDs: Optional list of primitive UUIDs to include. If None, includes all primitives.
2252 print_header: If True, writes column labels as the first line of the file
2255 ValueError: If filename is invalid, column_labels is empty, or UUIDs list is empty when provided
2256 HeliosFileIOError: If file cannot be written
2257 HeliosRuntimeError: If a column label doesn't exist for any primitive
2260 >>> # Write temperature and area for all primitives
2261 >>> context.writePrimitiveData("output.txt", ["UUID", "temperature", "area"])
2263 >>> # Write with header row
2264 >>> context.writePrimitiveData("output.txt", ["UUID", "radiation_flux"], print_header=True)
2266 >>> # Write only for selected primitives
2267 >>> context.writePrimitiveData("subset.txt", ["temperature"], UUIDs=[uuid1, uuid2])
2272 if not column_labels:
2273 raise ValueError(
"column_labels list cannot be empty")
2280 context_wrapper.writePrimitiveData(self.
context, validated_filename, column_labels, print_header)
2284 raise ValueError(
"UUIDs list cannot be empty when provided. Use UUIDs=None to include all primitives")
2290 context_wrapper.writePrimitiveDataWithUUIDs(self.
context, validated_filename, column_labels, UUIDs, print_header)
2293 colors: Optional[np.ndarray] =
None) -> List[int]:
2295 Add triangles from NumPy arrays (compatible with trimesh, Open3D format).
2298 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2299 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2300 colors: Optional NumPy array of shape (N, 3) or (M, 3) containing RGB colors as float32/float64
2301 If shape (N, 3): per-vertex colors
2302 If shape (M, 3): per-triangle colors
2305 List of UUIDs for the added triangles
2308 ValueError: If array dimensions are invalid
2311 if vertices.ndim != 2
or vertices.shape[1] != 3:
2312 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2313 if faces.ndim != 2
or faces.shape[1] != 3:
2314 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2317 max_vertex_index = np.max(faces)
2318 if max_vertex_index >= vertices.shape[0]:
2319 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2322 per_vertex_colors =
False
2323 per_triangle_colors =
False
2324 if colors
is not None:
2325 if colors.ndim != 2
or colors.shape[1] != 3:
2326 raise ValueError(f
"Colors array must have shape (N, 3) or (M, 3), got {colors.shape}")
2327 if colors.shape[0] == vertices.shape[0]:
2328 per_vertex_colors =
True
2329 elif colors.shape[0] == faces.shape[0]:
2330 per_triangle_colors =
True
2332 raise ValueError(f
"Colors array shape {colors.shape} doesn't match vertices ({vertices.shape[0]},) or faces ({faces.shape[0]},)")
2335 vertices_float = vertices.astype(np.float32)
2336 faces_int = faces.astype(np.int32)
2337 if colors
is not None:
2338 colors_float = colors.astype(np.float32)
2342 for i
in range(faces.shape[0]):
2344 v0_idx, v1_idx, v2_idx = faces_int[i]
2347 vertex0 = vertices_float[v0_idx].tolist()
2348 vertex1 = vertices_float[v1_idx].tolist()
2349 vertex2 = vertices_float[v2_idx].tolist()
2354 uuid = context_wrapper.addTriangle(self.
context, vertex0, vertex1, vertex2)
2355 elif per_triangle_colors:
2357 color = colors_float[i].tolist()
2358 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2359 elif per_vertex_colors:
2361 color = np.mean([colors_float[v0_idx], colors_float[v1_idx], colors_float[v2_idx]], axis=0).tolist()
2362 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2364 triangle_uuids.append(uuid)
2366 return triangle_uuids
2369 uv_coords: np.ndarray, texture_files: Union[str, List[str]],
2370 material_ids: Optional[np.ndarray] =
None) -> List[int]:
2372 Add textured triangles from NumPy arrays with support for multiple textures.
2374 This method supports both single-texture and multi-texture workflows:
2375 - Single texture: Pass a single texture file string, all faces use the same texture
2376 - Multiple textures: Pass a list of texture files and material_ids array specifying which texture each face uses
2379 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2380 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2381 uv_coords: NumPy array of shape (N, 2) containing UV texture coordinates as float32/float64
2382 texture_files: Single texture file path (str) or list of texture file paths (List[str])
2383 material_ids: Optional NumPy array of shape (M,) containing material ID for each face.
2384 If None and texture_files is a list, all faces use texture 0.
2385 If None and texture_files is a string, this parameter is ignored.
2388 List of UUIDs for the added textured triangles
2391 ValueError: If array dimensions are invalid or material IDs are out of range
2394 # Single texture usage (backward compatible)
2395 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, "texture.png")
2397 # Multi-texture usage (Open3D style)
2398 >>> texture_files = ["wood.png", "metal.png", "glass.png"]
2399 >>> material_ids = np.array([0, 0, 1, 1, 2, 2]) # 6 faces using different textures
2400 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, texture_files, material_ids)
2405 if vertices.ndim != 2
or vertices.shape[1] != 3:
2406 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2407 if faces.ndim != 2
or faces.shape[1] != 3:
2408 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2409 if uv_coords.ndim != 2
or uv_coords.shape[1] != 2:
2410 raise ValueError(f
"UV coordinates array must have shape (N, 2), got {uv_coords.shape}")
2413 if uv_coords.shape[0] != vertices.shape[0]:
2414 raise ValueError(f
"UV coordinates count ({uv_coords.shape[0]}) must match vertices count ({vertices.shape[0]})")
2417 max_vertex_index = np.max(faces)
2418 if max_vertex_index >= vertices.shape[0]:
2419 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2422 if isinstance(texture_files, str):
2424 texture_file_list = [texture_files]
2425 if material_ids
is None:
2426 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2429 if not np.all(material_ids == 0):
2430 raise ValueError(
"When using single texture file, all material IDs must be 0")
2433 texture_file_list = list(texture_files)
2434 if len(texture_file_list) == 0:
2435 raise ValueError(
"Texture files list cannot be empty")
2437 if material_ids
is None:
2439 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2442 if material_ids.ndim != 1
or material_ids.shape[0] != faces.shape[0]:
2443 raise ValueError(f
"Material IDs must have shape ({faces.shape[0]},), got {material_ids.shape}")
2446 max_material_id = np.max(material_ids)
2447 if max_material_id >= len(texture_file_list):
2448 raise ValueError(f
"Material ID {max_material_id} exceeds texture count {len(texture_file_list)}")
2451 for i, texture_file
in enumerate(texture_file_list):
2454 except (FileNotFoundError, ValueError)
as e:
2455 raise ValueError(f
"Texture file {i} ({texture_file}): {e}")
2458 if 'addTrianglesFromArraysMultiTextured' in context_wrapper._AVAILABLE_TRIANGLE_FUNCTIONS:
2459 return context_wrapper.addTrianglesFromArraysMultiTextured(
2460 self.
context, vertices, faces, uv_coords, texture_file_list, material_ids
2464 from .wrappers.DataTypes
import vec3, vec2
2466 vertices_float = vertices.astype(np.float32)
2467 faces_int = faces.astype(np.int32)
2468 uv_coords_float = uv_coords.astype(np.float32)
2471 for i
in range(faces.shape[0]):
2473 v0_idx, v1_idx, v2_idx = faces_int[i]
2476 vertex0 =
vec3(vertices_float[v0_idx][0], vertices_float[v0_idx][1], vertices_float[v0_idx][2])
2477 vertex1 =
vec3(vertices_float[v1_idx][0], vertices_float[v1_idx][1], vertices_float[v1_idx][2])
2478 vertex2 =
vec3(vertices_float[v2_idx][0], vertices_float[v2_idx][1], vertices_float[v2_idx][2])
2481 uv0 =
vec2(uv_coords_float[v0_idx][0], uv_coords_float[v0_idx][1])
2482 uv1 =
vec2(uv_coords_float[v1_idx][0], uv_coords_float[v1_idx][1])
2483 uv2 =
vec2(uv_coords_float[v2_idx][0], uv_coords_float[v2_idx][1])
2486 material_id = material_ids[i]
2487 texture_file = texture_file_list[material_id]
2491 triangle_uuids.append(uuid)
2493 return triangle_uuids
2501 Set primitive data as signed 32-bit integer for one or multiple primitives.
2504 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2505 label: String key for the data
2506 value: Signed integer scalar (broadcast to all UUIDs), or a list of
2507 values (one per UUID) to set a distinct value on each primitive.
2509 if isinstance(uuids_or_uuid, (list, tuple)):
2510 if isinstance(value, (list, tuple, np.ndarray)):
2511 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int', value)
2513 context_wrapper.setBroadcastPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2515 context_wrapper.setPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2519 Set primitive data as unsigned 32-bit integer for one or multiple primitives.
2521 Critical for properties like 'twosided_flag' which must be uint in C++.
2524 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2525 label: String key for the data
2526 value: Unsigned integer scalar (broadcast to all UUIDs), or a list of
2527 values (one per UUID) to set a distinct value on each primitive.
2529 if isinstance(uuids_or_uuid, (list, tuple)):
2530 if isinstance(value, (list, tuple, np.ndarray)):
2531 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'UInt', value)
2533 context_wrapper.setBroadcastPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2535 context_wrapper.setPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2539 Set primitive data as 32-bit float for one or multiple primitives.
2542 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2543 label: String key for the data
2544 value: Float scalar (broadcast to all UUIDs), or a list of values
2545 (one per UUID) to set a distinct value on each primitive.
2547 if isinstance(uuids_or_uuid, (list, tuple)):
2548 if isinstance(value, (list, tuple, np.ndarray)):
2549 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Float', value)
2551 context_wrapper.setBroadcastPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2553 context_wrapper.setPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2557 Set primitive data as 64-bit double for one or multiple primitives.
2560 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2561 label: String key for the data
2562 value: Double scalar (broadcast to all UUIDs), or a list of values
2563 (one per UUID) to set a distinct value on each primitive.
2565 if isinstance(uuids_or_uuid, (list, tuple)):
2566 if isinstance(value, (list, tuple, np.ndarray)):
2567 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Double', value)
2569 context_wrapper.setBroadcastPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2571 context_wrapper.setPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2575 Set primitive data as string for one or multiple primitives.
2578 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2579 label: String key for the data
2580 value: String scalar (broadcast to all UUIDs), or a list of strings
2581 (one per UUID) to set a distinct value on each primitive.
2583 if isinstance(uuids_or_uuid, (list, tuple)):
2584 if isinstance(value, (list, tuple, np.ndarray)):
2585 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'String', value)
2587 context_wrapper.setBroadcastPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2589 context_wrapper.setPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2593 Set primitive data as vec2 for one or multiple primitives.
2596 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2597 label: String key for the data
2598 x_or_vec: Either x component (float) or vec2 object
2599 y: Y component (if x_or_vec is float)
2601 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2602 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec2', x_or_vec)
2604 if hasattr(x_or_vec,
'x'):
2605 x, y = x_or_vec.x, x_or_vec.y
2608 if isinstance(uuids_or_uuid, (list, tuple)):
2609 context_wrapper.setBroadcastPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2611 context_wrapper.setPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2613 def setPrimitiveDataVec3(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
2615 Set primitive data as vec3 for one or multiple primitives.
2618 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2619 label: String key for the data
2620 x_or_vec: Either x component (float) or vec3 object
2621 y: Y component (if x_or_vec is float)
2622 z: Z component (if x_or_vec is float)
2624 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2625 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec3', x_or_vec)
2627 if hasattr(x_or_vec,
'x'):
2628 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2631 if isinstance(uuids_or_uuid, (list, tuple)):
2632 context_wrapper.setBroadcastPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2634 context_wrapper.setPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2636 def setPrimitiveDataVec4(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
2638 Set primitive data as vec4 for one or multiple primitives.
2641 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2642 label: String key for the data
2643 x_or_vec: Either x component (float) or vec4 object
2644 y: Y component (if x_or_vec is float)
2645 z: Z component (if x_or_vec is float)
2646 w: W component (if x_or_vec is float)
2648 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2649 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec4', x_or_vec)
2651 if hasattr(x_or_vec,
'x'):
2652 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2655 if isinstance(uuids_or_uuid, (list, tuple)):
2656 context_wrapper.setBroadcastPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2658 context_wrapper.setPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2662 Set primitive data as int2 for one or multiple primitives.
2665 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2666 label: String key for the data
2667 x_or_vec: Either x component (int) or int2 object
2668 y: Y component (if x_or_vec is int)
2670 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2671 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int2', x_or_vec)
2673 if hasattr(x_or_vec,
'x'):
2674 x, y = x_or_vec.x, x_or_vec.y
2677 if isinstance(uuids_or_uuid, (list, tuple)):
2678 context_wrapper.setBroadcastPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2680 context_wrapper.setPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2682 def setPrimitiveDataInt3(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
2684 Set primitive data as int3 for one or multiple primitives.
2687 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2688 label: String key for the data
2689 x_or_vec: Either x component (int) or int3 object
2690 y: Y component (if x_or_vec is int)
2691 z: Z component (if x_or_vec is int)
2693 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2694 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int3', x_or_vec)
2696 if hasattr(x_or_vec,
'x'):
2697 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2700 if isinstance(uuids_or_uuid, (list, tuple)):
2701 context_wrapper.setBroadcastPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2703 context_wrapper.setPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2705 def setPrimitiveDataInt4(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
2707 Set primitive data as int4 for one or multiple primitives.
2710 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2711 label: String key for the data
2712 x_or_vec: Either x component (int) or int4 object
2713 y: Y component (if x_or_vec is int)
2714 z: Z component (if x_or_vec is int)
2715 w: W component (if x_or_vec is int)
2717 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2718 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int4', x_or_vec)
2720 if hasattr(x_or_vec,
'x'):
2721 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2724 if isinstance(uuids_or_uuid, (list, tuple)):
2725 context_wrapper.setBroadcastPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2727 context_wrapper.setPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2731 Get primitive data for a specific primitive. If data_type is provided, it works like before.
2732 If data_type is None, it automatically detects the type and returns the appropriate value.
2735 uuid: UUID of the primitive
2736 label: String key for the data
2737 data_type: Optional. Python type to retrieve (int, uint, float, double, bool, str, vec2, vec3, vec4, int2, int3, int4, etc.)
2738 If None, auto-detects the type using C++ getPrimitiveDataType().
2741 The stored value of the specified or auto-detected type
2744 if data_type
is None:
2745 return context_wrapper.getPrimitiveDataAuto(self.
context, uuid, label)
2748 if data_type == int:
2749 return context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2750 elif data_type == float:
2751 return context_wrapper.getPrimitiveDataFloat(self.
context, uuid, label)
2752 elif data_type == bool:
2754 int_value = context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2755 return int_value != 0
2756 elif data_type == str:
2757 return context_wrapper.getPrimitiveDataString(self.
context, uuid, label)
2760 elif data_type == vec2:
2761 coords = context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2762 return vec2(coords[0], coords[1])
2763 elif data_type == vec3:
2764 coords = context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2765 return vec3(coords[0], coords[1], coords[2])
2766 elif data_type == vec4:
2767 coords = context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2768 return vec4(coords[0], coords[1], coords[2], coords[3])
2769 elif data_type == int2:
2770 coords = context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2771 return int2(coords[0], coords[1])
2772 elif data_type == int3:
2773 coords = context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2774 return int3(coords[0], coords[1], coords[2])
2775 elif data_type == int4:
2776 coords = context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2777 return int4(coords[0], coords[1], coords[2], coords[3])
2780 elif data_type ==
"uint":
2781 return context_wrapper.getPrimitiveDataUInt(self.
context, uuid, label)
2782 elif data_type ==
"double":
2783 return context_wrapper.getPrimitiveDataDouble(self.
context, uuid, label)
2786 elif data_type == list:
2788 return context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2789 elif data_type ==
"list_vec2":
2790 return context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2791 elif data_type ==
"list_vec4":
2792 return context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2793 elif data_type ==
"list_int2":
2794 return context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2795 elif data_type ==
"list_int3":
2796 return context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2797 elif data_type ==
"list_int4":
2798 return context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2801 raise ValueError(f
"Unsupported primitive data type: {data_type}. "
2802 f
"Supported types: int, float, bool, str, vec2, vec3, vec4, int2, int3, int4, "
2803 f
"'uint', 'double', list (for vec3), 'list_vec2', 'list_vec4', 'list_int2', 'list_int3', 'list_int4'")
2807 Check if primitive data exists for a specific primitive and label.
2810 uuid: UUID of the primitive
2811 label: String key for the data
2814 True if the data exists, False otherwise
2816 return context_wrapper.doesPrimitiveDataExistWrapper(self.
context, uuid, label)
2820 Convenience method to get float primitive data.
2823 uuid: UUID of the primitive
2824 label: String key for the data
2827 Float value stored for the primitive
2833 Get the Helios data type of primitive data.
2836 uuid: UUID of the primitive
2837 label: String key for the data
2840 HeliosDataType enum value as integer
2842 return context_wrapper.getPrimitiveDataTypeWrapper(self.
context, uuid, label)
2846 Get the size/length of primitive data (for vector data).
2849 uuid: UUID of the primitive
2850 label: String key for the data
2853 Size of data array, or 1 for scalar data
2855 return context_wrapper.getPrimitiveDataSizeWrapper(self.
context, uuid, label)
2859 Get primitive data values for multiple primitives as a NumPy array.
2861 This method retrieves primitive data for a list of UUIDs and returns the values
2862 as a NumPy array. The output array has the same length as the input UUID list,
2863 with each index corresponding to the primitive data value for that UUID.
2866 uuids: List of primitive UUIDs to get data for
2867 label: String key for the primitive data to retrieve
2870 NumPy array of primitive data values corresponding to each UUID.
2871 The array type depends on the data type:
2872 - int data: int32 array
2873 - uint data: uint32 array
2874 - float data: float32 array
2875 - double data: float64 array
2876 - vector data: float32 array with shape (N, vector_size)
2877 - string data: object array of strings
2880 ValueError: If UUID list is empty or UUIDs don't exist
2881 RuntimeError: If context is in mock mode or data doesn't exist for some UUIDs
2886 raise ValueError(
"UUID list cannot be empty")
2895 raise ValueError(f
"Primitive data '{label}' does not exist for UUID {uuid}")
2898 first_uuid = uuids[0]
2904 result = np.empty(len(uuids), dtype=np.int32)
2905 for i, uuid
in enumerate(uuids):
2908 elif data_type == 1:
2909 result = np.empty(len(uuids), dtype=np.uint32)
2910 for i, uuid
in enumerate(uuids):
2913 elif data_type == 2:
2914 result = np.empty(len(uuids), dtype=np.float32)
2915 for i, uuid
in enumerate(uuids):
2918 elif data_type == 3:
2919 result = np.empty(len(uuids), dtype=np.float64)
2920 for i, uuid
in enumerate(uuids):
2923 elif data_type == 4:
2924 result = np.empty((len(uuids), 2), dtype=np.float32)
2925 for i, uuid
in enumerate(uuids):
2927 result[i] = [vec_data.x, vec_data.y]
2929 elif data_type == 5:
2930 result = np.empty((len(uuids), 3), dtype=np.float32)
2931 for i, uuid
in enumerate(uuids):
2933 result[i] = [vec_data.x, vec_data.y, vec_data.z]
2935 elif data_type == 6:
2936 result = np.empty((len(uuids), 4), dtype=np.float32)
2937 for i, uuid
in enumerate(uuids):
2939 result[i] = [vec_data.x, vec_data.y, vec_data.z, vec_data.w]
2941 elif data_type == 7:
2942 result = np.empty((len(uuids), 2), dtype=np.int32)
2943 for i, uuid
in enumerate(uuids):
2945 result[i] = [int_data.x, int_data.y]
2947 elif data_type == 8:
2948 result = np.empty((len(uuids), 3), dtype=np.int32)
2949 for i, uuid
in enumerate(uuids):
2951 result[i] = [int_data.x, int_data.y, int_data.z]
2953 elif data_type == 9:
2954 result = np.empty((len(uuids), 4), dtype=np.int32)
2955 for i, uuid
in enumerate(uuids):
2957 result[i] = [int_data.x, int_data.y, int_data.z, int_data.w]
2959 elif data_type == 10:
2960 result = np.empty(len(uuids), dtype=object)
2961 for i, uuid
in enumerate(uuids):
2965 raise ValueError(f
"Unsupported primitive data type: {data_type}")
2971 colormap: str =
"hot", ncolors: int = 10,
2972 max_val: Optional[float] =
None, min_val: Optional[float] =
None):
2974 Color primitives based on primitive data values using pseudocolor mapping.
2976 This method applies a pseudocolor mapping to primitives based on the values
2977 of specified primitive data. The primitive colors are updated to reflect the
2978 data values using a color map.
2981 uuids: List of primitive UUIDs to color
2982 primitive_data: Name of primitive data to use for coloring (e.g., "radiation_flux_SW")
2983 colormap: Color map name - options include "hot", "cool", "parula", "rainbow", "gray", "lava"
2984 ncolors: Number of discrete colors in color map (default: 10)
2985 max_val: Maximum value for color scale (auto-determined if None)
2986 min_val: Minimum value for color scale (auto-determined if None)
2988 if max_val
is not None and min_val
is not None:
2989 context_wrapper.colorPrimitiveByDataPseudocolorWithRange(
2990 self.
context, uuids, primitive_data, colormap, ncolors, max_val, min_val)
2992 context_wrapper.colorPrimitiveByDataPseudocolor(
2993 self.
context, uuids, primitive_data, colormap, ncolors)
2996 def setTime(self, hour: int, minute: int = 0, second: int = 0):
2998 Set the simulation time.
3002 minute: Minute (0-59), defaults to 0
3003 second: Second (0-59), defaults to 0
3006 ValueError: If time values are out of range
3007 NotImplementedError: If time/date functions not available in current library build
3010 >>> context.setTime(14, 30) # Set to 2:30 PM
3011 >>> context.setTime(9, 15, 30) # Set to 9:15:30 AM
3013 context_wrapper.setTime(self.
context, hour, minute, second)
3015 def setDate(self, year: int, month: int, day: int):
3017 Set the simulation date.
3020 year: Year (1900-3000)
3025 ValueError: If date values are out of range
3026 NotImplementedError: If time/date functions not available in current library build
3029 >>> context.setDate(2023, 6, 21) # Set to June 21, 2023
3031 context_wrapper.setDate(self.
context, year, month, day)
3035 Set the simulation date using Julian day number.
3038 julian_day: Julian day (1-366)
3039 year: Year (1900-3000)
3042 ValueError: If values are out of range
3043 NotImplementedError: If time/date functions not available in current library build
3046 >>> context.setDateJulian(172, 2023) # Set to day 172 of 2023 (June 21)
3048 context_wrapper.setDateJulian(self.
context, julian_day, year)
3052 Get the current simulation time.
3055 Tuple of (hour, minute, second) as integers
3058 NotImplementedError: If time/date functions not available in current library build
3061 >>> hour, minute, second = context.getTime()
3062 >>> print(f"Current time: {hour:02d}:{minute:02d}:{second:02d}")
3064 return context_wrapper.getTime(self.
context)
3068 Get the current simulation date.
3071 Tuple of (year, month, day) as integers
3074 NotImplementedError: If time/date functions not available in current library build
3077 >>> year, month, day = context.getDate()
3078 >>> print(f"Current date: {year}-{month:02d}-{day:02d}")
3080 return context_wrapper.getDate(self.
context)
3086 def addTimeseriesData(self, label: str, value: float, date:
'Date', time:
'Time'):
3088 Add a data point to a timeseries variable.
3091 label: Name of the timeseries variable (e.g., "temperature")
3092 value: Value of the data point
3093 date: Date of the data point
3094 time: Time of the data point
3097 ValueError: If label is empty, or date/time are wrong types
3098 NotImplementedError: If timeseries functions not available
3101 >>> from pyhelios.types import Date, Time
3102 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3105 if not isinstance(label, str)
or not label:
3106 raise ValueError(
"Label must be a non-empty string")
3107 if not isinstance(date, Date):
3108 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3109 if not isinstance(time, Time):
3110 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3112 context_wrapper.addTimeseriesData(
3113 self.
context, label, float(value),
3114 date.day, date.month, date.year,
3115 time.hour, time.minute, time.second
3120 Update the value of an existing timeseries data point.
3123 label: Name of the timeseries variable (must already exist)
3124 date: Date of the existing point (must match exactly)
3125 time: Time of the existing point (must match exactly)
3126 new_value: Replacement value
3129 ValueError: If label is empty, or date/time are wrong types
3130 HeliosRuntimeError: If the variable does not exist or no point matches the (date, time)
3131 NotImplementedError: If timeseries functions not available
3134 >>> from pyhelios.types import Date, Time
3135 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3136 >>> context.updateTimeseriesData("temperature", Date(2024, 6, 15), Time(12, 0, 0), 26.5)
3139 if not isinstance(label, str)
or not label:
3140 raise ValueError(
"Label must be a non-empty string")
3141 if not isinstance(date, Date):
3142 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3143 if not isinstance(time, Time):
3144 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3146 context_wrapper.updateTimeseriesData(
3148 date.day, date.month, date.year,
3149 time.hour, time.minute, time.second,
3155 Set the Context date and time from a timeseries data point index.
3158 label: Name of the timeseries variable
3159 index: Index of the data point (0 = earliest, chronologically ordered)
3162 ValueError: If label is empty or index is negative
3163 NotImplementedError: If timeseries functions not available
3166 >>> context.setCurrentTimeseriesPoint("temperature", 0)
3169 if not isinstance(label, str)
or not label:
3170 raise ValueError(
"Label must be a non-empty string")
3171 if not isinstance(index, int)
or index < 0:
3172 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3174 context_wrapper.setCurrentTimeseriesPoint(self.
context, label, index)
3177 index: int =
None) -> float:
3179 Query a timeseries data value.
3181 Three modes of operation:
3182 - With date and time: returns interpolated value at the specified date/time
3183 - With index: returns value at the specified data point index
3184 - With neither: returns value at the current Context date/time
3187 label: Name of the timeseries variable
3188 date: Date to query at (requires time as well)
3189 time: Time to query at (requires date as well)
3190 index: Index of the data point (0 = earliest)
3193 The timeseries value as a float
3196 ValueError: If both date/time and index are provided, or if date without time
3197 NotImplementedError: If timeseries functions not available
3200 >>> # Query at specific date/time
3201 >>> val = context.queryTimeseriesData("temperature", date=Date(2024, 6, 15), time=Time(12, 0, 0))
3202 >>> # Query by index
3203 >>> val = context.queryTimeseriesData("temperature", index=0)
3204 >>> # Query at current context time
3205 >>> val = context.queryTimeseriesData("temperature")
3208 if not isinstance(label, str)
or not label:
3209 raise ValueError(
"Label must be a non-empty string")
3211 has_datetime = date
is not None or time
is not None
3212 has_index = index
is not None
3214 if has_datetime
and has_index:
3215 raise ValueError(
"Cannot specify both date/time and index. Use one or the other.")
3218 if date
is None or time
is None:
3219 raise ValueError(
"Both date and time must be provided together")
3220 if not isinstance(date, Date):
3221 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3222 if not isinstance(time, Time):
3223 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3224 return context_wrapper.queryTimeseriesDataDateTime(
3226 date.day, date.month, date.year,
3227 time.hour, time.minute, time.second
3231 if not isinstance(index, int)
or index < 0:
3232 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3233 return context_wrapper.queryTimeseriesDataIndex(self.
context, label, index)
3235 return context_wrapper.queryTimeseriesDataCurrent(self.
context, label)
3239 Get the Time associated with a timeseries data point.
3242 label: Name of the timeseries variable
3243 index: Index of the data point (0 = earliest)
3246 Time object for the data point
3249 ValueError: If label is empty or index is negative
3250 NotImplementedError: If timeseries functions not available
3253 >>> t = context.queryTimeseriesTime("temperature", 0)
3254 >>> print(f"{t.hour:02d}:{t.minute:02d}:{t.second:02d}")
3257 if not isinstance(label, str)
or not label:
3258 raise ValueError(
"Label must be a non-empty string")
3259 if not isinstance(index, int)
or index < 0:
3260 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3262 hour, minute, second = context_wrapper.queryTimeseriesTime(self.
context, label, index)
3263 return Time(hour=hour, minute=minute, second=second)
3267 Get the Date associated with a timeseries data point.
3270 label: Name of the timeseries variable
3271 index: Index of the data point (0 = earliest)
3274 Date object for the data point
3277 ValueError: If label is empty or index is negative
3278 NotImplementedError: If timeseries functions not available
3281 >>> d = context.queryTimeseriesDate("temperature", 0)
3282 >>> print(f"{d.year}-{d.month:02d}-{d.day:02d}")
3285 if not isinstance(label, str)
or not label:
3286 raise ValueError(
"Label must be a non-empty string")
3287 if not isinstance(index, int)
or index < 0:
3288 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3290 year, month, day = context_wrapper.queryTimeseriesDate(self.
context, label, index)
3291 return Date(year=year, month=month, day=day)
3295 Get the number of data points in a timeseries variable.
3298 label: Name of the timeseries variable
3301 Number of data points
3304 ValueError: If label is empty
3305 NotImplementedError: If timeseries functions not available
3308 >>> n = context.getTimeseriesLength("temperature")
3309 >>> print(f"Timeseries has {n} data points")
3312 if not isinstance(label, str)
or not label:
3313 raise ValueError(
"Label must be a non-empty string")
3315 return context_wrapper.getTimeseriesLength(self.
context, label)
3319 Check whether a timeseries variable exists.
3322 label: Name of the timeseries variable
3325 True if the variable exists, False otherwise
3328 ValueError: If label is empty
3329 NotImplementedError: If timeseries functions not available
3332 >>> if context.doesTimeseriesVariableExist("temperature"):
3333 ... print("Temperature data loaded")
3336 if not isinstance(label, str)
or not label:
3337 raise ValueError(
"Label must be a non-empty string")
3339 return context_wrapper.doesTimeseriesVariableExist(self.
context, label)
3343 List all existing timeseries variables.
3346 List of timeseries variable names
3349 NotImplementedError: If timeseries functions not available
3352 >>> variables = context.listTimeseriesVariables()
3353 >>> for var in variables:
3354 ... print(f" {var}: {context.getTimeseriesLength(var)} points")
3358 return context_wrapper.listTimeseriesVariables(self.
context)
3361 """Clear all timeseries data from the Context.
3363 Removes all timeseries variables and their associated date/time values.
3366 NotImplementedError: If timeseries functions not available
3369 >>> context.clearTimeseriesData()
3370 >>> context.listTimeseriesVariables()
3374 context_wrapper.clearTimeseriesData(self.
context)
3377 """Delete a single timeseries variable and all of its data points.
3379 Complements :meth:`clearTimeseriesData` (which removes all variables) and
3380 :meth:`updateTimeseriesData` (which modifies a single point).
3383 label: Name of the timeseries variable to delete.
3386 ValueError: If ``label`` is empty.
3387 NotImplementedError: If running against helios-core older than v1.3.72.
3390 If the variable does not exist, the underlying Helios API issues a
3391 non-fatal warning to stderr and the call is otherwise a no-op.
3394 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3395 >>> context.deleteTimeseriesVariable("temperature")
3396 >>> context.doesTimeseriesVariableExist("temperature")
3400 if not isinstance(label, str)
or not label:
3401 raise ValueError(
"Label must be a non-empty string")
3402 context_wrapper.deleteTimeseriesVariable(self.
context, label)
3405 """Delete a single timeseries data point at the given date and time.
3407 If ``label`` is provided, only that variable's matching point is removed. If ``label``
3408 is omitted (None), the matching point is removed from every timeseries variable.
3411 date: Date of the data point to delete.
3412 time: Time of the data point to delete.
3413 label: Optional name of the timeseries variable. None applies to all variables.
3416 ValueError: If date/time are wrong types, or label is an empty string.
3417 NotImplementedError: If running against helios-core older than v1.3.73.
3420 If no matching data point exists, the underlying Helios API issues a non-fatal
3421 warning to stderr and the call is otherwise a no-op. Matching uses the same
3422 (date, time) encoding as :meth:`addTimeseriesData`.
3425 >>> from pyhelios.types import Date, Time
3426 >>> context.deleteTimeseriesDataPoint(Date(2024, 6, 15), Time(12, 0, 0), "temperature")
3429 if not isinstance(date, Date):
3430 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3431 if not isinstance(time, Time):
3432 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3433 if label
is not None and (
not isinstance(label, str)
or not label):
3434 raise ValueError(
"label must be a non-empty string or None")
3437 context_wrapper.deleteTimeseriesDataPointAll(
3439 date.day, date.month, date.year,
3440 time.hour, time.minute, time.second
3443 context_wrapper.deleteTimeseriesDataPoint(
3445 date.day, date.month, date.year,
3446 time.hour, time.minute, time.second
3450 delimiter: str =
",", date_string_format: str =
"YYYYMMDD",
3451 headerlines: int = 0):
3453 Load tabular timeseries data from a text file.
3455 The file should contain columns of data with dates/times and measured values.
3456 Column labels specify how each column should be interpreted. Special labels
3457 include "year", "DOY", "date", "datetime", "hour", "minute", "second", "time".
3458 Other labels become timeseries variable names.
3461 data_file: Path to the text file containing tabular data
3462 column_labels: List of column label strings specifying what each column contains
3463 delimiter: Column delimiter string (default: ",")
3464 date_string_format: Format of date strings in the file. Supported formats:
3465 "YYYYMMDD", "YYYYMMDDHH", "YYYYMMDDHHMM", "DD/MM/YYYY",
3466 "MM/DD/YYYY", "DDMMYYYY", "YYYY-MM-DD", "DD/MM/YYYY HH:MM",
3467 "MM/DD/YYYY HH:MM", "ISO8601" (default: "YYYYMMDD")
3468 headerlines: Number of header lines to skip (default: 0)
3471 ValueError: If data_file is empty, column_labels is empty, or delimiter is empty
3472 RuntimeError: If the file cannot be read or parsed
3473 NotImplementedError: If timeseries functions not available
3476 >>> context.loadTabularTimeseriesData(
3477 ... "weather_data.csv",
3478 ... column_labels=["date", "hour", "temperature", "humidity"],
3482 >>> temp = context.queryTimeseriesData("temperature", index=0)
3485 if not isinstance(data_file, str)
or not data_file:
3486 raise ValueError(
"data_file must be a non-empty string")
3487 if not isinstance(column_labels, list)
or not column_labels:
3488 raise ValueError(
"column_labels must be a non-empty list of strings")
3489 for i, label
in enumerate(column_labels):
3490 if not isinstance(label, str):
3491 raise ValueError(f
"column_labels[{i}] must be a string, got {type(label).__name__}")
3492 if not isinstance(delimiter, str)
or not delimiter:
3493 raise ValueError(
"delimiter must be a non-empty string")
3495 context_wrapper.loadTabularTimeseriesData(
3496 self.
context, data_file, column_labels, delimiter,
3497 date_string_format, headerlines
3504 def deletePrimitive(self, uuids_or_uuid: Union[int, List[int]]) ->
None:
3506 Delete one or more primitives from the context.
3508 This removes the primitive(s) entirely from the context. If a primitive
3509 belongs to a compound object, it will be removed from that object. If the
3510 object becomes empty after removal, it is automatically deleted.
3513 uuids_or_uuid: Single UUID (int) or list of UUIDs to delete
3516 RuntimeError: If any UUID doesn't exist in the context
3517 ValueError: If UUID is invalid (negative)
3518 NotImplementedError: If delete functions not available in current library build
3521 >>> context = Context()
3522 >>> patch_id = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
3523 >>> context.deletePrimitive(patch_id) # Single deletion
3525 >>> # Multiple deletion
3526 >>> ids = [context.addPatch() for _ in range(5)]
3527 >>> context.deletePrimitive(ids) # Delete all at once
3531 if isinstance(uuids_or_uuid, (list, tuple)):
3532 for uuid
in uuids_or_uuid:
3534 raise ValueError(f
"UUID must be non-negative, got {uuid}")
3535 context_wrapper.deletePrimitives(self.
context, list(uuids_or_uuid))
3537 if uuids_or_uuid < 0:
3538 raise ValueError(f
"UUID must be non-negative, got {uuids_or_uuid}")
3539 context_wrapper.deletePrimitive(self.
context, uuids_or_uuid)
3541 def deleteObject(self, objIDs_or_objID: Union[int, List[int]]) ->
None:
3543 Delete one or more compound objects from the context.
3545 This removes the compound object(s) AND all their child primitives.
3546 Use this when you want to delete an entire object hierarchy at once.
3549 objIDs_or_objID: Single object ID (int) or list of object IDs to delete
3552 RuntimeError: If any object ID doesn't exist in the context
3553 ValueError: If object ID is invalid (negative)
3554 NotImplementedError: If delete functions not available in current library build
3557 >>> context = Context()
3558 >>> # Create a compound object (e.g., a tile with multiple patches)
3559 >>> patch_ids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2),
3560 ... tile_divisions=int2(2, 2))
3561 >>> obj_id = context.getPrimitiveParentObjectID(patch_ids[0])
3562 >>> context.deleteObject(obj_id) # Deletes tile and all its patches
3566 if isinstance(objIDs_or_objID, (list, tuple)):
3567 for objID
in objIDs_or_objID:
3569 raise ValueError(f
"Object ID must be non-negative, got {objID}")
3570 context_wrapper.deleteObjects(self.
context, list(objIDs_or_objID))
3572 if objIDs_or_objID < 0:
3573 raise ValueError(f
"Object ID must be non-negative, got {objIDs_or_objID}")
3574 context_wrapper.deleteObject(self.
context, objIDs_or_objID)
3579 Get list of available plugins for this PyHelios instance.
3582 List of available plugin names
3588 Check if a specific plugin is available.
3591 plugin_name: Name of the plugin to check
3594 True if plugin is available, False otherwise
3600 Get detailed information about available plugin capabilities.
3603 Dictionary mapping plugin names to capability information
3608 """Print detailed plugin status information."""
3613 Get list of requested plugins that are not available.
3616 requested_plugins: List of plugin names to check
3619 List of missing plugin names
3629 Create a new material for sharing visual properties across primitives.
3631 Materials enable efficient memory usage by allowing multiple primitives to
3632 share rendering properties. Changes to a material affect all primitives using it.
3635 material_label: Unique label for the material
3638 RuntimeError: If material label already exists
3641 >>> context.addMaterial("wood_oak")
3642 >>> context.setMaterialColor("wood_oak", (0.6, 0.4, 0.2, 1.0))
3643 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3645 context_wrapper.addMaterial(self.
context, material_label)
3648 """Check if a material with the given label exists."""
3649 return context_wrapper.doesMaterialExist(self.
context, material_label)
3652 """Get list of all material labels in the context."""
3653 return context_wrapper.listMaterials(self.
context)
3657 Delete a material from the context.
3659 Primitives using this material will be reassigned to the default material.
3662 material_label: Label of the material to delete
3665 RuntimeError: If material doesn't exist
3667 context_wrapper.deleteMaterial(self.
context, material_label)
3671 Get the RGBA color of a material.
3674 material_label: Label of the material
3680 RuntimeError: If material doesn't exist
3682 from .wrappers.DataTypes
import RGBAcolor
3683 color_list = context_wrapper.getMaterialColor(self.
context, material_label)
3684 return RGBAcolor(color_list[0], color_list[1], color_list[2], color_list[3])
3688 Set the RGBA color of a material.
3690 This affects all primitives that reference this material.
3693 material_label: Label of the material
3694 color: RGBAcolor object or tuple/list of (r, g, b, a) values
3697 RuntimeError: If material doesn't exist
3700 >>> from pyhelios.types import RGBAcolor
3701 >>> context.setMaterialColor("wood", RGBAcolor(0.6, 0.4, 0.2, 1.0))
3702 >>> context.setMaterialColor("wood", (0.6, 0.4, 0.2, 1.0))
3704 if isinstance(color, RGBAcolor):
3705 r, g, b, a = color.r, color.g, color.b, color.a
3706 elif isinstance(color, (list, tuple))
and len(color) == 4:
3707 r, g, b, a = color[0], color[1], color[2], color[3]
3709 raise ValueError(f
"Color must be an RGBAcolor or a 4-element list/tuple, got {type(color).__name__}")
3710 context_wrapper.setMaterialColor(self.
context, material_label, r, g, b, a)
3714 Get the texture file path for a material.
3717 material_label: Label of the material
3720 Texture file path, or empty string if no texture
3723 RuntimeError: If material doesn't exist
3725 return context_wrapper.getMaterialTexture(self.
context, material_label)
3729 Set the texture file for a material.
3731 This affects all primitives that reference this material.
3734 material_label: Label of the material
3735 texture_file: Path to texture image file
3738 RuntimeError: If material doesn't exist or texture file not found
3740 context_wrapper.setMaterialTexture(self.
context, material_label, texture_file)
3743 """Check if material texture color is overridden by material color."""
3744 return context_wrapper.isMaterialTextureColorOverridden(self.
context, material_label)
3747 """Set whether material color overrides texture color."""
3748 context_wrapper.setMaterialTextureColorOverride(self.
context, material_label, override)
3751 """Get the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3752 return context_wrapper.getMaterialTwosidedFlag(self.
context, material_label)
3755 """Set the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3756 context_wrapper.setMaterialTwosidedFlag(self.
context, material_label, twosided_flag)
3760 Assign a material to primitive(s).
3763 uuid: Single UUID (int) or list of UUIDs (List[int])
3764 material_label: Label of the material to assign
3767 RuntimeError: If primitive or material doesn't exist
3770 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3771 >>> context.assignMaterialToPrimitive([uuid1, uuid2, uuid3], "wood_oak")
3773 if isinstance(uuid, (list, tuple)):
3774 context_wrapper.assignMaterialToPrimitives(self.
context, uuid, material_label)
3776 context_wrapper.assignMaterialToPrimitive(self.
context, uuid, material_label)
3780 Assign a material to all primitives in compound object(s).
3783 objID: Single object ID (int) or list of object IDs (List[int])
3784 material_label: Label of the material to assign
3787 RuntimeError: If object or material doesn't exist
3790 >>> tree_id = wpt.buildTree(WPTType.LEMON)
3791 >>> context.assignMaterialToObject(tree_id, "tree_bark")
3792 >>> context.assignMaterialToObject([id1, id2], "grass")
3794 if isinstance(objID, (list, tuple)):
3795 context_wrapper.assignMaterialToObjects(self.
context, objID, material_label)
3797 context_wrapper.assignMaterialToObject(self.
context, objID, material_label)
3800 """Get the material label assigned to a primitive or multiple primitives.
3803 uuid: Single UUID (int) or list of UUIDs
3806 str for single UUID, or List[str] for list
3809 RuntimeError: If primitive doesn't exist
3811 if isinstance(uuid, (list, tuple)):
3815 ptr, offsets, total = context_wrapper.getBatchPrimitiveMaterialLabels(self.
context, uuid)
3816 if total == 0
or not ptr:
3817 return [
"" for _
in uuid]
3818 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3819 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3820 return context_wrapper.getPrimitiveMaterialLabel(self.
context, uuid)
3824 Get two-sided rendering flag for a primitive.
3826 Checks material first, then primitive data if no material assigned.
3829 uuid: UUID of the primitive
3830 default_value: Default value if no material/data (default 1 = two-sided)
3833 Two-sided flag (0 = one-sided, 1 = two-sided)
3835 return context_wrapper.getPrimitiveTwosidedFlag(self.
context, uuid, default_value)
3839 Get all primitive UUIDs that use a specific material.
3842 material_label: Label of the material
3845 List of primitive UUIDs using the material
3848 RuntimeError: If material doesn't exist
3850 return context_wrapper.getPrimitivesUsingMaterial(self.
context, material_label)
3857 """Get the texture file path of a primitive or multiple primitives.
3860 uuid: Single UUID (int) or list of UUIDs
3863 str for single UUID, or List[str] for list
3866 if isinstance(uuid, (list, tuple)):
3869 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureFiles(self.
context, uuid)
3870 if total == 0
or not ptr:
3871 return [
"" for _
in uuid]
3872 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3873 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3874 return context_wrapper.getPrimitiveTextureFile(self.
context, uuid)
3877 """Resolve material texture suppression for export.
3879 For each primitive, applies material-based texture suppression rules:
3880 1. If primitive has texture but material has no texture -> suppress texture, use material color
3881 2. If both have texture and textureColorOverride -> prefix "mask:", use material color
3882 3. Otherwise -> leave unchanged
3885 uuids: List of primitive UUIDs
3886 colors_np: numpy float32 array of shape (N, 3), modified IN-PLACE
3889 List[str] of resolved texture file paths
3894 return context_wrapper.resolveMaterialTextures(self.
context, uuids, colors_np)
3897 """Pack GPU-ready geometry buffers for a set of primitives in a single C++ pass.
3899 Produces a binary blob containing contiguous typed arrays (positions,
3900 colors, uvs, indices, faceToUuid) grouped by texture, ready for
3901 zero-copy loading into Three.js BufferGeometry attributes.
3904 uuids: List of primitive UUIDs
3907 bytes: Raw binary blob (see wire format v2 spec)
3912 return context_wrapper.packGPUBuffers(self.
context, uuids)
3915 """Set the texture file path of a primitive.
3918 uuid: UUID of the primitive
3919 texture_file: Path to the texture file
3922 context_wrapper.setPrimitiveTextureFile(self.
context, uuid, texture_file)
3925 """Get the texture size (width, height) of a primitive.
3928 uuid: UUID of the primitive
3931 int2 with width and height of the texture
3934 w, h = context_wrapper.getPrimitiveTextureSize(self.
context, uuid)
3938 """Get the texture UV coordinates of a primitive or multiple primitives.
3941 uuid: Single UUID (int) or list of UUIDs
3944 List[vec2] for single UUID, or tuple of (flat_data, offsets) for list
3947 if isinstance(uuid, (list, tuple)):
3949 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
3950 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureUV(self.
context, uuid)
3951 offsets_arr = np.array(offsets, dtype=np.uint32)
3952 if total == 0
or not ptr:
3953 return (np.empty((0,), dtype=np.float32), offsets_arr)
3954 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
3955 return (data, offsets_arr)
3956 uv_pairs = context_wrapper.getPrimitiveTextureUV(self.
context, uuid)
3957 return [
vec2(u, v)
for u, v
in uv_pairs]
3960 """Check if primitive texture has a transparency channel.
3963 uuid: UUID of the primitive
3966 True if texture has transparency channel
3969 return context_wrapper.primitiveTextureHasTransparencyChannel(self.
context, uuid)
3972 """Get the solid fraction of a primitive or multiple primitives.
3975 uuid: Single UUID (int) or list of UUIDs
3978 float for single UUID, or np.ndarray of shape (N,) for list
3981 if isinstance(uuid, (list, tuple)):
3983 return np.empty((0,), dtype=np.float32)
3984 ptr, size = context_wrapper.getBatchPrimitiveSolidFractions(self.
context, uuid)
3985 if size == 0
or not ptr:
3986 return np.empty((0,), dtype=np.float32)
3987 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
3988 return context_wrapper.getPrimitiveSolidFraction(self.
context, uuid)
3991 """Override texture color with the primitive's constant RGB color.
3994 uuids_or_uuid: A single UUID (int) or a list of UUIDs. When a list is
3995 given, the override is applied to all of them in a single bulk call.
3998 if isinstance(uuids_or_uuid, (list, tuple)):
3999 context_wrapper.overridePrimitiveTextureColorBatchWrapper(self.
context, list(uuids_or_uuid))
4001 context_wrapper.overridePrimitiveTextureColor(self.
context, uuids_or_uuid)
4004 """Use texture-map color instead of the constant RGB color.
4007 uuids_or_uuid: A single UUID (int) or a list of UUIDs. When a list is
4008 given, all of them are restored in a single bulk call.
4011 if isinstance(uuids_or_uuid, (list, tuple)):
4012 context_wrapper.usePrimitiveTextureColorBatchWrapper(self.
context, list(uuids_or_uuid))
4014 context_wrapper.usePrimitiveTextureColor(self.
context, uuids_or_uuid)
4017 """Check if primitive texture color is overridden.
4020 uuid: UUID of the primitive
4023 True if texture color is overridden with constant RGB
4026 return context_wrapper.isPrimitiveTextureColorOverridden(self.
context, uuid)
4033 """Get normals for all primitives. Returns ndarray of shape (N, 3)."""
4037 """Get colors for all primitives. Returns ndarray of shape (N, 3)."""
4041 """Get areas for all primitives. Returns ndarray of shape (N,)."""
4045 """Get types for all primitives. Returns ndarray of shape (N,) uint32."""
4049 """Get solid fractions for all primitives. Returns ndarray of shape (N,)."""
4053 """Get vertices for all primitives. Returns (flat_data, offsets) tuple."""
4057 """Get texture files for all primitives. Returns list of strings."""
4061 """Get material labels for all primitives. Returns list of strings."""
4067 """Hide one or more primitives. Hidden primitives are excluded from getAllUUIDs().
4070 uuids_or_uuid: Single UUID (int) or list of UUIDs to hide.
4072 if isinstance(uuids_or_uuid, (list, tuple)):
4073 context_wrapper.hidePrimitivesWrapper(self.
context, list(uuids_or_uuid))
4075 context_wrapper.hidePrimitiveWrapper(self.
context, uuids_or_uuid)
4078 """Show one or more previously hidden primitives.
4081 uuids_or_uuid: Single UUID (int) or list of UUIDs to show.
4083 if isinstance(uuids_or_uuid, (list, tuple)):
4084 context_wrapper.showPrimitivesWrapper(self.
context, list(uuids_or_uuid))
4086 context_wrapper.showPrimitiveWrapper(self.
context, uuids_or_uuid)
4089 """Check if a primitive is hidden.
4092 uuid: UUID of the primitive.
4095 True if the primitive is hidden.
4097 return context_wrapper.isPrimitiveHiddenWrapper(self.
context, uuid)
4099 def hideObject(self, objids_or_objid) -> None:
4100 """Hide one or more compound objects (and all their primitives).
4103 objids_or_objid: Single object ID (int) or list of object IDs to hide.
4105 if isinstance(objids_or_objid, (list, tuple)):
4106 context_wrapper.hideObjectsWrapper(self.
context, list(objids_or_objid))
4108 context_wrapper.hideObjectWrapper(self.
context, objids_or_objid)
4110 def showObject(self, objids_or_objid) -> None:
4111 """Show one or more previously hidden compound objects.
4114 objids_or_objid: Single object ID (int) or list of object IDs to show.
4116 if isinstance(objids_or_objid, (list, tuple)):
4117 context_wrapper.showObjectsWrapper(self.
context, list(objids_or_objid))
4119 context_wrapper.showObjectWrapper(self.
context, objids_or_objid)
4122 """Check if a compound object is hidden.
4128 True if the object is hidden.
4130 return context_wrapper.isObjectHiddenWrapper(self.
context, objID)
4134 def setObjectDataInt(self, objids_or_objid, label: str, value: int) ->
None:
4135 """Set object data as signed 32-bit integer. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4136 if isinstance(objids_or_objid, (list, tuple)):
4137 if isinstance(value, (list, tuple, np.ndarray)):
4138 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int', value)
4140 context_wrapper.setBroadcastObjectDataInt(self.
context, objids_or_objid, label, value)
4142 context_wrapper.setObjectDataInt(self.
context, objids_or_objid, label, value)
4145 """Set object data as unsigned 32-bit integer. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4146 if isinstance(objids_or_objid, (list, tuple)):
4147 if isinstance(value, (list, tuple, np.ndarray)):
4148 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'UInt', value)
4150 context_wrapper.setBroadcastObjectDataUInt(self.
context, objids_or_objid, label, value)
4152 context_wrapper.setObjectDataUInt(self.
context, objids_or_objid, label, value)
4155 """Set object data as 32-bit float. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4156 if isinstance(objids_or_objid, (list, tuple)):
4157 if isinstance(value, (list, tuple, np.ndarray)):
4158 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Float', value)
4160 context_wrapper.setBroadcastObjectDataFloat(self.
context, objids_or_objid, label, value)
4162 context_wrapper.setObjectDataFloat(self.
context, objids_or_objid, label, value)
4165 """Set object data as 64-bit double. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4166 if isinstance(objids_or_objid, (list, tuple)):
4167 if isinstance(value, (list, tuple, np.ndarray)):
4168 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Double', value)
4170 context_wrapper.setBroadcastObjectDataDouble(self.
context, objids_or_objid, label, value)
4172 context_wrapper.setObjectDataDouble(self.
context, objids_or_objid, label, value)
4175 """Set object data as string. Scalar broadcasts to all objIDs; a list of strings sets a distinct value per objID."""
4176 if isinstance(objids_or_objid, (list, tuple)):
4177 if isinstance(value, (list, tuple, np.ndarray)):
4178 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'String', value)
4180 context_wrapper.setBroadcastObjectDataString(self.
context, objids_or_objid, label, value)
4182 context_wrapper.setObjectDataString(self.
context, objids_or_objid, label, value)
4184 def setObjectDataVec2(self, objids_or_objid, label: str, x_or_vec, y: float =
None) ->
None:
4185 """Set object data as vec2. Accepts a vec2 / x,y components, or a list of vec2 (one per objID)."""
4186 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4187 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec2', x_or_vec)
4189 if hasattr(x_or_vec,
'x')
and y
is None:
4190 x, y = x_or_vec.x, x_or_vec.y
4193 if isinstance(objids_or_objid, (list, tuple)):
4194 context_wrapper.setBroadcastObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4196 context_wrapper.setObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4198 def setObjectDataVec3(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4199 """Set object data as vec3. Accepts a vec3 / x,y,z components, or a list of vec3 (one per objID)."""
4200 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4201 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec3', x_or_vec)
4203 if hasattr(x_or_vec,
'x')
and y
is None:
4204 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4207 if isinstance(objids_or_objid, (list, tuple)):
4208 context_wrapper.setBroadcastObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4210 context_wrapper.setObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4212 def setObjectDataVec4(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4213 """Set object data as vec4. Accepts a vec4 / x,y,z,w components, or a list of vec4 (one per objID)."""
4214 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4215 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec4', x_or_vec)
4217 if hasattr(x_or_vec,
'x')
and y
is None:
4218 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4221 if isinstance(objids_or_objid, (list, tuple)):
4222 context_wrapper.setBroadcastObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4224 context_wrapper.setObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4226 def setObjectDataInt2(self, objids_or_objid, label: str, x_or_vec, y: int =
None) ->
None:
4227 """Set object data as int2. Accepts an int2 / x,y components, or a list of int2 (one per objID)."""
4228 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4229 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int2', x_or_vec)
4231 if hasattr(x_or_vec,
'x')
and y
is None:
4232 x, y = x_or_vec.x, x_or_vec.y
4235 if isinstance(objids_or_objid, (list, tuple)):
4236 context_wrapper.setBroadcastObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4238 context_wrapper.setObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4240 def setObjectDataInt3(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4241 """Set object data as int3. Accepts an int3 / x,y,z components, or a list of int3 (one per objID)."""
4242 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4243 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int3', x_or_vec)
4245 if hasattr(x_or_vec,
'x')
and y
is None:
4246 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4249 if isinstance(objids_or_objid, (list, tuple)):
4250 context_wrapper.setBroadcastObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4252 context_wrapper.setObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4254 def setObjectDataInt4(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4255 """Set object data as int4. Accepts an int4 / x,y,z,w components, or a list of int4 (one per objID)."""
4256 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4257 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int4', x_or_vec)
4259 if hasattr(x_or_vec,
'x')
and y
is None:
4260 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4263 if isinstance(objids_or_objid, (list, tuple)):
4264 context_wrapper.setBroadcastObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4266 context_wrapper.setObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4268 def getObjectData(self, objID: int, label: str, data_type: type =
None):
4269 """Get object data with optional type specification. Auto-detects type if not specified."""
4270 if data_type
is None:
4271 return context_wrapper.getObjectDataAuto(self.
context, objID, label)
4272 if data_type == int:
4273 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4274 elif data_type == float:
4275 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4276 elif data_type == str:
4277 return context_wrapper.getObjectDataString(self.
context, objID, label)
4278 elif data_type == vec3:
4279 coords = context_wrapper.getObjectDataVec3(self.
context, objID, label)
4280 return vec3(coords[0], coords[1], coords[2])
4281 elif data_type == vec2:
4282 coords = context_wrapper.getObjectDataVec2(self.
context, objID, label)
4283 return vec2(coords[0], coords[1])
4284 elif data_type == vec4:
4285 coords = context_wrapper.getObjectDataVec4(self.
context, objID, label)
4286 return vec4(coords[0], coords[1], coords[2], coords[3])
4287 elif data_type == int2:
4288 coords = context_wrapper.getObjectDataInt2(self.
context, objID, label)
4289 return int2(coords[0], coords[1])
4290 elif data_type == int3:
4291 coords = context_wrapper.getObjectDataInt3(self.
context, objID, label)
4292 return int3(coords[0], coords[1], coords[2])
4293 elif data_type == int4:
4294 coords = context_wrapper.getObjectDataInt4(self.
context, objID, label)
4295 return int4(coords[0], coords[1], coords[2], coords[3])
4296 elif data_type ==
"uint":
4297 return context_wrapper.getObjectDataUInt(self.
context, objID, label)
4298 elif data_type ==
"double":
4299 return context_wrapper.getObjectDataDouble(self.
context, objID, label)
4301 raise ValueError(f
"Unsupported object data type: {data_type}")
4304 """Get float object data."""
4305 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4308 """Get int object data."""
4309 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4312 """Get string object data."""
4313 return context_wrapper.getObjectDataString(self.
context, objID, label)
4316 """Get the HeliosDataType enum for object data."""
4317 return context_wrapper.getObjectDataTypeWrapper(self.
context, objID, label)
4320 """Get the size of object data array."""
4321 return context_wrapper.getObjectDataSizeWrapper(self.
context, objID, label)
4324 """Check if object data exists."""
4325 return context_wrapper.doesObjectDataExistWrapper(self.
context, objID, label)
4328 """Clear object data. Accepts single ID or list."""
4329 if isinstance(objids_or_objid, (list, tuple)):
4330 context_wrapper.clearObjectDataBatchWrapper(self.
context, objids_or_objid, label)
4332 context_wrapper.clearObjectDataWrapper(self.
context, objids_or_objid, label)
4335 """Remove a named data field from every compound object in the Context.
4337 Clears the data with the given label from all objects (including hidden ones) and
4338 releases the registered data type for the label, so it may subsequently be
4339 re-registered with a different type. Requires helios-core v1.3.73 or newer.
4342 context_wrapper.clearAllObjectDataByLabelWrapper(self.
context, label)
4345 """List all data labels on a specific object."""
4346 return context_wrapper.listObjectDataWrapper(self.
context, objID)
4349 """List all object data labels in context."""
4350 return context_wrapper.listAllObjectDataLabelsWrapper(self.
context)
4353 """Copy object data to a new label."""
4354 context_wrapper.duplicateObjectDataWrapper(self.
context, objID, old_label, new_label)
4356 def renameObjectData(self, objID: int, old_label: str, new_label: str) ->
None:
4357 """Rename an object data label."""
4358 context_wrapper.renameObjectDataWrapper(self.
context, objID, old_label, new_label)
4360 def filterObjectsByData(self, objIDs: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4361 """Filter objects by data value. Auto-dispatches based on value type."""
4362 if isinstance(value, str):
4363 return context_wrapper.filterObjectsByDataStringWrapper(self.
context, objIDs, label, value)
4364 elif isinstance(value, float):
4365 return context_wrapper.filterObjectsByDataFloatWrapper(self.
context, objIDs, label, value, comparator)
4366 elif isinstance(value, int):
4367 return context_wrapper.filterObjectsByDataIntWrapper(self.
context, objIDs, label, value, comparator)
4369 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4374 """Set global data as signed 32-bit integer."""
4375 context_wrapper.setGlobalDataInt(self.
context, label, value)
4378 """Set global data as unsigned 32-bit integer."""
4379 context_wrapper.setGlobalDataUInt(self.
context, label, value)
4382 """Set global data as 32-bit float."""
4383 context_wrapper.setGlobalDataFloat(self.
context, label, value)
4386 """Set global data as 64-bit double."""
4387 context_wrapper.setGlobalDataDouble(self.
context, label, value)
4390 """Set global data as string."""
4391 context_wrapper.setGlobalDataString(self.
context, label, value)
4394 """Set global data as vec2."""
4395 if hasattr(x_or_vec,
'x')
and y
is None:
4396 x, y = x_or_vec.x, x_or_vec.y
4399 context_wrapper.setGlobalDataVec2(self.
context, label, x, y)
4401 def setGlobalDataVec3(self, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4402 """Set global data as vec3."""
4403 if hasattr(x_or_vec,
'x')
and y
is None:
4404 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4407 context_wrapper.setGlobalDataVec3(self.
context, label, x, y, z)
4409 def setGlobalDataVec4(self, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4410 """Set global data as vec4."""
4411 if hasattr(x_or_vec,
'x')
and y
is None:
4412 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4415 context_wrapper.setGlobalDataVec4(self.
context, label, x, y, z, w)
4418 """Set global data as int2."""
4419 if hasattr(x_or_vec,
'x')
and y
is None:
4420 x, y = x_or_vec.x, x_or_vec.y
4423 context_wrapper.setGlobalDataInt2(self.
context, label, x, y)
4425 def setGlobalDataInt3(self, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4426 """Set global data as int3."""
4427 if hasattr(x_or_vec,
'x')
and y
is None:
4428 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4431 context_wrapper.setGlobalDataInt3(self.
context, label, x, y, z)
4433 def setGlobalDataInt4(self, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4434 """Set global data as int4."""
4435 if hasattr(x_or_vec,
'x')
and y
is None:
4436 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4439 context_wrapper.setGlobalDataInt4(self.
context, label, x, y, z, w)
4441 def getGlobalData(self, label: str, data_type: type =
None):
4442 """Get global data with optional type specification. Auto-detects type if not specified."""
4443 if data_type
is None:
4444 return context_wrapper.getGlobalDataAuto(self.
context, label)
4445 if data_type == int:
4446 return context_wrapper.getGlobalDataInt(self.
context, label)
4447 elif data_type == float:
4448 return context_wrapper.getGlobalDataFloat(self.
context, label)
4449 elif data_type == str:
4450 return context_wrapper.getGlobalDataString(self.
context, label)
4451 elif data_type == vec3:
4452 coords = context_wrapper.getGlobalDataVec3(self.
context, label)
4453 return vec3(coords[0], coords[1], coords[2])
4454 elif data_type == vec2:
4455 coords = context_wrapper.getGlobalDataVec2(self.
context, label)
4456 return vec2(coords[0], coords[1])
4457 elif data_type == vec4:
4458 coords = context_wrapper.getGlobalDataVec4(self.
context, label)
4459 return vec4(coords[0], coords[1], coords[2], coords[3])
4460 elif data_type == int2:
4461 coords = context_wrapper.getGlobalDataInt2(self.
context, label)
4462 return int2(coords[0], coords[1])
4463 elif data_type == int3:
4464 coords = context_wrapper.getGlobalDataInt3(self.
context, label)
4465 return int3(coords[0], coords[1], coords[2])
4466 elif data_type == int4:
4467 coords = context_wrapper.getGlobalDataInt4(self.
context, label)
4468 return int4(coords[0], coords[1], coords[2], coords[3])
4469 elif data_type ==
"uint":
4470 return context_wrapper.getGlobalDataUInt(self.
context, label)
4471 elif data_type ==
"double":
4472 return context_wrapper.getGlobalDataDouble(self.
context, label)
4474 raise ValueError(f
"Unsupported global data type: {data_type}")
4477 """Get float global data."""
4478 return context_wrapper.getGlobalDataFloat(self.
context, label)
4481 """Get int global data."""
4482 return context_wrapper.getGlobalDataInt(self.
context, label)
4485 """Get string global data."""
4486 return context_wrapper.getGlobalDataString(self.
context, label)
4489 """Get the HeliosDataType enum for global data."""
4490 return context_wrapper.getGlobalDataTypeWrapper(self.
context, label)
4493 """Get the size of global data array."""
4494 return context_wrapper.getGlobalDataSizeWrapper(self.
context, label)
4497 """Check if global data exists."""
4498 return context_wrapper.doesGlobalDataExistWrapper(self.
context, label)
4501 """Clear global data."""
4502 context_wrapper.clearGlobalDataWrapper(self.
context, label)
4505 """Rename a global data label."""
4506 context_wrapper.renameGlobalDataWrapper(self.
context, old_label, new_label)
4509 """Duplicate global data to a new label."""
4510 context_wrapper.duplicateGlobalDataWrapper(self.
context, old_label, new_label)
4513 """List all global data labels."""
4514 return context_wrapper.listGlobalDataWrapper(self.
context)
4517 """Increment global data. Auto-dispatches based on increment type."""
4518 if isinstance(increment, float):
4519 context_wrapper.incrementGlobalDataFloatWrapper(self.
context, label, increment)
4520 elif isinstance(increment, int):
4521 context_wrapper.incrementGlobalDataIntWrapper(self.
context, label, increment)
4523 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4528 """Calculate arithmetic mean of primitive data across UUIDs.
4531 uuids: List of primitive UUIDs.
4533 return_type: float (default), "double", or vec3.
4535 if return_type == float:
4536 return context_wrapper.calculatePrimitiveDataMeanFloatWrapper(self.
context, uuids, label)
4537 elif return_type ==
"double":
4538 return context_wrapper.calculatePrimitiveDataMeanDoubleWrapper(self.
context, uuids, label)
4539 elif return_type == vec3:
4540 coords = context_wrapper.calculatePrimitiveDataMeanVec3Wrapper(self.
context, uuids, label)
4541 return vec3(coords[0], coords[1], coords[2])
4543 raise ValueError(f
"Unsupported return type: {return_type}")
4546 """Calculate area-weighted mean of primitive data."""
4547 if return_type == float:
4548 return context_wrapper.calculatePrimitiveDataAreaWeightedMeanFloatWrapper(self.
context, uuids, label)
4550 raise ValueError(f
"Unsupported return type: {return_type}")
4553 """Calculate sum of primitive data across UUIDs."""
4554 if return_type == float:
4555 return context_wrapper.calculatePrimitiveDataSumFloatWrapper(self.
context, uuids, label)
4556 elif return_type ==
"double":
4557 return context_wrapper.calculatePrimitiveDataSumDoubleWrapper(self.
context, uuids, label)
4559 raise ValueError(f
"Unsupported return type: {return_type}")
4562 """Calculate area-weighted sum of primitive data."""
4563 if return_type == float:
4564 return context_wrapper.calculatePrimitiveDataAreaWeightedSumFloatWrapper(self.
context, uuids, label)
4566 raise ValueError(f
"Unsupported return type: {return_type}")
4569 """Scale primitive data by a factor.
4572 scalePrimitiveData(uuids, label, factor) - scale for specific UUIDs
4573 scalePrimitiveData(label, factor) - scale for ALL primitives
4575 if isinstance(uuids_or_label, str):
4576 context_wrapper.scalePrimitiveDataAllWrapper(self.
context, uuids_or_label, label_or_factor)
4578 context_wrapper.scalePrimitiveDataWithUUIDsWrapper(self.
context, uuids_or_label, label_or_factor, factor)
4580 def incrementPrimitiveData(self, uuids: List[int], label: str, increment, data_type: str =
None) ->
None:
4581 """Increment primitive data for the given UUIDs.
4583 Each Helios increment overload only acts on fields whose stored type matches;
4584 fields of a different type are left unchanged. By default the overload is
4585 inferred from the Python type of ``increment`` (``int`` -> int, ``float`` ->
4586 float). To target an unsigned-int or double field, pass ``data_type``
4587 explicitly as one of ``'int'``, ``'uint'``, ``'float'``, ``'double'``.
4590 uuids: UUIDs whose data field to increment.
4591 label: Data field label.
4592 increment: Amount to add.
4593 data_type: Optional explicit field type to target.
4595 if data_type
is not None:
4596 dt = data_type.lower()
4598 context_wrapper.incrementPrimitiveDataIntWrapper(self.
context, uuids, label, int(increment))
4599 elif dt
in (
'uint',
'unsigned',
'unsigned int'):
4600 context_wrapper.incrementPrimitiveDataUIntWrapper(self.
context, uuids, label, int(increment))
4602 context_wrapper.incrementPrimitiveDataFloatWrapper(self.
context, uuids, label, float(increment))
4603 elif dt ==
'double':
4604 context_wrapper.incrementPrimitiveDataDoubleWrapper(self.
context, uuids, label, float(increment))
4606 raise ValueError(f
"Unsupported data_type: {data_type!r}. Expected one of 'int', 'uint', 'float', 'double'.")
4608 if isinstance(increment, float):
4609 context_wrapper.incrementPrimitiveDataFloatWrapper(self.
context, uuids, label, increment)
4610 elif isinstance(increment, int):
4611 context_wrapper.incrementPrimitiveDataIntWrapper(self.
context, uuids, label, increment)
4613 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4616 """Sum multiple primitive data fields into a new field."""
4617 context_wrapper.aggregatePrimitiveDataSumWrapper(self.
context, uuids, labels, result_label)
4620 """Multiply multiple primitive data fields into a new field."""
4621 context_wrapper.aggregatePrimitiveDataProductWrapper(self.
context, uuids, labels, result_label)
4624 """Calculate total one-sided surface area for a set of primitives."""
4625 return context_wrapper.sumPrimitiveSurfaceAreaWrapper(self.
context, uuids)
4627 def filterPrimitivesByData(self, uuids: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4628 """Filter primitives by data value. Auto-dispatches based on value type.
4631 uuids: UUIDs to filter.
4632 label: Data label to compare.
4633 value: Filter value (float, int, or str).
4634 comparator: Comparison operator ("=", "<", ">", "<=", ">="). Not used for strings.
4636 if isinstance(value, str):
4637 return context_wrapper.filterPrimitivesByDataStringWrapper(self.
context, uuids, label, value)
4638 elif isinstance(value, float):
4639 return context_wrapper.filterPrimitivesByDataFloatWrapper(self.
context, uuids, label, value, comparator)
4640 elif isinstance(value, int):
4641 return context_wrapper.filterPrimitivesByDataIntWrapper(self.
context, uuids, label, value, comparator)
4643 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4648 """Return the integer-coded `helios::ObjectType` of a compound object.
4650 Values follow the C++ `helios::ObjectType` enum
4651 (0=tile, 1=sphere, 2=tube, 3=box, 4=disk, 5=polymesh, 6=cone).
4654 return context_wrapper.getObjectTypeWrapper(self.
context, objID)
4658 x, y, z = context_wrapper.getObjectCenterWrapper(self.
context, objID)
4659 return vec3(x, y, z)
4662 """Get axis-aligned bounding box for one object or a list of objects.
4665 objIDs: Single object ID (int) or list of object IDs.
4668 Tuple of (min_corner: vec3, max_corner: vec3).
4671 if isinstance(objIDs, (list, tuple)):
4672 mn, mx = context_wrapper.getObjectBoundingBoxBatchWrapper(self.
context, list(objIDs))
4674 mn, mx = context_wrapper.getObjectBoundingBoxWrapper(self.
context, objIDs)
4675 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4678 """Get flattened primitive UUIDs for one object, a list of objects, or a list-of-lists.
4681 objIDs: int, List[int], or List[List[int]].
4684 Flat list of primitive UUIDs (union across all objects).
4687 if isinstance(objIDs, (list, tuple))
and objIDs
and isinstance(objIDs[0], (list, tuple)):
4688 return context_wrapper.getObjectPrimitiveUUIDsNestedWrapper(self.
context, [list(x)
for x
in objIDs])
4689 if isinstance(objIDs, (list, tuple)):
4690 return context_wrapper.getObjectPrimitiveUUIDsBatchWrapper(self.
context, list(objIDs))
4691 return context_wrapper.getObjectPrimitiveUUIDs(self.
context, int(objIDs))
4695 """Get tile-object area ratio for one or multiple tile objects."""
4697 if isinstance(objIDs, (list, tuple)):
4698 return context_wrapper.getTileObjectAreaRatioBatchWrapper(self.
context, list(objIDs))
4699 return context_wrapper.getTileObjectAreaRatioWrapper(self.
context, objIDs)
4703 x, y, z = context_wrapper.getTileObjectCenterWrapper(self.
context, objID)
4704 return vec3(x, y, z)
4708 x, y = context_wrapper.getTileObjectSizeWrapper(self.
context, objID)
4713 x, y = context_wrapper.getTileObjectSubdivisionCountWrapper(self.
context, objID)
4718 x, y, z = context_wrapper.getTileObjectNormalWrapper(self.
context, objID)
4723 pairs = context_wrapper.getTileObjectTextureUVWrapper(self.
context, objID)
4724 return [
vec2(u, v)
for u, v
in pairs]
4728 triples = context_wrapper.getTileObjectVerticesWrapper(self.
context, objID)
4729 return [
vec3(x, y, z)
for x, y, z
in triples]
4734 x, y, z = context_wrapper.getSphereObjectCenterWrapper(self.
context, objID)
4735 return vec3(x, y, z)
4738 """Get per-axis radii of a sphere object.
4740 Note: Helios spheres are spheroids with three independent radii (rx, ry, rz).
4741 Returns a vec3 (not a scalar).
4744 x, y, z = context_wrapper.getSphereObjectRadiusWrapper(self.
context, objID)
4745 return vec3(x, y, z)
4749 return context_wrapper.getSphereObjectSubdivisionCountWrapper(self.
context, objID)
4753 return context_wrapper.getSphereObjectVolumeWrapper(self.
context, objID)
4758 x, y, z = context_wrapper.getBoxObjectCenterWrapper(self.
context, objID)
4763 x, y, z = context_wrapper.getBoxObjectSizeWrapper(self.
context, objID)
4768 x, y, z = context_wrapper.getBoxObjectSubdivisionCountWrapper(self.
context, objID)
4773 return context_wrapper.getBoxObjectVolumeWrapper(self.
context, objID)
4778 x, y, z = context_wrapper.getDiskObjectCenterWrapper(self.
context, objID)
4783 x, y = context_wrapper.getDiskObjectSizeWrapper(self.
context, objID)
4788 return context_wrapper.getDiskObjectSubdivisionCountWrapper(self.
context, objID)
4793 return context_wrapper.getTubeObjectSubdivisionCountWrapper(self.
context, objID)
4797 return context_wrapper.getTubeObjectNodeCountWrapper(self.
context, objID)
4801 triples = context_wrapper.getTubeObjectNodesWrapper(self.
context, objID)
4802 return [
vec3(x, y, z)
for x, y, z
in triples]
4806 return context_wrapper.getTubeObjectNodeRadiiWrapper(self.
context, objID)
4810 triples = context_wrapper.getTubeObjectNodeColorsWrapper(self.
context, objID)
4811 return [
RGBcolor(r, g, b)
for r, g, b
in triples]
4815 return context_wrapper.getTubeObjectVolumeWrapper(self.
context, objID)
4819 return context_wrapper.getTubeObjectSegmentVolumeWrapper(self.
context, objID, segment_index)
4824 return context_wrapper.getConeObjectSubdivisionCountWrapper(self.
context, objID)
4828 triples = context_wrapper.getConeObjectNodesWrapper(self.
context, objID)
4829 return [
vec3(x, y, z)
for x, y, z
in triples]
4833 return context_wrapper.getConeObjectNodeRadiiWrapper(self.
context, objID)
4837 x, y, z = context_wrapper.getConeObjectNodeWrapper(self.
context, objID, number)
4838 return vec3(x, y, z)
4842 return context_wrapper.getConeObjectNodeRadiusWrapper(self.
context, objID, number)
4846 x, y, z = context_wrapper.getConeObjectAxisUnitVectorWrapper(self.
context, objID)
4847 return vec3(x, y, z)
4851 return context_wrapper.getConeObjectLengthWrapper(self.
context, objID)
4855 return context_wrapper.getConeObjectVolumeWrapper(self.
context, objID)
4861 x, y, z = context_wrapper.getPatchCenterWrapper(self.
context, uuid)
4862 return vec3(x, y, z)
4866 x, y = context_wrapper.getPatchSizeWrapper(self.
context, uuid)
4871 x, y, z = context_wrapper.getTriangleVertexWrapper(self.
context, uuid, number)
4876 x, y, z = context_wrapper.getVoxelCenterWrapper(self.
context, uuid)
4881 x, y, z = context_wrapper.getVoxelSizeWrapper(self.
context, uuid)
4884 def getPatchCount(self, include_hidden: bool =
True) -> int:
4886 return context_wrapper.getPatchCountWrapper(self.
context, include_hidden)
4890 return context_wrapper.getTriangleCountWrapper(self.
context, include_hidden)
4893 """Get axis-aligned bounding box for one primitive or a list of primitives.
4896 uuids: Single UUID (int) or list of UUIDs.
4899 Tuple of (min_corner: vec3, max_corner: vec3).
4902 if isinstance(uuids, (list, tuple)):
4903 mn, mx = context_wrapper.getPrimitiveBoundingBoxBatchWrapper(self.
context, list(uuids))
4905 mn, mx = context_wrapper.getPrimitiveBoundingBoxWrapper(self.
context, uuids)
4906 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4911 """Set the RGB or RGBA color of one primitive or a list of primitives.
4914 uuids: Single UUID (int) or list of UUIDs.
4915 color: RGBcolor or RGBAcolor.
4918 if isinstance(color, RGBAcolor):
4919 rgba = [color.r, color.g, color.b, color.a]
4920 if isinstance(uuids, (list, tuple)):
4921 context_wrapper.setPrimitiveColorRGBABatchWrapper(self.
context, list(uuids), rgba)
4923 context_wrapper.setPrimitiveColorRGBAWrapper(self.
context, uuids, rgba)
4924 elif isinstance(color, RGBcolor):
4925 rgb = [color.r, color.g, color.b]
4926 if isinstance(uuids, (list, tuple)):
4927 context_wrapper.setPrimitiveColorBatchWrapper(self.
context, list(uuids), rgb)
4929 context_wrapper.setPrimitiveColorWrapper(self.
context, uuids, rgb)
4931 raise ValueError(f
"color must be RGBcolor or RGBAcolor, got {type(color).__name__}")
4936 """Remove a named data field from one primitive or a list of primitives."""
4938 if isinstance(uuids, (list, tuple)):
4939 context_wrapper.clearPrimitiveDataByLabelBatchWrapper(self.
context, list(uuids), label)
4941 context_wrapper.clearPrimitiveDataByLabelWrapper(self.
context, uuids, label)
4944 """Remove a named data field from every primitive in the Context.
4946 Clears the data with the given label from all primitives (including hidden ones)
4947 and releases the registered data type for the label, so it may subsequently be
4948 re-registered with a different type. Requires helios-core v1.3.73 or newer.
4951 context_wrapper.clearAllPrimitiveDataByLabelWrapper(self.
context, label)
4954 """List all data labels attached to a primitive."""
4956 return context_wrapper.listPrimitiveDataWrapper(self.
context, uuid)
4962 if not isinstance(xbounds, vec2):
4963 raise ValueError(f
"xbounds must be a vec2, got {type(xbounds).__name__}")
4964 context_wrapper.cropDomainXWrapper(self.
context, xbounds.to_list())
4968 if not isinstance(ybounds, vec2):
4969 raise ValueError(f
"ybounds must be a vec2, got {type(ybounds).__name__}")
4970 context_wrapper.cropDomainYWrapper(self.
context, ybounds.to_list())
4974 if not isinstance(zbounds, vec2):
4975 raise ValueError(f
"zbounds must be a vec2, got {type(zbounds).__name__}")
4976 context_wrapper.cropDomainZWrapper(self.
context, zbounds.to_list())
4978 def cropDomain(self, *args) -> Optional[List[int]]:
4979 """Crop the context domain to the given XYZ bounds.
4982 cropDomain(xbounds: vec2, ybounds: vec2, zbounds: vec2)
4983 -> crop ALL primitives; returns None.
4984 cropDomain(uuids: List[int], xbounds: vec2, ybounds: vec2, zbounds: vec2)
4985 -> crop only the given primitives; returns the list of primitives
4986 that survived (in-bounds UUIDs). The input list is NOT mutated.
4991 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
4992 if not isinstance(b, vec2):
4993 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
4994 context_wrapper.cropDomainXYZWrapper(self.
context, xb.to_list(), yb.to_list(), zb.to_list())
4997 uuids, xb, yb, zb = args
4998 if not isinstance(uuids, (list, tuple)):
4999 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5000 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
5001 if not isinstance(b, vec2):
5002 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
5003 return context_wrapper.cropDomainByUUIDsWrapper(self.
context, list(uuids), xb.to_list(), yb.to_list(), zb.to_list())
5004 raise TypeError(f
"cropDomain() takes 3 or 4 positional arguments, got {len(args)}")
5013 """Return True if a compound object with the given ID exists."""
5015 return context_wrapper.doesObjectExistWrapper(self.
context, objID)
5018 """Return True if the given primitive UUID belongs to the given object."""
5020 return context_wrapper.doesObjectContainPrimitiveWrapper(self.
context, objID, uuid)
5023 """Return True if the named material has data stored under data_label."""
5025 return context_wrapper.doesMaterialDataExistWrapper(self.
context, material_label, data_label)
5028 """Return True if the compound object has a texture assigned."""
5030 return context_wrapper.objectHasTextureWrapper(self.
context, objID)
5033 """Return True if the primitive's geometry has been modified since the last clean mark."""
5035 return context_wrapper.isPrimitiveDirtyWrapper(self.
context, uuid)
5038 """Return True if value caching is enabled for the given object-data label."""
5040 return context_wrapper.isObjectDataValueCachingEnabledWrapper(self.
context, label)
5043 """Return True if value caching is enabled for the given primitive-data label."""
5045 return context_wrapper.isPrimitiveDataValueCachingEnabledWrapper(self.
context, label)
5048 """Return True if all primitives originally belonging to this object still exist
5049 (i.e., none have been deleted)."""
5051 return context_wrapper.areObjectPrimitivesCompleteWrapper(self.
context, objID)
5056 """Get the current simulation date as Julian day (1-366)."""
5058 return context_wrapper.getJulianDateWrapper(self.
context)
5061 """Return the total number of materials registered in the context."""
5063 return context_wrapper.getMaterialCountWrapper(self.
context)
5066 """Return the total surface area (one-sided) of all primitives in the object."""
5068 return context_wrapper.getObjectAreaWrapper(self.
context, objID)
5071 """Return the number of primitives currently belonging to the object."""
5073 return context_wrapper.getObjectPrimitiveCountWrapper(self.
context, objID)
5076 """Return the enclosed volume of a polymesh object."""
5078 return context_wrapper.getPolymeshObjectVolumeWrapper(self.
context, objID)
5081 """Look up a material ID from its human-readable label."""
5083 return context_wrapper.getMaterialIDFromLabelWrapper(self.
context, material_label)
5086 """Return the material ID assigned to the given primitive."""
5088 return context_wrapper.getPrimitiveMaterialIDWrapper(self.
context, uuid)
5091 """Return the version counter for a global data entry. Increments on each update;
5092 useful for cache invalidation."""
5094 return context_wrapper.getGlobalDataVersionWrapper(self.
context, label)
5097 """Return the ID of the compound object the primitive belongs to.
5099 Returns 0 if the primitive is not part of any compound object (the documented
5100 "no parent" sentinel). Raises ``HeliosRuntimeError`` if ``uuid`` does not exist.
5103 return context_wrapper.getPrimitiveParentObjectIDWrapper(self.
context, uuid)
5108 """Return the filesystem path of the texture assigned to the object, or an
5109 empty string if no texture is assigned."""
5111 return context_wrapper.getObjectTextureFileWrapper(self.
context, objID)
5114 """Return the union of all primitive-data labels used across every primitive
5117 return context_wrapper.listAllPrimitiveDataLabelsWrapper(self.
context)
5120 """Return the list of XML file paths that have been loaded into this context."""
5122 return context_wrapper.getLoadedXMLFilesWrapper(self.
context)
5127 """Print summary info for the object to stdout (for debugging)."""
5129 context_wrapper.printObjectInfoWrapper(self.
context, objID)
5132 """Print summary info for the primitive to stdout (for debugging)."""
5134 context_wrapper.printPrimitiveInfoWrapper(self.
context, uuid)
5137 """Enable value caching for the given primitive-data label. Required before
5138 using getUniquePrimitiveDataValues for that label."""
5140 context_wrapper.enablePrimitiveDataValueCachingWrapper(self.
context, label)
5143 """Disable value caching for the given primitive-data label."""
5145 context_wrapper.disablePrimitiveDataValueCachingWrapper(self.
context, label)
5148 """Enable value caching for the given object-data label. Required before
5149 using getUniqueObjectDataValues for that label."""
5151 context_wrapper.enableObjectDataValueCachingWrapper(self.
context, label)
5154 """Disable value caching for the given object-data label."""
5156 context_wrapper.disableObjectDataValueCachingWrapper(self.
context, label)
5159 """Compute the mean of the given primitive-data label across the object's
5160 primitives and store it as object data on the object itself under the
5163 context_wrapper.setObjectDataFromPrimitiveDataMeanWrapper(self.
context, objID, label)
5165 def renameMaterial(self, old_label: str, new_label: str) ->
None:
5166 """Rename an existing material."""
5168 context_wrapper.renameMaterialWrapper(self.
context, old_label, new_label)
5171 """Rename a primitive-data label on a single primitive."""
5173 context_wrapper.renamePrimitiveDataWrapper(self.
context, uuid, old_label, new_label)
5176 """Clear the named data entry on the given material."""
5178 context_wrapper.clearMaterialDataWrapper(self.
context, material_label, data_label)
5187 """Return the list of UUIDs that have been deleted from the context.
5189 These UUIDs are tombstoned and will not appear in getAllUUIDs(), but their
5190 IDs are tracked so they can be excluded from external references.
5193 return context_wrapper.getDeletedUUIDsWrapper(self.
context)
5195 def getDirtyUUIDs(self, include_deleted: bool =
True) -> List[int]:
5196 """Return the list of UUIDs whose geometry has been modified since the last
5197 markGeometryClean call.
5200 include_deleted: If True (default), include UUIDs that were deleted while
5201 dirty. If False, only return UUIDs that still exist.
5204 return context_wrapper.getDirtyUUIDsWrapper(self.
context, include_deleted)
5207 include_zero: bool =
True) -> List[int]:
5208 """Return the unique set of compound-object IDs that the given primitives
5212 uuids: List of primitive UUIDs to inspect.
5213 include_zero: If True (default), include the sentinel object ID 0
5214 (i.e., primitives with no parent object). If False, only return
5215 IDs of real compound objects.
5218 if not isinstance(uuids, (list, tuple)):
5219 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5220 return context_wrapper.getUniquePrimitiveParentObjectIDsWrapper(
5221 self.
context, list(uuids), include_zero
5227 """Return the area-weighted average normal of all primitives in the object."""
5229 x, y, z = context_wrapper.getObjectAverageNormalWrapper(self.
context, objID)
5230 return vec3(x, y, z)
5233 """Rotate the object so its area-weighted average normal aligns with
5234 new_normal. The rotation is applied about the given origin point."""
5236 if not isinstance(origin, vec3):
5237 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5238 if not isinstance(new_normal, vec3):
5239 raise ValueError(f
"new_normal must be a vec3, got {type(new_normal).__name__}")
5240 context_wrapper.setObjectAverageNormalWrapper(
5241 self.
context, objID, origin.to_list(), new_normal.to_list()
5245 """Translate the object so its origin is moved to the given point."""
5247 if not isinstance(origin, vec3):
5248 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5249 context_wrapper.setObjectOriginWrapper(self.
context, objID, origin.to_list())
5254 """Rotate a single primitive about the given origin so its azimuth
5255 equals new_azimuth (radians)."""
5257 if not isinstance(origin, vec3):
5258 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5259 context_wrapper.setPrimitiveAzimuthWrapper(
5260 self.
context, uuid, origin.to_list(), float(new_azimuth)
5264 """Rotate a single primitive about the given origin so its elevation
5265 equals new_elevation (radians)."""
5267 if not isinstance(origin, vec3):
5268 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5269 context_wrapper.setPrimitiveElevationWrapper(
5270 self.
context, uuid, origin.to_list(), float(new_elevation)
5275 def setTriangleVertices(self, uuid: int, vertex0: vec3, vertex1: vec3, vertex2: vec3) ->
None:
5276 """Replace the three vertices of an existing triangle primitive."""
5278 for name, v
in ((
"vertex0", vertex0), (
"vertex1", vertex1), (
"vertex2", vertex2)):
5279 if not isinstance(v, vec3):
5280 raise ValueError(f
"{name} must be a vec3, got {type(v).__name__}")
5281 context_wrapper.setTriangleVerticesWrapper(
5282 self.
context, uuid, vertex0.to_list(), vertex1.to_list(), vertex2.to_list()
5286 """Rotate one or more primitives so their normals align with new_normal.
5288 Accepts either a single UUID (int) or a list/tuple of UUIDs.
5289 The rotation is applied about the given origin point.
5292 if not isinstance(origin, vec3):
5293 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5294 if not isinstance(new_normal, vec3):
5295 raise ValueError(f
"new_normal must be a vec3, got {type(new_normal).__name__}")
5296 if isinstance(uuids_or_uuid, (list, tuple)):
5297 context_wrapper.setPrimitiveNormalBatchWrapper(
5298 self.
context, list(uuids_or_uuid), origin.to_list(), new_normal.to_list()
5301 context_wrapper.setPrimitiveNormalWrapper(
5302 self.
context, uuids_or_uuid, origin.to_list(), new_normal.to_list()
5306 """Reassign one or more primitives to belong to the given compound object.
5308 Accepts either a single UUID (int) or a list/tuple of UUIDs. Pass objID=0
5309 to detach primitive(s) from any object.
5312 if isinstance(uuids_or_uuid, (list, tuple)):
5313 context_wrapper.setPrimitiveParentObjectIDBatchWrapper(
5314 self.
context, list(uuids_or_uuid), int(objID)
5317 context_wrapper.setPrimitiveParentObjectIDWrapper(
5328 def setMaterialDataInt(self, material_label: str, data_label: str, value: int) ->
None:
5329 """Set int data on a material. Affects all primitives that reference it."""
5331 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5334 """Set unsigned int data on a material."""
5336 context_wrapper.setMaterialDataUIntWrapper(self.
context, material_label, data_label, int(value))
5339 """Set float data on a material."""
5341 context_wrapper.setMaterialDataFloatWrapper(self.
context, material_label, data_label, float(value))
5344 """Set double-precision float data on a material."""
5346 context_wrapper.setMaterialDataDoubleWrapper(self.
context, material_label, data_label, float(value))
5349 """Set string data on a material."""
5351 context_wrapper.setMaterialDataStringWrapper(self.
context, material_label, data_label, str(value))
5354 """Set vec2 data on a material."""
5356 if not isinstance(value, vec2):
5357 raise ValueError(f
"value must be a vec2, got {type(value).__name__}")
5358 context_wrapper.setMaterialDataVec2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5361 """Set vec3 data on a material."""
5363 if not isinstance(value, vec3):
5364 raise ValueError(f
"value must be a vec3, got {type(value).__name__}")
5365 context_wrapper.setMaterialDataVec3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5368 """Set vec4 data on a material."""
5370 if not isinstance(value, vec4):
5371 raise ValueError(f
"value must be a vec4, got {type(value).__name__}")
5372 context_wrapper.setMaterialDataVec4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5375 """Set int2 data on a material."""
5377 if not isinstance(value, int2):
5378 raise ValueError(f
"value must be an int2, got {type(value).__name__}")
5379 context_wrapper.setMaterialDataInt2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5382 """Set int3 data on a material."""
5384 if not isinstance(value, int3):
5385 raise ValueError(f
"value must be an int3, got {type(value).__name__}")
5386 context_wrapper.setMaterialDataInt3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5389 """Set int4 data on a material."""
5391 if not isinstance(value, int4):
5392 raise ValueError(f
"value must be an int4, got {type(value).__name__}")
5393 context_wrapper.setMaterialDataInt4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5399 return context_wrapper.getMaterialDataIntWrapper(self.
context, material_label, data_label)
5403 return context_wrapper.getMaterialDataUIntWrapper(self.
context, material_label, data_label)
5407 return context_wrapper.getMaterialDataFloatWrapper(self.
context, material_label, data_label)
5411 return context_wrapper.getMaterialDataDoubleWrapper(self.
context, material_label, data_label)
5415 return context_wrapper.getMaterialDataStringWrapper(self.
context, material_label, data_label)
5419 x, y = context_wrapper.getMaterialDataVec2Wrapper(self.
context, material_label, data_label)
5424 x, y, z = context_wrapper.getMaterialDataVec3Wrapper(self.
context, material_label, data_label)
5429 x, y, z, w = context_wrapper.getMaterialDataVec4Wrapper(self.
context, material_label, data_label)
5434 x, y = context_wrapper.getMaterialDataInt2Wrapper(self.
context, material_label, data_label)
5439 x, y, z = context_wrapper.getMaterialDataInt3Wrapper(self.
context, material_label, data_label)
5444 x, y, z, w = context_wrapper.getMaterialDataInt4Wrapper(self.
context, material_label, data_label)
5448 """Return the HeliosDataType enum value for the given material data entry.
5450 Encoding (from Helios core): 0=INT, 1=UINT, 2=FLOAT, 3=DOUBLE,
5451 4=VEC2, 5=VEC3, 6=VEC4, 7=INT2, 8=INT3, 9=INT4, 10=STRING.
5454 return context_wrapper.getMaterialDataTypeWrapper(self.
context, material_label, data_label)
5458 def setMaterialData(self, material_label: str, data_label: str, value) ->
None:
5459 """Set material data with type detection from the Python value.
5461 Dispatches to the correct typed setter based on ``isinstance`` of ``value``.
5462 For unambiguous numeric width control (e.g., uint vs int), call the
5463 per-type method directly (``setMaterialDataUInt``, etc.).
5466 if isinstance(value, bool):
5468 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5469 elif isinstance(value, int):
5470 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5471 elif isinstance(value, float):
5472 context_wrapper.setMaterialDataFloatWrapper(self.
context, material_label, data_label, float(value))
5473 elif isinstance(value, str):
5474 context_wrapper.setMaterialDataStringWrapper(self.
context, material_label, data_label, value)
5475 elif isinstance(value, vec2):
5476 context_wrapper.setMaterialDataVec2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5477 elif isinstance(value, vec3):
5478 context_wrapper.setMaterialDataVec3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5479 elif isinstance(value, vec4):
5480 context_wrapper.setMaterialDataVec4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5481 elif isinstance(value, int2):
5482 context_wrapper.setMaterialDataInt2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5483 elif isinstance(value, int3):
5484 context_wrapper.setMaterialDataInt3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5485 elif isinstance(value, int4):
5486 context_wrapper.setMaterialDataInt4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5489 f
"Unsupported value type for setMaterialData: {type(value).__name__}. "
5490 f
"Supported: int, float, str, vec2, vec3, vec4, int2, int3, int4. "
5491 f
"For uint/double, call setMaterialDataUInt/Double directly."
5494 def getMaterialData(self, material_label: str, data_label: str, data_type: type =
None):
5495 """Get material data, auto-detecting the type from Helios storage if not specified.
5498 material_label: Name of the material.
5499 data_label: Data entry label.
5500 data_type: Optional Python type (int, float, str, vec2, vec3, vec4, int2,
5501 int3, int4) or string ('uint', 'double'). If ``None``, the type is
5502 queried via getMaterialDataType and dispatched automatically.
5505 if data_type
is None:
5506 t = context_wrapper.getMaterialDataTypeWrapper(self.
context, material_label, data_label)
5509 return context_wrapper.getMaterialDataIntWrapper(self.
context, material_label, data_label)
5511 return context_wrapper.getMaterialDataUIntWrapper(self.
context, material_label, data_label)
5513 return context_wrapper.getMaterialDataFloatWrapper(self.
context, material_label, data_label)
5515 return context_wrapper.getMaterialDataDoubleWrapper(self.
context, material_label, data_label)
5517 x, y = context_wrapper.getMaterialDataVec2Wrapper(self.
context, material_label, data_label)
5520 x, y, z = context_wrapper.getMaterialDataVec3Wrapper(self.
context, material_label, data_label)
5521 return vec3(x, y, z)
5523 x, y, z, w = context_wrapper.getMaterialDataVec4Wrapper(self.
context, material_label, data_label)
5524 return vec4(x, y, z, w)
5526 x, y = context_wrapper.getMaterialDataInt2Wrapper(self.
context, material_label, data_label)
5529 x, y, z = context_wrapper.getMaterialDataInt3Wrapper(self.
context, material_label, data_label)
5530 return int3(x, y, z)
5532 x, y, z, w = context_wrapper.getMaterialDataInt4Wrapper(self.
context, material_label, data_label)
5533 return int4(x, y, z, w)
5535 return context_wrapper.getMaterialDataStringWrapper(self.
context, material_label, data_label)
5536 raise ValueError(f
"Unknown HeliosDataType code: {t}")
5539 if data_type == int:
5541 if data_type == float:
5543 if data_type == str:
5545 if data_type ==
"uint":
5547 if data_type ==
"double":
5549 if data_type == vec2:
5551 if data_type == vec3:
5553 if data_type == vec4:
5555 if data_type == int2:
5557 if data_type == int3:
5559 if data_type == int4:
5562 f
"Unsupported material data type: {data_type}. Supported: int, float, str, "
5563 f
"vec2, vec3, vec4, int2, int3, int4, 'uint', 'double'."
5569 """Return the unique values stored under ``label`` across all primitives.
5571 Requires value caching to be enabled for ``label`` first via
5572 ``enablePrimitiveDataValueCaching(label)``. Supported ``dtype`` values:
5573 ``int``, ``str``, or the string ``'uint'``.
5577 return context_wrapper.getUniquePrimitiveDataValuesIntWrapper(self.
context, label)
5579 return context_wrapper.getUniquePrimitiveDataValuesUIntWrapper(self.
context, label)
5581 return context_wrapper.getUniquePrimitiveDataValuesStringWrapper(self.
context, label)
5583 f
"Unsupported dtype for getUniquePrimitiveDataValues: {dtype}. "
5584 f
"Supported: int, str, 'uint'."
5588 """Return the unique values stored under ``label`` across all compound objects.
5590 Requires value caching to be enabled for ``label`` first via
5591 ``enableObjectDataValueCaching(label)``. Supported ``dtype`` values:
5592 ``int``, ``str``, or the string ``'uint'``.
5596 return context_wrapper.getUniqueObjectDataValuesIntWrapper(self.
context, label)
5598 return context_wrapper.getUniqueObjectDataValuesUIntWrapper(self.
context, label)
5600 return context_wrapper.getUniqueObjectDataValuesStringWrapper(self.
context, label)
5602 f
"Unsupported dtype for getUniqueObjectDataValues: {dtype}. "
5603 f
"Supported: int, str, 'uint'."
5612 """Coerce a 4x4 transformation matrix input into a flat list of 16 floats.
5614 Accepts: numpy.ndarray of shape (4,4) or (16,), list/tuple of 16 floats,
5615 or nested list/tuple of shape (4,4). Helios stores transformation matrices
5616 in **row-major** order: T[i*4 + j] = element (i, j). A numpy ndarray of
5617 shape (4,4) maps directly via .ravel() since numpy is row-major by default.
5620 if isinstance(value, np.ndarray):
5621 if value.shape == (4, 4):
5622 return [float(v)
for v
in value.ravel().tolist()]
5623 if value.shape == (16,):
5624 return [float(v)
for v
in value.tolist()]
5626 f
"Matrix ndarray must have shape (4,4) or (16,), got {value.shape}"
5629 if isinstance(value, (list, tuple))
and len(value) == 4
and \
5630 all(isinstance(row, (list, tuple))
and len(row) == 4
for row
in value):
5633 flat.extend(float(v)
for v
in row)
5636 if isinstance(value, (list, tuple))
and len(value) == 16:
5637 return [float(v)
for v
in value]
5639 f
"Matrix must be a (4,4) ndarray, (16,) ndarray, list of 16 floats, "
5640 f
"or nested 4x4 list. Got: {type(value).__name__}"
5645 """Convert a flat list of 16 floats (row-major) to a (4,4) numpy ndarray."""
5646 return np.array(flat, dtype=np.float32).reshape((4, 4))
5651 """Return the object's 4x4 transformation matrix as a (4,4) float32 ndarray.
5653 Helios stores matrices in row-major order, so element (i, j) is at
5654 position [i, j] of the returned ndarray. The translation column is at
5655 positions [0, 3], [1, 3], [2, 3].
5658 flat = context_wrapper.getObjectTransformationMatrixWrapper(self.
context, int(objID))
5662 """Set the 4x4 transformation matrix on one or more compound objects.
5665 objIDs_or_objID: A single object ID (int) or a list/tuple of object IDs.
5666 T: A 4x4 matrix as numpy.ndarray((4,4) | (16,) float), list of 16 floats,
5667 or a nested 4x4 list. Row-major; T[i, j] is element (i, j).
5671 if isinstance(objIDs_or_objID, (list, tuple)):
5672 context_wrapper.setObjectTransformationMatrixBatchWrapper(
5673 self.
context, list(objIDs_or_objID), flat
5676 context_wrapper.setObjectTransformationMatrixWrapper(
5677 self.
context, int(objIDs_or_objID), flat
5681 """Return the primitive's 4x4 transformation matrix as a (4,4) float32 ndarray
5682 (row-major; see getObjectTransformationMatrix for layout details)."""
5684 flat = context_wrapper.getPrimitiveTransformationMatrixWrapper(self.
context, int(uuid))
5688 """Set the 4x4 transformation matrix on one or more primitives.
5691 uuids_or_uuid: A single UUID (int) or a list/tuple of UUIDs.
5692 T: A 4x4 matrix; see setObjectTransformationMatrix for accepted formats.
5696 if isinstance(uuids_or_uuid, (list, tuple)):
5697 context_wrapper.setPrimitiveTransformationMatrixBatchWrapper(
5698 self.
context, list(uuids_or_uuid), flat
5701 context_wrapper.setPrimitiveTransformationMatrixWrapper(
5702 self.
context, int(uuids_or_uuid), flat
5708 """Return the axis-aligned bounding box of the domain (or a UUID subset).
5711 uuids: Optional list of primitive UUIDs to restrict the computation to.
5712 If None (default), uses every primitive in the context.
5715 ``(xbounds, ybounds, zbounds)`` where each element is a ``vec2(min, max)``.
5719 xb, yb, zb = context_wrapper.getDomainBoundingBoxWrapper(self.
context)
5721 if not isinstance(uuids, (list, tuple)):
5722 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5723 xb, yb, zb = context_wrapper.getDomainBoundingBoxFilteredWrapper(self.
context, list(uuids))
5724 return (
vec2(xb[0], xb[1]),
vec2(yb[0], yb[1]),
vec2(zb[0], zb[1]))
5727 """Return the bounding sphere of the domain (or a UUID subset).
5730 ``(center, radius)`` where ``center`` is a ``vec3`` and ``radius`` is a float.
5734 center, radius = context_wrapper.getDomainBoundingSphereWrapper(self.
context)
5736 if not isinstance(uuids, (list, tuple)):
5737 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5738 center, radius = context_wrapper.getDomainBoundingSphereFilteredWrapper(self.
context, list(uuids))
5739 return (
vec3(center[0], center[1], center[2]), float(radius))
5747 def setTubeNodes(self, objID: int, nodes: List[vec3]) ->
None:
5748 """Replace the node positions of an existing tube object."""
5750 if not isinstance(nodes, (list, tuple)):
5751 raise ValueError(f
"nodes must be a list or tuple, got {type(nodes).__name__}")
5753 for i, n
in enumerate(nodes):
5754 if not isinstance(n, vec3):
5755 raise ValueError(f
"nodes[{i}] must be a vec3, got {type(n).__name__}")
5756 flat.extend([n.x, n.y, n.z])
5757 context_wrapper.setTubeNodesWrapper(self.
context, int(objID), flat)
5759 def setTubeRadii(self, objID: int, radii: List[float]) ->
None:
5760 """Replace the per-node radii of an existing tube object."""
5762 if not isinstance(radii, (list, tuple)):
5763 raise ValueError(f
"radii must be a list or tuple, got {type(radii).__name__}")
5764 context_wrapper.setTubeRadiiWrapper(self.
context, int(objID), [float(r)
for r
in radii])
5766 def scaleTubeGirth(self, objID: int, scale_factor: float) ->
None:
5767 """Scale the radii of an existing tube object by ``scale_factor``."""
5769 context_wrapper.scaleTubeGirthWrapper(self.
context, int(objID), float(scale_factor))
5772 """Scale the lengths between tube nodes by ``scale_factor``."""
5774 context_wrapper.scaleTubeLengthWrapper(self.
context, int(objID), float(scale_factor))
5777 """Remove all tube nodes from index ``node_index`` to the end."""
5779 context_wrapper.pruneTubeNodesWrapper(self.
context, int(objID), int(node_index))
5782 color: Optional[RGBcolor] =
None,
5783 texture_file: Optional[str] =
None,
5784 uv: Optional[vec2] =
None) ->
None:
5785 """Append a new segment to an existing tube object.
5787 Pass exactly one of ``color`` (an RGBcolor) or both ``texture_file`` and
5788 ``uv`` (a vec2 of texture u-fractions) to specify how the new segment
5792 if not isinstance(node_position, vec3):
5793 raise ValueError(f
"node_position must be a vec3, got {type(node_position).__name__}")
5794 has_color = color
is not None
5795 has_texture = texture_file
is not None or uv
is not None
5796 if has_color == has_texture:
5798 "appendTubeSegment requires exactly one of (color) or "
5799 "(texture_file and uv); cannot mix or omit both."
5802 if not isinstance(color, RGBcolor):
5803 raise ValueError(f
"color must be an RGBcolor, got {type(color).__name__}")
5804 context_wrapper.appendTubeSegmentColorWrapper(
5805 self.
context, int(objID), node_position.to_list(), float(radius),
5806 [color.r, color.g, color.b]
5809 if texture_file
is None or uv
is None:
5811 "appendTubeSegment with texture requires both texture_file and uv."
5813 if not isinstance(uv, vec2):
5814 raise ValueError(f
"uv must be a vec2, got {type(uv).__name__}")
5816 texture_file, [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp']
5818 context_wrapper.appendTubeSegmentTextureWrapper(
5819 self.
context, int(objID), node_position.to_list(), float(radius),
5820 tex_path, [uv.x, uv.y]
5826 """Group the given primitives into a new polymesh compound object and return its ID."""
5828 if not isinstance(uuids, (list, tuple)):
5829 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5831 raise ValueError(
"addPolymeshObject requires at least one UUID")
5832 return context_wrapper.addPolymeshObjectWrapper(self.
context, list(uuids))
5837 """Set the color of one or more compound objects.
5839 Accepts a single object ID or list/tuple of IDs. ``color`` must be an
5840 ``RGBcolor`` or ``RGBAcolor``.
5843 if isinstance(color, RGBAcolor):
5844 comps = [color.r, color.g, color.b, color.a]
5845 if isinstance(objIDs_or_objID, (list, tuple)):
5846 context_wrapper.setObjectColorRGBABatchWrapper(self.
context, list(objIDs_or_objID), comps)
5848 context_wrapper.setObjectColorRGBAWrapper(self.
context, int(objIDs_or_objID), comps)
5849 elif isinstance(color, RGBcolor):
5850 comps = [color.r, color.g, color.b]
5851 if isinstance(objIDs_or_objID, (list, tuple)):
5852 context_wrapper.setObjectColorRGBBatchWrapper(self.
context, list(objIDs_or_objID), comps)
5854 context_wrapper.setObjectColorRGBWrapper(self.
context, int(objIDs_or_objID), comps)
5857 f
"color must be an RGBcolor or RGBAcolor, got {type(color).__name__}"
5861 """Override the texture mapping with the object's vertex color."""
5863 if isinstance(objIDs_or_objID, (list, tuple)):
5864 context_wrapper.overrideObjectTextureColorBatchWrapper(self.
context, list(objIDs_or_objID))
5866 context_wrapper.overrideObjectTextureColorWrapper(self.
context, int(objIDs_or_objID))
5869 """Restore use of the texture color (undoes overrideObjectTextureColor)."""
5871 if isinstance(objIDs_or_objID, (list, tuple)):
5872 context_wrapper.useObjectTextureColorBatchWrapper(self.
context, list(objIDs_or_objID))
5874 context_wrapper.useObjectTextureColorWrapper(self.
context, int(objIDs_or_objID))
5879 """Mark one or more primitives as dirty (geometry has been modified)."""
5881 if isinstance(uuids_or_uuid, (list, tuple)):
5882 context_wrapper.markPrimitiveDirtyBatchWrapper(self.
context, list(uuids_or_uuid))
5884 context_wrapper.markPrimitiveDirtyWrapper(self.
context, int(uuids_or_uuid))
5887 """Mark one or more primitives as clean (cancels dirty state)."""
5889 if isinstance(uuids_or_uuid, (list, tuple)):
5890 context_wrapper.markPrimitiveCleanBatchWrapper(self.
context, list(uuids_or_uuid))
5892 context_wrapper.markPrimitiveCleanWrapper(self.
context, int(uuids_or_uuid))
5897 """Set the (Nx, Ny) subdivision count of one or more tile objects.
5899 The Helios C++ API is batch-only; a single objID is wrapped as a
5900 single-element list.
5903 if not isinstance(subdiv, int2):
5904 raise ValueError(f
"subdiv must be an int2, got {type(subdiv).__name__}")
5905 if isinstance(objIDs_or_objID, (list, tuple)):
5906 ids = list(objIDs_or_objID)
5908 ids = [int(objIDs_or_objID)]
5909 context_wrapper.setTileObjectSubdivisionCountWrapper(
5910 self.
context, ids, int(subdiv.x), int(subdiv.y)
5914 """Set tile object subdivision dynamically based on a target area ratio.
5916 Each tile is subdivided so that its sub-tile area is approximately
5917 ``area_ratio`` times the tile's full area.
5920 if isinstance(objIDs_or_objID, (list, tuple)):
5921 ids = list(objIDs_or_objID)
5923 ids = [int(objIDs_or_objID)]
5924 context_wrapper.setTileObjectSubdivisionByAreaRatioWrapper(
5925 self.
context, ids, float(area_ratio)
5935 """Return a new list with deleted UUIDs removed; the input list is not mutated.
5937 This mirrors the convention used by ``cropDomain``, which returns the
5938 survivors rather than mutating in place.
5941 if not isinstance(uuids, (list, tuple)):
5942 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5943 return context_wrapper.cleanDeletedUUIDsWrapper(self.
context, list(uuids))
5946 """Return a new list with deleted object IDs removed; input is not mutated."""
5948 if not isinstance(objIDs, (list, tuple)):
5949 raise ValueError(f
"objIDs must be a list or tuple, got {type(objIDs).__name__}")
5950 return context_wrapper.cleanDeletedObjectIDsWrapper(self.
context, list(objIDs))
5954 def writeXML(self, filename: str, uuids: Optional[List[int]] =
None, quiet: bool =
False) ->
None:
5955 """Write the context (or a UUID subset) to an XML file.
5958 filename: Output file path. Must end in .xml.
5959 uuids: Optional list of primitive UUIDs to restrict the export. If
5960 None (default), all primitives are written.
5961 quiet: Suppress informational console output.
5966 context_wrapper.writeXMLWrapper(self.
context, path, bool(quiet))
5968 if not isinstance(uuids, (list, tuple)):
5969 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5970 context_wrapper.writeXMLFilteredWrapper(self.
context, path, list(uuids), bool(quiet))
5972 def writeXML_byobject(self, filename: str, objIDs: List[int], quiet: bool =
False) ->
None:
5973 """Write a subset of compound objects to an XML file."""
5976 if not isinstance(objIDs, (list, tuple)):
5977 raise ValueError(f
"objIDs must be a list or tuple, got {type(objIDs).__name__}")
5978 context_wrapper.writeXMLByObjectWrapper(self.
context, path, list(objIDs), bool(quiet))
5982 def randu(self, low=None, high=None):
5983 """Draw a uniform random number using the Context's RNG.
5986 ``randu()`` -> float in [0, 1)
5987 ``randu(low: float, high: float)`` -> float in [low, high)
5988 ``randu(low: int, high: int)`` -> int in [low, high]
5990 Whether the integer or float overload is invoked is determined by
5991 ``isinstance(low, int)``; pass ``low/high`` as Python ints for the
5995 if low
is None and high
is None:
5996 return context_wrapper.randuBasicWrapper(self.
context)
5997 if low
is None or high
is None:
5998 raise ValueError(
"randu requires both low and high, or neither.")
5999 if isinstance(low, bool)
or isinstance(high, bool):
6000 raise ValueError(
"randu bounds cannot be bool.")
6003 if isinstance(low, int)
and isinstance(high, int):
6004 return context_wrapper.randuIntRangeWrapper(self.
context, low, high)
6005 return context_wrapper.randuRangeWrapper(self.
context, float(low), float(high))
6007 def randn(self, mean=None, stddev=None) -> float:
6008 """Draw a normal random number using the Context's RNG.
6011 ``randn()`` -> standard normal (mean 0, stddev 1)
6012 ``randn(mean: float, stddev: float)`` -> N(mean, stddev**2)
6015 if mean
is None and stddev
is None:
6016 return context_wrapper.randnBasicWrapper(self.
context)
6017 if mean
is None or stddev
is None:
6018 raise ValueError(
"randn requires both mean and stddev, or neither.")
6019 return context_wrapper.randnParamsWrapper(self.
context, float(mean), float(stddev))
6023 def setLocation(self, location_or_lat, longitude=None, utc_offset=None, altitude=0.0) -> None:
6024 """Set the geographic location used by solar/radiation calculations.
6027 ``setLocation(loc: Location)``
6028 ``setLocation(latitude_deg: float, longitude_deg: float, utc_offset: float, altitude=0.0)``
6030 ``altitude`` is the height of the local Cartesian origin in meters above
6031 sea level. It is only used in the (lat, lon, utc) float form; when passing
6032 a ``Location`` object, the location's own altitude is used.
6035 if isinstance(location_or_lat, Location):
6036 if longitude
is not None or utc_offset
is not None or altitude != 0.0:
6037 raise ValueError(
"When passing a Location, do not also pass longitude/utc_offset/altitude; "
6038 "set them on the Location object instead.")
6039 loc = location_or_lat
6041 if longitude
is None or utc_offset
is None:
6043 "setLocation requires either a Location object or "
6044 "(latitude_deg, longitude_deg, utc_offset) as 3 floats."
6046 loc =
Location(float(location_or_lat), float(longitude), float(utc_offset), float(altitude))
6047 context_wrapper.setLocationWrapper(self.
context, loc.latitude, loc.longitude, loc.utc_offset, loc.altitude)
6050 """Return the Context's currently-configured geographic location."""
6052 lat, lon, utc, alt = context_wrapper.getLocationWrapper(self.
context)
6053 return Location(lat, lon, utc, alt)
6060 """Generate a colormap with ``n_colors`` entries from a named colormap.
6063 name: Helios colormap name (e.g., "hot", "cool", "lava", "rainbow").
6064 n_colors: Number of colors in the returned ramp.
6067 A list of ``RGBcolor`` instances of length ``n_colors``.
6070 flat = context_wrapper.generateColormapNamedWrapper(self.
context, name, int(n_colors))
6071 return [
RGBcolor(flat[i*3 + 0], flat[i*3 + 1], flat[i*3 + 2])
for i
in range(int(n_colors))]
6074 """Generate one texture file per color in ``colormap`` derived from
6075 ``texture_file``. Returns the list of generated file paths.
6078 if not isinstance(colormap, (list, tuple)):
6079 raise ValueError(f
"colormap must be a list or tuple, got {type(colormap).__name__}")
6081 for i, c
in enumerate(colormap):
6082 if not isinstance(c, RGBcolor):
6083 raise ValueError(f
"colormap[{i}] must be an RGBcolor, got {type(c).__name__}")
6084 flat.extend([c.r, c.g, c.b])
6087 texture_file, [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp']
6089 return context_wrapper.generateTexturesFromColormapWrapper(
6090 self.
context, validated_path, flat
6094 """Return the primitive's texture transparency mask as a 2D bool ndarray.
6096 Returns None if the primitive has no associated transparency channel
6097 (e.g., it is untextured or its texture has no alpha). The returned
6098 ndarray has shape (height, width) and dtype ``bool``.
6101 result = context_wrapper.getPrimitiveTextureTransparencyDataWrapper(self.
context, int(uuid))
6104 width, height, flat = result
6105 return np.array(flat, dtype=bool).reshape((height, width))
Central simulation environment for PyHelios that manages 3D primitives and their data.
getDomainBoundingSphere(self, Optional[List[int]] uuids=None)
Return the bounding sphere of the domain (or a UUID subset).
None setGlobalDataVec3(self, str label, x_or_vec, float y=None, float z=None)
Set global data as vec3.
int addTileObject(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), SphericalCoord rotation=SphericalCoord(1, 0, 0), int2 subdiv=int2(1, 1), Optional[RGBcolor] color=None, Optional[str] texturefile=None, Optional[int2] texture_repeat=None)
Add a tiled patch (subdivided patch) as a compound object to the context.
None scaleConeObjectGirth(self, int ObjID, float scale_factor)
Scale the girth of a Cone object by scaling the radii at both nodes.
int getTubeObjectNodeCount(self, int objID)
vec3 getBoxObjectSize(self, int objID)
None duplicateObjectData(self, int objID, str old_label, str new_label)
Copy object data to a new label.
str getMaterialTexture(self, str material_label)
Get the texture file path for a material.
getMaterialColor(self, str material_label)
Get the RGBA color of a material.
getObjectData(self, int objID, str label, type data_type=None)
Get object data with optional type specification.
getAllPrimitiveVertices(self)
Get vertices for all primitives.
None setObjectOrigin(self, int objID, vec3 origin)
Translate the object so its origin is moved to the given point.
List[RGBcolor] getTubeObjectNodeColors(self, int objID)
'np.ndarray' _mat4_to_ndarray(List[float] flat)
Convert a flat list of 16 floats (row-major) to a (4,4) numpy ndarray.
Union[int, List[int]] copyObject(self, Union[int, List[int]] ObjID)
Copy one or more compound objects.
List[str] listObjectData(self, int objID)
List all data labels on a specific object.
None scaleTubeLength(self, int objID, float scale_factor)
Scale the lengths between tube nodes by scale_factor.
int getMaterialTwosidedFlag(self, str material_label)
Get the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided).
Optional[List[int]] cropDomain(self, *args)
Crop the context domain to the given XYZ bounds.
None setMaterialDataVec3(self, str material_label, str data_label, vec3 value)
Set vec3 data on a material.
None markPrimitiveDirty(self, uuids_or_uuid)
Mark one or more primitives as dirty (geometry has been modified).
None deletePrimitive(self, Union[int, List[int]] uuids_or_uuid)
Delete one or more primitives from the context.
None setMaterialDataInt4(self, str material_label, str data_label, int4 value)
Set int4 data on a material.
None clearAllPrimitiveData(self, str label)
Remove a named data field from every primitive in the Context.
None setMaterialDataInt3(self, str material_label, str data_label, int3 value)
Set int3 data on a material.
_validate_uuid(self, int uuid)
Validate that a UUID exists in this context.
getGlobalData(self, str label, type data_type=None)
Get global data with optional type specification.
List[int] getAllUUIDs(self)
List[int] addBox(self, vec3 center=vec3(0, 0, 0), vec3 size=vec3(1, 1, 1), int3 subdiv=int3(1, 1, 1), Optional[RGBcolor] color=None)
Add a rectangular box to the context.
addTimeseriesData(self, str label, float value, 'Date' date, 'Time' time)
Add a data point to a timeseries variable.
None setObjectColor(self, objIDs_or_objID, color)
Set the color of one or more compound objects.
str _validate_output_file_path(self, str filename, List[str] expected_extensions=None)
Validate and normalize output file path for security.
bool primitiveTextureHasTransparencyChannel(self, int uuid)
Check if primitive texture has a transparency channel.
getPrimitiveMaterialLabel(self, uuid)
Get the material label assigned to a primitive or multiple primitives.
None enablePrimitiveDataValueCaching(self, str label)
Enable value caching for the given primitive-data label.
int getPrimitiveTwosidedFlag(self, int uuid, int default_value=1)
Get two-sided rendering flag for a primitive.
None cropDomainX(self, vec2 xbounds)
List[str] getAllPrimitiveTextureFiles(self)
Get texture files for all primitives.
None setObjectDataVec4(self, objids_or_objid, str label, x_or_vec, float y=None, float z=None, float w=None)
Set object data as vec4.
getDomainBoundingBox(self, Optional[List[int]] uuids=None)
Return the axis-aligned bounding box of the domain (or a UUID subset).
bool isPrimitiveHidden(self, int uuid)
Check if a primitive is hidden.
np.ndarray getPrimitiveDataArray(self, List[int] uuids, str label)
Get primitive data values for multiple primitives as a NumPy array.
None setObjectAverageNormal(self, int objID, vec3 origin, vec3 new_normal)
Rotate the object so its area-weighted average normal aligns with new_normal.
List[int] getObjectPrimitiveUUIDs(self, objIDs)
Get flattened primitive UUIDs for one object, a list of objects, or a list-of-lists.
None writeXML_byobject(self, str filename, List[int] objIDs, bool quiet=False)
Write a subset of compound objects to an XML file.
bool is_plugin_available(self, str plugin_name)
Check if a specific plugin is available.
getPrimitiveColor(self, uuid)
Get the color of a primitive or multiple primitives.
getPrimitiveArea(self, uuid)
Get the area of a primitive or multiple primitives.
str getGlobalDataString(self, str label)
Get string global data.
vec3 getTileObjectNormal(self, int objID)
bool doesTimeseriesVariableExist(self, str label)
Check whether a timeseries variable exists.
None setTubeNodes(self, int objID, List[vec3] nodes)
Replace the node positions of an existing tube object.
List[int] getAllObjectIDs(self)
bool isPrimitiveDataValueCachingEnabled(self, str label)
Return True if value caching is enabled for the given primitive-data label.
None setObjectDataUInt(self, objids_or_objid, str label, int value)
Set object data as unsigned 32-bit integer.
deleteTimeseriesVariable(self, str label)
Delete a single timeseries variable and all of its data points.
List[str] listAllObjectDataLabels(self)
List all object data labels in context.
List[str] get_available_plugins(self)
Get list of available plugins for this PyHelios instance.
__exit__(self, exc_type, exc_value, traceback)
int getPrimitiveParentObjectID(self, int uuid)
Return the ID of the compound object the primitive belongs to.
None setPrimitiveNormal(self, uuids_or_uuid, vec3 origin, vec3 new_normal)
Rotate one or more primitives so their normals align with new_normal.
None setPrimitiveDataInt4(self, uuids_or_uuid, str label, x_or_vec, int y=None, int z=None, int w=None)
Set primitive data as int4 for one or multiple primitives.
None setTileObjectSubdivisionCount(self, objIDs_or_objID, int2 subdiv)
Set the (Nx, Ny) subdivision count of one or more tile objects.
None setPrimitiveParentObjectID(self, uuids_or_uuid, int objID)
Reassign one or more primitives to belong to the given compound object.
None hidePrimitive(self, uuids_or_uuid)
Hide one or more primitives.
None clearMaterialData(self, str material_label, str data_label)
Clear the named data entry on the given material.
int getTimeseriesLength(self, str label)
Get the number of data points in a timeseries variable.
None setTriangleVertices(self, int uuid, vec3 vertex0, vec3 vertex1, vec3 vertex2)
Replace the three vertices of an existing triangle primitive.
int addSphereObject(self, vec3 center=vec3(0, 0, 0), Union[float, vec3] radius=1.0, int ndivs=20, Optional[RGBcolor] color=None, Optional[str] texturefile=None)
Add a spherical or ellipsoidal compound object to the context.
float getConeObjectVolume(self, int objID)
setCurrentTimeseriesPoint(self, str label, int index)
Set the Context date and time from a timeseries data point index.
calculatePrimitiveDataAreaWeightedMean(self, List[int] uuids, str label, type return_type=float)
Calculate area-weighted mean of primitive data.
bool doesMaterialDataExist(self, str material_label, str data_label)
Return True if the named material has data stored under data_label.
getTileObjectAreaRatio(self, objIDs)
Get tile-object area ratio for one or multiple tile objects.
bool doesPrimitiveDataExist(self, int uuid, str label)
Check if primitive data exists for a specific primitive and label.
None setMaterialDataVec2(self, str material_label, str data_label, vec2 value)
Set vec2 data on a material.
int getObjectDataSize(self, int objID, str label)
Get the size of object data array.
float getTubeObjectSegmentVolume(self, int objID, int segment_index)
List[str] listMaterials(self)
Get list of all material labels in the context.
None enableObjectDataValueCaching(self, str label)
Enable value caching for the given object-data label.
None setMaterialDataInt2(self, str material_label, str data_label, int2 value)
Set int2 data on a material.
List[int] cleanDeletedUUIDs(self, List[int] uuids)
Return a new list with deleted UUIDs removed; the input list is not mutated.
None setPrimitiveDataInt(self, uuids_or_uuid, str label, int value)
Set primitive data as signed 32-bit integer for one or multiple primitives.
None setPrimitiveDataDouble(self, uuids_or_uuid, str label, float value)
Set primitive data as 64-bit double for one or multiple primitives.
None setPrimitiveDataVec2(self, uuids_or_uuid, str label, x_or_vec, float y=None)
Set primitive data as vec2 for one or multiple primitives.
int getPrimitiveMaterialID(self, int uuid)
Return the material ID assigned to the given primitive.
'np.ndarray' getAllPrimitiveColors(self)
Get colors for all primitives.
int getPrimitiveCount(self)
vec3 getObjectAverageNormal(self, int objID)
Return the area-weighted average normal of all primitives in the object.
Optional[ 'np.ndarray'] getPrimitiveTextureTransparencyData(self, int uuid)
Return the primitive's texture transparency mask as a 2D bool ndarray.
None aggregatePrimitiveDataSum(self, List[int] uuids, List[str] labels, str result_label)
Sum multiple primitive data fields into a new field.
List[str] listGlobalData(self)
List all global data labels.
int addConeObject(self, vec3 node0, vec3 node1, float radius0, float radius1, int ndivs=20, Optional[RGBcolor] color=None, Optional[str] texturefile=None)
Add a cone/cylinder/frustum as a compound object to the context.
List[PrimitiveInfo] getAllPrimitiveInfo(self)
Get physical properties and geometry information for all primitives in the context.
float getMaterialDataDouble(self, str material_label, str data_label)
None incrementPrimitiveData(self, List[int] uuids, str label, increment, str data_type=None)
Increment primitive data for the given UUIDs.
None copyObjectData(self, int source_objID, int destination_objID)
Copy all object data from source to destination compound object.
'Time' queryTimeseriesTime(self, str label, int index)
Get the Time associated with a timeseries data point.
None setPrimitiveDataInt3(self, uuids_or_uuid, str label, x_or_vec, int y=None, int z=None)
Set primitive data as int3 for one or multiple primitives.
None showPrimitive(self, uuids_or_uuid)
Show one or more previously hidden primitives.
None writePLY(self, str filename, Optional[List[int]] UUIDs=None)
Write geometry to a PLY (Stanford Polygon) file.
List[int] filterPrimitivesByData(self, List[int] uuids, str label, value, str comparator="=")
Filter primitives by data value.
List[PrimitiveInfo] getPrimitivesInfoForObject(self, int object_id)
Get physical properties and geometry information for all primitives belonging to a specific object.
vec3 getSphereObjectCenter(self, int objID)
None setPrimitiveDataInt2(self, uuids_or_uuid, str label, x_or_vec, int y=None)
Set primitive data as int2 for one or multiple primitives.
int getGlobalDataType(self, str label)
Get the HeliosDataType enum for global data.
print_plugin_status(self)
Print detailed plugin status information.
vec3 getTriangleVertex(self, int uuid, int number)
float getSphereObjectVolume(self, int objID)
List getUniquePrimitiveDataValues(self, str label, type dtype)
Return the unique values stored under label across all primitives.
setDate(self, int year, int month, int day)
Set the simulation date.
List[int] getDirtyUUIDs(self, bool include_deleted=True)
Return the list of UUIDs whose geometry has been modified since the last markGeometryClean call.
bool objectHasTexture(self, int objID)
Return True if the compound object has a texture assigned.
int2 getPrimitiveTextureSize(self, int uuid)
Get the texture size (width, height) of a primitive.
vec3 getSphereObjectRadius(self, int objID)
Get per-axis radii of a sphere object.
List[vec3] getTileObjectVertices(self, int objID)
int getMaterialIDFromLabel(self, str material_label)
Look up a material ID from its human-readable label.
getPrimitiveData(self, int uuid, str label, type data_type=None)
Get primitive data for a specific primitive.
None setGlobalDataUInt(self, str label, int value)
Set global data as unsigned 32-bit integer.
None setTileObjectSubdivisionByAreaRatio(self, objIDs_or_objID, float area_ratio)
Set tile object subdivision dynamically based on a target area ratio.
List[float] getConeObjectNodeRadii(self, int objID)
List[str] get_missing_plugins(self, List[str] requested_plugins)
Get list of requested plugins that are not available.
None setMaterialData(self, str material_label, str data_label, value)
Set material data with type detection from the Python value.
None showObject(self, objids_or_objid)
Show one or more previously hidden compound objects.
List[vec3] getConeObjectNodes(self, int objID)
None setObjectDataInt(self, objids_or_objid, str label, int value)
Set object data as signed 32-bit integer.
bool isGeometryDirty(self)
None printObjectInfo(self, int objID)
Print summary info for the object to stdout (for debugging).
clearTimeseriesData(self)
Clear all timeseries data from the Context.
vec3 getVoxelCenter(self, int uuid)
None copyPrimitiveData(self, int sourceUUID, int destinationUUID)
Copy all primitive data from source to destination primitive.
List[str] getAllPrimitiveMaterialLabels(self)
Get material labels for all primitives.
None duplicateGlobalData(self, str old_label, str new_label)
Duplicate global data to a new label.
vec4 getMaterialDataVec4(self, str material_label, str data_label)
None setMaterialDataVec4(self, str material_label, str data_label, vec4 value)
Set vec4 data on a material.
colorPrimitiveByDataPseudocolor(self, List[int] uuids, str primitive_data, str colormap="hot", int ncolors=10, Optional[float] max_val=None, Optional[float] min_val=None)
Color primitives based on primitive data values using pseudocolor mapping.
__del__(self)
Destructor to ensure C++ resources freed even without 'with' statement.
'np.ndarray' getPrimitiveTransformationMatrix(self, int uuid)
Return the primitive's 4x4 transformation matrix as a (4,4) float32 ndarray (row-major; see getObject...
List[int] addTile(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), Optional[SphericalCoord] rotation=None, int2 subdiv=int2(1, 1), Optional[RGBcolor] color=None)
Add a subdivided patch (tile) to the context.
vec3 getDiskObjectCenter(self, int objID)
None renamePrimitiveData(self, int uuid, str old_label, str new_label)
Rename a primitive-data label on a single primitive.
None incrementGlobalData(self, str label, increment)
Increment global data.
getObjectBoundingBox(self, objIDs)
Get axis-aligned bounding box for one object or a list of objects.
None setGlobalDataInt4(self, str label, x_or_vec, int y=None, int z=None, int w=None)
Set global data as int4.
None setObjectDataVec3(self, objids_or_objid, str label, x_or_vec, float y=None, float z=None)
Set object data as vec3.
_check_context_available(self)
Helper method to check if context is available with detailed error messages.
loadTabularTimeseriesData(self, str data_file, List[str] column_labels, str delimiter=",", str date_string_format="YYYYMMDD", int headerlines=0)
Load tabular timeseries data from a text file.
None markPrimitiveClean(self, uuids_or_uuid)
Mark one or more primitives as clean (cancels dirty state).
vec3 getConeObjectNode(self, int objID, int number)
int3 getMaterialDataInt3(self, str material_label, str data_label)
None clearGlobalData(self, str label)
Clear global data.
vec3 getConeObjectAxisUnitVector(self, int objID)
None scaleTubeGirth(self, int objID, float scale_factor)
Scale the radii of an existing tube object by scale_factor.
None setObjectDataVec2(self, objids_or_objid, str label, x_or_vec, float y=None)
Set object data as vec2.
int4 getMaterialDataInt4(self, str material_label, str data_label)
int addPatch(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), Optional[SphericalCoord] rotation=None, Optional[RGBcolor] color=None)
None setTubeRadii(self, int objID, List[float] radii)
Replace the per-node radii of an existing tube object.
addMaterial(self, str material_label)
Create a new material for sharing visual properties across primitives.
vec2 getTileObjectSize(self, int objID)
int getObjectType(self, int objID)
Return the integer-coded helios::ObjectType of a compound object.
None disableObjectDataValueCaching(self, str label)
Disable value caching for the given object-data label.
bool isMaterialTextureColorOverridden(self, str material_label)
Check if material texture color is overridden by material color.
List[int] loadOBJ(self, str filename, Optional[vec3] origin=None, Optional[float] height=None, Optional[vec3] scale=None, Optional[SphericalCoord] rotation=None, Optional[RGBcolor] color=None, str upaxis="YUP", bool silent=False)
Load geometry from an OBJ (Wavefront) file.
bool isPrimitiveDirty(self, int uuid)
Return True if the primitive's geometry has been modified since the last clean mark.
None setPrimitiveDataVec4(self, uuids_or_uuid, str label, x_or_vec, float y=None, float z=None, float w=None)
Set primitive data as vec4 for one or multiple primitives.
None setObjectDataInt2(self, objids_or_objid, str label, x_or_vec, int y=None)
Set object data as int2.
None setPrimitiveDataFloat(self, uuids_or_uuid, str label, float value)
Set primitive data as 32-bit float for one or multiple primitives.
int addDiskObject(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), Union[int, int2] ndivs=20, Optional[SphericalCoord] rotation=None, Optional[Union[RGBcolor, RGBAcolor]] color=None, Optional[str] texturefile=None)
Add a disk as a compound object to the context.
List[int] addTube(self, List[vec3] nodes, Union[float, List[float]] radii, int ndivs=6, Optional[Union[RGBcolor, List[RGBcolor]]] colors=None)
Add a tube (pipe/cylinder) to the context.
vec2 getDiskObjectSize(self, int objID)
List[int] addCone(self, vec3 node0, vec3 node1, float radius0, float radius1, int ndivs=20, Optional[RGBcolor] color=None)
Add a cone (or cylinder/frustum) to the context.
List[str] listAllPrimitiveDataLabels(self)
Return the union of all primitive-data labels used across every primitive in the context.
str _validate_file_path(self, str filename, List[str] expected_extensions=None)
Validate and normalize file path for security.
int getJulianDate(self)
Get the current simulation date as Julian day (1-366).
List[int] addDisk(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), Union[int, int2] ndivs=20, Optional[SphericalCoord] rotation=None, Optional[Union[RGBcolor, RGBAcolor]] color=None)
Add a disk (circular or elliptical surface) to the context.
None setGlobalDataFloat(self, str label, float value)
Set global data as 32-bit float.
'np.ndarray' getAllPrimitiveTypes(self)
Get types for all primitives.
float queryTimeseriesData(self, str label, 'Date' date=None, 'Time' time=None, int index=None)
Query a timeseries data value.
int getConeObjectSubdivisionCount(self, int objID)
getPrimitiveVertices(self, uuid)
Get vertices of a primitive or multiple primitives.
None appendTubeSegment(self, int objID, vec3 node_position, float radius, *, Optional[RGBcolor] color=None, Optional[str] texture_file=None, Optional[vec2] uv=None)
Append a new segment to an existing tube object.
None writeXML(self, str filename, Optional[List[int]] uuids=None, bool quiet=False)
Write the context (or a UUID subset) to an XML file.
None setPrimitiveElevation(self, int uuid, vec3 origin, float new_elevation)
Rotate a single primitive about the given origin so its elevation equals new_elevation (radians).
assignMaterialToPrimitive(self, uuid, str material_label)
Assign a material to primitive(s).
randu(self, low=None, high=None)
Draw a uniform random number using the Context's RNG.
None scalePrimitiveData(self, uuids_or_label, label_or_factor, factor=None)
Scale primitive data by a factor.
List[int] getUniquePrimitiveParentObjectIDs(self, List[int] uuids, bool include_zero=True)
Return the unique set of compound-object IDs that the given primitives belong to.
bool doesObjectDataExist(self, int objID, str label)
Check if object data exists.
None scaleObject(self, Union[int, List[int]] ObjID, vec3 scale, Optional[vec3] point=None, bool about_center=False, bool about_origin=False)
Scale one or more objects.
getMaterialData(self, str material_label, str data_label, type data_type=None)
Get material data, auto-detecting the type from Helios storage if not specified.
bool isPrimitiveTextureColorOverridden(self, int uuid)
Check if primitive texture color is overridden.
List[int] getDeletedUUIDs(self)
Return the list of UUIDs that have been deleted from the context.
None clearPrimitiveData(self, uuids, str label)
Remove a named data field from one primitive or a list of primitives.
float getMaterialDataFloat(self, str material_label, str data_label)
float sumPrimitiveSurfaceArea(self, List[int] uuids)
Calculate total one-sided surface area for a set of primitives.
int3 getBoxObjectSubdivisionCount(self, int objID)
vec2 getPatchSize(self, int uuid)
None disablePrimitiveDataValueCaching(self, str label)
Disable value caching for the given primitive-data label.
bool doesObjectExist(self, int objID)
Return True if a compound object with the given ID exists.
int getPatchCount(self, bool include_hidden=True)
seedRandomGenerator(self, int seed)
Seed the random number generator for reproducible stochastic results.
None deleteObject(self, Union[int, List[int]] objIDs_or_objID)
Delete one or more compound objects from the context.
None translatePrimitive(self, Union[int, List[int]] UUID, vec3 shift)
Translate one or more primitives by a shift vector.
None setGlobalDataInt3(self, str label, x_or_vec, int y=None, int z=None)
Set global data as int3.
Union[int, List[int]] copyPrimitive(self, Union[int, List[int]] UUID)
Copy one or more primitives.
None setMaterialDataFloat(self, str material_label, str data_label, float value)
Set float data on a material.
None printPrimitiveInfo(self, int uuid)
Print summary info for the primitive to stdout (for debugging).
setMaterialTwosidedFlag(self, str material_label, int twosided_flag)
Set the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided).
None setObjectDataString(self, objids_or_objid, str label, str value)
Set object data as string.
None setPrimitiveDataString(self, uuids_or_uuid, str label, str value)
Set primitive data as string for one or multiple primitives.
None setPrimitiveColor(self, uuids, color)
Set the RGB or RGBA color of one primitive or a list of primitives.
None setGlobalDataDouble(self, str label, float value)
Set global data as 64-bit double.
None renameMaterial(self, str old_label, str new_label)
Rename an existing material.
float getTubeObjectVolume(self, int objID)
None setMaterialDataInt(self, str material_label, str data_label, int value)
Set int data on a material.
vec2 getMaterialDataVec2(self, str material_label, str data_label)
List[vec3] getTubeObjectNodes(self, int objID)
None overrideObjectTextureColor(self, objIDs_or_objID)
Override the texture mapping with the object's vertex color.
vec3 getMaterialDataVec3(self, str material_label, str data_label)
None rotateObject(self, Union[int, List[int]] ObjID, float angle, Union[str, vec3] axis, Optional[vec3] origin=None, bool about_origin=False)
Rotate one or more objects.
None writePrimitiveData(self, str filename, List[str] column_labels, Optional[List[int]] UUIDs=None, bool print_header=False)
Write primitive data to an ASCII text file.
float getConeObjectLength(self, int objID)
None setMaterialDataDouble(self, str material_label, str data_label, float value)
Set double-precision float data on a material.
None rotatePrimitive(self, Union[int, List[int]] UUID, float angle, Union[str, vec3] axis, Optional[vec3] origin=None)
Rotate one or more primitives.
List[str] getLoadedXMLFiles(self)
Return the list of XML file paths that have been loaded into this context.
vec3 getBoxObjectCenter(self, int objID)
updateTimeseriesData(self, str label, 'Date' date, 'Time' time, float new_value)
Update the value of an existing timeseries data point.
calculatePrimitiveDataSum(self, List[int] uuids, str label, type return_type=float)
Calculate sum of primitive data across UUIDs.
getPrimitiveTextureUV(self, uuid)
Get the texture UV coordinates of a primitive or multiple primitives.
PrimitiveInfo getPrimitiveInfo(self, int uuid)
Get physical properties and geometry information for a single primitive.
int getMaterialDataType(self, str material_label, str data_label)
Return the HeliosDataType enum value for the given material data entry.
None setObjectDataInt3(self, objids_or_objid, str label, x_or_vec, int y=None, int z=None)
Set object data as int3.
int getPrimitiveDataSize(self, int uuid, str label)
Get the size/length of primitive data (for vector data).
int getGlobalDataSize(self, str label)
Get the size of global data array.
getPrimitiveBoundingBox(self, uuids)
Get axis-aligned bounding box for one primitive or a list of primitives.
int addBoxObject(self, vec3 center=vec3(0, 0, 0), vec3 size=vec3(1, 1, 1), int3 subdiv=int3(1, 1, 1), Optional[RGBcolor] color=None, Optional[str] texturefile=None, bool reverse_normals=False)
Add a rectangular box (prism) as a compound object to the context.
int addPolymeshObject(self, List[int] uuids)
Group the given primitives into a new polymesh compound object and return its ID.
None setGlobalDataString(self, str label, str value)
Set global data as string.
None cropDomainY(self, vec2 ybounds)
vec3 getObjectCenter(self, int objID)
vec3 getPatchCenter(self, int uuid)
int2 getTileObjectSubdivisionCount(self, int objID)
None hideObject(self, objids_or_objid)
Hide one or more compound objects (and all their primitives).
None setObjectDataFloat(self, objids_or_objid, str label, float value)
Set object data as 32-bit float.
int addTriangleTextured(self, vec3 vertex0, vec3 vertex1, vec3 vertex2, str texture_file, vec2 uv0, vec2 uv1, vec2 uv2)
Add a textured triangle primitive to the context.
getPrimitiveType(self, uuid)
Get the type of a primitive or multiple primitives.
int getPrimitiveDataType(self, int uuid, str label)
Get the Helios data type of primitive data.
calculatePrimitiveDataAreaWeightedSum(self, List[int] uuids, str label, type return_type=float)
Calculate area-weighted sum of primitive data.
bool doesObjectContainPrimitive(self, int objID, int uuid)
Return True if the given primitive UUID belongs to the given object.
str getObjectDataString(self, int objID, str label)
Get string object data.
None cropDomainZ(self, vec2 zbounds)
None setMaterialDataString(self, str material_label, str data_label, str value)
Set string data on a material.
None setPrimitiveDataUInt(self, uuids_or_uuid, str label, int value)
Set primitive data as unsigned 32-bit integer for one or multiple primitives.
int getTubeObjectSubdivisionCount(self, int objID)
int addTubeObject(self, int ndivs, List[vec3] nodes, List[float] radii, Optional[List[RGBcolor]] colors=None, Optional[str] texturefile=None, Optional[List[float]] texture_uv=None)
Add a tube as a compound object to the context.
float getObjectDataFloat(self, int objID, str label)
Get float object data.
calculatePrimitiveDataMean(self, List[int] uuids, str label, type return_type=float)
Calculate arithmetic mean of primitive data across UUIDs.
int getMaterialCount(self)
Return the total number of materials registered in the context.
None setPrimitiveAzimuth(self, int uuid, vec3 origin, float new_azimuth)
Rotate a single primitive about the given origin so its azimuth equals new_azimuth (radians).
Location getLocation(self)
Return the Context's currently-configured geographic location.
int getMaterialDataUInt(self, str material_label, str data_label)
int getObjectDataInt(self, int objID, str label)
Get int object data.
List[vec2] getTileObjectTextureUV(self, int objID)
None clearAllObjectData(self, str label)
Remove a named data field from every compound object in the Context.
List[int] loadPLY(self, str filename, Optional[vec3] origin=None, Optional[float] height=None, Optional[SphericalCoord] rotation=None, Optional[RGBcolor] color=None, str upaxis="YUP", bool silent=False)
Load geometry from a PLY (Stanford Polygon) file.
None writeOBJ(self, str filename, Optional[List[int]] UUIDs=None, Optional[List[str]] primitive_data_fields=None, bool write_normals=False, bool silent=False)
Write geometry to an OBJ (Wavefront) file.
str getMaterialDataString(self, str material_label, str data_label)
None renameObjectData(self, int objID, str old_label, str new_label)
Rename an object data label.
None overridePrimitiveTextureColor(self, uuids_or_uuid)
Override texture color with the primitive's constant RGB color.
None translateObject(self, Union[int, List[int]] ObjID, vec3 shift)
Translate one or more compound objects by a shift vector.
float getPrimitiveDataFloat(self, int uuid, str label)
Convenience method to get float primitive data.
int2 getMaterialDataInt2(self, str material_label, str data_label)
int getGlobalDataVersion(self, str label)
Return the version counter for a global data entry.
None setGlobalDataVec2(self, str label, x_or_vec, float y=None)
Set global data as vec2.
deleteMaterial(self, str material_label)
Delete a material from the context.
dict get_plugin_capabilities(self)
Get detailed information about available plugin capabilities.
'np.ndarray' getObjectTransformationMatrix(self, int objID)
Return the object's 4x4 transformation matrix as a (4,4) float32 ndarray.
vec3 getVoxelSize(self, int uuid)
getTime(self)
Get the current simulation time.
getPrimitiveNormal(self, uuid)
Get the normal vector of a primitive or multiple primitives.
int getObjectPrimitiveCount(self, int objID)
Return the number of primitives currently belonging to the object.
int addPatchTextured(self, vec3 center, vec2 size, str texture_file, Optional[SphericalCoord] rotation=None, Optional[vec2] uv_center=None, Optional[vec2] uv_size=None)
Add a textured patch primitive to the context.
None scalePrimitive(self, Union[int, List[int]] UUID, vec3 scale, Optional[vec3] point=None)
Scale one or more primitives.
List[int] filterObjectsByData(self, List[int] objIDs, str label, value, str comparator="=")
Filter objects by data value.
List[int] cleanDeletedObjectIDs(self, List[int] objIDs)
Return a new list with deleted object IDs removed; input is not mutated.
List[str] generateTexturesFromColormap(self, str texture_file, List[RGBcolor] colormap)
Generate one texture file per color in colormap derived from texture_file.
List[int] getPrimitivesUsingMaterial(self, str material_label)
Get all primitive UUIDs that use a specific material.
float getPolymeshObjectVolume(self, int objID)
Return the enclosed volume of a polymesh object.
setDateJulian(self, int julian_day, int year)
Set the simulation date using Julian day number.
List[float] getTubeObjectNodeRadii(self, int objID)
None clearObjectData(self, objids_or_objid, str label)
Clear object data.
int getTriangleCount(self, bool include_hidden=True)
float getConeObjectNodeRadius(self, int objID, int number)
None pruneTubeNodes(self, int objID, int node_index)
Remove all tube nodes from index node_index to the end.
deleteTimeseriesDataPoint(self, 'Date' date, 'Time' time, Optional[str] label=None)
Delete a single timeseries data point at the given date and time.
int getObjectDataType(self, int objID, str label)
Get the HeliosDataType enum for object data.
None setObjectDataInt4(self, objids_or_objid, str label, x_or_vec, int y=None, int z=None, int w=None)
Set object data as int4.
setMaterialTextureColorOverride(self, str material_label, bool override)
Set whether material color overrides texture color.
int addTriangle(self, vec3 vertex0, vec3 vertex1, vec3 vertex2, Optional[RGBcolor] color=None)
Add a triangle primitive to the context.
getDate(self)
Get the current simulation date.
None useObjectTextureColor(self, objIDs_or_objID)
Restore use of the texture color (undoes overrideObjectTextureColor).
None setPrimitiveTransformationMatrix(self, uuids_or_uuid, T)
Set the 4x4 transformation matrix on one or more primitives.
'np.ndarray' getAllPrimitiveSolidFractions(self)
Get solid fractions for all primitives.
List getUniqueObjectDataValues(self, str label, type dtype)
Return the unique values stored under label across all compound objects.
getPrimitiveTextureFile(self, uuid)
Get the texture file path of a primitive or multiple primitives.
None setGlobalDataInt2(self, str label, x_or_vec, int y=None)
Set global data as int2.
List[int] addTrianglesFromArraysTextured(self, np.ndarray vertices, np.ndarray faces, np.ndarray uv_coords, Union[str, List[str]] texture_files, Optional[np.ndarray] material_ids=None)
Add textured triangles from NumPy arrays with support for multiple textures.
str getObjectTextureFile(self, int objID)
Return the filesystem path of the texture assigned to the object, or an empty string if no texture is...
None usePrimitiveTextureColor(self, uuids_or_uuid)
Use texture-map color instead of the constant RGB color.
float getBoxObjectVolume(self, int objID)
bool doesPrimitiveExist(self, uuid)
Check if a primitive exists for a given UUID or list of UUIDs.
float getGlobalDataFloat(self, str label)
Get float global data.
bool areObjectPrimitivesComplete(self, int objID)
Return True if all primitives originally belonging to this object still exist (i.e....
None setGlobalDataVec4(self, str label, x_or_vec, float y=None, float z=None, float w=None)
Set global data as vec4.
List[int] loadXML(self, str filename, bool quiet=False)
Load geometry from a Helios XML file.
float randn(self, mean=None, stddev=None)
Draw a normal random number using the Context's RNG.
None setGlobalDataInt(self, str label, int value)
Set global data as signed 32-bit integer.
setMaterialColor(self, str material_label, color)
Set the RGBA color of a material.
None setPrimitiveTextureFile(self, int uuid, str texture_file)
Set the texture file path of a primitive.
None aggregatePrimitiveDataProduct(self, List[int] uuids, List[str] labels, str result_label)
Multiply multiple primitive data fields into a new field.
bool doesMaterialExist(self, str material_label)
Check if a material with the given label exists.
None scaleConeObjectLength(self, int ObjID, float scale_factor)
Scale the length of a Cone object by scaling the distance between its two nodes.
None setObjectDataDouble(self, objids_or_objid, str label, float value)
Set object data as 64-bit double.
List[int] addSphere(self, vec3 center=vec3(0, 0, 0), float radius=1.0, int ndivs=10, Optional[RGBcolor] color=None)
Add a sphere to the context.
int getDiskObjectSubdivisionCount(self, int objID)
List[int] addTrianglesFromArrays(self, np.ndarray vertices, np.ndarray faces, Optional[np.ndarray] colors=None)
Add triangles from NumPy arrays (compatible with trimesh, Open3D format).
setTime(self, int hour, int minute=0, int second=0)
Set the simulation time.
getPrimitiveSolidFraction(self, uuid)
Get the solid fraction of a primitive or multiple primitives.
packGPUBuffers(self, uuids)
Pack GPU-ready geometry buffers for a set of primitives in a single C++ pass.
None setMaterialDataUInt(self, str material_label, str data_label, int value)
Set unsigned int data on a material.
None setObjectTransformationMatrix(self, objIDs_or_objID, T)
Set the 4x4 transformation matrix on one or more compound objects.
float getObjectArea(self, int objID)
Return the total surface area (one-sided) of all primitives in the object.
assignMaterialToObject(self, objID, str material_label)
Assign a material to all primitives in compound object(s).
int getGlobalDataInt(self, str label)
Get int global data.
None setObjectDataFromPrimitiveDataMean(self, int objID, str label)
Compute the mean of the given primitive-data label across the object's primitives and store it as obj...
'Date' queryTimeseriesDate(self, str label, int index)
Get the Date associated with a timeseries data point.
'np.ndarray' getAllPrimitiveAreas(self)
Get areas for all primitives.
List[float] _marshal_mat4(value)
Coerce a 4x4 transformation matrix input into a flat list of 16 floats.
None setLocation(self, location_or_lat, longitude=None, utc_offset=None, altitude=0.0)
Set the geographic location used by solar/radiation calculations.
'np.ndarray' getAllPrimitiveNormals(self)
Get normals for all primitives.
bool isObjectHidden(self, int objID)
Check if a compound object is hidden.
resolveMaterialTextures(self, uuids, colors_np)
Resolve material texture suppression for export.
None renameGlobalData(self, str old_label, str new_label)
Rename a global data label.
bool isObjectDataValueCachingEnabled(self, str label)
Return True if value caching is enabled for the given object-data label.
List[str] listTimeseriesVariables(self)
List all existing timeseries variables.
List[str] listPrimitiveData(self, int uuid)
List all data labels attached to a primitive.
None setPrimitiveDataVec3(self, uuids_or_uuid, str label, x_or_vec, float y=None, float z=None)
Set primitive data as vec3 for one or multiple primitives.
List[RGBcolor] generateColormap(self, str name, int n_colors)
Generate a colormap with n_colors entries from a named colormap.
bool doesGlobalDataExist(self, str label)
Check if global data exists.
int getMaterialDataInt(self, str material_label, str data_label)
vec3 getTileObjectCenter(self, int objID)
setMaterialTexture(self, str material_label, str texture_file)
Set the texture file for a material.
int getSphereObjectSubdivisionCount(self, int objID)
Physical properties and geometry information for a primitive.
__post_init__(self)
Calculate centroid from vertices if not provided.
Helios Date structure for representing date values.
Geographic location for solar position and radiation calculations.
Helios primitive type enumeration.
Helios Time structure for representing time values.