Add Sketchfab integration
This commit is contained in:
parent
972096e9dc
commit
8b7e89fbcd
271
addon.py
271
addon.py
@ -11,6 +11,7 @@ import tempfile
|
|||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import zipfile
|
||||||
from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty
|
from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty
|
||||||
import io
|
import io
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
@ -200,6 +201,7 @@ class BlenderMCPServer:
|
|||||||
"execute_code": self.execute_code,
|
"execute_code": self.execute_code,
|
||||||
"get_polyhaven_status": self.get_polyhaven_status,
|
"get_polyhaven_status": self.get_polyhaven_status,
|
||||||
"get_hyper3d_status": self.get_hyper3d_status,
|
"get_hyper3d_status": self.get_hyper3d_status,
|
||||||
|
"get_sketchfab_status": self.get_sketchfab_status,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add Polyhaven handlers only if enabled
|
# Add Polyhaven handlers only if enabled
|
||||||
@ -220,6 +222,14 @@ class BlenderMCPServer:
|
|||||||
"import_generated_asset": self.import_generated_asset,
|
"import_generated_asset": self.import_generated_asset,
|
||||||
}
|
}
|
||||||
handlers.update(polyhaven_handlers)
|
handlers.update(polyhaven_handlers)
|
||||||
|
|
||||||
|
# Add Sketchfab handlers only if enabled
|
||||||
|
if bpy.context.scene.blendermcp_use_sketchfab:
|
||||||
|
sketchfab_handlers = {
|
||||||
|
"search_sketchfab_models": self.search_sketchfab_models,
|
||||||
|
"download_sketchfab_model": self.download_sketchfab_model,
|
||||||
|
}
|
||||||
|
handlers.update(sketchfab_handlers)
|
||||||
|
|
||||||
handler = handlers.get(cmd_type)
|
handler = handlers.get(cmd_type)
|
||||||
if handler:
|
if handler:
|
||||||
@ -1373,6 +1383,248 @@ class BlenderMCPServer:
|
|||||||
return {"succeed": False, "error": str(e)}
|
return {"succeed": False, "error": str(e)}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Sketchfab API
|
||||||
|
def get_sketchfab_status(self):
|
||||||
|
"""Get the current status of Sketchfab integration"""
|
||||||
|
enabled = bpy.context.scene.blendermcp_use_sketchfab
|
||||||
|
api_key = bpy.context.scene.blendermcp_sketchfab_api_key
|
||||||
|
|
||||||
|
# Test the API key if present
|
||||||
|
if api_key:
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Token {api_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
"https://api.sketchfab.com/v3/me",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
user_data = response.json()
|
||||||
|
username = user_data.get("username", "Unknown user")
|
||||||
|
return {
|
||||||
|
"enabled": True,
|
||||||
|
"message": f"Sketchfab integration is enabled and ready to use. Logged in as: {username}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(f"API key test failed with status code {response.status_code}: {response.text}")
|
||||||
|
return {
|
||||||
|
"enabled": False,
|
||||||
|
"message": f"Sketchfab API key seems invalid. Status code: {response.status_code}"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error testing Sketchfab API key: {str(e)}")
|
||||||
|
return {
|
||||||
|
"enabled": False,
|
||||||
|
"message": f"Error testing Sketchfab API key: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled and api_key:
|
||||||
|
return {"enabled": True, "message": "Sketchfab integration is enabled and ready to use."}
|
||||||
|
elif enabled and not api_key:
|
||||||
|
return {
|
||||||
|
"enabled": False,
|
||||||
|
"message": """Sketchfab integration is currently enabled, but API key is not given. To enable it:
|
||||||
|
1. In the 3D Viewport, find the BlenderMCP panel in the sidebar (press N if hidden)
|
||||||
|
2. Keep the 'Use Sketchfab' checkbox checked
|
||||||
|
3. Enter your Sketchfab API Key
|
||||||
|
4. Restart the connection to Claude"""
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"enabled": False,
|
||||||
|
"message": """Sketchfab integration is currently disabled. To enable it:
|
||||||
|
1. In the 3D Viewport, find the BlenderMCP panel in the sidebar (press N if hidden)
|
||||||
|
2. Check the 'Use assets from Sketchfab' checkbox
|
||||||
|
3. Enter your Sketchfab API Key
|
||||||
|
4. Restart the connection to Claude"""
|
||||||
|
}
|
||||||
|
|
||||||
|
def search_sketchfab_models(self, query, categories=None, count=20, downloadable=True):
|
||||||
|
"""Search for models on Sketchfab based on query and optional filters"""
|
||||||
|
try:
|
||||||
|
api_key = bpy.context.scene.blendermcp_sketchfab_api_key
|
||||||
|
if not api_key:
|
||||||
|
return {"error": "Sketchfab API key is not configured"}
|
||||||
|
|
||||||
|
print(f"Searching Sketchfab with query: {query}, categories: {categories}, count: {count}, downloadable: {downloadable}")
|
||||||
|
|
||||||
|
# Build search parameters with exact fields from Sketchfab API docs
|
||||||
|
params = {
|
||||||
|
"type": "models",
|
||||||
|
"q": query,
|
||||||
|
"count": count,
|
||||||
|
"downloadable": downloadable,
|
||||||
|
"archives_flavours": False
|
||||||
|
}
|
||||||
|
|
||||||
|
if categories:
|
||||||
|
params["categories"] = categories
|
||||||
|
|
||||||
|
# Make API request to Sketchfab search endpoint
|
||||||
|
# The proper format according to Sketchfab API docs for API key auth
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Token {api_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Making request to Sketchfab API search endpoint with params: {params}")
|
||||||
|
|
||||||
|
# Use the search endpoint as specified in the API documentation
|
||||||
|
response = requests.get(
|
||||||
|
"https://api.sketchfab.com/v3/search",
|
||||||
|
headers=headers,
|
||||||
|
params=params
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
print(f"Authentication failed with status code 401. Check your API key.")
|
||||||
|
return {"error": f"Authentication failed (401). Check your API key."}
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"API request failed with status code {response.status_code}: {response.text}")
|
||||||
|
return {"error": f"API request failed with status code {response.status_code}: {response.text}"}
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
print(f"API response received with status code {response.status_code}")
|
||||||
|
|
||||||
|
# Safety check on the response structure
|
||||||
|
if response_data is None:
|
||||||
|
return {"error": "Received empty response from Sketchfab API"}
|
||||||
|
|
||||||
|
# Handle 'results' potentially missing from response
|
||||||
|
results = response_data.get("results", [])
|
||||||
|
if not isinstance(results, list):
|
||||||
|
return {"error": f"Unexpected response format from Sketchfab API: {response_data}"}
|
||||||
|
|
||||||
|
return response_data
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON decoding error: {str(e)}. Response text: {response.text if 'response' in locals() else 'No response'}")
|
||||||
|
return {"error": f"Invalid JSON response from Sketchfab API: {str(e)}"}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in search_sketchfab_models: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
def download_sketchfab_model(self, uid):
|
||||||
|
"""Download a model from Sketchfab by its UID"""
|
||||||
|
try:
|
||||||
|
api_key = bpy.context.scene.blendermcp_sketchfab_api_key
|
||||||
|
if not api_key:
|
||||||
|
return {"error": "Sketchfab API key is not configured"}
|
||||||
|
|
||||||
|
print(f"Attempting to download Sketchfab model with UID: {uid}")
|
||||||
|
|
||||||
|
# Use proper authorization header for API key auth
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Token {api_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Making download request to Sketchfab API with UID: {uid}")
|
||||||
|
|
||||||
|
# Request download URL using the exact endpoint from the documentation
|
||||||
|
download_endpoint = f"https://api.sketchfab.com/v3/models/{uid}/download"
|
||||||
|
print(f"Download endpoint: {download_endpoint}")
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
download_endpoint,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
print(f"Authentication failed with status code 401. Check your API key.")
|
||||||
|
return {"error": f"Authentication failed (401). Check your API key."}
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Download request failed with status code {response.status_code}: {response.text}")
|
||||||
|
return {"error": f"Download request failed with status code {response.status_code}: {response.text}"}
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Safety check for None data
|
||||||
|
if data is None:
|
||||||
|
return {"error": "Received empty response from Sketchfab API for download request"}
|
||||||
|
|
||||||
|
print(f"Download response data: {data}")
|
||||||
|
|
||||||
|
# Extract download URL with safety checks
|
||||||
|
gltf_data = data.get("gltf")
|
||||||
|
if not gltf_data:
|
||||||
|
print(f"No gltf data in response: {data}")
|
||||||
|
return {"error": "No gltf download URL available for this model. Response: " + str(data)}
|
||||||
|
|
||||||
|
download_url = gltf_data.get("url")
|
||||||
|
if not download_url:
|
||||||
|
print(f"No URL in gltf data: {gltf_data}")
|
||||||
|
return {"error": "No download URL available for this model. Make sure the model is downloadable and you have access."}
|
||||||
|
|
||||||
|
print(f"Download URL obtained: {download_url}")
|
||||||
|
|
||||||
|
# Download the model
|
||||||
|
model_response = requests.get(download_url)
|
||||||
|
|
||||||
|
if model_response.status_code != 200:
|
||||||
|
print(f"Model download failed with status code {model_response.status_code}")
|
||||||
|
return {"error": f"Model download failed with status code {model_response.status_code}"}
|
||||||
|
|
||||||
|
# Save to temporary file
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
file_path = os.path.join(temp_dir, f"{uid}.zip")
|
||||||
|
|
||||||
|
print(f"Saving downloaded model to temporary file: {file_path}")
|
||||||
|
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(model_response.content)
|
||||||
|
|
||||||
|
# Extract the zip file
|
||||||
|
print(f"Extracting zip file to: {temp_dir}")
|
||||||
|
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(temp_dir)
|
||||||
|
|
||||||
|
# Find the main glTF file
|
||||||
|
gltf_files = [f for f in os.listdir(temp_dir) if f.endswith('.gltf') or f.endswith('.glb')]
|
||||||
|
|
||||||
|
if not gltf_files:
|
||||||
|
print(f"No glTF file found in the downloaded model. Directory contents: {os.listdir(temp_dir)}")
|
||||||
|
return {"error": "No glTF file found in the downloaded model"}
|
||||||
|
|
||||||
|
main_file = os.path.join(temp_dir, gltf_files[0])
|
||||||
|
print(f"Found main glTF file: {main_file}")
|
||||||
|
|
||||||
|
# Import the model
|
||||||
|
print(f"Importing model using Blender's glTF importer")
|
||||||
|
bpy.ops.import_scene.gltf(filepath=main_file)
|
||||||
|
|
||||||
|
# Get the names of imported objects
|
||||||
|
imported_objects = [obj.name for obj in bpy.context.selected_objects]
|
||||||
|
print(f"Imported objects: {', '.join(imported_objects)}")
|
||||||
|
|
||||||
|
# Clean up temporary files
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
print(f"Temporary directory cleaned up: {temp_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to clean up temporary directory: {temp_dir}. Error: {str(e)}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Model imported successfully",
|
||||||
|
"imported_objects": imported_objects
|
||||||
|
}
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"JSON decoding error: {str(e)}. Response text: {response.text if 'response' in locals() else 'No response'}")
|
||||||
|
return {"error": f"Invalid JSON response from Sketchfab API: {str(e)}"}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in download_sketchfab_model: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"error": f"Failed to download model: {str(e)}"}
|
||||||
|
#endregion
|
||||||
|
|
||||||
# Blender UI Panel
|
# Blender UI Panel
|
||||||
class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
||||||
bl_label = "Blender MCP"
|
bl_label = "Blender MCP"
|
||||||
@ -1394,6 +1646,10 @@ class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
|||||||
layout.prop(scene, "blendermcp_hyper3d_api_key", text="API Key")
|
layout.prop(scene, "blendermcp_hyper3d_api_key", text="API Key")
|
||||||
layout.operator("blendermcp.set_hyper3d_free_trial_api_key", text="Set Free Trial API Key")
|
layout.operator("blendermcp.set_hyper3d_free_trial_api_key", text="Set Free Trial API Key")
|
||||||
|
|
||||||
|
layout.prop(scene, "blendermcp_use_sketchfab", text="Use assets from Sketchfab")
|
||||||
|
if scene.blendermcp_use_sketchfab:
|
||||||
|
layout.prop(scene, "blendermcp_sketchfab_api_key", text="API Key")
|
||||||
|
|
||||||
if not scene.blendermcp_server_running:
|
if not scene.blendermcp_server_running:
|
||||||
layout.operator("blendermcp.start_server", text="Connect to MCP server")
|
layout.operator("blendermcp.start_server", text="Connect to MCP server")
|
||||||
else:
|
else:
|
||||||
@ -1492,6 +1748,19 @@ def register():
|
|||||||
default=""
|
default=""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bpy.types.Scene.blendermcp_use_sketchfab = bpy.props.BoolProperty(
|
||||||
|
name="Use Sketchfab",
|
||||||
|
description="Enable Sketchfab asset integration",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
bpy.types.Scene.blendermcp_sketchfab_api_key = bpy.props.StringProperty(
|
||||||
|
name="Sketchfab API Key",
|
||||||
|
subtype="PASSWORD",
|
||||||
|
description="API Key provided by Sketchfab",
|
||||||
|
default=""
|
||||||
|
)
|
||||||
|
|
||||||
bpy.utils.register_class(BLENDERMCP_PT_Panel)
|
bpy.utils.register_class(BLENDERMCP_PT_Panel)
|
||||||
bpy.utils.register_class(BLENDERMCP_OT_SetFreeTrialHyper3DAPIKey)
|
bpy.utils.register_class(BLENDERMCP_OT_SetFreeTrialHyper3DAPIKey)
|
||||||
bpy.utils.register_class(BLENDERMCP_OT_StartServer)
|
bpy.utils.register_class(BLENDERMCP_OT_StartServer)
|
||||||
@ -1516,6 +1785,8 @@ def unregister():
|
|||||||
del bpy.types.Scene.blendermcp_use_hyper3d
|
del bpy.types.Scene.blendermcp_use_hyper3d
|
||||||
del bpy.types.Scene.blendermcp_hyper3d_mode
|
del bpy.types.Scene.blendermcp_hyper3d_mode
|
||||||
del bpy.types.Scene.blendermcp_hyper3d_api_key
|
del bpy.types.Scene.blendermcp_hyper3d_api_key
|
||||||
|
del bpy.types.Scene.blendermcp_use_sketchfab
|
||||||
|
del bpy.types.Scene.blendermcp_sketchfab_api_key
|
||||||
|
|
||||||
print("BlenderMCP addon unregistered")
|
print("BlenderMCP addon unregistered")
|
||||||
|
|
||||||
|
|||||||
@ -515,6 +515,144 @@ def get_hyper3d_status(ctx: Context) -> str:
|
|||||||
logger.error(f"Error checking Hyper3D status: {str(e)}")
|
logger.error(f"Error checking Hyper3D status: {str(e)}")
|
||||||
return f"Error checking Hyper3D status: {str(e)}"
|
return f"Error checking Hyper3D status: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_sketchfab_status(ctx: Context) -> str:
|
||||||
|
"""
|
||||||
|
Check if Sketchfab integration is enabled in Blender.
|
||||||
|
Returns a message indicating whether Sketchfab features are available.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
blender = get_blender_connection()
|
||||||
|
result = blender.send_command("get_sketchfab_status")
|
||||||
|
enabled = result.get("enabled", False)
|
||||||
|
message = result.get("message", "")
|
||||||
|
|
||||||
|
return message
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking Sketchfab status: {str(e)}")
|
||||||
|
return f"Error checking Sketchfab status: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def search_sketchfab_models(
|
||||||
|
ctx: Context,
|
||||||
|
query: str,
|
||||||
|
categories: str = None,
|
||||||
|
count: int = 20,
|
||||||
|
downloadable: bool = True
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Search for models on Sketchfab with optional filtering.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- query: Text to search for
|
||||||
|
- categories: Optional comma-separated list of categories
|
||||||
|
- count: Maximum number of results to return (default 20)
|
||||||
|
- downloadable: Whether to include only downloadable models (default True)
|
||||||
|
|
||||||
|
Returns a formatted list of matching models.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
blender = get_blender_connection()
|
||||||
|
logger.info(f"Searching Sketchfab models with query: {query}, categories: {categories}, count: {count}, downloadable: {downloadable}")
|
||||||
|
|
||||||
|
result = blender.send_command("search_sketchfab_models", {
|
||||||
|
"query": query,
|
||||||
|
"categories": categories,
|
||||||
|
"count": count,
|
||||||
|
"downloadable": downloadable
|
||||||
|
})
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
logger.error(f"Error from Sketchfab search: {result['error']}")
|
||||||
|
return f"Error: {result['error']}"
|
||||||
|
|
||||||
|
# Safely get results with fallbacks for None
|
||||||
|
if result is None:
|
||||||
|
logger.error("Received None result from Sketchfab search")
|
||||||
|
return "Error: Received no response from Sketchfab search"
|
||||||
|
|
||||||
|
# Format the results
|
||||||
|
models = result.get("results", []) or []
|
||||||
|
if not models:
|
||||||
|
return f"No models found matching '{query}'"
|
||||||
|
|
||||||
|
formatted_output = f"Found {len(models)} models matching '{query}':\n\n"
|
||||||
|
|
||||||
|
for model in models:
|
||||||
|
if model is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
model_name = model.get("name", "Unnamed model")
|
||||||
|
model_uid = model.get("uid", "Unknown ID")
|
||||||
|
formatted_output += f"- {model_name} (UID: {model_uid})\n"
|
||||||
|
|
||||||
|
# Get user info with safety checks
|
||||||
|
user = model.get("user") or {}
|
||||||
|
username = user.get("username", "Unknown author") if isinstance(user, dict) else "Unknown author"
|
||||||
|
formatted_output += f" Author: {username}\n"
|
||||||
|
|
||||||
|
# Get license info with safety checks
|
||||||
|
license_data = model.get("license") or {}
|
||||||
|
license_label = license_data.get("label", "Unknown") if isinstance(license_data, dict) else "Unknown"
|
||||||
|
formatted_output += f" License: {license_label}\n"
|
||||||
|
|
||||||
|
# Add face count and downloadable status
|
||||||
|
face_count = model.get("faceCount", "Unknown")
|
||||||
|
is_downloadable = "Yes" if model.get("isDownloadable") else "No"
|
||||||
|
formatted_output += f" Face count: {face_count}\n"
|
||||||
|
formatted_output += f" Downloadable: {is_downloadable}\n\n"
|
||||||
|
|
||||||
|
return formatted_output
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching Sketchfab models: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return f"Error searching Sketchfab models: {str(e)}"
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def download_sketchfab_model(
|
||||||
|
ctx: Context,
|
||||||
|
uid: str
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Download and import a Sketchfab model by its UID.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- uid: The unique identifier of the Sketchfab model
|
||||||
|
|
||||||
|
Returns a message indicating success or failure.
|
||||||
|
The model must be downloadable and you must have proper access rights.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
blender = get_blender_connection()
|
||||||
|
logger.info(f"Attempting to download Sketchfab model with UID: {uid}")
|
||||||
|
|
||||||
|
result = blender.send_command("download_sketchfab_model", {
|
||||||
|
"uid": uid
|
||||||
|
})
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
logger.error("Received None result from Sketchfab download")
|
||||||
|
return "Error: Received no response from Sketchfab download request"
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
logger.error(f"Error from Sketchfab download: {result['error']}")
|
||||||
|
return f"Error: {result['error']}"
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
imported_objects = result.get("imported_objects", [])
|
||||||
|
object_names = ", ".join(imported_objects) if imported_objects else "none"
|
||||||
|
return f"Successfully imported model. Created objects: {object_names}"
|
||||||
|
else:
|
||||||
|
return f"Failed to download model: {result.get('message', 'Unknown error')}"
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading Sketchfab model: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return f"Error downloading Sketchfab model: {str(e)}"
|
||||||
|
|
||||||
def _process_bbox(original_bbox: list[float] | list[int] | None) -> list[int] | None:
|
def _process_bbox(original_bbox: list[float] | list[int] | None) -> list[int] | None:
|
||||||
if original_bbox is None:
|
if original_bbox is None:
|
||||||
return None
|
return None
|
||||||
@ -705,7 +843,14 @@ def asset_creation_strategy() -> str:
|
|||||||
- For objects/models: Use download_polyhaven_asset() with asset_type="models"
|
- For objects/models: Use download_polyhaven_asset() with asset_type="models"
|
||||||
- For materials/textures: Use download_polyhaven_asset() with asset_type="textures"
|
- For materials/textures: Use download_polyhaven_asset() with asset_type="textures"
|
||||||
- For environment lighting: Use download_polyhaven_asset() with asset_type="hdris"
|
- For environment lighting: Use download_polyhaven_asset() with asset_type="hdris"
|
||||||
2. Hyper3D(Rodin)
|
2. Sketchfab
|
||||||
|
Use get_sketchfab_status() to verify its status
|
||||||
|
If Sketchfab is enabled:
|
||||||
|
- For objects/models: First search using search_sketchfab_models() with your query
|
||||||
|
- Then download specific models using download_sketchfab_model() with the UID
|
||||||
|
- Note that only downloadable models can be accessed, and API key must be properly configured
|
||||||
|
- Sketchfab has a wider variety of models than PolyHaven, especially for specific subjects
|
||||||
|
3. Hyper3D(Rodin)
|
||||||
Hyper3D Rodin is good at generating 3D models for single item.
|
Hyper3D Rodin is good at generating 3D models for single item.
|
||||||
So don't try to:
|
So don't try to:
|
||||||
1. Generate the whole scene with one shot
|
1. Generate the whole scene with one shot
|
||||||
@ -735,11 +880,17 @@ def asset_creation_strategy() -> str:
|
|||||||
- Ensure that all objects that should not be clipping are not clipping.
|
- Ensure that all objects that should not be clipping are not clipping.
|
||||||
- Items have right spatial relationship.
|
- Items have right spatial relationship.
|
||||||
|
|
||||||
|
4. Recommended asset source priority:
|
||||||
|
- For specific existing objects: First try Sketchfab, then PolyHaven
|
||||||
|
- For generic objects/furniture: First try PolyHaven, then Sketchfab
|
||||||
|
- For custom or unique items not available in libraries: Use Hyper3D Rodin
|
||||||
|
- For environment lighting: Use PolyHaven HDRIs
|
||||||
|
- For materials/textures: Use PolyHaven textures
|
||||||
|
|
||||||
Only fall back to scripting when:
|
Only fall back to scripting when:
|
||||||
- PolyHaven and Hyper3D are disabled
|
- PolyHaven, Sketchfab, and Hyper3D are all disabled
|
||||||
- A simple primitive is explicitly requested
|
- A simple primitive is explicitly requested
|
||||||
- No suitable PolyHaven asset exists
|
- No suitable asset exists in any of the libraries
|
||||||
- Hyper3D Rodin failed to generate the desired asset
|
- Hyper3D Rodin failed to generate the desired asset
|
||||||
- The task specifically requires a basic material/color
|
- The task specifically requires a basic material/color
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user