Little help with maya commands & python

Hello everyone,
I started learning python/mel for maya and I need a little help with my script.

I am trying to make a tool that will check the mesh and delete those who share the same world position, the same number of vertex and the same bounding box.

Here is the link to my script : http://bit.ly/2Vucv9k

the “import myMath” cointains 1 function that returns the distance between 2 points in the world
it’s just something like :
sqrt( (x1 - x2)² + (y1 - y2)² + (z1 - z2)²)
the “import windowError” show a window dialog with a simple text

the script works but I think the way I did my progress bar is weird and honestly I think there is a better way to do it but I don’t know how.
You are welcome to tell me anything that may be wrong in my script (and I am sure there are a lot of things that must be fixed :smiley: ) .

Thank you for your time :slight_smile:

I’ve only got a couple things to say.
First, THANK YOU for using the whole word for the keyword arguments! Please don’t ever stop.

It may be more convenient in the future to not have to unpack all those values when finding the distance between 2 points. So instead of def distance(x1, x2, y1, y2, z1, z2):
I would do something like def distance(point1, point2):. However, this is not needed at all, and is just my own personal preference.

Oh, and you shouldn’t ever use a mutable object as a default. You’ve got
def removeDuplicate(maxDistance = 0.1, allObjectsList = []):
It should be allObjectsList=None in the function definition
And then check if allObjectsList is None: allObjectsList = [] later in the function
The reason is changing that argument actually changes the default.

def myAppender(myVal, myArg=[]):
    myArg.append(myVal)
    return myArg

print myAppender('T') # prints ['T']
print myAppender('A') # prints ['T', 'A']
print myAppender('O') # prints ['T', 'A', 'O']

As for the progress bar, I don’t know my way around maya UI commands, sorry.
But other than that, LGTM.


As a small aside, does anybody know why we use import maya.cmds as cmds instead of from maya import cmds?

1 Like

I can answer that one, its an older idiom that persists in a lot of maya python examples and tutorials.

As for the progress bar stuff, I can’t remember exactly how those work anymore, but I’ll try to poke around tomorrow.

Thank you for your answers.

I mostly use whole words for the keyword arguments because it is easier for me to understand what I am changing. But I guess I’ll continue because you are not the only one who told me not to stop ^^

Hmm… I forgot that I could put “none” for a default.
I’ll change it later, thanks

For the “import maya.cmds as cmds” and the “from maya import cmds”

Which one would be the right one to use ?
I have seen both in 2 differents tutorial but I decided to use “import maya.cmds as cmds” because it seems more coherent to only use “import x”

They are both valid, and accomplish the same thing. It was just something I noticed, and didn’t have an answer for.

As a style thing, I’d suggest breaking the big main function up a bit. For example you’re doing this inline

BBObj = cmds.polyEvaluate(allObjectsList[obj], boundingBox = True)
                BBObj2 = cmds.polyEvaluate(allObjectsList[obj2], boundingBox = True)
        
                BBObjMin = (BBObj[0][0], BBObj[1][0], BBObj[2][0])
                BBObjMax = (BBObj[0][1], BBObj[1][1], BBObj[2][1])
                distanceMin = myMath.distance(BBObj[0][0], BBObj2[0][0], BBObj[1][0], BBObj2[1][0], BBObj[2][0], BBObj2[2][0])

                BBObj2Min = (BBObj2[0][0], BBObj2[1][0], BBObj2[2][0])
                BBObj2Max = (BBObj2[0][1], BBObj2[1][1], BBObj2[2][1])
                distanceMax = myMath.distance(BBObj[0][1], BBObj2[0][1], BBObj[1][1], BBObj2[1][1], BBObj[2][1], BBObj2[2][1])

You could convert that to a separate function like compare_bounds() and move it outside of the loop, then just call it. That helps reusability and also readability.

