Setting ZBrush Scene Scale

Tutorial / 24 July 2024

Setting the scene scale in ZBrush is an important step in any digital sculpting project. It ensures that all elements within your scene maintain accurate proportions and sizes relative to each other. This is particularly important when working on detailed models or when precision is required, such as in product design, or character creation.

Here's a quick step-by-step tutorial on how to set the scene scale in ZBrush.

Step 1

Launch ZBrush and open a new document. This will be your canvas for sculpting. You can save it as .zbr for later use after you have set the environment scale. OR if you have worked on a file, you may perform the same steps on a working file but make sure that you have selected the 3D cube.

Step 2

Go to the Tool Palette and navigate to the SubTool tab. Click on the Insert button and select 3DCube from the QuickPick menu. This cube will serve as our reference object for setting the scene scale. By default, ZBrush sets the scene scale based on the dimensions of the last inserted object. Since we've inserted a cube, the initial scale will be set according to the cube's dimensions.

Step 3

Press the ZPlugin located at the top of ZBrush UI. And from the dropdown menu find the Scale Master plugin and expand it. This plugin will allow us to adjust the scene scale precisely.

Step 4

Within the Scale Master plugin, click on the Set Scene Scale button. From the dropdown menu, select the desired scale. For example, if you're working on a miniature model, you might choose 2.00 x 2.00 x 2.00 mm. For this tutorial, I will set to 2x2x2 millimeters.

Step 5

Still within the Scale Master plugin, adjust the scale values to 25.4 x 25.4 x 25.4 mm which is 1 inch. Click the Resize Subtool button to apply the changes. This action resizes the cube (and thus the scene) to the new scale.

Step 6

To ensure the scene scale is correctly set, click on Set Scene Scale again in the Scale Master plugin. This step is important for confirming that the scene scale matches your intended dimensions. Once confirmed, click outside the Set Scene Scale window to exit and finalize the settings.

With the scene scale now accurately set, you're ready to begin sculpting. All future actions, including adding details or modifying the model, will adhere to this established scale.

Report

Install pip Pymel for Maya 2023 or 2024

Tutorial / 29 March 2024

Some scripts do not work properly without pip being installed in Maya. I have experienced issues where Meta Humans and MGear won't launch without it. Therefore, I took some time to research and created a few steps to get it to work, which is applicable only for Windows users.

  1. Go to the website, https://pypi.org/project/pymel/#files, and download the most recent file.
  2. When you click on the link, it will ask you where you would like to save the file. I recommend saving it in C:\Users\[USER]\Documents\maya, where [USER] should be replaced with your username.
  3. Once you have the file saved, right-click on the Windows icon and choose 'Run'. In the 'Open:' field, write CMD and click 'OK' to run the Command Prompt.
  4. After C:\Users\User>, write cd C:\Users\[USER]\Documents\maya and press ENTER on your keyboard. This will navigate the command prompt to the folder where you have saved your installation file. You should see C:\Users\[USER]\Documents\maya> appear in your CMD window.
  5. After the arrow, write pip install pymel-1.4.0-py2.py3-none-any.whl and press Enter to execute the installation. This should start the installation process, which takes about 10 seconds. You will be notified once it is completed.


Report

"Splinter Calculator" - A Simple Maya Tool Made with Python3

Tutorial / 30 October 2023
import maya.cmds as cmds


# Create a window
window = cmds.window(title="Python Calculator", sizeable=False)
cmds.columnLayout(adjustableColumn=True)


# Create input fields for numbers
number1 = cmds.textField(text='0', height=30)
number2 = cmds.textField(text='0', height=30)


# Create buttons for operations
cmds.rowLayout(numberOfColumns=4, columnWidth4=(100, 100, 100, 100))
add_button = cmds.button(label='+', command=lambda x: add_numbers(number1, number2, result))
subtract_button = cmds.button(label='-', command=lambda x: subtract_numbers(number1, number2, result))
multiply_button = cmds.button(label='*', command=lambda x: multiply_numbers(number1, number2, result))
divide_button = cmds.button(label='/', command=lambda x: divide_numbers(number1, number2, result))
cmds.setParent('..')


