Skip to content

Commit ac5fd8a

Browse files
committed
feat(camera-info): CreateCameraInfo node + CameraInfoState widget input
1 parent b78cec8 commit ac5fd8a

4 files changed

Lines changed: 104 additions & 74 deletions

File tree

comfy_api/latest/_io.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,16 @@ def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str
902902
self.default = {}
903903

904904

905+
@comfytype(io_type="CAMERA_INFO_STATE")
906+
class CameraInfoState(ComfyTypeI):
907+
Type = dict
908+
909+
class Input(WidgetInput):
910+
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None,
911+
socketless: bool=True, advanced: bool=None):
912+
super().__init__(id, display_name, optional, tooltip, None, None, socketless, None, None, None, None, advanced)
913+
914+
905915
@comfytype(io_type="PHOTOMAKER")
906916
class Photomaker(ComfyTypeIO):
907917
Type = Any
@@ -2460,6 +2470,7 @@ def as_dict(self):
24602470
"Load3DAnimation",
24612471
"Compositor",
24622472
"Layers",
2473+
"CameraInfoState",
24632474
"Photomaker",
24642475
"Point",
24652476
"FaceAnalysis",

comfy_extras/nodes_camera.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import math
2+
3+
from typing_extensions import override
4+
5+
import comfy.model_management
6+
from comfy_api.latest import ComfyExtension, IO
7+
from comfy_extras.nodes_gaussian_splat import _lookat_camera_info, _quat_camera_info
8+
9+
10+
class CreateCameraInfo(IO.ComfyNode):
11+
@classmethod
12+
def define_schema(cls):
13+
return IO.Schema(
14+
node_id="CreateCameraInfo",
15+
display_name="Create Camera Info",
16+
search_aliases=["camera position", "make camera info", "orbit camera", "look at camera"],
17+
category="3d",
18+
description="Build a camera_info"
19+
"Mode 'orbit' aims with yaw/pitch/distance around the target; "
20+
"'look_at' places the camera at world position. Coordinates are the viewer's world space (right-handed,Y-up).",
21+
inputs=[
22+
IO.DynamicCombo.Input("mode", options=[
23+
IO.DynamicCombo.Option("orbit", [
24+
IO.Float.Input("yaw", default=35.0, min=-360.0, max=360.0, step=1.0),
25+
IO.Float.Input("pitch", default=30.0, min=-89.0, max=89.0, step=1.0),
26+
IO.Float.Input("distance", default=4.0, min=0.01, max=1000.0, step=0.01,
27+
tooltip="Camera distance from the target."),
28+
]),
29+
IO.DynamicCombo.Option("look_at", [
30+
IO.Float.Input("position_x", default=4.0, min=-1000.0, max=1000.0, step=0.01,
31+
tooltip="Camera position in world space (right-handed, Y-up)."),
32+
IO.Float.Input("position_y", default=4.0, min=-1000.0, max=1000.0, step=0.01),
33+
IO.Float.Input("position_z", default=4.0, min=-1000.0, max=1000.0, step=0.01),
34+
]),
35+
IO.DynamicCombo.Option("quaternion", [
36+
IO.Float.Input("position_x", default=4.0, min=-1000.0, max=1000.0, step=0.01,
37+
tooltip="Camera position in world space (right-handed, Y-up)."),
38+
IO.Float.Input("position_y", default=4.0, min=-1000.0, max=1000.0, step=0.01),
39+
IO.Float.Input("position_z", default=4.0, min=-1000.0, max=1000.0, step=0.01),
40+
IO.Float.Input("quat_x", default=0.0, min=-1.0, max=1.0, step=0.001),
41+
IO.Float.Input("quat_y", default=0.0, min=-1.0, max=1.0, step=0.001),
42+
IO.Float.Input("quat_z", default=0.0, min=-1.0, max=1.0, step=0.001),
43+
IO.Float.Input("quat_w", default=1.0, min=-1.0, max=1.0, step=0.001,
44+
tooltip="Camera world-rotation quaternion (three.js: looks down local -Z). Normalized for you."),
45+
]),
46+
], tooltip="How to define the camera: orbit angles, an explicit position, or a position + quaternion."),
47+
IO.Float.Input("target_x", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True,
48+
tooltip="Look-at point (orbit pivot / aim). In orbit mode, move it to pan/translate the "
49+
"whole camera. Ignored in quaternion mode. Defaults to the origin."),
50+
IO.Float.Input("target_y", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True),
51+
IO.Float.Input("target_z", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True),
52+
IO.Float.Input("roll", default=0.0, min=-180.0, max=180.0, step=1.0,
53+
tooltip="Camera roll about the view axis, degrees."),
54+
IO.Float.Input("fov", default=35.0, min=1.0, max=120.0, step=1.0,
55+
tooltip="Vertical field of view in degrees."),
56+
IO.Float.Input("zoom", default=1.0, min=0.01, max=100.0, step=0.01,
57+
tooltip="Digital zoom (focal-length multiplier). >1 zooms in without moving the camera."),
58+
IO.Combo.Input("camera_type", options=["perspective", "orthographic"],
59+
tooltip="Projection used by Render Splat: perspective (foreshortening) or orthographic (parallel)."),
60+
IO.CameraInfoState.Input("camera_info_state"),
61+
],
62+
outputs=[IO.Load3DCamera.Output(display_name="camera_info")],
63+
)
64+
65+
@classmethod
66+
def execute(cls, mode, target_x, target_y, target_z, roll, fov, zoom=1.0, camera_type="perspective", camera_info_state=None) -> IO.NodeOutput:
67+
dev = comfy.model_management.get_torch_device()
68+
kind = mode["mode"]
69+
if kind == "quaternion": # explicit world position + camera rotation
70+
position = [mode["position_x"], mode["position_y"], mode["position_z"]]
71+
quat = [mode["quat_x"], mode["quat_y"], mode["quat_z"], mode["quat_w"]]
72+
return IO.NodeOutput(_quat_camera_info(position, quat, fov, dev, zoom=zoom, camera_type=camera_type))
73+
target = [target_x, target_y, target_z] # orbit pivot / aim; move it to pan the whole camera
74+
if kind == "orbit": # yaw/pitch/distance about the target (world Y-up)
75+
y, p = math.radians(mode["yaw"]), math.radians(mode["pitch"])
76+
cy, sy, cp, sp = math.cos(y), math.sin(y), math.cos(p), math.sin(p)
77+
d = mode["distance"]
78+
position = [target_x + d * cp * sy, target_y + d * sp, target_z + d * cp * cy]
79+
else: # look_at: explicit world-space camera position
80+
position = [mode["position_x"], mode["position_y"], mode["position_z"]]
81+
return IO.NodeOutput(_lookat_camera_info(position, target, fov, dev, zoom=zoom, camera_type=camera_type, roll=roll))
82+
83+
84+
class CameraExtension(ComfyExtension):
85+
@override
86+
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
87+
return [CreateCameraInfo]
88+
89+
90+
async def comfy_entrypoint() -> CameraExtension:
91+
return CameraExtension()

