object_print3d_utils: replace f-strings by str.format() for I18n
[blender-addons.git] / io_mesh_uv_layout / __init__.py
blob40d9b5016d0b4abae9f6b3bcbadc3dac7a30a7c0
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "UV Layout",
5 "author": "Campbell Barton, Matt Ebb",
6 "version": (1, 1, 3),
7 "blender": (3, 0, 0),
8 "location": "Image-Window > UVs > Export UV Layout",
9 "description": "Export the UV layout as a 2D graphic",
10 "warning": "",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_uv_layout.html",
12 "support": 'OFFICIAL',
13 "category": "Import-Export",
17 # @todo write the wiki page
19 if "bpy" in locals():
20 import importlib
21 if "export_uv_eps" in locals():
22 importlib.reload(export_uv_eps)
23 if "export_uv_png" in locals():
24 importlib.reload(export_uv_png)
25 if "export_uv_svg" in locals():
26 importlib.reload(export_uv_svg)
28 import os
29 import bpy
31 from bpy.props import (
32 StringProperty,
33 BoolProperty,
34 EnumProperty,
35 IntVectorProperty,
36 FloatProperty,
40 class ExportUVLayout(bpy.types.Operator):
41 """Export UV layout to file"""
43 bl_idname = "uv.export_layout"
44 bl_label = "Export UV Layout"
45 bl_options = {'REGISTER', 'UNDO'}
47 filepath: StringProperty(
48 subtype='FILE_PATH',
50 export_all: BoolProperty(
51 name="All UVs",
52 description="Export all UVs in this mesh (not just visible ones)",
53 default=False,
55 modified: BoolProperty(
56 name="Modified",
57 description="Exports UVs from the modified mesh",
58 default=False,
60 mode: EnumProperty(
61 items=(
62 ('SVG', "Scalable Vector Graphic (.svg)",
63 "Export the UV layout to a vector SVG file"),
64 ('EPS', "Encapsulate PostScript (.eps)",
65 "Export the UV layout to a vector EPS file"),
66 ('PNG', "PNG Image (.png)",
67 "Export the UV layout to a bitmap image"),
69 name="Format",
70 description="File format to export the UV layout to",
71 default='PNG',
73 size: IntVectorProperty(
74 size=2,
75 default=(1024, 1024),
76 min=8, max=32768,
77 description="Dimensions of the exported file",
79 opacity: FloatProperty(
80 name="Fill Opacity",
81 min=0.0, max=1.0,
82 default=0.25,
83 description="Set amount of opacity for exported UV layout",
85 # For the file-selector.
86 check_existing: BoolProperty(
87 default=True,
88 options={'HIDDEN'},
91 @classmethod
92 def poll(cls, context):
93 obj = context.active_object
94 return obj is not None and obj.type == 'MESH' and obj.data.uv_layers
96 def invoke(self, context, event):
97 self.size = self.get_image_size(context)
98 self.filepath = self.get_default_file_name(context) + "." + self.mode.lower()
99 context.window_manager.fileselect_add(self)
100 return {'RUNNING_MODAL'}
102 def get_default_file_name(self, context):
103 AMOUNT = 3
104 objects = list(self.iter_objects_to_export(context))
105 name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]]))
106 if len(objects) > AMOUNT:
107 name += " and more"
108 return name
110 def check(self, context):
111 if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
112 self.filepath = self.filepath[:-4]
114 ext = "." + self.mode.lower()
115 self.filepath = bpy.path.ensure_ext(self.filepath, ext)
116 return True
118 def execute(self, context):
119 obj = context.active_object
120 is_editmode = (obj.mode == 'EDIT')
121 if is_editmode:
122 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
124 filepath = self.filepath
125 filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower())
127 meshes = list(self.iter_meshes_to_export(context))
128 polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
129 different_colors = set(color for _, color in polygon_data)
130 if self.modified:
131 depsgraph = context.evaluated_depsgraph_get()
132 for obj in self.iter_objects_to_export(context):
133 obj_eval = obj.evaluated_get(depsgraph)
134 obj_eval.to_mesh_clear()
136 export = self.get_exporter()
137 export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
139 if is_editmode:
140 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
142 return {'FINISHED'}
144 def iter_meshes_to_export(self, context):
145 depsgraph = context.evaluated_depsgraph_get()
146 for obj in self.iter_objects_to_export(context):
147 if self.modified:
148 yield obj.evaluated_get(depsgraph).to_mesh()
149 else:
150 yield obj.data
152 @staticmethod
153 def iter_objects_to_export(context):
154 for obj in {*context.selected_objects, context.active_object}:
155 if obj.type != 'MESH':
156 continue
157 mesh = obj.data
158 if mesh.uv_layers.active is None:
159 continue
160 yield obj
162 @staticmethod
163 def currently_image_image_editor(context):
164 return isinstance(context.space_data, bpy.types.SpaceImageEditor)
166 def get_currently_opened_image(self, context):
167 if not self.currently_image_image_editor(context):
168 return None
169 return context.space_data.image
171 def get_image_size(self, context):
172 # fallback if not in image context
173 image_width = self.size[0]
174 image_height = self.size[1]
176 # get size of "active" image if some exist
177 image = self.get_currently_opened_image(context)
178 if image is not None:
179 width, height = image.size
180 if width and height:
181 image_width = width
182 image_height = height
184 return image_width, image_height
186 def iter_polygon_data_to_draw(self, context, meshes):
187 for mesh in meshes:
188 uv_layer = mesh.uv_layers.active.data
189 for polygon in mesh.polygons:
190 if self.export_all or polygon.select:
191 start = polygon.loop_start
192 end = start + polygon.loop_total
193 uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end])
194 yield (uvs, self.get_polygon_color(mesh, polygon))
196 @staticmethod
197 def get_polygon_color(mesh, polygon, default=(0.8, 0.8, 0.8)):
198 if polygon.material_index < len(mesh.materials):
199 material = mesh.materials[polygon.material_index]
200 if material is not None:
201 return tuple(material.diffuse_color)[:3]
202 return default
204 def get_exporter(self):
205 if self.mode == 'PNG':
206 from . import export_uv_png
207 return export_uv_png.export
208 elif self.mode == 'EPS':
209 from . import export_uv_eps
210 return export_uv_eps.export
211 elif self.mode == 'SVG':
212 from . import export_uv_svg
213 return export_uv_svg.export
214 else:
215 assert False
218 def menu_func(self, context):
219 self.layout.operator(ExportUVLayout.bl_idname)
222 def register():
223 bpy.utils.register_class(ExportUVLayout)
224 bpy.types.IMAGE_MT_uvs.append(menu_func)
227 def unregister():
228 bpy.utils.unregister_class(ExportUVLayout)
229 bpy.types.IMAGE_MT_uvs.remove(menu_func)
232 if __name__ == "__main__":
233 register()