def import_texture_extra_shader(self, b_mat, n_texture_prop, extra_datas): # extra texture shader slots for shader_tex_desc in n_texture_prop.shader_textures: if not shader_tex_desc.is_used: continue # it is used, figure out the slot it is used for for extra in extra_datas: if extra.integer_data == shader_tex_desc.map_index: shader_name = extra.name break else: NifLog.warn("No slot for shader texture {0}.".format( shader_tex_desc.texture_data.source.file_name)) continue try: extra_shader_index = ( self.nif_import.EXTRA_SHADER_TEXTURES.index(shader_name)) except ValueError: # shader_name not in self.EXTRA_SHADER_TEXTURES NifLog.warn("No slot for shader texture {0}.".format( shader_tex_desc.texture_data.source.file_name)) continue self.import_shader_by_type(shader_tex_desc, extra_shader_index)
def get_extend_from_flags(self, flags): if flags & 6 == 4: # 0b100 return "CONST" elif flags & 6 == 0: # 0b000 return "CYCLIC" NifLog.warn("Unsupported cycle mode in nif, using clamped.") return "CONST"
def get_b_interp_from_n_interp(self, n_ipol): if n_ipol == NifFormat.KeyType.LINEAR_KEY: return "LINEAR" elif n_ipol == NifFormat.KeyType.QUADRATIC_KEY: return "BEZIER" elif n_ipol == 0: # guessing, not documented in nif.xml return "CONSTANT" NifLog.warn( "Unsupported interpolation mode ({0}) in nif, using quadratic/bezier." .format(n_ipol)) return "BEZIER"
def get_n_apply_mode_from_b_blend_type(self, b_blend_type): if b_blend_type == "LIGHTEN": return NifFormat.ApplyMode.APPLY_HILIGHT elif b_blend_type == "MULTIPLY": return NifFormat.ApplyMode.APPLY_HILIGHT2 elif b_blend_type == "MIX": return NifFormat.ApplyMode.APPLY_MODULATE NifLog.warn( "Unsupported blend type ({0}) in material, using apply mode APPLY_MODULATE" .format(b_blend_type)) return NifFormat.ApplyMode.APPLY_MODULATE
def get_n_interp_from_b_interp(self, b_ipol): if b_ipol == "LINEAR": return NifFormat.KeyType.LINEAR_KEY elif b_ipol == "BEZIER": return NifFormat.KeyType.QUADRATIC_KEY elif b_ipol == "CONSTANT": return NifFormat.KeyType.CONST_KEY NifLog.warn( "Unsupported interpolation mode ({0}) in blend, using quadratic/bezier." .format(b_ipol)) return NifFormat.KeyType.QUADRATIC_KEY
def export_tex_desc(self, texdesc=None, uvlayers=None, b_mat_texslot=None): """Helper function for export_texturing_property to export each texture slot.""" try: texdesc.uv_set = uvlayers.index( b_mat_texslot.uv_layer) if b_mat_texslot.uv_layer else 0 except ValueError: # mtex.uv_layer not in uvlayers list NifLog.warn( "Bad uv layer name '{0}' in texture '{1}'. Using first uv layer" .format(b_mat_texslot.uv_layer, b_mat_texslot.texture.name)) texdesc.uv_set = 0 # assume 0 is active layer texdesc.source = self.export_source_texture(b_mat_texslot.texture)
def import_texture_source(self, source): """Convert a NiSourceTexture block, or simply a path string, to a Blender Texture object, return the Texture object and stores it in the self.nif_import.dict_textures dictionary to avoid future duplicate imports. """ # if the source block is not linked then return None if not source: return None # calculate the texture hash key texture_hash = self.get_texture_hash(source) try: # look up the texture in the dictionary of imported textures # and return it if found return self.nif_import.dict_textures[texture_hash] except KeyError: pass b_image = None if (isinstance(source, NifFormat.NiSourceTexture) and not source.use_external): fn, b_image = self.import_embedded_texture_source(source) else: fn, b_image = self.import_source(source) # create a stub image if the image could not be loaded b_text_name = os.path.basename(fn) if not b_image: NifLog.warn( "Texture '{0}' not found or not supported and no alternate available" .format(fn)) b_image = bpy.data.images.new(name=b_text_name, width=1, height=1, alpha=False) b_image.filepath = fn # create a texture b_texture = bpy.data.textures.new(name=b_text_name, type='IMAGE') b_texture.image = b_image b_texture.use_interpolation = True b_texture.use_mipmap = True # save texture to avoid duplicate imports, and return it self.nif_import.dict_textures[texture_hash] = b_texture return b_texture
def import_bhk_shape(self, bhkshape): """Imports any supported collision shape as list of blender meshes.""" if self.nif_import.data._user_version_value_._value == 12: if self.nif_import.data._user_version_2_value_._value == 83: self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE * 10 else: self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE if isinstance(bhkshape, NifFormat.bhkTransformShape): return self.import_bhktransform(bhkshape) elif isinstance(bhkshape, NifFormat.bhkRigidBody): return self.import_bhkridgidbody(bhkshape) elif isinstance(bhkshape, NifFormat.bhkBoxShape): return self.import_bhkbox_shape(bhkshape) elif isinstance(bhkshape, NifFormat.bhkSphereShape): return self.import_bhksphere_shape(bhkshape) elif isinstance(bhkshape, NifFormat.bhkCapsuleShape): return self.import_bhkcapsule_shape(bhkshape) elif isinstance(bhkshape, NifFormat.bhkConvexVerticesShape): return self.import_bhkconvex_vertices_shape(bhkshape) elif isinstance(bhkshape, NifFormat.bhkPackedNiTriStripsShape): return self.import_bhkpackednitristrips_shape(bhkshape) elif isinstance(bhkshape, NifFormat.bhkNiTriStripsShape): self.havok_mat = bhkshape.material return reduce(operator.add, (self.import_bhk_shape(strips) for strips in bhkshape.strips_data)) elif isinstance(bhkshape, NifFormat.NiTriStripsData): return self.import_nitristrips(bhkshape) elif isinstance(bhkshape, NifFormat.bhkMoppBvTreeShape): return self.import_bhk_shape(bhkshape.shape) elif isinstance(bhkshape, NifFormat.bhkListShape): return reduce(operator.add, (self.import_bhk_shape(subshape) for subshape in bhkshape.sub_shapes)) NifLog.warn("Unsupported bhk shape {0}".format( bhkshape.__class__.__name__)) return []
def get_b_blend_type_from_n_apply_mode(self, n_apply_mode): # TODO: - Check out n_apply_modes if n_apply_mode == NifFormat.ApplyMode.APPLY_MODULATE: return "MIX" elif n_apply_mode == NifFormat.ApplyMode.APPLY_REPLACE: return "COLOR" elif n_apply_mode == NifFormat.ApplyMode.APPLY_DECAL: return "OVERLAY" elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT: return "LIGHTEN" elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT2: # used by Oblivion for parallax return "MULTIPLY" else: NifLog.warn( "Unknown apply mode (%i) in material, using blend type 'MIX'". format(n_apply_mode)) return "MIX"
def export_source_texture(self, texture=None, filename=None): """Export a NiSourceTexture. :param texture: The texture object in blender to be exported. :param filename: The full or relative path to the texture file (this argument is used when exporting NiFlipControllers and when exporting default shader slots that have no use in being imported into Blender). :return: The exported NiSourceTexture block. """ # create NiSourceTexture srctex = NifFormat.NiSourceTexture() srctex.use_external = True if not filename is None: # preset filename srctex.file_name = filename elif not texture is None: srctex.file_name = self.export_texture_filename(texture) else: # this probably should not happen NifLog.warn( "Exporting source texture without texture or filename (bug?).") # fill in default values (TODO: can we use 6 for everything?) if bpy.context.scene.niftools_scene.nif_version >= 0x0A000100: srctex.pixel_layout = 6 else: srctex.pixel_layout = 5 srctex.use_mipmaps = 1 srctex.alpha_format = 3 srctex.unknown_byte = 1 # search for duplicate for block in self.nif_export.nif_export.dict_blocks: if isinstance(block, NifFormat.NiSourceTexture) and block.get_hash( ) == srctex.get_hash(): return block # no identical source texture found, so use and register # the new one return self.nif_export.nif_export.objecthelper.register_block( srctex, texture)
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 decompose_srt(matrix): """Decompose Blender transform matrix as a scale, rotation matrix, and translation vector.""" # get matrix components trans_vec, rot_quat, scale_vec = matrix.decompose() #obtain a combined scale and rotation matrix to test determinate rotmat = rot_quat.to_matrix() scalemat = mathutils.Matrix( ((scale_vec[0], 0.0, 0.0), (0.0, scale_vec[1], 0.0), (0.0, 0.0, scale_vec[2]))) scale_rot = scalemat * rotmat # and fix their sign if (scale_rot.determinant() < 0): scale_vec.negate() # only uniform scaling # allow rather large error to accomodate some nifs if abs(scale_vec[0] - scale_vec[1]) + abs(scale_vec[1] - scale_vec[2]) > 0.02: NifLog.warn("Non-uniform scaling not supported." + " Workaround: apply size and rotation (CTRL-A).") return [scale_vec[0], rotmat, trans_vec]
def import_shader_by_type(self, b_mat, shader_tex_desc, extra_shader_index): if extra_shader_index == 0: # EnvironmentMapIndex if shader_tex_desc.texture_data.source.file_name.lower( ).startswith("rrt_engine_env_map"): # sid meier's railroads: env map generated by engine # we can skip this NifLog.info( "Skipping environment map texture. Env Map is generated by Engine" ) envTexDesc = shader_tex_desc.texture_data self.has_envtex = True self.env_map = self.import_image_texture(b_mat, envTexDesc) elif extra_shader_index == 1: # NormalMapIndex bumpTexDesc = shader_tex_desc.texture_data self.has_bumptex = True self.bump_map = self.import_image_texture(b_mat, bumpTexDesc) elif extra_shader_index == 2: # SpecularIntensityIndex glossTexDesc = shader_tex_desc.texture_data self.has_glosstex = True self.gloss_map = self.import_image_texture(b_mat, glossTexDesc) elif extra_shader_index == 3: # EnvironmentIntensityIndex (this is reflection) refTexDesc = shader_tex_desc.texture_data self.has_reftex = True self.reflection_map = self.reflection_map = self.import_image_texture( b_mat, refTexDesc) elif extra_shader_index == 4: # LightCubeMapIndex if shader_tex_desc.texture_data.source.file_name.lower( ).startswith("rrt_cube_light_map"): # sid meier's railroads: light map generated by engine # we can skip this NifLog.info("Ignoring Env Map as generated by Engine") NifLog.warn("Skipping light cube texture.") elif extra_shader_index == 5: # ShadowTextureIndex NifLog.warn("Skipping shadow texture.") else: NifLog.warn("Unknown texture type found in extra_shader_index")
def export_nitextureprop_tex_descs(self, texprop): if self.base_mtex: texprop.has_base_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.base_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.base_mtex) # check for texture flip definition try: fliptxt = Blender.Text.Get(basemtex.texture.name) except NameError: pass else: # texture slot 0 = base self.animationhelper.texture_animation.export_flip_controller(fliptxt, self.base_mtex.texture, texprop, 0) if self.glow_mtex: texprop.has_glow_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.glow_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.glow_mtex) if self.bump_mtex: if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES: texprop.has_bump_map_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.bump_map_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.bump_mtex) texprop.bump_map_luma_scale = 1.0 texprop.bump_map_luma_offset = 0.0 texprop.bump_map_matrix.m_11 = 1.0 texprop.bump_map_matrix.m_12 = 0.0 texprop.bump_map_matrix.m_21 = 0.0 texprop.bump_map_matrix.m_22 = 1.0 if self.normal_mtex: shadertexdesc = texprop.shader_textures[1] shadertexdesc.is_used = True shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.normal_mtex.texture) if self.gloss_mtex: if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES: texprop.has_gloss_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.gloss_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.gloss_mtex) else: shadertexdesc = texprop.shader_textures[2] shadertexdesc.is_used = True shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.gloss_mtex.texture) if self.dark_mtex: texprop.has_dark_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.dark_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.dark_mtex) if self.detail_mtex: texprop.has_detail_texture = True self.texture_writer.export_tex_desc(texdesc=texprop.detail_texture, uvlayers=self.nif_export.dict_mesh_uvlayers, b_mat_texslot=self.detail_mtex) if self.ref_mtex: if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES: NifLog.warn("Cannot export reflection texture for this game.") # texprop.hasRefTexture = True # self.export_tex_desc(texdesc=texprop.refTexture, uvlayers=uvlayers, mtex=refmtex) else: shadertexdesc = texprop.shader_textures[3] shadertexdesc.is_used = True shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.ref_mtex.texture)
def import_constraint(self, hkbody): """Imports a bone havok constraint as Blender object constraint.""" assert (isinstance(hkbody, NifFormat.bhkRigidBody)) # check for constraints if not hkbody.constraints: return # find objects if len(self.nif_import.dict_havok_objects[hkbody]) != 1: NifLog.warn( "Rigid body with no or multiple shapes, constraints skipped") return b_hkobj = self.nif_import.dict_havok_objects[hkbody][0] NifLog.info("Importing constraints for %s" % b_hkobj.name) # now import all constraints for hkconstraint in hkbody.constraints: # check constraint entities if not hkconstraint.num_entities == 2: NifLog.warn("Constraint with more than 2 entities, skipped") continue if not hkconstraint.entities[0] is hkbody: NifLog.warn("First constraint entity not self, skipped") continue if not hkconstraint.entities[ 1] in self.nif_import.dict_havok_objects: NifLog.warn("Second constraint entity not imported, skipped") continue # get constraint descriptor if isinstance(hkconstraint, NifFormat.bhkRagdollConstraint): hkdescriptor = hkconstraint.ragdoll b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkLimitedHingeConstraint): hkdescriptor = hkconstraint.limited_hinge b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkHingeConstraint): hkdescriptor = hkconstraint.hinge b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkMalleableConstraint): if hkconstraint.type == 7: hkdescriptor = hkconstraint.ragdoll b_hkobj.rigid_body.enabled = False elif hkconstraint.type == 2: hkdescriptor = hkconstraint.limited_hinge b_hkobj.rigid_body.enabled = False else: NifLog.warn("Unknown malleable type ({0}), skipped".format( str(hkconstraint.type))) # extra malleable constraint settings ### damping parameters not yet in Blender Python API ### tau (force between bodies) not supported by Blender else: NifLog.warn("Unknown constraint type ({0}), skipped".format( hkconstraint.__class__.__name__)) continue # add the constraint as a rigid body joint b_constr = b_hkobj.constraints.new('RIGID_BODY_JOINT') b_constr.name = b_hkobj.name b_constr.show_pivot = True # note: rigidbodyjoint parameters (from Constraint.c) # CONSTR_RB_AXX 0.0 # CONSTR_RB_AXY 0.0 # CONSTR_RB_AXZ 0.0 # CONSTR_RB_EXTRAFZ 0.0 # CONSTR_RB_MAXLIMIT0 0.0 # CONSTR_RB_MAXLIMIT1 0.0 # CONSTR_RB_MAXLIMIT2 0.0 # CONSTR_RB_MAXLIMIT3 0.0 # CONSTR_RB_MAXLIMIT4 0.0 # CONSTR_RB_MAXLIMIT5 0.0 # CONSTR_RB_MINLIMIT0 0.0 # CONSTR_RB_MINLIMIT1 0.0 # CONSTR_RB_MINLIMIT2 0.0 # CONSTR_RB_MINLIMIT3 0.0 # CONSTR_RB_MINLIMIT4 0.0 # CONSTR_RB_MINLIMIT5 0.0 # CONSTR_RB_PIVX 0.0 # CONSTR_RB_PIVY 0.0 # CONSTR_RB_PIVZ 0.0 # CONSTR_RB_TYPE 12 # LIMIT 63 # PARSIZEY 63 # TARGET [Object "capsule.002"] # limit 3, 4, 5 correspond to angular limits along x, y and z # and are measured in degrees # pivx/y/z is the pivot point # set constraint target b_constr.target = \ self.nif_import.dict_havok_objects[hkconstraint.entities[1]][0] # set rigid body type (generic) b_constr.pivot_type = 'GENERIC_6_DOF' # limiting parameters (limit everything) b_constr.use_angular_limit_x = True b_constr.use_angular_limit_y = True b_constr.use_angular_limit_z = True # get pivot point pivot = mathutils.Vector( (hkdescriptor.pivot_a.x * self.HAVOK_SCALE, hkdescriptor.pivot_a.y * self.HAVOK_SCALE, hkdescriptor.pivot_a.z * self.HAVOK_SCALE)) # get z- and x-axes of the constraint # (also see export_nif.py NifImport.export_constraints) if isinstance(hkdescriptor, NifFormat.RagdollDescriptor): b_constr.pivot_type = 'CONE_TWIST' # for ragdoll, take z to be the twist axis (central axis of the # cone, that is) axis_z = mathutils.Vector( (hkdescriptor.twist_a.x, hkdescriptor.twist_a.y, hkdescriptor.twist_a.z)) # for ragdoll, let x be the plane vector axis_x = mathutils.Vector( (hkdescriptor.plane_a.x, hkdescriptor.plane_a.y, hkdescriptor.plane_a.z)) # set the angle limits # (see http://niftools.sourceforge.net/wiki/Oblivion/Bhk_Objects/Ragdoll_Constraint # for a nice picture explaining this) b_constr.limit_angle_min_x = \ hkdescriptor.plane_min_angle b_constr.limit_angle_max_x = \ hkdescriptor.plane_max_angle b_constr.limit_angle_min_y = \ -hkdescriptor.cone_max_angle b_constr.limit_angle_max_y = \ hkdescriptor.cone_max_angle b_constr.limit_angle_min_z = \ hkdescriptor.twist_min_angle b_constr.limit_angle_max_z = \ hkdescriptor.twist_max_angle b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor): # for hinge, y is the vector on the plane of rotation defining # the zero angle axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x, hkdescriptor.perp_2_axle_in_a_1.y, hkdescriptor.perp_2_axle_in_a_1.z)) # for hinge, take x to be the the axis of rotation # (this corresponds with Blender's convention for hinges) axis_x = mathutils.Vector( (hkdescriptor.axle_a.x, hkdescriptor.axle_a.y, hkdescriptor.axle_a.z)) # for hinge, z is the vector on the plane of rotation defining # the positive direction of rotation axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x, hkdescriptor.perp_2_axle_in_a_2.y, hkdescriptor.perp_2_axle_in_a_2.z)) # they should form a orthogonal basis if (mathutils.Vector.cross(axis_x, axis_y) - axis_z).length > 0.01: # either not orthogonal, or negative orientation if (mathutils.Vector.cross(-axis_x, axis_y) - axis_z).length > 0.01: NifLog.warn( "Axes are not orthogonal in {0}; Arbitrary orientation has been chosen" .format(hkdescriptor.__class__.__name__)) axis_z = mathutils.Vector.cross(axis_x, axis_y) else: # fix orientation NifLog.warn( "X axis flipped in {0} to fix orientation".format( hkdescriptor.__class__.__name__)) axis_x = -axis_x # getting properties with no blender constraint # equivalent and setting as obj properties b_constr.limit_angle_max_x = hkdescriptor.max_angle b_constr.limit_angle_min_x = hkdescriptor.min_angle b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction if hasattr(hkconstraint, "tau"): b_hkobj.niftools_constraint.tau = hkconstraint.tau b_hkobj.niftools_constraint.damping = hkconstraint.damping elif isinstance(hkdescriptor, NifFormat.HingeDescriptor): # for hinge, y is the vector on the plane of rotation defining # the zero angle axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x, hkdescriptor.perp_2_axle_in_a_1.y, hkdescriptor.perp_2_axle_in_a_1.z)) # for hinge, z is the vector on the plane of rotation defining # the positive direction of rotation axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x, hkdescriptor.perp_2_axle_in_a_2.y, hkdescriptor.perp_2_axle_in_a_2.z)) # take x to be the the axis of rotation # (this corresponds with Blender's convention for hinges) axis_x = mathutils.Vector.cross(axis_y, axis_z) b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction else: raise ValueError("unknown descriptor %s" % hkdescriptor.__class__.__name__) # transform pivot point and constraint matrix into object # coordinates # (also see export_nif.py NifImport.export_constraints) # the pivot point v is in hkbody coordinates # however blender expects it in object 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 * R * B * B'^{-1} * T^{-1} * O^{-1} # the local rotation L at the pivot point must be such that # (axis_z + v) * R * B = ([0 0 1] * L + v') * O * T * B' # so (taking the rotation parts of all matrices!!!) # [0 0 1] * L = axis_z * R * B * B'^{-1} * T^{-1} * O^{-1} # and similarly # [1 0 0] * L = axis_x * R * B * B'^{-1} * T^{-1} * O^{-1} # hence these give us the first and last row of L # which is exactly enough to provide the euler angles # multiply with rigid body transform if isinstance(hkbody, NifFormat.bhkRigidBodyT): # set rotation transform = mathutils.Quaternion( (hkbody.rotation.w, hkbody.rotation.x, hkbody.rotation.y, hkbody.rotation.z)).to_matrix() transform.resize_4x4() # set translation transform[0][3] = hkbody.translation.x * self.HAVOK_SCALE transform[1][3] = hkbody.translation.y * self.HAVOK_SCALE transform[2][3] = hkbody.translation.z * self.HAVOK_SCALE # apply transform pivot = pivot * transform transform = transform.to_3x3() axis_z = axis_z * transform axis_x = axis_x * transform # TODO: update this to use the new bone system # # next, cancel out bone matrix correction # # note that B' = X * B with X = self.nif_import.dict_bones_extra_matrix[B] # # so multiply with the inverse of X # for niBone in self.nif_import.dict_bones_extra_matrix: # if niBone.collision_object \ # and niBone.collision_object.body is hkbody: # transform = mathutils.Matrix( # self.nif_import.dict_bones_extra_matrix[niBone]) # transform.invert() # pivot = pivot * transform # transform = transform.to_3x3() # axis_z = axis_z * transform # axis_x = axis_x * transform # break # cancel out bone tail translation if b_hkobj.parent_bone: pivot[1] -= b_hkobj.parent.data.bones[ b_hkobj.parent_bone].length # cancel out object transform transform = mathutils.Matrix(b_hkobj.matrix_local) transform.invert() pivot = pivot * transform transform = transform.to_3x3() axis_z = axis_z * transform axis_x = axis_x * transform # set pivot point b_constr.pivot_x = pivot[0] b_constr.pivot_y = pivot[1] b_constr.pivot_z = pivot[2] # set euler angles constr_matrix = mathutils.Matrix( (axis_x, mathutils.Vector.cross(axis_z, axis_x), axis_z)) constr_euler = constr_matrix.to_euler() b_constr.axis_x = constr_euler.x b_constr.axis_y = constr_euler.y b_constr.axis_z = constr_euler.z # DEBUG assert ((axis_x - mathutils.Vector( (1, 0, 0)) * constr_matrix).length < 0.0001) assert ((axis_z - mathutils.Vector( (0, 0, 1)) * constr_matrix).length < 0.0001) # the generic rigid body type is very buggy... so for simulation # purposes let's transform it into ball and hinge if isinstance(hkdescriptor, NifFormat.RagdollDescriptor): # cone_twist b_constr.pivot_type = 'CONE_TWIST' elif isinstance( hkdescriptor, (NifFormat.LimitedHingeDescriptor, NifFormat.HingeDescriptor)): # (limited) hinge b_constr.pivot_type = 'HINGE' else: raise ValueError("unknown descriptor %s" % hkdescriptor.__class__.__name__)
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 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_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_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 import_bhkcapsule_shape(self, bhkshape): """Import a BhkCapsule block as a simple cylinder collision object""" b_radius = bhkshape.radius # create capsule mesh length = (bhkshape.first_point - bhkshape.second_point).norm() minx = miny = -b_radius * self.HAVOK_SCALE maxx = maxy = +b_radius * self.HAVOK_SCALE minz = -(length + 2 * b_radius) * (self.HAVOK_SCALE / 2) maxz = +(length + 2 * b_radius) * (self.HAVOK_SCALE / 2) b_mesh = bpy.data.meshes.new('capsule') vert_list = {} vert_index = 0 for x in [minx, maxx]: for y in [miny, maxy]: for z in [minz, maxz]: b_mesh.vertices.add(1) b_mesh.vertices[-1].co = (x, y, z) vert_list[vert_index] = [x, y, z] vert_index += 1 poly_gens = [[0, 1, 3, 2], [6, 7, 5, 4], [0, 2, 6, 4], [3, 1, 5, 7], [4, 5, 1, 0], [7, 6, 2, 3]] b_mesh = poly_gen.col_poly_gen(b_mesh, poly_gens) # Recalculate mesh to render correctly b_mesh.validate() b_mesh.update() # link box to scene and set transform b_obj = bpy.data.objects.new('Capsule', b_mesh) bpy.context.scene.objects.link(b_obj) scn = bpy.context.scene scn.objects.active = b_obj # set bounds type b_obj.draw_type = 'BOUNDS' b_obj.draw_bounds_type = 'CAPSULE' b_obj.game.use_collision_bounds = True b_obj.game.collision_bounds_type = 'CAPSULE' b_obj.game.radius = bhkshape.radius * self.HAVOK_SCALE b_obj.nifcollision.havok_material = NifFormat.HavokMaterial._enumkeys[ bhkshape.material] # find transform if length > NifOp.props.epsilon: normal = (bhkshape.first_point - bhkshape.second_point) / length normal = mathutils.Vector((normal.x, normal.y, normal.z)) else: NifLog.warn( "BhkCapsuleShape with identical points: using arbitrary axis") normal = mathutils.Vector((0, 0, 1)) minindex = min((abs(x), i) for i, x in enumerate(normal))[1] orthvec = mathutils.Vector([(1 if i == minindex else 0) for i in (0, 1, 2)]) vec1 = mathutils.Vector.cross(normal, orthvec) vec1.normalize() vec2 = mathutils.Vector.cross(normal, vec1) # the rotation matrix should be such that (0,0,1) maps to normal transform = mathutils.Matrix([vec1, vec2, normal]).transposed() transform.resize_4x4() transform[0][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.x + bhkshape.second_point.x) transform[1][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.y + bhkshape.second_point.y) transform[2][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.z + bhkshape.second_point.z) b_obj.matrix_local = transform # Recalculate mesh to render correctly b_mesh = b_obj.data b_mesh.validate() b_mesh.update() b_obj.select = True # return object return [b_obj]
def import_mesh_controllers(self, n_node, b_obj): """Import mesh controller for blender object.""" morphCtrl = nif_utils.find_controller( n_node, NifFormat.NiGeomMorpherController) if morphCtrl: b_mesh = b_obj.data morphData = morphCtrl.data if morphData.num_morphs: fps = bpy.context.scene.render.fps # insert base key at frame 1, using relative keys b_mesh.insertKey(1, 'relative') # get name for base key keyname = morphData.morphs[0].frame_name if not keyname: keyname = 'Base' # set name for base key b_mesh.key.blocks[0].name = keyname # get base vectors and import all morphs baseverts = morphData.morphs[0].vectors b_ipo = Blender.Ipo.New('Key', 'KeyIpo') b_mesh.key.ipo = b_ipo for idxMorph in range(1, morphData.num_morphs): # get name for key keyname = morphData.morphs[idxMorph].frame_name if not keyname: keyname = 'Key %i' % idxMorph NifLog.info("Inserting key '{0}'".format(keyname)) # get vectors morphverts = morphData.morphs[idxMorph].vectors # for each vertex calculate the key position from base # pos + delta offset assert (len(baseverts) == len(morphverts) == len(v_map)) for bv, mv, b_v_index in zip(baseverts, morphverts, v_map): base = mathutils.Vector(bv.x, bv.y, bv.z) delta = mathutils.Vector(mv.x, mv.y, mv.z) v = base + delta if applytransform: v *= transform b_mesh.vertices[b_v_index].co[0] = v.x b_mesh.vertices[b_v_index].co[1] = v.y b_mesh.vertices[b_v_index].co[2] = v.z # update the mesh and insert key b_mesh.insertKey(idxMorph, 'relative') # set name for key b_mesh.key.blocks[idxMorph].name = keyname # set up the ipo key curve try: b_curve = b_ipo.addCurve(keyname) except ValueError: # this happens when two keys have the same name # an instance of this is in fallout 3 # meshes/characters/_male/skeleton.nif HeadAnims:0 NifLog.warn( "Skipped duplicate of key '{0}'".format(keyname)) # no idea how to set up the bezier triples -> switching # to linear instead b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR # select extrapolation b_curve.extend = self.get_extend_from_flags( morphCtrl.flags) # set up the curve's control points # first find the keys # older versions store keys in the morphData morphkeys = morphData.morphs[idxMorph].keys # newer versions store keys in the controller if (not morphkeys) and morphCtrl.interpolators: morphkeys = morphCtrl.interpolators[ idxMorph].data.data.keys for key in morphkeys: x = key.value frame = 1 + int(key.time * fps + 0.5) b_curve.addBezier((frame, x)) # finally: return to base position for bv, b_v_index in zip(baseverts, v_map): base = mathutils.Vector(bv.x, bv.y, bv.z) if applytransform: base *= transform b_mesh.vertices[b_v_index].co[0] = base.x b_mesh.vertices[b_v_index].co[1] = base.y b_mesh.vertices[b_v_index].co[2] = base.z
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_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)