Speedball arenas to splatmap atlases.
Project description
layoutc
Copy-Paste Ready: All Python examples work with
pip install -e .and can be copied (or piped) directly intopython3. Examples use the included tournament layout files.
layoutc is a command-line utility and Python library for encoding and decoding spatial entity layouts in speedball arena formats. It supports converting between JSON-based layout representations and PNG-based splatmap atlases.
Speedball is a competitive paintball format featuring symmetrical field layouts with inflatable bunkers. This tool helps manage and convert layout data between different formats used by tournament software, game engines, and visualization tools.
Features
- Encode spatial entities from JSON layouts into PNG splatmap atlases.
- Decode spatial entities from PNG splatmap atlases into JSON layouts.
- Support for TSV (Tab-Separated Values) format for tabular data exchange.
- Customizable color depth and pixel pitch for encoding/decoding.
- Support for different spatial units (meters, degrees, turns).
- Quadrant-based spatial representation for efficient encoding/decoding.
- Automatic format detection based on file extensions and content.
- Extensible architecture for adding support for additional file formats.
Installation
For development, clone the repository and install in editable mode:
git clone https://github.com/infimalabs/layoutc.git
cd layoutc
pip install -e .
Otherwise, install via PyPI:
pip install layoutc
Quick Start
To test any code block:
- Windows: Copy the code, then paste into
python3 - macOS: Copy the code, then run
pbpaste | python3in Terminal or paste intopython3 - Linux: Copy the code, then run
xclip -o | python3in a terminal or paste intopython3
If any example doesn't work, ensure you have:
- Python 3.10+ installed
- The
layoutcrepository cloned locally - Your terminal is in the
layoutcproject directory - Installed the package in editable mode:
pip install -e . - Tried
python3 -m layoutc …instead iflayoutc …fails - Ensure your paste buffer contains what you expect
One use case is converting tournament layout files:
# Convert a single layout to PNG atlas for efficient storage
layoutc src/layouts/NXL-World-Cup-2021.json world_cup_2021.png
# Convert PNG atlas back to JSON for editing
layoutc world_cup_2021.png world_cup_2021_copy.json
# Convert all World Cup layouts into a single combined atlas
layoutc src/layouts/*World-Cup*.json all_world_cups.png
# Output all layouts as TSV to stdout (pipe-friendly)
layoutc src/layouts/*.json -
Copy-paste test:
from layoutc.codec import Codec
codec = Codec()
with open("src/layouts/NXL-World-Cup-2021.json", "rb") as fp:
codec.load(fp)
entities = list(codec)
print(f"✓ Loaded {len(entities)} entities from World Cup 2021")
print(f"✓ First entity: {entities[0]}")
Complete workflow example:
from layoutc.codec import Codec
from layoutc.entity import Entity
from layoutc import Unit
import glob
import json
# Load and analyze tournament data
with open("src/layouts/NXL-Texas-2019.json", "r") as f:
texas_data = json.load(f)
print(f"Loaded Texas 2019: {len(texas_data)} bunkers")
# Convert to different formats
codec = Codec()
with open("src/layouts/NXL-Texas-2019.json", "rb") as fp:
codec.load(fp)
with open("texas_atlas.png", "wb") as fp:
codec.dump(fp)
# Work with entities directly
for i, entity_data in enumerate(list(codec)[:3]):
entity = Entity(*entity_data)
display = entity.unfold(Unit.METER, Unit.DEGREE)
print(f"Bunker {i+1}: ({display.x:.1f}m, {display.y:.1f}m, {display.z:.0f}°)")
# Create multi-layout atlas
codec.clear()
world_cup_files = glob.glob("src/layouts/NXL-World-Cup-*.json")
for filename in world_cup_files:
with open(filename, "rb") as fp:
codec.load(fp)
with open("all_world_cups.png", "wb") as fp:
codec.dump(fp)
print(f"Created atlas from {len(world_cup_files)} World Cup layouts")
For Python integration:
from layoutc.codec import Codec
codec = Codec()
with open("src/layouts/NXL-World-Cup-2021.json", "rb") as fp:
codec.load(fp)
with open("world_cup_atlas.png", "wb") as fp:
codec.dump(fp)
print("Converted NXL World Cup 2021 layout to PNG atlas!")
Usage
Command-Line Interface
The layoutc command-line tool supports conversion between JSON, PNG, and TSV formats.
To encode multiple JSON layouts into a single PNG atlas:
# Combine 4 years of World Cup tournament layouts into one atlas
layoutc src/layouts/NXL-World-Cup-{2018,2019,2020,2021}.json world_cups_atlas.png
To decode a PNG atlas into a JSON layout:
# Convert back from PNG to JSON
layoutc world_cups_atlas.png decoded_layouts.json
To convert a layout to TSV format:
# Convert single layout to TSV
layoutc src/layouts/NXL-Barcelona-2019.json barcelona.tsv
Command Syntax
layoutc [input ...] [output]
The last argument is treated as the output file, and all preceding arguments are input files. Use - for stdin/stdout.
Examples using the 23 included tournament layouts:
# Process all 23 tournament layouts to stdout as TSV
layoutc src/layouts/*.json -
# Create atlas from subset of tournaments
layoutc src/layouts/NXL-*2021*.json tournaments_2021.png
# Convert first layout to different formats
layoutc src/layouts/NXL-Amsterdam-2019.json amsterdam.png
layoutc src/layouts/NXL-Amsterdam-2019.json amsterdam.tsv
# Pipe to other tools (all examples work with pbpaste | python)
layoutc src/layouts/*.json - | head -10 # Show first 10 lines
Options
--depth {254,127}: Set the color depth (default: 254).--pitch {762,381}: Set the pixel pitch in mm/px (default: 762).--from ENTITY: Set the input entity type (default: auto-detect).--into ENTITY: Set the output entity type (default: auto-detect).-v, --verbose: Show verbose traceback on error.
Format Detection
Input and output formats are automatically detected based on file extensions and content:
.jsonfiles are treated as JSON layouts.pngfiles are treated as PNG atlases.tsvfiles are treated as Tab-Separated Values- Use
--fromand--intooptions to override auto-detection
Python API
The layoutc library provides a Codec class for encoding and decoding spatial entities:
from layoutc.codec import Codec
from layoutc.entity import Entity
from layoutc import Unit
import glob
# Create a codec with default settings
codec = Codec()
# Load and convert layout files
with open("src/layouts/NXL-European-Champs-2021.json", "rb") as fp:
codec.load(fp)
with open("european_champs.png", "wb") as fp:
codec.dump(fp)
# Working with multiple layouts
codec.clear()
layout_files = glob.glob("src/layouts/NXL-*2022*.json")
for filename in layout_files:
with open(filename, "rb") as fp:
codec.load(fp)
with open("tournaments_2022.png", "wb") as fp:
codec.dump(fp)
print(f"Combined {len(layout_files)} 2022 tournament layouts into atlas!")
# Displaying entity information
codec.clear()
with open("src/layouts/NXL-Las-Vegas-2019.json", "rb") as fp:
codec.load(fp)
print("Las Vegas 2019 Layout entities:")
for entity_data in codec:
entity = Entity(*entity_data)
display_entity = entity.unfold(Unit.METER, Unit.DEGREE)
print(f"Bunker {entity.k} at ({display_entity.x:.1f}m, {display_entity.y:.1f}m, {display_entity.z:.0f}°)")
Advanced: Format-specific handling
Click to expand advanced usage details
Different file formats have different unit assumptions:
- JSON format: Coordinates in meters and degrees (tournament data format)
- TSV format: Coordinates in internal units (millimeters and arc minutes)
- PNG format: Stores data in internal units
from layoutc.entity import json as json_entity
from layoutc.codec import Codec
from layoutc.entity import Entity
from layoutc import Unit
import json
codec = Codec()
# Working directly with JSON data
with open("src/layouts/NXL-Prague-2019.json", "r") as f:
tournament_data = json.load(f)
first_bunker = tournament_data[0]
entity = json_entity.Entity.make(first_bunker)
codec.add(entity)
# Working with TSV/internal units
entity = Entity(x=1500, y=2000, z=5400) # 1.5m, 2m, 90° in internal units
codec.add(entity)
# Unit conversion
display_entity = entity @ Unit.METER @ Unit.DEGREE
print(f"Display units: x={display_entity.x}m, y={display_entity.y}m, z={display_entity.z}°")
internal_entity = Entity(x=1, y=1, z=90).fold(Unit.METER, Unit.DEGREE)
print(f"Internal units: x={internal_entity.x}mm, y={internal_entity.y}mm, z={internal_entity.z} arc-min")
The Entity class represents a spatial entity (bunker) with x, y, z coordinates and metadata attributes g (group), v (version), and k (kind/bunker ID).
Key concepts:
- JSON files: Store coordinates in meters and degrees (tournament standard)
- PNG atlases: Efficient binary storage format for multiple layouts
- TSV files: Tab-separated format for debugging and data analysis
- Automatic conversion: The library handles unit conversions between formats transparently
Practical Examples
Here are complete, copy-pasteable examples using the included tournament data:
Convert all tournaments to different formats:
from layoutc.codec import Codec
import glob
codec = Codec()
world_cup_files = glob.glob("src/layouts/NXL-World-Cup-*.json")
for filename in world_cup_files:
with open(filename, "rb") as fp:
codec.load(fp)
with open("world_cups_atlas.png", "wb") as fp:
codec.dump(fp)
print(f"Created atlas from {len(world_cup_files)} World Cup layouts")
Analyze tournament layout data:
import json
with open("src/layouts/NXL-Chicago-2019.json", "r") as f:
layout_data = json.load(f)
print(f"Chicago 2019 has {len(layout_data)} bunkers:")
for bunker in layout_data[:3]:
print(f" Bunker {bunker['bunkerID']}: ({bunker['xPosition']:.1f}m, {bunker['zPosition']:.1f}m, {bunker['yRotation']:.0f}°)")
Create atlas and convert back:
from layoutc.codec import Codec
# Round-trip conversion: JSON -> PNG -> JSON
codec = Codec()
with open("src/layouts/NXL-Barcelona-2019.json", "rb") as fp:
codec.load(fp)
with open("barcelona_atlas.png", "wb") as fp:
codec.dump(fp)
codec.clear()
with open("barcelona_atlas.png", "rb") as fp:
codec.load(fp)
with open("barcelona_restored.json", "wb") as fp:
codec.dump(fp)
# Save back as JSON
with open("barcelona_restored.json", "wb") as fp:
codec.dump(fp)
print("Successfully round-tripped Barcelona layout: JSON -> PNG -> JSON")
Technical Details
Internal representation and unit conversion
The system uses internal units (millimeters and arc minutes) for storage and computation:
- @ operator: Converts FROM internal units TO display units (e.g.,
entity @ Unit.METER @ Unit.DEGREE) - fold() method: Converts FROM display units TO internal units (e.g.,
.fold(Unit.METER, Unit.DEGREE)) - unfold() method: Like @ operator but also handles quadrants properly
The layoutc module also provides enums and constants for working with spatial units, quadrants, and dimensions:
Unit: Conversion factors (METER=1000, DEGREE=60, TURN=21600)Quadrant: Spatial quadrants (NE, NW, SW, SE)Pitch: Pixel resolution (LORES=762mm/px, HIRES=381mm/px)Depth: Color depth (LORES=127, HIRES=254)GVK: Group/Version/Kind attributes for entity classificationOrder: Atlas ordering for multi-layout collections
Extending layoutc
layoutc can be extended to support additional file formats.
First, create an appropriately-named module under layoutc.entity (ie. *.png is --from=layoutc.entity.png and *.json is --from=layoutc.entity.json). Then, create an Entity subclass in the module and implement its [auto]dump and [auto]load classmethods.
Unless --from or --into is used, layoutc.codec.Codec selects the most-appropriate entity class for each input or output file based on either its extension (dump) or its magic (load).
Development
This project uses Python >=3.10 and pip for dependency management and packaging.
To set up a development environment:
# Clone the repository
git clone https://github.com/infimalabs/layoutc.git
cd layoutc
# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install in development mode with dev dependencies
pip install -e '.[dev]'
# Run tests
pytest -v
# Try the examples with the included tournament data
layoutc src/layouts/*.json all_tournaments.png
layoutc src/layouts/NXL-World-Cup-2021.json world_cup.tsv
Quick development test:
from layoutc.codec import Codec
codec = Codec()
with open("src/layouts/NXL-Amsterdam-2019.json", "rb") as fp:
codec.load(fp)
print(f"Loaded {len(list(codec))} entities from Amsterdam 2019 layout")
for entity_data in list(codec)[:3]:
print(f" Entity: {entity_data}")
License
layoutc is released under the MIT License. See LICENSE for more information.
Troubleshooting
Common Issues
"No valid entities found in input files"
- Check that your JSON file contains valid layout data with
xPosition,zPosition,yRotation, andbunkerIDfields - Ensure PNG files contain non-zero alpha channel values (entities are stored in the alpha channel)
- Verify file format is supported (JSON, PNG, or TSV)
"Atlas limit exceeded: cannot create more than 256 layout groups"
layoutcsupports up to 256 separate layout groups in a single atlas- Split large collections into multiple smaller atlas files
- Consider combining similar layouts into single groups if appropriate
"X coordinate seems unusually large"
- JSON format expects coordinates in meters and rotations in degrees
- TSV format uses internal units (millimeters and arc minutes)
"Invalid PNG dimensions"
- PNG atlases must have specific aspect ratios: 5:4 (standard), 4:3 (large), or 1:1 (maximum)
- Supported resolutions depend on pitch setting (762 or 381 mm/pixel)
Format auto-detection issues
- Use
--fromand--intooptions to override automatic format detection - Ensure file extensions match content (.json for JSON, .png for PNG, .tsv for TSV)
Performance Tips
- Use PNG format for storage of large layout collections (more efficient than JSON)
- Higher pitch values (762mm/px) create smaller files but lower spatial resolution
- Lower depth values (127 colors) create smaller files but may reduce precision
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file layoutc-0.2.2.tar.gz.
File metadata
- Download URL: layoutc-0.2.2.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a21ff13e648475f5d138e2a85f47d5f420e4b8c73825ede23c1104a986f830c
|
|
| MD5 |
ff0861c4ac6f051855b0e9872a6c7d05
|
|
| BLAKE2b-256 |
1e96fe9ae828794ee0a2b4b8b3d52e147ed0957db53ae71a056bad7a6b95153c
|
File details
Details for the file layoutc-0.2.2-py3-none-any.whl.
File metadata
- Download URL: layoutc-0.2.2-py3-none-any.whl
- Upload date:
- Size: 16.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b5d109632d865ba53aed4cc4338789b32b70c39b08e5e565c5d4c0cde95ec2c
|
|
| MD5 |
dc654f2600ae447c38e89da0e15aac10
|
|
| BLAKE2b-256 |
798ecd895732f4b04aac6e0e20b3ae6400732b81254bd498a5961d632205c7e8
|