io_export_arm.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. """Armory Mesh Exporter"""
  2. #
  3. # Based on Open Game Engine Exchange
  4. # https://opengex.org/
  5. # Export plugin for Blender by Eric Lengyel
  6. # Copyright 2015, Terathon Software LLC
  7. #
  8. # This software is licensed under the Creative Commons
  9. # Attribution-ShareAlike 3.0 Unported License:
  10. # http://creativecommons.org/licenses/by-sa/3.0/deed.en_US
  11. import io
  12. import os
  13. import struct
  14. import time
  15. import bpy
  16. from bpy_extras.io_utils import ExportHelper
  17. from mathutils import Vector
  18. import numpy as np
  19. bl_info = {
  20. "name": "Armory Mesh Exporter",
  21. "category": "Import-Export",
  22. "location": "File -> Export",
  23. "description": "Armory mesh data",
  24. "author": "Armory3D.org",
  25. "version": (2025, 2, 0),
  26. "blender": (4, 3, 1),
  27. "doc_url": "",
  28. "tracker_url": "",
  29. }
  30. NodeTypeBone = 1
  31. NodeTypeMesh = 2
  32. structIdentifier = ["object", "bone_object", "mesh_object"]
  33. class ArmoryExporter(bpy.types.Operator, ExportHelper):
  34. """Export to Armory format"""
  35. bl_idname = "export_scene.arm"
  36. bl_label = "Export Armory"
  37. filename_ext = ".arm"
  38. def execute(self, context):
  39. profile_time = time.time()
  40. current_frame = context.scene.frame_current
  41. current_subframe = context.scene.frame_subframe
  42. self.scene = context.scene
  43. self.output = {}
  44. self.bobjectArray = {}
  45. self.bobjectBoneArray = {}
  46. self.meshArray = {}
  47. self.boneParentArray = {}
  48. self.bone_tracks = []
  49. self.depsgraph = context.evaluated_depsgraph_get()
  50. scene_objects = self.scene.collection.all_objects
  51. for bobject in scene_objects:
  52. if not bobject.parent:
  53. self.process_bobject(bobject)
  54. self.process_skinned_meshes()
  55. self.output["name"] = self.scene.name
  56. self.output["objects"] = []
  57. for bo in scene_objects:
  58. if not bo.parent:
  59. self.export_object(bo, self.scene)
  60. self.output["mesh_datas"] = []
  61. for o in self.meshArray.items():
  62. self.export_mesh(o)
  63. self.output["camera_datas"] = None
  64. self.output["camera_ref"] = None
  65. self.output["material_datas"] = None
  66. self.output["shader_datas"] = None
  67. self.output["world_datas"] = None
  68. self.output["world_ref"] = None
  69. self.output["speaker_datas"] = None
  70. self.output["embedded_datas"] = None
  71. self.write_arm(self.filepath, self.output)
  72. self.scene.frame_set(current_frame, subframe=current_subframe)
  73. print(f"Scene exported in {str(time.time() - profile_time)}")
  74. return {"FINISHED"}
  75. def write_arm(self, filepath, output):
  76. with open(filepath, "wb") as f:
  77. f.write(packb(output))
  78. def write_matrix(self, matrix):
  79. return [
  80. matrix[0][0],
  81. matrix[0][1],
  82. matrix[0][2],
  83. matrix[0][3],
  84. matrix[1][0],
  85. matrix[1][1],
  86. matrix[1][2],
  87. matrix[1][3],
  88. matrix[2][0],
  89. matrix[2][1],
  90. matrix[2][2],
  91. matrix[2][3],
  92. matrix[3][0],
  93. matrix[3][1],
  94. matrix[3][2],
  95. matrix[3][3],
  96. ]
  97. def find_bone(self, name):
  98. return next(
  99. (
  100. bobject_ref
  101. for bobject_ref in self.bobjectBoneArray.items()
  102. if bobject_ref[0].name == name
  103. ),
  104. None,
  105. )
  106. def collect_bone_animation(self, armature, name):
  107. path = 'pose.bones["' + name + '"].'
  108. curve_array = []
  109. if armature.animation_data:
  110. if action := armature.animation_data.action:
  111. curve_array.extend(
  112. fcurve
  113. for fcurve in action.fcurves
  114. if fcurve.data_path.startswith(path)
  115. )
  116. return curve_array
  117. def export_bone(self, armature, bone, scene, o, action):
  118. if bobjectRef := self.bobjectBoneArray.get(bone):
  119. o["name"] = bobjectRef["structName"]
  120. o["type"] = structIdentifier[bobjectRef["objectType"]]
  121. self.export_bone_transform(armature, bone, o, action)
  122. o["children"] = []
  123. for subbobject in bone.children:
  124. so = {}
  125. self.export_bone(armature, subbobject, scene, so, action)
  126. o["children"].append(so)
  127. def export_pose_markers(self, oanim, action):
  128. if action.pose_markers is None or len(action.pose_markers) == 0:
  129. return
  130. oanim["marker_frames"] = []
  131. oanim["marker_names"] = []
  132. for m in action.pose_markers:
  133. oanim["marker_frames"].append(int(m.frame))
  134. oanim["marker_names"].append(m.name)
  135. def process_bone(self, bone):
  136. self.bobjectBoneArray[bone] = {
  137. "objectType": NodeTypeBone,
  138. "structName": bone.name,
  139. }
  140. for subbobject in bone.children:
  141. self.process_bone(subbobject)
  142. def process_bobject(self, bobject):
  143. if bobject.type not in ["MESH", "ARMATURE"]:
  144. return
  145. btype = NodeTypeMesh if bobject.type == "MESH" else 0
  146. self.bobjectArray[bobject] = {"objectType": btype, "structName": bobject.name}
  147. if bobject.type == "ARMATURE":
  148. if skeleton := bobject.data:
  149. for bone in skeleton.bones:
  150. if not bone.parent:
  151. self.process_bone(bone)
  152. for subbobject in bobject.children:
  153. self.process_bobject(subbobject)
  154. def process_skinned_meshes(self):
  155. for bobjectRef in self.bobjectArray.items():
  156. if bobjectRef[1]["objectType"] == NodeTypeMesh:
  157. if armature := bobjectRef[0].find_armature():
  158. for bone in armature.data.bones:
  159. boneRef = self.find_bone(bone.name)
  160. if boneRef:
  161. boneRef[1]["objectType"] = NodeTypeBone
  162. def export_bone_transform(self, armature, bone, o, action):
  163. pose_bone = armature.pose.bones.get(bone.name)
  164. transform = bone.matrix_local.copy()
  165. if bone.parent is not None:
  166. transform = bone.parent.matrix_local.inverted_safe() @ transform
  167. o["transform"] = {}
  168. o["transform"]["values"] = self.write_matrix(transform)
  169. curve_array = self.collect_bone_animation(armature, bone.name)
  170. animation = len(curve_array) != 0
  171. if animation and pose_bone:
  172. begin_frame = int(action.frame_range[0])
  173. end_frame = int(action.frame_range[1])
  174. tracko = {}
  175. o["anim"] = {}
  176. o["anim"]["tracks"] = [tracko]
  177. tracko["target"] = "transform"
  178. tracko["frames"] = [
  179. i - begin_frame for i in range(begin_frame, end_frame + 1)
  180. ]
  181. tracko["values"] = []
  182. self.bone_tracks.append((tracko["values"], pose_bone))
  183. def write_bone_matrices(self, scene, action):
  184. if len(self.bone_tracks) > 0:
  185. begin_frame = int(action.frame_range[0])
  186. end_frame = int(action.frame_range[1])
  187. for i in range(begin_frame, end_frame + 1):
  188. scene.frame_set(i)
  189. for track in self.bone_tracks:
  190. values, pose_bone = track[0], track[1]
  191. if parent := pose_bone.parent:
  192. values += self.write_matrix(
  193. (parent.matrix.inverted_safe() @ pose_bone.matrix)
  194. )
  195. else:
  196. values += self.write_matrix(pose_bone.matrix)
  197. def export_object(self, bobject, scene, parento=None):
  198. if bobjectRef := self.bobjectArray.get(bobject):
  199. o = {}
  200. o["name"] = bobjectRef["structName"]
  201. o["type"] = structIdentifier[bobjectRef["objectType"]]
  202. o["data_ref"] = None
  203. o["transform"] = self.write_matrix(bobject.matrix_local)
  204. o["dimensions"] = None
  205. o["visible"] = True
  206. o["spawn"] = True
  207. o["anim"] = None
  208. o["material_refs"] = None
  209. o["children"] = None
  210. if bobject.parent_type == "BONE":
  211. o["anim"]["parent_bone"] = bobject.parent_bone
  212. if bobjectRef["objectType"] == NodeTypeMesh:
  213. objref = bobject.data
  214. if objref not in self.meshArray:
  215. self.meshArray[objref] = {
  216. "structName": objref.name,
  217. "objectTable": [bobject],
  218. }
  219. else:
  220. self.meshArray[objref]["objectTable"].append(bobject)
  221. oid = self.meshArray[objref]["structName"]
  222. o["data_ref"] = oid
  223. o["dimensions"] = self.calc_aabb(bobject)
  224. # If the object is parented to a bone and is not relative, undo the
  225. # bone's transform
  226. if bobject.parent_type == "BONE":
  227. armature = bobject.parent.data
  228. bone = armature.bones[bobject.parent_bone]
  229. o["anim"]["parent_bone_connected"] = bone.use_connect
  230. if bone.use_connect:
  231. bone_translation = Vector((0, bone.length, 0)) + bone.head
  232. o["anim"]["parent_bone_tail"] = [
  233. bone_translation[0],
  234. bone_translation[1],
  235. bone_translation[2],
  236. ]
  237. else:
  238. bone_translation = bone.tail - bone.head
  239. o["anim"]["parent_bone_tail"] = [
  240. bone_translation[0],
  241. bone_translation[1],
  242. bone_translation[2],
  243. ]
  244. pose_bone = bobject.parent.pose.bones[bobject.parent_bone]
  245. bone_translation_pose = pose_bone.tail - pose_bone.head
  246. o["anim"]["parent_bone_tail_pose"] = [
  247. bone_translation_pose[0],
  248. bone_translation_pose[1],
  249. bone_translation_pose[2],
  250. ]
  251. if bobject.type == "ARMATURE" and bobject.data is not None:
  252. bdata = bobject.data
  253. action = None
  254. adata = bobject.animation_data
  255. # Active action
  256. if adata is not None:
  257. action = adata.action
  258. if action is None:
  259. bobject.animation_data_create()
  260. actions = bpy.data.actions
  261. action = actions.get("armory_pose")
  262. if action is None:
  263. action = actions.new(name="armory_pose")
  264. # Collect export actions
  265. export_actions = [action]
  266. if hasattr(adata, "nla_tracks") and adata.nla_tracks is not None:
  267. for track in adata.nla_tracks:
  268. if track.strips is None:
  269. continue
  270. for strip in track.strips:
  271. if strip.action is None:
  272. continue
  273. if strip.action.name == action.name:
  274. continue
  275. export_actions.append(strip.action)
  276. basename = os.path.basename(self.filepath)[:-4]
  277. o["anim"]["bone_actions"] = []
  278. for action in export_actions:
  279. o["anim"]["bone_actions"].append(basename + "_" + action.name)
  280. orig_action = bobject.animation_data.action
  281. for action in export_actions:
  282. bobject.animation_data.action = action
  283. bones = []
  284. self.bone_tracks = []
  285. for bone in bdata.bones:
  286. if not bone.parent:
  287. boneo = {}
  288. self.export_bone(bobject, bone, scene, boneo, action)
  289. bones.append(boneo)
  290. self.write_bone_matrices(scene, action)
  291. if len(bones) > 0 and "anim" in bones[0]:
  292. self.export_pose_markers(bones[0]["anim"], action)
  293. # Save action separately
  294. action_obj = {}
  295. action_obj["name"] = action.name
  296. action_obj["objects"] = bones
  297. self.write_arm(
  298. self.filepath[:-4] + "_" + action.name + ".arm", action_obj
  299. )
  300. bobject.animation_data.action = orig_action
  301. if parento is None:
  302. self.output["objects"].append(o)
  303. else:
  304. parento["children"].append(o)
  305. if not hasattr(o, "children") and len(bobject.children) > 0:
  306. o["children"] = []
  307. for subbobject in bobject.children:
  308. self.export_object(subbobject, scene, o)
  309. def export_skin(self, bobject, armature, exportMesh, o):
  310. # This function exports all skinning data, which includes the skeleton
  311. # and per-vertex bone influence data
  312. oskin = {}
  313. o["skin"] = oskin
  314. # Write the skin bind pose transform
  315. otrans = {}
  316. oskin["transform"] = otrans
  317. otrans["values"] = self.write_matrix(bobject.matrix_world)
  318. bone_array = armature.data.bones
  319. bone_count = len(bone_array)
  320. max_bones = 128
  321. bone_count = min(bone_count, max_bones)
  322. # Write the bone object reference array
  323. oskin["bone_ref_array"] = np.empty(bone_count, dtype=object)
  324. oskin["bone_len_array"] = np.empty(bone_count, dtype="<f4")
  325. for i in range(bone_count):
  326. if boneRef := self.find_bone(bone_array[i].name):
  327. oskin["bone_ref_array"][i] = boneRef[1]["structName"]
  328. oskin["bone_len_array"][i] = bone_array[i].length
  329. else:
  330. oskin["bone_ref_array"][i] = ""
  331. oskin["bone_len_array"][i] = 0.0
  332. # Write the bind pose transform array
  333. oskin["transforms_inv"] = []
  334. for i in range(bone_count):
  335. skeleton_inv = (
  336. armature.matrix_world @ bone_array[i].matrix_local
  337. ).inverted_safe()
  338. skeleton_inv = skeleton_inv @ bobject.matrix_world
  339. oskin["transforms_inv"].append(self.write_matrix(skeleton_inv))
  340. # Export the per-vertex bone influence data
  341. group_remap = []
  342. for group in bobject.vertex_groups:
  343. for i in range(bone_count):
  344. if bone_array[i].name == group.name:
  345. group_remap.append(i)
  346. break
  347. else:
  348. group_remap.append(-1)
  349. bone_count_array = np.empty(len(exportMesh.loops), dtype="<i2")
  350. bone_index_array = np.empty(len(exportMesh.loops) * 4, dtype="<i2")
  351. bone_weight_array = np.empty(len(exportMesh.loops) * 4, dtype="<f4")
  352. vertices = bobject.data.vertices
  353. count = 0
  354. for index, l in enumerate(exportMesh.loops):
  355. bone_count = 0
  356. total_weight = 0.0
  357. bone_values = []
  358. for g in vertices[l.vertex_index].groups:
  359. bone_index = group_remap[g.group]
  360. bone_weight = g.weight
  361. if bone_index >= 0: # and bone_weight != 0.0:
  362. bone_values.append((bone_weight, bone_index))
  363. total_weight += bone_weight
  364. bone_count += 1
  365. if bone_count > 4:
  366. bone_count = 4
  367. bone_values.sort(reverse=True)
  368. bone_values = bone_values[:4]
  369. bone_count_array[index] = bone_count
  370. for bv in bone_values:
  371. bone_weight_array[count] = bv[0]
  372. bone_index_array[count] = bv[1]
  373. count += 1
  374. if total_weight not in (0.0, 1.0):
  375. normalizer = 1.0 / total_weight
  376. for i in range(bone_count):
  377. bone_weight_array[count - i - 1] *= normalizer
  378. bone_index_array = bone_index_array[:count]
  379. bone_weight_array = bone_weight_array[:count]
  380. bone_weight_array *= 32767
  381. bone_weight_array = np.array(bone_weight_array, dtype="<i2")
  382. oskin["bone_count_array"] = bone_count_array
  383. oskin["bone_index_array"] = bone_index_array
  384. oskin["bone_weight_array"] = bone_weight_array
  385. def calc_aabb(self, bobject):
  386. aabb_center = 0.125 * sum((Vector(b) for b in bobject.bound_box), Vector())
  387. return [
  388. abs(
  389. (bobject.bound_box[6][0] - bobject.bound_box[0][0]) / 2
  390. + abs(aabb_center[0])
  391. )
  392. * 2,
  393. abs(
  394. (bobject.bound_box[6][1] - bobject.bound_box[0][1]) / 2
  395. + abs(aabb_center[1])
  396. )
  397. * 2,
  398. abs(
  399. (bobject.bound_box[6][2] - bobject.bound_box[0][2]) / 2
  400. + abs(aabb_center[2])
  401. )
  402. * 2,
  403. ]
  404. def export_mesh_data(self, exportMesh, bobject, o, has_armature=False):
  405. exportMesh.calc_loop_triangles()
  406. loops = exportMesh.loops
  407. num_verts = len(loops)
  408. num_uv_layers = len(exportMesh.uv_layers)
  409. num_colors = len(exportMesh.vertex_colors)
  410. has_tex = num_uv_layers > 0
  411. has_tex1 = num_uv_layers > 1
  412. has_col = num_colors > 0
  413. has_tang = False
  414. # Scale for packed coords
  415. aabb = self.calc_aabb(bobject)
  416. maxdim = max(aabb[0], max(aabb[1], aabb[2]))
  417. if maxdim > 2:
  418. o["scale_pos"] = maxdim / 2
  419. else:
  420. o["scale_pos"] = 1.0
  421. if has_armature: # Allow up to 2x bigger bounds for skinned mesh
  422. o["scale_pos"] *= 2.0
  423. pdata = np.empty(num_verts * 4, dtype="<f4") # p.xyz, n.z
  424. ndata = np.empty(num_verts * 2, dtype="<f4") # n.xy
  425. if has_tex:
  426. t0map = 0 # Get active uvmap
  427. t0data = np.empty(num_verts * 2, dtype="<f4")
  428. uv_layers = exportMesh.uv_layers
  429. if uv_layers is not None:
  430. for i in range(0, len(uv_layers)):
  431. if uv_layers[i].active_render:
  432. t0map = i
  433. break
  434. if has_tex1:
  435. t1map = 1 if t0map == 0 else 0
  436. t1data = np.empty(num_verts * 2, dtype="<f4")
  437. # Scale for packed coords
  438. maxdim = 1.0
  439. lay0 = uv_layers[t0map]
  440. for v in lay0.data:
  441. if abs(v.uv[0]) > maxdim:
  442. maxdim = abs(v.uv[0])
  443. if abs(v.uv[1]) > maxdim:
  444. maxdim = abs(v.uv[1])
  445. if has_tex1:
  446. lay1 = uv_layers[t1map]
  447. for v in lay1.data:
  448. if abs(v.uv[0]) > maxdim:
  449. maxdim = abs(v.uv[0])
  450. if abs(v.uv[1]) > maxdim:
  451. maxdim = abs(v.uv[1])
  452. if maxdim > 1:
  453. o["scale_tex"] = maxdim
  454. invscale_tex = (1 / o["scale_tex"]) * 32767
  455. else:
  456. o["scale_tex"] = 1.0
  457. invscale_tex = 1 * 32767
  458. if has_tang:
  459. exportMesh.calc_tangents(uvmap=lay0.name)
  460. tangdata = np.empty(num_verts * 4, dtype="<f4")
  461. if has_col:
  462. cdata = np.empty(num_verts * 4, dtype="<f4")
  463. o["skin"] = None
  464. scale_pos = o["scale_pos"]
  465. invscale_pos = (1 / scale_pos) * 32767
  466. verts = exportMesh.vertices
  467. if has_tex:
  468. lay0 = exportMesh.uv_layers[t0map]
  469. if has_tex1:
  470. lay1 = exportMesh.uv_layers[t1map]
  471. if has_col:
  472. vcol0 = exportMesh.vertex_colors[0].data
  473. for i, loop in enumerate(loops):
  474. v = verts[loop.vertex_index]
  475. co = v.co
  476. normal = loop.normal
  477. tang = loop.tangent
  478. i4 = i * 4
  479. i2 = i * 2
  480. pdata[i4] = co[0]
  481. pdata[i4 + 1] = co[1]
  482. pdata[i4 + 2] = co[2]
  483. pdata[i4 + 3] = normal[2] * scale_pos # Cancel scale
  484. ndata[i2] = normal[0]
  485. ndata[i2 + 1] = normal[1]
  486. if has_tex:
  487. uv = lay0.data[loop.index].uv
  488. t0data[i2] = uv[0]
  489. t0data[i2 + 1] = 1.0 - uv[1] # Reverse Y
  490. if has_tex1:
  491. uv = lay1.data[loop.index].uv
  492. t1data[i2] = uv[0]
  493. t1data[i2 + 1] = 1.0 - uv[1]
  494. if has_tang:
  495. i4 = i * 4
  496. tangdata[i4] = tang[0]
  497. tangdata[i4 + 1] = tang[1]
  498. tangdata[i4 + 2] = tang[2]
  499. if has_col:
  500. col = vcol0[loop.index].color
  501. i4 = i * 4
  502. cdata[i4] = col[0]
  503. cdata[i4 + 1] = col[1]
  504. cdata[i4 + 2] = col[2]
  505. cdata[i4 + 3] = col[3]
  506. # Pack
  507. pdata *= invscale_pos
  508. ndata *= 32767
  509. pdata = np.array(pdata, dtype="<i2")
  510. ndata = np.array(ndata, dtype="<i2")
  511. if has_tex:
  512. t0data *= invscale_tex
  513. t0data = np.array(t0data, dtype="<i2")
  514. if has_tex1:
  515. t1data *= invscale_tex
  516. t1data = np.array(t1data, dtype="<i2")
  517. if has_col:
  518. cdata *= 32767
  519. cdata = np.array(cdata, dtype="<i2")
  520. if has_tang:
  521. tangdata *= 32767
  522. tangdata = np.array(tangdata, dtype="<i2")
  523. # Output
  524. o["vertex_arrays"] = []
  525. o["vertex_arrays"].append(
  526. {"attrib": "pos", "data": "short4norm", "values": pdata}
  527. )
  528. o["vertex_arrays"].append(
  529. {"attrib": "nor", "data": "short2norm", "values": ndata}
  530. )
  531. if has_tex:
  532. o["vertex_arrays"].append(
  533. {"attrib": "tex", "data": "short2norm", "values": t0data}
  534. )
  535. if has_tex1:
  536. o["vertex_arrays"].append(
  537. {"attrib": "tex1", "data": "short2norm", "values": t1data}
  538. )
  539. if has_col:
  540. o["vertex_arrays"].append(
  541. {"attrib": "col", "data": "short4norm", "values": cdata}
  542. )
  543. if has_tang:
  544. o["vertex_arrays"].append(
  545. {
  546. "attrib": "tang",
  547. "data": "short4norm",
  548. "values": tangdata,
  549. }
  550. )
  551. mats = exportMesh.materials
  552. poly_map = []
  553. for i in range(max(len(mats), 1)):
  554. poly_map.append([])
  555. for poly in exportMesh.polygons:
  556. poly_map[poly.material_index].append(poly)
  557. o["index_arrays"] = []
  558. # map polygon indices to triangle loops
  559. tri_loops = {}
  560. for loop in exportMesh.loop_triangles:
  561. if loop.polygon_index not in tri_loops:
  562. tri_loops[loop.polygon_index] = []
  563. tri_loops[loop.polygon_index].append(loop)
  564. for index, polys in enumerate(poly_map):
  565. tris = 0
  566. for poly in polys:
  567. tris += poly.loop_total - 2
  568. if tris == 0: # No face assigned
  569. continue
  570. prim = np.empty(tris * 3, dtype="<i4")
  571. i = 0
  572. for poly in polys:
  573. for loop in tri_loops[poly.index]:
  574. prim[i] = loops[loop.loops[0]].index
  575. prim[i + 1] = loops[loop.loops[1]].index
  576. prim[i + 2] = loops[loop.loops[2]].index
  577. i += 3
  578. ia = {}
  579. ia["material"] = 0
  580. if len(mats) > 1:
  581. for i in range(len(mats)): # Multi-mat mesh
  582. if mats[i] == mats[index]: # Default material for empty slots
  583. ia["material"] = i
  584. break
  585. ia["values"] = prim
  586. o["index_arrays"].append(ia)
  587. def export_mesh(self, objectRef):
  588. # This function exports a single mesh object
  589. table = objectRef[1]["objectTable"]
  590. bobject = table[0]
  591. oid = objectRef[1]["structName"]
  592. o = {}
  593. o["name"] = oid
  594. armature = bobject.find_armature()
  595. apply_modifiers = not armature
  596. bobject_eval = (
  597. bobject.evaluated_get(self.depsgraph) if apply_modifiers else bobject
  598. )
  599. exportMesh = bobject_eval.to_mesh()
  600. self.export_mesh_data(exportMesh, bobject, o, has_armature=armature is not None)
  601. if armature:
  602. self.export_skin(bobject, armature, exportMesh, o)
  603. self.output["mesh_datas"].append(o)
  604. bobject_eval.to_mesh_clear()
  605. def menu_func(self, context):
  606. self.layout.operator(ArmoryExporter.bl_idname, text="Armory (.arm)")
  607. def register():
  608. bpy.utils.register_class(ArmoryExporter)
  609. bpy.types.TOPBAR_MT_file_export.append(menu_func)
  610. def unregister():
  611. bpy.types.TOPBAR_MT_file_export.remove(menu_func)
  612. bpy.utils.unregister_class(ArmoryExporter)
  613. if __name__ == "__main__":
  614. register()
  615. # Msgpack parser with typed arrays
  616. # Based on u-msgpack-python v2.4.1 - v at sergeev.io
  617. # https://github.com/vsergeev/u-msgpack-python
  618. #
  619. # Permission is hereby granted, free of charge, to any person obtaining a copy
  620. # of this software and associated documentation files (the "Software"), to deal
  621. # in the Software without restriction, including without limitation the rights
  622. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  623. # copies of the Software, and to permit persons to whom the Software is
  624. # furnished to do so, subject to the following conditions:
  625. #
  626. # The above copyright notice and this permission notice shall be included in
  627. # all copies or substantial portions of the Software.
  628. #
  629. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  630. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  631. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  632. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  633. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  634. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  635. # THE SOFTWARE.
  636. def _pack_integer(obj, fp):
  637. fp.write(b"\xd2" + struct.pack("<i", obj))
  638. def _pack_nil(fp):
  639. fp.write(b"\xc0")
  640. def _pack_boolean(obj, fp):
  641. fp.write(b"\xc3" if obj else b"\xc2")
  642. def _pack_float(obj, fp):
  643. fp.write(b"\xca" + struct.pack("<f", obj))
  644. def _pack_string(obj, fp):
  645. obj = obj.encode("utf-8")
  646. fp.write(b"\xdb" + struct.pack("<I", len(obj)) + obj)
  647. def _pack_binary(obj, fp):
  648. fp.write(b"\xc6" + struct.pack("<I", len(obj)) + obj)
  649. def _pack_array(obj, fp):
  650. fp.write(b"\xdd" + struct.pack("<I", len(obj)))
  651. if len(obj) > 0 and isinstance(obj[0], float):
  652. fp.write(b"\xca")
  653. for e in obj:
  654. fp.write(struct.pack("<f", e))
  655. elif len(obj) > 0 and isinstance(obj[0], bool):
  656. for e in obj:
  657. pack(e, fp)
  658. elif len(obj) > 0 and isinstance(obj[0], int):
  659. fp.write(b"\xd2")
  660. for e in obj:
  661. fp.write(struct.pack("<i", e))
  662. # Float32
  663. elif len(obj) > 0 and isinstance(obj[0], np.float32):
  664. fp.write(b"\xca")
  665. fp.write(obj.tobytes())
  666. # Int32
  667. elif len(obj) > 0 and isinstance(obj[0], np.int32):
  668. fp.write(b"\xd2")
  669. fp.write(obj.tobytes())
  670. # Int16
  671. elif len(obj) > 0 and isinstance(obj[0], np.int16):
  672. fp.write(b"\xd1")
  673. fp.write(obj.tobytes())
  674. # Regular
  675. else:
  676. for e in obj:
  677. pack(e, fp)
  678. def _pack_map(obj, fp):
  679. fp.write(b"\xdf" + struct.pack("<I", len(obj)))
  680. for k, v in obj.items():
  681. pack(k, fp)
  682. pack(v, fp)
  683. def pack(obj, fp):
  684. if obj is None:
  685. _pack_nil(fp)
  686. elif isinstance(obj, bool):
  687. _pack_boolean(obj, fp)
  688. elif isinstance(obj, int):
  689. _pack_integer(obj, fp)
  690. elif isinstance(obj, float):
  691. _pack_float(obj, fp)
  692. elif isinstance(obj, str):
  693. _pack_string(obj, fp)
  694. elif isinstance(obj, bytes):
  695. _pack_binary(obj, fp)
  696. elif isinstance(obj, (list, np.ndarray, tuple)):
  697. _pack_array(obj, fp)
  698. elif isinstance(obj, dict):
  699. _pack_map(obj, fp)
  700. def packb(obj):
  701. fp = io.BytesIO()
  702. pack(obj, fp)
  703. return fp.getvalue()