# THIS FILE IS A PART OF VCStudio # PYTHON 3 ############################################################################# # This file will handle rendering on the background. For more info about how # it all works. See: # network/network_renders.py ############################################################################# import os import sys import json import socket import datetime from subprocess import * # Following 2 functions are copied from the code of Blender-Organizer. How cool # that python2 and python3 can share so much code. 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 output(string): # This function will act similar to python's print. But rather then just # printing the string to the terminal. It will also send a signal to the # VCStudio. And anyone who listening. string = str(string) print(string) for i in range(500): cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) cs1.sendto(bytes(string, 'utf-8'), ('127.0.0.1', 54545)) # Now to make it all work the script that will lanuch this script will give it # an agrument of a project location. Which we might need to use for rendering project = "" blender = "" if len(sys.argv) > 1: project = sys.argv[1] blender = sys.argv[2] if not project or not blender: exit() output(project) output(blender) # Now that we have the project. Let's get data about the current renders that # are setup for rendering. def get_active_renders(): # This tiny function will gives us the list of current files set to render # at any moment these will be needed. active_renders = [] try: active_renders = open(project+"/set/active_renders.data") active_renders = active_renders.read() active_renders = active_renders.split("\n") except: pass return active_renders def remove_active_render(render): # This function will edit the active renders and remove the current # render from the list. active_renders = open(project+"/set/active_renders.data") active_renders = active_renders.read() active_renders = active_renders.split("\n") s = open(project+"/set/active_renders.data", "w") for i in active_renders: if i != render and i: s.write(i+"\n") s.close() active_renders = get_active_renders() # Now I think we also need to get the data from the files. So to speak read their json # render settings to be able to know what are our start and end frames, format and such. def read_settings(filename): # This function will read data from the various render settings. folder = filename[:filename.rfind("/")]+"/extra" savefile = folder+filename[filename.rfind("/"):]+".json" data = {} try: with open(project+savefile) as json_file: data = json.load(json_file) except Exception as e: output(e) return data # Now let's start the main loop. I'm pretty sure that there will be tons of bugs # at this stage. So please look at the following code carefully. # What I want to do is always read the first line and render it. By the end # delete the first line from the render list. Remove it from a file. Tho the # removal could happen at any time anywhere. A user could remove the line # manually. Or delete the file from the renders in the VCStudio. It doesn't # matter. This file should be abborted and the next one should start rendering # to do this. I need to write some clever algorithm. while True: # I know wild. A while with a True. OMG. But I guess it's the only way to # insure that it will keep working if there are any current stuff in the # list. And will stop if there is absolutelly nothing in the list. to_break = True # This is our break thing active_renders = get_active_renders() # And here we update the current list for render in active_renders: if render: # If anything is found. Then we don't break. And will check again # on the next go around. This will be deleted from the file by # the end of rendering this file. to_break = False output(render) data = read_settings(render) output(data) # Before we start rendering let's do a couple of things. Mainly # create the folders and clean them if user so desires. folder = render[:render.rfind("/")] try: os.mkdir(project+folder+"/"+data["save_folder"]) except: pass try: if data["clean_folder"]: # Okay so if user so desires. We want to wipe the folder clean. # and so here is the code. for filename in os.listdir(project+folder+"/"+data["save_folder"]): os.remove(project+folder+"/"+data["save_folder"]+"/"+filename) except: pass # So we have our data. Let's do the rendering. But now so fast. We need # a brand new while loop here. I know wild. But I need to make sure that # every frame will be rendered regardless of whether it's crashed ot not. # So I will look for all frames currently in the folder. And render the # first one missing. Always. while True: to_break2 = True # We will need to find th for frame in range(data["start_frame"], data["end_frame"]+1): # I think it's fair to say that some changes to the cframe = getfileoutput(frame, data["image_format"] ) if cframe not in os.listdir(project+folder+"/"+data["save_folder"]): # Basically now we found a missing frame. It could be # what ever. Where-ever. Usually it's every next frame # but the user might delete a frame in the middle. And # will be a missing frame. output(cframe) to_break2 = False # Now that we found it I think we car actually render it progress = Popen(['stdbuf', '-o0', blender, "-b", project+render, "-o", project+folder+"/"+data["save_folder"]+"/####", "-F", data["image_format"] ,"-f", str(frame)], stdout=PIPE, universal_newlines=True) # Now while the render is not finished. We are going to # outout everything it saying to the outside. # But before we start I want to start counting time. So # we would have accurate analytics of the renders. stf = datetime.datetime.now() line = progress.stdout.readline()[:-1] while line: output("VCStudio : RENDERING : "+render+" : "+line) # Now at this stage i want it to also listen to a # command to stop. I might want my CPU back at any # moment. So let's do this. UDP_IP = "127.0.0.1" UDP_PORT = 54545 try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((UDP_IP, UDP_PORT)) sock.settimeout(0.05) input_line, addr = sock.recvfrom(1024) input_line = input_line.decode('utf8') sock.close() if input_line == "VCStudio : RENDER STOP": progress.kill() output("VCStudio : CLOSED") to_break2 = True to_break = True exit() except Exception as e: pass line = progress.stdout.readline()[:-1] # Now that the rendering of the frame is finished. I would # like to save the analytics data. We are going to use # microseconds here. fif = datetime.datetime.now() mil = fif - stf s = int(mil.seconds) m = int(mil.microseconds) thetime = (s*1000000)+m # Now I want to record the analytics per folder. So # data from test renders would not be mangled together # with data from final renders and so on. if data["save_folder"] not in data["analytics"]: data["analytics"][data["save_folder"]] = {} data["analytics"][data["save_folder"]][str(frame)] = thetime # And we want to save the file with the analitycs in them thefolderis = render[:render.rfind("/")]+"/extra" savefile = thefolderis+render[render.rfind("/"):]+".json" with open(project+savefile, 'w') as fp: json.dump(data, fp, indent=4) if to_break2: break remove_active_render(render) break if to_break: break