Watch at Ease看見世界,讀懂生活。

科技 · 實用指南

Bambu Studio 預設消失?重裝前先稽核檔案

選單看不到不等於預設 JSON 已刪除。先保全設定樹,再逐一雜湊使用者預設並解析繼承父項。

原創編輯示意圖:三步查找 Bambu Studio JSON、記錄精確證據並分類父項狀態。
原創編輯示意圖:修復即時設定前先清點每個使用者範圍。 Watch at Ease

自訂耗材預設從 Bambu Studio 消失時,不要先把零散 JSON 複製回即時設定,也不要直接覆蓋重裝。目前完整報告至少顯示兩種不同情況:繼承型預設仍在磁碟卻被隱藏,或更新後的系統設定包為空。因此同一症狀可能來自可見性、父項缺失、資料損壞,或檔案確實不存在。

本指南只在已保全的 Bambu Studio 設定樹副本上產生唯讀證據表。每個使用者預設都記錄帳號範圍、相對路徑、精確位元組數、SHA-256、名稱、inherits、相容印表機,以及 root-preset、parent-found、parent-missing、unreadable 四種狀態。它不登入雲端、不修改即時設定,也不保證恢復已刪除檔案。

改動 Studio 前先凍結證據

完全關閉 Bambu Studio,確認這次安裝實際使用的設定根目錄,再建立名稱獨立的完整副本。不要只複製 filament 資料夾;系統支援時保留時間戳與權限。先不要清理、同步、匯入、重設或重裝,因為這些動作可能改變用來區分隱藏與遺失的證據。

記錄 Bambu Studio 版本、作業系統、選取的印表機與噴嘴,以及製作副本的時間。若某個設定仍看得到預設,也先依文件匯出預設包。可見匯出與磁碟清單回答不同問題,兩者都保留較安全。

區分使用者預設與系統父項

Bambu Studio 可把使用者耗材預設放在多個使用者範圍,包括 default 與帳號資料夾。使用者預設也可能只保存相對系統預設的差異,並用 inherits 指向父項。因此找到 JSON 是必要證據,但不足以單獨證明 Studio 能載入它。

稽核會索引 system/BBL/filament 下具名的系統耗材預設,再核對每個使用者預設的 inherits。它不把同名檔案自動視為同一預設;會記錄 JSON 名稱與精確位元組,並拒絕重複鍵 JSON,而不是靜默接受最後一個值。

只在保全副本上執行稽核

將下列腳本存成 bambu_preset_audit.py。使用 Python 3.12 或相容 Python 3,傳入設定副本根目錄與副本外的新 CSV 路徑。報告採獨佔新建,不覆寫既有檔案;略過符號連結,且不寫入輸入樹。

