1 # SPDX-License-Identifier: GPL-2.0-or-later
3 __author__
= "imdjs, Nutti <nutti.metro@gmail.com>"
4 __status__
= "production"
6 __date__
= "22 Apr 2022"
9 from math
import atan2
, tan
, sin
, cos
12 from bpy
.props
import (
19 from mathutils
import Vector
21 from ..utils
.bl_class_registry
import BlClassRegistry
22 from ..utils
.property_class_registry
import PropertyClassRegistry
23 from ..utils
import compatibility
as compat
28 def _is_valid_context(context
):
29 # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
30 # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
32 if not common
.is_valid_space(context
, ['IMAGE_EDITOR', 'VIEW_3D']):
35 objs
= common
.get_uv_editable_objects(context
)
39 # only edit mode is allowed to execute
40 if context
.object.mode
!= 'EDIT':
46 # get sum vertex length of loop sequences
47 def _get_loop_vert_len(loops
):
49 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
50 diff
= l2
.vert
.co
- l1
.vert
.co
51 length
= length
+ abs(diff
.length
)
56 # get sum uv length of loop sequences
57 def _get_loop_uv_len(loops
, uv_layer
):
59 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
60 diff
= l2
[uv_layer
].uv
- l1
[uv_layer
].uv
61 length
= length
+ abs(diff
.length
)
66 # get center/radius of circle by 3 vertices
68 alpha
= atan2((v
[0].y
- v
[1].y
), (v
[0].x
- v
[1].x
)) + math
.pi
/ 2
69 beta
= atan2((v
[1].y
- v
[2].y
), (v
[1].x
- v
[2].x
)) + math
.pi
/ 2
70 ex
= (v
[0].x
+ v
[1].x
) / 2.0
71 ey
= (v
[0].y
+ v
[1].y
) / 2.0
72 fx
= (v
[1].x
+ v
[2].x
) / 2.0
73 fy
= (v
[1].y
+ v
[2].y
) / 2.0
74 cx
= (ey
- fy
- ex
* tan(alpha
) + fx
* tan(beta
)) / \
75 (tan(beta
) - tan(alpha
))
76 cy
= ey
- (ex
- cx
) * tan(alpha
)
77 center
= Vector((cx
, cy
))
85 # get position on circle with same arc length
86 def _calc_v_on_circle(v
, center
, radius
):
88 theta
= atan2(base
.y
- center
.y
, base
.x
- center
.x
)
90 for i
in range(len(v
)):
91 angle
= theta
+ i
* 2 * math
.pi
/ len(v
)
92 new_v
.append(Vector((center
.x
+ radius
* sin(angle
),
93 center
.y
+ radius
* cos(angle
))))
98 # get accumulate vertex lengths of loop sequences
99 def _get_loop_vert_accum_len(loops
):
100 accum_lengths
= [0.0]
102 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
103 diff
= l2
.vert
.co
- l1
.vert
.co
104 length
= length
+ abs(diff
.length
)
105 accum_lengths
.extend([length
])
110 # get sum uv length of loop sequences
111 def _get_loop_uv_accum_len(loops
, uv_layer
):
112 accum_lengths
= [0.0]
114 for l1
, l2
in zip(loops
[:-1], loops
[1:]):
115 diff
= l2
[uv_layer
].uv
- l1
[uv_layer
].uv
116 length
= length
+ abs(diff
.length
)
117 accum_lengths
.extend([length
])
122 # get horizontal differential of UV influenced by mesh vertex
123 def _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, pidx
, infl
):
125 "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx
, vidx
, pidx
))
127 base_uv
= loop_seqs
[0][vidx
][0][uv_layer
].uv
.copy()
129 # calculate original length
132 hloops
.extend([s
[vidx
][0], s
[vidx
][1]])
133 total_vlen
= _get_loop_vert_len(hloops
)
134 accum_vlens
= _get_loop_vert_accum_len(hloops
)
135 total_uvlen
= _get_loop_uv_len(hloops
, uv_layer
)
136 accum_uvlens
= _get_loop_uv_accum_len(hloops
, uv_layer
)
137 orig_uvs
= [l
[uv_layer
].uv
.copy() for l
in hloops
]
139 # calculate target length
140 tgt_noinfl
= total_uvlen
* (hidx
+ pidx
) / len(loop_seqs
)
141 tgt_infl
= total_uvlen
* accum_vlens
[hidx
* 2 + pidx
] / total_vlen
142 target_length
= tgt_noinfl
* (1 - infl
) + tgt_infl
* infl
143 common
.debug_print(target_length
)
144 common
.debug_print(accum_uvlens
)
146 # calculate target UV
147 for i
in range(len(accum_uvlens
[:-1])):
148 # get line segment which UV will be placed
149 if accum_uvlens
[i
] <= target_length
< accum_uvlens
[i
+ 1]:
150 tgt_seg_len
= target_length
- accum_uvlens
[i
]
151 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
153 uv2
= orig_uvs
[i
+ 1]
154 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
156 elif i
== (len(accum_uvlens
[:-1]) - 1):
157 if abs(accum_uvlens
[i
+ 1] - target_length
) > 0.000001:
159 "Internal Error: horizontal_target_length={}"
160 " is not equal to {}"
161 .format(target_length
, accum_uvlens
[-1]))
162 tgt_seg_len
= target_length
- accum_uvlens
[i
]
163 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
165 uv2
= orig_uvs
[i
+ 1]
166 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
169 raise Exception("Internal Error: horizontal_target_length={}"
170 " is not in range {} to {}"
171 .format(target_length
, accum_uvlens
[0],
177 # --------------------- LOOP STRUCTURE ----------------------
179 # loops[hidx][vidx][pidx]
180 # hidx: horizontal index
181 # vidx: vertical index
184 # <----- horizontal ----->
186 # (hidx, vidx, pidx) = (0, 3, 0)
187 # | (hidx, vidx, pidx) = (1, 3, 0)
191 # vertical | o --- oo --- o <- (hidx, vidx, pidx)
192 # | o --- oo --- o = (1, 2, 1)
196 # | (hidx, vidx, pidx) = (1, 0, 1)
197 # (hidx, vidx, pidx) = (0, 0, 0)
199 # -----------------------------------------------------------
202 # get vertical differential of UV influenced by mesh vertex
203 def _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, pidx
, infl
):
205 "loop_seqs[hidx={0}][vidx={1}][pidx={2}]".format(hidx
, vidx
, pidx
))
207 base_uv
= loop_seqs
[hidx
][0][pidx
][uv_layer
].uv
.copy()
209 # calculate original length
211 for s
in loop_seqs
[hidx
]:
212 vloops
.append(s
[pidx
])
213 total_vlen
= _get_loop_vert_len(vloops
)
214 accum_vlens
= _get_loop_vert_accum_len(vloops
)
215 total_uvlen
= _get_loop_uv_len(vloops
, uv_layer
)
216 accum_uvlens
= _get_loop_uv_accum_len(vloops
, uv_layer
)
217 orig_uvs
= [l
[uv_layer
].uv
.copy() for l
in vloops
]
219 # calculate target length
220 tgt_noinfl
= total_uvlen
* int((vidx
+ 1) / 2) * 2 / len(loop_seqs
[hidx
])
221 tgt_infl
= total_uvlen
* accum_vlens
[vidx
] / total_vlen
222 target_length
= tgt_noinfl
* (1 - infl
) + tgt_infl
* infl
223 common
.debug_print(target_length
)
224 common
.debug_print(accum_uvlens
)
226 # calculate target UV
227 for i
in range(len(accum_uvlens
[:-1])):
228 # get line segment which UV will be placed
229 if accum_uvlens
[i
] <= target_length
< accum_uvlens
[i
+ 1]:
230 tgt_seg_len
= target_length
- accum_uvlens
[i
]
231 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
233 uv2
= orig_uvs
[i
+ 1]
234 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
236 elif i
== (len(accum_uvlens
[:-1]) - 1):
237 if abs(accum_uvlens
[i
+ 1] - target_length
) > 0.000001:
238 raise Exception("Internal Error: horizontal_target_length={}"
239 " is not equal to {}"
240 .format(target_length
, accum_uvlens
[-1]))
241 tgt_seg_len
= target_length
- accum_uvlens
[i
]
242 seg_len
= accum_uvlens
[i
+ 1] - accum_uvlens
[i
]
244 uv2
= orig_uvs
[i
+ 1]
245 target_uv
= (uv1
- base_uv
) + (uv2
- uv1
) * tgt_seg_len
/ seg_len
248 raise Exception("Internal Error: horizontal_target_length={}"
249 " is not in range {} to {}"
250 .format(target_length
, accum_uvlens
[0],
256 # get horizontal differential of UV no influenced
257 def _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
):
258 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
259 h_uv
= loop_seqs
[-1][0][1][uv_layer
].uv
.copy() - base_uv
261 return hidx
* h_uv
/ len(loop_seqs
)
264 # get vertical differential of UV no influenced
265 def _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
):
266 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
267 v_uv
= loop_seqs
[0][-1][0][uv_layer
].uv
.copy() - base_uv
269 hseq
= loop_seqs
[hidx
]
270 return int((vidx
+ 1) / 2) * v_uv
/ (len(hseq
) / 2)
273 @PropertyClassRegistry()
278 def init_props(cls
, scene
):
279 scene
.muv_align_uv_enabled
= BoolProperty(
280 name
="Align UV Enabled",
281 description
="Align UV is enabled",
284 scene
.muv_align_uv_transmission
= BoolProperty(
286 description
="Align linked UVs",
289 scene
.muv_align_uv_select
= BoolProperty(
291 description
="Select UVs which are aligned",
294 scene
.muv_align_uv_vertical
= BoolProperty(
295 name
="Vert-Infl (Vertical)",
296 description
="Align vertical direction influenced "
297 "by mesh vertex proportion",
300 scene
.muv_align_uv_horizontal
= BoolProperty(
301 name
="Vert-Infl (Horizontal)",
302 description
="Align horizontal direction influenced "
303 "by mesh vertex proportion",
306 scene
.muv_align_uv_mesh_infl
= FloatProperty(
307 name
="Mesh Influence",
308 description
="Influence rate of mesh vertex",
313 scene
.muv_align_uv_location
= EnumProperty(
315 description
="Align location",
317 ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
318 ('MIDDLE', "Middle", "Align to middle"),
319 ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
324 scene
.muv_align_uv_snap_method
= EnumProperty(
326 description
="Snap method",
328 ('POINT', "Point", "Snap to point"),
329 ('EDGE', "Edge", "Snap to edge"),
333 scene
.muv_align_uv_snap_point_group
= EnumProperty(
334 name
="Snap Group (Point)",
335 description
="Group that snap (point) operation applies for",
337 ('VERT', "Vertex", "Vertex"),
338 ('FACE', "Face", "Face"),
339 ('UV_ISLAND', "UV Island", "UV Island"),
343 scene
.muv_align_uv_snap_point_target
= FloatVectorProperty(
344 name
="Snap Target (Point)",
345 description
="Target point where UV vertices snap to",
351 default
=(0.000, 0.000),
353 scene
.muv_align_uv_snap_edge_group
= EnumProperty(
354 name
="Snap Group (Edge)",
355 description
="Group that snap (edge) operation applies for",
357 ('EDGE', "Edge", "Edge"),
358 ('FACE', "Face", "Face"),
359 ('UV_ISLAND', "UV Island", "UV Island"),
363 scene
.muv_align_uv_snap_edge_target_1
= FloatVectorProperty(
364 name
="Snap Target (Edge)",
365 description
="Target edge where UV vertices snap to",
371 default
=(0.000, 0.000),
373 scene
.muv_align_uv_snap_edge_target_2
= FloatVectorProperty(
374 name
="Snap Target (Edge)",
375 description
="Target edge where UV vertices snap to",
381 default
=(0.000, 0.000),
385 def del_props(cls
, scene
):
386 del scene
.muv_align_uv_enabled
387 del scene
.muv_align_uv_transmission
388 del scene
.muv_align_uv_select
389 del scene
.muv_align_uv_vertical
390 del scene
.muv_align_uv_horizontal
391 del scene
.muv_align_uv_mesh_infl
392 del scene
.muv_align_uv_location
393 del scene
.muv_align_uv_snap_method
394 del scene
.muv_align_uv_snap_point_group
395 del scene
.muv_align_uv_snap_point_target
396 del scene
.muv_align_uv_snap_edge_group
397 del scene
.muv_align_uv_snap_edge_target_1
398 del scene
.muv_align_uv_snap_edge_target_2
402 @compat.make_annotations
403 class MUV_OT_AlignUV_Circle(bpy
.types
.Operator
):
405 bl_idname
= "uv.muv_align_uv_circle"
406 bl_label
= "Align UV (Circle)"
407 bl_description
= "Align UV coordinates to Circle"
408 bl_options
= {'REGISTER', 'UNDO'}
410 transmission
= BoolProperty(
412 description
="Align linked UVs",
415 select
= BoolProperty(
417 description
="Select UVs which are aligned",
422 def poll(cls
, context
):
423 # we can not get area/space/region from console
424 if common
.is_console_mode():
426 return _is_valid_context(context
)
428 def execute(self
, context
):
429 objs
= common
.get_uv_editable_objects(context
)
432 bm
= bmesh
.from_edit_mesh(obj
.data
)
433 if common
.check_version(2, 73, 0) >= 0:
434 bm
.faces
.ensure_lookup_table()
435 uv_layer
= bm
.loops
.layers
.uv
.verify()
437 # loop_seqs[horizontal][vertical][loop]
438 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
, True)
440 self
.report({'WARNING'},
441 "Object {}: {}".format(obj
.name
, error
))
444 # get circle and new UVs
445 uvs
= [hseq
[0][0][uv_layer
].uv
.copy() for hseq
in loop_seqs
]
446 c
, r
= _get_circle(uvs
[0:3])
447 new_uvs
= _calc_v_on_circle(uvs
, c
, r
)
449 # check if center is identical
450 center_is_identical
= False
451 center
= loop_seqs
[0][-1][0].vert
452 if (len(loop_seqs
[0][-1]) == 1) and \
453 loop_seqs
[0][-1][0].vert
== center
:
454 center_is_identical
= True
456 # check if topology is correct
457 if center_is_identical
:
458 for hseq
in loop_seqs
[1:]:
459 if len(hseq
[-1]) != 1:
460 self
.report({'WARNING'},
461 "Object {}: Last face must be triangle"
464 if hseq
[-1][0].vert
!= center
:
465 self
.report({'WARNING'},
466 "Object {}: Center must be identical"
470 for hseq
in loop_seqs
[1:]:
471 if len(hseq
[-1]) == 1:
472 self
.report({'WARNING'},
473 "Object {}: Last face must not be triangle"
476 if hseq
[-1][0].vert
== center
:
477 self
.report({'WARNING'},
478 "Object {}: Center must not be identical"
483 if self
.transmission
:
484 for hidx
, hseq
in enumerate(loop_seqs
):
485 for vidx
, pair
in enumerate(hseq
):
486 all_
= int((len(hseq
) + 1) / 2)
487 if center_is_identical
:
488 r
= (all_
- int((vidx
+ 1) / 2)) / all_
490 r
= (1 + all_
- int((vidx
+ 1) / 2)) / all_
491 pair
[0][uv_layer
].uv
= c
+ (new_uvs
[hidx
] - c
) * r
493 pair
[0][uv_layer
].select
= True
498 next_hidx
= (hidx
+ 1) % len(loop_seqs
)
499 pair
[1][uv_layer
].uv
= \
500 c
+ ((new_uvs
[next_hidx
]) - c
) * r
502 pair
[1][uv_layer
].select
= True
504 for hidx
, hseq
in enumerate(loop_seqs
):
506 pair
[0][uv_layer
].uv
= new_uvs
[hidx
]
507 pair
[1][uv_layer
].uv
= new_uvs
[(hidx
+ 1) % len(loop_seqs
)]
509 pair
[0][uv_layer
].select
= True
510 pair
[1][uv_layer
].select
= True
512 bmesh
.update_edit_mesh(obj
.data
)
518 @compat.make_annotations
519 class MUV_OT_AlignUV_Straighten(bpy
.types
.Operator
):
521 bl_idname
= "uv.muv_align_uv_straighten"
522 bl_label
= "Align UV (Straighten)"
523 bl_description
= "Straighten UV coordinates"
524 bl_options
= {'REGISTER', 'UNDO'}
526 transmission
= BoolProperty(
528 description
="Align linked UVs",
531 select
= BoolProperty(
533 description
="Select UVs which are aligned",
536 vertical
= BoolProperty(
537 name
="Vert-Infl (Vertical)",
538 description
="Align vertical direction influenced "
539 "by mesh vertex proportion",
542 horizontal
= BoolProperty(
543 name
="Vert-Infl (Horizontal)",
544 description
="Align horizontal direction influenced "
545 "by mesh vertex proportion",
548 mesh_infl
= FloatProperty(
549 name
="Mesh Influence",
550 description
="Influence rate of mesh vertex",
557 def poll(cls
, context
):
558 # we can not get area/space/region from console
559 if common
.is_console_mode():
561 return _is_valid_context(context
)
563 # selected and paralleled UV loop sequence will be aligned
564 def __align_w_transmission(self
, loop_seqs
, uv_layer
):
565 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
569 # hseq[vertical][loop]
570 for hidx
, hseq
in enumerate(loop_seqs
):
573 for vidx
in range(0, len(hseq
), 2):
576 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
578 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
580 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
581 hidx
, 0, self
.mesh_infl
),
582 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
583 hidx
, 1, self
.mesh_infl
),
587 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
),
588 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
+ 1),
589 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
),
590 _get_hdiff_uv(uv_layer
, loop_seqs
, hidx
+ 1)
594 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
596 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
598 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
599 hidx
, 0, self
.mesh_infl
),
600 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
601 hidx
, 1, self
.mesh_infl
),
605 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
606 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
607 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
608 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
610 diffs
.append([hdiff_uvs
, vdiff_uvs
])
611 diff_uvs
.append(diffs
)
614 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
615 for vidx
in range(0, len(hseq
), 2):
617 hseq
[vidx
][0], hseq
[vidx
][1],
618 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
620 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
621 diffs
[int(vidx
/ 2)][1]):
622 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
624 l
[uv_layer
].select
= True
626 # only selected UV loop sequence will be aligned
627 def __align_wo_transmission(self
, loop_seqs
, uv_layer
):
628 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
630 h_uv
= loop_seqs
[-1][0][1][uv_layer
].uv
.copy() - base_uv
631 for hidx
, hseq
in enumerate(loop_seqs
):
632 # only selected loop pair is targeted
634 hdiff_uv_0
= hidx
* h_uv
/ len(loop_seqs
)
635 hdiff_uv_1
= (hidx
+ 1) * h_uv
/ len(loop_seqs
)
636 pair
[0][uv_layer
].uv
= base_uv
+ hdiff_uv_0
637 pair
[1][uv_layer
].uv
= base_uv
+ hdiff_uv_1
639 pair
[0][uv_layer
].select
= True
640 pair
[1][uv_layer
].select
= True
642 def __align(self
, loop_seqs
, uv_layer
):
643 if self
.transmission
:
644 self
.__align
_w
_transmission
(loop_seqs
, uv_layer
)
646 self
.__align
_wo
_transmission
(loop_seqs
, uv_layer
)
648 def execute(self
, context
):
649 objs
= common
.get_uv_editable_objects(context
)
652 bm
= bmesh
.from_edit_mesh(obj
.data
)
653 if common
.check_version(2, 73, 0) >= 0:
654 bm
.faces
.ensure_lookup_table()
655 uv_layer
= bm
.loops
.layers
.uv
.verify()
657 # loop_seqs[horizontal][vertical][loop]
658 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
)
660 self
.report({'WARNING'},
661 "Object {}: {}".format(obj
.name
, error
))
665 self
.__align
(loop_seqs
, uv_layer
)
667 bmesh
.update_edit_mesh(obj
.data
)
673 @compat.make_annotations
674 class MUV_OT_AlignUV_Axis(bpy
.types
.Operator
):
676 bl_idname
= "uv.muv_align_uv_axis"
677 bl_label
= "Align UV (XY-Axis)"
678 bl_description
= "Align UV to XY-axis"
679 bl_options
= {'REGISTER', 'UNDO'}
681 transmission
= BoolProperty(
683 description
="Align linked UVs",
686 select
= BoolProperty(
688 description
="Select UVs which are aligned",
691 vertical
= BoolProperty(
692 name
="Vert-Infl (Vertical)",
693 description
="Align vertical direction influenced "
694 "by mesh vertex proportion",
697 horizontal
= BoolProperty(
698 name
="Vert-Infl (Horizontal)",
699 description
="Align horizontal direction influenced "
700 "by mesh vertex proportion",
703 location
= EnumProperty(
705 description
="Align location",
707 ('LEFT_TOP', "Left/Top", "Align to Left or Top"),
708 ('MIDDLE', "Middle", "Align to middle"),
709 ('RIGHT_BOTTOM', "Right/Bottom", "Align to Right or Bottom")
713 mesh_infl
= FloatProperty(
714 name
="Mesh Influence",
715 description
="Influence rate of mesh vertex",
722 def poll(cls
, context
):
723 # we can not get area/space/region from console
724 if common
.is_console_mode():
726 return _is_valid_context(context
)
729 def __get_uv_max_min(self
, loop_seqs
, uv_layer
):
730 uv_max
= Vector((-1000000.0, -1000000.0))
731 uv_min
= Vector((1000000.0, 1000000.0))
732 for hseq
in loop_seqs
:
735 uv_max
.x
= max(uv
.x
, uv_max
.x
)
736 uv_max
.y
= max(uv
.y
, uv_max
.y
)
737 uv_min
.x
= min(uv
.x
, uv_min
.x
)
738 uv_min
.y
= min(uv
.y
, uv_min
.y
)
740 return uv_max
, uv_min
742 # get UV differentiation when UVs are aligned to X-axis
743 def __get_x_axis_align_diff_uvs(self
, loop_seqs
, uv_layer
, uv_min
,
746 for hidx
, hseq
in enumerate(loop_seqs
):
748 luv0
= pair
[0][uv_layer
]
749 luv1
= pair
[1][uv_layer
]
750 target_uv0
= Vector((0.0, 0.0))
751 target_uv1
= Vector((0.0, 0.0))
752 if self
.location
== 'RIGHT_BOTTOM':
753 target_uv0
.y
= target_uv1
.y
= uv_min
.y
754 elif self
.location
== 'MIDDLE':
755 target_uv0
.y
= target_uv1
.y
= uv_min
.y
+ height
* 0.5
756 elif self
.location
== 'LEFT_TOP':
757 target_uv0
.y
= target_uv1
.y
= uv_min
.y
+ height
758 if luv0
.uv
.x
< luv1
.uv
.x
:
759 target_uv0
.x
= uv_min
.x
+ hidx
* width
/ len(loop_seqs
)
760 target_uv1
.x
= uv_min
.x
+ (hidx
+ 1) * width
/ len(loop_seqs
)
762 target_uv0
.x
= uv_min
.x
+ (hidx
+ 1) * width
/ len(loop_seqs
)
763 target_uv1
.x
= uv_min
.x
+ hidx
* width
/ len(loop_seqs
)
764 diff_uvs
.append([target_uv0
- luv0
.uv
, target_uv1
- luv1
.uv
])
768 # get UV differentiation when UVs are aligned to Y-axis
769 def __get_y_axis_align_diff_uvs(self
, loop_seqs
, uv_layer
, uv_min
,
772 for hidx
, hseq
in enumerate(loop_seqs
):
774 luv0
= pair
[0][uv_layer
]
775 luv1
= pair
[1][uv_layer
]
776 target_uv0
= Vector((0.0, 0.0))
777 target_uv1
= Vector((0.0, 0.0))
778 if self
.location
== 'RIGHT_BOTTOM':
779 target_uv0
.x
= target_uv1
.x
= uv_min
.x
+ width
780 elif self
.location
== 'MIDDLE':
781 target_uv0
.x
= target_uv1
.x
= uv_min
.x
+ width
* 0.5
782 elif self
.location
== 'LEFT_TOP':
783 target_uv0
.x
= target_uv1
.x
= uv_min
.x
784 if luv0
.uv
.y
< luv1
.uv
.y
:
785 target_uv0
.y
= uv_min
.y
+ hidx
* height
/ len(loop_seqs
)
786 target_uv1
.y
= uv_min
.y
+ (hidx
+ 1) * height
/ len(loop_seqs
)
788 target_uv0
.y
= uv_min
.y
+ (hidx
+ 1) * height
/ len(loop_seqs
)
789 target_uv1
.y
= uv_min
.y
+ hidx
* height
/ len(loop_seqs
)
790 diff_uvs
.append([target_uv0
- luv0
.uv
, target_uv1
- luv1
.uv
])
794 # only selected UV loop sequence will be aligned along to X-axis
795 def __align_to_x_axis_wo_transmission(self
, loop_seqs
, uv_layer
,
796 uv_min
, width
, height
):
797 # reverse if the UV coordinate is not sorted by position
798 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.x
> \
799 loop_seqs
[-1][0][0][uv_layer
].uv
.x
802 for hidx
, hseq
in enumerate(loop_seqs
):
803 for vidx
, pair
in enumerate(hseq
):
804 tmp
= loop_seqs
[hidx
][vidx
][0]
805 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
806 loop_seqs
[hidx
][vidx
][1] = tmp
808 # get UV differential
809 diff_uvs
= self
.__get
_x
_axis
_align
_diff
_uvs
(loop_seqs
,
814 for hseq
, duv
in zip(loop_seqs
, diff_uvs
):
816 luv0
= pair
[0][uv_layer
]
817 luv1
= pair
[1][uv_layer
]
818 luv0
.uv
= luv0
.uv
+ duv
[0]
819 luv1
.uv
= luv1
.uv
+ duv
[1]
821 # only selected UV loop sequence will be aligned along to Y-axis
822 def __align_to_y_axis_wo_transmission(self
, loop_seqs
, uv_layer
,
823 uv_min
, width
, height
):
824 # reverse if the UV coordinate is not sorted by position
825 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.y
> \
826 loop_seqs
[-1][0][0][uv_layer
].uv
.y
829 for hidx
, hseq
in enumerate(loop_seqs
):
830 for vidx
, pair
in enumerate(hseq
):
831 tmp
= loop_seqs
[hidx
][vidx
][0]
832 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
833 loop_seqs
[hidx
][vidx
][1] = tmp
835 # get UV differential
836 diff_uvs
= self
.__get
_y
_axis
_align
_diff
_uvs
(loop_seqs
,
841 for hseq
, duv
in zip(loop_seqs
, diff_uvs
):
843 luv0
= pair
[0][uv_layer
]
844 luv1
= pair
[1][uv_layer
]
845 luv0
.uv
= luv0
.uv
+ duv
[0]
846 luv1
.uv
= luv1
.uv
+ duv
[1]
848 # selected and paralleled UV loop sequence will be aligned along to X-axis
849 def __align_to_x_axis_w_transmission(self
, loop_seqs
, uv_layer
,
850 uv_min
, width
, height
):
851 # reverse if the UV coordinate is not sorted by position
852 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.x
> \
853 loop_seqs
[-1][0][0][uv_layer
].uv
.x
856 for hidx
, hseq
in enumerate(loop_seqs
):
857 for vidx
in range(len(hseq
)):
858 tmp
= loop_seqs
[hidx
][vidx
][0]
859 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
860 loop_seqs
[hidx
][vidx
][1] = tmp
862 # get offset UVs when the UVs are aligned to X-axis
863 align_diff_uvs
= self
.__get
_x
_axis
_align
_diff
_uvs
(loop_seqs
,
866 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
868 for hseq
, aduv
in zip(loop_seqs
, align_diff_uvs
):
869 luv0
= hseq
[0][0][uv_layer
]
870 luv1
= hseq
[0][1][uv_layer
]
871 offset_uvs
.append([luv0
.uv
+ aduv
[0] - base_uv
,
872 luv1
.uv
+ aduv
[1] - base_uv
])
874 # get UV differential
876 # hseq[vertical][loop]
877 for hidx
, hseq
in enumerate(loop_seqs
):
880 for vidx
in range(0, len(hseq
), 2):
883 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
885 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
887 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
888 hidx
, 0, self
.mesh_infl
),
889 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
890 hidx
, 1, self
.mesh_infl
),
892 hdiff_uvs
[0].y
= hdiff_uvs
[0].y
+ offset_uvs
[hidx
][0].y
893 hdiff_uvs
[1].y
= hdiff_uvs
[1].y
+ offset_uvs
[hidx
][1].y
894 hdiff_uvs
[2].y
= hdiff_uvs
[2].y
+ offset_uvs
[hidx
][0].y
895 hdiff_uvs
[3].y
= hdiff_uvs
[3].y
+ offset_uvs
[hidx
][1].y
905 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
907 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
909 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
910 hidx
, 0, self
.mesh_infl
),
911 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
912 hidx
, 1, self
.mesh_infl
),
916 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
917 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
918 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
919 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
921 diffs
.append([hdiff_uvs
, vdiff_uvs
])
922 diff_uvs
.append(diffs
)
925 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
926 for vidx
in range(0, len(hseq
), 2):
928 hseq
[vidx
][0], hseq
[vidx
][1],
929 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
931 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
932 diffs
[int(vidx
/ 2)][1]):
933 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
935 l
[uv_layer
].select
= True
937 # selected and paralleled UV loop sequence will be aligned along to Y-axis
938 def __align_to_y_axis_w_transmission(self
, loop_seqs
, uv_layer
,
939 uv_min
, width
, height
):
940 # reverse if the UV coordinate is not sorted by position
941 need_revese
= loop_seqs
[0][0][0][uv_layer
].uv
.y
> \
942 loop_seqs
[-1][0][-1][uv_layer
].uv
.y
945 for hidx
, hseq
in enumerate(loop_seqs
):
946 for vidx
in range(len(hseq
)):
947 tmp
= loop_seqs
[hidx
][vidx
][0]
948 loop_seqs
[hidx
][vidx
][0] = loop_seqs
[hidx
][vidx
][1]
949 loop_seqs
[hidx
][vidx
][1] = tmp
951 # get offset UVs when the UVs are aligned to Y-axis
952 align_diff_uvs
= self
.__get
_y
_axis
_align
_diff
_uvs
(loop_seqs
,
955 base_uv
= loop_seqs
[0][0][0][uv_layer
].uv
.copy()
957 for hseq
, aduv
in zip(loop_seqs
, align_diff_uvs
):
958 luv0
= hseq
[0][0][uv_layer
]
959 luv1
= hseq
[0][1][uv_layer
]
960 offset_uvs
.append([luv0
.uv
+ aduv
[0] - base_uv
,
961 luv1
.uv
+ aduv
[1] - base_uv
])
963 # get UV differential
965 # hseq[vertical][loop]
966 for hidx
, hseq
in enumerate(loop_seqs
):
969 for vidx
in range(0, len(hseq
), 2):
972 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
974 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
976 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
977 hidx
, 0, self
.mesh_infl
),
978 _get_hdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
979 hidx
, 1, self
.mesh_infl
),
981 hdiff_uvs
[0].x
= hdiff_uvs
[0].x
+ offset_uvs
[hidx
][0].x
982 hdiff_uvs
[1].x
= hdiff_uvs
[1].x
+ offset_uvs
[hidx
][1].x
983 hdiff_uvs
[2].x
= hdiff_uvs
[2].x
+ offset_uvs
[hidx
][0].x
984 hdiff_uvs
[3].x
= hdiff_uvs
[3].x
+ offset_uvs
[hidx
][1].x
994 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 0,
996 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
, hidx
, 1,
998 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
999 hidx
, 0, self
.mesh_infl
),
1000 _get_vdiff_uv_vinfl(uv_layer
, loop_seqs
, vidx
+ 1,
1001 hidx
, 1, self
.mesh_infl
),
1005 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
1006 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
, hidx
),
1007 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
),
1008 _get_vdiff_uv(uv_layer
, loop_seqs
, vidx
+ 1, hidx
)
1010 diffs
.append([hdiff_uvs
, vdiff_uvs
])
1011 diff_uvs
.append(diffs
)
1014 for hseq
, diffs
in zip(loop_seqs
, diff_uvs
):
1015 for vidx
in range(0, len(hseq
), 2):
1017 hseq
[vidx
][0], hseq
[vidx
][1],
1018 hseq
[vidx
+ 1][0], hseq
[vidx
+ 1][1]
1020 for l
, hdiff
, vdiff
in zip(loops
, diffs
[int(vidx
/ 2)][0],
1021 diffs
[int(vidx
/ 2)][1]):
1022 l
[uv_layer
].uv
= base_uv
+ hdiff
+ vdiff
1024 l
[uv_layer
].select
= True
1026 def __align(self
, loop_seqs
, uv_layer
, uv_min
, width
, height
):
1027 # align along to x-axis
1029 if self
.transmission
:
1030 self
.__align
_to
_x
_axis
_w
_transmission
(loop_seqs
,
1034 self
.__align
_to
_x
_axis
_wo
_transmission
(loop_seqs
,
1037 # align along to y-axis
1039 if self
.transmission
:
1040 self
.__align
_to
_y
_axis
_w
_transmission
(loop_seqs
,
1044 self
.__align
_to
_y
_axis
_wo
_transmission
(loop_seqs
,
1048 def execute(self
, context
):
1049 objs
= common
.get_uv_editable_objects(context
)
1052 bm
= bmesh
.from_edit_mesh(obj
.data
)
1053 if common
.check_version(2, 73, 0) >= 0:
1054 bm
.faces
.ensure_lookup_table()
1055 uv_layer
= bm
.loops
.layers
.uv
.verify()
1057 # loop_seqs[horizontal][vertical][loop]
1058 loop_seqs
, error
= common
.get_loop_sequences(bm
, uv_layer
)
1060 self
.report({'WARNING'},
1061 "Object {}: {}".format(obj
.name
, error
))
1062 return {'CANCELLED'}
1064 # get height and width
1065 uv_max
, uv_min
= self
.__get
_uv
_max
_min
(loop_seqs
, uv_layer
)
1066 width
= uv_max
.x
- uv_min
.x
1067 height
= uv_max
.y
- uv_min
.y
1069 self
.__align
(loop_seqs
, uv_layer
, uv_min
, width
, height
)
1071 bmesh
.update_edit_mesh(obj
.data
)
1077 @compat.make_annotations
1078 class MUV_OT_AlignUV_SnapToPoint(bpy
.types
.Operator
):
1080 bl_idname
= "uv.muv_align_uv_snap_to_point"
1081 bl_label
= "Align UV (Snap to Point)"
1082 bl_description
= "Align UV to the target point"
1083 bl_options
= {'REGISTER', 'UNDO'}
1085 group
= EnumProperty(
1087 description
="Group that snap operation applies for",
1089 ('VERT', "Vertex", "Vertex"),
1090 ('FACE', "Face", "Face"),
1091 ('UV_ISLAND', "UV Island", "UV Island"),
1095 target
= FloatVectorProperty(
1097 description
="Target where UV vertices snap to",
1103 default
=(0.000, 0.000),
1106 def _get_snap_target_loops(self
, context
, bm
, uv_layer
):
1109 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1111 # Process snap operation.
1112 for face
in selected_faces
:
1113 for l
in face
.loops
:
1114 if context
.tool_settings
.use_uv_select_sync
or \
1116 target_loops
.append(l
)
1120 def _get_snap_target_faces(self
, context
, bm
, uv_layer
):
1123 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1125 for face
in selected_faces
:
1126 for l
in face
.loops
:
1127 if not context
.tool_settings
.use_uv_select_sync
and \
1128 not l
[uv_layer
].select
:
1131 target_faces
.append(face
)
1135 def _get_snap_target_islands(self
, context
, bm
, uv_layer
):
1138 islands
= common
.get_island_info_from_bmesh(bm
, only_selected
=True)
1141 some_verts_not_selected
= False
1142 for face
in isl
["faces"]:
1143 for l
in face
["face"].loops
:
1144 if not context
.tool_settings
.use_uv_select_sync
and \
1145 not l
[uv_layer
].select
:
1146 some_verts_not_selected
= True
1148 if not some_verts_not_selected
:
1149 target_islands
.append(isl
)
1151 return target_islands
1153 def execute(self
, context
):
1154 objs
= common
.get_uv_editable_objects(context
)
1159 'UV_ISLAND': "UV Island",
1161 no_selection_reason
= group_to_reason
[self
.group
]
1164 bm
= bmesh
.from_edit_mesh(obj
.data
)
1165 if common
.check_version(2, 73, 0) >= 0:
1166 bm
.faces
.ensure_lookup_table()
1167 uv_layer
= bm
.loops
.layers
.uv
.verify()
1169 if self
.group
== 'VERT':
1171 self
._get
_snap
_target
_loops
(context
, bm
, uv_layer
)
1173 # Process snap operation.
1174 for l
in target_loops
:
1175 l
[uv_layer
].uv
= self
.target
1176 no_selection_reason
= None
1178 elif self
.group
== 'FACE':
1180 self
._get
_snap
_target
_faces
(context
, bm
, uv_layer
)
1182 for face
in target_faces
:
1183 ave_uv
= Vector((0.0, 0.0))
1184 for l
in face
.loops
:
1185 ave_uv
+= l
[uv_layer
].uv
1186 ave_uv
/= len(face
.loops
)
1187 diff
= Vector(self
.target
) - ave_uv
1189 # Process snap operation.
1190 for l
in face
.loops
:
1191 l
[uv_layer
].uv
+= diff
1192 no_selection_reason
= None
1194 elif self
.group
== 'UV_ISLAND':
1196 self
._get
_snap
_target
_islands
(context
, bm
, uv_layer
)
1198 for isl
in target_islands
:
1199 ave_uv
= Vector((0.0, 0.0))
1201 for face
in isl
["faces"]:
1202 for l
in face
["face"].loops
:
1203 ave_uv
+= l
[uv_layer
].uv
1207 diff
= Vector(self
.target
) - ave_uv
1209 # Process snap operation.
1210 for face
in isl
["faces"]:
1211 for l
in face
["face"].loops
:
1212 l
[uv_layer
].uv
+= diff
1213 no_selection_reason
= None
1215 bmesh
.update_edit_mesh(obj
.data
)
1217 if no_selection_reason
:
1220 "Must select more than 1 {}.".format(no_selection_reason
)
1222 return {'CANCELLED'}
1228 @compat.make_annotations
1229 class MUV_OT_AlignUV_Snap_SetPointTargetToCursor(bpy
.types
.Operator
):
1231 bl_idname
= "uv.muv_align_uv_snap_set_point_target_to_cursor"
1232 bl_label
= "Set Point Target to Cursor"
1233 bl_description
= """Set point target to the cursor for
1234 'Align UV (Snap to Point)'"""
1235 bl_options
= {'REGISTER', 'UNDO'}
1237 def execute(self
, context
):
1240 _
, _
, space
= common
.get_space('IMAGE_EDITOR', 'WINDOW',
1242 cursor_loc
= space
.cursor_location
1244 sc
.muv_align_uv_snap_point_target
= cursor_loc
1250 @compat.make_annotations
1251 class MUV_OT_AlignUV_Snap_SetPointTargetToVertexGroup(bpy
.types
.Operator
):
1253 bl_idname
= "uv.muv_align_uv_snap_set_point_target_to_vertex_group"
1254 bl_label
= "Set Point Target to Vertex Group"
1255 bl_description
= """Set point target to the average of vertices for
1256 'Align UV (Snap to Point)'"""
1257 bl_options
= {'REGISTER', 'UNDO'}
1259 def execute(self
, context
):
1261 objs
= common
.get_uv_editable_objects(context
)
1263 ave_uv
= Vector((0.0, 0.0))
1266 bm
= bmesh
.from_edit_mesh(obj
.data
)
1267 if common
.check_version(2, 73, 0) >= 0:
1268 bm
.faces
.ensure_lookup_table()
1269 uv_layer
= bm
.loops
.layers
.uv
.verify()
1271 selected_faces
= [f
for f
in bm
.faces
if f
.select
]
1272 for face
in selected_faces
:
1273 for l
in face
.loops
:
1274 if context
.tool_settings
.use_uv_select_sync
or \
1276 ave_uv
+= l
[uv_layer
].uv
1281 sc
.muv_align_uv_snap_point_target
= ave_uv
1287 @compat.make_annotations
1288 class MUV_OT_AlignUV_SnapToEdge(bpy
.types
.Operator
):
1290 bl_idname
= "uv.muv_align_uv_snap_to_edge"
1291 bl_label
= "Align UV (Snap to Edge)"
1292 bl_description
= "Align UV to the target edge"
1293 bl_options
= {'REGISTER', 'UNDO'}
1295 group
= EnumProperty(
1297 description
="Group that snap operation applies for",
1299 ('EDGE', "Edge", "Edge"),
1300 ('FACE', "Face", "Face"),
1301 ('UV_ISLAND', "UV Island", "UV Island"),
1305 target_1
= FloatVectorProperty(
1306 name
="Snap Target 1",
1307 description
="Vertex 1 of the target edge",
1313 default
=(0.000, 0.000),
1315 target_2
= FloatVectorProperty(
1316 name
="Snap Target 2",
1317 description
="Vertex 2 of the target edge",
1323 default
=(0.000, 0.000),
1326 def _calc_snap_move_amount(self
, loops
, uv_layer
):
1327 ave
= (loops
[0][uv_layer
].uv
+ loops
[1][uv_layer
].uv
) / 2
1328 target
= (Vector(self
.target_1
) + Vector(self
.target_2
)) / 2
1332 def _get_snap_target_loop_pairs(self
, bm
, uv_layer
):
1333 target_loop_pairs
= []
1335 selected_edges
= [e
for e
in bm
.edges
if e
.select
]
1338 for edge
in selected_edges
:
1339 for l
in edge
.link_loops
:
1340 if l
[uv_layer
].select
:
1341 cand_loops
.append(l
)
1343 for l
in cand_loops
:
1344 if l
[uv_layer
].select
and l
.link_loop_next
[uv_layer
].select
:
1345 d
= {l
, l
.link_loop_next
}
1346 if d
not in target_loop_pairs
:
1347 assert l
.face
== l
.link_loop_next
.face
1348 target_loop_pairs
.append(d
)
1350 return target_loop_pairs
1352 def _find_target_island_from_face(self
, islands
, face
):
1354 for f
in isl
["faces"]:
1355 if f
["face"] == face
:
1360 def execute(self
, context
):
1361 objs
= common
.get_uv_editable_objects(context
)
1365 bm
= bmesh
.from_edit_mesh(obj
.data
)
1366 if common
.check_version(2, 73, 0) >= 0:
1367 bm
.faces
.ensure_lookup_table()
1368 uv_layer
= bm
.loops
.layers
.uv
.verify()
1370 if self
.group
== 'EDGE':
1371 target_loop_pairs
= \
1372 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1374 for pair
in target_loop_pairs
:
1376 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1378 # Process snap operation.
1379 p
[0][uv_layer
].uv
+= diff
1380 p
[1][uv_layer
].uv
+= diff
1382 no_selection
= False
1384 elif self
.group
== 'FACE':
1385 target_loop_pairs
= \
1386 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1389 for pair
in target_loop_pairs
:
1391 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1393 # Process snap operation.
1395 if face
in face_processed
:
1398 "Must select only one edge per face. (Object: {})"
1401 return {'CANCELLED'}
1402 face_processed
.append(face
)
1403 for l
in face
.loops
:
1404 l
[uv_layer
].uv
+= diff
1405 no_selection
= False
1407 elif self
.group
== 'UV_ISLAND':
1408 target_loop_pairs
= \
1409 self
._get
_snap
_target
_loop
_pairs
(bm
, uv_layer
)
1411 islands
= common
.get_island_info_from_bmesh(
1412 bm
, only_selected
=False)
1415 for pair
in target_loop_pairs
:
1417 diff
= self
._calc
_snap
_move
_amount
(p
, uv_layer
)
1419 # Find island to process.
1422 self
._find
_target
_island
_from
_face
(islands
, face
)
1423 if target_isl
is None:
1426 "Failed to find island. (Object: {})"
1429 return {'CANCELLED'}
1430 if target_isl
in isl_processed
:
1433 """Must select only one edge per island.
1437 return {'CANCELLED'}
1438 isl_processed
.append(target_isl
)
1440 # Process snap operation.
1441 for f
in target_isl
["faces"]:
1442 for l
in f
["face"].loops
:
1443 l
[uv_layer
].uv
+= diff
1444 no_selection
= False
1446 bmesh
.update_edit_mesh(obj
.data
)
1449 self
.report({'WARNING'}, "Must select more than 1 Edge.")
1450 return {'CANCELLED'}
1456 @compat.make_annotations
1457 class MUV_OT_AlignUV_Snap_SetEdgeTargetToEdgeCenter(bpy
.types
.Operator
):
1459 bl_idname
= "uv.muv_align_uv_snap_set_edge_target_to_edge_center"
1460 bl_label
= "Set Edge Target to Edge Center"
1461 bl_description
= """Set edge target to the center of edge for
1462 'Align UV (Snap to Edge)'"""
1463 bl_options
= {'REGISTER', 'UNDO'}
1465 def _get_target_loop_pairs(self
, bm
, uv_layer
):
1466 target_loop_pairs
= []
1468 selected_edges
= [e
for e
in bm
.edges
if e
.select
]
1471 for edge
in selected_edges
:
1472 for l
in edge
.link_loops
:
1473 if l
[uv_layer
].select
:
1474 cand_loops
.append(l
)
1476 for l
in cand_loops
:
1477 if l
[uv_layer
].select
and l
.link_loop_next
[uv_layer
].select
:
1478 d
= {l
, l
.link_loop_next
}
1479 if d
not in target_loop_pairs
:
1480 assert l
.face
== l
.link_loop_next
.face
1481 target_loop_pairs
.append(d
)
1483 return target_loop_pairs
1485 def execute(self
, context
):
1487 objs
= common
.get_uv_editable_objects(context
)
1489 ave_uv_1
= Vector((0.0, 0.0))
1490 ave_uv_2
= Vector((0.0, 0.0))
1493 bm
= bmesh
.from_edit_mesh(obj
.data
)
1494 if common
.check_version(2, 73, 0) >= 0:
1495 bm
.faces
.ensure_lookup_table()
1496 uv_layer
= bm
.loops
.layers
.uv
.verify()
1498 target_loop_pairs
= self
._get
_target
_loop
_pairs
(bm
, uv_layer
)
1499 for pair
in target_loop_pairs
:
1501 uv_1
= p
[0][uv_layer
].uv
1502 uv_2
= p
[1][uv_layer
].uv
1511 sc
.muv_align_uv_snap_edge_target_1
= ave_uv_1
1512 sc
.muv_align_uv_snap_edge_target_2
= ave_uv_2