# Create a text field for the result
result = cmds.textField(editable=False, height=30)


# Define the functions for the operations
def add_numbers(num1, num2, result_field):
    num1 = cmds.textField(num1, query=True, text=True)
    num2 = cmds.textField(num2, query=True, text=True)
    result = int(num1) + int(num2)
    cmds.textField(result_field, edit=True, text=str(result))


def subtract_numbers(num1, num2, result_field):
    num1 = cmds.textField(num1, query=True, text=True)
    num2 = cmds.textField(num2, query=True, text=True)
    result = int(num1) - int(num2)
    cmds.textField(result_field, edit=True, text=str(result))


def multiply_numbers(num1, num2, result_field):
    num1 = cmds.textField(num1, query=True, text=True)
    num2 = cmds.textField(num2, query=True, text=True)
    result = int(num1) * int(num2)
    cmds.textField(result_field, edit=True, text=str(result))


def divide_numbers(num1, num2, result_field):
    num1 = cmds.textField(num1, query=True, text=True)
    num2 = cmds.textField(num2, query=True, text=True)
    result = int(num1) / int(num2)
    cmds.textField(result_field, edit=True, text=str(result))


# Show the window
cmds.showWindow(window)

Report

Enhancing 3D Foliage Creation in Maya with djPFXUVs Python3 Script

Tutorial / 26 October 2023

This script, an improved version of the djPFXUVs Python script by David Johnson, is designed for Maya and primarily used for arranging UVs in a 2x2, 3x3, or 4x4 grid within a 1x1 space. It's especially useful for creating foliage. The script is compatible with Python3 and works with Maya 2020 and later versions.

To use this script in Maya, follow these steps:

  1. Save the script as djPFXUVs.py in the Maya scripts directory. The scripts directory is typically located under your user's documents folder, in a directory structure similar to Documents/maya/scripts/.
  2. Once the script is saved in the correct location, you can import and use it in Maya. Open the Python command line or script editor in Maya and execute the following code:
import djPFXUVs
djPFXUVs.layoutUI()

This code will import the djPFXUVs module and run the layoutUI() function, which opens the user interface for the script. From here, you can select the source and target UV sets, the type of UV layout, and other options, then apply the layout to your selected meshes.

import maya.OpenMaya as om
import maya.cmds as mc
import random




'''
pfxUVs.py




author:     David Johnson
contact:    david@djx.com.au
web:        http://www.djx.com.au




version:    1.4
date:       10/26/2023




Description:
    Script is for automatically laying out uv's for meshes created by converting paintFX to polys
    By default the uv's for leaves and grass are the same for each leaf or blade and unitized.
    By offsetting and scaling the uv's for each shell it is possible to create a fileTexture with
    a number of variations and have these randomly distributed to the leaves or blades.
    Can either be run from the UI or directly as a command (marking menu or shelf)
    
    layoutUI()    create UI
    leafLayout()  lays out leaf uvs into tiles in a 2x2, 3x3, or 4x4 array
    grassLayout() lays out grass blades uv's in thin strips, randomly placed along u  
'''
#........................................................................................................
# Query the current setting of the Unfold Method
current_unfold_method = mc.optionVar(q='polyUnfold3dMethod')




