class Waterlog(wx.Panel, DefaultOperationUI):
    def __init__(
        self,
        parent: wx.Window,
        canvas: "EditCanvas",
        world: "BaseLevel",
        options_path: str,
    ):
        wx.Panel.__init__(self, parent)
        DefaultOperationUI.__init__(self, parent, canvas, world, options_path)
        self.Freeze()

        self._sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self._sizer)

        options = self._load_options({})

        top_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._sizer.Add(top_sizer, 0, wx.EXPAND | wx.ALL, 5)

        help_button = wx.BitmapButton(
            self, bitmap=image.icon.tablericons.help.bitmap(22, 22))
        top_sizer.Add(help_button)

        def on_button(evt):
            dialog = SimpleDialog(self, "Extra block help.")
            text = wx.TextCtrl(
                dialog,
                value=
                "Blocks in the newer versions of Minecraft support having two blocks in the same location.\n"
                "This is how the game is able to have water and blocks like fences at the same location.\n"
                "In the example of waterlogged fences the fence is the first block and the water is the second. Unless it is water the second block is usually just visual.\n"
                "In Java currently the second block is strictly water but in Bedrock there is no limit on what the second block can be.\n"
                "It is not possible to set non-water second blocks in the game but this operation enables the use of that feature.\n"
                "There are a number of different modes which can be selected at the top. A description of how it works will appear.",
                style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_BESTWRAP,
            )
            dialog.sizer.Add(text, 1, wx.EXPAND)
            dialog.ShowModal()
            evt.Skip()

        help_button.Bind(wx.EVT_BUTTON, on_button)

        self._mode = wx.Choice(self, choices=list(MODES.keys()))
        self._mode.SetSelection(0)
        top_sizer.Add(self._mode, 1, wx.EXPAND | wx.LEFT, 5)
        self._mode.Bind(wx.EVT_CHOICE, self._on_mode_change)

        self._mode_description = wx.TextCtrl(self,
                                             style=wx.TE_MULTILINE
                                             | wx.TE_READONLY | wx.TE_BESTWRAP)
        self._sizer.Add(self._mode_description, 0,
                        wx.EXPAND | wx.LEFT | wx.RIGHT, 5)

        self._mode_description.SetLabel(MODES[self._mode.GetString(
            self._mode.GetSelection())])
        self._mode_description.Fit()

        self._block_define = BlockDefine(
            self,
            world.level_wrapper.translation_manager,
            wx.VERTICAL,
            *(options.get("fill_block_options", [])
              or [world.level_wrapper.platform]),
            show_pick_block=True)
        self._block_define.Bind(EVT_PICK, self._on_pick_block_button)
        self._sizer.Add(self._block_define, 1,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self._run_button = wx.Button(self, label="Run Operation")
        self._run_button.Bind(wx.EVT_BUTTON, self._run_operation)
        self._sizer.Add(self._run_button, 0,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self.Layout()
        self.Thaw()

    @property
    def wx_add_options(self) -> Tuple[int, ...]:
        return (1, )

    def _on_mode_change(self, evt):
        self._mode_description.SetLabel(MODES[self._mode.GetString(
            self._mode.GetSelection())])
        self._mode_description.Fit()
        self.Layout()
        evt.Skip()

    def _on_pick_block_button(self, evt):
        """Set up listening for the block click"""
        self._show_pointer = True

    def _on_box_click(self):
        if self._show_pointer:
            self._show_pointer = False
            x, y, z = self._pointer.pointer_base
            self._block_define.universal_block = (
                self.world.get_block(x, y, z, self.canvas.dimension),
                None,
            )

    def _get_fill_block(self):
        return self._block_define.universal_block[0]

    def disable(self):
        self._save_options({
            "fill_block":
            self._get_fill_block(),
            "fill_block_options": (
                self._block_define.platform,
                self._block_define.version_number,
                self._block_define.force_blockstate,
                self._block_define.namespace,
                self._block_define.block_name,
                self._block_define.properties,
            ),
        })

    def _run_operation(self, _):
        self.canvas.run_operation(lambda: self._waterlog())

    def _waterlog(self):
        mode = self._mode.GetString(self._mode.GetSelection())
        waterlog_block = self._get_fill_block().base_block
        world = self.world
        selection = self.canvas.selection.selection_group
        dimension = self.canvas.dimension
        iter_count = len(list(world.get_coord_box(dimension, selection, True)))
        count = 0
        for chunk, slices, _ in world.get_chunk_slice_box(
                dimension, selection, True):
            original_blocks = chunk.blocks[slices]
            palette, blocks = numpy.unique(original_blocks,
                                           return_inverse=True)
            blocks = blocks.reshape(original_blocks.shape)
            if mode == "Overlay":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        waterlog_block
                        if world.block_palette[block_id].namespaced_name
                        == "universal_minecraft:air" else
                        world.block_palette[block_id].base_block +
                        waterlog_block  # get the Block object for that id and add the user specified block
                    )  # register the new block / get the numerical id if it was already registered
                    for block_id in palette
                ]  # add the new id to the palette
                                  )
            elif mode == "Underlay":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        waterlog_block
                        if world.block_palette[block_id].namespaced_name
                        == "universal_minecraft:air" else waterlog_block +
                        world.
                        block_palette[  # get the Block object for that id and add the user specified block
                            block_id].base_block
                    )  # register the new block / get the numerical id if it was already registered
                    for block_id in palette
                ]  # add the new id to the palette
                                  )
            elif mode == "Normal Fill":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        waterlog_block
                    )  # register the new block / get the numerical id if it was already registered
                ] * len(palette)  # add the new id to the palette
                                  )
            elif mode == "Game Fill":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        waterlog_block +
                        world.block_palette[block_id].base_block
                        if world.block_palette[block_id].namespaced_name
                        == "universal_minecraft:water" else waterlog_block
                    )  # register the new block / get the numerical id if it was already registered
                    for block_id in palette
                ]  # add the new id to the palette
                                  )
            elif mode == "Set First":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        waterlog_block +
                        world.block_palette[block_id].extra_blocks[0]
                        if world.block_palette[block_id].extra_blocks else
                        waterlog_block
                    )  # register the new block / get the numerical id if it was already registered
                    for block_id in palette
                ]  # add the new id to the palette
                                  )
            elif mode == "Set Second":
                lut = numpy.array([
                    world.block_palette.get_add_block(
                        world.block_palette[block_id].base_block +
                        waterlog_block
                    )  # register the new block / get the numerical id if it was already registered
                    for block_id in palette
                ]  # add the new id to the palette
                                  )
            else:
                raise Exception("hello")

            chunk.blocks[slices] = lut[blocks]

            chunk.changed = True
            count += 1
            yield count / iter_count
