FallingStars123's picture
Upload folder using huggingface_hub
b0998d1 verified
import numpy as np
import pyvista as pv
from nbtlib import File, Compound, Int, ByteArray, load
import re
import os
# 定义方块类型和子类型映射
BLOCK_TYPE_MAP = {
"log": 0,
"planks": 1,
"stairs": 2,
"slab": 3,
"fence": 4,
"glass_pane": 5,
"door": 6,
"functional": 7, # 比如箱子、工作台这种
"grass_block": 8,
"air": 9
}
STAIR_SUBTYPE_MAP = {
"oak_stairs": 0,
"dark_oak_stairs": 1,
"birch_stairs": 2,
"spruce_stairs": 3
}
def encode_block_data(decoded_data):
"""
编码解码后的 block_data,处理负数和特殊编码的方块 ID,恢复为原始数据格式。
"""
encoded_data = []
for value in decoded_data:
if value <= 127:
# 如果值小于等于 127,直接添加
encoded_data.append(value)
else:
# 需要分割为多个字节,处理超出 127 的数值
negative_value = (value % 128) - 128 # 计算负数部分
next_value = value // 128 # 计算增量部分
# 确保编码后的值在 -128 到 127 范围内
if negative_value < -128 or negative_value > 127:
raise ValueError(f"编码值 {negative_value} 超出了 ByteArray 可接受范围")
if next_value < -128 or next_value > 127:
raise ValueError(f"增量值 {next_value} 超出了 ByteArray 可接受范围")
encoded_data.append(negative_value)
encoded_data.append(next_value)
return encoded_data
def decode_block_data(block_data):
"""
解码 block_data,把负数和特殊编码的方块 ID 转换为正确的无符号数或计算值。
"""
decoded_data = []
i = 0
while i < len(block_data):
value = block_data[i]
if value == 127:
decoded_data.append(value) # Start mark as Byte(127)
i += 1
elif value < 0:
negative_value = value
next_value = block_data[i + 1]
# 根据规则计算最终值
calculated_value = (next_value + 1) * 128 + negative_value
decoded_data.append(calculated_value)
i += 2 # 跳过下一个增量值
else:
decoded_data.append(value) # 正常的 Byte(x)
i += 1
return decoded_data
def build_output_data(block_data, palette, width, height, length):
"""
根据 block_data 和 palette 构建输出数据,包括方块名称和三维坐标。
"""
# 解码 block_data
decoded_block_data = decode_block_data(block_data)
# 构建反向映射
id_to_block = {v: k for k, v in palette.items()}
# 构建输出数据
output_data = []
index = 0
for y in range(height):
for z in range(length):
for x in range(width):
if index >= len(decoded_block_data):
break
block_id = decoded_block_data[index]
block_name = id_to_block.get(block_id, 'unknown')
output_data.append({
'block': block_name,
'coordinates': (x, y, z)
})
index += 1
print(f"✅ 成功加载 {len(output_data)} 个方块数据!")
print(f"✅ 成功加载 {len(palette)} 个方块 ID!")
print(f"✅ 地图尺寸 (宽度x): {int(width)},(高度y): {int(height)},(长度z): {int(length)}")
return output_data
def generate_schem(block_array, palette, width, height, length, filename):
"""
将方块数组和 palette 转换为 .schem 文件。
参数:
block_array: 3D numpy 数组,表示方块在空间中的排布。
palette: 字典,映射方块名称到方块 ID(如 {'minecraft:air': 0, 'minecraft:gold_block': 1})。
width: X 轴方向的长度。
height: Y 轴方向的长度。
length: Z 轴方向的长度。
filename: 输出的 .schem 文件名(如 'output.schem')。
"""
# 构建 BlockData,确保 Y 轴倒序,这样在 Minecraft 中是“从下往上”展示
block_data = []
for y in range(height):
for z in range(length):
for x in range(width):
block_data.append(block_array[y, z, x])
# 编码 block_data
encoded_block_data = encode_block_data(block_data)
# 转换为 ByteArray 格式(编码后的数据不会超出 int8 范围)
block_data = ByteArray(encoded_block_data)
# 构建 NBT 数据
schem_data = File(Compound({
'Palette': Compound({block: Int(id) for block, id in palette.items()}),
'PaletteMax': Int(len(palette)),
'BlockData': block_data,
'Width': Int(width),
'Height': Int(height),
'Length': Int(length),
'Version': Int(1)
}))
# 保存为 .schem 文件
with open(filename, 'wb') as f:
schem_data.write(f)
print(f"✅ 成功生成 '{filename}' 文件!")
def parse_short(value):
return int(value.split('(')[1].rstrip(')'))
def preview_point_cloud(output_data, air_block='minecraft:air', point_size=50):
"""
使用 PyVista 可视化 Minecraft 方块数据的点云。
"""
points = []
colors = []
for data in output_data:
x, y, z = data['coordinates']
block_name = data['block']
if block_name == air_block:
continue # 跳过空气方块
# 把坐标加入点云,同时把 y 和 z 对调,让模型“站正”
points.append([x, z, y])
# 给不同的方块一个颜色(简单用哈希生成颜色)
color = hash(block_name) % 0xFFFFFF # 转成 24 位颜色
r = (color >> 16) & 0xFF
g = (color >> 8) & 0xFF
b = color & 0xFF
colors.append([r, g, b])
# 转换为 NumPy 数组
points = np.array(points)
colors = np.array(colors)
if len(points) == 0:
print("❌ 没有可视化的方块(可能都是空气方块)")
return
# 用 PyVista 创建点云
cloud = pv.PolyData(points)
cloud['colors'] = colors / 255.0 # PyVista 需要 0-1 范围的 RGB
# 绘图
plotter = pv.Plotter()
plotter.add_points(cloud, scalars='colors', rgb=True, point_size=point_size)
plotter.show()
def preview_cubes_with_colors(output_data, air_block='minecraft:air'):
"""
根据 Minecraft 方块数据生成立方体,并为每个方块设置不同的颜色。
"""
plotter = pv.Plotter()
for data in output_data:
x, y, z = data['coordinates']
block_name = data['block']
if block_name == air_block:
continue # 跳过空气方块
# 生成立方体
cube = pv.Cube(center=(x, z, y), x_length=1, y_length=1, z_length=1)
# 生成颜色(哈希转颜色)
color = hash(block_name) % 0xFFFFFF
r = (color >> 16) & 0xFF
g = (color >> 8) & 0xFF
b = color & 0xFF
color = [r / 255.0, g / 255.0, b / 255.0]
# 直接在 add_mesh 里传颜色,避免 point_data 的问题
plotter.add_mesh(cube, color=color, show_edges=False)
plotter.show()
def preview_slices(output_data, slice_axis='z', air_block='minecraft:air'):
"""
使用 PyVista 可视化 Minecraft 方块数据的切片展示。
"""
slices = []
blocks = []
for data in output_data:
x, y, z = data['coordinates']
block_name = data['block']
if block_name == air_block:
continue # 跳过空气方块
# 按切片轴对方块进行分组
if slice_axis == 'z':
slices.append(z)
elif slice_axis == 'y':
slices.append(y)
else:
slices.append(x)
blocks.append([x, y, z])
# 获取唯一的切片层(去重)
slice_layers = list(set(slices))
slice_layers.sort()
# 构建并展示每一层切片
plotter = pv.Plotter()
for slice_layer in slice_layers:
slice_data = [block for i, block in enumerate(blocks) if blocks[i][2] == slice_layer]
points = np.array(slice_data)
if len(points) > 0:
cloud = pv.PolyData(points)
plotter.add_points(cloud, color='blue', point_size=5)
plotter.show()
def rotate_block(x, y, z, rotation_angle, max_x, max_z):
if rotation_angle == 90:
return z, y, max_x - x
elif rotation_angle == 180:
return max_x - x, y, max_z - z
elif rotation_angle == 270:
return max_z - z, y, x
return x, y, z
def rotate_attr_vector(block_type, attr_vector, rotation_angle):
if block_type == BLOCK_TYPE_MAP["stairs"] or block_type == BLOCK_TYPE_MAP["door"]:
facing = attr_vector[0]
new_facing = (facing + rotation_angle // 90) % 4
attr_vector[0] = new_facing
elif block_type == BLOCK_TYPE_MAP["log"]:
axis = attr_vector[0]
axis_map = {0: 2, 2: 0} if rotation_angle in [90, 270] else {0: 0, 1: 1, 2: 2}
attr_vector[0] = axis_map.get(axis, axis)
elif block_type in [BLOCK_TYPE_MAP["fence"], BLOCK_TYPE_MAP["glass_pane"]]:
east, north, south, waterlogged, west = attr_vector
if rotation_angle == 90:
attr_vector = [north, west, east, waterlogged, south]
elif rotation_angle == 180:
attr_vector = [west, south, north, waterlogged, east]
elif rotation_angle == 270:
attr_vector = [south, east, west, waterlogged, north]
return attr_vector
def mirror_block(x, y, z, mirror_direction, max_x, max_z):
if mirror_direction == "north_south":
return max_x - x, y, z
elif mirror_direction == "east_west":
return x, y, max_z - z
return x, y, z
def mirror_attr_vector(block_type, attr_vector, mirror_direction):
if block_type == BLOCK_TYPE_MAP["stairs"] or block_type == BLOCK_TYPE_MAP["door"]:
facing = attr_vector[0]
if mirror_direction == "north_south":
attr_vector[0] = facing if facing in [0, 2] else 4 - facing
if block_type == BLOCK_TYPE_MAP["door"]:
attr_vector[2] = abs(attr_vector[2] - 1)
elif block_type == BLOCK_TYPE_MAP["stairs"]:
if attr_vector[2] in {1, 2, 3, 4}:
attr_vector[2] = {1: 2, 2: 1, 3: 4, 4: 3}.get(attr_vector[2], attr_vector[2])
elif mirror_direction == "east_west":
attr_vector[0] = facing if facing in [1, 3] else 2 - facing
if block_type == BLOCK_TYPE_MAP["door"]:
attr_vector[2] = abs(attr_vector[2] - 1)
elif block_type == BLOCK_TYPE_MAP["stairs"]:
if attr_vector[2] in {1, 2, 3, 4}:
attr_vector[2] = {1: 2, 2: 1, 3: 4, 4: 3}.get(attr_vector[2], attr_vector[2])
elif block_type in [BLOCK_TYPE_MAP["fence"], BLOCK_TYPE_MAP["glass_pane"]]:
east, north, south, waterlogged, west = attr_vector
if mirror_direction == "north_south":
attr_vector = [east, south, north, waterlogged, west]
elif mirror_direction == "east_west":
attr_vector = [west, north, south, waterlogged, east]
return attr_vector
def parse_block(block_str):
match = re.match(r"minecraft:(\w+)(?:\[(.*?)\])?", block_str)
if not match:
return None
block_name, properties = match.groups()
for key in BLOCK_TYPE_MAP:
if key in block_name:
block_type = BLOCK_TYPE_MAP[key]
break
else:
print(f"未知方块类型: {block_name}")
return None
attr_vector = []
subtype = -1
prop_dict = {}
if properties:
prop_dict = dict(prop.split('=') for prop in properties.split(','))
if block_type == BLOCK_TYPE_MAP["stairs"]:
subtype = STAIR_SUBTYPE_MAP.get(block_name, -1)
attr_vector = [
["north", "east", "south", "west"].index(prop_dict.get("facing", "north")),
0 if prop_dict.get("half", "bottom") == "bottom" else 1,
["straight","inner_left", "inner_right", "outer_left", "outer_right"].index(prop_dict.get("shape", "straight")),
0 if prop_dict.get("waterlogged", "false") == "false" else 1
]
elif block_type == BLOCK_TYPE_MAP["log"]:
axis_map = {'x': 0, 'y': 1, 'z': 2}
attr_vector = [axis_map.get(prop_dict.get('axis', 'y'))]
elif block_type == BLOCK_TYPE_MAP["slab"]:
attr_vector = [
0 if prop_dict.get("type", "bottom") == "bottom" else 1,
0 if prop_dict.get("waterlogged", "false") == "false" else 1
]
elif block_type in (BLOCK_TYPE_MAP["fence"], BLOCK_TYPE_MAP["glass_pane"]):
attr_vector = [
0 if prop_dict.get("east", "false") == "false" else 1,
0 if prop_dict.get("north", "false") == "false" else 1,
0 if prop_dict.get("south", "false") == "false" else 1,
0 if prop_dict.get("waterlogged", "false") == "false" else 1,
0 if prop_dict.get("west", "false") == "false" else 1
]
elif block_type == BLOCK_TYPE_MAP["door"]:
attr_vector = [
["north", "east", "south", "west"].index(prop_dict.get("facing", "north")),
0 if prop_dict.get("half", "lower") == "lower" else 1,
0 if prop_dict.get("hinge", "left") == "left" else 1,
0 if prop_dict.get("open", "false") == "false" else 1,
0 if prop_dict.get("powered", "false") == "false" else 1
]
elif block_type == BLOCK_TYPE_MAP["grass_block"]:
attr_vector = [0 if prop_dict.get("snowy", "false") == "false" else 1]
elif block_type == BLOCK_TYPE_MAP["air"]:
attr_vector = []
while len(attr_vector) < 5:
attr_vector.append(-1)
return (block_type, subtype, attr_vector)
def process_block_data(schem_file):
# 确保 schem 文件夹存在
if not os.path.exists("schem"):
os.makedirs("schem")
# 加载 .schem 文件
schem_path = os.path.join("schem", schem_file)
schem_data = load(schem_path)
palette = schem_data['Palette']
block_data = schem_data['BlockData']
width = schem_data['Width']
height = schem_data['Height']
length = schem_data['Length']
# 解码 block_data
decoded_block_data = decode_block_data(block_data)
# 构建输出数据
output_data = build_output_data(decoded_block_data, palette, width, height, length)
# 用户输入检测
while True:
user_input = input("请输入要可视化的选项(1: 点云, 2: 切片, 3: 彩色立方体, 13: 点云和彩色立方体, q/Q: 退出):")
if user_input.lower() in ['q', 'Q']:
print("退出程序。")
break
else:
# 检查用户输入中是否包含 1、2 或 3
if '1' in user_input:
preview_point_cloud(output_data)
if '2' in user_input:
preview_slices(output_data)
if '3' in user_input:
preview_cubes_with_colors(output_data)
if '1' not in user_input and '2' not in user_input and '3' not in user_input:
print("无效的输入,请重新输入。")
# 保存方块数据到文本文件
with open('block_data.txt', 'w', encoding='utf-8') as f:
for block in output_data:
x, y, z = block['coordinates']
block_name = block['block']
if block_name == 'minecraft:dirt':
block_name = 'minecraft:grass_block[snowy=false]'
if x == 9 and y == 9 and z == 9:
block_name = 'minecraft:air'
elif x == 0 and y == 9 and z == 9:
block_name = 'minecraft:air'
elif x == 0 and y == 9 and z == 0:
block_name = 'minecraft:air'
elif x == 9 and y == 9 and z == 0:
block_name = 'minecraft:air'
f.write(f"{x},{y},{z},{block_name}\n")
# 保存元数据
with open('metadata.txt', 'w', encoding='utf-8') as f:
f.write(f"{width},{height},{length}\n")
for block, block_id in palette.items():
f.write(f"{block},{block_id}\n")
print("✅ 方块数据已成功导出到 block_data.txt!")
print("✅ 元数据已成功导出到 metadata.txt!")
def parse_and_process_block_data():
input_file = "block_data.txt"
output_file = "parsed_block_data.txt"
with open(input_file, "r", encoding="utf-8") as f:
block_data = f.readlines()
with open(output_file, "w", encoding="utf-8") as f:
for line in block_data:
parts = line.strip().split(',', 3)
x, y, z, block_name = parts
result = int(x), int(y), int(z), parse_block(block_name)
if result:
f.write(f"{result}\n")
print(f"✅ 解析完成,结果已保存到 {output_file}")
def get_unique_arrays(arrays):
"""返回独特数组的数组(使用哈希表优化)"""
seen = set()
unique_arrays = []
for arr in arrays:
arr_hashable = tuple(map(tuple, arr)) # 将数组转换为可哈希的元组
if arr_hashable not in seen:
unique_arrays.append(arr)
seen.add(arr_hashable)
return unique_arrays
def generate_rotated_and_mirrored_data():
input_file = "parsed_block_data.txt"
output_file = "block_data_"
blocks_original, blocks_90, blocks_180, blocks_270, blocks_mirror_north_south, blocks_mirror_east_west = [], [], [], [], [], []
with open('metadata.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
width, height, length = map(parse_short, lines[0].strip().split(','))
with open(input_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
parts = eval(line)
x, y, z, (block_type, subtype, attr_vector) = parts
blocks_original.append([x, y, z, block_type, subtype] + attr_vector)
for angle in [90, 180, 270]:
new_x, new_y, new_z = rotate_block(x, y, z, angle, width-1, length-1)
new_attr_vector = rotate_attr_vector(block_type, attr_vector.copy(), angle)
locals()[f"blocks_{angle}"].append([new_x, new_y, new_z, block_type, subtype] + new_attr_vector)
for direction in ["north_south", "east_west"]:
new_x, new_y, new_z = mirror_block(x, y, z, direction, width-1, length-1)
new_attr_vector = mirror_attr_vector(block_type, attr_vector.copy(), direction)
locals()[f"blocks_mirror_{direction}"].append([new_x, new_y, new_z, block_type, subtype] + new_attr_vector)
arrays = [blocks_original, blocks_90, blocks_180, blocks_270, blocks_mirror_north_south, blocks_mirror_east_west]
for i in range(len(arrays)):
arrays[i] = sorted(arrays[i], key=lambda block: (block[0], block[1], block[2]))
original_array = np.array(blocks_original, dtype=np.int32)
mirror_north_south_array = np.array(blocks_mirror_north_south, dtype=np.int32)
mirror_east_west_array = np.array(blocks_mirror_east_west, dtype=np.int32)
original_array = original_array[np.lexsort(original_array[:, :3].T)]
mirror_north_south_array = mirror_north_south_array[np.lexsort(mirror_north_south_array[:, :3].T)]
mirror_east_west_array = mirror_east_west_array[np.lexsort(mirror_east_west_array[:, :3].T)]
if not np.array_equal(original_array, mirror_north_south_array):
for i in range(len(original_array)):
if not np.array_equal(original_array[i], mirror_north_south_array[i]):
print(f"原数组: {original_array[i]}")
print(f"南北镜像后: {mirror_north_south_array[i]}")
else:
print("原数组与南北镜像后的数组完全一致!")
unique_arrays = get_unique_arrays(arrays)
for i, array in enumerate(unique_arrays):
array = np.array(array, dtype=np.int32)
max_x, max_y, max_z = array[:, 0].max(), array[:, 1].max(), array[:, 2].max()
structure = np.full((max_x + 1, max_y + 1, max_z + 1, 7), -1, dtype=np.int32)
for x, y, z, block_type, subtype, *attr_vector in array:
structure[x, y, z] = [block_type, subtype] + attr_vector
# 确保 npy 文件夹存在
if not os.path.exists("npy"):
os.makedirs("npy")
# 保存到 npy 文件夹
np.save(os.path.join("npy", output_file + str(i)), structure)
print(f"✅ 数据成功保存到 npy/{output_file + str(i)}.npy,形状为 {structure.shape}")
def compare_npy_and_txt(npy_file, input_txt, check_file):
"""
比较 .npy 文件和解析后的文本文件,检查两者是否一致。
参数:
npy_file: .npy 文件路径。
input_txt: 解析后的文本文件路径。
check_file: 保存比对结果的文本文件路径。
"""
# 加载 .npy 数据
structure = np.load(npy_file)
# 获取形状信息
W, H, D, _ = structure.shape
# 把 npy 文件还原为文本格式
reconstructed_lines = []
# 修正遍历顺序,确保是 (x, y, z)
for y in range(H):
for z in range(D):
for x in range(W):
block_data = structure[x, y, z]
# 不跳过 -1,保留检查完整性
block_type, subtype, *attr_vector = block_data
block_type = int(block_type)
subtype = int(subtype)
attr_vector = [int(v) for v in attr_vector]
# 还原成文本行格式
reconstructed_line = f"({x}, {y}, {z}, ({block_type}, {subtype}, {attr_vector}))"
reconstructed_lines.append(reconstructed_line)
# 读取原始文本数据
with open(input_txt, "r", encoding="utf-8") as f:
original_lines = [line.strip() for line in f.readlines()]
# 比对并写入检查文件
with open(check_file, "w", encoding="utf-8") as f:
max_len = max(len(original_lines), len(reconstructed_lines))
for i in range(max_len):
orig = original_lines[i] if i < len(original_lines) else "[原文件缺失行]"
recon = reconstructed_lines[i] if i < len(reconstructed_lines) else "[还原文件缺失行]"
if orig != recon:
f.write(f"❌ 差异行:\n原始: {orig}\n还原: {recon}\n\n")
else:
f.write(f"✅ 一致行:\n{orig}\n\n")
print(f"检查完成,结果保存到 {check_file}")
def check_accuracy_of_txt2npy():
# 比较生成的 .npy 文件和解析后的文本文件
npy_file = "npy/block_data_0.npy" # 替换为你的 .npy 文件路径
input_txt = "parsed_block_data.txt" # 替换为你的解析后的文本文件路径
check_file = "check_txt2npy.txt" # 替换为你的检查结果文件路径
compare_npy_and_txt(npy_file, input_txt, check_file)
def main():
schem_file = "WoodHouse_3.schem" # 替换为你的 .schem 文件路径
process_block_data(schem_file)
parse_and_process_block_data()
generate_rotated_and_mirrored_data()
# 是否需要检查生成的 .npy 与 .txt 文件的一致性
check_accuracy_of_txt2npy()
if __name__ == "__main__":
main()