Skip to content

Instantly share code, notes, and snippets.

@nathanielanozie
Created April 22, 2023 02:26
Show Gist options
  • Save nathanielanozie/d8c74c9282d65e3c0145084c35989e4e to your computer and use it in GitHub Desktop.
Save nathanielanozie/d8c74c9282d65e3c0145084c35989e4e to your computer and use it in GitHub Desktop.
work in progress arc tracker for Blender 2.79
import bpy
import imp
bl_info = {
"name": "arc tracker (package)",
"author": "Nathaniel Anozie",
"version": (0, 1),
"blender": (2, 79, 0),
"location": "3D View",
"description": "simple animation arc tracker (package)",
"category": "Object",
}
from . import ui
imp.reload(ui)
from . import core
imp.reload(core)
def register():
ui.register()
def unregister():
ui.unregister()
if __name__ == "__main__":
register()
import logging
import bpy
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console
class BonePositionExtractor(object):
"""
#abstraction to get world positions of a pose bone on some previous frames
# get previous frame: current frame cf, cf-1, cf-2, cf-t
# get previous nth frames: current frame cf - 1*n, cf-2*n, cf-3*n ... cf-t*n
#started with what need abstraction to do
"""
"""
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/animationTools/naArcTracker/core.py") #change to path to python file
bpe = testTool.BonePositionExtractor("Armature", "Bone", 10) #currentFrame 10
bpe.getPreviousFrame(3) #should use frames 9,8,7
bpe.getPreviousNthFrame(3,4) #should use frames 6,2,-2
#made up of examples of what results we expect to get
"""
def __init__(self, armature, bone, currentFrame, includeCurrentFrame=True):
"""
@param armature (str) name of armature - data object name
@param bone (str) name of pose bone
@param currentFrame (double) what is current frame
@param includeCurrentFrame (bool) whether to include current frame in calculations or not
"""
self._armature = armature
self._bone = bone #todo: generalize to also support a mesh, maybe even a single vertex, or an empty
self._currentFrame = currentFrame
self._includeCurrentFrame = includeCurrentFrame
def getPreviousFrame(self, framesBack=1):
"""returns list of world positions of bone starting with previous frame
get previous frame: current frame cf, cf-1, cf-2, cf-t
@param framesBack (int) how many frames to go back or length of returned list. should be > 0
ex t=1 says return previous frame. t=2 says return previous frame position and next previous frame position
ex t=3 and current frame is 10 says return list of positions for frames 9, 8, 7
"""
result = []
assert(framesBack>0)
curFrame = self._currentFrame
framesToQuery = list(range((curFrame-framesBack), curFrame))
framesToQuery.reverse() #so first in list is previous frame
if self._includeCurrentFrame:
framesToQuery = [curFrame]+framesToQuery
#get world position on given frames
for frame in framesToQuery:
result.append(self._getWorldPositionOfBone(frame))
return result
def getPreviousNthFrame(self, length=1, skipFrames=2):
"""returns list of world positions of bone of previous nth frames. length of returned list is t
@param length (int) how many times to go back or length of returned list.
@param skipFrames (int) number of skipped frames.
ex: skipFrames=2 and length=2 for a current frame of 10 says return list of positions for frames 8, 6
"""
result = []
assert(length>0)
assert(skipFrames>0)
curFrame = self._currentFrame
framesToQuery = list(range(curFrame-length*skipFrames, curFrame, skipFrames))
framesToQuery.reverse() #so first in list is most recent frame of query
if self._includeCurrentFrame:
framesToQuery = [curFrame]+framesToQuery
#get world position on given frames
for frame in framesToQuery:
result.append(self._getWorldPositionOfBone(frame))
return result
def _getWorldPositionOfBone(self, frame):
"""get the world position of a pose bone for given frame
@param frame (double) frame to query position on
"""
armature = self._armature
bone = self._bone
#save scene current frame
curFrame = bpy.context.scene.frame_current
#get world position of bone
#i think works in pose or object modes
#go to query frame
bpy.context.scene.frame_set(frame)
position = tuple(bpy.data.objects[armature].pose.bones[bone].matrix.translation)
position = [round(x, 4) for x in position] #round to 4 places may want more accuracy
#restore scene current frame
bpy.context.scene.frame_set(curFrame)
return position
import bpy
import bgl
import blf
from . import core
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console
def drawArcCallback(self, context):
"""
currently draw positions for a pose bone over a certain number of frames back
"""
bgl.glEnable(bgl.GL_BLEND)
#do the drawing
width = 2
color = (0.0, 0.0, 1.0, 0.7)
currentFrame = context.scene.frame_current
logger.debug("current frame>>>"+str(currentFrame))
bone = self._bone #Bone
armature = self._armature #Armature
numFramesBack = self._numFramesBack #ex: 3
bpe = core.BonePositionExtractor(armature, bone, currentFrame)
allPoints = bpe.getPreviousFrame(framesBack=numFramesBack)
#ex: allPoints = [(0.0, 0.0, 0.0), (10.0, 10.0, 15.0)]
numPoints = len(allPoints)
bgl.glLineWidth(width)
bgl.glColor4f(*color)
bgl.glBegin(bgl.GL_LINES)
for i in range(0,numPoints-1):
startPoint = allPoints[i]
endPoint = allPoints[i+1]
#todo draw spheres between line segments or something like that
bgl.glVertex3f(*startPoint)
bgl.glVertex3f(*endPoint)
#end drawing
bgl.glEnd()
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
class naArcTrackerOperator(bpy.types.Operator):
bl_idname = "arc.modal_draw"
bl_label = "naArcTracker"
def modal(self, context, event):
#self.report({'INFO'}, event.type)
context.area.tag_redraw() #?
#how to exit
if event.type in {'RIGHTMOUSE', 'ESC'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handleArc, 'WINDOW')
return {'CANCELLED'}
return {'PASS_THROUGH'} #?
#return {'RUNNING_MODAL'} #i think using this stall viewport interaction
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
args = (self, context) #arguments passed to draw callback
#todo: support things other than a pose bone
self._armature = context.object.name
self._bone = context.selected_pose_bones[0].name #only using first selected bone
self._numFramesBack = 10 #todo: be able to get this from ui
self._handleArc = bpy.types.SpaceView3D.draw_handler_add(drawArcCallback, args, 'WINDOW', 'POST_VIEW')
#i think POST_VIEW for drawing in 3d viewport space
#i think POST_PIXEL is drawing in 2d pixel space
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "requires to be in 3d viewport to create arc tracking")
return {'CANCELLED'}
def register():
bpy.utils.register_class(naArcTrackerOperator)
def unregister():
bpy.utils.unregister_class(naArcTrackerOperator)
#inspired by:
#https://blender.stackexchange.com/questions/61699/how-to-draw-geometry-in-3d-view-window-with-bgl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment