# ##### 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 Shape (.shp)", "description": "Export MX Simulator Shape (.shp)", "author": "Josh Vanderhoof", "version": (0, 1), "blender": (2, 6, 3), "api": 36079, "location": "File > Export > MX Simulator Shape (.shp)", "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 is_connected_to(t, isle, vidx): for i in range(0, 3): if isle[t[i]] == vidx: return True return False def find_islands(v, t): isle = [-1] * len(v) for n in range(0, len(t)): keep_going = False for i in range(0, len(t)): if isle[t[i][0]] == -1: keep_going = True for j in range(0, 3): isle[t[i][j]] = n break if not keep_going: return isle keep_going = True while keep_going: keep_going = False for i in range(0, len(t)): if is_connected_to(t[i], isle, n): for j in range(0, 3): if isle[t[i][j]] == -1: keep_going = True isle[t[i][j]] = n def distance(a, b): x = a[0] - b[0] y = a[1] - b[1] z = a[2] - b[2] return math.sqrt(x * x + y * y + z * z) def island_position_and_radius(isle, verts, tris, islands): c = [ 0.0, 0.0, 0.0 ] n = 0 r = -1.0 for i in range(0, len(verts)): if islands[i] == isle: n = n + 1 for j in range(0, 3): c[j] += verts[i][j] if n == 0: return False for i in range(0, 3): c[i] /= n for i in range(0, len(verts)): if islands[i] == isle: d = distance(verts[i], c) if r == -1.0: r = d elif abs(r - d) > max(r, d) / 1024.0: raise RuntimeError("Non-spherical sub-mesh") return (c[0], c[1], c[2], r) def object_get_spheres(obj, props): mesh = obj.to_mesh(bpy.context.scene, props.apply_modifiers, "RENDER") verts = [] tris = [] spheres = [] if props.rot_x90: mesh.transform(mathutils.Matrix.Rotation(-math.pi * 0.5, 4, 'X')) mesh.calc_normals() for v in mesh.vertices: verts.append((v.co[0], v.co[1], v.co[2])) for face in mesh.tessfaces: tris.append((face.vertices[0], face.vertices[1], face.vertices[2])) if len(face.vertices) == 4: tris.append((face.vertices[2], face.vertices[3], face.vertices[0])) islands = find_islands(verts, tris) for i in range(0, len(verts)): s = island_position_and_radius(i, verts, tris, islands) if not s: break spheres.append(s) bpy.data.meshes.remove(mesh) return spheres def do_export(context, props, filepath): spheres = [] for obj in bpy.context.selected_objects: spheres.extend(object_get_spheres(obj, props)) if len(spheres) > 25: raise RuntimeError("Too many spheres") for s in spheres: if s[3] < 0.49: raise RuntimeError("Undersized sphere detected") file = open(filepath, "w", encoding="ascii", newline="") file.write("%d\n" % (len(spheres),)) for s in spheres[:0x19]: file.write("%f %f %f %f 1.0 25000 2500\n" % s) file.flush() file.close() return True class Export_shp(bpy.types.Operator, ExportHelper): '''Exports the active Object as a .shp file.''' bl_idname = "export_shape.shp" bl_label = "Export MX Simulator (.shp)" filename_ext = ".shp" 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_shp.bl_idname, text="MX Simulator Shape (.shp)") def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == "__main__": register()