Skip to content
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
73f11ad
WIP Idea
Lilaa3 Oct 13, 2024
0e0bf23
Update f3d_node_gen.py
Lilaa3 Oct 13, 2024
159c01b
use tab, remove bl id name from node inputs
Lilaa3 Apr 18, 2025
7c2e2e8
use lists for inputs and outputs, it works!!!!
Lilaa3 Apr 18, 2025
672359e
add version independence
Lilaa3 Apr 18, 2025
438c7ed
remaining bilinear bug still todo
Lilaa3 Apr 18, 2025
f21f2c6
still not fixed
Lilaa3 Apr 18, 2025
c7d7ad9
individual group files
Lilaa3 Apr 18, 2025
90f36f3
naming fixes
Lilaa3 Apr 18, 2025
91aa2fc
Update f3d_material.py
Lilaa3 Apr 18, 2025
2d89c29
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 Apr 18, 2025
7733006
make everything work, compress a bit more
Lilaa3 May 22, 2025
ae643ff
disable debug features
Lilaa3 May 22, 2025
7fabbd4
make existing code work
Lilaa3 May 22, 2025
40b3cf6
fix more things
Lilaa3 May 22, 2025
cf4ad1f
working in 4.1?
Lilaa3 May 22, 2025
d4877eb
fix shader node mix
Lilaa3 May 22, 2025
e2fad58
fix location and other stuff. missing input reorder for everything to…
Lilaa3 May 23, 2025
a020892
I think its all working now!
Lilaa3 May 23, 2025
6636482
vastly improve error handling to crush out remaining bugs
Lilaa3 May 25, 2025
da76469
match versions even more closely...
Lilaa3 May 25, 2025
b755f99
match versions even more closely...
Lilaa3 May 25, 2025
dc82583
Merge branch 'f3d_nodes_json' of github.com:Lilaa3/fast64 into f3d_no…
Lilaa3 May 25, 2025
721480f
4.1 officially works with nodes exported in 3.2
Lilaa3 May 25, 2025
0a96684
4.4.3 fixed by most disgusting line of code ever written
Lilaa3 May 25, 2025
26631eb
make updates great again
Lilaa3 May 25, 2025
0e862b4
specify encodings
Lilaa3 May 25, 2025
2d5ea6c
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 May 25, 2025
d3e2c79
auto node update fix
Lilaa3 May 25, 2025
31b0b45
Update fast64_internal/f3d/f3d_node_gen.py
Lilaa3 May 25, 2025
b538c1d
optimize defaults more
Lilaa3 May 25, 2025
afc71c7
Merge branch 'f3d_nodes_json' of github.com:Lilaa3/fast64 into f3d_no…
Lilaa3 May 25, 2025
427debd
add all useful defaults (no more savings left besides removing newlines)
Lilaa3 May 25, 2025
122846c
reposition use nodes
Lilaa3 May 25, 2025
719d46c
use format_exception
Lilaa3 May 25, 2025
aca3278
type annotations for errors
Lilaa3 May 25, 2025
30e01c6
make copy before removing
Lilaa3 May 25, 2025
a97e8ee
Merge differing scene property node group code
Lilaa3 May 25, 2025
69d3435
recreate all f3d nodes button, make material updates not forced with …
Lilaa3 May 25, 2025
6ab8f8c
add way to reconstruct node library from serialized
Lilaa3 May 25, 2025
c878c7f
remove library
Lilaa3 May 25, 2025
3e8b71b
i was using a : instead of a =
Lilaa3 May 27, 2025
7d2aa26
separate interface updates
Lilaa3 May 30, 2025
ee1b8a9
separate interface updates
Lilaa3 May 30, 2025
3cb28e2
Merge branch 'f3d_nodes_json' of github.com:Lilaa3/fast64 into f3d_no…
Lilaa3 May 30, 2025
7afc4bd
Revert "Merge branch 'f3d_nodes_json' of github.com:Lilaa3/fast64 int…
Lilaa3 May 30, 2025
8b543a2
add hash to material creation
Lilaa3 May 30, 2025
6faa916
change width default
Lilaa3 May 30, 2025
137dfc9
remove unnecessary "update"
Lilaa3 May 30, 2025
dc4260f
move update code together, improve update perf!
Lilaa3 May 30, 2025
70430ab
Improve performance with this one simple trick developers HATE
Lilaa3 May 30, 2025
e498190
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 Jun 8, 2025
d3ad8b1
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 Jul 25, 2025
125a830
Update operators.py
Lilaa3 Jul 25, 2025
b5f0e5d
sort keys for consistency
Lilaa3 Jul 25, 2025
7829ee0
4.5 support
Lilaa3 Jul 30, 2025
32ecabf
some of the sorting was missing
Lilaa3 Jul 30, 2025
f42be45
Keep track of fast64 made nodes
Lilaa3 Jul 30, 2025
96b1558
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 Aug 15, 2025
57d7e89
Merge remote-tracking branch 'upstream/main' into f3d_nodes_json
Lilaa3 Aug 15, 2025
c49e757
Merge branch 'main' into f3d_nodes_json
Lilaa3 Aug 16, 2025
b40c065
black
Lilaa3 Aug 16, 2025
8cf0da7
Merge branch 'main' into f3d_nodes_json
Lilaa3 Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@
from .fast64_internal.f3d.f3d_parser import f3d_parser_register, f3d_parser_unregister
from .fast64_internal.f3d.flipbook import flipbook_register, flipbook_unregister
from .fast64_internal.f3d.op_largetexture import op_largetexture_register, op_largetexture_unregister, ui_oplargetexture
from .fast64_internal.f3d.f3d_node_gen import f3d_node_gen_register, f3d_node_gen_unregister, generate_f3d_node_groups

from .fast64_internal.f3d_material_converter import (
MatUpdateConvert,
upgrade_f3d_version_all_meshes,
bsdf_conv_register,
bsdf_conv_unregister,
bsdf_conv_panel_regsiter,
bsdf_conv_panel_unregsiter,
upgrade_f3d_version_all_meshes,
)

from .fast64_internal.render_settings import (
Expand Down Expand Up @@ -375,7 +376,7 @@ def after_load(_a, _b):
if any(mat.is_f3d for mat in bpy.data.materials):
check_or_ask_color_management(bpy.context)
if not settings.internal_fixed_4_2 and bpy.app.version >= (4, 2, 0):
upgrade_f3d_version_all_meshes()
generate_f3d_node_groups(False, force_mat_update=True)
if bpy.app.version >= (4, 2, 0):
settings.internal_fixed_4_2 = True
upgrade_changed_props()
Expand Down Expand Up @@ -454,6 +455,7 @@ def register():
flipbook_register()
f3d_parser_register()
op_largetexture_register()
f3d_node_gen_register()

# ROM

Expand Down Expand Up @@ -485,6 +487,7 @@ def unregister():
utility_anim_unregister()
op_largetexture_unregister()
flipbook_unregister()
f3d_node_gen_unregister()
f3d_writer_unregister()
f3d_parser_unregister()
sm64_unregister(True)
Expand Down
263 changes: 34 additions & 229 deletions fast64_internal/f3d/f3d_material.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
import bpy, math, os
import bpy, os
from bpy.types import (
Attribute,
Context,
Expand All @@ -9,7 +9,6 @@
Material,
Menu,
Mesh,
NodeGroupOutput,
NodeInputs,
NodeLink,
NodeSocket,
Expand Down Expand Up @@ -42,12 +41,13 @@
)
from .f3d_material_presets import *
from ..utility import *
from ..render_settings import (
Fast64RenderSettings_Properties,
update_scene_props_from_render_settings,
ManualUpdatePreviewOperator,
from ..render_settings import ManualUpdatePreviewOperator
from .f3d_material_helpers import F3DMaterial_UpdateLock
from .f3d_node_gen import (
create_f3d_nodes_in_material,
generate_f3d_node_groups,
SHOW_GATHER_OPERATOR,
)
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

Expand Down Expand Up @@ -2302,6 +2302,8 @@ def update_tex_values_manual(material: Material, context, prop_path=None):
elif texture_settings.mute:
texture_settings.mute = False

set_texture_settings_node(material)

# linear requires tex gen to be enabled as well
isTexGen = f3dMat.rdp_settings.g_lighting and f3dMat.rdp_settings.g_tex_gen

Expand Down Expand Up @@ -2349,7 +2351,6 @@ def update_tex_values_manual(material: Material, context, prop_path=None):

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 shift_num(num: int, amt: int):
Expand Down Expand Up @@ -2435,203 +2436,14 @@ def has_f3d_nodes(material: Material):

@persistent
def load_handler(dummy):
logger.info("Checking for base F3D material library.")
for lib in bpy.data.libraries:
lib_path = bpy.path.abspath(lib.filepath)

# detect if this is one your addon's libraries here
if "f3d_material_library.blend" in lib_path:
addon_dir = os.path.dirname(os.path.abspath(__file__))
new_lib_path = os.path.join(addon_dir, "f3d_material_library.blend")

if lib_path != new_lib_path:
logger.info("Reloading the library: %s : %s => %s" % (lib.name, lib_path, new_lib_path))

lib.filepath = new_lib_path
lib.reload()
bpy.context.scene["f3d_lib_dir"] = None # force node reload!
link_f3d_material_library()
if not SHOW_GATHER_OPERATOR:
generate_f3d_node_groups(False, False)

for mat in bpy.data.materials:
if mat is not None and mat.use_nodes and mat.is_f3d:
rendermode_preset_to_advanced(mat)


bpy.app.handlers.load_post.append(load_handler)

SCENE_PROPERTIES_VERSION = 2


def createOrUpdateSceneProperties():
group = bpy.data.node_groups.get("SceneProperties")
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

if upgrade_group and group:
# Need to upgrade; remove old outputs
if bpy.app.version >= (4, 0, 0):
for item in group.interface.items_tree:
if item.item_type == "SOCKET" and item.in_out == "OUTPUT":
group.interface.remove(item)
else:
for out in group.outputs:
group.outputs.remove(out)
new_group = group
else:
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

# Create outputs
if bpy.app.version >= (4, 0, 0):
tree_interface = new_group.interface

_nodeFogEnable: NodeSocketFloat = tree_interface.new_socket(
"FogEnable", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeFogColor: NodeSocketColor = tree_interface.new_socket(
"FogColor", socket_type="NodeSocketColor", in_out="OUTPUT"
)
_nodeF3D_NearClip: NodeSocketFloat = tree_interface.new_socket(
"F3D_NearClip", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeF3D_FarClip: NodeSocketFloat = tree_interface.new_socket(
"F3D_FarClip", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeBlender_Game_Scale: NodeSocketFloat = tree_interface.new_socket(
"Blender_Game_Scale", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeFogNear: NodeSocketFloat = tree_interface.new_socket(
"FogNear", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeFogFar: NodeSocketFloat = tree_interface.new_socket(
"FogFar", socket_type="NodeSocketFloat", in_out="OUTPUT"
)

_nodeAmbientColor: NodeSocketColor = tree_interface.new_socket(
"AmbientColor", socket_type="NodeSocketColor", in_out="OUTPUT"
)
_nodeLight0Color: NodeSocketColor = tree_interface.new_socket(
"Light0Color", socket_type="NodeSocketColor", in_out="OUTPUT"
)
_nodeLight0Dir: NodeSocketVector = tree_interface.new_socket(
"Light0Dir", socket_type="NodeSocketVector", in_out="OUTPUT"
)
_nodeLight0Size: NodeSocketFloat = tree_interface.new_socket(
"Light0Size", socket_type="NodeSocketFloat", in_out="OUTPUT"
)
_nodeLight1Color: NodeSocketColor = tree_interface.new_socket(
"Light1Color", socket_type="NodeSocketColor", in_out="OUTPUT"
)
_nodeLight1Dir: NodeSocketVector = tree_interface.new_socket(
"Light1Dir", socket_type="NodeSocketVector", in_out="OUTPUT"
)
_nodeLight1Size: NodeSocketFloat = tree_interface.new_socket(
"Light1Size", socket_type="NodeSocketFloat", in_out="OUTPUT"
)

else:
_nodeFogEnable: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogEnable")
_nodeFogColor: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "FogColor")
_nodeF3D_NearClip: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_NearClip")
_nodeF3D_FarClip: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_FarClip")
_nodeBlender_Game_Scale: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "Blender_Game_Scale")
_nodeFogNear: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogNear")
_nodeFogFar: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogFar")

_nodeAmbientColor: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "AmbientColor")
_nodeLight0Color: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "Light0Color")
_nodeLight0Dir: NodeSocketVectorDirection = new_group.outputs.new("NodeSocketVectorDirection", "Light0Dir")
_nodeLight0Size: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "Light0Size")
_nodeLight1Color: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "Light1Color")
_nodeLight1Dir: NodeSocketVectorDirection = new_group.outputs.new("NodeSocketVectorDirection", "Light1Dir")
_nodeLight1Size: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "Light1Size")

# Set outputs from render settings
sceneOutputs: NodeGroupOutput = new_group.nodes["Group Output"]
renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings

update_scene_props_from_render_settings(sceneOutputs, renderSettings)


def createScenePropertiesForMaterial(material: 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"
scene_props.location = (-320, -23)
scene_props.node_tree = bpy.data.node_groups["SceneProperties"]

# Fog links to reroutes and the CalcFog block
node_tree.links.new(scene_props.outputs["FogEnable"], node_tree.nodes["FogEnable"].inputs[0])
node_tree.links.new(scene_props.outputs["FogColor"], node_tree.nodes["FogColor"].inputs[0])
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"])
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["FogNear"], node_tree.nodes["CalcFog"].inputs["FogNear"])
node_tree.links.new(scene_props.outputs["FogFar"], node_tree.nodes["CalcFog"].inputs["FogFar"])

# Lighting links to reroutes. The colors are connected to other reroutes for update_light_colors,
# the others go directly to the Shade Color block.
node_tree.links.new(scene_props.outputs["AmbientColor"], node_tree.nodes["AmbientColor"].inputs[0])
node_tree.links.new(scene_props.outputs["Light0Color"], node_tree.nodes["Light0Color"].inputs[0])
node_tree.links.new(scene_props.outputs["Light0Dir"], node_tree.nodes["Light0Dir"].inputs[0])
node_tree.links.new(scene_props.outputs["Light0Size"], node_tree.nodes["Light0Size"].inputs[0])
node_tree.links.new(scene_props.outputs["Light1Color"], node_tree.nodes["Light1Color"].inputs[0])
node_tree.links.new(scene_props.outputs["Light1Dir"], node_tree.nodes["Light1Dir"].inputs[0])
node_tree.links.new(scene_props.outputs["Light1Size"], node_tree.nodes["Light1Size"].inputs[0])


