MetaBox/Scripts/Modeling/UV/RizomBridge/RizomUVBridge.py
2025-01-14 02:12:52 +08:00

870 lines
35 KiB
Python

import os, platform, subprocess, sys
if sys.version_info.minor < 11:
# Maya 2024 and earlier
from PySide2 import QtGui
from PySide2 import QtWidgets, QtCore
from shiboken2 import wrapInstance
else:
# Maya 2025
from PySide6 import QtGui
from PySide6 import QtWidgets, QtCore
from shiboken6 import wrapInstance
if sys.version_info.major == 3:
# Python 3
from . import scriptlist
elif sys.version_info.major == 2:
# Python 2
import scriptlist
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
from maya import OpenMayaUI as omui
import maya.OpenMaya as om
import maya.cmds as cmds
import maya.mel as mel
import xml.dom.minidom as xml # For XML settings
DEBUG = False
PRNT_STRING = "<RizomUV Bridge>"
# om.MGlobal.displayError('ERROR!')
class Settings():
def __init__(self):
self.config_path = os.path.join('%s/RizomBridge' % os.getenv('APPDATA'))
config_file = 'uisettings.xml'
# TODO: Create a unique id for the lua script tied to the Maya instance.
# rizom_script_path = tempfile.gettempdir() + os.sep + "MayaRizomBridgeScript.lua"
rizom_script_path = os.path.join(self.config_path, "MayaRizomBridgeScript.lua")
self.rizom_script_path = rizom_script_path.replace('\\', '/')
self.apppath = None
self.config_file_path = os.path.join(self.config_path, config_file)
self.check_config_exists(self.config_path)
try:
self.read_settings()
except:
print("<RizomUV Bridge> Failed to read settings, creating new xml file.")
self.set_defaults()
self.save_xml()
self.read_settings()
self.reset_export_path()
def reset_export_path(self):
self.exportFile = os.path.join(self.config_path, self.objname)
self.exportFile = self.exportFile.replace('\\', '/')
def read_settings(self):
doc = xml.parse(self.config_file_path)
ui_app = doc.getElementsByTagName("Application")[0]
self.apppath = ui_app.getAttribute("apppath")
self.objname = ui_app.getAttribute("objname")
self.upaxis = ui_app.getAttribute("upaxis")
self.loaduvs = self.str_to_bool(ui_app.getAttribute("loaduvs"))
self.useuvlink = self.str_to_bool(ui_app.getAttribute("useuvlink"))
self.fixuvnames = self.str_to_bool(ui_app.getAttribute("fixuvnames"))
self.usecustompath = self.str_to_bool(ui_app.getAttribute("usecustompath"))
self.custompath = ui_app.getAttribute("custompath")
ui_packer = doc.getElementsByTagName("Packer")[0]
self.quality = int(ui_packer.getAttribute("quality"))
self.mutations = int(ui_packer.getAttribute("mutations"))
self.margin = int(ui_packer.getAttribute("margin"))
self.spacing = int(ui_packer.getAttribute("spacing"))
self.resolution = int(ui_packer.getAttribute("resolution"))
self.initscaleavg = self.str_to_bool(ui_packer.getAttribute("initscaleavg"))
self.autofit = self.str_to_bool(ui_packer.getAttribute("autofit"))
def set_defaults(self):
self.objname = "MayaRizomExport.fbx"
self.upaxis = "Y"
self.loaduvs = True
self.useuvlink = True
self.fixuvnames = False
self.usecustompath = False
self.custompath = ""
self.quality = 2
self.mutations = 256
self.margin = 2
self.spacing = 2
self.resolution = 1024
self.initscaleavg = True
self.autofit = True
self.apppath = self.findall_rizom_installs()[-1]
pass
def save_xml(self):
print(PRNT_STRING, "Saving settings to disk: {}".format(self.config_file_path))
doc = xml.Document()
root_element = doc.createElement("RizomBridge")
element_application = doc.createElement("Application")
element_application.setAttribute("apppath", str(self.apppath))
element_application.setAttribute("objname", str(self.objname))
element_application.setAttribute("upaxis", str(self.upaxis))
element_application.setAttribute("loaduvs", str(self.loaduvs))
element_application.setAttribute("useuvlink", str(self.useuvlink))
element_application.setAttribute("fixuvnames", str(self.fixuvnames))
element_application.setAttribute("usecustompath", str(self.usecustompath))
element_application.setAttribute("custompath", str(self.custompath))
element_packer = doc.createElement("Packer")
element_packer.setAttribute("quality", str(self.quality))
element_packer.setAttribute("mutations", str(self.mutations))
element_packer.setAttribute("margin", str(self.margin))
element_packer.setAttribute("spacing", str(self.spacing))
element_packer.setAttribute("resolution", str(self.resolution))
element_packer.setAttribute("initscaleavg", str(self.initscaleavg))
element_packer.setAttribute("autofit", str(self.autofit))
doc.appendChild(root_element)
root_element.appendChild(element_application)
root_element.appendChild(element_packer)
doc.writexml(open(self.config_file_path, 'w'), indent=" ", addindent=" ", newl='\n')
def check_config_exists(self, config_path):
print(PRNT_STRING, "Checking for config file:{}".format(self.config_file_path))
if not os.path.exists(config_path):
os.makedirs(config_path)
if not os.path.exists(self.config_file_path):
print(PRNT_STRING, "Config not found.")
self.set_defaults()
self.save_xml()
else:
print(PRNT_STRING, "Config found.")
def findall_rizom_installs(self):
""" Returns list of all rizom.exe in registry that exist on disk """
try:
import winreg
except:
self.manual_locate_rizom()
return [self.apppath]
key_path = "SOFTWARE\\Rizom Lab\\"
parent_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
installs = []
i = 0
while i < 1000:
try:
key = winreg.EnumKey(parent_key, i)
try:
exe_path = winreg.QueryValue(winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path + key),
"rizomuv.exe") + ".exe"
except FileNotFoundError:
exe_path = ""
if os.path.exists(exe_path):
installs.append(exe_path.replace("\\", "/"))
except WindowsError:
break
i += 1
print (PRNT_STRING, "Found RizomUV installs in Windows Registry:")
for inst in sorted(installs):
print(" ..", inst)
return sorted(installs)
def manual_locate_rizom(self):
om.MGlobal.displayWarning("Could not locate rizomuv.exe: %s" % self.apppath)
fname = QtWidgets.QFileDialog.getOpenFileName(None, 'Locate rizomuv.exe', 'C:/Program Files/Rizom Lab/', "Executable (*.exe)")
self.apppath = fname[0]
self.appver = self.get_version()
self.save_xml()
def get_version(self):
""" Return version number as list as read from the folder name, eg. [2022, 2] """
return os.path.dirname(self.apppath).split()[-1].split('.')
def check_lua_path(self):
if not os.path.exists(self.rizom_script_path):
with open(self.rizom_script_path, 'w') as f:
if DEBUG: print(PRNT_STRING, "Creating blank lua file at", self.rizom_script_path)
f.write('')
def str_to_bool(self, s):
if s == 'True':
return True
else:
# Return False for everything else, otherwise it might return None which will cause a crash.
return False
class RizomUVBridgeWindow(MayaQWidgetDockableMixin, QtWidgets.QDialog):
def __init__(self, rootWidget=None, *args, **kwargs):
super(RizomUVBridgeWindow, self).__init__()
"""
if not os.path.exists(config_path):
reset_config()
if DEBUG: print("<RizomUV Bridge>", config_path)
Config.read(config_path)
"""
self.conf = Settings()
self.conf.check_lua_path()
# self.rizomPath = self.conf.apppath
print("Rizom Path:", self.conf.apppath)
self.setWindowTitle("Bridge")
self.sj_num_selchange = cmds.scriptJob(parent=self.objectName(), event=['SelectionChanged', self.ui_update_uvchannels])
# self.setMinimumSize(200, 300)
self.resize(250, 300)
self.create_widgets()
self.create_layouts()
self.create_connections()
self.validate_export_path()
self.exported_objects = []
self.ui_update_uvchannels()
self.cleanse_namespaces()
if not os.path.exists(self.conf.apppath):
self.conf.manual_locate_rizom()
v = self.conf.get_version()
self.btn_run.setText('Run RizomUV {}.{}'.format(v[0], v[1]))
rizom_link_path = os.path.dirname(self.conf.apppath) + '/RizomUVLink'
self.port = None
if rizom_link_path not in sys.path:
sys.path.append(os.path.dirname(self.conf.apppath) + '/RizomUVLink')
try:
from RizomUVLink import CRizomUVLink
self.link = CRizomUVLink()
except:
self.link = None
# noinspection PyAttributeOutsideInit
def create_widgets(self):
v = ".".join(self.conf.get_version())
self.btn_run = QtWidgets.QPushButton('Run RizomUV {}'.format(v), self)
self.btn_export = QtWidgets.QPushButton('Export', self)
self.btn_import = QtWidgets.QPushButton('Import', self)
self.cbx_use_link = QtWidgets.QCheckBox('Attempt to use CRizomUVLink', self)
self.cbx_use_link.setChecked(self.conf.useuvlink)
self.cbx_fix_set_names = QtWidgets.QCheckBox('Fix UV set names on Import', self)
self.cbx_fix_set_names.setChecked(self.conf.fixuvnames)
self.cbx_custom_path = QtWidgets.QCheckBox('Use custom path for fbx', self)
self.cbx_custom_path.setChecked(self.conf.usecustompath)
self.field_custom_path = QtWidgets.QLineEdit("C:/")
self.field_custom_path.setText(self.conf.custompath)
# Export settings widgets
self.radioAxisY = QtWidgets.QRadioButton('Y')
self.radioAxisZ = QtWidgets.QRadioButton('Z')
if self.conf.upaxis == 'Y':
self.radioAxisY.setChecked(True)
else:
self.radioAxisZ.setChecked(True)
self.cbx_keepuv = QtWidgets.QCheckBox('Load Existing UVs', self)
self.cbx_keepuv.setChecked(self.conf.loaduvs)
self.combo_scripts = QtWidgets.QComboBox(self)
self.combo_scripts.addItem("No Script")
for s in scriptlist.scripts:
self.combo_scripts.addItem(s[1])
# Utilities widgets
self.btn_fix_shell_normals = QtWidgets.QPushButton('Set UV border edges to Hard')
self.btn_edit_settings = QtWidgets.QPushButton('Open settings folder')
self.combo_pack_uvset = QtWidgets.QComboBox(self)
#
self.btn_pack = QtWidgets.QPushButton('Export and Pack UVs', self)
self.combo_pack_quality = QtWidgets.QComboBox(self)
self.combo_pack_quality.addItems(['Low', 'Normal', 'High', 'Higher', 'Ultra'])
self.combo_pack_quality.setCurrentIndex(self.conf.quality)
#
self.slider_pack_uvmap = QtWidgets.QSlider(QtCore.Qt.Orientation(1))
self.slider_pack_uvmap.setMinimum(0)
self.slider_pack_uvmap.setMaximum(5)
self.slider_pack_uvmap.setValue(1)
self.label_uvmap = QtWidgets.QLabel("1")
self.dspin_pack_mutations = QtWidgets.QSpinBox()
self.dspin_pack_mutations.setSingleStep(1)
self.dspin_pack_mutations.setRange(1, 1000)
self.dspin_pack_mutations.setWrapping(False)
self.dspin_pack_mutations.setValue(self.conf.mutations)
#
self.dspin_pack_resolution = QtWidgets.QSpinBox()
self.dspin_pack_resolution.setSingleStep(8)
self.dspin_pack_resolution.setRange(8, 8192)
self.dspin_pack_resolution.setWrapping(False)
self.dspin_pack_resolution.setValue(self.conf.resolution)
#
self.dspin_pack_margin = QtWidgets.QSpinBox()
self.dspin_pack_margin.setSingleStep(1)
self.dspin_pack_margin.setWrapping(False)
self.dspin_pack_margin.setValue(self.conf.margin)
#
self.dspin_pack_spacing = QtWidgets.QSpinBox()
self.dspin_pack_spacing.setSingleStep(1)
self.dspin_pack_spacing.setWrapping(False)
self.dspin_pack_spacing.setValue(self.conf.spacing)
#
self.cbx_initial_scale_avg = QtWidgets.QCheckBox("Use 'Avarage' Initial Scale", self)
self.cbx_initial_scale_avg.setChecked(self.conf.initscaleavg)
#
self.cbx_layout_scaling = QtWidgets.QCheckBox("Layout: Auto Fit", self)
self.cbx_layout_scaling.setChecked(self.conf.autofit)
pass
# noinspection PyAttributeOutsideInit
def create_layouts(self):
main_layout = QtWidgets.QVBoxLayout(self)
grp_layout = QtWidgets.QVBoxLayout()
grp_layout.addWidget(self.btn_run)
grp_layout.addWidget(self.btn_export)
grp_layout.addWidget(self.btn_import)
grp_layout.addWidget(self.cbx_use_link)
grp_layout.addWidget(self.cbx_fix_set_names)
#hor_layout = QtWidgets.QHBoxLayout()
grp_layout.addWidget(self.cbx_custom_path)
grp_layout.addWidget(self.field_custom_path)
#grp_layout.addLayout(hor_layout)
# grp_layout.addWidget(self.btn_edit)
grp = QtWidgets.QGroupBox("UV Operations")
grp.setLayout(grp_layout)
main_layout.addWidget(grp)
# Export Settings
grp_layout = QtWidgets.QVBoxLayout()
grp_layout.addWidget(self.cbx_keepuv)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("Scripts"))
hor_layout.addWidget(self.combo_scripts)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("Up Axis"))
hor_layout.addWidget(self.radioAxisY)
hor_layout.addWidget(self.radioAxisZ)
grp_layout.addLayout(hor_layout)
grp = QtWidgets.QGroupBox("Export Settings")
grp.setLayout(grp_layout)
main_layout.addWidget(grp)
# Utilites
grp_layout = QtWidgets.QVBoxLayout()
grp_layout.addWidget(self.btn_fix_shell_normals)
grp_layout.addWidget(self.btn_edit_settings)
# hor_layout = QtWidgets.QHBoxLayout()
# hor_layout.addWidget(self.btn_uvl_edges)
# grp_layout.addLayout(hor_layout)
grp = QtWidgets.QGroupBox("Utilities")
grp.setLayout(grp_layout)
main_layout.addWidget(grp)
# UV Packer
grp_layout = QtWidgets.QVBoxLayout()
grp = QtWidgets.QGroupBox("UV Packer")
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(self.btn_pack)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("UV Set to pack"))
hor_layout.addWidget(self.combo_pack_uvset)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("Packing Quality"))
hor_layout.addWidget(self.combo_pack_quality)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("Mutations"))
hor_layout.addWidget(self.dspin_pack_mutations)
hor_layout.addWidget(QtWidgets.QLabel("Resolution"))
hor_layout.addWidget(self.dspin_pack_resolution)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(QtWidgets.QLabel("Margin"))
hor_layout.addWidget(self.dspin_pack_margin)
hor_layout.addWidget(QtWidgets.QLabel("Spacing"))
hor_layout.addWidget(self.dspin_pack_spacing)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(self.cbx_initial_scale_avg)
grp_layout.addLayout(hor_layout)
hor_layout = QtWidgets.QHBoxLayout()
hor_layout.addWidget(self.cbx_layout_scaling)
grp_layout.addLayout(hor_layout)
grp_layout.addStretch()
grp.setLayout(grp_layout)
main_layout.addWidget(grp)
pass
def create_connections(self):
self.btn_run.clicked.connect(self.riz_run)
self.btn_export.clicked.connect(self.riz_export)
self.btn_import.clicked.connect(self.riz_import)
self.btn_fix_shell_normals.clicked.connect(fix_shell_border_normals)
self.btn_edit_settings.clicked.connect(self.browse_settings_location)
self.cbx_custom_path.stateChanged.connect(self.validate_export_path)
self.field_custom_path.textEdited.connect(self.validate_export_path)
self.cbx_keepuv.stateChanged.connect(self.set_config)
self.slider_pack_uvmap.valueChanged.connect(self.ui_pack_update_labels)
self.radioAxisY.clicked.connect(self.set_config)
self.radioAxisZ.clicked.connect(self.set_config)
# self.combo_scripts.currentIndexChanged.connect(self.ui_toggle_roundtrip_option)
self.dspin_pack_mutations.valueChanged.connect(self.set_config)
self.dspin_pack_resolution.valueChanged.connect(self.set_config)
self.dspin_pack_margin.valueChanged.connect(self.set_config)
self.dspin_pack_spacing.valueChanged.connect(self.set_config)
self.cbx_layout_scaling.stateChanged.connect(self.set_config)
self.cbx_use_link.stateChanged.connect(self.set_config)
self.cbx_fix_set_names.stateChanged.connect(self.set_config)
self.btn_pack.clicked.connect(self.riz_pack)
return
def riz_run(self):
# Confirm application path
if not os.path.exists(self.conf.apppath):
self.manual_locate_rizom()
if self.link and self.cbx_use_link.isChecked():
self.port = self.link.RunRizomUV()
print(f"{PRNT_STRING} RizomUV {self.link.RizomUVVersion()} link established. Now listening to commands on TCP port: {str(self.port)}")
# Enable UI relevant only for RizomUV Link
# self.btn_uvl_edges.setEnabled(True)
# self.btn_uvl_edges.setStyleSheet("background-color: {}; color: white".format("#ef4000"))
else:
# Use original method if link is not working.
print(PRNT_STRING, "RizomUV link not available. Communicating using LUA script file.")
cmd = '"' + self.conf.apppath + '" -cf "' + self.conf.rizom_script_path + '"'
print(cmd)
if platform.system() == "Windows":
self.sp = subprocess.Popen(cmd, shell=True)
else:
self.sp = subprocess.Popen(["open", "-a", self.conf.apppath, "-cf", self.conf.rizom_script_path])
self.btn_export.setEnabled(True)
return
def riz_pack(self):
# Export model with no script loaded,
# Also tell Rizom not to load the model in the riz_export function
# because it sometimes does not have time to load before we overwrite the lua file with these commands.
current_uv = self.combo_pack_uvset.currentText()
# current_uv_index = self.combo_pack_uvset.currentIndex()
exported = self.riz_export(False, False)
if not exported:
return
# Construct LUA code from GUI options
cmd = ''
# Common repeated text chunk
cmd_properties_prefix = 'ZomIslandGroups({Mode="SetGroupsProperties", WorkingSet="Visible", MergingPolicyString="A_ADD|AIB_ADD_A_VALUE_B|B_CLONE", GroupPaths={ "RootGroup" }, '
# Load model
cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZUVW=true, UVWProps=true}})\n'
# cmd += 'ZomUvset({Mode="SetCurrent", Name="Channel%s"})\n' % str(self.label_uvmap.text())
cmd += 'ZomUvset({Mode="SetCurrent", Name="%s"})\n' % current_uv
# cmd += 'ZomSet({Path = "Vars.Viewport.ViewportUV.WorkingSet", Value = "Visible&Flat"})\n'
# cmd += 'ZomSet({Path="Prefs.PackOptions.MapResolution", Value=%i})\n' % self.dspin_pack_resolution.value()
cmd += cmd_properties_prefix + 'Properties={Pack={Rotate={Step=90}}}})\n'
cmd += cmd_properties_prefix + 'Properties={Pack={Rotate={Mode=0}}}})\n'
margin = float(self.dspin_pack_margin.value()) / self.dspin_pack_resolution.value()
spacing = float(self.dspin_pack_spacing.value()) / self.dspin_pack_resolution.value()
cmd += cmd_properties_prefix + 'Properties={Pack={SpacingSize=%f}}})\n' % spacing
cmd += cmd_properties_prefix + 'Properties={Pack={MarginSize=%f}}})\n' % margin
quality = [128, 256, 512, 1024, 2048]
cmd += cmd_properties_prefix + 'Properties={Pack={Resolution=%i}}})\n' % quality[self.combo_pack_quality.currentIndex()]
cmd += cmd_properties_prefix + 'Properties={Pack={MaxMutations=%i}}})\n' % self.dspin_pack_mutations.value()
init_scale = 0
if self.cbx_initial_scale_avg.isChecked():
init_scale = 2
layout_scale = 0
if self.cbx_layout_scaling.isChecked():
layout_scale = 2
cmd += 'ZomSet({Path="Prefs.PackOptions.__ScalingMode", Value=%i})\n' % init_scale
cmd += 'ZomSave({File={Path="c:/users/root/appdata/local/temp/MayaRizomExport.fbx", UVWProps=true}, __UpdateUIObjFileName=true})\n'
cmd += 'ZomIslandGroups({Mode="DistributeInTilesEvenly", WorkingSet="Visible&Flat", MergingPolicyString="A_ADD|AIB_ADD_A_VALUE_B|B_CLONE", UseTileLocks=true, UseIslandLocks=true})\n'
cmd += 'ZomPack({RootGroup="RootGroup", WorkingSet="Visible&Flat", ProcessTileSelection=false, RecursionDepth=1, Translate=true, LayoutScalingMode=%i, Scaling={Mode=%i}})\n' % (layout_scale, init_scale)
if DEBUG:
print(cmd)
self.write_to_lua_file(cmd)
return
def fbx_export(self):
# FBX Export
if not cmds.pluginInfo("fbxmaya", loaded=True, query=True):
cmds.loadPlugin("fbxmaya")
cmds.undoInfo(openChunk=True)
mel.eval('ConvertInstanceToObject;') # This is the only command that is important to undo.
mel.eval('FBXExportSmoothingGroups -v true;')
mel.eval('FBXExportTriangulate -v false;')
mel.eval('FBXExportSmoothMesh -v false;')
mel.eval('FBXExportUpAxis {};'.format(self.conf.upaxis))
print('FBXExport -s -f "{}";'.format(self.conf.exportFile))
mel.eval('FBXExport -s -f "{}";'.format(self.conf.exportFile))
try:
cmds.undo()
except RuntimeError as RE:
# There are no more commands to undo
pass
# End FBX Export
return
def riz_export(self, use_script=True, load_model=True):
# displaySmoothness -divisionsU 0
self.exported_objects = cmds.ls(selection=True, tr=True)
# Exit preview smooth, because the FBXExportSmoothMesh option is not always respected by the exporter.
cmds.displaySmoothness(du=0, dv=0, pw=4, ps=0, po=1)
if self.cbx_custom_path.isChecked() and not self.field_custom_path.text() == self.conf.custompath:
self.set_config()
if self.link and self.cbx_use_link.isChecked():
print("Rizom Link version", self.link.Version())
# FBX Export
self.fbx_export()
if not self.port:
self.riz_run()
params = {
"File.Path": self.conf.exportFile,
"File.XYZUVW": True, # 3D + UV data loaded (use File.XYZ instead to load 3D data only)
"File.UVWProps": True, # UVs properties such as pinning, texel density settings etc... will be loaded
"File.ImportGroups": True, # Island group hierarchy will be loaded
"__Focus": True, # Focus viewports on the loaded mesh
# "Data.UseImportedUVWPolygons": False,
}
self.link.Load(params)
if DEBUG: print(f"{PRNT_STRING} EXPORTED TO:", self.conf.exportFile)
cmds.select(self.exported_objects)
# Enable import button if it was disabled.
self.btn_import.setEnabled(True)
else:
# Classic Method #
print(f"{PRNT_STRING} Exporting model")
self.cleanse_namespaces() # Agressive cleanse
if not self.exported_objects:
return False
# Delete history in case their are empty groups that would be removed by this action.
# Otherwise they would be cleared during import and we have an object count missmatch.
cmds.bakePartialHistory()
cmd = ''
# Export Options
if load_model:
if self.cbx_keepuv.isChecked():
cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZUVW=true, UVWProps=true}})\n'
else:
cmd += 'ZomLoad({File={Path="'+self.conf.exportFile+'", ImportGroups=true, XYZ=true}, NormalizeUVW=true})\n'
# Add scripts
if self.combo_scripts.currentIndex() and use_script is True:
script_name = scriptlist.scripts[self.combo_scripts.currentIndex()-1][0]
script_path = os.path.join(os.path.dirname(__file__), 'lua_scripts/%s' % script_name)
with open(script_path, 'r') as lua_script:
cmd += lua_script.read()
if DEBUG: print(f"{PRNT_STRING} {cmd}")
# FBX Export
self.fbx_export()
if DEBUG: print(f"{PRNT_STRING} EXPORTED TO: {self.conf.exportFile}")
cmds.select(self.exported_objects)
self.write_to_lua_file(cmd)
self.btn_import.setEnabled(True)
return True
def riz_import(self):
if not os.path.exists(self.conf.exportFile):
om.MGlobal.displayError("Could not locate exported file: %s" % self.conf.exportFile)
self.btn_import.setEnabled(False)
if self.cbx_use_link.isChecked():
if self.link:
try:
self.link.Save({"File.Path": self.conf.exportFile})
except RuntimeError as RE:
cmds.error('Could not send Save command to Rizom over Python Link. Was it enabled when you exported?')
# FBX Import
namespace = ':RIZOMUV'
if not cmds.namespace(ex=namespace):
cmds.namespace(add=namespace)
cmds.namespace(set=namespace)
mel.eval('FBXImportMode -v add;')
mel.eval('FBXImport -f "{}";'.format(self.conf.exportFile))
# END FBX Import
imported_objects = cmds.ls('RIZOMUV:*', long=True, type="transform")
original_matches = []
for riz_obj in imported_objects:
print ("Imported object name", riz_obj)
original = riz_obj.replace('RIZOMUV:', '')
original_matches.append(original)
# List UV sets of each item, skip if it has none. This is simply to exclude objects like group nodes.
original_uvsets = cmds.polyUVSet(original, allUVSets=True, q=True)
if not original_uvsets:
print(f"Object {riz_obj} has no UVSets. Will not attempt UV Transfer")
continue
imported_uvsets = cmds.polyUVSet(riz_obj, allUVSets=True, q=True)
if original_uvsets:
# Check names #
if self.cbx_fix_set_names.isChecked():
for i in range(len(original_uvsets)):
try:
if not original_uvsets[i] == imported_uvsets[i]:
cmds.polyUVSet(riz_obj, rename=True, uvSet=imported_uvsets[i], newUVSet=original_uvsets[i])
except IndexError:
# This can happen due to a weird Maya bug that I don't understand where the object has more
# uvSets listed with the polyUVSet comman, than it has in the UV Set Editor
print ("UV Set Index Error. Objects don't seem to have the same amount of UV sets")
print("Original UV Sets:", original_uvsets, original)
print("Imported UV Sets:", imported_uvsets, riz_obj)
pass
try:
#cmds.polyTransfer(original, ao=riz_obj, ch=False, uv=True)
cmds.polyTransfer(original, ao=riz_obj, ch=False, vc=False, v=False, uv=True)
except RuntimeError as rt:
print ("<RizomUV Bridge> Could not transfer UVs from", riz_obj, "to", original)
if DEBUG: print ("<RizomUV Bridge> Transfering UVs from", riz_obj, "to", original)
cmds.select(original_matches)
cmds.bakePartialHistory()
cmds.delete(':RIZOMUV:*')
cmds.namespace(rm=':RIZOMUV')
return
def riz_link(self):
"""
Establishes link with RizomUV. Even though this is just a single line, I want it in a function to refresh UI.
"""
pass
def browse_settings_location(self):
os.startfile(os.path.dirname(self.conf.config_file_path))
def ui_pack_update_labels(self):
print("Value changed")
self.label_uvmap.setText(str(self.slider_pack_uvmap.value()))
def ui_update_uvchannels(self):
sel_obj = cmds.ls(sl=True, tr=True)
if not sel_obj:
self.combo_pack_uvset.clear()
return
uvsets = cmds.polyUVSet(sel_obj[0], allUVSets=True, q=True)
current_set = cmds.polyUVSet(sel_obj[0], cuv=True, q=True)
if uvsets:
print (uvsets)
self.combo_pack_uvset.clear()
self.combo_pack_uvset.addItems(uvsets)
self.combo_pack_uvset.setCurrentIndex(uvsets.index(current_set[0]))
def validate_export_path(self):
if self.cbx_custom_path.isChecked():
if not os.path.exists(self.field_custom_path.text()):
self.btn_import.setEnabled(False)
self.btn_export.setEnabled(False)
self.field_custom_path.setStyleSheet("color: red")
return
else:
self.conf.exportFile = os.path.join(self.conf.custompath, self.conf.objname)
self.btn_import.setEnabled(True)
self.btn_export.setEnabled(True)
self.field_custom_path.setStyleSheet("color: white")
else:
self.conf.reset_export_path()
self.field_custom_path.setStyleSheet("color: grey")
self.btn_import.setEnabled(True)
self.btn_export.setEnabled(True)
print(self.conf.exportFile)
def cleanse_namespaces(self):
""" Step One --
Try to delete all existing RIZOMUV namespaces until it fails, this should get rid of stacked instances like
RIZOMUV:RIZOMUV:RIZOMUV
"""
fail = False
while(not fail):
try:
cmds.namespace(rm=':RIZOMUV', mnr=True)
except:
fail = True
""" Step Two --
In one scene I got the namespaces had gotten renamed to RIZOMUV1, RIZOMUV2, RIZOMUV3 etc
so this finds any leftoverrs and deletes them.
"""
# existing_ns = cmds.namespaceInfo(listNamespace=True, listOnlyNamespaces=True)
# for entry in existing_ns:
# if 'RIZOMUV' in entry:
# cmds.namespace(rm=entry, mnr=True)
#
# return
""" Step Two, updated --
Let's try to remove other namespaces than simply RIZOM as having others can prevent the import from working
"""
c = cmds.listRelatives(ad=True)
if not c:
return
for obj in c:
ns_split = obj.split(':')
if len(ns_split)>1:
result = cmds.confirmDialog(m="Selected objects have namespaces assigned. \n"
"These must go before you can use the tool correctly\n"
"Should I delete them for you? (Including on unselected objects)\n\n"
"Namespace on object: \n%s" % ns_split[0], button=['Remove', 'Cancel'])
if result == 'Remove':
cmds.namespace(rm=ns_split[0], mnr=True)
else:
return
return
def write_to_lua_file(self, command):
with open(self.conf.rizom_script_path, 'w') as f:
f.write(command)
print("<RIZOM> Wrote command to lua file:", self.conf.rizom_script_path)
for line in command.split("\n"):
print("\t", line)
return
def set_config(self):
self.conf.loaduvs = self.cbx_keepuv.isChecked()
if self.radioAxisY.isChecked():
self.conf.upaxis = "Y"
else:
self.conf.upaxis = "Z"
self.conf.mutations = self.dspin_pack_mutations.value()
self.conf.resolution = self.dspin_pack_resolution.value()
self.conf.margin = self.dspin_pack_margin.value()
self.conf.spacing = self.dspin_pack_spacing.value()
self.conf.autofit = self.cbx_layout_scaling.isChecked()
self.conf.fixuvnames = self.cbx_fix_set_names.isChecked()
self.conf.useuvlink = self.cbx_use_link.isChecked()
self.conf.usecustompath = self.cbx_custom_path.isChecked()
self.conf.custompath = self.field_custom_path.text()
self.conf.save_xml()
def closeEvent(self, event):
self.conf.save_xml()
def GetMayaWidget():
"""
Return the Maya main window widget as a Python object
"""
main_window_ptr = omui.MQtUtil.mainWindow()
if sys.version_info.major >= 3:
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
else:
return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
def fix_shell_border_normals():
obj_list = cmds.ls(sl=True, o=True)
all_final_borders = []
for sub_obj in obj_list:
cmds.select(sub_obj, r=True)
cmds.polyNormalPerVertex(ufn=True)
cmds.polySoftEdge(sub_obj, a=180, ch=1)
print("Soften all")
# Select object UVs
cmds.select(sub_obj + '.map[*]')
mel.eval('polySelectBorderShell 1;')
uv_border = cmds.polyListComponentConversion(te=True, internal=True)
uv_border = cmds.ls(uv_border, fl=True)
final_border = []
# Magical filter
for curEdge in uv_border:
edge_uvs = cmds.polyListComponentConversion(curEdge, tuv=True)
edge_uvs = cmds.ls(edge_uvs, fl=True)
if len(edge_uvs) > 2:
final_border.append(curEdge)
cmds.polySoftEdge(final_border, a=0, ch=1)
all_final_borders.append(final_border)
cmds.select(cl=True)
for sel_l in all_final_borders:
cmds.select(sel_l, add=True)
cmds.hilite(obj_list)
def run():
scriptJobs = cmds.scriptJob(listJobs=True)
for sj in scriptJobs:
if "RizomBridge" in sj:
print(PRNT_STRING, "Killing preexisting scriptJob:", sj)
cmds.scriptJob(kill=int(sj.split(':')[0]))
d = RizomUVBridgeWindow()
d.show(dockable=True)
if __name__ == "__main__":
run()