godot-visual-mcp 1.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. godot_visual_mcp-1.5.0/LICENSE +21 -0
  2. godot_visual_mcp-1.5.0/PKG-INFO +168 -0
  3. godot_visual_mcp-1.5.0/README.md +116 -0
  4. godot_visual_mcp-1.5.0/pyproject.toml +69 -0
  5. godot_visual_mcp-1.5.0/setup.cfg +4 -0
  6. godot_visual_mcp-1.5.0/src/godot_visual_mcp/__init__.py +3 -0
  7. godot_visual_mcp-1.5.0/src/godot_visual_mcp/aseprite.py +43 -0
  8. godot_visual_mcp-1.5.0/src/godot_visual_mcp/assets.py +127 -0
  9. godot_visual_mcp-1.5.0/src/godot_visual_mcp/comfyui.py +372 -0
  10. godot_visual_mcp-1.5.0/src/godot_visual_mcp/extended_assets.py +176 -0
  11. godot_visual_mcp-1.5.0/src/godot_visual_mcp/filesystem.py +88 -0
  12. godot_visual_mcp-1.5.0/src/godot_visual_mcp/godot.py +156 -0
  13. godot_visual_mcp-1.5.0/src/godot_visual_mcp/history.py +59 -0
  14. godot_visual_mcp-1.5.0/src/godot_visual_mcp/limits.py +74 -0
  15. godot_visual_mcp-1.5.0/src/godot_visual_mcp/processing.py +190 -0
  16. godot_visual_mcp-1.5.0/src/godot_visual_mcp/projects.py +82 -0
  17. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/__init__.py +1 -0
  18. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/palettes/custom.json +4 -0
  19. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/palettes/gameboy.json +4 -0
  20. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/palettes/pico8.json +4 -0
  21. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/workflows/background.json +10 -0
  22. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/workflows/character.json +10 -0
  23. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/workflows/icon.json +10 -0
  24. godot_visual_mcp-1.5.0/src/godot_visual_mcp/resources/workflows/sprite.json +10 -0
  25. godot_visual_mcp-1.5.0/src/godot_visual_mcp/scenes.py +102 -0
  26. godot_visual_mcp-1.5.0/src/godot_visual_mcp/visual.py +182 -0
  27. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/PKG-INFO +168 -0
  28. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/SOURCES.txt +41 -0
  29. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/dependency_links.txt +1 -0
  30. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/entry_points.txt +2 -0
  31. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/requires.txt +12 -0
  32. godot_visual_mcp-1.5.0/src/godot_visual_mcp.egg-info/top_level.txt +2 -0
  33. godot_visual_mcp-1.5.0/src/server/__init__.py +2 -0
  34. godot_visual_mcp-1.5.0/src/server/main.py +819 -0
  35. godot_visual_mcp-1.5.0/tests/test_aseprite_history.py +29 -0
  36. godot_visual_mcp-1.5.0/tests/test_assets.py +40 -0
  37. godot_visual_mcp-1.5.0/tests/test_comfyui.py +98 -0
  38. godot_visual_mcp-1.5.0/tests/test_filesystem.py +37 -0
  39. godot_visual_mcp-1.5.0/tests/test_godot.py +50 -0
  40. godot_visual_mcp-1.5.0/tests/test_limits.py +21 -0
  41. godot_visual_mcp-1.5.0/tests/test_processing.py +52 -0
  42. godot_visual_mcp-1.5.0/tests/test_projects_extended_assets.py +46 -0
  43. godot_visual_mcp-1.5.0/tests/test_visual.py +38 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kta41
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: godot-visual-mcp
3
+ Version: 1.5.0
4
+ Summary: A secure, offline-first MCP server for Godot asset prototyping.
5
+ Author: Kta41
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 kta41
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/kta41/godot-visual-mcp
29
+ Project-URL: Repository, https://github.com/kta41/godot-visual-mcp
30
+ Project-URL: Issues, https://github.com/kta41/godot-visual-mcp/issues
31
+ Keywords: godot,mcp,pixel-art,asset-pipeline
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.11
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Requires-Python: >=3.11
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: fastmcp<4.0,>=3.2.0
42
+ Requires-Dist: Pillow<13.0,>=12.3.0
43
+ Requires-Dist: httpx<1.0,>=0.27
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
46
+ Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "dev"
47
+ Requires-Dist: ruff<1.0,>=0.8; extra == "dev"
48
+ Requires-Dist: mypy<2.0,>=1.13; extra == "dev"
49
+ Provides-Extra: ai
50
+ Requires-Dist: rembg<3.0,>=2.0; extra == "ai"
51
+ Dynamic: license-file
52
+
53
+ <div align="center">
54
+
55
+ # godot-visual-mcp
56
+
57
+ > Secure, offline-first asset tools for Godot exposed through a Model Context Protocol (MCP) server.
58
+
59
+ ![Python](https://img.shields.io/badge/python-3.11+-blue.svg) ![Godot](https://img.shields.io/badge/Godot-4.x-478CBF?logo=godotengine&logoColor=white) ![Protocol](https://img.shields.io/badge/Protocol-MCP-8A2BE2) ![License](https://img.shields.io/badge/license-MIT-green.svg)
60
+
61
+ Empower your LLM agents (Cline, Roo Code, Copilot) to inspect, generate, and transform visual assets directly within your Godot Engine project. Engineered with strict filesystem sandboxing, this server securely translates agent reasoning into your game's `res://` pipeline.
62
+ </div>
63
+
64
+ ## 🚀 Quickstart
65
+
66
+ Requires Python 3.11+. The core profile runs entirely offline and has no heavy AI or GPU dependencies.
67
+
68
+ The project uses `uv.lock` for reproducible environments. Configurable safety
69
+ limits include `MCP_MAX_INPUT_BYTES`, `MCP_MAX_PIXELS`, `MCP_MAX_FRAMES`,
70
+ `MCP_MAX_BATCH`, `MCP_MAX_OPERATION_SECONDS`, and `MCP_MAX_PROJECT_BYTES`.
71
+
72
+ ```bash
73
+ # Install core tools and development dependencies
74
+ uv sync
75
+
76
+ # Run the server (requires Godot project path)
77
+ GODOT_PROJECT_ROOT=/path/to/my-godot-project uv run python -m server.main
78
+ ```
79
+
80
+ ### Client Configuration (stdio)
81
+ Configure your MCP client to launch the server via `stdio`. Every asset path provided by the agent is automatically interpreted as relative to the `res://` directory and resolved against your configured `GODOT_PROJECT_ROOT`.
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "godot-visual-mcp": {
87
+ "command": "uv",
88
+ "args": ["run", "--no-dev", "python", "-m", "server.main"],
89
+ "env": {
90
+ "GODOT_PROJECT_ROOT": "/path/to/my-godot-project"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## 🧰 Available Tools
98
+
99
+ All tool responses use a standard envelope format (`status` / `data` / `warnings` / `errors`) ensuring LLM agents never receive raw, unhandled stack traces.
100
+
101
+ **Core Tools (v0.2):**
102
+ * **Inspection:** `inspect_asset`, `list_assets`, `validate_asset`
103
+ * **Prototyping:** `create_placeholder`, `generate_spritesheet` (assembled offline from equal-sized frames)
104
+ * **Palette Engine:** `apply_palette`, `list_palettes` (ships with Game Boy, PICO-8, and custom JSON palettes. Supports RGB/LAB distance matching while preserving alpha channels).
105
+
106
+ **Generative AI Tools (v0.3):**
107
+ * **Generation & Cleaning:** `generate_asset`, `remove_background`
108
+
109
+ ## 🧠 ComfyUI Integration (Optional)
110
+
111
+ The core installation intentionally omits heavy AI dependencies. To enable generative workflows and background removal, install the `[ai]` profile:
112
+
113
+ ```bash
114
+ uv sync --extra ai
115
+ ```
116
+
117
+ Configure your local ComfyUI instance via environment variables or pass the endpoint directly to the `generate_asset` tool:
118
+ `COMFYUI_ENDPOINT=http://127.0.0.1:8188`
119
+
120
+ **How generation works:**
121
+ 1. The tool submits a local JSON workflow (located in `workflows/`).
122
+ 2. Prompts, dimensions, seeds, and batch sizes are injected dynamically (credentials are never stored).
123
+ 3. The MCP polls without fixed completion assumptions.
124
+ 4. The output is retrieved, background is removed (if requested), transparent borders are cropped, and the final asset is written safely to `res://`.
125
+ 5. *Note: CPU-only machines can use ComfyUI's CPU backend; this project does not strictly require CUDA.*
126
+
127
+ ## 🛡️ Security Model
128
+
129
+ Security and directory integrity are core design principles:
130
+ * **Strict Sandboxing:** All operations are strictly bound to the configured `GODOT_PROJECT_ROOT`.
131
+ * **Path Validation:** Absolute paths, parent directory traversal (`../`), and symlink escapes are aggressively rejected.
132
+ * **Non-Destructive by Default:** Existing files are never overwritten unless a tool explicitly receives the `overwrite=true` parameter from the agent.
133
+
134
+ ## 🐳 Docker Deployment
135
+
136
+ The project includes an optional `Dockerfile` and `compose.yaml` to provide an isolated core container and an opt-in ComfyUI profile. The default core container has no GPU or AI runtime requirements, keeping the footprint minimal.
137
+
138
+ ## 📦 Releases
139
+
140
+ Releases are created by pushing a tag that matches the package version, for
141
+ example:
142
+
143
+ ```bash
144
+ git tag v1.5.0
145
+ git push origin v1.5.0
146
+ ```
147
+
148
+ The release workflow builds the wheel and source distribution with `uv`,
149
+ validates installation in Python 3.11, generates SHA-256 checksums and an SPDX
150
+ SBOM, publishes the package to PyPI through trusted publishing, and publishes
151
+ the container to GHCR. Configure a PyPI trusted publisher for the
152
+ `pypi` environment before using the workflow.
153
+
154
+ ## 🛠️ Development & Testing
155
+
156
+ Run the test suite and code quality checks using standard Python tooling:
157
+
158
+ ```bash
159
+ # Run unit tests
160
+ uv run pytest
161
+
162
+ # Run linter
163
+ uv run ruff check .
164
+ ```
165
+
166
+ ---
167
+
168
+ **Documentation:** [Security Model](SECURITY.md) | [Tool Reference](TOOL_REFERENCE.md) | [Changelog](CHANGELOG.md)
@@ -0,0 +1,116 @@
1
+ <div align="center">
2
+
3
+ # godot-visual-mcp
4
+
5
+ > Secure, offline-first asset tools for Godot exposed through a Model Context Protocol (MCP) server.
6
+
7
+ ![Python](https://img.shields.io/badge/python-3.11+-blue.svg) ![Godot](https://img.shields.io/badge/Godot-4.x-478CBF?logo=godotengine&logoColor=white) ![Protocol](https://img.shields.io/badge/Protocol-MCP-8A2BE2) ![License](https://img.shields.io/badge/license-MIT-green.svg)
8
+
9
+ Empower your LLM agents (Cline, Roo Code, Copilot) to inspect, generate, and transform visual assets directly within your Godot Engine project. Engineered with strict filesystem sandboxing, this server securely translates agent reasoning into your game's `res://` pipeline.
10
+ </div>
11
+
12
+ ## 🚀 Quickstart
13
+
14
+ Requires Python 3.11+. The core profile runs entirely offline and has no heavy AI or GPU dependencies.
15
+
16
+ The project uses `uv.lock` for reproducible environments. Configurable safety
17
+ limits include `MCP_MAX_INPUT_BYTES`, `MCP_MAX_PIXELS`, `MCP_MAX_FRAMES`,
18
+ `MCP_MAX_BATCH`, `MCP_MAX_OPERATION_SECONDS`, and `MCP_MAX_PROJECT_BYTES`.
19
+
20
+ ```bash
21
+ # Install core tools and development dependencies
22
+ uv sync
23
+
24
+ # Run the server (requires Godot project path)
25
+ GODOT_PROJECT_ROOT=/path/to/my-godot-project uv run python -m server.main
26
+ ```
27
+
28
+ ### Client Configuration (stdio)
29
+ Configure your MCP client to launch the server via `stdio`. Every asset path provided by the agent is automatically interpreted as relative to the `res://` directory and resolved against your configured `GODOT_PROJECT_ROOT`.
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "godot-visual-mcp": {
35
+ "command": "uv",
36
+ "args": ["run", "--no-dev", "python", "-m", "server.main"],
37
+ "env": {
38
+ "GODOT_PROJECT_ROOT": "/path/to/my-godot-project"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## 🧰 Available Tools
46
+
47
+ All tool responses use a standard envelope format (`status` / `data` / `warnings` / `errors`) ensuring LLM agents never receive raw, unhandled stack traces.
48
+
49
+ **Core Tools (v0.2):**
50
+ * **Inspection:** `inspect_asset`, `list_assets`, `validate_asset`
51
+ * **Prototyping:** `create_placeholder`, `generate_spritesheet` (assembled offline from equal-sized frames)
52
+ * **Palette Engine:** `apply_palette`, `list_palettes` (ships with Game Boy, PICO-8, and custom JSON palettes. Supports RGB/LAB distance matching while preserving alpha channels).
53
+
54
+ **Generative AI Tools (v0.3):**
55
+ * **Generation & Cleaning:** `generate_asset`, `remove_background`
56
+
57
+ ## 🧠 ComfyUI Integration (Optional)
58
+
59
+ The core installation intentionally omits heavy AI dependencies. To enable generative workflows and background removal, install the `[ai]` profile:
60
+
61
+ ```bash
62
+ uv sync --extra ai
63
+ ```
64
+
65
+ Configure your local ComfyUI instance via environment variables or pass the endpoint directly to the `generate_asset` tool:
66
+ `COMFYUI_ENDPOINT=http://127.0.0.1:8188`
67
+
68
+ **How generation works:**
69
+ 1. The tool submits a local JSON workflow (located in `workflows/`).
70
+ 2. Prompts, dimensions, seeds, and batch sizes are injected dynamically (credentials are never stored).
71
+ 3. The MCP polls without fixed completion assumptions.
72
+ 4. The output is retrieved, background is removed (if requested), transparent borders are cropped, and the final asset is written safely to `res://`.
73
+ 5. *Note: CPU-only machines can use ComfyUI's CPU backend; this project does not strictly require CUDA.*
74
+
75
+ ## 🛡️ Security Model
76
+
77
+ Security and directory integrity are core design principles:
78
+ * **Strict Sandboxing:** All operations are strictly bound to the configured `GODOT_PROJECT_ROOT`.
79
+ * **Path Validation:** Absolute paths, parent directory traversal (`../`), and symlink escapes are aggressively rejected.
80
+ * **Non-Destructive by Default:** Existing files are never overwritten unless a tool explicitly receives the `overwrite=true` parameter from the agent.
81
+
82
+ ## 🐳 Docker Deployment
83
+
84
+ The project includes an optional `Dockerfile` and `compose.yaml` to provide an isolated core container and an opt-in ComfyUI profile. The default core container has no GPU or AI runtime requirements, keeping the footprint minimal.
85
+
86
+ ## 📦 Releases
87
+
88
+ Releases are created by pushing a tag that matches the package version, for
89
+ example:
90
+
91
+ ```bash
92
+ git tag v1.5.0
93
+ git push origin v1.5.0
94
+ ```
95
+
96
+ The release workflow builds the wheel and source distribution with `uv`,
97
+ validates installation in Python 3.11, generates SHA-256 checksums and an SPDX
98
+ SBOM, publishes the package to PyPI through trusted publishing, and publishes
99
+ the container to GHCR. Configure a PyPI trusted publisher for the
100
+ `pypi` environment before using the workflow.
101
+
102
+ ## 🛠️ Development & Testing
103
+
104
+ Run the test suite and code quality checks using standard Python tooling:
105
+
106
+ ```bash
107
+ # Run unit tests
108
+ uv run pytest
109
+
110
+ # Run linter
111
+ uv run ruff check .
112
+ ```
113
+
114
+ ---
115
+
116
+ **Documentation:** [Security Model](SECURITY.md) | [Tool Reference](TOOL_REFERENCE.md) | [Changelog](CHANGELOG.md)
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "godot-visual-mcp"
3
+ version = "1.5.0"
4
+ description = "A secure, offline-first MCP server for Godot asset prototyping."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { file = "LICENSE" }
8
+ authors = [{ name = "Kta41" }]
9
+ keywords = ["godot", "mcp", "pixel-art", "asset-pipeline"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ ]
18
+ dependencies = [
19
+ "fastmcp>=3.2.0,<4.0",
20
+ "Pillow>=12.3.0,<13.0",
21
+ "httpx>=0.27,<1.0",
22
+ ]
23
+
24
+ [build-system]
25
+ requires = ["setuptools>=68"]
26
+ build-backend = "setuptools.build_meta"
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=8.0,<9.0",
31
+ "pytest-asyncio>=0.23,<1.0",
32
+ "ruff>=0.8,<1.0",
33
+ "mypy>=1.13,<2.0",
34
+ ]
35
+ ai = [
36
+ "rembg>=2.0,<3.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/kta41/godot-visual-mcp"
41
+ Repository = "https://github.com/kta41/godot-visual-mcp"
42
+ Issues = "https://github.com/kta41/godot-visual-mcp/issues"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "pytest>=8.0,<9.0",
47
+ "pytest-asyncio>=0.23,<1.0",
48
+ "ruff>=0.8,<1.0",
49
+ "mypy>=1.13,<2.0",
50
+ ]
51
+
52
+ [project.scripts]
53
+ godot-visual-mcp = "server.main:main"
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ pythonpath = ["src"]
58
+
59
+ [tool.ruff]
60
+ line-length = 100
61
+ target-version = "py311"
62
+
63
+ [tool.mypy]
64
+ python_version = "3.11"
65
+ strict = true
66
+ ignore_missing_imports = true
67
+
68
+ [tool.setuptools.package-data]
69
+ godot_visual_mcp = ["resources/palettes/*.json", "resources/workflows/*.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Secure Godot asset tools exposed through MCP."""
2
+
3
+ __version__ = "1.5.0"
@@ -0,0 +1,43 @@
1
+ """Aseprite JSON metadata and animation import helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from .filesystem import GodotProject
10
+
11
+
12
+ def inspect_aseprite_json(project: GodotProject, source: str) -> dict[str, Any]:
13
+ """Inspect a `.json` export produced by Aseprite."""
14
+ if Path(source).suffix.lower() != ".json":
15
+ raise ValueError("Aseprite metadata must be a .json file")
16
+ try:
17
+ payload = json.loads(project.safe_read(source).decode("utf-8"))
18
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
19
+ raise ValueError(f"Invalid Aseprite JSON: {source}") from exc
20
+ frames = payload.get("frames")
21
+ if isinstance(frames, dict):
22
+ frame_names = list(frames)
23
+ elif isinstance(frames, list):
24
+ frame_names = [str(item.get("filename", index)) for index, item in enumerate(frames)]
25
+ else:
26
+ raise TypeError("Aseprite JSON has no frames")
27
+ tags = payload.get("meta", {}).get("frameTags", [])
28
+ if not isinstance(tags, list):
29
+ raise TypeError("Aseprite frameTags must be a list")
30
+ animations = [
31
+ {"name": tag.get("name"), "from": tag.get("from"), "to": tag.get("to"), "direction": tag.get("direction", "forward")}
32
+ for tag in tags
33
+ if isinstance(tag, dict) and isinstance(tag.get("name"), str)
34
+ ]
35
+ return {"path": project.validate_asset_path(source), "frames": len(frame_names), "frame_names": frame_names, "animations": animations}
36
+
37
+
38
+ def create_animation_manifest(project: GodotProject, source: str, output: str, *, overwrite: bool = False) -> dict[str, Any]:
39
+ """Convert Aseprite frame tags into a stable JSON manifest for later import."""
40
+ metadata = inspect_aseprite_json(project, source)
41
+ content = json.dumps({"source": metadata["path"], "animations": metadata["animations"]}, indent=2) + "\n"
42
+ written = project.safe_write(output, content.encode("utf-8"), overwrite=overwrite)
43
+ return {"path": written, "animations": metadata["animations"], "frames": metadata["frames"]}
@@ -0,0 +1,127 @@
1
+ """Image inspection, validation, and deterministic placeholder generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ from typing import Any
7
+
8
+ from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError
9
+
10
+ from .filesystem import GodotProject
11
+ from .limits import ensure_image_dimensions, ensure_input_size
12
+
13
+ SUPPORTED_FORMATS = {"PNG", "JPEG", "WEBP", "BMP", "GIF", "TIFF"}
14
+
15
+
16
+ def _frame_count(width: int, height: int) -> int:
17
+ """Estimate uniform horizontal/vertical sprite frames from image geometry."""
18
+ if width >= height * 2 and width % height == 0:
19
+ return width // height
20
+ if height >= width * 2 and height % width == 0:
21
+ return height // width
22
+ return 1
23
+
24
+
25
+ def inspect_asset(project: GodotProject, path: str) -> dict[str, Any]:
26
+ """Return compact metadata suitable for an agent."""
27
+ data = project.safe_read(path)
28
+ ensure_input_size(len(data))
29
+ try:
30
+ with Image.open(io.BytesIO(data)) as image:
31
+ ensure_image_dimensions(image.width, image.height)
32
+ has_alpha = "A" in image.getbands() or "transparency" in image.info
33
+ return {
34
+ "path": project.validate_asset_path(path),
35
+ "format": image.format,
36
+ "width": image.width,
37
+ "height": image.height,
38
+ "has_alpha": has_alpha,
39
+ "frames": getattr(image, "n_frames", 1),
40
+ "sprite_frames": _frame_count(image.width, image.height),
41
+ "color_mode": image.mode,
42
+ }
43
+ except (UnidentifiedImageError, OSError) as exc:
44
+ raise ValueError(f"Unsupported or corrupt image: {path}") from exc
45
+
46
+
47
+ def list_assets(project: GodotProject, extension: str | None = None) -> list[str]:
48
+ """List project assets, optionally filtered by extension."""
49
+ assets = project.iter_assets()
50
+ if extension:
51
+ suffix = extension.lower()
52
+ if not suffix.startswith("."):
53
+ suffix = "." + suffix
54
+ assets = [path for path in assets if path.lower().endswith(suffix)]
55
+ return assets
56
+
57
+
58
+ def validate_asset(project: GodotProject, path: str) -> dict[str, Any]:
59
+ """Check that an image is readable and provide actionable diagnostics."""
60
+ try:
61
+ metadata = inspect_asset(project, path)
62
+ except (FileNotFoundError, ValueError) as exc:
63
+ return {"valid": False, "path": project.validate_asset_path(path), "errors": [str(exc)]}
64
+ errors: list[str] = []
65
+ if metadata["format"] not in SUPPORTED_FORMATS:
66
+ errors.append(f"Format {metadata['format']} is not supported")
67
+ if metadata["width"] <= 0 or metadata["height"] <= 0:
68
+ errors.append("Image dimensions must be positive")
69
+ return {
70
+ "valid": not errors,
71
+ "path": metadata["path"],
72
+ "errors": errors,
73
+ "metadata": metadata,
74
+ }
75
+
76
+
77
+ def verify_asset(project: GodotProject, path: str) -> dict[str, Any]:
78
+ """Verify that an output is present and import-ready without reading `.import` files."""
79
+ validation = validate_asset(project, path)
80
+ if not validation["valid"]:
81
+ return {
82
+ "ready": False,
83
+ "path": validation["path"],
84
+ "import_verification": "failed_validation",
85
+ "validation": validation,
86
+ }
87
+ return {
88
+ "ready": True,
89
+ "path": validation["path"],
90
+ "import_verification": "readable_and_supported",
91
+ "validation": validation,
92
+ }
93
+
94
+
95
+ def create_placeholder(
96
+ project: GodotProject,
97
+ output: str,
98
+ width: int,
99
+ height: int,
100
+ frames: int = 1,
101
+ label: str = "PLACEHOLDER",
102
+ *,
103
+ overwrite: bool = False,
104
+ ) -> dict[str, Any]:
105
+ """Create a labeled, deterministic RGBA placeholder spritesheet."""
106
+ if width <= 0 or height <= 0 or frames <= 0:
107
+ raise ValueError("width, height, and frames must be positive integers")
108
+ if frames > 128:
109
+ raise ValueError("frames must be 128 or fewer")
110
+ image = Image.new("RGBA", (width * frames, height), (38, 42, 54, 255))
111
+ draw = ImageDraw.Draw(image)
112
+ font = ImageFont.load_default()
113
+ for index in range(frames):
114
+ left = index * width
115
+ draw.rectangle((left, 0, left + width - 1, height - 1), outline=(235, 220, 120, 255), width=2)
116
+ text = label[:32]
117
+ bounds = draw.textbbox((0, 0), text, font=font)
118
+ draw.text(
119
+ (left + (width - (bounds[2] - bounds[0])) / 2, (height - (bounds[3] - bounds[1])) / 2),
120
+ text,
121
+ fill=(235, 220, 120, 255),
122
+ font=font,
123
+ )
124
+ buffer = io.BytesIO()
125
+ image.save(buffer, format="PNG", optimize=False)
126
+ written = project.safe_write(output, buffer.getvalue(), overwrite=overwrite)
127
+ return {"path": written, "width": width, "height": height, "frames": frames, "has_alpha": True}