top of page

Stack builder code

Stacker.py

#!/usr/bin/env python

#SETMODE 777

#----------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------ HEADER --#

"""

:author: sxl170007

 

:synopsis: Methods for stacking three objects on top of each other

 

:description: The stack_objs function accepts three objects' transform nodes, calls the verify_args method to check that they are valid, then calls the get_center_point function to get the center coordinates of a face of the object. Using this information, it calls the create stack method to move an object to the coordinates that it should be at.

 

:applications: Maya

 

:see_also: n/a

"""

#----------------------------------------------------------------------------------------#

#----------------------------------------------------------------------------- IMPORTS --#

 

# Default Python Imports

from PySide2 import QtGui, QtWidgets

from maya import OpenMayaUI as omui

from shiboken2 import wrapInstance

import maya.cmds as cmds

import xml.etree.ElementTree as et

from xml.dom import minidom

 

#----------------------------------------------------------------------------------------#

#--------------------------------------------------------------------------- FUNCTIONS --#

 

def offset_objs_in_x(no_move, move, offset):

"""

Uses the xform command to get the bounding boxes of the two objects passed in. Moves the second object along the 'x' axis. The corresponding bounding box edges are separated by the value provided. Reference the video to see how this looks.

"""

   #cmds.select(no_move)

   #bound1 = cmds.xform(boundingBox=True, query=True)

   #x1 = bound1[3]

   #cmds.select(move)

   #bound2 = cmds.xform(boundingBox=True, query=True)

   #x2 = bound2[0]

 

def stack_objs(objects):

"""

Stacks the objects given in a list.

:param list: List of the transform nodes for the objects

:type list

:return: success of the operation

:type bool

"""

   #this module 's functionality moved to within make_stacks

   sys.stdout.flush()

   print len(objects)

   if (verify_args(objects) == None):

   print "Incorrect arguments entered. "

   return None

   #moved to make_stacks

   return True

 

def create_stack(object_to_move, bottom_center, move_here):

"""

Moves given object to the appropriate location.

:param object_to_move: Name of the transform node of the object to be moved.

:type str :param bottom_center: Location of the bottom center of the object to be moved

:type list :param move_here: Location of where the bottom center of the object should be moved.

:type list :return: success of the operation

:type bool

"""

   x_diff = move_here[0] - bottom_center[0]

   y_diff = move_here[1] - bottom_center[1]

   z_diff = move_here[2] - bottom_center[2]

   cmds.select(object_to_move)

   cmds.move(x_diff, y_diff, z_diff, relative=True)

   return True

 

def get_center_point(object_name, top_center, bottom_center):

"""

Calculates and returns the center point of the top or bottom of an object

:param object_name: Name of the transform node of the object

:type str

:param top_center: If we are looking for the top center

:type boolean

:param bottom_center: If we are looking for the bottom center

:type boolean

:return: the center point coordinates of the object's top or bottom face

:type list

"""

   cmds.select(object_name)

   bounds = cmds.xform(boundingBox=True, query=True)

   x_center = (bounds[0] + bounds[3])/2

   z_center = (bounds[2] + bounds[5])/2

   if (top_center):

      y_center = bounds[4]

   else:

      y_center = bounds[1]

      center_point = [x_center, y_center, z_center]

      return center_point

 

def verify_args(list):

"""

Checks the validity of the three transform nodes.

:param list: List of transform nodes

:type list

:return: If all of the objects are valid

:type boolean

"""

   all_true = True

   for object in list:

      if (all_true and object):

          all_true = True

      else:

         all_true = False

   return all_true

 

#----------------------------------------------------------------------------------------#

#----------------------------------------------------------------------------- CLASSES --#

builder_Gui.py

#!/usr/bin/env python

#SETMODE 777

 

#----------------------------------------------------------------------------------------#

#------------------------------------------------------------------------------ HEADER --#

​

"""

:author: sxl170007

:synopsis: This module constructs a gui to facilitate stacking of objects.

:description: This module constructs a gui that allows the user to create three objects stacked, by allowing the user to select objects to be the bottom object, middle object, and then top object. Once the user presses the "Make Object" button, the code randomly selects an object from each of the groups, and constructs stacks in the number that the user entered in the gui with the separation value the user entered.

:applications: Maya

:see_also: Code snippets come from class_11, class_10, and class_9

"""

​

#----------------------------------------------------------------------------------------#

#----------------------------------------------------------------------------- IMPORTS --#

​

# Default Python Imports

from PySide2 import QtGui, QtWidgets

from maya import OpenMayaUI as omui

from shiboken2 import wrapInstance

import maya.cmds as cmds

import random

import xml.etree.ElementTree as et

from xml.dom import minidom

 

# Imports That You Wrote

from td_maya_tools.stacker

import stack_objs from td_maya_tools.stacker

import create_stack from td_maya_tools.stacker

import get_center_point from td_maya_tools.gen_utils

import read_stack_xml

​

#----------------------------------------------------------------------------------------#

#--------------------------------------------------------------------------- FUNCTIONS --#

 

def get_maya_window():

"""

This gets a pointer to the Maya window.

:return: A pointer to the Maya window.

:type: pointer

"""

   maya_main_window_ptr = omui.MQtUtil.mainWindow()

   return wrapInstance(long(maya_main_window_ptr), QtWidgets.QWidget)

​

#----------------------------------------------------------------------------------------#

#----------------------------------------------------------------------------- CLASSES --#

 

class BuilderGUI (QtWidgets.QDialog):

"""

A gui to allow the user to stack objects.

"""

 

def __init__(self):

"""

Declares all the variables that will be necessary for the GUI to function as expected.

"""

   QtWidgets.QDialog.__init__(self, parent=get_maya_window())

   self.le_01 = None

   self.le_02 = None

   self.le_03 = None

   self.countSpinBox = None

   self.heightSpinBox = None

   self.sepSpinBox = None

   self.tree_view = None

   self.baseObjs = []

   self.midObjs = []

   self.topObjs = []

​

def init_gui(self):

"""

Creates the gui to allow the user to stack the objects.

"""

   main_vb = QtWidgets.QVBoxLayout(self)

   hb_row1 = QtWidgets.QHBoxLayout()

   inputs = self.make_options_layout()

   hb_row1.addLayout(inputs)

   self.tree_view = QtWidgets.QTreeWidget()

   self.tree_view.setHeaderLabel('Object Stacks')

   self.tree_view.setAlternatingRowColors(True)

   self.tree_view.itemClicked.connect(self.tree_item_clicked)

   hb_row1.addWidget(self.tree_view)

   hb_row2 = QtWidgets.QHBoxLayout()

   btn_01 = QtWidgets.QPushButton('Load XML')

   btn_02 = QtWidgets.QPushButton('Make Stacks')

   btn_03 = QtWidgets.QPushButton('Cancel')

   btn_01.clicked.connect(self.apply_xml)

   btn_02.clicked.connect(self.make_stacks)

   btn_03.clicked.connect(self.close)

   hb_row2.addWidget(btn_01)

   hb_row2.addWidget(btn_02)

   hb_row2.addWidget(btn_03)

   main_vb.addLayout(hb_row1)

   main_vb.addLayout(hb_row2)

   # This stuff is for the dialog window.

   self.setGeometry(300, 300, 250, 150)

   self.setWindowTitle('Builder')

   self.show()

​

def make_options_layout(self):

"""

Creates a QForm layout for the button, line edit, and spinbox section of the gui.

:return: QFormLayout : The created layout

"""

   top_button = QtWidgets.QPushButton('Set Top Parts')

   top_button.setObjectName('btn_01')

   top_button.clicked.connect(self.set_selection)

   self.le_01 = QtWidgets.QLineEdit()

   self.le_01.setEnabled(False)

   mid_button = QtWidgets.QPushButton('Set Mid Parts')

   mid_button.setObjectName('btn_02')

   mid_button.clicked.connect(self.set_selection)

   self.le_02 = QtWidgets.QLineEdit()

   self.le_02.setEnabled(False)

   base_button = QtWidgets.QPushButton('Set Base Parts')

   base_button.setObjectName('btn_03')

   base_button.clicked.connect(self.set_selection)

   self.le_03 = QtWidgets.QLineEdit()

   self.le_03.setEnabled(False)

   label_01 = QtWidgets.QLabel()

   label_01.setText("Set Stack Count")

   label_02 = QtWidgets.QLabel()

   label_02.setText("Set Max Height")

   label_03 = QtWidgets.QLabel()

   label_03.setText("Set Separation")

   self.countSpinBox = QtWidgets.QSpinBox()

   self.countSpinBox.setMinimum(0)

   self.countSpinBox.setSingleStep(1)

   self.countSpinBox.setValue(3)

   self.heightSpinBox = QtWidgets.QSpinBox()

   self.heightSpinBox.setRange(0, 6)

   self.heightSpinBox.setSingleStep(1)

   self.heightSpinBox.setValue(3)

   self.sepSpinBox = QtWidgets.QDoubleSpinBox()

   self.sepSpinBox.setMinimum(0.0)

   self.sepSpinBox.setSingleStep(0.1)

   self.sepSpinBox.setValue(0.1)

   formLayout = QtWidgets.QFormLayout()

   formLayout.addRow(top_button, self.le_01)

   formLayout.addRow(mid_button, self.le_02)

   formLayout.addRow(base_button, self.le_03)

   formLayout.addRow(label_01, self.countSpinBox)

   formLayout.addRow(label_02, self.heightSpinBox)

   formLayout.addRow(label_03, self.sepSpinBox)

   return formLayout

 

def set_selection(self):

"""

Populates each of the line edits with the number of currently selected objects and gives a green background once each of the buttons is pressed.

:return: boolean : success of the operation

"""

   # Use the sender to see which button got us here.

   sender = self.sender()

   if sender:

      obj_name = str(sender.objectName())

      sel = cmds.ls(selection=True, type='transform')

      num = len(sel)

   if not sel: #ensures content in sel

      return None

   if (obj_name == "btn_01"):

      self.le_01.setText("%s objects" %num)

      self.topObjs = sel

      self.le_01.setStyleSheet("background: rgb(100, 255, 150);")

   if (obj_name == "btn_02"):

      self.le_02.setText("%s objects" %num)

      self.midObjs = sel

      self.le_02.setStyleSheet("background: rgb(100, 255, 150);")

   if (obj_name == "btn_03"):

      self.le_03.setText("%s objects" %num)

      self.baseObjs = sel

      self.le_03.setStyleSheet("background: rgb(100, 255, 150);")

 

