All notable changes to ComfyUI-CustomNodePacks are documented here.
- Hidden multiline-string widgets no longer leak as giant textareas on top
of editor nodes. On modern ComfyUI, multiline
STRINGwidgets are backed by a real<textarea>DOM element parented to adiv.dom-widgetwrapper. Settingwidget.type = "hidden"was no longer enough to hide them — the DOM textarea kept rendering on top of the canvas and madePointsMaskEditor,SplineMaskEditorMEC,MECAdvancedPaintCanvas, andInpaintCompositeMEClook like a wall of empty text fields. The hide path now also setselement.style.display = "none"and collapses thedom-widgetwrapper so the visible UI matches what those widgets are supposed to be: invisible internal state. Affected widgets:PointsMaskEditor.editor_dataSplineMaskEditorMEC.spline_data/mask_color/mask_opacityMECAdvancedPaintCanvas.canvas_dataInpaintCompositeMECmode-conditional widgets (blend_mode_override,color_match,upscale_method,feather_edges,feather_radius)
- Verified end‐to‐end via Playwright: created PointsMaskEditor and
SplineMaskEditorMEC, captured pre/post screenshots, and exercised the
interactive editors. Points editor accepts left/right click for
positive/negative points and Ctrl-drag for bboxes —
editor_datapayload contains real coordinates with distinctlabel:1/label:0values (no hardcoded zeros). Spline editor accepts 4 control points and renders a smooth catmull-rom curve closed loop, not a polygon.
- SAM Multi-Mask Picker — render real per-mask thumbnails: the JS
widget previously called an empty
buildMaskThumbnails()stub and then drew a flat redfillRectover all three thumbnails identically, so every candidate looked the same regardless of what SAM produced. The Python node now serializes the three candidate masks as grayscale PNGs to ComfyUI's temp directory (stable input-hash filenames) and ships them on the UI payload asmask_thumbs. The JS now loads each PNG via/view?...&type=tempintothis.maskImages[i]and composites the real mask onto each thumbnail using an offscreen canvas withglobalCompositeOperation="source-in"plus a per-thumb tint. - SAM Multi-Mask Picker — widget no longer renders ~600 px below the
node body: the
addCustomWidgetregistration was forwardingnode.pos[0]andnode.pos[1] + widgetYinto the inner draw/mouse handlers, but LiteGraph already translates the canvas context to the node origin before invoking widget callbacks — the result was a doubled offset that pushed the picker off the bottom of the node. Registration now passes widget-local coordinates (0, widgetYfor draw;pos[0], pos[1]for mouse), so the picker lives inside the node body where it belongs. - SAM Multi-Mask Picker — stop force-rerunning SAM on every queue:
removed
IS_CHANGED → NaN, which was unconditionally invalidating the cache and re-running SAM on identical inputs. Standard cache semantics now apply.
- Latest-ComfyUI compatibility: removed deprecated
disable_noise=Falsekwarg from twocomfy.sample.sample_custom(…)call sites inMECBuilderSampler(mec_paint_suite.py). The argument was dropped fromcomfy.sample.sample_custom's signature in recent ComfyUI builds and was raisingTypeError: unexpected keyword argument 'disable_noise'at sample time. - Audited all 41 nodes against the current ComfyUI runtime: full pack
imports cleanly, no other deprecated APIs in use
(
prepare_noise,sampler_object,calculate_sigmas,OUTPUT_NODE,IS_CHANGEDall match current signatures).
- ComfyUI's V3 node API (
io.ComfyNode/define_schema/MatchType/Autogrow/DynamicCombo) is opt-in and additive — the V1 API used throughout this pack is fully supported and not scheduled for removal. No mass migration required.
- New
InpaintCompositeMECnode merges the previous two-node split (InpaintStitchProMEC+InpaintPasteBackMEC) behind a singlemodedropdown:stitch_pro— advanced blend pipeline (edge-aware, Laplacian pyramid, frequency, gaussian) with optional Reinhard color match.paste_back— clean resize + paste with optional Gaussian-feathered edges. Faster, deterministic, no blend pipeline.
- Unified output signature
(IMAGE, MASK, STRING). Inpaste_backmode the MASK is the paste rectangle (feathered if enabled) so downstream nodes always receive a usable mask. - New
js/inpaint_composite.jsextension hides parameters that don't apply to the selected mode (clean UI without losing widget state). - Internally delegates to the existing Stitch and PasteBack classes — no duplicated logic, no behavioural drift.
- Legacy
InpaintStitchProMECandInpaintPasteBackMECare still registered for backward compatibility, but their display labels now read "— legacy (MEC)" to steer new workflows toward the unified node. - README updated with a new "Inpaint Composite" section, mode-vs-mode guidance, and an updated reference table.
- New
video_stab_strength(0–1) widget onInpaintCropProMECactivates a two-stage filter cascade on the per-frame mask centroid trajectory:- EMA low-pass at
4 Hz × strength + 0.05cutoff to kill HF jitter while preserving subject drift. - Critically-damped spring (
k = 8·strength + 0.5,damping = 2·√k) integrated with semi-implicit Euler for inertia and a tiny natural overshoot.
- Pre-warm pass: the spring is run once on the first 12 frames, the
final
(pos, vel)state is captured, and the real pass is initialised from it — eliminating the snap-to-first-frame artefact that plagues naive spring filters.
- EMA low-pass at
- 99th-percentile center-displacement sizing (not full envelope): crop width = P99(detected widths) + (P99(cx) − P1(cx)) + 2·padding. Matches cinema operator framing — tight on the subject, ignoring outliers.
- New
video_stab_fps(FPS for filter time-base) andvideo_stab_paddingwidgets. stitch_dataschema bumped to v3 with newframe_offsetsfield (per-frame(x, y)paste positions in canvas-space). v2 remains fully supported byInpaintStitchProMECandInpaintPasteBackMEC.InpaintStitchProMEC._stitch_v2andInpaintPasteBackMEC.paste_backrewritten with a per-frame paste loop that consumesframe_offsetswhen present (v3) and falls back to the original single-position paste for v2 round-trips.- New helpers in
nodes/stabilization_utils.py:_median_filter_1d,_ema_lowpass,_spring_filter,ronin_filter_1d,compute_ronin_bbox_trajectory.
InpaintCropProMEC:crop_for_inpaintsignature gainsvideo_stab_strength,video_stab_fps,video_stab_padding(all defaulted; backward compatible).Step 8.6 crop_masknow emits a per-frame rectangle stack when Ronin is active.Step 10info string reports the active stabilization mode.
- Numerical: 11.13 px raw frame-to-frame jitter → 1.34 / 1.64 / 1.76 px at strength 0.3 / 0.7 / 1.0 (≥85% reduction); init-snap ≤ 1.2 px (warmup works).
- Round-trip: identity inpaint + paste-back is bit-perfect (mean abs diff = 0.0000) on 12-frame jittery video.
- Static-subject test: 0 px center spread on stationary subject with ±1 px detector noise.
- MECAdvancedPaintCanvas – interactive paint canvas (DOM widget) with
Nuke-style procedural mask math: raw alpha →
mask_hardnesscore threshold →mask_expansionmorphological dilate/erode → Gaussianmask_blur_radiuslerped against the hard mask bymask_blur_strength. Outputs the alpha-composited painted image plus the processed mask. - MECContextInpainter – smart blend-back of an inpainted image. Per-channel
Reinhard colour match, CIE LAB lightness rescue when the inpainted region
is >5% darker, optional differential-diffusion preservation weight from
|orig − inpaint|, plus[SEP]/[SKIP]/[ASC]/[DSC]wildcard parsing across connected mask regions. - MECToneRefiner – percentile-based black/white-point tone curve
(smoothstep), gray-world colour balance, optional bicubic upscale and
centre-focus fake DOF driven by
ai_dof_focus_depth. - MECBuilderSampler – KSampler with adaptive CFG (
Constant/Linear/Ease Downviaset_model_sampler_cfg_functionkeyed on per-step sigma) and an optional 2-step self-correction polish pass at low denoise. js/mec_advanced_paint.js– RGBA canvas widget with L-paint / R-erase, linear-interpolated stamping (no dotted strokes on fast drags), brush cursor mirroring size + hardness, mouse-wheel brush sizing, base64-PNG serialisation into a hiddencanvas_datawidget so the Python node can decode the user's drawing.
- FolderIncrementer (JS) – removed the
isInputLoaderblock that was preventing filenames fromLoadImage/LoadVideo/VHS_LoadVideofrom being read. Loaders are now exactly the nodes whose filenames feed the versioning folder name. - FolderIncrementer (JS) – added
source_choicerouting (auto/image/video) andtrigger_image/trigger_videoinputs. Inautomode video is preferred over image when both are connected. Re-syncs on disconnect and onsource_choicechange. - Points & BBox Mask Editor – fixed canvas auto-zoom-in/out jitter
on first image load and on graph execution. The double-pass
fitView(rAF + 300 ms) was measuring a stale bounding rect from a pre-reflow layout, then snapping to the correct rect. Now a single deferred fit (two animation frames) runs after LiteGraph reflow has settled. - Points & BBox Mask Editor –
_hasAutoFittedis no longer reset on reference-image disconnect, so brief upstream disconnects keep the user's zoom/pan instead of crazy-refitting on reconnect. - Spline Mask Editor – wheel-zoom is hard-clamped (80 px ↔ 8 000 px) to prevent the preview bounds from collapsing or exploding after a few scroll events. Auto-fit only re-runs when the underlying image dimensions change, not on every preview update.
- VAEMergeMEC – merge two (or three) VAEs with 8 algorithms
(weighted_sum, add_difference, tensor_sum, triple_sum, slerp, dare_ties,
block_swap, clamp_interp). Per-block alpha overrides via JSON or
comma list, brightness/contrast post-tuning on
decoder.conv_out, CPU-side merge in float32, architecture detection (sd1x / sdxl / flux / mochi). Returns the merged VAE plus a JSON info report. - VAELatentInspectorMEC – per-channel min/max/mean/std/abs_mean
statistics, NaN/Inf counts, and a
verdictof corrupt / saturated / low_contrast / healthy. Optionalfail_on_corruptto raise hard. - VAESimilarityAnalyserMEC – cosine similarity between two VAEs globally and per block, with optional per-tensor breakdown.
- VAEBlockInspectorMEC – per-block weight statistics for any VAE (mean / std / abs_mean / count).
- ColorSpaceConvertMEC – sRGB ↔ Linear ↔ Rec.709 ↔ ACEScg via built-in matrices and OETFs. No PyOpenColorIO required.
- LUTApplyMEC – Adobe
.cubeLUT loader (1D and 3D) with trilinear interpolation in pure torch and a strength blend. - ExposureGradeMEC – exposure (stops), white-balance (temp / tint), and contrast around a configurable mid-grey pivot. Operates in linear by default with sRGB encode/decode round-trip.
- LoadEXRMEC, SaveEXRMEC – EXR read/write with OpenEXR-first, imageio-fallback, and final 16-bit TIFF fallback. Half-float by default.
- EXRMetadataReaderMEC – pure-python EXR header parser when OpenEXR isn't installed; surfaces width/height/channels/compression.
- MetadataWriterMEC – write/merge JSON sidecars next to image batches.
- ShotMetadataNodeMEC – read shot.json descriptor (show / shot / task / frame_in / frame_out / fps).
- FrameRangeRouterMEC – slice IMAGE/MASK batches by
[start:end:step]with negative-index support. - BatchVersionManagerMEC –
<root>/<show>/<shot>/<task>/v###/layout with safe sanitization and atomic version reservation via lockfile.
- MergeRenderPassesMEC – composite beauty + diffuse / specular / emission / AO with independent gains.
- DepthOfFieldMaskMEC – depth → CoC mask with focus_distance and aperture controls; emits both CoC and in-focus masks.
- DepthWarpMEC – horizontal parallax warp driven by a depth pass (cheap stereo synthesis).
- NormalToCurvatureMEC – curvature mask from a tangent-space normal pass via central-difference divergence.
- PositionPassSplitterMEC – split a world-position pass into per-axis X / Y / Z masks (auto- or manually-ranged).
- GrainMatchMEC – extract grain (reference − denoise(reference)) and re-apply it to a target plate, with seeded per-frame sampling.
- PlateStabilizerMEC – ORB+RANSAC affine stabilization (cv2) with FFT-translation fallback when cv2 isn't available.
- CleanPlateExtractorMEC – pixelwise median across a batch with optional mask exclusion of foreground samples.
- DifferenceMatteMEC – L1/L2 difference matte with threshold and softness.
- TemporalConsistencyCheckerMEC – flicker / instability score across a video batch using mask_iou, pixel_diff, or Farneback flow_warp.
- ModelMetadataExtractorMEC – inspect safetensors / checkpoint files
without ever unpickling. SHA256 fingerprint on size + first/last 1MB,
bounded
pickletools.disfor legacy pickles.
- FolderIncrementerMEC – added
folder_name_overrideSTRING andreserve_versionBOOLEAN. Names are sanitized against Windows reserved names and illegal characters. Outputs use POSIX-style paths. - SAMModelLoaderMEC – added
original_devicetracking and module helpers (move_to_inference_device,restore_device) for safe CPU↔GPU shuffling on offload-enabled wrappers. - MattingNodeMEC –
auto_downloadswitch + four-step fallback chain: vitmatte_base → vitmatte_small → cv2 guided filter → torch Gaussian. - UnifiedSegmentationNode – added
force_mode(auto / image / video).
- DrawShapeMEC – unified 12-shape drawing node with a single dropdown. All parameters exposed as named inputs with descriptive tooltips — no more raw JSON editing. Replaces the 5 legacy per-shape wrapper nodes.
- SplineMaskEditorMEC (JS rewrite) – complete rewrite of the spline
editor following Olm SplineMask patterns:
- Normalized [0,1] coordinates (resolution-independent)
- Segment insertion via Ctrl+click near curve
- Close path by clicking first point (highlighted orange)
- Right-click context menu (Delete, Open/Close, Smooth/Sharp)
- Zoom-relative point sizes and fonts
- Property-based persistence with backward-compatible deserialization
- Status bar with keyboard hints
- DrawCircleMEC, DrawRectangleMEC, DrawEllipseMEC, DrawPolygonMEC, DrawLineMEC – deprecated, now thin wrappers around DrawShapeMEC. Kept for backward compatibility with existing workflows.
- Node count updated: 38 → 47 (44 MEC + 3 FolderIncrementer).
- README, docs, and project structure updated for all new nodes.
- SplineMaskEditorMEC – interactive spline mask drawing with Catmull-Rom, Bezier, and polyline modes. Outputs mask, SAM-compatible coords, and SPLINE_DATA for downstream chaining.
- MotionMaskTrackerMEC – per-frame motion detection with 4 methods (pixel diff, optical flow, background subtraction, histogram diff), camera stabilization (homography/affine/translation), and post-processing.
- BBoxSmooth – smooth bounding-box sequences across video frames using moving-average or exponential smoothing.
- stabilization_utils.py – shared camera stabilization helpers for motion tracker and inpaint suite.
- InpaintSuite – uses stabilization_utils for camera-stable cropping.
- BBoxNodes – added BBoxSmooth to the 5 existing BBox nodes.
- InpaintCropProMEC – Canvas Expansion – crops near image edges now
extend beyond bounds via
F.pad(mode="replicate"), producing perfectly centered crops everywhere. Ported from lquesada/ComfyUI-Inpaint-CropAndStitch. - InpaintCropProMEC – optional_context_mask – new optional input: union of context mask bbox with crop bbox to include surrounding context.
- InpaintCropProMEC – Iterative Hole-Filling –
_fill_mask_holesrewritten with 14-threshold iterative approach using scipybinary_closing+binary_fill_holes(with pure-torch fallback). Preserves gradient values in soft masks. - Stitch Data v2 Format – new coordinate system:
ctc_x/y/w/h(crop-to-canvas) +cto_x/y/w/h(canvas-to-original) for perfect reversal of canvas expansion in stitching.
- InpaintStitchProMEC – v1/v2 dispatch: v2 uses canvas coordinates for seamless compositing of expanded crops; v1 fallback preserved for backward compatibility with existing workflows.
- InpaintPasteBackMEC – updated for v2 stitch format with canvas coordinate handling; v1 fallback preserved.
- Points Editor (JS) –
computeSizeoverride prevents LiteGraph relayout jitter; widget height computed from actual other-widget heights instead of magic number; locked dimensions after image load. - Image Comparer (JS) –
computeSizeoverride prevents resize jitter after image comparison loads.
- unified_segmentation.py – marked as DEPRECATED (dead module superseded by unified_segmentation_node.py + model_manager.py). Prevents confusion from divergent MODEL_REGISTRY.
- InpaintCropProMEC – 3 new mask preprocessing inputs from original
InpaintCropAndStitch:
mask_invert(flip inpaint/keep regions),mask_fill_holes(flood-fill enclosed gaps),mask_hipass_filter(threshold out near-transparent noise). - MaskBatchManager – 2 new temporal operations for video workflows:
smooth_temporal(Gaussian blur along time axis to reduce flicker) andreduce_flicker(median filter across frames to remove outliers). - BBoxSmooth (MEC) – new node to smooth bounding-box sequences across video frames using moving-average or exponential smoothing, eliminating jitter in tracked crops.
- InpaintCropProMEC – crop centering rewrite: bbox now expands symmetrically around the mask center, then clamps to image bounds. Fixes asymmetric off-center crops when the mask is near image edges.
- Points Mask Editor (JS) – complete toolbar UI overhaul: brighter
button colors, 11 px font, 36 px toolbar height,
textBaseline="middle"vertical centering, 120 ms active-state flash on click, wider separators, higher-opacity pills. - Points Mask Editor (Python) – bbox rendering rewritten to use soft
Gaussian-edged brushes: positive bboxes use
torch.max(mask, brush)(additive, preserves soft point edges), negative bboxes use multiplicative erase. Replaces hardmask = 1.0overwrite that destroyed soft blending.
- utils.py –
multi_scale_guided_refineandcolor_aware_refinenow handle 2D grayscale input arrays (was IndexError whenimg_np.ndim == 2). - model_manager.py – float8 dtype comparison guarded with
hasattr(torch, "float8_e4m3fn")to prevent AttributeError on PyTorch < 2.1. - Points Editor (JS) – fixed 4 memory leaks:
requestAnimationFramenow cancelled inonRemoved, resize debounce timer cleared, keyboard event listener properly removed, complete cleanup chain on node deletion. - InpaintCropProMEC –
_resize_lanczosfix for single-channel masks: squeeze channel dim to 2D for PIL, restore after (was crashing withCannot handle this data type: (1, 1, 1), |u1).
- InpaintPasteBackMEC – new lightweight node to paste inpainted crop back onto the original image using stitch_data, with optional Gaussian-feathered alpha blending at the crop boundary.
- InpaintCropProMEC –
downscale_methodandupscale_methodinputs with 5 interpolation modes: lanczos (PIL-based, highest quality), bicubic, bilinear, nearest-exact, area. Upscale method is stored in stitch_data and automatically used by InpaintStitchProMEC.
- InpaintCropProMEC –
padding_multiplenow enforces step=2, min=2 to guarantee even-valued padding (required by many tiled/diffusion models). - Image Comparer (JS) – mode labels now use Unicode icons (◧ Compare, ⊕ Overlay, ≠ Diff); labels fade during drag; divider grip uses a dot-grid pattern; overlay scrubber upgraded to rounded bar + circular handle.
- InpaintStitchProMEC – now reads
upscale_methodfrom stitch_data (defaulting to lanczos) instead of always using bilinear.
- ImageComparerMEC – new interactive before/after comparison node with three view modes: drag-slider, adjustable-opacity overlay, and amplified difference heatmap. Rendered entirely in-node via a DOM canvas widget.
- MaskDrawFrame – 7 new shapes: triangle, star, diamond, cross, rounded_rectangle, heart, arrow (total: 12 shapes).
- MaskDrawFrame –
rotationparameter (−360 ° to +360 °) applies to every shape including the original five. - SAMMaskGeneratorMEC –
negative_text_promptinput: describe what to exclude; GroundingDINO detects the region and injects negative points into SAM inference. - InpaintCropProMEC –
downscale_factor(0.25–1.0) to shrink the crop before sending to the inpainting model and automatically upscale on stitch. - InpaintCropProMEC –
mask_blur/mask_growpre-processing: blur and morphologically dilate/erode the mask before computing the crop region. - InpaintCropProMEC –
aspect_ratioselector with 12 presets (1:1, 4:3, 16:9, …) plus custom ratio; crop region is adjusted to match the chosen AR. - InpaintCropProMEC – 6th output
crop_mask: binary mask showing the cropped region in original image space.
- Points/BBox Editor (JS) – eliminated canvas jitter caused by
updateEditorSize()re-entrantly callingnode.setSize()in a ResizeObserver loop. Added 50 ms debounce, integer-snap for pixel positions, and a re-entrance guard. - Points/BBox Editor (JS) – toolbar separators and pill buttons now render at integer pixel positions, removing sub-pixel aliasing artefacts.
- Model Manager –
.safetensorsformat is now preferred over.pt/.pth/.binwhen a safetensors variant exists locally. SAM3 (sam3.pt) is explicitly excluded from this mapping. - InpaintCropProMEC – crop dimensions are now snapped up to the nearest
padding_multipleto avoid size mismatches with tiled models.
- GitHub Actions publish workflow: added
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24env var and fork guard. - Added missing
MIT-Licensefile required by PyPI metadata.
- README overview table expanded from 2 to 4 packs.