Skip to content
Snippets Groups Projects
animation_motion_trail.py 74.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •             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]
                        break
    
        # 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]
                        break
    
        # 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]
                            break
    
        # 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):
    
        """Edit motion trails in 3d-view"""
    
        bl_idname = "view3d.motion_trail"
        bl_label = "Motion Trail"
    
        @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'}
    
            select = context.user_preferences.inputs.select_mouse
    
            if (not context.active_object or
                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':
                # get clashing keymap items
                select = context.user_preferences.inputs.select_mouse
                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()
    
    Campbell Barton's avatar
    Campbell Barton committed
    
                    context.window_manager.modal_handler_add(self)
    
                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()
    
                    return {'FINISHED'}
    
    
            else:
                self.report({'WARNING'}, "View3D not found, cannot run operator")
                return {'CANCELLED'}
    
    
    class MotionTrailPanel(bpy.types.Panel):
    
        bl_category = "Animation"
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
        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("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 = bpy.props.BoolProperty(default=False)
    
    
        force_update = bpy.props.BoolProperty(name="internal use",
            description="Force calc_callback to fully execute",
            default=False)
    
        handle_type_enabled = bpy.props.BoolProperty(default=False)
        handle_type_frame = bpy.props.FloatProperty()
        handle_type_side = bpy.props.StringProperty()
        handle_type_action_ob = bpy.props.StringProperty()
        handle_type_child = bpy.props.StringProperty()
    
        handle_type_old = bpy.props.EnumProperty(items=(
            ("AUTO", "", ""),
            ("AUTO_CLAMPED", "", ""),
            ("VECTOR", "", ""),
            ("ALIGNED", "", ""),
            ("FREE", "", "")),
            default='AUTO')
    
    
        # visible in user interface
    
        calculate = bpy.props.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 = bpy.props.BoolProperty(name="Frames",
            description="Display frames, \n test",
            default=True,
            update=internal_update)
    
        handle_display = bpy.props.BoolProperty(name="Display",
            description="Display handles",
            default=True,
            update=internal_update)
    
    
        handle_type = bpy.props.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 = bpy.props.BoolProperty(name="Keyframe numbers",
            description="Display keyframe numbers",
            default=False,
            update=internal_update)
    
    
        mode = bpy.props.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 = bpy.props.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 = bpy.props.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 = bpy.props.BoolProperty(name="Path options",
            description="Display path options",
            default=True)
    
        path_resolution = bpy.props.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 = bpy.props.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 = bpy.props.IntProperty(name="Path transparency",
            description="Determines visibility of path",
            default=0,
            min=0,
            max=100,
            subtype='PERCENTAGE',
            update=internal_update)
    
        path_width = bpy.props.IntProperty(name="Path width",
            description="Width in pixels",
            default=1,
            min=1,
            soft_max=5,
            update=internal_update)
    
        timebeads = bpy.props.IntProperty(name="Time beads",
            description="Number of time beads to display per segment",
            default=5,
            min=1,
            soft_max = 10,
            update=internal_update)
    
    
    def register():
    
        bpy.utils.register_module(__name__)
        bpy.types.WindowManager.motion_trail = bpy.props.PointerProperty(
    
            type=MotionTrailProps)
    
    
    def unregister():
    
        MotionTrailOperator.handle_remove()
        bpy.utils.unregister_module(__name__)
    
        del bpy.types.WindowManager.motion_trail
    
    
    if __name__ == "__main__":
        register()