/** 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."]