Script takes days

I have very little experience with python, but have managed to piece together a script to import a game map from a game that I used to play.

The map is a 128x128 grid that has 0-3 cubes per tile. There is also “tiles” (smaller than 1x1 cubes) that is a separate render.

Problem is: the import process takes DAYS (4-5 days) to complete.

Could anyone give me guidance on reducing the render time? (aside from the console output)

In the zip: 2 binary data files for one of the smaller maps.

Appreciation in advance, thanks!


import bpy, os, sys, struct, math


def drawLowBlock(blockX, blockY, LowBoxTopMod, LowSidesTextureId, LowTopTextureId, LowBoxTopZ, LowObjectId, LowTopShape):
    bpy.context.scene.layers[1] = True
    
    low_height = 0
    low_top = 0
    
    if LowSidesTextureId != 0 or LowTopTextureId != 0 or LowBoxTopZ != 0:
        
        global blockcounter
        
        blockcounter = blockcounter + 1
        
        low_height = 1
        low_top = float(LowBoxTopZ / 64)


        x = -blockX * 2
        y = blockY * 2


        bpy.ops.mesh.primitive_cube_add(location=(-x, -y, -low_height + low_top * 2))
        
        cube = bpy.context.active_object


        cube.name = "lowblock x: " + str(blockX) + " y: " + str(blockY)
        cube.scale=(1, 1, low_height)
        
        print(str(blockcounter) + " " + cube.name)
                
def drawMidBlock(blockX, blockY, MidBoxBottomZ, MidBoxTopZ, MidBottomShape, MidSidesTextureId, MidTopTextureId, LowSidesTextureId, LowTopTextureId):
    bpy.context.scene.layers[2] = True
    
    mid_top = float(MidBoxTopZ / 64)
    mid_bottom = float(MidBoxBottomZ / 64)
    mid_height = float(math.fabs(mid_top - mid_bottom))
    
    if mid_height != 0:
        
        global blockcounter
        
        blockcounter = blockcounter + 1
        
        x = -blockX * 2
        y = blockY * 2
        
        bpy.ops.mesh.primitive_cube_add(location=(-x, -y, -mid_height + mid_top * 2))
        
        cube = bpy.context.object
        
        cube.name = "midblock x: " + str(blockX) + " y: " + str(blockY)
        cube.scale=(1, 1, mid_height)
        
        print(str(blockcounter) + " " + cube.name)


def drawHighBlock(blockX, blockY, HighTextureId, HighBoxBottomZ, CeilingTextureId, unknown_16):
    bpy.context.scene.layers[3] = True
    
    high_top = float(unknown_16 / 64)
    high_bottom = float(HighBoxBottomZ / 64)
    high_height = float(math.fabs(high_top - high_bottom))
    
    if CeilingTextureId == 0 and unknown_16 != 32767:
        CeilingTextureId = HighTextureId


    if CeilingTextureId != 0 and unknown_16 != 32767:
        
        global blockcounter
        
        blockcounter = blockcounter + 1
        
        x = -blockX * 2
        y = blockY * 2
        
        bpy.ops.mesh.primitive_cube_add(location=(-x, -y, -high_height + high_height * 2 + high_top * 2))
            
        cube = bpy.context.object
        
        cube.name = "highblock x: " + str(blockX) + " y: " + str(blockY)
        cube.scale=(1, 1, high_height)
        
        print(str(blockcounter) + " " + cube.name)


def getTile(LowObjectId):
    
    id = LowObjectId - 1
    
    misc_file = open(misc_path, "rb")
    misc_file.seek(49000 + (id * 256))


    misc_data = misc_file.read(256)
    misc_file.close()
    
    return misc_data


def drawTiles(blockX, blockY, LowObjectId, LowBoxTopZ):
    bpy.context.scene.layers[4] = True
    
    tiles = struct.unpack('<64i', getTile(LowObjectId))
    
    for c in range(1, 64 + 1):
        tileX  = math.floor((c - 1) % 8)
        tileY = math.floor((c - 1) / 8)
        
        tile_height = float(tiles[c - 1] / 64)
        low_top = float(LowBoxTopZ / 64)
        
        if tile_height != 0:
            
            global blockcounter
        
            blockcounter = blockcounter + 1
        
            locX = -blockX * 2 + ((-tileX + 3.5) * 0.25)
            locY = blockY * 2 + ((tileY - 3.5) * 0.25)
            bpy.ops.mesh.primitive_cube_add(location=(-locX, -locY, tile_height + low_top * 2))


            cube = bpy.context.object
            cube.name = "block x: " + str(blockX) + " y: " + str(blockY) + " tile x: " + str(tileX) + " y: " + str(tileY)
            cube.scale=(.125, .125, tile_height)
            
            print(str(blockcounter) + " " + cube.name)
            
