Commit d1cd236
feat: add sprite sheet support for 3D model thumbnails (#213)
* feat: add sprite sheet support for 3D model thumbnails
Implements sprite sheet generation and interactive viewing for OpenForge 3D models, reducing storage costs and improving user experience with 360° model previews.
## Backend Changes
- Add `sprite_metadata` JSONB column to images table (schema v15-16)
- Update image SQL queries to handle sprite_metadata
- Add `/api/sprites` endpoint for sprite sheet generation
- Fix image comparison to detect sprite_metadata changes
- Update `insert_image()` to use ON CONFLICT DO UPDATE for sprite metadata
## Scanner Improvements
- Add `--sprites` flag to generate sprite sheets during scan
- Add `bin/generate_sprite` tool for sprite sheet creation
- Add `bin/resort_fixtures` tool for stable fixture sorting
- Fix fixture sorting to use full_name for top-level array
- Use /tmp for temp sprite files to avoid path length issues
- Preserve sprite_metadata.angles sort order by index
## Frontend Components
- Add SpriteViewer component with interactive 3D rotation
- Implement unwrapped cube layout for angle selection (10 angles)
- Add keyboard navigation (arrow keys for rotation)
- Add mouse drag support for horizontal rotation
- Auto-focus viewer for immediate keyboard interaction
- Reset angle when switching between blueprints
## Type Definitions
- Add SpriteThumbnailData, SpriteAngle types
- Update ThumbnailData union type
- Add comprehensive test coverage (27 tests for SpriteViewer)
## Testing
- Add sprite_metadata change detection test
- Update image-gallery tests for sprite support
- Add full SpriteViewer test suite (rendering, controls, keyboard, mouse, a11y)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* adding design
* fix: address review feedback - improve code quality and maintainability
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use tempfile.gettempdir() for subprocess cwd
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: decompose generate_sprite into focused helper functions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add sprite_metadata support to update_image()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: remove accidentally shipped progress tracking file
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: remove accidentally shipped fixture data files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: restore sewers.json to original state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: remove version_15.py - should come from PR #153
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: extract sprite viewer event handling into custom hooks
- Extract keyboard rotation logic into useKeyboardRotation hook
- Extract mouse drag rotation logic into useMouseDragRotation hook
- Reduce SpriteViewer component from handling state + rendering + keyboard + mouse events to just state + rendering
- Follows single responsibility principle and reduces cognitive load
- Raise NotImplementedError for --db-update flag instead of silent warning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: remove --db-update flag from generate_sprite
Database updates will be handled through the fixture workflow,
not directly by this CLI tool.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: use key prop pattern for sprite viewer state reset
Replace useEffect-based state reset with React key prop pattern.
When sprite_url changes, React will unmount and remount the
component with fresh state - more idiomatic than manual reset.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: address code quality improvements from Gemini review
1. Add try/finally cleanup for sprite files in bin/generate_sprite
2. Validate default_angle against actual sprite_metadata instead of hardcoded max
3. Add try/finally cleanup for temp files in create_and_upload_thumbnail
4. Simplify CTE in insert_image (remove unnecessary WITH clause)
5. Derive angle constants from sprite metadata instead of hardcoding
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use blueprint.id for key and dynamic angleMap
1. Use blueprint.id instead of sprite_url for SpriteViewer key
- More explicit and robust for component remounting
2. Build angleMap dynamically from angle names
- Decouple frontend from backend array order
- Component adapts if angle structure changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add specific error handling for stl-thumb failures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: improve exception handling in helper functions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* security: only pass required environment keys to config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>1 parent 7b85135 commit d1cd236
124 files changed
Lines changed: 2634 additions & 537 deletions
File tree
- .beads
- .vscode
- bin
- docs
- integration_tests
- openforge
- app
- routes
- data
- db
- fixtures
- tag_descriptions
- schema
- sql
- openapi/schemas
- public
- src
- app
- __tests__
- admin
- components
- __tests__
- admin
- __tests__
- blueprint
- __tests__
- results
- __tests__
- ui
- __tests__
- contexts
- __tests__
- hooks
- __tests__
- lib
- __tests__
- stores/__tests__
- test-utils
- mocks
- utils
- __tests__
- tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
129 | 148 | | |
130 | 149 | | |
131 | 150 | | |
| |||
0 commit comments