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