# THIS FILE IS A PART OF VCStudio # PYTHON 3 ############################################################################## # Now you are probably asking yourself. Network? Render? How are these related? # Actually it's a complicated answer. So let's walk through the idea of the # Renderer implementation in VCStudio. # Why do we need renderer in the first place? Doesn't blender already has one? # Well yes. And I would not care if I was always sitting on a biffy, nice # machine. But back in 2016, 2017 when I was making I'm Not Even Human I realized # one very ennoying fact. # My lap top at the time could not manage the loads of the files. And while # rendering Blender would crash very often. Of course I was not dumb as was # rendering into image files and not straight into video. So I could start # rendering from the last frame. # Unfortunatly it was happening way too often. So I started looking at ways to # unload things from the memory so Blender would crash less. One of those ways # is rendering using a console. Type blender -b -a and it will launch # blender without UI saving some memory. Also I could do a simple script that # restores Blender when if it does crashes. # By the time of Blender-Organizer 4.0 I had a system of rendering that will # look into a folder and see if any file between start and end frame are missing # and render them. Instead of trying to render every single frame in sequense. # Also in Blender-Organizer 4.0 I made a very clever thing. I openned the # rendering not in a terminal, but rather in it's own process that is piped to # a little UI window. Where I show some quick analytics. Tho there was one # problem. Closing the window was closing the blender. Or if not there was no # way to cancel it. And it would continue till the end or till the crash. # So here since VCStudio is a complite re-write of Blender-Organizer I can try # to re-implement the renderer in a slightly better way. # I will run a script that recieves stuff from the pipe and has an ability to # kill the blender's process. And this script will have no UI. But will instead # use LOCAL NETWORK to talk to VCStudio. I'm using 127.0.0.1 for this so on # any normal system even without Internet connection it should work. Since this # IP adress simply means THIS COMPUTER. # I will probably implement some LOCAL NETWORK talk ability later for the # multiuser. # This file is collection of functions for Rendering network sub-system. The UI # are contained in studio folder. Render it self is a separate file. ############################################################################## import os import json import time import socket def get_runtime_data(project): # This will read a specific .json file from the system. template = {"to_render":True, "current_progress":"Fra: 69 | Mem: 420 |Sample: 69/420"} try: with open(project+"/render_runtime.json") as json_file: data = json.load(json_file) except: data = template.copy() return data def save_runtime_data(data, project): with open(project+"/render_runtime.json", 'w') as fp: json.dump(data, fp, indent=4) def read_renders(win): # This function will listen for 127.0.0.1 port 54545 for data about current # renders. NOTE: I'm not doing any encryption and will use UDP protocol. # so it's quite simple to mess arround with it. NO SECURITY. But I don't see # any use in hacking it appart from maybe making VCStudio think it's rendering # a different frame or something. # First thing. And you probably think that I'm crazy but bare with me. I # need to see if a active_renders.data exists. And will read from it on # each frame. I know. A bit of not cool. But to be honest. The language # files are also read at every frame. Yeah... if not os.path.exists(win.project+"/set/active_renders.data"): m = open(win.project+"/set/active_renders.data", "w") m.close() # Now we are going to read it on every frame to see that renders are still # there. r = open(win.project+"/set/active_renders.data") r = r.read() r = r.split("\n") filenames = [] for filename in r: if filename: # So basically the file contains list of files currently placed for # rendering. The renderer will go one by one and when finised will # remove the filename from this file. Also to cancel the render the # filename should be removed. The rest will be done using a very # crappy UDP network protocol on localhost. # Now if you just openned the VCStudio. While maybe render were # doing their job somewhere on the background. You want to re-new # all of the data. # So... if filename not in win.renders: # We are going to read the JSON file of the render. folder = filename[:filename.rfind("/")]+"/extra" savefile = folder+filename[filename.rfind("/"):]+".json" # It might not exits so we are going to do this with try. try: with open(win.project+savefile) as json_file: data = json.load(json_file) win.renders[filename] = data except: pass # Now let's make a list of all lines anyway if filename not in filenames: filenames.append(filename) # Now let's remove any of them renders that are finished or otherwise # removed from the file. for stuff in list(win.renders.keys()): if stuff not in filenames: del win.renders[stuff] try: if stuff == win.current["renders_window"]["filename"]: win.current["renders_window"]["filename"] = "" except: pass # Now that we know about the data. Let's read the stream from the network string_read = "" # try: # UDP_IP = "127.0.0.1" # UDP_PORT = 54545 # sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sock.bind((UDP_IP, UDP_PORT)) # # Now usually it will wait for a message untill one appears. But render # # could be finished. Or some error might accur. Or it could be that no # # render what so ever. So I don't want the UI to freez. So I'm going to # # put a timeout. Very short timeout. # sock.settimeout(0.005) # # This comes with it's own problems. Mainly I tested I need to send # # about 500 messages at ones for it to be recognized at all. But it's # # not a big deal what so ever. Just need to keep in mind. 500 messages. # data, addr = sock.recvfrom(1024) # data = data.decode('utf8') # sock.close() # string_read = data # except: # pass runtime = get_runtime_data(win.project) win.render_runtime = runtime if int(time.time()) > runtime.get("running", 0) + 100 or not runtime.get("to_render"): #print("Should be off") for blend in win.renders: win.renders[blend]["rendering"] = False else: #print("Should be on...") blend, blend_string = runtime.get("current_file", ""), runtime.get("current_progress", "") # So we've got 2 peaces of data from the renderer. Blender. Which # is a path to the blend file. Similar to the one in # active_renders.data file that we read previously. # And we've got a raw string that blender outputs during render. # Example: # Fra:70 Mem:93.09M (Peak 97.78M) | Time:00:01.83 | Rendering 26 / 64 samples # You can find a string like this in the top banner inside the blender. # From this string we can find out a bunch of things. But not everything is # that simple. Cycles and Eevee for exmple output different strings. Any # other, wild render engine will output it's own string. # Now I'd like to actually load the data from the JSON file at every step # like this. Because our rendering script will be recording and saving # render times of each frame into the file. I need it for analytics. if blend in win.renders: folder = blend[:blend.rfind("/")]+"/extra" savefile = folder+blend[blend.rfind("/"):]+".json" # It might not exits so we are going to do this with try. try: with open(win.project+savefile) as json_file: data = json.load(json_file) win.renders[blend] = data except: pass win.renders[blend]["rendering"] = blend_string # Next is let's try finding out the current frame rendering. It's # probably not that hard. Every string usually starts with Fra: # So let's try doing it. frame = 0 try: frame = int(blend_string[4:blend_string.find(" ")]) except: pass win.renders[blend]["current_frame"] = frame # The rest of it I will do in the UI. def stop_render(win): # This function will stop the rendering. It will bombard the renderer with # stop messages. # for i in range(5000): # 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("VCStudio : RENDER STOP", 'utf-8'), ('127.0.0.1', 54545)) runtime = get_runtime_data(win.project) runtime["to_render"] = False save_runtime_data(runtime, win.project) for blend in win.renders: win.renders[blend]["rendering"] = False