def layoutUI():
    # build UI
    windowName = 'pfxUVsLayoutUI'
    windowTitle = 'djPFXUVs Layout'
    
    if mc.window(windowName, exists=True):
        mc.deleteUI(windowName, window=True)
    
    mc.window(windowName, title=windowTitle)
    mc.columnLayout(columnAttach=("both", 5), rowSpacing=10, columnWidth=400)
    mc.rowColumnLayout( numberOfColumns=2, columnWidth=[(1, 100),(2, 220)], columnAttach=[(1, 'both', 5), (2, 'both', 0)], columnAlign=[(1,'left'),(2,'left')])
    
    # uv set dropdown menu           
    mc.text( label='UV Set ')
    mc.optionMenu('UVSetSourceSelection', label='', cc=pfxUVSetSourceCC)
    
    mc.text( label='UV Set ')
    mc.optionMenu('UVSetTargetSelection', label='', cc=pfxUVSetTargetOptionVar)
    
    mc.setParent('..')
    mc.rowColumnLayout( numberOfColumns=2, columnWidth=[(1, 100),(2, 100)], columnAttach=[(1, 'both', 5), (2, 'both', 0)], columnAlign=[(1,'left'),(2,'left')])




    # layout types
    mc.text( label='UV Layout Type')
    mc.optionMenu('UVLayout', label='', cc=pfxUVLayoutSetOptionVar)
    
    # layout options
    mc.text( 'UVLayoutParam1Text', label='')
    mc.floatField('UVLayoutParam1Val', minValue=0, maxValue=0.05, precision=3, value=0, cc=pfxUVLAyoutParam1SetOptionVar)
    
    mc.setParent('..')




    # doIt button
    mc.button('pfxUVDoItButton', label='', command=pfxUVLayoutDoIt, h=50)
        
    # fill in the blanks
    refreshWindow()
    
    # create scriptJob to refresh UI if selection changes
    mc.scriptJob(parent=windowName, event=['SelectionChanged',refreshWindow] )




    mc.showWindow(windowName)
    
#........................................................................................................
def refreshWindow():
    
    # clear the existing menu items
    menus = ('UVSetSourceSelection', 'UVSetTargetSelection', 'UVLayout')
    for m in menus:
        menuItems = mc.optionMenu(m,q=True,ill=True)
        if menuItems is not None:
            mc.deleteUI( menuItems, mi=True)
            
    # rebuild 'UVSetSourceSelection'
    selList = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selList)
    uvSets = getUVSets(selList)
    for s in uvSets:
        mc.menuItem( parent=menus[0], label=s  )    
    if len(uvSets)>1:
        mc.menuItem( parent=menus[0], label='' )
        
    # rebuild 'UVSetTargetSelection' (build drop-down later - depends on source)
    mc.menuItem( parent=menus[1], label=''  )    
 
    # rebuild 'UVLayout'
    layoutList = ('2x2', '3x3', '4x4', 'grass')
    for layout in layoutList:
        mc.menuItem( parent=menus[2], label=layout )    
    
    # apply prefs
    if len(uvSets):
        if mc.optionVar(exists='pfxUVSetSource'):
            pref_uvSet = mc.optionVar(q='pfxUVSetSource')
            if pref_uvSet=='' and len(uvSets)>1:
                i=len(uvSets)+1
            else:
                i = pfxUVMenuArrayIndex(pref_uvSet,uvSets)
            mc.optionMenu(menus[0], edit=True, sl=i)
            
        # now build target drop-down            
        if mc.optionMenu(menus[0], q=True, v=True)=='':
            #  is a special case where target must be source uv set
            mc.optionMenu(menus[1], edit=True, sl=1)
            mc.optionMenu(menus[1], edit=True, enable=False)
        else:
            mc.optionMenu(menus[1], edit=True, enable=True)
            for s in uvSets:
                mc.menuItem( parent=menus[1], label=s  )    
            mc.menuItem( parent=menus[1], label=''  )    
            if mc.optionVar(exists='pfxUVSetTarget'):
                pref_uvSet = mc.optionVar(q='pfxUVSetTarget')
                if pref_uvSet=='':
                    i=len(uvSets)+1
                else:
                    i = pfxUVMenuArrayIndex(pref_uvSet,uvSets)
                mc.optionMenu(menus[1], edit=True, sl=i)
            
    if mc.optionVar(exists='pfxUVLayout'):
        pref_uvLayout = mc.optionVar(q='pfxUVLayout')
        i = pfxUVMenuArrayIndex(pref_uvLayout,layoutList)
        mc.optionMenu(menus[2], edit=True, sl=i)
    else:
        pref_uvLayout = '2x2'
            
    refreshLayoutParam1(pref_uvLayout)
    
    # doIt button
    if selList.length():
        doItLabel = 'Layout UVs'
        doItState = True
        doItBgc = (0.6, 0.7, 0.6)
    else:
        doItLabel = '::: You need to select at least 1 poly mesh :::'
        doItState = False
        doItBgc = (0.7, 0.7, 0.5)
    
    mc.button('pfxUVDoItButton', edit=True, label=doItLabel, bgc=doItBgc, enable=doItState )




            
