diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/.gitattributes b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/.gitattributes new file mode 100644 index 0000000..a6344aa --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/README.md b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/README.md new file mode 100644 index 0000000..c76298b --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/README.md @@ -0,0 +1,18 @@ +--- +license: mit +pipeline_tag: image-to-3d +tags: +- partcrafter +- 3d-generation +- rectified-flow +--- +# PartCrafter: Structured 3D Mesh Generation via Compositional Latent Diffusion Transformers (Scene Level) + +PartCrafter is a structured 3D generative model that jointly generates multiple parts and objects from a single RGB image in one shot. +This HuggingFace🤗 repo stores pretrained model weights for PartCrafter on **scene level**. + +For more details about usage, please refer to the [official GitHub repo](https://github.com/wgsxm/PartCrafter). + +- Project page: https://wgsxm.github.io/projects/partcrafter/ +- Code: https://github.com/wgsxm/PartCrafter +- Paper: https://arxiv.org/abs/2506.05573 \ No newline at end of file diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/feature_extractor_dinov2/preprocessor_config.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/feature_extractor_dinov2/preprocessor_config.json new file mode 100644 index 0000000..ff5b47c --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/feature_extractor_dinov2/preprocessor_config.json @@ -0,0 +1,27 @@ +{ + "crop_size": { + "height": 224, + "width": 224 + }, + "do_center_crop": true, + "do_convert_rgb": true, + "do_normalize": true, + "do_rescale": true, + "do_resize": true, + "image_mean": [ + 0.485, + 0.456, + 0.406 + ], + "image_processor_type": "BitImageProcessor", + "image_std": [ + 0.229, + 0.224, + 0.225 + ], + "resample": 3, + "rescale_factor": 0.00392156862745098, + "size": { + "shortest_edge": 256 + } +} diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/image_encoder_dinov2/config.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/image_encoder_dinov2/config.json new file mode 100644 index 0000000..72ae708 --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/image_encoder_dinov2/config.json @@ -0,0 +1,60 @@ +{ + "apply_layernorm": true, + "architectures": [ + "Dinov2Model" + ], + "attention_probs_dropout_prob": 0.0, + "drop_path_rate": 0.0, + "hidden_act": "gelu", + "hidden_dropout_prob": 0.0, + "hidden_size": 1024, + "image_size": 518, + "initializer_range": 0.02, + "layer_norm_eps": 1e-06, + "layerscale_value": 1.0, + "mlp_ratio": 4, + "model_type": "dinov2", + "num_attention_heads": 16, + "num_channels": 3, + "num_hidden_layers": 24, + "out_features": [ + "stage24" + ], + "out_indices": [ + 24 + ], + "patch_size": 14, + "qkv_bias": true, + "reshape_hidden_states": true, + "stage_names": [ + "stem", + "stage1", + "stage2", + "stage3", + "stage4", + "stage5", + "stage6", + "stage7", + "stage8", + "stage9", + "stage10", + "stage11", + "stage12", + "stage13", + "stage14", + "stage15", + "stage16", + "stage17", + "stage18", + "stage19", + "stage20", + "stage21", + "stage22", + "stage23", + "stage24" + ], + "torch_dtype": "float16", + "transformers_version": "4.53.2", + "use_mask_token": true, + "use_swiglu_ffn": false +} diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/model_index.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/model_index.json new file mode 100644 index 0000000..1a846e7 --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/model_index.json @@ -0,0 +1,24 @@ +{ + "_class_name": "PartCrafterPipeline", + "_diffusers_version": "0.34.0", + "feature_extractor_dinov2": [ + "transformers", + "BitImageProcessor" + ], + "image_encoder_dinov2": [ + "transformers", + "Dinov2Model" + ], + "scheduler": [ + "partcrafter_src.schedulers.scheduling_rectified_flow", + "RectifiedFlowScheduler" + ], + "transformer": [ + "partcrafter_src.models.transformers.partcrafter_transformer", + "PartCrafterDiTModel" + ], + "vae": [ + "partcrafter_src.models.autoencoders.autoencoder_kl_triposg", + "TripoSGVAEModel" + ] +} diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/scheduler/scheduler_config.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/scheduler/scheduler_config.json new file mode 100644 index 0000000..1114433 --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/scheduler/scheduler_config.json @@ -0,0 +1,7 @@ +{ + "_class_name": "RectifiedFlowScheduler", + "_diffusers_version": "0.34.0", + "num_train_timesteps": 1000, + "shift": 1, + "use_dynamic_shifting": false +} diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/transformer/config.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/transformer/config.json new file mode 100644 index 0000000..5aa5692 --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/transformer/config.json @@ -0,0 +1,27 @@ +{ + "_class_name": "PartCrafterDiTModel", + "_diffusers_version": "0.34.0", + "cross_attention_dim": 1024, + "enable_global_cross_attn": true, + "enable_local_cross_attn": true, + "enable_part_embedding": true, + "global_attn_block_id_range": null, + "global_attn_block_ids": [ + 0, + 2, + 4, + 6, + 8, + 10, + 12, + 14, + 16, + 18, + 20 + ], + "in_channels": 64, + "max_num_parts": 32, + "num_attention_heads": 16, + "num_layers": 21, + "width": 2048 +} diff --git a/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/vae/config.json b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/vae/config.json new file mode 100644 index 0000000..4e372be --- /dev/null +++ b/Checkpoints/Diffusers/wgsxm/PartCrafter-Scene/vae/config.json @@ -0,0 +1,15 @@ +{ + "_class_name": "TripoSGVAEModel", + "_diffusers_version": "0.34.0", + "_name_or_path": "pretrained_weights/TripoSG", + "embed_frequency": 8, + "embed_include_pi": false, + "embedding_type": "frequency", + "in_channels": 3, + "latent_channels": 64, + "num_attention_heads": 8, + "num_layers_decoder": 16, + "num_layers_encoder": 8, + "width_decoder": 1024, + "width_encoder": 512 +} diff --git a/README.md b/README.md index 85418c6..2b42929 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,26 @@ Your ComfyUI Root Directory\python_embeded\python.exe install.py - **Note:** you need to put [Example Inputs Files & Folders](_Example_Workflows/_Example_Inputs_Files/) under ComfyUI Root Directory\ComfyUI\input folder before you can run the example workflow - **PartCrafter**: [wgsxm/PartCrafter](https://github.com/wgsxm/PartCrafter) - - Pipeline: - - Single image → 3D mesh with **part segmentation** - - Optional background removal (`rembg`) - - Output includes: - - Merged full mesh - - ZIP archive with individual part meshes - - Model weights: [HuggingFace - PartCrafter](https://huggingface.co/wgsxm/PartCrafter) - - [Workflow](./_Example_Workflows/PartCrafter.json) + - Two-model pipeline: + - **Single object** mode: + - Single image → 3D mesh with **part segmentation** + - Output: + - Merged full mesh + - ZIP archive with individual part meshes + - **Scene** mode: + - Scene image → multiple 3D part meshes + - Output: + - Merged full scene mesh + - ZIP archive with individual object meshes + - Model weights: + - [PartCrafter (object mode)](https://huggingface.co/wgsxm/PartCrafter) + - [PartCrafter-Scene (scene mode)](https://huggingface.co/wgsxm/PartCrafter-Scene) + - Workflows: + - [Single Object](./_Example_Workflows/PartCrafter/PartCrafter.json) + - [Scene](./_Example_Workflows/PartCrafter/PartCrafter-Scene.json) + - **Hunyuan3D_2.1**: [Tencent-Hunyuan/Hunyuan3D-2.1](https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1) - Updated two-stage pipeline: diff --git a/_Example_Workflows/PartCrafter/PartCrafter-Scene.json b/_Example_Workflows/PartCrafter/PartCrafter-Scene.json new file mode 100644 index 0000000..32352cd --- /dev/null +++ b/_Example_Workflows/PartCrafter/PartCrafter-Scene.json @@ -0,0 +1,254 @@ +{ + "id": "d67bc5f6-ca03-46ff-8337-c73a9f42c713", + "revision": 0, + "last_node_id": 5, + "last_link_id": 4, + "nodes": [ + { + "id": 2, + "type": "[Comfy3D] PartCrafter Generate", + "pos": [ + 2948.591796875, + 1531.039794921875 + ], + "size": [ + 318.76953125, + 314 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "partcrafter_pipe", + "type": "DIFFUSERS_PIPE", + "link": 1 + }, + { + "name": "image", + "type": "IMAGE", + "link": 2 + } + ], + "outputs": [ + { + "name": "parts_zip_path", + "type": "STRING", + "links": [ + 3 + ] + }, + { + "name": "glb_mesh_path", + "type": "STRING", + "links": [ + 4 + ] + }, + { + "name": "processed_image", + "type": "IMAGE", + "links": null + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] PartCrafter Generate" + }, + "widgets_values": [ + 6, + 482458609224077, + "randomize", + 1024, + 50, + 7, + 1000000000, + false, + true, + 1 + ] + }, + { + "id": 1, + "type": "[Comfy3D] Load PartCrafter Scene Pipeline", + "pos": [ + 2626.746826171875, + 1449.319091796875 + ], + "size": [ + 240.68515014648438, + 26 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "partcrafter_scene_pipe", + "type": "DIFFUSERS_PIPE", + "links": [ + 1 + ] + } + ], + "properties": { + "Node name for S&R": "[Comfy3D] Load PartCrafter Scene Pipeline" + }, + "widgets_values": [] + }, + { + "id": 5, + "type": "[Comfy3D] Preview 3DMesh", + "pos": [ + 3361.958984375, + 1528.9996337890625 + ], + "size": [ + 600, + 500 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "mesh_file_path", + "type": "STRING", + "widget": { + "name": "mesh_file_path" + }, + "link": 4 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "[Comfy3D] Preview 3DMesh" + }, + "widgets_values": [ + "", + null + ] + }, + { + "id": 4, + "type": "PreviewAny", + "pos": [ + 3363.604736328125, + 1391.216552734375 + ], + "size": [ + 342.048828125, + 88 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 3 + } + ], + "outputs": [], + "properties": { + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [] + }, + { + "id": 3, + "type": "LoadImage", + "pos": [ + 2522.745361328125, + 1527.1820068359375 + ], + "size": [ + 353.18182373046875, + 489.7575378417969 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 2 + ] + }, + { + "name": "MASK", + "type": "MASK", + "slot_index": 1, + "links": [] + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "dining_scene.png", + "image" + ] + } + ], + "links": [ + [ + 1, + 1, + 0, + 2, + 0, + "DIFFUSERS_PIPE" + ], + [ + 2, + 3, + 0, + 2, + 1, + "IMAGE" + ], + [ + 3, + 2, + 0, + 4, + 0, + "*" + ], + [ + 4, + 2, + 1, + 5, + 0, + "STRING" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.9802577600586283, + "offset": [ + -2455.460106090683, + -1318.8136943587429 + ] + }, + "node_versions": { + "ComfyUI-3D-Pack": "ffa5ff828e6bbe2940905b8b12cc394282a638c1", + "comfy-core": "0.3.44" + }, + "frontendVersion": "1.23.4", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/_Example_Workflows/PartCrafter.json b/_Example_Workflows/PartCrafter/PartCrafter.json similarity index 100% rename from _Example_Workflows/PartCrafter.json rename to _Example_Workflows/PartCrafter/PartCrafter.json diff --git a/_Example_Workflows/_Example_Inputs_Files/dining_scene.png b/_Example_Workflows/_Example_Inputs_Files/dining_scene.png new file mode 100644 index 0000000..e1c2ae6 Binary files /dev/null and b/_Example_Workflows/_Example_Inputs_Files/dining_scene.png differ diff --git a/nodes.py b/nodes.py index 2d1b55b..c5eddc9 100644 --- a/nodes.py +++ b/nodes.py @@ -5790,4 +5790,208 @@ class PartCrafter_Generate: print(f"GLB mesh path for Preview_3DMesh: {relative_scene_path}") return (zip_path, relative_scene_path, processed_image_tensor) +#------ partcrafter scene --------------------- + +class Load_PartCrafter_Scene_Pipeline: + """Load PartCrafter Scene Pipeline""" + + CATEGORY = "Comfy3D/Algorithm/PartCrafter" + RETURN_TYPES = ("DIFFUSERS_PIPE",) + RETURN_NAMES = ("partcrafter_scene_pipe",) + FUNCTION = "load" + + _REPO_ID = "wgsxm/PartCrafter-Scene" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + } + + @staticmethod + def _ensure_weights(): + safe_repo_name = Load_PartCrafter_Scene_Pipeline._REPO_ID.replace("/", "_") + base_dir = os.path.join(CKPT_DIFFUSERS_PATH, Load_PartCrafter_Scene_Pipeline._REPO_ID) + + required_files = [ + "model_index.json", + "transformer/diffusion_pytorch_model.safetensors", + "vae/diffusion_pytorch_model.safetensors", + ] + + files_missing = [] + + for file_path in required_files: + full_file_path = os.path.join(base_dir, file_path) + if not os.path.exists(full_file_path): + files_missing.append(file_path) + + if files_missing or not os.path.exists(base_dir): + print(f"Loading PartCrafter-Scene from {Load_PartCrafter_Scene_Pipeline._REPO_ID}...") + print(f"Missing files: {len(files_missing)}") + snapshot_download( + repo_id=Load_PartCrafter_Scene_Pipeline._REPO_ID, + repo_type="model", + local_dir=base_dir, + resume_download=True, + ignore_patterns=HF_DOWNLOAD_IGNORE, + ) + print(f"PartCrafter-Scene loaded successfully") + else: + print(f"PartCrafter-Scene weights already loaded") + + return base_dir + + def load(self): + base_dir = self._ensure_weights() + + pipeline = PartCrafterPipeline.from_pretrained(base_dir).to(DEVICE, WEIGHT_DTYPE) + + print(f"PartCrafter-Scene pipeline loaded") + return (pipeline,) + + +class PartCrafter_Generate: + """PartCrafter Generation - Creates multi-part 3D scenes with colored components""" + + CATEGORY = "Comfy3D/Algorithm/PartCrafter" + RETURN_TYPES = ("STRING", "STRING", "IMAGE") + RETURN_NAMES = ("parts_zip_path", "glb_mesh_path", "processed_image") + FUNCTION = "generate" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "partcrafter_pipe": ("DIFFUSERS_PIPE",), + "image": ("IMAGE",), + "num_parts": ("INT", {"default": 4, "min": 1, "max": 16}), + "seed": ("INT", {"default": 1234, "min": 0, "max": 0xffffffffffffffff}), + "num_tokens": ("INT", {"default": 1024, "min": 256, "max": 2048}), + "num_inference_steps": ("INT", {"default": 50, "min": 1, "max": 100}), + "guidance_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "step": 0.1}), + "max_num_expanded_coords": ("INT", {"default": 1000000000, "min": 1000, "max": 10000000000}), + "use_flash_decoder": ("BOOLEAN", {"default": False}), + "remove_background": ("BOOLEAN", {"default": True}), + "sampling_version": ("INT", {"default": 1, "min": 1, "max": 2}), + } + } + + @torch.no_grad() + def generate(self, partcrafter_pipe, image, num_parts, seed, num_tokens, num_inference_steps, + guidance_scale, max_num_expanded_coords, use_flash_decoder, remove_background, sampling_version): + + # Convert image + pil_image = torch_imgs_to_pils(image)[0] + + # Remove background if needed + if remove_background: + rmbg_worker = BackgroundRemover_2_1() + if pil_image.mode == "RGBA": + pil_image = pil_image.convert('RGB') + pil_image = rmbg_worker(pil_image) + del rmbg_worker + + # Set sampling version + if hasattr(partcrafter_pipe.vae, 'set_sampling_version'): + partcrafter_pipe.vae.set_sampling_version(sampling_version) + + # Generation + generator = torch.Generator(device=partcrafter_pipe.device) + generator = generator.manual_seed(int(seed)) + + outputs = partcrafter_pipe( + image=[pil_image] * num_parts, + attention_kwargs={"num_parts": num_parts}, + num_tokens=num_tokens, + generator=generator, + num_inference_steps=num_inference_steps, + guidance_scale=guidance_scale, + max_num_expanded_coords=max_num_expanded_coords, + use_flash_decoder=use_flash_decoder, + ).meshes + + # Ensure no None outputs + for i, mesh in enumerate(outputs): + if mesh is None: + outputs[i] = trimesh.Trimesh(vertices=[[0,0,0]], faces=[[0,0,0]]) + print(f"Replaced None mesh at index {i} with dummy mesh") + + merged_mesh_trimesh = get_colored_mesh_composition(outputs) + split_mesh = explode_mesh(merged_mesh_trimesh) + + # Debug logging + print(f"PartCrafter result type: {type(merged_mesh_trimesh)}") + if hasattr(merged_mesh_trimesh, 'geometry'): + print(f"Scene contains {len(merged_mesh_trimesh.geometry)} parts: {list(merged_mesh_trimesh.geometry.keys())}") + + # Create ZIP with individual parts + parts_output_dir = "output/partcrafter_parts" + os.makedirs(parts_output_dir, exist_ok=True) + + zip_path = os.path.join(parts_output_dir, f"parts.zip") + parts = [] + + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for idx, mesh in enumerate(outputs): + print(f"Part {idx} type: {type(mesh)}") + if mesh is not None: + # Save part as GLB file + part_path = os.path.join(parts_output_dir, f"part_{idx:02d}.glb") + mesh.export(part_path) + parts.append(part_path) + + # Add to ZIP + zipf.write(part_path, f"part_{idx:02d}.glb") + + # Clean up temporary file + try: + os.remove(part_path) + except: + pass + + print(f"Created parts ZIP: {zip_path} with {len(parts)} parts") + + # Save merged mesh + scene_output_dir = "output/partcrafter_scenes" + os.makedirs(scene_output_dir, exist_ok=True) + scene_file_path = os.path.join(scene_output_dir, f"scene.glb") + + # For Preview_3DMesh, return relative path from ComfyUI output directory + relative_scene_path = "partcrafter_scenes/scene.glb" + + # Export the merged mesh (same as app.py: merged.export(merged_path)) + merged_mesh_trimesh.export(scene_file_path) + print(f"Saved merged colored mesh to: {scene_file_path}") + + # Debug: check what was saved + if isinstance(merged_mesh_trimesh, trimesh.Scene): + print(f"Exported Scene with {len(merged_mesh_trimesh.geometry)} parts") + for geom_name in merged_mesh_trimesh.geometry: + geom = merged_mesh_trimesh.geometry[geom_name] + print(f" Part {geom_name}: vertices: {len(geom.vertices)}, faces: {len(geom.faces)}") + if hasattr(geom.visual, 'vertex_colors') and geom.visual.vertex_colors is not None: + print(f" Has vertex colors: {geom.visual.vertex_colors.shape}") + else: + print(f"Exported single mesh: vertices: {len(merged_mesh_trimesh.vertices)}, faces: {len(merged_mesh_trimesh.faces)}") + + # Debug info about the scene + try: + print(f"Scene file saved successfully: {scene_file_path}") + if os.path.exists(scene_file_path): + file_size = os.path.getsize(scene_file_path) / (1024 * 1024) # MB + print(f"Scene file size: {file_size:.2f} MB") + print(f"Relative path for Preview_3DMesh: {relative_scene_path}") + else: + print(f"Warning: Scene file was not created properly") + except Exception as e: + print(f"Error checking scene file: {e}") + + # Processed image + processed_image_tensor = pils_to_torch_imgs([pil_image]) + + print(f"PartCrafter: Generated {len(outputs)} parts with explode_mesh processing") + print(f"GLB mesh path for Preview_3DMesh: {relative_scene_path}") + + return (zip_path, relative_scene_path, processed_image_tensor)