during_render.py 11 KB

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