mcblueprint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. mcblueprint-0.1.0/CHANGELOG.md +33 -0
  2. mcblueprint-0.1.0/CODE_OF_CONDUCT.md +39 -0
  3. mcblueprint-0.1.0/CONTRIBUTING.md +87 -0
  4. mcblueprint-0.1.0/DESIGN.md +357 -0
  5. mcblueprint-0.1.0/LICENSE +21 -0
  6. mcblueprint-0.1.0/MANIFEST.in +32 -0
  7. mcblueprint-0.1.0/PKG-INFO +208 -0
  8. mcblueprint-0.1.0/README.en.md +174 -0
  9. mcblueprint-0.1.0/README.md +171 -0
  10. mcblueprint-0.1.0/ROADMAP.md +65 -0
  11. mcblueprint-0.1.0/SECURITY.md +39 -0
  12. mcblueprint-0.1.0/mcblueprint/__init__.py +41 -0
  13. mcblueprint-0.1.0/mcblueprint/analysis/__init__.py +89 -0
  14. mcblueprint-0.1.0/mcblueprint/api.py +106 -0
  15. mcblueprint-0.1.0/mcblueprint/core/__init__.py +16 -0
  16. mcblueprint-0.1.0/mcblueprint/core/block_state.py +79 -0
  17. mcblueprint-0.1.0/mcblueprint/core/blueprint.py +244 -0
  18. mcblueprint-0.1.0/mcblueprint/core/metadata.py +20 -0
  19. mcblueprint-0.1.0/mcblueprint/core/palette.py +40 -0
  20. mcblueprint-0.1.0/mcblueprint/core/region.py +159 -0
  21. mcblueprint-0.1.0/mcblueprint/errors.py +20 -0
  22. mcblueprint-0.1.0/mcblueprint/export/__init__.py +16 -0
  23. mcblueprint-0.1.0/mcblueprint/export/_colors.py +42 -0
  24. mcblueprint-0.1.0/mcblueprint/export/_common.py +17 -0
  25. mcblueprint-0.1.0/mcblueprint/export/heightmap.py +58 -0
  26. mcblueprint-0.1.0/mcblueprint/export/json_export.py +41 -0
  27. mcblueprint-0.1.0/mcblueprint/export/obj.py +55 -0
  28. mcblueprint-0.1.0/mcblueprint/export/png.py +81 -0
  29. mcblueprint-0.1.0/mcblueprint/formats/__init__.py +9 -0
  30. mcblueprint-0.1.0/mcblueprint/formats/_common.py +192 -0
  31. mcblueprint-0.1.0/mcblueprint/formats/base.py +40 -0
  32. mcblueprint-0.1.0/mcblueprint/formats/litematic.py +310 -0
  33. mcblueprint-0.1.0/mcblueprint/formats/schem.py +210 -0
  34. mcblueprint-0.1.0/mcblueprint/formats/vanilla_nbt.py +116 -0
  35. mcblueprint-0.1.0/mcblueprint/generate/__init__.py +4 -0
  36. mcblueprint-0.1.0/mcblueprint/generate/builder.py +153 -0
  37. mcblueprint-0.1.0/mcblueprint/generate/shapes.py +525 -0
  38. mcblueprint-0.1.0/mcblueprint/py.typed +0 -0
  39. mcblueprint-0.1.0/mcblueprint/transform/__init__.py +111 -0
  40. mcblueprint-0.1.0/mcblueprint/transform/geometry.py +44 -0
  41. mcblueprint-0.1.0/mcblueprint/transform/orientation.py +118 -0
  42. mcblueprint-0.1.0/mcblueprint.egg-info/PKG-INFO +208 -0
  43. mcblueprint-0.1.0/mcblueprint.egg-info/SOURCES.txt +63 -0
  44. mcblueprint-0.1.0/mcblueprint.egg-info/dependency_links.txt +1 -0
  45. mcblueprint-0.1.0/mcblueprint.egg-info/requires.txt +12 -0
  46. mcblueprint-0.1.0/mcblueprint.egg-info/top_level.txt +1 -0
  47. mcblueprint-0.1.0/pyproject.toml +128 -0
  48. mcblueprint-0.1.0/setup.cfg +4 -0
  49. mcblueprint-0.1.0/tests/__init__.py +0 -0
  50. mcblueprint-0.1.0/tests/fixtures/__init__.py +0 -0
  51. mcblueprint-0.1.0/tests/fixtures/external.litematic +0 -0
  52. mcblueprint-0.1.0/tests/fixtures/l1.nbt +0 -0
  53. mcblueprint-0.1.0/tests/fixtures/make_external_litematic.py +162 -0
  54. mcblueprint-0.1.0/tests/fixtures/make_sample.py +66 -0
  55. mcblueprint-0.1.0/tests/fixtures/sample.litematic +0 -0
  56. mcblueprint-0.1.0/tests/fixtures/testnbt.nbt +0 -0
  57. mcblueprint-0.1.0/tests/fixtures/vanilla-test.nbt +0 -0
  58. mcblueprint-0.1.0/tests/test_analysis.py +64 -0
  59. mcblueprint-0.1.0/tests/test_core.py +273 -0
  60. mcblueprint-0.1.0/tests/test_export.py +80 -0
  61. mcblueprint-0.1.0/tests/test_formats.py +534 -0
  62. mcblueprint-0.1.0/tests/test_generate.py +94 -0
  63. mcblueprint-0.1.0/tests/test_generate_demo.py +170 -0
  64. mcblueprint-0.1.0/tests/test_schem.py +104 -0
  65. mcblueprint-0.1.0/tests/test_transform.py +119 -0
