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

Building a Career in Animation/VFX

General / 15 March 2024

To increase your chances of securing work in animation/VFX, whether in a staff position or as a freelancer, it is important to focus on both the quality of your work and your level of exposure. Here are some strategies to improve your portfolio and increase your visibility in the industry:

Your Web Site 

  • Create a Links Page: Link with artists, producers, and coworkers to improve your search engine ranking and show your involvement in the community.
  • Clarify Your Availability: Make it clear on your site that you're available for work.
  • Resume Page: Ensure your resume page is well-structured and includes multiple versions (HTML, PDF, text file) for easy access.
  • Reel: Showcase your professional work in a video format. YouTube allows you to upload 4K videos and share online. Ensure your video is smooth and well-structured. Always include your name, position, and contact information at the beginning and end of your reel. Do not exceed 1-2 minutes of video time. 1 minute of your best work is sufficient to demonstrate your creativity.
  • "News" or "Updates" Section: This should be easily accessible to show recent updates or new work.
  • Good place to get started: Artstation, Behance, Wix.

Online Forums

  • Be Active: Engage in forums like CGTalk, CGChannel, 3dTotal, and Gamasutra by posting work, providing feedback, and starting threads.
  • Professional Presence: Use your real name, fill in your profile, and post constructive comments.
  • Signature Link: Include a signature with a link to your website and job title.

Finding Companies/Clients/Job Postings

  • Research Companies: Use resources like Gamasutra, MotionDesignDirectory, GameDevMap, AnimationCareer and CreativeHeads to find companies hiring.
  • Direct Outreach: Contact studios directly, mentioning your skill set and linking to your portfolio/reel.
  • Networking: Look at the resumes of artists you admire to find companies they've worked for and apply there.

Before Applying

  • Prepare Your Website: Ensure it's polished with updated work history, resume, and your contact information.
  • Determine Your Rates: Know your high and low rates.
  • Prepare for Remote Work: Have the latest versions of your listed programs working on your home machine.
  • Consider Relocation: Be ready to relocate if necessary.

Increasing Your Chances 

  • Personal Projects: Work on projects that interest you to showcase your skills and interests.
  • Online Presence: Use social media platforms to stay connected with the industry and recruiters.
  • Collaboration: Work on group projects to expand your network and portfolio.
  • Budgeting: Understand the value of your work and negotiate accordingly.
  • Payment Terms: Decide on your preferred payment method and terms.  Note: Usually, it takes two to three weeks after you submit your invoice for the check to reach your mailbox.

Continuous Learning and Skill Development

  • Stay Updated: The animation industry evolves rapidly. Keep learning new software, techniques, and industry trends. Participate in workshops, webinars, and online courses. Here are some online schools I would recommend, iAnimate, Rebelway, or TheGnomonWorkshop
  • Showcase Your Skills: Create a portfolio that not only showcases your work but also demonstrates your ability to learn and adapt. Include projects that highlight your versatility and range of skills.

Networking and Community Engagement

  • Join Professional Groups: Engage with professional animation or VFXs groups on platforms like LinkedIn, Facebook, Dribbble, and Instagram. These groups can provide networking opportunities and access to job postings.
  • Attend Industry Events: Participate in animation and VFXs conferences, festivals, and meetups. These events can offer networking opportunities, learning experiences, and sometimes job fairs.

Building a Strong Online Presence

  • Utilize Social Media: Platforms like Instagram, X(Twitter), and LinkedIn are great for showcasing your work and engaging with the animation/VFXs community. Regularly post updates, behind-the-scenes content, and industry news.
  • Blog or Vlog: Start a blog or vlog to share your journey, insights, and tips. This can help establish your expertise and attract potential clients or employers.

Tailoring Your Application

  • Customize Your Resume: Tailor your resume for each job application, highlighting the skills and experiences most relevant to positions.
  • Prepare a Portfolio: Ensure your portfolio is up-to-date and includes a variety of work samples. Highlight projects that demonstrate your range of skills and creativity.

Financial Planning

  • Set Your Rates: Research the market to determine competitive rates for your services. Be prepared to negotiate based on your experience and the scope of the project.
  • Budget for Equipment and Software: Invest in the latest software and equipment to stay competitive. Consider setting aside a budget for ongoing education and professional development.

Legal and Contractual Considerations

  • Understand Contracts: Familiarize yourself with standard contracts and terms of employment in the animation industry. Consider consulting with a legal professional if you're unsure about any aspect of a contract.
  • Work Visas and Permits: If you're considering relocating for work, research the visa and permit requirements for your country of origin and the country where you plan to work.

