Cleanup: remove "Tweak" event type
[blender-addons.git] / space_clip_editor_refine_solution.py
blob638542ccd9b99e2fd443045742ca849e36ca4b10
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Refine tracking solution",
5 "author": "Stephen Leger",
6 "license": "GPL",
7 "version": (1, 1, 5),
8 "blender": (2, 80, 0),
9 "location": "Clip Editor > Tools > Solve > Refine Solution",
10 "description": "Refine motion solution by setting track weight according"
11 " to reprojection error",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/video_tools/refine_tracking.html",
14 "category": "Video Tools",
17 import bpy
18 from bpy.types import (
19 Operator,
20 Panel,
22 from bpy.props import FloatProperty
23 from mathutils import Vector
26 class TRACKING_OP_refine_solution(Operator):
27 bl_idname = "tracking.refine_solution"
28 bl_label = "Refine"
29 bl_description = "Set track weight by error and solve camera motion"
30 bl_options = {"UNDO"}
32 @classmethod
33 def poll(cls, context):
34 return (context.area and context.area.spaces and
35 hasattr(context.area.spaces.active, 'clip') and
36 context.area.spaces.active.clip is not None
39 def execute(self, context):
40 error = context.window_manager.TrackingTargetError
41 smooth = context.window_manager.TrackingSmooth
42 clip = context.area.spaces.active.clip
43 try:
44 tracking = clip.tracking
45 tracks = tracking.tracks
46 winx = float(clip.size[0])
47 winy = float(clip.size[1])
48 aspy = 1.0 / tracking.camera.pixel_aspect
49 start = tracking.reconstruction.cameras[0].frame
50 end = tracking.reconstruction.cameras[-1].frame
51 except:
52 return {'CANCELLED'}
54 marker_position = Vector()
56 for frame in range(start, end):
57 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
58 if camera is not None:
59 camera_invert = camera.matrix.inverted()
60 else:
61 continue
63 for track in tracking.tracks:
64 marker = track.markers.find_frame(frame)
65 if marker is None:
66 continue
68 # weight incomplete tracks on start and end
69 if frame > start + smooth and frame < end - smooth:
70 for m in track.markers:
71 if not m.mute:
72 tstart = m
73 break
74 for m in reversed(track.markers):
75 if not m.mute:
76 tend = m
77 break
78 dt = min(0.5 * (tend.frame - tstart.frame), smooth)
79 if dt > 0:
80 t0 = min(1.0, (frame - tstart.frame) / dt)
81 t1 = min(1.0, (tend.frame - frame) / dt)
82 tw = min(t0, t1)
83 else:
84 tw = 0.0
85 else:
86 tw = 1.0
88 reprojected_position = camera_invert @ track.bundle
89 if reprojected_position.z == 0:
90 track.weight = 0
91 track.keyframe_insert("weight", frame=frame)
92 continue
93 reprojected_position = reprojected_position / -reprojected_position.z * \
94 tracking.camera.focal_length_pixels
95 reprojected_position = Vector(
96 (tracking.camera.principal[0] + reprojected_position[0],
97 tracking.camera.principal[1] * aspy + reprojected_position[1], 0)
100 marker_position[0] = (marker.co[0] + track.offset[0]) * winx
101 marker_position[1] = (marker.co[1] + track.offset[1]) * winy * aspy
103 dp = marker_position - reprojected_position
104 if dp.length == 0:
105 track.weight = 1.0
106 else:
107 track.weight = min(1.0, tw * error / dp.length)
108 track.keyframe_insert("weight", frame=frame)
110 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
112 return{'FINISHED'}
115 class TRACKING_OP_reset_solution(Operator):
116 bl_idname = "tracking.reset_solution"
117 bl_label = "Reset"
118 bl_description = "Reset track weight and solve camera motion"
119 bl_options = {"UNDO"}
121 @classmethod
122 def poll(cls, context):
123 return (context.area.spaces.active.clip is not None)
125 def execute(self, context):
126 clip = context.area.spaces.active.clip
127 try:
128 tracking = clip.tracking
129 tracks = tracking.tracks
130 start = tracking.reconstruction.cameras[0].frame
131 end = tracking.reconstruction.cameras[-1].frame
132 except:
133 return {'CANCELLED'}
135 start = tracking.reconstruction.cameras[0].frame
136 end = tracking.reconstruction.cameras[-1].frame
137 for frame in range(start, end):
138 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
139 if camera is None:
140 continue
141 for track in tracking.tracks:
142 marker = track.markers.find_frame(frame=frame)
143 if marker is None:
144 continue
145 track.weight = 1.0
146 track.keyframe_insert("weight", frame=frame)
148 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
150 return{'FINISHED'}
153 class TRACKING_PT_RefineMotionTracking(Panel):
154 bl_label = "Refine solution"
155 bl_space_type = "CLIP_EDITOR"
156 bl_region_type = "TOOLS"
157 bl_category = "Solve"
159 @classmethod
160 def poll(cls, context):
161 return (context.area.spaces.active.clip is not None)
163 def draw(self, context):
164 layout = self.layout
166 col = layout.column(align=True)
167 col.prop(context.window_manager, "TrackingTargetError", text="Target error")
168 col.prop(context.window_manager, "TrackingSmooth", text="Smooth transition")
169 sub_box = col.box()
170 sub_box.scale_y = 0.25
172 row = col.row(align=True)
173 row.operator("tracking.refine_solution")
174 row.operator("tracking.reset_solution")
177 classes =(
178 TRACKING_OP_refine_solution,
179 TRACKING_OP_reset_solution,
180 TRACKING_PT_RefineMotionTracking
184 def register():
185 bpy.types.WindowManager.TrackingTargetError = FloatProperty(
186 name="Target Error",
187 description="Refine motion track target error",
188 default=0.3,
189 min=0.01
191 bpy.types.WindowManager.TrackingSmooth = FloatProperty(
192 name="Smooth Transition",
193 description="Smooth weight transition on start and end of incomplete tracks",
194 default=25,
195 min=1
197 for cls in classes:
198 bpy.utils.register_class(cls)
201 def unregister():
202 for cls in reversed(classes):
203 bpy.utils.unregister_class(cls)
204 del bpy.types.WindowManager.TrackingTargetError
205 del bpy.types.WindowManager.TrackingSmooth
208 if __name__ == "__main__":
209 register()