On a related note it’s usually a good idea to break up index-access blocks (like the BBObj2[0][1], BBObj2[1][1], BBObj2[2][1]) into something more legible. For example

    def bounds_from_object(obj):
          xcoords, ycoords, zcoords = cmds.polyEvaluate(obj, boundingBox = True)
          min_bbox = xcoords[0], ycoords[0], zcoords[0]
          max_bbox = xcoords[1], ycoords[1], zcoords[1]

         return min_bbox, max_bbox

extracts the min and max corners of the box just as you are doing, but it’s easier to spot an issue and see a problem (whereas with all indices it’s harder to notice a bug like

       BBObj2Min = (BBObj2[0][0], BBObj2[0][0], BBObj2[2][0])

which accidentally uses the minimum X twice instead

It’s a good idea to think about how you’re structuring your loops. You don’t need to do indexes like you would in, say, c# ; instead you can just loop over lists directly:

        delenda = []
        for first_object in all_object_list:
             for second_object in all_object_list:
                   if first_object == second_object:
                        continue
                   
                   dist_min, dist_max = compare_bounds(first_object, second_object)
                   if distanceMin <= maxDistance and distanceMax <= maxDistance:
                         delenda.append(second_object)

Which raises a point about the algorithm: I think that as written you’re checking each pair of objects twice, since every item is checked against every other in the second loop, meaning you’ll try both A->B and B->A.

There are a couple of ways around that; the itertools module has a function that produces all the combinations in a list itertools.combinations(all_object_list, 2) would give you all the unique pairs of objects in all_object_list. Or, you could track which pairs have already been tried by using sets:

        delenda = []
        seen = []
        for first_object in all_object_list:
             for second_object in all_object_list:
                   if first_object == second_object:
                        continue
                   if set([first_object, second_object]) in seen:
                       continue
                   else:
                       seen.append(set([first_object, second_object]))
                   dist_min, dist_max = compare_bounds(first_object, second_object)
                   if distanceMin <= maxDistance and distanceMax <= maxDistance:
                         delenda.append(second_object)
1 Like

Keberos got that one mostly right. The obj + 1 in the inner loop keeps that from happening

    for obj in range(len(allObjectsList)):        
        for obj2 in range(obj + 1, len(allObjectsList)):

But now that you mention it, I would probably only loop to the second-to-last item in the outer loop as I show below. The original way still works because the range in the inner loop would be empty for the last outer loop, but “Explicit is better than implicit”, so I would still make the change.

    for obj in range(len(allObjectsList) - 1):
        for obj2 in range(obj + 1, len(allObjectsList)):

Oh and Keberos, In case you don’t know why I quoted “Explicit is better than implicit”. Go to a python interpreter and run import this, and it prints out a paragraph called “The Zen of Python”, which is just a list of loose guidelines and ideals that the python community likes. You can take it or leave it. But I like it, and “Explicit is better than implicit” is one of those.

As a style thing, I’d suggest breaking the big main function up a bit. For example you’re doing this inline

I thought about it but my mind was on another thing (like the progressBar).
It’s true though that the readability of this part isn’t perfect. Even I have a hard time reading it :smiley:

There are a couple of ways around that; the itertools module has a function that produces all the combinations in a list itertools.combinations(all_object_list, 2) would give you all the unique pairs of objects in all_object_list .

I read about these but I wanted to have more experience with the basic and I didn’t want to import other module.
This is why I created this:

sqrt( (x1 - x2)² + (y1 - y2)² + (z1 - z2)²)

and didn’t import the math module which would have help me get the distance between 2 points in space.

Or, you could track which pairs have already been tried by using sets: didn’t know about sets.

I didn’t know about set. I’ll have to look it up, Thanks

Oh and Keberos, In case you don’t know why I quoted “Explicit is better than implicit”. Go to a python interpreter and run import this, and it prints out a paragraph called “The Zen of Python”, which is just a list of loose guidelines and ideals that the python community likes. You can take it or leave it. But I like it, and “Explicit is better than implicit” is one of those.

Another things I’ll have to look it up :smiley:

Every tutorials (and I guess everyone in general) tell that it’s good to write code that can be reuse in the future.
But how much do you reuse your code ? And how do you reuse it ?
you call the function directly and copy/paste it ?

Is there an easy way do make my code run faster ?
I made another script who check if a face has another face on top of it. (just like my script here that removes duplicated mesh)
But it’s really slow. Even when there are no more than 1000 faces.

I surely is on another level of skills that I don’t have right now but I was wondering if there was a method I didn’t know who could help me.

Thank you for your answers !

I reuse code pretty constantly.

The idea that I like to follow is that you’ve got 2 types of code, you’ve got library code, and you’ve got tool code.

Library code is stuff that you use all over the place, tool code is stuff that is used for a specific tool, and isn’t intended to be used elsewhere.

Basically for library code you write all of your functions / classes in a module and then in your tool’s code you’d do:

from my_libs.math.helpers import distance, square_distance
dist = dist(pnt1, pnt2)

A good rule of thumb is that most UI related code is probably going to end up as tool code.
You might have a generic base dialog that you build everything on top of that comes out of a shared library, but each specific UI is generally pretty uniquely built for the purpose at hand.

Also one way to identify when code is better suited as library code is simply checking with yourself during each tool “am I writing something I’ve done before?” if yes, go back and refactor the original into some shared library, and now you’ll never have to write that again. (baring bugs and/or slight tweaks when you realize that get_selected_objects doesn’t always mean ALL the selected objects, because nothing is ever simple)

1 Like

I see. It’s a bit clearer now. Thank you agin bob.w
I think with more practice and I’ll understand better how much I can reuse my code and how to organize it.

Regarding UI, all the UI I have done are really simple and use maya commands.
It is basic but it works.
The script I have in mind would be more complicated and require something more dynamic. Something like a grid that has a lot of different background color.
But I think that would be impossible with the maya commands UI and I would need to learn a UI module like Qt.
Do you think learning Qt now is something I should do ? Or am I missing other module or maybe I am skipping steps ?
I don’t want to skip something important but it’s also important to work on something I find fun ^^

My personal bias is that it’s better to invest the time in learning maya and to keep the UI simple and functional until you really need more. the built in maya.cmds UI is very basic but it’s enough for most cases; if you’re still learning it’s probably better to learn the Maya side first before tackling an entire big new API.

Complete agree.

One thing to keep in mind is that the majority of the tools that ship with Maya are written with mel. And yes, the whole reason we exist as a profession is because often those tools are lacking, but on the other hand a whole lot can get done with that very simple toolkit.

Start small and simple if you can, and don’t stress yourself on making your first couple of tools super generic and extensible. In a few months you’ll probably realize better ways to build them, and a few months after that even better ways. Such is the process of learning.

1 Like

Thank you all for your advice. I’ll keep that in mind !

To get back on my first question :
Anyone knows how to connect correctly a progressBar ? :slight_smile:

Hello again :slightly_smiling_face:

I kept myself busy these days by creating new scripts.
I am trying to create an atlas generator.
I created a version where all the textures are baked with the same ratio.
This version works well.

And now I am trying to create an atlas generator who can accept 3 differents ratio.
And…It’s hard. I think I can see the end of this script but I have an error and I don’t know what it means or how to correct it.
The error :

# Error: TypeError: file <string> line 2: Object [[u'pCube44', u'pCube40', u'pCube41', u'pCube42'], [u'pCube43', u'pCube35', u'pCube34', u'pCube37'], [u'pCube36', u'pCube31', u'pCube30', u'pCube33'], [u'pCube32', u'pCube39', u'pCube38', u'pCube13']] is invalid #   

I am using the u3dlayout to create the atlas by only changing the scale of the object’s UVs and keeping a power of 2 ratio of each texture on the generated one.

How can I fix this error ?
Thank you

Can we get the full traceback? Make sure in the script editor “History” menu, that “Show Stack Trace” is checked. Then send the whole traceback that starts with the line Traceback (most recent call last):

That said, What I’m guessing you want to pass to the function is a flat list-of-strings. What you’re passing is a nested list-of-lists-of-strings There’s a couple ways to deal with that.


The way I’d probably do it:
You’re probably .append()-ing to that list somewhere. You should .extend() instead.


Another way:
You could flatten that list after the fact by using itertools.chain()
https://docs.python.org/2/library/itertools.html#itertools.chain

import itertools
flatList = list(itertools.chain.from_iterable(nestedList))

Here is the full traceback:

Traceback (most recent call last):
#   File "C:/Users/Yann/Documents/maya/2019/scripts\atlas.py", line 132, in generateRatio   
#     atlasRatio(atlas_final)
#   File "C:/Users/Yann/Documents/maya/2019/scripts\atlas.py", line 74, in atlasRatio
#     atlas(obj2)
#   File "C:/Users/Yann/Documents/maya/2019/scripts\atlas.py", line 99, in atlas
#     cmds.u3dLayout(obj, resolution = 32, box = (x_min, x_max, y_min, y_max), translate = False)
#   File "<string>", line 2, in u3dLayout
# TypeError: Object [u'pCube19', [u'pCube12', u'pCube11', u'pCube10', u'pCube7'], [u'pCube6',         
u'pCube5', u'pCube9', u'pCube8']] is invalid # 

