3from dataclasses
import dataclass
4from typing
import List, Optional, Union
9from .wrappers
import UContextWrapper
as context_wrapper
10from .wrappers.DataTypes
import vec2, vec3, vec4, int2, int3, int4, SphericalCoord, RGBcolor, RGBAcolor, PrimitiveType, Date, Time, Location
11from .plugins.loader
import LibraryLoadError, validate_library, get_library_info
12from .plugins.registry
import get_plugin_registry
13from .validation.geometry
import (
14 validate_patch_params, validate_triangle_params, validate_sphere_params,
15 validate_tube_params, validate_box_params
22 Physical properties and geometry information for a primitive.
23 This is separate from primitive data (user-defined key-value pairs).
26 primitive_type: PrimitiveType
31 centroid: Optional[vec3] =
None
32 texture_file: Optional[str] =
None
33 texture_uv: Optional[List[vec2]] =
None
34 solid_fraction: Optional[float] =
None
37 """Calculate centroid from vertices if not provided."""
40 total_x = sum(v.x
for v
in self.
vertices)
41 total_y = sum(v.y
for v
in self.
vertices)
42 total_z = sum(v.z
for v
in self.
vertices)
44 self.
centroid =
vec3(total_x / count, total_y / count, total_z / count)
49 Central simulation environment for PyHelios that manages 3D primitives and their data.
51 The Context class provides methods for:
52 - Creating geometric primitives (patches, triangles)
53 - Creating compound geometry (tiles, spheres, tubes, boxes)
54 - Loading 3D models from files (PLY, OBJ, XML)
55 - Managing primitive data (flexible key-value storage)
56 - Querying primitive properties and collections
57 - Batch operations on multiple primitives
60 - UUID-based primitive tracking
61 - Comprehensive primitive data system with auto-type detection
62 - Efficient array-based data retrieval via getPrimitiveDataArray()
63 - Cross-platform compatibility with mock mode support
64 - Context manager protocol for resource cleanup
67 >>> with Context() as context:
68 ... # Create primitives
69 ... patch_uuid = context.addPatch(center=vec3(0, 0, 0))
70 ... triangle_uuid = context.addTriangle(vec3(0,0,0), vec3(1,0,0), vec3(0.5,1,0))
72 ... # Set primitive data
73 ... context.setPrimitiveDataFloat(patch_uuid, "temperature", 25.5)
74 ... context.setPrimitiveDataFloat(triangle_uuid, "temperature", 30.2)
76 ... # Get data efficiently as NumPy array
77 ... temps = context.getPrimitiveDataArray([patch_uuid, triangle_uuid], "temperature")
78 ... print(temps) # [25.5 30.2]
89 library_info = get_library_info()
90 if library_info.get(
'is_mock',
False):
92 print(
"Warning: PyHelios running in development mock mode - functionality is limited")
93 print(
"Available plugins: None (mock mode)")
100 if not validate_library():
101 raise LibraryLoadError(
102 "Native Helios library validation failed. Some required functions are missing. "
103 "Try rebuilding the native library: build_scripts/build_helios"
105 except LibraryLoadError:
107 except Exception
as e:
108 raise LibraryLoadError(
109 f
"Failed to validate native Helios library: {e}. "
110 f
"To enable development mode without native libraries, set PYHELIOS_DEV_MODE=1"
115 self.
context = context_wrapper.createContext()
118 raise LibraryLoadError(
119 "Failed to create Helios context. Native library may not be functioning correctly."
124 except Exception
as e:
126 raise LibraryLoadError(
127 f
"Failed to create Helios context: {e}. "
128 f
"Ensure native libraries are built and accessible."
132 """Helper method to check if context is available with detailed error messages."""
137 "Context is in mock mode - native functionality not available.\n"
138 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
142 "Context has been cleaned up and is no longer usable.\n"
143 "This usually means you're trying to use a Context outside its 'with' statement scope.\n"
145 "Fix: Ensure all Context usage is inside the 'with Context() as context:' block:\n"
146 " with Context() as context:\n"
147 " # All context operations must be here\n"
148 " with SomePlugin(context) as plugin:\n"
149 " plugin.do_something()\n"
150 " with Visualizer() as vis:\n"
151 " vis.buildContextGeometry(context) # Still inside Context scope\n"
152 " # Context is cleaned up here - cannot use context after this point"
156 "Context creation failed - native functionality not available.\n"
157 "Build native libraries with 'python build_scripts/build_helios.py'"
162 f
"Context is not available (state: {self._lifecycle_state}).\n"
163 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
167 """Validate that a UUID exists in this context.
170 uuid: The UUID to validate
173 RuntimeError: If UUID is invalid or doesn't exist in context
176 if not isinstance(uuid, int)
or uuid < 0:
177 raise RuntimeError(f
"Invalid UUID: {uuid}. UUIDs must be non-negative integers.")
182 if uuid
not in valid_uuids:
183 raise RuntimeError(f
"UUID {uuid} does not exist in context. Valid UUIDs: {valid_uuids[:10]}{'...' if len(valid_uuids) > 10 else ''}")
193 def _validate_file_path(self, filename: str, expected_extensions: List[str] =
None) -> str:
194 """Validate and normalize file path for security.
197 filename: File path to validate
198 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
201 Normalized absolute path
205 ValueError: If path is invalid or potentially dangerous
206 FileNotFoundError: If file does not exist
211 abs_path = os.path.abspath(filename)
215 normalized_path = os.path.normpath(abs_path)
216 if abs_path != normalized_path:
217 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
220 if expected_extensions:
221 file_ext = os.path.splitext(abs_path)[1].lower()
222 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
223 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
226 if not os.path.exists(abs_path):
227 raise FileNotFoundError(f
"File not found: {abs_path}")
230 if not os.path.isfile(abs_path):
231 raise ValueError(f
"Path is not a file: {abs_path}")
236 """Validate and normalize output file path for security.
239 filename: Output file path to validate
240 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
243 Normalized absolute path
246 ValueError: If path is invalid or potentially dangerous
247 PermissionError: If output directory is not writable
252 if not filename
or not filename.strip():
253 raise ValueError(
"Filename cannot be empty")
256 abs_path = os.path.abspath(filename)
259 normalized_path = os.path.normpath(abs_path)
260 if abs_path != normalized_path:
261 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
264 if expected_extensions:
265 file_ext = os.path.splitext(abs_path)[1].lower()
266 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
267 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
270 output_dir = os.path.dirname(abs_path)
271 if not os.path.exists(output_dir):
272 raise ValueError(f
"Output directory does not exist: {output_dir}")
273 if not os.access(output_dir, os.W_OK):
274 raise PermissionError(f
"Output directory is not writable: {output_dir}")
281 def __exit__(self, exc_type, exc_value, traceback):
283 context_wrapper.destroyContext(self.
context)
288 """Destructor to ensure C++ resources freed even without 'with' statement."""
289 if hasattr(self,
'context')
and self.
context is not None:
291 context_wrapper.destroyContext(self.
context)
294 except Exception
as e:
302 warnings.warn(f
"Error in Context.__del__: {e}")
303 except BaseException:
312 context_wrapper.markGeometryClean(self.
context)
316 context_wrapper.markGeometryDirty(self.
context)
321 return context_wrapper.isGeometryDirty(self.
context)
325 Seed the random number generator for reproducible stochastic results.
328 seed: Integer seed value for random number generation
331 This is critical for reproducible results in stochastic simulations
332 (e.g., LiDAR scans with beam divergence, random perturbations).
335 context_wrapper.helios_lib.seedRandomGenerator(self.
context, seed)
337 @validate_patch_params
338 def addPatch(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1), rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None) -> int:
343 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
344 return context_wrapper.addPatchWithCenterSizeRotationAndColor(self.
context, center.to_list(), size.to_list(), rotation_list, color.to_list())
347 rotation: Optional[SphericalCoord] =
None,
348 uv_center: Optional[vec2] =
None,
349 uv_size: Optional[vec2] =
None) -> int:
350 """Add a textured patch primitive to the context.
352 Creates a rectangular patch with a texture image mapped to its surface.
355 center: 3D position of the patch center
356 size: Width and height of the patch
357 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
358 rotation: Optional spherical rotation (defaults to no rotation)
359 uv_center: Optional UV center of texture map (required if uv_size is provided)
360 uv_size: Optional UV size of texture map (required if uv_center is provided)
363 UUID of the created textured patch primitive
366 ValueError: If arguments have wrong types or UV params are partially specified
367 FileNotFoundError: If texture file doesn't exist
368 RuntimeError: If context is in mock mode
371 >>> context = Context()
372 >>> uuid = context.addPatchTextured(
373 ... center=vec3(0, 0, 0),
375 ... texture_file="texture.png"
380 if not isinstance(center, vec3):
381 raise ValueError(f
"center must be a vec3, got {type(center).__name__}")
382 if not isinstance(size, vec2):
383 raise ValueError(f
"size must be a vec2, got {type(size).__name__}")
384 if not isinstance(texture_file, str):
385 raise ValueError(f
"texture_file must be a str, got {type(texture_file).__name__}")
386 if rotation
is not None and not isinstance(rotation, SphericalCoord):
387 raise ValueError(f
"rotation must be a SphericalCoord, got {type(rotation).__name__}")
389 if (uv_center
is None) != (uv_size
is None):
390 raise ValueError(
"uv_center and uv_size must both be provided or both omitted")
391 if uv_center
is not None and not isinstance(uv_center, vec2):
392 raise ValueError(f
"uv_center must be a vec2, got {type(uv_center).__name__}")
393 if uv_size
is not None and not isinstance(uv_size, vec2):
394 raise ValueError(f
"uv_size must be a vec2, got {type(uv_size).__name__}")
397 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
400 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
402 if uv_center
is not None:
403 return context_wrapper.addPatchWithTextureAndUV(
404 self.
context, center.to_list(), size.to_list(), rotation_list,
405 validated_texture_file, uv_center.to_list(), uv_size.to_list()
408 return context_wrapper.addPatchWithTexture(
409 self.
context, center.to_list(), size.to_list(), rotation_list,
410 validated_texture_file
413 @validate_triangle_params
414 def addTriangle(self, vertex0: vec3, vertex1: vec3, vertex2: vec3, color: Optional[RGBcolor] =
None) -> int:
415 """Add a triangle primitive to the context
418 vertex0: First vertex of the triangle
419 vertex1: Second vertex of the triangle
420 vertex2: Third vertex of the triangle
421 color: Optional triangle color (defaults to white)
424 UUID of the created triangle primitive
428 return context_wrapper.addTriangle(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list())
430 return context_wrapper.addTriangleWithColor(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list(), color.to_list())
433 texture_file: str, uv0: vec2, uv1: vec2, uv2: vec2) -> int:
434 """Add a textured triangle primitive to the context
436 Creates a triangle with texture mapping. The texture image is mapped to the triangle
437 surface using UV coordinates, where (0,0) represents the top-left corner of the image
438 and (1,1) represents the bottom-right corner.
441 vertex0: First vertex of the triangle
442 vertex1: Second vertex of the triangle
443 vertex2: Third vertex of the triangle
444 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
445 uv0: UV texture coordinates for first vertex
446 uv1: UV texture coordinates for second vertex
447 uv2: UV texture coordinates for third vertex
450 UUID of the created textured triangle primitive
453 ValueError: If texture file path is invalid
454 FileNotFoundError: If texture file doesn't exist
455 RuntimeError: If context is in mock mode
458 >>> context = Context()
459 >>> # Create a textured triangle
460 >>> vertex0 = vec3(0, 0, 0)
461 >>> vertex1 = vec3(1, 0, 0)
462 >>> vertex2 = vec3(0.5, 1, 0)
463 >>> uv0 = vec2(0, 0) # Bottom-left of texture
464 >>> uv1 = vec2(1, 0) # Bottom-right of texture
465 >>> uv2 = vec2(0.5, 1) # Top-center of texture
466 >>> uuid = context.addTriangleTextured(vertex0, vertex1, vertex2,
467 ... "texture.png", uv0, uv1, uv2)
472 for name, val
in [(
"vertex0", vertex0), (
"vertex1", vertex1), (
"vertex2", vertex2)]:
473 if not isinstance(val, vec3):
474 raise ValueError(f
"{name} must be a vec3, got {type(val).__name__}")
475 for name, val
in [(
"uv0", uv0), (
"uv1", uv1), (
"uv2", uv2)]:
476 if not isinstance(val, vec2):
477 raise ValueError(f
"{name} must be a vec2, got {type(val).__name__}")
481 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
484 return context_wrapper.addTriangleWithTexture(
486 vertex0.to_list(), vertex1.to_list(), vertex2.to_list(),
487 validated_texture_file,
488 uv0.to_list(), uv1.to_list(), uv2.to_list()
492 """Get the type of a primitive or multiple primitives.
495 uuid: Single UUID (int) or list of UUIDs
498 PrimitiveType for single UUID, or np.ndarray of shape (N,) uint32 for list
501 if isinstance(uuid, (list, tuple)):
503 return np.empty((0,), dtype=np.uint32)
504 ptr, size = context_wrapper.getBatchPrimitiveTypes(self.
context, uuid)
505 if size == 0
or not ptr:
506 return np.empty((0,), dtype=np.uint32)
507 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
508 primitive_type = context_wrapper.getPrimitiveType(self.
context, uuid)
512 """Get the area of a primitive or multiple primitives.
515 uuid: Single UUID (int) or list of UUIDs
518 float for single UUID, or np.ndarray of shape (N,) for list
521 if isinstance(uuid, (list, tuple)):
523 return np.empty((0,), dtype=np.float32)
524 ptr, size = context_wrapper.getBatchPrimitiveAreas(self.
context, uuid)
525 if size == 0
or not ptr:
526 return np.empty((0,), dtype=np.float32)
527 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
528 return context_wrapper.getPrimitiveArea(self.
context, uuid)
531 """Get the normal vector of a primitive or multiple primitives.
534 uuid: Single UUID (int) or list of UUIDs
537 vec3 for single UUID, or np.ndarray of shape (N, 3) for list
540 if isinstance(uuid, (list, tuple)):
542 return np.empty((0, 3), dtype=np.float32)
543 ptr, size = context_wrapper.getBatchPrimitiveNormals(self.
context, uuid)
544 if size == 0
or not ptr:
545 return np.empty((0, 3), dtype=np.float32)
546 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
547 normal_ptr = context_wrapper.getPrimitiveNormal(self.
context, uuid)
548 return vec3(normal_ptr[0], normal_ptr[1], normal_ptr[2])
551 """Get vertices of a primitive or multiple primitives.
554 uuid: Single UUID (int) or list of UUIDs
557 List[vec3] for single UUID, or tuple of (flat_data, offsets) for list
558 where flat_data is a float32 ndarray and offsets is a uint32 ndarray
559 of length N+1. Vertices for primitive i are at
560 flat_data[offsets[i]:offsets[i+1]].
563 if isinstance(uuid, (list, tuple)):
565 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
566 ptr, offsets, total = context_wrapper.getBatchPrimitiveVertices(self.
context, uuid)
567 offsets_arr = np.array(offsets, dtype=np.uint32)
568 if total == 0
or not ptr:
569 return (np.empty((0,), dtype=np.float32), offsets_arr)
570 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
571 return (data, offsets_arr)
572 size = ctypes.c_uint()
573 vertices_ptr = context_wrapper.getPrimitiveVertices(self.
context, uuid, ctypes.byref(size))
575 vertices_list = ctypes.cast(vertices_ptr, ctypes.POINTER(ctypes.c_float * size.value)).contents
576 vertices = [
vec3(vertices_list[i], vertices_list[i+1], vertices_list[i+2])
for i
in range(0, size.value, 3)]
580 """Get the color of a primitive or multiple primitives.
583 uuid: Single UUID (int) or list of UUIDs
586 RGBcolor for single UUID, or np.ndarray of shape (N, 3) for list
589 if isinstance(uuid, (list, tuple)):
591 return np.empty((0, 3), dtype=np.float32)
592 ptr, size = context_wrapper.getBatchPrimitiveColors(self.
context, uuid)
593 if size == 0
or not ptr:
594 return np.empty((0, 3), dtype=np.float32)
595 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
596 color_ptr = context_wrapper.getPrimitiveColor(self.
context, uuid)
597 return RGBcolor(color_ptr[0], color_ptr[1], color_ptr[2])
601 return context_wrapper.getPrimitiveCount(self.
context)
604 """Check if a primitive exists for a given UUID or list of UUIDs.
607 uuid: A single UUID (int) or a list of UUIDs.
610 True if the primitive(s) exist, False otherwise.
611 For a list, returns True only if ALL primitives exist.
614 if isinstance(uuid, (list, tuple)):
615 arr = (ctypes.c_uint * len(uuid))(*uuid)
616 return context_wrapper.doesPrimitiveExistBatch(self.
context, arr, len(uuid))
617 return context_wrapper.doesPrimitiveExist(self.
context, uuid)
621 size = ctypes.c_uint()
622 uuids_ptr = context_wrapper.getAllUUIDs(self.
context, ctypes.byref(size))
623 return list(uuids_ptr[:size.value])
627 return context_wrapper.getObjectCount(self.
context)
631 size = ctypes.c_uint()
632 objectids_ptr = context_wrapper.getAllObjectIDs(self.
context, ctypes.byref(size))
633 return list(objectids_ptr[:size.value])
637 Get physical properties and geometry information for a single primitive.
640 uuid: UUID of the primitive
643 PrimitiveInfo object containing physical properties and geometry
658 solid_fraction =
None
663 except NotImplementedError:
669 except NotImplementedError:
673 except NotImplementedError:
674 solid_fraction =
None
678 primitive_type=primitive_type,
683 texture_file=texture_file,
684 texture_uv=texture_uv,
685 solid_fraction=solid_fraction,
690 Get physical properties and geometry information for all primitives in the context.
693 List of PrimitiveInfo objects for all primitives
700 Get physical properties and geometry information for all primitives belonging to a specific object.
703 object_id: ID of the object
706 List of PrimitiveInfo objects for primitives in the object
708 object_uuids = context_wrapper.getObjectPrimitiveUUIDs(self.
context, object_id)
712 def addTile(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
713 rotation: Optional[SphericalCoord] =
None, subdiv: int2 =
int2(1, 1),
714 color: Optional[RGBcolor] =
None) -> List[int]:
716 Add a subdivided patch (tile) to the context.
718 A tile is a patch subdivided into a regular grid of smaller patches,
719 useful for creating detailed surfaces or terrain.
722 center: 3D coordinates of tile center (default: origin)
723 size: Width and height of the tile (default: 1x1)
724 rotation: Orientation of the tile (default: no rotation)
725 subdiv: Number of subdivisions in x and y directions (default: 1x1)
726 color: Color of the tile (default: white)
729 List of UUIDs for all patches created in the tile
732 >>> context = Context()
733 >>> # Create a 2x2 meter tile subdivided into 4x4 patches
734 >>> tile_uuids = context.addTile(
735 ... center=vec3(0, 0, 1),
737 ... subdiv=int2(4, 4),
738 ... color=RGBcolor(0.5, 0.8, 0.2)
740 >>> print(f"Created {len(tile_uuids)} patches")
745 if not isinstance(center, vec3):
746 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
747 if not isinstance(size, vec2):
748 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
749 if rotation
is not None and not isinstance(rotation, SphericalCoord):
750 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
751 if not isinstance(subdiv, int2):
752 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
753 if color
is not None and not isinstance(color, RGBcolor):
754 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
757 if any(s <= 0
for s
in size.to_list()):
758 raise ValueError(
"All size dimensions must be positive")
759 if any(s <= 0
for s
in subdiv.to_list()):
760 raise ValueError(
"All subdivision counts must be positive")
766 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
768 if color
and not (color.r == 1.0
and color.g == 1.0
and color.b == 1.0):
769 return context_wrapper.addTileWithColor(
770 self.
context, center.to_list(), size.to_list(),
771 rotation_list, subdiv.to_list(), color.to_list()
774 return context_wrapper.addTile(
775 self.
context, center.to_list(), size.to_list(),
776 rotation_list, subdiv.to_list()
779 @validate_sphere_params
780 def addSphere(self, center: vec3 =
vec3(0, 0, 0), radius: float = 1.0,
781 ndivs: int = 10, color: Optional[RGBcolor] =
None) -> List[int]:
783 Add a sphere to the context.
785 The sphere is tessellated into triangular faces based on the specified
789 center: 3D coordinates of sphere center (default: origin)
790 radius: Radius of the sphere (default: 1.0)
791 ndivs: Number of divisions for tessellation (default: 10)
792 Higher values create smoother spheres but more triangles
793 color: Color of the sphere (default: white)
796 List of UUIDs for all triangles created in the sphere
799 >>> context = Context()
800 >>> # Create a red sphere at (1, 2, 3) with radius 0.5
801 >>> sphere_uuids = context.addSphere(
802 ... center=vec3(1, 2, 3),
805 ... color=RGBcolor(1, 0, 0)
807 >>> print(f"Created sphere with {len(sphere_uuids)} triangles")
812 if not isinstance(center, vec3):
813 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
814 if not isinstance(radius, (int, float)):
815 raise ValueError(f
"Radius must be a number, got {type(radius).__name__}")
816 if not isinstance(ndivs, int):
817 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
818 if color
is not None and not isinstance(color, RGBcolor):
819 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
823 raise ValueError(
"Sphere radius must be positive")
825 raise ValueError(
"Number of divisions must be at least 3")
828 return context_wrapper.addSphereWithColor(
829 self.
context, ndivs, center.to_list(), radius, color.to_list()
832 return context_wrapper.addSphere(
833 self.
context, ndivs, center.to_list(), radius
836 @validate_tube_params
837 def addTube(self, nodes: List[vec3], radii: Union[float, List[float]],
838 ndivs: int = 6, colors: Optional[Union[RGBcolor, List[RGBcolor]]] =
None) -> List[int]:
840 Add a tube (pipe/cylinder) to the context.
842 The tube is defined by a series of nodes (path) with radius at each node.
843 It's tessellated into triangular faces based on the number of radial divisions.
846 nodes: List of 3D points defining the tube path (at least 2 nodes)
847 radii: Radius at each node. Can be:
848 - Single float: constant radius for all nodes
849 - List of floats: radius for each node (must match nodes length)
850 ndivs: Number of radial divisions (default: 6)
851 Higher values create smoother tubes but more triangles
852 colors: Colors at each node. Can be:
854 - Single RGBcolor: constant color for all nodes
855 - List of RGBcolor: color for each node (must match nodes length)
858 List of UUIDs for all triangles created in the tube
861 >>> context = Context()
862 >>> # Create a curved tube with varying radius
863 >>> nodes = [vec3(0, 0, 0), vec3(1, 0, 0), vec3(2, 1, 0)]
864 >>> radii = [0.1, 0.2, 0.1]
865 >>> colors = [RGBcolor(1, 0, 0), RGBcolor(0, 1, 0), RGBcolor(0, 0, 1)]
866 >>> tube_uuids = context.addTube(nodes, radii, ndivs=8, colors=colors)
867 >>> print(f"Created tube with {len(tube_uuids)} triangles")
872 if not isinstance(nodes, (list, tuple)):
873 raise ValueError(f
"Nodes must be a list or tuple, got {type(nodes).__name__}")
874 if not isinstance(ndivs, int):
875 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
876 if colors
is not None and not isinstance(colors, (RGBcolor, list, tuple)):
877 raise ValueError(f
"Colors must be RGBcolor, list, tuple, or None, got {type(colors).__name__}")
881 raise ValueError(
"Tube requires at least 2 nodes")
883 raise ValueError(
"Number of radial divisions must be at least 3")
886 if isinstance(radii, (int, float)):
887 radii_list = [float(radii)] * len(nodes)
889 radii_list = [float(r)
for r
in radii]
890 if len(radii_list) != len(nodes):
891 raise ValueError(f
"Number of radii ({len(radii_list)}) must match number of nodes ({len(nodes)})")
894 if any(r <= 0
for r
in radii_list):
895 raise ValueError(
"All radii must be positive")
900 nodes_flat.extend(node.to_list())
904 return context_wrapper.addTube(self.
context, ndivs, nodes_flat, radii_list)
905 elif isinstance(colors, RGBcolor):
907 colors_flat = colors.to_list() * len(nodes)
910 if len(colors) != len(nodes):
911 raise ValueError(f
"Number of colors ({len(colors)}) must match number of nodes ({len(nodes)})")
914 colors_flat.extend(color.to_list())
916 return context_wrapper.addTubeWithColor(self.
context, ndivs, nodes_flat, radii_list, colors_flat)
919 def addBox(self, center: vec3 =
vec3(0, 0, 0), size: vec3 =
vec3(1, 1, 1),
920 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None) -> List[int]:
922 Add a rectangular box to the context.
924 The box is subdivided into patches on each face based on the specified
928 center: 3D coordinates of box center (default: origin)
929 size: Width, height, and depth of the box (default: 1x1x1)
930 subdiv: Number of subdivisions in x, y, and z directions (default: 1x1x1)
931 Higher values create more detailed surfaces
932 color: Color of the box (default: white)
935 List of UUIDs for all patches created on the box faces
938 >>> context = Context()
939 >>> # Create a blue box subdivided for detail
940 >>> box_uuids = context.addBox(
941 ... center=vec3(0, 0, 2),
942 ... size=vec3(2, 1, 0.5),
943 ... subdiv=int3(4, 2, 1),
944 ... color=RGBcolor(0, 0, 1)
946 >>> print(f"Created box with {len(box_uuids)} patches")
951 if not isinstance(center, vec3):
952 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
953 if not isinstance(size, vec3):
954 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
955 if not isinstance(subdiv, int3):
956 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
957 if color
is not None and not isinstance(color, RGBcolor):
958 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
961 if any(s <= 0
for s
in size.to_list()):
962 raise ValueError(
"All box dimensions must be positive")
963 if any(s < 1
for s
in subdiv.to_list()):
964 raise ValueError(
"All subdivision counts must be at least 1")
967 return context_wrapper.addBoxWithColor(
968 self.
context, center.to_list(), size.to_list(),
969 subdiv.to_list(), color.to_list()
972 return context_wrapper.addBox(
973 self.
context, center.to_list(), size.to_list(), subdiv.to_list()
976 def addDisk(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
977 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
978 color: Optional[Union[RGBcolor, RGBAcolor]] =
None) -> List[int]:
980 Add a disk (circular or elliptical surface) to the context.
982 A disk is a flat circular or elliptical surface tessellated into
983 triangular faces. Supports both uniform radial subdivisions and
984 separate radial/azimuthal subdivisions for finer control.
987 center: 3D coordinates of disk center (default: origin)
988 size: Semi-major and semi-minor radii of the disk (default: 1x1 circle)
989 ndivs: Number of radial divisions (int) or [radial, azimuthal] divisions (int2)
990 (default: 20). Higher values create smoother circles but more triangles.
991 rotation: Orientation of the disk (default: horizontal, normal = +z)
992 color: Color of the disk (default: white). Can be RGBcolor or RGBAcolor for transparency.
995 List of UUIDs for all triangles created in the disk
998 >>> context = Context()
999 >>> # Create a red disk at (0, 0, 1) with radius 0.5
1000 >>> disk_uuids = context.addDisk(
1001 ... center=vec3(0, 0, 1),
1002 ... size=vec2(0.5, 0.5),
1004 ... color=RGBcolor(1, 0, 0)
1006 >>> print(f"Created disk with {len(disk_uuids)} triangles")
1008 >>> # Create a semi-transparent blue elliptical disk
1009 >>> disk_uuids = context.addDisk(
1010 ... center=vec3(0, 0, 2),
1011 ... size=vec2(1.0, 0.5),
1013 ... rotation=SphericalCoord(1, 0.5, 0),
1014 ... color=RGBAcolor(0, 0, 1, 0.5)
1017 >>> # Create disk with polar/radial subdivisions for finer control
1018 >>> disk_uuids = context.addDisk(
1019 ... center=vec3(0, 0, 3),
1020 ... size=vec2(1, 1),
1021 ... ndivs=int2(10, 20), # 10 radial, 20 azimuthal divisions
1022 ... color=RGBcolor(0, 1, 0)
1028 if not isinstance(center, vec3):
1029 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1030 if not isinstance(size, vec2):
1031 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1032 if not isinstance(ndivs, (int, int2)):
1033 raise ValueError(f
"Ndivs must be an int or int2, got {type(ndivs).__name__}")
1034 if rotation
is not None and not isinstance(rotation, SphericalCoord):
1035 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
1036 if color
is not None and not isinstance(color, (RGBcolor, RGBAcolor)):
1037 raise ValueError(f
"Color must be an RGBcolor, RGBAcolor, or None, got {type(color).__name__}")
1040 if any(s <= 0
for s
in size.to_list()):
1041 raise ValueError(
"Disk size must be positive")
1044 if isinstance(ndivs, int):
1046 raise ValueError(
"Number of divisions must be at least 3")
1048 if any(n < 1
for n
in ndivs.to_list()):
1049 raise ValueError(
"Radial and angular divisions must be at least 1")
1052 if rotation
is None:
1057 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1060 if isinstance(ndivs, int2):
1063 if isinstance(color, RGBAcolor):
1064 return context_wrapper.addDiskPolarSubdivisionsRGBA(
1065 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1066 rotation_list, color.to_list()
1070 return context_wrapper.addDiskPolarSubdivisions(
1071 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1072 rotation_list, color.to_list()
1076 color_list = [1.0, 1.0, 1.0]
1077 return context_wrapper.addDiskPolarSubdivisions(
1078 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1079 rotation_list, color_list
1084 if isinstance(color, RGBAcolor):
1086 return context_wrapper.addDiskWithRGBAColor(
1087 self.
context, ndivs, center.to_list(), size.to_list(),
1088 rotation_list, color.to_list()
1092 return context_wrapper.addDiskWithColor(
1093 self.
context, ndivs, center.to_list(), size.to_list(),
1094 rotation_list, color.to_list()
1098 return context_wrapper.addDiskWithRotation(
1099 self.
context, ndivs, center.to_list(), size.to_list(),
1103 def addCone(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1104 ndivs: int = 20, color: Optional[RGBcolor] =
None) -> List[int]:
1106 Add a cone (or cylinder/frustum) to the context.
1108 A cone is a 3D shape connecting two circular cross-sections with
1109 potentially different radii. When radii are equal, creates a cylinder.
1110 When one radius is zero, creates a true cone.
1113 node0: 3D coordinates of the base center
1114 node1: 3D coordinates of the apex center
1115 radius0: Radius at base (node0). Use 0 for pointed end.
1116 radius1: Radius at apex (node1). Use 0 for pointed end.
1117 ndivs: Number of radial divisions for tessellation (default: 20)
1118 color: Color of the cone (default: white)
1121 List of UUIDs for all triangles created in the cone
1124 >>> context = Context()
1125 >>> # Create a cylinder (equal radii)
1126 >>> cylinder_uuids = context.addCone(
1127 ... node0=vec3(0, 0, 0),
1128 ... node1=vec3(0, 0, 2),
1134 >>> # Create a true cone (one radius = 0)
1135 >>> cone_uuids = context.addCone(
1136 ... node0=vec3(1, 0, 0),
1137 ... node1=vec3(1, 0, 1.5),
1141 ... color=RGBcolor(1, 0, 0)
1144 >>> # Create a frustum (different radii)
1145 >>> frustum_uuids = context.addCone(
1146 ... node0=vec3(2, 0, 0),
1147 ... node1=vec3(2, 0, 1),
1156 if not isinstance(node0, vec3):
1157 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1158 if not isinstance(node1, vec3):
1159 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1160 if not isinstance(ndivs, int):
1161 raise ValueError(f
"ndivs must be an int, got {type(ndivs).__name__}")
1162 if color
is not None and not isinstance(color, RGBcolor):
1163 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1166 if radius0 < 0
or radius1 < 0:
1167 raise ValueError(
"Radii must be non-negative")
1169 raise ValueError(
"Number of radial divisions must be at least 3")
1173 return context_wrapper.addConeWithColor(
1174 self.
context, ndivs, node0.to_list(), node1.to_list(),
1175 radius0, radius1, color.to_list()
1178 return context_wrapper.addCone(
1179 self.
context, ndivs, node0.to_list(), node1.to_list(),
1184 radius: Union[float, vec3] = 1.0, ndivs: int = 20,
1185 color: Optional[RGBcolor] =
None,
1186 texturefile: Optional[str] =
None) -> int:
1188 Add a spherical or ellipsoidal compound object to the context.
1190 Creates a sphere or ellipsoid as a compound object with a trackable object ID.
1191 Primitives within the object are registered as children of the object.
1194 center: Center position of sphere/ellipsoid (default: origin)
1195 radius: Radius as float (sphere) or vec3 (ellipsoid) (default: 1.0)
1196 ndivs: Number of tessellation divisions (default: 20)
1197 color: Optional RGB color
1198 texturefile: Optional texture image file path
1201 Object ID of the created compound object
1204 ValueError: If parameters are invalid
1205 NotImplementedError: If object-returning functions unavailable
1208 >>> # Create a basic sphere at origin
1209 >>> obj_id = ctx.addSphereObject()
1211 >>> # Create a colored sphere
1212 >>> obj_id = ctx.addSphereObject(
1213 ... center=vec3(0, 0, 5),
1215 ... color=RGBcolor(1, 0, 0)
1218 >>> # Create an ellipsoid (stretched sphere)
1219 >>> obj_id = ctx.addSphereObject(
1220 ... center=vec3(10, 0, 0),
1221 ... radius=vec3(2, 1, 1), # Elongated in x-direction
1228 if not isinstance(center, vec3):
1229 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1230 if not isinstance(radius, (int, float, vec3)):
1231 raise ValueError(f
"Radius must be a number or vec3, got {type(radius).__name__}")
1232 if color
is not None and not isinstance(color, RGBcolor):
1233 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1237 raise ValueError(
"Number of divisions must be at least 3")
1240 is_ellipsoid = isinstance(radius, vec3)
1246 return context_wrapper.addSphereObject_ellipsoid_texture(
1247 self.
context, ndivs, center.to_list(), radius.to_list(), texturefile
1250 return context_wrapper.addSphereObject_ellipsoid_color(
1251 self.
context, ndivs, center.to_list(), radius.to_list(), color.to_list()
1254 return context_wrapper.addSphereObject_ellipsoid(
1255 self.
context, ndivs, center.to_list(), radius.to_list()
1260 return context_wrapper.addSphereObject_texture(
1261 self.
context, ndivs, center.to_list(), radius, texturefile
1264 return context_wrapper.addSphereObject_color(
1265 self.
context, ndivs, center.to_list(), radius, color.to_list()
1268 return context_wrapper.addSphereObject_basic(
1269 self.
context, ndivs, center.to_list(), radius
1274 subdiv: int2 =
int2(1, 1),
1275 color: Optional[RGBcolor] =
None,
1276 texturefile: Optional[str] =
None,
1277 texture_repeat: Optional[int2] =
None) -> int:
1279 Add a tiled patch (subdivided patch) as a compound object to the context.
1281 Creates a rectangular patch subdivided into a grid of smaller patches,
1282 registered as a compound object with a trackable object ID.
1285 center: Center position of tile (default: origin)
1286 size: Size in x and y directions (default: 1x1)
1287 rotation: Spherical rotation (default: no rotation)
1288 subdiv: Number of subdivisions in x and y (default: 1x1)
1289 color: Optional RGB color
1290 texturefile: Optional texture image file path
1291 texture_repeat: Optional texture repetitions in x and y
1294 Object ID of the created compound object
1297 ValueError: If parameters are invalid
1298 NotImplementedError: If object-returning functions unavailable
1301 >>> # Create a basic 2x2 tile
1302 >>> obj_id = ctx.addTileObject(
1303 ... center=vec3(0, 0, 0),
1304 ... size=vec2(10, 10),
1305 ... subdiv=int2(2, 2)
1308 >>> # Create a colored tile with rotation
1309 >>> obj_id = ctx.addTileObject(
1310 ... center=vec3(5, 0, 0),
1311 ... size=vec2(10, 5),
1312 ... rotation=SphericalCoord(1, 0, 45),
1313 ... subdiv=int2(4, 2),
1314 ... color=RGBcolor(0, 1, 0)
1320 if not isinstance(center, vec3):
1321 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1322 if not isinstance(size, vec2):
1323 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1324 if not isinstance(rotation, SphericalCoord):
1325 raise ValueError(f
"Rotation must be a SphericalCoord, got {type(rotation).__name__}")
1326 if not isinstance(subdiv, int2):
1327 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
1328 if color
is not None and not isinstance(color, RGBcolor):
1329 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1330 if texture_repeat
is not None and not isinstance(texture_repeat, int2):
1331 raise ValueError(f
"texture_repeat must be an int2 or None, got {type(texture_repeat).__name__}")
1334 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1337 if texture_repeat
is not None:
1338 if texturefile
is None:
1339 raise ValueError(
"texture_repeat requires texturefile")
1340 return context_wrapper.addTileObject_texture_repeat(
1341 self.
context, center.to_list(), size.to_list(), rotation_list,
1342 subdiv.to_list(), texturefile, texture_repeat.to_list()
1345 return context_wrapper.addTileObject_texture(
1346 self.
context, center.to_list(), size.to_list(), rotation_list,
1347 subdiv.to_list(), texturefile
1350 return context_wrapper.addTileObject_color(
1351 self.
context, center.to_list(), size.to_list(), rotation_list,
1352 subdiv.to_list(), color.to_list()
1355 return context_wrapper.addTileObject_basic(
1356 self.
context, center.to_list(), size.to_list(), rotation_list,
1361 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None,
1362 texturefile: Optional[str] =
None, reverse_normals: bool =
False) -> int:
1364 Add a rectangular box (prism) as a compound object to the context.
1367 center: Center position (default: origin)
1368 size: Size in x, y, z directions (default: 1x1x1)
1369 subdiv: Subdivisions in x, y, z (default: 1x1x1)
1370 color: Optional RGB color
1371 texturefile: Optional texture file path
1372 reverse_normals: Reverse normal directions (default: False)
1375 Object ID of the created compound object
1380 if not isinstance(center, vec3):
1381 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1382 if not isinstance(size, vec3):
1383 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
1384 if not isinstance(subdiv, int3):
1385 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
1386 if color
is not None and not isinstance(color, RGBcolor):
1387 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1391 return context_wrapper.addBoxObject_texture_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile, reverse_normals)
1393 return context_wrapper.addBoxObject_color_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list(), reverse_normals)
1395 raise ValueError(
"reverse_normals requires either color or texturefile")
1397 return context_wrapper.addBoxObject_texture(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile)
1399 return context_wrapper.addBoxObject_color(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list())
1401 return context_wrapper.addBoxObject_basic(self.
context, center.to_list(), size.to_list(), subdiv.to_list())
1403 def addConeObject(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1404 ndivs: int = 20, color: Optional[RGBcolor] =
None,
1405 texturefile: Optional[str] =
None) -> int:
1407 Add a cone/cylinder/frustum as a compound object to the context.
1410 node0: Base position
1412 radius0: Radius at base
1413 radius1: Radius at top
1414 ndivs: Number of radial divisions (default: 20)
1415 color: Optional RGB color
1416 texturefile: Optional texture file path
1419 Object ID of the created compound object
1424 if not isinstance(node0, vec3):
1425 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1426 if not isinstance(node1, vec3):
1427 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1428 if not isinstance(radius0, (int, float)):
1429 raise ValueError(f
"radius0 must be a number, got {type(radius0).__name__}")
1430 if not isinstance(radius1, (int, float)):
1431 raise ValueError(f
"radius1 must be a number, got {type(radius1).__name__}")
1432 if color
is not None and not isinstance(color, RGBcolor):
1433 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1436 return context_wrapper.addConeObject_texture(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, texturefile)
1438 return context_wrapper.addConeObject_color(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, color.to_list())
1440 return context_wrapper.addConeObject_basic(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1)
1443 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
1444 color: Optional[Union[RGBcolor, RGBAcolor]] =
None,
1445 texturefile: Optional[str] =
None) -> int:
1447 Add a disk as a compound object to the context.
1450 center: Center position (default: origin)
1451 size: Semi-major and semi-minor radii (default: 1x1)
1452 ndivs: int (uniform) or int2 (polar/radial subdivisions) (default: 20)
1453 rotation: Optional spherical rotation
1454 color: Optional RGB or RGBA color
1455 texturefile: Optional texture file path
1458 Object ID of the created compound object
1462 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
if rotation
else [1, 0, 0]
1463 is_polar = isinstance(ndivs, int2)
1467 return context_wrapper.addDiskObject_polar_texture(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, texturefile)
1469 if isinstance(color, RGBAcolor):
1470 return context_wrapper.addDiskObject_polar_rgba(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1472 return context_wrapper.addDiskObject_polar_color(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1474 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())
1477 return context_wrapper.addDiskObject_texture(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, texturefile)
1479 if isinstance(color, RGBAcolor):
1480 return context_wrapper.addDiskObject_rgba(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1482 return context_wrapper.addDiskObject_color(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1484 return context_wrapper.addDiskObject_rotation(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list)
1486 return context_wrapper.addDiskObject_basic(self.
context, ndivs, center.to_list(), size.to_list())
1488 def addTubeObject(self, ndivs: int, nodes: List[vec3], radii: List[float],
1489 colors: Optional[List[RGBcolor]] =
None,
1490 texturefile: Optional[str] =
None,
1491 texture_uv: Optional[List[float]] =
None) -> int:
1493 Add a tube as a compound object to the context.
1496 ndivs: Number of radial subdivisions
1497 nodes: List of vec3 positions defining tube segments
1498 radii: List of radii at each node
1499 colors: Optional list of RGB colors for each segment
1500 texturefile: Optional texture file path
1501 texture_uv: Optional UV coordinates for texture mapping
1504 Object ID of the created compound object
1509 if not isinstance(nodes, (list, tuple)):
1510 raise ValueError(f
"Nodes must be a list, got {type(nodes).__name__}")
1511 for i, node
in enumerate(nodes):
1512 if not isinstance(node, vec3):
1513 raise ValueError(f
"nodes[{i}] must be a vec3, got {type(node).__name__}")
1514 if not isinstance(radii, (list, tuple)):
1515 raise ValueError(f
"Radii must be a list, got {type(radii).__name__}")
1516 if colors
is not None:
1517 if not isinstance(colors, (list, tuple)):
1518 raise ValueError(f
"Colors must be a list or None, got {type(colors).__name__}")
1519 for i, c
in enumerate(colors):
1520 if not isinstance(c, RGBcolor):
1521 raise ValueError(f
"colors[{i}] must be an RGBcolor, got {type(c).__name__}")
1524 raise ValueError(
"Tube requires at least 2 nodes")
1525 if len(radii) != len(nodes):
1526 raise ValueError(
"Number of radii must match number of nodes")
1528 nodes_flat = [coord
for node
in nodes
for coord
in node.to_list()]
1530 if texture_uv
is not None:
1531 if texturefile
is None:
1532 raise ValueError(
"texture_uv requires texturefile")
1533 return context_wrapper.addTubeObject_texture_uv(self.
context, ndivs, nodes_flat, radii, texturefile, texture_uv)
1535 return context_wrapper.addTubeObject_texture(self.
context, ndivs, nodes_flat, radii, texturefile)
1537 if len(colors) != len(nodes):
1538 raise ValueError(
"Number of colors must match number of nodes")
1539 colors_flat = [c
for color
in colors
for c
in color.to_list()]
1540 return context_wrapper.addTubeObject_color(self.
context, ndivs, nodes_flat, radii, colors_flat)
1542 return context_wrapper.addTubeObject_basic(self.
context, ndivs, nodes_flat, radii)
1544 def copyPrimitive(self, UUID: Union[int, List[int]]) -> Union[int, List[int]]:
1546 Copy one or more primitives.
1548 Creates a duplicate of the specified primitive(s) with all associated data.
1549 The copy is placed at the same location as the original.
1552 UUID: Single primitive UUID or list of UUIDs to copy
1555 Single UUID of copied primitive (if UUID is int) or
1556 List of UUIDs of copied primitives (if UUID is list)
1559 >>> context = Context()
1560 >>> original_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1561 >>> # Copy single primitive
1562 >>> copied_uuid = context.copyPrimitive(original_uuid)
1563 >>> # Copy multiple primitives
1564 >>> copied_uuids = context.copyPrimitive([uuid1, uuid2, uuid3])
1568 if isinstance(UUID, int):
1569 return context_wrapper.copyPrimitive(self.
context, UUID)
1570 elif isinstance(UUID, list):
1571 return context_wrapper.copyPrimitives(self.
context, UUID)
1573 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1577 Copy all primitive data from source to destination primitive.
1579 Copies all associated data (primitive data fields) from the source
1580 primitive to the destination primitive. Both primitives must already exist.
1583 sourceUUID: UUID of the source primitive
1584 destinationUUID: UUID of the destination primitive
1587 >>> context = Context()
1588 >>> source_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1589 >>> dest_uuid = context.addPatch(center=vec3(1, 0, 0), size=vec2(1, 1))
1590 >>> context.setPrimitiveData(source_uuid, "temperature", 25.5)
1591 >>> context.copyPrimitiveData(source_uuid, dest_uuid)
1592 >>> # dest_uuid now has temperature data
1596 if not isinstance(sourceUUID, int):
1597 raise ValueError(f
"sourceUUID must be int, got {type(sourceUUID).__name__}")
1598 if not isinstance(destinationUUID, int):
1599 raise ValueError(f
"destinationUUID must be int, got {type(destinationUUID).__name__}")
1601 context_wrapper.copyPrimitiveData(self.
context, sourceUUID, destinationUUID)
1603 def copyObject(self, ObjID: Union[int, List[int]]) -> Union[int, List[int]]:
1605 Copy one or more compound objects.
1607 Creates a duplicate of the specified compound object(s) with all
1608 associated primitives and data. The copy is placed at the same location
1612 ObjID: Single object ID or list of object IDs to copy
1615 Single object ID of copied object (if ObjID is int) or
1616 List of object IDs of copied objects (if ObjID is list)
1619 >>> context = Context()
1620 >>> original_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1621 >>> # Copy single object
1622 >>> copied_obj = context.copyObject(original_obj)
1623 >>> # Copy multiple objects
1624 >>> copied_objs = context.copyObject([obj1, obj2, obj3])
1628 if isinstance(ObjID, int):
1629 return context_wrapper.copyObject(self.
context, ObjID)
1630 elif isinstance(ObjID, list):
1631 return context_wrapper.copyObjects(self.
context, ObjID)
1633 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1635 def copyObjectData(self, source_objID: int, destination_objID: int) ->
None:
1637 Copy all object data from source to destination compound object.
1639 Copies all associated data (object data fields) from the source
1640 compound object to the destination object. Both objects must already exist.
1643 source_objID: Object ID of the source compound object
1644 destination_objID: Object ID of the destination compound object
1647 >>> context = Context()
1648 >>> source_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1649 >>> dest_obj = context.addTile(center=vec3(2, 0, 0), size=vec2(2, 2))
1650 >>> context.setObjectData(source_obj, "material", "wood")
1651 >>> context.copyObjectData(source_obj, dest_obj)
1652 >>> # dest_obj now has material data
1656 if not isinstance(source_objID, int):
1657 raise ValueError(f
"source_objID must be int, got {type(source_objID).__name__}")
1658 if not isinstance(destination_objID, int):
1659 raise ValueError(f
"destination_objID must be int, got {type(destination_objID).__name__}")
1661 context_wrapper.copyObjectData(self.
context, source_objID, destination_objID)
1665 Translate one or more primitives by a shift vector.
1667 Moves the specified primitive(s) by the given shift vector without
1668 changing their orientation or size.
1671 UUID: Single primitive UUID or list of UUIDs to translate
1672 shift: 3D vector representing the translation [x, y, z]
1675 >>> context = Context()
1676 >>> patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1677 >>> # Translate single primitive
1678 >>> context.translatePrimitive(patch_uuid, vec3(1, 0, 0)) # Move 1 unit in x
1679 >>> # Translate multiple primitives
1680 >>> context.translatePrimitive([uuid1, uuid2, uuid3], vec3(0, 0, 1)) # Move 1 unit in z
1685 if not isinstance(shift, vec3):
1686 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1688 if isinstance(UUID, int):
1689 context_wrapper.translatePrimitive(self.
context, UUID, shift.to_list())
1690 elif isinstance(UUID, list):
1691 context_wrapper.translatePrimitives(self.
context, UUID, shift.to_list())
1693 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1695 def translateObject(self, ObjID: Union[int, List[int]], shift: vec3) ->
None:
1697 Translate one or more compound objects by a shift vector.
1699 Moves the specified compound object(s) and all their constituent
1700 primitives by the given shift vector without changing orientation or size.
1703 ObjID: Single object ID or list of object IDs to translate
1704 shift: 3D vector representing the translation [x, y, z]
1707 >>> context = Context()
1708 >>> tile_uuids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1709 >>> obj_id = context.getPrimitiveParentObjectID(tile_uuids[0]) # Get object ID
1710 >>> # Translate single object
1711 >>> context.translateObject(obj_id, vec3(5, 0, 0)) # Move 5 units in x
1712 >>> # Translate multiple objects
1713 >>> context.translateObject([obj1, obj2, obj3], vec3(0, 2, 0)) # Move 2 units in y
1718 if not isinstance(shift, vec3):
1719 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1721 if isinstance(ObjID, int):
1722 context_wrapper.translateObject(self.
context, ObjID, shift.to_list())
1723 elif isinstance(ObjID, list):
1724 context_wrapper.translateObjects(self.
context, ObjID, shift.to_list())
1726 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1729 axis: Union[str, vec3], origin: Optional[vec3] =
None) ->
None:
1731 Rotate one or more primitives.
1734 UUID: Single UUID or list of UUIDs to rotate
1735 angle: Rotation angle in radians
1736 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1737 origin: Optional rotation origin point. If None, rotates about primitive center.
1738 If provided with string axis, raises ValueError.
1741 ValueError: If axis is invalid or if origin is provided with string axis
1746 if isinstance(axis, str):
1747 if axis
not in (
'x',
'y',
'z'):
1748 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1749 if origin
is not None:
1750 raise ValueError(
"origin parameter cannot be used with string axis")
1753 if isinstance(UUID, int):
1754 context_wrapper.rotatePrimitive_axisString(self.
context, UUID, angle, axis)
1755 elif isinstance(UUID, list):
1756 context_wrapper.rotatePrimitives_axisString(self.
context, UUID, angle, axis)
1758 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1760 elif isinstance(axis, vec3):
1761 axis_list = axis.to_list()
1764 if all(abs(v) < 1e-10
for v
in axis_list):
1765 raise ValueError(
"axis vector cannot be zero")
1769 if isinstance(UUID, int):
1770 context_wrapper.rotatePrimitive_axisVector(self.
context, UUID, angle, axis_list)
1771 elif isinstance(UUID, list):
1772 context_wrapper.rotatePrimitives_axisVector(self.
context, UUID, angle, axis_list)
1774 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1777 if not isinstance(origin, vec3):
1778 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1780 origin_list = origin.to_list()
1781 if isinstance(UUID, int):
1782 context_wrapper.rotatePrimitive_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1783 elif isinstance(UUID, list):
1784 context_wrapper.rotatePrimitives_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1786 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1788 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1790 def rotateObject(self, ObjID: Union[int, List[int]], angle: float,
1791 axis: Union[str, vec3], origin: Optional[vec3] =
None,
1792 about_origin: bool =
False) ->
None:
1794 Rotate one or more objects.
1797 ObjID: Single object ID or list of object IDs to rotate
1798 angle: Rotation angle in radians
1799 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1800 origin: Optional rotation origin point. If None, rotates about object center.
1801 If provided with string axis, raises ValueError.
1802 about_origin: If True, rotate about the object's own stored origin point
1803 (``object_origin``), which for most objects is its construction center —
1804 NOT the global origin (0,0,0). An object built away from the world origin
1805 therefore spins in place rather than orbiting the world origin. To orbit a
1806 specific point, pass that point as ``origin`` instead. Cannot be used with
1807 the origin parameter.
1810 ValueError: If axis is invalid or if origin and about_origin are both specified
1815 if origin
is not None and about_origin:
1816 raise ValueError(
"Cannot specify both origin and about_origin")
1819 if isinstance(axis, str):
1820 if axis
not in (
'x',
'y',
'z'):
1821 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1822 if origin
is not None:
1823 raise ValueError(
"origin parameter cannot be used with string axis")
1825 raise ValueError(
"about_origin parameter cannot be used with string axis")
1828 if isinstance(ObjID, int):
1829 context_wrapper.rotateObject_axisString(self.
context, ObjID, angle, axis)
1830 elif isinstance(ObjID, list):
1831 context_wrapper.rotateObjects_axisString(self.
context, ObjID, angle, axis)
1833 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1835 elif isinstance(axis, vec3):
1836 axis_list = axis.to_list()
1839 if all(abs(v) < 1e-10
for v
in axis_list):
1840 raise ValueError(
"axis vector cannot be zero")
1844 if isinstance(ObjID, int):
1845 context_wrapper.rotateObjectAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1846 elif isinstance(ObjID, list):
1847 context_wrapper.rotateObjectsAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1849 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1850 elif origin
is None:
1852 if isinstance(ObjID, int):
1853 context_wrapper.rotateObject_axisVector(self.
context, ObjID, angle, axis_list)
1854 elif isinstance(ObjID, list):
1855 context_wrapper.rotateObjects_axisVector(self.
context, ObjID, angle, axis_list)
1857 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1860 if not isinstance(origin, vec3):
1861 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1863 origin_list = origin.to_list()
1864 if isinstance(ObjID, int):
1865 context_wrapper.rotateObject_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1866 elif isinstance(ObjID, list):
1867 context_wrapper.rotateObjects_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1869 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1871 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1873 def scalePrimitive(self, UUID: Union[int, List[int]], scale: vec3, point: Optional[vec3] =
None) ->
None:
1875 Scale one or more primitives.
1878 UUID: Single UUID or list of UUIDs to scale
1879 scale: Scale factors as vec3(x, y, z)
1880 point: Optional point to scale about. If None, scales about primitive center.
1883 ValueError: If scale or point parameters are invalid
1887 if not isinstance(scale, vec3):
1888 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1890 scale_list = scale.to_list()
1894 if isinstance(UUID, int):
1895 context_wrapper.scalePrimitive(self.
context, UUID, scale_list)
1896 elif isinstance(UUID, list):
1897 context_wrapper.scalePrimitives(self.
context, UUID, scale_list)
1899 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1902 if not isinstance(point, vec3):
1903 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1905 point_list = point.to_list()
1906 if isinstance(UUID, int):
1907 context_wrapper.scalePrimitiveAboutPoint(self.
context, UUID, scale_list, point_list)
1908 elif isinstance(UUID, list):
1909 context_wrapper.scalePrimitivesAboutPoint(self.
context, UUID, scale_list, point_list)
1911 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1913 def scaleObject(self, ObjID: Union[int, List[int]], scale: vec3,
1914 point: Optional[vec3] =
None, about_center: bool =
False,
1915 about_origin: bool =
False) ->
None:
1917 Scale one or more objects.
1920 ObjID: Single object ID or list of object IDs to scale
1921 scale: Scale factors as vec3(x, y, z)
1922 point: Optional point to scale about
1923 about_center: If True, scale about object center (default behavior)
1924 about_origin: If True, scale about the object's own stored origin point
1925 (``object_origin``), not the global origin (0,0,0). Pass ``point`` to
1926 scale about a specific location instead.
1929 ValueError: If parameters are invalid or conflicting options specified
1934 options_count = sum([point
is not None, about_center, about_origin])
1935 if options_count > 1:
1936 raise ValueError(
"Cannot specify multiple scaling options (point, about_center, about_origin)")
1938 if not isinstance(scale, vec3):
1939 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1941 scale_list = scale.to_list()
1945 if isinstance(ObjID, int):
1946 context_wrapper.scaleObjectAboutOrigin(self.
context, ObjID, scale_list)
1947 elif isinstance(ObjID, list):
1948 context_wrapper.scaleObjectsAboutOrigin(self.
context, ObjID, scale_list)
1950 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1953 if isinstance(ObjID, int):
1954 context_wrapper.scaleObjectAboutCenter(self.
context, ObjID, scale_list)
1955 elif isinstance(ObjID, list):
1956 context_wrapper.scaleObjectsAboutCenter(self.
context, ObjID, scale_list)
1958 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1959 elif point
is not None:
1961 if not isinstance(point, vec3):
1962 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1964 point_list = point.to_list()
1965 if isinstance(ObjID, int):
1966 context_wrapper.scaleObjectAboutPoint(self.
context, ObjID, scale_list, point_list)
1967 elif isinstance(ObjID, list):
1968 context_wrapper.scaleObjectsAboutPoint(self.
context, ObjID, scale_list, point_list)
1970 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1973 if isinstance(ObjID, int):
1974 context_wrapper.scaleObject(self.
context, ObjID, scale_list)
1975 elif isinstance(ObjID, list):
1976 context_wrapper.scaleObjects(self.
context, ObjID, scale_list)
1978 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1982 Scale the length of a Cone object by scaling the distance between its two nodes.
1985 ObjID: Object ID of the Cone to scale
1986 scale_factor: Factor by which to scale the cone length (e.g., 2.0 doubles length)
1989 ValueError: If ObjID is not an integer or scale_factor is invalid
1990 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
1993 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
1994 method, enforcing better encapsulation.
1997 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
1998 >>> context.scaleConeObjectLength(cone_id, 1.5) # Make cone 50% longer
2000 if not isinstance(ObjID, int):
2001 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
2002 if not isinstance(scale_factor, (int, float)):
2003 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
2004 if scale_factor <= 0:
2005 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
2007 context_wrapper.scaleConeObjectLength(self.
context, ObjID, float(scale_factor))
2011 Scale the girth of a Cone object by scaling the radii at both nodes.
2014 ObjID: Object ID of the Cone to scale
2015 scale_factor: Factor by which to scale the cone girth (e.g., 2.0 doubles girth)
2018 ValueError: If ObjID is not an integer or scale_factor is invalid
2019 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
2022 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
2023 method, enforcing better encapsulation.
2026 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
2027 >>> context.scaleConeObjectGirth(cone_id, 2.0) # Double the cone girth
2029 if not isinstance(ObjID, int):
2030 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
2031 if not isinstance(scale_factor, (int, float)):
2032 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
2033 if scale_factor <= 0:
2034 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
2036 context_wrapper.scaleConeObjectGirth(self.
context, ObjID, float(scale_factor))
2038 def loadPLY(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2039 rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None,
2040 upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2042 Load geometry from a PLY (Stanford Polygon) file.
2045 filename: Path to the PLY file to load
2046 origin: Origin point for positioning the geometry (optional)
2047 height: Height scaling factor (optional)
2048 rotation: Rotation to apply to the geometry (optional)
2049 color: Default color for geometry without color data (optional)
2050 upaxis: Up axis orientation ("YUP" or "ZUP")
2051 silent: If True, suppress loading output messages
2054 List of UUIDs for the loaded primitives
2059 if origin
is not None and not isinstance(origin, vec3):
2060 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2061 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2062 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2063 if color
is not None and not isinstance(color, RGBcolor):
2064 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2069 if origin
is None and height
is None and rotation
is None and color
is None:
2071 return context_wrapper.loadPLY(self.
context, validated_filename, silent)
2073 elif origin
is not None and height
is not None and rotation
is None and color
is None:
2075 return context_wrapper.loadPLYWithOriginHeight(self.
context, validated_filename, origin.to_list(), height, upaxis, silent)
2077 elif origin
is not None and height
is not None and rotation
is not None and color
is None:
2079 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2080 return context_wrapper.loadPLYWithOriginHeightRotation(self.
context, validated_filename, origin.to_list(), height, rotation_list, upaxis, silent)
2082 elif origin
is not None and height
is not None and rotation
is None and color
is not None:
2084 return context_wrapper.loadPLYWithOriginHeightColor(self.
context, validated_filename, origin.to_list(), height, color.to_list(), upaxis, silent)
2086 elif origin
is not None and height
is not None and rotation
is not None and color
is not None:
2088 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2089 return context_wrapper.loadPLYWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation_list, color.to_list(), upaxis, silent)
2092 raise ValueError(
"Invalid parameter combination. When using transformations, both origin and height are required.")
2094 def loadOBJ(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2095 scale: Optional[vec3] =
None, rotation: Optional[SphericalCoord] =
None,
2096 color: Optional[RGBcolor] =
None, upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2098 Load geometry from an OBJ (Wavefront) file.
2101 filename: Path to the OBJ file to load
2102 origin: Origin point for positioning the geometry (optional)
2103 height: Height scaling factor (optional, alternative to scale)
2104 scale: Scale factor for all dimensions (optional, alternative to height)
2105 rotation: Rotation to apply to the geometry (optional)
2106 color: Default color for geometry without color data (optional)
2107 upaxis: Up axis orientation ("YUP" or "ZUP")
2108 silent: If True, suppress loading output messages
2111 List of UUIDs for the loaded primitives
2116 if origin
is not None and not isinstance(origin, vec3):
2117 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2118 if scale
is not None and not isinstance(scale, vec3):
2119 raise ValueError(f
"Scale must be a vec3 or None, got {type(scale).__name__}")
2120 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2121 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2122 if color
is not None and not isinstance(color, RGBcolor):
2123 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2128 if origin
is None and height
is None and scale
is None and rotation
is None and color
is None:
2130 return context_wrapper.loadOBJ(self.
context, validated_filename, silent)
2132 elif origin
is not None and height
is not None and scale
is None and rotation
is not None and color
is not None:
2134 return context_wrapper.loadOBJWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), silent)
2136 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":
2138 return context_wrapper.loadOBJWithOriginHeightRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), upaxis, silent)
2140 elif origin
is not None and scale
is not None and rotation
is not None and color
is not None:
2142 return context_wrapper.loadOBJWithOriginScaleRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), scale.to_list(), rotation.to_list(), color.to_list(), upaxis, silent)
2145 raise ValueError(
"Invalid parameter combination. For OBJ loading, you must provide either: " +
2146 "1) No parameters (simple load), " +
2147 "2) origin + height + rotation + color, " +
2148 "3) origin + height + rotation + color + upaxis, or " +
2149 "4) origin + scale + rotation + color + upaxis")
2151 def loadXML(self, filename: str, quiet: bool =
False) -> List[int]:
2153 Load geometry from a Helios XML file.
2156 filename: Path to the XML file to load
2157 quiet: If True, suppress loading output messages
2160 List of UUIDs for the loaded primitives
2166 return context_wrapper.loadXML(self.
context, validated_filename, quiet)
2168 def writePLY(self, filename: str, UUIDs: Optional[List[int]] =
None) ->
None:
2170 Write geometry to a PLY (Stanford Polygon) file.
2173 filename: Path to the output PLY file
2174 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2177 ValueError: If filename is invalid or UUIDs are invalid
2178 PermissionError: If output directory is not writable
2179 FileNotFoundError: If UUIDs do not exist in context
2180 RuntimeError: If Context is in mock mode
2183 >>> context.writePLY("output.ply") # Export all primitives
2184 >>> context.writePLY("subset.ply", [uuid1, uuid2]) # Export specific primitives
2193 context_wrapper.writePLY(self.
context, validated_filename)
2197 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2204 context_wrapper.writePLYWithUUIDs(self.
context, validated_filename, UUIDs)
2206 def writeOBJ(self, filename: str, UUIDs: Optional[List[int]] =
None,
2207 primitive_data_fields: Optional[List[str]] =
None,
2208 write_normals: bool =
False, silent: bool =
False) ->
None:
2210 Write geometry to an OBJ (Wavefront) file.
2213 filename: Path to the output OBJ file
2214 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2215 primitive_data_fields: Optional list of primitive data field names to export
2216 write_normals: Whether to include vertex normals in the output
2217 silent: Whether to suppress output messages during export
2220 ValueError: If filename is invalid, UUIDs are invalid, or data fields don't exist
2221 PermissionError: If output directory is not writable
2222 FileNotFoundError: If UUIDs do not exist in context
2223 RuntimeError: If Context is in mock mode
2226 >>> context.writeOBJ("output.obj") # Export all primitives
2227 >>> context.writeOBJ("subset.obj", [uuid1, uuid2]) # Export specific primitives
2228 >>> context.writeOBJ("with_data.obj", [uuid1], ["temperature", "area"]) # Export with data
2237 context_wrapper.writeOBJ(self.
context, validated_filename, write_normals, silent)
2238 elif primitive_data_fields
is None:
2241 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2247 context_wrapper.writeOBJWithUUIDs(self.
context, validated_filename, UUIDs, write_normals, silent)
2251 raise ValueError(
"UUIDs list cannot be empty when exporting primitive data")
2252 if not primitive_data_fields:
2253 raise ValueError(
"primitive_data_fields list cannot be empty")
2262 context_wrapper.writeOBJWithPrimitiveData(self.
context, validated_filename, UUIDs, primitive_data_fields, write_normals, silent)
2265 UUIDs: Optional[List[int]] =
None,
2266 print_header: bool =
False) ->
None:
2268 Write primitive data to an ASCII text file.
2270 Outputs a space-separated text file where each row corresponds to a primitive
2271 and each column corresponds to a primitive data label.
2274 filename: Path to the output file
2275 column_labels: List of primitive data labels to include as columns.
2276 Use "UUID" to include primitive UUIDs as a column.
2277 The order determines the column order in the output file.
2278 UUIDs: Optional list of primitive UUIDs to include. If None, includes all primitives.
2279 print_header: If True, writes column labels as the first line of the file
2282 ValueError: If filename is invalid, column_labels is empty, or UUIDs list is empty when provided
2283 HeliosFileIOError: If file cannot be written
2284 HeliosRuntimeError: If a column label doesn't exist for any primitive
2287 >>> # Write temperature and area for all primitives
2288 >>> context.writePrimitiveData("output.txt", ["UUID", "temperature", "area"])
2290 >>> # Write with header row
2291 >>> context.writePrimitiveData("output.txt", ["UUID", "radiation_flux"], print_header=True)
2293 >>> # Write only for selected primitives
2294 >>> context.writePrimitiveData("subset.txt", ["temperature"], UUIDs=[uuid1, uuid2])
2299 if not column_labels:
2300 raise ValueError(
"column_labels list cannot be empty")
2307 context_wrapper.writePrimitiveData(self.
context, validated_filename, column_labels, print_header)
2311 raise ValueError(
"UUIDs list cannot be empty when provided. Use UUIDs=None to include all primitives")
2317 context_wrapper.writePrimitiveDataWithUUIDs(self.
context, validated_filename, column_labels, UUIDs, print_header)
2320 colors: Optional[np.ndarray] =
None) -> List[int]:
2322 Add triangles from NumPy arrays (compatible with trimesh, Open3D format).
2325 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2326 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2327 colors: Optional NumPy array of shape (N, 3) or (M, 3) containing RGB colors as float32/float64
2328 If shape (N, 3): per-vertex colors
2329 If shape (M, 3): per-triangle colors
2332 List of UUIDs for the added triangles
2335 ValueError: If array dimensions are invalid
2338 if vertices.ndim != 2
or vertices.shape[1] != 3:
2339 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2340 if faces.ndim != 2
or faces.shape[1] != 3:
2341 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2344 max_vertex_index = np.max(faces)
2345 if max_vertex_index >= vertices.shape[0]:
2346 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2349 per_vertex_colors =
False
2350 per_triangle_colors =
False
2351 if colors
is not None:
2352 if colors.ndim != 2
or colors.shape[1] != 3:
2353 raise ValueError(f
"Colors array must have shape (N, 3) or (M, 3), got {colors.shape}")
2354 if colors.shape[0] == vertices.shape[0]:
2355 per_vertex_colors =
True
2356 elif colors.shape[0] == faces.shape[0]:
2357 per_triangle_colors =
True
2359 raise ValueError(f
"Colors array shape {colors.shape} doesn't match vertices ({vertices.shape[0]},) or faces ({faces.shape[0]},)")
2362 vertices_float = vertices.astype(np.float32)
2363 faces_int = faces.astype(np.int32)
2364 if colors
is not None:
2365 colors_float = colors.astype(np.float32)
2369 for i
in range(faces.shape[0]):
2371 v0_idx, v1_idx, v2_idx = faces_int[i]
2374 vertex0 = vertices_float[v0_idx].tolist()
2375 vertex1 = vertices_float[v1_idx].tolist()
2376 vertex2 = vertices_float[v2_idx].tolist()
2381 uuid = context_wrapper.addTriangle(self.
context, vertex0, vertex1, vertex2)
2382 elif per_triangle_colors:
2384 color = colors_float[i].tolist()
2385 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2386 elif per_vertex_colors:
2388 color = np.mean([colors_float[v0_idx], colors_float[v1_idx], colors_float[v2_idx]], axis=0).tolist()
2389 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2391 triangle_uuids.append(uuid)
2393 return triangle_uuids
2396 uv_coords: np.ndarray, texture_files: Union[str, List[str]],
2397 material_ids: Optional[np.ndarray] =
None) -> List[int]:
2399 Add textured triangles from NumPy arrays with support for multiple textures.
2401 This method supports both single-texture and multi-texture workflows:
2402 - Single texture: Pass a single texture file string, all faces use the same texture
2403 - Multiple textures: Pass a list of texture files and material_ids array specifying which texture each face uses
2406 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2407 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2408 uv_coords: NumPy array of shape (N, 2) containing UV texture coordinates as float32/float64
2409 texture_files: Single texture file path (str) or list of texture file paths (List[str])
2410 material_ids: Optional NumPy array of shape (M,) containing material ID for each face.
2411 If None and texture_files is a list, all faces use texture 0.
2412 If None and texture_files is a string, this parameter is ignored.
2415 List of UUIDs for the added textured triangles
2418 ValueError: If array dimensions are invalid or material IDs are out of range
2421 # Single texture usage (backward compatible)
2422 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, "texture.png")
2424 # Multi-texture usage (Open3D style)
2425 >>> texture_files = ["wood.png", "metal.png", "glass.png"]
2426 >>> material_ids = np.array([0, 0, 1, 1, 2, 2]) # 6 faces using different textures
2427 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, texture_files, material_ids)
2432 if vertices.ndim != 2
or vertices.shape[1] != 3:
2433 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2434 if faces.ndim != 2
or faces.shape[1] != 3:
2435 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2436 if uv_coords.ndim != 2
or uv_coords.shape[1] != 2:
2437 raise ValueError(f
"UV coordinates array must have shape (N, 2), got {uv_coords.shape}")
2440 if uv_coords.shape[0] != vertices.shape[0]:
2441 raise ValueError(f
"UV coordinates count ({uv_coords.shape[0]}) must match vertices count ({vertices.shape[0]})")
2444 max_vertex_index = np.max(faces)
2445 if max_vertex_index >= vertices.shape[0]:
2446 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2449 if isinstance(texture_files, str):
2451 texture_file_list = [texture_files]
2452 if material_ids
is None:
2453 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2456 if not np.all(material_ids == 0):
2457 raise ValueError(
"When using single texture file, all material IDs must be 0")
2460 texture_file_list = list(texture_files)
2461 if len(texture_file_list) == 0:
2462 raise ValueError(
"Texture files list cannot be empty")
2464 if material_ids
is None:
2466 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2469 if material_ids.ndim != 1
or material_ids.shape[0] != faces.shape[0]:
2470 raise ValueError(f
"Material IDs must have shape ({faces.shape[0]},), got {material_ids.shape}")
2473 max_material_id = np.max(material_ids)
2474 if max_material_id >= len(texture_file_list):
2475 raise ValueError(f
"Material ID {max_material_id} exceeds texture count {len(texture_file_list)}")
2478 for i, texture_file
in enumerate(texture_file_list):
2481 except (FileNotFoundError, ValueError)
as e:
2482 raise ValueError(f
"Texture file {i} ({texture_file}): {e}")
2485 if 'addTrianglesFromArraysMultiTextured' in context_wrapper._AVAILABLE_TRIANGLE_FUNCTIONS:
2486 return context_wrapper.addTrianglesFromArraysMultiTextured(
2487 self.
context, vertices, faces, uv_coords, texture_file_list, material_ids
2491 from .wrappers.DataTypes
import vec3, vec2
2493 vertices_float = vertices.astype(np.float32)
2494 faces_int = faces.astype(np.int32)
2495 uv_coords_float = uv_coords.astype(np.float32)
2498 for i
in range(faces.shape[0]):
2500 v0_idx, v1_idx, v2_idx = faces_int[i]
2503 vertex0 =
vec3(vertices_float[v0_idx][0], vertices_float[v0_idx][1], vertices_float[v0_idx][2])
2504 vertex1 =
vec3(vertices_float[v1_idx][0], vertices_float[v1_idx][1], vertices_float[v1_idx][2])
2505 vertex2 =
vec3(vertices_float[v2_idx][0], vertices_float[v2_idx][1], vertices_float[v2_idx][2])
2508 uv0 =
vec2(uv_coords_float[v0_idx][0], uv_coords_float[v0_idx][1])
2509 uv1 =
vec2(uv_coords_float[v1_idx][0], uv_coords_float[v1_idx][1])
2510 uv2 =
vec2(uv_coords_float[v2_idx][0], uv_coords_float[v2_idx][1])
2513 material_id = material_ids[i]
2514 texture_file = texture_file_list[material_id]
2518 triangle_uuids.append(uuid)
2520 return triangle_uuids
2528 Set primitive data as signed 32-bit integer for one or multiple primitives.
2531 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2532 label: String key for the data
2533 value: Signed integer scalar (broadcast to all UUIDs), or a list of
2534 values (one per UUID) to set a distinct value on each primitive.
2536 if isinstance(uuids_or_uuid, (list, tuple)):
2537 if isinstance(value, (list, tuple, np.ndarray)):
2538 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int', value)
2540 context_wrapper.setBroadcastPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2542 context_wrapper.setPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2546 Set primitive data as unsigned 32-bit integer for one or multiple primitives.
2548 Critical for properties like 'twosided_flag' which must be uint in C++.
2551 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2552 label: String key for the data
2553 value: Unsigned integer scalar (broadcast to all UUIDs), or a list of
2554 values (one per UUID) to set a distinct value on each primitive.
2556 if isinstance(uuids_or_uuid, (list, tuple)):
2557 if isinstance(value, (list, tuple, np.ndarray)):
2558 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'UInt', value)
2560 context_wrapper.setBroadcastPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2562 context_wrapper.setPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2566 Set primitive data as 32-bit float for one or multiple primitives.
2569 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2570 label: String key for the data
2571 value: Float scalar (broadcast to all UUIDs), or a list of values
2572 (one per UUID) to set a distinct value on each primitive.
2574 if isinstance(uuids_or_uuid, (list, tuple)):
2575 if isinstance(value, (list, tuple, np.ndarray)):
2576 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Float', value)
2578 context_wrapper.setBroadcastPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2580 context_wrapper.setPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2584 Set primitive data as 64-bit double for one or multiple primitives.
2587 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2588 label: String key for the data
2589 value: Double scalar (broadcast to all UUIDs), or a list of values
2590 (one per UUID) to set a distinct value on each primitive.
2592 if isinstance(uuids_or_uuid, (list, tuple)):
2593 if isinstance(value, (list, tuple, np.ndarray)):
2594 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Double', value)
2596 context_wrapper.setBroadcastPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2598 context_wrapper.setPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2602 Set primitive data as string for one or multiple primitives.
2605 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2606 label: String key for the data
2607 value: String scalar (broadcast to all UUIDs), or a list of strings
2608 (one per UUID) to set a distinct value on each primitive.
2610 if isinstance(uuids_or_uuid, (list, tuple)):
2611 if isinstance(value, (list, tuple, np.ndarray)):
2612 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'String', value)
2614 context_wrapper.setBroadcastPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2616 context_wrapper.setPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2620 Set primitive data as vec2 for one or multiple primitives.
2623 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2624 label: String key for the data
2625 x_or_vec: Either x component (float) or vec2 object
2626 y: Y component (if x_or_vec is float)
2628 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2629 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec2', x_or_vec)
2631 if hasattr(x_or_vec,
'x'):
2632 x, y = x_or_vec.x, x_or_vec.y
2635 if isinstance(uuids_or_uuid, (list, tuple)):
2636 context_wrapper.setBroadcastPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2638 context_wrapper.setPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2640 def setPrimitiveDataVec3(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
2642 Set primitive data as vec3 for one or multiple primitives.
2645 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2646 label: String key for the data
2647 x_or_vec: Either x component (float) or vec3 object
2648 y: Y component (if x_or_vec is float)
2649 z: Z component (if x_or_vec is float)
2651 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2652 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec3', x_or_vec)
2654 if hasattr(x_or_vec,
'x'):
2655 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2658 if isinstance(uuids_or_uuid, (list, tuple)):
2659 context_wrapper.setBroadcastPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2661 context_wrapper.setPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2663 def setPrimitiveDataVec4(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
2665 Set primitive data as vec4 for one or multiple primitives.
2668 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2669 label: String key for the data
2670 x_or_vec: Either x component (float) or vec4 object
2671 y: Y component (if x_or_vec is float)
2672 z: Z component (if x_or_vec is float)
2673 w: W component (if x_or_vec is float)
2675 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2676 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Vec4', x_or_vec)
2678 if hasattr(x_or_vec,
'x'):
2679 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2682 if isinstance(uuids_or_uuid, (list, tuple)):
2683 context_wrapper.setBroadcastPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2685 context_wrapper.setPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2689 Set primitive data as int2 for one or multiple primitives.
2692 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2693 label: String key for the data
2694 x_or_vec: Either x component (int) or int2 object
2695 y: Y component (if x_or_vec is int)
2697 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2698 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int2', x_or_vec)
2700 if hasattr(x_or_vec,
'x'):
2701 x, y = x_or_vec.x, x_or_vec.y
2704 if isinstance(uuids_or_uuid, (list, tuple)):
2705 context_wrapper.setBroadcastPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2707 context_wrapper.setPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2709 def setPrimitiveDataInt3(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
2711 Set primitive data as int3 for one or multiple primitives.
2714 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2715 label: String key for the data
2716 x_or_vec: Either x component (int) or int3 object
2717 y: Y component (if x_or_vec is int)
2718 z: Z component (if x_or_vec is int)
2720 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2721 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int3', x_or_vec)
2723 if hasattr(x_or_vec,
'x'):
2724 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2727 if isinstance(uuids_or_uuid, (list, tuple)):
2728 context_wrapper.setBroadcastPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2730 context_wrapper.setPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2732 def setPrimitiveDataInt4(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
2734 Set primitive data as int4 for one or multiple primitives.
2737 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2738 label: String key for the data
2739 x_or_vec: Either x component (int) or int4 object
2740 y: Y component (if x_or_vec is int)
2741 z: Z component (if x_or_vec is int)
2742 w: W component (if x_or_vec is int)
2744 if isinstance(uuids_or_uuid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
2745 context_wrapper.setPrimitiveDataArray(self.
context, uuids_or_uuid, label,
'Int4', x_or_vec)
2747 if hasattr(x_or_vec,
'x'):
2748 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2751 if isinstance(uuids_or_uuid, (list, tuple)):
2752 context_wrapper.setBroadcastPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2754 context_wrapper.setPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2758 Get primitive data for a specific primitive. If data_type is provided, it works like before.
2759 If data_type is None, it automatically detects the type and returns the appropriate value.
2762 uuid: UUID of the primitive
2763 label: String key for the data
2764 data_type: Optional. Python type to retrieve (int, uint, float, double, bool, str, vec2, vec3, vec4, int2, int3, int4, etc.)
2765 If None, auto-detects the type using C++ getPrimitiveDataType().
2768 The stored value of the specified or auto-detected type
2771 if data_type
is None:
2772 return context_wrapper.getPrimitiveDataAuto(self.
context, uuid, label)
2775 if data_type == int:
2776 return context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2777 elif data_type == float:
2778 return context_wrapper.getPrimitiveDataFloat(self.
context, uuid, label)
2779 elif data_type == bool:
2781 int_value = context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2782 return int_value != 0
2783 elif data_type == str:
2784 return context_wrapper.getPrimitiveDataString(self.
context, uuid, label)
2787 elif data_type == vec2:
2788 coords = context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2789 return vec2(coords[0], coords[1])
2790 elif data_type == vec3:
2791 coords = context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2792 return vec3(coords[0], coords[1], coords[2])
2793 elif data_type == vec4:
2794 coords = context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2795 return vec4(coords[0], coords[1], coords[2], coords[3])
2796 elif data_type == int2:
2797 coords = context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2798 return int2(coords[0], coords[1])
2799 elif data_type == int3:
2800 coords = context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2801 return int3(coords[0], coords[1], coords[2])
2802 elif data_type == int4:
2803 coords = context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2804 return int4(coords[0], coords[1], coords[2], coords[3])
2807 elif data_type ==
"uint":
2808 return context_wrapper.getPrimitiveDataUInt(self.
context, uuid, label)
2809 elif data_type ==
"double":
2810 return context_wrapper.getPrimitiveDataDouble(self.
context, uuid, label)
2813 elif data_type == list:
2815 return context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2816 elif data_type ==
"list_vec2":
2817 return context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2818 elif data_type ==
"list_vec4":
2819 return context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2820 elif data_type ==
"list_int2":
2821 return context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2822 elif data_type ==
"list_int3":
2823 return context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2824 elif data_type ==
"list_int4":
2825 return context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2828 raise ValueError(f
"Unsupported primitive data type: {data_type}. "
2829 f
"Supported types: int, float, bool, str, vec2, vec3, vec4, int2, int3, int4, "
2830 f
"'uint', 'double', list (for vec3), 'list_vec2', 'list_vec4', 'list_int2', 'list_int3', 'list_int4'")
2834 Check if primitive data exists for a specific primitive and label.
2837 uuid: UUID of the primitive
2838 label: String key for the data
2841 True if the data exists, False otherwise
2843 return context_wrapper.doesPrimitiveDataExistWrapper(self.
context, uuid, label)
2847 Convenience method to get float primitive data.
2850 uuid: UUID of the primitive
2851 label: String key for the data
2854 Float value stored for the primitive
2860 Get the Helios data type of primitive data.
2863 uuid: UUID of the primitive
2864 label: String key for the data
2867 HeliosDataType enum value as integer
2869 return context_wrapper.getPrimitiveDataTypeWrapper(self.
context, uuid, label)
2873 Get the size/length of primitive data (for vector data).
2876 uuid: UUID of the primitive
2877 label: String key for the data
2880 Size of data array, or 1 for scalar data
2882 return context_wrapper.getPrimitiveDataSizeWrapper(self.
context, uuid, label)
2886 Get primitive data values for multiple primitives as a NumPy array.
2888 This method retrieves primitive data for a list of UUIDs and returns the values
2889 as a NumPy array. The output array has the same length as the input UUID list,
2890 with each index corresponding to the primitive data value for that UUID.
2893 uuids: List of primitive UUIDs to get data for
2894 label: String key for the primitive data to retrieve
2897 NumPy array of primitive data values corresponding to each UUID.
2898 The array type depends on the data type:
2899 - int data: int32 array
2900 - uint data: uint32 array
2901 - float data: float32 array
2902 - double data: float64 array
2903 - vector data: float32 array with shape (N, vector_size)
2904 - string data: object array of strings
2907 ValueError: If UUID list is empty or UUIDs don't exist
2908 RuntimeError: If context is in mock mode or data doesn't exist for some UUIDs
2913 raise ValueError(
"UUID list cannot be empty")
2922 raise ValueError(f
"Primitive data '{label}' does not exist for UUID {uuid}")
2925 first_uuid = uuids[0]
2931 result = np.empty(len(uuids), dtype=np.int32)
2932 for i, uuid
in enumerate(uuids):
2935 elif data_type == 1:
2936 result = np.empty(len(uuids), dtype=np.uint32)
2937 for i, uuid
in enumerate(uuids):
2940 elif data_type == 2:
2941 result = np.empty(len(uuids), dtype=np.float32)
2942 for i, uuid
in enumerate(uuids):
2945 elif data_type == 3:
2946 result = np.empty(len(uuids), dtype=np.float64)
2947 for i, uuid
in enumerate(uuids):
2950 elif data_type == 4:
2951 result = np.empty((len(uuids), 2), dtype=np.float32)
2952 for i, uuid
in enumerate(uuids):
2954 result[i] = [vec_data.x, vec_data.y]
2956 elif data_type == 5:
2957 result = np.empty((len(uuids), 3), dtype=np.float32)
2958 for i, uuid
in enumerate(uuids):
2960 result[i] = [vec_data.x, vec_data.y, vec_data.z]
2962 elif data_type == 6:
2963 result = np.empty((len(uuids), 4), dtype=np.float32)
2964 for i, uuid
in enumerate(uuids):
2966 result[i] = [vec_data.x, vec_data.y, vec_data.z, vec_data.w]
2968 elif data_type == 7:
2969 result = np.empty((len(uuids), 2), dtype=np.int32)
2970 for i, uuid
in enumerate(uuids):
2972 result[i] = [int_data.x, int_data.y]
2974 elif data_type == 8:
2975 result = np.empty((len(uuids), 3), dtype=np.int32)
2976 for i, uuid
in enumerate(uuids):
2978 result[i] = [int_data.x, int_data.y, int_data.z]
2980 elif data_type == 9:
2981 result = np.empty((len(uuids), 4), dtype=np.int32)
2982 for i, uuid
in enumerate(uuids):
2984 result[i] = [int_data.x, int_data.y, int_data.z, int_data.w]
2986 elif data_type == 10:
2987 result = np.empty(len(uuids), dtype=object)
2988 for i, uuid
in enumerate(uuids):
2992 raise ValueError(f
"Unsupported primitive data type: {data_type}")
2998 colormap: str =
"hot", ncolors: int = 10,
2999 max_val: Optional[float] =
None, min_val: Optional[float] =
None):
3001 Color primitives based on primitive data values using pseudocolor mapping.
3003 This method applies a pseudocolor mapping to primitives based on the values
3004 of specified primitive data. The primitive colors are updated to reflect the
3005 data values using a color map.
3008 uuids: List of primitive UUIDs to color
3009 primitive_data: Name of primitive data to use for coloring (e.g., "radiation_flux_SW")
3010 colormap: Color map name - options include "hot", "cool", "parula", "rainbow", "gray", "lava"
3011 ncolors: Number of discrete colors in color map (default: 10)
3012 max_val: Maximum value for color scale (auto-determined if None)
3013 min_val: Minimum value for color scale (auto-determined if None)
3015 if max_val
is not None and min_val
is not None:
3016 context_wrapper.colorPrimitiveByDataPseudocolorWithRange(
3017 self.
context, uuids, primitive_data, colormap, ncolors, max_val, min_val)
3019 context_wrapper.colorPrimitiveByDataPseudocolor(
3020 self.
context, uuids, primitive_data, colormap, ncolors)
3023 def setTime(self, hour: int, minute: int = 0, second: int = 0):
3025 Set the simulation time.
3029 minute: Minute (0-59), defaults to 0
3030 second: Second (0-59), defaults to 0
3033 ValueError: If time values are out of range
3034 NotImplementedError: If time/date functions not available in current library build
3037 >>> context.setTime(14, 30) # Set to 2:30 PM
3038 >>> context.setTime(9, 15, 30) # Set to 9:15:30 AM
3040 context_wrapper.setTime(self.
context, hour, minute, second)
3042 def setDate(self, year: int, month: int, day: int):
3044 Set the simulation date.
3047 year: Year (1900-3000)
3052 ValueError: If date values are out of range
3053 NotImplementedError: If time/date functions not available in current library build
3056 >>> context.setDate(2023, 6, 21) # Set to June 21, 2023
3058 context_wrapper.setDate(self.
context, year, month, day)
3062 Set the simulation date using Julian day number.
3065 julian_day: Julian day (1-366)
3066 year: Year (1900-3000)
3069 ValueError: If values are out of range
3070 NotImplementedError: If time/date functions not available in current library build
3073 >>> context.setDateJulian(172, 2023) # Set to day 172 of 2023 (June 21)
3075 context_wrapper.setDateJulian(self.
context, julian_day, year)
3079 Get the current simulation time.
3082 Tuple of (hour, minute, second) as integers
3085 NotImplementedError: If time/date functions not available in current library build
3088 >>> hour, minute, second = context.getTime()
3089 >>> print(f"Current time: {hour:02d}:{minute:02d}:{second:02d}")
3091 return context_wrapper.getTime(self.
context)
3095 Get the current simulation date.
3098 Tuple of (year, month, day) as integers
3101 NotImplementedError: If time/date functions not available in current library build
3104 >>> year, month, day = context.getDate()
3105 >>> print(f"Current date: {year}-{month:02d}-{day:02d}")
3107 return context_wrapper.getDate(self.
context)
3113 def addTimeseriesData(self, label: str, value: float, date:
'Date', time:
'Time'):
3115 Add a data point to a timeseries variable.
3118 label: Name of the timeseries variable (e.g., "temperature")
3119 value: Value of the data point
3120 date: Date of the data point
3121 time: Time of the data point
3124 ValueError: If label is empty, or date/time are wrong types
3125 NotImplementedError: If timeseries functions not available
3128 >>> from pyhelios.types import Date, Time
3129 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3132 if not isinstance(label, str)
or not label:
3133 raise ValueError(
"Label must be a non-empty string")
3134 if not isinstance(date, Date):
3135 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3136 if not isinstance(time, Time):
3137 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3139 context_wrapper.addTimeseriesData(
3140 self.
context, label, float(value),
3141 date.day, date.month, date.year,
3142 time.hour, time.minute, time.second
3147 Update the value of an existing timeseries data point.
3150 label: Name of the timeseries variable (must already exist)
3151 date: Date of the existing point (must match exactly)
3152 time: Time of the existing point (must match exactly)
3153 new_value: Replacement value
3156 ValueError: If label is empty, or date/time are wrong types
3157 HeliosRuntimeError: If the variable does not exist or no point matches the (date, time)
3158 NotImplementedError: If timeseries functions not available
3161 >>> from pyhelios.types import Date, Time
3162 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3163 >>> context.updateTimeseriesData("temperature", Date(2024, 6, 15), Time(12, 0, 0), 26.5)
3166 if not isinstance(label, str)
or not label:
3167 raise ValueError(
"Label must be a non-empty string")
3168 if not isinstance(date, Date):
3169 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3170 if not isinstance(time, Time):
3171 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3173 context_wrapper.updateTimeseriesData(
3175 date.day, date.month, date.year,
3176 time.hour, time.minute, time.second,
3182 Set the Context date and time from a timeseries data point index.
3185 label: Name of the timeseries variable
3186 index: Index of the data point (0 = earliest, chronologically ordered)
3189 ValueError: If label is empty or index is negative
3190 NotImplementedError: If timeseries functions not available
3193 >>> context.setCurrentTimeseriesPoint("temperature", 0)
3196 if not isinstance(label, str)
or not label:
3197 raise ValueError(
"Label must be a non-empty string")
3198 if not isinstance(index, int)
or index < 0:
3199 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3201 context_wrapper.setCurrentTimeseriesPoint(self.
context, label, index)
3204 index: int =
None) -> float:
3206 Query a timeseries data value.
3208 Three modes of operation:
3209 - With date and time: returns interpolated value at the specified date/time
3210 - With index: returns value at the specified data point index
3211 - With neither: returns value at the current Context date/time
3214 label: Name of the timeseries variable
3215 date: Date to query at (requires time as well)
3216 time: Time to query at (requires date as well)
3217 index: Index of the data point (0 = earliest)
3220 The timeseries value as a float
3223 ValueError: If both date/time and index are provided, or if date without time
3224 NotImplementedError: If timeseries functions not available
3227 >>> # Query at specific date/time
3228 >>> val = context.queryTimeseriesData("temperature", date=Date(2024, 6, 15), time=Time(12, 0, 0))
3229 >>> # Query by index
3230 >>> val = context.queryTimeseriesData("temperature", index=0)
3231 >>> # Query at current context time
3232 >>> val = context.queryTimeseriesData("temperature")
3235 if not isinstance(label, str)
or not label:
3236 raise ValueError(
"Label must be a non-empty string")
3238 has_datetime = date
is not None or time
is not None
3239 has_index = index
is not None
3241 if has_datetime
and has_index:
3242 raise ValueError(
"Cannot specify both date/time and index. Use one or the other.")
3245 if date
is None or time
is None:
3246 raise ValueError(
"Both date and time must be provided together")
3247 if not isinstance(date, Date):
3248 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3249 if not isinstance(time, Time):
3250 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3251 return context_wrapper.queryTimeseriesDataDateTime(
3253 date.day, date.month, date.year,
3254 time.hour, time.minute, time.second
3258 if not isinstance(index, int)
or index < 0:
3259 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3260 return context_wrapper.queryTimeseriesDataIndex(self.
context, label, index)
3262 return context_wrapper.queryTimeseriesDataCurrent(self.
context, label)
3266 Get the Time associated with a timeseries data point.
3269 label: Name of the timeseries variable
3270 index: Index of the data point (0 = earliest)
3273 Time object for the data point
3276 ValueError: If label is empty or index is negative
3277 NotImplementedError: If timeseries functions not available
3280 >>> t = context.queryTimeseriesTime("temperature", 0)
3281 >>> print(f"{t.hour:02d}:{t.minute:02d}:{t.second:02d}")
3284 if not isinstance(label, str)
or not label:
3285 raise ValueError(
"Label must be a non-empty string")
3286 if not isinstance(index, int)
or index < 0:
3287 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3289 hour, minute, second = context_wrapper.queryTimeseriesTime(self.
context, label, index)
3290 return Time(hour=hour, minute=minute, second=second)
3294 Get the Date associated with a timeseries data point.
3297 label: Name of the timeseries variable
3298 index: Index of the data point (0 = earliest)
3301 Date object for the data point
3304 ValueError: If label is empty or index is negative
3305 NotImplementedError: If timeseries functions not available
3308 >>> d = context.queryTimeseriesDate("temperature", 0)
3309 >>> print(f"{d.year}-{d.month:02d}-{d.day:02d}")
3312 if not isinstance(label, str)
or not label:
3313 raise ValueError(
"Label must be a non-empty string")
3314 if not isinstance(index, int)
or index < 0:
3315 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3317 year, month, day = context_wrapper.queryTimeseriesDate(self.
context, label, index)
3318 return Date(year=year, month=month, day=day)
3322 Get the number of data points in a timeseries variable.
3325 label: Name of the timeseries variable
3328 Number of data points
3331 ValueError: If label is empty
3332 NotImplementedError: If timeseries functions not available
3335 >>> n = context.getTimeseriesLength("temperature")
3336 >>> print(f"Timeseries has {n} data points")
3339 if not isinstance(label, str)
or not label:
3340 raise ValueError(
"Label must be a non-empty string")
3342 return context_wrapper.getTimeseriesLength(self.
context, label)
3346 Check whether a timeseries variable exists.
3349 label: Name of the timeseries variable
3352 True if the variable exists, False otherwise
3355 ValueError: If label is empty
3356 NotImplementedError: If timeseries functions not available
3359 >>> if context.doesTimeseriesVariableExist("temperature"):
3360 ... print("Temperature data loaded")
3363 if not isinstance(label, str)
or not label:
3364 raise ValueError(
"Label must be a non-empty string")
3366 return context_wrapper.doesTimeseriesVariableExist(self.
context, label)
3370 List all existing timeseries variables.
3373 List of timeseries variable names
3376 NotImplementedError: If timeseries functions not available
3379 >>> variables = context.listTimeseriesVariables()
3380 >>> for var in variables:
3381 ... print(f" {var}: {context.getTimeseriesLength(var)} points")
3385 return context_wrapper.listTimeseriesVariables(self.
context)
3388 """Clear all timeseries data from the Context.
3390 Removes all timeseries variables and their associated date/time values.
3393 NotImplementedError: If timeseries functions not available
3396 >>> context.clearTimeseriesData()
3397 >>> context.listTimeseriesVariables()
3401 context_wrapper.clearTimeseriesData(self.
context)
3404 """Delete a single timeseries variable and all of its data points.
3406 Complements :meth:`clearTimeseriesData` (which removes all variables) and
3407 :meth:`updateTimeseriesData` (which modifies a single point).
3410 label: Name of the timeseries variable to delete.
3413 ValueError: If ``label`` is empty.
3414 NotImplementedError: If running against helios-core older than v1.3.72.
3417 If the variable does not exist, the underlying Helios API issues a
3418 non-fatal warning to stderr and the call is otherwise a no-op.
3421 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3422 >>> context.deleteTimeseriesVariable("temperature")
3423 >>> context.doesTimeseriesVariableExist("temperature")
3427 if not isinstance(label, str)
or not label:
3428 raise ValueError(
"Label must be a non-empty string")
3429 context_wrapper.deleteTimeseriesVariable(self.
context, label)
3432 """Delete a single timeseries data point at the given date and time.
3434 If ``label`` is provided, only that variable's matching point is removed. If ``label``
3435 is omitted (None), the matching point is removed from every timeseries variable.
3438 date: Date of the data point to delete.
3439 time: Time of the data point to delete.
3440 label: Optional name of the timeseries variable. None applies to all variables.
3443 ValueError: If date/time are wrong types, or label is an empty string.
3444 NotImplementedError: If running against helios-core older than v1.3.73.
3447 If no matching data point exists, the underlying Helios API issues a non-fatal
3448 warning to stderr and the call is otherwise a no-op. Matching uses the same
3449 (date, time) encoding as :meth:`addTimeseriesData`.
3452 >>> from pyhelios.types import Date, Time
3453 >>> context.deleteTimeseriesDataPoint(Date(2024, 6, 15), Time(12, 0, 0), "temperature")
3456 if not isinstance(date, Date):
3457 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3458 if not isinstance(time, Time):
3459 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3460 if label
is not None and (
not isinstance(label, str)
or not label):
3461 raise ValueError(
"label must be a non-empty string or None")
3464 context_wrapper.deleteTimeseriesDataPointAll(
3466 date.day, date.month, date.year,
3467 time.hour, time.minute, time.second
3470 context_wrapper.deleteTimeseriesDataPoint(
3472 date.day, date.month, date.year,
3473 time.hour, time.minute, time.second
3477 delimiter: str =
",", date_string_format: str =
"YYYYMMDD",
3478 headerlines: int = 0):
3480 Load tabular timeseries data from a text file.
3482 The file should contain columns of data with dates/times and measured values.
3483 Column labels specify how each column should be interpreted. Special labels
3484 include "year", "DOY", "date", "datetime", "hour", "minute", "second", "time".
3485 Other labels become timeseries variable names.
3488 data_file: Path to the text file containing tabular data
3489 column_labels: List of column label strings specifying what each column contains
3490 delimiter: Column delimiter string (default: ",")
3491 date_string_format: Format of date strings in the file. Supported formats:
3492 "YYYYMMDD", "YYYYMMDDHH", "YYYYMMDDHHMM", "DD/MM/YYYY",
3493 "MM/DD/YYYY", "DDMMYYYY", "YYYY-MM-DD", "DD/MM/YYYY HH:MM",
3494 "MM/DD/YYYY HH:MM", "ISO8601" (default: "YYYYMMDD")
3495 headerlines: Number of header lines to skip (default: 0)
3498 ValueError: If data_file is empty, column_labels is empty, or delimiter is empty
3499 RuntimeError: If the file cannot be read or parsed
3500 NotImplementedError: If timeseries functions not available
3503 >>> context.loadTabularTimeseriesData(
3504 ... "weather_data.csv",
3505 ... column_labels=["date", "hour", "temperature", "humidity"],
3509 >>> temp = context.queryTimeseriesData("temperature", index=0)
3512 if not isinstance(data_file, str)
or not data_file:
3513 raise ValueError(
"data_file must be a non-empty string")
3514 if not isinstance(column_labels, list)
or not column_labels:
3515 raise ValueError(
"column_labels must be a non-empty list of strings")
3516 for i, label
in enumerate(column_labels):
3517 if not isinstance(label, str):
3518 raise ValueError(f
"column_labels[{i}] must be a string, got {type(label).__name__}")
3519 if not isinstance(delimiter, str)
or not delimiter:
3520 raise ValueError(
"delimiter must be a non-empty string")
3522 context_wrapper.loadTabularTimeseriesData(
3523 self.
context, data_file, column_labels, delimiter,
3524 date_string_format, headerlines
3531 def deletePrimitive(self, uuids_or_uuid: Union[int, List[int]]) ->
None:
3533 Delete one or more primitives from the context.
3535 This removes the primitive(s) entirely from the context. If a primitive
3536 belongs to a compound object, it will be removed from that object. If the
3537 object becomes empty after removal, it is automatically deleted.
3540 uuids_or_uuid: Single UUID (int) or list of UUIDs to delete
3543 RuntimeError: If any UUID doesn't exist in the context
3544 ValueError: If UUID is invalid (negative)
3545 NotImplementedError: If delete functions not available in current library build
3548 >>> context = Context()
3549 >>> patch_id = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
3550 >>> context.deletePrimitive(patch_id) # Single deletion
3552 >>> # Multiple deletion
3553 >>> ids = [context.addPatch() for _ in range(5)]
3554 >>> context.deletePrimitive(ids) # Delete all at once
3558 if isinstance(uuids_or_uuid, (list, tuple)):
3559 for uuid
in uuids_or_uuid:
3561 raise ValueError(f
"UUID must be non-negative, got {uuid}")
3562 context_wrapper.deletePrimitives(self.
context, list(uuids_or_uuid))
3564 if uuids_or_uuid < 0:
3565 raise ValueError(f
"UUID must be non-negative, got {uuids_or_uuid}")
3566 context_wrapper.deletePrimitive(self.
context, uuids_or_uuid)
3568 def deleteObject(self, objIDs_or_objID: Union[int, List[int]]) ->
None:
3570 Delete one or more compound objects from the context.
3572 This removes the compound object(s) AND all their child primitives.
3573 Use this when you want to delete an entire object hierarchy at once.
3576 objIDs_or_objID: Single object ID (int) or list of object IDs to delete
3579 RuntimeError: If any object ID doesn't exist in the context
3580 ValueError: If object ID is invalid (negative)
3581 NotImplementedError: If delete functions not available in current library build
3584 >>> context = Context()
3585 >>> # Create a compound object (e.g., a tile with multiple patches)
3586 >>> patch_ids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2),
3587 ... tile_divisions=int2(2, 2))
3588 >>> obj_id = context.getPrimitiveParentObjectID(patch_ids[0])
3589 >>> context.deleteObject(obj_id) # Deletes tile and all its patches
3593 if isinstance(objIDs_or_objID, (list, tuple)):
3594 for objID
in objIDs_or_objID:
3596 raise ValueError(f
"Object ID must be non-negative, got {objID}")
3597 context_wrapper.deleteObjects(self.
context, list(objIDs_or_objID))
3599 if objIDs_or_objID < 0:
3600 raise ValueError(f
"Object ID must be non-negative, got {objIDs_or_objID}")
3601 context_wrapper.deleteObject(self.
context, objIDs_or_objID)
3606 Get list of available plugins for this PyHelios instance.
3609 List of available plugin names
3615 Check if a specific plugin is available.
3618 plugin_name: Name of the plugin to check
3621 True if plugin is available, False otherwise
3627 Get detailed information about available plugin capabilities.
3630 Dictionary mapping plugin names to capability information
3635 """Print detailed plugin status information."""
3640 Get list of requested plugins that are not available.
3643 requested_plugins: List of plugin names to check
3646 List of missing plugin names
3656 Create a new material for sharing visual properties across primitives.
3658 Materials enable efficient memory usage by allowing multiple primitives to
3659 share rendering properties. Changes to a material affect all primitives using it.
3662 material_label: Unique label for the material
3665 RuntimeError: If material label already exists
3668 >>> context.addMaterial("wood_oak")
3669 >>> context.setMaterialColor("wood_oak", (0.6, 0.4, 0.2, 1.0))
3670 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3672 context_wrapper.addMaterial(self.
context, material_label)
3675 """Check if a material with the given label exists."""
3676 return context_wrapper.doesMaterialExist(self.
context, material_label)
3679 """Get list of all material labels in the context."""
3680 return context_wrapper.listMaterials(self.
context)
3684 Delete a material from the context.
3686 Primitives using this material will be reassigned to the default material.
3689 material_label: Label of the material to delete
3692 RuntimeError: If material doesn't exist
3694 context_wrapper.deleteMaterial(self.
context, material_label)
3698 Get the RGBA color of a material.
3701 material_label: Label of the material
3707 RuntimeError: If material doesn't exist
3709 from .wrappers.DataTypes
import RGBAcolor
3710 color_list = context_wrapper.getMaterialColor(self.
context, material_label)
3711 return RGBAcolor(color_list[0], color_list[1], color_list[2], color_list[3])
3715 Set the RGBA color of a material.
3717 This affects all primitives that reference this material.
3720 material_label: Label of the material
3721 color: RGBAcolor object or tuple/list of (r, g, b, a) values
3724 RuntimeError: If material doesn't exist
3727 >>> from pyhelios.types import RGBAcolor
3728 >>> context.setMaterialColor("wood", RGBAcolor(0.6, 0.4, 0.2, 1.0))
3729 >>> context.setMaterialColor("wood", (0.6, 0.4, 0.2, 1.0))
3731 if isinstance(color, RGBAcolor):
3732 r, g, b, a = color.r, color.g, color.b, color.a
3733 elif isinstance(color, (list, tuple))
and len(color) == 4:
3734 r, g, b, a = color[0], color[1], color[2], color[3]
3736 raise ValueError(f
"Color must be an RGBAcolor or a 4-element list/tuple, got {type(color).__name__}")
3737 context_wrapper.setMaterialColor(self.
context, material_label, r, g, b, a)
3741 Get the texture file path for a material.
3744 material_label: Label of the material
3747 Texture file path, or empty string if no texture
3750 RuntimeError: If material doesn't exist
3752 return context_wrapper.getMaterialTexture(self.
context, material_label)
3756 Set the texture file for a material.
3758 This affects all primitives that reference this material.
3761 material_label: Label of the material
3762 texture_file: Path to texture image file
3765 RuntimeError: If material doesn't exist or texture file not found
3767 context_wrapper.setMaterialTexture(self.
context, material_label, texture_file)
3770 """Check if material texture color is overridden by material color."""
3771 return context_wrapper.isMaterialTextureColorOverridden(self.
context, material_label)
3774 """Set whether material color overrides texture color."""
3775 context_wrapper.setMaterialTextureColorOverride(self.
context, material_label, override)
3778 """Get the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3779 return context_wrapper.getMaterialTwosidedFlag(self.
context, material_label)
3782 """Set the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3783 context_wrapper.setMaterialTwosidedFlag(self.
context, material_label, twosided_flag)
3787 Assign a material to primitive(s).
3790 uuid: Single UUID (int) or list of UUIDs (List[int])
3791 material_label: Label of the material to assign
3794 RuntimeError: If primitive or material doesn't exist
3797 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3798 >>> context.assignMaterialToPrimitive([uuid1, uuid2, uuid3], "wood_oak")
3800 if isinstance(uuid, (list, tuple)):
3801 context_wrapper.assignMaterialToPrimitives(self.
context, uuid, material_label)
3803 context_wrapper.assignMaterialToPrimitive(self.
context, uuid, material_label)
3807 Assign a material to all primitives in compound object(s).
3810 objID: Single object ID (int) or list of object IDs (List[int])
3811 material_label: Label of the material to assign
3814 RuntimeError: If object or material doesn't exist
3817 >>> tree_id = wpt.buildTree(WPTType.LEMON)
3818 >>> context.assignMaterialToObject(tree_id, "tree_bark")
3819 >>> context.assignMaterialToObject([id1, id2], "grass")
3821 if isinstance(objID, (list, tuple)):
3822 context_wrapper.assignMaterialToObjects(self.
context, objID, material_label)
3824 context_wrapper.assignMaterialToObject(self.
context, objID, material_label)
3827 """Get the material label assigned to a primitive or multiple primitives.
3830 uuid: Single UUID (int) or list of UUIDs
3833 str for single UUID, or List[str] for list
3836 RuntimeError: If primitive doesn't exist
3838 if isinstance(uuid, (list, tuple)):
3842 ptr, offsets, total = context_wrapper.getBatchPrimitiveMaterialLabels(self.
context, uuid)
3843 if total == 0
or not ptr:
3844 return [
"" for _
in uuid]
3845 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3846 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3847 return context_wrapper.getPrimitiveMaterialLabel(self.
context, uuid)
3851 Get two-sided rendering flag for a primitive.
3853 Checks material first, then primitive data if no material assigned.
3856 uuid: UUID of the primitive
3857 default_value: Default value if no material/data (default 1 = two-sided)
3860 Two-sided flag (0 = one-sided, 1 = two-sided)
3862 return context_wrapper.getPrimitiveTwosidedFlag(self.
context, uuid, default_value)
3866 Get all primitive UUIDs that use a specific material.
3869 material_label: Label of the material
3872 List of primitive UUIDs using the material
3875 RuntimeError: If material doesn't exist
3877 return context_wrapper.getPrimitivesUsingMaterial(self.
context, material_label)
3884 """Get the texture file path of a primitive or multiple primitives.
3887 uuid: Single UUID (int) or list of UUIDs
3890 str for single UUID, or List[str] for list
3893 if isinstance(uuid, (list, tuple)):
3896 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureFiles(self.
context, uuid)
3897 if total == 0
or not ptr:
3898 return [
"" for _
in uuid]
3899 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3900 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3901 return context_wrapper.getPrimitiveTextureFile(self.
context, uuid)
3904 """Resolve material texture suppression for export.
3906 For each primitive, applies material-based texture suppression rules:
3907 1. If primitive has texture but material has no texture -> suppress texture, use material color
3908 2. If both have texture and textureColorOverride -> prefix "mask:", use material color
3909 3. Otherwise -> leave unchanged
3912 uuids: List of primitive UUIDs
3913 colors_np: numpy float32 array of shape (N, 3), modified IN-PLACE
3916 List[str] of resolved texture file paths
3921 return context_wrapper.resolveMaterialTextures(self.
context, uuids, colors_np)
3924 """Pack GPU-ready geometry buffers for a set of primitives in a single C++ pass.
3926 Produces a binary blob containing contiguous typed arrays (positions,
3927 colors, uvs, indices, faceToUuid) grouped by texture, ready for
3928 zero-copy loading into Three.js BufferGeometry attributes.
3931 uuids: List of primitive UUIDs
3934 bytes: Raw binary blob (see wire format v2 spec)
3939 return context_wrapper.packGPUBuffers(self.
context, uuids)
3942 """Set the texture file path of a primitive.
3945 uuid: UUID of the primitive
3946 texture_file: Path to the texture file
3949 context_wrapper.setPrimitiveTextureFile(self.
context, uuid, texture_file)
3952 """Get the texture size (width, height) of a primitive.
3955 uuid: UUID of the primitive
3958 int2 with width and height of the texture
3961 w, h = context_wrapper.getPrimitiveTextureSize(self.
context, uuid)
3965 """Get the texture UV coordinates of a primitive or multiple primitives.
3968 uuid: Single UUID (int) or list of UUIDs
3971 List[vec2] for single UUID, or tuple of (flat_data, offsets) for list
3974 if isinstance(uuid, (list, tuple)):
3976 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
3977 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureUV(self.
context, uuid)
3978 offsets_arr = np.array(offsets, dtype=np.uint32)
3979 if total == 0
or not ptr:
3980 return (np.empty((0,), dtype=np.float32), offsets_arr)
3981 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
3982 return (data, offsets_arr)
3983 uv_pairs = context_wrapper.getPrimitiveTextureUV(self.
context, uuid)
3984 return [
vec2(u, v)
for u, v
in uv_pairs]
3987 """Check if primitive texture has a transparency channel.
3990 uuid: UUID of the primitive
3993 True if texture has transparency channel
3996 return context_wrapper.primitiveTextureHasTransparencyChannel(self.
context, uuid)
3999 """Get the solid fraction of a primitive or multiple primitives.
4002 uuid: Single UUID (int) or list of UUIDs
4005 float for single UUID, or np.ndarray of shape (N,) for list
4008 if isinstance(uuid, (list, tuple)):
4010 return np.empty((0,), dtype=np.float32)
4011 ptr, size = context_wrapper.getBatchPrimitiveSolidFractions(self.
context, uuid)
4012 if size == 0
or not ptr:
4013 return np.empty((0,), dtype=np.float32)
4014 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
4015 return context_wrapper.getPrimitiveSolidFraction(self.
context, uuid)
4018 """Override texture color with the primitive's constant RGB color.
4021 uuids_or_uuid: A single UUID (int) or a list of UUIDs. When a list is
4022 given, the override is applied to all of them in a single bulk call.
4025 if isinstance(uuids_or_uuid, (list, tuple)):
4026 context_wrapper.overridePrimitiveTextureColorBatchWrapper(self.
context, list(uuids_or_uuid))
4028 context_wrapper.overridePrimitiveTextureColor(self.
context, uuids_or_uuid)
4031 """Use texture-map color instead of the constant RGB color.
4034 uuids_or_uuid: A single UUID (int) or a list of UUIDs. When a list is
4035 given, all of them are restored in a single bulk call.
4038 if isinstance(uuids_or_uuid, (list, tuple)):
4039 context_wrapper.usePrimitiveTextureColorBatchWrapper(self.
context, list(uuids_or_uuid))
4041 context_wrapper.usePrimitiveTextureColor(self.
context, uuids_or_uuid)
4044 """Check if primitive texture color is overridden.
4047 uuid: UUID of the primitive
4050 True if texture color is overridden with constant RGB
4053 return context_wrapper.isPrimitiveTextureColorOverridden(self.
context, uuid)
4060 """Get normals for all primitives. Returns ndarray of shape (N, 3)."""
4064 """Get colors for all primitives. Returns ndarray of shape (N, 3)."""
4068 """Get areas for all primitives. Returns ndarray of shape (N,)."""
4072 """Get types for all primitives. Returns ndarray of shape (N,) uint32."""
4076 """Get solid fractions for all primitives. Returns ndarray of shape (N,)."""
4080 """Get vertices for all primitives. Returns (flat_data, offsets) tuple."""
4084 """Get texture files for all primitives. Returns list of strings."""
4088 """Get material labels for all primitives. Returns list of strings."""
4094 """Hide one or more primitives. Hidden primitives are excluded from getAllUUIDs().
4097 uuids_or_uuid: Single UUID (int) or list of UUIDs to hide.
4099 if isinstance(uuids_or_uuid, (list, tuple)):
4100 context_wrapper.hidePrimitivesWrapper(self.
context, list(uuids_or_uuid))
4102 context_wrapper.hidePrimitiveWrapper(self.
context, uuids_or_uuid)
4105 """Show one or more previously hidden primitives.
4108 uuids_or_uuid: Single UUID (int) or list of UUIDs to show.
4110 if isinstance(uuids_or_uuid, (list, tuple)):
4111 context_wrapper.showPrimitivesWrapper(self.
context, list(uuids_or_uuid))
4113 context_wrapper.showPrimitiveWrapper(self.
context, uuids_or_uuid)
4116 """Check if a primitive is hidden.
4119 uuid: UUID of the primitive.
4122 True if the primitive is hidden.
4124 return context_wrapper.isPrimitiveHiddenWrapper(self.
context, uuid)
4126 def hideObject(self, objids_or_objid) -> None:
4127 """Hide one or more compound objects (and all their primitives).
4130 objids_or_objid: Single object ID (int) or list of object IDs to hide.
4132 if isinstance(objids_or_objid, (list, tuple)):
4133 context_wrapper.hideObjectsWrapper(self.
context, list(objids_or_objid))
4135 context_wrapper.hideObjectWrapper(self.
context, objids_or_objid)
4137 def showObject(self, objids_or_objid) -> None:
4138 """Show one or more previously hidden compound objects.
4141 objids_or_objid: Single object ID (int) or list of object IDs to show.
4143 if isinstance(objids_or_objid, (list, tuple)):
4144 context_wrapper.showObjectsWrapper(self.
context, list(objids_or_objid))
4146 context_wrapper.showObjectWrapper(self.
context, objids_or_objid)
4149 """Check if a compound object is hidden.
4155 True if the object is hidden.
4157 return context_wrapper.isObjectHiddenWrapper(self.
context, objID)
4161 def setObjectDataInt(self, objids_or_objid, label: str, value: int) ->
None:
4162 """Set object data as signed 32-bit integer. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4163 if isinstance(objids_or_objid, (list, tuple)):
4164 if isinstance(value, (list, tuple, np.ndarray)):
4165 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int', value)
4167 context_wrapper.setBroadcastObjectDataInt(self.
context, objids_or_objid, label, value)
4169 context_wrapper.setObjectDataInt(self.
context, objids_or_objid, label, value)
4172 """Set object data as unsigned 32-bit integer. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4173 if isinstance(objids_or_objid, (list, tuple)):
4174 if isinstance(value, (list, tuple, np.ndarray)):
4175 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'UInt', value)
4177 context_wrapper.setBroadcastObjectDataUInt(self.
context, objids_or_objid, label, value)
4179 context_wrapper.setObjectDataUInt(self.
context, objids_or_objid, label, value)
4182 """Set object data as 32-bit float. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4183 if isinstance(objids_or_objid, (list, tuple)):
4184 if isinstance(value, (list, tuple, np.ndarray)):
4185 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Float', value)
4187 context_wrapper.setBroadcastObjectDataFloat(self.
context, objids_or_objid, label, value)
4189 context_wrapper.setObjectDataFloat(self.
context, objids_or_objid, label, value)
4192 """Set object data as 64-bit double. Scalar broadcasts to all objIDs; a list of values sets a distinct value per objID."""
4193 if isinstance(objids_or_objid, (list, tuple)):
4194 if isinstance(value, (list, tuple, np.ndarray)):
4195 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Double', value)
4197 context_wrapper.setBroadcastObjectDataDouble(self.
context, objids_or_objid, label, value)
4199 context_wrapper.setObjectDataDouble(self.
context, objids_or_objid, label, value)
4202 """Set object data as string. Scalar broadcasts to all objIDs; a list of strings sets a distinct value per objID."""
4203 if isinstance(objids_or_objid, (list, tuple)):
4204 if isinstance(value, (list, tuple, np.ndarray)):
4205 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'String', value)
4207 context_wrapper.setBroadcastObjectDataString(self.
context, objids_or_objid, label, value)
4209 context_wrapper.setObjectDataString(self.
context, objids_or_objid, label, value)
4211 def setObjectDataVec2(self, objids_or_objid, label: str, x_or_vec, y: float =
None) ->
None:
4212 """Set object data as vec2. Accepts a vec2 / x,y components, or a list of vec2 (one per objID)."""
4213 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4214 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec2', x_or_vec)
4216 if hasattr(x_or_vec,
'x')
and y
is None:
4217 x, y = x_or_vec.x, x_or_vec.y
4220 if isinstance(objids_or_objid, (list, tuple)):
4221 context_wrapper.setBroadcastObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4223 context_wrapper.setObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4225 def setObjectDataVec3(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4226 """Set object data as vec3. Accepts a vec3 / x,y,z components, or a list of vec3 (one per objID)."""
4227 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4228 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec3', x_or_vec)
4230 if hasattr(x_or_vec,
'x')
and y
is None:
4231 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4234 if isinstance(objids_or_objid, (list, tuple)):
4235 context_wrapper.setBroadcastObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4237 context_wrapper.setObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4239 def setObjectDataVec4(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4240 """Set object data as vec4. Accepts a vec4 / x,y,z,w components, or a list of vec4 (one per objID)."""
4241 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4242 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Vec4', x_or_vec)
4244 if hasattr(x_or_vec,
'x')
and y
is None:
4245 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4248 if isinstance(objids_or_objid, (list, tuple)):
4249 context_wrapper.setBroadcastObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4251 context_wrapper.setObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4253 def setObjectDataInt2(self, objids_or_objid, label: str, x_or_vec, y: int =
None) ->
None:
4254 """Set object data as int2. Accepts an int2 / x,y components, or a list of int2 (one per objID)."""
4255 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4256 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int2', x_or_vec)
4258 if hasattr(x_or_vec,
'x')
and y
is None:
4259 x, y = x_or_vec.x, x_or_vec.y
4262 if isinstance(objids_or_objid, (list, tuple)):
4263 context_wrapper.setBroadcastObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4265 context_wrapper.setObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4267 def setObjectDataInt3(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4268 """Set object data as int3. Accepts an int3 / x,y,z components, or a list of int3 (one per objID)."""
4269 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4270 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int3', x_or_vec)
4272 if hasattr(x_or_vec,
'x')
and y
is None:
4273 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4276 if isinstance(objids_or_objid, (list, tuple)):
4277 context_wrapper.setBroadcastObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4279 context_wrapper.setObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4281 def setObjectDataInt4(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4282 """Set object data as int4. Accepts an int4 / x,y,z,w components, or a list of int4 (one per objID)."""
4283 if isinstance(objids_or_objid, (list, tuple))
and isinstance(x_or_vec, (list, tuple, np.ndarray)):
4284 context_wrapper.setObjectDataArray(self.
context, objids_or_objid, label,
'Int4', x_or_vec)
4286 if hasattr(x_or_vec,
'x')
and y
is None:
4287 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4290 if isinstance(objids_or_objid, (list, tuple)):
4291 context_wrapper.setBroadcastObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4293 context_wrapper.setObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4295 def getObjectData(self, objID: int, label: str, data_type: type =
None):
4296 """Get object data with optional type specification. Auto-detects type if not specified."""
4297 if data_type
is None:
4298 return context_wrapper.getObjectDataAuto(self.
context, objID, label)
4299 if data_type == int:
4300 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4301 elif data_type == float:
4302 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4303 elif data_type == str:
4304 return context_wrapper.getObjectDataString(self.
context, objID, label)
4305 elif data_type == vec3:
4306 coords = context_wrapper.getObjectDataVec3(self.
context, objID, label)
4307 return vec3(coords[0], coords[1], coords[2])
4308 elif data_type == vec2:
4309 coords = context_wrapper.getObjectDataVec2(self.
context, objID, label)
4310 return vec2(coords[0], coords[1])
4311 elif data_type == vec4:
4312 coords = context_wrapper.getObjectDataVec4(self.
context, objID, label)
4313 return vec4(coords[0], coords[1], coords[2], coords[3])
4314 elif data_type == int2:
4315 coords = context_wrapper.getObjectDataInt2(self.
context, objID, label)
4316 return int2(coords[0], coords[1])
4317 elif data_type == int3:
4318 coords = context_wrapper.getObjectDataInt3(self.
context, objID, label)
4319 return int3(coords[0], coords[1], coords[2])
4320 elif data_type == int4:
4321 coords = context_wrapper.getObjectDataInt4(self.
context, objID, label)
4322 return int4(coords[0], coords[1], coords[2], coords[3])
4323 elif data_type ==
"uint":
4324 return context_wrapper.getObjectDataUInt(self.
context, objID, label)
4325 elif data_type ==
"double":
4326 return context_wrapper.getObjectDataDouble(self.
context, objID, label)
4328 raise ValueError(f
"Unsupported object data type: {data_type}")
4331 """Get float object data."""
4332 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4335 """Get int object data."""
4336 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4339 """Get string object data."""
4340 return context_wrapper.getObjectDataString(self.
context, objID, label)
4343 """Get the HeliosDataType enum for object data."""
4344 return context_wrapper.getObjectDataTypeWrapper(self.
context, objID, label)
4347 """Get the size of object data array."""
4348 return context_wrapper.getObjectDataSizeWrapper(self.
context, objID, label)
4351 """Check if object data exists."""
4352 return context_wrapper.doesObjectDataExistWrapper(self.
context, objID, label)
4355 """Clear object data. Accepts single ID or list."""
4356 if isinstance(objids_or_objid, (list, tuple)):
4357 context_wrapper.clearObjectDataBatchWrapper(self.
context, objids_or_objid, label)
4359 context_wrapper.clearObjectDataWrapper(self.
context, objids_or_objid, label)
4362 """Remove a named data field from every compound object in the Context.
4364 Clears the data with the given label from all objects (including hidden ones) and
4365 releases the registered data type for the label, so it may subsequently be
4366 re-registered with a different type. Requires helios-core v1.3.73 or newer.
4369 context_wrapper.clearAllObjectDataByLabelWrapper(self.
context, label)
4372 """List all data labels on a specific object."""
4373 return context_wrapper.listObjectDataWrapper(self.
context, objID)
4376 """List all object data labels in context."""
4377 return context_wrapper.listAllObjectDataLabelsWrapper(self.
context)
4380 """Copy object data to a new label."""
4381 context_wrapper.duplicateObjectDataWrapper(self.
context, objID, old_label, new_label)
4383 def renameObjectData(self, objID: int, old_label: str, new_label: str) ->
None:
4384 """Rename an object data label."""
4385 context_wrapper.renameObjectDataWrapper(self.
context, objID, old_label, new_label)
4387 def filterObjectsByData(self, objIDs: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4388 """Filter objects by data value. Auto-dispatches based on value type."""
4389 if isinstance(value, str):
4390 return context_wrapper.filterObjectsByDataStringWrapper(self.
context, objIDs, label, value)
4391 elif isinstance(value, float):
4392 return context_wrapper.filterObjectsByDataFloatWrapper(self.
context, objIDs, label, value, comparator)
4393 elif isinstance(value, int):
4394 return context_wrapper.filterObjectsByDataIntWrapper(self.
context, objIDs, label, value, comparator)
4396 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4401 """Set global data as signed 32-bit integer."""
4402 context_wrapper.setGlobalDataInt(self.
context, label, value)
4405 """Set global data as unsigned 32-bit integer."""
4406 context_wrapper.setGlobalDataUInt(self.
context, label, value)
4409 """Set global data as 32-bit float."""
4410 context_wrapper.setGlobalDataFloat(self.
context, label, value)
4413 """Set global data as 64-bit double."""
4414 context_wrapper.setGlobalDataDouble(self.
context, label, value)
4417 """Set global data as string."""
4418 context_wrapper.setGlobalDataString(self.
context, label, value)
4421 """Set global data as vec2."""
4422 if hasattr(x_or_vec,
'x')
and y
is None:
4423 x, y = x_or_vec.x, x_or_vec.y
4426 context_wrapper.setGlobalDataVec2(self.
context, label, x, y)
4428 def setGlobalDataVec3(self, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4429 """Set global data as vec3."""
4430 if hasattr(x_or_vec,
'x')
and y
is None:
4431 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4434 context_wrapper.setGlobalDataVec3(self.
context, label, x, y, z)
4436 def setGlobalDataVec4(self, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4437 """Set global data as vec4."""
4438 if hasattr(x_or_vec,
'x')
and y
is None:
4439 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4442 context_wrapper.setGlobalDataVec4(self.
context, label, x, y, z, w)
4445 """Set global data as int2."""
4446 if hasattr(x_or_vec,
'x')
and y
is None:
4447 x, y = x_or_vec.x, x_or_vec.y
4450 context_wrapper.setGlobalDataInt2(self.
context, label, x, y)
4452 def setGlobalDataInt3(self, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4453 """Set global data as int3."""
4454 if hasattr(x_or_vec,
'x')
and y
is None:
4455 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4458 context_wrapper.setGlobalDataInt3(self.
context, label, x, y, z)
4460 def setGlobalDataInt4(self, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4461 """Set global data as int4."""
4462 if hasattr(x_or_vec,
'x')
and y
is None:
4463 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4466 context_wrapper.setGlobalDataInt4(self.
context, label, x, y, z, w)
4468 def getGlobalData(self, label: str, data_type: type =
None):
4469 """Get global data with optional type specification. Auto-detects type if not specified."""
4470 if data_type
is None:
4471 return context_wrapper.getGlobalDataAuto(self.
context, label)
4472 if data_type == int:
4473 return context_wrapper.getGlobalDataInt(self.
context, label)
4474 elif data_type == float:
4475 return context_wrapper.getGlobalDataFloat(self.
context, label)
4476 elif data_type == str:
4477 return context_wrapper.getGlobalDataString(self.
context, label)
4478 elif data_type == vec3:
4479 coords = context_wrapper.getGlobalDataVec3(self.
context, label)
4480 return vec3(coords[0], coords[1], coords[2])
4481 elif data_type == vec2:
4482 coords = context_wrapper.getGlobalDataVec2(self.
context, label)
4483 return vec2(coords[0], coords[1])
4484 elif data_type == vec4:
4485 coords = context_wrapper.getGlobalDataVec4(self.
context, label)
4486 return vec4(coords[0], coords[1], coords[2], coords[3])
4487 elif data_type == int2:
4488 coords = context_wrapper.getGlobalDataInt2(self.
context, label)
4489 return int2(coords[0], coords[1])
4490 elif data_type == int3:
4491 coords = context_wrapper.getGlobalDataInt3(self.
context, label)
4492 return int3(coords[0], coords[1], coords[2])
4493 elif data_type == int4:
4494 coords = context_wrapper.getGlobalDataInt4(self.
context, label)
4495 return int4(coords[0], coords[1], coords[2], coords[3])
4496 elif data_type ==
"uint":
4497 return context_wrapper.getGlobalDataUInt(self.
context, label)
4498 elif data_type ==
"double":
4499 return context_wrapper.getGlobalDataDouble(self.
context, label)
4501 raise ValueError(f
"Unsupported global data type: {data_type}")
4504 """Get float global data."""
4505 return context_wrapper.getGlobalDataFloat(self.
context, label)
4508 """Get int global data."""
4509 return context_wrapper.getGlobalDataInt(self.
context, label)
4512 """Get string global data."""
4513 return context_wrapper.getGlobalDataString(self.
context, label)
4516 """Get the HeliosDataType enum for global data."""
4517 return context_wrapper.getGlobalDataTypeWrapper(self.
context, label)
4520 """Get the size of global data array."""
4521 return context_wrapper.getGlobalDataSizeWrapper(self.
context, label)
4524 """Check if global data exists."""
4525 return context_wrapper.doesGlobalDataExistWrapper(self.
context, label)
4528 """Clear global data."""
4529 context_wrapper.clearGlobalDataWrapper(self.
context, label)
4532 """Rename a global data label."""
4533 context_wrapper.renameGlobalDataWrapper(self.
context, old_label, new_label)
4536 """Duplicate global data to a new label."""
4537 context_wrapper.duplicateGlobalDataWrapper(self.
context, old_label, new_label)
4540 """List all global data labels."""
4541 return context_wrapper.listGlobalDataWrapper(self.
context)
4544 """Increment global data. Auto-dispatches based on increment type."""
4545 if isinstance(increment, float):
4546 context_wrapper.incrementGlobalDataFloatWrapper(self.
context, label, increment)
4547 elif isinstance(increment, int):
4548 context_wrapper.incrementGlobalDataIntWrapper(self.
context, label, increment)
4550 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4555 """Calculate arithmetic mean of primitive data across UUIDs.
4558 uuids: List of primitive UUIDs.
4560 return_type: float (default), "double", or vec3.
4562 if return_type == float:
4563 return context_wrapper.calculatePrimitiveDataMeanFloatWrapper(self.
context, uuids, label)
4564 elif return_type ==
"double":
4565 return context_wrapper.calculatePrimitiveDataMeanDoubleWrapper(self.
context, uuids, label)
4566 elif return_type == vec3:
4567 coords = context_wrapper.calculatePrimitiveDataMeanVec3Wrapper(self.
context, uuids, label)
4568 return vec3(coords[0], coords[1], coords[2])
4570 raise ValueError(f
"Unsupported return type: {return_type}")
4573 """Calculate area-weighted mean of primitive data."""
4574 if return_type == float:
4575 return context_wrapper.calculatePrimitiveDataAreaWeightedMeanFloatWrapper(self.
context, uuids, label)
4577 raise ValueError(f
"Unsupported return type: {return_type}")
4580 """Calculate sum of primitive data across UUIDs."""
4581 if return_type == float:
4582 return context_wrapper.calculatePrimitiveDataSumFloatWrapper(self.
context, uuids, label)
4583 elif return_type ==
"double":
4584 return context_wrapper.calculatePrimitiveDataSumDoubleWrapper(self.
context, uuids, label)
4586 raise ValueError(f
"Unsupported return type: {return_type}")
4589 """Calculate area-weighted sum of primitive data."""
4590 if return_type == float:
4591 return context_wrapper.calculatePrimitiveDataAreaWeightedSumFloatWrapper(self.
context, uuids, label)
4593 raise ValueError(f
"Unsupported return type: {return_type}")
4596 """Scale primitive data by a factor.
4599 scalePrimitiveData(uuids, label, factor) - scale for specific UUIDs
4600 scalePrimitiveData(label, factor) - scale for ALL primitives
4602 if isinstance(uuids_or_label, str):
4603 context_wrapper.scalePrimitiveDataAllWrapper(self.
context, uuids_or_label, label_or_factor)
4605 context_wrapper.scalePrimitiveDataWithUUIDsWrapper(self.
context, uuids_or_label, label_or_factor, factor)
4607 def incrementPrimitiveData(self, uuids: List[int], label: str, increment, data_type: str =
None) ->
None:
4608 """Increment primitive data for the given UUIDs.
4610 Each Helios increment overload only acts on fields whose stored type matches;
4611 fields of a different type are left unchanged. By default the overload is
4612 inferred from the Python type of ``increment`` (``int`` -> int, ``float`` ->
4613 float). To target an unsigned-int or double field, pass ``data_type``
4614 explicitly as one of ``'int'``, ``'uint'``, ``'float'``, ``'double'``.
4617 uuids: UUIDs whose data field to increment.
4618 label: Data field label.
4619 increment: Amount to add.
4620 data_type: Optional explicit field type to target.
4622 if data_type
is not None:
4623 dt = data_type.lower()
4625 context_wrapper.incrementPrimitiveDataIntWrapper(self.
context, uuids, label, int(increment))
4626 elif dt
in (
'uint',
'unsigned',
'unsigned int'):
4627 context_wrapper.incrementPrimitiveDataUIntWrapper(self.
context, uuids, label, int(increment))
4629 context_wrapper.incrementPrimitiveDataFloatWrapper(self.
context, uuids, label, float(increment))
4630 elif dt ==
'double':
4631 context_wrapper.incrementPrimitiveDataDoubleWrapper(self.
context, uuids, label, float(increment))
4633 raise ValueError(f
"Unsupported data_type: {data_type!r}. Expected one of 'int', 'uint', 'float', 'double'.")
4635 if isinstance(increment, float):
4636 context_wrapper.incrementPrimitiveDataFloatWrapper(self.
context, uuids, label, increment)
4637 elif isinstance(increment, int):
4638 context_wrapper.incrementPrimitiveDataIntWrapper(self.
context, uuids, label, increment)
4640 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4643 """Sum multiple primitive data fields into a new field."""
4644 context_wrapper.aggregatePrimitiveDataSumWrapper(self.
context, uuids, labels, result_label)
4647 """Multiply multiple primitive data fields into a new field."""
4648 context_wrapper.aggregatePrimitiveDataProductWrapper(self.
context, uuids, labels, result_label)
4651 """Calculate total one-sided surface area for a set of primitives."""
4652 return context_wrapper.sumPrimitiveSurfaceAreaWrapper(self.
context, uuids)
4654 def filterPrimitivesByData(self, uuids: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4655 """Filter primitives by data value. Auto-dispatches based on value type.
4658 uuids: UUIDs to filter.
4659 label: Data label to compare.
4660 value: Filter value (float, int, or str).
4661 comparator: Comparison operator ("=", "<", ">", "<=", ">="). Not used for strings.
4663 if isinstance(value, str):
4664 return context_wrapper.filterPrimitivesByDataStringWrapper(self.
context, uuids, label, value)
4665 elif isinstance(value, float):
4666 return context_wrapper.filterPrimitivesByDataFloatWrapper(self.
context, uuids, label, value, comparator)
4667 elif isinstance(value, int):
4668 return context_wrapper.filterPrimitivesByDataIntWrapper(self.
context, uuids, label, value, comparator)
4670 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4675 """Return the integer-coded `helios::ObjectType` of a compound object.
4677 Values follow the C++ `helios::ObjectType` enum
4678 (0=tile, 1=sphere, 2=tube, 3=box, 4=disk, 5=polymesh, 6=cone).
4681 return context_wrapper.getObjectTypeWrapper(self.
context, objID)
4685 x, y, z = context_wrapper.getObjectCenterWrapper(self.
context, objID)
4686 return vec3(x, y, z)
4689 """Get axis-aligned bounding box for one object or a list of objects.
4691 The box encloses every vertex of every primitive belonging to the given
4695 objIDs: Single object ID (int) or list of object IDs.
4698 Tuple of (min_corner: vec3, max_corner: vec3).
4701 HeliosRuntimeError: If an object ID does not exist, or if the given
4702 object(s) contain no primitives at all (a bounding box would be
4703 undefined; this previously returned a misleading box at the origin).
4706 if isinstance(objIDs, (list, tuple)):
4707 mn, mx = context_wrapper.getObjectBoundingBoxBatchWrapper(self.
context, list(objIDs))
4709 mn, mx = context_wrapper.getObjectBoundingBoxWrapper(self.
context, objIDs)
4710 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4713 """Get flattened primitive UUIDs for one object, a list of objects, or a list-of-lists.
4716 objIDs: int, List[int], or List[List[int]].
4719 Flat list of primitive UUIDs (union across all objects).
4722 if isinstance(objIDs, (list, tuple))
and objIDs
and isinstance(objIDs[0], (list, tuple)):
4723 return context_wrapper.getObjectPrimitiveUUIDsNestedWrapper(self.
context, [list(x)
for x
in objIDs])
4724 if isinstance(objIDs, (list, tuple)):
4725 return context_wrapper.getObjectPrimitiveUUIDsBatchWrapper(self.
context, list(objIDs))
4726 return context_wrapper.getObjectPrimitiveUUIDs(self.
context, int(objIDs))
4730 """Get tile-object area ratio for one or multiple tile objects."""
4732 if isinstance(objIDs, (list, tuple)):
4733 return context_wrapper.getTileObjectAreaRatioBatchWrapper(self.
context, list(objIDs))
4734 return context_wrapper.getTileObjectAreaRatioWrapper(self.
context, objIDs)
4738 x, y, z = context_wrapper.getTileObjectCenterWrapper(self.
context, objID)
4739 return vec3(x, y, z)
4743 x, y = context_wrapper.getTileObjectSizeWrapper(self.
context, objID)
4748 x, y = context_wrapper.getTileObjectSubdivisionCountWrapper(self.
context, objID)
4753 x, y, z = context_wrapper.getTileObjectNormalWrapper(self.
context, objID)
4758 pairs = context_wrapper.getTileObjectTextureUVWrapper(self.
context, objID)
4759 return [
vec2(u, v)
for u, v
in pairs]
4763 triples = context_wrapper.getTileObjectVerticesWrapper(self.
context, objID)
4764 return [
vec3(x, y, z)
for x, y, z
in triples]
4769 x, y, z = context_wrapper.getSphereObjectCenterWrapper(self.
context, objID)
4770 return vec3(x, y, z)
4773 """Get per-axis radii of a sphere object.
4775 Note: Helios spheres are spheroids with three independent radii (rx, ry, rz).
4776 Returns a vec3 (not a scalar).
4779 x, y, z = context_wrapper.getSphereObjectRadiusWrapper(self.
context, objID)
4780 return vec3(x, y, z)
4784 return context_wrapper.getSphereObjectSubdivisionCountWrapper(self.
context, objID)
4788 return context_wrapper.getSphereObjectVolumeWrapper(self.
context, objID)
4793 x, y, z = context_wrapper.getBoxObjectCenterWrapper(self.
context, objID)
4798 x, y, z = context_wrapper.getBoxObjectSizeWrapper(self.
context, objID)
4803 x, y, z = context_wrapper.getBoxObjectSubdivisionCountWrapper(self.
context, objID)
4808 return context_wrapper.getBoxObjectVolumeWrapper(self.
context, objID)
4813 x, y, z = context_wrapper.getDiskObjectCenterWrapper(self.
context, objID)
4818 x, y = context_wrapper.getDiskObjectSizeWrapper(self.
context, objID)
4823 return context_wrapper.getDiskObjectSubdivisionCountWrapper(self.
context, objID)
4828 return context_wrapper.getTubeObjectSubdivisionCountWrapper(self.
context, objID)
4832 return context_wrapper.getTubeObjectNodeCountWrapper(self.
context, objID)
4836 triples = context_wrapper.getTubeObjectNodesWrapper(self.
context, objID)
4837 return [
vec3(x, y, z)
for x, y, z
in triples]
4841 return context_wrapper.getTubeObjectNodeRadiiWrapper(self.
context, objID)
4845 triples = context_wrapper.getTubeObjectNodeColorsWrapper(self.
context, objID)
4846 return [
RGBcolor(r, g, b)
for r, g, b
in triples]
4850 return context_wrapper.getTubeObjectVolumeWrapper(self.
context, objID)
4854 return context_wrapper.getTubeObjectSegmentVolumeWrapper(self.
context, objID, segment_index)
4859 return context_wrapper.getConeObjectSubdivisionCountWrapper(self.
context, objID)
4863 triples = context_wrapper.getConeObjectNodesWrapper(self.
context, objID)
4864 return [
vec3(x, y, z)
for x, y, z
in triples]
4868 return context_wrapper.getConeObjectNodeRadiiWrapper(self.
context, objID)
4872 x, y, z = context_wrapper.getConeObjectNodeWrapper(self.
context, objID, number)
4873 return vec3(x, y, z)
4877 return context_wrapper.getConeObjectNodeRadiusWrapper(self.
context, objID, number)
4881 x, y, z = context_wrapper.getConeObjectAxisUnitVectorWrapper(self.
context, objID)
4882 return vec3(x, y, z)
4886 return context_wrapper.getConeObjectLengthWrapper(self.
context, objID)
4890 return context_wrapper.getConeObjectVolumeWrapper(self.
context, objID)
4896 x, y, z = context_wrapper.getPatchCenterWrapper(self.
context, uuid)
4897 return vec3(x, y, z)
4901 x, y = context_wrapper.getPatchSizeWrapper(self.
context, uuid)
4906 x, y, z = context_wrapper.getTriangleVertexWrapper(self.
context, uuid, number)
4911 x, y, z = context_wrapper.getVoxelCenterWrapper(self.
context, uuid)
4916 x, y, z = context_wrapper.getVoxelSizeWrapper(self.
context, uuid)
4919 def getPatchCount(self, include_hidden: bool =
True) -> int:
4921 return context_wrapper.getPatchCountWrapper(self.
context, include_hidden)
4925 return context_wrapper.getTriangleCountWrapper(self.
context, include_hidden)
4928 """Get axis-aligned bounding box for one primitive or a list of primitives.
4931 uuids: Single UUID (int) or list of UUIDs.
4934 Tuple of (min_corner: vec3, max_corner: vec3).
4937 if isinstance(uuids, (list, tuple)):
4938 mn, mx = context_wrapper.getPrimitiveBoundingBoxBatchWrapper(self.
context, list(uuids))
4940 mn, mx = context_wrapper.getPrimitiveBoundingBoxWrapper(self.
context, uuids)
4941 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4946 """Set the RGB or RGBA color of one primitive or a list of primitives.
4949 uuids: Single UUID (int) or list of UUIDs.
4950 color: RGBcolor or RGBAcolor.
4953 if isinstance(color, RGBAcolor):
4954 rgba = [color.r, color.g, color.b, color.a]
4955 if isinstance(uuids, (list, tuple)):
4956 context_wrapper.setPrimitiveColorRGBABatchWrapper(self.
context, list(uuids), rgba)
4958 context_wrapper.setPrimitiveColorRGBAWrapper(self.
context, uuids, rgba)
4959 elif isinstance(color, RGBcolor):
4960 rgb = [color.r, color.g, color.b]
4961 if isinstance(uuids, (list, tuple)):
4962 context_wrapper.setPrimitiveColorBatchWrapper(self.
context, list(uuids), rgb)
4964 context_wrapper.setPrimitiveColorWrapper(self.
context, uuids, rgb)
4966 raise ValueError(f
"color must be RGBcolor or RGBAcolor, got {type(color).__name__}")
4971 """Remove a named data field from one primitive or a list of primitives."""
4973 if isinstance(uuids, (list, tuple)):
4974 context_wrapper.clearPrimitiveDataByLabelBatchWrapper(self.
context, list(uuids), label)
4976 context_wrapper.clearPrimitiveDataByLabelWrapper(self.
context, uuids, label)
4979 """Remove a named data field from every primitive in the Context.
4981 Clears the data with the given label from all primitives (including hidden ones)
4982 and releases the registered data type for the label, so it may subsequently be
4983 re-registered with a different type. Requires helios-core v1.3.73 or newer.
4986 context_wrapper.clearAllPrimitiveDataByLabelWrapper(self.
context, label)
4989 """List all data labels attached to a primitive."""
4991 return context_wrapper.listPrimitiveDataWrapper(self.
context, uuid)
4997 if not isinstance(xbounds, vec2):
4998 raise ValueError(f
"xbounds must be a vec2, got {type(xbounds).__name__}")
4999 context_wrapper.cropDomainXWrapper(self.
context, xbounds.to_list())
5003 if not isinstance(ybounds, vec2):
5004 raise ValueError(f
"ybounds must be a vec2, got {type(ybounds).__name__}")
5005 context_wrapper.cropDomainYWrapper(self.
context, ybounds.to_list())
5009 if not isinstance(zbounds, vec2):
5010 raise ValueError(f
"zbounds must be a vec2, got {type(zbounds).__name__}")
5011 context_wrapper.cropDomainZWrapper(self.
context, zbounds.to_list())
5013 def cropDomain(self, *args) -> Optional[List[int]]:
5014 """Crop the context domain to the given XYZ bounds.
5017 cropDomain(xbounds: vec2, ybounds: vec2, zbounds: vec2)
5018 -> crop ALL primitives; returns None.
5019 cropDomain(uuids: List[int], xbounds: vec2, ybounds: vec2, zbounds: vec2)
5020 -> crop only the given primitives; returns the list of primitives
5021 that survived (in-bounds UUIDs). The input list is NOT mutated.
5026 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
5027 if not isinstance(b, vec2):
5028 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
5029 context_wrapper.cropDomainXYZWrapper(self.
context, xb.to_list(), yb.to_list(), zb.to_list())
5032 uuids, xb, yb, zb = args
5033 if not isinstance(uuids, (list, tuple)):
5034 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5035 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
5036 if not isinstance(b, vec2):
5037 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
5038 return context_wrapper.cropDomainByUUIDsWrapper(self.
context, list(uuids), xb.to_list(), yb.to_list(), zb.to_list())
5039 raise TypeError(f
"cropDomain() takes 3 or 4 positional arguments, got {len(args)}")
5048 """Return True if a compound object with the given ID exists."""
5050 return context_wrapper.doesObjectExistWrapper(self.
context, objID)
5053 """Return True if the given primitive UUID belongs to the given object."""
5055 return context_wrapper.doesObjectContainPrimitiveWrapper(self.
context, objID, uuid)
5058 """Return True if the named material has data stored under data_label."""
5060 return context_wrapper.doesMaterialDataExistWrapper(self.
context, material_label, data_label)
5063 """Return True if the compound object has a texture assigned."""
5065 return context_wrapper.objectHasTextureWrapper(self.
context, objID)
5068 """Return True if the primitive's geometry has been modified since the last clean mark."""
5070 return context_wrapper.isPrimitiveDirtyWrapper(self.
context, uuid)
5073 """Return True if value caching is enabled for the given object-data label."""
5075 return context_wrapper.isObjectDataValueCachingEnabledWrapper(self.
context, label)
5078 """Return True if value caching is enabled for the given primitive-data label."""
5080 return context_wrapper.isPrimitiveDataValueCachingEnabledWrapper(self.
context, label)
5083 """Return True if all primitives originally belonging to this object still exist
5084 (i.e., none have been deleted)."""
5086 return context_wrapper.areObjectPrimitivesCompleteWrapper(self.
context, objID)
5091 """Get the current simulation date as Julian day (1-366)."""
5093 return context_wrapper.getJulianDateWrapper(self.
context)
5096 """Return the total number of materials registered in the context."""
5098 return context_wrapper.getMaterialCountWrapper(self.
context)
5101 """Return the total surface area (one-sided) of all primitives in the object."""
5103 return context_wrapper.getObjectAreaWrapper(self.
context, objID)
5106 """Return the number of primitives currently belonging to the object."""
5108 return context_wrapper.getObjectPrimitiveCountWrapper(self.
context, objID)
5111 """Return the enclosed volume of a polymesh object."""
5113 return context_wrapper.getPolymeshObjectVolumeWrapper(self.
context, objID)
5116 """Look up a material ID from its human-readable label."""
5118 return context_wrapper.getMaterialIDFromLabelWrapper(self.
context, material_label)
5121 """Return the material ID assigned to the given primitive."""
5123 return context_wrapper.getPrimitiveMaterialIDWrapper(self.
context, uuid)
5126 """Return the version counter for a global data entry. Increments on each update;
5127 useful for cache invalidation."""
5129 return context_wrapper.getGlobalDataVersionWrapper(self.
context, label)
5132 """Return the ID of the compound object the primitive belongs to.
5134 Returns 0 if the primitive is not part of any compound object (the documented
5135 "no parent" sentinel). Raises ``HeliosRuntimeError`` if ``uuid`` does not exist.
5138 return context_wrapper.getPrimitiveParentObjectIDWrapper(self.
context, uuid)
5143 """Return the filesystem path of the texture assigned to the object, or an
5144 empty string if no texture is assigned."""
5146 return context_wrapper.getObjectTextureFileWrapper(self.
context, objID)
5149 """Return the union of all primitive-data labels used across every primitive
5152 return context_wrapper.listAllPrimitiveDataLabelsWrapper(self.
context)
5155 """Return the list of XML file paths that have been loaded into this context."""
5157 return context_wrapper.getLoadedXMLFilesWrapper(self.
context)
5162 """Print summary info for the object to stdout (for debugging)."""
5164 context_wrapper.printObjectInfoWrapper(self.
context, objID)
5167 """Print summary info for the primitive to stdout (for debugging)."""
5169 context_wrapper.printPrimitiveInfoWrapper(self.
context, uuid)
5172 """Enable value caching for the given primitive-data label. Required before
5173 using getUniquePrimitiveDataValues for that label."""
5175 context_wrapper.enablePrimitiveDataValueCachingWrapper(self.
context, label)
5178 """Disable value caching for the given primitive-data label."""
5180 context_wrapper.disablePrimitiveDataValueCachingWrapper(self.
context, label)
5183 """Enable value caching for the given object-data label. Required before
5184 using getUniqueObjectDataValues for that label."""
5186 context_wrapper.enableObjectDataValueCachingWrapper(self.
context, label)
5189 """Disable value caching for the given object-data label."""
5191 context_wrapper.disableObjectDataValueCachingWrapper(self.
context, label)
5194 """Compute the mean of the given primitive-data label across the object's
5195 primitives and store it as object data on the object itself under the
5198 context_wrapper.setObjectDataFromPrimitiveDataMeanWrapper(self.
context, objID, label)
5200 def renameMaterial(self, old_label: str, new_label: str) ->
None:
5201 """Rename an existing material."""
5203 context_wrapper.renameMaterialWrapper(self.
context, old_label, new_label)
5206 """Rename a primitive-data label on a single primitive."""
5208 context_wrapper.renamePrimitiveDataWrapper(self.
context, uuid, old_label, new_label)
5211 """Clear the named data entry on the given material."""
5213 context_wrapper.clearMaterialDataWrapper(self.
context, material_label, data_label)
5222 """Return the list of UUIDs that have been deleted from the context.
5224 These UUIDs are tombstoned and will not appear in getAllUUIDs(), but their
5225 IDs are tracked so they can be excluded from external references.
5228 return context_wrapper.getDeletedUUIDsWrapper(self.
context)
5230 def getDirtyUUIDs(self, include_deleted: bool =
True) -> List[int]:
5231 """Return the list of UUIDs whose geometry has been modified since the last
5232 markGeometryClean call.
5235 include_deleted: If True (default), include UUIDs that were deleted while
5236 dirty. If False, only return UUIDs that still exist.
5239 return context_wrapper.getDirtyUUIDsWrapper(self.
context, include_deleted)
5242 include_zero: bool =
True) -> List[int]:
5243 """Return the unique set of compound-object IDs that the given primitives
5247 uuids: List of primitive UUIDs to inspect.
5248 include_zero: If True (default), include the sentinel object ID 0
5249 (i.e., primitives with no parent object). If False, only return
5250 IDs of real compound objects.
5253 if not isinstance(uuids, (list, tuple)):
5254 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5255 return context_wrapper.getUniquePrimitiveParentObjectIDsWrapper(
5256 self.
context, list(uuids), include_zero
5262 """Return the area-weighted average normal of all primitives in the object."""
5264 x, y, z = context_wrapper.getObjectAverageNormalWrapper(self.
context, objID)
5265 return vec3(x, y, z)
5268 """Rotate the object so its area-weighted average normal aligns with
5269 new_normal. The rotation is applied about the given origin point."""
5271 if not isinstance(origin, vec3):
5272 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5273 if not isinstance(new_normal, vec3):
5274 raise ValueError(f
"new_normal must be a vec3, got {type(new_normal).__name__}")
5275 context_wrapper.setObjectAverageNormalWrapper(
5276 self.
context, objID, origin.to_list(), new_normal.to_list()
5280 """Translate the object so its origin is moved to the given point."""
5282 if not isinstance(origin, vec3):
5283 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5284 context_wrapper.setObjectOriginWrapper(self.
context, objID, origin.to_list())
5289 """Rotate a single primitive about the given origin so its azimuth
5290 equals new_azimuth (radians)."""
5292 if not isinstance(origin, vec3):
5293 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5294 context_wrapper.setPrimitiveAzimuthWrapper(
5295 self.
context, uuid, origin.to_list(), float(new_azimuth)
5299 """Rotate a single primitive about the given origin so its elevation
5300 equals new_elevation (radians)."""
5302 if not isinstance(origin, vec3):
5303 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5304 context_wrapper.setPrimitiveElevationWrapper(
5305 self.
context, uuid, origin.to_list(), float(new_elevation)
5310 def setTriangleVertices(self, uuid: int, vertex0: vec3, vertex1: vec3, vertex2: vec3) ->
None:
5311 """Replace the three vertices of an existing triangle primitive."""
5313 for name, v
in ((
"vertex0", vertex0), (
"vertex1", vertex1), (
"vertex2", vertex2)):
5314 if not isinstance(v, vec3):
5315 raise ValueError(f
"{name} must be a vec3, got {type(v).__name__}")
5316 context_wrapper.setTriangleVerticesWrapper(
5317 self.
context, uuid, vertex0.to_list(), vertex1.to_list(), vertex2.to_list()
5321 """Rotate one or more primitives so their normals align with new_normal.
5323 Accepts either a single UUID (int) or a list/tuple of UUIDs.
5324 The rotation is applied about the given origin point.
5327 if not isinstance(origin, vec3):
5328 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
5329 if not isinstance(new_normal, vec3):
5330 raise ValueError(f
"new_normal must be a vec3, got {type(new_normal).__name__}")
5331 if isinstance(uuids_or_uuid, (list, tuple)):
5332 context_wrapper.setPrimitiveNormalBatchWrapper(
5333 self.
context, list(uuids_or_uuid), origin.to_list(), new_normal.to_list()
5336 context_wrapper.setPrimitiveNormalWrapper(
5337 self.
context, uuids_or_uuid, origin.to_list(), new_normal.to_list()
5341 """Reassign one or more primitives to belong to the given compound object.
5343 Accepts either a single UUID (int) or a list/tuple of UUIDs. Pass objID=0
5344 to detach primitive(s) from any object.
5347 if isinstance(uuids_or_uuid, (list, tuple)):
5348 context_wrapper.setPrimitiveParentObjectIDBatchWrapper(
5349 self.
context, list(uuids_or_uuid), int(objID)
5352 context_wrapper.setPrimitiveParentObjectIDWrapper(
5363 def setMaterialDataInt(self, material_label: str, data_label: str, value: int) ->
None:
5364 """Set int data on a material. Affects all primitives that reference it."""
5366 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5369 """Set unsigned int data on a material."""
5371 context_wrapper.setMaterialDataUIntWrapper(self.
context, material_label, data_label, int(value))
5374 """Set float data on a material."""
5376 context_wrapper.setMaterialDataFloatWrapper(self.
context, material_label, data_label, float(value))
5379 """Set double-precision float data on a material."""
5381 context_wrapper.setMaterialDataDoubleWrapper(self.
context, material_label, data_label, float(value))
5384 """Set string data on a material."""
5386 context_wrapper.setMaterialDataStringWrapper(self.
context, material_label, data_label, str(value))
5389 """Set vec2 data on a material."""
5391 if not isinstance(value, vec2):
5392 raise ValueError(f
"value must be a vec2, got {type(value).__name__}")
5393 context_wrapper.setMaterialDataVec2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5396 """Set vec3 data on a material."""
5398 if not isinstance(value, vec3):
5399 raise ValueError(f
"value must be a vec3, got {type(value).__name__}")
5400 context_wrapper.setMaterialDataVec3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5403 """Set vec4 data on a material."""
5405 if not isinstance(value, vec4):
5406 raise ValueError(f
"value must be a vec4, got {type(value).__name__}")
5407 context_wrapper.setMaterialDataVec4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5410 """Set int2 data on a material."""
5412 if not isinstance(value, int2):
5413 raise ValueError(f
"value must be an int2, got {type(value).__name__}")
5414 context_wrapper.setMaterialDataInt2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5417 """Set int3 data on a material."""
5419 if not isinstance(value, int3):
5420 raise ValueError(f
"value must be an int3, got {type(value).__name__}")
5421 context_wrapper.setMaterialDataInt3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5424 """Set int4 data on a material."""
5426 if not isinstance(value, int4):
5427 raise ValueError(f
"value must be an int4, got {type(value).__name__}")
5428 context_wrapper.setMaterialDataInt4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5434 return context_wrapper.getMaterialDataIntWrapper(self.
context, material_label, data_label)
5438 return context_wrapper.getMaterialDataUIntWrapper(self.
context, material_label, data_label)
5442 return context_wrapper.getMaterialDataFloatWrapper(self.
context, material_label, data_label)
5446 return context_wrapper.getMaterialDataDoubleWrapper(self.
context, material_label, data_label)
5450 return context_wrapper.getMaterialDataStringWrapper(self.
context, material_label, data_label)
5454 x, y = context_wrapper.getMaterialDataVec2Wrapper(self.
context, material_label, data_label)
5459 x, y, z = context_wrapper.getMaterialDataVec3Wrapper(self.
context, material_label, data_label)
5464 x, y, z, w = context_wrapper.getMaterialDataVec4Wrapper(self.
context, material_label, data_label)
5469 x, y = context_wrapper.getMaterialDataInt2Wrapper(self.
context, material_label, data_label)
5474 x, y, z = context_wrapper.getMaterialDataInt3Wrapper(self.
context, material_label, data_label)
5479 x, y, z, w = context_wrapper.getMaterialDataInt4Wrapper(self.
context, material_label, data_label)
5483 """Return the HeliosDataType enum value for the given material data entry.
5485 Encoding (from Helios core): 0=INT, 1=UINT, 2=FLOAT, 3=DOUBLE,
5486 4=VEC2, 5=VEC3, 6=VEC4, 7=INT2, 8=INT3, 9=INT4, 10=STRING.
5489 return context_wrapper.getMaterialDataTypeWrapper(self.
context, material_label, data_label)
5493 def setMaterialData(self, material_label: str, data_label: str, value) ->
None:
5494 """Set material data with type detection from the Python value.
5496 Dispatches to the correct typed setter based on ``isinstance`` of ``value``.
5497 For unambiguous numeric width control (e.g., uint vs int), call the
5498 per-type method directly (``setMaterialDataUInt``, etc.).
5501 if isinstance(value, bool):
5503 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5504 elif isinstance(value, int):
5505 context_wrapper.setMaterialDataIntWrapper(self.
context, material_label, data_label, int(value))
5506 elif isinstance(value, float):
5507 context_wrapper.setMaterialDataFloatWrapper(self.
context, material_label, data_label, float(value))
5508 elif isinstance(value, str):
5509 context_wrapper.setMaterialDataStringWrapper(self.
context, material_label, data_label, value)
5510 elif isinstance(value, vec2):
5511 context_wrapper.setMaterialDataVec2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5512 elif isinstance(value, vec3):
5513 context_wrapper.setMaterialDataVec3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5514 elif isinstance(value, vec4):
5515 context_wrapper.setMaterialDataVec4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5516 elif isinstance(value, int2):
5517 context_wrapper.setMaterialDataInt2Wrapper(self.
context, material_label, data_label, value.x, value.y)
5518 elif isinstance(value, int3):
5519 context_wrapper.setMaterialDataInt3Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z)
5520 elif isinstance(value, int4):
5521 context_wrapper.setMaterialDataInt4Wrapper(self.
context, material_label, data_label, value.x, value.y, value.z, value.w)
5524 f
"Unsupported value type for setMaterialData: {type(value).__name__}. "
5525 f
"Supported: int, float, str, vec2, vec3, vec4, int2, int3, int4. "
5526 f
"For uint/double, call setMaterialDataUInt/Double directly."
5529 def getMaterialData(self, material_label: str, data_label: str, data_type: type =
None):
5530 """Get material data, auto-detecting the type from Helios storage if not specified.
5533 material_label: Name of the material.
5534 data_label: Data entry label.
5535 data_type: Optional Python type (int, float, str, vec2, vec3, vec4, int2,
5536 int3, int4) or string ('uint', 'double'). If ``None``, the type is
5537 queried via getMaterialDataType and dispatched automatically.
5540 if data_type
is None:
5541 t = context_wrapper.getMaterialDataTypeWrapper(self.
context, material_label, data_label)
5544 return context_wrapper.getMaterialDataIntWrapper(self.
context, material_label, data_label)
5546 return context_wrapper.getMaterialDataUIntWrapper(self.
context, material_label, data_label)
5548 return context_wrapper.getMaterialDataFloatWrapper(self.
context, material_label, data_label)
5550 return context_wrapper.getMaterialDataDoubleWrapper(self.
context, material_label, data_label)
5552 x, y = context_wrapper.getMaterialDataVec2Wrapper(self.
context, material_label, data_label)
5555 x, y, z = context_wrapper.getMaterialDataVec3Wrapper(self.
context, material_label, data_label)
5556 return vec3(x, y, z)
5558 x, y, z, w = context_wrapper.getMaterialDataVec4Wrapper(self.
context, material_label, data_label)
5559 return vec4(x, y, z, w)
5561 x, y = context_wrapper.getMaterialDataInt2Wrapper(self.
context, material_label, data_label)
5564 x, y, z = context_wrapper.getMaterialDataInt3Wrapper(self.
context, material_label, data_label)
5565 return int3(x, y, z)
5567 x, y, z, w = context_wrapper.getMaterialDataInt4Wrapper(self.
context, material_label, data_label)
5568 return int4(x, y, z, w)
5570 return context_wrapper.getMaterialDataStringWrapper(self.
context, material_label, data_label)
5571 raise ValueError(f
"Unknown HeliosDataType code: {t}")
5574 if data_type == int:
5576 if data_type == float:
5578 if data_type == str:
5580 if data_type ==
"uint":
5582 if data_type ==
"double":
5584 if data_type == vec2:
5586 if data_type == vec3:
5588 if data_type == vec4:
5590 if data_type == int2:
5592 if data_type == int3:
5594 if data_type == int4:
5597 f
"Unsupported material data type: {data_type}. Supported: int, float, str, "
5598 f
"vec2, vec3, vec4, int2, int3, int4, 'uint', 'double'."
5604 """Return the unique values stored under ``label`` across all primitives.
5606 Requires value caching to be enabled for ``label`` first via
5607 ``enablePrimitiveDataValueCaching(label)``. Supported ``dtype`` values:
5608 ``int``, ``str``, or the string ``'uint'``.
5612 return context_wrapper.getUniquePrimitiveDataValuesIntWrapper(self.
context, label)
5614 return context_wrapper.getUniquePrimitiveDataValuesUIntWrapper(self.
context, label)
5616 return context_wrapper.getUniquePrimitiveDataValuesStringWrapper(self.
context, label)
5618 f
"Unsupported dtype for getUniquePrimitiveDataValues: {dtype}. "
5619 f
"Supported: int, str, 'uint'."
5623 """Return the unique values stored under ``label`` across all compound objects.
5625 Requires value caching to be enabled for ``label`` first via
5626 ``enableObjectDataValueCaching(label)``. Supported ``dtype`` values:
5627 ``int``, ``str``, or the string ``'uint'``.
5631 return context_wrapper.getUniqueObjectDataValuesIntWrapper(self.
context, label)
5633 return context_wrapper.getUniqueObjectDataValuesUIntWrapper(self.
context, label)
5635 return context_wrapper.getUniqueObjectDataValuesStringWrapper(self.
context, label)
5637 f
"Unsupported dtype for getUniqueObjectDataValues: {dtype}. "
5638 f
"Supported: int, str, 'uint'."
5647 """Coerce a 4x4 transformation matrix input into a flat list of 16 floats.
5649 Accepts: numpy.ndarray of shape (4,4) or (16,), list/tuple of 16 floats,
5650 or nested list/tuple of shape (4,4). Helios stores transformation matrices
5651 in **row-major** order: T[i*4 + j] = element (i, j). A numpy ndarray of
5652 shape (4,4) maps directly via .ravel() since numpy is row-major by default.
5655 if isinstance(value, np.ndarray):
5656 if value.shape == (4, 4):
5657 return [float(v)
for v
in value.ravel().tolist()]
5658 if value.shape == (16,):
5659 return [float(v)
for v
in value.tolist()]
5661 f
"Matrix ndarray must have shape (4,4) or (16,), got {value.shape}"
5664 if isinstance(value, (list, tuple))
and len(value) == 4
and \
5665 all(isinstance(row, (list, tuple))
and len(row) == 4
for row
in value):
5668 flat.extend(float(v)
for v
in row)
5671 if isinstance(value, (list, tuple))
and len(value) == 16:
5672 return [float(v)
for v
in value]
5674 f
"Matrix must be a (4,4) ndarray, (16,) ndarray, list of 16 floats, "
5675 f
"or nested 4x4 list. Got: {type(value).__name__}"
5680 """Convert a flat list of 16 floats (row-major) to a (4,4) numpy ndarray."""
5681 return np.array(flat, dtype=np.float32).reshape((4, 4))
5686 """Return the object's 4x4 transformation matrix as a (4,4) float32 ndarray.
5688 Helios stores matrices in row-major order, so element (i, j) is at
5689 position [i, j] of the returned ndarray. The translation column is at
5690 positions [0, 3], [1, 3], [2, 3].
5693 flat = context_wrapper.getObjectTransformationMatrixWrapper(self.
context, int(objID))
5697 """Set the 4x4 transformation matrix on one or more compound objects.
5700 objIDs_or_objID: A single object ID (int) or a list/tuple of object IDs.
5701 T: A 4x4 matrix as numpy.ndarray((4,4) | (16,) float), list of 16 floats,
5702 or a nested 4x4 list. Row-major; T[i, j] is element (i, j).
5706 if isinstance(objIDs_or_objID, (list, tuple)):
5707 context_wrapper.setObjectTransformationMatrixBatchWrapper(
5708 self.
context, list(objIDs_or_objID), flat
5711 context_wrapper.setObjectTransformationMatrixWrapper(
5712 self.
context, int(objIDs_or_objID), flat
5716 """Return the primitive's 4x4 transformation matrix as a (4,4) float32 ndarray
5717 (row-major; see getObjectTransformationMatrix for layout details)."""
5719 flat = context_wrapper.getPrimitiveTransformationMatrixWrapper(self.
context, int(uuid))
5723 """Set the 4x4 transformation matrix on one or more primitives.
5726 uuids_or_uuid: A single UUID (int) or a list/tuple of UUIDs.
5727 T: A 4x4 matrix; see setObjectTransformationMatrix for accepted formats.
5731 if isinstance(uuids_or_uuid, (list, tuple)):
5732 context_wrapper.setPrimitiveTransformationMatrixBatchWrapper(
5733 self.
context, list(uuids_or_uuid), flat
5736 context_wrapper.setPrimitiveTransformationMatrixWrapper(
5737 self.
context, int(uuids_or_uuid), flat
5743 """Return the axis-aligned bounding box of the domain (or a UUID subset).
5746 uuids: Optional list of primitive UUIDs to restrict the computation to.
5747 If None (default), uses every primitive in the context.
5750 ``(xbounds, ybounds, zbounds)`` where each element is a ``vec2(min, max)``.
5754 xb, yb, zb = context_wrapper.getDomainBoundingBoxWrapper(self.
context)
5756 if not isinstance(uuids, (list, tuple)):
5757 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5758 xb, yb, zb = context_wrapper.getDomainBoundingBoxFilteredWrapper(self.
context, list(uuids))
5759 return (
vec2(xb[0], xb[1]),
vec2(yb[0], yb[1]),
vec2(zb[0], zb[1]))
5762 """Return the bounding sphere of the domain (or a UUID subset).
5765 ``(center, radius)`` where ``center`` is a ``vec3`` and ``radius`` is a float.
5769 center, radius = context_wrapper.getDomainBoundingSphereWrapper(self.
context)
5771 if not isinstance(uuids, (list, tuple)):
5772 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5773 center, radius = context_wrapper.getDomainBoundingSphereFilteredWrapper(self.
context, list(uuids))
5774 return (
vec3(center[0], center[1], center[2]), float(radius))
5782 def setTubeNodes(self, objID: int, nodes: List[vec3]) ->
None:
5783 """Replace the node positions of an existing tube object."""
5785 if not isinstance(nodes, (list, tuple)):
5786 raise ValueError(f
"nodes must be a list or tuple, got {type(nodes).__name__}")
5788 for i, n
in enumerate(nodes):
5789 if not isinstance(n, vec3):
5790 raise ValueError(f
"nodes[{i}] must be a vec3, got {type(n).__name__}")
5791 flat.extend([n.x, n.y, n.z])
5792 context_wrapper.setTubeNodesWrapper(self.
context, int(objID), flat)
5794 def setTubeRadii(self, objID: int, radii: List[float]) ->
None:
5795 """Replace the per-node radii of an existing tube object."""
5797 if not isinstance(radii, (list, tuple)):
5798 raise ValueError(f
"radii must be a list or tuple, got {type(radii).__name__}")
5799 context_wrapper.setTubeRadiiWrapper(self.
context, int(objID), [float(r)
for r
in radii])
5801 def scaleTubeGirth(self, objID: int, scale_factor: float) ->
None:
5802 """Scale the radii of an existing tube object by ``scale_factor``."""
5804 context_wrapper.scaleTubeGirthWrapper(self.
context, int(objID), float(scale_factor))
5807 """Scale the lengths between tube nodes by ``scale_factor``."""
5809 context_wrapper.scaleTubeLengthWrapper(self.
context, int(objID), float(scale_factor))
5812 """Remove all tube nodes from index ``node_index`` to the end."""
5814 context_wrapper.pruneTubeNodesWrapper(self.
context, int(objID), int(node_index))
5817 color: Optional[RGBcolor] =
None,
5818 texture_file: Optional[str] =
None,
5819 uv: Optional[vec2] =
None) ->
None:
5820 """Append a new segment to an existing tube object.
5822 Pass exactly one of ``color`` (an RGBcolor) or both ``texture_file`` and
5823 ``uv`` (a vec2 of texture u-fractions) to specify how the new segment
5827 if not isinstance(node_position, vec3):
5828 raise ValueError(f
"node_position must be a vec3, got {type(node_position).__name__}")
5829 has_color = color
is not None
5830 has_texture = texture_file
is not None or uv
is not None
5831 if has_color == has_texture:
5833 "appendTubeSegment requires exactly one of (color) or "
5834 "(texture_file and uv); cannot mix or omit both."
5837 if not isinstance(color, RGBcolor):
5838 raise ValueError(f
"color must be an RGBcolor, got {type(color).__name__}")
5839 context_wrapper.appendTubeSegmentColorWrapper(
5840 self.
context, int(objID), node_position.to_list(), float(radius),
5841 [color.r, color.g, color.b]
5844 if texture_file
is None or uv
is None:
5846 "appendTubeSegment with texture requires both texture_file and uv."
5848 if not isinstance(uv, vec2):
5849 raise ValueError(f
"uv must be a vec2, got {type(uv).__name__}")
5851 texture_file, [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp']
5853 context_wrapper.appendTubeSegmentTextureWrapper(
5854 self.
context, int(objID), node_position.to_list(), float(radius),
5855 tex_path, [uv.x, uv.y]
5861 """Group the given primitives into a new polymesh compound object and return its ID."""
5863 if not isinstance(uuids, (list, tuple)):
5864 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5866 raise ValueError(
"addPolymeshObject requires at least one UUID")
5867 return context_wrapper.addPolymeshObjectWrapper(self.
context, list(uuids))
5872 """Set the color of one or more compound objects.
5874 Accepts a single object ID or list/tuple of IDs. ``color`` must be an
5875 ``RGBcolor`` or ``RGBAcolor``.
5878 if isinstance(color, RGBAcolor):
5879 comps = [color.r, color.g, color.b, color.a]
5880 if isinstance(objIDs_or_objID, (list, tuple)):
5881 context_wrapper.setObjectColorRGBABatchWrapper(self.
context, list(objIDs_or_objID), comps)
5883 context_wrapper.setObjectColorRGBAWrapper(self.
context, int(objIDs_or_objID), comps)
5884 elif isinstance(color, RGBcolor):
5885 comps = [color.r, color.g, color.b]
5886 if isinstance(objIDs_or_objID, (list, tuple)):
5887 context_wrapper.setObjectColorRGBBatchWrapper(self.
context, list(objIDs_or_objID), comps)
5889 context_wrapper.setObjectColorRGBWrapper(self.
context, int(objIDs_or_objID), comps)
5892 f
"color must be an RGBcolor or RGBAcolor, got {type(color).__name__}"
5896 """Override the texture mapping with the object's vertex color."""
5898 if isinstance(objIDs_or_objID, (list, tuple)):
5899 context_wrapper.overrideObjectTextureColorBatchWrapper(self.
context, list(objIDs_or_objID))
5901 context_wrapper.overrideObjectTextureColorWrapper(self.
context, int(objIDs_or_objID))
5904 """Restore use of the texture color (undoes overrideObjectTextureColor)."""
5906 if isinstance(objIDs_or_objID, (list, tuple)):
5907 context_wrapper.useObjectTextureColorBatchWrapper(self.
context, list(objIDs_or_objID))
5909 context_wrapper.useObjectTextureColorWrapper(self.
context, int(objIDs_or_objID))
5914 """Mark one or more primitives as dirty (geometry has been modified)."""
5916 if isinstance(uuids_or_uuid, (list, tuple)):
5917 context_wrapper.markPrimitiveDirtyBatchWrapper(self.
context, list(uuids_or_uuid))
5919 context_wrapper.markPrimitiveDirtyWrapper(self.
context, int(uuids_or_uuid))
5922 """Mark one or more primitives as clean (cancels dirty state)."""
5924 if isinstance(uuids_or_uuid, (list, tuple)):
5925 context_wrapper.markPrimitiveCleanBatchWrapper(self.
context, list(uuids_or_uuid))
5927 context_wrapper.markPrimitiveCleanWrapper(self.
context, int(uuids_or_uuid))
5932 """Set the (Nx, Ny) subdivision count of one or more tile objects.
5934 The Helios C++ API is batch-only; a single objID is wrapped as a
5935 single-element list.
5938 if not isinstance(subdiv, int2):
5939 raise ValueError(f
"subdiv must be an int2, got {type(subdiv).__name__}")
5940 if isinstance(objIDs_or_objID, (list, tuple)):
5941 ids = list(objIDs_or_objID)
5943 ids = [int(objIDs_or_objID)]
5944 context_wrapper.setTileObjectSubdivisionCountWrapper(
5945 self.
context, ids, int(subdiv.x), int(subdiv.y)
5949 """Set tile object subdivision dynamically based on a target area ratio.
5951 ``area_ratio`` is the approximate ratio between the whole tile's area and an
5952 individual sub-patch's area, so each tile is subdivided into roughly
5953 ``area_ratio`` sub-patches. It must be >= 1 (a sub-patch cannot be larger than
5954 the tile). The tile's position, size, and orientation are preserved.
5959 f
"area_ratio must be >= 1 (it is the ratio of the whole tile area to an "
5960 f
"individual sub-patch area), got {area_ratio}"
5962 if isinstance(objIDs_or_objID, (list, tuple)):
5963 ids = list(objIDs_or_objID)
5965 ids = [int(objIDs_or_objID)]
5966 context_wrapper.setTileObjectSubdivisionByAreaRatioWrapper(
5977 """Return a new list with deleted UUIDs removed; the input list is not mutated.
5979 This mirrors the convention used by ``cropDomain``, which returns the
5980 survivors rather than mutating in place.
5983 if not isinstance(uuids, (list, tuple)):
5984 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
5985 return context_wrapper.cleanDeletedUUIDsWrapper(self.
context, list(uuids))
5988 """Return a new list with deleted object IDs removed; input is not mutated."""
5990 if not isinstance(objIDs, (list, tuple)):
5991 raise ValueError(f
"objIDs must be a list or tuple, got {type(objIDs).__name__}")
5992 return context_wrapper.cleanDeletedObjectIDsWrapper(self.
context, list(objIDs))
5996 def writeXML(self, filename: str, uuids: Optional[List[int]] =
None, quiet: bool =
False) ->
None:
5997 """Write the context (or a UUID subset) to an XML file.
6000 filename: Output file path. Must end in .xml.
6001 uuids: Optional list of primitive UUIDs to restrict the export. If
6002 None (default), all primitives are written.
6003 quiet: Suppress informational console output.
6008 context_wrapper.writeXMLWrapper(self.
context, path, bool(quiet))
6010 if not isinstance(uuids, (list, tuple)):
6011 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
6012 context_wrapper.writeXMLFilteredWrapper(self.
context, path, list(uuids), bool(quiet))
6014 def writeXML_byobject(self, filename: str, objIDs: List[int], quiet: bool =
False) ->
None:
6015 """Write a subset of compound objects to an XML file."""
6018 if not isinstance(objIDs, (list, tuple)):
6019 raise ValueError(f
"objIDs must be a list or tuple, got {type(objIDs).__name__}")
6020 context_wrapper.writeXMLByObjectWrapper(self.
context, path, list(objIDs), bool(quiet))
6024 def randu(self, low=None, high=None):
6025 """Draw a uniform random number using the Context's RNG.
6028 ``randu()`` -> float in [0, 1)
6029 ``randu(low: float, high: float)`` -> float in [low, high)
6030 ``randu(low: int, high: int)`` -> int in [low, high]
6032 Whether the integer or float overload is invoked is determined by
6033 ``isinstance(low, int)``; pass ``low/high`` as Python ints for the
6037 if low
is None and high
is None:
6038 return context_wrapper.randuBasicWrapper(self.
context)
6039 if low
is None or high
is None:
6040 raise ValueError(
"randu requires both low and high, or neither.")
6041 if isinstance(low, bool)
or isinstance(high, bool):
6042 raise ValueError(
"randu bounds cannot be bool.")
6045 if isinstance(low, int)
and isinstance(high, int):
6046 return context_wrapper.randuIntRangeWrapper(self.
context, low, high)
6047 return context_wrapper.randuRangeWrapper(self.
context, float(low), float(high))
6049 def randn(self, mean=None, stddev=None) -> float:
6050 """Draw a normal random number using the Context's RNG.
6053 ``randn()`` -> standard normal (mean 0, stddev 1)
6054 ``randn(mean: float, stddev: float)`` -> N(mean, stddev**2)
6057 if mean
is None and stddev
is None:
6058 return context_wrapper.randnBasicWrapper(self.
context)
6059 if mean
is None or stddev
is None:
6060 raise ValueError(
"randn requires both mean and stddev, or neither.")
6061 return context_wrapper.randnParamsWrapper(self.
context, float(mean), float(stddev))
6065 def setLocation(self, location_or_lat, longitude=None, utc_offset=None, altitude=0.0) -> None:
6066 """Set the geographic location used by solar/radiation calculations.
6069 ``setLocation(loc: Location)``
6070 ``setLocation(latitude_deg: float, longitude_deg: float, utc_offset: float, altitude=0.0)``
6072 ``altitude`` is the height of the local Cartesian origin in meters above
6073 sea level. It is only used in the (lat, lon, utc) float form; when passing
6074 a ``Location`` object, the location's own altitude is used.
6077 if isinstance(location_or_lat, Location):
6078 if longitude
is not None or utc_offset
is not None or altitude != 0.0:
6079 raise ValueError(
"When passing a Location, do not also pass longitude/utc_offset/altitude; "
6080 "set them on the Location object instead.")
6081 loc = location_or_lat
6083 if longitude
is None or utc_offset
is None:
6085 "setLocation requires either a Location object or "
6086 "(latitude_deg, longitude_deg, utc_offset) as 3 floats."
6088 loc =
Location(float(location_or_lat), float(longitude), float(utc_offset), float(altitude))
6089 context_wrapper.setLocationWrapper(self.
context, loc.latitude, loc.longitude, loc.utc_offset, loc.altitude)
6092 """Return the Context's currently-configured geographic location."""
6094 lat, lon, utc, alt = context_wrapper.getLocationWrapper(self.
context)
6095 return Location(lat, lon, utc, alt)
6102 """Generate a colormap with ``n_colors`` entries from a named colormap.
6105 name: Helios colormap name (e.g., "hot", "cool", "lava", "rainbow").
6106 n_colors: Number of colors in the returned ramp.
6109 A list of ``RGBcolor`` instances of length ``n_colors``.
6112 flat = context_wrapper.generateColormapNamedWrapper(self.
context, name, int(n_colors))
6113 return [
RGBcolor(flat[i*3 + 0], flat[i*3 + 1], flat[i*3 + 2])
for i
in range(int(n_colors))]
6116 """Generate one texture file per color in ``colormap`` derived from
6117 ``texture_file``. Returns the list of generated file paths.
6120 if not isinstance(colormap, (list, tuple)):
6121 raise ValueError(f
"colormap must be a list or tuple, got {type(colormap).__name__}")
6123 for i, c
in enumerate(colormap):
6124 if not isinstance(c, RGBcolor):
6125 raise ValueError(f
"colormap[{i}] must be an RGBcolor, got {type(c).__name__}")
6126 flat.extend([c.r, c.g, c.b])
6129 texture_file, [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp']
6131 return context_wrapper.generateTexturesFromColormapWrapper(
6132 self.
context, validated_path, flat
6136 """Return the primitive's texture transparency mask as a 2D bool ndarray.
6138 Returns None if the primitive has no associated transparency channel
6139 (e.g., it is untextured or its texture has no alpha). The returned
6140 ndarray has shape (height, width) and dtype ``bool``.
6143 result = context_wrapper.getPrimitiveTextureTransparencyDataWrapper(self.
context, int(uuid))
6146 width, height, flat = result
6147 return np.array(flat, dtype=bool).reshape((height, width))
6151 """Raise if `context`'s native Context has already been destroyed.
6153 Plugin models pass ``context.getNativePtr()`` to a C++ constructor that
6154 stores the raw pointer for the lifetime of the model. Destroying the
6155 Context (via ``__exit__``, ``__del__``, or garbage collection) frees that
6156 memory without invalidating the model's copy, so any later call
6157 dereferences freed memory and segfaults.
6159 Models must hold a Python reference to the owning Context (keeping it
6160 alive) and call this before every native call (turning an explicit close
6161 into an actionable error instead of a crash).
6164 context: The Context the model was constructed from.
6165 owner_name: Class name of the calling model, used in the message.
6168 RuntimeError: If the Context has been destroyed.
6170 if context
is None or getattr(context,
'context',
None)
is None:
6172 f
"{owner_name} is bound to a Context that has already been destroyed.\n"
6173 "The native Context was freed while this model still referenced it; "
6174 "continuing would dereference freed memory and crash the interpreter.\n"
6176 "This usually means the model outlived its Context's 'with' block:\n"
6177 " with Context() as context:\n"
6178 f
" model = {owner_name}(context)\n"
6179 " model.run() # <-- Context already destroyed here\n"
6181 f
"Fix: keep all {owner_name} usage inside the Context's 'with' block, "
6182 "or create the Context without a 'with' statement so it lives as long "
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.
None check_context_alive('Context' context, str owner_name)
Raise if context's native Context has already been destroyed.