nbt2blueprint/main copy.py

177 lines
6.8 KiB
Python

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("---------------------------------------")