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 #####
21 "name": "Motion Trail",
22 "author": "Bart Crouch",
24 "blender": (2, 80, 0),
25 "location": "View3D > Toolbar > Motion Trail tab",
26 "warning": "Needs bgl draw update",
27 "description": "Display and edit motion trails in the 3D View",
28 "doc_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
29 "Scripts/Animation/Motion_Trail",
30 "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
31 "category": "Animation",
38 from bpy_extras
import view3d_utils
41 from bpy
.props
import (
51 # fake fcurve class, used if no fcurve is found for a path
53 def __init__(self
, object, index
, rotation
=False, scale
=False):
55 if not rotation
and not scale
:
56 self
.loc
= object.location
[index
]
59 self
.loc
= object.scale
[index
]
61 elif rotation
== 'QUATERNION':
62 self
.loc
= object.rotation_quaternion
[index
]
63 elif rotation
== 'AXIS_ANGLE':
64 self
.loc
= object.rotation_axis_angle
[index
]
66 self
.loc
= object.rotation_euler
[index
]
67 self
.keyframe_points
= []
69 def evaluate(self
, frame
):
76 # get location curves of the given object
77 def get_curves(object, child
=False):
78 if object.animation_data
and object.animation_data
.action
:
79 action
= object.animation_data
.action
83 fc
for fc
in action
.fcurves
if len(fc
.data_path
) >= 14 and
84 fc
.data_path
[-9:] == '.location' and
85 child
.name
in fc
.data_path
.split("\"")
89 curves
= [fc
for fc
in action
.fcurves
if fc
.data_path
== 'location']
91 elif object.animation_data
and object.animation_data
.use_nla
:
94 for track
in object.animation_data
.nla_tracks
:
95 not_handled
= [s
for s
in track
.strips
]
97 current_strip
= not_handled
.pop(-1)
98 if current_strip
.action
:
99 strips
.append(current_strip
)
100 if current_strip
.strips
:
102 not_handled
+= [s
for s
in current_strip
.strips
]
108 fc
for fc
in strip
.action
.fcurves
if
109 len(fc
.data_path
) >= 14 and fc
.data_path
[-9:] == '.location' and
110 child
.name
in fc
.data_path
.split("\"")
114 curves
= [fc
for fc
in strip
.action
.fcurves
if fc
.data_path
== 'location']
116 # use first strip with location fcurves
122 # ensure we have three curves per object
127 if fc
.array_index
== 0:
129 elif fc
.array_index
== 1:
131 elif fc
.array_index
== 2:
134 fcx
= fake_fcurve(object, 0)
136 fcy
= fake_fcurve(object, 1)
138 fcz
= fake_fcurve(object, 2)
140 return([fcx
, fcy
, fcz
])
143 # turn screen coordinates (x,y) into world coordinates vector
144 def screen_to_world(context
, x
, y
):
145 depth_vector
= view3d_utils
.region_2d_to_vector_3d(
146 context
.region
, context
.region_data
, [x
, y
]
148 vector
= view3d_utils
.region_2d_to_location_3d(
149 context
.region
, context
.region_data
, [x
, y
],
156 # turn 3d world coordinates vector into screen coordinate integers (x,y)
157 def world_to_screen(context
, vector
):
158 prj
= context
.region_data
.perspective_matrix
* \
159 mathutils
.Vector((vector
[0], vector
[1], vector
[2], 1.0))
160 width_half
= context
.region
.width
/ 2.0
161 height_half
= context
.region
.height
/ 2.0
163 x
= int(width_half
+ width_half
* (prj
.x
/ prj
.w
))
164 y
= int(height_half
+ height_half
* (prj
.y
/ prj
.w
))
166 # correction for corner cases in perspective mode
169 x
= context
.region
.width
* 2
171 x
= context
.region
.width
* -2
173 y
= context
.region
.height
* 2
175 y
= context
.region
.height
* -2
180 # calculate location of display_ob in worldspace
181 def get_location(frame
, display_ob
, offset_ob
, curves
):
183 bpy
.context
.scene
.frame_set(frame
)
184 display_mat
= getattr(display_ob
, "matrix", False)
186 # posebones have "matrix", objects have "matrix_world"
187 display_mat
= display_ob
.matrix_world
189 loc
= display_mat
.to_translation() + \
190 offset_ob
.matrix_world
.to_translation()
192 loc
= display_mat
.to_translation()
194 fcx
, fcy
, fcz
= curves
195 locx
= fcx
.evaluate(frame
)
196 locy
= fcy
.evaluate(frame
)
197 locz
= fcz
.evaluate(frame
)
198 loc
= mathutils
.Vector([locx
, locy
, locz
])
203 # get position of keyframes and handles at the start of dragging
204 def get_original_animation_data(context
, keyframes
):
208 if context
.active_object
and context
.active_object
.mode
== 'POSE':
209 armature_ob
= context
.active_object
210 objects
= [[armature_ob
, pb
, armature_ob
] for pb
in
211 context
.selected_pose_bones
]
213 objects
= [[ob
, False, False] for ob
in context
.selected_objects
]
215 for action_ob
, child
, offset_ob
in objects
:
216 if not action_ob
.animation_data
:
218 curves
= get_curves(action_ob
, child
)
221 fcx
, fcy
, fcz
= curves
225 display_ob
= action_ob
227 # get keyframe positions
228 frame_old
= context
.scene
.frame_current
229 keyframes_ori
[display_ob
.name
] = {}
230 for frame
in keyframes
[display_ob
.name
]:
231 loc
= get_location(frame
, display_ob
, offset_ob
, curves
)
232 keyframes_ori
[display_ob
.name
][frame
] = [frame
, loc
]
234 # get handle positions
235 handles_ori
[display_ob
.name
] = {}
236 for frame
in keyframes
[display_ob
.name
]:
237 handles_ori
[display_ob
.name
][frame
] = {}
238 left_x
= [frame
, fcx
.evaluate(frame
)]
239 right_x
= [frame
, fcx
.evaluate(frame
)]
240 for kf
in fcx
.keyframe_points
:
241 if kf
.co
[0] == frame
:
242 left_x
= kf
.handle_left
[:]
243 right_x
= kf
.handle_right
[:]
245 left_y
= [frame
, fcy
.evaluate(frame
)]
246 right_y
= [frame
, fcy
.evaluate(frame
)]
247 for kf
in fcy
.keyframe_points
:
248 if kf
.co
[0] == frame
:
249 left_y
= kf
.handle_left
[:]
250 right_y
= kf
.handle_right
[:]
252 left_z
= [frame
, fcz
.evaluate(frame
)]
253 right_z
= [frame
, fcz
.evaluate(frame
)]
254 for kf
in fcz
.keyframe_points
:
255 if kf
.co
[0] == frame
:
256 left_z
= kf
.handle_left
[:]
257 right_z
= kf
.handle_right
[:]
259 handles_ori
[display_ob
.name
][frame
]["left"] = [left_x
, left_y
,
261 handles_ori
[display_ob
.name
][frame
]["right"] = [right_x
, right_y
,
264 if context
.scene
.frame_current
!= frame_old
:
265 context
.scene
.frame_set(frame_old
)
267 return(keyframes_ori
, handles_ori
)
270 # callback function that calculates positions of all things that need be drawn
271 def calc_callback(self
, context
):
272 if context
.active_object
and context
.active_object
.mode
== 'POSE':
273 armature_ob
= context
.active_object
275 [armature_ob
, pb
, armature_ob
] for pb
in
276 context
.selected_pose_bones
279 objects
= [[ob
, False, False] for ob
in context
.selected_objects
]
280 if objects
== self
.displayed
:
281 selection_change
= False
283 selection_change
= True
285 if self
.lock
and not selection_change
and \
286 context
.region_data
.perspective_matrix
== self
.perspective
and not \
287 context
.window_manager
.motion_trail
.force_update
:
290 # dictionaries with key: objectname
291 self
.paths
= {} # value: list of lists with x, y, color
292 self
.keyframes
= {} # value: dict with frame as key and [x,y] as value
293 self
.handles
= {} # value: dict of dicts
294 self
.timebeads
= {} # value: dict with frame as key and [x,y] as value
295 self
.click
= {} # value: list of lists with frame, type, loc-vector
297 # value: editbone inverted rotation matrix or None
299 if selection_change
or not self
.lock
or context
.window_manager
.\
300 motion_trail
.force_update
:
301 # contains locations of path, keyframes and timebeads
303 "path": {}, "keyframes": {}, "timebeads_timing": {},
304 "timebeads_speed": {}
306 if self
.cached
["path"]:
310 self
.perspective
= context
.region_data
.perspective_matrix
.copy()
311 self
.displayed
= objects
# store, so it can be checked next time
312 context
.window_manager
.motion_trail
.force_update
= False
314 global_undo
= context
.preferences
.edit
.use_global_undo
315 context
.preferences
.edit
.use_global_undo
= False
317 for action_ob
, child
, offset_ob
in objects
:
320 self
.edit_bones
[action_ob
.name
] = None
322 bpy
.ops
.object.mode_set(mode
='EDIT')
323 editbones
= action_ob
.data
.edit_bones
324 mat
= editbones
[child
.name
].matrix
.copy().to_3x3().inverted()
325 bpy
.ops
.object.mode_set(mode
='POSE')
326 self
.edit_bones
[child
.name
] = mat
328 if not action_ob
.animation_data
:
330 curves
= get_curves(action_ob
, child
)
334 if context
.window_manager
.motion_trail
.path_before
== 0:
335 range_min
= context
.scene
.frame_start
338 context
.scene
.frame_start
,
339 context
.scene
.frame_current
-
340 context
.window_manager
.motion_trail
.path_before
342 if context
.window_manager
.motion_trail
.path_after
== 0:
343 range_max
= context
.scene
.frame_end
345 range_max
= min(context
.scene
.frame_end
,
346 context
.scene
.frame_current
+
347 context
.window_manager
.motion_trail
.path_after
349 fcx
, fcy
, fcz
= curves
353 display_ob
= action_ob
355 # get location data of motion path
358 frame_old
= context
.scene
.frame_current
359 step
= 11 - context
.window_manager
.motion_trail
.path_resolution
362 if display_ob
.name
not in self
.cached
["path"]:
363 self
.cached
["path"][display_ob
.name
] = {}
364 if use_cache
and range_min
- 1 in self
.cached
["path"][display_ob
.name
]:
365 prev_loc
= self
.cached
["path"][display_ob
.name
][range_min
- 1]
367 prev_loc
= get_location(range_min
- 1, display_ob
, offset_ob
, curves
)
368 self
.cached
["path"][display_ob
.name
][range_min
- 1] = prev_loc
370 for frame
in range(range_min
, range_max
+ 1, step
):
371 if use_cache
and frame
in self
.cached
["path"][display_ob
.name
]:
372 loc
= self
.cached
["path"][display_ob
.name
][frame
]
374 loc
= get_location(frame
, display_ob
, offset_ob
, curves
)
375 self
.cached
["path"][display_ob
.name
][frame
] = loc
376 if not context
.region
or not context
.space_data
:
378 x
, y
= world_to_screen(context
, loc
)
379 if context
.window_manager
.motion_trail
.path_style
== 'simple':
380 path
.append([x
, y
, [0.0, 0.0, 0.0], frame
, action_ob
, child
])
382 dloc
= (loc
- prev_loc
).length
383 path
.append([x
, y
, dloc
, frame
, action_ob
, child
])
387 # calculate color of path
388 if context
.window_manager
.motion_trail
.path_style
== 'speed':
390 min_speed
= speeds
[0]
391 d_speed
= speeds
[-1] - min_speed
392 for i
, [x
, y
, d_loc
, frame
, action_ob
, child
] in enumerate(path
):
393 relative_speed
= (d_loc
- min_speed
) / d_speed
# 0.0 to 1.0
394 red
= min(1.0, 2.0 * relative_speed
)
395 blue
= min(1.0, 2.0 - (2.0 * relative_speed
))
396 path
[i
][2] = [red
, 0.0, blue
]
397 elif context
.window_manager
.motion_trail
.path_style
== 'acceleration':
400 for i
, [x
, y
, d_loc
, frame
, action_ob
, child
] in enumerate(path
):
401 accel
= d_loc
- prev_speed
402 accelerations
.append(accel
)
406 min_accel
= accelerations
[0]
407 max_accel
= accelerations
[-1]
408 for i
, [x
, y
, accel
, frame
, action_ob
, child
] in enumerate(path
):
410 relative_accel
= accel
/ min_accel
# values from 0.0 to 1.0
411 green
= 1.0 - relative_accel
412 path
[i
][2] = [1.0, green
, 0.0]
414 relative_accel
= accel
/ max_accel
# values from 0.0 to 1.0
415 red
= 1.0 - relative_accel
416 path
[i
][2] = [red
, 1.0, 0.0]
418 path
[i
][2] = [1.0, 1.0, 0.0]
419 self
.paths
[display_ob
.name
] = path
421 # get keyframes and handles
427 if display_ob
.name
not in self
.cached
["keyframes"]:
428 self
.cached
["keyframes"][display_ob
.name
] = {}
431 for kf
in fc
.keyframe_points
:
432 # handles for location mode
433 if context
.window_manager
.motion_trail
.mode
== 'location':
434 if kf
.co
[0] not in handle_difs
:
435 handle_difs
[kf
.co
[0]] = {"left": mathutils
.Vector(),
436 "right": mathutils
.Vector(), "keyframe_loc": None}
437 handle_difs
[kf
.co
[0]]["left"][fc
.array_index
] = \
438 (mathutils
.Vector(kf
.handle_left
[:]) -
439 mathutils
.Vector(kf
.co
[:])).normalized()[1]
440 handle_difs
[kf
.co
[0]]["right"][fc
.array_index
] = \
441 (mathutils
.Vector(kf
.handle_right
[:]) -
442 mathutils
.Vector(kf
.co
[:])).normalized()[1]
444 if kf
.co
[0] in kf_time
:
446 kf_time
.append(kf
.co
[0])
449 if use_cache
and co
in \
450 self
.cached
["keyframes"][display_ob
.name
]:
451 loc
= self
.cached
["keyframes"][display_ob
.name
][co
]
453 loc
= get_location(co
, display_ob
, offset_ob
, curves
)
454 self
.cached
["keyframes"][display_ob
.name
][co
] = loc
456 handle_difs
[co
]["keyframe_loc"] = loc
458 x
, y
= world_to_screen(context
, loc
)
459 keyframes
[kf
.co
[0]] = [x
, y
]
460 if context
.window_manager
.motion_trail
.mode
!= 'speed':
461 # can't select keyframes in speed mode
462 click
.append([kf
.co
[0], "keyframe",
463 mathutils
.Vector([x
, y
]), action_ob
, child
])
464 self
.keyframes
[display_ob
.name
] = keyframes
466 # handles are only shown in location-altering mode
467 if context
.window_manager
.motion_trail
.mode
== 'location' and \
468 context
.window_manager
.motion_trail
.handle_display
:
469 # calculate handle positions
471 for frame
, vecs
in handle_difs
.items():
473 # bone space to world space
474 mat
= self
.edit_bones
[child
.name
].copy().inverted()
475 vec_left
= vecs
["left"] * mat
476 vec_right
= vecs
["right"] * mat
478 vec_left
= vecs
["left"]
479 vec_right
= vecs
["right"]
480 if vecs
["keyframe_loc"] is not None:
481 vec_keyframe
= vecs
["keyframe_loc"]
483 vec_keyframe
= get_location(frame
, display_ob
, offset_ob
,
485 x_left
, y_left
= world_to_screen(
486 context
, vec_left
* 2 + vec_keyframe
488 x_right
, y_right
= world_to_screen(
489 context
, vec_right
* 2 + vec_keyframe
491 handles
[frame
] = {"left": [x_left
, y_left
],
492 "right": [x_right
, y_right
]}
493 click
.append([frame
, "handle_left",
494 mathutils
.Vector([x_left
, y_left
]), action_ob
, child
])
495 click
.append([frame
, "handle_right",
496 mathutils
.Vector([x_right
, y_right
]), action_ob
, child
])
497 self
.handles
[display_ob
.name
] = handles
499 # calculate timebeads for timing mode
500 if context
.window_manager
.motion_trail
.mode
== 'timing':
502 n
= context
.window_manager
.motion_trail
.timebeads
* (len(kf_time
) - 1)
503 dframe
= (range_max
- range_min
) / (n
+ 1)
505 if display_ob
.name
not in self
.cached
["timebeads_timing"]:
506 self
.cached
["timebeads_timing"][display_ob
.name
] = {}
508 for i
in range(1, n
+ 1):
509 frame
= range_min
+ i
* dframe
510 if use_cache
and frame
in \
511 self
.cached
["timebeads_timing"][display_ob
.name
]:
512 loc
= self
.cached
["timebeads_timing"][display_ob
.name
][frame
]
514 loc
= get_location(frame
, display_ob
, offset_ob
, curves
)
515 self
.cached
["timebeads_timing"][display_ob
.name
][frame
] = loc
516 x
, y
= world_to_screen(context
, loc
)
517 timebeads
[frame
] = [x
, y
]
519 [frame
, "timebead", mathutils
.Vector([x
, y
]),
522 self
.timebeads
[display_ob
.name
] = timebeads
524 # calculate timebeads for speed mode
525 if context
.window_manager
.motion_trail
.mode
== 'speed':
526 angles
= dict([[kf
, {"left": [], "right": []}] for kf
in
527 self
.keyframes
[display_ob
.name
]])
529 for i
, kf
in enumerate(fc
.keyframe_points
):
531 angle
= mathutils
.Vector([-1, 0]).angle(
532 mathutils
.Vector(kf
.handle_left
) -
533 mathutils
.Vector(kf
.co
), 0
536 angles
[kf
.co
[0]]["left"].append(angle
)
537 if i
!= len(fc
.keyframe_points
) - 1:
538 angle
= mathutils
.Vector([1, 0]).angle(
539 mathutils
.Vector(kf
.handle_right
) -
540 mathutils
.Vector(kf
.co
), 0
543 angles
[kf
.co
[0]]["right"].append(angle
)
547 if display_ob
.name
not in self
.cached
["timebeads_speed"]:
548 self
.cached
["timebeads_speed"][display_ob
.name
] = {}
550 for frame
, sides
in angles
.items():
552 perc
= (sum(sides
["left"]) / len(sides
["left"])) / \
554 perc
= max(0.4, min(1, perc
* 5))
555 previous
= kf_time
[kf_time
.index(frame
) - 1]
556 bead_frame
= frame
- perc
* ((frame
- previous
- 2) / 2)
557 if use_cache
and bead_frame
in \
558 self
.cached
["timebeads_speed"][display_ob
.name
]:
559 loc
= self
.cached
["timebeads_speed"][display_ob
.name
][bead_frame
]
561 loc
= get_location(bead_frame
, display_ob
, offset_ob
,
563 self
.cached
["timebeads_speed"][display_ob
.name
][bead_frame
] = loc
564 x
, y
= world_to_screen(context
, loc
)
565 timebeads
[bead_frame
] = [x
, y
]
567 [bead_frame
, "timebead",
568 mathutils
.Vector([x
, y
]),
572 perc
= (sum(sides
["right"]) / len(sides
["right"])) / \
574 perc
= max(0.4, min(1, perc
* 5))
575 next
= kf_time
[kf_time
.index(frame
) + 1]
576 bead_frame
= frame
+ perc
* ((next
- frame
- 2) / 2)
577 if use_cache
and bead_frame
in \
578 self
.cached
["timebeads_speed"][display_ob
.name
]:
579 loc
= self
.cached
["timebeads_speed"][display_ob
.name
][bead_frame
]
581 loc
= get_location(bead_frame
, display_ob
, offset_ob
,
583 self
.cached
["timebeads_speed"][display_ob
.name
][bead_frame
] = loc
584 x
, y
= world_to_screen(context
, loc
)
585 timebeads
[bead_frame
] = [x
, y
]
587 [bead_frame
, "timebead",
588 mathutils
.Vector([x
, y
]),
591 self
.timebeads
[display_ob
.name
] = timebeads
593 # add frame positions to click-list
594 if context
.window_manager
.motion_trail
.frame_display
:
595 path
= self
.paths
[display_ob
.name
]
596 for x
, y
, color
, frame
, action_ob
, child
in path
:
599 mathutils
.Vector([x
, y
]),
603 self
.click
[display_ob
.name
] = click
605 if context
.scene
.frame_current
!= frame_old
:
606 context
.scene
.frame_set(frame_old
)
608 context
.preferences
.edit
.use_global_undo
= global_undo
611 # restore global undo in case of failure (see T52524)
612 context
.preferences
.edit
.use_global_undo
= global_undo
616 def draw_callback(self
, context
):
618 if (context
.mode
not in ('OBJECT', 'POSE') or
619 not context
.window_manager
.motion_trail
.enabled
):
623 if context
.window_manager
.motion_trail
.path_before
!= 0:
624 limit_min
= context
.scene
.frame_current
- \
625 context
.window_manager
.motion_trail
.path_before
628 if context
.window_manager
.motion_trail
.path_after
!= 0:
629 limit_max
= context
.scene
.frame_current
+ \
630 context
.window_manager
.motion_trail
.path_after
635 bgl
.glEnable(bgl
.GL_BLEND
)
636 bgl
.glLineWidth(context
.window_manager
.motion_trail
.path_width
)
637 alpha
= 1.0 - (context
.window_manager
.motion_trail
.path_transparency
/ 100.0)
639 if context
.window_manager
.motion_trail
.path_style
== 'simple':
640 bgl
.glColor4f(0.0, 0.0, 0.0, alpha
)
641 for objectname
, path
in self
.paths
.items():
642 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
643 for x
, y
, color
, frame
, action_ob
, child
in path
:
644 if frame
< limit_min
or frame
> limit_max
:
649 for objectname
, path
in self
.paths
.items():
650 for i
, [x
, y
, color
, frame
, action_ob
, child
] in enumerate(path
):
651 if frame
< limit_min
or frame
> limit_max
:
655 prev_path
= path
[i
- 1]
656 halfway
= [(x
+ prev_path
[0]) / 2, (y
+ prev_path
[1]) / 2]
657 bgl
.glColor4f(r
, g
, b
, alpha
)
658 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
659 bgl
.glVertex2i(int(halfway
[0]), int(halfway
[1]))
662 if i
!= len(path
) - 1:
663 next_path
= path
[i
+ 1]
664 halfway
= [(x
+ next_path
[0]) / 2, (y
+ next_path
[1]) / 2]
665 bgl
.glColor4f(r
, g
, b
, alpha
)
666 bgl
.glBegin(bgl
.GL_LINE_STRIP
)
668 bgl
.glVertex2i(int(halfway
[0]), int(halfway
[1]))
672 if context
.window_manager
.motion_trail
.frame_display
:
673 bgl
.glColor4f(1.0, 1.0, 1.0, 1.0)
675 bgl
.glBegin(bgl
.GL_POINTS
)
676 for objectname
, path
in self
.paths
.items():
677 for x
, y
, color
, frame
, action_ob
, child
in path
:
678 if frame
< limit_min
or frame
> limit_max
:
680 if self
.active_frame
and objectname
== self
.active_frame
[0] \
681 and abs(frame
- self
.active_frame
[1]) < 1e-4:
683 bgl
.glColor4f(1.0, 0.5, 0.0, 1.0)
685 bgl
.glBegin(bgl
.GL_POINTS
)
688 bgl
.glColor4f(1.0, 1.0, 1.0, 1.0)
690 bgl
.glBegin(bgl
.GL_POINTS
)
695 # time beads are shown in speed and timing modes
696 if context
.window_manager
.motion_trail
.mode
in ('speed', 'timing'):
697 bgl
.glColor4f(0.0, 1.0, 0.0, 1.0)
699 bgl
.glBegin(bgl
.GL_POINTS
)
700 for objectname
, values
in self
.timebeads
.items():
701 for frame
, coords
in values
.items():
702 if frame
< limit_min
or frame
> limit_max
:
704 if self
.active_timebead
and \
705 objectname
== self
.active_timebead
[0] and \
706 abs(frame
- self
.active_timebead
[1]) < 1e-4:
708 bgl
.glColor4f(1.0, 0.5, 0.0, 1.0)
709 bgl
.glBegin(bgl
.GL_POINTS
)
710 bgl
.glVertex2i(coords
[0], coords
[1])
712 bgl
.glColor4f(0.0, 1.0, 0.0, 1.0)
713 bgl
.glBegin(bgl
.GL_POINTS
)
715 bgl
.glVertex2i(coords
[0], coords
[1])
718 # handles are only shown in location mode
719 if context
.window_manager
.motion_trail
.mode
== 'location':
721 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
723 bgl
.glBegin(bgl
.GL_LINES
)
724 for objectname
, values
in self
.handles
.items():
725 for frame
, sides
in values
.items():
726 if frame
< limit_min
or frame
> limit_max
:
728 for side
, coords
in sides
.items():
729 if self
.active_handle
and \
730 objectname
== self
.active_handle
[0] and \
731 side
== self
.active_handle
[2] and \
732 abs(frame
- self
.active_handle
[1]) < 1e-4:
734 bgl
.glColor4f(.75, 0.25, 0.0, 1.0)
735 bgl
.glBegin(bgl
.GL_LINES
)
736 bgl
.glVertex2i(self
.keyframes
[objectname
][frame
][0],
737 self
.keyframes
[objectname
][frame
][1])
738 bgl
.glVertex2i(coords
[0], coords
[1])
740 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
741 bgl
.glBegin(bgl
.GL_LINES
)
743 bgl
.glVertex2i(self
.keyframes
[objectname
][frame
][0],
744 self
.keyframes
[objectname
][frame
][1])
745 bgl
.glVertex2i(coords
[0], coords
[1])
749 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
751 bgl
.glBegin(bgl
.GL_POINTS
)
752 for objectname
, values
in self
.handles
.items():
753 for frame
, sides
in values
.items():
754 if frame
< limit_min
or frame
> limit_max
:
756 for side
, coords
in sides
.items():
757 if self
.active_handle
and \
758 objectname
== self
.active_handle
[0] and \
759 side
== self
.active_handle
[2] and \
760 abs(frame
- self
.active_handle
[1]) < 1e-4:
762 bgl
.glColor4f(1.0, 0.5, 0.0, 1.0)
763 bgl
.glBegin(bgl
.GL_POINTS
)
764 bgl
.glVertex2i(coords
[0], coords
[1])
766 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
767 bgl
.glBegin(bgl
.GL_POINTS
)
769 bgl
.glVertex2i(coords
[0], coords
[1])
773 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
775 bgl
.glBegin(bgl
.GL_POINTS
)
776 for objectname
, values
in self
.keyframes
.items():
777 for frame
, coords
in values
.items():
778 if frame
< limit_min
or frame
> limit_max
:
780 if self
.active_keyframe
and \
781 objectname
== self
.active_keyframe
[0] and \
782 abs(frame
- self
.active_keyframe
[1]) < 1e-4:
784 bgl
.glColor4f(1.0, 0.5, 0.0, 1.0)
785 bgl
.glBegin(bgl
.GL_POINTS
)
786 bgl
.glVertex2i(coords
[0], coords
[1])
788 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
789 bgl
.glBegin(bgl
.GL_POINTS
)
791 bgl
.glVertex2i(coords
[0], coords
[1])
794 # draw keyframe-numbers
795 if context
.window_manager
.motion_trail
.keyframe_numbers
:
797 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
798 for objectname
, values
in self
.keyframes
.items():
799 for frame
, coords
in values
.items():
800 if frame
< limit_min
or frame
> limit_max
:
802 blf
.position(0, coords
[0] + 3, coords
[1] + 3, 0)
803 text
= str(frame
).split(".")
806 elif len(text
[1]) == 1 and text
[1] == "0":
809 text
= text
[0] + "." + text
[1][0]
810 if self
.active_keyframe
and \
811 objectname
== self
.active_keyframe
[0] and \
812 abs(frame
- self
.active_keyframe
[1]) < 1e-4:
813 bgl
.glColor4f(1.0, 0.5, 0.0, 1.0)
815 bgl
.glColor4f(1.0, 1.0, 0.0, 1.0)
819 # restore opengl defaults
821 bgl
.glDisable(bgl
.GL_BLEND
)
822 bgl
.glColor4f(0.0, 0.0, 0.0, 1.0)
826 # change data based on mouse movement
827 def drag(context
, event
, drag_mouse_ori
, active_keyframe
, active_handle
,
828 active_timebead
, keyframes_ori
, handles_ori
, edit_bones
):
829 # change 3d-location of keyframe
830 if context
.window_manager
.motion_trail
.mode
== 'location' and \
832 objectname
, frame
, frame_ori
, action_ob
, child
= active_keyframe
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
845 loc_ori_ws
= keyframes_ori
[objectname
][frame
][1]
846 loc_ori_bs
= loc_ori_ws
* mat
847 new_loc
= loc_ori_bs
+ d
848 curves
= get_curves(action_ob
, child
)
850 for i
, curve
in enumerate(curves
):
851 for kf
in curve
.keyframe_points
:
852 if kf
.co
[0] == frame
:
853 kf
.co
[1] = new_loc
[i
]
854 kf
.handle_left
[1] = handles_ori
[objectname
][frame
]["left"][i
][1] + d
[i
]
855 kf
.handle_right
[1] = handles_ori
[objectname
][frame
]["right"][i
][1] + d
[i
]
858 # change 3d-location of handle
859 elif context
.window_manager
.motion_trail
.mode
== 'location' and active_handle
:
860 objectname
, frame
, side
, action_ob
, child
= active_handle
862 mat
= action_ob
.matrix_world
.copy().inverted() * \
863 edit_bones
[child
.name
].copy().to_4x4()
867 mouse_ori_world
= screen_to_world(context
, drag_mouse_ori
[0],
868 drag_mouse_ori
[1]) * mat
869 vector
= screen_to_world(context
, event
.mouse_region_x
,
870 event
.mouse_region_y
) * mat
871 d
= vector
- mouse_ori_world
872 curves
= get_curves(action_ob
, child
)
874 for i
, curve
in enumerate(curves
):
875 for kf
in curve
.keyframe_points
:
876 if kf
.co
[0] == frame
:
878 # change handle type, if necessary
879 if kf
.handle_left_type
in (
883 kf
.handle_left_type
= 'ALIGNED'
884 elif kf
.handle_left_type
== 'VECTOR':
885 kf
.handle_left_type
= 'FREE'
886 # change handle position(s)
887 kf
.handle_left
[1] = handles_ori
[objectname
][frame
]["left"][i
][1] + d
[i
]
888 if kf
.handle_left_type
in (
894 abs(handles_ori
[objectname
][frame
]["right"][i
][0] -
895 kf
.co
[0]) / abs(kf
.handle_left
[0] -
898 kf
.handle_right
[1] = handles_ori
[objectname
][frame
]["right"][i
][1] - dif
899 elif side
== "right":
900 # change handle type, if necessary
901 if kf
.handle_right_type
in (
905 kf
.handle_left_type
= 'ALIGNED'
906 kf
.handle_right_type
= 'ALIGNED'
907 elif kf
.handle_right_type
== 'VECTOR':
908 kf
.handle_left_type
= 'FREE'
909 kf
.handle_right_type
= 'FREE'
910 # change handle position(s)
911 kf
.handle_right
[1] = handles_ori
[objectname
][frame
]["right"][i
][1] + d
[i
]
912 if kf
.handle_right_type
in (
918 abs(handles_ori
[objectname
][frame
]["left"][i
][0] -
919 kf
.co
[0]) / abs(kf
.handle_right
[0] -
922 kf
.handle_left
[1] = handles_ori
[objectname
][frame
]["left"][i
][1] - dif
925 # change position of all keyframes on timeline
926 elif context
.window_manager
.motion_trail
.mode
== 'timing' and \
928 objectname
, frame
, frame_ori
, action_ob
, child
= active_timebead
929 curves
= get_curves(action_ob
, child
)
930 ranges
= [val
for c
in curves
for val
in c
.range()]
932 range_min
= round(ranges
[0])
933 range_max
= round(ranges
[-1])
934 range = range_max
- range_min
935 dx_screen
= -(mathutils
.Vector([event
.mouse_region_x
,
936 event
.mouse_region_y
]) - drag_mouse_ori
)[0]
937 dx_screen
= dx_screen
/ context
.region
.width
* range
938 new_frame
= frame
+ dx_screen
939 shift_low
= max(1e-4, (new_frame
- range_min
) / (frame
- range_min
))
940 shift_high
= max(1e-4, (range_max
- new_frame
) / (range_max
- frame
))
943 for i
, curve
in enumerate(curves
):
944 for j
, kf
in enumerate(curve
.keyframe_points
):
946 if frame_map
< range_min
+ 1e-4 or \
947 frame_map
> range_max
- 1e-4:
950 for f
in keyframes_ori
[objectname
]:
951 if abs(f
- frame_map
) < 1e-4:
952 frame_ori
= keyframes_ori
[objectname
][f
][0]
953 value_ori
= keyframes_ori
[objectname
][f
]
957 if frame_ori
<= frame
:
958 frame_new
= (frame_ori
- range_min
) * shift_low
+ \
961 frame_new
= range_max
- (range_max
- frame_ori
) * \
964 range_min
+ j
, min(frame_new
, range_max
-
965 (len(curve
.keyframe_points
) - j
) + 1)
967 d_frame
= frame_new
- frame_ori
968 if frame_new
not in new_mapping
:
969 new_mapping
[frame_new
] = value_ori
971 kf
.handle_left
[0] = handles_ori
[objectname
][frame_ori
]["left"][i
][0] + d_frame
972 kf
.handle_right
[0] = handles_ori
[objectname
][frame_ori
]["right"][i
][0] + d_frame
973 del keyframes_ori
[objectname
]
974 keyframes_ori
[objectname
] = {}
975 for new_frame
, value
in new_mapping
.items():
976 keyframes_ori
[objectname
][new_frame
] = value
978 # change position of active keyframe on the timeline
979 elif context
.window_manager
.motion_trail
.mode
== 'timing' and \
981 objectname
, frame
, frame_ori
, action_ob
, child
= active_keyframe
983 mat
= action_ob
.matrix_world
.copy().inverted() * \
984 edit_bones
[child
.name
].copy().to_4x4()
986 mat
= action_ob
.matrix_world
.copy().inverted()
988 mouse_ori_world
= screen_to_world(context
, drag_mouse_ori
[0],
989 drag_mouse_ori
[1]) * mat
990 vector
= screen_to_world(context
, event
.mouse_region_x
,
991 event
.mouse_region_y
) * mat
992 d
= vector
- mouse_ori_world
994 locs_ori
= [[f_ori
, coords
] for f_mapped
, [f_ori
, coords
] in
995 keyframes_ori
[objectname
].items()]
999 for i
, [f_ori
, coords
] in enumerate(locs_ori
):
1000 if abs(frame_ori
- f_ori
) < 1e-4:
1002 # first keyframe, nothing before it
1004 range = [f_ori
, locs_ori
[i
+ 1][0]]
1005 elif i
== len(locs_ori
) - 1:
1006 # last keyframe, nothing after it
1007 range = [locs_ori
[i
- 1][0], f_ori
]
1009 current
= mathutils
.Vector(coords
)
1010 next
= mathutils
.Vector(locs_ori
[i
+ 1][1])
1011 previous
= mathutils
.Vector(locs_ori
[i
- 1][1])
1012 angle_to_next
= d
.angle(next
- current
, 0)
1013 angle_to_previous
= d
.angle(previous
- current
, 0)
1014 if angle_to_previous
< angle_to_next
:
1015 # mouse movement is in direction of previous keyframe
1017 range = [locs_ori
[i
- 1][0], locs_ori
[i
+ 1][0]]
1019 direction
*= -1 # feels more natural in 3d-view
1021 # keyframe not found, is impossible, but better safe than sorry
1022 return(active_keyframe
, active_timebead
, keyframes_ori
)
1023 # calculate strength of movement
1024 d_screen
= mathutils
.Vector([event
.mouse_region_x
,
1025 event
.mouse_region_y
]) - drag_mouse_ori
1026 if d_screen
.length
!= 0:
1027 d_screen
= d_screen
.length
/ (abs(d_screen
[0]) / d_screen
.length
*
1028 context
.region
.width
+ abs(d_screen
[1]) / d_screen
.length
*
1029 context
.region
.height
)
1030 d_screen
*= direction
# d_screen value ranges from -1.0 to 1.0
1033 new_frame
= d_screen
* (range[1] - range[0]) + frame_ori
1034 max_frame
= range[1]
1035 if max_frame
== frame_ori
:
1037 min_frame
= range[0]
1038 if min_frame
== frame_ori
:
1040 new_frame
= min(max_frame
- 1, max(min_frame
+ 1, new_frame
))
1041 d_frame
= new_frame
- frame_ori
1042 curves
= get_curves(action_ob
, child
)
1044 for i
, curve
in enumerate(curves
):
1045 for kf
in curve
.keyframe_points
:
1046 if abs(kf
.co
[0] - frame
) < 1e-4:
1047 kf
.co
[0] = new_frame
1048 kf
.handle_left
[0] = handles_ori
[objectname
][frame_ori
]["left"][i
][0] + d_frame
1049 kf
.handle_right
[0] = handles_ori
[objectname
][frame_ori
]["right"][i
][0] + d_frame
1051 active_keyframe
= [objectname
, new_frame
, frame_ori
, action_ob
, child
]
1053 # change position of active timebead on the timeline, thus altering speed
1054 elif context
.window_manager
.motion_trail
.mode
== 'speed' and \
1056 objectname
, frame
, frame_ori
, action_ob
, child
= active_timebead
1058 mat
= action_ob
.matrix_world
.copy().inverted() * \
1059 edit_bones
[child
.name
].copy().to_4x4()
1063 mouse_ori_world
= screen_to_world(context
, drag_mouse_ori
[0],
1064 drag_mouse_ori
[1]) * mat
1065 vector
= screen_to_world(context
, event
.mouse_region_x
,
1066 event
.mouse_region_y
) * mat
1067 d
= vector
- mouse_ori_world
1069 # determine direction (to next or previous keyframe)
1070 curves
= get_curves(action_ob
, child
)
1071 fcx
, fcy
, fcz
= curves
1072 locx
= fcx
.evaluate(frame_ori
)
1073 locy
= fcy
.evaluate(frame_ori
)
1074 locz
= fcz
.evaluate(frame_ori
)
1075 loc_ori
= mathutils
.Vector([locx
, locy
, locz
]) # bonespace
1076 keyframes
= [kf
for kf
in keyframes_ori
[objectname
]]
1077 keyframes
.append(frame_ori
)
1079 frame_index
= keyframes
.index(frame_ori
)
1080 kf_prev
= keyframes
[frame_index
- 1]
1081 kf_next
= keyframes
[frame_index
+ 1]
1083 mathutils
.Vector(keyframes_ori
[objectname
][kf_prev
][1]) *
1086 vec_next
= (mathutils
.Vector(keyframes_ori
[objectname
][kf_next
][1]) *
1089 d_normal
= d
.copy().normalized()
1090 dist_to_next
= (d_normal
- vec_next
).length
1091 dist_to_prev
= (d_normal
- vec_prev
).length
1092 if dist_to_prev
< dist_to_next
:
1097 if (kf_next
- frame_ori
) < (frame_ori
- kf_prev
):
1103 d_frame
= d
.length
* direction
* 2 # * 2 to make it more sensitive
1106 for i
, curve
in enumerate(curves
):
1107 for kf
in curve
.keyframe_points
:
1108 if abs(kf
.co
[0] - kf_bead
) < 1e-4:
1111 kf
.handle_left
[0] = min(
1112 handles_ori
[objectname
][kf_bead
]["left"][i
][0] +
1113 d_frame
, kf_bead
- 1
1115 angle
= mathutils
.Vector([-1, 0]).angle(
1116 mathutils
.Vector(kf
.handle_left
) -
1117 mathutils
.Vector(kf
.co
), 0
1120 angles
.append(angle
)
1123 kf
.handle_right
[0] = max(
1124 handles_ori
[objectname
][kf_bead
]["right"][i
][0] +
1125 d_frame
, kf_bead
+ 1
1127 angle
= mathutils
.Vector([1, 0]).angle(
1128 mathutils
.Vector(kf
.handle_right
) -
1129 mathutils
.Vector(kf
.co
), 0
1132 angles
.append(angle
)
1135 # update frame of active_timebead
1136 perc
= (sum(angles
) / len(angles
)) / (math
.pi
/ 2)
1137 perc
= max(0.4, min(1, perc
* 5))
1139 bead_frame
= kf_bead
- perc
* ((kf_bead
- kf_prev
- 2) / 2)
1141 bead_frame
= kf_bead
+ perc
* ((kf_next
- kf_bead
- 2) / 2)
1142 active_timebead
= [objectname
, bead_frame
, frame_ori
, action_ob
, child
]
1144 return(active_keyframe
, active_timebead
, keyframes_ori
)
1147 # revert changes made by dragging
1148 def cancel_drag(context
, active_keyframe
, active_handle
, active_timebead
,
1149 keyframes_ori
, handles_ori
, edit_bones
):
1150 # revert change in 3d-location of active keyframe and its handles
1151 if context
.window_manager
.motion_trail
.mode
== 'location' and \
1153 objectname
, frame
, frame_ori
, active_ob
, child
= active_keyframe
1154 curves
= get_curves(active_ob
, child
)
1155 loc_ori
= keyframes_ori
[objectname
][frame
][1]
1157 loc_ori
= loc_ori
* edit_bones
[child
.name
] * \
1158 active_ob
.matrix_world
.copy().inverted()
1159 for i
, curve
in enumerate(curves
):
1160 for kf
in curve
.keyframe_points
:
1161 if kf
.co
[0] == frame
:
1162 kf
.co
[1] = loc_ori
[i
]
1163 kf
.handle_left
[1] = handles_ori
[objectname
][frame
]["left"][i
][1]
1164 kf
.handle_right
[1] = handles_ori
[objectname
][frame
]["right"][i
][1]
1167 # revert change in 3d-location of active handle
1168 elif context
.window_manager
.motion_trail
.mode
== 'location' and \
1170 objectname
, frame
, side
, active_ob
, child
= active_handle
1171 curves
= get_curves(active_ob
, child
)
1172 for i
, curve
in enumerate(curves
):
1173 for kf
in curve
.keyframe_points
:
1174 if kf
.co
[0] == frame
:
1175 kf
.handle_left
[1] = handles_ori
[objectname
][frame
]["left"][i
][1]
1176 kf
.handle_right
[1] = handles_ori
[objectname
][frame
]["right"][i
][1]
1179 # revert position of all keyframes and handles on timeline
1180 elif context
.window_manager
.motion_trail
.mode
== 'timing' and \
1182 objectname
, frame
, frame_ori
, active_ob
, child
= active_timebead
1183 curves
= get_curves(active_ob
, child
)
1184 for i
, curve
in enumerate(curves
):
1185 for kf
in curve
.keyframe_points
:
1186 for kf_ori
, [frame_ori
, loc
] in keyframes_ori
[objectname
].\
1188 if abs(kf
.co
[0] - kf_ori
) < 1e-4:
1189 kf
.co
[0] = frame_ori
1190 kf
.handle_left
[0] = handles_ori
[objectname
][frame_ori
]["left"][i
][0]
1191 kf
.handle_right
[0] = handles_ori
[objectname
][frame_ori
]["right"][i
][0]
1194 # revert position of active keyframe and its handles on the timeline
1195 elif context
.window_manager
.motion_trail
.mode
== 'timing' and \
1197 objectname
, frame
, frame_ori
, active_ob
, child
= active_keyframe
1198 curves
= get_curves(active_ob
, child
)
1199 for i
, curve
in enumerate(curves
):
1200 for kf
in curve
.keyframe_points
:
1201 if abs(kf
.co
[0] - frame
) < 1e-4:
1202 kf
.co
[0] = keyframes_ori
[objectname
][frame_ori
][0]
1203 kf
.handle_left
[0] = handles_ori
[objectname
][frame_ori
]["left"][i
][0]
1204 kf
.handle_right
[0] = handles_ori
[objectname
][frame_ori
]["right"][i
][0]
1206 active_keyframe
= [objectname
, frame_ori
, frame_ori
, active_ob
, child
]
1208 # revert position of handles on the timeline
1209 elif context
.window_manager
.motion_trail
.mode
== 'speed' and \
1211 objectname
, frame
, frame_ori
, active_ob
, child
= active_timebead
1212 curves
= get_curves(active_ob
, child
)
1213 keyframes
= [kf
for kf
in keyframes_ori
[objectname
]]
1214 keyframes
.append(frame_ori
)
1216 frame_index
= keyframes
.index(frame_ori
)
1217 kf_prev
= keyframes
[frame_index
- 1]
1218 kf_next
= keyframes
[frame_index
+ 1]
1219 if (kf_next
- frame_ori
) < (frame_ori
- kf_prev
):
1223 for i
, curve
in enumerate(curves
):
1224 for kf
in curve
.keyframe_points
:
1225 if kf
.co
[0] == kf_frame
:
1226 kf
.handle_left
[0] = handles_ori
[objectname
][kf_frame
]["left"][i
][0]
1227 kf
.handle_right
[0] = handles_ori
[objectname
][kf_frame
]["right"][i
][0]
1229 active_timebead
= [objectname
, frame_ori
, frame_ori
, active_ob
, child
]
1231 return(active_keyframe
, active_timebead
)
1234 # return the handle type of the active selection
1235 def get_handle_type(active_keyframe
, active_handle
):
1237 objectname
, frame
, side
, action_ob
, child
= active_keyframe
1240 objectname
, frame
, side
, action_ob
, child
= active_handle
1242 # no active handle(s)
1245 # properties used when changing handle type
1246 bpy
.context
.window_manager
.motion_trail
.handle_type_frame
= frame
1247 bpy
.context
.window_manager
.motion_trail
.handle_type_side
= side
1248 bpy
.context
.window_manager
.motion_trail
.handle_type_action_ob
= \
1251 bpy
.context
.window_manager
.motion_trail
.handle_type_child
= child
.name
1253 bpy
.context
.window_manager
.motion_trail
.handle_type_child
= ""
1255 curves
= get_curves(action_ob
, child
=child
)
1257 for kf
in c
.keyframe_points
:
1258 if kf
.co
[0] == frame
:
1259 if side
in ("left", "both"):
1260 return(kf
.handle_left_type
)
1262 return(kf
.handle_right_type
)
1267 # turn the given frame into a keyframe
1268 def insert_keyframe(self
, context
, frame
):
1269 objectname
, frame
, frame
, action_ob
, child
= frame
1270 curves
= get_curves(action_ob
, child
)
1272 y
= c
.evaluate(frame
)
1273 if c
.keyframe_points
:
1274 c
.keyframe_points
.insert(frame
, y
)
1276 bpy
.context
.window_manager
.motion_trail
.force_update
= True
1277 calc_callback(self
, context
)
1280 # change the handle type of the active selection
1281 def set_handle_type(self
, context
):
1282 if not context
.window_manager
.motion_trail
.handle_type_enabled
:
1284 if context
.window_manager
.motion_trail
.handle_type_old
== \
1285 context
.window_manager
.motion_trail
.handle_type
:
1286 # function called because of selection change, not change in type
1288 context
.window_manager
.motion_trail
.handle_type_old
= \
1289 context
.window_manager
.motion_trail
.handle_type
1291 frame
= bpy
.context
.window_manager
.motion_trail
.handle_type_frame
1292 side
= bpy
.context
.window_manager
.motion_trail
.handle_type_side
1293 action_ob
= bpy
.context
.window_manager
.motion_trail
.handle_type_action_ob
1294 action_ob
= bpy
.data
.objects
[action_ob
]
1295 child
= bpy
.context
.window_manager
.motion_trail
.handle_type_child
1297 child
= action_ob
.pose
.bones
[child
]
1298 new_type
= context
.window_manager
.motion_trail
.handle_type
1300 curves
= get_curves(action_ob
, child
=child
)
1302 for kf
in c
.keyframe_points
:
1303 if kf
.co
[0] == frame
:
1304 # align if necessary
1305 if side
in ("right", "both") and new_type
in (
1306 "AUTO", "AUTO_CLAMPED", "ALIGNED"):
1307 # change right handle
1308 normal
= (kf
.co
- kf
.handle_left
).normalized()
1309 size
= (kf
.handle_right
[0] - kf
.co
[0]) / normal
[0]
1310 normal
= normal
* size
+ kf
.co
1311 kf
.handle_right
[1] = normal
[1]
1312 elif side
== "left" and new_type
in (
1313 "AUTO", "AUTO_CLAMPED", "ALIGNED"):
1314 # change left handle
1315 normal
= (kf
.co
- kf
.handle_right
).normalized()
1316 size
= (kf
.handle_left
[0] - kf
.co
[0]) / normal
[0]
1317 normal
= normal
* size
+ kf
.co
1318 kf
.handle_left
[1] = normal
[1]
1320 if side
in ("left", "both"):
1321 kf
.handle_left_type
= new_type
1322 if side
in ("right", "both"):
1323 kf
.handle_right_type
= new_type
1325 context
.window_manager
.motion_trail
.force_update
= True
1328 class MotionTrailOperator(bpy
.types
.Operator
):
1329 bl_idname
= "view3d.motion_trail"
1330 bl_label
= "Motion Trail"
1331 bl_description
= "Edit motion trails in 3d-view"
1337 def handle_add(self
, context
):
1338 MotionTrailOperator
._handle
_calc
= bpy
.types
.SpaceView3D
.draw_handler_add(
1339 calc_callback
, (self
, context
), 'WINDOW', 'POST_VIEW')
1340 MotionTrailOperator
._handle
_draw
= bpy
.types
.SpaceView3D
.draw_handler_add(
1341 draw_callback
, (self
, context
), 'WINDOW', 'POST_PIXEL')
1344 def handle_remove():
1345 if MotionTrailOperator
._handle
_calc
is not None:
1346 bpy
.types
.SpaceView3D
.draw_handler_remove(MotionTrailOperator
._handle
_calc
, 'WINDOW')
1347 if MotionTrailOperator
._handle
_draw
is not None:
1348 bpy
.types
.SpaceView3D
.draw_handler_remove(MotionTrailOperator
._handle
_draw
, 'WINDOW')
1349 MotionTrailOperator
._handle
_calc
= None
1350 MotionTrailOperator
._handle
_draw
= None
1352 def modal(self
, context
, event
):
1353 # XXX Required, or custom transform.translate will break!
1354 # XXX If one disables and re-enables motion trail, modal op will still be running,
1355 # XXX default translate op will unintentionally get called, followed by custom translate.
1356 if not context
.window_manager
.motion_trail
.enabled
:
1357 MotionTrailOperator
.handle_remove()
1358 context
.area
.tag_redraw()
1361 if not context
.area
or not context
.region
or event
.type == 'NONE':
1362 context
.area
.tag_redraw()
1363 return {'PASS_THROUGH'}
1365 wm
= context
.window_manager
1366 keyconfig
= wm
.keyconfigs
.active
1367 select
= getattr(keyconfig
.preferences
, "select_mouse", "LEFT")
1369 if (not context
.active_object
or
1370 context
.active_object
.mode
not in ('OBJECT', 'POSE')):
1374 context
.window_manager
.motion_trail
.force_update
= True
1375 # default hotkeys should still work
1376 if event
.type == self
.transform_key
and event
.value
== 'PRESS':
1377 if bpy
.ops
.transform
.translate
.poll():
1378 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
1379 elif event
.type == select
+ 'MOUSE' and event
.value
== 'PRESS' \
1380 and not self
.drag
and not event
.shift
and not event
.alt \
1382 if bpy
.ops
.view3d
.select
.poll():
1383 bpy
.ops
.view3d
.select('INVOKE_DEFAULT')
1384 elif event
.type == 'LEFTMOUSE' and event
.value
== 'PRESS' and not\
1385 event
.alt
and not event
.ctrl
and not event
.shift
:
1386 if eval("bpy.ops." + self
.left_action
+ ".poll()"):
1387 eval("bpy.ops." + self
.left_action
+ "('INVOKE_DEFAULT')")
1388 return {'PASS_THROUGH'}
1389 # check if event was generated within 3d-window, dragging is exception
1391 if not (0 < event
.mouse_region_x
< context
.region
.width
) or \
1392 not (0 < event
.mouse_region_y
< context
.region
.height
):
1393 return {'PASS_THROUGH'}
1395 if (event
.type == self
.transform_key
and event
.value
== 'PRESS' and
1396 (self
.active_keyframe
or
1397 self
.active_handle
or
1398 self
.active_timebead
or
1399 self
.active_frame
)):
1400 # override default translate()
1403 if self
.active_frame
:
1404 insert_keyframe(self
, context
, self
.active_frame
)
1405 self
.active_keyframe
= self
.active_frame
1406 self
.active_frame
= False
1407 self
.keyframes_ori
, self
.handles_ori
= \
1408 get_original_animation_data(context
, self
.keyframes
)
1409 self
.drag_mouse_ori
= mathutils
.Vector([event
.mouse_region_x
,
1410 event
.mouse_region_y
])
1417 context
.window_manager
.motion_trail
.force_update
= True
1418 elif event
.type == self
.transform_key
and event
.value
== 'PRESS':
1419 # call default translate()
1420 if bpy
.ops
.transform
.translate
.poll():
1421 bpy
.ops
.transform
.translate('INVOKE_DEFAULT')
1422 elif (event
.type == 'ESC' and self
.drag
and event
.value
== 'PRESS') or \
1423 (event
.type == 'RIGHTMOUSE' and self
.drag
and event
.value
== 'PRESS'):
1427 context
.window_manager
.motion_trail
.force_update
= True
1428 self
.active_keyframe
, self
.active_timebead
= cancel_drag(context
,
1429 self
.active_keyframe
, self
.active_handle
,
1430 self
.active_timebead
, self
.keyframes_ori
, self
.handles_ori
,
1432 elif event
.type == 'MOUSEMOVE' and self
.drag
:
1434 self
.active_keyframe
, self
.active_timebead
, self
.keyframes_ori
= \
1435 drag(context
, event
, self
.drag_mouse_ori
,
1436 self
.active_keyframe
, self
.active_handle
,
1437 self
.active_timebead
, self
.keyframes_ori
, self
.handles_ori
,
1439 elif event
.type == select
+ 'MOUSE' and event
.value
== 'PRESS' and \
1440 not self
.drag
and not event
.shift
and not event
.alt
and not \
1444 clicked
= mathutils
.Vector([event
.mouse_region_x
,
1445 event
.mouse_region_y
])
1446 self
.active_keyframe
= False
1447 self
.active_handle
= False
1448 self
.active_timebead
= False
1449 self
.active_frame
= False
1450 context
.window_manager
.motion_trail
.force_update
= True
1451 context
.window_manager
.motion_trail
.handle_type_enabled
= True
1454 if context
.window_manager
.motion_trail
.path_before
== 0:
1455 frame_min
= context
.scene
.frame_start
1458 context
.scene
.frame_start
,
1459 context
.scene
.frame_current
-
1460 context
.window_manager
.motion_trail
.path_before
1462 if context
.window_manager
.motion_trail
.path_after
== 0:
1463 frame_max
= context
.scene
.frame_end
1466 context
.scene
.frame_end
,
1467 context
.scene
.frame_current
+
1468 context
.window_manager
.motion_trail
.path_after
1471 for objectname
, values
in self
.click
.items():
1474 for frame
, type, coord
, action_ob
, child
in values
:
1475 if frame
< frame_min
or frame
> frame_max
:
1477 if (coord
- clicked
).length
<= treshold
:
1479 if type == "keyframe":
1480 self
.active_keyframe
= [objectname
, frame
, frame
,
1482 elif type == "handle_left":
1483 self
.active_handle
= [objectname
, frame
, "left",
1485 elif type == "handle_right":
1486 self
.active_handle
= [objectname
, frame
, "right",
1488 elif type == "timebead":
1489 self
.active_timebead
= [objectname
, frame
, frame
,
1491 elif type == "frame":
1492 self
.active_frame
= [objectname
, frame
, frame
,
1496 context
.window_manager
.motion_trail
.handle_type_enabled
= False
1497 # no motion trail selections, so pass on to normal select()
1498 if bpy
.ops
.view3d
.select
.poll():
1499 bpy
.ops
.view3d
.select('INVOKE_DEFAULT')
1501 handle_type
= get_handle_type(self
.active_keyframe
,
1504 context
.window_manager
.motion_trail
.handle_type_old
= \
1506 context
.window_manager
.motion_trail
.handle_type
= \
1509 context
.window_manager
.motion_trail
.handle_type_enabled
= \
1511 elif event
.type == 'LEFTMOUSE' and event
.value
== 'PRESS' and \
1516 context
.window_manager
.motion_trail
.force_update
= True
1517 elif event
.type == 'LEFTMOUSE' and event
.value
== 'PRESS' and not\
1518 event
.alt
and not event
.ctrl
and not event
.shift
:
1519 if eval("bpy.ops." + self
.left_action
+ ".poll()"):
1520 eval("bpy.ops." + self
.left_action
+ "('INVOKE_DEFAULT')")
1522 if context
.area
: # not available if other window-type is fullscreen
1523 context
.area
.tag_redraw()
1525 return {'PASS_THROUGH'}
1527 def invoke(self
, context
, event
):
1528 if context
.area
.type != 'VIEW_3D':
1529 self
.report({'WARNING'}, "View3D not found, cannot run operator")
1530 return {'CANCELLED'}
1532 # get clashing keymap items
1533 wm
= context
.window_manager
1534 keyconfig
= wm
.keyconfigs
.active
1535 select
= getattr(keyconfig
.preferences
, "select_mouse", "LEFT")
1537 bpy
.context
.window_manager
.keyconfigs
.active
.keymaps
['3D View'],
1538 bpy
.context
.window_manager
.keyconfigs
.active
.keymaps
['Object Mode']
1541 self
.left_action
= None
1542 self
.right_action
= None
1544 for kmi
in km
.keymap_items
:
1545 if kmi
.idname
== "transform.translate" and \
1546 kmi
.map_type
== 'KEYBOARD' and not \
1547 kmi
.properties
.texture_space
:
1549 self
.transform_key
= kmi
.type
1550 elif (kmi
.type == 'ACTIONMOUSE' and select
== 'RIGHT') \
1551 and not kmi
.alt
and not kmi
.any
and not kmi
.ctrl \
1554 self
.left_action
= kmi
.idname
1555 elif kmi
.type == 'SELECTMOUSE' and not kmi
.alt
and not \
1556 kmi
.any
and not kmi
.ctrl
and not kmi
.shift
:
1558 if select
== 'RIGHT':
1559 self
.right_action
= kmi
.idname
1561 self
.left_action
= kmi
.idname
1562 elif kmi
.type == 'LEFTMOUSE' and not kmi
.alt
and not \
1563 kmi
.any
and not kmi
.ctrl
and not kmi
.shift
:
1565 self
.left_action
= kmi
.idname
1567 if not context
.window_manager
.motion_trail
.enabled
:
1569 self
.active_keyframe
= False
1570 self
.active_handle
= False
1571 self
.active_timebead
= False
1572 self
.active_frame
= False
1576 self
.perspective
= context
.region_data
.perspective_matrix
1578 context
.window_manager
.motion_trail
.force_update
= True
1579 context
.window_manager
.motion_trail
.handle_type_enabled
= False
1581 "path": {}, "keyframes": {},
1582 "timebeads_timing": {}, "timebeads_speed": {}
1588 MotionTrailOperator
.handle_add(self
, context
)
1589 context
.window_manager
.motion_trail
.enabled
= True
1592 context
.area
.tag_redraw()
1594 context
.window_manager
.modal_handler_add(self
)
1595 return {'RUNNING_MODAL'}
1601 MotionTrailOperator
.handle_remove()
1602 context
.window_manager
.motion_trail
.enabled
= False
1605 context
.area
.tag_redraw()
1610 class MotionTrailPanel(bpy
.types
.Panel
):
1611 bl_idname
= "VIEW3D_PT_motion_trail"
1612 bl_category
= "Animation"
1613 bl_space_type
= 'VIEW_3D'
1614 bl_region_type
= 'UI'
1615 bl_label
= "Motion Trail"
1616 bl_options
= {'DEFAULT_CLOSED'}
1619 def poll(cls
, context
):
1620 if context
.active_object
is None:
1622 return context
.active_object
.mode
in ('OBJECT', 'POSE')
1624 def draw(self
, context
):
1625 col
= self
.layout
.column()
1626 if not context
.window_manager
.motion_trail
.enabled
:
1627 col
.operator("view3d.motion_trail", text
="Enable motion trail")
1629 col
.operator("view3d.motion_trail", text
="Disable motion trail")
1631 box
= self
.layout
.box()
1632 box
.prop(context
.window_manager
.motion_trail
, "mode")
1633 # box.prop(context.window_manager.motion_trail, "calculate")
1634 if context
.window_manager
.motion_trail
.mode
== 'timing':
1635 box
.prop(context
.window_manager
.motion_trail
, "timebeads")
1637 box
= self
.layout
.box()
1641 if context
.window_manager
.motion_trail
.path_display
:
1642 row
.prop(context
.window_manager
.motion_trail
, "path_display",
1643 icon
="DOWNARROW_HLT", text
="", emboss
=False)
1645 row
.prop(context
.window_manager
.motion_trail
, "path_display",
1646 icon
="RIGHTARROW", text
="", emboss
=False)
1648 row
.label(text
="Path options")
1650 if context
.window_manager
.motion_trail
.path_display
:
1651 col
.prop(context
.window_manager
.motion_trail
, "path_style",
1653 grouped
= col
.column(align
=True)
1654 grouped
.prop(context
.window_manager
.motion_trail
, "path_width",
1656 grouped
.prop(context
.window_manager
.motion_trail
,
1657 "path_transparency", text
="Transparency")
1658 grouped
.prop(context
.window_manager
.motion_trail
,
1660 row
= grouped
.row(align
=True)
1661 row
.prop(context
.window_manager
.motion_trail
, "path_before")
1662 row
.prop(context
.window_manager
.motion_trail
, "path_after")
1663 col
= col
.column(align
=True)
1664 col
.prop(context
.window_manager
.motion_trail
, "keyframe_numbers")
1665 col
.prop(context
.window_manager
.motion_trail
, "frame_display")
1667 if context
.window_manager
.motion_trail
.mode
== 'location':
1668 box
= self
.layout
.box()
1669 col
= box
.column(align
=True)
1670 col
.prop(context
.window_manager
.motion_trail
, "handle_display",
1672 if context
.window_manager
.motion_trail
.handle_display
:
1674 row
.enabled
= context
.window_manager
.motion_trail
.\
1676 row
.prop(context
.window_manager
.motion_trail
, "handle_type")
1679 class MotionTrailProps(bpy
.types
.PropertyGroup
):
1680 def internal_update(self
, context
):
1681 context
.window_manager
.motion_trail
.force_update
= True
1683 context
.area
.tag_redraw()
1686 enabled
: BoolProperty(default
=False)
1688 force_update
: BoolProperty(name
="internal use",
1689 description
="Force calc_callback to fully execute",
1692 handle_type_enabled
: BoolProperty(default
=False)
1693 handle_type_frame
: FloatProperty()
1694 handle_type_side
: StringProperty()
1695 handle_type_action_ob
: StringProperty()
1696 handle_type_child
: StringProperty()
1698 handle_type_old
: EnumProperty(
1701 ("AUTO_CLAMPED", "", ""),
1703 ("ALIGNED", "", ""),
1707 # visible in user interface
1708 calculate
: EnumProperty(name
="Calculate", items
=(
1709 ("fast", "Fast", "Recommended setting, change if the "
1710 "motion path is positioned incorrectly"),
1711 ("full", "Full", "Takes parenting and modifiers into account, "
1712 "but can be very slow on complicated scenes")),
1713 description
="Calculation method for determining locations",
1715 update
=internal_update
1717 frame_display
: BoolProperty(name
="Frames",
1718 description
="Display frames, \n test",
1720 update
=internal_update
1722 handle_display
: BoolProperty(name
="Display",
1723 description
="Display handles",
1725 update
=internal_update
1727 handle_type
: EnumProperty(name
="Type", items
=(
1728 ("AUTO", "Automatic", ""),
1729 ("AUTO_CLAMPED", "Auto Clamped", ""),
1730 ("VECTOR", "Vector", ""),
1731 ("ALIGNED", "Aligned", ""),
1732 ("FREE", "Free", "")),
1733 description
="Set handle type for the selected handle",
1735 update
=set_handle_type
1737 keyframe_numbers
: BoolProperty(name
="Keyframe numbers",
1738 description
="Display keyframe numbers",
1740 update
=internal_update
1742 mode
: EnumProperty(name
="Mode", items
=(
1743 ("location", "Location", "Change path that is followed"),
1744 ("speed", "Speed", "Change speed between keyframes"),
1745 ("timing", "Timing", "Change position of keyframes on timeline")),
1746 description
="Enable editing of certain properties in the 3d-view",
1748 update
=internal_update
1750 path_after
: IntProperty(name
="After",
1751 description
="Number of frames to show after the current frame, "
1755 update
=internal_update
1757 path_before
: IntProperty(name
="Before",
1758 description
="Number of frames to show before the current frame, "
1762 update
=internal_update
1764 path_display
: BoolProperty(name
="Path options",
1765 description
="Display path options",
1768 path_resolution
: IntProperty(name
="Resolution",
1769 description
="10 is smoothest, but could be "
1770 "slow when adjusting keyframes, handles or timebeads",
1774 update
=internal_update
1776 path_style
: EnumProperty(name
="Path style", items
=(
1777 ("acceleration", "Acceleration", "Gradient based on relative acceleration"),
1778 ("simple", "Simple", "Black line"),
1779 ("speed", "Speed", "Gradient based on relative speed")),
1780 description
="Information conveyed by path color",
1782 update
=internal_update
1784 path_transparency
: IntProperty(name
="Path transparency",
1785 description
="Determines visibility of path",
1789 subtype
='PERCENTAGE',
1790 update
=internal_update
1792 path_width
: IntProperty(name
="Path width",
1793 description
="Width in pixels",
1797 update
=internal_update
1799 timebeads
: IntProperty(name
="Time beads",
1800 description
="Number of time beads to display per segment",
1804 update
=internal_update
1810 MotionTrailOperator
,
1817 bpy
.utils
.register_class(cls
)
1819 bpy
.types
.WindowManager
.motion_trail
= PointerProperty(
1820 type=MotionTrailProps
1825 MotionTrailOperator
.handle_remove()
1827 bpy
.utils
.unregister_class(cls
)
1829 del bpy
.types
.WindowManager
.motion_trail
1832 if __name__
== "__main__":