386 lines
12 KiB
Python
386 lines
12 KiB
Python
#!/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)
|
|
|