Add Fusion 360 (.f3d) design file attachments to archives

- Add f3d_path field to PrintArchive model and ArchiveResponse schema
- Add API endpoints for F3D upload/download/delete
- Add cyan badge on archive cards when F3D file is attached
- Add context menu options: Upload/Replace F3D, Download F3D, Remove F3D
- Include F3D files in backup/restore
- Add database migration for f3d_path column
- Add API tests for F3D endpoints
- Rebuild CHANGELOG from GitHub releases

Resolves #90
This commit is contained in:
maziggy
2026-01-19 13:29:37 +01:00
committed by MartinNYHC
parent 5d6ba0f875
commit 0b2c0c214d
12 changed files with 625 additions and 714 deletions
+138 -644
View File
@@ -2,706 +2,200 @@
All notable changes to Bambuddy will be documented in this file.
## [0.1.6b15] - 2026-01-17
## [Unreleased]
### Added
- **Virtual Printer "Queue" mode** - New mode that archives files and adds them to the print queue:
- Three modes now available: Archive (immediate), Review (pending list), Queue (print queue)
- Queue mode creates unassigned queue items that can be assigned to a printer later
- Renamed old "Queue for Review" to "Review" to avoid confusion with print queue
- **Unassigned queue items** - Print queue now supports items without an assigned printer:
- Queue items can be created without a printer (printer_id nullable)
- Queue page shows "Unassigned" filter option and highlights unassigned items in orange
- Edit modal allows assigning a printer to unassigned items
- Useful for virtual printer uploads where target printer is decided later
- **Sidebar badge indicators** - Visual indicators on sidebar icons for pending items:
- Queue icon shows yellow badge with count of pending queue items
- Archive icon shows blue badge with count of pending uploads (virtual printer review items)
- Badges auto-update every 5 seconds and on window focus
- **Fusion 360 design file attachments** - Attach F3D files to archives for complete design tracking:
- Upload F3D files via archive context menu ("Upload F3D" / "Replace F3D")
- Cyan badge on archive card indicates attached F3D file (next to source 3MF badge)
- Click badge to download, or use "Download F3D" in context menu
- F3D files included in backup/restore
- API tests for F3D endpoints
### Changed
- Virtual printer mode labels: "Immediate" → "Archive", "Queue for Review" → "Review"
- Queue page printer filter now includes "Unassigned" option
## [0.1.6b14] - 2026-01-17
### Fixed
- **Chamber temp showing on printers without sensor in multi-printer setups** - Fixed regression where A1/P1S chamber temperature would appear after adding a second printer:
- Root cause: REST API `/printers/{id}/status` endpoint wasn't filtering chamber temp by model
- WebSocket broadcasts filtered correctly, but initial REST fetch didn't
- Now both REST and WebSocket consistently filter chamber temp for P1P/P1S/A1/A1Mini
- Fixes [#82](https://github.com/maziggy/bambuddy/issues/82) (multi-printer regression)
## [0.1.6b13] - 2026-01-16
### Fixed
- **Queue prints failing on A1 printers** - Fixed "MicroSD Card read/write exception error" when starting prints from queue:
- Root cause: Queue uploaded files to `/cache/` but print command referenced root `/`
- Queue now uploads to root directory like archive reprint does
- Added filename cleaning (handles `.gcode.3mf` double extensions)
- Added pre-delete of existing files to avoid FTP 553 errors
- Fixes [#86](https://github.com/maziggy/bambuddy/issues/86)
## [0.1.6b12] - 2026-01-16
### Added
- **Project parts tracking** - Track individual parts/objects separately from print plates:
- New "Target Parts" field in project create/edit modal alongside "Target Plates"
- Separate progress bars for plates (print jobs) vs parts (objects printed)
- Parts count auto-detected from 3MF files when archiving prints
- Project cards show both plates and parts counts in footer stats
- Example: Voron build with 25 plates producing 150 parts total
- Closes [#85](https://github.com/maziggy/bambuddy/issues/85)
- **Archive quantity auto-detection** - Automatically set parts count when archiving:
- Extracts printable object count from 3MF `slice_info.config`
- Respects skipped objects (not counted)
- Falls back to 1 if extraction fails
- **Migration script for existing archives** - Update quantities on existing archives:
- Run `python scripts/update_archive_quantities.py` to update existing archives
- Use `--dry-run` flag to preview changes without applying them
- Parses 3MF files to extract correct object counts
### Changed
- Project stats now correctly distinguish between plates (archive count) and parts (sum of quantities)
- Project list `completed_count` now represents parts printed, not print jobs
- Progress calculations: plates use `archive_count/target_count`, parts use `completed_count/target_parts_count`
## [0.1.6b11] - 2026-01-13
## [0.1.6b8] - 2026-01-17
### Added
- **MQTT Publishing** - Publish BamBuddy events to external MQTT brokers for integration with Home Assistant, Node-RED, and other automation platforms:
- New "Network" tab in Settings (between Filament and API Keys)
- Configure broker hostname, port, username/password, TLS, and topic prefix
- Auto-populate port when toggling TLS (1883 ↔ 8883)
- Real-time connection status indicator in tab and settings card
- Published topics include:
- `bambuddy/status` - Online/offline status
- `bambuddy/printers/{serial}/status` - Real-time printer state (throttled to 1/sec)
- `bambuddy/printers/{serial}/print/started|completed|failed` - Print lifecycle events
- `bambuddy/printers/{serial}/ams/changed` - AMS filament changes
- `bambuddy/queue/job_added|job_started|job_completed` - Print queue events
- `bambuddy/maintenance/alert|reset` - Maintenance notifications
- `bambuddy/smart_plugs/on|off|energy` - Smart plug state changes
- `bambuddy/archive/created|updated` - Archive events
- Supports TLS/SSL encryption with self-signed certificates
- Auto-reconnect when settings change
- Settings included in backup/restore
- **FTP Retry moved to Network tab** - FTP retry settings relocated from General to Network tab for better organization
- New "Network" tab in Settings for MQTT configuration
- Configure broker, port, credentials, TLS, and topic prefix
- Real-time connection status indicator
- Topics: printer status, print lifecycle, AMS changes, queue events, maintenance alerts, smart plug states, archive events
- **Virtual Printer Queue Mode** - New mode that archives files and adds them directly to the print queue:
- Three modes: Archive (immediate), Review (pending list), Queue (print queue)
- Queue mode creates unassigned items that can be assigned to a printer later
- **Unassigned Queue Items** - Print queue now supports items without an assigned printer:
- "Unassigned" filter option on Queue page
- Unassigned items highlighted in orange
- Assign printer via edit modal
- **Sidebar Badge Indicators** - Visual indicators on sidebar icons:
- Queue icon: yellow badge with pending item count
- Archive icon: blue badge with pending uploads count
- Auto-updates every 5 seconds and on window focus
- **Project Parts Tracking** - Track individual parts/objects separately from print plates:
- "Target Parts" field alongside "Target Plates"
- Separate progress bars for plates vs parts
- Parts count auto-detected from 3MF files
## [0.1.6b10] - 2026-01-11
### Fixed
- Chamber temp on A1/P1S - Fixed regression where chamber temperature appeared on printers without sensors in multi-printer setups
- Queue prints on A1 - Fixed "MicroSD Card read/write exception error" when starting prints from queue
- Spoolman sync - Fixed Bambu Lab spool detection and AMS tray data persistence
- FTP downloads - Fixed downloads failing for .3mf files without .gcode extension
- Project statistics - Fixed inconsistent display between project list and detail views
- Chamber light state - Fixed WebSocket broadcasts not including light state changes
- Backup/restore - Improved handling of nullable fields and AMS mapping data
## [0.1.6b7] - 2026-01-12
### Added
- **AMS color mapping** - Manually select which AMS slot to use for each filament:
- ReprintModal: Click dropdown to override auto-matched slots
- AddToQueueModal: Collapsible filament mapping section with slot selection
- Mapping stored with queued prints and used when print starts
- Blue ring indicator shows manually selected slots vs auto-matched
- **AMS Color Mapping** - Manual AMS slot selection in ReprintModal, AddToQueueModal, EditQueueItemModal:
- Dropdown to override auto-matched AMS slots with any loaded filament
- Blue ring indicator distinguishes manual selections from auto-matches
- Status indicators: green (match), yellow (type only), orange (not found)
- Re-read button to refresh AMS status if spools were swapped
- Color names shown in dropdowns and tooltips (decoded from Bambu filament codes or derived from hex)
## [0.1.6b9] - 2026-01-09
### Added
- **Chamber light control** - Toggle the printer's chamber LED from the UI:
- Light button next to camera button at bottom of printer card
- Custom icon with on/off states (yellow when lit)
- Works on all Bambu Lab printers (X1, P1, A1 series, H2D)
- H2D dual chamber lights controlled together
- **Archive list view improvements** - Full feature parity with card view:
- Edit and delete buttons inline with each row
- Three-dot menu button for context menu access
- All context menu actions (re-print, compare, add to project, etc.)
- **Archive object count** - Shows number of printable objects on archive cards:
- Displays in stats grid (e.g., "3 objs")
- Extracted from 3MF metadata automatically
- **Cross-view archive highlight** - Click an archive in calendar or project view to highlight it:
- Switches to card/list view and scrolls to the archive
- Yellow border highlight for 5 seconds
- Works in card, list, and calendar views
- **Context menu visual indicator** - Three-dot button on cards and list items:
- Shows on hover (desktop) or always visible (mobile)
- Provides quick access to context menu actions
- Positioned on left side for easy access
- **Spoolman location clearing** - When spools are removed from AMS, their location field is now cleared in Spoolman:
- Previously, location persisted even after spool removal
- Now correctly clears "Printer Name - AMS X Slot Y" when spool is no longer present
- **FTP retry for unreliable WiFi** - Configurable retry logic for all FTP operations:
- Enable/disable retry in Settings > General > FTP Retry
- Configure retry count (1-10 attempts) and delay (1-30 seconds)
- Configurable connection timeout (10-120 seconds, default 30s)
- Applies to: 3MF archiving, print uploads, timelapse downloads, firmware updates
- Helps P1S, X1C, and other printers with weak WiFi connections
- **A1/A1 Mini FTP fix** - Resolved FTP upload failures on A1 series printers:
- A1 printers have issues with SSL on the FTP data channel
- Automatic detection skips data channel SSL for A1 and A1 Mini models
- Control channel remains encrypted via implicit FTPS (port 990)
- Fixes "read operation timed out" errors during file uploads
- **Bulk project assignment** - Assign multiple archives to a project at once:
- New "Project" button in multi-select toolbar (next to Tags)
- Select a project to assign all selected archives
- "Remove from project" option to clear assignments
- Updates Projects page and Project Detail page instantly
- Shared color utility with ~200 Bambu color mappings
- Fixed AMS mapping format to match Bambu Studio exactly
- **Print Options in Reprint Modal** - Bed leveling, flow calibration, vibration calibration, first layer inspection, timelapse toggles
- **Time Format Setting** - New date utilities applied to 12 components, fixes archive times showing in UTC
- **Statistics Dashboard Improvements** - Size-aware rendering for PrintCalendar, SuccessRateWidget, TimeAccuracyWidget, FilamentTypesWidget, FailureAnalysisWidget
- **Firmware Update Helper** - Check firmware versions against Bambu Lab servers for LAN-only printers with one-click upload
- **FTP Reliability** - Configurable retry (1-10 attempts, 1-30s delay), A1/A1 Mini SSL fix, configurable timeout
- **Bulk Project Assignment** - Assign multiple archives to a project at once from multi-select toolbar
- **Chamber Light Control** - Light toggle button on printer cards
- **Support Bundle Feature** - Debug logging toggle with ZIP generation for issue reporting
- **Archive Improvements** - List view with full parity, object count display, cross-view highlighting, context menu button
- **Maintenance Improvements** - wiki_url field for documentation links, model-specific Bambu Lab wiki URLs
- **Spoolman Integration** - Clear location when spools removed from AMS during sync
### Fixed
- **Time format setting not applied** - Fixed 24-hour time format not being respected across the UI:
- ETA display on printer cards now uses configured time format
- Archive cards and timelapse file lists respect the setting
- AMS history charts use the configured format
- Project timeline, queue page, notification logs, and system info all updated
- Settings > General > Time Format now works consistently everywhere
- **QR code endpoint** - Fixed 500 error on archive QR code generation:
- Added `qrcode[pil]` to requirements.txt
- Improved error handling for missing dependencies
- Fixed PIL Image resizing method
## [0.1.6b8] - 2026-01-08
### Added
- **Reprint modal print options** - Configure print settings when reprinting from archive:
- Bed leveling toggle (default: enabled)
- Flow calibration toggle (default: disabled)
- Vibration calibration toggle (default: enabled)
- First layer inspection toggle (default: disabled)
- Timelapse recording toggle (default: disabled)
- Collapsible "Print Options" section with toggle switches
- Settings sent to printer match Bambu Studio's format exactly
### Fixed
- **AMS mapping for multi-color reprints** - Fixed filament slot mapping for multi-color prints:
- AMS mapping now matches Bambu Studio's exact format with `ams_mapping2` detailed structure
- Correct handling of 3MF filament slot IDs (1-indexed to 0-indexed conversion)
- Unused filament slots properly filled with `-1` placeholder
- Prevents duplicate tray assignment when multiple filaments match the same type
- Resolves "stuck at filament change" issue on X1C and other multi-AMS printers
- **Print command format** - Updated MQTT print command to match Bambu Studio exactly:
- Added `auto_bed_leveling`, `cfg`, `extrude_cali_flag`, `extrude_cali_manual_mode`, `nozzle_offset_cali` fields
- Uses American spelling `bed_leveling` (not British `bed_levelling`)
- Proper `sequence_id` format matching official apps
## [0.1.6b7] - 2026-01-04
### Added
- **Support bundle for issue reporting** - Collect debug logs and system info for troubleshooting:
- Toggle debug logging from System Information page
- Debug logging indicator banner shows across all pages with live timer
- Download support bundle as ZIP file with sanitized logs and system info
- Privacy-focused: filters sensitive data (passwords, tokens, emails, IPs)
- Clear explanation of what data is/isn't collected
- **Firmware update helper** - Check and upload firmware updates for LAN-only printers:
- Automatic firmware update checking against Bambu Lab's servers
- Orange "Update" badge on printer cards when updates are available
- Click badge to open firmware update modal with version info and release notes
- One-click firmware upload to printer's SD card via FTP
- Real-time upload progress tracking with actual bytes transferred
- Step-by-step instructions for triggering update from printer screen
- Supports all Bambu Lab printer models (X1C, X1, X1E, P1S, P1P, P2S, A1, A1 Mini, H2D, H2C, H2S)
- Firmware files cached locally for faster re-uploads
- Browser freeze from CameraPage WebSocket
- Project card filament badges showing duplicates and raw color codes
- Print object label positioning in skip objects modal
- Printer hour counter not updated on backend restart
- Virtual printer excluded from discovery
- Print cover fetch in Docker environments
- Archive delete safety checks prevent deleting parent dirs
## [0.1.6b6] - 2026-01-04
### Added
- **Resizable printer cards** - Adjust printer card size from the Printers page toolbar:
- Four sizes: Small, Medium (default), Large, XL
- Plus/minus buttons in toolbar header
- Size preference saved to localStorage
- Responsive grid adapts to selected size
- **Queue Only mode** - Stage prints without automatic scheduling:
- New "Queue Only" option when adding prints to queue
- Staged prints show purple "Staged" badge
- Play button to manually release staged prints to the queue
- Edit queue items to switch between ASAP, Scheduled, and Queue Only modes
- Useful for preparing print batches before activating
- **Virtual printer model selection** - Choose which Bambu printer model to emulate:
- Dropdown in Settings > Virtual Printer to select model
- Supports X1 series (X1C, X1, X1E), P series (P1S, P1P, P2S), A1 series (A1, A1 Mini), and H2 series (H2D, H2C, H2S)
- Affects how slicers detect and interact with the virtual printer
- Model change automatically restarts the virtual printer (no manual disable/enable needed)
- Models sorted alphabetically in dropdown
- **Pending upload delete confirmation** - Confirmation modal when discarding pending uploads in queue review mode
- **Tasmota admin link** - Direct link to Tasmota web interface on smart plug cards:
- Auto-login using stored credentials (when available)
- Opens in new tab for quick access to plug settings
- **Debug logging** - Added debug logging for printer hour counter and AMS slot mapping
- **Demo video recorder** - Playwright-based tool for recording demo videos:
- Automated walkthrough of all pages
- Outputs WebM format, easy conversion to MP4
- Located in `demo-video/` directory
- **Resizable Printer Cards** - Four sizes (S/M/L/XL) with +/- buttons in toolbar
- **Queue Only Mode** - Stage prints without auto-start, release when ready with purple "Staged" badge
- **Virtual Printer Model Selection** - Choose which Bambu printer model to emulate
- **Tasmota Admin Link** - Quick access to smart plug web interface with auto-login
- **Pending Upload Delete Confirmation** - Confirmation modal when discarding pending uploads
### Fixed
- **Camera stream reconnection** - Improved detection of stuck camera streams with automatic reconnection:
- Periodic stall detection checks every 5 seconds
- Automatic reconnection when stream stops receiving frames
- New `/api/v1/printers/{id}/camera/status` endpoint for stream health monitoring
- **Spoolman sync** - Fixed sync issues with Spoolman integration:
- Now only matches Bambu Lab vendor filaments when syncing
- Prevents incorrect matching with third-party filaments by color alone
- Improved filament matching accuracy
- **Archive card context menu** - Fixed context menu positioning issues (#46)
- **Printer card cover image** - Fixed wrong cover image displayed for multi-plate print files
- **Skip objects modal** - Fixed object ID markers not correctly positioned over build plate preview:
- Now uses `bbox_all` from plate metadata for accurate coordinate mapping
- Markers correctly position relative to the actual object bounds in the preview image
- Works correctly for multi-plate projects
- **Active AMS slot display (H2D)** - Fixed incorrect slot display on H2D printers with multiple AMS units:
- Now parses `snow` field from `device.extruder.info` which contains actual AMS ID
- Previously picked first AMS on the extruder, causing wrong display when multiple AMS connected
- Example: Switching from B2 to C1 now correctly shows C1 instead of A1
- **Spoolman link function** - Improved "Link to Spoolman" in AMS slot detail modal
- **GCode viewer** - Minor improvements to GCode visualization
- **Cover image retrieval** - Improved reliability of cover image extraction
- **Virtual printer SSDP model codes** - Corrected model codes for slicer compatibility:
- C11=P1P, C12=P1S (were incorrectly swapped)
- N7=P2S (was incorrectly using C13 which is X1E)
- 3DPrinter-X1-Carbon for X1C (full model name format)
- **Virtual printer serial prefixes** - Fixed serial number prefixes to match real printers:
- Based on actual Bambu Lab serial number format (MMM??RYMDDUUUUU)
- X1C=00M, P1S=01P, P1P=01S, P2S=22E, A1=039, A1M=030, H2D=094, X1E=03W
- **Virtual printer startup model** - Fixed model not loading from database on container restart:
- Virtual printer now correctly restores the saved model on startup
- Previously always started with default X1C model regardless of saved setting
- **Virtual printer model change** - Fixed model changes not taking effect while running:
- Model changes now automatically restart the virtual printer
- Removed frontend restriction that required manual disable/enable
- **Docker certificate persistence** - Fixed virtual printer certificate storage:
- Removed unused `bambuddy_vprinter` volume (was mounting to wrong path)
- Certificates now correctly persist in `bambuddy_data` volume
- Added optional bind mount for sharing certs between Docker and native installations
### Changed
- **Virtual printer setup documentation** - Improved setup instructions:
- Prominent "Setup Required" warning in UI linking to documentation
- Certificate must **replace** the last cert in slicer's printer.cer file (not append!)
- Clear guidance: one CA only per slicer, replace when switching hosts
- Platform-specific instructions for Linux, Docker, macOS, Windows, Unraid, Synology, TrueNAS, Proxmox
- Added troubleshooting for TLS connection errors
### Tests
- Added integration tests for print queue API endpoints (16 new tests)
- Tests cover queue CRUD, manual_start flag, and start/cancel endpoints
- Added unit tests for virtual printer model configuration (3 new tests)
- Updated VirtualPrinterSettings tests for new UI layout and model codes
- Fixed virtual printer tests with outdated model codes (BL-P001 → 3DPrinter-X1-Carbon)
- Camera stream reconnection with automatic recovery from stalled streams
- Active AMS slot display for H2D printers with multiple AMS units
- Spoolman sync matching only Bambu Lab vendor filaments
- Skip objects modal object ID markers positioning
- Virtual printer model codes, serial prefixes, startup model, certificate persistence
- Archive card context menu positioning
## [0.1.6b5] - 2026-01-02
### Added
- **Pre-built Docker images** - Ready-to-use container images on GitHub Container Registry:
- Pull directly: `docker pull ghcr.io/maziggy/bambuddy:latest`
- Multi-architecture support: `linux/amd64` and `linux/arm64` (Raspberry Pi 4/5)
- No build required - just `docker compose up -d`
- Automatic architecture detection - Docker pulls the right image for your system
- **Spoolman Link Spool feature** - Manually link existing Spoolman spools to AMS trays:
- Hover over any AMS slot to see "Link to Spoolman" button (when Spoolman enabled)
- Select from list of unlinked Spoolman spools
- Automatically sets the `extra.tag` field in Spoolman for proper sync
- Useful for connecting existing Spoolman inventory to physical spools
- **Spool UUID display** - View and copy Bambu Lab spool UUID in AMS hover card:
- Shows first 8 chars of UUID with copy button
- Full UUID copied to clipboard (with fallback for HTTP contexts)
- Only visible when Spoolman integration is enabled
- **Spoolman sync feedback** - Improved sync result display:
- Shows count of successfully synced spools
- Lists skipped spools with reasons (e.g., "Non-Bambu Lab spool")
- Expandable list when more than 5 spools skipped
- Color swatches for easy identification
- **Printer control buttons** - Stop and Pause/Resume buttons on printer cards when printing:
- Stop button cancels the current print job
- Pause/Resume toggle for pausing and resuming prints
- Confirmation modals for all actions to prevent accidental clicks
- Toast notifications for action feedback
- **Skip objects** - Skip individual objects during a print:
- Skip button in print status section (top right) when printing with 2+ objects
- Modal shows preview image with object ID markers overlaid
- Large ID badges to easily match with printer display
- Click to skip any object - it will not be printed
- Skipped objects shown with strikethrough styling and red badge on button
- Skip only available after layer 1 (printer limitation) with warning message
- Objects automatically loaded when print starts from 3MF metadata
- Parses skipped objects from printer MQTT for state persistence
- Light and dark theme support
- Close with ESC key or click outside
- Requires "Exclude Objects" option enabled in slicer
- **Interactive API Browser** - Explore and test all API endpoints directly in Bambuddy:
- Settings → API Keys now includes a full API browser
- Fetches OpenAPI schema automatically
- Endpoints grouped by category (printers, archives, settings, etc.)
- Expandable sections with color-coded method badges (GET, POST, PATCH, DELETE)
- Parameter inputs for path, query, and JSON body
- Auto-populates request body with schema examples
- Live API execution with response display (status, timing, formatted JSON)
- Paste API key to test authenticated endpoints
- Search to filter endpoints across all categories
- Two-column layout: API key management + API browser side-by-side
- **AMS slot RFID re-read** - Re-read RFID data for individual AMS slots:
- Menu button (⋮) appears on hover over AMS slots
- "Re-read RFID" option triggers filament info refresh
- Loading indicator shows while re-read is in progress
- Automatically tracks printer status to clear indicator when complete
- Menu hidden when printer is busy (printing)
- **Print quantity tracking** - Track number of items per print job for project progress:
- Set "Items Printed" quantity when editing archived prints
- Project stats now show total items vs print jobs
- Progress bar tracks items toward target count
- Useful for batch printing (e.g., 10 copies in one print = 10 items)
- Default quantity of 1 for backwards compatibility
- **Fan status display** - Real-time fan speeds in new Controls section:
- Part cooling fan, Auxiliary fan, Chamber fan status
- Distinct icons for each fan type (Fan, Wind, AirVent)
- Dynamic coloring: active fans show colored, off fans show gray
- Percentage display (0-100%)
- Real-time updates via WebSocket
- Always visible on printer cards in expanded view
### Changed
- **Printer card layout** - Reorganized expanded view with new Controls section:
- Temperature display is now standalone (no longer shares row with buttons)
- New Controls section contains fan status (left) and print buttons (right)
- Removed divider lines before Controls and Filaments sections for cleaner look
- **Cover image availability** - Print cover image now shown in PAUSE/PAUSED states (not just RUNNING) for skip objects modal
- **Spoolman info banner** - Updated settings UI with clearer sync documentation
- **Pre-built Docker Images** - Pull directly from GitHub Container Registry (ghcr.io)
- **Printer Controls** - Stop and Pause/Resume buttons on printer cards with confirmation modals
- **Skip Objects** - Skip individual objects during print without canceling entire job
- **Spoolman Improvements** - Link Spool, UUID Display, Sync Feedback
- **AMS Slot RFID Re-read** - Re-read filament info via hover menu
- **Print Quantity Tracking** - Track items per print for project progress
### Fixed
- **Spoolman spool creation** - Fixed 400 Bad Request error when creating new spools:
- Spoolman `extra` field values must be valid JSON
- Now properly JSON-encodes the `tag` value
- Affects both auto-sync and manual link operations
### Tests
- Added integration tests for printer control endpoints (stop, pause, resume)
- Added integration tests for AMS slot refresh endpoint
- Added integration tests for skip objects endpoints (get objects, skip objects, objects with positions)
- Spoolman 400 Bad Request when creating spools
- Update module for Docker based installations
## [0.1.6b4] - 2026-01-01
### Added
- **Docker update detection** - Automatically detects Docker installations and shows appropriate update instructions instead of failing with git errors
- **Camera popup window improvements**
- Auto-resize to fit video resolution on first open
- Persist window size and position to localStorage
- Restore saved window state for subsequent opens
- **Slicer protocol handler** - OS detection for correct protocol (Windows: `bambustudio://`, macOS/Linux: `bambustudioopen://`)
### Changed
- Refactored AMS section for better visual grouping and spacing
### Fixed
- **Maintenance duration display** - Show weeks instead of imprecise months for better countdown precision
- **Print hours display** - Convert large hour values to readable units (e.g., 478h → 3w, 100h → 4d)
- **Printer hour counter** - Fixed runtime_seconds not incrementing during prints (first timestamp was set but never committed)
### Changed
- **Printer cards** - Refactored AMS section for better visual grouping and spacing
- Printer hour counter not incrementing during prints
- Slicer protocol OS detection (Windows: bambustudio://, macOS/Linux: bambustudioopen://)
- Camera popup window auto-resize and position persistence
- Maintenance page duration display with better precision
- Docker update detection for in-app updates
## [0.1.6b3] - 2025-12-31
### Added
- **Customizable Theme System** - Comprehensive theme customization with independent settings for dark and light modes:
- **Style**: Classic (clean shadows), Glow (accent-colored glow effects), Vibrant (dramatic deep shadows)
- **Background**: Neutral, Warm, Cool (light mode) + OLED, Slate, Forest (dark mode only)
- **Accent Colors**: Green, Teal, Blue, Orange, Purple, Red
- All combinations work together (e.g., Glow style + Forest background + Teal accent)
- Settings sync across devices via database
- Live preview in Settings → Appearance
- Confirmation modal for quick power switch in sidebar
### Fixed
- **Printer hour counter** - Fixed bug in printer's hour counter display
### Changed
- **Sidebar power switch** - Added confirmation modal to sidebar's quick power switch
- Printer hour counter inconsistency between card and maintenance page
- Improved printer hour tracking accuracy with real-time runtime counter
- Add Smart Plug modal scrolling on lower resolution screens
- Excluded virtual printer from discovery results
- Bottom sidebar layout
## [0.1.6b2] - 2025-12-29
### Added
- **Virtual Printer** - Bambuddy now emulates a Bambu Lab printer on your network! Send prints directly from Bambu Studio or Orca Slicer without needing a physical printer connection. Features include:
- SSDP discovery (printer appears automatically in slicer)
- Secure TLS/MQTT communication with auto-generated certificates
- Queue mode (prints go to pending uploads) or auto-start mode
- Configurable access code for authentication
- Works with Docker (requires `network_mode: host`)
- Persistent certificates across container rebuilds via volume mount
- **Virtual Printer** - Emulates a Bambu Lab printer on your network:
- Auto-discovery via SSDP protocol
- Send prints directly from Bambu Studio/Orca Slicer
- Queue mode or Auto-start mode
- TLS 1.3 encrypted MQTT + FTPS with auto-generated certificates
- Persistent archive page filters
### Fixed
- **Backup/restore for virtual printer settings** - Virtual printer settings (enabled, access code, mode) now correctly persist after restore without being overwritten by auto-save
- AMS filament matching in reprint modal
- Archive card cache bug with wrong cover image
- Queueing module re-queue modal
## [0.1.6b] - 2025-12-28
### Added
- **Tasmota device discovery** - Automatically discover Tasmota smart plugs on your network. Click "Discover Tasmota Devices" in the Add Smart Plug modal to scan your local subnet. Supports devices with and without authentication.
- **Switchbar for quick smart plug access** - New sidebar widget for controlling smart plugs without leaving the current page. Enable "Show in Switchbar" for any plug to add it to the quick access panel. Shows real-time status, power consumption, and on/off controls.
- **Timelapse editor** - Edit timelapse videos with trim, speed adjustment (0.25x-4x), and music overlay. Uses FFmpeg for server-side processing with browser-based preview.
- **AMS filament preview** - Reprint modal shows filament comparison between what the print requires and what's currently loaded in the AMS. Compares both type and color with visual indicators (green=match, yellow=color mismatch, orange=type mismatch). Includes Re-read button to refresh AMS status from printer, fuzzy color matching for similar colors, and shows AMS slot location for each matched filament.
- **File type badge** - Archive cards now show GCODE (green) or SOURCE (orange) badge to indicate whether the file is a sliced print-ready file or source-only.
- **Docker printer discovery** - Subnet scanning for discovering printers when running in Docker with `network_mode: host`. Automatically detects Docker environment and shows subnet input field in Add Printer dialog.
- **Printer model mapping** - Discovery now shows friendly model names (X1C, H2D, P1S) instead of raw SSDP codes (BL-P001, O1D, C11).
- **Discovery API tests** - Comprehensive test coverage for discovery endpoints.
- **Project filament colors** - Project cards now display filament color swatches from assigned archives.
- **BOM filter** - Hide completed BOM items with "Hide done" toggle on project detail page.
- **Projects in backup/restore** - Projects, BOM items, and attachments now included in database backup/restore.
- **Attachment file validation** - File type validation for project attachments (images, documents, 3D files, archives, scripts, configs).
### Changed
- **Timelapse viewer** - Default playback speed changed from 2x to 1x.
- **GitHub issue template** - Added mandatory printer firmware version field and LAN-only mode checkbox for better bug reports.
- **Docker compose** - Clearer comments explaining `network_mode: host` requirement for printer discovery and camera streaming.
- **Project card design** - Enhanced visual polish with gradients, shadows, and glow effects on hover.
- **Project page layout** - Improved spacing and padding on project list and detail pages.
- **Delete confirmations** - Replaced browser confirm dialogs with styled confirmation modals.
- **Smart Plugs** - Tasmota device discovery and Switchbar quick access widget
- **Timelapse Editor** - Trim, speed adjustment (0.25x-4x), and music overlay
- **Printer Discovery** - Docker subnet scanning, printer model mapping, detailed status stages
- **Archives & Projects** - AMS filament preview, file type badges, project filament colors, BOM filter
- **Maintenance** - Custom maintenance types with manual per-printer assignment
- Delete printer options to keep or delete archives
### Fixed
- **Notification module** - Fixed bug where notifications were sent even when printer was offline.
- **Attachment uploads** - Fixed file attachments not persisting due to SQLAlchemy JSON column mutation detection.
- **Camera stream stability** - Fixed stream stopping after a few minutes by increasing ffmpeg read timeout (10s→30s), adding buffer options, and implementing auto-reconnection with exponential backoff in the frontend.
- Notifications sent when printer offline
- Camera stream stopping with auto-reconnection
- A1/P1 camera streaming with extended timeouts
- Attachment uploads not persisting
- Total print hours calculation
## [0.1.5] - 2025-12-19
### Fixed
- **Browser freeze on print completion** - Fixed freeze when camera stream was open during print completion by using buffered camera frames instead of spawning duplicate ffmpeg processes
- **Printer status "timelapse" effect** - Fixed issue where navigating to printer page after print showed metrics animating slowly from mid-print values to final state; printer_status messages now bypass the throttled queue
- **Timelapse auto-download** - Complete rewrite with retry mechanism and multiple path support
- **Timelapse detection for H2D** - H2D sends timelapse status in ipcam.timelapse field, not xcam.timelapse
- **Reprint from archive** - Fixed bug where print button sent slicer source file instead of sliced gcode
- **Import shadowing bugs** - Fixed ArchiveService import shadowing causing "cannot access local variable" error
- **Timelapse race condition** - xcam data was parsed before print state was set
### Added
- **Failure reason detection** - Auto-detects failure reasons from HMS errors:
- Filament runout (Module 0x07)
- Layer shift (Module 0x0C)
- Clogged nozzle (Module 0x05)
- **Hide failed prints filter** - Toggle to hide failed/aborted prints with localStorage persistence
- **Docker test suite** - Comprehensive tests for build, backend, frontend, and integration
- **Pre-commit hooks** - Ruff linter and formatter for code quality
- **Code quality tests** - Static analysis to catch import shadowing bugs automatically
### Changed
- **Timelapse viewer** - Default playback speed changed from 0.5x to 2x
- **Archive badges** - Shows "cancelled" for aborted prints, "failed" for failed prints
- **WebSocket optimization** - Removed large raw_data field from print_complete message; reduced throttle to 100ms for smoother updates
### Docker
- Added ffmpeg to Docker image
- Fixed build warnings (debconf, pip root user)
- Added comprehensive Docker documentation to README
- Added `--pull` flag to ensure fresh base images
## [0.1.5b6] - 2025-12-12
Notifications:
- Separate AMS and AMS-HT notification switches (one per device type)
- Fix notification variables not showing (duration, filament, estimated_time)
- Add fallback values for empty notification variables ("Unknown" instead of blank)
Settings:
- Fix API keys badge count only showing after visiting tab
- Move External Links card to third column above Updates
- Add Release Notes modal for viewing full notes before updating
Statistics:
- Fix filament usage trends not showing (wrong API parameters)
- Move dashboard controls (Hidden, Reset Layout) to header row
Camera:
- Fix ffmpeg processes not killed when closing webcam window
- Add /camera/stop endpoint with POST support for sendBeacon
- Track active streams and proper cleanup on disconnect
Documentation:
- Update README with missing features (camera streaming, AMS/AMS-HT monitoring,
chamber control, printer control, AI detection, calibration, energy tracking,
database backup/restore, system info dashboard)
## [0.1.5b5] - 2025-12-11
### Added
- Anonymous telemetry system with opt-out support
- System info page with database and resource statistics
## [0.1.5b4] - 2025-12-11
New Features
Mobile PWA Support - Progressive Web App support for mobile devices
AMS Humidity/Temperature History - Clickable indicators open charts with 6h/24h/48h/7d history, min/max/avg statistics, and threshold reference lines
Webhooks & API Keys - API key authentication with granular permissions for external integrations
System Info Page - New page showing system information
Multi-plate Cover Image - Archive cards now show cover image of the printed plate for multi-plate files
Quick Notification Disable - Button to quickly disable notifications
Projects / Print Grouping - Group related prints into projects with progress tracking
Full-Text Search (FTS5) - Efficient search across print names, filenames, tags, notes, designer, and filament type
Failure Analysis - Dashboard widget showing failure rate with correlations and trends
Archive Comparison - Compare 2-5 archives side-by-side with highlighted differences
CSV/Excel Export - Export archives and statistics with current filters
Improvements
Improved archive card context menu with submenu support
Improved notification scheduler and templates
Improved auto power off scheduler
Improved email notification provider
Configurable AMS data retention (default 30 days)
Bug Fixes
Fixed bug where not all AMS spools were synced to Spoolman
Fixed bug where external links were not respected by hotkeys
Fixed context menu submenu not showing
Fixed project card thumbnails using correct API endpoint
Fixed archive PATCH 500 error (FTS5 index rebuild)
Fixed clipboard API fallback for HTTP contexts
Infrastructure
Added comprehensive automated testing (pytest, vitest, playwright)
GitHub Actions CI/CD workflow for automated testing
Removed PWA push notifications
## [0.1.5b4] - 2025-12-10
### Added
- Docker support with containerized deployment
- Comprehensive mobile support with responsive navigation
- Hamburger drawer navigation for mobile (< 768px)
- Touch gesture context menus with long press support
- WCAG-compliant touch targets (44px minimum)
- Safe area insets support for notched devices
- External links can be embedded into sidebar navigation
- External links included in backup/restore module
- Filament spool fill levels on printer cards
- Issue and pull request templates
### Changed
- Improved external link module with better icon layout
- Documentation moved to separate repository
### Fixed
- Notification module now properly saves newly added notification types
- External link icons layout improvements
## [0.1.5b3] - 2025-12-09
### Added
- Comprehensive backup/restore module improvements
### Fixed
- Switched off printers no longer incorrectly show as active
- os.path issue in update module
## [0.1.5b2] - 2025-12-09
### Added
- User options to backup module
### Changed
- App renamed to "Bambuddy"
### Fixed
- HTTP 500 error in backup module
## [0.1.5b] - 2025-12-08
### Added
- Smart plug monitoring and scheduling
- Daily digest notifications
- Notification template system
- Maintenance interval type: calendar days
- Cloud Profiles template visibility and preset diff view
- AMS humidity/temperature indicators with configurable thresholds
- Printer image on printer card
- WiFi signal strength indicator on printer card
- Power switch dropdown for offline printers
- MQTT debug viewer with filter and search
- Total printer hours display on printer card
- AMS discovery module
- Dual-nozzle AMS wiring visualization
- **Docker Support** - One-command deployment with docker compose
- **Mobile PWA** - Full mobile support with responsive navigation and touch gestures
- **Projects** - Group related prints with progress tracking
- **Archive Comparison** - Compare 2-5 archives side-by-side
- **Smart Plug Automation** - Tasmota integration with auto power-on/off
- **Telemetry Dashboard** - Anonymous usage statistics (opt-out available)
- **Full-Text Search** - Efficient search across print names, filenames, tags, notes, designer, filament type
- **Failure Analysis** - Dashboard widget showing failure rate with correlations and trends
- **CSV/Excel Export** - Export archives and statistics with current filters
- **AMS Humidity/Temperature History** - Clickable indicators with charts and statistics
- **Daily Digest Notifications** - Consolidated daily summary
- **Notification Template System** - Customizable message templates
- **Webhooks & API Keys** - API key authentication with granular permissions
- **System Info Page** - Database and resource statistics
- **Comprehensive Backup/Restore** - Including user options and external links
### Changed
- Redesigned AMS section with BambuStudio-style device icons
- Tabbed design and auto-save for settings page
- Replaced camera settings with WiFi signal in top bar
- Completely refactored K-profile module
- Refactored maintenance settings
- Improved archive card context menu with submenu support
- WebSocket throttle reduced to 100ms for smoother updates
### Fixed
- HMS module bug
- Camera buttons appearance in light theme
- Browser freeze on print completion when camera stream was open
- Printer status "timelapse" effect after print completion
- Complete rewrite of timelapse auto-download with retry mechanism
- Reprint from archive sending slicer source file instead of sliced gcode
- Import shadowing bugs causing "cannot access local variable" error
- Archive PATCH 500 error
- ffmpeg processes not killed when closing webcam window
### Removed
- Control page (removed all related code)
## [0.1.4] - 2025-12-01
### Added
- Multi-language support
- Auto app update functionality
- Maintenance module with notifications
- Spoolman support for adding unknown Bambu Lab spools
- Source 3MF file upload to archive cards
### Fixed
- K profiles retrieval from printer
## [0.1.3] - 2025-11-30
### Added
- Push notification support (WhatsApp, ntfy, Pushover, Telegram, Email)
- K profile management
- Configurable logging with log levels
- Sidebar item reordering
- Default view settings
- Option to track energy per print or in total
- Timelapse viewer improvements
### Fixed
- WebSocket connection stability
- Power stage updates not reflecting in frontend
## [0.1.2-bugfix] - 2025-11-30
### Fixed
- WebSocket disconnection issues
## [0.1.2-final] - 2025-11-29
### Added
- Print scheduling and queueing system
- Power consumption cost calculation
- HMS (Health Management System) error handling on printer cards
- Camera snapshot on print completion
- Power switch and automation controls on printer card
- Timelapse video player with speed controls
- Print time accuracy calculation
- Duplicate detection and filtering
### Changed
- Unified printer card layout
### Fixed
- Auto poweroff feature improvements
- Archive file handling on print start
- Statistics display issues
## [0.1.2] - 2025-11-28
### Added
- HMS health status monitoring
- MQTT debug log window
- Tasmota smart power plug support with automation
- Project page viewer and editor
- Button to show/hide disconnected printers
- Favicons
### Fixed
- Collapsed sidebar layout
## [0.1.1] - 2025-11-28
### Added
- Initial public release
- Multi-printer support via MQTT
- Real-time printer status monitoring
- Print archives with history tracking
- Statistics and analytics dashboard
- Timelapse video support
- Light and dark theme support
---
For more information, visit the [Bambuddy GitHub repository](https://github.com/maziggy/bambuddy).
- Control page
- PWA push notifications (replaced with standard notification providers)
+107
View File
@@ -65,6 +65,7 @@ def archive_to_response(
"thumbnail_path": archive.thumbnail_path,
"timelapse_path": archive.timelapse_path,
"source_3mf_path": archive.source_3mf_path,
"f3d_path": archive.f3d_path,
"duplicates": duplicates,
"duplicate_count": duplicate_count if duplicates is None else len(duplicates),
"print_name": archive.print_name,
@@ -2477,3 +2478,109 @@ async def delete_source_3mf(
await db.commit()
return {"status": "deleted"}
# =============================================================================
# F3D API (Fusion 360 Design Files)
# =============================================================================
@router.post("/{archive_id}/f3d")
async def upload_f3d(
archive_id: int,
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db),
):
"""Upload a Fusion 360 design file for an archive."""
result = await db.execute(select(PrintArchive).where(PrintArchive.id == archive_id))
archive = result.scalar_one_or_none()
if not archive:
raise HTTPException(404, "Archive not found")
if not file.filename or not file.filename.endswith(".f3d"):
raise HTTPException(400, "File must be a .f3d file")
# Get archive directory and create f3d subdirectory
file_path = settings.base_dir / archive.file_path
archive_dir = file_path.parent
f3d_dir = archive_dir / "f3d"
f3d_dir.mkdir(exist_ok=True)
# Delete old F3D file if exists
if archive.f3d_path:
old_f3d_path = settings.base_dir / archive.f3d_path
if old_f3d_path.exists():
old_f3d_path.unlink()
# Save the F3D file - preserve original filename
f3d_filename = file.filename
f3d_path = f3d_dir / f3d_filename
content = await file.read()
f3d_path.write_bytes(content)
# Update archive with F3D path (relative to base_dir)
archive.f3d_path = str(f3d_path.relative_to(settings.base_dir))
await db.commit()
await db.refresh(archive)
return {
"status": "uploaded",
"f3d_path": archive.f3d_path,
"filename": f3d_filename,
}
@router.get("/{archive_id}/f3d")
async def download_f3d(
archive_id: int,
db: AsyncSession = Depends(get_db),
):
"""Download the Fusion 360 design file."""
result = await db.execute(select(PrintArchive).where(PrintArchive.id == archive_id))
archive = result.scalar_one_or_none()
if not archive:
raise HTTPException(404, "Archive not found")
if not archive.f3d_path:
raise HTTPException(404, "No F3D file attached to this archive")
f3d_path = settings.base_dir / archive.f3d_path
if not f3d_path.exists():
raise HTTPException(404, "F3D file not found on disk")
# Use the actual filename from the path
filename = f3d_path.name
return FileResponse(
path=f3d_path,
filename=filename,
media_type="application/octet-stream",
)
@router.delete("/{archive_id}/f3d")
async def delete_f3d(
archive_id: int,
db: AsyncSession = Depends(get_db),
):
"""Delete the Fusion 360 design file from an archive."""
result = await db.execute(select(PrintArchive).where(PrintArchive.id == archive_id))
archive = result.scalar_one_or_none()
if not archive:
raise HTTPException(404, "Archive not found")
if not archive.f3d_path:
raise HTTPException(404, "No F3D file attached to this archive")
# Delete the file
f3d_path = settings.base_dir / archive.f3d_path
if f3d_path.exists():
f3d_path.unlink()
# Clear the path in database
archive.f3d_path = None
await db.commit()
return {"status": "deleted"}
+7
View File
@@ -622,6 +622,12 @@ async def export_backup(
archive_data["source_3mf_path"] = a.source_3mf_path
backup_files.append((a.source_3mf_path, source_path))
if a.f3d_path:
f3d_path = base_dir / a.f3d_path
if f3d_path.exists():
archive_data["f3d_path"] = a.f3d_path
backup_files.append((a.f3d_path, f3d_path))
# Include photos
if a.photos:
for photo in a.photos:
@@ -1366,6 +1372,7 @@ async def import_backup(
thumbnail_path=archive_data.get("thumbnail_path"),
timelapse_path=archive_data.get("timelapse_path"),
source_3mf_path=archive_data.get("source_3mf_path"),
f3d_path=archive_data.get("f3d_path"),
print_name=archive_data.get("print_name"),
print_time_seconds=archive_data.get("print_time_seconds"),
filament_used_grams=archive_data.get("filament_used_grams"),
+7
View File
@@ -99,6 +99,13 @@ async def run_migrations(conn):
# Column already exists
pass
# Migration: Add f3d_path column to print_archives for Fusion 360 design files
try:
await conn.execute(text("ALTER TABLE print_archives ADD COLUMN f3d_path VARCHAR(500)"))
except Exception:
# Column already exists
pass
# Migration: Add on_maintenance_due column to notification_providers
try:
await conn.execute(text("ALTER TABLE notification_providers ADD COLUMN on_maintenance_due BOOLEAN DEFAULT 0"))
+1
View File
@@ -21,6 +21,7 @@ class PrintArchive(Base):
thumbnail_path: Mapped[str | None] = mapped_column(String(500))
timelapse_path: Mapped[str | None] = mapped_column(String(500))
source_3mf_path: Mapped[str | None] = mapped_column(String(500)) # Original project 3MF from slicer
f3d_path: Mapped[str | None] = mapped_column(String(500)) # Fusion 360 design file
# Print details from 3MF / printer
print_name: Mapped[str | None] = mapped_column(String(255))
+1
View File
@@ -40,6 +40,7 @@ class ArchiveResponse(BaseModel):
thumbnail_path: str | None
timelapse_path: str | None
source_3mf_path: str | None = None # Original project 3MF from slicer
f3d_path: str | None = None # Fusion 360 design file
# Duplicate detection
duplicates: list[ArchiveDuplicate] | None = None
@@ -268,3 +268,115 @@ class TestArchiveDataIntegrity:
result = response.json()
assert result["notes"] == "Updated notes"
assert result["is_favorite"] is True
class TestArchiveF3DEndpoints:
"""Tests for F3D (Fusion 360 design file) attachment endpoints."""
@pytest.mark.asyncio
@pytest.mark.integration
async def test_archive_response_includes_f3d_path(
self, async_client: AsyncClient, archive_factory, printer_factory, db_session
):
"""Verify f3d_path is included in archive response."""
printer = await printer_factory()
archive = await archive_factory(printer.id, f3d_path="archives/test/design.f3d")
response = await async_client.get(f"/api/v1/archives/{archive.id}")
assert response.status_code == 200
result = response.json()
assert "f3d_path" in result
assert result["f3d_path"] == "archives/test/design.f3d"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_archive_response_f3d_path_null_when_not_set(
self, async_client: AsyncClient, archive_factory, printer_factory, db_session
):
"""Verify f3d_path is null when no F3D file attached."""
printer = await printer_factory()
archive = await archive_factory(printer.id)
response = await async_client.get(f"/api/v1/archives/{archive.id}")
assert response.status_code == 200
result = response.json()
assert "f3d_path" in result
assert result["f3d_path"] is None
@pytest.mark.asyncio
@pytest.mark.integration
async def test_upload_f3d_to_nonexistent_archive(self, async_client: AsyncClient):
"""Verify 404 when uploading F3D to non-existent archive."""
# Create a minimal file-like upload
files = {"file": ("design.f3d", b"fake f3d content", "application/octet-stream")}
response = await async_client.post("/api/v1/archives/9999/f3d", files=files)
assert response.status_code == 404
@pytest.mark.asyncio
@pytest.mark.integration
async def test_download_f3d_not_found_when_no_file(
self, async_client: AsyncClient, archive_factory, printer_factory, db_session
):
"""Verify 404 when downloading F3D from archive without F3D file."""
printer = await printer_factory()
archive = await archive_factory(printer.id)
response = await async_client.get(f"/api/v1/archives/{archive.id}/f3d")
assert response.status_code == 404
@pytest.mark.asyncio
@pytest.mark.integration
async def test_download_f3d_nonexistent_archive(self, async_client: AsyncClient):
"""Verify 404 when downloading F3D from non-existent archive."""
response = await async_client.get("/api/v1/archives/9999/f3d")
assert response.status_code == 404
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_f3d_nonexistent_archive(self, async_client: AsyncClient):
"""Verify 404 when deleting F3D from non-existent archive."""
response = await async_client.delete("/api/v1/archives/9999/f3d")
assert response.status_code == 404
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_f3d_when_no_file(
self, async_client: AsyncClient, archive_factory, printer_factory, db_session
):
"""Verify 404 when deleting F3D from archive without F3D file."""
printer = await printer_factory()
archive = await archive_factory(printer.id)
response = await async_client.delete(f"/api/v1/archives/{archive.id}/f3d")
assert response.status_code == 404
@pytest.mark.asyncio
@pytest.mark.integration
async def test_list_archives_includes_f3d_path(
self, async_client: AsyncClient, archive_factory, printer_factory, db_session
):
"""Verify f3d_path is included in archive list responses."""
printer = await printer_factory()
await archive_factory(printer.id, print_name="With F3D", f3d_path="archives/test/design.f3d")
await archive_factory(printer.id, print_name="Without F3D")
response = await async_client.get("/api/v1/archives/")
assert response.status_code == 200
data = response.json()
assert len(data) >= 2
with_f3d = next((a for a in data if a["print_name"] == "With F3D"), None)
without_f3d = next((a for a in data if a["print_name"] == "Without F3D"), None)
assert with_f3d is not None
assert with_f3d["f3d_path"] == "archives/test/design.f3d"
assert without_f3d is not None
assert without_f3d["f3d_path"] is None
+21
View File
@@ -202,6 +202,7 @@ export interface Archive {
thumbnail_path: string | null;
timelapse_path: string | null;
source_3mf_path: string | null;
f3d_path: string | null;
duplicates: ArchiveDuplicate[] | null;
duplicate_count: number;
object_count: number | null;
@@ -1682,6 +1683,26 @@ export const api = {
request<{ status: string }>(`/archives/${archiveId}/source`, {
method: 'DELETE',
}),
// F3D (Fusion 360 design file)
getF3dDownloadUrl: (archiveId: number) =>
`${API_BASE}/archives/${archiveId}/f3d`,
uploadF3d: async (archiveId: number, file: File): Promise<{ status: string; filename: string }> => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${API_BASE}/archives/${archiveId}/f3d`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.detail || `HTTP ${response.status}`);
}
return response.json();
},
deleteF3d: (archiveId: number) =>
request<{ status: string }>(`/archives/${archiveId}/f3d`, {
method: 'DELETE',
}),
// QR Code
getArchiveQRCodeUrl: (archiveId: number, size = 200) =>
+162
View File
@@ -120,8 +120,10 @@ function ArchiveCard({
const [showProjectPage, setShowProjectPage] = useState(false);
const [showSchedule, setShowSchedule] = useState(false);
const [showDeleteSource3mfConfirm, setShowDeleteSource3mfConfirm] = useState(false);
const [showDeleteF3dConfirm, setShowDeleteF3dConfirm] = useState(false);
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
const source3mfInputRef = useRef<HTMLInputElement>(null);
const f3dInputRef = useRef<HTMLInputElement>(null);
const source3mfUploadMutation = useMutation({
mutationFn: (file: File) => api.uploadSource3mf(archive.id, file),
@@ -145,6 +147,28 @@ function ArchiveCard({
},
});
const f3dUploadMutation = useMutation({
mutationFn: (file: File) => api.uploadF3d(archive.id, file),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['archives'] });
showToast(`F3D attached: ${data.filename}`);
},
onError: (error: Error) => {
showToast(error.message || 'Failed to upload F3D', 'error');
},
});
const f3dDeleteMutation = useMutation({
mutationFn: () => api.deleteF3d(archive.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['archives'] });
showToast('F3D removed');
},
onError: (error: Error) => {
showToast(error.message || 'Failed to remove F3D', 'error');
},
});
const timelapseScanMutation = useMutation({
mutationFn: () => api.scanArchiveTimelapse(archive.id),
onSuccess: (data) => {
@@ -301,6 +325,27 @@ function ArchiveCard({
onClick: () => setShowDeleteSource3mfConfirm(true),
danger: true,
}] : []),
{
label: archive.f3d_path ? 'Replace F3D' : 'Upload F3D',
icon: <Box className="w-4 h-4" />,
onClick: () => f3dInputRef.current?.click(),
},
...(archive.f3d_path ? [{
label: 'Download F3D',
icon: <Download className="w-4 h-4" />,
onClick: () => {
const link = document.createElement('a');
link.href = api.getF3dDownloadUrl(archive.id);
link.download = `${archive.print_name || archive.filename}.f3d`;
link.click();
},
},
{
label: 'Remove F3D',
icon: <Trash2 className="w-4 h-4" />,
onClick: () => setShowDeleteF3dConfirm(true),
danger: true,
}] : []),
{ label: '', divider: true, onClick: () => {} },
{
label: 'Download',
@@ -511,6 +556,20 @@ function ArchiveCard({
<FileCode className="w-4 h-4 text-orange-400" />
</button>
)}
{/* F3D badge */}
{archive.f3d_path && (
<button
className={`absolute bottom-2 ${archive.source_3mf_path ? 'left-12' : 'left-2'} p-1.5 rounded bg-black/60 hover:bg-black/80 transition-colors`}
onClick={(e) => {
e.stopPropagation();
// Download F3D file
window.location.href = api.getF3dDownloadUrl(archive.id);
}}
title="Download Fusion 360 design file"
>
<Box className="w-4 h-4 text-cyan-400" />
</button>
)}
{/* Timelapse badge */}
{archive.timelapse_path && (
<button
@@ -833,6 +892,21 @@ function ArchiveCard({
/>
)}
{/* Delete F3D Confirmation */}
{showDeleteF3dConfirm && (
<ConfirmModal
title="Remove F3D"
message={`Are you sure you want to remove the Fusion 360 design file from "${archive.print_name || archive.filename}"?`}
confirmText="Remove"
variant="danger"
onConfirm={() => {
f3dDeleteMutation.mutate();
setShowDeleteF3dConfirm(false);
}}
onCancel={() => setShowDeleteF3dConfirm(false)}
/>
)}
{/* Context Menu */}
{contextMenu && (
<ContextMenu
@@ -973,6 +1047,20 @@ function ArchiveCard({
e.target.value = '';
}}
/>
{/* Hidden file input for F3D upload */}
<input
ref={f3dInputRef}
type="file"
accept=".f3d"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
f3dUploadMutation.mutate(file);
}
e.target.value = '';
}}
/>
</Card>
);
}
@@ -1008,8 +1096,10 @@ function ArchiveListRow({
const [showPhotos, setShowPhotos] = useState(false);
const [showProjectPage, setShowProjectPage] = useState(false);
const [showDeleteSource3mfConfirm, setShowDeleteSource3mfConfirm] = useState(false);
const [showDeleteF3dConfirm, setShowDeleteF3dConfirm] = useState(false);
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
const source3mfInputRef = useRef<HTMLInputElement>(null);
const f3dInputRef = useRef<HTMLInputElement>(null);
const source3mfUploadMutation = useMutation({
mutationFn: (file: File) => api.uploadSource3mf(archive.id, file),
@@ -1033,6 +1123,28 @@ function ArchiveListRow({
},
});
const f3dUploadMutation = useMutation({
mutationFn: (file: File) => api.uploadF3d(archive.id, file),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['archives'] });
showToast(`F3D attached: ${data.filename}`);
},
onError: (error: Error) => {
showToast(error.message || 'Failed to upload F3D', 'error');
},
});
const f3dDeleteMutation = useMutation({
mutationFn: () => api.deleteF3d(archive.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['archives'] });
showToast('F3D removed');
},
onError: (error: Error) => {
showToast(error.message || 'Failed to remove F3D', 'error');
},
});
const timelapseScanMutation = useMutation({
mutationFn: () => api.scanArchiveTimelapse(archive.id),
onSuccess: (data) => {
@@ -1186,6 +1298,27 @@ function ArchiveListRow({
onClick: () => setShowDeleteSource3mfConfirm(true),
danger: true,
}] : []),
{
label: archive.f3d_path ? 'Replace F3D' : 'Upload F3D',
icon: <Box className="w-4 h-4" />,
onClick: () => f3dInputRef.current?.click(),
},
...(archive.f3d_path ? [{
label: 'Download F3D',
icon: <Download className="w-4 h-4" />,
onClick: () => {
const link = document.createElement('a');
link.href = api.getF3dDownloadUrl(archive.id);
link.download = `${archive.print_name || archive.filename}.f3d`;
link.click();
},
},
{
label: 'Remove F3D',
icon: <Trash2 className="w-4 h-4" />,
onClick: () => setShowDeleteF3dConfirm(true),
danger: true,
}] : []),
{ label: '', divider: true, onClick: () => {} },
{
label: 'Download',
@@ -1489,6 +1622,21 @@ function ArchiveListRow({
/>
)}
{/* Delete F3D Confirmation */}
{showDeleteF3dConfirm && (
<ConfirmModal
title="Remove F3D"
message={`Are you sure you want to remove the Fusion 360 design file from "${archive.print_name || archive.filename}"?`}
confirmText="Remove"
variant="danger"
onConfirm={() => {
f3dDeleteMutation.mutate();
setShowDeleteF3dConfirm(false);
}}
onCancel={() => setShowDeleteF3dConfirm(false)}
/>
)}
{/* Context Menu */}
{contextMenu && (
<ContextMenu
@@ -1617,6 +1765,20 @@ function ArchiveListRow({
e.target.value = '';
}}
/>
{/* Hidden file input for F3D upload */}
<input
ref={f3dInputRef}
type="file"
accept=".f3d"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
f3dUploadMutation.mutate(file);
}
e.target.value = '';
}}
/>
</>
);
}
@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><g><path d="m153.221 184.577c19.326 4.833 44.858 7.496 71.895 7.496 27.024 0 52.549-2.662 71.871-7.496 24.458-6.118 36.347-14.976 36.347-27.081 0-12.092-11.89-20.943-36.349-27.057-19.327-4.831-44.85-7.491-71.869-7.491-27.03 0-52.562 2.661-71.893 7.491-24.462 6.113-36.353 14.963-36.353 27.057 0 12.105 11.89 20.963 36.351 27.081zm10.048-35.938c17.113-3.67 39.078-5.691 61.847-5.691 22.759 0 44.715 2.021 61.824 5.691 14.725 3.159 22.072 6.719 25.102 8.864-3.025 2.146-10.368 5.711-25.093 8.873-17.105 3.673-39.065 5.696-61.834 5.696-22.779 0-44.748-2.023-61.857-5.696-14.727-3.162-22.071-6.727-25.098-8.873 3.033-2.145 10.381-5.705 25.109-8.864z"/><path d="m511.621 299.939c-8.759-30.997-41.987-53.503-69.154-61.298-14.206-4.075-27.122-4.542-37.871-1.528v-19.488c29.607-16.814 45.608-37.674 45.608-60.13 0-27.828-24.579-53.21-69.209-71.469-41.8-17.101-97.159-26.518-155.879-26.518-58.733 0-114.101 9.418-155.904 26.518-44.632 18.259-69.212 43.64-69.212 71.47 0 22.464 16.011 43.33 45.636 60.146v76.727c-29.882 16.917-45.636 37.623-45.636 60.135 0 27.84 24.58 53.226 69.213 71.48 41.796 17.094 97.164 26.508 155.904 26.508 58.728 0 114.087-9.414 155.879-26.508 44.63-18.255 69.209-43.64 69.209-71.48 0-22.505-15.744-43.211-45.608-60.134v-35.729c7.473-4.29 18.961-4.619 32.356-.776 26.055 7.475 49.882 27.901 55.423 47.511 1.245 4.406 5.255 7.283 9.618 7.283.899 0 1.815-.123 2.724-.379 5.314-1.5 8.404-7.026 6.903-12.341zm-445.985 30.134c31.44 17.594 91.184 23.268 113.866 24.826.233.016.465.024.695.024 5.211 0 9.604-4.039 9.967-9.315.378-5.51-3.781-10.284-9.291-10.662-34.324-2.358-65.154-7.512-86.81-14.513-23.602-7.63-28.427-14.866-28.427-16.386v-24.377c9.234 5.161 21.644 9.701 37.204 13.591 32.773 8.195 76.199 12.707 122.276 12.707 46.066 0 89.487-4.513 122.264-12.707 15.565-3.891 27.979-8.432 37.216-13.594v24.381c0 1.52-4.827 8.755-28.44 16.386-21.663 7-52.499 12.155-86.826 14.513-5.51.378-9.669 5.152-9.291 10.662.362 5.277 4.756 9.315 9.967 9.315.229 0 .462-.008.695-.024 22.687-1.559 82.449-7.234 113.895-24.83v24.435c0 2.06-7.14 11.536-42.066 20.268-31.237 7.809-72.935 12.11-117.413 12.11-44.489 0-86.191-4.301-117.424-12.11-34.918-8.731-42.055-18.208-42.055-20.268v-24.432zm-45.636-172.577c0-18.893 20.697-38.195 56.785-52.958 39.456-16.141 92.134-25.03 148.331-25.03 56.184 0 108.854 8.889 148.306 25.03 36.086 14.763 56.782 34.065 56.782 52.958 0 15.801-14.467 31.885-40.286 45.363-.047.025-.094.049-.141.074-5.032 2.621-10.486 5.144-16.355 7.546-39.449 16.142-92.118 25.033-148.306 25.033-56.2 0-108.878-8.89-148.33-25.033-5.792-2.37-11.183-4.858-16.16-7.442-.197-.114-.399-.219-.604-.319-25.652-13.449-40.022-29.475-40.022-45.222zm49.212 71.493c41.8 17.103 97.167 26.522 155.905 26.522 58.724 0 114.083-9.419 155.88-26.522 1.217-.498 2.413-1.004 3.6-1.512v26.113c0 2.06-7.14 11.536-42.066 20.268-31.236 7.81-72.935 12.11-117.413 12.11-44.489 0-86.191-4.301-117.424-12.11-34.918-8.73-42.055-18.208-42.055-20.268v-26.103c1.177.506 2.364 1.008 3.573 1.502zm360.992 125.515c0 18.903-20.696 38.209-56.781 52.969-39.445 16.134-92.115 25.019-148.307 25.019-56.205 0-108.883-8.885-148.332-25.019-36.087-14.759-56.784-34.066-56.784-52.969 0-12.419 8.988-25.106 25.637-36.587v36.587c0 16.832 19.246 30.18 57.204 39.67 32.773 8.195 76.199 12.707 122.276 12.707 46.066 0 89.487-4.513 122.264-12.707 37.966-9.491 57.216-22.838 57.216-39.67v-36.577c16.629 11.481 25.607 24.165 25.607 36.577z"/><path d="m225.116 356.425h.028c5.522 0 9.986-4.477 9.986-10s-4.492-10-10.015-10-10 4.477-10 10c.001 5.523 4.479 10 10.001 10z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-BEswNysN.js"></script>
<script type="module" crossorigin src="/assets/index-BlbHG-iv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dzh7xD3q.css">
</head>
<body>