Add AIServer, character art pipeline, and Art package boot integration.

Bring in MaleExplorer assets/shaders, model tooling and Hunyuan updates, plus boot/YooAsset Art package support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-09-21 17:06:59 +08:00
co-authored by Cursor
parent b7a0bbcb89
commit 6550b8df4b
121 changed files with 313260 additions and 113 deletions
+43 -17
View File
@@ -52,12 +52,15 @@ def within_tolerance(target, actual, tol=TOLERANCE):
UV_FLIP_TOL = 0.02 # 质量门:UV 翻转面占比超此值回退 smart_project
UV_OVERLAP_TOL = 0.08 # 质量门:UV 重叠面占比阈值(手工 UV 同口径约 5%,灾难性失败 >20%)
UV_MAX_ISLAND_FRAC = 0.25 # 最大单岛 UV 面积占比超此值判不合格(挡光滑面巨型畸变岛)
def uv_gate_ok(flipped, overlap, overlap_tol=UV_OVERLAP_TOL):
"""UV 质量门:翻转按 UV_FLIP_TOL 固定,重叠按 overlap_tol(默认 UV_OVERLAP_TOL)。
overlap 为 None(op 不可用)按 0 处理。"""
return flipped <= UV_FLIP_TOL and (overlap or 0.0) <= overlap_tol
def uv_gate_ok(flipped, overlap, overlap_tol=UV_OVERLAP_TOL,
max_island_frac=0.0, island_frac_tol=UV_MAX_ISLAND_FRAC):
"""UV 质量门:翻转 / 重叠 / 最大单岛占比。overlap/max_island_frac 为 None 按 0。"""
return (flipped <= UV_FLIP_TOL
and (overlap or 0.0) <= overlap_tol
and (max_island_frac or 0.0) <= island_frac_tol)
def uvpm_mode_label(base, applied):
@@ -66,17 +69,17 @@ def uvpm_mode_label(base, applied):
def pick_best_candidate(candidates, overlap_tol=UV_OVERLAP_TOL):
"""从候选 UV 指标 dict 列表选过质量门且岛数最少者;无过门候选返回 None。
overlap_tol 覆盖重叠容忍。每个 candidate 至少含 flipped/overlap/islands。"""
"""从候选 UV 指标 dict 列表选过质量门且岛数最少者;无过门候选返回 None。"""
passing = [c for c in candidates
if uv_gate_ok(c["flipped"], c["overlap"], overlap_tol)]
if uv_gate_ok(c["flipped"], c["overlap"], overlap_tol,
c.get("max_island_frac", 0.0))]
if not passing:
return None
return min(passing, key=lambda c: c["islands"])
def count_islands(n_faces, adjacent_pairs):
"""union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。"""
def island_labels(n_faces, adjacent_pairs):
"""union-find:返回每个面所属岛根标签列表。"""
parent = list(range(n_faces))
def find(i):
@@ -89,7 +92,25 @@ def count_islands(n_faces, adjacent_pairs):
ra, rb = find(a), find(b)
if ra != rb:
parent[ra] = rb
return len({find(i) for i in range(n_faces)})
return [find(i) for i in range(n_faces)]
def count_islands(n_faces, adjacent_pairs):
"""union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。"""
return len(set(island_labels(n_faces, adjacent_pairs)))
def max_island_area_fraction(labels, abs_areas):
"""最大单岛 UV 面积占比。"""
if not labels or not abs_areas:
return 0.0
totals = {}
for lab, area in zip(labels, abs_areas):
totals[lab] = totals.get(lab, 0.0) + abs(area)
total = sum(totals.values())
if total <= 0.0:
return 0.0
return max(totals.values()) / total
def flipped_fraction(signed_areas):
@@ -168,7 +189,7 @@ UVPM_EXT = "bl_ext.user_default.uvpackmaster4" # UVPackmaster 4 扩展模块
UVPM_PIXEL_MARGIN = 4 # UVPM 岛间距(像素),与 PACK_MARGIN * UVPM_TEX_SIZE 同口径
UVPM_TEX_SIZE = 2048
UVPM_ROTATION_STEP = 15 # UVPM 旋转步进(度);比默认 90 更细,利于薄斜条对齐(启发式因引擎崩溃已弃用)
SEAM_ANGLE_SWEEP = (55.0, 45.0, 35.0) # 首选 SEAM_ANGLE_DEG 不过门时依次下探的 seam 角度
SEAM_ANGLE_SWEEP = (55.0, 45.0, 35.0, 30.0, 25.0, 20.0, 15.0) # 首选不过门时下探
STRETCH_ITERS = 30 # minimize_stretch 松弛迭代次数
REL_WELD = 1e-4 # 焊接距离占局部包围盒对角线比例(避免 cm/m 单位差异导致绝对阈值失准)
@@ -258,11 +279,14 @@ def _collect_uv_metrics(obj):
area += a.x * b.y - b.x * a.y
signed.append(area * 0.5)
n = len(bm.faces)
labels = island_labels(n, pairs)
abs_areas = [abs(a) for a in signed]
bm.free()
return {"islands": count_islands(n, pairs),
"fill": round(fill_ratio([abs(a) for a in signed]), 4),
return {"islands": len(set(labels)) if n else 0,
"fill": round(fill_ratio(abs_areas), 4),
"flipped": round(flipped_fraction(signed), 4),
"overlap": _overlap_fraction(obj)}
"overlap": _overlap_fraction(obj),
"max_island_frac": round(max_island_area_fraction(labels, abs_areas), 4)}
def _overlap_fraction(obj):
@@ -371,7 +395,8 @@ def _do_unwrap(obj, mode, warnings, overlap_tol=UV_OVERLAP_TOL):
m = _collect_uv_metrics(obj)
m["angle"] = ang
candidates.append(m)
if uv_gate_ok(m["flipped"], m["overlap"], overlap_tol):
if uv_gate_ok(m["flipped"], m["overlap"], overlap_tol,
m.get("max_island_frac", 0.0)):
break # 该档已过门;更低角度只会更碎,无需再试
best = pick_best_candidate(candidates, overlap_tol)
if best is not None:
@@ -383,9 +408,10 @@ def _do_unwrap(obj, mode, warnings, overlap_tol=UV_OVERLAP_TOL):
m["mode"] = uvpm_mode_label("seam@%d" % int(best["angle"]), uvpm)
return m
detail = "、".join(
"%d°(翻转%.1f%% 重叠%s)" % (
"%d°(翻转%.1f%% 重叠%s 大岛%.1f%%)" % (
int(c["angle"]), c["flipped"] * 100,
"%.1f%%" % (c["overlap"] * 100) if c["overlap"] is not None else "未知")
"%.1f%%" % (c["overlap"] * 100) if c["overlap"] is not None else "未知",
c.get("max_island_frac", 0.0) * 100)
for c in candidates)
warnings.append("所有 seam 角度均未过质量门(%s),回退 smart_project" % detail)
mode = "smart_fallback"