By following these strategies, you can significantly improve your chances of securing work in the animation and VFX industry, whether through staff positions or freelance opportunities.

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

Creating a Digital Product Shot: A Comprehensive Tutorial

Making Of / 27 October 2023

Welcome to the first in a series of blogs where I will guide you through the process of creating a stunning digital product shot. This tutorial is designed for intermediate artists who are eager to expand their skills. However, even if you're a beginner, you can still follow along and learn valuable techniques. Feel free to ask any questions or share your thoughts in the comments section.

In this tutorial, we'll embark on a journey from visual design development to the final rendering of the product for presentation. We'll also explore how to print your prototype using Shapeways.

Please, note that this tutorial is intended for artists with a basic understanding of 3D programs like Maya or Zbrush. If you're a beginner, it would be beneficial to familiarize yourself with these programs before proceeding.

Materials Needed


For this tutorial, you'll need the following materials:

  • A pen and paper for design sketches
  • Chavant clay for sculptingA digital photo camera
  • 3DF Zephyr for 3D scanning

Design Development

The first step in our process is to develop a concept for our design. This involves brainstorming ideas, sketching initial concepts, and refining our design. For this tutorial, we'll be designing a belt buckle with a wolf-like head.

Start by sketching the base of the belt buckle. The base should have rounded corners and a slightly concave shape to give it depth. The head of the buckle will be placed on top of the base, so it's important to have a clear idea of the base shape before moving on to the head design.

Next, we'll look for inspiration for the head design online. We'll focus on key features that define the creature and ignore details like fur to keep the design simple and emphasize hard edges. This design approach is inspired by the Renaissance period, also known as the Age of Gothic Dark Arts (mid 12th -16th century).

After we have our Design Card (DC), we'll move on to the sculpting process.

Sculpting The Prototype

Sculpting the product out of clay is a fun and rewarding process. During this stage, we're more concerned with the overall feel of the design than precision. It's important to remember that the final digital prototype may look slightly different when 3D printed, so it's always a good idea to have a physical model to start with.

For this project, we'll be using Chavant hard in green tint. This allows us to sculpt the edges more precisely once we've shaped the overall form. To soften the hard clay, we'll use a desktop lamp with a bulb.

Once we have the final sculpt of the belt buckle, we'll move on to the 3D scanning process.

3D Scan (Convert into point-cloud) in 8 Simple Steps

Step 1: Preparation

Before you start the scanning process, you will need a good light stage and a camera. A smartphone camera can be used for this purpose. For better quality, it's recommended to use a camera with a higher aspect ratio. The object you want to scan and the lights should remain fixed while you move the camera to capture multiple pictures.

Step 2: Capturing Images

Once you have taken the necessary pictures, save them into a folder on your computer. The next step is to convert these pictures into 3D data.


Step 3: Installing 3DF Zephyr

To convert your pictures into 3D data, we'll use 3DF Zephyr Pro (Free version). You can download the program from the 3D Flow website https://www.3dflow.net/3df-zephyr-photogrammetry-software/.

Step 4: Converting Pictures to 3D Data

After launching the program, you'll need to upload your pictures. From the top menu, click on Workflow > New Project. A New Project Window will pop up. Make sure you have 'Check online for precomputed camera calibration' highlighted (orange box). Click Next to go to the second window. Here, you'll load your pictures of your product. Press the plus sign (+) to load all the pictures from the folder. After all the pictures are loaded into the window, click Next again. The program will automatically check your pictures and identify the camera and other settings that will help 3DZephyr with the conversion.

Now, click on Next. This window is for the scan options. Here, you'll specify how much detail you want the program to capture from the photos. Depending on the project, the options may vary. For this project, set it to light conversion.

After you have all the settings matched, click Next and in the next window, click Run to start the point cloud creation. The process might take some time depending on your RAM and CPU power.

Step 5: Checking the Process

After the processing, the program will tell you how many pictures were accepted. If you have more than half pictures rejected, you will need to retake them under better light conditions (the best is cloudy daylight or under a shade). Click Finish to exit the window.


On your screen (3D workspace), you should see points. This is the point cloud that has been captured from your photos and placed in 3D space. To navigate your workspace, click SHIFT + LMB (Left Mouse Button) to zoom in or out, CTRL + LMB to shift or ALT + LMB to rotate.



Step 6: Generating an Additional Point Cloud

Next, generate an additional point cloud that will help with the details. To do this, click on Workflow > Dense Point Cloud Generation. The wizard window will pop up. It is the same process as part A. Press Next to get started. In the next window, check that you are in the presets settings. Set your Category to Close Range and Presets to Ultra.


