Fixed inflation

This commit is contained in:
2026-05-25 04:10:44 +03:00
parent eea50adfcb
commit b71b599d9c
3 changed files with 134 additions and 30 deletions
@@ -19,7 +19,8 @@ from transfer_shape_keys import (
smooth_penetration_areas,
create_bvh_for_source,
load_source_data,
fix_seams_across_objects
fix_seams_across_objects,
fix_normals_across_objects
)
def get_source_object_name(target_obj_info):
@@ -120,7 +121,8 @@ def process_blend(blend_path, output_path):
# Fix seams: synchronize shape key offsets for vertices sharing
# the same basis position across body parts.
fix_seams_across_objects()
fix_normals_across_objects()
bpy.ops.wm.save_as_mainfile(filepath=output_path)
print(f"Saved: {output_path}")
return True
+12 -22
View File
@@ -43,37 +43,27 @@ def get_transformation_matrices(obj):
return m, m_inv, m_normal
def raycast_and_adjust_vertices(target_body, bvh_cloth, out_ray_length=0.15):
"""Raycast from body to cloth and adjust vertices that intersect"""
m_body, m_body_inv, m_body_normal = get_transformation_matrices(target_body)
"""Raycast from body to cloth and mark vertices that are covered.
Returns a list where 1 = vertex is covered by clothing, 0 = visible.
Does NOT modify vertex positions -- that used to cause visible steps
at clothing boundaries because border vertices kept the inward offset
while exposed neighbours did not."""
m_body, _, m_body_normal = get_transformation_matrices(target_body)
num_verts = len(target_body.data.vertices)
hit_values = [0] * num_verts
has_shape_keys = target_body.data.shape_keys is not None
# Forward raycast (into cloth)
for i, v in enumerate(target_body.data.vertices):
v_world = m_body @ v.co
n_world = (m_body_normal @ v.normal).normalized()
# Raycast forward (into cloth) and backward (from inside cloth)
# Raycast forward (outward) and backward (inward)
hit_f, _, _, _ = bvh_cloth.ray_cast(v_world, n_world, out_ray_length)
hit_b, _, _, _ = bvh_cloth.ray_cast(v_world, -n_world, 0.005)
if hit_f or hit_b:
hit_values[i] = 1
# Adjust vertex position to be slightly outside cloth
offset = -n_world * (0.005 if hit_f else 0.01)
new_co = m_body_inv @ (v_world + offset)
v.co = new_co
# Update shape keys if they exist.
# In relative mode, Blender automatically uses v.co as basis,
# so shape key offsets don't need updating. Only update if
# the shape keys are in absolute mode (which we don't use).
if has_shape_keys and not target_body.data.shape_keys.use_relative:
for kb in target_body.data.shape_keys.key_blocks:
kb.data[i].co = new_co
return hit_values
def protect_and_remove_hidden_geometry(target_body, hit_values, threshold=4.0):
@@ -185,22 +185,45 @@ def get_target_object_by_name(obj_name):
return None
def delete_existing_shape_keys(target_obj):
"""Delete all existing shape keys from target object"""
"""Delete all existing shape keys from target object.
CRITICAL: Blender's shape_key_remove() leaves mesh vertices in their
*deformed* state rather than restoring basis positions. We must save
the Basis positions before deletion and restore them afterwards."""
if not target_obj.data.shape_keys:
return False
# Save Basis positions before deletion
basis_positions = None
for kb in target_obj.data.shape_keys.key_blocks:
if kb.name == 'Basis':
basis_positions = [v.co.copy() for v in kb.data]
break
# Fallback: if no Basis exists, use current vertex positions
if basis_positions is None:
basis_positions = [v.co.copy() for v in target_obj.data.vertices]
num_keys = len(target_obj.data.shape_keys.key_blocks)
print(f" Deleting {num_keys} existing shape keys...")
bpy.context.view_layer.objects.active = target_obj
target_obj.select_set(True)
bpy.ops.object.mode_set(mode='OBJECT')
while target_obj.data.shape_keys:
target_obj.active_shape_key_index = 0
bpy.ops.object.shape_key_remove()
target_obj.select_set(False)
# RESTORE BASIS POSITIONS - Blender leaves mesh deformed after removal
for i, v in enumerate(target_obj.data.vertices):
if i < len(basis_positions):
v.co = basis_positions[i]
target_obj.data.update_tag()
bpy.context.view_layer.update()
print(f" Restored basis positions for {len(basis_positions)} vertices")
return True
def ensure_shape_keys_structure(shape_key_names, target_obj):
@@ -1122,6 +1145,94 @@ def fix_seams_across_objects(tolerance=0.0001):
print(f"Fixed {fixed_vertices} vertices across {fixed_groups} position groups")
print(f"Seam fix complete")
def fix_normals_across_objects(tolerance=0.0001):
"""
Post-process normals to ensure vertices sharing the same basis
position (within tolerance) get identical normals. This fixes
lighting seams between body parts.
"""
body_parts = get_body_part_objects()
if not body_parts:
print("No body part objects found for normal fixing")
return
print(f"\n{'=' * 60}")
print("NORMAL FIX: Synchronizing normals across body parts")
print(f"{'=' * 60}")
print(f"Found {len(body_parts)} body part objects")
# Build spatial hash: position_key -> list of normals
pos_map = {}
for obj in body_parts:
obj.data.calc_normals()
for v in obj.data.vertices:
pos_key = round_pos(v.co, tolerance)
if pos_key not in pos_map:
pos_map[pos_key] = []
pos_map[pos_key].append(v.normal.copy())
# Compute averaged normals for positions with multiple vertices
avg_normals = {}
for pos_key, normals in pos_map.items():
if len(normals) < 2:
continue
avg = mathutils.Vector((0.0, 0.0, 0.0))
for n in normals:
avg += n
if avg.length > 0.001:
avg.normalize()
avg_normals[pos_key] = avg
if not avg_normals:
print("No matching vertices found, skipping normal fix")
return
print(f"Found {len(avg_normals)} position groups with matching vertices")
# Apply averaged normals to each object
fixed_loops = 0
fixed_verts = 0
for obj in body_parts:
obj.data.use_auto_smooth = True
if not obj.data.has_custom_normals:
obj.data.create_normals_split()
obj.data.calc_normals_split()
vert_normal_map = {}
for i, v in enumerate(obj.data.vertices):
pos_key = round_pos(v.co, tolerance)
if pos_key in avg_normals:
vert_normal_map[i] = avg_normals[pos_key]
if not vert_normal_map:
continue
custom_normals = []
for loop in obj.data.loops:
if loop.vertex_index in vert_normal_map:
n = vert_normal_map[loop.vertex_index]
custom_normals.append((n.x, n.y, n.z))
fixed_loops += 1
else:
if hasattr(obj.data, 'corner_normals'):
cn = obj.data.corner_normals[loop.index]
custom_normals.append((cn.vector.x, cn.vector.y, cn.vector.z))
else:
custom_normals.append((loop.normal.x, loop.normal.y, loop.normal.z))
obj.data.normals_split_custom_set(custom_normals)
fixed_verts += len(vert_normal_map)
obj.data.update_tag()
bpy.context.view_layer.update()
print(f"Fixed {fixed_verts} vertices ({fixed_loops} loops) across {len(body_parts)} objects")
print(f"Normal fix complete")
def main():
print("=" * 60)
print("Blender Shape Key Transfer Script - Boundary Velocity Limiting")
@@ -1184,7 +1295,8 @@ def main():
print(f"\nLoading final working file for seam fix...")
bpy.ops.wm.open_mainfile(filepath=working_file)
fix_seams_across_objects()
fix_normals_across_objects()
# Save after seam fix
print(f"\nSaving after seam fix...")
bpy.ops.wm.save_as_mainfile(filepath=working_file)