attashe commited on
Commit
01667d6
Β·
1 Parent(s): 8690078

initial commit

Browse files
Files changed (2) hide show
  1. app.py +302 -0
  2. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import runpod
3
+ import base64
4
+ import os
5
+ import io
6
+ import zipfile
7
+ import tempfile
8
+ import shutil
9
+ import trimesh
10
+ from PIL import Image
11
+ import requests
12
+
13
+
14
+ # Configuration
15
+ RUNPOD_API_KEY = os.environ.get("RUNPOD_API_KEY", "")
16
+ RUNPOD_ENDPOINT_ID = os.environ.get("RUNPOD_ENDPOINT_ID", "")
17
+
18
+
19
+ def convert_obj_to_glb(obj_path, texture_path, output_glb_path):
20
+ """Convert OBJ + texture to GLB format"""
21
+ try:
22
+ # Load the mesh
23
+ mesh = trimesh.load(obj_path, process=False)
24
+
25
+ # Load texture image
26
+ if os.path.exists(texture_path):
27
+ texture_image = Image.open(texture_path)
28
+
29
+ # Create a material with the texture
30
+ material = trimesh.visual.material.PBRMaterial(
31
+ baseColorTexture=texture_image,
32
+ baseColorFactor=[1.0, 1.0, 1.0, 1.0],
33
+ )
34
+
35
+ # Apply material to mesh
36
+ if hasattr(mesh, 'visual'):
37
+ mesh.visual.material = material
38
+
39
+ # Export to GLB
40
+ mesh.export(output_glb_path, file_type='glb')
41
+ print(f"βœ… Converted to GLB: {output_glb_path}")
42
+ return output_glb_path
43
+
44
+ except Exception as e:
45
+ print(f"❌ Error converting to GLB: {str(e)}")
46
+ # Fallback: try simple conversion without texture
47
+ try:
48
+ mesh = trimesh.load(obj_path)
49
+ mesh.export(output_glb_path, file_type='glb')
50
+ return output_glb_path
51
+ except Exception as e2:
52
+ print(f"❌ Fallback conversion also failed: {str(e2)}")
53
+ raise
54
+
55
+
56
+ def generate_3d_model(image, api_key, endpoint_id, progress=gr.Progress()):
57
+ """Generate 3D model from image using RunPod endpoint"""
58
+
59
+ # Validate inputs
60
+ if not api_key:
61
+ return None, None, "❌ Please provide RunPod API Key"
62
+
63
+ if not endpoint_id:
64
+ return None, None, "❌ Please provide RunPod Endpoint ID"
65
+
66
+ if image is None:
67
+ return None, None, "❌ Please upload an image"
68
+
69
+ try:
70
+ progress(0.1, desc="Encoding image...")
71
+
72
+ # Convert PIL Image to base64
73
+ buffered = io.BytesIO()
74
+ image.save(buffered, format="PNG")
75
+ image_base64 = base64.b64encode(buffered.getvalue()).decode()
76
+
77
+ progress(0.2, desc="Connecting to RunPod...")
78
+
79
+ # Set API key and get endpoint
80
+ runpod.api_key = api_key
81
+ endpoint = runpod.Endpoint(endpoint_id)
82
+
83
+ progress(0.3, desc="Sending request to RunPod (this may take a few minutes)...")
84
+
85
+ # Run inference
86
+ run_request = endpoint.run({
87
+ "input": {
88
+ "image": image_base64
89
+ }
90
+ })
91
+
92
+ progress(0.4, desc="Waiting for 3D model generation...")
93
+
94
+ # Wait for result with timeout
95
+ result = run_request.output(timeout=600) # 10 minute timeout
96
+
97
+ progress(0.7, desc="Processing results...")
98
+
99
+ # Check for errors
100
+ if "error" in result:
101
+ error_msg = result.get("error", "Unknown error")
102
+ return None, None, f"❌ RunPod Error: {error_msg}"
103
+
104
+ if "output" not in result:
105
+ return None, None, "❌ No output received from RunPod"
106
+
107
+ progress(0.8, desc="Decoding output...")
108
+
109
+ # Decode the zip file
110
+ zip_data = base64.b64decode(result["output"])
111
+
112
+ # Create temporary directory
113
+ temp_dir = tempfile.mkdtemp()
114
+ zip_path = os.path.join(temp_dir, "output.zip")
115
+
116
+ # Save zip file
117
+ with open(zip_path, "wb") as f:
118
+ f.write(zip_data)
119
+
120
+ # Extract zip
121
+ extract_dir = os.path.join(temp_dir, "extracted")
122
+ os.makedirs(extract_dir, exist_ok=True)
123
+
124
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
125
+ zip_ref.extractall(extract_dir)
126
+
127
+ progress(0.9, desc="Converting to GLB format...")
128
+
129
+ # Find the OBJ and texture files
130
+ obj_file = None
131
+ texture_file = None
132
+
133
+ for file in os.listdir(extract_dir):
134
+ if file.endswith('.obj') and 'textured' in file:
135
+ obj_file = os.path.join(extract_dir, file)
136
+ elif file.endswith('.jpg') and 'textured' in file and 'metallic' not in file and 'roughness' not in file:
137
+ texture_file = os.path.join(extract_dir, file)
138
+
139
+ if not obj_file:
140
+ # Fallback: find any OBJ file
141
+ for file in os.listdir(extract_dir):
142
+ if file.endswith('.obj'):
143
+ obj_file = os.path.join(extract_dir, file)
144
+ break
145
+
146
+ if not texture_file:
147
+ # Fallback: find any texture file
148
+ for file in os.listdir(extract_dir):
149
+ if file.endswith('.jpg') or file.endswith('.png'):
150
+ texture_file = os.path.join(extract_dir, file)
151
+ break
152
+
153
+ if not obj_file:
154
+ return None, zip_path, "❌ No OBJ file found in output"
155
+
156
+ # Convert to GLB
157
+ glb_path = os.path.join(temp_dir, "model.glb")
158
+ convert_obj_to_glb(obj_file, texture_file, glb_path)
159
+
160
+ progress(1.0, desc="Complete!")
161
+
162
+ # Return GLB path and zip path
163
+ return glb_path, zip_path, "βœ… 3D model generated successfully!"
164
+
165
+ except Exception as e:
166
+ error_msg = f"❌ Error: {str(e)}"
167
+ print(error_msg)
168
+ import traceback
169
+ traceback.print_exc()
170
+ return None, None, error_msg
171
+
172
+
173
+ # Create Gradio interface
174
+ with gr.Blocks(title="Image to 3D Model", theme=gr.themes.Soft()) as demo:
175
+ gr.Markdown("""
176
+ # 🎨 Image to 3D Model Generator
177
+
178
+ Generate textured 3D models from images using AI. Upload an image and get a downloadable 3D model!
179
+
180
+ **Powered by Hunyuan3D-2.1 via RunPod Serverless**
181
+ """)
182
+
183
+ with gr.Row():
184
+ with gr.Column(scale=1):
185
+ gr.Markdown("### πŸ“Έ Input")
186
+
187
+ # API Configuration
188
+ with gr.Accordion("βš™οΈ RunPod Configuration", open=False):
189
+ api_key_input = gr.Textbox(
190
+ label="RunPod API Key",
191
+ placeholder="Enter your RunPod API key",
192
+ type="password",
193
+ value=RUNPOD_API_KEY
194
+ )
195
+ endpoint_id_input = gr.Textbox(
196
+ label="RunPod Endpoint ID",
197
+ placeholder="Enter your endpoint ID",
198
+ value=RUNPOD_ENDPOINT_ID
199
+ )
200
+ gr.Markdown("""
201
+ Get your API key and Endpoint ID from [RunPod Dashboard](https://www.runpod.io/console/serverless)
202
+
203
+ You can also set them as environment variables:
204
+ - `RUNPOD_API_KEY`
205
+ - `RUNPOD_ENDPOINT_ID`
206
+ """)
207
+
208
+ # Image input
209
+ image_input = gr.Image(
210
+ label="Upload Image",
211
+ type="pil",
212
+ sources=["upload", "webcam", "clipboard"]
213
+ )
214
+
215
+ # Example images
216
+ gr.Examples(
217
+ examples=[
218
+ "examples/chair.jpg",
219
+ "examples/shoe.jpg",
220
+ "examples/vase.jpg",
221
+ ],
222
+ inputs=image_input,
223
+ label="Example Images (add your own in /examples folder)"
224
+ )
225
+
226
+ # Generate button
227
+ generate_btn = gr.Button("πŸš€ Generate 3D Model", variant="primary", size="lg")
228
+
229
+ # Status message
230
+ status_output = gr.Textbox(label="Status", interactive=False)
231
+
232
+ with gr.Column(scale=1):
233
+ gr.Markdown("### 🎯 Output")
234
+
235
+ # 3D Model viewer
236
+ model_output = gr.Model3D(
237
+ label="Generated 3D Model",
238
+ clear_color=[0.1, 0.1, 0.1, 1.0],
239
+ height=500
240
+ )
241
+
242
+ # Download button
243
+ download_output = gr.File(
244
+ label="πŸ“¦ Download Files (ZIP)",
245
+ interactive=False
246
+ )
247
+
248
+ gr.Markdown("""
249
+ ### πŸ“ Output Files Include:
250
+ - `demo_textured.obj` - 3D model geometry
251
+ - `demo_textured.jpg` - Color texture
252
+ - `demo_textured.mtl` - Material definition
253
+ - `demo_textured_metallic.jpg` - Metallic map
254
+ - `demo_textured_roughness.jpg` - Roughness map
255
+
256
+ ### πŸ’‘ Tips:
257
+ - Works best with clear object images on white/transparent background
258
+ - Single object works better than complex scenes
259
+ - Processing takes 2-5 minutes depending on server load
260
+ - You can import the GLB file into Blender, Unity, Unreal Engine, etc.
261
+ """)
262
+
263
+ # Event handler
264
+ generate_btn.click(
265
+ fn=generate_3d_model,
266
+ inputs=[image_input, api_key_input, endpoint_id_input],
267
+ outputs=[model_output, download_output, status_output]
268
+ )
269
+
270
+ gr.Markdown("""
271
+ ---
272
+ ### πŸ”§ Troubleshooting
273
+
274
+ **Error: No API key/endpoint ID**
275
+ - Set them in the configuration accordion above
276
+ - Or set as environment variables in Hugging Face Space settings
277
+
278
+ **Error: Request timeout**
279
+ - The endpoint might be cold-starting (first request takes longer)
280
+ - Try again after a few minutes
281
+
282
+ **Error: GPU out of memory**
283
+ - The RunPod endpoint might need more VRAM
284
+ - Try using a larger GPU tier in RunPod settings
285
+
286
+ **Model looks incorrect**
287
+ - Try using a clearer image with better lighting
288
+ - Ensure the object is centered and on a plain background
289
+
290
+ ### πŸ“š Resources
291
+ - [RunPod Serverless Docs](https://docs.runpod.io/serverless/overview)
292
+ - [Hunyuan3D GitHub](https://github.com/Tencent/Hunyuan3D)
293
+ """)
294
+
295
+
296
+ # Launch the app
297
+ if __name__ == "__main__":
298
+ demo.launch(
299
+ share=False,
300
+ server_name="0.0.0.0",
301
+ server_port=7860
302
+ )
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ runpod==1.7.2
3
+ trimesh==4.4.9
4
+ pillow==10.4.0
5
+ numpy==1.26.4
6
+ requests==2.32.3
7
+ pygltflib==1.16.2