In the next window, click Run for the process to get started.

Step 7: Mesh Extraction

The next step is to generate the mesh from the point cloud. Click on Workflow > Mesh Extraction to generate the mesh from the point cloud.


Step 8: Generating Texture from the Point Cloud

The last process is to generate texture from the point cloud. It will be available after the mesh extraction is complete. The texture extraction will be as simple as the previous actions. You will only need to set your Max Texture Size to 2048 and Image resolution to 100%. Everything else, leave as defaults.

To export your mesh with the textures, go to Export > export textured mesh or export mesh if you don't want the texture with your model. Set the desired mesh to OBJ and texture format to PNG. This section is done!

And that's it! You have successfully scanned a 3D model from your photos using 3DF Zephyr Pro.


>>> Page 02

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

Concept Sculpting an Orc Bust

General / 24 October 2023

Check out my latest blog post featuring a quick Zbrush concept made entirely in Zbrush. I was inspired to create my own version of an Orc after watching the FlippedNormals tutorial. It's been a while since I've used the program, but I decided to put my skills to the test and whip up this concept in just 30 minutes. I hope you enjoy it as much as I did creating it!

Report

Decoding the Distinction: CG Generalist vs. 3D Generalist - Which Path Fits Your Creative Journey?

Q & A / 16 October 2023

A CG Generalist and a 3D Generalist are often used interchangeably, but there can be some nuanced differences depending on the context and industry. Here's a breakdown of the terms.

CG Generalist

A CG Generalist, short for Computer Graphics Generalist, is an artist with skills in creating computer-generated imagery (CGI) across various mediums, which may include 2D, 3D, animation, and visual effects.

  • CG Generalists can work in a broader range of fields beyond just 3D. They might also be skilled in 2D graphics, motion graphics, and other digital media.
  • They can work in industries such as film, television, advertising, gaming, and more. Their responsibilities can vary widely, from creating visual effects to designing user interfaces for websites.
  • Their skill set is not limited to 3D modeling, texturing, lighting, and animation. They might also be proficient in 2D design, motion graphics, compositing, and other related areas.

3D Generalist

 A 3D Generalist is an artist specializing in creating 3D content. They are skilled in various aspects of 3D production, including modeling, texturing, lighting, rigging, animation, VFX, rendering, and compositing.

  • Their expertise primarily lies within the realm of 3D graphics and animation. While they may have skills in related areas, their main focus is on the 3D pipeline.
  • They are typically found in industries like animation studios, VFX houses, game development, and architectural visualization, where proficiency in 3D content creation is paramount.
  • They are expected to be proficient in all aspects of 3D production, from modeling to compositing, and are often capable of handling entire projects within the 3D realm.

Embarking on the journey towards becoming a proficient 3D Generalist starts with a solid foundation in essential skills such as 3D modeling, texturing, and basic animation. From there, it's crucial to broaden one's expertise by delving into areas like lighting, rigging, and VFX. As proficiency matures, the emphasis shifts towards honing specialized skills within the 3D pipeline. Continuous learning is key; keeping alongside industry trends and mastering evolving software tools ensures a dynamic and thriving career in the world of 3D graphics.

While a CG Generalist has a broader scope encompassing various forms of digital media beyond just 3D, a 3D Generalist is a specialist primarily focused on creating 3D content. Both roles have their own set of advantages and considerations, and choosing between them depends on individual interests and career goals.

Report

Creating a Simple Pop Out Window with Python in Maya

Making Of / 11 October 2023

To create a floating window in Maya using Python, you can use the `cmds` module. To make it look like a fancy window, you might want to customize the appearance using styles and colors.

Here's an example code to create a basic floating window:

---->

import maya.cmds as cmds




def create_window():
    window_name = "Window"
    if cmds.window(window_name, exists=True):
        cmds.deleteUI(window_name, window=True)
    
    window = cmds.window(window_name, title="Window", widthHeight=(300, 150))
    cmds.columnLayout(adjustableColumn=True)
    cmds.text(label="This is a window", align="center", font="boldLabelFont")
    cmds.button(label="Close", command=('cmds.deleteUI("' + window_name + '", window=True)'))
    cmds.button(label="Press Me", command=show_message)
    cmds.showWindow(window)




def show_message(*args):
    cmds.confirmDialog(title='Message', message='It works!', button=['OK'], defaultButton='OK')




create_window()

<----

This code defines a function `create_window()` that creates a Maya window with a title "Window", a label, and a button to close the window. You can customize this code to add more elements and style them according to your requirements.

Report