mirror of
https://github.com/TeamMidnightDust/BetterLeavesLite.git
synced 2025-12-15 12:05:09 +01:00
Compare commits
4 Commits
0ee2cb2195
...
9b24de4050
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b24de4050 | ||
|
|
c2a3bbe25d | ||
|
|
449143a209 | ||
|
|
ced8db9633 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
/.alt
|
/.alt
|
||||||
/__pycache__
|
*__pycache__
|
||||||
|
|||||||
Binary file not shown.
497
gen_pack.py
497
gen_pack.py
@@ -6,481 +6,13 @@
|
|||||||
# Depencency imports
|
# Depencency imports
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import zipfile
|
|
||||||
import shutil
|
|
||||||
import time
|
import time
|
||||||
import random
|
|
||||||
from PIL import Image
|
|
||||||
from distutils.dir_util import copy_tree
|
|
||||||
|
|
||||||
# Local imports
|
# Local imports
|
||||||
from download_helper import downloadFromModrinth
|
from src.generator import autoGen
|
||||||
|
from src.download_helper import downloadFromModrinth
|
||||||
minify = False
|
from src.zip_utils import makeZip
|
||||||
|
import src.json_utils
|
||||||
# Utility functions
|
|
||||||
def printGreen(out): print("\033[92m{}\033[00m".format(out))
|
|
||||||
def printCyan(out): print("\033[96m{}\033[00m" .format(out))
|
|
||||||
def printOverride(out): print(" -> {}".format(out))
|
|
||||||
def dumpJson(data, f): json.dump(data, f, separators=(',', ':')) if minify else json.dump(data, f, indent=4)
|
|
||||||
|
|
||||||
class BlockStateData:
|
|
||||||
def __init__(self, namespace, block_name, state):
|
|
||||||
self.namespace = namespace
|
|
||||||
self.block_name = block_name
|
|
||||||
self.state = state
|
|
||||||
def fromFile(leaf, root, infile):
|
|
||||||
with open(os.path.join(root, infile), "r") as f:
|
|
||||||
printOverride("Loading blockstate data from: "+f.name)
|
|
||||||
return BlockStateData.fromJson(leaf, json.load(f).get("blockStateData"))
|
|
||||||
def fromJson(leaf, data): return BlockStateData(data["block"].split(":")[0], data["block"].split(":")[1], data["state"]) if "block" in data else BlockStateData(leaf.getId().split(":")[0], leaf.getId().split(":")[1], data["state"])
|
|
||||||
|
|
||||||
class LeafBlock:
|
|
||||||
def __init__(self, namespace, block_name, texture_name):
|
|
||||||
self.namespace = namespace
|
|
||||||
self.block_name = block_name
|
|
||||||
self.texture_name = texture_name
|
|
||||||
base_model = "leaves"
|
|
||||||
has_carpet = False
|
|
||||||
has_no_tint = False
|
|
||||||
has_texture_override = False
|
|
||||||
should_generate_item_model = False
|
|
||||||
use_legacy_model = False
|
|
||||||
texture_prefix = ""
|
|
||||||
overlay_texture_id = ""
|
|
||||||
block_id_override = None
|
|
||||||
texture_id_override = None
|
|
||||||
dynamictrees_namespace = None
|
|
||||||
blockstate_data = None
|
|
||||||
sprite_overrides = None
|
|
||||||
def getId(self):
|
|
||||||
if (self.block_id_override != None): return self.block_id_override
|
|
||||||
return self.namespace+":"+self.block_name
|
|
||||||
def getTextureId(self):
|
|
||||||
if (self.texture_id_override != None): return self.texture_id_override
|
|
||||||
return self.namespace+":block/"+self.texture_prefix+self.texture_name
|
|
||||||
class CarpetBlock:
|
|
||||||
def __init__(self, carpet_id, leaf):
|
|
||||||
self.carpet_id = carpet_id
|
|
||||||
self.leaf = leaf
|
|
||||||
if (leaf.has_no_tint): self.base_model = "leaf_carpet_notint"
|
|
||||||
base_model = "leaf_carpet"
|
|
||||||
|
|
||||||
# This is where the magic happens
|
|
||||||
def autoGen(jsonData, args):
|
|
||||||
notint_overrides = jsonData["noTint"]
|
|
||||||
block_texture_overrides = jsonData["blockTextures"]
|
|
||||||
overlay_textures = jsonData["overlayTextures"]
|
|
||||||
compileonly_textures = jsonData["compileOnly"]
|
|
||||||
block_id_overrides = jsonData["blockIds"]
|
|
||||||
leaves_with_carpet = jsonData["leavesWithCarpet"]
|
|
||||||
dynamictrees_namespaces = jsonData["dynamicTreesNamespaces"]
|
|
||||||
generate_itemmodels_overrides = jsonData["generateItemModels"]
|
|
||||||
block_state_copies = jsonData["blockStateCopies"]
|
|
||||||
print("Generating assets...")
|
|
||||||
if (os.path.exists("./assets")): shutil.rmtree("./assets")
|
|
||||||
copy_tree("./base/assets/", "./assets/")
|
|
||||||
if minify: minifyJsonFiles()
|
|
||||||
|
|
||||||
filecount = 0
|
|
||||||
if (args.programmer): unpackTexturepacks("./input/programmer_art")
|
|
||||||
unpackTexturepacks()
|
|
||||||
unpackMods()
|
|
||||||
scanModsForTextures()
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk("./input/assets"):
|
|
||||||
for infile in files:
|
|
||||||
if infile.endswith(".png") and (len(root.split("/")) > 3):
|
|
||||||
texture_name = infile.replace(".png", "")
|
|
||||||
|
|
||||||
leaf = LeafBlock(root.split("/")[3], texture_name, texture_name)
|
|
||||||
|
|
||||||
# Handle leaf textures in subfolders
|
|
||||||
if (len(root.split("/")) > 6):
|
|
||||||
leaf.texture_prefix = root.split("/")[6]+"/"
|
|
||||||
if (leaf.block_name == "leaves"): # For mods that use a structure like "texture/woodtype/leaves.png"
|
|
||||||
leaf.block_name = leaf.texture_prefix.replace("/", "_")+leaf.block_name
|
|
||||||
printGreen(leaf.getId())
|
|
||||||
printOverride("Auto-redirected from "+leaf.getId())
|
|
||||||
else: # For mods that use a structure like "texture/natural/some_leaves.png"
|
|
||||||
printGreen(leaf.getId())
|
|
||||||
printOverride("Prefix: "+ leaf.texture_prefix);
|
|
||||||
else: printGreen(leaf.getId())
|
|
||||||
|
|
||||||
# We don't want to generate assets for overlay textures
|
|
||||||
if (leaf.getTextureId()) in overlay_textures.values():
|
|
||||||
printOverride("Skipping overlay texture")
|
|
||||||
continue
|
|
||||||
# We don't want to generate assets for compile-only textures
|
|
||||||
if (leaf.getTextureId()) in compileonly_textures:
|
|
||||||
printOverride("Skipping compile-only texture")
|
|
||||||
continue
|
|
||||||
|
|
||||||
texture = Image.open(os.path.join(root, infile))
|
|
||||||
leaf.use_legacy_model = texture.size[0] != texture.size[1]
|
|
||||||
if leaf.use_legacy_model: printOverride("Animated – using legacy model")
|
|
||||||
if args.legacy:
|
|
||||||
leaf.use_legacy_model = True
|
|
||||||
printOverride("Using legacy model as requested")
|
|
||||||
|
|
||||||
# Generate texture
|
|
||||||
if not leaf.use_legacy_model: generateTexture(root, infile, args.programmer)
|
|
||||||
|
|
||||||
# Set block id and apply overrides
|
|
||||||
if leaf.getId() in block_id_overrides:
|
|
||||||
leaf.block_id_override = block_id_overrides[leaf.getId()]
|
|
||||||
printOverride("ID Override: "+leaf.getId())
|
|
||||||
|
|
||||||
# Set texture id and apply overrides
|
|
||||||
leaf.has_texture_override = leaf.getId() in block_texture_overrides
|
|
||||||
if leaf.has_texture_override:
|
|
||||||
leaf.texture_id_override = block_texture_overrides[leaf.getId()]
|
|
||||||
printOverride("Texture Override: "+leaf.getTextureId())
|
|
||||||
|
|
||||||
# Check if the block appears in the notint overrides
|
|
||||||
leaf.has_no_tint = leaf.getId() in notint_overrides
|
|
||||||
if leaf.use_legacy_model:
|
|
||||||
leaf.base_model = "leaves_legacy"
|
|
||||||
elif leaf.has_no_tint:
|
|
||||||
leaf.base_model = "leaves_notint"
|
|
||||||
printOverride("No tint")
|
|
||||||
|
|
||||||
# Check if the block has an additional overlay texture
|
|
||||||
if leaf.getId() in overlay_textures:
|
|
||||||
leaf.base_model = "leaves_overlay"
|
|
||||||
leaf.overlay_texture_id = overlay_textures[leaf.getId()]
|
|
||||||
printOverride("Has overlay texture: "+leaf.overlay_texture_id)
|
|
||||||
|
|
||||||
# Check if the block has a dynamic trees addon namespace
|
|
||||||
|
|
||||||
if (leaf.namespace) in dynamictrees_namespaces:
|
|
||||||
leaf.dynamictrees_namespace = dynamictrees_namespaces[leaf.namespace]
|
|
||||||
|
|
||||||
# Check if the block should generate an item model
|
|
||||||
if leaf.getId() in generate_itemmodels_overrides:
|
|
||||||
leaf.should_generate_item_model = True
|
|
||||||
printOverride("Also generating item model")
|
|
||||||
|
|
||||||
# Check for blockstate data
|
|
||||||
if infile.replace(".png", ".betterleaves.json") in files:
|
|
||||||
with open(os.path.join(root, infile.replace(".png", ".betterleaves.json")), "r") as f:
|
|
||||||
jsonFile = json.load(f)
|
|
||||||
if "blockStateData" in jsonFile:
|
|
||||||
leaf.blockstate_data = BlockStateData.fromFile(leaf, root, infile.replace(".png", ".betterleaves.json"))
|
|
||||||
if "spriteOverrides" in jsonFile:
|
|
||||||
leaf.sprite_overrides = jsonFile["spriteOverrides"]
|
|
||||||
|
|
||||||
# Generate blockstates & models
|
|
||||||
generateBlockstate(leaf, block_state_copies)
|
|
||||||
generateBlockModels(leaf)
|
|
||||||
generateItemModel(leaf)
|
|
||||||
|
|
||||||
# Certain mods contain leaf carpets.
|
|
||||||
# Because we change the leaf texture, we need to fix the carpet models.
|
|
||||||
if (leaf.getId()) in leaves_with_carpet:
|
|
||||||
carpet_ids = leaves_with_carpet[leaf.getId()]
|
|
||||||
if not isinstance(carpet_ids, list): carpet_ids = [carpet_ids] # In case only one carpet is provided (as a string), turn it into a list
|
|
||||||
for carpet_id in carpet_ids:
|
|
||||||
carpet = CarpetBlock(carpet_id, leaf)
|
|
||||||
generateCarpetAssets(carpet)
|
|
||||||
printOverride(f"Generating leaf carpet: {carpet.carpet_id}")
|
|
||||||
|
|
||||||
filecount += 1
|
|
||||||
# End of autoGen
|
|
||||||
print()
|
|
||||||
if (args.programmer): cleanupTexturepacks("./input/programmer_art")
|
|
||||||
cleanupTexturepacks()
|
|
||||||
cleanupMods()
|
|
||||||
printCyan("Processed {} leaf blocks".format(filecount))
|
|
||||||
|
|
||||||
def unpackMods():
|
|
||||||
for root, dirs, files in os.walk("./input/mods"):
|
|
||||||
for infile in files:
|
|
||||||
if infile.endswith(".jar"):
|
|
||||||
print("Unpacking mod: "+infile)
|
|
||||||
zf = zipfile.ZipFile(os.path.join(root, infile), 'r')
|
|
||||||
zf.extractall(os.path.join(root, infile.replace(".jar", "_temp")))
|
|
||||||
zf.close()
|
|
||||||
|
|
||||||
def cleanupMods():
|
|
||||||
if (os.path.exists("./input/mods")): shutil.rmtree("./input/mods")
|
|
||||||
os.makedirs("./input/mods")
|
|
||||||
|
|
||||||
def scanModsForTextures():
|
|
||||||
for root, dirs, files in os.walk("./input/mods"):
|
|
||||||
for infile in files:
|
|
||||||
if len(root.split("assets")) > 1:
|
|
||||||
assetpath = root.split("assets")[1][1:]
|
|
||||||
modid = assetpath.split("textures")[0].replace("/", "")
|
|
||||||
if "textures/block" in root and infile.endswith(".png") and "leaves" in infile:
|
|
||||||
print(f"Found texture {assetpath}/{infile} in mod {modid}")
|
|
||||||
inputfolder = os.path.join("./input/assets/", assetpath)
|
|
||||||
os.makedirs(inputfolder, exist_ok=True)
|
|
||||||
shutil.copyfile(os.path.join(root, infile), os.path.join(inputfolder, infile))
|
|
||||||
|
|
||||||
|
|
||||||
def unpackTexturepacks(rootFolder="./input/texturepacks"):
|
|
||||||
for root, dirs, files in os.walk(rootFolder):
|
|
||||||
for infile in files:
|
|
||||||
if infile.endswith(".zip"):
|
|
||||||
print("Unpacking texturepack: "+infile)
|
|
||||||
zf = zipfile.ZipFile(os.path.join(root, infile), 'r')
|
|
||||||
zf.extractall(os.path.join(root, infile.replace(".zip", "_temp")))
|
|
||||||
zf.close()
|
|
||||||
|
|
||||||
def cleanupTexturepacks(rootFolder="./input/texturepacks"):
|
|
||||||
for root, dirs, files in os.walk(rootFolder):
|
|
||||||
for folder in dirs:
|
|
||||||
if folder.endswith("_temp"):
|
|
||||||
shutil.rmtree(os.path.join(root, folder))
|
|
||||||
|
|
||||||
def scanPacksForTexture(baseRoot, baseInfile, rootFolder="./input/texturepacks"):
|
|
||||||
for root, dirs, files in os.walk(rootFolder):
|
|
||||||
for infile in files:
|
|
||||||
if "assets" in root and "assets" in baseRoot:
|
|
||||||
if infile.endswith(".png") and (len(root.split("/")) > 3) and (baseInfile == infile) and (root.split("assets")[1] == baseRoot.split("assets")[1]):
|
|
||||||
printCyan(" Using texture from: " + root.split("assets")[0].replace(rootFolder, ""))
|
|
||||||
return root;
|
|
||||||
return baseRoot
|
|
||||||
|
|
||||||
def generateTexture(root, infile, useProgrammerArt=False):
|
|
||||||
outfolder = root.replace("assets", "").replace("input", "assets")
|
|
||||||
os.makedirs(outfolder, exist_ok=True)
|
|
||||||
|
|
||||||
# Check for texture stitching data
|
|
||||||
textureMap = {}
|
|
||||||
if os.path.isfile(os.path.join(root, infile.replace(".png", ".betterleaves.json"))):
|
|
||||||
with open(os.path.join(root, infile.replace(".png", ".betterleaves.json")), "r") as f:
|
|
||||||
json_data = json.load(f)
|
|
||||||
if "textureStitching" in json_data:
|
|
||||||
printOverride("Using texture stitching data from: " + f.name)
|
|
||||||
# Create texture map from stitching data
|
|
||||||
for key, value in json_data["textureStitching"].items():
|
|
||||||
if "-" in key:
|
|
||||||
for i in range(int(key.split("-")[0]), int(key.split("-")[1])+1): textureMap[str(i)] = value
|
|
||||||
else: textureMap[key] = value
|
|
||||||
# Turn texture map into absolute paths
|
|
||||||
for key, value in textureMap.items():
|
|
||||||
textureRoot = f"./input/assets/{value.split(':')[0]}/textures/"
|
|
||||||
textureFile = value.split(":")[1] + ".png"
|
|
||||||
if "/" in textureFile:
|
|
||||||
textureRoot += textureFile.rsplit("/")[0]
|
|
||||||
textureFile = textureFile[len(textureFile.rsplit("/")[0])+1:] # The rest of the string, starting behind the first '/'
|
|
||||||
textureRoot = scanPacksForTexture(textureRoot, textureFile)
|
|
||||||
if useProgrammerArt: root = scanPacksForTexture(textureRoot, textureFile, "./input/programmer_art")
|
|
||||||
textureMap[key] = os.path.join(textureRoot, textureFile)
|
|
||||||
|
|
||||||
root = scanPacksForTexture(root, infile)
|
|
||||||
if useProgrammerArt: root = scanPacksForTexture(root, infile, "./input/programmer_art")
|
|
||||||
|
|
||||||
outfile = os.path.splitext(os.path.join(outfolder, infile))[0] + ".png"
|
|
||||||
if infile != outfile:
|
|
||||||
try:
|
|
||||||
# First, let's open the regular texture
|
|
||||||
vanilla = Image.open(os.path.join(root, infile))
|
|
||||||
width, height = vanilla.size
|
|
||||||
# Second, let's generate a transparent texture that's twice the size
|
|
||||||
transparent = Image.new("RGBA", [int(2 * s) for s in vanilla.size], (255, 255, 255, 0))
|
|
||||||
out = transparent.copy()
|
|
||||||
|
|
||||||
# Now we paste the regular texture in a 3x3 grid, centered in the middle
|
|
||||||
for x in range(-1, 2):
|
|
||||||
for y in range(-1, 2):
|
|
||||||
texture = vanilla
|
|
||||||
index = (x + 2) + (y + 1) * 3 # Turns coordinates into a number from 1 to 9
|
|
||||||
if str(index) in textureMap: # Load texture from texture stitching map
|
|
||||||
texture = Image.open(textureMap[str(index)])
|
|
||||||
out.paste(texture, (int(width / 2 + width * x), int(height / 2 + height * y)))
|
|
||||||
|
|
||||||
# As the last step, we apply our custom mask to round the edges and smoothen things out
|
|
||||||
mask_location = f"input/masks/{width}px" # If possible, use a mask designed for the texture's size
|
|
||||||
if not os.path.isdir(mask_location) or len(os.listdir(mask_location)) == 0: mask_location = "input/masks/16px"
|
|
||||||
random.seed(infile) # Use the filename as a seed. This ensures we always get the same mask per block.
|
|
||||||
mask_location += f"/{random.choice(os.listdir(mask_location))}" # Choose a random mask to get some variation between the different types of leaves
|
|
||||||
mask = Image.open(mask_location).convert('L').resize(out.size, resample=Image.NEAREST)
|
|
||||||
out = Image.composite(out, transparent, mask)
|
|
||||||
|
|
||||||
# Finally, we save the texture to the assets folder
|
|
||||||
out.save(outfile, vanilla.format)
|
|
||||||
except IOError:
|
|
||||||
print("Error while generating texture for '%s'" % infile)
|
|
||||||
|
|
||||||
|
|
||||||
def generateBlockstate(leaf, block_state_copies):
|
|
||||||
mod_namespace = leaf.getId().split(":")[0]
|
|
||||||
block_name = leaf.getId().split(":")[1]
|
|
||||||
|
|
||||||
block_state_namespace = mod_namespace
|
|
||||||
block_state_name = block_name
|
|
||||||
|
|
||||||
state = ""
|
|
||||||
if leaf.blockstate_data != None: # In case custom blockstate data is defined
|
|
||||||
block_state_namespace = leaf.blockstate_data.namespace
|
|
||||||
block_state_name = leaf.blockstate_data.block_name
|
|
||||||
state = leaf.blockstate_data.state
|
|
||||||
|
|
||||||
# Create structure for blockstate file
|
|
||||||
block_state_file = f"assets/{block_state_namespace}/blockstates/{block_state_name}.json"
|
|
||||||
block_state_data = {
|
|
||||||
"variants": {
|
|
||||||
f"{state}": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if os.path.exists(block_state_file): # In case the blockstate file already exists, we want to add to it
|
|
||||||
with open(block_state_file, "r") as f:
|
|
||||||
block_state_data = json.load(f)
|
|
||||||
if state not in block_state_data["variants"]: block_state_data["variants"][state] = []
|
|
||||||
|
|
||||||
# Add four rotations for each of the four individual leaf models
|
|
||||||
for i in range(1, 5):
|
|
||||||
block_state_data["variants"][state] += { "model": f"{mod_namespace}:block/{block_name}{i}" }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 90 }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 180 }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 270 },
|
|
||||||
|
|
||||||
# Create blockstates folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/blockstates/".format(block_state_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Write blockstate file
|
|
||||||
with open(block_state_file, "w") as f:
|
|
||||||
dumpJson(block_state_data, f)
|
|
||||||
|
|
||||||
# Do the same for the dynamic trees namespace
|
|
||||||
if leaf.dynamictrees_namespace != None:
|
|
||||||
dyntrees_block_state_file = f"assets/{leaf.dynamictrees_namespace}/blockstates/{block_name}.json"
|
|
||||||
os.makedirs("assets/{}/blockstates/".format(leaf.dynamictrees_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Write blockstate file
|
|
||||||
with open(dyntrees_block_state_file, "w") as f:
|
|
||||||
dumpJson(block_state_data, f)
|
|
||||||
|
|
||||||
# Additional block state copies
|
|
||||||
if (leaf.getId()) in block_state_copies:
|
|
||||||
block_state_copy_ids = block_state_copies[leaf.getId()]
|
|
||||||
if not isinstance(block_state_copy_ids, list): block_state_copy_ids = [block_state_copy_ids] # In case only one blockstate is provided (as a string), turn it into a list
|
|
||||||
for block_state_copy_id in block_state_copy_ids:
|
|
||||||
block_state_copy_namespace = block_state_copy_id.split(":")[0]
|
|
||||||
block_state_copy_name = block_state_copy_id.split(":")[1]
|
|
||||||
|
|
||||||
block_state_copy_file = f"assets/{block_state_copy_namespace}/blockstates/{block_state_copy_name}.json"
|
|
||||||
os.makedirs("assets/{}/blockstates/".format(block_state_copy_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Write blockstate file
|
|
||||||
with open(block_state_copy_file, "w") as f:
|
|
||||||
dumpJson(block_state_data, f)
|
|
||||||
printOverride(f"Writing blockstate copy: {block_state_copy_id}")
|
|
||||||
|
|
||||||
|
|
||||||
def generateBlockModels(leaf):
|
|
||||||
mod_namespace = leaf.getId().split(":")[0]
|
|
||||||
block_name = leaf.getId().split(":")[1]
|
|
||||||
# Create models folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Create the four individual leaf models
|
|
||||||
for i in range(1, 5):
|
|
||||||
# Create structure for block model file
|
|
||||||
block_model_file = f"assets/{mod_namespace}/models/block/{block_name}{i}.json"
|
|
||||||
block_model_data = {
|
|
||||||
"parent": f"betterleaves:block/{leaf.base_model}{i}",
|
|
||||||
"textures": {
|
|
||||||
"all": f"{leaf.getTextureId()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add overlay texture on request
|
|
||||||
if (leaf.overlay_texture_id != ""):
|
|
||||||
block_model_data["textures"]["overlay"] = leaf.overlay_texture_id
|
|
||||||
|
|
||||||
# Add additional textures
|
|
||||||
if (leaf.sprite_overrides):
|
|
||||||
for key in leaf.sprite_overrides:
|
|
||||||
block_model_data["textures"][key] = leaf.sprite_overrides[key];
|
|
||||||
|
|
||||||
# Write block model file
|
|
||||||
with open(block_model_file, "w") as f:
|
|
||||||
dumpJson(block_model_data, f)
|
|
||||||
|
|
||||||
def generateItemModel(leaf):
|
|
||||||
mod_namespace = leaf.getId().split(":")[0]
|
|
||||||
block_name = leaf.getId().split(":")[1]
|
|
||||||
|
|
||||||
# Create models folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
block_item_model_file = f"assets/{mod_namespace}/models/block/{block_name}.json"
|
|
||||||
|
|
||||||
if leaf.has_texture_override: # Used for items that have a different texture than the block model
|
|
||||||
item_model_data = {
|
|
||||||
"parent": f"betterleaves:block/{leaf.base_model}",
|
|
||||||
"textures": {
|
|
||||||
"all": f"{mod_namespace}:block/{block_name}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else: # By default, the regular block texture is used
|
|
||||||
item_model_data = {
|
|
||||||
"parent": f"betterleaves:block/{leaf.base_model}",
|
|
||||||
"textures": {
|
|
||||||
"all": f"{leaf.getTextureId()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add overlay texture on request
|
|
||||||
if (leaf.overlay_texture_id != ""):
|
|
||||||
item_model_data["textures"]["overlay"] = leaf.overlay_texture_id
|
|
||||||
|
|
||||||
with open(block_item_model_file, "w") as f:
|
|
||||||
dumpJson(item_model_data, f)
|
|
||||||
|
|
||||||
if leaf.should_generate_item_model:
|
|
||||||
# Create models folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/models/item/".format(mod_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
item_model_file = f"assets/{mod_namespace}/models/item/{block_name}.json"
|
|
||||||
with open(item_model_file, "w") as f:
|
|
||||||
dumpJson(item_model_data, f)
|
|
||||||
|
|
||||||
def generateCarpetAssets(carpet):
|
|
||||||
mod_namespace = carpet.carpet_id.split(":")[0]
|
|
||||||
block_name = carpet.carpet_id.split(":")[1]
|
|
||||||
# Create blockstate folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/blockstates/".format(mod_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Create structure for blockstate file
|
|
||||||
block_state_file = f"assets/{mod_namespace}/blockstates/{block_name}.json"
|
|
||||||
block_state_data = {
|
|
||||||
"variants": {
|
|
||||||
"": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Add four rotations for the carpet model
|
|
||||||
block_state_data["variants"][""] += { "model": f"{mod_namespace}:block/{block_name}" }, { "model": f"{mod_namespace}:block/{block_name}", "y": 90 }, { "model": f"{mod_namespace}:block/{block_name}", "y": 180 }, { "model": f"{mod_namespace}:block/{block_name}", "y": 270 },
|
|
||||||
|
|
||||||
# Write blockstate file
|
|
||||||
with open(block_state_file, "w") as f:
|
|
||||||
dumpJson(block_state_data, f)
|
|
||||||
|
|
||||||
# Create models folder if it doesn't exist already
|
|
||||||
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
|
||||||
|
|
||||||
# Create structure for block model file
|
|
||||||
block_model_file = f"assets/{mod_namespace}/models/block/{block_name}.json"
|
|
||||||
block_model_data = {
|
|
||||||
"parent": f"betterleaves:block/{carpet.base_model}",
|
|
||||||
"textures": {
|
|
||||||
"wool": f"{carpet.leaf.getTextureId()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Save the carpet block model file
|
|
||||||
with open(block_model_file, "w") as f:
|
|
||||||
dumpJson(block_model_data, f)
|
|
||||||
|
|
||||||
def minifyJsonFiles(rootDir="./assets"):
|
|
||||||
for root, dirs, files in os.walk(rootDir):
|
|
||||||
for infile in files:
|
|
||||||
if infile.endswith(".json"):
|
|
||||||
minifyExistingJson(root, infile)
|
|
||||||
def minifyExistingJson(root, infile):
|
|
||||||
with open(os.path.join(root, infile), "r") as rf:
|
|
||||||
data = json.load(rf)
|
|
||||||
with open(os.path.join(root, infile), "w") as wf:
|
|
||||||
json.dump(data, wf, separators=(',', ':'))
|
|
||||||
|
|
||||||
def writeMetadata(args):
|
def writeMetadata(args):
|
||||||
edition = args.edition
|
edition = args.edition
|
||||||
@@ -490,25 +22,6 @@ def writeMetadata(args):
|
|||||||
line = line.replace("${version}", args.version).replace("${edition}", edition).replace("${year}", str(time.localtime().tm_year))
|
line = line.replace("${version}", args.version).replace("${edition}", edition).replace("${year}", str(time.localtime().tm_year))
|
||||||
outfile.write(line)
|
outfile.write(line)
|
||||||
|
|
||||||
# See https://stackoverflow.com/a/1855118
|
|
||||||
def zipdir(path, ziph):
|
|
||||||
# ziph is zipfile handle
|
|
||||||
for root, dirs, files in os.walk(path):
|
|
||||||
for file in files:
|
|
||||||
ziph.write(os.path.join(root, file),
|
|
||||||
os.path.relpath(os.path.join(root, file),
|
|
||||||
os.path.join(path, '..')))
|
|
||||||
|
|
||||||
# Creates a compressed zip file
|
|
||||||
def makeZip(filename, programmer_art=False):
|
|
||||||
with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
|
|
||||||
zipdir('assets/', zipf)
|
|
||||||
zipf.write('pack.mcmeta')
|
|
||||||
zipf.write('pack_programmer_art.png', arcname='pack.png') if programmer_art else zipf.write('pack.png')
|
|
||||||
zipf.write('LICENSE')
|
|
||||||
zipf.write('README.md')
|
|
||||||
|
|
||||||
|
|
||||||
# This is the main entry point, executed when the script is run
|
# This is the main entry point, executed when the script is run
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
@@ -529,7 +42,7 @@ if __name__ == '__main__':
|
|||||||
print("Motschen's Better Leaves Lite")
|
print("Motschen's Better Leaves Lite")
|
||||||
print("https://github.com/TeamMidnightDust/BetterLeavesLite")
|
print("https://github.com/TeamMidnightDust/BetterLeavesLite")
|
||||||
print()
|
print()
|
||||||
if args.minify: minify = True
|
if args.minify: src.json_utils.minify = True
|
||||||
if args.download != None: downloadFromModrinth(args.download)
|
if args.download != None: downloadFromModrinth(args.download)
|
||||||
|
|
||||||
# Loads overrides from the json file
|
# Loads overrides from the json file
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
pillow>=11.1.0
|
pillow>=11.1.0
|
||||||
setuptools>=76.0.0
|
setuptools>=76.0.0
|
||||||
|
tqdm>=4.67.1
|
||||||
|
requests>=2.32.0
|
||||||
|
|||||||
13
src/betterleaves_json.py
Normal file
13
src/betterleaves_json.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from src.data.leafblock import LeafBlock
|
||||||
|
from src.data.blockstate_data import BlockStateData
|
||||||
|
|
||||||
|
def applyJson(leaf: LeafBlock, root, infile, files):
|
||||||
|
if infile.replace(".png", ".betterleaves.json") in files:
|
||||||
|
with open(os.path.join(root, infile.replace(".png", ".betterleaves.json")), "r") as f:
|
||||||
|
jsonFile = json.load(f)
|
||||||
|
if "blockStateData" in jsonFile:
|
||||||
|
leaf.blockstate_data = BlockStateData.fromFile(leaf, root, infile.replace(".png", ".betterleaves.json"))
|
||||||
|
if "spriteOverrides" in jsonFile:
|
||||||
|
leaf.sprite_overrides = jsonFile["spriteOverrides"]
|
||||||
67
src/blockstate_generator.py
Normal file
67
src/blockstate_generator.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from src.json_utils import dumpJson
|
||||||
|
from src.utilities import printOverride
|
||||||
|
|
||||||
|
def generateBlockstate(leaf, block_state_copies):
|
||||||
|
mod_namespace = leaf.getId().split(":")[0]
|
||||||
|
block_name = leaf.getId().split(":")[1]
|
||||||
|
|
||||||
|
block_state_namespace = mod_namespace
|
||||||
|
block_state_name = block_name
|
||||||
|
|
||||||
|
state = ""
|
||||||
|
if leaf.blockstate_data != None: # In case custom blockstate data is defined
|
||||||
|
block_state_namespace = leaf.blockstate_data.namespace
|
||||||
|
block_state_name = leaf.blockstate_data.block_name
|
||||||
|
state = leaf.blockstate_data.state
|
||||||
|
|
||||||
|
# Create structure for blockstate file
|
||||||
|
block_state_file = f"assets/{block_state_namespace}/blockstates/{block_state_name}.json"
|
||||||
|
block_state_data = {
|
||||||
|
"variants": {
|
||||||
|
f"{state}": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.path.exists(block_state_file): # In case the blockstate file already exists, we want to add to it
|
||||||
|
with open(block_state_file, "r") as f:
|
||||||
|
block_state_data = json.load(f)
|
||||||
|
if state not in block_state_data["variants"]: block_state_data["variants"][state] = []
|
||||||
|
|
||||||
|
# Add four rotations for each of the four individual leaf models
|
||||||
|
for i in range(1, 5):
|
||||||
|
block_state_data["variants"][state] += { "model": f"{mod_namespace}:block/{block_name}{i}" }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 90 }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 180 }, { "model": f"{mod_namespace}:block/{block_name}{i}", "y": 270 },
|
||||||
|
|
||||||
|
# Create blockstates folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/blockstates/".format(block_state_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Write blockstate file
|
||||||
|
with open(block_state_file, "w") as f:
|
||||||
|
dumpJson(block_state_data, f)
|
||||||
|
|
||||||
|
# Do the same for the dynamic trees namespace
|
||||||
|
if leaf.dynamictrees_namespace != None:
|
||||||
|
dyntrees_block_state_file = f"assets/{leaf.dynamictrees_namespace}/blockstates/{block_name}.json"
|
||||||
|
os.makedirs("assets/{}/blockstates/".format(leaf.dynamictrees_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Write blockstate file
|
||||||
|
with open(dyntrees_block_state_file, "w") as f:
|
||||||
|
dumpJson(block_state_data, f)
|
||||||
|
|
||||||
|
# Additional block state copies
|
||||||
|
if (leaf.getId()) in block_state_copies:
|
||||||
|
block_state_copy_ids = block_state_copies[leaf.getId()]
|
||||||
|
if not isinstance(block_state_copy_ids, list): block_state_copy_ids = [block_state_copy_ids] # In case only one blockstate is provided (as a string), turn it into a list
|
||||||
|
for block_state_copy_id in block_state_copy_ids:
|
||||||
|
block_state_copy_namespace = block_state_copy_id.split(":")[0]
|
||||||
|
block_state_copy_name = block_state_copy_id.split(":")[1]
|
||||||
|
|
||||||
|
block_state_copy_file = f"assets/{block_state_copy_namespace}/blockstates/{block_state_copy_name}.json"
|
||||||
|
os.makedirs("assets/{}/blockstates/".format(block_state_copy_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Write blockstate file
|
||||||
|
with open(block_state_copy_file, "w") as f:
|
||||||
|
dumpJson(block_state_data, f)
|
||||||
|
printOverride(f"Writing blockstate copy: {block_state_copy_id}")
|
||||||
37
src/carpet_generator.py
Normal file
37
src/carpet_generator.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
from src.json_utils import dumpJson
|
||||||
|
|
||||||
|
def generateCarpetAssets(carpet):
|
||||||
|
mod_namespace = carpet.carpet_id.split(":")[0]
|
||||||
|
block_name = carpet.carpet_id.split(":")[1]
|
||||||
|
# Create blockstate folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/blockstates/".format(mod_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Create structure for blockstate file
|
||||||
|
block_state_file = f"assets/{mod_namespace}/blockstates/{block_name}.json"
|
||||||
|
block_state_data = {
|
||||||
|
"variants": {
|
||||||
|
"": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Add four rotations for the carpet model
|
||||||
|
block_state_data["variants"][""] += { "model": f"{mod_namespace}:block/{block_name}" }, { "model": f"{mod_namespace}:block/{block_name}", "y": 90 }, { "model": f"{mod_namespace}:block/{block_name}", "y": 180 }, { "model": f"{mod_namespace}:block/{block_name}", "y": 270 },
|
||||||
|
|
||||||
|
# Write blockstate file
|
||||||
|
with open(block_state_file, "w") as f:
|
||||||
|
dumpJson(block_state_data, f)
|
||||||
|
|
||||||
|
# Create models folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Create structure for block model file
|
||||||
|
block_model_file = f"assets/{mod_namespace}/models/block/{block_name}.json"
|
||||||
|
block_model_data = {
|
||||||
|
"parent": f"betterleaves:block/{carpet.base_model}",
|
||||||
|
"textures": {
|
||||||
|
"wool": f"{carpet.leaf.getTextureId()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Save the carpet block model file
|
||||||
|
with open(block_model_file, "w") as f:
|
||||||
|
dumpJson(block_model_data, f)
|
||||||
21
src/data/blockstate_data.py
Normal file
21
src/data/blockstate_data.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from src.data.leafblock import LeafBlock
|
||||||
|
from src.utilities import printOverride
|
||||||
|
|
||||||
|
class BlockStateData:
|
||||||
|
def __init__(self, namespace, block_name, state):
|
||||||
|
self.namespace = namespace
|
||||||
|
self.block_name = block_name
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
@classmethod # https://stackoverflow.com/a/682545
|
||||||
|
def fromJson(cls, leaf: LeafBlock, data):
|
||||||
|
return cls(data["block"].split(":")[0], data["block"].split(":")[1], data["state"]) if "block" in data else cls(leaf.getId().split(":")[0], leaf.getId().split(":")[1], data["state"])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromFile(cls, leaf: LeafBlock, root, infile):
|
||||||
|
with open(os.path.join(root, infile), "r") as f:
|
||||||
|
printOverride("Loading blockstate data from: "+f.name)
|
||||||
|
return BlockStateData.fromJson(leaf, json.load(f).get("blockStateData"))
|
||||||
7
src/data/carpetblock.py
Normal file
7
src/data/carpetblock.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
class CarpetBlock:
|
||||||
|
def __init__(self, carpet_id, leaf):
|
||||||
|
self.carpet_id = carpet_id
|
||||||
|
self.leaf = leaf
|
||||||
|
if (leaf.has_no_tint): self.base_model = "leaf_carpet_notint"
|
||||||
|
|
||||||
|
base_model = "leaf_carpet"
|
||||||
27
src/data/leafblock.py
Normal file
27
src/data/leafblock.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
class LeafBlock:
|
||||||
|
def __init__(self, namespace, block_name, texture_name):
|
||||||
|
self.namespace = namespace
|
||||||
|
self.block_name = block_name
|
||||||
|
self.texture_name = texture_name
|
||||||
|
|
||||||
|
base_model = "leaves"
|
||||||
|
has_carpet = False
|
||||||
|
has_no_tint = False
|
||||||
|
has_texture_override = False
|
||||||
|
should_generate_item_model = False
|
||||||
|
use_legacy_model = False
|
||||||
|
texture_prefix = ""
|
||||||
|
overlay_texture_id = ""
|
||||||
|
block_id_override = None
|
||||||
|
texture_id_override = None
|
||||||
|
dynamictrees_namespace = None
|
||||||
|
blockstate_data = None
|
||||||
|
sprite_overrides = None
|
||||||
|
|
||||||
|
def getId(self):
|
||||||
|
if (self.block_id_override != None): return self.block_id_override
|
||||||
|
return self.namespace+":"+self.block_name
|
||||||
|
|
||||||
|
def getTextureId(self):
|
||||||
|
if (self.texture_id_override != None): return self.texture_id_override
|
||||||
|
return self.namespace+":block/"+self.texture_prefix+self.texture_name
|
||||||
149
src/generator.py
Normal file
149
src/generator.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Depencency imports
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from PIL import Image
|
||||||
|
from setuptools._distutils.dir_util import copy_tree
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from src.data.leafblock import LeafBlock
|
||||||
|
from src.data.carpetblock import CarpetBlock
|
||||||
|
from src.mod_utils import unpackMods, cleanupMods, scanModsForTextures
|
||||||
|
from src.texturepack_utils import unpackTexturepacks, cleanupTexturepacks
|
||||||
|
from src.utilities import printCyan, printGreen, printOverride
|
||||||
|
from src.texture_generator import generateTexture
|
||||||
|
from src.model_generator import generateBlockModels, generateItemModel
|
||||||
|
from src.blockstate_generator import generateBlockstate
|
||||||
|
from src.carpet_generator import generateCarpetAssets
|
||||||
|
from src.json_utils import minifyJsonFiles, minify
|
||||||
|
from src.betterleaves_json import applyJson
|
||||||
|
|
||||||
|
# This is where the magic happens
|
||||||
|
def autoGen(jsonData, args):
|
||||||
|
print("Generating assets...")
|
||||||
|
if (os.path.exists("./assets")): shutil.rmtree("./assets")
|
||||||
|
copy_tree("./base/assets/", "./assets/")
|
||||||
|
if minify: minifyJsonFiles()
|
||||||
|
|
||||||
|
filecount = 0
|
||||||
|
if (args.programmer): unpackTexturepacks("./input/programmer_art")
|
||||||
|
unpackTexturepacks()
|
||||||
|
unpackMods()
|
||||||
|
scanModsForTextures()
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk("./input/assets"):
|
||||||
|
for infile in files:
|
||||||
|
if infile.endswith(".png") and (len(root.split("/")) > 3):
|
||||||
|
filecount += processLeaf(root, files, infile, jsonData, args)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if (args.programmer): cleanupTexturepacks("./input/programmer_art")
|
||||||
|
cleanupTexturepacks()
|
||||||
|
cleanupMods()
|
||||||
|
printCyan("Processed {} leaf blocks".format(filecount))
|
||||||
|
|
||||||
|
def processLeaf(root, files, infile, jsonData, args) -> int:
|
||||||
|
texture_name = infile.replace(".png", "")
|
||||||
|
leaf = LeafBlock(root.split("/")[3], texture_name, texture_name)
|
||||||
|
|
||||||
|
notint_overrides = jsonData["noTint"]
|
||||||
|
block_texture_overrides = jsonData["blockTextures"]
|
||||||
|
overlay_textures = jsonData["overlayTextures"]
|
||||||
|
compileonly_textures = jsonData["compileOnly"]
|
||||||
|
block_id_overrides = jsonData["blockIds"]
|
||||||
|
leaves_with_carpet = jsonData["leavesWithCarpet"]
|
||||||
|
dynamictrees_namespaces = jsonData["dynamicTreesNamespaces"]
|
||||||
|
generate_itemmodels_overrides = jsonData["generateItemModels"]
|
||||||
|
block_state_copies = jsonData["blockStateCopies"]
|
||||||
|
|
||||||
|
# Handle leaf textures in subfolders
|
||||||
|
if (len(root.split("/")) > 6):
|
||||||
|
leaf.texture_prefix = root.split("/")[6]+"/"
|
||||||
|
if (leaf.block_name == "leaves"): # For mods that use a structure like "texture/woodtype/leaves.png"
|
||||||
|
leaf.block_name = leaf.texture_prefix.replace("/", "_")+leaf.block_name
|
||||||
|
printGreen(leaf.getId())
|
||||||
|
printOverride("Auto-redirected from "+leaf.getId())
|
||||||
|
else: # For mods that use a structure like "texture/natural/some_leaves.png"
|
||||||
|
printGreen(leaf.getId())
|
||||||
|
printOverride("Prefix: "+ leaf.texture_prefix);
|
||||||
|
else: printGreen(leaf.getId())
|
||||||
|
|
||||||
|
# We don't want to generate assets for compile-only or overlay textures
|
||||||
|
if leaf.getTextureId() in compileonly_textures or leaf.getTextureId() in overlay_textures.values():
|
||||||
|
printOverride(f"Skipping {'compile-only' if leaf.getTextureId() in compileonly_textures else 'overlay'} texture")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
leaf.use_legacy_model = shouldUseLegacyModel(leaf, root, infile, args)
|
||||||
|
|
||||||
|
# Generate texture
|
||||||
|
if not leaf.use_legacy_model: generateTexture(root, infile, args.programmer)
|
||||||
|
|
||||||
|
# Set block id and apply overrides
|
||||||
|
if leaf.getId() in block_id_overrides:
|
||||||
|
leaf.block_id_override = block_id_overrides[leaf.getId()]
|
||||||
|
printOverride("ID Override: "+leaf.getId())
|
||||||
|
|
||||||
|
# Set texture id and apply overrides
|
||||||
|
leaf.has_texture_override = leaf.getId() in block_texture_overrides
|
||||||
|
if leaf.has_texture_override:
|
||||||
|
leaf.texture_id_override = block_texture_overrides[leaf.getId()]
|
||||||
|
printOverride("Texture Override: "+leaf.getTextureId())
|
||||||
|
|
||||||
|
# Check if the block appears in the notint overrides
|
||||||
|
leaf.has_no_tint = leaf.getId() in notint_overrides
|
||||||
|
if leaf.use_legacy_model:
|
||||||
|
leaf.base_model = "leaves_legacy"
|
||||||
|
elif leaf.has_no_tint:
|
||||||
|
leaf.base_model = "leaves_notint"
|
||||||
|
printOverride("No tint")
|
||||||
|
|
||||||
|
# Check if the block has an additional overlay texture
|
||||||
|
if leaf.getId() in overlay_textures:
|
||||||
|
leaf.base_model = "leaves_overlay"
|
||||||
|
leaf.overlay_texture_id = overlay_textures[leaf.getId()]
|
||||||
|
printOverride("Has overlay texture: "+leaf.overlay_texture_id)
|
||||||
|
|
||||||
|
# Check if the block has a dynamic trees addon namespace
|
||||||
|
|
||||||
|
if (leaf.namespace) in dynamictrees_namespaces:
|
||||||
|
leaf.dynamictrees_namespace = dynamictrees_namespaces[leaf.namespace]
|
||||||
|
|
||||||
|
# Check if the block should generate an item model
|
||||||
|
if leaf.getId() in generate_itemmodels_overrides:
|
||||||
|
leaf.should_generate_item_model = True
|
||||||
|
printOverride("Also generating item model")
|
||||||
|
|
||||||
|
# Check for blockstate data
|
||||||
|
applyJson(leaf, root, infile, files)
|
||||||
|
|
||||||
|
# Generate blockstates & models
|
||||||
|
generateBlockstate(leaf, block_state_copies)
|
||||||
|
generateBlockModels(leaf)
|
||||||
|
generateItemModel(leaf)
|
||||||
|
|
||||||
|
# Certain mods contain leaf carpets.
|
||||||
|
# Because we change the leaf texture, we need to fix the carpet models.
|
||||||
|
generateCarpet(leaf, leaves_with_carpet)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def shouldUseLegacyModel(leaf, root, infile, args) -> bool:
|
||||||
|
texture = Image.open(os.path.join(root, infile))
|
||||||
|
if texture.size[0] != texture.size[1]:
|
||||||
|
printOverride("Animated – using legacy model")
|
||||||
|
return True
|
||||||
|
if args.legacy:
|
||||||
|
printOverride("Using legacy model as requested")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def generateCarpet(leaf, leaves_with_carpet):
|
||||||
|
if (leaf.getId()) not in leaves_with_carpet: return
|
||||||
|
|
||||||
|
carpet_ids = leaves_with_carpet[leaf.getId()]
|
||||||
|
# In case only one carpet is provided (as a string), turn it into a list
|
||||||
|
if not isinstance(carpet_ids, list): carpet_ids = [carpet_ids]
|
||||||
|
|
||||||
|
for carpet_id in carpet_ids:
|
||||||
|
carpet = CarpetBlock(carpet_id, leaf)
|
||||||
|
generateCarpetAssets(carpet)
|
||||||
|
printOverride(f"Generating leaf carpet: {carpet.carpet_id}")
|
||||||
18
src/json_utils.py
Normal file
18
src/json_utils.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
minify = False
|
||||||
|
|
||||||
|
def minifyJsonFiles(rootDir="./assets"):
|
||||||
|
for root, dirs, files in os.walk(rootDir):
|
||||||
|
for infile in files:
|
||||||
|
if infile.endswith(".json"):
|
||||||
|
minifyExistingJson(root, infile)
|
||||||
|
def minifyExistingJson(root, infile):
|
||||||
|
with open(os.path.join(root, infile), "r") as rf:
|
||||||
|
data = json.load(rf)
|
||||||
|
with open(os.path.join(root, infile), "w") as wf:
|
||||||
|
json.dump(data, wf, separators=(',', ':'))
|
||||||
|
|
||||||
|
def dumpJson(data, f):
|
||||||
|
json.dump(data, f, separators=(',', ':')) if minify else json.dump(data, f, indent=4)
|
||||||
28
src/mod_utils.py
Normal file
28
src/mod_utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
def unpackMods():
|
||||||
|
for root, dirs, files in os.walk("./input/mods"):
|
||||||
|
for infile in files:
|
||||||
|
if infile.endswith(".jar"):
|
||||||
|
print("Unpacking mod: "+infile)
|
||||||
|
zf = zipfile.ZipFile(os.path.join(root, infile), 'r')
|
||||||
|
zf.extractall(os.path.join(root, infile.replace(".jar", "_temp")))
|
||||||
|
zf.close()
|
||||||
|
|
||||||
|
def cleanupMods():
|
||||||
|
if (os.path.exists("./input/mods")): shutil.rmtree("./input/mods")
|
||||||
|
os.makedirs("./input/mods")
|
||||||
|
|
||||||
|
def scanModsForTextures():
|
||||||
|
for root, dirs, files in os.walk("./input/mods"):
|
||||||
|
for infile in files:
|
||||||
|
if len(root.split("assets")) > 1:
|
||||||
|
assetpath = root.split("assets")[1][1:]
|
||||||
|
modid = assetpath.split("textures")[0].replace("/", "")
|
||||||
|
if "textures/block" in root and infile.endswith(".png") and "leaves" in infile:
|
||||||
|
print(f"Found texture {assetpath}/{infile} in mod {modid}")
|
||||||
|
inputfolder = os.path.join("./input/assets/", assetpath)
|
||||||
|
os.makedirs(inputfolder, exist_ok=True)
|
||||||
|
shutil.copyfile(os.path.join(root, infile), os.path.join(inputfolder, infile))
|
||||||
69
src/model_generator.py
Normal file
69
src/model_generator.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import os
|
||||||
|
from src.json_utils import dumpJson
|
||||||
|
|
||||||
|
def generateBlockModels(leaf):
|
||||||
|
mod_namespace = leaf.getId().split(":")[0]
|
||||||
|
block_name = leaf.getId().split(":")[1]
|
||||||
|
# Create models folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
# Create the four individual leaf models
|
||||||
|
for i in range(1, 5):
|
||||||
|
# Create structure for block model file
|
||||||
|
block_model_file = f"assets/{mod_namespace}/models/block/{block_name}{i}.json"
|
||||||
|
block_model_data = {
|
||||||
|
"parent": f"betterleaves:block/{leaf.base_model}{i}",
|
||||||
|
"textures": {
|
||||||
|
"all": f"{leaf.getTextureId()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Add overlay texture on request
|
||||||
|
if (leaf.overlay_texture_id != ""):
|
||||||
|
block_model_data["textures"]["overlay"] = leaf.overlay_texture_id
|
||||||
|
|
||||||
|
# Add additional textures
|
||||||
|
if (leaf.sprite_overrides):
|
||||||
|
for key in leaf.sprite_overrides:
|
||||||
|
block_model_data["textures"][key] = leaf.sprite_overrides[key];
|
||||||
|
|
||||||
|
# Write block model file
|
||||||
|
with open(block_model_file, "w") as f:
|
||||||
|
dumpJson(block_model_data, f)
|
||||||
|
|
||||||
|
def generateItemModel(leaf):
|
||||||
|
mod_namespace = leaf.getId().split(":")[0]
|
||||||
|
block_name = leaf.getId().split(":")[1]
|
||||||
|
|
||||||
|
# Create models folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/models/block/".format(mod_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
block_item_model_file = f"assets/{mod_namespace}/models/block/{block_name}.json"
|
||||||
|
|
||||||
|
if leaf.has_texture_override: # Used for items that have a different texture than the block model
|
||||||
|
item_model_data = {
|
||||||
|
"parent": f"betterleaves:block/{leaf.base_model}",
|
||||||
|
"textures": {
|
||||||
|
"all": f"{mod_namespace}:block/{block_name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else: # By default, the regular block texture is used
|
||||||
|
item_model_data = {
|
||||||
|
"parent": f"betterleaves:block/{leaf.base_model}",
|
||||||
|
"textures": {
|
||||||
|
"all": f"{leaf.getTextureId()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Add overlay texture on request
|
||||||
|
if (leaf.overlay_texture_id != ""):
|
||||||
|
item_model_data["textures"]["overlay"] = leaf.overlay_texture_id
|
||||||
|
|
||||||
|
with open(block_item_model_file, "w") as f:
|
||||||
|
dumpJson(item_model_data, f)
|
||||||
|
|
||||||
|
if leaf.should_generate_item_model:
|
||||||
|
# Create models folder if it doesn't exist already
|
||||||
|
os.makedirs("assets/{}/models/item/".format(mod_namespace), exist_ok=True)
|
||||||
|
|
||||||
|
item_model_file = f"assets/{mod_namespace}/models/item/{block_name}.json"
|
||||||
|
with open(item_model_file, "w") as f:
|
||||||
|
dumpJson(item_model_data, f)
|
||||||
77
src/texture_generator.py
Normal file
77
src/texture_generator.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Local imports
|
||||||
|
from src.texturepack_utils import scanPacksForTexture
|
||||||
|
from src.utilities import printOverride
|
||||||
|
|
||||||
|
def generateTexture(root, infile, useProgrammerArt=False):
|
||||||
|
outfolder = root.replace("assets", "").replace("input", "assets")
|
||||||
|
os.makedirs(outfolder, exist_ok=True)
|
||||||
|
|
||||||
|
# Check for texture stitching data
|
||||||
|
textureMap = createTextureMap(root, infile, useProgrammerArt)
|
||||||
|
|
||||||
|
root = scanPacksForTexture(root, infile)
|
||||||
|
if useProgrammerArt: root = scanPacksForTexture(root, infile, "./input/programmer_art")
|
||||||
|
|
||||||
|
outfile = os.path.splitext(os.path.join(outfolder, infile))[0] + ".png"
|
||||||
|
if infile != outfile:
|
||||||
|
try:
|
||||||
|
stitchTexture(textureMap, root, infile, outfile)
|
||||||
|
except IOError:
|
||||||
|
print("Error while generating texture for '%s'" % infile)
|
||||||
|
|
||||||
|
def createTextureMap(root, infile, useProgrammerArt):
|
||||||
|
textureMap = {}
|
||||||
|
if os.path.isfile(os.path.join(root, infile.replace(".png", ".betterleaves.json"))):
|
||||||
|
with open(os.path.join(root, infile.replace(".png", ".betterleaves.json")), "r") as f:
|
||||||
|
json_data = json.load(f)
|
||||||
|
if "textureStitching" in json_data:
|
||||||
|
printOverride("Using texture stitching data from: " + f.name)
|
||||||
|
# Create texture map from stitching data
|
||||||
|
for key, value in json_data["textureStitching"].items():
|
||||||
|
if "-" in key:
|
||||||
|
for i in range(int(key.split("-")[0]), int(key.split("-")[1])+1): textureMap[str(i)] = value
|
||||||
|
else: textureMap[key] = value
|
||||||
|
# Turn texture map into absolute paths
|
||||||
|
for key, value in textureMap.items():
|
||||||
|
textureRoot = f"./input/assets/{value.split(':')[0]}/textures/"
|
||||||
|
textureFile = value.split(":")[1] + ".png"
|
||||||
|
if "/" in textureFile:
|
||||||
|
textureRoot += textureFile.rsplit("/")[0]
|
||||||
|
textureFile = textureFile[len(textureFile.rsplit("/")[0])+1:] # The rest of the string, starting behind the first '/'
|
||||||
|
textureRoot = scanPacksForTexture(textureRoot, textureFile)
|
||||||
|
if useProgrammerArt: root = scanPacksForTexture(textureRoot, textureFile, "./input/programmer_art")
|
||||||
|
textureMap[key] = os.path.join(textureRoot, textureFile)
|
||||||
|
return textureMap
|
||||||
|
|
||||||
|
def stitchTexture(textureMap, root, infile, outfile):
|
||||||
|
# First, let's open the regular texture
|
||||||
|
vanilla = Image.open(os.path.join(root, infile))
|
||||||
|
width, height = vanilla.size
|
||||||
|
# Second, let's generate a transparent texture that's twice the size
|
||||||
|
transparent = Image.new("RGBA", [int(2 * s) for s in vanilla.size], (255, 255, 255, 0))
|
||||||
|
out = transparent.copy()
|
||||||
|
|
||||||
|
# Now we paste the regular texture in a 3x3 grid, centered in the middle
|
||||||
|
for x in range(-1, 2):
|
||||||
|
for y in range(-1, 2):
|
||||||
|
texture = vanilla
|
||||||
|
index = (x + 2) + (y + 1) * 3 # Turns coordinates into a number from 1 to 9
|
||||||
|
if str(index) in textureMap: # Load texture from texture stitching map
|
||||||
|
texture = Image.open(textureMap[str(index)])
|
||||||
|
out.paste(texture, (int(width / 2 + width * x), int(height / 2 + height * y)))
|
||||||
|
|
||||||
|
# As the last step, we apply our custom mask to round the edges and smoothen things out
|
||||||
|
mask_location = f"input/masks/{width}px" # If possible, use a mask designed for the texture's size
|
||||||
|
if not os.path.isdir(mask_location) or len(os.listdir(mask_location)) == 0: mask_location = "input/masks/16px"
|
||||||
|
random.seed(infile) # Use the filename as a seed. This ensures we always get the same mask per block.
|
||||||
|
mask_location += f"/{random.choice(os.listdir(mask_location))}" # Choose a random mask to get some variation between the different types of leaves
|
||||||
|
mask = Image.open(mask_location).convert('L').resize(out.size, resample=Image.NEAREST)
|
||||||
|
out = Image.composite(out, transparent, mask)
|
||||||
|
|
||||||
|
# Finally, we save the texture to the assets folder
|
||||||
|
out.save(outfile, vanilla.format)
|
||||||
28
src/texturepack_utils.py
Normal file
28
src/texturepack_utils.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
from src.utilities import printCyan
|
||||||
|
|
||||||
|
def unpackTexturepacks(rootFolder="./input/texturepacks"):
|
||||||
|
for root, dirs, files in os.walk(rootFolder):
|
||||||
|
for infile in files:
|
||||||
|
if infile.endswith(".zip"):
|
||||||
|
print("Unpacking texturepack: "+infile)
|
||||||
|
zf = zipfile.ZipFile(os.path.join(root, infile), 'r')
|
||||||
|
zf.extractall(os.path.join(root, infile.replace(".zip", "_temp")))
|
||||||
|
zf.close()
|
||||||
|
|
||||||
|
def cleanupTexturepacks(rootFolder="./input/texturepacks"):
|
||||||
|
for root, dirs, files in os.walk(rootFolder):
|
||||||
|
for folder in dirs:
|
||||||
|
if folder.endswith("_temp"):
|
||||||
|
shutil.rmtree(os.path.join(root, folder))
|
||||||
|
|
||||||
|
def scanPacksForTexture(baseRoot, baseInfile, rootFolder="./input/texturepacks"):
|
||||||
|
for root, dirs, files in os.walk(rootFolder):
|
||||||
|
for infile in files:
|
||||||
|
if "assets" in root and "assets" in baseRoot:
|
||||||
|
if infile.endswith(".png") and (len(root.split("/")) > 3) and (baseInfile == infile) and (root.split("assets")[1] == baseRoot.split("assets")[1]):
|
||||||
|
printCyan(" Using texture from: " + root.split("assets")[0].replace(rootFolder, ""))
|
||||||
|
return root;
|
||||||
|
return baseRoot
|
||||||
3
src/utilities.py
Normal file
3
src/utilities.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
def printGreen(out): print("\033[92m{}\033[00m".format(out))
|
||||||
|
def printCyan(out): print("\033[96m{}\033[00m" .format(out))
|
||||||
|
def printOverride(out): print(" -> {}".format(out))
|
||||||
20
src/zip_utils.py
Normal file
20
src/zip_utils.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
# See https://stackoverflow.com/a/1855118
|
||||||
|
def zipdir(path, ziph):
|
||||||
|
# ziph is zipfile handle
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for file in files:
|
||||||
|
ziph.write(os.path.join(root, file),
|
||||||
|
os.path.relpath(os.path.join(root, file),
|
||||||
|
os.path.join(path, '..')))
|
||||||
|
|
||||||
|
# Creates a compressed zip file
|
||||||
|
def makeZip(filename, programmer_art=False):
|
||||||
|
with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
|
||||||
|
zipdir('assets/', zipf)
|
||||||
|
zipf.write('pack.mcmeta')
|
||||||
|
zipf.write('pack_programmer_art.png', arcname='pack.png') if programmer_art else zipf.write('pack.png')
|
||||||
|
zipf.write('LICENSE')
|
||||||
|
zipf.write('README.md')
|
||||||
Reference in New Issue
Block a user