Implement conversion of single chests
[MineClone/MineClone2.git] / tools / Texture_Converter.py
blob344bfd78789f7e658d8fc90947bfa9712af6574f
1 #!/usr/bin/env python
2 # EXPERIMENTAL texture pack converting utility.
3 # This Python script helps in converting Minecraft texture packs. It has 2 main features:
4 # - Can create a Minetest texture pack (default)
5 # - Can update the MineClone 2 textures
6 # This script is currently incomplete, not all textures are converted.
8 # Requirements:
9 # - Python 3
10 # - ImageMagick
12 # Usage:
13 # - Make sure the file “Conversion_Table.csv” is in the same directory as the script
14 # - Run ./Texture_Converter.py -h to learn the available options
16 __author__ = "Wuzzy"
17 __license__ = "MIT License"
18 __status__ = "Development"
20 import shutil, csv, os, tempfile, sys, getopt
22 # Helper vars
23 home = os.environ["HOME"]
24 mineclone2_path = home + "/.minetest/games/mineclone2"
25 working_dir = os.getcwd()
26 output_dir_name = "New_MineClone_2_Texture_Pack"
27 appname = "Texture_Converter.py"
29 ### SETTINGS ###
30 output_dir = working_dir
32 base_dir = None
34 # If True, will only make console output but not convert anything.
35 dry_run = False
37 # If True, textures will be put into a texture pack directory structure.
38 # If False, textures will be put into MineClone 2 directories.
39 make_texture_pack = True
41 # If True, prints all copying actions
42 verbose = False
44 PXSIZE = 16
46 syntax_help = appname+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
47 Mandatory argument:
48 -i <input directory>
49 Directory of Minecraft resource pack to convert
51 Optional arguments:
52 -p <size>
53 Specify the size of the original textures (default: 16)
54 -o <output directory>
55 Directory in which to put the resulting MineClone 2 texture pack
56 (default: working directory)
58 The script will only pretend to convert textures by writing
59 to the console only, but not changing any files.
61 Prints out all copying actions
63 Shows this help an exits"""
64 try:
65 opts, args = getopt.getopt(sys.argv[1:],"hi:o:p:dv")
66 except getopt.GetoptError:
67 print(
68 """ERROR! The options you gave me make no sense!
70 Here's the syntax reference:""")
71 print(syntax_help)
72 sys.exit(2)
73 for opt, arg in opts:
74 if opt == "-h":
75 print(
76 """This is the official MineClone 2 Texture Converter.
77 This will convert textures from Minecraft resource packs to
78 a MineClone 2 texture pack.
80 Supported Minecraft version: 1.12 (Java Edition)
82 Syntax:""")
83 print(syntax_help)
84 sys.exit()
85 elif opt == "-d":
86 dry_run = True
87 elif opt == "-v":
88 verbose = True
89 elif opt == "-i":
90 base_dir = arg
91 elif opt == "-o":
92 output_dir = arg
93 elif opt == "-p":
94 PXSIZE = int(arg)
96 if base_dir == None:
97 print(
98 """ERROR: You forgot to tell me the path to the Minecraft resource pack.
99 Mind-reading has not been implemented yet.
101 Try this:
102 """+appname+""" -i <path to resource pack>
104 For the full help, use:
105 """+appname+""" -h""")
106 sys.exit(2);
108 ### END OF SETTINGS ###
110 tex_dir = base_dir + "/assets/minecraft/textures"
112 # FUNCTION DEFINITIONS
113 def colorize(colormap, source, colormap_pixel, texture_size, destination):
114 os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 -resize "+texture_size+"x"+texture_size+" "+tempfile1.name)
115 os.system("composite -compose Multiply "+tempfile1.name+" "+source+" "+destination)
117 def colorize_alpha(colormap, source, colormap_pixel, texture_size, destination):
118 colorize(colormap, source, colormap_pixel, texture_size, tempfile2.name)
119 os.system("composite -compose Dst_In "+source+" "+tempfile2.name+" -alpha Set "+destination)
121 # This function is unused atm.
122 # TODO: Implemnt colormap extraction
123 def extract_colormap(colormap, colormap_pixel, positions):
124 os.system("convert -size 16x16 canvas:black "+tempfile1.name)
127 for p in positions:
128 os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 "+tempfile2.name)
129 os.system("composite -geometry 16x16+"+x+"+"+y+" "+tempfile2.name)
130 x = x+1
132 def target_dir(directory):
133 if make_texture_pack:
134 return output_dir + "/" + output_dir_name
135 else:
136 return mineclone2_path + directory
138 # Copy texture files
139 def convert_textures():
140 failed_conversions = 0
141 print("Texture conversion BEGINS NOW!")
142 with open("Conversion_Table.csv", newline="") as csvfile:
143 reader = csv.reader(csvfile, delimiter=",", quotechar='"')
144 first_row = True
145 for row in reader:
146 # Skip first row
147 if first_row:
148 first_row = False
149 continue
151 src_dir = row[0]
152 src_filename = row[1]
153 dst_dir = row[2]
154 dst_filename = row[3]
155 if row[4] != "":
156 xs = int(row[4])
157 ys = int(row[5])
158 xl = int(row[6])
159 yl = int(row[7])
160 xt = int(row[8])
161 yt = int(row[9])
162 else:
163 xs = None
164 blacklisted = row[10]
166 if blacklisted == "y":
167 # Skip blacklisted files
168 continue
170 if make_texture_pack == False and dst_dir == "":
171 # If destination dir is empty, this texture is not supposed to be used in MCL2
172 # (but maybe an external mod). It should only be used in texture packs.
173 # Otherwise, it must be ignored.
174 # Example: textures for mcl_supplemental
175 continue
177 src_file = base_dir + src_dir + "/" + src_filename # source file
178 src_file_exists = os.path.isfile(src_file)
179 dst_file = target_dir(dst_dir) + "/" + dst_filename # destination file
181 if src_file_exists == False:
182 print("WARNING: Source file does not exist: "+src_file)
183 failed_conversions = failed_conversions + 1
184 continue
186 if xs != None:
187 # Crop and copy images
188 if not dry_run:
189 os.system("convert "+src_file+" -crop "+xl+"x"+yl+"+"+xs+"+"+ys+" "+dst_file)
190 if verbose:
191 print(src_file + " → " + dst_file)
192 else:
193 # Copy image verbatim
194 if not dry_run:
195 shutil.copy2(src_file, dst_file)
196 if verbose:
197 print(src_file + " → " + dst_file)
199 # Convert chest textures (requires ImageMagick)
200 chest_files = [
201 [ 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" ],
202 [ 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" ],
203 [ 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" ]
206 for c in chest_files:
207 chest_file = c[0]
208 if os.path.isfile(chest_file):
209 PPX = (PXSIZE/16)
210 CHPX= (PPX * 14) # Chests in MC are 2/16 smaller!
211 LIDPX =(PPX * 5) # Lid height
212 LOCKW = (PPX * 6) # Lock width
213 LOCKH = (PPX * 5) # Lock height
215 cdir = c[1]
216 top = cdir + "/" + c[2]
217 bottom = cdir + "/" + c[3]
218 front = cdir + "/" + c[4]
219 left = cdir + "/" + c[5]
220 right = cdir + "/" + c[6]
221 back = cdir + "/" + c[7]
222 # Top
223 os.system("convert " + chest_file + " \
224 \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+top)
225 # Bottom
226 os.system("convert " + chest_file + " \
227 \( -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)
228 # Front
229 os.system("convert " + chest_file + " \
230 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
231 \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX-LIDPX)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX)+" -composite \
232 -extent "+str(CHPX)+"x"+str(CHPX)+" "+front)
233 # TODO: Add lock
235 # Left, right back (use same texture, we're lazy
236 files = [ left, right, back ]
237 for f in files:
238 os.system("convert " + chest_file + " \
239 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
240 \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX-LIDPX)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX)+" -composite \
241 -extent "+str(CHPX)+"x"+str(CHPX)+" "+f)
243 # Generate railway crossings and t-junctions. Note: They may look strange.
244 # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
245 # TODO: Curves
246 rails = [
247 # (Straigt src, curved src, t-junction dest, crossing dest)
248 ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
249 ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
250 ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
251 ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
252 ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
253 ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
254 ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
256 for r in rails:
257 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])
258 os.system("convert "+tex_dir+"/blocks/"+r[0]+" -rotate 90 "+tempfile1.name)
259 os.system("composite -compose Dst_Over "+tempfile1.name+" "+tex_dir+"/blocks/"+r[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[3])
261 # Convert grass
262 grass_file = tex_dir + "/blocks/grass_top.png"
263 if os.path.isfile(grass_file):
264 FOLIAG = tex_dir+"/colormap/foliage.png"
265 GRASS = tex_dir+"/colormap/grass.png"
268 # Leaves
269 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_oak.png", "116+143", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
270 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")
271 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
272 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")
273 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")
274 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
276 # Waterlily
277 colorize_alpha(FOLIAG, tex_dir+"/blocks/waterlily.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
279 # Vines
280 colorize_alpha(FOLIAG, tex_dir+"/blocks/vine.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
282 # Tall grass, fern (inventory images)
283 pcol = "49+172" # Plains grass color
284 colorize_alpha(GRASS, tex_dir+"/blocks/tallgrass.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
285 colorize_alpha(GRASS, tex_dir+"/blocks/fern.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
286 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")
287 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")
289 # TODO: Convert grass palette
291 offset = [
292 [ pcol, "", "grass" ], # Default grass: Plains
293 [ "40+255", "_dry", "dry_grass" ], # Dry grass: Savanna, Mesa Plateau F, Nether, …
295 for o in offset:
296 colorize(GRASS, tex_dir+"/blocks/grass_top.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+".png")
297 colorize_alpha(GRASS, tex_dir+"/blocks/grass_side_overlay.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+"_side.png")
301 # TODO: Convert banner masks
302 # if os.path.isdir(tex_dir + "/entity/banner"):
303 # These are the ImageMagick commands needed to convert the mask images
304 # os.system("mogrify -transparent-color "+filename)
305 # os.system("mogrify -clip-mask "+tex_dir+"/entity/banner/base.png"+" -alpha Copy "+filename)
306 # os.system("mogrify -fill white -colorize 100 "+filename)
308 print("Textures conversion COMPLETE!")
309 if failed_conversions > 0:
310 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions))
311 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
312 if make_texture_pack:
313 print("You can now retrieve the texture pack in "+output_dir+"/"+output_dir_name+"/")
315 # ENTRY POINT
316 if make_texture_pack and not os.path.isdir(output_dir+"/"+output_dir_name):
317 os.mkdir(output_dir+"/"+output_dir_name)
319 tempfile1 = tempfile.NamedTemporaryFile()
320 tempfile2 = tempfile.NamedTemporaryFile()
322 convert_textures()
324 tempfile1.close()
325 tempfile2.close()