comfy_extras/nodes_gaussian_splat.py

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,79 +1014,6 @@ def execute(cls, splat, width, height, frames, splat_scale, sharpen, headlight_s
10141014
return IO.NodeOutput(torch.stack(imgs), torch.stack(masks))
10151015

10161016

1017-
class CreateCameraInfo(IO.ComfyNode): # TODO: move to better file
1018-
@classmethod
1019-
def define_schema(cls):
1020-
return IO.Schema(
1021-
node_id="CreateCameraInfo",
1022-
display_name="Create Camera Info",
1023-
search_aliases=["camera position", "make camera info", "orbit camera", "look at camera"],
1024-
category="3d",
1025-
description="Build a camera_info"
1026-
"Mode 'orbit' aims with yaw/pitch/distance around the target; "
1027-
"'look_at' places the camera at world position. Coordinates are the viewer's world space (right-handed,Y-up).",
1028-
inputs=[
1029-
IO.DynamicCombo.Input("mode", options=[
1030-
IO.DynamicCombo.Option("orbit", [
1031-
IO.Float.Input("yaw", default=35.0, min=-360.0, max=360.0, step=1.0),
1032-
IO.Float.Input("pitch", default=30.0, min=-89.0, max=89.0, step=1.0),
1033-
IO.Float.Input("distance", default=4.0, min=0.01, max=1000.0, step=0.01,
1034-
tooltip="Camera distance from the target."),
1035-
]),
1036-
IO.DynamicCombo.Option("look_at", [
1037-
IO.Float.Input("position_x", default=4.0, min=-1000.0, max=1000.0, step=0.01,
1038-
tooltip="Camera position in world space (right-handed, Y-up)."),
1039-
IO.Float.Input("position_y", default=4.0, min=-1000.0, max=1000.0, step=0.01),
1040-
IO.Float.Input("position_z", default=4.0, min=-1000.0, max=1000.0, step=0.01),
1041-
]),
1042-
IO.DynamicCombo.Option("quaternion", [
1043-
IO.Float.Input("position_x", default=4.0, min=-1000.0, max=1000.0, step=0.01,
1044-
tooltip="Camera position in world space (right-handed, Y-up)."),
1045-
IO.Float.Input("position_y", default=4.0, min=-1000.0, max=1000.0, step=0.01),
1046-
IO.Float.Input("position_z", default=4.0, min=-1000.0, max=1000.0, step=0.01),
1047-
IO.Float.Input("quat_x", default=0.0, min=-1.0, max=1.0, step=0.001),
1048-
IO.Float.Input("quat_y", default=0.0, min=-1.0, max=1.0, step=0.001),
1049-
IO.Float.Input("quat_z", default=0.0, min=-1.0, max=1.0, step=0.001),
1050-
IO.Float.Input("quat_w", default=1.0, min=-1.0, max=1.0, step=0.001,
1051-
tooltip="Camera world-rotation quaternion (three.js: looks down local -Z). Normalized for you."),
1052-
]),
1053-
], tooltip="How to define the camera: orbit angles, an explicit position, or a position + quaternion."),
1054-
IO.Float.Input("target_x", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True,
1055-
tooltip="Look-at point (orbit pivot / aim). In orbit mode, move it to pan/translate the "
1056-
"whole camera. Ignored in quaternion mode. Defaults to the origin."),
1057-
IO.Float.Input("target_y", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True),
1058-
IO.Float.Input("target_z", default=0.0, min=-1000.0, max=1000.0, step=0.01, advanced=True),
1059-
IO.Float.Input("roll", default=0.0, min=-180.0, max=180.0, step=1.0,
1060-
tooltip="Camera roll about the view axis, degrees."),
1061-
IO.Float.Input("fov", default=35.0, min=1.0, max=120.0, step=1.0,
1062-
tooltip="Vertical field of view in degrees."),
1063-
IO.Float.Input("zoom", default=1.0, min=0.01, max=100.0, step=0.01,
1064-
tooltip="Digital zoom (focal-length multiplier). >1 zooms in without moving the camera."),
1065-
IO.Combo.Input("camera_type", options=["perspective", "orthographic"],
1066-
tooltip="Projection used by Render Splat: perspective (foreshortening) or orthographic (parallel)."),
1067-
],
1068-
outputs=[IO.Load3DCamera.Output(display_name="camera_info")],
1069-
)
1070-
1071-
@classmethod
1072-
def execute(cls, mode, target_x, target_y, target_z, roll, fov, zoom=1.0, camera_type="perspective") -> IO.NodeOutput:
1073-
dev = comfy.model_management.get_torch_device()
1074-
kind = mode["mode"]
1075-
if kind == "quaternion": # explicit world position + camera rotation
1076-
position = [mode["position_x"], mode["position_y"], mode["position_z"]]
1077-
quat = [mode["quat_x"], mode["quat_y"], mode["quat_z"], mode["quat_w"]]
1078-
return IO.NodeOutput(_quat_camera_info(position, quat, fov, dev, zoom=zoom, camera_type=camera_type))
1079-
target = [target_x, target_y, target_z] # orbit pivot / aim; move it to pan the whole camera
1080-
if kind == "orbit": # yaw/pitch/distance about the target (world Y-up)
1081-
y, p = math.radians(mode["yaw"]), math.radians(mode["pitch"])
1082-
cy, sy, cp, sp = math.cos(y), math.sin(y), math.cos(p), math.sin(p)
1083-
d = mode["distance"]
1084-
position = [target_x + d * cp * sy, target_y + d * sp, target_z + d * cp * cy]
1085-
else: # look_at: explicit world-space camera position
1086-
position = [mode["position_x"], mode["position_y"], mode["position_z"]]
1087-
return IO.NodeOutput(_lookat_camera_info(position, target, fov, dev, zoom=zoom, camera_type=camera_type, roll=roll))
1088-
1089-
10901017
class TransformSplat(IO.ComfyNode):
10911018
@classmethod
10921019
def define_schema(cls):
@@ -1665,7 +1592,7 @@ def execute(cls, splat, resolution, kernel, smooth, level, min_component, min_op
16651592
class GaussianExtension(ComfyExtension):
16661593
@override
16671594
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
1668-
return [SplatToFile3D, File3DToSplat, RenderSplat, CreateCameraInfo, TransformSplat,
1595+
return [SplatToFile3D, File3DToSplat, RenderSplat, TransformSplat,
16691596
GetSplatCount, MergeSplat, SplatToMesh]
16701597

16711598

nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2525,6 +2525,7 @@ async def init_builtin_extra_nodes():
25252525
"nodes_moge.py",
25262526
"nodes_mediapipe.py",
25272527
"nodes_gaussian_splat.py",
2528+
"nodes_camera.py",
25282529
"nodes_triposplat.py",
25292530
"nodes_depth_anything_3.py",
25302531
"nodes_seed.py",

0 commit comments

Comments
 (0)