From 9950f0a0c210c235d2b33808b768191d57df1ca5 Mon Sep 17 00:00:00 2001 From: meta-androcto Date: Tue, 6 Jun 2017 20:07:33 +1000 Subject: auto tracker correct version --- space_clip_editor_autotracker.py | 808 ++++++++++++++++++++++++++------------- 1 file changed, 550 insertions(+), 258 deletions(-) diff --git a/space_clip_editor_autotracker.py b/space_clip_editor_autotracker.py index 1b9df0cf..cd00f2e7 100644 --- a/space_clip_editor_autotracker.py +++ b/space_clip_editor_autotracker.py @@ -2,7 +2,7 @@ # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 +# as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, @@ -11,304 +11,544 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Autotrack", - "author": "Miika Puustinen, Matti Kaihola", - "version": (0, 0, 95), + "author": "Miika Puustinen, Matti Kaihola, Stephen Leger", + "version": (0, 0, 99), "blender": (2, 78, 0), "location": "Movie clip Editor > Tools Panel > Autotrack", "description": "Motion Tracking with automatic feature detection.", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Motion_Tracking/Auto_Track", + "wiki_url": "https://github.com/miikapuustinen/blender_autotracker", "category": "Motion Tracking", } - import bpy +import bgl +import blf import math - - -class AutotrackerOperator(bpy.types.Operator): +from mathutils import Vector +from bpy.types import Operator, Panel, PropertyGroup, WindowManager +from bpy.props import BoolProperty, FloatProperty, IntProperty, EnumProperty, PointerProperty + +# for debug purpose +import time + +# http://blenderscripting.blogspot.ch/2011/07/bgl-drawing-with-opengl-onto-blender-25.html +class GlDrawOnScreen(): + black = (0.0, 0.0, 0.0, 0.7) + white = (1.0, 1.0, 1.0, 0.5) + progress_colour = (0.2, 0.7, 0.2, 0.5) + def String(self, text, x, y, size, colour): + ''' my_string : the text we want to print + pos_x, pos_y : coordinates in integer values + size : font height. + colour : used for definining the colour''' + dpi, font_id = 72, 0 # dirty fast assignment + bgl.glColor4f(*colour) + blf.position(font_id, x, y, 0) + blf.size(font_id, size, dpi) + blf.draw(font_id, text) + def _end(self): + bgl.glEnd() + bgl.glPopAttrib() + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + def _start_line(self, colour, width=2, style=bgl.GL_LINE_STIPPLE): + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glLineStipple(1, 0x9999) + bgl.glEnable(style) + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(*colour) + bgl.glLineWidth(width) + bgl.glBegin(bgl.GL_LINE_STRIP) + def Rectangle(self, x0, y0, x1, y1, colour, width=2, style=bgl.GL_LINE): + self._start_line(colour, width, style) + bgl.glVertex2i(x0, y0) + bgl.glVertex2i(x1, y0) + bgl.glVertex2i(x1, y1) + bgl.glVertex2i(x0, y1) + bgl.glVertex2i(x0, y0) + self._end() + def Polygon(self, pts, colour): + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(*colour) + bgl.glBegin(bgl.GL_POLYGON) + for pt in pts: + x, y = pt + bgl.glVertex2f(x, y) + self._end() + def ProgressBar(self, x, y, width, height, start, percent): + x1, y1 = x+width, y+height + # progress from current point to either start or end + xs = x+(x1-x) * float(start) + if percent > 0: + # going forward + xi = xs+(x1-xs) * float(percent) + else: + # going backward + xi = xs-(x-xs) * float(percent) + self.Polygon([(xs, y), (xs, y1), (xi, y1), (xi, y)], self.progress_colour) + self.Rectangle(x, y, x1, y1, self.white, width=1) + +def draw_callback(self, context): + #print("draw_callback : %s" % (self.progress)) + self.gl.ProgressBar(10, 24, 200, 16, self.start, self.progress) + self.gl.String(str(int(100*abs(self.progress)))+"% ESC to Cancel", 14, 28, 10, self.gl.white) + +class OP_Tracking_auto_tracker(Operator): """Autotrack. Esc to cancel.""" - bl_idname = "wm.modal_timer_operator" - bl_label = "Modal Timer Operator" + bl_idname = "tracking.auto_track" + bl_label = "AutoTracking" - limits = bpy.props.IntProperty(default=0) _timer = None - - def active_clip(self): - area = [i for i in bpy.context.screen.areas if i.type == 'CLIP_EDITOR'][0] - clip = area.spaces.active.clip.name - return clip - + _draw_handler = None + + gl = GlDrawOnScreen() + progress = 0 + limits = 0 + t = 0 + + def find_track_start(self, track): + for m in track.markers: + if not m.mute: + return m.frame + return track.markers[0].frame + + def find_track_end(self, track): + for m in reversed(track.markers): + if not m.mute: + return m.frame + return track.markers[-1].frame-1 + + def find_track_length(self, track): + tstart = self.find_track_start(track) + tend = self.find_track_end(track) + return tend-tstart + + def show_tracks(self, context): + scene = context.scene + clip = context.area.spaces.active.clip + tracks = clip.tracking.tracks + for track in tracks: + track.hide = False + + def get_vars_from_context(self, context): + scene = context.scene + props = context.window_manager.autotracker_props + clip = context.area.spaces.active.clip + tracks = clip.tracking.tracks + current_frame = scene.frame_current + clip_end = clip.frame_start+clip.frame_duration + clip_start = clip.frame_start + if props.track_backwards: + last_frame = min(clip_end, current_frame+props.frame_separation) + else: + last_frame = max(clip_start, current_frame-props.frame_separation) + return scene, props, clip, tracks, current_frame, last_frame + + def delete_tracks(self, to_delete): + bpy.ops.clip.select_all(action='DESELECT') + for track in to_delete: + track.select = True + bpy.ops.clip.delete_track() + # DETECT FEATURES - def auto_features(self, delete_threshold, limits): - tracks = [] + def auto_features(self, context): + """ + Detect features + """ + t = time.time() + + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + selected = [] old = [] to_delete = [] - + width = clip.size[0] + delete_threshold = float(props.delete_threshold)/100.0 + bpy.ops.clip.select_all(action='DESELECT') - + # Detect Features bpy.ops.clip.detect_features( - threshold=bpy.context.scene.autotracker_props.df_threshold, - min_distance=bpy.context.scene.autotracker_props.df_distance, - margin=bpy.context.scene.autotracker_props.df_margin, - placement=bpy.context.scene.autotracker_props.placement_list + threshold=props.df_threshold, + min_distance=props.df_distance/100.0*width, + margin=props.df_margin/100.0*width, + placement=props.placement_list ) - - current_frame = bpy.context.scene.frame_current - - tracks = bpy.data.movieclips[self.active_clip()].tracking.tracks + + # filter new and old tracks for track in tracks: - if track.markers.find_frame(current_frame) is not None: - if track.select is not True and track.hide is False and track.markers.find_frame(current_frame).mute is False: + if track.hide or track.lock: + continue + marker = track.markers.find_frame(current_frame) + if marker is not None: + if (not track.select) and (not marker.mute): old.append(track) - if track.select is True: + if track.select: selected.append(track) - + + added_tracks = len(selected) + # Select overlapping new markers - for i in selected: - for j in old: - i_marker = i.markers.find_frame(current_frame) - j_marker = j.markers.find_frame(current_frame) - distance = math.sqrt(((i_marker.co[0] - j_marker.co[0])**2) + ((i_marker.co[1] - j_marker.co[1])**2)) - + for track_new in selected: + marker0 = track_new.markers.find_frame(current_frame) + for track_old in old: + marker1 = track_old.markers.find_frame(current_frame) + distance = (marker1.co-marker0.co).length if distance < delete_threshold: - to_delete.append(i) + to_delete.append(track_new) + added_tracks -= 1 break - - # delete short tracks - for track in tracks: - muted = [] - active = [] - # print(track) - for marker in track.markers: - if marker.mute is True: - muted.append(marker) - else: - active.append(marker) - if len(muted) > 3 and len(active) < 1: - to_delete.append(track) - - if len(track.markers) > 1 and len(active) == 0: - to_delete.append(track) - + # Delete Overlapping Markers - bpy.ops.clip.select_all(action='DESELECT') - for track in tracks: - if track in to_delete: - track.select = True - - bpy.ops.clip.delete_track() - - print(str(len(selected)) + "/" + str(len(tracks)) + " tracks tracking.") - + self.delete_tracks(to_delete) + print("auto_features %.4f seconds add:%s tracks." % (time.time()-t, added_tracks)) + # AUTOTRACK FRAMES def track_frames_backward(self): - bpy.ops.clip.track_markers(backwards=True, sequence=False) - + # INVOKE_DEFAULT to show progress and take account of frame_limit + t = time.time() + res = bpy.ops.clip.track_markers('INVOKE_DEFAULT', backwards=True, sequence=True) + print("track_frames_backward %.2f seconds %s" % (time.time()-t, res)) + def track_frames_forward(self): - bpy.ops.clip.track_markers(backwards=False, sequence=False) - - # REMOVE BAD MARKERS - def remove_extra(self, jump_cut, track_backwards): - trackers = [] - - if track_backwards is True: - one_frame = -1 - two_frames = -2 + # INVOKE_DEFAULT to show progress and take account of frame_limit + t = time.time() + res = bpy.ops.clip.track_markers('INVOKE_DEFAULT', backwards=False, sequence=True) + print("track_frames_forward %.2f seconds %s" % (time.time()-t, res)) + + def get_active_tracks(self, context): + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + # Select active trackers for tracking + #bpy.ops.clip.select_all(action='DESELECT') + active_tracks = [] + for track in tracks: + if track.hide or track.lock: + continue + if len(track.markers) < 2: + active_tracks.append(track) + else: + marker = track.markers.find_frame(current_frame) + if (marker is not None) and (not marker.mute): + active_tracks.append(track) + return active_tracks + + def select_active_tracks(self, context): + t = time.time() + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + # Select active trackers for tracking + bpy.ops.clip.select_all(action='DESELECT') + selected = self.get_active_tracks(context) + for track in selected: + track.select = True + print("select_active_tracks %.2f seconds selected:%s" % (time.time()-t, len(selected))) + return selected + + def estimate_motion(self, context, last, frame): + """ + compute mean pixel motion for current frame + TODO: use statistic here to make filtering more efficient + last : last frame number + frame: current frame number + return mean pixel distance error + """ + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + nbtracks = 0 + distance = 0.0 + for track in tracks: + if track.hide or track.lock: + continue + marker0 = track.markers.find_frame(frame) + marker1 = track.markers.find_frame(last) + if marker0 is not None and marker1 is not None: + d = (marker0.co-marker1.co).length + # skip fixed tracks + if d > 0: + distance += d + nbtracks += 1 + if nbtracks > 0: + mean = distance / nbtracks else: - one_frame = 1 - two_frames = 2 - - if self.limits >= 3: - trackers = bpy.data.movieclips[self.active_clip()].tracking.tracks - - for i in trackers: - if len(i.markers) > 5: - current_frame = bpy.context.scene.frame_current - - if (i.markers.find_frame(current_frame) is not None and - i.markers.find_frame(current_frame - one_frame) is not None and - i.markers.find_frame(current_frame - two_frames) is not None): - - key_frame = i.markers.find_frame(current_frame).co - prev_frame = i.markers.find_frame(current_frame - one_frame).co - distance = math.sqrt(((key_frame[0] - prev_frame[0])**2) + ((key_frame[1] - prev_frame[1])**2)) - # Jump Cut threshold - if distance > jump_cut: - if (i.markers.find_frame(current_frame) is not None and - i.markers.find_frame(current_frame - one_frame) is not None): - - # create new track to new pos - new_track = \ - bpy.data.movieclips[self.active_clip()].tracking.tracks.new(frame=current_frame) - new_track.markers[0].co = i.markers.find_frame(current_frame).co - i.markers.find_frame(current_frame).mute = True - i.markers.find_frame(current_frame - one_frame).mute = True - + # arbitrary set to prevent division by 0 error + mean = 10 + + return mean + + # REMOVE SMALL TRACKS + def remove_small(self, context): + t = time.time() + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + to_delete = [] + bpy.ops.clip.select_all(action='DESELECT') + for track in tracks: + if track.hide or track.lock: + continue + if len(track.markers) > 1: + marker = track.markers.find_frame(current_frame) + if marker is None and self.find_track_length(track) < props.small_tracks: + to_delete.append(track) + deleted_tracks = len(to_delete) + self.delete_tracks(to_delete) + print("remove_small %.4f seconds %s tracks deleted." % (time.time()-t, deleted_tracks)) + + def split_track(self, context, track, split_frame, skip=0): + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + if props.track_backwards: + end = scene.frame_start + step = -1 + else: + end = scene.frame_end + step = 1 + new_track = \ + tracks.new(frame=split_frame) + for frame in range(split_frame, end, step): + marker = track.markers.find_frame(frame) + if marker is None: + return + # add new marker on new track for frame + if abs(frame - split_frame) >= skip: + new_marker = new_track.markers.find_frame(frame) + if new_marker is None: + new_marker = new_track.markers.insert_frame(frame) + new_marker.co = marker.co + # remove marker on track for frame + if frame == split_frame: + track.hide = True + else: + track.markers.delete_frame(frame) + marker.mute = True + + # REMOVE JUMPING MARKERS + def remove_jumping(self, context): + + t = time.time() + + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + + if props.track_backwards: + step = -1 + else: + step = 1 + + to_split = [None for track in tracks] + for frame in range(last_frame, current_frame, step): + + last = frame - step + + # mean motion (normalized [0-1]) distance for tracks between last and current frame + mean = self.estimate_motion(context, last, frame) + + # how much a track is allowed to move + allowed = mean * props.jump_cut + + for i, track in enumerate(tracks): + if track.hide or track.lock: + continue + marker0 = track.markers.find_frame(frame) + marker1 = track.markers.find_frame(last) + if marker0 is not None and marker1 is not None: + distance = (marker0.co-marker1.co).length + # Jump Cut threshold + if distance > allowed: + if to_split[i] is None: + to_split[i] = [frame, frame] + else: + to_split[i][1] = frame + + jumping = 0 + for i, split in enumerate(to_split): + if split is not None: + self.split_track(context, tracks[i], split[0], abs(split[0]-split[1])) + jumping += 1 + + print("remove_jumping :%.4f seconds %s tracks cut." % (time.time()-t, jumping)) + + def get_frame_range(self, context): + """ + get tracking frames range + use clip limits when clip shorter than scene + else use scene limits + """ + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + frame_start = max(scene.frame_start, clip.frame_start) + frame_end = min(scene.frame_end, clip.frame_start+clip.frame_duration) + frame_duration = frame_end - frame_start + return frame_start, frame_end, frame_duration + def modal(self, context, event): - scene = bpy.context.scene - if (event.type in {'ESC'} or scene.frame_current == scene.frame_end + 1 or - scene.frame_current == scene.frame_start - 1): - self.limits = 0 + + if event.type in {'ESC'}: + print("Cancelling") self.cancel(context) return {'FINISHED'} - - if event.type == 'TIMER': - - # PROP VARIABLES - delete_threshold = bpy.context.scene.autotracker_props.delete_threshold - endframe = bpy.context.scene.frame_end - start_frame = bpy.context.scene.frame_start - frame_separate = bpy.context.scene.autotracker_props.frame_separation - margin = bpy.context.scene.autotracker_props.df_margin - distance = bpy.context.scene.autotracker_props.df_distance - threshold = bpy.context.scene.autotracker_props.df_threshold - jump_cut = bpy.context.scene.autotracker_props.jump_cut - track_backwards = bpy.context.scene.autotracker_props.track_backwards - - # Auto features every frame separate step - if bpy.context.scene.frame_current % frame_separate == 0 or self.limits == 0: - limits = self.limits - self.auto_features(delete_threshold, limits) - - # Select all trackers for tracking - select_all = bpy.ops.clip.select_all(action='SELECT') - tracks = bpy.data.movieclips[self.active_clip()].tracking.tracks - active_tracks = [] - for track in tracks: - if track.lock is True: - track.select = False - else: - active_tracks.append(track) - - # Forwards or backwards tracking - if track_backwards is True: - if len(active_tracks) == 0: - print("No new tracks created. Doing nothing.") - self.limits = 0 - self.cancel(context) - return {'FINISHED'} - else: - self.track_frames_backward() - else: - if len(active_tracks) == 0: - print("No new tracks created. Doing nothing.") - self.limits = 0 - self.cancel(context) - return {'FINISHED'} - else: - self.track_frames_forward() - - # Remove bad tracks - self.remove_extra(jump_cut, track_backwards) - - self.limits += 1 - - return {'PASS_THROUGH'} - - def execute(self, context): - wm = context.window_manager - self._timer = wm.event_timer_add(time_step=0.5, window=context.window) - wm.modal_handler_add(self) + + scene, props, clip, tracks, current_frame, last_frame = self.get_vars_from_context(context) + frame_start, frame_end, frame_duration = self.get_frame_range(context) + + if (((not props.track_backwards) and current_frame >= frame_end) or + (props.track_backwards and current_frame <= frame_start)): + print("Reached clip end") + self.cancel(context) + return {'FINISHED'} + + # dont run this modal while tracking operator runs + # Known issue, youll have to keep ESC pressed + if event.type not in {'TIMER'} or context.scene.frame_current != self.next_frame: + return {'PASS_THROUGH'} + + # prevent own TIMER event while running + self.stop_timer(context) + + if props.track_backwards: + self.next_frame = scene.frame_current - props.frame_separation + total = self.start_frame - frame_start + else: + self.next_frame = scene.frame_current + props.frame_separation + total = frame_end - self.start_frame + + if total > 0: + self.progress = (current_frame-self.start_frame)/total + else: + self.progress = 0 + + print("Tracking frame %s" % (scene.frame_current)) + + # Remove bad tracks before adding new ones + self.remove_small(context) + self.remove_jumping(context) + + # add new tracks + self.auto_features(context) + + # Select active trackers for tracking + active_tracks = self.select_active_tracks(context) + + # finish if there is nothing to track + if len(active_tracks) == 0: + print("No new tracks created. Doing nothing.") + self.cancel(context) + return {'FINISHED'} + + # setup frame_limit on tracks + for track in active_tracks: + track.frames_limit = 0 + active_tracks[0].frames_limit = props.frame_separation + + # Forwards or backwards tracking + if props.track_backwards: + self.track_frames_backward() + else: + self.track_frames_forward() + + # setup a timer to broadcast a TIMER event to force modal to re-run as fast as possible (not waiting for any mouse or keyboard event) + self.start_timer(context) + + return {'RUNNING_MODAL'} + + def invoke(self, context, event): + scene = context.scene + frame_start, frame_end, frame_duration = self.get_frame_range(context) + + if scene.frame_current > frame_end: + scene.frame_current = frame_end + elif scene.frame_current < frame_start: + scene.frame_current = frame_start + + self.start_frame = scene.frame_current + self.start = (scene.frame_current-frame_start) / (frame_duration) + self.progress = 0 + + # keep track of frame at witch we should detect new features and filter tracks + self.next_frame = scene.frame_current + + # draw progress + args = (self, context) + self._draw_handler = bpy.types.SpaceClipEditor.draw_handler_add(draw_callback, args, 'WINDOW', 'POST_PIXEL') + + self.start_timer(context) + context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} + + def __init__(self): + self.t = time.time() + def __del__(self): + print("AutoTrack %.2f seconds" % (time.time()-self.t)) + + def execute(self, context): + print("execute") + return {'FINISHED'} + + def stop_timer(self, context): + context.window_manager.event_timer_remove(self._timer) + + def start_timer(self, context): + self._timer = context.window_manager.event_timer_add(time_step=0.1, window=context.window) + def cancel(self, context): - wm = context.window_manager - wm.event_timer_remove(self._timer) - - -# UI CREATION # - -class Autotracker_UI(bpy.types.Panel): - """Creates a Panel in the Render Layer properties window""" - bl_label = "Autotrack" - bl_idname = "autotrack" - bl_space_type = 'CLIP_EDITOR' - bl_region_type = 'TOOLS' - bl_category = "Track" - - # Draw UI - def draw(self, context): - layout = self.layout - - row = layout.row(align=True) - row.scale_y = 1.5 - - props = row.operator("wm.modal_timer_operator", text="Autotrack! ", icon='PLAY') - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "track_backwards") - - row = layout.row(align=True) # make next row - row.prop(context.scene.autotracker_props, "delete_threshold") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "frame_separation", text="Frame Separation") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "jump_cut", text="Jump Threshold") - - row = layout.row(align=True) - row.label(text="Detect Features Settings:") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "df_margin", text="Margin:") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "df_threshold", text="Threshold:") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "df_distance", text="Distance:") - - row = layout.row(align=True) - row.label(text="Feature Placement:") - - row = layout.row(align=True) - row.prop(context.scene.autotracker_props, "placement_list") - - -class AutotrackerSettings(bpy.types.PropertyGroup): + self.stop_timer(context) + self.show_tracks(context) + bpy.types.SpaceClipEditor.draw_handler_remove(self._draw_handler, 'WINDOW') + + @classmethod + def poll(cls, context): + return (context.area.spaces.active.clip is not None) + +class AutotrackerSettings(PropertyGroup): """Create properties""" - df_margin = bpy.props.IntProperty( + df_margin = FloatProperty( name="Detect Features Margin", description="Only features further margin pixels from the image edges are considered.", - default=16, + subtype='PERCENTAGE', + default=5, min=0, - max=2000 + max=100 ) - df_threshold = bpy.props.FloatProperty( + + df_threshold = FloatProperty( name="Detect Features Threshold", description="Threshold level to concider feature good enough for tracking.", - default=0.01, + default=0.3, min=0.0, max=1.0 ) - - df_distance = bpy.props.IntProperty( + # Note: merge this one with delete_threshold + df_distance = FloatProperty( name="Detect Features Distance", description="Minimal distance accepted between two features.", - default=64, + subtype='PERCENTAGE', + default=8, min=1, - max=300 + max=100 ) - delete_threshold = bpy.props.FloatProperty( + delete_threshold = FloatProperty( name="New Marker Threshold", description="Threshold how near new features can appear during autotracking.", - default=0.1, - min=0.0, - max=1.0 + subtype='PERCENTAGE', + default=8, + min=1, + max=100 ) - - frame_separation = bpy.props.IntProperty( + + small_tracks = IntProperty( + name="Minimum track length", + description="Delete tracks shortest than this number of frames (set to 0 to keep all tracks).", + default=50, + min=1, + max=1000 + ) + + frame_separation = IntProperty( name="Frame Separation", description="How often new features are generated.", default=5, @@ -316,20 +556,20 @@ class AutotrackerSettings(bpy.types.PropertyGroup): max=100 ) - jump_cut = bpy.props.FloatProperty( + jump_cut = FloatProperty( name="Jump Cut", description="Distance how much a marker can travel before it is considered " - "to be a bad track and cut. A new track is added.", - default=0.1, + "to be a bad track and cut. A new track is added. (factor relative to mean motion)", + default=5.0, min=0.0, - max=1.0 - ) + max=50.0 + ) - track_backwards = bpy.props.BoolProperty( + track_backwards = BoolProperty( name="AutoTrack Backwards", description="Autotrack backwards.", default=False - ) + ) # Dropdown menu list_items = [ @@ -338,28 +578,80 @@ class AutotrackerSettings(bpy.types.PropertyGroup): ("OUTSIDE_GPENCIL", "Outside Grease Pencil", "", 3), ] - placement_list = bpy.props.EnumProperty( + placement_list = EnumProperty( name="", - description="Feaure Placement", + description="Feature Placement", items=list_items ) + +""" + NOTE: + All size properties are in percent of clip size, so presets does not depends on clip size +""" +class AutotrackerPanel(Panel): + """Creates a Panel in the Render Layer properties window""" + bl_label = "Autotrack" + bl_idname = "autotrack" + bl_space_type = 'CLIP_EDITOR' + bl_region_type = 'TOOLS' + bl_category = "Track" + + @classmethod + def poll(cls, context): + return (context.area.spaces.active.clip is not None) + + # Draw UI + def draw(self, context): + layout = self.layout + wm = context.window_manager + row = layout.row(align=True) + row.scale_y = 1.5 + props = row.operator("tracking.auto_track", text="Autotrack! ", icon='PLAY') -# REGISTER BLOCK # -def register(): - bpy.utils.register_class(AutotrackerOperator) - bpy.utils.register_class(Autotracker_UI) - bpy.utils.register_class(AutotrackerSettings) + row = layout.row(align=True) + row.prop(wm.autotracker_props, "track_backwards") - bpy.types.Scene.autotracker_props = \ - bpy.props.PointerProperty(type=AutotrackerSettings) + row = layout.row(align=True) # make next row + row.prop(wm.autotracker_props, "delete_threshold") + + row = layout.row(align=True) # make next row + row.prop(wm.autotracker_props, "small_tracks") + row = layout.row(align=True) + row.prop(wm.autotracker_props, "frame_separation", text="Frame Separation") + + row = layout.row(align=True) + row.prop(wm.autotracker_props, "jump_cut", text="Jump Threshold") + + row = layout.row(align=True) + row.label(text="Detect Features Settings:") + + row = layout.row(align=True) + row.prop(wm.autotracker_props, "df_margin", text="Margin:") + + row = layout.row(align=True) + row.prop(wm.autotracker_props, "df_threshold", text="Threshold:") + row = layout.row(align=True) + row.prop(wm.autotracker_props, "df_distance", text="Distance:") + + row = layout.row(align=True) + row.label(text="Feature Placement:") + + row = layout.row(align=True) + row.prop(wm.autotracker_props, "placement_list") + +def register(): + bpy.utils.register_class(AutotrackerSettings) + WindowManager.autotracker_props = \ + PointerProperty(type=AutotrackerSettings) + bpy.utils.register_module(__name__) + def unregister(): - bpy.utils.unregister_class(AutotrackerOperator) - bpy.utils.unregister_class(Autotracker_UI) bpy.utils.unregister_class(AutotrackerSettings) - + bpy.utils.unregister_module(__name__) + del WindowManager.autotracker_props if __name__ == "__main__": register() -- cgit v1.2.3