Skill144 estrellas del repoactualizado 3mo ago
spine-animation
>
Instalar en Claude Code
Copiargit clone https://github.com/GenielabsOpenSource/spine-animation-ai ~/.claude/skills/spine-animationDespués abre una sesión nueva de Claude Code; el skill carga automáticamente.
Definición
SKILL.md
<!-- ⚠️ AUTO-GENERATED FILE — DO NOT EDIT DIRECTLY
Edit SKILL.template.md instead, then push to trigger rebuild.
Scripts are embedded automatically by build_skill.py via GitHub Actions. -->
# Spine Animation Skill
Turn pre-existing 2D character assets into fully animated, interactive Spine animations.
## Step 0: Set Up Scripts
This skill includes Python scripts that do the heavy lifting. Claude MUST write them to disk
before use. Each script is embedded below — Claude should save them to `/home/claude/spine-scripts/`
at the start of every session.
```bash
mkdir -p /home/claude/spine-scripts
pip install opencv-python Pillow numpy google-generativeai --break-system-packages -q
```
### Embedded Scripts
The following scripts are auto-injected from the repository's `scripts/` directory.
**Claude: read these carefully, then write each one to `/home/claude/spine-scripts/`**
before running the pipeline.
<!-- EMBED:scripts/split_character.py -->
<details>
<summary>📄 <code>scripts/split_character.py</code> (231 lines)</summary>
```python
#!/usr/bin/env python3
"""
split_character.py — Generate a sprite-sheet atlas from a full character image
using Google Gemini image generation, then segment individual body parts via
OpenCV connected-components analysis.
Usage:
python split_character.py <input_image> [--output-dir output_parts]
[--atlas-out atlas.png] [--min-area 500] [--padding 12]
[--bg-threshold 240]
Requires:
pip install google-generativeai opencv-python Pillow numpy
Environment variable GEMINI_API_KEY must be set.
"""
import argparse
import os
import sys
import cv2
import numpy as np
from PIL import Image
def get_gemini_client():
"""Initialise the Gemini generative-AI client, or exit with a helpful
error if the API key is missing."""
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print(
"ERROR: GEMINI_API_KEY environment variable is not set.\n"
"Get a free API key at: https://aistudio.google.com/app/apikey\n"
"Then run:\n"
" export GEMINI_API_KEY=your_key_here",
file=sys.stderr,
)
sys.exit(1)
from google import genai
client = genai.Client(api_key=api_key)
return client
POSITIVE_PROMPT = (
"A complete 2D game sprite sheet texture atlas for Spine animation of the "
"exact character in the reference image. The character is completely "
"deconstructed into separated, isolated body parts. Separated individual "
"parts laid out flatly: isolated head, isolated torso, isolated upper arms, "
"lower arms, hands, upper legs, lower legs, and feet. Spread out with clear "
"space between every single body part. No overlapping parts. Clean solid "
"white background. CRITICAL: Maintain the exact same art style, exact same "
"shading, exact face, and exact color palette as the reference image. "
"Identical style match, 2D game asset, flat layout, character design sheet."
)
NEGATIVE_PROMPT = (
"3D, realistic, altered style, different art style, different face, "
"redesign, overlapping parts, connected limbs, full body standing, dynamic "
"pose, background scenery, shadows, gradients on background, messy layout, "
"missing limbs, merged layers, text, watermarks."
)
def generate_atlas(client, input_image_path: str, atlas_out: str) -> str:
"""Send the reference image to Gemini and save the generated atlas PNG."""
from google.genai import types
ref_image = Image.open(input_image_path)
response = client.models.generate_content(
model="gemini-3.1-flash-image-preview",
contents=[
POSITIVE_PROMPT,
f"Negative prompt: {NEGATIVE_PROMPT}",
ref_image,
],
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
),
)
# Extract the generated image from the response parts
for part in response.candidates[0].content.parts:
if part.inline_data is not None:
image_data = part.inline_data.data
with open(atlas_out, "wb") as f:
f.write(image_data)
return atlas_out
print("ERROR: Gemini did not return an image in its response.", file=sys.stderr)
sys.exit(1)
def segment_parts(
atlas_path: str,
output_dir: str,
min_area: int = 500,
padding: int = 12,
bg_threshold: int = 240,
) -> list[str]:
"""Detect individual parts in the atlas using connected-components analysis.
Returns a list of saved part file paths.
"""
img = cv2.imread(atlas_path, cv2.IMREAD_UNCHANGED)
if img is None:
print(f"ERROR: Could not read atlas image: {atlas_path}", file=sys.stderr)
sys.exit(1)
# Convert to RGBA if needed
if img.shape[2] == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# Build a foreground mask: pixels whose RGB channels are all below the
# background threshold are considered foreground.
bgr = img[:, :, :3]
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, bg_threshold, 255, cv2.THRESH_BINARY_INV)
# Connected-components analysis (8-connectivity)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(
mask, connectivity=8
)
os.makedirs(output_dir, exist_ok=True)
saved: list[str] = []
part_idx = 0
h_img, w_img = img.shape[:2]
for label_id in range(1, num_labels): # skip background (label 0)
area = stats[label_id, cv2.CC_STAT_AREA]
if area < min_area:
continue
x = stats[label_id, cv2.CC_STAT_LEFT]
y = stats[label_id, cv2.CC_STAT_TOP]
w = stats[label_id, cv2.CC_STAT_WIDTH]
h = stats[label_id, cv2.CC_STAT_HEIGHT]
# Apply padding (clamped to image bounds)
x1 = max(x - padding, 0)
y1 = max(y - padding, 0)
x2 = min(x + w + padding, w_img)
y2 = min(y +