The other day I was thinking, could I make a certain plot in Lego? Well, Let's start at the start and make a simple n*m lego-brick. The basic measurements are:

brickstyle=Sequence[Red,EdgeForm[AbsoluteThickness[1]]]; gropts=Sequence[Boxed->False,ViewVector->(10{2.4, -1.3, 2.}),ViewAngle->8*Degree]; dims={dimx,dimy,dimz}={8.0,8.0,9.6}/8; (* size of a unit cell in lego-world *) knobd=4.8/8; (* knob diameter *) knobh=1.8/8; (* knob height *) botdi=4.8/8; (* bottom pillar inner diameter*) botdo=6.51/8; (* bottom pillar outer diameter*) wall=1.2/8; (* wall thickness*) thickness=1.0/8; (* top thickness *)

And here a function that will make simple brick:

ClearAll[DrawLego] DrawLego[{nx_Integer,ny_Integer,nz_Integer:1},detailed:(True|False|None):True]:=Module[{ptsout,ptsin,sides,bottom,rimi,rimo,knobs,knobs2}, ptsout=Tuples[{{0,0,0},{nx,ny,nz}dims}\[Transpose]]; ptsin=Tuples[{{wall,wall,0},{nx,ny,nz}dims-{wall,wall,thickness}}\[Transpose]]; sides=If[BooleanQ[detailed],If[TrueQ[detailed],{ptsin,ptsout},{ptsout}],{ptsout}]; sides=GraphicsComplex[#,{Polygon[{1,2,6,5}],Polygon[{3,4,8,7}],Polygon[{1,2,4,3}],Polygon[{5,6,8,7}],Polygon[{2,4,8,6}]}]&/@sides; If[TrueQ[detailed], ptsout=Tuples[{{0,0,0},{nx,ny,0}dims}\[Transpose]]; ptsin=Tuples[{{wall,wall,0},{nx,ny,nz}dims-{wall,wall,thickness}}\[Transpose]]; rimo=ptsout[[1;;;;2]][[{1,3,4,2}]]; rimi=ptsin[[1;;;;2]][[{1,3,4,2}]]; rimo=Partition[rimo,2,1,1]; rimi=Partition[rimi,2,1,1]; bottom=MapThread[Polygon[#1~Join~Reverse[#2]]&,{rimo,rimi}]; ]; If[BooleanQ[detailed], knobs=Tuples[Range[1,#]&/@({nx,ny})]-1/2; knobs=Cylinder[{Append[#{dimx,dimy},nz dimz],Append[#{dimx,dimy},nz dimz+knobh]}&/@knobs,knobd/2]; ]; If[TrueQ[detailed], knobs2=Tuples[Range[1,#]&/@({nx,ny}-1)]; knobs2=Tube[{Append[#{dimx,dimy},0],Append[#{dimx,dimy},nz dimz-thickness]}&/@knobs2,botdo/2]; ]; If[BooleanQ[detailed], If[TrueQ[detailed], {sides,bottom,knobs,{CapForm[None],knobs2}} , {sides,knobs} ] , {sides} ] ] DrawLego[{nx_Integer,ny_Integer,nz_Integer:1},p:{px_,py_,pz_},detailed_:True]:=Translate[DrawLego[{nx,ny,nz},detailed],p{1,1,dimz}-{0.5,0.5,0}]

So we can draw any brick now, at any place, and we have the option to have it detailed or not...

Graphics3D[{brickstyle, DrawLego[{4, 2}]}, Lighting -> "Neutral", Boxed -> False, Axes -> True] Graphics3D[{brickstyle, DrawLego[{4, 2, 1}]}, Lighting -> "Neutral", Boxed -> False, Axes -> True] Graphics3D[{brickstyle, DrawLego[{4, 2, 1}, {1, 1, 1}]}, Lighting -> "Neutral", Boxed -> False, Axes -> True] Graphics3D[{brickstyle, DrawLego[{4, 2, 1}, {1, 1, 1}, False]}, Lighting -> "Neutral", Boxed -> False, Axes -> True] Graphics3D[{brickstyle, DrawLego[{4, 2, 1}, {1, 1, 1}, None]}, Lighting -> "Neutral", Boxed -> False, Axes -> True]

giving:

So now that we can 'plot' any brick we can make a function that will cover a layer in brick-world with bricks of decreasingly smaller sizes iteratively, and will alternately go in the horizontal x and y directions:

ClearAll[TileWithLego,CreateLegos,TransformLego] TileWithLego[slice_List/;MatrixQ[slice],sizes_List,greedy:(True|False),bricks_List:{}]:=Module[{size,bounds,sizex,sizey,shift,dims,dimx,dimy,greedy\[Lambda],stepi,stepj,newarr,part,newbricks}, size={sizex,sizey}=First[sizes]; dims={dimy,dimx}=Dimensions[newarr=slice]; shift=Floor[First[size]/2]; {stepi,stepj}=If[greedy,{1,1},{sizex,sizey}]; greedy\[Lambda]=Boole[!greedy]; newbricks=Reap[Do[ bounds={{j,j+sizey-1},{i,i+sizex-1}}; part=Take[newarr,##]&@@bounds; If[Total[part,2]===sizex sizey, newarr[[Span@@bounds[[1]],Span@@bounds[[2]]]]=0; Sow[bounds]; ] , {j,1,dimy-sizey+1,stepj} , {i,1+greedy\[Lambda] Mod[(j-1)/sizey,2]shift,dimx-sizex+1,stepi} ]][[2]]; If[newbricks==={},newbricks={{}}]; newbricks=bricks~Join~newbricks[[1]]; If[Length[sizes]>1, TileWithLego[newarr,Rest[sizes],greedy,newbricks] , Reverse/@newbricks ] ] CreateLegos[slice_List/;MatrixQ[slice],sizes_List,rotate:(True|False),greedy:(True|False)]:=If[rotate,Reverse/@TileWithLego[slice\[Transpose],sizes,greedy],TileWithLego[slice,sizes,greedy]] TransformLego[slices_List,bricks_List,greedy:(True|False|Automatic)]:=Module[{len,greedies,heights,rotates,brickies,brickspec}, len=Length[slices]; heights=Range[len]; rotates=(#=!=0)&/@Mod[heights,2]; greedies=Switch[greedy,True,ConstantArray[True,len],False,ConstantArray[False,len],_,Switch[len,1,{False},2,{False,False},_,{False,False}~Join~ConstantArray[True,len-2]]]; brickies=MapThread[CreateLegos[#1,bricks,#2,#3]&,{slices,rotates,greedies}]; brickspec=MapThread[{#1[[All,All,2]]-#1[[All,All,1]]+1,{#1[[All,All,1]],ConstantArray[#2,Length[#1]]}\[Transpose]}\[Transpose]&,{brickies,heights}]; brickspec=Catenate[brickspec]; brickspec[[All,2]]=Flatten/@brickspec[[All,2]]; brickies=DrawLego[#1,#2,False (* detailed *)]&@@@brickspec; {Graphics3D[{brickstyle,brickies},Boxed->False,ImageSize->700],brickspec} ]

Let's turn a simple plot in to its Lego-presentation:

heightmap=Table[8+Round[3.5Sin[0.1(0.1x^2+y)]/1.2],{x,-15,24,2},{y,-30,28,2}]; ListPlot3D[%,Mesh->None,InterpolationOrder->0] minmax=MinMax[heightmap]+0.5{-1,1}; slices=UnitStep[heightmap-#+1]&/@Range@@minmax; {gr,bricks}=TransformLego[slices,{{4,2},{3,2},{2,2},{4,1},{3,1},{2,1},{1,1}},Automatic]; gr

giving:

We can try different shapes, namely a sphere:

slices=DiskMatrix[{9/dimz,9,9},20]; {gr,bricks}=TransformLego[slices,{{4,2},{3,2},{2,2},{4,1},{3,1},{2,1},{1,1}},Automatic]; gr

Or a pyramid:

slices=DiamondMatrix[{8,8,8},18][[10;;]]; {gr,bricks}=TransformLego[slices,{{4,2},{3,2},{2,2},{4,1},{3,1},{2,1},{1,1}},Automatic]; gr

The price (according to the online lego shop), would be:

prices = {{2, 4} -> 0.23, {1, 2} -> 0.11, {1, 1} -> 0.08, {1, 3} -> 0.15, {1, 4} -> 0.15, {2, 2} -> 0.15, {2, 3} -> 0.19}; Total[(Sort /@ bricks[[All, 1]]) /. prices] 18.76

We can go now and make some instructions for making this pyramid! Because I can't build something without instructions. Let's create some layer-by-layer instructions:

ClearAll[CreatePage,CreatePages] CreatePage[slices_List,pagenumber_Integer]:=Module[{add,old,image,gr,gr3,opts,width=500}, {add,old}=TakeDrop[slices,-1]; image=(DrawLego[#1,#2,False]&@@@#)&/@slices; add=Flatten[add,1]; add=SortBy[Minus@*First][Reverse/@Tally[Sort/@add[[All,1]]]]; add[[All,1]]=Style[Row[{#,"\[Cross]"}],16,Black]&/@add[[All,1]]; add[[All,2]]=Graphics3D[{brickstyle,DrawLego[#]},gropts,ImageSize->50,Background->None]&/@add[[All,2]]; add=Grid[add]; gr3=Graphics3D[{brickstyle,image},Boxed->False,ViewPoint->(10{2.4, -1.3, 2.}),ImageSize->2width/3]; gr=Graphics[ {LightBlue,Rectangle[{0,0},{1,1.5}], Inset[gr3,Scaled@{0.5,0.5}], Inset[Style[ToString[pagenumber],30,Black],Scaled@{0.5,0.05},Scaled@{0.5,0}], Inset[add,Scaled@{0.05,1},Scaled@{0,1}] }, Axes->False, Frame->False, ImageSize->(width{1,1.5}), PlotRange->{{0,1},{0,1.5}}, AspectRatio->Full ] ] CreatePages[bricks_List]:=Module[{brickslices,out}, brickslices=SortBy[Part[#,1,-1,-1]&][GatherBy[bricks,Part[#,-1,-1]&]]; out = Map[CreatePage[brickslices[[;;#]],#]&,Range[Length[brickslices]]]; Rasterize[#,"Image"]& /@ out ]

So let's call the function:

CreatePages[bricks]

gives me back 8 pages of instructions, including the bricks I need for that 'layer' !

Lastly, let's make one from a 3D model:

brickstyle=Sequence[RGBColor[0.55,0.38,0.19],EdgeForm[AbsoluteThickness[1]]]; bg=ExampleData[{"Geometry3D","Triceratops"},"BoundaryMeshRegion"] bounds={xbounds,ybounds,zbounds}=CoordinateBounds[ExampleData[{"Geometry3D","Triceratops"},"VertexData"]]; rmf=RegionMember[bg]; \[Delta]=2^-3; alldata=Boole[Table[rmf[{x,y,z}],{x,xbounds[[1]],xbounds[[2]],\[Delta]},{y,ybounds[[1]],ybounds[[2]],\[Delta]},{z,zbounds[[1]],zbounds[[2]],\[Delta]}]]; alldata=Transpose[alldata,{3,2,1}]; {gr,bricks}=TransformLego[alldata,{{4,2},{3,2},{2,2},{4,1},{3,1},{2,1},{1,1}},False]; gr

Now feel free to turn your own plots, 3d-scans, and models to Legos!