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