BOSL2 icon indicating copy to clipboard operation
BOSL2 copied to clipboard

Add hex_panel to the BOSL2 library

Open rjcarlson49 opened this issue 4 months ago • 12 comments

Proposal For my own library I've created hex_panel(), a module that produces a panel in any shape whose inkier is a honeycomb. I believe it would be a good addition to BOSL2. I've attached a full set of code here that is attachable and complies as best I can with BOSL2 norms. There is documentation and examples.

Background Since I started using OpenSCAD, I've liked using hexagonal structures for lightness and strength. And I think it often looks cool. I have gone through several iterations of my own code, but I was never really pleased with it. A few months ago someone posted an elegant little piece of code that imposed a honeycomb on any shape. I'm afraid I don't remember who posted it. It worked and I did not have the motivation to figure out how regions work, so I have just used it as it came to me.

There were various problems with that code, especially that it did not produce a panel with the expected dimensions. Because it used stroke, the width of the stroke was added to the shape. I began playing with it to produce something easier to use. This is the final result.

Features The main input is shape. Shape can be a 3D vector. Then you have a simple rectangular panel, that lies in the XY plane and z defines the thickness. The frame argument defines width of the solid outer frame of the panel.

Shape can also be a non crossing path that defines a polygon. Concave shapes are allowed. If shape is a path, the height of the panel must be given in the h argument.

Rectangular panels, i.e. shape is a 3D vector, can be beveled, always at 45 degrees. This makes it easy to combine panels cleanly into boxes or whatever. RIGHT, LEFT, FRONT and BACK represent the 4 possible edges. Add BOTTOM to an edge to make a reverse bevel. With some work, the bevel angle could be made an argument, but I don't see the usefulness.

I've started adding a 'help' feature to my library. When the first argument is "help", the module prints a synopsis and halts with an assert failure. If you don't care for that, delete it! I've found it to be a quick way to remind myself of argument lists.

Example Code

include <BOSL2/std.scad>;
include <BOSL2/rounding.scad>;

$fn = 180;
showRect = false;
showShapes = false;
showBevels = false;
showPoly = false;
showJoin = false;
showHelp = false;

if (showHelp) {
    hex_panel("help");
}
if (showShapes) {
    s0 = glued_circles(d=50, spread=50, tangent=30);
    s1 = circle(30);
    zdistribute(spacing = 20){
        hex_panel(s0, h = 10, frame = 5); 
        hex_panel(s1, h = 10, frame = 5);
    }
}
if (showPoly) {
    s2 = [[0, -40], [0, 40], [30, 20], [60, 40], [60, -40], [30, -20]];
    s1 = [[0, -40], [0, 40], [60, 0]];
    s0 = [[0, -40], [0, 70], [60, 0], [80, 20], [70, -20]];
    zdistribute(spacing = 20){
        hex_panel(s2, h = 10, frame = 5); 
        hex_panel(s0, h = 10, frame = 5); 
        hex_panel(s1, h = 10, frame = 5); 
    }
}
if (showJoin) {
    hex_panel([50, 100, 10], frame = 5, bevel = [FWD,  BACK], anchor = BACK + RIGHT + BOTTOM, orient = RIGHT);
    hex_panel([100, 50, 10], frame = 5, bevel = [LEFT, RIGHT], anchor = FWD + LEFT + BOTTOM, orient = FWD);
}
if (showRect) {
    zdistribute(spacing = 20){
        hex_panel([50, 100, 5], frame = 5);
        hex_panel([50, 100, 5], frame = 5, wall = 5);
        hex_panel([50, 100, 5], frame = 5, spacing = 20);
        hex_panel([50, 100, 5], frame = 10, spacing = 20, wall = 4);
    }
}
if (showBevels) {
    zdistribute(spacing = 20){
        hex_panel([50, 100, 10], frame = 5, bevel = []);
        hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT]);
        hex_panel([50, 100, 10], frame = 5, bevel = [FWD,  BACK]);
        hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT, FWD, BACK]);
        hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT, FWD+BOTTOM, BACK+BOTTOM]);
    }
}

t = .1;
t2 = 2 * t;
tiny = 1/128;

function _honeycomb(shape, spacing=10, hex_wall=1) =
    let(
        hex = hexagon(id=spacing-hex_wall, spin=180/6),
        bounds = pointlist_bounds(shape),
        size = bounds[1] - bounds[0],
        hex_rgn2 = grid_copies(spacing=spacing, size=size, stagger=true, p=hex),
        center = (bounds[0] + bounds[1]) / 2,
        hex_rgn = move(center, p=hex_rgn2),
        ihex_rgn = intersection(hex_rgn, shape),
        out_rgn = difference(shape, ihex_rgn)
    ) out_rgn;

