bl_test.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. import sys
  20. import os
  21. # may split this out into a new file
  22. def replace_bpy_app_version():
  23. """ So MD5's are predictable from output which uses blenders versions.
  24. """
  25. import bpy
  26. app = bpy.app
  27. app_fake = type(bpy)("bpy.app")
  28. for attr in dir(app):
  29. if not attr.startswith("_"):
  30. setattr(app_fake, attr, getattr(app, attr))
  31. app_fake.version = 0, 0, 0
  32. app_fake.version_string = "0.00 (sub 0)"
  33. bpy.app = app_fake
  34. def clear_startup_blend():
  35. import bpy
  36. for col in bpy.data.collections:
  37. for obj in col.objects:
  38. col.objects.unlink(obj)
  39. def blend_to_md5():
  40. import bpy
  41. scene = bpy.context.scene
  42. ROUND = 4
  43. def matrix2str(matrix):
  44. return "".join([str(round(axis, ROUND)) for vector in matrix for axis in vector]).encode('ASCII')
  45. def coords2str(seq, attr):
  46. return "".join([str(round(axis, ROUND)) for vertex in seq for axis in getattr(vertex, attr)]).encode('ASCII')
  47. import hashlib
  48. md5 = hashlib.new("md5")
  49. md5_update = md5.update
  50. for obj in scene.objects:
  51. md5_update(matrix2str(obj.matrix_world))
  52. data = obj.data
  53. if type(data) == bpy.types.Mesh:
  54. md5_update(coords2str(data.vertices, "co"))
  55. elif type(data) == bpy.types.Curve:
  56. for spline in data.splines:
  57. md5_update(coords2str(spline.bezier_points, "co"))
  58. md5_update(coords2str(spline.points, "co"))
  59. return md5.hexdigest()
  60. def main():
  61. argv = sys.argv
  62. print(" args:", " ".join(argv))
  63. argv = argv[argv.index("--") + 1:]
  64. def arg_extract(arg, optional=True, array=False):
  65. arg += "="
  66. if array:
  67. value = []
  68. else:
  69. value = None
  70. i = 0
  71. while i < len(argv):
  72. if argv[i].startswith(arg):
  73. item = argv[i][len(arg):]
  74. del argv[i]
  75. i -= 1
  76. if array:
  77. value.append(item)
  78. else:
  79. value = item
  80. break
  81. i += 1
  82. if (not value) and (not optional):
  83. print(" '%s' not set" % arg)
  84. sys.exit(1)
  85. return value
  86. run = arg_extract("--run", optional=False)
  87. md5 = arg_extract("--md5", optional=False)
  88. md5_method = arg_extract("--md5_method", optional=False) # 'SCENE' / 'FILE'
  89. # only when md5_method is 'FILE'
  90. md5_source = arg_extract("--md5_source", optional=True, array=True)
  91. # save blend file, for testing
  92. write_blend = arg_extract("--write-blend", optional=True)
  93. # ensure files are written anew
  94. for f in md5_source:
  95. if os.path.exists(f):
  96. os.remove(f)
  97. import bpy
  98. replace_bpy_app_version()
  99. if not bpy.data.filepath:
  100. clear_startup_blend()
  101. print(" Running: '%s'" % run)
  102. print(" MD5: '%s'!" % md5)
  103. try:
  104. result = eval(run)
  105. except:
  106. import traceback
  107. traceback.print_exc()
  108. sys.exit(1)
  109. if write_blend is not None:
  110. print(" Writing Blend: %s" % write_blend)
  111. bpy.ops.wm.save_mainfile('EXEC_DEFAULT', filepath=write_blend)
  112. print(" Result: '%s'" % str(result))
  113. if not result:
  114. print(" Running: %s -> False" % run)
  115. sys.exit(1)
  116. if md5_method == 'SCENE':
  117. md5_new = blend_to_md5()
  118. elif md5_method == 'FILE':
  119. if not md5_source:
  120. print(" Missing --md5_source argument")
  121. sys.exit(1)
  122. for f in md5_source:
  123. if not os.path.exists(f):
  124. print(" Missing --md5_source=%r argument does not point to a file")
  125. sys.exit(1)
  126. import hashlib
  127. md5_instance = hashlib.new("md5")
  128. md5_update = md5_instance.update
  129. for f in md5_source:
  130. filehandle = open(f, "rb")
  131. md5_update(filehandle.read())
  132. filehandle.close()
  133. md5_new = md5_instance.hexdigest()
  134. else:
  135. print(" Invalid --md5_method=%s argument is not a valid source")
  136. sys.exit(1)
  137. if md5 != md5_new:
  138. print(" Running: %s\n MD5 Received: %s\n MD5 Expected: %s" % (run, md5_new, md5))
  139. sys.exit(1)
  140. print(" Success: %s" % run)
  141. if __name__ == "__main__":
  142. # So a python error exits(1)
  143. try:
  144. main()
  145. except:
  146. import traceback
  147. traceback.print_exc()
  148. sys.exit(1)