Fixed recursive execute for xyPlot
This commit is contained in:
+64
-113
@@ -53,6 +53,7 @@ from nodes import MAX_RESOLUTION, ControlNetApplyAdvanced
|
||||
from nodes import NODE_CLASS_MAPPINGS as COMFY_CLASS_MAPPINGS
|
||||
|
||||
from .utils import CC, ttNl, ttNpaths, AnyType
|
||||
from .ttNexecutor import xyExecutor
|
||||
|
||||
class ttNloader:
|
||||
def __init__(self):
|
||||
@@ -439,18 +440,14 @@ class ttNsampler:
|
||||
)
|
||||
|
||||
class ttNadv_xyPlot:
|
||||
def __init__(self, adv_xyPlot, unique_id, prompt, extra_pnginfo, save_prefix, image_output):
|
||||
def __init__(self, adv_xyPlot, unique_id, prompt, extra_pnginfo, save_prefix, image_output, executor):
|
||||
self.executor = executor
|
||||
self.unique_id = str(unique_id)
|
||||
self.prompt = prompt
|
||||
self.extra_pnginfo = extra_pnginfo
|
||||
self.save_prefix = save_prefix
|
||||
self.image_output = image_output
|
||||
|
||||
self.outputs = {}
|
||||
self.outputs_ui = {}
|
||||
self.object_storage = {}
|
||||
|
||||
self.results = {}
|
||||
self.latent_list = []
|
||||
self.image_list = []
|
||||
self.ui_list = []
|
||||
@@ -471,6 +468,13 @@ class ttNadv_xyPlot:
|
||||
self.num = 0
|
||||
self.total = (self.num_cols if self.num_cols > 0 else 1) * (self.num_rows if self.num_rows > 0 else 1)
|
||||
|
||||
def reset(self):
|
||||
self.executor.reset()
|
||||
self.executor = None
|
||||
self.latent_list = []
|
||||
self.image_list = []
|
||||
self.ui_list = []
|
||||
|
||||
@staticmethod
|
||||
def get_font(font_size):
|
||||
return ImageFont.truetype(str(Path(ttNpaths.font_path)), font_size)
|
||||
@@ -574,6 +578,17 @@ class ttNadv_xyPlot:
|
||||
def getRelevantPrompt(self):
|
||||
nodes_to_keep = self._getNodesToKeep(self.unique_id, self.prompt)
|
||||
new_prompt = {node_id: self.prompt[node_id] for node_id in nodes_to_keep}
|
||||
|
||||
if self.save_individuals == True:
|
||||
if self.image_output in ["Hide", "Hide/Save"]:
|
||||
new_prompt[self.unique_id]["inputs"]["image_output"] = "Hide/Save"
|
||||
else:
|
||||
new_prompt[self.unique_id]["inputs"]["image_output"] = "Save"
|
||||
elif self.image_output in ["Preview", "Save"]:
|
||||
new_prompt[self.unique_id]["inputs"]["image_output"] = "Preview"
|
||||
else:
|
||||
new_prompt[self.unique_id]["inputs"]["image_output"] = "Hide"
|
||||
|
||||
return new_prompt
|
||||
|
||||
def plot_images(self):
|
||||
@@ -620,104 +635,28 @@ class ttNadv_xyPlot:
|
||||
return int(initial_font_size * (label_width / text_width) * scaling_factor)
|
||||
else:
|
||||
return initial_font_size
|
||||
|
||||
def execute_node(self, unique_id, prompt, object_storage, extra_data):
|
||||
inputs = prompt[str(unique_id)]["inputs"]
|
||||
class_type = prompt[str(unique_id)]["class_type"]
|
||||
if class_type == "ttN advanced xyPlot":
|
||||
class_def = ttN_Plotting #Fake class to avoid recursive execute of xy_plot node
|
||||
else:
|
||||
class_def = COMFY_CLASS_MAPPINGS[class_type]
|
||||
|
||||
for x in inputs:
|
||||
input_data = inputs[x]
|
||||
|
||||
if isinstance(input_data, list):
|
||||
input_unique_id = input_data[0]
|
||||
output_index = input_data[1]
|
||||
if input_unique_id not in self.outputs:
|
||||
if input_unique_id not in prompt:
|
||||
input_data = None
|
||||
else:
|
||||
self.outputs = self.execute_node(input_unique_id, prompt, object_storage, extra_data)
|
||||
|
||||
input_data = self.outputs[input_unique_id][output_index]
|
||||
|
||||
input_data_all = None
|
||||
try:
|
||||
input_data_all = execution.get_input_data(inputs, class_def, unique_id, self.outputs, prompt, extra_data)
|
||||
|
||||
obj = object_storage.get((unique_id, class_type), None)
|
||||
if obj is None:
|
||||
obj = class_def()
|
||||
object_storage[(unique_id, class_type)] = obj
|
||||
|
||||
output_data, output_ui = execution.get_output_data(obj, input_data_all)
|
||||
self.outputs[unique_id] = output_data
|
||||
self.outputs_ui[unique_id] = output_ui
|
||||
except comfy.model_management.InterruptProcessingException as iex:
|
||||
logging.info("Processing interrupted")
|
||||
raise iex
|
||||
|
||||
return self.outputs
|
||||
|
||||
def execute_prompt(self, prompt, extra_data, xpoint, ypoint, x_label, y_label):
|
||||
if self.save_individuals == True:
|
||||
if self.image_output in ["Hide", "Hide/Save"]:
|
||||
prompt[self.unique_id]["inputs"]["image_output"] = "Hide/Save"
|
||||
else:
|
||||
prompt[self.unique_id]["inputs"]["image_output"] = "Save"
|
||||
|
||||
elif self.image_output in ["Preview", "Save"]:
|
||||
prompt[self.unique_id]["inputs"]["image_output"] = "Preview"
|
||||
else:
|
||||
prompt[self.unique_id]["inputs"]["image_output"] = "Hide"
|
||||
|
||||
self.num += 1
|
||||
ttNl(f'{CC.GREY}X: {x_label}, Y: {y_label}').t(f'Plot Values {self.num}/{self.total} ->').p()
|
||||
def execute_prompt(self, prompt, extra_data, x_label, y_label):
|
||||
valid = execution.validate_prompt(prompt)
|
||||
|
||||
with torch.inference_mode():
|
||||
#delete cached outputs if nodes don't exist for them
|
||||
to_delete = []
|
||||
for o in self.outputs:
|
||||
if o not in prompt:
|
||||
to_delete += [o]
|
||||
for o in to_delete:
|
||||
d = self.outputs.pop(o)
|
||||
d2 = self.outputs_ui.pop(o)
|
||||
del d
|
||||
del d2
|
||||
to_delete = []
|
||||
for o in self.object_storage:
|
||||
if o[0] not in prompt:
|
||||
to_delete += [o]
|
||||
else:
|
||||
p = prompt[o[0]]
|
||||
if o[1] != p['class_type']:
|
||||
to_delete += [o]
|
||||
for o in to_delete:
|
||||
d = self.object_storage.pop(o)
|
||||
del d
|
||||
|
||||
comfy.model_management.cleanup_models(keep_clone_weights_loaded=True)
|
||||
if valid[0]:
|
||||
ttNl(f'{CC.GREY}X: {x_label}, Y: {y_label}').t(f'Plot Values {self.num}/{self.total} ->').p()
|
||||
|
||||
for node_id in prompt:
|
||||
self.execute_node(node_id, prompt, self.object_storage, extra_data)
|
||||
self.executor.execute(prompt, self.num, extra_data, valid[2])
|
||||
|
||||
if comfy.model_management.DISABLE_SMART_MEMORY:
|
||||
comfy.model_management.unload_all_models()
|
||||
self.latent_list.append(self.executor.outputs[self.unique_id][-6][0]["samples"])
|
||||
ui_out = self.executor.outputs_ui[self.unique_id].get('images')
|
||||
if ui_out is not None and len(ui_out) > 0:
|
||||
self.ui_list.append(ui_out[0])
|
||||
|
||||
self.latent_list.append(self.outputs[self.unique_id][-6][0]["samples"])
|
||||
ui_out = self.outputs_ui[self.unique_id].get('images')
|
||||
if ui_out is not None and len(ui_out) > 0:
|
||||
self.ui_list.append(ui_out[0])
|
||||
image = self.executor.outputs[self.unique_id][-3][0]
|
||||
pil_image = ttNsampler.tensor2pil(image)
|
||||
self.image_list.append(pil_image)
|
||||
|
||||
image = self.outputs[self.unique_id][-3][0]
|
||||
pil_image = ttNsampler.tensor2pil(image)
|
||||
self.image_list.append(pil_image)
|
||||
|
||||
self.max_width = max(self.max_width, pil_image.width)
|
||||
self.max_height = max(self.max_height, pil_image.height)
|
||||
self.max_width = max(self.max_width, pil_image.width)
|
||||
self.max_height = max(self.max_height, pil_image.height)
|
||||
else:
|
||||
raise Exception(valid[1])
|
||||
|
||||
@staticmethod
|
||||
def _parse_value(input_name, value, node_inputs, input_types, regex):
|
||||
@@ -750,13 +689,14 @@ class ttNadv_xyPlot:
|
||||
value = False
|
||||
value = bool(value)
|
||||
return input_name, value
|
||||
|
||||
|
||||
def xy_plot_process(self):
|
||||
if self.x_points is None and self.y_points is None:
|
||||
return None, None, None, None
|
||||
|
||||
regex = re.compile(r'%(.*?);(.*?)%')
|
||||
|
||||
|
||||
x_label, y_label = None, None
|
||||
base_prompt = self.getRelevantPrompt()
|
||||
|
||||
for xpoint, nodes in self.x_points.items():
|
||||
@@ -799,14 +739,16 @@ class ttNadv_xyPlot:
|
||||
for input_name, value in inputs.items():
|
||||
input_name, value = self._parse_value(input_name, value, y_node_inputs, y_input_types, regex)
|
||||
y_node_inputs[input_name] = value
|
||||
|
||||
self.execute_prompt(y_prompt, self.extra_pnginfo, xpoint, ypoint, x_label, y_label)
|
||||
|
||||
self.num += 1
|
||||
self.execute_prompt(y_prompt, self.extra_pnginfo, x_label, y_label)
|
||||
else:
|
||||
self.execute_prompt(x_prompt, self.extra_pnginfo, xpoint, "1", x_label, None)
|
||||
self.num += 1
|
||||
self.execute_prompt(x_prompt, self.extra_pnginfo, x_label, y_label)
|
||||
|
||||
# Rearrange latent array to match preview image grid
|
||||
self.latent_list = self.rearrange_tensors(self.latent_list, self.num_cols, self.num_rows)
|
||||
rearranged_image_list = self.rearrange_tensors(self.image_list, self.num_cols, self.num_rows)
|
||||
self.image_list = self.rearrange_tensors(self.image_list, self.num_cols, self.num_rows)
|
||||
|
||||
if self.image_output in ["Preview", "Save"]:
|
||||
rearranged_ui_list = self.rearrange_tensors(self.ui_list, self.num_cols, self.num_rows)
|
||||
@@ -818,7 +760,7 @@ class ttNadv_xyPlot:
|
||||
plot_image = self.plot_images()
|
||||
|
||||
images = []
|
||||
for image in rearranged_image_list:
|
||||
for image in self.image_list:
|
||||
images.append(sampler.pil2tensor(image))
|
||||
|
||||
images_out = torch.cat(images, dim=0)
|
||||
@@ -1312,8 +1254,11 @@ class ttN_pipeKSampler_v2:
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output)
|
||||
executor = xyExecutor()
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output, executor)
|
||||
plot_image, images, samples, ui_results = plotter.xy_plot_process()
|
||||
plotter.reset()
|
||||
del executor, plotter
|
||||
|
||||
if samples is None and images is None:
|
||||
return process_sample_state(samp_model, samp_images, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, lora_name, lora_model_strength, lora_clip_strength,
|
||||
@@ -1323,7 +1268,7 @@ class ttN_pipeKSampler_v2:
|
||||
|
||||
|
||||
plot_result = ttN_save.images(plot_image, save_prefix, image_output, embed_workflow, file_type)
|
||||
plot_result.extend(ui_results)
|
||||
#plot_result.extend(ui_results)
|
||||
|
||||
new_pipe = {
|
||||
"model": samp_model,
|
||||
@@ -1793,9 +1738,12 @@ class ttN_pipeKSamplerSDXL_v2:
|
||||
image_output, save_prefix, file_type, embed_workflow, prompt, extra_pnginfo, my_unique_id, preview_latent, adv_xyPlot):
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output)
|
||||
|
||||
executor = xyExecutor()
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output, executor)
|
||||
plot_image, images, samples, ui_results = plotter.xy_plot_process()
|
||||
plotter.reset()
|
||||
del executor, plotter
|
||||
|
||||
if samples is None and images is None:
|
||||
return process_sample_state(sdxl_model, sdxl_images, sdxl_clip, sdxl_samples, sdxl_vae, sdxl_seed, sdxl_positive, sdxl_negative, lora_name, lora_model_strength, lora_clip_strength,
|
||||
@@ -1806,7 +1754,7 @@ class ttN_pipeKSamplerSDXL_v2:
|
||||
|
||||
|
||||
plot_result = ttN_save.images(plot_image, save_prefix, image_output, embed_workflow, file_type)
|
||||
plot_result.extend(ui_results)
|
||||
#plot_result.extend(ui_results)
|
||||
|
||||
new_sdxl_pipe = {
|
||||
"model": sdxl_model,
|
||||
@@ -2412,9 +2360,12 @@ class ttN_KSampler_v2:
|
||||
image_output, save_prefix, file_type, embed_workflow, prompt, extra_pnginfo, my_unique_id, preview_latent, adv_xyPlot):
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output)
|
||||
|
||||
executor = xyExecutor()
|
||||
plotter = ttNadv_xyPlot(adv_xyPlot, my_unique_id, prompt, extra_pnginfo, save_prefix, image_output, executor)
|
||||
plot_image, images, samples, ui_results = plotter.xy_plot_process()
|
||||
plotter.reset()
|
||||
del executor, plotter
|
||||
|
||||
if samples is None and images is None:
|
||||
return process_sample_state(model, images, clip, samples, vae, seed, positive, negative, lora_name, lora_model_strength, lora_clip_strength,
|
||||
@@ -2424,7 +2375,7 @@ class ttN_KSampler_v2:
|
||||
|
||||
|
||||
plot_result = ttN_save.images(plot_image, save_prefix, image_output, embed_workflow, file_type)
|
||||
plot_result.extend(ui_results)
|
||||
#plot_result.extend(ui_results)
|
||||
|
||||
if image_output in ("Hide", "Hide/Save"):
|
||||
return (model, positive, negative, samples, vae, clip, images, seed, plot_image)
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
import nodes
|
||||
import torch
|
||||
import comfy.model_management
|
||||
import copy
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
from execution import full_type_name, get_input_data, get_output_data
|
||||
|
||||
class ttN_advanced_XYPlot:
|
||||
version = '1.1.0'
|
||||
plotPlaceholder = "_PLOT\nExample:\n\n<axis number:label1>\n[node_ID:widget_Name='value']\n\n<axis number2:label2>\n[node_ID:widget_Name='value2']\n[node_ID:widget2_Name='value']\n[node_ID2:widget_Name='value']\n\netc..."
|
||||
|
||||
def get_plot_points(plot_data, unique_id):
|
||||
if plot_data is None or plot_data.strip() == '':
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
axis_dict = {}
|
||||
lines = plot_data.split('<')
|
||||
new_lines = []
|
||||
temp_line = ''
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('lora'):
|
||||
temp_line += '<' + line
|
||||
new_lines[-1] = temp_line
|
||||
else:
|
||||
new_lines.append(line)
|
||||
temp_line = line
|
||||
|
||||
for line in new_lines:
|
||||
if line:
|
||||
values_label = []
|
||||
line = line.split('>', 1)
|
||||
num, label = line[0].split(':', 1)
|
||||
axis_dict[num] = {"label": label}
|
||||
for point in line[1].split('['):
|
||||
if point.strip() != '':
|
||||
node_id = point.split(':', 1)[0]
|
||||
axis_dict[num][node_id] = {}
|
||||
input_name = point.split(':', 1)[1].split('=')[0]
|
||||
value = point.split("'")[1].split("'")[0]
|
||||
values_label.append((value, input_name, node_id))
|
||||
|
||||
axis_dict[num][node_id][input_name] = value
|
||||
|
||||
if label in ['v_label', 'tv_label', 'idtv_label']:
|
||||
new_label = []
|
||||
for value, input_name, node_id in values_label:
|
||||
if label == 'v_label':
|
||||
new_label.append(value)
|
||||
elif label == 'tv_label':
|
||||
new_label.append(f'{input_name}: {value}')
|
||||
elif label == 'idtv_label':
|
||||
new_label.append(f'[{node_id}] {input_name}: {value}')
|
||||
axis_dict[num]['label'] = ', '.join(new_label)
|
||||
|
||||
except ValueError:
|
||||
ttNl('Invalid Plot - defaulting to None...').t(f'advanced_XYPlot[{unique_id}]').warn().p()
|
||||
return None
|
||||
return axis_dict
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"grid_spacing": ("INT",{"min": 0, "max": 500, "step": 5, "default": 0,}),
|
||||
"save_individuals": ("BOOLEAN", {"default": False}),
|
||||
"flip_xy": ("BOOLEAN", {"default": False}),
|
||||
|
||||
"x_plot": ("STRING",{"default": '', "multiline": True, "placeholder": 'X' + ttN_advanced_XYPlot.plotPlaceholder, "pysssss.autocomplete": False}),
|
||||
"y_plot": ("STRING",{"default": '', "multiline": True, "placeholder": 'Y' + ttN_advanced_XYPlot.plotPlaceholder, "pysssss.autocomplete": False}),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": ("PROMPT",),
|
||||
"extra_pnginfo": ("EXTRA_PNGINFO",),
|
||||
"my_unique_id": ("MY_UNIQUE_ID",),
|
||||
"ttNnodeVersion": ttN_advanced_XYPlot.version,
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ADV_XYPLOT", )
|
||||
RETURN_NAMES = ("adv_xyPlot", )
|
||||
FUNCTION = "plot"
|
||||
|
||||
CATEGORY = "🌏 tinyterra/xyPlot"
|
||||
|
||||
def plot(self, grid_spacing, save_individuals, flip_xy, x_plot=None, y_plot=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
x_plot = ttN_advanced_XYPlot.get_plot_points(x_plot, my_unique_id)
|
||||
y_plot = ttN_advanced_XYPlot.get_plot_points(y_plot, my_unique_id)
|
||||
|
||||
if x_plot == {}:
|
||||
x_plot = None
|
||||
if y_plot == {}:
|
||||
y_plot = None
|
||||
|
||||
if flip_xy == "True":
|
||||
x_plot, y_plot = y_plot, x_plot
|
||||
|
||||
xy_plot = {"x_plot": x_plot,
|
||||
"y_plot": y_plot,
|
||||
"grid_spacing": grid_spacing,
|
||||
"save_individuals": save_individuals,}
|
||||
|
||||
return (xy_plot, )
|
||||
|
||||
class ttN_Plotting(ttN_advanced_XYPlot):
|
||||
def plot(self, **args):
|
||||
xy_plot = None
|
||||
return (xy_plot, )
|
||||
|
||||
|
||||
def map_node_over_list(obj, input_data_all, func, allow_interrupt=False):
|
||||
# check if node wants the lists
|
||||
input_is_list = False
|
||||
if hasattr(obj, "INPUT_IS_LIST"):
|
||||
input_is_list = obj.INPUT_IS_LIST
|
||||
|
||||
if len(input_data_all) == 0:
|
||||
max_len_input = 0
|
||||
else:
|
||||
max_len_input = max([len(x) for x in input_data_all.values()])
|
||||
|
||||
# get a slice of inputs, repeat last input when list isn't long enough
|
||||
def slice_dict(d, i):
|
||||
d_new = dict()
|
||||
for k,v in d.items():
|
||||
d_new[k] = v[i if len(v) > i else -1]
|
||||
return d_new
|
||||
|
||||
results = []
|
||||
if input_is_list:
|
||||
if allow_interrupt:
|
||||
nodes.before_node_execution()
|
||||
results.append(getattr(obj, func)(**input_data_all))
|
||||
elif max_len_input == 0:
|
||||
if allow_interrupt:
|
||||
nodes.before_node_execution()
|
||||
results.append(getattr(obj, func)())
|
||||
else:
|
||||
for i in range(max_len_input):
|
||||
if allow_interrupt:
|
||||
nodes.before_node_execution()
|
||||
results.append(getattr(obj, func)(**slice_dict(input_data_all, i)))
|
||||
return results
|
||||
|
||||
def format_value(x):
|
||||
if x is None:
|
||||
return None
|
||||
elif isinstance(x, (int, float, bool, str)):
|
||||
return x
|
||||
else:
|
||||
return str(x)
|
||||
|
||||
def recursive_execute(prompt, outputs, current_item, extra_data, executed, prompt_id, outputs_ui, object_storage):
|
||||
unique_id = current_item
|
||||
inputs = prompt[unique_id]['inputs']
|
||||
class_type = prompt[unique_id]['class_type']
|
||||
if class_type == "ttN advanced xyPlot":
|
||||
class_def = ttN_Plotting #Fake class to avoid recursive execute of xy_plot node
|
||||
else:
|
||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||
|
||||
if unique_id in outputs:
|
||||
print('returning already executed', unique_id)
|
||||
return (True, None, None)
|
||||
|
||||
for x in inputs:
|
||||
input_data = inputs[x]
|
||||
|
||||
if isinstance(input_data, list):
|
||||
input_unique_id = input_data[0]
|
||||
output_index = input_data[1]
|
||||
if input_unique_id not in outputs:
|
||||
result = recursive_execute(prompt, outputs, input_unique_id, extra_data, executed, prompt_id, outputs_ui, object_storage)
|
||||
if result[0] is not True:
|
||||
# Another node failed further upstream
|
||||
return result
|
||||
|
||||
input_data_all = None
|
||||
try:
|
||||
input_data_all = get_input_data(inputs, class_def, unique_id, outputs, prompt, extra_data)
|
||||
|
||||
obj = object_storage.get((unique_id, class_type), None)
|
||||
if obj is None:
|
||||
obj = class_def()
|
||||
object_storage[(unique_id, class_type)] = obj
|
||||
|
||||
output_data, output_ui = get_output_data(obj, input_data_all)
|
||||
outputs[unique_id] = output_data
|
||||
if len(output_ui) > 0:
|
||||
outputs_ui[unique_id] = output_ui
|
||||
|
||||
except comfy.model_management.InterruptProcessingException as iex:
|
||||
logging.info("Processing interrupted")
|
||||
|
||||
# skip formatting inputs/outputs
|
||||
error_details = {
|
||||
"node_id": unique_id,
|
||||
}
|
||||
|
||||
return (False, error_details, iex)
|
||||
except Exception as ex:
|
||||
typ, _, tb = sys.exc_info()
|
||||
exception_type = full_type_name(typ)
|
||||
input_data_formatted = {}
|
||||
if input_data_all is not None:
|
||||
input_data_formatted = {}
|
||||
for name, inputs in input_data_all.items():
|
||||
input_data_formatted[name] = [format_value(x) for x in inputs]
|
||||
|
||||
output_data_formatted = {}
|
||||
for node_id, node_outputs in outputs.items():
|
||||
output_data_formatted[node_id] = [[format_value(x) for x in l] for l in node_outputs]
|
||||
|
||||
logging.error(f"!!! Exception during xyPlot processing!!! {ex}")
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
error_details = {
|
||||
"node_id": unique_id,
|
||||
"exception_message": str(ex),
|
||||
"exception_type": exception_type,
|
||||
"traceback": traceback.format_tb(tb),
|
||||
"current_inputs": input_data_formatted,
|
||||
"current_outputs": output_data_formatted
|
||||
}
|
||||
return (False, error_details, ex)
|
||||
|
||||
executed.add(unique_id)
|
||||
|
||||
return (True, None, None)
|
||||
|
||||
def recursive_will_execute(prompt, outputs, current_item, memo={}):
|
||||
unique_id = current_item
|
||||
|
||||
if unique_id in memo:
|
||||
return memo[unique_id]
|
||||
|
||||
inputs = prompt[unique_id]['inputs']
|
||||
will_execute = []
|
||||
if unique_id in outputs:
|
||||
return []
|
||||
|
||||
for x in inputs:
|
||||
input_data = inputs[x]
|
||||
if isinstance(input_data, list):
|
||||
input_unique_id = input_data[0]
|
||||
output_index = input_data[1]
|
||||
if input_unique_id not in outputs:
|
||||
will_execute += recursive_will_execute(prompt, outputs, input_unique_id, memo)
|
||||
|
||||
memo[unique_id] = will_execute + [unique_id]
|
||||
return memo[unique_id]
|
||||
|
||||
def recursive_output_delete_if_changed(prompt, old_prompt, outputs, current_item):
|
||||
unique_id = current_item
|
||||
inputs = prompt[unique_id]['inputs']
|
||||
class_type = prompt[unique_id]['class_type']
|
||||
class_def = nodes.NODE_CLASS_MAPPINGS[class_type]
|
||||
|
||||
is_changed_old = ''
|
||||
is_changed = ''
|
||||
to_delete = False
|
||||
if hasattr(class_def, 'IS_CHANGED'):
|
||||
if unique_id in old_prompt and 'is_changed' in old_prompt[unique_id]:
|
||||
is_changed_old = old_prompt[unique_id]['is_changed']
|
||||
if 'is_changed' not in prompt[unique_id]:
|
||||
input_data_all = get_input_data(inputs, class_def, unique_id, outputs)
|
||||
if input_data_all is not None:
|
||||
try:
|
||||
#is_changed = class_def.IS_CHANGED(**input_data_all)
|
||||
is_changed = map_node_over_list(class_def, input_data_all, "IS_CHANGED")
|
||||
prompt[unique_id]['is_changed'] = is_changed
|
||||
except:
|
||||
to_delete = True
|
||||
else:
|
||||
is_changed = prompt[unique_id]['is_changed']
|
||||
|
||||
if unique_id not in outputs:
|
||||
return True
|
||||
|
||||
if not to_delete:
|
||||
if is_changed != is_changed_old:
|
||||
to_delete = True
|
||||
elif unique_id not in old_prompt:
|
||||
to_delete = True
|
||||
elif inputs == old_prompt[unique_id]['inputs']:
|
||||
for x in inputs:
|
||||
input_data = inputs[x]
|
||||
|
||||
if isinstance(input_data, list):
|
||||
input_unique_id = input_data[0]
|
||||
output_index = input_data[1]
|
||||
if input_unique_id in outputs:
|
||||
to_delete = recursive_output_delete_if_changed(prompt, old_prompt, outputs, input_unique_id)
|
||||
else:
|
||||
to_delete = True
|
||||
if to_delete:
|
||||
break
|
||||
else:
|
||||
to_delete = True
|
||||
|
||||
if to_delete:
|
||||
d = outputs.pop(unique_id)
|
||||
del d
|
||||
return to_delete
|
||||
|
||||
|
||||
class xyExecutor:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.outputs = {}
|
||||
self.object_storage = {}
|
||||
self.outputs_ui = {}
|
||||
self.status_messages = []
|
||||
self.success = True
|
||||
self.old_prompt = {}
|
||||
|
||||
def add_message(self, event, data, broadcast: bool):
|
||||
self.status_messages.append((event, data))
|
||||
|
||||
def handle_execution_error(self, prompt_id, prompt, current_outputs, executed, error, ex):
|
||||
node_id = error["node_id"]
|
||||
class_type = prompt[node_id]["class_type"]
|
||||
|
||||
# First, send back the status to the frontend depending
|
||||
# on the exception type
|
||||
if isinstance(ex, comfy.model_management.InterruptProcessingException):
|
||||
mes = {
|
||||
"prompt_id": prompt_id,
|
||||
"node_id": node_id,
|
||||
"node_type": class_type,
|
||||
"executed": list(executed),
|
||||
}
|
||||
self.add_message("execution_interrupted", mes, broadcast=True)
|
||||
else:
|
||||
mes = {
|
||||
"prompt_id": prompt_id,
|
||||
"node_id": node_id,
|
||||
"node_type": class_type,
|
||||
"executed": list(executed),
|
||||
|
||||
"exception_message": error["exception_message"],
|
||||
"exception_type": error["exception_type"],
|
||||
"traceback": error["traceback"],
|
||||
"current_inputs": error["current_inputs"],
|
||||
"current_outputs": error["current_outputs"],
|
||||
}
|
||||
self.add_message("execution_error", mes, broadcast=False)
|
||||
|
||||
# Next, remove the subsequent outputs since they will not be executed
|
||||
to_delete = []
|
||||
for o in self.outputs:
|
||||
if (o not in current_outputs) and (o not in executed):
|
||||
to_delete += [o]
|
||||
if o in self.old_prompt:
|
||||
d = self.old_prompt.pop(o)
|
||||
del d
|
||||
for o in to_delete:
|
||||
d = self.outputs.pop(o)
|
||||
del d
|
||||
|
||||
def execute(self, prompt, prompt_id, extra_data={}, execute_outputs=[]):
|
||||
nodes.interrupt_processing(False)
|
||||
|
||||
self.status_messages = []
|
||||
self.add_message("execution_start", { "prompt_id": prompt_id}, broadcast=False)
|
||||
|
||||
with torch.inference_mode():
|
||||
#delete cached outputs if nodes don't exist for them
|
||||
to_delete = []
|
||||
for o in self.outputs:
|
||||
if o not in prompt:
|
||||
to_delete += [o]
|
||||
for o in to_delete:
|
||||
d = self.outputs.pop(o)
|
||||
del d
|
||||
to_delete = []
|
||||
for o in self.object_storage:
|
||||
if o[0] not in prompt:
|
||||
to_delete += [o]
|
||||
else:
|
||||
p = prompt[o[0]]
|
||||
if o[1] != p['class_type']:
|
||||
to_delete += [o]
|
||||
for o in to_delete:
|
||||
d = self.object_storage.pop(o)
|
||||
del d
|
||||
|
||||
for x in prompt:
|
||||
recursive_output_delete_if_changed(prompt, self.old_prompt, self.outputs, x)
|
||||
|
||||
current_outputs = set(self.outputs.keys())
|
||||
for x in list(self.outputs_ui.keys()):
|
||||
if x not in current_outputs:
|
||||
d = self.outputs_ui.pop(x)
|
||||
del d
|
||||
|
||||
comfy.model_management.cleanup_models(keep_clone_weights_loaded=True)
|
||||
self.add_message("execution_cached",
|
||||
{ "nodes": list(current_outputs) , "prompt_id": prompt_id},
|
||||
broadcast=False)
|
||||
executed = set()
|
||||
output_node_id = None
|
||||
to_execute = []
|
||||
|
||||
for node_id in list(execute_outputs):
|
||||
to_execute += [(0, node_id)]
|
||||
|
||||
while len(to_execute) > 0:
|
||||
#always execute the output that depends on the least amount of unexecuted nodes first
|
||||
memo = {}
|
||||
to_execute = sorted(list(map(lambda a: (len(recursive_will_execute(prompt, self.outputs, a[-1], memo)), a[-1]), to_execute)))
|
||||
output_node_id = to_execute.pop(0)[-1]
|
||||
|
||||
# This call shouldn't raise anything if there's an error deep in
|
||||
# the actual SD code, instead it will report the node where the
|
||||
# error was raised
|
||||
self.success, error, ex = recursive_execute(prompt, self.outputs, output_node_id, extra_data, executed, prompt_id, self.outputs_ui, self.object_storage)
|
||||
if self.success is not True:
|
||||
self.handle_execution_error(prompt_id, prompt, current_outputs, executed, error, ex)
|
||||
break
|
||||
|
||||
for x in executed:
|
||||
self.old_prompt[x] = copy.deepcopy(prompt[x])
|
||||
|
||||
if comfy.model_management.DISABLE_SMART_MEMORY:
|
||||
comfy.model_management.unload_all_models()
|
||||
Reference in New Issue
Block a user