module _honeycomb(shape, spacing=10, hex_wall=1) {
    rgn = _honeycomb(shape, spacing=spacing, hex_wall=hex_wall);
    region(rgn);
}

function _is_in(target, vector) = 
         let(r = search([target], vector)) 
         len(r) == 0 ? false : r[0] == [] ? false : true;

// Module: hex_panel()
// Usage:
//   hex_panel(shape, frame, [h,] [wall = 1.5,] [spacing = 10,] [bevel,] [anchor = CENTER,] [orient = UP,] [spin = 0])
// Description:
//   Produces a panel with a honeycomb interior. The panel consists of a frame containing
//   a honeycob interior. The frame is laid out in the XY plane with the honeycob interior 
//   and then extruded to the height h. The shape argument defines the outer bounderies of
//   the frame.
//   .
//   The simplest way to define the frame shape is to give a 3D vector in the shape argument.
//   In this case, x and y define a rectangle which is the frame and z is the height (h). 
//   The h argument is ignored in this case.
//   . 
//   The other option is to provide a 2D path as the shape argument. The path must be 
//   non-crossing and is assumed to be closed. 
//   .
//   Enter "help" as the shape argument to echo the synopsis to the console.
//   .
// Arguments:
//   shape = either a path as defined in BOSL2 or a 3D vector in the form [x, y, z]. 
//   if shape == "help" an assert will fail and a synopsis of the module is printed.
//   frame = width of the frame around the honeycomb, required
//   h = thickness of the panel, required when shape is a path.
//   ---
//   wall = thickness of the walls in the honeycomb, Default: 1.5
//   spacing = size of the hex cells in the honeycomb: Default: 10
//   bevel = vector of vectors. Each vector entry must be one of the BOSL2 directional
//   defines RIGHT, LEFT, BACK, or FRONT. BOTTOM may be added to produce a bevel
//   on the bottom of the panel, as in BACK+BOTTOM. Entries MUST be separated by commas.
//   Bevel is allowed only when shape is a simple 3D vector, [x, y, z]. Default: [].
//   anchor = the usual BOSL2 anchors
//   orient = the usual BOSL2 orientations
//   spin = the usual BOSL2 spin
// Example: Rectangular Panels
//   zdistribute(spacing = 20){
//     hex_panel([50, 100, 5], frame = 5);
//     hex_panel([50, 100, 5], frame = 5, wall = 5);
//     hex_panel([50, 100, 5], frame = 5, spacing = 20);
//     hex_panel([50, 100, 5], frame = 10, spacing = 20, wall = 4);
//   }
// Example: Bevel Combinations
//   zdistribute(spacing = 20){
//     hex_panel([50, 100, 10], frame = 5, bevel = []);
//     hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT]);
//     hex_panel([50, 100, 10], frame = 5, bevel = [FWD,  BACK]);
//     hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT, FWD, BACK]);
//     hex_panel([50, 100, 10], frame = 5, bevel = [LEFT, RIGHT, FWD+BOTTOM, BACK+BOTTOM]);
//  }
// Example: Shapes
//   s0 = glued_circles(d=50, spread=50, tangent=30);
//   s1 = circle(30);
//   zdistribute(spacing = 20){
//     hex_panel(s0, h = 10, frame = 5); 
//     hex_panel(s1, h = 10, frame = 5);
//   }

