From ec0c82eb87f9d9a11d0cd01704f5fa97559438da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E6=B5=AA?= <757078144@qq.com> Date: Sat, 6 Apr 2024 22:20:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A7=BF=E6=80=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E9=85=8D=E5=A5=97=E8=8A=82=E7=82=B9=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E6=94=B9BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/MultiControlNetApply.js | 152 ++++++++++++++++++++++++++++++++++ js/MultiGLIGENTextBoxApply.js | 7 +- js/MultiTextSetMask.js | 149 +++++++++++++++++++++++++++++++++ js/openPoseEditorPlus.js | 16 +++- js/utils.js | 12 +++ py/MultiControlNetApply.py | 68 +++++++++++++++ py/MultiGLIGENTextBoxApply.py | 5 +- py/MultiTextSetMask.py | 75 +++++++++++++++++ py/openPoseEditorPlus.py | 4 +- 9 files changed, 474 insertions(+), 14 deletions(-) create mode 100644 js/MultiControlNetApply.js create mode 100644 js/MultiTextSetMask.js create mode 100644 py/MultiControlNetApply.py create mode 100644 py/MultiTextSetMask.py diff --git a/js/MultiControlNetApply.js b/js/MultiControlNetApply.js new file mode 100644 index 0000000..c0bb1f9 --- /dev/null +++ b/js/MultiControlNetApply.js @@ -0,0 +1,152 @@ +import { app } from "/scripts/app.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; +import {CUSTOM_INT,CUSTOM_COMBO, recursiveLinkUpstream, transformFunc, swapInputs,swapOutputs, renameNodeInputs,renameNodeOutputs, removeNodeInputs,removeNodeOutputs, getDrawColor, computeCanvasSize} from "./utils.js" + + + +function addMultiControlNetApplySelectCanvas(node, app) { +const widget = { + type: "customLamCanvas", + name: "MultiControlNetApply-Canvas", + get value() { + return this.canvas.value; + }, + set value(x) { + this.canvas.value = x; + }, + draw: function (ctx, node, widgetWidth, widgetY) { + + const values = node.properties["values"] + const index = Math.round(node.widgets[node.index].value) + + const selectedColor = getDrawColor(index/values.length, "FF") + ctx.fillStyle = selectedColor + ctx.beginPath(); + + ctx.arc(LiteGraph.NODE_SLOT_HEIGHT*0.5, LiteGraph.NODE_SLOT_HEIGHT*(index+node.originalsize + 0.5)+4, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.lineWidth = 1; + ctx.strokeStyle = "white"; + ctx.stroke(); + }, +}; + +widget.canvas = document.createElement("canvas"); +widget.canvas.className = "dave-custom-canvas"; + +widget.parent = node; +document.body.appendChild(widget.canvas); +node.addCustomWidget(widget); + +return { minWidth: 200, minHeight: 200, widget } +} +// Displays input text on a node +app.registerExtension({ + name: "Comfy.lam.MultiControlNetApply", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + var names=["MultiControlNetApply"] + if (names.indexOf(nodeData.name)>=0) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + let control_net_name = this.widgets[this.widgets.findIndex(obj => obj.name === 'control_net_name')]; + let netNames=control_net_name.options.values; + this.setProperty("values", [[netNames[0],1.0],[netNames[0],1.0]]) + this.index=2 + this.originalsize=1 + this.inputType="IMAGE" + this.inputPrefix="image" + this.outputPrefix="" + + control_net_name.callback = function (v,_, node) { + node.properties["values"][node.widgets[node.index].value][0] = this.value + } + CUSTOM_INT(this, "strength", 1.0,function (v, _, node) {node.properties["values"][node.widgets[node.index].value][1] = this.value},{"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01,precision: 2 }) + CUSTOM_INT( + this, + "index", + 0, + function (v, _, node) { + let values = node.properties["values"] + node.widgets[node.index-2].value = values[v][0] + node.widgets[node.index-1].value = values[v][1] + }, + { step: 10, max: 1 } + + ) + //CUSTOM_COMBO(this, "control_net_name", netNames[0],function (v, _, node) {node.properties["values"][node.widgets[node.index].value][0] = this.value},{values:netNames}).widget; + //this.widgets.reverse(); + addMultiControlNetApplySelectCanvas(this, app) + + this.getExtraMenuOptions = function(_, options) { + options.unshift( + { + content: `最前插入参 /\\`, + callback: () => { + this.addInput(this.inputPrefix, this.inputType) + + const inputLenth = this.inputs.length-1-this.originalsize + const index = 0+this.originalsize + + for (let i = inputLenth; i > index; i--) { + swapInputs(this, i, i-1) + } + renameNodeInputs(this, this.inputPrefix,this.originalsize) + this.properties["values"].splice(index, 0, [netNames[0],1.0]) + this.widgets[this.index].options.max = inputLenth + this.setDirtyCanvas(true); + }, + }, + { + content: `最后插入参 \\/`, + callback: () => { + this.addInput(this.inputPrefix, this.inputType) + + const inputLenth = this.inputs.length-1-this.originalsize + const index = this.widgets[this.index].value+this.originalsize + + renameNodeInputs(this, this.inputPrefix,this.originalsize) + this.properties["values"].splice(index, 0, [netNames[0],1.0]) + this.widgets[this.index].options.max = inputLenth + this.setDirtyCanvas(true); + }, + }, + { + content: "删除全部未连接的入参框", + callback: () => { + let indexesToRemove = [] + + for (let i = 0; i < this.inputs.length; i++) { + if (!this.inputs[i].link&&i>=this.originalsize) { + indexesToRemove.push(i) + } + } + + removeNodeInputs(this, indexesToRemove,this.originalsize) + renameNodeInputs(this, this.inputPrefix,this.originalsize) + }, + } + ); + } + + + this.onSelected = function () { + this.selected = true + } + this.onDeselected = function () { + this.selected = false + } + + return r; + } + + } + }, + loadedGraphNode(node, _) { + if (node.type === "MultiControlNetApply") { + node.widgets[node.index].options["max"] = node.properties["values"].length-1 + } + }, +}); \ No newline at end of file diff --git a/js/MultiGLIGENTextBoxApply.js b/js/MultiGLIGENTextBoxApply.js index baaa7b5..66615b7 100644 --- a/js/MultiGLIGENTextBoxApply.js +++ b/js/MultiGLIGENTextBoxApply.js @@ -117,7 +117,6 @@ function addMultiGLIGENTextBoxApplyCanvas(node, app) { ctx.stroke(); ctx.closePath(); // Draw currently selected zone - console.log(index) let [x, y, w, h] = getDrawArea(values[index]) w = Math.max(32*scale, w) @@ -249,7 +248,7 @@ app.registerExtension({ callback: () => { this.addInput("text", "STRING") - const inputLenth = this.inputs.length-1 + const inputLenth = this.inputs.length-1-this.originalsize const index = this.widgets[this.index].value+this.originalsize for (let i = inputLenth; i > index; i--) { @@ -268,8 +267,8 @@ app.registerExtension({ content: `${this.widgets[this.index].value}框后插入 \\/`, callback: () => { this.addInput("text", "STRING") - - const inputLenth = this.inputs.length-1 + + const inputLenth = this.inputs.length-1-this.originalsize const index = this.widgets[this.index].value+this.originalsize for (let i = inputLenth; i > index+1; i--) { diff --git a/js/MultiTextSetMask.js b/js/MultiTextSetMask.js new file mode 100644 index 0000000..2d70a10 --- /dev/null +++ b/js/MultiTextSetMask.js @@ -0,0 +1,149 @@ +import { app } from "/scripts/app.js"; +import { ComfyWidgets } from "/scripts/widgets.js"; +import {CUSTOM_INT,CUSTOM_COMBO, recursiveLinkUpstream, transformFunc, swapInputs,swapOutputs, renameNodeInputs,renameNodeOutputs, removeNodeInputs,removeNodeOutputs, getDrawColor, computeCanvasSize} from "./utils.js" + + + +function addMultiTextSetMaskSelectCanvas(node, app) { +const widget = { + type: "customLamCanvas", + name: "MultiTextSetMask-Canvas", + get value() { + return this.canvas.value; + }, + set value(x) { + this.canvas.value = x; + }, + draw: function (ctx, node, widgetWidth, widgetY) { + + const values = node.properties["values"] + const index = Math.round(node.widgets[node.index].value) + + const selectedColor = getDrawColor(index/values.length, "FF") + ctx.fillStyle = selectedColor + ctx.beginPath(); + + ctx.arc(LiteGraph.NODE_SLOT_HEIGHT*0.5, LiteGraph.NODE_SLOT_HEIGHT*(index+node.originalsize + 0.5)+4, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.lineWidth = 1; + ctx.strokeStyle = "white"; + ctx.stroke(); + }, +}; + +widget.canvas = document.createElement("canvas"); +widget.canvas.className = "dave-custom-canvas"; + +widget.parent = node; +document.body.appendChild(widget.canvas); +node.addCustomWidget(widget); + +return { minWidth: 200, minHeight: 200, widget } +} +// Displays input text on a node +app.registerExtension({ + name: "Comfy.lam.MultiTextSetMask", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + var names=["MultiTextSetMask"] + if (names.indexOf(nodeData.name)>=0) { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + this.setProperty("values", [[1.0,"default"],[1.0,"default"]]) + this.index=3 + this.originalsize=3 + this.inputType="STRING" + this.inputPrefix="text" + this.outputPrefix="" + + CUSTOM_INT( + this, + "index", + 0, + function (v, _, node) { + let values = node.properties["values"] + + node.widgets[node.index+1].value = values[v][0] + node.widgets[node.index+2].value = values[v][1] + }, + { step: 10, max: 1 } + + ) + + CUSTOM_INT(this, "strength", 1.0,function (v, _, node) {node.properties["values"][node.widgets[node.index].value][0] = this.value},{"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01,precision: 2 }) + CUSTOM_COMBO(this, "set_cond_area", "default",function (v, _, node) {node.properties["values"][node.widgets[node.index].value][1] = this.value},{values:["default", "mask bounds"]}).widget; + addMultiTextSetMaskSelectCanvas(this, app) + + this.getExtraMenuOptions = function(_, options) { + options.unshift( + { + content: `最前插入参 /\\`, + callback: () => { + this.addInput(this.inputPrefix, this.inputType) + + const inputLenth = this.inputs.length-1-this.originalsize + const index = 0+this.originalsize + + for (let i = inputLenth; i > index; i--) { + swapInputs(this, i, i-1) + } + renameNodeInputs(this, this.inputPrefix,this.originalsize) + this.properties["values"].splice(index, 0, [1.0,"default"]) + this.widgets[this.index].options.max = inputLenth + this.setDirtyCanvas(true); + }, + }, + { + content: `最后插入参 \\/`, + callback: () => { + this.addInput(this.inputPrefix, this.inputType) + + const inputLenth = this.inputs.length-1-this.originalsize + const index = this.widgets[this.index].value+this.originalsize + + renameNodeInputs(this, this.inputPrefix,this.originalsize) + this.properties["values"].splice(index, 0, [1.0,"default"]) + this.widgets[this.index].options.max = inputLenth + this.setDirtyCanvas(true); + }, + }, + { + content: "删除全部未连接的入参框", + callback: () => { + let indexesToRemove = [] + + for (let i = 0; i < this.inputs.length; i++) { + if (!this.inputs[i].link&&i>=this.originalsize) { + indexesToRemove.push(i) + } + } + + removeNodeInputs(this, indexesToRemove,this.originalsize) + renameNodeInputs(this, this.inputPrefix,this.originalsize) + }, + } + ); + } + + + this.onSelected = function () { + this.selected = true + } + this.onDeselected = function () { + this.selected = false + } + + return r; + } + + } + }, + loadedGraphNode(node, _) { + if (node.type === "MultiTextSetMask") { + node.widgets[node.index].options["max"] = node.properties["values"].length-1 + } + }, +}); \ No newline at end of file diff --git a/js/openPoseEditorPlus.js b/js/openPoseEditorPlus.js index 17a96ba..1b4a4f5 100644 --- a/js/openPoseEditorPlus.js +++ b/js/openPoseEditorPlus.js @@ -104,6 +104,7 @@ class OpenPose { this.addPoseDemoInput.accept = "image/*"; this.addPoseDemoInput.style.display = "none"; this.addPoseDemoInput.addEventListener("change", this.addPoseDemo.bind(this)); + this.yWidth=750 document.body.appendChild(this.addPoseDemoInput); } @@ -786,24 +787,26 @@ function createOpenPose(node, inputName, inputData, app) { .multiplySelf(ctx.getTransform()) .translateSelf(margin, margin + y), w = (widgetWidth - margin * 2 - 3) * transform.a; + + let h=w/this.openpose.children[0].width*this.openpose.children[0].height Object.assign(this.openpose.style, { left: `${transform.a * margin + transform.e}px`, top: `${transform.d + transform.f}px`, width: w + "px", - height: w + "px", + height: h + "px", position: "absolute", zIndex: app.graph._nodes.indexOf(node), }); Object.assign(this.openpose.children[0].style, { width: w + "px", - height: w + "px", + height: h + "px", }); Object.assign(this.openpose.children[1].style, { width: w + "px", - height: w + "px", + height: h + "px", }); Array.from(this.openpose.children[2].children).forEach((element) => { @@ -822,6 +825,7 @@ function createOpenPose(node, inputName, inputData, app) { node.openPose = new OpenPose(node, canvasOpenPose); node.openPose.canvas.setWidth(512); node.openPose.canvas.setHeight(512); + node.setSize([node.openPose.yWidth, node.openPose.yWidth+250]); let index = node.widgets[node.widgets.findIndex(obj => obj.name === 'index')]; let width = node.widgets[node.widgets.findIndex(obj => obj.name === 'width')]; @@ -831,9 +835,13 @@ function createOpenPose(node, inputName, inputData, app) { } width.callback = function (v) { node.openPose.canvas.setWidth(v); + let h=node.openPose.yWidth/node.openPose.canvas.width*node.openPose.canvas.height + node.setSize([node.openPose.yWidth, h+220]); } height.callback = function (v) { node.openPose.canvas.setHeight(v); + let h=512/node.openPose.canvas.width*node.openPose.canvas.height + node.setSize([node.openPose.yWidth, h+220]); } widget.openpose = node.openPose.canvas.wrapperEl; @@ -1080,7 +1088,7 @@ app.registerExtension({ createOpenPose.apply(this, [this, nodeNamePNG, {}, app]); - this.setSize([750, 900]); + this.setSize([750, 950]); return r; }; diff --git a/js/utils.js b/js/utils.js index edde1bb..477e193 100644 --- a/js/utils.js +++ b/js/utils.js @@ -10,6 +10,18 @@ export function CUSTOM_INT(node, inputName, val, func, config = {}) { }; } +export function CUSTOM_COMBO(node, inputName, val, func, config = {}) { + return { + widget: node.addWidget( + "combo", + inputName, + val, + func, + Object.assign({}, config) + ), + }; +} + export function recursiveLinkUpstream(node, type, depth, index=null) { depth += 1 let connections = [] diff --git a/py/MultiControlNetApply.py b/py/MultiControlNetApply.py new file mode 100644 index 0000000..d17af24 --- /dev/null +++ b/py/MultiControlNetApply.py @@ -0,0 +1,68 @@ +import folder_paths +import comfy.controlnet +class MultiControlNetApply: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "conditioning": ("CONDITIONING", ), + "image0": ("IMAGE", ), + }, + "optional": { + "image1": ("IMAGE", {"extNetName":folder_paths.get_filename_list("controlnet")}), + "control_net_name": (folder_paths.get_filename_list("controlnet"), ) + }, + "hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "multi_control_net_apply" + + CATEGORY = "lam" + + def multi_control_net_apply(self, conditioning, extra_pnginfo, unique_id,**kwargs): + values=[] + for node in extra_pnginfo["workflow"]["nodes"]: + if node["id"] == int(unique_id): + values = node["properties"]["values"] + break + imageList=[] + for arg in kwargs: + if arg.startswith('image'): + imageList.append(kwargs[arg]) + + minSize=len(imageList) + for i in range(minSize): + controlnet_path = folder_paths.get_full_path("controlnet", values[i][0]) + controlnet = comfy.controlnet.load_controlnet(controlnet_path) + conditioning=self.apply_controlnet(conditioning, controlnet, imageList[i], float(values[i][1])) + + return (conditioning,) + + def apply_controlnet(self, conditioning, control_net, image, strength): + if strength == 0: + return conditioning + + c = [] + control_hint = image.movedim(-1,1) + for t in conditioning: + n = [t[0], t[1].copy()] + c_net = control_net.copy().set_cond_hint(control_hint, strength) + if 'control' in t[1]: + c_net.set_previous_controlnet(t[1]['control']) + n[1]['control'] = c_net + n[1]['control_apply_to_uncond'] = True + c.append(n) + return c + + +NODE_CLASS_MAPPINGS = { + "MultiControlNetApply": MultiControlNetApply +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MultiControlNetApply": "多ControlNet应用" +} \ No newline at end of file diff --git a/py/MultiGLIGENTextBoxApply.py b/py/MultiGLIGENTextBoxApply.py index 1528084..079422f 100644 --- a/py/MultiGLIGENTextBoxApply.py +++ b/py/MultiGLIGENTextBoxApply.py @@ -51,10 +51,7 @@ class MultiGLIGENTextBoxApply: k+=1 return (conditioning, resolutionX, resolutionY) - def encode(self, clip, text): - tokens = clip.tokenize(text) - cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) - return ([[cond, {"pooled_output": pooled}]], ) + def append(self, conditioning_to, clip, gligen_textbox_model, text, width, height, x, y): c = [] cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) diff --git a/py/MultiTextSetMask.py b/py/MultiTextSetMask.py new file mode 100644 index 0000000..f46ea58 --- /dev/null +++ b/py/MultiTextSetMask.py @@ -0,0 +1,75 @@ + +class MultiTextSetMask: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "clip": ("CLIP", ), + "body_masks": ("MASKS",), + "background_text": ("STRING", {"forceInput": True}), + "text0": ("STRING", {"forceInput": True}), + }, + "optional": { + "text1": ("STRING", {"forceInput": True}), + }, + "hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "multi_text_set_masks" + + CATEGORY = "lam" + + def multi_text_set_masks(self, clip,body_masks,background_text, extra_pnginfo, unique_id,**kwargs): + values=[] + for node in extra_pnginfo["workflow"]["nodes"]: + if node["id"] == int(unique_id): + values = node["properties"]["values"] + break + textList=[] + for arg in kwargs: + if type(kwargs[arg]) != str: + continue + if arg.startswith('text'): + textList.append(kwargs[arg]) + + minSize=min([len(body_masks),len(textList)]) + conditioning=self.encode(clip,background_text) + for i in range(minSize): + conditioning_1=self.encode(clip,textList[i]) + conditioning_2=self.condAppend(conditioning_1,body_masks[i],values[i][0],values[i][1]) + conditioning=conditioning+conditioning_2 + + return (conditioning,) + + def encode(self, clip, text): + tokens = clip.tokenize(text) + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + return [[cond, {"pooled_output": pooled}]] + + def condAppend(self, conditioning, mask, strength, set_cond_area): + c = [] + set_area_to_bounds = False + if set_cond_area != "default": + set_area_to_bounds = True + if len(mask.shape) < 3: + mask = mask.unsqueeze(0) + for t in conditioning: + n = [t[0], t[1].copy()] + _, h, w = mask.shape + n[1]['mask'] = mask + n[1]['set_area_to_bounds'] = set_area_to_bounds + n[1]['mask_strength'] = strength + c.append(n) + return c + +NODE_CLASS_MAPPINGS = { + "MultiTextSetMask": MultiTextSetMask +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "MultiTextSetMask": "多文本遮罩条件设置" +} \ No newline at end of file diff --git a/py/openPoseEditorPlus.py b/py/openPoseEditorPlus.py index 90114b2..c36fb1a 100644 --- a/py/openPoseEditorPlus.py +++ b/py/openPoseEditorPlus.py @@ -254,8 +254,8 @@ class openPoseEditorPlus: candidate=[[i[0]/float(width),i[1]/float(height)] for i in candidate ] - canvas = np.zeros(shape=(width, height, 3), dtype=np.uint8) - canvasHands = np.zeros(shape=(width, height, 3), dtype=np.uint8) + canvas = np.zeros(shape=(height,width, 3), dtype=np.uint8) + canvasHands = np.zeros(shape=(height,width, 3), dtype=np.uint8) for i in range(len(hands)): zhand=hands[i] for hand in zhand: