Sunday, 17 July 2016

Face_Hugster

Github link

So taking inspiration from the Object Planter script, I decided to expand the it into something more general. The original script only allows for spheres to be planted, but Face_Hugster allows for all sorts of geometries to be planted. I can put pre-generated mesh, or other primitives, as duplicates or as instances onto other surfaces. There's also a save selection function that allows the user to save their selection into a text file that they can retrieve later, even if this session of maya is closed.

What I can say though, is that usability wise, this way more clunky. If a tool can't be used intuitively and easily, with easy incorporation into an artist's workflow, then it's not going to be used by the artist, simple as that.

It's something to really think about.

Re publishing the code here:

----------------------------
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as OpenMaya
import os, string, math, re
from os import path, listdir, rename
from string import Template, zfill
from functools import partial

FACE_CENTER_DICT={}
SAVE_SELECTION_DICT={}

class Face_Center:
    def __init__(self):
        # This finds the face center of each face and uploads data to the global dictionary
        face_center = []

        selection = OpenMaya.MSelectionList()
        OpenMaya.MGlobal.getActiveSelectionList(selection)

        iter_sel = OpenMaya.MItSelectionList (selection, OpenMaya.MFn.kMeshPolygonComponent)

        while not iter_sel.isDone():
            dag_path = OpenMaya.MDagPath()
            component = OpenMaya.MObject()

            iter_sel.getDagPath(dag_path, component)

            poly_iter = OpenMaya.MItMeshPolygon(dag_path, component)

            while not poly_iter.isDone():
                # enumerates the faces in selection
                i = 0
                i = poly_iter.index()
                face_info = ("face %s" %i)
             
                # finds the face center of enumerated face              
                center = OpenMaya.MPoint
                center = poly_iter.center(OpenMaya.MSpace.kWorld)
                point = [0.0,0.0,0.0]
                point[0] = center.x
                point[1] = center.y
                point[2] = center.z
                face_center = point
             
                # uploads face to global dictionary
                FACE_CENTER_DICT.update({face_info:face_center})
             
                #goes to next face
                poly_iter.next()
             
            iter_sel.next()
                 
class Save_To_File:
    def __init__(self, dir_path, maya_file, write_file_name, sel_list):
        ##specify file directory file name to be saved      
        self.dir_path = dir_path
        self.maya_file = maya_file    
        self.file_name = write_file_name
     
        ##selection to be saved
        self.sel_list = sel_list      
        to_write_list = []
     
        for dummy_selection in self.sel_list:          
            for dummy_key, dummy_val in SAVE_SELECTION_DICT.iteritems():
                if dummy_selection == dummy_key:
                    ##format setup
                    dummy_selection_name = dummy_selection + ' = '
                    dummy_base_template = dummy_selection_name + ' _placeholder_ '
                    ##the string that joins the individual values in the list together
                    dummy_line_join = ', '
                    ##the join function
                    dummy_list_to_text = dummy_line_join.join(dummy_val)
                    ##generating the text to be written
                    if '_placeholder_' in dummy_base_template:
                        dummy_print_to_text = dummy_base_template.replace('_placeholder_', dummy_list_to_text)
                        to_write_list.append(dummy_print_to_text+'\r\n')
        ##writing to file
        file_open = open(self.dir_path+ '\\' + self.file_name, 'w+')
        file_open.writelines(to_write_list)
        file_open.close()
                         

class Mass_Planter:
 
    def __init__(self, root_obj_name, sel_name, run_option):
        # this function runs the script
        face_sel = cmds.ls(sl=True, fl=True) # makes a selection from the UI
        Face_Center()# passes the selection into Face_Center class to find centers of faces in face_sel
        self.run_option = run_option
        self.sel_name = sel_name
        self.dupe_geo_list = []
        for dummy_item in face_sel:                      
            for key, val in FACE_CENTER_DICT.iteritems(): # loops through global dict to find relevant coordinates
                if self.run_option == 'Instance':
                    dummy_new_geo = cmds.instance(root_obj_name)
                elif self.run_option == 'Duplicate':
                    dummy_new_geo = cmds.duplicate(root_obj_name)
                cmds.setAttr( str(dummy_new_geo[0])+'.translateX', val[0] )
                cmds.setAttr( str(dummy_new_geo[0])+'.translateY', val[1] )
                cmds.setAttr( str(dummy_new_geo[0])+'.translateZ', val[2] )
                dummy_constr = cmds.normalConstraint(dummy_item, str(dummy_new_geo[0]), aimVector = (0,1,0), u = (0,1,0), worldUpType= 0, wu = (0, 1, 0))
                cmds.delete(dummy_constr)
                self.dupe_geo_list.append(dummy_new_geo)
            cmds.setAttr( str(root_obj_name)+'.translateX', 0 )
            cmds.setAttr( str(root_obj_name)+'.translateY', 0 )
            cmds.setAttr( str(root_obj_name)+'.translateZ', 0 )
            cmds.setAttr( str(root_obj_name)+'.rotateX', 0 )
            cmds.setAttr( str(root_obj_name)+'.rotateY', 0 )
            cmds.setAttr( str(root_obj_name)+'.rotateZ', 0 )            
            FACE_CENTER_DICT.clear() # memory management purposes
        self.sel_name_grp=cmds.group( em=True, name=self.sel_name ) # creates a group to house the duplicated geometries
        for dummy_geo in self.dupe_geo_list:
            cmds.parent( dummy_geo, str(self.sel_name_grp) )              


class Mass_Planter_UI:
    def __init__(self, *args):
        # UI for script
        self.run_option = 'Duplicate'
        self.bin_loc = None
        self.bin_file = None
        window = cmds.window( title="Face Hugster", iconName='FceHug', w=280, h=220, s = True )
        cmds.columnLayout( adjustableColumn=True, rowSpacing=10)
             
        cmds.frameLayout( label='Selection Manager', borderStyle='in', cll=True, cl=True )
        cmds.text( label='Save Your Selection:', align='left' )
        self.scroll_box=cmds.textScrollList( numberOfRows=6, w = 250, h = 80, selectCommand=partial(self.select_from_list, 1),
                deleteKeyCommand = partial(self.delete_selection, 1) )
        cmds.button( label='Save Selection', command=partial(self.save_selection, 1) )
        cmds.button( label='Clear All Selection', command=partial(self.clear_all_selection, 1) )
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )
     
        cmds.frameLayout( label='Save And Load To File', borderStyle='in', cll=True, cl=True )
        cmds.text( label='', align='left' )
        cmds.button( label='Save Selection To File', command=partial(self.save_to_file, 1) )
        cmds.button( label='Load Selection', command=partial(self.load_file, 1) )
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )
     
        cmds.frameLayout( label='Duplicate Geometry From Selection', borderStyle='in', cll=True, cl=True )
        cmds.text( label='', align='left' )      
        cmds.button( label='Duplicate Selection', command=partial(self.duplicate_selection, 1) )
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )      

        cmds.frameLayout( label='Palette Manager', borderStyle='in', cll=True, cl=True )      
        cmds.text( label='Add Geometry to palette:', align='left' )
        self.palette_box=cmds.textScrollList( numberOfRows=6, w = 250, h = 80, selectCommand=partial(self.select_from_palette, 1),
                deleteKeyCommand = partial(self.delete_from_palette, 1) )
        cmds.button( label='Set To Palette', command=partial(self.set_palette_obj, 1) )
        cmds.button( label='Clear Palette', command=partial(self.clear_palette_obj, 1) )
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )        

        cmds.frameLayout( label='Run Face Hugster', borderStyle='in', cll=True, cl=True )      
        cmds.text( label='Run Command:', align='left' )
        cmds.radioButtonGrp( label='Duplicate Type:', labelArray2=['Instance', 'Copy'],
                numberOfRadioButtons=2, cw3 =[85, 85, 85], cal = [1, 'left'], on1=partial(self.option_instance, 1)
                                , on2=partial(self.option_copy, 1) )  
        cmds.button( label='Run', command=partial(self.run_command, 1) )
        cmds.separator( style='single' )      
        cmds.setParent( '..' )
        cmds.setParent( '..' )        


        cmds.frameLayout( label='Rotation Constrain', borderStyle='in', cll=True, cl=True )      
        cmds.text( label='Constrains selected geometries to locator:', align='left' )
        cmds.button( label='Constrain geometry', command=partial(self.constrain_geometry, 1) )
        cmds.button( label='Rehook Constrain', command=partial(self.rehook_constrain, 1) )
        cmds.button( label='Break Constrain', command=partial(self.break_constrain, 1) )
        cmds.separator( style='single' )      
        cmds.setParent( '..' )
        cmds.setParent( '..' )        
     
     
        cmds.setParent( '..' )
        cmds.showWindow( window )

    def save_selection(self, *args):
        # make save selection ala 3DS Max. Can save other selection types such as edges or vertices
        sel_list = cmds.ls(sl=True, fl=True)
        selection_name = str(raw_input('Selection Name: '))
        SAVE_SELECTION_DICT[selection_name] = sel_list
        cmds.textScrollList(self.scroll_box, edit=True, append=selection_name)
     
    def select_from_list(self, *args):
        # runs the cmds.select command for the core script to run
        list_name = cmds.textScrollList(self.scroll_box, query=True, selectItem=True)
        cmds.select(SAVE_SELECTION_DICT[list_name[0]])
 
    def delete_selection(self, *args):
        # deletes selection from list on command
        list_name = cmds.textScrollList(self.scroll_box, query=True, selectItem=True)
        cmds.textScrollList(self.scroll_box, edit=True, ri=list_name[0])
        SAVE_SELECTION_DICT.pop(list_name[0], None)
     
         
    def clear_all_selection(self, *args):
        # memory management. Highly recommended to run this before running another instance of the script or when task is done
        SAVE_SELECTION_DICT.clear()
        cmds.textScrollList(self.scroll_box, edit = True, removeAll=True)
 
    def save_to_file(self, *args):
     
        file_name = cmds.file(q=True, sn=True, shn=True)#maya file name
        file_loc = re.sub(file_name, '', cmds.file(q=True, loc=True))#maya file location
        if not os.path.exists(file_loc + 'FceHug_bin'):
            os.makedirs(file_loc + 'FceHug_bin')
        if not os.path.exists(file_loc + 'FceHug_bin' + '\\Save_Selection_For' + '_' + re.sub('.ma', '',file_name) +'.txt'):
           open(file_loc + 'FceHug_bin' + '\\Save_Selection_For' + '_' + re.sub('.ma', '',file_name) +'.txt', 'w+')
     
        self.bin_loc = file_loc + 'FceHug_bin'
        self.bin_file = 'Save_Selection_For' + '_' + re.sub('.ma', '', file_name) +'.txt'
     
        list_of_selection = cmds.textScrollList(self.scroll_box, query=True, ai=True)
        Save_To_File( self.bin_loc, file_name, self.bin_file, list_of_selection)          
     
    def load_file(self, *args):
        file_name = cmds.file(q=True, sn=True, shn=True)#maya file name
        file_loc = re.sub(file_name, '', cmds.file(q=True, loc=True))#maya file location
        self.bin_loc = file_loc + 'FceHug_bin'
        self.bin_file = 'Save_Selection_For' + '_' + re.sub('.ma', '', file_name) +'.txt'      
        file_open = open( self.bin_loc + '\\' + self.bin_file, 'r')
        content_list = file_open.readlines()
     
        ##loop to analyse and break the txt file and upload to global SAVE_SELECTION_DICT
        for dummy_file in content_list:
            dummy_holding_list = []#for holding face information
            slice_point = dummy_file.find(' = ')
            list_dict_key = dummy_file[:slice_point]#finding variable name/key
            list_dict_val = dummy_file[slice_point+3:].split()#gets the list
            for dummy_item in list_dict_val:
                ## seperating out the junk stuff from the ***.split() action
                if dummy_item == '[' or dummy_item == ']': #the brackets got split too
                    pass
                elif ',' in dummy_item:#all the variables except the last one has a dumbfuck comma behind it
                    dummy_new_item = re.sub(',', '', dummy_item)# removing the comma
                    dummy_holding_list.append(dummy_new_item)
                else:
                    dummy_holding_list.append(dummy_item)
            SAVE_SELECTION_DICT[str(list_dict_key)] = dummy_holding_list #uploading to global dict
            cmds.textScrollList(self.scroll_box, edit=True, append=list_dict_key)
             
    def duplicate_selection(self, *args):
        sel_list = cmds.ls(sl=True, fl = True)
        if len(sel_list) >= 1:
            slice_point = sel_list[0].find('.')
            geo_name = sel_list[0][:int(slice_point)]
        else:
            print 'Need a Selection'
            pass
        duplicate_geo_name = cmds.textScrollList(self.scroll_box, query=True, selectItem=True)
        duplicate_geo = cmds.duplicate(geo_name, n=duplicate_geo_name[0] + '_geo_copy')
     
        duplicate_geo_face_list = []
     
        for dummy_face in sel_list:
            new_face_name=re.sub(geo_name, duplicate_geo[0], dummy_face)
            duplicate_geo_face_list.append(new_face_name)
       
        cmds.select(duplicate_geo_face_list)
        mel.eval('InvertSelection')
        cmds.delete()
 
    def select_from_palette(self, *args):
        # select the target object to be duplicated
        palette_obj_name = cmds.textScrollList(self.palette_box, query=True, selectItem=True)
        return palette_obj_name[0]          
 
    def delete_from_palette(self, *args):
        palette_obj_name = cmds.textScrollList(self.palette_box, query=True, selectItem=True)
        cmds.textScrollList(self.palette_box, edit=True, ri=palette_obj_name[0])
 
    def set_palette_obj(self, *args):
        # puts the target object(s) into a palette for convienient usage
        palette_list = cmds.ls(sl=True, fl=True)
        for dummy_palette_obj in palette_list:
            cmds.textScrollList(self.palette_box, edit=True, append=dummy_palette_obj)
 
    def clear_palette_obj(self, *args):
        # clears the palette of all options
        cmds.textScrollList(self.palette_box, edit=True, ra=True)
         
    def option_instance(self, *args):
        # tells the script to duplicate instances
        self.run_option = 'Instance'
        print self.run_option
     
    def option_copy(self, *args):
        # tells the script to run standard duplication
        self.run_option = 'Duplicate'
        print self.run_option
 
    def constrain_geometry(self, *args):
        run_list = cmds.ls(sl=True, fl=True)
     
        for dummy_list in run_list:
            dummy_geo_list = cmds.listRelatives(dummy_list, c=True)
            dummy_locator = cmds.spaceLocator(n = dummy_list+'_constrain')
            #dummy_logo = cmds.textCurves( f='Times-Roman|h-1|w-400|c0', o = True, t=dummy_list + '_control' )
            #cmds.parent( dummy_logo, dummy_locator, add=True )
            for dummy_geo in dummy_geo_list:
                cmds.orientConstraint( dummy_locator, dummy_geo )
 
    def rehook_constrain(self, *args):
        group_sel = cmds.ls(sl=True, fl=True)
     
        for dummy_group in group_sel:
            locator_name = str(dummy_group) + '_constrain'
            dummy_geo_list = cmds.listRelatives(dummy_group, c=True)
            for dummy_geo in dummy_geo_list:
                cmds.orientConstraint( locator_name, dummy_geo )
         
 
    def break_constrain(self, *args):
        group_sel = cmds.ls(sl=True, fl=True)
     
        for dummy_group in group_sel:
            dummy_geo_list = cmds.listRelatives(dummy_group, c = True) or []
            for dummy_geo in dummy_geo_list:
                attr_list = cmds.listRelatives(dummy_geo) or []
                for attr in attr_list:
                    if 'orientConstraint' in attr:
                        cmds.delete(attr)
             
     

    def run_command(self, *args):
        # runs the core script
        self.list_name = cmds.textScrollList(self.scroll_box, query=True, selectItem=True) # query name list for creating relevant group name
        print 'Planting selection -- ' + self.list_name[0] + ' | with geometry -- ' + str(self.select_from_palette()) #+ ' as ' + self.run_option
        Mass_Planter(self.select_from_palette(), self.list_name[0], self.run_option)
               
