B25, Lego Builder


If somebody is interested in I hacked up some test to use blender to build some lego models. The code is mainly proto so …

Simple screenshot from http://odin.wipsl.com/~mikasa/static/LegoThird.png

To start up the addon. Change the directory variable at the LegoBuilder.py to point to the directory you uncompressed the tgz. Then ALT-P or Run Script to run the script. Then in the View3D there should be Lego Colors and Lego Bricks Toolbox. Open the Lego Bricks and expand one category (e.g. bricks) by pressing the small triange icon. Then press the brick name with left mouse and it should be appended to the view3d.

One important thingie: Some blender object must be selected before new bricks can be added with mouse. This issue will be removed once there is PythonTab added (if there ever be one).

AddOn + ldraw library + bricklink XML files from : http://odin.wipsl.com/~mikasa/static/blego.tgz

Not sure if it is really working in other machines than mine. Anyways there is some example core for ldraw importer e.g. if the script wont start up


Einfach SUPER!

Works great, after unpacking and changing the directory name to find the database directory!

I will see what I am able of … I will try to build an object from an last century
Lego-help folder … (if found and succeeded, I will proudly show it here!)

A little bit training will be necessary, I think …

my first ‘auto’ (hi hi) http://petertregarg.host22.com/pmwiki/uploads/Construction/auto1.jpg

Important remark.
The ‘crocodile’ has 13867 vertices, but there are a lot of dubbles, removing them gives
3220, thus much less!
Next observation the ‘nibbles’ are created like cylinders with 16 faces, 16 vertices are connected to the center with ‘tris’! This could (should?) be reduced to only 8 faces using 6 quads and 2 tris. (all faces are NOT connected I think, removing doubles will help already)
Especially the ground plate with a lot of nibbles costs’ a lot of time to ‘generate’ … and
less faces is probably nice too.


You are right the doubles should be removed ! I will add this to the script.

With current importer idea the only way to make the ground plate to be generated faster is to optimize the importer. This because the importer reads the whole ldraw data in and after that it will scale/optimize the data. Most of the time of the loading goes to generating the python list of the object data. I will look at this as well.

The original idea for this builder is to allow user to build up the model and then the software automatically would check the needed amount of bricks and would check the shop where all the bricks would be for sale, and sort all/wanted number using the price as a sort field (www.bricklink.com makes this possible. Also I already have proto for www.bricklink.com HTML parsing).

From technical side there are still quite a lot to do. 1) Some brick has to be selected before the adding works 2) There should be preview for the brick (material preview could be used for this) 3) drag & drop would be neat 4) grid isn’t best at the moment 5) The code is messy and should be cleaned a lot. Anyways there is at least some start already which we can make better.


Use from mathutils Vector and Matrix

import mathutils
import time

def multiply(vec, mat):
    x = vec[0]*mat[0][0] + vec[1]*mat[0][1] + vec[2]*mat[0][2]
    y = vec[0]*mat[1][0] + vec[1]*mat[1][1] + vec[2]*mat[1][2]
    z = vec[0]*mat[2][0] + vec[1]*mat[2][1] + vec[2]*mat[2][2]  
    return x,y,z

blmat = [[0.05, 0.0, 0.0],[0.0, 0.0, 0.05],[0.0, -0.05, 0.0]]
vec = [1.4,1.6,1.7]

ts =time.time()
nr = 1000000
for i in range(nr):    
    x,y,z = multiply(vec,blmat)
te = time.time()-ts    
print("multipy ",nr, " mal ", te)

Vec = mathutils.Vector(vec)
Blmat = mathutils.Matrix(blmat[0],blmat[1],blmat[2])
ts =time.time()
for i in range(nr):    
    x,y,z = Vec*Blmat
te = time.time()-ts    
print("multipy ",nr, " mal ", te)

multipy 10000 mal 0.0600001811981
multipy 10000 mal 0.0319998264313
multipy 100000 mal 0.585000038147
multipy 100000 mal 0.309000015259
multipy 1000000 mal 5.80399990082
multipy 1000000 mal 3.00900006294

using mathutils is about a factor of 2 better :eyebrowlift:

And at the moment you do such a vector matrix multiplication on each face and
the number of faces is ‘rather’ big, isn’t it?


Indeed ! I will switch the vector calculation to faster ones. Also I do have some ideas optimizing the loader a bit.


I get some encoding error with lines like this:
<ITEMNAME>HO Scale, VW Van with White Base and KØLEVOGN Pattern</ITEMNAME>

the Danisch Ø?!! and more such special characters in parts.txt !!!

It is important to use the blego *.dat files OK.
In LegoBrickSelectOperator in def execute
the faces are build …
For each line one face (at least for Lego 3024 my Test-object, the small 1x1 part)
(I extracted using print etc. somewhere in execute the list of floats for a face)

What I did.
I collected all numbers in groups of 3 floats (the coordinate of a point)
Used set (on a list of strings) to remove doubles
giving (finally) the list of all vertices used for this part
Then using index to translate the faces into each a list of vertex-indices used to
use finally in for e.g.: mesh.from_pydata(vertices,edges,faces)
edges is empty [] faces a list of the faces(just created) of the part and vertices the REDUCED list of (meanwhile) Vectors of the points used.
Because of using index, the correspondence between points en faces is ok.
This way doubles are avoided. (Thanks to the set-function with its properties)

Maybe one could get the needed info already a bit earlier (changing your code, which I understood yet nearly, but not yet totally, It would be easier to have an description of how the parts are put together … [ the check of lines 0 = comment 1 = … 2 =… etc revers? the meaning of CW and CCW], helps to shorten reverse engeneering :eyebrowlift2: )


Indeed the set could be used well for optimizing the vertice information. I am checking at the moment what would be the good place to add set generation in my recursive loading function.

The code is now using mathutils and I will check later on the Danish letters.



It seems not necessary to keep the CCW / CW code/flag in the code. The face is anyways double sided so it basically can be removed.

In the first place I used CCW/CW because I thought it must be to avoid “X” kind of faces to appear in the blender, but after better debugging I found out that “X” (indexes in wrong order) faces were actually just bugs in ldraw objects or something else which I just do not understand.


Hi, playing with Lego (code!) is very interesting.
Just building a ‘better’ disc.dat (not 16 tri’s but ontly two and 6 quads …
If finished (and tested) I will show it here …

Understanding ‘goes’ on … :wink:


Even sets functionality is perfect for unique vertice information, without indexing possiblity I am not sure if sets can actually be used in the loading phase. Faces must have at least index to each vertice and with sets that is not possible.

Either there is some nifty way to use sets (which I do not know) or we could use simple <class ‘list’> face and <class ‘list’> vertices

face = [[mat,(ind1, ind2, ind3, ind4)], … ]
vertices = [(x,y,z,), …)

Then just test vertices before adding new one by “(x,y,z) in vertices” if False then add, otherwise do not. This way we wouldn’t have duplicate vertices and also we would have blender compatible memory structure with material information added.

Problem with this is that “(x,y,z) in vertices” probably scans through the whole list and makes the loading phase slow. Tree or somekind of binary tree could be considerable, but I simply do not have time to start studying that at the moment (kids takes all the time).

If PKHG you do have some nifty solution for making the loading phase data structure I am in all ears :slight_smile:


I did a trick with sets … and tomorrow ‘further’ (sports are calling have to end now) bye

No time but look here: for disc.dat …

import bpy
from mathutils import Vector
f0 =[ 0.0,0.0 ,0.0 ,1.,0.0 ,0.0 ,0.9239 ,0.0 ,0.3827]
f1=[ 0.0,0.0 ,0.0 ,0.9239 ,0.0 ,0.3827 ,0.7071 ,0.0 ,0.7071]
f2=[ 0.0,0.0 ,0.0 ,0.7071 ,0.0 ,0.7071 ,0.3827 ,0.0 ,0.9239]
f3=[ 0.0,0.0 ,0.0 ,0.3827 ,0.0 ,0.9239 ,0.0 ,0.0 ,1]
f4=[ 0.0,0.0 ,0.0 ,0.0 ,0.0 ,1.,-0.3827 ,0.0 ,0.9239]
f5=[ 0.0,0.0 ,0.0 ,-0.3827 ,0.0 ,0.9239 ,-0.7071 ,0.0 ,0.7071]
f6=[ 0.0,0.0 ,0.0 ,-0.7071 ,0.0 ,0.7071 ,-0.9239 ,0.0 ,0.3827]
f7=[ 0.0,0.0 ,0.0 ,-0.9239 ,0.0 ,0.3827 ,-1.,0.0 ,-0]
f8=[ 0.0,0.0 ,0.0 ,-1.,0.0 ,-0.0 ,-0.9239 ,0.0 ,-0.3827]
f9=[ 0.0,0.0 ,0.0 ,-0.9239 ,0.0 ,-0.3827 ,-0.7071 ,0.0 ,-0.7071]
f10=[ 0.0,0.0 ,0.0 ,-0.7071 ,0.0 ,-0.7071 ,-0.3827 ,0.0 ,-0.9239]
f11=[ 0.0,0.0 ,0.0 ,-0.3827 ,0.0 ,-0.9239 ,0.0 ,0.0 ,-1]
f12=[ 0.0,0.0 ,0.0 ,0.0 ,0.0 ,-1.,0.3827 ,0.0 ,-0.9239]
f13=[ 0.0,0.0 ,0.0 ,0.3827 ,0.0 ,-0.9239 ,0.7071 ,0.0 ,-0.7071]
f14=[ 0.0,0.0 ,0.0 ,0.7071 ,0.0 ,-0.7071 ,0.9239 ,0.0 ,-0.3827]
f15=[ 0.0,0.0 ,0.0 ,0.9239 ,0.0 ,-0.3827 ,1.,0.0 ,0.0]
allVecs = []
def vecsvoor(f):
    l = len(f) 
    result = [ Vector([ f[i],f[i+1],f[i+2]])for i in range(0,l,3)]

allf = [f0,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15]
for el in allf:    
listvec = [str(el) for el in allVecs]
listset = set(listvec)
reducedAllVecs = [ eval(el) for el in listset]

mesh = bpy.data.meshes.new("disknew")
verts = reducedAllVecs
edges = []

facevecs = [ vecsvoor(el) for el in allf]

#f01n = vecsvoor(f01)
#f01ninds = [ verts.index(el) for el in f01n]
faces = [ [verts.index(el) for el in values] for values in facevecs] 
#faces = [f01ninds]

sc = bpy.context.scene
obj_new = bpy.data.objects.new("Hallo by PKHG",mesh)

If you change the data of 4-4disc.dat into

3 16 0 0 1 -0.3827 0 0.9239 0.3827 0 0.9239
3 16 0 0 -1 -0.3827 0 -0.9239 0.3827 0 -.9239
4 16 -0.3827 0 0.9239 0.3827 0 0.9239 0.7071 0 0.7071 -0.7071 0 0.7071
4 16 -0.7071 0 0.7071 0.7071 0 0.7071 0.9239 0 0.3827 -0.9239 0 0.3827
4 16 -0.9239 0 0.3827 0.9239 0 0.3827 1 0 0 -1 0 0
4 16 -1 0 0 1 0 0 0.9239 0 -0.3827 -0.9239 0 -0.3827
4 16 -0.9239 0 -0.3827 0.9239 0 -0.3827 0.7071 0 -0.7071 -0.7071 0 -0.7071
4 16 -0.7071 0 -0.7071 0.7071 0 -0.7071 0.3827 0 -0.9239 -0.3827 0 -0.9239
You get in place of 16 tris
2tris and 6 quads
:eyebrowlift: (checked in two cases, works, and I think it is ok everywhere … where 4-4disc.dat is used)

Now thinking about direct removing doubles …
Idea: adding a Vertex only if new using the index function and inserting the index in the face …thereafter.

using index is indeed for big parts much more expensive.

But let it do Blender itself :yes:

#around line 327
        bpy.ops.mesh.remove_doubles()               #add this line to your code!

Next, you scale by matrix multiplication of a diagonal matrix

#around line 247
        for c in faces:
            i = c[1]
            for xyz in range(0,len(i),3):
                i[xyz] *= 0.05
                i[xyz + 1] *= -0.05
                i[xyz + 2] *= 0.05    
        # Create new object

Included is a lot of material computation, but I do not see in Blender any result of THAT computations? Where should I look?


What I am currently testing is adding all the optimization to the LDrawFile itself. So basically changing/rewriting the whole file reading class. What is in mind is to split the data to faces and vertices, where vertices would be tuples and faces would be list. once all the tuples are read the whole list would be inserted to set and then indexes would be inserted to faces with material information. This way there would not be big list of all of the vertices but the set would grow depending if there would be new vertices.

At least if looking the time.time() this would be quite a bit faster than handling two lists (like you earlier pointed out).

About materials computation and how they effect => load one minifig head. The materials are mainly used in bricks where there are lots of drawings on it (heads, torsos, slopes with computer buttons).


I think I tryed your suggestion and got worse performence …

the bpy.ops.mesh.remove_doubles() is rather good!


You said about that localization problem where you do get error when parts.txt includes special characters. Do you get that every time when the parts.txt is XML parsed or only when such part is added ?

I am not getting any errors but I think it is because I am using UTF-8 through my linux distribution. Are you using propably Windows ? I should try finding some Windows machine and test then the problem there, but I think easiest way would be just using UTF-8 strings through the parsing phase to avoid the errors.


Oh, forgotten, because I removed e.g Danisch OE etc and esszed (German) … I should learn how to persuade … (the parser?) … to accept UTF-8 …

Umm… Why don’t you just use LDraw/Leocad then import?