def link_f3d_material_library():
dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "f3d_material_library.blend")

prevMode = bpy.context.mode
if prevMode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")

with bpy.data.libraries.load(dir) as (data_from, data_to):
dirMat = os.path.join(dir, "Material")
dirNode = os.path.join(dir, "NodeTree")
for mat in data_from.materials:
if mat is not None:
bpy.ops.wm.link(filepath=os.path.join(dirMat, mat), directory=dirMat, filename=mat)

# 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:
# 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.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 get_f3d_node_tree() -> bpy.types.NodeTree:
try:
link_f3d_material_library()
mat = bpy.data.materials["fast64_f3d_material_library_beefwashere"]
return mat.node_tree.copy()
finally:
bpy.data.materials.remove(mat)


def shouldConvOrCreateColorAttribute(mesh: Mesh, attr_name="Col"):
has_attr, conv_attr = False, False
if attr_name in mesh.attributes:
Expand Down Expand Up @@ -2695,27 +2507,18 @@ def add_f3d_mat_to_obj(obj: bpy.types.Object, material, index=None):


def createF3DMat(obj: Object | None, preset="Shaded Solid", index=None):
# link all node_groups + material from addon's data .blend
link_f3d_material_library()

# a linked material containing the default layout for all the linked node_groups
mat = bpy.data.materials["fast64_f3d_material_library_beefwashere"]
# duplicate and rename the linked material
material = mat.copy()
material.name = "f3dlite_material"
# remove the linked material so it doesn't bother anyone or get meddled with
bpy.data.materials.remove(mat)

