├── .gitignore
├── LICENSE.txt
├── README.rst
├── __init__.py
├── make_readme.py
└── operators
├── __init__.py
├── add_transform
├── __init__.py
└── add_transform.py
├── adjust_alpha
├── __init__.py
├── adjust_alpha.py
└── draw_alpha_controls.py
├── autocrop
├── __init__.py
└── autocrop.py
├── call_menu
├── __init__.py
├── call_menu.py
├── insert_keyframe.py
└── menu_insert_keyframe.py
├── crop
├── __init__.py
├── crop.py
├── crop_scale.py
├── draw_crop.py
├── get_perpendicular_point.py
├── set_corners.py
└── set_quads.py
├── delete
├── __init__.py
└── delete.py
├── duplicate
├── __init__.py
├── duplicate.py
└── get_vertical_translation.py
├── grab
├── __init__.py
└── grab.py
├── group
├── __init__.py
└── group.py
├── meta_toggle
├── __init__.py
└── meta_toggle.py
├── mouse_track
├── __init__.py
└── mouse_track.py
├── pixelate
├── __init__.py
├── draw_pixelate_controls.py
└── pixelate.py
├── rotate
├── __init__.py
├── apply_strip_rotation.py
└── rotate.py
├── scale
├── __init__.py
└── scale.py
├── select
├── __init__.py
└── select.py
├── set_cursor2d
├── __init__.py
├── get_important_edge_points.py
└── set_cursor2d.py
├── track_transform
├── __init__.py
└── track_transform.py
└── utils
├── __init__.py
├── draw
├── __init__.py
├── colors.py
├── draw_arrows.py
├── draw_axes.py
├── draw_line.py
├── draw_px_point.py
├── draw_snap.py
├── draw_square.py
└── draw_stippled_line.py
├── func_constrain_axis.py
├── func_constrain_axis_mmb.py
├── geometry
├── __init__.py
├── get_group_box.py
├── get_pos_x.py
├── get_pos_y.py
├── get_post_rot_bbox.py
├── get_preview_offset.py
├── get_res_factor.py
├── get_strip_box.py
├── get_strip_corners.py
├── get_transform_box.py
├── mouse_to_res.py
├── reposition_strip.py
├── reposition_transform_strip.py
├── rotate_point.py
├── set_pos_x.py
└── set_pos_y.py
├── process_input.py
└── selection
├── __init__.py
├── ensure_transforms.py
├── get_highest_transform.py
├── get_input_tree.py
├── get_nontransforms.py
├── get_transforms.py
└── get_visible_strips.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | _addon_linker.py
7 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | bl_info = {
2 | "name": "VSE Transform Tools",
3 | "description": "Quickly manipulate video strips in Blender's Video Sequence Editor",
4 | "author": "kgeogeo, DoubleZ, doakey3, NathanLovato",
5 | "version": (1, 2, 8),
6 | "blender": (2, 80, 0),
7 | "wiki_url": "https://github.com/doakey3/VSE_Transform_Tools",
8 | "tracker_url": "https://github.com/doakey3/VSE_Transform_Tools/issues",
9 | "category": "Sequencer"
10 | }
11 |
12 | """
13 | RegEx Classname
14 | ===============
15 | [A-Z][A-Z0-9_]*_{ABBREV}_[A-Za-z0-9_]+
16 |
17 | Abbrev
18 | ------
19 | Header: _HT_
20 | Menu: _MT_
21 | Operator: _OT_
22 | Panel: _PT_
23 | UIList: _UL_
24 | """
25 |
26 | import bpy
27 | from bpy.types import WorkSpaceTool
28 |
29 | from .operators import *
30 |
31 | handle_2d_cursor = None
32 |
33 | from .operators.utils.draw import draw_line
34 |
35 |
36 | def draw_callback_px_2d_cursor(self, context):
37 | c2d = context.region.view2d.view_to_region(
38 | context.scene.seq_cursor2d_loc[0],
39 | context.scene.seq_cursor2d_loc[1], clip=False)
40 |
41 | v1 = [c2d[0] - 5, c2d[1]]
42 | v2 = [c2d[0] + 5, c2d[1]]
43 |
44 | draw_line(v1, v2, 1, (1, 0, 0, 1))
45 |
46 | v1 = [c2d[0], c2d[1] - 5]
47 | v2 = [c2d[0], c2d[1] + 5]
48 |
49 | draw_line(v1, v2, 1, (1, 0, 0, 1))
50 |
51 |
52 | def Add_Icon_Pivot_Point(self, context):
53 | layout = self.layout
54 | layout.prop(
55 | context.scene, "seq_pivot_type", text='',
56 | expand=False, icon_only=True
57 | )
58 |
59 |
60 | def Add_Menu(self, context):
61 | layout = self.layout
62 | st = context.space_data
63 |
64 | if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}:
65 | layout.menu("SEQUENCER_MT_transform_tools_menu")
66 |
67 |
68 | def update_seq_cursor2d_loc(self, context):
69 | context.area.tag_redraw()
70 |
71 |
72 | def update_pivot_point(self, context):
73 | bpy.ops.vse_transform_tools.initialize_pivot()
74 |
75 |
76 | class PREV_OT_initialize_pivot(bpy.types.Operator):
77 | """
78 | The pivot icon won't show up if blender opens already on pivot type
79 | 2. This operator should be called whenever an action occurs on a
80 | strip.
81 |
82 | This has to be in the init file because of the global variable
83 | "handle_2d_cursor"
84 | """
85 | bl_idname = "vse_transform_tools.initialize_pivot"
86 | bl_label = "Make the pivot point appear if pivot styles is currently 2D cursor"
87 |
88 | @classmethod
89 | def poll(cls, context):
90 | ret = False
91 | if (context.scene.sequence_editor and
92 | context.space_data.type == 'SEQUENCE_EDITOR'):
93 | return True
94 | return False
95 |
96 | def execute(self, context):
97 | global handle_2d_cursor
98 | scene = context.scene
99 | args = (self, context)
100 |
101 | if scene.seq_pivot_type == '2' and not handle_2d_cursor:
102 | handle_2d_cursor = bpy.types.SpaceSequenceEditor.draw_handler_add(
103 | draw_callback_px_2d_cursor, args, 'PREVIEW', 'POST_PIXEL')
104 | elif not scene.seq_pivot_type == '2' and handle_2d_cursor:
105 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
106 | handle_2d_cursor, 'PREVIEW')
107 | handle_2d_cursor = None
108 |
109 | return {'FINISHED'}
110 |
111 |
112 | class SEQUENCER_MT_transform_tools_menu(bpy.types.Menu):
113 | bl_label = "Transform"
114 | bl_idname = "SEQUENCER_MT_transform_tools_menu"
115 |
116 | @classmethod
117 | def poll(cls, context):
118 | st = context.space_data
119 | return st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}
120 |
121 | def draw(self, context):
122 | layout = self.layout
123 | layout.operator_context = 'INVOKE_REGION_PREVIEW'
124 | #st = context.space_data
125 |
126 | #if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}:
127 |
128 | layout.operator("vse_transform_tools.add_transform")
129 |
130 | layout.separator()
131 |
132 | layout.operator("vse_transform_tools.grab")
133 | # layout.operator("vse_transform_tools.grab", 'G', 'PRESS', alt=True, shift=False)
134 | layout.operator("vse_transform_tools.scale")
135 | # layout.operator("vse_transform_tools.scale", 'S', 'PRESS', alt=True)
136 | layout.operator("vse_transform_tools.rotate")
137 | # layout.operator("vse_transform_tools.rotate", 'R', 'PRESS', alt=True)
138 |
139 | layout.separator()
140 |
141 | layout.operator("vse_transform_tools.crop")
142 | layout.operator("vse_transform_tools.autocrop")
143 | # layout.operator("vse_transform_tools.crop", 'C', 'PRESS', alt=True)
144 |
145 | layout.separator()
146 |
147 | layout.operator("vse_transform_tools.delete")
148 | # layout.operator("vse_transform_tools.delete", "DEL", "PRESS", shift=True)
149 | layout.operator("vse_transform_tools.duplicate")
150 |
151 | layout.separator()
152 |
153 | layout.operator("vse_transform_tools.call_menu", text="Insert Keyframe")
154 | layout.operator("vse_transform_tools.mouse_track")
155 |
156 | layout.separator()
157 |
158 | layout.operator("vse_transform_tools.adjust_alpha")
159 | # layout.operator("vse_transform_tools.adjust_alpha", 'Q', 'PRESS', alt=True)
160 | layout.operator("vse_transform_tools.pixelate")
161 |
162 | layout.separator()
163 |
164 | layout.operator("vse_transform_tools.group", text="Make Meta Strip")
165 | # layout.operator("vse_transform_tools.group", 'G', 'PRESS', ctrl=False, alt=True, shift=True)
166 | layout.operator("vse_transform_tools.meta_toggle")
167 |
168 | layout.operator_context = 'INVOKE_DEFAULT'
169 |
170 |
171 | class vse_transform_tools_select(WorkSpaceTool):
172 | bl_space_type='SEQUENCE_EDITOR'
173 | bl_context_mode='PREVIEW'
174 | bl_idname = "transform_tool.select"
175 | bl_label = "Select"
176 | bl_description = (
177 | "Move Strip in the Preview"
178 | )
179 | bl_icon = "ops.generic.select"
180 | bl_widget = None
181 | operator="transform.translate",
182 | bl_keymap = (
183 | ("vse_transform_tools.select", {"type": 'LEFTMOUSE', "value": 'PRESS'},
184 | {"properties": []}),
185 | ("vse_transform_tools.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
186 | {"properties": []}),
187 | ("vse_transform_tools.select", {"type": 'A', "value": 'PRESS'},
188 | {"properties": []}),
189 | )
190 |
191 | def draw_settings(context, layout, tool):
192 | tool.operator_properties("vse_transform_tools.select")
193 | scene = context.scene
194 | strip = scene.sequence_editor.active_strip
195 | if scene and strip and strip.type == 'TRANSFORM':
196 | layout.label(text=strip.name)
197 |
198 | class vse_transform_tools_grab(WorkSpaceTool):
199 | bl_space_type='SEQUENCE_EDITOR'
200 | bl_context_mode='PREVIEW'
201 | bl_idname = "transform_tool.grab"
202 | bl_label = "Move"
203 | bl_description = (
204 | "Move Strip in Preview"
205 | )
206 | bl_icon = "ops.transform.translate"
207 | bl_widget = None
208 | operator="transform.translate",
209 | bl_keymap = (
210 | ("vse_transform_tools.grab", {"type": 'LEFTMOUSE', "value": 'PRESS'},
211 | {"properties": []}),
212 | )
213 |
214 | @classmethod
215 | def poll(cls, context):
216 | if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
217 | return context.scene.sequence_editor.active_strip.type == 'TRANSFORM'
218 | else:
219 | return False
220 |
221 | def draw_settings(context, layout, tool):
222 | scene = context.scene
223 | strip = scene.sequence_editor.active_strip
224 | if scene and strip and strip.type == 'TRANSFORM':
225 | tool.operator_properties("vse_transform_tools.grab")
226 | layout.prop(strip, "interpolation")
227 | layout.prop(strip, "translation_unit")
228 | layout.prop(strip, "translate_start_x", text="X")
229 | layout.prop(strip, "translate_start_y", text="Y")
230 |
231 |
232 | class vse_transform_tools_rotate(WorkSpaceTool):
233 | bl_space_type='SEQUENCE_EDITOR'
234 | bl_context_mode='PREVIEW'
235 | bl_idname = "transform_tool.rotate"
236 | bl_label = "Rotate"
237 | bl_description = (
238 | "Rotate Strip in Preview"
239 | )
240 | bl_icon = "ops.transform.rotate"
241 | bl_widget = None
242 | operator="transform.translate",
243 | bl_keymap = (
244 | ("vse_transform_tools.rotate", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
245 | ("vse_transform_tools.rotate", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
246 | {"properties": []}),
247 | )
248 |
249 | @classmethod
250 | def poll(cls, context):
251 | if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
252 | return context.scene.sequence_editor.active_strip.type == 'TRANSFORM'
253 | else:
254 | return False
255 |
256 | def draw_settings(context, layout, tool):
257 | tool.operator_properties("vse_transform_tools.rotate")
258 | scene = context.scene
259 | strip = scene.sequence_editor.active_strip
260 | if scene and strip and strip.type == 'TRANSFORM':
261 | layout.prop(strip, "rotation_start", text="Rotation")
262 |
263 |
264 | class vse_transform_tools_scale(WorkSpaceTool):
265 | bl_space_type='SEQUENCE_EDITOR'
266 | bl_context_mode='PREVIEW'
267 | bl_idname = "transform_tool.scale"
268 | bl_label = "Scale"
269 | bl_description = (
270 | "Scale Strip in Preview"
271 | )
272 | bl_icon = "ops.transform.resize"
273 | bl_widget = None
274 | operator="transform.translate",
275 | bl_keymap = (
276 | ("vse_transform_tools.scale", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
277 | ("vse_transform_tools.scale", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
278 | {"properties": []}),
279 | )
280 |
281 | @classmethod
282 | def poll(cls, context):
283 | if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
284 | return context.scene.sequence_editor.active_strip.type == 'TRANSFORM'
285 | else:
286 | return False
287 |
288 | def draw_settings(context, layout, tool):
289 | tool.operator_properties("vse_transform_tools.scale")
290 | scene = context.scene
291 | strip = scene.sequence_editor.active_strip
292 | if scene and strip and strip.type == 'TRANSFORM':
293 | layout.prop(strip, "interpolation")
294 | layout.prop(strip, "translation_unit")
295 | layout.prop(strip, "scale_start_x", text="X")
296 | layout.prop(strip, "scale_start_y", text="Y")
297 |
298 |
299 | class vse_transform_tools_crop(WorkSpaceTool):
300 | bl_space_type='SEQUENCE_EDITOR'
301 | bl_context_mode='PREVIEW'
302 | bl_idname = "transform_tool.crop"
303 | bl_label = "Crop"
304 | bl_description = (
305 | "Crop Strip in Preview"
306 | )
307 | bl_icon = "ops.sequencer.blade"
308 | bl_widget = None
309 | operator="transform.translate",
310 | bl_keymap = (
311 | ("vse_transform_tools.crop", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
312 | ("vse_transform_tools.crop", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
313 | {"properties": []}),
314 | )
315 |
316 | @classmethod
317 | def poll(cls, context):
318 | if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
319 | return context.scene.sequence_editor.active_strip.type == 'TRANSFORM'
320 | else:
321 | return False
322 |
323 | def draw_settings(context, layout, tool):
324 | tool.operator_properties("vse_transform_tools.crop")
325 | scene = context.scene
326 | strip = scene.sequence_editor.active_strip
327 | if scene and strip and strip.type == 'TRANSFORM':
328 | layout.label(text=strip.name)
329 |
330 |
331 | class SEQUENCER_PT_track_transform_ui(bpy.types.Panel):
332 | bl_space_type = "SEQUENCE_EDITOR"
333 | bl_region_type = "UI"
334 | bl_label = "VSE_Transform_Tools"
335 | bl_options = {"DEFAULT_CLOSED"}
336 | bl_category = "Tools"
337 |
338 | @classmethod
339 | def poll(cls, context):
340 | return context.space_data.view_type == 'SEQUENCER'
341 |
342 | def draw(self, context):
343 | scene = context.scene
344 | layout = self.layout
345 |
346 | # TRANSFORM FROM 2D TRACK
347 | box = layout.box()
348 | row = box.row()
349 | row.label(text="Transform from 2D Track")
350 | row = box.row()
351 | row.prop(scene, "vse_transform_tools_use_rotation", text="Rotation")
352 | row.prop(scene, "vse_transform_tools_use_scale", text="Scale")
353 |
354 | row = box.row()
355 | row.prop(scene, "vse_transform_tools_tracker_1")
356 | row = box.row()
357 | row.prop(scene, "vse_transform_tools_tracker_2")
358 | if scene.vse_transform_tools_use_rotation or scene.vse_transform_tools_use_scale:
359 | row.enabled = True
360 | else:
361 | row.enabled = False
362 |
363 | row = box.row()
364 | row.operator("vse_transform_tools.track_transform")
365 |
366 |
367 | def get_tracker_list(self, context):
368 | tracks = [("None", "None", "")]
369 | for movieclip in bpy.data.movieclips:
370 | for track in movieclip.tracking.tracks:
371 | tracks.append((track.name, track.name, ""))
372 | return tracks
373 |
374 |
375 | def init_properties():
376 | bpy.types.Scene.seq_cursor2d_loc = bpy.props.IntVectorProperty(
377 | name="Scales",
378 | description="location of the cursor2d",
379 | subtype='XYZ',
380 | default=(50, 50),
381 | size=2,
382 | step=1,
383 | update=update_seq_cursor2d_loc
384 | )
385 |
386 | item_pivot_point = (
387 | ('0', 'Median Point', '', 'PIVOT_MEDIAN', 0),
388 | ('1', 'Individual Origins', '', 'PIVOT_BOUNDBOX', 1),
389 | ('2', '2D Cursor', '', 'PIVOT_CURSOR', 2),
390 | ('3', 'Active Strip', '', 'PIVOT_ACTIVE', 3)
391 | )
392 | bpy.types.Scene.seq_pivot_type = bpy.props.EnumProperty(
393 | name="Pivot Point",
394 | default="1",
395 | items=item_pivot_point,
396 | update=update_pivot_point
397 | )
398 |
399 | bpy.types.SEQUENCER_HT_header.append(Add_Icon_Pivot_Point)
400 |
401 | bpy.types.Scene.vse_transform_tools_use_rotation = bpy.props.BoolProperty(
402 | name="Rotation",
403 | default=True
404 | )
405 |
406 | bpy.types.Scene.vse_transform_tools_use_scale = bpy.props.BoolProperty(
407 | name="Scale",
408 | default=True
409 | )
410 |
411 | bpy.types.Scene.vse_transform_tools_tracker_1 = bpy.props.EnumProperty(
412 | name="Tracker 1",
413 | items=get_tracker_list
414 | )
415 |
416 | bpy.types.Scene.vse_transform_tools_tracker_2 = bpy.props.EnumProperty(
417 | name="Tracker 2",
418 | items=get_tracker_list
419 | )
420 |
421 | classes = [
422 | PREV_OT_initialize_pivot,
423 | PREV_OT_set_cursor_2d,
424 | PREV_OT_add_transform,
425 | PREV_OT_select,
426 | PREV_OT_grab,
427 | PREV_OT_scale,
428 | PREV_OT_rotate,
429 | PREV_OT_autocrop,
430 | PREV_OT_delete,
431 | PREV_OT_duplicate,
432 | PREV_OT_group,
433 | PREV_OT_meta_toggle,
434 | PREV_OT_adjust_alpha,
435 | PREV_OT_call_menu,
436 | PREV_OT_insert_keyframe,
437 | PREV_MT_menu_insert_keyframe,
438 | PREV_OT_pixelate,
439 | PREV_OT_mouse_track,
440 | PREV_OT_crop,
441 | SEQUENCER_MT_transform_tools_menu,
442 | SEQUENCER_OT_track_transform,
443 | SEQUENCER_PT_track_transform_ui
444 | ]
445 |
446 | addon_keymaps = []
447 |
448 | def register():
449 | from bpy.utils import register_class
450 | for cls in classes:
451 | register_class(cls)
452 |
453 | init_properties()
454 |
455 | wm = bpy.context.window_manager
456 | km = wm.keyconfigs.addon.keymaps.new(name="SequencerPreview", space_type="SEQUENCE_EDITOR", region_type="WINDOW")
457 |
458 | kmi = km.keymap_items.new("vse_transform_tools.add_transform", 'T', 'PRESS', shift=True)
459 |
460 | kmi = km.keymap_items.new("vse_transform_tools.grab", 'G', 'PRESS', alt=True, shift=False)
461 | kmi = km.keymap_items.new("vse_transform_tools.grab", 'G', 'PRESS')
462 |
463 | kmi = km.keymap_items.new("vse_transform_tools.scale", 'S', 'PRESS', alt=True)
464 | kmi = km.keymap_items.new("vse_transform_tools.scale", 'S', 'PRESS')
465 |
466 | kmi = km.keymap_items.new("vse_transform_tools.rotate", 'R', 'PRESS', alt=True)
467 | kmi = km.keymap_items.new("vse_transform_tools.rotate", 'R', 'PRESS')
468 |
469 | kmi = km.keymap_items.new("vse_transform_tools.autocrop", 'C', 'PRESS', shift=True)
470 |
471 | kmi = km.keymap_items.new("vse_transform_tools.crop", 'C', 'PRESS', alt=True)
472 | kmi = km.keymap_items.new("vse_transform_tools.crop", 'C', 'PRESS')
473 |
474 | kmi = km.keymap_items.new("vse_transform_tools.delete", "DEL", "PRESS", shift=True)
475 | kmi = km.keymap_items.new("vse_transform_tools.delete", "DEL", "PRESS")
476 |
477 | kmi = km.keymap_items.new("vse_transform_tools.duplicate", "D", 'PRESS', shift=True)
478 |
479 | kmi = km.keymap_items.new("vse_transform_tools.group", 'G', 'PRESS', ctrl=False, alt=True, shift=True)
480 | kmi = km.keymap_items.new("vse_transform_tools.group", 'G', 'PRESS', ctrl=True)
481 |
482 | kmi = km.keymap_items.new("vse_transform_tools.meta_toggle", "TAB", "PRESS")
483 |
484 | kmi = km.keymap_items.new("vse_transform_tools.adjust_alpha", 'Q', 'PRESS', alt=True)
485 | kmi = km.keymap_items.new("vse_transform_tools.adjust_alpha", 'Q', 'PRESS')
486 |
487 | kmi = km.keymap_items.new("vse_transform_tools.call_menu", 'I', 'PRESS')
488 |
489 | kmi = km.keymap_items.new("vse_transform_tools.pixelate", 'P', 'PRESS')
490 |
491 | kmi = km.keymap_items.new("vse_transform_tools.mouse_track", 'M', 'PRESS')
492 |
493 | #smb = bpy.data.window_managers["WinMan"].keyconfigs.active.preferences.select_mouse
494 | #smb = bpy.context.user_preferences.inputs.select_mouse
495 |
496 | smb = "RIGHT"
497 | kmi = km.keymap_items.new("vse_transform_tools.select", smb + 'MOUSE', 'PRESS')
498 | kmi = km.keymap_items.new("vse_transform_tools.select", smb + 'MOUSE', 'PRESS', shift=True)
499 | kmi = km.keymap_items.new("vse_transform_tools.select", 'A', 'PRESS')
500 |
501 | omb = "LEFT"
502 | kmi = km.keymap_items.new("vse_transform_tools.set_cursor2d", omb + 'MOUSE', 'PRESS', shift=True)
503 | kmi = km.keymap_items.new("vse_transform_tools.set_cursor2d", omb + 'MOUSE', 'PRESS', ctrl=True)
504 |
505 | addon_keymaps.append(km)
506 |
507 | bpy.utils.register_tool(vse_transform_tools_select, after={"builtin.sample"}, separator=True, group=False)
508 | bpy.utils.register_tool(vse_transform_tools_grab)
509 | bpy.utils.register_tool(vse_transform_tools_rotate)
510 | bpy.utils.register_tool(vse_transform_tools_scale)
511 | bpy.utils.register_tool(vse_transform_tools_crop)
512 |
513 | bpy.types.SEQUENCER_MT_editor_menus.append(Add_Menu)
514 |
515 | def unregister():
516 | from bpy.utils import unregister_class
517 | for cls in reversed(classes):
518 | unregister_class(cls)
519 |
520 | wm = bpy.context.window_manager
521 | for km in addon_keymaps:
522 | wm.keyconfigs.addon.keymaps.remove(km)
523 | addon_keymaps.clear()
524 |
525 | del bpy.types.Scene.seq_cursor2d_loc
526 | del bpy.types.Scene.seq_pivot_type
527 | bpy.types.SEQUENCER_HT_header.remove(Add_Icon_Pivot_Point)
528 | del bpy.types.Scene.vse_transform_tools_use_rotation
529 | del bpy.types.Scene.vse_transform_tools_use_scale
530 | del bpy.types.Scene.vse_transform_tools_tracker_1
531 | del bpy.types.Scene.vse_transform_tools_tracker_2
532 |
533 | bpy.utils.unregister_tool(vse_transform_tools_select)
534 | bpy.utils.unregister_tool(vse_transform_tools_grab)
535 | bpy.utils.unregister_tool(vse_transform_tools_rotate)
536 | bpy.utils.unregister_tool(vse_transform_tools_scale)
537 | bpy.utils.unregister_tool(vse_transform_tools_crop)
538 |
539 | bpy.types.SEQUENCER_MT_editor_menus.remove(Add_Menu)
540 |
--------------------------------------------------------------------------------
/make_readme.py:
--------------------------------------------------------------------------------
1 | import math
2 | from markdown2 import markdown
3 |
4 |
5 | def make_toc_label(label, description):
6 | """
7 | Make a table-of-contents item with a link to the operator and
8 | description for it's tooltip.
9 | """
10 | description = reflow_paragraph(description, 31)
11 |
12 | hla = label.replace(' ', '_')
13 | html_link = ''.join([
14 | '',
15 | label, ''])
16 | return html_link
17 |
18 |
19 | def reflow_paragraph(text, space, leading_space=''):
20 | '''
21 | Reflow a flattened paragraph so it fits inside horizontal
22 | space
23 | '''
24 | words = text.split(' ')
25 | growing_string = leading_space
26 | output_list = []
27 |
28 | while len(words) > 0:
29 | if growing_string == leading_space:
30 | growing_string += words[0]
31 | words.pop(0)
32 | elif len(growing_string + ' ' + words[0]) <= space:
33 | growing_string += ' ' + words[0]
34 | words.pop(0)
35 | else:
36 | output_list.append(growing_string + '\n')
37 | growing_string = leading_space
38 | output_list.append(growing_string)
39 | return ''.join(output_list)
40 |
41 |
42 | def make_toc(info):
43 | """
44 | Generate a table of contents from the operator info
45 | """
46 | toc = ['
']
47 |
48 | labels = []
49 |
50 | for key in sorted(info.keys()):
51 | label = info[key]['name']
52 | description = info[key]['description']
53 | labels.append(make_toc_label(label, description))
54 |
55 | columns = [[], [], [], []]
56 | row_count = math.ceil(len(labels) / len(columns))
57 |
58 | i = 0
59 | col = 0
60 | while i < len(labels):
61 | for x in range(row_count):
62 | try:
63 | columns[col].append(labels[i])
64 | i += 1
65 | except IndexError:
66 | break
67 | col += 1
68 |
69 | dead_column = False
70 | column_width = str(int((1 / len(columns)) * 888)) + 'px'
71 | for row in range(row_count):
72 | toc.append(' ')
73 |
74 | for col in range(len(columns)):
75 | try:
76 | toc.append(' ' + columns[col][row] + ' | ')
77 | except IndexError:
78 | if dead_column == False:
79 | remaining_rows = row_count - row
80 | toc.append(' | ')
81 | dead_column = True
82 | toc.append('
')
83 | toc.append('
')
84 |
85 | return '\n'.join(toc)
86 |
87 |
88 | def make_seg_label(label):
89 | """
90 | Make the title label for an operator segment with a link back to the
91 | matching table of contents label
92 | """
93 | hla = label.replace(' ', '_')
94 | seg_label = ''.join([
95 | ' ', ''])
97 | return seg_label
98 |
99 |
100 | def make_shortcuts_table(op_dict):
101 | """
102 | Make a table showing all the keyboard shortcuts, their functions,
103 | and a demo for a given operator
104 | """
105 | shortcuts = []
106 | functions = []
107 | demo = op_dict['demo']
108 |
109 | for i in range(len(op_dict['shortcuts'])):
110 | shortcuts.append(op_dict['shortcuts'][i].split(';')[0].strip())
111 | functions.append(op_dict['shortcuts'][i].split(';')[1].strip())
112 |
113 | for i in range(len(shortcuts)):
114 | hotkeys = shortcuts[i].split(' ')
115 | for x in range(len(hotkeys)):
116 | hotkeys[x] = '
'
117 | shortcuts[i] = ''.join(hotkeys)
118 |
119 | hotkeys_width = str(int((888 - 256) * 0.33)) + 'px'
120 | function_width = str(int((888 - 256) * 0.66)) + 'px'
121 | table = ['']
122 | table.append(' ')
123 | table.append(' Shortcut | ')
124 |
125 | if len(functions) > 0:
126 | table.append(' Function | ')
127 | if demo != '':
128 | table.append(' Demo | ')
129 |
130 | for i in range(len(shortcuts)):
131 | table.append('
')
132 |
133 | table.append(' ' + ''.join(shortcuts[i]) + ' | ')
134 | table.append(' ' + markdown(functions[i]) + ' | ')
135 |
136 | if i == 0 and demo != '':
137 | table.append(' ' + ' | ')
138 |
139 | table.append('
')
140 | table.append('
')
141 | return '\n'.join(table)
142 |
143 |
144 | def make_operator_segments(info):
145 | """
146 | Using the operator info, make segments that put all that info together.
147 | """
148 | segments = []
149 |
150 | for key in sorted(info.keys()):
151 | label = make_seg_label(info[key]['name'])
152 | description = markdown(info[key]['description'])
153 | shortcut_table = make_shortcuts_table(info[key])
154 |
155 | segments.append('\n'.join([label, description, shortcut_table]))
156 | return '\n'.join(segments)
157 |
158 |
159 | def make_readme():
160 | """
161 | Generate a nice-looking readme.
162 | """
163 |
164 | readme_path = 'README.rst'
165 |
166 | title = """
167 |
168 | VSE_Transform_Tools
169 |
170 | """
171 |
172 | intro = markdown("""
173 | ## Installation
174 | 1. Go to the [Releases](https://github.com/doakey3/VSE_Transform_Tools/releases) page and download the latest `VSE_Transform_Tools.zip`
175 | 2. Open Blender
176 | 3. Go to File > User Preferences > Addons
177 | 4. Click "Install From File" and navigate to the downloaded .zip file and install
178 | 5. Check the box next to "VSE Transform Tools"
179 | 6. Save User Settings so the addon remains active every time you open Blender
180 |
181 | Use the correct release for your Blender version. Add-ons for Blender 2.80 and above will not work for Blender 2.79
182 | """.strip(), extras=['cuddled_lists'])
183 |
184 | operator_info = {
185 | 'vse_transform_tools.add_transform': {
186 | 'name': 'Add Transform',
187 | 'description': "A transform modifier must be added to a strip before the strip can be scaled or rotated by this addon. If you're planning to make keyframes to adjust the scale or the rotation, ensure that you are modifying a transform strip by adding one with this operator.",
188 | 'shortcuts': ['T; Add Transform'],
189 | 'demo': 'https://i.imgur.com/v4racQW.gif',
190 | },
191 | 'vse_transform_tools.adjust_alpha': {
192 | 'name': 'Adjust Alpha',
193 | 'description': "",
194 | 'shortcuts': ['Q; Begin alpha adjusting', 'Ctrl; Round to nearest tenth', 'RIGHTMOUSE; Escape alpha adjust mode', 'LEFTMOUSE; Set alpha, end alpha adjust mode', 'RET; Set Alpha, end alpha adjust mode', 'ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE PERIOD; Set alpha to value entered'],
195 | 'demo': 'https://i.imgur.com/PNsjamH.gif',
196 | },
197 | 'vse_transform_tools.autocrop': {
198 | 'name': 'Autocrop',
199 | 'description': "Sets the scene resolution to fit all visible content in the preview window without changing strip sizes.",
200 | 'shortcuts': ['SHIFT C; Autocrop'],
201 | 'demo': 'https://i.imgur.com/IarxF14.gif',
202 | },
203 | 'vse_transform_tools.call_menu': {
204 | 'name': 'Call Menu',
205 | 'description': "Bring up the menu for inserting a keyframe. Alternatively, you may enable automatic keyframing.
",
206 | 'shortcuts': ['I; Call menu'],
207 | 'demo': 'https://i.imgur.com/9Cx6XKj.gif',
208 | },
209 | 'vse_transform_tools.crop': {
210 | 'name': 'Crop',
211 | 'description': "",
212 | 'shortcuts': ['C; Begin/Set cropping, adding a transform if needed', 'ESC; Escape crop mode', 'LEFTMOUSE; Click the handles to drag', 'RET; Set crop, end cropping', 'Alt C; Uncrop'],
213 | 'demo': 'https://i.imgur.com/k4r2alY.gif',
214 | },
215 | 'vse_transform_tools.delete': {
216 | 'name': 'Delete',
217 | 'description': "Deletes all selected strips as well as any strips that are inputs of those strips. For example, deleting a transform strip with this operator will also delete the strip it was transforming.",
218 | 'shortcuts': ['DEL; Delete', 'Shift DEL; Delete strips and remove any other strips in the timeline with the same source. For scene strips, the scenes themselves will also be deleted.'],
219 | 'demo': 'https://i.imgur.com/B0L7XoV.gif',
220 | },
221 | 'vse_transform_tools.duplicate': {
222 | 'name': 'Duplicate',
223 | 'description': "Duplicates all selected strips and any strips that are inputs of those strips. Calls the Grab operator immediately after duplicating.",
224 | 'shortcuts': ['Shift D; Duplicate'],
225 | 'demo': 'https://i.imgur.com/IJh7v3z.gif',
226 | },
227 | 'vse_transform_tools.grab': {
228 | 'name': 'Grab',
229 | 'description': "",
230 | 'shortcuts': ['G; Grab', 'Shift; Hold to enable fine tuning', 'Ctrl; Hold to enable snapping', 'RIGHTMOUSE; Escape grab mode', 'Esc; Escape grab mode', 'LEFTMOUSE; Set position, end grab mode', 'RET; Set position, end grab mode', 'ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE PERIOD; Set position by value entered', 'X Y; Constrain grabbing to the respective axis', 'MIDDLEMOUSE; Constrain grabbing to axis', 'ALT G; Set position to 0,0'],
231 | 'demo': 'https://i.imgur.com/yQCFI0s.gif',
232 | },
233 | 'vse_transform_tools.group': {
234 | 'name': 'Group',
235 | 'description': "",
236 | 'shortcuts': ['Ctrl G; Group together selected sequences', 'Alt Shift G; Ungroup selected meta strip'],
237 | 'demo': ''
238 | },
239 | 'vse_transform_tools.meta_toggle': {
240 | 'name': 'Meta Toggle',
241 | 'description': "Toggles the selected strip if it is a META. If the selected strip is not a meta, recursively checks inputs until a META strip is encountered and toggles it. If no META is found, this operator does nothing.",
242 | 'shortcuts': ['TAB; Meta toggle'],
243 | 'demo': 'https://i.imgur.com/ya0nEgV.gif',
244 | },
245 | 'vse_transform_tools.mouse_track': {
246 | 'name': 'Mouse Track',
247 | 'description': 'Select a transform strip or a strip with "image offset" enabled. Press Alt+A to play, hold M to continuously add keyframes to transform strip while tracking the position of the mouse.',
248 | 'shortcuts': ['M; Hold to add keyframes, release to stop'],
249 | 'demo': 'https://i.imgur.com/6091cqv.gif',
250 | },
251 | 'vse_transform_tools.pixelate': {
252 | 'name': 'Pixelate',
253 | 'description': "Pixelate a clip by adding 2 transform modifiers: 1 shrinking, 1 expanding.",
254 | 'shortcuts': ['P; Pixelate'],
255 | 'demo': 'https://i.imgur.com/u8nUPj6.gif',
256 | },
257 | 'vse_transform_tools.rotate': {
258 | 'name': 'Rotate',
259 | 'description': "",
260 | 'shortcuts': ['R; Begin rotating, adding transform if needed.', 'Shift; Hold to enable fine tuning', 'Ctrl; Hold to enable stepwise rotation', 'RIGHTMOUSE; Escape rotate mode', 'Esc; Escape rotate mode', 'LEFTMOUSE; Set rotation, end rotate mode', 'RET; Set rotation, end rotate mode', 'ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE PERIOD; Set rotation to value entered', 'ALT R; Set rotation to 0 degrees'],
261 | 'demo': 'https://i.imgur.com/3ru1Xl6.gif',
262 | },
263 | 'vse_transform_tools.scale': {
264 | 'name': 'Scale',
265 | 'description': "",
266 | 'shortcuts': ['S; Begin scaling, adding transform if needed.', 'Shift; hold to enable fine tuning', 'Ctrl; Hold to enable snapping', 'RIGHTMOUSE; Escape scaling mode', 'ESC; escape scaling mode', 'LEFTMOUSE; Set scale, end scaling mode', 'RET; Set scale, end scaling mode', 'ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE PERIOD; Set scale by value entered', 'X Y; Constrain scaling to respective axis', 'MIDDLEMOUSE; Constrain scaling to axis', 'Alt S; Unscale'],
267 | 'demo': 'https://i.imgur.com/oAxSEYB.gif',
268 | },
269 | 'vse_transform_tools.select': {
270 | 'name': 'Select',
271 | 'description': "",
272 | 'shortcuts': ['RIGHTMOUSE; Select visible strip', 'SHIFT; Enable multi selection', 'A; Toggle selection'],
273 | 'demo': 'https://i.imgur.com/EVzmMAm.gif',
274 | },
275 | 'vse_transform_tools.set_cursor2d': {
276 | 'name': 'Set Cursor 2D',
277 | 'description': "Set the pivot point (point of origin) location. This will affect how strips are rotated and scaled.",
278 | 'shortcuts': ['LEFTMOUSE; Cusor 2D to mouse position', 'Ctrl LEFTMOUSE; Snap cursor 2D to nearest strip corner or mid-point'],
279 | 'demo': 'https://i.imgur.com/1uTD9C1.gif',
280 | },
281 | 'vse_transform_tools.track_transform': {
282 | 'name': 'Track Transform',
283 | 'description': 'Use a pair of track points to pin a strip to another. The UI for this tool is located in the menu to the right of the sequencer in the Tools submenu. To pin rotation and/or scale, you must use 2 tracking points.
',
284 | 'shortcuts': [';'],
285 | 'demo': 'https://i.imgur.com/nWto3hH.gif',
286 | },
287 | }
288 |
289 | toc_title = "Operators
"
290 |
291 | table_of_contents = make_toc(operator_info)
292 |
293 | operator_segments = make_operator_segments(operator_info)
294 |
295 | html = '\n'.join([title, intro, toc_title, table_of_contents, operator_segments])
296 |
297 | lines = html.split('\n')
298 | for i in range(len(lines)):
299 | lines[i] = ' ' + lines[i]
300 |
301 | readme = '.. raw:: html\n\n' + '\n'.join(lines)
302 |
303 | with open(readme_path, 'w') as f:
304 | f.write(readme)
305 |
306 | if __name__ == "__main__":
307 | make_readme()
308 |
--------------------------------------------------------------------------------
/operators/__init__.py:
--------------------------------------------------------------------------------
1 | from .set_cursor2d import PREV_OT_set_cursor_2d
2 | from .add_transform import PREV_OT_add_transform
3 | from .select import PREV_OT_select
4 | from .grab import PREV_OT_grab
5 | from .scale import PREV_OT_scale
6 | from .rotate import PREV_OT_rotate
7 | from .autocrop import PREV_OT_autocrop
8 | from .delete import PREV_OT_delete
9 | from .duplicate import PREV_OT_duplicate
10 | from .group import PREV_OT_group
11 | from .meta_toggle import PREV_OT_meta_toggle
12 | from .adjust_alpha import PREV_OT_adjust_alpha
13 | from .call_menu import PREV_OT_call_menu
14 | from .call_menu import PREV_OT_insert_keyframe
15 | from .call_menu import PREV_MT_menu_insert_keyframe
16 | from .pixelate import PREV_OT_pixelate
17 | from .mouse_track import PREV_OT_mouse_track
18 | from .crop import PREV_OT_crop
19 | from .track_transform import SEQUENCER_OT_track_transform
20 |
--------------------------------------------------------------------------------
/operators/add_transform/__init__.py:
--------------------------------------------------------------------------------
1 | from .add_transform import PREV_OT_add_transform
2 |
--------------------------------------------------------------------------------
/operators/add_transform/add_transform.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from ..utils.geometry import get_transform_box
4 | from ..utils.geometry import get_strip_box
5 | from ..utils.selection import get_input_tree
6 |
7 | class PREV_OT_add_transform(bpy.types.Operator):
8 | """
9 | Adds a transform modifier to the selected strip(s)
10 | """
11 | bl_idname = "vse_transform_tools.add_transform"
12 | bl_label = "Add Transform"
13 | bl_description = "Add transform modifier to the selected strip(s)"
14 | bl_options = {'REGISTER', 'UNDO'}
15 |
16 | @classmethod
17 | def poll(cls, context):
18 | if context.scene.sequence_editor:
19 | return True
20 | return False
21 |
22 | def execute(self, context):
23 |
24 | scene = context.scene
25 |
26 | selected_strips = []
27 | for strip in context.selected_sequences:
28 | if not strip.type == 'SOUND':
29 | selected_strips.append(strip)
30 |
31 | for strip in selected_strips:
32 | strip.use_float = True
33 |
34 | bpy.ops.sequencer.select_all(action='DESELECT')
35 | scene.sequence_editor.active_strip = strip
36 | bpy.ops.sequencer.effect_strip_add(type="TRANSFORM")
37 |
38 | transform_strip = context.scene.sequence_editor.active_strip
39 | transform_strip.name = "[TR]-%s" % strip.name
40 |
41 | transform_strip.blend_type = 'ALPHA_OVER'
42 | transform_strip.blend_alpha = strip.blend_alpha
43 |
44 | tree = get_input_tree(transform_strip)[1::]
45 | for child in tree:
46 | child.mute = True
47 |
48 | if not strip.use_crop:
49 | strip.use_crop = True
50 | strip.crop.min_x = 0
51 | strip.crop.max_x = 0
52 | strip.crop.min_y = 0
53 | strip.crop.max_y = 0
54 |
55 | if strip.use_translation:
56 | if strip.type == 'TRANSFORM':
57 | left, right, bottom, top = get_transform_box(strip)
58 | else:
59 | left, right, bottom, top = get_strip_box(strip)
60 |
61 | width = right - left
62 | height = top - bottom
63 |
64 | res_x = context.scene.render.resolution_x
65 | res_y = context.scene.render.resolution_y
66 |
67 | ratio_x = width / res_x
68 | ratio_y = height / res_y
69 |
70 | transform_strip.scale_start_x = ratio_x
71 | transform_strip.scale_start_y = ratio_y
72 |
73 | offset_x = strip.transform.offset_x
74 | offset_y = strip.transform.offset_y
75 |
76 | flip_x = 1
77 | if strip.use_flip_x:
78 | flip_x = -1
79 |
80 | flip_y = 1
81 | if strip.use_flip_y:
82 | flip_y = -1
83 |
84 | pos_x = offset_x + (width / 2) - (res_x / 2)
85 | pos_x *= flip_x
86 |
87 | pos_y = offset_y + (height / 2) - (res_y / 2)
88 | pos_y *= flip_y
89 |
90 | if transform_strip.translation_unit == 'PERCENT':
91 | pos_x = (pos_x / res_x) * 100
92 | pos_y = (pos_y / res_y) * 100
93 |
94 | transform_strip.translate_start_x = pos_x
95 | transform_strip.translate_start_y = pos_y
96 |
97 | strip.use_translation = False
98 |
99 | return {'FINISHED'}
100 |
--------------------------------------------------------------------------------
/operators/adjust_alpha/__init__.py:
--------------------------------------------------------------------------------
1 | from .adjust_alpha import PREV_OT_adjust_alpha
2 |
--------------------------------------------------------------------------------
/operators/adjust_alpha/adjust_alpha.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from mathutils import Vector
4 |
5 | from .draw_alpha_controls import draw_alpha_controls
6 |
7 | from ..utils import process_input
8 |
9 |
10 | class PREV_OT_adjust_alpha(bpy.types.Operator):
11 | """
12 | 
13 | """
14 | bl_idname = "vse_transform_tools.adjust_alpha"
15 | bl_label = "Adjust Alpha"
16 | bl_description = "Adjust alpha (opacity) of strips in the Image Preview"
17 | bl_options = {'REGISTER', 'UNDO'}
18 |
19 | first_mouse = Vector((0, 0))
20 | pos = Vector((0, 0))
21 | alpha_init = 0
22 | fac = 0
23 | key_val = ''
24 |
25 | tab = []
26 |
27 | handle_alpha = None
28 |
29 | @classmethod
30 | def poll(cls, context):
31 | scene = context.scene
32 | if (scene.sequence_editor and
33 | scene.sequence_editor.active_strip and
34 | scene.sequence_editor.active_strip.select):
35 | return True
36 | return False
37 |
38 | def modal(self, context, event):
39 | context.area.tag_redraw()
40 | w = context.region.width
41 |
42 | mouse_x = event.mouse_region_x
43 | mouse_x += (self.alpha_init * w) / 5
44 | mouse_y = event.mouse_region_y
45 |
46 | self.pos = Vector((mouse_x, mouse_y))
47 | self.pos -= self.first_mouse
48 |
49 | if self.pos.x < 0:
50 | self.pos.x = 0
51 | if self.pos.x > w / 5:
52 | self.pos.x = w / 5
53 | self.fac = self.pos.x / (w / 5)
54 |
55 | process_input(self, event.type, event.value)
56 | if self.key_val != '':
57 | try:
58 | self.fac = abs(float(self.key_val))
59 | if self.fac > 1:
60 | self.fac = abs(float('0.' + self.key_val.replace('.', '')))
61 | self.pos.x = self.fac * (w / 5)
62 | except ValueError:
63 | pass
64 |
65 | precision = 3
66 | if event.ctrl:
67 | precision = 1
68 |
69 | self.fac = round(self.fac, precision)
70 | for strip in self.tab:
71 | strip.blend_alpha = self.fac
72 |
73 | if (event.type == 'LEFTMOUSE' or
74 | event.type == 'RET' or
75 | event.type == 'NUMPAD_ENTER'):
76 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
77 | self.handle_alpha, 'PREVIEW')
78 |
79 | scene = context.scene
80 | if scene.tool_settings.use_keyframe_insert_auto:
81 | for strip in self.tab:
82 | strip.keyframe_insert(data_path='blend_alpha')
83 |
84 | return {'FINISHED'}
85 |
86 | if event.type == 'ESC' or event.type == 'RIGHTMOUSE':
87 | for strip in self.tab:
88 | strip.blend_alpha = self.alpha_init
89 |
90 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
91 | self.handle_alpha, 'PREVIEW')
92 | return {'FINISHED'}
93 |
94 | return {'RUNNING_MODAL'}
95 |
96 | def invoke(self, context, event):
97 | if event.alt:
98 | for strip in context.selected_sequences:
99 | strip.blend_alpha = 1.0
100 | return {'FINISHED'}
101 |
102 | else:
103 | mouse_x = event.mouse_region_x
104 | mouse_y = event.mouse_region_y
105 | self.first_mouse = Vector((mouse_x, mouse_y))
106 |
107 | opacities = []
108 |
109 | selected_strips = []
110 | for strip in context.selected_sequences:
111 | if not strip.type == 'SOUND':
112 | selected_strips.append(strip)
113 |
114 | for strip in selected_strips:
115 | self.tab.append(strip)
116 | opacities.append(strip.blend_alpha)
117 |
118 | self.alpha_init = max(opacities)
119 |
120 | self.key_val != ''
121 |
122 | args = (self, context)
123 | self.handle_alpha = bpy.types.SpaceSequenceEditor.draw_handler_add(
124 | draw_alpha_controls, args, 'PREVIEW', 'POST_PIXEL')
125 | context.window_manager.modal_handler_add(self)
126 |
127 | return {'RUNNING_MODAL'}
128 |
--------------------------------------------------------------------------------
/operators/adjust_alpha/draw_alpha_controls.py:
--------------------------------------------------------------------------------
1 | from ..utils.draw import draw_line
2 | from ..utils.draw import draw_square
3 |
4 | import blf
5 |
6 | def draw_alpha_controls(self, context):
7 | """
8 | Draws the line, 2 boxes, and the control square
9 | """
10 | w = context.region.width
11 | h = context.region.height
12 | line_width = 2 * (w / 10)
13 | offset_x = (line_width / 2) - (line_width * self.alpha_init)
14 | x = self.first_mouse.x + offset_x
15 | y = self.first_mouse.y + self.pos.y
16 |
17 | v1 = [(-w / 10) + x, y]
18 | v2 = [(w / 10) + x, y]
19 |
20 | color = (0, 0.75, 1, 1)
21 |
22 | draw_line(v1, v2, 1, color)
23 |
24 | vertex = [x - (w / 10) + self.pos.x, y]
25 | draw_square(vertex, 10, color)
26 |
27 | # Numbers
28 | font_id = 0
29 | blf.position(font_id, vertex[0] - 20, vertex[1] + 10, 0)
30 | blf.size(font_id, 20, 72)
31 | blf.draw(font_id, str(self.fac))
32 |
--------------------------------------------------------------------------------
/operators/autocrop/__init__.py:
--------------------------------------------------------------------------------
1 | from .autocrop import PREV_OT_autocrop
2 |
--------------------------------------------------------------------------------
/operators/autocrop/autocrop.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 |
4 | from ..utils.selection import get_visible_strips
5 | from ..utils.selection import get_transforms
6 | from ..utils.selection import get_nontransforms
7 |
8 | from ..utils.geometry import get_group_box
9 | from ..utils.geometry import reposition_strip
10 | from ..utils.geometry import reposition_transform_strip
11 |
12 |
13 | class PREV_OT_autocrop(bpy.types.Operator):
14 | """
15 | Sets the scene resolution to fit all visible content in
16 | the preview window without changing strip sizes.
17 | """
18 | bl_idname = "vse_transform_tools.autocrop"
19 | bl_label = "Autocrop"
20 | bl_description = "Collapse canvas to fit visible content"
21 | bl_options = {'REGISTER', 'UNDO'}
22 |
23 | @classmethod
24 | def poll(cls, context):
25 | scene = context.scene
26 | if scene.sequence_editor:
27 | return True
28 | return False
29 |
30 | def execute(self, context):
31 | scene = context.scene
32 |
33 | strips = get_visible_strips()
34 |
35 | if len(strips) == 0:
36 | return {'FINISHED'}
37 |
38 | group_box = get_group_box(strips)
39 |
40 | #all_strips = scene.sequence_editor.sequences_all
41 | #inputs = []
42 | #for strip in all_strips:
43 | # if hasattr(strip, "input_1"):
44 | # inputs.append(strip.input_1)
45 | # if hasattr(strip, "input_2"):
46 | # inputs.append(strip.input_2)
47 |
48 | #parents = []
49 | #for strip in all_strips:
50 | # if not strip in inputs and not strip.type == "SOUND":
51 | # parents.append(strip)
52 |
53 | min_left, max_right, min_bottom, max_top = group_box
54 |
55 | total_width = max_right - min_left
56 | total_height = max_top - min_bottom
57 |
58 | nontransforms = get_nontransforms(strips)
59 | for strip in nontransforms:
60 | reposition_strip(strip, group_box)
61 |
62 | transforms = get_transforms(strips)
63 | for strip in transforms:
64 | reposition_transform_strip(strip, group_box)
65 |
66 | scene.render.resolution_x = total_width
67 | scene.render.resolution_y = total_height
68 |
69 | return {'FINISHED'}
70 |
--------------------------------------------------------------------------------
/operators/call_menu/__init__.py:
--------------------------------------------------------------------------------
1 | from .call_menu import PREV_OT_call_menu
2 | from .insert_keyframe import PREV_OT_insert_keyframe
3 | from .menu_insert_keyframe import PREV_MT_menu_insert_keyframe
4 |
--------------------------------------------------------------------------------
/operators/call_menu/call_menu.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | class PREV_OT_call_menu(bpy.types.Operator):
5 | """
6 | 
7 |
8 | You may also enable automatic keyframe insertion.
9 |
10 | 
11 | """
12 | bl_idname = "vse_transform_tools.call_menu"
13 | bl_label = "Call Menu"
14 | bl_description = "Open keyframe insertion menu"
15 |
16 | @classmethod
17 | def poll(cls, context):
18 | if (context.scene.sequence_editor and
19 | context.space_data.type == 'SEQUENCE_EDITOR' and
20 | context.region.type == 'PREVIEW'):
21 | return True
22 | return False
23 |
24 | def execute(self, context):
25 | bpy.ops.wm.call_menu(name="VSE_MT_Insert_keyframe_Menu")
26 | return {'FINISHED'}
27 |
--------------------------------------------------------------------------------
/operators/call_menu/insert_keyframe.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | class PREV_OT_insert_keyframe(bpy.types.Operator):
5 | bl_idname = "vse_transform_tools.insert_keyframe"
6 | bl_label = "Transform Insert KeyFrame"
7 | bl_options = {'REGISTER', 'UNDO'}
8 |
9 | ch: bpy.props.IntVectorProperty(
10 | name="ch",
11 | default=(0, 0, 0, 0, 0),
12 | size=5
13 | )
14 |
15 | @classmethod
16 | def poll(cls, context):
17 | ret = False
18 | if context.scene.sequence_editor:
19 | ret = True
20 | return ret and context.space_data.type == 'SEQUENCE_EDITOR'
21 |
22 | def execute(self, context):
23 | for seq in context.scene.sequence_editor.sequences:
24 | if seq.select and seq.type == 'TRANSFORM':
25 | if self.ch[0] == 1:
26 | seq.keyframe_insert(
27 | data_path="translate_start_x")
28 | seq.keyframe_insert(
29 | data_path="translate_start_y")
30 |
31 | if self.ch[1] == 1:
32 | seq.keyframe_insert(
33 | data_path="rotation_start")
34 |
35 | if self.ch[2] == 1:
36 | seq.keyframe_insert(
37 | data_path="scale_start_x")
38 | seq.keyframe_insert(
39 | data_path="scale_start_y")
40 |
41 | if self.ch[3] == 1:
42 | seq.keyframe_insert(
43 | data_path="blend_alpha")
44 |
45 | if self.ch[4] == 1 and seq.input_1.use_crop:
46 | seq.input_1.crop.keyframe_insert(
47 | data_path="min_x")
48 | seq.input_1.crop.keyframe_insert(
49 | data_path="max_x")
50 | seq.input_1.crop.keyframe_insert(
51 | data_path="min_y")
52 | seq.input_1.crop.keyframe_insert(
53 | data_path="max_y")
54 |
55 | elif seq.select and not seq.type == "SOUND":
56 | if self.ch[0] == 1 and seq.use_translation:
57 | seq.transform.keyframe_insert(data_path="offset_x")
58 | seq.transform.keyframe_insert(data_path="offset_y")
59 |
60 | if self.ch[4] == 1 and seq.use_crop:
61 | seq.transform.keyframe_insert(data_path="offset_x")
62 | seq.transform.keyframe_insert(data_path="offset_y")
63 |
64 | seq.crop.keyframe_insert(
65 | data_path="min_x")
66 | seq.crop.keyframe_insert(
67 | data_path="max_x")
68 | seq.crop.keyframe_insert(
69 | data_path="min_y")
70 | seq.crop.keyframe_insert(
71 | data_path="max_y")
72 |
73 | if self.ch[3] == 1:
74 | seq.keyframe_insert(
75 | data_path="blend_alpha", frame=cf)
76 |
77 | # Apparently redrawing is bad...
78 | # bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
79 | # https://docs.blender.org/api/blender_python_api_2_78_release/info_gotcha.html
80 | # So instead, we can update blender like this:
81 | scene = context.scene
82 | scrubbing = False
83 | if scene.use_audio_scrub:
84 | scrubbing = True
85 | scene.use_audio_scrub = False
86 |
87 | context.scene.frame_current += 1
88 | context.scene.frame_current -= 1
89 |
90 | if scrubbing:
91 | scene.use_audio_scrub = True
92 |
93 | return {'FINISHED'}
94 |
--------------------------------------------------------------------------------
/operators/call_menu/menu_insert_keyframe.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | class PREV_MT_menu_insert_keyframe(bpy.types.Menu):
5 | bl_label = "Insert KeyFrame Menu"
6 | bl_idname = "VSE_MT_Insert_keyframe_Menu"
7 |
8 | def draw(self, context):
9 | types = []
10 | use_translations = []
11 | use_crops = []
12 | for strip in bpy.context.selected_sequences:
13 | types.append(strip.type)
14 | use_translations.append(strip.use_translation)
15 | use_crops.append(strip.use_crop)
16 |
17 | layout = self.layout
18 |
19 | if "TRANSFORM" in types:
20 | layout.operator("vse_transform_tools.insert_keyframe",
21 | text="Location").ch = (1, 0, 0, 0, 0)
22 |
23 | layout.operator("vse_transform_tools.insert_keyframe",
24 | text="Rotation").ch = (0, 1, 0, 0, 0)
25 |
26 | layout.operator("vse_transform_tools.insert_keyframe",
27 | text="Scale").ch = (0, 0, 1, 0, 0)
28 |
29 | layout.operator("vse_transform_tools.insert_keyframe",
30 | text="LocRot").ch = (1, 1, 0, 0, 0)
31 |
32 | layout.operator("vse_transform_tools.insert_keyframe",
33 | text="LocScale").ch =(1, 0, 1, 0, 0)
34 |
35 | layout.operator("vse_transform_tools.insert_keyframe",
36 | text="RotScale").ch = (0, 1, 1, 0, 0)
37 |
38 | layout.operator("vse_transform_tools.insert_keyframe",
39 | text="LocRotScale").ch = (1, 1, 1, 0, 0)
40 |
41 | layout.separator()
42 |
43 | layout.operator("vse_transform_tools.insert_keyframe",
44 | text="Crop").ch = (0, 0, 0, 0, 1)
45 |
46 | layout.separator()
47 |
48 | if not all(elem == "SOUND" for elem in types):
49 | if True in use_translations:
50 | layout.operator("vse_transform_tools.insert_keyframe",
51 | text="Location").ch = (1, 0, 0, 0, 0)
52 |
53 | layout.operator("vse_transform_tools.insert_keyframe",
54 | text="Alpha").ch = (0, 0, 0, 1, 0)
55 |
56 | if True in use_crops:
57 | layout.operator("vse_transform_tools.insert_keyframe",
58 | text="Crop").ch = (0, 0, 0, 0, 1)
59 |
60 | if "TRANSFORM" in types:
61 | layout.separator()
62 |
63 | layout.operator("vse_transform_tools.insert_keyframe",
64 | text="All").ch = (1, 1, 1, 1, 1)
65 |
--------------------------------------------------------------------------------
/operators/crop/__init__.py:
--------------------------------------------------------------------------------
1 | from .crop import PREV_OT_crop
2 |
--------------------------------------------------------------------------------
/operators/crop/crop.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import copy
3 |
4 | import math
5 | from mathutils import Vector
6 | from mathutils.geometry import intersect_point_quad_2d
7 |
8 | from ..utils.geometry import get_preview_offset
9 | from ..utils.geometry import rotate_point
10 |
11 | from ..utils.selection import get_highest_transform
12 |
13 | from .draw_crop import draw_crop
14 | from .crop_scale import crop_scale
15 |
16 |
17 | class PREV_OT_crop(bpy.types.Operator):
18 | """
19 | Crop the active strip
20 | """
21 | bl_idname = "vse_transform_tools.crop"
22 | bl_label = "Crop"
23 | bl_description = "Crop a strip in the Image Preview"
24 | bl_options = {'REGISTER', 'UNDO'}
25 |
26 | init_pos_x = 0
27 | init_pos_y = 0
28 |
29 | mouse_pos = Vector([-1, -1])
30 | current_mouse = Vector([-1, -1])
31 |
32 | corners = [Vector([-1, -1]), Vector([-1, -1]), Vector([-1, -1]),
33 | Vector([-1, -1])]
34 | max_corners = [Vector([-1, -1]), Vector([-1, -1]), Vector([-1, -1]),
35 | Vector([-1, -1])]
36 | corner_quads = []
37 |
38 | clicked_quad = None
39 |
40 | handle_crop = None
41 |
42 | crop_left = 0
43 | crop_right = 0
44 | crop_bottom = 0
45 | crop_top = 0
46 |
47 | scale_factor_x = 1.0
48 | scale_factor_y = 1.0
49 |
50 | @classmethod
51 | def poll(cls, context):
52 | scene = context.scene
53 | if (scene.sequence_editor and
54 | scene.sequence_editor.active_strip and
55 | scene.sequence_editor.active_strip.select):
56 | return True
57 | return False
58 |
59 |
60 | def modal(self, context, event):
61 | context.area.tag_redraw()
62 |
63 | if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
64 | mouse_x = event.mouse_region_x
65 | mouse_y = event.mouse_region_y
66 |
67 | self.mouse_pos = Vector([mouse_x, mouse_y])
68 | self.current_mouse = Vector([mouse_x, mouse_y])
69 |
70 | for i in range(len(self.corner_quads)):
71 | quad = self.corner_quads[i]
72 |
73 | bl = quad[0]
74 | tl = quad[1]
75 | tr = quad[2]
76 | br = quad[3]
77 |
78 | intersects = intersect_point_quad_2d(
79 | self.mouse_pos, bl, tl, tr, br)
80 |
81 | if intersects:
82 | self.clicked_quad = i
83 |
84 | elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
85 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
86 |
87 | active_strip = context.scene.sequence_editor.active_strip
88 | if active_strip.type == "TRANSFORM":
89 | angle = math.radians(active_strip.rotation_start)
90 | else:
91 | angle = 0
92 |
93 | origin = self.max_corners[2] - self.max_corners[0]
94 |
95 | bl = rotate_point(self.max_corners[0], -angle, origin)
96 | tl = rotate_point(self.max_corners[1], -angle, origin)
97 | tr = rotate_point(self.max_corners[2], -angle, origin)
98 | br = rotate_point(self.max_corners[3], -angle, origin)
99 |
100 | bl_current = rotate_point(self.corners[0], -angle, origin)
101 | tl_current = rotate_point(self.corners[1], -angle, origin)
102 | tr_current = rotate_point(self.corners[2], -angle, origin)
103 | br_current = rotate_point(self.corners[3], -angle, origin)
104 |
105 | if self.clicked_quad is not None:
106 | for i in range(len(self.corners)):
107 | if i == 0:
108 | self.crop_left = bl_current.x - bl.x
109 | self.crop_left /= preview_zoom * fac
110 |
111 | self.crop_bottom = bl_current.y - bl.y
112 | self.crop_bottom /= preview_zoom * fac
113 | elif i == 1:
114 | self.crop_left = tl_current.x - tl.x
115 | self.crop_left /= preview_zoom * fac
116 |
117 | self.crop_top = tl.y - tl_current.y
118 | self.crop_top /= preview_zoom * fac
119 |
120 | elif i == 2:
121 | self.crop_right = tr.x - tr_current.x
122 | self.crop_right /= preview_zoom * fac
123 |
124 | self.crop_top = tr.y - tr_current.y
125 | self.crop_top /= preview_zoom * fac
126 |
127 | elif i == 3:
128 | self.crop_right = br.x - br_current.x
129 | self.crop_right /= preview_zoom * fac
130 |
131 | self.crop_bottom = br_current.y - br.y
132 | self.crop_bottom /= preview_zoom * fac
133 |
134 | self.clicked_quad = None
135 | self.mouse_pos = Vector([-1, -1])
136 |
137 | elif event.type == 'MOUSEMOVE' and self.clicked_quad is not None:
138 | mouse_x = event.mouse_region_x
139 | mouse_y = event.mouse_region_y
140 | self.current_mouse = Vector([mouse_x, mouse_y])
141 |
142 | elif event.type in ['MIDDLEMOUSE', 'WHEELDOWNMOUSE',
143 | 'WHEELUPMOUSE', 'RIGHT_ARROW', 'LEFT_ARROW']:
144 | return {'PASS_THROUGH'}
145 |
146 | elif event.type in ['C', 'RET', 'NUMPAD_ENTER'] and event.value == 'PRESS':
147 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
148 |
149 | active_strip = context.scene.sequence_editor.active_strip
150 | crops = [self.crop_left / self.scale_factor_x,
151 | self.crop_right / self.scale_factor_x,
152 | self.crop_bottom / self.scale_factor_y,
153 | self.crop_top / self.scale_factor_y,
154 | ]
155 |
156 | scene = context.scene
157 |
158 | if active_strip.type == "TRANSFORM":
159 | crop_scale(self, active_strip, crops)
160 |
161 | strip_in = active_strip.input_1
162 |
163 | if scene.tool_settings.use_keyframe_insert_auto:
164 | cf = context.scene.frame_current
165 | active_strip.keyframe_insert(data_path='translate_start_x')
166 | active_strip.keyframe_insert(data_path='translate_start_y')
167 | active_strip.keyframe_insert(data_path='scale_start_x')
168 | active_strip.keyframe_insert(data_path='scale_start_y')
169 |
170 | strip_in.crop.keyframe_insert(data_path='min_x')
171 | strip_in.crop.keyframe_insert(data_path='max_x')
172 | strip_in.crop.keyframe_insert(data_path='min_y')
173 | strip_in.crop.keyframe_insert(data_path='max_y')
174 |
175 | else:
176 | active_strip.use_crop = True
177 | active_strip.crop.min_x = crops[0]
178 | active_strip.crop.max_x = crops[1]
179 | active_strip.crop.min_y = crops[2]
180 | active_strip.crop.max_y = crops[3]
181 |
182 | active_strip.transform.offset_x = self.init_pos_x - self.init_crop_left + crops[0]
183 | active_strip.transform.offset_y = self.init_pos_y - self.init_crop_bottom + crops[2]
184 |
185 | if scene.tool_settings.use_keyframe_insert_auto:
186 | active_strip.crop.keyframe_insert(data_path='min_x')
187 | active_strip.crop.keyframe_insert(data_path='max_x')
188 | active_strip.crop.keyframe_insert(data_path='min_y')
189 | active_strip.crop.keyframe_insert(data_path='max_y')
190 |
191 | active_strip.transform.keyframe_insert(data_path="offset_x")
192 | active_strip.transform.keyframe_insert(data_path="offset_y")
193 |
194 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
195 | self.handle_crop, 'PREVIEW')
196 | return {'FINISHED'}
197 |
198 | elif (event.alt and event.type == 'C') or event.type == 'ESC':
199 | active_strip = context.scene.sequence_editor.active_strip
200 | crops = [self.init_crop_left, self.init_crop_right,
201 | self.init_crop_bottom, self.init_crop_top]
202 |
203 | if active_strip.type == "TRANSFORM":
204 | crop_scale(self, active_strip, crops)
205 |
206 | else:
207 | active_strip.use_crop = True
208 | active_strip.crop.min_x = crops[0]
209 | active_strip.crop.max_x = crops[1]
210 | active_strip.crop.min_y = crops[2]
211 | active_strip.crop.max_y = crops[3]
212 |
213 | active_strip.transform.offset_x = crops[0]
214 | active_strip.transform.offset_y = crops[2]
215 |
216 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
217 | self.handle_crop, 'PREVIEW')
218 | return {'FINISHED'}
219 |
220 | return {'RUNNING_MODAL'}
221 |
222 | def invoke(self, context, event):
223 | scene = context.scene
224 |
225 | res_x = scene.render.resolution_x
226 | res_y = scene.render.resolution_y
227 |
228 | strip = get_highest_transform(scene.sequence_editor.active_strip)
229 | scene.sequence_editor.active_strip = strip
230 |
231 | if not strip.type == "TRANSFORM" and not strip.use_translation:
232 | bpy.ops.vse_transform_tools.add_transform()
233 | strip.select = False
234 | strip = scene.sequence_editor.active_strip
235 |
236 | elif not strip.type == "TRANSFORM" and strip.use_translation:
237 |
238 | strip.use_translation = True
239 | strip.use_crop = True
240 |
241 | self.init_crop_left = strip.crop.min_x
242 | self.init_crop_right = strip.crop.max_x
243 | self.init_crop_bottom = strip.crop.min_y
244 | self.init_crop_top = strip.crop.max_y
245 |
246 | self.init_pos_x = strip.transform.offset_x
247 | self.init_pos_y = strip.transform.offset_y
248 |
249 | self.crop_left = strip.crop.min_x
250 | self.crop_right = strip.crop.max_x
251 | self.crop_bottom = strip.crop.min_y
252 | self.crop_top = strip.crop.max_y
253 |
254 | strip.crop.min_x = 0
255 | strip.crop.max_x = 0
256 | strip.crop.min_y = 0
257 | strip.crop.max_y = 0
258 |
259 | strip.transform.offset_x -= self.init_crop_left
260 | strip.transform.offset_y -= self.init_crop_bottom
261 |
262 | if event.alt:
263 | return {'FINISHED'}
264 |
265 | if strip.type == "TRANSFORM":
266 | strip_in = strip.input_1
267 |
268 | if not strip_in.use_crop:
269 | strip_in.use_crop = True
270 |
271 | strip_in.crop.min_x = 0
272 | strip_in.crop.max_x = 0
273 | strip_in.crop.min_y = 0
274 | strip_in.crop.max_y = 0
275 |
276 | if event.alt:
277 | crop_scale(self, strip, [0, 0, 0, 0])
278 | return {'FINISHED'}
279 |
280 | crop_scale(self, strip, [0, 0, 0, 0])
281 |
282 | args = (self, context)
283 | self.handle_crop = bpy.types.SpaceSequenceEditor.draw_handler_add(
284 | draw_crop, args, 'PREVIEW', 'POST_PIXEL')
285 | context.window_manager.modal_handler_add(self)
286 |
287 | return {'RUNNING_MODAL'}
288 |
--------------------------------------------------------------------------------
/operators/crop/crop_scale.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 |
4 | from ..utils.geometry import get_transform_box
5 | from ..utils.geometry import get_pos_x
6 | from ..utils.geometry import get_pos_y
7 | from ..utils.geometry import set_pos_x
8 | from ..utils.geometry import set_pos_y
9 | from ..utils.geometry import get_preview_offset
10 |
11 |
12 | def crop_scale(self, strip, crops):
13 | """
14 | Set the strip_in crop and the strip's scale and position.
15 |
16 | When cropping, the transform strip's position and scale must change
17 | along with the crop it's input in order to keep the same location in
18 | the preview window.
19 |
20 | Parameters
21 | ----------
22 | strip : bpy.types.Sequence
23 | This is the transform strip that will be cropped
24 | crops : list of int
25 | Left, right, bottom, top crops to apply to the strip
26 | """
27 | context = bpy.context
28 | scene = context.scene
29 |
30 | res_x = scene.render.resolution_x
31 | res_y = scene.render.resolution_y
32 |
33 | left, right, bottom, top = get_transform_box(strip)
34 | width = right - left
35 | height = top - bottom
36 |
37 | strip_in = strip.input_1
38 |
39 | crop_left, crop_right, crop_bottom, crop_top = crops
40 |
41 | rot = math.radians(strip.rotation_start)
42 |
43 | cos = math.cos(rot)
44 | sin = math.sin(rot)
45 |
46 | crop_xl = strip_in.crop.min_x
47 | crop_xr = strip_in.crop.max_x
48 | crop_yb = strip_in.crop.min_y
49 | crop_yt = strip_in.crop.max_y
50 |
51 | crop_adjust_l = crop_left - crop_xl
52 | crop_adjust_r = crop_right - crop_xr
53 | crop_adjust_b = crop_bottom - crop_yb
54 | crop_adjust_t = crop_top - crop_yt
55 |
56 | proxy_facs = {
57 | 'NONE': 1.0,
58 | 'SCENE': 1.0,
59 | 'FULL': 1.0,
60 | 'PROXY_100': 1.0,
61 | 'PROXY_75': 0.75,
62 | 'PROXY_50': 0.5,
63 | 'PROXY_25': 0.25
64 | }
65 |
66 | proxy_key = context.space_data.proxy_render_size
67 | proxy_fac = proxy_facs[proxy_key]
68 |
69 | orig_width = res_x
70 | orig_height = res_y
71 | if hasattr(strip_in, 'elements'):
72 | orig_width = strip_in.elements[0].orig_width
73 | orig_height = strip_in.elements[0].orig_height
74 |
75 | if not (strip_in.type == 'IMAGE' and proxy_fac < 1.0):
76 | orig_width /= proxy_fac
77 | orig_height /= proxy_fac
78 |
79 | elif strip_in.type == "SCENE":
80 | strip_scene = bpy.data.scenes[strip_in.name]
81 |
82 | orig_width = strip_scene.render.resolution_x
83 | orig_height = strip_scene.render.resolution_y
84 |
85 | strip_in.crop.min_x = crop_left
86 | strip_in.crop.max_x = crop_right
87 | strip_in.crop.min_y = crop_bottom
88 | strip_in.crop.max_y = crop_top
89 |
90 | # Find the scale_x growth factor
91 | scl_x = 0
92 | if orig_width - crop_left - crop_right > 0:
93 | scl_x = (orig_width - crop_left - crop_right) / (orig_width - crop_xl - crop_xr)
94 |
95 | scl_y = 0
96 | if orig_height - crop_top - crop_bottom > 0:
97 | scl_y = (orig_height - crop_bottom - crop_top) / (orig_height - crop_yb - crop_yt)
98 |
99 | strip.scale_start_x *= scl_x
100 | strip.scale_start_y *= scl_y
101 |
102 | # Find the translation difference between old and new
103 | width_diff = (width * scl_x) - width
104 | height_diff = (height * scl_y) - height
105 |
106 | left_shift = 0
107 | right_shift = 0
108 | if abs(crop_adjust_l + crop_adjust_r) > 0:
109 | left_shift = width_diff * (crop_adjust_l / (crop_adjust_l + crop_adjust_r))
110 | right_shift = width_diff * (crop_adjust_r / (crop_adjust_l + crop_adjust_r))
111 |
112 | bottom_shift = 0
113 | top_shift = 0
114 | if abs(crop_adjust_b + crop_adjust_t) > 0:
115 | bottom_shift = height_diff * (crop_adjust_b / (crop_adjust_b + crop_adjust_t))
116 | top_shift = height_diff * (crop_adjust_t / (crop_adjust_b + crop_adjust_t))
117 |
118 | pos_x = get_pos_x(strip)
119 | pos_y = get_pos_y(strip)
120 |
121 | pos_x -= (left_shift * cos) / 2
122 | pos_x += (right_shift * cos) / 2
123 | pos_x += (bottom_shift * sin) / 2
124 | pos_x -= (top_shift * sin) / 2
125 |
126 | pos_y -= (bottom_shift * cos) / 2
127 | pos_y += (top_shift * cos) / 2
128 | pos_y -= (left_shift * sin) / 2
129 | pos_y += (right_shift * sin) / 2
130 |
131 | strip.translate_start_x = set_pos_x(strip, pos_x)
132 | strip.translate_start_y = set_pos_y(strip, pos_y)
133 |
134 | self.scale_factor_x = (res_x / orig_width) * strip.scale_start_x
135 | self.scale_factor_y = (res_y / orig_height) * strip.scale_start_y
136 |
137 | self.init_crop_left = crop_xl
138 | self.init_crop_right = crop_xr
139 | self.init_crop_bottom = crop_yb
140 | self.init_crop_top = crop_yt
141 |
142 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
143 | self.crop_left = crop_xl * self.scale_factor_x
144 | self.crop_right = crop_xr * self.scale_factor_x
145 | self.crop_bottom = crop_yb * self.scale_factor_y
146 | self.crop_top = crop_yt * self.scale_factor_y
147 |
--------------------------------------------------------------------------------
/operators/crop/draw_crop.py:
--------------------------------------------------------------------------------
1 | import gpu
2 | from gpu_extras.batch import batch_for_shader
3 |
4 | from .set_corners import set_corners
5 | from .set_quads import set_quads
6 |
7 | from ..utils.draw import draw_line
8 |
9 | def draw_crop(self, context):
10 | active_strip = context.scene.sequence_editor.active_strip
11 |
12 | active_color = context.preferences.themes[0].sequence_editor.active_strip
13 | active_color = (active_color[0], active_color[1], active_color[2], 1.0)
14 | color = context.preferences.themes[0].sequence_editor.frame_current
15 | color = (color[0], color[1], color[2], 1.0)
16 | outline_color = (0, 0, 0, 1)
17 |
18 | set_corners(self, context)
19 | set_quads(self, context)
20 |
21 | vertices = []
22 | for corner in self.corners:
23 | vertices.append([corner[0], corner[1]])
24 |
25 | draw_line(vertices[0], vertices[1], 2, outline_color)
26 | draw_line(vertices[1], vertices[2], 2, outline_color)
27 | draw_line(vertices[2], vertices[3], 2, outline_color)
28 | draw_line(vertices[3], vertices[0], 2, outline_color)
29 |
30 | draw_line(vertices[0], vertices[1], 1, color)
31 | draw_line(vertices[1], vertices[2], 1, color)
32 | draw_line(vertices[2], vertices[3], 1, color)
33 | draw_line(vertices[3], vertices[0], 1, color)
34 |
35 | for i in range(len(self.corner_quads)):
36 | quad = self.corner_quads[i]
37 |
38 | bl = quad[0]
39 | tl = quad[1]
40 | tr = quad[2]
41 | br = quad[3]
42 |
43 | vertices = [bl, br, tl, tr]
44 |
45 | indices = ((0, 1, 2), (2, 1, 3))
46 |
47 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
48 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
49 |
50 | shader.bind()
51 |
52 | if self.clicked_quad == i:
53 | shader.uniform_float("color", active_color)
54 |
55 | else:
56 | shader.uniform_float("color", color)
57 |
58 | batch.draw(shader)
59 |
--------------------------------------------------------------------------------
/operators/crop/get_perpendicular_point.py:
--------------------------------------------------------------------------------
1 | from mathutils import Vector
2 | from mathutils.geometry import intersect_point_quad_2d
3 | from mathutils.geometry import intersect_line_line_2d
4 |
5 |
6 | def get_perpendicular_point(pt, bl, tl, tr, br):
7 | '''
8 | Return the point if it is inside the quad, else, return
9 | a point on the border of the quad.
10 |
11 | This function helps prevent the user from dragging crop handles
12 | outside the strip's area.
13 | '''
14 |
15 | intersects = intersect_point_quad_2d(
16 | pt, bl, tl, tr, br)
17 |
18 | if intersects:
19 | return pt
20 |
21 | elif pt.x <= bl.x and pt.y <= bl.y:
22 | return Vector(bl)
23 | elif pt.x <= tl.x and pt.y >= tl.y:
24 | return Vector(tl)
25 | elif pt.x >= tr.x and pt.y >= tr.y:
26 | return Vector(tr)
27 | elif pt.x >= br.x and pt.y <= br.y:
28 | return Vector(br)
29 |
30 | max_x = max([tr.x, br.x])
31 | min_x = min([tl.x, bl.x])
32 |
33 | max_y = max([tl.y, tr.y])
34 | min_y = min([bl.y, br.y])
35 |
36 | # pt left of left side
37 | if (pt.x <= tl.x or pt.x <= bl.x) and (pt.y >= bl.y and pt.y <= tl.y):
38 | right = Vector([max_x, pt.y])
39 | intersection = intersect_line_line_2d(bl, tl, pt, right)
40 |
41 | # pt right of right side
42 | elif (pt.x >= br.x or pt.x >= tr.x) and (pt.y >= br.y and pt.y <= tr.y):
43 | left = Vector([min_x, pt.y])
44 | intersection = intersect_line_line_2d(br, tr, pt, left)
45 |
46 | # pt above top side
47 | elif (pt.y >= tl.y or pt.y >= tr.y) and (pt.x >= tl.x and pt.x <= tr.x):
48 | bottom = Vector([pt.x, min_y])
49 | intersection = intersect_line_line_2d(tl, tr, pt, bottom)
50 |
51 | # pt below bottom side
52 | elif (pt.y <= bl.y or pt.y <= br.y) and (pt.x >= bl.x and pt.x <= br.x):
53 | top = Vector([pt.x, max_y])
54 | intersection = intersect_line_line_2d(bl, br, pt, top)
55 |
56 | return intersection
57 |
--------------------------------------------------------------------------------
/operators/crop/set_corners.py:
--------------------------------------------------------------------------------
1 | import math
2 | from mathutils import Vector
3 |
4 | from .get_perpendicular_point import get_perpendicular_point
5 | from ..utils.geometry import get_strip_corners
6 | from ..utils.geometry import get_preview_offset
7 | from ..utils.geometry import rotate_point
8 |
9 |
10 | def set_corners(self, context):
11 | """
12 | Set the crop corner handles based on how they are dragged by the
13 | user.
14 | """
15 | active_strip = context.scene.sequence_editor.active_strip
16 | if active_strip.type == "TRANSFORM":
17 | angle = math.radians(active_strip.rotation_start)
18 | else:
19 | angle = 0
20 |
21 | sin = math.sin(angle)
22 | cos = math.cos(angle)
23 |
24 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
25 |
26 | self.max_corners = get_strip_corners(active_strip)
27 | for corner in self.max_corners:
28 | corner.x = (corner.x * preview_zoom * fac) + offset_x
29 | corner.y = (corner.y * preview_zoom * fac) + offset_y
30 |
31 | origin = self.max_corners[2] - self.max_corners[0]
32 |
33 | bl = rotate_point(self.max_corners[0], -angle, origin)
34 | tl = rotate_point(self.max_corners[1], -angle, origin)
35 | tr = rotate_point(self.max_corners[2], -angle, origin)
36 | br = rotate_point(self.max_corners[3], -angle, origin)
37 |
38 | vec = self.current_mouse - self.mouse_pos
39 |
40 | crop_left = self.crop_left * preview_zoom * fac
41 | crop_bottom = self.crop_bottom * preview_zoom * fac
42 | crop_top = self.crop_top * preview_zoom * fac
43 | crop_right = self.crop_right * preview_zoom * fac
44 |
45 | cushion = 10
46 | if self.clicked_quad is not None:
47 | for i in range(len(self.max_corners)):
48 | # Bottom Left Clicked
49 | if self.clicked_quad == 0:
50 | pt_x = bl.x + (vec.x * cos) + (vec.y * sin) + crop_left
51 | pt_y = bl.y + (vec.y * cos) - (vec.x * sin) + crop_bottom
52 | pt = get_perpendicular_point(
53 | Vector([pt_x, pt_y]), bl, tl, tr, br)
54 |
55 | self.corners[2].x = tr.x - crop_right
56 | self.corners[2].y = tr.y - crop_top
57 |
58 | if pt.x > self.corners[2].x - cushion:
59 | pt.x = self.corners[2].x - cushion
60 | if pt.y > self.corners[2].y - cushion:
61 | pt.y = self.corners[2].y - cushion
62 |
63 | self.corners[0] = pt
64 |
65 | self.corners[1].x = tl.x + (pt.x - bl.x)
66 | self.corners[1].y = tl.y - crop_top
67 |
68 | self.corners[3].x = br.x - crop_right
69 | self.corners[3].y = br.y + (pt.y - bl.y)
70 |
71 | break
72 | # Top Left Clicked
73 | elif self.clicked_quad == 1:
74 | pt_x = tl.x + (vec.x * cos) + (vec.y * sin) + crop_left
75 | pt_y = tl.y + (vec.y * cos) - (vec.x * sin) - crop_top
76 | pt = get_perpendicular_point(
77 | Vector([pt_x, pt_y]), bl, tl, tr, br)
78 |
79 | self.corners[3].x = br.x - crop_right
80 | self.corners[3].y = br.y + crop_bottom
81 |
82 | if pt.x > self.corners[3].x - cushion:
83 | pt.x = self.corners[3].x - cushion
84 | if pt.y < self.corners[3].y + cushion:
85 | pt.y = self.corners[3].y + cushion
86 |
87 | self.corners[1] = pt
88 |
89 | self.corners[0].x = bl.x + (pt.x - tl.x)
90 | self.corners[0].y = bl.y + crop_bottom
91 |
92 | self.corners[2].x = tr.x - crop_right
93 | self.corners[2].y = tr.y - (tl.y - pt.y)
94 |
95 | break
96 | # Top Right Clicked
97 | elif self.clicked_quad == 2:
98 | pt_x = tr.x + (vec.x * cos) + (vec.y * sin) - crop_right
99 | pt_y = tr.y + (vec.y * cos) - (vec.x * sin) - crop_top
100 | pt = get_perpendicular_point(
101 | Vector([pt_x, pt_y]), bl, tl, tr, br)
102 |
103 | self.corners[0].x = bl.x + crop_left
104 | self.corners[0].y = bl.y + crop_bottom
105 |
106 | if pt.x < self.corners[0].x + cushion:
107 | pt.x = self.corners[0].x + cushion
108 | if pt.y < self.corners[0].y + cushion:
109 | pt.y = self.corners[0].y + cushion
110 |
111 | self.corners[2] = pt
112 |
113 | self.corners[1].x = tl.x + crop_left
114 | self.corners[1].y = tl.y - (tr.y - pt.y)
115 |
116 | self.corners[3].x = br.x - (tr.x - pt.x)
117 | self.corners[3].y = br.y + crop_bottom
118 | # Bottom Right Clicked
119 | elif self.clicked_quad == 3:
120 | pt_x = br.x + (vec.x * cos) + (vec.y * sin) - crop_right
121 | pt_y = br.y + (vec.y * cos) - (vec.x * sin) + crop_bottom
122 | pt = get_perpendicular_point(
123 | Vector([pt_x, pt_y]), bl, tl, tr, br)
124 |
125 | self.corners[1].x = tl.x + crop_left
126 | self.corners[1].y = tl.y - crop_top
127 |
128 | if pt.x < self.corners[1].x + cushion:
129 | pt.x = self.corners[1].x + cushion
130 | if pt.y > self.corners[1].y - cushion:
131 | pt.y = self.corners[1].y - cushion
132 |
133 | self.corners[3] = pt
134 |
135 | self.corners[0].x = bl.x + crop_left
136 | self.corners[0].y = bl.y + (pt.y - br.y)
137 |
138 | self.corners[2].x = tr.x - (tr.x - pt.x)
139 | self.corners[2].y = tr.y - crop_top
140 |
141 | else:
142 | self.corners[0].x = bl.x + crop_left
143 | self.corners[0].y = bl.y + crop_bottom
144 |
145 | self.corners[1].x = tl.x + crop_left
146 | self.corners[1].y = tl.y - crop_top
147 |
148 | self.corners[2].x = tr.x - crop_right
149 | self.corners[2].y = tr.y - crop_top
150 |
151 | self.corners[3].x = br.x - crop_right
152 | self.corners[3].y = br.y + crop_bottom
153 |
154 | self.corners[0] = rotate_point(self.corners[0], angle, origin)
155 | self.corners[1] = rotate_point(self.corners[1], angle, origin)
156 | self.corners[2] = rotate_point(self.corners[2], angle, origin)
157 | self.corners[3] = rotate_point(self.corners[3], angle, origin)
158 |
--------------------------------------------------------------------------------
/operators/crop/set_quads.py:
--------------------------------------------------------------------------------
1 | import math
2 | from mathutils import Vector
3 | from ..utils.geometry import rotate_point
4 |
5 |
6 | def set_quads(self, context):
7 | """
8 | Set the location of the crop handles
9 | """
10 | self.corner_quads = []
11 |
12 | active_strip = context.scene.sequence_editor.active_strip
13 | if active_strip.type == "TRANSFORM":
14 | angle = math.radians(active_strip.rotation_start)
15 | else:
16 | angle = 0
17 |
18 | rect_size = 5.5
19 |
20 | for corner in self.corners:
21 | origin = corner
22 |
23 | x1 = corner.x - rect_size
24 | x2 = corner.x + rect_size
25 | y1 = corner.y - rect_size
26 | y2 = corner.y + rect_size
27 |
28 | bl = rotate_point(Vector([x1, y1]), angle, origin)
29 | tl = rotate_point(Vector([x1, y2]), angle, origin)
30 | tr = rotate_point(Vector([x2, y2]), angle, origin)
31 | br = rotate_point(Vector([x2, y1]), angle, origin)
32 |
33 | self.corner_quads.append([bl, tl, tr, br])
34 |
--------------------------------------------------------------------------------
/operators/delete/__init__.py:
--------------------------------------------------------------------------------
1 | from .delete import PREV_OT_delete
2 |
--------------------------------------------------------------------------------
/operators/delete/delete.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from ..utils.selection import get_input_tree
3 |
4 |
5 | class PREV_OT_delete(bpy.types.Operator):
6 | """
7 | Deletes all selected strips as well as any strips that are inputs
8 | of those strips.
9 | For example, deleting a transform strip with this operator will
10 | also delete the strip it was transforming.
11 | """
12 | bl_idname = "vse_transform_tools.delete"
13 | bl_label = "Delete"
14 | bl_description = "Delete selected and their inputs recursively"
15 | bl_options = {'REGISTER', 'UNDO'}
16 |
17 | @classmethod
18 | def poll(cls, context):
19 | scene = context.scene
20 | if (scene.sequence_editor and
21 | scene.sequence_editor.active_strip):
22 | return True
23 | return False
24 |
25 | def invoke(self, context, event):
26 | scene = context.scene
27 | selected = []
28 |
29 | for strip in context.selected_sequences:
30 | if strip not in selected:
31 | selected.extend(get_input_tree(strip))
32 | for strip in selected:
33 | strip.select = True
34 |
35 | delete_count = len(selected)
36 |
37 | dead_scenes = []
38 | if event.shift:
39 | for strip in selected:
40 | if strip.type == "SCENE":
41 | for s in scene.sequence_editor.sequences_all:
42 | if s.type == "SCENE" and s.scene == strip.scene:
43 | s.select = True
44 | delete_count += 1
45 | dead_scenes.append(strip.scene)
46 | elif hasattr(strip, 'filepath'):
47 | for s in scene.sequence_editor.sequences_all:
48 | if hasattr(s, 'filepath') and s.filepath == strip.filepath:
49 | s.select = True
50 | delete_count += 1
51 |
52 | bpy.ops.sequencer.delete()
53 |
54 | for sce in dead_scenes:
55 | for obj in sce.objects:
56 | bpy.data.objects.remove(obj, True)
57 | bpy.data.scenes.remove(sce, True)
58 |
59 | selection_length = len(selected)
60 | report_message = ' '.join(
61 | ['Deleted', str(selection_length), 'sequence'])
62 |
63 | if selection_length > 1:
64 | report_message += 's'
65 |
66 | self.report({'INFO'}, report_message)
67 |
68 | return {"FINISHED"}
69 |
--------------------------------------------------------------------------------
/operators/duplicate/__init__.py:
--------------------------------------------------------------------------------
1 | from .duplicate import PREV_OT_duplicate
2 |
--------------------------------------------------------------------------------
/operators/duplicate/duplicate.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from ..utils.selection import get_input_tree
4 |
5 | from .get_vertical_translation import get_vertical_translation
6 |
7 |
8 | class PREV_OT_duplicate(bpy.types.Operator):
9 | """
10 | Duplicates all selected strips and any strips that are inputs
11 | of those strips.
12 | Calls the Grab operator immediately after duplicating.
13 | """
14 | bl_idname = "vse_transform_tools.duplicate"
15 | bl_label = "Duplicate"
16 | bl_description = "Duplicate selected and their inputs recursively"
17 | bl_options = {'REGISTER', 'UNDO'}
18 |
19 | @classmethod
20 | def poll(cls, context):
21 | scene = context.scene
22 | if (scene.sequence_editor and
23 | scene.sequence_editor.active_strip):
24 | return True
25 | return False
26 |
27 | def invoke(self, context, event):
28 |
29 | selected = context.selected_sequences
30 |
31 | duplicated = []
32 |
33 | for strip in selected:
34 | if strip not in duplicated:
35 | bpy.ops.sequencer.select_all(action="DESELECT")
36 |
37 | tree = get_input_tree(strip)
38 | for seq in tree:
39 | seq.select = True
40 |
41 | duplicated.extend(tree)
42 |
43 | vertical_translation = get_vertical_translation(
44 | context.selected_sequences)
45 |
46 | bpy.ops.sequencer.duplicate_move(
47 | SEQUENCER_OT_duplicate={},
48 | TRANSFORM_OT_seq_slide={
49 | "value": (0, vertical_translation)})
50 |
51 | return bpy.ops.vse_transform_tools.grab('INVOKE_DEFAULT')
52 |
--------------------------------------------------------------------------------
/operators/duplicate/get_vertical_translation.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from operator import attrgetter
3 |
4 |
5 | def get_vertical_translation(strips):
6 | """
7 | Determine how many channels up the strips need to be moved
8 | in order to accomodate them all
9 |
10 | Parameters
11 | ----------
12 | strips : list of bpy.types.Sequence()
13 | A group of strips in the sequencer
14 |
15 | Returns
16 | -------
17 | int
18 | The number of channels UP that the group of strips need to move
19 | in order to prevent any conflicts with other strips in the
20 | sequencer.
21 | """
22 | scene = bpy.context.scene
23 |
24 | min_channel = min(strips, key=attrgetter('channel')).channel
25 | max_channel = max(strips, key=attrgetter('channel')).channel
26 |
27 | channel_count = (max_channel - min_channel) + 1
28 |
29 | frame_start = min(strips,
30 | key=attrgetter('frame_start')).frame_start
31 | frame_end = max(strips,
32 | key=attrgetter('frame_final_end')).frame_final_end
33 |
34 | all_sequences = list(sorted(scene.sequence_editor.sequences,
35 | key=lambda x: x.frame_start))
36 |
37 | blocked_channels = []
38 | for seq in all_sequences:
39 | if (seq not in strips and
40 | seq.frame_start <= frame_end and
41 | seq.frame_final_end >= frame_start):
42 | blocked_channels.append(seq.channel)
43 | elif seq.frame_start > frame_end:
44 | break
45 |
46 | i = max_channel + 1
47 | while True:
48 | for x in range(i, i + channel_count):
49 | conflict = False
50 | if x in blocked_channels:
51 | conflict = True
52 | break
53 | if not conflict:
54 | return x - max_channel
55 | i += 1
56 |
--------------------------------------------------------------------------------
/operators/grab/__init__.py:
--------------------------------------------------------------------------------
1 | from .grab import PREV_OT_grab
2 |
--------------------------------------------------------------------------------
/operators/grab/grab.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from mathutils import Vector
3 |
4 | from ..utils import process_input
5 | from ..utils import func_constrain_axis_mmb
6 | from ..utils import func_constrain_axis
7 |
8 | from ..utils.geometry import get_pos_x
9 | from ..utils.geometry import get_pos_y
10 | from ..utils.geometry import set_pos_x
11 | from ..utils.geometry import set_pos_y
12 | from ..utils.geometry import get_res_factor
13 | from ..utils.geometry import get_group_box
14 | from ..utils.geometry import get_strip_box
15 | from ..utils.geometry import mouse_to_res
16 | from ..utils.geometry import get_preview_offset
17 |
18 | from ..utils.selection import ensure_transforms
19 | from ..utils.selection import get_highest_transform
20 | from ..utils.selection import get_visible_strips
21 |
22 | from ..utils.draw import draw_snap
23 |
24 |
25 | class PREV_OT_grab(bpy.types.Operator):
26 | """
27 | Move strip(s) in 2D space
28 | """
29 | bl_idname = "vse_transform_tools.grab"
30 | bl_label = "Grab"
31 | bl_description = "Change position of strips in Image Preview Window"
32 | bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', 'BLOCKING'}
33 |
34 | local_axis = True
35 | axis_x = True
36 | axis_y = True
37 | choose_axis = False
38 |
39 | first_mouse_pos = Vector([0, 0])
40 | pos_clic = Vector([0, 0])
41 | mouse_pos = Vector([0, 0])
42 | center_area = Vector([0, 0])
43 | vec_act = Vector([0, 0])
44 |
45 | group_width = 0
46 | group_height = 0
47 |
48 | tab_init = []
49 | tab = []
50 |
51 | key_val = ''
52 | key_period = False
53 | key_period_val = 1
54 |
55 | handle_axes = None
56 | handle_snap = None
57 |
58 | slow_factor = 10
59 | pre_slow_vec = Vector([0, 0])
60 | reduction_vec = Vector([0, 0])
61 | slow_act_fm = Vector([0, 0])
62 |
63 | horizontal_interests = []
64 | vertical_interests = []
65 |
66 | initially_shifted = False
67 |
68 | last_snap_orientation = ""
69 | last_line_loc = None
70 | orientation_conflict_winner = 0
71 |
72 | @classmethod
73 | def poll(cls, context):
74 | scene = context.scene
75 | if (scene.sequence_editor and
76 | scene.sequence_editor.active_strip):
77 | return True
78 | return False
79 |
80 | def modal(self, context, event):
81 | if self.tab:
82 | scene = context.scene
83 | res_x = scene.render.resolution_x
84 | res_y = scene.render.resolution_y
85 |
86 | mouse_x = event.mouse_region_x
87 | mouse_y = event.mouse_region_y
88 |
89 | mouse_vec = Vector([mouse_x, mouse_y])
90 | self.mouse_pos = mouse_to_res(mouse_vec)
91 |
92 | self.vec_act = self.mouse_pos - self.reduction_vec - self.first_mouse_pos
93 |
94 | func_constrain_axis_mmb(self, context, event.type, event.value, 0)
95 |
96 | func_constrain_axis(self, context, event.type, event.value, 0)
97 |
98 | process_input(self, event.type, event.value)
99 | if self.key_val != '':
100 | try:
101 | if self.axis_y and not self.axis_x:
102 | self.vec_act = Vector([0, float(self.key_val)])
103 | else:
104 | self.vec_act = Vector([float(self.key_val), 0])
105 | except ValueError:
106 | pass
107 |
108 | if not self.initially_shifted and 'SHIFT' in event.type and event.value == 'PRESS' and self.key_val == '':
109 | self.pre_slow_vec = self.mouse_pos
110 |
111 | elif not self.initially_shifted and 'SHIFT' in event.type and event.value == 'RELEASE' and self.key_val == '':
112 | self.vec_act = (self.pre_slow_vec - self.first_mouse_pos - self.reduction_vec) + self.slow_act_fm
113 | self.reduction_vec = self.reduction_vec + ((self.mouse_pos - self.pre_slow_vec) * (self.slow_factor - 1)) / self.slow_factor
114 |
115 | elif not self.initially_shifted and event.shift and self.key_val == '':
116 | self.slow_act_fm = (self.mouse_pos - self.pre_slow_vec) / self.slow_factor
117 | self.vec_act = (self.pre_slow_vec - self.first_mouse_pos - self.reduction_vec) + self.slow_act_fm
118 |
119 | elif 'SHIFT' in event.type and event.value == 'RELEASE':
120 | self.initially_shifted = False
121 |
122 | info_x = round(self.vec_act.x, 5)
123 | info_y = round(self.vec_act.y, 5)
124 | if not self.axis_x:
125 | self.vec_act = Vector((0, self.vec_act.y))
126 | context.area.header_text_set("D: %.4f along global Y" % info_y)
127 | if not self.axis_y:
128 | self.vec_act = Vector((self.vec_act.x, 0))
129 | context.area.header_text_set("D: %.4f along global X" % info_x)
130 | if self.axis_x and self.axis_y:
131 | context.area.header_text_set("Dx: %.4f Dy: %.4f" % (info_x, info_y))
132 |
133 | snap_distance = int(max([res_x, res_y]) / 100)
134 |
135 | group_pos_x = self.center_area.x + self.vec_act.x
136 | group_pos_y = self.center_area.y + self.vec_act.y
137 |
138 | current_left = group_pos_x - (self.group_width / 2)
139 | current_right = group_pos_x + (self.group_width / 2)
140 | current_bottom = group_pos_y - (self.group_height / 2)
141 | current_top = group_pos_y + (self.group_height / 2)
142 |
143 | trans_offset_x = 0
144 | trans_offset_y = 0
145 |
146 | orientations = []
147 | line_locs = []
148 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
149 | if event.ctrl:
150 | for line in self.horizontal_interests:
151 | if (current_left < line + snap_distance and
152 | current_left > line - snap_distance):
153 | trans_offset_x = line - current_left
154 |
155 | line_locs.append((line * fac * preview_zoom) + offset_x)
156 | orientations.append("VERTICAL")
157 |
158 | break
159 | if (current_right > line - snap_distance and
160 | current_right < line + snap_distance):
161 | trans_offset_x = line - current_right
162 |
163 | line_locs.append((line * fac * preview_zoom) + offset_x)
164 | orientations.append("VERTICAL")
165 |
166 | for line in self.vertical_interests:
167 | if (current_bottom < line + snap_distance and
168 | current_bottom > line - snap_distance):
169 | trans_offset_y = line - current_bottom
170 |
171 | line_locs.append((line * fac * preview_zoom) + offset_y)
172 | orientations.append("HORIZONTAL")
173 |
174 | break
175 | if (current_top > line - snap_distance and
176 | current_top < line + snap_distance):
177 | trans_offset_y = line - current_top
178 |
179 | line_locs.append((line * fac * preview_zoom) + offset_y)
180 | orientations.append("HORIZONTAL")
181 |
182 | orientation = ""
183 | line_loc = None
184 | if len(orientations) > 1 and self.last_snap_orientation != "" and self.orientation_conflict_winner == -1:
185 | index = orientations.index(self.last_snap_orientation)
186 | orientations.pop(index)
187 | line_locs.pop(index)
188 |
189 | self.orientation_conflict_winner = int(not index)
190 |
191 | orientation = orientations[0]
192 | line_loc = line_locs[0]
193 |
194 | elif len(orientations) > 1 and self.last_snap_orientation == "" and self.orientation_conflict_winner == -1:
195 | self.orientation_conflict_winner = 0
196 | orientation = orientations[0]
197 | line_loc = line_locs[0]
198 |
199 | elif len(orientations) > 1:
200 | orientation = orientations[self.orientation_conflict_winner]
201 | line_loc = line_locs[self.orientation_conflict_winner]
202 |
203 | elif len(orientations) > 0:
204 | self.orientation_conflict_winner = -1
205 | orientation = orientations[0]
206 | line_loc = line_locs[0]
207 |
208 | if orientation != "" and (self.handle_snap == None or "RNA_HANDLE_REMOVED" in str(self.handle_snap)):
209 | args = (self, line_loc, orientation)
210 | self.handle_snap = bpy.types.SpaceSequenceEditor.draw_handler_add(
211 | draw_snap, args, 'PREVIEW', 'POST_PIXEL')
212 | self.last_snap_orientation = orientation
213 | self.last_line_loc = line_loc
214 | elif (orientation != self.last_snap_orientation or line_loc != self.last_line_loc) and self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
215 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
216 | self.last_snap_orientation = orientation
217 | self.last_line_loc = line_loc
218 |
219 | elif self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
220 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
221 |
222 | for strip, init_pos in zip(self.tab, self.tab_init):
223 | pos_x = init_pos[0] + self.vec_act.x + trans_offset_x
224 | pos_y = init_pos[1] + self.vec_act.y + trans_offset_y
225 |
226 | if strip.type == "TRANSFORM":
227 | strip.translate_start_x = set_pos_x(strip, pos_x)
228 | strip.translate_start_y = set_pos_y(strip, pos_y)
229 | else:
230 | strip.transform.offset_x = pos_x
231 | strip.transform.offset_y = pos_y
232 |
233 | if (event.type == 'LEFTMOUSE' or
234 | event.type == 'RET' or
235 | event.type == 'NUMPAD_ENTER' or
236 | not self.tab):
237 | if self.handle_axes:
238 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
239 | self.handle_axes, 'PREVIEW')
240 |
241 | if self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
242 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
243 |
244 | if scene.tool_settings.use_keyframe_insert_auto:
245 | for strip in self.tab:
246 | if strip.type == "TRANSFORM":
247 | strip.keyframe_insert(data_path='translate_start_x')
248 | strip.keyframe_insert(data_path='translate_start_y')
249 | else:
250 | strip.transform.keyframe_insert(data_path='offset_x')
251 | strip.transform.keyframe_insert(data_path='offset_y')
252 |
253 | context.area.header_text_set(None)
254 | return {'FINISHED'}
255 |
256 | if event.type == 'ESC' or event.type == 'RIGHTMOUSE':
257 | if self.handle_axes:
258 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
259 | self.handle_axes, 'PREVIEW')
260 |
261 | if self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
262 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
263 |
264 | for strip, init_pos in zip(self.tab, self.tab_init):
265 | if strip.type == "TRANSFORM":
266 | strip.translate_start_x = set_pos_x(strip, init_pos[0])
267 | strip.translate_start_y = set_pos_y(strip, init_pos[1])
268 | else:
269 | strip.transform.offset_x = init_pos[0]
270 | strip.transform.offset_y = init_pos[1]
271 |
272 |
273 | context.area.header_text_set(None)
274 | return {'FINISHED'}
275 |
276 | return {'RUNNING_MODAL'}
277 |
278 | return {'FINISHED'}
279 |
280 | def invoke(self, context, event):
281 | scene = context.scene
282 |
283 | res_x = scene.render.resolution_x
284 | res_y = scene.render.resolution_y
285 |
286 | if event.alt:
287 | selected = bpy.context.selected_sequences
288 | for strip in selected:
289 | transform = get_highest_transform(strip)
290 | if transform.type == 'TRANSFORM':
291 | strip.translate_start_x = 0
292 | strip.translate_start_y = 0
293 | elif transform.use_translation:
294 | box = get_strip_box(transform)
295 | width = box[1] - box[0]
296 | height = box[3] - box[2]
297 | transform.transform.offset_x = (res_x / 2) - (width / 2)
298 | transform.transform.offset_y = (res_y / 2) - (height / 2)
299 | return {'FINISHED'}
300 |
301 | else:
302 | mouse_x = event.mouse_region_x
303 | mouse_y = event.mouse_region_y
304 |
305 | self.key_val = ''
306 |
307 | self.tab = []
308 | self.tab_init = []
309 | self.center_area = Vector([0, 0])
310 |
311 | self.group_width = 0
312 | self.group_height = 0
313 |
314 | self.horizontal_interests = [0, res_x]
315 | self.vertical_interests = [0, res_y]
316 |
317 | mouse_vec = Vector([mouse_x, mouse_y])
318 | self.first_mouse_pos = mouse_to_res(mouse_vec)
319 |
320 | fac = get_res_factor()
321 |
322 | #self.tab = ensure_transforms()
323 |
324 | image_offset_strips = []
325 | selected = context.selected_sequences
326 | for strip in selected:
327 | if not strip.type == 'SOUND':
328 | if strip.use_translation:
329 | image_offset_strips.append(strip)
330 | strip.select = False
331 |
332 | self.tab = ensure_transforms()
333 | self.tab.extend(image_offset_strips)
334 | visible_strips = get_visible_strips()
335 |
336 | for strip in visible_strips:
337 | if strip not in self.tab:
338 | left, right, bottom, top = get_group_box([strip])
339 |
340 | self.horizontal_interests.append(left)
341 | self.horizontal_interests.append(right)
342 |
343 | self.vertical_interests.append(bottom)
344 | self.vertical_interests.append(top)
345 |
346 | for strip in self.tab:
347 | strip.select = True
348 | if strip.type == "TRANSFORM":
349 | pos_x = get_pos_x(strip)
350 | pos_y = get_pos_y(strip)
351 | else:
352 | pos_x = strip.transform.offset_x
353 | pos_y = strip.transform.offset_y
354 |
355 | self.tab_init.append([pos_x, pos_y])
356 |
357 | if self.tab:
358 | group_box = get_group_box(self.tab)
359 | min_left, max_right, min_bottom, max_top = group_box
360 |
361 | self.group_width = max_right - min_left
362 | self.group_height = max_top - min_bottom
363 |
364 | center_x = (min_left + (self.group_width / 2))
365 | center_y = (min_bottom + (self.group_height / 2))
366 |
367 | self.center_area = Vector([center_x, center_y])
368 |
369 | # Prevents weird behavior if this op is called by
370 | # bpy.ops.vse_transform_tools.duplicate()
371 | if event.shift:
372 | self.initially_shifted = True
373 |
374 | context.window_manager.modal_handler_add(self)
375 | return {'RUNNING_MODAL'}
376 |
--------------------------------------------------------------------------------
/operators/group/__init__.py:
--------------------------------------------------------------------------------
1 | from .group import PREV_OT_group
2 |
--------------------------------------------------------------------------------
/operators/group/group.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from ..utils.geometry import get_group_box
4 | from ..utils.selection import get_input_tree
5 |
6 | class PREV_OT_group(bpy.types.Operator):
7 | bl_idname = "vse_transform_tools.group"
8 | bl_label = "Group"
9 | bl_description = "Group VSE Strips together"
10 | bl_options = {'REGISTER', 'UNDO'}
11 |
12 | @classmethod
13 | def poll(cls, context):
14 | scene = context.scene
15 | if (scene.sequence_editor and len(context.selected_sequences) > 0):
16 | return True
17 | return False
18 |
19 | def invoke(self, context, event):
20 | if event.alt:
21 | bpy.ops.sequencer.meta_separate()
22 | return {"FINISHED"}
23 |
24 | selected = context.selected_sequences
25 |
26 | for strip in selected:
27 | tree = get_input_tree(strip)
28 | for child in tree:
29 | child.select = True
30 | if not child in selected:
31 | selected.append(child)
32 |
33 | left, right, bottom, top = get_group_box(selected)
34 |
35 | res_x = context.scene.render.resolution_x
36 | res_y = context.scene.render.resolution_y
37 |
38 | bpy.ops.sequencer.meta_make()
39 |
40 | active = context.scene.sequence_editor.active_strip
41 | active.use_translation = True
42 | active.blend_type = "ALPHA_OVER"
43 |
44 | if not (left == right == bottom == top == 0):
45 | active.use_crop = True
46 | active.crop.min_x = left
47 | active.crop.max_x = res_x - right
48 |
49 | active.crop.min_y = bottom
50 | active.crop.max_y = res_y - top
51 |
52 | active.transform.offset_x = left
53 | active.transform.offset_y = bottom
54 |
55 |
56 | return {"FINISHED"}
57 |
--------------------------------------------------------------------------------
/operators/meta_toggle/__init__.py:
--------------------------------------------------------------------------------
1 | from .meta_toggle import PREV_OT_meta_toggle
2 |
--------------------------------------------------------------------------------
/operators/meta_toggle/meta_toggle.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from ..utils.selection import get_input_tree
3 |
4 |
5 | class PREV_OT_meta_toggle(bpy.types.Operator):
6 | """
7 | Toggles the selected strip if it is a META. If the selected strip is
8 | not a meta, recursively checks inputs until a META strip is
9 | encountered and toggles it. If no META is found, this operator does
10 | nothing.
11 | """
12 | bl_idname = "vse_transform_tools.meta_toggle"
13 | bl_label = "Meta Toggle"
14 | bl_description = "Toggle the Meta to reveal sequences within"
15 | bl_options = {'REGISTER', 'UNDO'}
16 |
17 | @classmethod
18 | def poll(cls, context):
19 | scene = context.scene
20 | if (scene.sequence_editor):
21 | return True
22 | return False
23 |
24 | def invoke(self, context, event):
25 | scene = context.scene
26 |
27 | active = scene.sequence_editor.active_strip
28 |
29 | children = get_input_tree(active)
30 | for child in children:
31 | try:
32 | if child.type == "META":
33 | bpy.ops.sequencer.select_all(action="DESELECT")
34 | scene.sequence_editor.active_strip = child
35 | child.select = True
36 | return bpy.ops.sequencer.meta_toggle('INVOKE_DEFAULT')
37 |
38 | except AttributeError:
39 | pass
40 |
41 | return bpy.ops.sequencer.meta_toggle('INVOKE_DEFAULT')
42 |
--------------------------------------------------------------------------------
/operators/mouse_track/__init__.py:
--------------------------------------------------------------------------------
1 | from .mouse_track import PREV_OT_mouse_track
2 |
--------------------------------------------------------------------------------
/operators/mouse_track/mouse_track.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from mathutils import Vector
3 | import math
4 |
5 | from ..utils.geometry import mouse_to_res
6 |
7 | class PREV_OT_mouse_track(bpy.types.Operator):
8 | """
9 | Track mouse position and apply as keyframes to a transform modifier
10 | """
11 | bl_idname = "vse_transform_tools.mouse_track"
12 | bl_label = "Mouse Track"
13 | bl_description = "Track mouse position and apply as keyframes to a transform modifier"
14 | bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', 'BLOCKING'}
15 |
16 | @classmethod
17 | def poll(cls, context):
18 | scene = context.scene
19 | strip = scene.sequence_editor.active_strip
20 | if (scene.sequence_editor and strip):
21 | if strip.type == "TRANSFORM":
22 | return True
23 | elif strip.use_translation:
24 | return True
25 | return False
26 |
27 | def modal(self, context, event):
28 | scene = context.scene
29 | fps = scene.render.fps / scene.render.fps_base
30 | fc = scene.frame_current
31 | cushion = fc + math.ceil(fps / 2)
32 |
33 | strip = scene.sequence_editor.active_strip
34 |
35 | res_x = scene.render.resolution_x
36 | res_y = scene.render.resolution_y
37 |
38 | mouse_x = event.mouse_region_x
39 | mouse_y = event.mouse_region_y
40 |
41 | mouse_vec = Vector([mouse_x, mouse_y])
42 | mouse_pos = mouse_to_res(mouse_vec)
43 |
44 | if strip.type == "TRANSFORM":
45 | x = mouse_pos.x - (res_x / 2)
46 | y = mouse_pos.y - (res_y / 2)
47 |
48 | if strip.translation_unit == 'PERCENT':
49 | x = ((mouse_pos.x * 100) / res_x) - 50
50 | y = ((mouse_pos.y * 100) / res_y) - 50
51 |
52 | strip.translate_start_x = x
53 | strip.translate_start_y = y
54 |
55 | for i in range(fc, cushion):
56 | try:
57 | strip.keyframe_delete('translate_start_x', frame=i)
58 | strip.keyframe_delete('translate_start_y', frame=i)
59 | except RuntimeError:
60 | pass
61 |
62 | strip.keyframe_insert(
63 | data_path="translate_start_x")
64 | strip.keyframe_insert(
65 | data_path="translate_start_y")
66 |
67 | else:
68 | strip.transform.offset_x = mouse_pos.x
69 | strip.transform.offset_y = mouse_pos.y
70 |
71 | for i in range(fc, cushion):
72 | try:
73 | strip.transform.keyframe_delete('offset_x', frame=i)
74 | strip.transform.keyframe_delete('offset_y', frame=i)
75 | except RuntimeError:
76 | pass
77 |
78 | strip.transform.keyframe_insert(
79 | data_path="offset_x")
80 | strip.transform.keyframe_insert(
81 | data_path="offset_y")
82 |
83 | if event.type == 'M' and event.value == 'RELEASE':
84 | return {'FINISHED'}
85 |
86 | return {'RUNNING_MODAL'}
87 |
88 | def invoke(self, context, event):
89 | self.initial_frame = context.scene.frame_current
90 | context.window_manager.modal_handler_add(self)
91 | return {'RUNNING_MODAL'}
92 |
--------------------------------------------------------------------------------
/operators/pixelate/__init__.py:
--------------------------------------------------------------------------------
1 | from .pixelate import PREV_OT_pixelate
2 |
--------------------------------------------------------------------------------
/operators/pixelate/draw_pixelate_controls.py:
--------------------------------------------------------------------------------
1 | from ..utils.draw import draw_line
2 | from ..utils.draw import draw_square
3 |
4 | import blf
5 |
6 | def draw_pixelate_controls(self, context):
7 | """
8 | Draws the line, 2 boxes, and the control square
9 | """
10 | w = context.region.width
11 | h = context.region.height
12 | line_width = 2 * (w / 10)
13 | offset_x = (line_width / 2) - (line_width * self.pixel_factor)
14 | x = self.first_mouse.x + offset_x
15 | y = self.first_mouse.y + self.pos.y
16 |
17 | v1 = [(-w / 10) + x, y]
18 | v2 = [(w / 10) + x, y]
19 |
20 | color = (0, 0.75, 1, 1)
21 |
22 | draw_line(v1, v2, 1, color)
23 |
24 | vertex = [x - (w / 10) + self.pos.x, y]
25 | draw_square(vertex, 10, color)
26 |
27 | # Numbers
28 | font_id = 0
29 | blf.position(font_id, vertex[0] - 20, vertex[1] + 10, 0)
30 | blf.size(font_id, 20, 72)
31 | blf.draw(font_id, str(self.fac))
32 |
--------------------------------------------------------------------------------
/operators/pixelate/pixelate.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from .draw_pixelate_controls import draw_pixelate_controls
6 |
7 | from ..utils.selection import get_input_tree
8 | from ..utils import process_input
9 |
10 |
11 | class PREV_OT_pixelate(bpy.types.Operator):
12 | """
13 | Pixelate a clip by adding 2 transform effects: 1 shrinking,
14 | 1 expanding.
15 | """
16 | bl_idname = "vse_transform_tools.pixelate"
17 | bl_label = "Pixelate"
18 | bl_description = "Pixelate a strip"
19 | bl_options = {'REGISTER', 'UNDO'}
20 |
21 | first_mouse = Vector((0, 0))
22 | pos = Vector((0, 0))
23 | pixel_factor = 0.0
24 | handle_pixelation = None
25 | fac = 0
26 |
27 | key_val = ''
28 |
29 | tab = []
30 |
31 | @classmethod
32 | def poll(cls, context):
33 | scene = context.scene
34 | if (scene.sequence_editor and
35 | scene.sequence_editor.active_strip and
36 | scene.sequence_editor.active_strip.select):
37 | return True
38 | return False
39 |
40 | def modal(self, context, event):
41 | scene = context.scene
42 | res_x = scene.render.resolution_x
43 | res_y = scene.render.resolution_y
44 |
45 | context.area.tag_redraw()
46 | w = context.region.width
47 |
48 | mouse_x = event.mouse_region_x
49 | mouse_x += (self.pixel_factor * w) / 5
50 | mouse_y = event.mouse_region_y
51 |
52 | self.pos = Vector([mouse_x, mouse_y])
53 | self.pos -= self.first_mouse
54 |
55 | if self.pos.x < 0:
56 | self.pos.x = 0.0
57 | if self.pos.x > w / 5:
58 | self.pos.x = w / 5
59 | self.fac = self.pos.x / (w / 5)
60 |
61 | process_input(self, event.type, event.value)
62 | if self.key_val != '':
63 | try:
64 | self.fac = abs(float(self.key_val))
65 | if self.fac > 1:
66 | self.fac = abs(float('0.' + self.key_val.replace('.', '')))
67 | self.pos.x = self.fac * (w / 5)
68 | except ValueError:
69 | pass
70 |
71 | precision = 2
72 | if event.ctrl:
73 | precision = 1
74 |
75 | self.fac = round(self.fac, precision)
76 |
77 | if (event.type == 'LEFTMOUSE' or
78 | event.type == 'RET' or
79 | event.type == 'NUMPAD_ENTER'):
80 |
81 | if self.fac > 0:
82 | selected_strips = []
83 | for strip in context.selected_sequences:
84 | if not strip.type == 'SOUND':
85 | selected_strips.append(strip)
86 |
87 | for strip in selected_strips:
88 | channel = strip.channel
89 |
90 | bpy.ops.sequencer.select_all(action='DESELECT')
91 | scene.sequence_editor.active_strip = strip
92 | bpy.ops.sequencer.effect_strip_add(type="TRANSFORM")
93 |
94 | shrinker = context.scene.sequence_editor.active_strip
95 | shrinker.name = "SHRINKER-%s" % strip.name
96 | shrinker.interpolation = "NONE"
97 |
98 | bpy.ops.sequencer.effect_strip_add(type="TRANSFORM")
99 | expander = context.scene.sequence_editor.active_strip
100 | expander.name = "EXPANDER-%s" % strip.name
101 | expander.blend_alpha = strip.blend_alpha
102 | expander.interpolation = "NONE"
103 | expander.use_float = True
104 |
105 | tree = get_input_tree(expander)[1::]
106 | for child in tree:
107 | child.mute = True
108 | child.select = True
109 |
110 | bpy.ops.sequencer.meta_make()
111 | meta = context.scene.sequence_editor.active_strip
112 | meta.name = "PIX-%s" % strip.name
113 | meta.channel = channel
114 | meta.blend_type = "ALPHA_OVER"
115 |
116 | pixel_fac = self.fac * 100
117 |
118 | shrink_fac = (100 / pixel_fac) / 100
119 |
120 | shrink_factor_x = (100 / (res_x / math.ceil(res_x * shrink_fac))) / 100
121 | shrink_factor_y = (100 / (res_y / math.ceil(res_y * shrink_fac))) / 100
122 |
123 | shrinker.scale_start_x = shrink_factor_x
124 | shrinker.scale_start_y = shrink_factor_y
125 |
126 | expand_factor_x = 1 / shrink_factor_x
127 | expand_factor_y = 1 / shrink_factor_y
128 |
129 | expander.scale_start_x = expand_factor_x
130 | expander.scale_start_y = expand_factor_y
131 |
132 | shrunk_x = round(res_x * shrink_factor_x)
133 | if shrunk_x % 2 == 1:
134 | expander.translate_start_x = ((-1 / (shrunk_x - 1)) / 2) * 100
135 |
136 | shrunk_y = round(res_y * shrink_factor_y)
137 | if shrunk_y % 2 == 1:
138 | expander.translate_start_y = ((-1 / (shrunk_y - 1)) / 2) * 100
139 |
140 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
141 | self.handle_pixelation, 'PREVIEW')
142 |
143 | return {'FINISHED'}
144 |
145 | else:
146 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
147 | self.handle_pixelation, 'PREVIEW')
148 | return {'FINISHED'}
149 |
150 | if event.type == 'ESC' or event.type == 'RIGHTMOUSE':
151 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
152 | self.handle_pixelation, 'PREVIEW')
153 | return {'FINISHED'}
154 |
155 | return {'RUNNING_MODAL'}
156 |
157 | def invoke(self, context, event):
158 | scene = context.scene
159 |
160 | mouse_x = event.mouse_region_x
161 | mouse_y = event.mouse_region_y
162 | self.first_mouse = Vector([mouse_x, mouse_y])
163 |
164 | self.pixel_factor = 0.0
165 | self.key_val != ''
166 |
167 | args = (self, context)
168 | self.handle_pixelation = bpy.types.SpaceSequenceEditor.draw_handler_add(
169 | draw_pixelate_controls, args, 'PREVIEW', 'POST_PIXEL')
170 | context.window_manager.modal_handler_add(self)
171 |
172 | return {'RUNNING_MODAL'}
173 |
--------------------------------------------------------------------------------
/operators/rotate/__init__.py:
--------------------------------------------------------------------------------
1 | from .rotate import PREV_OT_rotate
2 |
--------------------------------------------------------------------------------
/operators/rotate/apply_strip_rotation.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from ..utils.geometry import set_pos_x
6 | from ..utils.geometry import set_pos_y
7 | from ..utils.geometry import rotate_point
8 | from ..utils.geometry import get_res_factor
9 |
10 |
11 | def apply_strip_rotation(self, strip, rot, init_rot, init_t, event):
12 | """
13 | Update a strip's rotation & position
14 |
15 | Parameters
16 | ----------
17 | strip : bpy.types.Sequence
18 | The transform strip that is being rotated
19 | rot : float
20 | The change in rotation since init_rot
21 | init_t : list
22 | the [x, y] position of the strip
23 | event : bpy.types.Event
24 | Allows us to check if ctrl is pressed
25 | """
26 | flip_x = 1
27 | if strip.use_flip_x:
28 | flip_x = -1
29 |
30 | flip_y = 1
31 | if strip.use_flip_y:
32 | flip_y = -1
33 |
34 | strip_rot = init_rot + (flip_x * flip_y * rot)
35 |
36 | if event.ctrl:
37 | strip_rot = math.ceil(strip_rot / self.stepwise_increment)
38 | strip_rot *= self.stepwise_increment
39 |
40 | pivot_type = bpy.context.scene.seq_pivot_type
41 |
42 | if (pivot_type == '1' or
43 | pivot_type in ['0', '3'] and len(self.tab) == 1):
44 | strip.rotation_start = strip_rot
45 |
46 | elif pivot_type in ['0', '3'] and len(self.tab) > 1:
47 | pos_init = Vector([init_t[0], init_t[1]])
48 |
49 | pos_flip_x = flip_x * self.center_real.x
50 | pos_flip_y = flip_y * self.center_real.y
51 | pos_flip = Vector([pos_flip_x, pos_flip_y])
52 |
53 | pos_init -= pos_flip
54 | point_rot = flip_x * flip_y * math.radians(rot)
55 |
56 | np = rotate_point(pos_init, point_rot)
57 | if event.ctrl:
58 | p_rot_degs = math.radians(strip_rot - init_rot)
59 | point_rot = flip_x * flip_y * p_rot_degs
60 | np = rotate_point(pos_init, point_rot)
61 |
62 | pos_x = np.x + flip_x * self.center_real.x
63 | pos_x = set_pos_x(strip, pos_x)
64 |
65 | pos_y = np.y + flip_y * self.center_real.y
66 | pos_y = set_pos_y(strip, pos_y)
67 |
68 | if np.x == 0 and np.y == 0:
69 | strip.rotation_start = strip_rot
70 |
71 | else:
72 | strip.rotation_start = strip_rot
73 | strip.translate_start_x = pos_x
74 | strip.translate_start_y = pos_y
75 |
76 | elif pivot_type == '2':
77 | fac = get_res_factor()
78 |
79 | pos_x = flip_x * bpy.context.scene.seq_cursor2d_loc[0]
80 | pos_y = flip_y * bpy.context.scene.seq_cursor2d_loc[1]
81 |
82 | center_c2d = Vector([pos_x, pos_y])
83 | center_c2d /= fac
84 |
85 | pos_init = Vector([init_t[0], init_t[1]])
86 | pos_init -= center_c2d
87 |
88 | point_rot = flip_x * flip_y * math.radians(rot)
89 |
90 | np = rotate_point(pos_init, point_rot)
91 | if event.ctrl:
92 | p_rot_degs = math.radians(strip_rot - init_rot)
93 | point_rot = flip_x * flip_y * p_rot_degs
94 | np = rotate_point(pos_init, point_rot)
95 |
96 | strip.rotation_start = strip_rot
97 |
98 | pos_x = set_pos_x(strip, np.x + center_c2d.x)
99 | pos_y = set_pos_y(strip, np.y + center_c2d.y)
100 |
101 | strip.translate_start_x = pos_x
102 | strip.translate_start_y = pos_y
103 |
--------------------------------------------------------------------------------
/operators/rotate/rotate.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from ..utils import process_input
6 |
7 | from ..utils.geometry import get_res_factor
8 | from ..utils.geometry import rotate_point
9 | from ..utils.geometry import get_pos_x
10 | from ..utils.geometry import get_pos_y
11 | from ..utils.geometry import set_pos_x
12 | from ..utils.geometry import set_pos_y
13 |
14 | from ..utils.selection import ensure_transforms
15 |
16 | from ..utils.draw import draw_px_point
17 |
18 | from .apply_strip_rotation import apply_strip_rotation
19 |
20 |
21 | class PREV_OT_rotate(bpy.types.Operator):
22 | """
23 | Rotate selected strip(s)
24 | """
25 | bl_idname = "vse_transform_tools.rotate"
26 | bl_label = "Rotate"
27 | bl_description = "Rotate strips in the Image Preview"
28 | bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', 'BLOCKING'}
29 |
30 | first_mouse = Vector([0, 0])
31 | tab_init = []
32 | tab_init_t = []
33 | tab = []
34 |
35 | center_area = Vector([0, 0])
36 | center_real = Vector([0, 0])
37 |
38 | mouse_pos = Vector([-1, -1])
39 |
40 | rot_prev = 0
41 | vec_init = Vector([0, 0])
42 | vec_prev = Vector([0, 0])
43 | vec_act = Vector([0, 0])
44 |
45 | key_val = ''
46 | key_period = False
47 | key_period_val = 1
48 |
49 | stepwise_increment = 5
50 | slow_factor = 10
51 |
52 | @classmethod
53 | def poll(cls, context):
54 | scene = context.scene
55 | if (scene.sequence_editor and
56 | scene.sequence_editor.active_strip):
57 | return True
58 | return False
59 |
60 | def modal(self, context, event):
61 | context.area.tag_redraw()
62 |
63 | if self.tab:
64 | self.mouse_pos = Vector([event.mouse_region_x, event.mouse_region_y])
65 |
66 | self.vec_act = Vector([event.mouse_region_x, event.mouse_region_y])
67 | self.vec_act -= self.center_area
68 |
69 | rot = math.degrees(-self.vec_prev.angle_signed(self.vec_act))
70 |
71 | if event.shift:
72 | rot /= self.slow_factor
73 |
74 | self.rot_prev += rot
75 | self.rot_prev %= 360
76 |
77 | self.vec_prev = Vector(self.vec_act)
78 |
79 | rot = float(self.rot_prev)
80 |
81 | if abs(self.init_rot - (rot - 360)) < abs(self.init_rot - rot):
82 | rot = rot - 360
83 |
84 | process_input(self, event.type, event.value)
85 | if self.key_val != '':
86 | try:
87 | rot = -float(self.key_val)
88 | except ValueError:
89 | pass
90 |
91 | for i in range(len(self.tab)):
92 | strip = self.tab[i]
93 | init_rot = self.tab_init[i]
94 | init_t = self.tab_init_t[i]
95 |
96 | apply_strip_rotation(
97 | self, strip, rot, init_rot, init_t, event)
98 |
99 | if event.ctrl:
100 | rot = math.ceil(rot / self.stepwise_increment)
101 | rot *= self.stepwise_increment
102 | rot %= 360
103 |
104 | info_rot = (rot)
105 | context.area.header_text_set("Rotation %.4f " % info_rot)
106 |
107 | if (event.type == 'LEFTMOUSE' or
108 | event.type == 'RET' or
109 | event.type == 'NUMPAD_ENTER' or
110 | not self.tab):
111 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
112 | self.handle_line, 'PREVIEW')
113 |
114 | scene = context.scene
115 | if scene.tool_settings.use_keyframe_insert_auto:
116 | pivot_type = context.scene.seq_pivot_type
117 | if (pivot_type == '0' and len(self.tab) > 1) or pivot_type == '2':
118 | for strip in self.tab:
119 | strip.keyframe_insert(data_path='translate_start_x')
120 | strip.keyframe_insert(data_path='translate_start_y')
121 | strip.keyframe_insert(data_path='rotation_start')
122 | elif pivot_type == '1' or pivot_type == '3' or (pivot_type == '0' and len(self.tab) == 1):
123 | for strip in self.tab:
124 | strip.keyframe_insert(data_path='rotation_start')
125 |
126 | context.area.header_text_set(None)
127 | return {'FINISHED'}
128 |
129 | if event.type == 'ESC' or event.type == 'RIGHTMOUSE':
130 | for i in range(len(self.tab)):
131 | strip = self.tab[i]
132 | init_rot = self.tab_init[i]
133 | init_t = self.tab_init_t[i]
134 |
135 | strip.rotation_start = init_rot
136 | strip.translate_start_x = set_pos_x(strip, init_t[0])
137 | strip.translate_start_y = set_pos_y(strip, init_t[1])
138 |
139 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
140 | self.handle_line, 'PREVIEW')
141 | context.area.header_text_set(None)
142 | return {'FINISHED'}
143 |
144 | return {'RUNNING_MODAL'}
145 |
146 | def invoke(self, context, event):
147 | scene = context.scene
148 | view2d = context.region.view2d
149 |
150 | bpy.ops.vse_transform_tools.initialize_pivot()
151 |
152 | if event.alt:
153 | selected = ensure_transforms()
154 | for strip in selected:
155 | strip.select = True
156 | strip.rotation_start = 0.0
157 | return {'FINISHED'}
158 |
159 | else:
160 | fac = get_res_factor()
161 | self.tab_init = []
162 | self.tab = []
163 | self.tab_init_t = []
164 | self.center_real = Vector([0, 0])
165 | self.center_area = Vector([0, 0])
166 | self.key_val = ''
167 | self.slow_additions = []
168 | rotated_count = 0
169 |
170 | self.tab = ensure_transforms()
171 | active_strip = scene.sequence_editor.active_strip
172 |
173 | for strip in self.tab:
174 | strip.select = True
175 | pos_x = get_pos_x(strip)
176 | pos_y = get_pos_y(strip)
177 |
178 | self.tab_init.append(strip.rotation_start)
179 | self.tab_init_t.append([pos_x, pos_y])
180 |
181 | flip_x = 1
182 | if strip.use_flip_x:
183 | flip_x = -1
184 |
185 | flip_y = 1
186 | if strip.use_flip_y:
187 | flip_y = -1
188 |
189 | self.center_real += Vector((
190 | flip_x * pos_x, flip_y * pos_y))
191 | self.center_area += Vector((
192 | flip_x * pos_x, flip_y * pos_y))
193 |
194 | rotated_count += 1
195 |
196 | if self.tab:
197 | self.center_real /= rotated_count
198 | if context.scene.seq_pivot_type == '2':
199 | cur_loc = context.scene.seq_cursor2d_loc
200 | pos = view2d.view_to_region(cur_loc[0], cur_loc[1])
201 | self.center_area = Vector(pos)
202 |
203 | elif context.scene.seq_pivot_type == '3':
204 | flip_x = 1
205 | if strip.use_flip_x:
206 | flip_x = -1
207 |
208 | flip_y = 1
209 | if strip.use_flip_y:
210 | flip_y = -1
211 |
212 | pos_x = get_pos_x(active_strip)
213 | pos_y = get_pos_y(active_strip)
214 |
215 | self.center_real = Vector((
216 | flip_x * pos_x, flip_y * pos_y))
217 |
218 | pos_x *= flip_x * fac
219 | pos_y *= flip_y * fac
220 |
221 | view_2d = context.region.view2d
222 | pos = view_2d.view_to_region(pos_x, pos_y)
223 | self.center_area = Vector(pos)
224 |
225 | else:
226 | self.center_area /= rotated_count
227 |
228 | pos_x = self.center_area.x * fac
229 | pos_y = self.center_area.y * fac
230 | pos = view2d.view_to_region(
231 | pos_x, pos_y, clip=False)
232 | self.center_area = Vector(pos)
233 |
234 | self.vec_init = Vector(
235 | (event.mouse_region_x, event.mouse_region_y))
236 |
237 | self.vec_init -= self.center_area
238 |
239 | self.vec_prev = Vector(self.vec_init)
240 |
241 | self.init_rot = active_strip.rotation_start
242 |
243 | args = (self, context)
244 | self.handle_line = bpy.types.SpaceSequenceEditor.draw_handler_add(
245 | draw_px_point, args, 'PREVIEW', 'POST_PIXEL')
246 | context.window_manager.modal_handler_add(self)
247 |
248 | return {'RUNNING_MODAL'}
249 |
--------------------------------------------------------------------------------
/operators/scale/__init__.py:
--------------------------------------------------------------------------------
1 | from .scale import PREV_OT_scale
2 |
--------------------------------------------------------------------------------
/operators/scale/scale.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from mathutils import Vector
3 |
4 | from ..utils.geometry import get_pos_x
5 | from ..utils.geometry import get_pos_y
6 | from ..utils.geometry import set_pos_x
7 | from ..utils.geometry import set_pos_y
8 | from ..utils.geometry import get_res_factor
9 | from ..utils.geometry import get_group_box
10 | from ..utils.geometry import get_preview_offset
11 |
12 | from ..utils import func_constrain_axis_mmb
13 | from ..utils import func_constrain_axis
14 | from ..utils import process_input
15 |
16 | from ..utils.selection import get_visible_strips
17 | from ..utils.selection import ensure_transforms
18 | from ..utils.selection import get_highest_transform
19 |
20 | from ..utils.draw import draw_px_point
21 | from ..utils.draw import draw_snap
22 |
23 | class PREV_OT_scale(bpy.types.Operator):
24 | """
25 | 
26 | """
27 | bl_idname = "vse_transform_tools.scale"
28 | bl_label = "Scale"
29 | bl_description = "Scale strips in Image Preview Window"
30 | bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', 'BLOCKING'}
31 |
32 | axis_x = True
33 | axis_y = True
34 | choose_axis = False
35 |
36 | first_mouse = Vector([0, 0])
37 | pos_clic = Vector([0, 0])
38 | mouse_pos = Vector([-1, -1])
39 |
40 | vec_init = Vector([0, 0])
41 | vec_act = Vector([0, 0])
42 | vec_prev = Vector([0, 0])
43 |
44 | center_c2d = Vector([0, 0])
45 |
46 | center_area = Vector([0, 0])
47 | center_real = Vector([0, 0])
48 |
49 | key_val = ''
50 |
51 | handle_axes = None
52 | handle_line = None
53 | handle_snap = None
54 |
55 | group_width = 0
56 | group_height = 0
57 |
58 | slow_factor = 10
59 | scale_prev = 1.0
60 |
61 | horizontal_interests = []
62 | vertical_interests = []
63 |
64 | original_group_box = [Vector([0, 0]), Vector([0, 0]), Vector([0, 0]), Vector([0, 0])]
65 |
66 | last_snap_orientation = ""
67 | last_line_loc = None
68 | orientation_conflict_winner = 0
69 |
70 | @classmethod
71 | def poll(cls, context):
72 | scene = context.scene
73 | if (scene.sequence_editor and
74 | scene.sequence_editor.active_strip):
75 | return True
76 | return False
77 |
78 | def modal(self, context, event):
79 | scene = context.scene
80 |
81 | res_x = scene.render.resolution_x
82 | res_y = scene.render.resolution_y
83 |
84 | if self.tab:
85 | self.mouse_pos = Vector([event.mouse_region_x, event.mouse_region_y])
86 | self.vec_act = self.mouse_pos - self.center_area
87 |
88 | addition = (self.vec_act.length - self.vec_prev.length) / self.vec_init.length
89 | if event.shift:
90 | addition /= self.slow_factor
91 |
92 | self.scale_prev += addition
93 |
94 | diff = float(self.scale_prev)
95 |
96 | self.vec_prev = Vector(self.vec_act)
97 |
98 | func_constrain_axis_mmb(
99 | self, context, event.type, event.value,
100 | self.sign_rot * context.scene.sequence_editor.active_strip.rotation_start)
101 |
102 | func_constrain_axis(
103 | self, context, event.type, event.value,
104 | self.sign_rot * context.scene.sequence_editor.active_strip.rotation_start)
105 |
106 | process_input(self, event.type, event.value)
107 | if self.key_val != '':
108 | try:
109 | diff = abs(float(self.key_val))
110 | except ValueError:
111 | pass
112 |
113 | diff_x = 1
114 | if self.axis_x:
115 | diff_x = diff
116 |
117 | diff_y = 1
118 | if self.axis_y:
119 | diff_y = diff
120 |
121 | precision = 5
122 | snap_distance = int(max([res_x, res_y]) / 100)
123 | if event.ctrl:
124 |
125 | if context.scene.seq_pivot_type == '2':
126 | origin_point = Vector([self.center_c2d.x + (res_x / 2), self.center_c2d.y + (res_y / 2)])
127 | else:
128 | origin_x = self.center_real.x + (res_x / 2)
129 | origin_y = self.center_real.y + (res_y / 2)
130 | origin_point = Vector([origin_x, origin_y])
131 |
132 | orig_left = self.original_group_box[0]
133 | trans_diff_l = (((origin_point.x - orig_left) * diff_x) - (origin_point.x - orig_left))
134 | current_left = orig_left - trans_diff_l
135 |
136 | orig_right = self.original_group_box[1]
137 | trans_diff_r = (((origin_point.x - orig_right) * diff_x) - (origin_point.x - orig_right))
138 | current_right = orig_right - trans_diff_r
139 |
140 | orig_bottom = self.original_group_box[2]
141 | trans_diff_b = (((origin_point.y - orig_bottom) * diff_y) - (origin_point.y - orig_bottom))
142 | current_bottom = orig_bottom - trans_diff_b
143 |
144 | orig_top = self.original_group_box[3]
145 | trans_diff_t = (((origin_point.y - orig_top) * diff_y) - (origin_point.y - orig_top))
146 | current_top = orig_top - trans_diff_t
147 |
148 | orientations = []
149 | line_locs = []
150 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
151 |
152 | for line in self.horizontal_interests:
153 |
154 | if (current_left < line + snap_distance and
155 | current_left > line - snap_distance and
156 | abs(origin_point.x - orig_left) > snap_distance):
157 | scale_to_line = (origin_point.x - line) / (origin_point.x - orig_left)
158 |
159 | if self.axis_y:
160 | diff_y *= (scale_to_line / diff_x)
161 | diff_x = scale_to_line
162 |
163 | line_locs.append((line * fac * preview_zoom) + offset_x)
164 | orientations.append("VERTICAL")
165 |
166 | break
167 |
168 | if (current_right > line - snap_distance and
169 | current_right < line + snap_distance and
170 | abs(origin_point.x - orig_right) > snap_distance):
171 | scale_to_line = (origin_point.x - line) / (origin_point.x - orig_right)
172 |
173 | if self.axis_y:
174 | diff_y *= (scale_to_line / diff_x)
175 | diff_x = scale_to_line
176 |
177 | line_locs.append((line * fac * preview_zoom) + offset_x)
178 | orientations.append("VERTICAL")
179 |
180 | for line in self.vertical_interests:
181 | if (current_bottom < line + snap_distance and
182 | current_bottom > line - snap_distance and
183 | abs(origin_point.y - orig_bottom) > snap_distance):
184 | scale_to_line = (origin_point.y - line) / (origin_point.y - orig_bottom)
185 |
186 | if self.axis_x:
187 | diff_x *= (scale_to_line / diff_y)
188 | diff_y = scale_to_line
189 |
190 | line_locs.append((line * fac * preview_zoom) + offset_y)
191 | orientations.append("HORIZONTAL")
192 |
193 | break
194 |
195 | if (current_top > line - snap_distance and
196 | current_top < line + snap_distance and
197 | abs(origin_point.y - orig_top) > snap_distance):
198 |
199 | scale_to_line = (origin_point.y - line) / (origin_point.y - orig_top)
200 | if self.axis_x:
201 | diff_x *= (scale_to_line / diff_y)
202 | diff_y = scale_to_line
203 |
204 | line_locs.append((line * fac * preview_zoom) + offset_y)
205 | orientations.append("HORIZONTAL")
206 |
207 | orientation = ""
208 | line_loc = None
209 | if len(orientations) > 1 and self.last_snap_orientation != "" and self.orientation_conflict_winner == -1:
210 | index = orientations.index(self.last_snap_orientation)
211 | orientations.pop(index)
212 | line_locs.pop(index)
213 |
214 | self.orientation_conflict_winner = int(not index)
215 |
216 | orientation = orientations[0]
217 | line_loc = line_locs[0]
218 |
219 | elif len(orientations) > 1 and self.last_snap_orientation == "" and self.orientation_conflict_winner == -1:
220 | self.orientation_conflict_winner = 0
221 | orientation = orientations[0]
222 | line_loc = line_locs[0]
223 |
224 | elif len(orientations) > 1:
225 | orientation = orientations[self.orientation_conflict_winner]
226 | line_loc = line_locs[self.orientation_conflict_winner]
227 |
228 | elif len(orientations) > 0:
229 | self.orientation_conflict_winner = 0
230 | orientation = orientations[0]
231 | line_loc = line_locs[0]
232 |
233 | if orientation != "" and (self.handle_snap == None or "RNA_HANDLE_REMOVED" in str(self.handle_snap)):
234 | args = (self, line_loc, orientation)
235 | self.handle_snap = bpy.types.SpaceSequenceEditor.draw_handler_add(
236 | draw_snap, args, 'PREVIEW', 'POST_PIXEL')
237 | self.last_snap_orientation = orientation
238 | self.last_line_loc = line_loc
239 | elif (orientation != self.last_snap_orientation or line_loc != self.last_line_loc) and self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
240 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
241 | self.last_snap_orientation = orientation
242 | self.last_line_loc = line_loc
243 |
244 | elif self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
245 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
246 |
247 | info_x = round(diff_x, precision)
248 | info_y = round(diff_y, precision)
249 | if not self.axis_x:
250 | context.area.header_text_set("Scale: %.4f along local Y" % info_y)
251 | if not self.axis_y:
252 | context.area.header_text_set("Scale: %.4f along local X" % info_x)
253 | if self.axis_x and self.axis_y :
254 | context.area.header_text_set("Scale X:%.4f Y: %.4f" % (info_x, info_y))
255 |
256 | for strip, init_s, init_t in zip(self.tab, self.tab_init_s, self.tab_init_t):
257 | strip.scale_start_x = init_s[0] * round(diff_x, precision)
258 | strip.scale_start_y = init_s[1] * round(diff_y, precision)
259 |
260 | flip_x = 1
261 | if strip.use_flip_x:
262 | flip_x = -1
263 |
264 | flip_y = 1
265 | if strip.use_flip_y:
266 | flip_y = -1
267 |
268 | if context.scene.seq_pivot_type in ['0', '3']:
269 | strip.translate_start_x = set_pos_x(strip, (init_t[0] - flip_x * self.center_real.x) * round(diff_x, precision) + flip_x * self.center_real.x)
270 | strip.translate_start_y = set_pos_y(strip, (init_t[1] - flip_y * self.center_real.y) * round(diff_y, precision) + flip_y * self.center_real.y)
271 |
272 | if context.scene.seq_pivot_type == '2':
273 | strip.translate_start_x = set_pos_x(strip, (init_t[0] - self.center_c2d.x) * round(diff_x, precision) + self.center_c2d.x)
274 | strip.translate_start_y = set_pos_y(strip, (init_t[1] - self.center_c2d.y) * round(diff_y, precision) + self.center_c2d.y)
275 |
276 | if (event.type == 'LEFTMOUSE' or
277 | event.type == 'RET' or
278 | event.type == 'NUMPAD_ENTER' or
279 | not self.tab):
280 |
281 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_line, 'PREVIEW')
282 |
283 | if self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
284 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
285 |
286 | scene = context.scene
287 | if scene.tool_settings.use_keyframe_insert_auto:
288 | pivot_type = context.scene.seq_pivot_type
289 | if (pivot_type == '0' and len(self.tab) > 1) or pivot_type == '2':
290 | for strip in self.tab:
291 | strip.keyframe_insert(data_path='translate_start_x')
292 | strip.keyframe_insert(data_path='translate_start_y')
293 | strip.keyframe_insert(data_path='scale_start_x')
294 | strip.keyframe_insert(data_path='scale_start_y')
295 | elif pivot_type == '1' or pivot_type == '3' or (pivot_type == '0' and len(self.tab) == 1):
296 | for strip in self.tab:
297 | strip.keyframe_insert(data_path='scale_start_x')
298 | strip.keyframe_insert(data_path='scale_start_y')
299 |
300 | if self.handle_axes:
301 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_axes, 'PREVIEW')
302 | context.area.header_text_set(None)
303 | return {'FINISHED'}
304 |
305 | if event.type == 'ESC' or event.type == 'RIGHTMOUSE':
306 | for strip, init_s, init_t in zip(self.tab, self.tab_init_s, self.tab_init_t):
307 | strip.scale_start_x = init_s[0]
308 | strip.scale_start_y = init_s[1]
309 | strip.translate_start_x = set_pos_x(strip, init_t[0])
310 | strip.translate_start_y = set_pos_y(strip, init_t[1])
311 |
312 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_line, 'PREVIEW')
313 |
314 | if self.handle_snap != None and not "RNA_HANDLE_REMOVED" in str(self.handle_snap):
315 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_snap, 'PREVIEW')
316 |
317 | if self.handle_axes:
318 | bpy.types.SpaceSequenceEditor.draw_handler_remove(self.handle_axes, 'PREVIEW')
319 | context.area.header_text_set(None)
320 | return {'FINISHED'}
321 |
322 | else:
323 | return {'FINISHED'}
324 | return {'RUNNING_MODAL'}
325 |
326 | def invoke(self, context, event):
327 | scene = context.scene
328 | bpy.ops.vse_transform_tools.initialize_pivot()
329 |
330 | res_x = scene.render.resolution_x
331 | res_y = scene.render.resolution_y
332 |
333 | self.horizontal_interests = [0, res_x]
334 | self.vertical_interests = [0, res_y]
335 |
336 | if event.alt :
337 | selected = context.selected_sequences
338 | for strip in selected:
339 | transform = get_highest_transform(strip)
340 | if transform.type == 'TRANSFORM':
341 | reset_transform_scale(transform)
342 | else:
343 | transform.use_translation = True
344 | transform.blend_type = 'ALPHA_OVER'
345 |
346 | return {'FINISHED'}
347 |
348 | else:
349 | self.tab_init_s = []
350 | self.tab_init_t = []
351 |
352 | self.tab = []
353 |
354 | self.center_real = Vector([0, 0])
355 | self.center_area = Vector([0, 0])
356 |
357 | self.key_val = ''
358 | self.old_key_val = '0'
359 |
360 | fac = get_res_factor()
361 |
362 | scaled_count = 0
363 |
364 | self.tab = ensure_transforms()
365 | visible_strips = get_visible_strips()
366 |
367 | for strip in visible_strips:
368 | if strip not in self.tab:
369 | left, right, bottom, top = get_group_box([strip])
370 |
371 | self.horizontal_interests.append(left)
372 | self.horizontal_interests.append(right)
373 |
374 | self.vertical_interests.append(bottom)
375 | self.vertical_interests.append(top)
376 |
377 | for strip in self.tab:
378 | strip.select = True
379 | self.tab_init_s.append([strip.scale_start_x, strip.scale_start_y])
380 | self.tab_init_t.append([get_pos_x(strip), get_pos_y(strip)])
381 |
382 | flip_x = 1
383 | if strip.use_flip_x:
384 | flip_x = -1
385 |
386 | flip_y = 1
387 | if strip.use_flip_y:
388 | flip_y = -1
389 |
390 | self.sign_rot = flip_x * flip_y
391 |
392 | center_x = flip_x * get_pos_x(strip)
393 | center_y = flip_y * get_pos_y(strip)
394 | self.center_real += Vector([center_x, center_y])
395 | self.center_area += Vector([center_x, center_y])
396 |
397 | if len(self.tab) > 0:
398 | self.center_real /= len(self.tab)
399 | self.original_group_box = get_group_box(self.tab)
400 | if scene.seq_pivot_type == '2':
401 | cursor_x = context.scene.seq_cursor2d_loc[0]
402 | cursor_y = context.scene.seq_cursor2d_loc[1]
403 | cursor_pos = context.region.view2d.view_to_region(cursor_x, cursor_y)
404 | self.center_area = Vector(cursor_pos)
405 |
406 | self.center_c2d = Vector((flip_x * context.scene.seq_cursor2d_loc[0], flip_y * context.scene.seq_cursor2d_loc[1])) / fac
407 |
408 | elif scene.seq_pivot_type == '3':
409 | active_strip = scene.sequence_editor.active_strip
410 |
411 | flip_x = 1
412 | if active_strip.use_flip_x:
413 | flip_x = -1
414 |
415 | flip_y = 1
416 | if active_strip.use_flip_y:
417 | flip_y = -1
418 |
419 | pos_x = flip_x * get_pos_x(active_strip)
420 | pos_y = flip_y * get_pos_y(active_strip)
421 |
422 | self.center_real = Vector([pos_x, pos_y])
423 |
424 | pos = context.region.view2d.view_to_region(pos_x * fac, pos_y * fac)
425 | self.center_area = Vector(pos)
426 |
427 | else:
428 | self.center_area /= len(self.tab)
429 |
430 | pos_x = self.center_area.x * fac
431 | pos_y = self.center_area.y * fac
432 | pos = context.region.view2d.view_to_region(pos_x, pos_y,clip=False)
433 | self.center_area = Vector(pos)
434 |
435 | self.vec_init = Vector(
436 | (event.mouse_region_x, event.mouse_region_y))
437 | self.vec_init -= self.center_area
438 |
439 | self.vec_prev = Vector(self.vec_init)
440 |
441 | args = (self, context)
442 | self.handle_line = bpy.types.SpaceSequenceEditor.draw_handler_add(
443 | draw_px_point, args, 'PREVIEW', 'POST_PIXEL')
444 |
445 | context.window_manager.modal_handler_add(self)
446 | return {'RUNNING_MODAL'}
447 | return {'FINISHED'}
448 |
449 |
450 | def reset_transform_scale(strip):
451 | """Reset a strip to it's factor"""
452 | strip_in = strip.input_1
453 | res_x = bpy.context.scene.render.resolution_x
454 | res_y = bpy.context.scene.render.resolution_y
455 |
456 | if hasattr(strip_in, 'elements'):
457 | len_crop_x = strip_in.elements[0].orig_width
458 | len_crop_y = strip_in.elements[0].orig_height
459 |
460 | if strip_in.use_crop:
461 | len_crop_x -= (strip_in.crop.min_x + strip_in.crop.max_x)
462 | len_crop_y -= (strip_in.crop.min_y + strip_in.crop.max_y)
463 |
464 | ratio_x = len_crop_x / res_x
465 | ratio_y = len_crop_y / res_y
466 |
467 | strip.scale_start_x = ratio_x
468 | strip.scale_start_y = ratio_y
469 |
470 | elif strip_in.type == "SCENE":
471 | strip_scene = strip_in.scene
472 |
473 | ratio_x = strip_scene.render.resolution_x / res_x
474 | ratio_y = strip_scene.render.resolution_y / res_y
475 |
476 | strip.scale_start_x = ratio_x
477 | strip.scale_start_y = ratio_y
478 |
479 | else:
480 | strip.scale_start_x = 1.0
481 | strip.scale_start_y = 1.0
482 |
--------------------------------------------------------------------------------
/operators/select/__init__.py:
--------------------------------------------------------------------------------
1 | from .select import PREV_OT_select
2 |
--------------------------------------------------------------------------------
/operators/select/select.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from mathutils import Vector
4 | from mathutils.geometry import intersect_point_quad_2d
5 |
6 | from ..utils.geometry import get_strip_corners
7 | from ..utils.geometry import get_preview_offset
8 | from ..utils.geometry import mouse_to_res
9 |
10 | from ..utils.selection import get_visible_strips
11 |
12 | from ..utils.geometry import get_preview_offset
13 | from ..utils.geometry import get_strip_corners
14 |
15 | from ..utils.draw import draw_line
16 |
17 | def draw_select(context, seconds, fadeout_duration):
18 | active_color = context.preferences.themes[0].sequence_editor.active_strip
19 | select_color = context.preferences.themes[0].sequence_editor.selected_strip
20 | outline_color = (0, 0, 0, 0.2)
21 |
22 | opacity = 1 - (seconds / fadeout_duration)
23 |
24 | active_strip = context.scene.sequence_editor.active_strip
25 |
26 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
27 |
28 | for strip in context.selected_sequences:
29 | if strip == active_strip:
30 | color = (active_color[0], active_color[1], active_color[2], opacity)
31 | else:
32 | color = (select_color[0], select_color[1], select_color[2], opacity)
33 |
34 | corners = get_strip_corners(strip)
35 | vertices = []
36 | for corner in corners:
37 | corner_x = int(corner[0] * preview_zoom * fac) + offset_x
38 | corner_y = int(corner[1] * preview_zoom * fac) + offset_y
39 | vertices.append([corner_x, corner_y])
40 |
41 | draw_line(vertices[0], vertices[1], 2, outline_color)
42 | draw_line(vertices[1], vertices[2], 2, outline_color)
43 | draw_line(vertices[2], vertices[3], 2, outline_color)
44 | draw_line(vertices[3], vertices[0], 2, outline_color)
45 |
46 | draw_line(vertices[0], vertices[1], 1, color)
47 | draw_line(vertices[1], vertices[2], 1, color)
48 | draw_line(vertices[2], vertices[3], 1, color)
49 | draw_line(vertices[3], vertices[0], 1, color)
50 |
51 |
52 | class PREV_OT_select(bpy.types.Operator):
53 | """
54 | Selects a strip(s) when clicked
55 | """
56 | bl_idname = "vse_transform_tools.select"
57 | bl_label = "Select"
58 | bl_description = "Select visible sequences from the Image Preview"
59 |
60 | timer = None
61 | seconds = 0
62 | fadeout_duration = 100
63 | handle_select = None
64 |
65 | @classmethod
66 | def poll(self, context):
67 | if context.scene.sequence_editor:
68 | return True
69 | return False
70 |
71 | def modal(self, context, event):
72 | context.area.tag_redraw()
73 |
74 | if event.type == 'TIMER':
75 | self.seconds += 0.01
76 |
77 | if self.seconds > self.fadeout_duration:
78 | context.window_manager.event_timer_remove(self.timer)
79 |
80 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
81 | self.handle_select, 'PREVIEW')
82 |
83 | return {'FINISHED'}
84 |
85 | return {'PASS_THROUGH'}
86 |
87 | def invoke(self, context, event):
88 | #bpy.ops.vse_transform_tools.initialize_pivot()
89 |
90 | scene = context.scene
91 |
92 | mouse_x = event.mouse_region_x
93 | mouse_y = event.mouse_region_y
94 |
95 | mouse_vec = Vector([mouse_x, mouse_y])
96 | vector = mouse_to_res(mouse_vec)
97 |
98 | current_frame = scene.frame_current
99 | current_strips = []
100 |
101 | sequence_editor = scene.sequence_editor
102 | selection_list = []
103 |
104 | strips = get_visible_strips()
105 |
106 | if 'MOUSE' in event.type:
107 | for strip in reversed(strips):
108 |
109 | corners = get_strip_corners(strip)
110 |
111 | bottom_left = Vector(corners[0])
112 | top_left = Vector(corners[1])
113 | top_right = Vector(corners[2])
114 | bottom_right = Vector(corners[3])
115 |
116 | intersects = intersect_point_quad_2d(
117 | vector, bottom_left, top_left, top_right,
118 | bottom_right)
119 |
120 | if intersects and not event.type == 'A':
121 | selection_list.append(strip)
122 | if not event.shift:
123 | bpy.ops.sequencer.select_all(action='DESELECT')
124 | strip.select = True
125 | scene.sequence_editor.active_strip = strip
126 | break
127 | else:
128 | if not strip.select:
129 | strip.select = True
130 | scene.sequence_editor.active_strip = strip
131 | break
132 | else:
133 | strip.select = True
134 | break
135 | if not selection_list and not event.shift and not event.type == 'A':
136 | bpy.ops.sequencer.select_all(action='DESELECT')
137 |
138 | if strip.blend_type in ['CROSS', 'REPLACE']:
139 | return {'FINISHED'}
140 |
141 | elif event.type == 'A':
142 | all_selected = True
143 | for strip in strips:
144 | if not strip.select:
145 | all_selected = False
146 |
147 | bpy.ops.sequencer.select_all(action='DESELECT')
148 |
149 | if not all_selected:
150 | for strip in strips:
151 | strip.select = True
152 |
153 | args = (context, self.seconds, self.fadeout_duration)
154 | self.handle_select = bpy.types.SpaceSequenceEditor.draw_handler_add(
155 | draw_select, args, 'PREVIEW', 'POST_PIXEL')
156 |
157 | self.timer = context.window_manager.event_timer_add(0.01, window=context.window)
158 |
159 | context.window_manager.modal_handler_add(self)
160 |
161 | return {'RUNNING_MODAL'}
162 |
--------------------------------------------------------------------------------
/operators/set_cursor2d/__init__.py:
--------------------------------------------------------------------------------
1 | from .set_cursor2d import PREV_OT_set_cursor_2d
2 |
--------------------------------------------------------------------------------
/operators/set_cursor2d/get_important_edge_points.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from ..utils.selection import get_visible_strips
6 |
7 | from ..utils.geometry import rotate_point
8 | from ..utils.geometry import get_transform_box
9 | from ..utils.geometry import get_strip_box
10 | from ..utils.geometry import get_strip_corners
11 |
12 |
13 | def get_important_edge_points():
14 | """
15 | Get the edge locations for where a user may want to snap the cursor
16 | to.
17 |
18 | This is the top left, top middle, top right, right middle, bottom
19 | right, bottom middle, bottom left, left middle, and center points of
20 | all visible strips in the preview window.::
21 |
22 | x------x------x
23 | | |
24 | x x x
25 | | |
26 | x------x------x
27 |
28 | Returns
29 | -------
30 | list of mathutils.Vector
31 | """
32 | scene = bpy.context.scene
33 |
34 | strips = get_visible_strips()
35 |
36 | res_x = scene.render.resolution_x
37 | res_y = scene.render.resolution_y
38 |
39 | bl = Vector([0, 0])
40 | l = Vector([0, res_y / 2])
41 | tl = Vector([0, res_y])
42 | t = Vector([res_x / 2, res_y])
43 | tr = Vector([res_x, res_y])
44 | r = Vector([res_x, res_y / 2])
45 | br = Vector([res_x, 0])
46 | b = Vector([res_x / 2, 0])
47 | origin = Vector([res_x / 2, res_y / 2])
48 |
49 | vectors = [bl, l, tl, t, tr, r, br, b, origin]
50 |
51 | for vec in vectors:
52 | vec.x -= (res_x / 2)
53 | vec.y -= (res_y / 2)
54 |
55 |
56 | important_edge_points = vectors
57 | for strip in strips:
58 | if strip.type == "TRANSFORM":
59 | left, right, bottom, top = get_transform_box(strip)
60 |
61 | else:
62 | left, right, bottom, top = get_strip_box(strip)
63 |
64 | mid_x = left+ ((right - left) / 2)
65 | mid_y = bottom + ((top - bottom) / 2)
66 |
67 | l = Vector([left, mid_y])
68 | r = Vector([right, mid_y])
69 | t = Vector([mid_x, top])
70 | b = Vector([mid_x, bottom])
71 |
72 | origin = Vector([mid_x, mid_y])
73 |
74 | if strip.type == "TRANSFORM":
75 | angle = math.radians(strip.rotation_start)
76 |
77 | l = rotate_point(l, angle, origin=origin)
78 | r = rotate_point(r, angle, origin=origin)
79 | t = rotate_point(t, angle, origin=origin)
80 | b = rotate_point(b, angle, origin=origin)
81 |
82 | bl, tl, tr, br = get_strip_corners(strip)
83 |
84 | vectors = [bl, l, tl, t, tr, r, br, b, origin]
85 |
86 | for vec in vectors:
87 | vec.x -= (res_x / 2)
88 | vec.y -= (res_y / 2)
89 |
90 | important_edge_points.extend(vectors)
91 |
92 | return important_edge_points
93 |
--------------------------------------------------------------------------------
/operators/set_cursor2d/set_cursor2d.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from .get_important_edge_points import get_important_edge_points
6 |
7 |
8 | class PREV_OT_set_cursor_2d(bpy.types.Operator):
9 | """
10 | Set the pivot point (point of origin) location. This will affect
11 | how strips are rotated and scaled.
12 | """
13 | bl_label = "Set Cursor2D"
14 | bl_idname = "vse_transform_tools.set_cursor2d"
15 | bl_description = "Set the pivot point location"
16 |
17 | @classmethod
18 | def poll(cls, context):
19 | if (context.scene.sequence_editor and
20 | context.scene.seq_pivot_type == '2'):
21 | return True
22 | return False
23 |
24 | def invoke(self, context, event):
25 | bpy.ops.vse_transform_tools.initialize_pivot()
26 |
27 | mouse_x = event.mouse_region_x
28 | mouse_y = event.mouse_region_y
29 |
30 | pos = context.region.view2d.region_to_view(mouse_x, mouse_y)
31 | mouse_pos = Vector(pos)
32 |
33 | if event.ctrl:
34 | snap_points = get_important_edge_points()
35 | point = min(snap_points, key = lambda x: (x - mouse_pos).length)
36 | point.x = round(point.x)
37 | point.y = round(point.y)
38 |
39 | context.scene.seq_cursor2d_loc = [int(point.x), int(point.y)]
40 |
41 | else:
42 | context.scene.seq_cursor2d_loc = [round(pos[0]), round(pos[1])]
43 |
44 | return {'PASS_THROUGH'}
45 |
--------------------------------------------------------------------------------
/operators/track_transform/__init__.py:
--------------------------------------------------------------------------------
1 | from .track_transform import SEQUENCER_OT_track_transform
2 |
--------------------------------------------------------------------------------
/operators/track_transform/track_transform.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from ..utils.selection import get_input_tree
3 | import math
4 |
5 |
6 | class SEQUENCER_OT_track_transform(bpy.types.Operator):
7 | """
8 | Use a pair of track points to pin a strip to another. The UI for
9 | this tool is located in the menu to the right of the sequencer in
10 | the "Tools" submenu.
11 |
12 | 
13 |
14 | To pin rotation and/or scale, you must use 2 tracking points.
15 |
16 | More information on [this youtube video](https://www.youtube.com/watch?v=X885Uv1dzFY)
17 | """
18 | bl_idname = "vse_transform_tools.track_transform"
19 | bl_label = "Track Transform"
20 | bl_description = "Pin selected transform strip to tracker(s)"
21 | bl_options = {"REGISTER", "UNDO"}
22 |
23 | @classmethod
24 | def poll(cls, context):
25 | scene = context.scene
26 |
27 | if (scene.vse_transform_tools_tracker_1 != "None" and
28 | scene.sequence_editor and
29 | scene.sequence_editor.active_strip and
30 | scene.sequence_editor.active_strip.type == "TRANSFORM"):
31 | return True
32 | return False
33 |
34 | def execute(self, context):
35 | scene = context.scene
36 | res_x = scene.render.resolution_x
37 | res_y = scene.render.resolution_y
38 |
39 | tracker_names = []
40 |
41 | for movieclip in bpy.data.movieclips:
42 | for track in movieclip.tracking.tracks:
43 | if track.name == scene.vse_transform_tools_tracker_1:
44 | pos_track = track
45 | break
46 |
47 | start_frame = scene.frame_current
48 |
49 | offset_x = -1
50 | offset_y = -1
51 | for marker in pos_track.markers:
52 | if marker.frame == scene.frame_current:
53 | offset_x = marker.co.x * res_x
54 | offset_y = marker.co.y * res_y
55 | break
56 |
57 | if offset_x == -1 or offset_y == -1:
58 | offset_x = pos_track.markers[0].co.x * res_x
59 | offset_y = pos_track.markers[0].co.y * res_y
60 |
61 | active = scene.sequence_editor.active_strip
62 |
63 | if active.translation_unit == "PERCENT":
64 | active.translate_start_x = (active.translate_start_x - ((offset_x / res_x) * 100) + 50) / 2
65 | active.translate_start_y = (active.translate_start_y - ((offset_y / res_y) * 100) + 50) / 2
66 |
67 | else:
68 | active.translate_start_x = ((active.translate_start_x + (res_x / 2)) - offset_x) / 2
69 | active.translate_start_y = ((active.translate_start_y + (res_y / 2)) - offset_y) / 2
70 |
71 | active.scale_start_x = active.scale_start_x / 2
72 | active.scale_start_y = active.scale_start_y / 2
73 |
74 | for strip in context.selected_sequences:
75 | if not strip == active:
76 | strip.select = False
77 |
78 | bpy.ops.sequencer.effect_strip_add(type="TRANSFORM")
79 |
80 | transform_strip = context.scene.sequence_editor.active_strip
81 | transform_strip.name = "[TRACKED]-%s" % strip.name
82 | transform_strip.blend_type = 'ALPHA_OVER'
83 | transform_strip.use_uniform_scale = True
84 | transform_strip.scale_start_x = 2.0
85 |
86 | tree = get_input_tree(transform_strip)[1::]
87 | for child in tree:
88 | child.mute = True
89 |
90 | for marker in pos_track.markers:
91 | scene.frame_current = marker.frame
92 | transform_strip.translate_start_x = ((((marker.co.x * res_x) - (res_x / 2)) / res_x) * 100)
93 | transform_strip.translate_start_y = ((((marker.co.y * res_y) - (res_y / 2)) / res_y) * 100)
94 |
95 | transform_strip.keyframe_insert(
96 | data_path="translate_start_x", frame=scene.frame_current)
97 | transform_strip.keyframe_insert(
98 | data_path="translate_start_y", frame=scene.frame_current)
99 |
100 | ref_track = None
101 | if scene.vse_transform_tools_use_rotation or scene.vse_transform_tools_use_scale:
102 | for movieclip in bpy.data.movieclips:
103 | for track in movieclip.tracking.tracks:
104 | if track.name == scene.vse_transform_tools_tracker_2:
105 | ref_track = track
106 | break
107 |
108 | if scene.vse_transform_tools_use_rotation and ref_track:
109 | for marker in ref_track.markers:
110 | if marker.frame == start_frame:
111 |
112 | p1 = None
113 | for pos_marker in pos_track.markers:
114 | if pos_marker.frame == marker.frame:
115 | p1 = (pos_marker.co.x * res_x, pos_marker.co.y * res_y)
116 | break
117 |
118 | if not p1:
119 | return {"FINISHED"}
120 |
121 | p2 = (marker.co.x * res_x, marker.co.y * res_y)
122 | offset_angle = calculate_angle(p1, p2)
123 | break
124 |
125 | for marker in ref_track.markers:
126 | scene.frame_current = marker.frame
127 | p1 = None
128 | for pos_marker in pos_track.markers:
129 | if pos_marker.frame == marker.frame:
130 | p1 = (pos_marker.co.x * res_x, pos_marker.co.y * res_y)
131 | break
132 | if not p1:
133 | break
134 |
135 | p2 = (marker.co.x * res_x, marker.co.y * res_y)
136 | angle = calculate_angle(p1, p2) - offset_angle
137 |
138 | transform_strip.rotation_start = angle
139 | transform_strip.keyframe_insert(
140 | data_path="rotation_start", frame=scene.frame_current)
141 |
142 | if scene.vse_transform_tools_use_scale and ref_track:
143 | for marker in ref_track.markers:
144 | if marker.frame == start_frame:
145 |
146 | p1 = None
147 | for pos_marker in pos_track.markers:
148 | if pos_marker.frame == marker.frame:
149 | p1 = (pos_marker.co.x * res_x, pos_marker.co.y * res_y)
150 | break
151 |
152 | if not p1:
153 | return {"FINISHED"}
154 |
155 | p2 = (marker.co.x * res_x, marker.co.y * res_y)
156 | init_distance = distance_formula(p1, p2)
157 |
158 | for marker in ref_track.markers:
159 | scene.frame_current = marker.frame
160 | p1 = None
161 | for pos_marker in pos_track.markers:
162 | if pos_marker.frame == marker.frame:
163 | p1 = (pos_marker.co.x * res_x, pos_marker.co.y * res_y)
164 | break
165 | if not p1:
166 | break
167 |
168 | p2 = (marker.co.x * res_x, marker.co.y * res_y)
169 | distance = distance_formula(p1, p2)
170 | scl = (distance / init_distance) * 2.0
171 | transform_strip.scale_start_x = scl
172 | transform_strip.keyframe_insert(
173 | data_path="scale_start_x", frame=scene.frame_current)
174 |
175 | scene.frame_current = start_frame
176 |
177 | return {'FINISHED'}
178 |
179 |
180 | def calculate_angle(p1, p2):
181 | """
182 | Calculate the angle formed by p1, p2, and the x axis
183 |
184 | Parameters
185 | ----------
186 | p1 : list of float
187 | X & Y coordinates
188 | p2 : list of float
189 | X & Y coordinates
190 |
191 | Returns
192 | -------
193 | angle : float
194 | """
195 | a = p2[1] - p1[1]
196 | b = p2[0] - p1[0]
197 |
198 | p1p2 = math.degrees(math.atan2(a, b))
199 |
200 | return p1p2
201 |
202 |
203 | def distance_formula(p1, p2):
204 | """
205 | Calculate the distance between 2 points on a 2D Cartesian coordinate plane
206 | """
207 | x = p2[0] - p1[0]
208 | y = p2[1] - p1[1]
209 |
210 | distance = math.sqrt(x**2 + y**2)
211 | return distance
212 |
--------------------------------------------------------------------------------
/operators/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .func_constrain_axis import func_constrain_axis
2 | from .func_constrain_axis_mmb import func_constrain_axis_mmb
3 | from .process_input import process_input
--------------------------------------------------------------------------------
/operators/utils/draw/__init__.py:
--------------------------------------------------------------------------------
1 | from .draw_axes import draw_axes
2 | from .draw_line import draw_line
3 | from .draw_px_point import draw_px_point
4 | from .draw_snap import draw_snap
5 | from .draw_square import draw_square
6 |
--------------------------------------------------------------------------------
/operators/utils/draw/colors.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions to get colors from the user's theme. The functions convert colors to lists so we
3 | can use them for drawing directly.
4 | """
5 |
6 |
7 | def get_color_gizmo_primary(context):
8 | color = context.preferences.themes[0].user_interface.gizmo_primary
9 | return _color_to_list(color)
10 |
11 |
12 | def get_color_gizmo_secondary(context):
13 | color = context.preferences.themes[0].user_interface.gizmo_secondary
14 | return _color_to_list(color)
15 |
16 |
17 | def get_color_axis_x(context):
18 | color = context.preferences.themes[0].user_interface.axis_x
19 | return _color_to_list(color)
20 |
21 |
22 | def get_color_axis_y(context):
23 | color = context.preferences.themes[0].user_interface.axis_y
24 | return _color_to_list(color)
25 |
26 |
27 | def get_color_axis_z(context):
28 | color = context.preferences.themes[0].user_interface.axis_z
29 | return _color_to_list(color)
30 |
31 |
32 | def _color_to_list(color):
33 | """Converts a Blender Color to a list of 4 color values to use with shaders and drawing"""
34 | return list(color) + [1.0]
35 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_arrows.py:
--------------------------------------------------------------------------------
1 | import math
2 | from .draw_line import draw_line
3 |
4 | def get_next_point(origin, angle, distance):
5 | """
6 | Get the next point given an origin, an angle, and a distance to go
7 | """
8 | v2_x = origin[0] + (math.cos(angle) * distance)
9 | v2_y = origin[1] + (math.sin(angle) * distance)
10 |
11 | return [v2_x, v2_y]
12 |
13 |
14 | def draw_arrows(v1, v2, thickness, arrow_length, color, angle_offset=0):
15 | if v2[0] == v1[0]:
16 | if v2[1] > v1[1]:
17 | angle = math.radians(-90)
18 | else:
19 | angle = math.radians(90)
20 | else:
21 | angle = math.atan((v2[1] - v1[1]) / (v2[0] - v1[0]))
22 |
23 | if v2[0] > v1[0]:
24 | pass
25 | elif v2[1] < v1[1]:
26 | angle -= math.radians(180)
27 |
28 | else:
29 | angle += math.radians(180)
30 |
31 | angle += math.radians(angle_offset)
32 |
33 | r_start = get_next_point(v2, angle, thickness * 3)
34 | r_end = get_next_point(r_start, angle, arrow_length)
35 |
36 | r_upper_ala_end = get_next_point(r_end, angle + math.radians(210), arrow_length * 0.667)
37 | r_lower_ala_end = get_next_point(r_end, angle + math.radians(-210), arrow_length * 0.667)
38 |
39 |
40 | angle += math.radians(180)
41 |
42 | l_start = get_next_point(v2, angle, thickness * 3)
43 | l_end = get_next_point(l_start, angle, arrow_length)
44 |
45 | l_upper_ala_end = get_next_point(l_end, angle + math.radians(-210), arrow_length * 0.667)
46 | l_lower_ala_end = get_next_point(l_end, angle + math.radians(210), arrow_length * 0.667)
47 |
48 | draw_line(r_start, r_end, thickness, color)
49 | draw_line(r_end, r_upper_ala_end, thickness, color)
50 | draw_line(r_end, r_lower_ala_end, thickness, color)
51 |
52 | draw_line(l_start, l_end, thickness, color)
53 | draw_line(l_end, l_upper_ala_end, thickness, color)
54 | draw_line(l_end, l_lower_ala_end, thickness, color)
55 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_axes.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from ..geometry.get_group_box import get_group_box
4 | from ..geometry.get_preview_offset import get_preview_offset
5 |
6 | from .draw_line import draw_line
7 | from .draw_stippled_line import draw_stippled_line
8 |
9 | from .colors import get_color_axis_x, get_color_axis_y
10 |
11 |
12 | def draw_axes(self, context, angle):
13 | transforms = []
14 | strips = bpy.context.selected_sequences
15 | for strip in strips:
16 | if strip.type == 'TRANSFORM':
17 | transforms.append(strip)
18 |
19 | group_box = get_group_box(transforms)
20 | min_left, max_right, min_bottom, max_top = group_box
21 | group_width = max_right - min_left
22 | group_height = max_top - min_bottom
23 |
24 | group_pos_x = min_left + (group_width / 2)
25 | group_pos_y = min_bottom + (group_height / 2)
26 |
27 | offset_x, offset_y, fac, preview_zoom = get_preview_offset()
28 |
29 | x = (group_pos_x * fac * preview_zoom) + offset_x
30 | y = (group_pos_y * fac * preview_zoom) + offset_y
31 |
32 | color_axis_x = get_color_axis_x(context)
33 | color_axis_y = get_color_axis_y(context)
34 | thickness = 1
35 | stipple_length = 10
36 | far = 10000
37 |
38 | if self.choose_axis and not self.axis_y:
39 | draw_line([-far, y], [far, y], 2, color_axis_x)
40 | draw_stippled_line([x, -far], [x, far], thickness, stipple_length, color_axis_y)
41 | elif self.choose_axis and not self.axis_x:
42 | draw_stippled_line([-far, y], [far, y], thickness, stipple_length, color_axis_x)
43 | draw_line([x, -far], [x, far], thickness, color_axis_y)
44 | elif self.axis_x:
45 | draw_line([-far, y], [far, y], thickness, color_axis_x)
46 | elif self.axis_y:
47 | draw_line([x, -far], [x, far], thickness, color_axis_y)
48 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_line.py:
--------------------------------------------------------------------------------
1 | import math
2 | import gpu
3 | from gpu_extras.batch import batch_for_shader
4 |
5 | def draw_line(v1, v2, thickness, color):
6 | if (v2[0] == v1[0]):
7 | x = thickness
8 | y = 0
9 | elif (v2[1] == v1[1]):
10 | x = 0
11 | y = thickness
12 |
13 | else:
14 | angle = math.atan((v2[1] - v1[1]) / (v2[0] - v1[0]))
15 | x = thickness * math.sin(angle)
16 | y = thickness * math.cos(angle)
17 |
18 | vertices = (
19 | (v1[0] + x, v1[1] - y),
20 | (v2[0] + x, v2[1] - y),
21 | (v1[0] - x, v1[1] + y),
22 | (v2[0] - x, v2[1] + y),
23 | )
24 |
25 | indices = ((0, 1, 2), (2, 1, 3))
26 |
27 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
28 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
29 |
30 | shader.bind()
31 | shader.uniform_float("color", color)
32 | batch.draw(shader)
33 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_px_point.py:
--------------------------------------------------------------------------------
1 | from mathutils import Vector
2 | from .draw_stippled_line import draw_stippled_line
3 | from .draw_arrows import draw_arrows
4 |
5 | def draw_px_point(self, context):
6 | """
7 | Draws the handle seen when rotating or scaling
8 | """
9 | # Stopped working after API change --> theme = context.user_preferences.themes['Default']
10 | #active_color = theme.view_3d.object_active
11 |
12 | #color = (active_color[0], active_color[1], active_color[2], 1.0)
13 |
14 | color = (0, 0, 0, 1.0)
15 |
16 | v1 = [self.center_area.x, self.center_area.y]
17 | v2 = [self.mouse_pos.x, self.mouse_pos.y]
18 |
19 | if self.mouse_pos != Vector([-1, -1]):
20 | draw_stippled_line(v1, v2, 0.5, 4, color)
21 | if hasattr(self, 'rot_prev'):
22 | draw_arrows(v1, v2, 1.5, 12, color, angle_offset=90)
23 | else:
24 | draw_arrows(v1, v2, 1.5, 12, color)
25 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_snap.py:
--------------------------------------------------------------------------------
1 | import bgl
2 | from mathutils import Vector
3 |
4 | from .draw_line import draw_line
5 |
6 |
7 | def draw_snap(self, loc, orientation):
8 | """
9 | Draws the snap lines
10 | """
11 | color = (1.0, 1.0, 0.0, 0.5)
12 | outline_color = (0, 0, 0, 0.2)
13 |
14 | if orientation == "VERTICAL":
15 | v1 = [loc, -10000]
16 | v2 = [loc, 10000]
17 |
18 | elif orientation == "HORIZONTAL":
19 | v1 = [-10000, loc]
20 | v2 = [10000, loc]
21 |
22 | draw_line(v1, v2, 1.5, outline_color)
23 | draw_line(v1, v2, 0.5, color)
24 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_square.py:
--------------------------------------------------------------------------------
1 | import gpu
2 | from gpu_extras.batch import batch_for_shader
3 |
4 | def draw_square(vertex, thickness, color):
5 | """
6 | Draws a square
7 | """
8 | r = thickness / 2
9 |
10 | vertices = (
11 | (vertex[0] - r, vertex[1] - r),
12 | (vertex[0] + r, vertex[1] - r),
13 | (vertex[0] - r, vertex[1] + r),
14 | (vertex[0] + r, vertex[1] + r)
15 | )
16 |
17 | indices = ((0, 1, 2), (2, 1, 3))
18 |
19 | shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
20 | batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
21 |
22 | shader.bind()
23 | shader.uniform_float("color", color)
24 | batch.draw(shader)
25 |
--------------------------------------------------------------------------------
/operators/utils/draw/draw_stippled_line.py:
--------------------------------------------------------------------------------
1 | import math
2 | from math import sqrt
3 | import gpu
4 | from gpu_extras.batch import batch_for_shader
5 |
6 | from .draw_line import draw_line
7 |
8 | def distance_formula(p1, p2):
9 | """
10 | Calculate the distance between 2 points on a 2D Cartesian coordinate plane
11 |
12 | Parameters
13 | ----------
14 | p1: list of int
15 | The x and y of a point, ie: [1, 0]
16 | p2: list of int
17 | The x and y of a point, ie: [2, 3]
18 |
19 | Returns
20 | -------
21 | distance: float
22 | The distance between the 2 points
23 | """
24 | x = p2[0] - p1[0]
25 | y = p2[1] - p1[1]
26 |
27 | distance = sqrt(x**2 + y**2)
28 | return distance
29 |
30 | def get_next_point(origin, angle, distance):
31 | """
32 | Get the next point given an origin, an angle, and a distance to go
33 | """
34 | v2_x = origin[0] + (math.cos(angle) * distance)
35 | v2_y = origin[1] + (math.sin(angle) * distance)
36 |
37 | return [v2_x, v2_y]
38 |
39 | def draw_stippled_line(v1, v2, thickness, stipple_length, color):
40 | distance = distance_formula(v1, v2)
41 |
42 | count = int(distance / (stipple_length + (thickness * 3)))
43 |
44 | if v2[0] == v1[0]:
45 | if v2[1] > v1[1]:
46 | angle = math.radians(-90)
47 | else:
48 | angle = math.radians(90)
49 | else:
50 | angle = math.atan((v2[1] - v1[1]) / (v2[0] - v1[0]))
51 |
52 | if v2[0] > v1[0]:
53 | pass
54 | elif v2[1] < v1[1]:
55 | angle -= math.radians(180)
56 |
57 | else:
58 | angle += math.radians(180)
59 |
60 | pairs = []
61 |
62 | last_point = v1
63 | for i in range(count):
64 | new_point = get_next_point(last_point, angle, stipple_length)
65 | pairs.append([last_point, new_point])
66 | last_point = get_next_point(new_point, angle, (thickness * 3))
67 |
68 | for pair in pairs:
69 | draw_line(pair[0], pair[1], thickness, color)
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/operators/utils/func_constrain_axis.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .draw import draw_axes
3 |
4 |
5 | def func_constrain_axis(self, context, key, value, angle):
6 | if len(self.tab) > 1:
7 | angle = 0
8 | if key in ['X','Y']:
9 | if self.handle_axes == None:
10 | args = (self, context, angle)
11 | self.handle_axes = bpy.types.SpaceSequenceEditor.draw_handler_add(
12 | draw_axes, args, 'PREVIEW', 'POST_PIXEL')
13 | if key == 'X' and value == 'PRESS':
14 | if self.axis_x == True and self.axis_y == True:
15 | self.axis_y = False
16 | elif self.axis_x == True and self.axis_y == False:
17 | self.axis_y = True
18 | elif self.axis_x == False and self.axis_y == True:
19 | self.axis_y = False
20 | self.axis_x = True
21 |
22 | if key == 'Y' and value == 'PRESS':
23 | if self.axis_x == True and self.axis_y == True:
24 | self.axis_x = False
25 | elif self.axis_x == False and self.axis_y == True:
26 | self.axis_x = True
27 | elif self.axis_x == True and self.axis_y == False:
28 | self.axis_y = True
29 | self.axis_x = False
30 |
31 | if self.axis_x and self.axis_y:
32 | if self.handle_axes:
33 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
34 | self.handle_axes, 'PREVIEW')
35 | self.handle_axes = None
36 |
--------------------------------------------------------------------------------
/operators/utils/func_constrain_axis_mmb.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 | from mathutils import Quaternion
5 |
6 | from .draw import draw_axes
7 |
8 |
9 | def func_constrain_axis_mmb(self, context, key, value, angle):
10 | if len(self.tab) > 1:
11 | angle = 0
12 | if key == 'MIDDLEMOUSE':
13 | if value == 'PRESS':
14 | if self.handle_axes == None:
15 | args = (self, context, angle)
16 | self.handle_axes = bpy.types.SpaceSequenceEditor.draw_handler_add(
17 | draw_axes, args, 'PREVIEW', 'POST_PIXEL')
18 | self.choose_axis = True
19 | self.pos_clic = self.mouse_pos
20 | if value == 'RELEASE' :
21 | self.choose_axis = False
22 | if self.pos_clic == self.mouse_pos:
23 | self.axis_x = self.axis_y = True
24 | if self.handle_axes:
25 | bpy.types.SpaceSequenceEditor.draw_handler_remove(
26 | self.handle_axes, 'PREVIEW')
27 | self.handle_axes = None
28 | if self.choose_axis:
29 | vec_axis_z = Vector((0, 0, 1))
30 | vec_axis_x = Vector((1, 0, 0))
31 | vec_axis_x.rotate(Quaternion(vec_axis_z, math.radians(angle)))
32 | vec_axis_x = vec_axis_x.to_2d()
33 | vec_axis_y = Vector((0, 1, 0))
34 | vec_axis_y.rotate(Quaternion(vec_axis_z, math.radians(angle)))
35 | vec_axis_y = vec_axis_y.to_2d()
36 |
37 | ang_x = math.degrees(vec_axis_x.angle(self.mouse_pos - self.center_area))
38 | ang_y = math.degrees(vec_axis_y.angle(self.mouse_pos - self.center_area))
39 |
40 | if ang_x > 90:
41 | ang_x = 180 - ang_x
42 | if ang_y > 90:
43 | ang_y = 180 - ang_y
44 |
45 | if ang_x < ang_y:
46 | self.axis_x = True
47 | self.axis_y = False
48 | else :
49 | self.axis_x = False
50 | self.axis_y = True
51 |
--------------------------------------------------------------------------------
/operators/utils/geometry/__init__.py:
--------------------------------------------------------------------------------
1 | from .get_group_box import get_group_box
2 | from .get_pos_x import get_pos_x
3 | from .get_pos_y import get_pos_y
4 | from .get_post_rot_bbox import get_post_rot_bbox
5 | from .get_preview_offset import get_preview_offset
6 | from .get_res_factor import get_res_factor
7 | from .get_strip_box import get_strip_box
8 | from .get_strip_corners import get_strip_corners
9 | from .get_transform_box import get_transform_box
10 | from .mouse_to_res import mouse_to_res
11 | from .reposition_strip import reposition_strip
12 | from .reposition_transform_strip import reposition_transform_strip
13 | from .rotate_point import rotate_point
14 | from .set_pos_x import set_pos_x
15 | from .set_pos_y import set_pos_y
--------------------------------------------------------------------------------
/operators/utils/geometry/get_group_box.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from operator import attrgetter
4 |
5 | from .get_strip_box import get_strip_box
6 | from .get_transform_box import get_transform_box
7 | from .get_strip_corners import get_strip_corners
8 |
9 |
10 | def get_group_box(strips):
11 | """
12 | Get the bounding box of a group of strips
13 |
14 | Parameters
15 | ----------
16 | strips: list of bpy.types.Sequence
17 |
18 | Returns
19 | -------
20 | box: list of int
21 | The bounding box of all the strips ie: left, right, bottom, top
22 | """
23 | scene = bpy.context.scene
24 | boxes = []
25 |
26 | nontransforms = []
27 | for i in range(len(strips)):
28 | if strips[i].type != "TRANSFORM" and strips[i].type != "SOUND":
29 | nontransforms.append(strips[i])
30 |
31 | for strip in nontransforms:
32 | boxes.append(get_strip_box(strip))
33 |
34 | transforms = []
35 | for i in range(len(strips)):
36 | if strips[i].type == "TRANSFORM":
37 | transforms.append(strips[i])
38 |
39 | for strip in transforms:
40 | bl, tl, tr, br = get_strip_corners(strip)
41 | vectors = [bl, tl, tr, br]
42 |
43 | left = min(vectors, key=attrgetter('x')).x
44 | right = max(vectors, key=attrgetter('x')).x
45 |
46 | bottom = min(vectors, key=attrgetter('y')).y
47 | top = max(vectors, key=attrgetter('y')).y
48 |
49 | boxes.append([left, right, bottom, top])
50 |
51 | res_x = scene.render.resolution_x
52 | res_y = scene.render.resolution_y
53 |
54 | min_left = res_x
55 | max_right = 0
56 | min_bottom = res_y
57 | max_top = 0
58 |
59 | for box in boxes:
60 | if box[0] < min_left:
61 | min_left = box[0]
62 | if box[1] > max_right:
63 | max_right = box[1]
64 | if box[2] < min_bottom:
65 | min_bottom = box[2]
66 | if box[3] > max_top:
67 | max_top = box[3]
68 |
69 | return [min_left, max_right, min_bottom, max_top]
70 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_pos_x.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | def get_pos_x(strip):
5 | """
6 | Get the X position of a transform strip.
7 |
8 | Note that a transform strip's position may be defined as a
9 | percentage or a number of pixes.
10 |
11 |
12 | :param strip: A transform strip (bpy.types.Sequence)
13 | Returns
14 | :pos: An X position in pixels (int)
15 | """
16 | res_x = bpy.context.scene.render.resolution_x
17 |
18 | if strip.translation_unit == 'PERCENT':
19 | pos = strip.translate_start_x * res_x / 100
20 |
21 | else:
22 | pos = strip.translate_start_x
23 |
24 | return pos
25 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_pos_y.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | def get_pos_y(strip):
5 | """
6 | Get the Y position of a transform strip. Note that a transform
7 | strip's position may be defined as a percentage or a number of
8 | pixes.
9 |
10 | Args
11 | :strip: A transform strip (bpy.types.Sequence)
12 | Returns
13 | :pos: A Y position in pixels (int)
14 | """
15 | res_y = bpy.context.scene.render.resolution_y
16 |
17 | if strip.translation_unit == 'PERCENT':
18 | pos = strip.translate_start_y * res_y / 100
19 |
20 | else:
21 | pos = strip.translate_start_y
22 |
23 | return pos
24 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_post_rot_bbox.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 |
4 | def get_post_rot_bbox(left, right, bottom, top, rotation):
5 | """
6 | Return bounding box of a rotated rectangle.
7 |
8 | Args
9 | :left: left horizontal position of the unrotated rectangle
10 | (float)
11 | :right: right horizontal position of the unrotated rectangle
12 | (float)
13 | :bottom: bottom vertical position of the unrotated rectangle
14 | (float)
15 | :top: top vertical position of the unrotated rectangle
16 | (float)
17 | :rotation: rotation of the rectangle in radians (float)
18 | Returns
19 | :b_left: minimum horizontal extremity of the rotated rectangle
20 | :b_right: maximum horizontal extremity of the rotated rectangle
21 | :b_bottom: minimum vertical extremity of the rotated rectangle
22 | :b_top: maximum vertical extremity of the rotated rectangle
23 | """
24 |
25 | rotation = abs(rotation)
26 | width = right - left
27 | height = top - bottom
28 |
29 | b_width = (math.sin(rotation) * height) + (math.cos(rotation) * width)
30 | b_height = (math.sin(rotation) * width) + (math.cos(rotation) * height)
31 |
32 | diff_x = (b_width - width) / 2
33 | b_left = left - diff_x
34 | b_right = right + diff_x
35 |
36 | diff_y = (b_height - height) / 2
37 | b_bottom = bottom - diff_y
38 | b_top = top + diff_y
39 |
40 | return b_left, b_right, b_bottom, b_top
41 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_preview_offset.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | from .get_res_factor import get_res_factor
4 |
5 | def get_preview_offset():
6 | """
7 | Get the number of pixels (x and y) that the preview is offset in the
8 | preview window as well as other related information.
9 |
10 | Returns
11 | :offset_x: The horizontal offset of the preview window
12 | :offset_y: The vertical offset of the preview window
13 | :fac: The resolution factor for the scene
14 | :preview_zoom: The zoom level of the preview window
15 | """
16 | context = bpy.context
17 | fac = get_res_factor()
18 |
19 | width = context.region.width
20 | height = context.region.height
21 |
22 | rv1 = context.region.view2d.region_to_view(0, 0)
23 | rv2 = context.region.view2d.region_to_view(width, height)
24 |
25 | res_x = context.scene.render.resolution_x
26 | res_y = context.scene.render.resolution_y
27 |
28 | preview_zoom = (width / (rv2[0] - rv1[0]))
29 |
30 | offset_x = -int((rv1[0] + ((res_x * fac) / 2)) * preview_zoom)
31 | offset_y = -int((rv1[1] + ((res_y * fac) / 2)) * preview_zoom)
32 |
33 | return offset_x, offset_y, fac, preview_zoom
34 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_res_factor.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | def get_res_factor():
5 | """
6 | Get the scene resolution percentage as a factor
7 |
8 | Returns
9 | :fac: The resolution factor (float)
10 | """
11 | fac = 1.0
12 |
13 | prs = bpy.context.space_data.proxy_render_size
14 | res_perc = bpy.context.scene.render.resolution_percentage
15 |
16 | if prs == 'SCENE':
17 | fac = res_perc / 100
18 |
19 | return fac
20 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_strip_box.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | def get_strip_box(strip):
5 | """
6 | Gets the box of a non-transform strip
7 |
8 | Args
9 | :strip: A strip from the vse (bpy.types.Sequence)
10 | Returns
11 | :box: A list comprising the strip's left, right, bottom, top
12 | (list of int)
13 | """
14 | scene = bpy.context.scene
15 |
16 | res_x = scene.render.resolution_x
17 | res_y = scene.render.resolution_y
18 |
19 | proxy_facs = {
20 | 'NONE': 1.0,
21 | 'SCENE': 1.0,
22 | 'FULL': 1.0,
23 | 'PROXY_100': 1.0,
24 | 'PROXY_75': 0.75,
25 | 'PROXY_50': 0.5,
26 | 'PROXY_25': 0.25
27 | }
28 |
29 | proxy_key = bpy.context.space_data.proxy_render_size
30 | proxy_fac = proxy_facs[proxy_key]
31 |
32 | if not strip.use_translation and not strip.use_crop:
33 | left = 0
34 | right = res_x
35 | bottom = 0
36 | top = res_y
37 |
38 | elif strip.use_crop and not strip.use_translation:
39 | left = 0
40 | right = res_x
41 | bottom = 0
42 | top = res_y
43 |
44 | elif not hasattr(strip, 'elements'):
45 | len_crop_x = res_x
46 | len_crop_y = res_y
47 |
48 | if strip.type == "SCENE":
49 | factor = strip.scene.render.resolution_percentage / 100
50 | len_crop_x = strip.scene.render.resolution_x * factor
51 | len_crop_y = strip.scene.render.resolution_y * factor
52 | if strip.use_crop:
53 | len_crop_x -= (strip.crop.min_x + strip.crop.max_x)
54 | len_crop_y -= (strip.crop.min_y + strip.crop.max_y)
55 |
56 | if len_crop_x < 0:
57 | len_crop_x = 0
58 | if len_crop_y < 0:
59 | len_crop_y = 0
60 |
61 | left = 0
62 | right = res_x
63 | bottom = 0
64 | top = res_y
65 |
66 | if strip.use_translation:
67 | left = strip.transform.offset_x
68 | right = left + len_crop_x
69 | bottom = strip.transform.offset_y
70 | top = strip.transform.offset_y + len_crop_y
71 |
72 | elif strip.use_translation and not strip.use_crop:
73 | left = strip.transform.offset_x
74 | right = left + (strip.elements[0].orig_width / proxy_fac)
75 | bottom = strip.transform.offset_y
76 | top = bottom + (strip.elements[0].orig_height / proxy_fac)
77 |
78 | else:
79 | total_crop_x = strip.crop.min_x + strip.crop.max_x
80 | total_crop_y = strip.crop.min_y + strip.crop.max_y
81 |
82 | len_crop_x = (strip.elements[0].orig_width / proxy_fac) - total_crop_x
83 | len_crop_y = (strip.elements[0].orig_height / proxy_fac) - total_crop_y
84 |
85 | left = strip.transform.offset_x
86 | right = left + len_crop_x
87 | bottom = strip.transform.offset_y
88 | top = bottom + len_crop_y
89 |
90 | box = [left, right, bottom, top]
91 |
92 | return box
93 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_strip_corners.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import math
3 | from mathutils import Vector
4 |
5 | from .get_strip_box import get_strip_box
6 | from .get_transform_box import get_transform_box
7 | from .rotate_point import rotate_point
8 |
9 |
10 | def get_strip_corners(strip):
11 | """
12 | Get the bottom_left, top_left, top_right, bottom_right corners of a
13 | strip.
14 |
15 | Args
16 | :strip: A strip (bpy.types.Sequence)
17 | Returns
18 | :corners: The positions of the bottom-left, top-left, top-right,
19 | & bottom-right corners (list of mathutils.Vectors)
20 | """
21 | scene = bpy.context.scene
22 | if strip.type == 'TRANSFORM':
23 | box = get_transform_box(strip)
24 | left, right, bottom, top = box
25 |
26 | width = right - left
27 | height = top - bottom
28 |
29 | origin = Vector([left + (width / 2), bottom + (height / 2)])
30 | if strip.input_1.use_translation:
31 | off_x = strip.input_1.transform.offset_x
32 | off_y = strip.input_1.transform.offset_y
33 | scl_x = strip.scale_start_x
34 | scl_y = strip.scale_start_y
35 |
36 | origin_x = (width / 2) - (off_x * scl_x) / 2
37 | origin_y = (height / 2) - (off_y * scl_y) / 2
38 |
39 | origin = Vector([left + origin_x, bottom + origin_y])
40 |
41 | rot = math.radians(strip.rotation_start)
42 | if strip.use_translation or strip.use_crop:
43 | rot = 0
44 |
45 | bottom_left = Vector([left, bottom])
46 | top_left = Vector([left, top])
47 | top_right = Vector([right, top])
48 | bottom_right = Vector([right, bottom])
49 |
50 | corners = [bottom_left, top_left, top_right, bottom_right]
51 | for i in range(len(corners)):
52 | corners[i] = rotate_point(corners[i], rot, origin)
53 |
54 | bottom_left, top_left, top_right, bottom_right = corners
55 |
56 | else:
57 |
58 | box = get_strip_box(strip)
59 | left, right, bottom, top = box
60 |
61 | bottom_left = Vector([left, bottom])
62 | top_left = Vector([left, top])
63 |
64 | top_right = Vector([right, top])
65 | bottom_right = Vector([right, bottom])
66 |
67 | #if hasattr(strip, 'elements') and not strip.type == 'IMAGE' and proxy_fac < 1.0:
68 | # top_left = Vector([left, (bottom + (top - bottom)) / proxy_fac])
69 | # top_right = Vector([(left + (right - left)) / proxy_fac, (bottom + (top - bottom)) / proxy_fac])
70 | # bottom_right = Vector([(left + (right - left)) / proxy_fac, bottom])
71 |
72 | res_x = scene.render.resolution_x
73 | res_y = scene.render.resolution_y
74 |
75 | corners = [bottom_left, top_left, top_right, bottom_right]
76 |
77 | if strip.use_flip_x:
78 | for i in range(len(corners)):
79 | corners[i].x = res_x - corners[i].x
80 |
81 | if strip.use_flip_y:
82 | for i in range(len(corners)):
83 | corners[i].y = res_y - corners[i].y
84 |
85 | bottom_left, top_left, top_right, bottom_right = corners
86 |
87 | return [bottom_left, top_left, top_right, bottom_right]
88 |
--------------------------------------------------------------------------------
/operators/utils/geometry/get_transform_box.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | import os
3 |
4 | from .get_pos_x import get_pos_x
5 | from .get_pos_y import get_pos_y
6 | from .get_strip_box import get_strip_box
7 | from .get_res_factor import get_res_factor
8 |
9 |
10 | def get_transform_box(strip):
11 | """
12 | Gets the unrotated left, right, top, bottom of a transform strip
13 |
14 | Args
15 | :strip: A transform strip (bpy.types.Sequence) Returns
16 | :box: The left, right, top, bottom of a transform strip (list
17 | of int)
18 | """
19 | scene = bpy.context.scene
20 |
21 | left, right, bottom, top = get_strip_box(strip.input_1)
22 |
23 | res_x = scene.render.resolution_x
24 | res_y = scene.render.resolution_y
25 |
26 | strip_in = strip.input_1
27 | if strip_in.use_translation:
28 | left = 0
29 | right = res_x
30 | bottom = 0
31 | top = res_y
32 |
33 | width = right - left
34 | height = top - bottom
35 |
36 | t_pos_x = get_pos_x(strip)
37 | t_pos_y = get_pos_y(strip)
38 |
39 | world_left = left + t_pos_x
40 | world_right = right + t_pos_x
41 | world_bottom = bottom + t_pos_y
42 | world_top = top + t_pos_y
43 |
44 | origin_x = world_left + (width / 2)
45 | origin_y = world_bottom + (height / 2)
46 |
47 | scl_x = strip.scale_start_x
48 | scl_y = strip.scale_start_y
49 | if strip.use_uniform_scale:
50 | scl_x = scl_y = max([scl_x, scl_y])
51 |
52 | scaled_left = (world_left - origin_x) * scl_x
53 | scaled_bottom = (world_bottom - origin_y) * scl_y
54 |
55 | diff_x = scaled_left - (world_left - origin_x)
56 | diff_y = scaled_bottom - (world_bottom - origin_y)
57 |
58 | left = world_left + diff_x
59 | right = left + (width * scl_x)
60 | bottom = world_bottom + diff_y
61 | top = bottom + (height * scl_y)
62 |
63 | width = right - left
64 | height = top - bottom
65 |
66 | if right - left <= 0 or top - bottom <= 0:
67 | left = right = bottom = top = 0
68 |
69 | box = [left, right, bottom, top]
70 |
71 | return box
72 |
--------------------------------------------------------------------------------
/operators/utils/geometry/mouse_to_res.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from mathutils import Vector
3 |
4 | from .get_res_factor import get_res_factor
5 |
6 |
7 | def mouse_to_res(mouse_vec):
8 | """
9 | Convert mouse click position to pixel position.
10 |
11 | When a user clicks the preview window, the position is given as a
12 | unit of pixels from the bottom left of the entire preview window.
13 |
14 | This function converts that position to a unit of video resolution
15 | begining from the bottom left of the player part of the preview
16 | window
17 |
18 | Args
19 | :mouse_vec: The point at which the mouse click occured
20 | (mathutils.Vector)
21 | Returns
22 | :mouse_res: The point at which the mouse click occured, in
23 | resolution pixels (mathutils.Vector)
24 | """
25 | scene = bpy.context.scene
26 |
27 | res_x = scene.render.resolution_x
28 | res_y = scene.render.resolution_y
29 |
30 | mouse_x = mouse_vec.x
31 | mouse_y = mouse_vec.y
32 |
33 | fac = get_res_factor()
34 |
35 | pos = bpy.context.region.view2d.region_to_view(mouse_x, mouse_y)
36 |
37 | pos_x = (pos[0] + (res_x * fac / 2)) / fac
38 | pos_y = (pos[1] + (res_y * fac / 2)) / fac
39 |
40 | mouse_res = Vector((pos_x, pos_y))
41 |
42 | return mouse_res
43 |
--------------------------------------------------------------------------------
/operators/utils/geometry/reposition_strip.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .get_strip_box import get_strip_box
3 |
4 | def reposition_strip(strip, group_box):
5 | """
6 | Reposition a (non-transform) strip.
7 |
8 | After adjusting scene resolution, strip sizes will be
9 | discombobulated. This function will reset a strip's size and
10 | position to how it was relative to the group box prior to the
11 | resolution change.
12 |
13 | Args
14 | :strip: A strip (bpy.types.Sequence)
15 | :group_box: The group bounding box prior to the scene resolution
16 | change. (list of int: ie [left, right, bottom, top])
17 | """
18 | scene = bpy.context.scene
19 |
20 | min_left, max_right, min_bottom, max_top = group_box
21 |
22 | total_width = max_right - min_left
23 | total_height = max_top - min_bottom
24 |
25 | res_x = scene.render.resolution_x
26 | res_y = scene.render.resolution_y
27 |
28 | available_width = res_x - total_width
29 | available_height = res_y - total_height
30 |
31 | if strip.use_translation:
32 | strip.transform.offset_x -= min_left
33 | strip.transform.offset_y -= min_bottom
34 |
35 | # While this part works, it makes the strip blurry. Users shouldn't use this technique.
36 | """
37 | if strip.type == "META":
38 | left, right, bottom, top = get_strip_box(strip)
39 | width = right - left
40 | height = top - bottom
41 |
42 | fac_x = width / res_x
43 | fac_y = height / res_y
44 |
45 | new_crop_left = int(strip.crop.min_x * fac_x)
46 | new_crop_right = int(strip.crop.max_x * fac_x)
47 | new_crop_top = int(strip.crop.max_y * fac_y)
48 | new_crop_bottom = int(strip.crop.min_y * fac_y)
49 |
50 | strip.crop.min_x = new_crop_left
51 | strip.crop.max_x = new_crop_right
52 | strip.crop.min_y = new_crop_bottom
53 | strip.crop.max_y = new_crop_top
54 |
55 | strip.use_float = True
56 |
57 | bpy.ops.sequencer.select_all(action='DESELECT')
58 | scene.sequence_editor.active_strip = strip
59 | bpy.ops.sequencer.effect_strip_add(type="TRANSFORM")
60 |
61 | transform_strip = bpy.context.scene.sequence_editor.active_strip
62 | transform_strip.name = "[TR]-%s" % strip.name
63 |
64 | transform_strip.blend_type = 'ALPHA_OVER'
65 | transform_strip.blend_alpha = strip.blend_alpha
66 |
67 | strip.mute = True
68 |
69 | if strip.use_translation:
70 | transform_strip.scale_start_x = 1.0
71 | transform_strip.scale_start_y = 1.0
72 |
73 | offset_x = strip.transform.offset_x
74 | offset_y = strip.transform.offset_y
75 |
76 | flip_x = 1
77 | if strip.use_flip_x:
78 | flip_x = -1
79 |
80 | flip_y = 1
81 | if strip.use_flip_y:
82 | flip_y = -1
83 |
84 | delta_x = ((width / 2) / fac_x) - (width / 2)
85 | pos_x = offset_x + (width / 2) - (res_x / 2) + delta_x
86 | pos_x *= flip_x
87 |
88 | delta_y = ((height / 2) / fac_y) - (height / 2)
89 | pos_y = offset_y + (height / 2) - (res_y / 2) + delta_y
90 | pos_y *= flip_y
91 |
92 | if transform_strip.translation_unit == 'PERCENT':
93 | pos_x = (pos_x / res_x) * 100
94 | pos_y = (pos_y / res_y) * 100
95 |
96 | transform_strip.translate_start_x = pos_x
97 | transform_strip.translate_start_y = pos_y
98 |
99 | strip.use_translation = False
100 | """
101 |
102 | if not hasattr(strip, 'elements') and strip.use_crop:
103 | if strip.crop.min_x < available_width:
104 | available_width -= strip.crop.min_x
105 | strip.crop.min_x = 0
106 | else:
107 | strip.crop.min_x -= available_width
108 | available_width = 0
109 |
110 | if strip.crop.min_y < available_height:
111 | available_height -= strip.crop.min_y
112 | strip.crop.min_y = 0
113 | else:
114 | strip.crop.min_y -= available_height
115 | available_height = 0
116 |
117 | strip.crop.max_x -= available_width
118 | strip.crop.max_y -= available_height
119 |
--------------------------------------------------------------------------------
/operators/utils/geometry/reposition_transform_strip.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from operator import attrgetter
3 | import math
4 |
5 | from .get_transform_box import get_transform_box
6 | from .get_strip_box import get_strip_box
7 | from .get_post_rot_bbox import get_post_rot_bbox
8 | from .get_strip_corners import get_strip_corners
9 |
10 |
11 | def reposition_transform_strip(strip, group_box):
12 | """
13 | Reposition a transform strip.
14 |
15 | After adjusting scene resolution, strip sizes will be
16 | discombobulated. This function will reset a strip's size and
17 | position to how it was relative to the group box prior to the
18 | resolution change.
19 |
20 | Args
21 | :strip: A transform strip (bpy.types.Sequence)
22 | :group_box: The group bounding box prior to the scene resolution
23 | change. (list of int: ie [left, right, bottom, top])
24 | """
25 | scene = bpy.context.scene
26 |
27 | min_left, max_right, min_bottom, max_top = group_box
28 |
29 | total_width = max_right - min_left
30 | total_height = max_top - min_bottom
31 |
32 | res_x = scene.render.resolution_x
33 | res_y = scene.render.resolution_y
34 |
35 | min_left, max_right, min_bottom, max_top = group_box
36 |
37 | left, right, bottom, top = get_transform_box(strip)
38 |
39 | width = right - left
40 | height = top - bottom
41 |
42 | rot = math.radians(strip.rotation_start)
43 |
44 | bl, tl, tr, br = get_strip_corners(strip)
45 | vectors = [bl, tl, tr, br]
46 | b_left = min(vectors, key=attrgetter('x')).x
47 | b_right = max(vectors, key=attrgetter('x')).x
48 |
49 | b_bottom = min(vectors, key=attrgetter('y')).y
50 | b_top = max(vectors, key=attrgetter('y')).y
51 |
52 | primary_offset_x = b_left - min_left
53 | primary_offset_y = b_bottom - min_bottom
54 |
55 | b_width = b_right - b_left
56 | b_height = b_top - b_bottom
57 |
58 | scale_ratio_x = total_width / res_x
59 | scale_ratio_y = total_height / res_y
60 |
61 | current_width = width * scale_ratio_x
62 | current_height = height * scale_ratio_y
63 |
64 | current_left = left * scale_ratio_x
65 | current_bottom = bottom * scale_ratio_y
66 |
67 | current_right = current_left + current_width
68 | current_top = current_bottom + current_height
69 |
70 | box = get_post_rot_bbox(
71 | current_left, current_right, current_bottom, current_top, rot)
72 | current_b_left = box[0]
73 | current_b_right = box[1]
74 | current_b_bottom = box[2]
75 | current_b_top = box[3]
76 |
77 | current_b_width = current_b_right - current_b_left
78 | current_b_height = current_b_top - current_b_bottom
79 |
80 | collapse_offset_x = -current_b_left
81 | collapse_offset_y = -current_b_bottom
82 |
83 | scale_offset_x = (b_width - current_b_width) / 2
84 | scale_offset_y = (b_height - current_b_height) / 2
85 |
86 | offset_x, null, offset_y, null = get_strip_box(strip)
87 |
88 | if strip.use_translation:
89 | strip.transform.offset_x = 0
90 | strip.transform.offset_y = 0
91 |
92 | if strip.translation_unit == 'PERCENT':
93 | primary_offset_x = (primary_offset_x / total_width) * 100
94 | primary_offset_y = (primary_offset_y / total_height) * 100
95 |
96 | collapse_offset_x = (collapse_offset_x / total_width) * 100
97 | collapse_offset_y = (collapse_offset_y / total_height) * 100
98 |
99 | scale_offset_x = (scale_offset_x / total_width) * 100
100 | scale_offset_y = (scale_offset_y / total_height) * 100
101 |
102 | offset_x = (offset_x / res_x) * 100
103 | offset_y = (offset_y / res_y) * 100
104 |
105 | combo_offset_x = sum([primary_offset_x, collapse_offset_x,
106 | scale_offset_x, offset_x])
107 | combo_offset_y = sum([primary_offset_y, collapse_offset_y,
108 | scale_offset_y, offset_y])
109 |
110 | strip.translate_start_x += combo_offset_x
111 | strip.translate_start_y += combo_offset_y
112 |
113 | strip.scale_start_x = strip.scale_start_x / scale_ratio_x
114 | strip.scale_start_y = strip.scale_start_y / scale_ratio_y
115 |
--------------------------------------------------------------------------------
/operators/utils/geometry/rotate_point.py:
--------------------------------------------------------------------------------
1 | import math
2 | from mathutils import Vector
3 |
4 |
5 | def rotate_point(point, angle, origin=Vector([0, 0])):
6 | """
7 | Rotate a point around an origin and return it's new position
8 |
9 | Args
10 | :point: The point that will be rotated (mathutils.Vector)
11 | Returns
12 | :rotated_vector: A point that has been rotated about an origin
13 | (mathutils.Vector)
14 | """
15 |
16 | pos = point - origin
17 |
18 | sin = math.sin(angle)
19 | cos = math.cos(angle)
20 |
21 | pos_x = (pos.x * cos) - (pos.y * sin)
22 | pos_y = (pos.x * sin) + (pos.y * cos)
23 |
24 | rotated_vector = origin + Vector([pos_x, pos_y])
25 |
26 | return rotated_vector
27 |
--------------------------------------------------------------------------------
/operators/utils/geometry/set_pos_x.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | def set_pos_x(strip, pos):
4 | """
5 | Set the X position of a transform strip, accounting for whether or
6 | not the translation unit is set to PERCENT
7 |
8 | Args
9 | :strip: The transform strip (bpy.types.Sequence)
10 | :pos: The X position the strip should be moved to, in pixels
11 | (int)
12 | """
13 | res_x = bpy.context.scene.render.resolution_x
14 |
15 | if strip.translation_unit == 'PERCENT':
16 | pos = (pos * 100) / res_x
17 |
18 | return pos
19 |
--------------------------------------------------------------------------------
/operators/utils/geometry/set_pos_y.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 | def set_pos_y(strip, pos):
4 | """
5 | Set the Y position of a transform strip, accounting for whether or
6 | not the translation unit is set to PERCENT
7 |
8 | Args
9 | :strip: The transform strip (bpy.types.Sequence)
10 | :pos: The X position the strip should be moved to, in pixels
11 | (int)
12 | """
13 | res_y = bpy.context.scene.render.resolution_y
14 |
15 | if strip.translation_unit == 'PERCENT':
16 | pos = pos * 100 / res_y
17 |
18 | return pos
19 |
--------------------------------------------------------------------------------
/operators/utils/process_input.py:
--------------------------------------------------------------------------------
1 | def process_input(self, key, value):
2 | """
3 | Get the movement value inputted by the user.
4 |
5 | For example, after pressing r to rotate, the user can input a
6 | rotation amount (in degrees) by pressing numbers on the keyboard
7 | """
8 | numbers = {
9 | 'NUMPAD_0' : '0',
10 | 'NUMPAD_1' : '1',
11 | 'NUMPAD_2' : '2',
12 | 'NUMPAD_3' : '3',
13 | 'NUMPAD_4' : '4',
14 | 'NUMPAD_5' : '5',
15 | 'NUMPAD_6' : '6',
16 | 'NUMPAD_7' : '7',
17 | 'NUMPAD_8' : '8',
18 | 'NUMPAD_9' : '9',
19 | 'ZERO' : '0',
20 | 'ONE' : '1',
21 | 'TWO' : '2',
22 | 'THREE' : '3',
23 | 'FOUR' : '4',
24 | 'FIVE' : '5',
25 | 'SIX' : '6',
26 | 'SEVEN' : '7',
27 | 'EIGHT' : '8',
28 | 'NINE' : '9',
29 | 'PERIOD' : '.',
30 | 'NUMPAD_PERIOD' : '.',
31 | 'MINUS' : '-',
32 | 'NUMPAD_MINUS' : '-',
33 | 'BACK_SPACE' : '',
34 | }
35 | if key in numbers.keys() and value == 'PRESS' and not key == 'BACK_SPACE':
36 | self.key_val += numbers[key]
37 |
38 | elif key == 'BACK_SPACE' and value == 'PRESS' and len(self.key_val) > 0:
39 | self.key_val = self.key_val[0:-1]
40 |
41 | if len(self.key_val) > 1 and self.key_val.endswith('-'):
42 | self.key_val = '-' + self.key_val[0:-1]
43 |
44 | if self.key_val.count('-') > 1:
45 | if self.key_val.count('-') % 2 == 0:
46 | self.key_val = self.key_val.replace('-', '')
47 | else:
48 | self.key_val = '-' + self.key_val.replace('-', '')
49 |
--------------------------------------------------------------------------------
/operators/utils/selection/__init__.py:
--------------------------------------------------------------------------------
1 | from .ensure_transforms import ensure_transforms
2 | from .get_highest_transform import get_highest_transform
3 | from .get_input_tree import get_input_tree
4 | from .get_nontransforms import get_nontransforms
5 | from .get_transforms import get_transforms
6 | from .get_visible_strips import get_visible_strips
--------------------------------------------------------------------------------
/operators/utils/selection/ensure_transforms.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .get_highest_transform import get_highest_transform
3 |
4 |
5 | def ensure_transforms():
6 | """
7 | Check each selected strip. If it is not a transform strip, use
8 | bpy.ops.vse_transform_tools.add_transform() to add a transform to
9 | that strip.
10 |
11 | Returns
12 | :final_selected: A list of all the transform strips related to
13 | the current selected strips, both those
14 | pre-existing and those added by this function.
15 | (list of bpy.types.Sequence)
16 | """
17 | context = bpy.context
18 | scene = context.scene
19 |
20 | selected = bpy.context.selected_sequences
21 | transforms = []
22 | for strip in selected:
23 | transform_strip = get_highest_transform(strip)
24 | if transform_strip not in transforms:
25 | transforms.append(transform_strip)
26 |
27 | for strip in selected:
28 | if not strip in transforms:
29 | strip.select = False
30 |
31 | for strip in transforms:
32 | strip.select = True
33 |
34 | new_active = get_highest_transform(
35 | scene.sequence_editor.active_strip)
36 |
37 | scene.sequence_editor.active_strip = new_active
38 |
39 | final_selected = []
40 | for strip in transforms:
41 | bpy.ops.sequencer.select_all(action="DESELECT")
42 |
43 | if not strip.type == "TRANSFORM":
44 | strip.select = True
45 | bpy.ops.vse_transform_tools.add_transform()
46 | active = scene.sequence_editor.active_strip
47 | final_selected.append(active)
48 |
49 | else:
50 | final_selected.append(strip)
51 |
52 | return final_selected
53 |
54 |
--------------------------------------------------------------------------------
/operators/utils/selection/get_highest_transform.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from .get_input_tree import get_input_tree
3 |
4 |
5 | def get_highest_transform(strip):
6 | """
7 | Return the highest transform strip in a modifier hierarchy
8 |
9 | Args
10 | :strip: A strip (bpy.types.Sequence)
11 | """
12 | scene = bpy.context.scene
13 |
14 | all_sequences = list(sorted(scene.sequence_editor.sequences_all,
15 | key=lambda x: x.channel))
16 |
17 | all_sequences.reverse()
18 |
19 | checked_strips = []
20 |
21 | for seq in all_sequences:
22 | if seq not in checked_strips:
23 | tree = get_input_tree(seq)
24 |
25 | if strip in tree:
26 | for branch in tree:
27 | if branch.type == "TRANSFORM":
28 | return branch
29 |
30 | elif branch == strip:
31 | return seq
32 | else:
33 | checked_strips.extend(tree)
34 |
--------------------------------------------------------------------------------
/operators/utils/selection/get_input_tree.py:
--------------------------------------------------------------------------------
1 | def get_input_tree(strip):
2 | """
3 | Recursively gather a list of the strip and all subsequent input_1
4 | and input_2 strips in it's heirarchy.
5 |
6 | Arg
7 | :strip: A strip (bpy.types.Sequence)
8 | Return
9 | :inputs: A list of strips from an input heirarchy (list of
10 | bpy.types.Sequence)
11 | """
12 | inputs = [strip]
13 |
14 | if hasattr(strip, 'input_1'):
15 | inputs.extend(get_input_tree(strip.input_1))
16 | if hasattr(strip, 'input_2'):
17 | inputs.extend(get_input_tree(strip.input_2))
18 |
19 | return inputs
20 |
--------------------------------------------------------------------------------
/operators/utils/selection/get_nontransforms.py:
--------------------------------------------------------------------------------
1 | def get_nontransforms(strips):
2 | """
3 | Get a list of the non-transform strips from the given list of
4 | strips.
5 | """
6 | nontransforms = []
7 | for i in range(len(strips)):
8 | if strips[i].type != "TRANSFORM" and strips[i].type != "SOUND":
9 | nontransforms.append(strips[i])
10 |
11 | return nontransforms
12 |
13 |
--------------------------------------------------------------------------------
/operators/utils/selection/get_transforms.py:
--------------------------------------------------------------------------------
1 | def get_transforms(strips):
2 | """
3 | Get a list of the selected transform strips
4 | """
5 | transforms = []
6 | for i in range(len(strips)):
7 | if strips[i].type == "TRANSFORM":
8 | transforms.append(strips[i])
9 |
10 | return transforms
11 |
--------------------------------------------------------------------------------
/operators/utils/selection/get_visible_strips.py:
--------------------------------------------------------------------------------
1 | import bpy
2 |
3 |
4 | def get_visible_strips():
5 | """
6 | Get a list of the strips currently visible in the preview window
7 |
8 | Returns
9 | :strips: A list of visible strips (list of bpy.types.Sequence)
10 | """
11 | scene = bpy.context.scene
12 | sequence_editor = scene.sequence_editor
13 |
14 | if len(scene.sequence_editor.meta_stack) > 0:
15 | strips = list(sequence_editor.meta_stack[-1].sequences)
16 | else:
17 | strips = list(scene.sequence_editor.sequences)
18 |
19 | rejects = []
20 | for strip in strips:
21 | if strip.frame_final_start > scene.frame_current:
22 | rejects.append(strip)
23 | elif strip.frame_final_end <= scene.frame_current:
24 | rejects.append(strip)
25 | elif strip.type == 'SOUND':
26 | rejects.append(strip)
27 | elif hasattr(strip, 'input_1'):
28 | rejects.append(strip.input_1)
29 | elif hasattr(strip, 'input_2'):
30 | rejects.append(strip.input_2)
31 |
32 | filtered = []
33 | for strip in strips:
34 | if strip not in rejects:
35 | filtered.append(strip)
36 |
37 | strips = sorted(filtered, key=lambda strip: strip.channel)
38 | rejects = []
39 |
40 | blocked_visibility = False
41 | for strip in reversed(strips):
42 | if blocked_visibility:
43 | rejects.append(strip)
44 | if strip.blend_type in ['CROSS', 'REPLACE'] and not strip.mute and not strip.type == 'SOUND':
45 | blocked_visibility = True
46 |
47 | output = []
48 | for strip in strips:
49 | if strip not in rejects:
50 | output.append(strip)
51 |
52 | return output
53 |
54 |
55 |
--------------------------------------------------------------------------------