# 蓝图导出说明 `dist/exporter.js` 是无第三方依赖的浏览器 ES module。它生成真正的 gzip 压缩 NBT `.schem` 文件,采用 **Sponge Schematic v2**,默认目标是 **Minecraft Java 1.20.1 / DataVersion 3465**。它还提供保留编辑坐标的通用方块状态 JSON 数据导出。主界面另有可重新打开的项目 JSON 备份,格式见下文。 ## 接口 ```js import { createSchematic, createBuildJSON, summarizeBuild, } from './exporter.js'; const blocks = [ { x: -1, y: 0, z: 0, state: 'minecraft:oak_log[axis=y]' }, { x: 0, y: 0, z: 0, state: 'minecraft:oak_planks' }, ]; const options = { name: '林间小屋', author: '我的名字', dataVersion: 3465, offset: [0, 0, 0], // date: Date.now(), // 可选,Unix 毫秒;测试中可固定 }; const schemBlob = await createSchematic(blocks, options); const jsonBlob = createBuildJSON(blocks, options); const summary = summarizeBuild(blocks); function download(blob, filename) { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; document.body.append(anchor); anchor.click(); anchor.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } download(schemBlob, '林间小屋.schem'); ``` `createSchematic()` 返回 `Promise`,MIME 为 `application/gzip`。需要浏览器提供 `Blob`、`Response` 与 `CompressionStream('gzip')`。缺少 gzip 接口会返回明确错误,页面可以继续提供 JSON 备份。导出过程全部发生在浏览器本地。 `createBuildJSON()` 同步返回 `Blob`,用于导出通用方块状态数据。JSON 顶层为 `format: "isocraft-build"`、`version: 1`,包含元数据、`summary`、`blocks`,每个方块使用 `{x,y,z,state}`。坐标保留在原编辑器坐标系;去重及擦除规则与 `.schem` 一致。它适合后续程序处理,**不能通过本原型的项目导入入口重新打开**,也不能直接作为 Minecraft 蓝图导入。 主界面的“保存项目”和“下载可编辑 JSON”由 `app.js` 内的 `downloadProject()` 生成另一种格式:`format: "isocraft-project"`、`version: 1`,方块为 `{x,y,z,id,props?}`,其中 `id` 对应材质库 ID;可选 `props` 保留楼梯 `facing/half`、台阶 `type` 或柱状方块 `axis`。`geometry.js` 合并材质默认状态与实例属性,统一供画布和 `.schem` 使用。旧版无 `props` 的项目仍可打开。文件名后缀为 `.isocraft.json`,可以通过“帮助 → 打开已保存的 JSON 项目”恢复。 | JSON 来源 | 标识 | 方块材料字段 | 本原型帮助窗口可导入 | | --- | --- | --- | --- | | 主界面 `downloadProject()` | `isocraft-project` | `id`,原型调色板 ID | 可以 | | 模块 `createBuildJSON()` | `isocraft-build` | `state`,带命名空间的 MC 状态 | 不可以 | `summarizeBuild()` 返回: ```js { bounds: { min: { x: -1, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 }, }, size: { x: 2, y: 1, z: 1 }, count: 2, materials: [ { state: 'minecraft:oak_log[axis=y]', count: 1 }, { state: 'minecraft:oak_planks', count: 1 }, ], } ``` `bounds` 为包含两端点的非空气包围盒,`size` 为方块数。`count` 只统计最终非空气方块,材料表按数量降序、同数量按状态名排序。 ## 输入约束和坐标约定 - 输入为 `[{x,y,z,state}]` 数组,坐标必须是有符号 32 位整数;支持负坐标。 - 同一位置后写入覆盖前写入。`minecraft:air`、`minecraft:cave_air`、`minecraft:void_air` 均视为擦除。 - 删除和去重完成后计算非空气包围盒,文件内部坐标从包围盒最小点开始,未填写的位置补 `minecraft:air`。 - 状态必须有命名空间,如 `minecraft:stone`。属性按键名规范排序。模块验证语法,不包含完整的 Minecraft 注册表,因此不会确认任意自定义方块或属性是否存在。 - 空数组或最终全为空气时,`createSchematic()` 拒绝并抛出 `RangeError`。`summarizeBuild([])` 返回 `bounds:null`、三个尺寸为 `0`、`count:0`、空材料表;`createBuildJSON()` 允许导出空建筑数据,主界面项目 JSON 也可以保存空建筑。 - 最大输入 1,048,576 条、最大单轴 65,535 格、最大包围盒体积 4,194,304 格(包含空气)、最大 palette 65,536 项(含空气)。NBT 编码上限为 64 MiB。超限时明确报错;这些是本工具的内存保护限制。 - `options.offset` 默认 `[0,0,0]`。需要自定义粘贴锚点时,用它指定建筑最小点相对粘贴点的偏移。它同时写入根 `Offset` 和 `Metadata.WEOffsetX/Y/Z`,匹配 WorldEdit v2 的处理;默认相对粘贴从建筑最小点开始。偏移与尺寸之和也必须在 32 位坐标范围内。 - 输入最小坐标另存于自定义 `Metadata.IsoCraftOrigin`,用于溯源,不参与默认粘贴定位。其他工具可以忽略这个扩展元数据。 WorldEdit 的 v2 读取器用根 `Offset` 设置区域最小点,并用 `Metadata.WEOffset*` 推导剪贴板 origin。这里两组值一致,因此 origin 为零。这一选择依据[官方读取器实现](https://github.com/EngineHub/WorldEdit/blob/version/7.3.x/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/sponge/SpongeSchematicV1Reader.java),尚未在游戏内实测粘贴。 ## 二进制布局 根标签是名为 `Schematic` 的 `TAG_Compound`。整数为大端,NBT 字符串使用 Java modified UTF-8,支持中文、emoji 与 NUL;整个 NBT 经 gzip 压缩。以下字段按照 [Sponge v2 规范](https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md) 写入: | 字段 | NBT 类型 | 本模块写入内容 | | --- | --- | --- | | `Version` | Int | `2` | | `DataVersion` | Int | 默认 `3465`,可覆盖 | | `Width / Height / Length` | Short | 对应 `X / Y / Z` 尺寸,以 16 位无符号值解释 | | `Offset` | Int Array | 三个偏移值 | | `Palette` | Compound | 状态字符串 → 连续 Int 索引;空气固定为 `0` | | `PaletteMax` | Int | palette 项数,等于最大索引加 `1` | | `BlockData` | Byte Array | 每格一个 palette 索引,7 位 VarInt 编码 | | `BlockEntities / Entities` | List of Compound | 空列表 | | `Metadata` | Compound | 名称、作者、毫秒时间、模组命名空间、编辑原点及 WorldEdit 偏移 | 线性索引为: ```text index = x + z × Width + y × Width × Length ``` 遍历顺序是外层 `y`、中层 `z`、内层 `x`。每个 VarInt 的低 7 位携带数值,最高位表示后续字节;不能把 palette 索引直接逐字节写入,否则第 128 个及以后的索引会损坏。 仅导出方块状态。不包含箱子物品、告示牌文字、实体或生物群系数据。当前库包含 437 项,支持直楼梯四方向和上下半部、台阶 bottom/top/double、柱状材质 X/Y/Z 轴向;所有实例属性经允许值检查后写入完整状态。楼梯自动内外转角、门、栅栏、墙和玻璃板尚未提供编辑几何。双台阶的建筑与材料统计按一个占用格计算。 ## 版本选择与核实 Sponge 最新规范为 [v3](https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-3.md),其中 `Schematic` 改为嵌套在根 compound 内,方块数据位于 `Blocks.Palette` / `Blocks.Data`。本模块有意采用旧版 v2 布局,便于对接已有 v2 工具;不能只把 `Version` 改成 `3`。 WorldEdit 官方 7.3.0 API 明确提供 `SPONGE_V2_SCHEMATIC` 与 `SPONGE_V3_SCHEMATIC`,其旧的兼容常量仍指向 v2:[BuiltInClipboardFormat](https://docs.enginehub.org/javadoc/com.sk89q.worldedit/worldedit-core/7.3.0/com/sk89q/worldedit/extent/clipboard/io/BuiltInClipboardFormat.html)。这支持格式选择,但不代表已验证每个 WorldEdit、FAWE 或模组组合。 默认 DataVersion 没有使用第三方表格猜测。准备本套件时,读取了 [Mojang 1.20.1 官方版本元数据](https://piston-meta.mojang.com/v1/packages/d9ad5f3ab4523f9311a4e67d92a51f5a93d67333/1.20.1.json) 指向的[官方 client.jar](https://piston-data.mojang.com/v1/objects/0c3ec587af28e5a785c0b4a7b8a30f9a8f78f838/client.jar) 内部 `version.json`,结果为: ```json { "id": "1.20.1", "world_version": 3465, "protocol_version": 763 } ``` `DataVersion` 使用 `world_version`,不使用协议号。更改它不会把新方块转换成旧方块;目标版本及导入工具仍需识别实际方块状态。当前交付不包含 `.litematic`、老式 `.schematic` 或 Bedrock `.mcstructure` 编码器。 ## 验证记录和游戏内验收 运行: ```sh node scripts/test-export.mjs ``` 已通过 12 组独立验证。前 7 组覆盖:Node `zlib.gunzipSync` 解压;手写 NBT 解析器检查根名、标签类型与大小端;负坐标归一化、空气补位、轴顺序、重复覆盖、擦除、属性排序、中文及 emoji/NUL;两字节和三字节 VarInt;大于 32767 的 unsigned-short 尺寸;自定义偏移和版本;`createBuildJSON()` 的数据结构及坐标保留;非法输入、尺寸及体积超限;缺少浏览器 gzip 时的错误。 第 8–12 组直接读取主界面实际下载的 `.schem` 与项目 JSON,使用独立 NBT / VarInt 解析器解码,再由材质默认状态和保存的实例属性独立推导期望值,逐格比对全部方块与空气。林间工坊核对 **1,384 个方块、4,216 格空气、20×14×20 尺寸**;造型示例核对 **226 个方块、190 格空气、16×2×13 尺寸**,包含四方向楼梯、上下楼梯、三种台阶状态和三种原木轴向;快速填充示例核对 **104 个方块、16 格内部空气、10×4×3 尺寸**;选区变换核对 **62 个方块、288 格空气、10×7×5 尺寸**;窗户源组件核对 **31 个方块、19 格空气、5×5×2 尺寸**。五组均检查 DataVersion 3465、完整 palette 和原点元数据,不依赖重新生成文件后的字节相等。每对示例文件存在时运行,缺文件时明确显示跳过。 另有 `node scripts/test-materials.mjs` 的 6 组检查,核对 437 项 ID / 状态与固定 Java 1.20.1 注册表、全部 53 种楼梯、56 种可获取台阶及完整材料族。`node scripts/test-placement.mjs` 的 9 组检查覆盖 1,078 种可编辑状态及实际占位、重复 JSON 保存与 NBT 状态读取。 这验证了保存下来的真实 UI 下载文件与项目数据一致;测试脚本仍不操作主界面的文件选择及项目重新打开流程,也未执行游戏内导入。 测试用 `testmod:block_*` 是专门覆盖大 palette 的合成状态,不是可在原版 Minecraft 中导入的示例材料。可以额外运行 `node scripts/test-export.mjs --write-fixture` 生成一个仅使用原版状态的 `dist/examples/export-fixture.schem`,尺寸为 `3×2×2`、含 4 个非空气方块。 **已验证文件结构、本地 roundtrip 和真实 UI 下载文件的逐格一致性;未运行 Minecraft / WorldEdit,也未完成游戏内导入。** 首次游戏内验收建议使用独立测试世界、与目标游戏版本匹配的 WorldEdit: 1. 将 `.schem` 放进 WorldEdit 的 schematic 文件夹:服务器常见为 `plugins/WorldEdit/schematics`,其他平台常见为 `config/worldedit/schematics`。 2. `//schem load 文件名`,先用 `//paste -n` 查看选区定位,再用 `//paste -a` 放置非空气方块。 3. 核对尺寸、柱子轴向、玻璃位置、底层高度和粘贴锚点;需要撤销时使用 `//undo`。 路径、加载和粘贴选项依据 [WorldEdit 官方 Clipboard 文档](https://worldedit.enginehub.org/en/latest/usage/clipboard/)。导出时包含包围盒内空气;正常 `//paste` 会处理这些空气,`-a` 会跳过它们。最终兼容声明应记录实际测试的 Minecraft、WorldEdit 和平台版本。