Run this exact reviewed script from a copy of the configuration tree, not the live tree: ```python #!/usr/bin/env python3 """Read-only inventory for Bambu Studio user filament preset JSON files.""" from __future__ import annotations import argparse import csv import hashlib import json from pathlib import Path def object_no_duplicates(pairs): result = {} for key, value in pairs: if key in result: raise ValueError(f"duplicate JSON key: {key}") result[key] = value return result def load_json(path: Path): raw = path.read_bytes() value = json.loads(raw.decode("utf-8-sig"), object_pairs_hook=object_no_duplicates) if not isinstance(value, dict): raise ValueError("top-level JSON value is not an object") return raw, value def text_list(value): if value is None: return [] if isinstance(value, str): return [value] if isinstance(value, list) and all(isinstance(item, str) for item in value): return value raise ValueError("compatible_printers is not a string list") def system_names(root: Path): names = {} system_root = root / "system" / "BBL" / "filament" if not system_root.is_dir(): return names for path in sorted(system_root.rglob("*.json")): if path.is_symlink() or not path.is_file(): continue try: _, data = load_json(path) name = data.get("name") if isinstance(name, str) and name: names.setdefault(name, []).append(path.relative_to(root).as_posix()) except (OSError, UnicodeError, ValueError, json.JSONDecodeError): continue return names def audit(root: Path): parents = system_names(root) user_root = root / "user" rows = [] if not user_root.is_dir(): return rows for scope in sorted(user_root.iterdir()): filament = scope / "filament" if scope.is_symlink() or not scope.is_dir() or not filament.is_dir(): continue for path in sorted(filament.rglob("*.json")): if path.is_symlink() or not path.is_file(): continue row = { "account_scope": scope.name, "relative_path": path.relative_to(root).as_posix(), "bytes": path.stat().st_size, "sha256": "", "preset_name": "", "inherits": "", "parent_status": "unreadable", "parent_paths": "", "compatible_printers": "", "error": "", } try: raw, data = load_json(path) row["sha256"] = hashlib.sha256(raw).hexdigest() name = data.get("name", path.stem) if not isinstance(name, str): raise ValueError("name is not a string") inherits = data.get("inherits", "") if not isinstance(inherits, str): raise ValueError("inherits is not a string") printers = text_list(data.get("compatible_printers")) row["preset_name"] = name row["inherits"] = inherits row["compatible_printers"] = " | ".join(printers) if not inherits: row["parent_status"] = "root-preset" elif inherits in parents: row["parent_status"] = "parent-found" row["parent_paths"] = " | ".join(parents[inherits]) else: row["parent_status"] = "parent-missing" except (OSError, UnicodeError, ValueError, json.JSONDecodeError) as exc: row["error"] = str(exc) rows.append(row) return rows def main(): parser = argparse.ArgumentParser() parser.add_argument("config_root", type=Path) parser.add_argument("report_csv", type=Path) args = parser.parse_args() root = args.config_root.resolve(strict=True) report = args.report_csv.resolve() if not root.is_dir(): raise SystemExit("config_root is not a directory") if report == root or root in report.parents: raise SystemExit("report_csv must be outside config_root") rows = audit(root) fields = [ "account_scope", "relative_path", "bytes", "sha256", "preset_name", "inherits", "parent_status", "parent_paths", "compatible_printers", "error", ] with report.open("x", encoding="utf-8-sig", newline="") as handle: writer = csv.DictWriter(handle, fieldnames=fields) writer.writeheader() writer.writerows(rows) print(json.dumps({"config_root": str(root), "report_csv": str(report), "rows": len(rows)})) if __name__ == "__main__": main() ```

核對指令與輸出邊界

範例:python3 bambu_preset_audit.py '/evidence/BambuStudio-copy' '/evidence/bambu-preset-report.csv'。第二個路徑必須在設定副本之外。終端只輸出解析後的輸入路徑、報告路徑與列數;詳細證據在 CSV。把腳本與報告雜湊一併保存。

已審閱腳本為 4,697 位元組,SHA-256 是 4711e9c5db94afee575fc6dbbc2a206a29178eeae4ce17387db8a6159a21cf0。它在 CPython 3.12.14 測試中通過根預設、父項可解析、父項缺失與重複鍵損壞 JSON。測試證明有限分類器行為,不證明即時帳號、印表機或雲端相容性。

選擇修復前逐列閱讀

root-preset 表示未宣告父項;parent-found 表示至少一個系統預設名稱符合 inherits,需一起檢查 parent_paths 與 compatible_printers;parent-missing 表示使用者檔案存在但副本沒有對應父項;unreadable 保留路徑與錯誤,同時拒絕把損壞 JSON 當有效。

合併前先比較 account_scope。不同設定中同名預設可能位元組不同。SHA-256 只證明精確內容,不代表品質或相容性;除非路徑與雜湊支持有意合併,否則同名列應分開處理。

讓證據決定下一分支

若所需檔案存在且父項可解析,先檢查選定印表機/噴嘴與 Studio 是否隱藏不相容預設;看得到時優先匯出,不要編輯即時 JSON。若父項缺失,保留使用者檔案,只從與目前版本相符的可信來源修復系統設定層。

對 unreadable JSON,只審閱副本並與已知備份比較;不要重寫唯一證據。若沒有對應使用者檔案,稽核無法重建。只能依賴已知備份、仍可見的匯出或供應商支援的恢復方式。

明確完成標準與限制

當副本中每個使用者範圍的耗材 JSON 都有一列、CSV 位於副本外,且 parent-missing 與 unreadable 列都已審閱,證據階段才完成。修復成功另算:保全後再開 Studio,選定目標印表機/噴嘴並核對參數或匯入已驗證預設包。

本流程不判斷預設對材料或印表機是否安全、不檢查雲端同步,也不診斷所有歷史報告。它刻意在寫入即時設定前停止。若報告與畫面衝突,保留副本與 CSV 供支援重現。

原創編輯決策示意圖:依父項找到、父項缺失、無法讀取或檔案不存在選擇後續動作。
原創編輯示意圖:讓已保全的證據決定有限修復分支。 Watch at Ease