Skip to content

Commit 13f3eef

Browse files
committed
WIP material setup blender script
1 parent 15b57b5 commit 13f3eef

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

blendermat/matsetup.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import bpy
2+
import json
3+
import os.path
4+
5+
# debug globals
6+
dbg_shaderdb = "/media/bigDOwO/halo/infinite/bsptest/shaderdb.json"
7+
dbg_matdb = "/media/bigDOwO/halo/infinite/bsptest/test.dae.json"
8+
dbg_texpath = "/media/bigDOwO/halo/infinite/bsptest/tex/"
9+
dbg_shaderpath = "/media/bigDOwO/halo/infinite/bsptest/"
10+
# testing with /sbsp/0183a942
11+
12+
class MaterialImporterContext:
13+
def __init__(self) -> None:
14+
self.texpath = ""
15+
16+
# this currently doesn't fully support texture arrays, the first frame will always be used
17+
def getTexPath(self, id : str):
18+
return f"{self.texpath}/{id}.png"
19+
20+
def getImage(self, id : str):
21+
if id in bpy.data.images.keys():
22+
return bpy.data.images[id]
23+
24+
if not os.path.isfile(self.getTexPath(id)):
25+
# texture doesn't exist
26+
if f"{id}_dummy" in bpy.data.images.keys():
27+
return bpy.data.images[f"{id}_dummy"]
28+
img = bpy.data.images.new(f"{id}_dummy", 1, 1)
29+
return img
30+
img = bpy.data.images.load(self.getTexPath(id))
31+
img.name = id
32+
return img
33+
34+
def loadShader(self, name : str, filename : str):
35+
with bpy.data.libraries.load(f"{dbg_shaderpath}/{filename}", link=False) as (data_from, data_to):
36+
data_to.materials = [name]
37+
38+
def getUVName(self, idx : int):
39+
return f"UV{idx}"
40+
41+
42+
# copies nodes, default values of sockets, and links from one material to another one
43+
# existing nodes in the dstMat get deleted
44+
# there's a bit of a limitation here: a node must not have multiple inputs or outputs with the same name, or they can't be linked correctly
45+
# use group nodes to get around this
46+
def copyNodes(srcMat : bpy.types.Material, dstMat : bpy.types.Material):
47+
# clear dst material
48+
for node in dstMat.node_tree.nodes:
49+
dstMat.node_tree.nodes.remove(node)
50+
51+
for node in srcMat.node_tree.nodes:
52+
new_node = dstMat.node_tree.nodes.new(node.bl_idname)
53+
new_node.label = node.label
54+
new_node.location = node.location
55+
new_node.name = node.name
56+
57+
if(node.bl_idname == 'ShaderNodeGroup'):
58+
new_node.node_tree = node.node_tree
59+
60+
if(node.bl_idname == 'ShaderNodeMath'):
61+
new_node.operation = node.operation
62+
new_node.use_clamp = node.use_clamp
63+
64+
new_node.color = node.color
65+
new_node.width = node.width
66+
new_node.height = node.height
67+
for input in node.inputs:
68+
if input.type == "SHADER":
69+
# there's nothing I can set this to I think
70+
continue
71+
new_node.inputs[input.name].default_value = input.default_value
72+
new_node.update()
73+
# set up links/relationships between nodes after all nodes have been copied
74+
for node in srcMat.node_tree.nodes:
75+
new_node = dstMat.node_tree.nodes[node.name]
76+
if node.parent is not None:
77+
new_node.parent = dstMat.node_tree.nodes[node.parent.name]
78+
79+
for link in srcMat.node_tree.links:
80+
#print(f"Link from {link.from_node.name}:{link.from_socket.identifier} to {link.to_node.name}:{link.to_socket.identifier}")
81+
#print(dstMat.node_tree.nodes[link.to_node.name].inputs.values())
82+
dstMat.node_tree.links.new(dstMat.node_tree.nodes[link.from_node.name].outputs[link.from_socket.name],
83+
dstMat.node_tree.nodes[link.to_node.name].inputs[link.to_socket.name])
84+
85+
86+
# returns the shader description if a shader is found, or None otherwise
87+
def getBestMat(materialName : str ,materialParameters : dict, shaderdb : dict):
88+
# there's probably more efficient methods, but this should be simpler to implement
89+
# results could also be cached and tweaked
90+
matches = []
91+
matches.extend((0,) * len(shaderdb["shaders"]))
92+
for i,shaderdesc in enumerate(shaderdb["shaders"]):
93+
if "mat_blacklist" in shaderdesc.keys():
94+
if materialName in shaderdesc["mat_blacklist"]:
95+
matches[i] = -1 # blacklisted
96+
continue
97+
if not "parameters" in shaderdesc.keys():
98+
matches[i] = -1 # no parameters -> not useable
99+
continue
100+
for param in shaderdesc["parameters"]:
101+
if param["name"] in materialParameters.keys():
102+
matches[i]+=1 # the material has this parameter
103+
elif not ("optional" in param.keys() and param["optional"] is True):
104+
matches[i]-=2 # the material doesn't have this parameter (maybe also just discard it completely as an option instead)
105+
106+
currentmax = -1
107+
idx = -1
108+
for i, score in enumerate(matches):
109+
# maybe add priority in case multiple shaders match the same number of parameters. Or just improve the whole thing, this is essentially just for a quick test
110+
if score > currentmax:
111+
currentmax = score
112+
idx = i
113+
if currentmax > 0:
114+
winningshader = shaderdb["shaders"][idx]
115+
print(f"Found matching shader {winningshader['name']} for material {materialName} with score {currentmax}")
116+
return winningshader
117+
print(f"Could not find a fitting shader for material {materialName}")
118+
return None
119+
120+
121+
def setupMatParameters(mat : bpy.types.Material, shaderdesc : dict, materialdesc : dict, context : MaterialImporterContext):
122+
for parameter in shaderdesc["parameters"]:
123+
#print(parameter)
124+
if not parameter["maps_to_node"] in mat.node_tree.nodes.keys():
125+
print(f"Error: Could not find node {parameter['maps_to_node']} in material {mat.name}")
126+
continue
127+
if parameter["name"] not in materialdesc.keys():
128+
print(f"Parameter {parameter['name']} not in material {mat.name}")
129+
continue
130+
if "ignore" in parameter.keys() and parameter["ignore"] == True:
131+
continue
132+
133+
node = mat.node_tree.nodes[parameter["maps_to_node"]]
134+
if parameter["type"] == "texture":
135+
node.image = context.getImage(materialdesc[parameter["name"]]["name"])
136+
137+
elif parameter["type"] == "uv":
138+
node.uv_map = context.getUVName(materialdesc[parameter["name"]])
139+
elif parameter["type"] == "uv3": # special type for the UV3 parameter that sets the UV index to 2
140+
if materialdesc[parameter["name"]] == True:
141+
node.uv_map = context.getUVName(2)
142+
143+
elif parameter["type"] == "float":
144+
if not parameter["input"] in node.inputs.keys():
145+
print(f"Error: Could not find input socket {parameter['input']} on node {parameter['maps_to_node']}")
146+
continue
147+
node.inputs[parameter["input"]].default_value = materialdesc[parameter["name"]]
148+
149+
elif parameter["type"] == "condition_float":
150+
if not parameter["input"] in node.inputs.keys():
151+
print(f"Error: Could not find input socket {parameter['input']} on node {parameter['maps_to_node']}")
152+
continue
153+
node.inputs[parameter["input"]].default_value = parameter["value"]
154+
else:
155+
print(f"Unsupported parameter type {parameter['type']}")
156+
157+
def setupMat(mat : bpy.types.Material, shaderdesc : dict, materialdesc : dict, context : MaterialImporterContext):
158+
if shaderdesc["name"] not in bpy.data.materials.keys():
159+
context.loadShader(shaderdesc["name"], shaderdesc["file"])
160+
if shaderdesc["name"] not in bpy.data.materials.keys():
161+
# still not present/couldn't load
162+
print("Material not present in file (I still need to add loading materials from other blends)")
163+
return
164+
165+
copyNodes(bpy.data.materials[shaderdesc["name"]], mat)
166+
setupMatParameters(mat, shaderdesc, materialdesc, context)
167+
168+
# rename UVs to uniform names across all meshes using materials in materialdb
169+
def renameUVs(materialdb : dict, context : MaterialImporterContext):
170+
for mesh in bpy.data.meshes.values():
171+
usesMat = False
172+
#print(f"mesh: {mesh.name}")
173+
for mat in mesh.materials.keys():
174+
#print(f"Mat: {mat}")
175+
if mat in materialdb.keys():
176+
usesMat = True
177+
# this is just a simple filter to prevent messing up other meshes (doesn't work properly)
178+
if True:
179+
for i,uv in enumerate(mesh.uv_layers):
180+
uv.name = context.getUVName(i)
181+
182+
183+
def processMat(name : str, context : MaterialImporterContext):
184+
shaderdesc = getBestMat(name, matdb[name], shaderdb)
185+
if shaderdesc is not None:
186+
setupMat(bpy.data.materials[name], shaderdesc, matdb[name], importcontext)
187+
188+
if __name__ == "__main__":
189+
# test stuff
190+
191+
importcontext = MaterialImporterContext()
192+
importcontext.texpath = dbg_texpath
193+
194+
shaderdb_f = open(dbg_shaderdb,'rb')
195+
shaderdb = json.load(shaderdb_f)
196+
shaderdb_f.close()
197+
198+
matdb_f = open(dbg_matdb,'rb')
199+
matdb = json.load(matdb_f)
200+
matdb_f.close()
201+
202+
#tmat = getBestMat("/mat /3f72d37b", matdb["/mat /3f72d37b"], shaderdb)
203+
#print(tmat)
204+
#copyNodes(bpy.data.materials["testmat"], bpy.data.materials["/mat /3f72d37b"])
205+
#setupMat(bpy.data.materials["/mat /3f72d37b"], tmat, matdb["/mat /3f72d37b"], importcontext)
206+
#processMat("/mat /3f72d37b", importcontext)
207+
#processMat("/mat /8096811d", importcontext)
208+
#processMat("/mat /8779b860", importcontext)
209+
210+
# set up all materials (or try to) from the file
211+
# this might take a while, so probably don't use that when just testing
212+
renameUVs(matdb, importcontext)
213+
214+
for mat in matdb.keys():
215+
if mat in bpy.data.materials.keys():
216+
#print(f"Material: {mat}")
217+
processMat(mat, importcontext)

0 commit comments

Comments
 (0)