RENDER-BORDER.PY 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. bl_info = {
  19. "name": "Render Border",
  20. "description": "Render Border",
  21. "author": "Christian Brinkmann, David Boho",
  22. "version": (0, 0, 5),
  23. "blender": (2, 80, 0),
  24. "tracker_url": "https://github.com/p2or/blender-renderborder",
  25. "location": "Camera > Properties > Data > Render Border",
  26. "category": "Render"
  27. }
  28. import bpy
  29. from bpy.app.handlers import persistent
  30. def round_pixels(pixel_float):
  31. return round(pixel_float, 2)
  32. def calc_normalized(pixels_int, pixel_max):
  33. return pixels_int / pixel_max if pixel_max else 0.0
  34. def calc_pixels(normalized_float, pixel_max):
  35. return normalized_float * pixel_max
  36. def calc_width(res_x, min_x, max_x):
  37. return res_x * max_x - res_x * min_x
  38. def calc_height(res_y, min_y, max_y):
  39. return res_y * max_y - res_y * min_y
  40. def calc_centerX(res_x, min_x, width):
  41. return res_x * min_x + width / 2
  42. def calc_centerY(res_y, min_y, height):
  43. return res_y * min_y + height / 2
  44. # ------------------------------------------------------------------------
  45. # Properties
  46. # ------------------------------------------------------------------------
  47. class RenderBorder(bpy.types.PropertyGroup):
  48. # static member
  49. _rd = None
  50. _resX = _resY = _minX = _maxX = _minY = _maxY = 0
  51. _width = _height = _centerX = _centerY = 0
  52. def set_centerX(self, value):
  53. diffX = calc_normalized((value - self._centerX), self._resX)
  54. self._rd.border_min_x += diffX
  55. self._rd.border_max_x += diffX
  56. RenderBorder._minX = calc_pixels(self._rd.border_min_x, self._resX)
  57. RenderBorder._maxX = calc_pixels(self._rd.border_max_x, self._resX)
  58. RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
  59. RenderBorder._centerX = value
  60. def set_centerY(self, value):
  61. diffY = calc_normalized((value - self._centerY), self._resY)
  62. self._rd.border_min_y += diffY
  63. self._rd.border_max_y += diffY
  64. RenderBorder._minY = calc_pixels(self._rd.border_min_y, self._resY)
  65. RenderBorder._maxY = calc_pixels(self._rd.border_max_y, self._resY)
  66. RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
  67. RenderBorder._centerY = value
  68. def set_minX(self, value):
  69. self._rd.border_min_x = calc_normalized(value, self._resX)
  70. RenderBorder._minX = round_pixels(calc_pixels(self._rd.border_min_x, self._resX))
  71. RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
  72. RenderBorder._centerX = calc_centerX(self._resX, self._rd.border_min_x, self._width)
  73. def set_maxX(self, value):
  74. self._rd.border_max_x = calc_normalized(value, self._resX)
  75. RenderBorder._maxX = round_pixels(calc_pixels(self._rd.border_max_x, self._resX))
  76. RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
  77. RenderBorder._centerX = calc_centerX(self._resX, self._rd.border_min_x, self._width)
  78. def set_minY(self, value):
  79. self._rd.border_min_y = calc_normalized(value, self._resY)
  80. RenderBorder._minY = round_pixels(calc_pixels(self._rd.border_min_y, self._resY))
  81. RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
  82. RenderBorder._centerY = calc_centerY(self._resY, self._rd.border_min_y, self._height)
  83. def set_maxY(self, value):
  84. self._rd.border_max_y = calc_normalized(value, self._resY)
  85. RenderBorder._maxY = round_pixels(calc_pixels(self._rd.border_max_y, self._resY))
  86. RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
  87. RenderBorder._centerY = calc_centerY(self._resY, self._rd.border_min_y, self._height)
  88. def set_useBorder(self, value):
  89. self._rd.use_border = value
  90. def get_centerX(self):
  91. return RenderBorder._centerX
  92. def get_centerY(self):
  93. return RenderBorder._centerY
  94. def get_minX(self):
  95. return RenderBorder._minX
  96. def get_maxX(self):
  97. return RenderBorder._maxX
  98. def get_minY(self):
  99. return RenderBorder._minY
  100. def get_maxY(self):
  101. return RenderBorder._maxY
  102. def get_width(self):
  103. return abs(round_pixels(RenderBorder._width))
  104. def get_height(self):
  105. return abs(round_pixels(RenderBorder._height))
  106. def get_useBorder(self):
  107. bpy.ops.rborder.init_border()
  108. return self._rd.use_border
  109. center_x : bpy.props.IntProperty(
  110. name = "Center X",
  111. description = ("Horizontal center of the render border box"),
  112. min = 0, default = 0, get=get_centerX, set=set_centerX )
  113. center_y : bpy.props.IntProperty(
  114. name = "Center Y",
  115. description = ("Vertical center of the render border box"),
  116. min = 0, default = 0, get=get_centerY, set=set_centerY )
  117. width : bpy.props.IntProperty(
  118. name = "Width",
  119. description = ("Width of render border box"),
  120. min = 0, default = 0, get=get_width)
  121. height : bpy.props.IntProperty(
  122. name = "Height",
  123. description = ("Height of render border box"),
  124. min = 0, default = 0, get=get_height)
  125. min_x : bpy.props.IntProperty(
  126. description = ("Pixel distance between the left edge "
  127. "of the camera border and the left "
  128. "side of the render border box"),
  129. name = "Min X", min = 0, default = 0, get=get_minX, set=set_minX )
  130. max_x : bpy.props.IntProperty(
  131. description = ("Pixel distance between the right edge "
  132. "of the camera border and the right "
  133. "side of the render border box"),
  134. name = "Max X",min = 0, default = 0, get=get_maxX, set=set_maxX )
  135. min_y : bpy.props.IntProperty(
  136. description = ("Pixel distance between the bottom edge "
  137. "of the camera border and the bottom "
  138. "edge of the render border box"),
  139. name = "Min Y", min = 0, default = 0, get=get_minY, set=set_minY )
  140. max_y : bpy.props.IntProperty(
  141. description = ("Pixel distance between the top edge "
  142. "of the camera border and the top "
  143. "edge of the render border box"),
  144. name = "Max Y", min = 0, default = 0, get=get_maxY, set=set_maxY )
  145. use_rborder : bpy.props.BoolProperty(
  146. name = "Use render border", description = "Use render border",
  147. get=get_useBorder, set=set_useBorder)
  148. # ------------------------------------------------------------------------
  149. # Operators
  150. # ------------------------------------------------------------------------
  151. class RBORDER_OT_init_border(bpy.types.Operator):
  152. bl_idname = "rborder.init_border"
  153. bl_label = "Init Render Border"
  154. bl_options = {'INTERNAL'}
  155. def execute(self, context):
  156. scn = context.scene
  157. RenderBorder._rd = scn.render
  158. RenderBorder._resX = scn.render.resolution_x
  159. RenderBorder._resY = scn.render.resolution_y
  160. rbx = scn.renderborder
  161. rbx.min_x = round_pixels(calc_pixels(scn.render.border_min_x, scn.render.resolution_x))
  162. rbx.min_y = round_pixels(calc_pixels(scn.render.border_min_y, scn.render.resolution_y))
  163. rbx.max_x = round_pixels(calc_pixels(scn.render.border_max_x, scn.render.resolution_x))
  164. rbx.max_y = round_pixels(calc_pixels(scn.render.border_max_y, scn.render.resolution_y))
  165. return {'FINISHED'}
  166. class RBORDER_OT_reset_border(bpy.types.Operator):
  167. bl_idname = "rborder.reset_border"
  168. bl_label = "Reset Render Border"
  169. bl_description = "Fit render border to the current camera resolution"
  170. bl_options = {'REGISTER', 'UNDO'}
  171. def execute(self, context):
  172. scn = context.scene
  173. rbx = scn.renderborder
  174. rbx.min_x = 0
  175. rbx.min_y = 0
  176. rbx.max_x = scn.render.resolution_x
  177. rbx.max_y = scn.render.resolution_y
  178. self.report({'INFO'}, "Render Border adapted")
  179. return {'FINISHED'}
  180. # ------------------------------------------------------------------------
  181. # Panel
  182. # ------------------------------------------------------------------------
  183. class RBORDER_PT_camera(bpy.types.Panel):
  184. bl_label = "Render Border"
  185. bl_space_type = 'PROPERTIES'
  186. bl_region_type = 'WINDOW'
  187. bl_context = "data"
  188. @classmethod
  189. def poll(cls, context):
  190. return context.active_object.type == "CAMERA"
  191. def draw_header(self, context):
  192. scn = context.scene
  193. rbx = scn.renderborder
  194. self.layout.prop(rbx, "use_rborder", text="")
  195. def draw(self, context):
  196. scn = context.scene
  197. rbx = scn.renderborder
  198. layout = self.layout
  199. row = layout.row()
  200. col = row.column(align=True)
  201. rowsub = col.row(align=True)
  202. rowsub.prop(rbx, "min_x", text="X")
  203. rowsub.prop(rbx, "max_x", text="R")
  204. rowsub = col.row(align=True)
  205. rowsub.prop(rbx, "min_y", text="Y")
  206. rowsub.prop(rbx, "max_y", text="T")
  207. col.prop(rbx, "center_x")
  208. col.prop(rbx, "center_y")
  209. col.operator("rborder.reset_border", text="Reset Render Border", icon='FILE_REFRESH')
  210. row = layout.row()
  211. col = layout.column(align=True)
  212. rowsub = col.row(align=True)
  213. rowsub = row.split(factor=0.3, align=True)
  214. rowsub.prop(scn.render, "use_crop_to_border", text="Crop Image")
  215. rowsub.alignment = 'RIGHT'
  216. rowsub.label(text="Width: {}px Height: {}px".format(rbx.width, rbx.height))
  217. # ------------------------------------------------------------------------
  218. # Registration
  219. # ------------------------------------------------------------------------
  220. @persistent
  221. def init_renderborder_member(dummy):
  222. bpy.ops.rborder.init_border()
  223. classes = (
  224. RenderBorder,
  225. RBORDER_OT_init_border,
  226. RBORDER_OT_reset_border,
  227. RBORDER_PT_camera
  228. )
  229. def register():
  230. from bpy.utils import register_class
  231. for cls in classes:
  232. register_class(cls)
  233. bpy.types.Scene.renderborder = bpy.props.PointerProperty(type=RenderBorder)
  234. bpy.app.handlers.load_post.append(init_renderborder_member)
  235. def unregister():
  236. from bpy.utils import unregister_class
  237. for cls in reversed(classes):
  238. unregister_class(cls)
  239. bpy.app.handlers.load_post.remove(init_renderborder_member)
  240. del bpy.types.Scene.renderborder
  241. if __name__ == "__main__":
  242. register()