""" Kazah Tender materials (module of the import runner, see import_assets.py). Needs SourceArt/Kazah/kazah_manifest.json (Tools/export_kazah.ps1 makes it). Always rebuilds the cheap things (material graphs and instances) so they cannot drift from the manifest; that is safe to repeat. What it makes (all under /Game/Vehicles/Kazah/Materials): MI_ one instance per Blender material (14), parameters from the manifest M1 (DESIGN_UE.md section 7.9): the instances are parented to the game's masters /Game/Materials/M_DescentSurface (AmbientMix 0) and /Game/Materials/M_DescentGlass, built by the look track's helpers (Tools/ue/_helpers_look.py), so the Tender gets the water ambient term and the same glass as the rest of the game. The two M0 masters, M_Kazah_Opaque and M_Kazah_Glass, are deleted. Instance names and paths are unchanged. Leaves `ctx.shared["kazah_instances"]` = {blender material name: MaterialInstanceConstant} for the mesh module. """ import importlib.util import json import os import unreal GAME_DIR = "/Game/Vehicles/Kazah" MAT_DIR = GAME_DIR + "/Materials" # Blender's emission strength is in "watts per square metre"-ish units; in Unreal the emissive value is a # luminance multiplier. These factors make the exported glow read the same in a dark scene. EMISSIVE_SCALE = {"lens": 9.0, "emissive": 6.0} def build_material_instances(ctx, manifest, opaque, glass): """One MaterialInstanceConstant per Blender material, parameters straight from the manifest.""" MEL, EAL = ctx.mel, ctx.eal instances = {} for name, m in manifest["materials"].items(): mi_name = "MI_" + name[3:] if name.startswith("KZ_") else "MI_" + name kind = m["kind"] parent = glass if kind == "glass" else opaque mi = ctx.get_or_create(MAT_DIR, mi_name, unreal.MaterialInstanceConstant, unreal.MaterialInstanceConstantFactoryNew()) mi.set_editor_property("parent", parent) bc = m["base_color"] MEL.set_material_instance_vector_parameter_value(mi, "BaseColor", unreal.LinearColor(bc[0], bc[1], bc[2], 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "Roughness", float(m["roughness"])) if kind == "glass": pass # opacity / fresnel / IOR come from the master's defaults else: MEL.set_material_instance_scalar_parameter_value(mi, "Metallic", float(m["metallic"])) strength = float(m["emissive_strength"]) * EMISSIVE_SCALE.get(kind, 0.0) ec = m["emissive_color"] if strength > 0.0 else [0.0, 0.0, 0.0] MEL.set_material_instance_vector_parameter_value(mi, "EmissiveColor", unreal.LinearColor(ec[0], ec[1], ec[2], 1.0)) MEL.set_material_instance_scalar_parameter_value(mi, "EmissiveStrength", strength if strength > 0.0 else 0.0) MEL.update_material_instance(mi) EAL.save_loaded_asset(mi) instances[name] = mi ctx.log("material %-28s -> %s (%s)" % (name, mi_name, kind)) return instances def _look_helpers(ctx): spec = importlib.util.spec_from_file_location("descent_helpers_look", os.path.join(ctx.project_dir, "Tools", "ue", "_helpers_look.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def run(ctx): manifest_path = os.path.join(ctx.project_dir, "SourceArt", "Kazah", "kazah_manifest.json") if not os.path.isfile(manifest_path): raise RuntimeError("no manifest at %s - run Tools/export_kazah.ps1 first" % manifest_path) with open(manifest_path, "r", encoding="utf-8") as f: manifest = json.load(f) for d in (GAME_DIR, MAT_DIR): ctx.eal.make_directory(d) h = _look_helpers(ctx) mpc = h.ensure_mpc(ctx) opaque = h.ensure_surface_master(ctx, mpc) glass = h.ensure_glass_master(ctx) ctx.shared["kazah_instances"] = build_material_instances(ctx, manifest, opaque, glass) # The M0 masters are no longer used by anything: delete them (instances were re-parented above). for old in ("M_Kazah_Opaque", "M_Kazah_Glass"): path = MAT_DIR + "/" + old if ctx.eal.does_asset_exist(path): ctx.eal.delete_asset(path) ctx.log("deleted the M0 master " + path)