tree_style 新增 'polyhaven' 选项,用 wm.append 从 vendored .blend 加载 LOD1 网格(4 种 branch+leaf 组合),通过实例化放置场景中的树。 每棵树约 625 tris / 2 meshes (branch+leaf),比旧 procedural tree (~400 tris) 多了约 60%,但换来真实树形和 PBR 贴图,树叶有 alpha 透明度,在 Cesium 远距离比纯几何球体更可读。 纹理从 ~/Downloads 手动下载(Poly Haven CDN 的 API URL 不可用), 存放在 assets/models/polyhaven/island_tree_01/textures/(不入 git)。 默认 tree_style 仍为 'natural',parity 保持不变。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
139 lines
4.1 KiB
Python
139 lines
4.1 KiB
Python
"""Import Poly Haven island_tree_01 and export decimated GLBs.
|
|
|
|
Each output GLB contains exactly one tree variant (1 branch mesh + 1 leaf
|
|
mesh) with shared material references so the main generator can instance them
|
|
the same way it instances grass tufts.
|
|
"""
|
|
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
TREE_BLEND = os.path.join(
|
|
os.path.dirname(__file__), "..", "..",
|
|
"assets", "models", "polyhaven", "island_tree_01",
|
|
"island_tree_01_1k.blend",
|
|
)
|
|
|
|
TARGET_BRANCH_TRIS = 180
|
|
TARGET_LEAF_TRIS = 220
|
|
|
|
PAIRS = [("a", "b"), ("b", "a"), ("c", "a"), ("d", "b")]
|
|
|
|
|
|
def cli_args():
|
|
values = {"out": None}
|
|
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
|
i = 0
|
|
while i < len(argv):
|
|
if argv[i].startswith("--") and i + 1 < len(argv):
|
|
values[argv[i][2:]] = argv[i + 1]
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
if not values.get("out"):
|
|
raise RuntimeError("--out is required")
|
|
return values
|
|
|
|
|
|
def decimate_to_target(obj, target_tris):
|
|
source = obj.data
|
|
source.calc_loop_triangles()
|
|
source_tris = len(source.loop_triangles)
|
|
if source_tris <= target_tris:
|
|
return source.copy()
|
|
modifier = obj.modifiers.new("TreeDecimate", "DECIMATE")
|
|
modifier.ratio = max(0.05, target_tris / source_tris)
|
|
bpy.context.view_layer.update()
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
evaluated = obj.evaluated_get(depsgraph)
|
|
result = bpy.data.meshes.new_from_object(evaluated)
|
|
result.calc_loop_triangles()
|
|
return result
|
|
|
|
|
|
def export_one(out_path, b_mesh, l_mesh, materials):
|
|
"""Export a single tree variant to a clean GLB.
|
|
|
|
Works in a fresh, empty scene so nothing from the source blend leaks in.
|
|
"""
|
|
scene = bpy.data.scenes.new("_TreeIngest")
|
|
bpy.context.window.scene = scene
|
|
col = bpy.data.collections.new("_Tree")
|
|
scene.collection.children.link(col)
|
|
|
|
b_obj = bpy.data.objects.new("Branch", b_mesh)
|
|
l_obj = bpy.data.objects.new("Leaf", l_mesh)
|
|
col.objects.link(b_obj)
|
|
col.objects.link(l_obj)
|
|
|
|
b_obj.data.materials.append(materials["branch"])
|
|
l_obj.data.materials.append(materials["leaf"])
|
|
l_obj.data.materials.append(materials["branch"])
|
|
|
|
scene.view_layers[0].update()
|
|
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=out_path,
|
|
export_format="GLB",
|
|
use_selection=False,
|
|
export_apply=False,
|
|
export_texcoords=True,
|
|
export_normals=True,
|
|
export_materials="EXPORT",
|
|
export_image_format="JPEG",
|
|
)
|
|
|
|
bpy.data.scenes.remove(scene, do_unlink=True)
|
|
|
|
|
|
def main():
|
|
args = cli_args()
|
|
out_dir = args["out"]
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
|
|
if not os.path.exists(TREE_BLEND):
|
|
print(f"Missing: {TREE_BLEND}")
|
|
return 1
|
|
|
|
bpy.ops.wm.open_mainfile(filepath=TREE_BLEND)
|
|
|
|
branch_mat = bpy.data.materials.get("island_tree_01_branches")
|
|
leaf_mat = bpy.data.materials.get("island_tree_01_leaves")
|
|
if not branch_mat or not leaf_mat:
|
|
return 1
|
|
|
|
decimated = []
|
|
for vi, (branch_id, leaf_id) in enumerate(PAIRS):
|
|
branch_obj = bpy.data.objects.get(
|
|
f"island_tree_01_branches_{branch_id}_LOD1")
|
|
leaf_obj = bpy.data.objects.get(
|
|
f"island_tree_01_leaves_{leaf_id}_LOD1")
|
|
if not branch_obj or not leaf_obj:
|
|
continue
|
|
|
|
b_mesh = decimate_to_target(branch_obj, TARGET_BRANCH_TRIS)
|
|
l_mesh = decimate_to_target(leaf_obj, TARGET_LEAF_TRIS)
|
|
decimated.extend([b_mesh, l_mesh])
|
|
|
|
out_path = os.path.join(out_dir, f"tree_variant_{vi:02d}.glb")
|
|
export_one(out_path, b_mesh, l_mesh,
|
|
{"branch": branch_mat, "leaf": leaf_mat})
|
|
kb = os.path.getsize(out_path) // 1024
|
|
print(f" variant_{vi:02d}: branch={len(b_mesh.loop_triangles)}tris "
|
|
f"leaf={len(l_mesh.loop_triangles)}tris ({kb}KB)")
|
|
|
|
for mesh in decimated:
|
|
if mesh and mesh.name in bpy.data.meshes:
|
|
bpy.data.meshes.remove(mesh, do_unlink=True)
|
|
|
|
print(f"\nDone — {len(PAIRS)} variants → {out_dir}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|