It's been ages and ages since I touched this blog, but I thought I'd come back and document some of the changes in the direction I have been heading towards in my career.
So what's changed? Well, I have been headed towards a much more technical direction. Not just in CG, but in general. Mostly it started because I wanted to make my job a little easier and cutting and pasting Mel commands from script editors into shelf buttons....which led to learning how to program....which led to looking into things like Arduino, then building my own hexapod and now I would write tools to help productions in my Studio.
At the moment I'm learning to write shaders, as well as diving into the maths behind CG. I took a course on Robotics on Coursera. Didn't understand half of it, but a lot of the maths, like calculating matrices, vectors and rotation is exactly the same, and wonder of wonders, a lot of the shader code started making sense as well!
Anyhow, I'm going to use this post as an index to some of the code + technical research stuff I've worked on or doing. I do have a github page as well, but this will provide more of a explanation behind what I was doing.
Onwards!
Production Related:
01 -- ) Object Planter:
-My first full-on production related tool, for the anime movie One Piece Gold. The object planter helped immensely with the crazy amounts of light bulbs needed in the movie. But as always, the real challenge in computing isn't always what it seems to humans. Getting the lights into a form that can be animated like in Vegas was the real challenge.
02 -- ) Face_Hugster:
-Taking inspiration from the object planter, I turned it into a much more general toolset. But although this was immensely fun to work on, as a tool, I think it has a long way to go yet.
03 -- ) Chain Maker:
-Making chains in 3D is annoying. Even more so when there is no design, and no scale nor instructions on how large the chain links are. The original task was to come up with something visual to show the director, and he will decide if the scale/design/link size works......I'm really sorry guys, but I have urgent matters to attend to today, like watching paint dry. So...here, use this!
04 -- ) Shader Writing in Unity:
-On-going self-learning on shader writing in Unity. In time, I hope to take this much further!
--------------------------------------------------------------------------------------------------------------------------
Non Production related:
01 --) Stop Watch:
-A simple exercise, but I included it because here is where my thinking on solving computing problems using maths took a turn. I could have written this with a long if/elif conditional to format the time, but instead, using Euclidean division yielded a much simpler coding structure.
02 -- ) Windows Batch Rename:
-My first real os related script! This is where I really learnt how seemingly simple tasks for humans can be so difficult for computers...because natural sorting!
03 -- ) "Hello World!"....in Moorse Code!
-Exploring Arduino and writing in C for the first time. But I cheated and used python instead to write a converter script that will turn the english sentence you type into C so that Arduino can run the blinky lights.
3D-XJ
Hi, I'm XJ. I'm a 3d modelling and texture artist. I will be using this space to share my personal work.
Sunday, 17 July 2016
Learning Shader Writing - Unity Tutorial
I don't really know what possessed me to do it, but I took a course, again from Coursera, this time on Robotics flight of all things.
Almost, but not quite managed to pass. The last assignment took me too much time, and I could not complete it by the deadline. The maths was pretty tough, given I have very little background in it. But the plus side is that I can do matrix operations, and sort of calculate rotations and vectors in 3D space now.
And of course, 3D is all about matrices, vectors and trigonometry! I have been considering for a while now to moving into a tech artist position, and shader writing seems to be a really desirable skill to have. What do you know? That matrix, vector, and trigonometry stuff is running the show behind the back. Amazing, I can actually understand how the rim light, reflection, ambient light etc is derived now. Still going through the tutorial, which is from https://cgcookie.com/learn-unity/. The tutorial is by Alex Telford. It's archived now, but you can grab the stuff here.
Been really interesting so far!
Some sample code:
----------------------------
Shader "unityCookie/tut/beginner/6_textures"{
Properties{
_Color("Color Tint", Color)=(1.0, 1.0, 1.0, 1.0)
_MainTex("Diffuse Texture", 2D) = "white" {}
_SpecColor("Specular Color", Color) = (1.0, 1.0, 1.0, 1.0)
_Shininess("Shininess", Float) = 10
_RimColor("Rim Color", Color) = (1.0, 1.0, 1.0, 1.0)
_RimPower("Rim Power", Range(0.1, 10.0)) = 3.0
}
SubShader {
Pass{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//user-define variables
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float4 _RimColor;
uniform float _Shininess;
uniform float _RimPower;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
//unity defined variables
uniform float4 _LightColor0;
//base input structs
struct vertexInput{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput{
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
};
//vertex function
vertexOutput vert(vertexInput v){
vertexOutput o;
o.posWorld = mul(_Object2World, v.vertex);
o.normalDir = normalize(mul(float4(v.normal, 0.0), _World2Object).xyz);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.tex = v.texcoord;
return o;
}
//fragment function
float4 frag(vertexOutput i) : COLOR{
float3 normalDirection = i.normalDir;
float3 viewDirection = normalize( _WorldSpaceCameraPos.xyz - i.posWorld.xyz );
float3 lightDirection;
float atten;
if(_WorldSpaceLightPos0.w == 0.0){//directional light
atten = 1.0;
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else{
float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
float distance = length(fragmentToLightSource);
atten = 1.0/distance;
lightDirection = normalize(fragmentToLightSource);
}
//Lighting
float3 diffuseReflection = atten * _LightColor0.xyz * saturate(dot(normalDirection, lightDirection));
float3 specularReflection = diffuseReflection * _SpecColor.xyz * pow(saturate(dot(reflect(-lightDirection, normalDirection), viewDirection)) ,_Shininess);
//Rim Lighting
float rim = 1 - saturate(dot(viewDirection, normalDirection));
float3 rimLighting = saturate(dot(normalDirection, lightDirection) * _RimColor.xyz * _LightColor0.xyz * pow(rim, _RimPower));
float3 lightFinal = UNITY_LIGHTMODEL_AMBIENT.xyz + diffuseReflection + specularReflection + rimLighting;
//Texture Maps
float4 tex = tex2D(_MainTex, i.tex.xy * _MainTex_ST.xy + _MainTex_ST.zw);
return float4(tex.xyz * lightFinal * _Color.xyz, 1.0);
}
ENDCG
}
Pass{
Tags {"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//user-define variables
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float4 _RimColor;
uniform float _Shininess;
uniform float _RimPower;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
//unity defined variables
uniform float4 _LightColor0;
//base input structs
struct vertexInput{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct vertexOutput{
float4 pos : SV_POSITION;
float4 tex : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float3 normalDir : TEXCOORD2;
};
//vertex function
vertexOutput vert(vertexInput v){
vertexOutput o;
o.posWorld = mul(_Object2World, v.vertex);
o.normalDir = normalize(mul(float4(v.normal, 0.0), _World2Object).xyz);
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.tex = v.texcoord;
return o;
}
//fragment function
float4 frag(vertexOutput i) : COLOR{
float3 normalDirection = i.normalDir;
float3 viewDirection = normalize( _WorldSpaceCameraPos.xyz - i.posWorld.xyz );
float3 lightDirection;
float atten;
if(_WorldSpaceLightPos0.w == 0.0){//directional light
atten = 1.0;
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else{
float3 fragmentToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
float distance = length(fragmentToLightSource);
atten = 1.0/distance;
lightDirection = normalize(fragmentToLightSource);
}
//Lighting
float3 diffuseReflection = atten * _LightColor0.xyz * saturate(dot(normalDirection, lightDirection));
float3 specularReflection = diffuseReflection * _SpecColor.xyz * pow(saturate(dot(reflect(-lightDirection, normalDirection), viewDirection)) ,_Shininess);
//Rim Lighting
float rim = 1 - saturate(dot(viewDirection, normalDirection));
float3 rimLighting = saturate(dot(normalDirection, lightDirection) * _RimColor.xyz * _LightColor0.xyz * pow(rim, _RimPower));
float3 lightFinal = diffuseReflection + specularReflection + rimLighting;
//Texture Maps
float4 tex = tex2D(_MainTex, i.tex.xy * _MainTex_ST.xy + _MainTex_ST.zw);
return float4(tex.xyz * lightFinal * _Color.xyz, 1.0);
}
ENDCG
}
}
//Fallback "Specular"
}
----------------------------
Chain Maker
Like......haha, I think it might be more productive to watch paint dry. So I hammered this out when I got back home and had a chance to think about it. Now we can change the design all day, every day and it'd easy peasy!
The screen shot pretty much says all about the features. One thing I will say however, is that after Face_Hugster, my new philosophy towards making tools is.......One button solution is BEST! Three buttons if you MUST (but one of the three should be the exit button!)
----------------------------
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
class Find_Out():
'''
multipurpose class for finding length of anything. Well, work in progress
'''
def edge_length(self, vertex_list):
#find distance between two points. numpy required. need to rework this so numpy not required
vtx_p=cmds.xform(vertex_list,q=True,t=True,ws=True)
'''
this is numpy version. reuse for machines with numpy for quicker calculations:
vtx_p_array_a = np.array([[vtx_p[0]], [vtx_p[1]], [vtx_p[2]]])
vtx_p_array_b = np.array([[vtx_p[3]], [vtx_p[4]], [vtx_p[5]]])
dist = np.linalg.norm(vtx_p_array_a-vtx_p_array_b)
'''
dist = math.sqrt((vtx_p[3] - vtx_p[0])**2 + (vtx_p[4] - vtx_p[1])**2 + (vtx_p[5] - vtx_p[2])**2)
return dist
def curve_length(self, curve_sel):
#find length of curve
find_curve_length = cmds.arclen(curve_sel)
return find_curve_length
class Chain_Constrain():
def __init__(self, curve_sel, vertex_list, chain_geo):
self.curve_sel = curve_sel
self.verts = vertex_list
self.chain_geo = chain_geo
self.find_length = Find_Out()
self.link_length = self.find_length.edge_length(self.verts)
self.chain_length = self.find_length.curve_length(self.curve_sel)
self.link_total = int(self.chain_length/self.link_length)
self.motion_path_name = str(self.chain_geo) + '_Path'
cmds.pathAnimation(self.chain_geo, name = self.motion_path_name, fractionMode = True, follow= True, followAxis = 'x',
upAxis = 'y', worldUpType = 'object', startTimeU = 1, endTimeU = self.link_total, c = self.curve_sel)
cmds.setKeyframe(self.motion_path_name + '.frontTwist', v = 0.0, t = 1 )
cmds.setKeyframe(self.motion_path_name + '.frontTwist', v = 60.0*self.link_total, t = self.link_total )
cmds.keyTangent(self.motion_path_name + '.uValue', itt = 'linear', ott = 'linear' )
cmds.keyTangent(self.motion_path_name + '.frontTwist', itt = 'linear', ott = 'linear')
cmds.snapshot( self.chain_geo, constructionHistory=True, startTime=1, endTime = self.link_total, increment=1, update = 'animCurve',
name = str(self.chain_geo) + '_snapShot' )
self.chain_group = cmds.group( em=True, name=str(self.chain_geo) + '_geo_grp' )
self.chain_list = cmds.listRelatives(str(self.chain_geo + '_snapShotGroup'))
for dummy_geo in self.chain_list:
cmds.delete(icn = True, ch = True)
cmds.parent(dummy_geo, self.chain_group)
class Spline_Rig_Chain():
def __init__(self, curve_sel, vertex_list):
self.curve_sel = curve_sel
self.verts = vertex_list
self.find_length = Find_Out()
self.link_length = self.find_length.edge_length(self.verts)
self.chain_length = self.find_length.curve_length(self.curve_sel)
self.link_total = int(self.chain_length/self.link_length)
cmds.duplicate(self.curve_sel, n = 'buildCurve')
cmds.rebuildCurve('buildCurve', ch = 1, rpo = 1, rt = 0, end = 1, kr = 2, kep = 1, kt = 0, kcp = 0, s = self.link_total/2, d = 3, tol = 0.01 )
self.num_cv = int(cmds.getAttr ('buildCurve.degree'))+ (cmds.getAttr ('buildCurve.spans'))
for dummy_cv in range(self.num_cv):
dummy_cv_pos = (cmds.getAttr ('buildCurve.cv['+ str(dummy_cv) +']'))
if dummy_cv == 0:
cmds.joint(n=self.curve_sel+'_jointRoot',p = dummy_cv_pos[0])
elif dummy_cv == self.num_cv - 1:
cmds.joint(n=self.curve_sel+'_jointEnd', p = dummy_cv_pos[0])
else:
cmds.joint(n=self.curve_sel+'_joint_'+(str(dummy_cv)),p = dummy_cv_pos[0])
cmds.delete('buildCurve')
cmds.ikHandle( sj = (self.curve_sel+'_jointRoot'), ee = (self.curve_sel+'_jointEnd'), c = self.curve_sel,
sol = 'ikSplineSolver', scv = 0, pcv = 0, ccv = 0, ns = 4)
class Chain_Maker_UI():
def __init__(self):
window = cmds.window( title="Chain Maker", iconName='ChnMk', widthHeight=(300, 100) )
cmds.columnLayout( adjustableColumn=True )
cmds.separator( style='single' )
self.curve_sel_name = cmds.textFieldGrp( label = 'Curve Selection' )
cmds.separator( style='single' )
cmds.button( label='Run', command=partial(self.run_command, 1) )
cmds.separator( style='single' )
cmds.button( label='Exit', command=('cmds.deleteUI(\"' + window + '\", window=True)') )
cmds.setParent( '..' )
cmds.showWindow( window )
def curve_name(self, *args):
self.curve_sel = cmds.textFieldGrp(self.curve_sel_name, query=True, text=True)
return self.curve_sel
def run_command(self, *args):
curve_sel = self.curve_name()
vert_sel_list = cmds.ls(sl=True, fl = True)
if '.' in vert_sel_list[0]:
dummy_item_index = vert_sel_list[0].index('.')
self.geo_name = vert_sel_list[0][0:dummy_item_index]
Chain_Constrain(curve_sel, vert_sel_list, self.geo_name)
Spline_Rig_Chain(curve_sel, vert_sel_list)
self.chain_list = cmds.listRelatives(str(self.geo_name + '_geo_grp'))
joint_list = cmds.ls(type = 'joint')
for dummy_geo in self.chain_list:
cmds.select(dummy_geo, add = True)
for dummy_joint in joint_list:
if 'ikHandle' in dummy_joint:
pass
elif curve_sel in dummy_joint:
cmds.select(dummy_joint, add=True)
cmds.select('ikHandle*', d = True)
mel.eval('newBindSkin " -byClosestPoint -toSkeleton";')
Chain_Maker_UI()
----------------------------
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()
----------------------------
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.
Subscribe to:
Posts (Atom)