123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- # 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 time
- import signal
- 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
- 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)
- runtime = get_runtime_data(project)
-
- # 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
- # Starting Render
- runtime = get_runtime_data(project)
- runtime["started_rendering"] = time.time()
- runtime["current_frame"] = frame
- save_runtime_data(runtime, project)
-
- # 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
- runtime = get_runtime_data(project)
- if not runtime.get("to_render"):
- try:
- #progress.kill()
-
- output("VCStudio : CLOSED")
- to_break2 = True
- to_break = True
- os.killpg(os.getpgid(progress.pid), signal.SIGKILL)
- exit()
- except Exception as e:
- runtime["error"] = str(e)
- save_runtime_data(runtime, project)
-
- runtime["current_progress"] = line
- runtime["current_file"] = render
- runtime["running"] = int(time.time())
- runtime["blender_id"] = int(progress.pid)
-
- save_runtime_data(runtime, project)
-
- 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
|