Files
MachinePainting 3a574494eb Add short DESCRIPTION text to all 41 nodes
None of the nodes showed description text in ComfyUI because ComfyUI
reads obj_class.DESCRIPTION when it builds node_info - it does not read
the class docstring, which is what these nodes had.

Every registered node now carries a one-line DESCRIPTION, kept to
29-67 characters so it fits the limited space in ComfyUI Manager and
the node search results. Image Transform Pro's four-sentence
description is shortened to match.

Descriptions are derived from each node's existing docstring, or from
the README for the five classes that had no docstring
(ImageBlendPro, ColorMatchBlend and the three Boolean switch nodes).

README also updated for the recent changes: the New Nodes table now
lists the real "Rotate & Scale" category, the XY placement direction is
documented on both nodes (positive X right, positive Y up), and the
v2.2.0 changelog records the placement convention, the node outputs and
the MP_ImageTransform internal name.
2026-09-03 03:32:00 -07:00

139 lines
4.5 KiB
Python

import torch
import numpy as np
import cv2
class _MaskCompositeBase:
"""Base class for Mask Composite Pro variants."""
MAX_MASKS = 2 # Override in subclasses
@classmethod
def INPUT_TYPES(cls):
n = cls.MAX_MASKS
select_options = ["all"] + [str(i) for i in range(1, n + 1)]
inputs = {
"required": {
"mask_1": ("MASK",),
"output_select": (select_options, {"default": "all"}),
"invert_output": ("BOOLEAN", {"default": False, "label_on": "inverted", "label_off": "normal"}),
},
"optional": {}
}
for i in range(2, n + 1):
inputs["optional"][f"mask_{i}"] = ("MASK",)
for i in range(1, n + 1):
inputs["optional"][f"blur_mask_{i}"] = ("FLOAT", {
"default": 0.0,
"min": 0.0,
"max": 50.0,
"step": 0.5,
"display": "slider",
})
inputs["optional"][f"grow_shrink_mask_{i}"] = ("INT", {
"default": 0,
"min": -100,
"max": 100,
"step": 1,
"display": "slider",
})
inputs["optional"][f"opacity_mask_{i}"] = ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "slider",
})
return inputs
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("mask",)
FUNCTION = "composite"
CATEGORY = "MachinePaintingNodes/Mask"
def composite(self, mask_1, output_select="all", invert_output=False, **kwargs):
n = self.MAX_MASKS
masks_in = [mask_1] + [kwargs.get(f"mask_{i}") for i in range(2, n + 1)]
blurs = [kwargs.get(f"blur_mask_{i}", 0.0) for i in range(1, n + 1)]
grow_shrinks = [kwargs.get(f"grow_shrink_mask_{i}", 0) for i in range(1, n + 1)]
opacities = [kwargs.get(f"opacity_mask_{i}", 1.0) for i in range(1, n + 1)]
m1 = self._to_numpy(mask_1)
h, w = m1.shape[:2]
processed = []
for i in range(n):
if masks_in[i] is None:
processed.append(None)
continue
m = self._to_numpy(masks_in[i])
if m.shape[:2] != (h, w):
m = cv2.resize(m, (w, h), interpolation=cv2.INTER_LINEAR)
m = self._grow_shrink(m, grow_shrinks[i])
m = self._blur(m, blurs[i])
m = m * opacities[i]
m = np.clip(m, 0.0, 1.0)
processed.append(m)
if output_select != "all":
idx = int(output_select) - 1
if idx < len(processed) and processed[idx] is not None:
result = processed[idx]
else:
result = np.zeros((h, w), dtype=np.float32)
else:
result = np.zeros((h, w), dtype=np.float32)
for m in processed:
if m is not None:
result = 1.0 - (1.0 - result) * (1.0 - m)
result = np.clip(result, 0.0, 1.0)
if invert_output:
result = 1.0 - result
return (torch.from_numpy(result).unsqueeze(0),)
def _to_numpy(self, mask_tensor):
if len(mask_tensor.shape) == 3:
return mask_tensor[0].cpu().numpy().astype(np.float32)
return mask_tensor.cpu().numpy().astype(np.float32)
def _grow_shrink(self, mask, amount):
if amount == 0:
return mask
kernel_size = abs(amount) * 2 + 1
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
mask_u8 = (mask * 255).astype(np.uint8)
if amount > 0:
mask_u8 = cv2.dilate(mask_u8, kernel, iterations=1)
else:
mask_u8 = cv2.erode(mask_u8, kernel, iterations=1)
return mask_u8.astype(np.float32) / 255.0
def _blur(self, mask, radius):
if radius <= 0:
return mask
ksize = max(int(radius * 2) | 1, 3)
return cv2.GaussianBlur(mask, (ksize, ksize), radius)
class MaskCompositePro2X(_MaskCompositeBase):
"""Combine 2 masks with per-mask blur, grow/shrink, and opacity."""
DESCRIPTION = "Combine 2 masks with per-mask blur, grow/shrink and opacity."
MAX_MASKS = 2
class MaskCompositePro6X(_MaskCompositeBase):
"""Combine up to 6 masks with per-mask blur, grow/shrink, and opacity."""
DESCRIPTION = "Combine up to 6 masks with per-mask blur, grow/shrink and opacity."
MAX_MASKS = 6