Skip to content

Instantly share code, notes, and snippets.

@Greatness7
Last active October 26, 2023 16:28
Show Gist options
  • Save Greatness7/3371f4e8a5112a8c8e4e270a4d2f5174 to your computer and use it in GitHub Desktop.
Save Greatness7/3371f4e8a5112a8c8e4e270a4d2f5174 to your computer and use it in GitHub Desktop.
Create Character NIF
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