PDA

View Full Version : Create and assign multiple materials


Skeletor1
07-16-2012, 08:08 PM
I'm trying to make a script/plugin as a learning exercise for myself in Python. The script is supposed to change the colour individually on multiple objects depending on the distance they have to a control object.

I have created a simple gui that stores the desired objects, selects which object to be the control object and a button to colour the objects.

It was working with objects that already had materials assigned to them, but I want to be able to not have to assign the materials beforehand, so that if you're using 100s of objects it would be easier to work with.

I was thinking I would create a loop which creates and assigns a new material to an object for each iteration.

Could someone please explain how to do this?

This is what I've got at the moment (you can skip to the loop in the middle of the code with the big gaps):


import maya.standalone
maya.standalone.initialize()


import maya.cmds as mc
import Tkinter as tk
import math as m

app = tk.Tk()

#adds selected objects to the list box if they're not already in it
def addObjects():
listObjects = mc.ls(sl=True)
selSize = len(listObjects)

for i in range(0,selSize,1):
doesExist = False
for z in range(0,Lb1.size(),1):
if(listObjects[i]==Lb1.get(z)):
doesExist=True

if (doesExist==False):
Lb1.insert(i,listObjects[i])

print "Objects were added"

#removes the selected item from the list box
def removeObjects():
Lb1.delete(Lb1.curselection())
print "Object was removed"

#sets an objective to be active
def activateObject():
v.set(Lb1.get(Lb1.curselection()))
Lb1.delete(Lb1.curselection())
print "Object active"

#colours the objects depending on distance. Needs work, so it works with multiple objects,
#use the objects from the list and automatically assign a shader to the objects
def colourObjects():

activeObj = mc.getAttr(v.get() + ".translate")

for i in range(0,Lb1.size(),1):
otherObj = mc.getAttr(Lb1.get(i) + ".translate")

# to get distance take the square root of (x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2
distance = m.sqrt(m.pow(activeObj[0][0] - otherObj[0][0], 2) + m.pow(activeObj[0][1] - otherObj[0][1], 2) + m.pow(activeObj[0][2] - otherObj[0][2], 2))

red = 1
green = 1
blue = 1






#create a shader
shader=mc.shadingNode("blinn",asShader=True)
# a shading group
shading_group= mc.sets(renderable=True,noSurfaceShader=True,empty=True)
#connect shader to sg surface shader
mc.connectAttr('%s.outColor' %shader ,'%s.surfaceShader' %shading_group)

mc.sets(Lb1.get(i), e=True, forceElement=shader)

#mc.createAndAssignShader("blinn", Lb1.get(i))
#mc.connectAttr(










#mc.setAttr("blinn1.color", 1, 0, 0)

# change the scale to set the distance from red to white
# lower numbers make the distance greater
scale = .03
green = distance * scale
blue = distance * scale

# clamp values to 1
if green > 1:
green = 1
if blue > 1:
blue = 1

mc.setAttr("blinn%s.color" %i, red, green, blue)

#creates the list box
Lb1 = tk.Listbox(app)
Lb1.pack()

#creates the button to add objects
btnAdd = tk.Button(app, text="Add selected objects", command=addObjects)
btnAdd.pack()

#creates the button to remove objects
btnRemove = tk.Button(app, text="Remove object", command=removeObjects)
btnRemove.pack()

#creates the button to set the active object
btnActivate = tk.Button(app, text="Activate object", command=activateObject)
btnActivate.pack()

#creates a label for displaying the active object
v = tk.StringVar()
label = tk.Label(app, textvariable=v)
label.pack()
v.set("Select active object")

#creates the button to colour objects based on distance
btnColour = tk.Button(app, text="Colour objects", command=colourObjects)
btnColour.pack()

app.protocol("WM_DELETE_WINDOW",app.quit)
app.mainloop()
app.destroy()

NaughtyNathan
07-16-2012, 10:45 PM
there are two main ways to assign a material to an item (although both methods ultimately arrive at exactly the same result). "an item" can be a mesh shape or a series of faces.

There's the novice, easy way, and the "proper" way.

the first, novice method is to use the hypershade command (This is exactly what happens when you assign materials by RMB on the shader in the Hypershade).
mc.select(item) # item could be a single, or list of, renderable items
mc.hyperShade('material', assign=1) # 'material' is the name of your material/shader
The benefit to using this command is that it's easy to understand, and allows you to specify the actual material you want to assign. The problem with this method is that it only operates on selected items (never good when scripting) and it actually hides from the user how materials/shaders actually operate "under the hood".

Now in Maya, objects and faces are never actually connected or assigned directly to materials. Each material is connected to a shadingEngine and this is connected to the objects to be shaded (a shadingEngine is actually just a glorified "set" that the items are "in"). If you want to do this "properly", you have to create both a material and a shadingEngine, connect the two together then put the objects you want shaded "into" the shadingEngine set.
something like this:
material = mc.shadingNode('lambert', asShader=1, name='myShader')
SG = mc.sets(renderable=1, noSurfaceShader=1, empty=1, name='myShaderSG')
mc.connectAttr((material+'.outColor'),(SG+'.surfaceShader'),f=1)
mc.sets(item, e=1, forceElement=SG)

