/** This is an attempt to make a completely programmatical box for an object extruded from an image. It calculates a convex hull for the puzzle and makes a tight-fitting box. It also embosses a thin version of the puzzle onto the bottom of the box as a guide. This is used to make a box for this puzzle: 16Pesci.frink Original image used for extracting: https://frinklang.org/frinksamp/16PesciOrig.jpg Cleaned-up image: (you will need this to run this program) https://frinklang.org/frinksamp/16PesciBW.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 and lid 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/16PesciBW.png img = new image["file:16PesciBW.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], 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 = "16PesciBox.obj" print["Writing $filename..."] w = new Writer[filename] w.println[v.toObjFormat["16PesciBox", 1 / (r mm)]] w.close[] println["done."] filename = "16PesciLid.obj" print["Writing $filename..."] w = new Writer[filename] w.println[lw.toObjFormat["16PesciLid", 1 / (r mm)]] w.close[] println["done."]