ModelTranslator: RizomUVLink 驱动 AutoSeam/Unfold/Pack(带 300s 超时,失败抛 RizomError)
This commit is contained in:
@@ -50,3 +50,93 @@ def find_rizomuv(cli_arg=None, use_registry=True, default=DEFAULT_RIZOM):
|
||||
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})
|
||||
# AutoSeam:MOSAIC(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
|
||||
|
||||
Reference in New Issue
Block a user