Skip to content

Commit

Permalink
[OOT + F3D] More informative error messages for deleted lights (#491)
Browse files Browse the repository at this point in the history
* [OOT + F3D] More informative error messages for deleted lights

I thought of wrapping saveOrGetF3DMaterial to avoid these kind of empty errors in the future, im fine with reverting that tho I think it is for the better (in ac I wanted to throw errors where the material wasn't in the args already and it's very annoying, this way everything is consistent)

* fix from watching somone's stream
  • Loading branch information
Lilaa3 authored Jan 8, 2025
1 parent a072427 commit 79d3bad
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 109 deletions.
73 changes: 17 additions & 56 deletions fast64_internal/f3d/f3d_texture_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def moreSetupFromModel(
self.palLen = len(self.pal)
if self.palLen > (16 if self.texFormat == "CI4" else 256):
raise PluginError(
f"Error in {material.name}: texture {self.indexInMat}"
f"Texture {self.indexInMat}"
+ (" (all flipbook textures)" if self.flipbook is not None else "")
+ f" uses too many unique colors to fit in format {self.texFormat}."
)
Expand Down Expand Up @@ -566,49 +566,34 @@ def __init__(
f3dMat = material.f3d_mat
self.ti0, self.ti1 = TexInfo(), TexInfo()
if not self.ti0.fromMat(0, f3dMat):
raise PluginError(f"In {material.name} tex0: {self.ti0.errorMsg}")
raise PluginError(f"Tex0: {self.ti0.errorMsg}")
if not self.ti1.fromMat(1, f3dMat):
raise PluginError(f"In {material.name} tex1: {self.ti1.errorMsg}")
raise PluginError(f"Tex1: {self.ti1.errorMsg}")
self.ti0.moreSetupFromModel(material, fMaterial, fModel)
self.ti1.moreSetupFromModel(material, fMaterial, fModel)

self.isCI = self.ti0.isTexCI or self.ti1.isTexCI

if self.ti0.useTex and self.ti1.useTex:
if self.ti0.isTexCI != self.ti1.isTexCI:
raise PluginError(
"In material "
+ material.name
+ ": N64 does not support CI + non-CI texture. "
+ "Must be both CI or neither CI."
)
raise PluginError("N64 does not support CI + non-CI texture. Must be both CI or neither CI.")
if (
self.ti0.isTexRef
and self.ti1.isTexRef
and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference
and self.ti0.texProp.tex_reference_size != self.ti1.texProp.tex_reference_size
):
raise PluginError(
"In material " + material.name + ": Two textures with the same reference must have the same size."
)
raise PluginError("Two textures with the same reference must have the same size.")
if self.isCI:
if self.ti0.palFormat != self.ti1.palFormat:
raise PluginError(
"In material "
+ material.name
+ ": Both CI textures must use the same palette format (usually RGBA16)."
)
raise PluginError("Both CI textures must use the same palette format (usually RGBA16).")
if (
self.ti0.isTexRef
and self.ti1.isTexRef
and self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference
and self.ti0.texProp.pal_reference_size != self.ti1.texProp.pal_reference_size
):
raise PluginError(
"In material "
+ material.name
+ ": Two textures with the same palette reference must have the same palette size."
)
raise PluginError("Two textures with the same palette reference must have the same palette size.")

self.palFormat = self.ti0.palFormat if self.ti0.useTex else self.ti1.palFormat

Expand All @@ -626,9 +611,7 @@ def writeAll(
elif not convertTextureData:
if self.ti0.texFormat == "CI8" or self.ti1.texFormat == "CI8":
raise PluginError(
"In material "
+ material.name
+ ": When using export as PNGs mode, can't have multitexture with one or more CI8 textures."
"When using export as PNGs mode, can't have multitexture with one or more CI8 textures."
+ " Only single CI texture or two CI4 textures."
)
self.ti0.loadPal = self.ti1.loadPal = True
Expand All @@ -638,27 +621,19 @@ def writeAll(
if self.ti0.texFormat == "CI8" and self.ti1.texFormat == "CI8":
if (self.ti0.pal is None) != (self.ti1.pal is None):
raise PluginError(
"In material "
+ material.name
+ ": can't have two CI8 textures where only one is a non-flipbook reference; "
"Can't have two CI8 textures where only one is a non-flipbook reference; "
+ "no way to assign the palette."
)
self.ti0.loadPal = True
if self.ti0.pal is None:
if self.ti0.texProp.pal_reference != self.ti1.texProp.pal_reference:
raise PluginError(
"In material "
+ material.name
+ ": can't have two CI8 textures with different palette references."
)
raise PluginError("Can't have two CI8 textures with different palette references.")
else:
self.ti0.pal = mergePalettes(self.ti0.pal, self.ti1.pal)
self.ti0.palLen = len(self.ti0.pal)
if self.ti0.palLen > 256:
raise PluginError(
"In material "
+ material.name
+ ": the two CI textures together contain a total of "
"The two CI textures together contain a total of "
+ str(self.ti0.palLen)
+ " colors, which can't fit in a CI8 palette (256)."
)
Expand All @@ -679,9 +654,7 @@ def writeAll(
if self.ti0.pal is None or self.ti1.pal is None:
if ci8PalLen > 256 - 16:
raise PluginError(
"In material "
+ material.name
+ ": the CI8 texture has over 240 colors, which can't fit together with the CI4 palette."
"The CI8 texture has over 240 colors, which can't fit together with the CI4 palette."
)
self.ti0.loadPal = self.ti1.loadPal = True
if self.ti0.texFormat == "CI8":
Expand All @@ -697,9 +670,7 @@ def writeAll(
self.ti0.palLen = len(self.ti0.pal)
if self.ti0.palLen > 256:
raise PluginError(
"In material "
+ material.name
+ ": the two CI textures together contain a total of "
"The two CI textures together contain a total of "
+ str(self.ti0.palLen)
+ " colors, which can't fit in a CI8 palette (256)."
+ " The CI8 texture must contain up to 240 unique colors,"
Expand Down Expand Up @@ -792,7 +763,7 @@ def writeAll(
if sameTextures:
assert (
self.ti0.tmemSize == self.ti1.tmemSize
), f"Unreachable code path in material {material.name}, same textures (same image or reference) somehow not the same size"
), f"Unreachable code path, same textures (same image or reference) somehow not the same size"
tmemOccupied = self.ti0.tmemSize
self.ti1.doTexLoad = False
self.ti1.texAddr = 0
Expand Down Expand Up @@ -835,11 +806,7 @@ def writeAll(
self.ti1.texAddr = tmemSize - self.ti1.tmemSize
else:
# Both textures large
raise PluginError(
'Error in "'
+ material.name
+ '": Multitexture with two large textures is not currently supported.'
)
raise PluginError("Multitexture with two large textures is not currently supported.")
# Limited cases of 2x large textures could be supported in the
# future. However, these cases are either of questionable
# utility or have substantial restrictions. Most cases could be
Expand Down Expand Up @@ -877,16 +844,10 @@ def writeAll(
tmemOccupied = tmemSize
if tmemOccupied > tmemSize:
if sameTextures and useLargeTextures:
raise PluginError(
'Error in "'
+ material.name
+ '": Using the same texture for Tex0 and Tex1 is not compatible with large textures.'
)
raise PluginError("Using the same texture for Tex0 and Tex1 is not compatible with large textures.")
elif not bpy.context.scene.ignoreTextureRestrictions:
raise PluginError(
'Error in "'
+ material.name
+ '": Textures are too big. Max TMEM size is 4k '
"Textures are too big. Max TMEM size is 4k "
+ "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries."
)

Expand Down
26 changes: 10 additions & 16 deletions fast64_internal/f3d/f3d_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@ def getTexDimensions(material):
return texDimensions


@wrap_func_with_error_message(lambda args: (f"In material '{args['material'].name}': "))
def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
print(f"Writing material {material.name}")
if material.mat_ver > 3:
Expand Down Expand Up @@ -1317,7 +1318,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData):
)

if not material.is_f3d:
raise PluginError("Material named " + material.name + " is not an F3D material.")
raise PluginError("Not an F3D material.")
fMaterial = fModel.addMaterial(materialName)
useDict = all_combiner_uses(f3dMat)

Expand Down Expand Up @@ -1550,20 +1551,13 @@ def getLightDefinitions(fModel, material, lightsName=""):
else:
lights.a = Ambient(exportColor(material.ambient_light_color))

if material.f3d_light1 is not None:
addLightDefinition(material, material.f3d_light1, lights)
if material.f3d_light2 is not None:
addLightDefinition(material, material.f3d_light2, lights)
if material.f3d_light3 is not None:
addLightDefinition(material, material.f3d_light3, lights)
if material.f3d_light4 is not None:
addLightDefinition(material, material.f3d_light4, lights)
if material.f3d_light5 is not None:
addLightDefinition(material, material.f3d_light5, lights)
if material.f3d_light6 is not None:
addLightDefinition(material, material.f3d_light6, lights)
if material.f3d_light7 is not None:
addLightDefinition(material, material.f3d_light7, lights)
for i in range(1, 8):
try:
light_prop = getattr(material, f"f3d_light{i}")
if light_prop is not None:
addLightDefinition(light_prop, lights)
except Exception as exc:
raise PluginError(f"Failed to get custom light {i}: {exc}") from exc

return lights

Expand All @@ -1581,7 +1575,7 @@ def saveLightsDefinition(fModel, fMaterial, material, lightsName):
return lights


def addLightDefinition(mat, f3d_light, fLights):
def addLightDefinition(f3d_light, fLights):
lightObj = lightDataToObj(f3d_light)
fLights.l.append(
Light(
Expand Down
27 changes: 20 additions & 7 deletions fast64_internal/oot/exporter/scene/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,36 @@ def new(name: str, sceneObj: Object, transform: Matrix, useMacros: bool, saveTex
True,
)

mainHeader = SceneHeader.new(f"{name}_header{i:02}", sceneObj.ootSceneHeader, sceneObj, transform, i, useMacros)
try:
mainHeader = SceneHeader.new(
f"{name}_header{i:02}", sceneObj.ootSceneHeader, sceneObj, transform, i, useMacros
)
except Exception as exc:
raise PluginError(f"In main scene header: {exc}") from exc
hasAlternateHeaders = False
altHeader = SceneAlternateHeader(f"{name}_alternateHeaders")
altProp = sceneObj.ootAlternateSceneHeaders

for i, header in enumerate(altHeaderList, 1):
altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header")
if not altP.usePreviousHeader:
if altP.usePreviousHeader:
continue
try:
setattr(
altHeader, header, SceneHeader.new(f"{name}_header{i:02}", altP, sceneObj, transform, i, useMacros)
)
hasAlternateHeaders = True

altHeader.cutscenes = [
SceneHeader.new(f"{name}_header{i:02}", csHeader, sceneObj, transform, i, useMacros)
for i, csHeader in enumerate(altProp.cutsceneHeaders, 4)
]
except Exception as exc:
raise PluginError(f"In alternate scene header {header}: {exc}") from exc

altHeader.cutscenes = []
for i, csHeader in enumerate(altProp.cutsceneHeaders, 4):
try:
altHeader.cutscenes.append(
SceneHeader.new(f"{name}_header{i:02}", csHeader, sceneObj, transform, i, useMacros)
)
except Exception as exc:
raise PluginError(f"In alternate, cutscene header {i}: {exc}") from exc

hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else hasAlternateHeaders
altHeader = altHeader if hasAlternateHeaders else None
Expand Down
43 changes: 23 additions & 20 deletions fast64_internal/oot/exporter/scene/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,35 @@ class SceneLighting:
@staticmethod
def new(name: str, props: OOTSceneHeaderProperty):
envLightMode = Utility.getPropValue(props, "skyboxLighting")
lightList: list[OOTLightProperty] = []
lightList: dict[str, OOTLightProperty] = {}
settings: list[EnvLightSettings] = []

if envLightMode == "LIGHT_MODE_TIME":
todLights = props.timeOfDayLights
lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night]
lightList = {"Dawn": todLights.dawn, "Day": todLights.day, "Dusk": todLights.dusk, "Night": todLights.night}
else:
lightList = props.lightList

for lightProp in lightList:
light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True)
light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True)
settings.append(
EnvLightSettings(
envLightMode,
exportColor(lightProp.ambient),
light1[0],
light1[1],
light2[0],
light2[1],
exportColor(lightProp.fogColor),
lightProp.fogNear,
lightProp.z_far,
lightProp.transitionSpeed,
lightList = {str(i): light for i, light in enumerate(props.lightList)}

for name, lightProp in lightList.items():
try:
light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True)
light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True)
settings.append(
EnvLightSettings(
envLightMode,
exportColor(lightProp.ambient),
light1[0],
light1[1],
light2[0],
light2[1],
exportColor(lightProp.fogColor),
lightProp.fogNear,
lightProp.z_far,
lightProp.transitionSpeed,
)
)
)
except Exception as exc:
raise PluginError(f"In light settings {name}: {exc}") from exc
return SceneLighting(name, envLightMode, settings)

def getCmd(self):
Expand Down
44 changes: 34 additions & 10 deletions fast64_internal/utility.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator
import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator, inspect
from math import pi, ceil, degrees, radians, copysign
from mathutils import *
from .utility_anim import *
Expand Down Expand Up @@ -1662,7 +1662,9 @@ def lightDataToObj(lightData):
for obj in bpy.context.scene.objects:
if obj.data == lightData:
return obj
raise PluginError("A material is referencing a light that is no longer in the scene (i.e. has been deleted).")
raise PluginError(
f'Referencing a light ("{lightData.name}") that is no longer in the scene (i.e. has been deleted).'
)


def ootGetSceneOrRoomHeader(parent, idx, isRoom):
Expand Down Expand Up @@ -1699,14 +1701,17 @@ def ootGetBaseOrCustomLight(prop, idx, toExport: bool, errIfMissing: bool):
col = getattr(prop, "diffuse" + str(idx))
dir = (mathutils.Vector((1.0, -1.0, 1.0)) * (1.0 if idx == 0 else -1.0)).normalized()
if getattr(prop, "useCustomDiffuse" + str(idx)):
light = getattr(prop, "diffuse" + str(idx) + "Custom")
if light is None:
if errIfMissing:
raise PluginError("Error: Diffuse " + str(idx) + " light object not set in a scene lighting property.")
else:
col = tuple(c for c in light.color) + (1.0,)
lightObj = lightDataToObj(light)
dir = getObjDirectionVec(lightObj, toExport)
try:
light = getattr(prop, "diffuse" + str(idx) + "Custom")
if light is None:
if errIfMissing:
raise PluginError("Light object not set in a scene lighting property.")
else:
col = tuple(c for c in light.color) + (1.0,)
lightObj = lightDataToObj(light)
dir = getObjDirectionVec(lightObj, toExport)
except Exception as exc:
raise PluginError(f"In custom diffuse {idx}: {exc}") from exc
col = mathutils.Vector(tuple(c for c in col))
if toExport:
col, dir = exportColor(col), normToSigned8Vector(dir)
Expand Down Expand Up @@ -1909,3 +1914,22 @@ def set_if_different(owner: object, prop: str, value):
def set_prop_if_in_data(owner: object, prop_name: str, data: dict, data_name: str):
if data_name in data:
set_if_different(owner, prop_name, data[data_name])


def wrap_func_with_error_message(error_message: Callable):
"""Decorator for big, reused functions that need generic info in errors, such as material exports."""

def decorator(func):
def wrapper(*args, **kwargs):
# Get the argument names and values (positional and keyword)
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
try:
return func(*args, **kwargs)
except Exception as exc:
raise PluginError(f"{error_message(bound_args.arguments)} {exc}") from exc

return wrapper

return decorator

0 comments on commit 79d3bad

Please sign in to comment.