remove_nodes.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. #!/usr/bin/env python3
  2. """从 GLB 中删除指定节点,并清理不再使用的网格/accessor,输出更小的文件。
  3. 支持三种定位方式(可组合):
  4. --index N 节点下标(0 起,对应文件里 nodes 数组的顺序)
  5. --label NAME 查看器里显示的标签(同名部件按 three.js 规则:
  6. 第一个保留原名,其后依次 _1、_2…)
  7. --name NAME 原始名称精确匹配,删除所有同名节点
  8. 用法:
  9. python3 viewer/tools/remove_nodes.py --input a.glb --inplace --label 三维立体
  10. python3 viewer/tools/remove_nodes.py --input a.glb --output b.glb --index 0 12
  11. """
  12. import argparse, os, sys
  13. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  14. from split_glb import read_glb, BandBuilder, world_matrices
  15. def viewer_labels(gltf):
  16. """按 three.js GLTFLoader.createUniqueName 的规则,为每个节点算出显示标签。"""
  17. used = {}
  18. labels = {}
  19. for i, n in enumerate(gltf["nodes"]):
  20. nm = n.get("name", "")
  21. if not nm:
  22. continue
  23. if nm in used:
  24. used[nm] += 1
  25. labels[i] = f"{nm}_{used[nm]}"
  26. else:
  27. used[nm] = 0
  28. labels[i] = nm
  29. return labels
  30. def main():
  31. ap = argparse.ArgumentParser()
  32. ap.add_argument("--input", required=True)
  33. ap.add_argument("--output")
  34. ap.add_argument("--inplace", action="store_true")
  35. ap.add_argument("--index", type=int, nargs="*", default=[])
  36. ap.add_argument("--label", nargs="*", default=[])
  37. ap.add_argument("--name", nargs="*", default=[])
  38. args = ap.parse_args()
  39. src, src_bin = read_glb(args.input)
  40. labels = viewer_labels(src)
  41. drop = set(args.index)
  42. for i, lb in labels.items():
  43. if lb in args.label:
  44. drop.add(i)
  45. for i, n in enumerate(src["nodes"]):
  46. if n.get("name", "") in args.name:
  47. drop.add(i)
  48. if not drop:
  49. print("没有匹配到要删除的节点,未做改动。")
  50. return
  51. print(f"将删除 {len(drop)} 个节点:")
  52. for i in sorted(drop):
  53. n = src["nodes"][i]
  54. print(f" node{i}: name={n.get('name','')!r} label={labels.get(i,'')!r} mesh={n.get('mesh')}")
  55. world = world_matrices(src)
  56. b = BandBuilder(src)
  57. b.src_bin = src_bin
  58. kept = 0
  59. for i, n in enumerate(src["nodes"]):
  60. if i in drop:
  61. continue
  62. if "mesh" not in n:
  63. continue
  64. # 节点在原文件里是多层嵌套的,这里把世界变换烘焙进节点后挂到根上,
  65. # 避免仅复制局部变换导致部件错位。
  66. b.add_verbatim_node(i, world[i])
  67. kept += 1
  68. out = args.output or (args.input if args.inplace else args.input + ".removed.glb")
  69. if args.inplace:
  70. tmp = out + ".tmp"
  71. b.finish(tmp)
  72. os.replace(tmp, out)
  73. else:
  74. b.finish(out)
  75. print(f"保留 {kept} 个节点 -> {out} ({os.path.getsize(out)/1048576:.1f} MB)")
  76. if __name__ == "__main__":
  77. main()