@@ -0,0 +1,33 @@
1
+ # 变更日志
2
+
3
+ 本文件记录本库的显著变更,格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] — 未发布
9
+
10
+ 首个版本,尚未发布到 PyPI。
11
+
12
+ ### 新增
13
+
14
+ - **核心模型**(`mcblueprint.core`):`BlockState` / `Palette` / `Region` / `Blueprint` /
15
+ `Metadata` / `TileEntity`,与磁盘格式解耦。
16
+ - **格式适配**(`mcblueprint.formats`):原版结构 `.nbt`(含 Create 机械动力蓝图)、
17
+ Litematica `.litematic`(多 region、位打包 `BlockState`、负 `Size` 归一化)、
18
+ WorldEdit Sponge v2 `.schem`;统一 `detect`/`decode`/`encode` 接口。
19
+ - **统一 API**(`mcblueprint.api`):`load` / `save` / `load_bytes` / `save_bytes` /
20
+ `detect_format`,按「显式 fmt > 内容嗅探 > 扩展名」选择格式。
21
+ - **变换**(`mcblueprint.transform`):`translate` / `rotate` / `mirror`,几何变换同步
22
+ 重映射 `facing` / `axis` / `rotation` / `half` / `hinge` 等朝向属性。
23
+ - **形状生成**(`mcblueprint.generate`):链式 `Builder`,覆盖盒体、线、圆/环/椭圆/多边形、
24
+ 球/椭球/柱/锥/棱锥/穹顶、圆环/螺旋/斜坡/楼梯/管道。
25
+ - **分析**(`mcblueprint.analysis`):`surface_area` / `connected_components` / `diff`。
26
+ - **导出**(`mcblueprint.export`):JSON、OBJ、高度图(CSV/JSON/PNG)、PNG 分层切片
27
+ (PNG 相关需 `mcblueprint[export]`)。
28
+ - **异常体系**(`mcblueprint.errors`):`McBlueprintError` / `UnknownFormatError` /
29
+ `ConversionError` / `OutOfBoundsError`;畸形输入统一收敛为 `ConversionError`。
30
+ - 全量类型标注并随包分发 `py.typed`。
31
+
32
+ [Unreleased]: https://github.com/ShimamuraNdAdachi/mcblueprint/compare/v0.1.0...HEAD
33
+ [0.1.0]: https://github.com/ShimamuraNdAdachi/mcblueprint/releases/tag/v0.1.0
@@ -0,0 +1,39 @@
1
+ # 贡献者行为准则
2
+
3
+ 本项目采用 [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
4
+ 作为行为准则(中文译本:<https://www.contributor-covenant.org/zh-cn/version/2/1/code_of_conduct/>)。
5
+ 完整条款以该链接为准,本页仅说明本项目的执行方式。
6
+
7
+ ## 我们的承诺
8
+
9
+ 为了营造开放、友好的环境,我们承诺:无论年龄、体型、可见或不可见的残障、族裔、
10
+ 性别特征、性别认同与表达、经验水平、教育程度、社会经济状况、国籍、外貌、种族、宗教,
11
+ 或性取向与性认同,参与本项目的每个人都免于骚扰。
12
+
13
+ ## 期望行为
14
+
15
+ - 使用友好与包容的语言,尊重不同观点与经验;
16
+ - 接受建设性批评,也以建设性方式提出批评;
17
+ - 聚焦于对项目与社区最有利的事;
18
+ - 对代码与设计就事论事,不针对个人。
19
+
20
+ ## 不可接受的行为
21
+
22
+ - 性化的言语或图像、不受欢迎的性关注或挑逗;
23
+ - 挑衅、侮辱/贬损性评论、人身攻击或政治攻击;
24
+ - 公开或私下的骚扰、跟踪;
25
+ - 未经许可公布他人的私人信息(如住址、邮箱);
26
+ - 在专业场合中其他可被合理认定为不当的行为。
27
+
28
+ ## 执行
29
+
30
+ - **举报渠道**:请通过 GitHub 私信 [@ShimamuraNdAdachi](https://github.com/ShimamuraNdAdachi)
31
+ 或使用仓库的私密举报功能联系维护者;涉及安全漏洞请改用 [SECURITY.md](SECURITY.md) 的渠道。
32
+ - **处理方式**:维护者会审阅并调查所有投诉,必要时采取删除评论、关闭 issue/PR、
33
+ 警告、临时或永久封禁等措施,并尽量对举报者身份保密。
34
+ - **适用范围**:本准则适用于项目空间(issue、PR、讨论区)以及个人代表项目时的公开场合。
35
+
36
+ ## 归属
37
+
38
+ 本准则改编自 [Contributor Covenant](https://www.contributor-covenant.org) v2.1,
39
+ 以 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 授权。
@@ -0,0 +1,87 @@
1
+ # 贡献指南
2
+
3
+ 感谢愿意为本项目出力。提交前请花两分钟读完本页。
4
+
5
+ ## 环境准备
6
+
7
+ ```bash
8
+ git clone https://github.com/ShimamuraNdAdachi/mcblueprint.git
9
+ cd mcblueprint
10
+ python -m venv .venv
11
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
12
+ pip install -e ".[dev]"
13
+ ```
14
+
15
+ ## 提交前必须通过的三项检查
16
+
17
+ ```bash
18
+ ruff check mcblueprint tests tools # 静态检查
19
+ mypy # 类型检查(files = mcblueprint)
20
+ pytest -q # 单元测试
21
+ ```
22
+
23
+ CI 会以 Python 3.10 / 3.11 / 3.12 / 3.13 重复执行上述检查,并额外做一次
24
+ `python -m build` + 发行包内容校验。
25
+
26
+ ## 代码与文档规范
27
+
28
+ 1. **注释与文档使用中文**,代码中的展示文本(异常消息、日志等)统一英文(DESIGN §12)。
29
+ 2. 行宽上限 **120 列**(由 ruff 的 `line-length` 约束)。
30
+ 3. **不强制 `ruff format`**:现有代码的手工折行与格式化器输出不同;若要统一格式请单独提交,
31
+ 不要混在功能改动里。
32
+ 4. 中文注释/文档中的全角标点属既定风格,故 `RUF001`/`RUF002`/`RUF003` 已在配置中关闭。
33
+ 5. 公开 API 的变更必须同步 `DESIGN.md`(接口契约)、`ROADMAP.md`(取舍)与
34
+ `CHANGELOG.md`(面向使用者);`README.md` 与 `README.en.md` 是同一份内容的双语版本,
35
+ 改动其一必须同时改另一份。
36
+ 6. 遇到不确定的设计选择,先开 issue 讨论,不要直接往主分支堆实现。
37
+
38
+ ## 提交信息
39
+
40
+ 沿用现有风格:`范围: 说明`,范围用英文小写模块名,例如:
41
+
42
+ ```
43
+ formats: 结构不完整的文件改抛 ConversionError
44
+ core/blueprint: 统一区域重叠时的语义为「先出现者胜」
45
+ 工程化:引入 ruff + mypy + CI,并修掉检查暴露出的问题
46
+ ```
47
+
48
+ 一次提交只做一件事;纯格式化/重命名请与行为改动分开。
49
+
50
+ ## 测试约定
51
+
52
+ - 测试必须**可重复、可离线**运行,且不得写脏仓库:需要落盘时用
53
+ `tempfile.TemporaryDirectory()`(或在 pytest 中用 `tmp_path`)。
54
+ - 样本分两类,二者不可互相替代:
55
+ - `tests/fixtures/sample.litematic` 由本库自身生成,只能验证**自洽**(自指盲区);
56
+ - `tests/fixtures/external.litematic` 字段布局与位打包由独立实现手写,
57
+ 用于验证**与真实格式兼容**(见 `tests/fixtures/make_external_litematic.py`)。
58
+ - 新增格式/边界行为时,请同时补一个「畸形输入应抛 `ConversionError`」的负例。
59
+ - 需要人工在 Minecraft 里目视校验时,用 `python -m tests.test_generate_demo`(或设置环境变量
60
+ `MCBLUEPRINT_DEMO_DIR=generated`)把演示蓝图写到 `generated/`,不要把生成物提交进仓库。
61
+
62
+ ## 发布流程(维护者)
63
+
64
+ 发布使用 PyPI **Trusted Publishing**(OIDC),仓库里不存在也不应存在任何 PyPI token。
65
+
66
+ 1. 在 PyPI / TestPyPI 的项目设置里配置 trusted publisher:
67
+ - Owner: `ShimamuraNdAdachi`,Repository: `mcblueprint`;
68
+ - Workflow: `release.yml`,Environment: `pypi` / `testpypi`(需与工作流里的
69
+ `environment:` 完全一致)。
70
+ 2. 更新 `mcblueprint/__init__.py` 的 `__version__` 与 `CHANGELOG.md`,合并到 `main`。
71
+ 3. 手动触发 `.github/workflows/release.yml`(`target = testpypi`)做一次预演,
72
+ 在 TestPyPI 上用干净环境 `pip install --index-url https://test.pypi.org/simple/ mcblueprint` 验证。
73
+ 4. 打 tag 并推送:`git tag v0.1.0 && git push origin v0.1.0`。
74
+ 工作流会校验 tag 与 `__version__` 一致(不一致直接失败),构建、`twine check`、
75
+ 发布到 PyPI,并创建对应的 GitHub Release。
76
+
77
+ > 本仓库(`mcblueprint`)是**唯一的工作仓库**:开发、CI 与发布都在这里进行;
78
+ > 早期的 `mcblueprint-dev` 仅作历史存档(已在 GitHub 上归档)。
79
+ > 仓库的公开历史从 `v0.1.0` 的初始提交开始,开发期的提交记录不在本仓库中。
80
+ >
81
+ > 若将来仓库再次改名,需要同步四处:`pyproject.toml` 的 `[project.urls]`、
82
+ > `README.md` / `README.en.md` 的徽章与链接、`CHANGELOG.md` 的版本对比链接、
83
+ > 以及 PyPI 上的 trusted publisher 配置。
84
+
85
+ ## 许可证
86
+
87
+ 本项目以 [MIT](../LICENSE) 发布。提交贡献即表示你同意以同一许可证分发你的贡献。
@@ -0,0 +1,357 @@
1
+ # MC 蓝图操作库 — 架构与接口设计
2
+
3
+ > 状态:已实现(架构与接口已落地,功能取舍见 ROADMAP.md)
4
+ > 库名(暂定):`mcblueprint`,可随时改名
5
+
6
+ ## 1. 目标
7
+
8
+ 一个 Python 库,统一操作两种 Minecraft 蓝图:
9
+
10
+ | 简称 | 格式 | 扩展名 | 压缩 |
11
+ |---|---|---|---|
12
+ | `vanilla` | 原版结构(vanilla ``StructureTemplate``,机械动力蓝图亦为此格式) | `.nbt` | gzip |
13
+ | `litematic` | 投影模组(Litematica)蓝图 | `.litematic` | gzip |
14
+
15
+ 支持四类操作:**编辑修改、变换操作、格式转换、分析统计**。
16
+
17
+ ## 2. 格式背景(来自真实样本)
18
+
19
+ ### 2.1 原版结构 `.nbt`(样本 `l1.nbt`、`testnbt.nbt`、`vanilla-test.nbt`)
20
+
21
+ 机械动力(Create)的蓝图本质即原版 ``StructureTemplate`` 格式,故统一以 ``vanilla`` 命名。
22
+ 样本实际为 gzip 压缩(与初稿「裸 NBT」不符),加载端同时兼容裸 NBT。
23
+
24
+ ```
25
+ 根 Compound
26
+ ├─ size: List[Int] x3 # 蓝图尺寸 (sx, sy, sz)
27
+ ├─ entities: List[Compound] # 实体(样本为空)
28
+ ├─ blocks: List[Compound] # 每条 { pos: List[Int]x3, state: Int, nbt?: Compound }
29
+ ├─ palette: List[Compound] # 每条 { Name: String, Properties?: Compound }
30
+ └─ (mod 私有字段) DataVersion, Railways_DataVersion, brewinandchewin:data_version
31
+ ```
32
+
33
+ - **方块存储**:稀疏列表,`pos` 为相对坐标,`state` 指向 `palette` 下标。
34
+ - **无绝对位置**:蓝图坐标相对于锚点(原点 `(0,0,0)`)。
35
+ - **调色板条目**:`Name`(如 `minecraft:quartz_block`)+ 可选 `Properties`(如 `{facing: west}`)。
36
+ - **方块实体数据**:可选字段 `nbt`,内联在 `blocks` 条目中,仅当方块带方块实体时出现(如 `create:motor`、`minecraft:chest` 的 `Items`)。
37
+
38
+ ### 2.2 投影 `.litematic`(样本 `双维度猪灵交易480猪(主世界端).litematic`)
39
+
40
+ ```
41
+ 根 Compound(gzip 压缩)
42
+ ├─ Regions: Compound # key = region 名
43
+ │ └─ <region>: Compound
44
+ │ ├─ Size: Compound {x,y,z} # region 尺寸
45
+ │ ├─ Position: Compound {x,y,z} # region 世界坐标偏移
46
+ │ ├─ BlockStatePalette: List[Compound] # { Name, Properties? }
47
+ │ ├─ BlockStates: LongArray # 位打包的体素下标
48
+ │ ├─ TileEntities: List[Compound] # { id, x, y, z, ... }
49
+ │ ├─ Entities: List[Compound]
50
+ │ ├─ PendingBlockTicks / PendingFluidTicks: List
51
+ ├─ Metadata: Compound { Name, Author, Description, TimeCreated, ... }
52
+ ├─ Version, SubVersion, MinecraftDataVersion
53
+ ```
54
+
55
+ - **方块存储**:密集位打包。`BlockStates` 为 `long[]`,每条 `bits = max(2, ceil(log2(palette_size)))` 位;`palette_size == 1` 时仍写 `bits = 2` 的全零数组(不省略)。体素下标 `index = x + sx * (z + sz * y)`(x 最快,y 最慢)。
56
+ - **坐标**:region 有 `Position`(世界绝对坐标偏移),体素内部为本地坐标。`Size` 可为负——负号表示区域沿负方向延伸,`Position` 为最大角点;容器体素始终按最小角点顺序存储。
57
+ - **方块实体**:独立 `TileEntities` 列表,`x/y/z` 为体素本地坐标 `0..size-1`(与 `BlockStates` 同套坐标),而非世界坐标。
58
+ - **多 region**:一个文件可含多个 region。
59
+
60
+ ### 2.3 关键差异 → 决定了适配层必须做的事
61
+
62
+ | 维度 | vanilla | litematic |
63
+ |---|---|---|
64
+ | 压缩 | gzip | gzip |
65
+ | 体素存储 | 稀疏列表 `{pos,state}` | 密集位打包 `long[]` |
66
+ | 绝对坐标 | 无(锚点=原点) | region `Position` |
67
+ | region 数 | 单 | 多 |
68
+ | 方块实体数据 | 有,内联在 `blocks[i].nbt`(可选) | 有,独立 `TileEntities` 列表 |
69
+ | 实体 | `entities` | `Entities` + Ticks |
70
+
71
+ ---
72
+
73
+ ## 3. 总体架构(分层)
74
+
75
+ ```
76
+ mcblueprint/
77
+ ├── core/ # 格式无关的核心模型(唯一真值)
78
+ ├── formats/ # 格式适配层:磁盘格式 <-> core 模型
79
+ ├── transform/ # 变换与方块朝向属性重映射
80
+ ├── api.py # 统一对外入口
81
+ └── errors.py # 异常体系
82
+ ```
83
+
84
+ - **core**:所有编辑/变换/统计操作只针对核心模型,与格式解耦。
85
+ - **formats**:每个格式实现同一个接口,只负责编解码(差异封装于此)。
86
+ - **api**:用户唯一接触点,自动嗅探格式。
87
+
88
+ ---
89
+
90
+ ## 4. 核心数据模型(`core/`)
91
+
92
+ ### 4.1 `BlockState`
93
+
94
+ 不可变值对象,表示一个方块状态。
95
+
96
+ ```python
97
+ class BlockState:
98
+ name: str # "minecraft:hopper"
99
+ properties: dict[str, str] # {"facing": "west", "enabled": "true"}
100
+
101
+ @classmethod
102
+ def from_string(cls, s: str) -> "BlockState" # "minecraft:hopper[facing=west,enabled=true]"
103
+ def __str__(self) -> str # 标准字符串形式
104
+ # 实现 __eq__ / __hash__(基于 name + properties)
105
+ ```
106
+
107
+ 约定:字符串采用 Minecraft 标准语法 `namespace:id[prop=val,...]`,作为调色板 key 与用户交互的规范表示。
108
+
109
+ ### 4.2 `Palette`
110
+
111
+ 有序调色板,下标 <-> 状态互转。
112
+
113
+ ```python
114
+ class Palette:
115
+ def index_of(self, state: BlockState) -> int # 不存在抛 KeyError
116
+ def state_of(self, index: int) -> BlockState # 越界抛 IndexError
117
+ def add(self, state: BlockState) -> int # 去重,返回下标
118
+ def __len__(self) -> int
119
+ def __iter__(self) -> Iterator[BlockState]
120
+ ```
121
+
122
+ ### 4.3 `Region`
123
+
124
+ 单个体素区域(本地坐标 `0..size-1`)。
125
+
126
+ ```python
127
+ class Region:
128
+ name: str
129
+ position: tuple[int, int, int] # 本 region 在蓝图/世界空间的原点偏移
130
+ size: tuple[int, int, int] # (sx, sy, sz),恒为正
131
+
132
+ def get(self, x, y, z) -> BlockState
133
+ def set(self, x, y, z, state: BlockState)
134
+ def fill(self, box, state: BlockState) -> int # 返回写入方块数
135
+ def clone(self) -> "Region"
136
+ def stats(self) -> Counter[BlockState]
137
+
138
+ # ---- 方块实体 ----
139
+ def get_tile_entity(self, x, y, z) -> "TileEntity | None"
140
+ def set_tile_entity(self, x, y, z, nbt: dict)
141
+ def remove_tile_entity(self, x, y, z)
142
+ ```
143
+
144
+ - 内部体素存储封装为私有实现(稀疏 dict 或密集数组),对外只暴露 `get/set`。
145
+ - 坐标越界抛 `IndexError`。
146
+ - **坐标约定**:`get/set` 用**本地坐标**。
147
+
148
+ ### 4.4 `Blueprint`
149
+
150
+ 顶层对象,一个或多个 region + 元数据。
151
+
152
+ ```python
153
+ class Blueprint:
154
+ metadata: Metadata
155
+ regions: list[Region]
156
+
157
+ def region(self, name: str | None = None) -> Region
158
+ # ---- 编辑 ----
159
+ def get_block(self, x, y, z) -> BlockState # 全局坐标
160
+ def set_block(self, x, y, z, state: BlockState)
161
+ def replace(self, old, new) -> int # old/new 可为 BlockState 或字符串
162
+ # ---- 变换 ----
163
+ def translate(self, dx, dy, dz)
164
+ def rotate(self, axis: str, times: int = 1) # axis ∈ {x,y,z}
165
+ def mirror(self, axis: str) # axis 为镜像平面法线
166
+ # ---- 分析 ----
167
+ def bounds(self) -> tuple[tuple[int,int,int], tuple[int,int,int]] # (min, max) 全局
168
+ def stats(self) -> Counter[BlockState]
169
+ def block_count(self) -> int
170
+ # ---- 其他 ----
171
+ def clone(self) -> "Blueprint"
172
+ ```
173
+
174
+ ### 4.5 `Metadata`
175
+
176
+ ```python
177
+ @dataclass
178
+ class Metadata:
179
+ name: str = ""
180
+ author: str = ""
181
+ description: str = ""
182
+ time_created: int | None = None
183
+ time_modified: int | None = None
184
+ # 预留:其他格式特有字段放入 extras: dict
185
+ extras: dict = field(default_factory=dict)
186
+ ```
187
+
188
+ ### 4.6 坐标统一抽象规则
189
+
190
+ - 核心模型内所有体素用**本地坐标** `0..size-1`。
191
+ - `Region.position` 保存该 region 的原点偏移;`Blueprint.get_block` 用**全局坐标** = `position + 本地坐标`。
192
+ - **vanilla → core**:单 region,`position = (0,0,0)`(锚点即原点)。
193
+ - **litematic → core**:region 的 `position` 取自 NBT `Position` 字段;`size` 取 `Size` 的绝对值。`Size` 为负时,`Position` 是最大角点,加载器归一化为最小角点并取正尺寸(体素按最小角点顺序存储,无需按符号翻转)。
194
+ - 未命中任何 region 的全局坐标:`get_block` 返回 `air`(`minecraft:air`)。
195
+
196
+ ### 4.7 方块实体(`TileEntity`)
197
+
198
+ 承载方块实体 NBT(箱子的 `Items`、告示牌文字、漏斗内容物等),与方块状态属性分离。
199
+
200
+ ```python
201
+ class TileEntity:
202
+ position: tuple[int, int, int]
203
+ nbt: dict # 原样保留的复合标签,如 {"id": "minecraft:chest", "Items": [...]}
204
+ ```
205
+
206
+ - 存储方式:`Region` 内部用 `{位置: TileEntity}` 稀疏映射;`Region.clone` 连同方块实体一起深拷贝。
207
+ - 归属校验(某坐标是否有对应方块)由上层操作负责,核心模型不强制。
208
+ - 仅通过 `Region.get_tile_entity / set_tile_entity / remove_tile_entity` 访问,不在 `Blueprint` 层做全局坐标封装。
209
+ - **两格式统一映射**:
210
+ - vanilla:从 `blocks[i].nbt` 提取(`pos` 即位置);保存时写回 `nbt` 字段。
211
+ - litematic:从 `TileEntities` 列表提取(`x/y/z` 为本地坐标);保存时写回该列表。
212
+ - `nbt` 内的 `id` 是**方块实体类型 id**,可能与方块状态 `Name` 不同(如 `create:shaft` 方块对应 `create:simple_kinetic` 方块实体),原样保留,不做归一化。
213
+
214
+ ---
215
+
216
+ ## 5. 格式适配层(`formats/`)
217
+
218
+ ### 5.1 抽象接口
219
+
220
+ ```python
221
+ class BlueprintFormat(ABC):
222
+ name: str # "vanilla" / "litematic" / "schem"
223
+ extensions: list[str] # [".nbt"] / [".litematic"] / [".schem"]
224
+
225
+ @classmethod
226
+ def detect(cls, data: bytes) -> bool # 按魔数/结构判断,不依赖扩展名
227
+ @classmethod
228
+ def decode(cls, data: bytes) -> Blueprint # 字节 -> 核心模型
229
+ @classmethod
230
+ def encode(cls, bp: Blueprint) -> bytes # 核心模型 -> 字节
231
+ ```
232
+
233
+ - 适配器**只在字节层面**工作;文件读写、格式推断与选择由 `api.py` 统一负责,避免
234
+ 「基类走文件流、api 走字节」两条并行路径。
235
+ - `detect` 是**便宜的结构探测**,对任意输入都只返回 `True`/`False`(不抛异常)。
236
+ 「探测为真」不保证可解码:字段缺失或取值畸形时 `decode` 必须抛
237
+ `McBlueprintError` 子类(实测约定为 `ConversionError`),不得泄漏 `KeyError`/
238
+ `IndexError` 等原始异常。
239
+ - `detect`:vanilla 判断根含 `size`+`blocks`+`palette`;litematic 判断解压后根含
240
+ `Regions`;schem 判断根含 `Version==2` 且含 `Palette`+`BlockData`+`Width`+`Height`+`Length`。
241
+
242
+ ### 5.2 实现
243
+
244
+ ```python
245
+ class VanillaNbtFormat(BlueprintFormat): # formats/vanilla_nbt.py
246
+ class LitematicFormat(BlueprintFormat): # formats/litematic.py
247
+ class SchemFormat(BlueprintFormat): # formats/schem.py
248
+ ```
249
+
250
+ - `VanillaNbtFormat`:读 `blocks` 稀疏列表重建体素;写时从体素生成列表。
251
+ - `LitematicFormat`:读位打包 `long[]`(`bits = max(2, ceil(log2(palette_size)))`);写时按同规则重打包。负责 gzip 解压/压缩。
252
+ - `SchemFormat`:WorldEdit Sponge v2 `.schem`,`BlockData` 为 varint 数组,`Palette` 为状态字符串到下标。
253
+ - 单 region 格式(vanilla/schem)遇到多 region 时由 `_common.merge_regions` 合并;区域互相重叠时抛 `ConversionError`。
254
+ - 共享工具(gzip、`require_keys`、`palette_state`、`merge_regions`)集中在 `formats/_common.py`,适配器之间不互相引用私有实现。
255
+
256
+ ---
257
+
258
+ ## 6. 统一对外 API(`api.py`)
259
+
260
+ ```python
261
+ def load(path: str | Path | BinaryIO) -> Blueprint
262
+ def save(bp: Blueprint, path: str | Path | BinaryIO, fmt: str | None = None)
263
+
264
+ def load_bytes(data: bytes, fmt: str | None = None) -> Blueprint
265
+ def save_bytes(bp: Blueprint, fmt: str) -> bytes
266
+
267
+ def detect_format(data: bytes) -> str | None # "vanilla" | "litematic" | None
268
+ ```
269
+
270
+ - `load`:读字节 → `detect_format` 自动探测(`fmt` 未给定时)→ 调用对应 `load`。
271
+ - `save`:`fmt` 缺省时按扩展名推断(`.nbt`→vanilla,`.litematic`→litematic),也可显式指定。
272
+ - `detect_format` 内部按 `extensions` + 结构判断。
273
+
274
+ ---
275
+
276
+ ## 7. 变换操作(`transform/`)
277
+
278
+ 变换分两层:
279
+
280
+ 1. **几何变换**:坐标重排(旋转/镜像/平移),纯数组操作。
281
+ 2. **朝向属性重映射**:对带方向的方块状态,同步变换 `facing / axis / rotation / half` 等属性。
282
+
283
+ ```python
284
+ # transform/orientation.py
285
+ def rotate_state(state: BlockState, axis: str, times: int) -> BlockState
286
+ def mirror_state(state: BlockState, axis: str) -> BlockState
287
+ ```
288
+
289
+ - 采用**数据驱动规则表**,覆盖常见带朝向方块(`facing`/`axis`/`rotation`/`half`/`hinge` 等)。
290
+ - 规则表外未识别的方块:几何变换照做,属性原样保留(保守策略)。
291
+ - ⚠️ 这是全库**工作量最大**的模块,建议作为独立里程碑。
292
+
293
+ ---
294
+
295
+ ## 8. 格式转换策略
296
+
297
+ `load(A) → Blueprint → save(B)` 天然支持互转,但需明确差异处理:
298
+
299
+ | 方向 | 策略 |
300
+ |---|---|
301
+ | vanilla → litematic | 单 region;`Position=(0,0,0)`;生成 `Metadata`(Name 取文件名) |
302
+ | litematic → vanilla | 单 region 直接转;**多 region**:若各 region 不重叠则合并为一个 region,重叠则抛 `ConversionError` |
303
+ | 方块实体数据 | 两格式都支持,仅存储位置不同(vanilla `blocks[i].nbt` / litematic `TileEntities`)→ **可无损互转** |
304
+ | 实体 `Entities` | vanilla `entities` / litematic `Entities` 原样保留;vanilla→litematic 归入 `Entities` |
305
+
306
+ - 调色板在两格式间**结构一致**(均为 `{Name, Properties}`),可直接映射。
307
+
308
+ ---
309
+
310
+ ## 9. 错误体系(`errors.py`)
311
+
312
+ ```python
313
+ class McBlueprintError(Exception): ...
314
+ class UnknownFormatError(McBlueprintError): ... # 无法识别格式
315
+ class ConversionError(McBlueprintError): ... # 格式间无法无损转换
316
+ class OutOfBoundsError(IndexError, McBlueprintError): ... # 坐标越界
317
+ ```
318
+
319
+ ---
320
+
321
+ ## 10. 依赖与兼容
322
+
323
+ - 依赖:`nbtlib`(读写 NBT,含 gzip、`LongArray`、`List`、`Compound`)。
324
+ - Python ≥ 3.10(dataclass、`match`、`X | None` 语法)。
325
+ - 纯内存模型,不引入惰性加载。
326
+
327
+ ---
328
+
329
+ ## 11. 实现里程碑
330
+
331
+ 1. **core**:`BlockState` / `Palette` / `Region` / `Blueprint` + 单测。
332
+ 2. **formats**:`VanillaNbtFormat`、`LitematicFormat` 读写(含位打包、gzip)。
333
+ 3. **api**:`load/save/detect_format`,用两份样本做往返(round-trip)验证。
334
+ 4. **transform**:几何变换 + 朝向属性规则表(分阶段补全规则)。
335
+ 5. **分析统计 + 格式转换**:`stats/bounds/replace` 与互转的差异策略。
336
+ 6. 打包为可安装库(`pyproject.toml`)。
337
+
338
+ ---
339
+
340
+ ## 12. 开发规范
341
+
342
+ 1. 注释使用中文,代码使用的展示文本统一英文(如异常展示等)
343
+ 2. 遇到不确定选择停下确认
344
+ 3. 提交前本地跑齐三项检查(CI 会重复执行,配置见 `pyproject.toml`):
345
+
346
+ ```bash
347
+ ruff check mcblueprint tests # 静态检查
348
+ mypy # 类型检查(files = mcblueprint)
349
+ pytest -q # 单元测试
350
+ ```
351
+
352
+ 4. 行宽上限 120 列(由 ruff 的 `line-length` 约束);**不强制 `ruff format`**——现有代码
353
+ 的手工折行与格式化器输出不同,若要统一格式请作为独立提交,不要混在功能改动里
354
+ 5. 中文注释/文档中的全角标点属既定风格,故 ruff 的 `RUF001`/`RUF002`/`RUF003` 已关闭
355
+ 6. 测试样本分两类:`tests/fixtures/sample.litematic` 由本库自身生成,只能验证自洽;
356
+ 需要验证与真实格式兼容时使用 `tests/fixtures/external.litematic`(字段布局手写 +
357
+ 独立位打包实现,见 `tests/fixtures/make_external_litematic.py`)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShimamuraNdAdachi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # sdist 内容清单。
2
+ #
3
+ # 目的有二:
4
+ # 1. 让源码包自带许可证、README、变更日志与完整测试样本,可离线复现
5
+ # `pytest`(setuptools 默认只自动收录 `tests/test*.py`,会漏掉
6
+ # `tests/__init__.py` 与 `tests/fixtures/`)。
7
+ # 2. 收紧自动发现范围:否则仓库根目录下任何多余的 `.py` 都会被塞进 sdist。
8
+ #
9
+ # wheel 不受本文件影响:`[tool.setuptools.packages.find]` 只收录
10
+ # `mcblueprint*`,故 tests 不会进入 wheel。
11
+
12
+ include LICENSE
13
+ include README.md
14
+ include README.en.md
15
+ include CHANGELOG.md
16
+ include DESIGN.md
17
+ include ROADMAP.md
18
+ include CONTRIBUTING.md
19
+ include SECURITY.md
20
+ include CODE_OF_CONDUCT.md
21
+
22
+ recursive-include mcblueprint py.typed
23
+
24
+ recursive-include tests *.py *.nbt *.litematic
25
+
26
+ # 参考模组源码与本地运行产物永不入库、也不进发行包。
27
+ prune example-code
28
+ prune generated
29
+ prune .github
30
+ prune .claude
31
+
32
+ global-exclude *.py[cod] *.so __pycache__ .DS_Store Thumbs.db desktop.ini