Skip to content

Commit 6715c3c

Browse files
committed
feat: Add commit status
1 parent 16373e5 commit 6715c3c

4 files changed

Lines changed: 211 additions & 83 deletions

File tree

src/dpg.ini

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ Collapsed=0
55

66
[Window][###90]
77
Pos=3,3
8-
Size=290,495
8+
Size=226,181
99
Collapsed=0
1010

1111
[Window][###23]
12-
Pos=151,225
13-
Size=153,100
12+
Pos=152,225
13+
Size=458,100
1414
Collapsed=0
1515

1616
[Window][###93]
@@ -23,3 +23,8 @@ Pos=3,3
2323
Size=100,100
2424
Collapsed=0
2525

26+
[Window][###96]
27+
Pos=3,3
28+
Size=290,495
29+
Collapsed=0
30+

src/patchwork/app.py

Lines changed: 66 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
import copy
13
from datetime import datetime, timedelta
24
import logging
35
from pathlib import Path
@@ -12,6 +14,7 @@
1214

1315
import dearpygui.dearpygui as dpg
1416

17+
# Logging
1518
logging.basicConfig(
1619
level="NOTSET",
1720
format="%(message)s",
@@ -20,20 +23,32 @@
2023
)
2124
logger = logging.getLogger(__name__)
2225

26+
# GLOBAL VARS
27+
28+
app_file_path = Path(__file__)
29+
src_folder = app_file_path.parent.absolute()
30+
root_folder = src_folder.parent.absolute()
31+
32+
# Flags
33+
start_timecode_check_dismissed = False
34+
35+
# Counters
36+
in_3_sec = datetime.now() + timedelta(seconds=3)
37+
in_half_sec = datetime.now() + timedelta(seconds=0.5)
38+
39+
40+
# DearPyGUI Init
2341
dpg.create_context()
2442
dpg.set_global_font_scale(1.5)
2543

2644
def save_init():
2745
dpg.save_init_file("dpg.ini")
2846

29-
app_file_path = Path(__file__)
30-
src_folder = app_file_path.parent.absolute()
31-
root_folder = src_folder.parent.absolute()
32-
3347
width, height, channels, data = dpg.load_image(os.path.join(src_folder, "logo.png"))
3448
with dpg.texture_registry(show=False):
3549
dpg.add_static_texture(width=width, height=height, default_value=data, tag="texture_tag")
3650

51+
import routines
3752
from widgets import dialog_box
3853

3954
# RESOLVE INIT
@@ -233,9 +248,8 @@ def toggle_always_on_top():
233248
else:
234249
dpg.set_viewport_always_top(False)
235250

236-
# Commands
237251
def create_marker():
238-
252+
239253
timeline = resolve.active_timeline
240254
framerate = resolve.active_timeline.settings.frame_rate
241255
min_duration = int(framerate) * 2
@@ -250,8 +264,8 @@ def create_marker():
250264
name=f"Change - {get_next_free_marker_num()}",
251265
customdata="patchwork_marker"
252266
):
253-
254-
dialog_box.prompt("Looks like there's already a marker there!")
267+
return False
268+
return True
255269

256270
def clear_markers():
257271
"""
@@ -260,11 +274,27 @@ def clear_markers():
260274
timeline = resolve.active_timeline
261275
markers = timeline.markers.find_all("patchwork_marker")
262276

263-
if markers:
264-
265-
[x.delete() for x in markers]
266-
277+
if not markers:
278+
return False
279+
280+
[x.delete() for x in markers]
281+
return True
282+
283+
# Commands
284+
def add_change():
285+
286+
if not create_marker():
287+
dialog_box.prompt("Looks like there's already a marker there!")
288+
return
289+
290+
def clear_changes():
291+
292+
if not clear_markers():
293+
dialog_box.prompt("Oops, no changes to clear.")
294+
return
295+
267296
def commit_changes():
297+
268298
project = resolve.project
269299
timeline = resolve.active_timeline
270300
framerate = timeline.settings.frame_rate
@@ -299,6 +329,7 @@ def commit_changes():
299329
job_id = project.add_renderjob()
300330
job_ids.append(job_id)
301331
print(job_ids)
332+
302333

303334
def push_changes():
304335
...
@@ -349,10 +380,10 @@ def open_documentation():
349380
with dpg.group(tag="add_group", horizontal=True):
350381

351382
# ADD BUTTON
352-
dpg.add_button(label="Add", tag="Add", callback=create_marker)
383+
dpg.add_button(label="Add", tag="Add", callback=add_change)
353384

354385
# CLEAR BUTTON
355-
dpg.add_button(label="Clear Changes", tag="clear_changes", callback=clear_markers)
386+
dpg.add_button(label="Clear Changes", tag="clear_changes", callback=clear_changes)
356387
with dpg.tooltip("clear_changes"):
357388
dpg.add_text("Clear all of Patchwork's ranged-markers\nfrom the active timeline")
358389

@@ -363,7 +394,9 @@ def open_documentation():
363394

364395
# COMMIT BUTTON
365396
dpg.add_text("Commit changes and create render jobs", wrap=500)
366-
dpg.add_button(label="Commit", tag="Commit", callback=commit_changes)
397+
with dpg.group(tag="commit_group"):
398+
dpg.add_text("No changes to commit", tag="commit_status", color=[255, 100, 100], wrap=500)
399+
dpg.add_button(label="Commit", tag="Commit", callback=commit_changes)
367400

368401
dpg.add_separator()
369402
dpg.add_spacer(height=20)
@@ -407,77 +440,30 @@ def open_documentation():
407440
dpg.setup_dearpygui()
408441
dpg.show_viewport()
409442

410-
# Flags
411-
start_timecode_check_dismissed = False
412-
413-
# Counters
414-
in_3_sec = datetime.now() + timedelta(seconds=3)
415-
in_half_sec = datetime.now() + timedelta(seconds=0.5)
416443
while dpg.is_dearpygui_running():
417-
418-
if datetime.now() > in_half_sec:
419-
420-
# TODO: Check Resolve is open, lock up whole interface with warning otherwise
421-
# No option to dismiss dialog box. Automatically dismiss box when Resolve is opened
422444

423-
# TODO: Check timeline is open, lock up whole interface with warning otherwise
424-
# No option to dismiss dialog box. Automatically dismiss box when timeline is opened
425-
426-
# TODO: Check timeline is same as tracked timeline, disable changes page
427-
# On each timeline change, ensure custom settings are enabled. Make it so.
428-
429-
# TODO: Check timeline timecode start is 00:00:00, since it causes markers to be in the wrong place
430-
# Lock up interface with dialog with options to set timecode to 00:00:00 or close the app: "Set" "Exit"
431-
frame_rate = resolve.active_timeline.settings.frame_rate
432-
current_timecode = resolve.active_timeline.timecode
433-
434-
if not start_timecode_check_dismissed:
435-
if Timecode(frame_rate, current_timecode).hrs >= 1:
436-
if not dpg.is_item_shown("dialog_box"):
437-
438-
dialog_box.prompt(
439-
"Hey! Does your timeline timecode start at 01:00:00? If so, please set it to 00:00:00. "
440-
"Resolve's API has a glitch that causes markers to appear an hour later on the timeline. "
441-
"If not, nevermind."
442-
)
443-
start_timecode_check_dismissed = True
445+
# REACTIVE VARIABLES
446+
markers = copy.copy(resolve.active_timeline.markers)
447+
frame_rate = copy.copy(resolve.active_timeline.settings.frame_rate)
448+
current_timecode = copy.copy(resolve.active_timeline.timecode)
449+
450+
# SIMPLE
451+
resolve.active_timeline.custom_settings(True)
444452

445453

446-
# Refresh current timecode
447-
448-
current_timecode = resolve.active_timeline.timecode
449-
resolve.active_timeline.custom_settings(True)
450-
framerate = resolve.active_timeline.settings.frame_rate
451-
452-
current_marker = None
453-
current_frame = Timecode(framerate, resolve.active_timeline.timecode).frames
454-
455-
if current_frame:
456-
457-
current_frame -=1
458-
current_marker = [x for x in resolve.active_timeline.markers if current_frame >= x.frameid and current_frame < (x.frameid + x.duration)]
454+
# TODO: Check Resolve is open, lock up whole interface with warning otherwise
455+
# No option to dismiss dialog box. Automatically dismiss box when Resolve is opened
456+
457+
# TODO: Check timeline is open, lock up whole interface with warning otherwise
458+
# No option to dismiss dialog box. Automatically dismiss box when timeline is opened
459+
460+
# TODO: Check timeline is same as tracked timeline, disable changes page
461+
# On each timeline change, ensure custom settings are enabled. Make it so.
459462

460-
if current_marker:
461-
462-
assert len(current_marker) == 1
463-
current_marker = current_marker[0]
464-
465-
#TODO: Set flag to allow prompting "overwrite"
466-
467-
if not current_marker.customdata == "patchwork_marker":
468-
469-
dpg.set_value("current_timecode_display", f"On unsupported marker")
470-
dpg.configure_item("current_timecode_display", color=[250, 0, 0])
471-
472-
else:
473-
dpg.set_value("current_timecode_display", f"On marker: '{current_marker.name}'")
474-
dpg.configure_item("current_timecode_display", color=[0, 255, 0])
475-
476-
else:
477-
dpg.set_value("current_timecode_display", f"{current_timecode} | {current_frame}")
478-
dpg.configure_item("current_timecode_display", color=[50, 150, 255])
463+
routines.check_timecode_starts_at_zero(current_timecode, frame_rate)
464+
routines.refresh_add_status(markers, current_timecode, frame_rate)
465+
routines.refresh_commit_status(markers)
479466

480-
in_half_sec = datetime.now() + timedelta(seconds=0.5)
481467
dpg.render_dearpygui_frame()
482468

483469
# TODO: Fix save init file

src/patchwork/routines.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from datetime import datetime, timedelta
2+
from timecode import Timecode
3+
from dearpygui import dearpygui as dpg
4+
from pydavinci.wrappers.marker import MarkerCollection
5+
from widgets import dialog_box
6+
7+
# TODO: Fix this!
8+
# While it does block functions from running every half second
9+
# It also allows multiple function calls at dpg's refresh rate for the next half second...
10+
def is_refreshable(refresh_rate:float) -> bool:
11+
12+
current_time = datetime.now()
13+
last_run = dpg.get_value(f"{refresh_rate}_lastrun")
14+
15+
if last_run == None:
16+
with dpg.value_registry():
17+
dpg.add_string_value(tag=f"{refresh_rate}_lastrun", default_value=str(current_time.strftime('%y%m%d%H%M%S')))
18+
last_run = dpg.get_value(f"{refresh_rate}_lastrun")
19+
20+
last_run = datetime.strptime(last_run, '%y%m%d%H%M%S')
21+
if current_time - last_run < timedelta(seconds=refresh_rate):
22+
return False
23+
24+
dpg.set_value(f"{refresh_rate}_lastrun", str(current_time.strftime('%y%m%d%H%M%S')))
25+
return True
26+
27+
def refresh_add_status(markers:MarkerCollection, current_timecode:str, frame_rate:float, refresh_rate:float=0.5):
28+
29+
if not is_refreshable(refresh_rate):
30+
return
31+
32+
current_frame = Timecode(frame_rate, current_timecode).frames
33+
current_marker = None
34+
35+
if current_frame:
36+
37+
current_frame -=1 # Single frame offset for some reason
38+
current_marker = [x for x in markers if current_frame >= x.frameid and current_frame < (x.frameid + x.duration)]
39+
40+
if current_marker:
41+
42+
assert len(current_marker) == 1
43+
current_marker = current_marker[0]
44+
45+
#TODO: Set flag to allow prompting "overwrite"
46+
47+
if not current_marker.customdata == "patchwork_marker":
48+
49+
dpg.set_value("current_timecode_display", f"On unsupported marker")
50+
dpg.configure_item("current_timecode_display", color=[250, 0, 0])
51+
52+
else:
53+
dpg.set_value("current_timecode_display", f"On marker: '{current_marker.name}'")
54+
dpg.configure_item("current_timecode_display", color=[0, 255, 0])
55+
56+
else:
57+
dpg.set_value("current_timecode_display", f"{current_timecode} | {current_frame}")
58+
dpg.configure_item("current_timecode_display", color=[50, 150, 255])
59+
60+
def check_timecode_starts_at_zero(current_timecode:str, frame_rate:float, refresh_rate:float=0.5):
61+
"""
62+
Check that the Resolve timeline timecode starts at 00:00:00
63+
64+
If it doesn't weird things happen with the markers (Resolve's fault).
65+
Prompt the user once per session. Unfortunately, running this once outside of the
66+
render loop doesn't seem to work. So we set a flag, but run it each time.
67+
68+
Args:
69+
current_timecode (str): The current timecode at playhead position
70+
frame_rate (float): The timeline framerate
71+
start_timecode_check_dismissed (bool): The flag to check for dialog box dismissal
72+
"""
73+
74+
if not is_refreshable(refresh_rate):
75+
return
76+
77+
dismissed = dpg.get_value("zero_timecode_warning_dismissed")
78+
if dismissed is None:
79+
with dpg.value_registry():
80+
dpg.add_bool_value(tag="zero_timecode_warning_dismissed", default_value=False)
81+
82+
if not dismissed:
83+
84+
if Timecode(frame_rate, current_timecode).hrs >= 1:
85+
dialog_box.prompt(
86+
"Hey! Does your timeline timecode start at 01:00:00? If so, please set it to 00:00:00. "
87+
"Resolve's API has a glitch that causes markers to appear an hour later on the timeline. "
88+
"If not, nevermind."
89+
)
90+
dpg.set_value("zero_timecode_warning_dismissed", True)
91+
92+
def refresh_commit_status(markers:MarkerCollection, refresh_rate:float=0.5):
93+
94+
if not is_refreshable(refresh_rate):
95+
return
96+
else:
97+
print("hey")
98+
99+
committed_changes = []
100+
uncommitted_changes = []
101+
invalid_changes = []
102+
103+
for x in markers:
104+
if x.customdata != "patchwork_marker":
105+
continue
106+
if x.color == "Green":
107+
committed_changes.append(x)
108+
elif x.color == "Purple":
109+
uncommitted_changes.append(x)
110+
else:
111+
invalid_changes.append(x)
112+
113+
assert not invalid_changes
114+
115+
if committed_changes and not uncommitted_changes:
116+
dpg.configure_item("commit_status", color=[0, 255, 0])
117+
dpg.set_value("commit_status", f"All changes committed")
118+
119+
elif uncommitted_changes and not committed_changes:
120+
dpg.configure_item("commit_status", color=[255, 150, 0])
121+
dpg.set_value("commit_status", f"{len(uncommitted_changes)} uncommitted")
122+
123+
elif uncommitted_changes:
124+
dpg.configure_item("commit_status", color=[255, 150, 0])
125+
dpg.set_value(
126+
"commit_status",
127+
f"{len(uncommitted_changes)} uncommitted | "
128+
f"{len(committed_changes)} committed"
129+
)
130+
131+
elif not committed_changes and not uncommitted_changes:
132+
dpg.configure_item("commit_status", color=[50, 150, 255])
133+
dpg.set_value("commit_status", "No changes")
134+
135+
else:
136+
dpg.configure_item("commit_status", color=[255, 150, 0])
137+
dpg.set_value("commit_status", "N/A")

src/patchwork/widgets/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)