def execute(self): """Main import function.""" try: dirname = os.path.dirname(NifOp.props.filepath) kf_files = [os.path.join(dirname, file.name) for file in NifOp.props.files if file.name.lower().endswith(".kf")] # if an armature is present, prepare the bones for all actions b_armature = math.get_armature() if b_armature: # the axes used for bone correction depend on the armature in our scene math.set_bone_orientation(b_armature.data.niftools.axis_forward, b_armature.data.niftools.axis_up) # get nif space bind pose of armature here for all anims self.transform_anim.get_bind_data(b_armature) for kf_file in kf_files: kfdata = KFFile.load_kf(kf_file) self.apply_scale(kfdata, NifOp.props.scale_correction) # calculate and set frames per second self.transform_anim.set_frames_per_second(kfdata.roots) for kf_root in kfdata.roots: self.transform_anim.import_kf_root(kf_root, b_armature) except NifError: return {'CANCELLED'} NifLog.info("Finished successfully") return {'FINISHED'}
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 execute(self): """Main export function.""" NifLog.info(f"Exporting {NifOp.props.filepath}") # extract directory, base name, extension directory = os.path.dirname(NifOp.props.filepath) filebase, fileext = os.path.splitext( os.path.basename(NifOp.props.filepath)) if bpy.context.scene.niftools_scene.game == 'NONE': raise NifError( "You have not selected a game. Please select a game in the scene tab." ) prefix = "x" if bpy.context.scene.niftools_scene.game in ( 'MORROWIND', ) else "" self.version, data = scene.get_version_data() # todo[anim] - change to KfData, but create_controller() [and maybe more] has to be updated first NifData.init(data) b_armature = math.get_armature() # some scenes may not have an armature, so nothing to do here if b_armature: math.set_bone_orientation(b_armature.data.niftools.axis_forward, b_armature.data.niftools.axis_up) NifLog.info("Creating keyframe tree") kf_root = self.transform_anim.export_kf_root(b_armature) # write kf (and xkf if asked) ext = ".kf" NifLog.info(f"Writing {prefix}{ext} file") data.roots = [kf_root] data.neosteam = (bpy.context.scene.niftools_scene.game == 'NEOSTEAM') # scale correction for the skeleton self.apply_scale(data, round(1 / NifOp.props.scale_correction)) kffile = os.path.join(directory, prefix + filebase + ext) with open(kffile, "wb") as stream: data.write(stream) NifLog.info("Finished successfully") return {'FINISHED'}
def execute(self): """Main import function.""" try: dirname = os.path.dirname(NifOp.props.filepath) kf_files = [ os.path.join(dirname, file.name) for file in NifOp.props.files if file.name.lower().endswith(".kf") ] b_armature = math.get_armature() if not b_armature: raise NifError( "No armature was found in scene, can not import KF animation!" ) # the axes used for bone correction depend on the armature in our scene math.set_bone_orientation(b_armature.data.niftools.axis_forward, b_armature.data.niftools.axis_up) # get nif space bind pose of armature here for all anims bind_data = armature.get_bind_data(b_armature) for kf_file in kf_files: kfdata = KFFile.load_kf(kf_file) # use pyffi toaster to scale the tree toaster = pyffi.spells.nif.NifToaster() toaster.scale = NifOp.props.scale_correction pyffi.spells.nif.fix.SpellScale(data=kfdata, toaster=toaster).recurse() # calculate and set frames per second self.tranform_anim.set_frames_per_second(kfdata.roots) for kf_root in kfdata.roots: self.tranform_anim.import_kf_root(kf_root, b_armature, bind_data) except NifError: return {'CANCELLED'} NifLog.info("Finished successfully") return {'FINISHED'}
def execute(self): """Main export function.""" if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT', toggle=False) NifLog.info(f"Exporting {NifOp.props.filepath}") # extract directory, base name, extension directory = os.path.dirname(NifOp.props.filepath) filebase, fileext = os.path.splitext( os.path.basename(NifOp.props.filepath)) block_store.block_to_obj = {} # clear out previous iteration try: # catch export errors # protect against null nif versions if bpy.context.scene.niftools_scene.game == 'NONE': raise NifError( "You have not selected a game. Please select a game and" " nif version in the scene tab.") # find all objects that do not have a parent self.exportable_objects, self.root_objects = self.objecthelper.get_export_objects( ) if not self.exportable_objects: NifLog.warn("No objects can be exported!") return {'FINISHED'} for b_obj in self.exportable_objects: if b_obj.type == 'MESH': if b_obj.parent and b_obj.parent.type == 'ARMATURE': for b_mod in b_obj.modifiers: if b_mod.type == 'ARMATURE' and b_mod.use_bone_envelopes: raise NifError( f"'{b_obj.name}': Cannot export envelope skinning. If you have vertex groups, turn off envelopes.\n" f"If you don't have vertex groups, select the bones one by one press W to " f"convert their envelopes to vertex weights, and turn off envelopes." ) # check for non-uniform transforms scale = b_obj.scale if abs(scale.x - scale.y) > NifOp.props.epsilon or abs( scale.y - scale.z) > NifOp.props.epsilon: NifLog.warn( f"Non-uniform scaling not supported.\n" f"Workaround: apply size and rotation (CTRL-A) on '{b_obj.name}'." ) b_armature = math.get_armature() # some scenes may not have an armature, so nothing to do here if b_armature: math.set_bone_orientation( b_armature.data.niftools.axis_forward, b_armature.data.niftools.axis_up) prefix = "x" if bpy.context.scene.niftools_scene.game in ( 'MORROWIND', ) else "" NifLog.info("Exporting") if NifOp.props.animation == 'ALL_NIF': NifLog.info("Exporting geometry and animation") elif NifOp.props.animation == 'GEOM_NIF': # for morrowind: everything except keyframe controllers NifLog.info("Exporting geometry only") # find nif version to write self.version, data = scene.get_version_data() NifData.init(data) # export the actual root node (the name is fixed later to avoid confusing the exporter with duplicate names) root_block = self.objecthelper.export_root_node( self.root_objects, filebase) # post-processing: # ---------------- NifLog.info("Checking controllers") if bpy.context.scene.niftools_scene.game == 'MORROWIND': # animations without keyframe animations crash the TESCS # if we are in that situation, add a trivial keyframe animation has_keyframecontrollers = False for block in block_store.block_to_obj: if isinstance(block, NifFormat.NiKeyframeController): has_keyframecontrollers = True break if (not has_keyframecontrollers) and ( not NifOp.props.bs_animation_node): NifLog.info("Defining dummy keyframe controller") # add a trivial keyframe controller on the scene root self.transform_anim.create_controller( root_block, root_block.name) if NifOp.props.bs_animation_node: for block in block_store.block_to_obj: if isinstance(block, NifFormat.NiNode): # if any of the shape children has a controller or if the ninode has a controller convert its type if block.controller or any( child.controller for child in block.children if isinstance( child, NifFormat.NiGeometry)): new_block = NifFormat.NiBSAnimationNode( ).deepcopy(block) # have to change flags to 42 to make it work new_block.flags = 42 root_block.replace_global_node( block, new_block) if root_block is block: root_block = new_block # oblivion skeleton export: check that all bones have a transform controller and transform interpolator if bpy.context.scene.niftools_scene.game in ( 'OBLIVION', 'FALLOUT_3', 'SKYRIM') and filebase.lower() in ('skeleton', 'skeletonbeast'): self.transform_anim.add_dummy_controllers() # bhkConvexVerticesShape of children of bhkListShapes need an extra bhkConvexTransformShape (see issue #3308638, reported by Koniption) # note: block_store.block_to_obj changes during iteration, so need list copy for block in list(block_store.block_to_obj.keys()): if isinstance(block, NifFormat.bhkListShape): for i, sub_shape in enumerate(block.sub_shapes): if isinstance(sub_shape, NifFormat.bhkConvexVerticesShape): coltf = block_store.create_block( "bhkConvexTransformShape") coltf.material = sub_shape.material coltf.unknown_float_1 = 0.1 unk_8 = coltf.unknown_8_bytes unk_8[0] = 96 unk_8[1] = 120 unk_8[2] = 53 unk_8[3] = 19 unk_8[4] = 24 unk_8[5] = 9 unk_8[6] = 253 unk_8[7] = 4 coltf.transform.set_identity() coltf.shape = sub_shape block.sub_shapes[i] = coltf # export constraints for b_obj in self.exportable_objects: if b_obj.constraints: self.constrainthelper.export_constraints(b_obj, root_block) object_prop = ObjectProperty() object_prop.export_root_node_properties(root_block) # FIXME: """ if self.EXPORT_FLATTENSKIN: # (warning: trouble if armatures parent other armatures or # if bones parent geometries, or if object is animated) # flatten skins skelroots = set() affectedbones = [] for block in block_store.block_to_obj: if isinstance(block, NifFormat.NiGeometry) and block.is_skin(): NifLog.info("Flattening skin on geometry {0}".format(block.name)) affectedbones.extend(block.flatten_skin()) skelroots.add(block.skin_instance.skeleton_root) # remove NiNodes that do not affect skin for skelroot in skelroots: NifLog.info("Removing unused NiNodes in '{0}'".format(skelroot.name)) skelrootchildren = [child for child in skelroot.children if ((not isinstance(child, NifFormat.NiNode)) or (child in affectedbones))] skelroot.num_children = len(skelrootchildren) skelroot.children.update_size() for i, child in enumerate(skelrootchildren): skelroot.children[i] = child """ # apply scale data.roots = [root_block] scale_correction = bpy.context.scene.niftools_scene.scale_correction if abs(scale_correction) > NifOp.props.epsilon: self.apply_scale(data, round(1 / NifOp.props.scale_correction)) # also scale egm if EGMData.data: EGMData.data.apply_scale(1 / scale_correction) # generate mopps (must be done after applying scale!) if bpy.context.scene.niftools_scene.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): for block in block_store.block_to_obj: if isinstance(block, NifFormat.bhkMoppBvTreeShape): NifLog.info("Generating mopp...") block.update_mopp() # print "=== DEBUG: MOPP TREE ===" # block.parse_mopp(verbose = True) # print "=== END OF MOPP TREE ===" # warn about mopps on non-static objects if any(sub_shape.layer != 1 for sub_shape in block.shape.sub_shapes): NifLog.warn( "Mopps for non-static objects may not function correctly in-game. You may wish to use simple primitives for collision." ) # export nif file: # ---------------- if bpy.context.scene.niftools_scene.game == 'EMPIRE_EARTH_II': ext = ".nifcache" else: ext = ".nif" NifLog.info(f"Writing {ext} file") # make sure we have the right file extension if fileext.lower() != ext: NifLog.warn( f"Changing extension from {fileext} to {ext} on output file" ) niffile = os.path.join(directory, prefix + filebase + ext) data.roots = [root_block] # todo [export] I believe this is redundant and setting modification only is the current way? data.neosteam = ( bpy.context.scene.niftools_scene.game == 'NEOSTEAM') if bpy.context.scene.niftools_scene.game == 'NEOSTEAM': data.modification = "neosteam" elif bpy.context.scene.niftools_scene.game == 'ATLANTICA': data.modification = "ndoors" elif bpy.context.scene.niftools_scene.game == 'HOWLING_SWORD': data.modification = "jmihs1" with open(niffile, "wb") as stream: data.write(stream) # export egm file: # ----------------- if EGMData.data: ext = ".egm" NifLog.info(f"Writing {ext} file") egmfile = os.path.join(directory, filebase + ext) with open(egmfile, "wb") as stream: EGMData.data.write(stream) # save exported file (this is used by the test suite) self.root_blocks = [root_block] except NifError: return {'CANCELLED'} NifLog.info("Finished") return {'FINISHED'}