# Package Resources SimVX accepts game assets (textures, audio, meshes, glTF scenes, config files) as filesystem paths *or* as Python-package handles. The canonical handle for assets that ship inside a package is :class:`simvx.core.Resource`, which resolves to a file via :mod:`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 editable, installed from a wheel, frozen into a zipapp, or served from a Pyodide bundle. - **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.** No `.import`, `.uid`, or `.meta` files cluttering the tree, no manifest to regenerate. The Python module graph *is* the manifest. - **No codegen.** Scenes stay `.py` (see project rule) and asset references stay first-class Python objects. ## The Resource helper ```python 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: ```python from simvx.core import Resource HELMET = Resource("my_game.assets.models", "helmet.gltf") ``` ## Worked example: the model viewer demo `packages/graphics/examples/3d_model_viewer.py` loads the Khronos DamagedHelmet sample. The relevant pieces: ```python from simvx.core import Resource from simvx.graphics.assets.scene_import import import_gltf HELMET_GLTF = Resource("DamagedHelmet", "DamagedHelmet.gltf") # ...inside ready()... model = import_gltf(str(HELMET_GLTF.path)) self.add_child(model) ``` The `assets/DamagedHelmet/` directory ships an `__init__.py`, so `importlib.resources.files("DamagedHelmet")` resolves to that folder. The glTF parser opens the `.gltf` file at the resolved path and pulls the sibling `.bin` and `.jpg` textures via the relative URIs embedded inside the glTF, which "just works" because `Resource.path` returns a real on-disk `Path`. ## 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), just pass a `str` or `pathlib.Path`: ```python from pathlib import Path from simvx.core import AudioStream, Mesh AudioStream("music/theme.ogg") # relative to CWD / project root AudioStream(Path.home() / "tracks" / "x.ogg") # absolute Path Mesh.from_obj("models/ship.obj") # filesystem OBJ ``` Strings are filesystem paths only — there is no URI scheme to parse and no ambiguity between "package" and "path" syntax. ## Accepted source types `AudioStream`, `Mesh.from_obj`, `ResourceLoader.load_threaded_request`, 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`.