A dive into the Python code.


As I’ve written before: loading and displaying model on a isometric game level is a first goal. So let’s have a look into Python scripts loading Blender cubic models and into resultant JSON files structure. The code is here https://github.com/adamskopl/cubicforest/tree/master/cubicLoader.

The code’s structure reflects the structure of the model:

  • cubicLoader.py
  • cubicModel.py
  • cubicCubesGroup.py
  • cubucCube.py

Loader makes an instance of a Model and invokes loadGroups.

    def loadGroups(self):
        Fill groups list with CubesGroup objects.
        scene = bpy.data.scenes[0]
        sceneGroups = bpy.data.groups
        for sGroup in sceneGroups:
            groupName = sGroup.name
            groupCubes = sGroup.objects
            cubicGroup = cubicCubesGroup.CubesGroup(groupName)

Having in mind that maybe I’ll want to divide model into groups (e.g. for simple animation purposed), loading starts with reading them:

sceneGroups = bpy.data.groups

The bpy object is generated by Blender. Next the group loads its cubes and the result is appended to the groups collection.

    def loadCubes(self, groupCubes):
        Fill cubes list with objects from groupCubes list.
        for groupCube in groupCubes:
            cube = cubicCube.Cube(groupCube.name)

The Cube class is filled with data:

  • position
  • rotation
  • color

The location, rotation_euler and data.materials[0].name are provided by Blender. I’m saving material’s name, which will be later used to indicate the texture needed to render cube.

class Cube:
    Class defining model's cube.

    Blender's equivalent: Cube Object.
    def __init__(self, name):
        """ x """
        self.name = name
        self.pos = Vector()
        self.rot = Euler()

    def setPos(self, pos):
        self.pos = pos

    def setRot(self, rot):
        self.rot = rot

    def setCol(self, colName):
        self.colName = colName

Looks simple but it took some time to figure out how to extract needed data from the Blender’s scene.

Next step is to save the model as a JSON file:

    def saveModel(self, fStart, fEnd):
        config = configparser.ConfigParser()
        config.read(currentDir + "/config.ini")
        modelJson = self.model.toJson(fStart, fEnd)
        jsonData = simplejson.dumps(modelJson)
        fd = open(config['Paths']['writePath'] + '/' + self.modelName + ".json", "w")
    def toJson(self, fStart, fEnd):
        scene = bpy.data.scenes[0]
        modelDict = dict()
        framesList = list()
        for frameNum in range(fStart, fEnd+1):
            frameDict = dict()
            groupsList = list()
            for frameGroup in self.groups:
                groupDict = frameGroup.toJson()
            frameDict["number"] = frameNum
            frameDict["groups"] = groupsList
        modelDict["name"] = self.name
        modelDict["frames"] = framesList
        return modelDict
    def toJson(self):
        groupDict = dict()
        cubesList = list()
        for c in self.cubes:
            cubeDict = c.toJson()
        groupDict["name"] = self.name
        groupDict["cubes"] = cubesList
        return groupDict
    def toJson(self):
        cubeDict = dict()

        posDict["x"] = self.pos.x
        posDict["y"] = self.pos.y
        posDict["z"] = self.pos.z

        rotDict["x"] = self.rot.x
        rotDict["y"] = self.rot.y
        rotDict["z"] = self.rot.z

        cubeDict["name"] = self.name
        cubeDict["pos"] = posDict
        cubeDict["rot"] = rotDict
        cubeDict["colName"] = self.colName
        return cubeDict

Cubes are converted to JSON, added to the groups, which are also converted to JSON and passed to the model. Every part has its own toJson function.

