Python: 对从字典中读取的键应用通配符匹配

Python: apply wildcard match to keys being read from dictionary

这是我在 Blender 中 运行 编写的脚本,但问题涉及其中的 Python 部分。它不是 Blender 特有的。

该脚本最初来自 this answer,它将给定的 material(键)替换为其较新的等价物(值)。

代码如下:

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]]

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

我在 this answer 中查看 Python 中关于通配符的问题,看起来 fnmatch 可能非常适合这项任务。

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

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

所以比如一个对象是否有“SOLID-WHITE”或“SOLID-WHITE”-dot-some-number,它仍然会被替换为“Sld_WHITE”?

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

这是使用 fnmatch 的第一种方法的示例。 当颜色包含数字时,此方法将查找时间复杂度从 O(1) 更改为 O(n)。这种方法使用 __missing__ 方法扩展了 UserDict。如果在字典中找不到键,则调用 __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]]

我对 Blender 一无所知,所以我不确定我的问题是否正确,但以下内容如何?

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

def get_new_material(old_material):
    for k, v in mat_dict.items():
        # .split(".")[0] extracts the part to the left of the dot (if there is one)
        if old_material.split(".")[0] == k:
            return v
    return old_material

for obj in objects:
    for slot in obj.material_slots:
        new_material = get_new_material(slot.material.name)
        slot.material = bpy.data.materials[new_material]

您可以使用 or re.match 而不是 .split(".")[0],方法是将正则表达式存储为字典中的键。正如您在评论中注意到的那样,startswith 可能匹配太多,fnmatch.

的情况也是如此

上述函数的实际应用示例:

In [3]: get_new_material("SOLID-WHITE.001")
Out[3]: 'Sld_WHITE'

In [4]: get_new_material("SOLID-WHITE")
Out[4]: 'Sld_WHITE'

In [5]: get_new_material("SOLID-BLACK")
Out[5]: 'Sld_BLACK'

In [6]: get_new_material("test")
Out[6]: 'test'