1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
23 'name': "Motion Trail",
24 'author': "Bart Crouch",
27 'location': "View3D > Toolbar > Motion Trail tab",
29 'description': "Display and edit motion trails in the 3d-view",
30 'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.6/"\
31 "Py/Scripts/Animation/Motion_Trail",
32 'tracker_url': "http://projects.blender.org/tracker/index.php?"\
33 "func=detail&aid=26374",
34 'category': 'Animation'}
40 from bpy_extras import view3d_utils
45 # fake fcurve class, used if no fcurve is found for a path
47 def __init__(self, object, index, rotation=False, scale=False):
49 if not rotation and not scale:
50 self.loc = object.location[index]
53 self.loc = object.scale[index]
55 elif rotation == 'QUATERNION':
56 self.loc = object.rotation_quaternion[index]
57 elif rotation == 'AXIS_ANGLE':
58 self.loc = object.rotation_axis_angle[index]
60 self.loc = object.rotation_euler[index]
61 self.keyframe_points = []
63 def evaluate(self, frame):
70 # get location curves of the given object
71 def get_curves(object, child=False):
72 if object.animation_data and object.animation_data.action:
73 action = object.animation_data.action
76 curves = [fc for fc in action.fcurves if len(fc.data_path)>=14 \
77 and fc.data_path[-9:]=='.location' and \
78 child.name in fc.data_path.split("\"")]
81 curves = [fc for fc in action.fcurves if \
82 fc.data_path == 'location']
83 elif object.animation_data and object.animation_data.use_nla:
86 for track in object.animation_data.nla_tracks:
87 not_handled = [s for s in track.strips]
89 current_strip = not_handled.pop(-1)
90 if current_strip.action:
91 strips.append(current_strip)
92 if current_strip.strips:
94 not_handled += [s for s in current_strip.strips]
99 curves = [fc for fc in strip.action.fcurves if \
100 len(fc.data_path)>=14 and fc.data_path[-9:]=='.location' \
101 and child.name in fc.data_path.split("\"")]
104 curves = [fc for fc in strip.action.fcurves if \
105 fc.data_path == 'location']
107 # use first strip with location fcurves
113 # ensure we have three curves per object
118 if fc.array_index == 0:
120 elif fc.array_index == 1:
122 elif fc.array_index == 2:
125 fcx = fake_fcurve(object, 0)
127 fcy = fake_fcurve(object, 1)
129 fcz = fake_fcurve(object, 2)
131 return([fcx, fcy, fcz])
134 # turn screen coordinates (x,y) into world coordinates vector
135 def screen_to_world(context, x, y):
136 depth_vector = view3d_utils.region_2d_to_vector_3d(\
137 context.region, context.region_data, [x,y])
138 vector = view3d_utils.region_2d_to_location_3d(\
139 context.region, context.region_data, [x,y], depth_vector)
144 # turn 3d world coordinates vector into screen coordinate integers (x,y)
145 def world_to_screen(context, vector):
146 prj = context.region_data.perspective_matrix * \
147 mathutils.Vector((vector[0], vector[1], vector[2], 1.0))
148 width_half = context.region.width / 2.0
149 height_half = context.region.height / 2.0
151 x = int(width_half + width_half * (prj.x / prj.w))
152 y = int(height_half + height_half * (prj.y / prj.w))
154 # correction for corner cases in perspective mode
157 x = context.region.width * 2
159 x = context.region.width * -2
161 y = context.region.height * 2
163 y = context.region.height * -2
168 # calculate location of display_ob in worldspace
169 def get_location(frame, display_ob, offset_ob, curves):
171 bpy.context.scene.frame_set(frame)
172 display_mat = getattr(display_ob, "matrix", False)
174 # posebones have "matrix", objects have "matrix_world"
175 display_mat = display_ob.matrix_world
177 loc = display_mat.to_translation() + \
178 offset_ob.matrix_world.to_translation()
180 loc = display_mat.to_translation()
182 fcx, fcy, fcz = curves
183 locx = fcx.evaluate(frame)
184 locy = fcy.evaluate(frame)
185 locz = fcz.evaluate(frame)
186 loc = mathutils.Vector([locx, locy, locz])
191 # get position of keyframes and handles at the start of dragging
192 def get_original_animation_data(context, keyframes):
196 if context.active_object and context.active_object.mode == 'POSE':
197 armature_ob = context.active_object
198 objects = [[armature_ob, pb, armature_ob] for pb in \
199 context.selected_pose_bones]
201 objects = [[ob, False, False] for ob in context.selected_objects]
203 for action_ob, child, offset_ob in objects:
204 if not action_ob.animation_data:
206 curves = get_curves(action_ob, child)
209 fcx, fcy, fcz = curves
213 display_ob = action_ob
215 # get keyframe positions
216 frame_old = context.scene.frame_current
217 keyframes_ori[display_ob.name] = {}
218 for frame in keyframes[display_ob.name]:
219 loc = get_location(frame, display_ob, offset_ob, curves)
220 keyframes_ori[display_ob.name][frame] = [frame, loc]
222 # get handle positions
223 handles_ori[display_ob.name] = {}
224 for frame in keyframes[display_ob.name]:
225 handles_ori[display_ob.name][frame] = {}
226 left_x = [frame, fcx.evaluate(frame)]
227 right_x = [frame, fcx.evaluate(frame)]
228 for kf in fcx.keyframe_points:
229 if kf.co[0] == frame:
230 left_x = kf.handle_left[:]
231 right_x = kf.handle_right[:]
233 left_y = [frame, fcy.evaluate(frame)]
234 right_y = [frame, fcy.evaluate(frame)]
235 for kf in fcy.keyframe_points:
236 if kf.co[0] == frame:
237 left_y = kf.handle_left[:]
238 right_y = kf.handle_right[:]
240 left_z = [frame, fcz.evaluate(frame)]
241 right_z = [frame, fcz.evaluate(frame)]
242 for kf in fcz.keyframe_points:
243 if kf.co[0] == frame:
244 left_z = kf.handle_left[:]
245 right_z = kf.handle_right[:]
247 handles_ori[display_ob.name][frame]["left"] = [left_x, left_y,
249 handles_ori[display_ob.name][frame]["right"] = [right_x, right_y,
252 if context.scene.frame_current != frame_old:
253 context.scene.frame_set(frame_old)
255 return(keyframes_ori, handles_ori)
258 # callback function that calculates positions of all things that need be drawn
259 def calc_callback(self, context):
260 if context.active_object and context.active_object.mode == 'POSE':
261 armature_ob = context.active_object
262 objects = [[armature_ob, pb, armature_ob] for pb in \
263 context.selected_pose_bones]
265 objects = [[ob, False, False] for ob in context.selected_objects]
266 if objects == self.displayed:
267 selection_change = False
269 selection_change = True
271 if self.lock and not selection_change and \
272 context.region_data.perspective_matrix == self.perspective and not \
273 context.window_manager.motion_trail.force_update:
276 # dictionaries with key: objectname
277 self.paths = {} # value: list of lists with x, y, color
278 self.keyframes = {} # value: dict with frame as key and [x,y] as value
279 self.handles = {} # value: dict of dicts
280 self.timebeads = {} # value: dict with frame as key and [x,y] as value
281 self.click = {} # value: list of lists with frame, type, loc-vector
283 # value: editbone inverted rotation matrix or None
285 if selection_change or not self.lock or context.window_manager.\
286 motion_trail.force_update:
287 # contains locations of path, keyframes and timebeads
288 self.cached = {"path":{}, "keyframes":{}, "timebeads_timing":{},
289 "timebeads_speed":{}}
290 if self.cached["path"]:
294 self.perspective = context.region_data.perspective_matrix.copy()
295 self.displayed = objects # store, so it can be checked next time
296 context.window_manager.motion_trail.force_update = False
298 global_undo = context.user_preferences.edit.use_global_undo
299 context.user_preferences.edit.use_global_undo = False
301 for action_ob, child, offset_ob in objects:
304 self.edit_bones[action_ob.name] = None
306 bpy.ops.object.mode_set(mode='EDIT')
307 editbones = action_ob.data.edit_bones
308 mat = editbones[child.name].matrix.copy().to_3x3().inverted()
309 bpy.ops.object.mode_set(mode='POSE')
310 self.edit_bones[child.name] = mat
312 if not action_ob.animation_data:
314 curves = get_curves(action_ob, child)
318 if context.window_manager.motion_trail.path_before == 0:
319 range_min = context.scene.frame_start
321 range_min = max(context.scene.frame_start,
322 context.scene.frame_current - \
323 context.window_manager.motion_trail.path_before)
324 if context.window_manager.motion_trail.path_after == 0:
325 range_max = context.scene.frame_end
327 range_max = min(context.scene.frame_end,
328 context.scene.frame_current + \
329 context.window_manager.motion_trail.path_after)
330 fcx, fcy, fcz = curves
334 display_ob = action_ob
336 # get location data of motion path
339 frame_old = context.scene.frame_current
340 step = 11 - context.window_manager.motion_trail.path_resolution
343 if display_ob.name not in self.cached["path"]:
344 self.cached["path"][display_ob.name] = {}
345 if use_cache and range_min-1 in self.cached["path"][display_ob.name]:
346 prev_loc = self.cached["path"][display_ob.name][range_min-1]
348 prev_loc = get_location(range_min-1, display_ob, offset_ob, curves)
349 self.cached["path"][display_ob.name][range_min-1] = prev_loc
351 for frame in range(range_min, range_max + 1, step):
352 if use_cache and frame in self.cached["path"][display_ob.name]:
353 loc = self.cached["path"][display_ob.name][frame]
355 loc = get_location(frame, display_ob, offset_ob, curves)
356 self.cached["path"][display_ob.name][frame] = loc
357 if not context.region or not context.space_data:
359 x, y = world_to_screen(context, loc)
360 if context.window_manager.motion_trail.path_style == 'simple':
361 path.append([x, y, [0.0, 0.0, 0.0], frame, action_ob, child])
363 dloc = (loc - prev_loc).length
364 path.append([x, y, dloc, frame, action_ob, child])
368 # calculate color of path
369 if context.window_manager.motion_trail.path_style == 'speed':
371 min_speed = speeds[0]
372 d_speed = speeds[-1] - min_speed
373 for i, [x, y, d_loc, frame, action_ob, child] in enumerate(path):
374 relative_speed = (d_loc - min_speed) / d_speed # 0.0 to 1.0
375 red = min(1.0, 2.0 * relative_speed)
376 blue = min(1.0, 2.0 - (2.0 * relative_speed))
377 path[i][2] = [red, 0.0, blue]
378 elif context.window_manager.motion_trail.path_style == 'acceleration':
381 for i, [x, y, d_loc, frame, action_ob, child] in enumerate(path):
382 accel = d_loc - prev_speed
383 accelerations.append(accel)
387 min_accel = accelerations[0]
388 max_accel = accelerations[-1]
389 for i, [x, y, accel, frame, action_ob, child] in enumerate(path):
391 relative_accel = accel / min_accel # values from 0.0 to 1.0
392 green = 1.0 - relative_accel
393 path[i][2] = [1.0, green, 0.0]
395 relative_accel = accel / max_accel # values from 0.0 to 1.0
396 red = 1.0 - relative_accel
397 path[i][2] = [red, 1.0, 0.0]
399 path[i][2] = [1.0, 1.0, 0.0]
400 self.paths[display_ob.name] = path
402 # get keyframes and handles
408 if display_ob.name not in self.cached["keyframes"]:
409 self.cached["keyframes"][display_ob.name] = {}
412 for kf in fc.keyframe_points:
413 # handles for location mode
414 if context.window_manager.motion_trail.mode == 'location':
415 if kf.co[0] not in handle_difs:
416 handle_difs[kf.co[0]] = {"left":mathutils.Vector(),
417 "right":mathutils.Vector(), "keyframe_loc":None}
418 handle_difs[kf.co[0]]["left"][fc.array_index] = \
419 (mathutils.Vector(kf.handle_left[:]) - \
420 mathutils.Vector(kf.co[:])).normalized()[1]
421 handle_difs[kf.co[0]]["right"][fc.array_index] = \
422 (mathutils.Vector(kf.handle_right[:]) - \
423 mathutils.Vector(kf.co[:])).normalized()[1]
425 if kf.co[0] in kf_time:
427 kf_time.append(kf.co[0])
430 if use_cache and co in \
431 self.cached["keyframes"][display_ob.name]:
432 loc = self.cached["keyframes"][display_ob.name][co]
434 loc = get_location(co, display_ob, offset_ob, curves)
435 self.cached["keyframes"][display_ob.name][co] = loc
437 handle_difs[co]["keyframe_loc"] = loc
439 x, y = world_to_screen(context, loc)
440 keyframes[kf.co[0]] = [x, y]
441 if context.window_manager.motion_trail.mode != 'speed':
442 # can't select keyframes in speed mode
443 click.append([kf.co[0], "keyframe",
444 mathutils.Vector([x,y]), action_ob, child])
445 self.keyframes[display_ob.name] = keyframes
447 # handles are only shown in location-altering mode
448 if context.window_manager.motion_trail.mode == 'location' and \
449 context.window_manager.motion_trail.handle_display:
450 # calculate handle positions
452 for frame, vecs in handle_difs.items():
454 # bone space to world space
455 mat = self.edit_bones[child.name].copy().inverted()
456 vec_left = vecs["left"] * mat
457 vec_right = vecs["right"] * mat
459 vec_left = vecs["left"]
460 vec_right = vecs["right"]
461 if vecs["keyframe_loc"] != None:
462 vec_keyframe = vecs["keyframe_loc"]
464 vec_keyframe = get_location(frame, display_ob, offset_ob,
466 x_left, y_left = world_to_screen(context, vec_left*2 + \
468 x_right, y_right = world_to_screen(context, vec_right*2 + \
470 handles[frame] = {"left":[x_left, y_left],
471 "right":[x_right, y_right]}
472 click.append([frame, "handle_left",
473 mathutils.Vector([x_left, y_left]), action_ob, child])
474 click.append([frame, "handle_right",
475 mathutils.Vector([x_right, y_right]), action_ob, child])
476 self.handles[display_ob.name] = handles
478 # calculate timebeads for timing mode
479 if context.window_manager.motion_trail.mode == 'timing':
481 n = context.window_manager.motion_trail.timebeads * (len(kf_time) \
483 dframe = (range_max - range_min) / (n + 1)
485 if display_ob.name not in self.cached["timebeads_timing"]:
486 self.cached["timebeads_timing"][display_ob.name] = {}
488 for i in range(1, n+1):
489 frame = range_min + i * dframe
490 if use_cache and frame in \
491 self.cached["timebeads_timing"][display_ob.name]:
492 loc = self.cached["timebeads_timing"][display_ob.name]\
495 loc = get_location(frame, display_ob, offset_ob, curves)
496 self.cached["timebeads_timing"][display_ob.name][frame] = \
498 x, y = world_to_screen(context, loc)
499 timebeads[frame] = [x, y]
500 click.append([frame, "timebead", mathutils.Vector([x,y]),
502 self.timebeads[display_ob.name] = timebeads
504 # calculate timebeads for speed mode
505 if context.window_manager.motion_trail.mode == 'speed':
506 angles = dict([[kf, {"left":[], "right":[]}] for kf in \
507 self.keyframes[display_ob.name]])
509 for i, kf in enumerate(fc.keyframe_points):
511 angle = mathutils.Vector([-1, 0]).angle(mathutils.\
512 Vector(kf.handle_left) - mathutils.Vector(kf.co),
515 angles[kf.co[0]]["left"].append(angle)
516 if i != len(fc.keyframe_points) - 1:
517 angle = mathutils.Vector([1, 0]).angle(mathutils.\
518 Vector(kf.handle_right) - mathutils.Vector(kf.co),
521 angles[kf.co[0]]["right"].append(angle)
525 if display_ob.name not in self.cached["timebeads_speed"]:
526 self.cached["timebeads_speed"][display_ob.name] = {}
528 for frame, sides in angles.items():
530 perc = (sum(sides["left"]) / len(sides["left"])) / \
532 perc = max(0.4, min(1, perc * 5))
533 previous = kf_time[kf_time.index(frame) - 1]
534 bead_frame = frame - perc * ((frame - previous - 2) / 2)
535 if use_cache and bead_frame in \
536 self.cached["timebeads_speed"][display_ob.name]:
537 loc = self.cached["timebeads_speed"][display_ob.name]\
540 loc = get_location(bead_frame, display_ob, offset_ob,
542 self.cached["timebeads_speed"][display_ob.name]\
544 x, y = world_to_screen(context, loc)
545 timebeads[bead_frame] = [x, y]
546 click.append([bead_frame, "timebead", mathutils.\
547 Vector([x,y]), action_ob, child])
549 perc = (sum(sides["right"]) / len(sides["right"])) / \
551 perc = max(0.4, min(1, perc * 5))
552 next = kf_time[kf_time.index(frame) + 1]
553 bead_frame = frame + perc * ((next - frame - 2) / 2)
554 if use_cache and bead_frame in \
555 self.cached["timebeads_speed"][display_ob.name]:
556 loc = self.cached["timebeads_speed"][display_ob.name]\
559 loc = get_location(bead_frame, display_ob, offset_ob,
561 self.cached["timebeads_speed"][display_ob.name]\
563 x, y = world_to_screen(context, loc)
564 timebeads[bead_frame] = [x, y]
565 click.append([bead_frame, "timebead", mathutils.\
566 Vector([x,y]), action_ob, child])
567 self.timebeads[display_ob.name] = timebeads
569 # add frame positions to click-list
570 if context.window_manager.motion_trail.frame_display:
571 path = self.paths[display_ob.name]
572 for x, y, color, frame, action_ob, child in path:
573 click.append([frame, "frame", mathutils.Vector([x,y]),
576 self.click[display_ob.name] = click
578 if context.scene.frame_current != frame_old:
579 context.scene.frame_set(frame_old)
581 context.user_preferences.edit.use_global_undo = global_undo
585 def draw_callback(self, context):
587 if context.mode not in ['OBJECT', 'POSE'] or \
588 context.window_manager.motion_trail.enabled != 1:
592 if context.window_manager.motion_trail.path_before != 0:
593 limit_min = context.scene.frame_current - \
594 context.window_manager.motion_trail.path_before
597 if context.window_manager.motion_trail.path_after != 0:
598 limit_max = context.scene.frame_current + \
599 context.window_manager.motion_trail.path_after
604 bgl.glEnable(bgl.GL_BLEND)
605 bgl.glLineWidth(context.window_manager.motion_trail.path_width)
606 alpha = 1.0 - (context.window_manager.motion_trail.path_transparency / \
608 if context.window_manager.motion_trail.path_style == 'simple':
609 bgl.glColor4f(0.0, 0.0, 0.0, alpha)
610 for objectname, path in self.paths.items():
611 bgl.glBegin(bgl.GL_LINE_STRIP)
612 for x, y, color, frame, action_ob, child in path:
613 if frame < limit_min or frame > limit_max:
618 for objectname, path in self.paths.items():
619 for i, [x, y, color, frame, action_ob, child] in enumerate(path):
620 if frame < limit_min or frame > limit_max:
624 prev_path = path[i-1]
625 halfway = [(x + prev_path[0])/2, (y + prev_path[1])/2]
626 bgl.glColor4f(r, g, b, alpha)
627 bgl.glBegin(bgl.GL_LINE_STRIP)
628 bgl.glVertex2i(int(halfway[0]), int(halfway[1]))
631 if i != len(path) - 1:
632 next_path = path[i+1]
633 halfway = [(x + next_path[0])/2, (y + next_path[1])/2]
634 bgl.glColor4f(r, g, b, alpha)
635 bgl.glBegin(bgl.GL_LINE_STRIP)
637 bgl.glVertex2i(int(halfway[0]), int(halfway[1]))
641 if context.window_manager.motion_trail.frame_display:
642 bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
644 bgl.glBegin(bgl.GL_POINTS)
645 for objectname, path in self.paths.items():
646 for x, y, color, frame, action_ob, child in path:
647 if frame < limit_min or frame > limit_max:
649 if self.active_frame and objectname == self.active_frame[0] \
650 and abs(frame - self.active_frame[1]) < 1e-4:
652 bgl.glColor4f(1.0, 0.5, 0.0, 1.0)
654 bgl.glBegin(bgl.GL_POINTS)
657 bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
659 bgl.glBegin(bgl.GL_POINTS)
664 # time beads are shown in speed and timing modes
665 if context.window_manager.motion_trail.mode in ['speed', 'timing']:
666 bgl.glColor4f(0.0, 1.0, 0.0, 1.0)
668 bgl.glBegin(bgl.GL_POINTS)
669 for objectname, values in self.timebeads.items():
670 for frame, coords in values.items():
671 if frame < limit_min or frame > limit_max:
673 if self.active_timebead and \
674 objectname == self.active_timebead[0] and \
675 abs(frame - self.active_timebead[1]) < 1e-4:
677 bgl.glColor4f(1.0, 0.5, 0.0, 1.0)
678 bgl.glBegin(bgl.GL_POINTS)
679 bgl.glVertex2i(coords[0], coords[1])
681 bgl.glColor4f(0.0, 1.0, 0.0, 1.0)
682 bgl.glBegin(bgl.GL_POINTS)
684 bgl.glVertex2i(coords[0], coords[1])
687 # handles are only shown in location mode
688 if context.window_manager.motion_trail.mode == 'location':
690 bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
692 bgl.glBegin(bgl.GL_LINES)
693 for objectname, values in self.handles.items():
694 for frame, sides in values.items():
695 if frame < limit_min or frame > limit_max:
697 for side, coords in sides.items():
698 if self.active_handle and \
699 objectname == self.active_handle[0] and \
700 side == self.active_handle[2] and \
701 abs(frame - self.active_handle[1]) < 1e-4:
703 bgl.glColor4f(.75, 0.25, 0.0, 1.0)
704 bgl.glBegin(bgl.GL_LINES)
705 bgl.glVertex2i(self.keyframes[objectname][frame][0],
706 self.keyframes[objectname][frame][1])
707 bgl.glVertex2i(coords[0], coords[1])
709 bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
710 bgl.glBegin(bgl.GL_LINES)
712 bgl.glVertex2i(self.keyframes[objectname][frame][0],
713 self.keyframes[objectname][frame][1])
714 bgl.glVertex2i(coords[0], coords[1])
718 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
720 bgl.glBegin(bgl.GL_POINTS)
721 for objectname, values in self.handles.items():
722 for frame, sides in values.items():
723 if frame < limit_min or frame > limit_max:
725 for side, coords in sides.items():
726 if self.active_handle and \
727 objectname == self.active_handle[0] and \
728 side == self.active_handle[2] and \
729 abs(frame - self.active_handle[1]) < 1e-4:
731 bgl.glColor4f(1.0, 0.5, 0.0, 1.0)
732 bgl.glBegin(bgl.GL_POINTS)
733 bgl.glVertex2i(coords[0], coords[1])
735 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
736 bgl.glBegin(bgl.GL_POINTS)
738 bgl.glVertex2i(coords[0], coords[1])
742 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
744 bgl.glBegin(bgl.GL_POINTS)
745 for objectname, values in self.keyframes.items():
746 for frame, coords in values.items():
747 if frame < limit_min or frame > limit_max:
749 if self.active_keyframe and \
750 objectname == self.active_keyframe[0] and \
751 abs(frame - self.active_keyframe[1]) < 1e-4:
753 bgl.glColor4f(1.0, 0.5, 0.0, 1.0)
754 bgl.glBegin(bgl.GL_POINTS)
755 bgl.glVertex2i(coords[0], coords[1])
757 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
758 bgl.glBegin(bgl.GL_POINTS)
760 bgl.glVertex2i(coords[0], coords[1])
763 # draw keyframe-numbers
764 if context.window_manager.motion_trail.keyframe_numbers:
766 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
767 for objectname, values in self.keyframes.items():
768 for frame, coords in values.items():
769 if frame < limit_min or frame > limit_max:
771 blf.position(0, coords[0] + 3, coords[1] + 3, 0)
772 text = str(frame).split(".")
775 elif len(text[1]) == 1 and text[1] == "0":
778 text = text[0] + "." + text[1][0]
779 if self.active_keyframe and \
780 objectname == self.active_keyframe[0] and \
781 abs(frame - self.active_keyframe[1]) < 1e-4:
782 bgl.glColor4f(1.0, 0.5, 0.0, 1.0)
784 bgl.glColor4f(1.0, 1.0, 0.0, 1.0)
788 # restore opengl defaults
790 bgl.glDisable(bgl.GL_BLEND)
791 bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
795 # change data based on mouse movement
796 def drag(context, event, drag_mouse_ori, active_keyframe, active_handle,
797 active_timebead, keyframes_ori, handles_ori, edit_bones):
798 # change 3d-location of keyframe
799 if context.window_manager.motion_trail.mode == 'location' and \
801 objectname, frame, frame_ori, action_ob, child = active_keyframe
803 mat = action_ob.matrix_world.copy().inverted() * \
804 edit_bones[child.name].copy().to_4x4()
808 mouse_ori_world = screen_to_world(context, drag_mouse_ori[0],
809 drag_mouse_ori[1]) * mat
810 vector = screen_to_world(context, event.mouse_region_x,
811 event.mouse_region_y) * mat
812 d = vector - mouse_ori_world
814 loc_ori_ws = keyframes_ori[objectname][frame][1]
815 loc_ori_bs = loc_ori_ws * mat
816 new_loc = loc_ori_bs + d
817 curves = get_curves(action_ob, child)
819 for i, curve in enumerate(curves):
820 for kf in curve.keyframe_points:
821 if kf.co[0] == frame:
822 kf.co[1] = new_loc[i]
823 kf.handle_left[1] = handles_ori[objectname][frame]\
824 ["left"][i][1] + d[i]
825 kf.handle_right[1] = handles_ori[objectname][frame]\
826 ["right"][i][1] + d[i]
829 # change 3d-location of handle
830 elif context.window_manager.motion_trail.mode == 'location' and \
832 objectname, frame, side, action_ob, child = active_handle
834 mat = action_ob.matrix_world.copy().inverted() * \
835 edit_bones[child.name].copy().to_4x4()
839 mouse_ori_world = screen_to_world(context, drag_mouse_ori[0],
840 drag_mouse_ori[1]) * mat
841 vector = screen_to_world(context, event.mouse_region_x,
842 event.mouse_region_y) * mat
843 d = vector - mouse_ori_world
844 curves = get_curves(action_ob, child)
846 for i, curve in enumerate(curves):
847 for kf in curve.keyframe_points:
848 if kf.co[0] == frame:
850 # change handle type, if necessary
851 if kf.handle_left_type in ['AUTO', 'AUTO_CLAMPED',
853 kf.handle_left_type = 'ALIGNED'
854 elif kf.handle_left_type == 'VECTOR':
855 kf.handle_left_type = 'FREE'
856 # change handle position(s)
857 kf.handle_left[1] = handles_ori[objectname][frame]\
858 ["left"][i][1] + d[i]
859 if kf.handle_left_type in ['ALIGNED', 'ANIM_CLAMPED',
860 'AUTO', 'AUTO_CLAMPED']:
861 dif = (abs(handles_ori[objectname][frame]["right"]\
862 [i][0] - kf.co[0]) / abs(kf.handle_left[0] - \
864 kf.handle_right[1] = handles_ori[objectname]\
865 [frame]["right"][i][1] - dif
866 elif side == "right":
867 # change handle type, if necessary
868 if kf.handle_right_type in ['AUTO', 'AUTO_CLAMPED',
870 kf.handle_left_type = 'ALIGNED'
871 kf.handle_right_type = 'ALIGNED'
872 elif kf.handle_right_type == 'VECTOR':
873 kf.handle_left_type = 'FREE'
874 kf.handle_right_type = 'FREE'
875 # change handle position(s)
876 kf.handle_right[1] = handles_ori[objectname][frame]\
877 ["right"][i][1] + d[i]
878 if kf.handle_right_type in ['ALIGNED', 'ANIM_CLAMPED',
879 'AUTO', 'AUTO_CLAMPED']:
880 dif = (abs(handles_ori[objectname][frame]["left"]\
881 [i][0] - kf.co[0]) / abs(kf.handle_right[0] - \
883 kf.handle_left[1] = handles_ori[objectname]\
884 [frame]["left"][i][1] - dif
887 # change position of all keyframes on timeline
888 elif context.window_manager.motion_trail.mode == 'timing' and \
890 objectname, frame, frame_ori, action_ob, child = active_timebead
891 curves = get_curves(action_ob, child)
892 ranges = [val for c in curves for val in c.range()]
894 range_min = round(ranges[0])
895 range_max = round(ranges[-1])
896 range = range_max - range_min
897 dx_screen = -(mathutils.Vector([event.mouse_region_x,
898 event.mouse_region_y]) - drag_mouse_ori)[0]
899 dx_screen = dx_screen / context.region.width * range
900 new_frame = frame + dx_screen
901 shift_low = max(1e-4, (new_frame - range_min) / (frame - range_min))
902 shift_high = max(1e-4, (range_max - new_frame) / (range_max - frame))
905 for i, curve in enumerate(curves):
906 for j, kf in enumerate(curve.keyframe_points):
908 if frame_map < range_min + 1e-4 or \
909 frame_map > range_max - 1e-4:
912 for f in keyframes_ori[objectname]:
913 if abs(f - frame_map) < 1e-4:
914 frame_ori = keyframes_ori[objectname][f][0]
915 value_ori = keyframes_ori[objectname][f]
919 if frame_ori <= frame:
920 frame_new = (frame_ori - range_min) * shift_low + \
923 frame_new = range_max - (range_max - frame_ori) * \
925 frame_new = max(range_min + j, min(frame_new, range_max - \
926 (len(curve.keyframe_points)-j)+1))
927 d_frame = frame_new - frame_ori
928 if frame_new not in new_mapping:
929 new_mapping[frame_new] = value_ori
931 kf.handle_left[0] = handles_ori[objectname][frame_ori]\
932 ["left"][i][0] + d_frame
933 kf.handle_right[0] = handles_ori[objectname][frame_ori]\
934 ["right"][i][0] + d_frame
935 del keyframes_ori[objectname]
936 keyframes_ori[objectname] = {}
937 for new_frame, value in new_mapping.items():
938 keyframes_ori[objectname][new_frame] = value
940 # change position of active keyframe on the timeline
941 elif context.window_manager.motion_trail.mode == 'timing' and \
943 objectname, frame, frame_ori, action_ob, child = active_keyframe
945 mat = action_ob.matrix_world.copy().inverted() * \
946 edit_bones[child.name].copy().to_4x4()
948 mat = action_ob.matrix_world.copy().inverted()
950 mouse_ori_world = screen_to_world(context, drag_mouse_ori[0],
951 drag_mouse_ori[1]) * mat
952 vector = screen_to_world(context, event.mouse_region_x,
953 event.mouse_region_y) * mat
954 d = vector - mouse_ori_world
956 locs_ori = [[f_ori, coords] for f_mapped, [f_ori, coords] in \
957 keyframes_ori[objectname].items()]
961 for i, [f_ori, coords] in enumerate(locs_ori):
962 if abs(frame_ori - f_ori) < 1e-4:
964 # first keyframe, nothing before it
966 range = [f_ori, locs_ori[i+1][0]]
967 elif i == len(locs_ori) - 1:
968 # last keyframe, nothing after it
969 range = [locs_ori[i-1][0], f_ori]
971 current = mathutils.Vector(coords)
972 next = mathutils.Vector(locs_ori[i+1][1])
973 previous = mathutils.Vector(locs_ori[i-1][1])
974 angle_to_next = d.angle(next - current, 0)
975 angle_to_previous = d.angle(previous-current, 0)
976 if angle_to_previous < angle_to_next:
977 # mouse movement is in direction of previous keyframe
979 range = [locs_ori[i-1][0], locs_ori[i+1][0]]
981 direction *= -1 # feels more natural in 3d-view
983 # keyframe not found, is impossible, but better safe than sorry
984 return(active_keyframe, active_timebead, keyframes_ori)
985 # calculate strength of movement
986 d_screen = mathutils.Vector([event.mouse_region_x,
987 event.mouse_region_y]) - drag_mouse_ori
988 if d_screen.length != 0:
989 d_screen = d_screen.length / (abs(d_screen[0])/d_screen.length*\
990 context.region.width + abs(d_screen[1])/d_screen.length*\
991 context.region.height)
992 d_screen *= direction # d_screen value ranges from -1.0 to 1.0
995 new_frame = d_screen * (range[1] - range[0]) + frame_ori
997 if max_frame == frame_ori:
1000 if min_frame == frame_ori:
1002 new_frame = min(max_frame - 1, max(min_frame + 1, new_frame))
1003 d_frame = new_frame - frame_ori
1004 curves = get_curves(action_ob, child)
1006 for i, curve in enumerate(curves):
1007 for kf in curve.keyframe_points:
1008 if abs(kf.co[0] - frame) < 1e-4:
1009 kf.co[0] = new_frame
1010 kf.handle_left[0] = handles_ori[objectname][frame_ori]\
1011 ["left"][i][0] + d_frame
1012 kf.handle_right[0] = handles_ori[objectname][frame_ori]\
1013 ["right"][i][0] + d_frame
1015 active_keyframe = [objectname, new_frame, frame_ori, action_ob, child]
1017 # change position of active timebead on the timeline, thus altering speed
1018 elif context.window_manager.motion_trail.mode == 'speed' and \
1020 objectname, frame, frame_ori, action_ob, child = active_timebead
1022 mat = action_ob.matrix_world.copy().inverted() * \
1023 edit_bones[child.name].copy().to_4x4()
1027 mouse_ori_world = screen_to_world(context, drag_mouse_ori[0],
1028 drag_mouse_ori[1]) * mat
1029 vector = screen_to_world(context, event.mouse_region_x,
1030 event.mouse_region_y) * mat
1031 d = vector - mouse_ori_world
1033 # determine direction (to next or previous keyframe)
1034 curves = get_curves(action_ob, child)
1035 fcx, fcy, fcz = curves
1036 locx = fcx.evaluate(frame_ori)
1037 locy = fcy.evaluate(frame_ori)
1038 locz = fcz.evaluate(frame_ori)
1039 loc_ori = mathutils.Vector([locx, locy, locz]) # bonespace
1040 keyframes = [kf for kf in keyframes_ori[objectname]]
1041 keyframes.append(frame_ori)
1043 frame_index = keyframes.index(frame_ori)
1044 kf_prev = keyframes[frame_index - 1]
1045 kf_next = keyframes[frame_index + 1]
1046 vec_prev = (mathutils.Vector(keyframes_ori[objectname][kf_prev][1]) \
1047 * mat - loc_ori).normalized()
1048 vec_next = (mathutils.Vector(keyframes_ori[objectname][kf_next][1]) \
1049 * mat - loc_ori).normalized()
1050 d_normal = d.copy().normalized()
1051 dist_to_next = (d_normal - vec_next).length
1052 dist_to_prev = (d_normal - vec_prev).length
1053 if dist_to_prev < dist_to_next:
1058 if (kf_next - frame_ori) < (frame_ori - kf_prev):
1064 d_frame = d.length * direction * 2 # *2 to make it more sensitive
1067 for i, curve in enumerate(curves):
1068 for kf in curve.keyframe_points:
1069 if abs(kf.co[0] - kf_bead) < 1e-4:
1072 kf.handle_left[0] = min(handles_ori[objectname]\
1073 [kf_bead]["left"][i][0] + d_frame, kf_bead - 1)
1074 angle = mathutils.Vector([-1, 0]).angle(mathutils.\
1075 Vector(kf.handle_left) - mathutils.Vector(kf.co),
1078 angles.append(angle)
1081 kf.handle_right[0] = max(handles_ori[objectname]\
1082 [kf_bead]["right"][i][0] + d_frame, kf_bead + 1)
1083 angle = mathutils.Vector([1, 0]).angle(mathutils.\
1084 Vector(kf.handle_right) - mathutils.Vector(kf.co),
1087 angles.append(angle)
1090 # update frame of active_timebead
1091 perc = (sum(angles) / len(angles)) / (math.pi / 2)
1092 perc = max(0.4, min(1, perc * 5))
1094 bead_frame = kf_bead - perc * ((kf_bead - kf_prev - 2) / 2)
1096 bead_frame = kf_bead + perc * ((kf_next - kf_bead - 2) / 2)
1097 active_timebead = [objectname, bead_frame, frame_ori, action_ob, child]
1099 return(active_keyframe, active_timebead, keyframes_ori)
1102 # revert changes made by dragging
1103 def cancel_drag(context, active_keyframe, active_handle, active_timebead,
1104 keyframes_ori, handles_ori, edit_bones):
1105 # revert change in 3d-location of active keyframe and its handles
1106 if context.window_manager.motion_trail.mode == 'location' and \
1108 objectname, frame, frame_ori, active_ob, child = active_keyframe
1109 curves = get_curves(active_ob, child)
1110 loc_ori = keyframes_ori[objectname][frame][1]
1112 loc_ori = loc_ori * edit_bones[child.name] * \
1113 active_ob.matrix_world.copy().inverted()
1114 for i, curve in enumerate(curves):
1115 for kf in curve.keyframe_points:
1116 if kf.co[0] == frame:
1117 kf.co[1] = loc_ori[i]
1118 kf.handle_left[1] = handles_ori[objectname][frame]\
1120 kf.handle_right[1] = handles_ori[objectname][frame]\
1124 # revert change in 3d-location of active handle
1125 elif context.window_manager.motion_trail.mode == 'location' and \
1127 objectname, frame, side, active_ob, child = active_handle
1128 curves = get_curves(active_ob, child)
1129 for i, curve in enumerate(curves):
1130 for kf in curve.keyframe_points:
1131 if kf.co[0] == frame:
1132 kf.handle_left[1] = handles_ori[objectname][frame]\
1134 kf.handle_right[1] = handles_ori[objectname][frame]\
1138 # revert position of all keyframes and handles on timeline
1139 elif context.window_manager.motion_trail.mode == 'timing' and \
1141 objectname, frame, frame_ori, active_ob, child = active_timebead
1142 curves = get_curves(active_ob, child)
1143 for i, curve in enumerate(curves):
1144 for kf in curve.keyframe_points:
1145 for kf_ori, [frame_ori, loc] in keyframes_ori[objectname].\
1147 if abs(kf.co[0] - kf_ori) < 1e-4:
1148 kf.co[0] = frame_ori
1149 kf.handle_left[0] = handles_ori[objectname]\
1150 [frame_ori]["left"][i][0]
1151 kf.handle_right[0] = handles_ori[objectname]\
1152 [frame_ori]["right"][i][0]
1155 # revert position of active keyframe and its handles on the timeline
1156 elif context.window_manager.motion_trail.mode == 'timing' and \
1158 objectname, frame, frame_ori, active_ob, child = active_keyframe
1159 curves = get_curves(active_ob, child)
1160 for i, curve in enumerate(curves):
1161 for kf in curve.keyframe_points:
1162 if abs(kf.co[0] - frame) < 1e-4:
1163 kf.co[0] = keyframes_ori[objectname][frame_ori][0]
1164 kf.handle_left[0] = handles_ori[objectname][frame_ori]\
1166 kf.handle_right[0] = handles_ori[objectname][frame_ori]\
1169 active_keyframe = [objectname, frame_ori, frame_ori, active_ob, child]
1171 # revert position of handles on the timeline
1172 elif context.window_manager.motion_trail.mode == 'speed' and \
1174 objectname, frame, frame_ori, active_ob, child = active_timebead
1175 curves = get_curves(active_ob, child)
1176 keyframes = [kf for kf in keyframes_ori[objectname]]
1177 keyframes.append(frame_ori)
1179 frame_index = keyframes.index(frame_ori)
1180 kf_prev = keyframes[frame_index - 1]
1181 kf_next = keyframes[frame_index + 1]
1182 if (kf_next - frame_ori) < (frame_ori - kf_prev):
1186 for i, curve in enumerate(curves):
1187 for kf in curve.keyframe_points:
1188 if kf.co[0] == kf_frame:
1189 kf.handle_left[0] = handles_ori[objectname][kf_frame]\
1191 kf.handle_right[0] = handles_ori[objectname][kf_frame]\
1194 active_timebead = [objectname, frame_ori, frame_ori, active_ob, child]
1196 return(active_keyframe, active_timebead)
1199 # return the handle type of the active selection
1200 def get_handle_type(active_keyframe, active_handle):
1202 objectname, frame, side, action_ob, child = active_keyframe
1205 objectname, frame, side, action_ob, child = active_handle
1207 # no active handle(s)
1210 # properties used when changing handle type
1211 bpy.context.window_manager.motion_trail.handle_type_frame = frame
1212 bpy.context.window_manager.motion_trail.handle_type_side = side
1213 bpy.context.window_manager.motion_trail.handle_type_action_ob = \
1216 bpy.context.window_manager.motion_trail.handle_type_child = child.name
1218 bpy.context.window_manager.motion_trail.handle_type_child = ""
1220 curves = get_curves(action_ob, child=child)
1222 for kf in c.keyframe_points:
1223 if kf.co[0] == frame:
1224 if side in ["left", "both"]:
1225 return(kf.handle_left_type)
1227 return(kf.handle_right_type)
1232 # turn the given frame into a keyframe
1233 def insert_keyframe(self, context, frame):
1234 objectname, frame, frame, action_ob, child = frame
1235 curves = get_curves(action_ob, child)
1237 y = c.evaluate(frame)
1238 if c.keyframe_points:
1239 c.keyframe_points.insert(frame, y)
1241 bpy.context.window_manager.motion_trail.force_update = True
1242 calc_callback(self, context)
1245 # change the handle type of the active selection
1246 def set_handle_type(self, context):
1247 if not context.window_manager.motion_trail.handle_type_enabled:
1249 if context.window_manager.motion_trail.handle_type_old == \
1250 context.window_manager.motion_trail.handle_type:
1251 # function called because of selection change, not change in type
1253 context.window_manager.motion_trail.handle_type_old = \
1254 context.window_manager.motion_trail.handle_type
1256 frame = bpy.context.window_manager.motion_trail.handle_type_frame
1257 side = bpy.context.window_manager.motion_trail.handle_type_side
1258 action_ob = bpy.context.window_manager.motion_trail.handle_type_action_ob
1259 action_ob = bpy.data.objects[action_ob]
1260 child = bpy.context.window_manager.motion_trail.handle_type_child
1262 child = action_ob.pose.bones[child]
1263 new_type = context.window_manager.motion_trail.handle_type
1265 curves = get_curves(action_ob, child=child)
1267 for kf in c.keyframe_points:
1268 if kf.co[0] == frame:
1269 # align if necessary
1270 if side in ["right", "both"] and new_type in \
1271 ["AUTO", "AUTO_CLAMPED", "ALIGNED"]:
1272 # change right handle
1273 normal = (kf.co - kf.handle_left).normalized()
1274 size = (kf.handle_right[0] - kf.co[0]) / normal[0]
1275 normal = normal*size + kf.co
1276 kf.handle_right[1] = normal[1]
1277 elif side == "left" and new_type in ["AUTO", "AUTO_CLAMPED",
1279 # change left handle
1280 normal = (kf.co - kf.handle_right).normalized()
1281 size = (kf.handle_left[0] - kf.co[0]) / normal[0]
1282 normal = normal*size + kf.co
1283 kf.handle_left[1] = normal[1]
1285 if side in ["left", "both"]:
1286 kf.handle_left_type = new_type
1287 if side in ["right", "both"]:
1288 kf.handle_right_type = new_type
1290 context.window_manager.motion_trail.force_update = True
1293 class MotionTrailOperator(bpy.types.Operator):
1294 """Edit motion trails in 3d-view"""
1295 bl_idname = "view3d.motion_trail"
1296 bl_label = "Motion Trail"
1301 def modal(self, context, event):
1302 if context.window_manager.motion_trail.enabled == -1:
1303 context.window_manager.motion_trail.enabled = 0
1305 context.region.callback_remove(self._handle1)
1309 context.region.callback_remove(self._handle2)
1312 context.area.tag_redraw()
1315 if not context.area:
1316 return {'PASS_THROUGH'}
1317 if not context.region or event.type == 'NONE':
1318 context.area.tag_redraw()
1319 return {'PASS_THROUGH'}
1321 select = context.user_preferences.inputs.select_mouse
1322 if not context.active_object or not context.active_object.mode in \
1327 context.window_manager.motion_trail.force_update = True
1328 # default hotkeys should still work
1329 if event.type == self.transform_key and event.value == 'PRESS':
1330 if bpy.ops.transform.translate.poll():
1331 bpy.ops.transform.translate('INVOKE_DEFAULT')
1332 elif event.type == select + 'MOUSE' and event.value == 'PRESS' \
1333 and not self.drag and not event.shift and not event.alt \
1335 if bpy.ops.view3d.select.poll():
1336 bpy.ops.view3d.select('INVOKE_DEFAULT')
1337 elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and not\
1338 event.alt and not event.ctrl and not event.shift:
1339 if eval("bpy.ops."+self.left_action+".poll()"):
1340 eval("bpy.ops."+self.left_action+"('INVOKE_DEFAULT')")
1341 return {'PASS_THROUGH'}
1342 # check if event was generated within 3d-window, dragging is exception
1344 if not (0 < event.mouse_region_x < context.region.width) or \
1345 not (0 < event.mouse_region_y < context.region.height):
1346 return {'PASS_THROUGH'}
1348 if event.type == self.transform_key and event.value == 'PRESS' and \
1349 (self.active_keyframe or self.active_handle or self.active_timebead \
1350 or self.active_frame):
1351 # override default translate()
1354 if self.active_frame:
1355 insert_keyframe(self, context, self.active_frame)
1356 self.active_keyframe = self.active_frame
1357 self.active_frame = False
1358 self.keyframes_ori, self.handles_ori = \
1359 get_original_animation_data(context, self.keyframes)
1360 self.drag_mouse_ori = mathutils.Vector([event.mouse_region_x,
1361 event.mouse_region_y])
1368 context.window_manager.motion_trail.force_update = True
1369 elif event.type == self.transform_key and event.value == 'PRESS':
1370 # call default translate()
1371 if bpy.ops.transform.translate.poll():
1372 bpy.ops.transform.translate('INVOKE_DEFAULT')
1373 elif (event.type == 'ESC' and self.drag and event.value == 'PRESS') \
1374 or (event.type == 'RIGHTMOUSE' and self.drag and event.value == \
1379 context.window_manager.motion_trail.force_update = True
1380 self.active_keyframe, self.active_timebead = cancel_drag(context,
1381 self.active_keyframe, self.active_handle,
1382 self.active_timebead, self.keyframes_ori, self.handles_ori,
1384 elif event.type == 'MOUSEMOVE' and self.drag:
1386 self.active_keyframe, self.active_timebead, self.keyframes_ori = \
1387 drag(context, event, self.drag_mouse_ori,
1388 self.active_keyframe, self.active_handle,
1389 self.active_timebead, self.keyframes_ori, self.handles_ori,
1391 elif event.type == select + 'MOUSE' and event.value == 'PRESS' and \
1392 not self.drag and not event.shift and not event.alt and not \
1396 clicked = mathutils.Vector([event.mouse_region_x,
1397 event.mouse_region_y])
1398 self.active_keyframe = False
1399 self.active_handle = False
1400 self.active_timebead = False
1401 self.active_frame = False
1402 context.window_manager.motion_trail.force_update = True
1403 context.window_manager.motion_trail.handle_type_enabled = True
1406 if context.window_manager.motion_trail.path_before == 0:
1407 frame_min = context.scene.frame_start
1409 frame_min = max(context.scene.frame_start,
1410 context.scene.frame_current - \
1411 context.window_manager.motion_trail.path_before)
1412 if context.window_manager.motion_trail.path_after == 0:
1413 frame_max = context.scene.frame_end
1415 frame_max = min(context.scene.frame_end,
1416 context.scene.frame_current + \
1417 context.window_manager.motion_trail.path_after)
1419 for objectname, values in self.click.items():
1422 for frame, type, coord, action_ob, child in values:
1423 if frame < frame_min or frame > frame_max:
1425 if (coord - clicked).length <= treshold:
1427 if type == "keyframe":
1428 self.active_keyframe = [objectname, frame, frame,
1430 elif type == "handle_left":
1431 self.active_handle = [objectname, frame, "left",
1433 elif type == "handle_right":
1434 self.active_handle = [objectname, frame, "right",
1436 elif type == "timebead":
1437 self.active_timebead = [objectname, frame, frame,
1439 elif type == "frame":
1440 self.active_frame = [objectname, frame, frame,
1444 context.window_manager.motion_trail.handle_type_enabled = False
1445 # no motion trail selections, so pass on to normal select()
1446 if bpy.ops.view3d.select.poll():
1447 bpy.ops.view3d.select('INVOKE_DEFAULT')
1449 handle_type = get_handle_type(self.active_keyframe,
1452 context.window_manager.motion_trail.handle_type_old = \
1454 context.window_manager.motion_trail.handle_type = \
1457 context.window_manager.motion_trail.handle_type_enabled = \
1459 elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and \
1464 context.window_manager.motion_trail.force_update = True
1465 elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and not\
1466 event.alt and not event.ctrl and not event.shift:
1467 if eval("bpy.ops."+self.left_action+".poll()"):
1468 eval("bpy.ops."+self.left_action+"('INVOKE_DEFAULT')")
1470 if context.area: # not available if other window-type is fullscreen
1471 context.area.tag_redraw()
1473 return {'PASS_THROUGH'}
1475 def invoke(self, context, event):
1476 if context.area.type == 'VIEW_3D':
1477 # get clashing keymap items
1478 select = context.user_preferences.inputs.select_mouse
1479 kms = [bpy.context.window_manager.keyconfigs.active.\
1480 keymaps['3D View'], bpy.context.window_manager.keyconfigs.\
1481 active.keymaps['Object Mode']]
1483 self.left_action = None
1484 self.right_action = None
1486 for kmi in km.keymap_items:
1487 if kmi.idname == "transform.translate" and \
1488 kmi.map_type == 'KEYBOARD' and not \
1489 kmi.properties.texture_space:
1491 self.transform_key = kmi.type
1492 elif (kmi.type == 'ACTIONMOUSE' and select == 'RIGHT') \
1493 and not kmi.alt and not kmi.any and not kmi.ctrl \
1496 self.left_action = kmi.idname
1497 elif kmi.type == 'SELECTMOUSE' and not kmi.alt and not \
1498 kmi.any and not kmi.ctrl and not kmi.shift:
1500 if select == 'RIGHT':
1501 self.right_action = kmi.idname
1503 self.left_action = kmi.idname
1504 elif kmi.type == 'LEFTMOUSE' and not kmi.alt and not \
1505 kmi.any and not kmi.ctrl and not kmi.shift:
1507 self.left_action = kmi.idname
1509 if context.window_manager.motion_trail.enabled == 0:
1511 context.window_manager.motion_trail.enabled = 1
1512 self.active_keyframe = False
1513 self.active_handle = False
1514 self.active_timebead = False
1515 self.active_frame = False
1519 self.perspective = context.region_data.perspective_matrix
1521 context.window_manager.motion_trail.force_update = True
1522 context.window_manager.motion_trail.handle_type_enabled = False
1523 self.cached = {"path":{}, "keyframes":{},
1524 "timebeads_timing":{}, "timebeads_speed":{}}
1529 self._handle1 = context.region.callback_add(calc_callback,
1530 (self, context), 'POST_VIEW')
1531 self._handle2 = context.region.callback_add(draw_callback,
1532 (self, context), 'POST_PIXEL')
1534 context.area.tag_redraw()
1536 context.window_manager.modal_handler_add(self)
1539 context.window_manager.motion_trail.enabled = -1
1543 return {'RUNNING_MODAL'}
1546 self.report({'WARNING'}, "View3D not found, cannot run operator")
1547 return {'CANCELLED'}
1550 class MotionTrailPanel(bpy.types.Panel):
1551 bl_space_type = 'VIEW_3D'
1552 bl_region_type = 'TOOLS'
1553 bl_label = "Motion Trail"
1554 bl_options = {'DEFAULT_CLOSED'}
1556 def poll(cls, context):
1557 if not context.active_object:
1559 return(context.active_object.mode in ['OBJECT', 'POSE'])
1561 def draw(self, context):
1562 col = self.layout.column()
1563 if context.window_manager.motion_trail.enabled != 1:
1564 col.operator("view3d.motion_trail", text="Enable motion trail")
1566 col.operator("view3d.motion_trail", text="Disable motion trail")
1568 box = self.layout.box()
1569 box.prop(context.window_manager.motion_trail, "mode")
1570 #box.prop(context.window_manager.motion_trail, "calculate")
1571 if context.window_manager.motion_trail.mode in ['timing']:
1572 box.prop(context.window_manager.motion_trail, "timebeads")
1574 box = self.layout.box()
1577 if context.window_manager.motion_trail.path_display:
1578 row.prop(context.window_manager.motion_trail, "path_display",
1579 icon="DOWNARROW_HLT", text="", emboss=False)
1581 row.prop(context.window_manager.motion_trail, "path_display",
1582 icon="RIGHTARROW", text="", emboss=False)
1583 row.label("Path options")
1584 if context.window_manager.motion_trail.path_display:
1585 col.prop(context.window_manager.motion_trail, "path_style",
1587 grouped = col.column(align=True)
1588 grouped.prop(context.window_manager.motion_trail, "path_width",
1590 grouped.prop(context.window_manager.motion_trail,
1591 "path_transparency", text="Transparency")
1592 grouped.prop(context.window_manager.motion_trail,
1594 row = grouped.row(align=True)
1595 row.prop(context.window_manager.motion_trail, "path_before")
1596 row.prop(context.window_manager.motion_trail, "path_after")
1597 col = col.column(align=True)
1598 col.prop(context.window_manager.motion_trail, "keyframe_numbers")
1599 col.prop(context.window_manager.motion_trail, "frame_display")
1601 if context.window_manager.motion_trail.mode in ['location']:
1602 box = self.layout.box()
1603 col = box.column(align=True)
1604 col.prop(context.window_manager.motion_trail, "handle_display",
1606 if context.window_manager.motion_trail.handle_display:
1608 row.enabled = context.window_manager.motion_trail.\
1610 row.prop(context.window_manager.motion_trail, "handle_type")
1613 class MotionTrailProps(bpy.types.PropertyGroup):
1614 def internal_update(self, context):
1615 context.window_manager.motion_trail.force_update = True
1617 context.area.tag_redraw()
1620 enabled = bpy.props.IntProperty(default=0)
1621 force_update = bpy.props.BoolProperty(name="internal use",
1622 description="Force calc_callback to fully execute",
1624 handle_type_enabled = bpy.props.BoolProperty(default=False)
1625 handle_type_frame = bpy.props.FloatProperty()
1626 handle_type_side = bpy.props.StringProperty()
1627 handle_type_action_ob = bpy.props.StringProperty()
1628 handle_type_child = bpy.props.StringProperty()
1629 handle_type_old = bpy.props.EnumProperty(items=(("AUTO", "", ""),
1630 ("AUTO_CLAMPED", "", ""), ("VECTOR", "", ""), ("ALIGNED", "", ""),
1631 ("FREE", "", "")), default='AUTO',)
1633 # visible in user interface
1634 calculate = bpy.props.EnumProperty(name="Calculate",
1635 items=(("fast", "Fast", "Recommended setting, change if the "\
1636 "motion path is positioned incorrectly"),
1637 ("full", "Full", "Takes parenting and modifiers into account, "\
1638 "but can be very slow on complicated scenes")),
1639 description="Calculation method for determining locations",
1641 update=internal_update)
1642 frame_display = bpy.props.BoolProperty(name="Frames",
1643 description="Display frames, \n test",
1645 update=internal_update)
1646 handle_display = bpy.props.BoolProperty(name="Display",
1647 description="Display handles",
1649 update=internal_update)
1650 handle_type = bpy.props.EnumProperty(name="Type",
1651 items=(("AUTO", "Automatic", ""),
1652 ("AUTO_CLAMPED", "Auto Clamped", ""),
1653 ("VECTOR", "Vector", ""),
1654 ("ALIGNED", "Aligned", ""),
1655 ("FREE", "Free", "")),
1656 description="Set handle type for the selected handle",
1658 update=set_handle_type)
1659 keyframe_numbers = bpy.props.BoolProperty(name="Keyframe numbers",
1660 description="Display keyframe numbers",
1662 update=internal_update)
1663 mode = bpy.props.EnumProperty(name="Mode",
1664 items=(("location", "Location", "Change path that is followed"),
1665 ("speed", "Speed", "Change speed between keyframes"),
1666 ("timing", "Timing", "Change position of keyframes on timeline")),
1667 description="Enable editing of certain properties in the 3d-view",
1669 update=internal_update)
1670 path_after = bpy.props.IntProperty(name="After",
1671 description="Number of frames to show after the current frame, "\
1675 update=internal_update)
1676 path_before = bpy.props.IntProperty(name="Before",
1677 description="Number of frames to show before the current frame, "\
1681 update=internal_update)
1682 path_display = bpy.props.BoolProperty(name="Path options",
1683 description="Display path options",
1685 path_resolution = bpy.props.IntProperty(name="Resolution",
1686 description="10 is smoothest, but could be "\
1687 "slow when adjusting keyframes, handles or timebeads",
1691 update=internal_update)
1692 path_style = bpy.props.EnumProperty(name="Path style",
1693 items=(("acceleration", "Acceleration", "Gradient based on relative "\
1695 ("simple", "Simple", "Black line"),
1696 ("speed", "Speed", "Gradient based on relative speed")),
1697 description="Information conveyed by path color",
1699 update=internal_update)
1700 path_transparency = bpy.props.IntProperty(name="Path transparency",
1701 description="Determines visibility of path",
1705 subtype='PERCENTAGE',
1706 update=internal_update)
1707 path_width = bpy.props.IntProperty(name="Path width",
1708 description="Width in pixels",
1712 update=internal_update)
1713 timebeads = bpy.props.IntProperty(name="Time beads",
1714 description="Number of time beads to display per segment",
1718 update=internal_update)
1721 classes = [MotionTrailProps,
1722 MotionTrailOperator,
1728 bpy.utils.register_class(c)
1729 bpy.types.WindowManager.motion_trail = bpy.props.PointerProperty(\
1730 type=MotionTrailProps)
1735 bpy.utils.unregister_class(c)
1736 del bpy.types.WindowManager.motion_trail
1739 if __name__ == "__main__":