77from ..utility import CData , getGroupIndexFromname , readFile , writeFile
88from ..f3d .flipbook import flipbook_to_c , flipbook_2d_to_c , flipbook_data_to_c
99from ..f3d .f3d_material import createF3DMat , F3DMaterial_UpdateLock , update_preset_manual
10- from .utility import replaceMatchContent , getOOTScale
10+ from .utility import replaceMatchContent , getOOTScale , ootStripComments
1111from .texture_array import TextureFlipbook
1212
1313from ..f3d .f3d_writer import (
@@ -334,6 +334,131 @@ def writeTextureArraysExisting2D(data: str, flipbook: TextureFlipbook, flipbookA
334334 return newData
335335
336336
337+ def ootParseScaleLiteral (scaleExpr : str ) -> Optional [float ]:
338+ scaleExpr = scaleExpr .strip ().removesuffix ("f" )
339+
340+ try :
341+ return float (scaleExpr )
342+ except ValueError :
343+ return None
344+
345+
346+ def ootSplitTopLevel (expr : str ) -> list [str ]:
347+ entries = []
348+ start = 0
349+ depth = 0
350+
351+ for i , char in enumerate (expr ):
352+ if char in "({[" :
353+ depth += 1
354+ elif char in ")}]" :
355+ depth -= 1
356+ elif char == "," and depth == 0 :
357+ entries .append (expr [start :i ].strip ())
358+ start = i + 1
359+
360+ finalEntry = expr [start :].strip ()
361+ if finalEntry != "" :
362+ entries .append (finalEntry )
363+
364+ return entries
365+
366+
367+ def ootResolveStructArrayFieldScale (actorData : str , scaleExpr : str ) -> Optional [float ]:
368+ match = re .fullmatch (r"([A-Za-z_]\w*)\s*\[\s*[^\]]+\s*\]\s*\.\s*([A-Za-z_]\w*)" , scaleExpr .strip ())
369+ if match is None :
370+ return None
371+
372+ tableName , fieldName = match .groups ()
373+ actorData = ootStripComments (actorData )
374+
375+ tableMatch = re .search (
376+ rf"(?:static\s+)?(?P<struct_name>[A-Za-z_]\w*)\s+{ re .escape (tableName )} \s*\[\s*\]\s*=\s*\{{" ,
377+ actorData ,
378+ re .DOTALL ,
379+ )
380+ if tableMatch is None :
381+ return None
382+
383+ structMatch = re .search (
384+ rf"typedef\s+struct(?:\s+{ re .escape (tableMatch .group ('struct_name' ))} )?\s*\{{(?P<body>.*?)\}}\s*{ re .escape (tableMatch .group ('struct_name' ))} \s*;" ,
385+ actorData ,
386+ re .DOTALL ,
387+ )
388+ if structMatch is None :
389+ return None
390+
391+ fieldNames = [
392+ fieldMatch .group (1 )
393+ for fieldMatch in re .finditer (
394+ r"[A-Za-z_]\w*(?:\s*\*+)?\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*;" , structMatch .group ("body" )
395+ )
396+ ]
397+ if fieldName not in fieldNames :
398+ return None
399+
400+ fieldIndex = fieldNames .index (fieldName )
401+ values = set ()
402+ depth = 0
403+ entryStart = None
404+
405+ for i in range (tableMatch .end () - 1 , len (actorData )):
406+ char = actorData [i ]
407+ if char == "{" :
408+ if depth == 1 :
409+ entryStart = i + 1
410+ depth += 1
411+ elif char == "}" :
412+ depth -= 1
413+ if depth == 1 and entryStart is not None :
414+ entryValues = ootSplitTopLevel (actorData [entryStart :i ])
415+ if fieldIndex >= len (entryValues ):
416+ return None
417+
418+ value = ootParseScaleLiteral (entryValues [fieldIndex ])
419+ if value is None :
420+ return None
421+
422+ values .add (value )
423+ if len (values ) > 1 :
424+ return None
425+ entryStart = None
426+ elif depth == 0 :
427+ break
428+
429+ return values .pop () if len (values ) == 1 else None
430+
431+
432+ def ootResolveScaleExpression (actorData : str , scaleExpr : str ) -> Optional [float ]:
433+ scaleExpr = scaleExpr .strip ()
434+ literalValue = ootParseScaleLiteral (scaleExpr )
435+ if literalValue is not None :
436+ return literalValue
437+
438+ while scaleExpr .startswith ("(" ) and scaleExpr .endswith (")" ):
439+ scaleExpr = scaleExpr [1 :- 1 ].strip ()
440+ literalValue = ootParseScaleLiteral (scaleExpr )
441+ if literalValue is not None :
442+ return literalValue
443+
444+ depth = 0
445+ for i , char in enumerate (scaleExpr ):
446+ if char in "({[" :
447+ depth += 1
448+ elif char in ")}]" :
449+ depth -= 1
450+ elif depth == 0 and char in "*/" :
451+ leftValue = ootResolveScaleExpression (actorData , scaleExpr [:i ])
452+ rightValue = ootResolveScaleExpression (actorData , scaleExpr [i + 1 :])
453+ if leftValue is None or rightValue is None :
454+ return None
455+ if char == "*" :
456+ return leftValue * rightValue
457+ return None if rightValue == 0 else leftValue / rightValue
458+
459+ return ootResolveStructArrayFieldScale (actorData , scaleExpr )
460+
461+
337462# Note this does not work well with actors containing multiple "parts". (z_en_honotrap)
338463def ootReadActorScale (basePath : str , overlayName : str , isLink : bool ) -> Optional [float ]:
339464 if not isLink :
@@ -344,16 +469,16 @@ def ootReadActorScale(basePath: str, overlayName: str, isLink: bool) -> Optional
344469 chainInitMatch = re .search (r"CHAIN_VEC3F_DIV1000\s*\(\s*scale\s*,\s*(.*?)\s*," , actorData , re .DOTALL )
345470 if chainInitMatch is not None :
346471 scale = chainInitMatch .group (1 ).strip ()
347- if scale [ - 1 ] == "f" :
348- scale = scale [: - 1 ]
349- return getOOTScale (1 / (float ( scale ) / 1000 ))
472+ resolvedScale = ootResolveScaleExpression ( actorData , scale )
473+ if resolvedScale is not None :
474+ return getOOTScale (1 / (resolvedScale / 1000 ))
350475
351476 actorScaleMatch = re .search (r"Actor\_SetScale\s*\(.*?,\s*(.*?)\s*\)" , actorData , re .DOTALL )
352477 if actorScaleMatch is not None :
353478 scale = actorScaleMatch .group (1 ).strip ()
354- if scale [ - 1 ] == "f" :
355- scale = scale [: - 1 ]
356- return getOOTScale (1 / float ( scale ) )
479+ resolvedScale = ootResolveScaleExpression ( actorData , scale )
480+ if resolvedScale is not None :
481+ return getOOTScale (1 / resolvedScale )
357482
358483 print ("WARNING: auto-detection failed, defaulting to this panel's actor scale property value" )
359484 return None
0 commit comments