- Create Print Queue page showing pending/completed/cancelled jobs - Add needs_weighing filter to spool API for flagged spools - Add IPrintJob interface and needs_weighing to ISpool model - Add slicer_post_process.py for Elegoo/Orca Slicer integration - Add translations for print queue feature - Bump version to 0.23C.3 The workflow: 1. Slicer post-processing script creates pending print job 2. After print, user confirms (deducts filament) or cancels (flags for weighing) 3. Spools needing weigh-in are shown in Print Queue UI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Spoolman is a self-hosted web service for managing 3D printer filament spools. It tracks inventory, monitors usage, and integrates with OctoPrint and Klipper/Moonraker.
Tech Stack:
- Backend: Python 3.9-3.12, FastAPI, SQLAlchemy 2.0 (async), Alembic
- Frontend: React 19, TypeScript, Vite, Ant Design, Refine framework
- Databases: SQLite (default), PostgreSQL, MySQL, CockroachDB
Common Commands
Python (PDM)
pdm install # Install all dependencies including dev
pdm sync --prod --no-editable # Install production dependencies only
pdm run app # Run backend server (uvicorn)
pdm run itest # Run integration tests (all databases)
pdm run docs # Generate API documentation
Frontend
cd client
npm ci # Install dependencies
npm run dev # Development server with hot reload
npm run build # Production build
npm run tsc # Type check only
Linting/Formatting
black . # Format Python code
ruff check . --fix # Lint and auto-fix Python
pre-commit run --all-files # Run all pre-commit hooks
Integration Tests
Tests run in Docker containers against multiple database types:
python tests_integration/run.py sqlite # Single database
python tests_integration/run.py sqlite postgres # Multiple databases
pdm run itest # All databases
Run a single test:
pytest tests_integration/tests/spool/test_add.py::test_add_spool_remaining_weight -v
Database Migrations
- Edit models in
spoolman/database/models.py - Run the server to update local SQLite
pdm run alembic revision -m "description" --autogenerate- Review generated migration, format with Black/Ruff
Architecture
Backend (/spoolman/)
main.py- FastAPI app entry, startup/shutdown, SPA mountingenv.py- Environment variable parsing and configurationws.py- WebSocket manager for real-time updatesapi/v1/- REST endpoints and WebSocket handlersrouter.py- Main router with WebSocket root endpointspool.py,filament.py,vendor.py- CRUD endpoints per resourcemodels.py- Pydantic response models
database/- SQLAlchemy models and async database operationsmodels.py- ORM definitions (Vendor, Filament, Spool, Setting, *Field)database.py- Connection setup, backup logic, migrations
Frontend (/client/src/)
App.tsx- Routing and Refine framework setuppages/- Page components (spools/, filaments/, vendors/, settings/)components/dataProvider.ts- REST API integrationcomponents/liveProvider.ts- WebSocket live updates
Data Model
Vendor (1) ──→ (many) Filament (1) ──→ (many) Spool
All entities support custom fields via *Field tables (VendorField, FilamentField, SpoolField).
API Patterns
- REST: Standard CRUD at
/api/v1/{resource} - WebSocket: Real-time events at
WS /api/v1/orWS /api/v1/{resource} - Query params:
?skip=0&limit=50&sort_by=name&sort_order=asc
Key Configuration
Environment variables (see .env.example):
SPOOLMAN_DB_TYPE- Database type (sqlite/mysql/postgres/cockroachdb)SPOOLMAN_PORT- Server port (default: 7912)SPOOLMAN_DEBUG_MODE- Enables permissive CORSVITE_APIURL- Frontend API base URL (e.g.,/api/v1)
Code Quality
- Line length: 120 characters
- Python target: 3.9
- Pre-commit hooks enforce Black formatting and Ruff linting
- Backend is fully async using SQLAlchemy AsyncSession
Gitea Repository
- URL: http://192.168.0.5:3022/tonym/spoolman2
- API Token: 8a04b3cb5dbb54e2d895b707305523c3ad83a945
- Upstream: https://github.com/Donkie/Spoolman (remote:
upstream)
This is a fork with UX improvements. Issues are tracked on the Gitea instance above.
UX Improvement Issues
| # | Title | Status |
|---|---|---|
| 1 | Inline Creation Modals | ✅ DONE |
| 2 | Smart Density Defaults | ✅ DONE |
| 3 | QR Code Sizing Fixes | ✅ DONE |
| 4 | Layout Max-Width | ✅ DONE |
| 5 | Temperature Ranges | ✅ DONE |
| 6 | Spool Adjustment History | ✅ DONE |
| 7 | 3dfilamentprofiles Integration | ✅ DONE |
| 8 | (see Gitea #8) | ✅ DONE |
| 9 | Hierarchical Locations (Room/Bin) | Open |
| 10 | Database Import/Export | ✅ DONE |
| 11 | Theme Colors | ✅ DONE |
| 12 | Clickable Dashboard Cards | ✅ DONE |
| 13 | QR Code Size Setting | ✅ DONE |
| 14 | Parse Temps from Comments | ✅ DONE |
| 15 | Print Job Tracking / Slicer Integration | ✅ DONE |
| 16 | Prev/Next Navigation on Edit Pages | ✅ DONE |
Issue #1 Complete - Inline Creation Modals
Added ability to create filament/vendor inline without leaving the page:
client/src/components/vendorCreateModal.tsx- Quick vendor creationclient/src/components/filamentCreateModal.tsx- Quick filament creation with nested vendor modal- Modified spool create to add "Create Filament" in dropdown
- Modified filament create to add "Create Manufacturer" in dropdown
- Uses
dropdownRenderpattern (same as location select) - Uses
useInvalidateto refresh lists after creation
Issue #2 Complete - Smart Density Defaults
Auto-fill density based on material type:
client/src/utils/materialDefaults.ts- Density lookup table with fuzzy matching- Material field converted to AutoComplete (dropdown suggestions + free text)
- Fuzzy matching: "PLA Silk" → PLA (1.24 g/cm³)
- New filaments default to 1.75mm diameter and 1.24 g/cm³ (PLA)
- Applied to both full create page and FilamentCreateModal
- Edit page not affected (preserves user customizations)
Material densities in lookup:
PLA: 1.24, ABS: 1.04, PETG: 1.27, NYLON: 1.52, TPU: 1.21
PC: 1.3, WOOD: 1.28, CF: 1.3, PC/ABS: 1.19, HIPS: 1.03
PVA: 1.23, ASA: 1.05, PP: 0.9, POM: 1.4, PMMA: 1.18, FPE: 2.16
Issue #3 Complete - QR Code Sizing Fixes
See upstream issue: https://github.com/Donkie/Spoolman/issues/671
Changes:
- Removed fixed 50% max-width constraint (now uses fit-content by default)
- Added
qrCodePaddingsetting (0-10mm, default 2mm) - Added
qrCodeMaxWidthsetting (0-100%, 0 = auto) - Added UI controls with slider + input number for both settings
Files modified:
client/src/pages/printing/printing.tsx- Added settings to interfaceclient/src/pages/printing/qrCodePrintingDialog.tsx- CSS and UI controls
Issue #4 Complete - Layout Max-Width
Changes:
- Added 1400px max-width container in
SpoolmanLayout - Added 800px max-width for forms via CSS override in
overrides.css - Content is centered on wide screens
Issue #5 Complete - Temperature Ranges
Store extruder and bed temperatures as min/max ranges:
- Added 4 new DB columns:
settings_extruder_temp_min/max,settings_bed_temp_min/max - Old single-value columns kept for backward compatibility (deprecated)
- Alembic migration copies existing values to both min and max
- Frontend uses Space.Compact with min/max input pairs
- Label template shows ranges: "ET: 190-220 °C"
Files:
spoolman/database/models.py- New columnsmigrations/versions/2025_01_15_0200-*.py- Migrationspoolman/api/v1/models.py,filament.py- API changesclient/src/pages/filaments/create.tsx- Form updatesclient/src/pages/printing/spoolQrCodePrintingDialog.tsx- Template tags
Issue #6 Complete - Spool Adjustment History
Track history of all spool weight/length adjustments with timestamps and comments.
Backend:
spoolman/database/models.py- AddedSpoolAdjustmentmodel with relationship to Spoolmigrations/versions/2025_01_15_0300-*.py- Migration for spool_adjustment tablespoolman/api/v1/models.py- AddedSpoolAdjustmentPydantic modelspoolman/api/v1/spool.py- Added comment field to use/measure endpoints, GET /spool/{id}/adjustments endpointspoolman/database/spool.py- Updated use_weight, use_length, measure functions to record adjustments
Frontend:
client/src/pages/spools/model.tsx- AddedISpoolAdjustmentinterfaceclient/src/pages/spools/show.tsx- Collapsible adjustment history tableclient/public/locales/en/common.json- Translation keys for history table
Issue #7 Complete - 3dfilamentprofiles Integration
Added 3dfilamentprofiles.com as a second external database source alongside SpoolmanDB.
Backend (spoolman/externaldb.py):
- Added
ExternalSourceenum (SPOOLMANDB, FILAMENT_PROFILES_3D) - Added
Filament3DFPmodel for raw 3dfp data - Added
_transform_3dfp_to_external()to convert 3dfp format to ExternalFilament - Updated
_sync()to fetch from both sources and merge results - Environment variables:
EXTERNAL_DB_3DFP_URL,EXTERNAL_DB_3DFP_ENABLED
Frontend:
client/src/utils/queryExternalDB.ts- AddedExternalSourceenum,sourcefield, temp range fieldsclient/src/components/filamentImportModal.tsx- Added tabs to filter by source (All/SpoolmanDB/3DFP)client/src/pages/filaments/create.tsx- Updated import handler for temp range fields
Data mapping (3dfp → Spoolman):
brand_name→manufacturercolor+material_type→namergb→color_hex(strip # prefix)properties.temp_min/max→extruder_temp/extruder_temp_maxproperties.bed_temp_min/max→bed_temp/bed_temp_max- Diameter defaults to 1.75mm (3dfp doesn't provide)
- Density from material lookup table
Issue #10 Complete - Database Import/Export
Full backup/restore functionality for migrating between databases (PostgreSQL ↔ SQLite).
Backend (spoolman/api/v1/backup.py):
GET /api/v1/backup/export- Downloads all data as single JSON filePOST /api/v1/backup/import- Imports JSON withreplaceoption- Exports: vendors, filaments, spools, spool_adjustments, settings
- Handles ID remapping to maintain relationships
- Requires
python-multipartpackage for file uploads
Frontend (client/src/pages/settings/generalSettings.tsx):
- "Backup & Restore" section in Settings page
- "Download Backup" button for export
- File upload with "Import (Merge)" or "Import (Replace All)" options
- Confirmation dialog for destructive replace operation
Migration workflow:
- Export from PostgreSQL instance via Settings → Download Backup
- Reconfigure container for SQLite
- Import via Settings → Import (Replace All)
Local Development Setup
To run locally against the Unraid PostgreSQL database:
# Create virtualenv and install deps
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[postgres]"
# Start backend (reads .env file)
source .venv/bin/activate
export $(grep -v '^#' .env | xargs)
uvicorn spoolman.main:app --host 0.0.0.0 --port 7912
# Start frontend (separate terminal)
cd client
VITE_APIURL=http://localhost:7912/api/v1 npm run dev
Access at http://localhost:5173
Issue #11 Complete - Theme Colors
Added theme color selector with 5 color options:
client/src/contexts/color-mode/index.tsx- Theme color system with THEME_COLORS constant- Colors: orange, teal, red, green, blue
- Persists in localStorage
- Settings page has color selector buttons
Issue #12 Complete - Clickable Dashboard Cards
Dashboard cards now link to their respective list pages:
client/src/pages/home/index.tsx- Card body is clickable, links to list- Only the + button stays independent (for create)
Issue #13 Complete - QR Code Size Setting
Added explicit QR code size control:
client/src/pages/printing/printing.tsx- AddedqrCodeSizeto interfaceclient/src/pages/printing/qrCodePrintingDialog.tsx- Slider for size in mm (0=auto)
Issue #15 Complete - Print Job Tracking / Slicer Integration
Track pending print jobs from slicer and manage filament deduction after prints complete.
Workflow:
- Slicer post-processing script parses G-code for filament usage
- Script creates pending print job in Spoolman via API
- After print completes, user visits Print Queue UI
- Complete: Deducts filament from spool, records adjustment history
- Cancel: Flags spool for manual weigh-in (no automatic deduction)
Backend:
spoolman/database/models.py-PrintJobmodel,needs_weighingfield on Spoolspoolman/database/print_job.py- CRUD operations for print jobsspoolman/api/v1/print_job.py- REST API endpointsPOST /print-job- Create pending jobPOST /print-job/{id}/complete- Complete job, deduct filamentPOST /print-job/{id}/cancel- Cancel job, flag spool for weighingGET /print-job- List jobs with status filter
migrations/versions/2025_01_15_0400-c3d4e5f6g7h8_add_print_jobs.py
Frontend:
client/src/pages/print-queue/index.tsx- Print Queue UI pageclient/src/pages/spools/model.tsx-IPrintJobinterface,needs_weighingon ISpool
Slicer Script:
scripts/slicer_post_process.py- Elegoo/Orca Slicer post-processing script- Parses G-code comments for filament usage (weight in grams)
- Creates pending print job via API
- Configure via env vars:
SPOOLMAN_URL,SPOOLMAN_SPOOL_ID - Or config file:
~/.config/spoolman/slicer.conf
Deployment
Production (Unraid)
Deployed on Unraid with br0 network:
- URL: http://192.168.0.11:8000 or https://spoolman.nastynas.xyz
- Database: SQLite
- Network: br0 (own IP: 192.168.0.11)
Update script: /mnt/user/appdata/spoolman2-build/update.sh
ssh root@192.168.0.5 "/mnt/user/appdata/spoolman2-build/update.sh"
Known Issues / TODO
Parse Temperature from Comments
Some filaments have temp info in comment field (e.g., "extrude 190-230 / bed 50-70") but not in the min/max fields. Could add a migration or button to parse and populate.
Filament Select Not Refreshing After Inline Creation
When creating a filament via the inline modal on spool create page, the select dropdown doesn't immediately show the new filament with its name (shows ID instead).
3dfilamentprofiles Sample Data
The 3dfp GitHub repo only contains sample data (3 filaments). The full dataset may require using their website API directly.
Future: Issue #9 - Hierarchical Locations
Add room/bin/shelf support for locations. See Gitea issue #9 for details.