From 931ee1296fb89c7760b4db6b12f86a195b8dcc5d Mon Sep 17 00:00:00 2001 From: Tyler Fox Date: Thu, 22 Feb 2024 19:06:13 -0800 Subject: [PATCH] Allow for loading a simplex system onto a node with multiple shapes in Maya --- simplexui/commands/uvTransfer.py | 4 +- simplexui/interface/dummyInterface.py | 10 + simplexui/interface/mayaInterface.py | 99 +++-- simplexui/interface/xsiInterface.py | 10 + simplexui/items/simplex.py | 348 +++++++++--------- .../menu/mayaPlugins/softSelectToCluster.py | 3 +- simplexui/simplexDialog.py | 4 + 7 files changed, 282 insertions(+), 196 deletions(-) diff --git a/simplexui/commands/uvTransfer.py b/simplexui/commands/uvTransfer.py index 5890d5a..eb5b807 100644 --- a/simplexui/commands/uvTransfer.py +++ b/simplexui/commands/uvTransfer.py @@ -906,7 +906,7 @@ def uvTransferLoad( The target uv faces """ - import .alembicCommon as abc + from . import alembicCommon as abc from .mesh import Mesh if srcPath.endswith(".abc") or srcPath.endswith(".smpx"): @@ -978,7 +978,7 @@ def uvTransferFiles( ------- """ - import .alembicCommon as abc + from . import alembicCommon as abc tarVerts, tarFaces, tarUvs, tarUvFaces = uvTransferLoad( srcPath, tarPath, srcUvSet=srcUvSet, tarUvSet=tarUvSet, tol=tol, pBar=pBar diff --git a/simplexui/interface/dummyInterface.py b/simplexui/interface/dummyInterface.py index 10ccd1d..a263d4c 100644 --- a/simplexui/interface/dummyInterface.py +++ b/simplexui/interface/dummyInterface.py @@ -275,6 +275,16 @@ def postLoad(self, simp, preRet): """ pass + def checkForErrors(self, window): + """ Check for any DCC specific errors + + Parameters + ---------- + window : QMainWindow + The simplex window + """ + pass + # System IO @undoable def loadNodes(self, simp, thing, create=True, pBar=None): diff --git a/simplexui/interface/mayaInterface.py b/simplexui/interface/mayaInterface.py index bf85e03..050f5c2 100644 --- a/simplexui/interface/mayaInterface.py +++ b/simplexui/interface/mayaInterface.py @@ -274,6 +274,23 @@ def _checkAllShapeValidity(self, shapeNames): missingNames.append(shapeName) return missingNames, len(attrs) + + @classmethod + def _removeExtraShapeNodes(cls, tfm): + shapeNodes = cmds.listRelatives(tfm, shapes=True, noIntermediate=True) + if len(shapeNodes) > 1: + keeper = None + todel = [] + for sn in shapeNodes: + tfmChk = ''.join(sn.rsplit('Shape', 1)) + if tfmChk == tfm: + keeper = sn + else: + todel.append(sn) + if keeper is not None: + cmds.delete(todel) + + def preLoad(self, simp, simpDict, create=True, pBar=None): """ @@ -293,21 +310,24 @@ def preLoad(self, simp, simpDict, create=True, pBar=None): """ cmds.undoInfo(state=False) - if pBar is not None: - pBar.setLabelText("Loading Connections") - QApplication.processEvents() - ev = simpDict["encodingVersion"] + try: + if pBar is not None: + pBar.setLabelText("Loading Connections") + QApplication.processEvents() + ev = simpDict["encodingVersion"] - shapeNames = simpDict.get("shapes") - if not shapeNames: - return + shapeNames = simpDict.get("shapes") + if not shapeNames: + return - if ev > 1: - shapeNames = [i["name"] for i in shapeNames] + if ev > 1: + shapeNames = [i["name"] for i in shapeNames] + + toMake, nextIndex = self._checkAllShapeValidity(shapeNames) - toMake, nextIndex = self._checkAllShapeValidity(shapeNames) + if not toMake: + return - if toMake: if not create: if pBar is not None: msg = "\n".join( @@ -332,24 +352,33 @@ def preLoad(self, simp, simpDict, create=True, pBar=None): pBar.setValue(0) QApplication.processEvents() + baseShape = cmds.duplicate(self.mesh)[0] + cmds.delete(baseShape, constructionHistory=True) + self._removeExtraShapeNodes(baseShape) + for i, shapeName in enumerate(toMake): if pBar is not None: pBar.setLabelText("Creating Empty Shape:\n{0}".format(shapeName)) pBar.setValue(i) QApplication.processEvents() - newShape = cmds.duplicate(self.mesh, name=shapeName)[0] - cmds.delete(newShape, constructionHistory=True) + baseShape = cmds.rename(baseShape, shapeName) + index = self._firstAvailableIndex() cmds.blendShape( - self.shapeNode, edit=True, target=(self.mesh, index, newShape, 1.0) + self.shapeNode, edit=True, target=(self.mesh, index, baseShape, 1.0) ) weightAttr = "{0}.weight[{1}]".format(self.shapeNode, index) thing = cmds.ls(weightAttr)[0] + cmds.connectAttr("{0}.weights[{1}]".format(self.op, nextIndex), thing) - cmds.delete(newShape) nextIndex += 1 - return None + + cmds.delete(baseShape) + except Exception: + cmds.undoInfo(state=True) + raise + def postLoad(self, simp, preRet): """ @@ -366,6 +395,23 @@ def postLoad(self, simp, preRet): """ cmds.undoInfo(state=True) + def checkForErrors(self, window): + """ Check for any DCC specific errors + + Parameters + ---------- + window : QMainWindow + The simplex window + """ + shapeNodes = cmds.listRelatives(self.mesh, shapes=True, noIntermediate=True) + if len(shapeNodes) > 1: + msg = ( + "The current mesh has multiple shape nodes.", + "The UI will still mostly work, but extracting/connecting shapes" + "may fail in unexpected ways." + ) + QMessageBox.warning(window, "Multiple Shape Nodes", '\n'.join(msg)) + # System IO @undoable def loadNodes(self, simp, thing, create=True, pBar=None): @@ -655,18 +701,18 @@ def loadAbc(self, abcMesh, js, pBar=None): cmds.polyEvaluate(importHead, vertex=True) # Force a refresh cmds.disconnectAttr(abcNode + ".outPolyMesh[0]", importHeadShape + ".inMesh") - importBS = cmds.blendShape(self.mesh, importHead)[0] + importRest = cmds.duplicate(self.mesh, name="importRest")[0] + cmds.delete(importRest, constructionHistory=True) + self._removeExtraShapeNodes(importRest) + + importBS = cmds.blendShape(importRest, importHead)[0] cmds.blendShape(importBS, edit=True, weight=[(0, 1.0)]) # Maybe get shapeNode from self.mesh?? - inTarget = ( - importBS - + ".inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].inputGeomTarget" - ) - cmds.disconnectAttr(self.mesh + ".worldMesh[0]", inTarget) importOrig = [ i for i in cmds.listRelatives(importHead, shapes=True) if i.endswith("Orig") ][0] cmds.connectAttr(abcNode + ".outPolyMesh[0]", importOrig + ".inMesh") + cmds.delete(importRest) if pBar is not None: pBar.show() @@ -1239,6 +1285,15 @@ def renameSystem(self, name): ------- """ + if ( + self.mesh is None + or self.ctrl is None + or self.shapeNode is None + or self.op is None + or self.simplex is None + ): + raise ValueError("System is not set up. Cannot rename") + nn = self.mesh.replace(self.name, name) self.mesh = cmds.rename(self.mesh, nn) diff --git a/simplexui/interface/xsiInterface.py b/simplexui/interface/xsiInterface.py index 8bf5c1c..8bd0fe7 100644 --- a/simplexui/interface/xsiInterface.py +++ b/simplexui/interface/xsiInterface.py @@ -364,6 +364,16 @@ def postLoad(self, simp, preRet): self.recreateShapeNode() self.updateSlidersRange(simp.sliders) + def checkForErrors(self, window): + """ Check for any DCC specific errors + + Parameters + ---------- + window : QMainWindow + The simplex window + """ + pass + # System IO @undoable def loadNodes(self, simp, thing, create=True, pBar=None): diff --git a/simplexui/items/simplex.py b/simplexui/items/simplex.py index 36628f5..f710210 100644 --- a/simplexui/items/simplex.py +++ b/simplexui/items/simplex.py @@ -931,44 +931,46 @@ def loadV3(self, simpDict, create=True, pBar=None): """ preRet = self.DCC.preLoad(self, simpDict, create=create, pBar=pBar) - fos = simpDict.get("falloffs", []) - gs = simpDict.get("groups", []) - for f in fos: - Falloff.loadV2(self, f) - if gs: - for g in gs: - Group.loadV2(self, g) - else: - Group("Group_0", self, Slider) - Group("Group_1", self, Combo) - Group("Group_2", self, Traversal) - - if pBar is not None: - maxLen = max(len(i["name"]) for i in simpDict["shapes"]) - pBar.setLabelText("_" * maxLen) - pBar.setValue(0) - pBar.setMaximum(len(simpDict["shapes"]) + 1) - self.shapes = [] - for s in simpDict["shapes"]: - if not self._incPBar(pBar, s["name"]): - return - Shape.loadV2(self, s, create) - - self.restShape = self.shapes[0] - self.restShape.isRest = True - - progs = [Progression.loadV2(self, p) for p in simpDict["progressions"]] + try: + fos = simpDict.get("falloffs", []) + gs = simpDict.get("groups", []) + for f in fos: + Falloff.loadV2(self, f) + if gs: + for g in gs: + Group.loadV2(self, g) + else: + Group("Group_0", self, Slider) + Group("Group_1", self, Combo) + Group("Group_2", self, Traversal) - for s in simpDict["sliders"]: - Slider.loadV2(self, progs, s, create) - for c in simpDict["combos"]: - Combo.loadV2(self, progs, c) - for t in simpDict["traversals"]: - Traversal.loadV3(self, progs, t) + if pBar is not None: + maxLen = max(len(i["name"]) for i in simpDict["shapes"]) + pBar.setLabelText("_" * maxLen) + pBar.setValue(0) + pBar.setMaximum(len(simpDict["shapes"]) + 1) + self.shapes = [] + for s in simpDict["shapes"]: + if not self._incPBar(pBar, s["name"]): + return + Shape.loadV2(self, s, create) + + self.restShape = self.shapes[0] + self.restShape.isRest = True + + progs = [Progression.loadV2(self, p) for p in simpDict["progressions"]] + + for s in simpDict["sliders"]: + Slider.loadV2(self, progs, s, create) + for c in simpDict["combos"]: + Combo.loadV2(self, progs, c) + for t in simpDict["traversals"]: + Traversal.loadV3(self, progs, t) - for x in itertools.chain(self.sliders, self.combos, self.traversals): - x.prog.name = x.name - self.DCC.postLoad(self, preRet) + for x in itertools.chain(self.sliders, self.combos, self.traversals): + x.prog.name = x.name + finally: + self.DCC.postLoad(self, preRet) def loadV2(self, simpDict, create=True, pBar=None): """Load the version 2 simplex definition @@ -988,44 +990,46 @@ def loadV2(self, simpDict, create=True, pBar=None): """ preRet = self.DCC.preLoad(self, simpDict, create=create, pBar=pBar) - fos = simpDict.get("falloffs", []) - gs = simpDict.get("groups", []) - for f in fos: - Falloff.loadV2(self, f) - if gs: - for g in gs: - Group.loadV2(self, g) - else: - Group("Group_0", self, Slider) - Group("Group_1", self, Combo) - Group("Group_2", self, Traversal) - - if pBar is not None: - maxLen = max(len(i["name"]) for i in simpDict["shapes"]) - pBar.setLabelText("_" * maxLen) - pBar.setValue(0) - pBar.setMaximum(len(simpDict["shapes"]) + 1) - self.shapes = [] - for s in simpDict["shapes"]: - if not self._incPBar(pBar, s["name"]): - return - Shape.loadV2(self, s, create) - - self.restShape = self.shapes[0] - self.restShape.isRest = True - - progs = [Progression.loadV2(self, p) for p in simpDict["progressions"]] + try: + fos = simpDict.get("falloffs", []) + gs = simpDict.get("groups", []) + for f in fos: + Falloff.loadV2(self, f) + if gs: + for g in gs: + Group.loadV2(self, g) + else: + Group("Group_0", self, Slider) + Group("Group_1", self, Combo) + Group("Group_2", self, Traversal) - for s in simpDict["sliders"]: - Slider.loadV2(self, progs, s, create) - for c in simpDict["combos"]: - Combo.loadV2(self, progs, c) - for t in simpDict["traversals"]: - Traversal.loadV2(self, progs, t) + if pBar is not None: + maxLen = max(len(i["name"]) for i in simpDict["shapes"]) + pBar.setLabelText("_" * maxLen) + pBar.setValue(0) + pBar.setMaximum(len(simpDict["shapes"]) + 1) + self.shapes = [] + for s in simpDict["shapes"]: + if not self._incPBar(pBar, s["name"]): + return + Shape.loadV2(self, s, create) + + self.restShape = self.shapes[0] + self.restShape.isRest = True + + progs = [Progression.loadV2(self, p) for p in simpDict["progressions"]] + + for s in simpDict["sliders"]: + Slider.loadV2(self, progs, s, create) + for c in simpDict["combos"]: + Combo.loadV2(self, progs, c) + for t in simpDict["traversals"]: + Traversal.loadV2(self, progs, t) - for x in itertools.chain(self.sliders, self.combos, self.traversals): - x.prog.name = x.name - self.DCC.postLoad(self, preRet) + for x in itertools.chain(self.sliders, self.combos, self.traversals): + x.prog.name = x.name + finally: + self.DCC.postLoad(self, preRet) def loadV1(self, simpDict, create=True, pBar=None): """Load the version 1 simplex definition @@ -1045,111 +1049,113 @@ def loadV1(self, simpDict, create=True, pBar=None): """ preRet = self.DCC.preLoad(self, simpDict, create=create, pBar=pBar) - self.falloffs = [Falloff(f[0], self, *f[1:]) for f in simpDict["falloffs"]] - groupNames = simpDict["groups"] - - if pBar is not None: - maxLen = max(list(map(len, simpDict["shapes"]))) - pBar.setLabelText("_" * maxLen) - pBar.setValue(0) - pBar.setMaximum(len(simpDict["shapes"]) + 1) - - shapes = [] - for s in simpDict["shapes"]: - if not self._incPBar(pBar, s): - return - shapes.append(Shape(s, self)) - - self.restShape = shapes[0] - self.restShape.isRest = True - - progs = [] - for p in simpDict["progressions"]: - progShapes = [shapes[i] for i in p[1]] - progFalloffs = [self.falloffs[i] for i in p[4]] - progPairs = [ProgPair(self, s, pv) for s, pv in zip(progShapes, p[2])] - progs.append(Progression(p[0], self, progPairs, p[3], progFalloffs)) - - self.sliders = [] - self.sliderGroups = [] - createdSlidergroups = {} - for s in simpDict["sliders"]: - sliderProg = progs[s[1]] - - gn = groupNames[s[2]] - if gn in createdSlidergroups: - sliderGroup = createdSlidergroups[gn] - else: - sliderGroup = Group(gn, self, Slider) - createdSlidergroups[gn] = sliderGroup - - Slider(s[0], self, sliderProg, sliderGroup) - - self.combos = [] - self.comboGroups = [] - createdComboGroups = {} - for c in simpDict["combos"]: - prog = progs[c[1]] - sliderIdxs, sliderVals = list(zip(*c[2])) - sliders = [self.sliders[i] for i in sliderIdxs] - pairs = list(map(ComboPair, sliders, sliderVals)) - if len(c) >= 4: - gn = groupNames[c[3]] - else: - gn = "DEPTH_0" - - if gn in createdComboGroups: - comboGroup = createdComboGroups[gn] - else: - comboGroup = Group(gn, self, Combo) - createdComboGroups[gn] = comboGroup - - cmb = Combo(c[0], self, pairs, prog, comboGroup, None) - cmb.simplex = self + try: + self.falloffs = [Falloff(f[0], self, *f[1:]) for f in simpDict["falloffs"]] + groupNames = simpDict["groups"] - self.traversals = [] - self.traversalGroups = [] - createdTraversalGroups = {} - if "traversals" in simpDict: - for t in simpDict["traversals"]: - name = t["name"] - prog = progs[t["prog"]] - - pcIdx = t["progressControl"] - pcSearch = ( - self.sliders - if t["progressType"].lower() == "slider" - else self.combos - ) - pc = pcSearch[pcIdx] - pFlip = t["progressFlip"] - pp = TravPair(pc, -1 if pFlip else 1, "progress") - - mcIdx = t["multiplierControl"] - mcSearch = ( - self.sliders - if t["multiplierType"].lower() == "slider" - else self.combos - ) - mc = mcSearch[mcIdx] - mFlip = t["multiplierFlip"] - mm = TravPair(mc, -1 if mFlip else 1, "multiplier") + if pBar is not None: + maxLen = max(list(map(len, simpDict["shapes"]))) + pBar.setLabelText("_" * maxLen) + pBar.setValue(0) + pBar.setMaximum(len(simpDict["shapes"]) + 1) + + shapes = [] + for s in simpDict["shapes"]: + if not self._incPBar(pBar, s): + return + shapes.append(Shape(s, self)) + + self.restShape = shapes[0] + self.restShape.isRest = True + + progs = [] + for p in simpDict["progressions"]: + progShapes = [shapes[i] for i in p[1]] + progFalloffs = [self.falloffs[i] for i in p[4]] + progPairs = [ProgPair(self, s, pv) for s, pv in zip(progShapes, p[2])] + progs.append(Progression(p[0], self, progPairs, p[3], progFalloffs)) + + self.sliders = [] + self.sliderGroups = [] + createdSlidergroups = {} + for s in simpDict["sliders"]: + sliderProg = progs[s[1]] + + gn = groupNames[s[2]] + if gn in createdSlidergroups: + sliderGroup = createdSlidergroups[gn] + else: + sliderGroup = Group(gn, self, Slider) + createdSlidergroups[gn] = sliderGroup + + Slider(s[0], self, sliderProg, sliderGroup) + + self.combos = [] + self.comboGroups = [] + createdComboGroups = {} + for c in simpDict["combos"]: + prog = progs[c[1]] + sliderIdxs, sliderVals = list(zip(*c[2])) + sliders = [self.sliders[i] for i in sliderIdxs] + pairs = list(map(ComboPair, sliders, sliderVals)) + if len(c) >= 4: + gn = groupNames[c[3]] + else: + gn = "DEPTH_0" - gn = groupNames[t.get("group", 2)] - if gn in createdTraversalGroups: - travGroup = createdTraversalGroups[gn] + if gn in createdComboGroups: + comboGroup = createdComboGroups[gn] else: - travGroup = Group(gn, self, Traversal) - createdTraversalGroups[gn] = travGroup + comboGroup = Group(gn, self, Combo) + createdComboGroups[gn] = comboGroup + + cmb = Combo(c[0], self, pairs, prog, comboGroup, None) + cmb.simplex = self + + self.traversals = [] + self.traversalGroups = [] + createdTraversalGroups = {} + if "traversals" in simpDict: + for t in simpDict["traversals"]: + name = t["name"] + prog = progs[t["prog"]] + + pcIdx = t["progressControl"] + pcSearch = ( + self.sliders + if t["progressType"].lower() == "slider" + else self.combos + ) + pc = pcSearch[pcIdx] + pFlip = t["progressFlip"] + pp = TravPair(pc, -1 if pFlip else 1, "progress") + + mcIdx = t["multiplierControl"] + mcSearch = ( + self.sliders + if t["multiplierType"].lower() == "slider" + else self.combos + ) + mc = mcSearch[mcIdx] + mFlip = t["multiplierFlip"] + mm = TravPair(mc, -1 if mFlip else 1, "multiplier") - color = QColor(*t.get("color", (0, 0, 0))) + gn = groupNames[t.get("group", 2)] + if gn in createdTraversalGroups: + travGroup = createdTraversalGroups[gn] + else: + travGroup = Group(gn, self, Traversal) + createdTraversalGroups[gn] = travGroup - trav = Traversal(name, self, mm, pp, prog, travGroup, color) - trav.simplex = self + color = QColor(*t.get("color", (0, 0, 0))) - for x in itertools.chain(self.sliders, self.combos, self.traversals): - x.prog.name = x.name - self.DCC.postLoad(self, preRet) + trav = Traversal(name, self, mm, pp, prog, travGroup, color) + trav.simplex = self + + for x in itertools.chain(self.sliders, self.combos, self.traversals): + x.prog.name = x.name + finally: + self.DCC.postLoad(self, preRet) def storeExtras(self, simpDict): """Store any unknown keys when dumping, just in case they're important elsewhere diff --git a/simplexui/menu/mayaPlugins/softSelectToCluster.py b/simplexui/menu/mayaPlugins/softSelectToCluster.py index b39b204..99d143f 100644 --- a/simplexui/menu/mayaPlugins/softSelectToCluster.py +++ b/simplexui/menu/mayaPlugins/softSelectToCluster.py @@ -33,7 +33,8 @@ def registerTool(window, menu): def softSelectToClusterInterface(): sel = cmds.ls(sl=True, objectsOnly=True) if sel: - softSelectToCluster(sel[0], "{0}_Soft".format(sel[0])) + name = sel[0].split('|')[-1] + softSelectToCluster(sel[0], "{0}_Soft".format(name)) def getSoftSelectionValues(myNode, returnSimpleIndices=True): diff --git a/simplexui/simplexDialog.py b/simplexui/simplexDialog.py index bc9164b..fd6d66f 100644 --- a/simplexui/simplexDialog.py +++ b/simplexui/simplexDialog.py @@ -1158,6 +1158,9 @@ def loadFile(self, path): newSystem = Simplex.buildSystemFromJson( path, self._currentObject, sliderMul=self._sliderMul, pBar=pBar ) + else: + QMessageBox.warning(self, "Bad Filepath", "Path type not recognized") + return with signalsBlocked(self.uiCurrentSystemCBOX): self.loadObject(newSystem.DCC.mesh) @@ -1172,6 +1175,7 @@ def loadFile(self, path): self.setSystem(newSystem) pBar.close() + self.simplex.DCC.checkForErrors(self) def _fileDialog(self, title, initPath, filters, save=True): """Convenience function for displaying File Dialogs"""