Cleanup: io_import_BrushSet, autopep8, formatting
[blender-addons.git] / io_mesh_uv_layout / __init__.py
blob8c16a0d7bf53acc7a91e09feb4526ad4afeaedc5
1 # SPDX-FileCopyrightText: 2011-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "UV Layout",
7 "author": "Campbell Barton, Matt Ebb",
8 "version": (1, 1, 6),
9 "blender": (3, 0, 0),
10 "location": "UV Editor > UV > Export UV Layout",
11 "description": "Export the UV layout as a 2D graphic",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_uv_layout.html",
14 "support": 'OFFICIAL',
15 "category": "Import-Export",
19 # @todo write the wiki page
21 if "bpy" in locals():
22 import importlib
23 if "export_uv_eps" in locals():
24 importlib.reload(export_uv_eps)
25 if "export_uv_png" in locals():
26 importlib.reload(export_uv_png)
27 if "export_uv_svg" in locals():
28 importlib.reload(export_uv_svg)
30 import os
31 import bpy
33 from bpy.props import (
34 StringProperty,
35 BoolProperty,
36 EnumProperty,
37 IntVectorProperty,
38 FloatProperty,
42 class ExportUVLayout(bpy.types.Operator):
43 """Export UV layout to file"""
45 bl_idname = "uv.export_layout"
46 bl_label = "Export UV Layout"
47 bl_options = {'REGISTER', 'UNDO'}
49 filepath: StringProperty(
50 subtype='FILE_PATH',
52 export_all: BoolProperty(
53 name="All UVs",
54 description="Export all UVs in this mesh (not just visible ones)",
55 default=False,
57 modified: BoolProperty(
58 name="Modified",
59 description="Exports UVs from the modified mesh",
60 default=False,
62 mode: EnumProperty(
63 items=(
64 ('SVG', "Scalable Vector Graphic (.svg)",
65 "Export the UV layout to a vector SVG file"),
66 ('EPS', "Encapsulate PostScript (.eps)",
67 "Export the UV layout to a vector EPS file"),
68 ('PNG', "PNG Image (.png)",
69 "Export the UV layout to a bitmap image"),
71 name="Format",
72 description="File format to export the UV layout to",
73 default='PNG',
75 size: IntVectorProperty(
76 size=2,
77 default=(1024, 1024),
78 min=8, max=32768,
79 description="Dimensions of the exported file",
81 opacity: FloatProperty(
82 name="Fill Opacity",
83 min=0.0, max=1.0,
84 default=0.25,
85 description="Set amount of opacity for exported UV layout",
87 # For the file-selector.
88 check_existing: BoolProperty(
89 default=True,
90 options={'HIDDEN'},
93 @classmethod
94 def poll(cls, context):
95 obj = context.active_object
96 return obj is not None and obj.type == 'MESH' and obj.data.uv_layers
98 def invoke(self, context, event):
99 self.size = self.get_image_size(context)
100 self.filepath = self.get_default_file_name(context) + "." + self.mode.lower()
101 context.window_manager.fileselect_add(self)
102 return {'RUNNING_MODAL'}
104 def get_default_file_name(self, context):
105 AMOUNT = 3
106 objects = list(self.iter_objects_to_export(context))
107 name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]]))
108 if len(objects) > AMOUNT:
109 name += " and more"
110 return name
112 def check(self, context):
113 if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
114 self.filepath = self.filepath[:-4]
116 ext = "." + self.mode.lower()
117 self.filepath = bpy.path.ensure_ext(self.filepath, ext)
118 return True
120 def execute(self, context):
121 obj = context.active_object
122 is_editmode = (obj.mode == 'EDIT')
123 if is_editmode:
124 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
126 filepath = self.filepath
127 filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower())
129 meshes = list(self.iter_meshes_to_export(context))
130 polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
131 different_colors = set(color for _, color in polygon_data)
132 if self.modified:
133 depsgraph = context.evaluated_depsgraph_get()
134 for obj in self.iter_objects_to_export(context):
135 obj_eval = obj.evaluated_get(depsgraph)
136 obj_eval.to_mesh_clear()
138 export = self.get_exporter()
139 export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
141 if is_editmode:
142 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
144 return {'FINISHED'}
146 def iter_meshes_to_export(self, context):
147 depsgraph = context.evaluated_depsgraph_get()
148 for obj in self.iter_objects_to_export(context):
149 if self.modified:
150 yield obj.evaluated_get(depsgraph).to_mesh()
151 else:
152 yield obj.data
154 @staticmethod
155 def iter_objects_to_export(context):
156 for obj in {*context.selected_objects, context.active_object}:
157 if obj.type != 'MESH':
158 continue
159 mesh = obj.data
160 if mesh.uv_layers.active is None:
161 continue
162 yield obj
164 @staticmethod
165 def currently_image_image_editor(context):
166 return isinstance(context.space_data, bpy.types.SpaceImageEditor)
168 def get_currently_opened_image(self, context):
169 if not self.currently_image_image_editor(context):
170 return None
171 return context.space_data.image
173 def get_image_size(self, context):
174 # fallback if not in image context
175 image_width = self.size[0]
176 image_height = self.size[1]
178 # get size of "active" image if some exist
179 image = self.get_currently_opened_image(context)
180 if image is not None:
181 width, height = image.size
182 if width and height:
183 image_width = width
184 image_height = height
186 return image_width, image_height
188 def iter_polygon_data_to_draw(self, context, meshes):
189 for mesh in meshes:
190 uv_layer = mesh.uv_layers.active.data
191 for polygon in mesh.polygons:
192 if self.export_all or polygon.select:
193 start = polygon.loop_start
194 end = start + polygon.loop_total
195 uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end])
196 yield (uvs, self.get_polygon_color(mesh, polygon))
198 @staticmethod
199 def get_polygon_color(mesh, polygon, default=(0.8, 0.8, 0.8)):
200 if polygon.material_index < len(mesh.materials):
201 material = mesh.materials[polygon.material_index]
202 if material is not None:
203 return tuple(material.diffuse_color)[:3]
204 return default
206 def get_exporter(self):
207 if self.mode == 'PNG':
208 from . import export_uv_png
209 return export_uv_png.export
210 elif self.mode == 'EPS':
211 from . import export_uv_eps
212 return export_uv_eps.export
213 elif self.mode == 'SVG':
214 from . import export_uv_svg
215 return export_uv_svg.export
216 else:
217 assert False
220 def menu_func(self, context):
221 self.layout.operator(ExportUVLayout.bl_idname)
224 def register():
225 bpy.utils.register_class(ExportUVLayout)
226 bpy.types.IMAGE_MT_uvs.append(menu_func)
229 def unregister():
230 bpy.utils.unregister_class(ExportUVLayout)
231 bpy.types.IMAGE_MT_uvs.remove(menu_func)
234 if __name__ == "__main__":
235 register()