# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Export MX Simulator Model (.jm)", "description": "Export MX Simulator Model (.jm)", "author": "Josh Vanderhoof", "version": (0, 7), "blender": (2, 80, 0), "location": "File > Export > MX Simulator Model (.jm)", "warning": "", "category": "Import-Export" } import bpy from bpy.props import * import mathutils, math, struct from os import remove from bpy_extras.io_utils import ExportHelper def reduce_verts(v, t): s = [] # sorted with original indices for i in range(0, len(v)): s.append((v[i], i)) s.sort() r = [] # new vertex array tr = [] # new triangle array m = [0] * len(s) # maps original vertex indices to new indices r.append(s[0][0]) for i in range(0, len(s)): if r[len(r) - 1] != s[i][0]: r.append(s[i][0]) m[s[i][1]] = len(r) - 1 for i in range(0, len(t)): tr.append(m[t[i]]) return (r, tr) def bone_index(bonelist, bonename): for i in range(len(bonelist)): if bonename == bonelist[i]: return i return 0 def vert_to_string(v, bones): s = "%f %f %f %f %f %f %f %f" % (v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]) for i in range(len(bones)): w = " 0" for b in v[8]: if bones[i] == b[0]: w = " %f" % (b[1]) s += w s += "\n" return s def get_vertex_influences(obj, mesh, vidx): r = [] for g in mesh.vertices[vidx].groups: name = obj.vertex_groups[g.group].name r.append((name, g.weight)) return r def add_face(face, m, key, mesh, obj): if not key in m: m[key] = ([],[]) verts, tris = m[key] vbase = len(verts) uv = mesh.uv_layers.active for i in range(len(face.vertices)): vidx = face.vertices[i] lidx = face.loops[i] v = mesh.vertices[vidx] if uv: tc = (uv.data[lidx].uv[0], uv.data[lidx].uv[1]) else: tc = (0.0, 0.0) verts.append((v.co[0], v.co[1], v.co[2], v.normal[0], v.normal[1], v.normal[2]) + tc + (get_vertex_influences(obj, mesh, vidx),)) for i in range(3): tris.append(vbase + i) if len(face.vertices) == 4: # output other half of quad for i in (2, 3, 0): tris.append(vbase + i) def add_object_by_material(obj, m, props): depsgraph = bpy.context.evaluated_depsgraph_get() if props.apply_modifiers: tobj = obj.evaluated_get(depsgraph) else: tobj = obj mesh = tobj.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) if props.rot_x90: mesh.transform(mathutils.Matrix.Rotation(-math.pi * 0.5, 4, 'X')) mesh.calc_normals() if not mesh.loop_triangles and mesh.polygons: mesh.calc_loop_triangles() for face in mesh.loop_triangles: try: mat = mesh.materials[face.material_index] mirror_alpha = mat.metallic mirror_rgb = [ 1.0, 1.0, 1.0 ] spec_alpha = mat.specular_intensity spec_rgb = mat.specular_color if mat.roughness < 0.5: spec_exp = pow(10.0, -(mat.roughness - 0.5) * 4.0) else: spec_exp = 1.0 spec_alpha *= pow(1.0 - mat.roughness, 2) spec_rgb = [ spec_rgb[0] * spec_alpha, spec_rgb[1] * spec_alpha, spec_rgb[2] * spec_alpha ] spec_alpha = 1.0; name = mat.name except: mirror_alpha = 0.0 mirror_rgb = [0.0, 0.0, 0.0] spec_alpha = 0.0 spec_rgb = [0.0, 0.0, 0.0] spec_exp = 0 name = "0" if mirror_alpha == 0.0: key = (name,) + (spec_exp,) + (spec_alpha,) + tuple(spec_rgb) add_face(face, m, key, mesh, obj) elif mirror_alpha == 1.0: key = ("\x7f",) + (-1.0,) + (mirror_alpha,) + tuple(mirror_rgb) add_face(face, m, key, mesh, obj) else: key = (name,) + (spec_exp,) + (spec_alpha,) + tuple(spec_rgb) add_face(face, m, key, mesh, obj) key = ("\x7f",) + (-1.0,) + (mirror_alpha,) + tuple(mirror_rgb) add_face(face, m, key, mesh, obj) tobj.to_mesh_clear() def find_armature(obj): mseq = obj.modifiers for i in range(mseq.__len__()): m = mseq.__getitem__(i) if m.type == Modifier.Types.ARMATURE: return m.__getitem__(Modifier.Settings.OBJECT) return None def get_bones(obj): arm = obj.find_armature() if arm == None: return [] keys = [] for k in arm.data.bones.keys(): if arm.data.bones[k].use_deform: keys.append(k) return keys def do_export(context, props, filepath): file = open(filepath, "w", encoding="ascii", newline="") mesh_by_material = {} bones = [] for obj in bpy.context.selected_objects: add_object_by_material(obj, mesh_by_material, props) bones = bones + get_bones(obj) keys = list(mesh_by_material.keys()) keys.sort() bones.sort() if len(bones) > 0: file.write("JM2\n") file.write("%d\n" % (len(bones))) file.write(",".join(bones)) file.write("\n") else: file.write("JM1\n") for key in keys: verts, tris = mesh_by_material[key] verts, tris = reduce_verts(verts, tris) pad = "" for val in key[1:]: file.write("%s%f" % (pad, val)) pad = " " file.write("\n%d %d\n" % (len(verts), len(tris) / 3)) for v in verts: file.write(vert_to_string(v, bones)) for t in tris: file.write("%d\n" % t) file.flush() file.close() return True class Export_jm(bpy.types.Operator, ExportHelper): '''Exports the active Object as a .jm file.''' bl_idname = "export_shape.jm" bl_label = "Export MX Simulator (.jm)" filename_ext = ".jm" rot_x90: BoolProperty(name="Convert to Y-up", description="Rotate 90 degrees around X to convert to y-up", default=True) apply_modifiers: BoolProperty(name="Apply Modifiers", description="Applies the Modifiers", default=True) # "poll" reports if this operator makes sense in the current conditions. # (I think.) @classmethod def poll(cls, context): return context.active_object.type == 'MESH' # "execute" is called non interactively. def execute(self, context): props = self.properties filepath = self.filepath filepath = bpy.path.ensure_ext(filepath, self.filename_ext) exported = do_export(context, props, filepath) return {'FINISHED'} # "invoke" is called because of user interaction. def invoke(self, context, event): wm = context.window_manager if True: # File selector wm.fileselect_add(self) # will run self.execute() return {'RUNNING_MODAL'} elif True: # search the enum wm.invoke_search_popup(self) return {'RUNNING_MODAL'} elif False: # Redo popup return wm.invoke_props_popup(self, event) elif False: return self.execute(context) def menu_func(self, context): self.layout.operator(Export_jm.bl_idname, text="MX Simulator Model (.jm)") def register(): from bpy.utils import register_class register_class(Export_jm) bpy.types.TOPBAR_MT_file_export.append(menu_func) def unregister(): from bpy.utils import unregister_class unregister_class(Export_jm) bpy.types.TOPBAR_MT_file_export.remove(menu_func) if __name__ == "__main__": register()