From 7a8f881e648f72aff65bcd22f5b32cc8a07a11f5 Mon Sep 17 00:00:00 2001 From: PozzettiAndrea Date: Tue, 16 Jun 2026 18:42:22 +0200 Subject: [PATCH] Remesh blender_sharp: expose remove-disconnected cull, fix ranges, clearer tooltips blender_sharp = Blender's Remesh modifier in SHARP mode (octree dual-contouring, watertight output, reconstructs sharp edges). - Expose use_remove_disconnected + threshold as remove_disconnected (default true, matching Blender) + disconnected_threshold. Previously unset, so Blender's defaults silently culled small disconnected pieces; now it's controllable. Verified: true culls a floating cube (1 component), false keeps it (2 components). - sharpness max 5.0 -> 2.0 (Blender's normal slider range; higher just over-sharpens). - octree_depth max 10 -> 12 (Blender's real max), scale max 1.0 -> 0.99 (real max). - Rewrite octree_depth/scale/sharpness tooltips with concrete meaning (power-of-2 face growth, octree fit, sharpness behavior). Co-Authored-By: Claude Opus 4.8 --- nodes/blender_remeshing/backends/sharp.py | 17 ++++++++++++----- nodes/remeshing/remesh.py | 8 +++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/nodes/blender_remeshing/backends/sharp.py b/nodes/blender_remeshing/backends/sharp.py index c58bfc3d..9b59e867 100644 --- a/nodes/blender_remeshing/backends/sharp.py +++ b/nodes/blender_remeshing/backends/sharp.py @@ -25,9 +25,11 @@ class RemeshBlenderSharpNode(io.ComfyNode): is_output_node=True, inputs=[ io.Custom("TRIMESH").Input("trimesh"), - io.Int.Input("octree_depth", default=6, min=1, max=10, step=1, tooltip="Resolution. Higher = more detail, more faces."), - io.Float.Input("scale", default=0.9, min=0.0, max=1.0, step=0.05, display_mode="number", tooltip="Ratio of output size to input bounding box."), - io.Float.Input("sharpness", default=1.0, min=0.0, max=5.0, step=0.1, display_mode="number", tooltip="Edge sharpness for Sharp mode."), + io.Int.Input("octree_depth", default=6, min=1, max=12, step=1, tooltip="Octree resolution -- the detail knob. Power of 2: each +1 roughly QUADRUPLES face count and halves voxel size. 6 is a sane start; 8-9 is high detail; 10+ can be very heavy."), + io.Float.Input("scale", default=0.9, min=0.0, max=0.99, step=0.05, display_mode="number", tooltip="Octree fit relative to the bounding box (0-0.99). Higher = grid hugs the mesh tighter = finer effective resolution; too close to 1.0 can clip the outer shell. 0.9 default."), + io.Float.Input("sharpness", default=1.0, min=0.0, max=2.0, step=0.1, display_mode="number", tooltip="How aggressively dual-contouring snaps to sharp edges/corners. Higher = crisper edges but can spike on noisy input; lower = rounder. 0-2 is Blender's normal slider range (1.0 default); capped at 2 here since higher just over-sharpens."), + io.Combo.Input("remove_disconnected", options=["true", "false"], default="true", tooltip="Delete small disconnected (floating) pieces after remeshing. ON by default (matches Blender)."), + io.Float.Input("disconnected_threshold", default=1.0, min=0.0, max=1.0, step=0.05, display_mode="number", tooltip="Size cutoff for removal, relative to the largest component. Higher = remove more aggressively (1.0 ~ keep only the main body); lower = keep more; 0 = keep everything. Only used when remove_disconnected is on."), ], outputs=[ io.Custom("TRIMESH").Output(display_name="remeshed_mesh"), @@ -36,12 +38,14 @@ class RemeshBlenderSharpNode(io.ComfyNode): ) @classmethod - def execute(cls, trimesh, octree_depth=6, scale=0.9, sharpness=1.0): + def execute(cls, trimesh, octree_depth=6, scale=0.9, sharpness=1.0, + remove_disconnected="true", disconnected_threshold=1.0): import bpy log.info("Backend: blender_sharp") log.info("Input: %d vertices, %d faces", len(trimesh.vertices), len(trimesh.faces)) - log.info("Parameters: octree_depth=%d, scale=%s, sharpness=%s", octree_depth, scale, sharpness) + log.info("Parameters: octree_depth=%d, scale=%s, sharpness=%s, remove_disconnected=%s, threshold=%s", + octree_depth, scale, sharpness, remove_disconnected, disconnected_threshold) obj, mesh = _bpy_setup_object( np.asarray(trimesh.vertices, dtype=np.float32), @@ -52,6 +56,8 @@ class RemeshBlenderSharpNode(io.ComfyNode): mod.octree_depth = octree_depth mod.scale = scale mod.sharpness = sharpness + mod.use_remove_disconnected = (remove_disconnected == "true") + mod.threshold = disconnected_threshold bpy.ops.object.modifier_apply(modifier="Remesh") result = _bpy_extract_and_cleanup(obj) @@ -63,6 +69,7 @@ class RemeshBlenderSharpNode(io.ComfyNode): remeshed_mesh.metadata = trimesh.metadata.copy() remeshed_mesh.metadata['remeshing'] = { 'algorithm': 'blender_sharp', 'octree_depth': octree_depth, 'scale': scale, 'sharpness': sharpness, + 'remove_disconnected': remove_disconnected == "true", 'disconnected_threshold': disconnected_threshold, } log.info("Output: %d vertices, %d faces", len(remeshed_mesh.vertices), len(remeshed_mesh.faces)) diff --git a/nodes/remeshing/remesh.py b/nodes/remeshing/remesh.py index f49a5890..c6ca0f2b 100644 --- a/nodes/remeshing/remesh.py +++ b/nodes/remeshing/remesh.py @@ -131,9 +131,11 @@ class RemeshNode(io.ComfyNode): io.Float.Input("voxel_size", default=1, min=0.001, max=1.0, step=0.01, display_mode="number", tooltip="Voxel size. Smaller = more detail. Output is always watertight."), ]), io.DynamicCombo.Option("blender_sharp", [ - io.Int.Input("octree_depth", default=6, min=1, max=10, step=1, tooltip="Resolution. Higher = more detail, more faces."), - io.Float.Input("scale", default=0.9, min=0.0, max=1.0, step=0.05, display_mode="number", tooltip="Ratio of output size to input bounding box."), - io.Float.Input("sharpness", default=1.0, min=0.0, max=5.0, step=0.1, display_mode="number", tooltip="Edge sharpness."), + io.Int.Input("octree_depth", default=6, min=1, max=12, step=1, tooltip="Octree resolution -- the detail knob. Power of 2: each +1 roughly QUADRUPLES face count and halves voxel size. 6 is a sane start; 8-9 is high detail; 10+ can be very heavy."), + io.Float.Input("scale", default=0.9, min=0.0, max=0.99, step=0.05, display_mode="number", tooltip="Octree fit relative to the bounding box (0-0.99). Higher = grid hugs the mesh tighter = finer effective resolution; too close to 1.0 can clip the outer shell. 0.9 default."), + io.Float.Input("sharpness", default=1.0, min=0.0, max=2.0, step=0.1, display_mode="number", tooltip="How aggressively dual-contouring snaps to sharp edges/corners. Higher = crisper edges but can spike on noisy input; lower = rounder. 0-2 is Blender's normal slider range (1.0 default); capped at 2 here since higher just over-sharpens."), + io.Combo.Input("remove_disconnected", options=["true", "false"], default="true", tooltip="Delete small disconnected (floating) pieces after remeshing. ON by default (matches Blender)."), + io.Float.Input("disconnected_threshold", default=1.0, min=0.0, max=1.0, step=0.05, display_mode="number", tooltip="Size cutoff for removal, relative to the largest component. Higher = remove more aggressively (1.0 ~ keep only the main body); lower = keep more; 0 = keep everything. Only used when remove_disconnected is on."), ]), io.DynamicCombo.Option("blender_blocks", [ io.Int.Input("octree_depth", default=6, min=1, max=10, step=1, tooltip="Resolution. Higher = more detail, more faces."),