Creating a simple hierarchy structure in Maya using mel/python - python

So I want to create a very simple structure out of group and locator nodes in Maya which will then be exported for use in my game level.
e.g.
Group_Root
group_parent
- group1
- locator1
- group2
- locator2
- group3
There is only one Group_Root in the file, there are many group_parents ( each uniquely named )
However all group_parent's have the same three sub-group names( "group1", "group2", "group3" ) and all group1 have a locator called locator1
What I have so far is:
group_parent = c.group( em=True, name="group_parent", parent="Group_Root")
modes = ["group1", "group2", "group3"]
for mode in modes:
mode_group = c.group( em=True, n=mode, parent=group_parent )
if mode == "group1":
s = c.spaceLocator(name="locator1")
c.parent( mode_group )
elif mode == "group3":
s = c.spaceLocator(name="locator2")
c.parent( mode_group )
However I get this error at "c.parent(mode_group)"
# Error: Object group1 is invalid
Presumably because there are more than one node called "group1" so it doesn't know which one to parent.
Any idea how I do this with full paths? e.g. "Group_Root|group_parent|group1"

Have you seen VFX Overflow? It's Q&A for visual effects, so I'd expect a number of the watchers to be quite familiar with Maya/MEL and Python. That said, it is fairly new so the user base is still small...

Names can be a bit annoying with MEL. In general, it's good practice to never trust a name to be what you're specifying.
This is a good example of how *not to do things:
group -n myGroup1 circle1 sphere1;
..because that is in no way guaranteed to result in something named "group1". The better way to do it is to run your command and capture the result in a string variable, such as:
string $result = `group -n myGroup circle1 sphere1`;
Then, use $result to refer to the resulting group. That will still work, even if the group ended up being called 'myGroup23'.
I'm not sure how the above looks in Python, as I'm mainly familiar with straight MEL, but the same principles should apply.
Another thing to look at is the namespace functionality (namespace and namespaceInfo), which could be used to define a new namespace for the unique, top-leve group at hand.
Hope that helps

I'm guessing that it being over two years, you've figured this one out by now.. but for posterity, there were two issues - firstly, you were spot on with the need for absolute paths, but there was also a slight bug in the way you were applying the maya.cmds.parent() call. I've just done some light rewriting to illustrate - mainly you could use the fact that when you create things they become selected by default, and maya.cmds.ls() is smart enough to return you what you need.. Ergo:
c.group( em=True, name="group_parent", parent="Group_Root")
group_parent = c.ls(sl=True)[0]
modes = ["group1" , "group2", "group3"]
for mode in modes:
c.group( em=True, n=mode, parent=group_parent )
mode_group = c.ls(sl=True)[0]
if mode == "group1":
c.spaceLocator(name="locator1")
s = c.ls(sl=True)[0]
# maya.cmds.parent() with something selected will actually
# parent the specified object to the selected object.
# You don't want that.
# We might as well use the explicit syntax to be sure
# (parent everything specified to the last item in the list)
c.parent( s, mode_group )
elif mode == "group3":
c.spaceLocator(name="locator2")
s = c.ls(sl=True)[0]
c.parent( s, mode_group )

Related

Pymel: How do I close For loop after deleting null groups?

