def test_bounding_box_bsbound(self): """Oblivion bounding box (BSBound) test.""" def check_bsbound(root_blocks): bsbound = root_blocks[0].extra_data_list[0] assert (isinstance(bsbound, NifFormat.BSBound)) assert (bsbound.name == "BBX") assert (bsbound.next_extra_data is None) # using assert_equal because we compare floats self.assert_equal(bsbound.center.x, 0.0) self.assert_equal(bsbound.center.y, 0.0) self.assert_equal(bsbound.center.z, 66.2201843262) self.assert_equal(bsbound.dimensions.x, 23.0976696014) self.assert_equal(bsbound.dimensions.y, 17.6446208954) self.assert_equal(bsbound.dimensions.z, 66.2201843262) # import with closing(open('test/nif/bounding_box_bsbound.nif')) as stream: self.info("Reading test/nif/bounding_box_bsbound.nif") nif = NifFormat.Data() nif.read(stream) check_bsbound(nif.roots) nif_import = self.test(filename='test/nif/bounding_box_bsbound.nif') b_bbox = Blender.Object.Get("BSBound") # test stuff assert (b_bbox.display_bounds_type == 'BOX') # export nif_export = self.test(filename='test/nif/_bounding_box_bsbound.nif', config=dict(game='OBLIVION'), selection=['BSBound']) # test stuff... with closing(open('test/nif/_bounding_box_bsbound.nif')) as stream: self.info("Reading test/nif/_bounding_box_bsbound.nif") nif = NifFormat.Data() nif.read(stream) check_bsbound(nif.roots)
def build_nif_matrix(cls): n_mat = NifFormat.Matrix44() translation = (2.0, 3.0, 4.0) scale = 2.0 n_rhs_rot_x = (1.0, 0.0, 0.0, 0.0, 0.866, 0.5, 0.0, -0.5, 0.866) n_rhs_rot_y = (0.5, 0.0, -0.866, 0.0, 1.0, 0.0, 0.866, 0.0, 0.5) n_rhs_rot_z = (0, 1, 0, -1, 0, 0, 0, 0, 1) n_rhs_rot_x = cls.create_matrix(n_rhs_rot_x) n_rhs_rot_y = cls.create_matrix(n_rhs_rot_y) n_rhs_rot_z = cls.create_matrix(n_rhs_rot_z) n_mat33 = n_rhs_rot_z * n_rhs_rot_y * n_rhs_rot_x n_vec3 = NifFormat.Vector3() n_vec3.x = translation[0] n_vec3.y = translation[1] n_vec3.z = translation[2] n_mat.set_scale_rotation_translation(scale, n_mat33, n_vec3) return n_mat
def n_create_blocks(n_data): n_ninode_1 = NifFormat.NiNode() n_ninode_2 = NifFormat.NiNode() n_data.roots = [n_ninode_1] with ref(n_ninode_1) as n_ninode: n_ninode.name = b'Scene Root' n_ninode.num_children = 1 n_ninode.children.update_size() n_ninode.children[0] = n_ninode_2 with ref(n_ninode_2) as n_ninode: n_ninode.name = b'NifObject' n_ninode.flags = 14 with ref(n_ninode.translation) as n_vector3: n_vector3.x = 20 n_vector3.y = 20 n_vector3.z = 20 with ref(n_ninode.rotation) as n_matrix33: n_matrix33.m_11 = -1.43774e-14 n_matrix33.m_21 = -0.866025 n_matrix33.m_31 = 0.5 n_matrix33.m_12 = 0.5 n_matrix33.m_22 = 0.433013 n_matrix33.m_32 = 0.75 n_matrix33.m_13 = -0.866025 n_matrix33.m_23 = 0.25 n_matrix33.m_33 = 0.433013 assert (n_matrix33.is_rotation() ) # make sure in case we change values: n_ninode.scale = 0.75 return n_data
def build_nif_matrix(cls): n_mat = NifFormat.Matrix44() translation = (2.0, 3.0, 4.0) scale = 2.0 rhsrotx = (1.0, 0.0, 0.0, 0.0, 0.866, 0.5, 0.0, -0.5, 0.866) rhsroty = (0.5, 0.0, -0.866, 0.0, 1.0, 0.0, 0.866, 0.0, 0.5) rhsrotz = (0, 1, 0, -1, 0, 0, 0, 0, 1) rhsrotx = cls.create_matrix(rhsrotx) rhsroty = cls.create_matrix(rhsroty) rhsrotz = cls.create_matrix(rhsrotz) n_mat33 = rhsrotz * rhsroty * rhsrotx n_vec3 = NifFormat.Vector3() n_vec3.x = translation[0] n_vec3.y = translation[1] n_vec3.z = translation[2] n_mat.set_scale_rotation_translation(scale, n_mat33, n_vec3) return n_mat
def branchentry(self, branch): if not isinstance(branch, NifFormat.bhkConvexVerticesShape): # keep recursing return True else: self.toaster.msg("checking vertices and planes") for v4 in branch.vertices: v = NifFormat.Vector3() v.x = v4.x v.y = v4.y v.z = v4.z num_intersect = 0 for n4 in branch.normals: n = NifFormat.Vector3() n.x = n4.x n.y = n4.y n.z = n4.z d = n4.w if abs(v * n + d) < 0.01: num_intersect += 1 if num_intersect == 0: self.toaster.logger.error( "vertex %s does not intersect with any plane" % v) elif num_intersect == 1: self.toaster.logger.warn( "vertex %s only intersects with one plane" % v) elif num_intersect == 2: self.toaster.logger.warn( "vertex %s only intersects with two planes" % v) # stop recursing return False
def test_mw_nifxnifkf(self): """Test the nif xnif kf export option.""" def check_ctrl_flags(root): # test the kfctrl flags to be active + clamp for ctrl in root.get_global_iterator(): if not isinstance(ctrl, NifFormat.NiTimeController): continue if ctrl.flags != 12: raise ValueError("bad value for controller flags") # import a nif with animation dance = self.test(filename='test/nif/mw/dance.nif') check_ctrl_flags(dance.root_blocks[0]) # export as nif + xnif + kf self.test(filename='test/nif/mw/_testnifxnifkf.nif', config=dict(game='MORROWIND', animation='ALL_NIF_XNIF_XKF'), selection=['Dance'], next_layer=True) # check that these files are present, and check some of their properties with closing(open('test/nif/mw/_testnifxnifkf.nif')) as stream: self.info("Reading test/nif/mw/_testnifxnifkf.nif") nif = NifFormat.Data() nif.read(stream) with closing(open('test/nif/mw/x_testnifxnifkf.nif')) as stream: self.info("Reading test/nif/mw/x_testnifxnifkf.nif") xnif = NifFormat.Data() xnif.read(stream) with closing(open('test/nif/mw/x_testnifxnifkf.kf')) as stream: self.info("Reading test/nif/mw/x_testnifxnifkf.kf") xkf = NifFormat.Data() xkf.read(stream) # check root blocks assert (len(nif.roots) == 1) assert (len(xnif.roots) == 1) assert (len(xkf.roots) == 1) assert (isinstance(nif.roots[0], NifFormat.NiNode)) assert (isinstance(xnif.roots[0], NifFormat.NiNode)) assert (isinstance(xkf.roots[0], NifFormat.NiSequenceStreamHelper)) # compare text keys nif_textkeys = nif.roots[0].extra_data xkf_textkeys = xkf.roots[0].extra_data assert (isinstance(nif_textkeys, NifFormat.NiTextKeyExtraData)) assert (isinstance(xkf_textkeys, NifFormat.NiTextKeyExtraData)) #assert(nif_textkeys == xkf_textkeys) # ... up to extra data chain # check that xkf has no target set in keyframe controller ctrl = xkf.roots[0].controller while ctrl: if ctrl.target is not None: raise ValueError( "NiKeyframeController target should be None in xkf") ctrl = ctrl.next_controller # check controller flags check_ctrl_flags(xkf.roots[0])
def export_inventory_marker(n_root, root_objects): if bpy.context.scene.niftools_scene.game in ('SKYRIM', ): for root_object in root_objects: if root_object.niftools_bs_invmarker: for extra_item in n_root.extra_data_list: if isinstance(extra_item, NifFormat.BSInvMarker): raise 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 % (2 * pi)) * 1000 n_extra_list.rotation_y = ( -root_object.niftools_bs_invmarker[0].bs_inv_y % (2 * pi)) * 1000 n_extra_list.rotation_z = ( -root_object.niftools_bs_invmarker[0].bs_inv_z % (2 * pi)) * 1000 n_extra_list.zoom = root_object.niftools_bs_invmarker[ 0].bs_inv_zoom n_root.add_extra_data(n_extra_list)
def __init__(self): """Initialize the test.""" Base.__init__(self) self.n_data = NifFormat.Data() fp = INTEGRATION_ROOT root = os.path.join(fp, "gen") nif_path = os.path.join(root, "nif", self.g_path) nif_file_path = nif_path + os.path.sep + self.g_name self.n_filepath_0 = nif_file_path + "_py_code.nif" self.n_filepath_1 = nif_file_path + "_export_py_code.nif" self.n_filepath_2 = nif_file_path + "_export_user_ver.nif" blend_path = os.path.join(root, "autoblend", self.g_path) blend_file_path = blend_path + os.path.sep + self.g_name self.b_filepath_0 = blend_file_path + "_pycode_import.blend" self.b_filepath_1 = blend_file_path + "_userver.blend" self.b_filepath_2 = blend_file_path + "_userver_reimport.blend" self.b_filepath_except = blend_file_path + "_exception.blend" if not os.path.exists(nif_path): os.makedirs(nif_path) if not os.path.exists(blend_path): os.makedirs(blend_path)
def n_attach_material_prop(self, n_block): '''Attach a NiMaterialProperty to a blocks properties array at pos[0]''' n_nimaterialprop = NifFormat.NiMaterialProperty() # add property to top of list n_block.properties.reverse() n_block.num_properties += 1 n_block.properties.update_size() n_block.properties[-1] = n_nimaterialprop n_block.properties.reverse() n_nimaterialprop.name = b'Material' with ref(n_nimaterialprop.ambient_color) as ambient_color: ambient_color.r = 1.0 ambient_color.g = 1.0 ambient_color.b = 1.0 with ref(n_nimaterialprop.diffuse_color) as diffuse_color: diffuse_color.r = 1.0 diffuse_color.g = 1.0 diffuse_color.b = 1.0 with ref(n_nimaterialprop.emissive_color) as emissive_color: emissive_color.r = 0.5 n_nimaterialprop.glossiness = 25.0 n_nimaterialprop.alpha = 1.0 return n_block
def n_attach_bhkconvextransform(n_bhkshape): '''Attaches a bhkTransform shape to store transform information''' n_bhkconvextransformshape = NifFormat.bhkConvexTransformShape() n_bhkshape.shape = n_bhkconvextransformshape with ref(n_bhkconvextransformshape) as n_bhktransform: n_bhktransform.material = NifFormat.HavokMaterial.HAV_MAT_WOOD # 9 n_bhktransform.unknown_float_1 = 0.1 n_bhktransform.unknown_8_bytes.update_size() n_bhktransform.unknown_8_bytes[0] = 96 n_bhktransform.unknown_8_bytes[1] = 120 n_bhktransform.unknown_8_bytes[2] = 53 n_bhktransform.unknown_8_bytes[3] = 19 n_bhktransform.unknown_8_bytes[4] = 24 n_bhktransform.unknown_8_bytes[5] = 9 n_bhktransform.unknown_8_bytes[6] = 253 n_bhktransform.unknown_8_bytes[7] = 4 with ref(n_bhktransform.transform) as n_matrix44: n_matrix44.m_11 = -2.23517e-08 n_matrix44.m_21 = 0.649519 n_matrix44.m_31 = 0.375 n_matrix44.m_12 = -0.375 n_matrix44.m_22 = -0.32476 n_matrix44.m_32 = 0.5625 n_matrix44.m_13 = 0.649519 n_matrix44.m_23 = -0.1875 n_matrix44.m_33 = 0.324759 n_matrix44.m_14 = 2.85714 n_matrix44.m_24 = 2.85714 n_matrix44.m_34 = 2.85714 return n_bhktransform
def export_root_node(self, root_objects, filebase): """ Exports a nif's root node; use blender root if there is only one, else create a meta root """ # TODO [collsion] detect root collision -> root collision node (can be mesh or empty) # self.nif_export.collisionhelper.export_collision(b_obj, n_parent) # return None # done; stop here self.n_root = None # there is only one root object so that will be our final root if len(root_objects) == 1: b_obj = root_objects[0] self.export_node(b_obj, None) # there is more than one root object so we create a meta root else: NifLog.info("Created meta root because blender scene had multiple root objects") self.n_root = types.create_ninode() self.n_root.name = "Scene Root" for b_obj in root_objects: self.export_node(b_obj, self.n_root) # TODO [object] How dow we know we are selecting the right node in the case of multi-root? # making root block a fade node root_type = b_obj.niftools.rootnode if bpy.context.scene.niftools_scene.game in ('FALLOUT_3', 'SKYRIM') and root_type == 'BSFadeNode': NifLog.info("Making root block a BSFadeNode") fade_root_block = NifFormat.BSFadeNode().deepcopy(self.n_root) fade_root_block.replace_global_node(self.n_root, fade_root_block) self.n_root = fade_root_block # various extra datas object_property = ObjectDataProperty() object_property.export_bsxflags_upb(self.n_root, root_objects) object_property.export_inventory_marker(self.n_root, root_objects) object_property.export_weapon_location(self.n_root, b_obj) types.export_furniture_marker(self.n_root, filebase) return self.n_root
def export(path) -> List[ShapeCollection]: data = NifFormat.Data() with open(path, 'rb') as f: data.inspect(f) data.read(f) objects = [] for root in data.roots: name = root.name.decode('utf-8').lower() # For this project, only hair and head matters if 'hair' in name or 'head' in name: sys.stderr.write("> " + name + "\n") else: sys.stderr.write(" " + name + "\n") continue if isinstance(root, NifFormat.NiNode): objects.append(export_object(root)) # Some NiTriShape's are a root object if isinstance(root, NifFormat.NiTriShape): coll = ShapeCollection(name) coll.shapes = [export_shape(root)] objects.append(coll) return objects
def export_texturing_property(self, flags=0x0001, applymode=None, b_mat=None, b_obj=None): """Export texturing property.""" self.determine_texture_types(b_obj, b_mat) texprop = NifFormat.NiTexturingProperty() texprop.flags = flags texprop.apply_mode = applymode texprop.texture_count = 7 self.export_texture_shader_effect(texprop) self.export_nitextureprop_tex_descs(texprop) # search for duplicate for block in self.nif_export.dict_blocks: if isinstance(block, NifFormat.NiTexturingProperty ) and block.get_hash() == texprop.get_hash(): return block # no texturing property with given settings found, so use and register # the new one return texprop
def export_bs_effect_shader_property(self, b_mat): bsshader = NifFormat.BSEffectShaderProperty() self.texturehelper.export_bs_effect_shader_prop_textures(bsshader) # Alpha # TODO [Shader] Alpha property # 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.v # TODO [shader] Expose a emission multiplier value # bsshader.emissive_multiple = b_mat.emit # Shader Flags BSShaderProperty.export_shader_flags(b_mat, bsshader) return bsshader
def postprocessNif(filename): input_stream = open(filename, "rb") nifdata = NifFormat.Data() try: nifdata.read(input_stream) except Exception as e: # nif-read error, something wrong with NIF, delete... input_stream.close() os.remove(filename) s = "\n\npostprocessNif(): ERROR reading(" + filename + "); deleting file." # print(s) error_list(s) return -1 input_stream.close() try: nifdata = postprocess_nifdata(nifdata) output_stream = open(filename, "wb") nifdata.write(output_stream) output_stream.close() print "PostProcessing(" + filename + ") complete." except Exception as e: s = "\n\nERROR post-processing(" + filename + ")..." # print(s) error_list(s) return 0
def n_attach_bhkcollisionobject(n_ninode): '''Attaches a collision object to the NiNode''' n_bhkcollisionobject = NifFormat.bhkCollisionObject() n_ninode.collision_object = n_bhkcollisionobject #attach to ninode n_bhkcollisionobject.target = n_ninode return n_bhkcollisionobject
def walk_nif(nif_path, use_stdout=True): if not path.exists(nif_path): exit("Path `{0}` not found.".format(nif_path)) all_assets = [] for stream, data in NifFormat.walkData(nif_path): try: if use_stdout: print(stream.name, sep='', end=', ', file=stdout, flush=True) data.read(stream) assets = [] find_external_assets(data.roots, assets) assets = set(assets) # remove duplicates assets_string = "{0}".format( b', '.join(assets).decode(encoding="ISO-8859-1")) all_assets.append(assets_string) if use_stdout: print(assets_string, sep=', ', end='\n', file=stdout, flush=True) except ValueError as ex: print("\n Error with {0}: {1}".format(stream.name, str(ex.args)), sep='', end='\n', file=stdout, flush=True) except Exception as ex: print(ex) raise return all_assets
def branchentry(self, branch): if isinstance(branch, NifFormat.bhkRigidBody): if isinstance(branch.shape, (NifFormat.bhkNiTriStripsShape, NifFormat.bhkPackedNiTriStripsShape)): colmopp = NifFormat.bhkMoppBvTreeShape() colmopp.material = branch.shape.material colmopp.unknown_8_bytes[0] = 160 colmopp.unknown_8_bytes[1] = 13 colmopp.unknown_8_bytes[2] = 75 colmopp.unknown_8_bytes[3] = 1 colmopp.unknown_8_bytes[4] = 192 colmopp.unknown_8_bytes[5] = 207 colmopp.unknown_8_bytes[6] = 144 colmopp.unknown_8_bytes[7] = 11 colmopp.unknown_float = 1.0 if isinstance(branch.shape, NifFormat.bhkNiTriStripsShape): branch.shape = branch.shape.get_interchangeable_packed_shape( ) colmopp.shape = branch.shape branch.shape = colmopp self.changed = True branch.shape.update_mopp() self.toaster.msg("collision set to MOPP") # Don't need to recurse further return False else: # recurse further return True
def run(self): # fo3 body path fo3_male = os.path.join(self.config.get("path", "fallout3"), "meshes", "characters", "_male") nif_import = self.test(filename=os.path.join(fo3_male, "skeleton.nif"), config=dict(IMPORT_ANIMATION=True)) nif_export = self.test(filename='test/nif/fo3/_skeleton.nif', config=dict(game='FALLOUT_3', EXPORT_SMOOTHOBJECTSEAMS=True, EXPORT_FLATTENSKIN=True), selection=['Scene Root']) # open original nif to get rid of possible issues with scale # correction in nif_import data = NifFormat.Data() with open(os.path.join(fo3_male, "skeleton.nif")) as stream: data.read(stream) # compare NiNode transforms for n_imp_node in data.roots[0].tree(): if not isinstance(n_imp_node, NifFormat.NiNode): continue for n_exp_node in nif_export.root_blocks[0].tree(): if not isinstance(n_exp_node, NifFormat.NiNode): continue if n_imp_node.name != n_exp_node.name: continue # check that transforms are equal self.info("checking transform of %s" % n_imp_node.name) if (n_imp_node.get_transform() != n_exp_node.get_transform()): raise ValueError("transforms are different\n%s\n%s\n" % (n_imp_node.get_transform(), n_exp_node.get_transform()))
def import_egm_morphs(self, egmdata, b_obj, v_map, n_verts): """Import all EGM morphs as shape keys for blender object.""" # XXX if there is an egm, the assumption is that there is only one # XXX mesh in the nif b_mesh = b_obj.data sym_morphs = [list(morph.get_relative_vertices()) for morph in egmdata.sym_morphs] asym_morphs = [list(morph.get_relative_vertices()) for morph in egmdata.asym_morphs] # insert base key at frame 1, using absolute keys sk_basis = b_obj.shape_key_add("Basis") b_mesh.shape_keys.use_relative = False morphs = ([(morph, "EGM SYM %i" % i) for i, morph in enumerate(sym_morphs)] + [(morph, "EGM ASYM %i" % i) for i, morph in enumerate(asym_morphs)]) for morphverts, keyname in morphs: #convert tuples into vector here so we can simply add in morph_mesh() morphvert_out = [] for u in morphverts: v = NifFormat.Vector3() v.x, v.y, v.z = u morphvert_out.append(v) self.morph_mesh(b_mesh, n_verts, morphvert_out, v_map) shape_key = b_obj.shape_key_add(keyname, from_mix=False)
def import_bone_bind(self, n_block, b_armature_data, n_armature, b_parent_bone=None): """Adds a bone to the armature in edit mode.""" # check that n_block is indeed a bone if not self.is_bone(n_block): return None # bone name bone_name = block_store.import_name(n_block) # create a new bone b_edit_bone = b_armature_data.edit_bones.new(bone_name) # store nif block for access from object mode self.name_to_block[b_edit_bone.name] = n_block # get the nif bone's armature space matrix (under the hood all bone space matrixes are multiplied together) n_bind = math.nifformat_to_mathutils_matrix(self.bind_store.get(n_block, NifFormat.Matrix44())) # get transformation in blender's coordinate space b_bind = math.nif_bind_to_blender_bind(n_bind) # set the bone matrix - but set the tail first to prevent issues with zero-length bone b_edit_bone.tail = mathutils.Vector([0, 0, 1]) b_edit_bone.matrix = b_bind # link to parent if b_parent_bone: b_edit_bone.parent = b_parent_bone # import and parent bone children for n_child in n_block.children: self.import_bone_bind(n_child, b_armature_data, n_armature, b_edit_bone)
def branchentry(self, branch): if isinstance(branch, NifFormat.bhkRigidBody): if isinstance(branch.shape, (NifFormat.bhkNiTriStripsShape, NifFormat.bhkPackedNiTriStripsShape)): colmopp = NifFormat.bhkMoppBvTreeShape() colmopp.material = branch.shape.material colmopp.unknown_8_bytes[0] = 160 colmopp.unknown_8_bytes[1] = 13 colmopp.unknown_8_bytes[2] = 75 colmopp.unknown_8_bytes[3] = 1 colmopp.unknown_8_bytes[4] = 192 colmopp.unknown_8_bytes[5] = 207 colmopp.unknown_8_bytes[6] = 144 colmopp.unknown_8_bytes[7] = 11 colmopp.unknown_float = 1.0 if isinstance(branch.shape, NifFormat.bhkNiTriStripsShape): branch.shape = branch.shape.get_interchangeable_packed_shape() colmopp.shape = branch.shape branch.shape = colmopp self.changed = True branch.shape.update_mopp() self.toaster.msg("collision set to MOPP") # Don't need to recurse further return False else: # recurse further return True
def n_attach_material_prop(n_trishape): '''Attaches a NiMaterialProperty to a NiTrishape block property's array at pos[0]''' n_nimaterialprop = NifFormat.NiMaterialProperty() n_nimaterialprop.name = b'Material' with ref(n_nimaterialprop.ambient_color) as n_color3: n_color3.r = 1.0 n_color3.g = 1.0 n_color3.b = 1.0 with ref(n_nimaterialprop.diffuse_color) as n_color3: n_color3.r = 1.0 n_color3.g = 1.0 n_color3.b = 1.0 with ref(n_nimaterialprop.emissive_color) as n_color3: n_color3.r = 0.0 n_color3.g = 0.0 n_color3.b = 0.0 with ref(n_nimaterialprop.specular_color) as n_color3: n_color3.r = 0.0 n_color3.g = 0.0 n_color3.b = 0.0 n_nimaterialprop.glossiness = 12.5 # default nif.xml - 0.0, blender - 12.5 n_nimaterialprop.alpha = 1.0 # default nif.xml - 0.0 # add property to top of list n_trishape.properties.reverse() n_trishape.num_properties += 1 n_trishape.properties.update_size() n_trishape.properties[-1] = n_nimaterialprop n_trishape.properties.reverse()
def import_bone_bind(self, n_block, n_bind_store, b_armature_data, n_armature, b_parent_bone=None): """Adds a bone to the armature in edit mode.""" # check that n_block is indeed a bone if not self.is_bone(n_block): return None # bone name bone_name = block_store.import_name(n_block) # create a new bone b_edit_bone = b_armature_data.edit_bones.new(bone_name) # store nif block for access from object mode self.name_to_block[b_edit_bone.name] = n_block # get the nif bone's armature space matrix (under the hood all bone space matrixes are multiplied together) n_bind = mathutils.Matrix(n_bind_store.get(n_block, NifFormat.Matrix44()).as_list()).transposed() # get transformation in blender's coordinate space b_bind = math.nif_bind_to_blender_bind(n_bind) # the following is a workaround because blender can no longer set matrices to bones directly tail, roll = bpy.types.Bone.AxisRollFromMatrix(b_bind.to_3x3()) b_edit_bone.head = b_bind.to_translation() b_edit_bone.tail = tail + b_edit_bone.head b_edit_bone.roll = roll # link to parent if b_parent_bone: b_edit_bone.parent = b_parent_bone # import and parent bone children for n_child in n_block.children: self.import_bone_bind(n_child, n_bind_store, b_armature_data, n_armature, b_edit_bone)
def n_create_data(): n_data = NifFormat.Data() n_data.version = 0x14000005 n_data.user_version = 11 n_data.user_version_2 = 11 n_create_blocks(n_data) return n_data
def mathutils_to_nifformat_matrix(b_matrix): """Convert a blender matrix to a NifFormat.Matrix44""" # transpose to swap columns for rows so we can use pyffi's set_rows() directly # instead of setting every single value manually n_matrix = NifFormat.Matrix44() n_matrix.set_rows(*b_matrix.transposed()) return n_matrix
def import_skin(ni_block, b_obj): """Import a NiSkinInstance and its contents as vertex groups""" skininst = ni_block.skin_instance if skininst: skindata = skininst.data bones = skininst.bones # the usual case if skindata.has_vertex_weights: bone_weights = skindata.bone_list for idx, n_bone in enumerate(bones): # skip empty bones (see pyffi issue #3114079) if not n_bone: continue vertex_weights = bone_weights[idx].vertex_weights group_name = block_store.import_name(n_bone) if group_name not in b_obj.vertex_groups: v_group = b_obj.vertex_groups.new(name=group_name) for skinWeight in vertex_weights: vert = skinWeight.index weight = skinWeight.weight v_group.add([vert], weight, 'REPLACE') # WLP2 - hides the weights in the partition else: skin_partition = skininst.skin_partition for block in skin_partition.skin_partition_blocks: # create all vgroups for this block's bones block_bone_names = [block_store.import_name(bones[i]) for i in block.bones] for group_name in block_bone_names: b_obj.vertex_groups.new(name=group_name) # go over each vert in this block for vert, vertex_weights, bone_indices in zip(block.vertex_map, block.vertex_weights, block.bone_indices): # assign this vert's 4 weights to its 4 vgroups (at max) for w, b_i in zip(vertex_weights, bone_indices): if w > 0: group_name = block_bone_names[b_i] v_group = b_obj.vertex_groups[group_name] v_group.add([vert], w, 'REPLACE') # import body parts as face maps # get faces (triangles) as map of tuples to index tri_map = {frozenset(polygon.vertices): polygon.index for polygon in b_obj.data.polygons} if isinstance(skininst, NifFormat.BSDismemberSkinInstance): skinpart = ni_block.get_skin_partition() for bodypart, skinpartblock in zip(skininst.partitions, skinpart.skin_partition_blocks): bodypart_wrap = NifFormat.BSDismemberBodyPartType() bodypart_wrap.set_value(bodypart.body_part) group_name = bodypart_wrap.get_detail_display() # create face map if it did not exist yet if group_name not in b_obj.face_maps: f_group = b_obj.face_maps.new(name=group_name) # add the triangles to the face map f_group.add([tri_map[frozenset(vertices)] for vertices in skinpartblock.get_mapped_triangles()])
def export_uv_controller(self, b_material, n_geom): """Export the material UV controller data.""" # get fcurves - a bit more elaborate here so we can zip with the NiUVData later # nb. these are actually specific to the texture slot in blender # here we don't care and just take the first fcurve that matches fcurves = [] for dp, ind in (("offset", 0), ("offset", 1), ("scale", 0), ("scale", 1)): for fcu in b_material.animation_data.action.fcurves: if dp in fcu.data_path and fcu.array_index == ind: fcurves.append(fcu) break else: fcurves.append(None) # continue if at least one fcurve exists if not any(fcurves): return # get the uv curves and translate them into nif data n_uv_data = NifFormat.NiUVData() for fcu, n_uv_group in zip(fcurves, n_uv_data.uv_groups): if fcu: NifLog.debug(f"Exporting {fcu} as NiUVData") n_uv_group.num_keys = len(fcu.keyframe_points) n_uv_group.interpolation = NifFormat.KeyType.LINEAR_KEY n_uv_group.keys.update_size() for b_point, n_key in zip(fcu.keyframe_points, n_uv_group.keys): # add each point of the curve b_frame, b_value = b_point.co if "offset" in fcu.data_path: # offsets are negated in blender b_value = -b_value n_key.arg = n_uv_group.interpolation n_key.time = b_frame / self.fps n_key.value = b_value # if uv data is present then add the controller so it is exported if fcurves[0].keyframe_points: n_uv_ctrl = NifFormat.NiUVController() self.set_flags_and_timing(n_uv_ctrl, fcurves) n_uv_ctrl.data = n_uv_data # attach block to geometry n_geom.add_controller(n_uv_ctrl)
def test_nonaccum_export(self): """Test the nonaccum xy export option.""" # import a nif with animation dance = self.test( filename = 'test/nif/mw/dance.nif') # export as nif with animation, default self.test( filename='test/nif/ob/_testnonaccum_default.nif', config=dict(game='OBLIVION', EXPORT_NONACCUM=0), selection=['Dance'], next_layer=False) # export as nif with animation, accum xy self.test( filename='test/nif/ob/_testnonaccum_accumxy.nif', config=dict(game='OBLIVION', EXPORT_NONACCUM=1), selection=['Dance'], next_layer=False) # export as nif with animation, no accum self.test( filename='test/nif/ob/_testnonaccum_accumnone.nif', config=dict(game='OBLIVION', EXPORT_NONACCUM=2), selection=['Dance'], next_layer=False) # check that these files are present, and check some of their properties with closing(open('test/nif/ob/_testnonaccum_default.nif')) as stream: self.info("Reading test/nif/ob/_testnonaccum_default.nif") nif_default = NifFormat.Data() nif_default.read(stream) with closing(open('test/nif/ob/_testnonaccum_accumxy.nif')) as stream: self.info("Reading test/nif/ob/_testnonaccum_accumxy.nif") nif_xy = NifFormat.Data() nif_xy.read(stream) with closing(open('test/nif/ob/_testnonaccum_accumnone.nif')) as stream: self.info("Reading test/nif/ob/_testnonaccum_accumnone.nif") nif_none = NifFormat.Data() nif_none.read(stream) # check root blocks assert(len(nif_default.roots) == 1) assert(len(nif_xy.roots) == 1) assert(len(nif_none.roots) == 1) assert(isinstance(nif_default.roots[0], NifFormat.NiNode)) assert(isinstance(nif_xy.roots[0], NifFormat.NiNode)) assert(isinstance(nif_none.roots[0], NifFormat.NiNode))
def export_material_uv_controller(self, b_material, n_geom): """Export the material UV controller data.""" # get the material action b_action = b_material.action if not b_action: return # get the uv curves and translate them into nif data n_uvdata = NifFormat.NiUVData() n_times = [] # track all times (used later in start time and end time) b_channels = (Blender.Ipo.MA_OFSX, Blender.Ipo.MA_OFSY, Blender.Ipo.MA_SIZEX, Blender.Ipo.MA_SIZEY) for b_channel, n_uvgroup in zip(b_channels, n_uvdata.uv_groups): b_curve = b_action[b_channel] if b_curve: self.info("Exporting %s as NiUVData" % b_curve) n_uvgroup.num_keys = len(b_curve.bezierPoints) n_uvgroup.interpolation = self.get_n_action_interpolation_from_b_action_interpolation( b_curve.interpolation) n_uvgroup.keys.update_size() for b_point, n_key in zip(b_curve.bezierPoints, n_uvgroup.keys): # add each point of the curve b_time, b_value = b_point.pt if b_channel in (Blender.Ipo.MA_OFSX, Blender.Ipo.MA_OFSY): # offsets are negated in blender b_value = -b_value n_key.arg = n_uvgroup.interpolation n_key.time = (b_time - 1) * self.context.scene.render.fps n_key.value = b_value # track time n_times.append(n_key.time) # save extend mode to export later b_curve_extend = b_curve.extend # if uv data is present (we check this by checking if times were added) # then add the controller so it is exported if n_times: n_uvctrl = NifFormat.NiUVController() n_uvctrl.flags = 8 # active n_uvctrl.flags |= self.get_flags_from_extend(b_curve_extend) n_uvctrl.frequency = 1.0 n_uvctrl.start_time = min(n_times) n_uvctrl.stop_time = max(n_times) n_uvctrl.data = n_uvdata # attach block to geometry n_geom.add_controller(n_uvctrl)
def n_create_stencil_prop(n_trishapedata): n_nistencilproperty = NifFormat.NiStencilProperty() n_trishapedata.properties.reverse() # ensure property is at top of list n_trishapedata.num_properties += 1 n_trishapedata.properties.update_size() n_trishapedata.properties[-1] = n_nistencilproperty n_trishapedata.properties.reverse()
def LoadNif( filename ): global Version, UserVersion try: print "Reading Nif: %s" % filename f = open( filename, "rb" ) Version, UserVersion = NifFormat.getVersion( f ) if Version >= 0: print "( Version 0x%08X )" % Version root_blocks = NifFormat.read(f, version = Version, user_version = UserVersion, verbose = 0) for block in root_blocks: AddBlock( block ) elif Version == -1: raise NIFImportError( "Unsupported NIF version." ) else: raise NIFImportError( "Not a NIF file." ) except NIFImportError, e: # in that case, we raise a menu instead of an exception print 'NIFImportError: ' + e.value return
def nif_explore(self): oldtime = time.time() #Start the clock :) """NifFormate.walkData searches the directory for files""" for stream, data in NifFormat.walkData(self.search_path): self.index += 1 instance_count = 0; try: data.read(stream) for root in data.roots: self.n_roots.append(root) for block in root.tree(): self.n_blocks.append(block) if isinstance(block, self.instance): if stream.name.replace("\\","/") in self.files: self.instance_count += 1 self.instance_cname = stream.name.replace("\\","/") if self.property != None: if getattr(block, self.property): print("%s found in %s" % (self.property, stream.name.replace("\\","/"))) self.files.append(stream.name.replace("\\","/")) else: print("Warning: No property %s found in %s" % (self.property, stream.name.replace("\\","/"))) else: print("Found instance in %s" % stream.name.replace("\\","/")) self.files.append(stream.name.replace("\\","/")) self.fIndex += 1 except Exception: print("Warning: read failed due to corrupt file", ", corrupt format description or bug") for file in self.files: shutil.copy(file, self.result_path) if self.instance_count > 1: print("%s instance blocks in %s" % (self.instance_count, self.instance_cname)) print("Counted %s objects in %ss" % (self.index, time.time()-oldtime)) print("Output folder %s" % self.result_path)
def branchentry(self, branch): """Optimize a vertex based collision block: - remove duplicate vertices - rebuild triangle indice and welding info - update MOPP data if applicable. """ if branch in self.optimized: # already optimized return False # TODO: other collision geometry types if (isinstance(branch, NifFormat.bhkMoppBvTreeShape) and isinstance(branch.shape, NifFormat.bhkPackedNiTriStripsShape) and isinstance(branch.shape.data, NifFormat.hkPackedNiTriStripsData)): if branch.shape.data.num_vertices < 3: self.toaster.msg(_("less than 3 vertices: removing branch")) self.data.replace_global_node(branch, None) self.changed = True return False self.optimize_mopp(branch) # we found a geometry to optimize self.optimized.append(branch) # we're going to change the data self.changed = True return False # don't recurese farther elif (isinstance(branch, NifFormat.bhkRigidBody) and isinstance(branch.shape, NifFormat.bhkNiTriStripsShape)): # convert to a packed shape new_shape = branch.shape.get_interchangeable_packed_shape() if new_shape.data.num_vertices < 3: self.data.replace_global_node(branch, None) self.toaster.msg(_("less than 3 vertices: removing branch")) self.optimized.append(branch) else: self.data.replace_global_node(branch.shape, new_shape) self.toaster.msg(_("collision packed")) # call branchentry again in order to create a mopp for it self.branchentry(branch) self.changed = True # don't recurse further return False elif (isinstance(branch, NifFormat.bhkRigidBody) and isinstance(branch.shape, NifFormat.bhkPackedNiTriStripsShape)): # packed shape without mopp: add a mopp to it if it is static if any(sub_shape.layer != 1 for sub_shape in branch.shape.sub_shapes): # no mopps for non-static objects return False mopp = NifFormat.bhkMoppBvTreeShape() shape = branch.shape # store reference before replacing self.data.replace_global_node(branch.shape, mopp) mopp.shape = shape mopp.material = shape.sub_shapes[0].material mopp.unknown_8_bytes[0] = 160 mopp.unknown_8_bytes[1] = 13 mopp.unknown_8_bytes[2] = 75 mopp.unknown_8_bytes[3] = 1 mopp.unknown_8_bytes[4] = 192 mopp.unknown_8_bytes[5] = 207 mopp.unknown_8_bytes[6] = 144 mopp.unknown_8_bytes[7] = 11 mopp.unknown_float = 1.0 mopp.update_mopp_welding() self.toaster.msg(_("added mopp")) self.changed = True self.optimized.append(branch) return False #keep recursing return True