mirror of
https://github.com/minetest/minetestmapper.git
synced 2024-11-25 08:53:44 +01:00
Rewrite colors.txt generation script for more functions and better usability
This commit is contained in:
parent
e88fcf0dd8
commit
fa5c63cfc8
@ -1,132 +0,0 @@
|
|||||||
==FILE== mods/dumpnodes/init.lua
|
|
||||||
local function nd_get_tiles(nd)
|
|
||||||
return nd.tiles or nd.tile_images
|
|
||||||
end
|
|
||||||
|
|
||||||
local function nd_get_tile(nd, n)
|
|
||||||
local tile = nd_get_tiles(nd)[n]
|
|
||||||
if type(tile) == 'table' then
|
|
||||||
tile = tile.name
|
|
||||||
end
|
|
||||||
return tile
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pairs_s(dict)
|
|
||||||
local keys = {}
|
|
||||||
for k in pairs(dict) do
|
|
||||||
table.insert(keys, k)
|
|
||||||
end
|
|
||||||
table.sort(keys)
|
|
||||||
return ipairs(keys)
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.register_chatcommand("dumpnodes", {
|
|
||||||
params = "",
|
|
||||||
description = "",
|
|
||||||
func = function(player, param)
|
|
||||||
local n = 0
|
|
||||||
local ntbl = {}
|
|
||||||
for _, nn in pairs_s(minetest.registered_nodes) do
|
|
||||||
local nd = minetest.registered_nodes[nn]
|
|
||||||
local prefix, name = nn:match('(.*):(.*)')
|
|
||||||
if prefix == nil or name == nil then
|
|
||||||
print("ignored(1): " .. nn)
|
|
||||||
else
|
|
||||||
if ntbl[prefix] == nil then
|
|
||||||
ntbl[prefix] = {}
|
|
||||||
end
|
|
||||||
ntbl[prefix][name] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local out, err = io.open('nodes.txt', 'wb')
|
|
||||||
if not out then
|
|
||||||
return true, "io.open(): " .. err
|
|
||||||
end
|
|
||||||
for _, prefix in pairs_s(ntbl) do
|
|
||||||
out:write('# ' .. prefix .. '\n')
|
|
||||||
for _, name in pairs_s(ntbl[prefix]) do
|
|
||||||
local nn = prefix .. ":" .. name
|
|
||||||
local nd = minetest.registered_nodes[nn]
|
|
||||||
if nd.drawtype == 'airlike' or nd_get_tiles(nd) == nil then
|
|
||||||
print("ignored(2): " .. nn)
|
|
||||||
else
|
|
||||||
local tl = nd_get_tile(nd, 1)
|
|
||||||
tl = (tl .. '^'):match('(.-)^') -- strip modifiers
|
|
||||||
out:write(nn .. ' ' .. tl .. '\n')
|
|
||||||
n = n + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
out:write('\n')
|
|
||||||
end
|
|
||||||
out:close()
|
|
||||||
return true, n .. " nodes dumped."
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
==FILE== avgcolor.py
|
|
||||||
#!/usr/bin/env python
|
|
||||||
import sys
|
|
||||||
from math import sqrt
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Prints average color (RGB) of input image")
|
|
||||||
print("Usage: %s <input>" % sys.argv[0])
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
inp = Image.open(sys.argv[1]).convert('RGBA')
|
|
||||||
ind = inp.load()
|
|
||||||
|
|
||||||
cl = ([], [], [])
|
|
||||||
for x in range(inp.size[0]):
|
|
||||||
for y in range(inp.size[1]):
|
|
||||||
px = ind[x, y]
|
|
||||||
if px[3] < 128: continue # alpha
|
|
||||||
cl[0].append(px[0]**2)
|
|
||||||
cl[1].append(px[1]**2)
|
|
||||||
cl[2].append(px[2]**2)
|
|
||||||
|
|
||||||
if len(cl[0]) == 0:
|
|
||||||
print("Didn't find average color for %s" % sys.argv[1], file=sys.stderr)
|
|
||||||
print("0 0 0")
|
|
||||||
else:
|
|
||||||
cl = tuple(sqrt(sum(x)/len(x)) for x in cl)
|
|
||||||
print("%d %d %d" % cl)
|
|
||||||
==SCRIPT==
|
|
||||||
#!/bin/bash -e
|
|
||||||
AVGCOLOR_PATH=/path/to/avgcolor.py
|
|
||||||
GAME_PATH=/path/to/minetest_game
|
|
||||||
MODS_PATH= # path to "mods" folder, only set if you have loaded mods
|
|
||||||
NODESTXT_PATH=./nodes.txt
|
|
||||||
COLORSTXT_PATH=./colors.txt
|
|
||||||
|
|
||||||
while read -r line; do
|
|
||||||
set -- junk $line; shift
|
|
||||||
if [[ -z "$1" || $1 == "#" ]]; then
|
|
||||||
echo "$line"; continue
|
|
||||||
fi
|
|
||||||
tex=$(find $GAME_PATH -type f -name "$2")
|
|
||||||
[[ -z "$tex" && -n "$MODS_PATH" ]] && tex=$(find $MODS_PATH -type f -name "$2")
|
|
||||||
if [ -z "$tex" ]; then
|
|
||||||
echo "skip $1: texture not found" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "$1" $(python $AVGCOLOR_PATH "$tex")
|
|
||||||
echo "ok $1" >&2
|
|
||||||
done < $NODESTXT_PATH > $COLORSTXT_PATH
|
|
||||||
# Use nicer colors for water and lava:
|
|
||||||
sed -re 's/^default:((river_)?water_(flowing|source)) [0-9 ]+$/default:\1 39 66 106 128 224/g' $COLORSTXT_PATH -i
|
|
||||||
sed -re 's/^default:(lava_(flowing|source)) [0-9 ]+$/default:\1 255 100 0/g' $COLORSTXT_PATH -i
|
|
||||||
# Add transparency to glass nodes and xpanes:
|
|
||||||
sed -re 's/^default:(.*glass) ([0-9 ]+)$/default:\1 \2 64 16/g' $COLORSTXT_PATH -i
|
|
||||||
sed -re 's/^doors:(.*glass[^ ]*) ([0-9 ]+)$/doors:\1 \2 64 16/g' $COLORSTXT_PATH -i
|
|
||||||
sed -re 's/^xpanes:(.*(pane|bar)[^ ]*) ([0-9 ]+)$/xpanes:\1 \3 64 16/g' $COLORSTXT_PATH -i
|
|
||||||
# Delete some usually hidden nodes:
|
|
||||||
sed '/^doors:hidden /d' $COLORSTXT_PATH -i
|
|
||||||
sed '/^fireflies:firefly /d' $COLORSTXT_PATH -i
|
|
||||||
sed '/^butterflies:butterfly_/d' $COLORSTXT_PATH -i
|
|
||||||
==INSTRUCTIONS==
|
|
||||||
1) Make sure avgcolors.py works (outputs the usage instructions when run)
|
|
||||||
2) Add the dumpnodes mod to Minetest
|
|
||||||
3) Create a world and load dumpnodes & all mods you want to generate colors for
|
|
||||||
4) Execute /dumpnodes ingame
|
|
||||||
5) Run the script to generate colors.txt (make sure to adjust the PATH variables at the top)
|
|
61
util/dumpnodes/init.lua
Normal file
61
util/dumpnodes/init.lua
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
local function get_tile(tiles, n)
|
||||||
|
local tile = tiles[n]
|
||||||
|
if type(tile) == 'table' then
|
||||||
|
return tile.name
|
||||||
|
end
|
||||||
|
return tile
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pairs_s(dict)
|
||||||
|
local keys = {}
|
||||||
|
for k in pairs(dict) do
|
||||||
|
keys[#keys+1] = k
|
||||||
|
end
|
||||||
|
table.sort(keys)
|
||||||
|
return ipairs(keys)
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_chatcommand("dumpnodes", {
|
||||||
|
description = "Dump node and texture list for use with minetestmapper",
|
||||||
|
func = function()
|
||||||
|
local ntbl = {}
|
||||||
|
for _, nn in pairs_s(minetest.registered_nodes) do
|
||||||
|
local prefix, name = nn:match('(.*):(.*)')
|
||||||
|
if prefix == nil or name == nil then
|
||||||
|
print("ignored(1): " .. nn)
|
||||||
|
else
|
||||||
|
if ntbl[prefix] == nil then
|
||||||
|
ntbl[prefix] = {}
|
||||||
|
end
|
||||||
|
ntbl[prefix][name] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local out, err = io.open(minetest.get_worldpath() .. "/nodes.txt", 'wb')
|
||||||
|
if not out then
|
||||||
|
return true, err
|
||||||
|
end
|
||||||
|
local n = 0
|
||||||
|
for _, prefix in pairs_s(ntbl) do
|
||||||
|
out:write('# ' .. prefix .. '\n')
|
||||||
|
for _, name in pairs_s(ntbl[prefix]) do
|
||||||
|
local nn = prefix .. ":" .. name
|
||||||
|
local nd = minetest.registered_nodes[nn]
|
||||||
|
local tiles = nd.tiles or nd.tile_images
|
||||||
|
if tiles == nil or nd.drawtype == 'airlike' then
|
||||||
|
print("ignored(2): " .. nn)
|
||||||
|
else
|
||||||
|
local tex = get_tile(tiles, 1)
|
||||||
|
tex = (tex .. '^'):match('%(*(.-)%)*^') -- strip modifiers
|
||||||
|
if tex:find("[combine", 1, true) then
|
||||||
|
tex = tex:match('.-=([^:]-)') -- extract first texture
|
||||||
|
end
|
||||||
|
out:write(nn .. ' ' .. tex .. '\n')
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
out:write('\n')
|
||||||
|
end
|
||||||
|
out:close()
|
||||||
|
return true, n .. " nodes dumped."
|
||||||
|
end,
|
||||||
|
})
|
2
util/dumpnodes/mod.conf
Normal file
2
util/dumpnodes/mod.conf
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name = dumpnodes
|
||||||
|
description = minetestmapper development mod (node dumper)
|
183
util/generate_colorstxt.py
Executable file
183
util/generate_colorstxt.py
Executable file
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
import getopt
|
||||||
|
import re
|
||||||
|
from math import sqrt
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
except:
|
||||||
|
print("Could not load image routines, install PIL ('pillow' on pypi)!", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
############
|
||||||
|
############
|
||||||
|
# Instructions for generating a colors.txt file for custom games and/or mods:
|
||||||
|
# 1) Add the dumpnodes mod to a Minetest world with the chosen game and mods enabled.
|
||||||
|
# 2) Join ingame and run the /dumpnodes chat command.
|
||||||
|
# 3) Run this script and poin it to the installation path of the game using -g,
|
||||||
|
# the path(s) where mods are stored using -m and the nodes.txt in your world folder.
|
||||||
|
# Example command line:
|
||||||
|
# ./util/generate_colorstxt.py --game /usr/share/minetest/games/minetest_game \
|
||||||
|
# -m ~/.minetest/mods ~/.minetest/worlds/my_world/nodes.txt
|
||||||
|
# 4) Copy the resulting colors.txt file to your world folder or to any other places
|
||||||
|
# and use it with minetestmapper's --colors option.
|
||||||
|
###########
|
||||||
|
###########
|
||||||
|
|
||||||
|
# minimal sed syntax, s|match|replace| and /match/d supported
|
||||||
|
REPLACEMENTS = [
|
||||||
|
# Delete some nodes that are usually hidden
|
||||||
|
r'/^fireflies:firefly /d',
|
||||||
|
r'/^butterflies:butterfly_/d',
|
||||||
|
# Nicer colors for water and lava
|
||||||
|
r's/^(default:(river_)?water_(flowing|source)) [0-9 ]+$/\1 39 66 106 128 224/',
|
||||||
|
r's/^(default:lava_(flowing|source)) [0-9 ]+$/\1 255 100 0/',
|
||||||
|
# Transparency for glass nodes and panes
|
||||||
|
r's/^(default:.*glass) ([0-9 ]+)$/\1 \2 64 16/',
|
||||||
|
r's/^(doors:.*glass[^ ]*) ([0-9 ]+)$/\1 \2 64 16/',
|
||||||
|
r's/^(xpanes:.*(pane|bar)[^ ]*) ([0-9 ]+)$/\1 \3 64 16/',
|
||||||
|
]
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print("Usage: generate_colorstxt.py [options] [input file] [output file]")
|
||||||
|
print("If not specified the input file defaults to ./nodes.txt and the output file to ./colors.txt")
|
||||||
|
print(" -g / --game <folder>\t\tSet path to the game (for textures), required")
|
||||||
|
print(" -m / --mods <folder>\t\tAdd search path for mod textures")
|
||||||
|
print(" --replace <file>\t\tLoad replacements from file (ADVANCED)")
|
||||||
|
|
||||||
|
def collect_files(path):
|
||||||
|
dirs = []
|
||||||
|
with os.scandir(path) as it:
|
||||||
|
for entry in it:
|
||||||
|
if entry.name[0] == '.': continue
|
||||||
|
if entry.is_dir():
|
||||||
|
dirs.append(entry.path)
|
||||||
|
continue
|
||||||
|
if entry.is_file() and '.' in entry.name:
|
||||||
|
if entry.name not in textures.keys():
|
||||||
|
textures[entry.name] = entry.path
|
||||||
|
for path2 in dirs:
|
||||||
|
collect_files(path2)
|
||||||
|
|
||||||
|
def average_color(filename):
|
||||||
|
inp = Image.open(filename).convert('RGBA')
|
||||||
|
data = inp.load()
|
||||||
|
|
||||||
|
c0, c1, c2 = [], [], []
|
||||||
|
for x in range(inp.size[0]):
|
||||||
|
for y in range(inp.size[1]):
|
||||||
|
px = data[x, y]
|
||||||
|
if px[3] < 128: continue # alpha
|
||||||
|
c0.append(px[0]**2)
|
||||||
|
c1.append(px[1]**2)
|
||||||
|
c2.append(px[2]**2)
|
||||||
|
|
||||||
|
if len(c0) == 0:
|
||||||
|
print(f"didn't find color for '{os.path.basename(filename)}'", file=sys.stderr)
|
||||||
|
return "0 0 0"
|
||||||
|
c0 = sqrt(sum(c0) / len(c0))
|
||||||
|
c1 = sqrt(sum(c1) / len(c1))
|
||||||
|
c2 = sqrt(sum(c2) / len(c2))
|
||||||
|
return "%d %d %d" % (c0, c1, c2)
|
||||||
|
|
||||||
|
def apply_sed(line, exprs):
|
||||||
|
for expr in exprs:
|
||||||
|
if expr[0] == '/':
|
||||||
|
if not expr.endswith("/d"): raise ValueError()
|
||||||
|
if re.search(expr[1:-2], line):
|
||||||
|
return ''
|
||||||
|
elif expr[0] == 's':
|
||||||
|
expr = expr.split(expr[1])
|
||||||
|
if len(expr) != 4 or expr[3] != '': raise ValueError()
|
||||||
|
line = re.sub(expr[1], expr[2], line)
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
return line
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "hg:m:", ["help", "game=", "mods=", "replace="])
|
||||||
|
except getopt.GetoptError as e:
|
||||||
|
print(str(e))
|
||||||
|
exit(1)
|
||||||
|
if ('-h', '') in opts or ('--help', '') in opts:
|
||||||
|
usage()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
input_file = "./nodes.txt"
|
||||||
|
output_file = "./colors.txt"
|
||||||
|
texturepaths = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
gamepath = next(o[1] for o in opts if o[0] in ('-g', '--game'))
|
||||||
|
if not os.path.isdir(os.path.join(gamepath, "mods")):
|
||||||
|
print(f"'{gamepath}' doesn't exist or does not contain a game.", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
texturepaths.append(os.path.join(gamepath, "mods"))
|
||||||
|
except StopIteration:
|
||||||
|
print("No game path set but one is required. (see --help)", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tmp = next(o[1] for o in opts if o[0] == "--replace")
|
||||||
|
REPLACEMENTS.clear()
|
||||||
|
with open(tmp, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
if not line or line[0] == '#': continue
|
||||||
|
REPLACEMENTS.append(line.strip())
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for o in opts:
|
||||||
|
if o[0] not in ('-m', '--mods'): continue
|
||||||
|
if not os.path.isdir(o[1]):
|
||||||
|
print(f"Given path '{o[1]}' does not exist.'", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
texturepaths.append(o[1])
|
||||||
|
|
||||||
|
if len(args) > 2:
|
||||||
|
print("Too many arguments.", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
if len(args) > 1:
|
||||||
|
output_file = args[1]
|
||||||
|
if len(args) > 0:
|
||||||
|
input_file = args[0]
|
||||||
|
|
||||||
|
if not os.path.exists(input_file) or os.path.isdir(input_file):
|
||||||
|
print(f"Input file '{input_file}' does not exist.", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
print(f"Collecting textures from {len(texturepaths)} path(s)... ", end="", flush=True)
|
||||||
|
textures = {}
|
||||||
|
for path in texturepaths:
|
||||||
|
collect_files(path)
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
print("Processing nodes...")
|
||||||
|
fin = open(input_file, 'r')
|
||||||
|
fout = open(output_file, 'w')
|
||||||
|
n = 0
|
||||||
|
for line in fin:
|
||||||
|
line = line.rstrip('\r\n')
|
||||||
|
if not line or line[0] == '#':
|
||||||
|
fout.write(line + '\n')
|
||||||
|
continue
|
||||||
|
node, tex = line.split(" ")
|
||||||
|
if not tex or tex == "blank.png":
|
||||||
|
continue
|
||||||
|
elif tex not in textures.keys():
|
||||||
|
print(f"skip {node} texture not found")
|
||||||
|
continue
|
||||||
|
color = average_color(textures[tex])
|
||||||
|
line = f"{node} {color}"
|
||||||
|
#print(f"ok {node}")
|
||||||
|
line = apply_sed(line, REPLACEMENTS)
|
||||||
|
if line:
|
||||||
|
fout.write(line + '\n')
|
||||||
|
n += 1
|
||||||
|
fin.close()
|
||||||
|
fout.close()
|
||||||
|
print(f"Done, {n} entries written.")
|
Loading…
Reference in New Issue
Block a user