首页 > 解决方案 > Python:将通配符匹配应用于从字典中读取的键

问题描述

这是针对我在 Blender 中运行的脚本,但问题与它的 Python 部分有关。它不是特定于 Blender 的。

该脚本最初来自这个答案,它将给定的材料(键)替换为其较新的等效物(值)。

这是代码:

import bpy

objects = bpy.context.selected_objects

mat_dict =  {
  "SOLID-WHITE": "Sld_WHITE",
  "SOLID-BLACK": "Sld_BLACK",
  "SOLID-BLUE": "Sld_BLUE"
}

for obj in objects:
    for slot in obj.material_slots:
        slot.material = bpy.data.materials[mat_dict[slot.material.name]]

问题是,当场景可能不仅有材质为“SOLID-WHITE”的对象,还有“SOLID-WHITE.001”、“SOLID-WHITE.002”等对象时,如何处理重复项。

我正在查看关于 Python 中通配符的问题的答案,它似乎fnmatch非常适合这项任务。

我试过fnmatch进入代码的最后一行。我也试过用它包装字典键(我知道很湿)。这些方法都没有奏效。

如何在每个字典键上运行通配符匹配?

因此,例如,一个对象是否具有“SOLID-WHITE”或“SOLID-WHITE”-dot-some-number,它仍然会被替换为“Sld_WHITE”吗?

标签: pythondictionarykeywildcardblender

解决方案


有两种方法可以解决这个问题。您可以制作一个匹配模糊名称的智能词典。或者您可以更改用于查找颜色的键。

这是使用 fnmatch 的第一种方法的示例。当颜色包含数字时,这种方法将查找时间复杂度从 O(1) 更改为 O(n)。这种方法用一种方法扩展了UserDict__missing____missing__如果在字典中找不到键,则调用该方法。它使用 fnmatch 将每个键与给定键进行比较。

from collections import UserDict
import fnmatch
import bpy

objects = bpy.context.selected_objects

class Colors(UserDict):
    def __missing__(self, key):
        for color in self.keys():
            if fnmatch.fnmatch(key, color + "*"):
                return self[color]
        raise KeyError(f"could not match {key}")

mat_dict = Colors({
  "SOLID-WHITE": "Sld_WHITE",
  "SOLID-BLACK": "Sld_BLACK",
  "SOLID-BLUE": "Sld_BLUE"
})

for obj in objects:
    for slot in obj.material_slots:
        slot.material = bpy.data.materials[mat_dict[slot.material.name]]

这是使用正则表达式的第二种方法的示例。

import re
import bpy

objects = bpy.context.selected_objects

mat_dict =  {
  "SOLID-WHITE": "Sld_WHITE",
  "SOLID-BLACK": "Sld_BLACK",
  "SOLID-BLUE": "Sld_BLUE"
}

pattern = re.compile(r"([A-Z\-]+)(?:\.\d+)?")
# matches any number of capital letters and dashes
# can be followed by a dot followed by any number of digits
# this pattern can match the following strings
# ["AAAAA", "----", "AA-AA.00005"]


for obj in objects:
    for slot in obj.material_slots:
        match = pattern.fullmatch(slot.material.name)
        if match:
            slot.material = bpy.data.materials[mat_dict[match.group(1)]]
        else:
            slot.material = bpy.data.materials[mat_dict[slot.material.name]]

推荐阅读