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,选择目标打印机/喷嘴,并核对精确参数或导入已验证的预设包。

本流程不判断某预设对材料或打印机是否安全,不检查云同步,也不诊断所有历史求助。它有意在写入实时配置前停止。若报告与 Studio 显示冲突,向支持提交完整副本与 CSV;路径、哈希、父项状态和版本比空菜单截图更可复现。

原创编辑决策示意图:根据父项可找到、父项缺失、无法读取或文件不存在选择后续动作。
原创编辑示意图:让已保全的证据决定有限的修复分支。 Watch at Ease