# SPDX-License-Identifier: GPL-2.0-or-later # Contributors: bart:neeneenee*de, http://www.neeneenee.de/vrml, Campbell Barton """ This script exports to X3D format. Usage: Run this script from "File->Export" menu. A pop-up will ask whether you want to export only selected or all relevant objects. Known issues: Doesn't handle multiple materials (don't use material indices);
Doesn't handle multiple UV textures on a single mesh (create a mesh for each texture);
Can't get the texture array associated with material * not the UV ones; """ import math import os import bpy import mathutils from bpy_extras.io_utils import create_derived_objects # h3d defines H3D_TOP_LEVEL = 'TOP_LEVEL_TI' H3D_CAMERA_FOLLOW = 'CAMERA_FOLLOW_TRANSFORM' H3D_VIEW_MATRIX = 'view_matrix' def clamp_color(col): return tuple([max(min(c, 1.0), 0.0) for c in col]) def matrix_direction_neg_z(matrix): return (matrix.to_3x3() @ mathutils.Vector((0.0, 0.0, -1.0))).normalized()[:] def prefix_quoted_str(value, prefix): return value[0] + prefix + value[1:] def suffix_quoted_str(value, suffix): return value[:-1] + suffix + value[-1:] def bool_as_str(value): return ('false', 'true')[bool(value)] def clean_def(txt): # see report [#28256] if not txt: txt = "None" # no digit start if txt[0] in "1234567890+-": txt = "_" + txt return txt.translate({ # control characters 0x0-0x1f # 0x00: "_", 0x01: "_", 0x02: "_", 0x03: "_", 0x04: "_", 0x05: "_", 0x06: "_", 0x07: "_", 0x08: "_", 0x09: "_", 0x0a: "_", 0x0b: "_", 0x0c: "_", 0x0d: "_", 0x0e: "_", 0x0f: "_", 0x10: "_", 0x11: "_", 0x12: "_", 0x13: "_", 0x14: "_", 0x15: "_", 0x16: "_", 0x17: "_", 0x18: "_", 0x19: "_", 0x1a: "_", 0x1b: "_", 0x1c: "_", 0x1d: "_", 0x1e: "_", 0x1f: "_", 0x7f: "_", # 127 0x20: "_", # space 0x22: "_", # " 0x27: "_", # ' 0x23: "_", # # 0x2c: "_", # , 0x2e: "_", # . 0x5b: "_", # [ 0x5d: "_", # ] 0x5c: "_", # \ 0x7b: "_", # { 0x7d: "_", # } }) def build_hierarchy(objects): """ returns parent child relationships, skipping """ objects_set = set(objects) par_lookup = {} def test_parent(parent): while (parent is not None) and (parent not in objects_set): parent = parent.parent return parent for obj in objects: par_lookup.setdefault(test_parent(obj.parent), []).append((obj, [])) for parent, children in par_lookup.items(): for obj, subchildren in children: subchildren[:] = par_lookup.get(obj, []) return par_lookup.get(None, []) # ----------------------------------------------------------------------------- # H3D Functions # ----------------------------------------------------------------------------- def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_map): h3d_file = open(filepath, 'r', encoding='utf-8') lines = [] last_transform = None for l in h3d_file: if l.startswith("void main(void)"): lines.append("\n") lines.append("// h3d custom vars begin\n") for v in global_vars: lines.append("%s\n" % v) lines.append("// h3d custom vars end\n") lines.append("\n") elif l.lstrip().startswith("light_visibility_other("): w = l.split(', ') last_transform = w[1] + "_transform" # XXX - HACK!!! w[1] = '(view_matrix * %s_transform * vec4(%s.x, %s.y, %s.z, 1.0)).xyz' % (w[1], w[1], w[1], w[1]) l = ", ".join(w) elif l.lstrip().startswith("light_visibility_sun_hemi("): w = l.split(', ') w[0] = w[0][len("light_visibility_sun_hemi(") + 1:] if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]): w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0] else: w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * -%s)' % (last_transform, last_transform, last_transform, w[0])) l = "\tlight_visibility_sun_hemi(" + ", ".join(w) elif l.lstrip().startswith("light_visibility_spot_circle("): w = l.split(', ') w[0] = w[0][len("light_visibility_spot_circle(") + 1:] if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]): w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0] else: w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * %s)' % (last_transform, last_transform, last_transform, w[0])) l = "\tlight_visibility_spot_circle(" + ", ".join(w) lines.append(l) h3d_file.close() h3d_file = open(filepath, 'w', encoding='utf-8') h3d_file.writelines(lines) h3d_file.close() def h3d_is_object_view(scene, obj): camera = scene.camera parent = obj.parent while parent: if parent == camera: return True parent = parent.parent return False # ----------------------------------------------------------------------------- # Functions for writing output file # ----------------------------------------------------------------------------- def export(file, global_matrix, depsgraph, scene, view_layer, use_mesh_modifiers=False, use_selection=True, use_triangulate=False, use_normals=False, use_hierarchy=True, use_h3d=False, path_mode='AUTO', name_decorations=True, ): # ------------------------------------------------------------------------- # Global Setup # ------------------------------------------------------------------------- import bpy_extras from bpy_extras.io_utils import unique_name from xml.sax.saxutils import quoteattr, escape if name_decorations: # If names are decorated, the uuid map can be split up # by type for efficiency of collision testing # since objects of different types will always have # different decorated names. uuid_cache_object = {} # object uuid_cache_light = {} # 'LA_' + object.name uuid_cache_view = {} # object, different namespace uuid_cache_mesh = {} # mesh uuid_cache_material = {} # material uuid_cache_image = {} # image uuid_cache_world = {} # world CA_ = 'CA_' OB_ = 'OB_' ME_ = 'ME_' IM_ = 'IM_' WO_ = 'WO_' MA_ = 'MA_' LA_ = 'LA_' group_ = 'group_' else: # If names are not decorated, it may be possible for two objects to # have the same name, so there has to be a unified dictionary to # prevent uuid collisions. uuid_cache = {} uuid_cache_object = uuid_cache # object uuid_cache_light = uuid_cache # 'LA_' + object.name uuid_cache_view = uuid_cache # object, different namespace uuid_cache_mesh = uuid_cache # mesh uuid_cache_material = uuid_cache # material uuid_cache_image = uuid_cache # image uuid_cache_world = uuid_cache # world del uuid_cache CA_ = '' OB_ = '' ME_ = '' IM_ = '' WO_ = '' MA_ = '' LA_ = '' group_ = '' _TRANSFORM = '_TRANSFORM' # store files to copy copy_set = set() # store names of newly created meshes, so we dont overlap mesh_name_set = set() fw = file.write base_src = os.path.dirname(bpy.data.filepath) base_dst = os.path.dirname(file.name) filename_strip = os.path.splitext(os.path.basename(file.name))[0] gpu_shader_cache = {} if use_h3d: import gpu gpu_shader_dummy_mat = bpy.data.materials.new('X3D_DYMMY_MAT') gpu_shader_cache[None] = gpu.export_shader(scene, gpu_shader_dummy_mat) h3d_material_route = [] # ------------------------------------------------------------------------- # File Writing Functions # ------------------------------------------------------------------------- def writeHeader(ident): filepath_quoted = quoteattr(os.path.basename(file.name)) blender_ver_quoted = quoteattr('Blender %s' % bpy.app.version_string) fw('%s\n' % ident) if use_h3d: fw('%s\n' % ident) else: fw('%s\n' % ident) fw('%s\n' % ident) ident += '\t' fw('%s\n' % ident) ident += '\t' fw('%s\n' % (ident, filepath_quoted)) fw('%s\n' % (ident, blender_ver_quoted)) # this info was never updated, so blender version should be enough # fw('%s\n' % ident) ident = ident[:-1] fw('%s\n' % ident) fw('%s\n' % ident) ident += '\t' if use_h3d: # outputs the view matrix in glModelViewMatrix field fw('%s\n' % (ident, H3D_TOP_LEVEL)) return ident def writeFooter(ident): if use_h3d: # global for route in h3d_material_route: fw('%s%s\n' % (ident, route)) ident = ident[:-1] fw('%s\n' % ident) ident = ident[:-1] fw('%s' % ident) return ident def writeViewpoint(ident, obj, matrix, scene): view_id = quoteattr(unique_name(obj, CA_ + obj.name, uuid_cache_view, clean_func=clean_def, sep="_")) loc, rot, scale = matrix.decompose() rot = rot.to_axis_angle() rot = (*rot[0].normalized(), rot[1]) ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeFog(ident, world): if world: mtype = world.mist_settings.falloff mparam = world.mist_settings else: return if mparam.use_mist: ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') else: return def writeNavigationInfo(ident, scene, has_light): ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeTransform_begin(ident, matrix, def_id): ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') ident += '\t' return ident def writeTransform_end(ident): ident = ident[:-1] fw('%s\n' % ident) return ident def writeSpotLight(ident, obj, matrix, light, world): # note, light_id is not re-used light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world and 0: ambi = world.ambient_color amb_intensity = ((ambi[0] + ambi[1] + ambi[2]) / 3.0) / 2.5 del ambi else: amb_intensity = 0.0 # compute cutoff and beamwidth intensity = min(lamp.energy / 1.75, 1.0) beamWidth = lamp.spot_size * 0.37 # beamWidth=((lamp.spotSize*math.pi)/180.0)*.37 cutOffAngle = beamWidth * 1.3 orientation = matrix_direction_neg_z(matrix) location = matrix.to_translation()[:] radius = lamp.distance * math.cos(beamWidth) # radius = lamp.dist*math.cos(beamWidth) ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeDirectionalLight(ident, obj, matrix, light, world): # note, light_id is not re-used light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world and 0: ambi = world.ambient_color # ambi = world.amb amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5 else: ambi = 0 amb_intensity = 0.0 intensity = min(light.energy / 1.75, 1.0) orientation = matrix_direction_neg_z(matrix) ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writePointLight(ident, obj, matrix, light, world): # note, light_id is not re-used light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world and 0: ambi = world.ambient_color # ambi = world.amb amb_intensity = ((float(ambi[0] + ambi[1] + ambi[2])) / 3.0) / 2.5 else: ambi = 0.0 amb_intensity = 0.0 intensity = min(light.energy / 1.75, 1.0) location = matrix.to_translation()[:] ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeIndexedFaceSet(ident, obj, mesh, mesh_name, matrix, world): obj_id = quoteattr(unique_name(obj, OB_ + obj.name, uuid_cache_object, clean_func=clean_def, sep="_")) mesh_id = quoteattr(unique_name(mesh, ME_ + mesh_name, uuid_cache_mesh, clean_func=clean_def, sep="_")) mesh_id_group = prefix_quoted_str(mesh_id, group_) mesh_id_coords = prefix_quoted_str(mesh_id, 'coords_') mesh_id_normals = prefix_quoted_str(mesh_id, 'normals_') # Be sure tessellated loop triangles are available! if use_triangulate: if not mesh.loop_triangles and mesh.polygons: mesh.calc_loop_triangles() use_collnode = bool([mod for mod in obj.modifiers if mod.type == 'COLLISION' if mod.show_viewport]) if use_collnode: fw('%s\n' % ident) ident += '\t' # use _ifs_TRANSFORM suffix so we dont collide with transform node when # hierarchys are used. ident = writeTransform_begin(ident, matrix, suffix_quoted_str(obj_id, "_ifs" + _TRANSFORM)) if mesh.tag: fw('%s\n' % (ident, mesh_id_group)) else: mesh.tag = True fw('%s\n' % (ident, mesh_id_group)) ident += '\t' is_uv = bool(mesh.uv_layers.active) # is_col, defined for each material is_coords_written = False mesh_materials = mesh.materials[:] if not mesh_materials: mesh_materials = [None] mesh_material_tex = [None] * len(mesh_materials) mesh_material_mtex = [None] * len(mesh_materials) mesh_material_images = [None] * len(mesh_materials) for i, material in enumerate(mesh_materials): if 0 and material: for mtex in material.texture_slots: if mtex: tex = mtex.texture if tex and tex.type == 'IMAGE': image = tex.image if image: mesh_material_tex[i] = tex mesh_material_mtex[i] = mtex mesh_material_images[i] = image break # fast access! mesh_vertices = mesh.vertices[:] mesh_loops = mesh.loops[:] mesh_polygons = mesh.polygons[:] mesh_polygons_materials = [p.material_index for p in mesh_polygons] mesh_polygons_vertices = [p.vertices[:] for p in mesh_polygons] if len(set(mesh_material_images)) > 0: # make sure there is at least one image mesh_polygons_image = [mesh_material_images[material_index] for material_index in mesh_polygons_materials] else: mesh_polygons_image = [None] * len(mesh_polygons) mesh_polygons_image_unique = set(mesh_polygons_image) # group faces polygons_groups = {} for material_index in range(len(mesh_materials)): for image in mesh_polygons_image_unique: polygons_groups[material_index, image] = [] del mesh_polygons_image_unique for i, (material_index, image) in enumerate(zip(mesh_polygons_materials, mesh_polygons_image)): polygons_groups[material_index, image].append(i) # Py dict are sorted now, so we can use directly polygons_groups.items() # and still get consistent reproducible outputs. is_col = mesh.vertex_colors.active mesh_loops_col = mesh.vertex_colors.active.data if is_col else None # Check if vertex colors can be exported in per-vertex mode. # Do we have just one color per vertex in every face that uses the vertex? if is_col: def calc_vertex_color(): vert_color = [None] * len(mesh.vertices) for i, p in enumerate(mesh_polygons): for lidx in p.loop_indices: l = mesh_loops[lidx] if vert_color[l.vertex_index] is None: vert_color[l.vertex_index] = mesh_loops_col[lidx].color[:] elif vert_color[l.vertex_index] != mesh_loops_col[lidx].color[:]: return False, () return True, vert_color is_col_per_vertex, vert_color = calc_vertex_color() del calc_vertex_color # If using looptris, we need a mapping poly_index -> loop_tris_indices... if use_triangulate: polygons_to_loop_triangles_indices = [[] for i in range(len(mesh_polygons))] for ltri in mesh.loop_triangles: polygons_to_loop_triangles_indices[ltri.polygon_index].append(ltri) for (material_index, image), polygons_group in polygons_groups.items(): if polygons_group: material = mesh_materials[material_index] fw('%s\n' % ident) ident += '\t' is_smooth = False # kludge but as good as it gets! for i in polygons_group: if mesh_polygons[i].use_smooth: is_smooth = True break # UV's and VCols split verts off which effects smoothing # force writing normals in this case. # Also, creaseAngle is not supported for IndexedTriangleSet, # so write normals when is_smooth (otherwise # IndexedTriangleSet can have only all smooth/all flat shading). is_force_normals = use_triangulate and (is_smooth or is_uv or is_col) if use_h3d: gpu_shader = gpu_shader_cache.get(material) # material can be 'None', uses dummy cache if gpu_shader is None: gpu_shader = gpu_shader_cache[material] = gpu.export_shader(scene, material) if 1: # XXX DEBUG gpu_shader_tmp = gpu.export_shader(scene, material) import pprint print('\nWRITING MATERIAL:', material.name) del gpu_shader_tmp['fragment'] del gpu_shader_tmp['vertex'] pprint.pprint(gpu_shader_tmp, width=120) #pprint.pprint(val['vertex']) del gpu_shader_tmp fw('%s\n' % ident) ident += '\t' if image and not use_h3d: writeImageTexture(ident, image) # transform by mtex loc = mesh_material_mtex[material_index].offset[:2] # mtex_scale * tex_repeat sca_x, sca_y = mesh_material_mtex[material_index].scale[:2] sca_x *= mesh_material_tex[material_index].repeat_x sca_y *= mesh_material_tex[material_index].repeat_y # flip x/y is a sampling feature, convert to transform if mesh_material_tex[material_index].use_flip_axis: rot = math.pi / -2.0 sca_x, sca_y = sca_y, -sca_x else: rot = 0.0 ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') if use_h3d: mat_tmp = material if material else gpu_shader_dummy_mat writeMaterialH3D(ident, mat_tmp, world, obj, gpu_shader) del mat_tmp else: if material: writeMaterial(ident, material, world) ident = ident[:-1] fw('%s\n' % ident) mesh_loops_uv = mesh.uv_layers.active.data if is_uv else None #-- IndexedFaceSet or IndexedLineSet if use_triangulate: ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') ident += '\t' fw('%s\n') if use_normals or is_force_normals: fw('%s\n') if is_uv: fw('%s\n') if is_col: fw('%s\n') if use_h3d: # write attributes for gpu_attr in gpu_shader['attributes']: # UVs if gpu_attr['type'] == gpu.CD_MTFACE: if gpu_attr['datatype'] == gpu.GPU_DATA_2F: fw('%s\n') else: assert(0) elif gpu_attr['type'] == gpu.CD_MCOL: if gpu_attr['datatype'] == gpu.GPU_DATA_4UB: pass # XXX, H3D can't do else: assert(0) ident = ident[:-1] fw('%s\n' % ident) else: ident_step = ident + (' ' * (-len(ident) + \ fw('%s pi. fw(ident_step + 'creaseAngle="%.4f"\n' % (mesh.auto_smooth_angle if mesh.use_auto_smooth else 4.0)) if use_normals: # currently not optional, could be made so: fw(ident_step + 'normalPerVertex="true"\n') # IndexedTriangleSet assumes true if is_col and not is_col_per_vertex: fw(ident_step + 'colorPerVertex="false"\n') # for IndexedTriangleSet we use a uv per vertex so this isn't needed. if is_uv: fw(ident_step + 'texCoordIndex="') j = 0 for i in polygons_group: num_poly_verts = len(mesh_polygons_vertices[i]) fw('%s -1 ' % ' '.join((str(i) for i in range(j, j + num_poly_verts)))) j += num_poly_verts fw('"\n') # --- end texCoordIndex if True: fw(ident_step + 'coordIndex="') for i in polygons_group: poly_verts = mesh_polygons_vertices[i] fw('%s -1 ' % ' '.join((str(i) for i in poly_verts))) fw('"\n') # --- end coordIndex # close IndexedFaceSet fw(ident_step + '>\n') ident += '\t' # --- Write IndexedFaceSet Elements if True: if is_coords_written: fw('%s\n' % (ident, mesh_id_coords)) if use_normals: fw('%s\n' % (ident, mesh_id_normals)) else: ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') is_coords_written = True if use_normals: ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') if is_uv: fw('%s\n') if is_col: # Need better logic here, dynamic determination # which of the X3D coloring models fits better this mesh - per face # or per vertex. Probably with an explicit fallback mode parameter. fw('%s\n') #--- output vertexColors #--- output closing braces ident = ident[:-1] fw('%s\n' % ident) ident = ident[:-1] fw('%s\n' % ident) # XXX #fw('%s\n' % ident) #fw('%s \n' % ident) #fw('%s\n' % ident) ident = ident[:-1] fw('%s\n' % ident) ident = writeTransform_end(ident) if use_collnode: ident = ident[:-1] fw('%s\n' % ident) def writeMaterial(ident, material, world): material_id = quoteattr(unique_name(material, MA_ + material.name, uuid_cache_material, clean_func=clean_def, sep="_")) # look up material name, use it if available if material.tag: fw('%s\n' % (ident, material_id)) else: material.tag = True emit = 0.0 #material.emit ambient = 0.0 #material.ambient / 3.0 diffuseColor = material.diffuse_color[:3] if world and 0: ambiColor = ((material.ambient * 2.0) * world.ambient_color)[:] else: ambiColor = 0.0, 0.0, 0.0 emitColor = tuple(((c * emit) + ambiColor[i]) / 2.0 for i, c in enumerate(diffuseColor)) shininess = material.specular_intensity specColor = tuple((c + 0.001) / (1.25 / (material.specular_intensity + 0.001)) for c in material.specular_color) transp = 1.0 - material.diffuse_color[3] # ~ if material.use_shadeless: # ~ ambient = 1.0 # ~ shininess = 0.0 # ~ specColor = emitColor = diffuseColor ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeMaterialH3D(ident, material, world, obj, gpu_shader): material_id = quoteattr(unique_name(material, 'MA_' + material.name, uuid_cache_material, clean_func=clean_def, sep="_")) fw('%s\n' % ident) if material.tag: fw('%s\n' % (ident, material_id)) else: material.tag = True # GPU_material_bind_uniforms # GPU_begin_object_materials #~ CD_MCOL 6 #~ CD_MTFACE 5 #~ CD_ORCO 14 #~ CD_TANGENT 18 #~ GPU_DATA_16F 7 #~ GPU_DATA_1F 2 #~ GPU_DATA_1I 1 #~ GPU_DATA_2F 3 #~ GPU_DATA_3F 4 #~ GPU_DATA_4F 5 #~ GPU_DATA_4UB 8 #~ GPU_DATA_9F 6 #~ GPU_DYNAMIC_LIGHT_DYNCO 7 #~ GPU_DYNAMIC_LIGHT_DYNCOL 11 #~ GPU_DYNAMIC_LIGHT_DYNENERGY 10 #~ GPU_DYNAMIC_LIGHT_DYNIMAT 8 #~ GPU_DYNAMIC_LIGHT_DYNPERSMAT 9 #~ GPU_DYNAMIC_LIGHT_DYNVEC 6 #~ GPU_DYNAMIC_OBJECT_COLOR 5 #~ GPU_DYNAMIC_OBJECT_IMAT 4 #~ GPU_DYNAMIC_OBJECT_MAT 2 #~ GPU_DYNAMIC_OBJECT_VIEWIMAT 3 #~ GPU_DYNAMIC_OBJECT_VIEWMAT 1 #~ GPU_DYNAMIC_SAMPLER_2DBUFFER 12 #~ GPU_DYNAMIC_SAMPLER_2DIMAGE 13 #~ GPU_DYNAMIC_SAMPLER_2DSHADOW 14 ''' inline const char* typeToString( X3DType t ) { switch( t ) { case SFFLOAT: return "SFFloat"; case MFFLOAT: return "MFFloat"; case SFDOUBLE: return "SFDouble"; case MFDOUBLE: return "MFDouble"; case SFTIME: return "SFTime"; case MFTIME: return "MFTime"; case SFINT32: return "SFInt32"; case MFINT32: return "MFInt32"; case SFVEC2F: return "SFVec2f"; case MFVEC2F: return "MFVec2f"; case SFVEC2D: return "SFVec2d"; case MFVEC2D: return "MFVec2d"; case SFVEC3F: return "SFVec3f"; case MFVEC3F: return "MFVec3f"; case SFVEC3D: return "SFVec3d"; case MFVEC3D: return "MFVec3d"; case SFVEC4F: return "SFVec4f"; case MFVEC4F: return "MFVec4f"; case SFVEC4D: return "SFVec4d"; case MFVEC4D: return "MFVec4d"; case SFBOOL: return "SFBool"; case MFBOOL: return "MFBool"; case SFSTRING: return "SFString"; case MFSTRING: return "MFString"; case SFNODE: return "SFNode"; case MFNODE: return "MFNode"; case SFCOLOR: return "SFColor"; case MFCOLOR: return "MFColor"; case SFCOLORRGBA: return "SFColorRGBA"; case MFCOLORRGBA: return "MFColorRGBA"; case SFROTATION: return "SFRotation"; case MFROTATION: return "MFRotation"; case SFQUATERNION: return "SFQuaternion"; case MFQUATERNION: return "MFQuaternion"; case SFMATRIX3F: return "SFMatrix3f"; case MFMATRIX3F: return "MFMatrix3f"; case SFMATRIX4F: return "SFMatrix4f"; case MFMATRIX4F: return "MFMatrix4f"; case SFMATRIX3D: return "SFMatrix3d"; case MFMATRIX3D: return "MFMatrix3d"; case SFMATRIX4D: return "SFMatrix4d"; case MFMATRIX4D: return "MFMatrix4d"; case UNKNOWN_X3D_TYPE: default:return "UNKNOWN_X3D_TYPE"; ''' import gpu fw('%s\n' % (ident, material_id)) ident += '\t' shader_url_frag = 'shaders/%s_%s.frag' % (filename_strip, material_id[1:-1]) shader_url_vert = 'shaders/%s_%s.vert' % (filename_strip, material_id[1:-1]) # write files shader_dir = os.path.join(base_dst, 'shaders') if not os.path.isdir(shader_dir): os.mkdir(shader_dir) # ------------------------------------------------------ # shader-patch field_descr = " " fw('%s%s\n' % (ident, H3D_VIEW_MATRIX, field_descr)) frag_vars = ["uniform mat4 %s;" % H3D_VIEW_MATRIX] # annoying!, we need to track if some of the directional lamp # vars are children of the camera or not, since this adjusts how # they are patched. frag_uniform_var_map = {} h3d_material_route.append( '%s' % (H3D_TOP_LEVEL, material_id, H3D_VIEW_MATRIX, field_descr)) # ------------------------------------------------------ for uniform in gpu_shader['uniforms']: if uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DIMAGE: field_descr = " " fw('%s%s\n' % (ident, uniform['varname'], field_descr)) writeImageTexture(ident + '\t', uniform['image']) fw('%s\n' % ident) elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCO: light_obj = uniform['lamp'] frag_uniform_var_map[uniform['varname']] = light_obj if uniform['datatype'] == gpu.GPU_DATA_3F: # should always be true! light_obj_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) light_obj_transform_id = quoteattr(unique_name(light_obj, light_obj.name, uuid_cache_object, clean_func=clean_def, sep="_")) value = '%.6f %.6f %.6f' % (global_matrix * light_obj.matrix_world).to_translation()[:] field_descr = " " % light_obj.name fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) # ------------------------------------------------------ # shader-patch field_descr = " " % light_obj.name fw('%s%s\n' % (ident, uniform['varname'], field_descr)) # transform frag_vars.append("uniform mat4 %s_transform;" % uniform['varname']) h3d_material_route.append( '%s' % (suffix_quoted_str(light_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr)) h3d_material_route.append( ' %s' % (light_obj_id, material_id, uniform['varname'], field_descr)) # ------------------------------------------------------ else: assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCOL: # odd we have both 3, 4 types. light_obj = uniform['lamp'] frag_uniform_var_map[uniform['varname']] = light_obj lamp = light_obj.data value = '%.6f %.6f %.6f' % (lamp.color * lamp.energy)[:] field_descr = " " % light_obj.name if uniform['datatype'] == gpu.GPU_DATA_3F: fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) elif uniform['datatype'] == gpu.GPU_DATA_4F: fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) else: assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNENERGY: # not used ? assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNVEC: light_obj = uniform['lamp'] frag_uniform_var_map[uniform['varname']] = light_obj if uniform['datatype'] == gpu.GPU_DATA_3F: light_obj = uniform['lamp'] value = '%.6f %.6f %.6f' % ((global_matrix * light_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:] field_descr = " " % light_obj.name fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) # route so we can have the lamp update the view if h3d_is_object_view(scene, light_obj): light_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) h3d_material_route.append( '%s' % (light_id, material_id, uniform['varname'], field_descr)) else: assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_VIEWIMAT: frag_uniform_var_map[uniform['varname']] = None if uniform['datatype'] == gpu.GPU_DATA_16F: field_descr = " " % obj.name fw('%s%s\n' % (ident, uniform['varname'], field_descr)) h3d_material_route.append( '%s' % (H3D_TOP_LEVEL, material_id, uniform['varname'], field_descr)) else: assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_OBJECT_IMAT: frag_uniform_var_map[uniform['varname']] = None if uniform['datatype'] == gpu.GPU_DATA_16F: value = ' '.join(['%.6f' % f for v in (global_matrix * obj.matrix_world).inverted().transposed() for f in v]) field_descr = " " % obj.name fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) else: assert(0) elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DSHADOW: pass # XXX, shadow buffers not supported. elif uniform['type'] == gpu.GPU_DYNAMIC_SAMPLER_2DBUFFER: frag_uniform_var_map[uniform['varname']] = None if uniform['datatype'] == gpu.GPU_DATA_1I: if 1: tex = uniform['texpixels'] value = [] for i in range(0, len(tex) - 1, 4): col = tex[i:i + 4] value.append('0x%.2x%.2x%.2x%.2x' % (col[0], col[1], col[2], col[3])) field_descr = " " fw('%s%s\n' % (ident, uniform['varname'], field_descr)) ident += '\t' ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') ident = ident[:-1] fw('%s\n' % ident) #for i in range(0, 10, 4) #value = ' '.join(['%d' % f for f in uniform['texpixels']]) # value = ' '.join(['%.6f' % (f / 256) for f in uniform['texpixels']]) #fw('%s%s\n' % (ident, uniform['varname'], value, field_descr)) #print('test', len(uniform['texpixels'])) else: assert(0) else: print("SKIPPING", uniform['type']) file_frag = open(os.path.join(base_dst, shader_url_frag), 'w', encoding='utf-8') file_frag.write(gpu_shader['fragment']) file_frag.close() # patch it h3d_shader_glsl_frag_patch(os.path.join(base_dst, shader_url_frag), scene, frag_vars, frag_uniform_var_map, ) file_vert = open(os.path.join(base_dst, shader_url_vert), 'w', encoding='utf-8') file_vert.write(gpu_shader['vertex']) file_vert.close() fw('%s\n' % (ident, quoteattr(shader_url_frag))) fw('%s\n' % (ident, quoteattr(shader_url_vert))) ident = ident[:-1] fw('%s\n' % ident) def writeImageTexture(ident, image): image_id = quoteattr(unique_name(image, IM_ + image.name, uuid_cache_image, clean_func=clean_def, sep="_")) if image.tag: fw('%s\n' % (ident, image_id)) else: image.tag = True ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') def writeBackground(ident, world): if world is None: return # note, not re-used world_id = quoteattr(unique_name(world, WO_ + world.name, uuid_cache_world, clean_func=clean_def, sep="_")) # XXX World changed a lot in 2.8... For now do minimal get-it-to-work job. # ~ blending = world.use_sky_blend, world.use_sky_paper, world.use_sky_real # ~ grd_triple = clamp_color(world.horizon_color) # ~ sky_triple = clamp_color(world.zenith_color) # ~ mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3)) blending = (False, False, False) grd_triple = clamp_color(world.color) sky_triple = clamp_color(world.color) mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3)) ident_step = ident + (' ' * (-len(ident) + \ fw('%s\n') # ------------------------------------------------------------------------- # Export Object Hierarchy (recursively called) # ------------------------------------------------------------------------- def export_object(ident, obj_main_parent, obj_main, obj_children): matrix_fallback = mathutils.Matrix() world = scene.world derived_dict = create_derived_objects(depsgraph, [obj_main]) derived = derived_dict.get(obj_main) if use_hierarchy: obj_main_matrix_world = obj_main.matrix_world if obj_main_parent: obj_main_matrix = obj_main_parent.matrix_world.inverted(matrix_fallback) @ obj_main_matrix_world else: obj_main_matrix = obj_main_matrix_world obj_main_matrix_world_invert = obj_main_matrix_world.inverted(matrix_fallback) obj_main_id = quoteattr(unique_name(obj_main, obj_main.name, uuid_cache_object, clean_func=clean_def, sep="_")) ident = writeTransform_begin(ident, obj_main_matrix if obj_main_parent else global_matrix @ obj_main_matrix, suffix_quoted_str(obj_main_id, _TRANSFORM)) # Set here just incase we dont enter the loop below. is_dummy_tx = False for obj, obj_matrix in (() if derived is None else derived): obj_type = obj.type if use_hierarchy: # make transform node relative obj_matrix = obj_main_matrix_world_invert @ obj_matrix else: obj_matrix = global_matrix @ obj_matrix # H3D - use for writing a dummy transform parent is_dummy_tx = False if obj_type == 'CAMERA': writeViewpoint(ident, obj, obj_matrix, scene) if use_h3d and scene.camera == obj: view_id = uuid_cache_view[obj] fw('%s\n' % (ident, H3D_CAMERA_FOLLOW)) h3d_material_route.extend([ '' % (view_id, H3D_CAMERA_FOLLOW), '' % (view_id, H3D_CAMERA_FOLLOW), ]) is_dummy_tx = True ident += '\t' elif obj_type in {'MESH', 'CURVE', 'SURFACE', 'FONT'}: if (obj_type != 'MESH') or (use_mesh_modifiers and obj.is_modified(scene, 'PREVIEW')): obj_for_mesh = obj.evaluated_get(depsgraph) if use_mesh_modifiers else obj try: me = obj_for_mesh.to_mesh() except: me = None do_remove = True else: me = obj.data do_remove = False if me is not None: # ensure unique name, we could also do this by # postponing mesh removal, but clearing data - TODO if do_remove: me_name_new = me_name_original = obj.name.rstrip("1234567890").rstrip(".") count = 0 while me_name_new in mesh_name_set: me_name_new = "%.17s.%03d" % (me_name_original, count) count += 1 mesh_name_set.add(me_name_new) mesh_name = me_name_new del me_name_new, me_name_original, count else: mesh_name = me.name # done writeIndexedFaceSet(ident, obj, me, mesh_name, obj_matrix, world) # free mesh created with to_mesh() if do_remove: obj_for_mesh.to_mesh_clear() elif obj_type == 'LIGHT': data = obj.data datatype = data.type if datatype == 'POINT': writePointLight(ident, obj, obj_matrix, data, world) elif datatype == 'SPOT': writeSpotLight(ident, obj, obj_matrix, data, world) elif datatype == 'SUN': writeDirectionalLight(ident, obj, obj_matrix, data, world) else: writeDirectionalLight(ident, obj, obj_matrix, data, world) else: #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType) pass # --------------------------------------------------------------------- # write out children recursively # --------------------------------------------------------------------- for obj_child, obj_child_children in obj_children: export_object(ident, obj_main, obj_child, obj_child_children) if is_dummy_tx: ident = ident[:-1] fw('%s\n' % ident) is_dummy_tx = False if use_hierarchy: ident = writeTransform_end(ident) # ------------------------------------------------------------------------- # Main Export Function # ------------------------------------------------------------------------- def export_main(): world = scene.world # tag un-exported IDs bpy.data.meshes.tag(False) bpy.data.materials.tag(False) bpy.data.images.tag(False) if use_selection: objects = [obj for obj in view_layer.objects if obj.visible_get(view_layer=view_layer) and obj.select_get(view_layer=view_layer)] else: objects = [obj for obj in view_layer.objects if obj.visible_get(view_layer=view_layer)] print('Info: starting X3D export to %r...' % file.name) ident = '' ident = writeHeader(ident) writeNavigationInfo(ident, scene, any(obj.type == 'LIGHT' for obj in objects)) writeBackground(ident, world) writeFog(ident, world) ident = '\t\t' if use_hierarchy: objects_hierarchy = build_hierarchy(objects) else: objects_hierarchy = ((obj, []) for obj in objects) for obj_main, obj_main_children in objects_hierarchy: export_object(ident, None, obj_main, obj_main_children) ident = writeFooter(ident) export_main() # ------------------------------------------------------------------------- # global cleanup # ------------------------------------------------------------------------- file.close() if use_h3d: bpy.data.materials.remove(gpu_shader_dummy_mat) # copy all collected files. # print(copy_set) bpy_extras.io_utils.path_reference_copy(copy_set) print('Info: finished X3D export to %r' % file.name) ########################################################## # Callbacks, needed before Main ########################################################## def gzip_open_utf8(filepath, mode): """Workaround for py3k only allowing binary gzip writing""" import gzip # need to investigate encoding file = gzip.open(filepath, mode) write_real = file.write def write_wrap(data): return write_real(data.encode("utf-8")) file.write = write_wrap return file def save(context, filepath, *, use_selection=True, use_mesh_modifiers=False, use_triangulate=False, use_normals=False, use_compress=False, use_hierarchy=True, use_h3d=False, global_matrix=None, path_mode='AUTO', name_decorations=True ): bpy.path.ensure_ext(filepath, '.x3dz' if use_compress else '.x3d') if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') if use_compress: file = gzip_open_utf8(filepath, 'w') else: file = open(filepath, 'w', encoding='utf-8') if global_matrix is None: global_matrix = mathutils.Matrix() export(file, global_matrix, context.evaluated_depsgraph_get(), context.scene, context.view_layer, use_mesh_modifiers=use_mesh_modifiers, use_selection=use_selection, use_triangulate=use_triangulate, use_normals=use_normals, use_hierarchy=use_hierarchy, use_h3d=use_h3d, path_mode=path_mode, name_decorations=name_decorations, ) return {'FINISHED'}