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.
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
17 __license__
= "MIT License"
18 __status__
= "Development"
20 import shutil
, csv
, os
, tempfile
, sys
, getopt
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"
30 output_dir
= working_dir
34 # If True, will only make console output but not convert anything.
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
46 syntax_help
= appname
+""" -i <input dir> [-o <output dir>] [-d] [-v|-q] [-h]
49 Directory of Minecraft resource pack to convert
53 Specify the size of the original textures (default: 16)
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"""
65 opts
, args
= getopt
.getopt(sys
.argv
[1:],"hi:o:p:dv")
66 except getopt
.GetoptError
:
68 """ERROR! The options you gave me make no sense!
70 Here's the syntax reference:""")
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)
98 """ERROR: You forgot to tell me the path to the Minecraft resource pack.
99 Mind-reading has not been implemented yet.
102 """+appname
+""" -i <path to resource pack>
104 For the full help, use:
105 """+appname
+""" -h""")
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
)
128 os
.system("convert "+colormap
+" -crop 1x1+"+colormap_pixel
+" -depth 8 "+tempfile2
.name
)
129 os
.system("composite -geometry 16x16+"+x
+"+"+y
+" "+tempfile2
.name
)
132 def target_dir(directory
):
133 if make_texture_pack
:
134 return output_dir
+ "/" + output_dir_name
136 return mineclone2_path
+ directory
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
='"')
152 src_filename
= row
[1]
154 dst_filename
= row
[3]
164 blacklisted
= row
[10]
166 if blacklisted
== "y":
167 # Skip blacklisted files
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
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
187 # Crop and copy images
189 os
.system("convert "+src_file
+" -crop "+xl
+"x"+yl
+"+"+xs
+"+"+ys
+" "+dst_file
)
191 print(src_file
+ " → " + dst_file
)
193 # Copy image verbatim
195 shutil
.copy2(src_file
, dst_file
)
197 print(src_file
+ " → " + dst_file
)
199 # Convert chest textures (requires ImageMagick)
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
:
208 if os
.path
.isfile(chest_file
):
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
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]
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
)
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
)
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
)
236 # Left, right back (use same texture, we're lazy
237 files
= [ left
, right
, back
]
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
)
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
:
252 if os
.path
.isfile(chest_file
):
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
262 front
= cdir
+ "/" + c
[2]
263 top
= cdir
+ "/" + c
[3]
264 side
= cdir
+ "/" + c
[4]
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
)
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
)
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.
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"),
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
315 "half_horizontal_bottom",
318 "half_vertical_right",
332 "square_bottom_left",
333 "square_bottom_right",
342 orig
= tex_dir
+ "/entity/banner/" + o
+ ".png"
343 if os
.path
.isfile(orig
):
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
)
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"
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")
365 colorize_alpha(FOLIAG
, tex_dir
+"/blocks/waterlily.png", "16+39", str(PXSIZE
), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/flowers_waterlily.png")
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
380 [ pcol
, "", "grass" ], # Default grass: Plains
381 [ "40+255", "_dry", "dry_grass" ], # Dry grass: Savanna, Mesa Plateau F, Nether, …
384 colorize(GRASS
, tex_dir
+"/blocks/grass_top.png", o
[0], str(PXSIZE
), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o
[2]+".png")
385 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")
388 print("Textures conversion COMPLETE!")
389 if failed_conversions
> 0:
390 print("WARNING: Number of missing files in original resource pack: "+str(failed_conversions
))
391 print("NOTE: Please keep in mind this script does not reliably convert all the textures yet.")
392 if make_texture_pack
:
393 print("You can now retrieve the texture pack in "+output_dir
+"/"+output_dir_name
+"/")
396 if make_texture_pack
and not os
.path
.isdir(output_dir
+"/"+output_dir_name
):
397 os
.mkdir(output_dir
+"/"+output_dir_name
)
399 tempfile1
= tempfile
.NamedTemporaryFile()
400 tempfile2
= tempfile
.NamedTemporaryFile()