diff --git a/pyproject.toml b/pyproject.toml index c51deacf..3e2e18b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,12 +20,23 @@ dependencies = [ [project.optional-dependencies] mips = [ + "splat64[mips-min,assets-n64,compression]", +] + +mips-min = [ "spimdisasm>=1.42.1,<2.0.0", # This value should be keep in sync with the version listed on disassembler/spimdisasm_disassembler.py "rabbitizer>=1.12.0,<2.0.0", +] +assets-n64 = [ + "splat64[mips-min]", "pygfxd>=1.0.5", "n64img>=0.3.3", +] +compression = [ + "splat64[mips-min]", "crunch64>=0.5.1,<1.0.0", ] + dev = [ "splat64[mips]", "ruff", diff --git a/src/splat/segtypes/n64/__init__.py b/src/splat/segtypes/n64/__init__.py index 70eb16e2..85f4092e 100644 --- a/src/splat/segtypes/n64/__init__.py +++ b/src/splat/segtypes/n64/__init__.py @@ -1,21 +1,38 @@ -from . import ci as ci -from . import ci4 as ci4 -from . import ci8 as ci8 from . import decompressor as decompressor -from . import gfx as gfx from . import header as header -from . import i1 as i1 -from . import i4 as i4 -from . import i8 as i8 -from . import ia16 as ia16 -from . import ia4 as ia4 -from . import ia8 as ia8 -from . import img as img from . import ipl3 as ipl3 -from . import mio0 as mio0 from . import palette as palette -from . import rgba16 as rgba16 -from . import rgba32 as rgba32 from . import rsp as rsp from . import vtx as vtx -from . import yay0 as yay0 + +# Segments that require optional dependencies + +# assets-n64 (pygfxd) +try: + from . import gfx as gfx +except ImportError: + pass + +# assets-n64 (n64img) +try: + from . import ci as ci + from . import ci4 as ci4 + from . import ci8 as ci8 + from . import i1 as i1 + from . import i4 as i4 + from . import i8 as i8 + from . import ia16 as ia16 + from . import ia4 as ia4 + from . import ia8 as ia8 + from . import img as img + from . import rgba16 as rgba16 + from . import rgba32 as rgba32 +except ImportError: + pass + +# compression (crunch64) +try: + from . import mio0 as mio0 + from . import yay0 as yay0 +except ImportError: + pass diff --git a/src/splat/segtypes/segment.py b/src/splat/segtypes/segment.py index f086a3ea..561c8d7e 100644 --- a/src/splat/segtypes/segment.py +++ b/src/splat/segtypes/segment.py @@ -96,11 +96,34 @@ def empty_statistics() -> SegmentStatistics: return collections.defaultdict(lambda: SegmentStatisticsInfo(size=0, count=0)) +# Mapping for segments that are enabled only when the corresponding dependency +# is available at runtime. +# The value corresponds to the dependency group listed on the pyproject.toml +# file installs the required Python dependencies for the given segment. +_segments_from_optional_dependencies: dict[str, str] = { + "yay0": "compression", + "mio0": "compression", + "gfx": "assets-n64", + "ci": "assets-n64", + "ci4": "assets-n64", + "ci8": "assets-n64", + "i1": "assets-n64", + "i4": "assets-n64", + "i8": "assets-n64", + "ia16": "assets-n64", + "ia4": "assets-n64", + "ia8": "assets-n64", + "img": "assets-n64", + "rgba16": "assets-n64", + "rgba32": "assets-n64", +} + + class Segment: require_unique_name = True @staticmethod - def get_class_for_type(seg_type) -> Type["Segment"]: + def get_class_for_type(seg_type: str) -> Type["Segment"]: # so .data loads SegData, for example seg_type = seg_type.removeprefix(".") @@ -115,14 +138,21 @@ def get_class_for_type(seg_type) -> Type["Segment"]: segment_class = Segment.get_extension_segment_class(seg_type) if segment_class is None: + dependency_group = _segments_from_optional_dependencies.get(seg_type) + if dependency_group is not None: + log.error( + f"Could not load segment type '{seg_type}'.\n" + f"This segment is available by installing the optional dependency group `splat64[{dependency_group}]`" + ) log.error( - f"could not load segment type '{seg_type}'\n(hint: confirm your extension directory is configured correctly)" + f"Could not load segment type '{seg_type}'\n" + "(hint: confirm your extension directory is configured correctly)" ) return segment_class @staticmethod - def get_base_segment_class(seg_type): + def get_base_segment_class(seg_type: str) -> Optional[Type["Segment"]]: platform = options.opts.platform is_platform_seg = False @@ -144,7 +174,7 @@ def get_base_segment_class(seg_type): return getattr(segmodule, f"{seg_prefix}Seg{seg_type.capitalize()}") @staticmethod - def get_extension_segment_class(seg_type): + def get_extension_segment_class(seg_type: str) -> Optional[Type["Segment"]]: platform = options.opts.platform ext_path = options.opts.extensions_path @@ -167,7 +197,8 @@ def get_extension_segment_class(seg_type): return None return getattr( - ext_mod, f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}" + ext_mod, + f"{platform.upper()}Seg{seg_type[0].upper()}{seg_type[1:]}", ) @staticmethod