def load_fgm(ovl_data, fgm_file_path, fgm_sized_str_entry): fgm_data = FgmFile() fgm_data.load(fgm_file_path) sizedstr_bytes = as_bytes(fgm_data.fgm_info) + as_bytes( fgm_data.two_frags_pad) # todo - move texpad into fragment padding? textures_bytes = as_bytes(fgm_data.textures) + as_bytes(fgm_data.texpad) attributes_bytes = as_bytes(fgm_data.attributes) # the actual injection fgm_sized_str_entry.data_entry.update_data((fgm_data.buffer_bytes, )) fgm_sized_str_entry.pointers[0].update_data(sizedstr_bytes, update_copies=True) if len(fgm_sized_str_entry.fragments) == 4: datas = (textures_bytes, attributes_bytes, fgm_data.zeros_bytes, fgm_data.data_bytes) # fgms without zeros elif len(fgm_sized_str_entry.fragments) == 3: datas = (textures_bytes, attributes_bytes, fgm_data.data_bytes) # fgms for variants elif len(fgm_sized_str_entry.fragments) == 2: datas = (attributes_bytes, fgm_data.data_bytes) else: raise AttributeError("Unexpected fgm frag count") # inject fragment datas for frag, data in zip(fgm_sized_str_entry.fragments, datas): frag.pointers[1].update_data(data, update_copies=True)
def load_fgm(ovl, fgm_file_path, fgm_sized_str_entry): versions = get_versions(ovl) fgm_data = FgmFile() fgm_data.load(fgm_file_path) sizedstr_bytes = as_bytes( fgm_data.fgm_info, version_info=versions) + as_bytes( fgm_data.two_frags_pad, version_info=versions) # todo - move texpad into fragment padding? textures_bytes = as_bytes(fgm_data.textures, version_info=versions) + as_bytes( fgm_data.texpad, version_info=versions) attributes_bytes = as_bytes(fgm_data.attributes, version_info=versions) # the actual injection fgm_sized_str_entry.data_entry.update_data((fgm_data.buffer_bytes, )) fgm_sized_str_entry.pointers[0].update_data(sizedstr_bytes, update_copies=True) if len(fgm_sized_str_entry.fragments) == 4: datas = (textures_bytes, attributes_bytes, fgm_data.zeros_bytes, fgm_data.data_bytes) # fgms without zeros elif len(fgm_sized_str_entry.fragments) == 3: datas = (textures_bytes, attributes_bytes, fgm_data.data_bytes) # fgms for variants elif len(fgm_sized_str_entry.fragments) == 2: datas = (attributes_bytes, fgm_data.data_bytes) else: raise AttributeError("Unexpected fgm frag count") # inject fragment datas for frag, data in zip(fgm_sized_str_entry.fragments, datas): frag.pointers[1].update_data(data, update_copies=True) # update dependencies on ovl fgm_file_entry = get_file_entry(ovl, fgm_sized_str_entry) for dep_entry, tex_name in zip(fgm_file_entry.dependencies, fgm_data.texture_names): dep_entry.basename = tex_name dep_entry.name = dep_entry.basename + dep_entry.ext.replace(":", ".") dep_entry.file_hash = djb(tex_name.lower())
def load_matcol(matcol_path): lib_dir = os.path.normpath(os.path.dirname(matcol_path)) matcol_file = MatcolFile() matcol_file.load(matcol_path) slots = [] rootname = "anky_ankylo_backplates" basecol = ".pbasecolourtexture" baseheight = ".pheighttexture" all_textures = [ file for file in os.listdir(lib_dir) if file.lower().endswith(".png") ] base_textures = [ os.path.join(lib_dir, file) for file in all_textures if rootname in file and basecol in file ] height_textures = [ os.path.join(lib_dir, file) for file in all_textures if rootname in file and baseheight in file ] # print(base_textures) # for layer in matcol_file.layered_wrapper: # print(layer) for layer in matcol_file.layered_wrapper.layers: print(layer.name) if layer.name == "Default": print("Skipping Default layer") htex = None else: fgm_path = os.path.join(lib_dir, layer.name + ".fgm") # print(fgm_path) fgm_data = FgmFile() fgm_data.load(fgm_path) if fgm_data.textures[0].is_textured == 8: base_index = fgm_data.textures[0].indices[1] height_index = fgm_data.textures[1].indices[1] else: print("tell Developers not using indices") print("base_array_index", base_index) print("height_array_index", height_index) print("base", base_textures[base_index]) print("height", height_textures[height_index]) htex = height_textures[height_index] slots.append((layer.infos, htex)) return slots
def get_fgm_values(gui, start_dir, walk_ovls=True, walk_fgms=True): errors = [] if start_dir: export_dir = os.path.join(start_dir, "walker_export") if walk_ovls: bulk_extract_ovls(errors, export_dir, gui, start_dir, (".fgm", )) attributes = {} textures = set() shaders = set() if walk_fgms: fgm_data = FgmFile() fgm_files = walk_type(export_dir, extension=".fgm") mf_max = len(fgm_files) for mf_index, fgm_path in enumerate(fgm_files): fgm_name = os.path.basename(fgm_path) gui.update_progress("Walking FGM files: " + fgm_name, value=mf_index, vmax=mf_max) try: fgm_data.load(fgm_path) shaders.add(fgm_data.shader_name) for attrib in fgm_data.attributes: attributes[attrib.name] = attrib.dtype for texture in fgm_data.textures: textures.add(texture.name) except Exception as ex: traceback.print_exc() errors.append((fgm_path, ex)) # report print("\nThe following errors occured:") for file_path, ex in errors: print(file_path, str(ex)) out_path = os.path.join(export_dir, f"fgm_{os.path.basename(start_dir)}.py") with open(out_path, "w") as f: f.write(f"attributes = {attributes}\n\n") f.write(f"textures = {textures}\n\n") f.write(f"shaders = {shaders}\n\n") print(f"Written to {out_path}") gui.update_progress("Operation completed!", value=1, vmax=1)
def create_material(in_dir, matname): print(f"Importing material {matname}") # only create the material if it doesn't exist in the blend file, then just grab it # but we overwrite its contents anyway if matname not in bpy.data.materials: b_mat = bpy.data.materials.new(matname) else: b_mat = bpy.data.materials[matname] fgm_path = os.path.join(in_dir, matname + ".fgm") # print(fgm_path) try: fgm_data = FgmFile() fgm_data.load(fgm_path) except FileNotFoundError: print(f"{fgm_path} does not exist!") return b_mat # base_index = fgm_data.textures[0].layers[1] # height_index = fgm_data.textures[1].layers[1] tree = get_tree(b_mat) output = tree.nodes.new('ShaderNodeOutputMaterial') principled = tree.nodes.new('ShaderNodeBsdfPrincipled') all_textures = [ file for file in os.listdir(in_dir) if file.lower().endswith(".png") ] # map texture names to node tex_dic = {} for fgm_texture in fgm_data.textures: png_base = fgm_texture.name.lower() if "blendweights" in png_base or "warpoffset" in png_base: continue textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: png_base = png_base.lower().replace("_eyes", "").replace( "_fin", "").replace("_shell", "") textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: textures = [ png_base + ".png", ] # print(textures) for png_name in textures: png_path = os.path.join(in_dir, png_name) b_tex = load_tex(tree, png_path) k = png_name.lower().split(".")[1] tex_dic[k] = b_tex # get diffuse and AO for diffuse_name in ("pdiffusetexture", "pbasediffusetexture", "pbasecolourtexture", "pbasecolourandmasktexture", "pdiffusealphatexture", "pdiffuse_alphatexture", "palbinobasecolourandmasktexture"): # get diffuse if diffuse_name in tex_dic: diffuse = tex_dic[diffuse_name] # get AO for ao_name in ("paotexture", "pbasepackedtexture_03"): if ao_name in tex_dic: ao = tex_dic[ao_name] ao.image.colorspace_settings.name = "Non-Color" # apply AO to diffuse diffuse_premix = tree.nodes.new('ShaderNodeMixRGB') diffuse_premix.blend_type = "MULTIPLY" diffuse_premix.inputs["Fac"].default_value = .25 tree.links.new(diffuse.outputs[0], diffuse_premix.inputs["Color1"]) tree.links.new(ao.outputs[0], diffuse_premix.inputs["Color2"]) diffuse = diffuse_premix break # get marking fur_names = [ k for k in tex_dic.keys() if "marking" in k and "noise" not in k and "patchwork" not in k ] lut_names = [k for k in tex_dic.keys() if "pclut" in k] if fur_names and lut_names: marking = tex_dic[sorted(fur_names)[0]] lut = tex_dic[sorted(lut_names)[0]] marking.image.colorspace_settings.name = "Non-Color" # PZ LUTs usually occupy half of the texture, so scale the incoming greyscale coordinates so that # 1 lands in the center of the LUT scaler = tree.nodes.new('ShaderNodeMath') scaler.operation = "MULTIPLY" tree.links.new(marking.outputs[0], scaler.inputs[0]) scaler.inputs[1].default_value = 0.5 tree.links.new(scaler.outputs[0], lut.inputs[0]) # apply AO to diffuse diffuse_premix = tree.nodes.new('ShaderNodeMixRGB') diffuse_premix.blend_type = "MIX" tree.links.new(diffuse.outputs[0], diffuse_premix.inputs["Color1"]) tree.links.new(lut.outputs[0], diffuse_premix.inputs["Color2"]) tree.links.new(marking.outputs[0], diffuse_premix.inputs["Fac"]) diffuse = diffuse_premix # link finished diffuse to shader tree.links.new(diffuse.outputs[0], principled.inputs["Base Color"]) break if "pnormaltexture" in tex_dic: normal = tex_dic["pnormaltexture"] normal.image.colorspace_settings.name = "Non-Color" normal_map = tree.nodes.new('ShaderNodeNormalMap') tree.links.new(normal.outputs[0], normal_map.inputs[1]) # normal_map.inputs["Strength"].default_value = 1.0 tree.links.new(normal_map.outputs[0], principled.inputs["Normal"]) # PZ - specularity? for spec_name in ( "proughnesspackedtexture_02", "pspecularmaptexture_00", ): if spec_name in tex_dic: specular = tex_dic[spec_name] specular.image.colorspace_settings.name = "Non-Color" tree.links.new(specular.outputs[0], principled.inputs["Specular"]) # PZ - roughness? for roughness_name in ( "proughnesspackedtexture_01", ): # "pspecularmaptexture_01" ? if roughness_name in tex_dic: roughness = tex_dic[roughness_name] roughness.image.colorspace_settings.name = "Non-Color" tree.links.new(roughness.outputs[0], principled.inputs["Roughness"]) # JWE dinos - metalness for metal_name in ("pbasepackedtexture_02", ): if metal_name in tex_dic: metal = tex_dic[metal_name] metal.image.colorspace_settings.name = "Non-Color" tree.links.new(metal.outputs[0], principled.inputs["Metallic"]) # alpha alpha = None # JWE billboard: Foliage_Billboard if "pdiffusealphatexture" in tex_dic: alpha = tex_dic["pdiffusealphatexture"] alpha_pass = alpha.outputs[1] elif "pdiffuse_alphatexture" in tex_dic: alpha = tex_dic["pdiffuse_alphatexture"] alpha_pass = alpha.outputs[1] # PZ penguin elif "popacitytexture" in tex_dic: alpha = tex_dic["popacitytexture"] alpha_pass = alpha.outputs[0] elif "proughnesspackedtexture_00" in tex_dic and "Foliage_Clip" in fgm_data.shader_name: alpha = tex_dic["proughnesspackedtexture_00"] alpha_pass = alpha.outputs[0] # parrot: Metallic_Roughness_Clip -> 03 elif "proughnesspackedtexture_03" in tex_dic and "Foliage_Clip" not in fgm_data.shader_name: alpha = tex_dic["proughnesspackedtexture_03"] alpha_pass = alpha.outputs[0] if alpha: # transparency b_mat.blend_method = "CLIP" b_mat.shadow_method = "CLIP" for attrib in fgm_data.attributes: if attrib.name.lower() == "palphatestref": b_mat.alpha_threshold = attrib.value[0] break transp = tree.nodes.new('ShaderNodeBsdfTransparent') alpha_mixer = tree.nodes.new('ShaderNodeMixShader') tree.links.new(alpha_pass, alpha_mixer.inputs[0]) tree.links.new(transp.outputs[0], alpha_mixer.inputs[1]) tree.links.new(principled.outputs[0], alpha_mixer.inputs[2]) tree.links.new(alpha_mixer.outputs[0], output.inputs[0]) alpha_mixer.update() # no alpha else: b_mat.blend_method = "OPAQUE" tree.links.new(principled.outputs[0], output.inputs[0]) nodes_iterate(tree, output) return b_mat
def _get_data(file_path): fgm_data = FgmFile() fgm_data.load(file_path) return fgm_data
class MainWindow(widgets.MainWindow): def __init__(self): widgets.MainWindow.__init__( self, "FGM Editor", ) self.resize(800, 600) self.fgm_data = FgmFile() self.widgets = [] self.tooltips = config.read_config("ovl_util/tooltips/fgm.txt") self.shaders = {} for game in games: self.shaders[game] = config.read_list( f"ovl_util/tooltips/fgm-shaders-{game.lower().replace(' ', '-')}.txt" ) self.cleaner = QtCore.QObjectCleanupHandler() self.scrollarea = QtWidgets.QScrollArea(self) self.scrollarea.setWidgetResizable(True) self.setCentralWidget(self.scrollarea) # the actual scrollable stuff self.widget = QtWidgets.QWidget() self.scrollarea.setWidget(self.widget) self.game_container = widgets.LabelCombo("Game:", games) self.game_container.entry.currentIndexChanged.connect( self.game_changed) self.game_container.entry.setEditable(False) self.file_widget = widgets.FileWidget(self, self.cfg, dtype="FGM") self.shader_container = widgets.LabelCombo("Shader:", ()) self.shader_container.entry.activated.connect(self.shader_changed) self.tex_container = QtWidgets.QGroupBox("Textures") self.attrib_container = QtWidgets.QGroupBox("Attributes") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.file_widget) vbox.addWidget(self.game_container) vbox.addWidget(self.shader_container) vbox.addWidget(self.tex_container) vbox.addWidget(self.attrib_container) vbox.addStretch(1) self.widget.setLayout(vbox) self.tex_grid = self.create_grid() self.attrib_grid = self.create_grid() self.tex_container.setLayout(self.tex_grid) self.attrib_container.setLayout(self.attrib_grid) mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('File') helpMenu = mainMenu.addMenu('Help') button_data = ( (fileMenu, "Open", self.file_widget.ask_open, "CTRL+O", ""), \ (fileMenu, "Save", self.save_fgm, "CTRL+S", ""), \ (fileMenu, "Exit", self.close, "", ""), \ (helpMenu, "Report Bug", self.report_bug, "", ""), \ (helpMenu, "Documentation", self.online_support, "", ""), \ ) self.add_to_menu(button_data) def game_changed(self, ): if self.file_widget.filepath: self.shader_container.entry.clear() game = self.game_container.entry.currentText() self.shader_container.entry.addItems(self.shaders[game]) def shader_changed(self, ): """Change the fgm data shader name if gui changes""" if self.file_widget.filepath: self.fgm_data.shader_name = self.shader_container.entry.currentText( ) @property def fgm_name(self, ): return self.file_widget.entry.text() def create_grid(self, ): g = QtWidgets.QGridLayout() g.setHorizontalSpacing(3) g.setVerticalSpacing(0) return g def clear_layout(self, layout): w = QtWidgets.QWidget() w.setLayout(layout) # while layout.count(): # item = layout.takeAt(0) # widget = item.widget() # # if widget has some id attributes you need to # # save in a list to maintain order, you can do that here # # i.e.: aList.append(widget.someId) # widget.deleteLater() def load(self): if self.file_widget.filepath: for w in self.widgets: w.deleteLater() try: self.fgm_data.load(self.file_widget.filepath) print(self.fgm_data) game = get_game(self.fgm_data) print("from game", game) self.game_container.entry.setText(game) # also for self.game_changed() self.shader_container.entry.setText(self.fgm_data.shader_name) # delete existing widgets self.clear_layout(self.tex_grid) self.clear_layout(self.attrib_grid) self.tex_grid = self.create_grid() self.tex_grid.setColumnStretch(1, 3) self.tex_grid.setColumnStretch(2, 1) self.attrib_grid = self.create_grid() self.tex_container.setLayout(self.tex_grid) self.attrib_container.setLayout(self.attrib_grid) for line_i, tex in enumerate(self.fgm_data.textures): w = widgets.VectorEntry(tex, self.tooltips) self.tex_grid.addWidget(w.delete, line_i, 0) self.tex_grid.addWidget(w.entry, line_i, 1) self.tex_grid.addWidget(w.data, line_i, 2) for line_i, attrib in enumerate(self.fgm_data.attributes): w = widgets.VectorEntry(attrib, self.tooltips) self.attrib_grid.addWidget(w.delete, line_i, 0) self.attrib_grid.addWidget(w.entry, line_i, 1) self.attrib_grid.addWidget(w.data, line_i, 2) except Exception as ex: traceback.print_exc() ovl_util.interaction.showdialog(str(ex)) print(ex) print("Done!") def save_fgm(self): if self.file_widget.filepath: file_out = QtWidgets.QFileDialog.getSaveFileName( self, 'Save FGM', os.path.join(self.cfg.get("dir_fgms_out", "C://"), self.fgm_name), "FGM files (*.fgm)", )[0] if file_out: self.cfg["dir_fgms_out"], fgm_name = os.path.split(file_out) self.fgm_data.save(file_out) print("Done!")
def create_material(in_dir, matname): logging.info(f"Importing material {matname}") b_mat = bpy.data.materials.new(matname) fgm_path = os.path.join(in_dir, matname + ".fgm") # print(fgm_path) try: fgm_data = FgmFile() fgm_data.load(fgm_path) except FileNotFoundError: logging.warning(f"{fgm_path} does not exist!") return b_mat # base_index = fgm_data.textures[0].layers[1] # height_index = fgm_data.textures[1].layers[1] tree = get_tree(b_mat) output = tree.nodes.new('ShaderNodeOutputMaterial') principled = tree.nodes.new('ShaderNodeBsdfPrincipled') color_ramp = fgm_data.get_color_ramp("colourKey", "RGB") opacity_ramp = fgm_data.get_color_ramp("opacityKey", "Value") if color_ramp and opacity_ramp: # print(color_ramp, list(zip(color_ramp))) positions, colors = zip(*color_ramp) positions_2, opacities = zip(*color_ramp) ramp = tree.nodes.new('ShaderNodeValToRGB') for position, color, opacity in zip(positions, colors, opacities): print(position, color, opacity) pos_relative = (position - min(positions)) / (max(positions) - min(positions)) e = ramp.color_ramp.elements.new(pos_relative) e.color[:3] = color e.alpha = opacity[0] all_textures = [ file for file in os.listdir(in_dir) if file.lower().endswith(".png") ] # map texture names to node tex_dic = {} for fgm_texture in fgm_data.textures: if not fgm_texture.file: continue png_base = fgm_texture.file.lower() if "blendweights" in png_base or "warpoffset" in png_base: continue textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: png_base = png_base.lower().replace("_eyes", "").replace( "_fin", "").replace("_shell", "") textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: textures = [ png_base + ".png", ] # print(textures) for png_name in textures: png_path = os.path.join(in_dir, png_name) b_tex = load_tex(tree, png_path) k = png_name.lower().split(".")[1] tex_dic[k] = b_tex # get diffuse and AO for diffuse_name in ("pdiffusetexture", "pbasediffusetexture", "pbasecolourtexture", "pbasecolourandmasktexture", "pdiffusealphatexture", "pdiffuse_alphatexture", "palbinobasecolourandmasktexture"): # get diffuse if diffuse_name in tex_dic: diffuse = tex_dic[diffuse_name] # get AO for ao_name in ("paotexture", "pbasepackedtexture_[03]", "pbaseaotexture"): if ao_name in tex_dic: ao = tex_dic[ao_name] ao.image.colorspace_settings.name = "Non-Color" # apply AO to diffuse diffuse_premix = tree.nodes.new('ShaderNodeMixRGB') diffuse_premix.blend_type = "MULTIPLY" diffuse_premix.inputs["Fac"].default_value = .25 tree.links.new(diffuse.outputs[0], diffuse_premix.inputs["Color1"]) tree.links.new(ao.outputs[0], diffuse_premix.inputs["Color2"]) diffuse = diffuse_premix break # get marking fur_names = [ k for k in tex_dic.keys() if "marking" in k and "noise" not in k and "patchwork" not in k ] lut_names = [k for k in tex_dic.keys() if "pclut" in k] if fur_names and lut_names: marking = tex_dic[sorted(fur_names)[0]] lut = tex_dic[sorted(lut_names)[0]] marking.image.colorspace_settings.name = "Non-Color" # PZ LUTs usually occupy half of the texture, so scale the incoming greyscale coordinates so that # 1 lands in the center of the LUT scaler = tree.nodes.new('ShaderNodeMath') scaler.operation = "MULTIPLY" tree.links.new(marking.outputs[0], scaler.inputs[0]) scaler.inputs[1].default_value = 0.5 tree.links.new(scaler.outputs[0], lut.inputs[0]) # apply AO to diffuse diffuse_premix = tree.nodes.new('ShaderNodeMixRGB') diffuse_premix.blend_type = "MIX" tree.links.new(diffuse.outputs[0], diffuse_premix.inputs["Color1"]) tree.links.new(lut.outputs[0], diffuse_premix.inputs["Color2"]) tree.links.new(marking.outputs[0], diffuse_premix.inputs["Fac"]) diffuse = diffuse_premix # link finished diffuse to shader tree.links.new(diffuse.outputs[0], principled.inputs["Base Color"]) break for normal_name in ("pnormaltexture", "pbasenormaltexture_[0]"): # get diffuse if normal_name in tex_dic: normal = tex_dic[normal_name] normal.image.colorspace_settings.name = "Non-Color" normal_map = tree.nodes.new('ShaderNodeNormalMap') tree.links.new(normal.outputs[0], normal_map.inputs[1]) # normal_map.inputs["Strength"].default_value = 1.0 tree.links.new(normal_map.outputs[0], principled.inputs["Normal"]) # PZ - specularity? for spec_name in ( "proughnesspackedtexture_[02]", "pspecularmaptexture_[00]", ): if spec_name in tex_dic: specular = tex_dic[spec_name] specular.image.colorspace_settings.name = "Non-Color" tree.links.new(specular.outputs[0], principled.inputs["Specular"]) # PZ - roughness? for roughness_name in ( "proughnesspackedtexture_[01]", "pbasenormaltexture_[2]"): # "pspecularmaptexture_[01]" ? if roughness_name in tex_dic: roughness = tex_dic[roughness_name] roughness.image.colorspace_settings.name = "Non-Color" tree.links.new(roughness.outputs[0], principled.inputs["Roughness"]) # JWE dinos - metalness for metal_name in ("pbasepackedtexture_[02]", ): if metal_name in tex_dic: metal = tex_dic[metal_name] metal.image.colorspace_settings.name = "Non-Color" tree.links.new(metal.outputs[0], principled.inputs["Metallic"]) # alpha alpha = None # JWE billboard: Foliage_Billboard if "pdiffusealphatexture" in tex_dic: alpha = tex_dic["pdiffusealphatexture"] alpha_pass = alpha.outputs[1] elif "pdiffuse_alphatexture" in tex_dic: alpha = tex_dic["pdiffuse_alphatexture"] alpha_pass = alpha.outputs[1] # PZ penguin elif "popacitytexture" in tex_dic: alpha = tex_dic["popacitytexture"] alpha_pass = alpha.outputs[0] elif is_jwe( fgm_data ) and "proughnesspackedtexture_[00]" in tex_dic and "Foliage_Clip" in fgm_data.shader_name: alpha = tex_dic["proughnesspackedtexture_[00]"] alpha_pass = alpha.outputs[0] elif "proughnesspackedtexture_[03]" in tex_dic: alpha = tex_dic["proughnesspackedtexture_[03]"] alpha_pass = alpha.outputs[0] if alpha: # transparency b_mat.blend_method = "CLIP" b_mat.shadow_method = "CLIP" attr_dict = fgm_data.get_attr_dict() if "palphatestref" in attr_dict: b_mat.alpha_threshold = attr_dict["palphatestref"].value[0] transp = tree.nodes.new('ShaderNodeBsdfTransparent') alpha_mixer = tree.nodes.new('ShaderNodeMixShader') tree.links.new(alpha_pass, alpha_mixer.inputs[0]) tree.links.new(transp.outputs[0], alpha_mixer.inputs[1]) tree.links.new(principled.outputs[0], alpha_mixer.inputs[2]) tree.links.new(alpha_mixer.outputs[0], output.inputs[0]) alpha_mixer.update() # no alpha else: b_mat.blend_method = "OPAQUE" tree.links.new(principled.outputs[0], output.inputs[0]) nodes_iterate(tree, output) return b_mat
def create_material(in_dir, matname): print(f"Importing material {matname}") # only create the material if it doesn't exist in the blend file, then just grab it # but we overwrite its contents anyway if matname not in bpy.data.materials: mat = bpy.data.materials.new(matname) else: mat = bpy.data.materials[matname] fgm_path = os.path.join(in_dir, matname + ".fgm") # print(fgm_path) try: fgm_data = FgmFile() fgm_data.load(fgm_path) except FileNotFoundError: print(f"{fgm_path} does not exist!") return mat # base_index = fgm_data.textures[0].layers[1] # height_index = fgm_data.textures[1].layers[1] tree = get_tree(mat) output = tree.nodes.new('ShaderNodeOutputMaterial') principled = tree.nodes.new('ShaderNodeBsdfPrincipled') all_textures = [ file for file in os.listdir(in_dir) if file.lower().endswith(".png") ] # map texture names to node tex_dic = {} for fgm_texture in fgm_data.textures: png_base = f"{matname}.{fgm_texture.name}".lower() if "blendweights" in png_base or "warpoffset" in png_base: continue textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: png_base = png_base.lower().replace("_eyes", "").replace( "_fin", "").replace("_shell", "") textures = [ file for file in all_textures if file.lower().startswith(png_base) ] if not textures: textures = [ png_base + ".png", ] # print(textures) for png_name in textures: png_path = os.path.join(in_dir, png_name) b_tex = load_tex(tree, png_path) k = png_name.lower().split(".")[1] tex_dic[k] = b_tex # get diffuse and AO for diffuse_name in ("pbasediffusetexture", "pbasecolourtexture", "pbasecolourandmasktexture"): # get diffuse if diffuse_name in tex_dic: diffuse = tex_dic[diffuse_name] # get AO for ao_name in ("paotexture", "pbasepackedtexture_03"): if ao_name in tex_dic: ao = tex_dic[ao_name] ao.image.colorspace_settings.name = "Non-Color" # apply AO to diffuse diffuse_premix = tree.nodes.new('ShaderNodeMixRGB') diffuse_premix.blend_type = "MULTIPLY" diffuse_premix.inputs["Fac"].default_value = .25 tree.links.new(diffuse.outputs[0], diffuse_premix.inputs["Color1"]) tree.links.new(ao.outputs[0], diffuse_premix.inputs["Color2"]) diffuse = diffuse_premix break # link finished diffuse to shader tree.links.new(diffuse.outputs[0], principled.inputs["Base Color"]) break if "pnormaltexture" in tex_dic: normal = tex_dic["pnormaltexture"] normal.image.colorspace_settings.name = "Non-Color" normal_map = tree.nodes.new('ShaderNodeNormalMap') tree.links.new(normal.outputs[0], normal_map.inputs[1]) # normal_map.inputs["Strength"].default_value = 1.0 tree.links.new(normal_map.outputs[0], principled.inputs["Normal"]) # PZ - specularity? for spec_name in ("proughnesspackedtexture_02", ): if spec_name in tex_dic: specular = tex_dic[spec_name] specular.image.colorspace_settings.name = "Non-Color" tree.links.new(specular.outputs[0], principled.inputs["Specular"]) # PZ - roughness? for roughness_name in ("proughnesspackedtexture_01", ): if roughness_name in tex_dic: roughness = tex_dic[roughness_name] roughness.image.colorspace_settings.name = "Non-Color" tree.links.new(roughness.outputs[0], principled.inputs["Roughness"]) # JWE dinos - metalness for metal_name in ("pbasepackedtexture_02", ): if metal_name in tex_dic: metal = tex_dic[metal_name] metal.image.colorspace_settings.name = "Non-Color" tree.links.new(metal.outputs[0], principled.inputs["Metallic"]) # alpha if "proughnesspackedtexture_03" in tex_dic: # transparency mat.blend_method = "CLIP" mat.shadow_method = "CLIP" for attrib in fgm_data.attributes: if attrib.name.lower() == "palphatestref": mat.alpha_threshold = attrib.value[0] break # if material.AlphaBlendEnable: # mat.blend_method = "BLEND" transp = tree.nodes.new('ShaderNodeBsdfTransparent') alpha_mixer = tree.nodes.new('ShaderNodeMixShader') alpha = tex_dic["proughnesspackedtexture_03"] tree.links.new(alpha.outputs[0], alpha_mixer.inputs[0]) tree.links.new(transp.outputs[0], alpha_mixer.inputs[1]) tree.links.new(principled.outputs[0], alpha_mixer.inputs[2]) tree.links.new(alpha_mixer.outputs[0], output.inputs[0]) alpha_mixer.update() # no alpha else: mat.blend_method = "OPAQUE" tree.links.new(principled.outputs[0], output.inputs[0]) nodes_iterate(tree, output) return mat
class MainWindow(widgets.MainWindow): def __init__(self): widgets.MainWindow.__init__(self, "FGM Editor", ) self.resize(800, 600) self.fgm_data = FgmFile() self.tooltips = config.read_config("ovl_util/tooltips/fgm.txt") self.games = [g.value for g in games] self.fgm_dict = None self.cleaner = QtCore.QObjectCleanupHandler() self.scrollarea = QtWidgets.QScrollArea(self) self.scrollarea.setWidgetResizable(True) self.setCentralWidget(self.scrollarea) # the actual scrollable stuff self.widget = QtWidgets.QWidget() self.scrollarea.setWidget(self.widget) self.game_container = widgets.LabelCombo("Game:", self.games) self.game_container.entry.currentIndexChanged.connect(self.game_changed) self.game_container.entry.setEditable(False) self.file_widget = widgets.FileWidget(self, self.cfg, dtype="FGM") self.shader_choice = widgets.LabelCombo("Shader:", ()) self.shader_choice.entry.activated.connect(self.shader_changed) self.attribute_choice = widgets.LabelCombo("Attribute:", ()) self.texture_choice = widgets.LabelCombo("Texture:", ()) self.attribute_add = QtWidgets.QPushButton("Add Attribute") self.attribute_add.clicked.connect(self.add_attribute) self.texture_add = QtWidgets.QPushButton("Add Texture") self.texture_add.clicked.connect(self.add_texture) self.tex_container = ProptertyContainer(self, "Textures") self.attrib_container = ProptertyContainer(self, "Attributes") self.game_changed() # self.populate_choices() self.shader_changed() vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.file_widget) vbox.addWidget(self.game_container) vbox.addWidget(self.shader_choice) vbox.addWidget(self.attribute_choice) vbox.addWidget(self.attribute_add) vbox.addWidget(self.texture_choice) vbox.addWidget(self.texture_add) vbox.addWidget(self.tex_container) vbox.addWidget(self.attrib_container) vbox.addStretch(1) self.widget.setLayout(vbox) main_menu = self.menuBar() file_menu = main_menu.addMenu('File') help_menu = main_menu.addMenu('Help') button_data = ( (file_menu, "Open", self.file_widget.ask_open, "CTRL+O", "dir"), (file_menu, "Save", self.save_fgm, "CTRL+S", "save"), (file_menu, "Save As", self.save_as_fgm, "CTRL+SHIFT+S", "save"), (file_menu, "Exit", self.close, "", "exit"), (help_menu, "Report Bug", self.report_bug, "", "report"), (help_menu, "Documentation", self.online_support, "", "manual") ) self.add_to_menu(button_data) def game_changed(self,): game = self.game_container.entry.currentText() logging.info(f"Changed game to {game}") try: set_game(self.fgm_data.context, game) set_game(self.fgm_data, game) except BaseException as err: print(err) if is_jwe2(self.fgm_data): self.fgm_dict = fgm_jwe2 elif is_pz16(self.fgm_data) or is_pz(self.fgm_data): self.fgm_dict = fgm_pz else: self.fgm_dict = None self.populate_choices() def populate_choices(self): if self.fgm_dict: self.shader_choice.entry.clear() self.shader_choice.entry.addItems(sorted(self.fgm_dict.shaders)) self.attribute_choice.entry.clear() self.attribute_choice.entry.addItems(sorted(self.fgm_dict.attributes)) self.texture_choice.entry.clear() self.texture_choice.entry.addItems(sorted(self.fgm_dict.textures)) def shader_changed(self,): self.fgm_data.shader_name = self.shader_choice.entry.currentText() def add_attribute(self,): if self.fgm_dict: attrib_name = self.attribute_choice.entry.currentText() self.fgm_data.add_attrib(attrib_name, self.fgm_dict.attributes[attrib_name]) self.attrib_container.update_gui(self.fgm_data.attributes) def add_texture(self,): tex_name = self.texture_choice.entry.currentText() self.fgm_data.add_texture(tex_name) self.tex_container.update_gui(self.fgm_data.textures) @property def fgm_name(self,): return self.file_widget.entry.text() def create_grid(self,): g = QtWidgets.QGridLayout() g.setHorizontalSpacing(3) g.setVerticalSpacing(0) return g def clear_layout(self, layout): w = QtWidgets.QWidget() w.setLayout(layout) # while layout.count(): # item = layout.takeAt(0) # widget = item.widget() # # if widget has some id attributes you need to # # save in a list to maintain order, you can do that here # # i.e.: aList.append(widget.someId) # widget.deleteLater() def load(self): if self.file_widget.filepath: try: self.fgm_data.load(self.file_widget.filepath) game = get_game(self.fgm_data)[0] logging.debug(f"from game {game}") self.game_container.entry.setText(game.value) self.game_changed() self.shader_choice.entry.setText(self.fgm_data.shader_name) self.tex_container.update_gui(self.fgm_data.textures) self.attrib_container.update_gui(self.fgm_data.attributes) except Exception as ex: traceback.print_exc() ovl_util.interaction.showdialog(str(ex)) logging.warning(ex) logging.info("Done!") def save_fgm(self): if self.file_widget.filepath: self.fgm_data.save(self.file_widget.filepath) def save_as_fgm(self): file_out = QtWidgets.QFileDialog.getSaveFileName(self, 'Save FGM', os.path.join(self.cfg.get("dir_fgms_out", "C://"), self.fgm_name), "FGM files (*.fgm)",)[0] if file_out: self.cfg["dir_fgms_out"], fgm_name = os.path.split(file_out) try: self.fgm_data.save(file_out) print(self.fgm_data) except BaseException as err: traceback.print_exc() interaction.showdialog(str(err)) logging.error(err) logging.info("Done!")