Pipeline update
This commit is contained in:
@@ -47,15 +47,12 @@ set(VRM_IMPORTED_BLENDS
|
||||
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CHARACTER_GLBS}
|
||||
# DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
|
||||
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
set(FEMALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Accessoty")
|
||||
set(MALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottomPants;BodyBottom_Panties001;BodyBottom;BodyFeetPants;BodyFeetPantsShoes;BodyFeet;Hair;Face;BackHair;Accessory")
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend
|
||||
${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
"${MALE_OBJECTS}"
|
||||
"male"
|
||||
tmp-edited-male.blend
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
|
||||
@@ -76,7 +73,6 @@ add_custom_command(
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
|
||||
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female-consolidated.blend
|
||||
${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
"${FEMALE_OBJECTS}"
|
||||
"female"
|
||||
tmp-edited-female.blend
|
||||
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
|
||||
@@ -422,12 +418,19 @@ function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND)
|
||||
)
|
||||
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-female-bottom.blend)
|
||||
|
||||
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
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated-bottom.blend" # BOTTOM_OUTPUT_BLEND
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,7 @@ import bpy
|
||||
import bmesh
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import mathutils
|
||||
from mathutils.bvhtree import BVHTree
|
||||
|
||||
@@ -158,6 +159,13 @@ def process_clothing_pair(clothing_obj, target_obj, whitelist, is_clothing_copy=
|
||||
clothing_props[prop_name] = clothing_obj[prop_name]
|
||||
print(f" Stored property '{prop_name}' = {clothing_obj[prop_name]} from clothing")
|
||||
|
||||
# Also store metadata tags for export pipeline
|
||||
clothing_meta = {}
|
||||
for prop_name in ["ref_layer", "ref_part", "garment_id", "tags", "age", "sex", "slot"]:
|
||||
if prop_name in clothing_obj:
|
||||
clothing_meta[prop_name] = clothing_obj[prop_name]
|
||||
print(f" Stored meta '{prop_name}' = {clothing_obj[prop_name]} from clothing")
|
||||
|
||||
# Step A: Raycast & adjust vertices
|
||||
out_ray_length = 0.015
|
||||
if "ref_ray_length" in clothing_obj:
|
||||
@@ -204,6 +212,26 @@ def process_clothing_pair(clothing_obj, target_obj, whitelist, is_clothing_copy=
|
||||
new_target[prop_name] = prop_value
|
||||
print(f" Copied property '{prop_name}' = {prop_value} to combined object")
|
||||
|
||||
# NEW CODE: Aggregate clothing metadata onto combined object
|
||||
# Collect from all clothing objects processed for this target
|
||||
# Use JSON string since Blender ID properties don't support Python lists/sets
|
||||
if "_combined_meta" in new_target:
|
||||
meta = json.loads(new_target["_combined_meta"])
|
||||
else:
|
||||
meta = {"garments": [], "tags": [], "layers": []}
|
||||
if "garment_id" in clothing_meta:
|
||||
meta["garments"].append(clothing_meta["garment_id"])
|
||||
else:
|
||||
meta["garments"].append(clothing_name_for_final)
|
||||
if "tags" in clothing_meta and clothing_meta["tags"]:
|
||||
for t in str(clothing_meta["tags"]).split(";"):
|
||||
t = t.strip()
|
||||
if t and t not in meta["tags"]:
|
||||
meta["tags"].append(t)
|
||||
if "ref_layer" in clothing_meta:
|
||||
meta["layers"].append(int(clothing_meta["ref_layer"]))
|
||||
new_target["_combined_meta"] = json.dumps(meta)
|
||||
|
||||
# Rename the combined object using the appropriate clothing name
|
||||
new_target.name = f"{target_name}_{clothing_name_for_final}"
|
||||
whitelist.add(new_target)
|
||||
@@ -349,9 +377,34 @@ def run_batch_combine():
|
||||
print(f"Layer 2 direct to body results: {len(layer2_results_direct)}")
|
||||
print(f"Total combined objects: {len(all_results)}")
|
||||
|
||||
# Final cleanup - keep all combined objects and their armatures
|
||||
# Finalize aggregated metadata on all combined objects
|
||||
for obj in all_results:
|
||||
whitelist.add(obj)
|
||||
if "_combined_meta" in obj:
|
||||
meta = json.loads(obj["_combined_meta"])
|
||||
layers = meta.get("layers", [])
|
||||
garments = meta.get("garments", [])
|
||||
tags = meta.get("tags", [])
|
||||
|
||||
if layers:
|
||||
obj["layer"] = max(layers)
|
||||
else:
|
||||
obj["layer"] = 0
|
||||
|
||||
if garments:
|
||||
obj["garments"] = ";".join(garments)
|
||||
else:
|
||||
obj["garments"] = ""
|
||||
|
||||
if tags:
|
||||
obj["clothing_tags"] = ";".join(sorted(set(tags)))
|
||||
else:
|
||||
obj["clothing_tags"] = ""
|
||||
|
||||
print(f"Finalized metadata for {obj.name}: layer={obj['layer']}, garments={obj['garments']}, tags={obj['clothing_tags']}")
|
||||
|
||||
# Remove temporary meta property
|
||||
del obj["_combined_meta"]
|
||||
|
||||
cleanup_unused_objects(whitelist)
|
||||
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Garment System UI Panel for Blender
|
||||
|
||||
Run this script in Blender's Scripting editor (or via blender -b -P garment_system_ui.py)
|
||||
to add a side-panel for editing garment-system custom properties on mesh objects.
|
||||
|
||||
Properties managed:
|
||||
Body Part -> age, sex, slot, layer, garments, clothing_tags
|
||||
Clothing -> ref_layer, ref_part, garment_id, ref_sex, ref_age, ref_clothing, tags
|
||||
"""
|
||||
|
||||
import bpy
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Property helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SLOT_ITEMS = [
|
||||
("face", "Face", ""),
|
||||
("bottom", "Bottom", ""),
|
||||
("top", "Top", ""),
|
||||
("feet", "Feet", ""),
|
||||
("hair", "Hair", ""),
|
||||
]
|
||||
|
||||
SEX_ITEMS = [
|
||||
("male", "Male", ""),
|
||||
("female", "Female", ""),
|
||||
]
|
||||
|
||||
AGE_ITEMS = [
|
||||
("adult", "Adult", ""),
|
||||
("child", "Child", ""),
|
||||
("teen", "Teen", ""),
|
||||
("baby", "Baby", ""),
|
||||
]
|
||||
|
||||
|
||||
def _ensure_prop(obj, key, default):
|
||||
if key not in obj:
|
||||
obj[key] = default
|
||||
return obj[key]
|
||||
|
||||
|
||||
def _get_prop_str(obj, key, default=""):
|
||||
if key not in obj:
|
||||
return default
|
||||
return str(obj[key])
|
||||
|
||||
|
||||
def _get_prop_int(obj, key, default=0):
|
||||
if key not in obj:
|
||||
return default
|
||||
try:
|
||||
return int(obj[key])
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
|
||||
def _set_prop_str(obj, key, value):
|
||||
if value:
|
||||
obj[key] = value
|
||||
elif key in obj:
|
||||
del obj[key]
|
||||
|
||||
|
||||
def _set_prop_int(obj, key, value):
|
||||
obj[key] = int(value)
|
||||
|
||||
|
||||
def _del_prop(obj, key):
|
||||
if key in obj:
|
||||
del obj[key]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operators
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class GARMENT_OT_apply_body_part(bpy.types.Operator):
|
||||
bl_idname = "garment.apply_body_part"
|
||||
bl_label = "Apply Body Part"
|
||||
bl_description = "Write body-part custom properties to the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
age: bpy.props.EnumProperty(name="Age", items=AGE_ITEMS)
|
||||
sex: bpy.props.EnumProperty(name="Sex", items=SEX_ITEMS)
|
||||
slot: bpy.props.EnumProperty(name="Slot", items=SLOT_ITEMS)
|
||||
layer: bpy.props.IntProperty(name="Layer", min=0, max=2, default=0)
|
||||
garments: bpy.props.StringProperty(name="Garments", description="Semicolon-separated garment names")
|
||||
clothing_tags: bpy.props.StringProperty(name="Clothing Tags", description="Semicolon-separated tags")
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj or obj.type != "MESH":
|
||||
self.report({"WARNING"}, "Active object is not a mesh")
|
||||
return {"CANCELLED"}
|
||||
|
||||
_set_prop_str(obj, "age", self.age)
|
||||
_set_prop_str(obj, "sex", self.sex)
|
||||
_set_prop_str(obj, "slot", self.slot)
|
||||
_set_prop_int(obj, "layer", self.layer)
|
||||
_set_prop_str(obj, "garments", self.garments)
|
||||
_set_prop_str(obj, "clothing_tags", self.clothing_tags)
|
||||
|
||||
# Remove clothing-only props so they don't confuse the pipeline
|
||||
for k in ("ref_layer", "ref_part", "garment_id", "ref_sex",
|
||||
"ref_age", "ref_clothing", "tags"):
|
||||
_del_prop(obj, k)
|
||||
|
||||
self.report({"INFO"}, f"Body-part props applied to {obj.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
obj = context.active_object
|
||||
if obj:
|
||||
self.age = _get_prop_str(obj, "age", "adult")
|
||||
self.sex = _get_prop_str(obj, "sex", "male")
|
||||
self.slot = _get_prop_str(obj, "slot", "bottom")
|
||||
self.layer = _get_prop_int(obj, "layer", 0)
|
||||
self.garments = _get_prop_str(obj, "garments", "")
|
||||
self.clothing_tags = _get_prop_str(obj, "clothing_tags", "")
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class GARMENT_OT_apply_clothing(bpy.types.Operator):
|
||||
bl_idname = "garment.apply_clothing"
|
||||
bl_label = "Apply Clothing"
|
||||
bl_description = "Write clothing custom properties to the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
ref_layer: bpy.props.IntProperty(name="Ref Layer", min=1, max=2, default=1,
|
||||
description="1 = lingerie, 2 = clothing")
|
||||
ref_part: bpy.props.StringProperty(name="Ref Part",
|
||||
description="Target body-part object name, e.g. BodyBottom")
|
||||
garment_id: bpy.props.StringProperty(name="Garment ID",
|
||||
description="Garment name used for labels, e.g. panties7")
|
||||
ref_sex: bpy.props.EnumProperty(name="Ref Sex", items=SEX_ITEMS)
|
||||
ref_age: bpy.props.EnumProperty(name="Ref Age", items=AGE_ITEMS)
|
||||
ref_clothing: bpy.props.StringProperty(name="Ref Clothing",
|
||||
description="Mesh name used for weight transfer, e.g. BodyBottom")
|
||||
tags: bpy.props.StringProperty(name="Tags", description="Semicolon-separated tags")
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj or obj.type != "MESH":
|
||||
self.report({"WARNING"}, "Active object is not a mesh")
|
||||
return {"CANCELLED"}
|
||||
|
||||
_set_prop_int(obj, "ref_layer", self.ref_layer)
|
||||
_set_prop_str(obj, "ref_part", self.ref_part)
|
||||
_set_prop_str(obj, "garment_id", self.garment_id)
|
||||
_set_prop_str(obj, "ref_sex", self.ref_sex)
|
||||
_set_prop_str(obj, "ref_age", self.ref_age)
|
||||
_set_prop_str(obj, "ref_clothing", self.ref_clothing)
|
||||
_set_prop_str(obj, "tags", self.tags)
|
||||
|
||||
# Remove body-part-only props so they don't confuse the pipeline
|
||||
for k in ("age", "sex", "slot", "layer", "garments", "clothing_tags"):
|
||||
_del_prop(obj, k)
|
||||
|
||||
self.report({"INFO"}, f"Clothing props applied to {obj.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
obj = context.active_object
|
||||
if obj:
|
||||
self.ref_layer = _get_prop_int(obj, "ref_layer", 1)
|
||||
self.ref_part = _get_prop_str(obj, "ref_part", "")
|
||||
self.garment_id = _get_prop_str(obj, "garment_id", "")
|
||||
self.ref_sex = _get_prop_str(obj, "ref_sex", "male")
|
||||
self.ref_age = _get_prop_str(obj, "ref_age", "adult")
|
||||
self.ref_clothing = _get_prop_str(obj, "ref_clothing", "")
|
||||
self.tags = _get_prop_str(obj, "tags", "")
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class GARMENT_OT_auto_detect(bpy.types.Operator):
|
||||
bl_idname = "garment.auto_detect"
|
||||
bl_label = "Auto-detect from Name"
|
||||
bl_description = "Guess sex/slot from object name"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj or obj.type != "MESH":
|
||||
self.report({"WARNING"}, "Active object is not a mesh")
|
||||
return {"CANCELLED"}
|
||||
|
||||
name = obj.name.lower()
|
||||
changed = []
|
||||
|
||||
# Guess sex
|
||||
if "female" in name:
|
||||
obj["sex"] = "female"
|
||||
changed.append("sex=female")
|
||||
elif "male" in name:
|
||||
obj["sex"] = "male"
|
||||
changed.append("sex=male")
|
||||
|
||||
# Guess slot
|
||||
slot_map = {
|
||||
"face": "face",
|
||||
"bottom": "bottom",
|
||||
"top": "top",
|
||||
"feet": "feet",
|
||||
"hair": "hair",
|
||||
}
|
||||
for key, slot in slot_map.items():
|
||||
if key in name:
|
||||
obj["slot"] = slot
|
||||
changed.append(f"slot={slot}")
|
||||
break
|
||||
|
||||
# Guess layer from name hints
|
||||
if any(x in name for x in ("panties", "lingerie", "underwear")):
|
||||
obj["layer"] = 1
|
||||
changed.append("layer=1")
|
||||
elif any(x in name for x in ("pants", "skirt", "robe", "shirt", "dress", "cloth")):
|
||||
obj["layer"] = 2
|
||||
changed.append("layer=2")
|
||||
else:
|
||||
obj["layer"] = 0
|
||||
changed.append("layer=0")
|
||||
|
||||
# Set garment_id from object name for clothing
|
||||
if "ref_layer" in obj or "ref_part" in obj:
|
||||
if "garment_id" not in obj or not obj["garment_id"]:
|
||||
obj["garment_id"] = obj.name
|
||||
changed.append(f"garment_id={obj.name}")
|
||||
|
||||
self.report({"INFO"}, "Auto-detect: " + ", ".join(changed) if changed else "nothing changed")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GARMENT_OT_clear_props(bpy.types.Operator):
|
||||
bl_idname = "garment.clear_props"
|
||||
bl_label = "Clear All Garment Props"
|
||||
bl_description = "Remove all garment-system custom properties from the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
keys = list(obj.keys())
|
||||
removed = []
|
||||
for k in keys:
|
||||
if k in ("age", "sex", "slot", "layer", "garments", "clothing_tags",
|
||||
"ref_layer", "ref_part", "garment_id", "ref_sex", "ref_age",
|
||||
"ref_clothing", "tags"):
|
||||
del obj[k]
|
||||
removed.append(k)
|
||||
self.report({"INFO"}, f"Removed {len(removed)} props" if removed else "No garment props found")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GARMENT_OT_sync_tags(bpy.types.Operator):
|
||||
bl_idname = "garment.sync_tags"
|
||||
bl_label = "Sync Tags from Garments"
|
||||
bl_description = "Copy clothing_tags from garments field"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
garments = _get_prop_str(obj, "garments", "")
|
||||
obj["clothing_tags"] = garments
|
||||
self.report({"INFO"}, f"Tags set to: {garments}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Panel
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class GARMENT_PT_panel(bpy.types.Panel):
|
||||
bl_label = "Garment System"
|
||||
bl_idname = "GARMENT_PT_panel"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Garment"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.active_object
|
||||
|
||||
if not obj:
|
||||
layout.label(text="No active object")
|
||||
return
|
||||
|
||||
layout.label(text=f"Object: {obj.name}", icon="OBJECT_DATA")
|
||||
|
||||
# Detect current type
|
||||
is_body = any(k in obj for k in ("age", "sex", "slot"))
|
||||
is_cloth = any(k in obj for k in ("ref_layer", "ref_part", "garment_id"))
|
||||
|
||||
if is_body and is_cloth:
|
||||
layout.alert = True
|
||||
layout.label(text="WARNING: mixed props", icon="ERROR")
|
||||
layout.alert = False
|
||||
elif is_body:
|
||||
layout.label(text="Type: Body Part", icon="MESH_DATA")
|
||||
elif is_cloth:
|
||||
layout.label(text="Type: Clothing", icon="MOD_CLOTH")
|
||||
else:
|
||||
layout.label(text="Type: (unset)", icon="QUESTION")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Quick action buttons
|
||||
row = layout.row(align=True)
|
||||
row.operator("garment.apply_body_part", icon="MESH_DATA")
|
||||
row = layout.row(align=True)
|
||||
row.operator("garment.apply_clothing", icon="MOD_CLOTH")
|
||||
|
||||
layout.separator()
|
||||
row = layout.row(align=True)
|
||||
row.operator("garment.auto_detect", icon="VIEWZOOM")
|
||||
row.operator("garment.clear_props", icon="X")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Show current body-part props
|
||||
if is_body or not is_cloth:
|
||||
box = layout.box()
|
||||
box.label(text="Body Part Props", icon="MESH_DATA")
|
||||
col = box.column(align=True)
|
||||
col.label(text=f"age: {_get_prop_str(obj, 'age', '—')}")
|
||||
col.label(text=f"sex: {_get_prop_str(obj, 'sex', '—')}")
|
||||
col.label(text=f"slot: {_get_prop_str(obj, 'slot', '—')}")
|
||||
col.label(text=f"layer: {_get_prop_int(obj, 'layer', 0)}")
|
||||
col.label(text=f"garments: {_get_prop_str(obj, 'garments', '—')}")
|
||||
col.label(text=f"clothing_tags: {_get_prop_str(obj, 'clothing_tags', '—')}")
|
||||
if _get_prop_str(obj, "garments", ""):
|
||||
col.operator("garment.sync_tags", icon="FILE_REFRESH")
|
||||
|
||||
# Show current clothing props
|
||||
if is_cloth:
|
||||
box = layout.box()
|
||||
box.label(text="Clothing Props", icon="MOD_CLOTH")
|
||||
col = box.column(align=True)
|
||||
col.label(text=f"ref_layer: {_get_prop_int(obj, 'ref_layer', 1)}")
|
||||
col.label(text=f"ref_part: {_get_prop_str(obj, 'ref_part', '—')}")
|
||||
col.label(text=f"garment_id: {_get_prop_str(obj, 'garment_id', '—')}")
|
||||
col.label(text=f"ref_sex: {_get_prop_str(obj, 'ref_sex', '—')}")
|
||||
col.label(text=f"ref_age: {_get_prop_str(obj, 'ref_age', '—')}")
|
||||
col.label(text=f"ref_clothing: {_get_prop_str(obj, 'ref_clothing', '—')}")
|
||||
col.label(text=f"tags: {_get_prop_str(obj, 'tags', '—')}")
|
||||
|
||||
layout.separator()
|
||||
|
||||
# Scene overview
|
||||
box = layout.box()
|
||||
box.label(text="Scene Overview", icon="SCENE_DATA")
|
||||
body_count = 0
|
||||
cloth_count = 0
|
||||
for o in bpy.data.objects:
|
||||
if o.type != "MESH":
|
||||
continue
|
||||
if any(k in o for k in ("age", "sex", "slot")):
|
||||
body_count += 1
|
||||
elif any(k in o for k in ("ref_layer", "ref_part", "garment_id")):
|
||||
cloth_count += 1
|
||||
col = box.column(align=True)
|
||||
col.label(text=f"Body parts: {body_count}")
|
||||
col.label(text=f"Clothing: {cloth_count}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
classes = [
|
||||
GARMENT_OT_apply_body_part,
|
||||
GARMENT_OT_apply_clothing,
|
||||
GARMENT_OT_auto_detect,
|
||||
GARMENT_OT_clear_props,
|
||||
GARMENT_OT_sync_tags,
|
||||
GARMENT_PT_panel,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -122,6 +122,10 @@ def process_batch():
|
||||
|
||||
remove_empty_vertex_groups(clothing, threshold=0.001)
|
||||
|
||||
# Set garment_id from object name if not already set
|
||||
if "garment_id" not in clothing:
|
||||
clothing["garment_id"] = clothing.name
|
||||
|
||||
# 7. Final Parenting
|
||||
clothing.parent = rig
|
||||
arm_mod = clothing.modifiers.new(name="Armature", type='ARMATURE')
|
||||
|
||||
Reference in New Issue
Block a user