From ea9dc689e07f1f65edbaac9bfcbf230332777900 Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 7 Oct 2024 09:49:19 +0100 Subject: [PATCH 1/5] Ui and refactor f3d.__init.py__ To reduce main __init.py__ bloat --- __init__.py | 96 +---------- fast64_internal/__init__.py | 1 - fast64_internal/f3d/__init__.py | 135 +++++++++++++++- .../f3d/bsdf_converter/__init__.py | 10 ++ fast64_internal/f3d/bsdf_converter/classes.py | 11 ++ .../f3d/bsdf_converter/operators.py | 30 ++++ fast64_internal/f3d/bsdf_converter/ui.py | 11 ++ fast64_internal/f3d_material_converter.py | 152 ++---------------- 8 files changed, 212 insertions(+), 234 deletions(-) create mode 100644 fast64_internal/f3d/bsdf_converter/__init__.py create mode 100644 fast64_internal/f3d/bsdf_converter/classes.py create mode 100644 fast64_internal/f3d/bsdf_converter/operators.py create mode 100644 fast64_internal/f3d/bsdf_converter/ui.py diff --git a/__init__.py b/__init__.py index d3e5c548c..bd8835a96 100644 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ from . import addon_updater_ops -from .fast64_internal.utility import prop_split, multilineLabel, draw_and_check_tab +from .fast64_internal.utility import prop_split from .fast64_internal.repo_settings import ( draw_repo_settings, @@ -26,26 +26,9 @@ from .fast64_internal.mk64 import MK64_Properties, mk64_register, mk64_unregister -from .fast64_internal.f3d.f3d_material import ( - F3D_MAT_CUR_VERSION, - mat_register, - mat_unregister, - check_or_ask_color_management, -) -from .fast64_internal.f3d.f3d_render_engine import render_engine_register, render_engine_unregister -from .fast64_internal.f3d.f3d_writer import f3d_writer_register, f3d_writer_unregister -from .fast64_internal.f3d.f3d_parser import f3d_parser_register, f3d_parser_unregister -from .fast64_internal.f3d.flipbook import flipbook_register, flipbook_unregister -from .fast64_internal.f3d.op_largetexture import op_largetexture_register, op_largetexture_unregister, ui_oplargetexture - -from .fast64_internal.f3d_material_converter import ( - MatUpdateConvert, - upgrade_f3d_version_all_meshes, - bsdf_conv_register, - bsdf_conv_unregister, - bsdf_conv_panel_regsiter, - bsdf_conv_panel_unregsiter, -) +from .fast64_internal.f3d import f3d_register, f3d_unregister +from .fast64_internal.f3d.f3d_material import F3D_MAT_CUR_VERSION, check_or_ask_color_management +from .fast64_internal.f3d_material_converter import upgrade_f3d_version_all_meshes from .fast64_internal.render_settings import ( Fast64RenderSettings_Properties, @@ -73,36 +56,6 @@ ) -class F3D_GlobalSettingsPanel(bpy.types.Panel): - bl_idname = "F3D_PT_global_settings" - bl_label = "F3D Global Settings" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Fast64" - - @classmethod - def poll(cls, context): - return True - - # called every frame - def draw(self, context): - col = self.layout.column() - col.scale_y = 1.1 # extra padding - prop_split(col, context.scene, "f3d_type", "F3D Microcode") - col.prop(context.scene, "saveTextures") - col.prop(context.scene, "f3d_simple", text="Simple Material UI") - col.prop(context.scene, "exportInlineF3D", text="Bleed and Inline Material Exports") - if context.scene.exportInlineF3D: - multilineLabel( - col.box(), - "While inlining, all meshes will be restored to world default values.\n You can configure these values in the world properties tab.", - icon="INFO", - ) - col.prop(context.scene, "ignoreTextureRestrictions") - if context.scene.ignoreTextureRestrictions: - col.box().label(text="Width/height must be < 1024. Must be png format.") - - class Fast64_GlobalSettingsPanel(bpy.types.Panel): bl_idname = "FAST64_PT_global_settings" bl_label = "Fast64 Global Settings" @@ -135,26 +88,6 @@ def draw(self, context): col.prop(fast64_settings, "prefer_rgba_over_ci") -class Fast64_GlobalToolsPanel(bpy.types.Panel): - bl_idname = "FAST64_PT_global_tools" - bl_label = "Fast64 Tools" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Fast64" - - @classmethod - def poll(cls, context): - return True - - # called every frame - def draw(self, context): - col = self.layout.column() - col.operator(ArmatureApplyWithMeshOperator.bl_idname) - # col.operator(CreateMetarig.bl_idname) - ui_oplargetexture(col, context) - addon_updater_ops.update_notice_box_ui(self, context) - - def repo_path_update(self, context): load_repo_settings(context.scene, abspath(self.repo_settings_path), True) @@ -316,9 +249,7 @@ def draw(self, context): Fast64_Properties, Fast64_BoneProperties, Fast64_ObjectProperties, - F3D_GlobalSettingsPanel, Fast64_GlobalSettingsPanel, - Fast64_GlobalToolsPanel, UpgradeF3DMaterialsDialog, ) @@ -418,9 +349,7 @@ def register(): addon_updater_ops.register(bl_info) utility_anim_register() - mat_register() - render_engine_register() - bsdf_conv_register() + f3d_register(True) sm64_register(True) oot_register(True) mk64_register(True) @@ -430,12 +359,6 @@ def register(): for cls in classes: register_class(cls) - bsdf_conv_panel_regsiter() - f3d_writer_register() - flipbook_register() - f3d_parser_register() - op_largetexture_register() - # ROM bpy.types.Scene.ignoreTextureRestrictions = bpy.props.BoolProperty(name="Ignore Texture Restrictions") @@ -464,17 +387,10 @@ def register(): # called on add-on disabling def unregister(): utility_anim_unregister() - op_largetexture_unregister() - flipbook_unregister() - f3d_writer_unregister() - f3d_parser_unregister() + f3d_unregister(True) sm64_unregister(True) oot_unregister(True) mk64_unregister(True) - mat_unregister() - bsdf_conv_unregister() - bsdf_conv_panel_unregsiter() - render_engine_unregister() del bpy.types.Scene.fullTraceback del bpy.types.Scene.ignoreTextureRestrictions diff --git a/fast64_internal/__init__.py b/fast64_internal/__init__.py index 6bb605c2a..ade64e5ee 100644 --- a/fast64_internal/__init__.py +++ b/fast64_internal/__init__.py @@ -1,5 +1,4 @@ from .f3d_material_converter import * -from .f3d import * from .sm64 import * from .oot import * # is this really needed? from .panels import * diff --git a/fast64_internal/f3d/__init__.py b/fast64_internal/f3d/__init__.py index 9975de5d3..b8e3f4da4 100644 --- a/fast64_internal/f3d/__init__.py +++ b/fast64_internal/f3d/__init__.py @@ -1,4 +1,131 @@ -from .f3d_parser import * -from .f3d_material import * -from .f3d_render_engine import * -from .f3d_gbi import * +import bpy +from bpy.utils import register_class, unregister_class + +from ... import addon_updater_ops +from ..utility_anim import ArmatureApplyWithMeshOperator +from ..utility import prop_split, multilineLabel + +from .f3d_material import mat_register, mat_unregister +from .f3d_render_engine import render_engine_register, render_engine_unregister +from .f3d_writer import f3d_writer_register, f3d_writer_unregister +from .f3d_parser import f3d_parser_register, f3d_parser_unregister +from .flipbook import flipbook_register, flipbook_unregister +from .op_largetexture import op_largetexture_register, op_largetexture_unregister, ui_oplargetexture +from .bsdf_converter import bsdf_converter_register, bsdf_converter_unregister, bsdf_converter_panel_draw + + +class F3D_GlobalSettingsPanel(bpy.types.Panel): + bl_idname = "F3D_PT_global_settings" + bl_label = "F3D Global Settings" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Fast64" + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + col.scale_y = 1.1 # extra padding + prop_split(col, context.scene, "f3d_type", "F3D Microcode") + col.prop(context.scene, "saveTextures") + col.prop(context.scene, "f3d_simple", text="Simple Material UI") + col.prop(context.scene, "exportInlineF3D", text="Bleed and Inline Material Exports") + if context.scene.exportInlineF3D: + multilineLabel( + col.box(), + "While inlining, all meshes will be restored to world default values.\n You can configure these values in the world properties tab.", + icon="INFO", + ) + col.prop(context.scene, "ignoreTextureRestrictions") + if context.scene.ignoreTextureRestrictions: + col.box().label(text="Width/height must be < 1024. Must be png format.") + + +class Fast64_GlobalToolsPanel(bpy.types.Panel): + bl_idname = "FAST64_PT_global_tools" + bl_label = "Fast64 Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Fast64" + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + # TODO: figure out why a circular import is happening, this is bad + from ..f3d_material_converter import ( + mat_updater_draw, + ) + + col = self.layout.column() + col.operator(ArmatureApplyWithMeshOperator.bl_idname) + # col.operator(CreateMetarig.bl_idname) + ui_oplargetexture(col, context) + col.separator() + + box = col.box().column() + box.label(text="Material Updater") + mat_updater_draw(box, context) + col.separator() + + box = col.box().column() + box.label(text="BSDF Converter") + bsdf_converter_panel_draw(box, context) + col.separator() + + addon_updater_ops.update_notice_box_ui(self, context) + + +classes = tuple() +panel_classes = (F3D_GlobalSettingsPanel, Fast64_GlobalToolsPanel) + + +def f3d_panel_register(): + for cls in panel_classes: + register_class(cls) + + +def f3d_panel_unregister(): + for cls in reversed(panel_classes): + unregister_class(cls) + + +def f3d_register(register_panels: bool): + from ..f3d_material_converter import mat_updater_register + + for cls in classes: + register_class(cls) + mat_register() + render_engine_register() + mat_updater_register() + f3d_writer_register() + flipbook_register() + f3d_parser_register() + op_largetexture_register() + bsdf_converter_register() + + if register_panels: + f3d_panel_register() + + +def f3d_unregister(unregister_panels): + from ..f3d_material_converter import mat_updater_unregister + + for cls in reversed(classes): + unregister_class(cls) + bsdf_converter_unregister() + op_largetexture_unregister() + f3d_parser_unregister() + flipbook_unregister() + f3d_writer_unregister() + mat_updater_unregister() + render_engine_unregister() + mat_unregister() + + if unregister_panels: + f3d_panel_unregister() diff --git a/fast64_internal/f3d/bsdf_converter/__init__.py b/fast64_internal/f3d/bsdf_converter/__init__.py new file mode 100644 index 000000000..06cc53284 --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/__init__.py @@ -0,0 +1,10 @@ +from .operators import bsdf_converter_ops_register, bsdf_converter_ops_unregister +from .ui import bsdf_converter_panel_draw + + +def bsdf_converter_register(): + bsdf_converter_ops_register() + + +def bsdf_converter_unregister(): + bsdf_converter_ops_unregister() diff --git a/fast64_internal/f3d/bsdf_converter/classes.py b/fast64_internal/f3d/bsdf_converter/classes.py new file mode 100644 index 000000000..bfc148369 --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/classes.py @@ -0,0 +1,11 @@ +import dataclasses + + +@dataclasses.dataclass +class SimpleN64Texture: + name: str + + +@dataclasses.dataclass +class SimpleN64Material: + name: str diff --git a/fast64_internal/f3d/bsdf_converter/operators.py b/fast64_internal/f3d/bsdf_converter/operators.py new file mode 100644 index 000000000..d64cb711c --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/operators.py @@ -0,0 +1,30 @@ +from bpy.utils import register_class, unregister_class + +from ...operators import OperatorBase + + +class F3D_ConvertF3DToBSDF(OperatorBase): + bl_idname = "scene.f3d_convert_to_bsdf" + bl_label = "Convert F3D to BSDF" + bl_options = {"REGISTER", "UNDO", "PRESET"} + icon = "MATERIAL" + + +class F3D_ConvertBSDFToF3D(OperatorBase): + bl_idname = "scene.bsdf_convert_to_f3d" + bl_label = "Convert BSDF to F3D" + bl_options = {"REGISTER", "UNDO", "PRESET"} + icon = "NODE_MATERIAL" + + +classes = (F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D) + + +def bsdf_converter_ops_register(): + for cls in classes: + register_class(cls) + + +def bsdf_converter_ops_unregister(): + for cls in reversed(classes): + unregister_class(cls) diff --git a/fast64_internal/f3d/bsdf_converter/ui.py b/fast64_internal/f3d/bsdf_converter/ui.py new file mode 100644 index 000000000..1cc658971 --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/ui.py @@ -0,0 +1,11 @@ +"""Usually this would be panel.py but there is no actual panel since we only draw in the tools panel.""" + +from bpy.types import UILayout, Context + +from .operators import F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D + + +def bsdf_converter_panel_draw(layout: UILayout, _context: Context): + col = layout.column() + F3D_ConvertF3DToBSDF.draw_props(col) + F3D_ConvertBSDFToF3D.draw_props(col) diff --git a/fast64_internal/f3d_material_converter.py b/fast64_internal/f3d_material_converter.py index 992c2c221..744ca79b3 100644 --- a/fast64_internal/f3d_material_converter.py +++ b/fast64_internal/f3d_material_converter.py @@ -1,7 +1,9 @@ # This is not in the f3d package since copying materials requires copying collision settings from all games as well. import bpy +from bpy.types import UILayout, Context from bpy.utils import register_class, unregister_class + from .f3d.f3d_material import * from .f3d.f3d_material_helpers import node_tree_copy from .utility import * @@ -164,105 +166,12 @@ def convertF3DtoNewVersion( traceback.print_exc() -def convertAllBSDFtoF3D(objs, renameUV): - # Dict of non-f3d materials : converted f3d materials - # handles cases where materials are used in multiple objects - materialDict = {} - for obj in objs: - if renameUV: - for uv_layer in obj.data.uv_layers: - uv_layer.name = "UVMap" - for index in range(len(obj.material_slots)): - material = obj.material_slots[index].material - if material is not None and not material.is_f3d: - if material in materialDict: - print("Existing material") - obj.material_slots[index].material = materialDict[material] - else: - print("New material") - convertBSDFtoF3D(obj, index, material, materialDict) - - -def convertBSDFtoF3D(obj, index, material, materialDict): - if not material.use_nodes: - newMaterial = createF3DMat(obj, preset="Shaded Solid", index=index) - with bpy.context.temp_override(material=newMaterial): - newMaterial.f3d_mat.default_light_color = material.diffuse_color - updateMatWithName(newMaterial, material, materialDict) - - elif "Principled BSDF" in material.node_tree.nodes: - tex0Node = material.node_tree.nodes["Principled BSDF"].inputs["Base Color"] - if len(tex0Node.links) == 0: - newMaterial = createF3DMat(obj, preset=getDefaultMaterialPreset("Shaded Solid"), index=index) - with bpy.context.temp_override(material=newMaterial): - newMaterial.f3d_mat.default_light_color = tex0Node.default_value - updateMatWithName(newMaterial, material, materialDict) - else: - if isinstance(tex0Node.links[0].from_node, bpy.types.ShaderNodeTexImage): - if "convert_preset" in material: - presetName = material["convert_preset"] - if presetName not in [enumValue[0] for enumValue in enumMaterialPresets]: - raise PluginError( - "During BSDF to F3D conversion, for material '" - + material.name - + "'," - + " enum '" - + presetName - + "' was not found in material preset enum list." - ) - else: - presetName = getDefaultMaterialPreset("Shaded Texture") - newMaterial = createF3DMat(obj, preset=presetName, index=index) - with bpy.context.temp_override(material=newMaterial): - newMaterial.f3d_mat.tex0.tex = tex0Node.links[0].from_node.image - updateMatWithName(newMaterial, material, materialDict) - else: - print("Principled BSDF material does not have an Image Node attached to its Base Color.") - else: - print("Material is not a Principled BSDF or non-node material.") - - def updateMatWithName(f3dMat, oldMat, materialDict): f3dMat.name = oldMat.name + "_f3d" update_preset_manual(f3dMat, bpy.context) materialDict[oldMat] = f3dMat -class BSDFConvert(bpy.types.Operator): - # set bl_ properties - bl_idname = "object.convert_bsdf" - bl_label = "Principled BSDF to F3D Converter" - bl_options = {"REGISTER", "UNDO", "PRESET"} - - # Called on demand (i.e. button press, menu item) - # Can also be called from operator search menu (Spacebar) - def execute(self, context): - try: - if context.mode != "OBJECT": - raise PluginError("Operator can only be used in object mode.") - - if context.scene.bsdf_conv_all: - convertAllBSDFtoF3D( - [obj for obj in bpy.data.objects if obj.type == "MESH"], - context.scene.rename_uv_maps, - ) - else: - if len(context.selected_objects) == 0: - raise PluginError("Mesh not selected.") - elif type(context.selected_objects[0].data) is not bpy.types.Mesh: - raise PluginError("Mesh not selected.") - - obj = context.selected_objects[0] - convertAllBSDFtoF3D([obj], context.scene.rename_uv_maps) - - except Exception as e: - raisePluginError(self, e) - return {"CANCELLED"} - - self.report({"INFO"}, "Created F3D material.") - return {"FINISHED"} # must return a set - - class MatUpdateConvert(bpy.types.Operator): # set bl_ properties bl_idname = "object.convert_f3d_update" @@ -297,62 +206,27 @@ def execute(self, context): return {"FINISHED"} # must return a set -class F3DMaterialConverterPanel(bpy.types.Panel): - bl_label = "F3D Material Converter" - bl_idname = "MATERIAL_PT_F3D_Material_Converter" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Fast64" - - @classmethod - def poll(cls, context): - return True - # return hasattr(context, 'object') and context.object is not None and \ - # isinstance(context.object.data, bpy.types.Mesh) - - def draw(self, context): - # mesh = context.object.data - self.layout.operator(BSDFConvert.bl_idname) - self.layout.prop(context.scene, "bsdf_conv_all") - self.layout.prop(context.scene, "rename_uv_maps") - op = self.layout.operator(MatUpdateConvert.bl_idname) - op.update_conv_all = context.scene.update_conv_all - self.layout.prop(context.scene, "update_conv_all") - self.layout.operator(ReloadDefaultF3DPresets.bl_idname) - - -bsdf_conv_classes = ( - BSDFConvert, - MatUpdateConvert, -) +def mat_updater_draw(layout: UILayout, context: Context): + col = layout.column() + op = col.operator(MatUpdateConvert.bl_idname) + op.update_conv_all = context.scene.update_conv_all + col.prop(context.scene, "update_conv_all") + col.operator(ReloadDefaultF3DPresets.bl_idname) -bsdf_conv_panel_classes = (F3DMaterialConverterPanel,) - -def bsdf_conv_panel_regsiter(): - for cls in bsdf_conv_panel_classes: - register_class(cls) - - -def bsdf_conv_panel_unregsiter(): - for cls in bsdf_conv_panel_classes: - unregister_class(cls) +mat_updater_classes = (MatUpdateConvert,) -def bsdf_conv_register(): - for cls in bsdf_conv_classes: +def mat_updater_register(): + for cls in mat_updater_classes: register_class(cls) # Moved to Level Root - bpy.types.Scene.bsdf_conv_all = bpy.props.BoolProperty(name="Convert all objects", default=True) bpy.types.Scene.update_conv_all = bpy.props.BoolProperty(name="Convert all objects", default=True) - bpy.types.Scene.rename_uv_maps = bpy.props.BoolProperty(name="Rename UV maps", default=True) -def bsdf_conv_unregister(): - for cls in bsdf_conv_classes: +def mat_updater_unregister(): + for cls in mat_updater_classes: unregister_class(cls) - del bpy.types.Scene.bsdf_conv_all del bpy.types.Scene.update_conv_all - del bpy.types.Scene.rename_uv_maps From bcbcf097bb4dd4ecb9e4eeeea4a15d8fa39961a0 Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 7 Oct 2024 11:35:54 +0100 Subject: [PATCH 2/5] draw some ui and props and stuff, rename --- __init__.py | 3 +- fast64_internal/f3d/__init__.py | 24 +++++++++++--- .../f3d/bsdf_converter/__init__.py | 3 ++ fast64_internal/f3d/bsdf_converter/classes.py | 11 ------- .../f3d/bsdf_converter/converter.py | 20 ++++++++++++ .../f3d/bsdf_converter/operators.py | 28 +++++++++++++++++ .../f3d/bsdf_converter/properties.py | 31 +++++++++++++++++++ fast64_internal/f3d/bsdf_converter/ui.py | 3 +- 8 files changed, 106 insertions(+), 17 deletions(-) delete mode 100644 fast64_internal/f3d/bsdf_converter/classes.py create mode 100644 fast64_internal/f3d/bsdf_converter/converter.py create mode 100644 fast64_internal/f3d/bsdf_converter/properties.py diff --git a/__init__.py b/__init__.py index bd8835a96..fff8ad59c 100644 --- a/__init__.py +++ b/__init__.py @@ -26,7 +26,7 @@ from .fast64_internal.mk64 import MK64_Properties, mk64_register, mk64_unregister -from .fast64_internal.f3d import f3d_register, f3d_unregister +from .fast64_internal.f3d import f3d_register, f3d_unregister, F3D_Properties from .fast64_internal.f3d.f3d_material import F3D_MAT_CUR_VERSION, check_or_ask_color_management from .fast64_internal.f3d_material_converter import upgrade_f3d_version_all_meshes @@ -156,6 +156,7 @@ class Fast64_Properties(bpy.types.PropertyGroup): sm64: bpy.props.PointerProperty(type=SM64_Properties, name="SM64 Properties") oot: bpy.props.PointerProperty(type=OOT_Properties, name="OOT Properties") mk64: bpy.props.PointerProperty(type=MK64_Properties, name="MK64 Properties") + f3d: bpy.props.PointerProperty(type=F3D_Properties, name="F3D Properties") settings: bpy.props.PointerProperty(type=Fast64Settings_Properties, name="Fast64 Settings") renderSettings: bpy.props.PointerProperty(type=Fast64RenderSettings_Properties, name="Fast64 Render Settings") diff --git a/fast64_internal/f3d/__init__.py b/fast64_internal/f3d/__init__.py index b8e3f4da4..d8683f623 100644 --- a/fast64_internal/f3d/__init__.py +++ b/fast64_internal/f3d/__init__.py @@ -1,5 +1,7 @@ import bpy from bpy.utils import register_class, unregister_class +from bpy.props import PointerProperty +from bpy.types import PropertyGroup from ... import addon_updater_ops from ..utility_anim import ArmatureApplyWithMeshOperator @@ -11,7 +13,12 @@ from .f3d_parser import f3d_parser_register, f3d_parser_unregister from .flipbook import flipbook_register, flipbook_unregister from .op_largetexture import op_largetexture_register, op_largetexture_unregister, ui_oplargetexture -from .bsdf_converter import bsdf_converter_register, bsdf_converter_unregister, bsdf_converter_panel_draw +from .bsdf_converter import ( + bsdf_converter_register, + bsdf_converter_unregister, + bsdf_converter_panel_draw, + F3D_BSDFConverterProperties, +) class F3D_GlobalSettingsPanel(bpy.types.Panel): @@ -81,7 +88,16 @@ def draw(self, context): addon_updater_ops.update_notice_box_ui(self, context) -classes = tuple() +class F3D_Properties(PropertyGroup): + """ + Properties in scene.fast64.f3d. + All new scene f3d properties should be children of this property group. + """ + + bsdf_converter: PointerProperty(name="BSDF Converter", type=F3D_BSDFConverterProperties) + + +classes = (F3D_Properties,) panel_classes = (F3D_GlobalSettingsPanel, Fast64_GlobalToolsPanel) @@ -98,8 +114,6 @@ def f3d_panel_unregister(): def f3d_register(register_panels: bool): from ..f3d_material_converter import mat_updater_register - for cls in classes: - register_class(cls) mat_register() render_engine_register() mat_updater_register() @@ -108,6 +122,8 @@ def f3d_register(register_panels: bool): f3d_parser_register() op_largetexture_register() bsdf_converter_register() + for cls in classes: + register_class(cls) if register_panels: f3d_panel_register() diff --git a/fast64_internal/f3d/bsdf_converter/__init__.py b/fast64_internal/f3d/bsdf_converter/__init__.py index 06cc53284..b3540e514 100644 --- a/fast64_internal/f3d/bsdf_converter/__init__.py +++ b/fast64_internal/f3d/bsdf_converter/__init__.py @@ -1,10 +1,13 @@ from .operators import bsdf_converter_ops_register, bsdf_converter_ops_unregister from .ui import bsdf_converter_panel_draw +from .properties import F3D_BSDFConverterProperties, bsdf_converter_props_register, bsdf_converter_props_unregister def bsdf_converter_register(): bsdf_converter_ops_register() + bsdf_converter_props_register() def bsdf_converter_unregister(): bsdf_converter_ops_unregister() + bsdf_converter_props_unregister() diff --git a/fast64_internal/f3d/bsdf_converter/classes.py b/fast64_internal/f3d/bsdf_converter/classes.py deleted file mode 100644 index bfc148369..000000000 --- a/fast64_internal/f3d/bsdf_converter/classes.py +++ /dev/null @@ -1,11 +0,0 @@ -import dataclasses - - -@dataclasses.dataclass -class SimpleN64Texture: - name: str - - -@dataclasses.dataclass -class SimpleN64Material: - name: str diff --git a/fast64_internal/f3d/bsdf_converter/converter.py b/fast64_internal/f3d/bsdf_converter/converter.py new file mode 100644 index 000000000..d1d2bd9ed --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/converter.py @@ -0,0 +1,20 @@ +from bpy.types import Object + +import dataclasses + + +@dataclasses.dataclass +class SimpleN64Texture: + name: str + + +@dataclasses.dataclass +class SimpleN64Material: + name: str + + +def obj_to_f3d(obj: Object): + print(f"Converting BSDF materials in {obj.name}") + +def obj_to_bsdf(obj: Object): + print(f"Converting F3D materials in {obj.name}") \ No newline at end of file diff --git a/fast64_internal/f3d/bsdf_converter/operators.py b/fast64_internal/f3d/bsdf_converter/operators.py index d64cb711c..95722e597 100644 --- a/fast64_internal/f3d/bsdf_converter/operators.py +++ b/fast64_internal/f3d/bsdf_converter/operators.py @@ -1,7 +1,13 @@ from bpy.utils import register_class, unregister_class +from bpy.props import EnumProperty +from bpy.types import Context from ...operators import OperatorBase +from .converter import obj_to_f3d, obj_to_bsdf + +converter_enum = [("Object", "Selected Objects", "Object"), ("Scene", "Scene", "Scene")] + class F3D_ConvertF3DToBSDF(OperatorBase): bl_idname = "scene.f3d_convert_to_bsdf" @@ -9,6 +15,17 @@ class F3D_ConvertF3DToBSDF(OperatorBase): bl_options = {"REGISTER", "UNDO", "PRESET"} icon = "MATERIAL" + converter_type: EnumProperty(items=converter_enum) + + def execute_operator(self, context: Context): + if self.converter_type == "Object": + for obj in context.selected_objects: + obj_to_f3d(obj) + elif self.converter_type == "Scene": + for obj in context.scene.objects: + obj_to_f3d(obj) + self.report({"INFO"}, "Done.") + class F3D_ConvertBSDFToF3D(OperatorBase): bl_idname = "scene.bsdf_convert_to_f3d" @@ -16,6 +33,17 @@ class F3D_ConvertBSDFToF3D(OperatorBase): bl_options = {"REGISTER", "UNDO", "PRESET"} icon = "NODE_MATERIAL" + converter_type: EnumProperty(items=converter_enum) + + def execute_operator(self, context: Context): + if self.converter_type == "Object": + for obj in context.selected_objects: + obj_to_bsdf(obj) + elif self.converter_type == "Scene": + for obj in context.scene.objects: + obj_to_bsdf(obj) + self.report({"INFO"}, "Done.") + classes = (F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D) diff --git a/fast64_internal/f3d/bsdf_converter/properties.py b/fast64_internal/f3d/bsdf_converter/properties.py new file mode 100644 index 000000000..52aef05fd --- /dev/null +++ b/fast64_internal/f3d/bsdf_converter/properties.py @@ -0,0 +1,31 @@ +from bpy.utils import register_class, unregister_class +from bpy.types import PropertyGroup, UILayout +from bpy.props import EnumProperty + +from ...utility import prop_split, multilineLabel + + +class F3D_BSDFConverterProperties(PropertyGroup): + """ + Properties in scene.fast64.f3d.bsdf_converter + """ + + converter_type: EnumProperty(items=[("Object", "Selected Objects", "Object"), ("Scene", "Scene", "Scene")]) + + def draw_props(self, layout: UILayout): + col = layout.column() + prop_split(col, self, "converter_type", "Converter Type") + + + +classes = (F3D_BSDFConverterProperties,) + + +def bsdf_converter_props_register(): + for cls in classes: + register_class(cls) + + +def bsdf_converter_props_unregister(): + for cls in reversed(classes): + unregister_class(cls) diff --git a/fast64_internal/f3d/bsdf_converter/ui.py b/fast64_internal/f3d/bsdf_converter/ui.py index 1cc658971..0719929fd 100644 --- a/fast64_internal/f3d/bsdf_converter/ui.py +++ b/fast64_internal/f3d/bsdf_converter/ui.py @@ -5,7 +5,8 @@ from .operators import F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D -def bsdf_converter_panel_draw(layout: UILayout, _context: Context): +def bsdf_converter_panel_draw(layout: UILayout, context: Context): col = layout.column() + context.scene.fast64.f3d.bsdf_converter.draw_props(col) F3D_ConvertF3DToBSDF.draw_props(col) F3D_ConvertBSDFToF3D.draw_props(col) From c1e0f4a1bf5219ef9ed8ade22014913121d58114 Mon Sep 17 00:00:00 2001 From: Lila Date: Thu, 10 Oct 2024 12:00:37 +0100 Subject: [PATCH 3/5] WIP --- .../f3d/bsdf_converter/converter.py | 305 +++++++++++++++++- .../f3d/bsdf_converter/operators.py | 91 ++++-- .../f3d/bsdf_converter/properties.py | 5 +- fast64_internal/f3d/bsdf_converter/ui.py | 18 +- fast64_internal/f3d/f3d_material.py | 30 +- 5 files changed, 399 insertions(+), 50 deletions(-) diff --git a/fast64_internal/f3d/bsdf_converter/converter.py b/fast64_internal/f3d/bsdf_converter/converter.py index d1d2bd9ed..75e8bd41e 100644 --- a/fast64_internal/f3d/bsdf_converter/converter.py +++ b/fast64_internal/f3d/bsdf_converter/converter.py @@ -1,20 +1,309 @@ -from bpy.types import Object +import bpy +from bpy.types import Object, Material import dataclasses +from ...utility import get_clean_color, PluginError +from ..f3d_material import ( + combiner_uses, + get_output_method, + is_mat_f3d, + all_combiner_uses, + set_blend_to_output_method, + trunc_10_2, + F3DMaterialProperty, + RDPSettings, + TextureProperty, + CombinerProperty, +) + + +# Ideally we'd use mathutils.Color here but it does not support alpha (and mul for some reason) +@dataclasses.dataclass +class Color: + r: float = 0.0 + g: float = 0.0 + b: float = 0.0 + a: float = 0.0 + + def wrap(self, min_value: float, max_value: float): + def wrap_value(value, min_value=min_value, max_value=max_value): + range_width = max_value - min_value + return ((value - min_value) % range_width) + min_value + + return Color(wrap_value(self.r), wrap_value(self.g), wrap_value(self.b), wrap_value(self.a)) + + def to_clean_list(self): + def round_and_clamp(value): + return round(max(min(value, 1.0), 0.0), 4) + + return [ + round_and_clamp(self.r), + round_and_clamp(self.g), + round_and_clamp(self.b), + round_and_clamp(self.a), + ] + + def __sub__(self, other): + return Color(self.r - other.r, self.g - other.g, self.b - other.b, self.a - other.a) + + def __add__(self, other): + return Color(self.r + other.r, self.g + other.g, self.b + other.b, self.a + other.a) + + def __mul__(self, other): + return Color(self.r * other.r, self.g * other.g, self.b * other.b, self.a * other.a) + + +def get_color_component(inp: str, f3d_mat: F3DMaterialProperty, previous_alpha: float) -> float: + if inp == "0": + return 0.0 + elif inp == "1": + return 1.0 + elif inp.startswith("COMBINED"): + return previous_alpha + elif inp == "LOD_FRACTION": + return 0.0 # Fast64 always uses black, let's do that for now + elif inp == "PRIM_LOD_FRAC": + return f3d_mat.prim_lod_frac + elif inp == "PRIMITIVE_ALPHA": + return f3d_mat.prim_color[3] + elif inp == "ENV_ALPHA": + return f3d_mat.env_color[3] + elif inp == "K4": + return f3d_mat.k4 + elif inp == "K5": + return f3d_mat.k5 + + +def get_color_from_input( + inp: str, previous_color: Color, f3d_mat: F3DMaterialProperty, is_alpha: bool, default_color: Color +) -> Color: + if inp == "COMBINED" and not is_alpha: + return previous_color + elif inp == "CENTER": + return Color(*get_clean_color(f3d_mat.key_center), previous_color.a) + elif inp == "SCALE": + return Color(*list(f3d_mat.key_scale), previous_color.a) + elif inp == "PRIMITIVE": + return Color(*get_clean_color(f3d_mat.prim_color, True)) + elif inp == "ENVIRONMENT": + return Color(*get_clean_color(f3d_mat.env_color, True)) + elif inp == "SHADE": + if f3d_mat.rdp_settings.g_lighting and f3d_mat.set_lights and f3d_mat.use_default_lighting: + return Color(*get_clean_color(f3d_mat.default_light_color), previous_color.a) + return Color(1.0, 1.0, 1.0, previous_color.a) + else: + value = get_color_component(inp, f3d_mat, previous_color.a) + if value is not None: + return Color(value, value, value, value) + return default_color + + +def fake_color_from_cycle(cycle: list[str], previous_color: Color, f3d_mat: F3DMaterialProperty, is_alpha=False): + default_colors = [Color(1.0, 1.0, 1.0, 1.0), Color(), Color(1.0, 1.0, 1.0, 1.0), Color()] + a, b, c, d = [ + get_color_from_input(inp, previous_color, f3d_mat, is_alpha, default_color) + for inp, default_color in zip(cycle, default_colors) + ] + sign_extended_c = c.wrap(-1.0, 1.0001) + unwrapped_result = (a - b) * sign_extended_c + d + result = unwrapped_result.wrap(-0.5, 1.5) + if is_alpha: + result = Color(previous_color.r, previous_color.g, previous_color.b, result.a) + return result + + +def get_fake_color(f3d_mat: F3DMaterialProperty): + """Try to emulate solid colors""" + fake_color = Color() + cycle: CombinerProperty + combiners = [f3d_mat.combiner1] + if f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE": + combiners.append(f3d_mat.combiner2) + for cycle in combiners: + fake_color = fake_color_from_cycle([cycle.A, cycle.B, cycle.C, cycle.D], fake_color, f3d_mat) + fake_color = fake_color_from_cycle( + [cycle.A_alpha, cycle.B_alpha, cycle.C_alpha, cycle.D_alpha], fake_color, f3d_mat, True + ) + return fake_color.to_clean_list() + @dataclasses.dataclass -class SimpleN64Texture: - name: str +class AbstractedN64Texture: + """Very abstracted representation of a N64 texture""" + + tex: bpy.types.Image + offset: tuple[float, float] = (0.0, 0.0) + scale: tuple[float, float] = (1.0, 1.0) + repeat: bool = False @dataclasses.dataclass -class SimpleN64Material: - name: str +class AbstractedN64Material: + """Very abstracted representation of a N64 material""" + + lighting: bool = False + uv_geo: bool = False + point_filtering: bool = False + output_method: str = "OPA" + backface_culling: bool = False + color: Color = dataclasses.field(default_factory=Color) + textures: list[AbstractedN64Texture] = dataclasses.field(default_factory=list) + texture_sets_col: bool = False + texture_sets_alpha: bool = False + + @property + def main_texture(self): + return self.textures[0] if self.textures else None + + +def f3d_tex_to_abstracted(f3d_tex: TextureProperty): + def to_offset(low: float, tex_size: int): + offset = -trunc_10_2(low) * (1.0 / tex_size) + if offset == -0.0: + offset = 0.0 + return offset + if f3d_tex.tex is None: + raise PluginError("No texture set") -def obj_to_f3d(obj: Object): + abstracted_tex = AbstractedN64Texture(f3d_tex.tex, repeat=not f3d_tex.S.clamp or not f3d_tex.T.clamp) + size = f3d_tex.get_tex_size() + if size != [0, 0]: + abstracted_tex.offset = (to_offset(f3d_tex.S.low, size[0]), to_offset(f3d_tex.T.low, size[1])) + abstracted_tex.scale = (2.0 ** (f3d_tex.S.shift * -1.0), 2.0 ** (f3d_tex.T.shift * -1.0)) + + return abstracted_tex + + +def f3d_mat_to_abstracted(material: Material): + f3d_mat: F3DMaterialProperty = material.f3d_mat + rdp: RDPSettings = f3d_mat.rdp_settings + use_dict = all_combiner_uses(f3d_mat) + textures = [f3d_mat.tex0] if use_dict["Texture 0"] and f3d_mat.tex0.tex_set else [] + textures += [f3d_mat.tex1] if use_dict["Texture 1"] and f3d_mat.tex1.tex_set else [] + + abstracted_mat = AbstractedN64Material( + rdp.g_lighting, + rdp.g_tex_gen, + rdp.g_mdsft_text_filt == "G_TF_POINT", + get_output_method(material, True), + rdp.g_cull_back, + get_fake_color(f3d_mat), + ) + for i in range(2): + tex_prop = getattr(f3d_mat, f"tex{i}") + check_list = [f"TEXEL{i}", f"TEXEL{i}_ALPHA"] + sets_color = combiner_uses(f3d_mat, check_list, checkColor=True, checkAlpha=False) + sets_alpha = combiner_uses(f3d_mat, check_list, checkColor=False, checkAlpha=True) + if sets_color or sets_alpha: + abstracted_mat.textures.append(f3d_tex_to_abstracted(tex_prop)) + abstracted_mat.texture_sets_col |= sets_color + abstracted_mat.texture_sets_alpha |= sets_alpha + return abstracted_mat + + +def material_to_bsdf(material: Material): + abstracted_mat = f3d_mat_to_abstracted(material) + + new_material = bpy.data.materials.new(name=material.name) + new_material.use_nodes = True + node_tree = new_material.node_tree + nodes = node_tree.nodes + links = node_tree.links + nodes.clear() + + set_blend_to_output_method(new_material, abstracted_mat.output_method) + new_material.use_backface_culling = abstracted_mat.backface_culling + + node_x = node_y = 0 + + output_node = nodes.new(type="ShaderNodeOutputMaterial") + node_x -= 300 + node_y -= 25 + + # final shader node + if abstracted_mat.lighting: + shader_node = nodes.new(type="ShaderNodeBsdfPrincipled") + else: + shader_node = nodes.new(type="ShaderNodeEmission") + shader_node.location = (node_x, node_y) + node_x -= 300 + links.new(shader_node.outputs[0], output_node.inputs[0]) + + # texture nodes + tex_node_y = node_y + if abstracted_mat.textures: + if abstracted_mat.uv_geo: + uvmap_node = nodes.new(type="ShaderNodeTexCoord") + uvmap_output = uvmap_node.outputs["Camera"] + else: + uvmap_node = nodes.new(type="ShaderNodeUVMap") + uvmap_node.uv_map = "UVMap" + uvmap_output = uvmap_node.outputs["UV"] + uvmap_node.location = (node_x - 200, tex_node_y) + + texture_nodes = [] + for abstracted_tex in abstracted_mat.textures: + tex_node = nodes.new(type="ShaderNodeTexImage") + tex_node.location = (node_x, tex_node_y) + tex_node.image = abstracted_tex.tex + tex_node.extension = "REPEAT" if abstracted_tex.repeat else "EXTEND" + tex_node.interpolation = "Closest" if abstracted_mat.point_filtering else "Linear" + texture_nodes.append(tex_node) + + if abstracted_tex.offset != (0.0, 0.0) or abstracted_tex.scale != (1.0, 1.0): + uvmap_node.location = (node_x - 400, uvmap_node.location[1]) + + mapping_node = nodes.new(type="ShaderNodeMapping") + mapping_node.vector_type = "POINT" + mapping_node.location = (node_x - 200, tex_node_y) + mapping_node.inputs["Location"].default_value = abstracted_tex.offset + (0.0,) + mapping_node.inputs["Scale"].default_value = abstracted_tex.scale + (1.0,) + links.new(uvmap_output, mapping_node.inputs[0]) + links.new(mapping_node.outputs[0], tex_node.inputs[0]) + else: + links.new(uvmap_output, tex_node.inputs[0]) + + tex_node_y -= 300 + + if abstracted_mat.texture_sets_col: + links.new(texture_nodes[0].outputs[0], shader_node.inputs["Base Color"]) + else: + shader_node.inputs["Base Color"].default_value = abstracted_mat.color[:3] + [1.0] + if abstracted_mat.texture_sets_alpha: + links.new(texture_nodes[0].outputs[1], shader_node.inputs["Alpha"]) + else: + shader_node.inputs["Alpha"].default_value = abstracted_mat.color[3] + + return new_material + + +def material_to_f3d(material: Material): + pass + + +def obj_to_f3d(obj: Object, materials: dict[Material, Material]): + assert obj.type == "MESH" print(f"Converting BSDF materials in {obj.name}") + for index, material_slot in enumerate(obj.material_slots): + material = material_slot.material + if material is None or is_mat_f3d(material): + continue + if material in materials: + obj.material_slots[index].material = materials[material] + else: + obj.material_slots[index].material = material_to_f3d(material) + -def obj_to_bsdf(obj: Object): - print(f"Converting F3D materials in {obj.name}") \ No newline at end of file +def obj_to_bsdf(obj: Object, materials: dict[Material, Material]): + assert obj.type == "MESH" + print(f"Converting F3D materials in {obj.name}") + for index, material_slot in enumerate(obj.material_slots): + material = material_slot.material + if material is None or not is_mat_f3d(material): + continue + if material in materials: + obj.material_slots[index].material = materials[material] + else: + obj.material_slots[index].material = material_to_bsdf(material) diff --git a/fast64_internal/f3d/bsdf_converter/operators.py b/fast64_internal/f3d/bsdf_converter/operators.py index 95722e597..a02f1270f 100644 --- a/fast64_internal/f3d/bsdf_converter/operators.py +++ b/fast64_internal/f3d/bsdf_converter/operators.py @@ -1,51 +1,92 @@ +import copy + +import bpy from bpy.utils import register_class, unregister_class -from bpy.props import EnumProperty -from bpy.types import Context +from bpy.props import EnumProperty, BoolProperty +from bpy.types import Context, Object, Material from ...operators import OperatorBase +from ...utility import PluginError from .converter import obj_to_f3d, obj_to_bsdf converter_enum = [("Object", "Selected Objects", "Object"), ("Scene", "Scene", "Scene")] -class F3D_ConvertF3DToBSDF(OperatorBase): +class F3D_ConvertBSDF(OperatorBase): bl_idname = "scene.f3d_convert_to_bsdf" bl_label = "Convert F3D to BSDF" bl_options = {"REGISTER", "UNDO", "PRESET"} icon = "MATERIAL" + direction: EnumProperty(items=[("F3D", "BSDF To F3D", "F3D"), ("BSDF", "F3D To BSDF", "BSDF")]) converter_type: EnumProperty(items=converter_enum) + backup: BoolProperty(default=True, name="Backup") def execute_operator(self, context: Context): - if self.converter_type == "Object": - for obj in context.selected_objects: - obj_to_f3d(obj) - elif self.converter_type == "Scene": - for obj in context.scene.objects: - obj_to_f3d(obj) - self.report({"INFO"}, "Done.") - + collection = context.scene.collection + view_layer = context.view_layer + scene = context.scene -class F3D_ConvertBSDFToF3D(OperatorBase): - bl_idname = "scene.bsdf_convert_to_f3d" - bl_label = "Convert BSDF to F3D" - bl_options = {"REGISTER", "UNDO", "PRESET"} - icon = "NODE_MATERIAL" - - converter_type: EnumProperty(items=converter_enum) - - def execute_operator(self, context: Context): if self.converter_type == "Object": - for obj in context.selected_objects: - obj_to_bsdf(obj) + objs = context.selected_objects elif self.converter_type == "Scene": - for obj in context.scene.objects: - obj_to_bsdf(obj) + objs = scene.objects + + if not objs: + raise PluginError("No objects to convert.") + + objs: list[Object] = [obj for obj in objs if obj.type == "MESH"] + original_names = [obj.name for obj in objs] + new_objs: list[Object] = [] + backup_collection = None + + try: + materials: dict[Material, Material] = {} + for old_obj in objs: + obj = old_obj.copy() + obj.data = old_obj.data.copy() + new_objs.append(obj) + if self.direction == "F3D": + obj_to_f3d(obj, materials) + elif self.direction == "BSDF": + obj_to_bsdf(obj, materials) + + bpy.ops.object.select_all(action="DESELECT") + if self.backup: + backup_collection = bpy.data.collections.new("BSDF <-> F3D Backup") + scene.collection.children.link(backup_collection) + + for old_obj, obj, name in zip(objs, new_objs, original_names): + for collection in copy.copy(old_obj.users_collection): + collection.objects.link(obj) + collection.objects.unlink(old_obj) # remove old object from current collection + view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.make_single_user(type="SELECTED_OBJECTS") + obj.select_set(False) + + obj.name = name + if self.backup: + old_obj.name = f"{name}_backup" + backup_collection.objects.link(old_obj) + view_layer.objects.active = old_obj + else: + bpy.data.objects.remove(old_obj) + if self.backup: + for layer_collection in view_layer.layer_collection.children: + if layer_collection.collection == backup_collection: + layer_collection.exclude = True + except Exception as exc: + for obj in new_objs: + bpy.data.objects.remove(obj) + if backup_collection is not None: + bpy.data.collections.remove(backup_collection) + raise exc self.report({"INFO"}, "Done.") -classes = (F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D) +classes = (F3D_ConvertBSDF,) def bsdf_converter_ops_register(): diff --git a/fast64_internal/f3d/bsdf_converter/properties.py b/fast64_internal/f3d/bsdf_converter/properties.py index 52aef05fd..8db3e76df 100644 --- a/fast64_internal/f3d/bsdf_converter/properties.py +++ b/fast64_internal/f3d/bsdf_converter/properties.py @@ -1,6 +1,6 @@ from bpy.utils import register_class, unregister_class from bpy.types import PropertyGroup, UILayout -from bpy.props import EnumProperty +from bpy.props import EnumProperty, BoolProperty from ...utility import prop_split, multilineLabel @@ -11,11 +11,12 @@ class F3D_BSDFConverterProperties(PropertyGroup): """ converter_type: EnumProperty(items=[("Object", "Selected Objects", "Object"), ("Scene", "Scene", "Scene")]) + backup: BoolProperty(default=True, name="Backup") def draw_props(self, layout: UILayout): col = layout.column() prop_split(col, self, "converter_type", "Converter Type") - + col.prop(self, "backup") classes = (F3D_BSDFConverterProperties,) diff --git a/fast64_internal/f3d/bsdf_converter/ui.py b/fast64_internal/f3d/bsdf_converter/ui.py index 0719929fd..45b8b8bfb 100644 --- a/fast64_internal/f3d/bsdf_converter/ui.py +++ b/fast64_internal/f3d/bsdf_converter/ui.py @@ -2,11 +2,21 @@ from bpy.types import UILayout, Context -from .operators import F3D_ConvertF3DToBSDF, F3D_ConvertBSDFToF3D +from .operators import F3D_ConvertBSDF +from .properties import F3D_BSDFConverterProperties def bsdf_converter_panel_draw(layout: UILayout, context: Context): col = layout.column() - context.scene.fast64.f3d.bsdf_converter.draw_props(col) - F3D_ConvertF3DToBSDF.draw_props(col) - F3D_ConvertBSDFToF3D.draw_props(col) + bsdf_converter: F3D_BSDFConverterProperties = context.scene.fast64.f3d.bsdf_converter + bsdf_converter.draw_props(col) + + for direction in ("F3D", "BSDF"): + opposite = "BSDF" if direction == "F3D" else "F3D" + F3D_ConvertBSDF.draw_props( + col, + text=f"Convert {opposite} to {direction}", + direction=direction, + converter_type=bsdf_converter.converter_type, + backup=bsdf_converter.backup, + ) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 1f0970553..99ad2d157 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -145,6 +145,11 @@ } +def is_mat_f3d(material: Material): + assert material is None or isinstance(material, Material) + return material.is_f3d and material.mat_ver >= F3D_MAT_CUR_VERSION + + def getDefaultMaterialPreset(category): game = bpy.context.scene.gameEditorMode if game in defaultMaterialPresets[category]: @@ -303,9 +308,11 @@ def is_blender_doing_fog(settings: "RDPSettings") -> bool: ) -def get_output_method(material: bpy.types.Material) -> str: +def get_output_method(material: bpy.types.Material, check_decal=False) -> str: rendermode_preset_to_advanced(material) # Make sure advanced settings are updated settings = material.f3d_mat.rdp_settings + if check_decal and settings.zmode == "ZMODE_DEC": + return "DECAL" if settings.cvg_x_alpha: return "CLIP" if settings.force_bl and is_blender_equation_equal( @@ -315,23 +322,24 @@ def get_output_method(material: bpy.types.Material) -> str: return "OPA" -def update_blend_method(material: Material, context): - blend_mode = get_output_method(material) - if material.f3d_mat.rdp_settings.zmode == "ZMODE_DEC": - blend_mode = "DECAL" +def set_blend_to_output_method(material: Material, output_method: str): if bpy.app.version >= (4, 2, 0): - if blend_mode == "CLIP": + if output_method == "CLIP": material.surface_render_method = "DITHERED" else: material.surface_render_method = "BLENDED" - elif blend_mode == "OPA": + elif output_method == "OPA": material.blend_method = "OPAQUE" - elif blend_mode == "CLIP": + elif output_method == "CLIP": material.blend_method = "CLIP" - elif blend_mode in {"XLU", "DECAL"}: + elif output_method in {"XLU", "DECAL"}: material.blend_method = "BLEND" +def update_blend_method(material: Material, _context): + set_blend_to_output_method(material, get_output_method(material, True)) + + class DrawLayerProperty(PropertyGroup): sm64: bpy.props.EnumProperty(items=sm64EnumDrawLayers, default="1", update=update_draw_layer) oot: bpy.props.EnumProperty(items=ootEnumDrawLayers, default="Opaque", update=update_draw_layer) @@ -2865,9 +2873,9 @@ class TextureProperty(PropertyGroup): def get_tex_size(self) -> list[int]: if self.tex or self.use_tex_reference: if self.tex is not None: - return self.tex.size + return list(self.tex.size) else: - return self.tex_reference_size + return list(self.tex_reference_size) return [0, 0] def key(self): From 105a5e07c960a7fb57cb110e5b0f11e668acef86 Mon Sep 17 00:00:00 2001 From: Lila Date: Thu, 10 Oct 2024 18:16:16 +0100 Subject: [PATCH 4/5] Implement clip in 4.2 --- .../f3d/bsdf_converter/converter.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fast64_internal/f3d/bsdf_converter/converter.py b/fast64_internal/f3d/bsdf_converter/converter.py index 75e8bd41e..2535b43f6 100644 --- a/fast64_internal/f3d/bsdf_converter/converter.py +++ b/fast64_internal/f3d/bsdf_converter/converter.py @@ -228,9 +228,21 @@ def material_to_bsdf(material: Material): else: shader_node = nodes.new(type="ShaderNodeEmission") shader_node.location = (node_x, node_y) - node_x -= 300 + node_x -= 150 links.new(shader_node.outputs[0], output_node.inputs[0]) + alpha_input = shader_node.inputs["Alpha"] + if bpy.app.version >= (4, 2, 0) and abstracted_mat.output_method == "CLIP": + alpha_clip = nodes.new(type="ShaderNodeMath") + alpha_clip.location = (node_x, node_y - 100) + alpha_clip.operation = "GREATER_THAN" + alpha_clip.use_clamp = True + alpha_clip.inputs[1].default_value = 0.125 + links.new(alpha_clip.outputs[0], alpha_input) + alpha_input = alpha_clip.inputs[0] + + node_x -= 300 + # texture nodes tex_node_y = node_y if abstracted_mat.textures: @@ -272,9 +284,9 @@ def material_to_bsdf(material: Material): else: shader_node.inputs["Base Color"].default_value = abstracted_mat.color[:3] + [1.0] if abstracted_mat.texture_sets_alpha: - links.new(texture_nodes[0].outputs[1], shader_node.inputs["Alpha"]) + links.new(texture_nodes[0].outputs[1], alpha_input) else: - shader_node.inputs["Alpha"].default_value = abstracted_mat.color[3] + alpha_input.default_value = abstracted_mat.color[3] return new_material From 4e05533fb28acce6798dd9a83b003200c6041055 Mon Sep 17 00:00:00 2001 From: Lila Date: Wed, 16 Oct 2024 15:10:07 +0100 Subject: [PATCH 5/5] something --- .../f3d/bsdf_converter/converter.py | 416 +++++++++++++++--- .../f3d/bsdf_converter/operators.py | 14 +- .../f3d/bsdf_converter/properties.py | 2 + fast64_internal/f3d/bsdf_converter/ui.py | 1 + fast64_internal/f3d/f3d_material.py | 5 +- 5 files changed, 368 insertions(+), 70 deletions(-) diff --git a/fast64_internal/f3d/bsdf_converter/converter.py b/fast64_internal/f3d/bsdf_converter/converter.py index 2535b43f6..b63a05353 100644 --- a/fast64_internal/f3d/bsdf_converter/converter.py +++ b/fast64_internal/f3d/bsdf_converter/converter.py @@ -1,21 +1,47 @@ -import bpy -from bpy.types import Object, Material - +import typing import dataclasses +import numpy as np + +import bpy +from bpy.types import ( + Mesh, + Object, + Material, + ShaderNodeOutputMaterial, + ShaderNodeBsdfPrincipled, + ShaderNodeMixShader, + ShaderNodeBsdfTransparent, + ShaderNodeBackground, + ShaderNodeMath, + ShaderNodeMixRGB, + ShaderNodeVertexColor, + ShaderNodeTexCoord, + ShaderNodeUVMap, + ShaderNodeTexImage, + ShaderNodeMapping, + ShaderNode, +) from ...utility import get_clean_color, PluginError from ..f3d_material import ( combiner_uses, + createF3DMat, get_output_method, + getDefaultMaterialPreset, is_mat_f3d, all_combiner_uses, set_blend_to_output_method, trunc_10_2, + update_all_node_values, F3DMaterialProperty, RDPSettings, TextureProperty, + TextureFieldProperty, CombinerProperty, ) +from ..f3d_writer import getColorLayer + +# TODO: Vertex colors # Ideally we'd use mathutils.Color here but it does not support alpha (and mul for some reason) @@ -53,6 +79,12 @@ def __add__(self, other): def __mul__(self, other): return Color(self.r * other.r, self.g * other.g, self.b * other.b, self.a * other.a) + def __iter__(self): + yield self.r + yield self.g + yield self.b + yield self.a + def get_color_component(inp: str, f3d_mat: F3DMaterialProperty, previous_alpha: float) -> float: if inp == "0": @@ -75,9 +107,7 @@ def get_color_component(inp: str, f3d_mat: F3DMaterialProperty, previous_alpha: return f3d_mat.k5 -def get_color_from_input( - inp: str, previous_color: Color, f3d_mat: F3DMaterialProperty, is_alpha: bool, default_color: Color -) -> Color: +def get_color_from_input(inp: str, previous_color: Color, f3d_mat: F3DMaterialProperty, is_alpha: bool) -> Color: if inp == "COMBINED" and not is_alpha: return previous_color elif inp == "CENTER": @@ -96,15 +126,12 @@ def get_color_from_input( value = get_color_component(inp, f3d_mat, previous_color.a) if value is not None: return Color(value, value, value, value) - return default_color + return Color(1.0, 1.0, 1.0, 1.0) def fake_color_from_cycle(cycle: list[str], previous_color: Color, f3d_mat: F3DMaterialProperty, is_alpha=False): - default_colors = [Color(1.0, 1.0, 1.0, 1.0), Color(), Color(1.0, 1.0, 1.0, 1.0), Color()] - a, b, c, d = [ - get_color_from_input(inp, previous_color, f3d_mat, is_alpha, default_color) - for inp, default_color in zip(cycle, default_colors) - ] + default_colors = [Color(), Color(), Color(), Color(1.0, 1.0, 1.0, 1.0)] + a, b, c, d = [get_color_from_input(inp, previous_color, f3d_mat, is_alpha) for inp in cycle] sign_extended_c = c.wrap(-1.0, 1.0001) unwrapped_result = (a - b) * sign_extended_c + d result = unwrapped_result.wrap(-0.5, 1.5) @@ -136,6 +163,9 @@ class AbstractedN64Texture: offset: tuple[float, float] = (0.0, 0.0) scale: tuple[float, float] = (1.0, 1.0) repeat: bool = False + set_color: bool = False + set_alpha: bool = False + color_is_alpha: bool = False @dataclasses.dataclass @@ -143,10 +173,12 @@ class AbstractedN64Material: """Very abstracted representation of a N64 material""" lighting: bool = False - uv_geo: bool = False + uv_gen: bool = False point_filtering: bool = False - output_method: str = "OPA" + vertex_color: bool = False + vertex_alpha: bool = False backface_culling: bool = False + output_method: str = "OPA" color: Color = dataclasses.field(default_factory=Color) textures: list[AbstractedN64Texture] = dataclasses.field(default_factory=list) texture_sets_col: bool = False @@ -157,7 +189,7 @@ def main_texture(self): return self.textures[0] if self.textures else None -def f3d_tex_to_abstracted(f3d_tex: TextureProperty): +def f3d_tex_to_abstracted(f3d_tex: TextureProperty, set_color: bool, set_alpha: bool): def to_offset(low: float, tex_size: int): offset = -trunc_10_2(low) * (1.0 / tex_size) if offset == -0.0: @@ -172,6 +204,8 @@ def to_offset(low: float, tex_size: int): if size != [0, 0]: abstracted_tex.offset = (to_offset(f3d_tex.S.low, size[0]), to_offset(f3d_tex.T.low, size[1])) abstracted_tex.scale = (2.0 ** (f3d_tex.S.shift * -1.0), 2.0 ** (f3d_tex.T.shift * -1.0)) + abstracted_tex.set_color, abstracted_tex.set_alpha = set_color, set_alpha + abstracted_tex.color_is_alpha = f3d_tex.tex_format in {"I4", "I8"} return abstracted_tex @@ -182,13 +216,15 @@ def f3d_mat_to_abstracted(material: Material): use_dict = all_combiner_uses(f3d_mat) textures = [f3d_mat.tex0] if use_dict["Texture 0"] and f3d_mat.tex0.tex_set else [] textures += [f3d_mat.tex1] if use_dict["Texture 1"] and f3d_mat.tex1.tex_set else [] - + g_packed_normals = rdp.g_packed_normals if bpy.context.scene.f3d_type == "F3DEX3" else False abstracted_mat = AbstractedN64Material( - rdp.g_lighting, - rdp.g_tex_gen, + rdp.g_lighting and use_dict["Shade"], + rdp.g_tex_gen and rdp.g_lighting, rdp.g_mdsft_text_filt == "G_TF_POINT", - get_output_method(material, True), + (not rdp.g_lighting or g_packed_normals) and combiner_uses(f3d_mat, ["SHADE"], checkAlpha=False), + not rdp.g_fog and combiner_uses(f3d_mat, ["SHADE"], checkColor=False), rdp.g_cull_back, + get_output_method(material, True), get_fake_color(f3d_mat), ) for i in range(2): @@ -197,102 +233,332 @@ def f3d_mat_to_abstracted(material: Material): sets_color = combiner_uses(f3d_mat, check_list, checkColor=True, checkAlpha=False) sets_alpha = combiner_uses(f3d_mat, check_list, checkColor=False, checkAlpha=True) if sets_color or sets_alpha: - abstracted_mat.textures.append(f3d_tex_to_abstracted(tex_prop)) + abstracted_mat.textures.append(f3d_tex_to_abstracted(tex_prop, sets_color, sets_alpha)) abstracted_mat.texture_sets_col |= sets_color abstracted_mat.texture_sets_alpha |= sets_alpha + # print(abstracted_mat) return abstracted_mat -def material_to_bsdf(material: Material): +def material_to_bsdf(material: Material, put_alpha_into_color=False): abstracted_mat = f3d_mat_to_abstracted(material) new_material = bpy.data.materials.new(name=material.name) new_material.use_nodes = True - node_tree = new_material.node_tree - nodes = node_tree.nodes - links = node_tree.links + nodes = new_material.node_tree.nodes + links = new_material.node_tree.links nodes.clear() set_blend_to_output_method(new_material, abstracted_mat.output_method) new_material.use_backface_culling = abstracted_mat.backface_culling + new_material.alpha_threshold = 0.125 + + node_x = node_y = alpha_y_offset = 0 + + def set_location(node, set_x=False, x_offset=0, y_offset=0): + nonlocal node_x, node_y + node.location = (node_x - node.width + x_offset, node_y + y_offset) + if set_x: + node_x -= padded_from_node(node) + + def padded_from_node(node): + return node.width + 50 - node_x = node_y = 0 + T = typing.TypeVar("T") - output_node = nodes.new(type="ShaderNodeOutputMaterial") - node_x -= 300 + def create_node(typ: T, name: str, location=False, x_offset=0, y_offset=0): + node: T = nodes.new(typ.__name__) + node.name = node.label = name + set_location(node, location, x_offset=x_offset, y_offset=y_offset) + return node + + # output + output_node = create_node(ShaderNodeOutputMaterial, "Output", True) node_y -= 25 # final shader node if abstracted_mat.lighting: - shader_node = nodes.new(type="ShaderNodeBsdfPrincipled") + print("Creating bsdf principled shader node") + shader_node = create_node(ShaderNodeBsdfPrincipled, "Shader", True) + links.new(shader_node.outputs[0], output_node.inputs[0]) + alpha_input = shader_node.inputs["Alpha"] + color_input = shader_node.inputs["Base Color"] + if bpy.data.version >= (4, 2, 0): + node_y -= 22 + alpha_y_offset -= 88 + else: + node_y -= 80 + alpha_y_offset -= 462 else: - shader_node = nodes.new(type="ShaderNodeEmission") - shader_node.location = (node_x, node_y) - node_x -= 150 - links.new(shader_node.outputs[0], output_node.inputs[0]) + print("Creating unlit shader node") + mix_shader = create_node(ShaderNodeMixShader, "Mix Shader", True) + links.new(mix_shader.outputs[0], output_node.inputs[0]) + alpha_input = mix_shader.inputs["Fac"] + + # transparent bsdf shader + transparent_node = create_node(ShaderNodeBsdfTransparent, "Transparency Shader", y_offset=-47) + links.new(transparent_node.outputs[0], mix_shader.inputs[1]) + alpha_y_offset += transparent_node.height + 47 + + # background shader + background_node = create_node( + ShaderNodeBackground, "Background Shader", True, y_offset=-47 - transparent_node.height + ) + links.new(background_node.outputs[0], mix_shader.inputs[2]) + color_input = background_node.inputs["Color"] + node_y -= 172 - alpha_input = shader_node.inputs["Alpha"] + # cutout is removed in 4.2, it relies on the math node, glTF exporter supports this of course. if bpy.app.version >= (4, 2, 0) and abstracted_mat.output_method == "CLIP": - alpha_clip = nodes.new(type="ShaderNodeMath") - alpha_clip.location = (node_x, node_y - 100) + print("Creating alpha clip node") + alpha_clip = create_node(ShaderNodeMath, "Alpha Clip", True, y_offset=alpha_y_offset) alpha_clip.operation = "GREATER_THAN" alpha_clip.use_clamp = True alpha_clip.inputs[1].default_value = 0.125 links.new(alpha_clip.outputs[0], alpha_input) alpha_input = alpha_clip.inputs[0] - - node_x -= 300 - - # texture nodes - tex_node_y = node_y - if abstracted_mat.textures: - if abstracted_mat.uv_geo: - uvmap_node = nodes.new(type="ShaderNodeTexCoord") + + vertex_color = None + vertex_color_mul = None + if abstracted_mat.vertex_color: # create vertex color mul node + print("Creating vertex color node, mix rgb node and setting color input") + vertex_color_mul = create_node(ShaderNodeMixRGB, "Vertex Color Mul", True) + vertex_color_mul.use_clamp, vertex_color_mul.blend_type = True, "MULTIPLY" + vertex_color_mul.inputs[0].default_value = 1 + links.new(vertex_color_mul.outputs[0], color_input) + color_input = vertex_color_mul.inputs[2] + if abstracted_mat.vertex_alpha: # create vertex alpha mul node + print("Creating vertex alpha node, mul node and setting color input") + vertex_alpha_mul = create_node(ShaderNodeMath, "Vertex Alpha Mul", True, y_offset=alpha_y_offset) + vertex_alpha_mul.use_clamp, vertex_alpha_mul.operation = True, "MULTIPLY" + links.new(vertex_alpha_mul.outputs[0], alpha_input) + alpha_input = vertex_alpha_mul.inputs[1] + + if abstracted_mat.vertex_color or ( + put_alpha_into_color and abstracted_mat.vertex_alpha + ): # create vertex color node + vertex_color = create_node( + ShaderNodeVertexColor, "Vertex Color", True, y_offset=0 if abstracted_mat.vertex_color else alpha_y_offset + ) + vertex_color.layer_name = "Col" + if abstracted_mat.vertex_color: # link vertex color to vertex color mul + links.new(vertex_color.outputs[0], vertex_color_mul.inputs[1]) + if abstracted_mat.vertex_alpha: + if put_alpha_into_color: # link vertex color's alpha to vertex alpha mul + links.new(vertex_color.outputs[1], vertex_alpha_mul.inputs[0]) + else: # create vertex alpha + vertex_alpha = create_node(ShaderNodeVertexColor, "Vertex Alpha", True, y_offset=alpha_y_offset) + vertex_alpha.layer_name = "Alpha" + links.new(vertex_alpha.outputs[0], vertex_alpha_mul.inputs[0]) + + mix_rgb = False + if abstracted_mat.texture_sets_col and abstracted_mat.color[:3] != [1.0, 1.0, 1.0]: + print(f"Creating color mul node {abstracted_mat.color} and setting color input") + color_mul = create_node(ShaderNodeMixRGB, "Color Mul") + color_mul.use_clamp, color_mul.blend_type = True, "MULTIPLY" + color_mul.inputs[0].default_value = 1 + color_mul.inputs[1].default_value = abstracted_mat.color + links.new(color_mul.outputs[0], color_input) + color_input = color_mul.inputs[2] + mix_rgb = True + + if abstracted_mat.texture_sets_alpha and abstracted_mat.color[3] != 1.0 and abstracted_mat.output_method != "OPA": + print(f"Setting alpha mul node {abstracted_mat.color[3]} and setting alpha input") + alpha_mul = create_node(ShaderNodeMath, "Alpha Mul", y_offset=alpha_y_offset) + alpha_mul.use_clamp, alpha_mul.operation = True, "MULTIPLY" + alpha_mul.inputs[0].default_value = abstracted_mat.color[3] + links.new(alpha_mul.outputs[0], alpha_input) + alpha_input = alpha_mul.inputs[1] + mix_rgb = True + if mix_rgb: + node_x -= 140 + 50 + + uvmap_output = None + if abstracted_mat.textures: # create uvmap + if abstracted_mat.uv_gen: + print("Creating UVmap node") + uvmap_node = create_node(ShaderNodeTexCoord, "UVMap") uvmap_output = uvmap_node.outputs["Camera"] else: - uvmap_node = nodes.new(type="ShaderNodeUVMap") + print("Creating generated UVmap node (Camera output)") + uvmap_node = create_node(ShaderNodeUVMap, "UVMap") uvmap_node.uv_map = "UVMap" uvmap_output = uvmap_node.outputs["UV"] - uvmap_node.location = (node_x - 200, tex_node_y) + tex_x_offset = tex_y_offset = 0 texture_nodes = [] - for abstracted_tex in abstracted_mat.textures: - tex_node = nodes.new(type="ShaderNodeTexImage") - tex_node.location = (node_x, tex_node_y) + for abstracted_tex in abstracted_mat.textures: # create textures + tex_node = create_node(ShaderNodeTexImage, "Texture", y_offset=tex_y_offset) tex_node.image = abstracted_tex.tex tex_node.extension = "REPEAT" if abstracted_tex.repeat else "EXTEND" tex_node.interpolation = "Closest" if abstracted_mat.point_filtering else "Linear" texture_nodes.append(tex_node) + new_x_offset = -padded_from_node(tex_node) + tex_y_offset -= (tex_node.height * 2) + 125 + assert uvmap_output if abstracted_tex.offset != (0.0, 0.0) or abstracted_tex.scale != (1.0, 1.0): - uvmap_node.location = (node_x - 400, uvmap_node.location[1]) - - mapping_node = nodes.new(type="ShaderNodeMapping") + mapping_node = create_node(ShaderNodeMapping, "Mapping", x_offset=new_x_offset, y_offset=tex_y_offset + 98) mapping_node.vector_type = "POINT" - mapping_node.location = (node_x - 200, tex_node_y) + tex_y_offset -= mapping_node.height mapping_node.inputs["Location"].default_value = abstracted_tex.offset + (0.0,) mapping_node.inputs["Scale"].default_value = abstracted_tex.scale + (1.0,) links.new(uvmap_output, mapping_node.inputs[0]) links.new(mapping_node.outputs[0], tex_node.inputs[0]) + + new_x_offset -= padded_from_node(mapping_node) else: links.new(uvmap_output, tex_node.inputs[0]) + if new_x_offset < tex_x_offset: + tex_x_offset = new_x_offset + node_x += tex_x_offset # update node location - tex_node_y -= 300 + if abstracted_mat.textures: # update uvmap node location + if len(abstracted_mat.textures) > 1: + node_y += tex_y_offset / len(texture_nodes) + else: + node_y -= 30 + set_location(uvmap_node, True) + color_input.default_value = abstracted_mat.color[:3] + [1.0] if abstracted_mat.texture_sets_col: - links.new(texture_nodes[0].outputs[0], shader_node.inputs["Base Color"]) - else: - shader_node.inputs["Base Color"].default_value = abstracted_mat.color[:3] + [1.0] + links.new(texture_nodes[0].outputs[0], color_input) + + alpha_input.default_value = abstracted_mat.color[3] if abstracted_mat.texture_sets_alpha: - links.new(texture_nodes[0].outputs[1], alpha_input) - else: - alpha_input.default_value = abstracted_mat.color[3] + if abstracted_mat.main_texture.color_is_alpha: # i4/i8 + links.new(texture_nodes[0].outputs[0], alpha_input) + else: + links.new(texture_nodes[0].outputs[1], alpha_input) return new_material -def material_to_f3d(material: Material): - pass +def material_to_f3d(obj: Object, index: int, material: Material, use_lights_for_colors=False): + def find_output_node(material: Material): + for node in material.node_tree.nodes: + if isinstance(node, ShaderNodeOutputMaterial): + return node + return None + + def find_linked_nodes( + starting_node: ShaderNode, node_check: callable, specific_input_sockets=None, specific_output_sockets=None + ): + nodes: list[ShaderNode] = [] + for inp in starting_node.inputs: + if specific_input_sockets is not None and inp.name not in specific_input_sockets: + continue + for link in inp.links: + if link.to_node == starting_node and ( + specific_output_sockets is None or link.from_socket.name in specific_output_sockets + ): + if node_check(link.from_node): + nodes.append(link.from_node) + continue + nodes.extend( + find_linked_nodes(link.from_node, node_check, specific_input_sockets, specific_output_sockets) + ) + return nodes + + print(f"Converting BSDF material {material.name}") + + abstracted_mat = AbstractedN64Material() + + abstracted_mat.color = None + output_node = find_output_node(material) if material.use_nodes else None + if output_node is None: + abstracted_mat.color = material.diffuse_color + else: + shaders = find_linked_nodes( + output_node, + lambda node: node.bl_idname.startswith("ShaderNodeBsdf") + or node.bl_idname.removeprefix("ShaderNode") + in {"Background", "Emission", "SubsurfaceScattering", "VolumeAbsorption", "VolumeScatter"}, + specific_input_sockets={"Surface"}, + ) + if len(shaders) > 1: + print(f"WARNING: More than 1 shader connected to {material.name}. Using first shader.") + if len(shaders) == 0: + abstracted_mat.color = material.diffuse_color + print(f"WARNING: No shader connected to {material.name}. Using default color.") + else: + main_shader = shaders[0] + if main_shader.bl_idname in {"Background", "Emission"}: # is unlit + abstracted_mat.lighting = False + else: + abstracted_mat.lighting = True + alpha_textures = find_linked_nodes( + main_shader, + lambda node: node.bl_idname == "ShaderNodeTexImage", + specific_output_sockets={"Alpha"}, + ) + color_textures = find_linked_nodes( + main_shader, + lambda node: node.bl_idname == "ShaderNodeTexImage", + specific_output_sockets={"Color", "Base Color"}, + ) + textures: list[ShaderNodeTexImage] = list(dict.fromkeys(color_textures + alpha_textures).keys()) + if len(textures) > 2: + print(f"WARNING: More than 2 textures connected to {material.name}.") + if len(textures) > 0: + for tex_node in textures[:2]: + abstracted_tex = AbstractedN64Texture(tex_node.image) + mapping = find_linked_nodes(tex_node, lambda node: node.bl_idname == "ShaderNodeMapping") + if len(mapping) > 1: + print(f"WARNING: More than 1 mapping node connected to {tex_node.name}.") + elif len(mapping) == 1: + mapping = mapping[0] + abstracted_tex.offset = tuple(mapping.inputs["Location"].default_value) + abstracted_tex.scale = tuple(mapping.inputs["Scale"].default_value) + uv_gen = find_linked_nodes( + tex_node, + lambda node: node.bl_idname == "ShaderNodeTexCoord", + specific_input_sockets={"Vector"}, + specific_output_sockets={"Normal"}, + ) + if uv_gen: + abstracted_mat.uv_gen = True + if tex_node.interpolation == "Closest": + abstracted_mat.point_filtering = True + abstracted_tex.repeat = tex_node.extension == "REPEAT" + abstracted_tex.set_color = tex_node in color_textures + abstracted_tex.set_alpha = tex_node in alpha_textures + if abstracted_tex.set_color: + abstracted_mat.texture_sets_col = True + if abstracted_tex.set_alpha: + abstracted_mat.texture_sets_alpha = True + abstracted_mat.textures.append(abstracted_tex) + else: + abstracted_mat.color = Color(*shaders[0].inputs[0].default_value) + print(abstracted_mat) + preset = getDefaultMaterialPreset("Shaded Solid") + new_material = createF3DMat(obj, preset=preset, append=False) + new_material.name = material.name + f3d_mat: F3DMaterialProperty = new_material.f3d_mat + rdp: RDPSettings = f3d_mat.rdp_settings + + rdp.g_tex_gen = abstracted_mat.uv_gen + rdp.g_mdsft_text_filt = "G_TF_POINT" if abstracted_mat.point_filtering else "G_TF_BILERP" + + if abstracted_mat.color is not None: + f3d_mat.default_light_color = tuple(abstracted_mat.color) + f3d_mat.prim_color = tuple(abstracted_mat.color) + for i, abstracted_tex in enumerate(abstracted_mat.textures): + f3d_tex: TextureProperty = getattr(f3d_mat, f"tex{i}") + f3d_tex.tex = abstracted_tex.tex + f3d_tex.tex_set = True + f3d_tex.autoprop = abstracted_tex.offset == (0, 0) and abstracted_tex.scale == (1, 1) + s: TextureFieldProperty = f3d_tex.S + t: TextureFieldProperty = f3d_tex.T + s.low = abstracted_tex.offset[0] + t.low = abstracted_tex.offset[1] + s.shift = int(abstracted_tex.scale[0] // 2) + t.shift = int(abstracted_tex.scale[1] // 2) + + with bpy.context.temp_override(material=new_material): + update_all_node_values(new_material, bpy.context) # Reload everything + + return new_material def obj_to_f3d(obj: Object, materials: dict[Material, Material]): @@ -305,12 +571,34 @@ def obj_to_f3d(obj: Object, materials: dict[Material, Material]): if material in materials: obj.material_slots[index].material = materials[material] else: - obj.material_slots[index].material = material_to_f3d(material) + obj.material_slots[index].material = material_to_f3d(obj, index, material) + + +def apply_alpha(blender_mesh: Mesh): + color_layer = getColorLayer(blender_mesh, layer="Col") + alpha_layer = getColorLayer(blender_mesh, layer="Alpha") + if not color_layer or not alpha_layer: + return + color = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) + alpha = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32) + color_layer.foreach_get("color", color) + alpha_layer.foreach_get("color", alpha) + alpha = alpha.reshape(-1, 4) + color = color.reshape(-1, 4) + + # Calculate alpha from the median of the alpha layer RGB + alpha_median = np.median(alpha[:, :3], axis=1) + color[:, 3] = alpha_median + + color = color.flatten() + color_layer.foreach_set("color", color) -def obj_to_bsdf(obj: Object, materials: dict[Material, Material]): +def obj_to_bsdf(obj: Object, materials: dict[Material, Material], put_alpha_into_color: bool): assert obj.type == "MESH" print(f"Converting F3D materials in {obj.name}") + if put_alpha_into_color: + apply_alpha(obj.data) for index, material_slot in enumerate(obj.material_slots): material = material_slot.material if material is None or not is_mat_f3d(material): @@ -318,4 +606,4 @@ def obj_to_bsdf(obj: Object, materials: dict[Material, Material]): if material in materials: obj.material_slots[index].material = materials[material] else: - obj.material_slots[index].material = material_to_bsdf(material) + obj.material_slots[index].material = material_to_bsdf(material, put_alpha_into_color) diff --git a/fast64_internal/f3d/bsdf_converter/operators.py b/fast64_internal/f3d/bsdf_converter/operators.py index a02f1270f..523795aba 100644 --- a/fast64_internal/f3d/bsdf_converter/operators.py +++ b/fast64_internal/f3d/bsdf_converter/operators.py @@ -22,6 +22,7 @@ class F3D_ConvertBSDF(OperatorBase): direction: EnumProperty(items=[("F3D", "BSDF To F3D", "F3D"), ("BSDF", "F3D To BSDF", "BSDF")]) converter_type: EnumProperty(items=converter_enum) backup: BoolProperty(default=True, name="Backup") + put_alpha_into_color: BoolProperty(default=False, name="Put Alpha Into Color (F3D -> BSDF)") def execute_operator(self, context: Context): collection = context.scene.collection @@ -46,20 +47,25 @@ def execute_operator(self, context: Context): for old_obj in objs: obj = old_obj.copy() obj.data = old_obj.data.copy() + scene.collection.objects.link(obj) + view_layer.objects.active = obj new_objs.append(obj) if self.direction == "F3D": obj_to_f3d(obj, materials) elif self.direction == "BSDF": - obj_to_bsdf(obj, materials) + obj_to_bsdf(obj, materials, self.put_alpha_into_color) bpy.ops.object.select_all(action="DESELECT") if self.backup: - backup_collection = bpy.data.collections.new("BSDF <-> F3D Backup") - scene.collection.children.link(backup_collection) + name = "BSDF -> F3D Backup" if self.direction == "F3D" else "F3D -> BSDF Backup" + if name in bpy.data.collections: + backup_collection = bpy.data.collections[name] + else: + backup_collection = bpy.data.collections.new(name) + scene.collection.children.link(backup_collection) for old_obj, obj, name in zip(objs, new_objs, original_names): for collection in copy.copy(old_obj.users_collection): - collection.objects.link(obj) collection.objects.unlink(old_obj) # remove old object from current collection view_layer.objects.active = obj obj.select_set(True) diff --git a/fast64_internal/f3d/bsdf_converter/properties.py b/fast64_internal/f3d/bsdf_converter/properties.py index 8db3e76df..2b8ef3a19 100644 --- a/fast64_internal/f3d/bsdf_converter/properties.py +++ b/fast64_internal/f3d/bsdf_converter/properties.py @@ -12,11 +12,13 @@ class F3D_BSDFConverterProperties(PropertyGroup): converter_type: EnumProperty(items=[("Object", "Selected Objects", "Object"), ("Scene", "Scene", "Scene")]) backup: BoolProperty(default=True, name="Backup") + put_alpha_into_color: BoolProperty(default=False, name="Put Alpha Into Color (F3D -> BSDF)") def draw_props(self, layout: UILayout): col = layout.column() prop_split(col, self, "converter_type", "Converter Type") col.prop(self, "backup") + col.prop(self, "put_alpha_into_color") classes = (F3D_BSDFConverterProperties,) diff --git a/fast64_internal/f3d/bsdf_converter/ui.py b/fast64_internal/f3d/bsdf_converter/ui.py index 45b8b8bfb..2964e0b95 100644 --- a/fast64_internal/f3d/bsdf_converter/ui.py +++ b/fast64_internal/f3d/bsdf_converter/ui.py @@ -19,4 +19,5 @@ def bsdf_converter_panel_draw(layout: UILayout, context: Context): direction=direction, converter_type=bsdf_converter.converter_type, backup=bsdf_converter.backup, + put_alpha_into_color=bsdf_converter.put_alpha_into_color, ) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 99ad2d157..12c59ee43 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -2573,7 +2573,7 @@ def add_f3d_mat_to_obj(obj: bpy.types.Object, material, index=None): bpy.context.object.active_material_index = index -def createF3DMat(obj: Object | None, preset="Shaded Solid", index=None): +def createF3DMat(obj: Object | None, preset="Shaded Solid", index=None, append=True): # link all node_groups + material from addon's data .blend link_f3d_material_library() @@ -2587,7 +2587,8 @@ def createF3DMat(obj: Object | None, preset="Shaded Solid", index=None): createScenePropertiesForMaterial(material) - add_f3d_mat_to_obj(obj, material, index) + if append: + add_f3d_mat_to_obj(obj, material, index) material.is_f3d = True material.mat_ver = F3D_MAT_CUR_VERSION