dev: move File3D to _input_iml [ci skip]

This commit is contained in:
bigcat88 2026-02-01 19:45:54 +02:00
parent a18989ba03
commit a10dcec885
7 changed files with 147 additions and 33 deletions

View File

@ -6,8 +6,8 @@ from comfy_api.internal import ComfyAPIBase
from comfy_api.internal.singleton import ProxiedSingleton
from comfy_api.internal.async_to_sync import create_sync_class
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
from ._input_impl import VideoFromFile, VideoFromComponents
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D
from ._input_impl import VideoFromFile, VideoFromComponents, File3D
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL
from . import _io_public as io
from . import _ui_public as ui
from comfy_execution.utils import get_executing_context
@ -98,6 +98,7 @@ class Input:
class InputImpl:
VideoFromFile = VideoFromFile
VideoFromComponents = VideoFromComponents
File3D = File3D
class Types:
VideoCodec = VideoCodec

View File

@ -1,7 +1,9 @@
from .video_types import VideoFromFile, VideoFromComponents
from .file3d_types import File3D
__all__ = [
# Implementations
"VideoFromFile",
"VideoFromComponents",
"File3D",
]

View File

@ -0,0 +1,134 @@
from __future__ import annotations
import io
import shutil
from pathlib import Path
from typing import IO, Union
class File3D:
"""
Class representing a 3D file from a file path or binary stream.
Supports both disk-backed (file path) and memory-backed (BytesIO) storage,
similar to VideoFromFile. Disk-backed mode is more memory-efficient for
large 3D models.
"""
def __init__(self, path: Union[str, IO[bytes]], file_format: str = ""):
"""
Initialize the File3D object.
Args:
path: Either a file path (str) or a binary stream (BytesIO/IO[bytes])
containing the 3D file data.
file_format: The format of the 3D file (e.g., 'glb', 'fbx', 'obj').
If not provided and path is a string, it will be inferred
from the file extension.
"""
self._path = path
self._format = file_format or self._infer_format()
def _infer_format(self) -> str:
"""Infer file format from path if it's a string."""
if isinstance(self._path, str):
return Path(self._path).suffix.lstrip(".").lower()
return ""
@property
def format(self) -> str:
"""Get the file format."""
return self._format
@format.setter
def format(self, value: str) -> None:
"""Set the file format."""
self._format = value.lstrip(".").lower() if value else ""
@property
def is_disk_backed(self) -> bool:
"""Check if the file is stored on disk (vs in memory)."""
return isinstance(self._path, str)
def get_source(self) -> Union[str, IO[bytes]]:
"""
Get the underlying source for streaming.
Returns:
Either a file path (str) or a BytesIO object.
For disk-backed files, returns the path directly to avoid memory copy.
"""
if isinstance(self._path, str):
return self._path
# Reset stream position for IO objects
if hasattr(self._path, "seek"):
self._path.seek(0)
return self._path
@property
def data(self) -> io.BytesIO:
"""
Get the file data as a BytesIO object.
For disk-backed files, this reads the entire file into memory.
The returned BytesIO is positioned at the beginning.
"""
if isinstance(self._path, str):
# Read from disk
with open(self._path, "rb") as f:
result = io.BytesIO(f.read())
return result
# Already a stream - reset position and return
if hasattr(self._path, "seek"):
self._path.seek(0)
if isinstance(self._path, io.BytesIO):
return self._path
# For other IO types, wrap in BytesIO
return io.BytesIO(self._path.read())
def save_to(self, path: str) -> str:
"""
Save the 3D file to disk.
For disk-backed files, this uses an efficient file copy.
For memory-backed files, this writes the buffer to disk.
Args:
path: Destination file path.
Returns:
The destination path.
"""
dest = Path(path)
dest.parent.mkdir(parents=True, exist_ok=True)
if isinstance(self._path, str):
# Disk-backed: efficient copy
if Path(self._path).resolve() != dest.resolve():
shutil.copy2(self._path, dest)
else:
# Memory-backed: write bytes
if hasattr(self._path, "seek"):
self._path.seek(0)
with open(dest, "wb") as f:
f.write(self._path.read())
return str(dest)
def get_bytes(self) -> bytes:
"""
Get the raw bytes of the 3D file.
For disk-backed files, this reads the entire file.
For memory-backed files, this returns the buffer contents.
"""
if isinstance(self._path, str):
return Path(self._path).read_bytes()
if hasattr(self._path, "seek"):
self._path.seek(0)
return self._path.read()
def __repr__(self) -> str:
if isinstance(self._path, str):
return f"File3D(path={self._path!r}, format={self._format!r})"
return f"File3D(<stream>, format={self._format!r})"

View File

@ -27,7 +27,8 @@ if TYPE_CHECKING:
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
prune_dict, shallow_clone_class)
from comfy_execution.graph_utils import ExecutionBlocker
from ._util import MESH, VOXEL, File3D, SVG as _SVG
from ._util import MESH, VOXEL, SVG as _SVG
from ._input_impl import File3D
class FolderType(str, Enum):

View File

@ -1,5 +1,5 @@
from .video_types import VideoContainer, VideoCodec, VideoComponents
from .geometry_types import VOXEL, MESH, File3D
from .geometry_types import VOXEL, MESH
from .image_types import SVG
__all__ = [
@ -9,6 +9,5 @@ __all__ = [
"VideoComponents",
"VOXEL",
"MESH",
"File3D",
"SVG",
]

View File

@ -1,5 +1,3 @@
from io import BytesIO
import torch
@ -14,28 +12,7 @@ class MESH:
self.faces = faces
class File3D:
"""3D file type storing binary data in memory.
This is the backing class for all FILE_3D_* ComfyTypes.
"""
def __init__(self, data: BytesIO, file_format: str):
self._data = data
self.format = file_format
@property
def data(self) -> BytesIO:
"""Get the BytesIO data, seeking to the beginning."""
self._data.seek(0)
return self._data
def save_to(self, path: str) -> str:
"""Save the 3D file data to disk."""
self._data.seek(0)
with open(path, "wb") as f:
f.write(self._data.read())
return path
def __repr__(self) -> str:
return f"File3D({self.format})"
__all__ = [
"VOXEL",
"MESH",
]

View File

@ -296,4 +296,4 @@ async def download_url_to_file_3d(
output_path.write_bytes(data.getvalue())
data.seek(0)
return Types.File3D(data=data, file_format=file_format)
return Types.File3D(path=data, file_format=file_format)