mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-11 23:00:51 +08:00
Merge with upstream
This commit is contained in:
commit
8e9052c843
@ -159,7 +159,7 @@ On macOS, install exactly Python 3.11 using `brew`, which you can download from
|
|||||||
curl -L https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -o ./models/checkpoints/v1-5-pruned-emaonly.ckpt
|
curl -L https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -o ./models/checkpoints/v1-5-pruned-emaonly.ckpt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Put your VAE into the `models/vae` folder.
|
3. (Optional) Put your VAE into the `models/vae` folder.
|
||||||
|
|
||||||
4. (Optional) Create a virtual environment:
|
4. (Optional) Create a virtual environment:
|
||||||
1. Create an environment:
|
1. Create an environment:
|
||||||
|
|||||||
@ -497,7 +497,7 @@ def unet_dtype(device=None, model_params=0):
|
|||||||
return torch.float8_e4m3fn
|
return torch.float8_e4m3fn
|
||||||
if args.fp8_e5m2_unet:
|
if args.fp8_e5m2_unet:
|
||||||
return torch.float8_e5m2
|
return torch.float8_e5m2
|
||||||
if should_use_fp16(device=device, model_params=model_params):
|
if should_use_fp16(device=device, model_params=model_params, manual_cast=True):
|
||||||
return torch.float16
|
return torch.float16
|
||||||
return torch.float32
|
return torch.float32
|
||||||
|
|
||||||
@ -547,10 +547,8 @@ def text_encoder_dtype(device=None):
|
|||||||
if is_device_cpu(device):
|
if is_device_cpu(device):
|
||||||
return torch.float16
|
return torch.float16
|
||||||
|
|
||||||
if should_use_fp16(device, prioritize_performance=False):
|
return torch.float16
|
||||||
return torch.float16
|
|
||||||
else:
|
|
||||||
return torch.float32
|
|
||||||
|
|
||||||
def intermediate_device():
|
def intermediate_device():
|
||||||
if args.gpu_only:
|
if args.gpu_only:
|
||||||
@ -699,7 +697,7 @@ def is_device_mps(device):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def should_use_fp16(device=None, model_params=0, prioritize_performance=True):
|
def should_use_fp16(device=None, model_params=0, prioritize_performance=True, manual_cast=False):
|
||||||
global directml_enabled
|
global directml_enabled
|
||||||
|
|
||||||
if device is not None:
|
if device is not None:
|
||||||
@ -725,10 +723,13 @@ def should_use_fp16(device=None, model_params=0, prioritize_performance=True):
|
|||||||
if is_intel_xpu():
|
if is_intel_xpu():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if torch.cuda.is_bf16_supported():
|
if torch.version.hip:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
props = torch.cuda.get_device_properties("cuda")
|
props = torch.cuda.get_device_properties("cuda")
|
||||||
|
if props.major >= 8:
|
||||||
|
return True
|
||||||
|
|
||||||
if props.major < 6:
|
if props.major < 6:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -741,7 +742,7 @@ def should_use_fp16(device=None, model_params=0, prioritize_performance=True):
|
|||||||
if x in props.name.lower():
|
if x in props.name.lower():
|
||||||
fp16_works = True
|
fp16_works = True
|
||||||
|
|
||||||
if fp16_works:
|
if fp16_works or manual_cast:
|
||||||
free_model_memory = (get_free_memory() * 0.9 - minimum_inference_memory())
|
free_model_memory = (get_free_memory() * 0.9 - minimum_inference_memory())
|
||||||
if (not prioritize_performance) or model_params * 4 > free_model_memory:
|
if (not prioritize_performance) or model_params * 4 > free_model_memory:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -462,7 +462,7 @@ def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, o
|
|||||||
model.load_model_weights(sd, "model.diffusion_model.")
|
model.load_model_weights(sd, "model.diffusion_model.")
|
||||||
|
|
||||||
if output_vae:
|
if output_vae:
|
||||||
vae_sd = utils.state_dict_prefix_replace(sd, {"first_stage_model.": ""}, filter_keys=True)
|
vae_sd = utils.state_dict_prefix_replace(sd, {k: "" for k in model_config.vae_key_prefix}, filter_keys=True)
|
||||||
vae_sd = model_config.process_vae_state_dict(vae_sd)
|
vae_sd = model_config.process_vae_state_dict(vae_sd)
|
||||||
vae = VAE(sd=vae_sd)
|
vae = VAE(sd=vae_sd)
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class BASE:
|
|||||||
noise_aug_config = None
|
noise_aug_config = None
|
||||||
sampling_settings = {}
|
sampling_settings = {}
|
||||||
latent_format = latent_formats.LatentFormat
|
latent_format = latent_formats.LatentFormat
|
||||||
|
vae_key_prefix = ["first_stage_model."]
|
||||||
|
|
||||||
manual_cast_dtype = None
|
manual_cast_dtype = None
|
||||||
|
|
||||||
|
|||||||
@ -415,6 +415,8 @@ def tiled_scale(samples, function, tile_x=64, tile_y=64, overlap = 8, upscale_am
|
|||||||
out_div = torch.zeros((s.shape[0], out_channels, round(s.shape[2] * upscale_amount), round(s.shape[3] * upscale_amount)), device=output_device)
|
out_div = torch.zeros((s.shape[0], out_channels, round(s.shape[2] * upscale_amount), round(s.shape[3] * upscale_amount)), device=output_device)
|
||||||
for y in range(0, s.shape[2], tile_y - overlap):
|
for y in range(0, s.shape[2], tile_y - overlap):
|
||||||
for x in range(0, s.shape[3], tile_x - overlap):
|
for x in range(0, s.shape[3], tile_x - overlap):
|
||||||
|
x = max(0, min(s.shape[-1] - overlap, x))
|
||||||
|
y = max(0, min(s.shape[-2] - overlap, y))
|
||||||
s_in = s[:,:,y:y+tile_y,x:x+tile_x]
|
s_in = s[:,:,y:y+tile_y,x:x+tile_x]
|
||||||
|
|
||||||
ps = function(s_in).to(output_device)
|
ps = function(s_in).to(output_device)
|
||||||
|
|||||||
@ -910,6 +910,9 @@ export class GroupNodeHandler {
|
|||||||
const self = this;
|
const self = this;
|
||||||
const onNodeCreated = this.node.onNodeCreated;
|
const onNodeCreated = this.node.onNodeCreated;
|
||||||
this.node.onNodeCreated = function () {
|
this.node.onNodeCreated = function () {
|
||||||
|
if (!this.widgets) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const config = self.groupData.nodeData.config;
|
const config = self.groupData.nodeData.config;
|
||||||
if (config) {
|
if (config) {
|
||||||
for (const n in config) {
|
for (const n in config) {
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
.comfy-group-manage-list-items {
|
.comfy-group-manage-list-items {
|
||||||
max-height: 70vh;
|
max-height: calc(100% - 40px);
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,7 @@ async function uploadMask(filepath, formData) {
|
|||||||
ClipspaceDialog.invalidatePreview();
|
ClipspaceDialog.invalidatePreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepare_mask(image, maskCanvas, maskCtx) {
|
function prepare_mask(image, maskCanvas, maskCtx, maskColor) {
|
||||||
// paste mask data into alpha channel
|
// paste mask data into alpha channel
|
||||||
maskCtx.drawImage(image, 0, 0, maskCanvas.width, maskCanvas.height);
|
maskCtx.drawImage(image, 0, 0, maskCanvas.width, maskCanvas.height);
|
||||||
const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);
|
const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);
|
||||||
@ -74,9 +74,9 @@ function prepare_mask(image, maskCanvas, maskCtx) {
|
|||||||
else
|
else
|
||||||
maskData.data[i+3] = 255;
|
maskData.data[i+3] = 255;
|
||||||
|
|
||||||
maskData.data[i] = 0;
|
maskData.data[i] = maskColor.r;
|
||||||
maskData.data[i+1] = 0;
|
maskData.data[i+1] = maskColor.g;
|
||||||
maskData.data[i+2] = 0;
|
maskData.data[i+2] = maskColor.b;
|
||||||
}
|
}
|
||||||
|
|
||||||
maskCtx.globalCompositeOperation = 'source-over';
|
maskCtx.globalCompositeOperation = 'source-over';
|
||||||
@ -110,6 +110,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
|
|
||||||
createButton(name, callback) {
|
createButton(name, callback) {
|
||||||
var button = document.createElement("button");
|
var button = document.createElement("button");
|
||||||
|
button.style.pointerEvents = "auto";
|
||||||
button.innerText = name;
|
button.innerText = name;
|
||||||
button.addEventListener("click", callback);
|
button.addEventListener("click", callback);
|
||||||
return button;
|
return button;
|
||||||
@ -146,6 +147,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
divElement.style.display = "flex";
|
divElement.style.display = "flex";
|
||||||
divElement.style.position = "relative";
|
divElement.style.position = "relative";
|
||||||
divElement.style.top = "2px";
|
divElement.style.top = "2px";
|
||||||
|
divElement.style.pointerEvents = "auto";
|
||||||
self.brush_slider_input = document.createElement('input');
|
self.brush_slider_input = document.createElement('input');
|
||||||
self.brush_slider_input.setAttribute('type', 'range');
|
self.brush_slider_input.setAttribute('type', 'range');
|
||||||
self.brush_slider_input.setAttribute('min', '1');
|
self.brush_slider_input.setAttribute('min', '1');
|
||||||
@ -173,6 +175,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
bottom_panel.style.left = "20px";
|
bottom_panel.style.left = "20px";
|
||||||
bottom_panel.style.right = "20px";
|
bottom_panel.style.right = "20px";
|
||||||
bottom_panel.style.height = "50px";
|
bottom_panel.style.height = "50px";
|
||||||
|
bottom_panel.style.pointerEvents = "none";
|
||||||
|
|
||||||
var brush = document.createElement("div");
|
var brush = document.createElement("div");
|
||||||
brush.id = "brush";
|
brush.id = "brush";
|
||||||
@ -191,14 +194,29 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
this.element.appendChild(bottom_panel);
|
this.element.appendChild(bottom_panel);
|
||||||
document.body.appendChild(brush);
|
document.body.appendChild(brush);
|
||||||
|
|
||||||
|
var clearButton = this.createLeftButton("Clear", () => {
|
||||||
|
self.maskCtx.clearRect(0, 0, self.maskCanvas.width, self.maskCanvas.height);
|
||||||
|
});
|
||||||
|
|
||||||
this.brush_size_slider = this.createLeftSlider(self, "Thickness", (event) => {
|
this.brush_size_slider = this.createLeftSlider(self, "Thickness", (event) => {
|
||||||
self.brush_size = event.target.value;
|
self.brush_size = event.target.value;
|
||||||
self.updateBrushPreview(self, null, null);
|
self.updateBrushPreview(self, null, null);
|
||||||
});
|
});
|
||||||
var clearButton = this.createLeftButton("Clear",
|
|
||||||
() => {
|
this.colorButton = this.createLeftButton(this.getColorButtonText(), () => {
|
||||||
self.maskCtx.clearRect(0, 0, self.maskCanvas.width, self.maskCanvas.height);
|
if (self.brush_color_mode === "black") {
|
||||||
});
|
self.brush_color_mode = "white";
|
||||||
|
}
|
||||||
|
else if (self.brush_color_mode === "white") {
|
||||||
|
self.brush_color_mode = "negative";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.brush_color_mode = "black";
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateWhenBrushColorModeChanged();
|
||||||
|
});
|
||||||
|
|
||||||
var cancelButton = this.createRightButton("Cancel", () => {
|
var cancelButton = this.createRightButton("Cancel", () => {
|
||||||
document.removeEventListener("mouseup", MaskEditorDialog.handleMouseUp);
|
document.removeEventListener("mouseup", MaskEditorDialog.handleMouseUp);
|
||||||
document.removeEventListener("keydown", MaskEditorDialog.handleKeyDown);
|
document.removeEventListener("keydown", MaskEditorDialog.handleKeyDown);
|
||||||
@ -219,6 +237,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
bottom_panel.appendChild(this.saveButton);
|
bottom_panel.appendChild(this.saveButton);
|
||||||
bottom_panel.appendChild(cancelButton);
|
bottom_panel.appendChild(cancelButton);
|
||||||
bottom_panel.appendChild(this.brush_size_slider);
|
bottom_panel.appendChild(this.brush_size_slider);
|
||||||
|
bottom_panel.appendChild(this.colorButton);
|
||||||
|
|
||||||
imgCanvas.style.position = "absolute";
|
imgCanvas.style.position = "absolute";
|
||||||
maskCanvas.style.position = "absolute";
|
maskCanvas.style.position = "absolute";
|
||||||
@ -228,6 +247,10 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
|
|
||||||
maskCanvas.style.top = imgCanvas.style.top;
|
maskCanvas.style.top = imgCanvas.style.top;
|
||||||
maskCanvas.style.left = imgCanvas.style.left;
|
maskCanvas.style.left = imgCanvas.style.left;
|
||||||
|
|
||||||
|
const maskCanvasStyle = this.getMaskCanvasStyle();
|
||||||
|
maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode;
|
||||||
|
maskCanvas.style.opacity = maskCanvasStyle.opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async show() {
|
async show() {
|
||||||
@ -313,7 +336,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
let maskCtx = this.maskCanvas.getContext('2d', {willReadFrequently: true });
|
let maskCtx = this.maskCanvas.getContext('2d', {willReadFrequently: true });
|
||||||
|
|
||||||
imgCtx.drawImage(orig_image, 0, 0, orig_image.width, orig_image.height);
|
imgCtx.drawImage(orig_image, 0, 0, orig_image.width, orig_image.height);
|
||||||
prepare_mask(mask_image, this.maskCanvas, maskCtx);
|
prepare_mask(mask_image, this.maskCanvas, maskCtx, this.getMaskColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
async setImages(imgCanvas) {
|
async setImages(imgCanvas) {
|
||||||
@ -439,7 +462,84 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaskCanvasStyle() {
|
||||||
|
if (this.brush_color_mode === "negative") {
|
||||||
|
return {
|
||||||
|
mixBlendMode: "difference",
|
||||||
|
opacity: "1",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
mixBlendMode: "initial",
|
||||||
|
opacity: "0.7",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaskColor() {
|
||||||
|
if (this.brush_color_mode === "black") {
|
||||||
|
return { r: 0, g: 0, b: 0 };
|
||||||
|
}
|
||||||
|
if (this.brush_color_mode === "white") {
|
||||||
|
return { r: 255, g: 255, b: 255 };
|
||||||
|
}
|
||||||
|
if (this.brush_color_mode === "negative") {
|
||||||
|
// negative effect only works with white color
|
||||||
|
return { r: 255, g: 255, b: 255 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { r: 0, g: 0, b: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaskFillStyle() {
|
||||||
|
const maskColor = this.getMaskColor();
|
||||||
|
|
||||||
|
return "rgb(" + maskColor.r + "," + maskColor.g + "," + maskColor.b + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorButtonText() {
|
||||||
|
let colorCaption = "unknown";
|
||||||
|
|
||||||
|
if (this.brush_color_mode === "black") {
|
||||||
|
colorCaption = "black";
|
||||||
|
}
|
||||||
|
else if (this.brush_color_mode === "white") {
|
||||||
|
colorCaption = "white";
|
||||||
|
}
|
||||||
|
else if (this.brush_color_mode === "negative") {
|
||||||
|
colorCaption = "negative";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Color: " + colorCaption;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWhenBrushColorModeChanged() {
|
||||||
|
this.colorButton.innerText = this.getColorButtonText();
|
||||||
|
|
||||||
|
// update mask canvas css styles
|
||||||
|
|
||||||
|
const maskCanvasStyle = this.getMaskCanvasStyle();
|
||||||
|
this.maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode;
|
||||||
|
this.maskCanvas.style.opacity = maskCanvasStyle.opacity;
|
||||||
|
|
||||||
|
// update mask canvas rgb colors
|
||||||
|
|
||||||
|
const maskColor = this.getMaskColor();
|
||||||
|
|
||||||
|
const maskData = this.maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
||||||
|
|
||||||
|
for (let i = 0; i < maskData.data.length; i += 4) {
|
||||||
|
maskData.data[i] = maskColor.r;
|
||||||
|
maskData.data[i+1] = maskColor.g;
|
||||||
|
maskData.data[i+2] = maskColor.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maskCtx.putImageData(maskData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
brush_size = 10;
|
brush_size = 10;
|
||||||
|
brush_color_mode = "black";
|
||||||
drawing_mode = false;
|
drawing_mode = false;
|
||||||
lastx = -1;
|
lastx = -1;
|
||||||
lasty = -1;
|
lasty = -1;
|
||||||
@ -518,6 +618,19 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
self.pan_move(self, event);
|
self.pan_move(self, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1;
|
||||||
|
|
||||||
|
if(event.shiftKey && left_button_down) {
|
||||||
|
self.drawing_mode = false;
|
||||||
|
|
||||||
|
const y = event.clientY;
|
||||||
|
let delta = (self.zoom_lasty - y)*0.005;
|
||||||
|
self.zoom_ratio = Math.max(Math.min(10.0, self.last_zoom_ratio - delta), 0.2);
|
||||||
|
|
||||||
|
this.invalidatePanZoom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pan_move(self, event) {
|
pan_move(self, event) {
|
||||||
@ -535,7 +648,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
draw_move(self, event) {
|
draw_move(self, event) {
|
||||||
if(event.ctrlKey) {
|
if(event.ctrlKey || event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +659,10 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
|
|
||||||
self.updateBrushPreview(self);
|
self.updateBrushPreview(self);
|
||||||
|
|
||||||
if (window.TouchEvent && event instanceof TouchEvent || event.buttons == 1) {
|
let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1;
|
||||||
|
let right_button_down = [2, 5, 32].includes(event.buttons);
|
||||||
|
|
||||||
|
if (!event.altKey && left_button_down) {
|
||||||
var diff = performance.now() - self.lasttime;
|
var diff = performance.now() - self.lasttime;
|
||||||
|
|
||||||
const maskRect = self.maskCanvas.getBoundingClientRect();
|
const maskRect = self.maskCanvas.getBoundingClientRect();
|
||||||
@ -581,7 +697,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
if(diff > 20 && !this.drawing_mode)
|
if(diff > 20 && !this.drawing_mode)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
self.maskCtx.beginPath();
|
self.maskCtx.beginPath();
|
||||||
self.maskCtx.fillStyle = "rgb(0,0,0)";
|
self.maskCtx.fillStyle = this.getMaskFillStyle();
|
||||||
self.maskCtx.globalCompositeOperation = "source-over";
|
self.maskCtx.globalCompositeOperation = "source-over";
|
||||||
self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
|
self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false);
|
||||||
self.maskCtx.fill();
|
self.maskCtx.fill();
|
||||||
@ -591,7 +707,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
else
|
else
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
self.maskCtx.beginPath();
|
self.maskCtx.beginPath();
|
||||||
self.maskCtx.fillStyle = "rgb(0,0,0)";
|
self.maskCtx.fillStyle = this.getMaskFillStyle();
|
||||||
self.maskCtx.globalCompositeOperation = "source-over";
|
self.maskCtx.globalCompositeOperation = "source-over";
|
||||||
|
|
||||||
var dx = x - self.lastx;
|
var dx = x - self.lastx;
|
||||||
@ -613,7 +729,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
|
|
||||||
self.lasttime = performance.now();
|
self.lasttime = performance.now();
|
||||||
}
|
}
|
||||||
else if(event.buttons == 2 || event.buttons == 5 || event.buttons == 32) {
|
else if((event.altKey && left_button_down) || right_button_down) {
|
||||||
const maskRect = self.maskCanvas.getBoundingClientRect();
|
const maskRect = self.maskCanvas.getBoundingClientRect();
|
||||||
const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio;
|
const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio;
|
||||||
const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio;
|
const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio;
|
||||||
@ -687,13 +803,20 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
self.drawing_mode = true;
|
self.drawing_mode = true;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
if(event.shiftKey) {
|
||||||
|
self.zoom_lasty = event.clientY;
|
||||||
|
self.last_zoom_ratio = self.zoom_ratio;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const maskRect = self.maskCanvas.getBoundingClientRect();
|
const maskRect = self.maskCanvas.getBoundingClientRect();
|
||||||
const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio;
|
const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio;
|
||||||
const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio;
|
const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio;
|
||||||
|
|
||||||
self.maskCtx.beginPath();
|
self.maskCtx.beginPath();
|
||||||
if (event.button == 0) {
|
if (!event.altKey && event.button == 0) {
|
||||||
self.maskCtx.fillStyle = "rgb(0,0,0)";
|
self.maskCtx.fillStyle = this.getMaskFillStyle();
|
||||||
self.maskCtx.globalCompositeOperation = "source-over";
|
self.maskCtx.globalCompositeOperation = "source-over";
|
||||||
} else {
|
} else {
|
||||||
self.maskCtx.globalCompositeOperation = "destination-out";
|
self.maskCtx.globalCompositeOperation = "destination-out";
|
||||||
|
|||||||
@ -260,6 +260,12 @@ app.registerExtension({
|
|||||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
// Add menu options to conver to/from widgets
|
// Add menu options to conver to/from widgets
|
||||||
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||||
|
nodeType.prototype.convertWidgetToInput = function (widget) {
|
||||||
|
const config = getConfig.call(this, widget.name) ?? [widget.type, widget.options || {}];
|
||||||
|
if (!isConvertableWidget(widget, config)) return false;
|
||||||
|
convertToInput(this, widget, config);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||||
const r = origGetExtraMenuOptions ? origGetExtraMenuOptions.apply(this, arguments) : undefined;
|
const r = origGetExtraMenuOptions ? origGetExtraMenuOptions.apply(this, arguments) : undefined;
|
||||||
|
|
||||||
|
|||||||
@ -11549,7 +11549,7 @@ LGraphNode.prototype.executeAction = function(action)
|
|||||||
dialog.close();
|
dialog.close();
|
||||||
} else if (e.keyCode == 13) {
|
} else if (e.keyCode == 13) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
select(selected.innerHTML);
|
select(unescape(selected.dataset["type"]));
|
||||||
} else if (first) {
|
} else if (first) {
|
||||||
select(first);
|
select(first);
|
||||||
} else {
|
} else {
|
||||||
@ -11910,7 +11910,7 @@ LGraphNode.prototype.executeAction = function(action)
|
|||||||
var ctor = LiteGraph.registered_node_types[ type ];
|
var ctor = LiteGraph.registered_node_types[ type ];
|
||||||
if(filter && ctor.filter != filter )
|
if(filter && ctor.filter != filter )
|
||||||
return false;
|
return false;
|
||||||
if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1)
|
if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1 && (!ctor.title || ctor.title.toLowerCase().indexOf(str) === -1))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// filter by slot IN, OUT types
|
// filter by slot IN, OUT types
|
||||||
@ -11964,7 +11964,18 @@ LGraphNode.prototype.executeAction = function(action)
|
|||||||
if (!first) {
|
if (!first) {
|
||||||
first = type;
|
first = type;
|
||||||
}
|
}
|
||||||
help.innerText = type;
|
|
||||||
|
const nodeType = LiteGraph.registered_node_types[type];
|
||||||
|
if (nodeType?.title) {
|
||||||
|
help.innerText = nodeType?.title;
|
||||||
|
const typeEl = document.createElement("span");
|
||||||
|
typeEl.className = "litegraph lite-search-item-type";
|
||||||
|
typeEl.textContent = type;
|
||||||
|
help.append(typeEl);
|
||||||
|
} else {
|
||||||
|
help.innerText = type;
|
||||||
|
}
|
||||||
|
|
||||||
help.dataset["type"] = escape(type);
|
help.dataset["type"] = escape(type);
|
||||||
help.className = "litegraph lite-search-item";
|
help.className = "litegraph lite-search-item";
|
||||||
if (className) {
|
if (className) {
|
||||||
|
|||||||
@ -184,6 +184,7 @@
|
|||||||
color: white;
|
color: white;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.litegraph.litesearchbox .name {
|
.litegraph.litesearchbox .name {
|
||||||
@ -227,6 +228,18 @@
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.litegraph.lite-search-item-type {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
opacity: 0.8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* DIALOGs ******/
|
/* DIALOGs ******/
|
||||||
|
|
||||||
.litegraph .dialog {
|
.litegraph .dialog {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ class ComfyApi extends EventTarget {
|
|||||||
super();
|
super();
|
||||||
this.api_host = location.host;
|
this.api_host = location.host;
|
||||||
this.api_base = location.pathname.split('/').slice(0, -1).join('/');
|
this.api_base = location.pathname.split('/').slice(0, -1).join('/');
|
||||||
|
this.initialClientId = sessionStorage.getItem("clientId");
|
||||||
}
|
}
|
||||||
|
|
||||||
apiURL(route) {
|
apiURL(route) {
|
||||||
@ -118,7 +119,8 @@ class ComfyApi extends EventTarget {
|
|||||||
case "status":
|
case "status":
|
||||||
if (msg.data.sid) {
|
if (msg.data.sid) {
|
||||||
this.clientId = msg.data.sid;
|
this.clientId = msg.data.sid;
|
||||||
window.name = this.clientId;
|
window.name = this.clientId; // use window name so it isnt reused when duplicating tabs
|
||||||
|
sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status }));
|
this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status }));
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1499,12 +1499,17 @@ export class ComfyApp {
|
|||||||
// Load previous workflow
|
// Load previous workflow
|
||||||
let restored = false;
|
let restored = false;
|
||||||
try {
|
try {
|
||||||
const json = localStorage.getItem("workflow");
|
const loadWorkflow = async (json) => {
|
||||||
if (json) {
|
if (json) {
|
||||||
const workflow = JSON.parse(json);
|
const workflow = JSON.parse(json);
|
||||||
await this.loadGraphData(workflow);
|
await this.loadGraphData(workflow);
|
||||||
restored = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
const clientId = api.initialClientId ?? api.clientId;
|
||||||
|
restored =
|
||||||
|
(clientId && (await loadWorkflow(sessionStorage.getItem(`workflow:${clientId}`)))) ||
|
||||||
|
(await loadWorkflow(localStorage.getItem("workflow")));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading previous workflow", err);
|
console.error("Error loading previous workflow", err);
|
||||||
}
|
}
|
||||||
@ -1515,7 +1520,13 @@ export class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save current workflow automatically
|
// Save current workflow automatically
|
||||||
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.graph.serialize())), 1000);
|
setInterval(() => {
|
||||||
|
const workflow = JSON.stringify(this.graph.serialize());
|
||||||
|
localStorage.setItem("workflow", workflow);
|
||||||
|
if (api.clientId) {
|
||||||
|
sessionStorage.setItem(`workflow:${api.clientId}`, workflow);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
this.#addDrawNodeHandler();
|
this.#addDrawNodeHandler();
|
||||||
this.#addDrawGroupsHandler();
|
this.#addDrawGroupsHandler();
|
||||||
@ -2096,6 +2107,8 @@ export class ComfyApp {
|
|||||||
this.loadGraphData(JSON.parse(pngInfo.Workflow)); // Support loading workflows from that webp custom node.
|
this.loadGraphData(JSON.parse(pngInfo.Workflow)); // Support loading workflows from that webp custom node.
|
||||||
} else if (pngInfo.prompt) {
|
} else if (pngInfo.prompt) {
|
||||||
this.loadApiJson(JSON.parse(pngInfo.prompt));
|
this.loadApiJson(JSON.parse(pngInfo.prompt));
|
||||||
|
} else if (pngInfo.Prompt) {
|
||||||
|
this.loadApiJson(JSON.parse(pngInfo.Prompt)); // Support loading prompts from that webp custom node.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
|
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
|
||||||
@ -2149,8 +2162,17 @@ export class ComfyApp {
|
|||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
const [fromId, fromSlot] = value;
|
const [fromId, fromSlot] = value;
|
||||||
const fromNode = app.graph.getNodeById(fromId);
|
const fromNode = app.graph.getNodeById(fromId);
|
||||||
const toSlot = node.inputs?.findIndex((inp) => inp.name === input);
|
let toSlot = node.inputs?.findIndex((inp) => inp.name === input);
|
||||||
if (toSlot !== -1) {
|
if (toSlot == null || toSlot === -1) {
|
||||||
|
try {
|
||||||
|
// Target has no matching input, most likely a converted widget
|
||||||
|
const widget = node.widgets?.find((w) => w.name === input);
|
||||||
|
if (widget && node.convertWidgetToInput?.(widget)) {
|
||||||
|
toSlot = node.inputs?.length - 1;
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (toSlot != null || toSlot !== -1) {
|
||||||
fromNode.connect(fromSlot, node, toSlot);
|
fromNode.connect(fromSlot, node, toSlot);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -81,6 +81,9 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
|||||||
|
|
||||||
const isCombo = targetWidget.type === "combo";
|
const isCombo = targetWidget.type === "combo";
|
||||||
let comboFilter;
|
let comboFilter;
|
||||||
|
if (isCombo) {
|
||||||
|
valueControl.options.values.push("increment-wrap");
|
||||||
|
}
|
||||||
if (isCombo && options.addFilterList !== false) {
|
if (isCombo && options.addFilterList !== false) {
|
||||||
comboFilter = node.addWidget(
|
comboFilter = node.addWidget(
|
||||||
"string",
|
"string",
|
||||||
@ -128,6 +131,12 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
|||||||
case "increment":
|
case "increment":
|
||||||
current_index += 1;
|
current_index += 1;
|
||||||
break;
|
break;
|
||||||
|
case "increment-wrap":
|
||||||
|
current_index += 1;
|
||||||
|
if ( current_index >= current_length ) {
|
||||||
|
current_index = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "decrement":
|
case "decrement":
|
||||||
current_index -= 1;
|
current_index -= 1;
|
||||||
break;
|
break;
|
||||||
@ -295,7 +304,7 @@ export const ComfyWidgets = {
|
|||||||
let disable_rounding = app.ui.settings.getSettingValue("Comfy.DisableFloatRounding")
|
let disable_rounding = app.ui.settings.getSettingValue("Comfy.DisableFloatRounding")
|
||||||
if (precision == 0) precision = undefined;
|
if (precision == 0) precision = undefined;
|
||||||
const { val, config } = getNumberDefaults(inputData, 0.5, precision, !disable_rounding);
|
const { val, config } = getNumberDefaults(inputData, 0.5, precision, !disable_rounding);
|
||||||
return { widget: node.addWidget(widgetType, inputName, val,
|
return { widget: node.addWidget(widgetType, inputName, val,
|
||||||
function (v) {
|
function (v) {
|
||||||
if (config.round) {
|
if (config.round) {
|
||||||
this.value = Math.round(v/config.round)*config.round;
|
this.value = Math.round(v/config.round)*config.round;
|
||||||
|
|||||||
@ -126,7 +126,7 @@ class LatentBatchSeedBehavior:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "samples": ("LATENT",),
|
return {"required": { "samples": ("LATENT",),
|
||||||
"seed_behavior": (["random", "fixed"],),}}
|
"seed_behavior": (["random", "fixed"],{"default": "fixed"}),}}
|
||||||
|
|
||||||
RETURN_TYPES = ("LATENT",)
|
RETURN_TYPES = ("LATENT",)
|
||||||
FUNCTION = "op"
|
FUNCTION = "op"
|
||||||
|
|||||||
@ -6,6 +6,8 @@ class Example:
|
|||||||
-------------
|
-------------
|
||||||
INPUT_TYPES (dict):
|
INPUT_TYPES (dict):
|
||||||
Tell the main program input parameters of nodes.
|
Tell the main program input parameters of nodes.
|
||||||
|
IS_CHANGED:
|
||||||
|
optional method to control when the node is re executed.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
@ -89,6 +91,17 @@ class Example:
|
|||||||
image = 1.0 - image
|
image = 1.0 - image
|
||||||
return (image,)
|
return (image,)
|
||||||
|
|
||||||
|
"""
|
||||||
|
The node will always be re executed if any of the inputs change but
|
||||||
|
this method can be used to force the node to execute again even when the inputs don't change.
|
||||||
|
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||||
|
executed, if it is different the node will be executed again.
|
||||||
|
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||||
|
changes between executions the LoadImage node is executed again.
|
||||||
|
"""
|
||||||
|
#@classmethod
|
||||||
|
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||||
|
# return ""
|
||||||
|
|
||||||
# A dictionary that contains all nodes you want to export with their names
|
# A dictionary that contains all nodes you want to export with their names
|
||||||
# NOTE: names should be globally unique
|
# NOTE: names should be globally unique
|
||||||
|
|||||||
2
setup.py
2
setup.py
@ -28,7 +28,7 @@ version = '0.0.1'
|
|||||||
"""
|
"""
|
||||||
The package index to the torch built with AMD ROCm.
|
The package index to the torch built with AMD ROCm.
|
||||||
"""
|
"""
|
||||||
amd_torch_index = ("https://download.pytorch.org/whl/rocm5.6", "https://download.pytorch.org/whl/nightly/rocm5.7")
|
amd_torch_index = ("https://download.pytorch.org/whl/rocm5.7", "https://download.pytorch.org/whl/nightly/rocm6.0")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The package index to torch built with CUDA.
|
The package index to torch built with CUDA.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user