Panda3D is a framework for 3D rendering and game development for Python and C++ programs.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Panda3D provides a comprehensive asset loading system that handles 3D models, textures, animations, audio files, and other resources with support for various file formats and flexible loading strategies.
The Loader class provides the primary interface for loading game assets with caching, format conversion, and error handling.
class Loader:
def loadModel(self,
modelPath: str,
noCache: bool = False,
allowInstanceFromCache: bool = False,
callback: callable = None,
extraArgs: list = []) -> NodePath:
"""
Load 3D model from file.
Args:
modelPath: Path to model file
noCache: Skip caching system
allowInstanceFromCache: Allow instancing from cache
callback: Async loading callback
extraArgs: Extra arguments for callback
Returns:
NodePath containing loaded model
"""
def loadModelOnce(self, modelPath: str) -> NodePath:
"""Load model with guaranteed caching."""
def loadModelCopy(self, modelPath: str) -> NodePath:
"""Load unique copy of model (no instancing)."""
def unloadModel(self, model: NodePath) -> None:
"""Unload model and free resources."""
def loadTexture(self,
texturePath: str,
alphaPath: str = None,
readMipmaps: bool = False,
okMissing: bool = False,
minfilter: int = None,
magfilter: int = None) -> Texture:
"""
Load texture from file.
Args:
texturePath: Path to texture file
alphaPath: Optional separate alpha channel file
readMipmaps: Read mipmaps if available
okMissing: Don't error if file missing
minfilter: Minification filter mode
magfilter: Magnification filter mode
Returns:
Texture object
"""
def loadCubeMap(self, texturePath: str) -> Texture:
"""Load cube map texture for environment mapping."""
def load3DTexture(self, texturePath: str) -> Texture:
"""Load 3D volume texture."""
def loadFont(self, fontPath: str) -> TextFont:
"""Load font for text rendering."""
def loadSfx(self, soundPath: str) -> AudioSound:
"""Load sound effect file."""
def loadMusic(self, musicPath: str) -> AudioSound:
"""Load music file."""
def loadShader(self,
vertexPath: str = None,
fragmentPath: str = None,
geometryPath: str = None) -> Shader:
"""Load shader program from source files."""Panda3D supports a wide variety of asset file formats through built-in and plugin loaders.
# 3D Model Formats
MODEL_FORMATS = [
".egg", # Panda3D native format
".bam", # Panda3D binary format (fastest loading)
".x", # DirectX format
".dae", # COLLADA format
".obj", # Wavefront OBJ
".ply", # Stanford PLY
".lwo", # LightWave Object
".3ds", # 3D Studio format
".gltf", # glTF 2.0 (if plugin available)
".fbx" # FBX (if plugin available)
]
# Texture Formats
TEXTURE_FORMATS = [
".png", # PNG (recommended)
".jpg", ".jpeg", # JPEG
".tga", # Targa
".bmp", # Bitmap
".pnm", # Portable Network Graphics
".sgi", # Silicon Graphics Image
".rgb", # RGB format
".tiff", ".tif", # TIFF
".exr", # OpenEXR (HDR)
".hdr", # Radiance HDR
".dds" # DirectDraw Surface
]
# Audio Formats
AUDIO_FORMATS = [
".wav", # WAV (uncompressed)
".ogg", # Ogg Vorbis
".mp3", # MP3 (if available)
".flac" # FLAC (if available)
]
# Animation Formats
ANIMATION_FORMATS = [
".egg", # Panda3D animation
".bam", # Panda3D binary animation
]Configure and manage asset loading paths and search directories.
from panda3d.core import getModelPath, Filename, DSearchPath
# Get current model search path
def getAssetPath() -> DSearchPath:
"""Get current asset search path."""
return getModelPath()
# Add search directories
def addAssetPath(path: str) -> None:
"""Add directory to asset search path."""
getModelPath().appendDirectory(Filename(path))
def prependAssetPath(path: str) -> None:
"""Add directory to front of search path."""
getModelPath().prependDirectory(Filename(path))
# Search for assets
def findAsset(filename: str) -> str:
"""Find asset file in search path."""
path = getModelPath().findFile(filename)
return str(path) if path else NoneLoad assets asynchronously to avoid blocking the main thread.
class Loader:
def loadModelAsync(self,
modelPath: str,
callback: callable,
extraArgs: list = []) -> None:
"""Load model asynchronously with callback."""
def loadTextureAsync(self,
texturePath: str,
callback: callable,
extraArgs: list = []) -> None:
"""Load texture asynchronously with callback."""
def cancelAsyncRequest(self, request) -> None:
"""Cancel pending async loading request."""Manage loaded assets and control caching behavior.
class Loader:
def flushCache(self) -> None:
"""Clear all cached assets."""
def clearCache(self, filename: str) -> None:
"""Clear specific file from cache."""
def setCacheLimit(self, limit: int) -> None:
"""Set maximum cache size in bytes."""
def getCacheSize(self) -> int:
"""Get current cache size in bytes."""
def listCachedFiles(self) -> list:
"""List all files currently in cache."""Load assets from virtual file systems, compressed archives, and network sources.
from panda3d.core import VirtualFileSystem, Multifile
# Virtual file system access
vfs = VirtualFileSystem.getGlobalPtr()
# Mount archive files
def mountArchive(archive_path: str, mount_point: str = "/") -> bool:
"""Mount archive file as virtual directory."""
multifile = Multifile()
if multifile.openReadWrite(archive_path):
vfs.mount(multifile, mount_point, 0)
return True
return False
# Load from VFS
def loadFromVFS(vfs_path: str) -> NodePath:
"""Load model from virtual file system path."""
return loader.loadModel(vfs_path)from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
class AssetDemo(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# Configure asset paths
self.setupAssetPaths()
# Load various assets
self.loadGameAssets()
# Setup scene
self.setupScene()
def setupAssetPaths(self):
"""Configure asset search paths."""
# Add custom asset directories
getModelPath().appendDirectory("assets/models")
getModelPath().appendDirectory("assets/textures")
getModelPath().appendDirectory("assets/sounds")
# Add archive files
multifile = Multifile()
if multifile.openRead("game_assets.mf"):
vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(multifile, "/game", 0)
print("Mounted game_assets.mf")
def loadGameAssets(self):
"""Load all game assets."""
# Load 3D models
self.environment = self.loader.loadModel("environment.egg")
self.player_model = self.loader.loadModel("characters/player.egg")
self.enemy_model = self.loader.loadModel("characters/enemy.egg")
# Load textures
self.grass_texture = self.loader.loadTexture("grass.png")
self.stone_texture = self.loader.loadTexture("stone.jpg")
self.metal_texture = self.loader.loadTexture("metal.tga")
# Load sounds
self.footstep_sound = self.loader.loadSfx("footstep.wav")
self.background_music = self.loader.loadMusic("background.ogg")
# Load fonts
self.ui_font = self.loader.loadFont("fonts/arial.ttf")
print("All assets loaded successfully")
def setupScene(self):
"""Setup the 3D scene with loaded assets."""
# Setup environment
if self.environment:
self.environment.reparentTo(self.render)
self.environment.setTexture(self.grass_texture)
# Setup player
if self.player_model:
self.player_model.reparentTo(self.render)
self.player_model.setPos(0, 10, 0)
# Setup camera
self.camera.setPos(0, -10, 5)
self.camera.lookAt(self.player_model)
app = AssetDemo()
app.run()from direct.showbase.ShowBase import ShowBase
from direct.gui.DirectGui import *
class AsyncLoadingDemo(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# Create loading screen
self.createLoadingScreen()
# Asset loading queue
self.assets_to_load = [
("models/level1.egg", "level"),
("models/player.egg", "player"),
("models/enemies.egg", "enemies"),
("textures/environment.png", "env_texture"),
("sounds/music.ogg", "music")
]
self.loaded_assets = {}
self.loading_progress = 0
# Start async loading
self.startAsyncLoading()
def createLoadingScreen(self):
"""Create loading screen UI."""
# Background
self.loading_bg = DirectFrame(
frameColor=(0, 0, 0, 1),
frameSize=(-2, 2, -1, 1)
)
# Loading text
self.loading_text = OnscreenText(
text="Loading...",
pos=(0, 0.2),
scale=0.1,
fg=(1, 1, 1, 1)
)
# Progress bar background
self.progress_bg = DirectFrame(
frameColor=(0.3, 0.3, 0.3, 1),
frameSize=(-0.8, 0.8, -0.05, 0.05),
pos=(0, 0, -0.2)
)
# Progress bar
self.progress_bar = DirectFrame(
frameColor=(0, 1, 0, 1),
frameSize=(0, 0, -0.05, 0.05),
pos=(-0.8, 0, -0.2)
)
def startAsyncLoading(self):
"""Start asynchronous asset loading."""
if self.assets_to_load:
asset_path, asset_name = self.assets_to_load.pop(0)
# Determine asset type and load accordingly
if asset_path.endswith(('.egg', '.bam', '.x')):
self.loader.loadModel(
asset_path,
callback=self.onAssetLoaded,
extraArgs=[asset_name]
)
elif asset_path.endswith(('.png', '.jpg', '.tga')):
self.loader.loadTexture(
asset_path,
callback=self.onAssetLoaded,
extraArgs=[asset_name]
)
elif asset_path.endswith(('.wav', '.ogg', '.mp3')):
sound = self.loader.loadSfx(asset_path)
self.onAssetLoaded(sound, asset_name)
def onAssetLoaded(self, asset, asset_name):
"""Handle completed asset loading."""
self.loaded_assets[asset_name] = asset
self.loading_progress += 1
# Update progress
total_assets = len(self.assets_to_load) + self.loading_progress
progress = self.loading_progress / total_assets
# Update progress bar
bar_width = 1.6 * progress
self.progress_bar['frameSize'] = (0, bar_width, -0.05, 0.05)
# Update loading text
self.loading_text.setText(f"Loading... {int(progress * 100)}%")
# Continue loading or finish
if self.assets_to_load:
self.startAsyncLoading()
else:
self.onLoadingComplete()
def onLoadingComplete(self):
"""Handle loading completion."""
print("All assets loaded!")
# Hide loading screen
self.loading_bg.destroy()
self.loading_text.destroy()
self.progress_bg.destroy()
self.progress_bar.destroy()
# Setup game with loaded assets
self.setupGame()
def setupGame(self):
"""Setup game with loaded assets."""
# Use loaded assets
if "level" in self.loaded_assets:
level = self.loaded_assets["level"]
level.reparentTo(self.render)
if "player" in self.loaded_assets:
player = self.loaded_assets["player"]
player.reparentTo(self.render)
player.setPos(0, 10, 0)
print("Game setup complete!")
app = AsyncLoadingDemo()
app.run()from direct.showbase.DirectObject import DirectObject
import os
class AssetManager(DirectObject):
"""Centralized asset management system."""
def __init__(self):
DirectObject.__init__(self)
# Asset registries
self.models = {}
self.textures = {}
self.sounds = {}
self.fonts = {}
# Loading statistics
self.load_count = 0
self.cache_hits = 0
# Setup asset paths
self.setupPaths()
def setupPaths(self):
"""Setup asset search paths."""
paths = [
"assets/models",
"assets/textures",
"assets/sounds",
"assets/fonts",
"data"
]
for path in paths:
if os.path.exists(path):
getModelPath().appendDirectory(path)
def loadModel(self, name: str, path: str = None) -> NodePath:
"""Load and cache model."""
if name in self.models:
self.cache_hits += 1
return self.models[name]
model_path = path if path else name
model = loader.loadModel(model_path)
if model:
self.models[name] = model
self.load_count += 1
print(f"Loaded model: {name}")
else:
print(f"Failed to load model: {model_path}")
return model
def loadTexture(self, name: str, path: str = None) -> Texture:
"""Load and cache texture."""
if name in self.textures:
self.cache_hits += 1
return self.textures[name]
texture_path = path if path else name
texture = loader.loadTexture(texture_path)
if texture:
self.textures[name] = texture
self.load_count += 1
print(f"Loaded texture: {name}")
else:
print(f"Failed to load texture: {texture_path}")
return texture
def loadSound(self, name: str, path: str = None) -> AudioSound:
"""Load and cache sound."""
if name in self.sounds:
self.cache_hits += 1
return self.sounds[name]
sound_path = path if path else name
sound = loader.loadSfx(sound_path)
if sound:
self.sounds[name] = sound
self.load_count += 1
print(f"Loaded sound: {name}")
else:
print(f"Failed to load sound: {sound_path}")
return sound
def preloadAssets(self, asset_list: list):
"""Preload list of assets."""
for asset_type, name, path in asset_list:
if asset_type == "model":
self.loadModel(name, path)
elif asset_type == "texture":
self.loadTexture(name, path)
elif asset_type == "sound":
self.loadSound(name, path)
def getStats(self) -> dict:
"""Get loading statistics."""
return {
"models_loaded": len(self.models),
"textures_loaded": len(self.textures),
"sounds_loaded": len(self.sounds),
"total_loads": self.load_count,
"cache_hits": self.cache_hits
}
def cleanup(self):
"""Clean up all assets."""
# Models cleanup (NodePaths)
for model in self.models.values():
if model:
model.removeNode()
# Clear registries
self.models.clear()
self.textures.clear()
self.sounds.clear()
self.fonts.clear()
print("Asset manager cleaned up")
# Global asset manager instance
asset_manager = AssetManager()from panda3d.core import Texture, TextFont, AudioSound, Shader, NodePath
# Asset loading return types
NodePath # 3D models and scene graphs
Texture # Image textures and materials
AudioSound # Sound effects and music
TextFont # Fonts for text rendering
Shader # Vertex/fragment shader programs
# File format constants
class Texture:
# Texture formats
F_rgb = 1 # RGB format
F_rgba = 2 # RGBA format
F_alpha = 3 # Alpha only
F_luminance = 4 # Grayscale
# Filter modes
FT_nearest = 1 # Nearest neighbor
FT_linear = 2 # Linear filtering
FT_nearest_mipmap_nearest = 3
FT_linear_mipmap_nearest = 4
FT_nearest_mipmap_linear = 5
FT_linear_mipmap_linear = 6Install with Tessl CLI
npx tessl i tessl/pypi-panda3d