]> git.tdb.fi Git - libs/math.git/blob - blender/io_mspmath/export_shape.py
Add a Blender operator to export shapes
[libs/math.git] / blender / io_mspmath / export_shape.py
1 import math
2 from mathutils import Vector
3
4 class Vertex:
5         def __init__(self, v=None):
6                 self.vertex = v
7                 if v:
8                         self.co = v.co
9                 else:
10                         self.co = Vector((0, 0, 0))
11
12 class Edge:
13         def __init__(self, e=None):
14                 self.edge = e
15                 self.vertices = []
16                 self.polygons = []
17                 self.convex = True
18
19 class Polygon:
20         def __init__(self, p=None):
21                 self.polygon = p
22                 self.edges = []
23                 self.vertices = []
24                 if p:
25                         self.normal = p.normal
26                 else:
27                         self.normal = Vector((0, 0, 1))
28
29 class Mesh:
30         def __init__(self, m=None):
31                 if m:
32                         self.vertices = [Vertex(v) for v in m.vertices]
33                         self.edges = [Edge(e) for e in m.edges]
34                         self.polygons = [Polygon(p) for p in m.polygons]
35
36                         for e in self.edges:
37                                 e.vertices = [self.vertices[i] for i in e.edge.vertices]
38
39                         for p in self.polygons:
40                                 p.vertices = [self.vertices[i] for i in p.polygon.vertices]
41                                 p.edges = [self.edges[m.loops[i].edge_index] for i in p.polygon.loop_indices]
42                                 for e in p.edges:
43                                         e.polygons.append(p)
44
45                 else:
46                         self.edges = []
47                         self.vertices = []
48                         self.polygons = []
49                         self.convex = True
50
51 class SphereFit:
52         def __init__(self, mesh):
53                 self.mesh = mesh
54                 min_coords = tuple(map(min, zip(*(v.co for v in self.mesh.vertices))))
55                 max_coords = tuple(map(max, zip(*(v.co for v in self.mesh.vertices))))
56                 self.center = (Vector(min_coords)+Vector(max_coords))/2
57                 self.radius = max(max_coords[i]-min_coords[i] for i in range(3))/2
58
59         def calculate_center_error(self):
60                 error = Vector()
61                 for v in self.mesh.vertices:
62                         p = v.co-self.center
63                         p.normalize()
64                         p = self.center+p*self.radius
65                         error += p-v.co
66
67                 return error/len(self.mesh.vertices)
68
69         def calculate_radius_error(self):
70                 error = 0
71                 for v in self.mesh.vertices:
72                         error += self.radius-(v.co-self.center).length
73                 return error/len(self.mesh.vertices)
74
75         def calculate_fit_error(self):
76                 error = 0
77                 for v in self.mesh.vertices:
78                         error += (self.radius-(v.co-self.center).length)**2
79                 return math.sqrt(error/len(self.mesh.vertices))
80
81         def fit(self, epsilon=1e-3):
82                 while 1:
83                         error = self.calculate_radius_error()
84                         if error<epsilon:
85                                 return self.calculate_fit_error()
86
87                         self.center -= self.calculate_center_error()
88                         self.radius -= self.calculate_radius_error()
89
90 class BoxFit:
91         def __init__(self, mesh):
92                 self.mesh = mesh
93                 min_coords = tuple(map(min, zip(*(v.co for v in self.mesh.vertices))))
94                 max_coords = tuple(map(max, zip(*(v.co for v in self.mesh.vertices))))
95                 self.ranges = [[min_coords[i], max_coords[i]] for i in range(3)]
96                 self.update()
97
98         def update(self):
99                 self.dimensions = tuple(r[1]-r[0] for r in self.ranges)
100                 self.center = Vector(tuple((r[0]+r[1])/2 for r in self.ranges))
101
102         def calculate_side_errors(self):
103                 errors = [[0, 0], [0, 0], [0, 0]]
104                 counts = [[0, 0], [0, 0], [0, 0]]
105                 for v in self.mesh.vertices:
106                         distances = tuple((r[0]-v.co[i], v.co[i]-r[1]) for i, r in enumerate(self.ranges))
107                         side = (0, 0)
108                         for i, d in enumerate(distances):
109                                 for j in range(2):
110                                         if d[j]>distances[side[0]][side[1]]:
111                                                 side = (i, j)
112
113                         errors[side[0]][side[1]] += distances[side[0]][side[1]]
114                         counts[side[0]][side[1]] += 1
115
116                 for i in range(3):
117                         for j in range(2):
118                                 if counts[i][j]:
119                                         errors[i][j] /= counts[i][j]
120
121                 return errors
122
123         def calculate_fit_error(self):
124                 error = 0
125                 for v in self.mesh.vertices:
126                         distances = tuple((r[0]-v.co[i], v.co[i]-r[1]) for i, r in enumerate(self.ranges))
127                         error += max(max(*d) for d in distances)**2
128                 return math.sqrt(error/len(self.mesh.vertices))
129
130         def fit(self, epsilon=1e-3):
131                 while 1:
132                         errors = self.calculate_side_errors()
133                         max_error = max(max(map(abs, e)) for e in errors)
134                         if max_error<epsilon:
135                                 self.update()
136                                 return self.calculate_fit_error()
137
138                         for i in range(3):
139                                 for j in range(2):
140                                         e = errors[i][j]
141                                         if j==0:
142                                                 e = -e
143                                         self.ranges[i][j] += e
144
145 class ShapeExporter:
146         def export(self, context, out_file):
147                 obj = context.active_object
148                 if obj.type!="MESH":
149                         raise Exception("Can only export mesh data")
150
151                 mesh = Mesh(obj.data)
152
153                 from .outfile import open_output
154                 out_file = open_output(out_file)
155
156                 shape_type = obj.shape_type
157                 if shape_type=="AUTO":
158                         errors = []
159                         errors.append(("SPHERE", SphereFit(mesh).fit()))
160                         errors.append(("BOX", BoxFit(mesh).fit()))
161                         errors.sort(key=lambda x:x[1])
162                         print(errors)
163                         shape_type = errors[0][0]
164
165                 if shape_type=="SPHERE":
166                         sphere = SphereFit(mesh)
167                         error = sphere.fit()
168
169                         use_center = sphere.center.length>sphere.radius/1e5
170                         if use_center:
171                                 out_file.begin("transformed")
172                                 out_file.write("translate", *sphere.center)
173
174                         out_file.begin("sphere")
175                         out_file.write("radius", sphere.radius)
176                         out_file.end()
177
178                         if use_center:
179                                 out_file.end()
180
181                 elif shape_type=="BOX":
182                         box = BoxFit(mesh)
183                         error = box.fit()
184
185                         use_center = box.center.length>min(box.dimensions)/1e5
186                         if use_center:
187                                 out_file.begin("transformed")
188                                 out_file.write("translate", *box.center)
189
190                         out_file.begin("box")
191                         out_file.write("dimensions", *box.dimensions)
192                         out_file.end()
193
194                         if use_center:
195                                 out_file.end()