I am trying to create a script that would help me automate the creation of a spine rig, but I am running into a problem. I am following the tutorial provided here and I am working on the step where you skin the curve to the IK joints.
However, when I try to use mc.bindSkin(), I keep getting an error:
Error: RuntimeError: file[directory]/maya/2016.5/scripts\createRigSpine.py line 200: Maya command error)
It's too late right now to for me to do much experimenting, but I was hoping someone could help me, or tell me if I'm using the wrong commands.
mc.select(crvSpine, jntIkMidSpine, jntIkChest)
mc.bindSkin(crvSpine, jntIkMidSpine, jntIkChest, tsb=True)
(have also tried mc.bindSkin() and mc.bindSkin(tsb=True))
Ideally, I want the settings to be:
Bind To: Selected Joints
Bind Method: Closest Distance
Skinning Method: Classic Linear
Normalize Weights: Interactive
Edit: I wanted to use skinCluster, not bindSkin.
you should use the skinCluster command to bind your curve to the joints - and you can actually do it without selecting anything!
Try this:
import maya.cmds as mc
influences = [jntIkMidSpine, jntIkChest]
scls = mc.skinCluster(influences, crvSpine, name='spine_skinCluster', toSelectedBones=True, bindMethod=0, skinMethod=0, normalizeWeights=1)[0]
# alternatively, if you don't want such a long line of code:
#
influences = [jntIkMidSpine, jntIkChest]
kwargs = {
'name': 'spine_skinCluster', # or whatever you want to call it...
'toSelectedBones': True,
'bindMethod': 0,
'skinMethod': 0,
'normalizeWeights': 1
}
scls = mc.skinCluster(influences, crvSpine, **kwargs)[0]
# OR just use the short names for the kwargs...
#
influences = [jntIkMidSpine, jntIkChest]
scls = mc.skinCluster(influences, crvSpine, n='spine_skinCluster', tsb=True, bm=0, sm=0, nw=1)[0]
If you wanted to, you could also explicitly set the weights you want for each cv of the curve. You could use the skinPercent command, or even just use setAttr for the various weight attrs in the skinCluster (that's a little more difficult, but not much)
cmds.bindSkin() command made for binding bones to geometry. It's not suitable for binding to IK's only. So you need to assign what joint you need to bind to.
For example:
import maya.cmds as mc
mc.select('ikHandle1','nurbsCircle1','joint5')
mc.bindSkin('ikHandle1','nurbsCircle1','joint5')
# the order of selection is vital
For constraining selected objects use the commands like this:
mc.pointConstraint('ikHandle1','nurbsCircle1', weight=5.0)
To find out what constraints are available to you, use Rigging module – Constrain menu – Parent, Point, Orient, Scale, Aim, Pole Vector.
I was using the wrong command. mc.skinCluster is what I wanted to use, not mc.bindSkin.
Related
I'm getting very confused trying to setup my simulation correctly in PyDrake. What I want is to have an actuated robot (with e.g. an InverseDynamicsController on it) together with an object in the scene that the robot will manipulate. However, I'm struggling to sort out how to create and use the MultibodyPlant, SceneGraph, Context, Simulator combination correctly.
Here is roughly what I've tried to do:
builder = DiagramBuilder()
plant, scene_graph = AddMultibodyPlantSceneGraph(builder, time_step=1e-4)
parser = Parser(plant, scene_graph)
# Add my robot
robot = parser.AddModelFromFile(robot_urdf)
robot_base = plant.GetFrameByName('robot_base')
plant.WeldFrames(plant.world_frame(), robot_base)
# Add my object
parser.AddModelFromFile(FindResourceOrThrow("drake/my_object.urdf"))
plant.finalize()
# Add my controller
Kp = np.full(6, 100)
Ki = 2 * np.sqrt(Kp)
Kd = np.full(6, 1)
controller = builder.AddSystem(InverseDynamicsController(plant, Kp, Ki, Kd, False))
controller.set_name("sim_controller");
builder.Connect(plant.get_state_output_port(robot),
controller.get_input_port_estimated_state())
builder.Connect(controller.get_output_port_control(),
plant.get_actuation_input_port())
# Get the diagram, simulator, and contexts
diagram = builder.Build()
simulator = Simulator(diagram)
context = simulator.get_mutable_context()
plant_context = plant.GetMyContextFromRoot(context)
However, this has some undesirable qualities. First, as long as I've added the object, then I get this error:
Failure at systems/controllers/inverse_dynamics_controller.cc:32 in SetUp(): condition 'num_positions == dim' failed.
Second, with the object added, the object pose becomes part of my InverseKinematics problem, and when I do SetPositions with plant_context, I have to set both my arm joints AND the pose of the object, when I feel like I should only be setting the robot's joint positions with SetPositions.
I realize I've done something wrong with this setup, and I'm just wondering what is the correct way to have an instance of Simulator that I can run simulations with that has both an actuated robot, and a manipulable object? Am I supposed to create multiple plants? Multiple contexts? Who shares what with who?
I'd really appreciate some advice on this, or a pointer to an example. Drake is great, but I struggle to find minimal examples that do what I want.
Yes, you can add a separate MultibodyPlant for control. See https://github.com/RobotLocomotion/drake/blob/master/examples/planar_gripper/planar_gripper_simulation.cc for an example. The setup is similar to yours, though it's in C++. You can try mimicking the way the diagram is wired up there.
When you do have two plants, you want to call SetPositions on the simulation plant (not the control plant). You can set only the robot positions by using ModelInstanceIndex.
# Add my robot
robot = parser.AddModelFromFile(robot_urdf)
...
plant.SetPositions(plant_context, robot, robot_positions)
I am using PyObjC bindings to try to get a spoken sound file from phonemes.
I figured out that I can turn speech into sound as follows:
import AppKit
ss = AppKit.NSSpeechSynthesizer.alloc().init()
ss.setVoice_('com.apple.speech.synthesis.voice.Alex')
ss.startSpeakingString_toURL_("Hello", AppKit.NSURL.fileURLWithPath_("hello.aiff"))
# then wait until ve.isSpeaking() returns False
Next for greater control I'd like to turn the text first into phonemes, and then speak them.
phonemes = ss.phonemesFromText_("Hello")
But now I'm stuck, because I know from the docs that to get startSpeakingString to accept phonemes as input, you first need to set NSSpeechSynthesizer.SpeechPropertyKey.Mode to "phoneme". And I think I'm supposed to use setObject_forProperty_error_ to set that.
There are two things I don't understand:
Where is NSSpeechSynthesizer.SpeechPropertyKey.Mode in PyObjC? I grepped the entire PyObjC directory and SpeechPropertyKey is not mentioned anywhere.
How do I use setObject_forProperty_error_ to set it? I think based on the docs that the first argument is the value to set (although it's called just "an object", so True in this case?), and the second is the key (would be phoneme in this case?), and finally there is an error callback. But I'm not sure how I'd pass those arguments in Python.
Where is NSSpeechSynthesizer.SpeechPropertyKey.Mode in PyObjC?
Nowhere.
How do I use setObject_forProperty_error_ to set it?
ss.setObject_forProperty_error_("PHON", "inpt", None)
"PHON" is the same as NSSpeechSynthesizer.SpeechPropertyKey.Mode.phoneme
"inpt" is the same as NSSpeechSynthesizer.SpeechPropertyKey.inputMode
It seems these are not defined anywhere in PyObjC, but I found them by firing up XCode and writing a short Swift snippet:
import Foundation
import AppKit
let synth = NSSpeechSynthesizer()
let x = NSSpeechSynthesizer.SpeechPropertyKey.Mode.phoneme
let y = NSSpeechSynthesizer.SpeechPropertyKey.inputMode
Now looking at x and y in the debugger show that they are the strings mentioned above.
As for how to call setObject_forProperty_error_, I simply tried passing in those strings and None as the error handler, and that worked.
The Maya python code below gives a nurbs boolean surface by first taking the difference of two nurbs spheres, nurbsSphere1 and nurbsSphere2, to give the nurbs surface nurbsBooleanSurface1. It then takes the difference of this surface and a third sphere, nurbsSphere3. The result, as seen in the outliner, is the three nurbs spheres plus a surfaceVarGroup, nurbsBooleanSurface1, which 'parents' three transform nodes nurbsBooleanSurface1_1, nurbsBooleanSurface1_2 and nurbsBooleanSurface1_3.
import maya.cmds as cmds
cmds.sphere(nsp=10, r=50)
cmds.sphere(nsp=4, r=5)
cmds.setAttr("nurbsSphere2.translateX",-12.583733)
cmds.setAttr("nurbsSphere2.translateY",-2.2691557)
cmds.setAttr("nurbsSphere2.translateZ",48.33736)
cmds.nurbsBoolean("nurbsSphere1", "nurbsSphere2", nsf=1, op=1)
cmds.sphere(nsp=4, r=5)
cmds.setAttr("nurbsSphere3.translateX",-6.7379503)
cmds.setAttr("nurbsSphere3.translateY",3.6949043)
cmds.setAttr("nurbsSphere3.translateZ",49.40595)
cmds.nurbsBoolean("nurbsBooleanSurface1", "nurbsSphere3", nsf=1, op=1)
print(cmds.ls("nurbsBooleanSurface1_*", type="transform"))
Strangley (to me), the list command, cmds.ls("nurbsBooleanSurface1_*", type="transform") only yields [u'nurbsBooleanSurface1_1', u'nurbsBooleanSurface1_2']; nurbsBooleanSurface1_3 is missing.
But when, after having executed the above code, the print command
print(cmds.ls("nurbsBooleanSurface1_*", type="transform"))
is re-executed, the result is [u'nurbsBooleanSurface1_1', u'nurbsBooleanSurface1_2', u'nurbsBooleanSurface1_3'].
I've tried delaying the execution of the final print command using time.sleep(n) to no avail. I've played with the idea that the missing node might have spun off into another namespace and then re-appeared at the completion of the execution block (desperate, I know!). I've experimented with renaming the spheres and surfaces, using functions and threads (the latter only superficially). The cause of the unlisted nurbsBooleanSurface1_3 on the first execution of
print(cmds.ls("nurbsBooleanSurface1_*", type="transform"))
remains a mystery. Any help would be much appreciated.
A dirty way (but only way I could find) is to call cmds.refresh() during the script.
I have rewritten your script here. Notice that I store each sphere in a variable, this is good practice to make sure it'll work, even if an existing object is already called nurbsSphere3 for example.
import maya.cmds as cmds
sphere1 = cmds.sphere(nsp=10, r=50)
sphere2 = cmds.sphere(nsp=4, r=5)
cmds.setAttr(sphere2[0] + ".translateX",-12.583733)
cmds.setAttr(sphere2[0] + ".translateY",-2.2691557)
cmds.setAttr(sphere2[0] + ".translateZ",48.33736)
nurbsBool1 = cmds.nurbsBoolean("nurbsSphere1", "nurbsSphere2", nsf=1, op=1)
sphere3 = cmds.sphere(nsp=4, r=5)
cmds.setAttr(sphere3[0] + ".translateX",-6.7379503)
cmds.setAttr(sphere3[0] + ".translateY",3.6949043)
cmds.setAttr(sphere3[0] + ".translateZ",49.40595)
nurbsBool2 = cmds.nurbsBoolean(nurbsBool1[0], sphere3[0], nsf=1, op=1)
cmds.refresh(currentView=True) # Force evaluation, of current view only
print(cmds.listRelatives(nurbsBool2[0], children=True, type="transform"))
When you create an object using cmds.sphere() it returns a list of the object name and more. To access this, you can use
mySphere = cmds.sphere()
print(mySphere)
# Result: [u'nurbsSphere1', u'makeNurbSphere1']
print(mySphere[0]) # the first element in the list is the object name
# Result: nurbsSphere1
The same is true for the boolean operation. Look in the documentation for the command under Return value http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/index.html
Since hours I try to solve a problem I have with Maya / MEL / Python.
I have a script to set values of a fluid container.
E.g. setAttr "fluidShape1.densityDissipation" 0.2
Works great...
My problem: Actually it is not possible to change the value using the interface (see image). Is there a way to find out if the "text box" is enabled?
Thanks!!
P.S. I cant upload the image :(. But I hope you guys know what i mean
To find out if the attribute is settable, use
getAttr -settable your_object.your_attribute
it will return 1 if you can set the attribute using setAttr and 0 if you can't.
If the value is grayed out in the UI the attribute is locked, you can unlock it with
setAttr -lock 0 your_object.your_attribute
If the value is purple in the UI it's driven by a connection of some kind, you'll need to use the hypergraph or the listConnections command to find out what's driving it and decide if you want to override the connection.
I already tried the -settable flag, but for some reason this is not working in my case.
Ok, lets say I create a FluidContainer. The density is set to zero, by using this command:
setAttr "fluidShape1.densityMethod" 0;
By using the -settable flag
getAttr -settable "fluidShapeq.densityScale"
the result is 1. But I can not change the corresponding slider.
But still it is possible to change the values by using setAttr... That is confusing for me!
ok I think I found a "solution" for my problem, but I think I can do better.
I use the following command to get the "grougID" of the slider and the field:
import maya.cmds as cmds
txt = "attrFieldSliderGrp214"
cmds.attrFieldSliderGrp( txt, q=True, l=True ) # Density Scale
And now I can usethe enable field by:
gray = cmds.attrFieldSliderGrp(txt, q=True, en=True ) # True/False
This works fine for me.
def gui():
cmds.window()
cmds.columnLayout( adjustableColumn=True )
#since density support only upto 3 setting max to 3
getCurrentInd = int(cmds.getAttr("fluidShape1.densityMethod"))
cmds.intSlider( min=0, max=3, value=getCurrentInd, step=1, cc=updateDens)
cmds.showWindow()
def updateDens(value):
cmds.setAttr("fluidShape1.densityMethod", value)
gui()
By Interface if I understand that you mean the AttributeEditor, you could try this.
import pymel.core as pc
def findAEFieldByLabel(label='Dissipation', field_type=pc.ui.AttrFieldSliderGrp):
for i in pc.ui.PyUI('MainAttributeEditorLayout').walkChildren():
if isinstance(i, pc.ui.RowLayout):
name = i.name()
try:
grp = field_type(name)
if grp.getLabel() == label:
return grp
except:
pass
print findAEFieldByLabel().getEnable()
I used pymel here because it helps you find the type of a python ui. Well ... not quite however! because it recognized attrFieldSliderGrps as RowLayouts
I want to randomly rotate a list of objects on a given axis with a random amount retrieved from a specified range.
This is what I came up with:
import pymel.core as pm
import random as rndm
def rndmRotateX(targets, axisType, range=[0,180]):
for obj in targets:
rValue=rndm.randint(range[0],range[1])
xDeg='%sDeg' % (rValue)
#if axisType=='world':
# pm.rotate(rValue,0,0, obj, ws=1)
#if axisType=='object':
# pm.rotate(rValue,0,0, obj, os=1)
pm.rotate(xDeg,0,0,r=True)
targetList= pm.ls(sl=1)
randRange=[0,75]
rotAxis='world'
rndmRotateX(targetList,rotAxis,randRange)
Im using pm.rotate() because it allows me to specify whether I want the rotations done in world or obj space (unlike setAttr, as far as I can tell).
The problem is, it raises this error when I try to run this:
# Error: MayaNodeError: file C:\Program Files\Autodesk\Maya2012\Python\lib\site-packages\pymel\internal\pmcmds.py line 140: #
It must be something with they way I enter the arguments for pm.rotate() (Im assuming this due to the line error PyMel spits out, which has to do with its arguments conversion function), but I cant figure out for the life of me wth I did wrong. :/
I think the problem is in this line
pm.rotate(rValue,0,0, obj, os=1)
obj should be the first argument, so it should be
pm.rotate(obj, (rValue,0,0), os=1)
but to make it even prettier you could use
obj.setRotation((rValue,0,0), os=1)
And also. Use pm.selected() instead of pm.ls(sl=1). It looks better
Another way to go about doing this..
from pymel.core import *
import random as rand
def rotateObjectsRandomly(axis, rotateRange):
rotateValue = rand.random() * rotateRange
for obj in objects:
PyNode(str(selected()) + ".r" + axis).set(rotateValue)
objectRotation = [[obj, obj.r.get()] for obj in selected()]
print "\nObjects have been rotated in the {0} axis {1} degrees.\n".format(axis, rotateValue)
return objectRotation
rotateObjectsRandomly("z", 360)
Since rand.random() returns a random value between 0 - 1, I just multiplied that by the rotateRange specified by the user..or in my preference I would just do away with that all together and just multiply it by 360...
You also don't need all the feedback I just think it looks nice when ran..
Objects have been rotated in the z axis 154.145898182 degrees.
# Result: [[nt.Transform(u'myCube'), dt.Vector([42.6541437517, 0.0, 154.145898182])]] #
Just as a straight debug of what you've got...
Issue 01: it's case sensitive
pm.rotate("20deg",0,0) will work fine, but pm.rotate("20Deg",0,0) will fail and throw a MayaNodeError because it thinks that you're looking for a node called '20Deg'. Basically, you want to build your string as per: xDeg='%sdeg' % (rValue)
Issue 02: you're relying on pm.rotate()'s implicit "will apply to selected objects" behaviour
You won't see this til you apply the above fix, but if you have two selected objects, and ran the (patched) rndmRotateX function on them, you'd get both objects rotating by the exact same amount, because pm.rotate() is operating on the selection (both objects) rather than a per-object rotation.
If you want a quick fix, you need to insert a pm.select(obj) before the rotate. And you possibly want to save the selection list and restore it...however IMHO, it's a Really Bad Idea to rely on selections like this, and so I'd push you towards Kim's answer.