feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC. Content: - characters/ Lena/male body lanes, bakes, texture work, run logs - clothing/ garment pipeline, configs, gates, contract docs - garments/ MD-authored garment sources (.zprj/.zpac) - UAL-Lib/ Universal Animation Library 2 source (.blend/.fbx/.glb) - tools/ blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent - docs/, plans/, dev/, .agents/plans/ Repo hygiene: - .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out on purpose — ~250 are already tracked raw and converting them would rewrite every one without shrinking history. - .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies dropped by a `>/dev/null` redirect on Windows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
output_path = argv[0]
|
||||
|
||||
print(f"Importing {output_path}...")
|
||||
bpy.ops.import_scene.gltf(filepath=output_path)
|
||||
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print(f"Imported object: {obj.name}")
|
||||
print(f"Vertices: {len(obj.data.vertices)}")
|
||||
|
||||
# Merge by distance to fix UV seams
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.remove_doubles(threshold=0.00001)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
print(f"After merge: {len(obj.data.vertices)}")
|
||||
|
||||
# Save
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/01_imported.blend")
|
||||
print("Saved 01_imported.blend")
|
||||
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/01_imported.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print(f"Working on {obj.name}")
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
import bmesh
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
boundary_edges = [e for e in bm.edges if e.is_boundary]
|
||||
print(f"Total boundary edges: {len(boundary_edges)}")
|
||||
|
||||
hems = [
|
||||
{"name": "bra_band", "z": 0.637, "width": 0.005},
|
||||
{"name": "armholes", "z": 0.685, "width": 0.005},
|
||||
{"name": "neckline", "z": 0.734, "width": 0.005},
|
||||
{"name": "briefs_waist", "z": 0.588, "width": 0.005},
|
||||
{"name": "leg_openings", "z": 0.490, "width": 0.005}
|
||||
]
|
||||
|
||||
for hem in hems:
|
||||
z_min = hem["z"] - hem["width"]
|
||||
z_max = hem["z"] + hem["width"]
|
||||
count = 0
|
||||
for e in boundary_edges:
|
||||
v1 = e.verts[0].co.z
|
||||
v2 = e.verts[1].co.z
|
||||
if z_min <= v1 <= z_max or z_min <= v2 <= z_max:
|
||||
count += 1
|
||||
print(f"{hem["name"]} ({hem["z"]:.3f}): {count} boundary edges")
|
||||
|
||||
z_thigh_min, z_thigh_max = 0.28, 0.34
|
||||
thigh_count = 0
|
||||
for e in boundary_edges:
|
||||
v1 = e.verts[0].co.z
|
||||
v2 = e.verts[1].co.z
|
||||
if z_thigh_min <= v1 <= z_thigh_max or z_thigh_min <= v2 <= z_thigh_max:
|
||||
thigh_count += 1
|
||||
print(f"Control band (thigh 0.28-0.34): {thigh_count} boundary edges")
|
||||
|
||||
bm.free()
|
||||
@@ -0,0 +1,28 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/01_imported.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print(f"Working on {obj.name}")
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
bm.edges.ensure_lookup_table()
|
||||
for e in bm.edges:
|
||||
if e.is_boundary:
|
||||
e.select = True
|
||||
|
||||
print(f"Selected {len([e for e in bm.edges if e.select])} boundary edges")
|
||||
|
||||
bpy.ops.mesh.remove_doubles(threshold=0.001)
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
print("Welding complete.")
|
||||
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/02_welded.blend")
|
||||
print("Saved 02_welded.blend")
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/02_welded.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print(f"Working on {obj.name}")
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
# Get coordinates
|
||||
verts = mesh.vertices
|
||||
coords = np.zeros(len(verts) * 3, dtype=np.float32)
|
||||
verts.foreach_get("co", coords)
|
||||
coords = coords.reshape(-1, 3)
|
||||
|
||||
# Define Hem Z-bands
|
||||
hems = [
|
||||
{"name": "bra_band", "z": 0.637, "width": 0.005},
|
||||
{"name": "armholes", "z": 0.685, "width": 0.005},
|
||||
{"name": "neckline", "z": 0.734, "width": 0.005},
|
||||
{"name": "briefs_waist", "z": 0.588, "width": 0.005},
|
||||
{"name": "leg_openings", "z": 0.490, "width": 0.005}
|
||||
]
|
||||
|
||||
# Identify vertices in hem bands
|
||||
hem_indices = set()
|
||||
for hem in hems:
|
||||
z_min = hem["z"] - hem["width"]
|
||||
z_max = hem["z"] + hem["width"]
|
||||
mask = (coords[:, 2] >= z_min) & (coords[:, 2] <= z_max)
|
||||
indices = np.where(mask)[0]
|
||||
hem_indices.update(indices)
|
||||
print(f"{hem["name"]}: {len(indices)} vertices")
|
||||
|
||||
print(f"Total vertices in hem bands: {len(hem_indices)}")
|
||||
|
||||
# Store indices for next step
|
||||
np.save("characters/female/lena_nude/hires_work/hem_indices.npy", np.array(list(hem_indices)))
|
||||
print("Saved hem_indices.npy")
|
||||
@@ -0,0 +1,53 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
import math
|
||||
sys.path.append(os.getcwd())
|
||||
bpy.ops.wm.open_mainfile(filepath='characters/female/lena_nude/hires_work/02_welded.blend')
|
||||
obj = bpy.context.active_object
|
||||
mesh = obj.data
|
||||
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
|
||||
mesh.vertices.foreach_get('co', verts)
|
||||
verts = verts.reshape(-1, 3)
|
||||
z = verts[:, 2]
|
||||
y = verts[:, 1]
|
||||
x = verts[:, 0]
|
||||
print('--- Sculpting Breasts (V2) ---')
|
||||
center_z = 0.62
|
||||
apex_x_l = -0.035
|
||||
apex_x_r = 0.035
|
||||
chest_wall_y = -0.1565
|
||||
target_proj = 0.048
|
||||
print(f'Targeting Z={center_z} with proj {target_proj}')
|
||||
radius_x = 0.040
|
||||
radius_z = 0.050
|
||||
def breast_influence(vx, vz, cx, cz, rx, rz):
|
||||
dx = (vx - cx) / rx
|
||||
dz = (vz - cz) / rz
|
||||
return dx*dx + dz*dz
|
||||
influence_l = breast_influence(x, z, apex_x_l, center_z, radius_x, radius_z)
|
||||
influence_r = breast_influence(x, z, apex_x_r, center_z, radius_x, radius_z)
|
||||
mask_l = influence_l < 1.0
|
||||
mask_r = influence_r < 1.0
|
||||
def falloff(u):
|
||||
return (1.0 - u)**1.5
|
||||
disp_l = falloff(influence_l[mask_l]) * target_proj
|
||||
disp_r = falloff(influence_r[mask_r]) * target_proj
|
||||
direction = np.array([0, -1, 0], dtype=np.float32)
|
||||
verts[mask_l] += direction * disp_l[:, np.newaxis]
|
||||
verts[mask_r] += direction * disp_r[:, np.newaxis]
|
||||
print('Applying Cleavage (Strong Push)')
|
||||
cleavage_mask = (np.abs(x) < 0.015) & (z > 0.58) & (z < 0.66)
|
||||
if np.any(cleavage_mask):
|
||||
c_y = y[cleavage_mask]
|
||||
c_z = z[cleavage_mask]
|
||||
c_x = x[cleavage_mask]
|
||||
dist_from_center = np.abs(c_x)
|
||||
push_strength = 0.025 * (1.0 - dist_from_center/0.015)
|
||||
push_strength = np.maximum(push_strength, 0)
|
||||
verts[cleavage_mask, 0] += np.sign(c_x) * push_strength
|
||||
mesh.vertices.foreach_set('co', verts.ravel())
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath='characters/female/lena_nude/hires_work/03_sculpted.blend')
|
||||
print('Saved 03_sculpted.blend')
|
||||
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/02_welded.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print(f"Working on {obj.name}")
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
verts = mesh.vertices
|
||||
coords = np.zeros(len(verts) * 3, dtype=np.float32)
|
||||
verts.foreach_get("co", coords)
|
||||
coords = coords.reshape(-1, 3)
|
||||
|
||||
hem_indices = np.load("characters/female/lena_nude/hires_work/hem_indices.npy")
|
||||
print(f"Loaded {len(hem_indices)} hem vertices")
|
||||
|
||||
hems = [
|
||||
{"z": 0.637, "w": 0.005},
|
||||
{"z": 0.685, "w": 0.005},
|
||||
{"z": 0.734, "w": 0.005},
|
||||
{"z": 0.588, "w": 0.005},
|
||||
{"z": 0.490, "w": 0.005}
|
||||
]
|
||||
|
||||
is_hem = np.zeros(len(coords), dtype=bool)
|
||||
for h in hems:
|
||||
mask = (coords[:, 2] >= h["z"] - h["w"]) & (coords[:, 2] <= h["z"] + h["w"])
|
||||
is_hem |= mask
|
||||
|
||||
skin_indices = np.where(~is_hem)[0]
|
||||
print(f"Found {len(skin_indices)} skin vertices")
|
||||
|
||||
kd = KDTree(len(skin_indices))
|
||||
for i, idx in enumerate(skin_indices):
|
||||
kd.insert(coords[idx], i)
|
||||
kd.balance()
|
||||
|
||||
print("KDTree built.")
|
||||
|
||||
new_coords = coords.copy()
|
||||
count = 0
|
||||
for idx in hem_indices:
|
||||
co = coords[idx]
|
||||
# find_n returns a list of (Vector, index, distance)
|
||||
neighbors = kd.find_n(co, 5)
|
||||
if neighbors:
|
||||
avg_pos = np.mean([n[0] for n in neighbors], axis=0)
|
||||
new_coords[idx] = co * 0.5 + avg_pos * 0.5
|
||||
count += 1
|
||||
if count % 5000 == 0:
|
||||
print(f"Processed {count}/{len(hem_indices)}")
|
||||
|
||||
verts.foreach_set("co", new_coords.flatten())
|
||||
mesh.update()
|
||||
|
||||
print("Smoothing complete.")
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/03_smoothed.blend")
|
||||
print("Saved 03_smoothed.blend")
|
||||
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/03_smoothed.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print("Working on " + obj.name)
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
verts = mesh.vertices
|
||||
coords = np.zeros(len(verts) * 3, dtype=np.float32)
|
||||
verts.foreach_get("co", coords)
|
||||
coords = coords.reshape(-1, 3)
|
||||
|
||||
z_min_chest = 0.64
|
||||
z_max_chest = 0.74
|
||||
|
||||
mask_chest = (coords[:, 2] >= z_min_chest) & (coords[:, 2] <= z_max_chest)
|
||||
chest_indices = np.where(mask_chest)[0]
|
||||
print("Chest region vertices: " + str(len(chest_indices)))
|
||||
|
||||
target_apex_z = 0.688
|
||||
target_apex_x = 0.035
|
||||
target_projection = 0.034
|
||||
|
||||
y_vals = coords[mask_chest, 1]
|
||||
print("Y range in chest band: " + str(y_vals.min()) + " to " + str(y_vals.max()))
|
||||
|
||||
chest_wall_y = np.percentile(y_vals, 25)
|
||||
print("Estimated Chest Wall Y: " + str(chest_wall_y))
|
||||
|
||||
new_coords = coords.copy()
|
||||
|
||||
mask_front = coords[:, 1] > chest_wall_y
|
||||
active_indices = np.where(mask_chest & mask_front)[0]
|
||||
print("Active vertices to sculpt: " + str(len(active_indices)))
|
||||
|
||||
centers = [
|
||||
np.array([-target_apex_x, 0, target_apex_z]),
|
||||
np.array([target_apex_x, 0, target_apex_z])
|
||||
]
|
||||
|
||||
radii = np.array([0.04, 0.04, 0.06])
|
||||
|
||||
for idx in active_indices:
|
||||
v = coords[idx]
|
||||
dists = [np.linalg.norm(v - c) for c in centers]
|
||||
nearest_center_idx = np.argmin(dists)
|
||||
center = centers[nearest_center_idx]
|
||||
delta = v - center
|
||||
norm_dist = np.sqrt(np.sum((delta / radii)**2))
|
||||
if norm_dist < 1.0:
|
||||
u = delta / radii
|
||||
term = 1.0 - u[0]**2 - u[2]**2
|
||||
if term > 0:
|
||||
y_offset = target_projection * np.sqrt(term)
|
||||
target_y = center[1] + y_offset
|
||||
blend = 1.0 - norm_dist
|
||||
blend = max(0, min(1, blend))
|
||||
current_y = v[1]
|
||||
new_y = current_y * (1 - blend) + target_y * blend
|
||||
if new_y > current_y:
|
||||
new_coords[idx, 1] = new_y
|
||||
|
||||
verts.foreach_set("co", new_coords.flatten())
|
||||
mesh.update()
|
||||
|
||||
print("Breast sculpting complete.")
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/04_sculpted.blend")
|
||||
print("Saved 04_sculpted.blend")
|
||||
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
sys.path.append(os.getcwd())
|
||||
bpy.ops.wm.open_mainfile(filepath='characters/female/lena_nude/hires_work/03_sculpted.blend')
|
||||
obj = bpy.context.active_object
|
||||
mesh = obj.data
|
||||
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
|
||||
mesh.vertices.foreach_get('co', verts)
|
||||
verts = verts.reshape(-1, 3)
|
||||
z = verts[:, 2]
|
||||
y = verts[:, 1]
|
||||
x = verts[:, 0]
|
||||
print('--- Verification ---')
|
||||
apex_z = 0.688
|
||||
apex_x_l = -0.035
|
||||
apex_x_r = 0.035
|
||||
delta = 0.01
|
||||
mask_l = (np.abs(x - apex_x_l) < delta) & (np.abs(z - apex_z) < delta)
|
||||
mask_r = (np.abs(x - apex_x_r) < delta) & (np.abs(z - apex_z) < delta)
|
||||
y_apex_l = y[mask_l].min() if np.any(mask_l) else None
|
||||
y_apex_r = y[mask_r].min() if np.any(mask_r) else None
|
||||
print(f'Apex Y (L): {y_apex_l}')
|
||||
print(f'Apex Y (R): {y_apex_r}')
|
||||
chest_wall_y = -0.1565
|
||||
proj_l = abs(y_apex_l - chest_wall_y) if y_apex_l else 0
|
||||
proj_r = abs(y_apex_r - chest_wall_y) if y_apex_r else 0
|
||||
print(f'Projection (L): {proj_l}')
|
||||
print(f'Projection (R): {proj_r}')
|
||||
sternum_mask = (np.abs(x) < 0.001) & (np.abs(z - apex_z) < 0.005)
|
||||
y_sternum = y[sternum_mask].min() if np.any(sternum_mask) else None
|
||||
print(f'Sternum Y at apex Z: {y_sternum}')
|
||||
cleavage_depth = abs(y_sternum - y_apex_l) if (y_sternum and y_apex_l) else 0
|
||||
print(f'Cleavage Depth: {cleavage_depth}')
|
||||
print('--- Checks ---')
|
||||
print(f'Proj Gate (0.036-0.044): {0.036 <= proj_l <= 0.044}')
|
||||
print(f'Cleavage Gate (>0.015): {cleavage_depth > 0.015}')
|
||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/04_sculpted.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print("Working on " + obj.name)
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
verts = mesh.vertices
|
||||
coords = np.zeros(len(verts) * 3, dtype=np.float32)
|
||||
verts.foreach_get("co", coords)
|
||||
coords = coords.reshape(-1, 3)
|
||||
|
||||
# Define Crotch Region
|
||||
# Waistband z=0.588, Leg openings z=0.490
|
||||
# We want to smooth the region between z=0.45 and z=0.60
|
||||
z_min_crotch = 0.45
|
||||
z_max_crotch = 0.60
|
||||
|
||||
mask_crotch = (coords[:, 2] >= z_min_crotch) & (coords[:, 2] <= z_max_crotch)
|
||||
crotch_indices = np.where(mask_crotch)[0]
|
||||
print("Crotch region vertices: " + str(len(crotch_indices)))
|
||||
|
||||
# Identify the "inner" vertices (between legs)
|
||||
# These are vertices with low |x| and low y (front to back)
|
||||
# We will smooth them towards the median of the region to remove detail
|
||||
|
||||
# Simple approach: Smooth all vertices in this band towards their local average
|
||||
# This will flatten the crotch and remove the briefs waistband/leg rims
|
||||
|
||||
new_coords = coords.copy()
|
||||
|
||||
# We will use a simple box blur for robustness
|
||||
# For each vertex, average with neighbors in a small radius
|
||||
# Since we don't have topology easily, we use KDTree again
|
||||
|
||||
kd = KDTree(len(crotch_indices))
|
||||
for i, idx in enumerate(crotch_indices):
|
||||
kd.insert(coords[idx], i)
|
||||
kd.balance()
|
||||
|
||||
print("KDTree built for crotch.")
|
||||
|
||||
# Smooth factor
|
||||
alpha = 0.5
|
||||
radius = 0.02
|
||||
|
||||
count = 0
|
||||
for idx in crotch_indices:
|
||||
co = coords[idx]
|
||||
neighbors = kd.find_range(co, radius)
|
||||
if neighbors:
|
||||
avg_pos = np.mean([n[0] for n in neighbors], axis=0)
|
||||
new_coords[idx] = co * (1 - alpha) + avg_pos * alpha
|
||||
count += 1
|
||||
if count % 5000 == 0:
|
||||
print("Processed " + str(count) + "/" + str(len(crotch_indices)))
|
||||
|
||||
verts.foreach_set("co", new_coords.flatten())
|
||||
mesh.update()
|
||||
|
||||
print("Crotch smoothing complete.")
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/05_crotch_smoothed.blend")
|
||||
print("Saved 05_crotch_smoothed.blend")
|
||||
@@ -0,0 +1,99 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/05_crotch_smoothed.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print("Working on " + obj.name)
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
verts = mesh.vertices
|
||||
coords = np.zeros(len(verts) * 3, dtype=np.float32)
|
||||
verts.foreach_get("co", coords)
|
||||
coords = coords.reshape(-1, 3)
|
||||
|
||||
uvs = mesh.uv_layers.active.data
|
||||
uv_coords = np.zeros(len(uvs) * 2, dtype=np.float32)
|
||||
uvs.foreach_get("uv", uv_coords)
|
||||
uv_coords = uv_coords.reshape(-1, 2)
|
||||
|
||||
img = bpy.data.images["lena+glb_basecolor.jpg"]
|
||||
pixels = np.array(img.pixels[:], dtype=np.float32)
|
||||
pixels = pixels.reshape(img.size[1], img.size[0], 4)
|
||||
|
||||
z_min_bra = 0.64
|
||||
z_max_bra = 0.74
|
||||
z_min_briefs = 0.45
|
||||
z_max_briefs = 0.60
|
||||
chest_wall_y = -0.01787954
|
||||
|
||||
mask_bra = (coords[:, 2] >= z_min_bra) & (coords[:, 2] <= z_max_bra) & (coords[:, 1] > chest_wall_y)
|
||||
mask_briefs = (coords[:, 2] >= z_min_briefs) & (coords[:, 2] <= z_max_briefs)
|
||||
bra_indices = np.where(mask_bra)[0]
|
||||
briefs_indices = np.where(mask_briefs)[0]
|
||||
print("Bra vertices: " + str(len(bra_indices)))
|
||||
print("Briefs vertices: " + str(len(briefs_indices)))
|
||||
|
||||
mask_skin = ~(mask_bra | mask_briefs)
|
||||
skin_indices = np.where(mask_skin)[0]
|
||||
print("Skin vertices: " + str(len(skin_indices)))
|
||||
|
||||
kd = KDTree(len(skin_indices))
|
||||
for i, idx in enumerate(skin_indices):
|
||||
kd.insert(coords[idx], i)
|
||||
kd.balance()
|
||||
|
||||
loops = mesh.loops
|
||||
loop_verts = np.zeros(len(loops), dtype=np.int32)
|
||||
loops.foreach_get("vertex_index", loop_verts)
|
||||
loop_uvs = uv_coords
|
||||
|
||||
all_garment_indices = np.concatenate((bra_indices, briefs_indices))
|
||||
is_garment_loop = np.isin(loop_verts, all_garment_indices)
|
||||
garment_loop_indices = np.where(is_garment_loop)[0]
|
||||
print("Garment loops: " + str(len(garment_loop_indices)))
|
||||
|
||||
new_pixels = pixels.copy()
|
||||
|
||||
skin_loop_indices = np.where(~is_garment_loop)[0]
|
||||
skin_loop_verts = loop_verts[skin_loop_indices]
|
||||
skin_loop_uvs = loop_uvs[skin_loop_indices]
|
||||
|
||||
vert_to_uv = {}
|
||||
for i in range(len(skin_loop_indices)):
|
||||
v_idx = skin_loop_verts[i]
|
||||
if v_idx not in vert_to_uv:
|
||||
vert_to_uv[v_idx] = skin_loop_uvs[i]
|
||||
|
||||
count = 0
|
||||
for loop_idx in garment_loop_indices:
|
||||
v_idx = loop_verts[loop_idx]
|
||||
v_co = coords[v_idx]
|
||||
_, skin_v_idx, _ = kd.find_n(v_co, 1)[0]
|
||||
if skin_v_idx in vert_to_uv:
|
||||
uv = vert_to_uv[skin_v_idx]
|
||||
px = int(uv[0] * (img.size[0] - 1))
|
||||
py = int(uv[1] * (img.size[1] - 1))
|
||||
px = max(0, min(img.size[0]-1, px))
|
||||
py = max(0, min(img.size[1]-1, py))
|
||||
color = pixels[py, px]
|
||||
current_uv = loop_uvs[loop_idx]
|
||||
cpx = int(current_uv[0] * (img.size[0] - 1))
|
||||
cpy = int(current_uv[1] * (img.size[1] - 1))
|
||||
cpx = max(0, min(img.size[0]-1, cpx))
|
||||
cpy = max(0, min(img.size[1]-1, cpy))
|
||||
new_pixels[cpy, cpx] = color
|
||||
count += 1
|
||||
if count % 5000 == 0:
|
||||
print("Processed " + str(count) + "/" + str(len(garment_loop_indices)))
|
||||
|
||||
img.pixels[:] = new_pixels.flatten()
|
||||
|
||||
print("Texture repaint complete.")
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/06_textured.blend")
|
||||
print("Saved 06_textured.blend")
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
import bpy
|
||||
import numpy as np
|
||||
import bmesh
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath="characters/female/lena_nude/hires_work/06_textured.blend")
|
||||
obj = bpy.context.selected_objects[0]
|
||||
print("Working on " + obj.name)
|
||||
|
||||
mesh = obj.data
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
# Neutralise Normal Map
|
||||
norm_img = bpy.data.images["lena+glb_normal.jpg"]
|
||||
norm_pixels = np.array(norm_img.pixels[:], dtype=np.float32)
|
||||
norm_pixels = norm_pixels.reshape(norm_img.size[1], norm_img.size[0], 4)
|
||||
# Flat normal is 128, 128, 255 (0.5, 0.5, 1.0)
|
||||
norm_pixels[:, :, 0] = 0.5
|
||||
norm_pixels[:, :, 1] = 0.5
|
||||
norm_pixels[:, :, 2] = 1.0
|
||||
norm_img.pixels[:] = norm_pixels.flatten()
|
||||
print("Normal map neutralised.")
|
||||
|
||||
# Neutralise Roughness/Metallic Map
|
||||
rm_img = bpy.data.images["lena+glb_rm.jpg"]
|
||||
rm_pixels = np.array(rm_img.pixels[:], dtype=np.float32)
|
||||
rm_pixels = rm_pixels.reshape(rm_img.size[1], rm_img.size[0], 4)
|
||||
# Set to skin-like roughness (e.g. 0.5)
|
||||
rm_pixels[:, :, 0] = 0.5
|
||||
rm_img.pixels[:] = rm_pixels.flatten()
|
||||
print("RM map neutralised.")
|
||||
|
||||
# Save
|
||||
bpy.context.preferences.filepaths.save_version = 0 # no .blend1 autosave
|
||||
bpy.ops.wm.save_as_mainfile(filepath="characters/female/lena_nude/hires_work/07_final.blend")
|
||||
print("Saved 07_final.blend")
|
||||
@@ -0,0 +1,8 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.getcwd())
|
||||
bpy.ops.wm.open_mainfile(filepath='characters/female/lena_nude/hires_work/07_final.blend')
|
||||
obj = bpy.context.active_object
|
||||
print(f'Verts: {len(obj.data.vertices)}')
|
||||
print(f'Tris: {len(obj.data.loop_triangles)}')
|
||||
@@ -0,0 +1,140 @@
|
||||
|
||||
===== GLM agent start 2026-08-04 23:19:01 model=glm-4.6 budget=180.0min =====
|
||||
|
||||
--- step 1 (180 min left) ---
|
||||
COMMAND: mkdir -p characters/female/lena_nude/hires_work/review
|
||||
[output]
|
||||
exit=0
|
||||
(no output)
|
||||
|
||||
--- step 2 (180 min left) ---
|
||||
COMMAND: cp characters/originals/female/lena_sculpt_glb_v01.glb characters/female/lena_nude/hires_work/lena_sculpt_glb_v01.glb
|
||||
[output]
|
||||
exit=0
|
||||
(no output)
|
||||
|
||||
--- step 3 (180 min left) ---
|
||||
COMMAND: python -c "import sys; print(sys.version)" 2>&1 | head -1 || /c/Program\ Files/Blender\ Foundation/Blender\ 5.1/blender.exe --background --python-expr "import sys; print(sys.version)" 2>&1 | head -1
|
||||
[output]
|
||||
exit=0
|
||||
3.12.10 (tags/v3.12.10:0cc8128, Apr 8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)]
|
||||
|
||||
--- step 4 (180 min left) ---
|
||||
COMMAND: cat > characters/female/lena_nude/hires_work/01_import_and_merge.py << 'EOF'
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
# Args
|
||||
work_dir = sys.argv[-1]
|
||||
source_glb = os.path.join(work_dir, "lena_sculpt_glb_v01.glb")
|
||||
blend_path = os.path.join(work_dir, "step01_merged.blend")
|
||||
|
||||
# Clear scene
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete()
|
||||
|
||||
# Import
|
||||
bpy.ops.import_scene.gltf(filepath=source_glb)
|
||||
obj = bpy.context.selected_objects[0]
|
||||
obj.name = "Lena_Sculpt_Work"
|
||||
|
||||
# Merge by distance to close UV seams
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
# Threshold 1e-5 as suggested
|
||||
bpy.ops.mesh.remove_doubles(threshold=1e-5)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# Save
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
|
||||
print(f"Saved {blend_path}")
|
||||
print(f"Verts: {len(obj.data.vertices)}")
|
||||
EOF
|
||||
COMMAND: "/c/Program Files/Blender Foundation/Blender 5.1/blender.exe" --background --python characters/female/lena_nude/hires_work/01_import_and_merge.py -- characters/female/lena_nude/hires_work
|
||||
|
||||
===== GLM agent start 2026-08-04 23:19:37 model=glm-4.6 budget=180.0min =====
|
||||
|
||||
--- step 1 (180 min left) ---
|
||||
COMMAND: mkdir -p characters/female/lena_nude/hires_work/review
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 2 (180 min left) ---
|
||||
COMMAND: mkdir -p characters/female/lena_nude/hires_work/review && cp characters/originals/female/lena_sculpt_glb_v01.glb characters/female/lena_nude/hires_work/lena_sculpt_glb_v01.glb
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 3 (180 min left) ---
|
||||
COMMAND: ls -la characters/originals/female/ | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 4 (180 min left) ---
|
||||
COMMAND: pwd && ls -la
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 5 (180 min left) ---
|
||||
COMMAND: git ls-files | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 6 (180 min left) ---
|
||||
COMMAND: find . -name "lena_sculpt_glb_v01.glb" 2>/dev/null | head -5
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 7 (180 min left) ---
|
||||
COMMAND: ls -R . | grep -i "lena_sculpt" | head -10
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 8 (180 min left) ---
|
||||
COMMAND: ls -la characters/ 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 9 (180 min left) ---
|
||||
COMMAND: ls -la . 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 10 (180 min left) ---
|
||||
COMMAND: ls -la /c/animation/ 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 11 (180 min left) ---
|
||||
COMMAND: ls -la /c/ 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 12 (180 min left) ---
|
||||
COMMAND: ls -la / 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 13 (180 min left) ---
|
||||
COMMAND: cmd.exe /c "dir C:\ /b" 2>&1 | head -20
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 14 (180 min left) ---
|
||||
COMMAND: python -c "import os; print(os.getcwd())" 2>&1
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 15 (180 min left) ---
|
||||
COMMAND: echo "test" 2>&1
|
||||
[output]
|
||||
exit=HARNESS_ERROR FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)
|
||||
|
||||
--- step 16 (180 min left) ---
|
||||
DONE: I am unable to execute any shell commands. The environment is returning `FileNotFoundError(2, 'The system cannot find the file specified', None, 2, None)` for every command attempted, including basic built-ins like `pwd`, `ls`, and `echo`. This indicates a fundamental issue with the shell execution environment or the working directory path. I cannot proceed with the task without the ability to run commands.
|
||||
|
||||
===== agent reported DONE =====
|
||||
|
||||
===== GLM agent end 2026-08-04 23:20:03 =====
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
import bmesh
|
||||
sys.path.append(os.getcwd())
|
||||
bpy.ops.wm.open_mainfile(filepath='characters/female/lena_nude/hires_work/03_sculpted.blend')
|
||||
obj = bpy.context.active_object
|
||||
mesh = obj.data
|
||||
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
|
||||
mesh.vertices.foreach_get('co', verts)
|
||||
verts = verts.reshape(-1, 3)
|
||||
z = verts[:, 2]
|
||||
y = verts[:, 1]
|
||||
x = verts[:, 0]
|
||||
print('--- Verification ---')
|
||||
apex_z_target = 0.688
|
||||
apex_mask = (z > apex_z_target - 0.01) & (z < apex_z_target + 0.01)
|
||||
if np.any(apex_mask):
|
||||
apex_y = y[apex_mask]
|
||||
apex_x = x[apex_mask]
|
||||
min_y = np.min(apex_y)
|
||||
print(f'Apex Z range: {np.min(z[apex_mask]):.3f} - {np.max(z[apex_mask]):.3f}')
|
||||
print(f'Apex Y (front-most): {min_y:.4f}')
|
||||
print(f'Apex X range: {np.min(apex_x):.3f} to {np.max(apex_x):.3f}')
|
||||
chest_wall_y = -0.1565
|
||||
proj = abs(min_y - chest_wall_y)
|
||||
print(f'Projection: {proj:.4f} (Target 0.036-0.044)')
|
||||
else:
|
||||
print('No vertices found at apex Z.')
|
||||
print('--- Cleavage Check ---')
|
||||
sternum_mask = (np.abs(x) < 0.005) & (z > 0.65) & (z < 0.72)
|
||||
if np.any(sternum_mask):
|
||||
sternum_y = y[sternum_mask]
|
||||
sternum_z = z[sternum_mask]
|
||||
min_idx = np.argmin(sternum_y)
|
||||
print(f'Sternum at apex Z ({apex_z_target}): Y={sternum_y[min_idx]:.4f}')
|
||||
print(f'Gap to Apex: {min_y - sternum_y[min_idx]:.4f} (Target > 0.015)')
|
||||
else:
|
||||
print('No sternum vertices found.')
|
||||
print('--- Boundary Check ---')
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bm.edges.ensure_lookup_table()
|
||||
boundary_edges = [e for e in bm.edges if e.is_boundary]
|
||||
print(f'Open boundary edges: {len(boundary_edges)}')
|
||||
@@ -0,0 +1,49 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
import bmesh
|
||||
sys.path.append(os.getcwd())
|
||||
bpy.ops.wm.open_mainfile(filepath='characters/female/lena_nude/hires_work/07_final.blend')
|
||||
obj = bpy.context.active_object
|
||||
mesh = obj.data
|
||||
verts = np.empty(len(mesh.vertices) * 3, dtype=np.float32)
|
||||
mesh.vertices.foreach_get('co', verts)
|
||||
verts = verts.reshape(-1, 3)
|
||||
z = verts[:, 2]
|
||||
y = verts[:, 1]
|
||||
x = verts[:, 0]
|
||||
print('--- Breast Shape Check ---')
|
||||
apex_z_target = 0.688
|
||||
apex_mask = (z > apex_z_target - 0.01) & (z < apex_z_target + 0.01)
|
||||
if np.any(apex_mask):
|
||||
apex_y = y[apex_mask]
|
||||
apex_x = x[apex_mask]
|
||||
min_y = np.min(apex_y)
|
||||
print(f'Apex Z range: {np.min(z[apex_mask]):.3f} - {np.max(z[apex_mask]):.3f}')
|
||||
print(f'Apex Y (front-most): {min_y:.4f} (Target: < -0.1205)')
|
||||
print(f'Apex X range: {np.min(apex_x):.3f} to {np.max(apex_x):.3f}')
|
||||
else:
|
||||
print('No vertices found at apex Z.')
|
||||
print('--- Cleavage Check ---')
|
||||
sternum_mask = (np.abs(x) < 0.005) & (z > 0.65) & (z < 0.72)
|
||||
if np.any(sternum_mask):
|
||||
sternum_y = y[sternum_mask]
|
||||
sternum_z = z[sternum_mask]
|
||||
min_idx = np.argmin(sternum_y)
|
||||
print(f'Sternum at apex Z ({apex_z_target}): Y={sternum_y[min_idx]:.4f}')
|
||||
print(f'Gap to Apex: {min_y - sternum_y[min_idx]:.4f} (Target > 0.015)')
|
||||
else:
|
||||
print('No sternum vertices found.')
|
||||
print('--- Boundary Check ---')
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bm.edges.ensure_lookup_table()
|
||||
boundary_edges = [e for e in bm.edges if e.is_boundary]
|
||||
print(f'Open boundary edges: {len(boundary_edges)}')
|
||||
print('--- Hems Z-band Check ---')
|
||||
bands = [(0.485, 0.495), (0.583, 0.593), (0.632, 0.642), (0.680, 0.690), (0.729, 0.739)]
|
||||
for low, high in bands:
|
||||
mask = (z >= low) & (z <= high)
|
||||
count = np.sum(mask)
|
||||
print(f'Z {low:.3f}-{high:.3f}: {count} verts')
|
||||
Reference in New Issue
Block a user