# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import datetime import json import time 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 getnumstr(num): # This function turns numbers like 1 or 20 into numbers like 0001 or 0020 s = "" for i in range(4-len(str(num))): s = s + "0" return s+str(num) def getfileoutput(num, FORMAT): # Function gives an output of a file. From the current frame that's rendering. # instead of having frame 1 and format EXR it will give you 0001.exr s = getnumstr(num) if FORMAT == "JPEG": s = s + ".jpg" else: s = s + "." + FORMAT.lower() return s 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 main 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() ######################################################################### # # THE RENDER PREVIEW THINGY # ######################################################################### # Here I want to do a preview that's going to be rendered on the right side try: the_render = win.renders[list(win.renders.keys())[0]] amount_of_frames = the_render["end_frame"] - the_render["start_frame"] + 1 passed = time.time() - win.render_runtime.get("started_rendering", 0) passed = passed*1000000 if win.render_runtime.get("to_render"): longest = passed else: longest = 0 save_folder = the_render["save_folder"] for i in the_render["analytics"][save_folder].values(): if i > longest: longest = i shortest = longest for i in the_render["analytics"][save_folder].values(): if i < shortest: shortest = i rn_files = [] preview_image = "horse" render = list(win.renders.keys())[0] folder = render[:render.rfind("/")] if "render_preview_hovering" not in win.current: win.current["render_preview_hovering"] = False write_hovering = False for n, frame in enumerate(range(the_render["start_frame"], the_render["end_frame"]+1)): f = getfileoutput(frame, the_render["image_format"]) rn_image = win.project+folder+"/"+save_folder+"/"+f if os.path.exists(rn_image): rn_files.append(f) for n, frame in enumerate(range(the_render["start_frame"], the_render["end_frame"]+1)): frame_w = (win.current["w"]-630)/amount_of_frames frame_h = (win.current["h"]-200)/3 try: frac = the_render["analytics"][save_folder][str(frame)] / longest frame_h = frame_h * frac except Exception as e: frame_h = 0 if win.render_runtime.get("current_frame", 0) == frame and win.render_runtime.get("to_render"): frame_h = (win.current["h"]-200)/3 * (passed / longest) frame_h = max(frame_h, frame_w) minus_w = 2 round_r = int((frame_w-2)/2) if frame_w < 5: minus_w = -1 round_r = 0 f = getfileoutput(frame, the_render["image_format"]) UI_color.set(layer, win, "node_background") if f not in rn_files: UI_color.set(layer, win, "dark_overdrop") if the_render["analytics"][save_folder].get(str(frame)) == shortest: UI_color.set(layer, win, "node_blendfile") if win.render_runtime.get("current_frame", 0) == frame and win.render_runtime.get("to_render"): UI_color.set(layer, win, "progress_active") if the_render["analytics"][save_folder].get(str(frame)) == longest: UI_color.set(layer, win, "node_badfile") if int(win.current["frame"]/2) % (len(rn_files)) == frame\ and not win.current["render_preview_hovering"]: UI_color.set(layer, win, "progress_active") if 580+(n*frame_w) < win.current["mx"] < 580+(n*frame_w)+frame_w: UI_color.set(layer, win, "progress_active") preview_image = getfileoutput(frame, the_render["image_format"]) if str(frame) in the_render["analytics"][save_folder]: write_hovering = True UI_elements.tooltip(win, "Fra:"+str(frame)+"\n"+f+"\n"+UI_math.timestring(the_render["analytics"][save_folder].get(str(frame))/1000000)) elif not write_hovering: write_hovering = False UI_elements.roundrect(layer, win, 580+(n*frame_w), 100+(win.current["h"]-200-frame_h), frame_w-minus_w, frame_h, int((frame_w-2)/2)) win.current["render_preview_hovering"] = write_hovering rn_image = win.project+folder+"/"+save_folder+"/"+preview_image # CLIP UI_elements.roundrect(layer, win, 580, 100, win.current["w"] - 630, int((win.current["h"]-200)/3*1.9), 10, fill=False) layer.clip() if os.path.exists(rn_image): UI_elements.image(layer, win, rn_image, 580, 100, win.current["w"] - 630, int((win.current["h"]-200)/3*1.9), cell="render_preview") else: n = int(win.current["frame"]/2) % (len(rn_files)) f = rn_files[n] rn_image = win.project+folder+"/"+save_folder+"/"+f UI_elements.image(layer, win, rn_image, 580, 100, win.current["w"] - 630, int((win.current["h"]-200)/3*1.9), cell="render_preview") layer.reset_clip() if win.render_runtime.get("to_render"): UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(50, win.current["h"]-30) layer.show_text(win.render_runtime.get("current_progress", "")) except Exception as e: print(e) ######################################################################### # # THE RENDER MENU # ######################################################################### UI_color.set(layer, win, "node_background") UI_elements.roundrect(layer, win, 310-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, 310-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 try: with open(win.project+"/render_runtime.json") as json_file: runtime = json.load(json_file) except: runtime = {} runtime["to_render"] = True with open(win.project+"/render_runtime.json", 'w') as fp: json.dump(runtime, fp, indent=4) Popen(["python3", "network/during_render.py", win.project, oscalls.get_current_blender(win)]) UI_elements.roundrect(layer, win, 310-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, 310-20, win.current["h"]-140, 40, 40, 10, button=do, icon="stop") def do(): os.system("gnome-terminal -- python3 "+os.getcwd()+"/network/render_viewer.py "+win.project) UI_elements.roundrect(layer, win, 310-20-40, win.current["h"]-140, 40, 40, 10, button=do, icon="analytics") # Exit button def do(): win.current["calls"][call]["var"] = False UI_elements.roundrect(layer, win, 100+420, win.current["h"]-140, 40, 40, 10, button=do, icon="cancel", tip=talk.text("cancel"), url="render") x = 70 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 = max(0,min(1, fraction)) if 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 # 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 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+10, y+current_Y+win.scroll["render"], 100, 40, set_text=str(win.renders[render]["start_frame"]), tip=talk.text("rendering_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+70, y+current_Y+win.scroll["render"], 40, 40, 10, button=do, icon="ok", tip=talk.text("checked")) # FILE FORMATS # 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. # What I want to do is to ejucate the user on the Open Formats. # You know using OGG video instead of MP4 and stuff like that. # For images there are the same types of Open Formats. The point # is that these formats could be made and played using entirelly # free software. # I will let selection of formats that are not in the list but I # will mark them as non-recommended. I will do it like so. # [ ] [ PNG ] [ # V PNG ? # V JPEG ? # V EXR ? # X HDR ? # And so on and forth. You can see that HDR is marked with an X # it will be a button linking to the : linfo = "http://www.linfo.org/free_file_format.html" # so the user could read a full document describing desisions # about the formats. formats = { "PNG" : [True, "Image PNG", "https://en.wikipedia.org/wiki/Portable_Network_Graphics"], "JPEG": [True, "Image JPEG", "https://en.wikipedia.org/wiki/JPEG"], "EXR" : [True, "Open EXR", "https://en.wikipedia.org/wiki/OpenEXR"], "HDR" : [False,"Radiance HDR", "https://en.wikipedia.org/wiki/RGBE_image_format"], "BMP" : [False,"Microsoft BMP", "https://en.wikipedia.org/wiki/BMP_file_format"], "TGA" : [False,"Truevision TGA", "https://en.wikipedia.org/wiki/Truevision_TGA"], "TIFF": [False,"Tiff", "https://en.wikipedia.org/wiki/Tagged_Image_File_Format"] } if "selecting_render_file_format" not in win.current: win.current["selecting_render_file_format"] = False def do(): win.current["selecting_render_file_format"] = not win.current["selecting_render_file_format"] if win.current["selecting_render_file_format"]: win.scroll["render"] = win.scroll["render"]-50*len(formats) win.current["LMB"] = False win.previous["LMB"] = False UI_elements.roundrect(layer, win, x+120, y+current_Y + win.scroll["render"], 235, 40, 10, button=do, tip=talk.text("rendering_file_format")) currentformat = win.renders[render]["image_format"] if not win.current["selecting_render_file_format"]: UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(310-len(formats[currentformat][1])*6, y+current_Y + win.scroll["render"]+30) layer.show_text(formats[currentformat][1]) # END FRAME UI_elements.text(layer, win, render+"end_frame", x+365, y+current_Y+win.scroll["render"], 100, 40, set_text=str(win.renders[render]["end_frame"]), tip=talk.text("rendering_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 if win.current["selecting_render_file_format"]: 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+120, y+current_Y + win.scroll["render"], 235, 40, 10) def do(): win.renders[render]["image_format"] = f save_settings(win, render) win.current["selecting_render_file_format"] = False UI_elements.roundrect(layer, win, x+120, y+current_Y + win.scroll["render"], 235, 40, 10, button=do) UI_color.set(layer, win, "text_normal") layer.set_font_size(20) layer.move_to(310-len(formats[f][1])*6, y+current_Y + win.scroll["render"]+30) layer.show_text(formats[f][1]) # RECCOMENDATION if formats[f][0]: rec = talk.text("recommended_yes") ic = "ok" else: rec = talk.text("recommended_not") ic = "cancel" def do(): os.system("xdg-open "+linfo) UI_elements.roundrect(layer, win, x+10, y+current_Y + win.scroll["render"], 40, 40, 10, button=do, icon=ic, tip=rec) # WIKIPEDIA ABOUT THE FORMAT def do(): os.system("xdg-open "+formats[f][-1]) UI_elements.roundrect(layer, win, x+430, y+current_Y + win.scroll["render"], 40, 40, 10, button=do, icon="question", tip="Wikipedia") 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"] + 1) remaining = remaining/1000000 remaining = remaining - (time.time() - win.render_runtime.get("started_rendering", 0)) 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)) 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