math_vis: remove use of register_module
[blender-addons.git] / io_scene_x3d / export_x3d.py
blobc2ebfca7da0f4752466b7801f490091bb4158333
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 # Contributors: bart:neeneenee*de, http://www.neeneenee.de/vrml, Campbell Barton
23 """
24 This script exports to X3D format.
26 Usage:
27 Run this script from "File->Export" menu. A pop-up will ask whether you
28 want to export only selected or all relevant objects.
30 Known issues:
31 Doesn't handle multiple materials (don't use material indices);<br>
32 Doesn't handle multiple UV textures on a single mesh (create a mesh for each texture);<br>
33 Can't get the texture array associated with material * not the UV ones;
34 """
36 import math
37 import os
39 import bpy
40 import mathutils
42 from bpy_extras.io_utils import create_derived_objects, free_derived_objects
45 # h3d defines
46 H3D_TOP_LEVEL = 'TOP_LEVEL_TI'
47 H3D_CAMERA_FOLLOW = 'CAMERA_FOLLOW_TRANSFORM'
48 H3D_VIEW_MATRIX = 'view_matrix'
51 def clamp_color(col):
52 return tuple([max(min(c, 1.0), 0.0) for c in col])
55 def matrix_direction_neg_z(matrix):
56 return (matrix.to_3x3() * mathutils.Vector((0.0, 0.0, -1.0))).normalized()[:]
59 def prefix_quoted_str(value, prefix):
60 return value[0] + prefix + value[1:]
63 def suffix_quoted_str(value, suffix):
64 return value[:-1] + suffix + value[-1:]
67 def bool_as_str(value):
68 return ('false', 'true')[bool(value)]
71 def clean_def(txt):
72 # see report [#28256]
73 if not txt:
74 txt = "None"
75 # no digit start
76 if txt[0] in "1234567890+-":
77 txt = "_" + txt
78 return txt.translate({
79 # control characters 0x0-0x1f
80 # 0x00: "_",
81 0x01: "_",
82 0x02: "_",
83 0x03: "_",
84 0x04: "_",
85 0x05: "_",
86 0x06: "_",
87 0x07: "_",
88 0x08: "_",
89 0x09: "_",
90 0x0a: "_",
91 0x0b: "_",
92 0x0c: "_",
93 0x0d: "_",
94 0x0e: "_",
95 0x0f: "_",
96 0x10: "_",
97 0x11: "_",
98 0x12: "_",
99 0x13: "_",
100 0x14: "_",
101 0x15: "_",
102 0x16: "_",
103 0x17: "_",
104 0x18: "_",
105 0x19: "_",
106 0x1a: "_",
107 0x1b: "_",
108 0x1c: "_",
109 0x1d: "_",
110 0x1e: "_",
111 0x1f: "_",
113 0x7f: "_", # 127
115 0x20: "_", # space
116 0x22: "_", # "
117 0x27: "_", # '
118 0x23: "_", # #
119 0x2c: "_", # ,
120 0x2e: "_", # .
121 0x5b: "_", # [
122 0x5d: "_", # ]
123 0x5c: "_", # \
124 0x7b: "_", # {
125 0x7d: "_", # }
129 def build_hierarchy(objects):
130 """ returns parent child relationships, skipping
132 objects_set = set(objects)
133 par_lookup = {}
135 def test_parent(parent):
136 while (parent is not None) and (parent not in objects_set):
137 parent = parent.parent
138 return parent
140 for obj in objects:
141 par_lookup.setdefault(test_parent(obj.parent), []).append((obj, []))
143 for parent, children in par_lookup.items():
144 for obj, subchildren in children:
145 subchildren[:] = par_lookup.get(obj, [])
147 return par_lookup.get(None, [])
150 # -----------------------------------------------------------------------------
151 # H3D Functions
152 # -----------------------------------------------------------------------------
153 def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_map):
154 h3d_file = open(filepath, 'r', encoding='utf-8')
155 lines = []
157 last_transform = None
159 for l in h3d_file:
160 if l.startswith("void main(void)"):
161 lines.append("\n")
162 lines.append("// h3d custom vars begin\n")
163 for v in global_vars:
164 lines.append("%s\n" % v)
165 lines.append("// h3d custom vars end\n")
166 lines.append("\n")
167 elif l.lstrip().startswith("lamp_visibility_other("):
168 w = l.split(', ')
169 last_transform = w[1] + "_transform" # XXX - HACK!!!
170 w[1] = '(view_matrix * %s_transform * vec4(%s.x, %s.y, %s.z, 1.0)).xyz' % (w[1], w[1], w[1], w[1])
171 l = ", ".join(w)
172 elif l.lstrip().startswith("lamp_visibility_sun_hemi("):
173 w = l.split(', ')
174 w[0] = w[0][len("lamp_visibility_sun_hemi(") + 1:]
176 if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
177 w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
178 else:
179 w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * -%s)' %
180 (last_transform, last_transform, last_transform, w[0]))
182 l = "\tlamp_visibility_sun_hemi(" + ", ".join(w)
183 elif l.lstrip().startswith("lamp_visibility_spot_circle("):
184 w = l.split(', ')
185 w[0] = w[0][len("lamp_visibility_spot_circle(") + 1:]
187 if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
188 w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
189 else:
190 w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * %s)' %
191 (last_transform, last_transform, last_transform, w[0]))
193 l = "\tlamp_visibility_spot_circle(" + ", ".join(w)
195 lines.append(l)
197 h3d_file.close()
199 h3d_file = open(filepath, 'w', encoding='utf-8')
200 h3d_file.writelines(lines)
201 h3d_file.close()
204 def h3d_is_object_view(scene, obj):
205 camera = scene.camera
206 parent = obj.parent
207 while parent:
208 if parent == camera:
209 return True
210 parent = parent.parent
211 return False
214 # -----------------------------------------------------------------------------
215 # Functions for writing output file
216 # -----------------------------------------------------------------------------
218 def export(file,
219 global_matrix,
220 scene,
221 use_mesh_modifiers=False,
222 use_selection=True,
223 use_triangulate=False,
224 use_normals=False,
225 use_hierarchy=True,
226 use_h3d=False,
227 path_mode='AUTO',
228 name_decorations=True,
231 # -------------------------------------------------------------------------
232 # Global Setup
233 # -------------------------------------------------------------------------
234 import bpy_extras
235 from bpy_extras.io_utils import unique_name
236 from xml.sax.saxutils import quoteattr, escape
238 if name_decorations:
239 # If names are decorated, the uuid map can be split up
240 # by type for efficiency of collision testing
241 # since objects of different types will always have
242 # different decorated names.
243 uuid_cache_object = {} # object
244 uuid_cache_lamp = {} # 'LA_' + object.name
245 uuid_cache_view = {} # object, different namespace
246 uuid_cache_mesh = {} # mesh
247 uuid_cache_material = {} # material
248 uuid_cache_image = {} # image
249 uuid_cache_world = {} # world
250 CA_ = 'CA_'
251 OB_ = 'OB_'
252 ME_ = 'ME_'
253 IM_ = 'IM_'
254 WO_ = 'WO_'
255 MA_ = 'MA_'
256 LA_ = 'LA_'
257 group_ = 'group_'
258 else:
259 # If names are not decorated, it may be possible for two objects to
260 # have the same name, so there has to be a unified dictionary to
261 # prevent uuid collisions.
262 uuid_cache = {}
263 uuid_cache_object = uuid_cache # object
264 uuid_cache_lamp = uuid_cache # 'LA_' + object.name
265 uuid_cache_view = uuid_cache # object, different namespace
266 uuid_cache_mesh = uuid_cache # mesh
267 uuid_cache_material = uuid_cache # material
268 uuid_cache_image = uuid_cache # image
269 uuid_cache_world = uuid_cache # world
270 del uuid_cache
271 CA_ = ''
272 OB_ = ''
273 ME_ = ''
274 IM_ = ''
275 WO_ = ''
276 MA_ = ''
277 LA_ = ''
278 group_ = ''
280 _TRANSFORM = '_TRANSFORM'
282 # store files to copy
283 copy_set = set()
285 # store names of newly cerated meshes, so we dont overlap
286 mesh_name_set = set()
288 fw = file.write
289 base_src = os.path.dirname(bpy.data.filepath)
290 base_dst = os.path.dirname(file.name)
291 filename_strip = os.path.splitext(os.path.basename(file.name))[0]
292 gpu_shader_cache = {}
294 if use_h3d:
295 import gpu
296 gpu_shader_dummy_mat = bpy.data.materials.new('X3D_DYMMY_MAT')
297 gpu_shader_cache[None] = gpu.export_shader(scene, gpu_shader_dummy_mat)
298 h3d_material_route = []
300 # -------------------------------------------------------------------------
301 # File Writing Functions
302 # -------------------------------------------------------------------------
304 def writeHeader(ident):
305 filepath_quoted = quoteattr(os.path.basename(file.name))
306 blender_ver_quoted = quoteattr('Blender %s' % bpy.app.version_string)
308 fw('%s<?xml version="1.0" encoding="UTF-8"?>\n' % ident)
309 if use_h3d:
310 fw('%s<X3D profile="H3DAPI" version="1.4">\n' % ident)
311 else:
312 fw('%s<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "http://www.web3d.org/specifications/x3d-3.0.dtd">\n' % ident)
313 fw('%s<X3D version="3.0" profile="Immersive" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="http://www.web3d.org/specifications/x3d-3.0.xsd">\n' % ident)
315 ident += '\t'
316 fw('%s<head>\n' % ident)
317 ident += '\t'
318 fw('%s<meta name="filename" content=%s />\n' % (ident, filepath_quoted))
319 fw('%s<meta name="generator" content=%s />\n' % (ident, blender_ver_quoted))
320 # this info was never updated, so blender version should be enough
321 # fw('%s<meta name="translator" content="X3D exporter v1.55 (2006/01/17)" />\n' % ident)
322 ident = ident[:-1]
323 fw('%s</head>\n' % ident)
324 fw('%s<Scene>\n' % ident)
325 ident += '\t'
327 if use_h3d:
328 # outputs the view matrix in glModelViewMatrix field
329 fw('%s<TransformInfo DEF="%s" outputGLMatrices="true" />\n' % (ident, H3D_TOP_LEVEL))
331 return ident
333 def writeFooter(ident):
335 if use_h3d:
336 # global
337 for route in h3d_material_route:
338 fw('%s%s\n' % (ident, route))
340 ident = ident[:-1]
341 fw('%s</Scene>\n' % ident)
342 ident = ident[:-1]
343 fw('%s</X3D>' % ident)
344 return ident
346 def writeViewpoint(ident, obj, matrix, scene):
347 view_id = quoteattr(unique_name(obj, CA_ + obj.name, uuid_cache_view, clean_func=clean_def, sep="_"))
349 loc, rot, scale = matrix.decompose()
350 rot = rot.to_axis_angle()
351 rot = (*rot[0].normalized(), rot[1])
353 ident_step = ident + (' ' * (-len(ident) + \
354 fw('%s<Viewpoint ' % ident)))
355 fw('DEF=%s\n' % view_id)
356 fw(ident_step + 'centerOfRotation="0 0 0"\n')
357 fw(ident_step + 'position="%3.2f %3.2f %3.2f"\n' % loc[:])
358 fw(ident_step + 'orientation="%3.2f %3.2f %3.2f %3.2f"\n' % rot)
359 fw(ident_step + 'fieldOfView="%.3f"\n' % obj.data.angle)
360 fw(ident_step + '/>\n')
362 def writeFog(ident, world):
363 if world:
364 mtype = world.mist_settings.falloff
365 mparam = world.mist_settings
366 else:
367 return
369 if mparam.use_mist:
370 ident_step = ident + (' ' * (-len(ident) + \
371 fw('%s<Fog ' % ident)))
372 fw('fogType="%s"\n' % ('LINEAR' if (mtype == 'LINEAR') else 'EXPONENTIAL'))
373 fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clamp_color(world.horizon_color))
374 fw(ident_step + 'visibilityRange="%.3f"\n' % mparam.depth)
375 fw(ident_step + '/>\n')
376 else:
377 return
379 def writeNavigationInfo(ident, scene, has_lamp):
380 ident_step = ident + (' ' * (-len(ident) + \
381 fw('%s<NavigationInfo ' % ident)))
382 fw('headlight="%s"\n' % bool_as_str(not has_lamp))
383 fw(ident_step + 'visibilityLimit="0.0"\n')
384 fw(ident_step + 'type=\'"EXAMINE", "ANY"\'\n')
385 fw(ident_step + 'avatarSize="0.25, 1.75, 0.75"\n')
386 fw(ident_step + '/>\n')
388 def writeTransform_begin(ident, matrix, def_id):
389 ident_step = ident + (' ' * (-len(ident) + \
390 fw('%s<Transform ' % ident)))
391 if def_id is not None:
392 fw('DEF=%s\n' % def_id)
393 else:
394 fw('\n')
396 loc, rot, sca = matrix.decompose()
397 rot = rot.to_axis_angle()
398 rot = (*rot[0], rot[1])
400 fw(ident_step + 'translation="%.6f %.6f %.6f"\n' % loc[:])
401 # fw(ident_step + 'center="%.6f %.6f %.6f"\n' % (0, 0, 0))
402 fw(ident_step + 'scale="%.6f %.6f %.6f"\n' % sca[:])
403 fw(ident_step + 'rotation="%.6f %.6f %.6f %.6f"\n' % rot)
404 fw(ident_step + '>\n')
405 ident += '\t'
406 return ident
408 def writeTransform_end(ident):
409 ident = ident[:-1]
410 fw('%s</Transform>\n' % ident)
411 return ident
413 def writeSpotLight(ident, obj, matrix, lamp, world):
414 # note, lamp_id is not re-used
415 lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
417 if world:
418 ambi = world.ambient_color
419 amb_intensity = ((ambi[0] + ambi[1] + ambi[2]) / 3.0) / 2.5
420 del ambi
421 else:
422 amb_intensity = 0.0
424 # compute cutoff and beamwidth
425 intensity = min(lamp.energy / 1.75, 1.0)
426 beamWidth = lamp.spot_size * 0.37
427 # beamWidth=((lamp.spotSize*math.pi)/180.0)*.37
428 cutOffAngle = beamWidth * 1.3
430 orientation = matrix_direction_neg_z(matrix)
432 location = matrix.to_translation()[:]
434 radius = lamp.distance * math.cos(beamWidth)
435 # radius = lamp.dist*math.cos(beamWidth)
436 ident_step = ident + (' ' * (-len(ident) + \
437 fw('%s<SpotLight ' % ident)))
438 fw('DEF=%s\n' % lamp_id)
439 fw(ident_step + 'radius="%.4f"\n' % radius)
440 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
441 fw(ident_step + 'intensity="%.4f"\n' % intensity)
442 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
443 fw(ident_step + 'beamWidth="%.4f"\n' % beamWidth)
444 fw(ident_step + 'cutOffAngle="%.4f"\n' % cutOffAngle)
445 fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
446 fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location)
447 fw(ident_step + '/>\n')
449 def writeDirectionalLight(ident, obj, matrix, lamp, world):
450 # note, lamp_id is not re-used
451 lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
453 if world:
454 ambi = world.ambient_color
455 # ambi = world.amb
456 amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5
457 else:
458 ambi = 0
459 amb_intensity = 0.0
461 intensity = min(lamp.energy / 1.75, 1.0)
463 orientation = matrix_direction_neg_z(matrix)
465 ident_step = ident + (' ' * (-len(ident) + \
466 fw('%s<DirectionalLight ' % ident)))
467 fw('DEF=%s\n' % lamp_id)
468 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
469 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
470 fw(ident_step + 'intensity="%.4f"\n' % intensity)
471 fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
472 fw(ident_step + '/>\n')
474 def writePointLight(ident, obj, matrix, lamp, world):
475 # note, lamp_id is not re-used
476 lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
478 if world:
479 ambi = world.ambient_color
480 # ambi = world.amb
481 amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5
482 else:
483 ambi = 0.0
484 amb_intensity = 0.0
486 intensity = min(lamp.energy / 1.75, 1.0)
487 location = matrix.to_translation()[:]
489 ident_step = ident + (' ' * (-len(ident) + \
490 fw('%s<PointLight ' % ident)))
491 fw('DEF=%s\n' % lamp_id)
492 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
493 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color))
495 fw(ident_step + 'intensity="%.4f"\n' % intensity)
496 fw(ident_step + 'radius="%.4f" \n' % lamp.distance)
497 fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location)
498 fw(ident_step + '/>\n')
500 def writeIndexedFaceSet(ident, obj, mesh, matrix, world):
501 obj_id = quoteattr(unique_name(obj, OB_ + obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
502 mesh_id = quoteattr(unique_name(mesh, ME_ + mesh.name, uuid_cache_mesh, clean_func=clean_def, sep="_"))
503 mesh_id_group = prefix_quoted_str(mesh_id, group_)
504 mesh_id_coords = prefix_quoted_str(mesh_id, 'coords_')
505 mesh_id_normals = prefix_quoted_str(mesh_id, 'normals_')
507 # tessellation faces may not exist
508 if not mesh.tessfaces and mesh.polygons:
509 mesh.update(calc_tessface=True)
511 if not mesh.tessfaces:
512 return
514 use_collnode = bool([mod for mod in obj.modifiers
515 if mod.type == 'COLLISION'
516 if mod.show_viewport])
518 if use_collnode:
519 fw('%s<Collision enabled="true">\n' % ident)
520 ident += '\t'
522 # use _ifs_TRANSFORM suffix so we dont collide with transform node when
523 # hierarchys are used.
524 ident = writeTransform_begin(ident, matrix, suffix_quoted_str(obj_id, "_ifs" + _TRANSFORM))
526 if mesh.tag:
527 fw('%s<Group USE=%s />\n' % (ident, mesh_id_group))
528 else:
529 mesh.tag = True
531 fw('%s<Group DEF=%s>\n' % (ident, mesh_id_group))
532 ident += '\t'
534 is_uv = bool(mesh.tessface_uv_textures.active)
535 # is_col, defined for each material
537 is_coords_written = False
539 mesh_materials = mesh.materials[:]
540 if not mesh_materials:
541 mesh_materials = [None]
543 mesh_material_tex = [None] * len(mesh_materials)
544 mesh_material_mtex = [None] * len(mesh_materials)
545 mesh_material_images = [None] * len(mesh_materials)
547 for i, material in enumerate(mesh_materials):
548 if material:
549 for mtex in material.texture_slots:
550 if mtex:
551 tex = mtex.texture
552 if tex and tex.type == 'IMAGE':
553 image = tex.image
554 if image:
555 mesh_material_tex[i] = tex
556 mesh_material_mtex[i] = mtex
557 mesh_material_images[i] = image
558 break
560 mesh_materials_use_face_texture = [getattr(material, 'use_face_texture', True) for material in mesh_materials]
562 # fast access!
563 mesh_vertices = mesh.vertices[:]
564 mesh_faces = mesh.tessfaces[:]
565 mesh_faces_materials = [f.material_index for f in mesh_faces]
566 mesh_faces_vertices = [f.vertices[:] for f in mesh_faces]
568 if is_uv and True in mesh_materials_use_face_texture:
569 mesh_faces_image = [(fuv.image
570 if mesh_materials_use_face_texture[mesh_faces_materials[i]]
571 else mesh_material_images[mesh_faces_materials[i]])
572 for i, fuv in enumerate(mesh.tessface_uv_textures.active.data)]
574 mesh_faces_image_unique = set(mesh_faces_image)
575 elif len(set(mesh_material_images) | {None}) > 1: # make sure there is at least one image
576 mesh_faces_image = [mesh_material_images[material_index] for material_index in mesh_faces_materials]
577 mesh_faces_image_unique = set(mesh_faces_image)
578 else:
579 mesh_faces_image = [None] * len(mesh_faces)
580 mesh_faces_image_unique = {None}
582 # group faces
583 face_groups = {}
584 for material_index in range(len(mesh_materials)):
585 for image in mesh_faces_image_unique:
586 face_groups[material_index, image] = []
587 del mesh_faces_image_unique
589 for i, (material_index, image) in enumerate(zip(mesh_faces_materials, mesh_faces_image)):
590 face_groups[material_index, image].append(i)
592 # same as face_groups.items() but sorted so we can get predictable output.
593 face_groups_items = list(face_groups.items())
594 face_groups_items.sort(key=lambda m: (m[0][0], getattr(m[0][1], 'name', '')))
596 is_col = (mesh.tessface_vertex_colors.active and (material is None or material.use_vertex_color_paint))
597 mesh_faces_col = mesh.tessface_vertex_colors.active.data if is_col else None
599 # Check if vertex colors can be exported in per-vertex mode.
600 # Do we have just one color per vertex in every face that uses the vertex?
601 if is_col:
602 def calc_vertex_color():
603 vert_color = [None] * len(mesh.vertices)
605 for i, face in enumerate(mesh_faces):
606 fcol = mesh_faces_col[i]
607 face_colors = (fcol.color1, fcol.color2, fcol.color3, fcol.color4)
608 for j, vert_index in enumerate(face.vertices):
609 if vert_color[vert_index] is None:
610 vert_color[vert_index] = face_colors[j][:]
611 elif vert_color[vert_index] != face_colors[j][:]:
612 return False, ()
614 return True, vert_color
616 is_col_per_vertex, vert_color = calc_vertex_color()
617 del calc_vertex_color
619 for (material_index, image), face_group in face_groups_items: # face_groups.items()
620 if face_group:
621 material = mesh_materials[material_index]
623 fw('%s<Shape>\n' % ident)
624 ident += '\t'
626 is_smooth = False
628 # kludge but as good as it gets!
629 for i in face_group:
630 if mesh_faces[i].use_smooth:
631 is_smooth = True
632 break
634 # UV's and VCols split verts off which effects smoothing
635 # force writing normals in this case.
636 # Also, creaseAngle is not supported for IndexedTriangleSet,
637 # so write normals when is_smooth (otherwise
638 # IndexedTriangleSet can have only all smooth/all flat shading).
639 is_force_normals = use_triangulate and (is_smooth or is_uv or is_col)
641 if use_h3d:
642 gpu_shader = gpu_shader_cache.get(material) # material can be 'None', uses dummy cache
643 if gpu_shader is None:
644 gpu_shader = gpu_shader_cache[material] = gpu.export_shader(scene, material)
646 if 1: # XXX DEBUG
647 gpu_shader_tmp = gpu.export_shader(scene, material)
648 import pprint
649 print('\nWRITING MATERIAL:', material.name)
650 del gpu_shader_tmp['fragment']
651 del gpu_shader_tmp['vertex']
652 pprint.pprint(gpu_shader_tmp, width=120)
653 #pprint.pprint(val['vertex'])
654 del gpu_shader_tmp
656 fw('%s<Appearance>\n' % ident)
657 ident += '\t'
659 if image and not use_h3d:
660 writeImageTexture(ident, image)
662 if mesh_materials_use_face_texture[material_index]:
663 if image.use_tiles:
664 fw('%s<TextureTransform scale="%s %s" />\n' % (ident, image.tiles_x, image.tiles_y))
665 else:
666 # transform by mtex
667 loc = mesh_material_mtex[material_index].offset[:2]
669 # mtex_scale * tex_repeat
670 sca_x, sca_y = mesh_material_mtex[material_index].scale[:2]
672 sca_x *= mesh_material_tex[material_index].repeat_x
673 sca_y *= mesh_material_tex[material_index].repeat_y
675 # flip x/y is a sampling feature, convert to transform
676 if mesh_material_tex[material_index].use_flip_axis:
677 rot = math.pi / -2.0
678 sca_x, sca_y = sca_y, -sca_x
679 else:
680 rot = 0.0
682 ident_step = ident + (' ' * (-len(ident) + \
683 fw('%s<TextureTransform ' % ident)))
684 fw('\n')
685 # fw('center="%.6f %.6f" ' % (0.0, 0.0))
686 fw(ident_step + 'translation="%.6f %.6f"\n' % loc)
687 fw(ident_step + 'scale="%.6f %.6f"\n' % (sca_x, sca_y))
688 fw(ident_step + 'rotation="%.6f"\n' % rot)
689 fw(ident_step + '/>\n')
691 if use_h3d:
692 mat_tmp = material if material else gpu_shader_dummy_mat
693 writeMaterialH3D(ident, mat_tmp, world,
694 obj, gpu_shader)
695 del mat_tmp
696 else:
697 if material:
698 writeMaterial(ident, material, world)
700 ident = ident[:-1]
701 fw('%s</Appearance>\n' % ident)
703 mesh_faces_uv = mesh.tessface_uv_textures.active.data if is_uv else None
705 #-- IndexedFaceSet or IndexedLineSet
706 if use_triangulate:
707 ident_step = ident + (' ' * (-len(ident) + \
708 fw('%s<IndexedTriangleSet ' % ident)))
710 # --- Write IndexedTriangleSet Attributes (same as IndexedFaceSet)
711 fw('solid="%s"\n' % bool_as_str(material and material.game_settings.use_backface_culling))
713 if use_normals or is_force_normals:
714 fw(ident_step + 'normalPerVertex="true"\n')
715 else:
716 # Tell X3D browser to generate flat (per-face) normals
717 fw(ident_step + 'normalPerVertex="false"\n')
719 slot_uv = None
720 slot_col = None
722 if is_uv and is_col:
723 slot_uv = 0
724 slot_col = 1
726 def vertex_key(fidx, f_cnr_idx):
727 return (
728 mesh_faces_uv[fidx].uv[f_cnr_idx][:],
729 getattr(mesh_faces_col[fidx], "color%d" % (f_cnr_idx + 1))[:],
731 elif is_uv:
732 slot_uv = 0
734 def vertex_key(fidx, f_cnr_idx):
735 return (
736 mesh_faces_uv[fidx].uv[f_cnr_idx][:],
738 elif is_col:
739 slot_col = 0
741 def vertex_key(fidx, f_cnr_idx):
742 return (
743 getattr(mesh_faces_col[fidx], "color%d" % (f_cnr_idx + 1))[:],
745 else:
746 # ack, not especially efficient in this case
747 def vertex_key(fidx, f_cnr_idx):
748 return None
750 # build a mesh mapping dict
751 vertex_hash = [{} for i in range(len(mesh.vertices))]
752 # worst case every face is a quad
753 face_tri_list = [[None, None, None] for i in range(len(mesh.tessfaces) * 2)]
754 vert_tri_list = []
755 totvert = 0
756 totface = 0
757 temp_face = [None] * 4
758 for i in face_group:
759 fv = mesh_faces_vertices[i]
760 for j, v_idx in enumerate(fv):
761 key = vertex_key(i, j)
762 vh = vertex_hash[v_idx]
763 x3d_v = vh.get(key)
764 if x3d_v is None:
765 x3d_v = key, v_idx, totvert
766 vh[key] = x3d_v
767 # key / original_vertex / new_vertex
768 vert_tri_list.append(x3d_v)
769 totvert += 1
770 temp_face[j] = x3d_v
772 if len(fv) == 4:
773 f_iter = ((0, 1, 2), (0, 2, 3))
774 else:
775 f_iter = ((0, 1, 2), )
777 for f_it in f_iter:
778 # loop over a quad as 2 tris
779 f_tri = face_tri_list[totface]
780 for ji, j in enumerate(f_it):
781 f_tri[ji] = temp_face[j]
782 # quads run this twice
783 totface += 1
785 # clear unused faces
786 face_tri_list[totface:] = []
788 fw(ident_step + 'index="')
789 for x3d_f in face_tri_list:
790 fw('%i %i %i ' % (x3d_f[0][2], x3d_f[1][2], x3d_f[2][2]))
791 fw('"\n')
793 # close IndexedTriangleSet
794 fw(ident_step + '>\n')
795 ident += '\t'
797 fw('%s<Coordinate ' % ident)
798 fw('point="')
799 for x3d_v in vert_tri_list:
800 fw('%.6f %.6f %.6f ' % mesh_vertices[x3d_v[1]].co[:])
801 fw('" />\n')
803 if use_normals or is_force_normals:
804 fw('%s<Normal ' % ident)
805 fw('vector="')
806 for x3d_v in vert_tri_list:
807 fw('%.6f %.6f %.6f ' % mesh_vertices[x3d_v[1]].normal[:])
808 fw('" />\n')
810 if is_uv:
811 fw('%s<TextureCoordinate point="' % ident)
812 for x3d_v in vert_tri_list:
813 fw('%.4f %.4f ' % x3d_v[0][slot_uv])
814 fw('" />\n')
816 if is_col:
817 fw('%s<Color color="' % ident)
818 for x3d_v in vert_tri_list:
819 fw('%.3f %.3f %.3f ' % x3d_v[0][slot_col])
820 fw('" />\n')
822 if use_h3d:
823 # write attributes
824 for gpu_attr in gpu_shader['attributes']:
826 # UVs
827 if gpu_attr['type'] == gpu.CD_MTFACE:
828 if gpu_attr['datatype'] == gpu.GPU_DATA_2F:
829 fw('%s<FloatVertexAttribute ' % ident)
830 fw('name="%s" ' % gpu_attr['varname'])
831 fw('numComponents="2" ')
832 fw('value="')
833 for x3d_v in vert_tri_list:
834 fw('%.4f %.4f ' % x3d_v[0][slot_uv])
835 fw('" />\n')
836 else:
837 assert(0)
839 elif gpu_attr['type'] == gpu.CD_MCOL:
840 if gpu_attr['datatype'] == gpu.GPU_DATA_4UB:
841 pass # XXX, H3D can't do
842 else:
843 assert(0)
845 ident = ident[:-1]
847 fw('%s</IndexedTriangleSet>\n' % ident)
849 else:
850 ident_step = ident + (' ' * (-len(ident) + \
851 fw('%s<IndexedFaceSet ' % ident)))
853 # --- Write IndexedFaceSet Attributes (same as IndexedTriangleSet)
854 fw('solid="%s"\n' % bool_as_str(material and material.game_settings.use_backface_culling))
855 if is_smooth:
856 # use Auto-Smooth angle, if enabled. Otherwise make
857 # the mesh perfectly smooth by creaseAngle > pi.
858 fw(ident_step + 'creaseAngle="%.4f"\n' % (mesh.auto_smooth_angle if mesh.use_auto_smooth else 4.0))
860 if use_normals:
861 # currently not optional, could be made so:
862 fw(ident_step + 'normalPerVertex="true"\n')
864 # IndexedTriangleSet assumes true
865 if is_col and not is_col_per_vertex:
866 fw(ident_step + 'colorPerVertex="false"\n')
868 # for IndexedTriangleSet we use a uv per vertex so this isnt needed.
869 if is_uv:
870 fw(ident_step + 'texCoordIndex="')
872 j = 0
873 for i in face_group:
874 if len(mesh_faces_vertices[i]) == 4:
875 fw('%d %d %d %d -1 ' % (j, j + 1, j + 2, j + 3))
876 j += 4
877 else:
878 fw('%d %d %d -1 ' % (j, j + 1, j + 2))
879 j += 3
880 fw('"\n')
881 # --- end texCoordIndex
883 if True:
884 fw(ident_step + 'coordIndex="')
885 for i in face_group:
886 fv = mesh_faces_vertices[i]
887 if len(fv) == 3:
888 fw('%i %i %i -1 ' % fv)
889 else:
890 fw('%i %i %i %i -1 ' % fv)
892 fw('"\n')
893 # --- end coordIndex
895 # close IndexedFaceSet
896 fw(ident_step + '>\n')
897 ident += '\t'
899 # --- Write IndexedFaceSet Elements
900 if True:
901 if is_coords_written:
902 fw('%s<Coordinate USE=%s />\n' % (ident, mesh_id_coords))
903 if use_normals:
904 fw('%s<Normal USE=%s />\n' % (ident, mesh_id_normals))
905 else:
906 ident_step = ident + (' ' * (-len(ident) + \
907 fw('%s<Coordinate ' % ident)))
908 fw('DEF=%s\n' % mesh_id_coords)
909 fw(ident_step + 'point="')
910 for v in mesh.vertices:
911 fw('%.6f %.6f %.6f ' % v.co[:])
912 fw('"\n')
913 fw(ident_step + '/>\n')
915 is_coords_written = True
917 if use_normals:
918 ident_step = ident + (' ' * (-len(ident) + \
919 fw('%s<Normal ' % ident)))
920 fw('DEF=%s\n' % mesh_id_normals)
921 fw(ident_step + 'vector="')
922 for v in mesh.vertices:
923 fw('%.6f %.6f %.6f ' % v.normal[:])
924 fw('"\n')
925 fw(ident_step + '/>\n')
927 if is_uv:
928 fw('%s<TextureCoordinate point="' % ident)
929 for i in face_group:
930 for uv in mesh_faces_uv[i].uv:
931 fw('%.4f %.4f ' % uv[:])
932 del mesh_faces_uv
933 fw('" />\n')
935 if is_col:
936 # Need better logic here, dynamic determination
937 # which of the X3D coloring models fits better this mesh - per face
938 # or per vertex. Probably with an explicit fallback mode parameter.
939 fw('%s<Color color="' % ident)
940 if is_col_per_vertex:
941 for i in range(len(mesh.vertices)):
942 # may be None,
943 fw('%.3f %.3f %.3f ' % (vert_color[i] or (0.0, 0.0, 0.0)))
944 else: # Export as colors per face.
945 # TODO: average them rather than using the first one!
946 for i in face_group:
947 fw('%.3f %.3f %.3f ' % mesh_faces_col[i].color1[:])
948 fw('" />\n')
950 #--- output vertexColors
952 #--- output closing braces
953 ident = ident[:-1]
955 fw('%s</IndexedFaceSet>\n' % ident)
957 ident = ident[:-1]
958 fw('%s</Shape>\n' % ident)
960 # XXX
962 #fw('%s<PythonScript DEF="PS" url="object.py" >\n' % ident)
963 #fw('%s <ShaderProgram USE="MA_Material.005" containerField="references"/>\n' % ident)
964 #fw('%s</PythonScript>\n' % ident)
966 ident = ident[:-1]
967 fw('%s</Group>\n' % ident)
969 ident = writeTransform_end(ident)
971 if use_collnode:
972 ident = ident[:-1]
973 fw('%s</Collision>\n' % ident)
975 def writeMaterial(ident, material, world):
976 material_id = quoteattr(unique_name(material, MA_ + material.name, uuid_cache_material, clean_func=clean_def, sep="_"))
978 # look up material name, use it if available
979 if material.tag:
980 fw('%s<Material USE=%s />\n' % (ident, material_id))
981 else:
982 material.tag = True
984 emit = material.emit
985 ambient = material.ambient / 3.0
986 diffuseColor = material.diffuse_color[:]
987 if world:
988 ambiColor = ((material.ambient * 2.0) * world.ambient_color)[:]
989 else:
990 ambiColor = 0.0, 0.0, 0.0
992 emitColor = tuple(((c * emit) + ambiColor[i]) / 2.0 for i, c in enumerate(diffuseColor))
993 shininess = material.specular_hardness / 512.0
994 specColor = tuple((c + 0.001) / (1.25 / (material.specular_intensity + 0.001)) for c in material.specular_color)
995 transp = 1.0 - material.alpha
997 if material.use_shadeless:
998 ambient = 1.0
999 shininess = 0.0
1000 specColor = emitColor = diffuseColor
1002 ident_step = ident + (' ' * (-len(ident) + \
1003 fw('%s<Material ' % ident)))
1004 fw('DEF=%s\n' % material_id)
1005 fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clamp_color(diffuseColor))
1006 fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clamp_color(specColor))
1007 fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clamp_color(emitColor))
1008 fw(ident_step + 'ambientIntensity="%.3f"\n' % ambient)
1009 fw(ident_step + 'shininess="%.3f"\n' % shininess)
1010 fw(ident_step + 'transparency="%s"\n' % transp)
1011 fw(ident_step + '/>\n')
1013 def writeMaterialH3D(ident, material, world,
1014 obj, gpu_shader):
1015 material_id = quoteattr(unique_name(material, 'MA_' + material.name, uuid_cache_material, clean_func=clean_def, sep="_"))
1017 fw('%s<Material />\n' % ident)
1018 if material.tag:
1019 fw('%s<ComposedShader USE=%s />\n' % (ident, material_id))
1020 else:
1021 material.tag = True
1023 # GPU_material_bind_uniforms
1024 # GPU_begin_object_materials
1026 #~ CD_MCOL 6
1027 #~ CD_MTFACE 5
1028 #~ CD_ORCO 14
1029 #~ CD_TANGENT 18
1030 #~ GPU_DATA_16F 7
1031 #~ GPU_DATA_1F 2
1032 #~ GPU_DATA_1I 1
1033 #~ GPU_DATA_2F 3
1034 #~ GPU_DATA_3F 4
1035 #~ GPU_DATA_4F 5
1036 #~ GPU_DATA_4UB 8
1037 #~ GPU_DATA_9F 6
1038 #~ GPU_DYNAMIC_LAMP_DYNCO 7
1039 #~ GPU_DYNAMIC_LAMP_DYNCOL 11
1040 #~ GPU_DYNAMIC_LAMP_DYNENERGY 10
1041 #~ GPU_DYNAMIC_LAMP_DYNIMAT 8
1042 #~ GPU_DYNAMIC_LAMP_DYNPERSMAT 9
1043 #~ GPU_DYNAMIC_LAMP_DYNVEC 6
1044 #~ GPU_DYNAMIC_OBJECT_COLOR 5
1045 #~ GPU_DYNAMIC_OBJECT_IMAT 4
1046 #~ GPU_DYNAMIC_OBJECT_MAT 2
1047 #~ GPU_DYNAMIC_OBJECT_VIEWIMAT 3
1048 #~ GPU_DYNAMIC_OBJECT_VIEWMAT 1
1049 #~ GPU_DYNAMIC_SAMPLER_2DBUFFER 12
1050 #~ GPU_DYNAMIC_SAMPLER_2DIMAGE 13
1051 #~ GPU_DYNAMIC_SAMPLER_2DSHADOW 14
1054 inline const char* typeToString( X3DType t ) {
1055 switch( t ) {
1056 case SFFLOAT: return "SFFloat";
1057 case MFFLOAT: return "MFFloat";
1058 case SFDOUBLE: return "SFDouble";
1059 case MFDOUBLE: return "MFDouble";
1060 case SFTIME: return "SFTime";
1061 case MFTIME: return "MFTime";
1062 case SFINT32: return "SFInt32";
1063 case MFINT32: return "MFInt32";
1064 case SFVEC2F: return "SFVec2f";
1065 case MFVEC2F: return "MFVec2f";
1066 case SFVEC2D: return "SFVec2d";
1067 case MFVEC2D: return "MFVec2d";
1068 case SFVEC3F: return "SFVec3f";
1069 case MFVEC3F: return "MFVec3f";
1070 case SFVEC3D: return "SFVec3d";
1071 case MFVEC3D: return "MFVec3d";
1072 case SFVEC4F: return "SFVec4f";
1073 case MFVEC4F: return "MFVec4f";
1074 case SFVEC4D: return "SFVec4d";
1075 case MFVEC4D: return "MFVec4d";
1076 case SFBOOL: return "SFBool";
1077 case MFBOOL: return "MFBool";
1078 case SFSTRING: return "SFString";
1079 case MFSTRING: return "MFString";
1080 case SFNODE: return "SFNode";
1081 case MFNODE: return "MFNode";
1082 case SFCOLOR: return "SFColor";
1083 case MFCOLOR: return "MFColor";
1084 case SFCOLORRGBA: return "SFColorRGBA";
1085 case MFCOLORRGBA: return "MFColorRGBA";
1086 case SFROTATION: return "SFRotation";
1087 case MFROTATION: return "MFRotation";
1088 case SFQUATERNION: return "SFQuaternion";
1089 case MFQUATERNION: return "MFQuaternion";
1090 case SFMATRIX3F: return "SFMatrix3f";
1091 case MFMATRIX3F: return "MFMatrix3f";
1092 case SFMATRIX4F: return "SFMatrix4f";
1093 case MFMATRIX4F: return "MFMatrix4f";
1094 case SFMATRIX3D: return "SFMatrix3d";
1095 case MFMATRIX3D: return "MFMatrix3d";
1096 case SFMATRIX4D: return "SFMatrix4d";
1097 case MFMATRIX4D: return "MFMatrix4d";
1098 case UNKNOWN_X3D_TYPE:
1099 default:return "UNKNOWN_X3D_TYPE";
1101 import gpu
1103 fw('%s<ComposedShader DEF=%s language="GLSL" >\n' % (ident, material_id))
1104 ident += '\t'
1106 shader_url_frag = 'shaders/%s_%s.frag' % (filename_strip, material_id[1:-1])
1107 shader_url_vert = 'shaders/%s_%s.vert' % (filename_strip, material_id[1:-1])
1109 # write files
1110 shader_dir = os.path.join(base_dst, 'shaders')
1111 if not os.path.isdir(shader_dir):
1112 os.mkdir(shader_dir)
1114 # ------------------------------------------------------
1115 # shader-patch
1116 field_descr = " <!--- H3D View Matrix Patch -->"
1117 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, H3D_VIEW_MATRIX, field_descr))
1118 frag_vars = ["uniform mat4 %s;" % H3D_VIEW_MATRIX]
1120 # annoying!, we need to track if some of the directional lamp
1121 # vars are children of the camera or not, since this adjusts how
1122 # they are patched.
1123 frag_uniform_var_map = {}
1125 h3d_material_route.append(
1126 '<ROUTE fromNode="%s" fromField="glModelViewMatrix" toNode=%s toField="%s" />%s' %
1127 (H3D_TOP_LEVEL, material_id, H3D_VIEW_MATRIX, field_descr))
1128 # ------------------------------------------------------
1130 for uniform in gpu_shader['uniforms']:
1131 if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE:
1132 field_descr = " <!--- Dynamic Sampler 2d Image -->"
1133 fw('%s<field name="%s" type="SFNode" accessType="inputOutput">%s\n' % (ident, uniform['varname'], field_descr))
1134 writeImageTexture(ident + '\t', uniform['image'])
1135 fw('%s</field>\n' % ident)
1137 elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCO:
1138 lamp_obj = uniform['lamp']
1139 frag_uniform_var_map[uniform['varname']] = lamp_obj
1141 if uniform['datatype'] == gpu.GPU_DATA_3F: # should always be true!
1142 lamp_obj_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
1143 lamp_obj_transform_id = quoteattr(unique_name(lamp_obj, lamp_obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
1145 value = '%.6f %.6f %.6f' % (global_matrix * lamp_obj.matrix_world).to_translation()[:]
1146 field_descr = " <!--- Lamp DynCo '%s' -->" % lamp_obj.name
1147 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1149 # ------------------------------------------------------
1150 # shader-patch
1151 field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % lamp_obj.name
1152 fw('%s<field name="%s_transform" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr))
1154 # transform
1155 frag_vars.append("uniform mat4 %s_transform;" % uniform['varname'])
1156 h3d_material_route.append(
1157 '<ROUTE fromNode=%s fromField="accumulatedForward" toNode=%s toField="%s_transform" />%s' %
1158 (suffix_quoted_str(lamp_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr))
1160 h3d_material_route.append(
1161 '<ROUTE fromNode=%s fromField="location" toNode=%s toField="%s" /> %s' %
1162 (lamp_obj_id, material_id, uniform['varname'], field_descr))
1163 # ------------------------------------------------------
1165 else:
1166 assert(0)
1168 elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCOL:
1169 # odd we have both 3, 4 types.
1170 lamp_obj = uniform['lamp']
1171 frag_uniform_var_map[uniform['varname']] = lamp_obj
1173 lamp = lamp_obj.data
1174 value = '%.6f %.6f %.6f' % (lamp.color * lamp.energy)[:]
1175 field_descr = " <!--- Lamp DynColor '%s' -->" % lamp_obj.name
1176 if uniform['datatype'] == gpu.GPU_DATA_3F:
1177 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1178 elif uniform['datatype'] == gpu.GPU_DATA_4F:
1179 fw('%s<field name="%s" type="SFVec4f" accessType="inputOutput" value="%s 1.0" />%s\n' % (ident, uniform['varname'], value, field_descr))
1180 else:
1181 assert(0)
1183 elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNENERGY:
1184 # not used ?
1185 assert(0)
1187 elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNVEC:
1188 lamp_obj = uniform['lamp']
1189 frag_uniform_var_map[uniform['varname']] = lamp_obj
1191 if uniform['datatype'] == gpu.GPU_DATA_3F:
1192 lamp_obj = uniform['lamp']
1193 value = '%.6f %.6f %.6f' % ((global_matrix * lamp_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:]
1194 field_descr = " <!--- Lamp DynDirection '%s' -->" % lamp_obj.name
1195 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1197 # route so we can have the lamp update the view
1198 if h3d_is_object_view(scene, lamp_obj):
1199 lamp_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_"))
1200 h3d_material_route.append(
1201 '<ROUTE fromNode=%s fromField="direction" toNode=%s toField="%s" />%s' %
1202 (lamp_id, material_id, uniform['varname'], field_descr))
1204 else:
1205 assert(0)
1207 elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_VIEWIMAT:
1208 frag_uniform_var_map[uniform['varname']] = None
1209 if uniform['datatype'] == gpu.GPU_DATA_16F:
1210 field_descr = " <!--- Object View Matrix Inverse '%s' -->" % obj.name
1211 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr))
1213 h3d_material_route.append(
1214 '<ROUTE fromNode="%s" fromField="glModelViewMatrixInverse" toNode=%s toField="%s" />%s' %
1215 (H3D_TOP_LEVEL, material_id, uniform['varname'], field_descr))
1216 else:
1217 assert(0)
1219 elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_IMAT:
1220 frag_uniform_var_map[uniform['varname']] = None
1221 if uniform['datatype'] == gpu.GPU_DATA_16F:
1222 value = ' '.join(['%.6f' % f for v in (global_matrix * obj.matrix_world).inverted().transposed() for f in v])
1223 field_descr = " <!--- Object Invertex Matrix '%s' -->" % obj.name
1224 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1225 else:
1226 assert(0)
1228 elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW:
1229 pass # XXX, shadow buffers not supported.
1231 elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DBUFFER:
1232 frag_uniform_var_map[uniform['varname']] = None
1234 if uniform['datatype'] == gpu.GPU_DATA_1I:
1235 if 1:
1236 tex = uniform['texpixels']
1237 value = []
1238 for i in range(0, len(tex) - 1, 4):
1239 col = tex[i:i + 4]
1240 value.append('0x%.2x%.2x%.2x%.2x' % (col[0], col[1], col[2], col[3]))
1242 field_descr = " <!--- Material Buffer -->"
1243 fw('%s<field name="%s" type="SFNode" accessType="inputOutput">%s\n' % (ident, uniform['varname'], field_descr))
1245 ident += '\t'
1247 ident_step = ident + (' ' * (-len(ident) + \
1248 fw('%s<PixelTexture \n' % ident)))
1249 fw(ident_step + 'repeatS="false"\n')
1250 fw(ident_step + 'repeatT="false"\n')
1252 fw(ident_step + 'image="%s 1 4 %s"\n' % (len(value), " ".join(value)))
1254 fw(ident_step + '/>\n')
1256 ident = ident[:-1]
1258 fw('%s</field>\n' % ident)
1260 #for i in range(0, 10, 4)
1261 #value = ' '.join(['%d' % f for f in uniform['texpixels']])
1262 # value = ' '.join(['%.6f' % (f / 256) for f in uniform['texpixels']])
1264 #fw('%s<field name="%s" type="SFInt32" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1265 #print('test', len(uniform['texpixels']))
1266 else:
1267 assert(0)
1268 else:
1269 print("SKIPPING", uniform['type'])
1271 file_frag = open(os.path.join(base_dst, shader_url_frag), 'w', encoding='utf-8')
1272 file_frag.write(gpu_shader['fragment'])
1273 file_frag.close()
1274 # patch it
1275 h3d_shader_glsl_frag_patch(os.path.join(base_dst, shader_url_frag),
1276 scene,
1277 frag_vars,
1278 frag_uniform_var_map,
1281 file_vert = open(os.path.join(base_dst, shader_url_vert), 'w', encoding='utf-8')
1282 file_vert.write(gpu_shader['vertex'])
1283 file_vert.close()
1285 fw('%s<ShaderPart type="FRAGMENT" url=%s />\n' % (ident, quoteattr(shader_url_frag)))
1286 fw('%s<ShaderPart type="VERTEX" url=%s />\n' % (ident, quoteattr(shader_url_vert)))
1287 ident = ident[:-1]
1289 fw('%s</ComposedShader>\n' % ident)
1291 def writeImageTexture(ident, image):
1292 image_id = quoteattr(unique_name(image, IM_ + image.name, uuid_cache_image, clean_func=clean_def, sep="_"))
1294 if image.tag:
1295 fw('%s<ImageTexture USE=%s />\n' % (ident, image_id))
1296 else:
1297 image.tag = True
1299 ident_step = ident + (' ' * (-len(ident) + \
1300 fw('%s<ImageTexture ' % ident)))
1301 fw('DEF=%s\n' % image_id)
1303 # collect image paths, can load multiple
1304 # [relative, name-only, absolute]
1305 filepath = image.filepath
1306 filepath_full = bpy.path.abspath(filepath, library=image.library)
1307 filepath_ref = bpy_extras.io_utils.path_reference(filepath_full, base_src, base_dst, path_mode, "textures", copy_set, image.library)
1308 filepath_base = os.path.basename(filepath_full)
1310 images = [
1311 filepath_ref,
1312 filepath_base,
1314 if path_mode != 'RELATIVE':
1315 images.append(filepath_full)
1317 images = [f.replace('\\', '/') for f in images]
1318 images = [f for i, f in enumerate(images) if f not in images[:i]]
1320 fw(ident_step + "url='%s'\n" % ' '.join(['"%s"' % escape(f) for f in images]))
1321 fw(ident_step + '/>\n')
1323 def writeBackground(ident, world):
1325 if world is None:
1326 return
1328 # note, not re-used
1329 world_id = quoteattr(unique_name(world, WO_ + world.name, uuid_cache_world, clean_func=clean_def, sep="_"))
1331 blending = world.use_sky_blend, world.use_sky_paper, world.use_sky_real
1333 grd_triple = clamp_color(world.horizon_color)
1334 sky_triple = clamp_color(world.zenith_color)
1335 mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3))
1337 ident_step = ident + (' ' * (-len(ident) + \
1338 fw('%s<Background ' % ident)))
1339 fw('DEF=%s\n' % world_id)
1340 # No Skytype - just Hor color
1341 if blending == (False, False, False):
1342 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % grd_triple)
1343 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % grd_triple)
1344 # Blend Gradient
1345 elif blending == (True, False, False):
1346 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (grd_triple + mix_triple))
1347 fw(ident_step + 'groundAngle="1.57"\n')
1348 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + mix_triple))
1349 fw(ident_step + 'skyAngle="1.57"\n')
1350 # Blend+Real Gradient Inverse
1351 elif blending == (True, False, True):
1352 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1353 fw(ident_step + 'groundAngle="1.57"\n')
1354 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple + sky_triple))
1355 fw(ident_step + 'skyAngle="1.57, 3.14159"\n')
1356 # Paper - just Zen Color
1357 elif blending == (False, False, True):
1358 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % sky_triple)
1359 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % sky_triple)
1360 # Blend+Real+Paper - komplex gradient
1361 elif blending == (True, True, True):
1362 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1363 fw(ident_step + 'groundAngle="1.57"\n')
1364 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1365 fw(ident_step + 'skyAngle="1.57"\n')
1366 # Any Other two colors
1367 else:
1368 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % grd_triple)
1369 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % sky_triple)
1371 for tex in bpy.data.textures:
1372 if tex.type == 'IMAGE' and tex.image:
1373 namemat = tex.name
1374 pic = tex.image
1375 basename = quoteattr(bpy.path.basename(pic.filepath))
1377 if namemat == 'back':
1378 fw(ident_step + 'backUrl=%s\n' % basename)
1379 elif namemat == 'bottom':
1380 fw(ident_step + 'bottomUrl=%s\n' % basename)
1381 elif namemat == 'front':
1382 fw(ident_step + 'frontUrl=%s\n' % basename)
1383 elif namemat == 'left':
1384 fw(ident_step + 'leftUrl=%s\n' % basename)
1385 elif namemat == 'right':
1386 fw(ident_step + 'rightUrl=%s\n' % basename)
1387 elif namemat == 'top':
1388 fw(ident_step + 'topUrl=%s\n' % basename)
1390 fw(ident_step + '/>\n')
1392 # -------------------------------------------------------------------------
1393 # Export Object Hierarchy (recursively called)
1394 # -------------------------------------------------------------------------
1395 def export_object(ident, obj_main_parent, obj_main, obj_children):
1396 matrix_fallback = mathutils.Matrix()
1397 world = scene.world
1398 free, derived = create_derived_objects(scene, obj_main)
1400 if use_hierarchy:
1401 obj_main_matrix_world = obj_main.matrix_world
1402 if obj_main_parent:
1403 obj_main_matrix = obj_main_parent.matrix_world.inverted(matrix_fallback) * obj_main_matrix_world
1404 else:
1405 obj_main_matrix = obj_main_matrix_world
1406 obj_main_matrix_world_invert = obj_main_matrix_world.inverted(matrix_fallback)
1408 obj_main_id = quoteattr(unique_name(obj_main, obj_main.name, uuid_cache_object, clean_func=clean_def, sep="_"))
1410 ident = writeTransform_begin(ident, obj_main_matrix if obj_main_parent else global_matrix * obj_main_matrix, suffix_quoted_str(obj_main_id, _TRANSFORM))
1412 # Set here just incase we dont enter the loop below.
1413 is_dummy_tx = False
1415 for obj, obj_matrix in (() if derived is None else derived):
1416 obj_type = obj.type
1418 if use_hierarchy:
1419 # make transform node relative
1420 obj_matrix = obj_main_matrix_world_invert * obj_matrix
1421 else:
1422 obj_matrix = global_matrix * obj_matrix
1424 # H3D - use for writing a dummy transform parent
1425 is_dummy_tx = False
1427 if obj_type == 'CAMERA':
1428 writeViewpoint(ident, obj, obj_matrix, scene)
1430 if use_h3d and scene.camera == obj:
1431 view_id = uuid_cache_view[obj]
1432 fw('%s<Transform DEF="%s">\n' % (ident, H3D_CAMERA_FOLLOW))
1433 h3d_material_route.extend([
1434 '<ROUTE fromNode="%s" fromField="totalPosition" toNode="%s" toField="translation" />' % (view_id, H3D_CAMERA_FOLLOW),
1435 '<ROUTE fromNode="%s" fromField="totalOrientation" toNode="%s" toField="rotation" />' % (view_id, H3D_CAMERA_FOLLOW),
1437 is_dummy_tx = True
1438 ident += '\t'
1440 elif obj_type in {'MESH', 'CURVE', 'SURFACE', 'FONT'}:
1441 if (obj_type != 'MESH') or (use_mesh_modifiers and obj.is_modified(scene, 'PREVIEW')):
1442 try:
1443 me = obj.to_mesh(scene, use_mesh_modifiers, 'PREVIEW')
1444 except:
1445 me = None
1446 do_remove = True
1447 else:
1448 me = obj.data
1449 do_remove = False
1451 if me is not None:
1452 # ensure unique name, we could also do this by
1453 # postponing mesh removal, but clearing data - TODO
1454 if do_remove:
1455 me.name = obj.name.rstrip("1234567890").rstrip(".")
1456 me_name_new = me_name_org = me.name
1457 count = 0
1458 while me_name_new in mesh_name_set:
1459 me.name = "%.17s.%03d" % (me_name_org, count)
1460 me_name_new = me.name
1461 count += 1
1462 mesh_name_set.add(me_name_new)
1463 del me_name_new, me_name_org, count
1464 # done
1466 writeIndexedFaceSet(ident, obj, me, obj_matrix, world)
1468 # free mesh created with create_mesh()
1469 if do_remove:
1470 bpy.data.meshes.remove(me)
1472 elif obj_type == 'LAMP':
1473 data = obj.data
1474 datatype = data.type
1475 if datatype == 'POINT':
1476 writePointLight(ident, obj, obj_matrix, data, world)
1477 elif datatype == 'SPOT':
1478 writeSpotLight(ident, obj, obj_matrix, data, world)
1479 elif datatype == 'SUN':
1480 writeDirectionalLight(ident, obj, obj_matrix, data, world)
1481 else:
1482 writeDirectionalLight(ident, obj, obj_matrix, data, world)
1483 else:
1484 #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType)
1485 pass
1487 if free:
1488 free_derived_objects(obj_main)
1490 # ---------------------------------------------------------------------
1491 # write out children recursively
1492 # ---------------------------------------------------------------------
1493 for obj_child, obj_child_children in obj_children:
1494 export_object(ident, obj_main, obj_child, obj_child_children)
1496 if is_dummy_tx:
1497 ident = ident[:-1]
1498 fw('%s</Transform>\n' % ident)
1499 is_dummy_tx = False
1501 if use_hierarchy:
1502 ident = writeTransform_end(ident)
1504 # -------------------------------------------------------------------------
1505 # Main Export Function
1506 # -------------------------------------------------------------------------
1507 def export_main():
1508 world = scene.world
1510 # tag un-exported IDs
1511 bpy.data.meshes.tag(False)
1512 bpy.data.materials.tag(False)
1513 bpy.data.images.tag(False)
1515 if use_selection:
1516 objects = [obj for obj in scene.objects if obj.is_visible(scene) and obj.select]
1517 else:
1518 objects = [obj for obj in scene.objects if obj.is_visible(scene)]
1520 print('Info: starting X3D export to %r...' % file.name)
1521 ident = ''
1522 ident = writeHeader(ident)
1524 writeNavigationInfo(ident, scene, any(obj.type == 'LAMP' for obj in objects))
1525 writeBackground(ident, world)
1526 writeFog(ident, world)
1528 ident = '\t\t'
1530 if use_hierarchy:
1531 objects_hierarchy = build_hierarchy(objects)
1532 else:
1533 objects_hierarchy = ((obj, []) for obj in objects)
1535 for obj_main, obj_main_children in objects_hierarchy:
1536 export_object(ident, None, obj_main, obj_main_children)
1538 ident = writeFooter(ident)
1540 export_main()
1542 # -------------------------------------------------------------------------
1543 # global cleanup
1544 # -------------------------------------------------------------------------
1545 file.close()
1547 if use_h3d:
1548 bpy.data.materials.remove(gpu_shader_dummy_mat)
1550 # copy all collected files.
1551 # print(copy_set)
1552 bpy_extras.io_utils.path_reference_copy(copy_set)
1554 print('Info: finished X3D export to %r' % file.name)
1557 ##########################################################
1558 # Callbacks, needed before Main
1559 ##########################################################
1562 def gzip_open_utf8(filepath, mode):
1563 """Workaround for py3k only allowing binary gzip writing"""
1565 import gzip
1567 # need to investigate encoding
1568 file = gzip.open(filepath, mode)
1569 write_real = file.write
1571 def write_wrap(data):
1572 return write_real(data.encode("utf-8"))
1574 file.write = write_wrap
1576 return file
1579 def save(context,
1580 filepath,
1582 use_selection=True,
1583 use_mesh_modifiers=False,
1584 use_triangulate=False,
1585 use_normals=False,
1586 use_compress=False,
1587 use_hierarchy=True,
1588 use_h3d=False,
1589 global_matrix=None,
1590 path_mode='AUTO',
1591 name_decorations=True
1594 bpy.path.ensure_ext(filepath, '.x3dz' if use_compress else '.x3d')
1596 if bpy.ops.object.mode_set.poll():
1597 bpy.ops.object.mode_set(mode='OBJECT')
1599 if use_compress:
1600 file = gzip_open_utf8(filepath, 'w')
1601 else:
1602 file = open(filepath, 'w', encoding='utf-8')
1604 if global_matrix is None:
1605 global_matrix = mathutils.Matrix()
1607 export(file,
1608 global_matrix,
1609 context.scene,
1610 use_mesh_modifiers=use_mesh_modifiers,
1611 use_selection=use_selection,
1612 use_triangulate=use_triangulate,
1613 use_normals=use_normals,
1614 use_hierarchy=use_hierarchy,
1615 use_h3d=use_h3d,
1616 path_mode=path_mode,
1617 name_decorations=name_decorations,
1620 return {'FINISHED'}