network_renders.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # THIS FILE IS A PART OF VCStudio
  2. # PYTHON 3
  3. ##############################################################################
  4. # Now you are probably asking yourself. Network? Render? How are these related?
  5. # Actually it's a complicated answer. So let's walk through the idea of the
  6. # Renderer implementation in VCStudio.
  7. # Why do we need renderer in the first place? Doesn't blender already has one?
  8. # Well yes. And I would not care if I was always sitting on a biffy, nice
  9. # machine. But back in 2016, 2017 when I was making I'm Not Even Human I realized
  10. # one very ennoying fact.
  11. # My lap top at the time could not manage the loads of the files. And while
  12. # rendering Blender would crash very often. Of course I was not dumb as was
  13. # rendering into image files and not straight into video. So I could start
  14. # rendering from the last frame.
  15. # Unfortunatly it was happening way too often. So I started looking at ways to
  16. # unload things from the memory so Blender would crash less. One of those ways
  17. # is rendering using a console. Type blender -b <filename> -a and it will launch
  18. # blender without UI saving some memory. Also I could do a simple script that
  19. # restores Blender when if it does crashes.
  20. # By the time of Blender-Organizer 4.0 I had a system of rendering that will
  21. # look into a folder and see if any file between start and end frame are missing
  22. # and render them. Instead of trying to render every single frame in sequense.
  23. # Also in Blender-Organizer 4.0 I made a very clever thing. I openned the
  24. # rendering not in a terminal, but rather in it's own process that is piped to
  25. # a little UI window. Where I show some quick analytics. Tho there was one
  26. # problem. Closing the window was closing the blender. Or if not there was no
  27. # way to cancel it. And it would continue till the end or till the crash.
  28. # So here since VCStudio is a complite re-write of Blender-Organizer I can try
  29. # to re-implement the renderer in a slightly better way.
  30. # I will run a script that recieves stuff from the pipe and has an ability to
  31. # kill the blender's process. And this script will have no UI. But will instead
  32. # use LOCAL NETWORK to talk to VCStudio. I'm using 127.0.0.1 for this so on
  33. # any normal system even without Internet connection it should work. Since this
  34. # IP adress simply means THIS COMPUTER.
  35. # I will probably implement some LOCAL NETWORK talk ability later for the
  36. # multiuser.
  37. # This file is collection of functions for Rendering network sub-system. The UI
  38. # are contained in studio folder. Render it self is a separate file.
  39. ##############################################################################
  40. import os
  41. import json
  42. import time
  43. import socket
  44. def get_runtime_data(project):
  45. # This will read a specific .json file from the system.
  46. template = {"to_render":True,
  47. "current_progress":"Fra: 69 | Mem: 420 |Sample: 69/420"}
  48. try:
  49. with open(project+"/render_runtime.json") as json_file:
  50. data = json.load(json_file)
  51. except:
  52. data = template.copy()
  53. return data
  54. def save_runtime_data(data, project):
  55. with open(project+"/render_runtime.json", 'w') as fp:
  56. json.dump(data, fp, indent=4)
  57. def read_renders(win):
  58. # This function will listen for 127.0.0.1 port 54545 for data about current
  59. # renders. NOTE: I'm not doing any encryption and will use UDP protocol.
  60. # so it's quite simple to mess arround with it. NO SECURITY. But I don't see
  61. # any use in hacking it appart from maybe making VCStudio think it's rendering
  62. # a different frame or something.
  63. # First thing. And you probably think that I'm crazy but bare with me. I
  64. # need to see if a active_renders.data exists. And will read from it on
  65. # each frame. I know. A bit of not cool. But to be honest. The language
  66. # files are also read at every frame. Yeah...
  67. if not os.path.exists(win.project+"/set/active_renders.data"):
  68. m = open(win.project+"/set/active_renders.data", "w")
  69. m.close()
  70. # Now we are going to read it on every frame to see that renders are still
  71. # there.
  72. r = open(win.project+"/set/active_renders.data")
  73. r = r.read()
  74. r = r.split("\n")
  75. filenames = []
  76. for filename in r:
  77. if filename:
  78. # So basically the file contains list of files currently placed for
  79. # rendering. The renderer will go one by one and when finised will
  80. # remove the filename from this file. Also to cancel the render the
  81. # filename should be removed. The rest will be done using a very
  82. # crappy UDP network protocol on localhost.
  83. # Now if you just openned the VCStudio. While maybe render were
  84. # doing their job somewhere on the background. You want to re-new
  85. # all of the data.
  86. # So...
  87. if filename not in win.renders:
  88. # We are going to read the JSON file of the render.
  89. folder = filename[:filename.rfind("/")]+"/extra"
  90. savefile = folder+filename[filename.rfind("/"):]+".json"
  91. # It might not exits so we are going to do this with try.
  92. try:
  93. with open(win.project+savefile) as json_file:
  94. data = json.load(json_file)
  95. win.renders[filename] = data
  96. except:
  97. pass
  98. # Now let's make a list of all lines anyway
  99. if filename not in filenames:
  100. filenames.append(filename)
  101. # Now let's remove any of them renders that are finished or otherwise
  102. # removed from the file.
  103. for stuff in list(win.renders.keys()):
  104. if stuff not in filenames:
  105. del win.renders[stuff]
  106. try:
  107. if stuff == win.current["renders_window"]["filename"]:
  108. win.current["renders_window"]["filename"] = ""
  109. except:
  110. pass
  111. # Now that we know about the data. Let's read the stream from the network
  112. string_read = ""
  113. # try:
  114. # UDP_IP = "127.0.0.1"
  115. # UDP_PORT = 54545
  116. # sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  117. # sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  118. # sock.bind((UDP_IP, UDP_PORT))
  119. # # Now usually it will wait for a message untill one appears. But render
  120. # # could be finished. Or some error might accur. Or it could be that no
  121. # # render what so ever. So I don't want the UI to freez. So I'm going to
  122. # # put a timeout. Very short timeout.
  123. # sock.settimeout(0.005)
  124. # # This comes with it's own problems. Mainly I tested I need to send
  125. # # about 500 messages at ones for it to be recognized at all. But it's
  126. # # not a big deal what so ever. Just need to keep in mind. 500 messages.
  127. # data, addr = sock.recvfrom(1024)
  128. # data = data.decode('utf8')
  129. # sock.close()
  130. # string_read = data
  131. # except:
  132. # pass
  133. runtime = get_runtime_data(win.project)
  134. win.render_runtime = runtime
  135. if int(time.time()) > runtime.get("running", 0) + 100 or not runtime.get("to_render"):
  136. #print("Should be off")
  137. for blend in win.renders:
  138. win.renders[blend]["rendering"] = False
  139. else:
  140. #print("Should be on...")
  141. blend, blend_string = runtime.get("current_file", ""), runtime.get("current_progress", "")
  142. # So we've got 2 peaces of data from the renderer. Blender. Which
  143. # is a path to the blend file. Similar to the one in
  144. # active_renders.data file that we read previously.
  145. # And we've got a raw string that blender outputs during render.
  146. # Example:
  147. # Fra:70 Mem:93.09M (Peak 97.78M) | Time:00:01.83 | Rendering 26 / 64 samples
  148. # You can find a string like this in the top banner inside the blender.
  149. # From this string we can find out a bunch of things. But not everything is
  150. # that simple. Cycles and Eevee for exmple output different strings. Any
  151. # other, wild render engine will output it's own string.
  152. # Now I'd like to actually load the data from the JSON file at every step
  153. # like this. Because our rendering script will be recording and saving
  154. # render times of each frame into the file. I need it for analytics.
  155. if blend in win.renders:
  156. folder = blend[:blend.rfind("/")]+"/extra"
  157. savefile = folder+blend[blend.rfind("/"):]+".json"
  158. # It might not exits so we are going to do this with try.
  159. try:
  160. with open(win.project+savefile) as json_file:
  161. data = json.load(json_file)
  162. win.renders[blend] = data
  163. except:
  164. pass
  165. win.renders[blend]["rendering"] = blend_string
  166. # Next is let's try finding out the current frame rendering. It's
  167. # probably not that hard. Every string usually starts with Fra:<number>
  168. # So let's try doing it.
  169. frame = 0
  170. try:
  171. frame = int(blend_string[4:blend_string.find(" ")])
  172. except:
  173. pass
  174. if blend in win.renders:
  175. win.renders[blend]["current_frame"] = frame
  176. # The rest of it I will do in the UI.
  177. def stop_render(win):
  178. # This function will stop the rendering. It will bombard the renderer with
  179. # stop messages.
  180. # for i in range(5000):
  181. # cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  182. # cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  183. # cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  184. # cs1.sendto(bytes("VCStudio : RENDER STOP", 'utf-8'), ('127.0.0.1', 54545))
  185. runtime = get_runtime_data(win.project)
  186. runtime["to_render"] = False
  187. save_runtime_data(runtime, win.project)
  188. for blend in win.renders:
  189. win.renders[blend]["rendering"] = False