A fork of General_101/Halo-Asset-Blender-Development-Toolset, the seminal Blender add-on for authoring assets across the Halo title family. All of the upstream import/export surface (JMS, JMA, ASS, QUA, JMI, WRL, H1/H2 tag imports, cinematic cameras) is preserved — please support General_101 on Ko-Fi if this work is useful to you.
This README documents what's new in the fork. For the full baseline feature matrix, upstream credits, and per-game support table, see the upstream README.
The fork focuses on four themes the upstream add-on does not cover (or treats as partial):
- Halo 3 scenario import end-to-end — upstream lists Halo 3
structure_bsp/scenariotag import as "No Support". This fork ships a working H3.scenarioimporter, plus every tag adapter the scenario walker needs. - Blender 5.0 compatibility — silent regressions on Blender 5 (all-black materials, icon changes, auto-smooth API removal) are patched.
- Headless testing & regression coverage — a subprocess-based Blender harness plus a pytest suite exercises real shipping H3EK scenarios on every change.
- Reliability over silence — silent decode failures in H3 geometry and tag-layout parsing now surface as warnings instead of producing invisible meshes.
- New H3 tag adapters under io_scene_halo/file_tag/h3/:
file_scenario,file_scenario_structure_bsp,file_scenario_structure_lighting_info(stli),file_scenario_lightmap_bsp_data(Lbsp), plus object palette parsers for scenery, bipeds, vehicles, machines, controls, sound scenery, equipment, weapons, and crates. - BSP cluster + instanced-geometry decode (compressed vertices, strip indices, winding fix-up by
axis_flipsbucket), cluster portals, decal palette with tag-driven world radius, Tier 1 blocks (player starting profiles, editor folders, cubemaps, airprobes, soft ceilings/surfaces, effect scenery, decorators, flocks, recorded animations), Tier 2 MP sandbox blocks (mv *equipment/weapons/vehicles/scenery/goals/spawners/teleporters, spawn data, zone sets, scenario cluster data), and opt-in HSC script import. - Real-scenario verified:
guardian(MP) and130_epilogue(campaign) land in Blender with thousands of objects, per-BSP collections, proper decal orientation, and object hierarchies from the H3 hlmt → render_model pipeline.
- Native H3 shader-tag parameter resolution (
shad,stem,rmfl,rmhg,rmtr,rmcs,rmcu,rmd,rmdc,rmfo,rmsk,rmw,rmwt) bypassing upstream's H2-only shader codepath. tool.exe export-bitmap-tgarouting with result caching, shader-collection prefix map driven bylevels\shader_collections.txt.- Halogram materials detected and wired through the Principled BSDF Emission socket so holograms read as self-lit.
- H1:
shader_environmentandshader_modelreflection cube-map bitmaps are now unfolded into an equirectangular panorama at import in io_scene_halo/global_functions/shader_generation/shader_helper.py. All three Halo color-plate cube layouts are supported (4×3 horizontal cross, 6×1 horizontal strip, 1×6 vertical strip), projected with standard DirectX cube-map math and cached as<bitmap>_equirectinbpy.data.images. TheShaderNodeTexEnvironmentfeeding the reflection input is now driven byShaderNodeTexCoord.Reflectionin shader_environment.py (matchingshader_model.py), so the cube map samples as a real reflection vector instead of a flat lookup. Upstream produced a smeared/averaged tint because the raw cross plate was interpreted as an equirectangular image with no reflection direction. - H2: halo_2_shader.py now detects bitmap parameters whose backing
.bitmaptag hasbitmap_type == cube_mapand routes them through the same equirectangular unfold +TexEnvironment+ reflection-vector path. When the shader'senv_tint_color/env_glancing_tint_color(+env_brightness/env_glancing_brightness) parameters are present the sample is modulated by the existingReflection Tint Logicfresnel group, matching H1's perpendicular/parallel tint shape. Non-cube bitmaps keep their existing UV-scaled path unchanged. - H3 (opt-in): a new add-on preference
h3_import_reflection_cubemapsdrives shader_processing.py to resolve each material'senvironment_map/env_map/reflection_map/reflection_cube_mapparameter via io_scene_halo/file_tag/h3/container.py, export all six cube faces throughtool.exe export-bitmap-tga, stitch them into an equirect image (Pillow + numpy), and wire the sample into Principled BSDFBase Colorvia an additive mix after the existing base-map chain. Fresnel tint usesenv_tint_color/env_glancing_tint_colorwithenv_brightness/env_glancing_brightness. Materials whose.bitmaptag isn't a cube-map type, or whose TGA export returns fewer than six faces, silently skip reflection wiring with a print log. - H3 caveats:
- Off by default because it is slow.
tool.exe export-bitmap-tgais invoked once per environment-mapped material and writes six DXT-decoded TGAs per cube map. On a full scenario import with many reflective shaders the first run can add tens of seconds to minutes; the_run_tool_export_cachedwrapper skips re-export on subsequent imports when the source.bitmaptag hasn't changed, so steady-state cost is low. - Real-time Blender light probes are not used. The empties spawned by
generate_h3_cubemapsatcubemap positionin the scenario remain decorative — they are not converted to BlenderLIGHT_PROBE_CUBEobjects and Eevee/Cycles do not sample them. Every H3 reflection is instead a per-material baked equirect of the shader's tag-referenced cube bitmap, which gives a consistent look across Eevee and Cycles but loses the game's per-cluster probe placement. Converting those empties to real light probes (Eevee Next only) is tracked in backlog.md as a future pass.
- Off by default because it is slow.
- Lbsp (
.scenario_lightmap_bsp_data) parsing: lightprobe texture + dominant-light-intensity bitmap export via tool.exe, plus per-cluster and per-instance atlas-slice indices. UVMap_LightmapUV layer built on cluster and instanced-geometry meshes (1:1 by index when vertex counts match, otherwise KDTree nearest-neighbor remap to handle lightmap-unwrap seam splits).- Per-cluster material variants splicing a
ShaderNodeMixRGB(MULTIPLY)between the base color and the Principled BSDF, keyed on(bsp, slice)so clusters sharing a slice reuse one variant. - Gated behind a
h3_import_lightmap_texturesadd-on preference; default is off because tool.exe DXT5-array export is slow.
stli(.scenario_structure_lighting_info) parsing drives Blender Point / Spot / Sun lights with correct axis remap (Halo +X → Blender −Z, Halo +Z → Blender +Y, Halo +Y → Blender +X) and world-scale parity with the sbsp pipeline.- Spot cone angle and blend derived from falloff / hotspot; per-BSP
*_stli_lightscollections keep the outliner organized.
- Image pack/colorspace ordering fix in io_scene_halo/global_functions/shader_generation/shader_helper.py — on Blender 5, setting
colorspace_settings.namebeforeimage.pack()silently discards the pixel buffer and produces all-black H1/H2 materials. Fixed by packing first. mesh.use_auto_smoothaccess ishasattr-gated on the H3 build-mesh path; custom split normals are still applied vianormals_split_custom_set_from_vertices.- Deprecated
icon='SEARCH'migrated toicon='VIEWZOOM'in add-on preferences.
- Patched
io_scene_halo/_vendor/pytagfilelayout/tag_data_reader.pyto deep-merge inline-parsed scalars with chunked sub-struct results. The upstream vendored library silently clobbered inline fields whenever a struct declared both inline scalars and chunked children — which is every H3scenario_object_datum_struct(position, rotation, placement flags, scale). Without this fix, H3 object transforms decode as zeros everywhere.
- io_scene_halo/file_tag/h3/file_scenario_structure_bsp/ and the shared mesh decoder now emit
WARNINGreports (with cluster / IGD / instance context) for empty render geometry, missing mesh indices, or instance definitions that point out of range, instead of silently dropping geometry. - io_scene_halo/file_tag/build_scene/build_bsp.py bounds-checks mesh lookups before indexing (Python negative indexing used to silently wrap).
- tests/headless_import_harness.py + tests/blender_headless_import_runner.py launch Blender in background mode, run an import, and stream
WARNING/ERROR/HEADLESS_HARNESSsignal lines plus per-collection object counts. - tests/test_h3_scenario_import.py parametrizes regression cases across
guardian.scenario(MP) and130_epilogue.scenario(campaign), auto-locates H3EK under the default Steam path (or viaH3EK_ROOT), and gates lightmap / scripts tests on the corresponding prefs. - JMS parser unit tests, sorting tests, and integration smoke tests run through
pytestvia run_tests.py; see tests/README.md.
- Cross-platform path joins in io_scene_halo/misc/generate_level.py (was hard-coded to Windows separators).
- Missing-asset paths now raise an explanatory
FileNotFoundErrorlisting which games ship blend assets and what to add, instead of Blender's crypticfailed to open blend file.
- A labelled Halo 3 Import Options section in the add-on preferences surfaces
h3_import_lightmap_texturesandh3_import_scriptstoggles, instead of burying them under the MCC tag-path block.
The shared mesh-construction paths in io_scene_halo/global_functions/mesh_processing.py and io_scene_halo/file_tag/build_scene/generate_h3_scenario.py were rewritten around numpy + Blender's foreach_get / foreach_set bulk-transfer API:
- Per-loop UV, lightmap UV, vertex color, material-index, and region-attribute buffers are allocated as contiguous
np.zeros/np.emptyarrays (float32/int32) and streamed into mesh datablocks in a single C-level call per layer. The previous per-loop / per-polygon Python attribute assignment — the dominant cost on large H3 BSPs — is gone. mesh.from_pydata(...)is followed by a singlepolygons.foreach_set("use_smooth", ...)and, when normals are authored,normals_split_custom_set_from_verticesfed from numpy. No per-polygon Python loops.- H3 lightmap UV remap uses numpy-backed KDTree inputs (
mesh.vertices.foreach_get("co", ...)→ reshape → query) instead of Python-side vector lists, so seam-split cluster meshes process in one pass. - Shader pixel buffers in io_scene_halo/global_functions/shader_generation/shader_helper.py convert PIL images via
np.asarray(...).ravel()and hand the flatfloat32buffer directly toimage.pixels.foreach_set, avoiding the Python-level row-by-row copy.
Net effect on a real H3EK guardian.scenario import (thousands of clusters + instanced geometry + object palette meshes): BSP mesh construction is the fast path, no longer the bottleneck. Scenario walk + shader resolution now dominate the profile.
Everything upstream continues to work: JMS / JMA / ASS / QUA / JMI export and import for Halo CE, Halo 2, and the MCC classic variants; WRL importing; H1 and H2 tag importers for models, collision, physics, animations, camera tracks, BSPs, lightmaps, scenarios, and selected shader groups; Halo Reach and Halo 4 cinematic cameras. Refer to the upstream README for the authoritative per-game feature matrix and the upstream contributor credits.
Out of scope for this fork (see backlog.md for details): H3 animation (antr / jmad) import, H3 physics (phmo) import, and animation inside H3 .scenario import.
- backlog.md — deferred items with scope notes (H2 scenario material dedup, H3 JMS+JMA import gaps).
- tests/README.md — test layout and how to invoke the headless harness.
- run_tests.py — top-level pytest entry point.
Important — migrating from the upstream legacy add-on. This fork is repackaged as a Blender 4.2+ extension, not a legacy
scripts/addonsadd-on. If you previously installed the upstreamio_scene_haloby dropping its folder into%APPDATA%\Blender Foundation\Blender\<version>\scripts\addons\(or the equivalent on macOS / Linux), you must remove that copy before installing the extension. Leaving it in place producesAdd-on not loaded: "io_scene_halo", cause: No module named 'io_scene_halo'errors at startup and can shadow preferences lookups.Locations to clear (per Blender version directory):
- Windows:
%APPDATA%\Blender Foundation\Blender\<version>\scripts\addons\io_scene_halo- macOS:
~/Library/Application Support/Blender/<version>/scripts/addons/io_scene_halo- Linux:
~/.config/blender/<version>/scripts/addons/io_scene_haloAlso open Edit → Preferences → Add-ons, search for
halo, and click Remove on any legacy entries shown as "Add-on not loaded".
- Grab
dist/io_scene_halo-<version>.zipfrom this repo (or build it — see below). - In Blender, open Edit → Preferences → Get Extensions.
- Click the dropdown arrow next to the "Get Extensions" heading → Install from Disk… and pick the zip.
- Alternatively: drag the zip directly onto a running Blender 4.2+ window.
- Blender installs under
%APPDATA%\Blender Foundation\Blender\<version>\extensions\user_default\io_scene_halo\(or the OS equivalent). Pillow and lxml wheels bundled in the zip are unpacked automatically. - Open Edit → Preferences → Add-ons, find Halo Asset Blender Development Toolset, expand it, and set your tag/data paths (or click the per-game Auto-Detect buttons for MCC / Halo 2 Vista / H1 CE installs under Steam).
- Edit → Preferences → Get Extensions → (find the entry) → dropdown → Uninstall, then reinstall the new zip.
- The extension's preferences (tag paths, H3 import toggles) are stored per-Blender-version; reinstalling the same major version preserves them.
Run build_extension.py from the repo root to produce a Blender 4.2+/5 extension zip at dist/io_scene_halo-<version>.zip:
python build_extension.py --cleanThe script reads the version from bl_info, downloads Pillow and lxml wheels for Windows / macOS / Linux into build/wheels/, writes a blender_manifest.toml at the zip root, and packages everything in extension format. Pass --skip-wheels to re-use the wheel cache between builds and --no-wheels to omit dependency bundling entirely.
- External references preserved from upstream: C20 docs and H2Codez docs.
This repository is a fork of General_101's Halo-Asset-Blender-Development-Toolset. The upstream add-on is the foundation for everything here — JMS/JMA/ASS/QUA toolchains, H1/H2 tag import infrastructure, the build-scene pipeline this fork extends to H3. Please support General_101 at ko-fi.com/general101. The full contributor list (Cyboryxmen, Aerial Dave, conscars, MercyMoon, Fulsy, mosesofegypt, num0005, kornman00, the Sigmmma crew, chiefster_4014, dogbrainludus, meowsandstuff, JackOfChaos, and general_101 himself) is preserved in the upstream README credits section.
GPL, unchanged from upstream — see the upstream LICENSE.