ModelTranslator: 展开路径接入 UVPM4 排布——保底+增强,失败回退内置 pack
This commit is contained in:
@@ -103,6 +103,9 @@ def _decimate(obj, ratio):
|
||||
|
||||
SEAM_ANGLE_DEG = 66.0 # 锐边阈值:两面夹角超此值标 seam
|
||||
PACK_MARGIN = 0.002 # 岛间距 ≈ 2048 图 4px
|
||||
UVPM_EXT = "bl_ext.user_default.uvpackmaster4" # UVPackmaster 4 扩展模块名
|
||||
UVPM_PIXEL_MARGIN = 4 # UVPM 岛间距(像素),与 PACK_MARGIN * UVPM_TEX_SIZE 同口径
|
||||
UVPM_TEX_SIZE = 2048
|
||||
|
||||
|
||||
def _clear_uv_layers(mesh):
|
||||
@@ -204,21 +207,81 @@ def _overlap_fraction(obj):
|
||||
return round(sum(1 for p in polys if p.select) / float(len(polys)), 4)
|
||||
|
||||
|
||||
_uvpm_failed = False # 进程内失败一次即不再重试(避免重复警告与启动开销)
|
||||
|
||||
|
||||
def _uvpm_enable():
|
||||
"""启用 UVPM4 扩展(幂等)。headless 下先补丁 GPU shader 创建——
|
||||
UVPM4 导入期为视口覆盖层建 shader,后台模式无 GPU 绘图会 SystemError;
|
||||
覆盖层仅交互用,pack 不受影响。引擎路径经注册表自动发现。"""
|
||||
import bpy
|
||||
import addon_utils
|
||||
if bpy.app.background:
|
||||
import gpu
|
||||
orig = gpu.shader.from_builtin
|
||||
if not getattr(orig, "_mt_safe", False):
|
||||
def _safe(*a, **k):
|
||||
try:
|
||||
return orig(*a, **k)
|
||||
except SystemError:
|
||||
return None
|
||||
_safe._mt_safe = True
|
||||
gpu.shader.from_builtin = _safe
|
||||
# default_set=True 才建 preferences.addons 条目(UVPM4 register 依赖);
|
||||
# factory-startup 关闭偏好自动保存,不会写盘
|
||||
if addon_utils.enable(UVPM_EXT, default_set=True) is None:
|
||||
raise RuntimeError("扩展 %s 启用失败(未安装或版本不兼容)" % UVPM_EXT)
|
||||
|
||||
|
||||
def _uvpm_repack(obj, warnings):
|
||||
"""UVPM4 重排当前 UV 布局,成功返回 True;任何失败记警告返回 False,
|
||||
保底布局(pack_islands/smart_project)原样保留。"""
|
||||
global _uvpm_failed
|
||||
if _uvpm_failed:
|
||||
return False
|
||||
import bpy
|
||||
try:
|
||||
_uvpm_enable()
|
||||
p = bpy.context.scene.uvpm4_props.default_main_props
|
||||
p.pixel_margin_enable = True
|
||||
p.pixel_margin = UVPM_PIXEL_MARGIN
|
||||
p.pixel_margin_tex_size = UVPM_TEX_SIZE
|
||||
bpy.context.scene.tool_settings.use_uv_select_sync = True
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
try:
|
||||
ret = bpy.ops.uvpackmaster4.pack(mode_id='pack.single_tile',
|
||||
pack_op_type='0')
|
||||
finally:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
if 'FINISHED' not in ret:
|
||||
raise RuntimeError("pack 返回 %s" % sorted(ret))
|
||||
return True
|
||||
except Exception as e:
|
||||
_uvpm_failed = True
|
||||
warnings.append("UVPackmaster 排布不可用(%s),沿用内置 pack 布局" % e)
|
||||
return False
|
||||
|
||||
|
||||
def _do_unwrap(obj, mode, warnings):
|
||||
"""按模式展开;seam 质量不达标自动回退 smart。返回 uv 指标 dict(含 mode)。"""
|
||||
"""按模式展开(排布默认 UVPM4 增强,失败保底内置 pack);
|
||||
seam 质量不达标自动回退 smart。返回 uv 指标 dict(含 mode)。"""
|
||||
if mode == "seam":
|
||||
_unwrap_seam(obj, warnings)
|
||||
uvpm = _uvpm_repack(obj, warnings)
|
||||
m = _collect_uv_metrics(obj)
|
||||
if uv_gate_ok(m["flipped"], m["overlap"]):
|
||||
m["mode"] = "seam"
|
||||
m["mode"] = uvpm_mode_label("seam", uvpm)
|
||||
return m
|
||||
warnings.append("seam 展开质量不达标(翻转 %.1f%% 重叠 %s),回退 smart_project"
|
||||
% (m["flipped"] * 100,
|
||||
"%.1f%%" % (m["overlap"] * 100) if m["overlap"] is not None else "未知"))
|
||||
mode = "smart_fallback"
|
||||
_unwrap_smart(obj)
|
||||
uvpm = _uvpm_repack(obj, warnings)
|
||||
m = _collect_uv_metrics(obj)
|
||||
m["mode"] = mode if mode == "smart_fallback" else "smart"
|
||||
m["mode"] = uvpm_mode_label(mode if mode == "smart_fallback" else "smart", uvpm)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user