#........................................................................................................
def refreshLayoutParam1(layout):
    mc.text( 'UVLayoutParam1Text', edit=True, label=layoutParam1Text(layout))
    mc.floatField('UVLayoutParam1Val', edit=True, v=pfxUVLAyoutParam1GetOptionVar(layout))
    
#........................................................................................................
def layoutParam1Text(layout='2x2'):
    t = ''
    if layout == '2x2' or layout == '3x3' or layout == '4x4':
        t = 'tile separation'
    elif layout == 'grass':
        t = 'blade width'
    return t




#........................................................................................................
# get menu choices and launch 
def pfxUVLayoutDoIt(*args):
    refresh = 0
    
    # uvset is one or all
    uvSetSource = mc.optionMenu('UVSetSourceSelection', q=True, v=True)
    uvSetTarget = mc.optionMenu('UVSetTargetSelection', q=True, v=True)
    
    # get new target uv set name
    if uvSetTarget == '':
        result = mc.promptDialog(title='New Target UV Set Name',
                message='Enter Name: ',
                text='%s_copy' % uvSetSource,
                button=['OK', 'Cancel'],
                defaultButton='OK',
                cancelButton='Cancel',
                dismissString='Cancel')




        if result == 'OK':
            uvSetTarget = mc.promptDialog(query=True, text=True)
            refresh +=1
        else:
            print('    chickened out before doing anything')
            return
    
    # layout
    layout = mc.optionMenu('UVLayout', q=True, v=True)
    
    # param1
    param1 = mc.floatField('UVLayoutParam1Val', q=True, v=True)
    
    # layout uv's
    if layout == 'grass':
        grassLayout(uvSetSource, param1, uvSetTarget)
    else:
        leafLayout(uvSetSource, float(layout[0]), param1, uvSetTarget)
        
    # new uv sets
    if refresh:
        refreshWindow()




#........................................................................................................
def pfxUVSetCopy(uvSetSource='map1', uvSetTarget='map2'):
    pass




#........................................................................................................
def getUVSets(selList):   
    ''' get list of all possible uv sets from selected objects shapeNodes '''
    uvSets = []
    pathToShape = om.MDagPath()
    selListIter = om.MItSelectionList(selList, om.MFn.kMesh)
    while not selListIter.isDone():
        pathToShape = om.MDagPath()
        print('    %s' % pathToShape.partialPathName())
        selListIter.getDagPath(pathToShape)
        shapeFn = om.MFnMesh(pathToShape)
        uvSetsThisMesh = []
        shapeFn.getUVSetNames(uvSetsThisMesh)
        
        for s in uvSetsThisMesh:
            if s not in uvSets:
                uvSets.append(s)
                
        selListIter.next()
        
    return uvSets




#........................................................................................................
# menu change commands
def pfxUVSetSourceCC(uvSet):
    pfxUVSetSourceOptionVar(uvSet)
    refreshWindow()




#........................................................................................................
# optionVars handlers
#
def pfxUVSetSourceOptionVar(uvSet):
    mc.optionVar(sv=('pfxUVSetSource', uvSet))




def pfxUVSetTargetOptionVar(uvSet):
    mc.optionVar(sv=('pfxUVSetTarget', uvSet))




def pfxUVLayoutSetOptionVar(uvLayout):
    mc.optionVar(sv=('pfxUVLayout', uvLayout))
    refreshLayoutParam1(uvLayout)
    
def pfxUVLAyoutParam1SetOptionVar(val):
    layoutParamText = mc.text( 'UVLayoutParam1Text', q=True, label=True)
    if layoutParamText == 'tile separation':
        mc.optionVar(fv=('pfxUVTileSeparation', val))
    elif layoutParamText == 'blade width':
        mc.optionVar(fv=('pfxUVBladeWidth', val))




def pfxUVLAyoutParam1GetOptionVar(layout):
    v = 0.0
    if layout == '2x2' or layout == '3x3' or layout == '4x4':
        if mc.optionVar(exists='pfxUVTileSeparation'):
            v = mc.optionVar(q='pfxUVTileSeparation')
    elif layout == 'grass':
        if mc.optionVar(exists='pfxUVBladeWidth'):
            v = mc.optionVar(q='pfxUVBladeWidth')
    return v
#........................................................................................................
def pfxUVMenuArrayIndex(item, list):
    # menu item lists are 1 based
    try:
        i=list.index(item) + 1
    except:
        i=1
        
    return i




#........................................................................................................
# compute constants for the leaf layout math
def leafLayoutConstants(subdivs, separation):
    s = 1.0/subdivs - separation    # scale
    x = (1-s)/2                     # pivot back to 0.5,0.5 after scale
    o = {2:1.0/(subdivs*2), 3:1.0/subdivs, 4:1.0/(subdivs*2)}




    # build cell offset table (there's gotta be a better way!)
    if subdivs == 2:
        cellOffsets = ((-o[2],-o[2]), (o[2],-o[2]), (-o[2],o[2]), (o[2],o[2]))
    elif subdivs == 3:
        cellOffsets = ((-o[3],-o[3]), (0,-o[3]), (o[3],-o[3]), (-o[3],0), (0,0), (o[3],0), (-o[3],o[3]), (0,o[3]), (o[3],o[3]))
    elif subdivs == 4:
        cellOffsets = ((-3*o[4],-3*o[4]), (-o[4],-3*o[4]), (o[4],-3*o[4]), (3*o[4],-3*o[4]), (-3*o[4],-o[4]), (-o[4],-o[4]), (o[4],-o[4]), (3*o[4],-o[4]), (-3*o[4],o[4]), (-o[4],o[4]), (o[4],o[4]), (3*o[4],o[4]), (-3*o[4],3*o[4]), (-o[4],3*o[4]), (o[4],3*o[4]), (3*o[4],3*o[4]))




    return s,x,cellOffsets
#........................................................................................................
def leafLayout(uvSet='map1', subdivs=3, sep=0.01, uvSetTarget=''):
    '''
    leafLayout(uvSet, subdivs, separation [,uvSetTarget])
        Scale and offset the shell uvs from uvSet into the number of tiles defined by subdivs and separation
        If uvSet == '' then process all uvsets.
        subdivs can be 2, 3 or 4, resulting in 4, 9 or 16 tiles
        separation, the distance between tiles, should be between 0 and 0.1 (large values will create strange layouts)
    '''
