mirror of
https://github.com/blender/blender-addons-contrib.git
synced 2025-07-20 16:54:35 +00:00
687 lines
24 KiB
Python
687 lines
24 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# 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 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# 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, 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, Stephen Leger",
|
|
"version": (0, 1, 1),
|
|
"blender": (2, 78, 0),
|
|
"location": "Movie clip Editor > Tools Panel > Autotrack",
|
|
"description": "Motion Tracking with automatic feature detection.",
|
|
"warning": "",
|
|
"doc_url": "https://github.com/miikapuustinen/blender_autotracker",
|
|
"category": "Video Tools",
|
|
}
|
|
|
|
import bpy
|
|
import bgl
|
|
import blf
|
|
from bpy.types import (
|
|
Operator,
|
|
Panel,
|
|
PropertyGroup,
|
|
WindowManager,
|
|
)
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
FloatProperty,
|
|
IntProperty,
|
|
EnumProperty,
|
|
PointerProperty,
|
|
)
|
|
|
|
# for debug purposes
|
|
import time
|
|
|
|
# set to True to enable debug prints
|
|
DEBUG = False
|
|
|
|
|
|
# pass variables just like for the regular prints
|
|
def debug_print(*args, **kwargs):
|
|
global DEBUG
|
|
if DEBUG:
|
|
print(*args, **kwargs)
|
|
|
|
|
|
# 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 defining 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=None):
|
|
if style is None:
|
|
# NOTE: this is no longer supported in Blender3.0.
|
|
# style = bgl.GL_LINE_STIPPLE
|
|
style = bgl.GL_LINE
|
|
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):
|
|
self.gl.ProgressBar(10, 24, 200, 16, self.start, self.progress)
|
|
self.gl.String(str(int(100 * abs(self.progress))) + "% ESC to Stop", 14, 28, 10, self.gl.white)
|
|
|
|
|
|
class OP_Tracking_auto_tracker(Operator):
|
|
bl_idname = "tracking.auto_track"
|
|
bl_label = "AutoTracking"
|
|
bl_description = ("Start Autotracking, Press Esc to Stop \n"
|
|
"When stopped, the added Track Markers will be kept")
|
|
|
|
_timer = None
|
|
_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):
|
|
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, 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=props.df_threshold,
|
|
min_distance=props.df_distance / 100.0 * width,
|
|
margin=props.df_margin / 100.0 * width,
|
|
placement=props.placement_list
|
|
)
|
|
|
|
# filter new and old tracks
|
|
for track in tracks:
|
|
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:
|
|
selected.append(track)
|
|
|
|
added_tracks = len(selected)
|
|
|
|
# Select overlapping new markers
|
|
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(track_new)
|
|
added_tracks -= 1
|
|
break
|
|
|
|
# Delete Overlapping Markers
|
|
self.delete_tracks(to_delete)
|
|
debug_print("auto_features %.4f seconds, add: %s tracks" % (time.time() - t, added_tracks))
|
|
|
|
# AUTOTRACK FRAMES
|
|
def track_frames_backward(self):
|
|
# 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)
|
|
debug_print("track_frames_backward %.2f seconds %s" % (time.time() - t, res))
|
|
|
|
def track_frames_forward(self):
|
|
# 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)
|
|
debug_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)
|
|
|
|
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
|
|
debug_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:
|
|
# 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)
|
|
debug_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
|
|
|
|
debug_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):
|
|
|
|
if event.type in {'ESC'}:
|
|
self.report({'INFO'},
|
|
"Stopping, up to now added Markers will be kept. Autotracking Finished")
|
|
self.cancel(context)
|
|
return {'FINISHED'}
|
|
|
|
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)):
|
|
|
|
self.report({'INFO'},
|
|
"Reached the end of the Clip. Autotracking Finished")
|
|
self.cancel(context)
|
|
return {'FINISHED'}
|
|
|
|
# do not run this modal while tracking operator runs
|
|
# Known issue, you'll 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
|
|
|
|
debug_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:
|
|
self.report({'INFO'},
|
|
"No new tracks created, nothing to track. Autotrack Finished")
|
|
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):
|
|
debug_print("AutoTrack %.2f seconds" % (time.time() - self.t))
|
|
|
|
def execute(self, context):
|
|
debug_print("Autotrack execute called")
|
|
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):
|
|
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: FloatProperty(
|
|
name="Detect Features Margin",
|
|
description="Only consider features from pixels located outside\n"
|
|
"the defined margin from the clip borders",
|
|
subtype='PERCENTAGE',
|
|
default=5,
|
|
min=0,
|
|
max=100
|
|
)
|
|
df_threshold: FloatProperty(
|
|
name="Detect Features Threshold",
|
|
description="Threshold level to deem a feature being good enough for tracking",
|
|
default=0.3,
|
|
min=0.0,
|
|
max=1.0
|
|
)
|
|
# Note: merge this one with delete_threshold
|
|
df_distance: FloatProperty(
|
|
name="Detect Features Distance",
|
|
description="Minimal acceptable distance between two features",
|
|
subtype='PERCENTAGE',
|
|
default=8,
|
|
min=1,
|
|
max=100
|
|
)
|
|
delete_threshold: FloatProperty(
|
|
name="New Marker Threshold",
|
|
description="Threshold of how close a new features can appear during tracking",
|
|
subtype='PERCENTAGE',
|
|
default=8,
|
|
min=1,
|
|
max=100
|
|
)
|
|
small_tracks: IntProperty(
|
|
name="Minimum track length",
|
|
description="Delete tracks shorter than this number of frames\n"
|
|
"Note: set to 0 for keeping all tracks",
|
|
default=50,
|
|
min=1,
|
|
max=1000
|
|
)
|
|
frame_separation: IntProperty(
|
|
name="Frame Separation",
|
|
description="How often new features are generated",
|
|
default=5,
|
|
min=1,
|
|
max=100
|
|
)
|
|
jump_cut: FloatProperty(
|
|
name="Jump Cut",
|
|
description="How much distance a marker can travel before it is considered "
|
|
"to be a bad track and cut.\nA new track will be added "
|
|
"(factor relative to mean motion)",
|
|
default=5.0,
|
|
min=0.0,
|
|
max=50.0
|
|
)
|
|
track_backwards: BoolProperty(
|
|
name="AutoTrack Backwards",
|
|
description="Track from the last frame of the selected clip",
|
|
default=False
|
|
)
|
|
# Dropdown menu
|
|
list_items = [
|
|
("FRAME", "Whole Frame", "", 1),
|
|
("INSIDE_GPENCIL", "Inside Grease Pencil", "", 2),
|
|
("OUTSIDE_GPENCIL", "Outside Grease Pencil", "", 3),
|
|
]
|
|
placement_list: EnumProperty(
|
|
name="Placement",
|
|
description="Feature Placement",
|
|
items=list_items
|
|
)
|
|
|
|
|
|
"""
|
|
NOTE:
|
|
All size properties are in percent of the clip size,
|
|
so presets do not depend on the 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)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
wm = context.window_manager
|
|
|
|
row = layout.row()
|
|
row.scale_y = 1.5
|
|
row.operator("tracking.auto_track", text="Autotrack! ", icon='PLAY')
|
|
|
|
row = layout.row()
|
|
row.prop(wm.autotracker_props, "track_backwards")
|
|
|
|
row = layout.row()
|
|
col = layout.column(align=True)
|
|
col.prop(wm.autotracker_props, "delete_threshold")
|
|
col.prop(wm.autotracker_props, "small_tracks")
|
|
col.prop(wm.autotracker_props, "frame_separation", text="Frame Separation")
|
|
col.prop(wm.autotracker_props, "jump_cut", text="Jump Threshold")
|
|
|
|
row = layout.row()
|
|
row.label(text="Detect Features Settings:")
|
|
col = layout.column(align=True)
|
|
col.prop(wm.autotracker_props, "df_margin", text="Margin:")
|
|
col.prop(wm.autotracker_props, "df_distance", text="Distance:")
|
|
col.prop(wm.autotracker_props, "df_threshold", text="Threshold:")
|
|
|
|
row = layout.row()
|
|
row.label(text="Feature Placement:")
|
|
col = layout.column(align=True)
|
|
col.prop(wm.autotracker_props, "placement_list", text="")
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(AutotrackerSettings)
|
|
WindowManager.autotracker_props = PointerProperty(
|
|
type=AutotrackerSettings
|
|
)
|
|
bpy.utils.register_module(__name__)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(AutotrackerSettings)
|
|
bpy.utils.unregister_module(__name__)
|
|
del WindowManager.autotracker_props
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|