module hex_panel(
    shape,
    frame, 
    h,
    wall = 1.5, 
    spacing = 10,
    bevel = [],
    anchor = CENTER, 
    orient = UP, 
    spin = 0) 
{
    //d00 = echo("Frame ", frame, "  h ", h, "  shape ", shape);
    d0 = assert(shape!="help", "\n\nhex_panel(shape, frame, [h,] \n    [wall = 1.5,] [spacing = 10,] [bevel,]\n    [anchor = CENTER], [orient = UP,] [spin = 0])\n\n");

    assert(!is_undef(frame) && frame > 0, "frame must be > 0");
    assert(is_path(shape) || is_vector(shape, 3), "shape must be a path or a 3D vector");
    assert(len(bevel) == 0 || is_vector(shape, 3), "bevel must be used only on rectangular panels");
    assert(is_path(shape) || (shape.x > 0 && shape.y > 0 && shape.z > 0), 
           "shape must be a path or a 3D vector");
    assert(!(_is_in(FRONT, bevel) && _is_in(FRONT+BOTTOM, bevel)), "conflicting FRONT bevels");
    assert(!(_is_in(BACK,  bevel) && _is_in(BACK+BOTTOM,  bevel)), "conflicting BACK bevels");
    assert(!(_is_in(RIGHT, bevel) && _is_in(RIGHT+BOTTOM, bevel)), "conflicting RIGHT bevels");
    assert(!(_is_in(LEFT,  bevel) && _is_in(LEFT+BOTTOM,  bevel)), "conflicting LEFT bevels");
    shp = is_path(shape) ? shape : square([shape.x, shape.y], center = true);
    ht = is_path(shape) ? h : shape.z;
    
    //d1 = echo("is_path(shape) ", is_path(shape), " Shape ", shp);
    
    bounds = pointlist_bounds(shp);
    sizes = bounds[1] - bounds[0]; // [xsize, ysize]
    //d2 = echo("sizes, h ", sizes, ht);
    centers = (bounds[0] + bounds[1]) / 2;
    
    assert(frame*2 + spacing < sizes[0], "There must be room for at least 1 cell in the honeycomb");
    assert(frame*2 + spacing < sizes[1], "There must be room for at least 1 cell in the honeycomb");
    
    attachable(anchor = anchor, spin = spin, orient = orient, size = [sizes.x, sizes.y, ht]) {
        //d = echo("Bounds, Centers, Sizes ", bounds, centers, sizes);
        if (len(bevel) > 0) {
            intersection() {
                union() {
                    left(centers.x) fwd(centers.y) down(ht/2) 
                    linear_extrude(height = ht) {
                        _honeycomb(shp, spacing = spacing, hex_wall = wall);
            
                        offset_stroke(shp, width=[-frame, 0], closed=true);
                    }

                    for (b = bevel) _bevelWall(shape, b);
                }
                down(ht/2)
                _bevelSolid(shape, bevel);
            }
        } else {
            left(centers.x) fwd(centers.y) down(ht/2) 
            linear_extrude(height = ht) {
                _honeycomb(shp, spacing = spacing, hex_wall = wall);
    
                offset_stroke(shp, width=[-frame, 0], closed=true);
            }
        }
        children();
    } // attachable
}

module _bevelSolid(shape, bevel) {
    tX = _is_in(RIGHT,          bevel) ? -shape.z : 0;
    tx = _is_in(LEFT,           bevel) ?  shape.z : 0;
    tY = _is_in(BACK,           bevel) ? -shape.z : 0;
    ty = _is_in(FRONT,          bevel) ?  shape.z : 0;
    bX = _is_in(RIGHT + BOTTOM, bevel) ? -shape.z : 0;
    bx = _is_in(LEFT  + BOTTOM, bevel) ?  shape.z : 0;
    bY = _is_in(BACK  + BOTTOM, bevel) ? -shape.z : 0;
    by = _is_in(FRONT + BOTTOM, bevel) ?  shape.z : 0;

    pathB = [[ shape.x/2 + bX,  shape.y/2 + bY], [ shape.x/2 + bX, -shape.y/2 + by], 
             [-shape.x/2 + bx, -shape.y/2 + by], [-shape.x/2 + bx,  shape.y/2 + bY]];
    pathT = [[ shape.x/2 + tX,  shape.y/2 + tY], [ shape.x/2 + tX, -shape.y/2 + ty], 
             [-shape.x/2 + tx, -shape.y/2 + ty], [-shape.x/2 + tx,  shape.y/2 + tY]];
    
    hull() {
        down(tiny) linear_extrude(tiny) polygon(pathB);
        up(shape.z-tiny) linear_extrude(tiny) polygon(pathT);
    }
}

module _bevelWall(shape, bevel) {

    l = bevel.y != 0 ? shape.x : shape.y;
    d = bevel.y != 0 ? shape.y : shape.x;
    zr = bevel.y == -1 ? 180 
       : bevel.y ==  1 ? 0 
       : bevel.x == -1 ? 90 
       : bevel.x ==  1 ? 270 
       : undef;
    xr = bevel.x != 0 && bevel.z < 0 ? 180 : 0;
    yr = bevel.y != 0 && bevel.z < 0 ? 180 : 0;
    
    path = [[-1, 0], [0, 0], [-shape.z, -shape.z], [-shape.z-1, -shape.z]];

    xrot(xr) 
    yrot(yr)
    zrot(zr)
    down(shape.z/2) 
    back(d/2) 
    right(l/2) 
    zrot(90) 
    xrot(-90)
    linear_extrude(l) polygon(path);
}

Rectangular panels with various bevel combinations. bevels

rjcarlson49 avatar Feb 16 '24 22:02 rjcarlson49