UI: Move Extensions repositories popover to header
[blender-addons-contrib.git] / render_auto_save.py
blob8deb8e7f492db5d2baace6995f90c2632c331385
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_info = {
20 "name": "Auto Save Render",
21 "author": "tstscr(florianfelix)",
22 "version": (2, 1),
23 "blender": (2, 80, 0),
24 "location": "Rendertab -> Output Panel -> Subpanel",
25 "description": "Automatically save the image after rendering",
26 "warning": "",
27 "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Render/Auto_Save",
28 "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
29 "category": "Render"}
32 import bpy
33 from bpy.types import Panel
34 from bpy.props import BoolProperty, EnumProperty
35 from bpy.app.handlers import persistent
36 from os.path import dirname, exists, join
37 from bpy.path import basename
38 from os import mkdir, listdir
39 from re import findall, search
41 IMAGE_FORMATS = (
42 'BMP',
43 'IRIS',
44 'PNG',
45 'JPEG',
46 'JPEG2000',
47 'TARGA',
48 'TARGA_RAW',
49 'CINEON',
50 'DPX',
51 'OPEN_EXR_MULTILAYER',
52 'OPEN_EXR',
53 'HDR',
54 'TIFF')
55 IMAGE_EXTENSIONS = (
56 'bmp',
57 'rgb',
58 'png',
59 'jpg',
60 'jp2',
61 'tga',
62 'cin',
63 'dpx',
64 'exr',
65 'hdr',
66 'tif'
70 @persistent
71 def auto_save_render(scene):
72 if not scene.auto_save_after_render or not bpy.data.filepath:
73 return
74 rndr = scene.render
75 original_format = rndr.image_settings.file_format
77 if scene.auto_save_format == 'SCENE':
78 if original_format not in IMAGE_FORMATS:
79 print('{} Format is not an image format. Not Saving'.format(
80 original_format))
81 return
82 elif scene.auto_save_format == 'PNG':
83 rndr.image_settings.file_format = 'PNG'
84 elif scene.auto_save_format == 'OPEN_EXR_MULTILAYER':
85 rndr.image_settings.file_format = 'OPEN_EXR_MULTILAYER'
86 elif scene.auto_save_format == 'JPEG':
87 rndr.image_settings.file_format = 'JPEG'
89 frame_current = bpy.context.scene.frame_current
90 extension = rndr.file_extension
91 blendname = basename(bpy.data.filepath).rpartition('.')[0]
92 filepath = dirname(bpy.data.filepath) + '/auto_saves'
94 if not exists(filepath):
95 mkdir(filepath)
97 if scene.auto_save_subfolders:
98 filepath = join(filepath, blendname)
99 if not exists(filepath):
100 mkdir(filepath)
102 # imagefiles starting with the blendname
103 files = [f for f in listdir(filepath)
104 if f.startswith(blendname)
105 and f.lower().endswith(IMAGE_EXTENSIONS)]
107 def save_number_from_files(files):
109 Returns the new highest count number from file names
110 as 3 digit string.
112 highest = 0
113 if files:
114 for f in files:
115 # find last numbers in the filename
116 suffix = findall(r'\d+', f.split(blendname)[-1])
117 if suffix:
118 if int(suffix[-1]) > highest:
119 highest = int(suffix[-1])
120 return str(highest+1).zfill(3)
122 def this_frame_files(files):
124 Filters out files which have the current frame number in the file name
126 match_files = []
127 frame_pattern = r'_f[0-9]{4}_'
128 for file in files:
129 res = search(frame_pattern, file)
130 if res:
131 if int(res[0][2:-1]) == frame_current:
132 match_files.append(file)
133 return match_files
135 if scene.auto_save_use_framenumber:
136 if scene.auto_save_use_continuous:
137 save_number = save_number_from_files(files)
138 else:
139 frame_files = this_frame_files(files)
140 save_number = save_number_from_files(frame_files)
141 frame_number = 'f' + str(frame_current).zfill(4)
142 save_name = '_'.join([blendname, frame_number, save_number])
143 else:
144 save_number = save_number_from_files(files)
145 save_name = '_'.join([blendname, save_number])
146 save_name += extension
147 save_name = join(filepath, save_name)
149 image = bpy.data.images['Render Result']
150 if not image:
151 print('Auto Save: Render Result not found. Image not saved')
152 return
154 print('Auto_Save:', save_name)
155 image.save_render(save_name, scene=None)
157 if scene.auto_save_blend:
158 save_name_blend = join(filepath, save_name) + '.blend'
159 print('Blend_Save:', save_name_blend)
160 bpy.ops.wm.save_as_mainfile(filepath=save_name_blend, copy=True)
162 rndr.image_settings.file_format = original_format
164 ###########################################################################
167 class RENDER_PT_render_auto_save(Panel):
168 bl_space_type = 'PROPERTIES'
169 bl_region_type = 'WINDOW'
170 bl_context = "render"
171 bl_label = "Auto Save Render"
172 bl_parent_id = "RENDER_PT_output"
173 bl_options = {'DEFAULT_CLOSED'}
174 COMPAT_ENGINES = {'CYCLES', 'BLENDER_RENDER',
175 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
177 @classmethod
178 def poll(cls, context):
179 return (context.engine in cls.COMPAT_ENGINES)
181 def draw_header(self, context):
182 self.layout.prop(context.scene, 'auto_save_after_render', text="")
184 def draw(self, context):
185 layout = self.layout
186 layout.use_property_split = True
187 layout.use_property_decorate = False # No animation.
189 col = layout.column(align=True)
190 col.prop(context.scene, 'auto_save_format', text='as', expand=False)
191 col.prop(context.scene, 'auto_save_blend', toggle=False)
192 col.prop(context.scene, 'auto_save_subfolders', toggle=False)
193 col.prop(context.scene, 'auto_save_use_framenumber', toggle=False)
194 # subcol = col.column()
195 # subcol.active = context.scene.auto_save_use_framenumber
196 # subcol.prop(context.scene, 'auto_save_use_continuous', toggle=False)
199 classes = [
200 RENDER_PT_render_auto_save,
204 def register():
205 from bpy.utils import register_class
206 for cls in classes:
207 register_class(cls)
209 bpy.types.Scene.auto_save_after_render = BoolProperty(
210 name='Save after render',
211 default=False,
212 description='Automatically save rendered images into: //auto_save/')
213 bpy.types.Scene.auto_save_blend = BoolProperty(
214 name='Save .blend (copy) alongside image',
215 default=False,
216 description='Also save .blend (copy) file into: //auto_save/')
217 bpy.types.Scene.auto_save_format = EnumProperty(
218 name='Auto Save File Format',
219 description='File Format for the auto saves',
220 items=[
221 ('SCENE', 'scene format', 'Format set in output panel'),
222 ('PNG', 'png', 'Save as png'),
223 ('JPEG', 'jpg', 'Save as jpg'),
224 ('OPEN_EXR_MULTILAYER', 'exr', 'Save as multilayer exr'),
226 default='SCENE')
227 bpy.types.Scene.auto_save_subfolders = BoolProperty(
228 name='Save into subfolder',
229 default=False,
230 description='Save into individual subfolders per blend name')
231 bpy.types.Scene.auto_save_use_framenumber = BoolProperty(
232 name='Insert frame number',
233 default=False,
234 description='Insert frame number into file name'
236 bpy.types.Scene.auto_save_use_continuous = BoolProperty(
237 name='Continuous numbering',
238 default=False,
239 description='Use continuous numbering when inserting frame numbers'
241 bpy.app.handlers.render_post.append(auto_save_render)
244 def unregister():
245 from bpy.utils import unregister_class
246 for cls in reversed(classes):
247 unregister_class(cls)
249 del(bpy.types.Scene.auto_save_after_render)
250 del(bpy.types.Scene.auto_save_format)
251 del(bpy.types.Scene.auto_save_subfolders)
252 del(bpy.types.Scene.auto_save_use_framenumber)
253 del(bpy.types.Scene.auto_save_use_continuous)
254 bpy.app.handlers.render_post.remove(auto_save_render)
257 if __name__ == "__main__":
258 register()