From d19684707922af9c2399307c8d9bccc1b267cc3b Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Wed, 23 Aug 2023 16:37:31 -0700 Subject: [PATCH 1/7] feat: Add support for excluded_dirs to folder_paths.recursive_search Refactored variable names to better match what they represent --- folder_paths.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/folder_paths.py b/folder_paths.py index e321690dd..16de1bb66 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -121,18 +121,25 @@ def add_model_folder_path(folder_name, full_folder_path): def get_folder_paths(folder_name): return folder_names_and_paths[folder_name][0][:] -def recursive_search(directory): +def recursive_search(directory, excluded_dir_names=None): if not os.path.isdir(directory): return [], {} + + if excluded_dir_names is None: + excluded_dir_names = [] + result = [] dirs = {directory: os.path.getmtime(directory)} - for root, subdir, file in os.walk(directory, followlinks=True): - for filepath in file: - #we os.path,join directory with a blank string to generate a path separator at the end. - result.append(os.path.join(root, filepath).replace(os.path.join(directory,''),'')) - for d in subdir: - path = os.path.join(root, d) + for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True): + print("Checking directory: " + dirpath) + subdirs[:] = [d for d in subdirs if d not in excluded_dir_names] + for file_name in filenames: + relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory) + result.append(relative_path) + for d in subdirs: + path = os.path.join(dirpath, d) dirs[path] = os.path.getmtime(path) + print("Returning from recursive_search" + repr(result)) return result, dirs def filter_files_extensions(files, extensions): From 3e00fa433216335a874bd408de421f9d65432daf Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Wed, 23 Aug 2023 16:50:41 -0700 Subject: [PATCH 2/7] feat: Exclude .git when retrieving filename lists In the future could support user provided excluded dirs via config file --- folder_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/folder_paths.py b/folder_paths.py index 16de1bb66..a18052856 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -166,7 +166,7 @@ def get_filename_list_(folder_name): folders = folder_names_and_paths[folder_name] output_folders = {} for x in folders[0]: - files, folders_all = recursive_search(x) + files, folders_all = recursive_search(x, excluded_dir_names=[".git"]) output_list.update(filter_files_extensions(files, folders[1])) output_folders = {**output_folders, **folders_all} From f368e5ac7d31649b22c0c1e44bc9fa8002fcb117 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 5 Sep 2023 01:22:03 -0400 Subject: [PATCH 3/7] Don't paste nodes when target is a textarea or a text box. --- web/scripts/app.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/scripts/app.js b/web/scripts/app.js index 7f5073573..9c380d3fb 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -717,8 +717,12 @@ export class ComfyApp { this.loadGraphData(workflow); } else { + if (e.target.type === "text" || e.target.type === "textarea") { + return; + } + // Litegraph default paste - this.canvas.pasteFromClipboard(); + this.canvas.pasteFromClipboard(); } From bc1f6e21856f7be25db5c5c2956b89c27db93b3d Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Tue, 5 Sep 2023 15:06:46 -0700 Subject: [PATCH 4/7] fix(ui/widgets): Only set widget forceInput option if a widget is added --- web/scripts/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/scripts/app.js b/web/scripts/app.js index 9c380d3fb..a3661da64 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -1228,6 +1228,7 @@ export class ComfyApp { const inputData = inputs[inputName]; const type = inputData[0]; + let widgetCreated = true; if (Array.isArray(type)) { // Enums Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {}); @@ -1240,8 +1241,10 @@ export class ComfyApp { } else { // Node connection inputs this.addInput(inputName, type); + widgetCreated = false; } - if(inputData[1]?.forceInput && config?.widget) { + + if(widgetCreated && inputData[1]?.forceInput && config?.widget) { if (!config.widget.options) config.widget.options = {}; config.widget.options.forceInput = inputData[1].forceInput; } From 21a563d385ff520e1f7fdaada722212b35fb8d95 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 5 Sep 2023 23:46:37 -0400 Subject: [PATCH 5/7] Remove prints. --- folder_paths.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/folder_paths.py b/folder_paths.py index a18052856..82aedd43f 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -131,7 +131,6 @@ def recursive_search(directory, excluded_dir_names=None): result = [] dirs = {directory: os.path.getmtime(directory)} for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True): - print("Checking directory: " + dirpath) subdirs[:] = [d for d in subdirs if d not in excluded_dir_names] for file_name in filenames: relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory) @@ -139,7 +138,6 @@ def recursive_search(directory, excluded_dir_names=None): for d in subdirs: path = os.path.join(dirpath, d) dirs[path] = os.path.getmtime(path) - print("Returning from recursive_search" + repr(result)) return result, dirs def filter_files_extensions(files, extensions): From f88f7f413afbe04b42c4422e9deedbaa3269ce76 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 6 Sep 2023 03:26:55 -0400 Subject: [PATCH 6/7] Add a ConditioningSetAreaPercentage node. --- comfy/samplers.py | 15 ++++++++++++--- nodes.py | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/comfy/samplers.py b/comfy/samplers.py index 103ac33ff..3250b2edc 100644 --- a/comfy/samplers.py +++ b/comfy/samplers.py @@ -390,11 +390,20 @@ def get_mask_aabb(masks): return bounding_boxes, is_empty -def resolve_cond_masks(conditions, h, w, device): +def resolve_areas_and_cond_masks(conditions, h, w, device): # We need to decide on an area outside the sampling loop in order to properly generate opposite areas of equal sizes. # While we're doing this, we can also resolve the mask device and scaling for performance reasons for i in range(len(conditions)): c = conditions[i] + if 'area' in c[1]: + area = c[1]['area'] + if area[0] == "percentage": + modified = c[1].copy() + area = (max(1, round(area[1] * h)), max(1, round(area[2] * w)), round(area[3] * h), round(area[4] * w)) + modified['area'] = area + c = [c[0], modified] + conditions[i] = c + if 'mask' in c[1]: mask = c[1]['mask'] mask = mask.to(device=device) @@ -622,8 +631,8 @@ class KSampler: positive = positive[:] negative = negative[:] - resolve_cond_masks(positive, noise.shape[2], noise.shape[3], self.device) - resolve_cond_masks(negative, noise.shape[2], noise.shape[3], self.device) + resolve_areas_and_cond_masks(positive, noise.shape[2], noise.shape[3], self.device) + resolve_areas_and_cond_masks(negative, noise.shape[2], noise.shape[3], self.device) calculate_start_end_timesteps(self.model_wrap, negative) calculate_start_end_timesteps(self.model_wrap, positive) diff --git a/nodes.py b/nodes.py index fa26e5939..77d180526 100644 --- a/nodes.py +++ b/nodes.py @@ -159,6 +159,31 @@ class ConditioningSetArea: c.append(n) return (c, ) +class ConditioningSetAreaPercentage: + @classmethod + def INPUT_TYPES(s): + return {"required": {"conditioning": ("CONDITIONING", ), + "width": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), + "height": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}), + "x": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}), + "y": ("FLOAT", {"default": 0, "min": 0, "max": 1.0, "step": 0.01}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + }} + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "append" + + CATEGORY = "conditioning" + + def append(self, conditioning, width, height, x, y, strength): + c = [] + for t in conditioning: + n = [t[0], t[1].copy()] + n[1]['area'] = ("percentage", height, width, y, x) + n[1]['strength'] = strength + n[1]['set_area_to_bounds'] = False + c.append(n) + return (c, ) + class ConditioningSetMask: @classmethod def INPUT_TYPES(s): @@ -1583,6 +1608,7 @@ NODE_CLASS_MAPPINGS = { "ConditioningCombine": ConditioningCombine, "ConditioningConcat": ConditioningConcat, "ConditioningSetArea": ConditioningSetArea, + "ConditioningSetAreaPercentage": ConditioningSetAreaPercentage, "ConditioningSetMask": ConditioningSetMask, "KSamplerAdvanced": KSamplerAdvanced, "SetLatentNoiseMask": SetLatentNoiseMask, @@ -1644,6 +1670,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ConditioningAverage ": "Conditioning (Average)", "ConditioningConcat": "Conditioning (Concat)", "ConditioningSetArea": "Conditioning (Set Area)", + "ConditioningSetAreaPercentage": "Conditioning (Set Area with Percentage)", "ConditioningSetMask": "Conditioning (Set Mask)", "ControlNetApply": "Apply ControlNet", "ControlNetApplyAdvanced": "Apply ControlNet (Advanced)", From cb080e771e1e792e18611ef63d2d6a49aa50a524 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 6 Sep 2023 16:18:02 -0400 Subject: [PATCH 7/7] Lower refresh timeout for search in litegraph. --- web/lib/litegraph.core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index 4bb2f0d99..4a21a1b34 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -11529,7 +11529,7 @@ LGraphNode.prototype.executeAction = function(action) if (timeout) { clearInterval(timeout); } - timeout = setTimeout(refreshHelper, 250); + timeout = setTimeout(refreshHelper, 10); return; } e.preventDefault();