Skip to content
Snippets Groups Projects
animation_motion_trail.py 77.2 KiB
Newer Older
  • Learn to ignore specific revisions
  •         for i, [f_ori, coords] in enumerate(locs_ori):
                if abs(frame_ori - f_ori) < 1e-4:
                    if i == 0:
                        # first keyframe, nothing before it
                        direction = -1
    
                        range = [f_ori, locs_ori[i + 1][0]]
    
                    elif i == len(locs_ori) - 1:
                        # last keyframe, nothing after it
    
                        range = [locs_ori[i - 1][0], f_ori]
    
                    else:
                        current = mathutils.Vector(coords)
    
                        next = mathutils.Vector(locs_ori[i + 1][1])
                        previous = mathutils.Vector(locs_ori[i - 1][1])
    
                        angle_to_next = d.angle(next - current, 0)
    
                        angle_to_previous = d.angle(previous - current, 0)
    
                        if angle_to_previous < angle_to_next:
                            # mouse movement is in direction of previous keyframe
                            direction = -1
    
                        range = [locs_ori[i - 1][0], locs_ori[i + 1][0]]
    
            direction *= -1  # feels more natural in 3d-view
    
            if not range:
                # keyframe not found, is impossible, but better safe than sorry
                return(active_keyframe, active_timebead, keyframes_ori)
            # calculate strength of movement
            d_screen = mathutils.Vector([event.mouse_region_x,
                event.mouse_region_y]) - drag_mouse_ori
            if d_screen.length != 0:
    
                d_screen = d_screen.length / (abs(d_screen[0]) / d_screen.length *
                          context.region.width + abs(d_screen[1]) / d_screen.length *
                          context.region.height)
    
                d_screen *= direction  # d_screen value ranges from -1.0 to 1.0
            else:
                d_screen = 0.0
            new_frame = d_screen * (range[1] - range[0]) + frame_ori
            max_frame = range[1]
            if max_frame == frame_ori:
                max_frame += 1
            min_frame = range[0]
            if min_frame == frame_ori:
                min_frame -= 1
            new_frame = min(max_frame - 1, max(min_frame + 1, new_frame))
            d_frame = new_frame - frame_ori
            curves = get_curves(action_ob, child)
    
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if abs(kf.co[0] - frame) < 1e-4:
                        kf.co[0] = new_frame
    
                        kf.handle_left[0] = handles_ori[objectname][frame_ori]["left"][i][0] + d_frame
                        kf.handle_right[0] = handles_ori[objectname][frame_ori]["right"][i][0] + d_frame
    
                        break
            active_keyframe = [objectname, new_frame, frame_ori, action_ob, child]
    
        # change position of active timebead on the timeline, thus altering speed
        elif context.window_manager.motion_trail.mode == 'speed' and \
        active_timebead:
            objectname, frame, frame_ori, action_ob, child = active_timebead
            if child:
                mat = action_ob.matrix_world.copy().inverted() * \
                    edit_bones[child.name].copy().to_4x4()
            else:
                mat = 1
    
            mouse_ori_world = screen_to_world(context, drag_mouse_ori[0],
                drag_mouse_ori[1]) * mat
            vector = screen_to_world(context, event.mouse_region_x,
                event.mouse_region_y) * mat
            d = vector - mouse_ori_world
    
            # determine direction (to next or previous keyframe)
            curves = get_curves(action_ob, child)
            fcx, fcy, fcz = curves
            locx = fcx.evaluate(frame_ori)
            locy = fcy.evaluate(frame_ori)
            locz = fcz.evaluate(frame_ori)
    
            loc_ori = mathutils.Vector([locx, locy, locz])  # bonespace
    
            keyframes = [kf for kf in keyframes_ori[objectname]]
            keyframes.append(frame_ori)
            keyframes.sort()
            frame_index = keyframes.index(frame_ori)
            kf_prev = keyframes[frame_index - 1]
            kf_next = keyframes[frame_index + 1]
    
            vec_prev = (
                    mathutils.Vector(keyframes_ori[objectname][kf_prev][1]) *
                    mat - loc_ori
                    ).normalized()
            vec_next = (mathutils.Vector(keyframes_ori[objectname][kf_next][1]) *
                    mat - loc_ori
                    ).normalized()
    
            d_normal = d.copy().normalized()
            dist_to_next = (d_normal - vec_next).length
            dist_to_prev = (d_normal - vec_prev).length
            if dist_to_prev < dist_to_next:
                direction = 1
            else:
                direction = -1
    
            if (kf_next - frame_ori) < (frame_ori - kf_prev):
                kf_bead = kf_next
                side = "left"
            else:
                kf_bead = kf_prev
                side = "right"
    
            d_frame = d.length * direction * 2  # * 2 to make it more sensitive
    
            angles = []
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if abs(kf.co[0] - kf_bead) < 1e-4:
                        if side == "left":
                            # left side
    
                            kf.handle_left[0] = min(
                                                handles_ori[objectname][kf_bead]["left"][i][0] +
                                                d_frame, kf_bead - 1
                                                )
                            angle = mathutils.Vector([-1, 0]).angle(
                                                mathutils.Vector(kf.handle_left) -
                                                mathutils.Vector(kf.co), 0
                                                )
    
                            if angle != 0:
                                angles.append(angle)
                        else:
                            # right side
    
                            kf.handle_right[0] = max(
                                                handles_ori[objectname][kf_bead]["right"][i][0] +
                                                d_frame, kf_bead + 1
                                                )
                            angle = mathutils.Vector([1, 0]).angle(
                                                mathutils.Vector(kf.handle_right) -
                                                mathutils.Vector(kf.co), 0
                                                )
    
                            if angle != 0:
                                angles.append(angle)
                        break
    
            # update frame of active_timebead
            perc = (sum(angles) / len(angles)) / (math.pi / 2)
            perc = max(0.4, min(1, perc * 5))
            if side == "left":
                bead_frame = kf_bead - perc * ((kf_bead - kf_prev - 2) / 2)
            else:
                bead_frame = kf_bead + perc * ((kf_next - kf_bead - 2) / 2)
            active_timebead = [objectname, bead_frame, frame_ori, action_ob, child]
    
        return(active_keyframe, active_timebead, keyframes_ori)
    
    
    # revert changes made by dragging
    def cancel_drag(context, active_keyframe, active_handle, active_timebead,
    keyframes_ori, handles_ori, edit_bones):
        # revert change in 3d-location of active keyframe and its handles
        if context.window_manager.motion_trail.mode == 'location' and \
        active_keyframe:
            objectname, frame, frame_ori, active_ob, child = active_keyframe
            curves = get_curves(active_ob, child)
            loc_ori = keyframes_ori[objectname][frame][1]
            if child:
                loc_ori = loc_ori * edit_bones[child.name] * \
                    active_ob.matrix_world.copy().inverted()
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if kf.co[0] == frame:
                        kf.co[1] = loc_ori[i]
    
                        kf.handle_left[1] = handles_ori[objectname][frame]["left"][i][1]
                        kf.handle_right[1] = handles_ori[objectname][frame]["right"][i][1]
    
        # revert change in 3d-location of active handle
        elif context.window_manager.motion_trail.mode == 'location' and \
        active_handle:
            objectname, frame, side, active_ob, child = active_handle
            curves = get_curves(active_ob, child)
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if kf.co[0] == frame:
    
                        kf.handle_left[1] = handles_ori[objectname][frame]["left"][i][1]
                        kf.handle_right[1] = handles_ori[objectname][frame]["right"][i][1]
    
        # revert position of all keyframes and handles on timeline
        elif context.window_manager.motion_trail.mode == 'timing' and \
        active_timebead:
            objectname, frame, frame_ori, active_ob, child = active_timebead
            curves = get_curves(active_ob, child)
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    for kf_ori, [frame_ori, loc] in keyframes_ori[objectname].\
                    items():
                        if abs(kf.co[0] - kf_ori) < 1e-4:
                            kf.co[0] = frame_ori
    
                            kf.handle_left[0] = handles_ori[objectname][frame_ori]["left"][i][0]
                            kf.handle_right[0] = handles_ori[objectname][frame_ori]["right"][i][0]
    
        # revert position of active keyframe and its handles on the timeline
        elif context.window_manager.motion_trail.mode == 'timing' and \
        active_keyframe:
            objectname, frame, frame_ori, active_ob, child = active_keyframe
            curves = get_curves(active_ob, child)
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if abs(kf.co[0] - frame) < 1e-4:
                        kf.co[0] = keyframes_ori[objectname][frame_ori][0]
    
                        kf.handle_left[0] = handles_ori[objectname][frame_ori]["left"][i][0]
                        kf.handle_right[0] = handles_ori[objectname][frame_ori]["right"][i][0]
    
                        break
            active_keyframe = [objectname, frame_ori, frame_ori, active_ob, child]
    
        # revert position of handles on the timeline
        elif context.window_manager.motion_trail.mode == 'speed' and \
        active_timebead:
            objectname, frame, frame_ori, active_ob, child = active_timebead
            curves = get_curves(active_ob, child)
            keyframes = [kf for kf in keyframes_ori[objectname]]
            keyframes.append(frame_ori)
            keyframes.sort()
            frame_index = keyframes.index(frame_ori)
            kf_prev = keyframes[frame_index - 1]
            kf_next = keyframes[frame_index + 1]
            if (kf_next - frame_ori) < (frame_ori - kf_prev):
                kf_frame = kf_next
            else:
                kf_frame = kf_prev
            for i, curve in enumerate(curves):
                for kf in curve.keyframe_points:
                    if kf.co[0] == kf_frame:
    
                        kf.handle_left[0] = handles_ori[objectname][kf_frame]["left"][i][0]
                        kf.handle_right[0] = handles_ori[objectname][kf_frame]["right"][i][0]
    
                        break
            active_timebead = [objectname, frame_ori, frame_ori, active_ob, child]
    
        return(active_keyframe, active_timebead)
    
    
    # return the handle type of the active selection
    def get_handle_type(active_keyframe, active_handle):
        if active_keyframe:
            objectname, frame, side, action_ob, child = active_keyframe
            side = "both"
        elif active_handle:
            objectname, frame, side, action_ob, child = active_handle
        else:
            # no active handle(s)
            return(False)
    
        # properties used when changing handle type
        bpy.context.window_manager.motion_trail.handle_type_frame = frame
        bpy.context.window_manager.motion_trail.handle_type_side = side
        bpy.context.window_manager.motion_trail.handle_type_action_ob = \
            action_ob.name
        if child:
            bpy.context.window_manager.motion_trail.handle_type_child = child.name
        else:
            bpy.context.window_manager.motion_trail.handle_type_child = ""
    
        curves = get_curves(action_ob, child=child)
        for c in curves:
            for kf in c.keyframe_points:
                if kf.co[0] == frame:
    
                        return(kf.handle_left_type)
                    else:
                        return(kf.handle_right_type)
    
        return("AUTO")
    
    
    # turn the given frame into a keyframe
    def insert_keyframe(self, context, frame):
        objectname, frame, frame, action_ob, child = frame
        curves = get_curves(action_ob, child)
        for c in curves:
            y = c.evaluate(frame)
            if c.keyframe_points:
                c.keyframe_points.insert(frame, y)
    
        bpy.context.window_manager.motion_trail.force_update = True
        calc_callback(self, context)
    
    
    # change the handle type of the active selection
    def set_handle_type(self, context):
        if not context.window_manager.motion_trail.handle_type_enabled:
            return
        if context.window_manager.motion_trail.handle_type_old == \
        context.window_manager.motion_trail.handle_type:
            # function called because of selection change, not change in type
            return
        context.window_manager.motion_trail.handle_type_old = \
            context.window_manager.motion_trail.handle_type
    
        frame = bpy.context.window_manager.motion_trail.handle_type_frame
        side = bpy.context.window_manager.motion_trail.handle_type_side
        action_ob = bpy.context.window_manager.motion_trail.handle_type_action_ob
        action_ob = bpy.data.objects[action_ob]
        child = bpy.context.window_manager.motion_trail.handle_type_child
        if child:
            child = action_ob.pose.bones[child]
        new_type = context.window_manager.motion_trail.handle_type
    
        curves = get_curves(action_ob, child=child)
        for c in curves:
            for kf in c.keyframe_points:
                if kf.co[0] == frame:
                    # align if necessary
    
                    if side in ("right", "both") and new_type in (
    
                                "AUTO", "AUTO_CLAMPED", "ALIGNED"):
    
                        # change right handle
                        normal = (kf.co - kf.handle_left).normalized()
                        size = (kf.handle_right[0] - kf.co[0]) / normal[0]
    
                        normal = normal * size + kf.co
    
                        kf.handle_right[1] = normal[1]
    
                    elif side == "left" and new_type in (
    
                                "AUTO", "AUTO_CLAMPED", "ALIGNED"):
    
                        # change left handle
                        normal = (kf.co - kf.handle_right).normalized()
                        size = (kf.handle_left[0] - kf.co[0]) / normal[0]
    
                        normal = normal * size + kf.co
    
                        kf.handle_left[1] = normal[1]
                    # change type
    
                        kf.handle_left_type = new_type
    
                        kf.handle_right_type = new_type
    
        context.window_manager.motion_trail.force_update = True
    
    
    class MotionTrailOperator(bpy.types.Operator):
        bl_idname = "view3d.motion_trail"
        bl_label = "Motion Trail"
    
        bl_description = "Edit motion trails in 3d-view"
    
        @staticmethod
        def handle_add(self, context):
            MotionTrailOperator._handle_calc = bpy.types.SpaceView3D.draw_handler_add(
    
                calc_callback, (self, context), 'WINDOW', 'POST_VIEW')
    
            MotionTrailOperator._handle_draw = bpy.types.SpaceView3D.draw_handler_add(
    
                draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
    
    
        @staticmethod
        def handle_remove():
            if MotionTrailOperator._handle_calc is not None:
                bpy.types.SpaceView3D.draw_handler_remove(MotionTrailOperator._handle_calc, 'WINDOW')
            if MotionTrailOperator._handle_draw is not None:
                bpy.types.SpaceView3D.draw_handler_remove(MotionTrailOperator._handle_draw, 'WINDOW')
            MotionTrailOperator._handle_calc = None
            MotionTrailOperator._handle_draw = None
    
        def modal(self, context, event):
    
            # XXX Required, or custom transform.translate will break!
            # XXX If one disables and re-enables motion trail, modal op will still be running,
            # XXX default translate op will unintentionally get called, followed by custom translate.
    
            if not context.window_manager.motion_trail.enabled:
                MotionTrailOperator.handle_remove()
    
                context.area.tag_redraw()
                return {'FINISHED'}
    
    
            if not context.area or not context.region or event.type == 'NONE':
    
                context.area.tag_redraw()
                return {'PASS_THROUGH'}
    
            wm = context.window_manager
            keyconfig = wm.keyconfigs.active
            select = getattr(keyconfig.preferences, "select_mouse", "LEFT")
    
    
                    context.active_object.mode not in ('OBJECT', 'POSE')):
    
                if self.drag:
                    self.drag = False
                    self.lock = True
                    context.window_manager.motion_trail.force_update = True
                # default hotkeys should still work
                if event.type == self.transform_key and event.value == 'PRESS':
                    if bpy.ops.transform.translate.poll():
                        bpy.ops.transform.translate('INVOKE_DEFAULT')
                elif event.type == select + 'MOUSE' and event.value == 'PRESS' \
                and not self.drag and not event.shift and not event.alt \
                and not event.ctrl:
                    if bpy.ops.view3d.select.poll():
                        bpy.ops.view3d.select('INVOKE_DEFAULT')
                elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and not\
                event.alt and not event.ctrl and not event.shift:
    
                    if eval("bpy.ops." + self.left_action + ".poll()"):
                        eval("bpy.ops." + self.left_action + "('INVOKE_DEFAULT')")
    
                return {'PASS_THROUGH'}
            # check if event was generated within 3d-window, dragging is exception
            if not self.drag:
                if not (0 < event.mouse_region_x < context.region.width) or \
                not (0 < event.mouse_region_y < context.region.height):
                    return {'PASS_THROUGH'}
    
    
            if (event.type == self.transform_key and event.value == 'PRESS' and
    
                   (self.active_keyframe or
                    self.active_handle or
                    self.active_timebead or
                    self.active_frame)):
    
                # override default translate()
                if not self.drag:
                    # start drag
                    if self.active_frame:
                        insert_keyframe(self, context, self.active_frame)
                        self.active_keyframe = self.active_frame
                        self.active_frame = False
                    self.keyframes_ori, self.handles_ori = \
                        get_original_animation_data(context, self.keyframes)
                    self.drag_mouse_ori = mathutils.Vector([event.mouse_region_x,
                        event.mouse_region_y])
                    self.drag = True
                    self.lock = False
                else:
                    # stop drag
                    self.drag = False
                    self.lock = True
                    context.window_manager.motion_trail.force_update = True
            elif event.type == self.transform_key and event.value == 'PRESS':
                # call default translate()
                if bpy.ops.transform.translate.poll():
                    bpy.ops.transform.translate('INVOKE_DEFAULT')
    
            elif (event.type == 'ESC' and self.drag and event.value == 'PRESS') or \
                 (event.type == 'RIGHTMOUSE' and self.drag and event.value == 'PRESS'):
    
                # cancel drag
                self.drag = False
                self.lock = True
                context.window_manager.motion_trail.force_update = True
                self.active_keyframe, self.active_timebead = cancel_drag(context,
                    self.active_keyframe, self.active_handle,
                    self.active_timebead, self.keyframes_ori, self.handles_ori,
                    self.edit_bones)
            elif event.type == 'MOUSEMOVE' and self.drag:
                # drag
                self.active_keyframe, self.active_timebead, self.keyframes_ori = \
                    drag(context, event, self.drag_mouse_ori,
                    self.active_keyframe, self.active_handle,
                    self.active_timebead, self.keyframes_ori, self.handles_ori,
                    self.edit_bones)
            elif event.type == select + 'MOUSE' and event.value == 'PRESS' and \
            not self.drag and not event.shift and not event.alt and not \
            event.ctrl:
                # select
                treshold = 10
                clicked = mathutils.Vector([event.mouse_region_x,
                    event.mouse_region_y])
                self.active_keyframe = False
                self.active_handle = False
                self.active_timebead = False
                self.active_frame = False
                context.window_manager.motion_trail.force_update = True
                context.window_manager.motion_trail.handle_type_enabled = True
                found = False
    
                if context.window_manager.motion_trail.path_before == 0:
                    frame_min = context.scene.frame_start
                else:
    
                    frame_min = max(
                                context.scene.frame_start,
                                context.scene.frame_current -
                                context.window_manager.motion_trail.path_before
                                )
    
                if context.window_manager.motion_trail.path_after == 0:
                    frame_max = context.scene.frame_end
                else:
    
                    frame_max = min(
                                context.scene.frame_end,
                                context.scene.frame_current +
                                context.window_manager.motion_trail.path_after
                                )
    
                for objectname, values in self.click.items():
                    if found:
                        break
                    for frame, type, coord, action_ob, child in values:
                        if frame < frame_min or frame > frame_max:
                            continue
                        if (coord - clicked).length <= treshold:
                            found = True
                            if type == "keyframe":
                                self.active_keyframe = [objectname, frame, frame,
                                    action_ob, child]
                            elif type == "handle_left":
                                self.active_handle = [objectname, frame, "left",
                                    action_ob, child]
                            elif type == "handle_right":
                                self.active_handle = [objectname, frame, "right",
                                    action_ob, child]
                            elif type == "timebead":
                                self.active_timebead = [objectname, frame, frame,
                                    action_ob, child]
                            elif type == "frame":
                                self.active_frame = [objectname, frame, frame,
                                    action_ob, child]
                            break
                if not found:
                    context.window_manager.motion_trail.handle_type_enabled = False
                    # no motion trail selections, so pass on to normal select()
                    if bpy.ops.view3d.select.poll():
                        bpy.ops.view3d.select('INVOKE_DEFAULT')
                else:
                    handle_type = get_handle_type(self.active_keyframe,
                        self.active_handle)
                    if handle_type:
                        context.window_manager.motion_trail.handle_type_old = \
                            handle_type
                        context.window_manager.motion_trail.handle_type = \
                            handle_type
                    else:
                        context.window_manager.motion_trail.handle_type_enabled = \
                            False
            elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and \
            self.drag:
                # stop drag
                self.drag = False
                self.lock = True
                context.window_manager.motion_trail.force_update = True
            elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and not\
            event.alt and not event.ctrl and not event.shift:
    
                if eval("bpy.ops." + self.left_action + ".poll()"):
                    eval("bpy.ops." + self.left_action + "('INVOKE_DEFAULT')")
    
            if context.area:  # not available if other window-type is fullscreen
    
                context.area.tag_redraw()
    
            return {'PASS_THROUGH'}
    
        def invoke(self, context, event):
    
            if context.area.type != 'VIEW_3D':
                self.report({'WARNING'}, "View3D not found, cannot run operator")
                return {'CANCELLED'}
    
            # get clashing keymap items
    
            wm = context.window_manager
            keyconfig = wm.keyconfigs.active
            select = getattr(keyconfig.preferences, "select_mouse", "LEFT")
    
            kms = [
                bpy.context.window_manager.keyconfigs.active.keymaps['3D View'],
                bpy.context.window_manager.keyconfigs.active.keymaps['Object Mode']
                ]
            kmis = []
            self.left_action = None
            self.right_action = None
            for km in kms:
                for kmi in km.keymap_items:
                    if kmi.idname == "transform.translate" and \
                    kmi.map_type == 'KEYBOARD' and not \
                    kmi.properties.texture_space:
                        kmis.append(kmi)
                        self.transform_key = kmi.type
                    elif (kmi.type == 'ACTIONMOUSE' and select == 'RIGHT') \
                    and not kmi.alt and not kmi.any and not kmi.ctrl \
                    and not kmi.shift:
                        kmis.append(kmi)
                        self.left_action = kmi.idname
                    elif kmi.type == 'SELECTMOUSE' and not kmi.alt and not \
                    kmi.any and not kmi.ctrl and not kmi.shift:
                        kmis.append(kmi)
                        if select == 'RIGHT':
                            self.right_action = kmi.idname
                        else:
                            self.left_action = kmi.idname
                    elif kmi.type == 'LEFTMOUSE' and not kmi.alt and not \
                    kmi.any and not kmi.ctrl and not kmi.shift:
                        kmis.append(kmi)
                        self.left_action = kmi.idname
    
            if not context.window_manager.motion_trail.enabled:
                # enable
                self.active_keyframe = False
                self.active_handle = False
                self.active_timebead = False
                self.active_frame = False
                self.click = {}
                self.drag = False
                self.lock = True
                self.perspective = context.region_data.perspective_matrix
                self.displayed = []
                context.window_manager.motion_trail.force_update = True
                context.window_manager.motion_trail.handle_type_enabled = False
                self.cached = {
                        "path": {}, "keyframes": {},
                        "timebeads_timing": {}, "timebeads_speed": {}
                        }
    
                for kmi in kmis:
                    kmi.active = False
    
                MotionTrailOperator.handle_add(self, context)
                context.window_manager.motion_trail.enabled = True
    
                if context.area:
                    context.area.tag_redraw()
    
                context.window_manager.modal_handler_add(self)
                return {'RUNNING_MODAL'}
    
            else:
                # disable
                for kmi in kmis:
                    kmi.active = True
                MotionTrailOperator.handle_remove()
                context.window_manager.motion_trail.enabled = False
    
                if context.area:
                    context.area.tag_redraw()
    
    
    
    class MotionTrailPanel(bpy.types.Panel):
    
        bl_idname = "VIEW3D_PT_motion_trail"
    
        bl_category = "Animation"
    
        bl_space_type = 'VIEW_3D'
    
        bl_region_type = 'UI'
    
        bl_label = "Motion Trail"
    
    Brendon Murphy's avatar
    Brendon Murphy committed
        bl_options = {'DEFAULT_CLOSED'}
    
        @classmethod
        def poll(cls, context):
    
            if context.active_object is None:
                return False
            return context.active_object.mode in ('OBJECT', 'POSE')
    
    
        def draw(self, context):
            col = self.layout.column()
    
            if not context.window_manager.motion_trail.enabled:
    
                col.operator("view3d.motion_trail", text="Enable motion trail")
            else:
                col.operator("view3d.motion_trail", text="Disable motion trail")
    
            box = self.layout.box()
            box.prop(context.window_manager.motion_trail, "mode")
    
            # box.prop(context.window_manager.motion_trail, "calculate")
    
            if context.window_manager.motion_trail.mode == 'timing':
    
                box.prop(context.window_manager.motion_trail, "timebeads")
    
            box = self.layout.box()
            col = box.column()
            row = col.row()
    
            if context.window_manager.motion_trail.path_display:
                row.prop(context.window_manager.motion_trail, "path_display",
                    icon="DOWNARROW_HLT", text="", emboss=False)
            else:
                row.prop(context.window_manager.motion_trail, "path_display",
                    icon="RIGHTARROW", text="", emboss=False)
    
            row.label(text="Path options")
    
            if context.window_manager.motion_trail.path_display:
                col.prop(context.window_manager.motion_trail, "path_style",
                    text="Style")
                grouped = col.column(align=True)
                grouped.prop(context.window_manager.motion_trail, "path_width",
                    text="Width")
                grouped.prop(context.window_manager.motion_trail,
                    "path_transparency", text="Transparency")
                grouped.prop(context.window_manager.motion_trail,
                    "path_resolution")
                row = grouped.row(align=True)
                row.prop(context.window_manager.motion_trail, "path_before")
                row.prop(context.window_manager.motion_trail, "path_after")
                col = col.column(align=True)
                col.prop(context.window_manager.motion_trail, "keyframe_numbers")
                col.prop(context.window_manager.motion_trail, "frame_display")
    
    
            if context.window_manager.motion_trail.mode == 'location':
    
                box = self.layout.box()
                col = box.column(align=True)
                col.prop(context.window_manager.motion_trail, "handle_display",
                    text="Handles")
                if context.window_manager.motion_trail.handle_display:
                    row = col.row()
                    row.enabled = context.window_manager.motion_trail.\
                        handle_type_enabled
                    row.prop(context.window_manager.motion_trail, "handle_type")
    
    
    class MotionTrailProps(bpy.types.PropertyGroup):
        def internal_update(self, context):
            context.window_manager.motion_trail.force_update = True
            if context.area:
                context.area.tag_redraw()
    
        # internal use
    
        enabled: BoolProperty(default=False)
    
        force_update: BoolProperty(name="internal use",
    
            description="Force calc_callback to fully execute",
            default=False)
    
        handle_type_enabled: BoolProperty(default=False)
        handle_type_frame: FloatProperty()
        handle_type_side: StringProperty()
        handle_type_action_ob: StringProperty()
        handle_type_child: StringProperty()
    
        handle_type_old: EnumProperty(
    
                items=(
                    ("AUTO", "", ""),
                    ("AUTO_CLAMPED", "", ""),
                    ("VECTOR", "", ""),
                    ("ALIGNED", "", ""),
                    ("FREE", "", "")),
                default='AUTO'
                )
    
        # visible in user interface
    
        calculate: EnumProperty(name="Calculate", items=(
    
                ("fast", "Fast", "Recommended setting, change if the "
                                 "motion path is positioned incorrectly"),
                ("full", "Full", "Takes parenting and modifiers into account, "
                                 "but can be very slow on complicated scenes")),
                description="Calculation method for determining locations",
                default='full',
                update=internal_update
                )
    
        frame_display: BoolProperty(name="Frames",
    
                description="Display frames, \n test",
                default=True,
                update=internal_update
                )
    
        handle_display: BoolProperty(name="Display",
    
                description="Display handles",
                default=True,
                update=internal_update
                )
    
        handle_type: EnumProperty(name="Type", items=(
    
                ("AUTO", "Automatic", ""),
                ("AUTO_CLAMPED", "Auto Clamped", ""),
                ("VECTOR", "Vector", ""),
                ("ALIGNED", "Aligned", ""),
                ("FREE", "Free", "")),
                description="Set handle type for the selected handle",
                default='AUTO',
                update=set_handle_type
                )
    
        keyframe_numbers: BoolProperty(name="Keyframe numbers",
    
                description="Display keyframe numbers",
                default=False,
                update=internal_update
                )
    
        mode: EnumProperty(name="Mode", items=(
    
                ("location", "Location", "Change path that is followed"),
                ("speed", "Speed", "Change speed between keyframes"),
                ("timing", "Timing", "Change position of keyframes on timeline")),
                description="Enable editing of certain properties in the 3d-view",
                default='location',
                update=internal_update
                )
    
        path_after: IntProperty(name="After",
    
                description="Number of frames to show after the current frame, "
                            "0 = display all",
                default=50,
                min=0,
                update=internal_update
                )
    
        path_before: IntProperty(name="Before",
    
                description="Number of frames to show before the current frame, "
                            "0 = display all",
                default=50,
                min=0,
                update=internal_update
                )
    
        path_display: BoolProperty(name="Path options",
    
                description="Display path options",
                default=True
                )
    
        path_resolution: IntProperty(name="Resolution",
    
                description="10 is smoothest, but could be "
                            "slow when adjusting keyframes, handles or timebeads",
                default=10,
                min=1,
                max=10,
                update=internal_update
                )
    
        path_style: EnumProperty(name="Path style", items=(
    
                ("acceleration", "Acceleration", "Gradient based on relative acceleration"),
                ("simple", "Simple", "Black line"),
                ("speed", "Speed", "Gradient based on relative speed")),
                description="Information conveyed by path color",
                default='simple',
                update=internal_update
                )
    
        path_transparency: IntProperty(name="Path transparency",
    
                description="Determines visibility of path",
                default=0,
                min=0,
                max=100,
                subtype='PERCENTAGE',
                update=internal_update
                )
    
        path_width: IntProperty(name="Path width",
    
                description="Width in pixels",
                default=1,
                min=1,
                soft_max=5,
                update=internal_update
                )
    
        timebeads: IntProperty(name="Time beads",
    
                description="Number of time beads to display per segment",
                default=5,
                min=1,
                soft_max=10,
                update=internal_update
                )
    
    
    classes = (
            MotionTrailProps,
            MotionTrailOperator,
            MotionTrailPanel,
            )
    
        for cls in classes:
            bpy.utils.register_class(cls)
    
        bpy.types.WindowManager.motion_trail = PointerProperty(
                                                    type=MotionTrailProps
                                                    )
    
    
    
    def unregister():
    
        MotionTrailOperator.handle_remove()
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
    
        del bpy.types.WindowManager.motion_trail
    
    
    if __name__ == "__main__":
        register()