def generate(x_min, x_max, y_min, y_max):
    
    grid_data = mapdata
    
    position = 0
    
    for i in range(1, 128 * 128 + 1):
        blockX  = math.floor((i - 1) % 128)
        blockY = math.floor((i - 1) / 128)
        
        unknown_00 = struct.unpack("<h", grid_data[position + 0:position + 0 + 2])[0]
        LowBoxTopMod = struct.unpack("<h", grid_data[position + 2:position + 2 + 2])[0]
        LowSidesTextureId = struct.unpack("<h", grid_data[position + 4:position + 4 + 2])[0]
        LowTopTextureId = struct.unpack("<h", grid_data[position + 6:position + 6 + 2])[0]
        HighTextureId = struct.unpack("<h", grid_data[position + 8:position + 8 + 2])[0]
        LowBoxTopZ = struct.unpack("<h", grid_data[position + 10:position + 10 + 2])[0]
        MidBoxBottomZ = struct.unpack("<h", grid_data[position + 12:position + 12 + 2])[0]
        MidBoxTopZ = struct.unpack("<h", grid_data[position + 14:position + 14 + 2])[0]
        LowObjectId = struct.unpack("<h", grid_data[position + 16:position + 16 + 2])[0]
        HighBoxBottomZ = struct.unpack("<h", grid_data[position + 18:position + 18 + 2])[0]
        BlockType = struct.unpack("<h", grid_data[position + 20:position + 20 + 2])[0]
        LowTopShape = struct.unpack("<h", grid_data[position + 22:position + 22 + 2])[0]
        MidBottomShape = struct.unpack("<h", grid_data[position + 24:position + 24 + 2])[0]
        MidSidesTextureId = struct.unpack("<h", grid_data[position + 26:position + 26 + 2])[0]
        MidTopTextureId = struct.unpack("<h", grid_data[position + 28:position + 28 + 2])[0]
        CeilingTextureId = struct.unpack("<h", grid_data[position + 30:position + 30 + 2])[0]
        unknown_16 = struct.unpack("<h", grid_data[position + 32:position + 32 + 2])[0]
        unknown_17 = struct.unpack("<h", grid_data[position + 34:position + 34 + 2])[0]
        unknown_18 = struct.unpack("<h", grid_data[position + 36:position + 36 + 2])[0]
        
        position = position + 38
        
        if blockY <= y_max and blockY >= y_min and blockX <= x_max and blockX >= x_min:


            drawLowBlock(blockX, blockY, LowBoxTopMod, LowSidesTextureId, LowTopTextureId, LowBoxTopZ, LowObjectId, LowTopShape)
        
            drawMidBlock(blockX, blockY, MidBoxBottomZ, MidBoxTopZ, MidBottomShape, MidSidesTextureId, MidTopTextureId, LowSidesTextureId, LowTopTextureId)
            drawHighBlock(blockX, blockY, HighTextureId, HighBoxBottomZ, CeilingTextureId, unknown_16)
        
            if LowObjectId != 0:
                drawTiles(blockX, blockY, LowObjectId, LowBoxTopZ)
    
        
def getmapdata(path):
    
    grid_file = open(path, "rb")
    map_data = grid_file.read()
    
    grid_file.close()
    
    return map_data


    
        




# script start


#bpy.ops.group.create(name="GroupLow")
#bpy.ops.group.create(name="GroupMid")
#bpy.ops.group.create(name="GroupHigh")
#bpy.ops.group.create(name="GroupTiles")
#bpy.ops.object.group_link(group="myGroup")


os.system("cls")


scn = bpy.context.scene


grid_path = os.path.normpath("C:/Grids/Grid00/Grid.dat")
misc_path = os.path.normpath("C:/Grids/Grid00/Misc.dat")
tex_path = "C:/Grids/Grid00/Images/"






blockcounter = 0


global mapdata
mapdata = getmapdata(grid_path)
generate(0, 127, 0, 127)
#generate(42, 62, 49, 67)


print(blockcounter)

Attachments

Grid00.zip (13.6 KB)

Will take a closer look later.

A quick glance showed you using string addition.

Not a real biggy but every little bit.

Also try using api calls rather than operators.

Simple example creating 10,000 copies of the default cube.


import bpy
context = bpy.context
scene = context.scene
cube = bpy.data.objects["Cube"]

for i in range(10000):
    nc = bpy.data.objects.new("nc%d" % i, cube.data)  # nc = cube.copy()
    nc.location = (i % 100, i / 100, 0)
    scene.objects.link(nc)


Note every cube object shares the cube mesh. (use cube.data.copy() in new for ind copy) Try this against using bpy.ops.primitive_add.

Maybe this would help.

The advice about the string thing is a bit outdated. Python has gotten better since the Blender API doc was started :wink:

I might try to see how this work. From a very quick look it will be better to disable “print” commands because constant console output sucks tiny amounts of time and over a period of time it brings to much bloat.

thanks batFINGER. 53.4k cubes in ~14 minutes!