2High-level Visualizer interface for PyHelios.
4This module provides a user-friendly interface to the 3D visualization
5capabilities with graceful plugin handling and informative error messages.
11from pathlib
import Path
12from contextlib
import contextmanager
13from typing
import List, Optional, Union, Tuple
15from .plugins.registry
import get_plugin_registry
16from .plugins
import helios_lib
17from .wrappers
import UVisualizerWrapper
as visualizer_wrapper
18from .wrappers.DataTypes
import vec3, RGBcolor, SphericalCoord
19from .Context
import Context
20from .validation.plugin_decorators
import validate_build_geometry_params, validate_print_window_params
21from .assets
import get_asset_manager
23logger = logging.getLogger(__name__)
27_NUMERIC_TYPES = (int, float)
32 Resolve a user-provided path to an absolute path before working directory changes.
34 This ensures that user file paths are interpreted relative to their original
35 working directory, not the temporary working directory used for asset discovery.
38 path: User-provided file path (absolute or relative)
41 Absolute path resolved from the user's original working directory
43 from pathlib
import Path
46 if path_obj.is_absolute():
50 return str(Path.cwd().resolve() / path_obj)
55 Context manager that temporarily changes working directory for visualizer operations.
57 The C++ visualizer code expects to find assets at 'plugins/visualizer/' relative
58 to the current working directory. This context manager ensures the working directory
59 is set correctly during visualizer initialization and operations.
61 Note: This is required because the Helios C++ core prioritizes current working
62 directory for asset resolution over environment variables.
66 asset_manager = get_asset_manager()
67 working_dir = asset_manager._get_helios_build_path()
69 if working_dir
and working_dir.exists():
70 visualizer_assets = working_dir /
'plugins' /
'visualizer'
73 current_dir = Path(__file__).parent
74 packaged_build = current_dir /
'assets' /
'build'
76 if packaged_build.exists():
77 working_dir = packaged_build
78 visualizer_assets = working_dir /
'plugins' /
'visualizer'
81 repo_root = current_dir.parent
82 build_lib_dir = repo_root /
'pyhelios_build' /
'build' /
'lib'
83 working_dir = build_lib_dir.parent
84 visualizer_assets = working_dir /
'plugins' /
'visualizer'
86 if not build_lib_dir.exists():
87 logger.warning(f
"Build directory not found: {build_lib_dir}")
92 if not (visualizer_assets /
'shaders').exists():
94 asset_mgr = get_asset_manager()
95 if not asset_mgr._is_wheel_install():
96 logger.warning(f
"Visualizer assets not found at: {visualizer_assets}")
100 original_cwd = Path.cwd()
103 logger.debug(f
"Changing working directory from {original_cwd} to {working_dir}")
104 os.chdir(working_dir)
107 logger.debug(f
"Restoring working directory to {original_cwd}")
108 os.chdir(original_cwd)
112 """Raised when Visualizer operations fail."""
118 High-level interface for 3D visualization and rendering.
120 This class provides a user-friendly wrapper around the native Helios
121 visualizer plugin with automatic plugin availability checking and
122 graceful error handling.
124 The visualizer provides OpenGL-based 3D rendering with interactive controls,
125 image export, and comprehensive scene configuration options.
131 LIGHTING_PHONG_SHADOWED = 2
141 def __init__(self, width: int, height: int, antialiasing_samples: int = 1, headless: bool =
False):
143 Initialize Visualizer with graceful plugin handling.
146 width: Window width in pixels
147 height: Window height in pixels
148 antialiasing_samples: Number of antialiasing samples (default: 1)
149 headless: Enable headless mode for offscreen rendering (default: False)
152 VisualizerError: If visualizer plugin is not available
153 ValueError: If parameters are invalid
156 if not isinstance(width, _INT_TYPE):
157 raise ValueError(f
"Width must be an integer, got {type(width).__name__}")
158 if not isinstance(height, _INT_TYPE):
159 raise ValueError(f
"Height must be an integer, got {type(height).__name__}")
160 if not isinstance(antialiasing_samples, _INT_TYPE):
161 raise ValueError(f
"Antialiasing samples must be an integer, got {type(antialiasing_samples).__name__}")
162 if not isinstance(headless, bool):
163 raise ValueError(f
"Headless must be a boolean, got {type(headless).__name__}")
166 if width <= 0
or height <= 0:
167 raise ValueError(
"Width and height must be positive integers")
168 if antialiasing_samples < 1:
169 raise ValueError(
"Antialiasing samples must be at least 1")
178 registry = get_plugin_registry()
180 if not registry.is_plugin_available(
'visualizer'):
182 available_plugins = registry.get_available_plugins()
185 "Visualizer requires the 'visualizer' plugin which is not available.\n\n"
186 "The visualizer plugin provides OpenGL-based 3D rendering and visualization.\n"
187 "System requirements:\n"
188 "- OpenGL 3.3 or higher\n"
189 "- GLFW library for window management\n"
190 "- FreeType library for text rendering\n"
191 "- Display/graphics drivers (X11 on Linux, native on Windows/macOS)\n\n"
192 "To enable visualization:\n"
193 "1. Build PyHelios with visualizer plugin:\n"
194 " build_scripts/build_helios --plugins visualizer\n"
195 f
"\nCurrently available plugins: {available_plugins}"
200 system = platform.system().lower()
201 if 'linux' in system:
203 "\n\nLinux installation hints:\n"
204 "- Ubuntu/Debian: sudo apt-get install libx11-dev xorg-dev libgl1-mesa-dev libglu1-mesa-dev\n"
205 "- CentOS/RHEL: sudo yum install libX11-devel mesa-libGL-devel mesa-libGLU-devel"
207 elif 'darwin' in system:
209 "\n\nmacOS installation hints:\n"
210 "- Install XQuartz: brew install --cask xquartz\n"
211 "- OpenGL should be available by default"
213 elif 'windows' in system:
215 "\n\nWindows installation hints:\n"
216 "- OpenGL drivers should be provided by graphics card drivers\n"
217 "- Visual Studio runtime may be required"
225 if antialiasing_samples > 1:
226 self.
visualizer = visualizer_wrapper.create_visualizer_with_antialiasing(
227 width, height, antialiasing_samples, headless
230 self.
visualizer = visualizer_wrapper.create_visualizer(
231 width, height, headless
236 "Failed to create Visualizer instance. "
237 "This may indicate a problem with graphics drivers or OpenGL initialization."
239 logger.info(f
"Visualizer created successfully ({width}x{height}, AA:{antialiasing_samples}, headless:{headless})")
241 except Exception
as e:
245 """Context manager entry."""
248 def __exit__(self, exc_type, exc_value, traceback):
249 """Context manager exit with proper cleanup."""
253 visualizer_wrapper.destroy_visualizer(self.
visualizer)
254 logger.debug(
"Visualizer destroyed successfully")
255 except Exception
as e:
256 logger.warning(f
"Error destroying Visualizer: {e}")
260 @validate_build_geometry_params
263 Build Context geometry in the visualizer.
265 This method loads geometry from a Helios Context into the visualizer
266 for rendering. If no UUIDs are specified, all geometry is loaded.
269 context: Helios Context instance containing geometry
270 uuids: Optional list of primitive UUIDs to visualize (default: all)
273 VisualizerError: If geometry building fails
274 ValueError: If parameters are invalid
278 if not isinstance(context, Context):
279 raise ValueError(
"context must be a Context instance")
285 visualizer_wrapper.build_context_geometry(self.
visualizer, context.getNativePtr())
286 logger.debug(
"Built all Context geometry in visualizer")
290 raise ValueError(
"UUIDs list cannot be empty")
291 visualizer_wrapper.build_context_geometry_uuids(
292 self.
visualizer, context.getNativePtr(), uuids
294 logger.debug(f
"Built {len(uuids)} primitives in visualizer")
296 except Exception
as e:
301 Open interactive visualization window.
303 This method opens a window with the current scene and allows user
304 interaction (camera rotation, zooming, etc.). The program will pause
305 until the window is closed by the user.
307 Interactive controls:
308 - Mouse scroll: Zoom in/out
309 - Left mouse + drag: Rotate camera
310 - Right mouse + drag: Pan camera
311 - Arrow keys: Camera movement
312 - +/- keys: Zoom in/out
315 VisualizerError: If visualization fails
322 visualizer_wrapper.plot_interactive(self.
visualizer)
323 logger.debug(
"Interactive visualization completed")
324 except Exception
as e:
329 Update visualization (non-interactive).
331 This method updates the visualization window without user interaction.
332 The program continues immediately after rendering. Useful for batch
333 processing or creating image sequences.
335 In headless mode, automatically hides the window to prevent graphics driver crashes on some platforms.
338 VisualizerError: If visualization update fails
347 logger.debug(
"Visualization updated")
348 except Exception
as e:
351 @validate_print_window_params
352 def printWindow(self, filename: str, image_format: Optional[str] =
None) ->
None:
354 Save current visualization to image file.
356 This method exports the current visualization to an image file.
357 Starting from v1.3.53, supports both JPEG and PNG formats.
360 filename: Output filename for image
361 Can be absolute or relative to user's current working directory
362 Extension (.jpg, .png) is recommended but not required
363 image_format: Image format - "jpeg" or "png" (v1.3.53+).
364 If None, automatically detects from filename extension.
365 Defaults to "jpeg" if not detectable from extension.
368 VisualizerError: If image saving fails
369 ValueError: If filename or format is invalid
372 PNG format is required to preserve transparent backgrounds when using
373 setBackgroundTransparent(). JPEG format will render transparent areas as black.
376 >>> visualizer.printWindow("output.jpg") # Auto-detects JPEG
377 >>> visualizer.printWindow("output.png") # Auto-detects PNG
378 >>> visualizer.printWindow("output.img", image_format="png") # Explicit PNG
383 raise ValueError(
"Filename cannot be empty")
389 if image_format
is None:
390 if resolved_filename.lower().endswith(
'.png'):
392 elif resolved_filename.lower().endswith((
'.jpg',
'.jpeg')):
393 image_format =
'jpeg'
396 image_format =
'jpeg'
397 logger.debug(f
"No format specified and extension not recognized, defaulting to JPEG")
400 if image_format.lower()
not in [
'jpeg',
'png']:
401 raise ValueError(f
"Image format must be 'jpeg' or 'png', got '{image_format}'")
407 visualizer_wrapper.print_window_with_format(
412 logger.debug(f
"Visualization saved to {resolved_filename} ({image_format.upper()} format)")
413 except (AttributeError, NotImplementedError):
415 if image_format.lower() !=
'jpeg':
417 "PNG format requested but not available in current Helios version. "
418 "Falling back to JPEG format. Update to Helios v1.3.53+ for PNG support."
420 visualizer_wrapper.print_window(self.
visualizer, resolved_filename)
421 logger.debug(f
"Visualization saved to {resolved_filename} (JPEG format - legacy mode)")
422 except Exception
as e:
427 Close visualization window.
429 This method closes any open visualization window. It's safe to call
430 even if no window is open.
433 VisualizerError: If window closing fails
439 visualizer_wrapper.close_window(self.
visualizer)
440 logger.debug(
"Visualization window closed")
441 except Exception
as e:
446 Set camera position using Cartesian coordinates.
449 position: Camera position as vec3 in world coordinates
450 lookAt: Camera look-at point as vec3 in world coordinates
453 VisualizerError: If camera positioning fails
454 ValueError: If parameters are invalid
460 if not isinstance(position, vec3):
461 raise ValueError(f
"Position must be a vec3, got {type(position).__name__}")
462 if not isinstance(lookAt, vec3):
463 raise ValueError(f
"LookAt must be a vec3, got {type(lookAt).__name__}")
466 visualizer_wrapper.set_camera_position(self.
visualizer, position, lookAt)
467 logger.debug(f
"Camera position set to ({position.x}, {position.y}, {position.z}), looking at ({lookAt.x}, {lookAt.y}, {lookAt.z})")
468 except Exception
as e:
473 Set camera position using spherical coordinates.
476 angle: Camera position as SphericalCoord (radius, elevation, azimuth)
477 lookAt: Camera look-at point as vec3 in world coordinates
480 VisualizerError: If camera positioning fails
481 ValueError: If parameters are invalid
487 if not isinstance(angle, SphericalCoord):
488 raise ValueError(f
"Angle must be a SphericalCoord, got {type(angle).__name__}")
489 if not isinstance(lookAt, vec3):
490 raise ValueError(f
"LookAt must be a vec3, got {type(lookAt).__name__}")
493 visualizer_wrapper.set_camera_position_spherical(self.
visualizer, angle, lookAt)
494 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})")
495 except Exception
as e:
496 raise VisualizerError(f
"Failed to set camera position (spherical): {e}")
500 Set background color.
503 color: Background color as RGBcolor with values in range [0, 1]
506 VisualizerError: If color setting fails
507 ValueError: If color values are invalid
513 if not isinstance(color, RGBcolor):
514 raise ValueError(f
"Color must be an RGBcolor, got {type(color).__name__}")
517 if not (0 <= color.r <= 1
and 0 <= color.g <= 1
and 0 <= color.b <= 1):
518 raise ValueError(f
"Color components ({color.r}, {color.g}, {color.b}) must be in range [0, 1]")
521 visualizer_wrapper.set_background_color(self.
visualizer, color)
522 logger.debug(f
"Background color set to ({color.r}, {color.g}, {color.b})")
523 except Exception
as e:
528 Enable transparent background mode (v1.3.53+).
530 Sets the background to transparent with checkerboard pattern display.
531 Requires PNG output format to preserve transparency.
533 Note: When using transparent background, use printWindow() with PNG
534 format to save transparent images.
537 VisualizerError: If transparent background setting fails
543 visualizer_wrapper.set_background_transparent(self.
visualizer)
544 logger.debug(
"Background set to transparent mode")
545 except Exception
as e:
550 Set custom background image texture (v1.3.53+).
553 texture_file: Path to background image file
554 Can be absolute or relative to working directory
557 VisualizerError: If background image setting fails
558 ValueError: If texture file path is invalid
563 if not texture_file
or not isinstance(texture_file, str):
564 raise ValueError(
"Texture file path must be a non-empty string")
570 visualizer_wrapper.set_background_image(self.
visualizer, resolved_path)
571 logger.debug(f
"Background image set to {resolved_path}")
572 except Exception
as e:
577 Set sky sphere texture background with automatic scaling (v1.3.53+).
579 Creates a sky sphere that automatically scales with the scene.
580 Replaces the deprecated addSkyDomeByCenter() method.
583 texture_file: Path to spherical/equirectangular texture image
584 If None, uses default gradient sky texture
585 divisions: Number of sphere tessellation divisions (default: 50)
586 Higher values create smoother sphere but use more GPU
589 VisualizerError: If sky texture setting fails
590 ValueError: If parameters are invalid
593 >>> visualizer.setBackgroundSkyTexture() # Default gradient sky
594 >>> visualizer.setBackgroundSkyTexture("sky_hdri.jpg", divisions=100)
599 if not isinstance(divisions, _INT_TYPE)
or divisions <= 0:
600 raise ValueError(
"Divisions must be a positive integer")
605 if not isinstance(texture_file, str):
606 raise ValueError(
"Texture file must be a string")
610 visualizer_wrapper.set_background_sky_texture(
616 logger.debug(f
"Sky texture background set: {resolved_path}, divisions={divisions}")
618 logger.debug(f
"Default sky texture background set with divisions={divisions}")
619 except Exception
as e:
627 direction: Light direction vector as vec3 (will be normalized)
630 VisualizerError: If light direction setting fails
631 ValueError: If direction is invalid
637 if not isinstance(direction, vec3):
638 raise ValueError(f
"Direction must be a vec3, got {type(direction).__name__}")
641 if direction.x == 0
and direction.y == 0
and direction.z == 0:
642 raise ValueError(
"Light direction cannot be zero vector")
645 visualizer_wrapper.set_light_direction(self.
visualizer, direction)
646 logger.debug(f
"Light direction set to ({direction.x}, {direction.y}, {direction.z})")
647 except Exception
as e:
655 lighting_model: Lighting model, either:
656 - 0 or "none": No lighting
657 - 1 or "phong": Phong shading
658 - 2 or "phong_shadowed": Phong shading with shadows
661 VisualizerError: If lighting model setting fails
662 ValueError: If lighting model is invalid
668 if isinstance(lighting_model, str):
669 lighting_model_lower = lighting_model.lower()
670 if lighting_model_lower
in [
'none',
'no',
'off']:
672 elif lighting_model_lower
in [
'phong',
'phong_lighting']:
674 elif lighting_model_lower
in [
'phong_shadowed',
'phong_shadows',
'shadowed']:
677 raise ValueError(f
"Unknown lighting model string: {lighting_model}")
681 raise ValueError(f
"Lighting model must be 0 (NONE), 1 (PHONG), or 2 (PHONG_SHADOWED), got {lighting_model}")
684 visualizer_wrapper.set_lighting_model(self.
visualizer, lighting_model)
685 model_names = {0:
"NONE", 1:
"PHONG", 2:
"PHONG_SHADOWED"}
686 logger.debug(f
"Lighting model set to {model_names.get(lighting_model, lighting_model)}")
687 except Exception
as e:
692 Color context primitives based on primitive data values.
694 This method maps primitive data values to colors using the current colormap.
695 The visualization will be updated to show data variations across primitives.
697 The data must have been previously set on the primitives in the Context using
698 context.setPrimitiveDataFloat(UUID, data_name, value) before calling this method.
701 data_name: Name of the primitive data to use for coloring.
702 This should match the data label used with setPrimitiveDataFloat().
703 uuids: Optional list of specific primitive UUIDs to color.
704 If None, all primitives in context will be colored.
707 VisualizerError: If visualizer is not initialized or operation fails
708 ValueError: If data_name is invalid or UUIDs are malformed
711 >>> # Set data on primitives in context
712 >>> context.setPrimitiveDataFloat(patch_uuid, "radiation_flux_SW", 450.2)
713 >>> context.setPrimitiveDataFloat(triangle_uuid, "radiation_flux_SW", 320.1)
715 >>> # Build geometry and color by data
716 >>> visualizer.buildContextGeometry(context)
717 >>> visualizer.colorContextPrimitivesByData("radiation_flux_SW")
718 >>> visualizer.plotInteractive()
720 >>> # Color only specific primitives
721 >>> visualizer.colorContextPrimitivesByData("temperature", [uuid1, uuid2, uuid3])
726 if not data_name
or not isinstance(data_name, str):
727 raise ValueError(
"Data name must be a non-empty string")
732 visualizer_wrapper.color_context_primitives_by_data(self.
visualizer, data_name)
733 logger.debug(f
"Colored all primitives by data: {data_name}")
736 if not isinstance(uuids, (list, tuple))
or not uuids:
737 raise ValueError(
"UUIDs must be a non-empty list or tuple")
738 if not all(isinstance(uuid, _INT_TYPE)
and uuid >= 0
for uuid
in uuids):
739 raise ValueError(
"All UUIDs must be non-negative integers")
741 visualizer_wrapper.color_context_primitives_by_data_uuids(self.
visualizer, data_name, list(uuids))
742 logger.debug(f
"Colored {len(uuids)} primitives by data: {data_name}")
747 except Exception
as e:
748 raise VisualizerError(f
"Failed to color primitives by data '{data_name}': {e}")
754 Set camera field of view angle.
757 angle_FOV: Field of view angle in degrees
760 ValueError: If angle is invalid
761 VisualizerError: If operation fails
768 except (TypeError, ValueError):
769 raise ValueError(
"Field of view angle must be numeric")
770 if angle_FOV <= 0
or angle_FOV >= 180:
771 raise ValueError(
"Field of view angle must be between 0 and 180 degrees")
774 helios_lib.setCameraFieldOfView(self.
visualizer, ctypes.c_float(angle_FOV))
775 except Exception
as e:
780 Get current camera position and look-at point.
783 Tuple of (camera_position, look_at_point) as vec3 objects
786 VisualizerError: If operation fails
793 camera_pos = (ctypes.c_float * 3)()
794 look_at = (ctypes.c_float * 3)()
796 helios_lib.getCameraPosition(self.
visualizer, camera_pos, look_at)
798 return (
vec3(camera_pos[0], camera_pos[1], camera_pos[2]),
799 vec3(look_at[0], look_at[1], look_at[2]))
800 except Exception
as e:
805 Get current background color.
808 Background color as RGBcolor object
811 VisualizerError: If operation fails
818 color = (ctypes.c_float * 3)()
820 helios_lib.getBackgroundColor(self.
visualizer, color)
823 except Exception
as e:
830 Set light intensity scaling factor.
833 intensity_factor: Light intensity scaling factor (typically 0.1 to 10.0)
836 ValueError: If intensity factor is invalid
837 VisualizerError: If operation fails
842 if not isinstance(intensity_factor, _NUMERIC_TYPES):
843 raise ValueError(
"Light intensity factor must be numeric")
844 if intensity_factor <= 0:
845 raise ValueError(
"Light intensity factor must be positive")
848 helios_lib.setLightIntensityFactor(self.
visualizer, ctypes.c_float(intensity_factor))
849 except Exception
as e:
856 Get window size in pixels.
859 Tuple of (width, height) in pixels
862 VisualizerError: If operation fails
868 width = ctypes.c_uint()
869 height = ctypes.c_uint()
871 helios_lib.getWindowSize(self.
visualizer, ctypes.byref(width), ctypes.byref(height))
873 return (width.value, height.value)
874 except Exception
as e:
879 Get framebuffer size in pixels.
882 Tuple of (width, height) in pixels
885 VisualizerError: If operation fails
891 width = ctypes.c_uint()
892 height = ctypes.c_uint()
894 helios_lib.getFramebufferSize(self.
visualizer, ctypes.byref(width), ctypes.byref(height))
896 return (width.value, height.value)
897 except Exception
as e:
902 Print window with default filename.
905 VisualizerError: If operation fails
911 helios_lib.printWindowDefault(self.
visualizer)
912 except Exception
as e:
917 Display image from RGBA pixel data.
920 pixel_data: RGBA pixel data as list of integers (0-255)
921 width: Image width in pixels
922 height: Image height in pixels
925 ValueError: If parameters are invalid
926 VisualizerError: If operation fails
931 if not isinstance(pixel_data, (list, tuple)):
932 raise ValueError(
"Pixel data must be a list or tuple")
933 if not isinstance(width, _INT_TYPE)
or width <= 0:
934 raise ValueError(
"Width must be a positive integer")
935 if not isinstance(height, _INT_TYPE)
or height <= 0:
936 raise ValueError(
"Height must be a positive integer")
938 expected_size = width * height * 4
939 if len(pixel_data) != expected_size:
940 raise ValueError(f
"Pixel data size mismatch: expected {expected_size}, got {len(pixel_data)}")
944 pixel_array = (ctypes.c_ubyte * len(pixel_data))(*pixel_data)
945 helios_lib.displayImageFromPixels(self.
visualizer, pixel_array, width, height)
946 except Exception
as e:
951 Display image from file.
954 filename: Path to image file
957 ValueError: If filename is invalid
958 VisualizerError: If operation fails
963 if not isinstance(filename, str)
or not filename.strip():
964 raise ValueError(
"Filename must be a non-empty string")
967 helios_lib.displayImageFromFile(self.
visualizer, filename.encode(
'utf-8'))
968 except Exception
as e:
975 Get RGB pixel data from current window.
978 buffer: Pre-allocated buffer to store pixel data
981 ValueError: If buffer is invalid
982 VisualizerError: If operation fails
987 if not isinstance(buffer, list):
988 raise ValueError(
"Buffer must be a list")
992 buffer_array = (ctypes.c_uint * len(buffer))(*buffer)
996 for i
in range(len(buffer)):
997 buffer[i] = buffer_array[i]
998 except Exception
as e:
1001 def getDepthMap(self) -> Tuple[List[float], int, int]:
1003 Get depth map from current window.
1006 Tuple of (depth_pixels, width, height)
1009 VisualizerError: If operation fails
1015 depth_ptr = ctypes.POINTER(ctypes.c_float)()
1016 width = ctypes.c_uint()
1017 height = ctypes.c_uint()
1018 buffer_size = ctypes.c_uint()
1021 ctypes.byref(width), ctypes.byref(height),
1022 ctypes.byref(buffer_size))
1025 if depth_ptr
and buffer_size.value > 0:
1026 depth_data = [depth_ptr[i]
for i
in range(buffer_size.value)]
1027 return (depth_data, width.value, height.value)
1030 except Exception
as e:
1035 Plot depth map visualization.
1038 VisualizerError: If operation fails
1045 except Exception
as e:
1052 Clear all geometry from visualizer.
1055 VisualizerError: If operation fails
1062 except Exception
as e:
1067 Clear context geometry from visualizer.
1070 VisualizerError: If operation fails
1076 helios_lib.clearContextGeometry(self.
visualizer)
1077 except Exception
as e:
1082 Delete specific geometry by ID.
1085 geometry_id: ID of geometry to delete
1088 ValueError: If geometry ID is invalid
1089 VisualizerError: If operation fails
1094 if not isinstance(geometry_id, _INT_TYPE)
or geometry_id < 0:
1095 raise ValueError(
"Geometry ID must be a non-negative integer")
1098 helios_lib.deleteGeometry(self.
visualizer, geometry_id)
1099 except Exception
as e:
1104 Update context primitive colors.
1107 VisualizerError: If operation fails
1113 helios_lib.updateContextPrimitiveColors(self.
visualizer)
1114 except Exception
as e:
1115 raise VisualizerError(f
"Failed to update context primitive colors: {e}")
1121 Get vertices of a geometry primitive.
1124 geometry_id: Unique identifier of the geometry primitive
1127 List of vertices as vec3 objects
1130 ValueError: If geometry ID is invalid
1131 VisualizerError: If operation fails
1134 >>> # Get vertices of a specific geometry
1135 >>> vertices = visualizer.getGeometryVertices(geometry_id)
1136 >>> for vertex in vertices:
1137 ... print(f"Vertex: ({vertex.x}, {vertex.y}, {vertex.z})")
1142 if not isinstance(geometry_id, _INT_TYPE)
or geometry_id < 0:
1143 raise ValueError(
"Geometry ID must be a non-negative integer")
1146 vertices_list = visualizer_wrapper.get_geometry_vertices(self.
visualizer, geometry_id)
1148 return [
vec3(v[0], v[1], v[2])
for v
in vertices_list]
1149 except Exception
as e:
1154 Set vertices of a geometry primitive.
1156 This allows dynamic modification of geometry shapes during visualization.
1157 Useful for animating geometry or adjusting shapes based on simulation results.
1160 geometry_id: Unique identifier of the geometry primitive
1161 vertices: List of new vertices as vec3 objects
1164 ValueError: If parameters are invalid
1165 VisualizerError: If operation fails
1168 >>> # Modify vertices of an existing geometry
1169 >>> vertices = visualizer.getGeometryVertices(geometry_id)
1170 >>> # Scale all vertices by 2x
1171 >>> scaled_vertices = [vec3(v.x*2, v.y*2, v.z*2) for v in vertices]
1172 >>> visualizer.setGeometryVertices(geometry_id, scaled_vertices)
1177 if not isinstance(geometry_id, _INT_TYPE)
or geometry_id < 0:
1178 raise ValueError(
"Geometry ID must be a non-negative integer")
1180 if not vertices
or not isinstance(vertices, (list, tuple)):
1181 raise ValueError(
"Vertices must be a non-empty list")
1183 if not all(isinstance(v, vec3)
for v
in vertices):
1184 raise ValueError(
"All vertices must be vec3 objects")
1187 visualizer_wrapper.set_geometry_vertices(self.
visualizer, geometry_id, vertices)
1188 logger.debug(f
"Set {len(vertices)} vertices for geometry {geometry_id}")
1189 except Exception
as e:
1196 Add coordinate axes at origin with unit length.
1199 VisualizerError: If operation fails
1205 helios_lib.addCoordinateAxes(self.
visualizer)
1206 except Exception
as e:
1211 Add coordinate axes with custom properties.
1214 origin: Axes origin position
1215 length: Axes length in each direction
1216 sign: Axis direction ("both" or "positive")
1219 ValueError: If parameters are invalid
1220 VisualizerError: If operation fails
1225 if not isinstance(origin, vec3):
1226 raise ValueError(
"Origin must be a vec3")
1227 if not isinstance(length, vec3):
1228 raise ValueError(
"Length must be a vec3")
1229 if not isinstance(sign, str)
or sign
not in [
"both",
"positive"]:
1230 raise ValueError(
"Sign must be 'both' or 'positive'")
1233 origin_array = (ctypes.c_float * 3)(origin.x, origin.y, origin.z)
1234 length_array = (ctypes.c_float * 3)(length.x, length.y, length.z)
1235 helios_lib.addCoordinateAxesCustom(self.
visualizer, origin_array, length_array, sign.encode(
'utf-8'))
1236 except Exception
as e:
1241 Remove coordinate axes.
1244 VisualizerError: If operation fails
1250 helios_lib.disableCoordinateAxes(self.
visualizer)
1251 except Exception
as e:
1254 def addGridWireFrame(self, center: vec3, size: vec3, subdivisions: List[int]) ->
None:
1259 center: Grid center position
1260 size: Grid size in each direction
1261 subdivisions: Grid subdivisions [x, y, z]
1264 ValueError: If parameters are invalid
1265 VisualizerError: If operation fails
1270 if not isinstance(center, vec3):
1271 raise ValueError(
"Center must be a vec3")
1272 if not isinstance(size, vec3):
1273 raise ValueError(
"Size must be a vec3")
1274 if not isinstance(subdivisions, (list, tuple))
or len(subdivisions) != 3:
1275 raise ValueError(
"Subdivisions must be a list of 3 integers")
1276 if not all(isinstance(s, _INT_TYPE)
and s > 0
for s
in subdivisions):
1277 raise ValueError(
"All subdivisions must be positive integers")
1280 center_array = (ctypes.c_float * 3)(center.x, center.y, center.z)
1281 size_array = (ctypes.c_float * 3)(size.x, size.y, size.z)
1282 subdiv_array = (ctypes.c_int * 3)(*subdivisions)
1283 helios_lib.addGridWireFrame(self.
visualizer, center_array, size_array, subdiv_array)
1284 except Exception
as e:
1294 VisualizerError: If operation fails
1301 except Exception
as e:
1309 VisualizerError: If operation fails
1316 except Exception
as e:
1321 Set colorbar position.
1324 position: Colorbar position
1327 ValueError: If position is invalid
1328 VisualizerError: If operation fails
1333 if not isinstance(position, vec3):
1334 raise ValueError(
"Position must be a vec3")
1337 pos_array = (ctypes.c_float * 3)(position.x, position.y, position.z)
1338 helios_lib.setColorbarPosition(self.
visualizer, pos_array)
1339 except Exception
as e:
1347 width: Colorbar width
1348 height: Colorbar height
1351 ValueError: If size is invalid
1352 VisualizerError: If operation fails
1357 if not isinstance(width, _NUMERIC_TYPES)
or width <= 0:
1358 raise ValueError(
"Width must be a positive number")
1359 if not isinstance(height, _NUMERIC_TYPES)
or height <= 0:
1360 raise ValueError(
"Height must be a positive number")
1363 size_array = (ctypes.c_float * 2)(float(width), float(height))
1364 helios_lib.setColorbarSize(self.
visualizer, size_array)
1365 except Exception
as e:
1373 min_val: Minimum value
1374 max_val: Maximum value
1377 ValueError: If range is invalid
1378 VisualizerError: If operation fails
1383 if not isinstance(min_val, _NUMERIC_TYPES):
1384 raise ValueError(
"Minimum value must be numeric")
1385 if not isinstance(max_val, _NUMERIC_TYPES):
1386 raise ValueError(
"Maximum value must be numeric")
1387 if min_val >= max_val:
1388 raise ValueError(
"Minimum value must be less than maximum value")
1391 helios_lib.setColorbarRange(self.
visualizer, float(min_val), float(max_val))
1392 except Exception
as e:
1397 Set colorbar tick marks.
1400 ticks: List of tick values
1403 ValueError: If ticks are invalid
1404 VisualizerError: If operation fails
1409 if not isinstance(ticks, (list, tuple)):
1410 raise ValueError(
"Ticks must be a list or tuple")
1411 if not all(isinstance(t, _NUMERIC_TYPES)
for t
in ticks):
1412 raise ValueError(
"All tick values must be numeric")
1416 ticks_array = (ctypes.c_float * len(ticks))(*ticks)
1417 helios_lib.setColorbarTicks(self.
visualizer, ticks_array, len(ticks))
1419 helios_lib.setColorbarTicks(self.
visualizer,
None, 0)
1420 except Exception
as e:
1428 title: Colorbar title
1431 ValueError: If title is invalid
1432 VisualizerError: If operation fails
1437 if not isinstance(title, str):
1438 raise ValueError(
"Title must be a string")
1441 helios_lib.setColorbarTitle(self.
visualizer, title.encode(
'utf-8'))
1442 except Exception
as e:
1447 Set colorbar font color.
1453 ValueError: If color is invalid
1454 VisualizerError: If operation fails
1459 if not isinstance(color, RGBcolor):
1460 raise ValueError(
"Color must be an RGBcolor")
1463 color_array = (ctypes.c_float * 3)(color.r, color.g, color.b)
1464 helios_lib.setColorbarFontColor(self.
visualizer, color_array)
1465 except Exception
as e:
1470 Set colorbar font size.
1473 font_size: Font size
1476 ValueError: If font size is invalid
1477 VisualizerError: If operation fails
1482 if not isinstance(font_size, _INT_TYPE)
or font_size <= 0:
1483 raise ValueError(
"Font size must be a positive integer")
1486 helios_lib.setColorbarFontSize(self.
visualizer, font_size)
1487 except Exception
as e:
1492 def setColormap(self, colormap: Union[int, str]) ->
None:
1494 Set predefined colormap.
1497 colormap: Colormap ID (0-5) or name ("HOT", "COOL", "RAINBOW", "LAVA", "PARULA", "GRAY")
1500 ValueError: If colormap is invalid
1501 VisualizerError: If operation fails
1507 "HOT": 0,
"COOL": 1,
"RAINBOW": 2,
1508 "LAVA": 3,
"PARULA": 4,
"GRAY": 5
1511 if isinstance(colormap, str):
1512 if colormap.upper()
not in colormap_map:
1513 raise ValueError(f
"Unknown colormap name: {colormap}")
1514 colormap_id = colormap_map[colormap.upper()]
1515 elif isinstance(colormap, _INT_TYPE):
1516 if colormap < 0
or colormap > 5:
1517 raise ValueError(
"Colormap ID must be 0-5")
1518 colormap_id = colormap
1520 raise ValueError(
"Colormap must be integer ID or string name")
1523 helios_lib.setColormap(self.
visualizer, colormap_id)
1524 except Exception
as e:
1527 def setCustomColormap(self, colors: List[RGBcolor], divisions: List[float]) ->
None:
1529 Set custom colormap.
1532 colors: List of RGB colors
1533 divisions: List of division points (same length as colors)
1536 ValueError: If parameters are invalid
1537 VisualizerError: If operation fails
1542 if not isinstance(colors, (list, tuple))
or not colors:
1543 raise ValueError(
"Colors must be a non-empty list")
1544 if not isinstance(divisions, (list, tuple))
or not divisions:
1545 raise ValueError(
"Divisions must be a non-empty list")
1546 if len(colors) != len(divisions):
1547 raise ValueError(
"Colors and divisions must have the same length")
1549 if not all(isinstance(c, RGBcolor)
for c
in colors):
1550 raise ValueError(
"All colors must be RGBcolor objects")
1551 if not all(isinstance(d, _NUMERIC_TYPES)
for d
in divisions):
1552 raise ValueError(
"All divisions must be numeric")
1556 color_array = (ctypes.c_float * (len(colors) * 3))()
1557 for i, color
in enumerate(colors):
1558 color_array[i*3] = color.r
1559 color_array[i*3+1] = color.g
1560 color_array[i*3+2] = color.b
1562 divisions_array = (ctypes.c_float * len(divisions))(*divisions)
1564 helios_lib.setCustomColormap(self.
visualizer, color_array, divisions_array, len(colors))
1565 except Exception
as e:
1572 Color context primitives by object data.
1575 data_name: Name of object data to use for coloring
1576 obj_ids: Optional list of object IDs to color (None for all)
1579 ValueError: If parameters are invalid
1580 VisualizerError: If operation fails
1585 if not isinstance(data_name, str)
or not data_name.strip():
1586 raise ValueError(
"Data name must be a non-empty string")
1590 helios_lib.colorContextPrimitivesByObjectData(self.
visualizer, data_name.encode(
'utf-8'))
1592 if not isinstance(obj_ids, (list, tuple)):
1593 raise ValueError(
"Object IDs must be a list or tuple")
1594 if not all(isinstance(oid, _INT_TYPE)
and oid >= 0
for oid
in obj_ids):
1595 raise ValueError(
"All object IDs must be non-negative integers")
1598 obj_ids_array = (ctypes.c_uint * len(obj_ids))(*obj_ids)
1599 helios_lib.colorContextPrimitivesByObjectDataIDs(self.
visualizer, data_name.encode(
'utf-8'), obj_ids_array, len(obj_ids))
1601 helios_lib.colorContextPrimitivesByObjectDataIDs(self.
visualizer, data_name.encode(
'utf-8'),
None, 0)
1602 except Exception
as e:
1603 raise VisualizerError(f
"Failed to color primitives by object data '{data_name}': {e}")
1607 Color context primitives randomly.
1610 uuids: Optional list of primitive UUIDs to color (None for all)
1613 ValueError: If UUIDs are invalid
1614 VisualizerError: If operation fails
1621 helios_lib.colorContextPrimitivesRandomly(self.
visualizer,
None, 0)
1623 if not isinstance(uuids, (list, tuple)):
1624 raise ValueError(
"UUIDs must be a list or tuple")
1625 if not all(isinstance(uuid, _INT_TYPE)
and uuid >= 0
for uuid
in uuids):
1626 raise ValueError(
"All UUIDs must be non-negative integers")
1629 uuid_array = (ctypes.c_uint * len(uuids))(*uuids)
1630 helios_lib.colorContextPrimitivesRandomly(self.
visualizer, uuid_array, len(uuids))
1632 helios_lib.colorContextPrimitivesRandomly(self.
visualizer,
None, 0)
1633 except Exception
as e:
1638 Color context objects randomly.
1641 obj_ids: Optional list of object IDs to color (None for all)
1644 ValueError: If object IDs are invalid
1645 VisualizerError: If operation fails
1652 helios_lib.colorContextObjectsRandomly(self.
visualizer,
None, 0)
1654 if not isinstance(obj_ids, (list, tuple)):
1655 raise ValueError(
"Object IDs must be a list or tuple")
1656 if not all(isinstance(oid, _INT_TYPE)
and oid >= 0
for oid
in obj_ids):
1657 raise ValueError(
"All object IDs must be non-negative integers")
1660 obj_ids_array = (ctypes.c_uint * len(obj_ids))(*obj_ids)
1661 helios_lib.colorContextObjectsRandomly(self.
visualizer, obj_ids_array, len(obj_ids))
1663 helios_lib.colorContextObjectsRandomly(self.
visualizer,
None, 0)
1664 except Exception
as e:
1669 Clear primitive colors from previous coloring operations.
1672 VisualizerError: If operation fails
1679 except Exception
as e:
1686 Hide Helios logo watermark.
1689 VisualizerError: If operation fails
1696 except Exception
as e:
1701 Show Helios logo watermark.
1704 VisualizerError: If operation fails
1711 except Exception
as e:
1716 Update watermark geometry to match current window size.
1719 VisualizerError: If operation fails
1726 except Exception
as e:
1733 Hide navigation gizmo (coordinate axes indicator in corner).
1735 The navigation gizmo shows XYZ axes orientation and can be clicked
1736 to snap the camera to standard views (top, front, side, etc.).
1739 VisualizerError: If operation fails
1745 visualizer_wrapper.hide_navigation_gizmo(self.
visualizer)
1746 logger.debug(
"Navigation gizmo hidden")
1747 except Exception
as e:
1752 Show navigation gizmo (coordinate axes indicator in corner).
1754 The navigation gizmo shows XYZ axes orientation and can be clicked
1755 to snap the camera to standard views (top, front, side, etc.).
1757 Note: Navigation gizmo is shown by default in v1.3.53+.
1760 VisualizerError: If operation fails
1766 visualizer_wrapper.show_navigation_gizmo(self.
visualizer)
1767 logger.debug(
"Navigation gizmo shown")
1768 except Exception
as e:
1775 Enable standard output from visualizer plugin.
1778 VisualizerError: If operation fails
1785 except Exception
as e:
1790 Disable standard output from visualizer plugin.
1793 VisualizerError: If operation fails
1800 except Exception
as e:
1803 def plotOnce(self, get_keystrokes: bool =
True) ->
None:
1805 Run one rendering loop.
1808 get_keystrokes: Whether to process keystrokes
1811 VisualizerError: If operation fails
1817 helios_lib.plotOnce(self.
visualizer, get_keystrokes)
1818 except Exception
as e:
1823 Update visualization with window visibility control.
1826 hide_window: Whether to hide the window during update
1829 VisualizerError: If operation fails
1836 helios_lib.plotUpdateWithVisibility(self.
visualizer, hide_window)
1837 except Exception
as e:
1838 raise VisualizerError(f
"Failed to update plot with visibility control: {e}")
1841 """Destructor to ensure proper cleanup."""
1842 if hasattr(self,
'visualizer')
and self.
visualizer is not None:
1845 visualizer_wrapper.destroy_visualizer(self.
visualizer)
Raised when Visualizer operations fail.
None colorContextObjectsRandomly(self, Optional[List[int]] obj_ids=None)
Color context objects randomly.
None plotInteractive(self)
Open interactive visualization window.
None setColorbarFontColor(self, RGBcolor color)
Set colorbar font color.
__exit__(self, exc_type, exc_value, traceback)
Context manager exit with proper cleanup.
None hideNavigationGizmo(self)
Hide navigation gizmo (coordinate axes indicator in corner).
None setBackgroundImage(self, str texture_file)
Set custom background image texture (v1.3.53+).
None buildContextGeometry(self, Context context, Optional[List[int]] uuids=None)
Build Context geometry in the visualizer.
None disableMessages(self)
Disable standard output from visualizer plugin.
None hideWatermark(self)
Hide Helios logo watermark.
None setBackgroundTransparent(self)
Enable transparent background mode (v1.3.53+).
None colorContextPrimitivesByObjectData(self, str data_name, Optional[List[int]] obj_ids=None)
Color context primitives by object data.
__enter__(self)
Context manager entry.
__init__(self, int width, int height, int antialiasing_samples=1, bool headless=False)
Initialize Visualizer with graceful plugin handling.
None setGeometryVertices(self, int geometry_id, List[vec3] vertices)
Set vertices of a geometry primitive.
None clearColor(self)
Clear primitive colors from previous coloring operations.
None setColorbarPosition(self, vec3 position)
Set colorbar position.
None setCustomColormap(self, List[RGBcolor] colors, List[float] divisions)
Set custom colormap.
None deleteGeometry(self, int geometry_id)
Delete specific geometry by ID.
None getWindowPixelsRGB(self, List[int] buffer)
Get RGB pixel data from current window.
None setCameraPositionSpherical(self, SphericalCoord angle, vec3 lookAt)
Set camera position using spherical coordinates.
None clearGeometry(self)
Clear all geometry from visualizer.
None setCameraFieldOfView(self, float angle_FOV)
Set camera field of view angle.
None displayImageFromFile(self, str filename)
Display image from file.
None setLightingModel(self, Union[int, str] lighting_model)
Set lighting model.
None showNavigationGizmo(self)
Show navigation gizmo (coordinate axes indicator in corner).
None updateContextPrimitiveColors(self)
Update context primitive colors.
None setColorbarRange(self, float min_val, float max_val)
Set colorbar range.
int LIGHTING_PHONG_SHADOWED
None plotDepthMap(self)
Plot depth map visualization.
Tuple[int, int] getWindowSize(self)
Get window size in pixels.
List[vec3] getGeometryVertices(self, int geometry_id)
Get vertices of a geometry primitive.
None printWindowDefault(self)
Print window with default filename.
None disableColorbar(self)
Disable colorbar.
None plotUpdateWithVisibility(self, bool hide_window=False)
Update visualization with window visibility control.
Tuple[vec3, vec3] getCameraPosition(self)
Get current camera position and look-at point.
None setLightDirection(self, vec3 direction)
Set light direction.
None closeWindow(self)
Close visualization window.
None setColorbarTitle(self, str title)
Set colorbar title.
__del__(self)
Destructor to ensure proper cleanup.
None displayImageFromPixels(self, List[int] pixel_data, int width, int height)
Display image from RGBA pixel data.
None addCoordinateAxesCustom(self, vec3 origin, vec3 length, str sign="both")
Add coordinate axes with custom properties.
None setColormap(self, Union[int, str] colormap)
Set predefined colormap.
None setBackgroundColor(self, RGBcolor color)
Set background color.
None colorContextPrimitivesByData(self, str data_name, Optional[List[int]] uuids=None)
Color context primitives based on primitive data values.
None printWindow(self, str filename, Optional[str] image_format=None)
Save current visualization to image file.
None disableCoordinateAxes(self)
Remove coordinate axes.
None setColorbarSize(self, float width, float height)
Set colorbar size.
None clearContextGeometry(self)
Clear context geometry from visualizer.
Tuple[List[float], int, int] getDepthMap(self)
Get depth map from current window.
None setCameraPosition(self, vec3 position, vec3 lookAt)
Set camera position using Cartesian coordinates.
RGBcolor getBackgroundColor(self)
Get current background color.
None setLightIntensityFactor(self, float intensity_factor)
Set light intensity scaling factor.
None enableMessages(self)
Enable standard output from visualizer plugin.
None addCoordinateAxes(self)
Add coordinate axes at origin with unit length.
None addGridWireFrame(self, vec3 center, vec3 size, List[int] subdivisions)
Add grid wireframe.
None plotUpdate(self)
Update visualization (non-interactive).
None setColorbarFontSize(self, int font_size)
Set colorbar font size.
None setColorbarTicks(self, List[float] ticks)
Set colorbar tick marks.
None colorContextPrimitivesRandomly(self, Optional[List[int]] uuids=None)
Color context primitives randomly.
None showWatermark(self)
Show Helios logo watermark.
Tuple[int, int] getFramebufferSize(self)
Get framebuffer size in pixels.
None enableColorbar(self)
Enable colorbar.
None plotOnce(self, bool get_keystrokes=True)
Run one rendering loop.
None setBackgroundSkyTexture(self, Optional[str] texture_file=None, int divisions=50)
Set sky sphere texture background with automatic scaling (v1.3.53+).
None updateWatermark(self)
Update watermark geometry to match current window size.
str _resolve_user_path(str path)
Resolve a user-provided path to an absolute path before working directory changes.
_visualizer_working_directory()
Context manager that temporarily changes working directory for visualizer operations.