Files
osmWorkflow/blender/osmassets/tree.py
que01 a621ef743b feat: 接入 Poly Haven island_tree_01 真实扫描树模型
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>
2026-07-30 08:57:24 +08:00

103 lines
3.8 KiB
Python

"""Import Poly Haven island_tree_01 as instanced-tree asset.
This is the analog of load_tuft_variants() for trees. It appends only the
LOD1 meshes from the vendored .blend, which automatically pulls in their
material definitions and texture images. The top-level object parenting
(branch and leaf parts share one origin) is handled by the caller who
instances them as a single logical tree.
"""
import os
import bpy
TREE_BLEND = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "..",
"assets", "models", "polyhaven", "island_tree_01",
"island_tree_01_1k.blend",
)
# Branch / leaf LOD1 pairings for the four variants.
# Each pairing is a single logical tree that the caller instances as one.
VARIANT_PAIRS = [("a", "b"), ("b", "a"), ("c", "a"), ("d", "b")]
def load_tree_variants():
"""Import the vendored Poly Haven tree and return meshes ready to instance.
Returns a list of (branch_mesh, leaf_mesh) tuples, one per variant. The
meshes share the material datablocks that were appended alongside them, so
the textures and shader nodes are wired up automatically.
Returns [] when the source .blend is absent so a clean checkout still
builds. The caller falls back to procedural trees.
"""
if not os.path.exists(TREE_BLEND):
return []
before_objects = set(bpy.data.objects)
before_meshes = set(bpy.data.meshes)
# Gather the object names we need from the library.
wanted = []
for branch_id, leaf_id in VARIANT_PAIRS:
wanted.append(f"island_tree_01_branches_{branch_id}_LOD1")
wanted.append(f"island_tree_01_leaves_{leaf_id}_LOD1")
# bpy.ops.wm.append cannot handle a list of files in a single call
# reliably (it may crash when objects in the same library share data
# that was already linked by an earlier append in the same batch), so
# we append one at a time and skip the "already linked" warning.
for name in wanted:
try:
bpy.ops.wm.append(
filepath=TREE_BLEND + "/Object/" + name,
directory=TREE_BLEND + "/Object/",
files=[{"name": name}],
link=False,
)
except RuntimeError:
# Object already linked by an earlier append of a sibling mesh
# that shared materials; this is expected and harmless.
pass
imported = [obj for obj in set(bpy.data.objects) - before_objects
if obj.type == "MESH"]
# Pair branch + leaf into variants. We append in the order above so
# a simple zip works.
variant_meshes = []
for i in range(0, len(imported), 2):
if i + 1 >= len(imported):
break
# The import order is interleaved (branch, leaf, branch, leaf, …)
# because `wanted` alternated. Sort by name to be safe.
pass
# Rebuild the pairs from the imported object names.
variants = []
for branch_id, leaf_id in VARIANT_PAIRS:
b_obj = bpy.data.objects.get(
f"island_tree_01_branches_{branch_id}_LOD1")
l_obj = bpy.data.objects.get(
f"island_tree_01_leaves_{leaf_id}_LOD1")
if b_obj and l_obj:
# Copy the meshes so they survive object removal.
b_mesh = b_obj.data.copy()
b_mesh.name = "PolyHavenTree_Branch_" + branch_id
b_mesh.use_fake_user = True
l_mesh = l_obj.data.copy()
l_mesh.name = "PolyHavenTree_Leaf_" + leaf_id
l_mesh.use_fake_user = True
variants.append((b_mesh, l_mesh))
for obj in imported:
bpy.data.objects.remove(obj, do_unlink=True)
tris = sum(len(bm.loop_triangles) + len(lm.loop_triangles)
for bm, lm in variants)
print(f"Poly Haven tree variants loaded: {len(variants)} "
f"({tris} tris per instance)")
return variants