diff --git a/Tools/ModelTranslator/rizom_unwrap.py b/Tools/ModelTranslator/rizom_unwrap.py new file mode 100644 index 00000000..48718dd7 --- /dev/null +++ b/Tools/ModelTranslator/rizom_unwrap.py @@ -0,0 +1,50 @@ +"""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: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) + return winreg.QueryValue(key, "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)) diff --git a/Tools/ModelTranslator/tests/test_rizom_unwrap.py b/Tools/ModelTranslator/tests/test_rizom_unwrap.py new file mode 100644 index 00000000..aa74dc89 --- /dev/null +++ b/Tools/ModelTranslator/tests/test_rizom_unwrap.py @@ -0,0 +1,31 @@ +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import rizom_unwrap as ru + + +class TestCandidatePaths(unittest.TestCase): + def test_priority_order(self): + self.assertEqual(ru.candidate_paths("a.exe", "b.exe", "c.exe"), + ["a.exe", "b.exe", "c.exe", ru.DEFAULT_RIZOM]) + + def test_none_entries_skipped(self): + self.assertEqual(ru.candidate_paths(None, None, None), [ru.DEFAULT_RIZOM]) + + def test_env_only(self): + self.assertEqual(ru.candidate_paths(None, "b.exe", None), + ["b.exe", ru.DEFAULT_RIZOM]) + + +class TestFindRizomuv(unittest.TestCase): + def test_missing_exe_raises(self): + os.environ.pop("RIZOMUV_EXE", None) + with self.assertRaises(ru.RizomError): + ru.find_rizomuv("Z:/no/such/rizomuv.exe", use_registry=False, + default="Z:/no/default.exe") + + +if __name__ == "__main__": + unittest.main()