Search code examples
pythontrigonometrymaya

duplicate objects with trig function translations in Maya + Python


I'm trying to create a hexagonal array of spheres around a center sphere in the XY plane using a python loop function (couldn't figure out how to do it using duplicate special). It should end up looking something like this:

  0 0
0  0  0
  0 0    

Here's my code. I' getting a syntax error

# Error: line 1: invalid syntax # 

when I call it, though I'm pretty sure there's nothing wrong with line one.

import maya.cmds as cmds

class Sphere(radius, tx=0, ty=0, tz=0, sx=0, sy=0, sz=0):
    self.diameter = 2*radius

def createSphere(radius, tx, ty):
    newSphere = Sphere(radius=radius, tx=tx, ty=ty)
    return newSphere

def duplicateSphere(wholeSphere):
    for i in range(6, 1, -1):
        createSphere(tx=wholeSphere.diameter*math.cos(2*math.pi/i), ty=wholeSphere.diameter*math.sin(2*math.pi/i))
        # create spheres with projections onto x and y axes as translation params

duplicateSphere(createSphere(1.03))

Any ideas as to what's going on?


Solution

  • Ok, first off to answer your question, the SyntaxError is caused by improper class instantiation. class declaration must be separated from the constructor method in python, like so:

    class Sphere(object):
        def __init__(self, radius, tx=0, ty=0, tz=0, sx=0, sy=0, sz=0):
            self.diameter = 2 * radius
    

    Tip: In the maya script editor panel, if you enable History->Show Stack Trace it will give you a better idea of where the actual error is occurring.

    However, there are a couple other issues at play. For one, you are never storing the parameters you pass into the sphere class (except radius, which you are storing implicitly by storing the diameter). You probably wanted:

    class Sphere(object):
        def __init__(self, radius, tx=0, ty=0, tz=0, sx=1, sy=1, sz=1):
            self.radius = radius
            self.tx = tx
            self.ty = ty
            self.tz = tz
            self.sx = sx
            self.sy = sy
            self.sz = sz
            self.diameter = 2 * radius
    

    I changed the scale defaults to 1 instead of 0 so that the default sphere is not invisibly small.

    Also, as theodox pointed out, you have a TypeError in your createSphere method, which takes 3 params (none of them are keyword arguments currently and therefore not optional) and you are only passing in 1.

    However, the main issue is that currently you are not actually creating any spheres in maya. If the intention is that your sphere object is an object-oriented wrapper around maya.cmds, you will need it to call cmds.sphere somewhere, and you will probably want to cmds.move() and cmds.scale() it to by your t* and s* values. If you do all this in the constructor, you could actually then avoid setting instance variables for all the sphere properties if you wanted.

    This would look something like this:

    cmds.sphere(radius=self.radius)
    cmds.move(self.tx,self.ty,self.tz)
    cmds.scale(self.sx,self.sy,self.sz)
    

    Finally, I think your trig is a bit off (you want each iteration to vary by exactly 60° or π/3 radians). To get the proper angles, I think you want something more along the lines of:

    import math
    
    for i in range(6,0,-1):
        angle = math.pi * i / 3.0
        distance = wholeSphere.diameter
        tx = distance * math.cos(angle)
        ty = distance * math.sin(angle)
        # ...
    

    As a last note, consider looking at an object-oriented solution like pymel to avoid needing to reinvent the wheel in terms of wrapping maya.cmds commands in objects.

    Anyways, applying all these corrections produces something like:

    import maya.cmds as cmds
    import math
    
    class Sphere(object):
        def __init__(self, radius, tx=0, ty=0, tz=0, sx=1, sy=1, sz=1):
            self.diameter = 2*radius
            self.radius = radius
            self.tx = tx
            self.ty = ty
            self.tz = tz
            self.sx = sx
            self.sy = sy
            self.sz = sz
    
            cmds.sphere(radius=self.radius)
            cmds.move(self.tx,self.ty,self.tz)
            cmds.scale(self.sx,self.sy,self.sz)
    
    def createSphere(radius, tx=0, ty=0):
        newSphere = Sphere(radius, tx=tx, ty=ty)
        return newSphere
    
    def duplicateSphere(wholeSphere):
        for i in range(6, 0, -1):
            angle = math.pi * i / 3.0
            distance = wholeSphere.diameter
            tx = distance * math.cos(angle)
            ty = distance * math.sin(angle)
            createSphere(wholeSphere.radius, tx=tx, ty=ty)
    
    duplicateSphere(createSphere(1.03))