Initial commit
This commit is contained in:
commit
27c1c0d53b
10196
.gitignore
vendored
Normal file
10196
.gitignore
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Python Debugger: Current File",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
13774.schematic
Normal file
BIN
13774.schematic
Normal file
Binary file not shown.
BIN
18805.schem
Normal file
BIN
18805.schem
Normal file
Binary file not shown.
56
extract_java_textures.py
Normal file
56
extract_java_textures.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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)
|
||||||
380
helpers.py
Normal file
380
helpers.py
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
import re
|
||||||
|
from PIL import Image, ImageDraw, ImageFont # Make sure ImageDraw is imported
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
TEXTURE_SIZE = 16 # Default Minecraft texture size in pixels
|
||||||
|
|
||||||
|
# --- Constants for Blueprint Generation ---
|
||||||
|
OUTPUT_BLUEPRINT_DIR = "blueprints" # Directory to save generated blueprints
|
||||||
|
|
||||||
|
# --- Define the name for your missing texture file ---
|
||||||
|
MISSING_TEXTURE_FILENAME = "missing_texture.png" # Make sure this matches your file name
|
||||||
|
|
||||||
|
|
||||||
|
# --- Function to extract base block name from Java block string ---
|
||||||
|
def get_base_java_block_name(java_block_string, overrides):
|
||||||
|
"""
|
||||||
|
Extracts the base block name (e.g., 'stone', 'oak_log') from a full
|
||||||
|
Java block string (e.g., 'minecraft:stone', 'minecraft:oak_log[axis=y]'),
|
||||||
|
applying texture name overrides.
|
||||||
|
"""
|
||||||
|
# 1. Get the raw base name from the Java block string
|
||||||
|
name_without_prefix = java_block_string.replace("minecraft:", "")
|
||||||
|
raw_base_name = name_without_prefix.split('[')[0] # e.g., "spruce_trapdoor"
|
||||||
|
|
||||||
|
# 2. Apply override if defined, otherwise return the raw_base_name
|
||||||
|
overridden_name = overrides.get(raw_base_name, raw_base_name)
|
||||||
|
|
||||||
|
return overridden_name
|
||||||
|
|
||||||
|
def parse_block_states(java_block_string):
|
||||||
|
"""
|
||||||
|
Parses a Java block string to extract its state properties.
|
||||||
|
e.g., "minecraft:spruce_trapdoor[facing=north,half=bottom]" -> {'facing': 'north', 'half': 'bottom'}
|
||||||
|
"""
|
||||||
|
states = {}
|
||||||
|
match = re.search(r'\[(.*?)\]', java_block_string)
|
||||||
|
if match:
|
||||||
|
properties_str = match.group(1)
|
||||||
|
# Split by comma, then by equals sign
|
||||||
|
for prop_pair in properties_str.split(','):
|
||||||
|
if '=' in prop_pair:
|
||||||
|
key, value = prop_pair.split('=', 1)
|
||||||
|
states[key] = value
|
||||||
|
return states
|
||||||
|
|
||||||
|
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.
|
||||||
|
output_dir (str): Directory where the extracted textures will be saved (e.g., 'java_textures' will be created inside this).
|
||||||
|
"""
|
||||||
|
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') # Save to a subfolder
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
def create_arrow_texture(direction, size=TEXTURE_SIZE):
|
||||||
|
"""
|
||||||
|
Creates a red arrow pointing in the specified direction.
|
||||||
|
Args:
|
||||||
|
direction (str): 'north', 'south', 'east', 'west'.
|
||||||
|
size (int): The size of the square texture (e.g., 16).
|
||||||
|
Returns:
|
||||||
|
PIL.Image: An RGBA image of the arrow.
|
||||||
|
"""
|
||||||
|
arrow_img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) # Transparent background
|
||||||
|
draw = ImageDraw.Draw(arrow_img)
|
||||||
|
|
||||||
|
line_color = (255, 0, 0, 255) # Red with full alpha
|
||||||
|
line_width = 1 # Pixels
|
||||||
|
|
||||||
|
center_x, center_y = size // 2, size // 2
|
||||||
|
|
||||||
|
# Define arrow head size relative to TEXTURE_SIZE
|
||||||
|
# head_size = size // 4 # Example: 4 pixels for 16x16 texture
|
||||||
|
head_size = size // 8 # Example: 2 pixels for 16x16 texture
|
||||||
|
|
||||||
|
if direction == 'north': # Arrow points up (towards decreasing Z)
|
||||||
|
draw.line([(center_x, size), (center_x, 0)], fill=line_color, width=line_width)
|
||||||
|
draw.polygon([(center_x, 0), (center_x - head_size, head_size), (center_x + head_size, head_size)], fill=line_color)
|
||||||
|
elif direction == 'south': # Arrow points down (towards increasing Z)
|
||||||
|
draw.line([(center_x, 0), (center_x, size)], fill=line_color, width=line_width)
|
||||||
|
draw.polygon([(center_x, size), (center_x - head_size, size - head_size), (center_x + head_size, size - head_size)], fill=line_color)
|
||||||
|
elif direction == 'west': # Arrow points left (towards decreasing X)
|
||||||
|
draw.line([(size, center_y), (0, center_y)], fill=line_color, width=line_width)
|
||||||
|
draw.polygon([(0, center_y), (head_size, center_y - head_size), (head_size, center_y + head_size)], fill=line_color)
|
||||||
|
elif direction == 'east': # Arrow points right (towards increasing X)
|
||||||
|
draw.line([(0, center_y), (size, center_y)], fill=line_color, width=line_width)
|
||||||
|
draw.polygon([(size, center_y), (size - head_size, center_y - head_size), (size - head_size, center_y + head_size)], fill=line_color)
|
||||||
|
else:
|
||||||
|
print(f"Warning: Unknown direction '{direction}' for arrow.")
|
||||||
|
return Image.new('RGBA', (size, size), (0, 0, 0, 0)) # Return transparent if direction is invalid
|
||||||
|
|
||||||
|
return arrow_img
|
||||||
|
|
||||||
|
def draw_door_state_overlay(texture: Image.Image, is_open: bool):
|
||||||
|
"""
|
||||||
|
Draws a small overlay symbol in the top-left corner of the texture to indicate door state.
|
||||||
|
- Green open rectangle for open
|
||||||
|
- Red closed rectangle for closed
|
||||||
|
"""
|
||||||
|
draw = ImageDraw.Draw(texture)
|
||||||
|
size = texture.width
|
||||||
|
overlay_size = size // 4 # 1/4 of texture size
|
||||||
|
|
||||||
|
x0, y0 = 2, 2
|
||||||
|
x1, y1 = x0 + overlay_size, y0 + overlay_size
|
||||||
|
|
||||||
|
if is_open:
|
||||||
|
# Draw a green open rectangle (open door shape)
|
||||||
|
draw.rectangle([x0, y0, x1, y1], outline=(0, 200, 0, 255), width=2)
|
||||||
|
# Draw a gap to indicate "open"
|
||||||
|
draw.line([x0 + overlay_size//2, y0, x1, y1], fill=(0, 200, 0, 255), width=2)
|
||||||
|
else:
|
||||||
|
# Draw a red closed rectangle (closed door shape)
|
||||||
|
draw.rectangle([x0, y0, x1, y1], outline=(200, 0, 0, 255), width=2)
|
||||||
|
# Draw a line to indicate "closed"
|
||||||
|
draw.line([x0, y0, x1, y1], fill=(200, 0, 0, 255), width=2)
|
||||||
|
|
||||||
|
def combine_door_halves(
|
||||||
|
bottom_texture: Image.Image,
|
||||||
|
top_texture: Image.Image,
|
||||||
|
half: str = "lower",
|
||||||
|
overlay_opacity: int = 128 # 0-255, 128 = 50% opacity
|
||||||
|
) -> Image.Image:
|
||||||
|
"""
|
||||||
|
Combines the bottom and top door textures into a single 16x16 image.
|
||||||
|
The 'other' half is shown with a semi-transparent white overlay.
|
||||||
|
Args:
|
||||||
|
bottom_texture: PIL.Image for the door bottom (8x16 or 16x16, will be cropped).
|
||||||
|
top_texture: PIL.Image for the door top (8x16 or 16x16, will be cropped).
|
||||||
|
half: "lower" or "upper" (which half is the real one)
|
||||||
|
overlay_opacity: Opacity for the overlay (0=transparent, 255=opaque)
|
||||||
|
Returns:
|
||||||
|
PIL.Image: The combined 16x16 image.
|
||||||
|
"""
|
||||||
|
# Ensure both textures are 16x16 (crop or resize if needed)
|
||||||
|
size = 16
|
||||||
|
bottom = bottom_texture.crop((0, 0, size, size))
|
||||||
|
top = top_texture.crop((0, 0, size, size))
|
||||||
|
|
||||||
|
# Create a new blank image
|
||||||
|
combined = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||||
|
|
||||||
|
if half == "lower":
|
||||||
|
# Place bottom half as normal
|
||||||
|
combined.paste(bottom, (0, 8))
|
||||||
|
# Place top half above, faded
|
||||||
|
faded_top = top.crop((0, 0, size, 8)).copy()
|
||||||
|
overlay = Image.new("RGBA", (size, 8), (255, 255, 255, overlay_opacity))
|
||||||
|
faded_top = Image.alpha_composite(faded_top, overlay)
|
||||||
|
combined.paste(faded_top, (0, 0), faded_top)
|
||||||
|
else: # half == "upper"
|
||||||
|
# Place top half as normal
|
||||||
|
combined.paste(top, (0, 0))
|
||||||
|
# Place bottom half below, faded
|
||||||
|
faded_bottom = bottom.crop((0, 8, size, 16)).copy()
|
||||||
|
overlay = Image.new("RGBA", (size, 8), (255, 255, 255, overlay_opacity))
|
||||||
|
faded_bottom = Image.alpha_composite(faded_bottom, overlay)
|
||||||
|
combined.paste(faded_bottom, (0, 8), faded_bottom)
|
||||||
|
|
||||||
|
return combined
|
||||||
|
|
||||||
|
def draw_door_overlay_symbols(
|
||||||
|
texture: Image.Image,
|
||||||
|
show_lock: bool = False,
|
||||||
|
hinge_side: str = "left"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Draws overlay symbols in the top-left corner of the texture for doors:
|
||||||
|
- Optionally draws an unlocked (open) padlock (if show_lock is True)
|
||||||
|
- Optionally draws a small gray vertical bar on the hinge side ('left' or 'right')
|
||||||
|
Args:
|
||||||
|
texture (PIL.Image): The texture to draw on.
|
||||||
|
show_lock (bool): If True, draws an unlocked padlock.
|
||||||
|
hinge_side (str): 'left' or 'right' to indicate hinge side, or None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Flip texture if hinge is right
|
||||||
|
if hinge_side =="right":
|
||||||
|
texture = texture.transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(texture)
|
||||||
|
size = texture.width
|
||||||
|
overlay_size = size // 4 # 1/4 of texture size
|
||||||
|
|
||||||
|
x0, y0 = 2, 2
|
||||||
|
x1, y1 = x0 + overlay_size, y0 + overlay_size
|
||||||
|
|
||||||
|
# Draw hinge bar if specified
|
||||||
|
if hinge_side in ("left", "right"):
|
||||||
|
bar_width = max(1, overlay_size // 6)
|
||||||
|
bar_color = (120, 120, 120, 255) # Gray
|
||||||
|
if hinge_side == "left":
|
||||||
|
bar_x0 = x0
|
||||||
|
bar_x1 = x0 + bar_width
|
||||||
|
else: # "right"
|
||||||
|
bar_x0 = x1 - bar_width
|
||||||
|
bar_x1 = x1
|
||||||
|
draw.rectangle([bar_x0, y0, bar_x1, y1], fill=bar_color)
|
||||||
|
|
||||||
|
if show_lock:
|
||||||
|
# Draw an unlocked (open) padlock
|
||||||
|
lock_body_top = y1 - overlay_size // 3
|
||||||
|
lock_body_bottom = y1 - 2
|
||||||
|
lock_body_left = x0 + overlay_size // 4
|
||||||
|
lock_body_right = x1 - overlay_size // 4
|
||||||
|
# Body of the lock
|
||||||
|
draw.rectangle(
|
||||||
|
[lock_body_left, lock_body_top, lock_body_right, lock_body_bottom],
|
||||||
|
fill=(200, 200, 0, 255), outline=(120, 120, 0, 255), width=1
|
||||||
|
)
|
||||||
|
# Shackle of the lock (open arc)
|
||||||
|
shackle_box = [
|
||||||
|
lock_body_left - overlay_size // 8, lock_body_top - overlay_size // 4,
|
||||||
|
lock_body_right + overlay_size // 8, lock_body_top + overlay_size // 4
|
||||||
|
]
|
||||||
|
# Draw only part of the arc to look "open"
|
||||||
|
draw.arc(shackle_box, start=30, end=150, fill=(120, 120, 0, 255), width=2)
|
||||||
|
|
||||||
|
def render_grid(
|
||||||
|
textures_2d,
|
||||||
|
texture_size,
|
||||||
|
normal_padding=1,
|
||||||
|
bold_padding=3,
|
||||||
|
grid_line_color=(150, 150, 150, 255),
|
||||||
|
grid_line_bold_color=(80, 80, 80, 255),
|
||||||
|
grid_line_width=1,
|
||||||
|
grid_line_bold_width=3,
|
||||||
|
font_path="arial.ttf",
|
||||||
|
font_size=10,
|
||||||
|
margin=24
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Renders a 2D grid of textures with variable padding for gridlines, bold every 5th line,
|
||||||
|
and draws numbering outside the grid in an expanded canvas.
|
||||||
|
Args:
|
||||||
|
textures_2d: 2D list of PIL.Image objects (row-major: [z][x])
|
||||||
|
texture_size: Size of each texture square (pixels)
|
||||||
|
normal_padding: Space (pixels) between cells for normal gridlines
|
||||||
|
bold_padding: Space (pixels) between cells for every 5th (bold) gridline
|
||||||
|
grid_line_color: Color for normal grid lines
|
||||||
|
grid_line_bold_color: Color for every 5th grid line
|
||||||
|
grid_line_width: Width for normal grid lines
|
||||||
|
grid_line_bold_width: Width for every 5th grid line
|
||||||
|
font_path: Path to the font file
|
||||||
|
font_size: Font size for numbering
|
||||||
|
margin: Margin in pixels for numbering outside the grid
|
||||||
|
Returns:
|
||||||
|
PIL.Image: The rendered grid image with variable padding, gridlines, and outside numbering
|
||||||
|
"""
|
||||||
|
length = len(textures_2d)
|
||||||
|
width = len(textures_2d[0]) if length > 0 else 0
|
||||||
|
|
||||||
|
# Build padding arrays for columns and rows
|
||||||
|
paddings_col = []
|
||||||
|
for col in range(width + 1):
|
||||||
|
if col % 5 == 0:
|
||||||
|
paddings_col.append(bold_padding)
|
||||||
|
else:
|
||||||
|
paddings_col.append(normal_padding)
|
||||||
|
paddings_row = []
|
||||||
|
for row in range(length + 1):
|
||||||
|
if row % 5 == 0:
|
||||||
|
paddings_row.append(bold_padding)
|
||||||
|
else:
|
||||||
|
paddings_row.append(normal_padding)
|
||||||
|
|
||||||
|
# Calculate total image size (without margin)
|
||||||
|
img_width = width * texture_size + sum(paddings_col)
|
||||||
|
img_height = length * texture_size + sum(paddings_row)
|
||||||
|
|
||||||
|
# Create expanded image with margin for numbering (top and left)
|
||||||
|
expanded_width = img_width + margin
|
||||||
|
expanded_height = img_height + margin
|
||||||
|
out_img = Image.new('RGBA', (expanded_width, expanded_height), (255, 255, 255, 0))
|
||||||
|
draw = ImageDraw.Draw(out_img)
|
||||||
|
|
||||||
|
# Try to load a default font; fallback if not available
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(font_path, font_size)
|
||||||
|
except Exception:
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# Precompute cumulative paddings for fast lookup
|
||||||
|
cum_paddings_col = [0]
|
||||||
|
for pad in paddings_col[:-1]:
|
||||||
|
cum_paddings_col.append(cum_paddings_col[-1] + pad + texture_size)
|
||||||
|
cum_paddings_row = [0]
|
||||||
|
for pad in paddings_row[:-1]:
|
||||||
|
cum_paddings_row.append(cum_paddings_row[-1] + pad + texture_size)
|
||||||
|
|
||||||
|
# Paste textures with variable padding, offset by margin
|
||||||
|
for z in range(length):
|
||||||
|
for x in range(width):
|
||||||
|
tx = margin + sum(paddings_col[:x+1]) + x * texture_size
|
||||||
|
ty = margin + sum(paddings_row[:z+1]) + z * texture_size
|
||||||
|
out_img.paste(textures_2d[z][x], (tx, ty), textures_2d[z][x])
|
||||||
|
|
||||||
|
# Draw vertical gridlines and outside numbering
|
||||||
|
x = margin
|
||||||
|
for col in range(width + 1):
|
||||||
|
is_bold = (col % 5 == 0)
|
||||||
|
color = grid_line_bold_color if is_bold else grid_line_color
|
||||||
|
line_width = grid_line_bold_width if is_bold else grid_line_width
|
||||||
|
pad = paddings_col[col]
|
||||||
|
offset = (pad - line_width) // 2
|
||||||
|
draw.line([(x + offset, margin), (x + offset, margin + img_height)], fill=color, width=line_width)
|
||||||
|
|
||||||
|
# Draw numbering outside the grid (above the grid)
|
||||||
|
if is_bold and col != 0 and x < margin + img_width:
|
||||||
|
text = str(col)
|
||||||
|
bbox = font.getbbox(text)
|
||||||
|
text_width = bbox[2] - bbox[0]
|
||||||
|
text_height = bbox[3] - bbox[1]
|
||||||
|
text_x = x + offset - text_width // 2 + line_width // 2
|
||||||
|
text_y = (margin - text_height - 2) if (margin - text_height - 2) > 0 else 2
|
||||||
|
draw.rectangle(
|
||||||
|
[text_x, text_y, text_x + text_width, text_y + text_height],
|
||||||
|
fill=(255, 255, 255, 180)
|
||||||
|
)
|
||||||
|
draw.text((text_x, text_y), text, fill=(0, 0, 0, 255), font=font)
|
||||||
|
x += pad + (texture_size if col < width else 0)
|
||||||
|
|
||||||
|
# Draw horizontal gridlines and outside numbering
|
||||||
|
y = margin
|
||||||
|
for row in range(length + 1):
|
||||||
|
is_bold = (row % 5 == 0)
|
||||||
|
color = grid_line_bold_color if is_bold else grid_line_color
|
||||||
|
line_width = grid_line_bold_width if is_bold else grid_line_width
|
||||||
|
pad = paddings_row[row]
|
||||||
|
offset = (pad - line_width) // 2
|
||||||
|
draw.line([(margin, y + offset), (margin + img_width, y + offset)], fill=color, width=line_width)
|
||||||
|
|
||||||
|
# Draw numbering outside the grid (to the left of the grid)
|
||||||
|
if is_bold and row != 0 and y < margin + img_height:
|
||||||
|
text = str(row)
|
||||||
|
bbox = font.getbbox(text)
|
||||||
|
text_width = bbox[2] - bbox[0]
|
||||||
|
text_height = bbox[3] - bbox[1]
|
||||||
|
text_x = (margin - text_width - 2) if (margin - text_width - 2) > 0 else 2
|
||||||
|
text_y = y + offset - text_height // 2 + line_width // 2
|
||||||
|
draw.rectangle(
|
||||||
|
[text_x, text_y, text_x + text_width, text_y + text_height],
|
||||||
|
fill=(255, 255, 255, 180)
|
||||||
|
)
|
||||||
|
draw.text((text_x, text_y), text, fill=(0, 0, 0, 255), font=font)
|
||||||
|
y += pad + (texture_size if row < length else 0)
|
||||||
|
|
||||||
|
return out_img
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
177
main copy.py
Normal file
177
main copy.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
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("---------------------------------------")
|
||||||
|
|
||||||
|
|
||||||
428
main.py
Normal file
428
main.py
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
import nbtlib
|
||||||
|
import nbtlib.tag
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys # Import sys to print interpreter info
|
||||||
|
import zipfile
|
||||||
|
import re # For parsing Java block strings
|
||||||
|
from PIL import Image, ImageFont # Import Pillow for image manipulation
|
||||||
|
|
||||||
|
from helpers import *
|
||||||
|
|
||||||
|
|
||||||
|
# --- Constants for Blueprint Generation ---
|
||||||
|
OUTPUT_BLUEPRINT_DIR = "blueprints" # Directory to save generated blueprints
|
||||||
|
|
||||||
|
# --- Define the name for your missing texture file ---
|
||||||
|
MISSING_TEXTURE_FILENAME = "missing_texture.png" # Make sure this matches your file name!
|
||||||
|
|
||||||
|
|
||||||
|
# Adjusting for potential nbtlib API changes
|
||||||
|
# In newer nbtlib, exceptions are directly under nbtlib
|
||||||
|
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 # type: ignore
|
||||||
|
NBTFormatError = nbtlib.exceptions.NBTFormatError
|
||||||
|
except ImportError:
|
||||||
|
NBTFormatError = Exception # Generic fallback if not found
|
||||||
|
|
||||||
|
# --- Add this dictionary at the top of your script, or after TEXTURE_SIZE constant ---
|
||||||
|
# This maps specific Java base block names to the actual texture file name you want to use
|
||||||
|
# (if they don't have their own dedicated texture file)
|
||||||
|
TEXTURE_NAME_OVERRIDES = {
|
||||||
|
"deepslate_tile_stairs": "deepslate_tiles", # Use deepslate_tiles.png for deepslate_tile_stairs
|
||||||
|
"deepslate_tile_wall": "deepslate_tiles", # Use deepslate_tiles.png for deepslate_tile_wall
|
||||||
|
"deepslate_tile_slab": "deepslate_tiles", # Use deepslate_tiles.png for deepslate_tile_slab
|
||||||
|
"grass_block": "grass_block_side",
|
||||||
|
"grass": "short_grass", # Use short_grass.png for grass blocks
|
||||||
|
"tall_grass": "short_grass", # Use short_grass.png for tall_grass blocks
|
||||||
|
"chain": "chain_block",
|
||||||
|
"glass_pane": "glass", # Glass panes usually use the base glass texture
|
||||||
|
"dark_oak_log": "dark_oak_log_top", # Sometimes logs have specific top/side textures
|
||||||
|
#stairs
|
||||||
|
"cobblestone_stairs": "cobblestone",
|
||||||
|
"diorite_stairs": "diorite",
|
||||||
|
"oak_stairs": "oak_planks",
|
||||||
|
"spruce_stairs": "spruce_planks",
|
||||||
|
"stone_stairs": "stone",
|
||||||
|
#slabs
|
||||||
|
"cobblestone_slab": "cobblestone",
|
||||||
|
"diorite_slab": "diorite",
|
||||||
|
"oak_slab": "oak_planks",
|
||||||
|
"spruce_slab": "spruce_planks",
|
||||||
|
"stone_slab": "stone",
|
||||||
|
# Add more as needed based on your schematic's palette and texture files
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Function to load all Java block textures ---
|
||||||
|
def load_all_java_block_textures(java_textures_root_dir, missing_texture_file=MISSING_TEXTURE_FILENAME):
|
||||||
|
"""
|
||||||
|
Loads all .png textures from the 'block' subfolder of extracted Java textures.
|
||||||
|
Returns a dictionary mapping base block name (e.g., 'stone') to PIL Image objects.
|
||||||
|
"""
|
||||||
|
loaded_textures = {}
|
||||||
|
block_textures_dir = os.path.join(java_textures_root_dir, 'java_textures') # Assuming this is where extract_java_textures saved them
|
||||||
|
|
||||||
|
if not os.path.exists(block_textures_dir):
|
||||||
|
print(f"Error: Java block textures directory not found at {block_textures_dir}")
|
||||||
|
return loaded_textures
|
||||||
|
|
||||||
|
# --- Load the designated missing texture first ---
|
||||||
|
missing_texture_path = os.path.join(block_textures_dir, missing_texture_file)
|
||||||
|
try:
|
||||||
|
loaded_textures['__missing_texture_fallback__'] = Image.open(missing_texture_path).convert("RGBA")
|
||||||
|
loaded_textures['__missing_texture_fallback__'].info['name'] = "missing_texture"
|
||||||
|
print(f"Loaded missing texture fallback: {missing_texture_file}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Warning: Missing texture fallback '{missing_texture_file}' not found at {missing_texture_path}.")
|
||||||
|
# Create a simple bright pink square if the file is not found
|
||||||
|
loaded_textures['__missing_texture_fallback__'] = Image.new('RGBA', (TEXTURE_SIZE, TEXTURE_SIZE), (255, 0, 255, 255))
|
||||||
|
print("Using default bright pink square for missing textures.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not load missing texture fallback '{missing_texture_file}': {e}")
|
||||||
|
loaded_textures['__missing_texture_fallback__'] = Image.new('RGBA', (TEXTURE_SIZE, TEXTURE_SIZE), (255, 0, 255, 255))
|
||||||
|
print("Using default bright pink square for missing textures.")
|
||||||
|
|
||||||
|
# Add a fully transparent texture for 'air' blocks (if you want air to be transparent, not pink)
|
||||||
|
loaded_textures['air'] = Image.new('RGBA', (TEXTURE_SIZE, TEXTURE_SIZE), (0, 0, 0, 0))
|
||||||
|
|
||||||
|
# --- Load all other block textures ---
|
||||||
|
for filename in os.listdir(block_textures_dir):
|
||||||
|
if filename.endswith(".png"):
|
||||||
|
base_name = filename.replace(".png", "")
|
||||||
|
try:
|
||||||
|
img_path = os.path.join(block_textures_dir, filename)
|
||||||
|
img = Image.open(img_path).convert("RGBA") # Convert to RGBA for proper pasting
|
||||||
|
img.info['name'] = base_name
|
||||||
|
|
||||||
|
loaded_textures[base_name] = img
|
||||||
|
# loaded_textures[base_name] = Image.open(img_path).convert("RGBA") # Convert to RGBA for proper pasting
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not load texture {filename}: {e}")
|
||||||
|
|
||||||
|
print(f"Loaded {len(loaded_textures)} Java block textures.")
|
||||||
|
return loaded_textures
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
# Print debugging info for the nbtlib version being used at runtime
|
||||||
|
print(f"--- Debug Info ---")
|
||||||
|
print(f"Python executable: {sys.executable}")
|
||||||
|
print(f"nbtlib module path: {nbtlib.__file__}")
|
||||||
|
print(f"nbtlib version (runtime): {nbtlib.__version__}")
|
||||||
|
print(f"------------------")
|
||||||
|
|
||||||
|
schematic = nbtlib.load(filepath)
|
||||||
|
root = schematic # In newer nbtlib, the loaded 'schematic' object acts as the root compound tag itself.
|
||||||
|
|
||||||
|
# --- Extract Header Information ---
|
||||||
|
version = int(root["Version"])
|
||||||
|
if version != 2:
|
||||||
|
print(f"Warning: Schematic version is {version}. Expected 2 for .schem files. May not parse correctly.")
|
||||||
|
|
||||||
|
width = int(root["Width"])
|
||||||
|
height = int(root["Height"])
|
||||||
|
length = int(root["Length"])
|
||||||
|
|
||||||
|
print(f"Schematic Dimensions: Width={width}, Height={height}, Length={length}")
|
||||||
|
print(f"Schematic Version: {version}")
|
||||||
|
|
||||||
|
# --- Extract Block Palette ---
|
||||||
|
# Check for 'BlockPalette' first, then fall back to 'Palette'
|
||||||
|
block_palette_nbt = root.get("BlockPalette")
|
||||||
|
if block_palette_nbt is None:
|
||||||
|
block_palette_nbt = root.get("Palette")
|
||||||
|
if block_palette_nbt is None:
|
||||||
|
raise KeyError("Neither 'BlockPalette' nor 'Palette' tag found in the schematic.")
|
||||||
|
|
||||||
|
block_palette = {}
|
||||||
|
for block_id_str, int_id_tag in block_palette_nbt.items():
|
||||||
|
block_palette[int(int_id_tag)] = block_id_str
|
||||||
|
|
||||||
|
print(f"\nBlock Palette ({len(block_palette)} entries):")
|
||||||
|
|
||||||
|
# --- Extract Block Data ---
|
||||||
|
|
||||||
|
decoded_block_ids = []
|
||||||
|
bd = root["BlockData"].__array__()
|
||||||
|
for b in bd:
|
||||||
|
block_id = int(b)
|
||||||
|
decoded_block_ids.append(block_id)
|
||||||
|
|
||||||
|
print(f"\nDecoded Block Data Length: {len(decoded_block_ids)}")
|
||||||
|
|
||||||
|
# --- Reconstruct 3D block array ---
|
||||||
|
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):
|
||||||
|
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())
|
||||||
|
entities = root.get("Entities", nbtlib.tag.List())
|
||||||
|
|
||||||
|
print(f"\nNumber of Block Entities: {len(block_entities)}")
|
||||||
|
print(f"Number of Entities: {len(entities)}")
|
||||||
|
|
||||||
|
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. {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# --- IMPORTANT: Replace with the actual path to your .schem file ---
|
||||||
|
schem_file_path = "18805.schem"
|
||||||
|
BLUEPRINT_VERSION = "v2.3"
|
||||||
|
# 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)
|
||||||
|
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!
|
||||||
|
|
||||||
|
# Base directory where you want to save extracted textures and blueprints
|
||||||
|
resources_base_dir = "resources/"
|
||||||
|
|
||||||
|
# --- Step 1: Ensure Java textures are extracted ---
|
||||||
|
# This only needs to be run once, or when you update your game version.
|
||||||
|
# extract_java_textures(java_game_jar, resources_base_dir)
|
||||||
|
|
||||||
|
# --- Step 2: Load all extracted Java textures into memory ---
|
||||||
|
# This is the directory containing the 'java_textures' subfolder
|
||||||
|
java_textures_loaded = load_all_java_block_textures(resources_base_dir, MISSING_TEXTURE_FILENAME)
|
||||||
|
if not java_textures_loaded:
|
||||||
|
print("Failed to load Java textures. Exiting blueprint generation.")
|
||||||
|
sys.exit(1) # Exit if textures can't be loaded
|
||||||
|
|
||||||
|
# Check if the missing texture fallback was actually loaded/created
|
||||||
|
if '__missing_texture_fallback__' not in java_textures_loaded:
|
||||||
|
print("Fatal Error: Missing texture fallback could not be loaded or created. Exiting.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- Step 3: Parse the schematic file ---
|
||||||
|
parsed_data = parse_schem_file(schem_file_path)
|
||||||
|
|
||||||
|
if parsed_data:
|
||||||
|
print("\nSuccessfully parsed schematic data. Generating blueprints...")
|
||||||
|
|
||||||
|
width = parsed_data["width"]
|
||||||
|
height = parsed_data["height"]
|
||||||
|
length = parsed_data["length"]
|
||||||
|
blocks_3d = parsed_data["blocks_3d"]
|
||||||
|
|
||||||
|
# Create output directory for blueprints if it doesn't exist
|
||||||
|
os.makedirs(OUTPUT_BLUEPRINT_DIR, exist_ok=True)
|
||||||
|
# Create a 'v2' subfolder for these enhanced blueprints
|
||||||
|
os.makedirs(os.path.join(OUTPUT_BLUEPRINT_DIR, BLUEPRINT_VERSION), exist_ok=True)
|
||||||
|
|
||||||
|
# --- Gridline and Padding Settings ---
|
||||||
|
TEXTURE_SIZE = 16 # Space between cells for gridlines
|
||||||
|
GRID_LINE_COLOR = (150, 150, 150, 255)
|
||||||
|
GRID_LINE_BOLD_COLOR = (80, 80, 80, 255)
|
||||||
|
GRID_LINE_WIDTH = 1
|
||||||
|
GRID_LINE_BOLD_WIDTH = 2
|
||||||
|
NORMAL_PADDING = GRID_LINE_WIDTH
|
||||||
|
BOLD_PADDING = GRID_LINE_BOLD_WIDTH
|
||||||
|
GRID_FONT_SIZE = 12 # Font size for grid numbers
|
||||||
|
GRID_LAYER_MARGIN = 50 # Margin around the grid for layer numbers
|
||||||
|
|
||||||
|
# --- Step 4: Generate layer-by-layer blueprints ---
|
||||||
|
for y_layer in range(height):
|
||||||
|
|
||||||
|
current_layer = blocks_3d[y_layer]
|
||||||
|
# Prepare a 2D list of textures for this layer
|
||||||
|
textures_2d = []
|
||||||
|
|
||||||
|
for z in range(length):
|
||||||
|
row_textures = []
|
||||||
|
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)
|
||||||
|
block_states = parse_block_states(java_block_string)
|
||||||
|
|
||||||
|
# Get the base texture to start with. IMPORTANT: Make a .copy()!
|
||||||
|
if base_block_name_for_texture_lookup == 'air':
|
||||||
|
texture_to_paste = java_textures_loaded['air']
|
||||||
|
else:
|
||||||
|
# Use .copy() so we don't modify the original loaded texture in memory
|
||||||
|
texture_to_paste = java_textures_loaded.get(
|
||||||
|
base_block_name_for_texture_lookup,
|
||||||
|
java_textures_loaded['__missing_texture_fallback__']
|
||||||
|
).copy()
|
||||||
|
|
||||||
|
if not texture_to_paste.info['name']:
|
||||||
|
# If the texture doesn't have a name, set it to the base block name for debugging
|
||||||
|
texture_to_paste.info['name'] = java_block_string # Store the name for debugging
|
||||||
|
|
||||||
|
# --- Apply Slab Modifications ---
|
||||||
|
if "_slab" in original_base_block_id: # Simple check for slab types (e.g., 'oak_slab', 'stone_slab')
|
||||||
|
slab_type = block_states.get('type') # 'bottom', 'top', 'double'
|
||||||
|
draw = ImageDraw.Draw(texture_to_paste)
|
||||||
|
|
||||||
|
if slab_type == 'double':
|
||||||
|
# Draw a white horizontal line across the middle
|
||||||
|
draw.line([(0, TEXTURE_SIZE // 2), (TEXTURE_SIZE, TEXTURE_SIZE // 2)],
|
||||||
|
fill=(255, 255, 255, 255), width=1)
|
||||||
|
elif slab_type == 'top':
|
||||||
|
# Make the bottom half transparent (visually representing top half of block)
|
||||||
|
mask = Image.new('L', (TEXTURE_SIZE, TEXTURE_SIZE), 255) # Opaque by default
|
||||||
|
mask_draw = ImageDraw.Draw(mask)
|
||||||
|
mask_draw.rectangle([(0, TEXTURE_SIZE // 2), (TEXTURE_SIZE, TEXTURE_SIZE)], fill=0) # Make bottom half transparent (0)
|
||||||
|
texture_to_paste.putalpha(mask)
|
||||||
|
elif slab_type == 'bottom':
|
||||||
|
# Make the top half transparent (visually representing bottom half of block)
|
||||||
|
mask = Image.new('L', (TEXTURE_SIZE, TEXTURE_SIZE), 255)
|
||||||
|
mask_draw = ImageDraw.Draw(mask)
|
||||||
|
mask_draw.rectangle([(0, 0), (TEXTURE_SIZE, TEXTURE_SIZE // 2)], fill=0) # Make top half transparent (0)
|
||||||
|
texture_to_paste.putalpha(mask)
|
||||||
|
|
||||||
|
# --- Apply Stairs Modifications ---
|
||||||
|
elif "_stairs" in original_base_block_id: # Simple check for stairs types (e.g., 'oak_stairs', 'stone_stairs')
|
||||||
|
stair_half = block_states.get('half', 'bottom') # 'bottom', 'top'
|
||||||
|
stair_facing = block_states.get('facing', 'north') # 'north', 'south', 'east', 'west'
|
||||||
|
|
||||||
|
# Transparency for half (as requested)
|
||||||
|
if stair_half == 'bottom':
|
||||||
|
# Make upper-left quarter transparent (0,0 to TEXTURE_SIZE/2, TEXTURE_SIZE/2)
|
||||||
|
mask = Image.new('L', (TEXTURE_SIZE, TEXTURE_SIZE), 255)
|
||||||
|
mask_draw = ImageDraw.Draw(mask)
|
||||||
|
mask_draw.rectangle([(0, 0), (TEXTURE_SIZE // 2, TEXTURE_SIZE // 2)], fill=0)
|
||||||
|
texture_to_paste.putalpha(mask)
|
||||||
|
elif stair_half == 'top':
|
||||||
|
# Make bottom-left quarter transparent (0, TEXTURE_SIZE/2 to TEXTURE_SIZE/2, TEXTURE_SIZE)
|
||||||
|
mask = Image.new('L', (TEXTURE_SIZE, TEXTURE_SIZE), 255)
|
||||||
|
mask_draw = ImageDraw.Draw(mask)
|
||||||
|
mask_draw.rectangle([(0, TEXTURE_SIZE // 2), (TEXTURE_SIZE // 2, TEXTURE_SIZE)], fill=0)
|
||||||
|
texture_to_paste.putalpha(mask)
|
||||||
|
|
||||||
|
# Red arrow for facing direction
|
||||||
|
if stair_facing:
|
||||||
|
arrow_img = create_arrow_texture(stair_facing, TEXTURE_SIZE)
|
||||||
|
# Paste arrow at the center, using its alpha channel for transparency
|
||||||
|
paste_x = (TEXTURE_SIZE - arrow_img.width) // 2
|
||||||
|
paste_y = (TEXTURE_SIZE - arrow_img.height) // 2
|
||||||
|
texture_to_paste.paste(arrow_img, (paste_x, paste_y), arrow_img)
|
||||||
|
|
||||||
|
# Paste the (potentially modified) texture onto the layer image
|
||||||
|
|
||||||
|
# --- Apply Door Modifications ---
|
||||||
|
elif "_door" in original_base_block_id:
|
||||||
|
material = original_base_block_id.replace("_door", "")
|
||||||
|
door_half = block_states.get('half', 'lower') # 'lower' or 'upper'
|
||||||
|
door_facing = block_states.get('facing', 'north') # 'north', 'south', 'east', 'west'
|
||||||
|
door_hinge = block_states.get('hinge', 'left') # Default to 'left' if not specified
|
||||||
|
door_open = True if block_states.get('open') == "true" else False # True if door is open, False if closed
|
||||||
|
|
||||||
|
# Load both halves
|
||||||
|
bottom_texture = java_textures_loaded.get(
|
||||||
|
f"{material}_door_bottom",
|
||||||
|
java_textures_loaded['__missing_texture_fallback__']
|
||||||
|
).copy()
|
||||||
|
top_texture = java_textures_loaded.get(
|
||||||
|
f"{material}_door_top",
|
||||||
|
java_textures_loaded['__missing_texture_fallback__']
|
||||||
|
).copy()
|
||||||
|
|
||||||
|
# Combine them into a single texture
|
||||||
|
texture_to_paste = combine_door_halves(
|
||||||
|
bottom_texture,
|
||||||
|
top_texture,
|
||||||
|
half=door_half,
|
||||||
|
overlay_opacity=128 # 50% opacity for the faded half
|
||||||
|
)
|
||||||
|
|
||||||
|
if not texture_to_paste.info.get('name'):
|
||||||
|
texture_to_paste.info['name'] = f"{material}_door_{door_half}_combined"
|
||||||
|
|
||||||
|
# Red arrow for facing direction
|
||||||
|
if door_facing:
|
||||||
|
arrow_img = create_arrow_texture(door_facing, TEXTURE_SIZE)
|
||||||
|
# Paste arrow at the center, using its alpha channel for transparency
|
||||||
|
paste_x = (TEXTURE_SIZE - arrow_img.width) // 2
|
||||||
|
paste_y = (TEXTURE_SIZE - arrow_img.height) // 2
|
||||||
|
texture_to_paste.paste(arrow_img, (paste_x, paste_y), arrow_img)
|
||||||
|
|
||||||
|
# Only show unlocked padlock if door is open, always show hinge bar
|
||||||
|
draw_door_overlay_symbols(
|
||||||
|
texture_to_paste,
|
||||||
|
show_lock=door_open,
|
||||||
|
hinge_side=door_hinge
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
row_textures.append(texture_to_paste)
|
||||||
|
textures_2d.append(row_textures)
|
||||||
|
|
||||||
|
# --- Draw Grid Lines ---
|
||||||
|
layer_image_with_grid_and_numbers = render_grid(
|
||||||
|
textures_2d,
|
||||||
|
texture_size=TEXTURE_SIZE,
|
||||||
|
normal_padding=NORMAL_PADDING,
|
||||||
|
bold_padding=BOLD_PADDING,
|
||||||
|
grid_line_color=GRID_LINE_COLOR,
|
||||||
|
grid_line_bold_color=GRID_LINE_BOLD_COLOR,
|
||||||
|
grid_line_width=GRID_LINE_WIDTH,
|
||||||
|
grid_line_bold_width=GRID_LINE_BOLD_WIDTH,
|
||||||
|
font_size=GRID_FONT_SIZE, # or your preferred size
|
||||||
|
margin=GRID_LAYER_MARGIN # or your preferred margin
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
print(f"Generated blueprint for layer {y_layer} at {output_filepath}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("\nBlueprint generation complete!")
|
||||||
|
else:
|
||||||
|
print("Schematic parsing failed. Cannot generate blueprints.")
|
||||||
Loading…
x
Reference in New Issue
Block a user