Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pasting rotated Java schematic 180 degrees in .mcstructure causes 1 block of air on X-edge #252

Open
MSpaceDev opened this issue Jul 22, 2023 · 1 comment

Comments

@MSpaceDev
Copy link

Describe the bug
I have a script that takes in .schem files from Java, and converts them to Bedrock .mcstructure.

I have managed to get the code working for rotations [0, 90, 270], however 180 is not working. There is an air gap on the X-most edge, where my structure is cut off. It pastes air, meaning the blank structure size is correct. The Z-edge is fine. What could be causing the X-edge to be air?

To Reproduce
Steps to reproduce the behavior:

  1. Create new Amulet python script
import glob
from typing import List
from argparse import ArgumentParser
import shutil
import os
import sys

import amulet
from amulet.api.level import World
from amulet.api.level import Structure
from amulet.level.formats.mcstructure import MCStructureFormatWrapper
from amulet.level.formats.schematic import SchematicFormatWrapper
from amulet.api.selection import SelectionGroup
from amulet.api.selection import SelectionBox

def load_schematics(schematic_files: List[str]) -> List[Structure]:
    schematics = []
    for schematic_file in schematic_files:
        with open(schematic_file, "rb") as f:
            schematics.append(amulet.load_level(schematic_file))
    return schematics
    
def process_schematics(schematic_files: List[str], schematics: List[Structure]):
    for i, schematic in enumerate(schematics):
        # Get bounds of schematic
        bounds = schematic.bounds("main")
        x_size = bounds.max[0] - bounds.min[0]
        y_size = bounds.max[1] - bounds.min[1]
        z_size = bounds.max[2] - bounds.min[2]

        # Get basename of structure
        basename = schematic_files[i].replace(schematic_dir + "\\", "", -1)
        basename = basename.replace("\\", "_", -1)
        basename = basename.replace(" ", "_", -1)
        basename = basename.replace(".schem", "", -1)

        # Rotations
        rotations = [0, 90, 180, 270]
        for rotation in rotations:
            mcstructure_dir = os.path.join(output_dir, "structure", f"{basename}_{rotation}.mcstructure")
            mcstructure_wrapper = MCStructureFormatWrapper(mcstructure_dir)

            # Create new mcstructure file with correct bounds
            if rotation == 90 or rotation == 270:
                mcstructure_wrapper.create_and_open(platform, version, SelectionGroup(SelectionBox((0, 0, 0), (z_size, y_size, x_size))), True)
            else:
                mcstructure_wrapper.create_and_open(platform, version, SelectionGroup(SelectionBox((0, 0, 0), (x_size, y_size, z_size))), True)
            mcstructure_wrapper.save()
            mcstructure_wrapper.close()

            # Paste the schematic in the mcstructure
            if rotation == 90 or rotation == 270:
                paste_pos = (z_size / 2, y_size / 2, x_size / 2)
            else:
                paste_pos = (x_size / 2, y_size / 2, z_size / 2)

            mcstructure_level = amulet.load_level(mcstructure_dir)
            mcstructure_level.paste(schematic, "main", bounds, "main", paste_pos, (1.0, 1.0, 1.0), (0.0, rotation, 0.0))
            mcstructure_level.save()
            mcstructure_level.close()

def main():
    # Load all schematics
    schematic_files = glob.glob(os.path.join("./schematics", "**/*.schem"))
    schematics = load_schematics(schematic_files)
    
    # Place schematics & create mcstructures
    process_schematics(schematic_files, schematics)
  1. Place .schem in ./schematics/category/file.schem
  2. Run python script and copy new .mcstructures in ./output
  3. Notice that rotations 0, 90 & 270 are correct.
  4. Notice that rotation 180 is missing a single edge on the X-axis

Expected behavior
The generated .mcstructure for rotation 180 includes the full structure, and does not have the single axis air gap towards the X direction.

Screenshots
ApplicationFrameHost_cK28dpqdGx

Desktop (please complete the following information):

  • OS: Windows 11
  • Program Version: Latest Amulet-Core v1.9.17

Additional context
Download .schem: https://drive.google.com/file/d/1yStCN8vAfiQXQm7wfSJiHESWKcxiuFPZ/view?usp=sharing

@gentlegiantJGC
Copy link
Member

This is a problem stemming from rotating a structure with a mixture of even and odd lengths.
If all sides are even the centre point is on the corner of the blocks and if both sides are odd it is in the centre of a block.
If one is odd and one is even the rotation point is on the face between two of the blocks. When rotating this state the blocks don't align with the grid and it gets complicated.
My solution to this problem was to round the rotation point down to the most negative corner of the centre block.
Doing so has introduced this problem.

This code should solve your problem.

import glob
import os
from math import floor

import amulet
from amulet.level.formats.mcstructure import MCStructureFormatWrapper
from amulet.api.selection import SelectionGroup
from amulet.api.selection import SelectionBox


def process_schematics(schematic_dir: str, output_dir: str):
    for schematic_path in glob.glob(os.path.join(glob.escape(schematic_dir), "**/*.schem"), recursive=True):
        schematic = amulet.load_level(schematic_path)
        # Get bounds of schematic
        bounds = schematic.bounds("main")
        x_size = bounds.max_x - bounds.min_x
        y_size = bounds.max_y - bounds.min_y
        z_size = bounds.max_z - bounds.min_z

        # Get basename of structure
        basename = os.path.basename(schematic_path)[:-len(".schem")].replace(" ", "_")

        x_mid = x_size // 2
        y_mid = y_size // 2
        z_mid = z_size // 2

        for rotation, shape, paste_pos in (
            (0, (x_size, y_size, z_size), (x_mid, y_mid, z_mid)),
            (90, (z_size, y_size, x_size), (z_mid, floor(y_mid), x_size - x_mid)),
            (180, (x_size, y_size, z_size), (x_size - x_mid, floor(y_mid), z_size - z_mid)),
            (270, (z_size, y_size, x_size), (z_size - z_mid, y_mid, x_mid)),
        ):
            mcstructure_path = os.path.join(output_dir, f"{basename}_{rotation}.mcstructure")
            mcstructure_wrapper = MCStructureFormatWrapper(mcstructure_path)

            # Create new mcstructure file with correct bounds
            mcstructure_wrapper.create_and_open(
                "bedrock", (1, 20, 0),
                SelectionGroup(SelectionBox((0, 0, 0), shape)),
                True
            )
            mcstructure_wrapper.save()
            mcstructure_wrapper.close()

            # Paste the schematic in the mcstructure
            mcstructure_level = amulet.load_level(mcstructure_path)
            mcstructure_level.paste(schematic, "main", bounds, "main", paste_pos, (1.0, 1.0, 1.0), (0.0, rotation, 0.0))
            mcstructure_level.save()
            mcstructure_level.close()


def main():
    # Place schematics & create mcstructures
    process_schematics("schematics", "out")


if __name__ == '__main__':
    main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants