amulet-core 2.0a7__cp311-cp311-win_amd64.whl → 2.0.1.0.1297307203.19.43.34808.0a0__cp311-cp311-win_amd64.whl

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.

Potentially problematic release.


This version of amulet-core might be problematic. Click here for more details.

Files changed (325) hide show
  1. amulet/core/__init__.py +36 -0
  2. amulet/core/__pyinstaller/hook-amulet.core.py +4 -0
  3. amulet/core/_amulet_core.cp311-win_amd64.pyd +0 -0
  4. amulet/core/_amulet_core.pyi +7 -0
  5. amulet/{_version.py → core/_version.py} +3 -3
  6. amulet/core/amulet_core.dll +0 -0
  7. amulet/core/amulet_core.lib +0 -0
  8. amulet/core/amulet_coreConfig.cmake +18 -0
  9. amulet/{biome.pyi → core/biome/__init__.pyi} +3 -3
  10. amulet/core/biome/biome.hpp +53 -0
  11. amulet/{block.pyi → core/block/__init__.pyi} +25 -26
  12. amulet/core/block/block.hpp +156 -0
  13. amulet/{block_entity.pyi → core/block_entity/__init__.pyi} +7 -7
  14. amulet/core/block_entity/block_entity.hpp +84 -0
  15. amulet/{errors.py → core/chunk/__init__.pyi} +37 -33
  16. amulet/core/chunk/chunk.hpp +126 -0
  17. amulet/core/chunk/component/__init__.pyi +18 -0
  18. amulet/core/chunk/component/biome_3d_component.hpp +96 -0
  19. amulet/core/chunk/component/block_component.hpp +101 -0
  20. amulet/core/chunk/component/block_component.pyi +28 -0
  21. amulet/core/chunk/component/block_entity_component.hpp +119 -0
  22. amulet/core/chunk/component/section_array_map.hpp +129 -0
  23. amulet/{chunk_components.pyi → core/chunk/component/section_array_map.pyi} +4 -24
  24. amulet/core/dll.hpp +21 -0
  25. amulet/core/entity/__init__.pyi +105 -0
  26. amulet/core/entity/entity.hpp +100 -0
  27. amulet/{palette → core/palette}/__init__.pyi +2 -2
  28. amulet/core/palette/biome_palette.hpp +65 -0
  29. amulet/{palette → core/palette}/biome_palette.pyi +8 -8
  30. amulet/core/palette/block_palette.hpp +71 -0
  31. amulet/{palette → core/palette}/block_palette.pyi +12 -10
  32. amulet/core/selection/__init__.pyi +8 -0
  33. amulet/core/selection/box.hpp +86 -0
  34. amulet/core/selection/box.pyi +215 -0
  35. amulet/core/selection/group.hpp +80 -0
  36. amulet/core/selection/group.pyi +213 -0
  37. amulet/{version.pyi → core/version/__init__.pyi} +58 -10
  38. amulet/core/version/version.hpp +204 -0
  39. {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/METADATA +25 -20
  40. amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/RECORD +45 -0
  41. {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/WHEEL +1 -1
  42. amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info/entry_points.txt +2 -0
  43. amulet/__init__.cp311-win_amd64.pyd +0 -0
  44. amulet/__init__.py.cpp +0 -43
  45. amulet/__init__.pyi +0 -28
  46. amulet/__pyinstaller/hook-amulet.py +0 -4
  47. amulet/_init.py +0 -26
  48. amulet/biome.cpp +0 -36
  49. amulet/biome.hpp +0 -43
  50. amulet/biome.py.cpp +0 -122
  51. amulet/block.cpp +0 -435
  52. amulet/block.hpp +0 -119
  53. amulet/block.py.cpp +0 -377
  54. amulet/block_entity.cpp +0 -12
  55. amulet/block_entity.hpp +0 -56
  56. amulet/block_entity.py.cpp +0 -115
  57. amulet/chunk.cpp +0 -16
  58. amulet/chunk.hpp +0 -99
  59. amulet/chunk.py.cpp +0 -80
  60. amulet/chunk.pyi +0 -28
  61. amulet/chunk_components/biome_3d_component.cpp +0 -5
  62. amulet/chunk_components/biome_3d_component.hpp +0 -79
  63. amulet/chunk_components/block_component.cpp +0 -41
  64. amulet/chunk_components/block_component.hpp +0 -88
  65. amulet/chunk_components/block_entity_component.cpp +0 -5
  66. amulet/chunk_components/block_entity_component.hpp +0 -147
  67. amulet/chunk_components/section_array_map.cpp +0 -129
  68. amulet/chunk_components/section_array_map.hpp +0 -147
  69. amulet/collections/eq.py.hpp +0 -37
  70. amulet/collections/hash.py.hpp +0 -27
  71. amulet/collections/holder.py.hpp +0 -37
  72. amulet/collections/iterator.py.hpp +0 -80
  73. amulet/collections/mapping.py.hpp +0 -199
  74. amulet/collections/mutable_mapping.py.hpp +0 -226
  75. amulet/collections/sequence.py.hpp +0 -163
  76. amulet/collections.pyi +0 -40
  77. amulet/data_types.py +0 -29
  78. amulet/entity.py +0 -182
  79. amulet/game/__init__.py +0 -7
  80. amulet/game/_game.py +0 -152
  81. amulet/game/_universal/__init__.py +0 -1
  82. amulet/game/_universal/_biome.py +0 -17
  83. amulet/game/_universal/_block.py +0 -47
  84. amulet/game/_universal/_version.py +0 -68
  85. amulet/game/abc/__init__.py +0 -22
  86. amulet/game/abc/_block_specification.py +0 -150
  87. amulet/game/abc/biome.py +0 -213
  88. amulet/game/abc/block.py +0 -331
  89. amulet/game/abc/game_version_container.py +0 -25
  90. amulet/game/abc/json_interface.py +0 -27
  91. amulet/game/abc/version.py +0 -44
  92. amulet/game/bedrock/__init__.py +0 -1
  93. amulet/game/bedrock/_biome.py +0 -35
  94. amulet/game/bedrock/_block.py +0 -42
  95. amulet/game/bedrock/_version.py +0 -165
  96. amulet/game/java/__init__.py +0 -2
  97. amulet/game/java/_biome.py +0 -35
  98. amulet/game/java/_block.py +0 -60
  99. amulet/game/java/_version.py +0 -176
  100. amulet/game/translate/__init__.py +0 -12
  101. amulet/game/translate/_functions/__init__.py +0 -15
  102. amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
  103. amulet/game/translate/_functions/_code_functions/_text.py +0 -553
  104. amulet/game/translate/_functions/_code_functions/banner_pattern.py +0 -67
  105. amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +0 -152
  106. amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +0 -88
  107. amulet/game/translate/_functions/_code_functions/bedrock_sign.py +0 -152
  108. amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +0 -16
  109. amulet/game/translate/_functions/_code_functions/custom_name.py +0 -146
  110. amulet/game/translate/_functions/_frozen.py +0 -66
  111. amulet/game/translate/_functions/_state.py +0 -54
  112. amulet/game/translate/_functions/_typing.py +0 -98
  113. amulet/game/translate/_functions/abc.py +0 -123
  114. amulet/game/translate/_functions/carry_nbt.py +0 -160
  115. amulet/game/translate/_functions/carry_properties.py +0 -80
  116. amulet/game/translate/_functions/code.py +0 -143
  117. amulet/game/translate/_functions/map_block_name.py +0 -66
  118. amulet/game/translate/_functions/map_nbt.py +0 -111
  119. amulet/game/translate/_functions/map_properties.py +0 -93
  120. amulet/game/translate/_functions/multiblock.py +0 -112
  121. amulet/game/translate/_functions/new_block.py +0 -42
  122. amulet/game/translate/_functions/new_entity.py +0 -43
  123. amulet/game/translate/_functions/new_nbt.py +0 -206
  124. amulet/game/translate/_functions/new_properties.py +0 -64
  125. amulet/game/translate/_functions/sequence.py +0 -51
  126. amulet/game/translate/_functions/walk_input_nbt.py +0 -331
  127. amulet/game/translate/_translator.py +0 -433
  128. amulet/img/__init__.py +0 -10
  129. amulet/img/missing_no.png +0 -0
  130. amulet/img/missing_pack.png +0 -0
  131. amulet/img/missing_world.png +0 -0
  132. amulet/io/binary_reader.hpp +0 -45
  133. amulet/io/binary_writer.hpp +0 -30
  134. amulet/item.py +0 -75
  135. amulet/level/__init__.pyi +0 -23
  136. amulet/level/_load.py +0 -100
  137. amulet/level/abc/__init__.py +0 -12
  138. amulet/level/abc/_chunk_handle.py +0 -335
  139. amulet/level/abc/_dimension.py +0 -86
  140. amulet/level/abc/_history/__init__.py +0 -1
  141. amulet/level/abc/_history/_cache.py +0 -224
  142. amulet/level/abc/_history/_history_manager.py +0 -291
  143. amulet/level/abc/_level/__init__.py +0 -5
  144. amulet/level/abc/_level/_compactable_level.py +0 -10
  145. amulet/level/abc/_level/_creatable_level.py +0 -28
  146. amulet/level/abc/_level/_disk_level.py +0 -17
  147. amulet/level/abc/_level/_level.py +0 -449
  148. amulet/level/abc/_level/_loadable_level.py +0 -42
  149. amulet/level/abc/_player_storage.py +0 -7
  150. amulet/level/abc/_raw_level.py +0 -187
  151. amulet/level/abc/_registry.py +0 -40
  152. amulet/level/java/__init__.pyi +0 -16
  153. amulet/level/java/_chunk_handle.py +0 -17
  154. amulet/level/java/_dimension.py +0 -20
  155. amulet/level/java/_level.py +0 -184
  156. amulet/level/java/_raw/__init__.pyi +0 -15
  157. amulet/level/java/_raw/_chunk.pyi +0 -23
  158. amulet/level/java/_raw/_constant.py +0 -9
  159. amulet/level/java/_raw/_data_pack/__init__.py +0 -2
  160. amulet/level/java/_raw/_data_pack/data_pack.py +0 -241
  161. amulet/level/java/_raw/_data_pack/data_pack_manager.py +0 -77
  162. amulet/level/java/_raw/_dimension.py +0 -86
  163. amulet/level/java/_raw/_level.py +0 -507
  164. amulet/level/java/_raw/_typing.py +0 -3
  165. amulet/level/java/_raw/java_chunk_decode.cpp +0 -531
  166. amulet/level/java/_raw/java_chunk_decode.hpp +0 -23
  167. amulet/level/java/_raw/java_chunk_encode.cpp +0 -25
  168. amulet/level/java/_raw/java_chunk_encode.hpp +0 -23
  169. amulet/level/java/anvil/__init__.py +0 -2
  170. amulet/level/java/anvil/_dimension.py +0 -170
  171. amulet/level/java/anvil/_region.py +0 -421
  172. amulet/level/java/anvil/_sector_manager.py +0 -223
  173. amulet/level/java/chunk.pyi +0 -81
  174. amulet/level/java/chunk_/_chunk.py +0 -260
  175. amulet/level/java/chunk_/components/inhabited_time.py +0 -12
  176. amulet/level/java/chunk_/components/last_update.py +0 -12
  177. amulet/level/java/chunk_/components/legacy_version.py +0 -12
  178. amulet/level/java/chunk_/components/light_populated.py +0 -12
  179. amulet/level/java/chunk_/components/named_height_2d.py +0 -37
  180. amulet/level/java/chunk_/components/status.py +0 -11
  181. amulet/level/java/chunk_/components/terrain_populated.py +0 -12
  182. amulet/level/java/chunk_components/data_version_component.cpp +0 -32
  183. amulet/level/java/chunk_components/data_version_component.hpp +0 -31
  184. amulet/level/java/chunk_components/java_raw_chunk_component.cpp +0 -56
  185. amulet/level/java/chunk_components/java_raw_chunk_component.hpp +0 -45
  186. amulet/level/java/chunk_components.pyi +0 -22
  187. amulet/level/java/java_chunk.cpp +0 -170
  188. amulet/level/java/java_chunk.hpp +0 -141
  189. amulet/level/java/long_array.hpp +0 -175
  190. amulet/level/java/long_array.pyi +0 -39
  191. amulet/level/temporary_level/__init__.py +0 -1
  192. amulet/level/temporary_level/_level.py +0 -16
  193. amulet/mesh/__init__.py +0 -0
  194. amulet/mesh/block/__init__.py +0 -1
  195. amulet/mesh/block/block_mesh.py +0 -369
  196. amulet/mesh/block/cube.py +0 -149
  197. amulet/mesh/block/missing_block.py +0 -20
  198. amulet/mesh/util.py +0 -17
  199. amulet/palette/biome_palette.hpp +0 -85
  200. amulet/palette/block_palette.cpp +0 -32
  201. amulet/palette/block_palette.hpp +0 -93
  202. amulet/player.py +0 -62
  203. amulet/pybind11/collections.hpp +0 -118
  204. amulet/pybind11/numpy.hpp +0 -26
  205. amulet/pybind11/py_module.hpp +0 -34
  206. amulet/pybind11/type_hints.hpp +0 -51
  207. amulet/pybind11/types.hpp +0 -25
  208. amulet/pybind11/typing.hpp +0 -7
  209. amulet/resource_pack/__init__.py +0 -62
  210. amulet/resource_pack/abc/__init__.py +0 -2
  211. amulet/resource_pack/abc/resource_pack.py +0 -38
  212. amulet/resource_pack/abc/resource_pack_manager.py +0 -87
  213. amulet/resource_pack/bedrock/__init__.py +0 -2
  214. amulet/resource_pack/bedrock/bedrock_vanilla_fix/pack_icon.png +0 -0
  215. amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/grass_carried.png +0 -0
  216. amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/grass_side_carried.png +0 -0
  217. amulet/resource_pack/bedrock/bedrock_vanilla_fix/textures/blocks/water.png +0 -0
  218. amulet/resource_pack/bedrock/blockshapes/__init__.py +0 -31
  219. amulet/resource_pack/bedrock/blockshapes/air.py +0 -35
  220. amulet/resource_pack/bedrock/blockshapes/base_blockshape.py +0 -29
  221. amulet/resource_pack/bedrock/blockshapes/bubble_column.py +0 -29
  222. amulet/resource_pack/bedrock/blockshapes/cake.py +0 -46
  223. amulet/resource_pack/bedrock/blockshapes/chest.py +0 -54
  224. amulet/resource_pack/bedrock/blockshapes/comparator.py +0 -51
  225. amulet/resource_pack/bedrock/blockshapes/cross_texture.py +0 -186
  226. amulet/resource_pack/bedrock/blockshapes/cross_texture0.py +0 -17
  227. amulet/resource_pack/bedrock/blockshapes/cross_texture_green.py +0 -16
  228. amulet/resource_pack/bedrock/blockshapes/cube.py +0 -38
  229. amulet/resource_pack/bedrock/blockshapes/default.py +0 -14
  230. amulet/resource_pack/bedrock/blockshapes/door.py +0 -38
  231. amulet/resource_pack/bedrock/blockshapes/door1.py +0 -14
  232. amulet/resource_pack/bedrock/blockshapes/door2.py +0 -14
  233. amulet/resource_pack/bedrock/blockshapes/door3.py +0 -14
  234. amulet/resource_pack/bedrock/blockshapes/door4.py +0 -14
  235. amulet/resource_pack/bedrock/blockshapes/door5.py +0 -14
  236. amulet/resource_pack/bedrock/blockshapes/door6.py +0 -14
  237. amulet/resource_pack/bedrock/blockshapes/double_plant.py +0 -40
  238. amulet/resource_pack/bedrock/blockshapes/enchanting_table.py +0 -22
  239. amulet/resource_pack/bedrock/blockshapes/farmland.py +0 -22
  240. amulet/resource_pack/bedrock/blockshapes/fence.py +0 -22
  241. amulet/resource_pack/bedrock/blockshapes/flat.py +0 -55
  242. amulet/resource_pack/bedrock/blockshapes/flat_wall.py +0 -55
  243. amulet/resource_pack/bedrock/blockshapes/furnace.py +0 -44
  244. amulet/resource_pack/bedrock/blockshapes/furnace_lit.py +0 -14
  245. amulet/resource_pack/bedrock/blockshapes/green_cube.py +0 -39
  246. amulet/resource_pack/bedrock/blockshapes/ladder.py +0 -36
  247. amulet/resource_pack/bedrock/blockshapes/lilypad.py +0 -14
  248. amulet/resource_pack/bedrock/blockshapes/partial_block.py +0 -57
  249. amulet/resource_pack/bedrock/blockshapes/piston.py +0 -44
  250. amulet/resource_pack/bedrock/blockshapes/piston_arm.py +0 -72
  251. amulet/resource_pack/bedrock/blockshapes/portal_frame.py +0 -22
  252. amulet/resource_pack/bedrock/blockshapes/pressure_plate.py +0 -29
  253. amulet/resource_pack/bedrock/blockshapes/pumpkin.py +0 -36
  254. amulet/resource_pack/bedrock/blockshapes/pumpkin_carved.py +0 -14
  255. amulet/resource_pack/bedrock/blockshapes/pumpkin_lit.py +0 -14
  256. amulet/resource_pack/bedrock/blockshapes/red_dust.py +0 -14
  257. amulet/resource_pack/bedrock/blockshapes/repeater.py +0 -53
  258. amulet/resource_pack/bedrock/blockshapes/slab.py +0 -33
  259. amulet/resource_pack/bedrock/blockshapes/slab_double.py +0 -15
  260. amulet/resource_pack/bedrock/blockshapes/tree.py +0 -41
  261. amulet/resource_pack/bedrock/blockshapes/turtle_egg.py +0 -15
  262. amulet/resource_pack/bedrock/blockshapes/vine.py +0 -52
  263. amulet/resource_pack/bedrock/blockshapes/wall.py +0 -22
  264. amulet/resource_pack/bedrock/blockshapes/water.py +0 -38
  265. amulet/resource_pack/bedrock/download_resources.py +0 -147
  266. amulet/resource_pack/bedrock/resource_pack.py +0 -40
  267. amulet/resource_pack/bedrock/resource_pack_manager.py +0 -361
  268. amulet/resource_pack/bedrock/sort_blockshapes.py +0 -15
  269. amulet/resource_pack/java/__init__.py +0 -2
  270. amulet/resource_pack/java/download_resources.py +0 -212
  271. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_black.png +0 -0
  272. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_blue.png +0 -0
  273. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_brown.png +0 -0
  274. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_cyan.png +0 -0
  275. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_gray.png +0 -0
  276. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_green.png +0 -0
  277. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_blue.png +0 -0
  278. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_gray.png +0 -0
  279. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_lime.png +0 -0
  280. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_magenta.png +0 -0
  281. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_orange.png +0 -0
  282. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_pink.png +0 -0
  283. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_purple.png +0 -0
  284. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_red.png +0 -0
  285. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_white.png +0 -0
  286. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_yellow.png +0 -0
  287. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/barrier.png +0 -0
  288. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/end_portal.png +0 -0
  289. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/grass.png +0 -0
  290. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/lava.png +0 -0
  291. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/structure_void.png +0 -0
  292. amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/water.png +0 -0
  293. amulet/resource_pack/java/java_vanilla_fix/pack.png +0 -0
  294. amulet/resource_pack/java/resource_pack.py +0 -44
  295. amulet/resource_pack/java/resource_pack_manager.py +0 -551
  296. amulet/resource_pack/unknown_resource_pack.py +0 -10
  297. amulet/selection/__init__.py +0 -2
  298. amulet/selection/abstract_selection.py +0 -342
  299. amulet/selection/box.py +0 -852
  300. amulet/selection/group.py +0 -481
  301. amulet/utils/__init__.pyi +0 -23
  302. amulet/utils/call_spec/__init__.py +0 -24
  303. amulet/utils/call_spec/_call_spec.py +0 -257
  304. amulet/utils/comment_json.py +0 -188
  305. amulet/utils/format_utils.py +0 -41
  306. amulet/utils/generator.py +0 -18
  307. amulet/utils/matrix.py +0 -243
  308. amulet/utils/numpy.hpp +0 -36
  309. amulet/utils/numpy.pyi +0 -11
  310. amulet/utils/numpy_helpers.py +0 -19
  311. amulet/utils/shareable_lock.py +0 -335
  312. amulet/utils/signal/__init__.py +0 -10
  313. amulet/utils/signal/_signal.py +0 -228
  314. amulet/utils/task_manager.py +0 -235
  315. amulet/utils/typed_property.py +0 -111
  316. amulet/utils/weakref.py +0 -70
  317. amulet/utils/world_utils.py +0 -102
  318. amulet/version.cpp +0 -136
  319. amulet/version.hpp +0 -142
  320. amulet/version.py.cpp +0 -281
  321. amulet_core-2.0a7.dist-info/RECORD +0 -295
  322. amulet_core-2.0a7.dist-info/entry_points.txt +0 -2
  323. /amulet/{__pyinstaller → core/__pyinstaller}/__init__.py +0 -0
  324. /amulet/{py.typed → core/py.typed} +0 -0
  325. {amulet_core-2.0a7.dist-info → amulet_core-2.0.1.0.1297307203.19.43.34808.0a0.dist-info}/top_level.txt +0 -0
amulet/selection/box.py DELETED
@@ -1,852 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import itertools
4
- import numpy
5
- import math
6
-
7
- from typing import Iterable, Iterator, TYPE_CHECKING, Any, TypeAlias, overload
8
-
9
- from amulet.data_types import (
10
- BlockCoordinates,
11
- BlockCoordinatesArray,
12
- PointCoordinates,
13
- PointCoordinatesArray,
14
- ChunkCoordinates,
15
- SubChunkCoordinates,
16
- FloatTriplet,
17
- )
18
- from amulet.utils.world_utils import (
19
- block_coords_to_chunk_coords,
20
- blocks_slice_to_chunk_slice,
21
- )
22
- from amulet.utils.matrix import (
23
- transform_matrix,
24
- displacement_matrix,
25
- )
26
- from .abstract_selection import AbstractBaseSelection
27
- from amulet import selection
28
-
29
- if TYPE_CHECKING:
30
- from .group import SelectionGroup
31
-
32
- PySlice: TypeAlias = slice
33
-
34
-
35
- class SelectionBox(AbstractBaseSelection):
36
- """
37
- The SelectionBox class represents a single cuboid selection.
38
-
39
- When combined with :class:`~amulet.api.selection.SelectionGroup` it can represent any arbitrary shape.
40
- """
41
-
42
- __slots__ = (
43
- "_min_x",
44
- "_min_y",
45
- "_min_z",
46
- "_max_x",
47
- "_max_y",
48
- "_max_z",
49
- "_point_1",
50
- "_point_2",
51
- )
52
-
53
- _min_x: int
54
- _min_y: int
55
- _min_z: int
56
- _max_x: int
57
- _max_y: int
58
- _max_z: int
59
- _point_1: tuple[int, int, int]
60
- _point_2: tuple[int, int, int]
61
-
62
- def __init__(
63
- self,
64
- point_1: BlockCoordinates | BlockCoordinatesArray,
65
- point_2: BlockCoordinates | BlockCoordinatesArray,
66
- ):
67
- """
68
- Construct a new SelectionBox instance.
69
-
70
- >>> # a selection box that selects one block.
71
- >>> box = SelectionBox(
72
- >>> (0, 0, 0),
73
- >>> (1, 1, 1)
74
- >>> )
75
-
76
- :param point_1: The first point of the selection.
77
- :param point_2: The second point of the selection.
78
- """
79
- box = numpy.array([point_1, point_2]).round().astype(int)
80
- p1, p2 = box.tolist()
81
- self._point_1 = tuple(p1)
82
- self._point_2 = tuple(p2)
83
- self._min_x, self._min_y, self._min_z = numpy.min(box, 0).tolist()
84
- self._max_x, self._max_y, self._max_z = numpy.max(box, 0).tolist()
85
-
86
- @classmethod
87
- def create_chunk_box(
88
- cls, cx: int, cz: int, sub_chunk_size: int = 16
89
- ) -> SelectionBox:
90
- """
91
- Get a :class:`SelectionBox` containing the whole of a given chunk.
92
-
93
- >>> box = SelectionBox.create_chunk_box(1, 2)
94
- SelectionBox((16, -1073741824, 32), (32, 1073741824, 48))
95
-
96
- :param cx: The x coordinate of the chunk
97
- :param cz: The z coordinate of the chunk
98
- :param sub_chunk_size: The dimension of a sub-chunk. Default 16.
99
- """
100
- return cls(
101
- (cx * sub_chunk_size, -(2**30), cz * sub_chunk_size),
102
- ((cx + 1) * sub_chunk_size, 2**30, (cz + 1) * sub_chunk_size),
103
- )
104
-
105
- @classmethod
106
- def create_sub_chunk_box(
107
- cls, cx: int, cy: int, cz: int, sub_chunk_size: int = 16
108
- ) -> SelectionBox:
109
- """
110
- Get a :class:`SelectionBox` containing the whole of a given sub-chunk.
111
-
112
- >>> SelectionBox.create_sub_chunk_box(1, 0, 2)
113
- SelectionBox((16, 0, 32), (32, 16, 48))
114
-
115
- :param cx: The x coordinate of the chunk
116
- :param cy: The y coordinate of the chunk
117
- :param cz: The z coordinate of the chunk
118
- :param sub_chunk_size: The dimension of a sub-chunk. Default 16.
119
- """
120
- return cls(
121
- (cx * sub_chunk_size, cy * sub_chunk_size, cz * sub_chunk_size),
122
- (
123
- (cx + 1) * sub_chunk_size,
124
- (cy + 1) * sub_chunk_size,
125
- (cz + 1) * sub_chunk_size,
126
- ),
127
- )
128
-
129
- def create_moved_box(
130
- self, offset: BlockCoordinates | BlockCoordinatesArray, subtract: bool = False
131
- ) -> SelectionBox:
132
- """
133
- Create a new :class:`SelectionBox` based on this one with the coordinates moved by the given offset.
134
-
135
- :param offset: The amount to move the box.
136
- :param subtract: If true will subtract the offset rather than adding.
137
- :return: The new selection with the given offset.
138
- """
139
- offset = numpy.array(offset)
140
- if subtract:
141
- offset *= -1
142
- return SelectionBox(offset + self.min, offset + self.max)
143
-
144
- def chunk_locations(self, sub_chunk_size: int = 16) -> Iterator[ChunkCoordinates]:
145
- cx_min, cz_min, cx_max, cz_max = block_coords_to_chunk_coords(
146
- self.min_x,
147
- self.min_z,
148
- self.max_x - 1,
149
- self.max_z - 1,
150
- sub_chunk_size=sub_chunk_size,
151
- )
152
- yield from itertools.product(
153
- range(cx_min, cx_max + 1), range(cz_min, cz_max + 1)
154
- )
155
-
156
- def chunk_boxes(
157
- self, sub_chunk_size: int = 16
158
- ) -> Iterator[tuple[ChunkCoordinates, SelectionBox]]:
159
- for cx, cz in self.chunk_locations(sub_chunk_size):
160
- yield (cx, cz), self.intersection(
161
- SelectionBox.create_chunk_box(cx, cz, sub_chunk_size)
162
- )
163
-
164
- def chunk_y_locations(self, sub_chunk_size: int = 16) -> Iterable[int]:
165
- """
166
- An iterable of all the sub-chunk y indexes this box intersects.
167
-
168
- :param sub_chunk_size: The dimension of a sub-chunk. Default 16.
169
- """
170
- cy_min, cy_max = block_coords_to_chunk_coords(
171
- self.min_y, self._max_y - 1, sub_chunk_size=sub_chunk_size
172
- )
173
- for cy in range(cy_min, cy_max + 1):
174
- yield cy
175
-
176
- def sub_chunk_locations(
177
- self, sub_chunk_size: int = 16
178
- ) -> Iterator[SubChunkCoordinates]:
179
- for cx, cz in self.chunk_locations(sub_chunk_size):
180
- for cy in self.chunk_y_locations(sub_chunk_size):
181
- yield cx, cy, cz
182
-
183
- def chunk_count(self, sub_chunk_size: int = 16) -> int:
184
- cx_min, cz_min, cx_max, cz_max = block_coords_to_chunk_coords(
185
- self.min_x,
186
- self.min_z,
187
- self.max_x - 1,
188
- self.max_z - 1,
189
- sub_chunk_size=sub_chunk_size,
190
- )
191
- return (cx_max + 1 - cx_min) * (cz_max + 1 - cz_min)
192
-
193
- def sub_chunk_count(self, sub_chunk_size: int = 16) -> int:
194
- cy_min, cy_max = block_coords_to_chunk_coords(
195
- self.min_y,
196
- self.max_y - 1,
197
- sub_chunk_size=sub_chunk_size,
198
- )
199
- return (cy_max + 1 - cy_min) * self.chunk_count()
200
-
201
- def sub_chunk_boxes(
202
- self, sub_chunk_size: int = 16
203
- ) -> Iterator[tuple[SubChunkCoordinates, SelectionBox]]:
204
- for cx, cy, cz in self.sub_chunk_locations(sub_chunk_size):
205
- yield (cx, cy, cz), self.intersection(
206
- SelectionBox.create_sub_chunk_box(cx, cy, cz, sub_chunk_size)
207
- )
208
-
209
- @property
210
- def blocks(self) -> Iterator[BlockCoordinates]:
211
- return itertools.product(
212
- range(self._min_x, self._max_x),
213
- range(self._min_y, self._max_y),
214
- range(self._min_z, self._max_z),
215
- )
216
-
217
- def __repr__(self) -> str:
218
- return f"SelectionBox({self.point_1}, {self.point_2})"
219
-
220
- def __str__(self) -> str:
221
- return f"({self.point_1}, {self.point_2})"
222
-
223
- def contains_block(self, x: int, y: int, z: int) -> bool:
224
- return (
225
- self._min_x <= x < self._max_x
226
- and self._min_y <= y < self._max_y
227
- and self._min_z <= z < self._max_z
228
- )
229
-
230
- def contains_point(self, x: float, y: float, z: float) -> bool:
231
- return (
232
- self._min_x <= x <= self._max_x
233
- and self._min_y <= y <= self._max_y
234
- and self._min_z <= z <= self._max_z
235
- )
236
-
237
- def __eq__(self, other: Any) -> bool:
238
- if not isinstance(other, AbstractBaseSelection):
239
- return NotImplemented
240
- return self.min == other.min and self.max == other.max
241
-
242
- def __ne__(self, other: Any) -> bool:
243
- return not self == other
244
-
245
- def __hash__(self) -> int:
246
- return hash((*self.min, *self.max))
247
-
248
- @property
249
- def slice(self) -> tuple[PySlice, PySlice, PySlice]:
250
- """
251
- Converts the :class:`SelectionBox` minimum/maximum coordinates into slice arguments
252
-
253
- :return: The :class:`SelectionBox` coordinates as slices in (x,y,z) order
254
- """
255
- return (
256
- slice(self._min_x, self._max_x),
257
- slice(self._min_y, self._max_y),
258
- slice(self._min_z, self._max_z),
259
- )
260
-
261
- def chunk_slice(
262
- self, cx: int, cz: int, sub_chunk_size: int = 16
263
- ) -> tuple[PySlice, PySlice, PySlice]:
264
- """
265
- Get the slice of the box in relative form for a given chunk.
266
-
267
- >>> SelectionBox((0, 0, 0), (32, 32, 32)).chunk_slice(1, 1)
268
- (slice(0, 16, None), slice(0, 32, None), slice(0, 16, None))
269
-
270
- :param cx: The x coordinate of the chunk
271
- :param cz: The z coordinate of the chunk
272
- :param sub_chunk_size: The dimension of a sub-chunk. Default 16.
273
- """
274
- s_x, s_y, s_z = self.slice
275
- x_chunk_slice = blocks_slice_to_chunk_slice(s_x, sub_chunk_size, cx)
276
- z_chunk_slice = blocks_slice_to_chunk_slice(s_z, sub_chunk_size, cz)
277
- return x_chunk_slice, s_y, z_chunk_slice
278
-
279
- def sub_chunk_slice(
280
- self, cx: int, cy: int, cz: int, sub_chunk_size: int = 16
281
- ) -> tuple[PySlice, PySlice, PySlice]:
282
- """
283
- Get the slice of the box in relative form for a given sub-chunk.
284
-
285
- >>> SelectionBox((0, 0, 0), (32, 32, 32)).sub_chunk_slice(1, 1, 1)
286
- (slice(0, 16, None), slice(0, 16, None), slice(0, 16, None))
287
-
288
- :param cx: The x coordinate of the chunk
289
- :param cy: The y coordinate of the chunk
290
- :param cz: The z coordinate of the chunk
291
- :param sub_chunk_size: The dimension of a sub-chunk. Default 16.
292
- """
293
- x_chunk_slice, s_y, z_chunk_slice = self.chunk_slice(cx, cz, sub_chunk_size)
294
- y_chunk_slice = blocks_slice_to_chunk_slice(s_y, sub_chunk_size, cy)
295
- return x_chunk_slice, y_chunk_slice, z_chunk_slice
296
-
297
- @property
298
- def point_1(self) -> BlockCoordinates:
299
- """The first value given to the constructor."""
300
- return self._point_1
301
-
302
- @property
303
- def point_2(self) -> BlockCoordinates:
304
- """The second value given to the constructor."""
305
- return self._point_2
306
-
307
- @property
308
- def points(self) -> tuple[BlockCoordinates, BlockCoordinates]:
309
- """The points given to the constructor."""
310
- return self.point_1, self.point_2
311
-
312
- @property
313
- def points_array(self) -> numpy.ndarray:
314
- """The points given to the constructor as a numpy array."""
315
- return numpy.array(self.points)
316
-
317
- @property
318
- def min_x(self) -> int:
319
- return self._min_x
320
-
321
- @property
322
- def min_y(self) -> int:
323
- return self._min_y
324
-
325
- @property
326
- def min_z(self) -> int:
327
- return self._min_z
328
-
329
- @property
330
- def max_x(self) -> int:
331
- return self._max_x
332
-
333
- @property
334
- def max_y(self) -> int:
335
- return self._max_y
336
-
337
- @property
338
- def max_z(self) -> int:
339
- return self._max_z
340
-
341
- @property
342
- def min(self) -> BlockCoordinates:
343
- return self._min_x, self._min_y, self._min_z
344
-
345
- @property
346
- def min_array(self) -> numpy.ndarray:
347
- return numpy.array(self.min)
348
-
349
- @property
350
- def max(self) -> BlockCoordinates:
351
- return self._max_x, self._max_y, self._max_z
352
-
353
- @property
354
- def max_array(self) -> numpy.ndarray:
355
- return numpy.array(self.max)
356
-
357
- @property
358
- def bounds(self) -> tuple[BlockCoordinates, BlockCoordinates]:
359
- return (
360
- (self._min_x, self._min_y, self._min_z),
361
- (self._max_x, self._max_y, self._max_z),
362
- )
363
-
364
- @property
365
- def bounds_array(self) -> numpy.ndarray:
366
- return numpy.array(self.bounds)
367
-
368
- def bounding_box(self) -> SelectionBox:
369
- return self
370
-
371
- def selection_group(self) -> SelectionGroup:
372
- return selection.SelectionGroup(self)
373
-
374
- @property
375
- def size_x(self) -> int:
376
- """The length of the box in the x axis."""
377
- return self._max_x - self._min_x
378
-
379
- @property
380
- def size_y(self) -> int:
381
- """The length of the box in the y axis."""
382
- return self._max_y - self._min_y
383
-
384
- @property
385
- def size_z(self) -> int:
386
- """The length of the box in the z axis."""
387
- return self._max_z - self._min_z
388
-
389
- @property
390
- def shape(self) -> tuple[int, int, int]:
391
- """
392
- The shape of the box.
393
-
394
- >>> SelectionBox((0, 0, 0), (1, 1, 1)).shape
395
- (1, 1, 1)
396
- """
397
- return self.size_x, self.size_y, self.size_z
398
-
399
- @property
400
- def volume(self) -> int:
401
- """
402
- The number of blocks in the box.
403
-
404
- >>> SelectionBox((0, 0, 0), (1, 1, 1)).shape
405
- 1
406
- """
407
- return self.size_x * self.size_y * self.size_z
408
-
409
- def touches(self, other: SelectionBox) -> bool:
410
- """
411
- Method to check if this instance of :class:`SelectionBox` touches but does not intersect another SelectionBox.
412
-
413
- :param other: The other SelectionBox
414
- :return: True if the two :class:`SelectionBox` instances touch, False otherwise
415
- """
416
- # It touches if the box does not intersect but intersects when expanded by one block.
417
- # There may be a simpler way to do this.
418
- return self.touches_or_intersects(other) and not self.intersects(other)
419
-
420
- def touches_or_intersects(self, other: SelectionBox) -> bool:
421
- """
422
- Method to check if this instance of SelectionBox touches or intersects another SelectionBox.
423
-
424
- :param other: The other SelectionBox.
425
- :return: True if the two :class:`SelectionBox` instances touch or intersect, False otherwise.
426
- """
427
- return not (
428
- self.min_x >= other.max_x + 1
429
- or self.min_y >= other.max_y + 1
430
- or self.min_z >= other.max_z + 1
431
- or self.max_x <= other.min_x - 1
432
- or self.max_y <= other.min_y - 1
433
- or self.max_z <= other.min_z - 1
434
- )
435
-
436
- def _intersects(self, other: AbstractBaseSelection) -> bool:
437
- """
438
- Method to check whether this instance of SelectionBox intersects another SelectionBox.
439
-
440
- :param other: The other SelectionBox to check for intersection.
441
- :return: True if the two :class:`SelectionBox` instances intersect, False otherwise.
442
- """
443
- if isinstance(other, SelectionBox):
444
- return not (
445
- self.min_x >= other.max_x
446
- or self.min_y >= other.max_y
447
- or self.min_z >= other.max_z
448
- or self.max_x <= other.min_x
449
- or self.max_y <= other.min_y
450
- or self.max_z <= other.min_z
451
- )
452
- return NotImplemented
453
-
454
- def contains_box(self, other: SelectionBox) -> bool:
455
- """
456
- Method to check if the other SelectionBox other fits entirely within this instance of SelectionBox.
457
-
458
- :param other: The SelectionBox to test.
459
- :return: True if other fits with self, False otherwise.
460
- """
461
- return (
462
- self.min_x <= other.min_x
463
- and self.min_y <= other.min_y
464
- and self.min_z <= other.min_z
465
- and other.max_x <= self.max_x
466
- and other.max_y <= self.max_y
467
- and other.max_z <= self.max_z
468
- )
469
-
470
- @overload
471
- def intersection(self, other: SelectionBox) -> SelectionBox: ...
472
-
473
- @overload
474
- def intersection(self, other: SelectionGroup) -> SelectionGroup: ...
475
-
476
- @overload
477
- def intersection(self, other: AbstractBaseSelection) -> AbstractBaseSelection: ...
478
-
479
- def intersection(self, other: AbstractBaseSelection) -> AbstractBaseSelection:
480
- return super().intersection(other)
481
-
482
- def _intersection(self, other: AbstractBaseSelection) -> SelectionBox:
483
- if isinstance(other, SelectionBox):
484
- return SelectionBox(
485
- numpy.clip(other.min, self.min, self.max),
486
- numpy.clip(other.max, self.min, self.max),
487
- )
488
- return NotImplemented
489
-
490
- def subtract(self, other: AbstractBaseSelection) -> SelectionGroup:
491
- """
492
- Get a :class:`~amulet.api.selection.SelectionGroup` containing boxes that are in self but not in other.
493
-
494
- This may be empty if other fully contains self or equal to self if they do not intersect.
495
-
496
- :param other: The SelectionBox to subtract.
497
- :return:
498
- """
499
- if isinstance(other, SelectionBox):
500
- intersection = self._intersection(other)
501
- if intersection.volume == 0:
502
- # if the boxes do not intersect then the difference is self
503
- return selection.SelectionGroup(self)
504
- elif self == intersection:
505
- # if the two selections are the same there is no difference.
506
- return selection.SelectionGroup()
507
- else:
508
- boxes = []
509
- if self.min_y < intersection.min_y:
510
- # bottom box
511
- boxes.append(
512
- SelectionBox(
513
- (self.min_x, self.min_y, self.min_z),
514
- (self.max_x, intersection.min_y, self.max_z),
515
- )
516
- )
517
-
518
- if intersection.max_y < self.max_y:
519
- # top box
520
- boxes.append(
521
- SelectionBox(
522
- (self.min_x, intersection.max_y, self.min_z),
523
- (self.max_x, self.max_y, self.max_z),
524
- )
525
- )
526
-
527
- # BBB NNN TTT
528
- # BBB WOE TTT
529
- # BBB SSS TTT
530
-
531
- if self.min_z < intersection.min_z:
532
- # north box
533
- boxes.append(
534
- SelectionBox(
535
- (self.min_x, intersection.min_y, self.min_z),
536
- (self.max_x, intersection.max_y, intersection.min_z),
537
- )
538
- )
539
-
540
- if intersection.max_z < self.max_z:
541
- # south box
542
- boxes.append(
543
- SelectionBox(
544
- (self.min_x, intersection.min_y, intersection.max_z),
545
- (self.max_x, intersection.max_y, self.max_z),
546
- )
547
- )
548
-
549
- if self.min_x < intersection.min_x:
550
- # west box
551
- boxes.append(
552
- SelectionBox(
553
- (self.min_x, intersection.min_y, intersection.min_z),
554
- (
555
- intersection.min_x,
556
- intersection.max_y,
557
- intersection.max_z,
558
- ),
559
- )
560
- )
561
-
562
- if intersection.max_x < self.max_x:
563
- # east box
564
- boxes.append(
565
- SelectionBox(
566
- (
567
- intersection.max_x,
568
- intersection.min_y,
569
- intersection.min_z,
570
- ),
571
- (self.max_x, intersection.max_y, intersection.max_z),
572
- )
573
- )
574
-
575
- return selection.SelectionGroup(boxes)
576
- else:
577
- return self.selection_group().subtract(other)
578
-
579
- def intersects_vector(
580
- self,
581
- origin: PointCoordinates | PointCoordinatesArray,
582
- direction: PointCoordinates | PointCoordinatesArray,
583
- ) -> float | None:
584
- """
585
- Determine if a vector from a given point collides with this selection box.
586
-
587
- :param origin: Location of the origin of the vector
588
- :param direction: The look vector
589
- :return: Multiplier of the vector to the collision location. None if it does not collide
590
- """
591
- # Logic based on https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection
592
- origin_arr = numpy.asarray(origin, dtype=numpy.float64)
593
- direction_arr = numpy.asarray(direction, dtype=numpy.float64)
594
- direction_arr[abs(direction_arr) < 0.000001] = 0.000001
595
- t_min: float
596
- ty_min: float
597
- tz_min: float
598
- t_max: float
599
- ty_max: float
600
- tz_max: float
601
- (t_min, ty_min, tz_min), (t_max, ty_max, tz_max) = numpy.sort(
602
- (self.bounds_array - origin_arr) / direction_arr, axis=0
603
- )
604
-
605
- if t_min > ty_max or ty_min > t_max:
606
- return None
607
-
608
- if ty_min > t_min:
609
- t_min = ty_min
610
-
611
- if ty_max < t_max:
612
- t_max = ty_max
613
-
614
- if t_min > tz_max or tz_min > t_max:
615
- return None
616
-
617
- if tz_min > t_min:
618
- t_min = tz_min
619
-
620
- if tz_max < t_max:
621
- t_max = tz_max
622
-
623
- if t_min >= 0:
624
- return t_min
625
- elif t_max >= 0:
626
- return t_max
627
- else:
628
- return None
629
-
630
- @staticmethod
631
- def _transform_points(
632
- points: numpy.ndarray, matrix: numpy.ndarray
633
- ) -> numpy.ndarray:
634
- assert (
635
- isinstance(points, numpy.ndarray)
636
- and len(points.shape) == 2
637
- and points.shape[1] == 3
638
- )
639
- assert isinstance(matrix, numpy.ndarray) and matrix.shape == (4, 4)
640
- points_array = numpy.ones((points.shape[0], 4))
641
- points_array[:, :3] = points
642
- return numpy.matmul( # type: ignore
643
- matrix,
644
- points_array.T,
645
- ).T[:, :3]
646
-
647
- def _iter_transformed_boxes(self, transform: numpy.ndarray) -> Iterator[
648
- tuple[
649
- float, # progress
650
- SelectionBox, # The sub-chunk box.
651
- bool, # If True all blocks are contained, if False no blocks are contained.
652
- None,
653
- ]
654
- | tuple[
655
- float, # progress
656
- SelectionBox, # The sub-chunk box.
657
- numpy.ndarray, # The bool array of which of the transformed blocks are contained.
658
- numpy.ndarray, # A float array of where those blocks came from.
659
- ]
660
- ]:
661
- """The core logic for transform and transformed_points"""
662
- assert isinstance(transform, numpy.ndarray) and transform.shape == (4, 4)
663
- inverse_transform = numpy.linalg.inv(transform)
664
- inverse_transform2 = numpy.linalg.inv(
665
- numpy.matmul(displacement_matrix(-0.5, -0.5, -0.5), transform)
666
- )
667
-
668
- def transform_box(
669
- box_: SelectionBox, transform_: numpy.ndarray
670
- ) -> SelectionBox:
671
- """transform a box and get the AABB that contains this rotated box."""
672
-
673
- # find the transformed points of each of the corners
674
- points = numpy.matmul(
675
- transform_,
676
- numpy.array(
677
- list(
678
- itertools.product(
679
- [box_.min_x, box_.max_x],
680
- [box_.min_y, box_.max_y],
681
- [box_.min_z, box_.max_z],
682
- [1],
683
- )
684
- )
685
- ).T,
686
- ).T[:, :3]
687
- # this is a larger AABB that contains the rotated box and a bit more.
688
- return SelectionBox(numpy.min(points, axis=0), numpy.max(points, axis=0))
689
-
690
- aabb = transform_box(self, transform)
691
- count = aabb.sub_chunk_count()
692
- index = 0
693
-
694
- for _, box in aabb.sub_chunk_boxes():
695
- index += 1
696
- original_box = transform_box(box, inverse_transform)
697
- if self.intersects(original_box):
698
- # if the boxes do not intersect then nothing needs doing.
699
- if self.contains_box(original_box):
700
- # if the box is fully contained use the whole box.
701
- yield index / count, box, True, None
702
- else:
703
- # the original points the transformed locations relate to
704
- original_blocks = self._transform_points(
705
- numpy.transpose(
706
- numpy.mgrid[
707
- box.min_x : box.max_x,
708
- box.min_y : box.max_y,
709
- box.min_z : box.max_z,
710
- ],
711
- (1, 2, 3, 0),
712
- ).reshape(-1, 3),
713
- inverse_transform2,
714
- )
715
-
716
- box_shape = box.shape
717
- mask: numpy.ndarray = numpy.all(
718
- numpy.logical_and(
719
- original_blocks < self.max, original_blocks >= self.min
720
- ),
721
- axis=1,
722
- ).reshape(box_shape)
723
-
724
- yield index / count, box, mask, original_blocks.reshape(
725
- box_shape + (3,)
726
- )
727
- else:
728
- yield index / count, box, False, None
729
-
730
- def transformed_points(
731
- self, transform: numpy.ndarray
732
- ) -> Iterable[tuple[float, numpy.ndarray | None, numpy.ndarray | None]]:
733
- """
734
- Get the locations of the transformed blocks and the source blocks they came from.
735
-
736
- :param transform: The matrix that this box will be transformed by.
737
- :return: An iterable of two Nx3 numpy arrays of the source block locations and the destination block locations. The destination locations will be unique but the source may not be and some may not be included.
738
- """
739
- for progress, box, mask, original in self._iter_transformed_boxes(transform):
740
- if isinstance(mask, bool) and mask:
741
- new_points = numpy.transpose(
742
- numpy.mgrid[
743
- box.min_x : box.max_x,
744
- box.min_y : box.max_y,
745
- box.min_z : box.max_z,
746
- ],
747
- (1, 2, 3, 0),
748
- ).reshape(-1, 3)
749
- old_points = self._transform_points(
750
- new_points,
751
- numpy.linalg.inv(
752
- numpy.matmul(displacement_matrix(-0.5, -0.5, -0.5), transform)
753
- ),
754
- )
755
- yield progress, old_points, new_points
756
- elif isinstance(mask, numpy.ndarray) and numpy.any(mask):
757
- assert isinstance(original, numpy.ndarray)
758
- yield progress, original[mask], box.min_array + numpy.argwhere(mask)
759
- else:
760
- yield progress, None, None
761
-
762
- def transform(
763
- self, scale: FloatTriplet, rotation: FloatTriplet, translation: FloatTriplet
764
- ) -> SelectionGroup:
765
- """
766
- Creates a :class:`~amulet.api.selection.SelectionGroup` of transformed SelectionBox(es).
767
-
768
- :param scale: A tuple of scaling factors in the x, y and z axis.
769
- :param rotation: The rotation about the x, y and z axis in radians.
770
- :param translation: The translation about the x, y and z axis.
771
- :return: A new :class:`~amulet.api.selection.SelectionGroup` representing the transformed selection.
772
- """
773
- quadrant = math.pi / 2
774
- if all(abs(r - quadrant * round(r / quadrant)) < 0.0001 for r in rotation):
775
- min_point, max_point = numpy.matmul(
776
- transform_matrix(scale, rotation, translation),
777
- numpy.array([[*self.min, 1], [*self.max, 1]]).T,
778
- ).T[:, :3]
779
- return selection.SelectionGroup(SelectionBox(min_point, max_point))
780
- else:
781
- boxes = []
782
- for _, box, mask, _ in self._iter_transformed_boxes(
783
- transform_matrix(scale, rotation, translation)
784
- ):
785
- if isinstance(mask, bool):
786
- if mask:
787
- boxes.append(box)
788
- else:
789
- box_shape = box.shape
790
- any_array: numpy.ndarray = numpy.any(mask, axis=2)
791
- box_2d_shape = numpy.array(any_array.shape)
792
- any_array_flat = any_array.ravel()
793
- start_array = numpy.argmax(mask, axis=2)
794
- stop_array = box_shape[2] - numpy.argmax(
795
- numpy.flip(mask, axis=2), axis=2
796
- )
797
- # effectively a greedy meshing algorithm in 2D
798
- index: int | numpy.integer = 0
799
- while index < any_array_flat.size:
800
- # while there are unhandled true values
801
- index = numpy.argmax(any_array_flat[index:]) + index
802
- # find the first true value
803
- if any_array_flat[index]:
804
- # check that that value is actually True
805
- # create the bounds for the box
806
- min_x, min_y = max_x, max_y = numpy.unravel_index(
807
- index, box_2d_shape
808
- )
809
- # find the z bounds
810
- min_z = start_array[min_x, min_y]
811
- max_z = stop_array[min_x, min_y]
812
- while max_x < box_2d_shape[0] - 1:
813
- # expand in the x while the bounds are the same
814
- new_max_x = max_x + 1
815
- if (
816
- any_array[new_max_x, max_y]
817
- and start_array[new_max_x, max_y] == min_z
818
- and stop_array[new_max_x, max_y] == max_z
819
- ):
820
- # the box z values are the same
821
- max_x = new_max_x
822
- else:
823
- break
824
- while max_y < box_2d_shape[1] - 1:
825
- # expand in the y while the bounds are the same
826
- new_max_y = max_y + 1
827
- if (
828
- numpy.all(any_array[min_x : max_x + 1, new_max_y])
829
- and numpy.all(
830
- start_array[min_x : max_x + 1, new_max_y]
831
- == min_z
832
- )
833
- and numpy.all(
834
- stop_array[min_x : max_x + 1, new_max_y]
835
- == max_z
836
- )
837
- ):
838
- # the box z values are the same
839
- max_y = new_max_y
840
- else:
841
- break
842
- boxes.append(
843
- SelectionBox(
844
- box.min_array + (min_x, min_y, min_z),
845
- box.min_array + (max_x + 1, max_y + 1, max_z),
846
- )
847
- )
848
- any_array[min_x : max_x + 1, min_y : max_y + 1] = False
849
- else:
850
- # If there are no more True values argmax will return 0
851
- break
852
- return selection.SelectionGroup(boxes)