def from_attributes(attrs_inn, attrs_out, dagnodes=None, namespace=COMPOUND_DEFAULT_NAMESPACE): """ Create a compound from a set of provided input and output attributes. The network node will be automatically determined. :param attrs_inn: :type attrs_inn: list(str) :param List[pymel.Attribute] attrs_out: :type attrs_out: list(str) :param List[pymel.PyNode] dagnodes: :param str namespace: :return: A compound :rtype: Compound """ # TODO: Remove pymel usage # Conform dagnodes to set dagnodes = set(pymel.PyNode(dagnode) for dagnode in dagnodes) if dagnodes else set() # Conform inputs and outputs to pymel attrs_inn = [pymel.Attribute(attr) for attr in attrs_inn] attrs_out = [pymel.Attribute(attr) for attr in attrs_out] additional_dagnodes = _get_nodes_from_attributes(attrs_inn, attrs_out) dagnodes.update(additional_dagnodes) inst = create_from_nodes(dagnodes, namespace=namespace, expose=False) _expose_attributes(inst, attrs_inn, attrs_out) return inst
def importDeformers(fileName): deformerDict = json.loads(open(fileName).read()) fileDir = '\\'.join(fileName.split('\\')[:-1]) for key in deformerDict.keys(): geo = deformerDict[key]['geo'] pm.select(geo) dfm = pm.nonLinear(name=key, type=deformerDict[key]['nlType']) pm.xform(dfm[1], ws=1, m=deformerDict[key]['mtx']) parent = deformerDict[key]['parent'] if parent: dfm[1].setParent(parent) for param in deformerDict[key]['params'].keys(): attr = pm.Attribute(('%s.%s' % (dfm[0].name(), param))) if not attr.isArray(): attr.set(deformerDict[key]['params'][param]) for conn in deformerDict[key]['conns'].keys(): pm.Attribute(deformerDict[key]['conns'][conn]).connect( '%s.%s' % (dfm[0].name(), conn)) for g in geo: weightsFile = '%s_%s_weights.xml' % (g, key) pm.select(g) try: pm.deformerWeights(weightsFile, im=1, deformer=dfm[0], path=fileDir) except: print 'unable to load weights:\n\tdeformer: %s\n\tshape: %s\n\tweightsFile: %s' % ( dfm[0], g, weightsFile)
def matchAttr(): # NOTE 获取当前使用的工具 ctx = pm.currentCtx() sel_list = pm.ls(sl=1) if ctx == "moveSuperContext": attr_list = ['tx', 'ty', 'tz'] elif ctx == "RotateSuperContext": attr_list = ['rx', 'ry', 'rz'] elif ctx == "scaleSuperContext": attr_list = ['sx', 'sy', 'sz'] else: return if len(sel_list) != 2: pm.headsUpMessage(u"请选择两个物体") pm.warning(u"请选择两个物体") driver, driven = sel_list for attr in attr_list: if not hasattr(driver, attr) or not hasattr(driven, attr): continue driver_attr = pm.Attribute("%s.%s" % (driver, attr)) driven_attr = pm.Attribute("%s.%s" % (driven, attr)) if driven_attr.isLocked() and not driven_attr.isKeyable(): continue driven_attr.set(driver_attr.get())
def set_radiusAttr(self, **kwargs): self.clearMutiAttr("radiusScale") for key, value in kwargs.items(): if key != "radiusScale": pc.Attribute("%s.%s" % (self.nparticle.name(), key)).set(value) else: for i in range(len(value)): pc.Attribute("%s.%s[%d].radiusScale_Position" % (self.nparticle.name(), key, i)).set( value[i][0]) pc.Attribute("%s.%s[%d].radiusScale_FloatValue" % (self.nparticle.name(), key, i)).set( value[i][1]) pc.Attribute("%s.%s[%d].radiusScale_Interp" % (self.nparticle.name(), key, i)).set( value[i][2])
def _get_attributes_map_from_nodes(nodes): """ Determine the attribute to expose from a set of node. :param nodes: :return: """ # For now we don't want to deal with name mismatch so we'll again use pymel. nodes_pm = {pymel.PyNode(node) for node in nodes} # TODO: Ignore attributes that point back to the network. # Create an attribute map of the attributes we need to expose. map_inputs = [] map_outputs = [] input_connections = (cmds.listConnections( nodes, source=True, destination=False, connections=True, plugs=True) or []) output_connections = (cmds.listConnections( nodes, source=False, destination=True, connections=True, plugs=True) or []) for dst, src in pairwise(input_connections): if pymel.Attribute(src).node() in nodes_pm: continue map_inputs.append(dst) for src, dst in pairwise(output_connections): if pymel.Attribute(dst).node() in nodes_pm: continue map_outputs.append(src) return map_inputs, map_outputs
def negativeAttrRig(driverAttr=None, drivenAttr=None): driverAttr = pm.Attribute(driverAttr) drivenAttr = pm.Attribute(drivenAttr) minus = pm.createNode('plusMinusAverage') minus.operation.set(2) # minus minus.input1D[0].set(1) driverAttr >> minus.input1D[1] minus.output1D >> drivenAttr
def checkMainCtrl(self): ''' 总控制器属性,颜色,关联显隐 ''' overText = [] ctrlCharacter = 'Character' ctrlName = "Main" if not pm.objExists(ctrlCharacter): overText.append('没有总控制器 {0:s}'.format(ctrlCharacter)) if not pm.objExists(ctrlName): overText.append('没有总控制器 {0:s}'.format(ctrlName)) overText.append('手动自己改') self.Box_9_v = False return overText ctrl = pm.PyNode(ctrlName) if not ctrl.hasAttr('showMod'): overText.append('{:s} : 没有新增showMod属性'.format(ctrlName)) if ctrl.getShape().overrideColor.get() != 13 : if ctrl.overrideColor.get() != 13: overText.append('{:s} : 没有改成红色'.format(ctrlName)) if ctrl.hasAttr('showMod'): if self.getGeoGroup(): if self.getGeoGroup()[0] not in ctrl.showMod.outputs(): overText.append('{:s} : 没有关联geo组'.format(ctrlName)) for g in ['shave_G' , 'yeti_G' , 'hair_G']: if pm.objExists(g): if len(pm.PyNode(g).getAllParents()) == 1: if ctrl not in pm.PyNode(g).inputs(): overText.append('{:s} : 没有关联毛发组'.format(g)) if ctrl.hasAttr(g.split('_G')[0]) or ctrl.hasAttr('hairYeti'): if ctrl.hasAttr(g.split('_G')[0]): if pm.PyNode(g.replace('_G' , '_show_G')) not in pm.Attribute(ctrl+'.'+g.split('_G')[0]).outputs(): if not pm.objExists('arnold_loc*'): overText.append('{:s} : 没有关联毛发'.format(g.replace('_G' , '_show_G'))) if pm.Attribute(ctrl+'.'+g.split('_G')[0]).get() != 0: overText.append('{:s} : 属性没有是默认值'.format(g.split('_G')[0])) if ctrl.hasAttr('hairYeti'): if pm.PyNode(g.replace('_G' , '_show_G')) not in pm.Attribute(ctrl+'.hairYeti').outputs(): if not pm.objExists('arnold_loc*'): overText.append('{:s} : 没有关联毛发'.format(g.replace('_G' , '_show_G'))) if pm.Attribute(ctrl+'.hairYeti').get() != 0: if '{:s} : 属性没有是默认值'.format(ctrl+'.hairYeti') not in overText: overText.append('{:s} : 属性没有是默认值'.format(ctrl+'.hairYeti')) else: overText.append('{:s} :属性没有'.format( g.split('_G')[0])) self.Box_9_v = True return overText
def _lidsRotationSetup(topSrt, btmSrt, zone='inner'): ''' for each in topSrt and btmSrt: creates an animBlendNodeAdditiveDA to add lid_ud and lidPart_ud values creates another animBlendNodeAdditiveDA to add in the default/zero rotation a third animBlendNodeAdditiveDA mixes the results according to blink height one final animBlendNodeAdditiveDA for each srt blends between their total and the blended blin value ''' topZoneAttr = pm.Attribute('%s.top_%s_ud' % (self.eyeFK_ctl.name(), zone)) btmZoneAttr = pm.Attribute('%s.btm_%s_ud' % (self.eyeFK_ctl.name(), zone)) defaultTopRot = topSrt.rx.get() defaultBtmRot = btmSrt.rx.get() topWeight, btmWeight = 1.0, 1.0 if topMax > 0: topWeight = defaultTopRot / topMax if btmMax > 0: btmWeight = defaultBtmRot / btmMax top_ud_sum = mathUtils.addAngles(topZoneAttr, self.eyeFK_ctl.top_ud, name=self.getName('top_%s_ud_sum_utl' % zone)) top_ud_sum.weightB.set(topWeight) top_auto_sum = mathUtils.addAngles(eyeRotDm.outputRotateX, topSrt.rx.get(), name=self.getName('top_%s_auto_sum_utl' % zone)) self.eyeFK_ctl.auto_lids.connect(top_auto_sum.weightA) top_sum = mathUtils.addAngles(top_ud_sum.output, top_auto_sum.output, name=self.getName('top_%s_sum_utl' % zone)) btm_ud_sum = mathUtils.addAngles(btmZoneAttr, self.eyeFK_ctl.btm_ud, name=self.getName('btm_%s_ud_sum_utl' % zone)) btm_ud_sum.weightB.set(btmWeight) btm_auto_sum = mathUtils.addAngles(eyeRotDm.outputRotateX, btmSrt.rx.get(), name=self.getName('btm_%s_auto_sum_utl' % zone)) self.eyeFK_ctl.auto_lids.connect(btm_auto_sum.weightA) btm_sum = mathUtils.addAngles(btm_ud_sum.output, btm_auto_sum.output, name=self.getName('btm_%s_sum_utl' % zone)) blink = mathUtils.blendAngles(top_sum.output, btm_sum.output, blink_height_inv.outputX, self.eyeFK_ctl.blink_height) top_blink = mathUtils.blendAngles(top_sum.output, blink.output, blink_inv.outputX, self.eyeFK_ctl.blink, name=self.getName('top_blink_utl')) btm_blink = mathUtils.blendAngles(btm_sum.output, blink.output, blink_inv.outputX, self.eyeFK_ctl.blink, name=self.getName('btm_blink_utl')) top_blink.output.connect(topSrt.rx) btm_blink.output.connect(btmSrt.rx)
def checkSpecularBrdf(self, nodeName): specAtt = pm.Attribute(nodeName + ".specular_brdf") specAtt2 = pm.Attribute(nodeName + ".specular_brdf2") brdfValue = specAtt.get() brdfValue2 = specAtt2.get() dim = (brdfValue != 1) self.dimControl(nodeName, "specular_anisotropy", dim) self.dimControl(nodeName, "specular_rotation", dim) dim = (brdfValue2 != 1) self.dimControl(nodeName, "specular_anisotropy2", dim) self.dimControl(nodeName, "specular_rotation2", dim)
def connectRigs(): inputs = getInputs() for input in inputs: for element in input.rigInputs.elements(): attr = pmc.Attribute('%s.%s' % (input.name(), element)) splitString = attr.get().split('>>') sourceAttr = pmc.Attribute(splitString[0]) destAttr = pmc.Attribute('%s.%s' % (input.name(), splitString[1])) sourceAttr.connect(destAttr, f=1) parents = getParents() for p in parents: p.setParent(pmc.PyNode(p.rigParent.get())) pmc.sets('all_ctls_SEL', add='face_ctls_SEL')
def modLowLink(self, attrName, groupName='hairMod_show_G'): attr = pm.Attribute(self.main + '.' + attrName) reverses = [n for n in attr.outputs() if n.nodeType() == 'reverse'] if not reverses: reverse = pm.createNode('reverse', n='hair_reverse') attr.connect(reverse.inputX, f=True) else: reverse = reverses[0] if pm.Attribute(groupName + '.v') not in reverse.outputX.outputs(): reverse.outputX.connect(groupName + '.v', f=True)
def checkSpecularFresnel(self, nodeName): dimSpec1 = not self.thisNode.enable_spec1.get() dimSpec2 = not self.thisNode.enable_spec2.get() specAtt = pm.Attribute(nodeName + ".specular_Fresnel") specAtt2 = pm.Attribute(nodeName + ".specular_Fresnel2") iorAtt2 = pm.Attribute(nodeName + ".Fresnel_use_IOR") specFres = specAtt.get() specFres2 = specAtt2.get() fresIorValue = iorAtt2.get() dim = (specFres is False) or (fresIorValue is True) or dimSpec1 pm.editorTemplate(dimControl=(nodeName, "Ksn", dim)) dim = (specFres2 is False) or (fresIorValue is True) or dimSpec2 pm.editorTemplate(dimControl=(nodeName, "Ksn2", dim))
def saveDeformers(): saveFilePath = pm.fileDialog2(fileFilter='*.json')[0] if saveFilePath: saveFileDir = '/'.join(saveFilePath.split('/')[:-1]) + '/' dfms = [dfm for dfm in pm.ls() if pm.nodeType(dfm) in deformerTypes] deformerDict = {} for dfm in dfms: key = dfm.name() handle = getHandle(dfm) deformerDict[key] = {} deformerDict[key]['nlType'] = getNonlinearType(dfm) deformerDict[key]['mtx'] = pm.xform(handle, q=1, ws=1, m=1) parent = None if handle.getParent(): parent = handle.getParent().name() deformerDict[key]['parent'] = parent deformerDict[key]['params'] = {} deformerDict[key]['geo'] = [ geo for geo in pm.nonLinear(dfm, q=1, geometry=1) ] for geo in deformerDict[key]['geo']: fileName = '%s_%s_weights.xml' % (geo, key) toSkip = [node.name() for node in dfms if not node == dfm] pm.deformerWeights(fileName, export=1, sh=geo, skip=toSkip, path=saveFileDir) attrs = [ a for a in pm.listAttr(dfm) if pm.Attribute('%s.%s' % (key, a)).isKeyable() and not 'weight' in a ] for attr in attrs: deformerDict[key]['params'][attr] = pm.getAttr( '%s.%s' % (dfm.name(), attr)) deformerDict[key]['conns'] = {} for attr in attrs: if not pm.Attribute('%s.%s' % (key, attr)).isArray(): conn = pm.listConnections('%s.%s' % (key, attr), plugs=1, d=0) if conn: deformerDict[key]['conns'][attr] = conn[0].name() with open(saveFilePath, 'w') as outfile: json.dump(deformerDict, outfile)
def layerCreate(self, attribute): print "layerCreate attr", attribute pmAttr = pm.Attribute(attribute) self.thisNode = pmAttr.node() parent = pm.setParent(query=True) with pm.columnLayout(adjustableColumn=True, parent=parent): pm.button(label="Create New Layer") with pm.columnLayout(adjustableColumn=True): for i in range(pmAttr.numElements()): with pm.frameLayout(collapsable=False, label="Layer {0}".format(i)): with pm.columnLayout(adjustableColumn=True): nameCtrl = pm.textFieldGrp(label="Name") pm.connectControl(nameCtrl, pmAttr[i].layerName, index=2) weightCtrl = pm.floatFieldGrp(label="Weight", value1=0.0) pm.connectControl(weightCtrl, pmAttr[i].layerWeight, index=2) texCtrl = pm.attrColorSliderGrp( at=pmAttr[i].layerTexture, label="Texture") shdCtrl = pm.attrColorSliderGrp( at=pmAttr[i].layerShader, label="Shader") with pm.columnLayout(adjustableColumn=True): with pm.rowLayout(nc=3): pm.button(label="Up") pm.button(label="Delete") pm.button(label="Down")
def outputsReplace(self, attr): node, plug = attr.split('.', 1) attr = pm.Attribute(attr) #pm.setParent(self.frame) pm.mel.AEreplaceNonNumericMulti(self.frame, node, plug, "", "AEreplaceCompound", attr.getArrayIndices())
def addFloatAttribute(object, name, defaultValue=0, keyable=False, minValue=None, maxValue=None): attributeList = pm.ls(object + '.' + name) if len(attributeList) == 0: if minValue and maxValue: pm.addAttr(object, longName=name, attributeType='float', k=keyable, dv=defaultValue, min=minValue, max=maxValue) else: pm.addAttr(object, longName=name, attributeType='float', k=keyable, dv=defaultValue) else: pm.setAttr(object + '.' + name, defaultValue) attribute = pm.Attribute(object + '.' + name) return attribute
def addVectorAttribute(object, name, defaultValue=[0, 0, 0]): attributeList = pm.ls(object + '.' + name) if len(attributeList) == 0: pm.addAttr(object, longName=name, attributeType='double3') pm.addAttr(object, longName=name + 'X', attributeType='double', parent=name, dv=defaultValue[0]) pm.addAttr(object, longName=name + 'Y', attributeType='double', parent=name, dv=defaultValue[1]) pm.addAttr(object, longName=name + 'Z', attributeType='double', parent=name, dv=defaultValue[2]) else: pm.setAttr(object + '.' + name + 'X', defaultValue[0]) pm.setAttr(object + '.' + name + 'Y', defaultValue[1]) pm.setAttr(object + '.' + name + 'Z', defaultValue[2]) attribute = pm.Attribute(object + '.' + name) return attribute
def test_SubClassedRegisteredAttrs(self): self.test_CreateSubClass() for attr in self.RegisteredAttrs: assert getattr(self.MetaNode, attr) == self.RegisteredAttrs[attr] if attr != "MyHidden": assert pCore.Attribute("%s.%s" % \ (self.MetaNode.MetaNode, attr)).get() == self.RegisteredAttrs[attr]
def matrix44FromMtxAttr(initMtx): initMtx = initMtx.get() outList = [] def unpackList(outList, *args): for arg in args: if hasattr(arg, '__iter__'): unpackList(outList, *arg) else: outList.append(arg) unpackList(outList, *initMtx) mtxNode = pm.createNode('fourByFourMatrix') inputAttrs = [ 'in00', 'in01', 'in02', 'in03', 'in10', 'in11', 'in12', 'in13', 'in20', 'in21', 'in22', 'in23', 'in30', 'in31', 'in32', 'in33', ] for source, dest in zip(outList, inputAttrs): print source, dest pm.Attribute('%s.%s' % (mtxNode.name(), dest)).set(source) return mtxNode
def expose_output_attr(self, dagpath): """ Expose an attribute as an output attribute of the compound. :param str dagpath: The attribute to expose :return: The dagpath of the exposed attribute :raises: ValueError: If the attribute is the source of an existing connection. """ dagpath = ( dagpath.__melobject__() if hasattr(dagpath, "__melobject__") else dagpath ) if cmds.connectionInfo(dagpath, isSource=True): raise ValueError("Cannot expose a source attribute: %r" % dagpath) # TODO: Manage name collision src_node, attr_name = dagpath.split(".") dst_dagpath = _utils_attr.transfer_attribute(src_node, self.output, attr_name) # Our reference attribute might not be "writable" (a possible connection source). # TODO: Don't use pymel? pymel.Attribute(dst_dagpath).__apimattr__().setWritable(True) cmds.connectAttr(dagpath, dst_dagpath) return dst_dagpath
def replace(self, plug): plug = pm.Attribute(plug) pm.connectControl(self._coordsys.name(), plug, index=2) # populate the popup menu with coordinate systems self._popupMenu.deleteAllItems() with self._popupMenu: objects = [] # include the default, if any if self.attr.default: objects.append(self.attr.default) # add 3delight coordinate systems objects.extend(pm.ls(type="delightCoordinateSystem")) # TODO: add maya cameras for obj in objects: pm.menuItem(label=str(obj), command=lambda arg, plug=plug, coordsys=obj: plug.set(coordsys)) if objects: pm.menuItem(divider=True) pm.menuItem(label="Create New Coordinate System", boldFont=True, command=lambda arg, plug=plug: plug.set(self._createCoordsys()))
def getNodeWidget(self, item=None): node = self.listWidget.selectedItems()[0].text() attr = self.listWidget_2.selectedItems()[0].text() nodeAttribute = pm.Attribute(node + '.' + attr) self.animCurveList = [ ua for ua in nodeAttribute.outputs(scn=True) if ua.nodeType() in ['animCurveUA', 'animCurveUU', 'animCurveUL'] ] nodeList = [] self.nodeAttrDict = {} for uu in self.animCurveList: plugs = uu.outputs(p=True) for p in plugs: if p.node().name() not in nodeList: nodeList.append(p.node().name()) if p.node().name() not in self.nodeAttrDict.keys(): self.nodeAttrDict[p.node().name()] = [p.attrName()] else: values = self.nodeAttrDict[p.node().name()] if p.attrName() not in values: self.nodeAttrDict.pop(p.node().name()) values.append(p.attrName()) self.nodeAttrDict[p.node().name()] = values self.LoadingDit(nodeList, self.listWidget_3)
def addUIElement(uiType, attribute, uiLabel, callback, renderGlobalsNodeName): ui = None if uiType == 'bool': ui = pm.checkBoxGrp(label=uiLabel) if callback is not None: pm.checkBoxGrp(ui, edit=True, cc=callback) if uiType == 'int': ui = pm.intFieldGrp(label=uiLabel, numberOfFields=1) if callback is not None: pm.intFieldGrp(ui, edit=True, cc=callback) if uiType == 'float': ui = pm.floatFieldGrp(label=uiLabel, numberOfFields=1) if callback is not None: pm.floatFieldGrp(ui, edit=True, cc=callback) if uiType == 'enum': ui = pm.attrEnumOptionMenuGrp(label=uiLabel, at=attribute, ei=getEnumList(attribute)) # attrEnumOptionGrp has no cc callback, so I create a script job if callback is not None: attribute = pm.Attribute(renderGlobalsNodeName + "." + attribute) pm.scriptJob(attributeChange=[attribute, callback], parent=ui) if uiType == 'color': ui = pm.attrColorSliderGrp(label=uiLabel, at=attribute) if uiType == 'string': ui = pm.textFieldGrp(label=uiLabel) if callback is not None: pm.textFieldGrp(ui, edit=True, cc=callback) if uiType == 'vector': ui = pm.floatFieldGrp(label=uiLabel, nf=3) if callback is not None: pm.floatFieldGrp(ui, edit=True, cc=callback) return ui
def update(self): if self.nodeName is None or not pm.objExists(self.nodeName) \ or self.networkCol is None or not pm.layout(self.networkCol, exists=True): return nodeAttr = pm.Attribute(self.nodeAttr('aiCustomAOVs')) self.updateAOVFrame(nodeAttr)
def currentCamera(render_cam): ''' Create an fgshooter camera at same location as the render camera. ''' # Duplicate the render camera and make sure the duplicate is not renderable. fg_cam = pm.duplicate(render_cam, rr=True, name="fgshooterCamera")[0] fg_cam_shape = fg_cam.getShape() fg_cam_shape.renderable.set(False) # Change the fgshooter camera's wireframe color. pm.color(fg_cam, ud=2) # Find the render camera's transfrom and parent the fgshooter to it. render_cam_transform = render_cam.listRelatives(p=True, type="transform")[0] pm.parent(fg_cam, render_cam_transform) # Pass aperture and aspect information with the fgshooter camera's scale. aperture = render_cam.horizontalFilmAperture.get() aspect = aperture / pm.Attribute( "defaultResolution.deviceAspectRatio").get() fg_cam.scaleX.set(aperture) fg_cam.scaleY.set(aspect) # Connect the render camera's focal length to the fgshooter since it could be animated. multiply_divide = pm.createNode("multiplyDivide") multiply_divide.input2X.set(0.03937) render_cam.focalLength >> multiply_divide.input1X multiply_divide.outputX >> fg_cam.scaleZ return fg_cam_shape
def getCtrLAttrList(self): txt = self.comBoxAdd.currentText() if self.ctrName: emDirt = pm.Attribute(self.ctrName + '.' + txt).getEnums() enDirtKey = emDirt.keys() self.comBox.clear() self.comBox.addItems(enDirtKey) OpenMaya.MGlobal_displayInfo('<<<<<<<< Load Ok >>>>>>>>')
def _cast_string_to_attribute(val): if isinstance(val, pymel.Attribute): return val if isinstance(val, basestring): if not cmds.objExists(val): raise Exception("Can't find attribute {0}".format(val)) return pymel.Attribute(val) if isinstance(val, list): return [_cast_string_to_attribute(subval) for subval in val] raise IOError("Invalid argument {0}, expected type string or Attribute, got {1}".format(subval, type(subval)))
def apply_emitSeed(self): seedsAttr = self.nparticle.seed.elements() for attr in seedsAttr: pc.Attribute(self.nparticle + "." + attr).set(random.uniform( 0, 50))
def clearMutiAttr(self, attr): multies = pc.listAttr(self.nparticle, m=True, st=attr) for i in range(len(multies) - 1, 0, -1): pc.removeMultiInstance( pc.Attribute("%s.%s" % (self.nparticle.name(), multies[i])))
def getPoseVal(): attrs = __getCrvUI_keyAttrs() lib = [] for attr in attrs: attr = pm.Attribute(attr) val = attr.get() lib.append([attr.name(), val]) return lib