Extend AI image tooling with variants; drop dev asset folders from git
asset_pipeline generate gains --variants N (saved as <stem>-1..N.png). .gitignore now excludes assets/models/concepts, assets/models/npc, assets/textures/npc/ai, and raw assets/textures/ai outputs; only game_ready and the game-facing NPC maps/models stay tracked.
No files matched your search
@@ -44,3 +44,10 @@ user://
|
|||||||
build/
|
build/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# AI / asset-development outputs (regenerated by the tools; not shipped in the game)
|
||||||
|
assets/models/concepts/
|
||||||
|
assets/models/npc/
|
||||||
|
assets/textures/npc/ai/
|
||||||
|
assets/textures/ai/*
|
||||||
|
!assets/textures/ai/game_ready/
|
||||||
@@ -46,6 +46,16 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
|
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
|
||||||
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
|
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
|
||||||
|
|
||||||
|
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
|
||||||
|
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
|
||||||
|
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
|
||||||
|
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
|
||||||
|
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
|
||||||
|
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
|
||||||
|
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
|
||||||
|
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
|
||||||
|
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
|
||||||
|
|
||||||
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
|
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
|
||||||
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
|
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
|
||||||
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
|
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 856 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 929 KiB |
|
Before Width: | Height: | Size: 671 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 578 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 568 KiB |
|
Before Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 675 KiB |
|
Before Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 441 KiB |
|
Before Width: | Height: | Size: 801 KiB |
|
Before Width: | Height: | Size: 510 KiB |
|
Before Width: | Height: | Size: 465 KiB |
|
Before Width: | Height: | Size: 463 KiB |
|
Before Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 597 KiB |
|
Before Width: | Height: | Size: 452 KiB |
@@ -258,6 +258,13 @@ def image_paths(values):
|
|||||||
return sorted(set(found))
|
return sorted(set(found))
|
||||||
|
|
||||||
|
|
||||||
|
def _variant_output(output: Path, index: int, count: int) -> Path:
|
||||||
|
"""Suffix the output path for alternative images (N > 1 only)."""
|
||||||
|
if count <= 1:
|
||||||
|
return output
|
||||||
|
return output.with_name(f"{output.stem}-{index}{output.suffix}")
|
||||||
|
|
||||||
|
|
||||||
def _generate(args):
|
def _generate(args):
|
||||||
profile = PROFILES[args.role]
|
profile = PROFILES[args.role]
|
||||||
if (args.width is None) != (args.height is None):
|
if (args.width is None) != (args.height is None):
|
||||||
@@ -267,21 +274,24 @@ def _generate(args):
|
|||||||
transparent = args.transparent if args.transparent is not None else profile.get(
|
transparent = args.transparent if args.transparent is not None else profile.get(
|
||||||
"alpha") is True
|
"alpha") is True
|
||||||
prompt = f"{args.prompt.strip()}. {ROLE_GUIDANCE[args.role]}."
|
prompt = f"{args.prompt.strip()}. {ROLE_GUIDANCE[args.role]}."
|
||||||
image = generate_textures.generate_image(
|
count = max(1, int(getattr(args, "variants", 1) or 1))
|
||||||
prompt, args.model, args.request_size, transparent,
|
output = resolve_path(args.output)
|
||||||
aspect_ratio=API_ASPECTS[args.role])
|
for index in range(1, count + 1):
|
||||||
foreground = getattr(args, "foreground", None)
|
image = generate_textures.generate_image(
|
||||||
if foreground:
|
prompt, args.model, args.request_size, transparent,
|
||||||
image = colorize_alpha(image, foreground)
|
aspect_ratio=API_ASPECTS[args.role])
|
||||||
if args.role == "icon" and has_alpha(image):
|
foreground = getattr(args, "foreground", None)
|
||||||
image = resize_image_from_image(
|
if foreground:
|
||||||
image, width, height, "contain", (0, 0, 0, 0))
|
image = colorize_alpha(image, foreground)
|
||||||
else:
|
if args.role == "icon" and has_alpha(image):
|
||||||
image = ImageOps.fit(image, (width, height),
|
image = resize_image_from_image(
|
||||||
method=Image.Resampling.LANCZOS)
|
image, width, height, "contain", (0, 0, 0, 0))
|
||||||
if args.role == "tile":
|
else:
|
||||||
image = make_seamless(image)
|
image = ImageOps.fit(image, (width, height),
|
||||||
save_image(image, resolve_path(args.output))
|
method=Image.Resampling.LANCZOS)
|
||||||
|
if args.role == "tile":
|
||||||
|
image = make_seamless(image)
|
||||||
|
save_image(image, _variant_output(output, index, count))
|
||||||
|
|
||||||
|
|
||||||
def resize_image_from_image(image, width, height, fit, background):
|
def resize_image_from_image(image, width, height, fit, background):
|
||||||
@@ -365,6 +375,9 @@ def build_parser():
|
|||||||
default=generate_textures.GEN_SIZE)
|
default=generate_textures.GEN_SIZE)
|
||||||
generate.add_argument("--width", type=int)
|
generate.add_argument("--width", type=int)
|
||||||
generate.add_argument("--height", type=int)
|
generate.add_argument("--height", type=int)
|
||||||
|
generate.add_argument("--variants", type=int, default=1,
|
||||||
|
help="generate N alternative images; saved as "
|
||||||
|
"<stem>-1.png ... <stem>-N.png when N > 1")
|
||||||
generate.add_argument(
|
generate.add_argument(
|
||||||
"--foreground", type=parse_color,
|
"--foreground", type=parse_color,
|
||||||
help="replace visible RGB with this color while preserving alpha")
|
help="replace visible RGB with this color while preserving alpha")
|
||||||
|
|||||||