Repository navigation
Expand file tree
/
Copy pathblender_headless_import_runner.py
More file actions
200 lines (169 loc) · 7.99 KB
/
Copy pathblender_headless_import_runner.py
File metadata and controls
200 lines (169 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""Run a Halo tag import inside Blender (headless) and print structured logs.
This script is intended to be launched by Blender:
blender --background --factory-startup --addons io_scene_halo \
--python tests/blender_headless_import_runner.py -- --model-path <path>
"""
import argparse
import traceback
import addon_utils
import bpy
def _parse_args():
argv = []
if "--" in __import__("sys").argv:
argv = __import__("sys").argv[__import__("sys").argv.index("--") + 1 :]
parser = argparse.ArgumentParser(description="Run io_scene_halo model import in headless Blender")
parser.add_argument("--model-path", required=True, help="Absolute path to a .model/.hlmt tag")
parser.add_argument("--game-title", default="halo3", choices=["auto", "halo1", "halo2", "halo3"])
parser.add_argument("--addon-module", default="io_scene_halo")
parser.add_argument("--fix-rotations", action="store_true")
parser.add_argument("--empty-markers", action="store_true")
parser.add_argument(
"--h3-import-lightmap-textures",
action="store_true",
help="Enable the io_scene_halo h3_import_lightmap_textures pref for this run",
)
parser.add_argument(
"--h3-import-scripts",
action="store_true",
help="Enable the io_scene_halo h3_import_scripts pref for this run",
)
return parser.parse_args(argv)
def _ensure_addon_enabled(module_name):
enabled, loaded = addon_utils.check(module_name)
if enabled and loaded:
return
addon_utils.enable(module_name, default_set=False, persistent=False)
def _report(level, message):
level_name = "INFO"
if level:
level_name = next(iter(level))
print("operator | %s %s" % (level_name, message))
def main():
args = _parse_args()
print("headless_harness | START model=%s game=%s" % (args.model_path, args.game_title))
try:
_ensure_addon_enabled(args.addon_module)
from io_scene_halo.file_tag import import_tag
# Apply per-run pref overrides (CI-friendly; doesn't persist because
# the Blender process exits after import).
if args.h3_import_lightmap_textures:
try:
prefs = bpy.context.preferences.addons[args.addon_module].preferences
prefs.h3_import_lightmap_textures = True
print("headless_harness | PREF h3_import_lightmap_textures=True")
except Exception as pref_exc:
print("headless_harness | PREF_ERROR %s" % pref_exc)
if args.h3_import_scripts:
try:
prefs = bpy.context.preferences.addons[args.addon_module].preferences
prefs.h3_import_scripts = True
print("headless_harness | PREF h3_import_scripts=True")
except Exception as pref_exc:
print("headless_harness | PREF_ERROR_SCRIPTS %s" % pref_exc)
before_names = {obj.name for obj in bpy.data.objects}
result = import_tag.load_file(
bpy.context,
args.model_path,
args.game_title,
args.fix_rotations,
args.empty_markers,
_report,
)
after_names = {obj.name for obj in bpy.data.objects}
created_names = sorted(after_names - before_names)
proxy_created = "proxy_render_model" in after_names
print("headless_harness | OBJECTS_CREATED count=%s proxy=%s" % (len(created_names), proxy_created))
if created_names:
print("headless_harness | OBJECT_NAMES %s" % ", ".join(created_names[:20]))
# Per-collection counts (objects contained, recursive within a single collection; kept flat per bpy collection).
for coll in sorted(bpy.data.collections, key=lambda c: c.name):
try:
count = len(coll.objects)
except Exception:
count = -1
print("headless_harness | COLLECTION name=%r count=%s" % (coll.name, count))
# H3 lightmap-texture images (Image datablocks whose name matches
# ``<bsp>_(lightprobe|dominant_intensity)_NN``). Always emitted so
# regression tests can assert 0 when the pref is off.
import re as _re
lightmap_image_re = _re.compile(r"_(?:lightprobe|dominant_intensity)_\d{2}$")
lightmap_images = sorted(
img.name for img in bpy.data.images if lightmap_image_re.search(img.name)
)
print("headless_harness | LIGHTMAP_IMAGES count=%d" % len(lightmap_images))
if lightmap_images:
print("headless_harness | LIGHTMAP_IMAGE_NAMES %s" % ", ".join(lightmap_images[:10]))
# H3 lightmap wiring: objects tagged with the ``h3_lightmap_*``
# custom props and materials carrying the ``h3_lightmap_slice``
# marker. Split by custom-prop kind so regression tests can
# assert clusters vs instances independently.
wired_cluster_objs = 0
wired_instance_objs = 0
for obj in bpy.data.objects:
try:
idx = obj.get("h3_lightmap_texture_index")
except Exception:
idx = None
if idx is None:
continue
if obj.name.startswith("cluster_"):
wired_cluster_objs += 1
else:
wired_instance_objs += 1
lightmap_material_variants = sum(
1 for mat in bpy.data.materials if mat.get("h3_lightmap_slice") is not None
)
meshes_with_lightmap_uv = sum(
1 for mesh in bpy.data.meshes if mesh.uv_layers.get("UVMap_Lightmap") is not None
)
print("headless_harness | LIGHTMAP_WIRED_CLUSTERS count=%d" % wired_cluster_objs)
print("headless_harness | LIGHTMAP_WIRED_INSTANCES count=%d" % wired_instance_objs)
print("headless_harness | LIGHTMAP_MATERIAL_VARIANTS count=%d" % lightmap_material_variants)
print("headless_harness | MESHES_WITH_LIGHTMAP_UV count=%d" % meshes_with_lightmap_uv)
# H3 halogram emission check: materials whose name contains
# "light_volume" (H3EK's shipping hologram materials for light
# volumes / holograms) should land with a non-zero Principled
# BSDF Emission Strength when shader_halogram routing is live.
light_volume_emissive = 0
light_volume_total = 0
for mat in bpy.data.materials:
if "light_volume" not in mat.name.lower():
continue
light_volume_total += 1
try:
if not mat.use_nodes or mat.node_tree is None:
continue
for node in mat.node_tree.nodes:
if node.bl_idname != "ShaderNodeBsdfPrincipled":
continue
strength = node.inputs.get("Emission Strength")
if strength is not None and float(strength.default_value) > 0.0:
light_volume_emissive += 1
break
except Exception:
pass
print("headless_harness | LIGHT_VOLUME_MATERIALS total=%d emissive=%d" % (
light_volume_total, light_volume_emissive,
))
# Phase L — cluster portals (sbsp) and script-text datablocks.
portal_objects = [
obj for obj in bpy.data.objects
if "front_cluster" in obj.keys() and "back_cluster" in obj.keys()
]
print("headless_harness | CLUSTER_PORTALS count=%d" % len(portal_objects))
script_texts = sorted(
t.name for t in bpy.data.texts
if t.name.startswith("h3_script_") or t.name.startswith("h3_source_")
)
print("headless_harness | SCRIPT_TEXTS count=%d" % len(script_texts))
if script_texts:
print("headless_harness | SCRIPT_TEXT_NAMES %s" % ", ".join(script_texts[:10]))
print("headless_harness | RESULT %s" % result)
except Exception as exc:
print("headless_harness | EXCEPTION %s" % exc)
traceback.print_exc()
raise
finally:
print("headless_harness | END")
if __name__ == "__main__":
main()