runphoton.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from zipfile import ZipFile
  2. import subprocess
  3. import time
  4. from datetime import datetime
  5. import logging
  6. from collections import namedtuple, defaultdict
  7. try:
  8. import rpyc
  9. except ImportError:
  10. rpyc = None
  11. import hashlib
  12. from dateutil import parser
  13. from utils.path import path
  14. from utils.xml import StringToTree, FileToTree, subelement, TreeToFile, prettyPrint
  15. from utils.objparser import Mesh
  16. from utils.materials import materialsAsXml
  17. log = logging.getLogger('runphoton')
  18. Resolution = namedtuple('Resolution', 'x y z')
  19. BBox = namedtuple('BBox', 'x1 x2 y1 y2 z1 z2')
  20. MESH_CACHE = {}
  21. def prepareControlFile(ctrlfile, objfile, materials, light, resolution=None, bbox=None,
  22. density=None, numphotons=None, outname=None, disabledObjects=None,
  23. mesh=None, tmpdir=None, sceneName=None, rootAttribs=None, remapMaterials=None):
  24. ''' read ctrlfile and fill with values (materials, light and simulation params)
  25. @param materials: the material parameters
  26. @type materials: dict with key: matname and value namedtuple(reflect alpha ior)
  27. @param light: the params of the light source
  28. @type light: namedtuple(power x y z)
  29. @param objfile: file containing the scene in wavefront .obj format
  30. @type objfile: path
  31. @param mesh: a mesh from an allready parsed obj file
  32. @type mesh: Mesh()
  33. @param tmpdir: directory where the generated files should be stored, if None use parent dir of ctrlfile
  34. @type tmpdir: path
  35. @param disabledObjects: objects by name, that should be removed from ctrlfile
  36. @type disabledObjects: set(str)
  37. '''
  38. if mesh is None:
  39. cache_key = (objfile, objfile.mtime)
  40. if cache_key in MESH_CACHE:
  41. mesh = MESH_CACHE[cache_key]
  42. else:
  43. mesh = Mesh()
  44. mesh.parseObjFile(objfile)
  45. MESH_CACHE[cache_key] = mesh
  46. if tmpdir is None:
  47. tmpdir = ctrlfile.parent.joinpath('_tmp')
  48. if not tmpdir.exists():
  49. tmpdir.mkdir()
  50. tree = FileToTree(ctrlfile)
  51. know_materials = set()
  52. # add <brdf> material definitions to <scene>
  53. for e in materialsAsXml(materials).getroot().xpath('brdf'):
  54. know_materials.add(e.attrib['name'])
  55. tree.getroot().insert(0, e)
  56. obj2fnames = {}
  57. for objname, text in mesh.splitToText().items():
  58. objfname = tmpdir.joinpath('_%s.obj' % objname)
  59. objfname.write_text(text)
  60. obj2fnames[objname] = objfname
  61. for objname in mesh.objects:
  62. if disabledObjects is not None and objname in disabledObjects:
  63. continue
  64. matname = mesh.materials.get(objname, 'default')
  65. # XXX: hackish, only for thesis eval
  66. if remapMaterials is not None:
  67. matname = remapMaterials[matname]
  68. if matname == '(null)':
  69. log.warn('no material defined for object %s' % objname)
  70. continue
  71. elif not matname in know_materials:
  72. log.warn('unknown material: %s' % matname)
  73. continue
  74. attribs = dict([('m%s%s' % (i, j), 0.0 if i != j else 1.0) for i in range(4) for j in range(4)])
  75. tEl = subelement(tree.getroot(), 'transform', attribs=attribs)
  76. objEl = subelement(tEl, 'object', attribs={'name': objname, 'brdf_name': matname})
  77. meshEl = subelement(objEl, 'mesh', attribs={'autosmooth': 180})
  78. hash = hashlib.sha1(obj2fnames[objname].text()).hexdigest()
  79. subelement(meshEl, 'include', attribs={'file': obj2fnames[objname].name, 'hash': hash})
  80. lightEl = tree.getroot().xpath('light')[0]
  81. lightEl.attrib['power'] = str(light.power)
  82. fromEl = lightEl.xpath('from')[0]
  83. fromEl.attrib['x'] = str(light.x)
  84. fromEl.attrib['y'] = str(light.y)
  85. fromEl.attrib['z'] = str(light.z)
  86. if outname is not None:
  87. outputEl = tree.getroot().xpath('rwp/volumerender/output')[0]
  88. outputEl.attrib['filename'] = outname
  89. if density is not None:
  90. radiusEl = tree.getroot().xpath('rwp/volumerender/density_estimator/radius')[0]
  91. radiusEl.attrib['value'] = str(density)
  92. if resolution is not None:
  93. sizeEl = tree.getroot().xpath('rwp/volumerender/size')[0]
  94. sizeEl.attrib['x'] = str(resolution.x)
  95. sizeEl.attrib['y'] = str(resolution.y)
  96. sizeEl.attrib['z'] = str(resolution.z)
  97. if bbox is not None:
  98. minEl = tree.getroot().xpath('rwp/simulation/bbox/min')[0]
  99. minEl.attrib['x'] = str(bbox.x1)
  100. minEl.attrib['y'] = str(bbox.y1)
  101. minEl.attrib['z'] = str(bbox.z1)
  102. maxEl = tree.getroot().xpath('rwp/simulation/bbox/max')[0]
  103. maxEl.attrib['x'] = str(bbox.x2)
  104. maxEl.attrib['y'] = str(bbox.y2)
  105. maxEl.attrib['z'] = str(bbox.z2)
  106. if numphotons is not None:
  107. photonsEl = tree.getroot().xpath('rwp/simulation/photons')[0]
  108. photonsEl.attrib['value'] = str(numphotons)
  109. if rootAttribs is not None:
  110. tree.getroot().attrib.update(rootAttribs)
  111. material_ctrlfile = tmpdir.joinpath('_' + ctrlfile.name)
  112. material_ctrlfile.write_text(prettyPrint(tree))
  113. return material_ctrlfile
  114. def runphoton(ctrlfile, verbose=True):
  115. ''' run photon remotely via rpyc by using a (prepared) ctrlfile
  116. return ctrlfile
  117. '''
  118. ctrlfile = path(ctrlfile)
  119. workdir = ctrlfile.parent
  120. conn = rpyc.classic.connect("127.0.0.1", port=18812)
  121. remote_open = conn.modules.__builtin__.open
  122. remote_check_output = conn.modules.subprocess.check_output
  123. remote_getsize = conn.modules.os.path.getsize
  124. remote_ZipFile = conn.modules.zipfile.ZipFile
  125. tree = FileToTree(ctrlfile)
  126. s = ctrlfile.text()
  127. log.info('transmitting control file "%s" [%.2f kb] ...' % (ctrlfile, len(s) / 1024.0))
  128. remote_open('tmp/%s' % ctrlfile.name, 'wb').write(s)
  129. objfiles = tree.xpath('//mesh/include[@file]/@file')
  130. # full remote hash building
  131. conn.execute("import hashlib")
  132. remote_hash = lambda f: conn.eval("hashlib.sha1(open('tmp/%s').read()).hexdigest()" % f)
  133. try:
  134. file2hash = dict([(f, remote_hash(f)) for f in objfiles])
  135. except Exception:
  136. # probably a file does not exists
  137. print 'got not remote hashes for objfiles'
  138. file2hash = defaultdict(str)
  139. # send local files to server
  140. for objfile in objfiles:
  141. localfile = workdir.joinpath(objfile)
  142. if hashlib.sha1(open(localfile).read()).hexdigest() == file2hash[objfile]:
  143. print '%s is uptodate' % objfile
  144. continue
  145. s = localfile.text()
  146. log.info('transmitting obj file "%s" [%.2f kb] ...' % (objfile, len(s) / 1024.0))
  147. remote_open('tmp/%s' % objfile, 'wb').write(s)
  148. # run photon
  149. scenefile = 'tmp/%s' % ctrlfile.name
  150. log.info('running photon remotely on %s' % scenefile)
  151. try:
  152. cmd = ['photon', '-s', scenefile]
  153. output = remote_check_output(cmd)
  154. if verbose:
  155. print output
  156. except subprocess.CalledProcessError, e:
  157. log.error('error during executing: %s' % ' '.join(cmd))
  158. log.error('output:\n%s' % e.output)
  159. #~ raise
  160. except Exception:
  161. print 'XXXXX'
  162. outfname = tree.getroot().xpath('string(rwp/volumerender/output/@filename)')
  163. assert outfname != ''
  164. # zip .raw file to reduce download size
  165. rawfile = '%s.raw' % outfname
  166. log.info('zipping %s' % rawfile)
  167. zf = remote_ZipFile(rawfile + '.zip', mode='w', compression=conn.modules.zipfile.ZIP_DEFLATED)
  168. zf.write(rawfile)
  169. zf.close()
  170. # download result files
  171. for ext in '.raw.zip', '.dat':
  172. fn = outfname + ext
  173. log.info('transmitting %s file [%.2f kb] ...' % (fn, remote_getsize(fn) / 1024.0))
  174. t = time.time()
  175. s = remote_open(fn, 'rb').read()
  176. log.info('in %.2f sec' % (time.time() - t))
  177. fn = workdir.joinpath('%s%s' % (outfname, ext))
  178. open(fn, 'wb').write(s)
  179. fn = workdir.joinpath('%s.raw.zip' % outfname)
  180. log.info('unzipping %s' % fn)
  181. zf = ZipFile(fn)
  182. s = zf.read(rawfile)
  183. open(workdir.joinpath('%s.raw' % outfname), 'wb').write(s)
  184. conn.close()
  185. # return .dat file
  186. return {'datfile': workdir.joinpath('%s.dat' % outfname), 'ctrlfile': ctrlfile}
  187. if __name__ == '__main__':
  188. name = '../maps/onewall/One-Wall'
  189. runphoton(name)