Skeletor1
07-17-2012, 02:18 AM
Thank you Nathan. That worked a treat!

I ran in to a new shader related problem. When it reaches this line of code:

mc.setAttr('myShader%s'.color %i, red, green, blue)

I get the error:

# Exception in Tkinter callback
# Traceback (most recent call last):
# File "E:\Program Files\Autodesk\Maya2012\bin\python26.zip\lib-tk\Tkinter.py", line 1410, in __call__
# return self.func(*args)
# File "<maya console>", line 72, in colourObjects
# AttributeError: 'str' object has no attribute 'color'

I figured it doesn't understand it's a shader for some reason. I mean it is a string, but shouldn't it work?

This is the whole script:


import maya.standalone
maya.standalone.initialize()

import maya.cmds as mc
import Tkinter as tk
import math as m

app = tk.Tk()

#adds selected objects to the list box if they're not already in it
def addObjects():
listObjects = mc.ls(sl=True)
selSize = len(listObjects)

for i in range(0,selSize,1):
doesExist = False
for z in range(0,Lb1.size(),1):
if(listObjects[i]==Lb1.get(z)):
doesExist=True

if (doesExist==False):
Lb1.insert(i,listObjects[i])

print "Objects were added"

#removes the selected item from the list box
def removeObjects():
Lb1.delete(Lb1.curselection())
print "Object was removed"

#sets an objective to be active
def activateObject():
v.set(Lb1.get(Lb1.curselection()))
Lb1.delete(Lb1.curselection())
print "Object active"

#colours the objects depending on distance. Needs work, so it works with multiple objects,
#use the objects from the list and automatically assign a shader to the objects
def colourObjects():

activeObj = mc.getAttr(v.get() + ".translate")

for i in range(0,Lb1.size(),1):
otherObj = mc.getAttr(Lb1.get(i) + ".translate")

# to get distance take the square root of (x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2
distance = m.sqrt(m.pow(activeObj[0][0] - otherObj[0][0], 2) + m.pow(activeObj[0][1] - otherObj[0][1], 2) + m.pow(activeObj[0][2] - otherObj[0][2], 2))

red = 1
green = 1
blue = 1



material = mc.shadingNode('blinn', asShader=1, name='myShader%s'%i)
SG = mc.sets(renderable=1, noSurfaceShader=1, empty=1, name='myShaderSG%s'%i)
mc.connectAttr((material+'.outColor'),(SG+'.surfaceShader'),f=1)
mc.sets(Lb1.get(i), e=1, forceElement=SG)


# change the scale to set the distance from red to white
# lower numbers make the distance greater
scale = .03
green = distance * scale
blue = distance * scale

# clamp values to 1
if green > 1:
green = 1
if blue > 1:
blue = 1
print('myShader%s'%i)
print('myShader%s'.color %i)
mc.setAttr('myShader%s'.color %i, red, green, blue)

#creates the list box
Lb1 = tk.Listbox(app)
Lb1.pack()

#creates the button to add objects
btnAdd = tk.Button(app, text="Add selected objects", command=addObjects)
btnAdd.pack()

#creates the button to remove objects
btnRemove = tk.Button(app, text="Remove object", command=removeObjects)
btnRemove.pack()

#creates the button to set the active object
btnActivate = tk.Button(app, text="Activate object", command=activateObject)
btnActivate.pack()

#creates a label for displaying the active object
v = tk.StringVar()
label = tk.Label(app, textvariable=v)
label.pack()
v.set("Select active object")

#creates the button to colour objects based on distance
btnColour = tk.Button(app, text="Colour objects", command=colourObjects)
btnColour.pack()

app.protocol("WM_DELETE_WINDOW",app.quit)
app.mainloop()
app.destroy()

NaughtyNathan
07-17-2012, 10:06 AM
.color is the literal string name of a node attribute in Maya. it has nothing to do with python methods or attributes of a python string. In python string objects can have methods and attributes of their own which you call in this way, what you want to do is set the Maya attribute:
mc.setAttr('myShader%s.color' %i, red, green, blue)notice how this is now all a literal string, but before the .color part was not, so python saw it as an attribute of the string object 'myShader%s'. (this is kinda confusing if you're new to python or not 100% sure of how it's objects work)
Anyway, don't do this. By all means construct names when creating nodes, but never refer to them later by reconstructing the name again. always capture the output when you create (or change the name of) a node in Maya and use that as the variable:
material = mc.shadingNode('blinn', asShader=1, name='myShader%s'%i)
mc.setAttr(material+'.color', red, green, blue)

Skeletor1
07-17-2012, 10:40 AM
Nathan you're my hero! Thanks again - you're really good at explaining things.

I'm more used to programming in Java, so I mix up the syntax sometimes.

CGTalk Moderation
07-17-2012, 10:40 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.