Adding legenda

This commit is contained in:
Jeroen van der Hel 2025-05-27 19:50:27 +02:00
parent 27c1c0d53b
commit 94228b603f
5 changed files with 156 additions and 10231 deletions

9994
.gitignore vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
import zipfile
import os
def extract_java_textures(java_jar_path, output_dir):
"""
Extracts block textures from a Minecraft Java Edition client JAR.
Args:
java_jar_path (str): Full path to the Minecraft client JAR file (e.g., "C:/Users/YourUser/AppData/Roaming/.minecraft/versions/1.20.1/1.20.1.jar").
output_dir (str): Directory where the extracted textures will be saved.
"""
if not os.path.exists(java_jar_path):
print(f"Error: Java JAR file not found at {java_jar_path}")
return
texture_source_path_in_jar = 'assets/minecraft/textures/block/'
extracted_texture_path = os.path.join(output_dir, 'java_textures')
os.makedirs(extracted_texture_path, exist_ok=True)
print(f"Extracting textures from {java_jar_path} to {extracted_texture_path}...")
with zipfile.ZipFile(java_jar_path, 'r') as zip_ref:
for member in zip_ref.namelist():
if member.startswith(texture_source_path_in_jar) and member.endswith('.png'):
# Get the relative path within the block textures folder
relative_path = os.path.relpath(member, texture_source_path_in_jar)
# Construct the full output path
destination_path = os.path.join(extracted_texture_path, relative_path)
# Ensure the directory exists
os.makedirs(os.path.dirname(destination_path), exist_ok=True)
# Extract the file
with open(destination_path, 'wb') as outfile:
outfile.write(zip_ref.read(member))
print("Texture extraction complete.")
if __name__ == "__main__":
# --- IMPORTANT: Set these paths correctly ---
# Path to your Minecraft Java Edition JAR file
mc_java_version = "1.21.5"
java_game_jar = f"C:/Users/NP110306/AppData/Roaming/.minecraft/versions/{mc_java_version}/{mc_java_version}.jar" # Adjust as needed!
# Directory where you want to save the extracted Java textures
output_textures_base_dir = "resources/" # Adjust as needed!
extract_java_textures(java_game_jar, output_textures_base_dir)
# Now, the 'java_textures' folder inside output_textures_base_dir
# will contain all the Java block PNGs.
# You can then directly map "minecraft:stone" to
# "C:/Users/NP110306/Desktop/Blueprint_Resources/java_textures/stone.png"
# (after handling the .png extension)

View File