I, indeed, pass a list in a list in a list.
But it works for the list in a list. I don’t understand why it works for a nested list but not for a nested list in a list. :confused:

I’ll try to flatten the list and see if it works !

EDIT : I tried your solution to flatten the nested list. It works.
But it does work the way I want to.

What I do in my script is this:
I select all the objects.
I calculate their ratio based on their respective area and round them up and I only keep 3 differents ratio (ratio 1, ratio 0.5 and ratio 0.25)
I place all the objects with the ratio 0.25 in the UVs 4 by 4 (4 ratio 0.25 equal 1 ratio 0.5) and add them in a list (1st list)
I place all the objects with the ratio 0.5 + the previous list also 4 by 4 (so all the object have their proper ratio when placed in the UVs) and add each objects (or list of objects) inside another list
I place all the objects with the ratio 1 (only the ratio 1)

I create a list of the ratio 1 + the list of the 0.5 ratio (that have the objects with the ratio 0.5 + the list of the ratio 0.25)
And this is this last list that doesn’t work :confused:

If needed I can post my entire code

Yep, post the code.

(Huh, Apparently there’s a minimum post length)

I think I understand why there is an error.
As you said, I have to flatten my list.

I’ll try to fix it myself first and if I can’t, I’ll post my code!

I understood what was wrong with my code and I managed to fix it. But my script was a mess. Especially how I managed the ratio between all the objets. So I decided to start over…
I hope it’ll be easier now that I’ve already done it once.

