RichysHub / MagicaVoxel-VOX-importer

Blender import script for MagicaVoxel .vox format as cube primitives.
MIT License
227 stars 56 forks source link

Frame-based animations #11

Closed wizardgsz closed 5 years ago

wizardgsz commented 5 years ago

Shorlty: I think we should load the first (or a specific) frame instead of loading all of them (see SIZE and XYZI for old file format).

See also on YouTube: Magicavoxel Frame Based Animation - Tutorial

Chunk 'MAIN'
{
    // pack of models
    Chunk 'PACK'    : optional

    // models (or FRAMES)
    // First FRAME
    Chunk 'SIZE'
    Chunk 'XYZI'

    // Second FRAME
    Chunk 'SIZE'
    Chunk 'XYZI'

    ...
}

We are drawing all voxels, all frames now (deer.vox): first frame to the left (355 voxels) vs all frames (1415 voxels).

frames

deer

Here we can look for a "specific" frame voxels:

        current_frame = 0  # MagicaVoxels frames are 0-based

        while True:
        ...

            elif name == 'XYZI':
                # voxel data, READ ALWAYS for all frames, but...
                num_voxels, = struct.unpack('<i', vox.read(4))
                for voxel in range(num_voxels):
                    voxel_data = struct.unpack('<4B', vox.read(4))
                    # ...stores them for a SPECIFIC or first frame ONLY
                    voxels.append(voxel_data)

For example:

                # voxel data
                if load_frame == current_frame:
                    num_voxels, = struct.unpack('<i', vox.read(4))
                    for voxel in range(num_voxels):
                        voxel_data = struct.unpack('<4B', vox.read(4))
                        voxels.append(voxel_data)
                else:
                    print("Skipping voxels in frame #{}".format(current_frame))
                    vox.read(s_self)
                current_frame = current_frame + 1

About error handling:

RichysHub commented 5 years ago

Hmm, very good point, I hadn't realised this case. In the current framework, what you've laid out seems pretty reasonable.

In the case that the vox has less frames than the frame number provided, we could unpack every XYZI we see, into voxel_data. Then if we reach end of frames we know that the last one we saw was the last frame. Then we move that data over to voxels.

That would, in effect, make load_frame = min(load_frame, total_frames), which is how the upper voxel bound is currently handled.

Are separate XYZI chunks always separate frames? I worry with the scene graph stuff from the extension, and with layers that might not be the case.

wizardgsz commented 5 years ago

I have to further investigate about new file format (i.e. Transform Node, Group Node, Shape Node and Layer Chunks), but old files have always a pair of SIZE+XYZI for each "frame" (SIZE can be skipped of course).

So, SIZE+XYZI identifies a single frame.

Should I make a pull request or have you time to "fix" it in the next days? At least for old file format I mean...

load_frame, or whatever you prefer, is a new Integer property in [0, +inf] defined in

class ImportVOX(bpy.types.Operator, ImportHelper):
load_frame: IntProperty(name="Frame Number To Load", default=0, min=0)

The XYZI parser could be:

        # MagicaVoxels frames are 0-based
        current_frame = 0

        while True:

            ...

            elif name == 'XYZI':
                # voxel data
                if current_frame <= load_frame:
                    voxels.clear()
                    num_voxels, = struct.unpack('<i', vox.read(4))
                    for voxel in range(num_voxels):
                        voxel_data = struct.unpack('<4B', vox.read(4))
                        voxels.append(voxel_data)
                else:
                    print("Skipping voxels in frame #{}".format(current_frame))
                    vox.read(s_self)
                current_frame = current_frame + 1

Btw, should we keep track of changes and versioning in bl_info and "change log" in the script?

wizardgsz commented 5 years ago

Sorry, I forgot to modify the function call (new param load_frame=0):

def import_vox(path, *, voxel_spacing=1, voxel_size=1,
               load_frame=0, use_bounds=False, start_voxel=None, end_voxel=None,
               use_palette=True, gamma_correct=True, gamma_value=2.2, use_shadeless=False):

In 0.98 it seems we have load_frame in [0, 29] (max 30 frames), but in release note I read:

0.98 - 10/22/2016

Frame-Based Animation Supports up to 24 frames animation

And about latest release 0.99, no more frame animations:

Limitations: will be improved in future version

  • Not stable
  • Export: can only export single models, cannot export models with offsets and names
  • Render: no voxel shapes
  • Animation: no frame based animation
RichysHub commented 5 years ago

Pushed commit a543e8f to incorporate these changes. num_models from PACK chunk is used to clamp the value of load_frame, makes the implementation very clean.

Have updated bl_info to include a version, have made this version 2.1, and have tagged appropriately.

A lack of frame support in MV0.99 is annoying, but at least that resolves the question of how it and the new scene graph interact: they don't.