As I continue to study For Loops: I've run into some annoying errors. The problem is the script does exactly what I want it to. It deletes the null groups under the demo joints: but unlike other loops I've made for renaming which can be closed with a transform flag in the cmds.ls command: cmds.listRelatives doesn't allow a transform flag to close out the loop. You run the script by simply clicking Build Examples then hitting Delete Waste Groups
I've tried every flag according to the Maya documentation: but nothing seems to be closing the loop. I dont know if I need another variable, or a combination of some flags: or if I am using the wrong type of wording: but ideally what I would like this script to do is simply close out the loop so I dont get the error Error: No object matches name: curve
'''
import DS_wasteGroup_cleanerDemo
reload (DS_wasteGroup_cleanerDemo)
DS_wasteGroup_cleanerDemo.gui()
'''
import re
import maya.cmds as cmds
import maya.mel as mel
if cmds.window("renameWin", exists =True):
cmds.deleteUI("renameWin", window = True)
myWindow = cmds.window("renameWin",t='DS_wasteGroup_cleanerDemo',w=200, h=500, toolbox=True)
column = cmds.columnLayout(adj=True)
def gui():
cmds.button( label="Build Examples", c = buildExamples)
cmds.separator( w=200, h=3)
cmds.button( label="Delete Waste Groups", c = deleteWasteGrp)
cmds.separator( w=200, h=9)
cmds.setParent('..')
cmds.showWindow(myWindow)
def buildExamples(*args):
cmds.group(n='exampleGroup1',world=True,empty=True)
cmds.joint(n='demoJoint1')
cmds.group(n='curve1',world=True,empty=True)
cmds.parent('curve1','demoJoint1')
cmds.joint(n='demoJoint2')
cmds.parent('demoJoint2','exampleGroup1')
cmds.group(n='curve2',world=True,empty=True)
cmds.parent('curve2','demoJoint2')
cmds.joint(n='demoJoint3')
cmds.parent('demoJoint3','exampleGroup1')
cmds.group(n='curve3',world=True,empty=True)
cmds.parent('curve3','demoJoint3')
cmds.joint(n='demoJoint4')
cmds.parent('demoJoint4','exampleGroup1')
cmds.group(n='curve4',world=True,empty=True)
cmds.parent('curve4','demoJoint4')
cmds.joint(n='demoJoint5')
cmds.parent('demoJoint5','exampleGroup1')
cmds.group(n='curve5',world=True,empty=True)
cmds.parent('curve5','demoJoint5')
def deleteWasteGrp(*args):
grpList = cmds.listRelatives('demoJoint*',p=True,f=True)
for name in grpList:
print(grpList)
cmds.delete('curve*')
My apologies if I'm posting simple questions. I do write Python scripts to automate the most tedious tasks in rigging: but my knowledge is only intermediate. I want to learn more python so my scripts arent so clunky and brute forced: as well as the fact that I need them to be more adaptable to various types of characters: so any resources that dumb all this down would also be appreciated. Thank you for your help.
The error is correct, because the very first time the for loop executes, all "curve" obects are deleted, then in the next iteration, the same command does not find any curve objects because they are already deleted. If you place the delete command outside the for loop, the error should disappear.
Honestly I would take a whole different approach as you're hard-coding everything which could easily lead to disaster. When I mean hard-code, I mean you're trying to parent, let's say, "demoJoint2" to an object. This is bad because why are you assuming that "demoJoint2" even exists? If you create an object with a specific name that already exists, Maya will auto-rename the new object, and now you're referencing the wrong one right off the bat! Instead when you create your objects, capture their names in a variable then work with that, otherwise you'll be constantly shooting yourself in the foot.
Here's the same script with an approach I would try instead:
import maya.cmds as cmds
def gui():
if cmds.window("renameWin", exists=True):
cmds.deleteUI("renameWin", window=True)
myWindow = cmds.window("renameWin", t="DS_wasteGroup_cleanerDemo", w=200, h=500, toolbox=True)
column = cmds.columnLayout(adj=True)
cmds.button(label="Build Examples", c=buildExamples)
cmds.separator(w=200, h=3)
cmds.button(label="Delete Waste Groups", c=deleteWasteGrp)
cmds.separator(w=200, h=9)
cmds.setParent("..")
cmds.showWindow(myWindow)
def buildExamples(*args):
root = cmds.group(n="exampleGroup1", world=True, empty=True)
for i in range(5): # Loop to the amount of joints you want to create.
jnt = cmds.createNode("joint", name="demoJoint" + str(i + 1)) # Use `i` to help name the object.
jnt = cmds.parent(jnt, root)[0] # Parenting changes its long name, so recapture the joint in a variable.
crv = cmds.group(n="curve" + str(i + 1), world=True, empty=True) # Create empty group.
cmds.parent(crv, jnt) # Parent curve to joint.
def deleteWasteGrp(*args):
jnts = cmds.ls("demoJoint*", long=True, type="joint") # Get all `demoJoints`.
children = cmds.listRelatives(jnts, f=True, children=True, type="transform") or [] # Get all of their children, and only get transform types.
curves = [obj for obj in children if obj.split("|")[-1].startswith("curve")] # Don't assume we got the right objects. Run a final loop to collect any object that starts with `curve`. Need to use split as we're looping through long names but need to check its short name.
if curves: # `cmds.delete` will error if this list is empty, so don't assume.
cmds.delete(curves) # Delete all curves at once.
gui()
Now I can hit the build button as much as I want with no issues, and delete all the curves when pressing the delete button.
A few more notes:
Notice in buildExamples I'm using a loop to create all the objects instead of reusing redundant code that does the same thing. You could even have a spinbox in your gui that defines how many joints it creates now, where as before it wasn't possible because the count was hard-coded.
cmds.listRelatives does have a way to filter objects by transforms by setting parameter type="transform". In fact you'll see many commands have this same parameter (again start checking docs).
cmds.listRelatives('demoJoint*',p=True,f=True) was grabbing the joint's parent, not its children. The docs clearly state this.
Running cmds.delete('curve*') is going to delete ALL objects with names that start with curve, and since you're running this in a loop it's trying to do this multiple times.
maya.cmds is not pymel. There's a whole separate module called pymel.
If you're unsure with any parts of the code try adding in a print statement to see what it's doing.
I feel like you're going about this whole process a bit wrong, and I would love to elaborate if you're interested, but for now here is a fix for your loop situation:
def deleteWasteGrp(*args):
curveList = cmds.ls('curve*',transforms=True)
try:
cmds.delete(curveList)
print('Deleted the following objects: {}'.format(curveList))
except Exception as e:
cmds.warning('Cleanup failed: {}'.format(e))
The cmds.delete method accepts a list parameter, which in your case is the easiest way to get the job done. Keep in mind that when you delete a parent object, you also delete its children, so depending on your circumstances deleting objects can be order-specific.
Throwing any "likely to fail" calls in a try/except clause is generally a good idea, as it lets you handle the error gracefully. Be careful, however, to not suppress it and just move on -- you at the very least need to alert the user adequately.
Lastly, your buildExamples method will most likely fail if you run it more than once. Because you are addressing objects by string literals (hard coded names) instead of keeping track of their actual names (and full path). You will likely see this error eventually:
# Error: ValueError: file <maya console> line ??: More than one object matches name: demoJoint1 #
Edit: Some elaborations as requested
The commands cmds.group and cmds.joint return a string value indicating the actual name of the object created (in create mode). It's usually a good idea of storing this value in case Maya decides to name your object slightly differently than what you are expecting, usually when there is a naming clash. Eg:
print cmds.group(name='test', world=True, empty=True)
# Returns: test
print cmds.group(name='test', world=True, empty=True)
# Returns: test1
Example of how to capture object names as you create them. I've concatenated your five identical(ish) calls to create joints and curves in this loop:
import maya.cmds as cmds
topGroupName = 'exampleGroup'
actualTopGroupName = None
# Create top level group
actualTopGroupName = cmds.group(n=topGroupName, world=True, empty=True)
# Loop through 5 times and do the following:
for i in range(5):
# PS: hash character in name indicates "next available number"
cmds.select(clear=True)
jnt = cmds.joint(n='demoJoint#')
crv = cmds.group(n='curve#',world=True,empty=True)
cmds.parent(crv, jnt)
cmds.parent(jnt, actualTopGroupName)
Example of how to narrow down which objects to search for with cmds.ls:
topGroupName = 'exampleGroup'
print cmds.ls('|{}*|*|curve*'.format(topGroupName))
# Returns: [u'curve1', u'curve2', u'curve3', u'curve4', u'curve5']
# The string .format() expression is just a dynamic way of writing this:
# |exampleGroup*|*|curve*
Vertical pipes (|) indicate levels in a hierarchy, similar to how slashes (/) work in URLs. And asterisks/wildcards (*) indicate "any character, or none".
Hope this helps you along your way a little bit.

