import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Loader2, Check, AlertTriangle, Printer, Eye, EyeOff, Info, ChevronDown, ExternalLink } from 'lucide-react'; import { virtualPrinterApi } from '../api/client'; import { Card, CardContent, CardHeader } from './Card'; import { Button } from './Button'; import { useToast } from '../contexts/ToastContext'; export function VirtualPrinterSettings() { const queryClient = useQueryClient(); const { showToast } = useToast(); const [localEnabled, setLocalEnabled] = useState(false); const [localAccessCode, setLocalAccessCode] = useState(''); const [localMode, setLocalMode] = useState<'immediate' | 'queue'>('immediate'); const [localModel, setLocalModel] = useState('3DPrinter-X1-Carbon'); const [showAccessCode, setShowAccessCode] = useState(false); const [pendingAction, setPendingAction] = useState<'toggle' | 'accessCode' | 'mode' | 'model' | null>(null); // Fetch current settings const { data: settings, isLoading } = useQuery({ queryKey: ['virtual-printer-settings'], queryFn: virtualPrinterApi.getSettings, refetchInterval: 10000, // Refresh every 10 seconds for status updates }); // Fetch available models const { data: modelsData } = useQuery({ queryKey: ['virtual-printer-models'], queryFn: virtualPrinterApi.getModels, }); // Initialize local state from settings useEffect(() => { if (settings) { setLocalEnabled(settings.enabled); setLocalMode(settings.mode); setLocalModel(settings.model); } }, [settings]); // Update mutation const updateMutation = useMutation({ mutationFn: (data: { enabled?: boolean; access_code?: string; mode?: 'immediate' | 'queue'; model?: string }) => virtualPrinterApi.updateSettings(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['virtual-printer-settings'] }); showToast('Virtual printer settings updated'); setPendingAction(null); }, onError: (error: Error) => { showToast(error.message || 'Failed to update settings', 'error'); // Revert local state on error if (settings) { setLocalEnabled(settings.enabled); setLocalMode(settings.mode); setLocalModel(settings.model); } setPendingAction(null); }, }); const handleToggleEnabled = () => { const newEnabled = !localEnabled; // If enabling, must have access code if (newEnabled && !localAccessCode && !settings?.access_code_set) { showToast('Please set an access code first', 'error'); return; } setLocalEnabled(newEnabled); setPendingAction('toggle'); updateMutation.mutate({ enabled: newEnabled, access_code: localAccessCode || undefined, mode: localMode, }); }; const handleAccessCodeChange = () => { if (!localAccessCode) { showToast('Access code cannot be empty', 'error'); return; } if (localAccessCode.length !== 8) { showToast('Access code must be exactly 8 characters', 'error'); return; } setPendingAction('accessCode'); updateMutation.mutate({ access_code: localAccessCode, }); setLocalAccessCode(''); // Clear after saving }; const handleModeChange = (mode: 'immediate' | 'queue') => { setLocalMode(mode); setPendingAction('mode'); updateMutation.mutate({ mode }); }; const handleModelChange = (model: string) => { setLocalModel(model); setPendingAction('model'); updateMutation.mutate({ model }); }; if (isLoading) { return ( ); } const status = settings?.status; const isRunning = status?.running || false; return (
{/* Left Column - Settings */}

Virtual Printer

{status && (
{isRunning ? 'Running' : 'Stopped'}
)}

Enable a virtual printer that appears in Bambu Studio and OrcaSlicer. Files sent to this printer will be archived directly without printing.

{/* Enable/Disable Toggle */}
Enable Virtual Printer
{isRunning ? 'Visible as "Bambuddy" in slicer discovery' : 'Not visible to slicers'}
{/* Printer Model */}
Printer Model
Select which printer model to emulate.
{localEnabled && isRunning && (

Disable the virtual printer to change the model

)}
{/* Access Code */}
Access Code
{settings?.access_code_set ? ( Access code is set ) : ( No access code set - required to enable )}
setLocalAccessCode(e.target.value)} placeholder={settings?.access_code_set ? 'Enter new code to change' : 'Enter 8-char code'} maxLength={8} className="w-full bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-md px-3 py-2 text-white placeholder-bambu-gray pr-10 font-mono" />

Must be exactly 8 characters. Used by slicers to authenticate. {localAccessCode && ( {' '}({localAccessCode.length}/8) )}

{/* Archive Mode */}
Archive Mode
{/* Right Column - Info & Status */}
{/* Setup Required Warning */}

Setup Required

The virtual printer feature requires additional system configuration before it will work. This includes port forwarding, firewall rules, and platform-specific settings.

Read the setup guide before enabling
{/* How it works */}

How it works:

  1. Complete the setup guide for your platform
  2. Enable the virtual printer and set an access code
  3. In Bambu Studio or OrcaSlicer, go to "Add Printer"
  4. The "Bambuddy" printer should appear in the discovery list
  5. Connect using the access code you set
  6. When you "print" to Bambuddy, the 3MF file is archived instead
{/* Status Details (when running) */} {status && isRunning && (

Status Details

Printer Name
{status.name}
Model
{status.model_name || status.model}
Serial Number
{status.serial}
Mode
{status.mode}
Pending Files
{status.pending_files}
)}
); }