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 = "" # 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(" 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("", 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 (" Could not transfer UVs from", riz_obj, "to", original) if DEBUG: print (" 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(" 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()