how to add location constraints for an object in blender?

I want to make it so that my program will stop running and print object is out of bounds if an object goes say into the negative z part of the plane in blender.
the objects name is Cube.031. I will sudo code what I want to do I just am not sure about sure how to do the syntax for it.
if(Cube.031.zLocation < 0)
print(object is out of bounds)
end
If you know some programming, learning python won't take long.
For the blender specific info, almost everything is accessed through the bpy module, the API reference is online.
You can refer to an object by name in bpy.data.objects[]. There are also other lists available, like bpy.context.selected_objects[] and bpy.context.visible_objects[].
An objects location is an array of three values (x,y,z), you can either access the z location as location.z or location[2].
import bpy
obj = bpy.data.objects['Cube.031']
if obj.location.z < 0:
print('object is out of bounds')
If you wanted to go through all selected objects
for obj in bpy.context.selected_objects:
if obj.location.z < 0:
print('object {} is out of bounds'.format(obj.name))
Note that v2.80 is due for release soon and has some changes to the API, if you are just starting with blender you may want to start with 2.80. You will also find blender.stackexchange a better place to ask for blender specific help.

Getting a selection in 3ds Max into a list in Python

I am writing in Python, sometimes calling certain aspects of maxscript and I have gotten most of the basics to work. However, I still don't understand FPValues. I don't even understand while looking through the examples and the max help site how to get anything meaningful out of them. For example:
import MaxPlus as MP
import pymxs
MPEval = MP.Core.EvalMAXScript
objectList = []
def addBtnCheck():
select = MPEval('''GetCurrentSelection()''')
objectList.append(select)
print(objectList)
MPEval('''
try (destroyDialog unnamedRollout) catch()
rollout unnamedRollout "Centered" width:262 height:350
(
button 'addBtn' "Add Selection to List" pos:[16,24] width:88 height:38
align:#left
on 'addBtn' pressed do
(
python.Execute "addBtnCheck()"
)
)
''')
MP.Core.EvalMAXScript('''createDialog unnamedRollout''')
(I hope I got the indentation right, pretty new at this)
In the above code I successfully spawned my rollout, and used a button press to call a python function and then I try to put the selection of a group of objects in a variable that I can control through python.
The objectList print gives me this:
[<MaxPlus.FPValue; proxy of <Swig Object of type 'Autodesk::Max::FPValue *' at 0x00000000846E5F00> >]
When used on a selection of two objects. While I would like the object names, their positions, etc!
If anybody can point me in the right direction, or explain FPValues and how to use them like I am an actual five year old, I would be eternally grateful!
Where to start, to me the main issue seems to be the way you're approaching it:
why use MaxPlus at all, that's an low-level SDK wrapper as unpythonic (and incomplete) as it gets
why call maxscript from python for things that can be done in python (getCurrentSelection)
why use maxscript to create UI, you're in python, use pySide
if you can do it in maxscript, why would you do it in python in the first place? Aside from faster math ops, most of the scene operations will be orders of magnitude slower in python. And if you want, you can import and use python modules in maxscript, too.
import MaxPlus as MP
import pymxs
mySel = mp.SelectionManager.Nodes
objectList = []
for each in mySel:
x = each.Name
objectList.append(x)
print objectList
The easiest way I know is with the
my_selection = rt.selection
command...
However, I've found it works a little better for me to throw it into a list() function as well so I can get it as a Python list instead of a MAXscript array. This isn't required but some things get weird when using the default return from rt.selection.
my_selection = list(rt.selection)
Once you have the objects in a list you can just access its attributes by looking up what its called for MAXscript.
for obj in my_selection:
print(obj.name)

