sparing javascript lines

This commit is contained in:
Andrea Pozzetti
2026-02-12 22:16:40 +01:00
parent b4767f5f99
commit fa1933dc4f
13 changed files with 212 additions and 866 deletions
+11 -11
View File
@@ -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"]},
}),
}
}
+2 -2
View File
@@ -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"]},
}),
},
}
@@ -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,
+38 -15
View File
@@ -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"]},
}),
}
}
+22 -10
View File
@@ -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"]},
}),
}
}
+46 -75
View File
@@ -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()
generate_widget_mappings()
copy_example_assets()
+1
View File
@@ -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",
+1
View File
@@ -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
-44
View File
@@ -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"
}
}
+85 -60
View File
@@ -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");
-183
View File
@@ -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");
-250
View File
@@ -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");
-214
View File
@@ -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");