def main():

        Mass_Planter_UI()

if __name__=='__main__':
main()


----------------------------

Object Planter






I did some work as an environment artist on this anime movie. However, my main contribution is in writing the tools to create the flashing light bulbs such as in the screen shot. There are thousands of these light bulbs. This movie takes place in a casino themed ship, complete with towns and castles and stuff in the ship, and the light bulbs are everywhere, flashing Vegas style. There were just three in house environment modelers on this job. The lead, me, and another person. Most of the models were done by outsource vendors but the light bulbs had to be done in house.

3 person on this task would be crazy, so I wrote a couple of tools to help manage this. The first was a script which I will call the Object Planter. The task was to stick all these lightbulbs onto all these strips and surfaces you can see in the screen shot. This part is relatively easy. Take the surfaces, divide them evenly, then find the co-ordinates of the face centers of all the faces on the strip you want to plant the lightbulbs on. This can be done by accessing maya API. 

The really tricky part is not this bit at all however. As you might note in the trailer, these light bulb flash in sequence. So in order to do this, the light bulbs themselves had to be put into a logical sequence in the list before this can happen. Here's the problem. The script will generate the light bulbs based on the faces of the target geo, and those faces gets put into a loop list based on their face index. However, during the process of modeling those face index gets messed up. objectFace;1 could be sitting next to objectFace:329 and objectFace:2 can be sitting way over there.  When Maya reads the list however, it will still read in the ordered list. So lightbulb 1 will flash, and then light bulb 2 will flash way over there, and 327 times later, the light bulb besides 1 will flash.

To solve this I had to tell Maya to start from somewhere, and then instead of going through the face list, to instead convert the face it is currently looking at into edges. Then to compare the edges, and see which other face in the list shares the same edge number. If a face also has 1 matching edge, then that should be appended into a new list as the number 2. As can be expected, this can take a while.

This section is the code for planting the objects onto the surface of another geometry:


Object Planter Tool:
----------------------------
import numpy as np
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as OpenMaya
import math
import re
from string import Template, zfill
from functools import partial