msiexec scripting in python

Most of this is background, skip the next 3 paragraphs for the question:
I have developed a tool that calls some installers, changes registry items, and moves files around to help me test a product which has a fairly fast update cycle. So far so good, I have a GUI which runs in a separate process to the business logic to prevent it locking due to the GIL, everything works etc, however I have concerns with a section of my code where I make calls to msiexec.
Specifically it's the uninstall part which gives me concerns. Currently the GUID does not change so I am able to uninstall the product using an os.system('msiexec /x "{GUID}" /passive') sort of thing. It's actually a bit more complicated as I'm using subprocess.Popen and polling it until it finished from within an event loop to allow for concurrency with other steps.
My concern is that should the GUID change, obviously this will not work. I don't want to point msiexec directly at the installation source, as this would mean that it wouldn't work if I were to 'lose' the msi file, which I store in a temporary directory.
What I am looking for, is a way of querying by program name to get the GUID, or even a wrapper for msiexec that would do all of this, including the uninstall, for me. I thought of scanning through the registry, but the _winreg module seems very slow, so I'd prefer to avoid this if at all possible. If there's a better way to scan the registry, I'm all ears, as this would speed up other parts of the tool also.
Update0
Performance on this is critical as one of the design goals is to make the process which the tool follows faster than any other method, manual or otherwise, in order to gain wholesale adoption.
Update1
I have tried a slight variation of the registry version below however it consistently returns None. I'm not quite sure how this is happening - it seems like it is failing to open the appropriate key as I have inserted a breakpoint after the with statement which is never reached...
def get_guid_by_name(name):
from _winreg import (OpenKey,
QueryInfoKey,
EnumKey,
QueryValueEx,
HKEY_LOCAL_MACHINE,
)
with OpenKey(HKEY_LOCAL_MACHINE,
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall') as key:
subkeys, _0, _1 = QueryInfoKey(key) # The breakpoint here is never reached
del _0, _1
for i in range(subkeys):
subkey = EnumKey(key, i)
if subkey[0] != '{' or subkey[-1] != '}':
continue
with OpenKey(key, subkey) as _subkey:
if name in QueryValueEx(_subkey, 'DisplayName')[0]:
return subkey
return None
print get_guid_by_name('Microsoft Visual Studio')
Update2
Strike that - I'm a fool who doesn't check his indentation thoroughly enough - print get_guid_by_name('Microsoft Visual Studio') was within get_guid_by_name...
I'm not sure about the _winreg module being all that slow. I suppose if you were trying to enumerate the entire registry to find all instances of a string that might take a while, but with a decently targeted query it seems reasonably fast.
Here's an example:
from _winreg import *
def get_guid_by_name(name):
# Open the uninstaller key
with OpenKey(HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall') as key:
# We only care about subkeys of the installer key
subkeys, _, _ = QueryInfoKey(key)
for i in range(subkeys):
subkey = EnumKey(key, i)
# Since we're looking for uninstallers for MSI products,
# the key name will always be the GUID. We assume that any
# key starting with '{' and ending with '}' is a GUID, but
# if not the name won't match.
if subkey[0] != '{' or subkey[-1] != '}':
continue
# Query the display name or other property of the key to
# see if it's the one we want
with OpenKey(key, subkey) as _subkey:
if QueryValueEx(_subkey, 'DisplayName')[0] == name:
return subkey
return None
On my machine, querying for ActiveState's Komodo Edit (I actually used a regular expression rather than straight-value comparison), 1000 iterations of this took 8.18 seconds (timed using timeit), which seems like a negligible amount of time to me. Better yet, you can pull the UninstallString key from the registry and pass that straight to your subprocess (though you may want to add the /passive switch to the end.
Edit
Microsoft does, of course, provide a WMI class (Win32_Product) that provides a rather convenient interface to do all of this. Using Tim Golden's excellent WMI wrapper, one could initiate an install like this:
import wmi
c = wmi.WMI()
c.Win32_Product(Name = 'ProductName')[0].Uninstall()
However, as noted in this blog post, the Win32_Product class is extremely, painfully slow to use.

How do you escape a dash in Jython/Websphere?

I have a Jython script that is used to set up a JDBC datasource on a Websphere 7.0 server. I need to set several properties on that datasource. I am using this code, which works, unless value is '-'.
def setCustomProperty(datasource, name, value):
parms = ['-propertyName', name, '-propertyValue', value]
AdminTask.setResourceProperty(datasource, parms)
I need to set the dateSeparator property on my datasource to just that - a dash. When I run this script with setCustomProperty(ds, 'dateSeparator', '-') I get an exception that says, "Invalid property: ". I figured out that it thinks that the dash means that another parameter/argument pair is expected.
Is there any way to get AdminTask to accept a dash?
NOTE: I can't set it via AdminConfig because I cannot find a way to get the id of the right property (I have multiple datasources).
Here is a solution that uses AdminConfig so that you can set the property value to the dash -. The solution accounts for multiple data sources, finding the correct one by specifying the appropriate scope (i.e. the server, but this could be modified if your datasource exists within a different scope) and then finding the datasource by name. The solution also accounts for modifying the existing "dateSeparator" property if it exists, or it creates it if it doesn't.
The code doesn't look terribly elegant, but I think it should solve your problem :
def setDataSourceProperty(cell, node, server, ds, propName, propVal) :
scopes = AdminConfig.getid("/Cell:%s/Node:%s/Server:%s/" % (cell, node, server)).splitlines()
datasources = AdminConfig.list("DataSource", scopes[0]).splitlines()
for datasource in datasources :
if AdminConfig.showAttribute(datasource, "name") == ds :
propertySet = AdminConfig.list("J2EEResourcePropertySet", datasource).splitlines()
customProp = [["name", propName], ["value", propVal]]
for property in AdminConfig.list("J2EEResourceProperty", propertySet[0]).splitlines() :
if AdminConfig.showAttribute(property, "name") == propName :
AdminConfig.modify(property, customProp)
return
AdminConfig.create("J2EEResourceProperty", propertySet[0], customProp)
if (__name__ == "__main__"):
setDataSourceProperty("myCell01", "myNode01", "myServer", "myDataSource", "dateSeparator", "-")
AdminConfig.save()
Please see the Management Console preferences settings. You can do what you are attempting now and you should get to see the Jython equivalent that the Management Console is creating for its own use. Then just copy it.
#Schemetrical solution worked for me. Just giving another example with jvm args.
Not commenting on the actual answer because I don't have enough reputation.
server_name = 'server1'
AdminTask.setGenericJVMArguments('[ -serverName %s -genericJvmArguments "-agentlib:getClasses" ]' % (server_name))
Try using a String instead of an array to pass the parameters using double quotes to surround the values starting with a dash sign
Example:
AdminTask.setVariable('-variableName JDK_PARAMS -variableValue "-Xlp -Xscm250M" -variableDescription "-Yes -I -can -now -use -dashes -everywhere :-)" -scope Cell=MyCell')

Categories

Resources