diff --git a/README.en.md b/README.en.md index 3a03f29..0ccab0d 100644 --- a/README.en.md +++ b/README.en.md @@ -35,6 +35,7 @@ PS: Please update [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) to v2, and moved v1 models to **ComfyUI\models\ipadapter** (Otherwise, the latest model is automatically downloaded from Huggingface)
+- Added `easy sv3dLoader` - Added `easy dynamiCrafterLoader` - Added `easy ipadapterApply` - Added `easy ipadapterApplyADV` diff --git a/README.md b/README.md index 1d3e873..b5a31e4 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ PS: 请更新至最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus), 并移动v1版本模型文件至 ComfyUI\models\ipadapter (否则会自动从huggingface下载最新模型)
+- 增加 `easy sv3dLoader` - 增加 `easy dynamiCrafterLoader` - 增加 `easy ipadapterApply` - 增加 `easy ipadapterApplyADV` diff --git a/py/easyNodes.py b/py/easyNodes.py index 625dfa2..a826096 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -25,6 +25,7 @@ from .libs.xyplot import easyXYPlot from .libs.controlnet import easyControlnet from .libs.conditioning import prompt_to_cond, set_cond from .libs.cache import cache, update_cache +from .libs.easing import EasingBase sampler = easySampler() easyCache = easyLoader() @@ -1117,7 +1118,7 @@ class zero123Loader: @classmethod def INPUT_TYPES(cls): def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "zero123" in file] + return [file for file in filenames if file != "put_models_here.txt" and "zero123" in file.lower()] return {"required": { "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), @@ -1197,6 +1198,164 @@ class zero123Loader: return (pipe, model, vae) +# SV3D加载器 +class sv3DLoader(EasingBase): + + def __init__(self): + super().__init__() + + @classmethod + def INPUT_TYPES(cls): + def get_file_list(filenames): + return [file for file in filenames if file != "put_models_here.txt" and "sv3d" in file] + + return {"required": { + "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + + "init_image": ("IMAGE",), + "empty_latent_width": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + + "batch_size": ("INT", {"default": 21, "min": 1, "max": 4096}), + "interp_easing": (["linear", "ease_in", "ease_out", "ease_in_out"], {"default": "linear"}), + "easing_mode": (["azimuth", "elevation", "custom"], {"default": "azimuth"}), + }, + "optional": {"scheduler": ("STRING", {"default": "", "multiline": True})}, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "STRING") + RETURN_NAMES = ("pipe", "model", "interp_log") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, interp_easing, easing_mode, scheduler='',prompt=None, my_unique_id=None): + model: ModelPatcher | None = None + vae: VAE | None = None + clip: CLIP | None = None + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) + + output = clip_vision.encode_image(init_image) + pooled = output.image_embeds.unsqueeze(0) + pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, + -1) + encode_pixels = pixels[:, :, :, :3] + t = vae.encode(encode_pixels) + + azimuth_points = [] + elevation_points = [] + if easing_mode == 'azimuth': + azimuth_points = [(0, 0), (batch_size-1, 360)] + elevation_points = [(0, 0)] * batch_size + elif easing_mode == 'elevation': + azimuth_points = [(0, 0)] * batch_size + elevation_points = [(0, -90), (batch_size-1, 90)] + else: + schedulers = scheduler.rstrip('\n') + for line in schedulers.split('\n'): + frame_str, point_str = line.split(':') + point_str = point_str.strip()[1:-1] + point = point_str.split(',') + azimuth_point = point[0] + elevation_point = point[1] if point[1] else 0.0 + frame = int(frame_str.strip()) + azimuth = float(azimuth_point) + azimuth_points.append((frame, azimuth)) + elevation_val = float(elevation_point) + elevation_points.append((frame, elevation_val)) + azimuth_points.sort(key=lambda x: x[0]) + elevation_points.sort(key=lambda x: x[0]) + + #interpolation + next_point = 1 + next_elevation_point = 1 + elevations = [] + azimuths = [] + # For azimuth interpolation + for i in range(batch_size): + # Find the interpolated azimuth for the current frame + while next_point < len(azimuth_points) and i >= azimuth_points[next_point][0]: + next_point += 1 + if next_point == len(azimuth_points): + next_point -= 1 + prev_point = max(next_point - 1, 0) + + if azimuth_points[next_point][0] != azimuth_points[prev_point][0]: + timing = (i - azimuth_points[prev_point][0]) / ( + azimuth_points[next_point][0] - azimuth_points[prev_point][0]) + interpolated_azimuth = self.ease(azimuth_points[prev_point][1], azimuth_points[next_point][1], self.easing(timing, interp_easing)) + else: + interpolated_azimuth = azimuth_points[prev_point][1] + + # Interpolate the elevation + next_elevation_point = 1 + while next_elevation_point < len(elevation_points) and i >= elevation_points[next_elevation_point][0]: + next_elevation_point += 1 + if next_elevation_point == len(elevation_points): + next_elevation_point -= 1 + prev_elevation_point = max(next_elevation_point - 1, 0) + + if elevation_points[next_elevation_point][0] != elevation_points[prev_elevation_point][0]: + timing = (i - elevation_points[prev_elevation_point][0]) / ( + elevation_points[next_elevation_point][0] - elevation_points[prev_elevation_point][0]) + interpolated_elevation = self.ease(elevation_points[prev_point][1], elevation_points[next_point][1], self.easing(timing, interp_easing)) + else: + interpolated_elevation = elevation_points[prev_elevation_point][1] + + azimuths.append(interpolated_azimuth) + elevations.append(interpolated_elevation) + + log_node_info("easy sv3dLoader", "azimuths:" + str(azimuths)) + log_node_info("easy sv3dLoader", "elevations:" + str(elevations)) + + log = 'azimuths:' + str(azimuths) + '\n\n' + "elevations:" + str(elevations) + # Structure the final output + positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]] + negative = [[torch.zeros_like(pooled), + {"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]] + + latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]) + samples = {"samples": latent} + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + + pipe = {"model": model, + "positive": positive, + "negative": negative, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": 0, + + "loader_settings": {"ckpt_name": ckpt_name, + "vae_name": vae_name, + + "positive": positive, + "positive_l": None, + "positive_g": None, + "positive_balance": None, + "negative": negative, + "negative_l": None, + "negative_g": None, + "negative_balance": None, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + "seed": 0, + "empty_samples": samples, } + } + + return (pipe, model, log) + #svd加载器 class svdLoader: @@ -4871,18 +5030,18 @@ class pipeIn: neg = neg if neg is not None else pipe.get("negative") if neg is None: log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine") - samples = latent if latent is not None else pipe.get("samples") - if samples is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Latent missing from pipeLine") vae = vae if vae is not None else pipe.get("vae") if vae is None: log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine") clip = clip if clip is not None else pipe.get("clip") if clip is None: log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine") - if image is None: + if latent is not None: + samples = latent + elif image is None: + samples = pipe.get("samples") if pipe is not None else None image = pipe.get("images") if pipe is not None else None - else: + elif image is not None: if pipe is None: batch_size = 1 else: @@ -6166,8 +6325,9 @@ NODE_CLASS_MAPPINGS = { "easy fullLoader": fullLoader, "easy a1111Loader": a1111Loader, "easy comfyLoader": comfyLoader, - "easy zero123Loader": zero123Loader, "easy svdLoader": svdLoader, + "easy sv3dLoader": sv3DLoader, + "easy zero123Loader": zero123Loader, "easy dynamiCrafterLoader": dynamiCrafterLoader, "easy cascadeLoader": cascadeLoader, "easy loraStack": loraStackLoader, @@ -6258,8 +6418,9 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy fullLoader": "EasyLoader (Full)", "easy a1111Loader": "EasyLoader (A1111)", "easy comfyLoader": "EasyLoader (Comfy)", - "easy zero123Loader": "EasyLoader (Zero123)", "easy svdLoader": "EasyLoader (SVD)", + "easy sv3dLoader": "EasyLoader (SV3D)", + "easy zero123Loader": "EasyLoader (Zero123)", "easy dynamiCrafterLoader": "EasyLoader (DynamiCrafter)", "easy cascadeLoader": "EasyCascadeLoader", "easy loraStack": "EasyLoraStack", diff --git a/py/libs/easing.py b/py/libs/easing.py new file mode 100644 index 0000000..db300c9 --- /dev/null +++ b/py/libs/easing.py @@ -0,0 +1,27 @@ +@staticmethod +def easyIn(t: float)-> float: + return t*t +@staticmethod +def easyOut(t: float)-> float: + return -(t * (t - 2)) +@staticmethod +def easyInOut(t: float)-> float: + if t < 0.5: + return 2*t*t + else: + return (-2*t*t) + (4*t) - 1 + +class EasingBase: + + def easing(self, t: float, function='linear') -> float: + if function == 'easyIn': + return easyIn(t) + elif function == 'easyOut': + return easyOut(t) + elif function == 'easyInOut': + return easyInOut(t) + else: + return t + + def ease(self, start, end, t) -> float: + return end * t + start * (1 - t) \ No newline at end of file diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js index 434e060..5a77412 100644 --- a/web/js/easy/easyDynamicWidgets.js +++ b/web/js/easy/easyDynamicWidgets.js @@ -858,6 +858,44 @@ app.registerExtension({ }; } + if(["easy sv3dLoader"].includes(nodeData.name)){ + function changeSchedulerText(mode, batch_size, inputEl) { + console.log(mode) + switch (mode){ + case 'azimuth': + inputEl.readOnly = true + inputEl.style.opacity = 0.6 + return `0:(0.0,0.0)` + (batch_size > 1 ? `\n${batch_size-1}:(360.0,0.0)` : '') + case 'elevation': + inputEl.readOnly = true + inputEl.style.opacity = 0.6 + return `0:(-90.0,0.0)` + (batch_size > 1 ? `\n${batch_size-1}:(90.0,0.0)` : '') + case 'custom': + inputEl.readOnly = false + inputEl.style.opacity = 1 + return `0:(0.0,0.0)\n9:(180.0,0.0)\n20:(360.0,0.0)` + } + } + + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = async function () { + onNodeCreated ? onNodeCreated.apply(this, []) : undefined; + const easing_mode_widget = this.widgets.find(w => w.name == 'easing_mode') + const batch_size = this.widgets.find(w => w.name == 'batch_size') + const scheduler = this.widgets.find(w => w.name == 'scheduler') + setTimeout(_=>{ + if(!scheduler.value) scheduler.value = changeSchedulerText(easing_mode_widget.value, batch_size.value, scheduler.inputEl) + },1) + easing_mode_widget.callback = value=>{ + scheduler.value = changeSchedulerText(value, batch_size.value, scheduler.inputEl) + } + batch_size.callback = value =>{ + scheduler.value = changeSchedulerText(easing_mode_widget.value, value, scheduler.inputEl) + } + } + } + if (seedNodes.includes(nodeData.name)) { const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = async function () { @@ -999,7 +1037,11 @@ const getSetWidgets = ['rescale_after_model', 'rescale', 'refiner_lora1_name', 'refiner_lora2_name', 'upscale_method', 'image_output', 'add_noise', 'info', 'sampler_name', 'ckpt_B_name', 'ckpt_C_name', 'save_model', 'refiner_ckpt_name', - 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode', 'input_mode','lora_count','ckpt_count', 'conditioning_mode', 'preset', 'use_tiled', 'use_batch', 'num_embeds'] + 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', + 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode', 'input_mode', + 'lora_count','ckpt_count', 'conditioning_mode', 'preset', 'use_tiled', 'use_batch', 'num_embeds', + "easing_mode" +] function getSetters(node) { if (node.widgets)