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)