# Set the Unfold Method to Legacy
    mc.optionVar(sv=('polyUnfold3dMethod', 0))
    
    print('%s leafLayout start' % (__name__))
    
    # check arguments
    if subdivs not in [2,3,4]:
        print('subdivs=%i not valid. Must be 2, 3 or 4.' % (subdivs))
        return
    
    f = -1
    if sep<0:
        f=0
    elif sep>0.05:
        f=0.05
    if f != -1:
        print('sep=%d outside range 0 to 0.05 Using %d' % (sep,f))
        sep=f     
    
    # get the constants for the layout math
    s, x, cellOffsets = leafLayoutConstants(subdivs, sep)
    
    # step through the objects in selection list
    selList = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selList)
        
    if selList.isEmpty():
        print('    Nothing selected. Select a poly mesh and try again')
        return




    selListIter = om.MItSelectionList(selList, om.MFn.kMesh)
    
    while not selListIter.isDone():
        pathToShape = om.MDagPath()
        selListIter.getDagPath(pathToShape)
        shapeFn = om.MFnMesh(pathToShape)
        
        uvShellArray = om.MIntArray()
        uArray = om.MFloatArray()
        vArray = om.MFloatArray()
        
        shells = om.MScriptUtil()
        shells.createFromInt(0)
        shellsPtr = shells.asUintPtr()
    
        print('    %s' % pathToShape.partialPathName())
        
        # check specified uvSet exists on this mesh
        uvSets = []
        shapeFn.getUVSetNames(uvSets)
        if uvSet != '':
            if uvSet in uvSets and shapeFn.numUVs(uvSet):
                if uvSetTarget != '' and uvSetTarget != uvSet:
                    uvSet = shapeFn.copyUVSetWithName(uvSet, uvSetTarget)
                uvSets = [uvSet]                
            else:
                print('        **** uv set %s not found.' % (uvSet))
                print('')
                selListIter.next()
                continue
              
        uvSetsString=''
        for uvs in uvSets:
            uvSetsString += uvs + ', '
        print('        uvSets: %s' % (uvSetsString[:-2]))
        print('')
        
        # remember current uv set
        currentUVSet = shapeFn.currentUVSetName()
    
        for thisUVSet in uvSets:
            # thisUVSet needs to be current
            shapeFn.setCurrentUVSetName(thisUVSet)   
            
            print('        %s' % (thisUVSet))
            shapeFn.getUvShellsIds(uvShellArray, shellsPtr, thisUVSet)
            print('            %s shells' % shells.getUint(shellsPtr))
            
            shapeFn.getUVs(uArray, vArray, thisUVSet)
            print('            %s uvs' % uArray.length())
            
            uvDict = {}
            uvOffDict = {}
            for i in range(uArray.length()):
                if not uvShellArray[i] in uvDict:
                    uvOffDict[uvShellArray[i]] = random.randint(0,pow(subdivs,2)-1)
                    uvDict[uvShellArray[i]] = [i]
                else:
                    uvDict[uvShellArray[i]].append(i)
                
            # compute the new uv's                    
            for i in range(shells.getUint(shellsPtr)):
                for j in uvDict[i]:
                    uArray.set((x + uArray[j]*s + cellOffsets[uvOffDict[i]][0]),j)
                    vArray.set((x + vArray[j]*s + cellOffsets[uvOffDict[i]][1]),j)
                
            # write new uv's
            shapeFn.setUVs(uArray, vArray, thisUVSet)
            print('            done')
            print('')
            
            uvShellArray.clear()
            uArray.clear()
            vArray.clear()
        
        # restore current uv set       
        shapeFn.setCurrentUVSetName(currentUVSet)   
     
        selListIter.next()
        print('')
# Set the Unfold Method back to its original setting
    mc.optionVar(sv=('polyUnfold3dMethod', current_unfold_method))
    
    print('%s leafLayout done\n' % (__name__))
      
#........................................................................................................
def grassLayout(uvSet='map1', bladeWidth=0.01, uvSetTarget=''):
    '''   
    grassLayout(uvSet, bladeWidth [,uvSetTarget])
        Scales the uv's so each blade covers a strip specified by bladeWidth
        Offsets each shell randomly along u
    '''
    
    print('%s grassLayout start' % (__name__))




    # validate arguments
    f = -1
    if bladeWidth<0:
        f=0
    elif bladeWidth>0.5:
        f=0.5
    if f != -1:
        print('bladeWidth=%d outside valid range 0 to 0.5 Using %d' % (bladeWidth,f))
        bladeWidth=f     




    x = (1-bladeWidth)/2




    selList = om.MSelectionList()
    om.MGlobal.getActiveSelectionList(selList)
    if selList.isEmpty():
        print('    Nothing selected. Select a poly mesh and try again')
        return




    selListIter = om.MItSelectionList(selList, om.MFn.kMesh)
    
    while not selListIter.isDone():
        pathToShape = om.MDagPath()
        selListIter.getDagPath(pathToShape)
        shapeFn = om.MFnMesh(pathToShape)
        
        uvShellArray = om.MIntArray()
        uArray = om.MFloatArray()
        vArray = om.MFloatArray()
        
        shells = om.MScriptUtil()
        shells.createFromInt(0)
        shellsPtr = shells.asUintPtr()
    
        print('    %s' % pathToShape.partialPathName())
        
        # check specified uvSet exists on this mesh
        uvSets = []
        shapeFn.getUVSetNames(uvSets)
        if uvSet != '':
            if uvSet in uvSets and shapeFn.numUVs(uvSet):
                if uvSetTarget != '' and uvSetTarget != uvSet:
                    uvSet = shapeFn.copyUVSetWithName(uvSet, uvSetTarget)
                uvSets = [uvSet]                
            else:
                print('        **** uv set %s not found.' % (uvSet))
                print('')
                selListIter.next()
                continue
              
        uvSetsString=''
        for uvs in uvSets:
            uvSetsString += uvs + ', '
        print('        uvSets: %s' % (uvSetsString[:-2]))
        print('')
        
        # remember current uv set
        currentUVSet = shapeFn.currentUVSetName()
    
        for thisUVSet in uvSets:
            # thisUVSet needs to be current
            shapeFn.setCurrentUVSetName(thisUVSet)   
            
            print('        %s' % (thisUVSet))
            shapeFn.getUvShellsIds(uvShellArray, shellsPtr, thisUVSet)
            print('            %s shells' % shells.getUint(shellsPtr))
            
            shapeFn.getUVs(uArray, vArray, thisUVSet)
            print('            %s uvs' % uArray.length())
            
            uvDict = {}
            uvOffDict = {}
            for i in range(uArray.length()):
                if not uvShellArray[i] in uvDict:
                    uvOffDict[uvShellArray[i]] = random.uniform(-x,x)
                    uvDict[uvShellArray[i]] = [i]
                else:
                    uvDict[uvShellArray[i]].append(i)
            
            # compute the new u's                  
            for i in range(shells.getUint(shellsPtr)):
                for j in uvDict[i]:
                    uArray.set((x + uArray[j]*bladeWidth + uvOffDict[i]),j)
              
            # write new u's  
            shapeFn.setUVs(uArray, vArray, thisUVSet)
            print('            done')
            print('')
            
            uvShellArray.clear()
            uArray.clear()
            vArray.clear()
        
        # restore current uv set       
        shapeFn.setCurrentUVSetName(currentUVSet)
     
        selListIter.next()
        print('')
    
    print('%s grassLayout end\n' % (__name__))
    # Deselect all selected objects
    mc.select(clear=True)
    
#........................................................................................................

Here is video tutorial on how to use the tool: https://youtu.be/Dqr70n1gU1c

Report

Centering Pivot Point to World Origin for Selected Objects with Maya Python3 Script

Tutorial / 25 October 2023

The script's purpose is to manipulate the pivot point of selected objects in the 3D workspace. It only works with Maya.

The function, center_pivot_to_world_origin(), operates on the currently selected objects in the Maya scene. It sets the pivot point of each selected object to the world origin, which is the point (0,0,0) in the 3D space.

The function first retrieves the list of currently selected objects using the cmds.ls(selection=True) command. It then iterates over each selected object, and for each one, it sets the pivot point to the world origin (0,0,0) using the cmds.xform(obj, pivots=world_origin, worldSpace=True) command.

The worldSpace=True argument ensures that the pivot point is set in world space, not in the object's local space.

This script is useful when you want to align the pivot points of multiple objects to a common point in the 3D space, such as the world origin. This can be helpful in various 3D modeling and animation tasks, such as preparing objects for rigging, or aligning objects for a group operation.

import maya.cmds as cmds


def center_pivot_to_world_origin():
    # Get the currently selected objects
    selection = cmds.ls(selection=True)


    # Define the world origin
    world_origin = [0, 0, 0]


    # Loop through each selected object
    for obj in selection:
        # Set the pivot point to the world origin
        cmds.xform(obj, pivots=world_origin, worldSpace=True)


# Call the function
center_pivot_to_world_origin()

Please note that the script assumes that you have at least one object selected in Maya. If no objects are selected, the cmds.ls(selection=True) call will return an empty list, and the loop will not execute. If you want the script to handle the case where no objects are selected, you could add a check at the beginning of the function to see if any objects are selected, and if not, print a message and return early.


Report

Alembic (.abc) Export Settings

Tutorial / 06 October 2023

To export a .abc asset from Maya for use in other programs, follow these steps:

1. Ensure ABC Export and Import Plugins are Loaded:

   - Go to `Windows > Settings/Preferences > Plug-in Manager`.

   - Make sure both `AbcExport.mll` and `AbcImport.mll` are checked for "Loaded" and "Auto load". This ensures that the Alembic export and import plugins are enabled.

