mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-03 18:20:26 +08:00
dev: move File3D to _input_iml [ci skip]
This commit is contained in:
parent
a18989ba03
commit
a10dcec885
@ -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
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
from .video_types import VideoFromFile, VideoFromComponents
|
||||
from .file3d_types import File3D
|
||||
|
||||
__all__ = [
|
||||
# Implementations
|
||||
"VideoFromFile",
|
||||
"VideoFromComponents",
|
||||
"File3D",
|
||||
]
|
||||
|
||||
134
comfy_api/latest/_input_impl/file3d_types.py
Normal file
134
comfy_api/latest/_input_impl/file3d_types.py
Normal 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})"
|
||||
@ -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):
|
||||
|
||||
@ -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",
|
||||
]
|
||||
|
||||
@ -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",
|
||||
]
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user