Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,6 +6,7 @@ MCP Server + Gradio UI for MCP Hackathon
|
|
| 6 |
import os
|
| 7 |
import io
|
| 8 |
import json
|
|
|
|
| 9 |
import tempfile
|
| 10 |
import gradio as gr
|
| 11 |
from google import genai
|
|
@@ -55,7 +56,7 @@ def generate_3d_model(prompt: str) -> str:
|
|
| 55 |
prompt: Text description of the object to generate (e.g., "a red sports car", "a wooden chair")
|
| 56 |
|
| 57 |
Returns:
|
| 58 |
-
JSON string with
|
| 59 |
"""
|
| 60 |
if not client:
|
| 61 |
if not init_gemini():
|
|
@@ -78,7 +79,7 @@ def generate_3d_model(prompt: str) -> str:
|
|
| 78 |
break
|
| 79 |
|
| 80 |
if initial_image is None:
|
| 81 |
-
return json.dumps({"error": "Image generation failed"})
|
| 82 |
|
| 83 |
# STEP 2: Remove background
|
| 84 |
edit_prompt = "Remove the background completely, make the background transparent. Preserve the object's shadow for realism."
|
|
@@ -100,7 +101,7 @@ def generate_3d_model(prompt: str) -> str:
|
|
| 100 |
break
|
| 101 |
|
| 102 |
if final_image is None:
|
| 103 |
-
return json.dumps({"error": "Background removal failed"})
|
| 104 |
|
| 105 |
# STEP 3: Create grayscale mask
|
| 106 |
gray = final_image.convert("L")
|
|
@@ -108,59 +109,52 @@ def generate_3d_model(prompt: str) -> str:
|
|
| 108 |
# STEP 4: Run SAM-3D
|
| 109 |
ply_bytes, glb_bytes = run_sam3d(final_image, gray)
|
| 110 |
|
| 111 |
-
#
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
ply_path = os.path.join(temp_dir, "model.ply")
|
| 118 |
-
|
| 119 |
-
initial_image.save(original_path)
|
| 120 |
-
final_image.save(nobg_path)
|
| 121 |
-
gray.save(mask_path)
|
| 122 |
-
|
| 123 |
-
with open(ply_path, 'wb') as f:
|
| 124 |
-
f.write(ply_bytes)
|
| 125 |
-
|
| 126 |
-
glb_path = None
|
| 127 |
-
if glb_bytes:
|
| 128 |
-
glb_path = os.path.join(temp_dir, "model.glb")
|
| 129 |
-
with open(glb_path, 'wb') as f:
|
| 130 |
-
f.write(glb_bytes)
|
| 131 |
|
| 132 |
return json.dumps({
|
| 133 |
"success": True,
|
| 134 |
"prompt": prompt,
|
| 135 |
-
"
|
| 136 |
-
"
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
| 141 |
})
|
| 142 |
|
| 143 |
except Exception as e:
|
| 144 |
-
return json.dumps({
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
def edit_3d_model(edit_prompt: str,
|
| 148 |
"""
|
| 149 |
Edit an existing 3D model by modifying its transparent image and regenerating.
|
| 150 |
|
| 151 |
Args:
|
| 152 |
edit_prompt: Description of the edit to apply (e.g., "remove the wings", "change color to blue")
|
| 153 |
-
|
| 154 |
|
| 155 |
Returns:
|
| 156 |
-
JSON string with
|
| 157 |
"""
|
| 158 |
if not client:
|
| 159 |
if not init_gemini():
|
| 160 |
return json.dumps({"error": "GEMINI_API_KEY not configured"})
|
| 161 |
|
| 162 |
try:
|
| 163 |
-
|
|
|
|
|
|
|
| 164 |
|
| 165 |
image_part = types.Part.from_bytes(
|
| 166 |
data=image_to_bytes(current_image),
|
|
@@ -182,42 +176,35 @@ def edit_3d_model(edit_prompt: str, transparent_image_path: str) -> str:
|
|
| 182 |
break
|
| 183 |
|
| 184 |
if edited_image is None:
|
| 185 |
-
return json.dumps({"error": "Edit failed"})
|
| 186 |
|
| 187 |
gray = edited_image.convert("L")
|
| 188 |
ply_bytes, glb_bytes = run_sam3d(edited_image, gray)
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
edited_image.save(nobg_path)
|
| 197 |
-
gray.save(mask_path)
|
| 198 |
-
|
| 199 |
-
with open(ply_path, 'wb') as f:
|
| 200 |
-
f.write(ply_bytes)
|
| 201 |
-
|
| 202 |
-
glb_path = None
|
| 203 |
-
if glb_bytes:
|
| 204 |
-
glb_path = os.path.join(temp_dir, "model.glb")
|
| 205 |
-
with open(glb_path, 'wb') as f:
|
| 206 |
-
f.write(glb_bytes)
|
| 207 |
|
| 208 |
return json.dumps({
|
| 209 |
"success": True,
|
| 210 |
"edit_prompt": edit_prompt,
|
| 211 |
-
"
|
| 212 |
-
"
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
| 216 |
})
|
| 217 |
|
| 218 |
except Exception as e:
|
| 219 |
-
return json.dumps({
|
| 220 |
-
|
|
|
|
|
|
|
| 221 |
|
| 222 |
# ============================================================
|
| 223 |
# GRADIO UI FUNCTIONS
|
|
@@ -422,7 +409,7 @@ edit_tool = gr.Interface(
|
|
| 422 |
fn=edit_3d_model,
|
| 423 |
inputs=[
|
| 424 |
gr.Textbox(label="Edit Prompt", placeholder="Remove the wings"),
|
| 425 |
-
gr.Textbox(label="Transparent Image
|
| 426 |
],
|
| 427 |
outputs=gr.Textbox(label="Result (JSON)"),
|
| 428 |
api_name="edit_3d",
|
|
|
|
| 6 |
import os
|
| 7 |
import io
|
| 8 |
import json
|
| 9 |
+
import base64
|
| 10 |
import tempfile
|
| 11 |
import gradio as gr
|
| 12 |
from google import genai
|
|
|
|
| 56 |
prompt: Text description of the object to generate (e.g., "a red sports car", "a wooden chair")
|
| 57 |
|
| 58 |
Returns:
|
| 59 |
+
JSON string with base64-encoded files
|
| 60 |
"""
|
| 61 |
if not client:
|
| 62 |
if not init_gemini():
|
|
|
|
| 79 |
break
|
| 80 |
|
| 81 |
if initial_image is None:
|
| 82 |
+
return json.dumps({"error": "Image generation failed - no image in response"})
|
| 83 |
|
| 84 |
# STEP 2: Remove background
|
| 85 |
edit_prompt = "Remove the background completely, make the background transparent. Preserve the object's shadow for realism."
|
|
|
|
| 101 |
break
|
| 102 |
|
| 103 |
if final_image is None:
|
| 104 |
+
return json.dumps({"error": "Background removal failed - no image in response"})
|
| 105 |
|
| 106 |
# STEP 3: Create grayscale mask
|
| 107 |
gray = final_image.convert("L")
|
|
|
|
| 109 |
# STEP 4: Run SAM-3D
|
| 110 |
ply_bytes, glb_bytes = run_sam3d(final_image, gray)
|
| 111 |
|
| 112 |
+
# STEP 5: Encode outputs as base64 (NO FILE PATHS!)
|
| 113 |
+
original_b64 = base64.b64encode(image_to_bytes(initial_image)).decode()
|
| 114 |
+
transparent_b64 = base64.b64encode(image_to_bytes(final_image)).decode()
|
| 115 |
+
mask_b64 = base64.b64encode(image_to_bytes(gray)).decode()
|
| 116 |
+
ply_b64 = base64.b64encode(ply_bytes).decode()
|
| 117 |
+
glb_b64 = base64.b64encode(glb_bytes).decode() if glb_bytes else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
return json.dumps({
|
| 120 |
"success": True,
|
| 121 |
"prompt": prompt,
|
| 122 |
+
"message": f"✅ Successfully generated 3D model for: {prompt}",
|
| 123 |
+
"files": {
|
| 124 |
+
"original_image_base64": original_b64,
|
| 125 |
+
"transparent_image_base64": transparent_b64,
|
| 126 |
+
"mask_image_base64": mask_b64,
|
| 127 |
+
"ply_model_base64": ply_b64,
|
| 128 |
+
"glb_model_base64": glb_b64
|
| 129 |
+
},
|
| 130 |
+
"instructions": "Files are base64-encoded. To save: decode base64 and write to disk."
|
| 131 |
})
|
| 132 |
|
| 133 |
except Exception as e:
|
| 134 |
+
return json.dumps({
|
| 135 |
+
"error": str(e),
|
| 136 |
+
"error_type": type(e).__name__
|
| 137 |
+
})
|
| 138 |
|
| 139 |
+
def edit_3d_model(edit_prompt: str, transparent_image_base64: str) -> str:
|
| 140 |
"""
|
| 141 |
Edit an existing 3D model by modifying its transparent image and regenerating.
|
| 142 |
|
| 143 |
Args:
|
| 144 |
edit_prompt: Description of the edit to apply (e.g., "remove the wings", "change color to blue")
|
| 145 |
+
transparent_image_base64: Base64-encoded transparent PNG image from a previous generation
|
| 146 |
|
| 147 |
Returns:
|
| 148 |
+
JSON string with base64-encoded edited files
|
| 149 |
"""
|
| 150 |
if not client:
|
| 151 |
if not init_gemini():
|
| 152 |
return json.dumps({"error": "GEMINI_API_KEY not configured"})
|
| 153 |
|
| 154 |
try:
|
| 155 |
+
# Decode base64 image (instead of reading file path)
|
| 156 |
+
image_bytes = base64.b64decode(transparent_image_base64)
|
| 157 |
+
current_image = Image.open(io.BytesIO(image_bytes))
|
| 158 |
|
| 159 |
image_part = types.Part.from_bytes(
|
| 160 |
data=image_to_bytes(current_image),
|
|
|
|
| 176 |
break
|
| 177 |
|
| 178 |
if edited_image is None:
|
| 179 |
+
return json.dumps({"error": "Edit failed - no image in response"})
|
| 180 |
|
| 181 |
gray = edited_image.convert("L")
|
| 182 |
ply_bytes, glb_bytes = run_sam3d(edited_image, gray)
|
| 183 |
|
| 184 |
+
# Encode outputs as base64 (NO FILE PATHS!)
|
| 185 |
+
transparent_b64 = base64.b64encode(image_to_bytes(edited_image)).decode()
|
| 186 |
+
mask_b64 = base64.b64encode(image_to_bytes(gray)).decode()
|
| 187 |
+
ply_b64 = base64.b64encode(ply_bytes).decode()
|
| 188 |
+
glb_b64 = base64.b64encode(glb_bytes).decode() if glb_bytes else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
return json.dumps({
|
| 191 |
"success": True,
|
| 192 |
"edit_prompt": edit_prompt,
|
| 193 |
+
"message": f"✅ Successfully applied edit: {edit_prompt}",
|
| 194 |
+
"files": {
|
| 195 |
+
"transparent_image_base64": transparent_b64,
|
| 196 |
+
"mask_image_base64": mask_b64,
|
| 197 |
+
"ply_model_base64": ply_b64,
|
| 198 |
+
"glb_model_base64": glb_b64
|
| 199 |
+
},
|
| 200 |
+
"instructions": "Files are base64-encoded. To save: decode base64 and write to disk."
|
| 201 |
})
|
| 202 |
|
| 203 |
except Exception as e:
|
| 204 |
+
return json.dumps({
|
| 205 |
+
"error": str(e),
|
| 206 |
+
"error_type": type(e).__name__
|
| 207 |
+
})
|
| 208 |
|
| 209 |
# ============================================================
|
| 210 |
# GRADIO UI FUNCTIONS
|
|
|
|
| 409 |
fn=edit_3d_model,
|
| 410 |
inputs=[
|
| 411 |
gr.Textbox(label="Edit Prompt", placeholder="Remove the wings"),
|
| 412 |
+
gr.Textbox(label="Transparent Image (base64)", placeholder="Paste base64-encoded PNG from previous generation")
|
| 413 |
],
|
| 414 |
outputs=gr.Textbox(label="Result (JSON)"),
|
| 415 |
api_name="edit_3d",
|