BOSL2
BOSL2 copied to clipboard
Add hex_panel to the BOSL2 library
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.