"""Armory Mesh Exporter""" # # Based on Open Game Engine Exchange # https://opengex.org/ # Export plugin for Blender by Eric Lengyel # Copyright 2015, Terathon Software LLC # # This software is licensed under the Creative Commons # Attribution-ShareAlike 3.0 Unported License: # http://creativecommons.org/licenses/by-sa/3.0/deed.en_US import io import os import struct import time import bpy from bpy_extras.io_utils import ExportHelper from mathutils import Vector import numpy as np bl_info = { "name": "Armory Mesh Exporter", "category": "Import-Export", "location": "File -> Export", "description": "Armory mesh data", "author": "Armory3D.org", "version": (2025, 8, 0), "blender": (4, 3, 1), "doc_url": "", "tracker_url": "", } NodeTypeMesh = 1 structIdentifier = ["object", "mesh_object"] class ArmoryExporter(bpy.types.Operator, ExportHelper): """Export to Armory format""" bl_idname = "export_scene.arm" bl_label = "Export Armory" filename_ext = ".arm" def execute(self, context): profile_time = time.time() current_frame = context.scene.frame_current current_subframe = context.scene.frame_subframe self.scene = context.scene self.output = {} self.bobjectArray = {} self.meshArray = {} self.depsgraph = context.evaluated_depsgraph_get() scene_objects = self.scene.collection.all_objects for bobject in scene_objects: if not bobject.parent: self.process_bobject(bobject) self.output["name"] = self.scene.name self.output["objects"] = [] for bo in scene_objects: if not bo.parent: self.export_object(bo, self.scene) self.output["mesh_datas"] = [] for o in self.meshArray.items(): self.export_mesh(o) self.output["camera_datas"] = None self.output["camera_ref"] = None self.output["material_datas"] = None self.output["shader_datas"] = None self.output["world_datas"] = None self.output["world_ref"] = None self.output["speaker_datas"] = None self.output["embedded_datas"] = None self.write_arm(self.filepath, self.output) self.scene.frame_set(current_frame, subframe=current_subframe) print(f"Scene exported in {str(time.time() - profile_time)}") return {"FINISHED"} def write_arm(self, filepath, output): with open(filepath, "wb") as f: f.write(packb(output)) def write_matrix(self, matrix): return [ matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3], matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3], matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3], ] def process_bobject(self, bobject): if bobject.type not in ["MESH"]: return btype = NodeTypeMesh if bobject.type == "MESH" else 0 self.bobjectArray[bobject] = {"objectType": btype, "structName": bobject.name} for subbobject in bobject.children: self.process_bobject(subbobject) def export_object(self, bobject, scene, parento=None): if bobjectRef := self.bobjectArray.get(bobject): o = {} o["name"] = bobjectRef["structName"] o["type"] = structIdentifier[bobjectRef["objectType"]] o["data_ref"] = None o["transform"] = self.write_matrix(bobject.matrix_local) o["dimensions"] = None o["visible"] = True o["spawn"] = True o["anim"] = None o["material_refs"] = None o["children"] = None if bobjectRef["objectType"] == NodeTypeMesh: objref = bobject.data if objref not in self.meshArray: self.meshArray[objref] = { "structName": objref.name, "objectTable": [bobject], } else: self.meshArray[objref]["objectTable"].append(bobject) oid = self.meshArray[objref]["structName"] o["data_ref"] = oid o["dimensions"] = self.calc_aabb(bobject) if parento is None: self.output["objects"].append(o) else: parento["children"].append(o) if not hasattr(o, "children") and len(bobject.children) > 0: o["children"] = [] for subbobject in bobject.children: self.export_object(subbobject, scene, o) def calc_aabb(self, bobject): aabb_center = 0.125 * sum((Vector(b) for b in bobject.bound_box), Vector()) return [ abs( (bobject.bound_box[6][0] - bobject.bound_box[0][0]) / 2 + abs(aabb_center[0]) ) * 2, abs( (bobject.bound_box[6][1] - bobject.bound_box[0][1]) / 2 + abs(aabb_center[1]) ) * 2, abs( (bobject.bound_box[6][2] - bobject.bound_box[0][2]) / 2 + abs(aabb_center[2]) ) * 2, ] def export_mesh_data(self, exportMesh, bobject, o, has_armature=False): exportMesh.calc_loop_triangles() loops = exportMesh.loops num_verts = len(loops) num_uv_layers = len(exportMesh.uv_layers) num_colors = len(exportMesh.vertex_colors) has_tex = num_uv_layers > 0 has_tex1 = num_uv_layers > 1 has_col = num_colors > 0 has_tang = False # Scale for packed coords aabb = self.calc_aabb(bobject) maxdim = max(aabb[0], max(aabb[1], aabb[2])) if maxdim > 2: o["scale_pos"] = maxdim / 2 else: o["scale_pos"] = 1.0 pdata = np.empty(num_verts * 4, dtype=" maxdim: maxdim = abs(v.uv[0]) if abs(v.uv[1]) > maxdim: maxdim = abs(v.uv[1]) if has_tex1: lay1 = uv_layers[t1map] for v in lay1.data: if abs(v.uv[0]) > maxdim: maxdim = abs(v.uv[0]) if abs(v.uv[1]) > maxdim: maxdim = abs(v.uv[1]) if maxdim > 1: o["scale_tex"] = maxdim invscale_tex = (1 / o["scale_tex"]) * 32767 else: o["scale_tex"] = 1.0 invscale_tex = 1 * 32767 if has_tang: exportMesh.calc_tangents(uvmap=lay0.name) tangdata = np.empty(num_verts * 4, dtype=" 0 and isinstance(obj[0], float): fp.write(b"\xca") for e in obj: fp.write(struct.pack(" 0 and isinstance(obj[0], bool): for e in obj: pack(e, fp) elif len(obj) > 0 and isinstance(obj[0], int): fp.write(b"\xd2") for e in obj: fp.write(struct.pack(" 0 and isinstance(obj[0], np.float32): fp.write(b"\xca") fp.write(obj.tobytes()) # Int32 elif len(obj) > 0 and isinstance(obj[0], np.int32): fp.write(b"\xd2") fp.write(obj.tobytes()) # Int16 elif len(obj) > 0 and isinstance(obj[0], np.int16): fp.write(b"\xd1") fp.write(obj.tobytes()) # Regular else: for e in obj: pack(e, fp) def _pack_map(obj, fp): fp.write(b"\xdf" + struct.pack("