1 # ***** BEGIN GPL LICENSE BLOCK *****
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ***** END GPL LICENCE BLOCK *****
22 "name": "Render Time Estimation",
23 "author": "Jason van Gumster (Fweeb)",
25 "blender": (2, 80, 4),
26 "location": "UV/Image Editor > Properties > Image",
27 "description": "Estimates the time to complete rendering on animations",
28 "warning": "Does not work on OpenGL renders.",
29 "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Render/Render_Time_Estimation",
30 "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
35 from bpy
.app
.handlers
import persistent
36 from datetime
import timedelta
40 timer
= {"average": 0.0, "total": 0.0, "time_start": 0.0, "is_rendering": False, "hud": False}
42 def set_rendering(scene
):
43 timer
["is_rendering"] = True
46 def unset_rendering(scene
):
47 timer
["is_rendering"] = False
50 def start_timer(scene
):
53 if scene
.frame_current
== scene
.frame_start
:
54 timer
["average"] = 0.0
57 timer
["time_start"] = time
.time()
61 render_time
= time
.time() - timer
["time_start"]
62 timer
["total"] += render_time
63 if scene
.frame_current
== scene
.frame_start
:
64 timer
["average"] = render_time
66 timer
["average"] = (timer
["average"] + render_time
) / 2
68 print("Total render time: " + str(timedelta(seconds
= timer
["total"])))
69 print("Estimated completion: " + str(timedelta(seconds
= (timer
["average"] * (scene
.frame_end
- scene
.frame_current
)))))
74 def image_panel_rendertime(self
, context
):
78 if context
.space_data
.image
is not None and context
.space_data
.image
.type == 'RENDER_RESULT':
79 layout
.label(text
= "Total render time: " + str(timedelta(seconds
= timer
["total"])))
81 if timer
["is_rendering"] and scene
.frame_current
!= scene
.frame_start
:
82 layout
.label(text
= "Estimated completion: " + str(timedelta(seconds
= (timer
["average"] * (scene
.frame_end
- scene
.frame_current
)))))
84 def draw_callback_px(self
, context
):
87 font_id
= 0 # XXX, need to find out how best to get this.
90 blf
.position(font_id
, 15, 30, 0)
91 blf
.size(font_id
, 18, 72)
92 blf
.enable(font_id
, blf
.SHADOW
)
93 blf
.shadow(font_id
, 5, 0.0, 0.0, 0.0, 1.0)
95 # Shorten / cut off milliseconds
96 time_total
= str(timedelta(seconds
= timer
["total"]))
97 pos
= time_total
.rfind(".")
99 time_total
= time_total
[0:pos
+3]
101 time_estimated
= str(timedelta(seconds
= (timer
["average"] * (scene
.frame_end
- scene
.frame_current
))))
102 pos
= time_estimated
.rfind(".")
104 time_estimated
= time_estimated
[0:pos
]
107 blf
.draw(font_id
, "Total render time " + time_total
)
108 if timer
["is_rendering"] and scene
.frame_current
!= scene
.frame_start
:
109 blf
.position(font_id
, 15, 12, 0)
110 blf
.draw(font_id
, "Estimated completion: " + time_estimated
)
113 blf
.disable(font_id
, blf
.SHADOW
)
115 class RenderTimeHUD(bpy
.types
.Operator
):
116 bl_idname
= "view2d.rendertime_hud"
117 bl_label
= "Display Render Times"
118 last_activity
= 'NONE'
123 def handle_add(self
, context
):
124 RenderTimeHUD
._handle
= bpy
.types
.SpaceImageEditor
.draw_handler_add(
125 draw_callback_px
, (self
, context
), 'WINDOW', 'POST_PIXEL')
129 if RenderTimeHUD
._handle
is not None:
130 bpy
.types
.SpaceImageEditor
.draw_handler_remove(RenderTimeHUD
._handle
, 'WINDOW')
131 RenderTimeHUD
._handle
= None
133 def modal(self
, context
, event
):
135 context
.area
.tag_redraw()
137 #if event.type in {'ESC'}:
138 if timer
["hud"] == False:
139 RenderTimeHUD
.handle_remove()
142 return {'PASS_THROUGH'}
144 def invoke(self
, context
, event
):
145 if context
.area
.type == 'IMAGE_EDITOR':
146 if timer
["hud"] == False:
147 # Add the region OpenGL drawing callback
148 RenderTimeHUD
.handle_add(self
, context
)
151 context
.window_manager
.modal_handler_add(self
)
152 return {'RUNNING_MODAL'}
157 self
.report({'WARNING'}, "UV/Image Editor not found, cannot run operator")
160 def display_hud(self
, context
):
162 text
= RenderTimeHUD
.bl_label
if RenderTimeHUD
._handle
is None else "Hide Render Times"
163 layout
.operator("view2d.rendertime_hud", text
=text
)
168 bpy
.app
.handlers
.render_complete
.append(unset_rendering
)
169 bpy
.app
.handlers
.render_cancel
.append(unset_rendering
)
170 bpy
.app
.handlers
.render_pre
.append(start_timer
)
171 bpy
.app
.handlers
.render_post
.append(end_timer
)
172 bpy
.types
.IMAGE_PT_image_properties
.append(image_panel_rendertime
)
173 bpy
.utils
.register_class(RenderTimeHUD
)
174 bpy
.types
.IMAGE_HT_header
.append(display_hud
)
176 # Keymapping XXX TODO - This doesn't work for some reason
177 #kc = bpy.context.window_manager.keyconfigs.addon
178 #km = kc.keymaps.new(name = "View 2D", space_type = 'IMAGE_EDITOR')
179 #kmi = km.keymap_items.new("view2d.rendertime_hud", 'E', 'PRESS')
183 RenderTimeHUD
.handle_remove()
185 #kc = bpy.context.window_manager.keyconfigs.addon
186 #km = kc.keymaps["View 2D"]
187 #km.keymap_items.remove(km.keymap_items["view2d.rendertime_hud"])
189 bpy
.types
.IMAGE_HT_header
.remove(display_hud
)
190 bpy
.utils
.unregister_class(RenderTimeHUD
)
191 bpy
.app
.handlers
.render_pre
.remove(start_timer
)
192 bpy
.app
.handlers
.render_post
.remove(end_timer
)
193 bpy
.app
.handlers
.render_cancel
.remove(unset_rendering
)
194 bpy
.app
.handlers
.render_complete
.remove(unset_rendering
)
195 bpy
.types
.IMAGE_PT_image_properties
.remove(image_panel_rendertime
)
197 if __name__
== '__main__':