#!/usr/bin/python3 # by Joric, https://github.com/joric/io_scene_b3d try: from B3DParser import * except: pass try: from .B3DParser import * import bpy import mathutils from bpy_extras.image_utils import load_image from bpy_extras.io_utils import unpack_list, unpack_face_list import bmesh except: pass def flip(v): return ((v[0],v[2],v[1]) if len(v)<4 else (v[0], v[1],v[3],v[2])) def flip_all(v): return [y for y in [flip(x) for x in v]] material_mapping = {} weighting = {} """ def make_skeleton(node): objName = 'armature' a = bpy.data.objects.new(objName, bpy.data.armatures.new(objName)) armatures.append(a); ctx.scene.collection.objects.link(a) for i in bpy.context.selected_objects: i.select_set(state=False) a.select_set(state=True) a.show_in_front = True a.data.display_type = 'STICK' bpy.context.view_layer.objects.active = a bpy.ops.object.mode_set(mode='EDIT',toggle=False) bones = {} # copy bones positions from precalculated objects for bone_id, (name, pos, rot, parent_id) in enumerate(bonesdata): if name not in bpy.data.objects: return ob = bpy.data.objects[name] #if parent_id != -1: name = ob.parent.name bone = a.data.edit_bones.new(name) bones[bone_id] = bone v = ob.matrix_world.to_translation() # use short segment as a bone (smd-like hierarchy), will convert later bone.tail = ob.matrix_world.to_translation() bone.head = (v[0]-0.01,v[1],v[2]) if parent_id != -1: bone.parent = bones[parent_id] #bone.head = ob.parent.matrix_world.to_translation() # delete all objects with the same names as the bones for name, pos, rot, parent_id in bonesdata: try: bpy.data.objects.remove(bpy.data.objects[name]) except: pass bpy.ops.object.mode_set(mode='OBJECT') #for i in bpy.context.selected_objects: i.select = False #deselect all objects for i in bpy.context.selected_objects: i.select_set(state=False) #deselect all objects #2.8 fails # get parent mesh (hardcoded so far) objName = 'anim' if objName in bpy.data.objects.keys(): ob = bpy.data.objects[objName] else: return # apply armature modifier modifier = ob.modifiers.new(type="ARMATURE", name="armature") modifier.object = a # create vertex groups for bone in a.data.bones.values(): group = ob.vertex_groups.new(name=bone.name) if bone.name in weighting.keys(): for vertex_id, weight in weighting[bone.name]: #vertex_id = remaps[objName][vertex_id] group_indices = [vertex_id] group.add(group_indices, weight, 'REPLACE') actionName = 'default_action' action = bpy.data.actions.new(actionName) action.use_fake_user = True a.animation_data_create() a.animation_data.action = action #action.fps = 30fps if fps else 30 bpy.context.scene.render.fps = 60 bpy.context.scene.render.fps_base = 1 #ops.object.mode_set(mode='POSE') bpy.context.scene.frame_start = 0 bpy.context.scene.frame_end = node.frames - 1 ## ANIMATION! bone_string = 'Bip01' curvesLoc = None curvesRot = None bone_string = "pose.bones[\"{}\"].".format(bone.name) group = action.groups.new(name=bone_string) for bone_id, (name, keys, rot, parent_id) in enumerate(bonesdata): for frame in range(node.frames): # (unoptimized) walk through all keys and select the frame for key in keys: if key.frame==frame: pass #print(name, key) for keyframe in range(node.frames): if curvesLoc and curvesRot: break if keyframe.pos and not curvesLoc: curvesLoc = [] for i in range(3): curve = action.fcurves.new(data_path=bone_string + "location",index=i) curve.group = group curvesLoc.append(curve) if keyframe.rot and not curvesRot: curvesRot = [] for i in range(3 if smd.rotMode == 'XYZ' else 4): curve = action.fcurves.new(data_path=bone_string + "rotation_" + ("euler" if smd.rotMode == 'XYZ' else "quaternion"),index=i) curve.group = group curvesRot.append(curve) for i in range(3): curve = action.fcurves.new(data_path=bone_string + "location",index=i) group = action.groups.new(name=bone_name) curve.group = group location = (10,50,100) for frame in range(node.frames): for i in range(3): curve.keyframe_points.add(1) curve.keyframe_points[-1].co = [frame, location[i]] curve = action.fcurves.new(data_path=bone_string + "rotation_quaternion",index=i) group = action.groups.new(name=bone_name) curve.group = group rotation = (1,0,1,0) for i in range(4): curvesRot[i].keyframe_points.add(1) curvesRot[i].keyframe_points[-1].co = [keyframe.frame, bone.rotation_quaternion[i]] #curve = action.fcurves.new(data_path=bone_string + "rotation_quaternion",index=i) """ def import_mesh(node, parent): global material_mapping mesh = bpy.data.meshes.new(node.name) # join face arrays faces = [] for face in node.faces: faces.extend(face.indices) # create mesh from data mesh.from_pydata(flip_all(node.vertices), [], flip_all(faces)) # assign normals mesh.vertices.foreach_set('normal', unpack_list(node.normals)) # create object from mesh ob = bpy.data.objects.new(node.name, mesh) # assign uv coordinates bpymesh = ob.data uvs = [(0,0) if len(uv)==0 else (uv[0], 1-uv[1]) for uv in node.uvs] uvlist = [i for poly in bpymesh.polygons for vidx in poly.vertices for i in uvs[vidx]] bpymesh.uv_layers.new().data.foreach_set('uv', uvlist) # adding object materials (insert-ordered) for key, value in material_mapping.items(): ob.data.materials.append(bpy.data.materials[value]) # assign material_indexes poly = 0 for face in node.faces: for _ in face.indices: ob.data.polygons[poly].material_index = face.brush_id poly += 1 return ob def select_recursive(root): for c in root.children: select_recursive(c) root.select_set(state=True) def make_armature_recursive(root, a, parent_bone): bone = a.data.edit_bones.new(root.name) v = root.matrix_world.to_translation() bone.tail = v # bone.head = (v[0]-0.01,v[1],v[2]) # large handles! bone.parent = parent_bone if bone.parent: bone.head = bone.parent.tail parent_bone = bone for c in root.children: make_armature_recursive(c, a, parent_bone) def make_armatures(): global ctx global imported_armatures, weighting for dummy_root in imported_armatures: objName = 'armature' a = bpy.data.objects.new(objName, bpy.data.armatures.new(objName)) ctx.scene.collection.objects.link(a) for i in bpy.context.selected_objects: i.select_set(state=False) a.select_set(state=True) a.show_in_front = True a.data.display_type = 'OCTAHEDRAL' bpy.context.view_layer.objects.active = a bpy.ops.object.mode_set(mode='EDIT',toggle=False) make_armature_recursive(dummy_root, a, None) bpy.ops.object.mode_set(mode='OBJECT',toggle=False) # set ob to mesh object ob = dummy_root.parent a.parent = ob # delete dummy objects hierarchy for i in bpy.context.selected_objects: i.select_set(state=False) select_recursive(dummy_root) bpy.ops.object.delete(use_global=True) # apply armature modifier modifier = ob.modifiers.new(type="ARMATURE", name="armature") modifier.object = a # create vertex groups for bone in a.data.bones.values(): group = ob.vertex_groups.new(name=bone.name) if bone.name in weighting.keys(): for vertex_id, weight in weighting[bone.name]: group_indices = [vertex_id] group.add(group_indices, weight, 'REPLACE') a.parent.data.update() def import_bone(node, parent=None): global imported_armatures, weighting # add dummy objects to calculate bone positions later ob = bpy.data.objects.new(node.name, None) # fill weighting map for later use w = [] for vert_id, weight in node['bones']: w.append((vert_id, weight)) weighting[node.name] = w # check parent, add root armature if parent and parent.type=='MESH': imported_armatures.append(ob) return ob def import_node_recursive(node, parent=None): ob = None if 'vertices' in node and 'faces' in node: ob = import_mesh(node, parent) elif 'bones' in node: ob = import_bone(node, parent) elif node.name: ob = bpy.data.objects.new(node.name, None) if ob: ctx.scene.collection.objects.link(ob) if parent: ob.parent = parent ob.rotation_mode='QUATERNION' ob.rotation_quaternion = flip(node.rotation) ob.scale = flip(node.scale) ob.location = flip(node.position) for x in node.nodes: import_node_recursive(x, ob) def load_b3d(filepath, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, APPLY_MATRIX=True, global_matrix=None): global ctx global material_mapping ctx = context data = B3DTree().parse(filepath) # load images images = {} dirname = os.path.dirname(filepath) for i, texture in enumerate(data['textures'] if 'textures' in data else []): texture_name = os.path.basename(texture['name']) for mat in data.materials: if mat.tids[0]==i: images[i] = (texture_name, load_image(texture_name, dirname, check_existing=True, place_holder=False, recursive=IMAGE_SEARCH)) # create materials material_mapping = {} for i, mat in enumerate(data.materials if 'materials' in data else []): material = bpy.data.materials.new(mat.name) material_mapping[i] = material.name material.diffuse_color = mat.rgba material.blend_method = 'MULTIPLY' if mat.rgba[3] < 1.0 else 'OPAQUE' tid = mat.tids[0] if len(mat.tids) else -1 if tid in images: name, image = images[tid] texture = bpy.data.textures.new(name=name, type='IMAGE') material.use_nodes = True bsdf = material.node_tree.nodes["Principled BSDF"] texImage = material.node_tree.nodes.new('ShaderNodeTexImage') texImage.image = image material.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) global imported_armatures, weighting imported_armatures = [] weighting = {} import_node_recursive(data) make_armatures() def load(operator, context, filepath="", constrain_size=0.0, use_image_search=True, use_apply_transform=True, global_matrix=None, ): load_b3d(filepath, context, IMPORT_CONSTRAIN_BOUNDS=constrain_size, IMAGE_SEARCH=use_image_search, APPLY_MATRIX=use_apply_transform, global_matrix=global_matrix, ) return {'FINISHED'} #filepath = 'D:/Projects/github/io_scene_b3d/testing/gooey.b3d' filepath = 'C:/Games/GnomE/media/models/ded/ded.b3d' #filepath = 'C:/Games/GnomE/media/models/gnome/model.b3d' #filepath = 'C:/Games/GnomE/media/levels/level1.b3d' #filepath = 'C:/Games/GnomE/media/models/gnome/go.b3d' #filepath = 'C:/Games/GnomE/media/models/flag/flag.b3d' if __name__ == "__main__": p = B3DDebugParser() p.parse(filepath)