Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
459
custom_nodes/ComfyUI-Impact-Pack/js/mask-rect-area-advanced.js
Normal file
459
custom_nodes/ComfyUI-Impact-Pack/js/mask-rect-area-advanced.js
Normal file
@@ -0,0 +1,459 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { readLinkedNumber, getDrawColor, computeCanvasSize } from "./common.js";
|
||||
function showPreviewCanvas(node, app) {
|
||||
|
||||
const widget = {
|
||||
type: "customCanvas",
|
||||
name: "mask-rect-area-canvas",
|
||||
get value() {
|
||||
return this.canvas.value;
|
||||
},
|
||||
set value(x) {
|
||||
this.canvas.value = x;
|
||||
},
|
||||
draw: function (ctx, node, widgetWidth, widgetY) {
|
||||
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
if (!node.canvasHeight) {
|
||||
computeCanvasSize(node, node.size, 220, 240);
|
||||
}
|
||||
|
||||
const visible = true;
|
||||
const t = ctx.getTransform();
|
||||
const margin = 12;
|
||||
const border = 2;
|
||||
const widgetHeight = node.canvasHeight;
|
||||
|
||||
// Keep preview in sync when inputs are driven by links.
|
||||
syncLinkedInputsToPropertiesAdvanced(node);
|
||||
|
||||
const width = Math.max(1, Math.round(node.properties["width"]));
|
||||
const height = Math.max(1, Math.round(node.properties["height"]));
|
||||
const scale = Math.min(
|
||||
(widgetWidth - margin * 3) / width,
|
||||
(widgetHeight - margin * 3) / height
|
||||
);
|
||||
const blurRadius = node.properties["blur_radius"] || 0;
|
||||
const index = 0;
|
||||
|
||||
Object.assign(this.canvas.style, {
|
||||
left: `${t.e}px`,
|
||||
top: `${t.f + (widgetY * t.d)}px`,
|
||||
width: `${widgetWidth * t.a}px`,
|
||||
height: `${widgetHeight * t.d}px`,
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
fontSize: `${t.d * 10.0}px`,
|
||||
pointerEvents: "none"
|
||||
});
|
||||
|
||||
this.canvas.hidden = !visible;
|
||||
|
||||
let backgroundWidth = width * scale;
|
||||
let backgroundHeight = height * scale;
|
||||
|
||||
let xOffset = margin;
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
let yOffset = (margin / 2);
|
||||
if (backgroundHeight < widgetHeight) {
|
||||
yOffset += (widgetHeight - backgroundHeight) / 2 - margin;
|
||||
}
|
||||
|
||||
let widgetX = xOffset;
|
||||
widgetY = widgetY + yOffset;
|
||||
|
||||
// Draw the background border
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(widgetX - border, widgetY - border, backgroundWidth + border * 2, backgroundHeight + border * 2)
|
||||
|
||||
// Draw the main background area
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
|
||||
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
|
||||
|
||||
// Draw the conditioning zone
|
||||
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + x, widgetY + y, w, h);
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Draw grid lines
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetY);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetY + backgroundHeight);
|
||||
}
|
||||
|
||||
for (let y = 0; y <= height / 64; y += 1) {
|
||||
ctx.moveTo(widgetX, widgetY + y * 64 * scale);
|
||||
ctx.lineTo(widgetX + backgroundWidth, widgetY + y * 64 * scale);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#66666650";
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Draw current zone
|
||||
let [sx, sy, sw, sh] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "40");
|
||||
ctx.fillRect(widgetX + sx + border, widgetY + sy + border, sw - border * 2, sh - border * 2);
|
||||
|
||||
// Draw white border around the current zone
|
||||
ctx.strokeStyle = globalThis.LiteGraph.NODE_SELECTED_TITLE_COLOR;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
|
||||
// Display
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.arc(LiteGraph.NODE_SLOT_HEIGHT * 0.5, LiteGraph.NODE_SLOT_HEIGHT * (index + 0.5) + 4, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.closePath();
|
||||
|
||||
// Draw progress bar canvas
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
|
||||
// Adjust X and Y coordinates
|
||||
const barHeight = 8;
|
||||
let widgetYBar = widgetY + backgroundHeight + margin;
|
||||
|
||||
// Draw the border around the progress bar
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(
|
||||
widgetX - border,
|
||||
widgetYBar - border,
|
||||
backgroundWidth + border * 2,
|
||||
barHeight + border * 2
|
||||
);
|
||||
|
||||
// Draw the main bar area (background)
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth,
|
||||
barHeight
|
||||
);
|
||||
|
||||
// Draw progress bar grid
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "#66666650";
|
||||
|
||||
// Calculate the number of grid lines based on the bar size
|
||||
const numLines = Math.floor(backgroundWidth / 64);
|
||||
|
||||
// Draw grid lines
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetYBar);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetYBar + barHeight);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Draw progress (based on blur_radius)
|
||||
const progress = Math.min(blurRadius / 255, 1);
|
||||
ctx.fillStyle = "rgba(0, 120, 255, 0.5)";
|
||||
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth * progress,
|
||||
barHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
widget.canvas = document.createElement("canvas");
|
||||
widget.canvas.className = "mask-rect-area-canvas";
|
||||
widget.parent = node;
|
||||
|
||||
widget.computeLayoutSize = function (node) {
|
||||
return {
|
||||
minHeight: 200,
|
||||
maxHeight: 300
|
||||
};
|
||||
};
|
||||
|
||||
document.body.appendChild(widget.canvas);
|
||||
node.addCustomWidget(widget);
|
||||
|
||||
app.canvas.onDrawBackground = function () {
|
||||
// Draw node isnt fired once the node is off the screen
|
||||
// if it goes off screen quickly, the input may not be removed
|
||||
// this shifts it off screen so it can be moved back if the node is visible.
|
||||
for (let n in app.graph._nodes) {
|
||||
n = app.graph._nodes[n];
|
||||
for (let w in n.widgets) {
|
||||
let wid = n.widgets[w];
|
||||
if (Object.hasOwn(wid, "canvas")) {
|
||||
wid.canvas.style.left = -8000 + "px";
|
||||
wid.canvas.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeCanvasSize(node, size, 220, 240);
|
||||
};
|
||||
|
||||
return {minWidth: 200, minHeight: 200, widget};
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "drltdata.MaskRectAreaAdvanced",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name !== "MaskRectAreaAdvanced") {
|
||||
return;
|
||||
}
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
this.setProperty("width", 512);
|
||||
this.setProperty("height", 512);
|
||||
this.setProperty("x", 0);
|
||||
this.setProperty("y", 0);
|
||||
this.setProperty("w", 256);
|
||||
this.setProperty("h", 256);
|
||||
this.setProperty("blur_radius", 0);
|
||||
|
||||
this.selected = false;
|
||||
this.index = 3;
|
||||
this.serialize_widgets = true;
|
||||
|
||||
// If the node already provides widgets from Python/ComfyUI, do NOT recreate them
|
||||
const hasExisting = Array.isArray(this.widgets) && this.widgets.some(w => w && w.name === "x");
|
||||
|
||||
// Helper: attach callbacks to existing widgets to keep node.properties in sync (canvas preview).
|
||||
const hookWidget = (node, widgetName, propName, opts) => {
|
||||
if (!Array.isArray(node.widgets)) {
|
||||
return;
|
||||
}
|
||||
const w = node.widgets.find(ww => ww && ww.name === widgetName);
|
||||
if (!w) {
|
||||
return;
|
||||
}
|
||||
|
||||
const min = (opts && typeof opts.min === "number") ? opts.min : undefined;
|
||||
const max = (opts && typeof opts.max === "number") ? opts.max : undefined;
|
||||
const step = (opts && typeof opts.step === "number") ? opts.step : undefined;
|
||||
|
||||
if (node.properties && Object.prototype.hasOwnProperty.call(node.properties, propName)) {
|
||||
w.value = node.properties[propName];
|
||||
} else {
|
||||
node.properties[propName] = w.value;
|
||||
}
|
||||
|
||||
const prevCb = w.callback;
|
||||
w.callback = function (v, ...args) {
|
||||
let val = v;
|
||||
if (typeof val === "number") {
|
||||
if (typeof step === "number" && step > 0) {
|
||||
const s = step / 10;
|
||||
val = Math.round(val / s) * s;
|
||||
} else {
|
||||
val = Math.round(val);
|
||||
}
|
||||
if (typeof min === "number") {
|
||||
val = Math.max(min, val);
|
||||
}
|
||||
if (typeof max === "number") {
|
||||
val = Math.min(max, val);
|
||||
}
|
||||
}
|
||||
this.value = val;
|
||||
node.properties[propName] = val;
|
||||
if (prevCb) {
|
||||
return prevCb.call(this, val, ...args);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (hasExisting) {
|
||||
hookWidget(this, "x", "x", {"step": 10});
|
||||
hookWidget(this, "y", "y", {"step": 10});
|
||||
hookWidget(this, "width", "w", {"step": 10});
|
||||
hookWidget(this, "height", "h", {"step": 10});
|
||||
hookWidget(this, "image_width", "width", {"step": 10});
|
||||
hookWidget(this, "image_height", "height", {"step": 10});
|
||||
hookWidget(this, "blur_radius", "blur_radius", {"min": 0, "max": 255, "step": 10});
|
||||
} else {
|
||||
CUSTOM_INT(this, "x", 0, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["x"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "y", 0, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["y"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "width", 256, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["w"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "height", 256, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["h"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "image_width", 512, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["width"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "image_height", 512, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["height"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "blur_radius", 0, function (v, _, node) {
|
||||
this.value = Math.round(v) || 0;
|
||||
node.properties["blur_radius"] = this.value;
|
||||
},
|
||||
{"min": 0, "max": 255, "step": 10}
|
||||
);
|
||||
}
|
||||
|
||||
showPreviewCanvas(this, app);
|
||||
|
||||
this.onSelected = function () {
|
||||
this.selected = true;
|
||||
};
|
||||
this.onDeselected = function () {
|
||||
this.selected = false;
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate the drawing area using individual properties.
|
||||
function getDrawArea(node, backgroundWidth, backgroundHeight) {
|
||||
let x = node.properties["x"] * backgroundWidth / node.properties["width"];
|
||||
let y = node.properties["y"] * backgroundHeight / node.properties["height"];
|
||||
let w = node.properties["w"] * backgroundWidth / node.properties["width"];
|
||||
let h = node.properties["h"] * backgroundHeight / node.properties["height"];
|
||||
|
||||
if (x > backgroundWidth) {
|
||||
x = backgroundWidth;
|
||||
}
|
||||
if (y > backgroundHeight) {
|
||||
y = backgroundHeight;
|
||||
}
|
||||
|
||||
if (x + w > backgroundWidth) {
|
||||
w = Math.max(0, backgroundWidth - x);
|
||||
}
|
||||
|
||||
if (y + h > backgroundHeight) {
|
||||
h = Math.max(0, backgroundHeight - y);
|
||||
}
|
||||
|
||||
return [x, y, w, h];
|
||||
}
|
||||
|
||||
function CUSTOM_INT(node, inputName, val, func, config = {}) {
|
||||
return {
|
||||
widget: node.addWidget(
|
||||
"number",
|
||||
inputName,
|
||||
val,
|
||||
func,
|
||||
Object.assign({}, {min: 0, max: 4096, step: 640, precision: 0}, config)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function syncLinkedInputsToPropertiesAdvanced(node) {
|
||||
let changed = false;
|
||||
|
||||
const vx = readLinkedNumber(node, "x");
|
||||
if (vx != null) {
|
||||
const nv = Math.max(0, Math.round(vx));
|
||||
if (node.properties["x"] !== nv) {
|
||||
node.properties["x"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const vy = readLinkedNumber(node, "y");
|
||||
if (vy != null) {
|
||||
const nv = Math.max(0, Math.round(vy));
|
||||
if (node.properties["y"] !== nv) {
|
||||
node.properties["y"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Input "width" is the rectangle width in px -> property "w"
|
||||
const vw = readLinkedNumber(node, "width");
|
||||
if (vw != null) {
|
||||
const nv = Math.max(0, Math.round(vw));
|
||||
if (node.properties["w"] !== nv) {
|
||||
node.properties["w"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Input "height" is the rectangle height in px -> property "h"
|
||||
const vh = readLinkedNumber(node, "height");
|
||||
if (vh != null) {
|
||||
const nv = Math.max(0, Math.round(vh));
|
||||
if (node.properties["h"] !== nv) {
|
||||
node.properties["h"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Image size (must be >=1 to avoid division by zero in getDrawArea)
|
||||
const viw = readLinkedNumber(node, "image_width");
|
||||
if (viw != null) {
|
||||
const nv = Math.max(1, Math.round(viw));
|
||||
if (node.properties["width"] !== nv) {
|
||||
node.properties["width"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const vih = readLinkedNumber(node, "image_height");
|
||||
if (vih != null) {
|
||||
const nv = Math.max(1, Math.round(vih));
|
||||
if (node.properties["height"] !== nv) {
|
||||
node.properties["height"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const vbr = readLinkedNumber(node, "blur_radius");
|
||||
if (vbr != null) {
|
||||
const nv = Math.max(0, Math.min(255, Math.round(vbr)));
|
||||
if (node.properties["blur_radius"] !== nv) {
|
||||
node.properties["blur_radius"] = nv;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user