Merge branch 'blender-v3.3-release'
[blender-addons.git] / io_scene_x3d / export_x3d.py
blobe6cbd80c52498421c7f90711822b404ccaf6e7fd
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Contributors: bart:neeneenee*de, http://www.neeneenee.de/vrml, Campbell Barton
5 """
6 This script exports to X3D format.
8 Usage:
9 Run this script from "File->Export" menu. A pop-up will ask whether you
10 want to export only selected or all relevant objects.
12 Known issues:
13 Doesn't handle multiple materials (don't use material indices);<br>
14 Doesn't handle multiple UV textures on a single mesh (create a mesh for each texture);<br>
15 Can't get the texture array associated with material * not the UV ones;
16 """
18 import math
19 import os
21 import bpy
22 import mathutils
24 from bpy_extras.io_utils import create_derived_objects
27 # h3d defines
28 H3D_TOP_LEVEL = 'TOP_LEVEL_TI'
29 H3D_CAMERA_FOLLOW = 'CAMERA_FOLLOW_TRANSFORM'
30 H3D_VIEW_MATRIX = 'view_matrix'
33 def clamp_color(col):
34 return tuple([max(min(c, 1.0), 0.0) for c in col])
37 def matrix_direction_neg_z(matrix):
38 return (matrix.to_3x3() @ mathutils.Vector((0.0, 0.0, -1.0))).normalized()[:]
41 def prefix_quoted_str(value, prefix):
42 return value[0] + prefix + value[1:]
45 def suffix_quoted_str(value, suffix):
46 return value[:-1] + suffix + value[-1:]
49 def bool_as_str(value):
50 return ('false', 'true')[bool(value)]
53 def clean_def(txt):
54 # see report [#28256]
55 if not txt:
56 txt = "None"
57 # no digit start
58 if txt[0] in "1234567890+-":
59 txt = "_" + txt
60 return txt.translate({
61 # control characters 0x0-0x1f
62 # 0x00: "_",
63 0x01: "_",
64 0x02: "_",
65 0x03: "_",
66 0x04: "_",
67 0x05: "_",
68 0x06: "_",
69 0x07: "_",
70 0x08: "_",
71 0x09: "_",
72 0x0a: "_",
73 0x0b: "_",
74 0x0c: "_",
75 0x0d: "_",
76 0x0e: "_",
77 0x0f: "_",
78 0x10: "_",
79 0x11: "_",
80 0x12: "_",
81 0x13: "_",
82 0x14: "_",
83 0x15: "_",
84 0x16: "_",
85 0x17: "_",
86 0x18: "_",
87 0x19: "_",
88 0x1a: "_",
89 0x1b: "_",
90 0x1c: "_",
91 0x1d: "_",
92 0x1e: "_",
93 0x1f: "_",
95 0x7f: "_", # 127
97 0x20: "_", # space
98 0x22: "_", # "
99 0x27: "_", # '
100 0x23: "_", # #
101 0x2c: "_", # ,
102 0x2e: "_", # .
103 0x5b: "_", # [
104 0x5d: "_", # ]
105 0x5c: "_", # \
106 0x7b: "_", # {
107 0x7d: "_", # }
111 def build_hierarchy(objects):
112 """ returns parent child relationships, skipping
114 objects_set = set(objects)
115 par_lookup = {}
117 def test_parent(parent):
118 while (parent is not None) and (parent not in objects_set):
119 parent = parent.parent
120 return parent
122 for obj in objects:
123 par_lookup.setdefault(test_parent(obj.parent), []).append((obj, []))
125 for parent, children in par_lookup.items():
126 for obj, subchildren in children:
127 subchildren[:] = par_lookup.get(obj, [])
129 return par_lookup.get(None, [])
132 # -----------------------------------------------------------------------------
133 # H3D Functions
134 # -----------------------------------------------------------------------------
135 def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_map):
136 h3d_file = open(filepath, 'r', encoding='utf-8')
137 lines = []
139 last_transform = None
141 for l in h3d_file:
142 if l.startswith("void main(void)"):
143 lines.append("\n")
144 lines.append("// h3d custom vars begin\n")
145 for v in global_vars:
146 lines.append("%s\n" % v)
147 lines.append("// h3d custom vars end\n")
148 lines.append("\n")
149 elif l.lstrip().startswith("light_visibility_other("):
150 w = l.split(', ')
151 last_transform = w[1] + "_transform" # XXX - HACK!!!
152 w[1] = '(view_matrix * %s_transform * vec4(%s.x, %s.y, %s.z, 1.0)).xyz' % (w[1], w[1], w[1], w[1])
153 l = ", ".join(w)
154 elif l.lstrip().startswith("light_visibility_sun_hemi("):
155 w = l.split(', ')
156 w[0] = w[0][len("light_visibility_sun_hemi(") + 1:]
158 if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
159 w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
160 else:
161 w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * -%s)' %
162 (last_transform, last_transform, last_transform, w[0]))
164 l = "\tlight_visibility_sun_hemi(" + ", ".join(w)
165 elif l.lstrip().startswith("light_visibility_spot_circle("):
166 w = l.split(', ')
167 w[0] = w[0][len("light_visibility_spot_circle(") + 1:]
169 if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]):
170 w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0]
171 else:
172 w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * %s)' %
173 (last_transform, last_transform, last_transform, w[0]))
175 l = "\tlight_visibility_spot_circle(" + ", ".join(w)
177 lines.append(l)
179 h3d_file.close()
181 h3d_file = open(filepath, 'w', encoding='utf-8')
182 h3d_file.writelines(lines)
183 h3d_file.close()
186 def h3d_is_object_view(scene, obj):
187 camera = scene.camera
188 parent = obj.parent
189 while parent:
190 if parent == camera:
191 return True
192 parent = parent.parent
193 return False
196 # -----------------------------------------------------------------------------
197 # Functions for writing output file
198 # -----------------------------------------------------------------------------
200 def export(file,
201 global_matrix,
202 depsgraph,
203 scene,
204 view_layer,
205 use_mesh_modifiers=False,
206 use_selection=True,
207 use_triangulate=False,
208 use_normals=False,
209 use_hierarchy=True,
210 use_h3d=False,
211 path_mode='AUTO',
212 name_decorations=True,
215 # -------------------------------------------------------------------------
216 # Global Setup
217 # -------------------------------------------------------------------------
218 import bpy_extras
219 from bpy_extras.io_utils import unique_name
220 from xml.sax.saxutils import quoteattr, escape
222 if name_decorations:
223 # If names are decorated, the uuid map can be split up
224 # by type for efficiency of collision testing
225 # since objects of different types will always have
226 # different decorated names.
227 uuid_cache_object = {} # object
228 uuid_cache_light = {} # 'LA_' + object.name
229 uuid_cache_view = {} # object, different namespace
230 uuid_cache_mesh = {} # mesh
231 uuid_cache_material = {} # material
232 uuid_cache_image = {} # image
233 uuid_cache_world = {} # world
234 CA_ = 'CA_'
235 OB_ = 'OB_'
236 ME_ = 'ME_'
237 IM_ = 'IM_'
238 WO_ = 'WO_'
239 MA_ = 'MA_'
240 LA_ = 'LA_'
241 group_ = 'group_'
242 else:
243 # If names are not decorated, it may be possible for two objects to
244 # have the same name, so there has to be a unified dictionary to
245 # prevent uuid collisions.
246 uuid_cache = {}
247 uuid_cache_object = uuid_cache # object
248 uuid_cache_light = uuid_cache # 'LA_' + object.name
249 uuid_cache_view = uuid_cache # object, different namespace
250 uuid_cache_mesh = uuid_cache # mesh
251 uuid_cache_material = uuid_cache # material
252 uuid_cache_image = uuid_cache # image
253 uuid_cache_world = uuid_cache # world
254 del uuid_cache
255 CA_ = ''
256 OB_ = ''
257 ME_ = ''
258 IM_ = ''
259 WO_ = ''
260 MA_ = ''
261 LA_ = ''
262 group_ = ''
264 _TRANSFORM = '_TRANSFORM'
266 # store files to copy
267 copy_set = set()
269 # store names of newly cerated meshes, so we dont overlap
270 mesh_name_set = set()
272 fw = file.write
273 base_src = os.path.dirname(bpy.data.filepath)
274 base_dst = os.path.dirname(file.name)
275 filename_strip = os.path.splitext(os.path.basename(file.name))[0]
276 gpu_shader_cache = {}
278 if use_h3d:
279 import gpu
280 gpu_shader_dummy_mat = bpy.data.materials.new('X3D_DYMMY_MAT')
281 gpu_shader_cache[None] = gpu.export_shader(scene, gpu_shader_dummy_mat)
282 h3d_material_route = []
284 # -------------------------------------------------------------------------
285 # File Writing Functions
286 # -------------------------------------------------------------------------
288 def writeHeader(ident):
289 filepath_quoted = quoteattr(os.path.basename(file.name))
290 blender_ver_quoted = quoteattr('Blender %s' % bpy.app.version_string)
292 fw('%s<?xml version="1.0" encoding="UTF-8"?>\n' % ident)
293 if use_h3d:
294 fw('%s<X3D profile="H3DAPI" version="1.4">\n' % ident)
295 else:
296 fw('%s<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "http://www.web3d.org/specifications/x3d-3.0.dtd">\n' % ident)
297 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)
299 ident += '\t'
300 fw('%s<head>\n' % ident)
301 ident += '\t'
302 fw('%s<meta name="filename" content=%s />\n' % (ident, filepath_quoted))
303 fw('%s<meta name="generator" content=%s />\n' % (ident, blender_ver_quoted))
304 # this info was never updated, so blender version should be enough
305 # fw('%s<meta name="translator" content="X3D exporter v1.55 (2006/01/17)" />\n' % ident)
306 ident = ident[:-1]
307 fw('%s</head>\n' % ident)
308 fw('%s<Scene>\n' % ident)
309 ident += '\t'
311 if use_h3d:
312 # outputs the view matrix in glModelViewMatrix field
313 fw('%s<TransformInfo DEF="%s" outputGLMatrices="true" />\n' % (ident, H3D_TOP_LEVEL))
315 return ident
317 def writeFooter(ident):
319 if use_h3d:
320 # global
321 for route in h3d_material_route:
322 fw('%s%s\n' % (ident, route))
324 ident = ident[:-1]
325 fw('%s</Scene>\n' % ident)
326 ident = ident[:-1]
327 fw('%s</X3D>' % ident)
328 return ident
330 def writeViewpoint(ident, obj, matrix, scene):
331 view_id = quoteattr(unique_name(obj, CA_ + obj.name, uuid_cache_view, clean_func=clean_def, sep="_"))
333 loc, rot, scale = matrix.decompose()
334 rot = rot.to_axis_angle()
335 rot = (*rot[0].normalized(), rot[1])
337 ident_step = ident + (' ' * (-len(ident) + \
338 fw('%s<Viewpoint ' % ident)))
339 fw('DEF=%s\n' % view_id)
340 fw(ident_step + 'centerOfRotation="0 0 0"\n')
341 fw(ident_step + 'position="%3.2f %3.2f %3.2f"\n' % loc[:])
342 fw(ident_step + 'orientation="%3.2f %3.2f %3.2f %3.2f"\n' % rot)
343 fw(ident_step + 'fieldOfView="%.3f"\n' % obj.data.angle)
344 fw(ident_step + '/>\n')
346 def writeFog(ident, world):
347 if world:
348 mtype = world.mist_settings.falloff
349 mparam = world.mist_settings
350 else:
351 return
353 if mparam.use_mist:
354 ident_step = ident + (' ' * (-len(ident) + \
355 fw('%s<Fog ' % ident)))
356 fw('fogType="%s"\n' % ('LINEAR' if (mtype == 'LINEAR') else 'EXPONENTIAL'))
357 fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clamp_color(world.horizon_color))
358 fw(ident_step + 'visibilityRange="%.3f"\n' % mparam.depth)
359 fw(ident_step + '/>\n')
360 else:
361 return
363 def writeNavigationInfo(ident, scene, has_light):
364 ident_step = ident + (' ' * (-len(ident) + \
365 fw('%s<NavigationInfo ' % ident)))
366 fw('headlight="%s"\n' % bool_as_str(not has_light))
367 fw(ident_step + 'visibilityLimit="0.0"\n')
368 fw(ident_step + 'type=\'"EXAMINE", "ANY"\'\n')
369 fw(ident_step + 'avatarSize="0.25, 1.75, 0.75"\n')
370 fw(ident_step + '/>\n')
372 def writeTransform_begin(ident, matrix, def_id):
373 ident_step = ident + (' ' * (-len(ident) + \
374 fw('%s<Transform ' % ident)))
375 if def_id is not None:
376 fw('DEF=%s\n' % def_id)
377 else:
378 fw('\n')
380 loc, rot, sca = matrix.decompose()
381 rot = rot.to_axis_angle()
382 rot = (*rot[0], rot[1])
384 fw(ident_step + 'translation="%.6f %.6f %.6f"\n' % loc[:])
385 # fw(ident_step + 'center="%.6f %.6f %.6f"\n' % (0, 0, 0))
386 fw(ident_step + 'scale="%.6f %.6f %.6f"\n' % sca[:])
387 fw(ident_step + 'rotation="%.6f %.6f %.6f %.6f"\n' % rot)
388 fw(ident_step + '>\n')
389 ident += '\t'
390 return ident
392 def writeTransform_end(ident):
393 ident = ident[:-1]
394 fw('%s</Transform>\n' % ident)
395 return ident
397 def writeSpotLight(ident, obj, matrix, light, world):
398 # note, light_id is not re-used
399 light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
401 if world and 0:
402 ambi = world.ambient_color
403 amb_intensity = ((ambi[0] + ambi[1] + ambi[2]) / 3.0) / 2.5
404 del ambi
405 else:
406 amb_intensity = 0.0
408 # compute cutoff and beamwidth
409 intensity = min(lamp.energy / 1.75, 1.0)
410 beamWidth = lamp.spot_size * 0.37
411 # beamWidth=((lamp.spotSize*math.pi)/180.0)*.37
412 cutOffAngle = beamWidth * 1.3
414 orientation = matrix_direction_neg_z(matrix)
416 location = matrix.to_translation()[:]
418 radius = lamp.distance * math.cos(beamWidth)
419 # radius = lamp.dist*math.cos(beamWidth)
420 ident_step = ident + (' ' * (-len(ident) + \
421 fw('%s<SpotLight ' % ident)))
422 fw('DEF=%s\n' % light_id)
423 fw(ident_step + 'radius="%.4f"\n' % radius)
424 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
425 fw(ident_step + 'intensity="%.4f"\n' % intensity)
426 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(light.color))
427 fw(ident_step + 'beamWidth="%.4f"\n' % beamWidth)
428 fw(ident_step + 'cutOffAngle="%.4f"\n' % cutOffAngle)
429 fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
430 fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location)
431 fw(ident_step + '/>\n')
433 def writeDirectionalLight(ident, obj, matrix, light, world):
434 # note, light_id is not re-used
435 light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
437 if world and 0:
438 ambi = world.ambient_color
439 # ambi = world.amb
440 amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5
441 else:
442 ambi = 0
443 amb_intensity = 0.0
445 intensity = min(light.energy / 1.75, 1.0)
447 orientation = matrix_direction_neg_z(matrix)
449 ident_step = ident + (' ' * (-len(ident) + \
450 fw('%s<DirectionalLight ' % ident)))
451 fw('DEF=%s\n' % light_id)
452 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
453 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(light.color))
454 fw(ident_step + 'intensity="%.4f"\n' % intensity)
455 fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation)
456 fw(ident_step + '/>\n')
458 def writePointLight(ident, obj, matrix, light, world):
459 # note, light_id is not re-used
460 light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
462 if world and 0:
463 ambi = world.ambient_color
464 # ambi = world.amb
465 amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5
466 else:
467 ambi = 0.0
468 amb_intensity = 0.0
470 intensity = min(light.energy / 1.75, 1.0)
471 location = matrix.to_translation()[:]
473 ident_step = ident + (' ' * (-len(ident) + \
474 fw('%s<PointLight ' % ident)))
475 fw('DEF=%s\n' % light_id)
476 fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity)
477 fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(light.color))
479 fw(ident_step + 'intensity="%.4f"\n' % intensity)
480 fw(ident_step + 'radius="%.4f" \n' % light.distance)
481 fw(ident_step + 'location="%.4f %.4f %.4f"\n' % location)
482 fw(ident_step + '/>\n')
484 def writeIndexedFaceSet(ident, obj, mesh, mesh_name, matrix, world):
485 obj_id = quoteattr(unique_name(obj, OB_ + obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
486 mesh_id = quoteattr(unique_name(mesh, ME_ + mesh_name, uuid_cache_mesh, clean_func=clean_def, sep="_"))
487 mesh_id_group = prefix_quoted_str(mesh_id, group_)
488 mesh_id_coords = prefix_quoted_str(mesh_id, 'coords_')
489 mesh_id_normals = prefix_quoted_str(mesh_id, 'normals_')
491 # Be sure tessellated loop triangles are available!
492 if use_triangulate:
493 if not mesh.loop_triangles and mesh.polygons:
494 mesh.calc_loop_triangles()
496 use_collnode = bool([mod for mod in obj.modifiers
497 if mod.type == 'COLLISION'
498 if mod.show_viewport])
500 if use_collnode:
501 fw('%s<Collision enabled="true">\n' % ident)
502 ident += '\t'
504 # use _ifs_TRANSFORM suffix so we dont collide with transform node when
505 # hierarchys are used.
506 ident = writeTransform_begin(ident, matrix, suffix_quoted_str(obj_id, "_ifs" + _TRANSFORM))
508 if mesh.tag:
509 fw('%s<Group USE=%s />\n' % (ident, mesh_id_group))
510 else:
511 mesh.tag = True
513 fw('%s<Group DEF=%s>\n' % (ident, mesh_id_group))
514 ident += '\t'
516 is_uv = bool(mesh.uv_layers.active)
517 # is_col, defined for each material
519 is_coords_written = False
521 mesh_materials = mesh.materials[:]
522 if not mesh_materials:
523 mesh_materials = [None]
525 mesh_material_tex = [None] * len(mesh_materials)
526 mesh_material_mtex = [None] * len(mesh_materials)
527 mesh_material_images = [None] * len(mesh_materials)
529 for i, material in enumerate(mesh_materials):
530 if 0 and material:
531 for mtex in material.texture_slots:
532 if mtex:
533 tex = mtex.texture
534 if tex and tex.type == 'IMAGE':
535 image = tex.image
536 if image:
537 mesh_material_tex[i] = tex
538 mesh_material_mtex[i] = mtex
539 mesh_material_images[i] = image
540 break
542 # fast access!
543 mesh_vertices = mesh.vertices[:]
544 mesh_loops = mesh.loops[:]
545 mesh_polygons = mesh.polygons[:]
546 mesh_polygons_materials = [p.material_index for p in mesh_polygons]
547 mesh_polygons_vertices = [p.vertices[:] for p in mesh_polygons]
549 if len(set(mesh_material_images)) > 0: # make sure there is at least one image
550 mesh_polygons_image = [mesh_material_images[material_index] for material_index in mesh_polygons_materials]
551 else:
552 mesh_polygons_image = [None] * len(mesh_polygons)
553 mesh_polygons_image_unique = set(mesh_polygons_image)
555 # group faces
556 polygons_groups = {}
557 for material_index in range(len(mesh_materials)):
558 for image in mesh_polygons_image_unique:
559 polygons_groups[material_index, image] = []
560 del mesh_polygons_image_unique
562 for i, (material_index, image) in enumerate(zip(mesh_polygons_materials, mesh_polygons_image)):
563 polygons_groups[material_index, image].append(i)
565 # Py dict are sorted now, so we can use directly polygons_groups.items()
566 # and still get consistent reproducible outputs.
568 is_col = mesh.vertex_colors.active
569 mesh_loops_col = mesh.vertex_colors.active.data if is_col else None
571 # Check if vertex colors can be exported in per-vertex mode.
572 # Do we have just one color per vertex in every face that uses the vertex?
573 if is_col:
574 def calc_vertex_color():
575 vert_color = [None] * len(mesh.vertices)
577 for i, p in enumerate(mesh_polygons):
578 for lidx in p.loop_indices:
579 l = mesh_loops[lidx]
580 if vert_color[l.vertex_index] is None:
581 vert_color[l.vertex_index] = mesh_loops_col[lidx].color[:]
582 elif vert_color[l.vertex_index] != mesh_loops_col[lidx].color[:]:
583 return False, ()
585 return True, vert_color
587 is_col_per_vertex, vert_color = calc_vertex_color()
588 del calc_vertex_color
590 # If using looptris, we need a mapping poly_index -> loop_tris_indices...
591 if use_triangulate:
592 polygons_to_loop_triangles_indices = [[] for i in range(len(mesh_polygons))]
593 for ltri in mesh.loop_triangles:
594 polygons_to_loop_triangles_indices[ltri.polygon_index].append(ltri)
596 for (material_index, image), polygons_group in polygons_groups.items():
597 if polygons_group:
598 material = mesh_materials[material_index]
600 fw('%s<Shape>\n' % ident)
601 ident += '\t'
603 is_smooth = False
605 # kludge but as good as it gets!
606 for i in polygons_group:
607 if mesh_polygons[i].use_smooth:
608 is_smooth = True
609 break
611 # UV's and VCols split verts off which effects smoothing
612 # force writing normals in this case.
613 # Also, creaseAngle is not supported for IndexedTriangleSet,
614 # so write normals when is_smooth (otherwise
615 # IndexedTriangleSet can have only all smooth/all flat shading).
616 is_force_normals = use_triangulate and (is_smooth or is_uv or is_col)
618 if use_h3d:
619 gpu_shader = gpu_shader_cache.get(material) # material can be 'None', uses dummy cache
620 if gpu_shader is None:
621 gpu_shader = gpu_shader_cache[material] = gpu.export_shader(scene, material)
623 if 1: # XXX DEBUG
624 gpu_shader_tmp = gpu.export_shader(scene, material)
625 import pprint
626 print('\nWRITING MATERIAL:', material.name)
627 del gpu_shader_tmp['fragment']
628 del gpu_shader_tmp['vertex']
629 pprint.pprint(gpu_shader_tmp, width=120)
630 #pprint.pprint(val['vertex'])
631 del gpu_shader_tmp
633 fw('%s<Appearance>\n' % ident)
634 ident += '\t'
636 if image and not use_h3d:
637 writeImageTexture(ident, image)
639 # transform by mtex
640 loc = mesh_material_mtex[material_index].offset[:2]
642 # mtex_scale * tex_repeat
643 sca_x, sca_y = mesh_material_mtex[material_index].scale[:2]
645 sca_x *= mesh_material_tex[material_index].repeat_x
646 sca_y *= mesh_material_tex[material_index].repeat_y
648 # flip x/y is a sampling feature, convert to transform
649 if mesh_material_tex[material_index].use_flip_axis:
650 rot = math.pi / -2.0
651 sca_x, sca_y = sca_y, -sca_x
652 else:
653 rot = 0.0
655 ident_step = ident + (' ' * (-len(ident) + \
656 fw('%s<TextureTransform ' % ident)))
657 fw('\n')
658 # fw('center="%.6f %.6f" ' % (0.0, 0.0))
659 fw(ident_step + 'translation="%.6f %.6f"\n' % loc)
660 fw(ident_step + 'scale="%.6f %.6f"\n' % (sca_x, sca_y))
661 fw(ident_step + 'rotation="%.6f"\n' % rot)
662 fw(ident_step + '/>\n')
664 if use_h3d:
665 mat_tmp = material if material else gpu_shader_dummy_mat
666 writeMaterialH3D(ident, mat_tmp, world,
667 obj, gpu_shader)
668 del mat_tmp
669 else:
670 if material:
671 writeMaterial(ident, material, world)
673 ident = ident[:-1]
674 fw('%s</Appearance>\n' % ident)
676 mesh_loops_uv = mesh.uv_layers.active.data if is_uv else None
678 #-- IndexedFaceSet or IndexedLineSet
679 if use_triangulate:
680 ident_step = ident + (' ' * (-len(ident) + \
681 fw('%s<IndexedTriangleSet ' % ident)))
683 # --- Write IndexedTriangleSet Attributes (same as IndexedFaceSet)
684 fw('solid="%s"\n' % bool_as_str(material and material.use_backface_culling))
686 if use_normals or is_force_normals:
687 fw(ident_step + 'normalPerVertex="true"\n')
688 else:
689 # Tell X3D browser to generate flat (per-face) normals
690 fw(ident_step + 'normalPerVertex="false"\n')
692 slot_uv = None
693 slot_col = None
694 def _tuple_from_rounded_iter(it):
695 return tuple(round(v, 5) for v in it)
697 if is_uv and is_col:
698 slot_uv = 0
699 slot_col = 1
701 def vertex_key(lidx):
702 return (
703 _tuple_from_rounded_iter(mesh_loops_uv[lidx].uv),
704 _tuple_from_rounded_iter(mesh_loops_col[lidx].color),
706 elif is_uv:
707 slot_uv = 0
709 def vertex_key(lidx):
710 return (
711 _tuple_from_rounded_iter(mesh_loops_uv[lidx].uv),
713 elif is_col:
714 slot_col = 0
716 def vertex_key(lidx):
717 return (
718 _tuple_from_rounded_iter(mesh_loops_col[lidx].color),
720 else:
721 # ack, not especially efficient in this case
722 def vertex_key(lidx):
723 return None
725 # build a mesh mapping dict
726 vertex_hash = [{} for i in range(len(mesh.vertices))]
727 face_tri_list = [[None, None, None] for i in range(len(mesh.loop_triangles))]
728 vert_tri_list = []
729 totvert = 0
730 totface = 0
731 temp_tri = [None] * 3
732 for pidx in polygons_group:
733 for ltri in polygons_to_loop_triangles_indices[pidx]:
734 for tri_vidx, (lidx, vidx) in enumerate(zip(ltri.loops, ltri.vertices)):
735 key = vertex_key(lidx)
736 vh = vertex_hash[vidx]
737 x3d_v = vh.get(key)
738 if x3d_v is None:
739 x3d_v = key, vidx, totvert
740 vh[key] = x3d_v
741 # key / original_vertex / new_vertex
742 vert_tri_list.append(x3d_v)
743 totvert += 1
744 temp_tri[tri_vidx] = x3d_v
746 face_tri_list[totface][:] = temp_tri[:]
747 totface += 1
749 del vertex_key
750 del _tuple_from_rounded_iter
751 assert(len(face_tri_list) == len(mesh.loop_triangles))
753 fw(ident_step + 'index="')
754 for x3d_f in face_tri_list:
755 fw('%i %i %i ' % (x3d_f[0][2], x3d_f[1][2], x3d_f[2][2]))
756 fw('"\n')
758 # close IndexedTriangleSet
759 fw(ident_step + '>\n')
760 ident += '\t'
762 fw('%s<Coordinate ' % ident)
763 fw('point="')
764 for x3d_v in vert_tri_list:
765 fw('%.6f %.6f %.6f ' % mesh_vertices[x3d_v[1]].co[:])
766 fw('" />\n')
768 if use_normals or is_force_normals:
769 fw('%s<Normal ' % ident)
770 fw('vector="')
771 for x3d_v in vert_tri_list:
772 fw('%.6f %.6f %.6f ' % mesh_vertices[x3d_v[1]].normal[:])
773 fw('" />\n')
775 if is_uv:
776 fw('%s<TextureCoordinate point="' % ident)
777 for x3d_v in vert_tri_list:
778 fw('%.4f %.4f ' % x3d_v[0][slot_uv])
779 fw('" />\n')
781 if is_col:
782 fw('%s<ColorRGBA color="' % ident)
783 for x3d_v in vert_tri_list:
784 fw('%.3f %.3f %.3f %.3f ' % x3d_v[0][slot_col])
785 fw('" />\n')
787 if use_h3d:
788 # write attributes
789 for gpu_attr in gpu_shader['attributes']:
791 # UVs
792 if gpu_attr['type'] == gpu.CD_MTFACE:
793 if gpu_attr['datatype'] == gpu.GPU_DATA_2F:
794 fw('%s<FloatVertexAttribute ' % ident)
795 fw('name="%s" ' % gpu_attr['varname'])
796 fw('numComponents="2" ')
797 fw('value="')
798 for x3d_v in vert_tri_list:
799 fw('%.4f %.4f ' % x3d_v[0][slot_uv])
800 fw('" />\n')
801 else:
802 assert(0)
804 elif gpu_attr['type'] == gpu.CD_MCOL:
805 if gpu_attr['datatype'] == gpu.GPU_DATA_4UB:
806 pass # XXX, H3D can't do
807 else:
808 assert(0)
810 ident = ident[:-1]
812 fw('%s</IndexedTriangleSet>\n' % ident)
814 else:
815 ident_step = ident + (' ' * (-len(ident) + \
816 fw('%s<IndexedFaceSet ' % ident)))
818 # --- Write IndexedFaceSet Attributes (same as IndexedTriangleSet)
819 fw('solid="%s"\n' % bool_as_str(material and material.use_backface_culling))
820 if is_smooth:
821 # use Auto-Smooth angle, if enabled. Otherwise make
822 # the mesh perfectly smooth by creaseAngle > pi.
823 fw(ident_step + 'creaseAngle="%.4f"\n' % (mesh.auto_smooth_angle if mesh.use_auto_smooth else 4.0))
825 if use_normals:
826 # currently not optional, could be made so:
827 fw(ident_step + 'normalPerVertex="true"\n')
829 # IndexedTriangleSet assumes true
830 if is_col and not is_col_per_vertex:
831 fw(ident_step + 'colorPerVertex="false"\n')
833 # for IndexedTriangleSet we use a uv per vertex so this isn't needed.
834 if is_uv:
835 fw(ident_step + 'texCoordIndex="')
837 j = 0
838 for i in polygons_group:
839 num_poly_verts = len(mesh_polygons_vertices[i])
840 fw('%s -1 ' % ' '.join((str(i) for i in range(j, j + num_poly_verts))))
841 j += num_poly_verts
842 fw('"\n')
843 # --- end texCoordIndex
845 if True:
846 fw(ident_step + 'coordIndex="')
847 for i in polygons_group:
848 poly_verts = mesh_polygons_vertices[i]
849 fw('%s -1 ' % ' '.join((str(i) for i in poly_verts)))
851 fw('"\n')
852 # --- end coordIndex
854 # close IndexedFaceSet
855 fw(ident_step + '>\n')
856 ident += '\t'
858 # --- Write IndexedFaceSet Elements
859 if True:
860 if is_coords_written:
861 fw('%s<Coordinate USE=%s />\n' % (ident, mesh_id_coords))
862 if use_normals:
863 fw('%s<Normal USE=%s />\n' % (ident, mesh_id_normals))
864 else:
865 ident_step = ident + (' ' * (-len(ident) + \
866 fw('%s<Coordinate ' % ident)))
867 fw('DEF=%s\n' % mesh_id_coords)
868 fw(ident_step + 'point="')
869 for v in mesh.vertices:
870 fw('%.6f %.6f %.6f ' % v.co[:])
871 fw('"\n')
872 fw(ident_step + '/>\n')
874 is_coords_written = True
876 if use_normals:
877 ident_step = ident + (' ' * (-len(ident) + \
878 fw('%s<Normal ' % ident)))
879 fw('DEF=%s\n' % mesh_id_normals)
880 fw(ident_step + 'vector="')
881 for v in mesh.vertices:
882 fw('%.6f %.6f %.6f ' % v.normal[:])
883 fw('"\n')
884 fw(ident_step + '/>\n')
886 if is_uv:
887 fw('%s<TextureCoordinate point="' % ident)
888 for i in polygons_group:
889 for lidx in mesh_polygons[i].loop_indices:
890 fw('%.4f %.4f ' % mesh_loops_uv[lidx].uv[:])
891 fw('" />\n')
893 if is_col:
894 # Need better logic here, dynamic determination
895 # which of the X3D coloring models fits better this mesh - per face
896 # or per vertex. Probably with an explicit fallback mode parameter.
897 fw('%s<ColorRGBA color="' % ident)
898 if is_col_per_vertex:
899 for i in range(len(mesh.vertices)):
900 # may be None,
901 fw('%.3f %.3f %.3f %.3f ' % (vert_color[i] or (0.0, 0.0, 0.0, 0.0)))
902 else: # Export as colors per face.
903 # TODO: average them rather than using the first one!
904 for i in polygons_group:
905 fw('%.3f %.3f %.3f %.3f ' % mesh_loops_col[mesh_polygons[i].loop_start].color[:])
906 fw('" />\n')
908 #--- output vertexColors
910 #--- output closing braces
911 ident = ident[:-1]
913 fw('%s</IndexedFaceSet>\n' % ident)
915 ident = ident[:-1]
916 fw('%s</Shape>\n' % ident)
918 # XXX
920 #fw('%s<PythonScript DEF="PS" url="object.py" >\n' % ident)
921 #fw('%s <ShaderProgram USE="MA_Material.005" containerField="references"/>\n' % ident)
922 #fw('%s</PythonScript>\n' % ident)
924 ident = ident[:-1]
925 fw('%s</Group>\n' % ident)
927 ident = writeTransform_end(ident)
929 if use_collnode:
930 ident = ident[:-1]
931 fw('%s</Collision>\n' % ident)
933 def writeMaterial(ident, material, world):
934 material_id = quoteattr(unique_name(material, MA_ + material.name, uuid_cache_material, clean_func=clean_def, sep="_"))
936 # look up material name, use it if available
937 if material.tag:
938 fw('%s<Material USE=%s />\n' % (ident, material_id))
939 else:
940 material.tag = True
942 emit = 0.0 #material.emit
943 ambient = 0.0 #material.ambient / 3.0
944 diffuseColor = material.diffuse_color[:3]
945 if world and 0:
946 ambiColor = ((material.ambient * 2.0) * world.ambient_color)[:]
947 else:
948 ambiColor = 0.0, 0.0, 0.0
950 emitColor = tuple(((c * emit) + ambiColor[i]) / 2.0 for i, c in enumerate(diffuseColor))
951 shininess = material.specular_intensity
952 specColor = tuple((c + 0.001) / (1.25 / (material.specular_intensity + 0.001)) for c in material.specular_color)
953 transp = 1.0 - material.diffuse_color[3]
955 # ~ if material.use_shadeless:
956 # ~ ambient = 1.0
957 # ~ shininess = 0.0
958 # ~ specColor = emitColor = diffuseColor
960 ident_step = ident + (' ' * (-len(ident) + \
961 fw('%s<Material ' % ident)))
962 fw('DEF=%s\n' % material_id)
963 fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clamp_color(diffuseColor))
964 fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clamp_color(specColor))
965 fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clamp_color(emitColor))
966 fw(ident_step + 'ambientIntensity="%.3f"\n' % ambient)
967 fw(ident_step + 'shininess="%.3f"\n' % shininess)
968 fw(ident_step + 'transparency="%s"\n' % transp)
969 fw(ident_step + '/>\n')
971 def writeMaterialH3D(ident, material, world,
972 obj, gpu_shader):
973 material_id = quoteattr(unique_name(material, 'MA_' + material.name, uuid_cache_material, clean_func=clean_def, sep="_"))
975 fw('%s<Material />\n' % ident)
976 if material.tag:
977 fw('%s<ComposedShader USE=%s />\n' % (ident, material_id))
978 else:
979 material.tag = True
981 # GPU_material_bind_uniforms
982 # GPU_begin_object_materials
984 #~ CD_MCOL 6
985 #~ CD_MTFACE 5
986 #~ CD_ORCO 14
987 #~ CD_TANGENT 18
988 #~ GPU_DATA_16F 7
989 #~ GPU_DATA_1F 2
990 #~ GPU_DATA_1I 1
991 #~ GPU_DATA_2F 3
992 #~ GPU_DATA_3F 4
993 #~ GPU_DATA_4F 5
994 #~ GPU_DATA_4UB 8
995 #~ GPU_DATA_9F 6
996 #~ GPU_DYNAMIC_LIGHT_DYNCO 7
997 #~ GPU_DYNAMIC_LIGHT_DYNCOL 11
998 #~ GPU_DYNAMIC_LIGHT_DYNENERGY 10
999 #~ GPU_DYNAMIC_LIGHT_DYNIMAT 8
1000 #~ GPU_DYNAMIC_LIGHT_DYNPERSMAT 9
1001 #~ GPU_DYNAMIC_LIGHT_DYNVEC 6
1002 #~ GPU_DYNAMIC_OBJECT_COLOR 5
1003 #~ GPU_DYNAMIC_OBJECT_IMAT 4
1004 #~ GPU_DYNAMIC_OBJECT_MAT 2
1005 #~ GPU_DYNAMIC_OBJECT_VIEWIMAT 3
1006 #~ GPU_DYNAMIC_OBJECT_VIEWMAT 1
1007 #~ GPU_DYNAMIC_SAMPLER_2DBUFFER 12
1008 #~ GPU_DYNAMIC_SAMPLER_2DIMAGE 13
1009 #~ GPU_DYNAMIC_SAMPLER_2DSHADOW 14
1012 inline const char* typeToString( X3DType t ) {
1013 switch( t ) {
1014 case SFFLOAT: return "SFFloat";
1015 case MFFLOAT: return "MFFloat";
1016 case SFDOUBLE: return "SFDouble";
1017 case MFDOUBLE: return "MFDouble";
1018 case SFTIME: return "SFTime";
1019 case MFTIME: return "MFTime";
1020 case SFINT32: return "SFInt32";
1021 case MFINT32: return "MFInt32";
1022 case SFVEC2F: return "SFVec2f";
1023 case MFVEC2F: return "MFVec2f";
1024 case SFVEC2D: return "SFVec2d";
1025 case MFVEC2D: return "MFVec2d";
1026 case SFVEC3F: return "SFVec3f";
1027 case MFVEC3F: return "MFVec3f";
1028 case SFVEC3D: return "SFVec3d";
1029 case MFVEC3D: return "MFVec3d";
1030 case SFVEC4F: return "SFVec4f";
1031 case MFVEC4F: return "MFVec4f";
1032 case SFVEC4D: return "SFVec4d";
1033 case MFVEC4D: return "MFVec4d";
1034 case SFBOOL: return "SFBool";
1035 case MFBOOL: return "MFBool";
1036 case SFSTRING: return "SFString";
1037 case MFSTRING: return "MFString";
1038 case SFNODE: return "SFNode";
1039 case MFNODE: return "MFNode";
1040 case SFCOLOR: return "SFColor";
1041 case MFCOLOR: return "MFColor";
1042 case SFCOLORRGBA: return "SFColorRGBA";
1043 case MFCOLORRGBA: return "MFColorRGBA";
1044 case SFROTATION: return "SFRotation";
1045 case MFROTATION: return "MFRotation";
1046 case SFQUATERNION: return "SFQuaternion";
1047 case MFQUATERNION: return "MFQuaternion";
1048 case SFMATRIX3F: return "SFMatrix3f";
1049 case MFMATRIX3F: return "MFMatrix3f";
1050 case SFMATRIX4F: return "SFMatrix4f";
1051 case MFMATRIX4F: return "MFMatrix4f";
1052 case SFMATRIX3D: return "SFMatrix3d";
1053 case MFMATRIX3D: return "MFMatrix3d";
1054 case SFMATRIX4D: return "SFMatrix4d";
1055 case MFMATRIX4D: return "MFMatrix4d";
1056 case UNKNOWN_X3D_TYPE:
1057 default:return "UNKNOWN_X3D_TYPE";
1059 import gpu
1061 fw('%s<ComposedShader DEF=%s language="GLSL" >\n' % (ident, material_id))
1062 ident += '\t'
1064 shader_url_frag = 'shaders/%s_%s.frag' % (filename_strip, material_id[1:-1])
1065 shader_url_vert = 'shaders/%s_%s.vert' % (filename_strip, material_id[1:-1])
1067 # write files
1068 shader_dir = os.path.join(base_dst, 'shaders')
1069 if not os.path.isdir(shader_dir):
1070 os.mkdir(shader_dir)
1072 # ------------------------------------------------------
1073 # shader-patch
1074 field_descr = " <!--- H3D View Matrix Patch -->"
1075 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, H3D_VIEW_MATRIX, field_descr))
1076 frag_vars = ["uniform mat4 %s;" % H3D_VIEW_MATRIX]
1078 # annoying!, we need to track if some of the directional lamp
1079 # vars are children of the camera or not, since this adjusts how
1080 # they are patched.
1081 frag_uniform_var_map = {}
1083 h3d_material_route.append(
1084 '<ROUTE fromNode="%s" fromField="glModelViewMatrix" toNode=%s toField="%s" />%s' %
1085 (H3D_TOP_LEVEL, material_id, H3D_VIEW_MATRIX, field_descr))
1086 # ------------------------------------------------------
1088 for uniform in gpu_shader['uniforms']:
1089 if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE:
1090 field_descr = " <!--- Dynamic Sampler 2d Image -->"
1091 fw('%s<field name="%s" type="SFNode" accessType="inputOutput">%s\n' % (ident, uniform['varname'], field_descr))
1092 writeImageTexture(ident + '\t', uniform['image'])
1093 fw('%s</field>\n' % ident)
1095 elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCO:
1096 light_obj = uniform['lamp']
1097 frag_uniform_var_map[uniform['varname']] = light_obj
1099 if uniform['datatype'] == gpu.GPU_DATA_3F: # should always be true!
1100 light_obj_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
1101 light_obj_transform_id = quoteattr(unique_name(light_obj, light_obj.name, uuid_cache_object, clean_func=clean_def, sep="_"))
1103 value = '%.6f %.6f %.6f' % (global_matrix * light_obj.matrix_world).to_translation()[:]
1104 field_descr = " <!--- Lamp DynCo '%s' -->" % light_obj.name
1105 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1107 # ------------------------------------------------------
1108 # shader-patch
1109 field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % light_obj.name
1110 fw('%s<field name="%s_transform" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr))
1112 # transform
1113 frag_vars.append("uniform mat4 %s_transform;" % uniform['varname'])
1114 h3d_material_route.append(
1115 '<ROUTE fromNode=%s fromField="accumulatedForward" toNode=%s toField="%s_transform" />%s' %
1116 (suffix_quoted_str(light_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr))
1118 h3d_material_route.append(
1119 '<ROUTE fromNode=%s fromField="location" toNode=%s toField="%s" /> %s' %
1120 (light_obj_id, material_id, uniform['varname'], field_descr))
1121 # ------------------------------------------------------
1123 else:
1124 assert(0)
1126 elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCOL:
1127 # odd we have both 3, 4 types.
1128 light_obj = uniform['lamp']
1129 frag_uniform_var_map[uniform['varname']] = light_obj
1131 lamp = light_obj.data
1132 value = '%.6f %.6f %.6f' % (lamp.color * lamp.energy)[:]
1133 field_descr = " <!--- Lamp DynColor '%s' -->" % light_obj.name
1134 if uniform['datatype'] == gpu.GPU_DATA_3F:
1135 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1136 elif uniform['datatype'] == gpu.GPU_DATA_4F:
1137 fw('%s<field name="%s" type="SFVec4f" accessType="inputOutput" value="%s 1.0" />%s\n' % (ident, uniform['varname'], value, field_descr))
1138 else:
1139 assert(0)
1141 elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNENERGY:
1142 # not used ?
1143 assert(0)
1145 elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNVEC:
1146 light_obj = uniform['lamp']
1147 frag_uniform_var_map[uniform['varname']] = light_obj
1149 if uniform['datatype'] == gpu.GPU_DATA_3F:
1150 light_obj = uniform['lamp']
1151 value = '%.6f %.6f %.6f' % ((global_matrix * light_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:]
1152 field_descr = " <!--- Lamp DynDirection '%s' -->" % light_obj.name
1153 fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1155 # route so we can have the lamp update the view
1156 if h3d_is_object_view(scene, light_obj):
1157 light_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_"))
1158 h3d_material_route.append(
1159 '<ROUTE fromNode=%s fromField="direction" toNode=%s toField="%s" />%s' %
1160 (light_id, material_id, uniform['varname'], field_descr))
1162 else:
1163 assert(0)
1165 elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_VIEWIMAT:
1166 frag_uniform_var_map[uniform['varname']] = None
1167 if uniform['datatype'] == gpu.GPU_DATA_16F:
1168 field_descr = " <!--- Object View Matrix Inverse '%s' -->" % obj.name
1169 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr))
1171 h3d_material_route.append(
1172 '<ROUTE fromNode="%s" fromField="glModelViewMatrixInverse" toNode=%s toField="%s" />%s' %
1173 (H3D_TOP_LEVEL, material_id, uniform['varname'], field_descr))
1174 else:
1175 assert(0)
1177 elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_IMAT:
1178 frag_uniform_var_map[uniform['varname']] = None
1179 if uniform['datatype'] == gpu.GPU_DATA_16F:
1180 value = ' '.join(['%.6f' % f for v in (global_matrix * obj.matrix_world).inverted().transposed() for f in v])
1181 field_descr = " <!--- Object Invertex Matrix '%s' -->" % obj.name
1182 fw('%s<field name="%s" type="SFMatrix4f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1183 else:
1184 assert(0)
1186 elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW:
1187 pass # XXX, shadow buffers not supported.
1189 elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DBUFFER:
1190 frag_uniform_var_map[uniform['varname']] = None
1192 if uniform['datatype'] == gpu.GPU_DATA_1I:
1193 if 1:
1194 tex = uniform['texpixels']
1195 value = []
1196 for i in range(0, len(tex) - 1, 4):
1197 col = tex[i:i + 4]
1198 value.append('0x%.2x%.2x%.2x%.2x' % (col[0], col[1], col[2], col[3]))
1200 field_descr = " <!--- Material Buffer -->"
1201 fw('%s<field name="%s" type="SFNode" accessType="inputOutput">%s\n' % (ident, uniform['varname'], field_descr))
1203 ident += '\t'
1205 ident_step = ident + (' ' * (-len(ident) + \
1206 fw('%s<PixelTexture \n' % ident)))
1207 fw(ident_step + 'repeatS="false"\n')
1208 fw(ident_step + 'repeatT="false"\n')
1210 fw(ident_step + 'image="%s 1 4 %s"\n' % (len(value), " ".join(value)))
1212 fw(ident_step + '/>\n')
1214 ident = ident[:-1]
1216 fw('%s</field>\n' % ident)
1218 #for i in range(0, 10, 4)
1219 #value = ' '.join(['%d' % f for f in uniform['texpixels']])
1220 # value = ' '.join(['%.6f' % (f / 256) for f in uniform['texpixels']])
1222 #fw('%s<field name="%s" type="SFInt32" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr))
1223 #print('test', len(uniform['texpixels']))
1224 else:
1225 assert(0)
1226 else:
1227 print("SKIPPING", uniform['type'])
1229 file_frag = open(os.path.join(base_dst, shader_url_frag), 'w', encoding='utf-8')
1230 file_frag.write(gpu_shader['fragment'])
1231 file_frag.close()
1232 # patch it
1233 h3d_shader_glsl_frag_patch(os.path.join(base_dst, shader_url_frag),
1234 scene,
1235 frag_vars,
1236 frag_uniform_var_map,
1239 file_vert = open(os.path.join(base_dst, shader_url_vert), 'w', encoding='utf-8')
1240 file_vert.write(gpu_shader['vertex'])
1241 file_vert.close()
1243 fw('%s<ShaderPart type="FRAGMENT" url=%s />\n' % (ident, quoteattr(shader_url_frag)))
1244 fw('%s<ShaderPart type="VERTEX" url=%s />\n' % (ident, quoteattr(shader_url_vert)))
1245 ident = ident[:-1]
1247 fw('%s</ComposedShader>\n' % ident)
1249 def writeImageTexture(ident, image):
1250 image_id = quoteattr(unique_name(image, IM_ + image.name, uuid_cache_image, clean_func=clean_def, sep="_"))
1252 if image.tag:
1253 fw('%s<ImageTexture USE=%s />\n' % (ident, image_id))
1254 else:
1255 image.tag = True
1257 ident_step = ident + (' ' * (-len(ident) + \
1258 fw('%s<ImageTexture ' % ident)))
1259 fw('DEF=%s\n' % image_id)
1261 # collect image paths, can load multiple
1262 # [relative, name-only, absolute]
1263 filepath = image.filepath
1264 filepath_full = bpy.path.abspath(filepath, library=image.library)
1265 filepath_ref = bpy_extras.io_utils.path_reference(filepath_full, base_src, base_dst, path_mode, "textures", copy_set, image.library)
1266 filepath_base = os.path.basename(filepath_full)
1268 images = [
1269 filepath_ref,
1270 filepath_base,
1272 if path_mode != 'RELATIVE':
1273 images.append(filepath_full)
1275 images = [f.replace('\\', '/') for f in images]
1276 images = [f for i, f in enumerate(images) if f not in images[:i]]
1278 fw(ident_step + "url='%s'\n" % ' '.join(['"%s"' % escape(f) for f in images]))
1279 fw(ident_step + '/>\n')
1281 def writeBackground(ident, world):
1283 if world is None:
1284 return
1286 # note, not re-used
1287 world_id = quoteattr(unique_name(world, WO_ + world.name, uuid_cache_world, clean_func=clean_def, sep="_"))
1289 # XXX World changed a lot in 2.8... For now do minimal get-it-to-work job.
1290 # ~ blending = world.use_sky_blend, world.use_sky_paper, world.use_sky_real
1292 # ~ grd_triple = clamp_color(world.horizon_color)
1293 # ~ sky_triple = clamp_color(world.zenith_color)
1294 # ~ mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3))
1296 blending = (False, False, False)
1298 grd_triple = clamp_color(world.color)
1299 sky_triple = clamp_color(world.color)
1300 mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3))
1302 ident_step = ident + (' ' * (-len(ident) + \
1303 fw('%s<Background ' % ident)))
1304 fw('DEF=%s\n' % world_id)
1305 # No Skytype - just Hor color
1306 if blending == (False, False, False):
1307 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % grd_triple)
1308 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % grd_triple)
1309 # Blend Gradient
1310 elif blending == (True, False, False):
1311 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (grd_triple + mix_triple))
1312 fw(ident_step + 'groundAngle="1.57"\n')
1313 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + mix_triple))
1314 fw(ident_step + 'skyAngle="1.57"\n')
1315 # Blend+Real Gradient Inverse
1316 elif blending == (True, False, True):
1317 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1318 fw(ident_step + 'groundAngle="1.57"\n')
1319 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple + sky_triple))
1320 fw(ident_step + 'skyAngle="1.57, 3.14159"\n')
1321 # Paper - just Zen Color
1322 elif blending == (False, False, True):
1323 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % sky_triple)
1324 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % sky_triple)
1325 # Blend+Real+Paper - komplex gradient
1326 elif blending == (True, True, True):
1327 fw(ident_step + 'groundColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1328 fw(ident_step + 'groundAngle="1.57"\n')
1329 fw(ident_step + 'skyColor="%.3f %.3f %.3f, %.3f %.3f %.3f"\n' % (sky_triple + grd_triple))
1330 fw(ident_step + 'skyAngle="1.57"\n')
1331 # Any Other two colors
1332 else:
1333 fw(ident_step + 'groundColor="%.3f %.3f %.3f"\n' % grd_triple)
1334 fw(ident_step + 'skyColor="%.3f %.3f %.3f"\n' % sky_triple)
1336 for tex in bpy.data.textures:
1337 if tex.type == 'IMAGE' and tex.image:
1338 namemat = tex.name
1339 pic = tex.image
1340 basename = quoteattr(bpy.path.basename(pic.filepath))
1342 if namemat == 'back':
1343 fw(ident_step + 'backUrl=%s\n' % basename)
1344 elif namemat == 'bottom':
1345 fw(ident_step + 'bottomUrl=%s\n' % basename)
1346 elif namemat == 'front':
1347 fw(ident_step + 'frontUrl=%s\n' % basename)
1348 elif namemat == 'left':
1349 fw(ident_step + 'leftUrl=%s\n' % basename)
1350 elif namemat == 'right':
1351 fw(ident_step + 'rightUrl=%s\n' % basename)
1352 elif namemat == 'top':
1353 fw(ident_step + 'topUrl=%s\n' % basename)
1355 fw(ident_step + '/>\n')
1357 # -------------------------------------------------------------------------
1358 # Export Object Hierarchy (recursively called)
1359 # -------------------------------------------------------------------------
1360 def export_object(ident, obj_main_parent, obj_main, obj_children):
1361 matrix_fallback = mathutils.Matrix()
1362 world = scene.world
1363 derived_dict = create_derived_objects(depsgraph, [obj_main])
1364 derived = derived_dict.get(obj_main)
1366 if use_hierarchy:
1367 obj_main_matrix_world = obj_main.matrix_world
1368 if obj_main_parent:
1369 obj_main_matrix = obj_main_parent.matrix_world.inverted(matrix_fallback) @ obj_main_matrix_world
1370 else:
1371 obj_main_matrix = obj_main_matrix_world
1372 obj_main_matrix_world_invert = obj_main_matrix_world.inverted(matrix_fallback)
1374 obj_main_id = quoteattr(unique_name(obj_main, obj_main.name, uuid_cache_object, clean_func=clean_def, sep="_"))
1376 ident = writeTransform_begin(ident, obj_main_matrix if obj_main_parent else global_matrix @ obj_main_matrix, suffix_quoted_str(obj_main_id, _TRANSFORM))
1378 # Set here just incase we dont enter the loop below.
1379 is_dummy_tx = False
1381 for obj, obj_matrix in (() if derived is None else derived):
1382 obj_type = obj.type
1384 if use_hierarchy:
1385 # make transform node relative
1386 obj_matrix = obj_main_matrix_world_invert @ obj_matrix
1387 else:
1388 obj_matrix = global_matrix @ obj_matrix
1390 # H3D - use for writing a dummy transform parent
1391 is_dummy_tx = False
1393 if obj_type == 'CAMERA':
1394 writeViewpoint(ident, obj, obj_matrix, scene)
1396 if use_h3d and scene.camera == obj:
1397 view_id = uuid_cache_view[obj]
1398 fw('%s<Transform DEF="%s">\n' % (ident, H3D_CAMERA_FOLLOW))
1399 h3d_material_route.extend([
1400 '<ROUTE fromNode="%s" fromField="totalPosition" toNode="%s" toField="translation" />' % (view_id, H3D_CAMERA_FOLLOW),
1401 '<ROUTE fromNode="%s" fromField="totalOrientation" toNode="%s" toField="rotation" />' % (view_id, H3D_CAMERA_FOLLOW),
1403 is_dummy_tx = True
1404 ident += '\t'
1406 elif obj_type in {'MESH', 'CURVE', 'SURFACE', 'FONT'}:
1407 if (obj_type != 'MESH') or (use_mesh_modifiers and obj.is_modified(scene, 'PREVIEW')):
1408 obj_for_mesh = obj.evaluated_get(depsgraph) if use_mesh_modifiers else obj
1409 try:
1410 me = obj_for_mesh.to_mesh()
1411 except:
1412 me = None
1413 do_remove = True
1414 else:
1415 me = obj.data
1416 do_remove = False
1418 if me is not None:
1419 # ensure unique name, we could also do this by
1420 # postponing mesh removal, but clearing data - TODO
1421 if do_remove:
1422 me_name_new = me_name_original = obj.name.rstrip("1234567890").rstrip(".")
1423 count = 0
1424 while me_name_new in mesh_name_set:
1425 me_name_new = "%.17s.%03d" % (me_name_original, count)
1426 count += 1
1427 mesh_name_set.add(me_name_new)
1428 mesh_name = me_name_new
1429 del me_name_new, me_name_original, count
1430 else:
1431 mesh_name = me.name
1432 # done
1434 writeIndexedFaceSet(ident, obj, me, mesh_name, obj_matrix, world)
1436 # free mesh created with to_mesh()
1437 if do_remove:
1438 obj_for_mesh.to_mesh_clear()
1440 elif obj_type == 'LIGHT':
1441 data = obj.data
1442 datatype = data.type
1443 if datatype == 'POINT':
1444 writePointLight(ident, obj, obj_matrix, data, world)
1445 elif datatype == 'SPOT':
1446 writeSpotLight(ident, obj, obj_matrix, data, world)
1447 elif datatype == 'SUN':
1448 writeDirectionalLight(ident, obj, obj_matrix, data, world)
1449 else:
1450 writeDirectionalLight(ident, obj, obj_matrix, data, world)
1451 else:
1452 #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType)
1453 pass
1455 # ---------------------------------------------------------------------
1456 # write out children recursively
1457 # ---------------------------------------------------------------------
1458 for obj_child, obj_child_children in obj_children:
1459 export_object(ident, obj_main, obj_child, obj_child_children)
1461 if is_dummy_tx:
1462 ident = ident[:-1]
1463 fw('%s</Transform>\n' % ident)
1464 is_dummy_tx = False
1466 if use_hierarchy:
1467 ident = writeTransform_end(ident)
1469 # -------------------------------------------------------------------------
1470 # Main Export Function
1471 # -------------------------------------------------------------------------
1472 def export_main():
1473 world = scene.world
1475 # tag un-exported IDs
1476 bpy.data.meshes.tag(False)
1477 bpy.data.materials.tag(False)
1478 bpy.data.images.tag(False)
1480 if use_selection:
1481 objects = [obj for obj in view_layer.objects if obj.visible_get(view_layer=view_layer)
1482 and obj.select_get(view_layer=view_layer)]
1483 else:
1484 objects = [obj for obj in view_layer.objects if obj.visible_get(view_layer=view_layer)]
1486 print('Info: starting X3D export to %r...' % file.name)
1487 ident = ''
1488 ident = writeHeader(ident)
1490 writeNavigationInfo(ident, scene, any(obj.type == 'LIGHT' for obj in objects))
1491 writeBackground(ident, world)
1492 writeFog(ident, world)
1494 ident = '\t\t'
1496 if use_hierarchy:
1497 objects_hierarchy = build_hierarchy(objects)
1498 else:
1499 objects_hierarchy = ((obj, []) for obj in objects)
1501 for obj_main, obj_main_children in objects_hierarchy:
1502 export_object(ident, None, obj_main, obj_main_children)
1504 ident = writeFooter(ident)
1506 export_main()
1508 # -------------------------------------------------------------------------
1509 # global cleanup
1510 # -------------------------------------------------------------------------
1511 file.close()
1513 if use_h3d:
1514 bpy.data.materials.remove(gpu_shader_dummy_mat)
1516 # copy all collected files.
1517 # print(copy_set)
1518 bpy_extras.io_utils.path_reference_copy(copy_set)
1520 print('Info: finished X3D export to %r' % file.name)
1523 ##########################################################
1524 # Callbacks, needed before Main
1525 ##########################################################
1528 def gzip_open_utf8(filepath, mode):
1529 """Workaround for py3k only allowing binary gzip writing"""
1531 import gzip
1533 # need to investigate encoding
1534 file = gzip.open(filepath, mode)
1535 write_real = file.write
1537 def write_wrap(data):
1538 return write_real(data.encode("utf-8"))
1540 file.write = write_wrap
1542 return file
1545 def save(context,
1546 filepath,
1548 use_selection=True,
1549 use_mesh_modifiers=False,
1550 use_triangulate=False,
1551 use_normals=False,
1552 use_compress=False,
1553 use_hierarchy=True,
1554 use_h3d=False,
1555 global_matrix=None,
1556 path_mode='AUTO',
1557 name_decorations=True
1560 bpy.path.ensure_ext(filepath, '.x3dz' if use_compress else '.x3d')
1562 if bpy.ops.object.mode_set.poll():
1563 bpy.ops.object.mode_set(mode='OBJECT')
1565 if use_compress:
1566 file = gzip_open_utf8(filepath, 'w')
1567 else:
1568 file = open(filepath, 'w', encoding='utf-8')
1570 if global_matrix is None:
1571 global_matrix = mathutils.Matrix()
1573 export(file,
1574 global_matrix,
1575 context.evaluated_depsgraph_get(),
1576 context.scene,
1577 context.view_layer,
1578 use_mesh_modifiers=use_mesh_modifiers,
1579 use_selection=use_selection,
1580 use_triangulate=use_triangulate,
1581 use_normals=use_normals,
1582 use_hierarchy=use_hierarchy,
1583 use_h3d=use_h3d,
1584 path_mode=path_mode,
1585 name_decorations=name_decorations,
1588 return {'FINISHED'}