1+ import zipfile
2+ from io import BytesIO
3+
14from typing_extensions import override
25
36from comfy_api .latest import IO , ComfyExtension , Input , Types
1518)
1619from comfy_api_nodes .util import (
1720 ApiEndpoint ,
21+ bytesio_to_image_tensor ,
22+ download_url_to_bytesio ,
1823 download_url_to_file_3d ,
1924 download_url_to_image_tensor ,
2025 downscale_image_tensor_by_max_side ,
@@ -35,6 +40,29 @@ def _is_tencent_rate_limited(status: int, body: object) -> bool:
3540 )
3641
3742
43+ async def download_and_extract_obj_zip (url : str ) -> tuple [Types .File3D , Input .Image | None ]:
44+ """The Tencent API returns OBJ results as ZIP archives containing the .obj mesh, and a texture image."""
45+ data = BytesIO ()
46+ await download_url_to_bytesio (url , data )
47+ data .seek (0 )
48+ if not zipfile .is_zipfile (data ):
49+ data .seek (0 )
50+ return Types .File3D (source = data , file_format = "obj" ), None
51+ data .seek (0 )
52+ obj_bytes = None
53+ texture_tensor = None
54+ with zipfile .ZipFile (data ) as zf :
55+ for name in zf .namelist ():
56+ lower = name .lower ()
57+ if lower .endswith (".obj" ):
58+ obj_bytes = zf .read (name )
59+ elif any (lower .endswith (ext ) for ext in {".png" , ".jpg" , ".jpeg" , ".bmp" , ".tiff" , ".webp" }):
60+ texture_tensor = bytesio_to_image_tensor (BytesIO (zf .read (name )), mode = "RGB" )
61+ if obj_bytes is None :
62+ raise ValueError ("ZIP archive does not contain an OBJ file." )
63+ return Types .File3D (source = BytesIO (obj_bytes ), file_format = "obj" ), texture_tensor
64+
65+
3866def get_file_from_response (
3967 response_objs : list [ResultFile3D ], file_type : str , raise_if_not_found : bool = True
4068) -> ResultFile3D | None :
@@ -92,6 +120,7 @@ def define_schema(cls):
92120 IO .String .Output (display_name = "model_file" ), # for backward compatibility only
93121 IO .File3DGLB .Output (display_name = "GLB" ),
94122 IO .File3DOBJ .Output (display_name = "OBJ" ),
123+ IO .Image .Output (display_name = "texture_image" ),
95124 ],
96125 hidden = [
97126 IO .Hidden .auth_token_comfy_org ,
@@ -150,14 +179,16 @@ async def execute(
150179 response_model = To3DProTaskResultResponse ,
151180 status_extractor = lambda r : r .Status ,
152181 )
182+ obj_file , texture_image = await download_and_extract_obj_zip (
183+ get_file_from_response (result .ResultFile3Ds , "obj" ).Url
184+ )
153185 return IO .NodeOutput (
154186 f"{ task_id } .glb" ,
155187 await download_url_to_file_3d (
156188 get_file_from_response (result .ResultFile3Ds , "glb" ).Url , "glb" , task_id = task_id
157189 ),
158- await download_url_to_file_3d (
159- get_file_from_response (result .ResultFile3Ds , "obj" ).Url , "obj" , task_id = task_id
160- ),
190+ obj_file ,
191+ texture_image ,
161192 )
162193
163194
@@ -210,6 +241,7 @@ def define_schema(cls):
210241 IO .String .Output (display_name = "model_file" ), # for backward compatibility only
211242 IO .File3DGLB .Output (display_name = "GLB" ),
212243 IO .File3DOBJ .Output (display_name = "OBJ" ),
244+ IO .Image .Output (display_name = "texture_image" ),
213245 ],
214246 hidden = [
215247 IO .Hidden .auth_token_comfy_org ,
@@ -303,14 +335,16 @@ async def execute(
303335 response_model = To3DProTaskResultResponse ,
304336 status_extractor = lambda r : r .Status ,
305337 )
338+ obj_file , texture_image = await download_and_extract_obj_zip (
339+ get_file_from_response (result .ResultFile3Ds , "obj" ).Url
340+ )
306341 return IO .NodeOutput (
307342 f"{ task_id } .glb" ,
308343 await download_url_to_file_3d (
309344 get_file_from_response (result .ResultFile3Ds , "glb" ).Url , "glb" , task_id = task_id
310345 ),
311- await download_url_to_file_3d (
312- get_file_from_response (result .ResultFile3Ds , "obj" ).Url , "obj" , task_id = task_id
313- ),
346+ obj_file ,
347+ texture_image ,
314348 )
315349
316350
0 commit comments