Package resources

SimVX accepts game assets (textures, audio, meshes, glTF scenes, config files) as filesystem paths or as Python-package handles. An asset that ships inside a package is named with Resource(package, name), which resolves to a file through importlib.resources.

Why importlib.resources?

  • One naming scheme. Assets live in Python packages (a directory with an __init__.py). The same Resource(package, name) handle works whether the package is a source checkout, installed from a wheel, or bundled into a web export.

  • Refactor-safe. Renaming a package via your IDE/LSP keeps all Resource("my_game.assets", ...) references in sync: no separate UID database to migrate.

  • No sidecar files and no manifest to regenerate: the Python module graph is the manifest.

  • No code generation. Scenes are .py files and asset references are ordinary Python objects.

The Resource helper

from simvx.core import Resource

helmet = Resource("my_game.assets.models", "helmet.gltf")

helmet.path          # PosixPath('/.../my_game/assets/models/helmet.gltf')
helmet.read_bytes()  # b'...'

with helmet.open() as fh:
    data = fh.read()

Resource(package, name) is lazy: construction never touches the filesystem. Resolution happens on first access of .path / .read_bytes() / .open(). Errors are strict by default:

Failure

Exception

Empty package or name

ValueError

Package cannot be imported

ModuleNotFoundError

File missing inside package

FileNotFoundError

Resource instances are equal by (package, name) and hashable, so they work as dict keys and in sets.

Marking a directory as a resource package

A directory becomes a Python package, and therefore a Resource lookup target: by adding an empty __init__.py:

my_game/
    __init__.py
    main.py
    assets/
        __init__.py          <-- makes "my_game.assets" importable
        models/
            __init__.py      <-- makes "my_game.assets.models" importable
            helmet.gltf
            helmet.bin

Then anywhere in your game:

from simvx.core import Resource

HELMET = Resource("my_game.assets.models", "helmet.gltf")

Example: a glTF model

from simvx.core import Resource
from simvx.graphics import import_gltf

HELMET = Resource("my_game.assets.models", "helmet.gltf")

def on_ready(self):
    self.add_child(import_gltf(str(HELMET.path)))

Resource.path is a real file on disk, so the glTF loader finds the sibling .bin and texture files through the relative URIs inside the glTF.

Filesystem paths

For assets that don’t ship inside a Python package (e.g. user-supplied files, content downloaded at runtime, project-relative working copies), pass a str or pathlib.Path:

from pathlib import Path
from simvx.core import AudioClip, Mesh

AudioClip("music/theme.ogg")                   # relative to the working directory
AudioClip(Path.home() / "tracks" / "x.ogg")    # absolute
Mesh.from_obj(Path(__file__).parent / "models" / "ship.obj")   # beside this file

Strings are filesystem paths only: there is no URI scheme to parse and no ambiguity between “package” and “path” syntax.

Accepted source types

AudioClip, Mesh.from_obj, and resolve_asset_path all accept the same union:

Source

When to use

str / pathlib.Path

Filesystem path (absolute or project-root-relative).

Resource(package, name)

Asset shipped inside a Python package.

importlib.resources.Traversable

Raw importlib.resources.files(pkg) / name handle.

Anything else raises TypeError.