def import_bone_bind(self, n_block, n_bind_store, b_armature_data, n_armature, b_parent_bone=None): """Adds a bone to the armature in edit mode.""" # check that n_block is indeed a bone if not self.is_bone(n_block): return None # bone name bone_name = block_store.import_name(n_block) # create a new bone b_edit_bone = b_armature_data.edit_bones.new(bone_name) # store nif block for access from object mode self.name_to_block[b_edit_bone.name] = n_block # get the nif bone's armature space matrix (under the hood all bone space matrixes are multiplied together) n_bind = mathutils.Matrix(n_bind_store.get(n_block, NifFormat.Matrix44()).as_list()).transposed() # get transformation in blender's coordinate space b_bind = math.nif_bind_to_blender_bind(n_bind) # the following is a workaround because blender can no longer set matrices to bones directly tail, roll = bpy.types.Bone.AxisRollFromMatrix(b_bind.to_3x3()) b_edit_bone.head = b_bind.to_translation() b_edit_bone.tail = tail + b_edit_bone.head b_edit_bone.roll = roll # link to parent if b_parent_bone: b_edit_bone.parent = b_parent_bone # import and parent bone children for n_child in n_block.children: self.import_bone_bind(n_child, n_bind_store, b_armature_data, n_armature, b_edit_bone)
def import_bone_bind(self, n_block, b_armature_data, n_armature, b_parent_bone=None): """Adds a bone to the armature in edit mode.""" # check that n_block is indeed a bone if not self.is_bone(n_block): return None # bone name bone_name = block_store.import_name(n_block) # create a new bone b_edit_bone = b_armature_data.edit_bones.new(bone_name) # store nif block for access from object mode self.name_to_block[b_edit_bone.name] = n_block # get the nif bone's armature space matrix (under the hood all bone space matrixes are multiplied together) n_bind = math.nifformat_to_mathutils_matrix(self.bind_store.get(n_block, NifFormat.Matrix44())) # get transformation in blender's coordinate space b_bind = math.nif_bind_to_blender_bind(n_bind) # set the bone matrix - but set the tail first to prevent issues with zero-length bone b_edit_bone.tail = mathutils.Vector([0, 0, 1]) b_edit_bone.matrix = b_bind # link to parent if b_parent_bone: b_edit_bone.parent = b_parent_bone # import and parent bone children for n_child in n_block.children: self.import_bone_bind(n_child, b_armature_data, n_armature, b_edit_bone)
def import_armature(self, n_armature): """Scans an armature hierarchy, and returns a whole armature. This is done outside the normal node tree scan to allow for positioning of the bones before skins are attached.""" armature_name = block_store.import_name(n_armature) b_armature_data = bpy.data.armatures.new(armature_name) b_armature_data.display_type = 'STICK' # use heuristics to determine a suitable orientation forward, up = self.guess_orientation(n_armature) # pass them to the matrix utility math.set_bone_orientation(forward, up) # store axis orientation for export b_armature_data.niftools.axis_forward = forward b_armature_data.niftools.axis_up = up b_armature_obj = Object.create_b_obj(n_armature, b_armature_data) b_armature_obj.show_in_front = True armature_space_bind_store, armature_space_pose_store = self.import_pose( n_armature) # make armature editable and create bones bpy.ops.object.mode_set(mode='EDIT', toggle=False) for n_child in n_armature.children: self.import_bone_bind(n_child, armature_space_bind_store, b_armature_data, n_armature) self.fix_bone_lengths(b_armature_data) bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # The armature has been created in editmode, # now we are ready to set the bone keyframes and store the bones' long names. if NifOp.props.animation: self.transform_anim.create_action(b_armature_obj, armature_name + "-Anim") for bone_name, b_bone in b_armature_obj.data.bones.items(): n_block = self.name_to_block[bone_name] # the property is only available from object mode! block_store.store_longname(b_bone, safe_decode(n_block.name)) if NifOp.props.animation: self.transform_anim.import_transforms(n_block, b_armature_obj, bone_name) # import pose for b_name, n_block in self.name_to_block.items(): n_pose = armature_space_pose_store[n_block] b_pose_bone = b_armature_obj.pose.bones[b_name] n_bind = mathutils.Matrix(n_pose.as_list()).transposed() b_pose_bone.matrix = math.nif_bind_to_blender_bind(n_bind) # force update is required to ensure the transforms are set properly in blender bpy.context.view_layer.update() return b_armature_obj
def set_object_bind(self, b_obj, b_obj_children, b_armature): """ Sets up parent-child relationships for b_obj and all its children and corrects space for children of bones""" if isinstance(b_obj, bpy.types.Object): # simple object parentship, no space correction for b_child in b_obj_children: b_child.parent = b_obj elif isinstance(b_obj, bpy.types.Bone): # Mesh attached to bone (may be rigged or static) or a collider, needs bone space correction for b_child in b_obj_children: b_child.parent = b_armature b_child.parent_type = 'BONE' b_child.parent_bone = b_obj.name # this works even for arbitrary bone orientation # note that matrix_parent_inverse is a unity matrix on import, so could be simplified further with a constant mpi = math.nif_bind_to_blender_bind( b_child.matrix_parent_inverse).inverted() mpi.translation.y -= b_obj.length # essentially we mimic a transformed matrix_parent_inverse and delegate its transform # nb. matrix local is relative to the armature object, not the bone b_child.matrix_local = mpi @ b_child.matrix_basis else: raise RuntimeError(f"Unexpected object type {b_obj.__class__:s}")