304 lines
11 KiB
Python
304 lines
11 KiB
Python
# 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
|