textured unified
This commit is contained in:
@@ -46,6 +46,22 @@ def has_fields(mesh):
|
||||
return has_vertex_attrs or has_face_attrs
|
||||
|
||||
|
||||
def get_texture_info(mesh):
|
||||
"""Extract texture/visual information from a mesh."""
|
||||
has_visual = hasattr(mesh, 'visual') and mesh.visual is not None
|
||||
visual_kind = mesh.visual.kind if has_visual else None
|
||||
has_texture = visual_kind == 'texture' and hasattr(mesh.visual, 'material') if has_visual else False
|
||||
has_vertex_colors = visual_kind == 'vertex' if has_visual else False
|
||||
has_material = has_texture
|
||||
return {
|
||||
'has_visual': has_visual,
|
||||
'visual_kind': visual_kind,
|
||||
'has_texture': has_texture,
|
||||
'has_vertex_colors': has_vertex_colors,
|
||||
'has_material': has_material
|
||||
}
|
||||
|
||||
|
||||
class PreviewMeshDualNode:
|
||||
"""
|
||||
Unified dual mesh preview with VTK.js - supports both side-by-side and overlay layouts.
|
||||
@@ -63,6 +79,7 @@ class PreviewMeshDualNode:
|
||||
},
|
||||
"optional": {
|
||||
"layout": (["side_by_side", "overlay"], {"default": "side_by_side"}),
|
||||
"mode": (["fields", "texture"], {"default": "fields"}),
|
||||
"opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
@@ -72,20 +89,21 @@ class PreviewMeshDualNode:
|
||||
FUNCTION = "preview_dual"
|
||||
CATEGORY = "geompack/visualization"
|
||||
|
||||
def preview_dual(self, mesh_1, mesh_2, layout="side_by_side", opacity=1.0):
|
||||
def preview_dual(self, mesh_1, mesh_2, layout="side_by_side", mode="fields", opacity=1.0):
|
||||
"""
|
||||
Preview two meshes with chosen layout and field visualization.
|
||||
Preview two meshes with chosen layout and visualization mode.
|
||||
|
||||
Args:
|
||||
mesh_1: First trimesh object
|
||||
mesh_2: Second trimesh object
|
||||
layout: "side_by_side" or "overlay"
|
||||
mode: "fields" (scientific visualization) or "texture" (textured rendering)
|
||||
opacity: Opacity for both meshes (0.0-1.0)
|
||||
|
||||
Returns:
|
||||
dict: UI data for frontend widget
|
||||
"""
|
||||
print(f"[PreviewMeshDual] Layout: {layout}")
|
||||
print(f"[PreviewMeshDual] Layout: {layout}, Mode: {mode}")
|
||||
print(f"[PreviewMeshDual] Mesh 1: {get_geometry_type(mesh_1)} - {len(mesh_1.vertices)} vertices, {get_face_count(mesh_1)} faces")
|
||||
print(f"[PreviewMeshDual] Mesh 2: {get_geometry_type(mesh_2)} - {len(mesh_2.vertices)} vertices, {get_face_count(mesh_2)} faces")
|
||||
|
||||
@@ -100,6 +118,13 @@ class PreviewMeshDualNode:
|
||||
print(f"[PreviewMeshDual] Mesh 2 fields: {field_names_2}")
|
||||
print(f"[PreviewMeshDual] Common fields: {common_fields}")
|
||||
|
||||
# Check for texture/visual data
|
||||
texture_info_1 = get_texture_info(mesh_1)
|
||||
texture_info_2 = get_texture_info(mesh_2)
|
||||
|
||||
print(f"[PreviewMeshDual] Mesh 1 visual: kind={texture_info_1['visual_kind']}, texture={texture_info_1['has_texture']}, vertex_colors={texture_info_1['has_vertex_colors']}")
|
||||
print(f"[PreviewMeshDual] Mesh 2 visual: kind={texture_info_2['visual_kind']}, texture={texture_info_2['has_texture']}, vertex_colors={texture_info_2['has_vertex_colors']}")
|
||||
|
||||
# Check if meshes are point clouds (need VTP, STL doesn't support point clouds)
|
||||
mesh_1_is_pc = is_point_cloud(mesh_1)
|
||||
mesh_2_is_pc = is_point_cloud(mesh_2)
|
||||
@@ -108,13 +133,20 @@ class PreviewMeshDualNode:
|
||||
preview_id = uuid.uuid4().hex[:8]
|
||||
|
||||
if layout == "side_by_side":
|
||||
# Export meshes separately - use VTP for fields OR point clouds
|
||||
filename_1, filepath_1 = self._export_mesh(mesh_1, f"preview_dual_1_{preview_id}", mesh_1_has_fields or mesh_1_is_pc)
|
||||
filename_2, filepath_2 = self._export_mesh(mesh_2, f"preview_dual_2_{preview_id}", mesh_2_has_fields or mesh_2_is_pc)
|
||||
# Export meshes separately based on mode
|
||||
if mode == "texture":
|
||||
# Texture mode: export as GLB
|
||||
filename_1, filepath_1 = self._export_mesh(mesh_1, f"preview_dual_1_{preview_id}", use_vtp=False, use_glb=True)
|
||||
filename_2, filepath_2 = self._export_mesh(mesh_2, f"preview_dual_2_{preview_id}", use_vtp=False, use_glb=True)
|
||||
else:
|
||||
# Fields mode: use VTP for fields OR point clouds
|
||||
filename_1, filepath_1 = self._export_mesh(mesh_1, f"preview_dual_1_{preview_id}", use_vtp=(mesh_1_has_fields or mesh_1_is_pc), use_glb=False)
|
||||
filename_2, filepath_2 = self._export_mesh(mesh_2, f"preview_dual_2_{preview_id}", use_vtp=(mesh_2_has_fields or mesh_2_is_pc), use_glb=False)
|
||||
|
||||
# Build UI data for side-by-side mode
|
||||
ui_data = {
|
||||
"layout": [layout],
|
||||
"mode": [mode],
|
||||
"mesh_1_file": [filename_1],
|
||||
"mesh_2_file": [filename_2],
|
||||
"vertex_count_1": [len(mesh_1.vertices)],
|
||||
@@ -129,17 +161,44 @@ class PreviewMeshDualNode:
|
||||
"extents_2": [mesh_2.extents.tolist()],
|
||||
"is_watertight_1": [bool(mesh_1.is_watertight) if not is_point_cloud(mesh_1) else False],
|
||||
"is_watertight_2": [bool(mesh_2.is_watertight) if not is_point_cloud(mesh_2) else False],
|
||||
"field_names_1": [field_names_1],
|
||||
"field_names_2": [field_names_2],
|
||||
"common_fields": [common_fields],
|
||||
}
|
||||
|
||||
# Add mode-specific metadata
|
||||
if mode == "texture":
|
||||
# Texture mode metadata
|
||||
ui_data.update({
|
||||
"has_texture_1": [texture_info_1['has_texture']],
|
||||
"has_texture_2": [texture_info_2['has_texture']],
|
||||
"visual_kind_1": [texture_info_1['visual_kind'] if texture_info_1['visual_kind'] else "none"],
|
||||
"visual_kind_2": [texture_info_2['visual_kind'] if texture_info_2['visual_kind'] else "none"],
|
||||
"has_vertex_colors_1": [texture_info_1['has_vertex_colors']],
|
||||
"has_vertex_colors_2": [texture_info_2['has_vertex_colors']],
|
||||
"has_material_1": [texture_info_1['has_material']],
|
||||
"has_material_2": [texture_info_2['has_material']],
|
||||
})
|
||||
else:
|
||||
# Fields mode metadata
|
||||
ui_data.update({
|
||||
"field_names_1": [field_names_1],
|
||||
"field_names_2": [field_names_2],
|
||||
"common_fields": [common_fields],
|
||||
})
|
||||
|
||||
|
||||
else: # overlay
|
||||
# Combine meshes with color coding
|
||||
filename, filepath = self._export_combined_mesh(
|
||||
mesh_1, mesh_2, preview_id, opacity,
|
||||
mesh_1_has_fields, mesh_2_has_fields
|
||||
)
|
||||
if mode == "texture":
|
||||
# Texture mode: export combined mesh as GLB
|
||||
filename, filepath = self._export_combined_mesh(
|
||||
mesh_1, mesh_2, preview_id, opacity,
|
||||
mesh_1_has_fields, mesh_2_has_fields, use_glb=True
|
||||
)
|
||||
else:
|
||||
# Fields mode: export combined mesh as VTP
|
||||
filename, filepath = self._export_combined_mesh(
|
||||
mesh_1, mesh_2, preview_id, opacity,
|
||||
mesh_1_has_fields, mesh_2_has_fields, use_glb=False
|
||||
)
|
||||
|
||||
# Calculate combined bounds
|
||||
combined_bounds_min = np.minimum(mesh_1.bounds[0], mesh_2.bounds[0])
|
||||
@@ -149,6 +208,7 @@ class PreviewMeshDualNode:
|
||||
# Build UI data for overlay mode
|
||||
ui_data = {
|
||||
"layout": [layout],
|
||||
"mode": [mode],
|
||||
"mesh_file": [filename],
|
||||
"vertex_count_1": [len(mesh_1.vertices)],
|
||||
"vertex_count_2": [len(mesh_2.vertices)],
|
||||
@@ -160,17 +220,37 @@ class PreviewMeshDualNode:
|
||||
"opacity": [float(opacity)],
|
||||
"is_watertight_1": [bool(mesh_1.is_watertight) if not is_point_cloud(mesh_1) else False],
|
||||
"is_watertight_2": [bool(mesh_2.is_watertight) if not is_point_cloud(mesh_2) else False],
|
||||
"field_names_1": [field_names_1],
|
||||
"field_names_2": [field_names_2],
|
||||
"common_fields": [common_fields],
|
||||
}
|
||||
|
||||
# Add mode-specific metadata
|
||||
if mode == "texture":
|
||||
# Texture mode metadata
|
||||
ui_data.update({
|
||||
"has_texture_1": [texture_info_1['has_texture']],
|
||||
"has_texture_2": [texture_info_2['has_texture']],
|
||||
"visual_kind_1": [texture_info_1['visual_kind'] if texture_info_1['visual_kind'] else "none"],
|
||||
"visual_kind_2": [texture_info_2['visual_kind'] if texture_info_2['visual_kind'] else "none"],
|
||||
"has_vertex_colors_1": [texture_info_1['has_vertex_colors']],
|
||||
"has_vertex_colors_2": [texture_info_2['has_vertex_colors']],
|
||||
"has_material_1": [texture_info_1['has_material']],
|
||||
"has_material_2": [texture_info_2['has_material']],
|
||||
})
|
||||
else:
|
||||
# Fields mode metadata
|
||||
ui_data.update({
|
||||
"field_names_1": [field_names_1],
|
||||
"field_names_2": [field_names_2],
|
||||
"common_fields": [common_fields],
|
||||
})
|
||||
|
||||
print(f"[PreviewMeshDual] Preview ready")
|
||||
return {"ui": ui_data}
|
||||
|
||||
def _export_mesh(self, mesh, base_filename, use_vtp):
|
||||
def _export_mesh(self, mesh, base_filename, use_vtp, use_glb):
|
||||
"""Export a single mesh to appropriate format."""
|
||||
if use_vtp:
|
||||
if use_glb:
|
||||
filename = f"{base_filename}.glb"
|
||||
elif use_vtp:
|
||||
filename = f"{base_filename}.vtp"
|
||||
else:
|
||||
filename = f"{base_filename}.stl"
|
||||
@@ -181,7 +261,10 @@ class PreviewMeshDualNode:
|
||||
filepath = os.path.join(tempfile.gettempdir(), filename)
|
||||
|
||||
try:
|
||||
if use_vtp:
|
||||
if use_glb:
|
||||
mesh.export(filepath, file_type='glb', include_normals=True)
|
||||
print(f"[PreviewMeshDual] Exported GLB: {filepath}")
|
||||
elif use_vtp:
|
||||
export_mesh_with_scalars_vtp(mesh, filepath)
|
||||
print(f"[PreviewMeshDual] Exported VTP with fields: {filepath}")
|
||||
else:
|
||||
@@ -190,29 +273,38 @@ class PreviewMeshDualNode:
|
||||
except Exception as e:
|
||||
print(f"[PreviewMeshDual] Export failed: {e}, trying fallback")
|
||||
# Fallback to OBJ
|
||||
filename = filename.replace('.vtp', '.obj').replace('.stl', '.obj')
|
||||
filepath = filepath.replace('.vtp', '.obj').replace('.stl', '.obj')
|
||||
filename = filename.replace('.vtp', '.obj').replace('.stl', '.obj').replace('.glb', '.obj')
|
||||
filepath = filepath.replace('.vtp', '.obj').replace('.stl', '.obj').replace('.glb', '.obj')
|
||||
mesh.export(filepath, file_type='obj')
|
||||
print(f"[PreviewMeshDual] Exported OBJ fallback: {filepath}")
|
||||
|
||||
return filename, filepath
|
||||
|
||||
def _export_combined_mesh(self, mesh_1, mesh_2, preview_id, opacity,
|
||||
mesh_1_has_fields, mesh_2_has_fields):
|
||||
"""Export combined mesh for overlay mode as VTP."""
|
||||
mesh_1_has_fields, mesh_2_has_fields, use_glb):
|
||||
"""Export combined mesh for overlay mode as VTP or GLB."""
|
||||
|
||||
# Combine meshes (with or without fields)
|
||||
try:
|
||||
combined = trimesh_module.util.concatenate([mesh_1, mesh_2])
|
||||
filename = f"preview_dual_overlay_{preview_id}.vtp"
|
||||
|
||||
if use_glb:
|
||||
filename = f"preview_dual_overlay_{preview_id}.glb"
|
||||
else:
|
||||
filename = f"preview_dual_overlay_{preview_id}.vtp"
|
||||
|
||||
if COMFYUI_OUTPUT_FOLDER:
|
||||
filepath = os.path.join(COMFYUI_OUTPUT_FOLDER, filename)
|
||||
else:
|
||||
filepath = os.path.join(tempfile.gettempdir(), filename)
|
||||
|
||||
export_mesh_with_scalars_vtp(combined, filepath)
|
||||
print(f"[PreviewMeshDual] Exported combined VTP: {filepath}")
|
||||
if use_glb:
|
||||
combined.export(filepath, file_type='glb', include_normals=True)
|
||||
print(f"[PreviewMeshDual] Exported combined GLB: {filepath}")
|
||||
else:
|
||||
export_mesh_with_scalars_vtp(combined, filepath)
|
||||
print(f"[PreviewMeshDual] Exported combined VTP: {filepath}")
|
||||
|
||||
print(f"[PreviewMeshDual] Combined {get_geometry_type(combined)}: {len(combined.vertices)} vertices, {get_face_count(combined)} faces")
|
||||
return filename, filepath
|
||||
except Exception as e:
|
||||
|
||||
@@ -39,6 +39,7 @@ class PreviewMeshVTKNode:
|
||||
return {
|
||||
"required": {
|
||||
"trimesh": ("TRIMESH",),
|
||||
"mode": (["fields", "texture"], {"default": "fields"}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,15 +48,17 @@ class PreviewMeshVTKNode:
|
||||
FUNCTION = "preview_mesh_vtk"
|
||||
CATEGORY = "geompack/visualization"
|
||||
|
||||
def preview_mesh_vtk(self, trimesh):
|
||||
def preview_mesh_vtk(self, trimesh, mode="fields"):
|
||||
"""
|
||||
Export mesh and prepare for VTK.js preview.
|
||||
|
||||
Automatically detects scalar fields (vertex/face attributes) and exports
|
||||
to VTP format if present, otherwise exports to STL.
|
||||
Supports two visualization modes:
|
||||
- fields: Scientific visualization with scalar fields and colormaps (VTP/STL export)
|
||||
- texture: Textured mesh visualization with materials (GLB export)
|
||||
|
||||
Args:
|
||||
trimesh: Input trimesh_module.Trimesh object
|
||||
mode: Visualization mode - "fields" or "texture"
|
||||
|
||||
Returns:
|
||||
dict: UI data for frontend widget
|
||||
@@ -79,17 +82,36 @@ class PreviewMeshVTKNode:
|
||||
print(f"[PreviewMeshVTK] DEBUG - has_face_attrs: {has_face_attrs}")
|
||||
print(f"[PreviewMeshVTK] DEBUG - has_fields: {has_fields}")
|
||||
|
||||
# Check for visual data (textures/vertex colors)
|
||||
has_visual = hasattr(trimesh, 'visual') and trimesh.visual is not None
|
||||
visual_kind = trimesh.visual.kind if has_visual else None
|
||||
has_texture = visual_kind == 'texture' and hasattr(trimesh.visual, 'material') if has_visual else False
|
||||
has_vertex_colors = visual_kind == 'vertex' if has_visual else False
|
||||
has_material = has_texture
|
||||
|
||||
print(f"[PreviewMeshVTK] Mode: {mode}")
|
||||
print(f"[PreviewMeshVTK] Visual data - has_visual: {has_visual}, kind: {visual_kind}, texture: {has_texture}, vertex_colors: {has_vertex_colors}")
|
||||
|
||||
# Check if this is a point cloud
|
||||
is_pc = is_point_cloud(trimesh)
|
||||
|
||||
# Choose export format based on whether fields exist or if it's a point cloud
|
||||
if has_fields or is_pc:
|
||||
# Export to VTP for: scalar fields OR point clouds (STL doesn't support point clouds)
|
||||
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.vtp"
|
||||
print(f"[PreviewMeshVTK] Using VTP format (fields={has_fields}, point_cloud={is_pc})")
|
||||
# Choose export format based on visualization mode
|
||||
if mode == "texture":
|
||||
# Texture mode: Export GLB to preserve textures/materials/UVs
|
||||
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.glb"
|
||||
viewer_type = "texture"
|
||||
print(f"[PreviewMeshVTK] Using texture mode - GLB export")
|
||||
else:
|
||||
# Export to STL (compact format for simple surface meshes)
|
||||
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.stl"
|
||||
# Fields mode: Export VTP/STL for scalar field visualization
|
||||
if has_fields or is_pc:
|
||||
# Export to VTP for: scalar fields OR point clouds (STL doesn't support point clouds)
|
||||
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.vtp"
|
||||
print(f"[PreviewMeshVTK] Using VTP format (fields={has_fields}, point_cloud={is_pc})")
|
||||
else:
|
||||
# Export to STL (compact format for simple surface meshes)
|
||||
filename = f"preview_vtk_{uuid.uuid4().hex[:8]}.stl"
|
||||
viewer_type = "fields"
|
||||
print(f"[PreviewMeshVTK] Using fields mode - VTP/STL export")
|
||||
|
||||
# Use ComfyUI's output directory
|
||||
if COMFYUI_OUTPUT_FOLDER:
|
||||
@@ -99,7 +121,11 @@ class PreviewMeshVTKNode:
|
||||
|
||||
# Export mesh
|
||||
try:
|
||||
if has_fields or is_pc:
|
||||
if mode == "texture":
|
||||
# Export GLB for texture rendering
|
||||
trimesh.export(filepath, file_type='glb', include_normals=True)
|
||||
print(f"[PreviewMeshVTK] Exported GLB to: {filepath}")
|
||||
elif has_fields or is_pc:
|
||||
# Use VTP exporter for fields or point clouds
|
||||
export_mesh_with_scalars_vtp(trimesh, filepath)
|
||||
print(f"[PreviewMeshVTK] Exported VTP to: {filepath}")
|
||||
@@ -146,6 +172,8 @@ class PreviewMeshVTKNode:
|
||||
# Return metadata for frontend widget
|
||||
ui_data = {
|
||||
"mesh_file": [filename],
|
||||
"viewer_type": [viewer_type], # "fields" or "texture" - tells frontend which viewer to load
|
||||
"mode": [mode], # User-selected mode
|
||||
"vertex_count": [len(trimesh.vertices)],
|
||||
"face_count": [get_face_count(trimesh)],
|
||||
"bounds_min": [bounds[0].tolist()],
|
||||
@@ -153,19 +181,33 @@ class PreviewMeshVTKNode:
|
||||
"extents": [extents.tolist()],
|
||||
"max_extent": [float(max_extent)],
|
||||
"is_watertight": [bool(is_watertight)],
|
||||
"field_names": [field_names], # Always include (empty array if no fields)
|
||||
}
|
||||
|
||||
# Add mode-specific metadata
|
||||
if viewer_type == "texture":
|
||||
# Texture mode metadata
|
||||
ui_data.update({
|
||||
"has_texture": [has_texture],
|
||||
"has_vertex_colors": [has_vertex_colors],
|
||||
"has_material": [has_material],
|
||||
"visual_kind": [visual_kind if visual_kind else "none"],
|
||||
})
|
||||
else:
|
||||
# Fields mode metadata
|
||||
ui_data["field_names"] = [field_names] # Field visualization data
|
||||
|
||||
# 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:
|
||||
print(f"[PreviewMeshVTK] Mesh info: watertight={is_watertight}, volume={volume}, area={area}, fields={field_names}")
|
||||
if viewer_type == "texture":
|
||||
print(f"[PreviewMeshVTK] Texture mode info: watertight={is_watertight}, volume={volume}, area={area}, texture={has_texture}, vertex_colors={has_vertex_colors}")
|
||||
elif field_names:
|
||||
print(f"[PreviewMeshVTK] Fields mode info: watertight={is_watertight}, volume={volume}, area={area}, fields={field_names}")
|
||||
else:
|
||||
print(f"[PreviewMeshVTK] Mesh info: watertight={is_watertight}, volume={volume}, area={area}, no fields")
|
||||
print(f"[PreviewMeshVTK] Fields mode info: watertight={is_watertight}, volume={volume}, area={area}, no fields")
|
||||
|
||||
return {"ui": ui_data}
|
||||
|
||||
|
||||
+90
-18
@@ -39,8 +39,12 @@ app.registerExtension({
|
||||
console.log('[GeomPack Dual] Created iframe with minHeight:', iframe.style.minHeight);
|
||||
|
||||
// Point to unified dual VTK.js HTML viewer (with cache buster)
|
||||
// Note: viewer will be dynamically switched based on mode in onExecuted
|
||||
iframe.src = "/extensions/ComfyUI-GeometryPack/viewer_dual.html?v=" + Date.now();
|
||||
|
||||
// Track current viewer type to avoid unnecessary reloads
|
||||
let currentViewerType = "fields";
|
||||
|
||||
// Create mesh info panel
|
||||
const infoPanel = document.createElement("div");
|
||||
infoPanel.style.backgroundColor = "#1a1a1a";
|
||||
@@ -126,6 +130,14 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const layout = message.layout[0];
|
||||
const mode = message.mode?.[0] || "fields";
|
||||
console.log(`[GeomPack Dual] onExecuted: layout=${layout}, mode=${mode}`);
|
||||
|
||||
// Determine which viewer to use based on mode
|
||||
const viewerType = mode === "texture" ? "texture" : "fields";
|
||||
const viewerUrl = viewerType === "texture"
|
||||
? "/extensions/ComfyUI-GeometryPack/viewer_dual_textured.html"
|
||||
: "/extensions/ComfyUI-GeometryPack/viewer_dual.html";
|
||||
|
||||
let infoHTML = '';
|
||||
let postMessageData = {
|
||||
@@ -157,8 +169,15 @@ app.registerExtension({
|
||||
const extentsStr2 = extents2.length === 3 ?
|
||||
`${extents2.map(v => v.toFixed(2)).join(' × ')}` : 'N/A';
|
||||
|
||||
// Build info panel with mode info
|
||||
const modeLabel = mode.charAt(0).toUpperCase() + mode.slice(1);
|
||||
const modeColor = mode === "texture" ? '#c8c' : '#6cc';
|
||||
|
||||
infoHTML = `
|
||||
<div style="display: grid; grid-template-columns: auto 1fr 1fr; gap: 2px 12px;">
|
||||
<span style="color: #888;">Mode:</span>
|
||||
<span colspan="2" style="grid-column: 2 / 4; color: ${modeColor}; font-weight: bold;">${modeLabel}</span>
|
||||
|
||||
<span style="color: #888;"></span>
|
||||
<span style="color: #999; font-weight: bold; border-bottom: 1px solid #333;">Mesh 1</span>
|
||||
<span style="color: #999; font-weight: bold; border-bottom: 1px solid #333;">Mesh 2</span>
|
||||
@@ -189,13 +208,27 @@ app.registerExtension({
|
||||
`;
|
||||
}
|
||||
|
||||
// Add field info if available
|
||||
if (message.common_fields && message.common_fields[0].length > 0) {
|
||||
const commonFields = message.common_fields[0];
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span colspan="2" style="grid-column: 2 / 4; color: #9c9;">${commonFields.length} shared field(s)</span>
|
||||
`;
|
||||
// Add mode-specific metadata
|
||||
if (mode === "texture") {
|
||||
// Texture mode info
|
||||
if (message.has_texture_1 !== undefined) {
|
||||
const tex1 = message.has_texture_1[0] ? 'Yes' : 'No';
|
||||
const tex2 = message.has_texture_2[0] ? 'Yes' : 'No';
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Textures:</span>
|
||||
<span style="color: ${message.has_texture_1[0] ? '#c8c' : '#888'};">${tex1}</span>
|
||||
<span style="color: ${message.has_texture_2[0] ? '#c8c' : '#888'};">${tex2}</span>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Fields mode info
|
||||
if (message.common_fields && message.common_fields[0].length > 0) {
|
||||
const commonFields = message.common_fields[0];
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span colspan="2" style="grid-column: 2 / 4; color: #9c9;">${commonFields.length} shared field(s)</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
infoHTML += '</div>';
|
||||
@@ -221,8 +254,15 @@ app.registerExtension({
|
||||
const faces1 = message.face_count_1?.[0] || 'N/A';
|
||||
const faces2 = message.face_count_2?.[0] || 'N/A';
|
||||
|
||||
// Build info panel with mode info
|
||||
const modeLabel = mode.charAt(0).toUpperCase() + mode.slice(1);
|
||||
const modeColor = mode === "texture" ? '#c8c' : '#6cc';
|
||||
|
||||
infoHTML = `
|
||||
<div style="display: grid; grid-template-columns: auto 1fr 1fr; gap: 2px 12px;">
|
||||
<span style="color: #888;">Mode:</span>
|
||||
<span colspan="2" style="grid-column: 2 / 4; color: ${modeColor}; font-weight: bold;">${modeLabel} (Overlay)</span>
|
||||
|
||||
<span style="color: #888;"></span>
|
||||
<span style="color: #999; font-weight: bold; border-bottom: 1px solid #333;">Mesh 1</span>
|
||||
<span style="color: #999; font-weight: bold; border-bottom: 1px solid #333;">Mesh 2</span>
|
||||
@@ -236,13 +276,27 @@ app.registerExtension({
|
||||
<span>${faces2.toLocaleString()}</span>
|
||||
`;
|
||||
|
||||
// Add color info if using vertex coloring
|
||||
if (message.mesh_1_color && message.mesh_2_color) {
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Colors:</span>
|
||||
<span>${message.mesh_1_color[0]}</span>
|
||||
<span>${message.mesh_2_color[0]}</span>
|
||||
`;
|
||||
// Add mode-specific metadata
|
||||
if (mode === "texture") {
|
||||
// Texture mode info
|
||||
if (message.has_texture_1 !== undefined) {
|
||||
const tex1 = message.has_texture_1[0] ? 'Yes' : 'No';
|
||||
const tex2 = message.has_texture_2[0] ? 'Yes' : 'No';
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Textures:</span>
|
||||
<span style="color: ${message.has_texture_1[0] ? '#c8c' : '#888'};">${tex1}</span>
|
||||
<span style="color: ${message.has_texture_2[0] ? '#c8c' : '#888'};">${tex2}</span>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Fields mode info
|
||||
if (message.common_fields && message.common_fields[0].length > 0) {
|
||||
const commonFields = message.common_fields[0];
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span colspan="2" style="grid-column: 2 / 4; color: #9c9;">${commonFields.length} shared field(s)</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
infoHTML += '</div>';
|
||||
@@ -265,11 +319,29 @@ app.registerExtension({
|
||||
}
|
||||
};
|
||||
|
||||
// Send message after iframe is loaded
|
||||
if (iframeLoaded) {
|
||||
sendMessage();
|
||||
// Reload iframe if viewer type changed
|
||||
if (viewerType !== currentViewerType) {
|
||||
console.log(`[GeomPack Dual] Switching viewer from ${currentViewerType} to ${viewerType}`);
|
||||
currentViewerType = viewerType;
|
||||
iframeLoaded = false;
|
||||
|
||||
// Set up one-time load listener before changing src
|
||||
const onViewerLoaded = () => {
|
||||
console.log("[GeomPack Dual] New viewer loaded, sending mesh");
|
||||
iframeLoaded = true;
|
||||
sendMessage();
|
||||
};
|
||||
iframe.addEventListener('load', onViewerLoaded, { once: true });
|
||||
|
||||
// Change iframe src to trigger reload
|
||||
iframe.src = viewerUrl + "?v=" + Date.now();
|
||||
} else {
|
||||
setTimeout(sendMessage, 500);
|
||||
// No viewer change needed, send message immediately or after short delay
|
||||
if (iframeLoaded) {
|
||||
sendMessage();
|
||||
} else {
|
||||
setTimeout(sendMessage, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+82
-14
@@ -40,8 +40,12 @@ app.registerExtension({
|
||||
iframe.style.backgroundColor = "#2a2a2a";
|
||||
|
||||
// Point to VTK.js HTML viewer (with cache buster)
|
||||
// Note: viewer will be dynamically switched based on mode in onExecuted
|
||||
iframe.src = "/extensions/ComfyUI-GeometryPack/viewer_vtk.html?v=" + Date.now();
|
||||
|
||||
// Track current viewer type to avoid unnecessary reloads
|
||||
let currentViewerType = "fields";
|
||||
|
||||
// Create mesh info panel
|
||||
const infoPanel = document.createElement("div");
|
||||
infoPanel.style.backgroundColor = "#1a1a1a";
|
||||
@@ -155,7 +159,14 @@ app.registerExtension({
|
||||
// The message IS the UI data (not message.ui)
|
||||
if (message?.mesh_file && message.mesh_file[0]) {
|
||||
const filename = message.mesh_file[0];
|
||||
console.log(`[GeomPack VTK] Loading mesh: ${filename}`);
|
||||
const viewerType = message.viewer_type?.[0] || "fields";
|
||||
const mode = message.mode?.[0] || "fields";
|
||||
console.log(`[GeomPack VTK] Loading mesh: ${filename}, viewer type: ${viewerType}, mode: ${mode}`);
|
||||
|
||||
// Determine which viewer HTML to use
|
||||
const viewerUrl = viewerType === "texture"
|
||||
? "/extensions/ComfyUI-GeometryPack/viewer_vtk_textured.html"
|
||||
: "/extensions/ComfyUI-GeometryPack/viewer_vtk.html";
|
||||
|
||||
// Update mesh info panel with metadata
|
||||
const vertices = message.vertex_count?.[0] || 'N/A';
|
||||
@@ -177,8 +188,14 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
// Build info HTML
|
||||
const modeLabel = mode.charAt(0).toUpperCase() + mode.slice(1);
|
||||
const modeColor = mode === "texture" ? '#c8c' : '#6cc';
|
||||
|
||||
let infoHTML = `
|
||||
<div style="display: grid; grid-template-columns: auto 1fr; gap: 2px 8px;">
|
||||
<span style="color: #888;">Mode:</span>
|
||||
<span style="color: ${modeColor}; font-weight: bold;">${modeLabel}</span>
|
||||
|
||||
<span style="color: #888;">Vertices:</span>
|
||||
<span>${vertices.toLocaleString()}</span>
|
||||
|
||||
@@ -192,7 +209,7 @@ app.registerExtension({
|
||||
<span>${extentsStr}</span>
|
||||
`;
|
||||
|
||||
// Add optional fields if available
|
||||
// Add watertight status (always shown)
|
||||
if (message.is_watertight !== undefined) {
|
||||
const watertight = message.is_watertight[0] ? 'Yes' : 'No';
|
||||
const color = message.is_watertight[0] ? '#6c6' : '#c66';
|
||||
@@ -202,12 +219,45 @@ app.registerExtension({
|
||||
`;
|
||||
}
|
||||
|
||||
if (message.field_names && message.field_names[0]?.length > 0) {
|
||||
const fields = message.field_names[0].join(', ');
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span style="font-size: 9px;">${fields}</span>
|
||||
`;
|
||||
// Add mode-specific info
|
||||
if (viewerType === "texture") {
|
||||
// Texture mode info
|
||||
if (message.visual_kind !== undefined) {
|
||||
const visualKind = message.visual_kind[0] || 'none';
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Visual Kind:</span>
|
||||
<span>${visualKind}</span>
|
||||
`;
|
||||
}
|
||||
if (message.has_texture !== undefined) {
|
||||
const hasTexture = message.has_texture[0] ? 'Yes' : 'No';
|
||||
const texColor = message.has_texture[0] ? '#c8c' : '#888';
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Textures:</span>
|
||||
<span style="color: ${texColor};">${hasTexture}</span>
|
||||
`;
|
||||
}
|
||||
if (message.has_vertex_colors !== undefined) {
|
||||
const hasColors = message.has_vertex_colors[0] ? 'Yes' : 'No';
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Vertex Colors:</span>
|
||||
<span>${hasColors}</span>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Fields mode info
|
||||
if (message.field_names && message.field_names[0]?.length > 0) {
|
||||
const fields = message.field_names[0].join(', ');
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span style="font-size: 9px; color: #6cc;">${fields}</span>
|
||||
`;
|
||||
} else {
|
||||
infoHTML += `
|
||||
<span style="color: #888;">Fields:</span>
|
||||
<span style="color: #888;">None</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
infoHTML += '</div>';
|
||||
@@ -231,13 +281,31 @@ app.registerExtension({
|
||||
}
|
||||
};
|
||||
|
||||
// Send message after iframe is loaded
|
||||
if (iframeLoaded) {
|
||||
console.log("[GeomPack VTK DEBUG] Iframe already loaded, sending immediately");
|
||||
sendMessage();
|
||||
// Reload iframe if viewer type changed
|
||||
if (viewerType !== currentViewerType) {
|
||||
console.log(`[GeomPack VTK] Switching viewer from ${currentViewerType} to ${viewerType}`);
|
||||
currentViewerType = viewerType;
|
||||
iframeLoaded = false;
|
||||
|
||||
// Set up one-time load listener before changing src
|
||||
const onViewerLoaded = () => {
|
||||
console.log("[GeomPack VTK] New viewer loaded, sending mesh");
|
||||
iframeLoaded = true;
|
||||
sendMessage();
|
||||
};
|
||||
iframe.addEventListener('load', onViewerLoaded, { once: true });
|
||||
|
||||
// Change iframe src to trigger reload
|
||||
iframe.src = viewerUrl + "?v=" + Date.now();
|
||||
} else {
|
||||
console.log("[GeomPack VTK DEBUG] Waiting for iframe to load...");
|
||||
setTimeout(sendMessage, 500);
|
||||
// No viewer change needed, send message immediately or after short delay
|
||||
if (iframeLoaded) {
|
||||
console.log("[GeomPack VTK DEBUG] Iframe already loaded, sending immediately");
|
||||
sendMessage();
|
||||
} else {
|
||||
console.log("[GeomPack VTK DEBUG] Waiting for iframe to load...");
|
||||
setTimeout(sendMessage, 500);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("[GeomPack VTK] No mesh_file in message data. Keys:", Object.keys(message || {}));
|
||||
|
||||
+251
-16
@@ -73,6 +73,9 @@
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
min-height: 48px;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
}
|
||||
#controls button, #controls select {
|
||||
background: #333;
|
||||
@@ -108,6 +111,106 @@
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Settings panel */
|
||||
#settingsPanel {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
z-index: 2000;
|
||||
display: none;
|
||||
min-width: 280px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
#settingsPanel.visible {
|
||||
display: block;
|
||||
}
|
||||
#settingsPanel h3 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #ccc;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
#settingsPanel .setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#settingsPanel .setting-row label {
|
||||
flex: 0 0 100px;
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
#settingsPanel .setting-row input[type="color"] {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #555;
|
||||
background: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
#settingsPanel .setting-row input[type="number"] {
|
||||
flex: 1;
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
border: 1px solid #555;
|
||||
padding: 6px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
}
|
||||
#settingsPanel .setting-row select {
|
||||
flex: 1;
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
border: 1px solid #555;
|
||||
padding: 6px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
}
|
||||
#settingsPanel .button-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
#settingsPanel .button-row button {
|
||||
flex: 1;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
#settingsOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1999;
|
||||
display: none;
|
||||
}
|
||||
#settingsOverlay.visible {
|
||||
display: block;
|
||||
}
|
||||
#settingsButton {
|
||||
background: #333;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -130,9 +233,49 @@
|
||||
</div>
|
||||
|
||||
<div id="loading">Ready to load meshes</div>
|
||||
|
||||
<!-- Settings overlay and panel -->
|
||||
<div id="settingsOverlay"></div>
|
||||
<div id="settingsPanel">
|
||||
<h3>Appearance Settings</h3>
|
||||
<div class="setting-row">
|
||||
<label>Point Size:</label>
|
||||
<input type="number" id="pointSize" min="1" max="20" step="1" value="3">
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Mesh Color:</label>
|
||||
<input type="color" id="meshColor" value="#ffffff">
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Edge Width:</label>
|
||||
<input type="number" id="edgeWidth" min="1" max="10" step="1" value="1">
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Render Mode:</label>
|
||||
<select id="representationSettings">
|
||||
<option value="2">Surface</option>
|
||||
<option value="1">Wireframe</option>
|
||||
<option value="0">Points</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<input type="checkbox" id="parallelProjectionSetting"> Parallel Projection
|
||||
</label>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button id="applySettings">Apply</button>
|
||||
<button id="cancelSettings">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="controls">
|
||||
<!-- Settings button -->
|
||||
<button id="settingsButton" title="Appearance Settings">⚙</button>
|
||||
|
||||
<label class="divider"></label>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" id="showEdges"> Edges
|
||||
</label>
|
||||
@@ -149,9 +292,6 @@
|
||||
<label class="divider"></label>
|
||||
|
||||
<button id="resetCamera">Reset Camera</button>
|
||||
<label>
|
||||
<input type="checkbox" id="parallelProjection"> Parallel
|
||||
</label>
|
||||
|
||||
<!-- Field visualization controls -->
|
||||
<div class="field-controls" id="fieldControls">
|
||||
@@ -881,6 +1021,114 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Settings panel handlers
|
||||
const settingsButton = document.getElementById('settingsButton');
|
||||
const settingsPanel = document.getElementById('settingsPanel');
|
||||
const settingsOverlay = document.getElementById('settingsOverlay');
|
||||
const pointSizeInput = document.getElementById('pointSize');
|
||||
const meshColorInput = document.getElementById('meshColor');
|
||||
const edgeWidthInput = document.getElementById('edgeWidth');
|
||||
const representationSettings = document.getElementById('representationSettings');
|
||||
const parallelProjectionInput = document.getElementById('parallelProjectionSetting');
|
||||
const applySettingsButton = document.getElementById('applySettings');
|
||||
const cancelSettingsButton = document.getElementById('cancelSettings');
|
||||
|
||||
// Store current settings
|
||||
let currentPointSize = 3;
|
||||
let currentMeshColor = '#ffffff';
|
||||
let currentEdgeWidth = 1;
|
||||
let currentRepresentation = 2; // 2 = Surface
|
||||
let currentParallelProjection = false;
|
||||
|
||||
// Open settings panel
|
||||
settingsButton.addEventListener('click', () => {
|
||||
// Update inputs with current values
|
||||
pointSizeInput.value = currentPointSize;
|
||||
meshColorInput.value = currentMeshColor;
|
||||
edgeWidthInput.value = currentEdgeWidth;
|
||||
representationSettings.value = currentRepresentation.toString();
|
||||
parallelProjectionInput.checked = currentParallelProjection;
|
||||
|
||||
settingsPanel.classList.add('visible');
|
||||
settingsOverlay.classList.add('visible');
|
||||
});
|
||||
|
||||
// Close settings panel
|
||||
function closeSettingsPanel() {
|
||||
settingsPanel.classList.remove('visible');
|
||||
settingsOverlay.classList.remove('visible');
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
settingsOverlay.addEventListener('click', closeSettingsPanel);
|
||||
|
||||
// Cancel button
|
||||
cancelSettingsButton.addEventListener('click', closeSettingsPanel);
|
||||
|
||||
// Apply settings
|
||||
applySettingsButton.addEventListener('click', () => {
|
||||
const newPointSize = parseFloat(pointSizeInput.value);
|
||||
const newColor = meshColorInput.value;
|
||||
const newEdgeWidth = parseFloat(edgeWidthInput.value);
|
||||
const newRepresentation = parseInt(representationSettings.value);
|
||||
const newParallelProjection = parallelProjectionInput.checked;
|
||||
|
||||
console.log('[GeomPack VTK Dual Viewer] Applying settings - Point Size:', newPointSize, 'Color:', newColor, 'Edge Width:', newEdgeWidth, 'Representation:', newRepresentation, 'Parallel:', newParallelProjection);
|
||||
|
||||
// Convert hex color to RGB (0-1 range)
|
||||
const r = parseInt(newColor.substr(1, 2), 16) / 255;
|
||||
const g = parseInt(newColor.substr(3, 2), 16) / 255;
|
||||
const b = parseInt(newColor.substr(5, 2), 16) / 255;
|
||||
|
||||
// Apply settings to actors
|
||||
if (viewerLayout === 'side_by_side') {
|
||||
// Apply to both actors in side-by-side mode
|
||||
const leftProperty = leftActor.getProperty();
|
||||
const rightProperty = rightActor.getProperty();
|
||||
|
||||
leftProperty.setPointSize(newPointSize);
|
||||
leftProperty.setColor(r, g, b);
|
||||
leftProperty.setLineWidth(newEdgeWidth);
|
||||
leftProperty.setRepresentation(newRepresentation);
|
||||
|
||||
rightProperty.setPointSize(newPointSize);
|
||||
rightProperty.setColor(r, g, b);
|
||||
rightProperty.setLineWidth(newEdgeWidth);
|
||||
rightProperty.setRepresentation(newRepresentation);
|
||||
|
||||
// Apply parallel projection to both cameras
|
||||
leftRenderer.getActiveCamera().setParallelProjection(newParallelProjection);
|
||||
rightRenderer.getActiveCamera().setParallelProjection(newParallelProjection);
|
||||
|
||||
leftRenderWindow.render();
|
||||
rightRenderWindow.render();
|
||||
} else {
|
||||
// Apply to single actor in overlay mode
|
||||
const property = singleActor.getProperty();
|
||||
property.setPointSize(newPointSize);
|
||||
property.setColor(r, g, b);
|
||||
property.setLineWidth(newEdgeWidth);
|
||||
property.setRepresentation(newRepresentation);
|
||||
|
||||
// Apply parallel projection to camera
|
||||
singleRenderer.getActiveCamera().setParallelProjection(newParallelProjection);
|
||||
|
||||
singleRenderWindow.render();
|
||||
}
|
||||
|
||||
// Update stored settings
|
||||
currentPointSize = newPointSize;
|
||||
currentMeshColor = newColor;
|
||||
currentEdgeWidth = newEdgeWidth;
|
||||
currentRepresentation = newRepresentation;
|
||||
currentParallelProjection = newParallelProjection;
|
||||
|
||||
console.log('[GeomPack VTK Dual Viewer] Settings applied - RGB:', [r, g, b], 'Point Size:', newPointSize, 'Line Width:', newEdgeWidth, 'Representation:', newRepresentation, 'Parallel:', newParallelProjection);
|
||||
|
||||
// Close panel
|
||||
closeSettingsPanel();
|
||||
});
|
||||
|
||||
// Event handlers
|
||||
document.getElementById('showEdges').addEventListener('change', (e) => {
|
||||
const edgeViz = e.target.checked;
|
||||
@@ -895,19 +1143,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('parallelProjection').addEventListener('change', (e) => {
|
||||
const parallel = e.target.checked;
|
||||
if (viewerLayout === 'side_by_side') {
|
||||
leftRenderer.getActiveCamera().setParallelProjection(parallel);
|
||||
rightRenderer.getActiveCamera().setParallelProjection(parallel);
|
||||
leftRenderWindow.render();
|
||||
rightRenderWindow.render();
|
||||
} else {
|
||||
singleRenderer.getActiveCamera().setParallelProjection(parallel);
|
||||
singleRenderWindow.render();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('resetCamera').addEventListener('click', () => {
|
||||
if (!combinedBounds) return;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user