16AnimaliBox.frink

View or download 16AnimaliBox.frink in plain text format


/** This is an attempt to make a completely programmatical box for an object
    extruded from an image.

    The image was extracted from an eBay auction of the puzzle which sold for
    USD 350.

    Original image used for extracting:
     https://frinklang.org/frinksamp/16AnimaliOrig.jpg

    Cleaned-up image:  (you will need this to run this program)
     https://frinklang.org/frinksamp/16AnimaliBW.png
*/

use ConvexHull.frink

wallThickness=1.2 mm
origWidth = 150 mm  // Width I printed the original puzzle at (after cropping)
spacing = 0.15 mm    // The spacing between the puzzle and walls
wallHeight = 7 mm
floorOverhang = 2 mm
floorThickness = 0.8 mm
emboss = 0.7 mm
lidWallHeight = 12 mm + emboss + spacing
lidOverhang = 2 mm
lidThickness = 0.8 mm

// The image file to extract can be loaded from:
// https://frinklang.org/frinksamp/16AnimaliBW.png
img = new image["file:16AnimaliBW.png"]
img.show[]

//img = img.resize[200, undef]
[width, height] = img.getSize[]
println["width is $width, height is $height"]

print["Loading points..."]
points = new set

// We calculate the convex hull (which is O(n log n)) faster by first finding
// only the possible outside points.
LTOR:
for y = 0 to height-1
   for x = 0 to width-1
      if img.getPixelGrayInt[x,y] < 128
      {
         points.put[[x,y]]
         next LTOR
      }

RTOL:
for y = 0 to height-1
   for x = width-1 to 0 step -1
      if img.getPixelGrayInt[x,y] < 128
      {
         points.put[[x,y]]
         next RTOL
      }

DOWN:      
for x = 0 to width-1      
   for y = 0 to height-1
      if img.getPixelGrayInt[x,y] < 128
      {
         points.put[[x,y]]
         next DOWN
      }
      
UP:      
for x = 0 to width-1      
   for y = height-1 to 0 step -1
      if img.getPixelGrayInt[x,y] < 128
      {
         points.put[[x,y]]
         next UP
      }

println["done."]
println["Object has " + length[points] + " points."]

print["Calculating convex hull..."]
hull = ConvexHull.hull[points]
println["done."]
println["Hull has " + length[hull] + " points."]

// Make a Point2DFloatList (polygon) out of the points.
// TODO:  Find a way for ConvexHull to act on a Point2DFloatList?
hullFloat = newJava["frink.graphics.Point2DFloatList"]
for [x,y] = hull
   hullFloat.addPoint[x, -y]

// Calculate the voxel resolution that we printed the original at
bb = hullFloat.getBoundingBox[]
r = (bb.getMaxX[] - bb.getMinX[]) / (origWidth)
println["res is " + format[r, 1/in, 3] + "/in"]

// This is a Point2DFloat
[cx, cy] = hullFloat.getCentroid[].toExpression[]

offset = (spacing + wallThickness/2) r
floorOffset = (spacing + wallThickness + floorOverhang) r
lidWallOffset = (spacing + wallThickness + spacing + wallThickness/2) r
lidOffset = (spacing + wallThickness + spacing + wallThickness/2 + lidOverhang) r

wallPoly =  newJava["frink.graphics.Point2DFloatList"]
floorPoly = newJava["frink.graphics.Point2DFloatList"]
lidWall =  newJava["frink.graphics.Point2DFloatList"]
lid = newJava["frink.graphics.Point2DFloatList"]

points = hullFloat.getPointCount[]
for i=0 to points-1
{
   [px, py] = hullFloat.getPoint[i].toExpression[]
   wallPoly.addPoint[px < cx ? px-offset : px+offset,
                     py < cy ? py-offset : py+offset]
   floorPoly.addPoint[px < cx ? px-floorOffset : px+floorOffset,
                      py < cy ? py-floorOffset : py+floorOffset]
   lidWall.addPoint[px < cx ? px-lidWallOffset : px+lidWallOffset,
                    py < cy ? py-lidWallOffset : py+lidWallOffset]
   lid.addPoint[px < cx ? px-lidOffset : px+lidOffset,
                py < cy ? py-lidOffset : py+lidOffset]
}

// Make wall
v = callJava["frink.graphics.VoxelArray", "strokeZ", [wallPoly, floorThickness r , (wallHeight+floorThickness) r, wallThickness/2 r, true]]

// Make floor
f = callJava["frink.graphics.VoxelArray", "extrudeZ", [floorPoly, 0, floorThickness r]]
v = v.union[f]

// Emboss puzzle on floor
f = callJava["frink.graphics.VoxelArray", "extrudeZ", [img, round[floorThickness r], round[(floorThickness + emboss) r]]]
f.translate[round[cx-12], round[cy+1], 0]
v = v.union[f]

// Make lid wall
lw = callJava["frink.graphics.VoxelArray", "strokeZ", [lidWall, 0, lidWallHeight r, wallThickness/2 r, true]]

// Make lid surface
top = callJava["frink.graphics.VoxelArray", "extrudeZ", [lid, round[lidWallHeight r], round[(lidWallHeight + lidThickness) r]]]
lw = lw.union[top]


v.projectX[undef].show["X"]
v.projectY[undef].show["Y"]
v.projectZ[undef].show["Z"]

lw.projectX[undef].show["Lid X"]
lw.projectY[undef].show["Lid Y"]
lw.projectZ[undef].show["Lid Z"]

filename = "16AnimaliBox.obj"
print["Writing $filename..."]
w = new Writer[filename]
w.println[v.toObjFormat["16AnimaliBox", 1 / (r mm)]]
w.close[]
println["done."]

filename = "16AnimaliLid.obj"
print["Writing $filename..."]
w = new Writer[filename]
w.println[lw.toObjFormat["16AnimaliLid", 1 / (r mm)]]
w.close[]
println["done."]


View or download 16AnimaliBox.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 19097 days, 4 hours, 46 minutes ago.