Example #2
0
class FloodFill(wx.Panel, OperationUI):
    def __init__(
        self,
        parent: wx.Window,
        canvas: "EditCanvas",
        world: "BaseLevel",
        options_path: str,
    ):
        wx.Panel.__init__(self, parent)
        OperationUI.__init__(self, parent, canvas, world, options_path)

        self._sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self._sizer)

        options = self._load_options({})

        self._description = wx.TextCtrl(self,
                                        style=wx.TE_MULTILINE | wx.TE_READONLY
                                        | wx.TE_BESTWRAP)
        self._sizer.Add(self._description, 0, wx.ALL | wx.EXPAND, 5)
        self._description.SetLabel(
            "ペイントツールのバケツのように、選択したマスを起点として空洞を指定したブロックで埋めます。")
        self._description.Fit()

        self._find_size_label = wx.StaticText(self, wx.ID_ANY,
                                              "最大空洞探査ブロック数\n" + "※0指定で制限なし")
        self._sizer.Add(self._find_size_label, 0, wx.LEFT | wx.RIGHT, 5)

        self._find_size = wx.SpinCtrl(self,
                                      style=wx.SP_ARROW_KEYS,
                                      min=0,
                                      max=2000000000,
                                      initial=0)
        default_value = options.get('find_size')
        if default_value is not None:
            self._find_size.SetValue(int(default_value))

        self._sizer.Add(self._find_size, 0,
                        wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 5)

        self._block_define_label = wx.StaticText(self, wx.ID_ANY, "空洞を埋めるブロック")
        self._sizer.Add(self._block_define_label, 0, wx.LEFT | wx.RIGHT, 5)
        self._block_define = BlockDefine(
            self,
            world.translation_manager,
            wx.VERTICAL,
            *(options.get("fill_block_options", [])
              or [world.level_wrapper.platform]),
            show_pick_block=True)
        self._block_click_registered = False
        self._block_define.Bind(EVT_PICK, self._on_pick_block_button)
        self._sizer.Add(
            self._block_define, 1,
            wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self._run_button = wx.Button(self, label="実行")
        self._run_button.Bind(wx.EVT_BUTTON, self._run_operation)
        self._sizer.Add(self._run_button, 0,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self.Layout()

    @property
    def wx_add_options(self) -> Tuple[int, ...]:
        return (1, )

    def _on_pick_block_button(self, evt):
        """Set up listening for the block click"""
        if not self._block_click_registered:
            self.canvas.Bind(EVT_BOX_CLICK, self._on_pick_block)
            self._block_click_registered = True
        evt.Skip()

    def _on_pick_block(self, evt):
        self.canvas.Unbind(EVT_BOX_CLICK, handler=self._on_pick_block)
        self._block_click_registered = False
        x, y, z = self.canvas.cursor_location
        self._block_define.universal_block = (
            self.world.get_block(x, y, z, self.canvas.dimension),
            None,
        )

    def _get_fill_block(self):
        return self._block_define.universal_block[0]

    def unload(self):
        self._save_options({
            "find_size":
            self._find_size.GetValue(),
            "fill_block":
            self._get_fill_block(),
            "fill_block_options": (
                self._block_define.platform,
                self._block_define.version_number,
                self._block_define.force_blockstate,
                self._block_define.namespace,
                self._block_define.block_name,
                self._block_define.properties,
            ),
        })

    def _run_operation(self, _):
        self.canvas.run_operation(lambda: self._flood_fill())

    def _flood_fill(self):
        # 再起回数の上限突破
        sys.setrecursionlimit(2000000000)

        dimension = self.canvas.dimension
        count = 0
        air_count = 0
        que_count = 0
        max_count = self._find_size.GetValue()
        min_x, min_y, min_z = self.canvas.selection.selection_group.min
        max_x, max_y, max_z = self.canvas.selection.selection_group.max

        # 範囲選択が1マスでない場合エラー
        if not (min_x == (max_x - 1) and min_y == (max_y - 1)
                and min_z == (max_z - 1)):
            wx.MessageBox("選択範囲が1マスではありません", "塗りつぶし")
            return

        queue = [(min_x, min_y, min_z)]

        while len(queue) > 0:
            x, y, z = queue.pop()
            cx, cz = x >> 4, z >> 4
            offset_x, offset_z = x - 16 * cx, z - 16 * cz
            count += 1

            try:
                chunk = self.world.get_chunk(cx, cz, dimension)
            except ChunkDoesNotExist:
                # チャンク読み込めなかったら次のループ
                continue

            # 探査ブロックが空気ブロック以外の場合次のループ
            if not (chunk.get_block(offset_x, y, offset_z).base_name == "air"
                    or chunk.get_block(offset_x, y, offset_z).base_name
                    == "cave_air" or chunk.get_block(
                        offset_x, y, offset_z).base_name == "void_air"):
                continue

            # 探査ブロックの最大数を超えた場合終わり
            if 0 < max_count <= air_count:
                wx.MessageBox(
                    "探査数が最大値に到達しました\n" + "検査を行った分を塗りつぶしてあります\n" +
                    "不要な場合はRedoを実行してください", "塗りつぶし")
                return

            # ブロックの設置
            chunk.set_block(offset_x, y, offset_z, self._get_fill_block())

            # チャンクのセーブフラグをTrueにする
            chunk.changed = True

            air_count += 1

            # 隣接するブロックの探査キューを追加
            queue.append((x + 1, y, z))
            queue.append((x - 1, y, z))
            if y < 255:
                queue.append((x, y + 1, z))
                que_count += 1
            if y > 0:
                queue.append((x, y - 1, z))
                que_count += 1
            queue.append((x, y, z + 1))
            queue.append((x, y, z - 1))

            que_count += 4

            yield count / que_count
class Fill(wx.Panel, DefaultOperationUI):
    def __init__(
        self,
        parent: wx.Window,
        canvas: "EditCanvas",
        world: "BaseLevel",
        options_path: str,
    ):
        wx.Panel.__init__(self, parent)
        DefaultOperationUI.__init__(self, parent, canvas, world, options_path)
        self.Freeze()
        self._sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self._sizer)

        options = self._load_options({})

        self._block_define = BlockDefine(
            self,
            world.translation_manager,
            wx.VERTICAL,
            *(options.get("fill_block_options", [])
              or [world.level_wrapper.platform]),
            show_pick_block=True)
        self._block_define.Bind(EVT_PICK, self._on_pick_block_button)
        self._sizer.Add(self._block_define, 1,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self._run_button = wx.Button(self, label="Run Operation")
        self._run_button.Bind(wx.EVT_BUTTON, self._run_operation)
        self._sizer.Add(self._run_button, 0,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self.Layout()
        self.Thaw()

    @property
    def wx_add_options(self) -> Tuple[int, ...]:
        return (1, )

    def _on_pick_block_button(self, evt):
        """Set up listening for the block click"""
        self._show_pointer = True

    def _on_box_click(self):
        if self._show_pointer:
            self._show_pointer = False
            x, y, z = self._pointer.pointer_base
            self._block_define.universal_block = (
                self.world.get_block(x, y, z, self.canvas.dimension),
                None,
            )

    def _get_fill_block(self):
        return self._block_define.universal_block[0]

    def disable(self):
        self._save_options({
            "fill_block":
            self._get_fill_block(),
            "fill_block_options": (
                self._block_define.platform,
                self._block_define.version_number,
                self._block_define.force_blockstate,
                self._block_define.namespace,
                self._block_define.block_name,
                self._block_define.properties,
            ),
        })

    def _run_operation(self, _):
        self.canvas.run_operation(lambda: fill(
            self.world,
            self.canvas.dimension,
            self.canvas.selection.selection_group,
            self._get_fill_block(),
        ))
Example #4
0
class Fill(wx.Panel, OperationUI):
    def __init__(self, parent: wx.Window, canvas: "EditCanvas", world: "World",
                 options_path: str):
        wx.Panel.__init__(self, parent)
        OperationUI.__init__(self, parent, canvas, world, options_path)

        self._sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self._sizer)

        options = self._load_options({})

        self._block_define = BlockDefine(
            self,
            world.translation_manager,
            wx.VERTICAL,
            *(options.get("fill_block_options", [])
              or [world.world_wrapper.platform]),
            show_pick_block=True)
        self._block_click_registered = False
        self._block_define.Bind(EVT_PICK, self._on_pick_block_button)
        self._sizer.Add(self._block_define, 1,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self._run_button = wx.Button(self, label="Run Operation")
        self._run_button.Bind(wx.EVT_BUTTON, self._run_operation)
        self._sizer.Add(self._run_button, 0,
                        wx.ALL | wx.ALIGN_CENTRE_HORIZONTAL, 5)

        self.Layout()

    @property
    def wx_add_options(self) -> Tuple[int, ...]:
        return (1, )

    def _on_pick_block_button(self, evt):
        """Set up listening for the block click"""
        if not self._block_click_registered:
            self.canvas.Bind(EVT_BOX_CLICK, self._on_pick_block)
            self._block_click_registered = True
        evt.Skip()

    def _on_pick_block(self, evt):
        self.canvas.Unbind(EVT_BOX_CLICK, handler=self._on_pick_block)
        self._block_click_registered = False
        x, y, z = self.canvas.cursor_location
        self._block_define.universal_block = (
            self.world.get_block(x, y, z, self.canvas.dimension),
            None,
        )

    def _get_fill_block(self):
        return self._block_define.universal_block[0]

    def unload(self):
        self._save_options({
            "fill_block":
            self._get_fill_block(),
            "fill_block_options": (
                self._block_define.platform,
                self._block_define.version_number,
                self._block_define.force_blockstate,
                self._block_define.namespace,
                self._block_define.block_name,
                self._block_define.properties,
            ),
        })

    def _run_operation(self, _):
        self.canvas.run_operation(lambda: fill(
            self.world,
            self.canvas.dimension,
            self.canvas.selection_group,
            self._get_fill_block(),
        ))
Example #5
0
class _CollapsibleBlockDefine(wx.Panel):
    def __init__(self,
                 parent: MultiBlockDefine,
                 translation_manager,
                 collapsed=False):
        super().__init__(parent, style=wx.BORDER_SIMPLE)

        self.EXPAND = MAXIMIZE.bitmap(18, 18)
        self.COLLAPSE = MINIMIZE.bitmap(18, 18)

        self._collapsed = collapsed

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(sizer)

        header_sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(header_sizer, 0, wx.ALL, 5)

        self.expand_button = wx.BitmapButton(self, bitmap=self.EXPAND)
        header_sizer.Add(self.expand_button, 0, 5)

        self.up_button = wx.BitmapButton(self, bitmap=UP_CARET.bitmap(18, 18))
        header_sizer.Add(self.up_button, 0, wx.LEFT, 5)
        self.up_button.Bind(wx.EVT_BUTTON, lambda evt: parent.move_up(self))

        self.down_button = wx.BitmapButton(self,
                                           bitmap=DOWN_CARET.bitmap(18, 18))
        header_sizer.Add(self.down_button, 0, wx.LEFT, 5)
        self.down_button.Bind(wx.EVT_BUTTON,
                              lambda evt: parent.move_down(self))

        self.delete_button = wx.BitmapButton(self, bitmap=TRASH.bitmap(18, 18))
        header_sizer.Add(self.delete_button, 0, wx.LEFT, 5)
        self.delete_button.Bind(wx.EVT_BUTTON, lambda evt: parent.delete(self))

        self.block_define = BlockDefine(self, translation_manager,
                                        wx.HORIZONTAL)
        sizer.Add(self.block_define, 1,
                  wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 5)
        self.collapsed = collapsed

        self.block_label = wx.StaticText(
            self,
            label=self._gen_block_string(),
            style=wx.ST_ELLIPSIZE_END | wx.ST_NO_AUTORESIZE,
            size=(500, -1),
        )
        header_sizer.Add(self.block_label, 1,
                         wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        self.expand_button.Bind(wx.EVT_BUTTON,
                                lambda evt: self._toggle_block_expand(parent))
        self.block_define.Bind(EVT_PROPERTIES_CHANGE,
                               self._on_properties_change)

    @property
    def collapsed(self) -> bool:
        return self._collapsed

    @collapsed.setter
    def collapsed(self, collapsed: bool):
        self._collapsed = collapsed
        if self._collapsed:
            self.expand_button.SetBitmap(self.EXPAND)
            self.block_define.Hide()
        else:
            self.expand_button.SetBitmap(self.COLLAPSE)
            self.block_define.Show()
        self.TopLevelParent.Layout()

    def _toggle_block_expand(self, parent: MultiBlockDefine):
        if self.collapsed:
            parent.collapse()
        self.collapsed = not self.collapsed

    def _on_properties_change(self, evt):
        self.block_label.SetLabel(self._gen_block_string())
        self.TopLevelParent.Layout()
        evt.Skip()

    def _gen_block_string(self):
        base = f"{self.block_define.namespace}:{self.block_define.block_name}"
        properties = ",".join(
            (f"{key}={value}"
             for key, value in self.block_define.str_properties.items()))
        return f"{base}[{properties}]" if properties else base