Here’s the result file with data describing a model:

  • named ‘prize’
  • composed of 3x3x3=27 cubes
  • one frame, one group
  • all cubes are ‘red’
{"name": "prize", "frames": [{"number": 1, "groups": [{"name":
"Group", "cubes": [{"name": "Cube.027", "colName": "red", "pos": {"z":
7.0, "x": -1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}},
{"name": "Cube.026", "colName": "red", "pos": {"z": 7.0, "x": 1.0,
"y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.025", "colName": "red", "pos": {"z": 7.0, "x": 0.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.024",
"colName": "red", "pos": {"z": 6.0, "x": 0.0, "y": 0.0}, "rot": {"z":
0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.023", "colName": "gray",
"pos": {"z": 6.0, "x": 1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0,
"y": 0.0}}, {"name": "Cube.022", "colName": "red", "pos": {"z": 6.0,
"x": -1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.021", "colName": "red", "pos": {"z": 6.0, "x": 2.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.020",
"colName": "red", "pos": {"z": 6.0, "x": -2.0, "y": 0.0}, "rot": {"z":
0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.019", "colName": "red",
"pos": {"z": 5.0, "x": -2.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0,
"y": 0.0}}, {"name": "Cube.018", "colName": "red", "pos": {"z": 4.0,
"x": -2.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.016", "colName": "red", "pos": {"z": 5.0, "x": 2.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.015",
"colName": "red", "pos": {"z": 4.0, "x": 2.0, "y": 0.0}, "rot": {"z":
0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.013", "colName": "red",
"pos": {"z": 3.0, "x": 1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0,
"y": 0.0}}, {"name": "Cube.012", "colName": "red", "pos": {"z": 3.0,
"x": -1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.011", "colName": "red", "pos": {"z": 4.0, "x": -1.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.010",
"colName": "red", "pos": {"z": 5.0, "x": -1.0, "y": 0.0}, "rot": {"z":
0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.009", "colName": "red",
"pos": {"z": 4.0, "x": 1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0,
"y": 0.0}}, {"name": "Cube.008", "colName": "red", "pos": {"z": 5.0,
"x": 1.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.007", "colName": "brown", "pos": {"z": 2.0, "x": 0.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.006",
"colName": "red", "pos": {"z": 3.0, "x": 0.0, "y": 0.0}, "rot": {"z":
0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.005", "colName": "red",
"pos": {"z": 5.0, "x": 0.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0,
"y": 0.0}}, {"name": "Cube.004", "colName": "red", "pos": {"z": 4.0,
"x": 0.0, "y": 0.0}, "rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name":
"Cube.003", "colName": "brown", "pos": {"z": 0.0, "x": 0.0, "y": 0.0},
"rot": {"z": 0.0, "x": 0.0, "y": 0.0}}, {"name": "Cube.002",
"colName": "brown", "pos": {"z": 1.0, "x": 0.0, "y": 0.0}, "rot":
{"z": 0.0, "x": 0.0, "y": 0.0}}]}]}]}

written in Emacs with org2blog mode

3 Replies to “A dive into the Python code.”

  1. There are a few places in the Python scripts which would greatly benefit from refactoring. Keywords: “with statement”, “pep8” (python method naming conventions differ from Java ones)

    Also naming a method toJson when it returns… a Python dictionary(?) is probably a code smell.

    Shortening field names is okay but only if they are very concrete – “pos” usually means “position”, “rot” usually means “rotation”, but “colName” could mean “colorName” or “columnName” which can be problematic. Why not just “color” – it takes less space than “colName”?

    Do I understand correctly that the produced JSON is loaded by the browser? If yes then you should really find some minification method for it – it’s just one small model and it takes a lot of bytes when there are many repeating elements – the color, the name, even the position can be compressed (eg. when you assume that cubes in models take only discrete positions – integral ones).

  2. Yes, I totally agree that this code looks bad. I wrote it a long time ago, but Your comments have shown for me even more mistakes. And I won’t touch, because it works and there’s lot of other things to do… Although never mind: I’ll make some fixes just to feel better o_O

    Hah… yes, simple ‘toDict’ would fix the confusion.
    I would like to defend a ‘colorName’ (agreed that colName is bad) as it indicates it’s a descriptive name and not a RGB for example.

    Yes: some compression will be nice. And but still I will make that in the end. Maybe even choose other format or write a compressing script.

    Anyway: thanks for the tips. Useful and very appreciated as usual.

Leave a Reply

Your email address will not be published. Required fields are marked *