Maya – How to reorder Shelves alphabetically? - python

I'm trying to alphabetically order Maya shelves. I know that I can import shelves in order but I want to do this after the shelves are imported as the default Maya shelves are automatically imported into Maya.
I tried using the position argument in the shelfLayout command but I'm not sure if it's the right one or if it is, then I don't know how to use it. If someone could shed some light on this, it would be amazing.

Look at the following code snippet to find out how to reorder Shelf Tabs alphabetically:
import maya.cmds as cmds
import maya.mel as mel
def reorderTabsAlphabetically():
gshelf = mel.eval("$temp = $gShelfTopLevel")
shelves = cmds.tabLayout(gshelf, q=1, childArray=1)
total = len(shelves)
pref = 'abc'
print(total) # 16 tabs
shelf = sorted([s for s in shelves if s.startswith(pref)]) + # line break
sorted([s for s in shelves if not s.startswith(pref)])
for i, object in enumerate(shelf):
i += 1
sIndex = cmds.tabLayout(gshelf, q=1, childArray=1).index(object) + 1
cmds.tabLayout(gshelf, e=1, moveTab=(sIndex, i))
reorderTabsAlphabetically()
As you can see all the tabs are now arranged in the ascending order (alphabetically).

Related

How to traverse dictionary keys in sorted order

