2from dataclasses
import dataclass
3from typing
import List, Optional, Union
8from .wrappers
import UContextWrapper
as context_wrapper
9from .wrappers.DataTypes
import vec2, vec3, vec4, int2, int3, int4, SphericalCoord, RGBcolor, RGBAcolor, PrimitiveType, Date, Time
10from .plugins.loader
import LibraryLoadError, validate_library, get_library_info
11from .plugins.registry
import get_plugin_registry
12from .validation.geometry
import (
13 validate_patch_params, validate_triangle_params, validate_sphere_params,
14 validate_tube_params, validate_box_params
21 Physical properties and geometry information for a primitive.
22 This is separate from primitive data (user-defined key-value pairs).
25 primitive_type: PrimitiveType
30 centroid: Optional[vec3] =
None
31 texture_file: Optional[str] =
None
32 texture_uv: Optional[List[vec2]] =
None
33 solid_fraction: Optional[float] =
None
36 """Calculate centroid from vertices if not provided."""
39 total_x = sum(v.x
for v
in self.
vertices)
40 total_y = sum(v.y
for v
in self.
vertices)
41 total_z = sum(v.z
for v
in self.
vertices)
43 self.
centroid =
vec3(total_x / count, total_y / count, total_z / count)
48 Central simulation environment for PyHelios that manages 3D primitives and their data.
50 The Context class provides methods for:
51 - Creating geometric primitives (patches, triangles)
52 - Creating compound geometry (tiles, spheres, tubes, boxes)
53 - Loading 3D models from files (PLY, OBJ, XML)
54 - Managing primitive data (flexible key-value storage)
55 - Querying primitive properties and collections
56 - Batch operations on multiple primitives
59 - UUID-based primitive tracking
60 - Comprehensive primitive data system with auto-type detection
61 - Efficient array-based data retrieval via getPrimitiveDataArray()
62 - Cross-platform compatibility with mock mode support
63 - Context manager protocol for resource cleanup
66 >>> with Context() as context:
67 ... # Create primitives
68 ... patch_uuid = context.addPatch(center=vec3(0, 0, 0))
69 ... triangle_uuid = context.addTriangle(vec3(0,0,0), vec3(1,0,0), vec3(0.5,1,0))
71 ... # Set primitive data
72 ... context.setPrimitiveDataFloat(patch_uuid, "temperature", 25.5)
73 ... context.setPrimitiveDataFloat(triangle_uuid, "temperature", 30.2)
75 ... # Get data efficiently as NumPy array
76 ... temps = context.getPrimitiveDataArray([patch_uuid, triangle_uuid], "temperature")
77 ... print(temps) # [25.5 30.2]
88 library_info = get_library_info()
89 if library_info.get(
'is_mock',
False):
91 print(
"Warning: PyHelios running in development mock mode - functionality is limited")
92 print(
"Available plugins: None (mock mode)")
99 if not validate_library():
100 raise LibraryLoadError(
101 "Native Helios library validation failed. Some required functions are missing. "
102 "Try rebuilding the native library: build_scripts/build_helios"
104 except LibraryLoadError:
106 except Exception
as e:
107 raise LibraryLoadError(
108 f
"Failed to validate native Helios library: {e}. "
109 f
"To enable development mode without native libraries, set PYHELIOS_DEV_MODE=1"
114 self.
context = context_wrapper.createContext()
117 raise LibraryLoadError(
118 "Failed to create Helios context. Native library may not be functioning correctly."
123 except Exception
as e:
125 raise LibraryLoadError(
126 f
"Failed to create Helios context: {e}. "
127 f
"Ensure native libraries are built and accessible."
131 """Helper method to check if context is available with detailed error messages."""
136 "Context is in mock mode - native functionality not available.\n"
137 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
141 "Context has been cleaned up and is no longer usable.\n"
142 "This usually means you're trying to use a Context outside its 'with' statement scope.\n"
144 "Fix: Ensure all Context usage is inside the 'with Context() as context:' block:\n"
145 " with Context() as context:\n"
146 " # All context operations must be here\n"
147 " with SomePlugin(context) as plugin:\n"
148 " plugin.do_something()\n"
149 " with Visualizer() as vis:\n"
150 " vis.buildContextGeometry(context) # Still inside Context scope\n"
151 " # Context is cleaned up here - cannot use context after this point"
155 "Context creation failed - native functionality not available.\n"
156 "Build native libraries with 'python build_scripts/build_helios.py'"
161 f
"Context is not available (state: {self._lifecycle_state}).\n"
162 "Build native libraries with 'python build_scripts/build_helios.py' or set PYHELIOS_DEV_MODE=1 for development."
166 """Validate that a UUID exists in this context.
169 uuid: The UUID to validate
172 RuntimeError: If UUID is invalid or doesn't exist in context
175 if not isinstance(uuid, int)
or uuid < 0:
176 raise RuntimeError(f
"Invalid UUID: {uuid}. UUIDs must be non-negative integers.")
181 if uuid
not in valid_uuids:
182 raise RuntimeError(f
"UUID {uuid} does not exist in context. Valid UUIDs: {valid_uuids[:10]}{'...' if len(valid_uuids) > 10 else ''}")
192 def _validate_file_path(self, filename: str, expected_extensions: List[str] =
None) -> str:
193 """Validate and normalize file path for security.
196 filename: File path to validate
197 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
200 Normalized absolute path
204 ValueError: If path is invalid or potentially dangerous
205 FileNotFoundError: If file does not exist
210 abs_path = os.path.abspath(filename)
214 normalized_path = os.path.normpath(abs_path)
215 if abs_path != normalized_path:
216 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
219 if expected_extensions:
220 file_ext = os.path.splitext(abs_path)[1].lower()
221 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
222 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
225 if not os.path.exists(abs_path):
226 raise FileNotFoundError(f
"File not found: {abs_path}")
229 if not os.path.isfile(abs_path):
230 raise ValueError(f
"Path is not a file: {abs_path}")
235 """Validate and normalize output file path for security.
238 filename: Output file path to validate
239 expected_extensions: List of allowed file extensions (e.g., ['.ply', '.obj'])
242 Normalized absolute path
245 ValueError: If path is invalid or potentially dangerous
246 PermissionError: If output directory is not writable
251 if not filename
or not filename.strip():
252 raise ValueError(
"Filename cannot be empty")
255 abs_path = os.path.abspath(filename)
258 normalized_path = os.path.normpath(abs_path)
259 if abs_path != normalized_path:
260 raise ValueError(f
"Invalid file path (potential path traversal): {filename}")
263 if expected_extensions:
264 file_ext = os.path.splitext(abs_path)[1].lower()
265 if file_ext
not in [ext.lower()
for ext
in expected_extensions]:
266 raise ValueError(f
"Invalid file extension '{file_ext}'. Expected one of: {expected_extensions}")
269 output_dir = os.path.dirname(abs_path)
270 if not os.path.exists(output_dir):
271 raise ValueError(f
"Output directory does not exist: {output_dir}")
272 if not os.access(output_dir, os.W_OK):
273 raise PermissionError(f
"Output directory is not writable: {output_dir}")
280 def __exit__(self, exc_type, exc_value, traceback):
282 context_wrapper.destroyContext(self.
context)
287 """Destructor to ensure C++ resources freed even without 'with' statement."""
288 if hasattr(self,
'context')
and self.
context is not None:
290 context_wrapper.destroyContext(self.
context)
293 except Exception
as e:
295 warnings.warn(f
"Error in Context.__del__: {e}")
303 context_wrapper.markGeometryClean(self.
context)
307 context_wrapper.markGeometryDirty(self.
context)
312 return context_wrapper.isGeometryDirty(self.
context)
316 Seed the random number generator for reproducible stochastic results.
319 seed: Integer seed value for random number generation
322 This is critical for reproducible results in stochastic simulations
323 (e.g., LiDAR scans with beam divergence, random perturbations).
326 context_wrapper.helios_lib.seedRandomGenerator(self.
context, seed)
328 @validate_patch_params
329 def addPatch(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1), rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None) -> int:
334 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
335 return context_wrapper.addPatchWithCenterSizeRotationAndColor(self.
context, center.to_list(), size.to_list(), rotation_list, color.to_list())
338 rotation: Optional[SphericalCoord] =
None,
339 uv_center: Optional[vec2] =
None,
340 uv_size: Optional[vec2] =
None) -> int:
341 """Add a textured patch primitive to the context.
343 Creates a rectangular patch with a texture image mapped to its surface.
346 center: 3D position of the patch center
347 size: Width and height of the patch
348 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
349 rotation: Optional spherical rotation (defaults to no rotation)
350 uv_center: Optional UV center of texture map (required if uv_size is provided)
351 uv_size: Optional UV size of texture map (required if uv_center is provided)
354 UUID of the created textured patch primitive
357 ValueError: If arguments have wrong types or UV params are partially specified
358 FileNotFoundError: If texture file doesn't exist
359 RuntimeError: If context is in mock mode
362 >>> context = Context()
363 >>> uuid = context.addPatchTextured(
364 ... center=vec3(0, 0, 0),
366 ... texture_file="texture.png"
371 if not isinstance(center, vec3):
372 raise ValueError(f
"center must be a vec3, got {type(center).__name__}")
373 if not isinstance(size, vec2):
374 raise ValueError(f
"size must be a vec2, got {type(size).__name__}")
375 if not isinstance(texture_file, str):
376 raise ValueError(f
"texture_file must be a str, got {type(texture_file).__name__}")
377 if rotation
is not None and not isinstance(rotation, SphericalCoord):
378 raise ValueError(f
"rotation must be a SphericalCoord, got {type(rotation).__name__}")
380 if (uv_center
is None) != (uv_size
is None):
381 raise ValueError(
"uv_center and uv_size must both be provided or both omitted")
382 if uv_center
is not None and not isinstance(uv_center, vec2):
383 raise ValueError(f
"uv_center must be a vec2, got {type(uv_center).__name__}")
384 if uv_size
is not None and not isinstance(uv_size, vec2):
385 raise ValueError(f
"uv_size must be a vec2, got {type(uv_size).__name__}")
388 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
391 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
393 if uv_center
is not None:
394 return context_wrapper.addPatchWithTextureAndUV(
395 self.
context, center.to_list(), size.to_list(), rotation_list,
396 validated_texture_file, uv_center.to_list(), uv_size.to_list()
399 return context_wrapper.addPatchWithTexture(
400 self.
context, center.to_list(), size.to_list(), rotation_list,
401 validated_texture_file
404 @validate_triangle_params
405 def addTriangle(self, vertex0: vec3, vertex1: vec3, vertex2: vec3, color: Optional[RGBcolor] =
None) -> int:
406 """Add a triangle primitive to the context
409 vertex0: First vertex of the triangle
410 vertex1: Second vertex of the triangle
411 vertex2: Third vertex of the triangle
412 color: Optional triangle color (defaults to white)
415 UUID of the created triangle primitive
419 return context_wrapper.addTriangle(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list())
421 return context_wrapper.addTriangleWithColor(self.
context, vertex0.to_list(), vertex1.to_list(), vertex2.to_list(), color.to_list())
424 texture_file: str, uv0: vec2, uv1: vec2, uv2: vec2) -> int:
425 """Add a textured triangle primitive to the context
427 Creates a triangle with texture mapping. The texture image is mapped to the triangle
428 surface using UV coordinates, where (0,0) represents the top-left corner of the image
429 and (1,1) represents the bottom-right corner.
432 vertex0: First vertex of the triangle
433 vertex1: Second vertex of the triangle
434 vertex2: Third vertex of the triangle
435 texture_file: Path to texture image file (supports PNG, JPG, JPEG, TGA, BMP)
436 uv0: UV texture coordinates for first vertex
437 uv1: UV texture coordinates for second vertex
438 uv2: UV texture coordinates for third vertex
441 UUID of the created textured triangle primitive
444 ValueError: If texture file path is invalid
445 FileNotFoundError: If texture file doesn't exist
446 RuntimeError: If context is in mock mode
449 >>> context = Context()
450 >>> # Create a textured triangle
451 >>> vertex0 = vec3(0, 0, 0)
452 >>> vertex1 = vec3(1, 0, 0)
453 >>> vertex2 = vec3(0.5, 1, 0)
454 >>> uv0 = vec2(0, 0) # Bottom-left of texture
455 >>> uv1 = vec2(1, 0) # Bottom-right of texture
456 >>> uv2 = vec2(0.5, 1) # Top-center of texture
457 >>> uuid = context.addTriangleTextured(vertex0, vertex1, vertex2,
458 ... "texture.png", uv0, uv1, uv2)
463 for name, val
in [(
"vertex0", vertex0), (
"vertex1", vertex1), (
"vertex2", vertex2)]:
464 if not isinstance(val, vec3):
465 raise ValueError(f
"{name} must be a vec3, got {type(val).__name__}")
466 for name, val
in [(
"uv0", uv0), (
"uv1", uv1), (
"uv2", uv2)]:
467 if not isinstance(val, vec2):
468 raise ValueError(f
"{name} must be a vec2, got {type(val).__name__}")
472 [
'.png',
'.jpg',
'.jpeg',
'.tga',
'.bmp'])
475 return context_wrapper.addTriangleWithTexture(
477 vertex0.to_list(), vertex1.to_list(), vertex2.to_list(),
478 validated_texture_file,
479 uv0.to_list(), uv1.to_list(), uv2.to_list()
483 """Get the type of a primitive or multiple primitives.
486 uuid: Single UUID (int) or list of UUIDs
489 PrimitiveType for single UUID, or np.ndarray of shape (N,) uint32 for list
492 if isinstance(uuid, (list, tuple)):
494 return np.empty((0,), dtype=np.uint32)
495 ptr, size = context_wrapper.getBatchPrimitiveTypes(self.
context, uuid)
496 if size == 0
or not ptr:
497 return np.empty((0,), dtype=np.uint32)
498 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
499 primitive_type = context_wrapper.getPrimitiveType(self.
context, uuid)
503 """Get the area of a primitive or multiple primitives.
506 uuid: Single UUID (int) or list of UUIDs
509 float for single UUID, or np.ndarray of shape (N,) for list
512 if isinstance(uuid, (list, tuple)):
514 return np.empty((0,), dtype=np.float32)
515 ptr, size = context_wrapper.getBatchPrimitiveAreas(self.
context, uuid)
516 if size == 0
or not ptr:
517 return np.empty((0,), dtype=np.float32)
518 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
519 return context_wrapper.getPrimitiveArea(self.
context, uuid)
522 """Get the normal vector of a primitive or multiple primitives.
525 uuid: Single UUID (int) or list of UUIDs
528 vec3 for single UUID, or np.ndarray of shape (N, 3) for list
531 if isinstance(uuid, (list, tuple)):
533 return np.empty((0, 3), dtype=np.float32)
534 ptr, size = context_wrapper.getBatchPrimitiveNormals(self.
context, uuid)
535 if size == 0
or not ptr:
536 return np.empty((0, 3), dtype=np.float32)
537 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
538 normal_ptr = context_wrapper.getPrimitiveNormal(self.
context, uuid)
539 return vec3(normal_ptr[0], normal_ptr[1], normal_ptr[2])
542 """Get vertices of a primitive or multiple primitives.
545 uuid: Single UUID (int) or list of UUIDs
548 List[vec3] for single UUID, or tuple of (flat_data, offsets) for list
549 where flat_data is a float32 ndarray and offsets is a uint32 ndarray
550 of length N+1. Vertices for primitive i are at
551 flat_data[offsets[i]:offsets[i+1]].
554 if isinstance(uuid, (list, tuple)):
556 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
557 ptr, offsets, total = context_wrapper.getBatchPrimitiveVertices(self.
context, uuid)
558 offsets_arr = np.array(offsets, dtype=np.uint32)
559 if total == 0
or not ptr:
560 return (np.empty((0,), dtype=np.float32), offsets_arr)
561 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
562 return (data, offsets_arr)
563 size = ctypes.c_uint()
564 vertices_ptr = context_wrapper.getPrimitiveVertices(self.
context, uuid, ctypes.byref(size))
566 vertices_list = ctypes.cast(vertices_ptr, ctypes.POINTER(ctypes.c_float * size.value)).contents
567 vertices = [
vec3(vertices_list[i], vertices_list[i+1], vertices_list[i+2])
for i
in range(0, size.value, 3)]
571 """Get the color of a primitive or multiple primitives.
574 uuid: Single UUID (int) or list of UUIDs
577 RGBcolor for single UUID, or np.ndarray of shape (N, 3) for list
580 if isinstance(uuid, (list, tuple)):
582 return np.empty((0, 3), dtype=np.float32)
583 ptr, size = context_wrapper.getBatchPrimitiveColors(self.
context, uuid)
584 if size == 0
or not ptr:
585 return np.empty((0, 3), dtype=np.float32)
586 return np.ctypeslib.as_array(ptr, shape=(size,)).copy().reshape(-1, 3)
587 color_ptr = context_wrapper.getPrimitiveColor(self.
context, uuid)
588 return RGBcolor(color_ptr[0], color_ptr[1], color_ptr[2])
592 return context_wrapper.getPrimitiveCount(self.
context)
595 """Check if a primitive exists for a given UUID or list of UUIDs.
598 uuid: A single UUID (int) or a list of UUIDs.
601 True if the primitive(s) exist, False otherwise.
602 For a list, returns True only if ALL primitives exist.
605 if isinstance(uuid, (list, tuple)):
606 arr = (ctypes.c_uint * len(uuid))(*uuid)
607 return context_wrapper.doesPrimitiveExistBatch(self.
context, arr, len(uuid))
608 return context_wrapper.doesPrimitiveExist(self.
context, uuid)
612 size = ctypes.c_uint()
613 uuids_ptr = context_wrapper.getAllUUIDs(self.
context, ctypes.byref(size))
614 return list(uuids_ptr[:size.value])
618 return context_wrapper.getObjectCount(self.
context)
622 size = ctypes.c_uint()
623 objectids_ptr = context_wrapper.getAllObjectIDs(self.
context, ctypes.byref(size))
624 return list(objectids_ptr[:size.value])
628 Get physical properties and geometry information for a single primitive.
631 uuid: UUID of the primitive
634 PrimitiveInfo object containing physical properties and geometry
644 solid_fraction =
None
658 primitive_type=primitive_type,
663 texture_file=texture_file,
664 texture_uv=texture_uv,
665 solid_fraction=solid_fraction,
670 Get physical properties and geometry information for all primitives in the context.
673 List of PrimitiveInfo objects for all primitives
680 Get physical properties and geometry information for all primitives belonging to a specific object.
683 object_id: ID of the object
686 List of PrimitiveInfo objects for primitives in the object
688 object_uuids = context_wrapper.getObjectPrimitiveUUIDs(self.
context, object_id)
692 def addTile(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
693 rotation: Optional[SphericalCoord] =
None, subdiv: int2 =
int2(1, 1),
694 color: Optional[RGBcolor] =
None) -> List[int]:
696 Add a subdivided patch (tile) to the context.
698 A tile is a patch subdivided into a regular grid of smaller patches,
699 useful for creating detailed surfaces or terrain.
702 center: 3D coordinates of tile center (default: origin)
703 size: Width and height of the tile (default: 1x1)
704 rotation: Orientation of the tile (default: no rotation)
705 subdiv: Number of subdivisions in x and y directions (default: 1x1)
706 color: Color of the tile (default: white)
709 List of UUIDs for all patches created in the tile
712 >>> context = Context()
713 >>> # Create a 2x2 meter tile subdivided into 4x4 patches
714 >>> tile_uuids = context.addTile(
715 ... center=vec3(0, 0, 1),
717 ... subdiv=int2(4, 4),
718 ... color=RGBcolor(0.5, 0.8, 0.2)
720 >>> print(f"Created {len(tile_uuids)} patches")
725 if not isinstance(center, vec3):
726 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
727 if not isinstance(size, vec2):
728 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
729 if rotation
is not None and not isinstance(rotation, SphericalCoord):
730 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
731 if not isinstance(subdiv, int2):
732 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
733 if color
is not None and not isinstance(color, RGBcolor):
734 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
737 if any(s <= 0
for s
in size.to_list()):
738 raise ValueError(
"All size dimensions must be positive")
739 if any(s <= 0
for s
in subdiv.to_list()):
740 raise ValueError(
"All subdivision counts must be positive")
746 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
748 if color
and not (color.r == 1.0
and color.g == 1.0
and color.b == 1.0):
749 return context_wrapper.addTileWithColor(
750 self.
context, center.to_list(), size.to_list(),
751 rotation_list, subdiv.to_list(), color.to_list()
754 return context_wrapper.addTile(
755 self.
context, center.to_list(), size.to_list(),
756 rotation_list, subdiv.to_list()
759 @validate_sphere_params
760 def addSphere(self, center: vec3 =
vec3(0, 0, 0), radius: float = 1.0,
761 ndivs: int = 10, color: Optional[RGBcolor] =
None) -> List[int]:
763 Add a sphere to the context.
765 The sphere is tessellated into triangular faces based on the specified
769 center: 3D coordinates of sphere center (default: origin)
770 radius: Radius of the sphere (default: 1.0)
771 ndivs: Number of divisions for tessellation (default: 10)
772 Higher values create smoother spheres but more triangles
773 color: Color of the sphere (default: white)
776 List of UUIDs for all triangles created in the sphere
779 >>> context = Context()
780 >>> # Create a red sphere at (1, 2, 3) with radius 0.5
781 >>> sphere_uuids = context.addSphere(
782 ... center=vec3(1, 2, 3),
785 ... color=RGBcolor(1, 0, 0)
787 >>> print(f"Created sphere with {len(sphere_uuids)} triangles")
792 if not isinstance(center, vec3):
793 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
794 if not isinstance(radius, (int, float)):
795 raise ValueError(f
"Radius must be a number, got {type(radius).__name__}")
796 if not isinstance(ndivs, int):
797 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
798 if color
is not None and not isinstance(color, RGBcolor):
799 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
803 raise ValueError(
"Sphere radius must be positive")
805 raise ValueError(
"Number of divisions must be at least 3")
808 return context_wrapper.addSphereWithColor(
809 self.
context, ndivs, center.to_list(), radius, color.to_list()
812 return context_wrapper.addSphere(
813 self.
context, ndivs, center.to_list(), radius
816 @validate_tube_params
817 def addTube(self, nodes: List[vec3], radii: Union[float, List[float]],
818 ndivs: int = 6, colors: Optional[Union[RGBcolor, List[RGBcolor]]] =
None) -> List[int]:
820 Add a tube (pipe/cylinder) to the context.
822 The tube is defined by a series of nodes (path) with radius at each node.
823 It's tessellated into triangular faces based on the number of radial divisions.
826 nodes: List of 3D points defining the tube path (at least 2 nodes)
827 radii: Radius at each node. Can be:
828 - Single float: constant radius for all nodes
829 - List of floats: radius for each node (must match nodes length)
830 ndivs: Number of radial divisions (default: 6)
831 Higher values create smoother tubes but more triangles
832 colors: Colors at each node. Can be:
834 - Single RGBcolor: constant color for all nodes
835 - List of RGBcolor: color for each node (must match nodes length)
838 List of UUIDs for all triangles created in the tube
841 >>> context = Context()
842 >>> # Create a curved tube with varying radius
843 >>> nodes = [vec3(0, 0, 0), vec3(1, 0, 0), vec3(2, 1, 0)]
844 >>> radii = [0.1, 0.2, 0.1]
845 >>> colors = [RGBcolor(1, 0, 0), RGBcolor(0, 1, 0), RGBcolor(0, 0, 1)]
846 >>> tube_uuids = context.addTube(nodes, radii, ndivs=8, colors=colors)
847 >>> print(f"Created tube with {len(tube_uuids)} triangles")
852 if not isinstance(nodes, (list, tuple)):
853 raise ValueError(f
"Nodes must be a list or tuple, got {type(nodes).__name__}")
854 if not isinstance(ndivs, int):
855 raise ValueError(f
"Ndivs must be an integer, got {type(ndivs).__name__}")
856 if colors
is not None and not isinstance(colors, (RGBcolor, list, tuple)):
857 raise ValueError(f
"Colors must be RGBcolor, list, tuple, or None, got {type(colors).__name__}")
861 raise ValueError(
"Tube requires at least 2 nodes")
863 raise ValueError(
"Number of radial divisions must be at least 3")
866 if isinstance(radii, (int, float)):
867 radii_list = [float(radii)] * len(nodes)
869 radii_list = [float(r)
for r
in radii]
870 if len(radii_list) != len(nodes):
871 raise ValueError(f
"Number of radii ({len(radii_list)}) must match number of nodes ({len(nodes)})")
874 if any(r <= 0
for r
in radii_list):
875 raise ValueError(
"All radii must be positive")
880 nodes_flat.extend(node.to_list())
884 return context_wrapper.addTube(self.
context, ndivs, nodes_flat, radii_list)
885 elif isinstance(colors, RGBcolor):
887 colors_flat = colors.to_list() * len(nodes)
890 if len(colors) != len(nodes):
891 raise ValueError(f
"Number of colors ({len(colors)}) must match number of nodes ({len(nodes)})")
894 colors_flat.extend(color.to_list())
896 return context_wrapper.addTubeWithColor(self.
context, ndivs, nodes_flat, radii_list, colors_flat)
899 def addBox(self, center: vec3 =
vec3(0, 0, 0), size: vec3 =
vec3(1, 1, 1),
900 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None) -> List[int]:
902 Add a rectangular box to the context.
904 The box is subdivided into patches on each face based on the specified
908 center: 3D coordinates of box center (default: origin)
909 size: Width, height, and depth of the box (default: 1x1x1)
910 subdiv: Number of subdivisions in x, y, and z directions (default: 1x1x1)
911 Higher values create more detailed surfaces
912 color: Color of the box (default: white)
915 List of UUIDs for all patches created on the box faces
918 >>> context = Context()
919 >>> # Create a blue box subdivided for detail
920 >>> box_uuids = context.addBox(
921 ... center=vec3(0, 0, 2),
922 ... size=vec3(2, 1, 0.5),
923 ... subdiv=int3(4, 2, 1),
924 ... color=RGBcolor(0, 0, 1)
926 >>> print(f"Created box with {len(box_uuids)} patches")
931 if not isinstance(center, vec3):
932 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
933 if not isinstance(size, vec3):
934 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
935 if not isinstance(subdiv, int3):
936 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
937 if color
is not None and not isinstance(color, RGBcolor):
938 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
941 if any(s <= 0
for s
in size.to_list()):
942 raise ValueError(
"All box dimensions must be positive")
943 if any(s < 1
for s
in subdiv.to_list()):
944 raise ValueError(
"All subdivision counts must be at least 1")
947 return context_wrapper.addBoxWithColor(
948 self.
context, center.to_list(), size.to_list(),
949 subdiv.to_list(), color.to_list()
952 return context_wrapper.addBox(
953 self.
context, center.to_list(), size.to_list(), subdiv.to_list()
956 def addDisk(self, center: vec3 =
vec3(0, 0, 0), size: vec2 =
vec2(1, 1),
957 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
958 color: Optional[Union[RGBcolor, RGBAcolor]] =
None) -> List[int]:
960 Add a disk (circular or elliptical surface) to the context.
962 A disk is a flat circular or elliptical surface tessellated into
963 triangular faces. Supports both uniform radial subdivisions and
964 separate radial/azimuthal subdivisions for finer control.
967 center: 3D coordinates of disk center (default: origin)
968 size: Semi-major and semi-minor radii of the disk (default: 1x1 circle)
969 ndivs: Number of radial divisions (int) or [radial, azimuthal] divisions (int2)
970 (default: 20). Higher values create smoother circles but more triangles.
971 rotation: Orientation of the disk (default: horizontal, normal = +z)
972 color: Color of the disk (default: white). Can be RGBcolor or RGBAcolor for transparency.
975 List of UUIDs for all triangles created in the disk
978 >>> context = Context()
979 >>> # Create a red disk at (0, 0, 1) with radius 0.5
980 >>> disk_uuids = context.addDisk(
981 ... center=vec3(0, 0, 1),
982 ... size=vec2(0.5, 0.5),
984 ... color=RGBcolor(1, 0, 0)
986 >>> print(f"Created disk with {len(disk_uuids)} triangles")
988 >>> # Create a semi-transparent blue elliptical disk
989 >>> disk_uuids = context.addDisk(
990 ... center=vec3(0, 0, 2),
991 ... size=vec2(1.0, 0.5),
993 ... rotation=SphericalCoord(1, 0.5, 0),
994 ... color=RGBAcolor(0, 0, 1, 0.5)
997 >>> # Create disk with polar/radial subdivisions for finer control
998 >>> disk_uuids = context.addDisk(
999 ... center=vec3(0, 0, 3),
1000 ... size=vec2(1, 1),
1001 ... ndivs=int2(10, 20), # 10 radial, 20 azimuthal divisions
1002 ... color=RGBcolor(0, 1, 0)
1008 if not isinstance(center, vec3):
1009 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1010 if not isinstance(size, vec2):
1011 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1012 if not isinstance(ndivs, (int, int2)):
1013 raise ValueError(f
"Ndivs must be an int or int2, got {type(ndivs).__name__}")
1014 if rotation
is not None and not isinstance(rotation, SphericalCoord):
1015 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
1016 if color
is not None and not isinstance(color, (RGBcolor, RGBAcolor)):
1017 raise ValueError(f
"Color must be an RGBcolor, RGBAcolor, or None, got {type(color).__name__}")
1020 if any(s <= 0
for s
in size.to_list()):
1021 raise ValueError(
"Disk size must be positive")
1024 if isinstance(ndivs, int):
1026 raise ValueError(
"Number of divisions must be at least 3")
1028 if any(n < 1
for n
in ndivs.to_list()):
1029 raise ValueError(
"Radial and angular divisions must be at least 1")
1032 if rotation
is None:
1037 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1040 if isinstance(ndivs, int2):
1043 if isinstance(color, RGBAcolor):
1044 return context_wrapper.addDiskPolarSubdivisionsRGBA(
1045 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1046 rotation_list, color.to_list()
1050 return context_wrapper.addDiskPolarSubdivisions(
1051 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1052 rotation_list, color.to_list()
1056 color_list = [1.0, 1.0, 1.0]
1057 return context_wrapper.addDiskPolarSubdivisions(
1058 self.
context, ndivs.to_list(), center.to_list(), size.to_list(),
1059 rotation_list, color_list
1064 if isinstance(color, RGBAcolor):
1066 return context_wrapper.addDiskWithRGBAColor(
1067 self.
context, ndivs, center.to_list(), size.to_list(),
1068 rotation_list, color.to_list()
1072 return context_wrapper.addDiskWithColor(
1073 self.
context, ndivs, center.to_list(), size.to_list(),
1074 rotation_list, color.to_list()
1078 return context_wrapper.addDiskWithRotation(
1079 self.
context, ndivs, center.to_list(), size.to_list(),
1083 def addCone(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1084 ndivs: int = 20, color: Optional[RGBcolor] =
None) -> List[int]:
1086 Add a cone (or cylinder/frustum) to the context.
1088 A cone is a 3D shape connecting two circular cross-sections with
1089 potentially different radii. When radii are equal, creates a cylinder.
1090 When one radius is zero, creates a true cone.
1093 node0: 3D coordinates of the base center
1094 node1: 3D coordinates of the apex center
1095 radius0: Radius at base (node0). Use 0 for pointed end.
1096 radius1: Radius at apex (node1). Use 0 for pointed end.
1097 ndivs: Number of radial divisions for tessellation (default: 20)
1098 color: Color of the cone (default: white)
1101 List of UUIDs for all triangles created in the cone
1104 >>> context = Context()
1105 >>> # Create a cylinder (equal radii)
1106 >>> cylinder_uuids = context.addCone(
1107 ... node0=vec3(0, 0, 0),
1108 ... node1=vec3(0, 0, 2),
1114 >>> # Create a true cone (one radius = 0)
1115 >>> cone_uuids = context.addCone(
1116 ... node0=vec3(1, 0, 0),
1117 ... node1=vec3(1, 0, 1.5),
1121 ... color=RGBcolor(1, 0, 0)
1124 >>> # Create a frustum (different radii)
1125 >>> frustum_uuids = context.addCone(
1126 ... node0=vec3(2, 0, 0),
1127 ... node1=vec3(2, 0, 1),
1136 if not isinstance(node0, vec3):
1137 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1138 if not isinstance(node1, vec3):
1139 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1140 if not isinstance(ndivs, int):
1141 raise ValueError(f
"ndivs must be an int, got {type(ndivs).__name__}")
1142 if color
is not None and not isinstance(color, RGBcolor):
1143 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1146 if radius0 < 0
or radius1 < 0:
1147 raise ValueError(
"Radii must be non-negative")
1149 raise ValueError(
"Number of radial divisions must be at least 3")
1153 return context_wrapper.addConeWithColor(
1154 self.
context, ndivs, node0.to_list(), node1.to_list(),
1155 radius0, radius1, color.to_list()
1158 return context_wrapper.addCone(
1159 self.
context, ndivs, node0.to_list(), node1.to_list(),
1164 radius: Union[float, vec3] = 1.0, ndivs: int = 20,
1165 color: Optional[RGBcolor] =
None,
1166 texturefile: Optional[str] =
None) -> int:
1168 Add a spherical or ellipsoidal compound object to the context.
1170 Creates a sphere or ellipsoid as a compound object with a trackable object ID.
1171 Primitives within the object are registered as children of the object.
1174 center: Center position of sphere/ellipsoid (default: origin)
1175 radius: Radius as float (sphere) or vec3 (ellipsoid) (default: 1.0)
1176 ndivs: Number of tessellation divisions (default: 20)
1177 color: Optional RGB color
1178 texturefile: Optional texture image file path
1181 Object ID of the created compound object
1184 ValueError: If parameters are invalid
1185 NotImplementedError: If object-returning functions unavailable
1188 >>> # Create a basic sphere at origin
1189 >>> obj_id = ctx.addSphereObject()
1191 >>> # Create a colored sphere
1192 >>> obj_id = ctx.addSphereObject(
1193 ... center=vec3(0, 0, 5),
1195 ... color=RGBcolor(1, 0, 0)
1198 >>> # Create an ellipsoid (stretched sphere)
1199 >>> obj_id = ctx.addSphereObject(
1200 ... center=vec3(10, 0, 0),
1201 ... radius=vec3(2, 1, 1), # Elongated in x-direction
1208 if not isinstance(center, vec3):
1209 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1210 if not isinstance(radius, (int, float, vec3)):
1211 raise ValueError(f
"Radius must be a number or vec3, got {type(radius).__name__}")
1212 if color
is not None and not isinstance(color, RGBcolor):
1213 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1217 raise ValueError(
"Number of divisions must be at least 3")
1220 is_ellipsoid = isinstance(radius, vec3)
1226 return context_wrapper.addSphereObject_ellipsoid_texture(
1227 self.
context, ndivs, center.to_list(), radius.to_list(), texturefile
1230 return context_wrapper.addSphereObject_ellipsoid_color(
1231 self.
context, ndivs, center.to_list(), radius.to_list(), color.to_list()
1234 return context_wrapper.addSphereObject_ellipsoid(
1235 self.
context, ndivs, center.to_list(), radius.to_list()
1240 return context_wrapper.addSphereObject_texture(
1241 self.
context, ndivs, center.to_list(), radius, texturefile
1244 return context_wrapper.addSphereObject_color(
1245 self.
context, ndivs, center.to_list(), radius, color.to_list()
1248 return context_wrapper.addSphereObject_basic(
1249 self.
context, ndivs, center.to_list(), radius
1254 subdiv: int2 =
int2(1, 1),
1255 color: Optional[RGBcolor] =
None,
1256 texturefile: Optional[str] =
None,
1257 texture_repeat: Optional[int2] =
None) -> int:
1259 Add a tiled patch (subdivided patch) as a compound object to the context.
1261 Creates a rectangular patch subdivided into a grid of smaller patches,
1262 registered as a compound object with a trackable object ID.
1265 center: Center position of tile (default: origin)
1266 size: Size in x and y directions (default: 1x1)
1267 rotation: Spherical rotation (default: no rotation)
1268 subdiv: Number of subdivisions in x and y (default: 1x1)
1269 color: Optional RGB color
1270 texturefile: Optional texture image file path
1271 texture_repeat: Optional texture repetitions in x and y
1274 Object ID of the created compound object
1277 ValueError: If parameters are invalid
1278 NotImplementedError: If object-returning functions unavailable
1281 >>> # Create a basic 2x2 tile
1282 >>> obj_id = ctx.addTileObject(
1283 ... center=vec3(0, 0, 0),
1284 ... size=vec2(10, 10),
1285 ... subdiv=int2(2, 2)
1288 >>> # Create a colored tile with rotation
1289 >>> obj_id = ctx.addTileObject(
1290 ... center=vec3(5, 0, 0),
1291 ... size=vec2(10, 5),
1292 ... rotation=SphericalCoord(1, 0, 45),
1293 ... subdiv=int2(4, 2),
1294 ... color=RGBcolor(0, 1, 0)
1300 if not isinstance(center, vec3):
1301 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1302 if not isinstance(size, vec2):
1303 raise ValueError(f
"Size must be a vec2, got {type(size).__name__}")
1304 if not isinstance(rotation, SphericalCoord):
1305 raise ValueError(f
"Rotation must be a SphericalCoord, got {type(rotation).__name__}")
1306 if not isinstance(subdiv, int2):
1307 raise ValueError(f
"Subdiv must be an int2, got {type(subdiv).__name__}")
1308 if color
is not None and not isinstance(color, RGBcolor):
1309 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1310 if texture_repeat
is not None and not isinstance(texture_repeat, int2):
1311 raise ValueError(f
"texture_repeat must be an int2 or None, got {type(texture_repeat).__name__}")
1314 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
1317 if texture_repeat
is not None:
1318 if texturefile
is None:
1319 raise ValueError(
"texture_repeat requires texturefile")
1320 return context_wrapper.addTileObject_texture_repeat(
1321 self.
context, center.to_list(), size.to_list(), rotation_list,
1322 subdiv.to_list(), texturefile, texture_repeat.to_list()
1325 return context_wrapper.addTileObject_texture(
1326 self.
context, center.to_list(), size.to_list(), rotation_list,
1327 subdiv.to_list(), texturefile
1330 return context_wrapper.addTileObject_color(
1331 self.
context, center.to_list(), size.to_list(), rotation_list,
1332 subdiv.to_list(), color.to_list()
1335 return context_wrapper.addTileObject_basic(
1336 self.
context, center.to_list(), size.to_list(), rotation_list,
1341 subdiv: int3 =
int3(1, 1, 1), color: Optional[RGBcolor] =
None,
1342 texturefile: Optional[str] =
None, reverse_normals: bool =
False) -> int:
1344 Add a rectangular box (prism) as a compound object to the context.
1347 center: Center position (default: origin)
1348 size: Size in x, y, z directions (default: 1x1x1)
1349 subdiv: Subdivisions in x, y, z (default: 1x1x1)
1350 color: Optional RGB color
1351 texturefile: Optional texture file path
1352 reverse_normals: Reverse normal directions (default: False)
1355 Object ID of the created compound object
1360 if not isinstance(center, vec3):
1361 raise ValueError(f
"Center must be a vec3, got {type(center).__name__}")
1362 if not isinstance(size, vec3):
1363 raise ValueError(f
"Size must be a vec3, got {type(size).__name__}")
1364 if not isinstance(subdiv, int3):
1365 raise ValueError(f
"Subdiv must be an int3, got {type(subdiv).__name__}")
1366 if color
is not None and not isinstance(color, RGBcolor):
1367 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1371 return context_wrapper.addBoxObject_texture_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile, reverse_normals)
1373 return context_wrapper.addBoxObject_color_reverse(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list(), reverse_normals)
1375 raise ValueError(
"reverse_normals requires either color or texturefile")
1377 return context_wrapper.addBoxObject_texture(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), texturefile)
1379 return context_wrapper.addBoxObject_color(self.
context, center.to_list(), size.to_list(), subdiv.to_list(), color.to_list())
1381 return context_wrapper.addBoxObject_basic(self.
context, center.to_list(), size.to_list(), subdiv.to_list())
1383 def addConeObject(self, node0: vec3, node1: vec3, radius0: float, radius1: float,
1384 ndivs: int = 20, color: Optional[RGBcolor] =
None,
1385 texturefile: Optional[str] =
None) -> int:
1387 Add a cone/cylinder/frustum as a compound object to the context.
1390 node0: Base position
1392 radius0: Radius at base
1393 radius1: Radius at top
1394 ndivs: Number of radial divisions (default: 20)
1395 color: Optional RGB color
1396 texturefile: Optional texture file path
1399 Object ID of the created compound object
1404 if not isinstance(node0, vec3):
1405 raise ValueError(f
"node0 must be a vec3, got {type(node0).__name__}")
1406 if not isinstance(node1, vec3):
1407 raise ValueError(f
"node1 must be a vec3, got {type(node1).__name__}")
1408 if not isinstance(radius0, (int, float)):
1409 raise ValueError(f
"radius0 must be a number, got {type(radius0).__name__}")
1410 if not isinstance(radius1, (int, float)):
1411 raise ValueError(f
"radius1 must be a number, got {type(radius1).__name__}")
1412 if color
is not None and not isinstance(color, RGBcolor):
1413 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
1416 return context_wrapper.addConeObject_texture(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, texturefile)
1418 return context_wrapper.addConeObject_color(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1, color.to_list())
1420 return context_wrapper.addConeObject_basic(self.
context, ndivs, node0.to_list(), node1.to_list(), radius0, radius1)
1423 ndivs: Union[int, int2] = 20, rotation: Optional[SphericalCoord] =
None,
1424 color: Optional[Union[RGBcolor, RGBAcolor]] =
None,
1425 texturefile: Optional[str] =
None) -> int:
1427 Add a disk as a compound object to the context.
1430 center: Center position (default: origin)
1431 size: Semi-major and semi-minor radii (default: 1x1)
1432 ndivs: int (uniform) or int2 (polar/radial subdivisions) (default: 20)
1433 rotation: Optional spherical rotation
1434 color: Optional RGB or RGBA color
1435 texturefile: Optional texture file path
1438 Object ID of the created compound object
1442 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
if rotation
else [1, 0, 0]
1443 is_polar = isinstance(ndivs, int2)
1447 return context_wrapper.addDiskObject_polar_texture(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, texturefile)
1449 if isinstance(color, RGBAcolor):
1450 return context_wrapper.addDiskObject_polar_rgba(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1452 return context_wrapper.addDiskObject_polar_color(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list, color.to_list())
1454 return context_wrapper.addDiskObject_polar_color(self.
context, ndivs.to_list(), center.to_list(), size.to_list(), rotation_list,
RGBcolor(0.5, 0.5, 0.5).to_list())
1457 return context_wrapper.addDiskObject_texture(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, texturefile)
1459 if isinstance(color, RGBAcolor):
1460 return context_wrapper.addDiskObject_rgba(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1462 return context_wrapper.addDiskObject_color(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list, color.to_list())
1464 return context_wrapper.addDiskObject_rotation(self.
context, ndivs, center.to_list(), size.to_list(), rotation_list)
1466 return context_wrapper.addDiskObject_basic(self.
context, ndivs, center.to_list(), size.to_list())
1468 def addTubeObject(self, ndivs: int, nodes: List[vec3], radii: List[float],
1469 colors: Optional[List[RGBcolor]] =
None,
1470 texturefile: Optional[str] =
None,
1471 texture_uv: Optional[List[float]] =
None) -> int:
1473 Add a tube as a compound object to the context.
1476 ndivs: Number of radial subdivisions
1477 nodes: List of vec3 positions defining tube segments
1478 radii: List of radii at each node
1479 colors: Optional list of RGB colors for each segment
1480 texturefile: Optional texture file path
1481 texture_uv: Optional UV coordinates for texture mapping
1484 Object ID of the created compound object
1489 if not isinstance(nodes, (list, tuple)):
1490 raise ValueError(f
"Nodes must be a list, got {type(nodes).__name__}")
1491 for i, node
in enumerate(nodes):
1492 if not isinstance(node, vec3):
1493 raise ValueError(f
"nodes[{i}] must be a vec3, got {type(node).__name__}")
1494 if not isinstance(radii, (list, tuple)):
1495 raise ValueError(f
"Radii must be a list, got {type(radii).__name__}")
1496 if colors
is not None:
1497 if not isinstance(colors, (list, tuple)):
1498 raise ValueError(f
"Colors must be a list or None, got {type(colors).__name__}")
1499 for i, c
in enumerate(colors):
1500 if not isinstance(c, RGBcolor):
1501 raise ValueError(f
"colors[{i}] must be an RGBcolor, got {type(c).__name__}")
1504 raise ValueError(
"Tube requires at least 2 nodes")
1505 if len(radii) != len(nodes):
1506 raise ValueError(
"Number of radii must match number of nodes")
1508 nodes_flat = [coord
for node
in nodes
for coord
in node.to_list()]
1510 if texture_uv
is not None:
1511 if texturefile
is None:
1512 raise ValueError(
"texture_uv requires texturefile")
1513 return context_wrapper.addTubeObject_texture_uv(self.
context, ndivs, nodes_flat, radii, texturefile, texture_uv)
1515 return context_wrapper.addTubeObject_texture(self.
context, ndivs, nodes_flat, radii, texturefile)
1517 if len(colors) != len(nodes):
1518 raise ValueError(
"Number of colors must match number of nodes")
1519 colors_flat = [c
for color
in colors
for c
in color.to_list()]
1520 return context_wrapper.addTubeObject_color(self.
context, ndivs, nodes_flat, radii, colors_flat)
1522 return context_wrapper.addTubeObject_basic(self.
context, ndivs, nodes_flat, radii)
1524 def copyPrimitive(self, UUID: Union[int, List[int]]) -> Union[int, List[int]]:
1526 Copy one or more primitives.
1528 Creates a duplicate of the specified primitive(s) with all associated data.
1529 The copy is placed at the same location as the original.
1532 UUID: Single primitive UUID or list of UUIDs to copy
1535 Single UUID of copied primitive (if UUID is int) or
1536 List of UUIDs of copied primitives (if UUID is list)
1539 >>> context = Context()
1540 >>> original_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1541 >>> # Copy single primitive
1542 >>> copied_uuid = context.copyPrimitive(original_uuid)
1543 >>> # Copy multiple primitives
1544 >>> copied_uuids = context.copyPrimitive([uuid1, uuid2, uuid3])
1548 if isinstance(UUID, int):
1549 return context_wrapper.copyPrimitive(self.
context, UUID)
1550 elif isinstance(UUID, list):
1551 return context_wrapper.copyPrimitives(self.
context, UUID)
1553 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1557 Copy all primitive data from source to destination primitive.
1559 Copies all associated data (primitive data fields) from the source
1560 primitive to the destination primitive. Both primitives must already exist.
1563 sourceUUID: UUID of the source primitive
1564 destinationUUID: UUID of the destination primitive
1567 >>> context = Context()
1568 >>> source_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1569 >>> dest_uuid = context.addPatch(center=vec3(1, 0, 0), size=vec2(1, 1))
1570 >>> context.setPrimitiveData(source_uuid, "temperature", 25.5)
1571 >>> context.copyPrimitiveData(source_uuid, dest_uuid)
1572 >>> # dest_uuid now has temperature data
1576 if not isinstance(sourceUUID, int):
1577 raise ValueError(f
"sourceUUID must be int, got {type(sourceUUID).__name__}")
1578 if not isinstance(destinationUUID, int):
1579 raise ValueError(f
"destinationUUID must be int, got {type(destinationUUID).__name__}")
1581 context_wrapper.copyPrimitiveData(self.
context, sourceUUID, destinationUUID)
1583 def copyObject(self, ObjID: Union[int, List[int]]) -> Union[int, List[int]]:
1585 Copy one or more compound objects.
1587 Creates a duplicate of the specified compound object(s) with all
1588 associated primitives and data. The copy is placed at the same location
1592 ObjID: Single object ID or list of object IDs to copy
1595 Single object ID of copied object (if ObjID is int) or
1596 List of object IDs of copied objects (if ObjID is list)
1599 >>> context = Context()
1600 >>> original_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1601 >>> # Copy single object
1602 >>> copied_obj = context.copyObject(original_obj)
1603 >>> # Copy multiple objects
1604 >>> copied_objs = context.copyObject([obj1, obj2, obj3])
1608 if isinstance(ObjID, int):
1609 return context_wrapper.copyObject(self.
context, ObjID)
1610 elif isinstance(ObjID, list):
1611 return context_wrapper.copyObjects(self.
context, ObjID)
1613 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1615 def copyObjectData(self, source_objID: int, destination_objID: int) ->
None:
1617 Copy all object data from source to destination compound object.
1619 Copies all associated data (object data fields) from the source
1620 compound object to the destination object. Both objects must already exist.
1623 source_objID: Object ID of the source compound object
1624 destination_objID: Object ID of the destination compound object
1627 >>> context = Context()
1628 >>> source_obj = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1629 >>> dest_obj = context.addTile(center=vec3(2, 0, 0), size=vec2(2, 2))
1630 >>> context.setObjectData(source_obj, "material", "wood")
1631 >>> context.copyObjectData(source_obj, dest_obj)
1632 >>> # dest_obj now has material data
1636 if not isinstance(source_objID, int):
1637 raise ValueError(f
"source_objID must be int, got {type(source_objID).__name__}")
1638 if not isinstance(destination_objID, int):
1639 raise ValueError(f
"destination_objID must be int, got {type(destination_objID).__name__}")
1641 context_wrapper.copyObjectData(self.
context, source_objID, destination_objID)
1645 Translate one or more primitives by a shift vector.
1647 Moves the specified primitive(s) by the given shift vector without
1648 changing their orientation or size.
1651 UUID: Single primitive UUID or list of UUIDs to translate
1652 shift: 3D vector representing the translation [x, y, z]
1655 >>> context = Context()
1656 >>> patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
1657 >>> # Translate single primitive
1658 >>> context.translatePrimitive(patch_uuid, vec3(1, 0, 0)) # Move 1 unit in x
1659 >>> # Translate multiple primitives
1660 >>> context.translatePrimitive([uuid1, uuid2, uuid3], vec3(0, 0, 1)) # Move 1 unit in z
1665 if not isinstance(shift, vec3):
1666 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1668 if isinstance(UUID, int):
1669 context_wrapper.translatePrimitive(self.
context, UUID, shift.to_list())
1670 elif isinstance(UUID, list):
1671 context_wrapper.translatePrimitives(self.
context, UUID, shift.to_list())
1673 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1675 def translateObject(self, ObjID: Union[int, List[int]], shift: vec3) ->
None:
1677 Translate one or more compound objects by a shift vector.
1679 Moves the specified compound object(s) and all their constituent
1680 primitives by the given shift vector without changing orientation or size.
1683 ObjID: Single object ID or list of object IDs to translate
1684 shift: 3D vector representing the translation [x, y, z]
1687 >>> context = Context()
1688 >>> tile_uuids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2))
1689 >>> obj_id = context.getPrimitiveParentObjectID(tile_uuids[0]) # Get object ID
1690 >>> # Translate single object
1691 >>> context.translateObject(obj_id, vec3(5, 0, 0)) # Move 5 units in x
1692 >>> # Translate multiple objects
1693 >>> context.translateObject([obj1, obj2, obj3], vec3(0, 2, 0)) # Move 2 units in y
1698 if not isinstance(shift, vec3):
1699 raise ValueError(f
"shift must be a vec3, got {type(shift).__name__}")
1701 if isinstance(ObjID, int):
1702 context_wrapper.translateObject(self.
context, ObjID, shift.to_list())
1703 elif isinstance(ObjID, list):
1704 context_wrapper.translateObjects(self.
context, ObjID, shift.to_list())
1706 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1709 axis: Union[str, vec3], origin: Optional[vec3] =
None) ->
None:
1711 Rotate one or more primitives.
1714 UUID: Single UUID or list of UUIDs to rotate
1715 angle: Rotation angle in radians
1716 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1717 origin: Optional rotation origin point. If None, rotates about primitive center.
1718 If provided with string axis, raises ValueError.
1721 ValueError: If axis is invalid or if origin is provided with string axis
1726 if isinstance(axis, str):
1727 if axis
not in (
'x',
'y',
'z'):
1728 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1729 if origin
is not None:
1730 raise ValueError(
"origin parameter cannot be used with string axis")
1733 if isinstance(UUID, int):
1734 context_wrapper.rotatePrimitive_axisString(self.
context, UUID, angle, axis)
1735 elif isinstance(UUID, list):
1736 context_wrapper.rotatePrimitives_axisString(self.
context, UUID, angle, axis)
1738 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1740 elif isinstance(axis, vec3):
1741 axis_list = axis.to_list()
1744 if all(abs(v) < 1e-10
for v
in axis_list):
1745 raise ValueError(
"axis vector cannot be zero")
1749 if isinstance(UUID, int):
1750 context_wrapper.rotatePrimitive_axisVector(self.
context, UUID, angle, axis_list)
1751 elif isinstance(UUID, list):
1752 context_wrapper.rotatePrimitives_axisVector(self.
context, UUID, angle, axis_list)
1754 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1757 if not isinstance(origin, vec3):
1758 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1760 origin_list = origin.to_list()
1761 if isinstance(UUID, int):
1762 context_wrapper.rotatePrimitive_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1763 elif isinstance(UUID, list):
1764 context_wrapper.rotatePrimitives_originAxisVector(self.
context, UUID, angle, origin_list, axis_list)
1766 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1768 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1770 def rotateObject(self, ObjID: Union[int, List[int]], angle: float,
1771 axis: Union[str, vec3], origin: Optional[vec3] =
None,
1772 about_origin: bool =
False) ->
None:
1774 Rotate one or more objects.
1777 ObjID: Single object ID or list of object IDs to rotate
1778 angle: Rotation angle in radians
1779 axis: Rotation axis - either 'x', 'y', 'z' or a vec3 direction vector
1780 origin: Optional rotation origin point. If None, rotates about object center.
1781 If provided with string axis, raises ValueError.
1782 about_origin: If True, rotate about global origin (0,0,0). Cannot be used with origin parameter.
1785 ValueError: If axis is invalid or if origin and about_origin are both specified
1790 if origin
is not None and about_origin:
1791 raise ValueError(
"Cannot specify both origin and about_origin")
1794 if isinstance(axis, str):
1795 if axis
not in (
'x',
'y',
'z'):
1796 raise ValueError(
"axis must be 'x', 'y', or 'z'")
1797 if origin
is not None:
1798 raise ValueError(
"origin parameter cannot be used with string axis")
1800 raise ValueError(
"about_origin parameter cannot be used with string axis")
1803 if isinstance(ObjID, int):
1804 context_wrapper.rotateObject_axisString(self.
context, ObjID, angle, axis)
1805 elif isinstance(ObjID, list):
1806 context_wrapper.rotateObjects_axisString(self.
context, ObjID, angle, axis)
1808 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1810 elif isinstance(axis, vec3):
1811 axis_list = axis.to_list()
1814 if all(abs(v) < 1e-10
for v
in axis_list):
1815 raise ValueError(
"axis vector cannot be zero")
1819 if isinstance(ObjID, int):
1820 context_wrapper.rotateObjectAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1821 elif isinstance(ObjID, list):
1822 context_wrapper.rotateObjectsAboutOrigin_axisVector(self.
context, ObjID, angle, axis_list)
1824 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1825 elif origin
is None:
1827 if isinstance(ObjID, int):
1828 context_wrapper.rotateObject_axisVector(self.
context, ObjID, angle, axis_list)
1829 elif isinstance(ObjID, list):
1830 context_wrapper.rotateObjects_axisVector(self.
context, ObjID, angle, axis_list)
1832 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1835 if not isinstance(origin, vec3):
1836 raise ValueError(f
"origin must be a vec3, got {type(origin).__name__}")
1838 origin_list = origin.to_list()
1839 if isinstance(ObjID, int):
1840 context_wrapper.rotateObject_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1841 elif isinstance(ObjID, list):
1842 context_wrapper.rotateObjects_originAxisVector(self.
context, ObjID, angle, origin_list, axis_list)
1844 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1846 raise ValueError(f
"axis must be str or vec3, got {type(axis).__name__}")
1848 def scalePrimitive(self, UUID: Union[int, List[int]], scale: vec3, point: Optional[vec3] =
None) ->
None:
1850 Scale one or more primitives.
1853 UUID: Single UUID or list of UUIDs to scale
1854 scale: Scale factors as vec3(x, y, z)
1855 point: Optional point to scale about. If None, scales about primitive center.
1858 ValueError: If scale or point parameters are invalid
1862 if not isinstance(scale, vec3):
1863 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1865 scale_list = scale.to_list()
1869 if isinstance(UUID, int):
1870 context_wrapper.scalePrimitive(self.
context, UUID, scale_list)
1871 elif isinstance(UUID, list):
1872 context_wrapper.scalePrimitives(self.
context, UUID, scale_list)
1874 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1877 if not isinstance(point, vec3):
1878 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1880 point_list = point.to_list()
1881 if isinstance(UUID, int):
1882 context_wrapper.scalePrimitiveAboutPoint(self.
context, UUID, scale_list, point_list)
1883 elif isinstance(UUID, list):
1884 context_wrapper.scalePrimitivesAboutPoint(self.
context, UUID, scale_list, point_list)
1886 raise ValueError(f
"UUID must be int or List[int], got {type(UUID).__name__}")
1888 def scaleObject(self, ObjID: Union[int, List[int]], scale: vec3,
1889 point: Optional[vec3] =
None, about_center: bool =
False,
1890 about_origin: bool =
False) ->
None:
1892 Scale one or more objects.
1895 ObjID: Single object ID or list of object IDs to scale
1896 scale: Scale factors as vec3(x, y, z)
1897 point: Optional point to scale about
1898 about_center: If True, scale about object center (default behavior)
1899 about_origin: If True, scale about global origin (0,0,0)
1902 ValueError: If parameters are invalid or conflicting options specified
1907 options_count = sum([point
is not None, about_center, about_origin])
1908 if options_count > 1:
1909 raise ValueError(
"Cannot specify multiple scaling options (point, about_center, about_origin)")
1911 if not isinstance(scale, vec3):
1912 raise ValueError(f
"scale must be a vec3, got {type(scale).__name__}")
1914 scale_list = scale.to_list()
1918 if isinstance(ObjID, int):
1919 context_wrapper.scaleObjectAboutOrigin(self.
context, ObjID, scale_list)
1920 elif isinstance(ObjID, list):
1921 context_wrapper.scaleObjectsAboutOrigin(self.
context, ObjID, scale_list)
1923 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1926 if isinstance(ObjID, int):
1927 context_wrapper.scaleObjectAboutCenter(self.
context, ObjID, scale_list)
1928 elif isinstance(ObjID, list):
1929 context_wrapper.scaleObjectsAboutCenter(self.
context, ObjID, scale_list)
1931 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1932 elif point
is not None:
1934 if not isinstance(point, vec3):
1935 raise ValueError(f
"point must be a vec3, got {type(point).__name__}")
1937 point_list = point.to_list()
1938 if isinstance(ObjID, int):
1939 context_wrapper.scaleObjectAboutPoint(self.
context, ObjID, scale_list, point_list)
1940 elif isinstance(ObjID, list):
1941 context_wrapper.scaleObjectsAboutPoint(self.
context, ObjID, scale_list, point_list)
1943 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1946 if isinstance(ObjID, int):
1947 context_wrapper.scaleObject(self.
context, ObjID, scale_list)
1948 elif isinstance(ObjID, list):
1949 context_wrapper.scaleObjects(self.
context, ObjID, scale_list)
1951 raise ValueError(f
"ObjID must be int or List[int], got {type(ObjID).__name__}")
1955 Scale the length of a Cone object by scaling the distance between its two nodes.
1958 ObjID: Object ID of the Cone to scale
1959 scale_factor: Factor by which to scale the cone length (e.g., 2.0 doubles length)
1962 ValueError: If ObjID is not an integer or scale_factor is invalid
1963 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
1966 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
1967 method, enforcing better encapsulation.
1970 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
1971 >>> context.scaleConeObjectLength(cone_id, 1.5) # Make cone 50% longer
1973 if not isinstance(ObjID, int):
1974 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
1975 if not isinstance(scale_factor, (int, float)):
1976 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
1977 if scale_factor <= 0:
1978 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
1980 context_wrapper.scaleConeObjectLength(self.
context, ObjID, float(scale_factor))
1984 Scale the girth of a Cone object by scaling the radii at both nodes.
1987 ObjID: Object ID of the Cone to scale
1988 scale_factor: Factor by which to scale the cone girth (e.g., 2.0 doubles girth)
1991 ValueError: If ObjID is not an integer or scale_factor is invalid
1992 HeliosRuntimeError: If operation fails (e.g., ObjID is not a Cone object)
1995 Added in helios-core v1.3.59 as a replacement for the removed getConeObjectPointer()
1996 method, enforcing better encapsulation.
1999 >>> cone_id = context.addConeObject(10, [0,0,0], [0,0,1], 0.1, 0.05)
2000 >>> context.scaleConeObjectGirth(cone_id, 2.0) # Double the cone girth
2002 if not isinstance(ObjID, int):
2003 raise ValueError(f
"ObjID must be an integer, got {type(ObjID).__name__}")
2004 if not isinstance(scale_factor, (int, float)):
2005 raise ValueError(f
"scale_factor must be numeric, got {type(scale_factor).__name__}")
2006 if scale_factor <= 0:
2007 raise ValueError(f
"scale_factor must be positive, got {scale_factor}")
2009 context_wrapper.scaleConeObjectGirth(self.
context, ObjID, float(scale_factor))
2011 def loadPLY(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2012 rotation: Optional[SphericalCoord] =
None, color: Optional[RGBcolor] =
None,
2013 upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2015 Load geometry from a PLY (Stanford Polygon) file.
2018 filename: Path to the PLY file to load
2019 origin: Origin point for positioning the geometry (optional)
2020 height: Height scaling factor (optional)
2021 rotation: Rotation to apply to the geometry (optional)
2022 color: Default color for geometry without color data (optional)
2023 upaxis: Up axis orientation ("YUP" or "ZUP")
2024 silent: If True, suppress loading output messages
2027 List of UUIDs for the loaded primitives
2032 if origin
is not None and not isinstance(origin, vec3):
2033 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2034 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2035 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2036 if color
is not None and not isinstance(color, RGBcolor):
2037 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2042 if origin
is None and height
is None and rotation
is None and color
is None:
2044 return context_wrapper.loadPLY(self.
context, validated_filename, silent)
2046 elif origin
is not None and height
is not None and rotation
is None and color
is None:
2048 return context_wrapper.loadPLYWithOriginHeight(self.
context, validated_filename, origin.to_list(), height, upaxis, silent)
2050 elif origin
is not None and height
is not None and rotation
is not None and color
is None:
2052 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2053 return context_wrapper.loadPLYWithOriginHeightRotation(self.
context, validated_filename, origin.to_list(), height, rotation_list, upaxis, silent)
2055 elif origin
is not None and height
is not None and rotation
is None and color
is not None:
2057 return context_wrapper.loadPLYWithOriginHeightColor(self.
context, validated_filename, origin.to_list(), height, color.to_list(), upaxis, silent)
2059 elif origin
is not None and height
is not None and rotation
is not None and color
is not None:
2061 rotation_list = [rotation.radius, rotation.elevation, rotation.azimuth]
2062 return context_wrapper.loadPLYWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation_list, color.to_list(), upaxis, silent)
2065 raise ValueError(
"Invalid parameter combination. When using transformations, both origin and height are required.")
2067 def loadOBJ(self, filename: str, origin: Optional[vec3] =
None, height: Optional[float] =
None,
2068 scale: Optional[vec3] =
None, rotation: Optional[SphericalCoord] =
None,
2069 color: Optional[RGBcolor] =
None, upaxis: str =
"YUP", silent: bool =
False) -> List[int]:
2071 Load geometry from an OBJ (Wavefront) file.
2074 filename: Path to the OBJ file to load
2075 origin: Origin point for positioning the geometry (optional)
2076 height: Height scaling factor (optional, alternative to scale)
2077 scale: Scale factor for all dimensions (optional, alternative to height)
2078 rotation: Rotation to apply to the geometry (optional)
2079 color: Default color for geometry without color data (optional)
2080 upaxis: Up axis orientation ("YUP" or "ZUP")
2081 silent: If True, suppress loading output messages
2084 List of UUIDs for the loaded primitives
2089 if origin
is not None and not isinstance(origin, vec3):
2090 raise ValueError(f
"Origin must be a vec3 or None, got {type(origin).__name__}")
2091 if scale
is not None and not isinstance(scale, vec3):
2092 raise ValueError(f
"Scale must be a vec3 or None, got {type(scale).__name__}")
2093 if rotation
is not None and not isinstance(rotation, SphericalCoord):
2094 raise ValueError(f
"Rotation must be a SphericalCoord or None, got {type(rotation).__name__}")
2095 if color
is not None and not isinstance(color, RGBcolor):
2096 raise ValueError(f
"Color must be an RGBcolor or None, got {type(color).__name__}")
2101 if origin
is None and height
is None and scale
is None and rotation
is None and color
is None:
2103 return context_wrapper.loadOBJ(self.
context, validated_filename, silent)
2105 elif origin
is not None and height
is not None and scale
is None and rotation
is not None and color
is not None:
2107 return context_wrapper.loadOBJWithOriginHeightRotationColor(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), silent)
2109 elif origin
is not None and height
is not None and scale
is None and rotation
is not None and color
is not None and upaxis !=
"YUP":
2111 return context_wrapper.loadOBJWithOriginHeightRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), height, rotation.to_list(), color.to_list(), upaxis, silent)
2113 elif origin
is not None and scale
is not None and rotation
is not None and color
is not None:
2115 return context_wrapper.loadOBJWithOriginScaleRotationColorUpaxis(self.
context, validated_filename, origin.to_list(), scale.to_list(), rotation.to_list(), color.to_list(), upaxis, silent)
2118 raise ValueError(
"Invalid parameter combination. For OBJ loading, you must provide either: " +
2119 "1) No parameters (simple load), " +
2120 "2) origin + height + rotation + color, " +
2121 "3) origin + height + rotation + color + upaxis, or " +
2122 "4) origin + scale + rotation + color + upaxis")
2124 def loadXML(self, filename: str, quiet: bool =
False) -> List[int]:
2126 Load geometry from a Helios XML file.
2129 filename: Path to the XML file to load
2130 quiet: If True, suppress loading output messages
2133 List of UUIDs for the loaded primitives
2139 return context_wrapper.loadXML(self.
context, validated_filename, quiet)
2141 def writePLY(self, filename: str, UUIDs: Optional[List[int]] =
None) ->
None:
2143 Write geometry to a PLY (Stanford Polygon) file.
2146 filename: Path to the output PLY file
2147 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2150 ValueError: If filename is invalid or UUIDs are invalid
2151 PermissionError: If output directory is not writable
2152 FileNotFoundError: If UUIDs do not exist in context
2153 RuntimeError: If Context is in mock mode
2156 >>> context.writePLY("output.ply") # Export all primitives
2157 >>> context.writePLY("subset.ply", [uuid1, uuid2]) # Export specific primitives
2166 context_wrapper.writePLY(self.
context, validated_filename)
2170 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2177 context_wrapper.writePLYWithUUIDs(self.
context, validated_filename, UUIDs)
2179 def writeOBJ(self, filename: str, UUIDs: Optional[List[int]] =
None,
2180 primitive_data_fields: Optional[List[str]] =
None,
2181 write_normals: bool =
False, silent: bool =
False) ->
None:
2183 Write geometry to an OBJ (Wavefront) file.
2186 filename: Path to the output OBJ file
2187 UUIDs: Optional list of primitive UUIDs to export. If None, exports all primitives
2188 primitive_data_fields: Optional list of primitive data field names to export
2189 write_normals: Whether to include vertex normals in the output
2190 silent: Whether to suppress output messages during export
2193 ValueError: If filename is invalid, UUIDs are invalid, or data fields don't exist
2194 PermissionError: If output directory is not writable
2195 FileNotFoundError: If UUIDs do not exist in context
2196 RuntimeError: If Context is in mock mode
2199 >>> context.writeOBJ("output.obj") # Export all primitives
2200 >>> context.writeOBJ("subset.obj", [uuid1, uuid2]) # Export specific primitives
2201 >>> context.writeOBJ("with_data.obj", [uuid1], ["temperature", "area"]) # Export with data
2210 context_wrapper.writeOBJ(self.
context, validated_filename, write_normals, silent)
2211 elif primitive_data_fields
is None:
2214 raise ValueError(
"UUIDs list cannot be empty. Use UUIDs=None to export all primitives")
2220 context_wrapper.writeOBJWithUUIDs(self.
context, validated_filename, UUIDs, write_normals, silent)
2224 raise ValueError(
"UUIDs list cannot be empty when exporting primitive data")
2225 if not primitive_data_fields:
2226 raise ValueError(
"primitive_data_fields list cannot be empty")
2235 context_wrapper.writeOBJWithPrimitiveData(self.
context, validated_filename, UUIDs, primitive_data_fields, write_normals, silent)
2238 UUIDs: Optional[List[int]] =
None,
2239 print_header: bool =
False) ->
None:
2241 Write primitive data to an ASCII text file.
2243 Outputs a space-separated text file where each row corresponds to a primitive
2244 and each column corresponds to a primitive data label.
2247 filename: Path to the output file
2248 column_labels: List of primitive data labels to include as columns.
2249 Use "UUID" to include primitive UUIDs as a column.
2250 The order determines the column order in the output file.
2251 UUIDs: Optional list of primitive UUIDs to include. If None, includes all primitives.
2252 print_header: If True, writes column labels as the first line of the file
2255 ValueError: If filename is invalid, column_labels is empty, or UUIDs list is empty when provided
2256 HeliosFileIOError: If file cannot be written
2257 HeliosRuntimeError: If a column label doesn't exist for any primitive
2260 >>> # Write temperature and area for all primitives
2261 >>> context.writePrimitiveData("output.txt", ["UUID", "temperature", "area"])
2263 >>> # Write with header row
2264 >>> context.writePrimitiveData("output.txt", ["UUID", "radiation_flux"], print_header=True)
2266 >>> # Write only for selected primitives
2267 >>> context.writePrimitiveData("subset.txt", ["temperature"], UUIDs=[uuid1, uuid2])
2272 if not column_labels:
2273 raise ValueError(
"column_labels list cannot be empty")
2280 context_wrapper.writePrimitiveData(self.
context, validated_filename, column_labels, print_header)
2284 raise ValueError(
"UUIDs list cannot be empty when provided. Use UUIDs=None to include all primitives")
2290 context_wrapper.writePrimitiveDataWithUUIDs(self.
context, validated_filename, column_labels, UUIDs, print_header)
2293 colors: Optional[np.ndarray] =
None) -> List[int]:
2295 Add triangles from NumPy arrays (compatible with trimesh, Open3D format).
2298 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2299 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2300 colors: Optional NumPy array of shape (N, 3) or (M, 3) containing RGB colors as float32/float64
2301 If shape (N, 3): per-vertex colors
2302 If shape (M, 3): per-triangle colors
2305 List of UUIDs for the added triangles
2308 ValueError: If array dimensions are invalid
2311 if vertices.ndim != 2
or vertices.shape[1] != 3:
2312 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2313 if faces.ndim != 2
or faces.shape[1] != 3:
2314 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2317 max_vertex_index = np.max(faces)
2318 if max_vertex_index >= vertices.shape[0]:
2319 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2322 per_vertex_colors =
False
2323 per_triangle_colors =
False
2324 if colors
is not None:
2325 if colors.ndim != 2
or colors.shape[1] != 3:
2326 raise ValueError(f
"Colors array must have shape (N, 3) or (M, 3), got {colors.shape}")
2327 if colors.shape[0] == vertices.shape[0]:
2328 per_vertex_colors =
True
2329 elif colors.shape[0] == faces.shape[0]:
2330 per_triangle_colors =
True
2332 raise ValueError(f
"Colors array shape {colors.shape} doesn't match vertices ({vertices.shape[0]},) or faces ({faces.shape[0]},)")
2335 vertices_float = vertices.astype(np.float32)
2336 faces_int = faces.astype(np.int32)
2337 if colors
is not None:
2338 colors_float = colors.astype(np.float32)
2342 for i
in range(faces.shape[0]):
2344 v0_idx, v1_idx, v2_idx = faces_int[i]
2347 vertex0 = vertices_float[v0_idx].tolist()
2348 vertex1 = vertices_float[v1_idx].tolist()
2349 vertex2 = vertices_float[v2_idx].tolist()
2354 uuid = context_wrapper.addTriangle(self.
context, vertex0, vertex1, vertex2)
2355 elif per_triangle_colors:
2357 color = colors_float[i].tolist()
2358 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2359 elif per_vertex_colors:
2361 color = np.mean([colors_float[v0_idx], colors_float[v1_idx], colors_float[v2_idx]], axis=0).tolist()
2362 uuid = context_wrapper.addTriangleWithColor(self.
context, vertex0, vertex1, vertex2, color)
2364 triangle_uuids.append(uuid)
2366 return triangle_uuids
2369 uv_coords: np.ndarray, texture_files: Union[str, List[str]],
2370 material_ids: Optional[np.ndarray] =
None) -> List[int]:
2372 Add textured triangles from NumPy arrays with support for multiple textures.
2374 This method supports both single-texture and multi-texture workflows:
2375 - Single texture: Pass a single texture file string, all faces use the same texture
2376 - Multiple textures: Pass a list of texture files and material_ids array specifying which texture each face uses
2379 vertices: NumPy array of shape (N, 3) containing vertex coordinates as float32/float64
2380 faces: NumPy array of shape (M, 3) containing triangle vertex indices as int32/int64
2381 uv_coords: NumPy array of shape (N, 2) containing UV texture coordinates as float32/float64
2382 texture_files: Single texture file path (str) or list of texture file paths (List[str])
2383 material_ids: Optional NumPy array of shape (M,) containing material ID for each face.
2384 If None and texture_files is a list, all faces use texture 0.
2385 If None and texture_files is a string, this parameter is ignored.
2388 List of UUIDs for the added textured triangles
2391 ValueError: If array dimensions are invalid or material IDs are out of range
2394 # Single texture usage (backward compatible)
2395 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, "texture.png")
2397 # Multi-texture usage (Open3D style)
2398 >>> texture_files = ["wood.png", "metal.png", "glass.png"]
2399 >>> material_ids = np.array([0, 0, 1, 1, 2, 2]) # 6 faces using different textures
2400 >>> uuids = context.addTrianglesFromArraysTextured(vertices, faces, uvs, texture_files, material_ids)
2405 if vertices.ndim != 2
or vertices.shape[1] != 3:
2406 raise ValueError(f
"Vertices array must have shape (N, 3), got {vertices.shape}")
2407 if faces.ndim != 2
or faces.shape[1] != 3:
2408 raise ValueError(f
"Faces array must have shape (M, 3), got {faces.shape}")
2409 if uv_coords.ndim != 2
or uv_coords.shape[1] != 2:
2410 raise ValueError(f
"UV coordinates array must have shape (N, 2), got {uv_coords.shape}")
2413 if uv_coords.shape[0] != vertices.shape[0]:
2414 raise ValueError(f
"UV coordinates count ({uv_coords.shape[0]}) must match vertices count ({vertices.shape[0]})")
2417 max_vertex_index = np.max(faces)
2418 if max_vertex_index >= vertices.shape[0]:
2419 raise ValueError(f
"Face indices reference vertex {max_vertex_index}, but only {vertices.shape[0]} vertices provided")
2422 if isinstance(texture_files, str):
2424 texture_file_list = [texture_files]
2425 if material_ids
is None:
2426 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2429 if not np.all(material_ids == 0):
2430 raise ValueError(
"When using single texture file, all material IDs must be 0")
2433 texture_file_list = list(texture_files)
2434 if len(texture_file_list) == 0:
2435 raise ValueError(
"Texture files list cannot be empty")
2437 if material_ids
is None:
2439 material_ids = np.zeros(faces.shape[0], dtype=np.uint32)
2442 if material_ids.ndim != 1
or material_ids.shape[0] != faces.shape[0]:
2443 raise ValueError(f
"Material IDs must have shape ({faces.shape[0]},), got {material_ids.shape}")
2446 max_material_id = np.max(material_ids)
2447 if max_material_id >= len(texture_file_list):
2448 raise ValueError(f
"Material ID {max_material_id} exceeds texture count {len(texture_file_list)}")
2451 for i, texture_file
in enumerate(texture_file_list):
2454 except (FileNotFoundError, ValueError)
as e:
2455 raise ValueError(f
"Texture file {i} ({texture_file}): {e}")
2458 if 'addTrianglesFromArraysMultiTextured' in context_wrapper._AVAILABLE_TRIANGLE_FUNCTIONS:
2459 return context_wrapper.addTrianglesFromArraysMultiTextured(
2460 self.
context, vertices, faces, uv_coords, texture_file_list, material_ids
2464 from .wrappers.DataTypes
import vec3, vec2
2466 vertices_float = vertices.astype(np.float32)
2467 faces_int = faces.astype(np.int32)
2468 uv_coords_float = uv_coords.astype(np.float32)
2471 for i
in range(faces.shape[0]):
2473 v0_idx, v1_idx, v2_idx = faces_int[i]
2476 vertex0 =
vec3(vertices_float[v0_idx][0], vertices_float[v0_idx][1], vertices_float[v0_idx][2])
2477 vertex1 =
vec3(vertices_float[v1_idx][0], vertices_float[v1_idx][1], vertices_float[v1_idx][2])
2478 vertex2 =
vec3(vertices_float[v2_idx][0], vertices_float[v2_idx][1], vertices_float[v2_idx][2])
2481 uv0 =
vec2(uv_coords_float[v0_idx][0], uv_coords_float[v0_idx][1])
2482 uv1 =
vec2(uv_coords_float[v1_idx][0], uv_coords_float[v1_idx][1])
2483 uv2 =
vec2(uv_coords_float[v2_idx][0], uv_coords_float[v2_idx][1])
2486 material_id = material_ids[i]
2487 texture_file = texture_file_list[material_id]
2491 triangle_uuids.append(uuid)
2493 return triangle_uuids
2501 Set primitive data as signed 32-bit integer for one or multiple primitives.
2504 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2505 label: String key for the data
2506 value: Signed integer value (broadcast to all UUIDs if list provided)
2508 if isinstance(uuids_or_uuid, (list, tuple)):
2509 context_wrapper.setBroadcastPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2511 context_wrapper.setPrimitiveDataInt(self.
context, uuids_or_uuid, label, value)
2515 Set primitive data as unsigned 32-bit integer for one or multiple primitives.
2517 Critical for properties like 'twosided_flag' which must be uint in C++.
2520 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2521 label: String key for the data
2522 value: Unsigned integer value (broadcast to all UUIDs if list provided)
2524 if isinstance(uuids_or_uuid, (list, tuple)):
2525 context_wrapper.setBroadcastPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2527 context_wrapper.setPrimitiveDataUInt(self.
context, uuids_or_uuid, label, value)
2531 Set primitive data as 32-bit float for one or multiple primitives.
2534 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2535 label: String key for the data
2536 value: Float value (broadcast to all UUIDs if list provided)
2538 if isinstance(uuids_or_uuid, (list, tuple)):
2539 context_wrapper.setBroadcastPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2541 context_wrapper.setPrimitiveDataFloat(self.
context, uuids_or_uuid, label, value)
2545 Set primitive data as 64-bit double for one or multiple primitives.
2548 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2549 label: String key for the data
2550 value: Double value (broadcast to all UUIDs if list provided)
2552 if isinstance(uuids_or_uuid, (list, tuple)):
2553 context_wrapper.setBroadcastPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2555 context_wrapper.setPrimitiveDataDouble(self.
context, uuids_or_uuid, label, value)
2559 Set primitive data as string for one or multiple primitives.
2562 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2563 label: String key for the data
2564 value: String value (broadcast to all UUIDs if list provided)
2566 if isinstance(uuids_or_uuid, (list, tuple)):
2567 context_wrapper.setBroadcastPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2569 context_wrapper.setPrimitiveDataString(self.
context, uuids_or_uuid, label, value)
2573 Set primitive data as vec2 for one or multiple primitives.
2576 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2577 label: String key for the data
2578 x_or_vec: Either x component (float) or vec2 object
2579 y: Y component (if x_or_vec is float)
2581 if hasattr(x_or_vec,
'x'):
2582 x, y = x_or_vec.x, x_or_vec.y
2585 if isinstance(uuids_or_uuid, (list, tuple)):
2586 context_wrapper.setBroadcastPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2588 context_wrapper.setPrimitiveDataVec2(self.
context, uuids_or_uuid, label, x, y)
2590 def setPrimitiveDataVec3(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
2592 Set primitive data as vec3 for one or multiple primitives.
2595 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2596 label: String key for the data
2597 x_or_vec: Either x component (float) or vec3 object
2598 y: Y component (if x_or_vec is float)
2599 z: Z component (if x_or_vec is float)
2601 if hasattr(x_or_vec,
'x'):
2602 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2605 if isinstance(uuids_or_uuid, (list, tuple)):
2606 context_wrapper.setBroadcastPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2608 context_wrapper.setPrimitiveDataVec3(self.
context, uuids_or_uuid, label, x, y, z)
2610 def setPrimitiveDataVec4(self, uuids_or_uuid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
2612 Set primitive data as vec4 for one or multiple primitives.
2615 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2616 label: String key for the data
2617 x_or_vec: Either x component (float) or vec4 object
2618 y: Y component (if x_or_vec is float)
2619 z: Z component (if x_or_vec is float)
2620 w: W component (if x_or_vec is float)
2622 if hasattr(x_or_vec,
'x'):
2623 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2626 if isinstance(uuids_or_uuid, (list, tuple)):
2627 context_wrapper.setBroadcastPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2629 context_wrapper.setPrimitiveDataVec4(self.
context, uuids_or_uuid, label, x, y, z, w)
2633 Set primitive data as int2 for one or multiple primitives.
2636 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2637 label: String key for the data
2638 x_or_vec: Either x component (int) or int2 object
2639 y: Y component (if x_or_vec is int)
2641 if hasattr(x_or_vec,
'x'):
2642 x, y = x_or_vec.x, x_or_vec.y
2645 if isinstance(uuids_or_uuid, (list, tuple)):
2646 context_wrapper.setBroadcastPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2648 context_wrapper.setPrimitiveDataInt2(self.
context, uuids_or_uuid, label, x, y)
2650 def setPrimitiveDataInt3(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
2652 Set primitive data as int3 for one or multiple primitives.
2655 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2656 label: String key for the data
2657 x_or_vec: Either x component (int) or int3 object
2658 y: Y component (if x_or_vec is int)
2659 z: Z component (if x_or_vec is int)
2661 if hasattr(x_or_vec,
'x'):
2662 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
2665 if isinstance(uuids_or_uuid, (list, tuple)):
2666 context_wrapper.setBroadcastPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2668 context_wrapper.setPrimitiveDataInt3(self.
context, uuids_or_uuid, label, x, y, z)
2670 def setPrimitiveDataInt4(self, uuids_or_uuid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
2672 Set primitive data as int4 for one or multiple primitives.
2675 uuids_or_uuid: Single UUID (int) or list of UUIDs to set data for
2676 label: String key for the data
2677 x_or_vec: Either x component (int) or int4 object
2678 y: Y component (if x_or_vec is int)
2679 z: Z component (if x_or_vec is int)
2680 w: W component (if x_or_vec is int)
2682 if hasattr(x_or_vec,
'x'):
2683 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
2686 if isinstance(uuids_or_uuid, (list, tuple)):
2687 context_wrapper.setBroadcastPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2689 context_wrapper.setPrimitiveDataInt4(self.
context, uuids_or_uuid, label, x, y, z, w)
2693 Get primitive data for a specific primitive. If data_type is provided, it works like before.
2694 If data_type is None, it automatically detects the type and returns the appropriate value.
2697 uuid: UUID of the primitive
2698 label: String key for the data
2699 data_type: Optional. Python type to retrieve (int, uint, float, double, bool, str, vec2, vec3, vec4, int2, int3, int4, etc.)
2700 If None, auto-detects the type using C++ getPrimitiveDataType().
2703 The stored value of the specified or auto-detected type
2706 if data_type
is None:
2707 return context_wrapper.getPrimitiveDataAuto(self.
context, uuid, label)
2710 if data_type == int:
2711 return context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2712 elif data_type == float:
2713 return context_wrapper.getPrimitiveDataFloat(self.
context, uuid, label)
2714 elif data_type == bool:
2716 int_value = context_wrapper.getPrimitiveDataInt(self.
context, uuid, label)
2717 return int_value != 0
2718 elif data_type == str:
2719 return context_wrapper.getPrimitiveDataString(self.
context, uuid, label)
2722 elif data_type == vec2:
2723 coords = context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2724 return vec2(coords[0], coords[1])
2725 elif data_type == vec3:
2726 coords = context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2727 return vec3(coords[0], coords[1], coords[2])
2728 elif data_type == vec4:
2729 coords = context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2730 return vec4(coords[0], coords[1], coords[2], coords[3])
2731 elif data_type == int2:
2732 coords = context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2733 return int2(coords[0], coords[1])
2734 elif data_type == int3:
2735 coords = context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2736 return int3(coords[0], coords[1], coords[2])
2737 elif data_type == int4:
2738 coords = context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2739 return int4(coords[0], coords[1], coords[2], coords[3])
2742 elif data_type ==
"uint":
2743 return context_wrapper.getPrimitiveDataUInt(self.
context, uuid, label)
2744 elif data_type ==
"double":
2745 return context_wrapper.getPrimitiveDataDouble(self.
context, uuid, label)
2748 elif data_type == list:
2750 return context_wrapper.getPrimitiveDataVec3(self.
context, uuid, label)
2751 elif data_type ==
"list_vec2":
2752 return context_wrapper.getPrimitiveDataVec2(self.
context, uuid, label)
2753 elif data_type ==
"list_vec4":
2754 return context_wrapper.getPrimitiveDataVec4(self.
context, uuid, label)
2755 elif data_type ==
"list_int2":
2756 return context_wrapper.getPrimitiveDataInt2(self.
context, uuid, label)
2757 elif data_type ==
"list_int3":
2758 return context_wrapper.getPrimitiveDataInt3(self.
context, uuid, label)
2759 elif data_type ==
"list_int4":
2760 return context_wrapper.getPrimitiveDataInt4(self.
context, uuid, label)
2763 raise ValueError(f
"Unsupported primitive data type: {data_type}. "
2764 f
"Supported types: int, float, bool, str, vec2, vec3, vec4, int2, int3, int4, "
2765 f
"'uint', 'double', list (for vec3), 'list_vec2', 'list_vec4', 'list_int2', 'list_int3', 'list_int4'")
2769 Check if primitive data exists for a specific primitive and label.
2772 uuid: UUID of the primitive
2773 label: String key for the data
2776 True if the data exists, False otherwise
2778 return context_wrapper.doesPrimitiveDataExistWrapper(self.
context, uuid, label)
2782 Convenience method to get float primitive data.
2785 uuid: UUID of the primitive
2786 label: String key for the data
2789 Float value stored for the primitive
2795 Get the Helios data type of primitive data.
2798 uuid: UUID of the primitive
2799 label: String key for the data
2802 HeliosDataType enum value as integer
2804 return context_wrapper.getPrimitiveDataTypeWrapper(self.
context, uuid, label)
2808 Get the size/length of primitive data (for vector data).
2811 uuid: UUID of the primitive
2812 label: String key for the data
2815 Size of data array, or 1 for scalar data
2817 return context_wrapper.getPrimitiveDataSizeWrapper(self.
context, uuid, label)
2821 Get primitive data values for multiple primitives as a NumPy array.
2823 This method retrieves primitive data for a list of UUIDs and returns the values
2824 as a NumPy array. The output array has the same length as the input UUID list,
2825 with each index corresponding to the primitive data value for that UUID.
2828 uuids: List of primitive UUIDs to get data for
2829 label: String key for the primitive data to retrieve
2832 NumPy array of primitive data values corresponding to each UUID.
2833 The array type depends on the data type:
2834 - int data: int32 array
2835 - uint data: uint32 array
2836 - float data: float32 array
2837 - double data: float64 array
2838 - vector data: float32 array with shape (N, vector_size)
2839 - string data: object array of strings
2842 ValueError: If UUID list is empty or UUIDs don't exist
2843 RuntimeError: If context is in mock mode or data doesn't exist for some UUIDs
2848 raise ValueError(
"UUID list cannot be empty")
2857 raise ValueError(f
"Primitive data '{label}' does not exist for UUID {uuid}")
2860 first_uuid = uuids[0]
2866 result = np.empty(len(uuids), dtype=np.int32)
2867 for i, uuid
in enumerate(uuids):
2870 elif data_type == 1:
2871 result = np.empty(len(uuids), dtype=np.uint32)
2872 for i, uuid
in enumerate(uuids):
2875 elif data_type == 2:
2876 result = np.empty(len(uuids), dtype=np.float32)
2877 for i, uuid
in enumerate(uuids):
2880 elif data_type == 3:
2881 result = np.empty(len(uuids), dtype=np.float64)
2882 for i, uuid
in enumerate(uuids):
2885 elif data_type == 4:
2886 result = np.empty((len(uuids), 2), dtype=np.float32)
2887 for i, uuid
in enumerate(uuids):
2889 result[i] = [vec_data.x, vec_data.y]
2891 elif data_type == 5:
2892 result = np.empty((len(uuids), 3), dtype=np.float32)
2893 for i, uuid
in enumerate(uuids):
2895 result[i] = [vec_data.x, vec_data.y, vec_data.z]
2897 elif data_type == 6:
2898 result = np.empty((len(uuids), 4), dtype=np.float32)
2899 for i, uuid
in enumerate(uuids):
2901 result[i] = [vec_data.x, vec_data.y, vec_data.z, vec_data.w]
2903 elif data_type == 7:
2904 result = np.empty((len(uuids), 2), dtype=np.int32)
2905 for i, uuid
in enumerate(uuids):
2907 result[i] = [int_data.x, int_data.y]
2909 elif data_type == 8:
2910 result = np.empty((len(uuids), 3), dtype=np.int32)
2911 for i, uuid
in enumerate(uuids):
2913 result[i] = [int_data.x, int_data.y, int_data.z]
2915 elif data_type == 9:
2916 result = np.empty((len(uuids), 4), dtype=np.int32)
2917 for i, uuid
in enumerate(uuids):
2919 result[i] = [int_data.x, int_data.y, int_data.z, int_data.w]
2921 elif data_type == 10:
2922 result = np.empty(len(uuids), dtype=object)
2923 for i, uuid
in enumerate(uuids):
2927 raise ValueError(f
"Unsupported primitive data type: {data_type}")
2933 colormap: str =
"hot", ncolors: int = 10,
2934 max_val: Optional[float] =
None, min_val: Optional[float] =
None):
2936 Color primitives based on primitive data values using pseudocolor mapping.
2938 This method applies a pseudocolor mapping to primitives based on the values
2939 of specified primitive data. The primitive colors are updated to reflect the
2940 data values using a color map.
2943 uuids: List of primitive UUIDs to color
2944 primitive_data: Name of primitive data to use for coloring (e.g., "radiation_flux_SW")
2945 colormap: Color map name - options include "hot", "cool", "parula", "rainbow", "gray", "lava"
2946 ncolors: Number of discrete colors in color map (default: 10)
2947 max_val: Maximum value for color scale (auto-determined if None)
2948 min_val: Minimum value for color scale (auto-determined if None)
2950 if max_val
is not None and min_val
is not None:
2951 context_wrapper.colorPrimitiveByDataPseudocolorWithRange(
2952 self.
context, uuids, primitive_data, colormap, ncolors, max_val, min_val)
2954 context_wrapper.colorPrimitiveByDataPseudocolor(
2955 self.
context, uuids, primitive_data, colormap, ncolors)
2958 def setTime(self, hour: int, minute: int = 0, second: int = 0):
2960 Set the simulation time.
2964 minute: Minute (0-59), defaults to 0
2965 second: Second (0-59), defaults to 0
2968 ValueError: If time values are out of range
2969 NotImplementedError: If time/date functions not available in current library build
2972 >>> context.setTime(14, 30) # Set to 2:30 PM
2973 >>> context.setTime(9, 15, 30) # Set to 9:15:30 AM
2975 context_wrapper.setTime(self.
context, hour, minute, second)
2977 def setDate(self, year: int, month: int, day: int):
2979 Set the simulation date.
2982 year: Year (1900-3000)
2987 ValueError: If date values are out of range
2988 NotImplementedError: If time/date functions not available in current library build
2991 >>> context.setDate(2023, 6, 21) # Set to June 21, 2023
2993 context_wrapper.setDate(self.
context, year, month, day)
2997 Set the simulation date using Julian day number.
3000 julian_day: Julian day (1-366)
3001 year: Year (1900-3000)
3004 ValueError: If values are out of range
3005 NotImplementedError: If time/date functions not available in current library build
3008 >>> context.setDateJulian(172, 2023) # Set to day 172 of 2023 (June 21)
3010 context_wrapper.setDateJulian(self.
context, julian_day, year)
3014 Get the current simulation time.
3017 Tuple of (hour, minute, second) as integers
3020 NotImplementedError: If time/date functions not available in current library build
3023 >>> hour, minute, second = context.getTime()
3024 >>> print(f"Current time: {hour:02d}:{minute:02d}:{second:02d}")
3026 return context_wrapper.getTime(self.
context)
3030 Get the current simulation date.
3033 Tuple of (year, month, day) as integers
3036 NotImplementedError: If time/date functions not available in current library build
3039 >>> year, month, day = context.getDate()
3040 >>> print(f"Current date: {year}-{month:02d}-{day:02d}")
3042 return context_wrapper.getDate(self.
context)
3048 def addTimeseriesData(self, label: str, value: float, date:
'Date', time:
'Time'):
3050 Add a data point to a timeseries variable.
3053 label: Name of the timeseries variable (e.g., "temperature")
3054 value: Value of the data point
3055 date: Date of the data point
3056 time: Time of the data point
3059 ValueError: If label is empty, or date/time are wrong types
3060 NotImplementedError: If timeseries functions not available
3063 >>> from pyhelios.types import Date, Time
3064 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3067 if not isinstance(label, str)
or not label:
3068 raise ValueError(
"Label must be a non-empty string")
3069 if not isinstance(date, Date):
3070 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3071 if not isinstance(time, Time):
3072 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3074 context_wrapper.addTimeseriesData(
3075 self.
context, label, float(value),
3076 date.day, date.month, date.year,
3077 time.hour, time.minute, time.second
3082 Update the value of an existing timeseries data point.
3085 label: Name of the timeseries variable (must already exist)
3086 date: Date of the existing point (must match exactly)
3087 time: Time of the existing point (must match exactly)
3088 new_value: Replacement value
3091 ValueError: If label is empty, or date/time are wrong types
3092 HeliosRuntimeError: If the variable does not exist or no point matches the (date, time)
3093 NotImplementedError: If timeseries functions not available
3096 >>> from pyhelios.types import Date, Time
3097 >>> context.addTimeseriesData("temperature", 25.3, Date(2024, 6, 15), Time(12, 0, 0))
3098 >>> context.updateTimeseriesData("temperature", Date(2024, 6, 15), Time(12, 0, 0), 26.5)
3101 if not isinstance(label, str)
or not label:
3102 raise ValueError(
"Label must be a non-empty string")
3103 if not isinstance(date, Date):
3104 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3105 if not isinstance(time, Time):
3106 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3108 context_wrapper.updateTimeseriesData(
3110 date.day, date.month, date.year,
3111 time.hour, time.minute, time.second,
3117 Set the Context date and time from a timeseries data point index.
3120 label: Name of the timeseries variable
3121 index: Index of the data point (0 = earliest, chronologically ordered)
3124 ValueError: If label is empty or index is negative
3125 NotImplementedError: If timeseries functions not available
3128 >>> context.setCurrentTimeseriesPoint("temperature", 0)
3131 if not isinstance(label, str)
or not label:
3132 raise ValueError(
"Label must be a non-empty string")
3133 if not isinstance(index, int)
or index < 0:
3134 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3136 context_wrapper.setCurrentTimeseriesPoint(self.
context, label, index)
3139 index: int =
None) -> float:
3141 Query a timeseries data value.
3143 Three modes of operation:
3144 - With date and time: returns interpolated value at the specified date/time
3145 - With index: returns value at the specified data point index
3146 - With neither: returns value at the current Context date/time
3149 label: Name of the timeseries variable
3150 date: Date to query at (requires time as well)
3151 time: Time to query at (requires date as well)
3152 index: Index of the data point (0 = earliest)
3155 The timeseries value as a float
3158 ValueError: If both date/time and index are provided, or if date without time
3159 NotImplementedError: If timeseries functions not available
3162 >>> # Query at specific date/time
3163 >>> val = context.queryTimeseriesData("temperature", date=Date(2024, 6, 15), time=Time(12, 0, 0))
3164 >>> # Query by index
3165 >>> val = context.queryTimeseriesData("temperature", index=0)
3166 >>> # Query at current context time
3167 >>> val = context.queryTimeseriesData("temperature")
3170 if not isinstance(label, str)
or not label:
3171 raise ValueError(
"Label must be a non-empty string")
3173 has_datetime = date
is not None or time
is not None
3174 has_index = index
is not None
3176 if has_datetime
and has_index:
3177 raise ValueError(
"Cannot specify both date/time and index. Use one or the other.")
3180 if date
is None or time
is None:
3181 raise ValueError(
"Both date and time must be provided together")
3182 if not isinstance(date, Date):
3183 raise ValueError(f
"date must be a Date instance, got {type(date).__name__}")
3184 if not isinstance(time, Time):
3185 raise ValueError(f
"time must be a Time instance, got {type(time).__name__}")
3186 return context_wrapper.queryTimeseriesDataDateTime(
3188 date.day, date.month, date.year,
3189 time.hour, time.minute, time.second
3193 if not isinstance(index, int)
or index < 0:
3194 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3195 return context_wrapper.queryTimeseriesDataIndex(self.
context, label, index)
3197 return context_wrapper.queryTimeseriesDataCurrent(self.
context, label)
3201 Get the Time associated with a timeseries data point.
3204 label: Name of the timeseries variable
3205 index: Index of the data point (0 = earliest)
3208 Time object for the data point
3211 ValueError: If label is empty or index is negative
3212 NotImplementedError: If timeseries functions not available
3215 >>> t = context.queryTimeseriesTime("temperature", 0)
3216 >>> print(f"{t.hour:02d}:{t.minute:02d}:{t.second:02d}")
3219 if not isinstance(label, str)
or not label:
3220 raise ValueError(
"Label must be a non-empty string")
3221 if not isinstance(index, int)
or index < 0:
3222 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3224 hour, minute, second = context_wrapper.queryTimeseriesTime(self.
context, label, index)
3225 return Time(hour=hour, minute=minute, second=second)
3229 Get the Date associated with a timeseries data point.
3232 label: Name of the timeseries variable
3233 index: Index of the data point (0 = earliest)
3236 Date object for the data point
3239 ValueError: If label is empty or index is negative
3240 NotImplementedError: If timeseries functions not available
3243 >>> d = context.queryTimeseriesDate("temperature", 0)
3244 >>> print(f"{d.year}-{d.month:02d}-{d.day:02d}")
3247 if not isinstance(label, str)
or not label:
3248 raise ValueError(
"Label must be a non-empty string")
3249 if not isinstance(index, int)
or index < 0:
3250 raise ValueError(f
"Index must be a non-negative integer, got {index}")
3252 year, month, day = context_wrapper.queryTimeseriesDate(self.
context, label, index)
3253 return Date(year=year, month=month, day=day)
3257 Get the number of data points in a timeseries variable.
3260 label: Name of the timeseries variable
3263 Number of data points
3266 ValueError: If label is empty
3267 NotImplementedError: If timeseries functions not available
3270 >>> n = context.getTimeseriesLength("temperature")
3271 >>> print(f"Timeseries has {n} data points")
3274 if not isinstance(label, str)
or not label:
3275 raise ValueError(
"Label must be a non-empty string")
3277 return context_wrapper.getTimeseriesLength(self.
context, label)
3281 Check whether a timeseries variable exists.
3284 label: Name of the timeseries variable
3287 True if the variable exists, False otherwise
3290 ValueError: If label is empty
3291 NotImplementedError: If timeseries functions not available
3294 >>> if context.doesTimeseriesVariableExist("temperature"):
3295 ... print("Temperature data loaded")
3298 if not isinstance(label, str)
or not label:
3299 raise ValueError(
"Label must be a non-empty string")
3301 return context_wrapper.doesTimeseriesVariableExist(self.
context, label)
3305 List all existing timeseries variables.
3308 List of timeseries variable names
3311 NotImplementedError: If timeseries functions not available
3314 >>> variables = context.listTimeseriesVariables()
3315 >>> for var in variables:
3316 ... print(f" {var}: {context.getTimeseriesLength(var)} points")
3320 return context_wrapper.listTimeseriesVariables(self.
context)
3323 """Clear all timeseries data from the Context.
3325 Removes all timeseries variables and their associated date/time values.
3328 NotImplementedError: If timeseries functions not available
3331 >>> context.clearTimeseriesData()
3332 >>> context.listTimeseriesVariables()
3336 context_wrapper.clearTimeseriesData(self.
context)
3339 delimiter: str =
",", date_string_format: str =
"YYYYMMDD",
3340 headerlines: int = 0):
3342 Load tabular timeseries data from a text file.
3344 The file should contain columns of data with dates/times and measured values.
3345 Column labels specify how each column should be interpreted. Special labels
3346 include "year", "DOY", "date", "datetime", "hour", "minute", "second", "time".
3347 Other labels become timeseries variable names.
3350 data_file: Path to the text file containing tabular data
3351 column_labels: List of column label strings specifying what each column contains
3352 delimiter: Column delimiter string (default: ",")
3353 date_string_format: Format of date strings in the file. Supported formats:
3354 "YYYYMMDD", "YYYYMMDDHH", "YYYYMMDDHHMM", "DD/MM/YYYY",
3355 "MM/DD/YYYY", "DDMMYYYY", "YYYY-MM-DD", "DD/MM/YYYY HH:MM",
3356 "MM/DD/YYYY HH:MM", "ISO8601" (default: "YYYYMMDD")
3357 headerlines: Number of header lines to skip (default: 0)
3360 ValueError: If data_file is empty, column_labels is empty, or delimiter is empty
3361 RuntimeError: If the file cannot be read or parsed
3362 NotImplementedError: If timeseries functions not available
3365 >>> context.loadTabularTimeseriesData(
3366 ... "weather_data.csv",
3367 ... column_labels=["date", "hour", "temperature", "humidity"],
3371 >>> temp = context.queryTimeseriesData("temperature", index=0)
3374 if not isinstance(data_file, str)
or not data_file:
3375 raise ValueError(
"data_file must be a non-empty string")
3376 if not isinstance(column_labels, list)
or not column_labels:
3377 raise ValueError(
"column_labels must be a non-empty list of strings")
3378 for i, label
in enumerate(column_labels):
3379 if not isinstance(label, str):
3380 raise ValueError(f
"column_labels[{i}] must be a string, got {type(label).__name__}")
3381 if not isinstance(delimiter, str)
or not delimiter:
3382 raise ValueError(
"delimiter must be a non-empty string")
3384 context_wrapper.loadTabularTimeseriesData(
3385 self.
context, data_file, column_labels, delimiter,
3386 date_string_format, headerlines
3393 def deletePrimitive(self, uuids_or_uuid: Union[int, List[int]]) ->
None:
3395 Delete one or more primitives from the context.
3397 This removes the primitive(s) entirely from the context. If a primitive
3398 belongs to a compound object, it will be removed from that object. If the
3399 object becomes empty after removal, it is automatically deleted.
3402 uuids_or_uuid: Single UUID (int) or list of UUIDs to delete
3405 RuntimeError: If any UUID doesn't exist in the context
3406 ValueError: If UUID is invalid (negative)
3407 NotImplementedError: If delete functions not available in current library build
3410 >>> context = Context()
3411 >>> patch_id = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
3412 >>> context.deletePrimitive(patch_id) # Single deletion
3414 >>> # Multiple deletion
3415 >>> ids = [context.addPatch() for _ in range(5)]
3416 >>> context.deletePrimitive(ids) # Delete all at once
3420 if isinstance(uuids_or_uuid, (list, tuple)):
3421 for uuid
in uuids_or_uuid:
3423 raise ValueError(f
"UUID must be non-negative, got {uuid}")
3424 context_wrapper.deletePrimitives(self.
context, list(uuids_or_uuid))
3426 if uuids_or_uuid < 0:
3427 raise ValueError(f
"UUID must be non-negative, got {uuids_or_uuid}")
3428 context_wrapper.deletePrimitive(self.
context, uuids_or_uuid)
3430 def deleteObject(self, objIDs_or_objID: Union[int, List[int]]) ->
None:
3432 Delete one or more compound objects from the context.
3434 This removes the compound object(s) AND all their child primitives.
3435 Use this when you want to delete an entire object hierarchy at once.
3438 objIDs_or_objID: Single object ID (int) or list of object IDs to delete
3441 RuntimeError: If any object ID doesn't exist in the context
3442 ValueError: If object ID is invalid (negative)
3443 NotImplementedError: If delete functions not available in current library build
3446 >>> context = Context()
3447 >>> # Create a compound object (e.g., a tile with multiple patches)
3448 >>> patch_ids = context.addTile(center=vec3(0, 0, 0), size=vec2(2, 2),
3449 ... tile_divisions=int2(2, 2))
3450 >>> obj_id = context.getPrimitiveParentObjectID(patch_ids[0])
3451 >>> context.deleteObject(obj_id) # Deletes tile and all its patches
3455 if isinstance(objIDs_or_objID, (list, tuple)):
3456 for objID
in objIDs_or_objID:
3458 raise ValueError(f
"Object ID must be non-negative, got {objID}")
3459 context_wrapper.deleteObjects(self.
context, list(objIDs_or_objID))
3461 if objIDs_or_objID < 0:
3462 raise ValueError(f
"Object ID must be non-negative, got {objIDs_or_objID}")
3463 context_wrapper.deleteObject(self.
context, objIDs_or_objID)
3468 Get list of available plugins for this PyHelios instance.
3471 List of available plugin names
3477 Check if a specific plugin is available.
3480 plugin_name: Name of the plugin to check
3483 True if plugin is available, False otherwise
3489 Get detailed information about available plugin capabilities.
3492 Dictionary mapping plugin names to capability information
3497 """Print detailed plugin status information."""
3502 Get list of requested plugins that are not available.
3505 requested_plugins: List of plugin names to check
3508 List of missing plugin names
3518 Create a new material for sharing visual properties across primitives.
3520 Materials enable efficient memory usage by allowing multiple primitives to
3521 share rendering properties. Changes to a material affect all primitives using it.
3524 material_label: Unique label for the material
3527 RuntimeError: If material label already exists
3530 >>> context.addMaterial("wood_oak")
3531 >>> context.setMaterialColor("wood_oak", (0.6, 0.4, 0.2, 1.0))
3532 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3534 context_wrapper.addMaterial(self.
context, material_label)
3537 """Check if a material with the given label exists."""
3538 return context_wrapper.doesMaterialExist(self.
context, material_label)
3541 """Get list of all material labels in the context."""
3542 return context_wrapper.listMaterials(self.
context)
3546 Delete a material from the context.
3548 Primitives using this material will be reassigned to the default material.
3551 material_label: Label of the material to delete
3554 RuntimeError: If material doesn't exist
3556 context_wrapper.deleteMaterial(self.
context, material_label)
3560 Get the RGBA color of a material.
3563 material_label: Label of the material
3569 RuntimeError: If material doesn't exist
3571 from .wrappers.DataTypes
import RGBAcolor
3572 color_list = context_wrapper.getMaterialColor(self.
context, material_label)
3573 return RGBAcolor(color_list[0], color_list[1], color_list[2], color_list[3])
3577 Set the RGBA color of a material.
3579 This affects all primitives that reference this material.
3582 material_label: Label of the material
3583 color: RGBAcolor object or tuple/list of (r, g, b, a) values
3586 RuntimeError: If material doesn't exist
3589 >>> from pyhelios.types import RGBAcolor
3590 >>> context.setMaterialColor("wood", RGBAcolor(0.6, 0.4, 0.2, 1.0))
3591 >>> context.setMaterialColor("wood", (0.6, 0.4, 0.2, 1.0))
3593 if isinstance(color, RGBAcolor):
3594 r, g, b, a = color.r, color.g, color.b, color.a
3595 elif isinstance(color, (list, tuple))
and len(color) == 4:
3596 r, g, b, a = color[0], color[1], color[2], color[3]
3598 raise ValueError(f
"Color must be an RGBAcolor or a 4-element list/tuple, got {type(color).__name__}")
3599 context_wrapper.setMaterialColor(self.
context, material_label, r, g, b, a)
3603 Get the texture file path for a material.
3606 material_label: Label of the material
3609 Texture file path, or empty string if no texture
3612 RuntimeError: If material doesn't exist
3614 return context_wrapper.getMaterialTexture(self.
context, material_label)
3618 Set the texture file for a material.
3620 This affects all primitives that reference this material.
3623 material_label: Label of the material
3624 texture_file: Path to texture image file
3627 RuntimeError: If material doesn't exist or texture file not found
3629 context_wrapper.setMaterialTexture(self.
context, material_label, texture_file)
3632 """Check if material texture color is overridden by material color."""
3633 return context_wrapper.isMaterialTextureColorOverridden(self.
context, material_label)
3636 """Set whether material color overrides texture color."""
3637 context_wrapper.setMaterialTextureColorOverride(self.
context, material_label, override)
3640 """Get the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3641 return context_wrapper.getMaterialTwosidedFlag(self.
context, material_label)
3644 """Set the two-sided rendering flag for a material (0 = one-sided, 1 = two-sided)."""
3645 context_wrapper.setMaterialTwosidedFlag(self.
context, material_label, twosided_flag)
3649 Assign a material to primitive(s).
3652 uuid: Single UUID (int) or list of UUIDs (List[int])
3653 material_label: Label of the material to assign
3656 RuntimeError: If primitive or material doesn't exist
3659 >>> context.assignMaterialToPrimitive(uuid, "wood_oak")
3660 >>> context.assignMaterialToPrimitive([uuid1, uuid2, uuid3], "wood_oak")
3662 if isinstance(uuid, (list, tuple)):
3663 context_wrapper.assignMaterialToPrimitives(self.
context, uuid, material_label)
3665 context_wrapper.assignMaterialToPrimitive(self.
context, uuid, material_label)
3669 Assign a material to all primitives in compound object(s).
3672 objID: Single object ID (int) or list of object IDs (List[int])
3673 material_label: Label of the material to assign
3676 RuntimeError: If object or material doesn't exist
3679 >>> tree_id = wpt.buildTree(WPTType.LEMON)
3680 >>> context.assignMaterialToObject(tree_id, "tree_bark")
3681 >>> context.assignMaterialToObject([id1, id2], "grass")
3683 if isinstance(objID, (list, tuple)):
3684 context_wrapper.assignMaterialToObjects(self.
context, objID, material_label)
3686 context_wrapper.assignMaterialToObject(self.
context, objID, material_label)
3689 """Get the material label assigned to a primitive or multiple primitives.
3692 uuid: Single UUID (int) or list of UUIDs
3695 str for single UUID, or List[str] for list
3698 RuntimeError: If primitive doesn't exist
3700 if isinstance(uuid, (list, tuple)):
3704 ptr, offsets, total = context_wrapper.getBatchPrimitiveMaterialLabels(self.
context, uuid)
3705 if total == 0
or not ptr:
3706 return [
"" for _
in uuid]
3707 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3708 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3709 return context_wrapper.getPrimitiveMaterialLabel(self.
context, uuid)
3713 Get two-sided rendering flag for a primitive.
3715 Checks material first, then primitive data if no material assigned.
3718 uuid: UUID of the primitive
3719 default_value: Default value if no material/data (default 1 = two-sided)
3722 Two-sided flag (0 = one-sided, 1 = two-sided)
3724 return context_wrapper.getPrimitiveTwosidedFlag(self.
context, uuid, default_value)
3728 Get all primitive UUIDs that use a specific material.
3731 material_label: Label of the material
3734 List of primitive UUIDs using the material
3737 RuntimeError: If material doesn't exist
3739 return context_wrapper.getPrimitivesUsingMaterial(self.
context, material_label)
3746 """Get the texture file path of a primitive or multiple primitives.
3749 uuid: Single UUID (int) or list of UUIDs
3752 str for single UUID, or List[str] for list
3755 if isinstance(uuid, (list, tuple)):
3758 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureFiles(self.
context, uuid)
3759 if total == 0
or not ptr:
3760 return [
"" for _
in uuid]
3761 full_str = ptr.decode(
'utf-8')
if isinstance(ptr, bytes)
else ptr
3762 return [full_str[offsets[i]:offsets[i+1]]
for i
in range(len(uuid))]
3763 return context_wrapper.getPrimitiveTextureFile(self.
context, uuid)
3766 """Resolve material texture suppression for export.
3768 For each primitive, applies material-based texture suppression rules:
3769 1. If primitive has texture but material has no texture -> suppress texture, use material color
3770 2. If both have texture and textureColorOverride -> prefix "mask:", use material color
3771 3. Otherwise -> leave unchanged
3774 uuids: List of primitive UUIDs
3775 colors_np: numpy float32 array of shape (N, 3), modified IN-PLACE
3778 List[str] of resolved texture file paths
3783 return context_wrapper.resolveMaterialTextures(self.
context, uuids, colors_np)
3786 """Pack GPU-ready geometry buffers for a set of primitives in a single C++ pass.
3788 Produces a binary blob containing contiguous typed arrays (positions,
3789 colors, uvs, indices, faceToUuid) grouped by texture, ready for
3790 zero-copy loading into Three.js BufferGeometry attributes.
3793 uuids: List of primitive UUIDs
3796 bytes: Raw binary blob (see wire format v2 spec)
3801 return context_wrapper.packGPUBuffers(self.
context, uuids)
3804 """Set the texture file path of a primitive.
3807 uuid: UUID of the primitive
3808 texture_file: Path to the texture file
3811 context_wrapper.setPrimitiveTextureFile(self.
context, uuid, texture_file)
3814 """Get the texture size (width, height) of a primitive.
3817 uuid: UUID of the primitive
3820 int2 with width and height of the texture
3823 w, h = context_wrapper.getPrimitiveTextureSize(self.
context, uuid)
3827 """Get the texture UV coordinates of a primitive or multiple primitives.
3830 uuid: Single UUID (int) or list of UUIDs
3833 List[vec2] for single UUID, or tuple of (flat_data, offsets) for list
3836 if isinstance(uuid, (list, tuple)):
3838 return (np.empty((0,), dtype=np.float32), np.zeros((1,), dtype=np.uint32))
3839 ptr, offsets, total = context_wrapper.getBatchPrimitiveTextureUV(self.
context, uuid)
3840 offsets_arr = np.array(offsets, dtype=np.uint32)
3841 if total == 0
or not ptr:
3842 return (np.empty((0,), dtype=np.float32), offsets_arr)
3843 data = np.ctypeslib.as_array(ptr, shape=(total,)).copy()
3844 return (data, offsets_arr)
3845 uv_pairs = context_wrapper.getPrimitiveTextureUV(self.
context, uuid)
3846 return [
vec2(u, v)
for u, v
in uv_pairs]
3849 """Check if primitive texture has a transparency channel.
3852 uuid: UUID of the primitive
3855 True if texture has transparency channel
3858 return context_wrapper.primitiveTextureHasTransparencyChannel(self.
context, uuid)
3861 """Get the solid fraction of a primitive or multiple primitives.
3864 uuid: Single UUID (int) or list of UUIDs
3867 float for single UUID, or np.ndarray of shape (N,) for list
3870 if isinstance(uuid, (list, tuple)):
3872 return np.empty((0,), dtype=np.float32)
3873 ptr, size = context_wrapper.getBatchPrimitiveSolidFractions(self.
context, uuid)
3874 if size == 0
or not ptr:
3875 return np.empty((0,), dtype=np.float32)
3876 return np.ctypeslib.as_array(ptr, shape=(size,)).copy()
3877 return context_wrapper.getPrimitiveSolidFraction(self.
context, uuid)
3880 """Override texture color with constant RGB color for a primitive.
3883 uuid: UUID of the primitive
3886 context_wrapper.overridePrimitiveTextureColor(self.
context, uuid)
3889 """Use texture map color instead of constant RGB for a primitive.
3892 uuid: UUID of the primitive
3895 context_wrapper.usePrimitiveTextureColor(self.
context, uuid)
3898 """Check if primitive texture color is overridden.
3901 uuid: UUID of the primitive
3904 True if texture color is overridden with constant RGB
3907 return context_wrapper.isPrimitiveTextureColorOverridden(self.
context, uuid)
3914 """Get normals for all primitives. Returns ndarray of shape (N, 3)."""
3918 """Get colors for all primitives. Returns ndarray of shape (N, 3)."""
3922 """Get areas for all primitives. Returns ndarray of shape (N,)."""
3926 """Get types for all primitives. Returns ndarray of shape (N,) uint32."""
3930 """Get solid fractions for all primitives. Returns ndarray of shape (N,)."""
3934 """Get vertices for all primitives. Returns (flat_data, offsets) tuple."""
3938 """Get texture files for all primitives. Returns list of strings."""
3942 """Get material labels for all primitives. Returns list of strings."""
3948 """Hide one or more primitives. Hidden primitives are excluded from getAllUUIDs().
3951 uuids_or_uuid: Single UUID (int) or list of UUIDs to hide.
3953 if isinstance(uuids_or_uuid, (list, tuple)):
3954 context_wrapper.hidePrimitivesWrapper(self.
context, list(uuids_or_uuid))
3956 context_wrapper.hidePrimitiveWrapper(self.
context, uuids_or_uuid)
3959 """Show one or more previously hidden primitives.
3962 uuids_or_uuid: Single UUID (int) or list of UUIDs to show.
3964 if isinstance(uuids_or_uuid, (list, tuple)):
3965 context_wrapper.showPrimitivesWrapper(self.
context, list(uuids_or_uuid))
3967 context_wrapper.showPrimitiveWrapper(self.
context, uuids_or_uuid)
3970 """Check if a primitive is hidden.
3973 uuid: UUID of the primitive.
3976 True if the primitive is hidden.
3978 return context_wrapper.isPrimitiveHiddenWrapper(self.
context, uuid)
3980 def hideObject(self, objids_or_objid) -> None:
3981 """Hide one or more compound objects (and all their primitives).
3984 objids_or_objid: Single object ID (int) or list of object IDs to hide.
3986 if isinstance(objids_or_objid, (list, tuple)):
3987 context_wrapper.hideObjectsWrapper(self.
context, list(objids_or_objid))
3989 context_wrapper.hideObjectWrapper(self.
context, objids_or_objid)
3991 def showObject(self, objids_or_objid) -> None:
3992 """Show one or more previously hidden compound objects.
3995 objids_or_objid: Single object ID (int) or list of object IDs to show.
3997 if isinstance(objids_or_objid, (list, tuple)):
3998 context_wrapper.showObjectsWrapper(self.
context, list(objids_or_objid))
4000 context_wrapper.showObjectWrapper(self.
context, objids_or_objid)
4003 """Check if a compound object is hidden.
4009 True if the object is hidden.
4011 return context_wrapper.isObjectHiddenWrapper(self.
context, objID)
4015 def setObjectDataInt(self, objids_or_objid, label: str, value: int) ->
None:
4016 """Set object data as signed 32-bit integer for one or multiple objects."""
4017 if isinstance(objids_or_objid, (list, tuple)):
4018 context_wrapper.setBroadcastObjectDataInt(self.
context, objids_or_objid, label, value)
4020 context_wrapper.setObjectDataInt(self.
context, objids_or_objid, label, value)
4023 """Set object data as unsigned 32-bit integer for one or multiple objects."""
4024 if isinstance(objids_or_objid, (list, tuple)):
4025 context_wrapper.setBroadcastObjectDataUInt(self.
context, objids_or_objid, label, value)
4027 context_wrapper.setObjectDataUInt(self.
context, objids_or_objid, label, value)
4030 """Set object data as 32-bit float for one or multiple objects."""
4031 if isinstance(objids_or_objid, (list, tuple)):
4032 context_wrapper.setBroadcastObjectDataFloat(self.
context, objids_or_objid, label, value)
4034 context_wrapper.setObjectDataFloat(self.
context, objids_or_objid, label, value)
4037 """Set object data as 64-bit double for one or multiple objects."""
4038 if isinstance(objids_or_objid, (list, tuple)):
4039 context_wrapper.setBroadcastObjectDataDouble(self.
context, objids_or_objid, label, value)
4041 context_wrapper.setObjectDataDouble(self.
context, objids_or_objid, label, value)
4044 """Set object data as string for one or multiple objects."""
4045 if isinstance(objids_or_objid, (list, tuple)):
4046 context_wrapper.setBroadcastObjectDataString(self.
context, objids_or_objid, label, value)
4048 context_wrapper.setObjectDataString(self.
context, objids_or_objid, label, value)
4050 def setObjectDataVec2(self, objids_or_objid, label: str, x_or_vec, y: float =
None) ->
None:
4051 """Set object data as vec2. Accepts vec2 object or x,y components."""
4052 if hasattr(x_or_vec,
'x')
and y
is None:
4053 x, y = x_or_vec.x, x_or_vec.y
4056 if isinstance(objids_or_objid, (list, tuple)):
4057 context_wrapper.setBroadcastObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4059 context_wrapper.setObjectDataVec2(self.
context, objids_or_objid, label, x, y)
4061 def setObjectDataVec3(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4062 """Set object data as vec3. Accepts vec3 object or x,y,z components."""
4063 if hasattr(x_or_vec,
'x')
and y
is None:
4064 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4067 if isinstance(objids_or_objid, (list, tuple)):
4068 context_wrapper.setBroadcastObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4070 context_wrapper.setObjectDataVec3(self.
context, objids_or_objid, label, x, y, z)
4072 def setObjectDataVec4(self, objids_or_objid, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4073 """Set object data as vec4. Accepts vec4 object or x,y,z,w components."""
4074 if hasattr(x_or_vec,
'x')
and y
is None:
4075 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4078 if isinstance(objids_or_objid, (list, tuple)):
4079 context_wrapper.setBroadcastObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4081 context_wrapper.setObjectDataVec4(self.
context, objids_or_objid, label, x, y, z, w)
4083 def setObjectDataInt2(self, objids_or_objid, label: str, x_or_vec, y: int =
None) ->
None:
4084 """Set object data as int2. Accepts int2 object or x,y components."""
4085 if hasattr(x_or_vec,
'x')
and y
is None:
4086 x, y = x_or_vec.x, x_or_vec.y
4089 if isinstance(objids_or_objid, (list, tuple)):
4090 context_wrapper.setBroadcastObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4092 context_wrapper.setObjectDataInt2(self.
context, objids_or_objid, label, x, y)
4094 def setObjectDataInt3(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4095 """Set object data as int3. Accepts int3 object or x,y,z components."""
4096 if hasattr(x_or_vec,
'x')
and y
is None:
4097 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4100 if isinstance(objids_or_objid, (list, tuple)):
4101 context_wrapper.setBroadcastObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4103 context_wrapper.setObjectDataInt3(self.
context, objids_or_objid, label, x, y, z)
4105 def setObjectDataInt4(self, objids_or_objid, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4106 """Set object data as int4. Accepts int4 object or x,y,z,w components."""
4107 if hasattr(x_or_vec,
'x')
and y
is None:
4108 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4111 if isinstance(objids_or_objid, (list, tuple)):
4112 context_wrapper.setBroadcastObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4114 context_wrapper.setObjectDataInt4(self.
context, objids_or_objid, label, x, y, z, w)
4116 def getObjectData(self, objID: int, label: str, data_type: type =
None):
4117 """Get object data with optional type specification. Auto-detects type if not specified."""
4118 if data_type
is None:
4119 return context_wrapper.getObjectDataAuto(self.
context, objID, label)
4120 if data_type == int:
4121 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4122 elif data_type == float:
4123 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4124 elif data_type == str:
4125 return context_wrapper.getObjectDataString(self.
context, objID, label)
4126 elif data_type == vec3:
4127 coords = context_wrapper.getObjectDataVec3(self.
context, objID, label)
4128 return vec3(coords[0], coords[1], coords[2])
4129 elif data_type == vec2:
4130 coords = context_wrapper.getObjectDataVec2(self.
context, objID, label)
4131 return vec2(coords[0], coords[1])
4132 elif data_type == vec4:
4133 coords = context_wrapper.getObjectDataVec4(self.
context, objID, label)
4134 return vec4(coords[0], coords[1], coords[2], coords[3])
4135 elif data_type == int2:
4136 coords = context_wrapper.getObjectDataInt2(self.
context, objID, label)
4137 return int2(coords[0], coords[1])
4138 elif data_type == int3:
4139 coords = context_wrapper.getObjectDataInt3(self.
context, objID, label)
4140 return int3(coords[0], coords[1], coords[2])
4141 elif data_type == int4:
4142 coords = context_wrapper.getObjectDataInt4(self.
context, objID, label)
4143 return int4(coords[0], coords[1], coords[2], coords[3])
4144 elif data_type ==
"uint":
4145 return context_wrapper.getObjectDataUInt(self.
context, objID, label)
4146 elif data_type ==
"double":
4147 return context_wrapper.getObjectDataDouble(self.
context, objID, label)
4149 raise ValueError(f
"Unsupported object data type: {data_type}")
4152 """Get float object data."""
4153 return context_wrapper.getObjectDataFloat(self.
context, objID, label)
4156 """Get int object data."""
4157 return context_wrapper.getObjectDataInt(self.
context, objID, label)
4160 """Get string object data."""
4161 return context_wrapper.getObjectDataString(self.
context, objID, label)
4164 """Get the HeliosDataType enum for object data."""
4165 return context_wrapper.getObjectDataTypeWrapper(self.
context, objID, label)
4168 """Get the size of object data array."""
4169 return context_wrapper.getObjectDataSizeWrapper(self.
context, objID, label)
4172 """Check if object data exists."""
4173 return context_wrapper.doesObjectDataExistWrapper(self.
context, objID, label)
4176 """Clear object data. Accepts single ID or list."""
4177 if isinstance(objids_or_objid, (list, tuple)):
4178 context_wrapper.clearObjectDataBatchWrapper(self.
context, objids_or_objid, label)
4180 context_wrapper.clearObjectDataWrapper(self.
context, objids_or_objid, label)
4183 """List all data labels on a specific object."""
4184 return context_wrapper.listObjectDataWrapper(self.
context, objID)
4187 """List all object data labels in context."""
4188 return context_wrapper.listAllObjectDataLabelsWrapper(self.
context)
4191 """Copy object data to a new label."""
4192 context_wrapper.duplicateObjectDataWrapper(self.
context, objID, old_label, new_label)
4194 def renameObjectData(self, objID: int, old_label: str, new_label: str) ->
None:
4195 """Rename an object data label."""
4196 context_wrapper.renameObjectDataWrapper(self.
context, objID, old_label, new_label)
4198 def filterObjectsByData(self, objIDs: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4199 """Filter objects by data value. Auto-dispatches based on value type."""
4200 if isinstance(value, str):
4201 return context_wrapper.filterObjectsByDataStringWrapper(self.
context, objIDs, label, value)
4202 elif isinstance(value, float):
4203 return context_wrapper.filterObjectsByDataFloatWrapper(self.
context, objIDs, label, value, comparator)
4204 elif isinstance(value, int):
4205 return context_wrapper.filterObjectsByDataIntWrapper(self.
context, objIDs, label, value, comparator)
4207 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4212 """Set global data as signed 32-bit integer."""
4213 context_wrapper.setGlobalDataInt(self.
context, label, value)
4216 """Set global data as unsigned 32-bit integer."""
4217 context_wrapper.setGlobalDataUInt(self.
context, label, value)
4220 """Set global data as 32-bit float."""
4221 context_wrapper.setGlobalDataFloat(self.
context, label, value)
4224 """Set global data as 64-bit double."""
4225 context_wrapper.setGlobalDataDouble(self.
context, label, value)
4228 """Set global data as string."""
4229 context_wrapper.setGlobalDataString(self.
context, label, value)
4232 """Set global data as vec2."""
4233 if hasattr(x_or_vec,
'x')
and y
is None:
4234 x, y = x_or_vec.x, x_or_vec.y
4237 context_wrapper.setGlobalDataVec2(self.
context, label, x, y)
4239 def setGlobalDataVec3(self, label: str, x_or_vec, y: float =
None, z: float =
None) ->
None:
4240 """Set global data as vec3."""
4241 if hasattr(x_or_vec,
'x')
and y
is None:
4242 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4245 context_wrapper.setGlobalDataVec3(self.
context, label, x, y, z)
4247 def setGlobalDataVec4(self, label: str, x_or_vec, y: float =
None, z: float =
None, w: float =
None) ->
None:
4248 """Set global data as vec4."""
4249 if hasattr(x_or_vec,
'x')
and y
is None:
4250 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4253 context_wrapper.setGlobalDataVec4(self.
context, label, x, y, z, w)
4256 """Set global data as int2."""
4257 if hasattr(x_or_vec,
'x')
and y
is None:
4258 x, y = x_or_vec.x, x_or_vec.y
4261 context_wrapper.setGlobalDataInt2(self.
context, label, x, y)
4263 def setGlobalDataInt3(self, label: str, x_or_vec, y: int =
None, z: int =
None) ->
None:
4264 """Set global data as int3."""
4265 if hasattr(x_or_vec,
'x')
and y
is None:
4266 x, y, z = x_or_vec.x, x_or_vec.y, x_or_vec.z
4269 context_wrapper.setGlobalDataInt3(self.
context, label, x, y, z)
4271 def setGlobalDataInt4(self, label: str, x_or_vec, y: int =
None, z: int =
None, w: int =
None) ->
None:
4272 """Set global data as int4."""
4273 if hasattr(x_or_vec,
'x')
and y
is None:
4274 x, y, z, w = x_or_vec.x, x_or_vec.y, x_or_vec.z, x_or_vec.w
4277 context_wrapper.setGlobalDataInt4(self.
context, label, x, y, z, w)
4279 def getGlobalData(self, label: str, data_type: type =
None):
4280 """Get global data with optional type specification. Auto-detects type if not specified."""
4281 if data_type
is None:
4282 return context_wrapper.getGlobalDataAuto(self.
context, label)
4283 if data_type == int:
4284 return context_wrapper.getGlobalDataInt(self.
context, label)
4285 elif data_type == float:
4286 return context_wrapper.getGlobalDataFloat(self.
context, label)
4287 elif data_type == str:
4288 return context_wrapper.getGlobalDataString(self.
context, label)
4289 elif data_type == vec3:
4290 coords = context_wrapper.getGlobalDataVec3(self.
context, label)
4291 return vec3(coords[0], coords[1], coords[2])
4292 elif data_type == vec2:
4293 coords = context_wrapper.getGlobalDataVec2(self.
context, label)
4294 return vec2(coords[0], coords[1])
4295 elif data_type == vec4:
4296 coords = context_wrapper.getGlobalDataVec4(self.
context, label)
4297 return vec4(coords[0], coords[1], coords[2], coords[3])
4298 elif data_type == int2:
4299 coords = context_wrapper.getGlobalDataInt2(self.
context, label)
4300 return int2(coords[0], coords[1])
4301 elif data_type == int3:
4302 coords = context_wrapper.getGlobalDataInt3(self.
context, label)
4303 return int3(coords[0], coords[1], coords[2])
4304 elif data_type == int4:
4305 coords = context_wrapper.getGlobalDataInt4(self.
context, label)
4306 return int4(coords[0], coords[1], coords[2], coords[3])
4307 elif data_type ==
"uint":
4308 return context_wrapper.getGlobalDataUInt(self.
context, label)
4309 elif data_type ==
"double":
4310 return context_wrapper.getGlobalDataDouble(self.
context, label)
4312 raise ValueError(f
"Unsupported global data type: {data_type}")
4315 """Get float global data."""
4316 return context_wrapper.getGlobalDataFloat(self.
context, label)
4319 """Get int global data."""
4320 return context_wrapper.getGlobalDataInt(self.
context, label)
4323 """Get string global data."""
4324 return context_wrapper.getGlobalDataString(self.
context, label)
4327 """Get the HeliosDataType enum for global data."""
4328 return context_wrapper.getGlobalDataTypeWrapper(self.
context, label)
4331 """Get the size of global data array."""
4332 return context_wrapper.getGlobalDataSizeWrapper(self.
context, label)
4335 """Check if global data exists."""
4336 return context_wrapper.doesGlobalDataExistWrapper(self.
context, label)
4339 """Clear global data."""
4340 context_wrapper.clearGlobalDataWrapper(self.
context, label)
4343 """Rename a global data label."""
4344 context_wrapper.renameGlobalDataWrapper(self.
context, old_label, new_label)
4347 """Duplicate global data to a new label."""
4348 context_wrapper.duplicateGlobalDataWrapper(self.
context, old_label, new_label)
4351 """List all global data labels."""
4352 return context_wrapper.listGlobalDataWrapper(self.
context)
4355 """Increment global data. Auto-dispatches based on increment type."""
4356 if isinstance(increment, float):
4357 context_wrapper.incrementGlobalDataFloatWrapper(self.
context, label, increment)
4358 elif isinstance(increment, int):
4359 context_wrapper.incrementGlobalDataIntWrapper(self.
context, label, increment)
4361 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4366 """Calculate arithmetic mean of primitive data across UUIDs.
4369 uuids: List of primitive UUIDs.
4371 return_type: float (default), "double", or vec3.
4373 if return_type == float:
4374 return context_wrapper.calculatePrimitiveDataMeanFloatWrapper(self.
context, uuids, label)
4375 elif return_type ==
"double":
4376 return context_wrapper.calculatePrimitiveDataMeanDoubleWrapper(self.
context, uuids, label)
4377 elif return_type == vec3:
4378 coords = context_wrapper.calculatePrimitiveDataMeanVec3Wrapper(self.
context, uuids, label)
4379 return vec3(coords[0], coords[1], coords[2])
4381 raise ValueError(f
"Unsupported return type: {return_type}")
4384 """Calculate area-weighted mean of primitive data."""
4385 if return_type == float:
4386 return context_wrapper.calculatePrimitiveDataAreaWeightedMeanFloatWrapper(self.
context, uuids, label)
4388 raise ValueError(f
"Unsupported return type: {return_type}")
4391 """Calculate sum of primitive data across UUIDs."""
4392 if return_type == float:
4393 return context_wrapper.calculatePrimitiveDataSumFloatWrapper(self.
context, uuids, label)
4394 elif return_type ==
"double":
4395 return context_wrapper.calculatePrimitiveDataSumDoubleWrapper(self.
context, uuids, label)
4397 raise ValueError(f
"Unsupported return type: {return_type}")
4400 """Calculate area-weighted sum of primitive data."""
4401 if return_type == float:
4402 return context_wrapper.calculatePrimitiveDataAreaWeightedSumFloatWrapper(self.
context, uuids, label)
4404 raise ValueError(f
"Unsupported return type: {return_type}")
4407 """Scale primitive data by a factor.
4410 scalePrimitiveData(uuids, label, factor) - scale for specific UUIDs
4411 scalePrimitiveData(label, factor) - scale for ALL primitives
4413 if isinstance(uuids_or_label, str):
4414 context_wrapper.scalePrimitiveDataAllWrapper(self.
context, uuids_or_label, label_or_factor)
4416 context_wrapper.scalePrimitiveDataWithUUIDsWrapper(self.
context, uuids_or_label, label_or_factor, factor)
4419 """Increment primitive data. Auto-dispatches based on increment type."""
4420 if isinstance(increment, float):
4421 context_wrapper.incrementPrimitiveDataFloatWrapper(self.
context, uuids, label, increment)
4422 elif isinstance(increment, int):
4423 context_wrapper.incrementPrimitiveDataIntWrapper(self.
context, uuids, label, increment)
4425 raise ValueError(f
"Unsupported increment type: {type(increment).__name__}")
4428 """Sum multiple primitive data fields into a new field."""
4429 context_wrapper.aggregatePrimitiveDataSumWrapper(self.
context, uuids, labels, result_label)
4432 """Multiply multiple primitive data fields into a new field."""
4433 context_wrapper.aggregatePrimitiveDataProductWrapper(self.
context, uuids, labels, result_label)
4436 """Calculate total one-sided surface area for a set of primitives."""
4437 return context_wrapper.sumPrimitiveSurfaceAreaWrapper(self.
context, uuids)
4439 def filterPrimitivesByData(self, uuids: List[int], label: str, value, comparator: str =
"=") -> List[int]:
4440 """Filter primitives by data value. Auto-dispatches based on value type.
4443 uuids: UUIDs to filter.
4444 label: Data label to compare.
4445 value: Filter value (float, int, or str).
4446 comparator: Comparison operator ("=", "<", ">", "<=", ">="). Not used for strings.
4448 if isinstance(value, str):
4449 return context_wrapper.filterPrimitivesByDataStringWrapper(self.
context, uuids, label, value)
4450 elif isinstance(value, float):
4451 return context_wrapper.filterPrimitivesByDataFloatWrapper(self.
context, uuids, label, value, comparator)
4452 elif isinstance(value, int):
4453 return context_wrapper.filterPrimitivesByDataIntWrapper(self.
context, uuids, label, value, comparator)
4455 raise ValueError(f
"Unsupported filter value type: {type(value).__name__}")
4460 """Return the integer-coded `helios::ObjectType` of a compound object.
4462 Values follow the C++ `helios::ObjectType` enum
4463 (0=tile, 1=sphere, 2=tube, 3=box, 4=disk, 5=polymesh, 6=cone).
4466 return context_wrapper.getObjectTypeWrapper(self.
context, objID)
4470 x, y, z = context_wrapper.getObjectCenterWrapper(self.
context, objID)
4471 return vec3(x, y, z)
4474 """Get axis-aligned bounding box for one object or a list of objects.
4477 objIDs: Single object ID (int) or list of object IDs.
4480 Tuple of (min_corner: vec3, max_corner: vec3).
4483 if isinstance(objIDs, (list, tuple)):
4484 mn, mx = context_wrapper.getObjectBoundingBoxBatchWrapper(self.
context, list(objIDs))
4486 mn, mx = context_wrapper.getObjectBoundingBoxWrapper(self.
context, objIDs)
4487 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4490 """Get flattened primitive UUIDs for one object, a list of objects, or a list-of-lists.
4493 objIDs: int, List[int], or List[List[int]].
4496 Flat list of primitive UUIDs (union across all objects).
4499 if isinstance(objIDs, (list, tuple))
and objIDs
and isinstance(objIDs[0], (list, tuple)):
4500 return context_wrapper.getObjectPrimitiveUUIDsNestedWrapper(self.
context, [list(x)
for x
in objIDs])
4501 if isinstance(objIDs, (list, tuple)):
4502 return context_wrapper.getObjectPrimitiveUUIDsBatchWrapper(self.
context, list(objIDs))
4503 return context_wrapper.getObjectPrimitiveUUIDs(self.
context, int(objIDs))
4507 """Get tile-object area ratio for one or multiple tile objects."""
4509 if isinstance(objIDs, (list, tuple)):
4510 return context_wrapper.getTileObjectAreaRatioBatchWrapper(self.
context, list(objIDs))
4511 return context_wrapper.getTileObjectAreaRatioWrapper(self.
context, objIDs)
4515 x, y, z = context_wrapper.getTileObjectCenterWrapper(self.
context, objID)
4516 return vec3(x, y, z)
4520 x, y = context_wrapper.getTileObjectSizeWrapper(self.
context, objID)
4525 x, y = context_wrapper.getTileObjectSubdivisionCountWrapper(self.
context, objID)
4530 x, y, z = context_wrapper.getTileObjectNormalWrapper(self.
context, objID)
4535 pairs = context_wrapper.getTileObjectTextureUVWrapper(self.
context, objID)
4536 return [
vec2(u, v)
for u, v
in pairs]
4540 triples = context_wrapper.getTileObjectVerticesWrapper(self.
context, objID)
4541 return [
vec3(x, y, z)
for x, y, z
in triples]
4546 x, y, z = context_wrapper.getSphereObjectCenterWrapper(self.
context, objID)
4547 return vec3(x, y, z)
4550 """Get per-axis radii of a sphere object.
4552 Note: Helios spheres are spheroids with three independent radii (rx, ry, rz).
4553 Returns a vec3 (not a scalar).
4556 x, y, z = context_wrapper.getSphereObjectRadiusWrapper(self.
context, objID)
4557 return vec3(x, y, z)
4561 return context_wrapper.getSphereObjectSubdivisionCountWrapper(self.
context, objID)
4565 return context_wrapper.getSphereObjectVolumeWrapper(self.
context, objID)
4570 x, y, z = context_wrapper.getBoxObjectCenterWrapper(self.
context, objID)
4575 x, y, z = context_wrapper.getBoxObjectSizeWrapper(self.
context, objID)
4580 x, y, z = context_wrapper.getBoxObjectSubdivisionCountWrapper(self.
context, objID)
4585 return context_wrapper.getBoxObjectVolumeWrapper(self.
context, objID)
4590 x, y, z = context_wrapper.getDiskObjectCenterWrapper(self.
context, objID)
4595 x, y = context_wrapper.getDiskObjectSizeWrapper(self.
context, objID)
4600 return context_wrapper.getDiskObjectSubdivisionCountWrapper(self.
context, objID)
4605 return context_wrapper.getTubeObjectSubdivisionCountWrapper(self.
context, objID)
4609 return context_wrapper.getTubeObjectNodeCountWrapper(self.
context, objID)
4613 triples = context_wrapper.getTubeObjectNodesWrapper(self.
context, objID)
4614 return [
vec3(x, y, z)
for x, y, z
in triples]
4618 return context_wrapper.getTubeObjectNodeRadiiWrapper(self.
context, objID)
4622 triples = context_wrapper.getTubeObjectNodeColorsWrapper(self.
context, objID)
4623 return [
RGBcolor(r, g, b)
for r, g, b
in triples]
4627 return context_wrapper.getTubeObjectVolumeWrapper(self.
context, objID)
4631 return context_wrapper.getTubeObjectSegmentVolumeWrapper(self.
context, objID, segment_index)
4636 return context_wrapper.getConeObjectSubdivisionCountWrapper(self.
context, objID)
4640 triples = context_wrapper.getConeObjectNodesWrapper(self.
context, objID)
4641 return [
vec3(x, y, z)
for x, y, z
in triples]
4645 return context_wrapper.getConeObjectNodeRadiiWrapper(self.
context, objID)
4649 x, y, z = context_wrapper.getConeObjectNodeWrapper(self.
context, objID, number)
4650 return vec3(x, y, z)
4654 return context_wrapper.getConeObjectNodeRadiusWrapper(self.
context, objID, number)
4658 x, y, z = context_wrapper.getConeObjectAxisUnitVectorWrapper(self.
context, objID)
4659 return vec3(x, y, z)
4663 return context_wrapper.getConeObjectLengthWrapper(self.
context, objID)
4667 return context_wrapper.getConeObjectVolumeWrapper(self.
context, objID)
4673 x, y, z = context_wrapper.getPatchCenterWrapper(self.
context, uuid)
4674 return vec3(x, y, z)
4678 x, y = context_wrapper.getPatchSizeWrapper(self.
context, uuid)
4683 x, y, z = context_wrapper.getTriangleVertexWrapper(self.
context, uuid, number)
4688 x, y, z = context_wrapper.getVoxelCenterWrapper(self.
context, uuid)
4693 x, y, z = context_wrapper.getVoxelSizeWrapper(self.
context, uuid)
4696 def getPatchCount(self, include_hidden: bool =
True) -> int:
4698 return context_wrapper.getPatchCountWrapper(self.
context, include_hidden)
4702 return context_wrapper.getTriangleCountWrapper(self.
context, include_hidden)
4705 """Get axis-aligned bounding box for one primitive or a list of primitives.
4708 uuids: Single UUID (int) or list of UUIDs.
4711 Tuple of (min_corner: vec3, max_corner: vec3).
4714 if isinstance(uuids, (list, tuple)):
4715 mn, mx = context_wrapper.getPrimitiveBoundingBoxBatchWrapper(self.
context, list(uuids))
4717 mn, mx = context_wrapper.getPrimitiveBoundingBoxWrapper(self.
context, uuids)
4718 return (
vec3(mn[0], mn[1], mn[2]),
vec3(mx[0], mx[1], mx[2]))
4723 """Set the RGB or RGBA color of one primitive or a list of primitives.
4726 uuids: Single UUID (int) or list of UUIDs.
4727 color: RGBcolor or RGBAcolor.
4730 if isinstance(color, RGBAcolor):
4731 rgba = [color.r, color.g, color.b, color.a]
4732 if isinstance(uuids, (list, tuple)):
4733 context_wrapper.setPrimitiveColorRGBABatchWrapper(self.
context, list(uuids), rgba)
4735 context_wrapper.setPrimitiveColorRGBAWrapper(self.
context, uuids, rgba)
4736 elif isinstance(color, RGBcolor):
4737 rgb = [color.r, color.g, color.b]
4738 if isinstance(uuids, (list, tuple)):
4739 context_wrapper.setPrimitiveColorBatchWrapper(self.
context, list(uuids), rgb)
4741 context_wrapper.setPrimitiveColorWrapper(self.
context, uuids, rgb)
4743 raise ValueError(f
"color must be RGBcolor or RGBAcolor, got {type(color).__name__}")
4748 """Remove a named data field from one primitive or a list of primitives."""
4750 if isinstance(uuids, (list, tuple)):
4751 context_wrapper.clearPrimitiveDataByLabelBatchWrapper(self.
context, list(uuids), label)
4753 context_wrapper.clearPrimitiveDataByLabelWrapper(self.
context, uuids, label)
4756 """List all data labels attached to a primitive."""
4758 return context_wrapper.listPrimitiveDataWrapper(self.
context, uuid)
4764 if not isinstance(xbounds, vec2):
4765 raise ValueError(f
"xbounds must be a vec2, got {type(xbounds).__name__}")
4766 context_wrapper.cropDomainXWrapper(self.
context, xbounds.to_list())
4770 if not isinstance(ybounds, vec2):
4771 raise ValueError(f
"ybounds must be a vec2, got {type(ybounds).__name__}")
4772 context_wrapper.cropDomainYWrapper(self.
context, ybounds.to_list())
4776 if not isinstance(zbounds, vec2):
4777 raise ValueError(f
"zbounds must be a vec2, got {type(zbounds).__name__}")
4778 context_wrapper.cropDomainZWrapper(self.
context, zbounds.to_list())
4780 def cropDomain(self, *args) -> Optional[List[int]]:
4781 """Crop the context domain to the given XYZ bounds.
4784 cropDomain(xbounds: vec2, ybounds: vec2, zbounds: vec2)
4785 -> crop ALL primitives; returns None.
4786 cropDomain(uuids: List[int], xbounds: vec2, ybounds: vec2, zbounds: vec2)
4787 -> crop only the given primitives; returns the list of primitives
4788 that survived (in-bounds UUIDs). The input list is NOT mutated.
4793 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
4794 if not isinstance(b, vec2):
4795 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
4796 context_wrapper.cropDomainXYZWrapper(self.
context, xb.to_list(), yb.to_list(), zb.to_list())
4799 uuids, xb, yb, zb = args
4800 if not isinstance(uuids, (list, tuple)):
4801 raise ValueError(f
"uuids must be a list or tuple, got {type(uuids).__name__}")
4802 for name, b
in ((
"xbounds", xb), (
"ybounds", yb), (
"zbounds", zb)):
4803 if not isinstance(b, vec2):
4804 raise ValueError(f
"{name} must be a vec2, got {type(b).__name__}")
4805 return context_wrapper.cropDomainByUUIDsWrapper(self.
context, list(uuids), xb.to_list(), yb.to_list(), zb.to_list())
4806 raise TypeError(f
"cropDomain() takes 3 or 4 positional arguments, got {len(args)}")
Central simulation environment for PyHelios that manages 3D primitives and their data.
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.
List[RGBcolor] getTubeObjectNodeColors(self, int objID)
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.
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 deletePrimitive(self, Union[int, List[int]] uuids_or_uuid)
Delete one or more primitives from the context.
_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.
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.
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.
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.
List[int] getObjectPrimitiveUUIDs(self, objIDs)
Get flattened primitive UUIDs for one object, a list of objects, or a list-of-lists.
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.
List[int] getAllObjectIDs(self)
None setObjectDataUInt(self, objids_or_objid, str label, int value)
Set object data as unsigned 32-bit integer for one or multiple objects.
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)
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 hidePrimitive(self, uuids_or_uuid)
Hide one or more primitives.
int getTimeseriesLength(self, str label)
Get the number of data points in a timeseries variable.
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.
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.
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 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.
'np.ndarray' getAllPrimitiveColors(self)
Get colors for all primitives.
int getPrimitiveCount(self)
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.
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)
setDate(self, int year, int month, int day)
Set the simulation date.
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)
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.
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 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 for one or multiple objects.
bool isGeometryDirty(self)
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.
None usePrimitiveTextureColor(self, int uuid)
Use texture map color instead of constant RGB for a primitive.
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.
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 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.
vec3 getConeObjectNode(self, int objID, int number)
None clearGlobalData(self, str label)
Clear global data.
vec3 getConeObjectAxisUnitVector(self, int objID)
None setObjectDataVec2(self, objids_or_objid, str label, x_or_vec, float y=None)
Set object data as vec2.
int addPatch(self, vec3 center=vec3(0, 0, 0), vec2 size=vec2(1, 1), Optional[SphericalCoord] rotation=None, Optional[RGBcolor] color=None)
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.
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.
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.
str _validate_file_path(self, str filename, List[str] expected_extensions=None)
Validate and normalize file path for security.
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 overridePrimitiveTextureColor(self, int uuid)
Override texture color with constant RGB color for a primitive.
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.
assignMaterialToPrimitive(self, uuid, str material_label)
Assign a material to primitive(s).
None scalePrimitiveData(self, uuids_or_label, label_or_factor, factor=None)
Scale primitive data by a factor.
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.
bool isPrimitiveTextureColorOverridden(self, int uuid)
Check if primitive texture color is overridden.
None clearPrimitiveData(self, uuids, str label)
Remove a named data field from one primitive or a list of primitives.
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)
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.
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 for one or multiple objects.
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.
float getTubeObjectVolume(self, int objID)
List[vec3] getTubeObjectNodes(self, int objID)
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 rotatePrimitive(self, Union[int, List[int]] UUID, float angle, Union[str, vec3] axis, Optional[vec3] origin=None)
Rotate one or more primitives.
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.
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.
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 for one or multiple objects.
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.
str getObjectDataString(self, int objID, str label)
Get string object data.
None cropDomainZ(self, vec2 zbounds)
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 getObjectDataInt(self, int objID, str label)
Get int object data.
List[vec2] getTileObjectTextureUV(self, int objID)
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.
None renameObjectData(self, int objID, str old_label, str new_label)
Rename an object data label.
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.
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.
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 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] getPrimitivesUsingMaterial(self, str material_label)
Get all primitive UUIDs that use a specific material.
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)
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.
'np.ndarray' getAllPrimitiveSolidFractions(self)
Get solid fractions for all primitives.
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.
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.
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.
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 for one or multiple objects.
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.
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.
'Date' queryTimeseriesDate(self, str label, int index)
Get the Date associated with a timeseries data point.
None incrementPrimitiveData(self, List[int] uuids, str label, increment)
Increment primitive data.
'np.ndarray' getAllPrimitiveAreas(self)
Get areas for all primitives.
'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.
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.
bool doesGlobalDataExist(self, str label)
Check if global data exists.
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.
Helios primitive type enumeration.
Helios Time structure for representing time values.