mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-08 04:22:36 +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.singleton import ProxiedSingleton
|
||||||
from comfy_api.internal.async_to_sync import create_sync_class
|
from comfy_api.internal.async_to_sync import create_sync_class
|
||||||
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
from ._input import ImageInput, AudioInput, MaskInput, LatentInput, VideoInput
|
||||||
from ._input_impl import VideoFromFile, VideoFromComponents
|
from ._input_impl import VideoFromFile, VideoFromComponents, File3D
|
||||||
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL, File3D
|
from ._util import VideoCodec, VideoContainer, VideoComponents, MESH, VOXEL
|
||||||
from . import _io_public as io
|
from . import _io_public as io
|
||||||
from . import _ui_public as ui
|
from . import _ui_public as ui
|
||||||
from comfy_execution.utils import get_executing_context
|
from comfy_execution.utils import get_executing_context
|
||||||
@ -98,6 +98,7 @@ class Input:
|
|||||||
class InputImpl:
|
class InputImpl:
|
||||||
VideoFromFile = VideoFromFile
|
VideoFromFile = VideoFromFile
|
||||||
VideoFromComponents = VideoFromComponents
|
VideoFromComponents = VideoFromComponents
|
||||||
|
File3D = File3D
|
||||||
|
|
||||||
class Types:
|
class Types:
|
||||||
VideoCodec = VideoCodec
|
VideoCodec = VideoCodec
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
from .video_types import VideoFromFile, VideoFromComponents
|
from .video_types import VideoFromFile, VideoFromComponents
|
||||||
|
from .file3d_types import File3D
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Implementations
|
# Implementations
|
||||||
"VideoFromFile",
|
"VideoFromFile",
|
||||||
"VideoFromComponents",
|
"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,
|
from comfy_api.internal import (_ComfyNodeInternal, _NodeOutputInternal, classproperty, copy_class, first_real_override, is_class,
|
||||||
prune_dict, shallow_clone_class)
|
prune_dict, shallow_clone_class)
|
||||||
from comfy_execution.graph_utils import ExecutionBlocker
|
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):
|
class FolderType(str, Enum):
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from .video_types import VideoContainer, VideoCodec, VideoComponents
|
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
|
from .image_types import SVG
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -9,6 +9,5 @@ __all__ = [
|
|||||||
"VideoComponents",
|
"VideoComponents",
|
||||||
"VOXEL",
|
"VOXEL",
|
||||||
"MESH",
|
"MESH",
|
||||||
"File3D",
|
|
||||||
"SVG",
|
"SVG",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|
||||||
@ -14,28 +12,7 @@ class MESH:
|
|||||||
self.faces = faces
|
self.faces = faces
|
||||||
|
|
||||||
|
|
||||||
class File3D:
|
__all__ = [
|
||||||
"""3D file type storing binary data in memory.
|
"VOXEL",
|
||||||
|
"MESH",
|
||||||
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})"
|
|
||||||
|
|||||||
@ -296,4 +296,4 @@ async def download_url_to_file_3d(
|
|||||||
output_path.write_bytes(data.getvalue())
|
output_path.write_bytes(data.getvalue())
|
||||||
data.seek(0)
|
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