UV Export: I18n: disambiguate "Modified" message
[blender-addons.git] / io_mesh_uv_layout / __init__.py
blob511e78cca856b1dd420ddb0ea3cb17c4378c0afa
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.app.translations import contexts as i18n_contexts
35 from bpy.props import (
36 StringProperty,
37 BoolProperty,
38 EnumProperty,
39 IntVectorProperty,
40 FloatProperty,
44 class ExportUVLayout(bpy.types.Operator):
45 """Export UV layout to file"""
47 bl_idname = "uv.export_layout"
48 bl_label = "Export UV Layout"
49 bl_options = {'REGISTER', 'UNDO'}
51 filepath: StringProperty(
52 subtype='FILE_PATH',
54 export_all: BoolProperty(
55 name="All UVs",
56 description="Export all UVs in this mesh (not just visible ones)",
57 default=False,
59 modified: BoolProperty(
60 name="Modified",
61 description="Exports UVs from the modified mesh",
62 default=False,
63 translation_context=i18n_contexts.id_mesh,
65 mode: EnumProperty(
66 items=(
67 ('SVG', "Scalable Vector Graphic (.svg)",
68 "Export the UV layout to a vector SVG file"),
69 ('EPS', "Encapsulated PostScript (.eps)",
70 "Export the UV layout to a vector EPS file"),
71 ('PNG', "PNG Image (.png)",
72 "Export the UV layout to a bitmap image"),
74 name="Format",
75 description="File format to export the UV layout to",
76 default='PNG',
78 size: IntVectorProperty(
79 size=2,
80 default=(1024, 1024),
81 min=8, max=32768,
82 description="Dimensions of the exported file",
84 opacity: FloatProperty(
85 name="Fill Opacity",
86 min=0.0, max=1.0,
87 default=0.25,
88 description="Set amount of opacity for exported UV layout",
90 # For the file-selector.
91 check_existing: BoolProperty(
92 default=True,
93 options={'HIDDEN'},
96 @classmethod
97 def poll(cls, context):
98 obj = context.active_object
99 return obj is not None and obj.type == 'MESH' and obj.data.uv_layers
101 def invoke(self, context, event):
102 self.size = self.get_image_size(context)
103 self.filepath = self.get_default_file_name(context) + "." + self.mode.lower()
104 context.window_manager.fileselect_add(self)
105 return {'RUNNING_MODAL'}
107 def get_default_file_name(self, context):
108 AMOUNT = 3
109 objects = list(self.iter_objects_to_export(context))
110 name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]]))
111 if len(objects) > AMOUNT:
112 name += " and more"
113 return name
115 def check(self, context):
116 if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
117 self.filepath = self.filepath[:-4]
119 ext = "." + self.mode.lower()
120 self.filepath = bpy.path.ensure_ext(self.filepath, ext)
121 return True
123 def execute(self, context):
124 obj = context.active_object
125 is_editmode = (obj.mode == 'EDIT')
126 if is_editmode:
127 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
129 filepath = self.filepath
130 filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower())
132 meshes = list(self.iter_meshes_to_export(context))
133 polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
134 different_colors = set(color for _, color in polygon_data)
135 if self.modified:
136 depsgraph = context.evaluated_depsgraph_get()
137 for obj in self.iter_objects_to_export(context):
138 obj_eval = obj.evaluated_get(depsgraph)
139 obj_eval.to_mesh_clear()
141 export = self.get_exporter()
142 export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
144 if is_editmode:
145 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
147 return {'FINISHED'}
149 def iter_meshes_to_export(self, context):
150 depsgraph = context.evaluated_depsgraph_get()
151 for obj in self.iter_objects_to_export(context):
152 if self.modified:
153 yield obj.evaluated_get(depsgraph).to_mesh()
154 else:
155 yield obj.data
157 @staticmethod
158 def iter_objects_to_export(context):
159 for obj in {*context.selected_objects, context.active_object}:
160 if obj.type != 'MESH':
161 continue
162 mesh = obj.data
163 if mesh.uv_layers.active is None:
164 continue
165 yield obj
167 @staticmethod
168 def currently_image_image_editor(context):
169 return isinstance(context.space_data, bpy.types.SpaceImageEditor)
171 def get_currently_opened_image(self, context):
172 if not self.currently_image_image_editor(context):
173 return None
174 return context.space_data.image
176 def get_image_size(self, context):
177 # fallback if not in image context
178 image_width = self.size[0]
179 image_height = self.size[1]
181 # get size of "active" image if some exist
182 image = self.get_currently_opened_image(context)
183 if image is not None:
184 width, height = image.size
185 if width and height:
186 image_width = width
187 image_height = height
189 return image_width, image_height
191 def iter_polygon_data_to_draw(self, context, meshes):
192 for mesh in meshes:
193 uv_layer = mesh.uv_layers.active.data
194 for polygon in mesh.polygons:
195 if self.export_all or polygon.select:
196 start = polygon.loop_start
197 end = start + polygon.loop_total
198 uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end])
199 yield (uvs, self.get_polygon_color(mesh, polygon))
201 @staticmethod
202 def get_polygon_color(mesh, polygon, default=(0.8, 0.8, 0.8)):
203 if polygon.material_index < len(mesh.materials):
204 material = mesh.materials[polygon.material_index]
205 if material is not None:
206 return tuple(material.diffuse_color)[:3]
207 return default
209 def get_exporter(self):
210 if self.mode == 'PNG':
211 from . import export_uv_png
212 return export_uv_png.export
213 elif self.mode == 'EPS':
214 from . import export_uv_eps
215 return export_uv_eps.export
216 elif self.mode == 'SVG':
217 from . import export_uv_svg
218 return export_uv_svg.export
219 else:
220 assert False
223 def menu_func(self, context):
224 self.layout.operator(ExportUVLayout.bl_idname)
227 def register():
228 bpy.utils.register_class(ExportUVLayout)
229 bpy.types.IMAGE_MT_uvs.append(menu_func)
232 def unregister():
233 bpy.utils.unregister_class(ExportUVLayout)
234 bpy.types.IMAGE_MT_uvs.remove(menu_func)
237 if __name__ == "__main__":
238 register()