Prevent opening double chests with solid block(s) on the top
[MineClone/MineClone2.git] / tools / Texture_Converter.py
blob9fa90365bcd9c26392b2e0041aaf47322bc57810
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Texture Converter.
4 # Converts Minecraft resource packs to Minetest texture packs.
5 # See README.md.
7 __author__ = "Wuzzy"
8 __license__ = "MIT License"
9 __status__ = "Development"
11 import shutil, csv, os, tempfile, sys, getopt
13 # Helper vars
14 home = os.environ["HOME"]
15 mineclone2_path = home + "/.minetest/games/mineclone2"
16 working_dir = os.getcwd()
17 appname = "Texture_Converter.py"
19 ### SETTINGS ###
20 output_dir = working_dir
22 base_dir = None
24 # If True, will only make console output but not convert anything.
25 dry_run = False
27 # If True, textures will be put into a texture pack directory structure.
28 # If False, textures will be put into MineClone 2 directories.
29 make_texture_pack = True
31 # If True, prints all copying actions
32 verbose = False
34 PXSIZE = 16
36 syntax_help = appname+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
37 Mandatory argument:
38 -i <input directory>
39 Directory of Minecraft resource pack to convert
41 Optional arguments:
42 -p <texture size>
43 Specify the size (in pixels) of the original textures (default: 16)
44 -o <output directory>
45 Directory in which to put the resulting Minetest texture pack
46 (default: working directory)
48 Just pretend to convert textures and just print output, but do not actually
49 change any files.
51 Print out all copying actions
53 Show this help and exit"""
54 try:
55 opts, args = getopt.getopt(sys.argv[1:],"hi:o:p:dv")
56 except getopt.GetoptError:
57 print(
58 """ERROR! The options you gave me make no sense!
60 Here's the syntax reference:""")
61 print(syntax_help)
62 sys.exit(2)
63 for opt, arg in opts:
64 if opt == "-h":
65 print(
66 """This is the official MineClone 2 Texture Converter.
67 This will convert textures from Minecraft resource packs to
68 a Minetest texture pack.
70 Supported Minecraft version: 1.12 (Java Edition)
72 Syntax:""")
73 print(syntax_help)
74 sys.exit()
75 elif opt == "-d":
76 dry_run = True
77 elif opt == "-v":
78 verbose = True
79 elif opt == "-i":
80 base_dir = arg
81 elif opt == "-o":
82 output_dir = arg
83 elif opt == "-p":
84 PXSIZE = int(arg)
86 if base_dir == None:
87 print(
88 """ERROR: You didn't tell me the path to the Minecraft resource pack.
89 Mind-reading has not been implemented yet.
91 Try this:
92 """+appname+""" -i <path to resource pack> -p <texture size>
94 For the full help, use:
95 """+appname+""" -h""")
96 sys.exit(2);
98 ### END OF SETTINGS ###
100 tex_dir = base_dir + "/assets/minecraft/textures"
102 # Get texture pack name (from directory name)
103 bdir_split = base_dir.split("/")
104 output_dir_name = bdir_split[-1]
105 if len(output_dir_name) == 0:
106 if len(bdir_split) >= 2:
107 output_dir_name = base_dir.split("/")[-2]
108 else:
109 # Fallback
110 output_dir_name = "New_MineClone_2_Texture_Pack"
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) # Chest width
211 LIDPX = (PPX * 5) # Lid height
212 LIDLOW = (PPX * 10) # Lower lid section height
213 LOCKW = (PPX * 6) # Lock width
214 LOCKH = (PPX * 5) # Lock height
216 cdir = c[1]
217 top = cdir + "/" + c[2]
218 bottom = cdir + "/" + c[3]
219 front = cdir + "/" + c[4]
220 left = cdir + "/" + c[5]
221 right = cdir + "/" + c[6]
222 back = cdir + "/" + c[7]
223 # Top
224 os.system("convert " + chest_file + " \
225 \( -clone 0 -crop "+str(CHPX)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX)+"x"+str(CHPX)+" "+top)
226 # Bottom
227 os.system("convert " + chest_file + " \
228 \( -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)
229 # Front
230 os.system("convert " + chest_file + " \
231 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
232 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
233 -extent "+str(CHPX)+"x"+str(CHPX)+" "+front)
234 # TODO: Add lock
236 # Left, right back (use same texture, we're lazy
237 files = [ left, right, back ]
238 for f in files:
239 os.system("convert " + chest_file + " \
240 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
241 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
242 -extent "+str(CHPX)+"x"+str(CHPX)+" "+f)
244 # Double chests
246 chest_files = [
247 [ 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" ],
248 [ 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" ]
250 for c in chest_files:
251 chest_file = c[0]
252 if os.path.isfile(chest_file):
253 PPX = (PXSIZE/16)
254 CHPX = (PPX * 14) # Chest width (short side)
255 CHPX2 = (PPX * 15) # Chest width (long side)
256 LIDPX = (PPX * 5) # Lid height
257 LIDLOW = (PPX * 10) # Lower lid section height
258 LOCKW = (PPX * 6) # Lock width
259 LOCKH = (PPX * 5) # Lock height
261 cdir = c[1]
262 front = cdir + "/" + c[2]
263 top = cdir + "/" + c[3]
264 side = cdir + "/" + c[4]
265 # Top
266 os.system("convert " + chest_file + " \
267 \( -clone 0 -crop "+str(CHPX2)+"x"+str(CHPX)+"+"+str(CHPX)+"+0 \) -geometry +0+0 -composite -extent "+str(CHPX2)+"x"+str(CHPX)+" "+top)
268 # Front
269 # TODO: Add lock
270 os.system("convert " + chest_file + " \
271 \( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDPX)+"+"+str(CHPX)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
272 \( -clone 0 -crop "+str(CHPX2)+"x"+str(LIDLOW)+"+"+str(CHPX)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
273 -extent "+str(CHPX2)+"x"+str(CHPX)+" "+front)
274 # Side
275 os.system("convert " + chest_file + " \
276 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDPX)+"+"+str(0)+"+"+str(CHPX)+" \) -geometry +0+0 -composite \
277 \( -clone 0 -crop "+str(CHPX)+"x"+str(LIDLOW)+"+"+str(0)+"+"+str(CHPX*2+LIDPX)+" \) -geometry +0+"+str(LIDPX-PPX)+" -composite \
278 -extent "+str(CHPX)+"x"+str(CHPX)+" "+side)
281 # Generate railway crossings and t-junctions. Note: They may look strange.
282 # Note: these may be only a temporary solution, as crossings and t-junctions do not occour in MC.
283 # TODO: Curves
284 rails = [
285 # (Straigt src, curved src, t-junction dest, crossing dest)
286 ("rail_normal.png", "rail_normal_turned.png", "default_rail_t_junction.png", "default_rail_crossing.png"),
287 ("rail_golden.png", "rail_normal_turned.png", "carts_rail_t_junction_pwr.png", "carts_rail_crossing_pwr.png"),
288 ("rail_golden_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"),
289 ("rail_detector.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"),
290 ("rail_detector_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"),
291 ("rail_activator.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"),
292 ("rail_activator_powered.png", "rail_normal_turned.png", "mcl_minecarts_rail_activator_d_t_junction.png", "mcl_minecarts_rail_activator_powered_crossing.png"),
294 for r in rails:
295 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])
296 os.system("convert "+tex_dir+"/blocks/"+r[0]+" -rotate 90 "+tempfile1.name)
297 os.system("composite -compose Dst_Over "+tempfile1.name+" "+tex_dir+"/blocks/"+r[0]+" "+target_dir("/mods/ENTITIES/mcl_minecarts/textures")+"/"+r[3])
299 # Convert banner overlays
300 overlays = [
301 "base",
302 "border",
303 "bricks",
304 "circle",
305 "creeper",
306 "cross",
307 "curly_border",
308 "diagonal_left",
309 "diagonal_right",
310 "diagonal_up_left",
311 "diagonal_up_right",
312 "flower",
313 "gradient",
314 "gradient_up",
315 "half_horizontal_bottom",
316 "half_horizontal",
317 "half_vertical",
318 "half_vertical_right",
319 "rhombus",
320 "mojang",
321 "skull",
322 "small_stripes",
323 "straight_cross",
324 "stripe_bottom",
325 "stripe_center",
326 "stripe_downleft",
327 "stripe_downright",
328 "stripe_left",
329 "stripe_middle",
330 "stripe_right",
331 "stripe_top",
332 "square_bottom_left",
333 "square_bottom_right",
334 "square_top_left",
335 "square_top_right",
336 "triangle_bottom",
337 "triangles_bottom",
338 "triangle_top",
339 "triangles_top",
341 for o in overlays:
342 orig = tex_dir + "/entity/banner/" + o + ".png"
343 if os.path.isfile(orig):
344 if o == "mojang":
345 o = "thing"
346 dest = target_dir("/mods/ITEMS/mcl_banners/textures")+"/"+"mcl_banners_"+o+".png"
347 os.system("convert "+orig+" -transparent-color white -background black -alpha remove -alpha copy -channel RGB -white-threshold 0 "+dest)
349 # Convert grass
350 grass_file = tex_dir + "/blocks/grass_top.png"
351 if os.path.isfile(grass_file):
352 FOLIAG = tex_dir+"/colormap/foliage.png"
353 GRASS = tex_dir+"/colormap/grass.png"
356 # Leaves
357 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_oak.png", "116+143", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_leaves.png")
358 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")
359 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_acacia.png", "40+255", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_acacia_leaves.png")
360 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")
361 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")
362 colorize_alpha(FOLIAG, tex_dir+"/blocks/leaves_jungle.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_jungleleaves.png")
364 # Waterlily
365 colorize_alpha(FOLIAG, tex_dir+"/blocks/waterlily.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
367 # Vines
368 colorize_alpha(FOLIAG, tex_dir+"/blocks/vine.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
370 # Tall grass, fern (inventory images)
371 pcol = "49+172" # Plains grass color
372 colorize_alpha(GRASS, tex_dir+"/blocks/tallgrass.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
373 colorize_alpha(GRASS, tex_dir+"/blocks/fern.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
374 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")
375 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")
377 # TODO: Convert grass palette
379 offset = [
380 [ pcol, "", "grass" ], # Default grass: Plains
382 for o in offset:
383 colorize(GRASS, tex_dir+"/blocks/grass_top.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+".png")
384 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")
386 # Metadata
387 if make_texture_pack:
388 # Create description file
389 description = "Texture pack for MineClone 2. Automatically converted from a Minecraft resource pack by the MineClone 2 Texture Converter. Size: "+str(PXSIZE)+"×"+str(PXSIZE)
390 description_file = open(target_dir("/") + "/description.txt", "w")
391 description_file.write(description)
392 description_file.close()
394 # Create preview image (screenshot.png)
395 os.system("convert -size 300x200 canvas:transparent "+target_dir("/") + "/screenshot.png")
396 os.system("composite "+base_dir+"/pack.png "+target_dir("/") + "/screenshot.png -gravity center "+target_dir("/") + "/screenshot.png")
398 print("Textures conversion COMPLETE!")
399 if failed_conversions > 0:
400 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions))
401 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
402 if make_texture_pack:
403 print("You can now retrieve the texture pack in "+output_dir+"/"+output_dir_name+"/")
405 # ENTRY POINT
406 if make_texture_pack and not os.path.isdir(output_dir+"/"+output_dir_name):
407 os.mkdir(output_dir+"/"+output_dir_name)
409 tempfile1 = tempfile.NamedTemporaryFile()
410 tempfile2 = tempfile.NamedTemporaryFile()
412 convert_textures()
414 tempfile1.close()
415 tempfile2.close()