Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2017-06-06 13:07:33 +0300
committermeta-androcto <meta.androcto1@gmail.com>2017-06-06 13:07:33 +0300
commit9950f0a0c210c235d2b33808b768191d57df1ca5 (patch)
tree5e89de53a39093e565c1012584a11f7c7eb06998
parentbe1ffdc175be417a0f9eabc95e6c7c6f243dab01 (diff)
auto tracker correct version
-rw-r--r--space_clip_editor_autotracker.py808
1 files 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 <http://www.gnu.org/licenses/>.
+# 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()