import bpy from bpy.props import (StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty, CollectionProperty) from bpy.types import Operator from bpy_extras.io_utils import ImportHelper, ExportHelper from io_scene_gltf2 import ConvertGLTF2_Base from .common import Font # taken from blender_git/blender/scripts/addons/io_scene_gltf2/__init__.py def get_font_faces_in_file(filepath): from io_scene_gltf2.io.imp.gltf2_io_gltf import glTFImporter, ImportError try: import_settings = { 'import_user_extensions': [] } gltf_importer = glTFImporter(filepath, import_settings) gltf_importer.read() gltf_importer.checks() out = [] for node in gltf_importer.data.nodes: if type(node.extras) != type(None) \ and "glyph" in node.extras \ and not ("type" in node.extras and node.extras["type"] == "metrics"): out.append(node.extras) return out except ImportError as e: return None # taken from blender_git/blender/scripts/addons/io_scene_gltf2/__init__.py class GetFontFacesInFile(Operator, ImportHelper): """Load a glTF 2.0 font and check which faces are in there""" bl_idname = f"abc3d.check_font_gltf" bl_label = 'Check glTF 2.0 Font' bl_options = {'REGISTER', 'UNDO'} files: CollectionProperty( name="File Path", type=bpy.types.OperatorFileListElement, ) # bpy.ops.abc3d.check_font_gltf(filepath="/home/jrkb/.config/blender/4.1/datafiles/abc3d/fonts/JRKB_LOL.glb") found_fonts = [] def execute(self, context): return self.check_gltf2(context) def check_gltf2(self, context): import os import sys if self.files: # Multiple file check ret = {'CANCELLED'} dirname = os.path.dirname(self.filepath) for file in self.files: path = os.path.join(dirname, file.name) if self.unit_check(path) == {'FINISHED'}: ret = {'FINISHED'} return ret else: # Single file check return self.unit_check(self.filepath) def unit_check(self, filename): self.found_fonts.append(["LOL","WHATEVER"]) return {'FINISHED'} class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper): """Load a glTF 2.0 font""" bl_idname = f"abc3d.import_font_gltf" bl_label = 'Import glTF 2.0 Font' bl_options = {'REGISTER', 'UNDO'} filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'}) files: CollectionProperty( name="File Path", type=bpy.types.OperatorFileListElement, ) loglevel: IntProperty( name='Log Level', description="Log Level") import_pack_images: BoolProperty( name='Pack Images', description='Pack all images into .blend file', default=True ) merge_vertices: BoolProperty( name='Merge Vertices', description=( 'The glTF format requires discontinuous normals, UVs, and ' 'other vertex attributes to be stored as separate vertices, ' 'as required for rendering on typical graphics hardware. ' 'This option attempts to combine co-located vertices where possible. ' 'Currently cannot combine verts with different normals' ), default=False, ) import_shading: EnumProperty( name="Shading", items=(("NORMALS", "Use Normal Data", ""), ("FLAT", "Flat Shading", ""), ("SMOOTH", "Smooth Shading", "")), description="How normals are computed during import", default="NORMALS") bone_heuristic: EnumProperty( name="Bone Dir", items=( ("BLENDER", "Blender (best for import/export round trip)", "Good for re-importing glTFs exported from Blender, " "and re-exporting glTFs to glTFs after Blender editing. " "Bone tips are placed on their local +Y axis (in glTF space)"), ("TEMPERANCE", "Temperance (average)", "Decent all-around strategy. " "A bone with one child has its tip placed on the local axis " "closest to its child"), ("FORTUNE", "Fortune (may look better, less accurate)", "Might look better than Temperance, but also might have errors. " "A bone with one child has its tip placed at its child's root. " "Non-uniform scalings may get messed up though, so beware"), ), description="Heuristic for placing bones. Tries to make bones pretty", default="BLENDER", ) guess_original_bind_pose: BoolProperty( name='Guess Original Bind Pose', description=( 'Try to guess the original bind pose for skinned meshes from ' 'the inverse bind matrices. ' 'When off, use default/rest pose as bind pose' ), default=True, ) import_webp_texture: BoolProperty( name='Import WebP textures', description=( "If a texture exists in WebP format, " "loads the WebP texture instead of the fallback PNG/JPEG one" ), default=False, ) glyphs: StringProperty( name='Import only these glyphs', description=( "Loading glyphs is expensive, if the meshes are huge" "So we can filter all glyphs out that we do not want" ), default="A", ) marker_property: StringProperty( name="Mark imported objects with this custom property.", default="font_import", ) font_name: StringProperty( name="If defined, only import this font", default="", ) face_name: StringProperty( name="If defined, only import this font face", default="", ) def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. layout.prop(self, 'import_pack_images') layout.prop(self, 'merge_vertices') layout.prop(self, 'import_shading') layout.prop(self, 'guess_original_bind_pose') layout.prop(self, 'bone_heuristic') layout.prop(self, 'export_import_convert_lighting_mode') layout.prop(self, 'import_webp_texture') def invoke(self, context, event): import sys preferences = bpy.context.preferences for addon_name in preferences.addons.keys(): try: if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'): importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel()) except Exception: pass self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0 return ImportHelper.invoke(self, context, event) def execute(self, context): return self.import_gltf2(context) def import_gltf2(self, context): import os self.set_debug_log() import_settings = self.as_keywords() user_extensions = [] import sys preferences = bpy.context.preferences for addon_name in preferences.addons.keys(): try: module = sys.modules[addon_name] except Exception: continue if hasattr(module, 'glTF2ImportUserExtension'): extension_ctor = module.glTF2ImportUserExtension user_extensions.append(extension_ctor()) import_settings['import_user_extensions'] = user_extensions if self.files: # Multiple file import ret = {'CANCELLED'} dirname = os.path.dirname(self.filepath) for file in self.files: path = os.path.join(dirname, file.name) if self.unit_import(path, import_settings) == {'FINISHED'}: ret = {'FINISHED'} return ret else: # Single file import return self.unit_import(self.filepath, import_settings) def unit_import(self, filename, import_settings): import time from io_scene_gltf2.io.imp.gltf2_io_gltf import glTFImporter, ImportError from io_scene_gltf2.blender.imp.gltf2_blender_gltf import BlenderGlTF from io_scene_gltf2.blender.imp.gltf2_blender_vnode import VNode, compute_vnodes from io_scene_gltf2.blender.com.gltf2_blender_extras import set_extras from io_scene_gltf2.blender.imp.gltf2_blender_node import BlenderNode try: gltf = glTFImporter(filename, import_settings) gltf.read() gltf.checks() # start filtering glyphs like this: # - collect indices of nodes that contain our glyphs # - collect indices of their meshes # - collect the node's parent tree # - use these indices to create new lists of nodes and meshes # - update the scene tree to contain only our nodes # - replace the node and mesh list with ours # indices of meshes to keep mesh_indices = [] # indices of nodes to keep node_indices = [] # convenience function to add a node to the indices def add_node(node, recursive=True): node_index = gltf.data.nodes.index(node) if node_index not in node_indices: node_indices.append(node_index) if type(node.mesh) != type(None) and node.mesh >= 0: mesh_index = node.mesh if mesh_index not in mesh_indices: mesh_indices.append(mesh_index) if recursive and type(node.children) != type(None): for c in node.children: child = gltf.data.nodes[c] add_node(child) # convenience function to add a mesh to the indices def add_parent_node(node, recursive=True): index = gltf.data.nodes.index(node) for parent in gltf.data.nodes: if type(parent.children) != type(None) and index in parent.children: add_node(parent, False) if recursive: add_parent_node(parent) # populate our node_indices and mesh_indices # by iterating through the nodes and check if they are # indeed representing a glyph we want for node in gltf.data.nodes: # :-O woah if type(node.extras) != type(None) \ and "glyph" in node.extras \ and (node.extras["glyph"] in self.glyphs \ or len(self.glyphs) == 0) \ and (self.font_name == "" or \ ( "font_name" in node.extras \ and (node.extras["font_name"] in self.font_name \ or len(self.glyphs) == 0))) \ and (self.face_name == "" or \ ( "face_name" in node.extras \ and (node.extras["face_name"] in self.face_name \ or len(self.glyphs) == 0))): # if there is a match, add the node incl children .. add_node(node) # .. and their parents recursively add_parent_node(node) # in the end we need the objects, not the indices # so let's prepare empy lists meshes = [] nodes = [] # the indices will be off, as we have fewer elements # so let's have a lookup table mesh_index_table = {} node_index_table = {} # first, add all meshes and fill in lookup table for mesh_index, mesh in enumerate(gltf.data.meshes): if mesh_index in mesh_indices: meshes.append(mesh) mesh_index_table[mesh_index] = len(meshes) - 1 # second, add all nodes and fill in lookup table # nodes also refer to their meshes for node_index, node in enumerate(gltf.data.nodes): if node_index in node_indices: if type(node.mesh) != type(None): node.mesh = mesh_index_table[node.mesh] nodes.append(node) node_index_table[node_index] = len(nodes) - 1 # the indices to children are messed up. # some children are lost :( # and some have different indices for node in nodes: if type(node.children) != type(None): children = [] # brand new children for i, c in enumerate(node.children): # check if children are lost if c in node_indices: children.append(node_index_table[c]) # now replace old children with the new, however # if we don't have children, we don't even need a list! node.children = None if len(children) == 0 else children # last step, kick nodes out of the scene tree if they're lost for s in gltf.data.scenes: scene_nodes = [] for n in s.nodes: if n in node_indices: scene_nodes.append(node_index_table[n]) s.nodes = scene_nodes # very last step, replace nodes and meshes gltf.data.nodes = nodes gltf.data.meshes = meshes # that's fucking it, we're done! # hand over back to default blender behaviour :-) # or.. not! blender will do some funny scene stuff # which we don't want. # so let's do it quick print("Data are loaded, start creating Blender stuff") start_time = time.time() # first, convert gltf to blender BlenderGlTF.set_convert_functions(gltf) # compute things BlenderGlTF.pre_compute(gltf) compute_vnodes(gltf) # apparently we need a scene, because # when creating the objects, it will link the objects here gltf.blender_scene = bpy.context.scene.name def create_blender_object(gltf, vi, nodes): vnode = gltf.vnodes[vi] if vnode.type == VNode.Object: if vnode.parent is not None: if not hasattr(gltf.vnodes[vnode.parent], "blender_object"): create_blender_object(gltf, vnode.parent, nodes) if not hasattr(vnode, "blender_object"): obj = BlenderNode.create_object(gltf, vi) obj["font_import"] = True n_vars = vars(nodes[vi]) if "extras" in n_vars: set_extras(obj, n_vars["extras"]) if "glyph" in n_vars["extras"] and \ not ("type" in n_vars["extras"] and \ n_vars["extras"]["type"] == "metrics"): obj["type"] = "glyph" for vi, vnode in gltf.vnodes.items(): create_blender_object(gltf, vi, nodes) elapsed_s = "{:.2f}s".format(time.time() - start_time) print("font import gltf finished in " + elapsed_s) gltf.log.removeHandler(gltf.log_handler) return {'FINISHED'} except ImportError as e: self.report({'ERROR'}, e.args[0]) return {'CANCELLED'} def set_debug_log(self): import logging if bpy.app.debug_value == 0: self.loglevel = logging.CRITICAL elif bpy.app.debug_value == 1: self.loglevel = logging.ERROR elif bpy.app.debug_value == 2: self.loglevel = logging.WARNING elif bpy.app.debug_value == 3: self.loglevel = logging.INFO else: self.loglevel = logging.NOTSET