-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmesh_file_export.py
435 lines (326 loc) · 14.6 KB
/
mesh_file_export.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
import bpy
import bmesh
import copy
import math
import bpy_extras
test_points = [
["Test", [0.12334, 343.32432, 123.576567],
[3654.12334, 123.33452, 123.576567],
[0.12334, 12.123, 123.45],
[0.12334, 23.123, 56.45]
]
]
test_vertices = [
[
[0.12334, 343.32432, 123.576567],
[3654.12334, 123.33452, 123.576567],
[0.12334, 12.123, 123.45],
0, 0.2344345, 0.34554654, 0.123343, 0.123343
]
]
test_tris = [
[0, 1, 2, 0]
]
#Thanks to Gaukler for these 3 functions
def create_export_list(collection):
export_list = []
if(collection.hide_viewport):
return export_list
for object in collection.objects:
if(object.type == 'MESH' and object.hide_viewport == False):
export_list.append(object)
for child in collection.children:
export_list.extend(create_export_list(child))
return export_list
def clean_name(name):
# remove Blenders numbers at the end of names
if name[len(name) - 4:len(name) - 3] == ".":
cut = name[0:len(name) - 4]
return cut
else:
return name
def get_export_armature(collection):
if(collection.hide_viewport):
return None
for object in collection.objects:
if(object.type == 'ARMATURE' and object.hide_viewport == False):
return object
for child in collection.children:
return get_export_armature(child)
def write_indented(string, level):
return level * "\t" + string + "\n"
def write_labeled_int(label, value):
return label + " " + str(int(value))
def write_labeled_string(label, value):
conv = lambda i : i or ''
return label + " " + '"' + conv(value) + '"'
def write_labeled_hex(label, value):
return label + " " + str(value)
def write_labeled_float(label, value):
return label + " " + "{:.6f}".format(value)
def write_3list(list):
return ("[ " + "{:.6f}".format(list[0]) + " "
+ "{:.6f}".format(list[1]) + " "
+ "{:.6f}".format(list[2]) + " ]")
def write_labeled_3list(label, list):
return label + " " + write_3list(list)
def write_materials(diffuse_textures=[None],
self_illum_textures=[None],
normal_textures=[None],
disp_textures=[None],
tc_textures=[None],
diffuses=["ffffffff"],
ambients=["ffffffff"],
speculars=["ffffffff"],
emissives=["ffffffff"],
glosses=[50.0]):
"""
Writes Materials to string
Materials should have textures and values in lists where list index = material index
"""
number_materials = len(diffuse_textures)
output = write_indented(write_labeled_int("NumMaterials", number_materials), 1)
for i in range(number_materials):
print("Outputting material ", i+1)
diffuse_tex = write_labeled_string("DiffuseTextureFileName", diffuse_textures[i])
self_illum_tex = write_labeled_string("SelfIlluminationTextureFileName", self_illum_textures[i])
normal_tex = write_labeled_string("NormalTextureFileName", normal_textures[i])
disp_tex = write_labeled_string("DisplacementTextureFileName", disp_textures[i])
tc_tex = write_labeled_string("TeamColorTextureFileName", tc_textures[i])
diffuse = write_labeled_hex("Diffuse", diffuses[i])
ambient = write_labeled_hex("Ambient", ambients[i])
specular = write_labeled_hex("Specular", speculars[i])
emissive = write_labeled_hex("Emissive", emissives[i])
gloss =write_labeled_hex("Glossiness", glosses[i])
output += write_indented("Material", 1)
output += write_indented(diffuse_tex, 2)
output += write_indented(self_illum_tex, 2)
output += write_indented(normal_tex, 2)
output += write_indented(disp_tex, 2)
output += write_indented(tc_tex, 2)
output += write_indented(diffuse, 2)
output += write_indented(ambient, 2)
output += write_indented(specular, 2)
output += write_indented(emissive, 2)
output += write_indented(gloss, 2)
return output
def write_points(points):
"""
Writes Points to string
Points should be in format
[[Name, Position [x, y, z], Orientation [x, y, z], [x, y, z], [x, y, z]]]
"""
number_points = len(points)
output = write_indented(write_labeled_int("NumPoints", number_points), 1)
print("Outputting ", number_points, " points")
if number_points == 0:
return output
for point in points:
datastring = write_labeled_string("DataString", point[0])
position = write_labeled_3list("Position", point[1])
orientation1 = " " + write_3list(point[2])
orientation2 = " " + write_3list(point[3])
orientation3 = " " + write_3list(point[4])
output += write_indented("Point", 1)
output += write_indented(datastring, 2)
output += write_indented(position, 2)
output += write_indented("Orientation", 2)
output += write_indented(orientation1, 3)
output += write_indented(orientation2, 3)
output += write_indented(orientation3, 3)
return output
def write_vertices(vertices):
"""
Writes verts to string
Vertices should be in format
[[Position [x, y, z], Normal [x, y, z], Tangent [x, y, z], Color, U0, V0, U1, V1]]
"""
number_verts = len(vertices)
output = write_indented(write_labeled_int("NumVertices", number_verts), 1)
print("Outputting ", number_verts, " vertices")
for vert in vertices:
position = write_labeled_3list("Position", vert[0])
normal = write_labeled_3list("Normal", vert[1])
tangent = write_labeled_3list("Tangent", vert[2])
color = write_labeled_int("Color", vert[3])
u0 = write_labeled_float("U0", vert[4])
v0 = write_labeled_float("V0", vert[5])
u1 = write_labeled_float("U1", vert[6])
v1 = write_labeled_float("V1", vert[7])
output += write_indented("Vertex", 1)
output += write_indented(position, 2)
output += write_indented(normal, 2)
output += write_indented(tangent, 2)
output += write_indented(color, 2)
output += write_indented(u0, 2)
output += write_indented(v0, 2)
output += write_indented(u1, 2)
output += write_indented(v1, 2)
return output
def write_triangles(triangles):
"""
Writes faces to string
Triangles should be in format
[[vertex0, vertex1, vertex2, material index]]
"""
number_tris = len(triangles)
output = write_indented(write_labeled_int("NumTriangles", number_tris), 1)
print("Outputting ", number_tris, " triangles")
for tri in triangles:
vert0 = write_labeled_int("iVertex0", tri[0])
vert1 = write_labeled_int("iVertex1", tri[1])
vert2 = write_labeled_int("iVertex2", tri[2])
mat = write_labeled_int("iMaterial", tri[3])
output += write_indented("Triangle", 1)
output += write_indented(vert0, 2)
output += write_indented(vert1, 2)
output += write_indented(vert2, 2)
output += write_indented(mat, 2)
return output
def write_mesh_data(context, filepath):
print("running write_mesh_data...")
collection = bpy.context.scene.collection
axis_convertor = bpy_extras.io_utils.axis_conversion(to_forward='Z', to_up='-Y')
armature_object = get_export_armature(collection)
if armature_object:
export_armature = armature_object.data
else:
export_armature = None
mesh_list = create_export_list(collection)
#Join mesh objects, DESTRUCTIVE
for object in mesh_list:
context.view_layer.objects.active = object
object.select_set(True)
bpy.ops.object.join()
#Setup mesh object
object = context.view_layer.objects.active
depsgraph = context.evaluated_depsgraph_get()
object_eval = object.evaluated_get(depsgraph)
mesh = bpy.data.meshes.new_from_object(object_eval, preserve_all_data_layers=True, depsgraph=depsgraph)
mesh.calc_tangents()
#Create bmesh
bm = bmesh.new()
bm.from_mesh(mesh)
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
bmesh.ops.split_edges(bm, edges=bm.edges)
bmesh.ops.triangulate(bm, faces=bm.faces)
vertices = [None] * len(bm.verts)
triangles = [None] * len(bm.faces)
bm.verts.index_update()
bm.faces.index_update()
#Set up UV layers
uv_layer_0 = bm.loops.layers.uv[0]
if len(bm.loops.layers.uv) > 1:
uv_layer_1 = bm.loops.layers.uv[1]
else:
uv_layer_1 = uv_layer_0
#Set up triangle and vert lists
for face in bm.faces:
face_index = face.index
faceverts = [0] * 4
for i, loop in enumerate(face.loops):
vert = loop.vert
index = vert.index
faceverts[i] = index
vert_pos = vert.co @ axis_convertor
vert_normal = vert.normal @ axis_convertor
vert_tangent = copy.copy(mesh.loops[index].tangent @ axis_convertor)
vert_u0 = loop[uv_layer_0].uv.x
vert_v0 = 1 - loop[uv_layer_0].uv.y
vert_u1 = loop[uv_layer_1].uv.x
vert_v1 = 1 - loop[uv_layer_1].uv.y
vertices[index] = [vert_pos,
vert_normal,
vert_tangent,
0,
vert_u0,
vert_v0,
vert_u1,
vert_v1]
faceverts[3] = face.material_index
triangles[face_index] = faceverts
if export_armature:
points = []
for bone in export_armature.bones:
name = bone.name
position = (bone.head_local @ axis_convertor).to_tuple()
orientation = bone.matrix_local.to_3x3() @ axis_convertor
points.append([name, position, orientation[0].to_tuple(), orientation[1].to_tuple(),orientation[2].to_tuple()])
else:
points = []
print(points)
#Bounding box calculations
bounding_box = object.bound_box
summed_box = [xyz[0] + xyz[1] + xyz[2] for xyz in bounding_box]
summed_sq_box = [xyz[0]**2 + xyz[1]**2 + xyz[2]**2 for xyz in bounding_box]
max_extent = max(summed_box)
min_extent = min(summed_box)
max_ext_index = summed_box.index(max_extent)
min_ext_index = summed_box.index(min_extent)
radius = math.sqrt(max(summed_sq_box))
#Creating giant string, top level
file_out = "TXT\nMeshData\n"
#Header info
file_out += write_indented("maxDiffuseMipLevel 0", 1)
file_out += write_indented("hasValidTangents TRUE", 1)
bounding_rad = write_labeled_float("BoundingRadius", radius)
max_bound = write_labeled_3list("MaxBoundingExtents", bounding_box[max_ext_index])
min_bound = write_labeled_3list("MinBoundingExtents", bounding_box[min_ext_index])
file_out += write_indented(bounding_rad, 1)
file_out += write_indented(max_bound, 1)
file_out += write_indented(min_bound, 1)
#Important stuff; materials, bones, verts, faces
file_out += write_materials()
file_out += write_points(points)
file_out += write_vertices(vertices)
file_out += write_triangles(triangles)
#Cached verts
cached_up = write_labeled_int("NumCachedVertexIndicesInDirection:UP", 0)
cached_down = write_labeled_int("NumCachedVertexIndicesInDirection:DOWN", 0)
cached_left = write_labeled_int("NumCachedVertexIndicesInDirection:LEFT", 0)
cached_right = write_labeled_int("NumCachedVertexIndicesInDirection:RIGHT", 0)
cached_front = write_labeled_int("NumCachedVertexIndicesInDirection:FRONT", 0)
cached_back = write_labeled_int("NumCachedVertexIndicesInDirection:BACK", 0)
file_out += write_indented(cached_up, 1)
file_out += write_indented(cached_down, 1)
file_out += write_indented(cached_left, 1)
file_out += write_indented(cached_right, 1)
file_out += write_indented(cached_front, 1)
file_out += write_indented(cached_back, 1)
f = open(filepath, 'w', encoding='utf-8')
f.write(file_out)
f.close()
print("Export complete")
return {'FINISHED'}
# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator
class ExportMeshData(Operator, ExportHelper):
"""This appears in the tooltip of the operator and in the generated docs"""
bl_idname = "export_test.mesh_data" # important since its how bpy.ops.import_test.mesh_data is constructed
bl_label = "Export .mesh"
# ExportHelper mixin class uses this
filename_ext = ".mesh"
filter_glob: StringProperty(
default="*.mesh",
options={'HIDDEN'},
maxlen=255, # Max internal buffer length, longer would be clamped.
)
def execute(self, context):
return write_mesh_data(context, self.filepath)
# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
self.layout.operator(ExportMeshData.bl_idname, text="SOASE (.mesh)")
def register():
bpy.utils.register_class(ExportMeshData)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(ExportMeshData)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
# test call
bpy.ops.export_test.mesh_data('INVOKE_DEFAULT')