From fa1933dc4fc44686fe4eefd1e5e8d1dd5780a8e6 Mon Sep 17 00:00:00 2001 From: Andrea Pozzetti Date: Thu, 12 Feb 2026 22:16:40 +0100 Subject: [PATCH] sparing javascript lines --- nodes/remeshing/remesh.py | 22 +- nodes/repair/fill_holes.py | 4 +- .../texture_remeshing/texture_to_geometry.py | 8 +- nodes/transforms/transform.py | 53 ++-- nodes/uv/uv_unwrap.py | 32 ++- prestartup_script.py | 121 ++++----- pyproject.toml | 1 + requirements.txt | 1 + web/js/backend_mappings.json | 44 --- web/js/dynamic_widgets.js | 145 +++++----- web/js/texture_to_geometry_dynamic.js | 183 ------------- web/js/transform_dynamic.js | 250 ------------------ web/js/uv_unwrap_dynamic.js | 214 --------------- 13 files changed, 212 insertions(+), 866 deletions(-) delete mode 100644 web/js/backend_mappings.json delete mode 100644 web/js/texture_to_geometry_dynamic.js delete mode 100644 web/js/transform_dynamic.js delete mode 100644 web/js/uv_unwrap_dynamic.js diff --git a/nodes/remeshing/remesh.py b/nodes/remeshing/remesh.py index 3ebd3249..41f4e748 100644 --- a/nodes/remeshing/remesh.py +++ b/nodes/remeshing/remesh.py @@ -52,7 +52,7 @@ class RemeshNode: "step": 0.01, "display": "number", "tooltip": "Target edge length for output triangles. Value is relative to mesh scale.", - "backends": ["pymeshlab_isotropic", "cgal_isotropic"], + "visible_when": {"backend": ["pymeshlab_isotropic", "cgal_isotropic"]}, }), "iterations": ("INT", { "default": 3, @@ -60,7 +60,7 @@ class RemeshNode: "max": 20, "step": 1, "tooltip": "Number of remeshing passes. More iterations = smoother result, slower processing.", - "backends": ["pymeshlab_isotropic", "cgal_isotropic"], + "visible_when": {"backend": ["pymeshlab_isotropic", "cgal_isotropic"]}, }), # PyMeshLab-specific "feature_angle": ("FLOAT", { @@ -69,18 +69,18 @@ class RemeshNode: "max": 180.0, "step": 1.0, "tooltip": "Angle threshold (degrees) for feature edge detection. Edges with dihedral angle greater than this are preserved as sharp creases.", - "backends": ["pymeshlab_isotropic"], + "visible_when": {"backend": ["pymeshlab_isotropic"]}, }), "adaptive": (["true", "false"], { "default": "false", "tooltip": "Use curvature-adaptive edge lengths. Creates smaller triangles in high-curvature areas, larger triangles in flat areas.", - "backends": ["pymeshlab_isotropic"], + "visible_when": {"backend": ["pymeshlab_isotropic"]}, }), # CGAL-specific "protect_boundaries": (["true", "false"], { "default": "true", "tooltip": "Lock boundary/open edges in place during remeshing. Prevents modification of mesh borders and holes.", - "backends": ["cgal_isotropic"], + "visible_when": {"backend": ["cgal_isotropic"]}, }), # Blender voxel "voxel_size": ("FLOAT", { @@ -90,7 +90,7 @@ class RemeshNode: "step": 0.01, "display": "number", "tooltip": "Voxel size for Blender voxel remesh. Smaller = more detail, more faces. Output is always watertight.", - "backends": ["blender_voxel"], + "visible_when": {"backend": ["blender_voxel"]}, }), # CuMesh / Quadriflow "target_face_count": ("INT", { @@ -99,7 +99,7 @@ class RemeshNode: "max": 5000000, "step": 1000, "tooltip": "Target number of output faces for cumesh/quadriflow backends.", - "backends": ["cumesh", "blender_quadriflow"], + "visible_when": {"backend": ["cumesh", "blender_quadriflow"]}, }), # CuMesh specific (matches TRELLIS2) "remesh_band": ("FLOAT", { @@ -108,7 +108,7 @@ class RemeshNode: "max": 5.0, "step": 0.1, "tooltip": "Band width for CuMesh dual-contouring. Affects surface detail capture. Higher = smoother but may lose fine details.", - "backends": ["cumesh"], + "visible_when": {"backend": ["cumesh"]}, }), # Instant Meshes specific "target_vertex_count": ("INT", { @@ -117,12 +117,12 @@ class RemeshNode: "max": 1000000, "step": 100, "tooltip": "Target vertex count for Instant Meshes output. Creates field-aligned quad-dominant mesh.", - "backends": ["instant_meshes"], + "visible_when": {"backend": ["instant_meshes"]}, }), "deterministic": (["true", "false"], { "default": "true", "tooltip": "Use deterministic algorithm for reproducible results. Disable for potentially better quality but non-reproducible output.", - "backends": ["instant_meshes"], + "visible_when": {"backend": ["instant_meshes"]}, }), "crease_angle": ("FLOAT", { "default": 0.0, @@ -130,7 +130,7 @@ class RemeshNode: "max": 180.0, "step": 1.0, "tooltip": "Angle threshold (degrees) for preserving sharp/crease edges in Instant Meshes. 0 = no crease preservation.", - "backends": ["instant_meshes"], + "visible_when": {"backend": ["instant_meshes"]}, }), } } diff --git a/nodes/repair/fill_holes.py b/nodes/repair/fill_holes.py index 16dec2f7..12cb57fc 100644 --- a/nodes/repair/fill_holes.py +++ b/nodes/repair/fill_holes.py @@ -51,7 +51,7 @@ class FillHolesNode: "min": 0.001, "max": 1.0, "step": 0.001, - "backends": ["cumesh"], + "visible_when": {"method": ["cumesh"]}, }), # PyMeshLab parameter "maxholesize": ("INT", { @@ -59,7 +59,7 @@ class FillHolesNode: "min": 1, "max": 100000, "step": 100, - "backends": ["pymeshlab"], + "visible_when": {"method": ["pymeshlab"]}, }), }, } diff --git a/nodes/texture_remeshing/texture_to_geometry.py b/nodes/texture_remeshing/texture_to_geometry.py index be76492b..ce2ac0ed 100644 --- a/nodes/texture_remeshing/texture_to_geometry.py +++ b/nodes/texture_remeshing/texture_to_geometry.py @@ -58,10 +58,14 @@ class TextureToGeometryNode: "min": 4, "max": 12, "step": 1, - "tooltip": "Octree depth for Poisson reconstruction (higher = more detail)" + "tooltip": "Octree depth for Poisson reconstruction (higher = more detail)", + "visible_when": {"backend": ["poisson_pymeshlab", "poisson_open3d"]}, }), "invert_height": (["false", "true"], {"default": "false"}), - "smooth_normals": (["true", "false"], {"default": "true"}), + "smooth_normals": (["true", "false"], { + "default": "true", + "visible_when": {"backend": ["grid"]}, + }), "skip_black": (["false", "true"], {"default": "false", "tooltip": "Skip faces connected to near-black pixels in the depth map"}), "black_threshold": ("FLOAT", { "default": 0.01, diff --git a/nodes/transforms/transform.py b/nodes/transforms/transform.py index 3ec53981..edad3031 100644 --- a/nodes/transforms/transform.py +++ b/nodes/transforms/transform.py @@ -53,74 +53,97 @@ class TransformMeshNode: "default": 0.0, "min": -1000.0, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["translate"]}, }), "translate_y": ("FLOAT", { "default": 0.0, "min": -1000.0, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["translate"]}, }), "translate_z": ("FLOAT", { "default": 0.0, "min": -1000.0, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["translate"]}, }), # Rotate (degrees) "rotate_x": ("FLOAT", { "default": 0.0, "min": -360.0, "max": 360.0, - "step": 1.0 + "step": 1.0, + "visible_when": {"operation": ["rotate"]}, }), "rotate_y": ("FLOAT", { "default": 0.0, "min": -360.0, "max": 360.0, - "step": 1.0 + "step": 1.0, + "visible_when": {"operation": ["rotate"]}, }), "rotate_z": ("FLOAT", { "default": 0.0, "min": -360.0, "max": 360.0, - "step": 1.0 + "step": 1.0, + "visible_when": {"operation": ["rotate"]}, }), # Scale "scale_uniform": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["scale"]}, }), "scale_x": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["scale"]}, }), "scale_y": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["scale"]}, }), "scale_z": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"operation": ["scale"]}, }), # Mirror - "mirror_axis": (["x", "y", "z"], {"default": "x"}), + "mirror_axis": (["x", "y", "z"], { + "default": "x", + "visible_when": {"operation": ["mirror"]}, + }), # Center options - "center_x": (["true", "false"], {"default": "true"}), - "center_y": (["true", "false"], {"default": "true"}), - "center_z": (["true", "false"], {"default": "true"}), + "center_x": (["true", "false"], { + "default": "true", + "visible_when": {"operation": ["center"]}, + }), + "center_y": (["true", "false"], { + "default": "true", + "visible_when": {"operation": ["center"]}, + }), + "center_z": (["true", "false"], { + "default": "true", + "visible_when": {"operation": ["center"]}, + }), # Matrix (16 comma-separated floats, row-major) "matrix_string": ("STRING", { "default": "1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1", - "multiline": False + "multiline": False, + "visible_when": {"operation": ["apply_matrix"]}, }), } } diff --git a/nodes/uv/uv_unwrap.py b/nodes/uv/uv_unwrap.py index ab4791e0..8e6f8dc4 100644 --- a/nodes/uv/uv_unwrap.py +++ b/nodes/uv/uv_unwrap.py @@ -52,25 +52,29 @@ class UVUnwrapNode: "default": 90.0, "min": 0.0, "max": 359.9, - "step": 1.0 + "step": 1.0, + "visible_when": {"method": ["cumesh"]}, }), "chart_refine_iterations": ("INT", { "default": 0, "min": 0, "max": 10, - "step": 1 + "step": 1, + "visible_when": {"method": ["cumesh"]}, }), "chart_global_iterations": ("INT", { "default": 1, "min": 0, "max": 10, - "step": 1 + "step": 1, + "visible_when": {"method": ["cumesh"]}, }), "chart_smooth_strength": ("INT", { "default": 1, "min": 0, "max": 10, - "step": 1 + "step": 1, + "visible_when": {"method": ["cumesh"]}, }), # libigl_arap parameters @@ -78,7 +82,8 @@ class UVUnwrapNode: "default": 10, "min": 1, "max": 100, - "step": 1 + "step": 1, + "visible_when": {"method": ["libigl_arap"]}, }), # Blender smart_uv parameters @@ -86,28 +91,35 @@ class UVUnwrapNode: "default": 66.0, "min": 1.0, "max": 89.0, - "step": 1.0 + "step": 1.0, + "visible_when": {"method": ["blender_smart"]}, }), "island_margin": ("FLOAT", { "default": 0.02, "min": 0.0, "max": 1.0, - "step": 0.01 + "step": 0.01, + "visible_when": {"method": ["blender_smart"]}, }), # Blender projection parameters - "scale_to_bounds": (["true", "false"], {"default": "true"}), + "scale_to_bounds": (["true", "false"], { + "default": "true", + "visible_when": {"method": ["blender_smart", "blender_cube", "blender_cylinder", "blender_sphere"]}, + }), "cube_size": ("FLOAT", { "default": 1.0, "min": 0.1, "max": 10.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"method": ["blender_cube"]}, }), "cylinder_radius": ("FLOAT", { "default": 1.0, "min": 0.1, "max": 10.0, - "step": 0.1 + "step": 0.1, + "visible_when": {"method": ["blender_cylinder"]}, }), } } diff --git a/prestartup_script.py b/prestartup_script.py index ae8edeec..c545bedc 100644 --- a/prestartup_script.py +++ b/prestartup_script.py @@ -3,126 +3,97 @@ """ GeometryPack PreStartup Script -- Generates backend_mappings.json for dynamic widget visibility +- Generates dynamic widget visibility mappings - Copies example 3D assets to ComfyUI input folder on startup """ -import os -import re import json +import os import shutil -def generate_backend_mappings(): - """ - Parse Python node files and generate backend_mappings.json for JS. - - Reads 'backends' metadata from INPUT_TYPES in node files and writes - a JSON file that JavaScript can fetch to show/hide widgets dynamically. - """ - custom_node_dir = os.path.dirname(os.path.abspath(__file__)) - - # Node files to parse: (file_path, node_class, backend_widget_name) - node_files = [ - ("nodes/remeshing/remesh.py", "GeomPackRemesh", "backend"), - ("nodes/repair/fill_holes.py", "GeomPackFillHoles", "method"), - ] - - mappings = {} - backend_widgets = {} - - for rel_path, node_class, backend_widget in node_files: - file_path = os.path.join(custom_node_dir, rel_path) - if not os.path.exists(file_path): - print(f"[GeometryPack] Warning: {rel_path} not found, skipping") - continue - - with open(file_path, 'r') as f: - source = f.read() - - # Extract param -> backends mapping using regex - # Matches: "param_name": (TYPE, {..., "backends": ["a", "b"], ...}) - node_mapping = {} - pattern = r'"(\w+)":\s*\([^)]+\{[^}]*"backends":\s*\[([^\]]+)\]' - - for match in re.finditer(pattern, source, re.DOTALL): - param_name = match.group(1) - backends_str = match.group(2) - backends = [b.strip().strip('"\'') for b in backends_str.split(',')] - - for backend in backends: - if backend not in node_mapping: - node_mapping[backend] = [] - node_mapping[backend].append(param_name) - - if node_mapping: - mappings[node_class] = node_mapping - backend_widgets[node_class] = backend_widget - print(f"[GeometryPack] Parsed {node_class}: {len(node_mapping)} backends") - - # Write to web/js/backend_mappings.json - output = { - "mappings": mappings, - "backend_widgets": backend_widgets, - } - - output_path = os.path.join(custom_node_dir, "web", "js", "backend_mappings.json") - os.makedirs(os.path.dirname(output_path), exist_ok=True) - - with open(output_path, 'w') as f: - json.dump(output, f, indent=2) - - print(f"[GeometryPack] Generated backend_mappings.json") - def copy_example_assets(): """Copy all files and folders from assets/ directory to ComfyUI input/3d directory.""" try: import folder_paths - + input_folder = folder_paths.get_input_directory() custom_node_dir = os.path.dirname(os.path.abspath(__file__)) - + # Create input/3d subdirectory input_3d_folder = os.path.join(input_folder, "3d") os.makedirs(input_3d_folder, exist_ok=True) - + # Copy entire assets/ folder structure assets_folder = os.path.join(custom_node_dir, "assets") if not os.path.exists(assets_folder): print(f"[GeometryPack] Warning: assets folder not found at {assets_folder}") return - + copied_count = 0 for root, dirs, files in os.walk(assets_folder): # Calculate relative path from assets folder rel_path = os.path.relpath(root, assets_folder) - + # Create corresponding subdirectory in destination if rel_path != '.': dest_dir = os.path.join(input_3d_folder, rel_path) os.makedirs(dest_dir, exist_ok=True) else: dest_dir = input_3d_folder - + # Copy files for file in files: source_file = os.path.join(root, file) dest_file = os.path.join(dest_dir, file) - + if not os.path.exists(dest_file): shutil.copy2(source_file, dest_file) copied_count += 1 # Show relative path for clarity rel_dest = os.path.join(rel_path, file) if rel_path != '.' else file print(f"[GeometryPack] Copied {rel_dest} to input/3d/") - + if copied_count > 0: print(f"[GeometryPack] [OK] Copied {copied_count} asset(s) to {input_3d_folder}") else: print(f"[GeometryPack] All assets already exist in {input_3d_folder}") - + except Exception as e: print(f"[GeometryPack] Error copying assets: {e}") + +def generate_widget_mappings(): + """Generate dynamic widget visibility mappings using comfy-dynamic-widgets.""" + try: + from comfy_dynamic_widgets import scan_all_nodes, generate_mappings + + # Scan all nodes for visible_when metadata + configs = scan_all_nodes() + + if not configs: + print("[GeometryPack] No visible_when metadata found in any nodes") + return + + # Generate JS-friendly mappings + mappings = generate_mappings(configs) + + # Write to web/js/mappings.json + custom_node_dir = os.path.dirname(os.path.abspath(__file__)) + output_path = os.path.join(custom_node_dir, "web", "js", "mappings.json") + + with open(output_path, "w") as f: + json.dump(mappings, f, indent=2) + + node_count = len(configs) + selector_count = sum(len(c.get("selectors", {})) for c in configs.values()) + print(f"[GeometryPack] Generated widget mappings for {node_count} nodes ({selector_count} selectors)") + + except ImportError: + print("[GeometryPack] Warning: comfy-dynamic-widgets not installed, skipping widget mappings") + except Exception as e: + print(f"[GeometryPack] Error generating widget mappings: {e}") + + # Run on import -generate_backend_mappings() -copy_example_assets() \ No newline at end of file +generate_widget_mappings() +copy_example_assets() diff --git a/pyproject.toml b/pyproject.toml index 2b13df1d..c272d0e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ classifiers = [ ] dependencies = [ "comfy-env==0.0.19", + "comfy-dynamic-widgets>=0.0.2", "trimesh>=3.15.0", "libigl>=2.6.1", "numpy>=1.21.0", diff --git a/requirements.txt b/requirements.txt index a9267536..92af74d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # Installation dependencies (for install.py optimization) comfy-env==0.0.19 +comfy-dynamic-widgets>=0.0.2 requests>=2.25.0 tqdm>=4.60.0 diff --git a/web/js/backend_mappings.json b/web/js/backend_mappings.json deleted file mode 100644 index a33c1ee0..00000000 --- a/web/js/backend_mappings.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "mappings": { - "GeomPackRemesh": { - "pymeshlab_isotropic": [ - "target_edge_length", - "iterations", - "feature_angle", - "adaptive" - ], - "cgal_isotropic": [ - "target_edge_length", - "iterations", - "protect_boundaries" - ], - "blender_voxel": [ - "voxel_size" - ], - "cumesh": [ - "target_face_count", - "remesh_band" - ], - "blender_quadriflow": [ - "target_face_count" - ], - "instant_meshes": [ - "target_vertex_count", - "deterministic", - "crease_angle" - ] - }, - "GeomPackFillHoles": { - "cumesh": [ - "perimeter" - ], - "pymeshlab": [ - "maxholesize" - ] - } - }, - "backend_widgets": { - "GeomPackRemesh": "backend", - "GeomPackFillHoles": "method" - } -} \ No newline at end of file diff --git a/web/js/dynamic_widgets.js b/web/js/dynamic_widgets.js index 41ad1fa2..615b311d 100644 --- a/web/js/dynamic_widgets.js +++ b/web/js/dynamic_widgets.js @@ -1,24 +1,29 @@ /** - * Dynamic Widget Visibility for Multi-Backend Nodes + * Dynamic Widget Visibility for ComfyUI * - * Shows/hides widgets based on selected backend. - * Mappings are loaded from backend_mappings.json (generated by prestartup_script.py). + * Shows/hides widgets based on selector widget values. + * Mappings are loaded from mappings.json (generated by prestartup_script.py). + * + * Usage in node definitions: + * "voxel_size": ("FLOAT", { + * "default": 0.1, + * "visible_when": {"backend": ["blender_voxel"]}, + * }) */ import { app } from "../../../scripts/app.js"; -console.log("[GeomPack DynamicWidgets] Loading extension..."); +console.log("[DynamicWidgets] Loading extension..."); // Will be populated from JSON -let BACKEND_MAPPINGS = {}; -let BACKEND_WIDGET_NAME = {}; +let MAPPINGS = null; /** - * Fetch mappings from generated JSON (try both casing for manager installs) + * Fetch mappings from generated JSON */ async function loadMappings() { const paths = [ - "/extensions/ComfyUI-GeometryPack/js/backend_mappings.json", - "/extensions/comfyui-geometrypack/js/backend_mappings.json", + "/extensions/ComfyUI-GeometryPack/js/mappings.json", + "/extensions/comfyui-geometrypack/js/mappings.json", ]; for (const path of paths) { @@ -26,16 +31,16 @@ async function loadMappings() { const response = await fetch(path); if (response.ok) { const data = await response.json(); - BACKEND_MAPPINGS = data.mappings || {}; - BACKEND_WIDGET_NAME = data.backend_widgets || {}; - console.log("[GeomPack DynamicWidgets] Loaded mappings from", path); + MAPPINGS = data; + const nodeCount = Object.keys(data.nodes || {}).length; + console.log(`[DynamicWidgets] Loaded mappings for ${nodeCount} nodes from ${path}`); return true; } } catch (e) { // Try next path } } - console.warn("[GeomPack DynamicWidgets] Could not load backend_mappings.json"); + console.warn("[DynamicWidgets] Could not load mappings.json - no dynamic widgets configured"); return false; } @@ -43,37 +48,49 @@ async function loadMappings() { loadMappings(); /** - * Update widget visibility based on selected backend + * Get the node configuration from mappings */ -function updateWidgetVisibility(node, selectedBackend) { - const nodeClass = node.comfyClass; - const mapping = BACKEND_MAPPINGS[nodeClass]; - if (!mapping) return; +function getNodeConfig(nodeClass) { + if (!MAPPINGS || !MAPPINGS.nodes) return null; + return MAPPINGS.nodes[nodeClass] || null; +} - const visibleWidgets = mapping[selectedBackend] || []; - const backendWidgetName = BACKEND_WIDGET_NAME[nodeClass]; +/** + * Update widget visibility based on selector value + */ +function updateWidgetVisibility(node, selectorName, selectedValue) { + const nodeConfig = getNodeConfig(node.comfyClass); + if (!nodeConfig || !nodeConfig.selectors) return; - console.log(`[GeomPack DynamicWidgets] ${nodeClass}: backend="${selectedBackend}", visible=[${visibleWidgets.join(', ')}]`); + const selectorConfig = nodeConfig.selectors[selectorName]; + if (!selectorConfig) return; + + // Get widgets that should be visible for this selector value + const visibleWidgets = selectorConfig[selectedValue] || []; + + // Get all widgets controlled by this selector + const allControlledWidgets = new Set(); + for (const widgets of Object.values(selectorConfig)) { + for (const w of widgets) { + allControlledWidgets.add(w); + } + } + + console.log(`[DynamicWidgets] ${node.comfyClass}: ${selectorName}="${selectedValue}", visible=[${visibleWidgets.join(', ')}]`); let visibilityChanged = false; for (const widget of node.widgets || []) { - // Skip the backend/method selector itself and the mesh input - if (widget.name === backendWidgetName || widget.name === "trimesh" || widget.name === "mesh") continue; + // Only control widgets that are in our mapping + if (!allControlledWidgets.has(widget.name)) continue; - // Check if this widget should be visible for the current backend - const allWidgets = Object.values(mapping).flat(); - const isBackendWidget = allWidgets.includes(widget.name); + const shouldShow = visibleWidgets.includes(widget.name); + const wasHidden = widget.hidden; + widget.hidden = !shouldShow; - if (isBackendWidget) { - const shouldShow = visibleWidgets.includes(widget.name); - const wasHidden = widget.hidden; - widget.hidden = !shouldShow; - - if (wasHidden !== widget.hidden) { - visibilityChanged = true; - console.log(`[GeomPack DynamicWidgets] Widget "${widget.name}": hidden=${widget.hidden}`); - } + if (wasHidden !== widget.hidden) { + visibilityChanged = true; + console.log(`[DynamicWidgets] Widget "${widget.name}": hidden=${widget.hidden}`); } } @@ -83,37 +100,45 @@ function updateWidgetVisibility(node, selectedBackend) { } } +/** + * Set up visibility control for a selector widget + */ +function setupSelectorWidget(node, selectorName) { + const selectorWidget = node.widgets?.find(w => w.name === selectorName); + if (!selectorWidget) { + console.log(`[DynamicWidgets] Selector widget "${selectorName}" not found on ${node.comfyClass}`); + return; + } + + // Store original callback + const originalCallback = selectorWidget.callback; + + // Override callback to update visibility on change + selectorWidget.callback = function(value) { + if (originalCallback) originalCallback.call(this, value); + updateWidgetVisibility(node, selectorName, value); + }; + + // Initial visibility update (with delay to ensure widgets are created) + setTimeout(() => { + updateWidgetVisibility(node, selectorName, selectorWidget.value); + }, 50); +} + app.registerExtension({ - name: "GeomPack.DynamicWidgets", + name: "Comfy.DynamicWidgets", nodeCreated(node) { - const nodeClass = node.comfyClass; - if (!BACKEND_MAPPINGS[nodeClass]) return; + const nodeConfig = getNodeConfig(node.comfyClass); + if (!nodeConfig || !nodeConfig.selectors) return; - console.log(`[GeomPack DynamicWidgets] Setting up node: ${nodeClass}`); + console.log(`[DynamicWidgets] Setting up node: ${node.comfyClass}`); - const backendWidgetName = BACKEND_WIDGET_NAME[nodeClass]; - const backendWidget = node.widgets?.find(w => w.name === backendWidgetName); - - if (!backendWidget) { - console.log(`[GeomPack DynamicWidgets] No ${backendWidgetName} widget found`); - return; + // Set up each selector + for (const selectorName of Object.keys(nodeConfig.selectors)) { + setupSelectorWidget(node, selectorName); } - - // Store original callback - const originalCallback = backendWidget.callback; - - // Override callback to update visibility on change - backendWidget.callback = function(value) { - if (originalCallback) originalCallback.call(this, value); - updateWidgetVisibility(node, value); - }; - - // Initial visibility update (with delay to ensure widgets are created) - setTimeout(() => { - updateWidgetVisibility(node, backendWidget.value); - }, 50); } }); -console.log("[GeomPack DynamicWidgets] Extension registered"); +console.log("[DynamicWidgets] Extension registered"); diff --git a/web/js/texture_to_geometry_dynamic.js b/web/js/texture_to_geometry_dynamic.js deleted file mode 100644 index 1d4d6157..00000000 --- a/web/js/texture_to_geometry_dynamic.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * ComfyUI-GeometryPack Dynamic Parameter Management - * - * This extension manages the visibility of backend-specific parameters - * in the Depth Map to Mesh node based on the selected backend. - */ - -import { app } from "../../../scripts/app.js"; - -const DEBUG = false; - -function log(...args) { - if (DEBUG) { - console.log("[GeomPack-TextureToGeometry]", ...args); - } -} - -// Helper function to hide a widget -function hideWidget(node, widget) { - if (!widget) return; - if (widget._hidden) { - log("Widget already hidden:", widget.name); - return; - } - - log("Hiding widget:", widget.name); - - const index = node.widgets.indexOf(widget); - if (index === -1) { - log(" ERROR: Widget not found in array!"); - return; - } - - if (!widget.origType) { - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.origSerializeValue = widget.serializeValue; - } - - widget._originalIndex = index; - widget._hidden = true; - - node.widgets.splice(index, 1); - - log(" Widget removed from index:", index); - - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => hideWidget(node, w)); - } -} - -// Helper function to show a widget -function showWidget(node, widget) { - if (!widget) return; - if (!widget._hidden) { - log("Widget already visible:", widget.name); - return; - } - - log("Showing widget:", widget.name); - - if (widget.origType) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - if (widget.origSerializeValue) { - widget.serializeValue = widget.origSerializeValue; - } - } - - const insertIndex = Math.min(widget._originalIndex, node.widgets.length); - node.widgets.splice(insertIndex, 0, widget); - - log(" Widget restored to index:", insertIndex); - - widget._hidden = false; - - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => showWidget(node, w)); - } -} - -// Helper to update node size and redraw -function updateNodeSize(node) { - node.setDirtyCanvas(true, true); - if (app.graph) { - app.graph.setDirtyCanvas(true, true); - } - - requestAnimationFrame(() => { - const newSize = node.computeSize(); - node.setSize([node.size[0], newSize[1]]); - node.setDirtyCanvas(true, true); - - if (app.canvas) { - app.canvas.setDirty(true, true); - } - - requestAnimationFrame(() => { - if (app.canvas) { - app.canvas.draw(true, true); - } - log("Widget visibility update complete"); - }); - }); -} - -// Main extension registration -app.registerExtension({ - name: "geompack.texture_to_geometry_dynamic", - - async nodeCreated(node) { - if (node.comfyClass === "GeomPackTextureToGeometry") { - setTimeout(() => { - this.setupTextureToGeometryNode(node); - }, 100); - } - }, - - setupTextureToGeometryNode(node) { - log("Setting up GeomPackTextureToGeometry node"); - - // Find the backend selector widget - const backendWidget = node.widgets?.find(w => w.name === "backend"); - if (!backendWidget) { - log("ERROR: Backend widget not found!"); - return; - } - - log("Backend widget found, current value:", backendWidget.value); - - // Find all parameter widgets - const poissonDepthWidget = node.widgets?.find(w => w.name === "poisson_depth"); - const smoothNormalsWidget = node.widgets?.find(w => w.name === "smooth_normals"); - - log("Found parameter widgets:", { - poisson_depth: !!poissonDepthWidget, - smooth_normals: !!smoothNormalsWidget - }); - - // Function to update widget visibility based on selected backend - const updateWidgetVisibility = (selectedBackend) => { - log("Updating widget visibility for backend:", selectedBackend); - - // Hide all backend-specific parameters first - hideWidget(node, poissonDepthWidget); - hideWidget(node, smoothNormalsWidget); - - // Show relevant parameters based on backend - if (selectedBackend === "grid") { - // Grid: show smooth_normals, hide poisson_depth - showWidget(node, smoothNormalsWidget); - } else if (selectedBackend === "poisson_pymeshlab" || selectedBackend === "poisson_open3d") { - // Poisson backends: show poisson_depth, hide smooth_normals - showWidget(node, poissonDepthWidget); - } else if (selectedBackend === "delaunay_2d") { - // Delaunay: hide both poisson_depth and smooth_normals - // (already hidden above) - } - - updateNodeSize(node); - }; - - // Store original callback - const origCallback = backendWidget.callback; - - // Override callback to update visibility when backend changes - backendWidget.callback = function(value) { - log("Backend changed to:", value); - - const result = origCallback?.apply(this, arguments); - - updateWidgetVisibility(value); - - return result; - }; - - // Initialize visibility on node creation - log("Initializing widget visibility..."); - updateWidgetVisibility(backendWidget.value); - } -}); - -log("GeomPack TextureToGeometry dynamic parameters extension loaded"); diff --git a/web/js/transform_dynamic.js b/web/js/transform_dynamic.js deleted file mode 100644 index 961c8731..00000000 --- a/web/js/transform_dynamic.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * ComfyUI-GeometryPack Transform Dynamic Parameter Management - * - * This extension manages the visibility of operation-specific parameters - * in the Transform Mesh node based on the selected operation. - */ - -import { app } from "../../../scripts/app.js"; - -const DEBUG = false; // Set to true to enable console logging - -function log(...args) { - if (DEBUG) { - console.log("[GeomPack-Transform-Dynamic]", ...args); - } -} - -// Helper function to hide a widget (removes from array) -function hideWidget(node, widget) { - if (!widget) return; - if (widget._hidden) { - log("Widget already hidden:", widget.name); - return; - } - - log("Hiding widget:", widget.name); - - const index = node.widgets.indexOf(widget); - if (index === -1) { - log(" ERROR: Widget not found in array!"); - return; - } - - // Store original properties for restoration - if (!widget.origType) { - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.origSerializeValue = widget.serializeValue; - } - - widget._originalIndex = index; - widget._hidden = true; - - // Remove widget from array - node.widgets.splice(index, 1); - - log(" Widget removed from index:", index); - - // Hide linked widgets if any - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => hideWidget(node, w)); - } -} - -// Helper function to show a widget (re-inserts into array) -function showWidget(node, widget) { - if (!widget) return; - if (!widget._hidden) { - log("Widget already visible:", widget.name); - return; - } - - log("Showing widget:", widget.name); - - // Restore original properties - if (widget.origType) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - if (widget.origSerializeValue) { - widget.serializeValue = widget.origSerializeValue; - } - } - - // Re-insert widget at original position - const insertIndex = Math.min(widget._originalIndex, node.widgets.length); - node.widgets.splice(insertIndex, 0, widget); - - log(" Widget restored to index:", insertIndex); - - widget._hidden = false; - - // Show linked widgets if any - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => showWidget(node, w)); - } -} - -// Helper to update node size and redraw -function updateNodeSize(node) { - node.setDirtyCanvas(true, true); - if (app.graph) { - app.graph.setDirtyCanvas(true, true); - } - - requestAnimationFrame(() => { - const newSize = node.computeSize(); - node.setSize([node.size[0], newSize[1]]); - node.setDirtyCanvas(true, true); - - if (app.canvas) { - app.canvas.setDirty(true, true); - } - - requestAnimationFrame(() => { - if (app.canvas) { - app.canvas.draw(true, true); - } - log("Widget visibility update complete"); - }); - }); -} - -// Main extension registration -app.registerExtension({ - name: "geompack.transform_dynamic_parameters", - - async nodeCreated(node) { - // Handle Transform Mesh node - if (node.comfyClass === "GeomPackTransformMesh") { - setTimeout(() => { - this.setupTransformNode(node); - }, 100); - } - }, - - setupTransformNode(node) { - log("Setting up GeomPackTransformMesh node"); - - // Find the operation selector widget - const operationWidget = node.widgets?.find(w => w.name === "operation"); - if (!operationWidget) { - log("ERROR: Operation widget not found!"); - return; - } - - log("Operation widget found, current value:", operationWidget.value); - - // Find all parameter widgets - const translateXWidget = node.widgets?.find(w => w.name === "translate_x"); - const translateYWidget = node.widgets?.find(w => w.name === "translate_y"); - const translateZWidget = node.widgets?.find(w => w.name === "translate_z"); - - const rotateXWidget = node.widgets?.find(w => w.name === "rotate_x"); - const rotateYWidget = node.widgets?.find(w => w.name === "rotate_y"); - const rotateZWidget = node.widgets?.find(w => w.name === "rotate_z"); - - const scaleUniformWidget = node.widgets?.find(w => w.name === "scale_uniform"); - const scaleXWidget = node.widgets?.find(w => w.name === "scale_x"); - const scaleYWidget = node.widgets?.find(w => w.name === "scale_y"); - const scaleZWidget = node.widgets?.find(w => w.name === "scale_z"); - - const mirrorAxisWidget = node.widgets?.find(w => w.name === "mirror_axis"); - - const centerXWidget = node.widgets?.find(w => w.name === "center_x"); - const centerYWidget = node.widgets?.find(w => w.name === "center_y"); - const centerZWidget = node.widgets?.find(w => w.name === "center_z"); - - const matrixStringWidget = node.widgets?.find(w => w.name === "matrix_string"); - - log("Found parameter widgets:", { - translate_x: !!translateXWidget, - translate_y: !!translateYWidget, - translate_z: !!translateZWidget, - rotate_x: !!rotateXWidget, - rotate_y: !!rotateYWidget, - rotate_z: !!rotateZWidget, - scale_uniform: !!scaleUniformWidget, - scale_x: !!scaleXWidget, - scale_y: !!scaleYWidget, - scale_z: !!scaleZWidget, - mirror_axis: !!mirrorAxisWidget, - center_x: !!centerXWidget, - center_y: !!centerYWidget, - center_z: !!centerZWidget, - matrix_string: !!matrixStringWidget - }); - - // Function to update widget visibility based on selected operation - const updateWidgetVisibility = (selectedOperation) => { - log("Updating widget visibility for operation:", selectedOperation); - - // Hide all optional parameters first - hideWidget(node, translateXWidget); - hideWidget(node, translateYWidget); - hideWidget(node, translateZWidget); - hideWidget(node, rotateXWidget); - hideWidget(node, rotateYWidget); - hideWidget(node, rotateZWidget); - hideWidget(node, scaleUniformWidget); - hideWidget(node, scaleXWidget); - hideWidget(node, scaleYWidget); - hideWidget(node, scaleZWidget); - hideWidget(node, mirrorAxisWidget); - hideWidget(node, centerXWidget); - hideWidget(node, centerYWidget); - hideWidget(node, centerZWidget); - hideWidget(node, matrixStringWidget); - - // Show relevant parameters based on operation - if (selectedOperation === "translate") { - showWidget(node, translateXWidget); - showWidget(node, translateYWidget); - showWidget(node, translateZWidget); - } else if (selectedOperation === "rotate") { - showWidget(node, rotateXWidget); - showWidget(node, rotateYWidget); - showWidget(node, rotateZWidget); - } else if (selectedOperation === "scale") { - showWidget(node, scaleUniformWidget); - showWidget(node, scaleXWidget); - showWidget(node, scaleYWidget); - showWidget(node, scaleZWidget); - } else if (selectedOperation === "mirror") { - showWidget(node, mirrorAxisWidget); - } else if (selectedOperation === "center") { - showWidget(node, centerXWidget); - showWidget(node, centerYWidget); - showWidget(node, centerZWidget); - } else if (selectedOperation === "align_to_axes") { - // No parameters needed - all hidden - } else if (selectedOperation === "apply_matrix") { - showWidget(node, matrixStringWidget); - } - - updateNodeSize(node); - }; - - // Store original callback - const origCallback = operationWidget.callback; - - // Override callback to update visibility when operation changes - operationWidget.callback = function(value) { - log("Operation changed to:", value); - - // Call original callback if it exists - const result = origCallback?.apply(this, arguments); - - // Update widget visibility - updateWidgetVisibility(value); - - return result; - }; - - // Initialize visibility on node creation - log("Initializing widget visibility..."); - updateWidgetVisibility(operationWidget.value); - } -}); - -log("GeomPack transform dynamic parameters extension loaded"); diff --git a/web/js/uv_unwrap_dynamic.js b/web/js/uv_unwrap_dynamic.js deleted file mode 100644 index 840bc13f..00000000 --- a/web/js/uv_unwrap_dynamic.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * ComfyUI-GeometryPack UV Unwrap Dynamic Parameter Management - * - * This extension manages the visibility of method-specific parameters - * in the UV Unwrap node based on the selected method. - */ - -import { app } from "../../../scripts/app.js"; - -const DEBUG = false; // Set to true to enable console logging - -function log(...args) { - if (DEBUG) { - console.log("[GeomPack-UVUnwrap]", ...args); - } -} - -// Helper function to hide a widget (removes from array) -function hideWidget(node, widget) { - if (!widget) return; - if (widget._hidden) { - log("Widget already hidden:", widget.name); - return; - } - - log("Hiding widget:", widget.name); - - const index = node.widgets.indexOf(widget); - if (index === -1) { - log(" ERROR: Widget not found in array!"); - return; - } - - // Store original properties for restoration - if (!widget.origType) { - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.origSerializeValue = widget.serializeValue; - } - - widget._originalIndex = index; - widget._hidden = true; - - // Remove widget from array - node.widgets.splice(index, 1); - - log(" Widget removed from index:", index); - - // Hide linked widgets if any - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => hideWidget(node, w)); - } -} - -// Helper function to show a widget (re-inserts into array) -function showWidget(node, widget) { - if (!widget) return; - if (!widget._hidden) { - log("Widget already visible:", widget.name); - return; - } - - log("Showing widget:", widget.name); - - // Restore original properties - if (widget.origType) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - if (widget.origSerializeValue) { - widget.serializeValue = widget.origSerializeValue; - } - } - - // Re-insert widget at original position - const insertIndex = Math.min(widget._originalIndex, node.widgets.length); - node.widgets.splice(insertIndex, 0, widget); - - log(" Widget restored to index:", insertIndex); - - widget._hidden = false; - - // Show linked widgets if any - if (widget.linkedWidgets) { - widget.linkedWidgets.forEach(w => showWidget(node, w)); - } -} - -// Helper to update node size and redraw -function updateNodeSize(node) { - node.setDirtyCanvas(true, true); - if (app.graph) { - app.graph.setDirtyCanvas(true, true); - } - - requestAnimationFrame(() => { - const newSize = node.computeSize(); - node.setSize([node.size[0], newSize[1]]); - node.setDirtyCanvas(true, true); - - if (app.canvas) { - app.canvas.setDirty(true, true); - } - - requestAnimationFrame(() => { - if (app.canvas) { - app.canvas.draw(true, true); - } - log("Widget visibility update complete"); - }); - }); -} - -// Main extension registration -app.registerExtension({ - name: "geompack.uv_unwrap_dynamic", - - async nodeCreated(node) { - // Handle UV Unwrap node - if (node.comfyClass === "GeomPackUVUnwrap") { - setTimeout(() => { - this.setupUVUnwrapNode(node); - }, 100); - } - }, - - setupUVUnwrapNode(node) { - log("Setting up GeomPackUVUnwrap node"); - - // Find the method selector widget - const methodWidget = node.widgets?.find(w => w.name === "method"); - if (!methodWidget) { - log("ERROR: Method widget not found!"); - return; - } - - log("Method widget found, current value:", methodWidget.value); - - // Find all parameter widgets - const iterationsWidget = node.widgets?.find(w => w.name === "iterations"); - const angleLimitWidget = node.widgets?.find(w => w.name === "angle_limit"); - const islandMarginWidget = node.widgets?.find(w => w.name === "island_margin"); - const scaleToBoundsWidget = node.widgets?.find(w => w.name === "scale_to_bounds"); - const cubeSizeWidget = node.widgets?.find(w => w.name === "cube_size"); - const cylinderRadiusWidget = node.widgets?.find(w => w.name === "cylinder_radius"); - - log("Found parameter widgets:", { - iterations: !!iterationsWidget, - angle_limit: !!angleLimitWidget, - island_margin: !!islandMarginWidget, - scale_to_bounds: !!scaleToBoundsWidget, - cube_size: !!cubeSizeWidget, - cylinder_radius: !!cylinderRadiusWidget - }); - - // Function to update widget visibility based on selected method - const updateWidgetVisibility = (selectedMethod) => { - log("Updating widget visibility for method:", selectedMethod); - - // Hide all optional parameters first - hideWidget(node, iterationsWidget); - hideWidget(node, angleLimitWidget); - hideWidget(node, islandMarginWidget); - hideWidget(node, scaleToBoundsWidget); - hideWidget(node, cubeSizeWidget); - hideWidget(node, cylinderRadiusWidget); - - // Show relevant parameters based on method - if (selectedMethod === "xatlas" || - selectedMethod === "libigl_lscm" || - selectedMethod === "libigl_harmonic") { - // No parameters for these methods - log(" No parameters needed for", selectedMethod); - } else if (selectedMethod === "libigl_arap") { - showWidget(node, iterationsWidget); - } else if (selectedMethod === "blender_smart") { - showWidget(node, angleLimitWidget); - showWidget(node, islandMarginWidget); - showWidget(node, scaleToBoundsWidget); - } else if (selectedMethod === "blender_cube") { - showWidget(node, cubeSizeWidget); - showWidget(node, scaleToBoundsWidget); - } else if (selectedMethod === "blender_cylinder") { - showWidget(node, cylinderRadiusWidget); - showWidget(node, scaleToBoundsWidget); - } else if (selectedMethod === "blender_sphere") { - showWidget(node, scaleToBoundsWidget); - } - - updateNodeSize(node); - }; - - // Store original callback - const origCallback = methodWidget.callback; - - // Override callback to update visibility when method changes - methodWidget.callback = function(value) { - log("Method changed to:", value); - - // Call original callback if it exists - const result = origCallback?.apply(this, arguments); - - // Update widget visibility - updateWidgetVisibility(value); - - return result; - }; - - // Initialize visibility on node creation - log("Initializing widget visibility..."); - updateWidgetVisibility(methodWidget.value); - } -}); - -log("GeomPack UV Unwrap dynamic parameters extension loaded");