I am currently working on a little script that creates a crane-like rig automatically in Autodesk Maya, the user gets to choose the amount of joints by a UI.
My question is how do I take the integer input of the user and use it as the variable value for my "jointAmount"?
I am also wondering how I would be able to call my function(AutoCraneRig) to actually run the script from the UI. I have a "apply"-button but I am unsure how to connect it to my function.
I have seen similar posts like mine but I feel that the solutions shown are somewhat hard for me to understand and/or I can't really relate what is shown to my own problem.
If anything is unclear or more information is needed from me please don't hesitate to call me out.
Here is what my current UI look like
import maya.cmds as cmds
import pymel.core as pm
def jntctrl():
number = pm.intField(jnt, q=1, v=1)
print(number)
if pm.window("stuff", exists = True):
pm.deleteUI("stuff")
pm.window("stuff", t = "Crane Rig Generator", w=400, h=200)
pm.columnLayout(adj = True)
pm.text(label="Joint Amount:")
jnt = pm.intField(changeCommand = 'jntctrl()')
pm.button(label="Create Crane")
pm.showWindow()
#Defining how many joints the user want to have for their crane rig
jointAmmount = 5
#Defining how many controllers the user want to have to orient the crane.
#May not exceed the joint amount
controllerAmount = 5
def autoCraneRig():
#Creating the joints
for i in range(jointAmmount):
pm.joint()
pm.move(0, i, 0)
#Creating the controllers
for i in range(controllerAmount):
pm.circle()
pm.rotate (0,90,0)
pm.makeIdentity (apply= True)
#Creating the groups
for i in range(controllerAmount):
pm.group()
#Somehow one of the nurbs get parented to a group when running the script, here i select both the groups and then unparent them.
pm.select("group*", "nurbsCircle*")
pm.parent(world = True)
#Creating lists/dictionaries for the groups
#Since I wanted to parent my objects by their number I had to put all objects in lists/dictionries to get access.
groups = pm.ls('group*')
nbs = [int(n.split('group')[-1]) for n in groups]
groupDic = dict(zip(nbs, groups))
#Create a list/dictionary for the joints
joint = pm.ls('joint*', type='joint')
nbs = [int(n.split('joint')[-1]) for n in joint]
jointDic = dict(zip(nbs, joint))
common = list(set(groupDic.keys())&set(jointDic.keys()))
#Parenting the groups to the joints
for i in common:
pm.parent(groupDic[i], jointDic[i])
#Reseting the transformations of the groups and then unparenting them to still have the transformation data of the joints
pm.select("group*")
pm.makeIdentity()
pm.parent(world = True)
#Creating a list/dictionary for the nurbs aswell that will be parented to the groups in numeric order
nurbs_sh = pm.ls('nurbsCircle*', type='nurbsCurve')
#I had to get the transformation information from the nurbs before parenting them with anything would work(took a long time to get it right).
nurbs_tr = pm.listRelatives(nurbs_sh, p=1)
nbs = [int(n.split('nurbsCircle')[-1]) for n in nurbs_tr]
curveDic = dict(zip(nbs, nurbs_tr))
common = list(set(groupDic.keys())&set(curveDic.keys()))
#Parent the nurbs to the groups
for i in common:
pm.parent(curveDic[i], groupDic[i])
#Select the nurbs and reset transformations and then freeze transform
pm.select("nurbsCircle*")
pm.makeIdentity()
#Orient constrain the controllers/nurbs to the joints
for i in common:
pm.orientConstraint(curveDic[i], jointDic[i])
#Parent the 2nd group with the first controller. Do this for the whole hierarchy.
for i in common:
pm.parent(groupDic[i+1], curveDic[i])
#I'm getting keyError after I put the "+1" in my groupDic and I don't know why, although it still works, I guess.
autoCraneRig()
Here's an example for how to call a specific function/command when a button is clicked, and how to get the value of an int field. The key is in naming the fields, so you can reference the UI control later.
import pymel.core as pm
def ui():
if (pm.window("myWindow", exists=True)):
pm.deleteUI("myWindow")
window = pm.window("myWindow", t="My Window", w=400, h=200)
pm.columnLayout(adj=True)
pm.intField("myIntField")
pm.button("Button", aop=True, command="action()")
pm.showWindow(window)
def action():
print("Button clicked!")
value = pm.intField("myIntField", q=True, v=True)
print(value)
ui()
If you want to get more into making UI's, I would recommend you watch these two videos:
PySide UI Creation in Maya: Video One
PySide UI Creation in Maya: Video Two
Related
I'm trying to make a picker. As a test, I currently have 3 cubes in the scene, they represent the arm. I've stored the selection into the button's command, and individually it works. I could even store all 3 in one button selection, but I'd like to be able to drag select all 3 of them at once and make it more intuitive and flexible
I'm aware I could use PySide or QT for this, but I'm trying to keep it to maya.cmds for certain reasons. Basically without having to install additional modules and such to Maya.
Here's the code.
import maya.cmds as cmds
import maya.mel as mel
cmds.window("Arm_Control", title="Arm", wh=[288.0, 288.0], s=False)
cmds.formLayout("formLayout", numberOfDivisions=100, w=288.0, h=288.0)
#Creates the cubes and parents them to each other. This is supposed to mimick an arm
cmds.polyCube(n="Shoulder")
cmds.polyCube(n="Elbow")
cmds.polyCube(n="Wrist")
cmds.setAttr("Elbow.ty", -1.5)
cmds.setAttr("Wrist.ty", -3)
cmds.parent("Wrist", "Elbow", )
cmds.parent("Elbow", "Shoulder" )
#We create the buttons and a function that stores the selection sets. Upon pressing the button, we select the cubes.
def Wrist_Button_SS(button):
exec(str( cmds.select("Wrist")))
button_Wrist_Button_SS = Wrist_Button_SS
cmds.button("Wrist_Button_SS", l="Wrist", ann="", recomputeSize = True, bgc=[0.45400167849240863, 0.45400167849240863, 0.45400167849240863], h = 49.362586349587794, w = 100.0,c=button_Wrist_Button_SS, parent ="formLayout")
cmds.formLayout("formLayout", edit=True, attachForm=[("Wrist_Button_SS", "top",191.40357806156595), ("Wrist_Button_SS", "left",82.79126994609834)])
def Elbow_Button_SS(button):
exec(str( cmds.select("Elbow")))
button_Elbow_Button_SS = Elbow_Button_SS
cmds.button("Elbow_Button_SS", l="Elbow", ann="", recomputeSize = True, bgc=[0.45400167849240863, 0.45400167849240863, 0.45400167849240863], h = 49.362586349587794, w = 100.0,c=button_Elbow_Button_SS, parent ="formLayout")
cmds.formLayout("formLayout", edit=True, attachForm=[("Elbow_Button_SS", "top",119.03931848716638), ("Elbow_Button_SS", "left",81.88357425928116)])
def Shoulder_Button_SS(button):
exec(str( cmds.select("Shoulder")))
button_Shoulder_Button_SS = Shoulder_Button_SS
cmds.button("Shoulder_Button_SS", l="Shoulder", ann="", recomputeSize = True, bgc=[0.45400167849240863, 0.45400167849240863, 0.45400167849240863], h = 49.362586349587794, w = 100.0,c=button_Shoulder_Button_SS, parent ="formLayout")
cmds.formLayout("formLayout", edit=True, attachForm=[("Shoulder_Button_SS", "top",44.684211468350256), ("Shoulder_Button_SS", "left",81.88357201371167)])
cmds.showWindow("Arm_Control")
I'm aware that this is a longshot. If I can't drag/marquee select on the buttons or have that way to select selection sets, I might make it so that the user can hold shift when clicking the buttons to add onto the selection, but I'd prefer to set it up with a drag selection, sort of like DW picker or Animschool picker. Thank you to whoever can help out with this.
I am trying since days to assign new materials and random colors to meshes that already exist in a Blender scene.
I need to do it in Python but I cannot find a solution.
I would like each mesh to have different materials and different colors.
I have found two scripts that are very useful but I don't manage to combine them together, can anyone help please?
Thank you in advance
Script 1) assign a new material to all the meshes in a scene (but color not assigned):
import bpy
bpy.ops.object.select_all(action='SELECT')
# I separated the creation of nodes to a function so it's easier
# to edit later if needed
def create_nodes(mat):
mat.use_nodes = True
nodes = mat.node_tree.nodes
for every_node in nodes: # this removes all existing nodes
nodes.remove(every_node)
# creating Principled node and moving it:
node = nodes.new('ShaderNodeBsdfPrincipled')
node.location = (-190,100)
# creating Output node and moving it:
output_node = nodes.new('ShaderNodeOutputMaterial')
output_node.location = (40,100)
# creating the link between the two nodes:
links = mat.node_tree.links
link = links.new(node.outputs[0], output_node.inputs[0])
# this saves the currently active object so it can be restored later
active = bpy.context.object
# let's loop through all selected objects
for every_object in bpy.context.selected_objects:
# I only want to work with objects capable of having a material
if every_object.type in {'MESH','CURVE', 'SURFACE','META', 'FONT'}:
if every_object.name not in bpy.data.materials:
# if there is no material named after the object yet let's make one
mat = bpy.data.materials.new(every_object.name)
# and let's create the nodes for it
create_nodes(mat)
else:
# if the material already exists let's just use it
mat = bpy.data.materials.get(every_object.name)
if len(every_object.material_slots) == 0: # if there are no material slots
every_object.data.materials.append(mat)
# The only thing left now is to assign the material to
# all material slots. We probably do not want to loose the info
# about how the object is divided into separate materials
for every_slot in every_object.material_slots:
every_slot.material = mat
Script 2) assign a new material and a random color to an existing object in the scene (but works only for 1 object):
import bpy, random
ob = bpy.data.objects.get("Cube")
if ob != None:
# Create materials.
mat_one = bpy.data.materials.get("mat_one")
if mat_one == None:
mat_one = bpy.data.materials.new("mat_one")
mat_one.diffuse_color = (random.random(),random.random(),random.random(),random.random())
# Add materials to slots.
if len(ob.material_slots) != 1:
ob.data.materials.append(mat_one)
Lets say I have an assembly like this:
MainProduct:
-Product1 (Instance of Part1)
-Product2 (Instance of Part2)
-Product3 (Instance of Part2)
-Product4 (Instance of Part3)
...
Now, I want to copy/paste a feature from Product3 into another one.
But I run into problems when selecting the feature programmatically, because there are 2 instances of the part of that feature.
I can't control which feature will be selected by CATIA.ActiveDocument.Selection.Add(myExtractReference)
Catia always selects the feature from Product2 instead of the feature from Product3. So the position of the pasted feature will be wrong!
Does anybody know this problem and has a solution to it?
Edit:
The feature reference which I want to copy already exists as a variable because it was newly created (an extract of selected geometry)
I could get help else where. Still want to share my solution. It's written in Python but in VBA its almost the same.
The clue is to access CATIA.Selection.Item(1).LeafProduct in order to know where the initial selection was made.
import win32com.client
import pycatia
CATIA = win32com.client.dynamic.DumbDispatch('CATIA.Application')
c_doc = CATIA.ActiveDocument
c_sel = c_doc.Selection
c_prod = c_doc.Product
# New part where the feature should be pasted
new_prod = c_prod.Products.AddNewComponent("Part", "")
new_part_doc = new_prod.ReferenceProduct.Parent
# from user selection
sel_obj = c_sel.Item(1).Value
sel_prod_by_user = c_sel.Item(1).LeafProduct # reference to the actual product where the selection was made
doc_from_sel = sel_prod_by_user.ReferenceProduct.Parent # part doc from selection
hb = doc_from_sel.Part.HybridBodies.Add() # new hybrid body for the extract. will be deleted later on
extract = doc_from_sel.Part.HybridShapeFactory.AddNewExtract(sel_obj)
hb.AppendHybridShape(extract)
doc_from_sel.Part.Update()
# Add the extract to the selection and copy it
c_sel.Clear()
c_sel.Add(extract)
sel_prod_by_catia = c_sel.Item(1).LeafProduct # reference to the product where Catia makes the selection
c_sel_copy() # will call Selection.Copy from VBA. Buggy in Python.
# Paste the extract into the new part in a new hybrid body
c_sel.Clear()
new_hb = new_part_doc.Part.HybridBodies.Item(1)
c_sel.Add(new_hb)
c_sel.PasteSpecial("CATPrtResultWithOutLink")
new_part_doc.Part.Update()
new_extract = new_hb.HybridShapes.Item(new_hb.HybridShapes.Count)
# Redo changes in the part, where the selection was made
c_sel.Clear()
c_sel.Add(hb)
c_sel.Delete()
# Create axis systems from Position object of sel_prd_by_user and sel_prd_by_catia
prod_list = [sel_prod_by_user, sel_prod_by_catia]
axs_list = []
for prod in prod_list:
pc_pos = pycatia.in_interfaces.position.Position(prod.Position) # conversion to pycata's Position object, necessary
# in order to use Position.GetComponents
ax_comp = pc_pos.get_components()
axs = new_part_doc.Part.AxisSystems.Add()
axs.PutOrigin(ax_comp[9:12])
axs.PutXAxis(ax_comp[0:3])
axs.PutYAxis(ax_comp[3:6])
axs.PutZAxis(ax_comp[6:9])
axs_list.append(axs)
new_part_doc.Part.Update()
# Translate the extract from axis system derived from sel_prd_by_catia to sel_prd_by_user
extract_ref = new_part_doc.Part.CreateReferenceFromObject(new_extract)
tgt_ax_ref = new_part_doc.Part.CreateReferenceFromObject(axs_list[0])
ref_ax_ref = new_part_doc.Part.CreateReferenceFromObject(axs_list[1])
new_extract_translated = new_part_doc.Part.HybridShapeFactory.AddNewAxisToAxis(extract_ref, ref_ax_ref, tgt_ax_ref)
new_hb.AppendHybridShape(new_extract_translated)
new_part_doc.Part.Update()
I would suggest a differed approach. Instead of adding references you get from somewhere (by name probably) add the actual instance of part to selection while iterating trough all the products. Or use instance Names to get the correct part.
Here is a simple VBA example of iterating one lvl tree and select copy paste scenario.
If you want to copy features, you have to dive deeper on the Instance objects.
Public Sub CatMain()
Dim ActiveDoc As ProductDocument
Dim ActiveSel As Selection
If TypeOf CATIA.ActiveDocument Is ProductDocument Then 'of all the checks that people are using I think this one is most elegant and reliable
Set ActiveDoc = CATIA.ActiveDocument
Set ActiveSel = ActiveDoc.Selection
Else
Exit Sub
End If
Dim Instance As Product
For Each Instance In ActiveDoc.Product.Products 'object oriented for ideal for us in this scenario
If Instance.Products.Count = 0 Then 'beware that products without parts have also 0 items and are therefore mistaken for parts
Call ActiveSel.Add(Instance)
End If
Next
Call ActiveSel.Copy
Call ActiveSel.Clear
Dim NewDoc As ProductDocument
Set NewDoc = CATIA.Documents.Add("CATProduct")
Set ActiveSel = NewDoc.Selection
Call ActiveSel.Add(NewDoc.Product)
Call ActiveSel.Paste
Call ActiveSel.Clear
End Sub
I’m really sorry if I’m asking a question that’s been already answered but I couldn’t find an answer.
I’m writing a code that would allow me to connect a translate of several controllers into a blendWeighted node input channels. The amount of the controllers may vary depending on the selection. I’m struggling with the part where they need to connect to a single blendWeighted node input. Could someone tell me how I could connect every new controller to the next input channel of the blendWeighted node?
I’m sorry if my code is a bit childlike, I’m still learning ^^;
sel = mc.ls(sl=True, fl=True)
drvConnect = []
for i in sel:
name = i.split('_Crv')[0]
dGP = mc.createNode('transform', n='%s_Drv'%name, p=i)
drvConnect.append(dGP)
sh = mc.listRelatives(i, shapes=True)[0]
blendX = mc.createNode('blendWeighted', n='%s_X'%name)
blendY = mc.createNode('blendWeighted', n='%s_Y'%name)
blendZ = mc.createNode('blendWeighted', n='%s_Z'%name)
mc.connectAttr(dGP + '.translateX', blendX +'.input')
mc.connectAttr(dGP + '.translateY', blendY +'.input')
mc.connectAttr(dGP + '.translateZ', blendZ +'.input')
I assume you only want to create a single blendWeighted node. If that's the case, consider that the blendWeighted node's input attribute is an array attribute, so if you want to connect multiple items to it you would need to specify the target index.
For example, to connect the three translate outputs of a node to the first three inputs of the blend node you would use something like this:
mc.connectAttr('ctrl.translateX', 'blend.input[0]')
mc.connectAttr('ctrl.translateY', 'blend.input[1]')
mc.connectAttr('ctrl.translateZ', 'blend.input[2]')
(Maya will take care of creating the items in the array)
In our case you could simply keep a counter of the added items while you loop through the selection and the transform components (just a guide - not tested):
sel = mc.ls(sl=True, fl=True)
drvConnect = []
blendNode = mc.createNode('blendWeighted', n='blend_main')
for i in sel:
name = i.split('_Crv')[0]
dGP = mc.createNode('transform', n='%s_Drv'%name, p=i)
drvConnect.append(dGP)
for comp in ('X', 'Y', 'Z'):
mc.connectAttr(
'{}.translate{}'.format(dGP, comp),
'{}.input[{}]'.format(blendNode, blendIndex))
blendIndex += 1
I've written some code for a tool that duplicates and moves controllers/objects to joints. Its really just code to copy and move one object to a list of other objects. Its very basic but it does work.
The pasted code is a snippet that I've taken out from the rest but will work if run in Maya.
This code in particular will create a nurbsCurve, put it inside a group, then move that group to the first object on the list. I then used a loop to do it again, but then parent the group underneath the nurbsCurve from the previous group to create a parent-hierarchy all the way down.
import maya.cmds as cmds
def setZero(target):
cmds.setAttr("%s.translateX" % target, 0)
cmds.setAttr("%s.translateY" % target, 0)
cmds.setAttr("%s.translateZ" % target, 0)
cmds.setAttr("%s.rotateX" % target, 0)
cmds.setAttr("%s.rotateY" % target, 0)
cmds.setAttr("%s.rotateZ" % target, 0)
selJoint = cmds.ls(selection = True, shortNames = True)
firstCtrl = cmds.circle(normal =( 1,0,0))
firstGrp = cmds.group(firstCtrl)
cmds.parent(firstGrp,selJoint[0])
setZero(firstGrp)
cmds.parent(firstGrp, world = True)
#Use loop for the rest of the joints
for joint in selJoint:
#Skip the first joint since its already done
if joint == selJoint[0]:
continue
circleCtrl = cmds.circle(normal =( 1,0,0))
offsetGrp = cmds.group(circleCtrl)
cmds.parent(offsetGrp, joint)
setZero(offsetGrp)
cmds.parent(offsetGrp, world = True)
cmds.parent(offsetGrp, firstCtrl) #Parent new offset Group to the old controller
firstCtrl = circleCtrl #The new controller is now the target for the next offset/ctrl to be parented under
It works as intended but I get this warning:
Warning: Cannot parent components or objects in the underworld
I've tried looking this issue up, but I haven't found anything on how to fix the problem. But I do know that the "underworld" is another space in Maya, and that it can cause issues down the line.
The warning is being thrown at the end on this line cmds.parent(offsetGrp, firstCtrl), and if you print out firstCtrl it'll output something like this:
[u'nurbsCircle1', u'makeNurbCircle1']
So what it's doing is it's trying to parent firstCtrl and 'nurbsCircle1' to 'makeNurbCircle1'. Since 'makeNurbCircle1' is a node with no transform, it throws that warning, because obviously you can't parent to something with no transform.
Luckily it's very easy to fix. Now that we know that it's a list, we just parent to the first index of it like so: cmds.parent(offsetGrp, firstCtrl[0])
If you want more info on this same warning then you can also check out this question.