]> git.tdb.fi Git - libs/math.git/blobdiff - blender/io_mspmath/export_shape.py
Add a Blender operator to export shapes
[libs/math.git] / blender / io_mspmath / export_shape.py
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()