SmylerMC / litemapy

Litemapy's goal is to provide an easy to use way to read and edit Litematica's schematic file format
GNU General Public License v3.0
52 stars 6 forks source link

Trouble With Interpreting/Using Blockstate Class #48

Closed TheMelonHead286 closed 3 months ago

TheMelonHead286 commented 3 months ago

For context, I found this issue with a strange goal in mind, I wanted to take blocks from an existing litematica file into another litematica file, essentially fusing the two.

This was my first code attempt. Ex2 It had the unwanted side-effect of removing the block state from all blocks to the litematica file it was pasted in... Ex1

My second attempt, Ex4 however, because of how block states are formatted it failed. Ex3 Ex5 Using my console, I found that the block id and block states were fused together in one string.

My final attempt was to split them manually Ex6 unfortunately I couldn't use a string as the basis of putting in the block data. Also here is the image of my console for the third attempt. Ex7

I was wondering if there is an easy way to have the block state carry over from getting the block from another litematica region. As putting in manually every state that a block could be would work, just extremely tedious.

SmylerMC commented 3 months ago

Hi, you appear to be doing some string manipulations that are not necessary and end up creating invalid block states which are discarded by Minecraft when the schematic is loaded.

The code below is untested and incomplete, but should be enough to illustrate what I mean.

I hope it all helps.

TLDR.

When you call Region.getblock(), you get a BlockState object as a result. You can use it directly, you do not need to create new one.

from litemapy import Region, BlockState, Schematic

schematic = Schematic.load("...")
region = list(schematic.regions.values())[0]

# Be careful with the width, height and length, they might be negative, and so can block coordinates.
new_region = Region(0, 0, 0, region.width, region.height, region.length)
save_schematic = new_region.as_schematic(...)

for x, y, z in region.allblockpos():
    state = region.getblock(x, y, z)
    new_region.setblock(x, y, z, state)

# You don't need to call updatemeta, it is done automatically for you by default.
save_schematic.save(...)

Or, with the latest version of Litemapy (0.9.0b0):

from litemapy import Region, BlockState, Schematic

schematic = Schematic.load("...")
region = list(schematic.regions.values())[0]

new_region = Region(0, 0, 0, region.width, region.height, region.length)
save_schematic = new_region.as_schematic(...)

for x, y, z in region.block_positions():
    new_region[x, y, z] = region[x, y, z]

save_schematic.save(...)

I refer you to the documentation to understand how you can use Litemapy in more details.

You may also want to read a bit about Python object oriented programming if that's new to you, to understand how you can manipulate the objects from Litemapy.

First attempt

RegBlock = Reg.getblock(x, y, z)
Block = RegBlock.blockid
NewReg.setblock(x, y, z, BlockState(f"{Block}"))

There are two problems here:

  1. Block states are made of two types of information, the block id (e.g. minecraft:oack_stairs), and the block properties (e.g. facing=west). When you create a new Block variable from RegBlock.blockid, you are getting a copy of that state's block id, which is a string. By creating a new BlockState object from that variable alone, you are loosing all the information from the properties. This is why all your stairs are facing the same direction.
  2. You are using a format string as the argument to BlockState(f"{Block}"). This is not invalid by itself, but does not serve any purpose as your Block variable is already a string.

Second attempt

RegBlock = Reg.getblock(x, y, z)
Block = RegBlock.blockid
NewReg.setblock(x, y, z, BlockState(f"{RegBlock}"))

Using my console, I found that the block id and block states were fused together in one string.

No. The block id and block properties are stored together as separate fields within a BlockState object. When you print that object to the console, you are forcing Python to convert it into a string, which it does by calling the __repr__ magic method Litemapy defines for BlockStates. That method simply returns the usual string representation of block states you would use in commands (and other places).

The exact same thing happens when you use that variable in the format string. Since you pass that format string to the BlockState constructor, you are creating a new state that has the full textual representation of the original block state as its id (e.g. minecraft:oak_stairs[facing=north]). Since those ids are not valid for Minecraft, it discards the states when loading the schematic. That's why blocks disappear.