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 <noreply@anthropic.com>
This commit is contained in:
PozzettiAndrea
2026-06-16 18:42:22 +02:00
co-authored by Claude Opus 4.8
parent fd4e322174
commit 7a8f881e64
2 changed files with 17 additions and 8 deletions
+12 -5
View File
@@ -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))
+5 -3
View File
@@ -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."),