FACE_CENTER_DICT={}
ORDERED_LIST = []
class SelectFaces():

    def __init__(self, geometry, start_face):
        ORDERED_LIST[:] = []
        self.start_face = start_face
        self.reorder_face_list(geometry)       

    def get_edge_list(self, face):
        cmds.select(cmds.polyListComponentConversion( face, ff=True, te=True ))
        edge_list = cmds.ls(sl=True, fl = True)
        
        return edge_list
        
    def reorder_face_list(self, geometry):
        # convienience function to convert current geometry selection to list of faces
        cmds.select(str(geometry)+'.f[*]') # command to select all faces of selected geometry
        self.face_list = cmds.ls(sl=True, fl=True) # turns selected faces into a list
        self.working_list = []
        #self.ordered_list = []
        self.edge_list = []        
        
        starting_face = self.start_face
        ORDERED_LIST.append(starting_face)
        self.edge_list = self.get_edge_list(ORDERED_LIST[-1])
        count = 0

        for dummy_face in self.face_list:
            if dummy_face != starting_face:
                self.working_list.append(dummy_face)
                
        for dummy_count in range(len(self.working_list)):
            if len(ORDERED_LIST) <= len(self.working_list):
                for dummy_var in self.working_list:
                    self.edge_list = self.get_edge_list(ORDERED_LIST[-1])
                    dummy_var_edge = self.get_edge_list(dummy_var)
                    count += 1
                    for edge in dummy_var_edge:
                        if edge in self.edge_list:
                            if dummy_var not in ORDERED_LIST:
                                ORDERED_LIST.append(dummy_var)
                            else:
                                pass
                        else:
                            pass

        
class Face_Center:
    def __init__(self):
        # This finds the face center of each face and uploads data to the global dictionary
        faceCenter = []
        cmds.select(ORDERED_LIST)
        selection = OpenMaya.MSelectionList()
        OpenMaya.MGlobal.getActiveSelectionList(selection)

        iter = OpenMaya.MItSelectionList (selection, OpenMaya.MFn.kMeshPolygonComponent)

        while not iter.isDone():
            dagPath = OpenMaya.MDagPath()
            component = OpenMaya.MObject()

            iter.getDagPath(dagPath, component)

            polyIter = OpenMaya.MItMeshPolygon(dagPath, component)

            while not polyIter.isDone():
                # enumerates the faces in selection
                i = 0
                i = polyIter.index()
                faceInfo = ("face [%s]" %i)
                
                # finds the face center of enumerated face                
                center = OpenMaya.MPoint
                center = polyIter.center(OpenMaya.MSpace.kWorld)
                point = [0.0,0.0,0.0]
                point[0] = center.x
                point[1] = center.y
                point[2] = center.z
                faceCenter = point
                
                # uploads face to global dictionary
                FACE_CENTER_DICT.update({faceInfo:faceCenter})
                
                #goes to next face
                polyIter.next()
                
            iter.next()
                   
class Find_Interger_In_String:
    
        def tryint(self, string):
            try:
                return int(string)
            except:
                return string
        def numeric_key(self, string):
            #import re
            return [self.tryint(string_bits) for string_bits in re.split("([0-9]+)", string)]        
        
class Mass_Planter:
    
    def __init__(self, radius, subdivision_list, item_list):
        self.radius = radius 
        self.subdivision_list = subdivision_list
        self.item_list = item_list
        self.dummy_list = []
        self.dummy_grp=cmds.group( em=True, name=str(self.item_list[0])+'_objCopy_grp' )    
        self.tryint = Find_Interger_In_String()
        SelectFaces(self.item_list[0], self.item_list[1])
        Face_Center()
       
        for dummy_index, dummy_item in enumerate(ORDERED_LIST):            
            for dummy_key, dummy_val in FACE_CENTER_DICT.iteritems():
                string_list = self.tryint.numeric_key(dummy_item)
                '''
                the geometry selected MUST have a number behind it AND ONLY THAT, or the string slicing will pick up the wrong
                list index and cause error
                '''
                face_num_var = '[' + str(string_list[3]) + ']'
                if face_num_var in dummy_key:
                    dummy_sphere=cmds.polySphere(n=str(self.item_list[0]) + '_pika_1',  sx=self.subdivision_list[0], sy=self.subdivision_list[1], r=self.radius)
                    cmds.setAttr( str(dummy_sphere[0])+'.translateX', dummy_val[0] )
                    cmds.setAttr( str(dummy_sphere[0])+'.translateY', dummy_val[1] )
                    cmds.setAttr( str(dummy_sphere[0])+'.translateZ', dummy_val[2] )
                    dummy_constr = cmds.normalConstraint(dummy_item, str(dummy_sphere[0]), 
                                        aimVector = (0,1,0), u = (0,1,0), worldUpType= 0, wu = (0, 1, 0))                    
                    cmds.delete(dummy_constr)
                    self.dummy_list.append(dummy_sphere)                    
        for dummy_geo in self.dummy_list:
            cmds.parent( dummy_geo, str(self.dummy_grp) )
        
        FACE_CENTER_DICT.clear()       

                        
class Mass_Planter_Legacy:
    
    def __init__(self, radius, subdivision_list, geo_sel):
        self.radius = radius 
        self.subdivision_list = subdivision_list
        self.geo_sel = geo_sel
        self.dummy_list = []    
        self.tryint = Find_Interger_In_String()

                        
        for dummy_item in geo_sel:
            ORDERED_LIST[:] = []
            cmds.select(str(dummy_item)+'.f[*]')
            sel_list = cmds.ls(sl = True, fl = True)
            for dummy_var in sel_list:
                ORDERED_LIST.append(dummy_var)
            Face_Center()
            dummy_list = []
            dummy_grp=cmds.group( em=True, name=str(dummy_item)+'_objCopy_grp' )            
            for key, val in FACE_CENTER_DICT.iteritems():
                dummy_sphere=cmds.polySphere(sx=self.subdivision_list[0], sy=self.subdivision_list[1], r=self.radius)
                cmds.setAttr( str(dummy_sphere[0])+'.translateX', val[0] )
                cmds.setAttr( str(dummy_sphere[0])+'.translateY', val[1] )
                cmds.setAttr( str(dummy_sphere[0])+'.translateZ', val[2] )
                dummy_constr = cmds.normalConstraint(dummy_item, str(dummy_sphere[0]), aimVector = (0,1,0), u = (0,1,0), worldUpType= 0, wu = (0, 1, 0))
                cmds.delete(dummy_constr)
                dummy_list.append(dummy_sphere)
            for dummy_geo in dummy_list:
                cmds.parent( dummy_geo, str(dummy_grp) )                
            FACE_CENTER_DICT.clear()
  


