Fix PMX import for Blender 5.0 - remove deprecated UV texture APIs

- Replace mesh.uv_textures with mesh.uv_layers
- Remove deprecated UV selection properties
- Add compatibility helpers for UV vertex selection
- Fix morph operators UV handling
This commit is contained in:
Yusarina
2025-11-19 05:06:13 +00:00
parent 0b5bff9222
commit f0bda259d3
3 changed files with 37 additions and 20 deletions
+4 -9
View File
@@ -746,22 +746,18 @@ class PMXImporter:
mesh.polygons.foreach_set("use_smooth", (True,) * len(pmxModel.faces)) mesh.polygons.foreach_set("use_smooth", (True,) * len(pmxModel.faces))
mesh.polygons.foreach_set("material_index", material_indices) mesh.polygons.foreach_set("material_index", material_indices)
uv_textures, uv_layers = getattr(mesh, "uv_textures", mesh.uv_layers), mesh.uv_layers uv_layers = mesh.uv_layers
uv_tex = uv_textures.new() uv_layer = uv_layers.new()
uv_layer = uv_layers[uv_tex.name]
uv_table = {vi: self.flipUV_V(v.uv) for vi, v in enumerate(pmxModel.vertices)} uv_table = {vi: self.flipUV_V(v.uv) for vi, v in enumerate(pmxModel.vertices)}
uv_layer.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in uv_table[i])) uv_layer.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in uv_table[i]))
if hasattr(mesh, "uv_textures"):
for bf, mi in zip(uv_tex.data, material_indices):
bf.image = self.__imageTable.get(mi, None)
if pmxModel.header and pmxModel.header.additional_uvs: if pmxModel.header and pmxModel.header.additional_uvs:
logger.info(f"Importing {pmxModel.header.additional_uvs} additional UVs") logger.info(f"Importing {pmxModel.header.additional_uvs} additional UVs")
zw_data_map = collections.OrderedDict() zw_data_map = collections.OrderedDict()
split_uvzw = lambda uvi: (self.flipUV_V(uvi[:2]), uvi[2:]) split_uvzw = lambda uvi: (self.flipUV_V(uvi[:2]), uvi[2:])
for i in range(pmxModel.header.additional_uvs): for i in range(pmxModel.header.additional_uvs):
add_uv = uv_layers[uv_textures.new(name="UV" + str(i + 1)).name] add_uv = uv_layers.new(name="UV" + str(i + 1))
logger.info(f" - {add_uv.name}...(uv channels)") logger.info(f" - {add_uv.name}...(uv channels)")
uv_table = {vi: split_uvzw(v.additional_uvs[i]) for vi, v in enumerate(pmxModel.vertices)} uv_table = {vi: split_uvzw(v.additional_uvs[i]) for vi, v in enumerate(pmxModel.vertices)}
add_uv.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in uv_table[i][0])) add_uv.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in uv_table[i][0]))
@@ -771,11 +767,10 @@ class PMXImporter:
zw_data_map["_" + add_uv.name] = {k: self.flipUV_V(v[1]) for k, v in uv_table.items()} zw_data_map["_" + add_uv.name] = {k: self.flipUV_V(v[1]) for k, v in uv_table.items()}
for name, zw_table in zw_data_map.items(): for name, zw_table in zw_data_map.items():
logger.info(f" - {name}...(zw channels of {name[1:]})") logger.info(f" - {name}...(zw channels of {name[1:]})")
add_zw = uv_textures.new(name=name) add_zw = uv_layers.new(name=name)
if add_zw is None: if add_zw is None:
logger.warning("\t* Lost zw channels") logger.warning("\t* Lost zw channels")
continue continue
add_zw = uv_layers[add_zw.name]
add_zw.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in zw_table[i])) add_zw.data.foreach_set("uv", tuple(v for i in loop_indices_orig for v in zw_table[i]))
self.__fixOverlappingFaceMaterials(mesh.materials, mesh.vertices, loop_indices, material_indices) self.__fixOverlappingFaceMaterials(mesh.materials, mesh.vertices, loop_indices, material_indices)
+9 -8
View File
@@ -643,12 +643,13 @@ class ViewUVMorph(bpy.types.Operator):
base_uv_data = mesh.uv_layers.active.data base_uv_data = mesh.uv_layers.active.data
temp_uv_data = mesh.uv_layers[uv_tex.name].data temp_uv_data = mesh.uv_layers[uv_tex.name].data
for i, l in enumerate(mesh.loops): for i, l in enumerate(mesh.loops):
select = temp_uv_data[i].select = l.vertex_index in offsets # Blender 5.0+: UV selection is now stored in face-corner attributes
# Skipping UV selection assignment as it's not critical for morph preview
select = l.vertex_index in offsets
if select: if select:
temp_uv_data[i].uv = base_uv_data[i].uv + offsets[l.vertex_index] temp_uv_data[i].uv = base_uv_data[i].uv + offsets[l.vertex_index]
uv_textures.active = uv_tex uv_textures.active = uv_tex
uv_tex.active_render = True
meshObj.hide_set(False) meshObj.hide_set(False)
meshObj.select_set(selected) meshObj.select_set(selected)
logger.info(f"Viewing UV morph: {morph.name}") logger.info(f"Viewing UV morph: {morph.name}")
@@ -667,13 +668,13 @@ class ClearUVMorphView(bpy.types.Operator):
assert root is not None assert root is not None
for m in FnModel.iterate_mesh_objects(root): for m in FnModel.iterate_mesh_objects(root):
mesh = m.data mesh = m.data
uv_textures = getattr(mesh, "uv_textures", mesh.uv_layers) uv_layers = mesh.uv_layers
for t in uv_textures: for t in list(uv_layers): # Create a copy to iterate safely
if t.name.startswith("__uv."): if t.name.startswith("__uv."):
uv_textures.remove(t) uv_layers.remove(t)
if len(uv_textures) > 0: if len(uv_layers) > 0:
uv_textures[0].active_render = True # Only set active_index
uv_textures.active_index = 0 uv_layers.active_index = 0
animation_data = mesh.animation_data animation_data = mesh.animation_data
if animation_data: if animation_data:
+24 -3
View File
@@ -8,6 +8,26 @@ from ...core.translations import t
from ...core.logging_setup import logger from ...core.logging_setup import logger
import traceback import traceback
def get_uv_vertex_selection(mesh: Mesh) -> List[bool]:
"""
Get UV vertex selection state for Blender 5.0.
UV selection is stored in mesh attributes (.uv_select_vert).
"""
uv_select_attr = mesh.attributes['.uv_select_vert']
selection = [False] * len(mesh.loops)
uv_select_attr.data.foreach_get('value', selection)
return selection
def set_uv_vertex_selection(mesh: Mesh, loop_index: int, value: bool) -> None:
"""
Set UV vertex selection state for Blender 5.0.
UV selection is stored in mesh attributes (.uv_select_vert).
"""
uv_select_attr = mesh.attributes['.uv_select_vert']
uv_select_attr.data[loop_index].value = value
class GenerateLoopTreeResult(TypedDict): class GenerateLoopTreeResult(TypedDict):
tree: Dict[str, Set[str]] tree: Dict[str, Set[str]]
selected_loops: Dict[str, List[int]] selected_loops: Dict[str, List[int]]
@@ -78,8 +98,9 @@ class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator):
# that two vertices share the same face loop, and therefore are connected. # that two vertices share the same face loop, and therefore are connected.
#hmmm real stupid grimlin hours with this one. Using a string as the index of a dictionary of loop corners that end up on the same coordinate #hmmm real stupid grimlin hours with this one. Using a string as the index of a dictionary of loop corners that end up on the same coordinate
for k,i in enumerate(uv_lay.vertex_selection): uv_selection = get_uv_vertex_selection(me)
if (i.value == True) and (bm.verts[me.loops[k].vertex_index].select == True) and (bm.verts[me.loops[k].vertex_index].hide == False): for k, is_selected in enumerate(uv_selection):
if (is_selected == True) and (bm.verts[me.loops[k].vertex_index].select == True) and (bm.verts[me.loops[k].vertex_index].hide == False):
key = np.array(uv_lay.uv[k].vector[:]) key = np.array(uv_lay.uv[k].vector[:])
key = key.round(decimals=5) key = key.round(decimals=5)
@@ -140,7 +161,7 @@ class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator):
uv_lay = me.uv_layers.active uv_lay = me.uv_layers.active
for uvcoordstr in vert_target_loops: for uvcoordstr in vert_target_loops:
for loop in vert_target_loops[uvcoordstr]: for loop in vert_target_loops[uvcoordstr]:
uv_lay.vertex_selection[loop].value = True set_uv_vertex_selection(me, loop, True)
bm.free() bm.free()
me.validate() me.validate()