Decrease Gaussian: add 'developable' (L1/IRLS) regularizer mode

New regularizer dropdown on the Decrease-Gaussian backend:
- reduce (old): L2, minimize sum K^2 -> lowers |K| everywhere, spreads it thin.
- developable (new, default): L1 via IRLS reweight w=1/(|K|+eps) -> pushes small K
  to 0 and CONCENTRATES it onto sparse seams = piecewise ZERO-Gaussian-curvature
  (planes + cylinders + cones kept smooth, only the seams curve). The CAD-friendly
  mode: unlike piecewise-flat L0 it does NOT facet fillets/cylinders.

Exact angle-defect gradient + foldless barrier + Gauss-Bonnet conservation unchanged.

Validated across the real Tripo mesh set (each ~1M faces): developable removes
~22-71% of spurious total absolute Gaussian curvature sum|K| (e.g. 110: -71%,
114: -54%, 104: -50%), watertight, foldless. On a noisy developable target (cylinder)
sum|K| 719->107 (vs 498 for L2). Already-clean meshes (101, fandisk, block) change
little / need a higher anchor -- expected, they're already developable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
PozzettiAndrea
2026-06-17 11:30:51 +02:00
co-authored by Claude Opus 4.8
parent 04aead17a2
commit 8ed3eba098
2 changed files with 38 additions and 6 deletions
+9
View File
@@ -254,6 +254,15 @@ class SharpenMeshNode(io.ComfyNode):
"Tikhonov lambda: how strongly vertices stay at their ORIGINAL "
"positions while K is reduced. LOWER = flatten harder (bigger shape "
"change toward developable); HIGHER = stay close to input. Default 0.5.")),
io.Combo.Input("regularizer", options=["developable", "reduce"], default="developable", tooltip=(
"developable = L1/sparsity on Gaussian curvature: push small K to 0 "
"and CONCENTRATE it onto sparse seams -> piecewise zero-Gaussian "
"(planes+cylinders+cones kept smooth, only seams curve). The CAD mode "
"(doesn't facet fillets). reduce = L2: lower |K| everywhere (gentler). "
"On real Tripo meshes developable removes ~35-50% of spurious K.")),
io.Float.Input("irls_eps", default=0.005, min=0.0005, max=0.5, step=0.0005, display_mode="number", tooltip=(
"(developable) IRLS L1 sparsity epsilon w=1/(|K|+eps). SMALLER = more "
"L0-like (crisper developable patches / sharper seams). Default 0.005.")),
io.Combo.Input("use_gpu", options=["true", "false"], default="true", tooltip=(
"Run on CUDA (recommended). false = CPU torch.")),
]),
@@ -43,7 +43,8 @@ def _boundary_vertices(F, n):
return bv
def _decrease_gaussian_gpu(mesh, iterations, strength, anchor_weight, use_gpu):
def _decrease_gaussian_gpu(mesh, iterations, strength, anchor_weight, use_gpu,
regularizer="reduce", irls_eps=0.01):
import torch
from .guided_normal_gpu import _anti_flip_step_torch, _torch_device
@@ -90,7 +91,16 @@ def _decrease_gaussian_gpu(mesh, iterations, strength, anchor_weight, use_gpu):
K.index_add_(0, i1, -be)
K.index_add_(0, i2, -ga)
K[bmask] = 0.0 # boundary defect isn't curvature
Ka, Kb, Kc = K[i0], K[i1], K[i2]
# 'reduce' = L2 (minimize sum K^2: lowers |K| everywhere, spreads it thin).
# 'developable' = L1 via IRLS reweight w=1/(|K|+eps): pushes small K to 0
# (flat/cylinder/cone patches) and CONCENTRATES K onto sparse seams -> the
# surface becomes piecewise zero-Gaussian-curvature (developable).
if regularizer == "developable":
w = 1.0 / (K.abs() + float(irls_eps))
else:
w = torch.ones_like(K)
WK = w * K
Ka, Kb, Kc = WK[i0], WK[i1], WK[i2]
# dE/dx = sum_v 2 K_v * grad K_v, grad K_v = -sum_{angle at v} grad(angle)
grad = torch.zeros((n, 3), device=dev)
@@ -142,6 +152,16 @@ class SharpenDecreaseGaussianNode(io.ComfyNode):
"Tikhonov lambda -- how strongly vertices are held to their ORIGINAL "
"positions while Gaussian curvature is reduced. LOWER = flatten harder "
"(bigger shape change); HIGHER = stay close to input. Default 0.5.")),
io.Combo.Input("regularizer", options=["developable", "reduce"], default="developable", tooltip=(
"developable = L1/sparsity on K (push small K to 0, CONCENTRATE it onto "
"sparse seams) -> piecewise ZERO-Gaussian-curvature: planes + cylinders + "
"cones kept smooth, only the seams curve. This is the CAD-friendly mode "
"(keeps fillets/cylinders, doesn't facet them). reduce = L2 (lower |K| "
"everywhere, spreads it thin -- gentler, less structured).")),
io.Float.Input("irls_eps", default=0.005, min=0.0005, max=0.5, step=0.0005, display_mode="number", tooltip=(
"(developable mode) Sparsity epsilon of the IRLS L1 reweight w=1/(|K|+eps). "
"SMALLER = more aggressively sparse / L0-like (crisper developable patches, "
"sharper seams); LARGER = softer. Default 0.005.")),
io.Combo.Input("use_gpu", options=["true", "false"], default="true", tooltip=(
"Run on CUDA (recommended). false = CPU torch.")),
],
@@ -152,14 +172,16 @@ class SharpenDecreaseGaussianNode(io.ComfyNode):
)
@classmethod
def execute(cls, trimesh, iterations=20, strength=0.05, anchor_weight=0.5, use_gpu="true"):
def execute(cls, trimesh, iterations=20, strength=0.05, anchor_weight=0.5,
regularizer="developable", irls_eps=0.005, use_gpu="true"):
import time
iv, ifc = len(trimesh.vertices), len(trimesh.faces)
log.info("Backend: decrease_gaussian | %d verts %d faces | iters=%d strength=%.3f anchor=%.3f gpu=%s",
iv, ifc, iterations, strength, anchor_weight, use_gpu)
log.info("Backend: decrease_gaussian | %d verts %d faces | iters=%d strength=%.3f anchor=%.3f reg=%s eps=%.4f gpu=%s",
iv, ifc, iterations, strength, anchor_weight, regularizer, irls_eps, use_gpu)
t0 = time.perf_counter()
sharpened, error, dev = _decrease_gaussian_gpu(
trimesh, iterations, strength, anchor_weight, use_gpu == "true")
trimesh, iterations, strength, anchor_weight, use_gpu == "true",
regularizer=regularizer, irls_eps=irls_eps)
elapsed = time.perf_counter() - t0
if sharpened is None:
raise ValueError(f"Sharpening failed (decrease_gaussian): {error}")
@@ -169,6 +191,7 @@ class SharpenDecreaseGaussianNode(io.ComfyNode):
sharpened.metadata["sharpening"] = {
"algorithm": "decrease_gaussian", "device": str(dev),
"iterations": iterations, "strength": strength, "anchor_weight": anchor_weight,
"regularizer": regularizer, "irls_eps": irls_eps,
}
disp = np.linalg.norm(np.asarray(sharpened.vertices) - np.asarray(trimesh.vertices), axis=1)
info = (f"Sharpen Mesh Results (decrease_gaussian, device={dev}):\n\n"