diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ea4355a6..bfc82d31a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
## [0.1.9b] - Not released
### New Features
+- **Configurable Slicer Preference** ([#313](https://github.com/maziggy/bambuddy/issues/313)) — New "Preferred Slicer" setting in General settings to choose between Bambu Studio and OrcaSlicer. Controls the protocol used by all "Open in Slicer" buttons across Archives, 3D Preview, and context menus. OrcaSlicer uses the `orcaslicer://open?file=` protocol. Default remains Bambu Studio for backward compatibility.
- **Local Profiles — OrcaSlicer Import** ([#310](https://github.com/maziggy/bambuddy/issues/310)) — Import slicer presets from OrcaSlicer without Bambu Cloud. Supports `.orca_filament`, `.bbscfg`, `.bbsflmt`, `.zip`, and `.json` exports. Resolves OrcaSlicer inheritance chains by fetching base Bambu profiles from GitHub (cached locally with 7-day TTL). Stores presets in the database with extracted core fields (material type, vendor, nozzle temps, pressure advance, compatible printers). New "Local Profiles" tab on the Profiles page with drag-and-drop import, 3-column layout (Filament/Process/Printer), search, and expandable preset details. Local filament presets appear in AMS slot configuration alongside cloud presets. Includes smart profile type detection (explicit type field, ZIP path hints, settings ID keys, content heuristics, and name-based patterns) and material/vendor extraction from preset names as fallback.
- **Hostname Support for Printers** ([#290](https://github.com/maziggy/bambuddy/issues/290)) — Printers can now be added using hostnames (e.g., `printer.local`, `my-printer.home.lan`) in addition to IPv4 addresses. Updated backend validation, frontend forms, and all locale labels.
- **Camera View Controls** ([#291](https://github.com/maziggy/bambuddy/issues/291)) — Added chamber light toggle and skip objects buttons to both embedded camera viewer and standalone camera page. Extracted skip objects modal into a reusable `SkipObjectsModal` component shared across PrintersPage and both camera views.
diff --git a/README.md b/README.md
index 900838438..f5f3a2756 100644
--- a/README.md
+++ b/README.md
@@ -191,7 +191,7 @@ Perfect for remote print farms, traveling makers, or accessing your home printer
-**Plus:** Customizable themes (style, background, accent) • Mobile responsive • Keyboard shortcuts • Multi-language (EN/DE) • Auto updates • Database backup/restore • System info dashboard
+**Plus:** Configurable slicer (Bambu Studio / OrcaSlicer) • Customizable themes (style, background, accent) • Mobile responsive • Keyboard shortcuts • Multi-language (EN/DE) • Auto updates • Database backup/restore • System info dashboard
---
diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py
index 0e692e4db..3c4df280a 100644
--- a/backend/app/schemas/settings.py
+++ b/backend/app/schemas/settings.py
@@ -130,6 +130,12 @@ class AppSettings(BaseModel):
description="Camera view mode: 'window' opens in new browser window, 'embedded' shows overlay on main screen",
)
+ # Preferred slicer application
+ preferred_slicer: str = Field(
+ default="bambu_studio",
+ description="Preferred slicer: 'bambu_studio' or 'orcaslicer'",
+ )
+
# Prometheus metrics endpoint
prometheus_enabled: bool = Field(default=False, description="Enable Prometheus metrics endpoint at /metrics")
prometheus_token: str = Field(
@@ -191,5 +197,6 @@ class AppSettingsUpdate(BaseModel):
library_archive_mode: str | None = None
library_disk_warning_gb: float | None = None
camera_view_mode: str | None = None
+ preferred_slicer: str | None = None
prometheus_enabled: bool | None = None
prometheus_token: str | None = None
diff --git a/backend/tests/unit/test_slicer_settings.py b/backend/tests/unit/test_slicer_settings.py
new file mode 100644
index 000000000..f1d03c9d9
--- /dev/null
+++ b/backend/tests/unit/test_slicer_settings.py
@@ -0,0 +1,48 @@
+"""Unit tests for preferred_slicer setting in AppSettings schema."""
+
+import pytest
+
+from backend.app.schemas.settings import AppSettings, AppSettingsUpdate
+
+
+@pytest.mark.unit
+class TestPreferredSlicerSchema:
+ """Tests for the preferred_slicer field in settings schemas."""
+
+ def test_default_value_is_bambu_studio(self):
+ """Default preferred_slicer should be bambu_studio."""
+ settings = AppSettings()
+ assert settings.preferred_slicer == "bambu_studio"
+
+ def test_set_to_orcaslicer(self):
+ """Should accept orcaslicer as a valid value."""
+ settings = AppSettings(preferred_slicer="orcaslicer")
+ assert settings.preferred_slicer == "orcaslicer"
+
+ def test_set_to_bambu_studio_explicit(self):
+ """Should accept bambu_studio as an explicit value."""
+ settings = AppSettings(preferred_slicer="bambu_studio")
+ assert settings.preferred_slicer == "bambu_studio"
+
+ def test_update_schema_default_is_none(self):
+ """AppSettingsUpdate preferred_slicer should default to None."""
+ update = AppSettingsUpdate()
+ assert update.preferred_slicer is None
+
+ def test_update_schema_accepts_value(self):
+ """AppSettingsUpdate should accept a preferred_slicer value."""
+ update = AppSettingsUpdate(preferred_slicer="orcaslicer")
+ assert update.preferred_slicer == "orcaslicer"
+
+ def test_serialization_roundtrip(self):
+ """Settings should survive serialization roundtrip."""
+ settings = AppSettings(preferred_slicer="orcaslicer")
+ data = settings.model_dump()
+ restored = AppSettings(**data)
+ assert restored.preferred_slicer == "orcaslicer"
+
+ def test_partial_update_preserves_other_fields(self):
+ """Updating preferred_slicer should not affect other fields."""
+ update = AppSettingsUpdate(preferred_slicer="orcaslicer")
+ data = update.model_dump(exclude_none=True)
+ assert data == {"preferred_slicer": "orcaslicer"}
diff --git a/frontend/src/__tests__/pages/SettingsPage.test.tsx b/frontend/src/__tests__/pages/SettingsPage.test.tsx
index 45c40ad8e..4eeeb8de6 100644
--- a/frontend/src/__tests__/pages/SettingsPage.test.tsx
+++ b/frontend/src/__tests__/pages/SettingsPage.test.tsx
@@ -118,6 +118,23 @@ describe('SettingsPage', () => {
});
});
+ it('shows preferred slicer setting', async () => {
+ render(
+ {t('settings.preferredSlicerDescription')} +
+