From e66d8f73662d9ef6daaed21d68c33b0afbf5b752 Mon Sep 17 00:00:00 2001 From: thecozies Date: Thu, 30 Jun 2022 19:10:18 -0500 Subject: [PATCH] Formatted V5 changes --- __init__.py | 46 ++- fast64_internal/f3d/f3d_gbi.py | 76 ++--- fast64_internal/f3d/f3d_material.py | 358 ++++++++++++-------- fast64_internal/f3d/f3d_material_helpers.py | 17 +- fast64_internal/f3d/f3d_writer.py | 75 ++-- fast64_internal/oot/__init__.py | 4 +- fast64_internal/render_settings.py | 87 +++-- fast64_internal/sm64/__init__.py | 4 +- fast64_internal/sm64/sm64_objects.py | 26 +- fast64_internal/utility.py | 23 +- 10 files changed, 424 insertions(+), 292 deletions(-) diff --git a/__init__.py b/__init__.py index 428639d..a9a1792 100644 --- a/__init__.py +++ b/__init__.py @@ -10,7 +10,11 @@ from pathlib import Path from .fast64_internal import * from .fast64_internal.panels import SM64_Panel from .fast64_internal.oot.oot_level import OOT_ObjectProperties -from .fast64_internal.render_settings import Fast64RenderSettings_Properties, resync_scene_props, on_update_render_settings +from .fast64_internal.render_settings import ( + Fast64RenderSettings_Properties, + resync_scene_props, + on_update_render_settings, +) import cProfile import pstats @@ -264,6 +268,7 @@ class Fast64_GlobalToolsPanel(bpy.types.Panel): # col.operator(CreateMetarig.bl_idname) addon_updater_ops.update_notice_box_ui(self, context) + class Fast64Settings_Properties(bpy.types.PropertyGroup): """Settings affecting exports for all games found in scene.fast64.settings""" @@ -328,11 +333,12 @@ class Fast64_ObjectProperties(bpy.types.PropertyGroup): sm64: bpy.props.PointerProperty(type=SM64_ObjectProperties, name="SM64 Object Properties") oot: bpy.props.PointerProperty(type=OOT_ObjectProperties, name="OOT Object Properties") + class UpgradeF3DMaterialsDialog(bpy.types.Operator): bl_idname = "dialog.upgrade_f3d_materials" bl_label = "Upgrade F3D Materials" - bl_options = {'REGISTER', 'UNDO'} - + bl_options = {"REGISTER", "UNDO"} + done = False def draw(self, context): @@ -348,13 +354,13 @@ class UpgradeF3DMaterialsDialog(bpy.types.Operator): purge_box.separator(factor=0.5) purge_box.label(text="How to purge:") purge_box.separator(factor=0.5) - purge_box.label(text='Go to the outliner, change the display mode') + purge_box.label(text="Go to the outliner, change the display mode") purge_box.label(text='to "Orphan Data" (broken heart icon)') purge_box.separator(factor=0.25) purge_box.label(text='Click "Purge" in the top right corner.') purge_box.separator(factor=0.25) - purge_box.label(text='Purge multiple times until the node groups') - purge_box.label(text='are gone.') + purge_box.label(text="Purge multiple times until the node groups") + purge_box.label(text="are gone.") layout.separator(factor=0.25) layout.label(text="You may click anywhere to close this dialog.") return @@ -365,14 +371,14 @@ class UpgradeF3DMaterialsDialog(bpy.types.Operator): box.separator() col = box.column() - col.alignment = 'CENTER' + col.alignment = "CENTER" col.alert = True col.label(text="Upgrade F3D Materials?") - + def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=600) - def execute(self, context: 'bpy.types.Context'): + def execute(self, context: "bpy.types.Context"): if context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") @@ -382,7 +388,8 @@ class UpgradeF3DMaterialsDialog(bpy.types.Operator): MatUpdateConvert.version, ) self.done = True - return {'FINISHED'} + return {"FINISHED"} + # def updateGameEditor(scene, context): # if scene.currentGameEditorMode == 'SM64': @@ -433,13 +440,14 @@ def upgrade_changed_props(): SM64_Properties.upgrade_changed_props() SM64_ObjectProperties.upgrade_changed_props() + def upgrade_scene_props_node(): - '''update f3d materials with SceneProperties node''' - has_old_f3d_mats = bool(len([ - mat for mat in bpy.data.materials if mat.is_f3d and mat.mat_ver < MatUpdateConvert.version - ])) + """update f3d materials with SceneProperties node""" + has_old_f3d_mats = bool( + len([mat for mat in bpy.data.materials if mat.is_f3d and mat.mat_ver < MatUpdateConvert.version]) + ) if has_old_f3d_mats: - bpy.ops.dialog.upgrade_f3d_materials('INVOKE_DEFAULT') + bpy.ops.dialog.upgrade_f3d_materials("INVOKE_DEFAULT") @bpy.app.handlers.persistent @@ -498,13 +506,17 @@ def register(): bpy.types.Scene.saveTextures = bpy.props.BoolProperty(name="Save Textures As PNGs (Breaks CI Textures)") bpy.types.Scene.generateF3DNodeGraph = bpy.props.BoolProperty(name="Generate F3D Node Graph", default=True) bpy.types.Scene.exportHiddenGeometry = bpy.props.BoolProperty(name="Export Hidden Geometry", default=True) - bpy.types.Scene.blenderF3DScale = bpy.props.FloatProperty(name="F3D Blender Scale", default=100, update=on_update_render_settings) + bpy.types.Scene.blenderF3DScale = bpy.props.FloatProperty( + name="F3D Blender Scale", default=100, update=on_update_render_settings + ) bpy.types.Scene.fast64 = bpy.props.PointerProperty(type=Fast64_Properties, name="Fast64 Properties") bpy.types.Bone.fast64 = bpy.props.PointerProperty(type=Fast64_BoneProperties, name="Fast64 Bone Properties") bpy.types.Object.fast64 = bpy.props.PointerProperty(type=Fast64_ObjectProperties, name="Fast64 Object Properties") - bpy.types.Scene.alreadyLinkedMaterialNodes = bpy.props.BoolProperty(name="Already Linked New Material Nodes", default=False) + bpy.types.Scene.alreadyLinkedMaterialNodes = bpy.props.BoolProperty( + name="Already Linked New Material Nodes", default=False + ) bpy.app.handlers.load_post.append(after_load) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 99a5933..2e48bd8 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -74,40 +74,40 @@ drawLayerRenderMode = { } CCMUXDict = { - 'COMBINED' : 0, - 'TEXEL0' : 1, - 'TEXEL1' : 2, - 'PRIMITIVE' : 3, - 'SHADE' : 4, - 'ENVIRONMENT' : 5, - 'CENTER' : 6, - 'SCALE' : 6, - 'COMBINED_ALPHA' : 7, - 'TEXEL0_ALPHA' : 8, - 'TEXEL1_ALPHA' : 9, - 'PRIMITIVE_ALPHA' : 10, - 'SHADE_ALPHA' : 11, - 'ENV_ALPHA' : 12, - 'LOD_FRACTION' : 13, - 'PRIM_LOD_FRAC' : 14, - 'NOISE' : 7, - 'K4' : 7, - 'K5' : 15, - '1' : 6, - '0' : 31 + "COMBINED": 0, + "TEXEL0": 1, + "TEXEL1": 2, + "PRIMITIVE": 3, + "SHADE": 4, + "ENVIRONMENT": 5, + "CENTER": 6, + "SCALE": 6, + "COMBINED_ALPHA": 7, + "TEXEL0_ALPHA": 8, + "TEXEL1_ALPHA": 9, + "PRIMITIVE_ALPHA": 10, + "SHADE_ALPHA": 11, + "ENV_ALPHA": 12, + "LOD_FRACTION": 13, + "PRIM_LOD_FRAC": 14, + "NOISE": 7, + "K4": 7, + "K5": 15, + "1": 6, + "0": 31, } ACMUXDict = { - 'COMBINED' : 0, - 'TEXEL0' : 1, - 'TEXEL1' : 2, - 'PRIMITIVE' : 3, - 'SHADE' : 4, - 'ENVIRONMENT' : 5, - 'LOD_FRACTION' : 0, - 'PRIM_LOD_FRAC' : 6, - '1' : 6, - '0' : 7, + "COMBINED": 0, + "TEXEL0": 1, + "TEXEL1": 2, + "PRIMITIVE": 3, + "SHADE": 4, + "ENVIRONMENT": 5, + "LOD_FRACTION": 0, + "PRIM_LOD_FRAC": 6, + "1": 6, + "0": 7, } @@ -1680,11 +1680,9 @@ class F3D: raise PluginError("Invalid G_MWO_b value for lights: " + n) -g_F3D = { - "GBI": None, - "f3d_type": None, - "isHWv1": None -} +g_F3D = {"GBI": None, "f3d_type": None, "isHWv1": None} + + def get_cached_F3D_GBI(f3d_type: str, isHWv1: bool) -> F3D: """Get constructed/cached F3D class""" global g_F3D @@ -1694,10 +1692,12 @@ def get_cached_F3D_GBI(f3d_type: str, isHWv1: bool) -> F3D: g_F3D["GBI"] = F3D(f3d_type, isHWv1) return g_F3D["GBI"] + def get_F3D_GBI() -> F3D: """Gets cached F3D class and automatically supplies params""" return get_cached_F3D_GBI(bpy.context.scene.f3d_type, bpy.context.scene.isHWv1) + def _SHIFTL(value, amount, mask): return (int(value) & ((1 << mask) - 1)) << amount @@ -5133,9 +5133,7 @@ class DPSetCombineMode: | GCCc1w0(CCMUXDict[self.a1], CCMUXDict[self.c1]), 0, 24, - ), GCCc0w1( - CCMUXDict[self.b0], CCMUXDict[self.d0], ACMUXDict[self.Ab0], ACMUXDict[self.Ad0] - ) | GCCc1w1( + ), GCCc0w1(CCMUXDict[self.b0], CCMUXDict[self.d0], ACMUXDict[self.Ab0], ACMUXDict[self.Ad0]) | GCCc1w1( CCMUXDict[self.b1], ACMUXDict[self.Aa1], ACMUXDict[self.Ac1], diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 2d351ad..670e2ea 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -17,9 +17,9 @@ from typing import Generator, Tuple from time import perf_counter_ns -logging.basicConfig(format='%(asctime)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') +logging.basicConfig(format="%(asctime)s: %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p") logger = logging.getLogger(__name__) -logger.setLevel('DEBUG') +logger.setLevel("DEBUG") bitSizeDict = { "G_IM_SIZ_4b": 4, @@ -145,7 +145,7 @@ def update_draw_layer(self, context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return - logger.info('update_draw_layer 2') + logger.info("update_draw_layer 2") drawLayer = material.f3d_mat.draw_layer if context.scene.gameEditorMode == "SM64": @@ -159,9 +159,9 @@ def update_draw_layer(self, context): material.f3d_mat.draw_layer.sm64 = "5" material.f3d_mat.presetName = "Custom" update_blend_method(material, context) - logger.info('update_draw_layer 3') + logger.info("update_draw_layer 3") set_output_node_groups(material) - logger.info('update_draw_layer 4') + logger.info("update_draw_layer 4") def get_blend_method(material): @@ -663,7 +663,7 @@ class F3DPanel(bpy.types.Panel): prop_input_left.enabled = f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade lightSettings: bpy.types.UILayout = prop_input.column() if f3d_mat.rdp_settings.g_lighting: - prop_input_left.separator(factor=.25) + prop_input_left.separator(factor=0.25) light_controls = prop_input_left.box() light_controls.enabled = f3d_mat.set_lights @@ -694,9 +694,7 @@ class F3DPanel(bpy.types.Panel): # layout.box().label(text = "Note: Lighting preview is not 100% accurate.") # layout.box().label(text = "For vertex colors, clear 'Lighting'.") - prop_input.enabled = ( - f3d_mat.set_lights and f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade - ) + prop_input.enabled = f3d_mat.set_lights and f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade return inputGroup @@ -938,9 +936,12 @@ class F3DPanel(bpy.types.Panel): rowAlpha2.prop(f3dMat.combiner2, "D_alpha") if useDict["Texture 0"]: - cc_list = ['A', 'B', 'C', 'D', 'A_alpha', 'B_alpha', 'C_alpha', 'D_alpha'] - if len([c for c in cc_list if getattr(f3dMat.combiner2, c) == 'TEXEL1']): - combinerBox2.label(text="Warning: Using 'Texture 1' in Cycle 2 can cause display issues!", icon="LIBRARY_DATA_BROKEN") + cc_list = ["A", "B", "C", "D", "A_alpha", "B_alpha", "C_alpha", "D_alpha"] + if len([c for c in cc_list if getattr(f3dMat.combiner2, c) == "TEXEL1"]): + combinerBox2.label( + text="Warning: Using 'Texture 1' in Cycle 2 can cause display issues!", + icon="LIBRARY_DATA_BROKEN", + ) combinerBox2.label(text="Note: In second cycle, texture 0 and texture 1 are flipped.") if menuTab == "Sources": @@ -1025,6 +1026,7 @@ class F3DPanel(bpy.types.Panel): presetCol.prop(context.scene, "f3dUserPresetsOnly") self.draw_full(f3dMat, material, layout, context) + def ui_tileScroll(tex, name, layout): row = layout.row() row.label(text=name) @@ -1120,12 +1122,15 @@ def update_node_values(self, context, update_preset=True): if update_preset: material.f3d_mat.presetName = "Custom" + def update_node_values_with_preset(self, context): update_node_values(self, context, update_preset=True) + def update_node_values_without_preset(self, context): update_node_values(self, context, update_preset=False) + def update_light_properties(self, context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: @@ -1133,6 +1138,7 @@ def update_light_properties(self, context): update_light_colors(material, context) + def getSocketFromCombinerToNodeDictColor(nodes, combinerInput): nodeName, socketIndex = combinerToNodeDictColor[combinerInput] return nodes[nodeName].outputs[socketIndex] if nodeName is not None else None @@ -1182,15 +1188,20 @@ alpha_combiner_inputs = { "0": (None, 0), } -def remove_first_link_if_exists(material: bpy.types.Material, links): # TODO: (V5) add links type annotation + +def remove_first_link_if_exists(material: bpy.types.Material, links): # TODO: (V5) add links type annotation if len(links) > 0: link = links[0] material.node_tree.links.remove(link) -def link_if_none_exist(material: bpy.types.Material, fromOutput, toInput): # TODO: (V5) add output/input type annotations + +def link_if_none_exist( + material: bpy.types.Material, fromOutput, toInput +): # TODO: (V5) add output/input type annotations if len(fromOutput.links) == 0: material.node_tree.links.new(fromOutput, toInput) + def update_node_combiner(material, combinerInputs, cycleIndex): nodes = material.node_tree.nodes @@ -1201,8 +1212,10 @@ def update_node_combiner(material, combinerInputs, cycleIndex): for i in range(8): combiner_input = combinerInputs[i] - if cycleIndex == 2 and 'TEXEL' in combiner_input: - combiner_input = combiner_input.replace("0", "1") if "0" in combiner_input else combiner_input.replace("1", "0") + if cycleIndex == 2 and "TEXEL" in combiner_input: + combiner_input = ( + combiner_input.replace("0", "1") if "0" in combiner_input else combiner_input.replace("1", "0") + ) if combiner_input == "0": for link in cycle_node.inputs[i].links: @@ -1213,10 +1226,10 @@ def update_node_combiner(material, combinerInputs, cycleIndex): if cycleIndex == 2: if combiner_input == "COMBINED": node_name = "Combined_C" - output_name = 0 # using an index due to it being a reroute node + output_name = 0 # using an index due to it being a reroute node elif combiner_input == "COMBINED_ALPHA": node_name = "Combined_A" - output_name = 0 # using an index due to it being a reroute node + output_name = 0 # using an index due to it being a reroute node if node_name is not None: input_node = nodes[node_name] input_value = input_node.outputs[output_name] @@ -1226,32 +1239,30 @@ def update_node_combiner(material, combinerInputs, cycleIndex): if cycleIndex == 2: if combiner_input == "COMBINED": node_name = "Combined_A" - output_name = 0 # using an index due to it being a reroute node + output_name = 0 # using an index due to it being a reroute node if node_name is not None: input_node = nodes[node_name] input_value = input_node.outputs[output_name] material.node_tree.links.new(cycle_node.inputs[i], input_value) + def check_fog_settings(material: bpy.types.Material): f3dMat: "F3DMaterialProperty" = material.f3d_mat fog_enabled: bool = f3dMat.rdp_settings.g_fog fog_rendermode_enabled: bool = fog_enabled - + is_one_cycle = f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_1CYCLE" - + if is_one_cycle or fog_enabled == False: fog_rendermode_enabled = False elif f3dMat.rdp_settings.set_rendermode: if f3dMat.rdp_settings.rendermode_advanced_enabled: - if ( - f3dMat.rdp_settings.blend_p1 == "G_BL_CLR_FOG" - and f3dMat.rdp_settings.blend_a1 == "G_BL_A_SHADE" - ): + if f3dMat.rdp_settings.blend_p1 == "G_BL_CLR_FOG" and f3dMat.rdp_settings.blend_a1 == "G_BL_A_SHADE": fog_rendermode_enabled = True else: f3d = get_F3D_GBI() r_mode = getattr(f3d, f3dMat.rdp_settings.rendermode_preset_cycle_1, f3d.G_RM_PASS) - + # Note: GBL_c1 uses (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18 # This checks if m1a is G_BL_CLR_FOG and m1b is G_BL_A_SHADE if r_mode & (f3d.G_BL_CLR_FOG << 30) != 0 and r_mode & (f3d.G_BL_A_SHADE << 26): @@ -1262,7 +1273,7 @@ def check_fog_settings(material: bpy.types.Material): fog_rendermode_enabled = True return fog_enabled, fog_rendermode_enabled - + def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): nodes = material.node_tree.nodes @@ -1271,7 +1282,7 @@ def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): fog_enabled, fog_rendermode_enabled = check_fog_settings(material) nodes["Shade Color"].inputs["Fog"].default_value = int(fog_enabled) - + fogBlender: bpy.types.ShaderNodeGroup = nodes["FogBlender"] if fog_rendermode_enabled and fog_enabled: fogBlender.node_tree = bpy.data.node_groups["FogBlender_On"] @@ -1281,29 +1292,35 @@ def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): if fog_enabled: inherit_fog = f3dMat.use_global_fog or not f3dMat.set_fog if 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"]) - link_if_none_exist(material, nodes['SceneProperties'].outputs["FogFar"], nodes["CalcFog"].inputs["FogFar"]) + 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"] + ) + link_if_none_exist(material, nodes["SceneProperties"].outputs["FogFar"], nodes["CalcFog"].inputs["FogFar"]) else: remove_first_link_if_exists(material, nodes["FogBlender"].inputs["Fog Color"].links) remove_first_link_if_exists(material, nodes["CalcFog"].inputs["FogNear"].links) remove_first_link_if_exists(material, nodes["CalcFog"].inputs["FogFar"].links) - fogBlender.inputs['Fog Color'].default_value = f3dMat.fog_color + fogBlender.inputs["Fog Color"].default_value = f3dMat.fog_color nodes["CalcFog"].inputs["FogNear"].default_value = f3dMat.fog_position[0] nodes["CalcFog"].inputs["FogFar"].default_value = f3dMat.fog_position[1] + def update_noise_nodes(material: bpy.types.Material): f3dMat: "F3DMaterialProperty" = material.f3d_mat - uses_noise = f3dMat.combiner1.A == 'NOISE' or f3dMat.combiner2.A == 'NOISE' + uses_noise = f3dMat.combiner1.A == "NOISE" or f3dMat.combiner2.A == "NOISE" noise_group = bpy.data.node_groups["F3DNoise_Animated" if uses_noise else "F3DNoise_NonAnimated"] nodes = material.node_tree.nodes if nodes["F3DNoiseFactor"].node_tree is not noise_group: nodes["F3DNoiseFactor"].node_tree = noise_group -def update_combiner_connections(material: bpy.types.Material, context: bpy.types.Context, combiner: (int | None) = None): + +def update_combiner_connections( + material: bpy.types.Material, context: bpy.types.Context, combiner: (int | None) = None +): f3dMat: "F3DMaterialProperty" = material.f3d_mat update_noise_nodes(material) @@ -1335,6 +1352,7 @@ def update_combiner_connections(material: bpy.types.Material, context: bpy.types ] update_node_combiner(material, combinerInputs2, 2) + def set_output_node_groups(material: bpy.types.Material): nodes = material.node_tree.nodes f3dMat: "F3DMaterialProperty" = material.f3d_mat @@ -1352,13 +1370,14 @@ def set_output_node_groups(material: bpy.types.Material): else: output_node.node_tree = bpy.data.node_groups["OUTPUT_2CYCLE_XLU"] + def update_light_colors(material, context): f3dMat: "F3DMaterialProperty" = material.f3d_mat nodes = material.node_tree.nodes - + if f3dMat.use_default_lighting and f3dMat.set_ambient_from_light: amb: Color = Color(f3dMat.default_light_color[:3]) - amb.v /= 4.672 # dividing by 4.672 approximates to half of the light color's value after gamma correction is performed on both + amb.v /= 4.672 # dividing by 4.672 approximates to half of the light color's value after gamma correction is performed on both new_amb = [c for c in amb] new_amb.append(1.0) @@ -1376,7 +1395,7 @@ def update_light_colors(material, context): light = f3dMat.f3d_light1.color else: light = [1.0, 1.0, 1.0, 1.0] - + corrected_col = gammaCorrect(light) corrected_col.append(1.0) corrected_amb = gammaCorrect(f3dMat.ambient_light_color) @@ -1389,11 +1408,12 @@ def update_light_colors(material, context): amb_col = [0.5, 0.5, 0.5, 1.0] nodes["Shade Color"].inputs["Shade Color"].default_value = tuple(c for c in col) nodes["Shade Color"].inputs["Ambient Color"].default_value = tuple(c for c in amb_col) - link_if_none_exist(material, nodes['ShadeColOut'].outputs[0], nodes["Shade Color"].inputs["Shade Color"]) - link_if_none_exist(material, nodes['AmbientColOut'].outputs[0], nodes["Shade Color"].inputs["Ambient Color"]) + link_if_none_exist(material, nodes["ShadeColOut"].outputs[0], nodes["Shade Color"].inputs["Shade Color"]) + link_if_none_exist(material, nodes["AmbientColOut"].outputs[0], nodes["Shade Color"].inputs["Ambient Color"]) + def update_color_node(combiner_inputs, color: Color, prefix: str): - '''Function for updating either Prim or Env colors''' + """Function for updating either Prim or Env colors""" # TODO: feature to toggle gamma correction corrected_prim = gammaCorrect(color) combiner_inputs[f"{prefix} Color"].default_value = ( @@ -1403,7 +1423,8 @@ def update_color_node(combiner_inputs, color: Color, prefix: str): 1.0, ) combiner_inputs[f"{prefix} Alpha"].default_value = color[3] - + + # prim_color | Prim # env_color | Env def get_color_input_update_callback(attr_name="", prefix=""): @@ -1415,8 +1436,9 @@ def get_color_input_update_callback(attr_name="", prefix=""): nodes = material.node_tree.nodes combiner_inputs = nodes["CombinerInputs"].inputs update_color_node(combiner_inputs, getattr(f3dMat, attr_name), prefix) + return input_update_callback - + def update_node_values_of_material(material: bpy.types.Material, context): nodes = material.node_tree.nodes @@ -1471,19 +1493,20 @@ def update_node_values_of_material(material: bpy.types.Material, context): update_blend_method(material, context) update_fog_nodes(material, context) + def set_texture_settings_node(material: bpy.types.Material): nodes = material.node_tree.nodes textureSettings: bpy.types.ShaderNodeGroup = nodes["TextureSettings"] - desired_group = bpy.data.node_groups['TextureSettings_Lite'] - if ( - (material.f3d_mat.tex0.tex and not material.f3d_mat.tex0.autoprop) - or (material.f3d_mat.tex1.tex and not material.f3d_mat.tex1.autoprop) + desired_group = bpy.data.node_groups["TextureSettings_Lite"] + if (material.f3d_mat.tex0.tex and not material.f3d_mat.tex0.autoprop) or ( + material.f3d_mat.tex1.tex and not material.f3d_mat.tex1.autoprop ): - desired_group = bpy.data.node_groups['TextureSettings_Advanced'] + desired_group = bpy.data.node_groups["TextureSettings_Advanced"] if textureSettings.node_tree is not desired_group: textureSettings.node_tree = desired_group + def setAutoProp(fieldProperty, pixelLength): fieldProperty.mask = math.ceil(math.log(pixelLength, 2) - 0.001) fieldProperty.shift = 0 @@ -1493,34 +1516,34 @@ def setAutoProp(fieldProperty, pixelLength): fieldProperty.high *= 2 fieldProperty.high -= 1 + def set_texture_size(self, tex_size, tex_index): nodes = self.node_tree.nodes - uv_basis: bpy.types.ShaderNodeGroup = nodes['UV Basis'] + uv_basis: bpy.types.ShaderNodeGroup = nodes["UV Basis"] inputs = uv_basis.inputs - + inputs[f"{tex_index} S TexSize"].default_value = tex_size[0] inputs[f"{tex_index} T TexSize"].default_value = tex_size[1] + def round_10_2(val: float): - return (float(int(val * 4)) / 4.0) + return float(int(val * 4)) / 4.0 + def update_tex_values_field( - self: bpy.types.Material, - texProperty: "TextureProperty", - tex_size: list[int], - tex_index: int + self: bpy.types.Material, texProperty: "TextureProperty", tex_size: list[int], tex_index: int ): nodes = self.node_tree.nodes textureSettings: bpy.types.ShaderNodeGroup = nodes["TextureSettings"] inputs = textureSettings.inputs - + set_texture_size(self, tex_size, tex_index) if texProperty.autoprop: - # # TODO: (V5) is this f****** necessary? it happens in like 50 places + # # TODO: (V5) is this f****** necessary? it happens in like 50 places setAutoProp(texProperty.S, tex_size[0]) setAutoProp(texProperty.T, tex_size[1]) - + str_index = str(tex_index) # S/T Low @@ -1547,22 +1570,22 @@ def update_tex_values_field( inputs[str_index + " S Shift"].default_value = texProperty.S.shift inputs[str_index + " T Shift"].default_value = texProperty.T.shift + def iter_tex_nodes(node_tree: bpy.types.NodeTree, texIndex: int) -> Generator[bpy.types.TextureNodeImage, None, None]: for i in range(1, 5): - nodeName = f'Tex{texIndex}_{i}' + nodeName = f"Tex{texIndex}_{i}" if node_tree.nodes.get(nodeName): yield node_tree.nodes[nodeName] + def set_texture_nodes_settings( - material: bpy.types.Material, - texProperty: "TextureProperty", - texIndex: int + material: bpy.types.Material, texProperty: "TextureProperty", texIndex: int ) -> (list[int] | None): node_tree = material.node_tree f3dMat: "F3DMaterialProperty" = material.f3d_mat - + # Return value - texSize: None | list['int'] = None + texSize: None | list["int"] = None # Enforce typing from generator texNode: None | bpy.types.TextureNodeImage = None @@ -1582,17 +1605,12 @@ def set_texture_nodes_settings( return texSize -def update_tex_values_index( - self: bpy.types.Material, - *, - texProperty: "TextureProperty", - texIndex -): +def update_tex_values_index(self: bpy.types.Material, *, texProperty: "TextureProperty", texIndex): nodes = self.node_tree.nodes tex_size = set_texture_nodes_settings(self, texProperty, texIndex) - if tex_size: # only returns tex size if a texture is being set + if tex_size: # only returns tex size if a texture is being set if tex_size[0] > 0 and tex_size[1] > 0: if texProperty.autoprop: setAutoProp(texProperty.S, tex_size[0]) @@ -1612,6 +1630,7 @@ def update_tex_values_index( if tex_I_node.node_tree is not desired_node: tex_I_node.node_tree = desired_node + def update_tex_values_and_formats(self, context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: @@ -1624,6 +1643,7 @@ def update_tex_values_and_formats(self, context): update_tex_values_manual(context.material, context) + def update_tex_values(self, context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: @@ -1636,27 +1656,29 @@ def update_tex_values(self, context): update_tex_values_manual(material, context, prop_path=prop_path) + def get_tex_basis_size(f3d_mat: "F3DMaterialProperty"): tex_size = None if f3d_mat.tex0.tex is not None and f3d_mat.tex1.tex is not None: - return f3d_mat.tex0.tex.size if f3d_mat.uv_basis == 'TEXEL0' else \ - f3d_mat.tex1.tex.size + return f3d_mat.tex0.tex.size if f3d_mat.uv_basis == "TEXEL0" else f3d_mat.tex1.tex.size elif f3d_mat.tex0.tex is not None: return f3d_mat.tex0.tex.size elif f3d_mat.tex1.tex is not None: return f3d_mat.tex1.tex.size return tex_size + def get_tex_gen_size(tex_size: list[int | float]): return (tex_size[0] - 1) / 1024, (tex_size[1] - 1) / 1024 + def update_tex_values_manual(material: bpy.types.Material, context, prop_path=None): f3dMat: "F3DMaterialProperty" = material.f3d_mat nodes = material.node_tree.nodes texture_settings = nodes["TextureSettings"] texture_inputs: bpy.types.NodeInputs = texture_settings.inputs - - isTexGen = f3dMat.rdp_settings.g_tex_gen # linear requires tex gen to be enabled as well + + isTexGen = f3dMat.rdp_settings.g_tex_gen # linear requires tex gen to be enabled as well if f3dMat.scale_autoprop: if isTexGen: @@ -1666,7 +1688,7 @@ def update_tex_values_manual(material: bpy.types.Material, context, prop_path=No # This is needed for exporting tex gen! f3dMat.tex_scale = get_tex_gen_size(tex_size) else: - f3dMat.tex_scale = (1,1) + f3dMat.tex_scale = (1, 1) if f3dMat.tex0.tex is not None: texture_inputs["0 S TexSize"].default_value = f3dMat.tex0.tex.size[0] @@ -1675,35 +1697,36 @@ def update_tex_values_manual(material: bpy.types.Material, context, prop_path=No texture_inputs["1 S TexSize"].default_value = f3dMat.tex1.tex.size[0] texture_inputs["1 T TexSize"].default_value = f3dMat.tex1.tex.size[0] - uv_basis: bpy.types.ShaderNodeGroup = nodes['UV Basis'] + uv_basis: bpy.types.ShaderNodeGroup = nodes["UV Basis"] if f3dMat.uv_basis == "TEXEL0": uv_basis.node_tree = bpy.data.node_groups["UV Basis 0"] else: uv_basis.node_tree = bpy.data.node_groups["UV Basis 1"] if not isTexGen: - uv_basis.inputs['S Scale'].default_value = f3dMat.tex_scale[0] - uv_basis.inputs['T Scale'].default_value = f3dMat.tex_scale[1] + uv_basis.inputs["S Scale"].default_value = f3dMat.tex_scale[0] + uv_basis.inputs["T Scale"].default_value = f3dMat.tex_scale[1] elif f3dMat.scale_autoprop: # Tex gen is 1:1 - uv_basis.inputs['S Scale'].default_value = 1 - uv_basis.inputs['T Scale'].default_value = 1 + uv_basis.inputs["S Scale"].default_value = 1 + uv_basis.inputs["T Scale"].default_value = 1 else: gen_size = get_tex_gen_size(get_tex_basis_size(f3dMat)) # scale tex gen proportionally node_uv_scale = (f3dMat.tex_scale[0] / gen_size[0], f3dMat.tex_scale[1] / gen_size[1]) - uv_basis.inputs['S Scale'].default_value = node_uv_scale[0] - uv_basis.inputs['T Scale'].default_value = node_uv_scale[1] + uv_basis.inputs["S Scale"].default_value = node_uv_scale[0] + uv_basis.inputs["T Scale"].default_value = node_uv_scale[1] - if not prop_path or 'tex0' in prop_path: + if not prop_path or "tex0" in prop_path: update_tex_values_index(material, texProperty=f3dMat.tex0, texIndex=0) - if not prop_path or 'tex1' in prop_path: + if not prop_path or "tex1" in prop_path: update_tex_values_index(material, texProperty=f3dMat.tex1, texIndex=1) - texture_inputs['3 Point'].default_value = int(f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_BILERP") - uv_basis.inputs['EnableOffset'].default_value = int(f3dMat.rdp_settings.g_mdsft_text_filt != "G_TF_POINT") + texture_inputs["3 Point"].default_value = int(f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_BILERP") + uv_basis.inputs["EnableOffset"].default_value = int(f3dMat.rdp_settings.g_mdsft_text_filt != "G_TF_POINT") set_texture_settings_node(material) + def getMaterialScrollDimensions(material): useDict = all_combiner_uses(material) @@ -1738,6 +1761,7 @@ def getMaterialScrollDimensions(material): else: return [32, 32] + def update_preset_manual(material, context): if hasNodeGraph(material): update_node_values_of_material(material, context) @@ -1781,25 +1805,23 @@ def load_handler(dummy): lib.filepath = new_lib_path lib.reload() - bpy.context.scene['f3d_lib_dir'] = None # force node reload! + bpy.context.scene["f3d_lib_dir"] = None # force node reload! link_f3d_material_library() bpy.app.handlers.load_post.append(load_handler) # bpy.context.mode returns the key's here, while the values are required by bpy.ops.object.mode_set -BLENDER_MODE_TO_MODE_SET = { - "PAINT_VERTEX": "VERTEX_PAINT", - "EDIT_MESH": "EDIT" -} +BLENDER_MODE_TO_MODE_SET = {"PAINT_VERTEX": "VERTEX_PAINT", "EDIT_MESH": "EDIT"} get_mode_set_from_context_mode = lambda mode: BLENDER_MODE_TO_MODE_SET.get(mode, "OBJECT") SCENE_PROPERTIES_VERSION = 1 + def createOrUpdateSceneProperties(): group = bpy.data.node_groups.get("SceneProperties") - upgrade_group = bool(group and group.get('version', -1) < SCENE_PROPERTIES_VERSION) - + upgrade_group = bool(group and group.get("version", -1) < SCENE_PROPERTIES_VERSION) + if group and not upgrade_group: # Group is ready and up to date return @@ -1810,13 +1832,13 @@ def createOrUpdateSceneProperties(): group.outputs.remove(out) new_group = group else: - logger.info('Creating Scene Properties') + logger.info("Creating Scene Properties") # create a group new_group = bpy.data.node_groups.new("SceneProperties", "ShaderNodeTree") # create group outputs new_group.nodes.new("NodeGroupOutput") - new_group['version'] = SCENE_PROPERTIES_VERSION + new_group["version"] = SCENE_PROPERTIES_VERSION # Create outputs _nodeFogEnable: bpy.types.NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogEnable") @@ -1828,20 +1850,23 @@ def createOrUpdateSceneProperties(): _nodeFogFar: bpy.types.NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogFar") _nodeShadeColor: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketColor", "ShadeColor") _nodeAmbientColor: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketColor", "AmbientColor") - _nodeLightDirection: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketVectorDirection", "LightDirection") + _nodeLightDirection: bpy.types.NodeSocketColor = new_group.outputs.new( + "NodeSocketVectorDirection", "LightDirection" + ) # Set outputs from render settings - sceneOutputs: bpy.types.NodeGroupOutput = new_group.nodes['Group Output'] + sceneOutputs: bpy.types.NodeGroupOutput = new_group.nodes["Group Output"] renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings update_scene_props_from_render_settings(bpy.context, sceneOutputs, renderSettings) + def createScenePropertiesForMaterial(material: bpy.types.Material): node_tree = material.node_tree - + # Either create or update SceneProperties if needed createOrUpdateSceneProperties() - + # create a new group node to hold the tree scene_props = node_tree.nodes.new(type="ShaderNodeGroup") scene_props.name = "SceneProperties" @@ -1852,7 +1877,9 @@ def createScenePropertiesForMaterial(material: bpy.types.Material): node_tree.links.new(scene_props.outputs["FogColor"], node_tree.nodes["FogColor"].inputs[0]) node_tree.links.new(scene_props.outputs["FogNear"], node_tree.nodes["CalcFog"].inputs["FogNear"]) node_tree.links.new(scene_props.outputs["FogFar"], node_tree.nodes["CalcFog"].inputs["FogFar"]) - node_tree.links.new(scene_props.outputs["Blender_Game_Scale"], node_tree.nodes["CalcFog"].inputs["Blender_Game_Scale"]) + node_tree.links.new( + scene_props.outputs["Blender_Game_Scale"], node_tree.nodes["CalcFog"].inputs["Blender_Game_Scale"] + ) node_tree.links.new(scene_props.outputs["F3D_NearClip"], node_tree.nodes["CalcFog"].inputs["F3D_NearClip"]) node_tree.links.new(scene_props.outputs["F3D_FarClip"], node_tree.nodes["CalcFog"].inputs["F3D_FarClip"]) @@ -1860,6 +1887,7 @@ def createScenePropertiesForMaterial(material: bpy.types.Material): node_tree.links.new(scene_props.outputs["AmbientColor"], node_tree.nodes["AmbientColor"].inputs[0]) node_tree.links.new(scene_props.outputs["LightDirection"], node_tree.nodes["LightDirection"].inputs[0]) + def link_f3d_material_library(): dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "f3d_material_library.blend") @@ -1876,21 +1904,19 @@ def link_f3d_material_library(): # linking is SUPER slow, this only links if the scene hasnt been linked yet # in future updates, this will likely need to be something numerated so if more nodes are added then they will be linked - if bpy.context.scene.get('f3d_lib_dir') != dirNode: + if bpy.context.scene.get("f3d_lib_dir") != dirNode: # link groups after to bring extra node_groups for node_group in data_from.node_groups: if node_group is not None: - bpy.ops.wm.link( - filepath=os.path.join(dirNode, node_group), - directory=dirNode, - filename=node_group) + bpy.ops.wm.link(filepath=os.path.join(dirNode, node_group), directory=dirNode, filename=node_group) bpy.context.scene.alreadyLinkedMaterialNodes = True - bpy.context.scene['f3d_lib_dir'] = dirNode - + bpy.context.scene["f3d_lib_dir"] = dirNode + # TODO: Figure out a better way to save the user's old mode if prevMode != "OBJECT": bpy.ops.object.mode_set(mode=get_mode_set_from_context_mode(prevMode)) + def shouldConvOrCreateColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): has_attr, conv_attr = False, False if attr_name in mesh.attributes: @@ -1899,6 +1925,7 @@ def shouldConvOrCreateColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): conv_attr = attribute.data_type != "FLOAT_COLOR" or attribute.domain != "CORNER" return has_attr, conv_attr + def convertColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): prev_index = mesh.attributes.active_index attr_index = mesh.attributes.find(attr_name) @@ -1909,6 +1936,7 @@ def convertColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): bpy.ops.geometry.attribute_convert(mode="GENERIC", domain="CORNER", data_type="FLOAT_COLOR") mesh.attributes.active_index = prev_index + def addColorAttributesToModel(obj: bpy.types.Object): if not isinstance(obj.data, bpy.types.Mesh): return @@ -1916,7 +1944,7 @@ def addColorAttributesToModel(obj: bpy.types.Object): prevMode = bpy.context.mode if prevMode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") - + selectSingleObject(obj) mesh: bpy.types.Mesh = obj.data @@ -1936,6 +1964,7 @@ def addColorAttributesToModel(obj: bpy.types.Object): if prevMode != "OBJECT": bpy.ops.object.mode_set(mode=get_mode_set_from_context_mode(prevMode)) + def createF3DMat(obj: bpy.types.Object | None, preset="Shaded Solid", index=None): # link all node_groups + material from addon's data .blend link_f3d_material_library() @@ -2011,14 +2040,17 @@ class ReloadDefaultF3DPresets(bpy.types.Operator): self.report({"INFO"}, "Success!") return {"FINISHED"} # must return a set + def get_tex_prop_from_path(material: bpy.types.Material, path: str) -> Tuple["TextureProperty", int]: - if 'tex0' in path: + if "tex0" in path: return material.f3d_mat.tex0, 0 return material.f3d_mat.tex1, 1 + def already_updating_material(material: bpy.types.Material | None): """Check if material is updating already""" - return getattr(material, 'f3d_update_flag', False) + return getattr(material, "f3d_update_flag", False) + def update_tex_field_prop(self: bpy.types.Property, context: bpy.types.Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: @@ -2033,11 +2065,12 @@ def update_tex_field_prop(self: bpy.types.Property, context: bpy.types.Context): update_tex_values_field(material, tex_property, tex_size, tex_index) set_texture_settings_node(material) + def toggle_auto_prop(self, context: bpy.types.Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return - + prop_path = self.path_from_id() tex_property, tex_index = get_tex_prop_from_path(material, prop_path) if tex_property.autoprop: @@ -2049,6 +2082,7 @@ def toggle_auto_prop(self, context: bpy.types.Context): set_texture_settings_node(material) + class TextureFieldProperty(bpy.types.PropertyGroup): clamp: bpy.props.BoolProperty(name="Clamp", update=update_tex_field_prop) mirror: bpy.props.BoolProperty(name="Mirror", update=update_tex_field_prop) @@ -2057,6 +2091,7 @@ class TextureFieldProperty(bpy.types.PropertyGroup): mask: bpy.props.IntProperty(name="Mask", min=0, max=15, default=5, update=update_tex_field_prop) shift: bpy.props.IntProperty(name="Shift", min=-5, max=10, update=update_tex_field_prop) + class SetTileSizeScrollProperty(bpy.types.PropertyGroup): s: bpy.props.IntProperty(min=-4095, max=4095, default=0) t: bpy.props.IntProperty(min=-4095, max=4095, default=0) @@ -2083,7 +2118,7 @@ class TextureProperty(bpy.types.PropertyGroup): tex_set: bpy.props.BoolProperty(default=True, update=update_node_values_with_preset) autoprop: bpy.props.BoolProperty(name="Autoprop", update=toggle_auto_prop, default=True) tile_scroll: bpy.props.PointerProperty(type=SetTileSizeScrollProperty) - + def get_tex_size(self) -> list[int]: if self.tex or self.use_tex_reference: if self.tex is not None: @@ -2100,6 +2135,7 @@ def on_tex_autoprop(texProperty, context): setAutoProp(texProperty.S, tex_size[0]) setAutoProp(texProperty.T, tex_size[1]) + def update_combiner_connections_and_preset(self, context: bpy.types.Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: @@ -2108,25 +2144,42 @@ def update_combiner_connections_and_preset(self, context: bpy.types.Context): material.f3d_mat.presetName = "Custom" prop_path = self.path_from_id() - combiner = 1 if 'combiner1' in prop_path else 2 + combiner = 1 if "combiner1" in prop_path else 2 update_combiner_connections(material, context, combiner=combiner) + class CombinerProperty(bpy.types.PropertyGroup): A: bpy.props.EnumProperty( - name="A", description="A", items=combiner_enums["Case A"], default="TEXEL0", update=update_combiner_connections_and_preset + name="A", + description="A", + items=combiner_enums["Case A"], + default="TEXEL0", + update=update_combiner_connections_and_preset, ) B: bpy.props.EnumProperty( - name="B", description="B", items=combiner_enums["Case B"], default="0", update=update_combiner_connections_and_preset + name="B", + description="B", + items=combiner_enums["Case B"], + default="0", + update=update_combiner_connections_and_preset, ) C: bpy.props.EnumProperty( - name="C", description="C", items=combiner_enums["Case C"], default="SHADE", update=update_combiner_connections_and_preset + name="C", + description="C", + items=combiner_enums["Case C"], + default="SHADE", + update=update_combiner_connections_and_preset, ) D: bpy.props.EnumProperty( - name="D", description="D", items=combiner_enums["Case D"], default="0", update=update_combiner_connections_and_preset + name="D", + description="D", + items=combiner_enums["Case D"], + default="0", + update=update_combiner_connections_and_preset, ) A_alpha: bpy.props.EnumProperty( @@ -2241,7 +2294,10 @@ class RDPSettings(bpy.types.PropertyGroup): name="Texture Detail", items=enumTextDetail, default="G_TD_CLAMP", update=update_node_values_with_preset ) g_mdsft_textpersp: bpy.props.EnumProperty( - name="Texture Perspective Correction", items=enumTextPersp, default="G_TP_PERSP", update=update_node_values_with_preset + name="Texture Perspective Correction", + items=enumTextPersp, + default="G_TP_PERSP", + update=update_node_values_with_preset, ) g_mdsft_cycletype: bpy.props.EnumProperty( name="Cycle Type", items=enumCycleType, default="G_CYC_1CYCLE", update=update_node_values_with_preset @@ -2291,7 +2347,9 @@ class RDPSettings(bpy.types.PropertyGroup): z_upd: bpy.props.BoolProperty(update=update_node_values_with_preset) im_rd: bpy.props.BoolProperty(update=update_node_values_with_preset) clr_on_cvg: bpy.props.BoolProperty(update=update_node_values_with_preset) - cvg_dst: bpy.props.EnumProperty(name="Coverage Destination", items=enumCoverage, update=update_node_values_with_preset) + cvg_dst: bpy.props.EnumProperty( + name="Coverage Destination", items=enumCoverage, update=update_node_values_with_preset + ) zmode: bpy.props.EnumProperty(name="Z Mode", items=enumZMode, update=update_node_values_with_preset) cvg_x_alpha: bpy.props.BoolProperty(update=update_node_values_with_preset) alpha_cvg_sel: bpy.props.BoolProperty(update=update_node_values_with_preset) @@ -2655,6 +2713,7 @@ class AddPresetF3D(AddPresetBase, Operator): return {"FINISHED"} + def convertToNewMat(material, oldMat): # mat_register_old() material.f3d_mat.presetName = oldMat.get("presetName", "Custom") @@ -2746,6 +2805,7 @@ def convertToNewMat(material, oldMat): material.f3d_mat.menu_lower_render = oldMat.get("menu_lower_render", material.f3d_mat.menu_lower_render) recursiveCopyOldPropertyGroup(oldMat["rdp_settings"], material.f3d_mat.rdp_settings) + class F3DMaterialProperty(bpy.types.PropertyGroup): presetName: bpy.props.StringProperty(name="Preset Name", default="Custom") @@ -2810,7 +2870,9 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): ) # Chroma - key_scale: bpy.props.FloatVectorProperty(name="Key Scale", min=0, max=1, step=1, update=update_node_values_with_preset) + key_scale: bpy.props.FloatVectorProperty( + name="Key Scale", min=0, max=1, step=1, update=update_node_values_with_preset + ) key_width: bpy.props.FloatVectorProperty(name="Key Width", min=0, max=16, update=update_node_values_with_preset) # Convert @@ -2822,8 +2884,12 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): k5: bpy.props.FloatProperty(min=-1, max=1, default=42 / 255, step=1, update=update_node_values_with_preset) # Prim - prim_lod_frac: bpy.props.FloatProperty(name="Prim LOD Frac", min=0, max=1, step=1, update=update_node_values_with_preset) - prim_lod_min: bpy.props.FloatProperty(name="Min LOD Ratio", min=0, max=1, step=1, update=update_node_values_with_preset) + prim_lod_frac: bpy.props.FloatProperty( + name="Prim LOD Frac", min=0, max=1, step=1, update=update_node_values_with_preset + ) + prim_lod_min: bpy.props.FloatProperty( + name="Min LOD Ratio", min=0, max=1, step=1, update=update_node_values_with_preset + ) # lights default_light_color: bpy.props.FloatVectorProperty( @@ -2835,7 +2901,9 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): default=(1, 1, 1, 1), update=update_light_properties, ) - set_ambient_from_light: bpy.props.BoolProperty("Automatic Ambient Color", default=True, update=update_light_properties) + set_ambient_from_light: bpy.props.BoolProperty( + "Automatic Ambient Color", default=True, update=update_light_properties + ) ambient_light_color: bpy.props.FloatVectorProperty( name="Ambient Light Color", subtype="COLOR", @@ -2855,10 +2923,18 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): # Fog Properties fog_color: bpy.props.FloatVectorProperty( - name="Fog Color", subtype="COLOR", size=4, min=0, max=1, default=(0, 0, 0, 1), update=update_node_values_without_preset + name="Fog Color", + subtype="COLOR", + size=4, + min=0, + max=1, + default=(0, 0, 0, 1), + update=update_node_values_without_preset, ) # TODO: (V5) dragorn421 should ask me if this is _actually_ the fog position max because this seems wrong to him - fog_position: bpy.props.IntVectorProperty(name="Fog Range", size=2, min=0, max=0x10000, default=(985, 1000), update=update_node_values_without_preset) + fog_position: bpy.props.IntVectorProperty( + name="Fog Range", size=2, min=0, max=0x10000, default=(985, 1000), update=update_node_values_without_preset + ) set_fog: bpy.props.BoolProperty(update=update_node_values_without_preset) use_global_fog: bpy.props.BoolProperty(default=False, update=update_node_values_without_preset) @@ -2924,6 +3000,7 @@ class UpdateF3DNodes(bpy.types.Operator): material.f3d_update_flag = False return {"FINISHED"} # must return a set + class F3DRenderSettingsPanel(bpy.types.Panel): bl_label = "F3D Render Settings" bl_idname = "OBJECT_PT_F3D_RENDER_SETTINGS_PANEL" @@ -2935,41 +3012,41 @@ class F3DRenderSettingsPanel(bpy.types.Panel): @classmethod def poll(cls, context): return True - + def draw(self, context): layout = self.layout layout.ui_units_x = 16 renderSettings = context.scene.fast64.renderSettings - + globalSettingsBox = layout.box() # globalSettingsBox.emboss = "RADIAL_MENU" labelbox = globalSettingsBox.box() labelbox.label(text="Global Settings") labelbox.ui_units_x = 6 - + globalSettingsBox.prop(renderSettings, "enableFogPreview") - prop_split(globalSettingsBox, renderSettings, 'fogPreviewColor', "Fog Color") - prop_split(globalSettingsBox, renderSettings, 'fogPreviewPosition', "Fog Position") - prop_split(globalSettingsBox, renderSettings, 'clippingPlanes', "Clipping Planes") + prop_split(globalSettingsBox, renderSettings, "fogPreviewColor", "Fog Color") + prop_split(globalSettingsBox, renderSettings, "fogPreviewPosition", "Fog Position") + prop_split(globalSettingsBox, renderSettings, "clippingPlanes", "Clipping Planes") globalSettingsBox.separator(factor=0.125) # TODO: (v5) add headings - prop_split(globalSettingsBox, renderSettings, 'ambientColor', "Ambient Light") - prop_split(globalSettingsBox, renderSettings, 'lightColor', "Light Color") - prop_split(globalSettingsBox, renderSettings, 'lightDirection', "Light Direction") - prop_split(globalSettingsBox, renderSettings, 'useWorldSpaceLighting', "Use World Space Lighting") - + prop_split(globalSettingsBox, renderSettings, "ambientColor", "Ambient Light") + prop_split(globalSettingsBox, renderSettings, "lightColor", "Light Color") + prop_split(globalSettingsBox, renderSettings, "lightDirection", "Light Direction") + prop_split(globalSettingsBox, renderSettings, "useWorldSpaceLighting", "Use World Space Lighting") + if context.scene.gameEditorMode in ["SM64", "OOT"]: layout.separator(factor=0.5) gameSettingsBox = layout.box() gameSettingsBox.label(text="Preview Context") - + match context.scene.gameEditorMode: case "SM64": if renderSettings.sm64Area is not None: - gameSettingsBox.prop(renderSettings, 'useObjectRenderPreview', text="Use Area for Preview") + gameSettingsBox.prop(renderSettings, "useObjectRenderPreview", text="Use Area for Preview") - gameSettingsBox.prop(renderSettings, 'sm64Area') + gameSettingsBox.prop(renderSettings, "sm64Area") case "OOT": # TODO: OOT scene preview options @@ -2986,6 +3063,7 @@ def draw_f3d_render_settings(self, context): layout: bpy.types.UILayout = self.layout layout.popover(F3DRenderSettingsPanel.bl_idname) + mat_classes = ( UnlinkF3DImage0, UnlinkF3DImage1, diff --git a/fast64_internal/f3d/f3d_material_helpers.py b/fast64_internal/f3d/f3d_material_helpers.py index 360c42d..66c0b79 100644 --- a/fast64_internal/f3d/f3d_material_helpers.py +++ b/fast64_internal/f3d/f3d_material_helpers.py @@ -1,5 +1,6 @@ import bpy + class F3DMaterial_UpdateLock: material: bpy.types.Material = None @@ -8,28 +9,28 @@ class F3DMaterial_UpdateLock: if self.mat_is_locked(): # Disallow access to locked materials self.material = None - + def __enter__(self): if self.mat_is_locked(): return None self.lock_material() return self.material - + def __exit__(self, exc_type, exc_value, traceback): self.unlock_material() if exc_value: print("\nExecution type:", exc_type) print("\nExecution value:", exc_value) print("\nTraceback:", traceback) - + def mat_is_locked(self): - return getattr(self.material, 'f3d_update_flag', True) or not getattr(self.material, 'is_f3d', False) - + return getattr(self.material, "f3d_update_flag", True) or not getattr(self.material, "is_f3d", False) + def lock_material(self): - if hasattr(self.material, 'f3d_update_flag'): + if hasattr(self.material, "f3d_update_flag"): self.material.f3d_update_flag = True - + def unlock_material(self): - if hasattr(self.material, 'f3d_update_flag'): + if hasattr(self.material, "f3d_update_flag"): self.material.f3d_update_flag = False diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 4b4ffe3..e52edc6 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -20,13 +20,15 @@ from .f3d_gbi import _DPLoadTextureBlock from ..utility import * -def getColorLayer(mesh: bpy.types.Mesh, layer = "Col"): + +def getColorLayer(mesh: bpy.types.Mesh, layer="Col"): if layer in mesh.attributes and getattr(mesh.attributes[layer], "data", None): return mesh.attributes[layer].data if layer in mesh.vertex_colors: return mesh.vertex_colors[layer].data return None + def getEdgeToFaceDict(mesh): edgeDict = {} for face in mesh.loop_triangles: @@ -234,12 +236,7 @@ class TileLoad: # 1024 wraps around to 0 # -1 is because the high value is (max value - 1) # ex. 32 pixel width -> high = 31 - return int( - min( - math.ceil(value), - min(self.texDimensions[field], 1024) - ) - 1 - ) + return int(min(math.ceil(value), min(self.texDimensions[field], 1024)) - 1) def tryAppend(self, other): return self.appendTile(other.sl, other.sh, other.tl, other.th) @@ -955,7 +952,7 @@ class TriangleConverter: self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, - tex_scale=self.tex_scale + tex_scale=self.tex_scale, ) ) @@ -989,7 +986,7 @@ class TriangleConverter: self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, - tex_scale=self.tex_scale + tex_scale=self.tex_scale, ) ) @@ -1183,7 +1180,15 @@ def UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled): def convertVertexData( - mesh, loopPos, loopUV, loopColorOrNormal, texDimensions, transformMatrix, isPointSampled, exportVertexColors, tex_scale=(1, 1) + mesh, + loopPos, + loopUV, + loopColorOrNormal, + texDimensions, + transformMatrix, + isPointSampled, + exportVertexColors, + tex_scale=(1, 1), ): # Position (8 bytes) position = [int(round(floatValue)) for floatValue in (transformMatrix @ loopPos)] @@ -1193,7 +1198,7 @@ def convertVertexData( # However, Point samples from the corner. # Thus we add 0.5 to the UV only if bilinear filtering. # see section 13.7.5.3 in programming manual. - pixelOffset = (0, 0) if isPointSampled else (0.5/tex_scale[0], 0.5/tex_scale[1]) + pixelOffset = (0, 0) if isPointSampled else (0.5 / tex_scale[0], 0.5 / tex_scale[1]) uv = [ convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset[0]), @@ -1215,10 +1220,11 @@ def convertVertexData( return Vtx(position, uv, colorOrNormal) + @functools.lru_cache(0) def is3_2_or_above(): return bpy.app.version[0] >= 3 and bpy.app.version[1] >= 2 - + def getLoopColor(loop: bpy.types.MeshLoop, mesh, mat_ver): @@ -1530,11 +1536,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): if useDict["Primitive"] and f3dMat.set_prim: color = exportColor(f3dMat.prim_color[0:3]) + [scaleToU8(f3dMat.prim_color[3])] fMaterial.material.commands.append( - DPSetPrimColor( - scaleToU8(f3dMat.prim_lod_min), - scaleToU8(f3dMat.prim_lod_frac), - *color - ) + DPSetPrimColor(scaleToU8(f3dMat.prim_lod_min), scaleToU8(f3dMat.prim_lod_frac), *color) ) if useDict["Environment"] and f3dMat.set_env: @@ -2146,41 +2148,21 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image: bpy.types.Image, imageN ( ( ( - ( - int( - round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F) - ) - & 0x1F - ) + (int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3 ) | ( - ( - int( - round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F) - ) - & 0x1F - ) + (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) >> 2 ) ), ( ( - ( - int( - round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F) - ) - & 0x03 - ) + (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6 ) | ( - ( - int( - round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F) - ) - & 0x1F - ) + (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) << 1 ) | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) @@ -2278,7 +2260,12 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image: bpy.types.Image, imageN int( round( colorToLuminance( - pixels[(j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels + 3] + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] ) * 0xFF ) @@ -2351,6 +2338,7 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image: bpy.types.Image, imageN return fImage + def saveLightsDefinition(fModel, fMaterial, material, lightsName): lights = fModel.getLightAndHandleShared(lightsName) if lights is not None: @@ -2395,7 +2383,8 @@ def addLightDefinition(mat, f3d_light, fLights): def scaleToU8(val): - return min(int(round(val*0xFF)), 255) + return min(int(round(val * 0xFF)), 255) + def exportColor(lightColor): return [scaleToU8(value) for value in gammaCorrect(lightColor)] diff --git a/fast64_internal/oot/__init__.py b/fast64_internal/oot/__init__.py index a5177da..bf8cf34 100644 --- a/fast64_internal/oot/__init__.py +++ b/fast64_internal/oot/__init__.py @@ -86,7 +86,9 @@ def oot_register(registerPanels): if registerPanels: oot_panel_register() - bpy.types.Scene.ootBlenderScale = bpy.props.FloatProperty(name="Blender To OOT Scale", default=10, update=on_update_render_settings) + bpy.types.Scene.ootBlenderScale = bpy.props.FloatProperty( + name="Blender To OOT Scale", default=10, update=on_update_render_settings + ) bpy.types.Scene.ootActorBlenderScale = bpy.props.FloatProperty(name="Blender To OOT Actor Scale", default=1000) bpy.types.Scene.ootDecompPath = bpy.props.StringProperty(name="Decomp Folder", subtype="FILE_PATH") diff --git a/fast64_internal/render_settings.py b/fast64_internal/render_settings.py index 4979e1d..9212682 100644 --- a/fast64_internal/render_settings.py +++ b/fast64_internal/render_settings.py @@ -2,6 +2,7 @@ import bpy import mathutils from .utility import get_blender_to_game_scale, transform_mtx_blender_to_n64 + def on_update_sm64_render_settings(self, context: bpy.types.Context): renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings if renderSettings.sm64Area and renderSettings.useObjectRenderPreview: @@ -11,54 +12,64 @@ def on_update_sm64_render_settings(self, context: bpy.types.Context): renderSettings.clippingPlanes = tuple(float(p) for p in area.clipPlanes) + def on_update_oot_render_settings(self, context: bpy.types.Context): # TODO: Update render properties from selected OOTLightProperty pass + def update_lighting_space(renderSettings: "Fast64RenderSettings_Properties"): if renderSettings.useWorldSpaceLighting: - bpy.data.node_groups['ShdCol_L'].nodes['GeometryNormal'].node_tree = bpy.data.node_groups['GeometryNormal_WorldSpace'] + bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ + "GeometryNormal_WorldSpace" + ] else: - bpy.data.node_groups['ShdCol_L'].nodes['GeometryNormal'].node_tree = bpy.data.node_groups['GeometryNormal_ViewSpace'] + bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ + "GeometryNormal_ViewSpace" + ] -def update_scene_props_from_render_settings(context: bpy.types.Context, sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties"): + +def update_scene_props_from_render_settings( + context: bpy.types.Context, + sceneOutputs: bpy.types.NodeGroupOutput, + renderSettings: "Fast64RenderSettings_Properties", +): enableFog = int(renderSettings.enableFogPreview) - sceneOutputs.inputs['FogEnable'].default_value = enableFog + sceneOutputs.inputs["FogEnable"].default_value = enableFog - sceneOutputs.inputs['FogColor'].default_value = tuple(c for c in renderSettings.fogPreviewColor) - sceneOutputs.inputs['FogNear'].default_value = renderSettings.fogPreviewPosition[0] - sceneOutputs.inputs['FogFar'].default_value = renderSettings.fogPreviewPosition[1] + sceneOutputs.inputs["FogColor"].default_value = tuple(c for c in renderSettings.fogPreviewColor) + sceneOutputs.inputs["FogNear"].default_value = renderSettings.fogPreviewPosition[0] + sceneOutputs.inputs["FogFar"].default_value = renderSettings.fogPreviewPosition[1] - sceneOutputs.inputs['F3D_NearClip'].default_value = float(renderSettings.clippingPlanes[0]) - sceneOutputs.inputs['F3D_FarClip'].default_value = float(renderSettings.clippingPlanes[1]) + sceneOutputs.inputs["F3D_NearClip"].default_value = float(renderSettings.clippingPlanes[0]) + sceneOutputs.inputs["F3D_FarClip"].default_value = float(renderSettings.clippingPlanes[1]) - sceneOutputs.inputs['ShadeColor'].default_value = tuple(c for c in renderSettings.lightColor) - sceneOutputs.inputs['AmbientColor'].default_value = tuple(c for c in renderSettings.ambientColor) - sceneOutputs.inputs['LightDirection'].default_value = tuple( - d for d in ( - mathutils.Vector(renderSettings.lightDirection) @ transform_mtx_blender_to_n64() - ) + sceneOutputs.inputs["ShadeColor"].default_value = tuple(c for c in renderSettings.lightColor) + sceneOutputs.inputs["AmbientColor"].default_value = tuple(c for c in renderSettings.ambientColor) + sceneOutputs.inputs["LightDirection"].default_value = tuple( + d for d in (mathutils.Vector(renderSettings.lightDirection) @ transform_mtx_blender_to_n64()) ) update_lighting_space(renderSettings) - sceneOutputs.inputs['Blender_Game_Scale'].default_value = float(get_blender_to_game_scale(context)) + sceneOutputs.inputs["Blender_Game_Scale"].default_value = float(get_blender_to_game_scale(context)) def on_update_render_preview_nodes(self, context: bpy.types.Context): sceneProps = bpy.data.node_groups.get("SceneProperties") if sceneProps == None: - print('Could not locate SceneProperties!') + print("Could not locate SceneProperties!") return - sceneOutputs: bpy.types.NodeGroupOutput = sceneProps.nodes['Group Output'] + sceneOutputs: bpy.types.NodeGroupOutput = sceneProps.nodes["Group Output"] renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings update_scene_props_from_render_settings(context, sceneOutputs, renderSettings) + def on_update_render_settings(self, context: bpy.types.Context): sceneProps = bpy.data.node_groups.get("SceneProperties") if sceneProps == None: - print('Could not locate sceneProps!') + print("Could not locate sceneProps!") return match context.scene.gameEditorMode: @@ -75,11 +86,13 @@ def on_update_render_settings(self, context: bpy.types.Context): def poll_sm64_area(self, object): return object.sm64_obj_type == "Area Root" + def poll_oot_scene(self, object): return object.ootEmptyType == "Scene" + def resync_scene_props(): - if 'ShdCol_L' in bpy.data.node_groups and 'GeometryNormal_WorldSpace' in bpy.data.node_groups: + if "ShdCol_L" in bpy.data.node_groups and "GeometryNormal_WorldSpace" in bpy.data.node_groups: renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings # Lighting space needs to be updated due to the nodes being shared and reloaded update_lighting_space(renderSettings) @@ -94,7 +107,7 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=0, max=1, default=(1, 1, 1, 1), - update=on_update_render_preview_nodes + update=on_update_render_preview_nodes, ) ambientColor: bpy.props.FloatVectorProperty( name="Ambient Light", @@ -103,7 +116,7 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=0, max=1, default=(0.5, 0.5, 0.5, 1), - update=on_update_render_preview_nodes + update=on_update_render_preview_nodes, ) lightColor: bpy.props.FloatVectorProperty( name="Light Color", @@ -112,7 +125,7 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=0, max=1, default=(1, 1, 1, 1), - update=on_update_render_preview_nodes + update=on_update_render_preview_nodes, ) lightDirection: bpy.props.FloatVectorProperty( name="Light Direction", @@ -120,16 +133,28 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): size=3, min=-1, max=1, - default=mathutils.Vector((0.5, 0.5, 1)).normalized(), # pre normalized - update=on_update_render_preview_nodes + default=mathutils.Vector((0.5, 0.5, 1)).normalized(), # pre normalized + update=on_update_render_preview_nodes, + ) + useWorldSpaceLighting: bpy.props.BoolProperty( + name="Use World Space Lighting", default=True, update=on_update_render_settings ) - useWorldSpaceLighting: bpy.props.BoolProperty(name="Use World Space Lighting", default=True, update=on_update_render_settings) # Fog Preview is int because values reflect F3D values - fogPreviewPosition: bpy.props.IntVectorProperty(name="Fog Position", size=2, min=0, max=0x7FFFFFFF, default=(985, 1000), update=on_update_render_preview_nodes) + fogPreviewPosition: bpy.props.IntVectorProperty( + name="Fog Position", size=2, min=0, max=0x7FFFFFFF, default=(985, 1000), update=on_update_render_preview_nodes + ) # Clipping planes are float because values reflect F3D values - clippingPlanes: bpy.props.FloatVectorProperty(name="Clipping Planes", size=2, min=0, default=(100, 30000), update=on_update_render_preview_nodes) - useObjectRenderPreview: bpy.props.BoolProperty(name="Use Object Preview", default=True, update=on_update_render_settings) + clippingPlanes: bpy.props.FloatVectorProperty( + name="Clipping Planes", size=2, min=0, default=(100, 30000), update=on_update_render_preview_nodes + ) + useObjectRenderPreview: bpy.props.BoolProperty( + name="Use Object Preview", default=True, update=on_update_render_settings + ) # SM64 - sm64Area: bpy.props.PointerProperty(name="Area Object", type=bpy.types.Object, update=on_update_sm64_render_settings, poll=poll_sm64_area) + sm64Area: bpy.props.PointerProperty( + name="Area Object", type=bpy.types.Object, update=on_update_sm64_render_settings, poll=poll_sm64_area + ) # OOT - ootSceneObject: bpy.props.PointerProperty(name="Scene Object", type=bpy.types.Object, update=on_update_oot_render_settings, poll=poll_oot_scene) + ootSceneObject: bpy.props.PointerProperty( + name="Scene Object", type=bpy.types.Object, update=on_update_oot_render_settings, poll=poll_oot_scene + ) diff --git a/fast64_internal/sm64/__init__.py b/fast64_internal/sm64/__init__.py index c9f12fb..ec7ab79 100644 --- a/fast64_internal/sm64/__init__.py +++ b/fast64_internal/sm64/__init__.py @@ -252,7 +252,9 @@ def sm64_register(registerPanels): bpy.types.Scene.refreshVer = bpy.props.EnumProperty(items=enumRefreshVer, name="Refresh", default="Refresh 13") bpy.types.Scene.disableScroll = bpy.props.BoolProperty(name="Disable Scrolling Textures") - bpy.types.Scene.blenderToSM64Scale = bpy.props.FloatProperty(name="Blender To SM64 Scale", default=100, update=on_update_render_settings) + bpy.types.Scene.blenderToSM64Scale = bpy.props.FloatProperty( + name="Blender To SM64 Scale", default=100, update=on_update_render_settings + ) bpy.types.Scene.decompPath = bpy.props.StringProperty(name="Decomp Folder", subtype="FILE_PATH") bpy.types.Scene.compressionFormat = bpy.props.EnumProperty( diff --git a/fast64_internal/sm64/sm64_objects.py b/fast64_internal/sm64/sm64_objects.py index 4429b82..13a9f02 100644 --- a/fast64_internal/sm64/sm64_objects.py +++ b/fast64_internal/sm64/sm64_objects.py @@ -1135,10 +1135,10 @@ class SM64ObjectPanel(bpy.types.Panel): for i in range(1, 5): row = column.row() row.prop(game_object, f"bparam{i}", text=f"Param {i}") - individuals.separator(factor=.25) + individuals.separator(factor=0.25) individuals.label(text=f"Result: {game_object.get_combined_bparams()}") else: - box.separator(factor=.5) + box.separator(factor=0.5) box.label(text="All Behavior Parameters") box.prop(game_object, "bparams", text="") parent_box.separator() @@ -1844,6 +1844,7 @@ def sm64_obj_panel_unregister(): for cls in sm64_obj_panel_classes: unregister_class(cls) + def sm64_on_update_area_render_settings(self: bpy.types.Object, context: bpy.types.Context): renderSettings = context.scene.fast64.renderSettings if renderSettings.useObjectRenderPreview and renderSettings.sm64Area == self: @@ -1852,7 +1853,7 @@ def sm64_on_update_area_render_settings(self: bpy.types.Object, context: bpy.typ renderSettings.fogPreviewPosition = tuple(round(p) for p in area.area_fog_position) renderSettings.clippingPlanes = tuple(float(p) for p in area.clipPlanes) - + def sm64_obj_register(): for cls in sm64_obj_classes: @@ -1928,14 +1929,27 @@ def sm64_obj_register(): bpy.types.Object.useDefaultScreenRect = bpy.props.BoolProperty(name="Use Default Screen Rect", default=True) - bpy.types.Object.clipPlanes = bpy.props.IntVectorProperty(name="Clip Planes", size=2, min=0, default=(100, 30000), update=sm64_on_update_area_render_settings) + bpy.types.Object.clipPlanes = bpy.props.IntVectorProperty( + name="Clip Planes", size=2, min=0, default=(100, 30000), update=sm64_on_update_area_render_settings + ) bpy.types.Object.area_fog_color = bpy.props.FloatVectorProperty( - name="Area Fog Color", subtype="COLOR", size=4, min=0, max=1, default=(0, 0, 0, 1), update=sm64_on_update_area_render_settings + name="Area Fog Color", + subtype="COLOR", + size=4, + min=0, + max=1, + default=(0, 0, 0, 1), + update=sm64_on_update_area_render_settings, ) bpy.types.Object.area_fog_position = bpy.props.FloatVectorProperty( - name="Area Fog Position", size=2, min=0, max=0x7FFFFFFF, default=(985, 1000), update=sm64_on_update_area_render_settings + name="Area Fog Position", + size=2, + min=0, + max=0x7FFFFFFF, + default=(985, 1000), + update=sm64_on_update_area_render_settings, ) bpy.types.Object.areaOverrideBG = bpy.props.BoolProperty(name="Override Background") diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index d8b2830..f03ce63 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -110,11 +110,13 @@ def checkObjectReference(obj, title): title + " not in current view layer.\n The object is either in a different view layer or is deleted." ) + def selectSingleObject(obj: bpy.types.Object): bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) bpy.context.view_layer.objects.active = obj + def parentObject(parent, child): bpy.ops.object.select_all(action="DESELECT") @@ -194,12 +196,12 @@ def copyPropertyGroup(oldProp, newProp): def get_attr_or_property(prop: dict | object, attr: str, newProp: dict | object): """Safely get an attribute or old dict property""" val = getattr(prop, attr, prop.get(attr)) - + # might be a dead enum that needs to be mapped back if type(val) is int: try: newPropDef: bpy.types.Property = newProp.bl_rna.properties[attr] - if 'Enum' in newPropDef.bl_rna.name: # Should be "Enum Definition" + if "Enum" in newPropDef.bl_rna.name: # Should be "Enum Definition" # change type hint to proper type newPropDef: bpy.types.EnumProperty = newPropDef return newPropDef.enum_items[val].identifier @@ -227,9 +229,9 @@ def recursiveCopyOldPropertyGroup(oldProp, newProp): continue sub_value = get_attr_or_property(oldProp, sub_value_attr, newProp) - if ( - isinstance(sub_value, bpy.types.PropertyGroup) - or type(sub_value).__name__ in ("bpy_prop_collection_idprop", "IDPropertyGroup") + if isinstance(sub_value, bpy.types.PropertyGroup) or type(sub_value).__name__ in ( + "bpy_prop_collection_idprop", + "IDPropertyGroup", ): newCollection = getattr(newProp, sub_value_attr) recursiveCopyOldPropertyGroup(sub_value, newCollection) @@ -505,12 +507,16 @@ def getRGBA16Tuple(color): | (1 if color[3] > 0.5 else 0) ) + RGB_TO_LUM_COEF = mathutils.Vector([0.2126729, 0.7151522, 0.0721750]) + + def colorToLuminance(color: mathutils.Color | list[float] | Vector): # https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/intern/cycles/render/shader.cpp#L387 # These coefficients are used by Blender, so we use them as well for parity between Fast64 exports and Blender color conversions return RGB_TO_LUM_COEF.dot(color[:3]) + def getIA16Tuple(color): intensity = colorToLuminance(color[0:3]) alpha = color[3] @@ -1139,17 +1145,21 @@ def getNameFromPath(path, removeExtension=False): def gammaCorrect(linearColor): return list(c for c in mathutils.Color(linearColor[:3]).from_scene_linear_to_srgb()) + def gammaCorrectValue(linearValue): # doesn't need to use `colorToLuminance` since all values are the same return mathutils.Color((linearValue, linearValue, linearValue)).from_scene_linear_to_srgb().v + def gammaInverse(sRGBColor): return list(c for c in mathutils.Color(sRGBColor[:3]).from_srgb_to_scene_linear()) + def gammaInverseValue(sRGBValue): # doesn't need to use `colorToLuminance` since all values are the same return mathutils.Color((sRGBValue, sRGBValue, sRGBValue)).from_srgb_to_scene_linear().v + def printBlenderMessage(msgSet, message, blenderOp): if blenderOp is not None: blenderOp.report(msgSet, message) @@ -1366,6 +1376,7 @@ def rotate_quat_blender_to_n64(rotation: mathutils.Quaternion): def all_values_equal_x(vals: Iterable, test): return len(set(vals) - set([test])) == 0 + def get_blender_to_game_scale(context): match context.scene.gameEditorMode: case "SM64": @@ -1383,7 +1394,7 @@ def get_blender_to_game_scale(context): def get_material_from_context(context: bpy.types.Context): """Safely check if the context has a valid material and return it""" try: - if type(getattr(context, 'material', None)) == bpy.types.Material: + if type(getattr(context, "material", None)) == bpy.types.Material: return context.material return context.material_slot.material except: