Metadata-Version: 2.4
Name: pspine
Version: 0.1.0
Summary: Convert Photoshop PSD files to Spine 2D skeletons (JSON + atlas + page PNG).
Project-URL: Homepage, https://github.com/USER/pspine
Project-URL: Repository, https://github.com/USER/pspine
Project-URL: Issues, https://github.com/USER/pspine/issues
Author: pspine contributors
License: MIT
License-File: LICENSE
Keywords: 2d-animation,atlas,libgdx,photoshop,psd,skeleton,spine,spine2d
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: click>=8.1.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: psd-tools>=1.9.30
Requires-Dist: rich>=13.0.0
Description-Content-Type: text/markdown

# pspine

Convert Photoshop **PSD** files to **Spine 2D** skeletons (`.json` + `.atlas` + `.png`) — without opening Spine.

A reverse-engineered re-implementation of Spine's PSD importer. Verified against Spine 4.2.43 ground truth: byte-identical skeleton JSON (modulo `hash`) and pixel-identical per-layer regions; atlas page layout is structurally equivalent (different shelf-packer geometry — Spine doesn't care).

## Quick start

```sh
uvx pspine my-symbol.psd
```

Writes `my-symbol.json`, `my-symbol.atlas`, `my-symbol.png` next to the input.

## Usage

```sh
pspine [OPTIONS] PSD_FILES...
```

### Options

| Flag | Effect |
|------|--------|
| `-o`, `--output DIR` | Write outputs to `DIR` instead of next to each PSD. |
| `-n`, `--name NAME` | Base filename (no extension) for outputs. Single input only. |
| `-q`, `--quiet` | Suppress progress; errors still print to stderr. |
| `-v`, `--verbose` | Print extra detail per step (canvas size, every layer name, atlas shape). |
| `--no-color` | Disable ANSI colors. |
| `-h`, `--help` | Show help. |
| `--version` | Show version. |

Output is colorful by default and downgrades automatically when stdout isn't a TTY.

### Examples

```sh
# Same folder as PSD (default)
pspine input.psd

# Specific output folder
pspine input.psd -o build/spine/

# Multiple PSDs into one folder
pspine assets/*.psd -o build/spine/

# Custom base name
pspine input.psd -n symbols-blue

# Verbose run
pspine input.psd -v

# Pipeline-friendly (no colors, no progress)
pspine input.psd --quiet --no-color
```

## Layer name conventions

`pspine` mirrors Spine's PSD importer rules:

- Each visible **leaf pixel layer** becomes one slot + one attachment with the layer's literal name.
- Layer names containing **`[ignore]`** are skipped.
- All slot names within the skeleton must be **unique**. Duplicates after the first are skipped with a yellow `⚠` warning.

> Group structure is currently flattened (no `[skin]` / `[bone]` / `[slot]` group-tag handling). If your PSD has duplicate leaf names across groups (a common pattern in slot-machine sheets), either rename the duplicates or `[ignore]` the noise layers. Multi-skin support is on the roadmap; see [`docs/reverse-engineering.md`](docs/reverse-engineering.md) for the full Spine importer spec.

## Output

For an input named `foo.psd`:

| File | Contents |
|------|----------|
| `foo.json` | Spine skeleton: one root bone, one slot per layer, one default skin. Spine version `4.2.43`. |
| `foo.atlas` | libgdx atlas: `pma:true`, `filter:Linear,Linear`, one region per slot. |
| `foo.png` | Packed atlas page (RGBA, premultiplied). |

Each region carries 1 px transparent edge padding so bilinear filtering can't sample neighbours.

## Library use

```python
from pspine import convert

result = convert("input.psd", out_dir="build/spine", base_name="symbols")
print(result.json_path, result.atlas_path, result.page_path)
print(f"{len(result.skeleton.slots)} slots")
```

A progress callback exposes the same events the CLI prints:

```python
def on_event(event: str, payload: dict) -> None:
    print(event, payload)

convert("input.psd", on_progress=on_event)
```

## Install

```sh
# One-shot run (no install)
uvx pspine ...

# Install globally as a tool
uv tool install pspine
# or
pipx install pspine

# As a library dependency
uv add pspine
# or
pip install pspine
```

## Development

```sh
git clone https://github.com/USER/pspine
cd pspine
uv sync
uv run pspine --help
```

### Publishing

The package is published to PyPI with a token. From a clean checkout:

```sh
uv build                               # produces dist/*.whl and dist/*.tar.gz
UV_PUBLISH_TOKEN=pypi-... uv publish   # uploads dist/*
```

To bump the version, edit `__version__` in [`src/pspine/__init__.py`](src/pspine/__init__.py); `pyproject.toml` reads it dynamically.

## License

MIT. See [LICENSE](LICENSE).