class Mass_Planter_UI:
    def __init__(self, *args):
        window = cmds.window( title="PikaPikaTamaChu", iconName='PiAdj', widthHeight=(300, 360) )
        cmds.columnLayout( adjustableColumn=True, rowSpacing=10)
        cmds.separator( style='single' )
        cmds.text( label='1) Select faces of geometries. \n2) Set sphere settings. \n3) Run.', align='left' )
        cmds.separator( style='single' )
        self.obj_radius_value = cmds.floatSliderGrp( field=True, label='Radius',
                 minValue=0.0, maxValue=1000.0, fieldMinValue=1, fieldMaxValue=1000, value=1 )
        cmds.button( label='SetRadius', command=partial(self.print_sphere_radius, 1) )
        self.obj_axis_value = cmds.intSliderGrp( field=True, label='Subdivisions Axis',
                 minValue=1, maxValue=1000, fieldMinValue=1, fieldMaxValue=1000, value=1 )
        self.obj_height_value = cmds.intSliderGrp( field=True, label='Subdivisions Height',
                 minValue=1, maxValue=1000, fieldMinValue=1, fieldMaxValue=1000, value=1 )
        cmds.button( label='Set Subdivision', command=partial(self.print_sphere_subdivision, 1) )
        cmds.separator( style='single' )  
        cmds.button( label='Run', command=partial(self.run_command, 1) )
        cmds.separator( style='single' )    
        cmds.button( label='Legacy Run', command=partial(self.legacy_run_command, 1) )        
        cmds.setParent( '..' )
        cmds.showWindow( window )
        
    def set_root_obj(self, *args):
        root_obj = cmds.textFieldGrp(self.root_obj_name, query=True, text=True)
        start_point = cmds.textFieldGrp(self.start_face, query=True, text=True)
        item_list = [root_obj, start_point]
        return item_list
    
    def sphere_radius(self, *args):
        radius = cmds.floatSliderGrp(self.obj_radius_value, q=True, v=True)
        return radius
        
    def sphere_subdivision(self, *args):
        subdivision_setting = [cmds.intSliderGrp(self.obj_axis_value, q=True, v=True), 
                cmds.intSliderGrp(self.obj_height_value, q=True, v=True)]
        return subdivision_setting
    
    def print_root_obj(self, *args):
        print_list = self.set_root_obj()
        print 'selected asset is ' + str(print_list[0]) + '\n' + 'start point is ' + str(print_list[1]) 
    
    def print_sphere_radius(self, *args):
        print 'sphere radius set at '+ str(self.sphere_radius())
        
    def print_sphere_subdivision(self, *args):
        subdivision_setting = self.sphere_subdivision()
        print 'subD Axis set at ' + str(subdivision_setting[0]) + ' | subD Height set at ' + str(subdivision_setting[1])
    
    
    def run_command(self, *args):
        face_list=cmds.ls(sl=True, fl=True)
        item_list = list()        
        for face in face_list:
            slice_point = face.find('.')
            geo_name = face[:slice_point]
            runtime_list = [geo_name, face]            
            Mass_Planter(self.sphere_radius(), self.sphere_subdivision(), runtime_list)
        
    def legacy_run_command(self, *args):
        obj_sel = cmds.ls(sl = True)
        Mass_Planter_Legacy(self.sphere_radius(), self.sphere_subdivision(), obj_sel)

        
                  
def main():

        Mass_Planter_UI()

if __name__=='__main__':
main()


----------------------------



The next script tool is just something that allows the artist to quickly sort through the generated lightbulbs and order them in whatever multiples they need to.

Select in Multiple Tool:
----------------------------
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as OpenMaya
import math, re
import os, string, shutil
from os import path, listdir
from string import Template, zfill
from functools import partial
from operator import itemgetter
from itertools import groupby

NEXT_LIST = []
ATTR_DICT = {'tX':'translateX', 'tY':'translateY', 'tZ':'translateZ', 'rX':'rotateX', 'rY':'rotateY', 'rZ':'rotateZ', 'sX':'scaleX', 'sY':'scaleY', 'sZ':'scaleZ'}

class Init_Run:
    
    def __init__(self, geo_group):
        self.set_name = str(geo_group)
        
    def next_run(self, new_modulo):
        global NEXT_LIST
        self.new_modulo = int(new_modulo)
        self.filtered_list = []
        self.shape_list = cmds.listRelatives(self.set_name, c=True)
        for dummy_index, dummy_item in enumerate(self.shape_list):         
            if (dummy_index+1) % self.new_modulo == 0:
                self.filtered_list.append(dummy_item)
        
        cmds.select(self.filtered_list)
        #cmds.sets(n=self.set_name + '_by_' + str(self.new_modulo))

class Vray_Obj_Id:
    def __init__(self, geo_list, id_num):
        self.geo_list = geo_list
        self.id_num = id_num #cmds.intSliderGrp(self.obj_id_value, q=True, v=True)
        self.node_list = cmds.listRelatives(self.geo_list, c=True)
        print self.node_list
        
        if cmds.objExists(str(self.node_list[0]) + ".vrayObjectID"):
            pass           
        else:
            cmds.vray("addAttributesFromGroup", self.node_list[0], "vray_objectID", 1)
        
        cmds.setAttr((str(self.node_list[0])+".vrayObjectID"), self.id_num)
        

class Reorder_Selection:
    def tryint(self, string):
        try:
            return int(string)
        except:
            return string
        
    def numeric_key(self, string):
        import re
        return [self.tryint(string_bits) for string_bits in re.split("([0-9]+)", string)]
        
    def natsort(self, input_list):
        input_list.sort(key=self.numeric_key) 

##Essential classes for setting transform attributes (to be used here for replacing duplicates with instances)
class GetAttribute:
    
    def __init__(self, obj, objAttr):
        self.obj = obj
        self.objAttr = objAttr
        
    def run_getAttr_cmd(self):
        attrValue = cmds.getAttr(str(str(self.obj)+"."+str(self.objAttr)))
        return attrValue

