CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-panda3d

Panda3D is a framework for 3D rendering and game development for Python and C++ programs.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

asset-loading.mddocs/

Asset Loading

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.

Capabilities

Loader - Main Asset Loading Interface

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."""

Supported File Formats

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
]

Asset Path Resolution

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 None

Asynchronous Loading

Load 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."""

Resource Management and Caching

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."""

Virtual File System Integration

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)

Usage Examples

Basic Asset Loading

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()

Asynchronous Asset Loading

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()

Asset Management System

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()

Types

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 = 6

Install with Tessl CLI

npx tessl i tessl/pypi-panda3d

docs

animation.md

application-framework.md

asset-loading.md

audio.md

index.md

input.md

mathematics.md

scene-graph.md

task-event.md

user-interface.md

tile.json