I am reading a cfg file, and receive a dictionary for each section. So, for example:
Config-File:
[General]
parameter1="Param1"
parameter2="Param2"
[FileList]
file001="file1.txt"
file002="file2.txt" ......
I have the FileList section stored in a dictionary called section. In this example, I can access "file1.txt" as test = section["file001"], so test == "file1.txt". To access every file of FileList one after the other, I could try the following:
for i in range(1, (number_of_files + 1)):
access_key = str("file_00" + str(i))
print(section[access_key])
This is my current solution, but I don't like it at all. First of all, it looks kind of messy in python, but I will also face problems when more than 9 files are listed in the config.
I could also do it like:
for i in range(1, (number_of_files + 1)):
if (i <= 9):
access_key = str("file_00" + str(i))
elif (i > 9 and i < 100):
access_key = str("file_0" + str(i))
print(section[access_key])
But I don't want to start with that because it becomes even worse. So my question is: What would be a proper and relatively clean way to go through all the file names in order? I definitely need the loop because I need to perform some actions with every file.
Use zero padding to generate the file number (for e.g. see this SO question answer: https://stackoverflow.com/a/339013/3775361). That way you don’t have to write the logic of moving through digit rollover yourself—you can use built-in Python functionality to do it for you. If you’re using Python 3 I’d also recommend you try out f-strings (one of the suggested solutions at the link above). They’re awesome!
If we can assume the file number has three digits, then you can do the followings to achieve zero padding. All of the below returns "015".
i = 15
str(i).zfill(3)
# or
"%03d" % i
# or
"{:0>3}".format(i)
# or
f"{i:0>3}"
Start by looking at the keys you actually have instead of guessing what they might be. You need to filter out the ones that match your pattern, and sort according to the numerical portion.
keys = [key for key in section.keys() if key.startswith('file') and key[4:].isdigit()]
You can add additional conditions, like len(key) > 4, or drop the conditions entirely. You might also consider learning regular expressions to make the checking more elegant.
To sort the names without having to account for padding, you can do something like
keys = sorted(keys, key=lambda s: int(s[4:]))
You can also try a library like natsort, which will handle the custom sort key much more generally.
Now you can iterate over the keys and do whatever you want:
for key in sorted((k for k in section if k.startswith('file') and k[4:].isdigit()), key=lambda s: int(s[4:])):
print(section[key])
Here is what a solution equipt with re and natsort might look like:
import re
from natsort import natsorted
pattern = re.compile(r'file\d+')
for key in natsorted(k for k in section if pattern.fullmatch(k)):
print(section[key])

Make input and run loop to create custom number of objects

I have been trying to learn how to code for Maya for a while. And here I am making a rock generator using reduce face, quad-mesh, smooth in a loop. Now I would like to make an input so user can specify how many rock they want to make. Like if you input 5, it would make 5 rocks.
However I am currently stuck with the naming, like if I created the polyPlatonicSolid with rock#, the loop got confused and said something like rock# is invalid, so I am just looking for a simple solution for that.
import maya.cmds as MC
import random as RN
def rockGen():
#GenerateBase
rockCreation = MC.polyPlatonicSolid(name="rock", r=5)
MC.displaySmoothness( polygonObject= 0)
obj=MC.ls(sl=True)
MC.polySmooth(rockCreation, divisions = 2)
MC.polySoftEdge('rock', a=0, ch=1)
#Evaluate face counts
face_count = MC.polyEvaluate('rock', v=True)
#Procedural rock creation
for i in range(10):
random_face = RN.randint(0, face_count)
print random_face
# Select faces
targetFace = MC.select('rock.f[0:' + str(random_face)+ ']')
# Reduce faces
MC.polyReduce(p=20, kb=True, t=False)
MC.polyQuad('rock', a=20)
#Quad the rock
MC.polySmooth('rock', ch=1, ost=0, khe=0, ps=0.1, kmb=1, bnr=1, mth=0, suv=1,
peh=0, ksb=1, ro=1, sdt=2, ofc=0, kt=1, ovb=1, dv=1, ofb=3, kb=1,
c=1, ocr=0, dpe=1, sl=1)
#Select and rename
MC.select('rock')
MC.rename('inst#')
I tried this:
numberRock=input()
for u in range (numberRock)
rockCreation = MC.polyPlatonicSolid(name="rock#", r=5)
But after that all my rock# command just deliver an invalid object feedback
One other challenge that was given by the teacher is that doing this script without the select command and I don't know the other way to do that yet.
I have change the way you are naming your rock in order to be unique.
Even if maya is based string, try to always use variable instead of 'rock'.
Also instead of cmds.select, you can in most of the cases use cmds.ls to select your object. It is just a command that will return the name string if it exists, then you can use this to feed most of the commands in maya.
example :
my_rocks = cmds.ls('rock*')
cmds.rename(my_rocks[0], 'anythin_you_want')
here is your modified code that handle unique naming for your piece of rocks.
Last advice, take the habit to write with .format() and also put parenthesis with print()
import maya.cmds as MC
import random as RN
def isUnique(name):
if MC.ls(name):
return False
return True
def rockGen(name='rock'):
#GenerateBase
_iter = 1
new_name = '{}_{:03d}'.format(name, _iter)
while not isUnique(new_name):
_iter += 1
new_name = '{}_{:03d}'.format(name, _iter)
rockCreation = MC.polyPlatonicSolid(name=new_name, r=5)
MC.displaySmoothness( polygonObject= 0)
obj=MC.ls(sl=True)
MC.polySmooth(rockCreation, divisions = 2)
MC.polySoftEdge(new_name, a=0, ch=1)
#Evaluate face counts
face_count = MC.polyEvaluate(new_name, v=True)
#Procedural rock creation
for i in range(10):
random_face = RN.randint(0, face_count)
# print(random_face)
# Select faces
targetFace = MC.select('{}.f[0:{}]'.format(new_name, random_face))
# Reduce faces
MC.polyReduce(new_name, p=20, kb=True, t=False)
MC.polyQuad(new_name, a=20)
#Quad the rock
MC.polySmooth(new_name, ch=1, ost=0, khe=0, ps=0.1, kmb=1, bnr=1, mth=0, suv=1,
peh=0, ksb=1, ro=1, sdt=2, ofc=0, kt=1, ovb=1, dv=1, ofb=3, kb=1,
c=1, ocr=0, dpe=1, sl=1)
MC.delete(new_name, ch=True)
return new_name
so with this you can loop :
my_rocks = []
my_number = 5
for x in range(my_number):
rock = rockGen()
my_rocks.append(rock)
print(my_rocks)

How to create a lettered list using docx?

I use python for teaching some of my science courses, where I use it to generate unique assignments and tests for students. I've run into an issue that I can't sort out on my own.
I'm trying to make a series of nested lists. For example, I would like to have a numbered question, and then sub parts to the question underneath. For example:
Use the Henderson-Hasselbalch equation to determine pH of the following solutions:
A. 250 mM Ammonium Chloride
B. 100 mM Acetic Acid
I've used style "List Number" to create the numbered list, but I can't figure out how to create a custom list that starts with the letters.
Here is what I've got so far:
import sys
import os
if os.uname()[1] == 'iMac':
sys.path.append("/Users/mgreene3/Library/Python/2.7/lib/python/site-packages")
else:
sys.path.append("/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python")
import numpy as np
import math
import random
import textwrap
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.style import WD_STYLE_TYPE
from docx.text.tabstops import TabStop as ts
from docx.text.parfmt import ParagraphFormat
assignment = Document()
ordered = "a"
style = assignment.styles["Normal"]
font = style.font
font.name = "Calibri"
font.size = Pt(12)
style.paragraph_format.space_after = Pt(0)
LetteredList = style.paragraph_format._NumberingStyle(ordered)
sub_style = assignment.styles["ListBullet"]
sub_font = sub_style.font
sub_font.name = "Calibri"
###sub_style.paragraph_format.style("List")
sub_font.size = Pt(12)
sub_style.paragraph_format.left_indent = Inches(1)
sub_style.paragraph_format.space_before = Pt(0)
sub_style.paragraph_format.space_after = Pt(40)
doc_heading = assignment.add_paragraph("Name:_______________________")
doc_heading.add_run("\t" * 4)
doc_heading.add_run(" " * 12)
doc_heading.add_run("BIOL444: Biochemistry\t\t\t\t\t\t ")
doc_heading.add_run("\n")
doc_heading.add_run("Take Home 1, v.")
doc_heading.add_run((str(1).zfill(2)))
doc_heading.add_run("\n" * 2)
doc_heading.add_run("Instructions: Complete test (")
show_work = doc_heading.add_run("show work")
show_work.bold = True
show_work.underline = True
show_work
doc_heading.add_run("), submit ")
hard_copy = doc_heading.add_run("hard copy")
hard_copy.bold = True
hard_copy.underline = True
hard_copy
doc_heading.add_run(" by ")
doc_heading.add_run("11:59 pm, Friday, February 10").bold =True
doc_heading.add_run(". Late submissions will ")
doc_heading.add_run("NOT").bold=True
doc_heading.add_run(" be accepted.")
question1 = assignment.add_paragraph("Using the data for K", style = "List Number")
question1.add_run("a").font.subscript = True
question1.add_run(" and pK")
question1.add_run("a").font.subscript = True
question1.add_run(" of the following compounds, calculate the concentrations (M) of all ionic species as well as the pH of the following aqueous solutions: ")
question1.add_run("\n")
question1a = assignment.add_paragraph("100 mM Acetic acid", style = sub_style)
question1b = assignment.add_paragraph("250 mM NaOH", style = sub_style)
assignment.save("TestDocx.docx")
The short answer is that it's probably more trouble than it's worth. Creating numbered lists, especially nested numbered lists in Word is a complex operation, possibly for legacy reasons (we're on version 14 or something of Word). Partly because of this complexity, API support for this doesn't yet exist in python-docx.
If you really wanted to do it, it would entail manipulating numbering definitions that exist in another package part from the document part (I believe it's numbering.xml). This would be using low-level lxml calls.
For myself, I'd be strongly inclined to use RestructuredText for a job like this, rendering to PDF, perhaps using Sphinx. As a side-effect, you could easily get HTML version as well for posting assignments on the web. However, I'm too far away from your actual requirements to say that would really suit; you'll have to check it out and see for yourself :)

Renaming duplicated list "L" to "R" in Maya python

What I want to do is duplicate a controller to other side and rename/replace _L to _R. So I just have to select controller and it will create a group and then another group to mirror it on right side and renaming that other group to _R. Then unparent first group to world. thats all I want to do. but I'm stuck on renaming. I know I have to sort list in reverse order to rename it but whenever I do it Maya says:
More than one object matches name
Duplicated object has different parent name and same children name. Please tell me how should I do it and what I'm missing.
import maya.cmds as cmds
list = cmds.ls(sl=1)
grp = cmds.group(em=1, name=("grp" + list[0]))
# creating constraint to match transform and deleting it
pc = cmds.pointConstraint(list, grp, o=[0,0,0], w=1)
oc = cmds.orientConstraint(list, grp, o=[0,0,0], w=1)
cmds.delete(pc, oc)
# parenting it to controller
cmds.parent(list, grp)
# creating new group to reverse it to another side
Newgrp = cmds.group(em=1)
cmds.parent(grp, Newgrp)
Reversedgrp = cmds.duplicate(Newgrp)
cmds.setAttr(Reversedgrp[0] +'.sx', -1)
selection = cmds.ls(Reversedgrp, long=1)
selection.sort(key=len, reverse=1)
Renaming in Maya is very annoying, because the names are your only handle to the objects themselves.
The usually trick is basically:
Duplicate the items with the rr flag, so you only get the top nodes
Use listRelatives with the ad and full flags to get all the children of the duplicated top node in long form like |Parent|Child|Grandchild. In this form the where the entire hierarchy above the name is listed in order (you can get this form with cmds.ls(l=True) on objects as well)
Sort that list and then reverse it. This will put the longest path names first, so you can start with the leaf nodes and work your way upwards
Now loop through the items and apply your renaming pattern
So something like this, though you probably want to replace the selection here with something you control:
import maya.cmds as cmds
dupes = cmds.duplicate(cmds.ls(sl=True), rr=True) # duplicate, return only roots
dupes += cmds.listRelatives(dupes, ad=True, f=True) # add children as long names
longnames = cmds.ls(dupes, l=True) # make sure we have long name for root
longnames.sort() # usually these sort automatically, but's good to be safe
for item in longnames[::-1]: # this is shorthand for 'walk through the list backwards'
shortname = item.rpartition("|")[-1] # get the last bit of the name
cmds.rename(item, shortname.replace("r","l")) # at last, rename the item
thanks "theodox" it was very usefull. but still little bit confused in sorting, long names, short names and .rpartition... but anyway i have created this script finally.
import maya.cmds as cmds
_list = cmds.ls(sl=1)
grp = cmds.group(em=1, name=("grp_"+ _list[0]))
#creating constraint to match transfor and deleting it.
pc=cmds.pointConstraint( _list, grp, o=[0,0,0],w=1 )
oc=cmds.orientConstraint( _list, grp, o=[0,0,0],w=1 )
cmds.delete(pc,oc)
cmds.parent( _list, grp )
Newgrp=cmds.group(em=1)
cmds.parent(grp,Newgrp)
#duplicating new group and reversing it to negative side
dupes = cmds.duplicate(cmds.ls(Newgrp,s=0), rr=True) # duplicate, return only roots
cmds.setAttr( dupes[0] +'.sx', -1 )
#renaming
dupes += cmds.listRelatives(dupes, ad=True, f=True) # add children as long names
longnames = cmds.ls(dupes, l=True,s=0) # make sure we have long name for root
longnames.sort() # usually these sort automatically, but's good to be safe
print longnames
for item in longnames[::-1]: # this is shorthand for 'walk through the list backwards'
shortname = item.rpartition("|")[-1] # get the last bit of the name
cmds.rename(item, shortname.replace("_L","_R")) # at last, rename the item
#ungrouping back to world and delting unused nodes
cmds.parent( grp, world=True )
duplicatedGrp=cmds.listRelatives(dupes[0], c=True)
cmds.parent( duplicatedGrp, world=True )
cmds.delete(dupes[0],Newgrp)
anyone can use this code for mirroring controllers just change "l","r" in rename command.
thank you.

Proper way to use setAttr with channel box selection

please bear with me - I'm new to all this. I tried the searches and have only found bits and pieces to what I'm looking for, but not what I need to connect them.
Basically, I'm trying to create a Python script that allows the user to simply "0" out multiple selected attributes on Maya's Channel box.
So far I have:
import maya.cmds as cmds
selObjs = cmds.ls(sl=1)
selAttrs = cmds.channelBox("mainChannelBox", q=1, sma=1)
print selObjs # returns [u'pCube1']
print selAttrs # returns [u'ty']
If I would like to set the attributes:
cmds.setAttr(selObjs + "." + selAttrs, '0')
of course this is wrong, so how do I properly execute the setAttr command in this sceneario? (The intention includes having to set them if I have multiple selected attributes in the channel box).
I found that in MEL, it works like this. So really I just need help figuring out how to create the python counterpart of this:
string $object[] = `ls -sl`;
string $attribute[] = `channelBox -q -sma mainChannelBox`;
for ($item in $object)
for($attr in $attribute)
setAttr ($item + "." + $attr) 0;
Moving after that, I need an if loop where, if the attribute selected is a scale attribute, the value should be 1 - but this is something I'll look into later, but wouldn't mind being advised on.
Thanks!
So here's what I finally came up with:
import maya.cmds as cmds
selObjs = cmds.ls(sl=1)
selAttrs = cmds.channelBox("mainChannelBox", q=1, sma=1)
scales = ['sy','sx','sz','v']
if not selObjs:
print "no object and attribute is selected!"
elif not selAttrs:
print "no attribute is selected!"
else:
for eachObj in selObjs:
for eachAttr in selAttrs:
if any(scaleVizItem in eachAttr for scaleVizItem in scales):
cmds.setAttr (eachObj+"."+eachAttr, 1)
else:
cmds.setAttr (eachObj+"."+eachAttr, 0)
This will reset the basic transformations to their defaults. Including an if for the scale and visibility values.
I managed to come up with this:
import maya.cmds as cmds
selObjs = cmds.ls(sl=1)
selAttrs = cmds.channelBox("mainChannelBox", q=1, sma=1)
for each in selObjs:
for eachAttr in selAttrs:
cmds.setAttr (each+"."+eachAttr, 0)
And It's working to zero out selected attributes perfectly.
Now im at the stage of figuring out how to get the script to recognize if it contains scale attributes - to change that value to 1 instead of 0. (stuck at how to extract values from a list at the moment)

Categories