From 7cdb9205f34bc443e4546ca85b4e4ba224402acd Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 23 Sep 2024 21:20:26 +0100 Subject: [PATCH 1/7] [F3D] Quick BSDF converter fix (#465) --- fast64_internal/f3d_material_converter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fast64_internal/f3d_material_converter.py b/fast64_internal/f3d_material_converter.py index fffb903da..992c2c221 100644 --- a/fast64_internal/f3d_material_converter.py +++ b/fast64_internal/f3d_material_converter.py @@ -186,16 +186,16 @@ def convertAllBSDFtoF3D(objs, renameUV): def convertBSDFtoF3D(obj, index, material, materialDict): if not material.use_nodes: newMaterial = createF3DMat(obj, preset="Shaded Solid", index=index) - f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial - f3dMat.default_light_color = material.diffuse_color + 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) - f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial - f3dMat.default_light_color = tex0Node.default_value + 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): @@ -213,8 +213,8 @@ def convertBSDFtoF3D(obj, index, material, materialDict): else: presetName = getDefaultMaterialPreset("Shaded Texture") newMaterial = createF3DMat(obj, preset=presetName, index=index) - f3dMat = newMaterial.f3d_mat if newMaterial.mat_ver > 3 else newMaterial - f3dMat.tex0.tex = tex0Node.links[0].from_node.image + 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.") From 1661b8d6d02d5e327fa6c4d67024aba766d608d7 Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 14 Oct 2024 16:32:41 +0100 Subject: [PATCH 2/7] [Repo Settings] Only set if different (#468) * [Repo Settings] Only set if different Fixes lag at start up from draw layers being set unnecessarily. Some clean up that I already did in #461 * Request Changes Co-Authored-By: Dragorn421 --------- Co-authored-by: Dragorn421 --- __init__.py | 15 +++++++- fast64_internal/repo_settings.py | 19 ++++------ fast64_internal/sm64/settings/panels.py | 11 +++--- fast64_internal/sm64/settings/properties.py | 36 ++++++++++++++----- .../sm64/settings/repo_settings.py | 31 +++------------- fast64_internal/utility.py | 10 ++++++ 6 files changed, 69 insertions(+), 53 deletions(-) diff --git a/__init__.py b/__init__.py index d3e5c548c..1e45c1003 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, multilineLabel, set_prop_if_in_data from .fast64_internal.repo_settings import ( draw_repo_settings, @@ -213,6 +213,19 @@ class Fast64Settings_Properties(bpy.types.PropertyGroup): internal_game_update_ver: bpy.props.IntProperty(default=0) + def to_repo_settings(self): + data = {} + data["autoLoad"] = self.auto_repo_load_settings + data["autoPickTextureFormat"] = self.auto_pick_texture_format + if self.auto_pick_texture_format: + data["preferRGBAOverCI"] = self.prefer_rgba_over_ci + return data + + def from_repo_settings(self, data: dict): + set_prop_if_in_data(self, "auto_repo_load_settings", data, "autoLoad") + set_prop_if_in_data(self, "auto_pick_texture_format", data, "autoPickTextureFormat") + set_prop_if_in_data(self, "prefer_rgba_over_ci", data, "preferRGBAOverCI") + class Fast64_Properties(bpy.types.PropertyGroup): """ diff --git a/fast64_internal/repo_settings.py b/fast64_internal/repo_settings.py index 04418e821..e0af49b6f 100644 --- a/fast64_internal/repo_settings.py +++ b/fast64_internal/repo_settings.py @@ -6,7 +6,7 @@ from bpy.props import StringProperty from bpy.path import abspath -from .utility import filepath_checks, prop_split, filepath_ui_warnings, draw_and_check_tab +from .utility import filepath_checks, prop_split, filepath_ui_warnings, draw_and_check_tab, set_prop_if_in_data from .operators import OperatorBase from .f3d.f3d_material import draw_rdp_world_defaults from .sm64.settings.repo_settings import load_sm64_repo_settings, save_sm64_repo_settings @@ -71,13 +71,10 @@ def load_repo_settings(scene: Scene, path: os.PathLike, skip_if_no_auto_load=Fal ) fast64_settings = scene.fast64.settings - fast64_settings.auto_repo_load_settings = data.get("autoLoad", fast64_settings.auto_repo_load_settings) - fast64_settings.auto_pick_texture_format = data.get( - "autoPickTextureFormat", fast64_settings.auto_pick_texture_format - ) - fast64_settings.prefer_rgba_over_ci = data.get("preferRGBAOverCI", fast64_settings.prefer_rgba_over_ci) - scene.f3d_type = data.get("microcode", scene.f3d_type) - scene.saveTextures = data.get("saveTextures", scene.saveTextures) + fast64_settings.from_repo_settings(data) + set_prop_if_in_data(scene, "f3d_type", data, "microcode") + set_prop_if_in_data(scene, "saveTextures", data, "saveTextures") + rdp_defaults: RDPSettings = scene.world.rdp_defaults rdp_defaults.from_dict(data.get("rdpDefaults", {})) @@ -90,12 +87,10 @@ def save_repo_settings(scene: Scene, path: os.PathLike): data = {} data["version"] = CUR_VERSION - data["autoLoad"] = fast64_settings.auto_repo_load_settings + data.update(fast64_settings.to_repo_settings()) data["microcode"] = scene.f3d_type data["saveTextures"] = scene.saveTextures - data["autoPickTextureFormat"] = fast64_settings.auto_pick_texture_format - if fast64_settings.auto_pick_texture_format: - data["preferRGBAOverCI"] = fast64_settings.prefer_rgba_over_ci + rdp_defaults: RDPSettings = scene.world.rdp_defaults data["rdpDefaults"] = rdp_defaults.to_dict() diff --git a/fast64_internal/sm64/settings/panels.py b/fast64_internal/sm64/settings/panels.py index 97fe73040..ba82351a7 100644 --- a/fast64_internal/sm64/settings/panels.py +++ b/fast64_internal/sm64/settings/panels.py @@ -2,8 +2,7 @@ from bpy.types import Context from ...panels import SM64_Panel - -from .repo_settings import draw_repo_settings +from ...utility import draw_and_check_tab class SM64_GeneralSettingsPanel(SM64_Panel): @@ -18,10 +17,12 @@ def draw(self, context: Context): if sm64_props.export_type == "C": # If the repo settings tab is open, we pass show_repo_settings as False # because we want to draw those specfic properties in the repo settings box - draw_repo_settings(scene, col.box()) - col.separator() + box = col.box().column() + if draw_and_check_tab(box, sm64_props, "sm64_repo_settings_tab", icon="PROPERTIES"): + sm64_props.draw_repo_settings(box) + col.separator() - sm64_props.draw_props(col, not sm64_props.sm64_repo_settings_tab) + sm64_props.draw_props(col, not sm64_props.sm64_repo_settings_tab or sm64_props.binary_export) else: sm64_props.draw_props(col, True) diff --git a/fast64_internal/sm64/settings/properties.py b/fast64_internal/sm64/settings/properties.py index 471ad617d..a5744ad36 100644 --- a/fast64_internal/sm64/settings/properties.py +++ b/fast64_internal/sm64/settings/properties.py @@ -6,7 +6,7 @@ from bpy.utils import register_class, unregister_class from ...render_settings import on_update_render_settings -from ...utility import directory_path_checks, directory_ui_warnings, prop_split, upgrade_old_prop +from ...utility import directory_path_checks, directory_ui_warnings, prop_split, set_prop_if_in_data, upgrade_old_prop from ..sm64_constants import defaultExtendSegment4 from ..sm64_objects import SM64_CombinedObjectProperties from ..sm64_utility import export_rom_ui_warnings, import_rom_ui_warnings @@ -133,6 +133,29 @@ def upgrade_changed_props(): upgrade_old_prop(combined_props, new, scene, old) sm64_props.version = SM64_Properties.cur_version + def to_repo_settings(self): + data = {} + data["refresh_version"] = self.refresh_version + data["compression_format"] = self.compression_format + data["force_extended_ram"] = self.force_extended_ram + data["matstack_fix"] = self.matstack_fix + return data + + def from_repo_settings(self, data: dict): + set_prop_if_in_data(self, "refresh_version", data, "refresh_version") + set_prop_if_in_data(self, "compression_format", data, "compression_format") + set_prop_if_in_data(self, "force_extended_ram", data, "force_extended_ram") + set_prop_if_in_data(self, "matstack_fix", data, "matstack_fix") + + def draw_repo_settings(self, layout: UILayout): + col = layout.column() + if not self.binary_export: + col.prop(self, "disable_scroll") + prop_split(col, self, "compression_format", "Compression Format") + prop_split(col, self, "refresh_version", "Refresh (Function Map)") + col.prop(self, "force_extended_ram") + col.prop(self, "matstack_fix") + def draw_props(self, layout: UILayout, show_repo_settings: bool = True): col = layout.column() @@ -152,14 +175,9 @@ def draw_props(self, layout: UILayout, show_repo_settings: bool = True): directory_ui_warnings(col, abspath(self.decomp_path)) col.separator() - if not self.binary_export: - col.prop(self, "disable_scroll") - if show_repo_settings: - prop_split(col, self, "compression_format", "Compression Format") - prop_split(col, self, "refresh_version", "Refresh (Function Map)") - col.prop(self, "force_extended_ram") - col.prop(self, "matstack_fix") - col.separator() + if show_repo_settings: + self.draw_repo_settings(col) + col.separator() col.prop(self, "show_importing_menus") if self.show_importing_menus: diff --git a/fast64_internal/sm64/settings/repo_settings.py b/fast64_internal/sm64/settings/repo_settings.py index f74b7eb3d..a32df8621 100644 --- a/fast64_internal/sm64/settings/repo_settings.py +++ b/fast64_internal/sm64/settings/repo_settings.py @@ -2,7 +2,7 @@ from bpy.types import Scene, UILayout -from ...utility import draw_and_check_tab, prop_split +from ...utility import draw_and_check_tab, prop_split, set_prop_if_in_data def save_sm64_repo_settings(scene: Scene): @@ -18,11 +18,7 @@ def save_sm64_repo_settings(scene: Scene): } sm64_props = scene.fast64.sm64 - data["refresh_version"] = sm64_props.refresh_version - data["compression_format"] = sm64_props.compression_format - data["force_extended_ram"] = sm64_props.force_extended_ram - data["matstack_fix"] = sm64_props.matstack_fix - + data.update(sm64_props.to_repo_settings()) return data @@ -33,26 +29,9 @@ def load_sm64_repo_settings(scene: Scene, data: dict[str, Any]): for layer in range(8): draw_layer = draw_layers.get(str(layer), {}) if "cycle_1" in draw_layer: - setattr(world, f"draw_layer_{layer}_cycle_1", draw_layer["cycle_1"]) + set_prop_if_in_data(world, f"draw_layer_{layer}_cycle_1", draw_layer, "cycle_1") if "cycle_2" in draw_layer: - setattr(world, f"draw_layer_{layer}_cycle_2", draw_layer["cycle_2"]) - - sm64_props = scene.fast64.sm64 - sm64_props.refresh_version = data.get("refresh_version", sm64_props.refresh_version) - sm64_props.compression_format = data.get("compression_format", sm64_props.compression_format) - sm64_props.force_extended_ram = data.get("force_extended_ram", sm64_props.force_extended_ram) - sm64_props.matstack_fix = data.get("matstack_fix", sm64_props.matstack_fix) + set_prop_if_in_data(world, f"draw_layer_{layer}_cycle_2", draw_layer, "cycle_2") - -def draw_repo_settings(scene: Scene, layout: UILayout): - col = layout.column() sm64_props = scene.fast64.sm64 - if not draw_and_check_tab(col, sm64_props, "sm64_repo_settings_tab", icon="PROPERTIES"): - return - - prop_split(col, sm64_props, "compression_format", "Compression Format") - prop_split(col, sm64_props, "refresh_version", "Refresh (Function Map)") - col.prop(sm64_props, "force_extended_ram") - col.prop(sm64_props, "matstack_fix") - - col.label(text="See Fast64 repo settings for general settings", icon="INFO") + sm64_props.from_repo_settings(data) diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 14f6aea59..3237b1c8c 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -1896,3 +1896,13 @@ def create_or_get_world(scene: Scene) -> World: WORLD_WARNING_COUNT = 0 print(f'No world in this file, creating world named "Fast64".') return bpy.data.worlds.new("Fast64") + + +def set_if_different(owner: object, prop: str, value): + if getattr(owner, prop) != value: + setattr(owner, prop, value) + + +def set_prop_if_in_data(owner: object, prop_name: str, data: dict, data_name: str): + if data_name in data: + set_if_different(owner, prop_name, data[data_name]) From 3e47569beef0d9e6f7613fcb84bfbe6fc06c9af0 Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 14 Oct 2024 16:34:20 +0100 Subject: [PATCH 3/7] [F3D] Fix sameTextures condition, make assert more user friendly (#466) * [F3D] Fix sameTextures condition, make assert more user friendly * world peace --- fast64_internal/f3d/f3d_texture_writer.py | 25 +++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index d08d80ced..0f912619b 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -763,14 +763,25 @@ def writeAll( # Assign TMEM addresses sameTextures = ( - self.ti0.useTex - and self.ti1.useTex + (self.ti0.useTex and self.ti1.useTex) + and self.ti0.isTexRef == self.ti1.isTexRef + and self.ti0.tmemSize == self.ti1.tmemSize + and self.ti0.texFormat == self.ti1.texFormat and ( - (not self.ti0.isTexRef and not self.ti1.isTexRef and self.ti0.texProp.tex == self.ti1.texProp.tex) - or ( + ( # not a reference + not self.ti0.isTexRef and self.ti0.texProp.tex == self.ti1.texProp.tex # same image + ) + or ( # reference self.ti0.isTexRef - and self.ti1.isTexRef and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference + and self.ti0.texProp.tex_reference_size == self.ti1.texProp.tex_reference_size + and ( # ci format reference + not self.isCI + or ( + self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference + and self.ti0.texProp.pal_reference_size == self.ti1.texProp.pal_reference_size + ) + ) ) ) ) @@ -779,7 +790,9 @@ def writeAll( self.ti1.texAddr = None # must be set whenever tex 1 used (and loaded or tiled) tmemOccupied = self.texDimensions = None # must be set on all codepaths if sameTextures: - assert self.ti0.tmemSize == self.ti1.tmemSize + assert ( + self.ti0.tmemSize == self.ti1.tmemSize + ), f"Unreachable code path in material {material.name}, same textures (same image or reference) somehow not the same size" tmemOccupied = self.ti0.tmemSize self.ti1.doTexLoad = False self.ti1.texAddr = 0 From ac5ff611f314a4c99086fc0d338d4142dcd4c5d1 Mon Sep 17 00:00:00 2001 From: Lila Date: Mon, 14 Oct 2024 16:34:46 +0100 Subject: [PATCH 4/7] [SM64] Add back custom include directory + add automatic option (#464) * [SM64] Add back custom include directory + add automatic option Fixes #463 * bump sm64 settings version * Requested Change + extra checks for ui and code clarity Co-Authored-By: Dragorn421 --------- Co-authored-by: Dragorn421 --- fast64_internal/sm64/settings/properties.py | 3 ++- fast64_internal/sm64/sm64_geolayout_writer.py | 6 ++---- fast64_internal/sm64/sm64_objects.py | 20 +++++++++++++++++-- fast64_internal/utility.py | 3 +++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/fast64_internal/sm64/settings/properties.py b/fast64_internal/sm64/settings/properties.py index a5744ad36..64ba59713 100644 --- a/fast64_internal/sm64/settings/properties.py +++ b/fast64_internal/sm64/settings/properties.py @@ -32,7 +32,7 @@ class SM64_Properties(PropertyGroup): """Global SM64 Scene Properties found under scene.fast64.sm64""" version: IntProperty(name="SM64_Properties Version", default=0) - cur_version = 3 # version after property migration + cur_version = 4 # version after property migration # UI Selection show_importing_menus: BoolProperty(name="Show Importing Menus", default=False) @@ -107,6 +107,7 @@ def upgrade_changed_props(): "custom_level_name": {"levelName", "geoLevelName", "colLevelName", "animLevelName"}, "non_decomp_level": {"levelCustomExport"}, "export_header_type": {"geoExportHeaderType", "colExportHeaderType", "animExportHeaderType"}, + "custom_include_directory": {"geoTexDir"}, } for scene in bpy.data.scenes: sm64_props: SM64_Properties = scene.fast64.sm64 diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 7a3fb7abd..df0c30cd7 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -2858,7 +2858,7 @@ def execute(self, context): obj, final_transform, export_path, - bpy.context.scene.geoTexDir, + props.custom_include_directory, save_textures, save_textures and bpy.context.scene.geoSeparateTextureDef, None, @@ -3058,7 +3058,7 @@ def execute(self, context): obj, final_transform, export_path, - bpy.context.scene.geoTexDir, + props.custom_include_directory, save_textures, save_textures and bpy.context.scene.geoSeparateTextureDef, None, @@ -3252,7 +3252,6 @@ def sm64_geo_writer_register(): bpy.types.Scene.textDumpGeoPath = bpy.props.StringProperty(name="Text Dump Path", subtype="FILE_PATH") bpy.types.Scene.geoUseBank0 = bpy.props.BoolProperty(name="Use Bank 0") bpy.types.Scene.geoRAMAddr = bpy.props.StringProperty(name="RAM Address", default="80000000") - bpy.types.Scene.geoTexDir = bpy.props.StringProperty(name="Include Path", default="actors/mario/") bpy.types.Scene.geoSeparateTextureDef = bpy.props.BoolProperty(name="Save texture.inc.c separately") bpy.types.Scene.geoInsertableBinaryPath = bpy.props.StringProperty(name="Filepath", subtype="FILE_PATH") bpy.types.Scene.geoIsSegPtr = bpy.props.BoolProperty(name="Is Segmented Address") @@ -3282,7 +3281,6 @@ def sm64_geo_writer_unregister(): del bpy.types.Scene.textDumpGeoPath del bpy.types.Scene.geoUseBank0 del bpy.types.Scene.geoRAMAddr - del bpy.types.Scene.geoTexDir del bpy.types.Scene.geoSeparateTextureDef del bpy.types.Scene.geoInsertableBinaryPath del bpy.types.Scene.geoIsSegPtr diff --git a/fast64_internal/sm64/sm64_objects.py b/fast64_internal/sm64/sm64_objects.py index 951e8bcc5..bd87622e2 100644 --- a/fast64_internal/sm64/sm64_objects.py +++ b/fast64_internal/sm64/sm64_objects.py @@ -1941,6 +1941,7 @@ def update_or_inherit(new_cmd, index, arg_val, bhv_arg): group_name: bpy.props.EnumProperty(name="Group Name", default="group0", items=groups_obj_export) # custom export path, no headers written custom_export_path: bpy.props.StringProperty(name="Custom Path", subtype="FILE_PATH") + custom_include_directory: bpy.props.StringProperty(name="Include directory", subtype="FILE_PATH") # common export opts custom_group_name: bpy.props.StringProperty(name="custom") # for custom group @@ -2156,6 +2157,10 @@ def draw_export_options(self, layout): col.prop(self, "export_bhv") self.draw_obj_name(layout) + @property + def actor_names(self) -> list: + return list(dict.fromkeys(filter(None, [self.obj_name_col, self.obj_name_gfx])).keys()) + def draw_level_path(self, layout): if not directory_ui_warnings(layout, bpy.path.abspath(self.base_level_path)): return @@ -2218,7 +2223,7 @@ def draw_props(self, layout): self.draw_level_path(box.box()) col.separator() # object exports - box = col.box() + box = col.box().column() if not self.export_col and not self.export_bhv and not self.export_gfx: col = box.column() col.operator("object.sm64_export_combined_object", text="Export Object") @@ -2237,8 +2242,9 @@ def draw_props(self, layout): # pathing for gfx/col exports prop_split(box, self, "export_header_type", "Export Type") - if self.export_header_type == "Custom": + if self.export_header_type == "Custom" and bpy.context.scene.saveTextures: prop_split(box, self, "custom_export_path", "Custom Path") + prop_split(box, self, "custom_include_directory", "Texture Include Directory") elif self.export_header_type == "Actor": prop_split(box, self, "group_name", "Group") @@ -2277,6 +2283,16 @@ def draw_props(self, layout): elif self.export_header_type == "Actor": if not self.draw_actor_path(info_box): return + elif self.export_header_type == "Custom" and bpy.context.scene.saveTextures: + if self.custom_include_directory: + info_box.label(text=f'Include directory "{self.custom_include_directory}"') + else: + actor_names = self.actor_names + joined = ",".join(self.actor_names) + if len(actor_names) > 1: + joined = "{" f"{joined}" "}" + directory = f"{Path(bpy.path.abspath(self.custom_export_path)).name}/{joined}" + info_box.label(text=f'Empty include directory, defaults to "{directory}"') if self.obj_name_gfx and self.export_gfx: self.draw_gfx_names(info_box) diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 3237b1c8c..702ddd697 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -1,3 +1,4 @@ +from pathlib import Path import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator from math import pi, ceil, degrees, radians, copysign from mathutils import * @@ -708,6 +709,8 @@ def getExportDir(customExport, dirPath, headerType, levelName, texDir, dirName): elif headerType == "Level": dirPath = os.path.join(dirPath, "levels/" + levelName) texDir = "levels/" + levelName + elif not texDir: + texDir = (Path(dirPath).name / Path(dirName)).as_posix() return dirPath, texDir From b0bad2f2e2e142b5de767e3fb33825b3351a2a91 Mon Sep 17 00:00:00 2001 From: Lila Date: Sun, 10 Nov 2024 12:05:56 +0000 Subject: [PATCH 5/7] T3D UI (#389) * T3D UI * black * some fixes for main and small changes * personal nitpick * one more nitpick * removal of unnecessary checks * black format * small typo * black * Add rdpq --- __init__.py | 2 +- fast64_internal/f3d/f3d_enums.py | 23 +- fast64_internal/f3d/f3d_gbi.py | 22 +- fast64_internal/f3d/f3d_material.py | 380 +++++++++++++++++----------- fast64_internal/repo_settings.py | 2 +- 5 files changed, 270 insertions(+), 159 deletions(-) diff --git a/__init__.py b/__init__.py index 1e45c1003..d822f902b 100644 --- a/__init__.py +++ b/__init__.py @@ -88,7 +88,7 @@ def poll(cls, context): def draw(self, context): col = self.layout.column() col.scale_y = 1.1 # extra padding - prop_split(col, context.scene, "f3d_type", "F3D Microcode") + prop_split(col, context.scene, "f3d_type", "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") diff --git a/fast64_internal/f3d/f3d_enums.py b/fast64_internal/f3d/f3d_enums.py index 993a87316..d9e032c23 100644 --- a/fast64_internal/f3d/f3d_enums.py +++ b/fast64_internal/f3d/f3d_enums.py @@ -378,13 +378,22 @@ } enumF3D = [ - ("F3D", "F3D", "Original microcode used in SM64"), - ("F3DEX/LX", "F3DEX/LX", "F3DEX version 1"), - ("F3DLX.Rej", "F3DLX.Rej", "F3DLX.Rej"), - ("F3DLP.Rej", "F3DLP.Rej", "F3DLP.Rej"), - ("F3DEX2/LX2", "F3DEX2/LX2/ZEX", "Family of microcodes used in later N64 games including OoT and MM"), - ("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "Variant of F3DEX2 family using vertex rejection instead of clipping"), - ("F3DEX3", "F3DEX3", "Custom microcode by Sauraen"), + ("", "F3D Family", "", 7), + ("F3D", "F3D", "Original microcode used in SM64", 0), + ("F3DEX/LX", "F3DEX/LX", "F3DEX version 1", 1), + ("F3DLX.Rej", "F3DLX.Rej", "F3DLX.Rej", 2), + ("F3DLP.Rej", "F3DLP.Rej", "F3DLP.Rej", 3), + ("F3DEX2/LX2", "F3DEX2/LX2/ZEX", "Family of microcodes used in later N64 games including OoT and MM", 4), + ( + "F3DEX2.Rej/LX2.Rej", + "F3DEX2.Rej/LX2.Rej", + "Variant of F3DEX2 family using vertex rejection instead of clipping", + 5, + ), + ("F3DEX3", "F3DEX3", "Custom microcode by Sauraen", 6), + ("", "Homebrew", "", 8), + ("RDPQ", "RDPQ", "Base libdragon microcode", 9), + ("T3D", "Tiny3D", "Custom libdragon microcode by HailToDodongo", 10), ] enumLargeEdges = [ diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 1eb5b7d80..a5f8520e9 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -70,6 +70,7 @@ class GfxMatWriteMethod(enum.Enum): "F3DEX2/LX2": (32, 32), "F3DEX2.Rej/LX2.Rej": (64, 64), "F3DEX3": (56, 56), + "T3D": (70, 70), } sm64_default_draw_layers = { @@ -144,6 +145,14 @@ def isUcodeF3DEX3(F3D_VER: str) -> bool: return F3D_VER == "F3DEX3" +def is_ucode_t3d(UCODE_VER: str) -> bool: + return UCODE_VER == "T3D" + + +def is_ucode_f3d(UCODE_VER: str) -> bool: + return UCODE_VER not in {"T3D", "RDPQ"} + + class F3D: """NOTE: do not initialize this class manually! use get_F3D_GBI so that the single instance is cached from the microcode type.""" @@ -154,6 +163,7 @@ def __init__(self, F3D_VER): F3DEX_GBI_3 = self.F3DEX_GBI_3 = isUcodeF3DEX3(F3D_VER) F3DLP_GBI = self.F3DLP_GBI = self.F3DEX_GBI self.F3D_OLD_GBI = not (F3DEX_GBI or F3DEX_GBI_2 or F3DEX_GBI_3) + self.F3D_GBI = is_ucode_f3d(F3D_VER) # F3DEX2 is F3DEX1 and F3DEX3 is F3DEX2, but F3DEX3 is not F3DEX1 if F3DEX_GBI_2: @@ -161,8 +171,12 @@ def __init__(self, F3D_VER): elif F3DEX_GBI_3: F3DEX_GBI_2 = self.F3DEX_GBI_2 = True - self.vert_buffer_size = vertexBufferSize[F3D_VER][0] - self.vert_load_size = vertexBufferSize[F3D_VER][1] + if F3D_VER in vertexBufferSize: + self.vert_buffer_size = vertexBufferSize[F3D_VER][0] + self.vert_load_size = vertexBufferSize[F3D_VER][1] + else: + self.vert_buffer_size = self.vert_load_size = None + self.G_MAX_LIGHTS = 9 if F3DEX_GBI_3 else 7 self.G_INPUT_BUFFER_CMDS = 21 @@ -2331,6 +2345,10 @@ def __init__( self.materialRevert: Union[GfxList, None] = None # F3D library self.f3d: F3D = get_F3D_GBI() + if not self.f3d.F3D_GBI: + raise PluginError( + f"Current microcode {self.f3d.F3D_VER} is not part of the f3d family of microcodes, fast64 cannot export it" + ) # array of FModel self.subModels: list[FModel] = [] self.parentModel: Union[FModel, None] = None diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 1f0970553..29d4eaa6d 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -31,7 +31,15 @@ from mathutils import Color from .f3d_enums import * -from .f3d_gbi import get_F3D_GBI, enumTexScroll, isUcodeF3DEX1, default_draw_layers +from .f3d_gbi import ( + get_F3D_GBI, + enumTexScroll, + isUcodeF3DEX1, + isUcodeF3DEX3, + is_ucode_f3d, + is_ucode_t3d, + default_draw_layers, +) from .f3d_material_presets import * from ..utility import * from ..render_settings import ( @@ -119,13 +127,14 @@ "Overlay": "1", } -enumF3DMenu = [ - ("Combiner", "Combiner", "Combiner"), - ("Sources", "Sources", "Sources"), - ("Geo", "Geo", "Geo"), - ("Upper", "Upper", "Upper"), - ("Lower", "Lower", "Lower"), -] + +def menu_items_enum(_self, context): + items = ["Combiner", "Sources"] + if len(geo_modes_in_ucode(context.scene.f3d_type)) > 1: + items.append("Geo") + items.extend(["Upper", "Lower"]) + return [(item, item, item) for item in items] + enumF3DSource = [ ("None", "None", "None"), @@ -144,6 +153,68 @@ "Shaded Texture": {"SM64": "Shaded Texture", "OOT": "oot_shaded_texture"}, } +F3D_GEO_MODES = { + "zBuffer": "g_zbuffer", + "shade": "g_shade", + "cullFront": "g_cull_front", + "cullBack": "g_cull_back", + "fog": "g_fog", + "lighting": "g_lighting", + "texGen": "g_tex_gen", + "texGenLinear": "g_tex_gen_linear", + "lod": "g_lod", + "shadeSmooth": "g_shade_smooth", +} + +F3DLX_GEO_MODES = { + "clipping": "g_clipping", +} + +F3DEX3_GEO_MODES = { + "ambientOcclusion": "g_ambocclusion", + "attroffsetZ": "g_attroffset_z_enable", + "attroffsetST": "g_attroffset_st_enable", + "packedNormals": "g_packed_normals", + "lightToAlpha": "g_lighttoalpha", + "specularLighting": "g_lighting_specular", + "fresnelToColor": "g_fresnel_color", + "fresnelToAlpha": "g_fresnel_alpha", +} + + +T3D_GEO_MODES = { + "cullFront": "g_cull_front", + "cullBack": "g_cull_back", + "fog": "g_fog", + "texGen": "g_tex_gen", +} + + +def geo_modes_in_ucode(UCODE_VER: str): + geo_modes = {} + if is_ucode_f3d(UCODE_VER): + geo_modes.update(F3D_GEO_MODES) + if isUcodeF3DEX1(UCODE_VER): + geo_modes.update(F3DLX_GEO_MODES) + if isUcodeF3DEX3(UCODE_VER): + geo_modes.update(F3DEX3_GEO_MODES) + if is_ucode_t3d(UCODE_VER): + geo_modes.update(T3D_GEO_MODES) + return geo_modes + + +def sources_in_ucode(UCODE_VER: str): + sources = ["Primitive", "Environment", "Key", "Convert", "Fog"] + if not is_ucode_t3d(UCODE_VER) or is_ucode_f3d(UCODE_VER): + sources.extend(["Lighting", "Clip Ratio"]) + if isUcodeF3DEX3(UCODE_VER): + sources.extend(["AO", "Fresnel", "ST Attr Offset", "Z Attr Offset"]) + return sources + + +def inherit_light_and_fog(): + return is_ucode_t3d(bpy.context.scene.f3d_type) + def getDefaultMaterialPreset(category): game = bpy.context.scene.gameEditorMode @@ -443,6 +514,7 @@ def all_combiner_uses(f3d_mat: "F3DMaterialProperty") -> dict[str, bool]: def ui_geo_mode(settings, dataHolder, layout, useDropdown): + f3d = get_F3D_GBI() inputGroup = layout.column() if useDropdown: inputGroup.prop( @@ -454,7 +526,24 @@ def ui_geo_mode(settings, dataHolder, layout, useDropdown): if not useDropdown or dataHolder.menu_geo: disable_dependent = False # Don't disable dependent props in world defaults + def should_draw(*props): + return any(settings.has_prop_in_ucode(prop) for prop in props) + + def is_on(*props): + return all(settings.is_geo_mode_on(prop) for prop in props) + + def draw_mode(layout: UILayout, *props): + failed = False + for prop in props: + if settings.has_prop_in_ucode(prop): + layout.prop(settings, prop) + else: + failed = True + return not failed + def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], isText: bool) -> UILayout: + if not isText and not should_draw(textOrProp): + return parent.column(align=True) c = parent.column(align=True) if isText: c.label(text=textOrProp) @@ -468,8 +557,6 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], c.enabled = enable or not disable_dependent return c - isF3DEX3 = bpy.context.scene.f3d_type == "F3DEX3" - lightFxPrereq = isF3DEX3 and settings.g_lighting ccWarnings = shadeInCC = False blendWarnings = shadeInBlender = zInBlender = False if isinstance(dataHolder, F3DMaterialProperty): @@ -482,96 +569,103 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], shadeInBlender = settings.does_blender_use_input("G_BL_A_SHADE") zInBlender = settings.z_cmp or settings.z_upd - inputGroup.prop(settings, "g_shade_smooth") + draw_mode(inputGroup, "g_shade_smooth") c = indentGroup(inputGroup, "g_lighting", False) - if ccWarnings and not shadeInCC and settings.g_lighting and not settings.g_tex_gen: + if ccWarnings and not shadeInCC and is_on("g_lighting") and not is_on("g_tex_gen"): multilineLabel(c, "Shade not used in CC, can disable\nlighting.", icon="INFO") - if isF3DEX3: - c.prop(settings, "g_packed_normals") - c.prop(settings, "g_lighting_specular") - c.prop(settings, "g_ambocclusion") - c.prop(settings, "g_fresnel_color") - d = indentGroup(c, "g_tex_gen", False) - d.prop(settings, "g_tex_gen_linear") - - if lightFxPrereq and settings.g_fresnel_color: + draw_mode(c, "g_packed_normals", "g_lighting_specular", "g_ambocclusion", "g_fresnel_color") + if should_draw("g_tex_gen_linear"): + d = indentGroup(c, "g_tex_gen", False) + else: + draw_mode(c, "g_tex_gen") + d = c + draw_mode(d, "g_tex_gen_linear") + + if is_ucode_t3d(f3d.F3D_VER): + shadeColorLabel = "Lighting * vertex color" + if is_on("g_fresnel_color"): shadeColorLabel = "Fresnel" - elif not settings.g_lighting or (lightFxPrereq and settings.g_lighttoalpha): + elif not is_on("g_lighting") or is_on("g_lighttoalpha"): shadeColorLabel = "Vertex color" - elif lightFxPrereq and settings.g_packed_normals and not settings.g_lighttoalpha: + elif is_on("g_lighting") and is_on("g_packed_normals") and not is_on("g_lighttoalpha"): shadeColorLabel = "Lighting * vertex color" else: shadeColorLabel = "Lighting" - inputGroup.label(text=f"Shade color = {shadeColorLabel}") + inputGroup.column().label(text=f"Shade color = {shadeColorLabel}") + + draw_mode(inputGroup, "g_fog") shadowMapInShadeAlpha = False - if settings.g_fog: + if is_on("g_fog"): shadeAlphaLabel = "Fog" - elif lightFxPrereq and settings.g_fresnel_alpha: + elif is_on("g_lighting", "g_fresnel_alpha"): shadeAlphaLabel = "Fresnel" - elif lightFxPrereq and settings.g_lighttoalpha: + elif is_on("g_lighting", "g_lighttoalpha"): shadeAlphaLabel = "Light intensity" - elif lightFxPrereq and settings.g_ambocclusion: + elif is_on("g_lighting", "g_ambocclusion"): shadeAlphaLabel = "Shadow map / AO in vtx alpha" shadowMapInShadeAlpha = True else: shadeAlphaLabel = "Vtx alpha" - c = indentGroup(inputGroup, f"Shade alpha = {shadeAlphaLabel}:", True) - if isF3DEX3: - lighting_group = c.column(align=True) - lighting_group.enabled = settings.g_lighting or not disable_dependent - lighting_group.prop(settings, "g_lighttoalpha") - lighting_group.prop(settings, "g_fresnel_alpha") - c.prop(settings, "g_fog") - if lightFxPrereq and settings.g_fog and settings.g_fresnel_alpha: - c.label(text="Fog overrides Fresnel Alpha.", icon="ERROR") - if lightFxPrereq and settings.g_fog and settings.g_lighttoalpha: - c.label(text="Fog overrides Light-to-Alpha.", icon="ERROR") - if lightFxPrereq and settings.g_fresnel_alpha and settings.g_lighttoalpha: - c.label(text="Fresnel Alpha overrides Light-to-Alpha.", icon="ERROR") - if shadowMapInShadeAlpha and ccWarnings and ccUse["Shade Alpha"]: - c.label(text="Shadow map = shade alpha used in CC, probably wrong.", icon="INFO") - if settings.g_fog and ccWarnings and ccUse["Shade Alpha"]: - c.label(text="Fog = shade alpha used in CC, probably wrong.", icon="INFO") - if blendWarnings and shadeInBlender and not settings.g_fog: - c.label(text="Rendermode uses shade alpha, probably fog.", icon="INFO") - elif blendWarnings and not shadeInBlender and settings.g_fog: - c.label(text="Fog not used in rendermode / blender, can disable.", icon="INFO") - if isF3DEX3: - c = indentGroup(inputGroup, "Attribute offsets:", True) - c.prop(settings, "g_attroffset_st_enable") - c.prop(settings, "g_attroffset_z_enable") - - c = indentGroup(inputGroup, "Face culling:", True) - c.prop(settings, "g_cull_front") - c.prop(settings, "g_cull_back") - if settings.g_cull_front and settings.g_cull_back: - c.label(text="Nothing will be drawn.", icon="ERROR") - - c = indentGroup(inputGroup, "Disable if not using:", True) - c.prop(settings, "g_zbuffer") - if blendWarnings and not settings.g_zbuffer and zInBlender: - c.label(text="Rendermode / blender using Z, must enable.", icon="ERROR") - elif blendWarnings and settings.g_zbuffer and not zInBlender: - c.label(text="Z is not being used, can disable.", icon="INFO") - c.prop(settings, "g_shade") - if ccWarnings and not settings.g_shade and (shadeInCC or shadeInBlender): - if shadeInCC and shadeInBlender: - where = "CC and blender" - elif shadeInCC: - where = "CC" - else: - where = "rendermode / blender" - c.label(text=f"Shade in use in {where}, must enable.", icon="ERROR") - elif ccWarnings and settings.g_shade and not shadeInCC and not shadeInBlender: - c.label(text="Shade is not being used, can disable.", icon="INFO") + warnings, errors = [], [] + if is_on("g_lighting", "g_fog", "g_fresnel_alpha"): + errors.append("Fog overrides Fresnel Alpha.") + if is_on("g_lighting", "g_fog", "g_lighttoalpha"): + errors.append("Fog overrides Light-to-Alpha.") + if is_on("g_lighting", "g_fresnel_alpha", "g_lighttoalpha"): + errors.append("Fresnel Alpha overrides Light-to-Alpha.") + if shadowMapInShadeAlpha and ccWarnings and ccUse["Shade Alpha"]: + warnings.append("Shadow map = shade alpha used in CC, probably wrong.") + if is_on("g_fog") and ccWarnings and ccUse["Shade Alpha"]: + warnings.append("Fog = shade alpha used in CC, probably wrong.") + if blendWarnings and shadeInBlender and not is_on("g_fog"): + warnings.append("Rendermode uses shade alpha, probably fog.") + elif blendWarnings and not shadeInBlender and is_on("g_fog"): + warnings.append("Fog not used in rendermode / blender, can disable.") + + if should_draw("g_lighttoalpha", "g_fresnel_alpha") or warnings or errors: + c = indentGroup(inputGroup, f"Shade alpha = {shadeAlphaLabel}:", True) + draw_mode(c, "g_lighttoalpha", "g_fresnel_alpha") + if warnings: + multilineLabel(c, "\n".join(warnings), "INFO") + if errors: + multilineLabel(c, "\n".join(errors), "ERROR") + else: + inputGroup.column().label(text=f"Shade alpha = {shadeAlphaLabel}") + + if should_draw("g_attroffset_st_enable", "g_attroffset_z_enable"): + indentGroup(inputGroup, "Attribute offsets:", True) + draw_mode(inputGroup, "g_attroffset_st_enable", "g_attroffset_z_enable") + + if should_draw("g_cull_front", "g_cull_back"): + c = indentGroup(inputGroup, "Face culling:", True) + draw_mode(c, "g_cull_front", "g_cull_back") + if is_on("g_cull_front", "g_cull_back"): + c.label(text="Nothing will be drawn.", icon="ERROR") + + if should_draw("g_zbuffer", "g_shade"): + c = indentGroup(inputGroup, "Disable if not using:", True) + draw_mode(c, "g_zbuffer", "g_shade") + if blendWarnings and not is_on("g_zbuffer") and zInBlender: + c.label(text="Rendermode / blender using Z, must enable.", icon="ERROR") + elif blendWarnings and is_on("g_zbuffer") and not zInBlender: + c.label(text="Z is not being used, can disable.", icon="INFO") + if ccWarnings and not is_on("g_shade") and (shadeInCC or shadeInBlender): + if shadeInCC and shadeInBlender: + where = "CC and blender" + elif shadeInCC: + where = "CC" + else: + where = "rendermode / blender" + c.label(text=f"Shade in use in {where}, must enable.", icon="ERROR") + elif ccWarnings and is_on("g_shade") and not shadeInCC and not shadeInBlender: + c.label(text="Shade is not being used, can disable.", icon="INFO") - c = indentGroup(inputGroup, "Not useful:", True) - c.prop(settings, "g_lod") - if isUcodeF3DEX1(bpy.context.scene.f3d_type): - c.prop(settings, "g_clipping") + if should_draw("g_lod", "g_clipping"): + c = indentGroup(inputGroup, "Not useful:", True) + draw_mode(c, "g_lod", "g_clipping") def ui_upper_mode(settings, dataHolder, layout: UILayout, useDropdown): @@ -643,8 +737,9 @@ def ui_other(settings, dataHolder, layout, useDropdown): dataHolder, "menu_other", text="Other Settings", icon="TRIA_DOWN" if dataHolder.menu_other else "TRIA_RIGHT" ) if not useDropdown or dataHolder.menu_other: - clipRatioGroup = inputGroup.column() - prop_split(clipRatioGroup, settings, "clip_ratio", "Clip Ratio") + if "Clip Ratio" in sources_in_ucode(bpy.context.scene.f3d_type): + clipRatioGroup = inputGroup.column() + prop_split(clipRatioGroup, settings, "clip_ratio", "Clip Ratio") if isinstance(dataHolder, Material) or isinstance(dataHolder, F3DMaterialProperty): blend_color_group = layout.row() @@ -765,6 +860,8 @@ def ui_chroma(self, material, layout, name, setName, setProp, showCheckBox): return inputGroup def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: UILayout, name, showCheckBox): + if inherit_light_and_fog(): + return inputGroup = layout.row() prop_input_left = inputGroup.column() prop_input = inputGroup.column() @@ -773,9 +870,10 @@ def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: UILayout, name, show else: prop_input_left.label(text=name) - prop_input_left.enabled = f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade + settings = f3d_mat.rdp_settings + prop_input_left.enabled = settings.is_geo_mode_on("g_lighting") and settings.is_geo_mode_on("g_shade") lightSettings: UILayout = prop_input.column() - if f3d_mat.rdp_settings.g_lighting: + if settings.is_geo_mode_on("g_lighting"): prop_input_left.separator(factor=0.25) light_controls = prop_input_left.box() light_controls.enabled = f3d_mat.set_lights @@ -805,8 +903,6 @@ def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: UILayout, name, show if f3d_mat.f3d_light6 is not None: lightSettings.prop_search(f3d_mat, "f3d_light7", bpy.data, "lights", text="") - prop_input.enabled = f3d_mat.set_lights and f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade - return inputGroup def ui_convert(self, material, layout, showCheckBox): @@ -961,7 +1057,8 @@ def ui_draw_layer(self, material, layout, context): prop_split(layout, material.f3d_mat.draw_layer, "oot", "Draw Layer") def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBox: bool) -> None: - if f3dMat.rdp_settings.g_ambocclusion: + sources = sources_in_ucode(bpy.context.scene.f3d_type) + if "AO" in sources and f3dMat.rdp_settings.g_ambocclusion: if showCheckBox or f3dMat.set_ao: inputGroup = inputCol.column() if showCheckBox: @@ -971,7 +1068,7 @@ def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBo prop_split(inputGroup.row(), f3dMat, "ao_directional", "AO Directional") prop_split(inputGroup.row(), f3dMat, "ao_point", "AO Point") - if f3dMat.rdp_settings.g_fresnel_color or f3dMat.rdp_settings.g_fresnel_alpha: + if "Fresnel" in sources and f3dMat.rdp_settings.g_fresnel_color or f3dMat.rdp_settings.g_fresnel_alpha: if showCheckBox or f3dMat.set_fresnel: inputGroup = inputCol.column() if showCheckBox: @@ -980,7 +1077,7 @@ def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBo prop_split(inputGroup.row(), f3dMat, "fresnel_lo", "Fresnel Lo") prop_split(inputGroup.row(), f3dMat, "fresnel_hi", "Fresnel Hi") - if f3dMat.rdp_settings.g_attroffset_st_enable: + if "ST Attr Offset" in sources and f3dMat.rdp_settings.g_attroffset_st_enable: if showCheckBox or f3dMat.set_attroffs_st: inputGroup = inputCol.column() if showCheckBox: @@ -988,7 +1085,7 @@ def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBo if f3dMat.set_attroffs_st: prop_split(inputGroup.row(), f3dMat, "attroffs_st", "ST Attr Offset") - if f3dMat.rdp_settings.g_attroffset_z_enable: + if "Z Attr Offset" in sources and f3dMat.rdp_settings.g_attroffset_z_enable: if showCheckBox or f3dMat.set_attroffs_z: inputGroup = inputCol.column() if showCheckBox: @@ -996,7 +1093,7 @@ def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBo if f3dMat.set_attroffs_z: prop_split(inputGroup.row(), f3dMat, "attroffs_z", "Z Attr Offset") - if f3dMat.rdp_settings.using_fog: + if "Fog" in sources and f3dMat.rdp_settings.using_fog and not inherit_light_and_fog(): if showCheckBox or f3dMat.set_fog: inputGroup = inputCol.column() if showCheckBox: @@ -1101,17 +1198,16 @@ def ui_cel_shading(self, material: Material, layout: UILayout): def checkDrawLayersWarnings(self, f3dMat: "F3DMaterialProperty", useDict: Dict[str, bool], layout: UILayout): settings = f3dMat.rdp_settings - isF3DEX3 = bpy.context.scene.f3d_type == "F3DEX3" - lightFxPrereq = isF3DEX3 and settings.g_lighting + f3d_type = bpy.context.scene.f3d_type anyUseShadeAlpha = useDict["Shade Alpha"] or settings.does_blender_use_input("G_BL_A_SHADE") - g_lighting = settings.g_lighting - g_fog = settings.g_fog - g_packed_normals = lightFxPrereq and settings.g_packed_normals - g_ambocclusion = lightFxPrereq and settings.g_ambocclusion - g_lighttoalpha = lightFxPrereq and settings.g_lighttoalpha - g_fresnel_color = lightFxPrereq and settings.g_fresnel_color - g_fresnel_alpha = lightFxPrereq and settings.g_fresnel_alpha + g_lighting = settings.is_geo_mode_on("g_lighting") or is_ucode_t3d(f3d_type) + g_fog = settings.is_geo_mode_on("g_fog") + g_packed_normals = settings.is_geo_mode_on("g_packed_normals") or is_ucode_t3d(f3d_type) + g_ambocclusion = settings.is_geo_mode_on("g_ambocclusion") + g_lighttoalpha = settings.is_geo_mode_on("g_lighttoalpha") + g_fresnel_color = settings.is_geo_mode_on("g_fresnel_color") + g_fresnel_alpha = settings.is_geo_mode_on("g_fresnel_alpha") usesVertexColor = useDict["Shade"] and (not g_lighting or (g_packed_normals and not g_fresnel_color)) usesVertexAlpha = anyUseShadeAlpha and (g_ambocclusion or not (g_fog or g_lighttoalpha or g_fresnel_alpha)) @@ -1141,6 +1237,7 @@ def checkDrawMixedCIWarning(self, layout, useDict, f3dMat): layout.box().column().label(text="Two CI textures must use the same CI format.", icon="ERROR") def draw_simple(self, f3dMat, material, layout, context): + f3d = get_F3D_GBI() self.ui_uvCheck(layout, context) inputCol = layout.column() @@ -1171,7 +1268,12 @@ def draw_simple(self, f3dMat, material, layout, context): if useDict["Environment"] and f3dMat.set_env: self.ui_env(material, inputCol, False) - showLightProperty = f3dMat.set_lights and f3dMat.rdp_settings.g_lighting and f3dMat.rdp_settings.g_shade + showLightProperty = ( + f3dMat.set_lights + and "Lighting" in sources_in_ucode(bpy.context.scene.f3d_type) + and f3dMat.rdp_settings.is_geo_mode_on("g_lighting") + and f3dMat.rdp_settings.is_geo_mode_on("g_shade") + ) if useDict["Shade"] and showLightProperty: self.ui_lights(f3dMat, inputCol, "Lighting", False) @@ -1187,6 +1289,7 @@ def draw_full(self, f3dMat, material, layout: UILayout, context): layout.row().prop(material, "menu_tab", expand=True) menuTab = material.menu_tab useDict = all_combiner_uses(f3dMat) + f3d = get_F3D_GBI() if menuTab == "Combiner": self.ui_draw_layer(material, layout, context) @@ -1260,7 +1363,7 @@ def drawCCProps(ui: UILayout, combiner: "CombinerProperty", isAlpha: bool, enabl if useDict["Environment"]: self.ui_env(material, inputCol, True) - if useDict["Shade"]: + if useDict["Shade"] and "Lighting" in sources_in_ucode(bpy.context.scene.f3d_type): self.ui_lights(f3dMat, inputCol, "Lighting", True) if useDict["Key"]: @@ -1652,7 +1755,7 @@ def update_fog_nodes(material: Material, context: Context): else: # If fog is not being calculated, pass in shade alpha material.node_tree.links.new(nodes["Shade Color"].outputs["Alpha"], fogBlender.inputs["FogAmount"]) - if f3dMat.use_global_fog or not f3dMat.set_fog: # Inherit fog + if f3dMat.use_global_fog or not f3dMat.set_fog or inherit_light_and_fog(): # Inherit fog link_if_none_exist(material, nodes["SceneProperties"].outputs["FogColor"], nodes["FogColor"].inputs[0]) link_if_none_exist(material, nodes["GlobalFogColor"].outputs[0], fogBlender.inputs["Fog Color"]) link_if_none_exist(material, nodes["SceneProperties"].outputs["FogNear"], nodes["CalcFog"].inputs["FogNear"]) @@ -1752,7 +1855,7 @@ def update_light_colors(material, context): f3dMat.ambient_light_color = new_amb - if f3dMat.set_lights: + if f3dMat.set_lights or inherit_light_and_fog(): remove_first_link_if_exists(material, nodes["Shade Color"].inputs["AmbientColor"].links) remove_first_link_if_exists(material, nodes["Shade Color"].inputs["Light0Color"].links) remove_first_link_if_exists(material, nodes["Shade Color"].inputs["Light1Color"].links) @@ -1806,6 +1909,7 @@ def update_node_values_of_material(material: Material, context): return f3dMat: "F3DMaterialProperty" = material.f3d_mat + settings: RDPSettings = f3dMat.rdp_settings update_combiner_connections(material, context) @@ -1813,8 +1917,8 @@ def update_node_values_of_material(material: Material, context): nodes = material.node_tree.nodes - if f3dMat.rdp_settings.g_lighting and f3dMat.rdp_settings.g_tex_gen: - if f3dMat.rdp_settings.g_tex_gen_linear: + if (settings.is_geo_mode_on("g_lighting") or inherit_light_and_fog()) and settings.is_geo_mode_on("g_tex_gen"): + if settings.is_geo_mode_on("g_tex_gen_linear"): nodes["UV"].node_tree = bpy.data.node_groups["UV_EnvMap_Linear"] else: nodes["UV"].node_tree = bpy.data.node_groups["UV_EnvMap"] @@ -1832,7 +1936,9 @@ def update_node_values_of_material(material: Material, context): "g_fog", "g_lighting", ]: - shdcol_inputs[propName.upper()].default_value = getattr(f3dMat.rdp_settings, propName) + shdcol_inputs[propName.upper()].default_value = f3dMat.rdp_settings.is_geo_mode_on(propName) + if is_ucode_t3d(bpy.context.scene.f3d_type): # Tiny3d always uses lighting * vertex color + shdcol_inputs["G_LIGHTING"].default_value = shdcol_inputs["G_PACKED_NORMALS"].default_value = True shdcol_inputs["AO Ambient"].default_value = f3dMat.ao_ambient shdcol_inputs["AO Directional"].default_value = f3dMat.ao_directional @@ -3562,6 +3668,12 @@ def blend_inputs(self): yield from self.blend_color_inputs yield from self.blend_alpha_inputs + def has_prop_in_ucode(self, prop: str) -> bool: + return prop in geo_modes_in_ucode(bpy.context.scene.f3d_type).values() + + def is_geo_mode_on(self, prop: str) -> bool: + return getattr(self, prop) if self.has_prop_in_ucode(prop) else False + def does_blender_use_input(self, setting: str) -> bool: return any(input == setting for input in self.blend_inputs) @@ -3577,46 +3689,18 @@ def attributes_from_dict(self, data: dict, info: dict): for key, attr, default in info: setattr(self, attr, data.get(key, default)) - geo_mode_all_attributes = [ - ("zBuffer", "g_zbuffer", False), - ("shade", "g_shade", False), - ("cullFront", "g_cull_front", False), - ("cullBack", "g_cull_back", False), - ("fog", "g_fog", False), - ("lighting", "g_lighting", False), - ("texGen", "g_tex_gen", False), - ("texGenLinear", "g_tex_gen_linear", False), - ("lod", "g_lod", False), - ("shadeSmooth", "g_shade_smooth", False), - ] - - geo_mode_f3dex_attributes = [ - ("clipping", "g_clipping", True), - ] + geo_mode_attributes = {**F3D_GEO_MODES, **F3DLX_GEO_MODES, **F3DEX3_GEO_MODES} - geo_mode_f3dex3_attributes = [ - ("ambientOcclusion", "g_ambocclusion", False), - ("attroffsetZ", "g_attroffset_z_enable", False), - ("attroffsetST", "g_attroffset_st_enable", False), - ("packedNormals", "g_packed_normals", False), - ("lightToAlpha", "g_lighttoalpha", False), - ("specularLighting", "g_lighting_specular", False), - ("fresnelToColor", "g_fresnel_color", False), - ("fresnelToAlpha", "g_fresnel_alpha", False), - ] - geo_mode_attributes = geo_mode_all_attributes + geo_mode_f3dex_attributes + geo_mode_f3dex3_attributes - - def geo_mode_to_dict(self, f3d=None): - f3d = f3d if f3d else get_F3D_GBI() - data = self.attributes_to_dict(self.geo_mode_all_attributes) - if f3d.F3DEX_GBI or f3d.F3DLP_GBI: - data.update(self.attributes_to_dict(self.geo_mode_f3dex_attributes)) - if f3d.F3DEX_GBI_3: - data.update(self.attributes_to_dict(self.geo_mode_f3dex3_attributes)) + def geo_mode_to_dict(self): + data = {} + for key, attr in geo_modes_in_ucode(bpy.context.scene.f3d_type).items(): + if getattr(self, attr): + data[key] = True return data def geo_mode_from_dict(self, data: dict): - self.attributes_from_dict(data, self.geo_mode_attributes) + for key, attr in self.geo_mode_attributes.items(): + setattr(self, attr, data.get(key, False)) other_mode_h_attributes = [ ("alphaDither", "g_mdsft_alpha_dither", "G_AD_DISABLE"), @@ -3729,9 +3813,9 @@ def other_from_dict(self, data: dict): self.clip_ratio = data.get("clipRatio", self.clip_ratio) self.num_textures_mipmapped = data.get("mipmapCount", self.num_textures_mipmapped) - def to_dict(self, f3d=None): + def to_dict(self): data = {} - data["geometryMode"] = self.geo_mode_to_dict(f3d) + data["geometryMode"] = self.geo_mode_to_dict() data["otherModeH"] = self.other_mode_h_to_dict() data["otherModeL"] = self.other_mode_l_to_dict() data["other"] = self.other_to_dict() @@ -4867,7 +4951,7 @@ def mat_register(): savePresets() Scene.f3d_type = bpy.props.EnumProperty( - name="F3D Microcode", items=enumF3D, default="F3D", update=update_all_material_nodes + name="Microcode", items=enumF3D, default="F3D", update=update_all_material_nodes ) # RDP Defaults @@ -4882,7 +4966,7 @@ def mat_register(): Material.mat_ver = bpy.props.IntProperty(default=1) Material.f3d_update_flag = bpy.props.BoolProperty() Material.f3d_mat = bpy.props.PointerProperty(type=F3DMaterialProperty) - Material.menu_tab = bpy.props.EnumProperty(items=enumF3DMenu) + Material.menu_tab = bpy.props.EnumProperty(items=menu_items_enum) Scene.f3dUserPresetsOnly = bpy.props.BoolProperty(name="User Presets Only") Scene.f3d_simple = bpy.props.BoolProperty(name="Display Simple", default=True) diff --git a/fast64_internal/repo_settings.py b/fast64_internal/repo_settings.py index e0af49b6f..85354df16 100644 --- a/fast64_internal/repo_settings.py +++ b/fast64_internal/repo_settings.py @@ -114,7 +114,7 @@ def draw_repo_settings(layout: UILayout, context: Context): SaveRepoSettings.draw_props(col, path=path) col.prop(fast64_settings, "auto_repo_load_settings") - prop_split(col, scene, "f3d_type", "F3D Microcode") + prop_split(col, scene, "f3d_type", "Microcode") col.prop(scene, "saveTextures") col.prop(fast64_settings, "auto_pick_texture_format") if fast64_settings.auto_pick_texture_format: From bce9ebd3761dde6c74418205f5dcf90a6f238cb2 Mon Sep 17 00:00:00 2001 From: Lila Date: Sun, 1 Dec 2024 19:51:44 +0000 Subject: [PATCH 6/7] [SM64] show custom export path without save textures (#475) recent regression caused by me, yay --- fast64_internal/sm64/sm64_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fast64_internal/sm64/sm64_objects.py b/fast64_internal/sm64/sm64_objects.py index bd87622e2..d093cfb99 100644 --- a/fast64_internal/sm64/sm64_objects.py +++ b/fast64_internal/sm64/sm64_objects.py @@ -2242,9 +2242,10 @@ def draw_props(self, layout): # pathing for gfx/col exports prop_split(box, self, "export_header_type", "Export Type") - if self.export_header_type == "Custom" and bpy.context.scene.saveTextures: + if self.export_header_type == "Custom": prop_split(box, self, "custom_export_path", "Custom Path") - prop_split(box, self, "custom_include_directory", "Texture Include Directory") + if bpy.context.scene.saveTextures: + prop_split(box, self, "custom_include_directory", "Texture Include Directory") elif self.export_header_type == "Actor": prop_split(box, self, "group_name", "Group") From 81fd463532b78959837ff8650b18d4a142d09637 Mon Sep 17 00:00:00 2001 From: fig02 Date: Tue, 3 Dec 2024 10:47:47 -0500 Subject: [PATCH 7/7] [OoT] Update cutscene macro names (#481) * update CS_HEADER and CS_END_OF_SCRIPT * deleted a word * format --- fast64_internal/oot/cutscene/constants.py | 6 +++--- fast64_internal/oot/cutscene/exporter/classes.py | 4 +++- fast64_internal/oot/cutscene/importer/classes.py | 8 ++++---- fast64_internal/oot/cutscene/operators.py | 6 +++--- fast64_internal/oot/cutscene_docs.md | 4 ++-- fast64_internal/oot/exporter/cutscene/__init__.py | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/fast64_internal/oot/cutscene/constants.py b/fast64_internal/oot/cutscene/constants.py index b3525246a..d5e155e5b 100644 --- a/fast64_internal/oot/cutscene/constants.py +++ b/fast64_internal/oot/cutscene/constants.py @@ -207,14 +207,14 @@ ] ootCSSingleCommands = [ - "CS_BEGIN_CUTSCENE", - "CS_END", + "CS_HEADER", + "CS_END_OF_SCRIPT", "CS_TRANSITION", "CS_DESTINATION", ] ootCSListAndSingleCommands = ootCSSingleCommands + ootCSListCommands -ootCSListAndSingleCommands.remove("CS_BEGIN_CUTSCENE") +ootCSListAndSingleCommands.remove("CS_HEADER") ootCutsceneCommandsC = ootCSSingleCommands + ootCSListCommands + ootCSListEntryCommands cmdToClass = { diff --git a/fast64_internal/oot/cutscene/exporter/classes.py b/fast64_internal/oot/cutscene/exporter/classes.py index f655b5483..8b42ca5ef 100644 --- a/fast64_internal/oot/cutscene/exporter/classes.py +++ b/fast64_internal/oot/cutscene/exporter/classes.py @@ -458,5 +458,7 @@ def getExportData(self): self.frameCount += self.motionFrameCount - self.frameCount return ( - (indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n") + csData + (indent + "CS_END(),\n") + (indent + f"CS_HEADER({self.entryTotal}, {self.frameCount}),\n") + + csData + + (indent + "CS_END_OF_SCRIPT(),\n") ) diff --git a/fast64_internal/oot/cutscene/importer/classes.py b/fast64_internal/oot/cutscene/importer/classes.py index ce76b694f..46e9df3eb 100644 --- a/fast64_internal/oot/cutscene/importer/classes.py +++ b/fast64_internal/oot/cutscene/importer/classes.py @@ -69,7 +69,7 @@ def getCmdParams(self, data: str, cmdName: str, paramNumber: int): return params def getNewCutscene(self, csData: str, name: str): - params = self.getCmdParams(csData, "CS_BEGIN_CUTSCENE", Cutscene.paramNumber) + params = self.getCmdParams(csData, "CS_HEADER", Cutscene.paramNumber) return Cutscene(name, getInteger(params[0]), getInteger(params[1])) def getParsedCutscenes(self): @@ -158,7 +158,7 @@ def getParsedCutscenes(self): if curCmd in ootCutsceneCommandsC: line = line.removesuffix(",") + "\n" - if curCmd in ootCSSingleCommands and curCmd != "CS_END": + if curCmd in ootCSSingleCommands and curCmd != "CS_END_OF_SCRIPT": parsedData += line if not cmdListFound and curCmd in ootCSListCommands: @@ -180,7 +180,7 @@ def getParsedCutscenes(self): print(f"{csName}, command:\n{line}") raise PluginError(f"ERROR: Found a list entry outside a list inside ``{csName}``!") - if cmdListFound and nextCmd == "CS_END" or nextCmd in ootCSListAndSingleCommands: + if cmdListFound and nextCmd == "CS_END_OF_SCRIPT" or nextCmd in ootCSListAndSingleCommands: cmdListFound = False parsedCS.append(parsedData) parsedData = "" @@ -213,7 +213,7 @@ def getCutsceneList(self): cmdListName = cmdListData.strip().split("(")[0] # create a new cutscene data - if cmdListName == "CS_BEGIN_CUTSCENE": + if cmdListName == "CS_HEADER": cutscene = self.getNewCutscene(data, parsedCS.csName) # if we have a cutscene, create and add the commands data in it diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index d8ac08975..f956158f4 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -67,13 +67,13 @@ def insertCutsceneData(filePath: str, csName: str): foundCutscene = True if foundCutscene: - if "CS_BEGIN_CUTSCENE" in line: + if "CS_HEADER" in line: # save the index of the line that contains the entry total and the framecount for later use beginIndex = i # looking at next line to see if we reached the end of the cs script index = i + 1 - if index < len(fileLines) and "CS_END" in fileLines[index]: + if index < len(fileLines) and "CS_END_OF_SCRIPT" in fileLines[index]: # exporting first to get the new framecount and the total of entries values fileLines.insert(index, motionExporter.getExportData()) @@ -90,7 +90,7 @@ def insertCutsceneData(filePath: str, csName: str): frames = re.sub(r"\b([0-9a-fA-F]*)\)", f"{frameCount + motionExporter.frameCount})", beginLine) fileLines[beginIndex] = f"{entries.split(', ')[0]}, {frames.split(', ')[1]}" else: - raise PluginError("ERROR: Can't find `CS_BEGIN_CUTSCENE()` parameters!") + raise PluginError("ERROR: Can't find `CS_HEADER()` parameters!") break fileData = CData() diff --git a/fast64_internal/oot/cutscene_docs.md b/fast64_internal/oot/cutscene_docs.md index e22acb573..c80d5413f 100644 --- a/fast64_internal/oot/cutscene_docs.md +++ b/fast64_internal/oot/cutscene_docs.md @@ -10,8 +10,8 @@ ### Commands More detailed informations and commands' parameters can be found [here](https://github.com/zeldaret/oot/blob/master/include/z64cutscene_commands.h) -- ``CS_BEGIN_CUTSCENE``: defines the beginning of a cutscene script -- ``CS_END``: defines the end of a cutscene script +- ``CS_HEADER``: defines the length and the total number of command entries for a cutscene script +- ``CS_END_OF_SCRIPT``: defines the end of a command list in a cutscene script - ``CS_CAM_POINT``: defines a single camera point, it can be used with any of the "eye" or "at" camera commands - ``CS_CAM_EYE``: defines a single eye point, this feature is not used in the final game and lacks polish - ``CS_CAM_EYE_SPLINE``: declares a list of "eye" camera points that forms a spline diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index f1cca53b6..a9d8c5b1a 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -83,10 +83,10 @@ def getC(self): csData.source = ( declarationBase + " = {\n" - + (indent + f"CS_BEGIN_CUTSCENE({self.totalEntries}, {self.frameCount}),\n") + + (indent + f"CS_HEADER({self.totalEntries}, {self.frameCount}),\n") + (self.data.destination.getCmd() if self.data.destination is not None else "") + "".join(entry.getCmd() for curList in dataListNames for entry in getattr(self.data, curList)) - + (indent + "CS_END(),\n") + + (indent + "CS_END_OF_SCRIPT(),\n") + "};\n\n" )