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 sameResource(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.metafiles 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¶
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 |
|
Package cannot be imported |
|
File missing inside package |
|
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")
Worked example: the model viewer demo¶
packages/graphics/examples/3d_model_viewer.py loads the Khronos
DamagedHelmet sample. The relevant pieces:
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:
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 |
|---|---|
|
Filesystem path (absolute or project-root-relative). |
|
Asset shipped inside a Python package. |
|
Raw |
Anything else raises TypeError.