Cleanup: Node Wrangler: capitalize comments in AddReroutes operator
[blender-addons.git] / space_clip_editor_refine_solution.py
blob7e0719f378efd373545693acc438458f3be772f2
1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Refine tracking solution",
7 "author": "Stephen Leger",
8 "license": "GPL",
9 "version": (1, 1, 5),
10 "blender": (2, 80, 0),
11 "location": "Clip Editor > Tools > Solve > Refine Solution",
12 "description": "Refine motion solution by setting track weight according"
13 " to reprojection error",
14 "warning": "",
15 "doc_url": "{BLENDER_MANUAL_URL}/addons/video_tools/refine_tracking.html",
16 "category": "Video Tools",
19 import bpy
20 from bpy.types import (
21 Operator,
22 Panel,
24 from bpy.props import FloatProperty
25 from mathutils import Vector
28 class TRACKING_OP_refine_solution(Operator):
29 bl_idname = "tracking.refine_solution"
30 bl_label = "Refine"
31 bl_description = "Set track weight by error and solve camera motion"
32 bl_options = {"UNDO"}
34 @classmethod
35 def poll(cls, context):
36 return (context.area and context.area.spaces and
37 hasattr(context.area.spaces.active, 'clip') and
38 context.area.spaces.active.clip is not None
41 def execute(self, context):
42 error = context.window_manager.TrackingTargetError
43 smooth = context.window_manager.TrackingSmooth
44 clip = context.area.spaces.active.clip
45 try:
46 tracking = clip.tracking
47 tracks = tracking.tracks
48 winx = float(clip.size[0])
49 winy = float(clip.size[1])
50 aspy = 1.0 / tracking.camera.pixel_aspect
51 start = tracking.reconstruction.cameras[0].frame
52 end = tracking.reconstruction.cameras[-1].frame
53 except:
54 return {'CANCELLED'}
56 marker_position = Vector()
58 for frame in range(start, end):
59 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
60 if camera is not None:
61 camera_invert = camera.matrix.inverted()
62 else:
63 continue
65 for track in tracking.tracks:
66 marker = track.markers.find_frame(frame)
67 if marker is None:
68 continue
70 # weight incomplete tracks on start and end
71 if frame > start + smooth and frame < end - smooth:
72 for m in track.markers:
73 if not m.mute:
74 tstart = m
75 break
76 for m in reversed(track.markers):
77 if not m.mute:
78 tend = m
79 break
80 dt = min(0.5 * (tend.frame - tstart.frame), smooth)
81 if dt > 0:
82 t0 = min(1.0, (frame - tstart.frame) / dt)
83 t1 = min(1.0, (tend.frame - frame) / dt)
84 tw = min(t0, t1)
85 else:
86 tw = 0.0
87 else:
88 tw = 1.0
90 reprojected_position = camera_invert @ track.bundle
91 if reprojected_position.z == 0:
92 track.weight = 0
93 track.keyframe_insert("weight", frame=frame)
94 continue
95 reprojected_position = reprojected_position / -reprojected_position.z * \
96 tracking.camera.focal_length_pixels
97 reprojected_position = Vector(
98 (tracking.camera.principal_point_pixels[0] + reprojected_position[0],
99 tracking.camera.principal_point_pixels[1] * aspy + reprojected_position[1], 0)
102 marker_position[0] = (marker.co[0] + track.offset[0]) * winx
103 marker_position[1] = (marker.co[1] + track.offset[1]) * winy * aspy
105 dp = marker_position - reprojected_position
106 if dp.length == 0:
107 track.weight = 1.0
108 else:
109 track.weight = min(1.0, tw * error / dp.length)
110 track.keyframe_insert("weight", frame=frame)
112 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
114 return{'FINISHED'}
117 class TRACKING_OP_reset_solution(Operator):
118 bl_idname = "tracking.reset_solution"
119 bl_label = "Reset"
120 bl_description = "Reset track weight and solve camera motion"
121 bl_options = {"UNDO"}
123 @classmethod
124 def poll(cls, context):
125 return (context.area.spaces.active.clip is not None)
127 def execute(self, context):
128 clip = context.area.spaces.active.clip
129 try:
130 tracking = clip.tracking
131 tracks = tracking.tracks
132 start = tracking.reconstruction.cameras[0].frame
133 end = tracking.reconstruction.cameras[-1].frame
134 except:
135 return {'CANCELLED'}
137 start = tracking.reconstruction.cameras[0].frame
138 end = tracking.reconstruction.cameras[-1].frame
139 for frame in range(start, end):
140 camera = tracking.reconstruction.cameras.find_frame(frame=frame)
141 if camera is None:
142 continue
143 for track in tracking.tracks:
144 marker = track.markers.find_frame(frame=frame)
145 if marker is None:
146 continue
147 track.weight = 1.0
148 track.keyframe_insert("weight", frame=frame)
150 bpy.ops.clip.solve_camera('INVOKE_DEFAULT')
152 return{'FINISHED'}
155 class TRACKING_PT_RefineMotionTracking(Panel):
156 bl_label = "Refine solution"
157 bl_space_type = "CLIP_EDITOR"
158 bl_region_type = "TOOLS"
159 bl_category = "Solve"
161 @classmethod
162 def poll(cls, context):
163 return (context.area.spaces.active.clip is not None)
165 def draw(self, context):
166 layout = self.layout
168 col = layout.column(align=True)
169 col.prop(context.window_manager, "TrackingTargetError", text="Target error")
170 col.prop(context.window_manager, "TrackingSmooth", text="Smooth transition")
171 sub_box = col.box()
172 sub_box.scale_y = 0.25
174 row = col.row(align=True)
175 row.operator("tracking.refine_solution")
176 row.operator("tracking.reset_solution")
179 classes =(
180 TRACKING_OP_refine_solution,
181 TRACKING_OP_reset_solution,
182 TRACKING_PT_RefineMotionTracking
186 def register():
187 bpy.types.WindowManager.TrackingTargetError = FloatProperty(
188 name="Target Error",
189 description="Refine motion track target error",
190 default=0.3,
191 min=0.01
193 bpy.types.WindowManager.TrackingSmooth = FloatProperty(
194 name="Smooth Transition",
195 description="Smooth weight transition on start and end of incomplete tracks",
196 default=25,
197 min=1
199 for cls in classes:
200 bpy.utils.register_class(cls)
203 def unregister():
204 for cls in reversed(classes):
205 bpy.utils.unregister_class(cls)
206 del bpy.types.WindowManager.TrackingTargetError
207 del bpy.types.WindowManager.TrackingSmooth
210 if __name__ == "__main__":
211 register()