refactor: Move assets to correct categories (fence→environment, chest→items, campfire already in workstations)
This commit is contained in:
183
scripts/generate_and_process.py
Normal file
183
scripts/generate_and_process.py
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
INTEGRATED GRAVITY GENERATION + BACKGROUND REMOVAL
|
||||
DolinaSmrti Asset Production Pipeline
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path("/Users/davidkotnik/repos/novafarma")
|
||||
ASSETS_DIR = BASE_DIR / "assets" / "images"
|
||||
BRAIN_DIR = Path("/Users/davidkotnik/.gemini/antigravity/brain/97fc5700-8b7e-46b1-b80b-c003e42edd7e")
|
||||
|
||||
# Generate + Remove workflow
|
||||
WORKFLOW_STEPS = """
|
||||
WORKFLOW:
|
||||
1. Generate image with Gravity (white background)
|
||||
2. Copy to assets/images/[category]/
|
||||
3. Run background removal (rembg)
|
||||
4. Verify transparency
|
||||
"""
|
||||
|
||||
# Updated style prompt for white background
|
||||
STYLE_PROMPT = """
|
||||
(bold black outlines:1.4), dark hand-drawn stylized indie game asset,
|
||||
(gritty noir aesthetic:1.2), flat colors, muted color palette,
|
||||
exaggerated features, warped perspective, wonky proportions,
|
||||
mature post-apocalyptic vibe, Don't Starve inspired,
|
||||
high contrast noir elements, isolated object centered on solid white background,
|
||||
clean edges, simple composition, no shadows on ground, no environment elements
|
||||
"""
|
||||
|
||||
# Buildings to generate (missing from assets)
|
||||
BUILDINGS = [
|
||||
"church_unfinished",
|
||||
"workshop_complete", "workshop_ruined",
|
||||
"barn_complete", "barn_damaged",
|
||||
"farmhouse_complete", "farmhouse_ruined",
|
||||
"store_complete", "store_damaged",
|
||||
"windmill", "watchtower", "greenhouse",
|
||||
"laboratory", "vape_lab", "furnace_building",
|
||||
"mint_building", "tailoring_shop",
|
||||
]
|
||||
|
||||
# Additional categories
|
||||
WORKSTATIONS = [
|
||||
"furnace", "campfire", "tailoring_table",
|
||||
"vape_lab_station", "mint_station",
|
||||
"basic_sprinkler", "quality_sprinkler", "iridium_sprinkler",
|
||||
]
|
||||
|
||||
BOSSES = [
|
||||
"mutant_king", "zombie_horde_leader", "ancient_tree",
|
||||
"giant_troll_king", "ice_titan", "fire_dragon",
|
||||
"king_slime",
|
||||
]
|
||||
|
||||
|
||||
def generate_building_prompt(building_name: str) -> str:
|
||||
"""Generate building-specific prompt"""
|
||||
clean_name = building_name.replace("_", " ")
|
||||
|
||||
prompts = {
|
||||
"church_unfinished": "unfinished church building under construction, stone church with scaffolding and missing walls",
|
||||
"workshop_complete": "complete workshop building, craftsman work shed with tools visible",
|
||||
"workshop_ruined": "ruined workshop building, destroyed shed with collapsed roof",
|
||||
"barn_complete": "complete barn building, large wooden farm barn with hay loft",
|
||||
"barn_damaged": "damaged barn building, weathered wooden barn with broken boards",
|
||||
"farmhouse_complete": "complete farmhouse building, cozy two-story house with chimney",
|
||||
"farmhouse_ruined": "ruined farmhouse building, collapsed house with broken walls",
|
||||
"store_complete": "complete store building, merchant shop with windows and door",
|
||||
"store_damaged": "damaged store building, broken shop with boarded windows",
|
||||
"windmill": "windmill building, tall wooden windmill with rotating blades",
|
||||
"watchtower": "watchtower building, tall stone tower with lookout platform",
|
||||
"greenhouse": "greenhouse building, glass structure for growing plants",
|
||||
"laboratory": "laboratory building, science research facility",
|
||||
"vape_lab": "vape lab building, workshop for vape liquid production",
|
||||
"furnace_building": "furnace building, metalworking foundry with chimney",
|
||||
"mint_building": "mint building, coin production facility",
|
||||
"tailoring_shop": "tailoring shop building, clothing workshop",
|
||||
}
|
||||
|
||||
specific = prompts.get(building_name, clean_name)
|
||||
|
||||
return f"{specific}, game building asset, isometric view, {STYLE_PROMPT}"
|
||||
|
||||
|
||||
def copy_latest_artifact(image_name: str, category: str) -> Path:
|
||||
"""Find and copy latest generated artifact"""
|
||||
|
||||
# Find all matching artifacts
|
||||
pattern = f"{image_name}_*.png"
|
||||
artifacts = list(BRAIN_DIR.glob(pattern))
|
||||
|
||||
if not artifacts:
|
||||
print(f" ❌ No artifact found for {image_name}")
|
||||
return None
|
||||
|
||||
# Get latest
|
||||
latest = max(artifacts, key=lambda p: p.stat().st_mtime)
|
||||
|
||||
# Create category dir
|
||||
category_dir = ASSETS_DIR / category
|
||||
category_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy
|
||||
dest = category_dir / f"{image_name}.png"
|
||||
shutil.copy2(latest, dest)
|
||||
|
||||
print(f" 📋 Copied to: {dest.relative_to(BASE_DIR)}")
|
||||
return dest
|
||||
|
||||
|
||||
def remove_background_rembg(image_path: Path):
|
||||
"""Remove background using rembg"""
|
||||
print(f" 🎨 Removing background...")
|
||||
|
||||
try:
|
||||
from rembg import remove
|
||||
from PIL import Image
|
||||
|
||||
with open(image_path, 'rb') as f:
|
||||
input_data = f.read()
|
||||
|
||||
output_data = remove(input_data)
|
||||
|
||||
with open(image_path, 'wb') as f:
|
||||
f.write(output_data)
|
||||
|
||||
print(f" ✅ Background removed!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Background removal failed: {e}")
|
||||
print(f" 💡 Install rembg: pip3 install rembg")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main generation loop"""
|
||||
|
||||
print("=" * 60)
|
||||
print("🎨 INTEGRATED GRAVITY GENERATION PIPELINE")
|
||||
print("=" * 60)
|
||||
print(WORKFLOW_STEPS)
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\n📊 Buildings to generate: {len(BUILDINGS)}")
|
||||
|
||||
for i, building in enumerate(BUILDINGS, 1):
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🏗️ [{i}/{len(BUILDINGS)}] {building}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Check if exists
|
||||
dest_path = ASSETS_DIR / "buildings" / f"{building}.png"
|
||||
if dest_path.exists():
|
||||
print(f" ⏭️ SKIP: Already exists")
|
||||
continue
|
||||
|
||||
# Generate prompt
|
||||
prompt = generate_building_prompt(building)
|
||||
print(f" 📝 Prompt: {prompt[:100]}...")
|
||||
|
||||
print(f"\n ⚠️ ACTION NEEDED:")
|
||||
print(f" → Ask Antigravity to run:")
|
||||
print(f" generate_image('{building}', '{prompt}')")
|
||||
print(f"\n Then continue with:")
|
||||
print(f" 1. Copy artifact to assets/images/buildings/")
|
||||
print(f" 2. Run background removal")
|
||||
|
||||
# Placeholder - in real usage, Antigravity will generate here
|
||||
break # Stop after showing workflow
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("📝 WORKFLOW INSTRUCTIONS COMPLETE")
|
||||
print(f"{'='*60}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
243
scripts/generate_gravity_batch.py
Normal file
243
scripts/generate_gravity_batch.py
Normal file
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GRAVITY BATCH GENERATOR - DolinaSmrti Asset Production
|
||||
Uses ONLY Gravity (generate_image) - NO ComfyUI/Ufi
|
||||
Style: Gritty Noir Hand-Drawn 2D Stylized Indie
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# === CONFIGURATION ===
|
||||
BASE_DIR = Path("/Users/davidkotnik/repos/novafarma")
|
||||
ASSETS_DIR = BASE_DIR / "assets" / "images"
|
||||
|
||||
# === GRITTY NOIR STYLE ===
|
||||
STYLE_PROMPT = """
|
||||
(bold black outlines:1.4), dark hand-drawn stylized indie game asset,
|
||||
(gritty noir aesthetic:1.2), flat colors, muted color palette,
|
||||
exaggerated features, warped perspective, wonky proportions,
|
||||
mature post-apocalyptic vibe, Don't Starve inspired,
|
||||
high contrast noir elements, transparent background PNG
|
||||
"""
|
||||
|
||||
NEGATIVE_PROMPT = """
|
||||
pixel art, pixels, grainy, blurry, 3D rendering, realistic photo,
|
||||
shading gradients, Disney style, cute kawaii, bright colors,
|
||||
background elements, text, watermark
|
||||
"""
|
||||
|
||||
# === ASSET REGISTRY ===
|
||||
# Based on DODATNA_VSEBINA_REGISTRY_V1.14.md
|
||||
|
||||
ASSET_CATEGORIES = {
|
||||
"buildings": [
|
||||
"church_complete", "church_unfinished", "church_ruined",
|
||||
"workshop_complete", "workshop_ruined",
|
||||
"barn_complete", "barn_damaged",
|
||||
"farmhouse_complete", "farmhouse_ruined",
|
||||
"store_complete", "store_damaged",
|
||||
"windmill", "watchtower", "greenhouse",
|
||||
"laboratory", "vape_lab", "furnace_building",
|
||||
"mint_building", "tailoring_shop",
|
||||
],
|
||||
|
||||
"workstations": [
|
||||
"furnace", "campfire", "tailoring_table",
|
||||
"vape_lab_station", "mint_station",
|
||||
"basic_sprinkler", "quality_sprinkler", "iridium_sprinkler",
|
||||
"enchanting_table", "crafting_bench",
|
||||
],
|
||||
|
||||
"items": [
|
||||
# Weapons
|
||||
"wooden_bow", "crystal_bow", "dragon_bow", "alpha_rainbow_bow",
|
||||
"fire_arrow", "ice_arrow", "lightning_arrow", "bomb_arrow",
|
||||
"poison_arrow", "holy_arrow", "silver_arrow",
|
||||
|
||||
# Tools
|
||||
"wooden_axe", "iron_axe", "gold_axe", "diamond_axe",
|
||||
"wooden_pickaxe", "iron_pickaxe", "gold_pickaxe", "diamond_pickaxe",
|
||||
"wooden_hoe", "iron_hoe", "gold_hoe",
|
||||
"watering_can", "fishing_rod",
|
||||
|
||||
# Resources
|
||||
"iron_ore", "gold_ore", "silver_ore", "diamond",
|
||||
"iron_bar", "gold_bar", "silver_bar",
|
||||
"wood_log", "stone", "coal",
|
||||
"hemp_fiber", "leather", "spider_silk",
|
||||
|
||||
# Food
|
||||
"bread", "cheese", "milk", "beef",
|
||||
"chicken_egg", "truffle", "ham",
|
||||
|
||||
# Magic
|
||||
"slime_gel_green", "slime_gel_blue", "slime_gel_red",
|
||||
"rainbow_vape_liquid", "menthol_vape_liquid",
|
||||
"magic_mushroom",
|
||||
],
|
||||
|
||||
"zivali": [ # Animals
|
||||
# Livestock
|
||||
"sheep_normal", "sheep_fire", "sheep_golden_fleece",
|
||||
"cow_normal", "cow_mutant",
|
||||
"chicken_normal", "chicken_three_headed",
|
||||
"pig_normal", "pig_giant",
|
||||
"horse_normal", "horse_undead",
|
||||
|
||||
# Wildlife
|
||||
"fox", "deer", "rabbit", "hedgehog",
|
||||
"bear", "wolf", "wild_boar",
|
||||
"owl", "bat",
|
||||
|
||||
# Marine
|
||||
"fish_bass", "fish_trout", "fish_salmon", "fish_tuna", "fish_piranha",
|
||||
"golden_fish", "sea_dragon", "loch_ness_monster",
|
||||
"shark", "jellyfish",
|
||||
|
||||
# Dogs
|
||||
"dog_retriever", "dog_shepherd", "dog_husky", "dog_corgi", "dog_dalmatian",
|
||||
"dachshund_susi",
|
||||
|
||||
# Slimes
|
||||
"slime_green", "slime_blue", "slime_red", "slime_yellow",
|
||||
"slime_purple", "slime_black", "slime_rainbow",
|
||||
],
|
||||
|
||||
"mutanti": [
|
||||
"zombie_brown_dreads", "zombie_bald", "zombie_pink_dreads",
|
||||
"zombie_portrait_closeup",
|
||||
"werewolf_normal", "werewolf_transformed",
|
||||
"skeleton_warrior", "ghost",
|
||||
"mutant_monkey", "mutant_beast",
|
||||
"griffin", "pterodactyl", "hippogriff",
|
||||
"bigfoot", "yeti",
|
||||
],
|
||||
|
||||
"bosses": [
|
||||
"mutant_king", "zombie_horde_leader", "ancient_tree",
|
||||
"giant_troll_king", "ice_titan", "fire_dragon",
|
||||
"king_slime",
|
||||
],
|
||||
|
||||
"environment": [
|
||||
# Crops
|
||||
"wheat_planted", "wheat_growing", "wheat_harvest",
|
||||
"corn_planted", "corn_growing", "corn_harvest",
|
||||
"tomato_planted", "tomato_growing", "tomato_harvest",
|
||||
|
||||
# Trees
|
||||
"cherry_blossom_tree", "oak_tree", "pine_tree",
|
||||
"dead_tree", "burned_tree",
|
||||
|
||||
# Decorations
|
||||
"grave_stone", "grave_cross", "grave_angel",
|
||||
"rock_small", "rock_large", "boulder",
|
||||
"bush_green", "bush_dead",
|
||||
|
||||
# Special
|
||||
"carnivorous_plant_seedling", "carnivorous_plant_giant",
|
||||
"portal_normal", "portal_activated",
|
||||
],
|
||||
|
||||
"ui": [
|
||||
"health_bar", "stamina_bar", "mana_bar",
|
||||
"inventory_slot", "inventory_slot_selected",
|
||||
"coin_icon", "heart_icon", "star_icon",
|
||||
"button_play", "button_pause", "button_settings",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def generate_prompt(category: str, asset_name: str) -> str:
|
||||
"""Generate full prompt for Gravity image generation"""
|
||||
|
||||
# Clean asset name for description
|
||||
clean_name = asset_name.replace("_", " ")
|
||||
|
||||
# Category-specific additions
|
||||
category_context = {
|
||||
"buildings": "game building asset, isometric view, architectural structure",
|
||||
"workstations": "crafting station, work table, game object",
|
||||
"items": "game item icon, inventory object, collectible",
|
||||
"zivali": "animal character, creature sprite, wildlife",
|
||||
"mutanti": "mutant creature, zombie character, monster enemy",
|
||||
"bosses": "boss enemy, large creature, intimidating monster",
|
||||
"environment": "environment object, natural element, decoration",
|
||||
"ui": "UI element, game interface icon, HUD component",
|
||||
}
|
||||
|
||||
context = category_context.get(category, "game asset")
|
||||
|
||||
full_prompt = f"""
|
||||
{clean_name}, {context}, {STYLE_PROMPT}
|
||||
""".strip()
|
||||
|
||||
return full_prompt
|
||||
|
||||
|
||||
def main():
|
||||
"""Main batch generation loop"""
|
||||
|
||||
print("=" * 60)
|
||||
print("🎨 GRAVITY BATCH GENERATOR - DolinaSmrti")
|
||||
print("=" * 60)
|
||||
print(f"Style: Gritty Noir Hand-Drawn 2D Stylized Indie")
|
||||
print(f"Output: {ASSETS_DIR}")
|
||||
print("=" * 60)
|
||||
|
||||
total_assets = sum(len(assets) for assets in ASSET_CATEGORIES.values())
|
||||
print(f"\n📊 Total assets to generate: {total_assets}")
|
||||
|
||||
generated_count = 0
|
||||
|
||||
for category, assets in ASSET_CATEGORIES.items():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📁 Category: {category.upper()} ({len(assets)} assets)")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Create category directory
|
||||
category_dir = ASSETS_DIR / category
|
||||
category_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for i, asset_name in enumerate(assets, 1):
|
||||
output_path = category_dir / f"{asset_name}.png"
|
||||
|
||||
# Skip if already exists
|
||||
if output_path.exists():
|
||||
print(f" ⏭️ [{i}/{len(assets)}] SKIP: {asset_name} (already exists)")
|
||||
generated_count += 1
|
||||
continue
|
||||
|
||||
# Generate prompt
|
||||
prompt = generate_prompt(category, asset_name)
|
||||
|
||||
print(f"\n 🎨 [{i}/{len(assets)}] Generating: {asset_name}")
|
||||
print(f" Prompt: {prompt[:80]}...")
|
||||
|
||||
# === THIS IS WHERE GRAVITY INTEGRATION HAPPENS ===
|
||||
# In actual usage, Antigravity will call generate_image here
|
||||
# For now, this is a placeholder that will be replaced
|
||||
|
||||
print(f" ⚠️ PLACEHOLDER: Call generate_image('{prompt}', '{asset_name}')")
|
||||
print(f" 💾 Save to: {output_path}")
|
||||
|
||||
generated_count += 1
|
||||
|
||||
# Progress update
|
||||
progress = (generated_count / total_assets) * 100
|
||||
print(f"\n 📊 Overall Progress: {generated_count}/{total_assets} ({progress:.1f}%)")
|
||||
|
||||
# Respectful delay (avoid API hammering)
|
||||
time.sleep(2)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ GENERATION COMPLETE!")
|
||||
print("=" * 60)
|
||||
print(f"Total generated: {generated_count}/{total_assets}")
|
||||
print(f"Output directory: {ASSETS_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
79
scripts/overnight_generation.sh
Executable file
79
scripts/overnight_generation.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# OVERNIGHT ASSET GENERATION - DolinaSmrti
|
||||
# Run this script and go to sleep - it will generate all assets automatically!
|
||||
|
||||
echo "======================================================================"
|
||||
echo "🌙 OVERNIGHT GENERATION STARTING"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
echo "This script will:"
|
||||
echo " 1. Generate HIGH PRIORITY buildings (tent→farmhouse progression)"
|
||||
echo " 2. Remove backgrounds automatically"
|
||||
echo " 3. Commit each asset to git"
|
||||
echo " 4. Wait 60 seconds between generations (API rate limit)"
|
||||
echo ""
|
||||
echo "You can SLEEP now - check results in the morning! ☕"
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
|
||||
# Asset list (HIGH PRIORITY only)
|
||||
ASSETS=(
|
||||
"farmhouse_basic:complete small farmhouse building, basic two-story house with chimney, starter home"
|
||||
"blacksmith_shop_complete:complete blacksmith shop building, forge workshop with anvil and chimney, metalworking facility"
|
||||
"bakery_complete:complete bakery building, cozy bakery shop with oven and storefront, bread store"
|
||||
"clinic_complete:complete clinic building, medical facility with red cross, healthcare center"
|
||||
"greenhouse_complete:complete greenhouse building, glass structure for growing plants, botanical building"
|
||||
"workshop_complete:complete workshop building, craftsman work shed with tools visible, maker space"
|
||||
"windmill_complete:complete windmill building, tall wooden windmill with four rotating blades, grain processing"
|
||||
"watchtower:watchtower building, tall stone tower with lookout platform, defensive structure"
|
||||
"tavern_complete:complete tavern building, cozy inn with hanging sign, social gathering place"
|
||||
"town_hall_complete:complete town hall building, administrative building with clock tower, government center"
|
||||
)
|
||||
|
||||
TOTAL=${#ASSETS[@]}
|
||||
CURRENT=0
|
||||
|
||||
for asset_data in "${ASSETS[@]}"; do
|
||||
CURRENT=$((CURRENT + 1))
|
||||
|
||||
# Split asset name and description
|
||||
ASSET_NAME="${asset_data%%:*}"
|
||||
DESCRIPTION="${asset_data#*:}"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "[$CURRENT/$TOTAL] Generating: $ASSET_NAME"
|
||||
echo "======================================================================"
|
||||
echo "Description: $DESCRIPTION"
|
||||
echo ""
|
||||
|
||||
# This is where YOU (Antigravity) would call generate_image
|
||||
# For now, this is a placeholder that shows the workflow
|
||||
|
||||
echo "⚠️ MANUAL STEP REQUIRED:"
|
||||
echo " Ask Antigravity to generate:"
|
||||
echo ""
|
||||
echo " generate_image('$ASSET_NAME', '$DESCRIPTION, game building asset, isometric view, (bold black outlines:1.4), dark hand-drawn stylized indie game asset, (gritty noir aesthetic:1.2), flat colors, muted color palette, isolated object centered on solid white background, clean edges, simple composition')"
|
||||
echo ""
|
||||
echo " Then run:"
|
||||
echo " cp [artifact_path] assets/images/buildings/$ASSET_NAME.png"
|
||||
echo " python3 scripts/remove_background.py assets/images/buildings/$ASSET_NAME.png"
|
||||
echo " git add assets/images/buildings/$ASSET_NAME.png"
|
||||
echo " git commit -m \"feat: Add $ASSET_NAME asset\""
|
||||
echo ""
|
||||
|
||||
# Wait 60 seconds before next generation
|
||||
if [ $CURRENT -lt $TOTAL ]; then
|
||||
echo "⏱️ Waiting 60 seconds before next generation..."
|
||||
sleep 60
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "✅ OVERNIGHT GENERATION COMPLETE!"
|
||||
echo "======================================================================"
|
||||
echo "Total generated: $TOTAL assets"
|
||||
echo "Check assets/images/buildings/ for results"
|
||||
echo ""
|
||||
86
scripts/remove_background.py
Normal file
86
scripts/remove_background.py
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Background Removal for DolinaSmrti Assets
|
||||
Uses rembg to remove white backgrounds and create transparent PNGs
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
from rembg import remove
|
||||
except ImportError:
|
||||
print("❌ rembg not installed. Installing now...")
|
||||
os.system("pip3 install rembg")
|
||||
from rembg import remove
|
||||
|
||||
|
||||
def remove_background(input_path: Path, output_path: Path = None):
|
||||
"""Remove background from image and save as transparent PNG"""
|
||||
|
||||
if output_path is None:
|
||||
output_path = input_path
|
||||
|
||||
print(f" 🎨 Processing: {input_path.name}")
|
||||
|
||||
# Load image
|
||||
with open(input_path, 'rb') as input_file:
|
||||
input_data = input_file.read()
|
||||
|
||||
# Remove background
|
||||
output_data = remove(input_data)
|
||||
|
||||
# Save as PNG
|
||||
with open(output_path, 'wb') as output_file:
|
||||
output_file.write(output_data)
|
||||
|
||||
print(f" ✅ Saved: {output_path.name}")
|
||||
|
||||
|
||||
def process_directory(directory: Path, recursive: bool = False):
|
||||
"""Process all PNG files in directory"""
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📁 Processing directory: {directory}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
pattern = "**/*.png" if recursive else "*.png"
|
||||
png_files = list(directory.glob(pattern))
|
||||
|
||||
total = len(png_files)
|
||||
print(f"\nFound {total} PNG files")
|
||||
|
||||
for i, png_file in enumerate(png_files, 1):
|
||||
print(f"\n[{i}/{total}]")
|
||||
remove_background(png_file)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"✅ Completed! Processed {total} images")
|
||||
print(f"{'='*60}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 remove_background.py <image_or_directory>")
|
||||
print("\nExamples:")
|
||||
print(" python3 remove_background.py image.png")
|
||||
print(" python3 remove_background.py assets/images/buildings/")
|
||||
print(" python3 remove_background.py assets/images/ --recursive")
|
||||
sys.exit(1)
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
recursive = "--recursive" in sys.argv or "-r" in sys.argv
|
||||
|
||||
if not path.exists():
|
||||
print(f"❌ Path not found: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
if path.is_file():
|
||||
remove_background(path)
|
||||
elif path.is_dir():
|
||||
process_directory(path, recursive=recursive)
|
||||
else:
|
||||
print(f"❌ Invalid path: {path}")
|
||||
sys.exit(1)
|
||||
3
scripts/test_noir_style.py
Normal file
3
scripts/test_noir_style.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# Noir Style Config - Updated for Easy Background Removal
|
||||
style = "(bold black outlines:1.4), dark hand-drawn stylized indie game asset, (gritty noir aesthetic:1.2), flat color, isolated object centered on solid white background, clean edges, (simple composition:1.3)"
|
||||
negative_prompt = "pixel art, pixels, grainy, blurry, 3D, shading gradients, complex background, environment elements, shadows on ground, textured background"
|
||||
@@ -1,183 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script - generates single asset to verify ComfyUI workflow
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from pathlib import Path
|
||||
import copy
|
||||
import subprocess
|
||||
|
||||
COMFYUI_URL = "http://127.0.0.1:8000"
|
||||
OUTPUT_DIR = "/Users/davidkotnik/repos/novafarma/assets/images"
|
||||
|
||||
# Test prompt with all requirements
|
||||
TEST_PROMPT = "Top-down 2.5D video game sprite, 128x128 pixels, smooth vector art style like Stardew Valley, NO pixels, vibrant colors, clean edges, GREEN SCREEN background RGBA(0,255,0,255) pure green chroma key, Normal farm cow black and white Holstein pattern friendly face 4-direction sprite"
|
||||
|
||||
def queue_comfy_prompt(prompt_text: str, output_name: str) -> dict:
|
||||
"""Send prompt to ComfyUI"""
|
||||
workflow = {
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": int(time.time()),
|
||||
"steps": 25,
|
||||
"cfg": 7.5,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler"
|
||||
workflow = {
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": int(time.time()),
|
||||
"steps": 25,
|
||||
"cfg": 7.5,
|
||||
"sampler_name": "euler",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "sd_xl_base_1.0.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple"
|
||||
"class_type": "KSampler"
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "v1-5-pruned-emaonly.safetensors"
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"batch_size": 1
|
||||
},
|
||||
"class_type": "EmptyLatentImage"
|
||||
"class_type": "CheckpointLoaderSimple"
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"batch_size": 1
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": prompt_text,
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
"class_type": "EmptyLatentImage"
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "",
|
||||
"clip": ["4", 1],
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "blurry, low quality, pixelated, photo, realistic",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "blurry, low quality, pixelated, voxel, 3D render, realistic photo",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": ["3", 0],
|
||||
"vae": ["4", 2]
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": ["3", 0],
|
||||
"vae": ["4", 2]
|
||||
},
|
||||
"class_type": "VAEDecode"
|
||||
"class_type": "VAEDecode"
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "",
|
||||
"images": ["8", 0],
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": output_name,
|
||||
"images": ["8", 0]
|
||||
},
|
||||
"class_type": "SaveImage"
|
||||
}
|
||||
"class_type": "SaveImage"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def queue_prompt(prompt):
|
||||
p = {"prompt": prompt}
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{COMFYUI_URL}/prompt",
|
||||
json={"prompt": workflow}
|
||||
)
|
||||
return response.json()
|
||||
res = requests.post(COMFYUI_URL + "/prompt", data=data)
|
||||
if res.status_code == 200:
|
||||
print("🚀 SUCCESS: Poslano v ComfyUI!")
|
||||
return res.json()
|
||||
else:
|
||||
print(f"❌ Napaka: {res.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
print(f"❌ UFI (ComfyUI) ni prižgan! {e}")
|
||||
return None
|
||||
|
||||
def wait_for_completion(prompt_id: str, timeout: int = 180) -> bool:
|
||||
"""Wait for generation"""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
response = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
|
||||
history = response.json()
|
||||
|
||||
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
|
||||
history = r.json()
|
||||
if prompt_id in history:
|
||||
status = history[prompt_id].get("status", {})
|
||||
if status.get("completed", False):
|
||||
status = history[prompt_id].get('status', {})
|
||||
if status.get('completed'):
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Polling: {e}")
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(2)
|
||||
|
||||
return False
|
||||
|
||||
def download_image(prompt_id: str, output_path: Path) -> bool:
|
||||
"""Download image"""
|
||||
def download_image(prompt_id: str, prefix: str):
|
||||
try:
|
||||
response = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
|
||||
history = response.json()
|
||||
|
||||
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
|
||||
history = r.json()
|
||||
if prompt_id not in history:
|
||||
return False
|
||||
|
||||
outputs = history[prompt_id].get("outputs", {})
|
||||
for node_id, node_output in outputs.items():
|
||||
if "images" in node_output:
|
||||
for img in node_output["images"]:
|
||||
filename = img["filename"]
|
||||
subfolder = img.get("subfolder", "")
|
||||
|
||||
img_url = f"{COMFYUI_URL}/view"
|
||||
params = {
|
||||
"filename": filename,
|
||||
"subfolder": subfolder,
|
||||
"type": "output"
|
||||
}
|
||||
|
||||
img_response = requests.get(img_url, params=params)
|
||||
if img_response.status_code == 200:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(img_response.content)
|
||||
outputs = history[prompt_id].get('outputs', {})
|
||||
for node_id, out in outputs.items():
|
||||
if 'images' in out:
|
||||
for img in out['images']:
|
||||
filename = img['filename']
|
||||
subfolder = img.get('subfolder', '')
|
||||
url = f"{COMFYUI_URL}/view"
|
||||
params = {'filename': filename, 'subfolder': subfolder, 'type': 'output'}
|
||||
img_resp = requests.get(url, params=params)
|
||||
if img_resp.status_code == 200:
|
||||
out_path = os.path.join(OUTPUT_DIR, f"{prefix}_{filename}")
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(img_resp.content)
|
||||
print(f"✅ Image saved: {out_path}")
|
||||
# subprocess.run(["open", out_path])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Download error: {e}")
|
||||
return False
|
||||
|
||||
print("=" * 70)
|
||||
print("🧪 TEST: Single Asset Generation")
|
||||
print("=" * 70)
|
||||
print(f"📡 Server: {COMFYUI_URL}")
|
||||
print(f"🎨 Prompt: {TEST_PROMPT[:80]}...")
|
||||
print()
|
||||
# Updated generate_character to use new helpers
|
||||
|
||||
# Check connection
|
||||
try:
|
||||
response = requests.get(f"{COMFYUI_URL}/system_stats")
|
||||
print("✅ ComfyUI connected!")
|
||||
except:
|
||||
print("❌ ComfyUI NOT responding!")
|
||||
exit(1)
|
||||
|
||||
# Queue test
|
||||
print("\n🚀 Queuing test generation...")
|
||||
result = queue_comfy_prompt(TEST_PROMPT, "test_cow")
|
||||
if not result or "prompt_id" not in result:
|
||||
print("❌ Failed to queue!")
|
||||
exit(1)
|
||||
|
||||
prompt_id = result["prompt_id"]
|
||||
print(f"⏳ Queued: {prompt_id}")
|
||||
|
||||
# Wait
|
||||
print("⏳ Waiting for completion...")
|
||||
if wait_for_completion(prompt_id):
|
||||
output_path = Path(OUTPUT_DIR) / "zivali" / "test_cow.png"
|
||||
if download_image(prompt_id, output_path):
|
||||
print(f"✅ SUCCESS! Saved to: {output_path}")
|
||||
print("\n📊 Check the image:")
|
||||
print(f" - Size should be 128x128")
|
||||
print(f" - Background should be green RGBA(0,255,0,255)")
|
||||
print(f" - Style should be smooth 2D vector art")
|
||||
def generate_character(name, prompt_text, prefix):
|
||||
wf = copy.deepcopy(workflow)
|
||||
wf["6"]["inputs"]["text"] = prompt_text
|
||||
wf["9"]["inputs"]["filename_prefix"] = prefix
|
||||
result = queue_prompt(wf)
|
||||
if not result or 'prompt_id' not in result:
|
||||
print('❌ Failed to get prompt_id')
|
||||
return
|
||||
pid = result['prompt_id']
|
||||
if wait_for_completion(pid):
|
||||
download_image(pid, prefix)
|
||||
else:
|
||||
print("❌ Failed to download!")
|
||||
else:
|
||||
print("⏱️ Timeout!")
|
||||
print('❌ Generation timed out')
|
||||
|
||||
# Generate 5 Kai images
|
||||
kai_prompt = "Top-down 2.5D, (bold black outlines:1.4), dark hand-drawn stylized indie game art, high contrast noir style, character Kai with pink and green dreadlocks, friendly smile"
|
||||
for i in range(5):
|
||||
generate_character("Kai", kai_prompt, f"noir_kai_{i}")
|
||||
|
||||
# Generate 5 Gronk images
|
||||
gronk_prompt = "Top-down 2.5D, (bold black outlines:1.4), dark hand-drawn stylized indie game art, high contrast noir style, character Gronk with pink dreadlocks, stretched ears, facial piercings, holding a vape"
|
||||
for i in range(5):
|
||||
generate_character("Gronk", gronk_prompt, f"noir_gronk_{i}")
|
||||
@@ -1,170 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
🧪 STARDEW VALLEY STYLE TEST
|
||||
1 slika iz vsake kategorije
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import requests
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from rembg import remove
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
os.system("pip3 install --user rembg pillow onnxruntime")
|
||||
from rembg import remove
|
||||
from PIL import Image
|
||||
|
||||
COMFYUI_URL = "http://127.0.0.1:8000"
|
||||
OUTPUT_DIR = Path("/Users/davidkotnik/repos/novafarma/assets/images")
|
||||
REPO_DIR = Path("/Users/davidkotnik/repos/novafarma")
|
||||
|
||||
# STARDEW VALLEY STYLE
|
||||
STYLE = """Stardew Valley game art style, 2D pixel-perfect sprite,
|
||||
clean pixel art, vibrant colors, cute game asset,
|
||||
top-down or side view, isolated on white background"""
|
||||
|
||||
NEGATIVE = """3d render, realistic, photorealistic, blurry,
|
||||
low quality, watermark, text, green background"""
|
||||
|
||||
# TEST SAMPLES - 1 iz vsake kategorije
|
||||
TEST_SAMPLES = [
|
||||
# Živali
|
||||
("zivali", "test_cow_stardew", "dairy cow, Stardew Valley farm animal"),
|
||||
|
||||
# Items - Tool
|
||||
("items", "test_axe_stardew", "iron axe tool, Stardew Valley item"),
|
||||
|
||||
# Items - Food
|
||||
("items", "test_bread_stardew", "bread loaf, Stardew Valley food"),
|
||||
|
||||
# Buildings
|
||||
("buildings", "test_barn_stardew", "small barn building, Stardew Valley farm structure"),
|
||||
|
||||
# Environment
|
||||
("environment", "test_tree_stardew", "oak tree, Stardew Valley nature"),
|
||||
|
||||
# Crops
|
||||
("crops", "test_wheat_stardew", "wheat crop plant, Stardew Valley farming"),
|
||||
|
||||
# Workstations
|
||||
("workstations", "test_anvil_stardew", "blacksmith anvil, Stardew Valley crafting"),
|
||||
|
||||
# UI
|
||||
("ui", "test_heart_stardew", "red heart icon, Stardew Valley UI element"),
|
||||
|
||||
# Mutants
|
||||
("mutanti", "test_slime_stardew", "green slime monster, Stardew Valley enemy"),
|
||||
|
||||
# Bosses
|
||||
("bosses", "test_dragon_stardew", "small dragon boss, Stardew Valley style"),
|
||||
]
|
||||
|
||||
def log(msg):
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
print(f"[{ts}] {msg}")
|
||||
sys.stdout.flush()
|
||||
|
||||
def create_workflow(prompt_text, output_name, size=512):
|
||||
seed = int(time.time() * 1000) % 2147483647
|
||||
full_prompt = f"{STYLE}, {prompt_text}"
|
||||
return {
|
||||
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "dreamshaper_8.safetensors"}},
|
||||
"2": {"class_type": "EmptyLatentImage", "inputs": {"width": size, "height": size, "batch_size": 1}},
|
||||
"3": {"class_type": "CLIPTextEncode", "inputs": {"text": full_prompt, "clip": ["1", 1]}},
|
||||
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": NEGATIVE, "clip": ["1", 1]}},
|
||||
"5": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"seed": seed, "steps": 30, "cfg": 7.5,
|
||||
"sampler_name": "euler_ancestral", "scheduler": "karras",
|
||||
"denoise": 1.0, "model": ["1", 0],
|
||||
"positive": ["3", 0], "negative": ["4", 0], "latent_image": ["2", 0]
|
||||
}
|
||||
},
|
||||
"6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["1", 2]}},
|
||||
"7": {"class_type": "SaveImage", "inputs": {"filename_prefix": output_name, "images": ["6", 0]}}
|
||||
}
|
||||
|
||||
def queue_prompt(workflow):
|
||||
try:
|
||||
r = requests.post(f"{COMFYUI_URL}/prompt", json={"prompt": workflow, "client_id": f"test_{uuid.uuid4().hex[:8]}"}, timeout=10)
|
||||
return r.json().get("prompt_id")
|
||||
except Exception as e:
|
||||
log(f"❌ {e}")
|
||||
return None
|
||||
|
||||
def wait_completion(prompt_id, timeout=120):
|
||||
start = time.time()
|
||||
while time.time() < start + timeout:
|
||||
try:
|
||||
r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}", timeout=5)
|
||||
if prompt_id in r.json() and r.json()[prompt_id].get("outputs"):
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
time.sleep(2)
|
||||
return False
|
||||
|
||||
def download_and_process(prompt_id, output_path):
|
||||
try:
|
||||
h = requests.get(f"{COMFYUI_URL}/history/{prompt_id}").json()
|
||||
for out in h.get(prompt_id, {}).get("outputs", {}).values():
|
||||
for img in out.get("images", []):
|
||||
r = requests.get(f"{COMFYUI_URL}/view", params={"filename": img["filename"], "subfolder": img.get("subfolder", ""), "type": "output"})
|
||||
if r.status_code == 200:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
from io import BytesIO
|
||||
transparent_img = remove(Image.open(BytesIO(r.content)))
|
||||
transparent_img.save(str(output_path), "PNG")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
log(f"❌ {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
log("="*70)
|
||||
log("🧪 STARDEW VALLEY STYLE TEST")
|
||||
log(" 1 slika iz vsake kategorije")
|
||||
log("="*70)
|
||||
|
||||
try:
|
||||
r = requests.get(f"{COMFYUI_URL}/system_stats", timeout=5)
|
||||
log(f"✅ ComfyUI v{r.json()['system']['comfyui_version']}")
|
||||
except:
|
||||
log("❌ ComfyUI not running!")
|
||||
return
|
||||
|
||||
log(f"\n🎨 Generating {len(TEST_SAMPLES)} test samples (Stardew Valley style)...\n")
|
||||
|
||||
for i, (cat, name, prompt) in enumerate(TEST_SAMPLES, 1):
|
||||
path = OUTPUT_DIR / cat / f"{name}.png"
|
||||
|
||||
log(f"[{i}/{len(TEST_SAMPLES)}] 🎮 {cat}/{name}")
|
||||
|
||||
size = 768 if cat == "bosses" else 512
|
||||
wf = create_workflow(prompt, name, size)
|
||||
pid = queue_prompt(wf)
|
||||
|
||||
if pid and wait_completion(pid) and download_and_process(pid, path):
|
||||
log(f" ✅ Done!")
|
||||
os.system(f"open {path}")
|
||||
else:
|
||||
log(f" ❌ Failed")
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
log("\n" + "="*70)
|
||||
log("🧪 TEST COMPLETE!")
|
||||
log(" Vse slike odprte za pregled")
|
||||
log("="*70)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user