Files
PozzettiAndrea-ComfyUI-Geom…/nodes/visualization.py
T
2025-11-14 22:04:06 +01:00

748 lines
28 KiB
Python

"""
Visualization Nodes - 3D mesh preview and visualization
"""
import trimesh as trimesh_module
import os
import tempfile
import uuid
import sys
from pathlib import Path
# Import create_bbox_visualization from Hunyuan3D-Part
# Add parent directory to path to allow cross-custom-node imports
custom_nodes_dir = Path(__file__).parent.parent.parent
hunyuan_utils_path = custom_nodes_dir / "ComfyUI-Hunyuan3D-Part" / "node_utils"
if str(hunyuan_utils_path) not in sys.path:
sys.path.insert(0, str(hunyuan_utils_path))
try:
from mesh_utils import create_bbox_visualization, export_scene_to_vtp
BBOX_VIZ_AVAILABLE = True
except ImportError:
print("[GeomPack] WARNING: Could not import bbox functions from Hunyuan3D-Part")
print("[GeomPack] The Preview Bounding Boxes node will not be available")
BBOX_VIZ_AVAILABLE = False
export_scene_to_vtp = None
# ComfyUI folder paths
try:
import folder_paths
COMFYUI_OUTPUT_FOLDER = folder_paths.get_output_directory()
except:
COMFYUI_OUTPUT_FOLDER = None
def _export_mesh_with_scalars_vtp(trimesh: trimesh_module.Trimesh, filepath: str):
"""
Export trimesh to VTK PolyData XML format (.vtp) with vertex scalar attributes.
VTP format preserves vertex attributes (scalar fields) which can be visualized
in VTK.js with color mapping.
Args:
trimesh: Trimesh object with optional vertex_attributes
filepath: Output .vtp file path
"""
import xml.etree.ElementTree as ET
print(f"[_export_mesh_with_scalars_vtp] Exporting to VTP: {filepath}")
# Create VTK PolyData XML structure
vtk_file = ET.Element('VTKFile', type='PolyData', version='1.0', byte_order='LittleEndian')
poly_data = ET.SubElement(vtk_file, 'PolyData')
num_verts = len(trimesh.vertices)
num_faces = len(trimesh.faces)
piece = ET.SubElement(poly_data, 'Piece', NumberOfPoints=str(num_verts), NumberOfPolys=str(num_faces))
# Points section
points = ET.SubElement(piece, 'Points')
points_data_array = ET.SubElement(points, 'DataArray',
type='Float32',
NumberOfComponents='3',
format='ascii')
# Flatten vertices to space-separated string
verts_flat = trimesh.vertices.flatten()
points_data_array.text = ' '.join(map(str, verts_flat))
# PointData section (scalar fields)
point_data = ET.SubElement(piece, 'PointData')
# Add vertex attributes as scalar arrays
if hasattr(trimesh, 'vertex_attributes') and trimesh.vertex_attributes:
for attr_name, attr_values in trimesh.vertex_attributes.items():
print(f"[_export_mesh_with_scalars_vtp] Adding scalar field: {attr_name}")
scalar_array = ET.SubElement(point_data, 'DataArray',
type='Float32',
Name=attr_name,
format='ascii')
scalar_array.text = ' '.join(map(str, attr_values.flatten()))
# Polys section (faces/triangles)
polys = ET.SubElement(piece, 'Polys')
# Connectivity: vertex indices for each face
connectivity = ET.SubElement(polys, 'DataArray',
type='Int32',
Name='connectivity',
format='ascii')
faces_flat = trimesh.faces.flatten()
connectivity.text = ' '.join(map(str, faces_flat))
# Offsets: cumulative count of indices (each triangle has 3 vertices)
offsets = ET.SubElement(polys, 'DataArray',
type='Int32',
Name='offsets',
format='ascii')
offset_values = [(i + 1) * 3 for i in range(num_faces)]
offsets.text = ' '.join(map(str, offset_values))
# Write to file with pretty formatting
tree = ET.ElementTree(vtk_file)
ET.indent(tree, space=' ')
tree.write(filepath, encoding='utf-8', xml_declaration=True)
print(f"[_export_mesh_with_scalars_vtp] Export complete: {num_verts} vertices, {num_faces} faces")
class PreviewMeshNode:
"""
Preview mesh with interactive 3D viewer.
Displays mesh in an interactive Three.js viewer with orbit controls.
Allows rotating, panning, and zooming to inspect the mesh geometry.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_mesh"
CATEGORY = "geompack/visualization"
def preview_mesh(self, trimesh):
"""
Export mesh to GLB and prepare for 3D preview.
Args:
trimesh: Input trimesh_module.Trimesh object
Returns:
dict: UI data for frontend widget
"""
print(f"[PreviewMesh] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Generate unique filename
filename = f"preview_{uuid.uuid4().hex[:8]}.glb"
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export to GLB (best format for Three.js)
try:
trimesh.export(filepath, file_type='glb')
print(f"[PreviewMesh] Exported to: {filepath}")
except Exception as e:
print(f"[PreviewMesh] Export failed: {e}")
# Fallback to OBJ
filename = filename.replace('.glb', '.obj')
filepath = filepath.replace('.glb', '.obj')
trimesh.export(filepath, file_type='obj')
print(f"[PreviewMesh] Exported to OBJ: {filepath}")
# Calculate bounding box info for camera setup
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Return metadata for frontend widget
return {
"ui": {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
}
}
class PreviewMeshVTKNode:
"""
Preview mesh with VTK.js scientific visualization viewer.
Displays mesh in an interactive VTK.js viewer with trackball controls.
Better for scientific visualization, mesh analysis, and large datasets.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_mesh_vtk"
CATEGORY = "geompack/visualization"
def preview_mesh_vtk(self, trimesh):
"""
Export mesh to STL and prepare for VTK.js preview.
Args:
trimesh: Input trimesh_module.Trimesh object
Returns:
dict: UI data for frontend widget
"""
print(f"[PreviewMeshVTK] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Generate unique filename
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.stl"
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export to STL (native format for VTK.js)
try:
trimesh.export(filepath, file_type='stl')
print(f"[PreviewMeshVTK] Exported to: {filepath}")
except Exception as e:
print(f"[PreviewMeshVTK] Export failed: {e}")
# Fallback to OBJ
filename = filename.replace('.stl', '.obj')
filepath = filepath.replace('.stl', '.obj')
trimesh.export(filepath, file_type='obj')
print(f"[PreviewMeshVTK] Exported to OBJ: {filepath}")
# Calculate bounding box info for camera setup
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Check if mesh is watertight
is_watertight = trimesh.is_watertight
# Calculate volume and area (only if watertight)
volume = None
area = None
try:
if is_watertight:
volume = float(trimesh.volume)
area = float(trimesh.area)
except Exception as e:
print(f"[PreviewMeshVTK] Could not calculate volume/area: {e}")
# Get field names (vertex/face data arrays)
field_names = []
if hasattr(trimesh, 'vertex_attributes') and trimesh.vertex_attributes:
field_names.extend([f"vertex.{k}" for k in trimesh.vertex_attributes.keys()])
if hasattr(trimesh, 'face_attributes') and trimesh.face_attributes:
field_names.extend([f"face.{k}" for k in trimesh.face_attributes.keys()])
# Return metadata for frontend widget
ui_data = {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
"is_watertight": [bool(is_watertight)],
}
# Add optional fields if available
if volume is not None:
ui_data["volume"] = [volume]
if area is not None:
ui_data["area"] = [area]
if field_names:
ui_data["field_names"] = [field_names]
print(f"[PreviewMeshVTK] Mesh info: watertight={is_watertight}, volume={volume}, area={area}, fields={len(field_names)}")
return {"ui": ui_data}
class PreviewMeshVTKHidableMenuNode:
"""
Preview mesh with VTK.js scientific visualization viewer (with hidable menu).
Displays mesh in an interactive VTK.js viewer with collapsible controls menu.
Provides a cleaner interface with toggle button to show/hide the control options.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_mesh_vtk_hidable"
CATEGORY = "geompack/visualization"
def preview_mesh_vtk_hidable(self, trimesh):
"""
Export mesh to STL and prepare for VTK.js preview with hidable menu.
Args:
trimesh: Input trimesh_module.Trimesh object
Returns:
dict: UI data for frontend widget
"""
print(f"[PreviewMeshVTKHidable] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Generate unique filename
filename = f"preview_vtk_hidable_{uuid.uuid4().hex[:8]}.stl"
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export to STL (native format for VTK.js)
try:
trimesh.export(filepath, file_type='stl')
print(f"[PreviewMeshVTKHidable] Exported to: {filepath}")
except Exception as e:
print(f"[PreviewMeshVTKHidable] Export failed: {e}")
# Fallback to OBJ
filename = filename.replace('.stl', '.obj')
filepath = filepath.replace('.stl', '.obj')
trimesh.export(filepath, file_type='obj')
print(f"[PreviewMeshVTKHidable] Exported to OBJ: {filepath}")
# Calculate bounding box info for camera setup
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Check if mesh is watertight
is_watertight = trimesh.is_watertight
# Calculate volume and area (only if watertight)
volume = None
area = None
try:
if is_watertight:
volume = float(trimesh.volume)
area = float(trimesh.area)
except Exception as e:
print(f"[PreviewMeshVTKHidable] Could not calculate volume/area: {e}")
# Get field names (vertex/face data arrays)
field_names = []
if hasattr(trimesh, 'vertex_attributes') and trimesh.vertex_attributes:
field_names.extend([f"vertex.{k}" for k in trimesh.vertex_attributes.keys()])
if hasattr(trimesh, 'face_attributes') and trimesh.face_attributes:
field_names.extend([f"face.{k}" for k in trimesh.face_attributes.keys()])
# Return metadata for frontend widget
ui_data = {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
"is_watertight": [bool(is_watertight)],
}
# Add optional fields if available
if volume is not None:
ui_data["volume"] = [volume]
if area is not None:
ui_data["area"] = [area]
if field_names:
ui_data["field_names"] = [field_names]
print(f"[PreviewMeshVTKHidable] Mesh info: watertight={is_watertight}, volume={volume}, area={area}, fields={len(field_names)}")
return {"ui": ui_data}
class PreviewMeshVTKFiltersNode:
"""
Preview mesh with VTK.js scientific visualization viewer including filters.
Displays mesh in an interactive VTK.js viewer with trackball controls
and filter controls (smoothing, outline, cutting plane).
Better for scientific visualization, mesh analysis, and large datasets.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_mesh_vtk_filters"
CATEGORY = "geompack/visualization"
def preview_mesh_vtk_filters(self, trimesh):
"""
Export mesh to STL and prepare for VTK.js preview with filters.
Args:
trimesh: Input trimesh_module.Trimesh object
Returns:
dict: UI data for frontend widget
"""
print(f"[PreviewMeshVTKFilters] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Check if mesh has vertex attributes (scalar fields)
has_vertex_attrs = hasattr(trimesh, 'vertex_attributes') and len(trimesh.vertex_attributes) > 0
print(f"[PreviewMeshVTKFilters] hasattr(trimesh, 'vertex_attributes'): {hasattr(trimesh, 'vertex_attributes')}")
if hasattr(trimesh, 'vertex_attributes'):
print(f"[PreviewMeshVTKFilters] trimesh.vertex_attributes: {trimesh.vertex_attributes.keys()}")
print(f"[PreviewMeshVTKFilters] Number of vertex attributes: {len(trimesh.vertex_attributes)}")
# Generate unique filename
# Use VTP format if we have vertex attributes, otherwise STL
if has_vertex_attrs:
filename = f"preview_vtk_filters_{uuid.uuid4().hex[:8]}.vtp"
file_format = 'vtp'
print(f"[PreviewMeshVTKFilters] Mesh has vertex attributes, using VTP format")
else:
filename = f"preview_vtk_filters_{uuid.uuid4().hex[:8]}.stl"
file_format = 'stl'
print(f"[PreviewMeshVTKFilters] No vertex attributes found, using STL format")
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export mesh with vertex attributes
try:
if file_format == 'vtp':
# Export to VTK PolyData format (XML) which preserves vertex attributes
_export_mesh_with_scalars_vtp(trimesh, filepath)
print(f"[PreviewMeshVTKFilters] Exported VTP with {len(trimesh.vertex_attributes)} scalar fields to: {filepath}")
else:
# Export to STL (no scalar data)
trimesh.export(filepath, file_type='stl')
print(f"[PreviewMeshVTKFilters] Exported to STL: {filepath}")
except Exception as e:
print(f"[PreviewMeshVTKFilters] Export failed: {e}")
# Fallback to OBJ
filename = filename.replace('.vtp', '.obj').replace('.stl', '.obj')
filepath = filepath.replace('.vtp', '.obj').replace('.stl', '.obj')
trimesh.export(filepath, file_type='obj')
print(f"[PreviewMeshVTKFilters] Exported to OBJ: {filepath}")
# Calculate bounding box info for camera setup
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Check if mesh is watertight
is_watertight = trimesh.is_watertight
# Calculate volume and area (only if watertight)
volume = None
area = None
try:
if is_watertight:
volume = float(trimesh.volume)
area = float(trimesh.area)
except Exception as e:
print(f"[PreviewMeshVTKFilters] Could not calculate volume/area: {e}")
# Get field names (vertex/face data arrays)
field_names = []
if hasattr(trimesh, 'vertex_attributes') and trimesh.vertex_attributes:
field_names.extend([f"vertex.{k}" for k in trimesh.vertex_attributes.keys()])
if hasattr(trimesh, 'face_attributes') and trimesh.face_attributes:
field_names.extend([f"face.{k}" for k in trimesh.face_attributes.keys()])
# Return metadata for frontend widget
ui_data = {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
"is_watertight": [bool(is_watertight)],
}
# Add optional fields if available
if volume is not None:
ui_data["volume"] = [volume]
if area is not None:
ui_data["area"] = [area]
if field_names:
ui_data["field_names"] = [field_names]
print(f"[PreviewMeshVTKFilters] Mesh info: watertight={is_watertight}, volume={volume}, area={area}, fields={len(field_names)}")
return {"ui": ui_data}
class PreviewMeshVTKFieldsNode:
"""
Preview mesh with VTK.js viewer optimized for scalar field visualization.
Specifically designed for visualizing vertex scalar fields (like boundary edges,
curvature, distance fields, etc.) with color mapping. Always exports as VTP
format to preserve scalar data.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_mesh_vtk_fields"
CATEGORY = "geompack/visualization"
def preview_mesh_vtk_fields(self, trimesh):
"""
Export mesh with scalar fields to VTP and prepare for VTK.js preview.
Args:
trimesh: Input trimesh_module.Trimesh object
Returns:
dict: UI data for frontend widget
"""
print(f"[PreviewMeshVTKFields] Preparing preview: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
# Check if mesh has vertex attributes (scalar fields)
has_vertex_attrs = hasattr(trimesh, 'vertex_attributes') and len(trimesh.vertex_attributes) > 0
if hasattr(trimesh, 'vertex_attributes'):
print(f"[PreviewMeshVTKFields] trimesh.vertex_attributes: {trimesh.vertex_attributes.keys()}")
print(f"[PreviewMeshVTKFields] Number of vertex attributes: {len(trimesh.vertex_attributes)}")
if not has_vertex_attrs:
print(f"[PreviewMeshVTKFields] WARNING: No vertex attributes found. This node is for scalar field visualization.")
# Always use VTP format for this node (designed for scalar fields)
filename = f"preview_vtk_fields_{uuid.uuid4().hex[:8]}.vtp"
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export mesh with vertex attributes as VTP
try:
_export_mesh_with_scalars_vtp(trimesh, filepath)
num_fields = len(trimesh.vertex_attributes) if has_vertex_attrs else 0
print(f"[PreviewMeshVTKFields] Exported VTP with {num_fields} scalar fields to: {filepath}")
except Exception as e:
print(f"[PreviewMeshVTKFields] Export failed: {e}")
import traceback
traceback.print_exc()
raise RuntimeError(f"Failed to export mesh with scalar fields: {e}")
# Calculate bounding box info for camera setup
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Check if mesh is watertight
is_watertight = trimesh.is_watertight
# Get field names (vertex/face data arrays)
field_names = []
if hasattr(trimesh, 'vertex_attributes') and trimesh.vertex_attributes:
field_names.extend(list(trimesh.vertex_attributes.keys()))
if hasattr(trimesh, 'face_attributes') and trimesh.face_attributes:
field_names.extend([f"face.{k}" for k in trimesh.face_attributes.keys()])
# Return metadata for frontend widget
ui_data = {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
"is_watertight": [bool(is_watertight)],
"field_names": [field_names],
}
print(f"[PreviewMeshVTKFields] Scalar fields: {field_names}")
return {"ui": ui_data}
class PreviewBoundingBoxesVTKNode:
"""
Preview mesh with bounding boxes using VTK.js viewer.
Displays the original mesh with wireframe bounding boxes overlaid,
showing the segmentation results from P3-SAM or other bbox-generating nodes.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"trimesh": ("TRIMESH",),
"bboxes": ("BBOXES_3D",),
"line_width": ("FLOAT", {
"default": 2.0,
"min": 0.5,
"max": 10.0,
"step": 0.5,
"display": "slider",
"tooltip": "Thickness of bounding box wireframe lines in pixels. Rendered by VTK.js."
}),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview_bboxes_vtk"
CATEGORY = "geompack/visualization"
def preview_bboxes_vtk(self, trimesh, bboxes, line_width):
"""
Export mesh with bounding boxes to VTP and prepare for VTK.js preview.
Args:
trimesh: Input trimesh_module.Trimesh object
bboxes: BBOXES_3D dict with 'bboxes' (array [N, 2, 3]) and 'num_parts'
line_width: Thickness of wireframe lines in pixels (for VTK.js rendering)
Returns:
dict: UI data for frontend widget
"""
if not BBOX_VIZ_AVAILABLE:
raise RuntimeError(
"create_bbox_visualization not available. "
"Make sure ComfyUI-Hunyuan3D-Part is installed."
)
# Extract bboxes array from BBOXES_3D dictionary
if isinstance(bboxes, dict):
bboxes_array = bboxes['bboxes']
num_parts = bboxes.get('num_parts', len(bboxes_array))
else:
# Fallback for raw array
bboxes_array = bboxes
num_parts = len(bboxes_array)
print(f"[PreviewBoundingBoxesVTK] Preparing preview with {num_parts} bounding boxes")
print(f"[PreviewBoundingBoxesVTK] Mesh: {len(trimesh.vertices)} vertices, {len(trimesh.faces)} faces")
print(f"[PreviewBoundingBoxesVTK] Line width: {line_width}px")
# Create scene with mesh and bounding boxes
# Use Path3D wireframes (rendered as lines in VTK.js)
scene = create_bbox_visualization(trimesh, bboxes_array, use_tubes=False)
# Generate unique filename - use VTP which supports wireframes
filename = f"preview_bboxes_vtk_{uuid.uuid4().hex[:8]}.vtp"
# Use ComfyUI's output directory
if COMFYUI_OUTPUT_FOLDER:
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
else:
filepath = os.path.join(tempfile.gettempdir(), filename)
# Export scene to VTP (preserves wireframes)
try:
# Use custom VTP exporter that preserves both mesh and wireframe lines
export_scene_to_vtp(scene, filepath)
print(f"[PreviewBoundingBoxesVTK] Exported scene with {num_parts} bounding boxes to: {filepath}")
except Exception as e:
print(f"[PreviewBoundingBoxesVTK] VTP export failed: {e}")
import traceback
traceback.print_exc()
# Fallback to STL with tubes
print(f"[PreviewBoundingBoxesVTK] Falling back to STL with tube meshes")
scene = create_bbox_visualization(trimesh, bboxes_array, use_tubes=True)
filename = filename.replace('.vtp', '.stl')
filepath = filepath.replace('.vtp', '.stl')
scene.export(filepath, file_type='stl')
print(f"[PreviewBoundingBoxesVTK] Exported to: {filepath}")
# Calculate bounding box info for camera setup
# Use the original mesh bounds for camera positioning
bounds = trimesh.bounds
extents = trimesh.extents
max_extent = max(extents)
# Return metadata for frontend widget
ui_data = {
"mesh_file": [filename],
"vertex_count": [len(trimesh.vertices)],
"face_count": [len(trimesh.faces)],
"bounds_min": [bounds[0].tolist()],
"bounds_max": [bounds[1].tolist()],
"extents": [extents.tolist()],
"max_extent": [float(max_extent)],
"num_bboxes": [num_parts],
"line_width": [float(line_width)],
}
print(f"[PreviewBoundingBoxesVTK] Created visualization with {num_parts} bounding boxes")
return {"ui": ui_data}
# Node mappings
NODE_CLASS_MAPPINGS = {
"GeomPackPreviewMesh": PreviewMeshNode,
"GeomPackPreviewMeshVTK": PreviewMeshVTKNode,
"GeomPackPreviewMeshVTKHidableMenu": PreviewMeshVTKHidableMenuNode,
"GeomPackPreviewMeshVTKFilters": PreviewMeshVTKFiltersNode,
"GeomPackPreviewMeshVTKFields": PreviewMeshVTKFieldsNode,
"GeomPackPreviewBoundingBoxesVTK": PreviewBoundingBoxesVTKNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"GeomPackPreviewMesh": "Preview Mesh (3D)",
"GeomPackPreviewMeshVTK": "Preview Mesh (VTK)",
"GeomPackPreviewMeshVTKHidableMenu": "Preview Mesh VTK (Hidable Menu)",
"GeomPackPreviewMeshVTKFilters": "Preview Mesh (VTK with Filters)",
"GeomPackPreviewMeshVTKFields": "Preview Mesh (VTK with Fields)",
"GeomPackPreviewBoundingBoxesVTK": "Preview Bounding Boxes (VTK)",
}