# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import datetime import json from subprocess import * # GTK module ( Graphical interface import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import GLib from gi.repository import Gdk import cairo # Own modules from settings import settings from settings import talk from settings import fileformats from settings import oscalls from project_manager import pm_project #UI modules from UI import UI_elements from UI import UI_color from UI import UI_math # Studio from studio import studio_dialogs from studio import analytics from studio import story # Network / Rendering from network import network_renders def save_settings(win, filename): ############################################################################ # This function will save the render settings file. ############################################################################ folder = filename[:filename.rfind("/")]+"/extra" savefile = folder+filename[filename.rfind("/"):]+".json" # First let's make sure that the extra folder exists. try: os.makedirs(win.project+folder) except: pass # Then let's write the file in there. with open(win.project+savefile, 'w') as fp: json.dump(win.renders[filename], fp, sort_keys=True, indent=4) def layer(win, call): ########################################################################## # This file will setup and manage rendering of shots. It's a bit complex # in function. I have 2 network scripts at the moment. And it might grow # beyond that. # See: # network/during_render.py # network/network_renders.py # This file is the UI part of the process. ( The maing UI ) Some beats and # peaces will exists in various windows through out the program. ########################################################################## # Making the layer surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'], win.current['h']) layer = cairo.Context(surface) #text setting layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) UI_color.set(layer, win, "dark_overdrop") layer.rectangle( 0, 0, win.current["w"], win.current["h"], ) layer.fill() UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, win.current["w"]/2-250, 100, 500, win.current["h"]-200, 10) # Documentation entry def do(): def after(win, var): pass studio_dialogs.help(win, "help", after, SEARCH=talk.text("documentation_render")) UI_elements.roundrect(layer, win, win.current["w"]/2-250, win.current["h"]-140, 40, 40, 10, do, "question") is_rendering = False # This will determen whether for render in win.renders: if win.renders[render]["rendering"]: is_rendering = True if not is_rendering: # Render button def do(): # This here will launch a script that will be on it's on from now # on. See: # network/during_render.py Popen(["python3", "network/during_render.py", win.project, oscalls.get_current_blender(win)]) UI_elements.roundrect(layer, win, win.current["w"]/2-20, win.current["h"]-140, 40, 40, 10, button=do, icon="right") else: # Stop Render button def do(): network_renders.stop_render(win) UI_elements.roundrect(layer, win, win.current["w"]/2-20, win.current["h"]-140, 40, 40, 10, button=do, icon="stop") # Exit button def do(): win.current["calls"][call]["var"] = False UI_elements.roundrect(layer, win, win.current["w"]/2+210, win.current["h"]-140, 40, 40, 10, button=do, icon="cancel", tip=talk.text("cancel"), url="render") x = win.current["w"]/2-250 + 10 y = 100 + 10 width = 500 - 20 height = win.current["h"]-200 - 20 UI_elements.roundrect(layer, win, x, y, width, height-60, 10, fill=False) layer.clip() clip = [ x, y, width, height-60] # Let's get the filename of the current file that we want to setup. filename = win.current["renders_window"]["filename"] # So this window could be accessed from both main window and from the script. # One is to see where each render is. And ther other is to configure and add # renders to the list. if filename: # Basically if any file is inputted. It means that it's to configure. # but sometimes. ( I'm tyring to see myself using it ) the user will # click on the render button to just access the render. And to hell with # it. Let's make the filename variable the selection of the file. if filename not in win.renders: # So on the initialization we want to do a couple of things. # First of which will be to get render settings data from the # blend file. # I think a quick bpy script could do. # See: # studio/bpy_get_render_settings.py blenderpath = oscalls.get_current_blender(win) blend = win.project+filename checkframes = Popen([blenderpath, "-b", blend , "-P", os.getcwd()+"/studio/bpy_get_render_settings.py"],stdout=PIPE, universal_newlines=True) checkframes.wait() checkstring = checkframes.stdout.read() # We are going to need the following options. start_frame = 0 end_frame = 250 image_format = "PNG" save_folder = "storyboard" for line in checkstring.split("\n"): if line.startswith("Start_frame"): try: start_frame = int(line[line.find(":")+1:]) except: pass if line.startswith("End_frame"): try: end_frame = int(line[line.find(":")+1:]) except: pass # Now since we've got the data. Let's write it to the dictionary first. win.renders[filename] = { "start_frame" :start_frame , # The frame we want to start on "end_frame" :end_frame , # The frame we want to end on "image_format" :image_format, # What format to save the images "save_folder" :save_folder , # Into what folder to save images "clean_folder" :False , # Whether to delete current frames before rendering "current_frame":0 , # What is current frame rendering "rendering" :False , # Whether it's currently rendering "analytics" :{} # Times of each frame } # Now in order not to loose the data immediatly. We are going to need # to add the filename into a special file. s = open(win.project+"/set/active_renders.data", "a") s.write(filename+"\n") s.close() # Also we want to make a little json file in the extra folder of # the shot. This will contain our settings. And the file will be # read by the renderer script while it's running. It has to be on # it's own. So it's good to have extendable files. save_settings(win, filename) # Now let's get to the actuall UI of the stuff. Basically I want it to # always give us a list of the currently set renders. And one of them # might be selected and expendet to see they settings / current data. # Setting up the scroll if "render" not in win.scroll: win.scroll["render"] = 0 current_Y = 0 # So let's do this. is_rendering = False # This will determen whether # Before we dive into settings and graphs. Let's make a deletion if 65535 in win.current["keys"] and not win.renders[win.current["renders_window"]["filename"]]["rendering"]: try: del win.renders[win.current["renders_window"]["filename"]] active_renders = open(win.project+"/set/active_renders.data") active_renders = active_renders.read() active_renders = active_renders.split("\n") s = open(win.project+"/set/active_renders.data", "w") for i in active_renders: if i != win.current["renders_window"]["filename"] and i: s.write(i+"\n") s.close() except: pass win.current["renders_window"]["filename"] = "" win.current["keys"] = [] for render in win.renders: # Let's get a shot name for each render. shot = render[:render.rfind("/")].replace("/rnd", "") blend = render[render.rfind("/"):] tip = (shot+blend).replace("/", "", 1).replace("/", " | ") if win.renders[render]["rendering"]: tip = win.renders[render]["rendering"] def do(): win.current["renders_window"]["filename"] = render UI_elements.roundrect(layer, win, x, y+current_Y+win.scroll["render"], width, 80, 10, button=do, tip=tip, clip=clip) # I think the render logo could be cool. UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/render.png", x+5, y+current_Y+win.scroll["render"]+5, 40, 40) # And the name of the shot UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(x+60, y+current_Y + win.scroll["render"]+30) layer.show_text((shot+blend).replace("/", "", 1).replace("/", " | ")) # And let's draw the fraction fraction = (win.renders[render]["current_frame"] - win.renders[render]["start_frame"])\ / (win.renders[render]["end_frame"] - win.renders[render]["start_frame"]) fraction = min(1, fraction) UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x+20, y+current_Y + win.scroll["render"]+55, width-40, 0, 5) UI_color.set(layer, win, "progress_active") UI_elements.roundrect(layer, win, x+20, y+current_Y + win.scroll["render"]+55, (width-40)*fraction, 0, 5) # Now selection. When you click on one you expend it. And you can see # what settings are inside. if win.current["renders_window"]["filename"] == render: UI_color.set(layer, win, "progress_background") UI_elements.roundrect(layer, win, x, y+current_Y+win.scroll["render"], width, 80, 10, fill=False) layer.stroke() current_Y = current_Y + 90 # We are going to have 2 differnt UI's for those who are rendering # and those who are not rendering. if not win.renders[render]["rendering"]: # We need to choose the folder of where the given render will be done. # For the user tho I don't think nessesary to understand that it's a # folder nessesary. They will see 4 circles. 4 render qualities. And # will decide stuff based on that. fouricons = [ "storyboard", "opengl", "test_rnd", "rendered"] for num, f in enumerate(fouricons): if f == win.renders[render]["save_folder"]: UI_color.set(layer, win, "progress_time") UI_elements.roundrect(layer, win, x+20+(40*num), y+current_Y + win.scroll["render"], 40, 40, 10) def do(): win.renders[render]["save_folder"] = f save_settings(win, render) UI_elements.roundrect(layer, win, x+20+(40*num), y+current_Y + win.scroll["render"], 40, 40, 10, button=do, icon=f, tip=f) # Here also I want to have a little clean icon. This will make sure # to clean the current frames that are currently inside the folder. # I know it's slightly counter intuitive compared to the rest of # the program in that it's better not to give the user the ability # to delete any actuall files. But in this case it makes a bit # sense. Since for the user rendering again is like replacing the # previous files with new ones. And the algorythm always renders # from the last frame in the folder. So it never deletes anything # by defaul. So I guess a button to clean frames could be a good # thing. if win.renders[render]["clean_folder"]: UI_color.set(layer, win, "progress_time") UI_elements.roundrect(layer, win, x+width-40, y+current_Y + win.scroll["render"], 40, 40, 10) def do(): win.renders[render]["clean_folder"] = not win.renders[render]["clean_folder"] save_settings(win, render) UI_elements.roundrect(layer, win, x+width-40, y+current_Y + win.scroll["render"], 40, 40, 10, button=do, icon="clean", tip=talk.text("clean_render_folder")) current_Y = current_Y + 50 # I will give 4 formats to save the renders too. The user will have # to select 1 of those 4. # PNG: The good old PNG. Which is an open format and is widly used # in GNU / Linux operating system. Also it has lossless compre- # ssion and an alha channel to store transparent parts. # JPEG: A standard JPEG image type. Widly used to store photos. It # content aware compression. Which is lossy, but more effective # then PNG. Tho JPEG lacks alpha channel. # EXR: In comparison to PNG or JPEG EXR format stores way broader # range of values for each channel. Not simply from black to # white. But also values that can be less then black and more # then white. Giving you a High Dynamic Range look. And a # possibility to store data such as Z depth. # HDR: Similar to EXR. But more industry standard. And has bit more # effective compression. # I will not add any video files since the algoryhm will require # actuall frames to be stored as separate files. This will insure # that ALL frames were rendered. And in case of crash the algorythm # will pick up from a previous frame in a folder. With video is not # only impossible. But in case of a crash with video all the previous # frames will be lost. formats = ["JPEG", "PNG", "EXR", "HDR"] for num, f in enumerate(formats): if f == win.renders[render]["image_format"]: UI_color.set(layer, win, "progress_time") UI_elements.roundrect(layer, win, x+20+(80*num), y+current_Y + win.scroll["render"], 70, 40, 10) def do(): win.renders[render]["image_format"] = f save_settings(win, render) UI_elements.roundrect(layer, win, x+20+(80*num), y+current_Y + win.scroll["render"], 70, 40, 10, button=do) UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(x+30+(80*num), y+current_Y + win.scroll["render"]+30) layer.show_text(f) current_Y = current_Y + 50 # Now let's put the settings them selves. # First thing is we need start and end frames. And we also need it # somehow readable for the user. I think a schedule logo would # go fine here. UI_elements.image(layer, win, "settings/themes/"+win.settings["Theme"]+"/icons/schedule.png", x+5, y+current_Y+win.scroll["render"], 40, 40) # And the stuff it self # I will probably need this. def is_number(string): try: int(string) return True except: return False # START FRAME UI_elements.text(layer, win, render+"start_frame", x+60, y+current_Y+win.scroll["render"], 200, 40, set_text=str(win.renders[render]["start_frame"])) if win.text[render+"start_frame"]["text"] != str(win.renders[render]["start_frame"])\ and is_number(win.text[render+"start_frame"]["text"]): def do(): win.renders[render]["start_frame"] = int(win.text[render+"start_frame"]["text"]) save_settings(win, render) UI_elements.roundrect(layer, win, x+215, y+current_Y+win.scroll["render"], 40, 40, 10, button=do, icon="ok", tip=talk.text("checked")) # END FRAME UI_elements.text(layer, win, render+"end_frame", x+60+210, y+current_Y+win.scroll["render"], 200, 40, set_text=str(win.renders[render]["end_frame"])) if win.text[render+"end_frame"]["text"] != str(win.renders[render]["end_frame"])\ and is_number(win.text[render+"end_frame"]["text"]): def do(): win.renders[render]["end_frame"] = int(win.text[render+"end_frame"]["text"]) save_settings(win, render) UI_elements.roundrect(layer, win, x+215+210, y+current_Y+win.scroll["render"], 40, 40, 10, button=do, icon="ok", tip=talk.text("checked")) current_Y = current_Y + 50 else: # And here comes the UI of when it's during RENDERING # First thing will be to draw a little graph. This will show current # frame and analytics data about render times. UI_color.set(layer, win, "dark_overdrop") layer.rectangle( x+5, y+current_Y+win.scroll["render"], width-10, 100) layer.fill() for frame in range(win.renders[render]["start_frame"], win.renders[render]["end_frame"]+1): numofis = win.renders[render]["end_frame"] - win.renders[render]["start_frame"] if frame == win.renders[render]["current_frame"]: UI_color.set(layer, win, "progress_time") layer.rectangle( x+5+(width-10)/numofis*(frame-1), y+current_Y+win.scroll["render"], (width-10)/numofis, 100) layer.fill() # Now I want to be able to interact with the graph. For this I want to # add a little mouse sensitive region. That will give me data about the # current frame. In a tooltip? if int(win.current["mx"]) in range(int(x+5+(width-10)/numofis*frame),int(x+5+(width-10)/numofis*frame+(width-10)/numofis))\ and int(win.current["my"]) in range(int(y+current_Y+win.scroll["render"]), int(y+current_Y+win.scroll["render"]+100)): UI_color.set(layer, win, "progress_background") layer.move_to(x+5+(width-10)/numofis*(frame-0.5), y+current_Y+win.scroll["render"] ) layer.line_to( x+5+(width-10)/numofis*(frame-0.5), y+current_Y+win.scroll["render"]+100 ) layer.stroke() if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: if str(frame) in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: value = win.renders[render]["analytics"][win.renders[render]["save_folder"]][str(frame)] UI_elements.tooltip(win, UI_math.timestring(value/1000000)) # Now let's draw a graph. I love graphs. They are cool AF. First of all tho # we need to know the maximum value. Because who know how long was the render mx = 0 allvalues = [] if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: for v in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: mx = max(win.renders[render]["analytics"][win.renders[render]["save_folder"]][v], mx) allvalues.append(win.renders[render]["analytics"][win.renders[render]["save_folder"]][v]) UI_color.set(layer, win, "progress_background") layer.move_to(x+5, y+current_Y+win.scroll["render"]+100) for frame in range(win.renders[render]["start_frame"], win.renders[render]["end_frame"]+1): numofis = win.renders[render]["end_frame"] - win.renders[render]["start_frame"] if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: if str(frame) in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: value = win.renders[render]["analytics"][win.renders[render]["save_folder"]][str(frame)] layer.line_to( x+5+(width-10)/numofis*(frame-0.5), (y+current_Y+win.scroll["render"]+100)-(100/mx*value) ) layer.stroke() current_Y = current_Y + 110 # Now let's put out some data in the text format. For the user to # know stuff he or she might want to know. # AVARAGE TIME avarage = 0 try: avarage = sum(allvalues) / len(allvalues) except: pass UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(x+10, y+current_Y + win.scroll["render"]+30) layer.show_text(talk.text("render_avarage_time")+" "+UI_math.timestring(avarage/1000000)) current_Y = current_Y + 40 # REMAINING TIME remaining = avarage * (win.renders[render]["end_frame"] - win.renders[render]["current_frame"]) UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(x+10, y+current_Y + win.scroll["render"]+30) layer.show_text(talk.text("render_remaining_time")+" "+UI_math.timestring(remaining/1000000)) current_Y = current_Y + 40 else: current_Y = current_Y + 85 ########################### UI_elements.scroll_area(layer, win, "render", x, y, width, height-60, current_Y, bar=True, mmb=True, url="render" ) return surface