def make_stacks(self):

"""

Randomly selects the object from each of the 3 categories and constructs the stack at the origin. Also groups and names the objects in consecutive order. Also takes the functionality of create stacks because my function was struggling.

:return: boolean : success of the operation

"""

   if not self.verify_args():

      return None

   num_to_make = self.countSpinBox.text()

   for i in range(int(num_to_make)):

      list_of_objs = [] #loops the number of stacks to be created

      base_obj = random.choice(self.baseObjs)

      base_obj = cmds.duplicate(base_obj)

      list_of_objs.append(base_obj)

      cmds.move(0, 0, 0, ".scalePivot", ".rotatePivot", absolute=True)

      cmds.move(0, 1, 0, base_obj, absolute=True)

   for j in range(int(self.heightSpinBox.text())-2):

      mid_obj = random.choice(self.midObjs)

      mid_obj = cmds.duplicate(mid_obj)

      list_of_objs.append(mid_obj)

      top_obj = random.choice(self.topObjs)

      top_obj = cmds.duplicate(top_obj)

      list_of_objs.append(top_obj)

   for cnt in range(len(list_of_objs) - 1):

      base_top_center = get_center_point(list_of_objs[cnt], True, False)

      mid_bottom_center = get_center_point(list_of_objs[cnt + 1], False, True)

      create_stack(list_of_objs[cnt + 1], mid_bottom_center, base_top_center) # moves mid to on top of base

   #stack_objs(list_of_objs)

   count = i + 1 #starts at 1 instead of 0

   cmds.group(empty = True, name ="stack" + "{0:0=3d}".format(count))

   for obj in list_of_objs:

      cmds.parent(obj, "stack" + "{0:0=3d}".format(count))

   self.add_stack_to_tree_view("stack" + "{0:0=3d}".format(count), list_of_objs)

​

return True

 

def verify_args(self):

"""

Checks the GUI to make sure it has all the information it needs, and sends errors to the user if all the information is not entered. :return: boolean : success of the operation

"""

   if (self.le_01.text() == ""):

      self.warn_user("Selection", "You must set a selection for the top parts.")

      return None

   if (self.le_02.text() == ""):

      self.warn_user("Selection", "You must set a selection for the middle parts.")

      return None

   if (self.le_03.text() == ""):

      self.warn_user("Selection", "You must set a selection for the base parts.")

      return None

   if (int(self.countSpinBox.text()) < 1):

      self.warn_user("Count", "You must make at least one stack.")

   if (int(self.heightSpinBox.text()) < 3):

      self.warn_user("Count", "Height must be a value from 3 to 6.")

      return None

   if (float(self.sepSpinBox.text()) < 0.1):

      self.warn_user("Count", "The distance must be greater than 0.00.")

   return None

return True

​

def add_stack_to_tree_view(self, name, objects):

"""

Adds stack and objects to the tree view

:param: name: name of the stack

:type str

:param: objects: group of objects

:type: list

:return:

"""

   root = QtWidgets.QTreeWidgetItem(self.tree_view, [name])

   for stack_obj in objects:

      stack = QtWidgets.QTreeWidgetItem(root, stack_obj)

 

def apply_xml(self):

"""

Moves existing objects to the places listed in selected xml file.

:return: bool : success of the operation

"""

   file_path, ffilter = \ QtWidgets.QFileDialog.getOpenFileName(caption='Open File',dir='C:/Users', filter='XML Files (*.xml)')

   if not file_path:

      self.warn_user("File Path", "File path is not valid.")

      return None

   dictionary = read_stack_xml(file_path)

   #apply values to the stacks

   count = 0

   while ind < len(dictionary):

      if (dictionary[ind].startsWith("stack")):

         transxline = dictionary[ind + 1].substring(10)

         transx = transxline.substring(0, indexOf('\"'))

         transyline = dictionary[ind + 2].substring(10)

         transy = transxline.substring(0, indexOf('\"'))

         transzline = dictionary[ind + 3].substring(10)

         transz = transxline.substring(0, indexOf('\"'))

      else:

         cmds.setAttr(dictionary[ind] + '.tx', transx)

         cmds.setAttr(dictionary[ind] + '.ty', transx)

         cmds.setAttr(dictionary[ind] + '.tz', transx)

   return True

 

def tree_item_clicked(self, it, col):

"""

Selects the object or stack that was clicked.

:return:

"""

   cmds.select(it.text(col))

 

def warn_user(self, title, message):

"""

This method displays a warning to the user.

:param title: The title of the message box.

:type: str

:param message: A message for the user.

:type: str

"""

   message_box = QtWidgets.QMessageBox()

   message_box.setText(message)

   message_box.setWindowTitle(title)

   message_box.exec_()

bottom of page