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