]> git.tdb.fi Git - libs/math.git/commitdiff
Add a Blender operator to export shapes
authorMikko Rasa <tdb@tdb.fi>
Tue, 9 Feb 2016 15:04:50 +0000 (17:04 +0200)
committerMikko Rasa <tdb@tdb.fi>
Tue, 9 Feb 2016 15:04:50 +0000 (17:04 +0200)
blender/io_mspmath/__init__.py [new file with mode: 0644]
blender/io_mspmath/export_shape.py [new file with mode: 0644]
blender/io_mspmath/outfile.py [new file with mode: 0644]
blender/io_mspmath/properties.py [new file with mode: 0644]

diff --git a/blender/io_mspmath/__init__.py b/blender/io_mspmath/__init__.py
new file mode 100644 (file)
index 0000000..1490ce6
--- /dev/null
@@ -0,0 +1,48 @@
+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()
diff --git a/blender/io_mspmath/export_shape.py b/blender/io_mspmath/export_shape.py
new file mode 100644 (file)
index 0000000..793daae
--- /dev/null
@@ -0,0 +1,195 @@
+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()
diff --git a/blender/io_mspmath/outfile.py b/blender/io_mspmath/outfile.py
new file mode 100644 (file)
index 0000000..2c747a8
--- /dev/null
@@ -0,0 +1,37 @@
+import sys
+
+class OutFile:
+       def __init__(self, fn):
+               self.filename = fn
+               if fn==None:
+                       self.file = sys.stdout
+               else:
+                       self.file = open(fn, "w")
+               self.indent = 0
+
+       def make(self, kwd, *params):
+               pstr = ""
+               for p in params:
+                       if type(p)==float:
+                               pstr += " %#.6g"%p
+                       else:
+                               pstr += " %s"%p
+               return "%s%s"%(kwd, pstr)
+
+       def write(self, kwd, *params):
+               self.file.write("%s%s;\n"%('\t'*self.indent, self.make(kwd, *params)))
+
+       def begin(self, kwd, *params):
+               i = '\t'*self.indent
+               self.file.write("%s%s\n%s{\n"%(i, self.make(kwd, *params), i))
+               self.indent += 1
+
+       def end(self):
+               self.indent -= 1
+               self.file.write("%s};\n"%('\t'*self.indent))
+
+
+def open_output(f):
+       if isinstance(f, OutFile):
+               return f
+       return OutFile(f)
diff --git a/blender/io_mspmath/properties.py b/blender/io_mspmath/properties.py
new file mode 100644 (file)
index 0000000..b296e5a
--- /dev/null
@@ -0,0 +1,19 @@
+import bpy
+
+class MspMathObjectProperties(bpy.types.Panel):
+       bl_idname = "OBJECT_PT_mspgl_properties"
+       bl_label = "MspGL properties"
+       bl_space_type = "PROPERTIES"
+       bl_region_type = "WINDOW"
+       bl_context = "object"
+
+       def draw(self, context):
+               obj = context.active_object
+
+               self.layout.prop(obj, "shape_type");
+
+def register_properties():
+       bpy.types.Object.shape_type = bpy.props.EnumProperty(name="Shape type", description="Type of shape to use for exporting this object", default="AUTO",
+               items=(("AUTO", "Automatic", "Automatic"),
+                       ("BOX", "Box", "Box"),
+                       ("SPHERE", "Sphere", "Sphere")))