class SetAttribute:
    def __init__(self, rootObj, objAttr, attrValue):
        self.rootObj = rootObj
        self.objAttr = objAttr
        self.attrValue = attrValue
        
    def setAttrCmd(self):
        cmds.setAttr(str(str(self.rootObj)+"."+str(self.objAttr)), self.attrValue)
##Essential classes for setting transform attributes (to be used here for replacing duplicates with instances)

class Instance_Replace:
    def __init__(self, geo_list, geo_type_name):
        self.geo_list = geo_list
        self.geo_type_name = geo_type_name
        instance_root = cmds.instance(self.geo_list[0], n = self.geo_type_name+str(self.geo_list[0]))

        for dummy_geo in self.geo_list:
            if dummy_geo == self.geo_list[0]:
                cmds.delete(dummy_geo)
            else:
                new_instance = cmds.instance(instance_root, n = self.geo_type_name + str(dummy_geo))
                translation_matrix = cmds.xform(dummy_geo, ws=True, q=True, a=True, t=True)
                rotation_matrix = cmds.xform(dummy_geo, ws=True, q=True, a=True, ro=True)
                scale_matrix = cmds.xform(dummy_geo, ws=True, q=True, a=True, s=True)
                self.query = cmds.listRelatives(new_instance, allParents=True)
                cmds.parent( self.query[0] + '|' + new_instance[0], world=True )
                cmds.xform( new_instance, a=True, t=(translation_matrix[0], translation_matrix[1], translation_matrix[2]) )
                cmds.xform( new_instance, a=True, ro=(rotation_matrix[0], rotation_matrix[1], rotation_matrix[2]) )
                cmds.xform( new_instance, a=True, s=(scale_matrix[0], scale_matrix[1], scale_matrix[2]) )                
                
                '''
                for dummy_key, dummy_val in ATTR_DICT.iteritems():
                    transform = GetAttribute(dummy_geo, dummy_val)
                    transform_value = transform.run_getAttr_cmd() 
                    set_transform = SetAttribute(new_instance[0], dummy_val, transform_value)
                    set_transform.setAttrCmd()
                '''
                cmds.delete(dummy_geo)


class Shader_Setup:
    def __init__(self, id_num):
        #self.group_name = group_name
        #self.geo_list = cmds.listRelatives(self.group_name, c=True)
        self.id_num = str(id_num)
        #slice_poin t= new_geo_list[0].find('_pika')
        #shader_root = new_geo_list[0][:slice_point]
        shader_name = 'ObjID_' + self.id_num             
        shader=cmds.shadingNode("surfaceShader",asShader=True, name=shader_name+'_lightShader' )
        shading_group = cmds.sets(renderable=True,noSurfaceShader=True,empty=True, name=str(shader)+'SG')
        cmds.connectAttr('%s.outColor' %shader ,'%s.surfaceShader' %shading_group)
        cmds.setAttr(str(shader)+"."+"outColor", 0.0, 0.0, 0.0, type="double3" )
                

class Select_By_Multiple_UI:
    def __init__(self, *args):
        self.selection_group_list = list()
        window = cmds.window( title="Select_By_Multiple", iconName='SlMul', mnb = True, mxb = False, sizeable = False )
        cmds.columnLayout( adjustableColumn=True, rowSpacing=10, w = 280)
        cmds.frameLayout( label='Select Groups', borderStyle='in', cll=True )
        cmds.columnLayout(adjustableColumn=True, rowSpacing=10, w = 280)
        cmds.button( label='Set Master Group', command=partial(self.master_list, 1) )
        cmds.separator( style='single' ) 
        cmds.setParent( '..' )
        cmds.setParent( '..' )
        
        cmds.frameLayout( label='Select Multiples', borderStyle='in', cll=True )
        cmds.columnLayout(adjustableColumn=True, rowSpacing=10, w = 280)
        self.new_modulo_val_set = cmds.textFieldGrp( label = 'Next Multiples Of?' )
        cmds.button( label='Select Objects', command=partial(self.run_command_second, 1) )  
        #cmds.button( label='Set Up Groups', command=partial(self.group_set_up, 1) )     
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )       

        cmds.frameLayout( label='Assign Shaders and Obj ID', borderStyle='in', cll=True )
        cmds.columnLayout(adjustableColumn=True, rowSpacing=10, w = 280)        
        cmds.separator( style='single' )
        
        self.obj_id_value = cmds.intSliderGrp( field=True, label='SetObjectID',
                 minValue=0, maxValue=30, fieldMinValue=0, fieldMaxValue=1000, value=0 )          
        cmds.button( label='Assign Shaders and Obj ID', command=partial(self.add_shdr_obj_id, 1) )   
        cmds.separator( style='single' )
        cmds.setParent( '..' )
        cmds.setParent( '..' )   

        cmds.frameLayout( label='Sort Selection', borderStyle='in', cll=True )
        cmds.columnLayout(adjustableColumn=True, rowSpacing=10, w = 280)                  
        self.sort_list_grp = cmds.textFieldGrp( label='Sort List Group' )
        cmds.button( label='Sort Selection', command=partial(self.run_sort_selection, 1) )
        cmds.separator( style='single' )

        cmds.button( label='Unparent', command=partial(self.unparent, 1) ) 
        cmds.setParent( '..' )
        cmds.setParent( '..' )    
        cmds.setParent( '..' )
        cmds.showWindow( window )
        
    def master_list(self, *args):
        self.selection_group_list = cmds.ls(sl=True, fl=True)
        
    def new_modulo(self, *args):
        new_modulo_val = cmds.textFieldGrp(self.new_modulo_val_set, query=True, text=True)
        return new_modulo_val
            
    def print_modulo(self, *args):
        print 'selecting by ' + str(self.modulo())
    
    def print_new_modulo(self, *args):
        print 'selecting by ' + str(self.new_modulo()) 
        
    def group_set_up(self, *args):
        #Puts selected geo into groups
        geo_list = cmds.ls(sl=True, fl=True)
        geo_type_name = 'instance_'
        new_geo_list = []
        new_group_list = []
        id_num = self.new_modulo()
        slice_point=geo_list[0].find('_pika')
        new_name = geo_list[0][:slice_point]
        modulo_index = cmds.textFieldGrp(self.new_modulo_val_set, query=True, text=True)
        new_group = cmds.group( em=True, name=new_name + '_Sel_x' + modulo_index )
        
        for dummy_geo in geo_list:
            cmds.parent( dummy_geo, new_group )
        
        ## Use the below for converting to instance. 
        '''
        Instance_Replace(geo_list, geo_type_name)
        for dummy_geo in geo_list:
            new_geo_list.append(geo_type_name + dummy_geo)

        for dummy_group in self.selection_group_list:
            group_name = cmds.group( em=True, name=geo_type_name + dummy_group + '_Sel_' + str(id_num))
            new_group_list.append(group_name)
        for dummy_geo in new_geo_list:
            slice_point=dummy_geo.find('_pika')
            new_name = dummy_geo[:slice_point]
            for dummy_group in new_group_list:
                if new_name in dummy_group:
                    cmds.parent( dummy_geo, dummy_group )
        cmds.select(new_geo_list)       
        '''
    def add_shdr_obj_id(self, *args):
        group_list = cmds.ls(sl=True, fl=True)
        id_num = cmds.intSliderGrp(self.obj_id_value, q=True, v=True)
        Shader_Setup(id_num)
        for dummy_group in group_list:
            Vray_Obj_Id(dummy_group, id_num)
            dummy_geo_list = cmds.listRelatives(dummy_group, c=True)
            for dummy_geo in dummy_geo_list:
                cmds.sets(dummy_geo, e=True, forceElement='ObjID_' + str(id_num) +'_lightShaderSG')
        cmds.select(group_list)
        
        
    def run_sort_selection(self, *args):
        sort_cmd = Reorder_Selection()        
        dummy_list = cmds.ls(sl=True, fl=True)
        sort_cmd.natsort(dummy_list)
        group_name = cmds.textFieldGrp(self.sort_list_grp, query=True, text=True)
        sort_group = cmds.group( em=True, name=group_name )
        for dummy_geo in dummy_list:
            cmds.parent( dummy_geo, str(sort_group) )
            
    def unparent(self, *args):
        sel_list = cmds.ls(sl=True, fl=True)
        for dummy_item in sel_list:
            query = cmds.listRelatives(dummy_item, allParents=True)
            for dummy_query in query:
                cmds.parent( dummy_query + '|' + dummy_item, world=True )
    
    def run_command_second(self, *args):
        self.print_new_modulo()
        self.geo_selection_list = list()
        for dummy_group in self.selection_group_list:
            run_cmd = Init_Run(dummy_group)
            run_cmd.next_run(self.new_modulo())
            dummy_tmp_list = cmds.ls(sl=True, fl=True)
            for dummy_tmp in dummy_tmp_list:
                self.geo_selection_list.append(dummy_tmp)
        cmds.select(self.geo_selection_list)
                  