2. Select the Geometry to Export:

   - Choose the geometry you want to export. Make sure it's selected in the viewport.

3. Access the Alembic Export Options:

   - At the top of the Maya window, navigate to `Cache > Alembic Cache > Export Selection to Alembic...`.

4. Set General Options:

   - If you're exporting a static asset (non-moving), set "General Options" to "Current frame". This means it will export the geometry exactly as it appears in the current frame.

5. Configure Advanced Options:

   - Ensure the following settings are enabled under "Advanced Options":

     [x] Strip Namespaces: Removes any namespaces from the exported geometry. Namespaces are a way to organize objects in Maya, but they might not be supported in other programs.

     [x] UV Write: Ensures that UV (texture) coordinates are exported along with the geometry. This is essential for maintaining textures in other programs.

     [x] World Space: Exports the geometry in world coordinates, which is typically what you want when transferring assets between different software.

     [x] Write UV Sets: This ensures that all UV sets are exported. This is critical if you have multiple sets of texture coordinates.

     [x] Filter Euler Rotations: Helps to avoid gimbal lock issues with rotations. This can be important for maintaining correct orientation.

     [x] Write Visibility: Exports information about whether the object is visible or not. This can be useful for things like animation or simulations.

6. Export the Asset:

   - After configuring the settings, click on the "Export" button to save the .abc file.

Following these steps will allow you to manually export assets in a way that ensures compatibility with other programs. Keep in mind that this process may vary slightly depending on the specific versions of Maya and the other software you're working with.


Report

The Light Demystified

Tutorial / 05 October 2023

A comprehensive overview of various lighting techniques used in film, photography, and traditional paintings. Each technique serves a specific purpose and can create distinct visual effects.

1. Front Lighting (Flat Lighting):

This technique involves placing the key light directly in front of the subject, often used in commercials, product shots, or comedy scenes. It results in minimal or indistinct shadows, creating a flat appearance.

2. Loop Lighting:

Loop lighting positions the key light at the top right or left side of the subject, usually at eye level for faces. This creates a small shadow from the nose on the cheek, offering a subtle three-dimensional effect. Fill and background lights can be added to enhance the subject's prominence.

3. Rembrandt Lighting:

Similar to loop lighting, Rembrandt lighting places the key light at a 45-degree angle, casting a longer shadow from the nose onto the cheek. This technique is recognized for forming a distinct triangle of light on one side of the subject's face. It may also incorporate fill and backlighting for further control over shadows.

4. Split Lighting:

Split lighting divides the subject's surface into two halves, creating a dramatic and bold effect. The key light is positioned at a 90-degree angle from the subject, and fill light can be used to soften shadows. Additionally, background light can be employed for added depth.

5. Paramount Lighting (Butterfly Lighting):

 Originating from the 1950s Paramount company, this technique illuminates female faces with a key light positioned in front and above the subject. It produces a subtle shadow beneath the nose, just above the upper lip. Rim or backlighting can be added to define hair or separate the subject from the background.

6. Reversed Butterfly Lighting:

In this technique, the key light is placed under the subject, resulting in a more dramatic and potentially eerie appearance.

7. Backlighting:

This technique relies solely on a backlight, creating strong highlights and pronounced silhouettes of the subject.

8. Chiaroscuro Lighting (Stage Lighting):

Derived from the Italian words for "light" and "dark," Chiaroscuro lighting, often associated with Caravaggio, involves a powerful key light from the side and a background light to delineate the subject from the darker surroundings.

9. Film Noir Lighting (a.k.a. Chiaroscuro Lighting):

A variation of Chiaroscuro lighting, Film Noir lighting emphasizes strong key and backlighting, often with minimal or no fill light. It draws inspiration from Caravaggio's techniques for a distinct, moody atmosphere.


Each of these techniques serves to create specific moods, highlight forms, and emphasize certain aspects of the subject. Understanding and utilizing these techniques can greatly enhance visual storytelling in film, photography, and painting.

Please find additional resources for you to explore and recognize various lighting styles:

- [Evan Richards Photography](https://www.evanerichards.com)

- [Film Grab](https://film-grab.com/)

Report