1 # SPDX-FileCopyrightText: 2018-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 __author__
= "imdjs, Nutti <nutti.metro@gmail.com>"
6 __status__
= "production"
8 __date__
= "22 Apr 2022"
11 from math
import atan2
, tan
, sin
, cos
14 from bpy
.props
import (
21 from mathutils
import Vector
23 from ..utils
.bl_class_registry
import BlClassRegistry
24 from ..utils
.property_class_registry
import PropertyClassRegistry
25 from ..utils
import compatibility
as compat
30 def _is_valid_context(context
):
31 # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
32 # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
34 if not common
.is_valid_space(context
, ['IMAGE_EDITOR', 'VIEW_3D']):
37 objs
= common
.get_uv_editable_objects(context
)
41 # only edit mode is allowed to execute
42 if context
.object.mode
!= 'EDIT':
48 # get sum vertex length of loop sequences
49 def _get_loop_vert_len(loops
):
51 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
52 diff
= l2
.vert
.co
- l1
.vert
.co
53 length
= length
+ abs(diff
.length
)
58 # get sum uv length of loop sequences
59 def _get_loop_uv_len(loops
, uv_layer
):
61 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
62 diff
= l2
[uv_layer
].uv
- l1
[uv_layer
].uv
63 length
= length
+ abs(diff
.length
)
68 # get center/radius of circle by 3 vertices
70 alpha
= atan2((v
[0].y
- v
[1].y
), (v
[0].x
- v
[1].x
)) + math
.pi
/ 2
71 beta
= atan2((v
[1].y
- v
[2].y
), (v
[1].x
- v
[2].x
)) + math
.pi
/ 2
72 ex
= (v
[0].x
+ v
[1].x
) / 2.0
73 ey
= (v
[0].y
+ v
[1].y
) / 2.0
74 fx
= (v
[1].x
+ v
[2].x
) / 2.0
75 fy
= (v
[1].y
+ v
[2].y
) / 2.0
76 cx
= (ey
- fy
- ex
* tan(alpha
) + fx
* tan(beta
)) / \
77 (tan(beta
) - tan(alpha
))
78 cy
= ey
- (ex
- cx
) * tan(alpha
)
79 center
= Vector((cx
, cy
))
87 # get position on circle with same arc length
88 def _calc_v_on_circle(v
, center
, radius
):
90 theta
= atan2(base
.y
- center
.y
, base
.x
- center
.x
)
92 for i
in range(len(v
)):
93 angle
= theta
+ i
* 2 * math
.pi
/ len(v
)
94 new_v
.append(Vector((center
.x
+ radius
* sin(angle
),
95 center
.y
+ radius
* cos(angle
))))
100 # get accumulate vertex lengths of loop sequences
101 def _get_loop_vert_accum_len(loops
):
102 accum_lengths
= [0.0]
104 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
105 diff
= l2
.vert
.co
- l1
.vert
.co
106 length
= length
+ abs(diff
.length
)
107 accum_lengths
.extend([length
])
112 # get sum uv length of loop sequences
113 def _get_loop_uv_accum_len(loops
, uv_layer
):
114 accum_lengths
= [0.0]
116 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
117 diff
= l2
[uv_layer
].uv
- l1
[uv_layer
].uv
118 length
= length
+ abs(diff
.length
)
119 accum_lengths
.extend([length
])
124 # get horizontal differential of UV influenced by mesh vertex
125 def _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, pidx
, infl
):
127 "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx
, vidx
, pidx
))
129 base_uv
= loop_seqs
[0][vidx
][0][uv_layer
].uv
.copy()
131 # calculate original length
134 hloops
.extend([s
[vidx
][0], s
[vidx
][1]])
135 total_vlen
= _get_loop_vert_len(hloops
)
136 accum_vlens
= _get_loop_vert_accum_len(hloops
)
137 total_uvlen
= _get_loop_uv_len(hloops
, uv_layer
)
138 accum_uvlens
= _get_loop_uv_accum_len(hloops
, uv_layer
)
139 orig_uvs
= [l
[uv_layer
].uv
.copy() for l
in hloops
]
141 # calculate target length
142 tgt_noinfl
= total_uvlen
* (hidx
+ pidx
) / len(loop_seqs
)
143 tgt_infl
= total_uvlen
* accum_vlens
[hidx
* 2 + pidx
] / total_vlen
144 target_length
= tgt_noinfl
* (1 - infl
) + tgt_infl
* infl
145 common
.debug_print(target_length
)
146 common
.debug_print(accum_uvlens
)
148 # calculate target UV
149 for i
in range(len(accum_uvlens
[:-1])):
150 # get line segment which UV will be placed
151 if accum_uvlens
[i
] <= target_length
< accum_uvlens
[i
+ 1]:
152 tgt_seg_len
= target_length
- accum_uvlens
[i
]
153 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
155 uv2
= orig_uvs
[i
+ 1]
156 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
158 elif i
== (len(accum_uvlens
[:-1]) - 1):
159 if abs(accum_uvlens
[i
+ 1] - target_length
) > 0.000001:
161 "Internal Error: horizontal_target_length={}"
162 " is not equal to {}"
163 .format(target_length
, accum_uvlens
[-1]))
164 tgt_seg_len
= target_length
- accum_uvlens
[i
]
165 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
167 uv2
= orig_uvs
[i
+ 1]
168 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
171 raise Exception("Internal Error: horizontal_target_length={}"
172 " is not in range {} to {}"
173 .format(target_length
, accum_uvlens
[0],
179 # --------------------- LOOP STRUCTURE ----------------------
181 # loops[hidx][vidx][pidx]
182 # hidx: horizontal index
183 # vidx: vertical index
186 # <----- horizontal ----->
188 # (hidx, vidx, pidx) = (0, 3, 0)
189 # | (hidx, vidx, pidx) = (1, 3, 0)
193 # vertical | o --- oo --- o <- (hidx, vidx, pidx)
194 # | o --- oo --- o = (1, 2, 1)
198 # | (hidx, vidx, pidx) = (1, 0, 1)
199 # (hidx, vidx, pidx) = (0, 0, 0)
201 # -----------------------------------------------------------
204 # get vertical differential of UV influenced by mesh vertex
205 def _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, pidx
, infl
):
207 "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx
, vidx
, pidx
))
209 base_uv
= loop_seqs
[hidx
][0][pidx
][uv_layer
].uv
.copy()
211 # calculate original length
213 for s
in loop_seqs
[hidx
]:
214 vloops
.append(s
[pidx
])
215 total_vlen
= _get_loop_vert_len(vloops
)
216 accum_vlens
= _get_loop_vert_accum_len(vloops
)
217 total_uvlen
= _get_loop_uv_len(vloops
, uv_layer
)
218 accum_uvlens
= _get_loop_uv_accum_len(vloops
, uv_layer
)
219 orig_uvs
= [l
[uv_layer
].uv
.copy() for l
in vloops
]
221 # calculate target length
222 tgt_noinfl
= total_uvlen
* int((vidx
+ 1) / 2) * 2 / len(loop_seqs
[hidx
])
223 tgt_infl
= total_uvlen
* accum_vlens
[vidx
] / total_vlen
224 target_length
= tgt_noinfl
* (1 - infl
) + tgt_infl
* infl
225 common
.debug_print(target_length
)
226 common
.debug_print(accum_uvlens
)
228 # calculate target UV
229 for i
in range(len(accum_uvlens
[:-1])):
230 # get line segment which UV will be placed
231 if accum_uvlens
[i
] <= target_length
< accum_uvlens
[i
+ 1]:
232 tgt_seg_len
= target_length
- accum_uvlens
[i
]
233 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
235 uv2
= orig_uvs
[i
+ 1]
236 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
238 elif i
== (len(accum_uvlens
[:-1]) - 1):
239 if abs(accum_uvlens
[i
+ 1] - target_length
) > 0.000001:
240 raise Exception("Internal Error: horizontal_target_length={}"
241 " is not equal to {}"
242 .format(target_length
, accum_uvlens
[-1]))
243 tgt_seg_len
= target_length
- accum_uvlens
[i
]
244 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
246 uv2
= orig_uvs
[i
+ 1]
247 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
250 raise Exception("Internal Error: horizontal_target_length={}"
251 " is not in range {} to {}"
252 .format(target_length
, accum_uvlens
[0],
258 # get horizontal differential of UV no influenced
259 def _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
):
260 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
261 h_uv
= loop_seqs
[-1][0][1][uv_layer
].uv
.copy() - base_uv
263 return hidx
* h_uv
/ len(loop_seqs
)
266 # get vertical differential of UV no influenced
267 def _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
):
268 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
269 v_uv
= loop_seqs
[0][-1][0][uv_layer
].uv
.copy() - base_uv
271 hseq
= loop_seqs
[hidx
]
272 return int((vidx
+ 1) / 2) * v_uv
/ (len(hseq
) / 2)
275 @PropertyClassRegistry()
280 def init_props(cls
, scene
):
281 scene
.muv_align_uv_enabled
= BoolProperty(
282 name
="Align UV Enabled",
283 description
="Align UV is enabled",
286 scene
.muv_align_uv_transmission
= BoolProperty(
288 description
="Align linked UVs",
291 scene
.muv_align_uv_select
= BoolProperty(
293 description
="Select UVs which are aligned",
296 scene
.muv_align_uv_vertical
= BoolProperty(
297 name
="Vert-Infl (Vertical)",
298 description
="Align vertical direction influenced "
299 "by mesh vertex proportion",
302 scene
.muv_align_uv_horizontal
= BoolProperty(
303 name
="Vert-Infl (Horizontal)",
304 description
="Align horizontal direction influenced "
305 "by mesh vertex proportion",
308 scene
.muv_align_uv_mesh_infl
= FloatProperty(
309 name
="Mesh Influence",
310 description
="Influence rate of mesh vertex",
315 scene
.muv_align_uv_location
= EnumProperty(
317 description
="Align location",
319 ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
320 ('MIDDLE', "Middle", "Align to middle"),
321 ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
326 scene
.muv_align_uv_snap_method
= EnumProperty(
328 description
="Snap method",
330 ('POINT', "Point", "Snap to point"),
331 ('EDGE', "Edge", "Snap to edge"),
335 scene
.muv_align_uv_snap_point_group
= EnumProperty(
336 name
="Snap Group (Point)",
337 description
="Group that snap (point) operation applies for",
339 ('VERT', "Vertex", "Vertex"),
340 ('FACE', "Face", "Face"),
341 ('UV_ISLAND', "UV Island", "UV Island"),
345 scene
.muv_align_uv_snap_point_target
= FloatVectorProperty(
346 name
="Snap Target (Point)",
347 description
="Target point where UV vertices snap to",
353 default
=(0.000, 0.000),
355 scene
.muv_align_uv_snap_edge_group
= EnumProperty(
356 name
="Snap Group (Edge)",
357 description
="Group that snap (edge) operation applies for",
359 ('EDGE', "Edge", "Edge"),
360 ('FACE', "Face", "Face"),
361 ('UV_ISLAND', "UV Island", "UV Island"),
365 scene
.muv_align_uv_snap_edge_target_1
= FloatVectorProperty(
366 name
="Snap Target (Edge)",
367 description
="Target edge where UV vertices snap to",
373 default
=(0.000, 0.000),
375 scene
.muv_align_uv_snap_edge_target_2
= FloatVectorProperty(
376 name
="Snap Target (Edge)",
377 description
="Target edge where UV vertices snap to",
383 default
=(0.000, 0.000),
387 def del_props(cls
, scene
):
388 del scene
.muv_align_uv_enabled
389 del scene
.muv_align_uv_transmission
390 del scene
.muv_align_uv_select
391 del scene
.muv_align_uv_vertical
392 del scene
.muv_align_uv_horizontal
393 del scene
.muv_align_uv_mesh_infl
394 del scene
.muv_align_uv_location
395 del scene
.muv_align_uv_snap_method
396 del scene
.muv_align_uv_snap_point_group
397 del scene
.muv_align_uv_snap_point_target
398 del scene
.muv_align_uv_snap_edge_group
399 del scene
.muv_align_uv_snap_edge_target_1
400 del scene
.muv_align_uv_snap_edge_target_2
404 @compat.make_annotations
405 class MUV_OT_AlignUV_Circle(bpy
.types
.Operator
):
407 bl_idname
= "uv.muv_align_uv_circle"
408 bl_label
= "Align UV (Circle)"
409 bl_description
= "Align UV coordinates to Circle"
410 bl_options
= {'REGISTER', 'UNDO'}
412 transmission
= BoolProperty(
414 description
="Align linked UVs",
417 select
= BoolProperty(
419 description
="Select UVs which are aligned",
424 def poll(cls
, context
):
425 # we can not get area/space/region from console
426 if common
.is_console_mode():
428 return _is_valid_context(context
)
430 def execute(self
, context
):
431 objs
= common
.get_uv_editable_objects(context
)
434 bm
= bmesh
.from_edit_mesh(obj
.data
)
435 if common
.check_version(2, 73, 0) >= 0:
436 bm
.faces
.ensure_lookup_table()
437 uv_layer
= bm
.loops
.layers
.uv
.verify()
439 # loop_seqs[horizontal][vertical][loop]
440 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
, True)
442 self
.report({'WARNING'},
443 "Object {}: {}".format(obj
.name
, error
))
446 # get circle and new UVs
447 uvs
= [hseq
[0][0][uv_layer
].uv
.copy() for hseq
in loop_seqs
]
448 c
, r
= _get_circle(uvs
[0:3])
449 new_uvs
= _calc_v_on_circle(uvs
, c
, r
)
451 # check if center is identical
452 center_is_identical
= False
453 center
= loop_seqs
[0][-1][0].vert
454 if (len(loop_seqs
[0][-1]) == 1) and \
455 loop_seqs
[0][-1][0].vert
== center
:
456 center_is_identical
= True
458 # check if topology is correct
459 if center_is_identical
:
460 for hseq
in loop_seqs
[1:]:
461 if len(hseq
[-1]) != 1:
462 self
.report({'WARNING'},
463 "Object {}: Last face must be triangle"
466 if hseq
[-1][0].vert
!= center
:
467 self
.report({'WARNING'},
468 "Object {}: Center must be identical"
472 for hseq
in loop_seqs
[1:]:
473 if len(hseq
[-1]) == 1:
474 self
.report({'WARNING'},
475 "Object {}: Last face must not be triangle"
478 if hseq
[-1][0].vert
== center
:
479 self
.report({'WARNING'},
480 "Object {}: Center must not be identical"
485 if self
.transmission
:
486 for hidx
, hseq
in enumerate(loop_seqs
):
487 for vidx
, pair
in enumerate(hseq
):
488 all_
= int((len(hseq
) + 1) / 2)
489 if center_is_identical
:
490 r
= (all_
- int((vidx
+ 1) / 2)) / all_
492 r
= (1 + all_
- int((vidx
+ 1) / 2)) / all_
493 pair
[0][uv_layer
].uv
= c
+ (new_uvs
[hidx
] - c
) * r
495 pair
[0][uv_layer
].select
= True
500 next_hidx
= (hidx
+ 1) % len(loop_seqs
)
501 pair
[1][uv_layer
].uv
= \
502 c
+ ((new_uvs
[next_hidx
]) - c
) * r
504 pair
[1][uv_layer
].select
= True
506 for hidx
, hseq
in enumerate(loop_seqs
):
508 pair
[0][uv_layer
].uv
= new_uvs
[hidx
]
509 pair
[1][uv_layer
].uv
= new_uvs
[(hidx
+ 1) % len(loop_seqs
)]
511 pair
[0][uv_layer
].select
= True
512 pair
[1][uv_layer
].select
= True
514 bmesh
.update_edit_mesh(obj
.data
)
520 @compat.make_annotations
521 class MUV_OT_AlignUV_Straighten(bpy
.types
.Operator
):
523 bl_idname
= "uv.muv_align_uv_straighten"
524 bl_label
= "Align UV (Straighten)"
525 bl_description
= "Straighten UV coordinates"
526 bl_options
= {'REGISTER', 'UNDO'}
528 transmission
= BoolProperty(
530 description
="Align linked UVs",
533 select
= BoolProperty(
535 description
="Select UVs which are aligned",
538 vertical
= BoolProperty(
539 name
="Vert-Infl (Vertical)",
540 description
="Align vertical direction influenced "
541 "by mesh vertex proportion",
544 horizontal
= BoolProperty(
545 name
="Vert-Infl (Horizontal)",
546 description
="Align horizontal direction influenced "
547 "by mesh vertex proportion",
550 mesh_infl
= FloatProperty(
551 name
="Mesh Influence",
552 description
="Influence rate of mesh vertex",
559 def poll(cls
, context
):
560 # we can not get area/space/region from console
561 if common
.is_console_mode():
563 return _is_valid_context(context
)
565 # selected and paralleled UV loop sequence will be aligned
566 def __align_w_transmission(self
, loop_seqs
, uv_layer
):
567 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
571 # hseq[vertical][loop]
572 for hidx
, hseq
in enumerate(loop_seqs
):
575 for vidx
in range(0, len(hseq
), 2):
578 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
580 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
582 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
583 hidx
, 0, self
.mesh_infl
),
584 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
585 hidx
, 1, self
.mesh_infl
),
589 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
),
590 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
+ 1),
591 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
),
592 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
+ 1)
596 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
598 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
600 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
601 hidx
, 0, self
.mesh_infl
),
602 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
603 hidx
, 1, self
.mesh_infl
),
607 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
608 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
609 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
610 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
612 diffs
.append([hdiff_uvs
, vdiff_uvs
])
613 diff_uvs
.append(diffs
)
616 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
617 for vidx
in range(0, len(hseq
), 2):
619 hseq
[vidx
][0], hseq
[vidx
][1],
620 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
622 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
623 diffs
[int(vidx
/ 2)][1]):
624 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
626 l
[uv_layer
].select
= True
628 # only selected UV loop sequence will be aligned
629 def __align_wo_transmission(self
, loop_seqs
, uv_layer
):
630 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
632 h_uv
= loop_seqs
[-1][0][1][uv_layer
].uv
.copy() - base_uv
633 for hidx
, hseq
in enumerate(loop_seqs
):
634 # only selected loop pair is targeted
636 hdiff_uv_0
= hidx
* h_uv
/ len(loop_seqs
)
637 hdiff_uv_1
= (hidx
+ 1) * h_uv
/ len(loop_seqs
)
638 pair
[0][uv_layer
].uv
= base_uv
+ hdiff_uv_0
639 pair
[1][uv_layer
].uv
= base_uv
+ hdiff_uv_1
641 pair
[0][uv_layer
].select
= True
642 pair
[1][uv_layer
].select
= True
644 def __align(self
, loop_seqs
, uv_layer
):
645 if self
.transmission
:
646 self
.__align
_w
_transmission
(loop_seqs
, uv_layer
)
648 self
.__align
_wo
_transmission
(loop_seqs
, uv_layer
)
650 def execute(self
, context
):
651 objs
= common
.get_uv_editable_objects(context
)
654 bm
= bmesh
.from_edit_mesh(obj
.data
)
655 if common
.check_version(2, 73, 0) >= 0:
656 bm
.faces
.ensure_lookup_table()
657 uv_layer
= bm
.loops
.layers
.uv
.verify()
659 # loop_seqs[horizontal][vertical][loop]
660 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
)
662 self
.report({'WARNING'},
663 "Object {}: {}".format(obj
.name
, error
))
667 self
.__align
(loop_seqs
, uv_layer
)
669 bmesh
.update_edit_mesh(obj
.data
)
675 @compat.make_annotations
676 class MUV_OT_AlignUV_Axis(bpy
.types
.Operator
):
678 bl_idname
= "uv.muv_align_uv_axis"
679 bl_label
= "Align UV (XY-Axis)"
680 bl_description
= "Align UV to XY-axis"
681 bl_options
= {'REGISTER', 'UNDO'}
683 transmission
= BoolProperty(
685 description
="Align linked UVs",
688 select
= BoolProperty(
690 description
="Select UVs which are aligned",
693 vertical
= BoolProperty(
694 name
="Vert-Infl (Vertical)",
695 description
="Align vertical direction influenced "
696 "by mesh vertex proportion",
699 horizontal
= BoolProperty(
700 name
="Vert-Infl (Horizontal)",
701 description
="Align horizontal direction influenced "
702 "by mesh vertex proportion",
705 location
= EnumProperty(
707 description
="Align location",
709 ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
710 ('MIDDLE', "Middle", "Align to middle"),
711 ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
715 mesh_infl
= FloatProperty(
716 name
="Mesh Influence",
717 description
="Influence rate of mesh vertex",
724 def poll(cls
, context
):
725 # we can not get area/space/region from console
726 if common
.is_console_mode():
728 return _is_valid_context(context
)
731 def __get_uv_max_min(self
, loop_seqs
, uv_layer
):
732 uv_max
= Vector((-1000000.0, -1000000.0))
733 uv_min
= Vector((1000000.0, 1000000.0))
734 for hseq
in loop_seqs
:
737 uv_max
.x
= max(uv
.x
, uv_max
.x
)
738 uv_max
.y
= max(uv
.y
, uv_max
.y
)
739 uv_min
.x
= min(uv
.x
, uv_min
.x
)
740 uv_min
.y
= min(uv
.y
, uv_min
.y
)
742 return uv_max
, uv_min
744 # get UV differentiation when UVs are aligned to X-axis
745 def __get_x_axis_align_diff_uvs(self
, loop_seqs
, uv_layer
, uv_min
,
748 for hidx
, hseq
in enumerate(loop_seqs
):
750 luv0
= pair
[0][uv_layer
]
751 luv1
= pair
[1][uv_layer
]
752 target_uv0
= Vector((0.0, 0.0))
753 target_uv1
= Vector((0.0, 0.0))
754 if self
.location
== 'RIGHT_BOTTOM':
755 target_uv0
.y
= target_uv1
.y
= uv_min
.y
756 elif self
.location
== 'MIDDLE':
757 target_uv0
.y
= target_uv1
.y
= uv_min
.y
+ height
* 0.5
758 elif self
.location
== 'LEFT_TOP':
759 target_uv0
.y
= target_uv1
.y
= uv_min
.y
+ height
760 if luv0
.uv
.x
< luv1
.uv
.x
:
761 target_uv0
.x
= uv_min
.x
+ hidx
* width
/ len(loop_seqs
)
762 target_uv1
.x
= uv_min
.x
+ (hidx
+ 1) * width
/ len(loop_seqs
)
764 target_uv0
.x
= uv_min
.x
+ (hidx
+ 1) * width
/ len(loop_seqs
)
765 target_uv1
.x
= uv_min
.x
+ hidx
* width
/ len(loop_seqs
)
766 diff_uvs
.append([target_uv0
- luv0
.uv
, target_uv1
- luv1
.uv
])
770 # get UV differentiation when UVs are aligned to Y-axis
771 def __get_y_axis_align_diff_uvs(self
, loop_seqs
, uv_layer
, uv_min
,
774 for hidx
, hseq
in enumerate(loop_seqs
):
776 luv0
= pair
[0][uv_layer
]
777 luv1
= pair
[1][uv_layer
]
778 target_uv0
= Vector((0.0, 0.0))
779 target_uv1
= Vector((0.0, 0.0))
780 if self
.location
== 'RIGHT_BOTTOM':
781 target_uv0
.x
= target_uv1
.x
= uv_min
.x
+ width
782 elif self
.location
== 'MIDDLE':
783 target_uv0
.x
= target_uv1
.x
= uv_min
.x
+ width
* 0.5
784 elif self
.location
== 'LEFT_TOP':
785 target_uv0
.x
= target_uv1
.x
= uv_min
.x
786 if luv0
.uv
.y
< luv1
.uv
.y
:
787 target_uv0
.y
= uv_min
.y
+ hidx
* height
/ len(loop_seqs
)
788 target_uv1
.y
= uv_min
.y
+ (hidx
+ 1) * height
/ len(loop_seqs
)
790 target_uv0
.y
= uv_min
.y
+ (hidx
+ 1) * height
/ len(loop_seqs
)
791 target_uv1
.y
= uv_min
.y
+ hidx
* height
/ len(loop_seqs
)
792 diff_uvs
.append([target_uv0
- luv0
.uv
, target_uv1
- luv1
.uv
])
796 # only selected UV loop sequence will be aligned along to X-axis
797 def __align_to_x_axis_wo_transmission(self
, loop_seqs
, uv_layer
,
798 uv_min
, width
, height
):
799 # reverse if the UV coordinate is not sorted by position
800 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.x
> \
801 loop_seqs
[-1][0][0][uv_layer
].uv
.x
804 for hidx
, hseq
in enumerate(loop_seqs
):
805 for vidx
, pair
in enumerate(hseq
):
806 tmp
= loop_seqs
[hidx
][vidx
][0]
807 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
808 loop_seqs
[hidx
][vidx
][1] = tmp
810 # get UV differential
811 diff_uvs
= self
.__get
_x
_axis
_align
_diff
_uvs
(loop_seqs
,
816 for hseq
, duv
in zip(loop_seqs
, diff_uvs
):
818 luv0
= pair
[0][uv_layer
]
819 luv1
= pair
[1][uv_layer
]
820 luv0
.uv
= luv0
.uv
+ duv
[0]
821 luv1
.uv
= luv1
.uv
+ duv
[1]
823 # only selected UV loop sequence will be aligned along to Y-axis
824 def __align_to_y_axis_wo_transmission(self
, loop_seqs
, uv_layer
,
825 uv_min
, width
, height
):
826 # reverse if the UV coordinate is not sorted by position
827 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.y
> \
828 loop_seqs
[-1][0][0][uv_layer
].uv
.y
831 for hidx
, hseq
in enumerate(loop_seqs
):
832 for vidx
, pair
in enumerate(hseq
):
833 tmp
= loop_seqs
[hidx
][vidx
][0]
834 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
835 loop_seqs
[hidx
][vidx
][1] = tmp
837 # get UV differential
838 diff_uvs
= self
.__get
_y
_axis
_align
_diff
_uvs
(loop_seqs
,
843 for hseq
, duv
in zip(loop_seqs
, diff_uvs
):
845 luv0
= pair
[0][uv_layer
]
846 luv1
= pair
[1][uv_layer
]
847 luv0
.uv
= luv0
.uv
+ duv
[0]
848 luv1
.uv
= luv1
.uv
+ duv
[1]
850 # selected and paralleled UV loop sequence will be aligned along to X-axis
851 def __align_to_x_axis_w_transmission(self
, loop_seqs
, uv_layer
,
852 uv_min
, width
, height
):
853 # reverse if the UV coordinate is not sorted by position
854 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.x
> \
855 loop_seqs
[-1][0][0][uv_layer
].uv
.x
858 for hidx
, hseq
in enumerate(loop_seqs
):
859 for vidx
in range(len(hseq
)):
860 tmp
= loop_seqs
[hidx
][vidx
][0]
861 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
862 loop_seqs
[hidx
][vidx
][1] = tmp
864 # get offset UVs when the UVs are aligned to X-axis
865 align_diff_uvs
= self
.__get
_x
_axis
_align
_diff
_uvs
(loop_seqs
,
868 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
870 for hseq
, aduv
in zip(loop_seqs
, align_diff_uvs
):
871 luv0
= hseq
[0][0][uv_layer
]
872 luv1
= hseq
[0][1][uv_layer
]
873 offset_uvs
.append([luv0
.uv
+ aduv
[0] - base_uv
,
874 luv1
.uv
+ aduv
[1] - base_uv
])
876 # get UV differential
878 # hseq[vertical][loop]
879 for hidx
, hseq
in enumerate(loop_seqs
):
882 for vidx
in range(0, len(hseq
), 2):
885 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
887 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
889 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
890 hidx
, 0, self
.mesh_infl
),
891 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
892 hidx
, 1, self
.mesh_infl
),
894 hdiff_uvs
[0].y
= hdiff_uvs
[0].y
+ offset_uvs
[hidx
][0].y
895 hdiff_uvs
[1].y
= hdiff_uvs
[1].y
+ offset_uvs
[hidx
][1].y
896 hdiff_uvs
[2].y
= hdiff_uvs
[2].y
+ offset_uvs
[hidx
][0].y
897 hdiff_uvs
[3].y
= hdiff_uvs
[3].y
+ offset_uvs
[hidx
][1].y
907 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
909 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
911 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
912 hidx
, 0, self
.mesh_infl
),
913 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
914 hidx
, 1, self
.mesh_infl
),
918 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
919 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
920 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
921 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
923 diffs
.append([hdiff_uvs
, vdiff_uvs
])
924 diff_uvs
.append(diffs
)
927 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
928 for vidx
in range(0, len(hseq
), 2):
930 hseq
[vidx
][0], hseq
[vidx
][1],
931 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
933 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
934 diffs
[int(vidx
/ 2)][1]):
935 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
937 l
[uv_layer
].select
= True
939 # selected and paralleled UV loop sequence will be aligned along to Y-axis
940 def __align_to_y_axis_w_transmission(self
, loop_seqs
, uv_layer
,
941 uv_min
, width
, height
):
942 # reverse if the UV coordinate is not sorted by position
943 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.y
> \
944 loop_seqs
[-1][0][-1][uv_layer
].uv
.y
947 for hidx
, hseq
in enumerate(loop_seqs
):
948 for vidx
in range(len(hseq
)):
949 tmp
= loop_seqs
[hidx
][vidx
][0]
950 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
951 loop_seqs
[hidx
][vidx
][1] = tmp
953 # get offset UVs when the UVs are aligned to Y-axis
954 align_diff_uvs
= self
.__get
_y
_axis
_align
_diff
_uvs
(loop_seqs
,
957 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
959 for hseq
, aduv
in zip(loop_seqs
, align_diff_uvs
):
960 luv0
= hseq
[0][0][uv_layer
]
961 luv1
= hseq
[0][1][uv_layer
]
962 offset_uvs
.append([luv0
.uv
+ aduv
[0] - base_uv
,
963 luv1
.uv
+ aduv
[1] - base_uv
])
965 # get UV differential
967 # hseq[vertical][loop]
968 for hidx
, hseq
in enumerate(loop_seqs
):
971 for vidx
in range(0, len(hseq
), 2):
974 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
976 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
978 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
979 hidx
, 0, self
.mesh_infl
),
980 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
981 hidx
, 1, self
.mesh_infl
),
983 hdiff_uvs
[0].x
= hdiff_uvs
[0].x
+ offset_uvs
[hidx
][0].x
984 hdiff_uvs
[1].x
= hdiff_uvs
[1].x
+ offset_uvs
[hidx
][1].x
985 hdiff_uvs
[2].x
= hdiff_uvs
[2].x
+ offset_uvs
[hidx
][0].x
986 hdiff_uvs
[3].x
= hdiff_uvs
[3].x
+ offset_uvs
[hidx
][1].x
996 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
998 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
1000 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
1001 hidx
, 0, self
.mesh_infl
),
1002 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
1003 hidx
, 1, self
.mesh_infl
),
1007 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
1008 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
1009 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
1010 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
1012 diffs
.append([hdiff_uvs
, vdiff_uvs
])
1013 diff_uvs
.append(diffs
)
1016 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
1017 for vidx
in range(0, len(hseq
), 2):
1019 hseq
[vidx
][0], hseq
[vidx
][1],
1020 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
1022 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
1023 diffs
[int(vidx
/ 2)][1]):
1024 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
1026 l
[uv_layer
].select
= True
1028 def __align(self
, loop_seqs
, uv_layer
, uv_min
, width
, height
):
1029 # align along to x-axis
1031 if self
.transmission
:
1032 self
.__align
_to
_x
_axis
_w
_transmission
(loop_seqs
,
1036 self
.__align
_to
_x
_axis
_wo
_transmission
(loop_seqs
,
1039 # align along to y-axis
1041 if self
.transmission
:
1042 self
.__align
_to
_y
_axis
_w
_transmission
(loop_seqs
,
1046 self
.__align
_to
_y
_axis
_wo
_transmission
(loop_seqs
,
1050 def execute(self
, context
):
1051 objs
= common
.get_uv_editable_objects(context
)
1054 bm
= bmesh
.from_edit_mesh(obj
.data
)
1055 if common
.check_version(2, 73, 0) >= 0:
1056 bm
.faces
.ensure_lookup_table()
1057 uv_layer
= bm
.loops
.layers
.uv
.verify()
1059 # loop_seqs[horizontal][vertical][loop]
1060 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
)
1062 self
.report({'WARNING'},
1063 "Object {}: {}".format(obj
.name
, error
))
1064 return {'CANCELLED'}
1066 # get height and width
1067 uv_max
, uv_min
= self
.__get
_uv
_max
_min
(loop_seqs
, uv_layer
)
1068 width
= uv_max
.x
- uv_min
.x
1069 height
= uv_max
.y
- uv_min
.y
1071 self
.__align
(loop_seqs
, uv_layer
, uv_min
, width
, height
)
1073 bmesh
.update_edit_mesh(obj
.data
)
1079 @compat.make_annotations
1080 class MUV_OT_AlignUV_SnapToPoint(bpy
.types
.Operator
):
1082 bl_idname
= "uv.muv_align_uv_snap_to_point"
1083 bl_label
= "Align UV (Snap to Point)"
1084 bl_description
= "Align UV to the target point"
1085 bl_options
= {'REGISTER', 'UNDO'}
1087 group
= EnumProperty(
1089 description
="Group that snap operation applies for",
1091 ('VERT', "Vertex", "Vertex"),
1092 ('FACE', "Face", "Face"),
1093 ('UV_ISLAND', "UV Island", "UV Island"),
1097 target
= FloatVectorProperty(
1099 description
="Target where UV vertices snap to",
1105 default
=(0.000, 0.000),
1108 def _get_snap_target_loops(self
, context
, bm
, uv_layer
):
1111 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1113 # Process snap operation.
1114 for face
in selected_faces
:
1115 for l
in face
.loops
:
1116 if context
.tool_settings
.use_uv_select_sync
or \
1118 target_loops
.append(l
)
1122 def _get_snap_target_faces(self
, context
, bm
, uv_layer
):
1125 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1127 for face
in selected_faces
:
1128 for l
in face
.loops
:
1129 if not context
.tool_settings
.use_uv_select_sync
and \
1130 not l
[uv_layer
].select
:
1133 target_faces
.append(face
)
1137 def _get_snap_target_islands(self
, context
, bm
, uv_layer
):
1140 islands
= common
.get_island_info_from_bmesh(bm
, only_selected
=True)
1143 some_verts_not_selected
= False
1144 for face
in isl
["faces"]:
1145 for l
in face
["face"].loops
:
1146 if not context
.tool_settings
.use_uv_select_sync
and \
1147 not l
[uv_layer
].select
:
1148 some_verts_not_selected
= True
1150 if not some_verts_not_selected
:
1151 target_islands
.append(isl
)
1153 return target_islands
1155 def execute(self
, context
):
1156 objs
= common
.get_uv_editable_objects(context
)
1161 'UV_ISLAND': "UV Island",
1163 no_selection_reason
= group_to_reason
[self
.group
]
1166 bm
= bmesh
.from_edit_mesh(obj
.data
)
1167 if common
.check_version(2, 73, 0) >= 0:
1168 bm
.faces
.ensure_lookup_table()
1169 uv_layer
= bm
.loops
.layers
.uv
.verify()
1171 if self
.group
== 'VERT':
1173 self
._get
_snap
_target
_loops
(context
, bm
, uv_layer
)
1175 # Process snap operation.
1176 for l
in target_loops
:
1177 l
[uv_layer
].uv
= self
.target
1178 no_selection_reason
= None
1180 elif self
.group
== 'FACE':
1182 self
._get
_snap
_target
_faces
(context
, bm
, uv_layer
)
1184 for face
in target_faces
:
1185 ave_uv
= Vector((0.0, 0.0))
1186 for l
in face
.loops
:
1187 ave_uv
+= l
[uv_layer
].uv
1188 ave_uv
/= len(face
.loops
)
1189 diff
= Vector(self
.target
) - ave_uv
1191 # Process snap operation.
1192 for l
in face
.loops
:
1193 l
[uv_layer
].uv
+= diff
1194 no_selection_reason
= None
1196 elif self
.group
== 'UV_ISLAND':
1198 self
._get
_snap
_target
_islands
(context
, bm
, uv_layer
)
1200 for isl
in target_islands
:
1201 ave_uv
= Vector((0.0, 0.0))
1203 for face
in isl
["faces"]:
1204 for l
in face
["face"].loops
:
1205 ave_uv
+= l
[uv_layer
].uv
1209 diff
= Vector(self
.target
) - ave_uv
1211 # Process snap operation.
1212 for face
in isl
["faces"]:
1213 for l
in face
["face"].loops
:
1214 l
[uv_layer
].uv
+= diff
1215 no_selection_reason
= None
1217 bmesh
.update_edit_mesh(obj
.data
)
1219 if no_selection_reason
:
1222 "Must select more than 1 {}.".format(no_selection_reason
)
1224 return {'CANCELLED'}
1230 @compat.make_annotations
1231 class MUV_OT_AlignUV_Snap_SetPointTargetToCursor(bpy
.types
.Operator
):
1233 bl_idname
= "uv.muv_align_uv_snap_set_point_target_to_cursor"
1234 bl_label
= "Set Point Target to Cursor"
1235 bl_description
= """Set point target to the cursor for
1236 'Align UV (Snap to Point)'"""
1237 bl_options
= {'REGISTER', 'UNDO'}
1239 def execute(self
, context
):
1242 _
, _
, space
= common
.get_space('IMAGE_EDITOR', 'WINDOW',
1244 cursor_loc
= space
.cursor_location
1246 sc
.muv_align_uv_snap_point_target
= cursor_loc
1252 @compat.make_annotations
1253 class MUV_OT_AlignUV_Snap_SetPointTargetToVertexGroup(bpy
.types
.Operator
):
1255 bl_idname
= "uv.muv_align_uv_snap_set_point_target_to_vertex_group"
1256 bl_label
= "Set Point Target to Vertex Group"
1257 bl_description
= """Set point target to the average of vertices for
1258 'Align UV (Snap to Point)'"""
1259 bl_options
= {'REGISTER', 'UNDO'}
1261 def execute(self
, context
):
1263 objs
= common
.get_uv_editable_objects(context
)
1265 ave_uv
= Vector((0.0, 0.0))
1268 bm
= bmesh
.from_edit_mesh(obj
.data
)
1269 if common
.check_version(2, 73, 0) >= 0:
1270 bm
.faces
.ensure_lookup_table()
1271 uv_layer
= bm
.loops
.layers
.uv
.verify()
1273 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1274 for face
in selected_faces
:
1275 for l
in face
.loops
:
1276 if context
.tool_settings
.use_uv_select_sync
or \
1278 ave_uv
+= l
[uv_layer
].uv
1283 sc
.muv_align_uv_snap_point_target
= ave_uv
1289 @compat.make_annotations
1290 class MUV_OT_AlignUV_SnapToEdge(bpy
.types
.Operator
):
1292 bl_idname
= "uv.muv_align_uv_snap_to_edge"
1293 bl_label
= "Align UV (Snap to Edge)"
1294 bl_description
= "Align UV to the target edge"
1295 bl_options
= {'REGISTER', 'UNDO'}
1297 group
= EnumProperty(
1299 description
="Group that snap operation applies for",
1301 ('EDGE', "Edge", "Edge"),
1302 ('FACE', "Face", "Face"),
1303 ('UV_ISLAND', "UV Island", "UV Island"),
1307 target_1
= FloatVectorProperty(
1308 name
="Snap Target 1",
1309 description
="Vertex 1 of the target edge",
1315 default
=(0.000, 0.000),
1317 target_2
= FloatVectorProperty(
1318 name
="Snap Target 2",
1319 description
="Vertex 2 of the target edge",
1325 default
=(0.000, 0.000),
1328 def _calc_snap_move_amount(self
, loops
, uv_layer
):
1329 ave
= (loops
[0][uv_layer
].uv
+ loops
[1][uv_layer
].uv
) / 2
1330 target
= (Vector(self
.target_1
) + Vector(self
.target_2
)) / 2
1334 def _get_snap_target_loop_pairs(self
, bm
, uv_layer
):
1335 target_loop_pairs
= []
1337 selected_edges
= [e
for e
in bm
.edges
if e
.select
]
1340 for edge
in selected_edges
:
1341 for l
in edge
.link_loops
:
1342 if l
[uv_layer
].select
:
1343 cand_loops
.append(l
)
1345 for l
in cand_loops
:
1346 if l
[uv_layer
].select
and l
.link_loop_next
[uv_layer
].select
:
1347 d
= {l
, l
.link_loop_next
}
1348 if d
not in target_loop_pairs
:
1349 assert l
.face
== l
.link_loop_next
.face
1350 target_loop_pairs
.append(d
)
1352 return target_loop_pairs
1354 def _find_target_island_from_face(self
, islands
, face
):
1356 for f
in isl
["faces"]:
1357 if f
["face"] == face
:
1362 def execute(self
, context
):
1363 objs
= common
.get_uv_editable_objects(context
)
1367 bm
= bmesh
.from_edit_mesh(obj
.data
)
1368 if common
.check_version(2, 73, 0) >= 0:
1369 bm
.faces
.ensure_lookup_table()
1370 uv_layer
= bm
.loops
.layers
.uv
.verify()
1372 if self
.group
== 'EDGE':
1373 target_loop_pairs
= \
1374 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1376 for pair
in target_loop_pairs
:
1378 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1380 # Process snap operation.
1381 p
[0][uv_layer
].uv
+= diff
1382 p
[1][uv_layer
].uv
+= diff
1384 no_selection
= False
1386 elif self
.group
== 'FACE':
1387 target_loop_pairs
= \
1388 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1391 for pair
in target_loop_pairs
:
1393 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1395 # Process snap operation.
1397 if face
in face_processed
:
1400 "Must select only one edge per face. (Object: {})"
1403 return {'CANCELLED'}
1404 face_processed
.append(face
)
1405 for l
in face
.loops
:
1406 l
[uv_layer
].uv
+= diff
1407 no_selection
= False
1409 elif self
.group
== 'UV_ISLAND':
1410 target_loop_pairs
= \
1411 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1413 islands
= common
.get_island_info_from_bmesh(
1414 bm
, only_selected
=False)
1417 for pair
in target_loop_pairs
:
1419 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1421 # Find island to process.
1424 self
._find
_target
_island
_from
_face
(islands
, face
)
1425 if target_isl
is None:
1428 "Failed to find island. (Object: {})"
1431 return {'CANCELLED'}
1432 if target_isl
in isl_processed
:
1435 """Must select only one edge per island.
1439 return {'CANCELLED'}
1440 isl_processed
.append(target_isl
)
1442 # Process snap operation.
1443 for f
in target_isl
["faces"]:
1444 for l
in f
["face"].loops
:
1445 l
[uv_layer
].uv
+= diff
1446 no_selection
= False
1448 bmesh
.update_edit_mesh(obj
.data
)
1451 self
.report({'WARNING'}, "Must select more than 1 Edge.")
1452 return {'CANCELLED'}
1458 @compat.make_annotations
1459 class MUV_OT_AlignUV_Snap_SetEdgeTargetToEdgeCenter(bpy
.types
.Operator
):
1461 bl_idname
= "uv.muv_align_uv_snap_set_edge_target_to_edge_center"
1462 bl_label
= "Set Edge Target to Edge Center"
1463 bl_description
= """Set edge target to the center of edge for
1464 'Align UV (Snap to Edge)'"""
1465 bl_options
= {'REGISTER', 'UNDO'}
1467 def _get_target_loop_pairs(self
, bm
, uv_layer
):
1468 target_loop_pairs
= []
1470 selected_edges
= [e
for e
in bm
.edges
if e
.select
]
1473 for edge
in selected_edges
:
1474 for l
in edge
.link_loops
:
1475 if l
[uv_layer
].select
:
1476 cand_loops
.append(l
)
1478 for l
in cand_loops
:
1479 if l
[uv_layer
].select
and l
.link_loop_next
[uv_layer
].select
:
1480 d
= {l
, l
.link_loop_next
}
1481 if d
not in target_loop_pairs
:
1482 assert l
.face
== l
.link_loop_next
.face
1483 target_loop_pairs
.append(d
)
1485 return target_loop_pairs
1487 def execute(self
, context
):
1489 objs
= common
.get_uv_editable_objects(context
)
1491 ave_uv_1
= Vector((0.0, 0.0))
1492 ave_uv_2
= Vector((0.0, 0.0))
1495 bm
= bmesh
.from_edit_mesh(obj
.data
)
1496 if common
.check_version(2, 73, 0) >= 0:
1497 bm
.faces
.ensure_lookup_table()
1498 uv_layer
= bm
.loops
.layers
.uv
.verify()
1500 target_loop_pairs
= self
._get
_target
_loop
_pairs
(bm
, uv_layer
)
1501 for pair
in target_loop_pairs
:
1503 uv_1
= p
[0][uv_layer
].uv
1504 uv_2
= p
[1][uv_layer
].uv
1513 sc
.muv_align_uv_snap_edge_target_1
= ave_uv_1
1514 sc
.muv_align_uv_snap_edge_target_2
= ave_uv_2