Skip to content

Instantly share code, notes, and snippets.

@nathanielanozie
Created April 18, 2020 21:41
Show Gist options
  • Save nathanielanozie/316be49bb726905dc7b8821877a90c9c to your computer and use it in GitHub Desktop.
Save nathanielanozie/316be49bb726905dc7b8821877a90c9c to your computer and use it in GitHub Desktop.
learning some shape key tools in blender 2.79
#learning some shape key tools on a robot face. two brows a mouth
"""
import bpy
import sys
#example if wanted to test script without addon part. change to your path here
sys.path.append('/Users/Nathaniel/Documents/src_blender/python/riggingTools/faces')
import naSimpleRobotFaceRig as mod
import imp
imp.reload(mod)
#bpy.app.debug=True #temporary
mod.doIt(bpy.context)
"""
"""
#files to test:
#testFace_v3.blend
"""
import imp
import bpy
import bmesh
from math import pi
def doIt(context):
"""build robot face shape key rig
"""
browName = 'brow'
mouthName = 'mouth'
#i think used for all slider
armatureName = 'simpleRobot'
rootBoneName = 'simpleRobotRoot'
armatureObj = makeFaceArmature(name = armatureName, rootBoneName = rootBoneName )
####for first shape key mesh----------
#pos and neg shape name for slider, position for slider
##brow lf
faceMesh = browName+'.L'
browLeft = brow(faceMesh,\
updnAmount = (0.8,0.9),\
fwdBckAmount = (.8,.5),\
inVerts = [2,0],\
outVerts = [3,1])
data = [\
{'meshes' : browLeft['updnAll'], 'pos':(2,0,4) },
{'meshes' : browLeft['fwdbckAll'], 'pos':(4,0,4) },
{'meshes' : browLeft['updnIn'], 'pos':(6,0,4) },
{'meshes' : browLeft['updnOut'], 'pos':(8,0,4) }
]
"""
data = [\
{'meshes' : {'neg':'browDn.R','pos':'browUp.R'}, 'pos':(2,0,2) },
{'meshes' : {'pos':'browFwd.R','neg':'browBck.R'}, 'pos':(4,0,2) }
]
"""
makeRigSliders(faceMesh,armatureObj,rootBoneName,data)
###end first shape key mesh --------------
###brow rt
faceMesh = browName+'.R'
browRight = brow(faceMesh,side='rt',\
updnAmount = (0.8,0.9),\
fwdBckAmount = (.8,.5),\
inVerts = [2,0],\
outVerts = [3,1])
data = [\
{'meshes' : browRight['updnAll'], 'pos':(-2,0,4) },
{'meshes' : browRight['fwdbckAll'], 'pos':(-4,0,4) },
{'meshes' : browRight['updnIn'], 'pos':(-6,0,4) },
{'meshes' : browRight['updnOut'], 'pos':(-8,0,4) }
]
makeRigSliders(faceMesh,armatureObj,rootBoneName,data)
########end second mesh
###lip
faceMesh = mouthName
lipShapes = lip(faceMesh,
lfVerts = [3,1],
rtVerts = [2,0] )
data = [\
{'meshes' : lipShapes['updnAll'], 'pos':(2,0,-4) },
{'meshes' : lipShapes['fwdbckAll'], 'pos':(-2,0,-4) },
{'meshes' : lipShapes['updnLf'], 'pos':(2,0,-12) },
{'meshes' : lipShapes['updnRt'], 'pos':(-2,0,-12) }
]
makeRigSliders(faceMesh,armatureObj,rootBoneName,data)
def makeRigSliders(faceMesh=None,armatureObj=None,rootBoneName=None,data=None):
#draw ui
#boneControlsText = getBoneControlsText(armatureObj,rootBoneName)
boneControls = getBoneControls(armatureObj,rootBoneName,data,faceMesh)
#connect sliders to blendshapes
for boneCtrl in boneControls:
boneCtrl.connectShapeKey(faceMesh)
#up +z
#fwd -x (for character left brow)
def lip(meshName=None,
updnAmount = (0.7,0.7),
fwdBckAmount = (.8,.5),
lfVerts = [],
rtVerts = [] ):
"""
#lfVerts are vertex ids on character's left side lip corner
amounts ex:updnAmount tells how shape should be sculpted. how many units is up shape dn shape
"""
if meshName not in bpy.data.objects:
print("cannot find mesh %s" %meshName)
return
result = {}
meshObj = bpy.data.objects[meshName]
#assuming mesh exists and no shapekeys on it
basis = meshObj.shape_key_add('Basis')
basis.interpolation='KEY_LINEAR'
vids = range(0,len(meshObj.data.vertices))
#updnAll
upAllShape = meshObj.shape_key_add('upAllShape')
upAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upAllShape.data[vid].co.z += updnAmount[0]
dnAllShape = meshObj.shape_key_add('dnAllShape')
dnAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnAllShape.data[vid].co.z -= updnAmount[1]
#fwdbckAll
fwdAllShape = meshObj.shape_key_add('fwdAllShape')
fwdAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
fwdAllShape.data[vid].co.x -= fwdBckAmount[0]
bckAllShape = meshObj.shape_key_add('bckAllShape')
bckAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
bckAllShape.data[vid].co.x += fwdBckAmount[1]
#handle character lf corner/ rt corner shapes
vids = lfVerts
upLfShape = meshObj.shape_key_add('upLfShape')
upLfShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upLfShape.data[vid].co.z += updnAmount[0]
dnLfShape = meshObj.shape_key_add('dnLfShape')
dnLfShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnLfShape.data[vid].co.z -= updnAmount[1]
#rt corner
vids = rtVerts
upRtShape = meshObj.shape_key_add('upRtShape')
upRtShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upRtShape.data[vid].co.z += updnAmount[0]
dnRtShape = meshObj.shape_key_add('dnRtShape')
dnRtShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnRtShape.data[vid].co.z -= updnAmount[1]
result['updnAll'] = {'pos':'upAllShape','neg':'dnAllShape'}
result['fwdbckAll'] = {'pos':'fwdAllShape','neg':'bckAllShape'}
#
result['updnLf'] = {'pos':'upLfShape','neg':'dnLfShape'}
result['updnRt'] = {'pos':'upRtShape','neg':'dnRtShape'}
return result
def brow(meshName=None,
side='lf',
updnAmount = (0.5,0.5),
fwdBckAmount = (.8,.5),
inVerts = [],
outVerts = []
):
if meshName not in bpy.data.objects:
print("cannot find mesh %s" %meshName)
return
result = {} #{ 'updnAll':{},'fwdbckAll':{} } #this is for all. later add for in/out verts
sideSuffix ='.L'
if side == 'rt':
sideSuffix = '.R'
meshObj = bpy.data.objects[meshName]
#assuming mesh exists and no shapekeys on it
basis = meshObj.shape_key_add('Basis')
basis.interpolation='KEY_LINEAR'
vids = range(0,len(meshObj.data.vertices))
#updnAll
upAllShape = meshObj.shape_key_add('upAllShape'+sideSuffix)
upAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upAllShape.data[vid].co.z += updnAmount[0]
dnAllShape = meshObj.shape_key_add('dnAllShape'+sideSuffix)
dnAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnAllShape.data[vid].co.z -= updnAmount[1]
#fwdbckAll
fwdAllShape = meshObj.shape_key_add('fwdAllShape'+sideSuffix)
fwdAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
#get fwdbck to work depending on side of brow
if side == 'lf':
fwdAllShape.data[vid].co.x -= fwdBckAmount[0]
else:
fwdAllShape.data[vid].co.x += fwdBckAmount[0]
bckAllShape = meshObj.shape_key_add('bckAllShape'+sideSuffix)
bckAllShape.interpolation = 'KEY_LINEAR'
for vid in vids:
if side == 'lf':
bckAllShape.data[vid].co.x += fwdBckAmount[1]
else:
bckAllShape.data[vid].co.x -= fwdBckAmount[1]
#handle in/out shapes (currently using same shapekey deltas as all brow)
vids = inVerts
upInShape = meshObj.shape_key_add('upInShape'+sideSuffix)
upInShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upInShape.data[vid].co.z += updnAmount[0]
dnInShape = meshObj.shape_key_add('dnInShape'+sideSuffix)
dnInShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnInShape.data[vid].co.z -= updnAmount[1]
vids = outVerts
upOutShape = meshObj.shape_key_add('upOutShape'+sideSuffix)
upOutShape.interpolation = 'KEY_LINEAR'
for vid in vids:
upOutShape.data[vid].co.z += updnAmount[0]
dnOutShape = meshObj.shape_key_add('dnOutShape'+sideSuffix)
dnOutShape.interpolation = 'KEY_LINEAR'
for vid in vids:
dnOutShape.data[vid].co.z -= updnAmount[1]
result['updnAll'] = {'pos':'upAllShape'+sideSuffix,'neg':'dnAllShape'+sideSuffix}
result['fwdbckAll'] = {'pos':'fwdAllShape'+sideSuffix,'neg':'bckAllShape'+sideSuffix}
#corners
result['updnIn'] = {'pos':'upInShape'+sideSuffix,'neg':'dnInShape'+sideSuffix}
result['updnOut'] = {'pos':'upOutShape'+sideSuffix,'neg':'dnOutShape'+sideSuffix}
return result
####################
#for building simple facial sliders for blendshapes
#
def getBoneControls(armatureObj = None, rootBoneName=None, data=[], faceMesh=None):
"""this draws slider returns slider FaceBoneControl objects.
"""
boneControls = []
#bone shapes
boneShape, bgBoneShape = getUiControlShapes()
for i in range(0,len(data)):
boneCtrl = FaceUiBoneControl(**data[i],armatureObj=armatureObj,rootBoneName = rootBoneName,boneShape=boneShape,bgBoneShape=bgBoneShape, faceMesh = faceMesh)
boneControls.append(boneCtrl)
return boneControls
def getUiControlShapes():
drvShapeName = 'naDriverShape'
bgShapeName = 'naBgShape'
#so only rotate controls once when created
if not drvShapeName in bpy.data.objects:
boneShape = drawCircleShape(drvShapeName)
bgBoneShape = drawRectangleShape(bgShapeName)
#rotate shape so control facing us in xz plane
rotateShape(boneShape,amount=pi/2,axis='x')
else:
boneShape = bpy.data.objects[drvShapeName]
bgBoneShape = bpy.data.objects[bgShapeName]
return boneShape, bgBoneShape
class FaceUiBoneControl(object):
"""an object that represents a blendshape slider
"""
def __init__(self,**kwargs):
self.armatureObj = kwargs.get('armatureObj')
self.rootBoneName = kwargs.get('rootBoneName')
self.meshes = kwargs.get('meshes')
self.pos = kwargs.get('pos')
self.boneShape = kwargs.get('boneShape')
self.bgBoneShape =kwargs.get('bgBoneShape')
self.faceMesh = kwargs.get('faceMesh')
self.driverBone = None #used for circlular shape to be animated
self.bgBone = None #used for square bg shape
self.draw()
def draw(self):
xzPos = (self.pos[0],self.pos[2])
boneBaseName = self.faceMesh+self.meshes['pos']+'_anim'#self.rootBoneName+self.meshes['pos']+'_anim'#self.name+'_'+self.param+'_'+self.side+'_posXZ_%s_%s' %(xzPos[0],xzPos[1])
self.driverBone, self.bgBone = drawBones(self.armatureObj,xzPos,boneBaseName,self.rootBoneName,self.boneShape,self.bgBoneShape)
#make bg unselectable
print(self.armatureObj.data)
self.armatureObj.data.bones[self.bgBone].hide_select = True
def connectShapeKey(self, faceMesh=None):
"""input default base mesh to add shape keys to
"""
makeDriverForPosNegShapeKey(faceMesh=faceMesh,
drvArmature = self.armatureObj.name,
drvBoneName=self.driverBone,
posShapeKey=self.meshes['pos'],
negShapeKey=self.meshes['neg'])
def drawTextBone(armatureObj=None,rootBoneName=None,textStr=None,position=(0,0,0) ):
"""add bones to armature using text as custom shape
"""
textObjShape = drawTextShape(textStr,(0,0,0))
textBoneName = textStr+'%s_%s_%s_bone' %(position[0],position[1],position[2]) #assuming these are unique
arm_obj = armatureObj
#make bone and add textobj as custom shape
print("adding text bone")
if not isExistBone(armatureObj.name,textBoneName):
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
if arm_obj == bpy.context.active_object and bpy.context.mode == "EDIT_ARMATURE":
print("adding text bone >>")
#anim bone
textBone = arm_obj.data.edit_bones.new(textBoneName)#arm_data_obj.edit_bones.new(driverBoneName)
textBone.head = (position[0],position[1],position[2])
textBone.tail = (position[0],position[1],position[2]+1)
# exit edit mode to save bones so they can be used in pose mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
print("start text parenting")
arm_obj.data.edit_bones[textBoneName].parent = arm_obj.data.edit_bones[rootBoneName]
print("finished text parenting")
print("start adding text custom shape")
arm_obj.pose.bones[textBoneName].custom_shape = textObjShape
print("finished adding text custom shape")
print("finished adding text bone")
#parent bone to root
def drawBones(armatureObj=None,xzPos=(),boneBaseName=None,rootBoneName=None,boneShape=None,bgBoneShape=None):
"""draws 2 bones using position and armature
"""
driverBone = None
bgBone = None
arm_obj = armatureObj
armatureName = armatureObj.name
driverBoneName = boneBaseName+'_driver'
bgBoneName = boneBaseName+'_bg'
if isExistBone(armatureName,driverBoneName) or isExistBone(armatureName,bgBoneName):
print("bones already created exiting")
return (None,None)
print('drawBones')
print('found armature >> creating bones')
###animated bone
if not isExistBone(armatureName,driverBoneName):
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT', toggle=False) #need to be in edit mode to add bones
if arm_obj == bpy.context.active_object and bpy.context.mode == "EDIT_ARMATURE":
print("adding anim bone")
#anim bone
driverBone = arm_obj.data.edit_bones.new(driverBoneName)#arm_data_obj.edit_bones.new(driverBoneName)
driverBone.head = (xzPos[0],0,xzPos[1])#driverBone.head = (xzPos[0],0,xzPos[1])
driverBone.tail = (xzPos[0],1,xzPos[1])#driverBone.tail = (xzPos[0],0,xzPos[1]+1)
print("adding bg bone")
#bg bone
bgBone = arm_obj.data.edit_bones.new(bgBoneName)
bgBone.head = (xzPos[0],0,xzPos[1])
bgBone.tail = (xzPos[0],0,xzPos[1]+3)
# exit edit mode to save bones so they can be used in pose mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
else:
print("doing nothing not in armature drawing context")
#"""
#add shape to bones
print("adding shape to bones")
#should be one shape for all bones
#boneShape = drawCircleShape('naDriverShape')
#bgBoneShape = drawRectangleShape('naBgShape')
#bpy.ops.object.mode_set(mode='POSE', toggle=False)
bpy.ops.object.mode_set(mode='OBJECT')
arm_obj.pose.bones[driverBoneName].custom_shape = boneShape #custom shape is poseBone property
arm_obj.pose.bones[driverBoneName].custom_shape_scale = 4 #to make it easier to see while testing
#show_wire property of Bone (editbone)
arm_obj.data.bones[driverBoneName].show_wire=True
arm_obj.pose.bones[bgBoneName].custom_shape = bgBoneShape
arm_obj.data.bones[bgBoneName].show_wire=True
print("finished adding shapes")
#make bg shape skinnier
#arm_obj.pose.bones[bgBoneName].scale[0] = 0.25
#parent driver bone to bg bone
print("doing parenting")
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
if arm_obj == bpy.context.active_object and bpy.context.mode == "EDIT_ARMATURE":
print("parenting bone >>")
arm_obj.data.edit_bones[driverBoneName].parent = arm_obj.data.edit_bones[bgBoneName]
arm_obj.data.edit_bones[bgBoneName].parent = arm_obj.data.edit_bones[rootBoneName]
#driverBone.parent = bgBone
print("finished parenting bone")
#"""
print("finished drawing bone")
bpy.ops.object.mode_set(mode='OBJECT')
print("finished drawbones")
return (driverBoneName,bgBoneName)
def isExistBone(armatureName, bone):
result = False
bpy.context.scene.objects.active = bpy.data.objects[armatureName]
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
for boneObj in bpy.data.armatures[armatureName].edit_bones:
if boneObj.name == bone:
result = True
break
return result
def makeFaceArmature(name,rootBoneName):
#if armature doesnt exist make it
print('makeFaceArmature')
if name in bpy.data.objects:
print('armature alread created %s skipping' %(name))
return bpy.data.objects[name]
arm_dat = bpy.data.armatures.new(name)
arm_obj = bpy.data.objects.new(name,arm_dat)
arm_obj.data = arm_dat
scene = bpy.context.scene
scene.objects.link(arm_obj)
scene.objects.active = arm_obj #need to select armature to put it in edit mode
scene.update()
#make bone
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bone = bpy.data.armatures[name].edit_bones.new(rootBoneName)
bone.head = (0,0,0)
bone.tail = (0,0,1)
bpy.ops.object.editmode_toggle()
bpy.ops.object.mode_set(mode='OBJECT')
return arm_obj
def makeDriverForPosNegShapeKey(faceMesh=None,
drvArmature = None,
drvBoneName=None,
posShapeKey=None,
negShapeKey=None):
"""create driver on ex: tz 1 0 to turnon/off meshes. assumes faceMesh has shape key names given.
all inputs given are strings
"""
#if faceMesh object doesnt have shape keys exit
#if armature doesnt have drv bone exit
print('makeDriverForPosNegShapeKey')
faceObj = bpy.context.scene.objects[faceMesh]
drvArmatureObj = bpy.data.objects[drvArmature]
#pos shape key
driver = faceObj.data.shape_keys.key_blocks[posShapeKey].driver_add("value").driver
driver.type = "SCRIPTED"
driver.expression= '0 if drvloc < 0 else drvloc'
var = driver.variables.new()
var.name = "drvloc"
var.type="SINGLE_PROP" #not sure if this is needed
var.targets[0].id = drvArmatureObj
var.targets[0].data_path = drvArmatureObj.pose.bones[drvBoneName].path_from_id()+".location.z" #using z location of bone to drive blendshape
#neg shape key
driver = faceObj.data.shape_keys.key_blocks[negShapeKey].driver_add("value").driver
driver.type = "SCRIPTED"
driver.expression= '0 if drvloc > 0 else drvloc*-1.0'
var = driver.variables.new()
var.name = "drvloc"
var.type="SINGLE_PROP" #not sure if this is needed
var.targets[0].id = drvArmatureObj
var.targets[0].data_path = drvArmatureObj.pose.bones[drvBoneName].path_from_id()+".location.z" #using z location of bone to drive blendshape
"""
def getBoneControlsText(armatureObj=None,rootBoneName=None):
#text for the simple facial ui. x is right z is up. it needs armature and rootbone name
print("getBoneControlsText")
#first column
drawTextBone(armatureObj,rootBoneName,"up/dn",(-5,0,10) )
drawTextBone(armatureObj,rootBoneName,"in/out",(-2.5,0,10) )
#middle column
drawTextBone(armatureObj,rootBoneName,"Brows",(0,0,8) )
drawTextBone(armatureObj,rootBoneName,"LipCorner",(0,0,4) )
drawTextBone(armatureObj,rootBoneName,"Mouth",(0,0,0) )
#right column
drawTextBone(armatureObj,rootBoneName,"up/dn",(5,0,10) )
drawTextBone(armatureObj,rootBoneName,"in/out",(7.5,0,10) )
print("finished getBoneControlsText")
"""
def drawTextShape(textStr = '', position=(0.0,0.0,0.0) ):
"""draw text mesh left aligned at position. position is tuple xyz"""
#need to be in object mode
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.text_add()
obj = bpy.context.object
obj.data.body = textStr
obj.matrix_world.translation = position
#convert it to mesh object
bpy.ops.object.convert(target="MESH")
return obj
def getVertsEdgesFromSelected():
"""help for from_pydata creation of widgets. returns tuple verts and edges of selected
"""
obj = bpy.context.object
verts = []
edges = []
for i in range(0,len(obj.data.vertices)):
verts.append( tuple(bpy.context.object.data.vertices[i].co) )
for i in range(0,len(obj.data.edges)):
edges.append( (bpy.context.object.data.edges[i].vertices[0],bpy.context.object.data.edges[i].vertices[1]) )
return verts,edges
def drawRectangleShape(name = None):
""" draw square mesh no faces. if it exists does nothing"""
if not name:
print("requires a name >> exiting")
return
scene = bpy.context.scene
#if data object already created return the object
if name in bpy.data.objects:
print("%s is already created doing nothing" %name)
return bpy.data.objects[name]
#create the object in data
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name,mesh)
scene.objects.link(obj)
if obj is None:
print("issue drawing shape")
return None
#use for from_pydata
verts,edges = ([(-0.25, -1.0, 0.0), (0.25, -1.0, 0.0), (-0.25, 1.0, 0.0), (0.25, 1.0, 0.0)], [(2, 0), (0, 1), (1, 3), (3, 2)])
meshData = obj.data
meshData.from_pydata(verts,edges,[])
meshData.update()
return obj
def drawSquareShape(name = None):
""" draw square mesh no faces. if it exists does nothing"""
if not name:
print("requires a name >> exiting")
return
scene = bpy.context.scene
#if data object already created return the object
if name in bpy.data.objects:
print("%s is already created doing nothing" %name)
return bpy.data.objects[name]
#create the object in data
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name,mesh)
scene.objects.link(obj)
if obj is None:
print("issue drawing shape")
return None
#use for from_pydata
verts,edges = ([(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (-1.0, 1.0, 0.0), (1.0, 1.0, 0.0)], [(2, 0), (0, 1), (1, 3), (3, 2)])
meshData = obj.data
meshData.from_pydata(verts,edges,[])
meshData.update()
return obj
def drawCircleShape(shapeName = None):
scene = bpy.context.scene
bpy.ops.object.mode_set(mode='OBJECT')
#do nothing if shape already exists
if shapeName in bpy.data.objects.keys():
return bpy.data.objects[shapeName]
meshObj = bpy.data.meshes.new("Mesh")
boneShape = bpy.data.objects.new(shapeName, meshObj)
scene.objects.link(boneShape)
#new bmesh
bm = bmesh.new()
#load in a mesh
bm.from_mesh(meshObj)
#create circle
bmesh.ops.create_circle(bm,cap_ends=False,diameter=0.1,segments=8)
#write back to mesh
bm.to_mesh(meshObj)
bm.free()
#set active selection
#scene.objects.active = boneShape
#boneShape.select = True
scene.update()
#boneShape.layers = [False]*19+[True] #move to different layer
return boneShape
def rotateShape(shapeObj,amount = 0.0, axis = 'x'):
"""
rotate shape in axis. amount in radians
"""
if axis == 'x':
rotateAxis = (True,False,False)
elif axis == 'y':
rotateAxis = (False,True,False)
elif axis == 'z':
rotateAxis = (False,False,True)
bpy.context.scene.objects.active = shapeObj
bpy.ops.object.mode_set(mode='EDIT',toggle = False)
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.rotate(value = amount,axis = rotateAxis)
bpy.ops.object.mode_set(mode='OBJECT',toggle=False)
##inspired by
#Nathan Vegdahl's rigify
#https://blender.stackexchange.com/questions/145472/blender-2-8-python-convert-text-to-mesh
#https://blender.stackexchange.com/questions/143256/animate-text-bpy-ops-transform-translate
#https://stackoverflow.com/questions/17388912/blender-script-how-to-write-to-text-object
#https://blender.stackexchange.com/questions/15072/scripting-modifying-an-object-in-edit-mode
#https://blender.stackexchange.com/questions/51684/python-create-custom-armature-without-ops
#https://blenderscripting.blogspot.com/2011/06/using-frompydata.html
#https://blender.stackexchange.com/questions/51684/python-create-custom-armature-without-ops
#https://blender.stackexchange.com/questions/91400/how-to-add-circle-to-existing-mesh-by-python-script
#https://blender.stackexchange.com/questions/68907/add-armature-at-each-vertex-location
#https://github.com/StefanUlbrich/RobotEditor/blob/master/armatures.py
#https://blender.stackexchange.com/questions/51684/python-create-custom-armature-without-ops
#https://blenderartists.org/t/making-a-mirror-copy/411641/3 editmode p to separate selected verts after mirror modifier
#https://blenderartists.org/t/restrict-selection-script-help/512403/2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment