1 | # ##### BEGIN GPL LICENSE BLOCK #####
|
---|
2 | #
|
---|
3 | # This program is free software; you can redistribute it and/or
|
---|
4 | # modify it under the terms of the GNU General Public License
|
---|
5 | # as published by the Free Software Foundation; either version 2
|
---|
6 | # of the License, or (at your option) any later version.
|
---|
7 | #
|
---|
8 | # This program is distributed in the hope that it will be useful,
|
---|
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
11 | # GNU General Public License for more details.
|
---|
12 | #
|
---|
13 | # You should have received a copy of the GNU General Public License
|
---|
14 | # along with this program; if not, write to the Free Software Foundation,
|
---|
15 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
---|
16 | #
|
---|
17 | # ##### END GPL LICENSE BLOCK #####
|
---|
18 |
|
---|
19 | """
|
---|
20 | Blender exporter for Three.js (ASCII JSON format).
|
---|
21 |
|
---|
22 | TODO
|
---|
23 | - binary format
|
---|
24 | """
|
---|
25 |
|
---|
26 | import bpy
|
---|
27 | import mathutils
|
---|
28 |
|
---|
29 | import shutil
|
---|
30 | import os
|
---|
31 | import os.path
|
---|
32 | import math
|
---|
33 | import operator
|
---|
34 | import random
|
---|
35 |
|
---|
36 | # #####################################################
|
---|
37 | # Configuration
|
---|
38 | # #####################################################
|
---|
39 |
|
---|
40 | DEFAULTS = {
|
---|
41 | "bgcolor" : [0, 0, 0],
|
---|
42 | "bgalpha" : 1.0,
|
---|
43 |
|
---|
44 | "position" : [0, 0, 0],
|
---|
45 | "rotation" : [0, 0, 0],
|
---|
46 | "scale" : [1, 1, 1],
|
---|
47 |
|
---|
48 | "camera" :
|
---|
49 | {
|
---|
50 | "name" : "default_camera",
|
---|
51 | "type" : "PerspectiveCamera",
|
---|
52 | "near" : 1,
|
---|
53 | "far" : 10000,
|
---|
54 | "fov" : 60,
|
---|
55 | "aspect": 1.333,
|
---|
56 | "position" : [0, 0, 10],
|
---|
57 | "target" : [0, 0, 0]
|
---|
58 | },
|
---|
59 |
|
---|
60 | "light" :
|
---|
61 | {
|
---|
62 | "name" : "default_light",
|
---|
63 | "type" : "DirectionalLight",
|
---|
64 | "direction" : [0, 1, 1],
|
---|
65 | "color" : [1, 1, 1],
|
---|
66 | "intensity" : 0.8
|
---|
67 | }
|
---|
68 | }
|
---|
69 |
|
---|
70 | ROTATE_X_PI2 = mathutils.Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)).to_matrix().to_4x4()
|
---|
71 |
|
---|
72 | # default colors for debugging (each material gets one distinct color):
|
---|
73 | # white, red, green, blue, yellow, cyan, magenta
|
---|
74 | COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
|
---|
75 |
|
---|
76 |
|
---|
77 | # skinning
|
---|
78 | MAX_INFLUENCES = 2
|
---|
79 |
|
---|
80 |
|
---|
81 | # #####################################################
|
---|
82 | # Templates - scene
|
---|
83 | # #####################################################
|
---|
84 |
|
---|
85 | TEMPLATE_SCENE_ASCII = """\
|
---|
86 | {
|
---|
87 |
|
---|
88 | "metadata" :
|
---|
89 | {
|
---|
90 | "formatVersion" : 3.2,
|
---|
91 | "type" : "scene",
|
---|
92 | "sourceFile" : "%(fname)s",
|
---|
93 | "generatedBy" : "Blender 2.65 Exporter",
|
---|
94 | "objects" : %(nobjects)s,
|
---|
95 | "geometries" : %(ngeometries)s,
|
---|
96 | "materials" : %(nmaterials)s,
|
---|
97 | "textures" : %(ntextures)s
|
---|
98 | },
|
---|
99 |
|
---|
100 | "urlBaseType" : %(basetype)s,
|
---|
101 |
|
---|
102 | %(sections)s
|
---|
103 |
|
---|
104 | "transform" :
|
---|
105 | {
|
---|
106 | "position" : %(position)s,
|
---|
107 | "rotation" : %(rotation)s,
|
---|
108 | "scale" : %(scale)s
|
---|
109 | },
|
---|
110 |
|
---|
111 | "defaults" :
|
---|
112 | {
|
---|
113 | "bgcolor" : %(bgcolor)s,
|
---|
114 | "bgalpha" : %(bgalpha)f,
|
---|
115 | "camera" : %(defcamera)s
|
---|
116 | }
|
---|
117 |
|
---|
118 | }
|
---|
119 | """
|
---|
120 |
|
---|
121 | TEMPLATE_SECTION = """
|
---|
122 | "%s" :
|
---|
123 | {
|
---|
124 | %s
|
---|
125 | },
|
---|
126 | """
|
---|
127 |
|
---|
128 | TEMPLATE_OBJECT = """\
|
---|
129 | %(object_id)s : {
|
---|
130 | "geometry" : %(geometry_id)s,
|
---|
131 | "groups" : [ %(group_id)s ],
|
---|
132 | "material" : %(material_id)s,
|
---|
133 | "position" : %(position)s,
|
---|
134 | "rotation" : %(rotation)s,
|
---|
135 | "quaternion": %(quaternion)s,
|
---|
136 | "scale" : %(scale)s,
|
---|
137 | "visible" : %(visible)s,
|
---|
138 | "castShadow" : %(castShadow)s,
|
---|
139 | "receiveShadow" : %(receiveShadow)s,
|
---|
140 | "doubleSided" : %(doubleSided)s
|
---|
141 | }"""
|
---|
142 |
|
---|
143 | TEMPLATE_EMPTY = """\
|
---|
144 | %(object_id)s : {
|
---|
145 | "groups" : [ %(group_id)s ],
|
---|
146 | "position" : %(position)s,
|
---|
147 | "rotation" : %(rotation)s,
|
---|
148 | "quaternion": %(quaternion)s,
|
---|
149 | "scale" : %(scale)s
|
---|
150 | }"""
|
---|
151 |
|
---|
152 | TEMPLATE_GEOMETRY_LINK = """\
|
---|
153 | %(geometry_id)s : {
|
---|
154 | "type" : "ascii",
|
---|
155 | "url" : %(model_file)s
|
---|
156 | }"""
|
---|
157 |
|
---|
158 | TEMPLATE_GEOMETRY_EMBED = """\
|
---|
159 | %(geometry_id)s : {
|
---|
160 | "type" : "embedded",
|
---|
161 | "id" : %(embed_id)s
|
---|
162 | }"""
|
---|
163 |
|
---|
164 | TEMPLATE_TEXTURE = """\
|
---|
165 | %(texture_id)s : {
|
---|
166 | "url": %(texture_file)s%(extras)s
|
---|
167 | }"""
|
---|
168 |
|
---|
169 | TEMPLATE_MATERIAL_SCENE = """\
|
---|
170 | %(material_id)s : {
|
---|
171 | "type": %(type)s,
|
---|
172 | "parameters": { %(parameters)s }
|
---|
173 | }"""
|
---|
174 |
|
---|
175 | TEMPLATE_CAMERA_PERSPECTIVE = """\
|
---|
176 | %(camera_id)s : {
|
---|
177 | "type" : "PerspectiveCamera",
|
---|
178 | "fov" : %(fov)f,
|
---|
179 | "aspect": %(aspect)f,
|
---|
180 | "near" : %(near)f,
|
---|
181 | "far" : %(far)f,
|
---|
182 | "position": %(position)s,
|
---|
183 | "target" : %(target)s
|
---|
184 | }"""
|
---|
185 |
|
---|
186 | TEMPLATE_CAMERA_ORTHO = """\
|
---|
187 | %(camera_id)s : {
|
---|
188 | "type" : "OrthographicCamera",
|
---|
189 | "left" : %(left)f,
|
---|
190 | "right" : %(right)f,
|
---|
191 | "top" : %(top)f,
|
---|
192 | "bottom": %(bottom)f,
|
---|
193 | "near" : %(near)f,
|
---|
194 | "far" : %(far)f,
|
---|
195 | "position": %(position)s,
|
---|
196 | "target" : %(target)s
|
---|
197 | }"""
|
---|
198 |
|
---|
199 | TEMPLATE_LIGHT_POINT = """\
|
---|
200 | %(light_id)s : {
|
---|
201 | "type" : "PointLight",
|
---|
202 | "position" : %(position)s,
|
---|
203 | "rotation" : %(rotation)s,
|
---|
204 | "color" : %(color)d,
|
---|
205 | "distance" : %(distance).3f,
|
---|
206 | "intensity" : %(intensity).3f
|
---|
207 | }"""
|
---|
208 |
|
---|
209 | TEMPLATE_LIGHT_SUN = """\
|
---|
210 | %(light_id)s : {
|
---|
211 | "type" : "AmbientLight",
|
---|
212 | "position" : %(position)s,
|
---|
213 | "rotation" : %(rotation)s,
|
---|
214 | "color" : %(color)d,
|
---|
215 | "distance" : %(distance).3f,
|
---|
216 | "intensity" : %(intensity).3f
|
---|
217 | }"""
|
---|
218 |
|
---|
219 | TEMPLATE_LIGHT_SPOT = """\
|
---|
220 | %(light_id)s : {
|
---|
221 | "type" : "SpotLight",
|
---|
222 | "position" : %(position)s,
|
---|
223 | "rotation" : %(rotation)s,
|
---|
224 | "color" : %(color)d,
|
---|
225 | "distance" : %(distance).3f,
|
---|
226 | "intensity" : %(intensity).3f,
|
---|
227 | "use_shadow" : %(use_shadow)d,
|
---|
228 | "angle" : %(angle).3f
|
---|
229 | }"""
|
---|
230 |
|
---|
231 | TEMPLATE_LIGHT_HEMI = """\
|
---|
232 | %(light_id)s : {
|
---|
233 | "type" : "HemisphereLight",
|
---|
234 | "position" : %(position)s,
|
---|
235 | "rotation" : %(rotation)s,
|
---|
236 | "color" : %(color)d,
|
---|
237 | "distance" : %(distance).3f,
|
---|
238 | "intensity" : %(intensity).3f
|
---|
239 | }"""
|
---|
240 |
|
---|
241 | TEMPLATE_LIGHT_AREA = """\
|
---|
242 | %(light_id)s : {
|
---|
243 | "type" : "AreaLight",
|
---|
244 | "position" : %(position)s,
|
---|
245 | "rotation" : %(rotation)s,
|
---|
246 | "color" : %(color)d,
|
---|
247 | "distance" : %(distance).3f,
|
---|
248 | "intensity" : %(intensity).3f,
|
---|
249 | "gamma" : %(gamma).3f,
|
---|
250 | "shape" : "%(shape)s",
|
---|
251 | "size" : %(size).3f,
|
---|
252 | "size_y" : %(size_y).3f
|
---|
253 | }"""
|
---|
254 |
|
---|
255 |
|
---|
256 | TEMPLATE_VEC4 = '[ %g, %g, %g, %g ]'
|
---|
257 | TEMPLATE_VEC3 = '[ %g, %g, %g ]'
|
---|
258 | TEMPLATE_VEC2 = '[ %g, %g ]'
|
---|
259 | TEMPLATE_STRING = '"%s"'
|
---|
260 | TEMPLATE_HEX = "0x%06x"
|
---|
261 |
|
---|
262 | # #####################################################
|
---|
263 | # Templates - model
|
---|
264 | # #####################################################
|
---|
265 |
|
---|
266 | TEMPLATE_FILE_ASCII = """\
|
---|
267 | {
|
---|
268 |
|
---|
269 | "metadata" :
|
---|
270 | {
|
---|
271 | "formatVersion" : 3.1,
|
---|
272 | "generatedBy" : "Blender 2.65 Exporter",
|
---|
273 | "vertices" : %(nvertex)d,
|
---|
274 | "faces" : %(nface)d,
|
---|
275 | "normals" : %(nnormal)d,
|
---|
276 | "colors" : %(ncolor)d,
|
---|
277 | "uvs" : [%(nuvs)s],
|
---|
278 | "materials" : %(nmaterial)d,
|
---|
279 | "morphTargets" : %(nmorphTarget)d,
|
---|
280 | "bones" : %(nbone)d
|
---|
281 | },
|
---|
282 |
|
---|
283 | %(model)s
|
---|
284 |
|
---|
285 | }
|
---|
286 | """
|
---|
287 |
|
---|
288 | TEMPLATE_MODEL_ASCII = """\
|
---|
289 | "scale" : %(scale)f,
|
---|
290 |
|
---|
291 | "materials" : [%(materials)s],
|
---|
292 |
|
---|
293 | "vertices" : [%(vertices)s],
|
---|
294 |
|
---|
295 | "morphTargets" : [%(morphTargets)s],
|
---|
296 |
|
---|
297 | "normals" : [%(normals)s],
|
---|
298 |
|
---|
299 | "colors" : [%(colors)s],
|
---|
300 |
|
---|
301 | "uvs" : [%(uvs)s],
|
---|
302 |
|
---|
303 | "faces" : [%(faces)s],
|
---|
304 |
|
---|
305 | "bones" : [%(bones)s],
|
---|
306 |
|
---|
307 | "skinIndices" : [%(indices)s],
|
---|
308 |
|
---|
309 | "skinWeights" : [%(weights)s],
|
---|
310 |
|
---|
311 | "animations" : [%(animations)s]
|
---|
312 | """
|
---|
313 |
|
---|
314 | TEMPLATE_VERTEX = "%g,%g,%g"
|
---|
315 | TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
|
---|
316 |
|
---|
317 | TEMPLATE_N = "%g,%g,%g"
|
---|
318 | TEMPLATE_UV = "%g,%g"
|
---|
319 | TEMPLATE_C = "%d"
|
---|
320 |
|
---|
321 | # #####################################################
|
---|
322 | # Utils
|
---|
323 | # #####################################################
|
---|
324 |
|
---|
325 | def veckey3(x,y,z):
|
---|
326 | return round(x, 6), round(y, 6), round(z, 6)
|
---|
327 |
|
---|
328 | def veckey3d(v):
|
---|
329 | return veckey3(v.x, v.y, v.z)
|
---|
330 |
|
---|
331 | def veckey2d(v):
|
---|
332 | return round(v[0], 6), round(v[1], 6)
|
---|
333 |
|
---|
334 | def get_faces(obj):
|
---|
335 | if hasattr(obj, "tessfaces"):
|
---|
336 | return obj.tessfaces
|
---|
337 | else:
|
---|
338 | return obj.faces
|
---|
339 |
|
---|
340 | def get_normal_indices(v, normals, mesh):
|
---|
341 | n = []
|
---|
342 | mv = mesh.vertices
|
---|
343 |
|
---|
344 | for i in v:
|
---|
345 | normal = mv[i].normal
|
---|
346 | key = veckey3d(normal)
|
---|
347 |
|
---|
348 | n.append( normals[key] )
|
---|
349 |
|
---|
350 | return n
|
---|
351 |
|
---|
352 | def get_uv_indices(face_index, uvs, mesh, layer_index):
|
---|
353 | uv = []
|
---|
354 | uv_layer = mesh.tessface_uv_textures[layer_index].data
|
---|
355 | for i in uv_layer[face_index].uv:
|
---|
356 | uv.append( uvs[veckey2d(i)] )
|
---|
357 | return uv
|
---|
358 |
|
---|
359 | def get_color_indices(face_index, colors, mesh):
|
---|
360 | c = []
|
---|
361 | color_layer = mesh.tessface_vertex_colors.active.data
|
---|
362 | face_colors = color_layer[face_index]
|
---|
363 | face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
|
---|
364 | for i in face_colors:
|
---|
365 | c.append( colors[hexcolor(i)] )
|
---|
366 | return c
|
---|
367 |
|
---|
368 | def rgb2int(rgb):
|
---|
369 | color = (int(rgb[0]*255) << 16) + (int(rgb[1]*255) << 8) + int(rgb[2]*255);
|
---|
370 | return color
|
---|
371 |
|
---|
372 | # #####################################################
|
---|
373 | # Utils - files
|
---|
374 | # #####################################################
|
---|
375 |
|
---|
376 | def write_file(fname, content):
|
---|
377 | out = open(fname, "w")
|
---|
378 | out.write(content)
|
---|
379 | out.close()
|
---|
380 |
|
---|
381 | def ensure_folder_exist(foldername):
|
---|
382 | """Create folder (with whole path) if it doesn't exist yet."""
|
---|
383 |
|
---|
384 | if not os.access(foldername, os.R_OK|os.W_OK|os.X_OK):
|
---|
385 | os.makedirs(foldername)
|
---|
386 |
|
---|
387 | def ensure_extension(filepath, extension):
|
---|
388 | if not filepath.lower().endswith(extension):
|
---|
389 | filepath += extension
|
---|
390 | return filepath
|
---|
391 |
|
---|
392 | def generate_mesh_filename(meshname, filepath):
|
---|
393 | normpath = os.path.normpath(filepath)
|
---|
394 | path, ext = os.path.splitext(normpath)
|
---|
395 | return "%s.%s%s" % (path, meshname, ext)
|
---|
396 |
|
---|
397 |
|
---|
398 | # #####################################################
|
---|
399 | # Utils - alignment
|
---|
400 | # #####################################################
|
---|
401 |
|
---|
402 | def bbox(vertices):
|
---|
403 | """Compute bounding box of vertex array.
|
---|
404 | """
|
---|
405 |
|
---|
406 | if len(vertices)>0:
|
---|
407 | minx = maxx = vertices[0].co.x
|
---|
408 | miny = maxy = vertices[0].co.y
|
---|
409 | minz = maxz = vertices[0].co.z
|
---|
410 |
|
---|
411 | for v in vertices[1:]:
|
---|
412 | if v.co.x < minx:
|
---|
413 | minx = v.co.x
|
---|
414 | elif v.co.x > maxx:
|
---|
415 | maxx = v.co.x
|
---|
416 |
|
---|
417 | if v.co.y < miny:
|
---|
418 | miny = v.co.y
|
---|
419 | elif v.co.y > maxy:
|
---|
420 | maxy = v.co.y
|
---|
421 |
|
---|
422 | if v.co.z < minz:
|
---|
423 | minz = v.co.z
|
---|
424 | elif v.co.z > maxz:
|
---|
425 | maxz = v.co.z
|
---|
426 |
|
---|
427 | return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
|
---|
428 |
|
---|
429 | else:
|
---|
430 | return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
|
---|
431 |
|
---|
432 | def translate(vertices, t):
|
---|
433 | """Translate array of vertices by vector t.
|
---|
434 | """
|
---|
435 |
|
---|
436 | for i in range(len(vertices)):
|
---|
437 | vertices[i].co.x += t[0]
|
---|
438 | vertices[i].co.y += t[1]
|
---|
439 | vertices[i].co.z += t[2]
|
---|
440 |
|
---|
441 | def center(vertices):
|
---|
442 | """Center model (middle of bounding box).
|
---|
443 | """
|
---|
444 |
|
---|
445 | bb = bbox(vertices)
|
---|
446 |
|
---|
447 | cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
|
---|
448 | cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
|
---|
449 | cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
|
---|
450 |
|
---|
451 | translate(vertices, [-cx,-cy,-cz])
|
---|
452 |
|
---|
453 | return [-cx,-cy,-cz]
|
---|
454 |
|
---|
455 | def top(vertices):
|
---|
456 | """Align top of the model with the floor (Y-axis) and center it around X and Z.
|
---|
457 | """
|
---|
458 |
|
---|
459 | bb = bbox(vertices)
|
---|
460 |
|
---|
461 | cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
|
---|
462 | cy = bb['y'][1]
|
---|
463 | cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
|
---|
464 |
|
---|
465 | translate(vertices, [-cx,-cy,-cz])
|
---|
466 |
|
---|
467 | return [-cx,-cy,-cz]
|
---|
468 |
|
---|
469 | def bottom(vertices):
|
---|
470 | """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
|
---|
471 | """
|
---|
472 |
|
---|
473 | bb = bbox(vertices)
|
---|
474 |
|
---|
475 | cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
|
---|
476 | cy = bb['y'][0]
|
---|
477 | cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
|
---|
478 |
|
---|
479 | translate(vertices, [-cx,-cy,-cz])
|
---|
480 |
|
---|
481 | return [-cx,-cy,-cz]
|
---|
482 |
|
---|
483 | # #####################################################
|
---|
484 | # Elements rendering
|
---|
485 | # #####################################################
|
---|
486 |
|
---|
487 | def hexcolor(c):
|
---|
488 | return ( int(c[0] * 255) << 16 ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
|
---|
489 |
|
---|
490 | def generate_vertices(vertices, option_vertices_truncate, option_vertices):
|
---|
491 | if not option_vertices:
|
---|
492 | return ""
|
---|
493 |
|
---|
494 | return ",".join(generate_vertex(v, option_vertices_truncate) for v in vertices)
|
---|
495 |
|
---|
496 | def generate_vertex(v, option_vertices_truncate):
|
---|
497 | if not option_vertices_truncate:
|
---|
498 | return TEMPLATE_VERTEX % (v.co.x, v.co.y, v.co.z)
|
---|
499 | else:
|
---|
500 | return TEMPLATE_VERTEX_TRUNCATE % (v.co.x, v.co.y, v.co.z)
|
---|
501 |
|
---|
502 | def generate_normal(n):
|
---|
503 | return TEMPLATE_N % (n[0], n[1], n[2])
|
---|
504 |
|
---|
505 | def generate_vertex_color(c):
|
---|
506 | return TEMPLATE_C % c
|
---|
507 |
|
---|
508 | def generate_uv(uv):
|
---|
509 | return TEMPLATE_UV % (uv[0], uv[1])
|
---|
510 |
|
---|
511 | # #####################################################
|
---|
512 | # Model exporter - faces
|
---|
513 | # #####################################################
|
---|
514 |
|
---|
515 | def setBit(value, position, on):
|
---|
516 | if on:
|
---|
517 | mask = 1 << position
|
---|
518 | return (value | mask)
|
---|
519 | else:
|
---|
520 | mask = ~(1 << position)
|
---|
521 | return (value & mask)
|
---|
522 |
|
---|
523 | def generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces):
|
---|
524 |
|
---|
525 | if not option_faces:
|
---|
526 | return "", 0
|
---|
527 |
|
---|
528 | vertex_offset = 0
|
---|
529 | material_offset = 0
|
---|
530 |
|
---|
531 | chunks = []
|
---|
532 | for mesh, object in meshes:
|
---|
533 |
|
---|
534 | vertexUV = len(mesh.uv_textures) > 0
|
---|
535 | vertexColors = len(mesh.vertex_colors) > 0
|
---|
536 |
|
---|
537 | mesh_colors = option_colors and vertexColors
|
---|
538 | mesh_uvs = option_uv_coords and vertexUV
|
---|
539 |
|
---|
540 | if vertexUV:
|
---|
541 | active_uv_layer = mesh.uv_textures.active
|
---|
542 | if not active_uv_layer:
|
---|
543 | mesh_extract_uvs = False
|
---|
544 |
|
---|
545 | if vertexColors:
|
---|
546 | active_col_layer = mesh.vertex_colors.active
|
---|
547 | if not active_col_layer:
|
---|
548 | mesh_extract_colors = False
|
---|
549 |
|
---|
550 | for i, f in enumerate(get_faces(mesh)):
|
---|
551 | face = generate_face(f, i, normals, uv_layers, colors, mesh, option_normals, mesh_colors, mesh_uvs, option_materials, vertex_offset, material_offset)
|
---|
552 | chunks.append(face)
|
---|
553 |
|
---|
554 | vertex_offset += len(mesh.vertices)
|
---|
555 |
|
---|
556 | material_count = len(mesh.materials)
|
---|
557 | if material_count == 0:
|
---|
558 | material_count = 1
|
---|
559 |
|
---|
560 | material_offset += material_count
|
---|
561 |
|
---|
562 | return ",".join(chunks), len(chunks)
|
---|
563 |
|
---|
564 | def generate_face(f, faceIndex, normals, uv_layers, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, vertex_offset, material_offset):
|
---|
565 | isTriangle = ( len(f.vertices) == 3 )
|
---|
566 |
|
---|
567 | if isTriangle:
|
---|
568 | nVertices = 3
|
---|
569 | else:
|
---|
570 | nVertices = 4
|
---|
571 |
|
---|
572 | hasMaterial = option_materials
|
---|
573 |
|
---|
574 | hasFaceUvs = False # not supported in Blender
|
---|
575 | hasFaceVertexUvs = option_uv_coords
|
---|
576 |
|
---|
577 | hasFaceNormals = False # don't export any face normals (as they are computed in engine)
|
---|
578 | hasFaceVertexNormals = option_normals
|
---|
579 |
|
---|
580 | hasFaceColors = False # not supported in Blender
|
---|
581 | hasFaceVertexColors = option_colors
|
---|
582 |
|
---|
583 | faceType = 0
|
---|
584 | faceType = setBit(faceType, 0, not isTriangle)
|
---|
585 | faceType = setBit(faceType, 1, hasMaterial)
|
---|
586 | faceType = setBit(faceType, 2, hasFaceUvs)
|
---|
587 | faceType = setBit(faceType, 3, hasFaceVertexUvs)
|
---|
588 | faceType = setBit(faceType, 4, hasFaceNormals)
|
---|
589 | faceType = setBit(faceType, 5, hasFaceVertexNormals)
|
---|
590 | faceType = setBit(faceType, 6, hasFaceColors)
|
---|
591 | faceType = setBit(faceType, 7, hasFaceVertexColors)
|
---|
592 |
|
---|
593 | faceData = []
|
---|
594 |
|
---|
595 | # order is important, must match order in JSONLoader
|
---|
596 |
|
---|
597 | # face type
|
---|
598 | # vertex indices
|
---|
599 | # material index
|
---|
600 | # face uvs index
|
---|
601 | # face vertex uvs indices
|
---|
602 | # face color index
|
---|
603 | # face vertex colors indices
|
---|
604 |
|
---|
605 | faceData.append(faceType)
|
---|
606 |
|
---|
607 | # must clamp in case on polygons bigger than quads
|
---|
608 |
|
---|
609 | for i in range(nVertices):
|
---|
610 | index = f.vertices[i] + vertex_offset
|
---|
611 | faceData.append(index)
|
---|
612 |
|
---|
613 | if hasMaterial:
|
---|
614 | index = f.material_index + material_offset
|
---|
615 | faceData.append( index )
|
---|
616 |
|
---|
617 | if hasFaceVertexUvs:
|
---|
618 | for layer_index, uvs in enumerate(uv_layers):
|
---|
619 | uv = get_uv_indices(faceIndex, uvs, mesh, layer_index)
|
---|
620 | for i in range(nVertices):
|
---|
621 | index = uv[i]
|
---|
622 | faceData.append(index)
|
---|
623 |
|
---|
624 | if hasFaceVertexNormals:
|
---|
625 | n = get_normal_indices(f.vertices, normals, mesh)
|
---|
626 | for i in range(nVertices):
|
---|
627 | index = n[i]
|
---|
628 | faceData.append(index)
|
---|
629 |
|
---|
630 | if hasFaceVertexColors:
|
---|
631 | c = get_color_indices(faceIndex, colors, mesh)
|
---|
632 | for i in range(nVertices):
|
---|
633 | index = c[i]
|
---|
634 | faceData.append(index)
|
---|
635 |
|
---|
636 | return ",".join( map(str, faceData) )
|
---|
637 |
|
---|
638 |
|
---|
639 | # #####################################################
|
---|
640 | # Model exporter - normals
|
---|
641 | # #####################################################
|
---|
642 |
|
---|
643 | def extract_vertex_normals(mesh, normals, count):
|
---|
644 | for f in get_faces(mesh):
|
---|
645 | for v in f.vertices:
|
---|
646 |
|
---|
647 | normal = mesh.vertices[v].normal
|
---|
648 | key = veckey3d(normal)
|
---|
649 |
|
---|
650 | if key not in normals:
|
---|
651 | normals[key] = count
|
---|
652 | count += 1
|
---|
653 |
|
---|
654 | return count
|
---|
655 |
|
---|
656 | def generate_normals(normals, option_normals):
|
---|
657 | if not option_normals:
|
---|
658 | return ""
|
---|
659 |
|
---|
660 | chunks = []
|
---|
661 | for key, index in sorted(normals.items(), key = operator.itemgetter(1)):
|
---|
662 | chunks.append(key)
|
---|
663 |
|
---|
664 | return ",".join(generate_normal(n) for n in chunks)
|
---|
665 |
|
---|
666 | # #####################################################
|
---|
667 | # Model exporter - vertex colors
|
---|
668 | # #####################################################
|
---|
669 |
|
---|
670 | def extract_vertex_colors(mesh, colors, count):
|
---|
671 | color_layer = mesh.tessface_vertex_colors.active.data
|
---|
672 |
|
---|
673 | for face_index, face in enumerate(get_faces(mesh)):
|
---|
674 |
|
---|
675 | face_colors = color_layer[face_index]
|
---|
676 | face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
|
---|
677 |
|
---|
678 | for c in face_colors:
|
---|
679 | key = hexcolor(c)
|
---|
680 | if key not in colors:
|
---|
681 | colors[key] = count
|
---|
682 | count += 1
|
---|
683 |
|
---|
684 | return count
|
---|
685 |
|
---|
686 | def generate_vertex_colors(colors, option_colors):
|
---|
687 | if not option_colors:
|
---|
688 | return ""
|
---|
689 |
|
---|
690 | chunks = []
|
---|
691 | for key, index in sorted(colors.items(), key=operator.itemgetter(1)):
|
---|
692 | chunks.append(key)
|
---|
693 |
|
---|
694 | return ",".join(generate_vertex_color(c) for c in chunks)
|
---|
695 |
|
---|
696 | # #####################################################
|
---|
697 | # Model exporter - UVs
|
---|
698 | # #####################################################
|
---|
699 |
|
---|
700 | def extract_uvs(mesh, uv_layers, counts):
|
---|
701 | for index, layer in enumerate(mesh.tessface_uv_textures):
|
---|
702 |
|
---|
703 | if len(uv_layers) <= index:
|
---|
704 | uvs = {}
|
---|
705 | count = 0
|
---|
706 | uv_layers.append(uvs)
|
---|
707 | counts.append(count)
|
---|
708 | else:
|
---|
709 | uvs = uv_layers[index]
|
---|
710 | count = counts[index]
|
---|
711 |
|
---|
712 | uv_layer = layer.data
|
---|
713 |
|
---|
714 | for face_index, face in enumerate(get_faces(mesh)):
|
---|
715 |
|
---|
716 | for uv_index, uv in enumerate(uv_layer[face_index].uv):
|
---|
717 |
|
---|
718 | key = veckey2d(uv)
|
---|
719 | if key not in uvs:
|
---|
720 | uvs[key] = count
|
---|
721 | count += 1
|
---|
722 |
|
---|
723 | counts[index] = count
|
---|
724 |
|
---|
725 | return counts
|
---|
726 |
|
---|
727 | def generate_uvs(uv_layers, option_uv_coords):
|
---|
728 | if not option_uv_coords:
|
---|
729 | return "[]"
|
---|
730 |
|
---|
731 | layers = []
|
---|
732 | for uvs in uv_layers:
|
---|
733 | chunks = []
|
---|
734 | for key, index in sorted(uvs.items(), key=operator.itemgetter(1)):
|
---|
735 | chunks.append(key)
|
---|
736 | layer = ",".join(generate_uv(n) for n in chunks)
|
---|
737 | layers.append(layer)
|
---|
738 |
|
---|
739 | return ",".join("[%s]" % n for n in layers)
|
---|
740 |
|
---|
741 | # ##############################################################################
|
---|
742 | # Model exporter - armature
|
---|
743 | # (only the first armature will exported)
|
---|
744 | # ##############################################################################
|
---|
745 | def get_armature():
|
---|
746 | if len(bpy.data.armatures) == 0:
|
---|
747 | print("Warning: no armatures in the scene")
|
---|
748 | return None, None
|
---|
749 |
|
---|
750 | armature = bpy.data.armatures[0]
|
---|
751 |
|
---|
752 | # Someone please figure out a proper way to get the armature node
|
---|
753 | for object in bpy.data.objects:
|
---|
754 | if object.type == 'ARMATURE':
|
---|
755 | return armature, object
|
---|
756 |
|
---|
757 | print("Warning: no node of type 'ARMATURE' in the scene")
|
---|
758 | return None, None
|
---|
759 |
|
---|
760 | # ##############################################################################
|
---|
761 | # Model exporter - bones
|
---|
762 | # (only the first armature will exported)
|
---|
763 | # ##############################################################################
|
---|
764 |
|
---|
765 | def generate_bones(option_bones, flipyz):
|
---|
766 |
|
---|
767 | if not option_bones:
|
---|
768 | return "", 0
|
---|
769 |
|
---|
770 | armature, armatureObject = get_armature()
|
---|
771 | if armature is None or armatureObject is None:
|
---|
772 | return "", 0
|
---|
773 |
|
---|
774 | hierarchy = []
|
---|
775 |
|
---|
776 | TEMPLATE_BONE = '{"parent":%d,"name":"%s","pos":[%g,%g,%g],"rotq":[0,0,0,1]}'
|
---|
777 |
|
---|
778 | for bone in armature.bones:
|
---|
779 | bonePos = None
|
---|
780 | boneIndex = None
|
---|
781 | if bone.parent is None:
|
---|
782 | bonePos = bone.head_local
|
---|
783 | boneIndex = -1
|
---|
784 | else:
|
---|
785 | bonePos = bone.head_local - bone.parent.head_local
|
---|
786 | boneIndex = i = 0
|
---|
787 | for parent in armature.bones:
|
---|
788 | if parent.name == bone.parent.name:
|
---|
789 | boneIndex = i
|
---|
790 | i += 1
|
---|
791 |
|
---|
792 | bonePosWorld = armatureObject.matrix_world * bonePos
|
---|
793 | if flipyz:
|
---|
794 | joint = TEMPLATE_BONE % (boneIndex, bone.name, bonePosWorld.x, bonePosWorld.z, -bonePosWorld.y)
|
---|
795 | hierarchy.append(joint)
|
---|
796 | else:
|
---|
797 | joint = TEMPLATE_BONE % (boneIndex, bone.name, bonePosWorld.x, bonePosWorld.y, bonePosWorld.z)
|
---|
798 | hierarchy.append(joint)
|
---|
799 |
|
---|
800 | bones_string = ",".join(hierarchy)
|
---|
801 |
|
---|
802 | return bones_string, len(armature.bones)
|
---|
803 |
|
---|
804 |
|
---|
805 | # ##############################################################################
|
---|
806 | # Model exporter - skin indices and weights
|
---|
807 | # ##############################################################################
|
---|
808 |
|
---|
809 | def generate_indices_and_weights(meshes, option_skinning):
|
---|
810 |
|
---|
811 | if not option_skinning or len(bpy.data.armatures) == 0:
|
---|
812 | return "", ""
|
---|
813 |
|
---|
814 | indices = []
|
---|
815 | weights = []
|
---|
816 |
|
---|
817 | armature, armatureObject = get_armature()
|
---|
818 |
|
---|
819 | for mesh, object in meshes:
|
---|
820 |
|
---|
821 | i = 0
|
---|
822 | mesh_index = -1
|
---|
823 |
|
---|
824 | # find the original object
|
---|
825 |
|
---|
826 | for obj in bpy.data.objects:
|
---|
827 | if obj.name == mesh.name or obj == object:
|
---|
828 | mesh_index = i
|
---|
829 | i += 1
|
---|
830 |
|
---|
831 | if mesh_index == -1:
|
---|
832 | print("generate_indices: couldn't find object for mesh", mesh.name)
|
---|
833 | continue
|
---|
834 |
|
---|
835 | object = bpy.data.objects[mesh_index]
|
---|
836 |
|
---|
837 | for vertex in mesh.vertices:
|
---|
838 |
|
---|
839 | # sort bones by influence
|
---|
840 |
|
---|
841 | bone_array = []
|
---|
842 |
|
---|
843 | for group in vertex.groups:
|
---|
844 | index = group.group
|
---|
845 | weight = group.weight
|
---|
846 |
|
---|
847 | bone_array.append( (index, weight) )
|
---|
848 |
|
---|
849 | bone_array.sort(key = operator.itemgetter(1), reverse=True)
|
---|
850 |
|
---|
851 | # select first N bones
|
---|
852 |
|
---|
853 | for i in range(MAX_INFLUENCES):
|
---|
854 |
|
---|
855 | if i < len(bone_array):
|
---|
856 | bone_proxy = bone_array[i]
|
---|
857 |
|
---|
858 | found = 0
|
---|
859 | index = bone_proxy[0]
|
---|
860 | weight = bone_proxy[1]
|
---|
861 |
|
---|
862 | for j, bone in enumerate(armature.bones):
|
---|
863 | if object.vertex_groups[index].name == bone.name:
|
---|
864 | indices.append('%d' % j)
|
---|
865 | weights.append('%g' % weight)
|
---|
866 | found = 1
|
---|
867 | break
|
---|
868 |
|
---|
869 | if found != 1:
|
---|
870 | indices.append('0')
|
---|
871 | weights.append('0')
|
---|
872 |
|
---|
873 | else:
|
---|
874 | indices.append('0')
|
---|
875 | weights.append('0')
|
---|
876 |
|
---|
877 |
|
---|
878 | indices_string = ",".join(indices)
|
---|
879 | weights_string = ",".join(weights)
|
---|
880 |
|
---|
881 | return indices_string, weights_string
|
---|
882 |
|
---|
883 |
|
---|
884 | # ##############################################################################
|
---|
885 | # Model exporter - skeletal animation
|
---|
886 | # (only the first action will exported)
|
---|
887 | # ##############################################################################
|
---|
888 |
|
---|
889 | def generate_animation(option_animation_skeletal, option_frame_step, flipyz, action_index):
|
---|
890 |
|
---|
891 | if not option_animation_skeletal or len(bpy.data.actions) == 0 or len(bpy.data.actions) == 0:
|
---|
892 | return ""
|
---|
893 |
|
---|
894 | # TODO: Add scaling influences
|
---|
895 |
|
---|
896 | action = bpy.data.actions[action_index]
|
---|
897 | armature, armatureObject = get_armature()
|
---|
898 | if armature is None or armatureObject is None:
|
---|
899 | return "", 0
|
---|
900 | armatureMat = armatureObject.matrix_world
|
---|
901 | l,r,s = armatureMat.decompose()
|
---|
902 | armatureRotMat = r.to_matrix()
|
---|
903 |
|
---|
904 | parents = []
|
---|
905 | parent_index = -1
|
---|
906 |
|
---|
907 | fps = bpy.data.scenes[0].render.fps
|
---|
908 |
|
---|
909 | end_frame = action.frame_range[1]
|
---|
910 | start_frame = action.frame_range[0]
|
---|
911 |
|
---|
912 | frame_length = end_frame - start_frame
|
---|
913 |
|
---|
914 | TEMPLATE_KEYFRAME_FULL = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g],"scl":[1,1,1]}'
|
---|
915 | TEMPLATE_KEYFRAME = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g]}'
|
---|
916 | TEMPLATE_KEYFRAME_POS = '{"time":%g,"pos":[%g,%g,%g]}'
|
---|
917 | TEMPLATE_KEYFRAME_ROT = '{"time":%g,"rot":[%g,%g,%g,%g]}'
|
---|
918 |
|
---|
919 | for hierarchy in armature.bones:
|
---|
920 |
|
---|
921 | keys = []
|
---|
922 |
|
---|
923 | for frame in range(int(start_frame), int(end_frame / option_frame_step) + 1):
|
---|
924 |
|
---|
925 | pos, pchange = position(hierarchy, frame * option_frame_step, action, armatureMat)
|
---|
926 | rot, rchange = rotation(hierarchy, frame * option_frame_step, action, armatureRotMat)
|
---|
927 |
|
---|
928 | if flipyz:
|
---|
929 | px, py, pz = pos.x, pos.z, -pos.y
|
---|
930 | rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
|
---|
931 | else:
|
---|
932 | px, py, pz = pos.x, pos.y, pos.z
|
---|
933 | rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
|
---|
934 |
|
---|
935 | # START-FRAME: needs pos, rot and scl attributes (required frame)
|
---|
936 |
|
---|
937 | if frame == int(start_frame):
|
---|
938 |
|
---|
939 | time = (frame * option_frame_step - start_frame) / fps
|
---|
940 | keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
|
---|
941 | keys.append(keyframe)
|
---|
942 |
|
---|
943 | # END-FRAME: needs pos, rot and scl attributes with animation length (required frame)
|
---|
944 |
|
---|
945 | elif frame == int(end_frame / option_frame_step):
|
---|
946 |
|
---|
947 | time = frame_length / fps
|
---|
948 | keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
|
---|
949 | keys.append(keyframe)
|
---|
950 |
|
---|
951 | # MIDDLE-FRAME: needs only one of the attributes, can be an empty frame (optional frame)
|
---|
952 |
|
---|
953 | elif pchange == True or rchange == True:
|
---|
954 |
|
---|
955 | time = (frame * option_frame_step - start_frame) / fps
|
---|
956 |
|
---|
957 | if pchange == True and rchange == True:
|
---|
958 | keyframe = TEMPLATE_KEYFRAME % (time, px, py, pz, rx, ry, rz, rw)
|
---|
959 | elif pchange == True:
|
---|
960 | keyframe = TEMPLATE_KEYFRAME_POS % (time, px, py, pz)
|
---|
961 | elif rchange == True:
|
---|
962 | keyframe = TEMPLATE_KEYFRAME_ROT % (time, rx, ry, rz, rw)
|
---|
963 |
|
---|
964 | keys.append(keyframe)
|
---|
965 |
|
---|
966 | keys_string = ",".join(keys)
|
---|
967 | parent = '{"parent":%d,"keys":[%s]}' % (parent_index, keys_string)
|
---|
968 | parent_index += 1
|
---|
969 | parents.append(parent)
|
---|
970 |
|
---|
971 | hierarchy_string = ",".join(parents)
|
---|
972 | animation_string = '"name":"%s","fps":%d,"length":%g,"hierarchy":[%s]' % (action.name, fps, (frame_length / fps), hierarchy_string)
|
---|
973 |
|
---|
974 | return animation_string
|
---|
975 |
|
---|
976 | def generate_all_animations(option_animation_skeletal, option_frame_step, flipyz):
|
---|
977 | all_animations_string = ""
|
---|
978 | if option_animation_skeletal:
|
---|
979 | for index in range(0, len(bpy.data.actions)):
|
---|
980 | if index != 0 :
|
---|
981 | all_animations_string += ", \n"
|
---|
982 | all_animations_string += "{" + generate_animation(option_animation_skeletal, option_frame_step, flipyz, index) + "}"
|
---|
983 | return all_animations_string
|
---|
984 |
|
---|
985 | def handle_position_channel(channel, frame, position):
|
---|
986 |
|
---|
987 | change = False
|
---|
988 |
|
---|
989 | if channel.array_index in [0, 1, 2]:
|
---|
990 | for keyframe in channel.keyframe_points:
|
---|
991 | if keyframe.co[0] == frame:
|
---|
992 | change = True
|
---|
993 |
|
---|
994 | value = channel.evaluate(frame)
|
---|
995 |
|
---|
996 | if channel.array_index == 0:
|
---|
997 | position.x = value
|
---|
998 |
|
---|
999 | if channel.array_index == 1:
|
---|
1000 | position.y = value
|
---|
1001 |
|
---|
1002 | if channel.array_index == 2:
|
---|
1003 | position.z = value
|
---|
1004 |
|
---|
1005 | return change
|
---|
1006 |
|
---|
1007 | def position(bone, frame, action, armatureMatrix):
|
---|
1008 |
|
---|
1009 | position = mathutils.Vector((0,0,0))
|
---|
1010 | change = False
|
---|
1011 |
|
---|
1012 | ngroups = len(action.groups)
|
---|
1013 |
|
---|
1014 | if ngroups > 0:
|
---|
1015 |
|
---|
1016 | index = 0
|
---|
1017 |
|
---|
1018 | for i in range(ngroups):
|
---|
1019 | if action.groups[i].name == bone.name:
|
---|
1020 | index = i
|
---|
1021 |
|
---|
1022 | for channel in action.groups[index].channels:
|
---|
1023 | if "location" in channel.data_path:
|
---|
1024 | hasChanged = handle_position_channel(channel, frame, position)
|
---|
1025 | change = change or hasChanged
|
---|
1026 |
|
---|
1027 | else:
|
---|
1028 |
|
---|
1029 | bone_label = '"%s"' % bone.name
|
---|
1030 |
|
---|
1031 | for channel in action.fcurves:
|
---|
1032 | data_path = channel.data_path
|
---|
1033 | if bone_label in data_path and "location" in data_path:
|
---|
1034 | hasChanged = handle_position_channel(channel, frame, position)
|
---|
1035 | change = change or hasChanged
|
---|
1036 |
|
---|
1037 | position = position * bone.matrix_local.inverted()
|
---|
1038 |
|
---|
1039 | if bone.parent is None:
|
---|
1040 |
|
---|
1041 | position.x += bone.head.x
|
---|
1042 | position.y += bone.head.y
|
---|
1043 | position.z += bone.head.z
|
---|
1044 |
|
---|
1045 | else:
|
---|
1046 |
|
---|
1047 | parent = bone.parent
|
---|
1048 |
|
---|
1049 | parentInvertedLocalMatrix = parent.matrix_local.inverted()
|
---|
1050 | parentHeadTailDiff = parent.tail_local - parent.head_local
|
---|
1051 |
|
---|
1052 | position.x += (bone.head * parentInvertedLocalMatrix).x + parentHeadTailDiff.x
|
---|
1053 | position.y += (bone.head * parentInvertedLocalMatrix).y + parentHeadTailDiff.y
|
---|
1054 | position.z += (bone.head * parentInvertedLocalMatrix).z + parentHeadTailDiff.z
|
---|
1055 |
|
---|
1056 | return armatureMatrix*position, change
|
---|
1057 |
|
---|
1058 | def handle_rotation_channel(channel, frame, rotation):
|
---|
1059 |
|
---|
1060 | change = False
|
---|
1061 |
|
---|
1062 | if channel.array_index in [0, 1, 2, 3]:
|
---|
1063 |
|
---|
1064 | for keyframe in channel.keyframe_points:
|
---|
1065 | if keyframe.co[0] == frame:
|
---|
1066 | change = True
|
---|
1067 |
|
---|
1068 | value = channel.evaluate(frame)
|
---|
1069 |
|
---|
1070 | if channel.array_index == 1:
|
---|
1071 | rotation.x = value
|
---|
1072 |
|
---|
1073 | elif channel.array_index == 2:
|
---|
1074 | rotation.y = value
|
---|
1075 |
|
---|
1076 | elif channel.array_index == 3:
|
---|
1077 | rotation.z = value
|
---|
1078 |
|
---|
1079 | elif channel.array_index == 0:
|
---|
1080 | rotation.w = value
|
---|
1081 |
|
---|
1082 | return change
|
---|
1083 |
|
---|
1084 | def rotation(bone, frame, action, armatureMatrix):
|
---|
1085 |
|
---|
1086 | # TODO: calculate rotation also from rotation_euler channels
|
---|
1087 |
|
---|
1088 | rotation = mathutils.Vector((0,0,0,1))
|
---|
1089 |
|
---|
1090 | change = False
|
---|
1091 |
|
---|
1092 | ngroups = len(action.groups)
|
---|
1093 |
|
---|
1094 | # animation grouped by bones
|
---|
1095 |
|
---|
1096 | if ngroups > 0:
|
---|
1097 |
|
---|
1098 | index = -1
|
---|
1099 |
|
---|
1100 | for i in range(ngroups):
|
---|
1101 | if action.groups[i].name == bone.name:
|
---|
1102 | index = i
|
---|
1103 |
|
---|
1104 | if index > -1:
|
---|
1105 | for channel in action.groups[index].channels:
|
---|
1106 | if "quaternion" in channel.data_path:
|
---|
1107 | hasChanged = handle_rotation_channel(channel, frame, rotation)
|
---|
1108 | change = change or hasChanged
|
---|
1109 |
|
---|
1110 | # animation in raw fcurves
|
---|
1111 |
|
---|
1112 | else:
|
---|
1113 |
|
---|
1114 | bone_label = '"%s"' % bone.name
|
---|
1115 |
|
---|
1116 | for channel in action.fcurves:
|
---|
1117 | data_path = channel.data_path
|
---|
1118 | if bone_label in data_path and "quaternion" in data_path:
|
---|
1119 | hasChanged = handle_rotation_channel(channel, frame, rotation)
|
---|
1120 | change = change or hasChanged
|
---|
1121 |
|
---|
1122 | rot3 = rotation.to_3d()
|
---|
1123 | rotation.xyz = rot3 * bone.matrix_local.inverted()
|
---|
1124 | rotation.xyz = armatureMatrix * rotation.xyz
|
---|
1125 |
|
---|
1126 | return rotation, change
|
---|
1127 |
|
---|
1128 | # #####################################################
|
---|
1129 | # Model exporter - materials
|
---|
1130 | # #####################################################
|
---|
1131 |
|
---|
1132 | def generate_color(i):
|
---|
1133 | """Generate hex color corresponding to integer.
|
---|
1134 |
|
---|
1135 | Colors should have well defined ordering.
|
---|
1136 | First N colors are hardcoded, then colors are random
|
---|
1137 | (must seed random number generator with deterministic value
|
---|
1138 | before getting colors).
|
---|
1139 | """
|
---|
1140 |
|
---|
1141 | if i < len(COLORS):
|
---|
1142 | #return "0x%06x" % COLORS[i]
|
---|
1143 | return COLORS[i]
|
---|
1144 | else:
|
---|
1145 | #return "0x%06x" % int(0xffffff * random.random())
|
---|
1146 | return int(0xffffff * random.random())
|
---|
1147 |
|
---|
1148 | def generate_mtl(materials):
|
---|
1149 | """Generate dummy materials.
|
---|
1150 | """
|
---|
1151 |
|
---|
1152 | mtl = {}
|
---|
1153 | for m in materials:
|
---|
1154 | index = materials[m]
|
---|
1155 | mtl[m] = {
|
---|
1156 | "DbgName": m,
|
---|
1157 | "DbgIndex": index,
|
---|
1158 | "DbgColor": generate_color(index),
|
---|
1159 | "vertexColors" : False
|
---|
1160 | }
|
---|
1161 | return mtl
|
---|
1162 |
|
---|
1163 | def value2string(v):
|
---|
1164 | if type(v) == str and v[0:2] != "0x":
|
---|
1165 | return '"%s"' % v
|
---|
1166 | elif type(v) == bool:
|
---|
1167 | return str(v).lower()
|
---|
1168 | elif type(v) == list:
|
---|
1169 | return "[%s]" % (", ".join(value2string(x) for x in v))
|
---|
1170 | return str(v)
|
---|
1171 |
|
---|
1172 | def generate_materials(mtl, materials, draw_type):
|
---|
1173 | """Generate JS array of materials objects
|
---|
1174 | """
|
---|
1175 |
|
---|
1176 | mtl_array = []
|
---|
1177 | for m in mtl:
|
---|
1178 | index = materials[m]
|
---|
1179 |
|
---|
1180 | # add debug information
|
---|
1181 | # materials should be sorted according to how
|
---|
1182 | # they appeared in OBJ file (for the first time)
|
---|
1183 | # this index is identifier used in face definitions
|
---|
1184 | mtl[m]['DbgName'] = m
|
---|
1185 | mtl[m]['DbgIndex'] = index
|
---|
1186 | mtl[m]['DbgColor'] = generate_color(index)
|
---|
1187 |
|
---|
1188 | if draw_type in [ "BOUNDS", "WIRE" ]:
|
---|
1189 | mtl[m]['wireframe'] = True
|
---|
1190 | mtl[m]['DbgColor'] = 0xff0000
|
---|
1191 |
|
---|
1192 | mtl_raw = ",\n".join(['\t\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
|
---|
1193 | mtl_string = "\t{\n%s\n\t}" % mtl_raw
|
---|
1194 | mtl_array.append([index, mtl_string])
|
---|
1195 |
|
---|
1196 | return ",\n\n".join([m for i,m in sorted(mtl_array)]), len(mtl_array)
|
---|
1197 |
|
---|
1198 | def extract_materials(mesh, scene, option_colors, option_copy_textures, filepath):
|
---|
1199 | world = scene.world
|
---|
1200 |
|
---|
1201 | materials = {}
|
---|
1202 | for m in mesh.materials:
|
---|
1203 | if m:
|
---|
1204 | materials[m.name] = {}
|
---|
1205 | material = materials[m.name]
|
---|
1206 |
|
---|
1207 | material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
|
---|
1208 | m.diffuse_intensity * m.diffuse_color[1],
|
---|
1209 | m.diffuse_intensity * m.diffuse_color[2]]
|
---|
1210 |
|
---|
1211 | material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
|
---|
1212 | m.specular_intensity * m.specular_color[1],
|
---|
1213 | m.specular_intensity * m.specular_color[2]]
|
---|
1214 |
|
---|
1215 | material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
|
---|
1216 | m.ambient * material['colorDiffuse'][1],
|
---|
1217 | m.ambient * material['colorDiffuse'][2]]
|
---|
1218 |
|
---|
1219 | material['transparency'] = m.alpha
|
---|
1220 |
|
---|
1221 | # not sure about mapping values to Blinn-Phong shader
|
---|
1222 | # Blender uses INT from [1, 511] with default 0
|
---|
1223 | # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
|
---|
1224 |
|
---|
1225 | material["specularCoef"] = m.specular_hardness
|
---|
1226 |
|
---|
1227 | textures = guess_material_textures(m)
|
---|
1228 |
|
---|
1229 | handle_texture('diffuse', textures, material, filepath, option_copy_textures)
|
---|
1230 | handle_texture('light', textures, material, filepath, option_copy_textures)
|
---|
1231 | handle_texture('normal', textures, material, filepath, option_copy_textures)
|
---|
1232 | handle_texture('specular', textures, material, filepath, option_copy_textures)
|
---|
1233 | handle_texture('bump', textures, material, filepath, option_copy_textures)
|
---|
1234 |
|
---|
1235 | material["vertexColors"] = m.THREE_useVertexColors and option_colors
|
---|
1236 |
|
---|
1237 | # can't really use this reliably to tell apart Phong from Lambert
|
---|
1238 | # as Blender defaults to non-zero specular color
|
---|
1239 | #if m.specular_intensity > 0.0 and (m.specular_color[0] > 0 or m.specular_color[1] > 0 or m.specular_color[2] > 0):
|
---|
1240 | # material['shading'] = "Phong"
|
---|
1241 | #else:
|
---|
1242 | # material['shading'] = "Lambert"
|
---|
1243 |
|
---|
1244 | if textures['normal']:
|
---|
1245 | material['shading'] = "Phong"
|
---|
1246 | else:
|
---|
1247 | material['shading'] = m.THREE_materialType
|
---|
1248 |
|
---|
1249 | material['blending'] = m.THREE_blendingType
|
---|
1250 | material['depthWrite'] = m.THREE_depthWrite
|
---|
1251 | material['depthTest'] = m.THREE_depthTest
|
---|
1252 | material['transparent'] = m.use_transparency
|
---|
1253 |
|
---|
1254 | return materials
|
---|
1255 |
|
---|
1256 | def generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath, offset):
|
---|
1257 |
|
---|
1258 | random.seed(42) # to get well defined color order for debug materials
|
---|
1259 |
|
---|
1260 | materials = {}
|
---|
1261 | if mesh.materials:
|
---|
1262 | for i, m in enumerate(mesh.materials):
|
---|
1263 | mat_id = i + offset
|
---|
1264 | if m:
|
---|
1265 | materials[m.name] = mat_id
|
---|
1266 | else:
|
---|
1267 | materials["undefined_dummy_%0d" % mat_id] = mat_id
|
---|
1268 |
|
---|
1269 |
|
---|
1270 | if not materials:
|
---|
1271 | materials = { 'default': 0 }
|
---|
1272 |
|
---|
1273 | # default dummy materials
|
---|
1274 |
|
---|
1275 | mtl = generate_mtl(materials)
|
---|
1276 |
|
---|
1277 | # extract real materials from the mesh
|
---|
1278 |
|
---|
1279 | mtl.update(extract_materials(mesh, scene, option_colors, option_copy_textures, filepath))
|
---|
1280 |
|
---|
1281 | return generate_materials(mtl, materials, draw_type)
|
---|
1282 |
|
---|
1283 | def handle_texture(id, textures, material, filepath, option_copy_textures):
|
---|
1284 |
|
---|
1285 | if textures[id] and textures[id]['texture'].users > 0 and len(textures[id]['texture'].users_material) > 0:
|
---|
1286 | texName = 'map%s' % id.capitalize()
|
---|
1287 | repeatName = 'map%sRepeat' % id.capitalize()
|
---|
1288 | wrapName = 'map%sWrap' % id.capitalize()
|
---|
1289 |
|
---|
1290 | slot = textures[id]['slot']
|
---|
1291 | texture = textures[id]['texture']
|
---|
1292 | image = texture.image
|
---|
1293 | fname = extract_texture_filename(image)
|
---|
1294 | material[texName] = fname
|
---|
1295 |
|
---|
1296 | if option_copy_textures:
|
---|
1297 | save_image(image, fname, filepath)
|
---|
1298 |
|
---|
1299 | if texture.repeat_x != 1 or texture.repeat_y != 1:
|
---|
1300 | material[repeatName] = [texture.repeat_x, texture.repeat_y]
|
---|
1301 |
|
---|
1302 | if texture.extension == "REPEAT":
|
---|
1303 | wrap_x = "repeat"
|
---|
1304 | wrap_y = "repeat"
|
---|
1305 |
|
---|
1306 | if texture.use_mirror_x:
|
---|
1307 | wrap_x = "mirror"
|
---|
1308 | if texture.use_mirror_y:
|
---|
1309 | wrap_y = "mirror"
|
---|
1310 |
|
---|
1311 | material[wrapName] = [wrap_x, wrap_y]
|
---|
1312 |
|
---|
1313 | if slot.use_map_normal:
|
---|
1314 | if slot.normal_factor != 1.0:
|
---|
1315 | if id == "bump":
|
---|
1316 | material['mapBumpScale'] = slot.normal_factor
|
---|
1317 | else:
|
---|
1318 | material['mapNormalFactor'] = slot.normal_factor
|
---|
1319 |
|
---|
1320 |
|
---|
1321 | # #####################################################
|
---|
1322 | # ASCII model generator
|
---|
1323 | # #####################################################
|
---|
1324 |
|
---|
1325 | def generate_ascii_model(meshes, morphs,
|
---|
1326 | scene,
|
---|
1327 | option_vertices,
|
---|
1328 | option_vertices_truncate,
|
---|
1329 | option_faces,
|
---|
1330 | option_normals,
|
---|
1331 | option_uv_coords,
|
---|
1332 | option_materials,
|
---|
1333 | option_colors,
|
---|
1334 | option_bones,
|
---|
1335 | option_skinning,
|
---|
1336 | align_model,
|
---|
1337 | flipyz,
|
---|
1338 | option_scale,
|
---|
1339 | option_copy_textures,
|
---|
1340 | filepath,
|
---|
1341 | option_animation_morph,
|
---|
1342 | option_animation_skeletal,
|
---|
1343 | option_frame_step):
|
---|
1344 |
|
---|
1345 | vertices = []
|
---|
1346 |
|
---|
1347 | vertex_offset = 0
|
---|
1348 | vertex_offsets = []
|
---|
1349 |
|
---|
1350 | nnormal = 0
|
---|
1351 | normals = {}
|
---|
1352 |
|
---|
1353 | ncolor = 0
|
---|
1354 | colors = {}
|
---|
1355 |
|
---|
1356 | nuvs = []
|
---|
1357 | uv_layers = []
|
---|
1358 |
|
---|
1359 | nmaterial = 0
|
---|
1360 | materials = []
|
---|
1361 |
|
---|
1362 | for mesh, object in meshes:
|
---|
1363 |
|
---|
1364 | vertexUV = len(mesh.uv_textures) > 0
|
---|
1365 | vertexColors = len(mesh.vertex_colors) > 0
|
---|
1366 |
|
---|
1367 | mesh_extract_colors = option_colors and vertexColors
|
---|
1368 | mesh_extract_uvs = option_uv_coords and vertexUV
|
---|
1369 |
|
---|
1370 | if vertexUV:
|
---|
1371 | active_uv_layer = mesh.uv_textures.active
|
---|
1372 | if not active_uv_layer:
|
---|
1373 | mesh_extract_uvs = False
|
---|
1374 |
|
---|
1375 | if vertexColors:
|
---|
1376 | active_col_layer = mesh.vertex_colors.active
|
---|
1377 | if not active_col_layer:
|
---|
1378 | mesh_extract_colors = False
|
---|
1379 |
|
---|
1380 | vertex_offsets.append(vertex_offset)
|
---|
1381 | vertex_offset += len(vertices)
|
---|
1382 |
|
---|
1383 | vertices.extend(mesh.vertices[:])
|
---|
1384 |
|
---|
1385 | if option_normals:
|
---|
1386 | nnormal = extract_vertex_normals(mesh, normals, nnormal)
|
---|
1387 |
|
---|
1388 | if mesh_extract_colors:
|
---|
1389 | ncolor = extract_vertex_colors(mesh, colors, ncolor)
|
---|
1390 |
|
---|
1391 | if mesh_extract_uvs:
|
---|
1392 | nuvs = extract_uvs(mesh, uv_layers, nuvs)
|
---|
1393 |
|
---|
1394 | if option_materials:
|
---|
1395 | mesh_materials, nmaterial = generate_materials_string(mesh, scene, mesh_extract_colors, object.draw_type, option_copy_textures, filepath, nmaterial)
|
---|
1396 | materials.append(mesh_materials)
|
---|
1397 |
|
---|
1398 |
|
---|
1399 | morphTargets_string = ""
|
---|
1400 | nmorphTarget = 0
|
---|
1401 |
|
---|
1402 | if option_animation_morph:
|
---|
1403 | chunks = []
|
---|
1404 | for i, morphVertices in enumerate(morphs):
|
---|
1405 | morphTarget = '{ "name": "%s_%06d", "vertices": [%s] }' % ("animation", i, morphVertices)
|
---|
1406 | chunks.append(morphTarget)
|
---|
1407 |
|
---|
1408 | morphTargets_string = ",\n\t".join(chunks)
|
---|
1409 | nmorphTarget = len(morphs)
|
---|
1410 |
|
---|
1411 | if align_model == 1:
|
---|
1412 | center(vertices)
|
---|
1413 | elif align_model == 2:
|
---|
1414 | bottom(vertices)
|
---|
1415 | elif align_model == 3:
|
---|
1416 | top(vertices)
|
---|
1417 |
|
---|
1418 | faces_string, nfaces = generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces)
|
---|
1419 |
|
---|
1420 | bones_string, nbone = generate_bones(option_bones, flipyz)
|
---|
1421 | indices_string, weights_string = generate_indices_and_weights(meshes, option_skinning)
|
---|
1422 |
|
---|
1423 | materials_string = ",\n\n".join(materials)
|
---|
1424 |
|
---|
1425 | model_string = TEMPLATE_MODEL_ASCII % {
|
---|
1426 | "scale" : option_scale,
|
---|
1427 |
|
---|
1428 | "uvs" : generate_uvs(uv_layers, option_uv_coords),
|
---|
1429 | "normals" : generate_normals(normals, option_normals),
|
---|
1430 | "colors" : generate_vertex_colors(colors, option_colors),
|
---|
1431 |
|
---|
1432 | "materials" : materials_string,
|
---|
1433 |
|
---|
1434 | "vertices" : generate_vertices(vertices, option_vertices_truncate, option_vertices),
|
---|
1435 |
|
---|
1436 | "faces" : faces_string,
|
---|
1437 |
|
---|
1438 | "morphTargets" : morphTargets_string,
|
---|
1439 |
|
---|
1440 | "bones" : bones_string,
|
---|
1441 | "indices" : indices_string,
|
---|
1442 | "weights" : weights_string,
|
---|
1443 | "animations" : generate_all_animations(option_animation_skeletal, option_frame_step, flipyz)
|
---|
1444 | }
|
---|
1445 |
|
---|
1446 | text = TEMPLATE_FILE_ASCII % {
|
---|
1447 | "nvertex" : len(vertices),
|
---|
1448 | "nface" : nfaces,
|
---|
1449 | "nuvs" : ",".join("%d" % n for n in nuvs),
|
---|
1450 | "nnormal" : nnormal,
|
---|
1451 | "ncolor" : ncolor,
|
---|
1452 | "nmaterial" : nmaterial,
|
---|
1453 | "nmorphTarget": nmorphTarget,
|
---|
1454 | "nbone" : nbone,
|
---|
1455 |
|
---|
1456 | "model" : model_string
|
---|
1457 | }
|
---|
1458 |
|
---|
1459 |
|
---|
1460 | return text, model_string
|
---|
1461 |
|
---|
1462 |
|
---|
1463 | # #####################################################
|
---|
1464 | # Model exporter - export single mesh
|
---|
1465 | # #####################################################
|
---|
1466 |
|
---|
1467 | def extract_meshes(objects, scene, export_single_model, option_scale, flipyz):
|
---|
1468 |
|
---|
1469 | meshes = []
|
---|
1470 |
|
---|
1471 | for object in objects:
|
---|
1472 |
|
---|
1473 | if object.type == "MESH" and object.THREE_exportGeometry:
|
---|
1474 |
|
---|
1475 | # collapse modifiers into mesh
|
---|
1476 |
|
---|
1477 | mesh = object.to_mesh(scene, True, 'RENDER')
|
---|
1478 |
|
---|
1479 | if not mesh:
|
---|
1480 | raise Exception("Error, could not get mesh data from object [%s]" % object.name)
|
---|
1481 |
|
---|
1482 | # preserve original name
|
---|
1483 |
|
---|
1484 | mesh.name = object.name
|
---|
1485 |
|
---|
1486 | if export_single_model:
|
---|
1487 |
|
---|
1488 | if flipyz:
|
---|
1489 |
|
---|
1490 | # that's what Blender's native export_obj.py does to flip YZ
|
---|
1491 |
|
---|
1492 | X_ROT = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
|
---|
1493 | mesh.transform(X_ROT * object.matrix_world)
|
---|
1494 |
|
---|
1495 | else:
|
---|
1496 | mesh.transform(object.matrix_world)
|
---|
1497 |
|
---|
1498 |
|
---|
1499 | mesh.update(calc_tessface=True)
|
---|
1500 |
|
---|
1501 | mesh.calc_normals()
|
---|
1502 | mesh.calc_tessface()
|
---|
1503 | mesh.transform(mathutils.Matrix.Scale(option_scale, 4))
|
---|
1504 | meshes.append([mesh, object])
|
---|
1505 |
|
---|
1506 | return meshes
|
---|
1507 |
|
---|
1508 | def generate_mesh_string(objects, scene,
|
---|
1509 | option_vertices,
|
---|
1510 | option_vertices_truncate,
|
---|
1511 | option_faces,
|
---|
1512 | option_normals,
|
---|
1513 | option_uv_coords,
|
---|
1514 | option_materials,
|
---|
1515 | option_colors,
|
---|
1516 | option_bones,
|
---|
1517 | option_skinning,
|
---|
1518 | align_model,
|
---|
1519 | flipyz,
|
---|
1520 | option_scale,
|
---|
1521 | export_single_model,
|
---|
1522 | option_copy_textures,
|
---|
1523 | filepath,
|
---|
1524 | option_animation_morph,
|
---|
1525 | option_animation_skeletal,
|
---|
1526 | option_frame_step):
|
---|
1527 |
|
---|
1528 | meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
|
---|
1529 |
|
---|
1530 | morphs = []
|
---|
1531 |
|
---|
1532 | if option_animation_morph:
|
---|
1533 |
|
---|
1534 | original_frame = scene.frame_current # save animation state
|
---|
1535 |
|
---|
1536 | scene_frames = range(scene.frame_start, scene.frame_end + 1, option_frame_step)
|
---|
1537 |
|
---|
1538 | for index, frame in enumerate(scene_frames):
|
---|
1539 | scene.frame_set(frame, 0.0)
|
---|
1540 |
|
---|
1541 | anim_meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
|
---|
1542 |
|
---|
1543 | frame_vertices = []
|
---|
1544 |
|
---|
1545 | for mesh, object in anim_meshes:
|
---|
1546 | frame_vertices.extend(mesh.vertices[:])
|
---|
1547 |
|
---|
1548 | if index == 0:
|
---|
1549 | if align_model == 1:
|
---|
1550 | offset = center(frame_vertices)
|
---|
1551 | elif align_model == 2:
|
---|
1552 | offset = bottom(frame_vertices)
|
---|
1553 | elif align_model == 3:
|
---|
1554 | offset = top(frame_vertices)
|
---|
1555 | else:
|
---|
1556 | offset = False
|
---|
1557 | else:
|
---|
1558 | if offset:
|
---|
1559 | translate(frame_vertices, offset)
|
---|
1560 |
|
---|
1561 | morphVertices = generate_vertices(frame_vertices, option_vertices_truncate, option_vertices)
|
---|
1562 | morphs.append(morphVertices)
|
---|
1563 |
|
---|
1564 | # remove temp meshes
|
---|
1565 |
|
---|
1566 | for mesh, object in anim_meshes:
|
---|
1567 | bpy.data.meshes.remove(mesh)
|
---|
1568 |
|
---|
1569 | scene.frame_set(original_frame, 0.0) # restore animation state
|
---|
1570 |
|
---|
1571 |
|
---|
1572 | text, model_string = generate_ascii_model(meshes, morphs,
|
---|
1573 | scene,
|
---|
1574 | option_vertices,
|
---|
1575 | option_vertices_truncate,
|
---|
1576 | option_faces,
|
---|
1577 | option_normals,
|
---|
1578 | option_uv_coords,
|
---|
1579 | option_materials,
|
---|
1580 | option_colors,
|
---|
1581 | option_bones,
|
---|
1582 | option_skinning,
|
---|
1583 | align_model,
|
---|
1584 | flipyz,
|
---|
1585 | option_scale,
|
---|
1586 | option_copy_textures,
|
---|
1587 | filepath,
|
---|
1588 | option_animation_morph,
|
---|
1589 | option_animation_skeletal,
|
---|
1590 | option_frame_step)
|
---|
1591 |
|
---|
1592 | # remove temp meshes
|
---|
1593 |
|
---|
1594 | for mesh, object in meshes:
|
---|
1595 | bpy.data.meshes.remove(mesh)
|
---|
1596 |
|
---|
1597 | return text, model_string
|
---|
1598 |
|
---|
1599 | def export_mesh(objects,
|
---|
1600 | scene, filepath,
|
---|
1601 | option_vertices,
|
---|
1602 | option_vertices_truncate,
|
---|
1603 | option_faces,
|
---|
1604 | option_normals,
|
---|
1605 | option_uv_coords,
|
---|
1606 | option_materials,
|
---|
1607 | option_colors,
|
---|
1608 | option_bones,
|
---|
1609 | option_skinning,
|
---|
1610 | align_model,
|
---|
1611 | flipyz,
|
---|
1612 | option_scale,
|
---|
1613 | export_single_model,
|
---|
1614 | option_copy_textures,
|
---|
1615 | option_animation_morph,
|
---|
1616 | option_animation_skeletal,
|
---|
1617 | option_frame_step):
|
---|
1618 |
|
---|
1619 | """Export single mesh"""
|
---|
1620 |
|
---|
1621 | text, model_string = generate_mesh_string(objects,
|
---|
1622 | scene,
|
---|
1623 | option_vertices,
|
---|
1624 | option_vertices_truncate,
|
---|
1625 | option_faces,
|
---|
1626 | option_normals,
|
---|
1627 | option_uv_coords,
|
---|
1628 | option_materials,
|
---|
1629 | option_colors,
|
---|
1630 | option_bones,
|
---|
1631 | option_skinning,
|
---|
1632 | align_model,
|
---|
1633 | flipyz,
|
---|
1634 | option_scale,
|
---|
1635 | export_single_model,
|
---|
1636 | option_copy_textures,
|
---|
1637 | filepath,
|
---|
1638 | option_animation_morph,
|
---|
1639 | option_animation_skeletal,
|
---|
1640 | option_frame_step)
|
---|
1641 |
|
---|
1642 | write_file(filepath, text)
|
---|
1643 |
|
---|
1644 | print("writing", filepath, "done")
|
---|
1645 |
|
---|
1646 |
|
---|
1647 | # #####################################################
|
---|
1648 | # Scene exporter - render elements
|
---|
1649 | # #####################################################
|
---|
1650 |
|
---|
1651 | def generate_quat(quat):
|
---|
1652 | return TEMPLATE_VEC4 % (quat.x, quat.y, quat.z, quat.w)
|
---|
1653 |
|
---|
1654 | def generate_vec4(vec):
|
---|
1655 | return TEMPLATE_VEC4 % (vec[0], vec[1], vec[2], vec[3])
|
---|
1656 |
|
---|
1657 | def generate_vec3(vec, flipyz = False):
|
---|
1658 | if flipyz:
|
---|
1659 | return TEMPLATE_VEC3 % (vec[0], vec[2], vec[1])
|
---|
1660 | return TEMPLATE_VEC3 % (vec[0], vec[1], vec[2])
|
---|
1661 |
|
---|
1662 | def generate_vec2(vec):
|
---|
1663 | return TEMPLATE_VEC2 % (vec[0], vec[1])
|
---|
1664 |
|
---|
1665 | def generate_hex(number):
|
---|
1666 | return TEMPLATE_HEX % number
|
---|
1667 |
|
---|
1668 | def generate_string(s):
|
---|
1669 | return TEMPLATE_STRING % s
|
---|
1670 |
|
---|
1671 | def generate_string_list(src_list):
|
---|
1672 | return ", ".join(generate_string(item) for item in src_list)
|
---|
1673 |
|
---|
1674 | def generate_section(label, content):
|
---|
1675 | return TEMPLATE_SECTION % (label, content)
|
---|
1676 |
|
---|
1677 | def get_mesh_filename(mesh):
|
---|
1678 | object_id = mesh["data"]["name"]
|
---|
1679 | filename = "%s.js" % sanitize(object_id)
|
---|
1680 | return filename
|
---|
1681 |
|
---|
1682 | def generate_material_id_list(materials):
|
---|
1683 | chunks = []
|
---|
1684 | for material in materials:
|
---|
1685 | chunks.append(material.name)
|
---|
1686 |
|
---|
1687 | return chunks
|
---|
1688 |
|
---|
1689 | def generate_group_id_list(obj):
|
---|
1690 | chunks = []
|
---|
1691 |
|
---|
1692 | for group in bpy.data.groups:
|
---|
1693 | if obj.name in group.objects:
|
---|
1694 | chunks.append(group.name)
|
---|
1695 |
|
---|
1696 | return chunks
|
---|
1697 |
|
---|
1698 | def generate_bool_property(property):
|
---|
1699 | if property:
|
---|
1700 | return "true"
|
---|
1701 | return "false"
|
---|
1702 |
|
---|
1703 | # #####################################################
|
---|
1704 | # Scene exporter - objects
|
---|
1705 | # #####################################################
|
---|
1706 |
|
---|
1707 | def generate_objects(data):
|
---|
1708 | chunks = []
|
---|
1709 |
|
---|
1710 | for obj in data["objects"]:
|
---|
1711 |
|
---|
1712 | if obj.type == "MESH" and obj.THREE_exportGeometry:
|
---|
1713 | object_id = obj.name
|
---|
1714 |
|
---|
1715 | #if len(obj.modifiers) > 0:
|
---|
1716 | # geo_name = obj.name
|
---|
1717 | #else:
|
---|
1718 | geo_name = obj.data.name
|
---|
1719 |
|
---|
1720 | geometry_id = "geo_%s" % geo_name
|
---|
1721 |
|
---|
1722 | material_ids = generate_material_id_list(obj.material_slots)
|
---|
1723 | group_ids = generate_group_id_list(obj)
|
---|
1724 |
|
---|
1725 | if data["flipyz"]:
|
---|
1726 | matrix_world = ROTATE_X_PI2 * obj.matrix_world
|
---|
1727 | else:
|
---|
1728 | matrix_world = obj.matrix_world
|
---|
1729 |
|
---|
1730 | position, quaternion, scale = matrix_world.decompose()
|
---|
1731 | rotation = quaternion.to_euler("ZYX")
|
---|
1732 |
|
---|
1733 | # use empty material string for multi-material objects
|
---|
1734 | # this will trigger use of MeshFaceMaterial in SceneLoader
|
---|
1735 |
|
---|
1736 | material_string = '""'
|
---|
1737 | if len(material_ids) == 1:
|
---|
1738 | material_string = generate_string_list(material_ids)
|
---|
1739 |
|
---|
1740 | group_string = ""
|
---|
1741 | if len(group_ids) > 0:
|
---|
1742 | group_string = generate_string_list(group_ids)
|
---|
1743 |
|
---|
1744 | castShadow = obj.THREE_castShadow
|
---|
1745 | receiveShadow = obj.THREE_receiveShadow
|
---|
1746 | doubleSided = obj.THREE_doubleSided
|
---|
1747 |
|
---|
1748 | visible = True
|
---|
1749 |
|
---|
1750 | geometry_string = generate_string(geometry_id)
|
---|
1751 |
|
---|
1752 | object_string = TEMPLATE_OBJECT % {
|
---|
1753 | "object_id" : generate_string(object_id),
|
---|
1754 | "geometry_id" : geometry_string,
|
---|
1755 | "group_id" : group_string,
|
---|
1756 | "material_id" : material_string,
|
---|
1757 |
|
---|
1758 | "position" : generate_vec3(position),
|
---|
1759 | "rotation" : generate_vec3(rotation),
|
---|
1760 | "quaternion" : generate_quat(quaternion),
|
---|
1761 | "scale" : generate_vec3(scale),
|
---|
1762 |
|
---|
1763 | "castShadow" : generate_bool_property(castShadow),
|
---|
1764 | "receiveShadow" : generate_bool_property(receiveShadow),
|
---|
1765 | "doubleSided" : generate_bool_property(doubleSided),
|
---|
1766 | "visible" : generate_bool_property(visible)
|
---|
1767 | }
|
---|
1768 | chunks.append(object_string)
|
---|
1769 |
|
---|
1770 | elif obj.type == "EMPTY" or (obj.type == "MESH" and not obj.THREE_exportGeometry):
|
---|
1771 |
|
---|
1772 | object_id = obj.name
|
---|
1773 | group_ids = generate_group_id_list(obj)
|
---|
1774 |
|
---|
1775 | if data["flipyz"]:
|
---|
1776 | matrix_world = ROTATE_X_PI2 * obj.matrix_world
|
---|
1777 | else:
|
---|
1778 | matrix_world = obj.matrix_world
|
---|
1779 |
|
---|
1780 | position, quaternion, scale = matrix_world.decompose()
|
---|
1781 | rotation = quaternion.to_euler("ZYX")
|
---|
1782 |
|
---|
1783 | group_string = ""
|
---|
1784 | if len(group_ids) > 0:
|
---|
1785 | group_string = generate_string_list(group_ids)
|
---|
1786 |
|
---|
1787 | object_string = TEMPLATE_EMPTY % {
|
---|
1788 | "object_id" : generate_string(object_id),
|
---|
1789 | "group_id" : group_string,
|
---|
1790 |
|
---|
1791 | "position" : generate_vec3(position),
|
---|
1792 | "rotation" : generate_vec3(rotation),
|
---|
1793 | "quaternion" : generate_quat(quaternion),
|
---|
1794 | "scale" : generate_vec3(scale)
|
---|
1795 | }
|
---|
1796 | chunks.append(object_string)
|
---|
1797 |
|
---|
1798 | return ",\n\n".join(chunks), len(chunks)
|
---|
1799 |
|
---|
1800 | # #####################################################
|
---|
1801 | # Scene exporter - geometries
|
---|
1802 | # #####################################################
|
---|
1803 |
|
---|
1804 | def generate_geometries(data):
|
---|
1805 | chunks = []
|
---|
1806 |
|
---|
1807 | geo_set = set()
|
---|
1808 |
|
---|
1809 | for obj in data["objects"]:
|
---|
1810 | if obj.type == "MESH" and obj.THREE_exportGeometry:
|
---|
1811 |
|
---|
1812 | #if len(obj.modifiers) > 0:
|
---|
1813 | # name = obj.name
|
---|
1814 | #else:
|
---|
1815 | name = obj.data.name
|
---|
1816 |
|
---|
1817 | if name not in geo_set:
|
---|
1818 |
|
---|
1819 | geometry_id = "geo_%s" % name
|
---|
1820 |
|
---|
1821 | if data["embed_meshes"]:
|
---|
1822 |
|
---|
1823 | embed_id = "emb_%s" % name
|
---|
1824 |
|
---|
1825 | geometry_string = TEMPLATE_GEOMETRY_EMBED % {
|
---|
1826 | "geometry_id" : generate_string(geometry_id),
|
---|
1827 | "embed_id" : generate_string(embed_id)
|
---|
1828 | }
|
---|
1829 |
|
---|
1830 | else:
|
---|
1831 |
|
---|
1832 | model_filename = os.path.basename(generate_mesh_filename(name, data["filepath"]))
|
---|
1833 |
|
---|
1834 | geometry_string = TEMPLATE_GEOMETRY_LINK % {
|
---|
1835 | "geometry_id" : generate_string(geometry_id),
|
---|
1836 | "model_file" : generate_string(model_filename)
|
---|
1837 | }
|
---|
1838 |
|
---|
1839 | chunks.append(geometry_string)
|
---|
1840 |
|
---|
1841 | geo_set.add(name)
|
---|
1842 |
|
---|
1843 | return ",\n\n".join(chunks), len(chunks)
|
---|
1844 |
|
---|
1845 | # #####################################################
|
---|
1846 | # Scene exporter - textures
|
---|
1847 | # #####################################################
|
---|
1848 |
|
---|
1849 | def generate_textures_scene(data):
|
---|
1850 | chunks = []
|
---|
1851 |
|
---|
1852 | # TODO: extract just textures actually used by some objects in the scene
|
---|
1853 |
|
---|
1854 | for texture in bpy.data.textures:
|
---|
1855 |
|
---|
1856 | if texture.type == 'IMAGE' and texture.image and texture.users > 0 and len(texture.users_material) > 0:
|
---|
1857 |
|
---|
1858 | img = texture.image
|
---|
1859 |
|
---|
1860 | texture_id = img.name
|
---|
1861 | texture_file = extract_texture_filename(img)
|
---|
1862 |
|
---|
1863 | if data["copy_textures"]:
|
---|
1864 | save_image(img, texture_file, data["filepath"])
|
---|
1865 |
|
---|
1866 | extras = ""
|
---|
1867 |
|
---|
1868 | if texture.repeat_x != 1 or texture.repeat_y != 1:
|
---|
1869 | extras += ',\n "repeat": [%g, %g]' % (texture.repeat_x, texture.repeat_y)
|
---|
1870 |
|
---|
1871 | if texture.extension == "REPEAT":
|
---|
1872 | wrap_x = "repeat"
|
---|
1873 | wrap_y = "repeat"
|
---|
1874 |
|
---|
1875 | if texture.use_mirror_x:
|
---|
1876 | wrap_x = "mirror"
|
---|
1877 | if texture.use_mirror_y:
|
---|
1878 | wrap_y = "mirror"
|
---|
1879 |
|
---|
1880 | extras += ',\n "wrap": ["%s", "%s"]' % (wrap_x, wrap_y)
|
---|
1881 |
|
---|
1882 | texture_string = TEMPLATE_TEXTURE % {
|
---|
1883 | "texture_id" : generate_string(texture_id),
|
---|
1884 | "texture_file" : generate_string(texture_file),
|
---|
1885 | "extras" : extras
|
---|
1886 | }
|
---|
1887 | chunks.append(texture_string)
|
---|
1888 |
|
---|
1889 | return ",\n\n".join(chunks), len(chunks)
|
---|
1890 |
|
---|
1891 | def extract_texture_filename(image):
|
---|
1892 | fn = bpy.path.abspath(image.filepath)
|
---|
1893 | fn = os.path.normpath(fn)
|
---|
1894 | fn_strip = os.path.basename(fn)
|
---|
1895 | return fn_strip
|
---|
1896 |
|
---|
1897 | def save_image(img, name, fpath):
|
---|
1898 | dst_dir = os.path.dirname(fpath)
|
---|
1899 | dst_path = os.path.join(dst_dir, name)
|
---|
1900 |
|
---|
1901 | ensure_folder_exist(dst_dir)
|
---|
1902 |
|
---|
1903 | if img.packed_file:
|
---|
1904 | img.save_render(dst_path)
|
---|
1905 |
|
---|
1906 | else:
|
---|
1907 | src_path = bpy.path.abspath(img.filepath)
|
---|
1908 | shutil.copy(src_path, dst_dir)
|
---|
1909 |
|
---|
1910 | # #####################################################
|
---|
1911 | # Scene exporter - materials
|
---|
1912 | # #####################################################
|
---|
1913 |
|
---|
1914 | def extract_material_data(m, option_colors):
|
---|
1915 | world = bpy.context.scene.world
|
---|
1916 |
|
---|
1917 | material = { 'name': m.name }
|
---|
1918 |
|
---|
1919 | material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
|
---|
1920 | m.diffuse_intensity * m.diffuse_color[1],
|
---|
1921 | m.diffuse_intensity * m.diffuse_color[2]]
|
---|
1922 |
|
---|
1923 | material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
|
---|
1924 | m.specular_intensity * m.specular_color[1],
|
---|
1925 | m.specular_intensity * m.specular_color[2]]
|
---|
1926 |
|
---|
1927 | material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
|
---|
1928 | m.ambient * material['colorDiffuse'][1],
|
---|
1929 | m.ambient * material['colorDiffuse'][2]]
|
---|
1930 |
|
---|
1931 | material['transparency'] = m.alpha
|
---|
1932 |
|
---|
1933 | # not sure about mapping values to Blinn-Phong shader
|
---|
1934 | # Blender uses INT from [1,511] with default 0
|
---|
1935 | # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
|
---|
1936 |
|
---|
1937 | material["specularCoef"] = m.specular_hardness
|
---|
1938 |
|
---|
1939 | material["vertexColors"] = m.THREE_useVertexColors and option_colors
|
---|
1940 |
|
---|
1941 | material['mapDiffuse'] = ""
|
---|
1942 | material['mapLight'] = ""
|
---|
1943 | material['mapSpecular'] = ""
|
---|
1944 | material['mapNormal'] = ""
|
---|
1945 | material['mapBump'] = ""
|
---|
1946 |
|
---|
1947 | material['mapNormalFactor'] = 1.0
|
---|
1948 | material['mapBumpScale'] = 1.0
|
---|
1949 |
|
---|
1950 | textures = guess_material_textures(m)
|
---|
1951 |
|
---|
1952 | if textures['diffuse']:
|
---|
1953 | material['mapDiffuse'] = textures['diffuse']['texture'].image.name
|
---|
1954 |
|
---|
1955 | if textures['light']:
|
---|
1956 | material['mapLight'] = textures['light']['texture'].image.name
|
---|
1957 |
|
---|
1958 | if textures['specular']:
|
---|
1959 | material['mapSpecular'] = textures['specular']['texture'].image.name
|
---|
1960 |
|
---|
1961 | if textures['normal']:
|
---|
1962 | material['mapNormal'] = textures['normal']['texture'].image.name
|
---|
1963 | if textures['normal']['slot'].use_map_normal:
|
---|
1964 | material['mapNormalFactor'] = textures['normal']['slot'].normal_factor
|
---|
1965 |
|
---|
1966 | if textures['bump']:
|
---|
1967 | material['mapBump'] = textures['bump']['texture'].image.name
|
---|
1968 | if textures['bump']['slot'].use_map_normal:
|
---|
1969 | material['mapBumpScale'] = textures['bump']['slot'].normal_factor
|
---|
1970 |
|
---|
1971 | material['shading'] = m.THREE_materialType
|
---|
1972 | material['blending'] = m.THREE_blendingType
|
---|
1973 | material['depthWrite'] = m.THREE_depthWrite
|
---|
1974 | material['depthTest'] = m.THREE_depthTest
|
---|
1975 | material['transparent'] = m.use_transparency
|
---|
1976 |
|
---|
1977 | return material
|
---|
1978 |
|
---|
1979 | def guess_material_textures(material):
|
---|
1980 | textures = {
|
---|
1981 | 'diffuse' : None,
|
---|
1982 | 'light' : None,
|
---|
1983 | 'normal' : None,
|
---|
1984 | 'specular': None,
|
---|
1985 | 'bump' : None
|
---|
1986 | }
|
---|
1987 |
|
---|
1988 | # just take first textures of each, for the moment three.js materials can't handle more
|
---|
1989 | # assume diffuse comes before lightmap, normalmap has checked flag
|
---|
1990 |
|
---|
1991 | for i in range(len(material.texture_slots)):
|
---|
1992 | slot = material.texture_slots[i]
|
---|
1993 | if slot:
|
---|
1994 | texture = slot.texture
|
---|
1995 | if slot.use and texture and texture.type == 'IMAGE':
|
---|
1996 |
|
---|
1997 | # normal map in Blender UI: textures => image sampling => normal map
|
---|
1998 |
|
---|
1999 | if texture.use_normal_map:
|
---|
2000 | textures['normal'] = { "texture": texture, "slot": slot }
|
---|
2001 |
|
---|
2002 | # bump map in Blender UI: textures => influence => geometry => normal
|
---|
2003 |
|
---|
2004 | elif slot.use_map_normal:
|
---|
2005 | textures['bump'] = { "texture": texture, "slot": slot }
|
---|
2006 |
|
---|
2007 | elif slot.use_map_specular or slot.use_map_hardness:
|
---|
2008 | textures['specular'] = { "texture": texture, "slot": slot }
|
---|
2009 |
|
---|
2010 | else:
|
---|
2011 | if not textures['diffuse'] and not slot.blend_type == 'MULTIPLY':
|
---|
2012 | textures['diffuse'] = { "texture": texture, "slot": slot }
|
---|
2013 |
|
---|
2014 | else:
|
---|
2015 | textures['light'] = { "texture": texture, "slot": slot }
|
---|
2016 |
|
---|
2017 | if textures['diffuse'] and textures['normal'] and textures['light'] and textures['specular'] and textures['bump']:
|
---|
2018 | break
|
---|
2019 |
|
---|
2020 | return textures
|
---|
2021 |
|
---|
2022 | def generate_material_string(material):
|
---|
2023 |
|
---|
2024 | material_id = material["name"]
|
---|
2025 |
|
---|
2026 | # default to Lambert
|
---|
2027 |
|
---|
2028 | shading = material.get("shading", "Lambert")
|
---|
2029 |
|
---|
2030 | # normal and bump mapped materials must use Phong
|
---|
2031 | # to get all required parameters for normal shader
|
---|
2032 |
|
---|
2033 | if material['mapNormal'] or material['mapBump']:
|
---|
2034 | shading = "Phong"
|
---|
2035 |
|
---|
2036 | type_map = {
|
---|
2037 | "Lambert" : "MeshLambertMaterial",
|
---|
2038 | "Phong" : "MeshPhongMaterial"
|
---|
2039 | }
|
---|
2040 |
|
---|
2041 | material_type = type_map.get(shading, "MeshBasicMaterial")
|
---|
2042 |
|
---|
2043 | parameters = '"color": %d' % rgb2int(material["colorDiffuse"])
|
---|
2044 | parameters += ', "ambient": %d' % rgb2int(material["colorDiffuse"])
|
---|
2045 | parameters += ', "opacity": %.2g' % material["transparency"]
|
---|
2046 |
|
---|
2047 | if shading == "Phong":
|
---|
2048 | parameters += ', "ambient": %d' % rgb2int(material["colorAmbient"])
|
---|
2049 | parameters += ', "specular": %d' % rgb2int(material["colorSpecular"])
|
---|
2050 | parameters += ', "shininess": %.1g' % material["specularCoef"]
|
---|
2051 |
|
---|
2052 | colorMap = material['mapDiffuse']
|
---|
2053 | lightMap = material['mapLight']
|
---|
2054 | specularMap = material['mapSpecular']
|
---|
2055 | normalMap = material['mapNormal']
|
---|
2056 | bumpMap = material['mapBump']
|
---|
2057 | normalMapFactor = material['mapNormalFactor']
|
---|
2058 | bumpMapScale = material['mapBumpScale']
|
---|
2059 |
|
---|
2060 | if colorMap:
|
---|
2061 | parameters += ', "map": %s' % generate_string(colorMap)
|
---|
2062 | if lightMap:
|
---|
2063 | parameters += ', "lightMap": %s' % generate_string(lightMap)
|
---|
2064 | if specularMap:
|
---|
2065 | parameters += ', "specularMap": %s' % generate_string(specularMap)
|
---|
2066 | if normalMap:
|
---|
2067 | parameters += ', "normalMap": %s' % generate_string(normalMap)
|
---|
2068 | if bumpMap:
|
---|
2069 | parameters += ', "bumpMap": %s' % generate_string(bumpMap)
|
---|
2070 |
|
---|
2071 | if normalMapFactor != 1.0:
|
---|
2072 | parameters += ', "normalMapFactor": %g' % normalMapFactor
|
---|
2073 |
|
---|
2074 | if bumpMapScale != 1.0:
|
---|
2075 | parameters += ', "bumpMapScale": %g' % bumpMapScale
|
---|
2076 |
|
---|
2077 | if material['vertexColors']:
|
---|
2078 | parameters += ', "vertexColors": "vertex"'
|
---|
2079 |
|
---|
2080 | if material['transparent']:
|
---|
2081 | parameters += ', "transparent": true'
|
---|
2082 |
|
---|
2083 | parameters += ', "blending": "%s"' % material['blending']
|
---|
2084 |
|
---|
2085 | if not material['depthWrite']:
|
---|
2086 | parameters += ', "depthWrite": false'
|
---|
2087 |
|
---|
2088 | if not material['depthTest']:
|
---|
2089 | parameters += ', "depthTest": false'
|
---|
2090 |
|
---|
2091 |
|
---|
2092 | material_string = TEMPLATE_MATERIAL_SCENE % {
|
---|
2093 | "material_id" : generate_string(material_id),
|
---|
2094 | "type" : generate_string(material_type),
|
---|
2095 | "parameters" : parameters
|
---|
2096 | }
|
---|
2097 |
|
---|
2098 | return material_string
|
---|
2099 |
|
---|
2100 | def generate_materials_scene(data):
|
---|
2101 | chunks = []
|
---|
2102 |
|
---|
2103 | def material_is_used(mat):
|
---|
2104 | minimum_users = 1
|
---|
2105 | if mat.use_fake_user:
|
---|
2106 | minimum_users = 2 #we must ignore the "fake user" in this case
|
---|
2107 | return mat.users >= minimum_users
|
---|
2108 |
|
---|
2109 | used_materials = [m for m in bpy.data.materials if material_is_used(m)]
|
---|
2110 |
|
---|
2111 | for m in used_materials:
|
---|
2112 | material = extract_material_data(m, data["use_colors"])
|
---|
2113 | material_string = generate_material_string(material)
|
---|
2114 | chunks.append(material_string)
|
---|
2115 |
|
---|
2116 | return ",\n\n".join(chunks), len(chunks)
|
---|
2117 |
|
---|
2118 | # #####################################################
|
---|
2119 | # Scene exporter - cameras
|
---|
2120 | # #####################################################
|
---|
2121 |
|
---|
2122 | def generate_cameras(data):
|
---|
2123 | chunks = []
|
---|
2124 |
|
---|
2125 | if data["use_cameras"]:
|
---|
2126 |
|
---|
2127 | cams = bpy.data.objects
|
---|
2128 | cams = [ob for ob in cams if (ob.type == 'CAMERA')]
|
---|
2129 |
|
---|
2130 | if not cams:
|
---|
2131 | camera = DEFAULTS["camera"]
|
---|
2132 |
|
---|
2133 | if camera["type"] == "PerspectiveCamera":
|
---|
2134 |
|
---|
2135 | camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
|
---|
2136 | "camera_id" : generate_string(camera["name"]),
|
---|
2137 | "fov" : camera["fov"],
|
---|
2138 | "aspect" : camera["aspect"],
|
---|
2139 | "near" : camera["near"],
|
---|
2140 | "far" : camera["far"],
|
---|
2141 | "position" : generate_vec3(camera["position"]),
|
---|
2142 | "target" : generate_vec3(camera["target"])
|
---|
2143 | }
|
---|
2144 |
|
---|
2145 | elif camera["type"] == "OrthographicCamera":
|
---|
2146 |
|
---|
2147 | camera_string = TEMPLATE_CAMERA_ORTHO % {
|
---|
2148 | "camera_id" : generate_string(camera["name"]),
|
---|
2149 | "left" : camera["left"],
|
---|
2150 | "right" : camera["right"],
|
---|
2151 | "top" : camera["top"],
|
---|
2152 | "bottom" : camera["bottom"],
|
---|
2153 | "near" : camera["near"],
|
---|
2154 | "far" : camera["far"],
|
---|
2155 | "position" : generate_vec3(camera["position"]),
|
---|
2156 | "target" : generate_vec3(camera["target"])
|
---|
2157 | }
|
---|
2158 |
|
---|
2159 | chunks.append(camera_string)
|
---|
2160 |
|
---|
2161 | else:
|
---|
2162 |
|
---|
2163 | for cameraobj in cams:
|
---|
2164 | camera = bpy.data.cameras[cameraobj.name]
|
---|
2165 |
|
---|
2166 | # TODO:
|
---|
2167 | # Support more than perspective camera
|
---|
2168 | # Calculate a target/lookat
|
---|
2169 | # Get correct aspect ratio
|
---|
2170 | if camera.id_data.type == "PERSP":
|
---|
2171 |
|
---|
2172 | camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
|
---|
2173 | "camera_id" : generate_string(camera.name),
|
---|
2174 | "fov" : (camera.angle / 3.14) * 180.0,
|
---|
2175 | "aspect" : 1.333,
|
---|
2176 | "near" : camera.clip_start,
|
---|
2177 | "far" : camera.clip_end,
|
---|
2178 | "position" : generate_vec3([cameraobj.location[0], -cameraobj.location[1], cameraobj.location[2]], data["flipyz"]),
|
---|
2179 | "target" : generate_vec3([0, 0, 0])
|
---|
2180 | }
|
---|
2181 |
|
---|
2182 | chunks.append(camera_string)
|
---|
2183 |
|
---|
2184 | return ",\n\n".join(chunks), len(chunks)
|
---|
2185 |
|
---|
2186 | # #####################################################
|
---|
2187 | # Scene exporter - lights
|
---|
2188 | # #####################################################
|
---|
2189 |
|
---|
2190 | def generate_lights(data):
|
---|
2191 | chunks = []
|
---|
2192 |
|
---|
2193 | if data["use_lights"]:
|
---|
2194 | lamps = data["objects"]
|
---|
2195 | lamps = [ob for ob in lamps if (ob.type == 'LAMP')]
|
---|
2196 |
|
---|
2197 | for lamp in lamps:
|
---|
2198 | light_string = ""
|
---|
2199 | concrete_lamp = lamp.data
|
---|
2200 |
|
---|
2201 | if concrete_lamp.type == "POINT":
|
---|
2202 | light_string = TEMPLATE_LIGHT_POINT % {
|
---|
2203 | "light_id" : generate_string(concrete_lamp.name),
|
---|
2204 | "position" : generate_vec3(lamp.location, data["flipyz"]),
|
---|
2205 | "rotation" : generate_vec3(lamp.rotation_euler, data["flipyz"]),
|
---|
2206 | "color" : rgb2int(concrete_lamp.color),
|
---|
2207 | "distance" : concrete_lamp.distance,
|
---|
2208 | "intensity" : concrete_lamp.energy
|
---|
2209 | }
|
---|
2210 | elif concrete_lamp.type == "SUN":
|
---|
2211 | light_string = TEMPLATE_LIGHT_SUN % {
|
---|
2212 | "light_id" : generate_string(concrete_lamp.name),
|
---|
2213 | "position" : generate_vec3(lamp.location, data["flipyz"]),
|
---|
2214 | "rotation" : generate_vec3(lamp.rotation_euler, data["flipyz"]),
|
---|
2215 | "color" : rgb2int(concrete_lamp.color),
|
---|
2216 | "distance" : concrete_lamp.distance,
|
---|
2217 | "intensity" : concrete_lamp.energy
|
---|
2218 | }
|
---|
2219 | elif concrete_lamp.type == "SPOT":
|
---|
2220 | light_string = TEMPLATE_LIGHT_SPOT % {
|
---|
2221 | "light_id" : generate_string(concrete_lamp.name),
|
---|
2222 | "position" : generate_vec3(lamp.location, data["flipyz"]),
|
---|
2223 | "rotation" : generate_vec3(lamp.rotation_euler, data["flipyz"]),
|
---|
2224 | "color" : rgb2int(concrete_lamp.color),
|
---|
2225 | "distance" : concrete_lamp.distance,
|
---|
2226 | "intensity" : concrete_lamp.energy,
|
---|
2227 | "use_shadow" : concrete_lamp.use_shadow,
|
---|
2228 | "angle" : concrete_lamp.spot_size
|
---|
2229 | }
|
---|
2230 | elif concrete_lamp.type == "HEMI":
|
---|
2231 | light_string = TEMPLATE_LIGHT_HEMI % {
|
---|
2232 | "light_id" : generate_string(concrete_lamp.name),
|
---|
2233 | "position" : generate_vec3(lamp.location, data["flipyz"]),
|
---|
2234 | "rotation" : generate_vec3(lamp.rotation_euler, data["flipyz"]),
|
---|
2235 | "color" : rgb2int(concrete_lamp.color),
|
---|
2236 | "distance" : concrete_lamp.distance,
|
---|
2237 | "intensity" : concrete_lamp.energy
|
---|
2238 | }
|
---|
2239 | elif concrete_lamp.type == "AREA":
|
---|
2240 | light_string = TEMPLATE_LIGHT_AREA % {
|
---|
2241 | "light_id" : generate_string(concrete_lamp.name),
|
---|
2242 | "position" : generate_vec3(lamp.location, data["flipyz"]),
|
---|
2243 | "rotation" : generate_vec3(lamp.rotation_euler, data["flipyz"]),
|
---|
2244 | "color" : rgb2int(concrete_lamp.color),
|
---|
2245 | "distance" : concrete_lamp.distance,
|
---|
2246 | "intensity" : concrete_lamp.energy,
|
---|
2247 | "gamma" : concrete_lamp.gamma,
|
---|
2248 | "shape" : concrete_lamp.shape,
|
---|
2249 | "size" : concrete_lamp.size,
|
---|
2250 | "size_y" : concrete_lamp.size_y
|
---|
2251 | }
|
---|
2252 |
|
---|
2253 | chunks.append(light_string)
|
---|
2254 |
|
---|
2255 | if not lamps:
|
---|
2256 | lamps.append(DEFAULTS["light"])
|
---|
2257 |
|
---|
2258 | return ",\n\n".join(chunks), len(chunks)
|
---|
2259 |
|
---|
2260 |
|
---|
2261 | # #####################################################
|
---|
2262 | # Scene exporter - embedded meshes
|
---|
2263 | # #####################################################
|
---|
2264 |
|
---|
2265 | def generate_embeds(data):
|
---|
2266 |
|
---|
2267 | if data["embed_meshes"]:
|
---|
2268 |
|
---|
2269 | chunks = []
|
---|
2270 |
|
---|
2271 | for e in data["embeds"]:
|
---|
2272 |
|
---|
2273 | embed = '"emb_%s": {%s}' % (e, data["embeds"][e])
|
---|
2274 | chunks.append(embed)
|
---|
2275 |
|
---|
2276 | return ",\n\n".join(chunks)
|
---|
2277 |
|
---|
2278 | return ""
|
---|
2279 |
|
---|
2280 | # #####################################################
|
---|
2281 | # Scene exporter - generate ASCII scene
|
---|
2282 | # #####################################################
|
---|
2283 |
|
---|
2284 | def generate_ascii_scene(data):
|
---|
2285 |
|
---|
2286 | objects, nobjects = generate_objects(data)
|
---|
2287 | geometries, ngeometries = generate_geometries(data)
|
---|
2288 | textures, ntextures = generate_textures_scene(data)
|
---|
2289 | materials, nmaterials = generate_materials_scene(data)
|
---|
2290 | lights, nlights = generate_lights(data)
|
---|
2291 | cameras, ncameras = generate_cameras(data)
|
---|
2292 |
|
---|
2293 | embeds = generate_embeds(data)
|
---|
2294 |
|
---|
2295 | if nlights > 0:
|
---|
2296 | if nobjects > 0:
|
---|
2297 | objects = objects + ",\n\n" + lights
|
---|
2298 | else:
|
---|
2299 | objects = lights
|
---|
2300 | nobjects += nlights
|
---|
2301 |
|
---|
2302 | if ncameras > 0:
|
---|
2303 | if nobjects > 0:
|
---|
2304 | objects = objects + ",\n\n" + cameras
|
---|
2305 | else:
|
---|
2306 | objects = cameras
|
---|
2307 | nobjects += ncameras
|
---|
2308 |
|
---|
2309 | basetype = "relativeTo"
|
---|
2310 |
|
---|
2311 | if data["base_html"]:
|
---|
2312 | basetype += "HTML"
|
---|
2313 | else:
|
---|
2314 | basetype += "Scene"
|
---|
2315 |
|
---|
2316 | sections = [
|
---|
2317 | ["objects", objects],
|
---|
2318 | ["geometries", geometries],
|
---|
2319 | ["textures", textures],
|
---|
2320 | ["materials", materials],
|
---|
2321 | ["embeds", embeds]
|
---|
2322 | ]
|
---|
2323 |
|
---|
2324 | chunks = []
|
---|
2325 | for label, content in sections:
|
---|
2326 | if content:
|
---|
2327 | chunks.append(generate_section(label, content))
|
---|
2328 |
|
---|
2329 | sections_string = "\n".join(chunks)
|
---|
2330 |
|
---|
2331 | default_camera = ""
|
---|
2332 | if data["use_cameras"]:
|
---|
2333 | cams = [ob for ob in bpy.data.objects if (ob.type == 'CAMERA' and ob.select)]
|
---|
2334 | if not cams:
|
---|
2335 | default_camera = "default_camera"
|
---|
2336 | else:
|
---|
2337 | default_camera = cams[0].name
|
---|
2338 |
|
---|
2339 | parameters = {
|
---|
2340 | "fname" : data["source_file"],
|
---|
2341 |
|
---|
2342 | "sections" : sections_string,
|
---|
2343 |
|
---|
2344 | "bgcolor" : generate_vec3(DEFAULTS["bgcolor"]),
|
---|
2345 | "bgalpha" : DEFAULTS["bgalpha"],
|
---|
2346 | "defcamera" : generate_string(default_camera),
|
---|
2347 |
|
---|
2348 | "nobjects" : nobjects,
|
---|
2349 | "ngeometries" : ngeometries,
|
---|
2350 | "ntextures" : ntextures,
|
---|
2351 | "basetype" : generate_string(basetype),
|
---|
2352 | "nmaterials" : nmaterials,
|
---|
2353 |
|
---|
2354 | "position" : generate_vec3(DEFAULTS["position"]),
|
---|
2355 | "rotation" : generate_vec3(DEFAULTS["rotation"]),
|
---|
2356 | "scale" : generate_vec3(DEFAULTS["scale"])
|
---|
2357 | }
|
---|
2358 |
|
---|
2359 | text = TEMPLATE_SCENE_ASCII % parameters
|
---|
2360 |
|
---|
2361 | return text
|
---|
2362 |
|
---|
2363 | def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds, option_url_base_html, option_copy_textures):
|
---|
2364 |
|
---|
2365 | source_file = os.path.basename(bpy.data.filepath)
|
---|
2366 |
|
---|
2367 | # objects are contained in scene and linked groups
|
---|
2368 | objects = []
|
---|
2369 |
|
---|
2370 | # get scene objects
|
---|
2371 | sceneobjects = scene.objects
|
---|
2372 | for obj in sceneobjects:
|
---|
2373 | objects.append(obj)
|
---|
2374 |
|
---|
2375 | scene_text = ""
|
---|
2376 | data = {
|
---|
2377 | "scene" : scene,
|
---|
2378 | "objects" : objects,
|
---|
2379 | "embeds" : embeds,
|
---|
2380 | "source_file" : source_file,
|
---|
2381 | "filepath" : filepath,
|
---|
2382 | "flipyz" : flipyz,
|
---|
2383 | "use_colors" : option_colors,
|
---|
2384 | "use_lights" : option_lights,
|
---|
2385 | "use_cameras" : option_cameras,
|
---|
2386 | "embed_meshes" : option_embed_meshes,
|
---|
2387 | "base_html" : option_url_base_html,
|
---|
2388 | "copy_textures": option_copy_textures
|
---|
2389 | }
|
---|
2390 | scene_text += generate_ascii_scene(data)
|
---|
2391 |
|
---|
2392 | write_file(filepath, scene_text)
|
---|
2393 |
|
---|
2394 | # #####################################################
|
---|
2395 | # Main
|
---|
2396 | # #####################################################
|
---|
2397 |
|
---|
2398 | def save(operator, context, filepath = "",
|
---|
2399 | option_flip_yz = True,
|
---|
2400 | option_vertices = True,
|
---|
2401 | option_vertices_truncate = False,
|
---|
2402 | option_faces = True,
|
---|
2403 | option_normals = True,
|
---|
2404 | option_uv_coords = True,
|
---|
2405 | option_materials = True,
|
---|
2406 | option_colors = True,
|
---|
2407 | option_bones = True,
|
---|
2408 | option_skinning = True,
|
---|
2409 | align_model = 0,
|
---|
2410 | option_export_scene = False,
|
---|
2411 | option_lights = False,
|
---|
2412 | option_cameras = False,
|
---|
2413 | option_scale = 1.0,
|
---|
2414 | option_embed_meshes = True,
|
---|
2415 | option_url_base_html = False,
|
---|
2416 | option_copy_textures = False,
|
---|
2417 | option_animation_morph = False,
|
---|
2418 | option_animation_skeletal = False,
|
---|
2419 | option_frame_step = 1,
|
---|
2420 | option_all_meshes = True):
|
---|
2421 |
|
---|
2422 | #print("URL TYPE", option_url_base_html)
|
---|
2423 |
|
---|
2424 | filepath = ensure_extension(filepath, '.js')
|
---|
2425 |
|
---|
2426 | scene = context.scene
|
---|
2427 |
|
---|
2428 | if scene.objects.active:
|
---|
2429 | bpy.ops.object.mode_set(mode='OBJECT')
|
---|
2430 |
|
---|
2431 | if option_all_meshes:
|
---|
2432 | sceneobjects = scene.objects
|
---|
2433 | else:
|
---|
2434 | sceneobjects = context.selected_objects
|
---|
2435 |
|
---|
2436 | # objects are contained in scene and linked groups
|
---|
2437 | objects = []
|
---|
2438 |
|
---|
2439 | # get scene objects
|
---|
2440 | for obj in sceneobjects:
|
---|
2441 | objects.append(obj)
|
---|
2442 |
|
---|
2443 | if option_export_scene:
|
---|
2444 |
|
---|
2445 | geo_set = set()
|
---|
2446 | embeds = {}
|
---|
2447 |
|
---|
2448 | for object in objects:
|
---|
2449 | if object.type == "MESH" and object.THREE_exportGeometry:
|
---|
2450 |
|
---|
2451 | # create extra copy of geometry with applied modifiers
|
---|
2452 | # (if they exist)
|
---|
2453 |
|
---|
2454 | #if len(object.modifiers) > 0:
|
---|
2455 | # name = object.name
|
---|
2456 |
|
---|
2457 | # otherwise can share geometry
|
---|
2458 |
|
---|
2459 | #else:
|
---|
2460 | name = object.data.name
|
---|
2461 |
|
---|
2462 | if name not in geo_set:
|
---|
2463 |
|
---|
2464 | if option_embed_meshes:
|
---|
2465 |
|
---|
2466 | text, model_string = generate_mesh_string([object], scene,
|
---|
2467 | option_vertices,
|
---|
2468 | option_vertices_truncate,
|
---|
2469 | option_faces,
|
---|
2470 | option_normals,
|
---|
2471 | option_uv_coords,
|
---|
2472 | option_materials,
|
---|
2473 | option_colors,
|
---|
2474 | option_bones,
|
---|
2475 | option_skinning,
|
---|
2476 | False, # align_model
|
---|
2477 | option_flip_yz,
|
---|
2478 | option_scale,
|
---|
2479 | False, # export_single_model
|
---|
2480 | False, # option_copy_textures
|
---|
2481 | filepath,
|
---|
2482 | option_animation_morph,
|
---|
2483 | option_animation_skeletal,
|
---|
2484 | option_frame_step)
|
---|
2485 |
|
---|
2486 | embeds[object.data.name] = model_string
|
---|
2487 |
|
---|
2488 | else:
|
---|
2489 |
|
---|
2490 | fname = generate_mesh_filename(name, filepath)
|
---|
2491 | export_mesh([object], scene,
|
---|
2492 | fname,
|
---|
2493 | option_vertices,
|
---|
2494 | option_vertices_truncate,
|
---|
2495 | option_faces,
|
---|
2496 | option_normals,
|
---|
2497 | option_uv_coords,
|
---|
2498 | option_materials,
|
---|
2499 | option_colors,
|
---|
2500 | option_bones,
|
---|
2501 | option_skinning,
|
---|
2502 | False, # align_model
|
---|
2503 | option_flip_yz,
|
---|
2504 | option_scale,
|
---|
2505 | False, # export_single_model
|
---|
2506 | option_copy_textures,
|
---|
2507 | option_animation_morph,
|
---|
2508 | option_animation_skeletal,
|
---|
2509 | option_frame_step)
|
---|
2510 |
|
---|
2511 | geo_set.add(name)
|
---|
2512 |
|
---|
2513 | export_scene(scene, filepath,
|
---|
2514 | option_flip_yz,
|
---|
2515 | option_colors,
|
---|
2516 | option_lights,
|
---|
2517 | option_cameras,
|
---|
2518 | option_embed_meshes,
|
---|
2519 | embeds,
|
---|
2520 | option_url_base_html,
|
---|
2521 | option_copy_textures)
|
---|
2522 |
|
---|
2523 | else:
|
---|
2524 |
|
---|
2525 | export_mesh(objects, scene, filepath,
|
---|
2526 | option_vertices,
|
---|
2527 | option_vertices_truncate,
|
---|
2528 | option_faces,
|
---|
2529 | option_normals,
|
---|
2530 | option_uv_coords,
|
---|
2531 | option_materials,
|
---|
2532 | option_colors,
|
---|
2533 | option_bones,
|
---|
2534 | option_skinning,
|
---|
2535 | align_model,
|
---|
2536 | option_flip_yz,
|
---|
2537 | option_scale,
|
---|
2538 | True, # export_single_model
|
---|
2539 | option_copy_textures,
|
---|
2540 | option_animation_morph,
|
---|
2541 | option_animation_skeletal,
|
---|
2542 | option_frame_step)
|
---|
2543 |
|
---|
2544 | return {'FINISHED'}
|
---|