2023-12-18 03:18:40 -05:00
|
|
|
import torch
|
|
|
|
|
import nodes
|
|
|
|
|
import comfy.utils
|
2025-10-04 22:33:48 +03:00
|
|
|
from typing_extensions import override
|
|
|
|
|
from comfy_api.latest import ComfyExtension, io
|
2023-12-18 03:18:40 -05:00
|
|
|
|
|
|
|
|
def camera_embeddings(elevation, azimuth):
|
|
|
|
|
elevation = torch.as_tensor([elevation])
|
|
|
|
|
azimuth = torch.as_tensor([azimuth])
|
|
|
|
|
embeddings = torch.stack(
|
|
|
|
|
[
|
|
|
|
|
torch.deg2rad(
|
|
|
|
|
(90 - elevation) - (90)
|
|
|
|
|
), # Zero123 polar is 90-elevation
|
|
|
|
|
torch.sin(torch.deg2rad(azimuth)),
|
|
|
|
|
torch.cos(torch.deg2rad(azimuth)),
|
|
|
|
|
torch.deg2rad(
|
|
|
|
|
90 - torch.full_like(elevation, 0)
|
|
|
|
|
),
|
|
|
|
|
], dim=-1).unsqueeze(1)
|
|
|
|
|
|
|
|
|
|
return embeddings
|
|
|
|
|
|
|
|
|
|
|
2025-10-04 22:33:48 +03:00
|
|
|
class StableZero123_Conditioning(io.ComfyNode):
|
2023-12-18 03:18:40 -05:00
|
|
|
@classmethod
|
2025-10-04 22:33:48 +03:00
|
|
|
def define_schema(cls):
|
|
|
|
|
return io.Schema(
|
|
|
|
|
node_id="StableZero123_Conditioning",
|
2026-06-17 08:33:09 +08:00
|
|
|
category="model/conditioning/stable zero123",
|
2025-10-04 22:33:48 +03:00
|
|
|
inputs=[
|
|
|
|
|
io.ClipVision.Input("clip_vision"),
|
|
|
|
|
io.Image.Input("init_image"),
|
|
|
|
|
io.Vae.Input("vae"),
|
|
|
|
|
io.Int.Input("width", default=256, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("height", default=256, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("batch_size", default=1, min=1, max=4096),
|
|
|
|
|
io.Float.Input("elevation", default=0.0, min=-180.0, max=180.0, step=0.1, round=False),
|
|
|
|
|
io.Float.Input("azimuth", default=0.0, min=-180.0, max=180.0, step=0.1, round=False)
|
|
|
|
|
],
|
|
|
|
|
outputs=[
|
|
|
|
|
io.Conditioning.Output(display_name="positive"),
|
|
|
|
|
io.Conditioning.Output(display_name="negative"),
|
|
|
|
|
io.Latent.Output(display_name="latent")
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def execute(cls, clip_vision, init_image, vae, width, height, batch_size, elevation, azimuth) -> io.NodeOutput:
|
2023-12-18 03:18:40 -05:00
|
|
|
output = clip_vision.encode_image(init_image)
|
|
|
|
|
pooled = output.image_embeds.unsqueeze(0)
|
|
|
|
|
pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1)
|
|
|
|
|
encode_pixels = pixels[:,:,:,:3]
|
|
|
|
|
t = vae.encode(encode_pixels)
|
|
|
|
|
cam_embeds = camera_embeddings(elevation, azimuth)
|
2024-01-27 02:51:27 -05:00
|
|
|
cond = torch.cat([pooled, cam_embeds.to(pooled.device).repeat((pooled.shape[0], 1, 1))], dim=-1)
|
2023-12-18 03:18:40 -05:00
|
|
|
|
|
|
|
|
positive = [[cond, {"concat_latent_image": t}]]
|
|
|
|
|
negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]]
|
|
|
|
|
latent = torch.zeros([batch_size, 4, height // 8, width // 8])
|
2025-10-04 22:33:48 +03:00
|
|
|
return io.NodeOutput(positive, negative, {"samples":latent})
|
|
|
|
|
|
|
|
|
|
class StableZero123_Conditioning_Batched(io.ComfyNode):
|
|
|
|
|
@classmethod
|
|
|
|
|
def define_schema(cls):
|
|
|
|
|
return io.Schema(
|
|
|
|
|
node_id="StableZero123_Conditioning_Batched",
|
2026-06-17 08:33:09 +08:00
|
|
|
category="model/conditioning/stable zero123",
|
2025-10-04 22:33:48 +03:00
|
|
|
inputs=[
|
|
|
|
|
io.ClipVision.Input("clip_vision"),
|
|
|
|
|
io.Image.Input("init_image"),
|
|
|
|
|
io.Vae.Input("vae"),
|
|
|
|
|
io.Int.Input("width", default=256, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("height", default=256, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("batch_size", default=1, min=1, max=4096),
|
|
|
|
|
io.Float.Input("elevation", default=0.0, min=-180.0, max=180.0, step=0.1, round=False),
|
|
|
|
|
io.Float.Input("azimuth", default=0.0, min=-180.0, max=180.0, step=0.1, round=False),
|
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
|
|
|
io.Float.Input("elevation_batch_increment", default=0.0, min=-180.0, max=180.0, step=0.1, round=False, advanced=True),
|
|
|
|
|
io.Float.Input("azimuth_batch_increment", default=0.0, min=-180.0, max=180.0, step=0.1, round=False, advanced=True)
|
2025-10-04 22:33:48 +03:00
|
|
|
],
|
|
|
|
|
outputs=[
|
|
|
|
|
io.Conditioning.Output(display_name="positive"),
|
|
|
|
|
io.Conditioning.Output(display_name="negative"),
|
|
|
|
|
io.Latent.Output(display_name="latent")
|
|
|
|
|
]
|
|
|
|
|
)
|
2023-12-18 03:18:40 -05:00
|
|
|
|
2024-01-05 04:20:03 -05:00
|
|
|
@classmethod
|
2025-10-04 22:33:48 +03:00
|
|
|
def execute(cls, clip_vision, init_image, vae, width, height, batch_size, elevation, azimuth, elevation_batch_increment, azimuth_batch_increment) -> io.NodeOutput:
|
2024-01-05 04:20:03 -05:00
|
|
|
output = clip_vision.encode_image(init_image)
|
|
|
|
|
pooled = output.image_embeds.unsqueeze(0)
|
|
|
|
|
pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1)
|
|
|
|
|
encode_pixels = pixels[:,:,:,:3]
|
|
|
|
|
t = vae.encode(encode_pixels)
|
|
|
|
|
|
|
|
|
|
cam_embeds = []
|
|
|
|
|
for i in range(batch_size):
|
|
|
|
|
cam_embeds.append(camera_embeddings(elevation, azimuth))
|
|
|
|
|
elevation += elevation_batch_increment
|
|
|
|
|
azimuth += azimuth_batch_increment
|
|
|
|
|
|
|
|
|
|
cam_embeds = torch.cat(cam_embeds, dim=0)
|
|
|
|
|
cond = torch.cat([comfy.utils.repeat_to_batch_size(pooled, batch_size), cam_embeds], dim=-1)
|
|
|
|
|
|
|
|
|
|
positive = [[cond, {"concat_latent_image": t}]]
|
|
|
|
|
negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]]
|
|
|
|
|
latent = torch.zeros([batch_size, 4, height // 8, width // 8])
|
2025-10-04 22:33:48 +03:00
|
|
|
return io.NodeOutput(positive, negative, {"samples":latent, "batch_index": [0] * batch_size})
|
2024-01-05 04:20:03 -05:00
|
|
|
|
2025-10-04 22:33:48 +03:00
|
|
|
class SV3D_Conditioning(io.ComfyNode):
|
2024-03-18 10:04:51 -04:00
|
|
|
@classmethod
|
2025-10-04 22:33:48 +03:00
|
|
|
def define_schema(cls):
|
|
|
|
|
return io.Schema(
|
|
|
|
|
node_id="SV3D_Conditioning",
|
2026-06-17 08:33:09 +08:00
|
|
|
category="model/conditioning/stable video 3d",
|
2025-10-04 22:33:48 +03:00
|
|
|
inputs=[
|
|
|
|
|
io.ClipVision.Input("clip_vision"),
|
|
|
|
|
io.Image.Input("init_image"),
|
|
|
|
|
io.Vae.Input("vae"),
|
|
|
|
|
io.Int.Input("width", default=576, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("height", default=576, min=16, max=nodes.MAX_RESOLUTION, step=8),
|
|
|
|
|
io.Int.Input("video_frames", default=21, min=1, max=4096),
|
|
|
|
|
io.Float.Input("elevation", default=0.0, min=-90.0, max=90.0, step=0.1, round=False)
|
|
|
|
|
],
|
|
|
|
|
outputs=[
|
|
|
|
|
io.Conditioning.Output(display_name="positive"),
|
|
|
|
|
io.Conditioning.Output(display_name="negative"),
|
|
|
|
|
io.Latent.Output(display_name="latent")
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def execute(cls, clip_vision, init_image, vae, width, height, video_frames, elevation) -> io.NodeOutput:
|
2024-03-18 10:04:51 -04:00
|
|
|
output = clip_vision.encode_image(init_image)
|
|
|
|
|
pooled = output.image_embeds.unsqueeze(0)
|
|
|
|
|
pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1)
|
|
|
|
|
encode_pixels = pixels[:,:,:,:3]
|
|
|
|
|
t = vae.encode(encode_pixels)
|
|
|
|
|
|
|
|
|
|
azimuth = 0
|
|
|
|
|
azimuth_increment = 360 / (max(video_frames, 2) - 1)
|
|
|
|
|
|
|
|
|
|
elevations = []
|
|
|
|
|
azimuths = []
|
|
|
|
|
for i in range(video_frames):
|
|
|
|
|
elevations.append(elevation)
|
|
|
|
|
azimuths.append(azimuth)
|
|
|
|
|
azimuth += azimuth_increment
|
|
|
|
|
|
|
|
|
|
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([video_frames, 4, height // 8, width // 8])
|
2025-10-04 22:33:48 +03:00
|
|
|
return io.NodeOutput(positive, negative, {"samples":latent})
|
|
|
|
|
|
2024-03-18 10:04:51 -04:00
|
|
|
|
2025-10-04 22:33:48 +03:00
|
|
|
class Stable3DExtension(ComfyExtension):
|
|
|
|
|
@override
|
|
|
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
|
|
|
return [
|
|
|
|
|
StableZero123_Conditioning,
|
|
|
|
|
StableZero123_Conditioning_Batched,
|
|
|
|
|
SV3D_Conditioning,
|
|
|
|
|
]
|
2024-01-05 04:20:03 -05:00
|
|
|
|
2025-10-04 22:33:48 +03:00
|
|
|
async def comfy_entrypoint() -> Stable3DExtension:
|
|
|
|
|
return Stable3DExtension()
|