def ensure_uniform_scale(self): if self.is_bounding_box: return l, r, s = decompose(self.matrix_local) if not np.allclose(s[:1], s[1:], rtol=0, atol=0.001): print(f"[INFO] {self.name} has non-uniform scale") if self.source.type == "MESH": self.vertex_transform = s self.matrix_local = compose(l, r, 1) for child in self.children: child.matrix_local[:3, :3] *= s for child in self.children: child.ensure_uniform_scale()
def correct_rest_positions(self): """Correct the rest pose of the root bone. The rest pose of vanilla assets often feature inconvenient transforms. This is not an issue in-game since you would only ever see actors with their animations applied. When working in Blender however, a sane rest pose will make the lives of artists much easier. This function replaces the root bone's rest matrix with an edited copy of its posed matrix. This edited copy is identical to pose matrix with regards to location and scale, but has had its rotation about all axes aligned to the nearest 90 degree angle. """ if not self.armatures: return root = self.get_armature_node() root_bone = next(self.iter_bones(root)) # calculate corrected transformation matrix l, r, s = decompose(root_bone.matrix_posed) r = nif_utils.snap_rotation(r) corrected_matrix = compose(l, r, s) # only do corrections if they are necessary if np.allclose(root_bone.matrix_world, corrected_matrix, rtol=0, atol=1e-6): return # correct the rest matrix of skinned meshes inverse = la.inv(root_bone.matrix_world) for node in self.get_skinned_meshes(): if root_bone not in node.parents: node.matrix_world = corrected_matrix @ ( inverse @ node.matrix_world) # correct the rest matrix of the root bone root_bone.matrix_world = corrected_matrix
def correct_rest_positions(self): if not self.armatures: return root = self.get(*self.armatures) # __history__ root_bone = next(self.iter_bones(root)) # __history__ # calculate corrected transformation matrix l, r, s = decompose(root_bone.matrix_posed) r = nif_utils.snap_rotation(r) corrected_matrix = compose(l, r, s) # correct the rest matrix of skinned meshes bone_inverse = la.inv(root_bone.matrix_world) for node in self.nodes: skin = getattr(node.source, "skin", None) if skin and (skin.root is root.source) and (root_bone not in node.parents): node.matrix_world = corrected_matrix @ bone_inverse @ node.matrix_world # correct the rest matrix of the root bone root_bone.matrix_world = corrected_matrix
def matrix(self, value): self.center, self.rotation, self.extents = decompose(value)
def create_bounding_volume(self): c, r, e = decompose(self.matrix_world) e *= self.source.empty_display_size self.output.bounding_volume = nif.NiBoxBV(center=c, rotation=r, extents=e) self.output.matrix = ID44
def matrix(self, value: ndarray): self.center, self.axes, self.extents = decompose(value)
def get_mesh_data(self): vertices = self.source.data.vertices normals = self.source.data.normals uv_sets = self.source.data.uv_sets.copy() vertex_colors = self.source.data.vertex_colors vertex_weights = self.source.vertex_weights() vertex_morphs = self.source.vertex_morphs() triangles = self.source.data.triangles if len(normals): # re-unitize, fixes landscape meshes normals /= la.norm(normals, axis=1, keepdims=True) # reconstruct as per-triangle layout normals = normals[triangles].reshape(-1, 3) if len(uv_sets): # convert OpenGL into Blender format uv_sets[..., 1] = 1 - uv_sets[..., 1] # reconstruct as per-triangle layout uv_sets = uv_sets[:, triangles].reshape(-1, triangles.size, 2) if len(vertex_colors): # reconstruct as per-triangle layout vertex_colors = vertex_colors[triangles].reshape(-1, 3) # remove doubles scale = decompose(self.matrix_world)[-1] indices, inverse = nif_utils.unique_rows( vertices * scale, *vertex_weights, *vertex_morphs, precision=self.importer.vertex_precision, ) if len(vertices) > len(indices) > 3: vertices = vertices[indices] vertex_weights = vertex_weights[:, indices] vertex_morphs = vertex_morphs[:, indices] triangles = inverse[triangles] # ''' # Blender does not allow two faces to use identical vertex indices, regardless of order. # This is problematic as such occurances are commonly found throughout most nif data sets. # The usual case is "double-sided" faces, which share vertex indices but differ in winding. # Identify the problematic faces and duplicate their vertices to ensure the indices are unique. uniques, indices = np.unique(np.sort(triangles, axis=1), axis=0, return_index=True) if len(triangles) > len(uniques): # boolean mask of the triangles to be updated target_faces = np.full(len(triangles), True) target_faces[indices] = False # indices of the vertices that must be copied target_verts = triangles[target_faces].ravel() # find the vertices used in problematic faces new_vertices = vertices[target_verts] new_vertex_weights = vertex_weights[:, target_verts] new_vertex_morphs = vertex_morphs[:, target_verts] new_vertex_indices = np.arange(len(new_vertices)) + len(vertices) # update our final mesh data with new geometry vertices = np.vstack((vertices, new_vertices)) vertex_weights = np.hstack((vertex_weights, new_vertex_weights)) vertex_morphs = np.hstack((vertex_morphs, new_vertex_morphs)) # copy is needed since shapes could share data triangles = triangles.copy() triangles[target_faces] = new_vertex_indices.reshape(-1, 3) # ''' return nif_utils.Namespace( triangles=triangles, vertices=vertices, normals=normals, uv_sets=uv_sets, vertex_colors=vertex_colors, vertex_weights=vertex_weights, vertex_morphs=vertex_morphs, )