addons-contrib: more view_layer syntax updates
[blender-addons-contrib.git] / render_auto_save.py
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
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     "wiki_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"}
30
31
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
40
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'
67 )
68
69
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
76
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'
88
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'
93
94     if not exists(filepath):
95         mkdir(filepath)
96
97     if scene.auto_save_subfolders:
98         filepath = join(filepath, blendname)
99         if not exists(filepath):
100             mkdir(filepath)
101
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)]
106
107     def save_number_from_files(files):
108         '''
109         Returns the new highest count number from file names
110         as 3 digit string.
111         '''
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)
121
122     def this_frame_files(files):
123         '''
124         Filters out files which have the current frame number in the file name
125         '''
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
134
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)
148
149     image = bpy.data.images['Render Result']
150     if not image:
151         print('Auto Save: Render Result not found. Image not saved')
152         return
153
154     print('Auto_Save:', save_name)
155     image.save_render(save_name, scene=None)
156
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)
161
162     rndr.image_settings.file_format = original_format
163
164 ###########################################################################
165
166
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'}
176
177     @classmethod
178     def poll(cls, context):
179         return (context.engine in cls.COMPAT_ENGINES)
180
181     def draw_header(self, context):
182         self.layout.prop(context.scene, 'auto_save_after_render', text="")
183
184     def draw(self, context):
185         layout = self.layout
186         layout.use_property_split = True
187         layout.use_property_decorate = False  # No animation.
188
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)
197
198
199 classes = [
200     RENDER_PT_render_auto_save,
201 ]
202
203
204 def register():
205     from bpy.utils import register_class
206     for cls in classes:
207         register_class(cls)
208
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'),
225             ],
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'
235     )
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'
240     )
241     bpy.app.handlers.render_post.append(auto_save_render)
242
243
244 def unregister():
245     from bpy.utils import unregister_class
246     for cls in reversed(classes):
247         unregister_class(cls)
248
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)
255
256
257 if __name__ == "__main__":
258     register()