+
!isDisabled && setIsOpen(!isOpen)}
+ disabled={isDisabled}
+ className={`flex items-center gap-2 px-3 py-1.5 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded text-sm text-white min-w-[100px] justify-between ${
+ isDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:border-bambu-gray'
+ }`}
+ >
+ {loading ? (
+
+ ) : (
+ {displayValue}
+ )}
+
+
+ {isOpen && !isDisabled && (
+ <>
+
setIsOpen(false)} />
+
+ {options.map((option) => (
+ {
+ onChange(option.toLowerCase());
+ setIsOpen(false);
+ }}
+ className={`w-full px-3 py-2 text-left text-sm hover:bg-bambu-dark-tertiary ${
+ value.toLowerCase() === option.toLowerCase() ? 'text-bambu-green' : 'text-white'
+ }`}
+ >
+ {option}
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+export function PrintOptionsModal({ printer, status, onClose }: PrintOptionsModalProps) {
+ const queryClient = useQueryClient();
+ const printOptions = status?.print_options;
+ const isConnected = status?.connected ?? false;
+
+ // Close on Escape key
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [onClose]);
+
+ // Set print option mutation - track both UI and API module names
+ const setOptionMutation = useMutation({
+ mutationFn: ({
+ apiModuleName,
+ enabled,
+ printHalt,
+ sensitivity,
+ }: {
+ uiModuleName: string; // The module name from the UI (for loading state) - accessed via variables
+ apiModuleName: string; // The module name sent to API (may differ due to firmware quirk)
+ enabled: boolean;
+ printHalt?: boolean;
+ sensitivity?: string;
+ }) => api.setPrintOption(printer.id, apiModuleName, enabled, printHalt, sensitivity),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['printerStatuses'] });
+ },
+ });
+
+ const handleToggle = (moduleName: string, enabled: boolean, sensitivity?: string) => {
+ const printHalt = enabled && sensitivity !== undefined && sensitivity !== 'never_halt';
+ setOptionMutation.mutate({
+ uiModuleName: moduleName,
+ apiModuleName: moduleName,
+ enabled,
+ printHalt,
+ sensitivity: sensitivity ?? 'medium'
+ });
+ };
+
+ const handleSensitivityChange = (moduleName: string, sensitivity: string) => {
+ // Spaghetti and Pileup sensitivities are linked in firmware
+ // For pileup, we send spaghetti_detector to set the shared sensitivity
+ // All other detectors use their own module name
+ let apiModuleName = moduleName;
+ if (moduleName === 'pileup_detector') {
+ // Pileup sensitivity is linked to spaghetti - use spaghetti_detector
+ apiModuleName = 'spaghetti_detector';
+ }
+
+ setOptionMutation.mutate({
+ uiModuleName: moduleName,
+ apiModuleName,
+ enabled: true,
+ printHalt: true,
+ sensitivity
+ });
+ };
+
+ // Check loading state using the UI module name (not API module name)
+ const isToggling = (moduleName: string) =>
+ setOptionMutation.isPending && setOptionMutation.variables?.uiModuleName === moduleName;
+
+ // Check if sensitivity is being changed for a specific detector
+ // This also returns true for linked detectors (e.g., spaghetti loading shows for pileup too)
+ const isSensitivityLoading = (moduleName: string) => {
+ if (!setOptionMutation.isPending || !setOptionMutation.variables?.sensitivity) return false;
+ const uiModule = setOptionMutation.variables.uiModuleName;
+ // Direct match
+ if (uiModule === moduleName) return true;
+ // Linked: spaghetti and pileup share sensitivity
+ if ((uiModule === 'spaghetti_detector' && moduleName === 'pileup_detector') ||
+ (uiModule === 'pileup_detector' && moduleName === 'spaghetti_detector')) return true;
+ return false;
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+ {/* Header */}
+
+ Print Options
+
+
+
+
+
+ {/* Content - Scrollable */}
+
+ {!isConnected && (
+
+
+
Printer not connected. Options cannot be changed.
+
+ )}
+
+ {!printOptions ? (
+
+
+
Print options not available. Try refreshing.
+
+ ) : (
+ <>
+ {/* AI Detections Section */}
+
+
AI Detections
+
+ Printer will send assistant message or pause printing if any of the following problem is detected.
+
+
+ {/* Spaghetti Detection */}
+
+
+ handleToggle('spaghetti_detector', checked, printOptions.halt_print_sensitivity)}
+ disabled={!isConnected}
+ loading={isToggling('spaghetti_detector')}
+ />
+ Spaghetti Detection
+
+
+ Detects spaghetti failure (scattered loose filament).
+
+
+ Pausing Sensitivity:
+ handleSensitivityChange('spaghetti_detector', value)}
+ disabled={!isConnected || !printOptions.spaghetti_detector}
+ loading={isSensitivityLoading('spaghetti_detector')}
+ />
+
+
+
+ {/* Purge Chute Pile-Up Detection */}
+
+
+ handleToggle('pileup_detector', checked, printOptions.pileup_sensitivity)}
+ disabled={!isConnected}
+ loading={isToggling('pileup_detector')}
+ />
+ Purge Chute Pile-Up Detection
+
+
+ Monitors if the waste is piled up in the purge chute.
+
+
+ Pausing Sensitivity:
+ handleSensitivityChange('pileup_detector', value)}
+ disabled={!isConnected || !printOptions.pileup_detector}
+ loading={isSensitivityLoading('pileup_detector')}
+ />
+
+
+
+ {/* Nozzle Clumping Detection */}
+
+
+ handleToggle('clump_detector', checked, printOptions.nozzle_clumping_sensitivity)}
+ disabled={!isConnected}
+ loading={isToggling('clump_detector')}
+ />
+ Nozzle Clumping Detection
+
+
+ Checks if the nozzle is clumping by filaments or other foreign objects.
+
+
+ Pausing Sensitivity:
+ handleSensitivityChange('clump_detector', value)}
+ disabled={!isConnected || !printOptions.nozzle_clumping_detector}
+ loading={isSensitivityLoading('clump_detector')}
+ />
+
+
+
+ {/* Air Printing Detection */}
+
+
+ handleToggle('airprint_detector', checked, printOptions.airprint_sensitivity)}
+ disabled={!isConnected}
+ loading={isToggling('airprint_detector')}
+ />
+ Air Printing Detection
+
+
+ Detects air printing caused by nozzle clogging or filament grinding.
+
+
+ Pausing Sensitivity:
+ handleSensitivityChange('airprint_detector', value)}
+ disabled={!isConnected || !printOptions.airprint_detector}
+ loading={isSensitivityLoading('airprint_detector')}
+ />
+
+
+
+
+
+
+
+ {/* Build Plate Detection Section */}
+
+
Build Plate Detection
+
+ Ensures the build plate type and placement are correct.
+
+
+
+ handleToggle('buildplate_marker_detector', checked)}
+ disabled={!isConnected}
+ loading={isToggling('buildplate_marker_detector')}
+ />
+ Type Detection
+
+
+ Pauses printing when the detected build plate type does not match the selected one.
+
+
+
+
+
+
+ {/* Other Options */}
+
+ {/* Auto-recover from step loss */}
+
+ handleToggle('auto_recovery_step_loss', checked)}
+ disabled={!isConnected}
+ loading={isToggling('auto_recovery_step_loss')}
+ />
+ Auto-recover from step loss
+
+
+ {/* Store Sent Files on External Storage - Read-only (no MQTT command available) */}
+
+
+ {}}
+ disabled={true}
+ />
+ Store Sent Files on External Storage
+ (read-only)
+
+
+ Save the printing files initiated from Bambu Studio, Bambu Handy and MakerWorld on External Storage.
+ This option can only be changed on the printer's touchscreen.
+
+
+
+ >
+ )}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/ControlPage.tsx b/frontend/src/pages/ControlPage.tsx
index ec27558b8..b88e22ddb 100644
--- a/frontend/src/pages/ControlPage.tsx
+++ b/frontend/src/pages/ControlPage.tsx
@@ -12,6 +12,7 @@ import { ExtruderControls } from '../components/control/ExtruderControls';
import { AMSSectionDual } from '../components/control/AMSSectionDual';
import { CameraSettingsModal } from '../components/control/CameraSettingsModal';
import { PrinterPartsModal } from '../components/control/PrinterPartsModal';
+import { PrintOptionsModal } from '../components/control/PrintOptionsModal';
import { Loader2, WifiOff, Video, Webcam, Settings } from 'lucide-react';
export function ControlPage() {
@@ -19,6 +20,7 @@ export function ControlPage() {
const [selectedPrinterId, setSelectedPrinterId] = useState
(null);
const [showCameraSettings, setShowCameraSettings] = useState(false);
const [showPrinterParts, setShowPrinterParts] = useState(false);
+ const [showPrintOptions, setShowPrintOptions] = useState(false);
// Fetch all printers
const { data: printers, isLoading: loadingPrinters } = useQuery({
@@ -178,7 +180,10 @@ export function ControlPage() {
>
Printer Parts
-
+ setShowPrintOptions(true)}
+ className="px-4 py-1.5 text-xs rounded bg-bambu-green text-white hover:bg-bambu-green-dark"
+ >
Print Options
@@ -261,6 +266,15 @@ export function ControlPage() {
onClose={() => setShowPrinterParts(false)}
/>
)}
+
+ {/* Print Options Modal */}
+ {showPrintOptions && selectedPrinter && (
+ setShowPrintOptions(false)}
+ />
+ )}
);
}
diff --git a/static/assets/index-DVDHG02m.js b/static/assets/index-DKrE4YAX.js
similarity index 70%
rename from static/assets/index-DVDHG02m.js
rename to static/assets/index-DKrE4YAX.js
index 0e643955f..44b730e0c 100644
--- a/static/assets/index-DVDHG02m.js
+++ b/static/assets/index-DKrE4YAX.js
@@ -1,18 +1,18 @@
-function Bte(t,e){for(var n=0;n