Compare commits
2 Commits
b19033b557
...
3a3edf785c
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a3edf785c | |||
| fc486eea82 |
@@ -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.
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user