1 # SPDX-License-Identifier: GPL-2.0-or-later
3 __author__
= "Nutti <nutti.metro@gmail.com>"
4 __status__
= "production"
6 __date__
= "22 Apr 2022"
11 from bpy
.props
import (
18 from mathutils
import Vector
20 from ..utils
.bl_class_registry
import BlClassRegistry
21 from ..utils
.property_class_registry
import PropertyClassRegistry
22 from ..utils
.graph
import graph_is_isomorphic
23 from ..utils
import compatibility
as compat
27 def _is_valid_context(context
):
28 # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
29 # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
31 if not common
.is_valid_space(context
, ['IMAGE_EDITOR', 'VIEW_3D']):
34 objs
= common
.get_uv_editable_objects(context
)
38 # only edit mode is allowed to execute
39 if context
.object.mode
!= 'EDIT':
45 def _sort_island_faces(kd
, uvs
, isl1
, isl2
):
51 for f
in isl1
['sorted']:
53 Vector((f
['ave_uv'].x
, f
['ave_uv'].y
, 0.0)))
54 sorted_faces
.append(isl2
['faces'][uvs
[idx
]['face_idx']])
58 def _group_island(island_info
, allowable_center_deviation
,
59 allowable_size_deviation
):
66 # search islands which is not parsed yet
68 for isl_1
in island_info
:
69 if isl_1
['group'] == -1:
72 break # all faces are parsed
75 isl_1
['group'] = num_group
76 isl_1
['sorted'] = isl_1
['faces']
79 for isl_2
in island_info
:
80 if isl_2
['group'] == -1:
81 dcx
= isl_2
['center'].x
- isl_1
['center'].x
82 dcy
= isl_2
['center'].y
- isl_1
['center'].y
83 dsx
= isl_2
['size'].x
- isl_1
['size'].x
84 dsy
= isl_2
['size'].y
- isl_1
['size'].y
86 fabs(dcx
) < allowable_center_deviation
[0]
89 fabs(dcy
) < allowable_center_deviation
[1]
92 fabs(dsx
) < allowable_size_deviation
[0]
95 fabs(dsy
) < allowable_size_deviation
[1]
97 center_matched
= center_x_matched
and center_y_matched
98 size_matched
= size_x_matched
and size_y_matched
99 num_uv_matched
= (isl_2
['num_uv'] == isl_1
['num_uv'])
100 # are islands have same?
101 if center_matched
and size_matched
and num_uv_matched
:
102 isl_2
['group'] = num_group
103 kd
= mathutils
.kdtree
.KDTree(len(isl_2
['faces']))
107 (f
['ave_uv'].x
, f
['ave_uv'].y
, 0.0)
110 } for fidx
, f
in enumerate(isl_2
['faces'])
112 for i
, uv
in enumerate(uvs
):
113 kd
.insert(uv
['uv'], i
)
115 # sort faces for copy/paste UV
116 isl_2
['sorted'] = _sort_island_faces(kd
, uvs
, isl_1
, isl_2
)
117 num_group
= num_group
+ 1
122 @PropertyClassRegistry()
127 def init_props(cls
, scene
):
128 scene
.muv_pack_uv_enabled
= BoolProperty(
129 name
="Pack UV Enabled",
130 description
="Pack UV is enabled",
133 scene
.muv_pack_uv_allowable_center_deviation
= FloatVectorProperty(
134 name
="Allowable Center Deviation",
135 description
="Allowable center deviation to judge same UV island",
138 default
=(0.001, 0.001),
142 scene
.muv_pack_uv_allowable_size_deviation
= FloatVectorProperty(
143 name
="Allowable Size Deviation",
144 description
="Allowable sizes deviation to judge same UV island",
147 default
=(0.001, 0.001),
151 scene
.muv_pack_uv_accurate_island_copy
= BoolProperty(
152 name
="Accurate Island Copy",
153 description
="Copy islands topologically",
156 scene
.muv_pack_uv_stride
= FloatVectorProperty(
158 description
="Stride UV coordinates",
165 scene
.muv_pack_uv_apply_pack_uv
= BoolProperty(
166 name
="Apply Pack UV",
167 description
="Apply Pack UV operation intrinsic to Blender itself",
172 def del_props(cls
, scene
):
173 del scene
.muv_pack_uv_enabled
174 del scene
.muv_pack_uv_allowable_center_deviation
175 del scene
.muv_pack_uv_allowable_size_deviation
176 del scene
.muv_pack_uv_accurate_island_copy
177 del scene
.muv_pack_uv_stride
178 del scene
.muv_pack_uv_apply_pack_uv
182 @compat.make_annotations
183 class MUV_OT_PackUV(bpy
.types
.Operator
):
185 Operation class: Pack UV with same UV islands are integrated
186 Island matching algorithm
187 - Same center of UV island
188 - Same size of UV island
192 bl_idname
= "uv.muv_pack_uv"
194 bl_description
= "Pack UV (Same UV Islands are integrated)"
195 bl_options
= {'REGISTER', 'UNDO'}
197 rotate
= BoolProperty(
199 description
="Rotate option used by default pack UV function",
201 margin
= FloatProperty(
203 description
="Margin used by default pack UV function",
208 allowable_center_deviation
= FloatVectorProperty(
209 name
="Allowable Center Deviation",
210 description
="Allowable center deviation to judge same UV island",
213 default
=(0.001, 0.001),
217 allowable_size_deviation
= FloatVectorProperty(
218 name
="Allowable Size Deviation",
219 description
="Allowable sizse deviation to judge same UV island",
222 default
=(0.001, 0.001),
226 accurate_island_copy
= BoolProperty(
227 name
="Accurate Island Copy",
228 description
="Copy islands topologically",
231 stride
= FloatVectorProperty(
233 description
="Stride UV coordinates",
240 apply_pack_uv
= BoolProperty(
241 name
="Apply Pack UV",
242 description
="Apply Pack UV operation intrinsic to Blender itself",
247 def poll(cls
, context
):
248 # we can not get area/space/region from console
249 if common
.is_console_mode():
251 return _is_valid_context(context
)
253 def execute(self
, context
):
254 objs
= common
.get_uv_editable_objects(context
)
259 island_to_uv_layer
= {}
260 bm_to_loop_lists
= {}
262 bm
= bmesh
.from_edit_mesh(obj
.data
)
263 if common
.check_version(2, 73, 0) >= 0:
264 bm
.faces
.ensure_lookup_table()
265 if not bm
.loops
.layers
.uv
:
266 self
.report({'WARNING'},
267 "Object {} must have more than one UV map"
270 uv_layer
= bm
.loops
.layers
.uv
.verify()
272 selected_faces
.extend([f
for f
in bm
.faces
if f
.select
])
273 isl
= common
.get_island_info(obj
)
274 for i
, info
in enumerate(isl
):
275 id_
= i
+ len(island_info
)
276 island_to_bm
[id_
] = bm
277 island_to_uv_layer
[id_
] = uv_layer
279 island_info
.extend(isl
)
280 bm_to_loop_lists
[bm
] = [l
for f
in bm
.faces
for l
in f
.loops
]
282 num_group
= _group_island(island_info
,
283 self
.allowable_center_deviation
,
284 self
.allowable_size_deviation
)
285 bpy
.ops
.mesh
.select_all(action
='DESELECT')
288 for gidx
in range(num_group
):
290 lambda i
, idx
=gidx
: i
['group'] == idx
, island_info
))
291 for f
in group
[0]['faces']:
292 f
['face'].select
= True
294 bmesh
.update_edit_mesh(obj
.data
)
295 bpy
.ops
.uv
.select_all(action
='SELECT')
296 if self
.apply_pack_uv
:
297 bpy
.ops
.uv
.pack_islands(rotate
=self
.rotate
, margin
=self
.margin
)
299 # copy/paste UV among same islands
300 for gidx
in range(num_group
):
302 lambda i
, idx
=gidx
: i
['group'] == idx
, island_info
))
305 src_bm
= island_to_bm
[group
[0]["id"]]
306 src_uv_layer
= island_to_uv_layer
[group
[0]["id"]]
307 src_loop_lists
= bm_to_loop_lists
[src_bm
]
310 for f
in group
[0]["faces"]:
311 for l
in f
["face"].loops
:
314 src_uv_graph
= common
.create_uv_graph(src_loops
, src_uv_layer
)
316 for stride_idx
, g
in enumerate(group
[1:]):
317 dst_bm
= island_to_bm
[g
["id"]]
318 dst_uv_layer
= island_to_uv_layer
[g
["id"]]
319 dst_loop_lists
= bm_to_loop_lists
[dst_bm
]
323 for l
in f
["face"].loops
:
326 dst_uv_graph
= common
.create_uv_graph(dst_loops
, dst_uv_layer
)
328 uv_stride
= Vector(((stride_idx
+ 1) * self
.stride
.x
,
329 (stride_idx
+ 1) * self
.stride
.y
))
330 if self
.accurate_island_copy
:
331 # Check if the graph is isomorphic.
332 # If the graph is isomorphic, matching pair is returned.
333 result
, pairs
= graph_is_isomorphic(
334 src_uv_graph
, dst_uv_graph
)
338 "Island does not match. "
339 "Disable 'Accurate Island Copy' and try again")
343 for n1
, n2
in pairs
.items():
344 uv1
= n1
.value
["uv_vert"][src_uv_layer
].uv
345 l2
= n2
.value
["loops"]
347 l
[dst_uv_layer
].uv
= uv1
+ uv_stride
349 for (src_face
, dest_face
) in zip(
350 group
[0]['sorted'], g
['sorted']):
351 for (src_loop
, dest_loop
) in zip(
352 src_face
['face'].loops
,
353 dest_face
['face'].loops
):
354 src_lidx
= src_loop
.index
355 dst_lidx
= dest_loop
.index
356 dst_loop_lists
[dst_lidx
][dst_uv_layer
].uv
= \
357 src_loop_lists
[src_lidx
][src_uv_layer
].uv
+ \
360 # restore face/UV selection
361 bpy
.ops
.uv
.select_all(action
='DESELECT')
362 bpy
.ops
.mesh
.select_all(action
='DESELECT')
363 for f
in selected_faces
:
365 bpy
.ops
.uv
.select_all(action
='SELECT')
368 bmesh
.update_edit_mesh(obj
.data
)