TL;DR?
Full code here (this post is a bit silly)
And now for something completely different.
This site’s chief weapon is R. R and SQL. Two, this sites two chief weapons are R, SQL, and JavaScript. THREE! OUR THREE chief weapons are R, SQL, JavaScript and an almost fanatical devotion to Monty Python.
I’ll start again shall I. I wasn’t expecting the Spanish Inquisition.
That’s gona cause some confusion. Mind if we call you Bruce?
Lego bricks can be difficult to describe. I remember as a kid building with friends, that the word I used to describe pieces being different to those they used. It is easy enough to settle on a common set of terms, or a schema for part names however I want my app to be as frictionless as possible. I decided that to avoid ambiguity any description of a piece must be accompanied by a picture.
Python
In modern projects it’s difficult to not use Python. In this project I use Python to generate 3D models of Lego pieces in Blender. I have used Blender a lot for creating video game assets. It is a 3D modelling application written in Python and highly extensible with it. It has a large active community and importantly, is free!
By programmatically generating piece geometry, I am able to create a set of consistent professional looking renders. At the same time, as the images are at a consistent scale I can take automated measurements of the pieces to generate some relative size metadata which may be valuable in the model training step.
What… is your name? What… is your quest? What… is the air-speed velocity of an unladen swallow?
Blender includes the “bpy” python module to allow user scripts to interface with the 3D workspace. With this we can define a function to add geometry to a new mesh object to add to the workspace.
I want one function to consistently generate pieces of different sizes. I take the class, OBJECT_OT_add_object, containing these variables to allow the user to easily change these in Blender’s GUI
studs_x = bpy.props.IntProperty(default=3)
studs_y = bpy.props.IntProperty(default=2)
plates_high = bpy.props.IntProperty(default=3)
plate_verts = bpy.props.BoolProperty(default=False)
tile = bpy.props.BoolProperty(default=False)
Creating meshes in Blender is as simple as creating a list of vertices, and then specifying lists of edges and/or faces by which vertices make up the edge/face. This is simple when defining a single primitive but becomes complicated when you want to programmatically generate geometry from a number of user-controllable inputs.
Ministry of Silly Walks
An un-ambiguous method for calculating the position of a vertex in the list is required. For repeatability I choose to “walk” around the mesh a layer at a time. We can then walk around again adding faces.
It’s not particularly silly, is it? I mean, the right leg isn’t silly at all and the left leg merely does a forward aerial half turn every alternate step. This is represented in the below code snippets:
#add verts
for lyr_n in range(0, layers_to_do):
#walk the perimiter
for stu_y in range(0, self.studs_y):
verts.append(Vector((
(offs_x),
(offs_y+stu_y*width_per_stud),
(lyr_n*plate_height))))
for stu_x in range(0, self.studs_x):
verts.append(Vector((
(offs_x+stu_x*width_per_stud),
(offs_y+width_per_stud*self.studs_y),
(lyr_n*plate_height))))
for stu_y in reversed(range(0, self.studs_y)):
verts.append(Vector((
(offs_x+width_per_stud*self.studs_x),
(offs_y+(stu_y+1)*width_per_stud),
(lyr_n*plate_height))))
for stu_x in reversed(range(0, self.studs_x)):
verts.append(Vector((
(offs_x+(stu_x+1)*width_per_stud),
#(offs_y+width_per_stud*self.studs_y),
(offs_y),
(lyr_n*plate_height))))
#add faces
#if this is the 2nd or higher layer, add faces to layer below
for lyr_n in range(1, layers_to_do):
for v_n in range(0, verts_per_layer):
tl = lyr_n*verts_per_layer+v_n
tr = tl+1
bl = (lyr_n-1)*verts_per_layer+v_n
br = bl+1
#if this is the end of the layer, need to adjust to wrap round
if (v_n+1) % verts_per_layer == 0:
tr = tr - verts_per_layer
br = br - verts_per_layer
faces.append([tl, tr, br, bl])
The full script creates consistent starting points, from there a small amount of 3D modelling can be done to create less-basic parts.
I’d like to have an argument please.
A subtlety easily miss-able using Blender’s built-in script editor is that the Vector type form bpy does not take three inputs but instead one tuple which itself contains the x,y,z inputs. If you get the error:
TypeError: mathutils.Vector(): more than a single arg given
then the constructor is looking for a good argument. It isn’t just saying “no it isn’t”. yes it is. No it isn’t. To fix, put the x,y,z arguments in a tuple by placing a set of brackets around them.
Example:
#this will fail, three arguments:
verts.append(Vector(1,2,3))
#this will work, one argument which has three elements:
verts.append(Vector((1,2,3)))
Go on, go on. Do the punchline.
No, no this is silly. No, the whole premise is silly and it’s very badly written. I’m the senior officer here and I haven’t had a funny line yet. So I’m stopping it.