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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user