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 #####
20 "name": "Auto Save Render",
21 "author": "tstscr(florianfelix)",
23 "blender": (2, 80, 0),
24 "location": "Rendertab -> Output Panel -> Subpanel",
25 "description": "Automatically save the image after rendering",
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/",
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
51 'OPEN_EXR_MULTILAYER',
71 def auto_save_render(scene
):
72 if not scene
.auto_save_after_render
or not bpy
.data
.filepath
:
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(
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
):
97 if scene
.auto_save_subfolders
:
98 filepath
= join(filepath
, blendname
)
99 if not exists(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
115 # find last numbers in the filename
116 suffix
= findall(r
'\d+', f
.split(blendname
)[-1])
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
127 frame_pattern
= r
'_f[0-9]{4}_'
129 res
= search(frame_pattern
, file)
131 if int(res
[0][2:-1]) == frame_current
:
132 match_files
.append(file)
135 if scene
.auto_save_use_framenumber
:
136 if scene
.auto_save_use_continuous
:
137 save_number
= save_number_from_files(files
)
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
])
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']
151 print('Auto Save: Render Result not found. Image not saved')
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'}
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
):
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)
200 RENDER_PT_render_auto_save
,
205 from bpy
.utils
import register_class
209 bpy
.types
.Scene
.auto_save_after_render
= BoolProperty(
210 name
='Save after render',
212 description
='Automatically save rendered images into: //auto_save/')
213 bpy
.types
.Scene
.auto_save_blend
= BoolProperty(
214 name
='Save .blend (copy) alongside image',
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',
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'),
227 bpy
.types
.Scene
.auto_save_subfolders
= BoolProperty(
228 name
='Save into subfolder',
230 description
='Save into individual subfolders per blend name')
231 bpy
.types
.Scene
.auto_save_use_framenumber
= BoolProperty(
232 name
='Insert frame number',
234 description
='Insert frame number into file name'
236 bpy
.types
.Scene
.auto_save_use_continuous
= BoolProperty(
237 name
='Continuous numbering',
239 description
='Use continuous numbering when inserting frame numbers'
241 bpy
.app
.handlers
.render_post
.append(auto_save_render
)
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__":