poll() as a python '@staticmethod' was too limiting and didnt allow useful base class...
[blender.git] / release / scripts / op / uv.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 *
23
24
25 class ExportUVLayout(bpy.types.Operator):
26     '''Export the Mesh as SVG'''
27
28     bl_idname = "uv.export_layout"
29     bl_label = "Export UV Layout"
30     bl_options = {'REGISTER', 'UNDO'}
31
32     filepath = StringProperty(name="File Path", description="File path used for exporting the SVG file", maxlen=1024, default="")
33     check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'})
34     export_all = BoolProperty(name="All UV's", description="Export all UVs in this mesh (not just the visible ones)", default=False)
35     mode = EnumProperty(items=(
36                         ('SVG', "Scalable Vector Graphic (.svg)", "Export the UV layout to a vector SVG file"),
37                         ('EPS', "Encapsulate PostScript (.eps)", "Export the UV layout to a vector EPS file")),
38                 name="Format",
39                 description="File format to export the UV layout to",
40                 default='SVG')
41
42     @classmethod
43     def poll(cls, context):
44         obj = context.active_object
45         return (obj and obj.type == 'MESH')
46
47     def _space_image(self, context):
48         space_data = context.space_data
49         if type(space_data) == bpy.types.SpaceImageEditor:
50             return space_data
51         else:
52             return None
53
54     def _image_size(self, context, default_width=1024, default_height=1024):
55         # fallback if not in image context.
56         image_width, image_height = default_width, default_height
57
58         space_data = self._space_image(context)
59         if space_data:
60             image = space_data.image
61             if image:
62                 width, height = tuple(context.space_data.image.size)
63                 # incase no data is found.
64                 if width and height:
65                     image_width, image_height = width, height
66
67         return image_width, image_height
68
69     def _face_uv_iter(self, context):
70         obj = context.active_object
71         mesh = obj.data
72         uv_layer = mesh.active_uv_texture.data
73         uv_layer_len = len(uv_layer)
74
75         if not self.properties.export_all:
76
77             local_image = Ellipsis
78
79             if context.tool_settings.uv_local_view:
80                 space_data = self._space_image(context)
81                 if space_data:
82                     local_image = space_data.image
83
84             faces = mesh.faces
85
86             for i in range(uv_layer_len):
87                 uv_elem = uv_layer[i]
88                 # context checks
89                 if faces[i].select and (local_image is Ellipsis or local_image == uv_elem.image):
90                     #~ uv = uv_elem.uv
91                     #~ if False not in uv_elem.select_uv[:len(uv)]:
92                     #~     yield (i, uv)
93
94                     # just write what we see.
95                     yield (i, uv_layer[i].uv)
96         else:
97             # all, simple
98             for i in range(uv_layer_len):
99                 yield (i, uv_layer[i].uv)
100
101     def execute(self, context):
102         # for making an XML compatible string
103         from xml.sax.saxutils import escape
104         from os.path import basename
105
106         obj = context.active_object
107         is_editmode = (obj.mode == 'EDIT')
108         if is_editmode:
109             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
110
111         image_width, image_height = self._image_size(context)
112         mesh = obj.data
113         faces = mesh.faces
114
115         mode = self.properties.mode
116
117         filepath = self.properties.filepath
118         filepath = bpy.path.ensure_ext(filepath, "." + mode.lower())
119         file = open(filepath, "w")
120         fw = file.write
121
122         if mode == 'SVG':
123
124             fw('<?xml version="1.0" standalone="no"?>\n')
125             fw('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n')
126             fw('  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
127             fw('<svg width="%dpx" height="%dpx" viewBox="0px 0px %dpx %dpx"\n' % (image_width, image_height, image_width, image_height))
128             fw('     xmlns="http://www.w3.org/2000/svg" version="1.1">\n')
129             desc = "%s, %s, %s (Blender %s)" % (basename(bpy.data.filepath), obj.name, mesh.name, bpy.app.version_string)
130             fw('<desc>%s</desc>\n' % escape(desc))
131
132             # svg colors
133             fill_settings = []
134             fill_default = 'fill="grey"'
135             for mat in mesh.materials if mesh.materials else [None]:
136                 if mat:
137                     fill_settings.append('fill="rgb(%d, %d, %d)"' % tuple(int(c * 255) for c in mat.diffuse_color))
138                 else:
139                     fill_settings.append(fill_default)
140
141             for i, uvs in self._face_uv_iter(context):
142                 try: # rare cases material index is invalid.
143                     fill = fill_settings[faces[i].material_index]
144                 except IndexError:
145                     fill = fill_default
146
147                 fw('<polygon %s fill-opacity="0.5" stroke="black" stroke-width="1px" \n' % fill)
148                 fw('  points="')
149
150                 for j, uv in enumerate(uvs):
151                     x, y = uv[0], 1.0 - uv[1]
152                     fw('%.3f,%.3f ' % (x * image_width, y * image_height))
153                 fw('" />\n')
154             fw('\n')
155             fw('</svg>\n')
156
157         elif mode == 'EPS':
158             fw('%!PS-Adobe-3.0 EPSF-3.0\n')
159             fw("%%%%Creator: Blender %s\n" % bpy.app.version_string)
160             fw('%%Pages: 1\n')
161             fw('%%Orientation: Portrait\n')
162             fw("%%%%BoundingBox: 0 0 %d %d\n" % (image_width, image_height))
163             fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" % (image_width, image_height))
164             fw('%%EndComments\n')
165             fw('%%Page: 1 1\n')
166             fw('0 0 translate\n')
167             fw('1.0 1.0 scale\n')
168             fw('0 0 0 setrgbcolor\n')
169             fw('[] 0 setdash\n')
170             fw('1 setlinewidth\n')
171             fw('1 setlinejoin\n')
172             fw('1 setlinecap\n')
173             fw('/DRAW {')
174             # can remove from here to next comment to disable filling, aparently alpha is not supported
175             fw('gsave\n')
176             fw('0.7 setgray\n')
177             fw('fill\n')
178             fw('grestore\n')
179             fw('0 setgray\n')
180             # remove to here
181             fw('stroke\n')
182             fw('} def\n')
183             fw('newpath\n')
184
185             firstline = True
186             for i, uvs in self._face_uv_iter(context):
187                 for j, uv in enumerate(uvs):
188                     x, y = uv[0], uv[1]
189                     if j == 0:
190                         if not firstline:
191                             fw('closepath\n')
192                             fw('DRAW\n')
193                             fw('newpath\n')
194                         firstline = False
195                         fw('%.5f %.5f moveto\n' % (x * image_width, y * image_height))
196                     else:
197                         fw('%.5f %.5f lineto\n' % (x * image_width, y * image_height))
198
199             fw('closepath\n')
200             fw('DRAW\n')
201             fw('showpage\n')
202             fw('%%EOF\n')
203
204         if is_editmode:
205             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
206
207         return {'FINISHED'}
208
209     def invoke(self, context, event):
210         wm = context.manager
211         wm.add_fileselect(self)
212         return {'RUNNING_MODAL'}
213
214
215 def menu_func(self, context):
216     import os
217     default_path = os.path.splitext(bpy.data.filepath)[0] + ".svg"
218     self.layout.operator(ExportUVLayout.bl_idname).filepath = default_path
219
220
221 def register():
222     bpy.types.IMAGE_MT_uvs.append(menu_func)
223
224
225 def unregister():
226     bpy.types.IMAGE_MT_uvs.remove(menu_func)
227
228 if __name__ == "__main__":
229     register()