Merge pull request 'Overhaul Texture_Converter.py and Conversion_Table.csv' (#4133) from Impulse/MineClone2:texture-conversion-120 into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/4133
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
This commit is contained in:
the-real-herowl 2024-01-21 02:25:22 +00:00
commit b507838e13
15 changed files with 3030 additions and 1441 deletions

4
.gitignore vendored

@ -5,4 +5,6 @@
*.blend3
/.idea/
*.xcf
.Rproj.user
.Rproj.user
prompt.txt
__pycache__

File diff suppressed because it is too large Load Diff

@ -1,474 +1,42 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Texture Converter.
# Converts Minecraft resource packs to Minetest texture packs.
# See README.md.
# cli.py
__author__ = "Wuzzy"
__license__ = "MIT License"
__status__ = "Development"
import argparse
import sys
from libtextureconverter.gui import main as launch_gui
from libtextureconverter.config import SUPPORTED_MINECRAFT_VERSION, working_dir, appname, home
from libtextureconverter.utils import handle_default_minecraft_texture, find_all_minecraft_resourcepacks
from libtextureconverter.common import convert_resource_packs
import shutil, csv, os, tempfile, sys, getopt
def main():
make_texture_pack = True
parser = argparse.ArgumentParser(description=f"This is the official MineClone 2 Texture Converter. This will convert textures from Minecraft resource packs to a Minetest texture pack. Supported Minecraft version: {SUPPORTED_MINECRAFT_VERSION} (Java Edition)")
parser.add_argument("-i", "--input", help="Directory of Minecraft resource pack to convert")
parser.add_argument("-o", "--output", default=working_dir, help="Directory in which to put the resulting Minetest texture pack")
parser.add_argument("-p", "--pixel-size", type=int, help="Size (in pixels) of the original textures")
parser.add_argument("-d", "--dry-run", action="store_true", help="Pretend to convert textures without changing any files")
parser.add_argument("-v", "--verbose", action="store_true", help="Print out all copying actions")
parser.add_argument("-def", "--default", action="store_true", help="Use the default Minecraft texture pack")
parser.add_argument("-a", "--all", action="store_true", help="Convert all known Minecraft texturepacks")
args = parser.parse_args()
# Helper vars
home = os.environ["HOME"]
mineclone2_path = home + "/.minetest/games/mineclone2"
working_dir = os.getcwd()
appname = "Texture_Converter.py"
if len(sys.argv) == 1:
launch_gui()
else:
resource_packs = []
if args.default:
resource_packs.append(handle_default_minecraft_texture(home, args.output))
elif args.all:
resource_packs.extend(find_all_minecraft_resourcepacks())
elif args.input:
resource_packs.append(args.input)
### SETTINGS ###
output_dir = working_dir
if not resource_packs:
print(f"ERROR: No valid resource packs specified. Use '{appname} -h' for help.")
sys.exit(2)
base_dir = None
convert_resource_packs(resource_packs, args.output, args.pixel_size, args.dry_run, args.verbose, make_texture_pack)
# If True, will only make console output but not convert anything.
dry_run = False
# If True, textures will be put into a texture pack directory structure.
# If False, textures will be put into MineClone 2 directories.
make_texture_pack = True
# If True, prints all copying actions
verbose = False
PXSIZE = 16
syntax_help = appname+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
Mandatory argument:
-i <input directory>
Directory of Minecraft resource pack to convert
Optional arguments:
-p <texture size>
Specify the size (in pixels) of the original textures (default: 16)
-o <output directory>
Directory in which to put the resulting Minetest texture pack
(default: working directory)
-d
Just pretend to convert textures and just print output, but do not actually
change any files.
-v
Print out all copying actions
-h
Show this help and exit"""
try:
opts, args = getopt.getopt(sys.argv[1:],"hi:o:p:dv")
except getopt.GetoptError:
print(
"""ERROR! The options you gave me make no sense!
Here's the syntax reference:""")
print(syntax_help)
sys.exit(2)
for opt, arg in opts:
if opt == "-h":
print(
"""This is the official MineClone 2 Texture Converter.
This will convert textures from Minecraft resource packs to
a Minetest texture pack.
Supported Minecraft version: 1.12 (Java Edition)
Syntax:""")
print(syntax_help)
sys.exit()
elif opt == "-d":
dry_run = True
elif opt == "-v":
verbose = True
elif opt == "-i":
base_dir = arg
elif opt == "-o":
output_dir = arg
elif opt == "-p":
PXSIZE = int(arg)
if base_dir == None:
print(
"""ERROR: You didn't tell me the path to the Minecraft resource pack.
Mind-reading has not been implemented yet.
Try this:
"""+appname+""" -i <path to resource pack> -p <texture size>
For the full help, use:
"""+appname+""" -h""")
sys.exit(2);
### END OF SETTINGS ###
tex_dir = base_dir + "/assets/minecraft/textures"
# Get texture pack name (from directory name)
bdir_split = base_dir.split("/")
output_dir_name = bdir_split[-1]
if len(output_dir_name) == 0:
if len(bdir_split) >= 2:
output_dir_name = base_dir.split("/")[-2]
else:
# Fallback
output_dir_name = "New_MineClone_2_Texture_Pack"
# FUNCTION DEFINITIONS
def colorize(colormap, source, colormap_pixel, texture_size, destination):
os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 -resize "+texture_size+"x"+texture_size+" "+tempfile1.name)
os.system("composite -compose Multiply "+tempfile1.name+" "+source+" "+destination)
def colorize_alpha(colormap, source, colormap_pixel, texture_size, destination):
colorize(colormap, source, colormap_pixel, texture_size, tempfile2.name)
os.system("composite -compose Dst_In "+source+" "+tempfile2.name+" -alpha Set "+destination)
def target_dir(directory):
if make_texture_pack:
return output_dir + "/" + output_dir_name
else:
return mineclone2_path + directory
# Copy texture files
def convert_textures():
failed_conversions = 0
print("Texture conversion BEGINS NOW!")
with open("Conversion_Table.csv", newline="") as csvfile:
reader = csv.reader(csvfile, delimiter=",", quotechar='"')
first_row = True
for row in reader:
# Skip first row
if first_row:
first_row = False
continue
src_dir = row[0]
src_filename = row[1]
dst_dir = row[2]
dst_filename = row[3]
if row[4] != "":
xs = int(row[4])
ys = int(row[5])
xl = int(row[6])
yl = int(row[7])
xt = int(row[8])
yt = int(row[9])
else:
xs = None
blacklisted = row[10]
if blacklisted == "y":
# Skip blacklisted files
continue
if make_texture_pack == False and dst_dir == "":
# If destination dir is empty, this texture is not supposed to be used in MCL2
# (but maybe an external mod). It should only be used in texture packs.
# Otherwise, it must be ignored.
# Example: textures for mcl_supplemental
continue
src_file = base_dir + src_dir + "/" + src_filename # source file
src_file_exists = os.path.isfile(src_file)
dst_file = target_dir(dst_dir) + "/" + dst_filename # destination file
if src_file_exists == False:
print("WARNING: Source file does not exist: "+src_file)
failed_conversions = failed_conversions + 1
continue
if xs != None:
# Crop and copy images
if not dry_run:
os.system("convert "+src_file+" -crop "+xl+"x"+yl+"+"+xs+"+"+ys+" "+dst_file)
if verbose:
print(src_file + "" + dst_file)
else:
# Copy image verbatim
if not dry_run:
shutil.copy2(src_file, dst_file)
if verbose:
print(src_file + "" + dst_file)
# Convert map background
map_background_file = tex_dir + "/map/map_background.png"
if os.path.isfile(map_background_file):
os.system("convert " + map_background_file + " -interpolate Integer -filter point -resize \"140x140\" " + target_dir("/mods/ITEMS/mcl_maps/textures") + "/mcl_maps_map_background.png")
# Convert armor textures (requires ImageMagick)
armor_files = [
[ tex_dir + "/models/armor/leather_layer_1.png", tex_dir + "/models/armor/leather_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_leather.png", "mcl_armor_chestplate_leather.png", "mcl_armor_leggings_leather.png", "mcl_armor_boots_leather.png" ],
[ tex_dir + "/models/armor/chainmail_layer_1.png", tex_dir + "/models/armor/chainmail_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_chain.png", "mcl_armor_chestplate_chain.png", "mcl_armor_leggings_chain.png", "mcl_armor_boots_chain.png" ],
[ tex_dir + "/models/armor/gold_layer_1.png", tex_dir + "/models/armor/gold_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_gold.png", "mcl_armor_chestplate_gold.png", "mcl_armor_leggings_gold.png", "mcl_armor_boots_gold.png" ],
[ tex_dir + "/models/armor/iron_layer_1.png", tex_dir + "/models/armor/iron_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_iron.png", "mcl_armor_chestplate_iron.png", "mcl_armor_leggings_iron.png", "mcl_armor_boots_iron.png" ],
[ tex_dir + "/models/armor/diamond_layer_1.png", tex_dir + "/models/armor/diamond_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_diamond.png", "mcl_armor_chestplate_diamond.png", "mcl_armor_leggings_diamond.png", "mcl_armor_boots_diamond.png" ],
[ tex_dir + "/models/armor/netherite_layer_1.png", tex_dir + "/models/armor/netherite_layer_2.png", target_dir("/mods/ITEMS/mcl_armor/textures"), "mcl_armor_helmet_netherite.png", "mcl_armor_chestplate_netherite.png", "mcl_armor_leggings_netherite.png", "mcl_armor_boots_netherite.png" ]
]
for a in armor_files:
APXSIZE = 16 # for some reason MineClone2 requires this
layer_1 = a[0]
layer_2 = a[1]
adir = a[2]
if os.path.isfile(layer_1):
helmet = adir + "/" + a[3]
chestplate = adir + "/" + a[4]
boots = adir + "/" + a[6]
os.system("convert -size "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" xc:none \\( "+layer_1+" -scale "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" -geometry +"+str(APXSIZE * 2)+"+0 -crop "+str(APXSIZE * 2)+"x"+str(APXSIZE)+"+0+0 \) -composite -channel A -fx \"(a > 0.0) ? 1.0 : 0.0\" "+helmet)
os.system("convert -size "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" xc:none \\( "+layer_1+" -scale "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" -geometry +"+str(APXSIZE)+"+"+str(APXSIZE)+" -crop "+str(APXSIZE * 2.5)+"x"+str(APXSIZE)+"+"+str(APXSIZE)+"+"+str(APXSIZE)+" \) -composite -channel A -fx \"(a > 0.0) ? 1.0 : 0.0\" "+chestplate)
os.system("convert -size "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" xc:none \\( "+layer_1+" -scale "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" -geometry +0+"+str(APXSIZE)+" -crop "+str(APXSIZE)+"x"+str(APXSIZE)+"+0+"+str(APXSIZE)+" \) -composite -channel A -fx \"(a > 0.0) ? 1.0 : 0.0\" "+boots)
if os.path.isfile(layer_2):
leggings = adir + "/" + a[5]
os.system("convert -size "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" xc:none \\( "+layer_2+" -scale "+str(APXSIZE * 4)+"x"+str(APXSIZE * 2)+" -geometry +0+"+str(APXSIZE)+" -crop "+str(APXSIZE * 2.5)+"x"+str(APXSIZE)+"+0+"+str(APXSIZE)+" \) -composite -channel A -fx \"(a > 0.0) ? 1.0 : 0.0\" "+leggings)
# Convert chest textures (requires ImageMagick)
chest_files = [
[ tex_dir + "/entity/chest/normal.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_top.png", "mcl_chests_chest_bottom.png", "default_chest_front.png", "mcl_chests_chest_left.png", "mcl_chests_chest_right.png", "mcl_chests_chest_back.png" ],
[ tex_dir + "/entity/chest/trapped.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png", "mcl_chests_chest_trapped_front.png", "mcl_chests_chest_trapped_left.png", "mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_back.png" ],
[ tex_dir + "/entity/chest/ender.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png", "mcl_chests_ender_chest_front.png", "mcl_chests_ender_chest_left.png", "mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_back.png" ]
]
for c in chest_files:
chest_file = c[0]
if os.path.isfile(chest_file):
PPX = (PXSIZE/16)
CHPX = (PPX * 14) # Chest width
LIDPX = (PPX * 5) # Lid height
LIDLOW = (PPX * 10) # Lower lid section height
LOCKW = (PPX * 6) # Lock width
LOCKH = (PPX * 5) # Lock height
cdir = c[1]
top = cdir + "/" + c[2]
bottom = cdir + "/" + c[3]
front = cdir + "/" + c[4]
left = cdir + "/" + c[5]
right = cdir + "/" + c[6]
back = cdir + "/" + c[7]
# Top
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+top)
# Bottom
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX*2)+"+"+str(CHPX+LIDPX)+" \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+bottom)
# Front
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
-extent "+str(CHPX)+"x"+str(CHPX)+" "+front)
# TODO: Add lock
# Left, right back (use same texture, we're lazy
files = [ left, right, back ]
for f in files:
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
-extent "+str(CHPX)+"x"+str(CHPX)+" "+f)
# Double chests
chest_files = [
[ tex_dir + "/entity/chest/normal_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "default_chest_front_big.png", "default_chest_top_big.png", "default_chest_side_big.png" ],
[ tex_dir + "/entity/chest/trapped_double.png", target_dir("/mods/ITEMS/mcl_chests/textures"), "mcl_chests_chest_trapped_front_big.png", "mcl_chests_chest_trapped_top_big.png", "mcl_chests_chest_trapped_side_big.png" ]
]
for c in chest_files:
chest_file = c[0]
if os.path.isfile(chest_file):
PPX = (PXSIZE/16)
CHPX = (PPX * 14) # Chest width (short side)
CHPX2 = (PPX * 15) # Chest width (long side)
LIDPX = (PPX * 5) # Lid height
LIDLOW = (PPX * 10) # Lower lid section height
LOCKW = (PPX * 6) # Lock width
LOCKH = (PPX * 5) # Lock height
cdir = c[1]
front = cdir + "/" + c[2]
top = cdir + "/" + c[3]
side = cdir + "/" + c[4]
# Top
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX2)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2)+"x"+str(CHPX)+" "+top)
# Front
# TODO: Add lock
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
\( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
-extent "+str(CHPX2)+"x"+str(CHPX)+" "+front)
# Side
os.system("convert " + chest_file + " \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
\( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
-extent "+str(CHPX)+"x"+str(CHPX)+" "+side)
# Generate railway crossings and t-junctions. Note: They may look strange.
# Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
# TODO: Curves
rails = [
# (Straigt src, curved src, t-junction dest, crossing dest)
("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
]
for r in rails:
os.system("composite -compose Dst_Over "+tex_dir+"/blocks/"+r[0]+" "+tex_dir+"/blocks/"+r[1]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[2])
os.system("convert "+tex_dir+"/blocks/"+r[0]+" -rotate 90 "+tempfile1.name)
os.system("composite -compose Dst_Over "+tempfile1.name+" "+tex_dir+"/blocks/"+r[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[3])
# Convert banner overlays
overlays = [
"base",
"border",
"bricks",
"circle",
"creeper",
"cross",
"curly_border",
"diagonal_left",
"diagonal_right",
"diagonal_up_left",
"diagonal_up_right",
"flower",
"gradient",
"gradient_up",
"half_horizontal_bottom",
"half_horizontal",
"half_vertical",
"half_vertical_right",
"rhombus",
"mojang",
"skull",
"small_stripes",
"straight_cross",
"stripe_bottom",
"stripe_center",
"stripe_downleft",
"stripe_downright",
"stripe_left",
"stripe_middle",
"stripe_right",
"stripe_top",
"square_bottom_left",
"square_bottom_right",
"square_top_left",
"square_top_right",
"triangle_bottom",
"triangles_bottom",
"triangle_top",
"triangles_top",
]
for o in overlays:
orig = tex_dir + "/entity/banner/" + o + ".png"
if os.path.isfile(orig):
if o == "mojang":
o = "thing"
dest = target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o+".png"
os.system("convert "+orig+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest)
# Convert grass
grass_file = tex_dir + "/blocks/grass_top.png"
if os.path.isfile(grass_file):
FOLIAG = tex_dir+"/colormap/foliage.png"
GRASS = tex_dir+"/colormap/grass.png"
# Leaves
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_oak.png", "116+143", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_big_oak.png", "158+177", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_big_oak.png")
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_spruce.png", "226+230", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_spruce.png")
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_birch.png", "141+186", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_leaves_birch.png")
colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
# Waterlily
colorize_alpha(FOLIAG, tex_dir+"/blocks/waterlily.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
# Vines
colorize_alpha(FOLIAG, tex_dir+"/blocks/vine.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
# Tall grass, fern (inventory images)
pcol = "50+173" # Plains grass color
colorize_alpha(GRASS, tex_dir+"/blocks/tallgrass.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
colorize_alpha(GRASS, tex_dir+"/blocks/fern.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_fern_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_fern_inv.png")
colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_grass_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_grass_inv.png")
# Convert grass palette: https://minecraft.fandom.com/wiki/Tint
grass_colors = [
# [Coords or #Color, AdditionalTint], # Index - Minecraft biome name (MineClone2 biome names)
["50+173"], # 0 - Plains (flat, Plains, Plains_beach, Plains_ocean, End)
["0+255"], # 1 - Savanna (Savanna, Savanna_beach, Savanna_ocean)
["255+255"], # 2 - Ice Spikes (IcePlainsSpikes, IcePlainsSpikes_ocean)
["255+255"], # 3 - Snowy Taiga (ColdTaiga, ColdTaiga_beach, ColdTaiga_beach_water, ColdTaiga_ocean)
["178+193"], # 4 - Giant Tree Taiga (MegaTaiga, MegaTaiga_ocean)
["178+193"], # 5 - Giant Tree Taiga (MegaSpruceTaiga, MegaSpruceTaiga_ocean)
["203+239"], # 6 - Montains (ExtremeHills, ExtremeHills_beach, ExtremeHills_ocean)
["203+239"], # 7 - Montains (ExtremeHillsM, ExtremeHillsM_ocean)
["203+239"], # 8 - Montains (ExtremeHills+, ExtremeHills+_snowtop, ExtremeHills+_ocean)
["50+173"], # 9 - Beach (StoneBeach, StoneBeach_ocean)
["255+255"], # 10 - Snowy Tundra (IcePlains, IcePlains_ocean)
["50+173"], # 11 - Sunflower Plains (SunflowerPlains, SunflowerPlains_ocean)
["191+203"], # 12 - Taiga (Taiga, Taiga_beach, Taiga_ocean)
["76+112"], # 13 - Forest (Forest, Forest_beach, Forest_ocean)
["76+112"], # 14 - Flower Forest (FlowerForest, FlowerForest_beach, FlowerForest_ocean)
["101+163"], # 15 - Birch Forest (BirchForest, BirchForest_ocean)
["101+163"], # 16 - Birch Forest Hills (BirchForestM, BirchForestM_ocean)
["0+255"], # 17 - Desert and Nether (Desert, Desert_ocean, Nether)
["76+112", "#28340A"], # 18 - Dark Forest (RoofedForest, RoofedForest_ocean)
["#90814d"], # 19 - Mesa (Mesa, Mesa_sandlevel, Mesa_ocean, )
["#90814d"], # 20 - Mesa (MesaBryce, MesaBryce_sandlevel, MesaBryce_ocean)
["#90814d"], # 21 - Mesa (MesaPlateauF, MesaPlateauF_grasstop, MesaPlateauF_sandlevel, MesaPlateauF_ocean)
["#90814d"], # 22 - Mesa (MesaPlateauFM, MesaPlateauFM_grasstop, MesaPlateauFM_sandlevel, MesaPlateauFM_ocean)
["0+255"], # 23 - Shattered Savanna (or Savanna Plateau ?) (SavannaM, SavannaM_ocean)
["12+36"], # 24 - Jungle (Jungle, Jungle_shore, Jungle_ocean)
["12+36"], # 25 - Modified Jungle (JungleM, JungleM_shore, JungleM_ocean)
["12+61"], # 26 - Jungle Edge (JungleEdge, JungleEdge_ocean)
["12+61"], # 27 - Modified Jungle Edge (JungleEdgeM, JungleEdgeM_ocean)
["#6A7039"], # 28 - Swamp (Swampland, Swampland_shore, Swampland_ocean)
["25+25"], # 29 - Mushroom Fields and Mushroom Field Shore (MushroomIsland, MushroomIslandShore, MushroomIsland_ocean)
]
grass_palette_file = target_dir("/mods/ITEMS/mcl_core/textures") + "/mcl_core_palette_grass.png"
os.system("convert -size 16x16 canvas:transparent " + grass_palette_file)
for i, color in enumerate(grass_colors):
if color[0][0] == "#":
os.system("convert -size 1x1 xc:\"" + color[0] + "\" " + tempfile1.name + ".png")
else:
os.system("convert " + GRASS + " -crop 1x1+" + color[0] + " " + tempfile1.name + ".png")
if len(color) > 1:
os.system("convert " + tempfile1.name + ".png \\( -size 1x1 xc:\"" + color[1] + "\" \\) -compose blend -define compose:args=50,50 -composite " + tempfile1.name + ".png")
os.system("convert " + grass_palette_file + " \\( " + tempfile1.name + ".png -geometry +" + str(i % 16) + "+" + str(int(i / 16)) + " \\) -composite " + grass_palette_file)
# Metadata
if make_texture_pack:
# Create description file
description = "Texture pack for MineClone 2. Automatically converted from a Minecraft resource pack by the MineClone 2 Texture Converter. Size: "+str(PXSIZE)+"×"+str(PXSIZE)
description_file = open(target_dir("/") + "/description.txt", "w")
description_file.write(description)
description_file.close()
# Create preview image (screenshot.png)
os.system("convert -size 300x200 canvas:transparent "+target_dir("/") + "/screenshot.png")
os.system("composite "+base_dir+"/pack.png "+target_dir("/") + "/screenshot.png -gravity center "+target_dir("/") + "/screenshot.png")
print("Textures conversion COMPLETE!")
if failed_conversions > 0:
print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions))
print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
if make_texture_pack:
print("You can now retrieve the texture pack in "+output_dir+"/"+output_dir_name+"/")
# ENTRY POINT
if make_texture_pack and not os.path.isdir(output_dir+"/"+output_dir_name):
os.mkdir(output_dir+"/"+output_dir_name)
tempfile1 = tempfile.NamedTemporaryFile()
tempfile2 = tempfile.NamedTemporaryFile()
convert_textures()
tempfile1.close()
tempfile2.close()
if __name__ == "__main__":
main()

@ -0,0 +1,68 @@
import shutil
import csv
import os
import tempfile
import sys
import argparse
import glob
from PIL import Image
from collections import Counter
from libtextureconverter.utils import detect_pixel_size, target_dir, colorize, colorize_alpha, handle_default_minecraft_texture, find_all_minecraft_resourcepacks
from libtextureconverter.convert import convert_textures
from libtextureconverter.config import SUPPORTED_MINECRAFT_VERSION, working_dir, mineclone2_path, appname, home
def convert_resource_packs(
resource_packs,
output_dir,
PXSIZE,
dry_run,
verbose,
make_texture_pack):
for base_dir in resource_packs:
print(f"Converting resource pack: {base_dir}")
# Autodetect pixel size if not provided
if not PXSIZE:
pixel_size = detect_pixel_size(base_dir)
else:
pixel_size = PXSIZE
# Construct the path to the textures within the resource pack
tex_dir = os.path.join(base_dir, "assets", "minecraft", "textures")
# Determine the name of the output directory for the converted texture
# pack
output_dir_name = os.path.basename(os.path.normpath(base_dir))
# Create the output directory if it doesn't exist
output_path = os.path.join(output_dir, output_dir_name)
if not os.path.isdir(output_path):
os.makedirs(output_path, exist_ok=True)
# Temporary files for conversion (if needed by your conversion process)
tempfile1 = tempfile.NamedTemporaryFile(delete=False)
tempfile2 = tempfile.NamedTemporaryFile(delete=False)
try:
# Perform the actual conversion
convert_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
pixel_size)
finally:
# Clean up temporary files
tempfile1.close()
os.unlink(tempfile1.name)
tempfile2.close()
os.unlink(tempfile2.name)
print(f"Finished converting resource pack: {base_dir}")

@ -0,0 +1,30 @@
import os
import platform
def get_minetest_directory():
system = platform.system()
# Windows
if system == 'Windows':
return os.environ.get('MINETEST_USER_PATH', os.path.expandvars('%APPDATA%\\Minetest'))
# Linux
elif system == 'Linux':
return os.environ.get('MINETEST_USER_PATH', os.path.expanduser('~/.minetest'))
# macOS
elif system == 'Darwin': # Darwin is the system name for macOS
return os.environ.get('MINETEST_USER_PATH', os.path.expanduser('~/Library/Application Support/minetest'))
# Unsupported system
else:
return None
# Constants
SUPPORTED_MINECRAFT_VERSION = "1.20"
# Helper vars
home = os.environ["HOME"]
mineclone2_path = os.path.join(get_minetest_directory(),"games","mineclone2")
working_dir = os.getcwd()
appname = "Texture_Converter.py"

@ -0,0 +1,134 @@
from .special_convert_cases import convert_map_textures, convert_armor_textures, convert_chest_textures, convert_rail_textures, convert_banner_overlays, convert_grass_textures
from .utils import target_dir, colorize, colorize_alpha
import shutil
import csv
import os
import tempfile
import sys
import argparse
import glob
def convert_standard_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
failed_conversions = 0
with open("Conversion_Table.csv", newline="") as csvfile:
reader = csv.reader(csvfile, delimiter=",", quotechar='"')
first_row = True
for row in reader:
# Skip first row
if first_row:
first_row = False
continue
src_dir = row[0]
src_filename = row[1]
dst_dir = './textures'
dst_filename = row[2]
if row[4] != "":
xs = int(row[3])
ys = int(row[4])
xl = int(row[5])
yl = int(row[6])
xt = int(row[7])
yt = int(row[8])
else:
xs = None
blacklisted = row[9]
if blacklisted == "y":
# Skip blacklisted files
continue
if make_texture_pack == False and dst_dir == "":
# If destination dir is empty, this texture is not supposed to be used in MCL2
# (but maybe an external mod). It should only be used in texture packs.
# Otherwise, it must be ignored.
# Example: textures for mcl_supplemental
continue
src_file = base_dir + src_dir + "/" + src_filename # source file
src_file_exists = os.path.isfile(src_file)
dst_file = target_dir(dst_dir, make_texture_pack, output_dir, output_dir_name,
mineclone2_path) + "/" + dst_filename # destination file
if src_file_exists == False:
print("WARNING: Source file does not exist: " + src_file)
failed_conversions = failed_conversions + 1
continue
if xs != None:
# Crop and copy images
if not dry_run:
crop_width = int(xl)
crop_height = int(yl)
offset_x = int(xs)
offset_y = int(ys)
with Image(filename=src_file) as img:
# Crop the image
img.crop(left=offset_x, top=offset_y, width=crop_width, height=crop_height)
# Save the result
img.save(filename=dst_file)
if verbose:
print(src_file + "" + dst_file)
else:
# Copy image verbatim
if not dry_run:
shutil.copy2(src_file, dst_file)
if verbose:
print(src_file + "" + dst_file)
return failed_conversions
def convert_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2, output_dir, output_dir_name, mineclone2_path, PXSIZE):
print("Texture conversion BEGINS NOW!")
# Convert textures listed in the Conversion_Table.csv
failed_conversions = convert_standard_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir,
tempfile1, tempfile2, output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Conversion of map backgrounds
convert_map_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir,
tempfile1, tempfile2, output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Convert armor textures
convert_armor_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2,output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Convert chest textures
convert_chest_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2,output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Generate railway crossings and t-junctions
convert_rail_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2,output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Convert banner overlays
convert_banner_overlays(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2,output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Convert grass and related textures
convert_grass_textures(make_texture_pack, dry_run, verbose, base_dir, tex_dir, tempfile1, tempfile2,output_dir, output_dir_name, mineclone2_path, PXSIZE)
# Metadata
if make_texture_pack:
# Create description file
description = "Texture pack for MineClone 2. Automatically converted from a Minecraft resource pack by the MineClone 2 Texture Converter. Size: "+str(PXSIZE)+"×"+str(PXSIZE)
description_file = open(target_dir("/", make_texture_pack, output_dir, output_dir_name, mineclone2_path) + "/description.txt", "w")
description_file.write(description)
description_file.close()
# Create preview image (screenshot.png)
os.system("convert -size 300x200 canvas:transparent "+target_dir("/", make_texture_pack, output_dir, output_dir_name, mineclone2_path) + "/screenshot.png")
os.system("composite "+base_dir+"/pack.png "+target_dir("/", make_texture_pack, output_dir, output_dir_name, mineclone2_path) + "/screenshot.png -gravity center "+target_dir("/", make_texture_pack, output_dir, output_dir_name, mineclone2_path) + "/screenshot.png")
print("Textures conversion COMPLETE!")
if failed_conversions > 0:
print("WARNING: Number of missing files in original resource pack: " + str(failed_conversions))
print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
if make_texture_pack:
print("You can now retrieve the texture pack in " + output_dir + "/" + output_dir_name + "/")

@ -0,0 +1,198 @@
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, font
from libtextureconverter.utils import handle_default_minecraft_texture, find_all_minecraft_resourcepacks
from libtextureconverter.config import home, get_minetest_directory
from libtextureconverter.common import convert_resource_packs
import time
import os
import threading
class TextureConverterGUI:
def __init__(self, root):
self.root = root
self.root.title("Choose resource packs to convert")
self.create_widgets()
def create_widgets(self):
# Frame for instructions
self.instruction_frame = tk.Frame(self.root)
self.instruction_frame.pack(fill='x', padx=10, pady=10)
tk.Label(
self.instruction_frame,
text="Do you want to convert installed resource packs, or convert a single folder?").pack(
side='left',
fill='x',
expand=True)
# Table-like structure using Treeview
self.tree = ttk.Treeview(self.root, columns=(
'Convert', 'Description'), show='headings')
self.tree.heading('Convert', text='Convert')
self.tree.heading('Description', text='Description')
# Inserting options into the table
entries = [
('all', 'Find Minecraft resource packs installed in your minecraft folders and convert those automatically'),
('default', 'Convert the default resource pack'),
('other', 'Choose a folder to convert manually')
]
for entry in entries:
self.tree.insert('', 'end', values=entry)
# Button Frame
self.button_frame = tk.Frame(self.root)
# Ensure the buttons are at the bottom
self.button_frame.pack(fill='x', padx=10, pady=10, side='bottom')
# Create and pack the buttons separately
self.ok_button = tk.Button(
self.button_frame, text="OK", command=self.confirm_selection)
self.ok_button.pack(side=tk.RIGHT, padx=5)
self.cancel_button = tk.Button(
self.button_frame, text="Cancel", command=self.cancel_conversion)
self.cancel_button.pack(side=tk.RIGHT)
self.tree.pack(fill='both', expand=True, padx=10, pady=10)
self.root.after(1, self.adjust_column_widths)
def adjust_column_widths(self):
self.root.update_idletasks() # Update the geometry of the widgets
# Measure and set the column widths
convert_width = tk.font.Font().measure('Convert') + 20
description_width = max(
tk.font.Font().measure(
self.tree.set(
item,
'Description')) for item in self.tree.get_children()) + 20
# Apply the column widths
self.tree.column('Convert', width=convert_width, anchor='center')
self.tree.column('Description', width=description_width, anchor='w')
# Calculate the height for each row
row_height = tk.font.Font().metrics('linespace') + 2
# Adjust the Treeview height
num_items = len(self.tree.get_children())
tree_height = (row_height * num_items) * 1.8
self.tree.config(height=num_items)
# Calculate the total height needed
total_height = self.instruction_frame.winfo_height(
) + self.button_frame.winfo_height() + tree_height + 20
# Calculate the total width needed
total_width = convert_width + description_width + 20
# Set the size of the window based on content
self.root.geometry(f"{int(total_width)}x{int(total_height)}")
# Prevent the window from resizing smaller than it should
self.root.minsize(int(total_width), int(total_height))
# Update the idle tasks to recalculate sizes, may help to remove extra
# space
self.root.update_idletasks()
def confirm_selection(self):
self.cancel_button.config(state=tk.NORMAL)
selected_item = self.tree.focus()
selection = self.tree.item(selected_item)
option = selection['values'][0]
self.show_loading_screen(option)
def set_min_window_size(self):
self.root.update_idletasks() # Update the geometry of the widgets
self.root.minsize(self.root.winfo_width(), self.root.winfo_height())
def show_loading_screen(self, option):
# Display a non-blocking loading message
self.loading_label = tk.Label(
self.root, text="Converting textures, please wait...", fg="blue")
self.loading_label.pack()
# Start the conversion process in a separate thread
conversion_thread = threading.Thread(
target=self.perform_conversion, args=(option,), daemon=True)
conversion_thread.start()
# Disable the OK button while the conversion is in progress
self.ok_button.config(state=tk.DISABLED)
self.cancel_button.config(state=tk.NORMAL)
def perform_conversion(self, option):
# Set default values for pixelsize, dry_run, and verbose
pixelsize = None
dry_run = False
verbose = False
output_dir = os.path.join(get_minetest_directory(), "textures")
make_texture_pack = True
# Determine the resource packs to convert based on the option
if option == 'all':
resource_packs = find_all_minecraft_resourcepacks()
elif option == 'default':
resource_packs = [
handle_default_minecraft_texture(home, output_dir)]
elif option == 'other':
folder_selected = filedialog.askdirectory()
if folder_selected:
resource_packs = [folder_selected]
else:
# User canceled the folder selection
self.loading_label.pack_forget()
self.ok_button.config(state=tk.NORMAL)
return
# Convert resource packs
convert_resource_packs(resource_packs, output_dir,
pixelsize, dry_run, verbose, make_texture_pack)
# Update the GUI after conversion
self.loading_label.pack_forget()
self.ok_button.config(state=tk.NORMAL)
messagebox.showinfo(
"Conversion Complete",
f"Resource Packs '{', '.join(resource_packs)}' converted.")
def convert_all(self):
# Simulate a conversion process
print("Converting all resource packs")
time.sleep(2) # Simulate some time for conversion
def convert_default(self):
# Simulate a conversion process
print("Converting default resource pack")
time.sleep(2) # Simulate some time for conversion
def open_folder_dialog(self):
folder_selected = filedialog.askdirectory()
if folder_selected:
# Simulate a conversion process
print(f"Folder selected for conversion: {folder_selected}")
time.sleep(2) # Simulate some time for conversion
def cancel_conversion(self):
# Placeholder for cancel action, you may need to implement actual
# cancellation logic
print("Conversion cancelled by user.")
self.loading_label.pack_forget()
self.ok_button.config(state=tk.NORMAL)
self.cancel_button.config(state=tk.DISABLED)
def main():
root = tk.Tk()
app = TextureConverterGUI(root)
app.adjust_column_widths()
root.mainloop()
if __name__ == "__main__":
main()

@ -0,0 +1,809 @@
import os
from .utils import target_dir, colorize, colorize_alpha
import shutil
import csv
import tempfile
import sys
import argparse
import glob
from wand.image import Image
from wand.color import Color
from wand.display import display
from wand.drawing import Drawing
import warnings
# Conversion of map backgrounds
def convert_map_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Convert map background
map_background_file = tex_dir + "/map/map_background.png"
if os.path.isfile(map_background_file):
destination_path = target_dir("/mods/ITEMS/mcl_maps/textures", make_texture_pack, output_dir, output_dir_name, mineclone2_path) + "/mcl_maps_map_background.png"
with Image(filename=map_background_file) as img:
# Resize the image with 'point' filter
img.resize(140, 140, filter='point')
# Save the result
img.save(filename=destination_path)
# Convert armor textures
def convert_armor_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Convert armor textures (requires ImageMagick)
armor_files = [[tex_dir + "/models/armor/leather_layer_1.png",
tex_dir + "/models/armor/leather_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_leather.png",
"mcl_armor_chestplate_leather.png",
"mcl_armor_leggings_leather.png",
"mcl_armor_boots_leather.png"],
[tex_dir + "/models/armor/chainmail_layer_1.png",
tex_dir + "/models/armor/chainmail_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_chain.png",
"mcl_armor_chestplate_chain.png",
"mcl_armor_leggings_chain.png",
"mcl_armor_boots_chain.png"],
[tex_dir + "/models/armor/gold_layer_1.png",
tex_dir + "/models/armor/gold_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_gold.png",
"mcl_armor_chestplate_gold.png",
"mcl_armor_leggings_gold.png",
"mcl_armor_boots_gold.png"],
[tex_dir + "/models/armor/iron_layer_1.png",
tex_dir + "/models/armor/iron_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_iron.png",
"mcl_armor_chestplate_iron.png",
"mcl_armor_leggings_iron.png",
"mcl_armor_boots_iron.png"],
[tex_dir + "/models/armor/diamond_layer_1.png",
tex_dir + "/models/armor/diamond_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_diamond.png",
"mcl_armor_chestplate_diamond.png",
"mcl_armor_leggings_diamond.png",
"mcl_armor_boots_diamond.png"],
[tex_dir + "/models/armor/netherite_layer_1.png",
tex_dir + "/models/armor/netherite_layer_2.png",
target_dir("/mods/ITEMS/mcl_armor/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_armor_helmet_netherite.png",
"mcl_armor_chestplate_netherite.png",
"mcl_armor_leggings_netherite.png",
"mcl_armor_boots_netherite.png"]]
for a in armor_files:
APXSIZE = 16 # for some reason MineClone2 requires this
layer_1 = a[0]
layer_2 = a[1]
adir = a[2]
if os.path.isfile(layer_1):
helmet = adir + "/" + a[3]
chestplate = adir + "/" + a[4]
boots = adir + "/" + a[6]
# helmet
os.system("convert -size " +
str(APXSIZE *
4) +
"x" +
str(APXSIZE *
2) +
" xc:none \\( " +
layer_1 +
" -scale " +
str(APXSIZE *
4) +
"x" +
str(APXSIZE *
2) +
" -geometry +" +
str(APXSIZE *
2) +
"+0 -crop " +
str(APXSIZE *
2) +
"x" +
str(APXSIZE) +
"+0+0 \\) -composite -channel A -fx \"(a > 0.0) ? 1.0 : 0.0\" " +
helmet)
# chestplate
with Image(width=APXSIZE * 4, height=APXSIZE * 2, background=Color('none')) as img:
# Load layer_1 and scale
with Image(filename=layer_1) as layer1:
layer1.resize(APXSIZE * 4, APXSIZE * 2)
# Define the crop geometry
crop_width = int(APXSIZE * 2.5)
crop_height = APXSIZE
crop_x = APXSIZE
crop_y = APXSIZE
# Crop the image
layer1.crop(crop_x, crop_y, width=crop_width, height=crop_height)
# Composite layer1 over the transparent image
img.composite(layer1, APXSIZE, APXSIZE)
# Apply channel operation
img.fx("a > 0.0 ? 1.0 : 0.0", channel='alpha')
# Save the result
img.save(filename=chestplate)
with Image(width=APXSIZE * 4, height=APXSIZE * 2, background=Color('none')) as img:
with Image(filename=layer_1) as layer1:
# Scale the image
layer1.resize(APXSIZE * 4, APXSIZE * 2)
# Crop the image
crop_x = 0
crop_y = APXSIZE
crop_width = APXSIZE
crop_height = APXSIZE
layer1.crop(crop_x, crop_y, width=crop_width, height=crop_height)
# Composite the cropped image over the transparent image
img.composite(layer1, 0, APXSIZE)
# Apply the channel operation
img.fx("a > 0.0 ? 1.0 : 0.0", channel='alpha')
# Save the result
img.save(filename=boots)
if os.path.isfile(layer_2):
leggings = adir + "/" + a[5]
with Image(width=APXSIZE * 4, height=APXSIZE * 2, background=Color('none')) as img:
with Image(filename=layer_2) as layer2:
# Scale the image
layer2.resize(APXSIZE * 4, APXSIZE * 2)
# Apply geometry and crop
crop_width = int(APXSIZE * 2.5)
crop_height = APXSIZE
crop_x = 0
crop_y = APXSIZE
layer2.crop(left=crop_x, top=crop_y, width=crop_width, height=crop_height)
# Composite the cropped image over the transparent image
img.composite(layer2, 0, APXSIZE)
# Apply channel operation
img.fx("a > 0.0 ? 1.0 : 0.0", channel='alpha')
# Save the result
img.save(filename=leggings)
# Convert chest textures
def convert_chest_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Convert chest textures (requires ImageMagick)
chest_files = [[tex_dir + "/entity/chest/normal.png",
target_dir("/mods/ITEMS/mcl_chests/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"default_chest_top.png",
"mcl_chests_chest_bottom.png",
"default_chest_front.png",
"mcl_chests_chest_left.png",
"mcl_chests_chest_right.png",
"mcl_chests_chest_back.png"],
[tex_dir + "/entity/chest/trapped.png",
target_dir("/mods/ITEMS/mcl_chests/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_chests_chest_trapped_top.png",
"mcl_chests_chest_trapped_bottom.png",
"mcl_chests_chest_trapped_front.png",
"mcl_chests_chest_trapped_left.png",
"mcl_chests_chest_trapped_right.png",
"mcl_chests_chest_trapped_back.png"],
[tex_dir + "/entity/chest/ender.png",
target_dir("/mods/ITEMS/mcl_chests/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_chests_ender_chest_top.png",
"mcl_chests_ender_chest_bottom.png",
"mcl_chests_ender_chest_front.png",
"mcl_chests_ender_chest_left.png",
"mcl_chests_ender_chest_right.png",
"mcl_chests_ender_chest_back.png"]]
for c in chest_files:
chest_file = c[0]
if os.path.isfile(chest_file):
PPX = (PXSIZE / 16)
CHPX = (PPX * 14) # Chest width
LIDPX = (PPX * 5) # Lid height
LIDLOW = (PPX * 10) # Lower lid section height
LOCKW = (PPX * 6) # Lock width
LOCKH = (PPX * 5) # Lock height
cdir = c[1]
top = cdir + "/" + c[2]
bottom = cdir + "/" + c[3]
front = cdir + "/" + c[4]
left = cdir + "/" + c[5]
right = cdir + "/" + c[6]
back = cdir + "/" + c[7]
# Top
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(CHPX) + "+" + str(CHPX) + "+0 \\) -geometry +0+0 -composite -extent " + str(CHPX) + "x" + str(CHPX) + " " + top)
# Bottom
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(CHPX) + "+" + str(CHPX * 2) + "+" + str(CHPX + LIDPX) + " \\) -geometry +0+0 -composite -extent " + str(CHPX) + "x" + str(CHPX) + " " + bottom)
# Front
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDPX) + "+" + str(CHPX) + "+" + str(CHPX) + " \\) -geometry +0+0 -composite \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDLOW) + "+" + str(CHPX) + "+" + str(CHPX * 2 + LIDPX) + " \\) -geometry +0+" + str(LIDPX - PPX) + " -composite \
-extent " + str(CHPX) + "x" + str(CHPX) + " " + front)
# TODO: Add lock
# Left, right back (use same texture, we're lazy
files = [left, right, back]
for f in files:
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDPX) + "+" + str(0) + "+" + str(CHPX) + " \\) -geometry +0+0 -composite \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDLOW) + "+" + str(0) + "+" + str(CHPX * 2 + LIDPX) + " \\) -geometry +0+" + str(LIDPX - PPX) + " -composite \
-extent " + str(CHPX) + "x" + str(CHPX) + " " + f)
# Double chests
chest_files = [[tex_dir + "/entity/chest/normal_double.png",
target_dir("/mods/ITEMS/mcl_chests/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"default_chest_front_big.png",
"default_chest_top_big.png",
"default_chest_side_big.png"],
[tex_dir + "/entity/chest/trapped_double.png",
target_dir("/mods/ITEMS/mcl_chests/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path),
"mcl_chests_chest_trapped_front_big.png",
"mcl_chests_chest_trapped_top_big.png",
"mcl_chests_chest_trapped_side_big.png"]]
for c in chest_files:
chest_file = c[0]
if os.path.isfile(chest_file):
PPX = (PXSIZE / 16)
CHPX = (PPX * 14) # Chest width (short side)
CHPX2 = (PPX * 15) # Chest width (long side)
LIDPX = (PPX * 5) # Lid height
LIDLOW = (PPX * 10) # Lower lid section height
LOCKW = (PPX * 6) # Lock width
LOCKH = (PPX * 5) # Lock height
cdir = c[1]
front = cdir + "/" + c[2]
top = cdir + "/" + c[3]
side = cdir + "/" + c[4]
# Top
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX2) + "x" + str(CHPX) + "+" + str(CHPX) + "+0 \\) -geometry +0+0 -composite -extent " + str(CHPX2) + "x" + str(CHPX) + " " + top)
# Front
# TODO: Add lock
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX2) + "x" + str(LIDPX) + "+" + str(CHPX) + "+" + str(CHPX) + " \\) -geometry +0+0 -composite \
\\( -clone 0 -crop " + str(CHPX2) + "x" + str(LIDLOW) + "+" + str(CHPX) + "+" + str(CHPX * 2 + LIDPX) + " \\) -geometry +0+" + str(LIDPX - PPX) + " -composite \
-extent " + str(CHPX2) + "x" + str(CHPX) + " " + front)
# Side
os.system("convert " + chest_file + " \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDPX) + "+" + str(0) + "+" + str(CHPX) + " \\) -geometry +0+0 -composite \
\\( -clone 0 -crop " + str(CHPX) + "x" + str(LIDLOW) + "+" + str(0) + "+" + str(CHPX * 2 + LIDPX) + " \\) -geometry +0+" + str(LIDPX - PPX) + " -composite \
-extent " + str(CHPX) + "x" + str(CHPX) + " " + side)
# Generate railway crossings and t-junctions
def convert_rail_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Generate railway crossings and t-junctions. Note: They may look strange.
# Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
# TODO: Curves
rails = [
# (Straigt src, curved src, t-junction dest, crossing dest)
("rail.png", "rail_corner.png",
"default_rail_t_junction.png", "default_rail_crossing.png"),
("powered_rail.png", "rail_corner.png",
"carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
("powered_rail_on.png", "rail_corner.png", "mcl_minecarts_rail_golden_t_junction_powered.png",
"mcl_minecarts_rail_golden_crossing_powered.png"),
("detector_rail.png", "rail_corner.png", "mcl_minecarts_rail_detector_t_junction.png",
"mcl_minecarts_rail_detector_crossing.png"),
("detector_rail_on.png", "rail_corner.png", "mcl_minecarts_rail_detector_t_junction_powered.png",
"mcl_minecarts_rail_detector_crossing_powered.png"),
("activator_rail.png", "rail_corner.png", "mcl_minecarts_rail_activator_t_junction.png",
"mcl_minecarts_rail_activator_crossing.png"),
("activator_rail_on.png", "rail_corner.png", "mcl_minecarts_rail_activator_d_t_junction.png",
"mcl_minecarts_rail_activator_powered_crossing.png"),
]
for r in rails:
os.system(
"composite -compose Dst_Over " +
tex_dir +
"/block/" +
r[0] +
" " +
tex_dir +
"/block/" +
r[1] +
" " +
target_dir(
"/mods/ENTITIES/mcl_minecarts/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/" +
r[2])
os.system("convert " + tex_dir + "/block/" +
r[0] + " -rotate 90 " + tempfile1.name)
os.system(
"composite -compose Dst_Over " +
tempfile1.name +
" " +
tex_dir +
"/block/" +
r[0] +
" " +
target_dir(
"/mods/ENTITIES/mcl_minecarts/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/" +
r[3])
# Convert banner overlays
def convert_banner_overlays(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Convert banner overlays
overlays = [
"base",
"border",
"bricks",
"circle",
"creeper",
"cross",
"curly_border",
"diagonal_left",
"diagonal_right",
"diagonal_up_left",
"diagonal_up_right",
"flower",
"gradient",
"gradient_up",
"half_horizontal_bottom",
"half_horizontal",
"half_vertical",
"half_vertical_right",
"rhombus",
"mojang",
"skull",
"small_stripes",
"straight_cross",
"stripe_bottom",
"stripe_center",
"stripe_downleft",
"stripe_downright",
"stripe_left",
"stripe_middle",
"stripe_right",
"stripe_top",
"square_bottom_left",
"square_bottom_right",
"square_top_left",
"square_top_right",
"triangle_bottom",
"triangles_bottom",
"triangle_top",
"triangles_top",
]
for o in overlays:
orig = tex_dir + "/entity/banner/" + o + ".png"
if os.path.isfile(orig):
if o == "mojang":
o = "thing"
dest = target_dir(
"/mods/ITEMS/mcl_banners/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) + "/" + "mcl_banners_" + o + ".png"
os.system(
"convert " +
orig +
" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 " +
dest)
# Convert grass and related textures
def convert_grass_textures(
make_texture_pack,
dry_run,
verbose,
base_dir,
tex_dir,
tempfile1,
tempfile2,
output_dir,
output_dir_name,
mineclone2_path,
PXSIZE):
# Convert grass
grass_file = tex_dir + "/block/grass_block_top.png"
if os.path.isfile(grass_file):
FOLIAG = tex_dir + "/colormap/foliage.png"
GRASS = tex_dir + "/colormap/grass.png"
# Leaves
colorize_alpha(
FOLIAG,
tex_dir +
"/block/oak_leaves.png",
"116+143",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/default_leaves.png",
tempfile2.name)
colorize_alpha(
FOLIAG,
tex_dir +
"/block/dark_oak_leaves.png",
"158+177",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_core_leaves_big_oak.png",
tempfile2.name)
colorize_alpha(
FOLIAG,
tex_dir +
"/block/acacia_leaves.png",
"40+255",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/default_acacia_leaves.png",
tempfile2.name)
colorize_alpha(
FOLIAG,
tex_dir +
"/block/spruce_leaves.png",
"226+230",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_core_leaves_spruce.png",
tempfile2.name)
colorize_alpha(
FOLIAG,
tex_dir +
"/block/birch_leaves.png",
"141+186",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_core_leaves_birch.png",
tempfile2.name)
colorize_alpha(
FOLIAG,
tex_dir +
"/block/jungle_leaves.png",
"16+39",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/default_jungleleaves.png",
tempfile2.name)
# Waterlily
colorize_alpha(
FOLIAG,
tex_dir +
"/block/lily_pad.png",
"16+39",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/flowers_waterlily.png",
tempfile2.name)
# Vines
colorize_alpha(
FOLIAG,
tex_dir +
"/block/vine.png",
"16+39",
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_core_vine.png",
tempfile2.name)
# Tall grass, fern (inventory images)
pcol = "50+173" # Plains grass color
# TODO: TALLGRASS.png does no longer exist
colorize_alpha(
GRASS,
tex_dir +
"/block/tallgrass.png",
pcol,
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_flowers_tallgrass_inv.png",
tempfile2.name)
colorize_alpha(
GRASS,
tex_dir +
"/block/fern.png",
pcol,
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_flowers_fern_inv.png",
tempfile2.name)
colorize_alpha(
GRASS,
tex_dir +
"/block/large_fern_top.png",
pcol,
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_flowers_double_plant_fern_inv.png",
tempfile2.name)
colorize_alpha(
GRASS,
tex_dir +
"/block/tall_grass_top.png",
pcol,
str(PXSIZE),
target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) +
"/mcl_flowers_double_plant_grass_inv.png",
tempfile2.name)
# Convert grass palette: https://minecraft.fandom.com/wiki/Tint
grass_colors = [
# [Coords or #Color, AdditionalTint], # Index - Minecraft biome name (MineClone2 biome names)
# 0 - Plains (flat, Plains, Plains_beach, Plains_ocean, End)
["50+173"],
# 1 - Savanna (Savanna, Savanna_beach, Savanna_ocean)
["0+255"],
# 2 - Ice Spikes (IcePlainsSpikes, IcePlainsSpikes_ocean)
["255+255"],
# 3 - Snowy Taiga (ColdTaiga, ColdTaiga_beach, ColdTaiga_beach_water, ColdTaiga_ocean)
["255+255"],
# 4 - Giant Tree Taiga (MegaTaiga, MegaTaiga_ocean)
["178+193"],
# 5 - Giant Tree Taiga (MegaSpruceTaiga, MegaSpruceTaiga_ocean)
["178+193"],
# 6 - Montains (ExtremeHills, ExtremeHills_beach, ExtremeHills_ocean)
["203+239"],
# 7 - Montains (ExtremeHillsM, ExtremeHillsM_ocean)
["203+239"],
# 8 - Montains (ExtremeHills+, ExtremeHills+_snowtop, ExtremeHills+_ocean)
["203+239"],
["50+173"], # 9 - Beach (StoneBeach, StoneBeach_ocean)
["255+255"], # 10 - Snowy Tundra (IcePlains, IcePlains_ocean)
# 11 - Sunflower Plains (SunflowerPlains, SunflowerPlains_ocean)
["50+173"],
["191+203"], # 12 - Taiga (Taiga, Taiga_beach, Taiga_ocean)
["76+112"], # 13 - Forest (Forest, Forest_beach, Forest_ocean)
# 14 - Flower Forest (FlowerForest, FlowerForest_beach, FlowerForest_ocean)
["76+112"],
# 15 - Birch Forest (BirchForest, BirchForest_ocean)
["101+163"],
# 16 - Birch Forest Hills (BirchForestM, BirchForestM_ocean)
["101+163"],
# 17 - Desert and Nether (Desert, Desert_ocean, Nether)
["0+255"],
# 18 - Dark Forest (RoofedForest, RoofedForest_ocean)
["76+112", "#28340A"],
["#90814d"], # 19 - Mesa (Mesa, Mesa_sandlevel, Mesa_ocean, )
# 20 - Mesa (MesaBryce, MesaBryce_sandlevel, MesaBryce_ocean)
["#90814d"],
# 21 - Mesa (MesaPlateauF, MesaPlateauF_grasstop, MesaPlateauF_sandlevel, MesaPlateauF_ocean)
["#90814d"],
# 22 - Mesa (MesaPlateauFM, MesaPlateauFM_grasstop, MesaPlateauFM_sandlevel, MesaPlateauFM_ocean)
["#90814d"],
# 23 - Shattered Savanna (or Savanna Plateau ?) (SavannaM, SavannaM_ocean)
["0+255"],
["12+36"], # 24 - Jungle (Jungle, Jungle_shore, Jungle_ocean)
# 25 - Modified Jungle (JungleM, JungleM_shore, JungleM_ocean)
["12+36"],
["12+61"], # 26 - Jungle Edge (JungleEdge, JungleEdge_ocean)
# 27 - Modified Jungle Edge (JungleEdgeM, JungleEdgeM_ocean)
["12+61"],
# 28 - Swamp (Swampland, Swampland_shore, Swampland_ocean)
["#6A7039"],
# 29 - Mushroom Fields and Mushroom Field Shore (MushroomIsland, MushroomIslandShore, MushroomIsland_ocean)
["25+25"],
]
grass_palette_file = target_dir(
"/textures",
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path) + "/mcl_core_palette_grass.png"
os.system("convert -size 16x16 canvas:transparent " +
grass_palette_file)
for i, color in enumerate(grass_colors):
if color[0][0] == "#":
os.system("convert -size 1x1 xc:\"" +
color[0] + "\" " + tempfile1.name + ".png")
else:
os.system("convert " + GRASS + " -crop 1x1+" +
color[0] + " " + tempfile1.name + ".png")
if len(color) > 1:
os.system(
"convert " +
tempfile1.name +
".png \\( -size 1x1 xc:\"" +
color[1] +
"\" \\) -compose blend -define compose:args=50,50 -composite " +
tempfile1.name +
".png")
os.system("convert " +
grass_palette_file +
" \\( " +
tempfile1.name +
".png -geometry +" +
str(i %
16) +
"+" +
str(int(i /
16)) +
" \\) -composite " +
grass_palette_file)

@ -0,0 +1,211 @@
import shutil
import csv
import os
import tempfile
import sys
import argparse
import glob
import re
import zipfile
from .config import SUPPORTED_MINECRAFT_VERSION, home
from collections import Counter
import platform
from wand.image import Image
from wand.color import Color
from wand.display import display
import warnings
def detect_pixel_size(directory):
from PIL import Image
sizes = []
for filename in glob.glob(directory + '/**/*.png', recursive=True):
with Image.open(filename) as img:
sizes.append(img.size)
if not sizes:
return 16 # Default to 16x16 if no PNG files are found
most_common_size = Counter(sizes).most_common(1)[0][0]
print(
f"Autodetected pixel size: {most_common_size[0]}x{most_common_size[1]}")
return most_common_size[0]
def target_dir(
directory,
make_texture_pack,
output_dir,
output_dir_name,
mineclone2_path):
if make_texture_pack:
return output_dir + "/" + output_dir_name
else:
return mineclone2_path + directory
def colorize(colormap, source, colormap_pixel, texture_size, destination, tempfile1_name):
try:
# Convert the colormap_pixel to integer coordinates
x, y = map(int, colormap_pixel.split('+'))
# Define texture size as integer
texture_size = int(texture_size)
with Image(filename=colormap) as img:
# Crop the image
img.crop(x, y, width=1, height=1)
# Set depth (This might be ignored by Wand as it manages depth automatically)
img.depth = 8
# Resize the image
img.resize(texture_size, texture_size)
# Save the result
img.save(filename=tempfile1_name)
except Exception as e:
warnings.warn(f"An error occurred during the first image processing operation: {e}")
try:
# Load the images
with Image(filename=tempfile1_name) as top_image:
with Image(filename=source) as bottom_image:
# Perform composite operation with Multiply blend mode
bottom_image.composite(top_image, 0, 0, operator='multiply')
# Save the result
bottom_image.save(filename=destination)
except Exception as e:
warnings.warn(f"An error occurred during the second image processing operation: {e}")
def colorize_alpha(
colormap,
source,
colormap_pixel,
texture_size,
destination,
tempfile2_name):
colorize(colormap, source, colormap_pixel,
texture_size, destination, tempfile2_name)
try:
with Image(filename=source) as source_image:
with Image(filename=tempfile2_name) as tempfile2_image:
# Perform composite operation with Dst_In blend mode
tempfile2_image.composite(source_image, 0, 0, operator='dst_in')
# Set alpha channel
tempfile2_image.alpha_channel = 'set'
# Save the result
tempfile2_image.save(filename=destination)
except Exception as e:
warnings.warn(f"An error occurred during the second image processing operation: {e}")
def find_highest_minecraft_version(home, supported_version):
version_pattern = re.compile(re.escape(supported_version) + r"\.\d+")
versions_dir = os.path.join(home, ".minecraft", "versions")
highest_version = None
if os.path.isdir(versions_dir):
for folder in os.listdir(versions_dir):
if version_pattern.match(folder):
if not highest_version or folder > highest_version:
highest_version = folder
return highest_version
def find_all_minecraft_resourcepacks():
resourcepacks_dir = os.path.join(home, '.minecraft', 'resourcepacks')
if not os.path.isdir(resourcepacks_dir):
print(f"Resource packs directory not found: {resourcepacks_dir}")
return
resourcepacks = []
for folder in os.listdir(resourcepacks_dir):
folder_path = os.path.join(resourcepacks_dir, folder)
if os.path.isdir(folder_path):
pack_png_path = os.path.join(folder_path, 'pack.png')
if os.path.isfile(pack_png_path):
print(f"Adding resourcepack '{folder}'")
resourcepacks.append(folder_path)
else:
print(
f"pack.png not found in resourcepack '{folder}', not converting")
return resourcepacks
def handle_default_minecraft_texture(home, output_dir):
version = find_highest_minecraft_version(home, SUPPORTED_MINECRAFT_VERSION)
if not version:
print("No suitable Minecraft version found.")
sys.exit(1)
jar_file = os.path.join(
home, ".minecraft", "versions", version, f"{version}.jar")
if not os.path.isfile(jar_file):
print("Minecraft JAR file not found.")
sys.exit(1)
temp_zip = f"/tmp/mc-default-{version.replace('.', '')}.zip"
shutil.copy2(jar_file, temp_zip)
extract_folder = temp_zip.replace(".zip", "")
with zipfile.ZipFile(temp_zip, 'r') as zip_ref:
zip_ref.extractall(extract_folder)
if not os.path.exists(extract_folder):
print(f"Extraction failed, folder not found: {extract_folder}")
sys.exit(1)
# Normalize the extract folder path
extract_folder = os.path.normpath(extract_folder)
# Define the textures directory and normalize it
textures_directory = os.path.normpath(
f"{extract_folder}/assets/minecraft/textures")
# Using glob to find all files
all_files = glob.glob(f"{extract_folder}/**/*.*", recursive=True)
# Remove all non-png files except pack.mcmeta and pack.png in the root
for file_path in all_files:
if not file_path.endswith('.png') and not file_path.endswith(
'pack.mcmeta') and not file_path.endswith('pack.png'):
# print(f"Removing file: {file_path}")
os.remove(file_path)
# Remove all directories in the root except 'assets'
for item in os.listdir(extract_folder):
item_path = os.path.join(extract_folder, item)
if os.path.isdir(item_path) and item != "assets":
# print(f"Removing directory: {item_path}")
shutil.rmtree(item_path, ignore_errors=True)
# Remove directories in 'minecraft' except for 'textures'
minecraft_directory = os.path.normpath(
f"{extract_folder}/assets/minecraft")
for item in os.listdir(minecraft_directory):
item_path = os.path.join(minecraft_directory, item)
if os.path.isdir(item_path) and item != "textures":
print(f"Removing directory: {item_path}")
shutil.rmtree(item_path, ignore_errors=True)
# Copy the textures directory to the output directory
output_textures_directory = os.path.join(
output_dir, 'assets/minecraft/textures')
if os.path.exists(textures_directory) and not os.path.exists(
output_textures_directory):
os.makedirs(os.path.dirname(output_textures_directory), exist_ok=True)
shutil.copytree(textures_directory,
output_textures_directory, dirs_exist_ok=True)
# Copy pack.mcmeta and pack.png file if exists
for file_name in ['pack.mcmeta', 'pack.png']:
file_path = os.path.join(extract_folder, file_name)
if os.path.exists(file_path):
shutil.copy(file_path, output_dir)
print(f"Filtered and extracted to: {extract_folder}")
return extract_folder

2
tools/requirements.txt Normal file

@ -0,0 +1,2 @@
Pillow
Wand

@ -0,0 +1,38 @@
import csv
import os
def validate_csv(file_path):
with open(file_path, newline='') as csvfile:
reader = csv.reader(csvfile, delimiter=',', quotechar='"')
line_num = 1
for row in reader:
# Skip the header
if line_num == 1:
line_num += 1
continue
# Check if row has correct number of columns
if len(row) != 10:
print(f"Warning: Line {line_num} is not a valid CSV row.")
line_num += 1
continue
# Validate source path
if "/assets/minecraft/" not in row[0]:
print(f"Warning: Line {line_num} does not contain '/assets/minecraft/' in the source path.")
# Validate Source file and Target file
if not row[1].endswith('.png'):
print(f"Warning: Line {line_num} has an invalid or missing Source file. It should end with '.png'.")
if not row[2].endswith('.png'):
print(f"Warning: Line {line_num} has an invalid or missing Target file. It should end with '.png'.")
line_num += 1
if __name__ == "__main__":
csv_file = 'Conversion_Table.csv'
if os.path.exists(csv_file):
validate_csv(csv_file)
print("Validated CSV, if no warnings or errors, your good!")
else:
print(f"Error: The file '{csv_file}' does not exist.")

@ -0,0 +1,40 @@
import csv
def read_csv(file_path):
with open(file_path, mode='r', encoding='utf-8') as file:
return list(csv.reader(file))
def write_csv(file_path, data):
with open(file_path, mode='w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
def merge_tables(original_csv, new_csv):
# Convert the lists to dictionaries for easier manipulation
original_dict = {row[3]: row for row in original_csv}
new_dict = {row[3]: row for row in new_csv}
# Update or add new entries
for key in new_dict:
original_dict[key] = new_dict[key]
# Convert the dictionary back to a list
merged_data = list(original_dict.values())
return merged_data
def main():
original_csv_path = './Conversion_Table.csv'
new_csv_path = './Conversion_Table_New.csv'
original_csv = read_csv(original_csv_path)
new_csv = read_csv(new_csv_path)
# Skip the header row in new_csv
merged_data = merge_tables(original_csv, new_csv[1:])
write_csv(original_csv_path, merged_data)
print("Conversion tables have been merged and updated successfully.")
if __name__ == "__main__":
main()

@ -0,0 +1,36 @@
import csv
def read_missing_textures(file_path):
with open(file_path, 'r') as file:
return [line.strip().split('/')[-1] for line in file.readlines()]
def read_conversion_table(file_path):
with open(file_path, 'r') as file:
return list(csv.reader(file))
def find_outstanding_entries(missing_textures, conversion_table):
outstanding_entries = []
for row in conversion_table:
if row[1] in missing_textures:
outstanding_entries.append(row)
return outstanding_entries
def write_outstanding_entries(file_path, outstanding_entries):
with open(file_path, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerows(outstanding_entries)
def main():
missing_textures_file = './missing_textures_filtered.txt'
conversion_table_file = './Conversion_Table.csv'
output_file = './Conversion_Table_Outstanding.csv'
missing_textures = read_missing_textures(missing_textures_file)
conversion_table = read_conversion_table(conversion_table_file)
outstanding_entries = find_outstanding_entries(missing_textures, conversion_table)
write_outstanding_entries(output_file, outstanding_entries)
print("Outstanding conversion table entries written to:", output_file)
if __name__ == "__main__":
main()

@ -0,0 +1,15 @@
def remove_null_lines(input_file, output_file):
with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
for line in infile:
if "NULL" not in line:
outfile.write(line)
def main():
input_file = './Conversion_Table.csv' # Replace with your input file path
output_file = './Conversion_Table_New.csv' # Replace with your output file path
remove_null_lines(input_file, output_file)
print("File processed successfully, NULL lines removed.")
if __name__ == "__main__":
main()