""" Writes the interior as a placement list for Unreal: models/vessel-interior/vessel_interior_layout.json. blender -b --factory-startup models/M_Ship_v2_interior_season2.blend --python build/export_layout.py [-- out.json] It reads the .blend as it is, so pieces moved or added by hand are exported too, as long as they are instances of kit meshes (mesh datablock with ["ue_mesh"]) inside "10 INTERIOR | Season 2 layout". instances one per kit mesh object: Unreal asset path, matrix, per-instance material overrides, zone decals the pack's lettering: Unreal decal material, matrix, decal size lights the demo rooms' lights with their original Unreal values; the lamps added for the dock room and the bridge markers named points for game use (fixtures the game places itself) own the parts made for this hull (bulkheads, gallery, bridge deck, pylon), the stair and the carousel by name: they are not kit assets and travel with the ship model, not with this list Matrices are Unreal's, in the vessel's own frame: cm, left-handed, four rows (X axis, Y axis, Z axis, origin); Unreal +X = bow, +Y = starboard, +Z = up, i.e. (x, -y, z) * 100 of the Blender ship frame. """ import json import math import os import sys import bpy sys.dont_write_bytecode = True HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from bl_kit import ROOT, matrix_to_ue_rows # noqa: E402 from units import LIGHT_FIX # noqa: E402 ARGS = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] OUT = ARGS[0] if ARGS else os.path.join(ROOT, "vessel_interior_layout.json") TOP = "10 INTERIOR | Season 2 layout" def r(rows): return [[round(v, 4) for v in row] for row in rows] def main(): top = bpy.data.collections.get(TOP) if top is None: raise RuntimeError("no collection %r in this file" % TOP) out = { "name": "Vessel interior (M-Ship v2), Season 2 layout with Season 1 mixed in", "source": "Modular Scifi Season 2 Starter Bundle (/Game/ModSciInteriors, /Game/ModSci_Engineer, /Game/ModSci_EngiProps) and " "Modular SciFi Season 1 Starter Bundle (/Game/StarterBundle)", "units": "Unreal: cm, left-handed. Matrices are 4 rows: X axis, Y axis, Z axis, origin. Vessel frame: +X bow, +Y starboard.", "instances": [], "decals": [], "lights": [], "markers": [], "own": [], } fix_inv = LIGHT_FIX.inverted() used = {} zones = {} for ob in sorted(top.all_objects, key=lambda o: o.name): zone = ob.get("zone", "") z = zones.setdefault(zone, [0, 0, 0]) if ob.name.startswith("MARKER | "): loc = ob.matrix_world.translation out["markers"].append({"name": ob.name[9:], "role": ob.get("ue_role", ""), "location": [round(loc.x * 100.0, 2), round(-loc.y * 100.0, 2), round(loc.z * 100.0, 2)]}) elif ob.type == "MESH" and ob.get("ue_decal_material"): out["decals"].append({"name": ob.name, "material": ob["ue_decal_material"], "size": list(ob.get("ue_decal_size", [1, 1, 1])), "matrix": r(matrix_to_ue_rows(ob.matrix_world)), "zone": zone}) z[1] += 1 elif ob.type == "MESH" and ob.data.get("ue_mesh"): rec = {"name": ob.name, "mesh": ob.data["ue_mesh"], "matrix": r(matrix_to_ue_rows(ob.matrix_world)), "zone": zone} over = {str(i): s.material["ue_material"] for i, s in enumerate(ob.material_slots) if s.link == "OBJECT" and s.material is not None and s.material.get("ue_material")} if over: rec["materials"] = over if ob.get("ue_role"): rec["role"] = ob["ue_role"] out["instances"].append(rec) used[rec["mesh"]] = used.get(rec["mesh"], 0) + 1 z[0] += 1 elif ob.type == "LIGHT": m = ob.matrix_world @ fix_inv if ob.get("ue_light") else ob.matrix_world rec = {"name": ob.name, "class": ob.get("ue_light_class", "PointLightComponent"), "matrix": r(matrix_to_ue_rows(m)), "zone": zone} if ob.get("ue_light"): rec["ue"] = json.loads(ob["ue_light"]) else: rec["blender_watts"] = round(ob.data.energy, 1) rec["color"] = [round(c, 3) for c in ob.data.color] out["lights"].append(rec) z[2] += 1 elif ob.type == "MESH" and not ob.data.name.startswith("DECAL_"): out["own"].append({"name": ob.name, "mesh": ob.data.name, "matrix": r(matrix_to_ue_rows(ob.matrix_world))}) s1n = sum(1 for i in out["instances"] if i["mesh"].startswith("/Game/StarterBundle/")) out["summary"] = {"instances": len(out["instances"]), "season1_instances": s1n, "season2_instances": len(out["instances"]) - s1n, "decals": len(out["decals"]), "lights": len(out["lights"]), "markers": len(out["markers"]), "own_parts": len(out["own"]), "distinct_meshes": len(used), "zones": {k: {"meshes": v[0], "decals": v[1], "lights": v[2]} for k, v in sorted(zones.items()) if any(v)}} out["meshes_used"] = dict(sorted(used.items())) with open(OUT, "w", encoding="utf-8") as f: json.dump(out, f, indent=1) print("[layout] wrote %s: %s" % (OUT, json.dumps(out["summary"]))) main()