re-commit temp workaround [#35920], this still fails for OSX retina display,
[blender-staging.git] / release / scripts / startup / bl_operators / image.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 # <pep8-80 compliant>
20
21 import bpy
22 from bpy.types import Operator
23 from bpy.props import StringProperty
24
25
26 class EditExternally(Operator):
27     """Edit image in an external application"""
28     bl_idname = "image.external_edit"
29     bl_label = "Image Edit Externally"
30     bl_options = {'REGISTER'}
31
32     filepath = StringProperty(
33             subtype='FILE_PATH',
34             )
35
36     def _editor_guess(self, context):
37         import sys
38
39         image_editor = context.user_preferences.filepaths.image_editor
40
41         # use image editor in the preferences when available.
42         if not image_editor:
43             if sys.platform[:3] == "win":
44                 image_editor = ["start"]  # not tested!
45             elif sys.platform == "darwin":
46                 image_editor = ["open"]
47             else:
48                 image_editor = ["gimp"]
49         else:
50             if sys.platform == "darwin":
51                 # blender file selector treats .app as a folder
52                 # and will include a trailing backslash, so we strip it.
53                 image_editor.rstrip('\\')
54                 image_editor = ["open", "-a", image_editor]
55             else:
56                 image_editor = [image_editor]
57
58         return image_editor
59
60     def execute(self, context):
61         import os
62         import subprocess
63
64         filepath = self.filepath
65
66         if not filepath:
67             self.report({'ERROR'}, "Image path not set")
68             return {'CANCELLED'}
69
70         if not os.path.exists(filepath) or not os.path.isfile(filepath):
71             self.report({'ERROR'},
72                         "Image path %r not found, image may be packed or "
73                         "unsaved" % filepath)
74             return {'CANCELLED'}
75
76         cmd = self._editor_guess(context) + [filepath]
77
78         try:
79             subprocess.Popen(cmd)
80         except:
81             import traceback
82             traceback.print_exc()
83             self.report({'ERROR'},
84                         "Image editor not found, "
85                         "please specify in User Preferences > File")
86
87             return {'CANCELLED'}
88
89         return {'FINISHED'}
90
91     def invoke(self, context, event):
92         import os
93         try:
94             image = context.space_data.image
95         except AttributeError:
96             self.report({'ERROR'}, "Context incorrect, image not found")
97             return {'CANCELLED'}
98
99         if image.packed_file:
100             self.report({'ERROR'}, "Image is packed, unpack before editing")
101             return {'CANCELLED'}
102
103         filepath = bpy.path.abspath(image.filepath, library=image.library)
104
105         self.filepath = os.path.normpath(filepath)
106         self.execute(context)
107
108         return {'FINISHED'}
109
110
111 class SaveDirty(Operator):
112     """Save all modified textures"""
113     bl_idname = "image.save_dirty"
114     bl_label = "Save Dirty"
115     bl_options = {'REGISTER', 'UNDO'}
116
117     def execute(self, context):
118         unique_paths = set()
119         for image in bpy.data.images:
120             if image.is_dirty:
121                 if image.packed_file:
122                     if image.library:
123                         self.report({'WARNING'},
124                                     "Packed library image: %r from library %r"
125                                     " can't be re-packed" %
126                                     (image.name, image.library.filepath))
127                     else:
128                         image.pack(as_png=True)
129                 else:
130                     filepath = bpy.path.abspath(image.filepath,
131                                                 library=image.library)
132                     if "\\" not in filepath and "/" not in filepath:
133                         self.report({'WARNING'}, "Invalid path: " + filepath)
134                     elif filepath in unique_paths:
135                         self.report({'WARNING'},
136                                     "Path used by more than one image: %r" %
137                                     filepath)
138                     else:
139                         unique_paths.add(filepath)
140                         image.save()
141         return {'FINISHED'}
142
143
144 class ProjectEdit(Operator):
145     """Edit a snapshot of the view-port in an external image editor"""
146     bl_idname = "image.project_edit"
147     bl_label = "Project Edit"
148     bl_options = {'REGISTER'}
149
150     _proj_hack = [""]
151
152     def execute(self, context):
153         import os
154
155         EXT = "png"  # could be made an option but for now ok
156
157         for image in bpy.data.images:
158             image.tag = True
159
160         # opengl buffer may fail, we can't help this, but best report it.
161         try:
162             bpy.ops.paint.image_from_view()
163         except RuntimeError as err:
164             self.report({'ERROR'}, str(err))
165             return {'CANCELLED'}
166
167         image_new = None
168         for image in bpy.data.images:
169             if not image.tag:
170                 image_new = image
171                 break
172
173         if not image_new:
174             self.report({'ERROR'}, "Could not make new image")
175             return {'CANCELLED'}
176
177         filepath = os.path.basename(bpy.data.filepath)
178         filepath = os.path.splitext(filepath)[0]
179         # fixes <memory> rubbish, needs checking
180         # filepath = bpy.path.clean_name(filepath)
181
182         if bpy.data.is_saved:
183             filepath = "//" + filepath
184         else:
185             tmpdir = context.user_preferences.filepaths.temporary_directory
186             filepath = os.path.join(tmpdir, "project_edit")
187
188         obj = context.object
189
190         if obj:
191             filepath += "_" + bpy.path.clean_name(obj.name)
192
193         filepath_final = filepath + "." + EXT
194         i = 0
195
196         while os.path.exists(bpy.path.abspath(filepath_final)):
197             filepath_final = filepath + ("%.3d.%s" % (i, EXT))
198             i += 1
199
200         image_new.name = bpy.path.basename(filepath_final)
201         ProjectEdit._proj_hack[0] = image_new.name
202
203         image_new.filepath_raw = filepath_final  # TODO, filepath raw is crummy
204         image_new.file_format = 'PNG'
205         image_new.save()
206
207         filepath_final = bpy.path.abspath(filepath_final)
208
209         try:
210             bpy.ops.image.external_edit(filepath=filepath_final)
211         except RuntimeError as re:
212             self.report({'ERROR'}, str(re))
213
214         return {'FINISHED'}
215
216
217 class ProjectApply(Operator):
218     """Project edited image back onto the object"""
219     bl_idname = "image.project_apply"
220     bl_label = "Project Apply"
221     bl_options = {'REGISTER'}
222
223     def execute(self, context):
224         image_name = ProjectEdit._proj_hack[0]  # TODO, deal with this nicer
225
226         try:
227             image = bpy.data.images[image_name, None]
228         except KeyError:
229             import traceback
230             traceback.print_exc()
231             self.report({'ERROR'}, "Could not find image '%s'" % image_name)
232             return {'CANCELLED'}
233
234         image.reload()
235         bpy.ops.paint.project_image(image=image_name)
236
237         return {'FINISHED'}