Sunday 18 November 2018

'Kit-setting' 3D for printing with Python in Maya

I've been toying about a lot with prepping models for 3D printing and one of those things where problems pop up is working out how to cleanly break up a model to minimise the need for supports.  The same goes for how to deal with round shapes like spheres - if the bottom is on the bed, you can lose some of the 'sphere-ness'.



One way is to split things in half so that they have a flat surface, then print in parts and glue back.  That works, but one thing that can be difficult is making sure things line up.  That's where some guide pins and sockets come in.


Booleans

The most obvious way here is to consider modelling socket insets and pins separately, then boolean them into the mesh.  To create this, I found using a cylinder, removing its base and then extruding the edges out to form a 'hat' worked great.  A pin can be created in the same way - with a smaller radius to fit 'inside' the socket.
Pin (red) and Socket (blue)

Making sure that the edges of the brim area cut all the way outside the mesh act like a slice, where as the cylinder punches the hole or pin.

All about the slice-n-dice

To cut a socket, the mesh was simply booleaned with 'difference' to cut the hole.
To add the pin, the mesh used an 'intersection' boolean operation which removed anything 'outside' this volume.

"Doctor - the operation was a success."


If its behind the fence...

It was interesting to note that in most cases booleans work best with closed volumes, however even without the rear area extruded and closed, Maya still treated anything behind the faces as though it was 'within' a virtual volume.  This meant for very long shapes there was no need to create a large shape to enclose it within.

No closed volume needed... Just make sure normals face the same way

This technique was perfect for what I needed.  It wasn't guaranteed to work with everything (booleans can be temperamental at times) and each time you do a boolean, you want to make sure to trash the construction history (which is generally what I do when modelling anyway)


Time consuming!

While cool, this is a fairly frustrating process making the two shapes for socket and pin, and also having to duplicate the mesh twice to perform the two operations as well as clearing history and repeating...  This is where scripting really helps a lot!  Automation of all these steps makes life so much simpler.  Grouping the two shapes also helps in transforming the socket in the model.  And it wasn't actually very hard to do either!


Functions

The first thing I had considered is how I was going to approach creating and slicing the mesh.  I figured for ease of re-use that it would be a good idea to break the tool into two functions.


Creating the slicing objects (hats)

One function has to create the cutting forms.  It should automatically make the pin smaller for us, but it should also allow us to specify the amount to adjust it as well.  Once created, it should group it and make it ready for us to move, scale and rotate.

Here's the code.  I've added plenty of comments to assist in explaining how all of this works.

Note that variable names are short, really because I want to try and keep formatting in the blog easy to read (rather than going over two or so lines) in case you wondered.

import maya.cmds as cmds

########################################################################
# FUNCTION : Create socket and pin group
# Creates the socket and pin, then groups for easy placement
# ARGUMENTS : spacing between socket/pin shape edges
########################################################################

def createSockPin(sckDiff):

    ########################################################################
    ## CREATE SOCKET ##
    ########################################################################

    # Create a cylinder to form 'socket'
    sckN = cmds.polyCylinder(n='boolSocket',r=1,h=1,sx=16)

    # Delete the base poly (number 16) for the cylinder
    # Note that faces start at outside (0-15), base is 16 and top 17
    cmds.delete('{0}.f[16]'.format(sckN[0]))

    # Extrude selected edges out on Z
    cmds.polyExtrudeEdge('{0}.e[0:15]'.format(sckN[0]),ltz=4)
    
    ########################################################################
    ## CREATE PIN ##
    ########################################################################

    # Create a cylinder to form 'socket'
    # Then we follow the same process as above
    pinN = cmds.polyCylinder(n='boolPin',r=(1.0-sckDiff),h=1,sx=16)
    cmds.polyMoveFacet('{0}.f[17]'.format(pinN[0]),ty=-sckDiff)
    cmds.delete('{0}.f[16]'.format(pinN[0]))

    # Extrude selected edges out on Z
    cmds.polyExtrudeEdge('{0}.e[0:15]'.format(pinN[0]),ltz=4+sckDiff)
    
    ########################################################################
    ## Group for ease of adjustment, etc of the two parts
    ########################################################################

    sckGrp = cmds.group(sckN[0],pinN[0],n='sockAndPin')

    # Make sure only the group is selected
    cmds.select(sckGrp)

    # Finally return the group name
    return sckGrp


Performing the booleans

The other function does all the slice-n-dice.  I wanted to be able to select the mesh to boolean and let the tool automatically deal with selecting the slicing items in the group, duplication of the mesh, boolean operations and clearing history.

########################################################################
# FUNCTION : Perform boolean operation using selected mesh and the
# Socket and Pin set that was created
# ARGUMENTS :  Name of Socket and pin group
########################################################################

def cutParts(spGrp):

    # Grab the current selected object for socket-ization ;-)
    objOrg = cmds.ls(sl=True)
    
    # Get the names of the boolean objects
    nPin = [i for i in cmds.listRelatives(spGrp,c=True) if 'Pin' in i][0]
    nSck = [i for i in cmds.listRelatives(spGrp,c=True) if 'Socket' in i][0]
        
    if objOrg:

        # Duplicate for pin
        objDupPin = cmds.duplicate(objOrg,n='pinDuplicate')
        
        # Apply the boolean 'intersection' for the pin
        # Make sure to clear the history
        cmds.polyBoolOp(objDupPin,nPin,op=3)
        cmds.delete(all=True,ch=True)
        
        # Apply the boolean 'difference' for the socket
        # Make sure to clear the history...  Just a note:
        # I'm just deleting ALL scene history - quick and dirty ;)
        cmds.polyBoolOp(objOrg,nSck,op=2)
        cmds.delete(all=True,ch=True)

    else:
        cmds.confirmDialog(m='No object was selected')

All good - how do you use it???

Good question.  Obviously we have two functions - so all we do is this:

# Create a new socket and pin group with a 0.05 unit spacing
pinSockGroup = createSockPin(0.05)

Then, position the grouped socket and pin to where you need them in your 3D object.  You may want to also resize the group - and sometimes you may need to select the 'brim' edges of our two hat-shaped objects to cut through the area you're about to split.

Once its all in place, select the object you want to slice.  Then call this function:

# Slice the selected object
cutParts(pinSockGroup)

Viole!  All done (hopefully)


Once its all over...

You can break up those models into mini kitsets to help create cleaner 3D prints.  This example is of a classroom exercise I do building 'Finn' from Adventure Time.  The character was in a T-pose, and to keep it clean it mean't simply cutting off the arms and rotating them vertically.


As the character was taller than the printer's area, slicing the body in half at the waste meant a larger character could be printed as well.

Happy kitsetting!

0 comments:

Post a Comment