1 # SPDX-License-Identifier: GPL-2.0-or-later
5 __author__
= "Nutti <nutti.metro@gmail.com>"
6 __status__
= "production"
8 __date__
= "6 Mar 2021"
11 from bpy
.props
import StringProperty
, EnumProperty
, BoolProperty
13 from mathutils
import Vector
16 from ..utils
.bl_class_registry
import BlClassRegistry
17 from ..utils
.property_class_registry
import PropertyClassRegistry
18 from ..utils
import compatibility
as compat
21 def _is_valid_context(context
):
22 objs
= common
.get_uv_editable_objects(context
)
26 # only edit mode is allowed to execute
27 if context
.object.mode
!= 'EDIT':
30 # only 'VIEW_3D' space is allowed to execute
31 if not common
.is_valid_space(context
, ['VIEW_3D']):
37 @PropertyClassRegistry()
39 idname
= "preserve_uv_aspect"
42 def init_props(cls
, scene
):
43 def get_loaded_texture_name(_
, __
):
44 items
= [(key
, key
, "") for key
in bpy
.data
.images
.keys()]
45 items
.append(("None", "None", ""))
48 scene
.muv_preserve_uv_aspect_enabled
= BoolProperty(
49 name
="Preserve UV Aspect Enabled",
50 description
="Preserve UV Aspect is enabled",
53 scene
.muv_preserve_uv_aspect_tex_image
= EnumProperty(
55 description
="Texture Image",
56 items
=get_loaded_texture_name
58 scene
.muv_preserve_uv_aspect_origin
= EnumProperty(
60 description
="Aspect Origin",
62 ('CENTER', 'Center', 'Center'),
63 ('LEFT_TOP', 'Left Top', 'Left Bottom'),
64 ('LEFT_CENTER', 'Left Center', 'Left Center'),
65 ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
66 ('CENTER_TOP', 'Center Top', 'Center Top'),
67 ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
68 ('RIGHT_TOP', 'Right Top', 'Right Top'),
69 ('RIGHT_CENTER', 'Right Center', 'Right Center'),
70 ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
77 def del_props(cls
, scene
):
78 del scene
.muv_preserve_uv_aspect_enabled
79 del scene
.muv_preserve_uv_aspect_tex_image
80 del scene
.muv_preserve_uv_aspect_origin
84 @compat.make_annotations
85 class MUV_OT_PreserveUVAspect(bpy
.types
.Operator
):
87 Operation class: Preserve UV Aspect
90 bl_idname
= "uv.muv_preserve_uv_aspect"
91 bl_label
= "Preserve UV Aspect"
92 bl_description
= "Choose Image"
93 bl_options
= {'REGISTER', 'UNDO'}
95 dest_img_name
= StringProperty(options
={'HIDDEN'})
96 origin
= EnumProperty(
98 description
="Aspect Origin",
100 ('CENTER', 'Center', 'Center'),
101 ('LEFT_TOP', 'Left Top', 'Left Bottom'),
102 ('LEFT_CENTER', 'Left Center', 'Left Center'),
103 ('LEFT_BOTTOM', 'Left Bottom', 'Left Bottom'),
104 ('CENTER_TOP', 'Center Top', 'Center Top'),
105 ('CENTER_BOTTOM', 'Center Bottom', 'Center Bottom'),
106 ('RIGHT_TOP', 'Right Top', 'Right Top'),
107 ('RIGHT_CENTER', 'Right Center', 'Right Center'),
108 ('RIGHT_BOTTOM', 'Right Bottom', 'Right Bottom')
115 def poll(cls
, context
):
116 # we can not get area/space/region from console
117 if common
.is_console_mode():
119 return _is_valid_context(context
)
121 def execute(self
, context
):
122 # Note: the current system only works if the
123 # f[tex_layer].image doesn't return None
124 # which will happen in certain cases
125 objs
= common
.get_uv_editable_objects(context
)
127 obj_list
= {} # { Material: Object }
129 if common
.check_version(2, 80, 0) >= 0:
130 # If more than two selected objects shares same
131 # material, we need to calculate new UV coordinates
132 # before image on texture node is overwritten.
133 material_to_rewrite
= []
134 for slot
in obj
.material_slots
:
135 if not slot
.material
:
137 nodes
= common
.find_texture_nodes_from_material(
142 "Object {} must not have more than 2 "
143 "shader nodes with image texture"
148 material_to_rewrite
.append(slot
.material
)
150 if len(material_to_rewrite
) >= 2:
153 "Object {} must not have more than 2 "
154 "materials with image texture"
157 if len(material_to_rewrite
) == 0:
160 "Object {} must not have more than 1 "
161 "material with image texture"
164 if material_to_rewrite
[0] not in obj_list
.keys():
165 obj_list
[material_to_rewrite
[0]] = []
166 obj_list
[material_to_rewrite
[0]].append(obj
)
168 # If blender version is < (2, 79), multiple objects editing
169 # mode is not supported. So, we add dummy key to obj_list.
170 obj_list
["Dummy"] = [obj
]
172 # pylint: disable=R1702
173 for mtrl
, o
in obj_list
.items():
175 bm
= bmesh
.from_edit_mesh(obj
.data
)
177 if common
.check_version(2, 73, 0) >= 0:
178 bm
.faces
.ensure_lookup_table()
180 if not bm
.loops
.layers
.uv
:
181 self
.report({'WARNING'},
182 "Object must have more than one UV map")
185 uv_layer
= bm
.loops
.layers
.uv
.verify()
187 sel_faces
= [f
for f
in bm
.faces
if f
.select
]
188 dest_img
= bpy
.data
.images
[self
.dest_img_name
]
192 if compat
.check_version(2, 80, 0) >= 0:
193 tex_image
= common
.find_image(obj
)
195 if tex_image
not in info
.keys():
197 info
[tex_image
]['faces'] = []
198 info
[tex_image
]['faces'].append(f
)
200 tex_layer
= bm
.faces
.layers
.tex
.verify()
202 if not f
[tex_layer
].image
in info
.keys():
203 info
[f
[tex_layer
].image
] = {}
204 info
[f
[tex_layer
].image
]['faces'] = []
205 info
[f
[tex_layer
].image
]['faces'].append(f
)
213 dest_img
.size
[0] / src_img
.size
[0],
214 dest_img
.size
[1] / src_img
.size
[1]))
216 if self
.origin
== 'CENTER':
217 origin
= Vector((0.0, 0.0))
219 for f
in info
[img
]['faces']:
224 origin
= origin
/ num
225 elif self
.origin
== 'LEFT_TOP':
226 origin
= Vector((100000.0, -100000.0))
227 for f
in info
[img
]['faces']:
230 origin
.x
= min(origin
.x
, uv
.x
)
231 origin
.y
= max(origin
.y
, uv
.y
)
232 elif self
.origin
== 'LEFT_CENTER':
233 origin
= Vector((100000.0, 0.0))
235 for f
in info
[img
]['faces']:
238 origin
.x
= min(origin
.x
, uv
.x
)
239 origin
.y
= origin
.y
+ uv
.y
241 origin
.y
= origin
.y
/ num
242 elif self
.origin
== 'LEFT_BOTTOM':
243 origin
= Vector((100000.0, 100000.0))
244 for f
in info
[img
]['faces']:
247 origin
.x
= min(origin
.x
, uv
.x
)
248 origin
.y
= min(origin
.y
, uv
.y
)
249 elif self
.origin
== 'CENTER_TOP':
250 origin
= Vector((0.0, -100000.0))
252 for f
in info
[img
]['faces']:
255 origin
.x
= origin
.x
+ uv
.x
256 origin
.y
= max(origin
.y
, uv
.y
)
258 origin
.x
= origin
.x
/ num
259 elif self
.origin
== 'CENTER_BOTTOM':
260 origin
= Vector((0.0, 100000.0))
262 for f
in info
[img
]['faces']:
265 origin
.x
= origin
.x
+ uv
.x
266 origin
.y
= min(origin
.y
, uv
.y
)
268 origin
.x
= origin
.x
/ num
269 elif self
.origin
== 'RIGHT_TOP':
270 origin
= Vector((-100000.0, -100000.0))
271 for f
in info
[img
]['faces']:
274 origin
.x
= max(origin
.x
, uv
.x
)
275 origin
.y
= max(origin
.y
, uv
.y
)
276 elif self
.origin
== 'RIGHT_CENTER':
277 origin
= Vector((-100000.0, 0.0))
279 for f
in info
[img
]['faces']:
282 origin
.x
= max(origin
.x
, uv
.x
)
283 origin
.y
= origin
.y
+ uv
.y
285 origin
.y
= origin
.y
/ num
286 elif self
.origin
== 'RIGHT_BOTTOM':
287 origin
= Vector((-100000.0, 100000.0))
288 for f
in info
[img
]['faces']:
291 origin
.x
= max(origin
.x
, uv
.x
)
292 origin
.y
= min(origin
.y
, uv
.y
)
294 self
.report({'ERROR'}, "Unknown Operation")
297 info
[img
]['ratio'] = ratio
298 info
[img
]['origin'] = origin
304 for f
in info
[img
]['faces']:
305 if compat
.check_version(2, 80, 0) < 0:
306 tex_layer
= bm
.faces
.layers
.tex
.verify()
307 f
[tex_layer
].image
= dest_img
310 origin
= info
[img
]['origin']
311 ratio
= info
[img
]['ratio']
313 diff
.x
= diff
.x
/ ratio
.x
314 diff
.y
= diff
.y
/ ratio
.y
315 uv
.x
= origin
.x
+ diff
.x
316 uv
.y
= origin
.y
+ diff
.y
319 bmesh
.update_edit_mesh(obj
.data
)
321 if compat
.check_version(2, 80, 0) >= 0:
322 nodes
= common
.find_texture_nodes_from_material(mtrl
)
323 nodes
[0].image
= dest_img