diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3af1031..c00daf8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,10 +1,10 @@ name: ๐Ÿž Bug Report -title: "[bug] " +title: '[bug] ' description: Report a bug -labels: ["type: ๐Ÿ› bug", "status: ๐Ÿงน needs triage"] +labels: ['type: ๐Ÿ› bug', 'status: ๐Ÿงน needs triage'] assignees: - melMass - + body: - type: markdown attributes: @@ -12,6 +12,8 @@ body: ## Before submiting an issue - Make sure to read the README & INSTALL instructions. - Please search for [existing issues](https://github.com/melMass/comfy_mtb/issues?q=is%3Aissue) around your problem before filing a report. + - Optionally check the `#mtb-nodes` channel on the Banodoco discord: + [![](https://dcbadge.vercel.app/api/server/AXhsabmDhn?style=flat)](https://discord.gg/IAXhsabmDhn) ### Try using the debug mode to get more info @@ -54,7 +56,7 @@ body: default: 0 validations: required: true - + - type: dropdown id: comfy_mode attributes: diff --git a/README.md b/README.md index 4448a34..8204aff 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ [**Install Guide**](./INSTALL.md) | [**Examples**](https://github.com/melMass/comfy_mtb/wiki/Examples) +There is now a dedicated `#mtb-nodes` channel on the Banodoco discord: +[![](https://dcbadge.vercel.app/api/server/AXhsabmDhn?style=flat)](https://discord.gg/IAXhsabmDhn) + +--- + Welcome to the MTB Nodes project! This codebase is open for you to explore and utilize as you wish. Its primary purpose is to build proof-of-concepts (POCs) for implementation in [MLOPs](https://github.com/Bismuth-Consultancy-BV/MLOPs). Many nodes in this project are inspired by existing community contributions or built-in functionalities. Before proceeding, please be aware of the licenses associated with certain libraries used in this project. For example, the `deepbump` library is licensed under [GPLv3](https://github.com/HugoTini/DeepBump/blob/master/LICENSE). @@ -41,6 +46,7 @@ mtb add a few widgets like `COLOR` A few nodes have the concept of "dynamic" inputs: dynamic inputs + # Node List diff --git a/nodes/batch.py b/nodes/batch.py index 99b7c9f..afb1909 100644 --- a/nodes/batch.py +++ b/nodes/batch.py @@ -199,8 +199,8 @@ class BatchFloat: {"default": "Steps"}, ), "count": ("INT", {"default": 1}), - "min": ("FLOAT", {"default": 0.0}), - "max": ("FLOAT", {"default": 1.0}), + "min": ("FLOAT", {"default": 0.0, "step": 0.001}), + "max": ("FLOAT", {"default": 1.0, "step": 0.001}), "easing": ( [ "Linear", @@ -276,7 +276,7 @@ class BatchMerge: FUNCTION = "merge_batches" CATEGORY = "mtb/batch" - def merge_batches(self, fusion_mode, fill, **kwargs): + def merge_batches(self, fusion_mode: str, fill: str, **kwargs): images = kwargs.values() max_frames = max(img.shape[0] for img in images) @@ -340,9 +340,12 @@ class Batch2dTransform: FUNCTION = "transform_batch" CATEGORY = "mtb/batch" - def get_num_elements(self, param) -> int: + def get_num_elements( + self, param: None | torch.Tensor | list[torch.Tensor] | list[float] + ) -> int: if isinstance(param, torch.Tensor): return torch.numel(param) + elif isinstance(param, list): return len(param) @@ -367,20 +370,26 @@ class Batch2dTransform: "At least one transform parameter must be provided" ) - keyframes = {"x": [], "y": [], "zoom": [], "angle": [], "shear": []} + keyframes: dict[str, list[float]] = { + "x": [], + "y": [], + "zoom": [], + "angle": [], + "shear": [], + } default_vals = {"x": 0, "y": 0, "zoom": 1.0, "angle": 0, "shear": 0} - if self.get_num_elements(x) > 0: - keyframes["x"] = x # type: ignore - if self.get_num_elements(y) > 0: - keyframes["y"] = y # type: ignore - if self.get_num_elements(zoom) > 0: - keyframes["zoom"] = zoom # type: ignore - if self.get_num_elements(angle) > 0: - keyframes["angle"] = angle # type: ignore - if self.get_num_elements(shear) > 0: - keyframes["shear"] = shear # type: ignore + if x and self.get_num_elements(x) > 0: + keyframes["x"] = x + if y and self.get_num_elements(y) > 0: + keyframes["y"] = y + if zoom and self.get_num_elements(zoom) > 0: + keyframes["zoom"] = zoom + if angle and self.get_num_elements(angle) > 0: + keyframes["angle"] = angle + if shear and self.get_num_elements(shear) > 0: + keyframes["shear"] = shear for name, values in keyframes.items(): count = len(values) diff --git a/web/comfy_shared.js b/web/comfy_shared.js index 3ab6522..03ea3f7 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -54,7 +54,7 @@ export class LocalStorageManager { // - log utilities function createLogger(emoji, color, consoleMethod = 'log') { - return function (message, ...args) { + return function(message, ...args) { if (window.MTB?.DEBUG) { console[consoleMethod]( `%c${emoji} ${message}`, @@ -155,14 +155,16 @@ export function getWidgetType(config) { } export const setupDynamicConnections = (nodeType, prefix, inputType) => { const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { + // check if it's a list + const inputList = typeof inputType === 'object' + nodeType.prototype.onNodeCreated = function() { const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined - this.addInput(`${prefix}_1`, inputType) + this.addInput(`${prefix}_1`, inputList ? '*' : inputType) return r } const onConnectionsChange = nodeType.prototype.onConnectionsChange - nodeType.prototype.onConnectionsChange = function ( + nodeType.prototype.onConnectionsChange = function( type, index, connected, @@ -171,7 +173,7 @@ export const setupDynamicConnections = (nodeType, prefix, inputType) => { const r = onConnectionsChange ? onConnectionsChange.apply(this, arguments) : undefined - dynamic_connection(this, index, connected, `${prefix}_`, inputType) + dynamic_connection(this, index, connected, `${prefix}_`, inputList) } } export const dynamic_connection = ( @@ -182,9 +184,12 @@ export const dynamic_connection = ( connectionType = 'PSDLAYER', nameArray = [], ) => { + if (!node.inputs[index].name.startsWith(connectionPrefix)) { return } + const listConnection = typeof connectionType === 'object' + // remove all non connected inputs if (!connected && node.inputs.length > 1) { log(`Removing input ${index} (${node.inputs[index].name})`) @@ -198,12 +203,12 @@ export const dynamic_connection = ( node.removeInput(index) // make inputs sequential again - for (let i = 0; i < node.inputs.length; i++) { - const name = - i < nameArray.length ? nameArray[i] : `${connectionPrefix}${i + 1}` - node.inputs[i].label = name - node.inputs[i].name = name - } + // for (let i = 0; i < node.inputs.length; i++) { + // const name = + // i < nameArray.length ? nameArray[i] : `${connectionPrefix}${i + 1}` + // node.inputs[i].label = name + // node.inputs[i].name = name + // } } // add an extra input @@ -216,7 +221,7 @@ export const dynamic_connection = ( log(`Adding input ${nextIndex + 1} (${name})`) - node.addInput(name, connectionType) + node.addInput(name, listConnection ? '*' : connectionType) } } @@ -246,7 +251,7 @@ export function calculateTotalChildrenHeight(parentElement) { */ export function addMenuHandler(nodeType, cb) { const getOpts = nodeType.prototype.getExtraMenuOptions - nodeType.prototype.getExtraMenuOptions = function () { + nodeType.prototype.getExtraMenuOptions = function() { const r = getOpts.apply(this, arguments) cb.apply(this, arguments) return r @@ -409,7 +414,7 @@ function getBrightness(rgbObj) { (parseInt(rgbObj[0]) * 299 + parseInt(rgbObj[1]) * 587 + parseInt(rgbObj[2]) * 114) / - 1000, + 1000, ) } diff --git a/web/imageFeed.js b/web/imageFeed.js index c5894e4..626fec6 100644 --- a/web/imageFeed.js +++ b/web/imageFeed.js @@ -11,7 +11,7 @@ import { api } from '../../scripts/api.js' import { app } from '../../scripts/app.js' -import { LocalStorageManager } from "./comfy_shared.js" +import { LocalStorageManager } from './comfy_shared.js' const styles = { lighbox: { position: 'fixed', @@ -53,9 +53,9 @@ let currentImageIndex = 0 const imageUrls = [] let image_menu = null -const storage = new LocalStorageManager('mtb'); +const storage = new LocalStorageManager('mtb') -let activated = storage.get("image_feed", true) +let activated = storage.get('image_feed', false) app.registerExtension({ name: 'mtb.ImageFeed', @@ -71,19 +71,21 @@ app.registerExtension({ }, }, async onChange(value) { - storage.set("image_feed", value) + storage.set('image_feed', value) activated = value }, }) }, init: async () => { - if (!activated) { return } + if (!activated) { + return + } const pythongossFeed = app.extensions.find( - (e) => e.name == 'pysssss.ImageFeed' + (e) => e.name === 'pysssss.ImageFeed', ) if (pythongossFeed) { console.warn( - "[mtb] - Aborting the loading of mtb's imageFeed in favor of pysssss.ImageFeed" + "[mtb] - Aborting the loading of mtb's imageFeed in favor of pysssss.ImageFeed", ) activated = false // just in case other methods are added later on return @@ -114,7 +116,7 @@ app.registerExtension({ const lightboxCloseBtn = document.createElement('button') Object.assign( lightboxCloseBtn.style, - styles.lightboxBtn({ right: '0', top: '0' }) + styles.lightboxBtn({ right: '0', top: '0' }), ) lightboxCloseBtn.textContent = 'โŒ' @@ -184,7 +186,7 @@ app.registerExtension({ //- append to DOM document.body.append(imageListContainer) - showBtn.textContent = '๐Ÿ–ผ๏ธ' + showBtn.textContent = '๐Ÿ–ผ' showBtn.onclick = () => { imageListContainer.style.display = 'block' showBtn.style.display = 'none' @@ -250,8 +252,9 @@ app.registerExtension({ objectFit: 'cover', }) - img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${src.type - }&subfolder=${encodeURIComponent(src.subfolder)}` + img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${ + src.type + }&subfolder=${encodeURIComponent(src.subfolder)}` imageUrls.push(img.src) diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index dcb3ac2..5a57897 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -20,7 +20,7 @@ import * as THREE from './extern/three.module.js' import { log } from './comfy_shared.js' // NOTE: new widget types registered by MTB Widgets -const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX'] +const newTypes = [/*'BOOL'n,*/ 'COLOR', 'BBOX'] const deprecated_nodes = { // 'Animation Builder': @@ -1060,6 +1060,10 @@ const mtb_widgets = { shared.setupDynamicConnections(nodeType, 'var', '*') break } + case 'Save Data Bundle (mtb)': { + shared.setupDynamicConnections(nodeType, 'data', '*') // [MASK,IMAGE] + break + } case 'Add To Playlist (mtb)': { shared.setupDynamicConnections(nodeType, 'video', 'VIDEO') break diff --git a/web/note_plus.js b/web/note_plus.js index ff89108..e3c206f 100644 --- a/web/note_plus.js +++ b/web/note_plus.js @@ -140,9 +140,6 @@ const themes = [ 'vscode', ] class NotePlus extends LiteGraph.LGraphNode { - title = 'Note+ (mtb)' - category = 'mtb/utils' - // same values as the comfy note color = LGraphCanvas.node_colors.yellow.color bgcolor = LGraphCanvas.node_colors.yellow.bgcolor @@ -692,6 +689,9 @@ app.registerExtension({ registerCustomNodes() { LiteGraph.registerNodeType('Note Plus (mtb)', NotePlus) + NotePlus.category = 'mtb/utils' + NotePlus.title = 'Note+ (mtb)' + NotePlus.title_mode = LiteGraph.NO_TITLE }, })