Hello everyone!
I’m working on a script in which I’m trying to search the Render Materials in the Rhino Document and return the Render Material names matching the search result and organize the results in order of “best match”
In example if the Rhino doc contains render materials named:
“Oak redwood”, “Red Oak”, “Red Oak Polished”, “Polished Dark Oak”, “Red Tile Polished”, “Red Metal Polished”, “Red Corrugated Metal Polished”
And I input search result:
“oak”
I expect Oak Redwood first as that is the “closest match”. This would be followed by Red Oak, then Red Oak Polished, and then lastly Polished Dark Oak.
If I input search result:
Red Polished
I would expect Red Oak Polished, Red Tile Polished, and Red Metal Polished to be equally weighted score wise and would probably sort alphabetically for “tie breakers”.
I would expect Red Metal Polished to come before Red Corrugated Metal Polished.
If I input search result:
polished oak
I would expect Polished Dark Oak before Red Oak Polished.
Here’s my code thus far, I tried to setup a scoring method based on the summation of “found words” but a bit stuck at the moment and curious if I am “overthinking” all of this and there is a method that already handles this kind of “best match” sorting?
Code:
import scriptcontext
search_text = "oak polished"
search_limit = 3
# Finds materials that are actively in the model and match the search text partially and case-insensitively
def FindMaterialsByPartialName(mat_name, limit):
mat_table = scriptcontext.doc.Materials
render_mat_table = scriptcontext.doc.RenderMaterials
matched_materials = {}
matched_render_materials = {}
search_words = mat_name.lower().split() # Split search text into individual words
# Search for regular materials
for i in range(mat_table.Count):
mat = mat_table[i]
# Check if the material is in use by any objects
in_use = False
for obj in scriptcontext.doc.Objects:
if obj.Attributes.MaterialIndex == i:
in_use = True
break
if not in_use:
continue
# Calculate the score based on the number of matched words
score = sum(word in mat.Name.lower() for word in search_words)
# Check for partial and case-insensitive match with any of the search words
if score > 0:
if mat.Name in matched_materials:
matched_materials[mat.Name].append((i, score))
else:
matched_materials[mat.Name] = [(i, score)]
# Search for render materials
for i in range(render_mat_table.Count):
render_mat = render_mat_table[i]
# Calculate the score based on the number of matched words
score = sum(word in render_mat.Name.lower() for word in search_words)
# Check for partial and case-insensitive match with any of the search words
if score > 0:
render_mat_name = f"{render_mat.Name}"
if render_mat_name in matched_render_materials:
matched_render_materials[render_mat_name].append((i, score))
else:
matched_render_materials[render_mat_name] = [(i, score)]
# Print render materials found
if matched_render_materials:
print("RENDER MATERIALS:")
count = 0
# Sort render materials based on score before printing
for mat_name, indices in sorted(matched_render_materials.items(), key=lambda x: max(x[1], key=lambda y: y[1]), reverse=True):
if count >= limit:
break
indices_str = ','.join(str(index[0]) for index in indices)
print("{0} found at Render Mat List[{1}]".format(mat_name, indices_str))
count += 1
else:
print("No render materials matching '{}' found".format(mat_name))
if __name__ == "__main__":
FindMaterialsByPartialName(search_text, search_limit)
Thank you all for your response!