From 8f3c36e9202e28e24ec823c61335a4febb56f0cf Mon Sep 17 00:00:00 2001 From: Martin Ziegler Date: Fri, 5 Dec 2025 15:20:09 +0100 Subject: [PATCH] Improvements AMS card --- backend/app/api/routes/printer_control.py | 39 ++ backend/app/services/bambu_mqtt.py | 54 +++ frontend/src/api/client.ts | 15 + .../components/control/AMSMaterialsModal.tsx | 82 ++++- .../src/components/control/AMSSectionDual.tsx | 28 +- .../components/control/useAmsOperations.ts | 348 ++++++++++++++++++ .../{index-CXerUj0x.js => index-CLNJVLog.js} | 258 ++++++------- static/index.html | 2 +- 8 files changed, 682 insertions(+), 144 deletions(-) create mode 100644 frontend/src/components/control/useAmsOperations.ts rename static/assets/{index-CXerUj0x.js => index-CLNJVLog.js} (62%) diff --git a/backend/app/api/routes/printer_control.py b/backend/app/api/routes/printer_control.py index c99e6fe39..fadfd91b1 100644 --- a/backend/app/api/routes/printer_control.py +++ b/backend/app/api/routes/printer_control.py @@ -141,6 +141,18 @@ class AMSRefreshTrayRequest(BaseModel): tray_id: int = Field(..., ge=0, le=3, description="Tray ID within the AMS (0-3)") +class AMSFilamentSettingRequest(BaseModel): + ams_id: int = Field(..., ge=0, le=128, description="AMS unit ID (0-3, or 128 for H2D external)") + tray_id: int = Field(..., ge=0, le=3, description="Tray ID within the AMS (0-3)") + tray_info_idx: str = Field(..., description="Filament preset ID (e.g., 'GFA00')") + tray_type: str = Field(..., description="Filament type (e.g., 'PLA', 'PETG')") + tray_sub_brands: str = Field(default="", description="Sub-brand name (e.g., 'PLA Basic')") + tray_color: str = Field(..., description="Color in RRGGBBAA hex format") + nozzle_temp_min: int = Field(..., ge=150, le=350, description="Minimum nozzle temperature") + nozzle_temp_max: int = Field(..., ge=150, le=350, description="Maximum nozzle temperature") + k: float = Field(..., ge=0, le=1, description="Pressure advance (K) value") + + class GcodeRequest(ConfirmableRequest): command: str = Field(..., min_length=1, max_length=500, description="G-code command(s)") @@ -635,6 +647,33 @@ async def ams_refresh_tray( return ControlResponse(success=success, message=message) +@router.post("/{printer_id}/control/ams/filament-setting", response_model=ControlResponse) +async def ams_set_filament_setting( + printer_id: int, + request: AMSFilamentSettingRequest, + db: AsyncSession = Depends(get_db), +): + """Set filament settings for an AMS tray including K (pressure advance) value.""" + await get_printer_or_404(printer_id, db) + client = get_mqtt_client_or_503(printer_id) + + success = client.ams_set_filament_setting( + ams_id=request.ams_id, + tray_id=request.tray_id, + tray_info_idx=request.tray_info_idx, + tray_type=request.tray_type, + tray_sub_brands=request.tray_sub_brands, + tray_color=request.tray_color, + nozzle_temp_min=request.nozzle_temp_min, + nozzle_temp_max=request.nozzle_temp_max, + k=request.k, + ) + return ControlResponse( + success=success, + message=f"Updated AMS {request.ams_id} tray {request.tray_id} with K={request.k}" if success else "Failed to update filament setting" + ) + + # ============================================================================= # Advanced: G-code Command # ============================================================================= diff --git a/backend/app/services/bambu_mqtt.py b/backend/app/services/bambu_mqtt.py index ccdd33312..5bc1d9127 100644 --- a/backend/app/services/bambu_mqtt.py +++ b/backend/app/services/bambu_mqtt.py @@ -2627,6 +2627,60 @@ class BambuMQTTClient: logger.info(f"[{self.serial_number}] Triggering RFID re-read: AMS {ams_id}, slot {tray_id}") return True, f"Refreshing AMS {ams_id} tray {tray_id}" + def ams_set_filament_setting( + self, + ams_id: int, + tray_id: int, + tray_info_idx: str, + tray_type: str, + tray_sub_brands: str, + tray_color: str, + nozzle_temp_min: int, + nozzle_temp_max: int, + k: float, + ) -> bool: + """Set AMS tray filament settings including K (pressure advance) value. + + Args: + ams_id: AMS unit ID (0-3) + tray_id: Tray ID within the AMS (0-3) + tray_info_idx: Filament preset ID (e.g., "GFA00") + tray_type: Filament type (e.g., "PLA", "PETG") + tray_sub_brands: Sub-brand name (e.g., "PLA Basic", "PETG HF") + tray_color: Color in RRGGBBAA hex format (e.g., "FFFF00FF") + nozzle_temp_min: Minimum nozzle temperature + nozzle_temp_max: Maximum nozzle temperature + k: Pressure advance (K) value (e.g., 0.020) + + Returns: + True if command was sent, False otherwise + """ + if not self._client or not self.state.connected: + logger.warning(f"[{self.serial_number}] Cannot set AMS filament setting: not connected") + return False + + command = { + "print": { + "command": "ams_filament_setting", + "ams_id": ams_id, + "tray_id": tray_id, + "tray_info_idx": tray_info_idx, + "tray_type": tray_type, + "tray_sub_brands": tray_sub_brands, + "tray_color": tray_color, + "nozzle_temp_min": nozzle_temp_min, + "nozzle_temp_max": nozzle_temp_max, + "k": k, + "sequence_id": "0" + } + } + + command_json = json.dumps(command) + logger.info(f"[{self.serial_number}] Publishing ams_filament_setting: AMS {ams_id}, tray {tray_id}, k={k}") + logger.debug(f"[{self.serial_number}] ams_filament_setting command: {command_json}") + self._client.publish(self.topic_publish, command_json) + return True + def set_timelapse(self, enable: bool) -> bool: """Enable or disable timelapse recording. diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 1adeaa60a..0f3a4fe0f 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1281,6 +1281,21 @@ export const api = { }), amsUnloadFilament: (printerId: number) => request(`/printers/${printerId}/control/ams/unload`, { method: 'POST' }), + amsSetFilamentSetting: (printerId: number, data: { + ams_id: number; + tray_id: number; + tray_info_idx: string; + tray_type: string; + tray_sub_brands: string; + tray_color: string; + nozzle_temp_min: number; + nozzle_temp_max: number; + k: number; + }) => + request(`/printers/${printerId}/control/ams/filament-setting`, { + method: 'POST', + body: JSON.stringify(data), + }), sendGcode: (printerId: number, command: string, confirmToken?: string) => request(`/printers/${printerId}/control/gcode`, { method: 'POST', diff --git a/frontend/src/components/control/AMSMaterialsModal.tsx b/frontend/src/components/control/AMSMaterialsModal.tsx index 5e6613d39..5a9908d87 100644 --- a/frontend/src/components/control/AMSMaterialsModal.tsx +++ b/frontend/src/components/control/AMSMaterialsModal.tsx @@ -74,6 +74,12 @@ function trayColorToHex(trayColor: string | null): string { return `#${cleanHex}`; } +function hexToTrayColor(hex: string): string { + // Convert #RRGGBB to RRGGBBFF (with full opacity) + const cleanHex = hex.replace('#', '').toUpperCase(); + return cleanHex.length === 6 ? `${cleanHex}FF` : `${cleanHex.substring(0, 6)}FF`; +} + // Check if tray has valid Bambu Lab UUID (32-char hex) function isBambuLabSpool(trayUuid: string | null): boolean { if (!trayUuid) return false; @@ -210,16 +216,40 @@ export function AMSMaterialsModal({ }, }); + // Mutation for saving filament settings to printer (including K value) + const saveFilamentSettingMutation = useMutation({ + mutationFn: (data: { + ams_id: number; + tray_id: number; + tray_info_idx: string; + tray_type: string; + tray_sub_brands: string; + tray_color: string; + nozzle_temp_min: number; + nozzle_temp_max: number; + k: number; + }) => api.amsSetFilamentSetting(printerId, data), + onSuccess: (result) => { + console.log('[AMSMaterialsModal] saveFilamentSettingMutation SUCCESS:', result); + }, + onError: (error) => { + console.error('[AMSMaterialsModal] saveFilamentSettingMutation ERROR:', error); + }, + }); + // Get filament presets from cloud settings: - // 1. Show ALL filament presets (like Bambu Studio does in cloud profiles) + // 1. Filter out presets starting with "#" (experimental/special presets) // 2. Prioritize current printer model, then deduplicate by base name // 3. Sort alphabetically const filamentPresets = (() => { const allPresets = cloudSettings?.filament || []; + // Filter out presets starting with "#" (experimental/special presets) + const filteredPresets = allPresets.filter(p => !p.name.startsWith('# ')); + // Sort presets: current printer model first, then others const modelPattern = new RegExp(`@.*\\b${printerModel}\\b`, 'i'); - const sortedByModel = [...allPresets].sort((a, b) => { + const sortedByModel = [...filteredPresets].sort((a, b) => { const aMatches = modelPattern.test(a.name); const bMatches = modelPattern.test(b.name); if (aMatches && !bMatches) return -1; @@ -323,6 +353,22 @@ export function AMSMaterialsModal({ const bambuColorName = getColorNameFromTrayId(tray.tray_id_name); useEffect(() => { + // First, try to find a profile matching the tray's current K value (from printer) + // This preserves the user's selection when reopening the modal + const currentK = tray.k; + if (currentK && currentK > 0) { + const matchingKProfile = matchingProfiles.find(p => { + const profileK = parseFloat(p.k_value); + return Math.abs(profileK - currentK) < 0.0001; // Allow small floating point tolerance + }); + if (matchingKProfile) { + setSelectedKProfile(matchingKProfile.name); + setKValue(currentK); + return; + } + } + + // Fall back to name-based matching if no K-value match found // For Bambu spools, use tray_sub_brands (e.g., "PLA Basic") and color name // For non-Bambu, use clean name from preset (e.g., "Bambu PLA Basic" without #) for K-profile matching const subBrands = isBambuSpool @@ -340,7 +386,7 @@ export function AMSMaterialsModal({ setSelectedKProfile('Default'); setKValue(0); } - }, [profiles, selectedFilamentType, selectedPresetName, tray.tray_type, tray.tray_sub_brands, tray.tray_id_name, isBambuSpool, bambuColorName]); + }, [profiles, matchingProfiles, selectedFilamentType, selectedPresetName, tray.tray_type, tray.tray_sub_brands, tray.tray_id_name, tray.k, isBambuSpool, bambuColorName]); // Close on Escape key useEffect(() => { @@ -360,14 +406,44 @@ export function AMSMaterialsModal({ }; const handleConfirm = () => { + console.log('[AMSMaterialsModal] handleConfirm called'); + console.log('[AMSMaterialsModal] amsId:', amsId, 'tray.id:', tray.id, 'kValue:', kValue); + // Save preset mapping for non-Bambu slots if (isEditable && selectedPresetId && selectedPresetName) { + console.log('[AMSMaterialsModal] Saving preset mapping'); savePresetMutation.mutate({ presetId: selectedPresetId, presetName: selectedPresetName, }); } + // Send filament settings to printer (including K value) + // Use current tray data for Bambu spools, or edited values for non-Bambu + const trayColor = isBambuSpool + ? (tray.tray_color || '808080FF') + : hexToTrayColor(selectedColor); + const trayType = isBambuSpool + ? (tray.tray_type || 'PLA') + : (selectedFilamentType || 'PLA'); + const traySubBrands = isBambuSpool + ? (tray.tray_sub_brands || '') + : (selectedPresetName ? getCleanFilamentName(selectedPresetName) : ''); + + const payload = { + ams_id: amsId, + tray_id: tray.id, + tray_info_idx: tray.tray_info_idx || '', + tray_type: trayType, + tray_sub_brands: traySubBrands, + tray_color: trayColor, + nozzle_temp_min: nozzleTempMin, + nozzle_temp_max: nozzleTempMax, + k: kValue, + }; + console.log('[AMSMaterialsModal] Calling saveFilamentSettingMutation with payload:', payload); + saveFilamentSettingMutation.mutate(payload); + onConfirm?.({ filamentType: selectedFilamentType, color: selectedColor, diff --git a/frontend/src/components/control/AMSSectionDual.tsx b/frontend/src/components/control/AMSSectionDual.tsx index 2ab414940..2148abac4 100644 --- a/frontend/src/components/control/AMSSectionDual.tsx +++ b/frontend/src/components/control/AMSSectionDual.tsx @@ -1,11 +1,12 @@ import { useState, useEffect, useRef } from 'react'; -import { useQuery, useMutation } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { api } from '../../api/client'; import type { PrinterStatus, AMSUnit, AMSTray, KProfile } from '../../api/client'; import { Loader2, ChevronDown, ChevronUp, RotateCw } from 'lucide-react'; import { AMSHumidityModal } from './AMSHumidityModal'; import { AMSMaterialsModal } from './AMSMaterialsModal'; import { useToast } from '../../contexts/ToastContext'; +import { useAmsOperations } from './useAmsOperations'; interface AMSSectionDualProps { @@ -519,7 +520,10 @@ function AMSPanelContent({ return (