expre-gen/blender/3.4/scripts/addons/magic_uv/op/pack_uv.py
2025-10-17 09:47:23 +08:00

371 lines
12 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
__version__ = "6.6"
__date__ = "22 Apr 2022"
from math import fabs
import bpy
from bpy.props import (
FloatProperty,
FloatVectorProperty,
BoolProperty,
)
import bmesh
import mathutils
from mathutils import Vector
from ..utils.bl_class_registry import BlClassRegistry
from ..utils.property_class_registry import PropertyClassRegistry
from ..utils.graph import graph_is_isomorphic
from ..utils import compatibility as compat
from .. import common
def _is_valid_context(context):
# 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
# If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
# after the execution
if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
objs = common.get_uv_editable_objects(context)
if not objs:
return False
# only edit mode is allowed to execute
if context.object.mode != 'EDIT':
return False
return True
def _sort_island_faces(kd, uvs, isl1, isl2):
"""
Sort faces in island
"""
sorted_faces = []
for f in isl1['sorted']:
_, idx, _ = kd.find(
Vector((f['ave_uv'].x, f['ave_uv'].y, 0.0)))
sorted_faces.append(isl2['faces'][uvs[idx]['face_idx']])
return sorted_faces
def _group_island(island_info, allowable_center_deviation,
allowable_size_deviation):
"""
Group island
"""
num_group = 0
while True:
# search islands which is not parsed yet
isl_1 = None
for isl_1 in island_info:
if isl_1['group'] == -1:
break
else:
break # all faces are parsed
if isl_1 is None:
break
isl_1['group'] = num_group
isl_1['sorted'] = isl_1['faces']
# search same island
for isl_2 in island_info:
if isl_2['group'] == -1:
dcx = isl_2['center'].x - isl_1['center'].x
dcy = isl_2['center'].y - isl_1['center'].y
dsx = isl_2['size'].x - isl_1['size'].x
dsy = isl_2['size'].y - isl_1['size'].y
center_x_matched = (
fabs(dcx) < allowable_center_deviation[0]
)
center_y_matched = (
fabs(dcy) < allowable_center_deviation[1]
)
size_x_matched = (
fabs(dsx) < allowable_size_deviation[0]
)
size_y_matched = (
fabs(dsy) < allowable_size_deviation[1]
)
center_matched = center_x_matched and center_y_matched
size_matched = size_x_matched and size_y_matched
num_uv_matched = (isl_2['num_uv'] == isl_1['num_uv'])
# are islands have same?
if center_matched and size_matched and num_uv_matched:
isl_2['group'] = num_group
kd = mathutils.kdtree.KDTree(len(isl_2['faces']))
uvs = [
{
'uv': Vector(
(f['ave_uv'].x, f['ave_uv'].y, 0.0)
),
'face_idx': fidx
} for fidx, f in enumerate(isl_2['faces'])
]
for i, uv in enumerate(uvs):
kd.insert(uv['uv'], i)
kd.balance()
# sort faces for copy/paste UV
isl_2['sorted'] = _sort_island_faces(kd, uvs, isl_1, isl_2)
num_group = num_group + 1
return num_group
@PropertyClassRegistry()
class _Properties:
idname = "pack_uv"
@classmethod
def init_props(cls, scene):
scene.muv_pack_uv_enabled = BoolProperty(
name="Pack UV Enabled",
description="Pack UV is enabled",
default=False
)
scene.muv_pack_uv_allowable_center_deviation = FloatVectorProperty(
name="Allowable Center Deviation",
description="Allowable center deviation to judge same UV island",
min=0.000001,
max=10.0,
default=(0.001, 0.001),
size=2,
subtype='XYZ'
)
scene.muv_pack_uv_allowable_size_deviation = FloatVectorProperty(
name="Allowable Size Deviation",
description="Allowable sizes deviation to judge same UV island",
min=0.000001,
max=10.0,
default=(0.001, 0.001),
size=2,
subtype='XYZ'
)
scene.muv_pack_uv_accurate_island_copy = BoolProperty(
name="Accurate Island Copy",
description="Copy islands topologically",
default=True
)
scene.muv_pack_uv_stride = FloatVectorProperty(
name="Stride",
description="Stride UV coordinates",
min=-100.0,
max=100.0,
default=(0.0, 0.0),
size=2,
subtype='XYZ'
)
scene.muv_pack_uv_apply_pack_uv = BoolProperty(
name="Apply Pack UV",
description="Apply Pack UV operation intrinsic to Blender itself",
default=True
)
@classmethod
def del_props(cls, scene):
del scene.muv_pack_uv_enabled
del scene.muv_pack_uv_allowable_center_deviation
del scene.muv_pack_uv_allowable_size_deviation
del scene.muv_pack_uv_accurate_island_copy
del scene.muv_pack_uv_stride
del scene.muv_pack_uv_apply_pack_uv
@BlClassRegistry()
@compat.make_annotations
class MUV_OT_PackUV(bpy.types.Operator):
"""
Operation class: Pack UV with same UV islands are integrated
Island matching algorithm
- Same center of UV island
- Same size of UV island
- Same number of UV
"""
bl_idname = "uv.muv_pack_uv"
bl_label = "Pack UV"
bl_description = "Pack UV (Same UV Islands are integrated)"
bl_options = {'REGISTER', 'UNDO'}
rotate = BoolProperty(
name="Rotate",
description="Rotate option used by default pack UV function",
default=False)
margin = FloatProperty(
name="Margin",
description="Margin used by default pack UV function",
min=0,
max=1,
default=0.001
)
allowable_center_deviation = FloatVectorProperty(
name="Allowable Center Deviation",
description="Allowable center deviation to judge same UV island",
min=0.000001,
max=10.0,
default=(0.001, 0.001),
size=2,
subtype='XYZ'
)
allowable_size_deviation = FloatVectorProperty(
name="Allowable Size Deviation",
description="Allowable sizse deviation to judge same UV island",
min=0.000001,
max=10.0,
default=(0.001, 0.001),
size=2,
subtype='XYZ'
)
accurate_island_copy = BoolProperty(
name="Accurate Island Copy",
description="Copy islands topologically",
default=True
)
stride = FloatVectorProperty(
name="Stride",
description="Stride UV coordinates",
min=-100.0,
max=100.0,
default=(0.0, 0.0),
size=2,
subtype='XYZ'
)
apply_pack_uv = BoolProperty(
name="Apply Pack UV",
description="Apply Pack UV operation intrinsic to Blender itself",
default=True
)
@classmethod
def poll(cls, context):
# we can not get area/space/region from console
if common.is_console_mode():
return True
return _is_valid_context(context)
def execute(self, context):
objs = common.get_uv_editable_objects(context)
island_info = []
selected_faces = []
island_to_bm = {}
island_to_uv_layer = {}
bm_to_loop_lists = {}
for obj in objs:
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
if not bm.loops.layers.uv:
self.report({'WARNING'},
"Object {} must have more than one UV map"
.format(obj.name))
return {'CANCELLED'}
uv_layer = bm.loops.layers.uv.verify()
selected_faces.extend([f for f in bm.faces if f.select])
isl = common.get_island_info(obj)
for i, info in enumerate(isl):
id_ = i + len(island_info)
island_to_bm[id_] = bm
island_to_uv_layer[id_] = uv_layer
info["id"] = id_
island_info.extend(isl)
bm_to_loop_lists[bm] = [l for f in bm.faces for l in f.loops]
num_group = _group_island(island_info,
self.allowable_center_deviation,
self.allowable_size_deviation)
bpy.ops.mesh.select_all(action='DESELECT')
# pack UV
for gidx in range(num_group):
group = list(filter(
lambda i, idx=gidx: i['group'] == idx, island_info))
for f in group[0]['faces']:
f['face'].select = True
for obj in objs:
bmesh.update_edit_mesh(obj.data)
bpy.ops.uv.select_all(action='SELECT')
if self.apply_pack_uv:
bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin)
# copy/paste UV among same islands
for gidx in range(num_group):
group = list(filter(
lambda i, idx=gidx: i['group'] == idx, island_info))
if len(group) <= 1:
continue
src_bm = island_to_bm[group[0]["id"]]
src_uv_layer = island_to_uv_layer[group[0]["id"]]
src_loop_lists = bm_to_loop_lists[src_bm]
src_loops = []
for f in group[0]["faces"]:
for l in f["face"].loops:
src_loops.append(l)
src_uv_graph = common.create_uv_graph(src_loops, src_uv_layer)
for stride_idx, g in enumerate(group[1:]):
dst_bm = island_to_bm[g["id"]]
dst_uv_layer = island_to_uv_layer[g["id"]]
dst_loop_lists = bm_to_loop_lists[dst_bm]
dst_loops = []
for f in g["faces"]:
for l in f["face"].loops:
dst_loops.append(l)
dst_uv_graph = common.create_uv_graph(dst_loops, dst_uv_layer)
uv_stride = Vector(((stride_idx + 1) * self.stride.x,
(stride_idx + 1) * self.stride.y))
if self.accurate_island_copy:
# Check if the graph is isomorphic.
# If the graph is isomorphic, matching pair is returned.
result, pairs = graph_is_isomorphic(
src_uv_graph, dst_uv_graph)
if not result:
self.report(
{'WARNING'},
"Island does not match. "
"Disable 'Accurate Island Copy' and try again")
return {'CANCELLED'}
# Paste UV island.
for n1, n2 in pairs.items():
uv1 = n1.value["uv_vert"][src_uv_layer].uv
l2 = n2.value["loops"]
for l in l2:
l[dst_uv_layer].uv = uv1 + uv_stride
else:
for (src_face, dest_face) in zip(
group[0]['sorted'], g['sorted']):
for (src_loop, dest_loop) in zip(
src_face['face'].loops,
dest_face['face'].loops):
src_lidx = src_loop.index
dst_lidx = dest_loop.index
dst_loop_lists[dst_lidx][dst_uv_layer].uv = \
src_loop_lists[src_lidx][src_uv_layer].uv + \
uv_stride
# restore face/UV selection
bpy.ops.uv.select_all(action='DESELECT')
bpy.ops.mesh.select_all(action='DESELECT')
for f in selected_faces:
f.select = True
bpy.ops.uv.select_all(action='SELECT')
for obj in objs:
bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}