Showing posts with label face rigging. Show all posts
Showing posts with label face rigging. Show all posts

Friday, February 16, 2024

doodles face rigging

 

character design by: Abigail Munoz

 

character design by: Alan Stewart

Happy Sketching!

Saturday, January 13, 2024

using drawing notes to help plan face rigging deformations

 







character design by Abigail Muñoz (abigailmunoz dot com, rockandroar dot com)

Happy Rigging!

and a Blender scripting tip using python list comprehension to set all data mesh names to match the object name tested in Blender 2.79 - which was helpful before exporting meshes to obj:

import bpy
def matchAllMeshDataAndObjectNames():
    """make meshes object names match data mesh name
    """
    meshObjects = [x for x in bpy.data.objects if x.type == 'MESH']
    for obj in meshObjects:
        if obj.name == obj.data.name:
            continue
        #match naming
        obj.data.name = obj.name

Friday, January 5, 2024

facial modelling

 some facial modelling examples using ZBrush and Blender:


 Monkey, Wolf and Tiger character design by Alan Stewart (alanstewart79 dot blogspot dot com)

Miles character design by Abigail Muñoz (abigailmunoz dot com, rockandroar dot com)

for the face rigging i used action constraints and bendy bones to create the various mesh shapes in Blender

 

Thanks for looking






Wednesday, August 23, 2023

doodle python lock all but selected bones in Blender 2.79

this doodle locks the vertex groups of all bones except the selected bones - it requires to be in either pose mode or weight paint mode.  could be helpful when painting weights.  tested in Blender 2.79.  There may be bugs so please modify/use at your own risk.


import bpy


def lockAllButSelectedBones():
    """lock all but selected bones. requires to be in pose mode or weight paint mode
    """
    meshName = None
    selectedBones = [obj.name for obj in bpy.context.selected_pose_bones]
    
    if not selectedBones:
        raise RuntimeError("requires at least one bone selected")
        
    #get mesh slightly differently whether in pose mode or weight paint mode
    #if in pose mode
    if bpy.context.mode == 'POSE':
        #get mesh from bone
        armatureName = bpy.context.object.name
        #loop all meshes to find mesh parented to armature
        for ob in bpy.data.objects:
            if ob.type == "MESH" and (ob.parent == bpy.data.objects[armatureName]):
                meshName = ob.name
        print("meshName ", meshName)
        
    elif bpy.context.mode == 'PAINT_WEIGHT':
        #if in weight paint mode
        meshName = bpy.context.object.name
    else:
        raise RuntimeError("requires to be in POSE mode or weight paint mode")
    
    
    #lock all vertex groups
    bpy.data.objects[meshName].select = True
    bpy.context.scene.objects.active = bpy.data.objects[meshName] 
    bpy.ops.object.vertex_group_lock(action='LOCK')
       
    #unlock selected bones
    allVertexGroups = bpy.data.objects[meshName].vertex_groups
    for i in range(0,len(allVertexGroups)):
        if allVertexGroups[i].name in selectedBones:
            allVertexGroups[i].lock_weight = False
            
       

Thursday, August 17, 2023

doodle python check if mesh is symmetric in Blender 2.79

doodle for checking if a mesh is symmetric in x-axis tested in Blender 2.79 (useful for checking mesh before trying to mirror skin deformations).  there may be bugs so please modify use at your own risk.

"""how to test in Blender text editor
import bpy
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/snippets/deformation_snippets.py") #change to path to python file
testTool.isMeshSymmetric(mesh="notsymmetricPlane")
"""

import bpy

def isMeshSymmetric(mesh = None):
    """return bool true if mesh is symmetric false otherwise. tested in blender 2.79
    i dont think it requires mesh to be at origin
    @param mesh - string data object name of mesh to check
    """
    if not mesh:
        raise RuntimeError("requires a mesh the data object name")
        
    """
    save all +x vert ids
    save all -x vert ids
    
    if number +x and -v verts dont match break mesh not symmetric
    
    for each +x vert loop through all -x verts
        if found a -x vert closer than 0.000001 go to next +x vert
        else break mesh not symmetric    
    """
    plusxVertIds = []
    negxVertIds = []
    howClose = 0.00001
    if mesh not in bpy.data.objects:
        raise RuntimeError("requires {0} to exist in data objects".format(mesh))
        
    meshObj = bpy.data.objects[mesh]
    
    plusxVertIds = [ vert.index for vert in meshObj.data.vertices if vert.co.x > 0]
    negxVertIds = [ vert.index for vert in meshObj.data.vertices if vert.co.x < 0]
    
    numPlusx = len(plusxVertIds)
    numNegx = len(negxVertIds)
    if numPlusx != numNegx:
        print("couldnt find symmetry number left side vertices {0} and right side vertices {1} dont match".format(numPlusx,numNegx))
        return False
        
    for plusVertId in plusxVertIds:
        plusPos = meshObj.data.vertices[plusVertId].co
        isSymmetric = False
        for negVertId in negxVertIds:
            negPos = meshObj.data.vertices[negVertId].co
            #print("plusPos:",plusPos)
            #print("negPos:",negPos)
            if (abs(plusPos.x + negPos.x) <= howClose) and (abs(plusPos.y - negPos.y) <= howClose) and (abs(plusPos.z - negPos.z) <= howClose):
                isSymmetric = True
                break
        if not isSymmetric: #if cant find symmetric for a single vertex exit
            print("couldnt find symmetry for at least vertex id:"+str(plusVertId))
            return False
    print("great :) mesh is symmetric")
    return True #all mesh must be symmetric - i think dont need to check center verts
     

