Skip to content

Commit

Permalink
Initial pegboard module
Browse files Browse the repository at this point in the history
  • Loading branch information
ringerc committed Oct 21, 2019
0 parents commit 024dd82
Show file tree
Hide file tree
Showing 4 changed files with 571 additions and 0 deletions.
163 changes: 163 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
OpenSCAD library to generate various parametric pegboard-like shapes, i.e.
perforated rectangles.

![Image of the test suite output](pegboard_tests.png)

See [Thingiverse](https://www.thingiverse.com/thing:3926282) for previews,
examples, etc. Customiser enabled, but really, it's not at all hard to just use
OpenSCAD for the job.

## Basic Usage

Download `pegboard.scad` and put it in the same directory as your project, then:

```
use <pegboard.scad>
board_dimensions = [30, 30, 3]; // 30mm x 30mm rectangle, 3mm thick
hole_diameter = 3; // 3mm holes
hole_pitch = 6; // hole centers 6mm apart
pegboard(board_dimensions, hole_diameter, hole_pitch);
```

(It's better to put it on the OpenSCAD library path rather than putting it in
your project directory, but that'll get you started).

## Modules:

* **`pegboard`**: produces a rectangular board with peg holes.

Holes may optionally be less than full depth, and peg pattern may optionally
be a square grid instead of the hexagonally packed default arrangement.
Pitch, peg diameter and edge margins with no pegs are all customisable.

`pegboard(dims, hole_diameter, hole_pitch)`
`pegboard(dims, hole_diameter, hole_pitch, hexpattern, margins, center, hole_depth)`

* **`peg_grid`**: produces a rectangular arrangement of cylindrical pegs, i.e. a
negative pegboard. Used to subtract from other objects. You can clip it etc
too.

`peg_grid(dims, peg_diameter, peg_pitch)`
`peg_grid(dims, peg_diameter, peg_pitch, hexpattern, margins, center)`

## Parameters

All parameters may be passed as positional parameters or by name.

All dimensional units are millimeters.

* **`dims` (3-vector of +number, required)**:

Vector `[x,y,z]` of dimensions of the pegboard, or in the case of the peg
grid, the outer bounds within which the grid should be laid out. Positive
number.

For `peg_grid` the z-dimension is the peg length; for `pegboard` it's the
board thickness and the holes default to fully punching through both sides of
the board. Internally some slop is added when subtracting the pegs to ensure
there are no floating point artifacts here.

* **`hole_diameter` / `peg_diameter` (+number, required)**:

Diameter of the pegs/holes.

Remember printer tolerances, print some tests for fit before you do a big
print run, or print with extra wall thickness and be prepared to do some
filing/drilling. Consider whether you're going for a friction fit or free
movement too.

TODO: support non-cylindrical pegs/holes: basic shapes, cylinders with
friction grip knobs, and `children()` for arbitrary holes.

* **`hole_pitch` / `peg_pitch` (+number, required)**:

Center-to-center spacing of the pegs/holes. The solid space between holes
(the bridge size) will be `hole_pitch` - `hole_diameter`. If set the same as
`hole_diameter`, pegs/holes will touch. If less than `hole_diameter` the
pegs/holes will intersect each other. See examples in test suite.

A sensible initial choice can be `hole_diameter * 2`, which makes the spaces
between pegs (bridges between holes) the same size as the pegs themselves.

* **`hexpattern` (boolean, optional)**:

Arrange the pegs/holes in a tighter packed hexagonal pattern where every second
row's pegs are horizontally offset by `hole_pitch/2` from the pegs on the
adjacent rows. This yields a stronger shape because the pattern is now
tesselated triangles instead of squares. also gives more options for
positioning things in the holes.

hexpattern breaks y-symmetry if an even number of rows are produced.

Default: `true`

* **`margins` (number or 2-vector of number, optional):**

Margins around the edges of the board where no holes should be placed. Can be
a 2-vector `[x,y]` or a scalar. Zero margins makes the hole edges touch the
edge of the square defined by `dims` so it's the default for `peg_grid`,
while `peg_board` defaults to a hole radius worth of margin.

If `center=true` and the hole pitch/diameter doesn't cause the holes to fully
pack the available space, actual effective will be slightly larger because
the hole grid will get centered.

Default: `peg_diameter/2` for `pegboard`, `0` for `peg_grid`.

* **`center` (boolean, optional):**

Arrange the holes / peg grid within the supplied x/y `dims` bounds so that
any extra space not filled by the peg grid is equal on both sides, i.e. the
peg grid is centered. This enables you to safely mirror the peg grid across
the y axis (flip horizontally) and have the holes in the mirrored pieces line
up. It'll be vertically symmetrical too unless it's a hex pattern grid with an
even number of rows.

Default: `true`

## Examples

See the basic test suite in `pegboard_test.scad` for examples.

beam_margins = hole_pitch/2;

See `pegboard.scad`, its tests and its examples for all the things you can do with it.

## License and derivatives

This library is BSD licensed. The license specifies what you *can* do with it,
and nothing in the following restricts the permissions granted by the license.

I have a few requests that I would *appreciate* you following, though the
license does not require you to do so:

1. **Please do not create derivatives of this library on Thingiverse or
elsewhere without linking to the original**. I would prefer that you submit
changes to me for inclusion in the main library instead, if you can make
them work without breaking the existing tests.
2. Credit me as original author if you publish derivatives. Feel free to credit
me for the bugs too ;) .

## Testing

To test changes you make to the library, enable the run_tests parameter in the
file. It'll produce a set of tests. There are a few assertions in there for
basic functionality, but OpenSCAD doesn't make it easy to write useful tests
since it lacks the ability to introspect objects usefully. So you have to
eyeball it and compare the described expected results to what you see on the
test pattern. A sample test result `.stl` and `.png` is included.

## TODO

At some stage I may be motivated to:

* Support non-rectangular shapes
* Support non-cylindrical holes
* Use OpenSCAD's `children()` to let you define your own arbitrary hole modules

... none of which is hard, so if you want it, ask me for advice and I'll point you in the right direction.

Code contributions appreciated.

209 changes: 209 additions & 0 deletions pegboard.scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* pegboard.scad - library for generating pegboard and grids of pegs/holes
*
* Copyright 2019 Craig Ringer <[email protected]>
*
* BSD Licensed
*/

// if negative, print
negative = false;

// Length (mm) of board section (x axis)
p_board_length = 120;
// Width (mm) of board section (y axis)
p_board_width = 21;
// Depth (mm) of board section (z axis)
p_board_thickness = 3;

// Diameter of holes in board
p_hole_diameter = 4.0;
// Hole pitch (distance between centers of holes)
p_hole_pitch = 5.0;
// Should holes be in a packed triangular environment (true) or a regular grid (false)
p_hole_hexpattern = true;

// Margins around the edges of the board where no holes should be placed. Can be a 2-array [x,y] or a scalar. Actual margins will be slightly larger because the hole grid will get centered.
p_beam_margins = p_hole_pitch/2;

/*
* Example usage:
*
* pegboard([board_length, board_width, board_thickness],
* peg_diameter, peg_pitch, hole_hexpattern,
* beam_margins);
*
*/

/* [Hidden] */

module pegboard(dims, hole_diameter, hole_pitch, hexpattern = true, margins = undef, center = true, hole_depth = undef)
{
t_length = dims[0]; // x
t_height = dims[1]; // y
t_thickness = dims[2]; // z

hole_depth =
hole_depth == undef ? t_thickness: hole_depth;

// margins can be an xy array or a scalar, or unset. Negative
// margins are allowed.
margin_x = margins == undef ? hole_diameter/2 :
margins[0] == undef ? margins : margins[0];
margin_y = margins[1] == undef ? margin_x : margins[1];

punchthrough_slop = 0.1;

// Make sure the pegs punch fully out the top, and the bottom if
// we're going through both sides.
peg_zoff = hole_depth >= t_thickness ? -punchthrough_slop : 0;
peg_length = hole_depth + punchthrough_slop - peg_zoff;

difference() {
cube([t_length, t_height, t_thickness]);


// Defend against floating point issues with holes
// that fully punch out of the object by pushing
// them right through. Extra height will be added
// to the hole being punched to compensate.
translate([0,0,peg_zoff])
peg_grid([t_length,t_height,peg_length], hole_diameter, hole_pitch, hexpattern, [margin_x, margin_y], center)

{
// Hack to allow tests etc
assert($strip_ncols != undef);
assert($strip_nrows != undef);
let ($strip_ncols = $strip_ncols,
$strip_nrows = $strip_nrows,
$pegboard_running_tests = true)
children();

}
}
}

module peg_grid(dims, peg_diameter, peg_pitch, hexpattern=true, margins=0, center = true)
{
t_length = dims[0]; // x
t_height = dims[1]; // y
peg_length = dims[2]; // z

// margins can be an xy array or a scalar, or unset. Negative
// margins are allowed.
margin_x = margins == undef ? 0 : margins[0] == undef ? margins : margins[0];
margin_y = margins[1] == undef ? margin_x : margins[1];

assert(t_length > 0);
assert(t_height > 0);
assert(peg_length > 0);
assert(peg_diameter > 0);
assert(peg_pitch >= 0);
assert(margin_x != undef);
assert(margin_y != undef);

/*
* distance between centers of two adjacent holes in the same row.
*/
xpitch = peg_pitch;
/*
* Y gap between lines drawn through centers of holes in two
* adjacent rows. If hexpatterned then there are equilateral
* triangles between centers of each row; the pitch is the
* hypotenuse so the y-gap is the long side of the triangle
* formed between the y-axis and the line between the centers.
*/
ypitch = xpitch * (hexpattern ? sin(60) : 1);
nrows = 1 + max(0, floor(
(t_height - margin_y * 2 - peg_diameter)/ypitch));
ncols = 1 + max(0, floor(
(t_length - margin_x * 2 - peg_diameter)/peg_pitch));

assert(nrows != undef && nrows >= 0);
assert(ncols != undef && ncols >= 0);
assert((xpitch > 0 && ncols > 0) || ncols == 0);
assert((ypitch > 0 && nrows > 0) || nrows == 0);

/*
* Compute bridge size for reporting use. Bridge min
* size should not vary based on hexpattern since we x-offset
* to compensate for the closer rows.
*/
bridge_size = (peg_pitch - peg_diameter);

/*
* Work out how much space is left once we pack in the hole grid
* area and subtract the margins. If we pad the margins by this
* leftover/unused space the grid will be centered.
*/
unused_x = t_length - margin_x * 2 - xpitch * (ncols-1) - peg_diameter;
unused_y = t_height - margin_y * 2 - ypitch * (nrows-1) - peg_diameter;
adjusted_margin_x = margin_x + (center ? unused_x / 2 : 0);
adjusted_margin_y = margin_y + (center ? unused_y / 2 : 0);

echo(str("pegboard or peg_grid: ", t_length, "x", t_height, " margins ", adjusted_margin_x, "x", adjusted_margin_y, " rows ", nrows, " cols ", ncols, , " holes diameter ", peg_diameter, (hexpattern ? " hexpatterned" : " straight"), " at pitch ", peg_pitch, " (bridges ", bridge_size, ")"));

translate([adjusted_margin_x + peg_diameter/2,
adjusted_margin_y + peg_diameter/2, 0])
union() {
for (r = [0 : 1 : nrows - 1])
{
for (i = [0 : 1 : ncols - 1])
{
/*
* For hexpatterned holes, t_ncols is computed to fit
* the non-offset row within the margins. The last
* hole of the offset row won't fit and must be
* omitted.
*/
if (!(hexpattern && r % 2 == 1 && i == ncols - 1))
{
// Handle row hexpattern
translate(
hexpattern ?
/*
* Rows are hexpatterned so the y-offset is
* sin(45deg) of the pitch and every second
* row is inset by half the pitch.
*/
[(r % 2)*xpitch/2 + i*xpitch, r * ypitch, 0]
:
// Not hexpatterned, regular grid
[i*xpitch, r * ypitch, 0]
)
cylinder(d=peg_diameter,
h=peg_length);
}
}
}
};


/*
* OpenSCAD doesn't let us assign values to outer scopes. Nor does
* it let us pass functions as objects. So to allow assertions etc,
* permit definition of child objects here but discard the product.
*
* There must be a better way to do this; what we really want to
* do is pass a function to evaluate, or export some values to
* the outer scope.
*/
let ($strip_ncols = ncols,
$strip_nrows = nrows,
$pegboard_running_tests = true)
children();
}



if (negative) {
peg_grid([p_board_length, p_board_width, p_board_thickness],
p_hole_diameter, p_hole_pitch, p_hole_hexpattern,
p_beam_margins);
}
else
{
pegboard([p_board_length, p_board_width, p_board_thickness],
p_hole_diameter, p_hole_pitch, p_hole_hexpattern,
p_beam_margins);
}
Binary file added pegboard_tests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 024dd82

Please sign in to comment.