--- /dev/null
+bl_info = {
+ "name": "Msp math datafiles",
+ "author": "Mikko Rasa",
+ "location": "File > Export",
+ "description": "Export Msp math data",
+ "category": "Import-Export" }
+
+if "bpy" in locals():
+ import imp
+ for sub in "export_shape", "outfile", "properties":
+ if sub in locals():
+ imp.reload(locals()[sub])
+
+import bpy
+from bpy_extras.io_utils import ExportHelper
+
+class ExportMspMathShape(bpy.types.Operator, ExportHelper):
+ bl_idname = "export_mesh.mspmath_shape"
+ bl_label = "Export Msp math shape"
+
+ filename_ext = ".shape"
+
+ def execute(self, context):
+ from .export_shape import ShapeExporter
+ exporter = ShapeExporter()
+ exporter.export(context, self.filepath)
+ return {"FINISHED"}
+
+def menu_func_export(self, context):
+ self.layout.operator(ExportMspMathShape.bl_idname, text="Msp math shape")
+
+from .properties import MspMathObjectProperties
+
+def register():
+ bpy.utils.register_module(__name__)
+
+ bpy.types.INFO_MT_file_export.append(menu_func_export)
+
+ from .properties import register_properties
+ register_properties();
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+ bpy.types.INFO_MT_file_export.remove(menu_func_export)
+
+if __name__=="__main__":
+ register()
--- /dev/null
+import math
+from mathutils import Vector
+
+class Vertex:
+ def __init__(self, v=None):
+ self.vertex = v
+ if v:
+ self.co = v.co
+ else:
+ self.co = Vector((0, 0, 0))
+
+class Edge:
+ def __init__(self, e=None):
+ self.edge = e
+ self.vertices = []
+ self.polygons = []
+ self.convex = True
+
+class Polygon:
+ def __init__(self, p=None):
+ self.polygon = p
+ self.edges = []
+ self.vertices = []
+ if p:
+ self.normal = p.normal
+ else:
+ self.normal = Vector((0, 0, 1))
+
+class Mesh:
+ def __init__(self, m=None):
+ if m:
+ self.vertices = [Vertex(v) for v in m.vertices]
+ self.edges = [Edge(e) for e in m.edges]
+ self.polygons = [Polygon(p) for p in m.polygons]
+
+ for e in self.edges:
+ e.vertices = [self.vertices[i] for i in e.edge.vertices]
+
+ for p in self.polygons:
+ p.vertices = [self.vertices[i] for i in p.polygon.vertices]
+ p.edges = [self.edges[m.loops[i].edge_index] for i in p.polygon.loop_indices]
+ for e in p.edges:
+ e.polygons.append(p)
+
+ else:
+ self.edges = []
+ self.vertices = []
+ self.polygons = []
+ self.convex = True
+
+class SphereFit:
+ def __init__(self, mesh):
+ self.mesh = mesh
+ min_coords = tuple(map(min, zip(*(v.co for v in self.mesh.vertices))))
+ max_coords = tuple(map(max, zip(*(v.co for v in self.mesh.vertices))))
+ self.center = (Vector(min_coords)+Vector(max_coords))/2
+ self.radius = max(max_coords[i]-min_coords[i] for i in range(3))/2
+
+ def calculate_center_error(self):
+ error = Vector()
+ for v in self.mesh.vertices:
+ p = v.co-self.center
+ p.normalize()
+ p = self.center+p*self.radius
+ error += p-v.co
+
+ return error/len(self.mesh.vertices)
+
+ def calculate_radius_error(self):
+ error = 0
+ for v in self.mesh.vertices:
+ error += self.radius-(v.co-self.center).length
+ return error/len(self.mesh.vertices)
+
+ def calculate_fit_error(self):
+ error = 0
+ for v in self.mesh.vertices:
+ error += (self.radius-(v.co-self.center).length)**2
+ return math.sqrt(error/len(self.mesh.vertices))
+
+ def fit(self, epsilon=1e-3):
+ while 1:
+ error = self.calculate_radius_error()
+ if error<epsilon:
+ return self.calculate_fit_error()
+
+ self.center -= self.calculate_center_error()
+ self.radius -= self.calculate_radius_error()
+
+class BoxFit:
+ def __init__(self, mesh):
+ self.mesh = mesh
+ min_coords = tuple(map(min, zip(*(v.co for v in self.mesh.vertices))))
+ max_coords = tuple(map(max, zip(*(v.co for v in self.mesh.vertices))))
+ self.ranges = [[min_coords[i], max_coords[i]] for i in range(3)]
+ self.update()
+
+ def update(self):
+ self.dimensions = tuple(r[1]-r[0] for r in self.ranges)
+ self.center = Vector(tuple((r[0]+r[1])/2 for r in self.ranges))
+
+ def calculate_side_errors(self):
+ errors = [[0, 0], [0, 0], [0, 0]]
+ counts = [[0, 0], [0, 0], [0, 0]]
+ for v in self.mesh.vertices:
+ distances = tuple((r[0]-v.co[i], v.co[i]-r[1]) for i, r in enumerate(self.ranges))
+ side = (0, 0)
+ for i, d in enumerate(distances):
+ for j in range(2):
+ if d[j]>distances[side[0]][side[1]]:
+ side = (i, j)
+
+ errors[side[0]][side[1]] += distances[side[0]][side[1]]
+ counts[side[0]][side[1]] += 1
+
+ for i in range(3):
+ for j in range(2):
+ if counts[i][j]:
+ errors[i][j] /= counts[i][j]
+
+ return errors
+
+ def calculate_fit_error(self):
+ error = 0
+ for v in self.mesh.vertices:
+ distances = tuple((r[0]-v.co[i], v.co[i]-r[1]) for i, r in enumerate(self.ranges))
+ error += max(max(*d) for d in distances)**2
+ return math.sqrt(error/len(self.mesh.vertices))
+
+ def fit(self, epsilon=1e-3):
+ while 1:
+ errors = self.calculate_side_errors()
+ max_error = max(max(map(abs, e)) for e in errors)
+ if max_error<epsilon:
+ self.update()
+ return self.calculate_fit_error()
+
+ for i in range(3):
+ for j in range(2):
+ e = errors[i][j]
+ if j==0:
+ e = -e
+ self.ranges[i][j] += e
+
+class ShapeExporter:
+ def export(self, context, out_file):
+ obj = context.active_object
+ if obj.type!="MESH":
+ raise Exception("Can only export mesh data")
+
+ mesh = Mesh(obj.data)
+
+ from .outfile import open_output
+ out_file = open_output(out_file)
+
+ shape_type = obj.shape_type
+ if shape_type=="AUTO":
+ errors = []
+ errors.append(("SPHERE", SphereFit(mesh).fit()))
+ errors.append(("BOX", BoxFit(mesh).fit()))
+ errors.sort(key=lambda x:x[1])
+ print(errors)
+ shape_type = errors[0][0]
+
+ if shape_type=="SPHERE":
+ sphere = SphereFit(mesh)
+ error = sphere.fit()
+
+ use_center = sphere.center.length>sphere.radius/1e5
+ if use_center:
+ out_file.begin("transformed")
+ out_file.write("translate", *sphere.center)
+
+ out_file.begin("sphere")
+ out_file.write("radius", sphere.radius)
+ out_file.end()
+
+ if use_center:
+ out_file.end()
+
+ elif shape_type=="BOX":
+ box = BoxFit(mesh)
+ error = box.fit()
+
+ use_center = box.center.length>min(box.dimensions)/1e5
+ if use_center:
+ out_file.begin("transformed")
+ out_file.write("translate", *box.center)
+
+ out_file.begin("box")
+ out_file.write("dimensions", *box.dimensions)
+ out_file.end()
+
+ if use_center:
+ out_file.end()