def main():

        Select_By_Multiple_UI()

if __name__=='__main__':
main()

----------------------------
    


Needless to say, this re-order of faces generally works only for strips of polygons. Full planar surfaces is very difficult, as the shapes and circumstances they can come in is probably infinite. 

Still this had been a really satisfying challenge for me.

"Hello World!"....in Morse Code!



My forey into electronics! It's a simple setup, just some blinking lights. Really simple C code. This video was a bunch of colored lights blinking, but I was also playing around with getting the blinking lights to blink in Morse Code. It's not that hard, but getting the lights to blink once in C is like 5 lines of code or so. Then you have Morse, which is like 3 or 4 blink patterns per letter. Rewriting all that every time I want to test different phrases is tedious with so I wrote out an array in C of the Morse Code, and a Python file that will convert text I type in into something for Arduino.

----------------------------
import string, os
from os import path, listdir

class MorseCode():

    def __init__(self):
        self.MorseCodex = {'a':'alphaA ();', 'b':'alphaB ();', 'c':'alphaC ();', 'd':'alphaD ();', 'e':'alphaE ();', 
                 'f':'alphaF ();', 'g':'alphaG ();', 'h':'alphaH ();', 'i':'alphaI ();', 'j':'alphaJ ();',
                 'k':'alphaK ();', 'l':'alphaL ();', 'm':'alphaM ();', 'n':'alphaN ();', 'o':'alphaO ();',
                 'p':'alphaP ();', 'q':'alphaQ ();', 'r':'alphaR ();', 's':'alphaS ();', 't':'alphaT ();',
                 'u':'alphaU ();', 'v':'alphaV ();', 'w':'alphaW ();', 'x':'alphaX ();', 'y':'alphaY ();',
                 'z':'alphaZ ();', '0':'Null ();', '1':'Eins ();', '2':'Zwei ();', '3':'Drei ();', '4':'Vier ();',
                 '5':'Funf ();', '6':'Sechs ();', '7':'Sieben ();', '8':'Acht ();', '9':'Neun ();', ' ':'wordSpace ();', 
                 '.':'sentenceSpace ();'}

    def returnMorseCodex(self):
        return self.MorseCodex
 

class DirectoryInput():
    def __init__(self):
        self.dirInput='C:\\Users\\HP-OK\\Desktop\\MorseCodeConverterText'

    def formatDirPath(self):
        workDir=self.dirInput
        fLst=os.listdir(workDir)
        return fLst

class UserEntry():

    def __init__(self):
        self.engText = raw_input("Enter Your Text: ")

    def formatToList(self):
        lowerAllText = self.engText.lower()
        charList = list(lowerAllText)
        return charList

    #def tst(self):
        #self.testing = MorseCode()
        #return self.testing.returnMorseCodex()

class Converter():

    def __init__(self):
        userInput = UserEntry()
        dirPathInput = DirectoryInput()
        morseCodeLib = MorseCode()
        self.letterList = userInput.formatToList()
        self.dirPath = dirPathInput.formatDirPath()
        self.morseLib = morseCodeLib.returnMorseCodex()
        
    def convertEngToMorse(self):
        morseList = list()
        lineBreak = '\nletterSpace ();\n'

        for item in self.letterList:
            for key, val in self.morseLib.iteritems():
                if item == key:
                    morseList.append(val)

        convertedText = lineBreak.join(morseList)

        bseTemplate = 'void loop() {\n\nplaceholder\nletterSpace ();\n\n}'

        if 'placeholder' in bseTemplate:
            self.newTemplate = bseTemplate.replace('placeholder', convertedText)

        for fname in self.dirPath:
            f_open=open(fname, 'w+')
            f_open.write(self.newTemplate)
            f_open.close()