Wednesday, August 16, 2023

rigging head deformation test and drawover notes

head/neck deformation test for personal work character.  used drawover to indicate some areas that need to be improved.





 


Tuesday, August 15, 2023

rhino blendshape testing in ZBrush

testing lip corner up/dn, brow up/dn and jaw open blendshapes for a personal work character i designed and modeled/sculpted in ZBrush and Blender.


 


Monday, August 14, 2023

Fox (swift) lip corner up/dn brow up/dn blendshapes in ZBrush

was finding it helpful using morph brush in ZBrush to paint the brow blendshape to the default for the eyelid area (needed to polygroup eyelid area so could select it easier) so the brow blendshape didn't affect eyelids.  also was finding it helpful using transform move/rotate on lip corner area to build smile as opposed to relying only on the move brush.  This is for a personal work character Swift i designed, modeled and sculpted in ZBrush and Blender. the shapes are still a work in progress.


Monday, August 7, 2023

doodle dog eyelid modeling

i'm not a professional modeller but found it helpful using a torus to start making eyelids of this personal work character Barky.  using the torus helped with getting same number of eyelid spans on top and bottom lids - helpful for rigging later.  Here is some of the process (the topology is still a work in progress):

after roughly scaling/fitting torus eyelid to overall face (using eyes as reference).  for making the torus eyelid in Blender needed to specify number of edges to create and deleted unneeded back faces:

after adjusting some of the surrounding spans of eyelid so the torus eyelid could be attached to the main face - using vertex merging in Blender.  (also helpful using proportional fall off for moving surrounding vertices):

adding additional edge loops to ring loop to help with making blinks later.
extrude and scale interior edges:
Thanks for looking.


Monday, July 31, 2023

testing lid blink blendshape in ZBrush

testing some of the lid topology by making blink blendshapes in ZBrush.  because there was some lid to eyeball intersection on the full lid closing also made halfway blendshapes.  later would want to be able to hook these blendshapes to an animator control.  for sculpting the up/dn lid closing used mostly move/move topology/smooth brush (also used two polygroups one for upper lid area and one for lower lid area to help with isolating sculpt to relevant lid area using masking).  for the halfway shapes started by using the full closing lid shape layer with the slider value at roughly where half the blink occurs.  This is for facial rigging of a personal work original character Handles.


Thanks for looking

Friday, July 28, 2023

doodle sculpting lip roll in ZBrush

used a layer to get lip separation so it was easier to mask the upper and lower lips with a polygroup. this is for a personal work character i'm working on named Handles.

Thanks for looking

 


Saturday, July 15, 2023

concept face sculpt - Handles


testing brow all up/dn and lip corner up/dn blendshape in ZBrush:

(mostly used move brush and mask lasso)

testing jaw open and lip corner up/dn blendshape in ZBrush:

(used a polygroup on the jaw to help with masking area for rotations)





Monday, April 10, 2023

brow and lip corner up down test

 

used action constraints - which allowed making a pose as well as allowing for offset of the bones.
used bendy bones - which allowed to get some automatic starting weights as well as fleshiness

