Merge branch 'blender-v2.81-release'
[blender-addons.git] / space_clip_editor_refine_solution.py
blobfb423f7c1707c692e42ca1346527713e415b9848
1 # -*- coding:utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # All rights reserved.
19 # ##### END GPL LICENSE BLOCK #####
21 # <pep8 compliant>
23 bl_info = {
24 "name": "Refine tracking solution",
25 "author": "Stephen Leger",
26 "license": "GPL",
27 "version": (1, 1, 5),
28 "blender": (2, 80, 0),
29 "location": "Clip Editor > Tools > Solve > Refine Solution",
30 "description": "Refine motion solution by setting track weight according"
31 " to reprojection error",
32 "warning": "",
33 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/"
34 "Motion_Tracking/Refine_Track",
35 "category": "Video Tools",
38 import bpy
39 from bpy.types import (
40 Operator,
41 Panel,
43 from bpy.props import FloatProperty
44 from mathutils import Vector
47 class TRACKING_OP_refine_solution(Operator):
48 bl_idname = "tracking.refine_solution"
49 bl_label = "Refine"
50 bl_description = "Set track weight by error and solve camera motion"
51 bl_options = {"UNDO"}
53 @classmethod
54 def poll(cls, context):
55 return (context.area and context.area.spaces and
56 hasattr(context.area.spaces.active, 'clip') and
57 context.area.spaces.active.clip is not None
60 def execute(self, context):
61 error = context.window_manager.TrackingTargetError
62 smooth = context.window_manager.TrackingSmooth
63 clip = context.area.spaces.active.clip
64 try:
65 tracking = clip.tracking
66 tracks = tracking.tracks
67 winx = float(clip.size[0])
68 winy = float(clip.size[1])
69 aspy = 1.0 / tracking.camera.pixel_aspect
70 start = tracking.reconstruction.cameras[0].frame
71 end = tracking.reconstruction.cameras[-1].frame
72 except:
73 return {'CANCELLED'}
75 marker_position = Vector()
77 for frame in range(start, end):
78 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
79 if camera is not None:
80 camera_invert = camera.matrix.inverted()
81 else:
82 continue
84 for track in tracking.tracks:
85 marker = track.markers.find_frame(frame)
86 if marker is None:
87 continue
89 # weight incomplete tracks on start and end
90 if frame > start + smooth and frame < end - smooth:
91 for m in track.markers:
92 if not m.mute:
93 tstart = m
94 break
95 for m in reversed(track.markers):
96 if not m.mute:
97 tend = m
98 break
99 dt = min(0.5 * (tend.frame - tstart.frame), smooth)
100 if dt > 0:
101 t0 = min(1.0, (frame - tstart.frame) / dt)
102 t1 = min(1.0, (tend.frame - frame) / dt)
103 tw = min(t0, t1)
104 else:
105 tw = 0.0
106 else:
107 tw = 1.0
109 reprojected_position = camera_invert @ track.bundle
110 if reprojected_position.z == 0:
111 track.weight = 0
112 track.keyframe_insert("weight", frame=frame)
113 continue
114 reprojected_position = reprojected_position / -reprojected_position.z * \
115 tracking.camera.focal_length_pixels
116 reprojected_position = Vector(
117 (tracking.camera.principal[0] + reprojected_position[0],
118 tracking.camera.principal[1] * aspy + reprojected_position[1], 0)
121 marker_position[0] = (marker.co[0] + track.offset[0]) * winx
122 marker_position[1] = (marker.co[1] + track.offset[1]) * winy * aspy
124 dp = marker_position - reprojected_position
125 if dp.length == 0:
126 track.weight = 1.0
127 else:
128 track.weight = min(1.0, tw * error / dp.length)
129 track.keyframe_insert("weight", frame=frame)
131 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
133 return{'FINISHED'}
136 class TRACKING_OP_reset_solution(Operator):
137 bl_idname = "tracking.reset_solution"
138 bl_label = "Reset"
139 bl_description = "Reset track weight and solve camera motion"
140 bl_options = {"UNDO"}
142 @classmethod
143 def poll(cls, context):
144 return (context.area.spaces.active.clip is not None)
146 def execute(self, context):
147 clip = context.area.spaces.active.clip
148 try:
149 tracking = clip.tracking
150 tracks = tracking.tracks
151 start = tracking.reconstruction.cameras[0].frame
152 end = tracking.reconstruction.cameras[-1].frame
153 except:
154 return {'CANCELLED'}
156 start = tracking.reconstruction.cameras[0].frame
157 end = tracking.reconstruction.cameras[-1].frame
158 for frame in range(start, end):
159 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
160 if camera is None:
161 continue
162 for track in tracking.tracks:
163 marker = track.markers.find_frame(frame=frame)
164 if marker is None:
165 continue
166 track.weight = 1.0
167 track.keyframe_insert("weight", frame=frame)
169 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
171 return{'FINISHED'}
174 class TRACKING_PT_RefineMotionTracking(Panel):
175 bl_label = "Refine solution"
176 bl_space_type = "CLIP_EDITOR"
177 bl_region_type = "TOOLS"
178 bl_category = "Solve"
180 @classmethod
181 def poll(cls, context):
182 return (context.area.spaces.active.clip is not None)
184 def draw(self, context):
185 layout = self.layout
187 col = layout.column(align=True)
188 col.prop(context.window_manager, "TrackingTargetError", text="Target error")
189 col.prop(context.window_manager, "TrackingSmooth", text="Smooth transition")
190 sub_box = col.box()
191 sub_box.scale_y = 0.25
193 row = col.row(align=True)
194 row.operator("tracking.refine_solution")
195 row.operator("tracking.reset_solution")
198 classes =(
199 TRACKING_OP_refine_solution,
200 TRACKING_OP_reset_solution,
201 TRACKING_PT_RefineMotionTracking
205 def register():
206 bpy.types.WindowManager.TrackingTargetError = FloatProperty(
207 name="Target Error",
208 description="Refine motion track target error",
209 default=0.3,
210 min=0.01
212 bpy.types.WindowManager.TrackingSmooth = FloatProperty(
213 name="Smooth Transition",
214 description="Smooth weight transition on start and end of incomplete tracks",
215 default=25,
216 min=1
218 for cls in classes:
219 bpy.utils.register_class(cls)
222 def unregister():
223 for cls in reversed(classes):
224 bpy.utils.unregister_class(cls)
225 del bpy.types.WindowManager.TrackingTargetError
226 del bpy.types.WindowManager.TrackingSmooth
229 if __name__ == "__main__":
230 register()