myString = Converter()

myString.convertEngToMorse()

    
----------------------------


Windows Batch Rename

There are numerous window tools out there that allows you to manage batch window files in the way that Linux does, and I didn't have to write this but I did anyway, for practice.

The main thing I learnt with this project is just how seemingly simple tasks can be difficult for the computer to implement, and by which I mean counting in this specific case.

It's not something you'd see often nowadays, but when you have long sequences of files numbered 1 - 100 for example, the computer is not going to number them 1 - 100 for you. It will be 1, 10, 11....19, 2, 20, 21.....and so on.

Given that in animation, numbering of the files are super important, if the batch renamer can't sort the files into a list naturally, then it's worse than useless. It would rename the number 10 frame as 2, and the number 2 frame as 10....So natural sorting turns out to be pretty tricky to implement. My original code just for natural sorting was about 40 lines or so and wonky, but I found an implementation using regular expression that was much simpler. Unfortunately, I could no longer remember where I found the code from so could not attribute it. In any case, the code comes below, runs off IDLE, and has no UI hahaha. The key portion for natural sorting is in the first 3 functions.

----------------------------
import os, string
from os import path, listdir, rename
from string import Template, zfill
usrInput=raw_input("Enter directory pathway: ")
workDir=usrInput.replace("\\", "\\\\")
fLst=os.listdir(workDir)

def tryint(s):
    try:
        return int(s)
    except:
        return s
       
def numeric_key(s):
    import re
    return [tryint(c) for c in re.split("([0-9]+)", s)]

def natsort(Lst):
    fLst.sort(key=numeric_key)
   
natsort(fLst)
print fLst

def srep():
    nWord = raw_input("enter new name: ")
    old = raw_input("enter old name: ")
    if len(old)<1:
        print "no entry detected, try again"
        exit()
    for item in fLst:
        original_file=os.path.join(workDir, item)
        if old in original_file:
            base, ext = os.path.splitext(original_file)
            newFile=item.replace(old, nWord)
            os.rename(original_file, (os.path.join(workDir, newFile)))
            print '{0} --> {1}'.format(item, newFile)

       
def rname():
    count = 0
    class BatchRename(Template):
        delimiter = '%'
    usrInpFmt=raw_input('Enter rename style (%d-date %n-seqnum %f-format):  ')
    pad=int(raw_input('Enter padding: '))
    start=int(raw_input('Start From?: '))
    fmt=usrInpFmt+"%n%f"
 
    t = BatchRename(fmt)

    for i, filename in enumerate(fLst):
        original_file=os.path.join(workDir, filename)
        count=start+i
        base, ext = os.path.splitext(filename)
        newName = t.substitute(n=string.zfill(count, pad), f=ext)
        os.rename(original_file, (os.path.join(workDir, newName)))
        print '{0} --> {1}'.format(filename, newName)

style=raw_input("function type? 1) search and replace 2) rename _ ")
if style is "1":
    srep()
elif style is "2":
    rname()
else:
    print "function not within call"
----------------------------

Saturday, 16 July 2016

Stop Watch!

Code

This was an assignment for the Python Course I took on Coursera, Interactive Python Programming. It's written in a web-based Python shell called CodeSkulptor developed by the course instructors, and comes with some of Python's libraries as well as some gui commands built into it. It's a simple game where you hit the stop button just as the clock hits the second mark. You can run it by hitting the play button at the top left hand off the page.

Anyone who knows about computers will likely know that that computers don't keep track of time in minutes and hours. Instead it's just one giant chunk of miliseconds. So one minute is 60 seconds*10 miliseconds.

One way I could have solved this is to write some long tedious if/elif loop, where I say something like:

----------------------------
msec = total_time%10

if msec == 10:
    msec_counter = 0
    sec_counter = 1
    if sec_counter == 60:
        sec_counter = 0
        minute_counter = 1
----------------------------

and so on for hours and days. Personally, I don't like code with too many nested loops, because it's hard to read, and logic errors can crop up easily. It also makes it challenging to edit later. Instead, I like to use functions to break up the computations.

I looked for other ways to implement this, and this is where I think I fell in love with using mathematics to implement solutions. I could do this in a much clearer way (to me at least) then writing long-winded loops with the added bonus that I can use that function later on.

This is the implementation I ended up using:

----------------------------
def format(t):
    # using euclid division a = bq + r
    # where a = t, b = 600(60 secs)
    # q = minutes(t/b), r = remainder
 
    def euclid_division(t):
        r = t-600*(t/600)       # collapsed equation using a = bq+r
        return r                      # we want to find remainder (r), therefore
                                          # the new equation is r = a - bq
                                          # where a is t, b is 600, and q is quotient
     
    def euclid_division_min(t):
        r = euclid_division(t)     # collapsed equation from a = bq+r
        loc_min = (t -r)/600       # we try to find out how many minutes given t deciseconds
        return loc_min               # so new equation is q = (a - r)/b
                                               # where a = t, r is derived from function euclid_division(t)
                                               # and b is 600. q is given "loc_min" as variable name here
                             
    # declare global variables
    global decisec                # declared global var for later use to keep score.
    decisec = t%10              # decisecond portion easily found with modulo.
    # local variables
    sec = euclid_division(t)/10
    min = euclid_division_min(t)
 
    if sec < 10:                      # if string.zfill() function exist, would be a lot simpler
        sec = "0" + str(sec)     # but since it's not loaded, longer, clumsier way neccesary

    if min < 10:
        min = "0" + str(min)
     
     
    return str(min) + ":" + str(sec) + "." + str(decisec)
----------------------------

The solution I happened upon uses euclidean division, a = bq + r. Within this equation are all the ingredients I need to find the second, minute, hour for implementing the clock. We can find the msec portion with modulo, and the second/minute portion can be found with the two functions euclid_division() and euclid_division_min().