@ -376,5 +376,144 @@ def render_grid(
return out_img
def draw_blueprint_legend(
image: Image.Image,
font_path="arial.ttf",
font_size=14,
margin=16,
box_width=220,
box_height=110
):
"""
Draws a legend box in the top-left corner of the blueprint image.
Explains gridlines, padlock, hinge bar, and other symbols.
"""
draw = ImageDraw.Draw(image)
try:
font = ImageFont.truetype(font_path, font_size)
except Exception:
font = ImageFont.load_default()
# Legend box background
box_x0, box_y0 = margin, margin
box_x1, box_y1 = box_x0 + box_width, box_y0 + box_height
draw.rectangle([box_x0, box_y0, box_x1, box_y1], fill=(255, 255, 255, 230), outline=(80, 80, 80, 255), width=2)
y = box_y0 + 10
x = box_x0 + 10
line_spacing = font_size + 6
# 1. Gridlines
# Normal
draw.line([x, y + font_size // 2, x + 30, y + font_size // 2], fill=(150, 150, 150, 255), width=1)
draw.text((x + 40, y), "Normal gridline", fill=(0, 0, 0, 255), font=font)
y += line_spacing
# Bold
draw.line([x, y + font_size // 2, x + 30, y + font_size // 2], fill=(80, 80, 80, 255), width=3)
draw.text((x + 40, y), "Every 5th gridline", fill=(0, 0, 0, 255), font=font)
y += line_spacing
# 2. Padlock (unlocked)
padlock_x = x + 5
padlock_y = y + 2
# Draw padlock body
draw.rectangle([padlock_x, padlock_y + 8, padlock_x + 12, padlock_y + 16], fill=(200, 200, 0, 255), outline=(120, 120, 0, 255), width=1)
# Draw shackle (open arc)
draw.arc([padlock_x - 2, padlock_y + 2, padlock_x + 14, padlock_y + 14], start=30, end=150, fill=(120, 120, 0, 255), width=2)
draw.text((x + 40, y), "Unlocked door", fill=(0, 0, 0, 255), font=font)
y += line_spacing
# 3. Hinge bar
bar_x = x + 5
bar_y = y + 4
draw.rectangle([bar_x, bar_y, bar_x + 4, bar_y + 16], fill=(120, 120, 120, 255))
draw.text((x + 40, y), "Door hinge side", fill=(0, 0, 0, 255), font=font)
y += line_spacing
# 4. Arrow
arrow_x = x + 5
arrow_y = y + 10
draw.line([arrow_x, arrow_y, arrow_x + 16, arrow_y], fill=(200, 0, 0, 255), width=3)
draw.polygon([arrow_x + 16, arrow_y - 4, arrow_x + 24, arrow_y, arrow_x + 16, arrow_y + 4], fill=(200, 0, 0, 255))
draw.text((x + 40, y), "Facing direction", fill=(0, 0, 0, 255), font=font)
# (Add more symbols as needed)
def draw_dynamic_block_legend_left(
grid_image: Image.Image,
textures_2d,
java_textures_loaded,
block_names_2d,
font_path="arial.ttf",
font_size=14,
margin=24,
entry_height=28,
entry_icon_size=20,
legend_bg=(255, 255, 255, 230),
legend_outline=(80, 80, 80, 255)
):
"""
Draws a legend for only the block types used in the current layer, in a margin to the left of the grid.
Returns a new image with the legend and the grid side by side.
Args:
grid_image: The rendered grid image (PIL.Image).
textures_2d: 2D list of PIL.Image objects for the layer.
java_textures_loaded: dict mapping block name to PIL.Image.
block_names_2d: 2D list of block base names (same shape as textures_2d).
font_path: Path to font file.
font_size: Font size for legend text.
margin: Margin between legend and grid.
entry_height: Height of each legend entry.
entry_icon_size: Size of the block icon in the legend.
legend_bg: RGBA tuple for legend background.
legend_outline: RGBA tuple for legend outline.
Returns:
PIL.Image: New image with legend on the left and grid on the right.
"""
# 1. Find unique block types in the layer (excluding 'air')
unique_blocks = []
seen = set()
for row in block_names_2d:
for name in row:
if name != 'air' and name not in seen:
unique_blocks.append(name)
seen.add(name)
if not unique_blocks:
# No blocks, just return the grid image
return grid_image
# 2. Prepare legend image
legend_width = margin + entry_icon_size + 10 + 120 + margin # icon + spacing + text + margin
legend_height = margin + len(unique_blocks) * entry_height + margin
legend_img = Image.new('RGBA', (legend_width, legend_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(legend_img)
try:
font = ImageFont.truetype(font_path, font_size)
except Exception:
font = ImageFont.load_default()
# Draw legend background and outline
draw.rectangle([0, 0, legend_width-1, legend_height-1], fill=legend_bg, outline=legend_outline, width=2)
# 3. Draw each block type in the legend
y = margin
for block_name in unique_blocks:
# Draw block icon
icon = java_textures_loaded.get(block_name)
if icon is not None:
icon = icon.resize((entry_icon_size, entry_icon_size), Image.NEAREST)
legend_img.paste(icon, (margin, y), icon)
# Draw block name
text_x = margin + entry_icon_size + 10
draw.text((text_x, y + (entry_height - font_size) // 2), block_name, fill=(0, 0, 0, 255), font=font)
y += entry_height
# 4. Combine legend and grid image side by side
total_height = max(legend_img.height, grid_image.height)
total_width = legend_img.width + grid_image.width
combined_img = Image.new('RGBA', (total_width, total_height), (255, 255, 255, 0))
combined_img.paste(legend_img, (0, 0), legend_img)
combined_img.paste(grid_image, (legend_img.width, 0), grid_image)
return combined_img

View File

@ -1,177 +0,0 @@
import nbtlib
import nbtlib.tag
from nbtlib.tag import Int
import gzip
import os
import sys # Import sys to print interpreter info
NBTFormatError = getattr(nbtlib, 'NBTFormatError', None)
if NBTFormatError is None:
# Fallback for older nbtlib where it might have been in nbtlib.exceptions
try:
import nbtlib.exceptions
NBTFormatError = nbtlib.exceptions.NBTFormatError
except ImportError:
NBTFormatError = Exception # Generic fallback if not found
def parse_schem_file(filepath):
"""
Parses a .schem file and extracts its main components.
Args:
filepath (str): The path to the .schem file.
Returns:
dict: A dictionary containing schematic dimensions, block palette,
block data, and potentially block entities and entities.
Returns None if parsing fails.
"""
if not os.path.exists(filepath):
print(f"Error: File not found at {filepath}")
return None
try:
# nbtlib can directly load GZipped NBT files
schematic = nbtlib.load(filepath)
# Access the root compound tag
root = schematic
# --- Extract Header Information ---
version = root["Version"]
if version != Int(2):
print(f"Warning: Schematic version is {version}. Expected 2 for .schem files. May not parse correctly.")
width = root["Width"].value
height = root["Height"].value
length = root["Length"].value
print(f"Schematic Dimensions: Width={width}, Height={height}, Length={length}")
print(f"Schematic Version: {version}")
# --- Extract Block Palette ---
block_palette_nbt = root["BlockPalette"]
block_palette = {}
# The BlockPalette in .schem is a compound where keys are block string IDs
# and values are integer IDs. We reverse this to map int ID -> string ID.
for block_id_str, int_id_tag in block_palette_nbt.items():
block_palette[int_id_tag.value] = block_id_str
print(f"\nBlock Palette ({len(block_palette)} entries):")
# print(block_palette) # Uncomment to see the full palette
# --- Extract Block Data ---
# BlockData is typically a byte array or sometimes an int array
block_data_raw = root["BlockData"].value # This is a bytes object
# BlockData in .schem is often variable-length encoded (VLQ)
# We need to decode it to get the actual block IDs
# This part requires a custom VLQ decoder. nbtlib doesn't do this automatically.
# A simple VLQ decoder (from WorldEdit docs / common implementations)
# This function reads integers that are variable-length encoded.
# Each byte, if its 7th bit is 1, indicates more bytes follow.
# The actual value is in the lower 7 bits.
decoded_block_ids = []
i = 0
while i < len(block_data_raw):
value = 0
j = 0
while True:
byte = block_data_raw[i]
value |= (byte & 0x7F) << (7 * j)
i += 1
if not (byte & 0x80): # If the 7th bit is 0, this is the last byte
break
j += 1
if j > 5: # Prevent infinite loops for corrupted data
raise Exception("Corrupted VLQ data: too many bytes in integer")
decoded_block_ids.append(value)
print(f"\nDecoded Block Data Length: {len(decoded_block_ids)}")
# print(decoded_block_ids[:10]) # Print first 10 decoded IDs
# --- Reconstruct 3D block array (optional, but useful for processing) ---
# Order is typically (Y * Length + Z) * Width + X
blocks_3d = [[[None for _ in range(width)] for _ in range(length)] for _ in range(height)]
for y in range(height):
for z in range(length):
for x in range(width):
# Calculate the 1D index
idx_1d = (y * length + z) * width + x
if idx_1d < len(decoded_block_ids):
palette_id = decoded_block_ids[idx_1d]
block_string_id = block_palette.get(palette_id, "minecraft:unknown")
blocks_3d[y][z][x] = block_string_id
else:
blocks_3d[y][z][x] = "minecraft:missing"
# --- Extract Block Entities and Entities (if present) ---
block_entities = root.get("BlockEntities", nbtlib.tag.List()) # List of Compound Tags
entities = root.get("Entities", nbtlib.tag.List()) # List of Compound Tags
print(f"\nNumber of Block Entities: {len(block_entities)}")
if len(block_entities) > 0:
# print("First Block Entity:", block_entities[0].pretty_tree()) # Pretty print first one
pass # Or process them further
print(f"Number of Entities: {len(entities)}")
if len(entities) > 0:
# print("First Entity:", entities[0].pretty_tree()) # Pretty print first one
pass # Or process them further
return {
"version": version,
"width": width,
"height": height,
"length": length,
"block_palette": block_palette,
"decoded_block_ids": decoded_block_ids,
"blocks_3d": blocks_3d,
"block_entities": block_entities,
"entities": entities
}
except NBTFormatError as e:
print(f"Error: Not a valid NBT or schematic file. {e}")
except KeyError as e:
print(f"Error: Missing expected NBT tag in schematic. Likely not a valid .schem file or corrupted. Missing tag: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
# def import_schem_file(filepath):
# nbtfile = nbtlib.load(filepath)
# input()
if __name__ == '__main__':
# inputfile = "18805.schem"
# import_schem_file(inputfile)
schem_file_path = "18805.schem"
parsed_data = parse_schem_file(schem_file_path)
if parsed_data:
print("\nSuccessfully parsed schematic data.")
# You can now access and process the data:
# print(parsed_data["blocks_3d"][0][0][0]) # Block at Y=0, Z=0, X=0
# print(parsed_data["block_palette"])
# Example: Print a top-down view of the first layer (Y=0)
print("\n--- Top-Down View of Layer 0 (Y=0) ---")
for z in range(parsed_data["length"]):
row_blocks = []
for x in range(parsed_data["width"]):
# Get the block string ID and truncate for display
block_id = parsed_data["blocks_3d"][0][z][x]
row_blocks.append(block_id.replace("minecraft:", "")[:6].ljust(6)) # Shorten for display
print(" ".join(row_blocks))
print("---------------------------------------")

21
main.py
View File

@ -219,7 +219,7 @@ def parse_schem_file(filepath):
if __name__ == "__main__":
# --- IMPORTANT: Replace with the actual path to your .schem file ---
schem_file_path = "18805.schem"
BLUEPRINT_VERSION = "v2.3"
BLUEPRINT_VERSION = "v2.4"
# schem_file_path = "13774.schematic"
# Path to your Minecraft Java Edition JAR file (e.g., from .minecraft/versions/1.20.1/1.20.1.jar)
@ -278,14 +278,15 @@ if __name__ == "__main__":
current_layer = blocks_3d[y_layer]
# Prepare a 2D list of textures for this layer
textures_2d = []
block_names_2d = []
for z in range(length):
row_textures = []
row_names =[]
for x in range(width):
java_block_string = blocks_3d[y_layer][z][x]
original_base_block_id = java_block_string.replace("minecraft:", "").split('[')[0]
base_block_name_for_texture_lookup = get_base_java_block_name(java_block_string, TEXTURE_NAME_OVERRIDES)
row_names.append(base_block_name_for_texture_lookup)
block_states = parse_block_states(java_block_string)
# Get the base texture to start with. IMPORTANT: Make a .copy()!
@ -400,6 +401,7 @@ if __name__ == "__main__":
row_textures.append(texture_to_paste)
textures_2d.append(row_textures)
block_names_2d.append(row_names)
# --- Draw Grid Lines ---
layer_image_with_grid_and_numbers = render_grid(
@ -415,14 +417,23 @@ if __name__ == "__main__":
margin=GRID_LAYER_MARGIN # or your preferred margin
)
# Add legend to the left
layer_with_legend = draw_dynamic_block_legend_left(
layer_image_with_grid_and_numbers,
textures_2d,
java_textures_loaded,
block_names_2d,
font_size=14,
margin=24
)
# Save the generated layer image
output_filepath = os.path.join(OUTPUT_BLUEPRINT_DIR, BLUEPRINT_VERSION, f"blueprint_layer_{y_layer:03d}.png")
layer_image_with_grid_and_numbers.save(output_filepath)
layer_with_legend.save(output_filepath)
print(f"Generated blueprint for layer {y_layer} at {output_filepath}")
print("\nBlueprint generation complete!")
else:
print("Schematic parsing failed. Cannot generate blueprints.")