127 LIGHTING_PHONG_SHADOWED = 2
137 def __init__(self, width: int, height: int, antialiasing_samples: int = 1, headless: bool =
False):
139 Initialize Visualizer with graceful plugin handling.
142 width: Window width in pixels
143 height: Window height in pixels
144 antialiasing_samples: Number of antialiasing samples (default: 1)
145 headless: Enable headless mode for offscreen rendering (default: False)
148 VisualizerError: If visualizer plugin is not available
149 ValueError: If parameters are invalid
152 if not isinstance(width, int):
153 raise ValueError(f
"Width must be an integer, got {type(width).__name__}")
154 if not isinstance(height, int):
155 raise ValueError(f
"Height must be an integer, got {type(height).__name__}")
156 if not isinstance(antialiasing_samples, int):
157 raise ValueError(f
"Antialiasing samples must be an integer, got {type(antialiasing_samples).__name__}")
158 if not isinstance(headless, bool):
159 raise ValueError(f
"Headless must be a boolean, got {type(headless).__name__}")
162 if width <= 0
or height <= 0:
163 raise ValueError(
"Width and height must be positive integers")
164 if antialiasing_samples < 1:
165 raise ValueError(
"Antialiasing samples must be at least 1")
174 registry = get_plugin_registry()
176 if not registry.is_plugin_available(
'visualizer'):
178 available_plugins = registry.get_available_plugins()
181 "Visualizer requires the 'visualizer' plugin which is not available.\n\n"
182 "The visualizer plugin provides OpenGL-based 3D rendering and visualization.\n"
183 "System requirements:\n"
184 "- OpenGL 3.3 or higher\n"
185 "- GLFW library for window management\n"
186 "- FreeType library for text rendering\n"
187 "- Display/graphics drivers (X11 on Linux, native on Windows/macOS)\n\n"
188 "To enable visualization:\n"
189 "1. Build PyHelios with visualizer plugin:\n"
190 " build_scripts/build_helios --plugins visualizer\n"
191 f
"\nCurrently available plugins: {available_plugins}"
196 system = platform.system().lower()
197 if 'linux' in system:
199 "\n\nLinux installation hints:\n"
200 "- Ubuntu/Debian: sudo apt-get install libx11-dev xorg-dev libgl1-mesa-dev libglu1-mesa-dev\n"
201 "- CentOS/RHEL: sudo yum install libX11-devel mesa-libGL-devel mesa-libGLU-devel"
203 elif 'darwin' in system:
205 "\n\nmacOS installation hints:\n"
206 "- Install XQuartz: brew install --cask xquartz\n"
207 "- OpenGL should be available by default"
209 elif 'windows' in system:
211 "\n\nWindows installation hints:\n"
212 "- OpenGL drivers should be provided by graphics card drivers\n"
213 "- Visual Studio runtime may be required"
221 if antialiasing_samples > 1:
222 self.
visualizer = visualizer_wrapper.create_visualizer_with_antialiasing(
223 width, height, antialiasing_samples, headless
226 self.
visualizer = visualizer_wrapper.create_visualizer(
227 width, height, headless
232 "Failed to create Visualizer instance. "
233 "This may indicate a problem with graphics drivers or OpenGL initialization."
235 logger.info(f
"Visualizer created successfully ({width}x{height}, AA:{antialiasing_samples}, headless:{headless})")
237 except Exception
as e:
241 """Context manager entry."""
244 def __exit__(self, exc_type, exc_value, traceback):
245 """Context manager exit with proper cleanup."""
250 logger.debug(
"Visualizer destroyed successfully")
251 except Exception
as e:
252 logger.warning(f
"Error destroying Visualizer: {e}")
256 @validate_build_geometry_params
259 Build Context geometry in the visualizer.
261 This method loads geometry from a Helios Context into the visualizer
262 for rendering. If no UUIDs are specified, all geometry is loaded.
265 context: Helios Context instance containing geometry
266 uuids: Optional list of primitive UUIDs to visualize (default: all)
269 VisualizerError: If geometry building fails
270 ValueError: If parameters are invalid
274 if not isinstance(context, Context):
275 raise ValueError(
"context must be a Context instance")
281 visualizer_wrapper.build_context_geometry(self.
visualizer, context.getNativePtr())
282 logger.debug(
"Built all Context geometry in visualizer")
286 raise ValueError(
"UUIDs list cannot be empty")
287 visualizer_wrapper.build_context_geometry_uuids(
288 self.
visualizer, context.getNativePtr(), uuids
290 logger.debug(f
"Built {len(uuids)} primitives in visualizer")
292 except Exception
as e:
297 Open interactive visualization window.
299 This method opens a window with the current scene and allows user
300 interaction (camera rotation, zooming, etc.). The program will pause
301 until the window is closed by the user.
303 Interactive controls:
304 - Mouse scroll: Zoom in/out
305 - Left mouse + drag: Rotate camera
306 - Right mouse + drag: Pan camera
307 - Arrow keys: Camera movement
308 - +/- keys: Zoom in/out
311 VisualizerError: If visualization fails
318 visualizer_wrapper.plot_interactive(self.
visualizer)
319 logger.debug(
"Interactive visualization completed")
320 except Exception
as e:
325 Update visualization (non-interactive).
327 This method updates the visualization window without user interaction.
328 The program continues immediately after rendering. Useful for batch
329 processing or creating image sequences.
332 VisualizerError: If visualization update fails
339 visualizer_wrapper.plot_update(self.
visualizer)
340 logger.debug(
"Visualization updated")
341 except Exception
as e:
344 @validate_print_window_params
345 def printWindow(self, filename: str, image_format: Optional[str] =
None) ->
None:
347 Save current visualization to image file.
349 This method exports the current visualization to an image file.
350 Starting from v1.3.53, supports both JPEG and PNG formats.
353 filename: Output filename for image
354 Can be absolute or relative to user's current working directory
355 Extension (.jpg, .png) is recommended but not required
356 image_format: Image format - "jpeg" or "png" (v1.3.53+)
357 If None, automatically detects from filename extension
358 Default: "jpeg" if not detectable from extension
361 VisualizerError: If image saving fails
362 ValueError: If filename or format is invalid
365 PNG format is required to preserve transparent backgrounds when using
366 setBackgroundTransparent(). JPEG format will render transparent areas as black.
369 >>> visualizer.printWindow("output.jpg") # Auto-detects JPEG
370 >>> visualizer.printWindow("output.png") # Auto-detects PNG
371 >>> visualizer.printWindow("output.img", image_format="png") # Explicit PNG
376 raise ValueError(
"Filename cannot be empty")
382 if image_format
is None:
383 if resolved_filename.lower().endswith(
'.png'):
385 elif resolved_filename.lower().endswith((
'.jpg',
'.jpeg')):
386 image_format =
'jpeg'
389 image_format =
'jpeg'
390 logger.debug(f
"No format specified and extension not recognized, defaulting to JPEG")
393 if image_format.lower()
not in [
'jpeg',
'png']:
394 raise ValueError(f
"Image format must be 'jpeg' or 'png', got '{image_format}'")
400 visualizer_wrapper.print_window_with_format(
405 logger.debug(f
"Visualization saved to {resolved_filename} ({image_format.upper()} format)")
406 except (AttributeError, NotImplementedError):
408 if image_format.lower() !=
'jpeg':
410 "PNG format requested but not available in current Helios version. "
411 "Falling back to JPEG format. Update to Helios v1.3.53+ for PNG support."
413 visualizer_wrapper.print_window(self.
visualizer, resolved_filename)
414 logger.debug(f
"Visualization saved to {resolved_filename} (JPEG format - legacy mode)")
415 except Exception
as e:
420 Close visualization window.
422 This method closes any open visualization window. It's safe to call
423 even if no window is open.
426 VisualizerError: If window closing fails
432 visualizer_wrapper.close_window(self.
visualizer)
433 logger.debug(
"Visualization window closed")
434 except Exception
as e:
439 Set camera position using Cartesian coordinates.
442 position: Camera position as vec3 in world coordinates
443 lookAt: Camera look-at point as vec3 in world coordinates
446 VisualizerError: If camera positioning fails
447 ValueError: If parameters are invalid
453 if not isinstance(position, vec3):
454 raise ValueError(f
"Position must be a vec3, got {type(position).__name__}")
455 if not isinstance(lookAt, vec3):
456 raise ValueError(f
"LookAt must be a vec3, got {type(lookAt).__name__}")
459 visualizer_wrapper.set_camera_position(self.
visualizer, position, lookAt)
460 logger.debug(f
"Camera position set to ({position.x}, {position.y}, {position.z}), looking at ({lookAt.x}, {lookAt.y}, {lookAt.z})")
461 except Exception
as e:
466 Set camera position using spherical coordinates.
469 angle: Camera position as SphericalCoord (radius, elevation, azimuth)
470 lookAt: Camera look-at point as vec3 in world coordinates
473 VisualizerError: If camera positioning fails
474 ValueError: If parameters are invalid
480 if not isinstance(angle, SphericalCoord):
481 raise ValueError(f
"Angle must be a SphericalCoord, got {type(angle).__name__}")
482 if not isinstance(lookAt, vec3):
483 raise ValueError(f
"LookAt must be a vec3, got {type(lookAt).__name__}")
486 visualizer_wrapper.set_camera_position_spherical(self.
visualizer, angle, lookAt)
487 logger.debug(f
"Camera position set to spherical (r={angle.radius}, el={angle.elevation}, az={angle.azimuth}), looking at ({lookAt.x}, {lookAt.y}, {lookAt.z})")
488 except Exception
as e:
489 raise VisualizerError(f
"Failed to set camera position (spherical): {e}")
493 Set background color.
496 color: Background color as RGBcolor with values in range [0, 1]
499 VisualizerError: If color setting fails
500 ValueError: If color values are invalid
506 if not isinstance(color, RGBcolor):
507 raise ValueError(f
"Color must be an RGBcolor, got {type(color).__name__}")
510 if not (0 <= color.r <= 1
and 0 <= color.g <= 1
and 0 <= color.b <= 1):
511 raise ValueError(f
"Color components ({color.r}, {color.g}, {color.b}) must be in range [0, 1]")
514 visualizer_wrapper.set_background_color(self.
visualizer, color)
515 logger.debug(f
"Background color set to ({color.r}, {color.g}, {color.b})")
516 except Exception
as e:
521 Enable transparent background mode (v1.3.53+).
523 Sets the background to transparent with checkerboard pattern display.
524 Requires PNG output format to preserve transparency.
526 Note: When using transparent background, use printWindow() with PNG
527 format to save transparent images.
530 VisualizerError: If transparent background setting fails
536 visualizer_wrapper.set_background_transparent(self.
visualizer)
537 logger.debug(
"Background set to transparent mode")
538 except Exception
as e:
543 Set custom background image texture (v1.3.53+).
546 texture_file: Path to background image file
547 Can be absolute or relative to working directory
550 VisualizerError: If background image setting fails
551 ValueError: If texture file path is invalid
556 if not texture_file
or not isinstance(texture_file, str):
557 raise ValueError(
"Texture file path must be a non-empty string")
563 visualizer_wrapper.set_background_image(self.
visualizer, resolved_path)
564 logger.debug(f
"Background image set to {resolved_path}")
565 except Exception
as e:
570 Set sky sphere texture background with automatic scaling (v1.3.53+).
572 Creates a sky sphere that automatically scales with the scene.
573 Replaces the deprecated addSkyDomeByCenter() method.
576 texture_file: Path to spherical/equirectangular texture image
577 If None, uses default gradient sky texture
578 divisions: Number of sphere tessellation divisions (default: 50)
579 Higher values create smoother sphere but use more GPU
582 VisualizerError: If sky texture setting fails
583 ValueError: If parameters are invalid
586 >>> visualizer.setBackgroundSkyTexture() # Default gradient sky
587 >>> visualizer.setBackgroundSkyTexture("sky_hdri.jpg", divisions=100)
592 if not isinstance(divisions, int)
or divisions <= 0:
593 raise ValueError(
"Divisions must be a positive integer")
598 if not isinstance(texture_file, str):
599 raise ValueError(
"Texture file must be a string")
603 visualizer_wrapper.set_background_sky_texture(
609 logger.debug(f
"Sky texture background set: {resolved_path}, divisions={divisions}")
611 logger.debug(f
"Default sky texture background set with divisions={divisions}")
612 except Exception
as e:
620 direction: Light direction vector as vec3 (will be normalized)
623 VisualizerError: If light direction setting fails
624 ValueError: If direction is invalid
630 if not isinstance(direction, vec3):
631 raise ValueError(f
"Direction must be a vec3, got {type(direction).__name__}")
634 if direction.x == 0
and direction.y == 0
and direction.z == 0:
635 raise ValueError(
"Light direction cannot be zero vector")
638 visualizer_wrapper.set_light_direction(self.
visualizer, direction)
639 logger.debug(f
"Light direction set to ({direction.x}, {direction.y}, {direction.z})")
640 except Exception
as e:
648 lighting_model: Lighting model, either:
649 - 0 or "none": No lighting
650 - 1 or "phong": Phong shading
651 - 2 or "phong_shadowed": Phong shading with shadows
654 VisualizerError: If lighting model setting fails
655 ValueError: If lighting model is invalid
661 if isinstance(lighting_model, str):
662 lighting_model_lower = lighting_model.lower()
663 if lighting_model_lower
in [
'none',
'no',
'off']:
665 elif lighting_model_lower
in [
'phong',
'phong_lighting']:
667 elif lighting_model_lower
in [
'phong_shadowed',
'phong_shadows',
'shadowed']:
670 raise ValueError(f
"Unknown lighting model string: {lighting_model}")
674 raise ValueError(f
"Lighting model must be 0 (NONE), 1 (PHONG), or 2 (PHONG_SHADOWED), got {lighting_model}")
677 visualizer_wrapper.set_lighting_model(self.
visualizer, lighting_model)
678 model_names = {0:
"NONE", 1:
"PHONG", 2:
"PHONG_SHADOWED"}
679 logger.debug(f
"Lighting model set to {model_names.get(lighting_model, lighting_model)}")
680 except Exception
as e:
685 Color context primitives based on primitive data values.
687 This method maps primitive data values to colors using the current colormap.
688 The visualization will be updated to show data variations across primitives.
690 The data must have been previously set on the primitives in the Context using
691 context.setPrimitiveDataFloat(UUID, data_name, value) before calling this method.
694 data_name: Name of the primitive data to use for coloring.
695 This should match the data label used with setPrimitiveDataFloat().
696 uuids: Optional list of specific primitive UUIDs to color.
697 If None, all primitives in context will be colored.
700 VisualizerError: If visualizer is not initialized or operation fails
701 ValueError: If data_name is invalid or UUIDs are malformed
704 >>> # Set data on primitives in context
705 >>> context.setPrimitiveDataFloat(patch_uuid, "radiation_flux_SW", 450.2)
706 >>> context.setPrimitiveDataFloat(triangle_uuid, "radiation_flux_SW", 320.1)
708 >>> # Build geometry and color by data
709 >>> visualizer.buildContextGeometry(context)
710 >>> visualizer.colorContextPrimitivesByData("radiation_flux_SW")
711 >>> visualizer.plotInteractive()
713 >>> # Color only specific primitives
714 >>> visualizer.colorContextPrimitivesByData("temperature", [uuid1, uuid2, uuid3])
719 if not data_name
or not isinstance(data_name, str):
720 raise ValueError(
"Data name must be a non-empty string")
725 visualizer_wrapper.color_context_primitives_by_data(self.
visualizer, data_name)
726 logger.debug(f
"Colored all primitives by data: {data_name}")
729 if not isinstance(uuids, (list, tuple))
or not uuids:
730 raise ValueError(
"UUIDs must be a non-empty list or tuple")
731 if not all(isinstance(uuid, int)
and uuid >= 0
for uuid
in uuids):
732 raise ValueError(
"All UUIDs must be non-negative integers")
734 visualizer_wrapper.color_context_primitives_by_data_uuids(self.
visualizer, data_name, list(uuids))
735 logger.debug(f
"Colored {len(uuids)} primitives by data: {data_name}")
740 except Exception
as e:
741 raise VisualizerError(f
"Failed to color primitives by data '{data_name}': {e}")
747 Set camera field of view angle.
750 angle_FOV: Field of view angle in degrees
753 ValueError: If angle is invalid
754 VisualizerError: If operation fails
759 if not isinstance(angle_FOV, (int, float)):
760 raise ValueError(
"Field of view angle must be numeric")
761 if angle_FOV <= 0
or angle_FOV >= 180:
762 raise ValueError(
"Field of view angle must be between 0 and 180 degrees")
765 helios_lib.setCameraFieldOfView(self.
visualizer, ctypes.c_float(angle_FOV))
766 except Exception
as e:
771 Get current camera position and look-at point.
774 Tuple of (camera_position, look_at_point) as vec3 objects
777 VisualizerError: If operation fails
784 camera_pos = (ctypes.c_float * 3)()
785 look_at = (ctypes.c_float * 3)()
787 helios_lib.getCameraPosition(self.
visualizer, camera_pos, look_at)
789 return (
vec3(camera_pos[0], camera_pos[1], camera_pos[2]),
790 vec3(look_at[0], look_at[1], look_at[2]))
791 except Exception
as e:
796 Get current background color.
799 Background color as RGBcolor object
802 VisualizerError: If operation fails
809 color = (ctypes.c_float * 3)()
811 helios_lib.getBackgroundColor(self.
visualizer, color)
814 except Exception
as e:
821 Set light intensity scaling factor.
824 intensity_factor: Light intensity scaling factor (typically 0.1 to 10.0)
827 ValueError: If intensity factor is invalid
828 VisualizerError: If operation fails
833 if not isinstance(intensity_factor, (int, float)):
834 raise ValueError(
"Light intensity factor must be numeric")
835 if intensity_factor <= 0:
836 raise ValueError(
"Light intensity factor must be positive")
839 helios_lib.setLightIntensityFactor(self.
visualizer, ctypes.c_float(intensity_factor))
840 except Exception
as e:
847 Get window size in pixels.
850 Tuple of (width, height) in pixels
853 VisualizerError: If operation fails
859 width = ctypes.c_uint()
860 height = ctypes.c_uint()
862 helios_lib.getWindowSize(self.
visualizer, ctypes.byref(width), ctypes.byref(height))
864 return (width.value, height.value)
865 except Exception
as e:
870 Get framebuffer size in pixels.
873 Tuple of (width, height) in pixels
876 VisualizerError: If operation fails
882 width = ctypes.c_uint()
883 height = ctypes.c_uint()
885 helios_lib.getFramebufferSize(self.
visualizer, ctypes.byref(width), ctypes.byref(height))
887 return (width.value, height.value)
888 except Exception
as e:
893 Print window with default filename.
896 VisualizerError: If operation fails
902 helios_lib.printWindowDefault(self.
visualizer)
903 except Exception
as e:
908 Display image from RGBA pixel data.
911 pixel_data: RGBA pixel data as list of integers (0-255)
912 width: Image width in pixels
913 height: Image height in pixels
916 ValueError: If parameters are invalid
917 VisualizerError: If operation fails
922 if not isinstance(pixel_data, (list, tuple)):
923 raise ValueError(
"Pixel data must be a list or tuple")
924 if not isinstance(width, int)
or width <= 0:
925 raise ValueError(
"Width must be a positive integer")
926 if not isinstance(height, int)
or height <= 0:
927 raise ValueError(
"Height must be a positive integer")
929 expected_size = width * height * 4
930 if len(pixel_data) != expected_size:
931 raise ValueError(f
"Pixel data size mismatch: expected {expected_size}, got {len(pixel_data)}")
935 pixel_array = (ctypes.c_ubyte * len(pixel_data))(*pixel_data)
936 helios_lib.displayImageFromPixels(self.
visualizer, pixel_array, width, height)
937 except Exception
as e:
942 Display image from file.
945 filename: Path to image file
948 ValueError: If filename is invalid
949 VisualizerError: If operation fails
954 if not isinstance(filename, str)
or not filename.strip():
955 raise ValueError(
"Filename must be a non-empty string")
958 helios_lib.displayImageFromFile(self.
visualizer, filename.encode(
'utf-8'))
959 except Exception
as e:
966 Get RGB pixel data from current window.
969 buffer: Pre-allocated buffer to store pixel data
972 ValueError: If buffer is invalid
973 VisualizerError: If operation fails
978 if not isinstance(buffer, list):
979 raise ValueError(
"Buffer must be a list")
983 buffer_array = (ctypes.c_uint * len(buffer))(*buffer)
987 for i
in range(len(buffer)):
988 buffer[i] = buffer_array[i]
989 except Exception
as e:
992 def getDepthMap(self) -> Tuple[List[float], int, int]:
994 Get depth map from current window.
997 Tuple of (depth_pixels, width, height)
1000 VisualizerError: If operation fails
1006 depth_ptr = ctypes.POINTER(ctypes.c_float)()
1007 width = ctypes.c_uint()
1008 height = ctypes.c_uint()
1009 buffer_size = ctypes.c_uint()
1012 ctypes.byref(width), ctypes.byref(height),
1013 ctypes.byref(buffer_size))
1016 if depth_ptr
and buffer_size.value > 0:
1017 depth_data = [depth_ptr[i]
for i
in range(buffer_size.value)]
1018 return (depth_data, width.value, height.value)
1021 except Exception
as e:
1026 Plot depth map visualization.
1029 VisualizerError: If operation fails
1036 except Exception
as e:
1043 Clear all geometry from visualizer.
1046 VisualizerError: If operation fails
1053 except Exception
as e:
1058 Clear context geometry from visualizer.
1061 VisualizerError: If operation fails
1067 helios_lib.clearContextGeometry(self.
visualizer)
1068 except Exception
as e:
1073 Delete specific geometry by ID.
1076 geometry_id: ID of geometry to delete
1079 ValueError: If geometry ID is invalid
1080 VisualizerError: If operation fails
1085 if not isinstance(geometry_id, int)
or geometry_id < 0:
1086 raise ValueError(
"Geometry ID must be a non-negative integer")
1089 helios_lib.deleteGeometry(self.
visualizer, geometry_id)
1090 except Exception
as e:
1095 Update context primitive colors.
1098 VisualizerError: If operation fails
1104 helios_lib.updateContextPrimitiveColors(self.
visualizer)
1105 except Exception
as e:
1106 raise VisualizerError(f
"Failed to update context primitive colors: {e}")
1112 Get vertices of a geometry primitive.
1115 geometry_id: Unique identifier of the geometry primitive
1118 List of vertices as vec3 objects
1121 ValueError: If geometry ID is invalid
1122 VisualizerError: If operation fails
1125 >>> # Get vertices of a specific geometry
1126 >>> vertices = visualizer.getGeometryVertices(geometry_id)
1127 >>> for vertex in vertices:
1128 ... print(f"Vertex: ({vertex.x}, {vertex.y}, {vertex.z})")
1133 if not isinstance(geometry_id, int)
or geometry_id < 0:
1134 raise ValueError(
"Geometry ID must be a non-negative integer")
1137 vertices_list = visualizer_wrapper.get_geometry_vertices(self.
visualizer, geometry_id)
1139 return [
vec3(v[0], v[1], v[2])
for v
in vertices_list]
1140 except Exception
as e:
1145 Set vertices of a geometry primitive.
1147 This allows dynamic modification of geometry shapes during visualization.
1148 Useful for animating geometry or adjusting shapes based on simulation results.
1151 geometry_id: Unique identifier of the geometry primitive
1152 vertices: List of new vertices as vec3 objects
1155 ValueError: If parameters are invalid
1156 VisualizerError: If operation fails
1159 >>> # Modify vertices of an existing geometry
1160 >>> vertices = visualizer.getGeometryVertices(geometry_id)
1161 >>> # Scale all vertices by 2x
1162 >>> scaled_vertices = [vec3(v.x*2, v.y*2, v.z*2) for v in vertices]
1163 >>> visualizer.setGeometryVertices(geometry_id, scaled_vertices)
1168 if not isinstance(geometry_id, int)
or geometry_id < 0:
1169 raise ValueError(
"Geometry ID must be a non-negative integer")
1171 if not vertices
or not isinstance(vertices, (list, tuple)):
1172 raise ValueError(
"Vertices must be a non-empty list")
1174 if not all(isinstance(v, vec3)
for v
in vertices):
1175 raise ValueError(
"All vertices must be vec3 objects")
1178 visualizer_wrapper.set_geometry_vertices(self.
visualizer, geometry_id, vertices)
1179 logger.debug(f
"Set {len(vertices)} vertices for geometry {geometry_id}")
1180 except Exception
as e:
1187 Add coordinate axes at origin with unit length.
1190 VisualizerError: If operation fails
1196 helios_lib.addCoordinateAxes(self.
visualizer)
1197 except Exception
as e:
1202 Add coordinate axes with custom properties.
1205 origin: Axes origin position
1206 length: Axes length in each direction
1207 sign: Axis direction ("both" or "positive")
1210 ValueError: If parameters are invalid
1211 VisualizerError: If operation fails
1216 if not isinstance(origin, vec3):
1217 raise ValueError(
"Origin must be a vec3")
1218 if not isinstance(length, vec3):
1219 raise ValueError(
"Length must be a vec3")
1220 if not isinstance(sign, str)
or sign
not in [
"both",
"positive"]:
1221 raise ValueError(
"Sign must be 'both' or 'positive'")
1224 origin_array = (ctypes.c_float * 3)(origin.x, origin.y, origin.z)
1225 length_array = (ctypes.c_float * 3)(length.x, length.y, length.z)
1226 helios_lib.addCoordinateAxesCustom(self.
visualizer, origin_array, length_array, sign.encode(
'utf-8'))
1227 except Exception
as e:
1232 Remove coordinate axes.
1235 VisualizerError: If operation fails
1241 helios_lib.disableCoordinateAxes(self.
visualizer)
1242 except Exception
as e:
1245 def addGridWireFrame(self, center: vec3, size: vec3, subdivisions: List[int]) ->
None:
1250 center: Grid center position
1251 size: Grid size in each direction
1252 subdivisions: Grid subdivisions [x, y, z]
1255 ValueError: If parameters are invalid
1256 VisualizerError: If operation fails
1261 if not isinstance(center, vec3):
1262 raise ValueError(
"Center must be a vec3")
1263 if not isinstance(size, vec3):
1264 raise ValueError(
"Size must be a vec3")
1265 if not isinstance(subdivisions, (list, tuple))
or len(subdivisions) != 3:
1266 raise ValueError(
"Subdivisions must be a list of 3 integers")
1267 if not all(isinstance(s, int)
and s > 0
for s
in subdivisions):
1268 raise ValueError(
"All subdivisions must be positive integers")
1271 center_array = (ctypes.c_float * 3)(center.x, center.y, center.z)
1272 size_array = (ctypes.c_float * 3)(size.x, size.y, size.z)
1273 subdiv_array = (ctypes.c_int * 3)(*subdivisions)
1274 helios_lib.addGridWireFrame(self.
visualizer, center_array, size_array, subdiv_array)
1275 except Exception
as e:
1285 VisualizerError: If operation fails
1292 except Exception
as e:
1300 VisualizerError: If operation fails
1307 except Exception
as e:
1312 Set colorbar position.
1315 position: Colorbar position
1318 ValueError: If position is invalid
1319 VisualizerError: If operation fails
1324 if not isinstance(position, vec3):
1325 raise ValueError(
"Position must be a vec3")
1328 pos_array = (ctypes.c_float * 3)(position.x, position.y, position.z)
1329 helios_lib.setColorbarPosition(self.
visualizer, pos_array)
1330 except Exception
as e:
1338 width: Colorbar width
1339 height: Colorbar height
1342 ValueError: If size is invalid
1343 VisualizerError: If operation fails
1348 if not isinstance(width, (int, float))
or width <= 0:
1349 raise ValueError(
"Width must be a positive number")
1350 if not isinstance(height, (int, float))
or height <= 0:
1351 raise ValueError(
"Height must be a positive number")
1354 size_array = (ctypes.c_float * 2)(float(width), float(height))
1355 helios_lib.setColorbarSize(self.
visualizer, size_array)
1356 except Exception
as e:
1364 min_val: Minimum value
1365 max_val: Maximum value
1368 ValueError: If range is invalid
1369 VisualizerError: If operation fails
1374 if not isinstance(min_val, (int, float)):
1375 raise ValueError(
"Minimum value must be numeric")
1376 if not isinstance(max_val, (int, float)):
1377 raise ValueError(
"Maximum value must be numeric")
1378 if min_val >= max_val:
1379 raise ValueError(
"Minimum value must be less than maximum value")
1382 helios_lib.setColorbarRange(self.
visualizer, float(min_val), float(max_val))
1383 except Exception
as e:
1388 Set colorbar tick marks.
1391 ticks: List of tick values
1394 ValueError: If ticks are invalid
1395 VisualizerError: If operation fails
1400 if not isinstance(ticks, (list, tuple)):
1401 raise ValueError(
"Ticks must be a list or tuple")
1402 if not all(isinstance(t, (int, float))
for t
in ticks):
1403 raise ValueError(
"All tick values must be numeric")
1407 ticks_array = (ctypes.c_float * len(ticks))(*ticks)
1408 helios_lib.setColorbarTicks(self.
visualizer, ticks_array, len(ticks))
1411 except Exception
as e:
1419 title: Colorbar title
1422 ValueError: If title is invalid
1423 VisualizerError: If operation fails
1428 if not isinstance(title, str):
1429 raise ValueError(
"Title must be a string")
1432 helios_lib.setColorbarTitle(self.
visualizer, title.encode(
'utf-8'))
1433 except Exception
as e:
1438 Set colorbar font color.
1444 ValueError: If color is invalid
1445 VisualizerError: If operation fails
1450 if not isinstance(color, RGBcolor):
1451 raise ValueError(
"Color must be an RGBcolor")
1454 color_array = (ctypes.c_float * 3)(color.r, color.g, color.b)
1455 helios_lib.setColorbarFontColor(self.
visualizer, color_array)
1456 except Exception
as e:
1461 Set colorbar font size.
1464 font_size: Font size
1467 ValueError: If font size is invalid
1468 VisualizerError: If operation fails
1473 if not isinstance(font_size, int)
or font_size <= 0:
1474 raise ValueError(
"Font size must be a positive integer")
1477 helios_lib.setColorbarFontSize(self.
visualizer, font_size)
1478 except Exception
as e:
1485 Set predefined colormap.
1488 colormap: Colormap ID (0-5) or name ("HOT", "COOL", "RAINBOW", "LAVA", "PARULA", "GRAY")
1491 ValueError: If colormap is invalid
1492 VisualizerError: If operation fails
1498 "HOT": 0,
"COOL": 1,
"RAINBOW": 2,
1499 "LAVA": 3,
"PARULA": 4,
"GRAY": 5
1502 if isinstance(colormap, str):
1503 if colormap.upper()
not in colormap_map:
1504 raise ValueError(f
"Unknown colormap name: {colormap}")
1505 colormap_id = colormap_map[colormap.upper()]
1506 elif isinstance(colormap, int):
1507 if colormap < 0
or colormap > 5:
1508 raise ValueError(
"Colormap ID must be 0-5")
1509 colormap_id = colormap
1511 raise ValueError(
"Colormap must be integer ID or string name")
1514 helios_lib.setColormap(self.
visualizer, colormap_id)
1515 except Exception
as e:
1518 def setCustomColormap(self, colors: List[RGBcolor], divisions: List[float]) ->
None:
1520 Set custom colormap.
1523 colors: List of RGB colors
1524 divisions: List of division points (same length as colors)
1527 ValueError: If parameters are invalid
1528 VisualizerError: If operation fails
1533 if not isinstance(colors, (list, tuple))
or not colors:
1534 raise ValueError(
"Colors must be a non-empty list")
1535 if not isinstance(divisions, (list, tuple))
or not divisions:
1536 raise ValueError(
"Divisions must be a non-empty list")
1537 if len(colors) != len(divisions):
1538 raise ValueError(
"Colors and divisions must have the same length")
1540 if not all(isinstance(c, RGBcolor)
for c
in colors):
1541 raise ValueError(
"All colors must be RGBcolor objects")
1542 if not all(isinstance(d, (int, float))
for d
in divisions):
1543 raise ValueError(
"All divisions must be numeric")
1547 color_array = (ctypes.c_float * (len(colors) * 3))()
1548 for i, color
in enumerate(colors):
1549 color_array[i*3] = color.r
1550 color_array[i*3+1] = color.g
1551 color_array[i*3+2] = color.b
1553 divisions_array = (ctypes.c_float * len(divisions))(*divisions)
1555 helios_lib.setCustomColormap(self.
visualizer, color_array, divisions_array, len(colors))
1556 except Exception
as e:
1563 Color context primitives by object data.
1566 data_name: Name of object data to use for coloring
1567 obj_ids: Optional list of object IDs to color (None for all)
1570 ValueError: If parameters are invalid
1571 VisualizerError: If operation fails
1576 if not isinstance(data_name, str)
or not data_name.strip():
1577 raise ValueError(
"Data name must be a non-empty string")
1581 helios_lib.colorContextPrimitivesByObjectData(self.
visualizer, data_name.encode(
'utf-8'))
1583 if not isinstance(obj_ids, (list, tuple)):
1584 raise ValueError(
"Object IDs must be a list or tuple")
1585 if not all(isinstance(oid, int)
and oid >= 0
for oid
in obj_ids):
1586 raise ValueError(
"All object IDs must be non-negative integers")
1589 obj_ids_array = (ctypes.c_uint * len(obj_ids))(*obj_ids)
1590 helios_lib.colorContextPrimitivesByObjectDataIDs(self.
visualizer, data_name.encode(
'utf-8'), obj_ids_array, len(obj_ids))
1592 helios_lib.colorContextPrimitivesByObjectDataIDs(self.
visualizer, data_name.encode(
'utf-8'),
None, 0)
1593 except Exception
as e:
1594 raise VisualizerError(f
"Failed to color primitives by object data '{data_name}': {e}")
1598 Color context primitives randomly.
1601 uuids: Optional list of primitive UUIDs to color (None for all)
1604 ValueError: If UUIDs are invalid
1605 VisualizerError: If operation fails
1612 helios_lib.colorContextPrimitivesRandomly(self.
visualizer,
None, 0)
1614 if not isinstance(uuids, (list, tuple)):
1615 raise ValueError(
"UUIDs must be a list or tuple")
1616 if not all(isinstance(uuid, int)
and uuid >= 0
for uuid
in uuids):
1617 raise ValueError(
"All UUIDs must be non-negative integers")
1620 uuid_array = (ctypes.c_uint * len(uuids))(*uuids)
1621 helios_lib.colorContextPrimitivesRandomly(self.
visualizer, uuid_array, len(uuids))
1623 helios_lib.colorContextPrimitivesRandomly(self.
visualizer,
None, 0)
1624 except Exception
as e:
1629 Color context objects randomly.
1632 obj_ids: Optional list of object IDs to color (None for all)
1635 ValueError: If object IDs are invalid
1636 VisualizerError: If operation fails
1643 helios_lib.colorContextObjectsRandomly(self.
visualizer,
None, 0)
1645 if not isinstance(obj_ids, (list, tuple)):
1646 raise ValueError(
"Object IDs must be a list or tuple")
1647 if not all(isinstance(oid, int)
and oid >= 0
for oid
in obj_ids):
1648 raise ValueError(
"All object IDs must be non-negative integers")
1651 obj_ids_array = (ctypes.c_uint * len(obj_ids))(*obj_ids)
1652 helios_lib.colorContextObjectsRandomly(self.
visualizer, obj_ids_array, len(obj_ids))
1654 helios_lib.colorContextObjectsRandomly(self.
visualizer,
None, 0)
1655 except Exception
as e:
1660 Clear primitive colors from previous coloring operations.
1663 VisualizerError: If operation fails
1670 except Exception
as e:
1677 Hide Helios logo watermark.
1680 VisualizerError: If operation fails
1687 except Exception
as e:
1692 Show Helios logo watermark.
1695 VisualizerError: If operation fails
1702 except Exception
as e:
1707 Update watermark geometry to match current window size.
1710 VisualizerError: If operation fails
1717 except Exception
as e:
1724 Hide navigation gizmo (coordinate axes indicator in corner).
1726 The navigation gizmo shows XYZ axes orientation and can be clicked
1727 to snap the camera to standard views (top, front, side, etc.).
1730 VisualizerError: If operation fails
1736 visualizer_wrapper.hide_navigation_gizmo(self.
visualizer)
1737 logger.debug(
"Navigation gizmo hidden")
1738 except Exception
as e:
1743 Show navigation gizmo (coordinate axes indicator in corner).
1745 The navigation gizmo shows XYZ axes orientation and can be clicked
1746 to snap the camera to standard views (top, front, side, etc.).
1748 Note: Navigation gizmo is shown by default in v1.3.53+.
1751 VisualizerError: If operation fails
1757 visualizer_wrapper.show_navigation_gizmo(self.
visualizer)
1758 logger.debug(
"Navigation gizmo shown")
1759 except Exception
as e:
1766 Enable standard output from visualizer plugin.
1769 VisualizerError: If operation fails
1776 except Exception
as e:
1781 Disable standard output from visualizer plugin.
1784 VisualizerError: If operation fails
1791 except Exception
as e:
1794 def plotOnce(self, get_keystrokes: bool =
True) ->
None:
1796 Run one rendering loop.
1799 get_keystrokes: Whether to process keystrokes
1802 VisualizerError: If operation fails
1808 helios_lib.plotOnce(self.
visualizer, get_keystrokes)
1809 except Exception
as e:
1814 Update visualization with window visibility control.
1817 hide_window: Whether to hide the window during update
1820 VisualizerError: If operation fails
1827 helios_lib.plotUpdateWithVisibility(self.
visualizer, hide_window)
1828 except Exception
as e:
1829 raise VisualizerError(f
"Failed to update plot with visibility control: {e}")
1832 """Destructor to ensure proper cleanup."""
1833 if hasattr(self,
'visualizer')
and self.
visualizer is not None:
1836 visualizer_wrapper.destroy_visualizer(self.
visualizer)