face rigging done in Blender 2.79
character design: Alan Stewart (http://alanstewart79 dot blogspot dot com)
model/scripting/face setup: Nathaniel Anozie

Thanks for looking

Saturday, March 25, 2023

doodle facial rig setup

Hi,

this is a personal work in progress doodle i made to begin facial setup using bendybones in Blender 2.79.

todo: easier selection of tweaker controls
todo: creating the macro controls
todo: overall movements like headRotation/jawOpen/eyeControls
todo: better deformations



 


this is the work in progress python script to add tweaker controls to the bendy bones:

import bpy
import logging

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console

#fixed bug if bone had a parent using head_local tail_local
#todo - add to one of the face tools addons or make into its own addon.
#can test it with assets first using snippet

class BendyBoneAnimatorController(object):
    """add animator posability to a single bendy bone segment.
    @todo use an input bendy bone for setup
    @todo create animator controls for bendy bone 
    @todo add rotation ability to controls
    """
    def __init__(self, bbone, armatureObjName, boneSize=0.3):
        """
        @param: bbone - (str) name for bendy bone we want to add animator posability
        @param: armatureObjName - (str) name of armature object to add bones to
        @param: boneSize - (float) scale for animator control bones smaller number makes it appear smaller
        """ 
        self.armatureObjName = armatureObjName
        self.boneSize = boneSize
        
        #need to determine these from input bendy bone
        ###
        uppos = _getStartBonePositions(bbone,armatureObjName)
        lowpos = _getEndBonePositions(bbone,armatureObjName)
        logger.debug("uppos:")
        logger.debug(uppos)
        logger.debug("lowpos:")
        logger.debug(lowpos)
        upHeadPos=uppos[0] #bbone tail
        upTailPos=uppos[1]
        lowHeadPos=lowpos[0]
        lowTailPos=lowpos[1] #bbone head
        roll = _getRoll(bbone,armatureObjName)
        ###
        
        self.upHeadPos = tuple(upHeadPos) #i think there was a bug using Vector
        self.upTailPos = tuple(upTailPos)
        self.lowHeadPos = tuple(lowHeadPos)
        self.lowTailPos = tuple(lowTailPos)
        self.roll = roll
        #self.segments = segments
        
        rigPrefix = bbone.split('.')[0] #basically same name as bendy bone 
        side = bbone.split('.')[1] #assumes bendybone has a side
        self.headBoneName = rigPrefix+'_'+'head'+'.'+side
        self.tailBoneName = rigPrefix+'_'+'tail'+'.'+side
        self.midBoneName = bbone
        
    def doIt(self):
        self.createBones()
        self.scaleControlBones()
        self.addConstraint()
        self.addAnimatorControls()
        
    def createBones(self):
        """create up and low. the middle parented to up the low not parented to anything
        """
        #start with nothing selected
        #bpy.ops.object.select_all(action='DESELECT')
        arm_obj = bpy.data.objects[self.armatureObjName]
        bpy.context.scene.objects.active = arm_obj
        #make sure armature in bendy bone view mode
        arm_obj.data.draw_type = 'BBONE'
        
        bpy.ops.object.mode_set(mode='OBJECT') #in case started in edit mode and want to update changes
        bpy.ops.object.mode_set(mode='EDIT')
        
        #make head bone
        headBone = arm_obj.data.edit_bones.new(self.headBoneName)
        headBone.head = self.upHeadPos
        headBone.tail = self.upTailPos
        headBone.use_deform = False
        
        #make tail bone
        tailBone = arm_obj.data.edit_bones.new(self.tailBoneName)
        tailBone.head = self.lowHeadPos
        tailBone.tail = self.lowTailPos
        tailBone.use_deform = False
        
        #testing - parent head/tail bones to the parent of midbone
        midboneParent = arm_obj.data.edit_bones[self.midBoneName].parent
        if midboneParent:
            logger.debug("midboneParent:{}".format(midboneParent.name))
            arm_obj.data.edit_bones[self.headBoneName].parent = midboneParent
            arm_obj.data.edit_bones[self.tailBoneName].parent = midboneParent
        #
        
        #parent bbone to headBone
        arm_obj.data.edit_bones[self.midBoneName].parent = arm_obj.data.edit_bones[self.headBoneName]
        
        bpy.context.scene.objects.active = arm_obj
        bpy.ops.object.mode_set(mode='OBJECT')
        
        #set the handles for bendy bone
        arm_obj.pose.bones[self.midBoneName].use_bbone_custom_handles = True
        arm_obj.pose.bones[self.midBoneName].bbone_custom_handle_start = arm_obj.pose.bones[self.headBoneName]
        arm_obj.pose.bones[self.midBoneName].bbone_custom_handle_end = arm_obj.pose.bones[self.tailBoneName]

    def addConstraint(self):
        """add stretch-to constraint on middle bone aiming at end handle tail bone
        """
        arm_obj = bpy.data.objects[self.armatureObjName]
        bpy.context.scene.objects.active = arm_obj
        bpy.ops.object.mode_set(mode='OBJECT')
        
        constraint = arm_obj.pose.bones[self.midBoneName].constraints.new('STRETCH_TO')
        constraint.target = arm_obj
        constraint.subtarget = self.tailBoneName
        
    def addAnimatorControls(self):
        """
        """
        #draw animator controls
        #add control curves to appropriate bones
        
    def scaleControlBones(self):
        """
        """
        arm_obj = bpy.data.objects[self.armatureObjName]
        bpy.context.scene.objects.active = arm_obj
        bpy.ops.object.mode_set(mode='OBJECT')
        #select the two end bones
        _deselectAllBones(self.armatureObjName)
        arm_obj.data.bones[self.headBoneName].select = True
        arm_obj.data.bones[self.tailBoneName].select = True
        
        #scale them
        bpy.ops.object.mode_set(mode='POSE')
        bpy.ops.transform.transform(mode='BONE_SIZE', value=(self.boneSize, self.boneSize, self.boneSize,0))
        
def _getEndBonePositions(bbone,armature):
    """simple method that uses the direction of bbone to determine positions for a posible end control bone
    @return list of 2 tuples [startWorldPosition,endWorldPosition]
    """
    obj = bpy.data.objects[armature]
    bone = obj.data.bones[bbone]
    pctbbone_length = 0.25 #1/4 might want to try bigger for a longer control bone
    return [ tuple(bone.tail_local), tuple( bone.tail_local + (bone.tail_local-bone.head_local)*(0.25) ) ]
    
def _getStartBonePositions(bbone,armature):
    """simple method that uses the direction of bbone to determine positions for a posible start control bone
    @return list of 2 tuples [startWorldPosition,endWorldPosition]
    """
    obj = bpy.data.objects[armature]
    bone = obj.data.bones[bbone]
    pctbbone_length = 0.25 #1/4 might want to try bigger for a longer control bone
    return [ tuple(bone.head_local - (bone.tail_local-bone.head_local)*(0.25)), tuple(bone.head_local) ]    

def _getRoll(bone,armature):
    """
    @return double - roll of bone
    """
    obj = bpy.data.objects[armature]
    bpy.context.scene.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT')    
    roll = obj.data.edit_bones[bone].roll
    #todo restore mode
    return roll
    

def _deselectAllBones(armature):
    """
    """
    assert armature
    obj = bpy.data.objects[armature]
    bpy.context.scene.objects.active = obj
    bpy.ops.object.mode_set(mode='OBJECT')
    for bone in obj.data.bones:
        bone.select=False
    

def getAllDeformBones(armature):
    """return all deform bones of armature
    """
    result = []
    obj = bpy.data.objects[armature]
    bpy.context.scene.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT')
    result = [bone.name for bone in obj.data.edit_bones if bone.use_deform]
    bpy.ops.object.mode_set(mode='OBJECT')#todo return to previous mode
    
    return result

def addControlsToBBones(armature, bbones = []):
    """add animator control bones to bendy bones
    @param: armature (str) name of armature object
    @param: bbones (list of str) names for bendy bones to add controls to
    """
    assert armature
    bones = bbones
    if not bones:
        bones = getAllDeformBones(armature) or []
    for bone in bones: #["socket_dn.L"]
        bb = BendyBoneAnimatorController(bone,armature)
        bb.doIt()

"""
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/riggingTools/faceTools/addControlsToBendyBones.py") #change to path to python file

testTool.addControlsToBBones("Armature")


#bb = testTool.BendyBoneAnimatorController("Bone","Armature") #name of bendybone, name of armature
#bb.doIt()

#print(testTool._getEndBonePositions("Bone","Armature"))

print(testTool._getEndBonePositions("socket_dn.L","Armature"))

print(testTool._getStartBonePositions("socket_dn.L","Armature"))

"""

character design: Alan Stewart (http://alanstewart79 dot blogspot dot com)

modeling/facialSetup: Nathaniel Anozie

Happy Sketching!


Saturday, October 29, 2022

sculpting mouth narrow blendshape using python in Blender

this is a simple script i wrote to sculpt a narrow blendshape using python scripting for spherical mouth in Blender 2.79 (the script may need to be edited to work in higher Blender versions. also it might be better to use bmesh).  it takes the mesh name (object name) and need to specify grow (how many loops should be affected in sculpt beginning from innermost selected.  The scale parameters tell how much scaling should happen at ends of shape.  it needs the innermost vertex loop selected in edit mode.  in the snippet to run in blender need to replace the path to the python file with where the code was saved. (modify use at your own risk).

(it needs the innermost vertex loop selected in edit mode)

(it successively grows that selection each time scaling the verts with a linear falloff. currently using numpy's linspace)
 
"""to test in blender
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/snippets/testingStuff/narrowShape.py") #change to path to python file that has class

shp = testTool.NarrowShapeTask(mesh = 'Sphere')
shp.build( grow = 5, max_scale = 0.2, min_scale = 0.9 )
"""

import bpy
import numpy

class NarrowShapeTask(object):
    """simple class to help make narrow shape on spherical or torus mesh
    grow selection a few times each time scaling the edgeloops. want a smooth falloff
    little error checking
    """
    def __init__(self, mesh):
        self.mesh = mesh

    def build(self, grow = 4, max_scale = 0.3, min_scale = 0.9 ):
        """sculpt the (narrow) shape
        @param grow - times to grow inital selection. example 2 would say give me first two edgeloops. should be 2 or more
        @param max_scale - most amount to scale. for the inner most loop
        @param min_scale - smallest amount to scale. for the outermost loop
        """
        vertexIdLoops = self.getEachVertexLoop(grow = grow)
        falloffs = list(numpy.linspace(max_scale, min_scale, grow)) #get evenly spaced scales from start to end value. want first value scaled down the most for innermost part of narrow shape
        #assumes both lengths should be equal
        
        #change size of the loops smoothly
        for i in range(0,grow):
            vloop = vertexIdLoops[i]
            falloff = falloffs[i]
            self.selectMeshLoop( vertexLoop = vloop )
            self.scaleSelectedMeshLoop(value = falloff)

        print("NarrowShapeTask >>> build success")
        return True
        
        
    def getEachVertexLoop( self, grow = 4 ):
        """get list where each element are verts(ids) in that loop. should be ordered. each element is a list
        assumes innermost edgeloop selected in edit mode
        @param mesh - data object name for mesh
        @param grow - times to grow inital selection. example 2 would say give me first two edgeloops. should be 2 or more
        @return [list] - each element are verts(ids) in that loop. should be ordered. each element is a list
        """
        result = []
        #check grow is 2 or more
        meshObj = bpy.data.objects[self.mesh] 
        
        #for first loop dont need to do anything fancy
        curSel = []
        self._refreshSelection() #i think can move this and below line into own method later
        curSel = [ v.index for v in meshObj.data.vertices if v.select ]
        result.append(curSel)
        
        for i in range(1,grow):
            self._refreshSelection()
            prevSel = [ v.index for v in meshObj.data.vertices if v.select ]
            bpy.ops.mesh.select_more()
            self._refreshSelection()
            curSel = [ v.index for v in meshObj.data.vertices if v.select ]
            loopVertexIds = list(set(curSel)-set(prevSel))
            result.append(loopVertexIds)
            
        return result

    def scaleSelectedMeshLoop(self, value = 1.0):
        """scale the currently selected vertex loop. assumes in edit mode
        @param value - the scale amount
        """
        bpy.ops.transform.resize( value=(value,value,value) ) #scale by same amount in all axis
        
    def selectMeshLoop( self, vertexLoop = [] ):
        """select given vertex ids. replaces selection. assumes in edit mode
        ex: shp.selectMeshLoop( vertexLoop = [80] )
        @param vertexLoop - list of vertex ids
        """
        #print("vertexLoop",vertexLoop)
        
        meshObj = bpy.data.objects[self.mesh]
        #deselect all vertices
        bpy.ops.mesh.select_all(action='DESELECT')
        
        #need to be in object mode
        bpy.ops.object.mode_set(mode="OBJECT")
        
        #select the loop
        loop = [ meshObj.data.vertices[vid] for vid in vertexLoop]

        for v in loop:
            #print("v>>",v.index)
            v.select = True
        
        self._refreshSelection() #so vertex selection is registered
        

        
    def _refreshSelection(self):
        """need to toggle in and out of object mode so vertex selection is registered
        """
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.mode_set(mode="EDIT")        
        
#tried first making methods > then later made into class

(on the side i'm trying to learn c/c++ and i'm finding it helpful to learn from other's examples for example on blender git source by making a list of lines do not understand the syntax. then searching google example stackoverflow to find out what that line syntax means. kindof like a way of breaking down the problem into smaller manageable chunks. i think reading code is a great way to begin learning how to write code.)

Thursday, October 27, 2022

flat mouth open and narrow/wide blendshapes using lattices

 

 for this created a simplified flat mouth mesh and 3 lattices for narrow/wide and open shapes in Blender.
> for making mesh (to act as a simple planar lip) started with a torus and made major segments 8 and minor segments 3. then removed the bottom faces and moved inner edge loop down so whole mesh was roughly flat.  then subdivided all the edges 2 times.
> for making the open shape. created one lattice for the upper open shape and a second lattice for the lower open.  added divisions to lattice to help make the deformation and scaled the lattice in object mode to fit the upper part of lip.  then selected top vertices of lip mesh and created a vertex group so the upper lattice only modified upper vertices. then made lattice modifier and assigned lattice to the lip mesh and used the upper vertex group. then added a shapekey/blendshape to the lattice moving a single point of lattice to create open shape. repeated similar steps for the lower open shape.
> for making wide/narrow shape did something similar with the open shape but this time used just one lattice (note the deformations are a work in progress).

Saturday, October 22, 2022

upper lip rig doodle

this is a lip rig doodle i made in Blender. (for one side of the upper lip)
it has ability to make larger area edits (via a lattice) and be able to make finer edits (via joints per span).

-to make it i first created a plane and subdivided it.
-then i made a lattice and scaled it to fit the plane (in Blender needed to scale lattice in object mode)
-then i added some u divisions to lattice (in places want the larger deformation) > and assigned lattice to the plane lip geo
-then i made an armature and some bones for moving the lattice > some of the lattice points were moved by bones via hooks method.
-then i made some joints for each span and used auto weighting to get the bones to move the plane. (before auto weighting i set deform off on the lattice bones as didn’t want them to be weighted to the plane)
-i then parented all the bones to an overall mover bone > also parented the lattice to that bone
-then on the lip geo i reordered the lattice putting it below the armature in stack > so could translate the mover bone without messing up the lattice.

Thursday, July 14, 2022

Using Lattices to sculpt 2D Mouth open and LipCornerUp blendshape in Blender

Hi,

This is still is still a work in progress and i haven't scripted it yet. But was finding it helpful to use lattices for sculpting a simple 2D mouth's mouth open and lip corner up blendshape.  One lattice was used for the lip corner up shape and the second lattice was used on the lower mouth for the mouth open shape.  I created a vertex group for the lower mouth (excluding 2 verts at extreme ends) and used that vertex group when creating lattice.

(to sculpt the simple 2D mouth created a plane subdivided and positioned it. then duplicated it and joined top and bottom together).

(to make the lattices used add lattice then shaped it in object mode and then created a modifier on the lip mesh and gave it the lattice to use)

one advantage of using lattice in Blender to sculpt lip corner up blendshape is it allows for more control of keeping the mouth closed when the lip corner is moving up.

Hope you find it helpful.



 

Thanks,

Nathaniel

also check out Chris Pagoria's example of using lattices for more complex meshes in his online tutorial "Fast Start For Facial Blend Shapes".

also check out the amazing work of Nima Azarba (nimaazarba dot blogspot dot com)



Saturday, November 6, 2021

smile blendshape dog doodle

mesh shaped using photo reference. it was helpful bringing the photo reference into ZBrush to better match reference. used same technique to sculpt smile as in previous bear smile post.




Monday, November 1, 2021

weights transfer partial mesh to whole in blender

Hi,

This is a tool i wrote for blender 2.79 to transfer weights from a partial mesh to a whole mesh. the partial mesh needs to be exactly same as whole mesh with some faces removed.  it also requires the source bones to be named the same as the destination bones.  The tool is still a work in progress and hasn’t been tested on full faces yet. (modify use at your own risk)


 

Happy Scripting! and Happy Sketching!
Nate

 

#simple tool to help transfer weights from a partial mesh to a whole mesh.
#it assumes partial mesh is whole mesh with some faces deleted.
#
#modify use at your own risk

#last modified
#102621 -- working on initial release
#101521,1016 -- wrote main class - working on blocking

bl_info = {
    "name":"weights copy tool",
    "description":"copy weights from a partial mesh to whole mesh",
    "category": "Object",
    "author":"Nathaniel Anozie",
    "blender":(2,79,0)
}


import bpy

from bpy.props import(
    StringProperty,
    PointerProperty
    )

from bpy.types import(
    Operator,
    Panel,
    PropertyGroup
    )


class WeightsTransferOperator(Operator):
    """transfer weights from a partial mesh to a whole mesh
    """
    bl_idname = "obj.do_weightstransfer"
    bl_label = "Weights Transfer"
    bl_options = {"REGISTER"}
    
    def execute(self, context):
    
        self.report({'INFO'}, "Starting Weights Transfer ...")
        wtc_obj = WeightsTransferCreator(source_mesh = context.scene.weightstransfer_prop.source_mesh, 
                                destination_mesh= context.scene.weightstransfer_prop.destination_mesh, 
                                remainder_bone_source = context.scene.weightstransfer_prop.remainder_bone_source,
                                remainder_bone_destination = context.scene.weightstransfer_prop.remainder_bone_destination
                                )
        wtc_obj.run()

        self.report({'INFO'}, "Completed Weights Transfer")
        return {'FINISHED'}
        
class WeightsTransferPanel(Panel):
    bl_label = "Weights Transfer Panel"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    
    def draw(self, context):
        
        #here we add textfields and button to ui
        #
        layout = self.layout
        layout.label(text = "Weights Transfer tool")
        
        #4 text fields in a row
        #text fields
        layout.prop( context.scene.weightstransfer_prop, "source_mesh", text = "source mesh" )
        layout.prop( context.scene.weightstransfer_prop, "destination_mesh", text = "destination mesh" )
        layout.prop( context.scene.weightstransfer_prop, "remainder_bone_source", text = "source remainder bone" )
        layout.prop( context.scene.weightstransfer_prop, "remainder_bone_destination", text = "destination remainder bone" )
        
        #button
        layout.operator( "obj.do_weightstransfer")
 
 
class WeightsTransferProperties(PropertyGroup):
    #here we make each textfield
    source_mesh = StringProperty(
        name = "source_mesh",
        description = "this is the source partial mesh object name. it should have the same faces as destination mesh with some deleted"
        )

    destination_mesh = StringProperty(
        name = "destination_mesh",
        description = "this is the destination whole mesh object name"
        )

    remainder_bone_source = StringProperty(
        name = "remainder_bone_source",
        description = "bone to hold remainder weights of source mesh"
        )

    remainder_bone_destination = StringProperty(
        name = "remainder_bone_destination",
        description = "bone to hold remainder weights of destination mesh"
        )

def register():
    bpy.utils.register_class(WeightsTransferOperator)
    bpy.utils.register_class(WeightsTransferPanel)
    bpy.utils.register_class(WeightsTransferProperties)
    #here we name the property that holds all our textfields
    bpy.types.Scene.weightstransfer_prop = PointerProperty(
        type = WeightsTransferProperties
        )

def unregister():
    bpy.utils.unregister_class(WeightsTransferOperator)
    bpy.utils.unregister_class(WeightsTransferPanel)
    bpy.utils.unregister_class(WeightsTransferProperties)
    del bpy.types.Scene.weightstransfer_prop


class WeightsTransferCreator(object):
    """this object helps with transfering weights from a partial source mesh to a whole destination mesh
    """
    def __init__(self, source_mesh = None, destination_mesh = None, bones_source_to_destination = None, remainder_bone_source = None, remainder_bone_destination = None ):
        """
        source_mesh - this is the source partial mesh object str name
        destination_mesh - this is the destination whole mesh object str name
        bones_source_to_destination - this is a mapping from the source bones to destination bones as dictionary ex: {'bone_a':'bone_a','bone_b':'bone_b'}
         if nothing provided it assumes same bone names in destination as there are in source
        remainder_bone_source - bone to hold remainder weights of source mesh
        remainder_bone_destination - bone to hold remainder weights of destination mesh
        """
        #verify inputs exist (meshes exist, bones exist etc)
        self.source_mesh = source_mesh
        self.destination_mesh = destination_mesh
        self.remainder_bone_source = remainder_bone_source
        self.remainder_bone_destination = remainder_bone_destination
        self.bones_source_to_destination = {}
        if bones_source_to_destination:
            self.bones_source_to_destination = bones_source_to_destination
        else:
            #assume same bone names in destination as there are in source
            self.bones_source_to_destination = self._getDefaultSourceToDestinationMapping(mesh_name = source_mesh, excludes = [remainder_bone_source])
        
    def run(self):
        """to be called by user to do the entire weight transfer process
        """
        vertex_source_to_destination = self._getVertexIDSourceToDestination()
        if not vertex_source_to_destination:
            print("could not do the weight transfer cannot find source to destination vertex mapping")
            return
        self._transferWeights(vertex_source_to_destination)
    
    def _transferWeights(self, vertices_source_to_destination = None ):
        """this changes weights on destination mesh. it uses source meshes weights
        vertices_source_to_destination - mapping of source vertex id to its closes destination vertex ex: {0:3,1:2}
        """
        #print("vertices_source_to_destination >>",vertices_source_to_destination)
        
        #loop vertices of source mesh
        for src_vert_id, dest_vert_id in vertices_source_to_destination.items():
            bones_affecting_vertex = self._getBoneNamesAffectingVertex( vertex_id = src_vert_id, mesh_name = self.source_mesh )
            
            #get weights from source mesh
            for src_bone, dest_bone in self.bones_source_to_destination.items():
                #skip if src_bone has no weights on source vertex
                if src_bone not in bones_affecting_vertex:
                    continue
                src_weight = bpy.data.objects[self.source_mesh].vertex_groups[src_bone].weight(src_vert_id)
                #print("for destination vert %s and dest bone %s source weight is %s" %(dest_vert_id, dest_bone, src_weight) )
                ##do the actual setting of individual bone weight on destination mesh
                bpy.data.objects[self.destination_mesh].vertex_groups[dest_bone].add([dest_vert_id], src_weight, 'REPLACE' )
                
            #handle the remainder bone weight to be set once per vertex
            src_remainder_weight = bpy.data.objects[self.source_mesh].vertex_groups[self.remainder_bone_source].weight(src_vert_id)
            #print("for destination vert %s and dest bone %s source remainder weight is %s" %(dest_vert_id, dest_bone, src_remainder_weight) )              
            ##set the remainder weight on destination mesh
            bpy.data.objects[self.destination_mesh].vertex_groups[self.remainder_bone_destination].add([dest_vert_id], src_remainder_weight, 'REPLACE' )

    def _getBoneNamesAffectingVertex( self, vertex_id = None, mesh_name = None ):
        """get all bone names that have a weight on vertex id
        mesh_name - mesh object name str that has given vertex_id
        """
        result = []
        vgListGroups = bpy.data.objects[mesh_name].vertex_groups
        for grp in bpy.data.objects[mesh_name].data.vertices[vertex_id].groups:
            if grp.weight > 0.0:
                vgListIndex = grp.group
                #print(vgListGroups[vgListIndex].name)
                result.append(vgListGroups[vgListIndex].name)
        return result

    def _getVertexIDSourceToDestination(self):
        """uses nearest position of vertices to find closes destination mesh vertex to source mesh vertex
        """
        result = {}
        close_enough_dist = 0.00001 #closer than this counts as a match
        
        #loop MeshVertex objects of source finding closest in destination
        for src_vert in bpy.data.objects[self.source_mesh].data.vertices:
            src_pos = src_vert.co
            for dest_vert in bpy.data.objects[self.destination_mesh].data.vertices:
                dest_pos = dest_vert.co
                if (src_pos - dest_pos).magnitude < close_enough_dist:
                    #found closest vertex
                    result[src_vert.index] = dest_vert.index
                    break
        #might be nice to check if number keys match number verts of source mesh to indicate we found a match for all verts
        if len(bpy.data.objects[self.source_mesh].data.vertices) != len(result.keys()):
            print("check that source partial mesh is the whole mesh with some faces deleted. need them to overlap exactly")
            return False
        return result

    def _getDefaultSourceToDestinationMapping(self, mesh_name = None, excludes = []):
        """returns a dictionary of form {"Bone.001":"Bone.001","Bone.002":"Bone.002"} 
        they are all the bone names bound to mesh excluding the bone names in excludes list.
        it assumes mesh_name is already bound to some bones
        """
        result = {}
        bones = [vg.name for vg in bpy.data.objects[mesh_name].vertex_groups if vg.name not in excludes]
        for bone in bones:
            result[bone] = bone
        return result

"""
import bpy
import sys
sys.path.append("/users/Nathaniel/Documents/src_blender/python/weightTools")
import naPartPaintToWholeAddon as mod
import imp
imp.reload(mod)

obj = mod.WeightsTransferCreator(source_mesh = "partial", 
                                destination_mesh="whole", 
                                remainder_bone_source = "Bone",
                                remainder_bone_destination = "Bone"
                                )
obj.run()

"""

"""
obj = mod.WeightsTransferCreator(source_mesh = "partial", 
                                destination_mesh="whole", 
                                remainder_bone_source = "Bone",
                                remainder_bone_destination = "Bone",
                                bones_source_to_destination = {"Bone.001":"Bone.001","Bone.002":"Bone.002"}
                                )
obj.run()
"""


"""getting world position of a vertex or face
this gets local position - it doesnt change when mesh is moved in object space
#uses vertex id
>>> bpy.data.objects['Cube'].data.vertices[4].co
Vector((1.0, -1.0, -1.0))

#uses face id
>>> bpy.data.objects['Cube'].data.polygons[2].center
Vector((1.0, 0.0, 0.0))

"""

"""getting all the bones with some weight on a vertex

#getting a bone weighted to a vertex id (vertex id is 1, assumes groups has a zero index
>>> bpy.data.objects['Cube'].data.vertices[1].groups[0].group
1
#the number returned above is the index in panel of vertex groups
>>> bpy.data.objects['Cube'].vertex_groups[1].name
'Bone.001'

#two ways to find weight of a vertex id. (vertex id is 1)
>>> bpy.data.objects['Cube'].vertex_groups[1].weight(1)
1.0
#or
>>> bpy.data.objects['Cube'].data.vertices[1].groups[0].weight
1.0

####to get all bone names with some weight for vertex id (vertex id is 5)
import bpy
vgListGroups = bpy.data.objects['Cube'].vertex_groups
for grp in bpy.data.objects['Cube'].data.vertices[5].groups:
    if grp.weight > 0.0:
        vgListIndex = grp.group
        print(vgListGroups[vgListIndex].name)
"""


"""setting weight on a vertex id and bone name (vertex id is 1)
#need to be in object mode
>>> bpy.data.objects['Cube'].vertex_groups['Bone'].add([1], 0.5, 'REPLACE' )
"""