sketchfab preview/model size normalisation#163
Conversation
- Add get_sketchfab_model_preview handler in addon.py - Fetches thumbnail from Sketchfab API - Returns base64-encoded image with model info - Add get_sketchfab_model_preview MCP tool in server.py - Returns Image type for visual confirmation - Allows users to preview models before downloading
- Add normalize_size and target_size parameters to download_sketchfab_model - Calculate combined bounding box for all imported mesh objects - Apply uniform scale to normalize largest dimension to target_size - Return detailed import info: dimensions, bounding box, scale factor - Default: normalize_size=True, target_size=1.0 (1 meter) This prevents extremely large or small models from being imported, making it easier for LLMs to work with consistent object sizes.
- Remove normalize_size parameter (always normalize now) - Make target_size a required parameter with no default value - Add helpful examples in docstring for common object sizes - Forces LLM to explicitly consider and specify model size
WalkthroughAdded a new public endpoint get_sketchfab_model_preview(uid) to fetch and return a base64-encoded Sketchfab thumbnail with metadata, and extended download_sketchfab_model(uid, normalize_size=False, target_size=1.0) to optionally normalize imported model size, recalculate world bounds, and return richer metadata and bounding box information. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server as src/blender_mcp/server.py
participant Addon as addon.py
participant Sketchfab
participant Blender
rect rgb(220,235,255)
Note over Client,Server: Preview request flow
Client->>Server: get_sketchfab_model_preview(uid)
Server->>Addon: get_sketchfab_model_preview(uid)
Addon->>Sketchfab: GET model metadata (auth) -> get thumbnail URLs
Sketchfab-->>Addon: metadata + thumbnail URLs
Addon->>Sketchfab: Download chosen thumbnail
Sketchfab-->>Addon: Image bytes
Addon->>Addon: Encode image to base64 + attach metadata
Addon-->>Server: base64 image + metadata
Server-->>Client: Image object / metadata
end
rect rgb(255,245,220)
Note over Client,Server: Download + optional normalization flow
Client->>Server: download_sketchfab_model(uid, target_size)
Server->>Addon: download_sketchfab_model(uid, normalize_size=True/False, target_size)
Addon->>Sketchfab: Fetch model files (auth)
Sketchfab-->>Addon: Model archive/files
Addon->>Blender: Import model into scene
Addon->>Blender: Query world bounding box & dimensions
Blender-->>Addon: bbox & dimensions
alt normalize_size == True
Addon->>Addon: Compute scale factor to match target_size
Addon->>Blender: Apply scale to imported root objects
Addon->>Blender: Recompute world bbox & dimensions
Blender-->>Addon: updated bbox & dimensions
end
Addon-->>Server: Result payload (object names, bbox, dims, scale_applied, normalized)
Server-->>Client: Formatted success message + metadata
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/blender_mcp/server.py (2)
696-698: Consider using exception chaining for better traceability.When re-raising exceptions, use
raise ... from eto preserve the original traceback chain.except Exception as e: logger.error(f"Error getting Sketchfab preview: {str(e)}") - raise Exception(f"Failed to get preview: {str(e)}") + raise Exception(f"Failed to get preview: {str(e)}") from e
747-747: Remove unnecessary f-string prefix.This string has no placeholders, so the
fprefix is unnecessary.- output = f"Successfully imported model.\n" + output = "Successfully imported model.\n"addon.py (1)
1770-1845: Consider extracting duplicate bounding box calculation logic.The bounding box calculation code (lines 1774-1793) is duplicated after scaling (lines 1820-1838). Consider extracting this into a helper method to reduce code duplication and improve maintainability.
Example helper:
def _calculate_combined_aabb(self, mesh_objects): """Calculate combined AABB for multiple mesh objects.""" all_min = mathutils.Vector((float('inf'), float('inf'), float('inf'))) all_max = mathutils.Vector((float('-inf'), float('-inf'), float('-inf'))) for obj in mesh_objects: try: bbox = self._get_aabb(obj) obj_min = mathutils.Vector(bbox[0]) obj_max = mathutils.Vector(bbox[1]) all_min.x = min(all_min.x, obj_min.x) all_min.y = min(all_min.y, obj_min.y) all_min.z = min(all_min.z, obj_min.z) all_max.x = max(all_max.x, obj_max.x) all_max.y = max(all_max.y, obj_max.y) all_max.z = max(all_max.z, obj_max.z) except Exception as e: print(f"Warning: Could not get AABB for {obj.name}: {e}") continue return all_min, all_max
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
addon.py(3 hunks)src/blender_mcp/server.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/blender_mcp/server.py (1)
addon.py (2)
get_sketchfab_model_preview(1572-1656)download_sketchfab_model(1658-1875)
addon.py (1)
src/blender_mcp/server.py (2)
get_sketchfab_model_preview(660-698)download_sketchfab_model(702-772)
🪛 Ruff (0.14.7)
src/blender_mcp/server.py
661-661: Unused function argument: ctx
(ARG001)
680-680: Abstract raise to an inner function
(TRY301)
680-680: Create your own exception
(TRY002)
680-680: Avoid specifying long messages outside the exception class
(TRY003)
683-683: Abstract raise to an inner function
(TRY301)
683-683: Create your own exception
(TRY002)
696-696: Do not catch blind exception: Exception
(BLE001)
697-697: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
697-697: Use explicit conversion flag
Replace with conversion flag
(RUF010)
698-698: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
698-698: Create your own exception
(TRY002)
698-698: Avoid specifying long messages outside the exception class
(TRY003)
698-698: Use explicit conversion flag
Replace with conversion flag
(RUF010)
703-703: Unused function argument: ctx
(ARG001)
747-747: f-string without any placeholders
Remove extraneous f prefix
(F541)
addon.py
1653-1653: Do not catch blind exception: Exception
(BLE001)
1656-1656: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1791-1791: Do not catch blind exception: Exception
(BLE001)
1837-1838: try-except-continue detected, consider logging the exception
(S112)
1837-1837: Do not catch blind exception: Exception
(BLE001)
1866-1866: Consider moving this statement to an else block
(TRY300)
🔇 Additional comments (6)
src/blender_mcp/server.py (2)
701-724: Good design: requiring explicit size specification.Making
target_sizea required parameter forces the caller to think about proper model sizing, which helps maintain consistent scale relationships between objects in the scene. The comprehensive docstring with examples is helpful.
660-694: LGTM!The preview functionality is well-implemented with proper error handling and logging. This enables visual confirmation of models before downloading, which is a nice UX improvement.
addon.py (4)
233-238: LGTM!Handler registration follows the established pattern and is properly guarded by the Sketchfab enable flag.
1572-1656: LGTM!The implementation is solid with:
- Proper API authentication
- Smart thumbnail selection preferring medium sizes (400-800px)
- Comprehensive error handling for various HTTP status codes
- Timeout protection
- Rich metadata in the response
1808-1818: LGTM!Correctly applies scaling to all imported objects (not just meshes) to maintain hierarchy and relative positioning, then updates the view layer to refresh transforms.
1852-1866: LGTM!Result construction is clean with appropriate conditional inclusion of metadata and sensible precision for float values.
| if mesh_objects: | ||
| # Calculate combined AABB for all meshes | ||
| all_min = mathutils.Vector((float('inf'), float('inf'), float('inf'))) | ||
| all_max = mathutils.Vector((float('-inf'), float('-inf'), float('-inf'))) | ||
|
|
||
| for obj in mesh_objects: | ||
| try: | ||
| bbox = self._get_aabb(obj) | ||
| obj_min = mathutils.Vector(bbox[0]) | ||
| obj_max = mathutils.Vector(bbox[1]) | ||
|
|
||
| all_min.x = min(all_min.x, obj_min.x) | ||
| all_min.y = min(all_min.y, obj_min.y) | ||
| all_min.z = min(all_min.z, obj_min.z) | ||
|
|
||
| all_max.x = max(all_max.x, obj_max.x) | ||
| all_max.y = max(all_max.y, obj_max.y) | ||
| all_max.z = max(all_max.z, obj_max.z) | ||
| except Exception as e: | ||
| print(f"Warning: Could not get AABB for {obj.name}: {e}") | ||
| continue | ||
|
|
||
| # Calculate dimensions | ||
| dimensions = [ | ||
| all_max.x - all_min.x, | ||
| all_max.y - all_min.y, | ||
| all_max.z - all_min.z | ||
| ] | ||
| max_dimension = max(dimensions) |
There was a problem hiding this comment.
Potential edge case: invalid bounding box when all mesh AABBs fail.
If all mesh objects fail the _get_aabb call (e.g., due to exceptions), all_min and all_max will remain at their initial inf/-inf values, resulting in invalid bounding box and dimension calculations.
Consider adding a check after the loop:
+ # Check if any valid bounding boxes were found
+ if all_min.x == float('inf'):
+ # No valid bounding boxes found
+ world_bounding_box = None
+ dimensions = None
+ max_dimension = 0
+ else:
# Calculate dimensions
dimensions = [
all_max.x - all_min.x,
...🧰 Tools
🪛 Ruff (0.14.7)
1791-1791: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In addon.py around lines 1773 to 1801, the loop that collects per-mesh AABBs
doesn't handle the case where every _get_aabb call fails, leaving all_min/
all_max as inf/-inf and producing invalid dimensions; after the loop check
whether all_min.x is still inf (or any component is inf/-inf) and if so handle
the error path (e.g., log/raise a clear error or set dimensions to zeros or skip
further processing) before computing dimensions and max_dimension so you never
compute with infinite values.
…rchical models
- Add recursive get_all_mesh_children() to collect meshes from entire hierarchy
- Fix bounding box calculation to include all nested mesh objects
- Use transform_apply() to properly apply scale to entire object hierarchy
- Prevents parts from becoming scattered when scaling complex models
This fixes issues with Sketchfab models that have deep hierarchies like:
Sketchfab_model (EMPTY)
└─ RootNode (EMPTY)
└─ Chassis (MESH) ← now correctly included
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
addon.py (2)
1572-1656: Preview flow is solid; consider narrowing the catch-all exceptionThe overall logic (API key check, model fetch, thumbnail selection, base64 encoding, and contextual metadata) looks good and matches how
get_sketchfab_model_previewis consumed inserver.py. To avoid masking programming/runtime errors and to address BLE001, you could narrow the finalexcept Exception as eto something likerequests.exceptions.RequestExceptionplusjson.JSONDecodeError, keeping only genuinely unexpected failures out of the happy path while still returning a clean error dict for network/JSON issues.
1658-1883: Download + normalization pipeline looks correct; tighten normalization API around target_sizeThe download, zip path‑traversal checks, glTF import, hierarchy mesh aggregation, and AABB/dimension recomputation after scaling all look coherent and line up with how
download_sketchfab_modelis called fromserver.py(including the expected result keys). Given that the MCP server always passesnormalize_size=Trueand requirestarget_size, you might (a) simplify this API by defaultingnormalize_sizetoTrueor removing it to reflect that normalization is now mandatory from the tool’s perspective, and (b) add an early guard for invalidtarget_size(e.g.,<= 0) when normalization is requested so models are never accidentally scaled to zero or a negative size.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
addon.py(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
addon.py (1)
src/blender_mcp/server.py (2)
get_sketchfab_model_preview(660-698)download_sketchfab_model(702-772)
🪛 Ruff (0.14.7)
addon.py
1653-1653: Do not catch blind exception: Exception
(BLE001)
1656-1656: Use explicit conversion flag
Replace with conversion flag
(RUF010)
1882-1882: Consider moving this statement to an else block
(TRY300)
🔇 Additional comments (1)
addon.py (1)
231-239: Sketchfab handler registration correctly exposes the new preview commandAdding
"get_sketchfab_model_preview": self.get_sketchfab_model_previewto the Sketchfab handler map is consistent with the existing pattern and keeps the Blender side aligned with the MCP server tools. No changes needed.
- Find root objects (objects without parents) instead of all selected objects - Apply scale factor ONLY to root objects, not children - Remove transform_apply (parent scale automatically affects children via matrix_world) - Child meshes inherit scaling through Blender's hierarchy system Fixes issue where scale was applied to every object in hierarchy, causing cumulative scaling (0.007^5 ≈ 0) that made models invisible.
* feat: Add Sketchfab model preview functionality
- Add get_sketchfab_model_preview handler in addon.py
- Fetches thumbnail from Sketchfab API
- Returns base64-encoded image with model info
- Add get_sketchfab_model_preview MCP tool in server.py
- Returns Image type for visual confirmation
- Allows users to preview models before downloading
* feat: Add size normalization for Sketchfab model imports
- Add normalize_size and target_size parameters to download_sketchfab_model
- Calculate combined bounding box for all imported mesh objects
- Apply uniform scale to normalize largest dimension to target_size
- Return detailed import info: dimensions, bounding box, scale factor
- Default: normalize_size=True, target_size=1.0 (1 meter)
This prevents extremely large or small models from being imported,
making it easier for LLMs to work with consistent object sizes.
* feat: Make target_size required parameter for Sketchfab downloads
- Remove normalize_size parameter (always normalize now)
- Make target_size a required parameter with no default value
- Add helpful examples in docstring for common object sizes
- Forces LLM to explicitly consider and specify model size
* chore: Remove temporary test files from development
* fix: Correct bounding box calculation and scale application for hierarchical models
- Add recursive get_all_mesh_children() to collect meshes from entire hierarchy
- Fix bounding box calculation to include all nested mesh objects
- Use transform_apply() to properly apply scale to entire object hierarchy
- Prevents parts from becoming scattered when scaling complex models
This fixes issues with Sketchfab models that have deep hierarchies like:
Sketchfab_model (EMPTY)
└─ RootNode (EMPTY)
└─ Chassis (MESH) ← now correctly included
* fix: Apply scale only to root objects to prevent cumulative scaling
- Find root objects (objects without parents) instead of all selected objects
- Apply scale factor ONLY to root objects, not children
- Remove transform_apply (parent scale automatically affects children via matrix_world)
- Child meshes inherit scaling through Blender's hierarchy system
Fixes issue where scale was applied to every object in hierarchy,
causing cumulative scaling (0.007^5 ≈ 0) that made models invisible.
User description
PR Type
Enhancement
Description
Add model preview functionality with thumbnail retrieval from Sketchfab API
Implement automatic size normalization for imported 3D models with explicit target_size parameter
Calculate and return detailed bounding box and dimension information for imported models
Make target_size a required parameter to force LLM consideration of model scale
Diagram Walkthrough
File Walkthrough
addon.py
Add preview and size normalization for Sketchfab modelsaddon.py
get_sketchfab_model_previewhandler to fetch and encode modelthumbnails as base64
download_sketchfab_modelwithnormalize_sizeandtarget_sizeparameters
_get_aabbmethod for all meshobjects
and scale factor
server.py
Add preview tool and require explicit size specificationsrc/blender_mcp/server.py
get_sketchfab_model_previewMCP tool that returns Image typefor visual confirmation
download_sketchfab_modelto requiretarget_sizeparameter withpractical examples
normalize_sizeparameter)normalization details
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.