Character ages and Phase 8 checkpoint (interiors)
This commit is contained in:
@@ -33,6 +33,18 @@ def prepare_armature(mapping):
|
||||
orig_armature = bpy.data.objects[mapping.armature_name]
|
||||
armature_name = orig_armature.name
|
||||
orig_armature.name = orig_armature.name + "_orig"
|
||||
# Rename the original data block too, so the baked armature can use the
|
||||
# exact stem as its data name (and therefore the exported skeleton file
|
||||
# is `<stem>.skeleton`, not `<stem>.005.skeleton`).
|
||||
orig_armature.data.name = armature_name + "_orig"
|
||||
# Orphaned armature data blocks left by removed duplicate clothing-stage
|
||||
# rigs still occupy the stem name; move them out of the way.
|
||||
for ad in bpy.data.armatures:
|
||||
if ad is orig_armature.data or ad is new_armature.data:
|
||||
continue
|
||||
if ad.name == armature_name or ad.name.startswith(
|
||||
armature_name + "."):
|
||||
ad.name = ad.name + "_unused"
|
||||
new_armature.name = armature_name
|
||||
queue = []
|
||||
if new_armature.animation_data is None:
|
||||
@@ -276,6 +288,19 @@ for mapping in[CommandLineMapping()]:
|
||||
else:
|
||||
print(f"No hair with own skeleton detected")
|
||||
# ---------------------------------------------
|
||||
# Helper/reference meshes (Body, Tops, Bottoms, Shoes, HairBack,
|
||||
# Accessory, Body_shapes) are build-time weight-transfer aids and
|
||||
# must not be exported: they are not catalog parts, and exporting
|
||||
# them bloats the resource tree and creates texture-name collisions
|
||||
# across age directories.
|
||||
allowed = set(mapping.objs)
|
||||
allowed.update(o.name for o in hair_own_skel_objs)
|
||||
for ob in list(bpy.data.objects):
|
||||
if ob.type != 'MESH':
|
||||
continue
|
||||
if ob.name not in allowed:
|
||||
print(f"Removing non-catalog helper mesh '{ob.name}'")
|
||||
bpy.data.objects.remove(ob, do_unlink=True)
|
||||
else:
|
||||
bpy.ops.wm.append(
|
||||
filepath=os.path.join(mapping.blend_path, mapping.inner_path),
|
||||
@@ -390,8 +415,46 @@ for mapping in[CommandLineMapping()]:
|
||||
hair_arm.hide_render = False
|
||||
obj_names = mapping.objs
|
||||
prefix = mapping.armature_name + "_"
|
||||
material_obj_names = list(obj_names)
|
||||
material_obj_names += [o.name for o in hair_own_skel_objs]
|
||||
|
||||
for name in obj_names:
|
||||
# Deduplicate image datablocks that came from multiple clothing stages
|
||||
# but point at the same source file. Without this, the exporter writes
|
||||
# `foo.png`, `foo.001.png`, ... for one texture.
|
||||
def dedupe_images():
|
||||
canonical = {}
|
||||
for img in list(bpy.data.images):
|
||||
if not img.filepath:
|
||||
continue
|
||||
try:
|
||||
key = os.path.normpath(bpy.path.abspath(img.filepath))
|
||||
except Exception:
|
||||
continue
|
||||
if not key:
|
||||
continue
|
||||
if key not in canonical:
|
||||
canonical[key] = img
|
||||
continue
|
||||
keep = canonical[key]
|
||||
for mat in bpy.data.materials:
|
||||
if not mat.node_tree:
|
||||
continue
|
||||
for node in mat.node_tree.nodes:
|
||||
if node.type == 'TEX_IMAGE' and node.image is img:
|
||||
node.image = keep
|
||||
try:
|
||||
bpy.data.images.remove(img)
|
||||
except Exception:
|
||||
pass
|
||||
dedupe_images()
|
||||
try:
|
||||
bpy.ops.outliner.orphans_purge(do_local_ids=True,
|
||||
do_linked_ids=True,
|
||||
do_recursive=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for name in material_obj_names:
|
||||
obj = bpy.data.objects.get(name)
|
||||
|
||||
if obj and obj.type == 'MESH':
|
||||
@@ -499,6 +562,7 @@ for mapping in[CommandLineMapping()]:
|
||||
lod_obj_names = []
|
||||
if mapping_lods_path and os.path.exists(mapping_lods_path):
|
||||
print("Appending character LOD objects from: " + mapping_lods_path)
|
||||
before_lod_objects = set(bpy.data.objects)
|
||||
with bpy.data.libraries.load(mapping_lods_path) as (data_from, data_to):
|
||||
data_to.objects = [
|
||||
n for n in data_from.objects
|
||||
@@ -525,6 +589,16 @@ for mapping in[CommandLineMapping()]:
|
||||
if mod.type == 'ARMATURE':
|
||||
mod.object = body_arm
|
||||
print(f" LOD object '{obj.name}' -> mesh '{obj.data.name}'")
|
||||
# The lods blend also contains its own body armature. Drop appended
|
||||
# armature objects so their data blocks cannot steal the stem
|
||||
# skeleton name during export.
|
||||
for obj in list(bpy.data.objects):
|
||||
if obj in before_lod_objects or obj.type != 'ARMATURE':
|
||||
continue
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
for ad in list(bpy.data.armatures):
|
||||
if ad.users == 0:
|
||||
bpy.data.armatures.remove(ad)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# Triangulate body part meshes before OGRE export to prevent the exporter's
|
||||
@@ -560,16 +634,53 @@ for mapping in[CommandLineMapping()]:
|
||||
|
||||
armobj = bpy.data.objects.get(mapping.armature_name)
|
||||
armobj.data.name = armobj.name
|
||||
print("EXPORT ARMATURE DATA: obj=%s data=%s all=%s" % (
|
||||
armobj.name, armobj.data.name,
|
||||
[(a.name, a.users) for a in bpy.data.armatures]))
|
||||
|
||||
# ---- Fix: sync Image filepath to Image name ----
|
||||
# Blender silently reuses existing Images when appending
|
||||
# from libraries, so the Image's filepath may still point
|
||||
# to the old texture even after the user renamed it.
|
||||
# blender2ogre writes the filepath to .material files,
|
||||
# so we must update filepath to match the Image name.
|
||||
# ---- Prefix image resources per stem ----
|
||||
# All age directories share the "Characters" resource group, so texture
|
||||
# file names must be unique across ages/sexes. Adult gets
|
||||
# `male-...`/`female-...`; new ages get `baby-male-...` etc. The prefix
|
||||
# is idempotent (an image already prefixed with the stem is left alone).
|
||||
import re as _re
|
||||
seen_imgs = set()
|
||||
for name in obj_names:
|
||||
stem = mapping.armature_name
|
||||
|
||||
def _unique_image_name(base):
|
||||
if bpy.data.images.get(base) is None:
|
||||
return base
|
||||
i = 1
|
||||
while bpy.data.images.get(f"{base}.{i:03d}") is not None:
|
||||
i += 1
|
||||
return f"{base}.{i:03d}"
|
||||
|
||||
image_obj_names = list(obj_names)
|
||||
image_obj_names += [o.name for o in hair_own_skel_objs]
|
||||
# LOD impostors use the already-unique `charlod_<stem>-atlas.png` /
|
||||
# `charlod_<stem>` material created by generate_lods.py; prefixing them
|
||||
# again would make the material reference a different file/format.
|
||||
# Normalize their image format to the filename extension, though; some
|
||||
# Blender versions keep the image blob as TARGA even when saved as PNG.
|
||||
for name in lod_obj_names:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj or obj.type != 'MESH':
|
||||
continue
|
||||
for slot in obj.material_slots:
|
||||
if not slot.material or not slot.material.node_tree:
|
||||
continue
|
||||
for node in slot.material.node_tree.nodes:
|
||||
if node.type == 'TEX_IMAGE' and node.image:
|
||||
fp = node.image.filepath.lower()
|
||||
if fp.endswith(".png"):
|
||||
node.image.file_format = 'PNG'
|
||||
elif fp.endswith(".jpg") or fp.endswith(".jpeg"):
|
||||
node.image.file_format = 'JPEG'
|
||||
elif fp.endswith(".tga"):
|
||||
node.image.file_format = 'TARGA'
|
||||
output_dir = os.path.dirname(mapping.gltf_path)
|
||||
|
||||
for name in image_obj_names:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if obj and obj.type == 'MESH':
|
||||
for slot in obj.material_slots:
|
||||
@@ -577,28 +688,50 @@ for mapping in[CommandLineMapping()]:
|
||||
for node in slot.material.node_tree.nodes:
|
||||
if node.type == 'TEX_IMAGE' and node.image:
|
||||
img = node.image
|
||||
if img.name not in seen_imgs:
|
||||
seen_imgs.add(img.name)
|
||||
# Strip Blender auto-suffix (.NNN)
|
||||
clean = _re.sub(
|
||||
r'\.\d{3}$', '', img.name)
|
||||
# Extract just the filename from
|
||||
# the current filepath
|
||||
old_name = os.path.basename(
|
||||
img.filepath)
|
||||
if old_name != clean and clean:
|
||||
# Rebuild filepath with new
|
||||
# filename
|
||||
dirpart = os.path.dirname(
|
||||
img.filepath)
|
||||
new_path = os.path.join(
|
||||
dirpart, clean)
|
||||
print(f" Updating Image "
|
||||
f"'{img.name}': "
|
||||
f"filepath "
|
||||
f"'{old_name}' -> "
|
||||
f"'{clean}'")
|
||||
img.filepath = new_path
|
||||
if img.name in seen_imgs:
|
||||
continue
|
||||
seen_imgs.add(img.name)
|
||||
clean = _re.sub(r'\.\d{3}$', '', img.name)
|
||||
old_name = os.path.basename(img.filepath)
|
||||
if not old_name:
|
||||
# Packed/generated image: use the datablock
|
||||
# name as the file base.
|
||||
old_name = clean
|
||||
if not os.path.splitext(old_name)[1]:
|
||||
old_name += ".png"
|
||||
# Strip Blender auto-suffix before the extension.
|
||||
base, ext = os.path.splitext(old_name)
|
||||
base = _re.sub(r'\.\d{3}$', '', base)
|
||||
if not ext:
|
||||
ext = ".png"
|
||||
if (base.startswith(stem + "-") or
|
||||
base.startswith(stem + "_")):
|
||||
new_base = base
|
||||
else:
|
||||
new_base = f"{stem}-{base}"
|
||||
image_name = _unique_image_name(new_base)
|
||||
new_file = image_name + ext
|
||||
new_path = os.path.join(output_dir, new_file)
|
||||
if img.name != image_name:
|
||||
print(f" Prefixing Image "
|
||||
f"'{img.name}' -> '{image_name}' "
|
||||
f"({new_file})")
|
||||
img.name = image_name
|
||||
if new_file.lower().endswith(".png"):
|
||||
img.file_format = 'PNG'
|
||||
elif new_file.lower().endswith(".jpg") or \
|
||||
new_file.lower().endswith(".jpeg"):
|
||||
img.file_format = 'JPEG'
|
||||
elif new_file.lower().endswith(".tga"):
|
||||
img.file_format = 'TARGA'
|
||||
img.filepath = new_path
|
||||
img.filepath_raw = new_path
|
||||
try:
|
||||
img.save()
|
||||
except Exception as exc:
|
||||
print(f" WARNING: could not save image "
|
||||
f"'{img.name}': {exc}")
|
||||
seen_imgs.add(img.name)
|
||||
# ---------------------------------------------------
|
||||
|
||||
bpy.ops.ogre.export(filepath=mapping.gltf_path.replace(".glb", ".scene"), EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='0', EX_LOD_DISTANCE=20, EX_LOD_LEVELS=4, EX_GENERATE_TANGENTS='4')
|
||||
|
||||
Reference in New Issue
Block a user