createScenePropertiesForMaterial(material)

add_f3d_mat_to_obj(obj, material, index)

material.is_f3d = True
material.mat_ver = F3D_MAT_CUR_VERSION

update_preset_manual_v4(material, preset)

return material
material = bpy.data.materials.new("f3d_material")
try:
generate_f3d_node_groups()
material.is_f3d = True
material.mat_ver = F3D_MAT_CUR_VERSION
create_f3d_nodes_in_material(material)
add_f3d_mat_to_obj(obj, material, index)
update_preset_manual_v4(material, preset)
return material
except Exception as exc:
bpy.data.materials.remove(material)
raise exc
Comment thread
Lilaa3 marked this conversation as resolved.


def reloadDefaultF3DPresets():
Expand Down Expand Up @@ -2809,9 +2612,8 @@ def execute(self, context):
if material is None:
self.report({"ERROR"}, "No active material.")
else:
node_tree_copy(get_f3d_node_tree(), material.node_tree)
createScenePropertiesForMaterial(material)
update_all_node_values(material, context)
generate_f3d_node_groups(False)
create_f3d_nodes_in_material(material)
self.report({"INFO"}, "Success!")
return {"FINISHED"}

Expand Down Expand Up @@ -2859,28 +2661,27 @@ def update_tex_field_prop(self: Property, context: Context):
if not material:
return

set_texture_settings_node(material)
prop_path = self.path_from_id()
tex_property, tex_index = get_tex_prop_from_path(material, prop_path)
tex_size = tex_property.get_tex_size()

if tex_size[0] > 0 and tex_size[1] > 0:
update_tex_values_field(material, tex_property, tex_size, tex_index)
set_texture_settings_node(material)


def toggle_auto_prop(self, context: Context):
with F3DMaterial_UpdateLock(get_material_from_context(context)) as material:
if not material:
return
set_texture_settings_node(material)

prop_path = self.path_from_id()
tex_property, tex_index = get_tex_prop_from_path(material, prop_path)
if tex_property.autoprop:
for i in range(2):
tex_property = getattr(material.f3d_mat, f"tex{i}")
tex_size = tuple([s for s in tex_property.get_tex_size()])
if tex_size[0] > 0 and tex_size[1] > 0:
update_tex_values_field(material, tex_property, tex_size, tex_index)

set_texture_settings_node(material)
update_tex_values_field(material, tex_property, tex_size, i)


class TextureFieldProperty(PropertyGroup):
Expand Down Expand Up @@ -5023,10 +4824,14 @@ def mat_register():
Object.is_occlusion_planes = bpy.props.BoolProperty(name="Is Occlusion Planes")

VIEW3D_HT_header.append(draw_f3d_render_settings)
bpy.app.handlers.load_post.append(load_handler)


def mat_unregister():
VIEW3D_HT_header.remove(draw_f3d_render_settings)
# not having this previously means we have to run this until all handlers are removed
while load_handler in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(load_handler)

del Material.menu_tab
del Material.f3d_mat
Expand Down
Loading