source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/export_threejs.py@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 76.9 KB
Line 
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"""
20Blender exporter for Three.js (ASCII JSON format).
21
22TODO
23 - binary format
24"""
25
26import bpy
27import mathutils
28
29import shutil
30import os
31import os.path
32import math
33import operator
34import random
35
36# #####################################################
37# Configuration
38# #####################################################
39
40DEFAULTS = {
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
70ROTATE_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
74COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
75
76
77# skinning
78MAX_INFLUENCES = 2
79
80
81# #####################################################
82# Templates - scene
83# #####################################################
84
85TEMPLATE_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
121TEMPLATE_SECTION = """
122"%s" :
123{
124%s
125},
126"""
127
128TEMPLATE_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
143TEMPLATE_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
152TEMPLATE_GEOMETRY_LINK = """\
153 %(geometry_id)s : {
154 "type" : "ascii",
155 "url" : %(model_file)s
156 }"""
157
158TEMPLATE_GEOMETRY_EMBED = """\
159 %(geometry_id)s : {
160 "type" : "embedded",
161 "id" : %(embed_id)s
162 }"""
163
164TEMPLATE_TEXTURE = """\
165 %(texture_id)s : {
166 "url": %(texture_file)s%(extras)s
167 }"""
168
169TEMPLATE_MATERIAL_SCENE = """\
170 %(material_id)s : {
171 "type": %(type)s,
172 "parameters": { %(parameters)s }
173 }"""
174
175TEMPLATE_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
186TEMPLATE_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
199TEMPLATE_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
209TEMPLATE_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
219TEMPLATE_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
231TEMPLATE_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
241TEMPLATE_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
256TEMPLATE_VEC4 = '[ %g, %g, %g, %g ]'
257TEMPLATE_VEC3 = '[ %g, %g, %g ]'
258TEMPLATE_VEC2 = '[ %g, %g ]'
259TEMPLATE_STRING = '"%s"'
260TEMPLATE_HEX = "0x%06x"
261
262# #####################################################
263# Templates - model
264# #####################################################
265
266TEMPLATE_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
288TEMPLATE_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
314TEMPLATE_VERTEX = "%g,%g,%g"
315TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
316
317TEMPLATE_N = "%g,%g,%g"
318TEMPLATE_UV = "%g,%g"
319TEMPLATE_C = "%d"
320
321# #####################################################
322# Utils
323# #####################################################
324
325def veckey3(x,y,z):
326 return round(x, 6), round(y, 6), round(z, 6)
327
328def veckey3d(v):
329 return veckey3(v.x, v.y, v.z)
330
331def veckey2d(v):
332 return round(v[0], 6), round(v[1], 6)
333
334def get_faces(obj):
335 if hasattr(obj, "tessfaces"):
336 return obj.tessfaces
337 else:
338 return obj.faces
339
340def 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
352def 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
359def 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
368def 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
376def write_file(fname, content):
377 out = open(fname, "w")
378 out.write(content)
379 out.close()
380
381def 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
387def ensure_extension(filepath, extension):
388 if not filepath.lower().endswith(extension):
389 filepath += extension
390 return filepath
391
392def 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
402def 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
432def 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
441def 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
455def 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
469def 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
487def hexcolor(c):
488 return ( int(c[0] * 255) << 16 ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
489
490def 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
496def 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
502def generate_normal(n):
503 return TEMPLATE_N % (n[0], n[1], n[2])
504
505def generate_vertex_color(c):
506 return TEMPLATE_C % c
507
508def generate_uv(uv):
509 return TEMPLATE_UV % (uv[0], uv[1])
510
511# #####################################################
512# Model exporter - faces
513# #####################################################
514
515def 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
523def 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
564def 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
643def 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
656def 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
670def 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
686def 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
700def 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
727def 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# ##############################################################################
745def 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
765def 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
809def 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
889def 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
976def 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
985def 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
1007def 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
1058def 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
1084def 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
1132def 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
1148def 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
1163def 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
1172def 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
1198def 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
1256def 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
1283def 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
1325def 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
1467def 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
1508def 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
1599def 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
1651def generate_quat(quat):
1652 return TEMPLATE_VEC4 % (quat.x, quat.y, quat.z, quat.w)
1653
1654def generate_vec4(vec):
1655 return TEMPLATE_VEC4 % (vec[0], vec[1], vec[2], vec[3])
1656
1657def 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
1662def generate_vec2(vec):
1663 return TEMPLATE_VEC2 % (vec[0], vec[1])
1664
1665def generate_hex(number):
1666 return TEMPLATE_HEX % number
1667
1668def generate_string(s):
1669 return TEMPLATE_STRING % s
1670
1671def generate_string_list(src_list):
1672 return ", ".join(generate_string(item) for item in src_list)
1673
1674def generate_section(label, content):
1675 return TEMPLATE_SECTION % (label, content)
1676
1677def get_mesh_filename(mesh):
1678 object_id = mesh["data"]["name"]
1679 filename = "%s.js" % sanitize(object_id)
1680 return filename
1681
1682def generate_material_id_list(materials):
1683 chunks = []
1684 for material in materials:
1685 chunks.append(material.name)
1686
1687 return chunks
1688
1689def 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
1698def generate_bool_property(property):
1699 if property:
1700 return "true"
1701 return "false"
1702
1703# #####################################################
1704# Scene exporter - objects
1705# #####################################################
1706
1707def 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
1804def 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
1849def 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
1891def 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
1897def 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
1914def 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
1979def 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
2022def 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
2100def 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
2122def 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
2190def 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
2265def 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
2284def 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
2363def 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
2398def 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'}
Note: See TracBrowser for help on using the repository browser.