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