Files
AIC-Project/Tools/ModelTranslator/rizom_unwrap.py
T

143 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""RizomUV 自动展开:官方 RizomUVLink 驱动 rizomuv.exe 做 AutoSeam/Unfold/Pack。
仅在系统 Python 中使用(不进 Blender);任何不可用/失败抛 RizomError
由 model_decimate.py 回退 Blender seam 展开。"""
import os
import sys
import threading
DEFAULT_RIZOM = r"C:\Program Files\RizomUV2025\rizomuv.exe"
MAP_RES = 2048 # Pack 目标贴图分辨率
PADDING_PX = 4 # 岛间距(像素)= bl_decimate.PACK_MARGIN(0.002) * 2048
TIMEOUT_S = 300 # 单条命令超时
class RizomError(Exception):
"""RizomUV 不可用或处理失败(上层据此回退)。"""
def candidate_paths(cli_arg=None, env_val=None, reg_val=None, default=DEFAULT_RIZOM):
"""rizomuv.exe 候选路径,优先级:CLI 参数 > 环境变量 > 注册表 > 默认安装位置。"""
return [p for p in (cli_arg, env_val, reg_val, default) if p]
def registry_rizom_path():
"""官方 README 的注册表查找(HKLM\\SOFTWARE\\Rizom Lab\\RizomUV VS RS 202X.Y),
新版本优先;找不到或非 Windows 返回 None。"""
try:
import winreg
except ImportError:
return None
for i in range(9, 1, -1):
for j in range(10, -1, -1):
if i == 2 and j < 2:
continue
key_path = "SOFTWARE\\Rizom Lab\\RizomUV VS RS 202%d.%d" % (i, j)
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
val = winreg.QueryValue(key, "rizomuv.exe")
# 注册表值是不带后缀的安装前缀(官方只取 dirname 用),拼回真实 exe
return os.path.join(os.path.dirname(val), "rizomuv.exe")
except OSError:
continue
return None
def find_rizomuv(cli_arg=None, use_registry=True, default=DEFAULT_RIZOM):
"""返回第一个真实存在的 rizomuv.exe;都不存在抛 RizomError。"""
reg = registry_rizom_path() if use_registry else None
cands = candidate_paths(cli_arg, os.environ.get("RIZOMUV_EXE"), reg, default)
for p in cands:
if os.path.isfile(p):
return p
raise RizomError("找不到 rizomuv.exe(尝试过:%s" % "; ".join(cands))
def _import_link(exe):
"""从 RizomUV 安装目录动态引入官方 RizomUVLink 模块(随安装自带,MIT)。"""
link_dir = os.path.join(os.path.dirname(exe), "RizomUVLink")
if not os.path.isdir(link_dir):
raise RizomError("RizomUVLink 目录不存在:%s" % link_dir)
if link_dir not in sys.path:
sys.path.insert(0, link_dir)
try:
import RizomUVLink
return RizomUVLink
except Exception as e: # pyd 与 Python 版本不匹配等
raise RizomError("RizomUVLink 导入失败:%s" % e)
def _call(label, fn, params, timeout=TIMEOUT_S):
"""带超时执行一条 RizomUV 命令;超时或 CZEx 抛 RizomError。"""
out, err = [], []
def run():
try:
out.append(fn(params))
except Exception as e:
err.append(e)
t = threading.Thread(target=run, daemon=True)
t.start()
t.join(timeout)
if t.is_alive():
raise RizomError("%s 超时(%ds" % (label, timeout))
if err:
raise RizomError("%s 失败:%s" % (label, err[0]))
res = out[0] if out else None
# 部分命令(如 Load 文件不存在)不抛 CZEx,而是返回 {'Error': {'Msg':..,'Code':..}}
if isinstance(res, dict) and res.get("Error"):
raise RizomError("%s 失败:%s" % (label, res["Error"]))
return res
def rizom_unwrap(fbx_path, exe, map_res=MAP_RES, padding_px=PADDING_PX):
"""AutoSeam 全自动切缝 -> Unfold -> Pack -> 覆盖保存 fbx_path。失败抛 RizomError。"""
mod = _import_link(exe)
link = mod.CRizomUVLink()
try:
link.RunRizomUV(exePath=exe)
except Exception as e:
raise RizomError("RizomUV 启动失败(license/环境):%s" % e)
fbx_abs = os.path.abspath(fbx_path)
pad_uv = float(padding_px) / map_res
try:
_call("Load", link.Load, {"File.Path": fbx_abs, "File.XYZ": True})
# AutoSeamMOSAIC(QuasiDevelopable) + 切柄/连洞 + 防重叠后处理
_call("Select(AutoSeam)", link.Select, {
"PrimType": "Edge", "WorkingSet": "Visible",
"Select": True, "ResetBefore": True,
"Auto": {
"QuasiDevelopable": {"Developability": 0.5, "IslandPolyNBMin": 1,
"AreaMinRatio": 0.0, "FitCones": False,
"Straighten": True},
"HandleCutter": True,
"PipesCutter": True,
"SkeletonUnoverlap": {},
"FlatteningMode": 0,
"FlatteningUnfoldParams": {"BorderIntersections": True,
"TriangleFlips": True},
"StoreCoordsUVW": True,
"Quality": 0.5,
}})
_call("Cut", link.Cut, {"PrimType": "Edge", "WorkingSet": "Visible"})
_call("Unfold", link.Unfold, {"PrimType": "Edge", "WorkingSet": "Visible",
"BorderIntersections": True,
"TriangleFlips": True})
# Scaling.Mode=3 (AVG_RATIO_OF_GROUP):官方文档建议首次展开后用该模式统一密度。
# 注:pyd docstring 写的 Global 包装层已被 2025 应用侧拒绝(exe 内 Pack schema
# 无 Global,打包属性直接为顶层参数),故扁平传参。
_call("Pack", link.Pack, {
"RootGroup": "RootGroup", "WorkingSet": "Visible",
"ProcessTileSelection": False, "RecursionDepth": 1,
"Translate": True,
"MapResolution": map_res,
"PaddingSize": pad_uv, "MarginSize": pad_uv,
"Rotate": {"Step": 90.0},
"Scaling": {"Mode": 3}})
_call("Save", link.Save, {"File.Path": fbx_abs})
finally:
try:
link.Quit({})
except Exception:
pass