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/folder_paths.py b/folder_paths.py index e321690dd..82aedd43f 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -121,17 +121,22 @@ 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): + 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) return result, dirs @@ -159,7 +164,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} 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)", 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(); diff --git a/web/scripts/app.js b/web/scripts/app.js index 7f5073573..a3661da64 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(); } @@ -1224,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) || {}); @@ -1236,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; }