diff --git a/LowPolySkinnedMario.blend b/LowPolySkinnedMario.blend deleted file mode 100644 index f000d488c..000000000 Binary files a/LowPolySkinnedMario.blend and /dev/null differ diff --git a/LowPolySkinnedMario_V5.blend b/LowPolySkinnedMario_V5.blend deleted file mode 100644 index 53dcda582..000000000 Binary files a/LowPolySkinnedMario_V5.blend and /dev/null differ diff --git a/README.md b/README.md index 30fc20dc1..3cb45df09 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Make sure to save often, as this plugin is prone to crashing when creating mater +### Example models can be found [here](https://github.com/Fast-64/fast64-models) ![alt-text](/images/mat_inspector.png) ### Credits Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing. -Thanks to InTheBeef for LowPolySkinnedMario. ### Discord Server We have a Discord server for support as well as development [here](https://discord.gg/ny7PDcN2x8). diff --git a/__init__.py b/__init__.py index 7473759da..d3e5c548c 100644 --- a/__init__.py +++ b/__init__.py @@ -49,6 +49,7 @@ from .fast64_internal.render_settings import ( Fast64RenderSettings_Properties, + ManualUpdatePreviewOperator, resync_scene_props, on_update_render_settings, ) @@ -311,6 +312,7 @@ def draw(self, context): classes = ( Fast64Settings_Properties, Fast64RenderSettings_Properties, + ManualUpdatePreviewOperator, Fast64_Properties, Fast64_BoneProperties, Fast64_ObjectProperties, diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index f4be233ed..1f0970553 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -34,7 +34,11 @@ from .f3d_gbi import get_F3D_GBI, enumTexScroll, isUcodeF3DEX1, default_draw_layers from .f3d_material_presets import * from ..utility import * -from ..render_settings import Fast64RenderSettings_Properties, update_scene_props_from_render_settings +from ..render_settings import ( + Fast64RenderSettings_Properties, + update_scene_props_from_render_settings, + ManualUpdatePreviewOperator, +) from .f3d_material_helpers import F3DMaterial_UpdateLock, node_tree_copy from bpy.app.handlers import persistent from typing import Generator, Optional, Tuple, Any, Dict, Union @@ -2425,7 +2429,7 @@ def createOrUpdateSceneProperties(): sceneOutputs: NodeGroupOutput = new_group.nodes["Group Output"] renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings - update_scene_props_from_render_settings(bpy.context, sceneOutputs, renderSettings) + update_scene_props_from_render_settings(sceneOutputs, renderSettings) def createScenePropertiesForMaterial(material: Material): @@ -4709,6 +4713,18 @@ def draw(self, context): labelbox.label(text="Global Settings") labelbox.ui_units_x = 6 + # Only show the update preview UI if the render engine is EEVEE, + # as there's no point in updating the nodes otherwise. + if context.scene.render.engine in { + "BLENDER_EEVEE", # <4.2 + "BLENDER_EEVEE_NEXT", # 4.2+ + }: + updatePreviewRow = globalSettingsBox.row() + updatePreviewRow.prop(renderSettings, "enableAutoUpdatePreview") + if not renderSettings.enableAutoUpdatePreview: + updatePreviewRow.operator(ManualUpdatePreviewOperator.bl_idname) + globalSettingsBox.separator() + globalSettingsBox.prop(renderSettings, "enableFogPreview") prop_split(globalSettingsBox, renderSettings, "fogPreviewColor", "Fog Color") prop_split(globalSettingsBox, renderSettings, "fogPreviewPosition", "Fog Position") 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.") diff --git a/fast64_internal/render_settings.py b/fast64_internal/render_settings.py index 6476f8f1f..29e5a7385 100644 --- a/fast64_internal/render_settings.py +++ b/fast64_internal/render_settings.py @@ -92,45 +92,155 @@ def interpColors(cola, colb, fade): ) -def update_lighting_space(renderSettings: "Fast64RenderSettings_Properties"): - bpy.data.node_groups["GetSpecularNormal"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ - "GeometryNormal_WorldSpace" if renderSettings.useWorldSpaceLighting else "GeometryNormal_ViewSpace" - ] +def update_scene_props_from_rs_enableFogPreview( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): + sceneOutputs.inputs["FogEnable"].default_value = int(renderSettings.enableFogPreview) -def update_scene_props_from_render_settings( - context: bpy.types.Context, - sceneOutputs: bpy.types.NodeGroupOutput, - renderSettings: "Fast64RenderSettings_Properties", +def update_scene_props_from_rs_fogPreviewColor( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" ): - sceneOutputs.inputs["FogEnable"].default_value = int(renderSettings.enableFogPreview) sceneOutputs.inputs["FogColor"].default_value = s_rgb_alpha_1_tuple(renderSettings.fogPreviewColor) + + +def update_scene_props_from_rs_clippingPlanes( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["F3D_NearClip"].default_value = float(renderSettings.clippingPlanes[0]) sceneOutputs.inputs["F3D_FarClip"].default_value = float(renderSettings.clippingPlanes[1]) - sceneOutputs.inputs["Blender_Game_Scale"].default_value = float(get_blender_to_game_scale(context)) + + +def update_scene_props_from_rs_fogPreviewPosition( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["FogNear"].default_value = renderSettings.fogPreviewPosition[0] sceneOutputs.inputs["FogFar"].default_value = renderSettings.fogPreviewPosition[1] + +def update_scene_props_from_rs_ambientColor( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["AmbientColor"].default_value = s_rgb_alpha_1_tuple(renderSettings.ambientColor) + + +def update_scene_props_from_rs_light0Color( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light0Color"].default_value = s_rgb_alpha_1_tuple(renderSettings.light0Color) + + +def update_scene_props_from_rs_light0Direction( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light0Dir"].default_value = renderSettings.light0Direction + + +def update_scene_props_from_rs_light0SpecSize( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light0Size"].default_value = renderSettings.light0SpecSize + + +def update_scene_props_from_rs_light1Color( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light1Color"].default_value = s_rgb_alpha_1_tuple(renderSettings.light1Color) + + +def update_scene_props_from_rs_light1Direction( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light1Dir"].default_value = renderSettings.light1Direction + + +def update_scene_props_from_rs_light1SpecSize( + sceneOutputs: bpy.types.NodeGroupOutput, renderSettings: "Fast64RenderSettings_Properties" +): sceneOutputs.inputs["Light1Size"].default_value = renderSettings.light1SpecSize - update_lighting_space(renderSettings) + +def update_scene_props_from_rs_useWorldSpaceLighting(renderSettings: "Fast64RenderSettings_Properties"): + bpy.data.node_groups["GetSpecularNormal"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ + "GeometryNormal_WorldSpace" if renderSettings.useWorldSpaceLighting else "GeometryNormal_ViewSpace" + ] -def on_update_render_preview_nodes(self, context: bpy.types.Context): +def update_scene_props_from_render_settings( + sceneOutputs: bpy.types.NodeGroupOutput, + renderSettings: "Fast64RenderSettings_Properties", +): + update_scene_props_from_rs_enableFogPreview(sceneOutputs, renderSettings) + update_scene_props_from_rs_fogPreviewColor(sceneOutputs, renderSettings) + update_scene_props_from_rs_clippingPlanes(sceneOutputs, renderSettings) + update_scene_props_from_rs_fogPreviewPosition(sceneOutputs, renderSettings) + update_scene_props_from_rs_ambientColor(sceneOutputs, renderSettings) + update_scene_props_from_rs_light0Color(sceneOutputs, renderSettings) + update_scene_props_from_rs_light0Direction(sceneOutputs, renderSettings) + update_scene_props_from_rs_light0SpecSize(sceneOutputs, renderSettings) + update_scene_props_from_rs_light1Color(sceneOutputs, renderSettings) + update_scene_props_from_rs_light1Direction(sceneOutputs, renderSettings) + update_scene_props_from_rs_light1SpecSize(sceneOutputs, renderSettings) + update_scene_props_from_rs_useWorldSpaceLighting(renderSettings) + + # TODO use a callback on the scale props to set this value + sceneOutputs.inputs["Blender_Game_Scale"].default_value = float(get_blender_to_game_scale(bpy.context)) + + +def getSceneOutputs(): sceneProps = bpy.data.node_groups.get("SceneProperties") if sceneProps == None: print("Could not locate SceneProperties!") - return + return None sceneOutputs: bpy.types.NodeGroupOutput = sceneProps.nodes["Group Output"] - renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings - update_scene_props_from_render_settings(context, sceneOutputs, renderSettings) + return sceneOutputs + + +class ManualUpdatePreviewOperator(bpy.types.Operator): + bl_idname = "view3d.fast64_manual_update_preview" + bl_label = "Update Preview" + bl_description = "Apply the F3D Render Settings to the view" + + def execute(self, context): + sceneOutputs = getSceneOutputs() + renderSettings = bpy.context.scene.fast64.renderSettings + + if sceneOutputs is None: + return {"CANCELLED"} + + update_scene_props_from_render_settings(sceneOutputs, renderSettings) + return {"FINISHED"} + + +def make_callback(update_scene_props_from_rs_func): + def on_update_rs_func(self: "Fast64RenderSettings_Properties", context): + if not self.enableAutoUpdatePreview: + return + sceneOutputs = getSceneOutputs() + if sceneOutputs is not None: + update_scene_props_from_rs_func(sceneOutputs, self) + + return on_update_rs_func + + +# These are all the callbacks that modify values in the scene properties node group +# Since modifying node values turns out to be very slow, +# we need one callback per prop in order to update the specific associated value. +on_update_rs_enableFogPreview = make_callback(update_scene_props_from_rs_enableFogPreview) +on_update_rs_fogPreviewColor = make_callback(update_scene_props_from_rs_fogPreviewColor) +on_update_rs_clippingPlanes = make_callback(update_scene_props_from_rs_clippingPlanes) +on_update_rs_fogPreviewPosition = make_callback(update_scene_props_from_rs_fogPreviewPosition) +on_update_rs_ambientColor = make_callback(update_scene_props_from_rs_ambientColor) +on_update_rs_light0Color = make_callback(update_scene_props_from_rs_light0Color) +on_update_rs_light0Direction = make_callback(update_scene_props_from_rs_light0Direction) +on_update_rs_light0SpecSize = make_callback(update_scene_props_from_rs_light0SpecSize) +on_update_rs_light1Color = make_callback(update_scene_props_from_rs_light1Color) +on_update_rs_light1Direction = make_callback(update_scene_props_from_rs_light1Direction) +on_update_rs_light1SpecSize = make_callback(update_scene_props_from_rs_light1SpecSize) +on_update_rs_useWorldSpaceLighting = make_callback(update_scene_props_from_rs_useWorldSpaceLighting) + +del make_callback def on_update_render_settings(self, context: bpy.types.Context): @@ -147,7 +257,9 @@ def on_update_render_settings(self, context: bpy.types.Context): case _: pass - on_update_render_preview_nodes(self, context) + sceneOutputs = getSceneOutputs() + if sceneOutputs is not None: + update_scene_props_from_render_settings(sceneOutputs, self) def poll_sm64_area(self, object): @@ -161,11 +273,27 @@ def poll_oot_scene(self, object): def resync_scene_props(): if "GetSpecularNormal" in bpy.data.node_groups: # Lighting space needs to be updated due to the nodes being shared and reloaded - update_lighting_space(bpy.context.scene.fast64.renderSettings) + update_scene_props_from_rs_useWorldSpaceLighting(bpy.context.scene.fast64.renderSettings) + + +def on_update_render_settings_enableAutoUpdatePreview(self, context): + # Update on enabling but not disabling + if self.enableAutoUpdatePreview: + on_update_render_settings(self, context) class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): - enableFogPreview: bpy.props.BoolProperty(name="Enable Fog Preview", default=True, update=on_update_render_settings) + enableAutoUpdatePreview: bpy.props.BoolProperty( + name="Auto Update Preview", + description="If enabled, the view will update automatically when changing render settings", + default=True, + update=on_update_render_settings_enableAutoUpdatePreview, + ) + enableFogPreview: bpy.props.BoolProperty( + name="Enable Fog Preview", + default=True, + update=on_update_render_settings, + ) fogPreviewColor: bpy.props.FloatVectorProperty( name="Fog Color", subtype="COLOR", @@ -173,7 +301,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_rs_fogPreviewColor, ) ambientColor: bpy.props.FloatVectorProperty( name="Ambient Light", @@ -182,7 +310,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_rs_ambientColor, ) light0Color: bpy.props.FloatVectorProperty( name="Light 0 Color", @@ -191,7 +319,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_rs_light0Color, ) light0Direction: bpy.props.FloatVectorProperty( name="Light 0 Direction", @@ -200,14 +328,14 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=-1, max=1, default=mathutils.Vector((1.0, -1.0, 1.0)).normalized(), # pre normalized - update=on_update_render_preview_nodes, + update=on_update_rs_light0Direction, ) light0SpecSize: bpy.props.IntProperty( name="Light 0 Specular Size", min=1, max=255, default=3, - update=on_update_render_preview_nodes, + update=on_update_rs_light0SpecSize, ) light1Color: bpy.props.FloatVectorProperty( name="Light 1 Color", @@ -216,7 +344,7 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=0, max=1, default=(0, 0, 0, 1), - update=on_update_render_preview_nodes, + update=on_update_rs_light1Color, ) light1Direction: bpy.props.FloatVectorProperty( name="Light 1 Direction", @@ -225,36 +353,55 @@ class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): min=-1, max=1, default=mathutils.Vector((-1.0, 1.0, -1.0)).normalized(), # pre normalized - update=on_update_render_preview_nodes, + update=on_update_rs_light1Direction, ) light1SpecSize: bpy.props.IntProperty( name="Light 1 Specular Size", min=1, max=255, default=3, - update=on_update_render_preview_nodes, + update=on_update_rs_light1SpecSize, ) useWorldSpaceLighting: bpy.props.BoolProperty( - name="Use World Space Lighting", default=True, update=on_update_render_settings + 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=1000, default=(985, 1000), update=on_update_render_preview_nodes + name="Fog Position", + size=2, + min=0, + max=1000, + default=(985, 1000), + update=on_update_rs_fogPreviewPosition, ) # 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 + name="Clipping Planes", + size=2, + min=0, + default=(100, 30000), + update=on_update_rs_clippingPlanes, ) useObjectRenderPreview: bpy.props.BoolProperty( - name="Use Object Preview", default=True, update=on_update_render_settings + 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 + 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 + name="Scene Object", + type=bpy.types.Object, + update=on_update_oot_render_settings, + poll=poll_oot_scene, ) ootSceneHeader: bpy.props.IntProperty( name="Header/Setup", diff --git a/mario.blend b/mario.blend deleted file mode 100644 index 8ba071893..000000000 Binary files a/mario.blend and /dev/null differ