Saturday, 31 March 2018

688 Stormtrooper Minifigures - behind the scenes...










688 Stormtroopers combines two python scripts that I’ve been working on for quite a while. The first is a reasonably simple mosaic script that I wrote to create Lego mosaics using single coloured studs. The script analyses the pixels in an image the matches the pixel colour to a nearest Lego colour. There are quite a few Lego mosaic building tools out there now and there is even one with a web interface to build your own mosaic.

Here's my version of a Lego mosaic script....

import random,sys,math
from ldraw.colours import *
from ldraw.figure import *
from ldraw.pieces import Group, Piece
from PIL import Image

#From http://stackoverflow.com/questions/34366981/python-pil-finding-nearest-color-rounding-colors
def distance(c1, c2): # Work out the nearest colour 
    (r1,g1,b1) = c1
    (r2,g2,b2) = c2
    return math.sqrt((r1 - r2)**2 + (g1 - g2) ** 2 + (b1 - b2) **2)

#Open the image
im = Image.open('flower.bmp').convert('RGB')
#Get the pixel data
pixels = list(im.getdata())
width, height = im.size
pixels = [pixels[i * width:(i + 1) * width] for i in xrange(height)]
rgbPixels = im.load()

#print pixels #Used for checking

print "Creating Lego..."
print "Width...", width, "  Height...", height
onexonexone = Group(Vector(0, 0, 0), Identity())

#Set up the LDraw file
LDrawFile = open('picture.ldr', 'w')
LDrawFile.write('0 // Mosiac Maker Colour - Neil Marsden'+'\n')

#Set Up the Lego Colour Dictionary
#rgb_code_dictionary = {(27,42,53):26, (242,243,243):1, (163,162,165):194}
#(255, 217, 168):RED,(196, 0, 255):FLESH,(30, 0, 255):PURPLE
#rgb_code_dictionary = {(0,0,0):0, (87,87,87):8, (110,110,110):7, (190,190,190):503, (255,255,255):15} #GreyScaleOnly - Black,DarkGrey,Grey,LightGrey,White
rgb_code_dictionary = {(252, 252, 252): 15, (199, 200, 223): 20, (84, 163, 173): 11, (248, 227, 148): 18, (239, 203, 54):14, (153, 159, 155): 7, (87, 56, 39): 6, (198, 111, 158):5, (185, 230, 11): 27, (0, 84, 189): 1, (239, 111, 93): 12, (37, 121, 61): 2, (5, 19, 29): 0, (74, 157, 73): 10, (144, 56, 119): 26, (249, 149, 170): 13, (178,208, 224): 9, (128, 0, 122): 22, (113, 14, 15): 320, (167, 84, 0): 484, (0, 129, 141): 3, (199, 26, 9): 4, (108, 109, 91): 8, (192, 216, 182): 17, (225, 203, 156): 19, (32, 49, 174): 23,(100, 73, 61): 19,(86, 59, 43): 92,(116, 66, 4): 25}

#set start co-ordinates
x=0
y=-20
z=0
rot = 0
counter = 0
col = 0
#Work through each row of pixels in the image
for i,row in enumerate(pixels): 
#print row
for j,pixel in enumerate(row): # Then work through each pixel in the row
#Get the  RGBA value (colour) of each pixel

col = rgbPixels[j,i]
#Check the pixel colour with the Lego dictionary colours
point = col
colors = list(rgb_code_dictionary.keys())
closest_colors = sorted(colors, key=lambda color: distance(color, point))
closest_color = closest_colors[0]
code = rgb_code_dictionary[closest_color]


#set the Lego colour to the colour code
col = int(code)
#Up the counter and increment the brick position

counter += 1
y += 20
#Create a new row when the counter is greater than the width of the image
if counter > width: 
#print "new row"
LDrawFile.write('0 // NEW ROW '+ str(x/20) + '\n')
x += 20
y = 0
counter = 1

#print i,j,x,y,col,code #Used for checking
#Write the brick to the file
#print "Wrting brick here..."
LDrawFile.write(str(Piece(Colour(col), Vector(x, z, y), Identity().rotate(rot, YAxis), "3024", onexonexone))+'\n')

It's worth remembering that a 50x50 pixel images will be 2500 pixels = 2500 studs so you can very quickly end up with a lot of Lego pieces!   Here's and example of an original image and the resulting mosaic.


My second script was more complex, and uses a library folder of different ldraw models to create a much larger and more complex model (and I also use Tribex's libldrparser.py to parse my initial ldraw library models). I’m a bit of a hack and mash programmer, so I hack bits of code from the internet and mash them all together to get the code to do what I want, I can usually get the output I'm after but sometimes I’m not entirely sure what’s going on!  My early tests used some simple "city" based micro models


So my script reads and stores the data about each piece in the mini-model in the library folder and then randomly moves (on a 4x4 grid) and rotates each library model to create a larger model. So this "city" is created using 11 mini-models.


So the final model, whilst looking complex is built based on the library of mini-models provided.   Here's a link to 5 random city models - the script creates one of these "cites" in less than 1 minute







Or this “library” of flamingos...






I realized that a minifigure is just a library of parts (legs,torso,arms,hands,head etc) and if I used the mosiac image analysis script and combined it with the my ldraw model placement and rotation script that I should be able to create 688 Stormtroopers.  Instead of placing a single stud I could place and rotate a minifigure. In the process I discovered that there was a rounding error in the rotation element of my mini model placement script and I had to fix all the stormtrooper hands manually in Excel - painful! I don’t really want to put my script up till it’s working properly so I’m going to try to fix the rotation error but my understanding of rotation matrices and euler angles is not great and it might take a bit longer to fix.







No comments:

Post a Comment