enzyme69 / blendersushi

Blender Sushi related scripts. Mostly about Sverchok, Geometry Nodes, Animation Nodes, and related Python scripts.
243 stars 33 forks source link

LIVENODING 876 / Magica Voxel VOX Format Reader As Node #242

Open enzyme69 opened 6 years ago

enzyme69 commented 6 years ago

I am studying IO input output interoperability between Magica Voxel app and Blender. I am using a bit of Python knowledge and Sverchok add-on to assist me in the process.

Basically I started by reading the Python script by Mate Steinforth below for Cinema4D and making it work for Blender Python bpy. https://github.com/MateSteinforth/ImportVOX

The script is 2 years old and still works fine. I modified and simplified the script so that the script will read the VOX bytes and parse it into LIST OF NUMBERS that we can use to regenerate voxel box in Blender. Surprisingly it is not as complicated after all I can extract the X Y Z and Palette Index Number of VOX.

import struct
import random
import bpy

# Import MagicaVoxel .VOX files and MagicaVoxel .PNG Palette Files
# by @matesteinforth
# 
# modified by Blender Sushi Guy 20171229 to work for Blender

mypath = "/Users/jimmygunawan/facemonster_002.vox"

def importVox(path):

    # Parse file
    with open(path, 'rb') as f:

        # Check filetype
        bytes = f.read(4)
        file_id = struct.unpack(">4s",  bytes)
        #print(file_id[0])

        if file_id[0] == b'VOX ':

            print("---------- Reading VOX....")

            # Init material list
            matlist = [];
            #BaseCube = c4d.BaseObject(c4d.Ocube)
            #BaseCube[c4d.PRIM_CUBE_DOFILLET]=True
            #BaseCube[c4d.PRIM_CUBE_FRAD]=8    
            #BaseCube[c4d.PRIM_CUBE_SUBF]=1
            #doc.InsertObject(BaseCube)

            #skip header
            f.seek(56)

            # Read number of voxels, stuct.unpack parses binary data to variables
            bytes = f.read(4)
            numvoxels = struct.unpack('<I', bytes)

            #print(len(numvoxels))

            # Iterate through voxels
            for x in range(0, numvoxels[0]):

                # Read voxels, ( each voxel : 1 byte x 4 : x, y, z, colorIndex ) x numVoxels
                bytes = f.read(4)
                voxel = struct.unpack('<bbbB', bytes)  

                #print(voxel)  

                # Generate Cube and set position, change to 'Oinstance' for instances

                #MyCube = c4d.BaseObject(c4d.Oinstance)
                #MyCube[c4d.INSTANCEOBJECT_RENDERINSTANCE]=True
                #MyCube[c4d.INSTANCEOBJECT_LINK]=BaseCube
                #MyCube.SetAbsPos(c4d.Vector(-voxel[1]*200,voxel[2]*200,voxel[0]*200))
                bpy.ops.mesh.primitive_cube_add(radius=0.5, view_align=False, enter_editmode=False, location=(-voxel[0], voxel[1], voxel[2]))

                #print(voxel[0], voxel[1], voxel[2])

                #update material list, generate new material only if it isn't in the list yet
                matid = voxel[3]

                print(voxel[0], voxel[1], voxel[2], matid)

                #if matid not in matlist:
                    #matlist.append(matid)
                    #myMat = c4d.BaseMaterial(c4d.Mmaterial)
                    #myMat.SetName(str(matid))
                    #myMat[c4d.MATERIAL_COLOR_COLOR]=c4d.Vector(random.random(), random.random(), random.random())
                    #doc.InsertMaterial(myMat)

                # Assign material to voxel and insert everything into the scene
                #mat = doc.SearchMaterial(str(matid))
                #textag = c4d.TextureTag()
                #textag.SetMaterial(mat)
                #MyCube.InsertTag(textag)
                #doc.InsertObject(MyCube)

        else:
            #gui.MessageDialog('Not a .VOX file')
            print('Not a VOX file!')

#            
#def importPalette(path):
#    orig = bitmaps.BaseBitmap()
#    if orig.InitWith(path)[0] != c4d.IMAGERESULT_OK:
#        #gui.MessageDialog("Cannot load image \"" + path + "\".")
#        return
#    
#    width, height = orig.GetSize()
#    
#    if height != 1:
#        print("nope")
#        #gui.MessageDialog("This is not a MagicaVoxel .PNG Palette")
#    else:
#        for x in range(0, width):
#            mat = doc.SearchMaterial(str(x+1))
#            if mat:
#                color = orig.GetPixel(x, 0)
#                #mat[c4d.MATERIAL_COLOR_COLOR]=c4d.Vector(float(color[0])/255, float(color[1])/255, float(color[2])/255)

