def import_kf_standalone(self, kf_root, b_armature_obj, bind_data): """ Import a kf animation. Needs a suitable armature in blender scene. """ NifLog.info("Importing KF tree") # check that this is an Oblivion style kf file if not isinstance(kf_root, NifFormat.NiControllerSequence): raise nif_utils.NifError("non-Oblivion .kf import not supported") # import text keys self.import_text_keys(kf_root) self.create_action(b_armature_obj, kf_root.name.decode()) # go over all controlled blocks (NiKeyframeController) for controlledblock in kf_root.controlled_blocks: # nb: this yielded just an empty bytestring # nodename = controlledblock.get_node_name() kfc = controlledblock.controller bone_name = armature.get_bone_name_for_blender( controlledblock.target_name) if bone_name in bind_data: niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans = bind_data[ bone_name] self.armature_animation.import_keyframe_controller( kfc, b_armature_obj, bone_name, niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans)
def export_flip_controller(self, fliptxt, texture, target, target_tex): ## TODO:port code to use native Blender texture flipping system # # export a NiFlipController # # fliptxt is a blender text object containing the flip definitions # texture is the texture object in blender ( texture is used to checked for pack and mipmap flags ) # target is the NiTexturingProperty # target_tex is the texture to flip ( 0 = base texture, 4 = glow texture ) # # returns exported NiFlipController # tlist = fliptxt.asLines() # create a NiFlipController flip = self.nif_export.objecthelper.create_block( "NiFlipController", fliptxt) target.add_controller(flip) # fill in NiFlipController's values flip.flags = 8 # active flip.frequency = 1.0 flip.start_time = (bpy.context.scene.frame_start - 1) * bpy.context.scene.render.fps flip.stop_time = ( bpy.context.scene.frame_end - bpy.context.scene.frame_start) * bpy.context.scene.render.fps flip.texture_slot = target_tex count = 0 for t in tlist: if len(t) == 0: continue # skip empty lines # create a NiSourceTexture for each flip tex = self.nif_export.texturehelper.texture_writer.export_source_texture( texture, t) flip.num_sources += 1 flip.sources.update_size() flip.sources[flip.num_sources - 1] = tex count += 1 if count < 2: raise nif_utils.NifError("Error in Texture Flip buffer '%s':" " must define at least two textures" % fliptxt.name) flip.delta = (flip.stop_time - flip.start_time) / count
def export_collision(self, b_obj, parent_block): """Main function for adding collision object b_obj to a node.""" if NifOp.props.game == 'MORROWIND': if b_obj.game.collision_bounds_type != 'TRIANGLE_MESH': raise nif_utils.NifError( "Morrowind only supports Triangle Mesh collisions.") node = self.objecthelper.create_block("RootCollisionNode", b_obj) parent_block.add_child(node) node.flags = 0x0003 # default self.objecthelper.set_object_matrix(b_obj, node) for child in b_obj.children: self.objecthelper.export_node(child, node, None) elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): nodes = [parent_block] nodes.extend([ block for block in parent_block.children if block.name[:14] == 'collisiondummy' ]) for node in nodes: try: self.bhkshapehelper.export_collision_helper(b_obj, node) break except ValueError: # adding collision failed continue else: # all nodes failed so add new one node = self.objecthelper.create_ninode(b_obj) node.set_transform(self.IDENTITY44) node.name = 'collisiondummy%i' % parent_block.num_children if b_obj.niftools.objectflags != 0: node_flag_hex = hex(b_obj.niftools.objectflags) else: node_flag_hex = 0x000E # default node.flags = node_flag_hex parent_block.add_child(node) self.bhkshapehelper.export_collision_helper(b_obj, node) else: NifLog.warn( "Only Morrowind, Oblivion, and Fallout 3 collisions are supported, skipped collision object '{0}'" .format(b_obj.name))
def execute(self): """Main import function.""" 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 = armature.get_armature() if not b_armature: raise nif_utils.NifError("No armature was found in scene, can not import KF animation!") #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) #the axes used for bone correction depend on the nif version armature.set_bone_correction_from_version(kfdata.version) # use pyffi toaster to scale the tree toaster = pyffi.spells.nif.NifToaster() toaster.scale = NifOp.props.scale_correction_import pyffi.spells.nif.fix.SpellScale(data=kfdata, toaster=toaster).recurse() # calculate and set frames per second self.animationhelper.set_frames_per_second( kfdata.roots ) for kf_root in kfdata.roots: self.animationhelper.import_kf_standalone( kf_root, b_armature, bind_data ) return {'FINISHED'}
def export_text_keys( self, block_parent, ): """Parse the animation groups buffer and write an extra string data block, and attach it to an existing block (typically, the root of the nif tree).""" if NifOp.props.animation == 'GEOM_NIF': # animation group extra data is not present in geometry only files return if "Anim" not in bpy.data.texts: return animtxt = bpy.data.texts["Anim"] NifLog.info("Exporting animation groups") # -> get animation groups information # parse the anim text descriptor # the format is: # frame/string1[/string2[.../stringN]] # example: # 001/Idle: Start/Idle: Stop/Idle2: Start/Idle2: Loop Start # 051/Idle2: Stop/Idle3: Start # 101/Idle3: Loop Start/Idle3: Stop slist = animtxt.asLines() flist = [] dlist = [] for s in slist: # ignore empty lines if not s: continue # parse line t = s.split('/') if (len(t) < 2): raise nif_utils.NifError("Syntax error in Anim buffer ('%s')" % s) f = int(t[0]) if ((f < bpy.context.scene.frame_start) or (f > bpy.context.scene.frame_end)): NifLog.warn( "Frame in animation buffer out of range ({0} not between [{1}, {2}])" .format(str(f), str(bpy.context.scene.frame_start), str(bpy.context.scene.frame_end))) d = t[1].strip() for i in range(2, len(t)): d = d + '\r\n' + t[i].strip() #print 'frame %d'%f + ' -> \'%s\''%d # debug flist.append(f) dlist.append(d) # -> now comes the real export # add a NiTextKeyExtraData block, and refer to this block in the # parent node (we choose the root block) textextra = self.nif_export.objecthelper.create_block( "NiTextKeyExtraData", animtxt) block_parent.add_extra_data(textextra) # create a text key for each frame descriptor textextra.num_text_keys = len(flist) textextra.text_keys.update_size() for i, key in enumerate(textextra.text_keys): key.time = flist[i] / self.fps key.value = dlist[i] return textextra
def export_keyframes(self, parent_block, b_obj=None, bone=None): """ If called on b_obj=None and bone=None it should save an empty controller. If called on an b_obj = type(armature), it expects a bone too. If called on an object, with bone=None, it exports object level animation. """ # sometimes we need to export an empty keyframe... scale_curve = [] quat_curve = [] euler_curve = [] trans_curve = [] exp_fcurves = [] #just for more detailed error reporting later on bonestr = "" #we have either skeletal or object animation if b_obj and b_obj.animation_data and b_obj.animation_data.action: action = b_obj.animation_data.action #skeletal animation - with bone correction & coordinate corrections if bone and bone.name in action.groups: # get bind matrix for bone or object bind_matrix = self.nif_export.objecthelper.get_object_bind( bone) exp_fcurves = action.groups[bone.name].channels #just for more detailed error reporting later on bonestr = " in bone " + bone.name #object level animation - no coordinate corrections elif not bone: # raise error on any objects parented to bones if b_obj.parent and b_obj.parent_type == "BONE": raise nif_utils.NifError( b_obj.name + " is parented to a bone AND has animations. The nif format does not support this!" ) # we have either a root object (Scene Root), in which case we take the coordinates without modification # or a generic object parented to an empty = node # objects may have an offset from their parent that is not apparent in the user input (ie. UI values and keyframes) # we want to export matrix_local, and the keyframes are in matrix_basis, so do: # matrix_local = matrix_parent_inverse * matrix_basis bind_matrix = b_obj.matrix_parent_inverse exp_fcurves = [ fcu for fcu in action.fcurves if fcu.data_path in ("rotation_quaternion", "rotation_euler", "location", "scale") ] # decompose the bind matrix if exp_fcurves: bind_scale, bind_rot, bind_trans = nif_utils.decompose_srt( bind_matrix) bind_rot = bind_rot.to_4x4() start_frame, stop_frame = action.frame_range # we are supposed to export an empty controller else: # only set frame range start_frame = bpy.context.scene.frame_start stop_frame = bpy.context.scene.frame_end if NifOp.props.animation == 'GEOM_NIF' and self.nif_export.version < 0x0A020000: # keyframe controllers are not present in geometry only files # for more recent versions, the controller and interpolators are # present, only the data is not present (see further on) return # add a keyframecontroller block, and refer to this block in the # parent's time controller if self.nif_export.version < 0x0A020000: kfc = self.nif_export.objecthelper.create_block( "NiKeyframeController", exp_fcurves) else: kfc = self.nif_export.objecthelper.create_block( "NiTransformController", exp_fcurves) kfi = self.nif_export.objecthelper.create_block( "NiTransformInterpolator", exp_fcurves) # link interpolator from the controller kfc.interpolator = kfi # set interpolator default data kfi.scale, kfi.rotation, kfi.translation = \ parent_block.get_transform().get_scale_quat_translation() #if parent is a node, attach controller to that node if isinstance(parent_block, NifFormat.NiNode): parent_block.add_controller(kfc) #else ControllerSequence, so create a link elif isinstance(parent_block, NifFormat.NiControllerSequence): controlledblock = parent_block.add_controlled_block() if self.nif_export.version < 0x0A020000: # older versions need the actual controller blocks controlledblock.target_name = armature.get_bone_name_for_nif( bone.name) controlledblock.controller = kfc # erase reference to target node kfc.target = None else: # newer versions need the interpolator blocks controlledblock.interpolator = kfi else: raise nif_utils.NifError("Unsupported KeyframeController parent!") # fill in the non-trivial values self.set_flags_and_timing(kfc, exp_fcurves, start_frame, stop_frame) if NifOp.props.animation == 'GEOM_NIF': # keyframe data is not present in geometry files return # get the desired fcurves for each data type from exp_fcurves quaternions = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("quaternion") ] translations = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("location") ] eulers = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("euler") ] scales = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("scale") ] # go over all fcurves collected above and transform and store all keys if scales: #just use the first scale curve and assume even scale over all curves for frame, scale in self.iter_frame_key(scales, mathutils.Vector): scale_curve.append((frame, scale[0])) if quaternions: if len(quaternions) != 4: raise nif_utils.NifError("Incomplete ROT key set" + bonestr + " for action " + action.name) else: for frame, quat in self.iter_frame_key(quaternions, mathutils.Quaternion): quat = armature.export_keymat(bind_rot, quat.to_matrix().to_4x4(), bone).to_quaternion() quat_curve.append((frame, quat)) if eulers: if len(eulers) != 3: raise nif_utils.NifError("Incomplete Euler key set" + bonestr + " for action " + action.name) else: for frame, euler in self.iter_frame_key( eulers, mathutils.Euler): euler = armature.export_keymat(bind_rot, euler.to_matrix().to_4x4(), bone).to_euler( "XYZ", euler) euler_curve.append((frame, euler)) if translations: if len(translations) != 3: raise nif_utils.NifError("Incomplete LOC key set" + bonestr + " for action " + action.name) else: for frame, trans in self.iter_frame_key( translations, mathutils.Vector): trans = armature.export_keymat( bind_rot, mathutils.Matrix.Translation(trans), bone).to_translation() + bind_trans trans_curve.append((frame, trans)) # finally we can export the data calculated above if (max(len(quat_curve), len(euler_curve), len(trans_curve), len(scale_curve)) <= 1 and self.nif_export.version >= 0x0A020000): # only add data if number of keys is > 1 # (see importer comments with import_kf_root: a single frame # keyframe denotes an interpolator without further data) # insufficient keys, so set the data and we're done! if trans_curve: trans = trans_curve[0][1] kfi.translation.x = trans[0] kfi.translation.y = trans[1] kfi.translation.z = trans[2] if quat_curve: quat = quat_curve[0][1] kfi.rotation.x = quat.x kfi.rotation.y = quat.y kfi.rotation.z = quat.z kfi.rotation.w = quat.w elif euler_curve: quat = euler_curve[0][1].to_quaternion() kfi.rotation.x = quat.x kfi.rotation.y = quat.y kfi.rotation.z = quat.z kfi.rotation.w = quat.w # ignore scale for now... kfi.scale = 1.0 # done! return # add the keyframe data if self.nif_export.version < 0x0A020000: kfd = self.nif_export.objecthelper.create_block( "NiKeyframeData", exp_fcurves) kfc.data = kfd else: # number of frames is > 1, so add transform data kfd = self.nif_export.objecthelper.create_block( "NiTransformData", exp_fcurves) kfi.data = kfd ### TODO [animation] support other interpolation modes, get interpolation from blender? ### probably requires additional data like tangents and stuff # save all nif keys if euler_curve: kfd.rotation_type = NifFormat.KeyType.XYZ_ROTATION_KEY kfd.num_rotation_keys = 1 # *NOT* len(frames) this crashes the engine! for i, coord in enumerate(kfd.xyz_rotations): coord.num_keys = len(euler_curve) coord.interpolation = NifFormat.KeyType.LINEAR_KEY coord.keys.update_size() for key, (frame, euler) in zip(coord.keys, euler_curve): key.time = frame / self.fps key.value = euler[i] elif quat_curve: kfd.rotation_type = NifFormat.KeyType.LINEAR_KEY kfd.num_rotation_keys = len(quat_curve) kfd.quaternion_keys.update_size() for key, (frame, quat) in zip(kfd.quaternion_keys, quat_curve): key.time = frame / self.fps key.value.w = quat.w key.value.x = quat.x key.value.y = quat.y key.value.z = quat.z kfd.translations.interpolation = NifFormat.KeyType.LINEAR_KEY kfd.translations.num_keys = len(trans_curve) kfd.translations.keys.update_size() for key, (frame, trans) in zip(kfd.translations.keys, trans_curve): key.time = frame / self.fps key.value.x, key.value.y, key.value.z = trans kfd.scales.interpolation = NifFormat.KeyType.LINEAR_KEY kfd.scales.num_keys = len(scale_curve) kfd.scales.keys.update_size() for key, (frame, scale) in zip(kfd.scales.keys, scale_curve): key.time = frame / self.fps key.value = scale
def mark_armatures_bones(self, niBlock): """Mark armatures and bones by peeking into NiSkinInstance blocks.""" # case where we import skeleton only, # or importing an Oblivion or Fallout 3 skeleton: # do all NiNode's as bones if NifOp.props.skeleton == "SKELETON_ONLY" or ( self.nif_import.data.version in (0x14000005, 0x14020007) and (os.path.basename(NifOp.props.filepath).lower() in ('skeleton.nif', 'skeletonbeast.nif'))): if not isinstance(niBlock, NifFormat.NiNode): raise nif_utils.NifError( "cannot import skeleton: root is not a NiNode") # for morrowind, take the Bip01 node to be the skeleton root if self.nif_import.data.version == 0x04000002: skelroot = niBlock.find(block_name='Bip01', block_type=NifFormat.NiNode) if not skelroot: skelroot = niBlock else: skelroot = niBlock if skelroot not in self.nif_import.dict_armatures: self.nif_import.dict_armatures[skelroot] = [] NifLog.info("Selecting node '%s' as skeleton root".format( skelroot.name)) # add bones for bone in skelroot.tree(): if bone is skelroot: continue if not isinstance(bone, NifFormat.NiNode): continue if self.nif_import.is_grouping_node(bone): continue if bone not in self.nif_import.dict_armatures[skelroot]: self.nif_import.dict_armatures[skelroot].append(bone) return # done! # attaching to selected armature -> first identify armature and bones elif NifOp.props.skeleton == "GEOMETRY_ONLY" and not self.nif_import.dict_armatures: skelroot = niBlock.find( block_name=self.nif_import.selected_objects[0].name) if not skelroot: raise nif_utils.NifError( "nif has no armature '%s'" % self.nif_import.selected_objects[0].name) NifLog.debug("Identified '{0}' as armature".format(skelroot.name)) self.nif_import.dict_armatures[skelroot] = [] for bone_name in self.nif_import.selected_objects[ 0].data.bones.keys(): # blender bone naming -> nif bone naming nif_bone_name = armature.get_bone_name_for_nif(bone_name) # find a block with bone name bone_block = skelroot.find(block_name=nif_bone_name) # add it to the name list if there is a bone with that name if bone_block: NifLog.info( "Identified nif block '{0}' with bone '{1}' in selected armature" .format(nif_bone_name, bone_name)) self.nif_import.dict_names[bone_block] = bone_name self.nif_import.dict_armatures[skelroot].append(bone_block) self.complete_bone_tree(bone_block, skelroot) # search for all NiTriShape or NiTriStrips blocks... if isinstance(niBlock, NifFormat.NiTriBasedGeom): # yes, we found one, get its skin instance if niBlock.is_skin(): NifLog.debug("Skin found on block '{0}'".format(niBlock.name)) # it has a skin instance, so get the skeleton root # which is an armature only if it's not a skinning influence # so mark the node to be imported as an armature skininst = niBlock.skin_instance skelroot = skininst.skeleton_root if NifOp.props.skeleton == "EVERYTHING": if skelroot not in self.nif_import.dict_armatures: self.nif_import.dict_armatures[skelroot] = [] NifLog.debug("'{0}' is an armature".format( skelroot.name)) elif NifOp.props.skeleton == "GEOMETRY_ONLY": if skelroot not in self.nif_import.dict_armatures: raise nif_utils.NifError( "nif structure incompatible with '%s' as armature:" " node '%s' has '%s' as armature" % (self.nif_import.selected_objects[0].name, niBlock.name, skelroot.name)) for boneBlock in skininst.bones: # boneBlock can be None; see pyffi issue #3114079 if not boneBlock: continue if boneBlock not in self.nif_import.dict_armatures[ skelroot]: self.nif_import.dict_armatures[skelroot].append( boneBlock) NifLog.debug( "'{0}' is a bone of armature '{1}'".format( boneBlock.name, skelroot.name)) # now we "attach" the bone to the armature: # we make sure all NiNodes from this bone all the way # down to the armature NiNode are marked as bones self.complete_bone_tree(boneBlock, skelroot) # mark all nodes as bones if asked if self.nif_import.IMPORT_EXTRANODES: # add bones for bone in skelroot.tree(): if bone is skelroot: continue if not isinstance(bone, NifFormat.NiNode): continue if isinstance(bone, NifFormat.NiLODNode): # LOD nodes are never bones continue if self.nif_import.is_grouping_node(bone): continue if bone not in self.nif_import.dict_armatures[ skelroot]: self.nif_import.dict_armatures[skelroot].append( bone) NifLog.debug( "'{0}' marked as extra bone of armature '{1}'". format(bone.name, skelroot.name)) # we make sure all NiNodes from this bone # all the way down to the armature NiNode # are marked as bones self.complete_bone_tree(bone, skelroot) # continue down the tree for child in niBlock.get_refs(): if not isinstance(child, NifFormat.NiAVObject): continue # skip blocks that don't have transforms self.mark_armatures_bones(child)
def execute(self): """Main export function.""" if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT', toggle=False) NifLog.info("Exporting {0}".format(NifOp.props.filepath)) # TODO: ''' if NifOp.props.animation == 'ALL_NIF_XNIF_XKF' and NifOp.props.game == 'MORROWIND': # if exporting in nif+xnif+kf mode, then first export # the nif with geometry + animation, which is done by: NifOp.props.animation = 'ALL_NIF' ''' # extract directory, base name, extension directory = os.path.dirname(NifOp.props.filepath) filebase, fileext = os.path.splitext( os.path.basename(NifOp.props.filepath)) self.dict_armatures = {} self.dict_bone_priorities = {} self.dict_havok_objects = {} self.dict_names = {} self.dict_blocks = {} self.dict_block_names = [] self.dict_materials = {} self.dict_textures = {} self.dict_mesh_uvlayers = [] # if an egm is exported, this will contain the data self.egm_data = None try: # catch export errors for b_obj in bpy.data.objects: # armatures should not be in rest position if b_obj.type == 'ARMATURE': # ensure we get the mesh vertices in animation mode, # and not in rest position! b_obj.data.pose_position = 'POSE' if b_obj.type == 'MESH': # TODO - Need to implement correct armature checking # b_armature_modifier = None b_obj_name = b_obj.name if b_obj.parent: for b_mod in bpy.data.objects[b_obj_name].modifiers: if b_mod.type == 'ARMATURE': # b_armature_modifier = b_mod.name if b_mod.use_bone_envelopes: raise nif_utils.NifError( "'%s': Cannot export envelope skinning. If you have vertex groups, turn off envelopes.\n" "If you don't have vertex groups, select the bones one by one press W to " "convert their envelopes to vertex weights, and turn off envelopes." % b_obj.name) # if not b_armature_modifier: # raise nif_utils.NifError("'%s': is parented but does not have" # " the armature modifier set. This will" # " cause animations to fail." # % b_obj.name) # check for non-uniform transforms # (lattices are not exported so ignore them as they often tend # to have non-uniform scaling) if b_obj.type != 'LATTICE': scale = b_obj.matrix_local.to_scale() if abs(scale.x - scale.y) > NifOp.props.epsilon or abs( scale.y - scale.z) > NifOp.props.epsilon: raise nif_utils.NifError( "Non-uniform scaling not supported.\n " "Workaround: apply size and rotation (CTRL-A) on '%s'." % b_obj.name) root_name = filebase # get the root object from selected object # only export empties, meshes, and armatures if not bpy.context.selected_objects: raise nif_utils.NifError( "Please select the object(s) to export, and run this script again." ) root_objects = set() export_types = ('EMPTY', 'MESH', 'ARMATURE') exportable_objects = [ b_obj for b_obj in bpy.context.selected_objects if b_obj.type in export_types ] for root_object in exportable_objects: while root_object.parent: root_object = root_object.parent if NifOp.props.game in ('CIVILIZATION_IV', 'OBLIVION', 'FALLOUT_3', 'ZOO_TYCOON_2'): if (root_object.type == 'ARMATURE') or (root_object.name.lower() == "bip01"): root_name = 'Scene Root' # TODO remove as already filtered if root_object.type not in export_types: raise nif_utils.NifError( "Root object (%s) must be an 'EMPTY', 'MESH', or 'ARMATURE' object." % root_object.name) root_objects.add(root_object) # smooth seams of objects if NifOp.props.smooth_object_seams: self.objecthelper.mesh_helper.smooth_mesh_seams( bpy.context.scene.objects) # TODO: use Blender actions for animation groups # check for animation groups definition in a text buffer 'Anim' try: animtxt = None # Changed for testing needs fix bpy.data.texts["Anim"] except NameError: animtxt = None # rebuild the full name dictionary from the 'FullNames' text buffer self.objecthelper.rebuild_full_names() # export nif: # ----------- NifLog.info("Exporting") # find nif version to write # TODO Move fully to scene level self.version = NifOp.op.version[NifOp.props.game] self.user_version, self.user_version_2 = scene_export.get_version_info( NifOp.props) #the axes used for bone correction depend on the nif version armature.set_bone_correction_from_version(self.version) # create a nif object # export the root node (the name is fixed later to avoid confusing the # exporter with duplicate names) root_block = self.objecthelper.export_node(None, None, '') # TODO Move to object system and redo # export objects NifLog.info("Exporting objects") for root_object in root_objects: if NifOp.props.game in 'SKYRIM': if root_object.niftools_bs_invmarker: for extra_item in root_block.extra_data_list: if isinstance(extra_item, NifFormat.BSInvMarker): raise nif_utils.NifError( "Multiple Items have Inventory marker data only one item may contain this data" ) else: n_extra_list = NifFormat.BSInvMarker() n_extra_list.name = root_object.niftools_bs_invmarker[ 0].name.encode() n_extra_list.rotation_x = root_object.niftools_bs_invmarker[ 0].bs_inv_x n_extra_list.rotation_y = root_object.niftools_bs_invmarker[ 0].bs_inv_y n_extra_list.rotation_z = root_object.niftools_bs_invmarker[ 0].bs_inv_z n_extra_list.zoom = root_object.niftools_bs_invmarker[ 0].bs_inv_zoom root_block.add_extra_data(n_extra_list) # export the root objects as a NiNodes; their children are # exported as well self.objecthelper.export_node(root_object, root_block, root_object.name) # post-processing: # ---------------- # if we exported animations, but no animation groups are defined, # define a default animation group NifLog.info("Checking animation groups") if not animtxt: has_controllers = False for block in self.dict_blocks: # has it a controller field? if isinstance(block, NifFormat.NiObjectNET): if block.controller: has_controllers = True break if has_controllers: NifLog.info("Defining default animation group.") # write the animation group text buffer animtxt = bpy.data.texts.new("Anim") animtxt.write( "%i/Idle: Start/Idle: Loop Start\n%i/Idle: Loop Stop/Idle: Stop" % (bpy.context.scene.frame_start, bpy.context.scene.frame_end)) # animations without keyframe animations crash the TESCS # if we are in that situation, add a trivial keyframe animation NifLog.info("Checking controllers") if animtxt and NifOp.props.game == 'MORROWIND': has_keyframecontrollers = False for block in self.dict_blocks: 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.animationhelper.export_keyframes(root_block) if NifOp.props.bs_animation_node and NifOp.props.game == 'MORROWIND': for block in self.dict_blocks: 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 NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM') and filebase.lower() in ( 'skeleton', 'skeletonbeast'): # here comes everything that is Oblivion skeleton export specific NifLog.info( "Adding controllers and interpolators for skeleton") for block in list(self.dict_blocks.keys()): if isinstance(block, NifFormat.NiNode ) and block.name.decode() == "Bip01": for bone in block.tree(block_type=NifFormat.NiNode): ctrl = self.objecthelper.create_block( "NiTransformController") interp = self.objecthelper.create_block( "NiTransformInterpolator") ctrl.interpolator = interp bone.add_controller(ctrl) ctrl.flags = 12 ctrl.frequency = 1.0 ctrl.phase = 0.0 ctrl.start_time = self.FLOAT_MAX ctrl.stop_time = self.FLOAT_MIN interp.translation.x = bone.translation.x interp.translation.y = bone.translation.y interp.translation.z = bone.translation.z scale, quat = bone.rotation.get_scale_quat() interp.rotation.x = quat.x interp.rotation.y = quat.y interp.rotation.z = quat.z interp.rotation.w = quat.w interp.scale = bone.scale else: # here comes everything that should be exported EXCEPT # for Oblivion skeleton exports # export animation groups (not for skeleton.nif export!) if animtxt: # TODO: removed temorarily to process bseffectshader export anim_textextra = None # self.animationhelper.export_text_keys(root_block) else: anim_textextra = None # oblivion and Fallout 3 furniture markers if NifOp.props.game in ( 'OBLIVION', 'FALLOUT_3', 'SKYRIM') and filebase[:15].lower() == 'furnituremarker': # exporting a furniture marker for Oblivion/FO3 try: furniturenumber = int(filebase[15:]) except ValueError: raise nif_utils.NifError( "Furniture marker has invalid number (%s).\n" "Name your file 'furnituremarkerxx.nif' where xx is a number between 00 and 19." % filebase[15:]) # name scene root name the file base name root_name = filebase # create furniture marker block furnmark = self.objecthelper.create_block("BSFurnitureMarker") furnmark.name = "FRN" furnmark.num_positions = 1 furnmark.positions.update_size() furnmark.positions[0].position_ref_1 = furniturenumber furnmark.positions[0].position_ref_2 = furniturenumber # create extra string data sgoKeep sgokeep = self.objecthelper.create_block("NiStringExtraData") sgokeep.name = "UPB" # user property buffer sgokeep.string_data = "sgoKeep=1 ExportSel = Yes" # Unyielding = 0, sgoKeep=1ExportSel = Yes # add extra blocks root_block.add_extra_data(furnmark) root_block.add_extra_data(sgokeep) # FIXME: NifLog.info("Checking collision") # activate oblivion/Fallout 3 collision and physics if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): hascollision = False for b_obj in bpy.data.objects: if b_obj.game.use_collision_bounds: hascollision = True break if hascollision: # enable collision bsx = self.objecthelper.create_block("BSXFlags") bsx.name = 'BSX' bsx.integer_data = b_obj.niftools.bsxflags root_block.add_extra_data(bsx) # many Oblivion nifs have a UPB, but export is disabled as # they do not seem to affect anything in the game if b_obj.niftools.upb: upb = self.objecthelper.create_block( "NiStringExtraData") upb.name = 'UPB' if b_obj.niftools.upb == '': upb.string_data = 'Mass = 0.000000\r\nEllasticity = 0.300000\r\nFriction = 0.300000\r\nUnyielding = 0\r\nSimulation_Geometry = 2\r\nProxy_Geometry = <None>\r\nUse_Display_Proxy = 0\r\nDisplay_Children = 1\r\nDisable_Collisions = 0\r\nInactive = 0\r\nDisplay_Proxy = <None>\r\n' else: upb.string_data = b_obj.niftools.upb.encode() root_block.add_extra_data(upb) # update rigid body center of gravity and mass if self.EXPORT_OB_COLLISION_DO_NOT_USE_BLENDER_PROPERTIES: # we are not using blender properties to set the mass # so calculate mass automatically first calculate distribution of mass total_mass = 0 for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): block.update_mass_center_inertia( solid=self.EXPORT_OB_SOLID) total_mass += block.mass if total_mass == 0: # to avoid zero division error later (if mass is zero then this does not matter anyway) total_mass = 1 # now update the mass ensuring that total mass is self.EXPORT_OB_MASS for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): mass = self.EXPORT_OB_MASS * block.mass / total_mass # lower bound on mass if mass < 0.0001: mass = 0.05 block.update_mass_center_inertia( mass=mass, solid=self.EXPORT_OB_SOLID) else: # using blender properties, so block.mass *should* have # been set properly for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): # lower bound on mass if block.mass < 0.0001: block.mass = 0.05 block.update_mass_center_inertia( mass=block.mass, solid=self.EXPORT_OB_SOLID) # bhkConvexVerticesShape of children of bhkListShapes need an extra bhkConvexTransformShape (see issue #3308638, reported by Koniption) # note: self.dict_blocks changes during iteration, so need list copy for block in list(self.dict_blocks): if isinstance(block, NifFormat.bhkListShape): for i, sub_shape in enumerate(block.sub_shapes): if isinstance(sub_shape, NifFormat.bhkConvexVerticesShape): coltf = self.objecthelper.create_block( "bhkConvexTransformShape") coltf.material = sub_shape.material coltf.unknown_float_1 = 0.1 coltf.unknown_8_bytes[0] = 96 coltf.unknown_8_bytes[1] = 120 coltf.unknown_8_bytes[2] = 53 coltf.unknown_8_bytes[3] = 19 coltf.unknown_8_bytes[4] = 24 coltf.unknown_8_bytes[5] = 9 coltf.unknown_8_bytes[6] = 253 coltf.unknown_8_bytes[7] = 4 coltf.transform.set_identity() coltf.shape = sub_shape block.sub_shapes[i] = coltf # export constraints for b_obj in self.objecthelper.get_exported_objects(): if isinstance(b_obj, bpy.types.Object) and b_obj.constraints: self.constrainthelper.export_constraints(b_obj, root_block) # export weapon location if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): if self.EXPORT_OB_PRN != "NONE": # add string extra data prn = self.objecthelper.create_block("NiStringExtraData") prn.name = 'Prn' prn.string_data = { "BACK": "BackWeapon", "SIDE": "SideWeapon", "QUIVER": "Quiver", "SHIELD": "Bip01 L ForearmTwist", "HELM": "Bip01 Head", "RING": "Bip01 R Finger1" }[self.EXPORT_OB_PRN] root_block.add_extra_data(prn) # add vertex color and zbuffer properties for civ4 and railroads if NifOp.props.game in ('CIVILIZATION_IV', 'SID_MEIER_S_RAILROADS'): self.propertyhelper.object_property.export_vertex_color_property( root_block) self.propertyhelper.object_property.export_z_buffer_property( root_block) elif NifOp.props.game in ('EMPIRE_EARTH_II', ): self.propertyhelper.object_property.export_vertex_color_property( root_block) self.propertyhelper.object_property.export_z_buffer_property( root_block, flags=15, function=1) # 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 self.dict_blocks: 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 if abs(NifOp.props.scale_correction_export) > NifOp.props.epsilon: NifLog.info("Applying scale correction {0}".format( str(NifOp.props.scale_correction_export))) data = NifFormat.Data() data.roots = [root_block] toaster = pyffi.spells.nif.NifToaster() toaster.scale = NifOp.props.scale_correction_export pyffi.spells.nif.fix.SpellScale(data=data, toaster=toaster).recurse() # also scale egm if self.egm_data: self.egm_data.apply_scale( NifOp.props.scale_correction_export) # generate mopps (must be done after applying scale!) if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): for block in self.dict_blocks: 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." ) # delete original scene root if a scene root object was already defined if root_block.num_children == 1 and ( root_block.children[0].name in ['Scene Root', 'Bip01'] or root_block.children[0].name[-3:] == 'nif'): if root_block.children[0].name[-3:] == 'nif': root_block.children[0].name = filebase NifLog.info("Making '{0}' the root block".format( root_block.children[0].name)) # remove root_block from self.dict_blocks self.dict_blocks.pop(root_block) # set new root block old_root_block = root_block root_block = old_root_block.children[0] # copy extra data and properties for extra in old_root_block.get_extra_datas(): # delete links in extras to avoid parentship problems extra.next_extra_data = None # now add it root_block.add_extra_data(extra) for b in old_root_block.get_controllers(): root_block.add_controller(b) for b in old_root_block.properties: root_block.add_property(b) for b in old_root_block.effects: root_block.add_effect(b) else: root_block.name = root_name self.root_ninode = None for root_obj in root_objects: if root_obj.niftools.rootnode == 'BSFadeNode': self.root_ninode = 'BSFadeNode' elif self.root_ninode is None: self.root_ninode = 'NiNode' # making root block a fade node if NifOp.props.game in ('FALLOUT_3', 'SKYRIM' ) and self.root_ninode == 'BSFadeNode': NifLog.info("Making root block a BSFadeNode") fade_root_block = NifFormat.BSFadeNode().deepcopy(root_block) fade_root_block.replace_global_node(root_block, fade_root_block) root_block = fade_root_block export_animation = NifOp.props.animation if export_animation == 'ALL_NIF': NifLog.info("Exporting geometry and animation") elif export_animation == 'GEOM_NIF': # for morrowind: everything except keyframe controllers NifLog.info("Exporting geometry only") elif export_animation == 'ANIM_KF': # for morrowind: only keyframe controllers NifLog.info("Exporting animation only (as .kf file)") # export nif file: # ---------------- NifLog.info("Writing NIF version 0x%08X" % self.version) if export_animation != 'ANIM_KF': if NifOp.props.game == 'EMPIRE_EARTH_II': ext = ".nifcache" else: ext = ".nif" NifLog.info("Writing {0} file".format(ext)) # make sure we have the right file extension if fileext.lower() != ext: NifLog.warn( "Changing extension from {0} to {1} on output file". format(fileext, ext)) niffile = os.path.join(directory, filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [root_block] if NifOp.props.game == 'NEOSTEAM': data.modification = "neosteam" elif NifOp.props.game == 'ATLANTICA': data.modification = "ndoors" elif NifOp.props.game == 'HOWLING_SWORD': data.modification = "jmihs1" with open(niffile, "wb") as stream: data.write(stream) # create and export keyframe file and xnif file: # ---------------------------------------------- # convert root_block tree into a keyframe tree if export_animation == 'ANIM_KF' or export_animation == 'ALL_NIF_XNIF_XKF': NifLog.info("Creating keyframe tree") # find all nodes and relevant controllers node_kfctrls = {} for node in root_block.tree(): if not isinstance(node, NifFormat.NiAVObject): continue # get list of all controllers for this node ctrls = node.get_controllers() for ctrl in ctrls: if NifOp.props.game == 'MORROWIND': # morrowind: only keyframe controllers if not isinstance(ctrl, NifFormat.NiKeyframeController): continue if node not in node_kfctrls: node_kfctrls[node] = [] node_kfctrls[node].append(ctrl) # morrowind if NifOp.props.game in ('MORROWIND', 'FREEDOM_FORCE'): # create kf root header kf_root = self.objecthelper.create_block( "NiSequenceStreamHelper") kf_root.add_extra_data(anim_textextra) # reparent controller tree for node, ctrls in node_kfctrls.items(): for ctrl in ctrls: # create node reference by name nodename_extra = self.objecthelper.create_block( "NiStringExtraData") nodename_extra.bytes_remaining = len(node.name) + 4 nodename_extra.string_data = node.name # break the controller chain ctrl.next_controller = None # add node reference and controller kf_root.add_extra_data(nodename_extra) kf_root.add_controller(ctrl) # wipe controller target ctrl.target = None # oblivion elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'CIVILIZATION_IV', 'ZOO_TYCOON_2', 'FREEDOM_FORCE_VS_THE_3RD_REICH'): # TODO [animation] allow for object kf only # create kf root header kf_root = self.objecthelper.create_block( "NiControllerSequence") # if self.EXPORT_ANIMSEQUENCENAME: # kf_root.name = self.EXPORT_ANIMSEQUENCENAME # else: kf_root.name = filebase kf_root.unknown_int_1 = 1 kf_root.weight = 1.0 kf_root.text_keys = anim_textextra kf_root.cycle_type = NifFormat.CycleType.CYCLE_CLAMP kf_root.frequency = 1.0 kf_root.start_time = bpy.context.scene.frame_start * bpy.context.scene.render.fps kf_root.stop_time = (bpy.context.scene.frame_end - bpy.context.scene.frame_start ) * bpy.context.scene.render.fps # quick hack to set correct target name if "Bip01" in b_armature.data.bones: targetname = "Bip01" elif "Bip02" in b_armature.data.bones: targetname = "Bip02" else: targetname = root_block.name kf_root.target_name = targetname kf_root.string_palette = NifFormat.NiStringPalette() b_armature = armature.get_armature() # per-node animation if b_armature: for b_bone in b_armature.data.bones: self.animationhelper.export_keyframes( kf_root, b_armature, b_bone) # per-object animation else: for b_obj in bpy.data.objects: self.animationhelper.export_keyframes( kf_root, b_obj) # for node, ctrls in zip(iter(node_kfctrls.keys()), iter(node_kfctrls.values())): # # export a block for every interpolator in every controller # for ctrl in ctrls: # # XXX add get_interpolators to pyffi interface # if isinstance(ctrl, NifFormat.NiSingleInterpController): # interpolators = [ctrl.interpolator] # elif isinstance( ctrl, (NifFormat.NiGeomMorpherController, NifFormat.NiMorphWeightsController)): # interpolators = ctrl.interpolators # if isinstance(ctrl, NifFormat.NiGeomMorpherController): # variable_2s = [morph.frame_name for morph in ctrl.data.morphs] # else: # variable_2s = [None for interpolator in interpolators] # for interpolator, variable_2 in zip(interpolators, variable_2s): # # create ControlledLink for each interpolator # controlledblock = kf_root.add_controlled_block() # if self.version < 0x0A020000: # # older versions need the actual controller blocks # controlledblock.target_name = node.name # controlledblock.controller = ctrl # # erase reference to target node # ctrl.target = None # else: # # newer versions need the interpolator blocks # controlledblock.interpolator = interpolator # # get bone animation priority (previously fetched from the constraints during export_bones) # if not node.name in self.dict_bone_priorities or self.EXPORT_ANIM_DO_NOT_USE_BLENDER_PROPERTIES: # if self.EXPORT_ANIMPRIORITY != 0: # priority = self.EXPORT_ANIMPRIORITY # else: # priority = 26 # NifLog.warn("No priority set for bone {0}, falling back on default value ({1})".format(node.name, str(priority))) # else: # priority = self.dict_bone_priorities[node.name] # controlledblock.priority = priority # # set palette, and node and controller type names, and variables # controlledblock.string_palette = kf_root.string_palette # controlledblock.set_node_name(node.name) # controlledblock.set_controller_type(ctrl.__class__.__name__) # if variable_2: # controlledblock.set_variable_2(variable_2) else: raise nif_utils.NifError( "Keyframe export for '%s' is not supported.\nOnly Morrowind, Oblivion, Fallout 3, Civilization IV," " Zoo Tycoon 2, Freedom Force, and Freedom Force vs. the 3rd Reich keyframes are supported." % NifOp.props.game) # write kf (and xnif if asked) prefix = "" if ( export_animation != 'ALL_NIF_XNIF_XKF') else "x" ext = ".kf" NifLog.info("Writing {0} file".format(prefix + ext)) kffile = os.path.join(directory, prefix + filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [kf_root] data.neosteam = (NifOp.props.game == 'NEOSTEAM') stream = open(kffile, "wb") try: data.write(stream) finally: stream.close() if export_animation == 'ALL_NIF_XNIF_XKF': NifLog.info("Detaching keyframe controllers from nif") # detach the keyframe controllers from the nif (for xnif) for node in root_block.tree(): if not isinstance(node, NifFormat.NiNode): continue # remove references to keyframe controllers from node # (for xnif) while isinstance(node.controller, NifFormat.NiKeyframeController): node.controller = node.controller.next_controller ctrl = node.controller while ctrl: if isinstance(ctrl.next_controller, NifFormat.NiKeyframeController): ctrl.next_controller = ctrl.next_controller.next_controller else: ctrl = ctrl.next_controller NifLog.info("Detaching animation text keys from nif") # detach animation text keys if root_block.extra_data is not anim_textextra: raise RuntimeError( "Oops, you found a bug! Animation extra data" " wasn't where expected...") root_block.extra_data = None prefix = "x" # we are in morrowind 'nifxnifkf mode' ext = ".nif" NifLog.info("Writing {0} file".format(prefix + ext)) xniffile = os.path.join(directory, prefix + filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [root_block] data.neosteam = (NifOp.props.game == 'NEOSTEAM') stream = open(xniffile, "wb") try: data.write(stream) finally: stream.close() # export egm file: # ----------------- if self.egm_data: ext = ".egm" NifLog.info("Writing {0} file".format(ext)) egmfile = os.path.join(directory, filebase + ext) stream = open(egmfile, "wb") try: self.egm_data.write(stream) finally: stream.close() finally: # clear progress bar NifLog.info("Finished") # save exported file (this is used by the test suite) self.root_blocks = [root_block] return {'FINISHED'}
def export_texture_filename(self, texture): """Returns file name from texture. @param texture: The texture object in blender. @return: The file name of the image used in the texture. """ if texture.type == 'ENVIRONMENT_MAP': # this works for morrowind only if NifOp.props.game != 'MORROWIND': raise nif_utils.NifError( "cannot export environment maps for nif version '%s'" % NifOp.props.game) return "enviro 01.TGA" elif texture.type == 'IMAGE': # get filename from image # XXX still needed? can texture.image be None in current blender? # check that image is loaded if texture.image is None: raise nif_utils.NifError( "image type texture has no file loaded ('%s')" % texture.name) filename = texture.image.filepath # warn if packed flag is enabled if texture.image.packed_file: NifLog.warn( "Packed image in texture '{0}' ignored, exporting as '{1}' instead." .format(texture.name, filename)) # try and find a DDS alternative, force it if required ddsfilename = "%s%s" % (filename[:-4], '.dds') if os.path.exists(ddsfilename) or NifOp.props.force_dds: filename = ddsfilename # sanitize file path if not NifOp.props.game in ('MORROWIND', 'OBLIVION', 'FALLOUT_3', 'SKYRIM'): # strip texture file path filename = os.path.basename(filename) else: # strip the data files prefix from the texture's file name filename = filename.lower() idx = filename.find("textures") if (idx >= 0): filename = filename[idx:] else: NifLog.warn( "{0} does not reside in a 'Textures' folder; texture path will be stripped and textures may not display in-game" .format(filename)) filename = os.path.basename(filename) # for linux export: fix path seperators return filename.replace('/', '\\') else: # texture must be of type IMAGE or ENVMAP raise nif_utils.NifError( "Error: Texture '%s' must be of type IMAGE or ENVMAP" % texture.name)
def export_bs_shader_property(self, b_obj=None, b_mat=None): """Export a Bethesda shader property block.""" self.determine_texture_types(b_obj, b_mat) # create new block if b_obj.niftools_shader.bs_shadertype == 'BSShaderPPLightingProperty': bsshader = NifFormat.BSShaderPPLightingProperty() # set shader options # TODO: FIXME: b_s_type = NifFormat.BSShaderType._enumkeys.index(b_obj.niftools_shader.bsspplp_shaderobjtype) bsshader.shader_type = NifFormat.BSShaderType._enumvalues[b_s_type] # Shader Flags if hasattr(bsshader, 'shader_flags'): self.export_shader_flags(b_obj, bsshader) if b_obj.niftools_shader.bs_shadertype == 'BSLightingShaderProperty': bsshader = NifFormat.BSLightingShaderProperty() b_s_type = NifFormat.BSLightingShaderPropertyShaderType._enumkeys.index(b_obj.niftools_shader.bslsp_shaderobjtype) bsshader.shader_type = NifFormat.BSLightingShaderPropertyShaderType._enumvalues[b_s_type] # UV Offset if hasattr(bsshader, 'uv_offset'): self.export_uv_offset(bsshader) # UV Scale if hasattr(bsshader, 'uv_scale'): self.export_uv_scale(bsshader) # Texture Clamping mode if not self.base_mtex.texture.image.use_clamp_x: wrap_s = 2 else: wrap_s = 0 if not self.base_mtex.texture.image.use_clamp_y: wrap_t = 1 else: wrap_t = 0 bsshader.texture_clamp_mode = (wrap_s + wrap_t) # Diffuse color bsshader.skin_tint_color.r = b_mat.diffuse_color.r bsshader.skin_tint_color.g = b_mat.diffuse_color.g bsshader.skin_tint_color.b = b_mat.diffuse_color.b # b_mat.diffuse_intensity = 1.0 bsshader.lighting_effect_1 = b_mat.niftools.lightingeffect1 bsshader.lighting_effect_2 = b_mat.niftools.lightingeffect2 # Emissive bsshader.emissive_color.r = b_mat.niftools.emissive_color.r bsshader.emissive_color.g = b_mat.niftools.emissive_color.g bsshader.emissive_color.b = b_mat.niftools.emissive_color.b bsshader.emissive_multiple = b_mat.emit # gloss bsshader.glossiness = b_mat.specular_hardness # Specular color bsshader.specular_color.r = b_mat.specular_color.r bsshader.specular_color.g = b_mat.specular_color.g bsshader.specular_color.b = b_mat.specular_color.b bsshader.specular_strength = b_mat.specular_intensity # Alpha if b_mat.use_transparency: bsshader.alpha = (1 - b_mat.alpha) # Shader Flags if hasattr(bsshader, 'shader_flags_1'): self.export_shader_flags(b_obj, bsshader) if b_obj.niftools_shader.bs_shadertype == 'BSEffectShaderProperty': bsshader = NifFormat.BSEffectShaderProperty() # Alpha if b_mat.use_transparency: bsshader.alpha = (1 - b_mat.alpha) # clamp Mode bsshader.texture_clamp_mode = 65283 # Emissive bsshader.emissive_color.r = b_mat.niftools.emissive_color.r bsshader.emissive_color.g = b_mat.niftools.emissive_color.g bsshader.emissive_color.b = b_mat.niftools.emissive_color.b bsshader.emissive_color.a = b_mat.niftools.emissive_alpha bsshader.emissive_multiple = b_mat.emit # Shader Flags if hasattr(bsshader, 'shader_flags_1'): self.export_shader_flags(b_obj, bsshader) if b_obj.niftools_shader.bs_shadertype == 'None': raise nif_utils.NifError("Export version expected shader. " "No shader applied to mesh '%s', these cannot be exported to NIF." " Set shader before exporting." % b_obj) # set textures texset = NifFormat.BSShaderTextureSet() bsshader.texture_set = texset if self.base_mtex: texset.textures[0] = self.texture_writer.export_texture_filename(self.base_mtex.texture) if self.normal_mtex: texset.textures[1] = self.texture_writer.export_texture_filename(self.normal_mtex.texture) if self.glow_mtex: texset.textures[2] = self.texture_writer.export_texture_filename(self.glow_mtex.texture) if self.detail_mtex: texset.textures[3] = self.texture_writer.export_texture_filename(self.detail_mtex.texture) if b_obj.niftools_shader.bs_shadertype == 'BSLightingShaderProperty': texset.num_textures = 9 texset.textures.update_size() if self.detail_mtex: texset.textures[6] = self.texture_writer.export_texture_filename(self.detail_mtex.texture) if self.gloss_mtex: texset.textures[7] = self.texture_writer.export_texture_filename(self.gloss_mtex.texture) if b_obj.niftools_shader.bs_shadertype == 'BSEffectShaderProperty': bsshader.source_texture = self.texture_writer.export_texture_filename(self.base_mtex.texture) bsshader.greyscale_texture = self.texture_writer.export_texture_filename(self.glow_mtex.texture) return bsshader
def determine_texture_types(self, b_obj, b_mat): used_slots = self.get_used_textslots(b_mat) self.base_mtex = None self.bump_mtex = None self.dark_mtex = None self.detail_mtex = None self.gloss_mtex = None self.glow_mtex = None self.normal_mtex = None self.ref_mtex = None for b_mat_texslot in used_slots: # check REFL-mapped textures # (used for "NiTextureEffect" materials) if b_mat_texslot.texture_coords == 'REFLECTION': if not b_mat_texslot.use_map_color_diffuse: # it should map to colour raise nif_utils.NifError("Non-COL-mapped reflection texture in mesh '%s', material '%s', these cannot be exported to NIF.\n" "Either delete all non-COL-mapped reflection textures, or in the Shading Panel, under Material Buttons, set texture 'Map To' to 'COL'." % (b_obj.name, b_mat.name)) if b_mat_texslot.blend_type != 'ADD': # it should have "ADD" blending mode NifLog.warn("Reflection texture should have blending mode 'Add' on texture in mesh '{0}', material '{1}').".format(b_obj.name, b_mat.name)) # an envmap image should have an empty... don't care self.ref_mtex = b_mat_texslot # check UV-mapped textures elif b_mat_texslot.texture_coords == 'UV': # update set of uv layers that must be exported if not b_mat_texslot.uv_layer in self.nif_export.dict_mesh_uvlayers: self.nif_export.dict_mesh_uvlayers.append(b_mat_texslot.uv_layer) # glow tex if b_mat_texslot.use_map_emit: # multi-check if self.glow_mtex: raise nif_utils.NifError("Multiple emissive textures in mesh '%s', material '%s'.\n" " Make sure there is only one texture set as Influence > emit" % (b_obj.name, b_mat.name)) # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True self.glow_mtex = b_mat_texslot # specular elif b_mat_texslot.use_map_specular or b_mat_texslot.use_map_color_spec: # multi-check if self.gloss_mtex: raise nif_utils.NifError("Multiple specular gloss textures in mesh '%s', material '%s'.\n" "Make sure there is only one texture set as Influence > specular" % (b_obj.name, b_mat.name)) # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True # got the gloss map self.gloss_mtex = b_mat_texslot # bump map elif b_mat_texslot.use_map_normal and \ b_mat_texslot.texture.use_normal_map == False: # multi-check if self.bump_mtex: raise nif_utils.NifError("Multiple bump/normal texture in mesh '%s', material '%s'.\n" "Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name)) # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True self.bump_mtex = b_mat_texslot # normal map elif b_mat_texslot.use_map_normal and b_mat_texslot.texture.use_normal_map: # multi-check if self.normal_mtex: raise nif_utils.NifError("Multiple bump/normal textures in mesh '%s', material '%s'." " Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name)) # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True self.normal_mtex = b_mat_texslot # darken elif b_mat_texslot.use_map_color_diffuse and b_mat_texslot.blend_type == 'DARKEN': if self.dark_mtex: raise nif_utils.NifError("Multiple Darken textures in mesh '%s', material '%s'." " Make sure there is only one texture with Influence > Blend Type > Dark" % (b_obj.name, b_mat.name)) # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True # got the dark map self.dark_mtex = b_mat_texslot # diffuse elif b_mat_texslot.use_map_color_diffuse: if self.base_mtex: raise nif_utils.NifError("Multiple Diffuse textures in mesh '%s', material '%s'.\n" "Make sure there is only one texture with Influence > Diffuse > color" % (b_obj.name, b_mat.name)) self.base_mtex = b_mat_texslot # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True ''' # in this case, Blender replaces the texture transparant parts with the underlying material color... # in NIF, material alpha is multiplied with texture alpha channel... # how can we emulate the NIF alpha system (simply multiplying material alpha with texture alpha) when MapTo.ALPHA is turned on? # require the Blender material alpha to be 0.0 (no material color can show up), and use the "Var" slider in the texture blending mode tab! # but... if mesh_mat_transparency > NifOp.props.epsilon: raise nif_utils.NifError( "Cannot export this type of" " transparency in material '%s': " " instead, try to set alpha to 0.0" " and to use the 'Var' slider" " in the 'Map To' tab under the" " material buttons." %b_mat.name) if (b_mat.animation_data and b_mat.animation_data.action.fcurves['Alpha']): raise nif_utils.NifError( "Cannot export animation for" " this type of transparency" " in material '%s':" " remove alpha animation," " or turn off MapTo.ALPHA," " and try again." %b_mat.name) mesh_mat_transparency = b_mat_texslot.varfac # we must use the "Var" value ''' # detail elif b_mat_texslot.use_map_color_diffuse: if self.detail_mtex: raise nif_utils.NifError("Multiple detail textures in mesh '%s', material '%s'.\n" " Make sure there is only one texture with Influence Diffuse > color" % (b_obj.name, b_mat.name)) # extra diffuse consider as detail texture # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True self.detail_mtex = b_mat_texslot # reflection elif b_mat_texslot.use_map_mirror or b_mat_texslot.use_map_raymir: # multi-check if self.glow_mtex: raise nif_utils.NifError("Multiple reflection textures in mesh '%s', material '%s'.\n" "Make sure there is only one texture set as Influence > Mirror/Ray Mirror" % (b_obj.name, b_mat.name)) # got the reflection map # check if alpha channel is enabled for this texture if b_mat_texslot.use_map_alpha: mesh_hasalpha = True self.ref_mtex = b_mat_texslot # unsupported map else: raise nif_utils.NifError("Do not know how to export texture '%s', in mesh '%s', material '%s'.\n" "Either delete it, or if this texture is to be your base texture.\n" "Go to the Shading Panel Material Buttons, and set texture 'Map To' to 'COL'." % (b_mat_texslot.texture.name, b_obj.name, b_mat.name)) # nif only support UV-mapped textures else: NifLog.warn("Non-UV texture in mesh '{0}', material '{1}'.\nEither delete all non-UV textures or " "create a UV map for every texture associated with selected object and run the script again.".format(b_obj.name, b_mat.name))
def export_collision_object(self, b_obj, layer, n_havok_mat): """Export object obj as box, sphere, capsule, or convex hull. Note: polyheder is handled by export_collision_packed.""" # find bounding box data if not b_obj.data.vertices: NifLog.warn("Skipping collision object {0} without vertices.".format(b_obj)) return None b_vertlist = [vert.co for vert in b_obj.data.vertices] minx = min([b_vert[0] for b_vert in b_vertlist]) miny = min([b_vert[1] for b_vert in b_vertlist]) minz = min([b_vert[2] for b_vert in b_vertlist]) maxx = max([b_vert[0] for b_vert in b_vertlist]) maxy = max([b_vert[1] for b_vert in b_vertlist]) maxz = max([b_vert[2] for b_vert in b_vertlist]) calc_bhkshape_radius = (maxx - minx + maxy - miny + maxz - minz) / (6.0 * self.HAVOK_SCALE) if(b_obj.game.radius - calc_bhkshape_radius > NifOp.props.epsilon): radius = calc_bhkshape_radius else: radius = b_obj.game.radius if b_obj.game.collision_bounds_type in {'BOX', 'SPHERE'}: # note: collision settings are taken from lowerclasschair01.nif coltf = self.nif_export.objecthelper.create_block("bhkConvexTransformShape", b_obj) coltf.material = n_havok_mat coltf.unknown_float_1 = 0.1 coltf.unknown_8_bytes[0] = 96 coltf.unknown_8_bytes[1] = 120 coltf.unknown_8_bytes[2] = 53 coltf.unknown_8_bytes[3] = 19 coltf.unknown_8_bytes[4] = 24 coltf.unknown_8_bytes[5] = 9 coltf.unknown_8_bytes[6] = 253 coltf.unknown_8_bytes[7] = 4 hktf = mathutils.Matrix( self.nif_export.objecthelper.get_object_matrix(b_obj).as_list()) # the translation part must point to the center of the data # so calculate the center in local coordinates center = mathutils.Vector(((minx + maxx) / 2.0, (miny + maxy) / 2.0, (minz + maxz) / 2.0)) # and transform it to global coordinates center = center * hktf hktf[0][3] = center[0] hktf[1][3] = center[1] hktf[2][3] = center[2] # we need to store the transpose of the matrix hktf.transpose() coltf.transform.set_rows(*hktf) # fix matrix for havok coordinate system coltf.transform.m_41 /= self.HAVOK_SCALE coltf.transform.m_42 /= self.HAVOK_SCALE coltf.transform.m_43 /= self.HAVOK_SCALE if b_obj.game.collision_bounds_type == 'BOX': colbox = self.nif_export.objecthelper.create_block("bhkBoxShape", b_obj) coltf.shape = colbox colbox.material = n_havok_mat colbox.radius = radius colbox.unknown_8_bytes[0] = 0x6b colbox.unknown_8_bytes[1] = 0xee colbox.unknown_8_bytes[2] = 0x43 colbox.unknown_8_bytes[3] = 0x40 colbox.unknown_8_bytes[4] = 0x3a colbox.unknown_8_bytes[5] = 0xef colbox.unknown_8_bytes[6] = 0x8e colbox.unknown_8_bytes[7] = 0x3e # fix dimensions for havok coordinate system colbox.dimensions.x = (maxx - minx) / (2.0 * self.HAVOK_SCALE) colbox.dimensions.y = (maxy - miny) / (2.0 * self.HAVOK_SCALE) colbox.dimensions.z = (maxz - minz) / (2.0 * self.HAVOK_SCALE) colbox.minimum_size = min(colbox.dimensions.x, colbox.dimensions.y, colbox.dimensions.z) elif b_obj.game.collision_bounds_type == 'SPHERE': colsphere = self.nif_export.objecthelper.create_block("bhkSphereShape", b_obj) coltf.shape = colsphere colsphere.material = n_havok_mat # take average radius and # Todo find out what this is: fix for havok coordinate system (6 * 7 = 42) colsphere.radius = radius return coltf elif b_obj.game.collision_bounds_type in {'CYLINDER', 'CAPSULE'}: # take average radius and calculate end points localradius = (maxx + maxy - minx - miny) / 4.0 transform = b_obj.matrix_local.transposed() vert1 = mathutils.Vector( [ (maxx + minx)/2.0, (maxy + miny)/2.0, maxz - localradius ] ) vert2 = mathutils.Vector( [ (maxx + minx) / 2.0, (maxy + miny) / 2.0, minz + localradius ] ) vert1 = vert1 * transform vert2 = vert2 * transform # check if end points are far enough from each other if (vert1 - vert2).length < NifOp.props.epsilon: NifLog.warn("End points of cylinder {0} too close, converting to sphere.".format(b_obj)) # change type b_obj.game.collision_bounds_type = 'SPHERE' # instead of duplicating code, just run the function again return self.export_collision_object(b_obj, layer, n_havok_mat) # end points are ok, so export as capsule colcaps = self.nif_export.objecthelper.create_block("bhkCapsuleShape", b_obj) colcaps.material = n_havok_mat colcaps.first_point.x = vert1[0] / self.HAVOK_SCALE colcaps.first_point.y = vert1[1] / self.HAVOK_SCALE colcaps.first_point.z = vert1[2] / self.HAVOK_SCALE colcaps.second_point.x = vert2[0] / self.HAVOK_SCALE colcaps.second_point.y = vert2[1] / self.HAVOK_SCALE colcaps.second_point.z = vert2[2] / self.HAVOK_SCALE # set radius, with correct scale size_x = b_obj.scale.x size_y = b_obj.scale.y size_z = b_obj.scale.z colcaps.radius = localradius * (size_x + size_y) * 0.5 colcaps.radius_1 = colcaps.radius colcaps.radius_2 = colcaps.radius # fix havok coordinate system for radii colcaps.radius /= self.HAVOK_SCALE colcaps.radius_1 /= self.HAVOK_SCALE colcaps.radius_2 /= self.HAVOK_SCALE return colcaps elif b_obj.game.collision_bounds_type == 'CONVEX_HULL': b_mesh = b_obj.data b_transform_mat = mathutils.Matrix(self.nif_export.objecthelper.get_object_matrix(b_obj).as_list()) b_rot_quat = b_transform_mat.decompose()[1] b_scale_vec = b_transform_mat.decompose()[0] ''' scale = math.avg(b_scale_vec.to_tuple()) if scale < 0: scale = - (-scale) ** (1.0 / 3) else: scale = scale ** (1.0 / 3) rotation /= scale ''' # calculate vertices, normals, and distances vertlist = [b_transform_mat * vert.co for vert in b_mesh.vertices] fnormlist = [b_rot_quat * b_face.normal for b_face in b_mesh.polygons] fdistlist = [(b_transform_mat * (-1 * b_mesh.vertices[b_mesh.polygons[b_face.index].vertices[0]].co)).dot( b_rot_quat.to_matrix() * b_face.normal) for b_face in b_mesh.polygons ] # remove duplicates through dictionary vertdict = {} for i, vert in enumerate(vertlist): vertdict[(int(vert[0]*self.nif_export.VERTEX_RESOLUTION), int(vert[1]*self.nif_export.VERTEX_RESOLUTION), int(vert[2]*self.nif_export.VERTEX_RESOLUTION))] = i fdict = {} for i, (norm, dist) in enumerate(zip(fnormlist, fdistlist)): fdict[(int(norm[0]*self.nif_export.NORMAL_RESOLUTION), int(norm[1]*self.nif_export.NORMAL_RESOLUTION), int(norm[2]*self.nif_export.NORMAL_RESOLUTION), int(dist*self.nif_export.VERTEX_RESOLUTION))] = i # sort vertices and normals vertkeys = sorted(vertdict.keys()) fkeys = sorted(fdict.keys()) vertlist = [ vertlist[vertdict[hsh]] for hsh in vertkeys ] fnormlist = [ fnormlist[fdict[hsh]] for hsh in fkeys ] fdistlist = [ fdistlist[fdict[hsh]] for hsh in fkeys ] if len(fnormlist) > 65535 or len(vertlist) > 65535: raise nif_utils.NifError( "ERROR%t|Too many polygons/vertices." " Decimate/split your b_mesh and try again.") colhull = self.nif_export.objecthelper.create_block("bhkConvexVerticesShape", b_obj) colhull.material = n_havok_mat colhull.radius = radius colhull.unknown_6_floats[2] = -0.0 # enables arrow detection colhull.unknown_6_floats[5] = -0.0 # enables arrow detection # note: unknown 6 floats are usually all 0 colhull.num_vertices = len(vertlist) colhull.vertices.update_size() for vhull, vert in zip(colhull.vertices, vertlist): vhull.x = vert[0] / self.HAVOK_SCALE vhull.y = vert[1] / self.HAVOK_SCALE vhull.z = vert[2] / self.HAVOK_SCALE # w component is 0 colhull.num_normals = len(fnormlist) colhull.normals.update_size() for nhull, norm, dist in zip(colhull.normals, fnormlist, fdistlist): nhull.x = norm[0] nhull.y = norm[1] nhull.z = norm[2] nhull.w = dist / self.HAVOK_SCALE return colhull else: raise nif_utils.NifError( 'cannot export collision type %s to collision shape list' % b_obj.game.collision_bounds_type)
def export_constraints(self, b_obj, root_block): """Export the constraints of an object. @param b_obj: The object whose constraints to export. @param root_block: The root of the nif tree (required for update_a_b).""" if isinstance(b_obj, bpy.types.Bone): # bone object has its constraints stored in the posebone # so now we should get the posebone, but no constraints for # bones are exported anyway for now # so skip this object return if not hasattr(b_obj, "constraints"): # skip text buffers etc return for b_constr in b_obj.constraints: # rigid body joints if b_constr.type == 'RIGID_BODY_JOINT': if NifOp.props.game not in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): NifLog.warn("Only Oblivion/Fallout/Skyrim rigid body constraints currently supported: Skipping {0}.".format(b_constr)) continue # check that the object is a rigid body for otherbody, otherobj in self.nif_export.dict_blocks.items(): if isinstance(otherbody, NifFormat.bhkRigidBody) \ and otherobj is b_obj: hkbody = otherbody break else: # no collision body for this object raise nif_utils.NifError( "Object %s has a rigid body constraint," " but is not exported as collision object" % b_obj.name) # yes there is a rigid body constraint # is it of a type that is supported? if b_constr.pivot_type == 'CONE_TWIST': # ball if b_obj.rigid_body.enabled == True: hkconstraint = self.nif_export.objecthelper.create_block( "bhkRagdollConstraint", b_constr) else: hkconstraint = self.nif_export.objecthelper.create_block( "bhkMalleableConstraint", b_constr) hkconstraint.type = 7 hkdescriptor = hkconstraint.ragdoll elif b_constr.pivot_type == 'HINGE': # hinge if b_obj.rigid_body.enabled == True: hkconstraint = self.nif_export.objecthelper.create_block( "bhkLimitedHingeConstraint", b_constr) else: hkconstraint = self.nif_export.objecthelper.create_block( "bhkMalleableConstraint", b_constr) hkconstraint.type = 2 hkdescriptor = hkconstraint.limited_hinge else: raise nif_utils.NifError( "Unsupported rigid body joint type (%i)," " only ball and hinge are supported." % b_constr.type) # defaults and getting object properties for user # settings (should use constraint properties, but # blender does not have those...) if b_constr.limit_angle_max_x != 0: max_angle = b_constr.limit_angle_max_x else: max_angle = 1.5 if b_constr.limit_angle_min_x != 0: min_angle = b_constr.limit_angle_min_x else: min_angle = 0.0 # friction: again, just picking a reasonable value if # no real value given if b_obj.niftools_constraint.LHMaxFriction != 0: max_friction = b_obj.niftools_constraint.LHMaxFriction else: if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint): # malleable typically have 0 # (perhaps because they have a damping parameter) max_friction = 0 else: # non-malleable typically have 10 if NifOp.props.game == 'FALLOUT_3': max_friction = 100 else: # oblivion max_friction = 10 # parent constraint to hkbody hkbody.num_constraints += 1 hkbody.constraints.update_size() hkbody.constraints[-1] = hkconstraint # export hkconstraint settings hkconstraint.num_entities = 2 hkconstraint.entities.update_size() hkconstraint.entities[0] = hkbody # is there a target? targetobj = b_constr.target if not targetobj: NifLog.warn("Constraint {0} has no target, skipped".format(b_constr)) continue # find target's bhkRigidBody for otherbody, otherobj in self.nif_export.dict_blocks.items(): if isinstance(otherbody, NifFormat.bhkRigidBody) \ and otherobj == targetobj: hkconstraint.entities[1] = otherbody break else: # not found raise nif_utils.NifError( "Rigid body target not exported in nif tree" " check that %s is selected during export." % targetobj) # priority hkconstraint.priority = 1 # extra malleable constraint settings if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint): # unknowns hkconstraint.unknown_int_2 = 2 hkconstraint.unknown_int_3 = 1 # force required to keep bodies together hkconstraint.tau = b_obj.niftools_constraint.tau hkconstraint.damping = b_obj.niftools_constraint.damping # calculate pivot point and constraint matrix pivot = mathutils.Vector([ b_constr.pivot_x, b_constr.pivot_y, b_constr.pivot_z, ]) constr_matrix = mathutils.Euler(( b_constr.axis_x, b_constr.axis_y, b_constr.axis_z)) constr_matrix = constr_matrix.to_matrix() # transform pivot point and constraint matrix into bhkRigidBody # coordinates (also see import_nif.py, the # NifImport.import_bhk_constraints method) # the pivot point v' is in object coordinates # however nif expects it in hkbody coordinates, v # v * R * B = v' * O * T * B' # with R = rigid body transform (usually unit tf) # B = nif bone matrix # O = blender object transform # T = bone tail matrix (translation in Y direction) # B' = blender bone matrix # so we need to cancel out the object transformation by # v = v' * O * T * B' * B^{-1} * R^{-1} # for the rotation matrix, we transform in the same way # but ignore all translation parts # assume R is unit transform... # apply object transform relative to the bone head # (this is O * T * B' * B^{-1} at once) transform = mathutils.Matrix( b_obj.matrix_local) pivot = pivot * transform constr_matrix = constr_matrix * transform.to_3x3() # export hkdescriptor pivot point hkdescriptor.pivot_a.x = pivot[0] / self.HAVOK_SCALE hkdescriptor.pivot_a.y = pivot[1] / self.HAVOK_SCALE hkdescriptor.pivot_a.z = pivot[2] / self.HAVOK_SCALE # export hkdescriptor axes and other parameters # (also see import_nif.py NifImport.import_bhk_constraints) axis_x = mathutils.Vector([1,0,0]) * constr_matrix axis_y = mathutils.Vector([0,1,0]) * constr_matrix axis_z = mathutils.Vector([0,0,1]) * constr_matrix if isinstance(hkdescriptor, NifFormat.RagdollDescriptor): # z axis is the twist vector hkdescriptor.twist_a.x = axis_z[0] hkdescriptor.twist_a.y = axis_z[1] hkdescriptor.twist_a.z = axis_z[2] # x axis is the plane vector hkdescriptor.plane_a.x = axis_x[0] hkdescriptor.plane_a.y = axis_x[1] hkdescriptor.plane_a.z = axis_x[2] # angle limits # take them twist and plane to be 45 deg (3.14 / 4 = 0.8) hkdescriptor.plane_min_angle = b_constr.limit_angle_min_x hkdescriptor.plane_max_angle = b_constr.limit_angle_max_x hkdescriptor.cone_max_angle = b_constr.limit_angle_max_y hkdescriptor.twist_min_angle = b_constr.limit_angle_min_z hkdescriptor.twist_max_angle = b_constr.limit_angle_max_z # same for maximum cone angle hkdescriptor.max_friction = max_friction elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor): # y axis is the zero angle vector on the plane of rotation hkdescriptor.perp_2_axle_in_a_1.x = axis_y[0] hkdescriptor.perp_2_axle_in_a_1.y = axis_y[1] hkdescriptor.perp_2_axle_in_a_1.z = axis_y[2] # x axis is the axis of rotation hkdescriptor.axle_a.x = axis_x[0] hkdescriptor.axle_a.y = axis_x[1] hkdescriptor.axle_a.z = axis_x[2] # z is the remaining axis determining the positive # direction of rotation hkdescriptor.perp_2_axle_in_a_2.x = axis_z[0] hkdescriptor.perp_2_axle_in_a_2.y = axis_z[1] hkdescriptor.perp_2_axle_in_a_2.z = axis_z[2] # angle limits # typically, the constraint on one side is defined # by the z axis hkdescriptor.min_angle = min_angle # the maximum axis is typically about 90 degrees # 3.14 / 2 = 1.5 hkdescriptor.max_angle = max_angle # friction hkdescriptor.max_friction = max_friction else: raise ValueError("unknown descriptor %s" % hkdescriptor.__class__.__name__) # do AB hkconstraint.update_a_b(root_block)