I have a quick question about another part of my script :

I use the “surfaceSampler” command to bake the objects. But I have an error when I run my script (something about not being able to write a file).
If I run maya in administrator mode, the error doesn’t exist. Is there a way to avoid loading maya in admin mode and being able to run the surfaceSampler ?

EDIT : Ok I know what was wrong. I tried to write a file in C:
If I try to save in the Documents folder, it works even if I don’t run maya in admin mode…

Hello !
As I said in my previous post, I started over my script for creating atlas.
Now I have a script that can create atlas either with same or different ratio.
And it bakes everything with the surfaceSampler. It can be slow but, in the end, it works.

( I’m wondering if I can create my atlasmap without the surfaceSampler by just resizing, moving and mixing all the textures used by the objects with code. That way, there will no render time. But I guess, if I want to do this, I’ll have to import a new python module… I am not quite there yet :smiley: )

Now that I can bake my diffuseMap, I want to bake my normalMap, specular map etc…
So, in my script, I changed the name of the texture call by the file node. (brick_DiffuseMap.tga will become brick_NormalMap.tga)
But…
I can’t find a way to check if the new texture exists. (and if not, I’ll put a default color or texture)
I tried to use the filePathEditor. But it returns a list of all the textures the scene.
Is there a command that I don’t know who can do what I want ?

Thank you :slight_smile:

EDIT: With the filePathEditor I got a list of the unresolved texture. But not their absolute path. And that’s a problem :confused: