during_render.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. #############################################################################
  4. # This file will handle rendering on the background. For more info about how
  5. # it all works. See:
  6. # network/network_renders.py
  7. #############################################################################
  8. import os
  9. import sys
  10. import json
  11. import time
  12. import signal
  13. import socket
  14. import datetime
  15. from subprocess import *
  16. # Following 2 functions are copied from the code of Blender-Organizer. How cool
  17. # that python2 and python3 can share so much code.
  18. def getnumstr(num):
  19. # This function turns numbers like 1 or 20 into numbers like 0001 or 0020
  20. s = ""
  21. for i in range(4-len(str(num))):
  22. s = s + "0"
  23. return s+str(num)
  24. def getfileoutput(num, FORMAT):
  25. # Function gives an output of a file. From the current frame that's rendering.
  26. # instead of having frame 1 and format EXR it will give you 0001.exr
  27. s = getnumstr(num)
  28. if FORMAT == "JPEG":
  29. s = s + ".jpg"
  30. else:
  31. s = s + "." + FORMAT.lower()
  32. return s
  33. def output(string):
  34. # This function will act similar to python's print. But rather then just
  35. # printing the string to the terminal. It will also send a signal to the
  36. # VCStudio. And anyone who listening.
  37. string = str(string)
  38. print(string)
  39. # for i in range(500):
  40. # cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  41. # cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  42. # cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  43. # cs1.sendto(bytes(string, 'utf-8'), ('127.0.0.1', 54545))
  44. # Now to make it all work the script that will lanuch this script will give it
  45. # an agrument of a project location. Which we might need to use for rendering
  46. project = ""
  47. blender = ""
  48. if len(sys.argv) > 1:
  49. project = sys.argv[1]
  50. blender = sys.argv[2]
  51. if not project or not blender:
  52. exit()
  53. output(project)
  54. output(blender)
  55. # Now that we have the project. Let's get data about the current renders that
  56. # are setup for rendering.
  57. def get_active_renders():
  58. # This tiny function will gives us the list of current files set to render
  59. # at any moment these will be needed.
  60. active_renders = []
  61. try:
  62. active_renders = open(project+"/set/active_renders.data")
  63. active_renders = active_renders.read()
  64. active_renders = active_renders.split("\n")
  65. except:
  66. pass
  67. return active_renders
  68. def remove_active_render(render):
  69. # This function will edit the active renders and remove the current
  70. # render from the list.
  71. active_renders = open(project+"/set/active_renders.data")
  72. active_renders = active_renders.read()
  73. active_renders = active_renders.split("\n")
  74. s = open(project+"/set/active_renders.data", "w")
  75. for i in active_renders:
  76. if i != render and i:
  77. s.write(i+"\n")
  78. s.close()
  79. active_renders = get_active_renders()
  80. # Now I think we also need to get the data from the files. So to speak read their json
  81. # render settings to be able to know what are our start and end frames, format and such.
  82. def read_settings(filename):
  83. # This function will read data from the various render settings.
  84. folder = filename[:filename.rfind("/")]+"/extra"
  85. savefile = folder+filename[filename.rfind("/"):]+".json"
  86. data = {}
  87. try:
  88. with open(project+savefile) as json_file:
  89. data = json.load(json_file)
  90. except Exception as e:
  91. output(e)
  92. return data
  93. def get_runtime_data(project):
  94. # This will read a specific .json file from the system.
  95. template = {"to_render":True,
  96. "current_progress":"Fra: 69 | Mem: 420 |Sample: 69/420"}
  97. try:
  98. with open(project+"/render_runtime.json") as json_file:
  99. data = json.load(json_file)
  100. except:
  101. data = template.copy()
  102. return data
  103. def save_runtime_data(data, project):
  104. with open(project+"/render_runtime.json", 'w') as fp:
  105. json.dump(data, fp, indent=4)
  106. runtime = get_runtime_data(project)
  107. # Now let's start the main loop. I'm pretty sure that there will be tons of bugs
  108. # at this stage. So please look at the following code carefully.
  109. # What I want to do is always read the first line and render it. By the end
  110. # delete the first line from the render list. Remove it from a file. Tho the
  111. # removal could happen at any time anywhere. A user could remove the line
  112. # manually. Or delete the file from the renders in the VCStudio. It doesn't
  113. # matter. This file should be abborted and the next one should start rendering
  114. # to do this. I need to write some clever algorithm.
  115. while True:
  116. # I know wild. A while with a True. OMG. But I guess it's the only way to
  117. # insure that it will keep working if there are any current stuff in the
  118. # list. And will stop if there is absolutelly nothing in the list.
  119. to_break = True # This is our break thing
  120. active_renders = get_active_renders() # And here we update the current list
  121. for render in active_renders:
  122. if render:
  123. # If anything is found. Then we don't break. And will check again
  124. # on the next go around. This will be deleted from the file by
  125. # the end of rendering this file.
  126. to_break = False
  127. output(render)
  128. data = read_settings(render)
  129. output(data)
  130. # Before we start rendering let's do a couple of things. Mainly
  131. # create the folders and clean them if user so desires.
  132. folder = render[:render.rfind("/")]
  133. try:
  134. os.mkdir(project+folder+"/"+data["save_folder"])
  135. except:
  136. pass
  137. try:
  138. if data["clean_folder"]:
  139. # Okay so if user so desires. We want to wipe the folder clean.
  140. # and so here is the code.
  141. for filename in os.listdir(project+folder+"/"+data["save_folder"]):
  142. os.remove(project+folder+"/"+data["save_folder"]+"/"+filename)
  143. except:
  144. pass
  145. # So we have our data. Let's do the rendering. But now so fast. We need
  146. # a brand new while loop here. I know wild. But I need to make sure that
  147. # every frame will be rendered regardless of whether it's crashed ot not.
  148. # So I will look for all frames currently in the folder. And render the
  149. # first one missing. Always.
  150. while True:
  151. to_break2 = True
  152. # We will need to find th
  153. for frame in range(data["start_frame"], data["end_frame"]+1):
  154. # I think it's fair to say that some changes to the
  155. cframe = getfileoutput(frame, data["image_format"] )
  156. if cframe not in os.listdir(project+folder+"/"+data["save_folder"]):
  157. # Basically now we found a missing frame. It could be
  158. # what ever. Where-ever. Usually it's every next frame
  159. # but the user might delete a frame in the middle. And
  160. # will be a missing frame.
  161. output(cframe)
  162. to_break2 = False
  163. # Starting Render
  164. runtime = get_runtime_data(project)
  165. runtime["started_rendering"] = time.time()
  166. runtime["current_frame"] = frame
  167. save_runtime_data(runtime, project)
  168. # Now that we found it I think we car actually render it
  169. progress = Popen(['stdbuf', '-o0', blender, "-b",
  170. project+render, "-o",
  171. project+folder+"/"+data["save_folder"]+"/####", "-F",
  172. data["image_format"] ,"-f",
  173. str(frame)], stdout=PIPE, universal_newlines=True)
  174. # Now while the render is not finished. We are going to
  175. # outout everything it saying to the outside.
  176. # But before we start I want to start counting time. So
  177. # we would have accurate analytics of the renders.
  178. stf = datetime.datetime.now()
  179. line = progress.stdout.readline()[:-1]
  180. while line:
  181. output("VCStudio : RENDERING : "+render+" : "+line)
  182. # Now at this stage i want it to also listen to a
  183. # command to stop. I might want my CPU back at any
  184. # moment. So let's do this.
  185. # UDP_IP = "127.0.0.1"
  186. # UDP_PORT = 54545
  187. # try:
  188. # sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  189. # sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  190. # sock.bind((UDP_IP, UDP_PORT))
  191. # sock.settimeout(0.05)
  192. # input_line, addr = sock.recvfrom(1024)
  193. # input_line = input_line.decode('utf8')
  194. # sock.close()
  195. # if input_line == "VCStudio : RENDER STOP":
  196. # progress.kill()
  197. # output("VCStudio : CLOSED")
  198. # to_break2 = True
  199. # to_break = True
  200. # exit()
  201. # except Exception as e:
  202. # pass
  203. runtime = get_runtime_data(project)
  204. if not runtime.get("to_render"):
  205. try:
  206. #progress.kill()
  207. output("VCStudio : CLOSED")
  208. to_break2 = True
  209. to_break = True
  210. os.killpg(os.getpgid(progress.pid), signal.SIGKILL)
  211. exit()
  212. except Exception as e:
  213. runtime["error"] = str(e)
  214. save_runtime_data(runtime, project)
  215. runtime["current_progress"] = line
  216. runtime["current_file"] = render
  217. runtime["running"] = int(time.time())
  218. runtime["blender_id"] = int(progress.pid)
  219. save_runtime_data(runtime, project)
  220. line = progress.stdout.readline()[:-1]
  221. # Now that the rendering of the frame is finished. I would
  222. # like to save the analytics data. We are going to use
  223. # microseconds here.
  224. fif = datetime.datetime.now()
  225. mil = fif - stf
  226. s = int(mil.seconds)
  227. m = int(mil.microseconds)
  228. thetime = (s*1000000)+m
  229. # Now I want to record the analytics per folder. So
  230. # data from test renders would not be mangled together
  231. # with data from final renders and so on.
  232. if data["save_folder"] not in data["analytics"]:
  233. data["analytics"][data["save_folder"]] = {}
  234. data["analytics"][data["save_folder"]][str(frame)] = thetime
  235. # And we want to save the file with the analitycs in them
  236. thefolderis = render[:render.rfind("/")]+"/extra"
  237. savefile = thefolderis+render[render.rfind("/"):]+".json"
  238. with open(project+savefile, 'w') as fp:
  239. json.dump(data, fp, indent=4)
  240. if to_break2:
  241. break
  242. remove_active_render(render)
  243. break
  244. if to_break:
  245. break