-
-
Save acidzebra/4a9a8c21c8c92e59b621ce3e20afe54c to your computer and use it in GitHub Desktop.
Create Character NIF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pathlib import Path | |
from es3.utils.math import ID44 | |
from es3 import nif | |
meshes_path = Path("C:/Users/Admin/Games/Morrowind/Data Files/Meshes/") | |
output_path = meshes_path / "g7/export.nif" | |
skins = { | |
"b/b_n_dark elf_m_skins.nif", | |
} | |
bodyparts = { | |
"groin" : "b/b_n_dark elf_m_groin.nif", | |
"hair" : "b/b_n_dark elf_m_hair_01.nif", | |
"head" : "b/b_n_dark elf_m_head_01.nif", | |
"neck" : "b/b_n_dark elf_m_neck.nif", | |
"right ankle" : "b/b_n_dark elf_m_ankle.nif", | |
"right foot" : "b/b_n_dark elf_m_foot.nif", | |
"right forearm" : "b/b_n_dark elf_m_forearm.nif", | |
"right knee" : "b/b_n_dark elf_m_knee.nif", | |
"right upper arm" : "b/b_n_dark elf_m_upper arm.nif", | |
"right upper leg" : "b/b_n_dark elf_m_upper leg.nif", | |
"right wrist" : "b/b_n_dark elf_m_wrist.nif", | |
} | |
shields = { | |
} | |
weapons = { | |
} | |
xbase_anim = nif.NiStream() | |
xbase_anim.load(meshes_path / "xbase_anim.nif") | |
# clean meshes | |
for parent, child in [*xbase_anim.root.descendants_pairs()]: | |
if not isinstance(child, nif.NiNode) and not child.is_shadow: | |
parent.children.remove(child) | |
# -------------- | |
# ATTACH SKINNED | |
# -------------- | |
slots = ( | |
"chest", | |
"groin", | |
"head", | |
"left ankle", | |
"left clavicle", | |
"left foot", | |
"left forearm", | |
"left hand", | |
"left knee", | |
"left upper arm", | |
"left upper leg", | |
"left wrist", | |
"neck", | |
"right ankle", | |
"right clavicle", | |
"right foot", | |
"right forearm", | |
"right hand", | |
"right knee", | |
"right upper arm", | |
"right upper leg", | |
"right wrist", | |
"shield", | |
"weapon", | |
) | |
for file in skins: | |
temp = nif.NiStream() | |
temp.load(meshes_path / file) | |
for shape in temp.objects_of_type(nif.NiTriShape): | |
if not shape.skin: | |
continue | |
if not shape.name.lower().startswith("tri "): | |
continue | |
base_name = shape.name[4:].lower() | |
if base_name.endswith(" 0"): | |
base_name = base_name[:-2] | |
if base_name not in slots: | |
continue | |
i = 0 | |
while shape: | |
# remap root | |
shape.skin.root = xbase_anim.find_object_by_name(shape.skin.root.name) | |
assert shape.skin.root is not None | |
# remap bones | |
for j, bone in enumerate(shape.skin.bones): | |
shape.skin.bones[j] = xbase_anim.find_object_by_name(bone.name) | |
assert shape.skin.bones[j] is not None | |
# remap parent | |
shape.skin.root.children.append(shape) | |
# continue next | |
i += 1 | |
shape = temp.find_object_by_name(f"Tri {base_name} {i}") | |
# ---------------- | |
# ATTACH BODYPARTS | |
# ---------------- | |
for name, mesh in bodyparts.items(): | |
bodypart = nif.NiStream() | |
bodypart.load(meshes_path / mesh) | |
if name == "hair": | |
name = "head" # dunno if this is right | |
parent_node = xbase_anim.find_object_by_name(name) | |
# fix up skin instances | |
skin_instances = list(bodypart.objects_of_type(nif.NiSkinInstance)) | |
for skin_instance in skin_instances: | |
# correct root | |
skin_instance.root = xbase_anim.find_object_by_name(skin_instance.root.name) | |
# correct bones | |
for i, bone in enumerate(skin_instance.bones): | |
skin_instance.bones[i] = xbase_anim.find_object_by_name(bone.name) | |
# attach items | |
if any(skin_instances): | |
# extract any objects named "Tri {...} N" | |
for obj in bodypart.objects_of_type(nif.NiTriShape): | |
if obj.name.lower().startswith(f"tri {name}"): | |
parent_node.children.append(obj) | |
else: | |
# no skinning, just attach all the roots | |
parent_node.children.extend(bodypart.roots) | |
# do mirroring to opposite side | |
from copy import copy, deepcopy | |
if "right" in name: | |
right_parent = parent_node | |
left_parent = xbase_anim.find_object_by_name(name.replace("right", "left")) | |
left_parent.children = list(map(deepcopy, right_parent.children)) | |
flip_matrix = ID44.copy() | |
flip_matrix[0, 0] = -1 | |
left_parent.matrix = left_parent.matrix @ flip_matrix | |
left_parent.properties.append( | |
nif.NiStencilProperty(draw_mode=nif.NiStencilProperty.DrawMode.DRAW_CW) | |
) | |
# -------------- | |
# ATTACH WEAPONS | |
# -------------- | |
parent = xbase_anim.find_object_by_name("Weapon Bone") | |
for name, mesh in weapons.items(): | |
temp = nif.NiStream() | |
temp.load(meshes_path / mesh) | |
for root in temp.roots: | |
parent.children.append(root) | |
# -------------- | |
# ATTACH SHIELDS | |
# -------------- | |
parent = xbase_anim.find_object_by_name("Shield Bone") | |
for name, mesh in shields.items(): | |
temp = nif.NiStream() | |
temp.load(meshes_path / mesh) | |
for root in temp.roots: | |
parent.children.append(root) | |
# -------- | |
# FINALIZE | |
# -------- | |
# clear unused | |
for obj in xbase_anim.objects_of_type(nif.NiNode): | |
obj.children = [ | |
c for c in obj.children | |
if len(getattr(obj, "children", [None])) | |
] | |
# clear controllers | |
for obj in xbase_anim.objects_of_type(nif.NiObjectNET): | |
obj.controller = None | |
xbase_anim.save(output_path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment