Compare commits

...

2 Commits

Author SHA1 Message Date
slapin 3a3edf785c Character pipeline fixes 2026-05-22 18:09:26 +03:00
slapin fc486eea82 Pipeline update clothes 2026-05-21 20:04:06 +03:00
10 changed files with 247 additions and 15 deletions
+28 -5
View File
@@ -324,8 +324,8 @@ function(add_blend_consolidation INPUT_BLEND COMBINED_BLEND OUTPUT_BLEND)
# Get the base name from the combined blend file for stamp derivation
get_filename_component(COMBINED_NAME "${COMBINED_BLEND}" NAME_WE)
# Remove "_combined" suffix if present
string(REGEX REPLACE "_combined$" "" TARGET_BASE "${COMBINED_NAME}")
# Remove "_combined" or "_shaped" suffix if present
string(REGEX REPLACE "_(combined|shaped)$" "" TARGET_BASE "${COMBINED_NAME}")
# Derive stamp dependency
get_filename_component(COMBINED_DIR "${COMBINED_BLEND}" DIRECTORY)
@@ -404,10 +404,10 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND)
${COMBINED_BLEND}
${SHAPED_BLEND}
)
# Step 2: Consolidate
# Step 2: Consolidate (use shaped blend which has shape keys transferred)
add_blend_consolidation(
"${INPUT_BLEND}"
"${COMBINED_BLEND}"
"${SHAPED_BLEND}"
"${FINAL_OUTPUT_BLEND}"
)
@@ -420,8 +420,12 @@ endfunction()
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-top.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-bottom.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-feet.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-top.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-bottom.blend)
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-female-feet.blend)
# male
add_clothes_pipeline(
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-bottom_weighted.blend" # WEIGHTED_BLEND
@@ -431,12 +435,31 @@ add_clothes_pipeline(
add_clothes_pipeline(
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-bottom.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-top_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend" # FINAL_OUTPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-top.blend" # FINAL_OUTPUT_BLEND
)
add_clothes_pipeline(
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-top.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-feet_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend" # FINAL_OUTPUT_BLEND
)
# female
add_clothes_pipeline(
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-female.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-female-bottom_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated-bottom.blend" # FINAL_OUTPUT_BLEND
)
add_clothes_pipeline(
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated-bottom.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-female-top_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated-top.blend" # FINAL_OUTPUT_BLEND
)
add_clothes_pipeline(
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated-top.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-female-feet_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend" # FINAL_OUTPUT_BLEND
)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+67 -6
View File
@@ -7,10 +7,16 @@ import mathutils
from mathutils.bvhtree import BVHTree
def load_blend_files(clothes_blend_path, body_blend_path):
"""Load objects from blend files and return all loaded objects"""
"""Load objects from blend files and return all loaded objects
IMPORTANT: Body file must be loaded FIRST so its objects keep their original names.
The clothes file may contain reference meshes with the same names as body parts
(e.g., 'BodyTop' used for weight painting). If clothes are loaded first, the body's
real objects get renamed to 'BodyTop.001' etc., breaking the ref_part lookup.
"""
loaded_objects = []
for path in [clothes_blend_path, body_blend_path]:
for path in [body_blend_path, clothes_blend_path]:
with bpy.data.libraries.load(path) as (data_from, data_to):
data_to.objects = data_from.objects
for obj in data_to.objects:
@@ -265,11 +271,66 @@ def run_batch_combine():
all_objs = bpy.data.objects
# Separate body objects (no ref_layer property)
body_objects = [o for o in all_objs if o.type == 'MESH' and "ref_layer" not in o]
# Filter out rig control shapes (cs_*), helper objects, and objects with no vertices
body_objects = []
skipped_objects = []
for o in all_objs:
if o.type != 'MESH' or "ref_layer" in o:
continue
# Skip rig control shapes and helper objects
if o.name.startswith("cs_") or o.name.startswith("WGT-") or o.name.startswith("MCH-") or o.name.startswith("ORG-"):
skipped_objects.append(o.name)
continue
# Skip objects with no vertices (empty meshes)
if len(o.data.vertices) == 0:
skipped_objects.append(o.name)
continue
body_objects.append(o)
# Separate clothing by layer
clothing_layer1 = [o for o in all_objs if o.type == 'MESH' and "ref_layer" in o and o["ref_layer"] == 1]
clothing_layer2 = [o for o in all_objs if o.type == 'MESH' and "ref_layer" in o and o["ref_layer"] == 2]
if skipped_objects:
print(f"Skipped {len(skipped_objects)} non-body mesh objects: {', '.join(skipped_objects[:10])}{'...' if len(skipped_objects) > 10 else ''}")
# Filter body objects: those missing age/sex/slot are treated as helpers/references
required_body_props = {"age", "sex", "slot"}
valid_body_objects = []
skipped_body_objects = []
for body_obj in body_objects:
missing = [p for p in required_body_props if p not in body_obj]
if missing:
print(f"WARNING: Mesh object '{body_obj.name}' is missing required body properties {missing}.")
print(f" Treating as helper/reference object (not a body part).")
print(f" Available properties: {[k for k in body_obj.keys() if not k.startswith('_')]}")
skipped_body_objects.append(body_obj.name)
else:
valid_body_objects.append(body_obj)
if skipped_body_objects:
print(f"Skipped {len(skipped_body_objects)} mesh objects treated as helpers: {', '.join(skipped_body_objects)}")
body_objects = valid_body_objects
# Separate clothing by layer, filtering out objects missing required clothing properties
required_clothing_props = {"ref_layer", "ref_part", "garment_id"}
clothing_layer1 = []
clothing_layer2 = []
skipped_clothing_objects = []
for o in all_objs:
if o.type != 'MESH' or "ref_layer" not in o:
continue
# Check if this is a valid clothing object (has required clothing properties)
missing = [p for p in required_clothing_props if p not in o]
if missing:
print(f"WARNING: Mesh object '{o.name}' has ref_layer but is missing required clothing properties {missing}.")
print(f" Treating as helper/reference object (not clothing).")
skipped_clothing_objects.append(o.name)
continue
if o["ref_layer"] == 1:
clothing_layer1.append(o)
elif o["ref_layer"] == 2:
clothing_layer2.append(o)
if skipped_clothing_objects:
print(f"Skipped {len(skipped_clothing_objects)} mesh objects treated as helpers: {', '.join(skipped_clothing_objects)}")
print(f"Found {len(body_objects)} body objects")
print(f"Found {len(clothing_layer1)} layer 1 clothing objects")
+8
View File
@@ -7,6 +7,7 @@ def process_append(source_files, output_path):
for file_path in source_files:
if not os.path.exists(file_path):
print(f"Warning: Source file not found: {file_path}")
continue
with bpy.data.libraries.load(file_path) as (data_from, data_to):
@@ -50,6 +51,13 @@ def process_append(source_files, output_path):
print(f"Processed {obj.name}: Parented and Modset to {arm_name}")
else:
print(f"Warning: Armature '{arm_name}' not found for {obj.name}")
elif obj.type == 'MESH':
# Object is a mesh but missing required properties - treat as helper/reference
missing = [p for p in required_props if p not in obj.keys()]
print(f"WARNING: Mesh object '{obj.name}' is missing required properties {missing}.")
print(f" Treating as helper/reference object (not a body part).")
print(f" Available properties: {[k for k in obj.keys() if not k.startswith('_')]}")
# Don't remove - keep helper/reference objects in the scene
else:
# Clean up data not meeting criteria
bpy.data.objects.remove(obj, do_unlink=True)
@@ -272,6 +272,103 @@ class GARMENT_OT_sync_tags(bpy.types.Operator):
return {"FINISHED"}
class GARMENT_OT_check_scene(bpy.types.Operator):
bl_idname = "garment.check_scene"
bl_label = "Check Scene"
bl_description = "Validate all garment-system objects for pipeline readiness"
bl_options = {"REGISTER"}
def execute(self, context):
errors = []
warnings = []
body_count = 0
cloth_count = 0
ok_count = 0
required_body_props = {"age", "sex", "slot"}
required_clothing_props = {"ref_layer", "ref_part", "garment_id"}
for obj in bpy.data.objects:
if obj.type != "MESH":
continue
has_body_props = all(p in obj for p in required_body_props)
has_clothing_props = all(p in obj for p in required_clothing_props)
if has_body_props and has_clothing_props:
errors.append(f"'{obj.name}': has BOTH body-part and clothing properties (mixed)")
continue
if has_body_props:
body_count += 1
# Check body part values are valid
age = str(obj.get("age", ""))
sex = str(obj.get("sex", ""))
slot = str(obj.get("slot", ""))
valid_ages = {"adult", "child", "teen", "baby"}
valid_sexes = {"male", "female"}
valid_slots = {"face", "bottom", "top", "feet", "hair"}
if age not in valid_ages:
errors.append(f"'{obj.name}': invalid age='{age}' (valid: {', '.join(sorted(valid_ages))})")
if sex not in valid_sexes:
errors.append(f"'{obj.name}': invalid sex='{sex}' (valid: {', '.join(sorted(valid_sexes))})")
if slot not in valid_slots:
errors.append(f"'{obj.name}': invalid slot='{slot}' (valid: {', '.join(sorted(valid_slots))})")
ok_count += 1
elif has_clothing_props:
cloth_count += 1
# Check clothing values are valid
ref_sex = str(obj.get("ref_sex", ""))
ref_age = str(obj.get("ref_age", ""))
valid_ages = {"adult", "child", "teen", "baby"}
valid_sexes = {"male", "female"}
if ref_sex and ref_sex not in valid_sexes:
errors.append(f"'{obj.name}': invalid ref_sex='{ref_sex}' (valid: {', '.join(sorted(valid_sexes))})")
if ref_age and ref_age not in valid_ages:
errors.append(f"'{obj.name}': invalid ref_age='{ref_age}' (valid: {', '.join(sorted(valid_ages))})")
# Check ref_part points to an existing body object
ref_part = str(obj.get("ref_part", ""))
if ref_part and ref_part not in bpy.data.objects:
warnings.append(f"'{obj.name}': ref_part='{ref_part}' not found in scene")
ok_count += 1
else:
# Mesh object with no garment props - check if it should have them
if obj.name.startswith("Body") or any(x in obj.name.lower() for x in ("shoes", "pants", "shirt", "skirt", "dress", "hat", "hair", "top", "bottom")):
warnings.append(f"'{obj.name}': mesh object with no garment properties (may need age/sex/slot or ref_layer/ref_part/garment_id)")
# Report results
self.report({"INFO"}, f"Check complete: {body_count} body parts, {cloth_count} clothing, {len(errors)} errors, {len(warnings)} warnings")
# Print detailed results to console
print("\n" + "=" * 60)
print("GARMENT SYSTEM SCENE CHECK")
print("=" * 60)
print(f"Body parts: {body_count}")
print(f"Clothing: {cloth_count}")
print(f"OK objects: {ok_count}")
print(f"Errors: {len(errors)}")
print(f"Warnings: {len(warnings)}")
print("-" * 60)
if errors:
print("\nERRORS (must fix before pipeline run):")
for e in errors:
print(f" [ERROR] {e}")
if warnings:
print("\nWARNINGS (review recommended):")
for w in warnings:
print(f" [WARN] {w}")
if not errors and not warnings:
print("\n ✓ Scene looks good! Pipeline should run successfully.")
print("=" * 60)
return {"FINISHED"}
# ---------------------------------------------------------------------------
# Panel
# ---------------------------------------------------------------------------
@@ -352,6 +449,12 @@ class GARMENT_PT_panel(bpy.types.Panel):
layout.separator()
# Check scene button
layout.separator()
row = layout.row(align=True)
row.scale_y = 1.5
row.operator("garment.check_scene", icon="CHECKBOX_HLT")
# Scene overview
box = layout.box()
box.label(text="Scene Overview", icon="SCENE_DATA")
@@ -379,6 +482,7 @@ classes = [
GARMENT_OT_auto_detect,
GARMENT_OT_clear_props,
GARMENT_OT_sync_tags,
GARMENT_OT_check_scene,
GARMENT_PT_panel,
]
@@ -137,9 +137,10 @@ def load_target_file(target_path):
# Store information about target objects without keeping references
target_objects_info = []
required_props = {'age', 'sex', 'slot'}
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.data:
if all(prop in obj for prop in ['age', 'sex', 'slot']):
if all(prop in obj for prop in required_props):
obj_info = {
'name': obj.name,
'age': obj['age'],
@@ -157,6 +158,24 @@ def load_target_file(target_path):
if obj_info['ref_shapes']:
print(f" - ref_shapes: '{obj_info['ref_shapes']}'")
if not target_objects_info:
print("\n" + "=" * 70)
print("WARNING: No target objects found with required properties (age, sex, slot)")
print("=" * 70)
print("\nThis likely means the combine_clothes.py step did not properly")
print("propagate age/sex/slot properties from body objects to combined objects,")
print("or the source blend file only contains helper/reference objects.")
print("\nAvailable mesh objects and their properties:")
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.data:
props = [k for k in obj.keys() if not k.startswith('_')]
has_req = all(p in obj for p in required_props)
marker = " [OK]" if has_req else " [MISSING age/sex/slot]"
print(f" - {obj.name}: {props}{marker}")
print("\nReturning empty target list. The pipeline will skip shape key transfer.")
print("=" * 70)
return target_objects_info, temp_target
return target_objects_info, temp_target
def get_target_object_by_name(obj_name):
@@ -1006,7 +1025,14 @@ def main():
if not target_objects_info:
print("\nNo target objects found with required properties")
sys.exit(1)
print("Skipping shape key transfer. Saving target file as-is.")
# Save the target file as-is (no shape keys transferred)
bpy.ops.wm.save_as_mainfile(filepath=output_file)
print(f"\nSaved output (unchanged): {output_file}")
print("=" * 60)
print("Script completed (no shape keys transferred)")
print("=" * 60)
return
print(f"\nFound {len(target_objects_info)} target objects to process")
+1
View File
@@ -347,6 +347,7 @@ set(EDITSCENE_HEADERS
)
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})
add_dependencies(editSceneEditor morph)
# Define JPH_DEBUG_RENDERER for physics debug drawing
target_compile_definitions(editSceneEditor PRIVATE JPH_DEBUG_RENDERER)