1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
# SPDX-FileCopyrightText: 2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
from os import path
import re
def split_into_components(fname):
"""
Split filename into components
'WallTexture_diff_2k.002.jpg' -> ['Wall', 'Texture', 'diff', 'k']
"""
# Remove extension
fname = path.splitext(fname)[0]
# Remove digits
fname = "".join(i for i in fname if not i.isdigit())
# Separate CamelCase by space
fname = re.sub(r"([a-z])([A-Z])", r"\g<1> \g<2>", fname)
# Replace common separators with SPACE
separators = ["_", ".", "-", "__", "--", "#"]
for sep in separators:
fname = fname.replace(sep, " ")
components = fname.split(" ")
components = [c.lower() for c in components]
return components
def remove_common_prefix(names_to_tag_lists):
"""
Accepts a mapping of file names to tag lists that should be used for socket
matching.
This function modifies the provided mapping so that any common prefix
between all the tag lists is removed.
Returns true if some prefix was removed, false otherwise.
"""
if not names_to_tag_lists:
return False
sample_tags = next(iter(names_to_tag_lists.values()))
if not sample_tags:
return False
common_prefix = sample_tags[0]
for tag_list in names_to_tag_lists.values():
if tag_list[0] != common_prefix:
return False
for name, tag_list in names_to_tag_lists.items():
names_to_tag_lists[name] = tag_list[1:]
return True
def remove_common_suffix(names_to_tag_lists):
"""
Accepts a mapping of file names to tag lists that should be used for socket
matching.
This function modifies the provided mapping so that any common suffix
between all the tag lists is removed.
Returns true if some suffix was removed, false otherwise.
"""
if not names_to_tag_lists:
return False
sample_tags = next(iter(names_to_tag_lists.values()))
if not sample_tags:
return False
common_suffix = sample_tags[-1]
for tag_list in names_to_tag_lists.values():
if tag_list[-1] != common_suffix:
return False
for name, tag_list in names_to_tag_lists.items():
names_to_tag_lists[name] = tag_list[:-1]
return True
def files_to_clean_file_names_for_sockets(files, sockets):
"""
Accepts a list of files and a list of sockets.
Returns a mapping from file names to tag lists that should be used for
classification.
A file is something that we can do x.name on to figure out the file name.
A socket is a tuple containing:
* name
* list of tags
* a None field where the selected file name will go later. Ignored by us.
"""
names_to_tag_lists = {}
for file in files:
names_to_tag_lists[file.name] = split_into_components(file.name)
all_tags = set()
for socket in sockets:
socket_tags = socket[1]
all_tags.update(socket_tags)
while len(names_to_tag_lists) > 1:
something_changed = False
# Common prefixes / suffixes provide zero information about what file
# should go to which socket, but they can confuse the mapping. So we get
# rid of them here.
something_changed |= remove_common_prefix(names_to_tag_lists)
something_changed |= remove_common_suffix(names_to_tag_lists)
# Names matching zero tags provide no value, remove those
names_to_remove = set()
for name, tag_list in names_to_tag_lists.items():
match_found = False
for tag in tag_list:
if tag in all_tags:
match_found = True
if not match_found:
names_to_remove.add(name)
for name_to_remove in names_to_remove:
del names_to_tag_lists[name_to_remove]
something_changed = True
if not something_changed:
break
return names_to_tag_lists
def match_files_to_socket_names(files, sockets):
"""
Given a list of files and a list of sockets, match file names to sockets.
A file is something that you can get a file name out of using x.name.
After this function returns, all possible sockets have had their file names
filled in. Sockets without any matches will not get their file names
changed.
Sockets list format. Note that all file names are initially expected to be
None. Tags are strings, as are the socket names: [
[
socket_name, [tags], Optional[file_name]
]
]
"""
names_to_tag_lists = files_to_clean_file_names_for_sockets(files, sockets)
for sname in sockets:
for name, tag_list in names_to_tag_lists.items():
if sname[0] == "Normal":
# Blender wants GL normals, not DX (DirectX) ones:
# https://www.reddit.com/r/blender/comments/rbuaua/texture_contains_normaldx_and_normalgl_files/
if 'dx' in tag_list:
continue
if 'directx' in tag_list:
continue
matches = set(sname[1]).intersection(set(tag_list))
if matches:
sname[2] = name
break
|