Skip to main content

forge-pipeline-library

Use the forge-gpu asset pipeline library — a Python package installed via uv for scanning, fingerprinting, processing, and bundling game assets with a plugin architecture. Use when working with pipeline code, writing plugins, processing textures/meshes/animations, bundling assets, or extending the pipeline CLI.

설치로 이동

소스 정보

저장소
Nebulavenus/forge-gpu
최근 소스 활동
2026년 3월 22일 03:50
감지된 SKILL.md 언어
영어
스타
38
포크
7

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
forge-pipeline-library
description
Use the forge-gpu asset pipeline library — a Python package installed via uv for scanning, fingerprinting, processing, and bundling game assets with a plugin architecture. Use when working with pipeline code, writing plugins, processing textures/meshes/animations, bundling assets, or extending the pipeline CLI.
# forge-pipeline-library The forge asset pipeline is a Python package at the repo root (`pipeline/`), installed via `uv sync`. It processes raw game assets (textures, meshes, animations) into GPU-ready formats with incremental builds driven by content hashing. ## When to use this skill - Writing or modifying pipeline code in `pipeline/` - Creating a new asset plugin (texture, mesh, animation, or custom) - Processing assets with the `forge-pipeline` CLI - Bundling processed assets into `.forgepak` archives - Debugging pipeline configuration or plugin discovery - Extending the pipeline with new file types or processing steps - Loading pipeline-processed assets in C/GPU code - Writing tests for pipeline modules ## Installation ```bash # Install all dependencies from the lockfile (including dev extras) uv sync --extra dev # Verify uv run forge-pipeline --help ``` The package is defined in `pyproject.toml` at the repo root. The CLI entry point is `forge-pipeline`, which maps to `pipeline.__main__:main`. ## Package structure ```text pipeline/ ├── __init__.py # Package version (__version__ = "0.1.0") ├── __main__.py # CLI entry point — argparse, scan, process, bundle ├── atlas.py # Texture atlas packing (guillotine + shelf algorithms) ├── bundler.py # .forgepak bundle writer/reader, dependency graph ├── config.py # TOML config loader → PipelineConfig dataclass ├── import_settings.py # Per-asset import settings (TOML sidecars, three-layer merge) ├── plugin.py # AssetPlugin base class, PluginRegistry, discovery ├── scanner.py # File scanning, SHA-256 fingerprinting, cache ├── server.py # Web UI backend (FastAPI) └── plugins/ ├── __init__.py ├── animation.py # AnimationPlugin — glTF animation → .fanim binary ├── atlas.py # AtlasPlugin — texture atlas packing ├── mesh.py # MeshPlugin — deduplicate, optimize, tangents, LOD ├── scene.py # ScenePlugin — glTF scene hierarchy → .fscene binary └── texture.py # TexturePlugin — resize, mipmaps, GPU compression ``` ## Core modules ### config.py — Configuration ```python from pipeline.config import load_config, default_config, PipelineConfig, ConfigError # Load from TOML file config = load_config(Path("pipeline.toml")) # Use defaults if no file exists config = default_config() # Access typed fields config.source_dir # Path — where raw assets live config.output_dir # Path — where processed files go config.cache_dir # Path — fingerprint cache location config.plugin_settings # dict[str, dict] — per-plugin config sections config.raw # dict — full parsed TOML for forward-compatibility ``` **TOML structure:** ```toml [pipeline] source_dir = "assets/raw" output_dir = "assets/processed" cache_dir = ".forge-cache" [texture] max_size = 2048 generate_mipmaps = true output_format = "png" compression = "none" # none, basisu, or astc [mesh] deduplicate = true optimize = true generate_tangents = true lod_levels = [1.0, 0.5, 0.25] [animation] tool_path = "" # override forge-anim-tool location ``` Per-plugin sections (everything except `[pipeline]`) are passed to plugins as the `settings` dict in their `process()` method. ### plugin.py — Plugin system ```python from pipeline.plugin import AssetPlugin, AssetResult, PluginRegistry # Base class — subclass this to create a new plugin class MyPlugin(AssetPlugin): name = "my-type" extensions = [".xyz", ".abc"] def process(self, source: Path, output_dir: Path, settings: dict) -> AssetResult: output = output_dir / f"{source.stem}.processed" # ... do work ... return AssetResult(source=source, output=output, metadata={"key": "val"}) # Registry — discovers and stores plugins registry = PluginRegistry() registry.register(MyPlugin()) # manual registration count = registry.discover(Path("plugins/")) # file-based discovery # Lookup plugins = registry.get_by_extension(".xyz") # returns list[AssetPlugin] plugin = registry.get_by_name("my-type") # returns AssetPlugin | None all_exts = registry.supported_extensions # set[str] all_plugins = registry.plugins # list[AssetPlugin] ``` **Discovery rules:** - Scans `*.py` files in a directory (skips `_`-prefixed files) - Imports each module, finds `AssetPlugin` subclasses with a non-empty `name` - Only registers classes defined in that module (not imported base classes) - Multiple plugins can handle the same extension (all are invoked) ### scanner.py — Fingerprinting and change detection ```python from pipeline.scanner import scan, fingerprint_file, FingerprintCache, FileStatus, ScannedFile # Fingerprint a single file digest = fingerprint_file(Path("texture.png")) # SHA-256 hex string # Load or create a cache cache = FingerprintCache(Path(".forge-cache/fingerprints.json")) cached_hash = cache.get(Path("textures/brick.png")) # str | None cache.set(Path("textures/brick.png"), digest) cache.save() # persist to disk # Scan a directory files: list[ScannedFile] = scan( source_dir=Path("assets/raw"), supported_extensions={".png", ".jpg", ".obj", ".gltf"}, cache=cache, ) # Each ScannedFile has: for f in files: f.path # Path — absolute path f.relative # Path — relative to source_dir f.extension # str — lowercase with dot f.fingerprint # str — SHA-256 hex f.status # FileStatus.NEW | CHANGED | UNCHANGED ``` **Why content hashes, not timestamps:** - Deterministic — same bytes always produce the same hash - Portable — survives git clone, file copies, CI - Correct — touching a file without changing content skips reprocessing ### bundler.py — Asset bundles ```python from pipeline.bundler import ( BundleWriter, BundleReader, BundleManifest, BundleEntry, DependencyGraph, create_bundle, BundleError, BundleFormatError, ) # Write a bundle writer = BundleWriter(Path("game.forgepak"), compression_level=3) writer.add("textures/brick.png", png_bytes) writer.add_file(Path("hero.fmesh"), "meshes/hero.fmesh", dependencies=["textures/brick.png"]) manifest = writer.finalize() # Read a bundle (random access) with BundleReader(Path("game.forgepak")) as reader: data = reader.read("textures/brick.png") paths = reader.manifest.paths entry = reader.manifest.get("meshes/hero.fmesh") # Bundle an entire directory manifest = create_bundle( output_dir=Path("assets/processed"), bundle_path=Path("game.forgepak"), compress=True, compression_level=3, patterns=["*.png", "*.fmesh"], # optional glob filters ) # Dependency graph (from .meta.json sidecars) graph = DependencyGraph.from_meta_files(Path("assets/processed")) deps = graph.dependencies_of("meshes/hero.fmesh") dependents = graph.dependents_of("textures/brick.png") order = graph.topological_order() ``` **Bundle format (`.forgepak`):** - Header (24 bytes): magic `FPAK`, version, entry count, TOC offset/size - Entry data: each entry compressed independently with zstd - TOC at end: zstd-compressed JSON array of entry metadata - O(1) random access — seek to offset, decompress one entry ## Built-in plugins ### TexturePlugin (`plugins/texture.py`) Handles: `.png`, `.jpg`, `.jpeg`, `.tga`, `.bmp` Processing steps: 1. Load with Pillow, convert to RGB/RGBA 2. Resize to fit `max_size` (preserves aspect ratio) 3. Save in `output_format` (png, jpg, bmp) 4. Generate mipmap chain (halved sizes down to 1x1) 5. Optional GPU compression via `basisu` (KTX2) or `astcenc` (ASTC) 6. Write `.meta.json` sidecar Settings (`[texture]` in pipeline.toml): | Setting | Default | Description | |---|---|---| | `max_size` | 2048 | Clamp width/height | | `generate_mipmaps` | true | Create mip chain | | `output_format` | "png" | Output format | | `jpg_quality` | 90 | JPEG quality (1-100) | | `compression` | "none" | none, basisu, astc | | `basisu_format` | "uastc" | etc1s or uastc | | `basisu_quality` | 128 | 1-255 | | `astc_block_size` | "6x6" | 4x4, 5x5, 6x6, 8x8 | | `astc_quality` | "medium" | fastest..exhaustive | | `normal_map` | false | BC5/linear encoding | ### MeshPlugin (`plugins/mesh.py`) Handles: `.obj`, `.gltf`, `.glb` Invokes `forge-mesh-tool` (compiled C binary) as a subprocess for: 1. Vertex deduplication 2. Index/vertex cache optimization (meshoptimizer) 3. MikkTSpace tangent generation 4. LOD simplification at configurable ratios Output: `.fmesh` binary + `.meta.json` + optional `.fmat` material sidecar Settings (`[mesh]` in pipeline.toml): | Setting | Default | Description | |---|---|---| | `deduplicate` | true | Remove duplicate vertices | | `optimize` | true | Cache-friendly index reorder | | `generate_tangents` | true | MikkTSpace tangent frames | | `lod_levels` | [1.0] | Target triangle ratios | | `tool_path` | "" | Override tool location | Falls back gracefully if the C tool is not installed. ### AnimationPlugin (`plugins/animation.py`) Handles: `.gltf`, `.glb` Invokes `forge-anim-tool` (compiled C binary) to extract glTF animation clips into `.fanim` binary files with channels, samplers, and keyframe data. Settings (`[animation]` in pipeline.toml): | Setting | Default | Description | |---|---|---| | `tool_path` | "" | Override tool location | Multiple plugins can handle the same extension — both `MeshPlugin` and `AnimationPlugin` register `.gltf`/`.glb`, so a single glTF file produces both a `.fmesh` and a `.fanim`. ## CLI usage ```bash # Scan and process all assets (uses pipeline.toml or defaults) forge-pipeline # Dry run — scan and report without processing forge-pipeline --dry-run # Verbose output forge-pipeline -v # Custom config file forge-pipeline -c my-config.toml # Override source directory forge-pipeline --source-dir path/to/assets # Process only one plugin type forge-pipeline --plugin texture # Bundle processed assets forge-pipeline bundle forge-pipeline bundle -o game.forgepak --level 9 forge-pipeline bundle --pattern "*.png" --pattern "*.fmesh" # Inspect a bundle forge-pipeline info game.forgepak ``` ## Writing a new plugin 1. Create `pipeline/plugins/my_type.py` 2. Subclass `AssetPlugin`, set `name` and `extensions` 3. Implement `process()` returning an `AssetResult` 4. Add a `[my_type]` section to `pipeline.toml` for settings 5. Add tests in `tests/pipeline/test_my_type.py` ```python """My custom asset type plugin.""" from pathlib import Path from pipeline.plugin import AssetPlugin, AssetResult class MyTypePlugin(AssetPlugin): name = "my_type" extensions = [".xyz"] def process(self, source: Path, output_dir: Path, settings: dict) -> AssetResult: threshold = int(settings.get("threshold", 100)) output = output_dir / f"{source.stem}.processed" # ... transform source into output ... return AssetResult( source=source, output=output, metadata={"threshold": threshold, "processed": True}, ) ``` The plugin is auto-discovered — no registration code needed. ## Settings-aware caching The pipeline combines each file's content hash with a digest of its plugin settings. Changing a setting (e.g. `max_size` from 2048 to 1024) invalidates previously processed assets even if the source file hasn't changed. ```python # From __main__.py — the combined fingerprint combined = sha256(f"{content_hash}:{json.dumps(settings, sort_keys=True)}") ``` ## Metadata sidecars Every plugin writes a `.meta.json` file alongside its output. Sidecars record the source file, output dimensions, processing settings, and dependency information. The bundler reads these to build the dependency graph for correct processing order and invalidation. ## Testing ```bash # Run all pipeline tests uv run pytest tests/pipeline/ -v # Run a specific test module uv run pytest tests/pipeline/test_scanner.py -v # Lint uv run ruff check pipeline/ tests/pipeline/ uv run ruff format --check pipeline/ tests/pipeline/ ``` ## Common mistakes | Mistake | Fix | |---|---| | Using timestamps for change detection | Use SHA-256 content hashes — timestamps break on git clone | | Hardcoding plugin list in core code | Use file-based discovery — drop a `.py` file to add a type | | Storing absolute paths in the cache | Use POSIX-style relative paths for portability | | Not validating plugin registration | Check for duplicate names — silent conflicts cause confusion | | Forgetting `tomllib` fallback for Python < 3.11 | Guard with `sys.version_info`, fall back to `tomli` | | Skipping `_`-prefixed plugin files | Convention: `_`-prefixed files are internal helpers | | Not writing `.meta.json` sidecars | Bundler needs them for dependency graph construction | | Single Write call for large files | Use chunked-write pattern for files over 800 lines | ## Cross-references | Lesson | What it covers | |---|---| | [Asset 01 — Pipeline Scaffold](../../../lessons/assets/01-pipeline-scaffold/) | CLI, config, scanner, plugin system | | [Asset 02 — Texture Processing](../../../lessons/assets/02-texture-processing/) | TexturePlugin, mipmaps, GPU compression |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기