importVox(mypath)

print("---------- Completed!")
enzyme69 commented 6 years ago

So with script above it will import the Voxel, but it does not really consider the Color Index Palette of each voxel box.

I further think a bit and decided maybe we can PRINT OUT the list of data as Binary Text, something like below [ x y z index ]

8 13 3 15
4 3 1 188
3 3 1 188
16 5 3 15
17 4 3 15
7 15 3 15
8 14 3 15
.....

Above I can read and parse using Sverchok nodes to become Matrix placement of Box object.

Text In node will do the job.

screen shot 2017-12-30 at 12 14 56 pm
enzyme69 commented 6 years ago
screen shot 2017-12-30 at 12 15 31 pm

Once it become a data inside Blender that NODES can operate and process, we can simply generate voxel with random color, with color based on Hue Spectrum, or maybe even supplying our own PNG Color Palette ala Magica Voxel.

enzyme69 commented 6 years ago

But I went further and actually modify the Python script executable above into a node. Maybe below setup can improve. I am using Script Node Lite SNL node to turn the script into node.

screen shot 2017-12-30 at 12 16 36 pm

By doing it this way, I can read and update multiple VOX files on the fly and generate the result in Blender. Thanks to SV nodes.

screen shot 2017-12-30 at 12 18 30 pm

## HERE IS THE BLEND ZIP vox_reader_study_020_2017_12_30_01_19.zip

enzyme69 commented 6 years ago
### SNL READ FILES OF TYPE VOX

"""
in dummy s
out filepaths s
out filenames s
"""

import os
import bpy

# put the location to the folder where the objs are located here in this fashion
# this line will only work on windows ie C:\objects
#path_to_obj_dir = os.path.join('C:\\', 'Some\\Folder\\Collada\\')
path_to_obj_dir = os.path.join('/Users/jimmygunawan/', 'Documents', '_VOX_FILES')

# get list of all files in directory
file_list = sorted(os.listdir(path_to_obj_dir))

# get a list of files ending in 'obj'
obj_list = [item for item in file_list if item.endswith('.vox')]

# loop through the strings in obj_list and add the files to the scene
for item in obj_list:
    path_to_file = os.path.join(path_to_obj_dir, item)
    filepaths.append(path_to_file)
    filenames.append(item)
    #print(path_to_file)
enzyme69 commented 6 years ago
### SNL VOX READER AS NODE

"""
in mypath s
out obpos v
out colorindex s
"""

import struct
import bpy

# Import MagicaVoxel .VOX files and MagicaVoxel .PNG Palette Files
# by @matesteinforth
# modified by Blender Sushi Guy 20171229 to work for Blender via Sverchok Node

#mypath = "/Users/jimmygunawan/facemonster_002.vox"

#mypath = mypath[0][0]
mypath = mypath[0]

# Parse file
with open(mypath, 'rb') as f:

    # Check filetype
    bytes = f.read(4)
    file_id = struct.unpack(">4s",  bytes)

    if file_id[0] == b'VOX ':

        # Init material list
        matlist = []

        #skip header
        f.seek(56)

        # Read number of voxels, stuct.unpack parses binary data to variables
        bytes = f.read(4)
        numvoxels = struct.unpack('<I', bytes)

        # Iterate through voxels
        for x in range(0, numvoxels[0]):

            # Read voxels, ( each voxel : 1 byte x 4 : x, y, z, colorIndex ) x numVoxels
            bytes = f.read(4)
            voxel = struct.unpack('<bbbB', bytes)  

            #update material list, generate new material only if it isn't in the list yet
            matid = voxel[3]

            #print(voxel[0], voxel[1], voxel[2], matid)

            obpos.append([voxel[0], voxel[1], voxel[2]])
            colorindex.append(matid)
enzyme69 commented 6 years ago

So you have 3 methods up there to import VOX using Blender Python and SV nodes: 1) Once off Python script to grab the Voxel, no material or color 2) Get list of data and assign color procedurally using Sverchok 3) Actually use NODES to read VOX files in the directory and read and update every frame as needed to generate VOX on the fly.

enzyme69 commented 6 years ago

KEEP IN MIND:

Magica Voxel version 0.98 and 0.99 should work with this importer.

VOXEL is definitely a great study, especially in case of Magica Voxel app, it has a lot of treasures.