196 lines
7.1 KiB
Python
196 lines
7.1 KiB
Python
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
from pathlib import Path
|
|
import os
|
|
import folder_paths
|
|
|
|
class AspectRatioLatentNode:
|
|
"""
|
|
A ComfyUI node that creates an empty latent matching the closest aspect ratio
|
|
from a predefined list based on input image or dimensions.
|
|
"""
|
|
|
|
# Predefined aspect ratios from the training hyperparameters
|
|
ASPECT_RATIOS = [
|
|
(512, 2048, 0.25), (512, 1984, 0.26), (512, 1920, 0.27), (512, 1856, 0.28),
|
|
(576, 1792, 0.32), (576, 1728, 0.33), (576, 1664, 0.35), (640, 1600, 0.4),
|
|
(640, 1536, 0.42), (704, 1472, 0.48), (704, 1408, 0.5), (704, 1344, 0.52),
|
|
(768, 1344, 0.57), (768, 1280, 0.6), (832, 1216, 0.68), (832, 1152, 0.72),
|
|
(896, 1152, 0.78), (896, 1088, 0.82), (960, 1088, 0.88), (960, 1024, 0.94),
|
|
(1024, 1024, 1.0), (1024, 960, 1.07), (1088, 960, 1.13), (1088, 896, 1.21),
|
|
(1152, 896, 1.29), (1152, 832, 1.38), (1216, 832, 1.46), (1280, 768, 1.67),
|
|
(1344, 768, 1.75), (1408, 704, 2.0), (1472, 704, 2.09), (1536, 640, 2.4),
|
|
(1600, 640, 2.5), (1664, 576, 2.89), (1728, 576, 3.0), (1792, 576, 3.11),
|
|
(1856, 512, 3.62), (1920, 512, 3.75), (1984, 512, 3.88), (2048, 512, 4.0)
|
|
]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 64}),
|
|
},
|
|
"optional": {
|
|
"image": ("IMAGE",),
|
|
"width": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 8}),
|
|
"height": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 8}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LATENT", "INT", "INT", "FLOAT", "STRING")
|
|
RETURN_NAMES = ("latent", "width", "height", "aspect_ratio", "info")
|
|
FUNCTION = "create_latent"
|
|
CATEGORY = "Koala"
|
|
|
|
def find_closest_aspect_ratio(self, target_ratio):
|
|
"""Find the closest aspect ratio from the predefined list."""
|
|
min_diff = float('inf')
|
|
closest = None
|
|
|
|
for height, width, ratio in self.ASPECT_RATIOS:
|
|
diff = abs(ratio - target_ratio)
|
|
if diff < min_diff:
|
|
min_diff = diff
|
|
closest = (height, width, ratio)
|
|
|
|
return closest
|
|
|
|
def create_latent(self, batch_size, image=None, width=None, height=None):
|
|
"""Create an empty latent with dimensions matching the closest aspect ratio."""
|
|
|
|
# Determine input dimensions
|
|
if image is not None:
|
|
# Get dimensions from image
|
|
# ComfyUI images are in format [batch, height, width, channels]
|
|
_, h, w, _ = image.shape
|
|
input_width = w
|
|
input_height = h
|
|
else:
|
|
# Use provided width and height
|
|
if width is None or height is None:
|
|
raise ValueError("Either an image or both width and height must be provided")
|
|
input_width = width
|
|
input_height = height
|
|
|
|
# Calculate aspect ratio
|
|
input_ratio = input_height / input_width
|
|
|
|
# Find closest matching aspect ratio
|
|
matched_height, matched_width, matched_ratio = self.find_closest_aspect_ratio(input_ratio)
|
|
|
|
# Create empty latent
|
|
# Latent dimensions are 1/8 of the image dimensions for SD models
|
|
latent_height = matched_height // 8
|
|
latent_width = matched_width // 8
|
|
|
|
# Create the latent tensor
|
|
latent = torch.zeros([batch_size, 4, latent_height, latent_width])
|
|
|
|
# Create info string
|
|
info = f"Input: {input_width}x{input_height} (AR: {input_ratio:.2f}) → Matched: {matched_width}x{matched_height} (AR: {matched_ratio:.2f})"
|
|
|
|
return ({"samples": latent}, matched_width, matched_height, matched_ratio, info)
|
|
|
|
class SaveMeshAnywhere:
|
|
"""
|
|
A ComfyUI node that saves a 3D mesh to any location on disk.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"trimesh": ("TRIMESH",),
|
|
"save_directory": ("STRING", {"default": "C:/output"}),
|
|
"filename": ("STRING", {"default": "model"}),
|
|
"file_format": (["glb", "obj", "ply", "stl", "3mf", "dae"],),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("file_path",)
|
|
FUNCTION = "save_mesh"
|
|
CATEGORY = "Koala"
|
|
OUTPUT_NODE = True
|
|
|
|
def save_mesh(self, trimesh, save_directory, filename, file_format):
|
|
# Make sure the directory exists
|
|
if save_directory and not os.path.exists(save_directory):
|
|
os.makedirs(save_directory, exist_ok=True)
|
|
|
|
# Strip any extension from the filename
|
|
filename = os.path.splitext(filename)[0]
|
|
# Create the full path with the correct extension
|
|
save_path = os.path.join(save_directory, f"{filename}.{file_format}")
|
|
# Save the mesh to the specified path
|
|
trimesh.export(save_path, file_type=file_format)
|
|
|
|
return (save_path,)
|
|
|
|
class SaveImageAnywhere:
|
|
"""
|
|
A ComfyUI node that saves an image to any location on disk.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"images": ("IMAGE",),
|
|
"save_directory": ("STRING", {"default": "C:/output"}),
|
|
"filename": ("STRING", {"default": "image"}),
|
|
"file_format": (["png", "jpg"],),
|
|
"quality": ("INT", {"default": 95, "min": 1, "max": 100, "step": 1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("file_path",)
|
|
FUNCTION = "save_image"
|
|
CATEGORY = "Koala"
|
|
OUTPUT_NODE = True
|
|
|
|
def save_image(self, images, save_directory, filename, file_format, quality):
|
|
# Make sure the directory exists
|
|
if save_directory and not os.path.exists(save_directory):
|
|
os.makedirs(save_directory, exist_ok=True)
|
|
|
|
# Get the first image from the batch
|
|
image = images[0]
|
|
|
|
# Convert from torch tensor to PIL Image
|
|
# ComfyUI images are in format [batch, height, width, channels] and in range 0-1
|
|
i = 255. * image.cpu().numpy()
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
|
|
|
# Strip any extension from the filename
|
|
filename = os.path.splitext(filename)[0]
|
|
|
|
# Create the full path with the correct extension
|
|
save_path = os.path.join(save_directory, f"{filename}.{file_format}")
|
|
|
|
# Save the image to the specified path
|
|
if file_format.lower() == "jpg" or file_format.lower() == "jpeg":
|
|
img.save(save_path, format="JPEG", quality=quality)
|
|
else: # png
|
|
img.save(save_path, format="PNG")
|
|
|
|
return (save_path,)
|
|
|
|
# Node registration for ComfyUI
|
|
NODE_CLASS_MAPPINGS = {
|
|
"AspectRatioLatentNode": AspectRatioLatentNode,
|
|
"SaveMeshAnywhere": SaveMeshAnywhere,
|
|
"SaveImageAnywhere": SaveImageAnywhere,
|
|
# Add more nodes here as you create them
|
|
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"AspectRatioLatentNode": "Koala Aspect Ratio Empty Latent",
|
|
"SaveMeshAnywhere": "Koala Save 3D Mesh Anywhere",
|
|
"SaveImageAnywhere": "Koala Save Image Anywhere",
|
|
# Add more display names here
|
|
|
|
} |