diff --git a/README.md b/README.md
index 9049c43..d590c12 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ For inserting magnets, check out [the jig](#jig).
- [Plate Wall](#plate-wall)
- [Top plate wall](#top-plate-wall)
- [Bottom plate wall](#bottom-plate-wall)
+ - [Variable wall height](#variable-wall-height)
- [Vertical Screws](#vertical-screws)
- [Screw dimensions](#screw-dimensions)
- [Screw locations](#screw-locations)
@@ -553,6 +554,16 @@ A bottom wall can be used to keep your grid from slipping off e.g. a table, with
+### Variable wall height
+
+The top and bottom wall height can also be adjusted individually for each corner using the `plate_wall_below` and `plate_wall_above` options. This allows you to create shelf-style grids. The following example has a plate wall thickness of 1mm on all sides except south, and a variable height of 20mm for the NW and NE corners.
+
+
+
+
+> [!NOTE]
+> Variable height does not currently render correctly with segmented baseplates.
+
## Vertical Screws
Vertical screws are inserted at cell intersections. They can be used to screw down the plate. Screws can be placed at various positions depending on use case.
diff --git a/docs/images/hscrews.png b/docs/images/hscrews.png
index c85de30..6436b57 100644
Binary files a/docs/images/hscrews.png and b/docs/images/hscrews.png differ
diff --git a/docs/images/wall-bottom.png b/docs/images/wall-bottom.png
index bf1a424..b65eb3d 100644
Binary files a/docs/images/wall-bottom.png and b/docs/images/wall-bottom.png differ
diff --git a/docs/images/wall-top.png b/docs/images/wall-top.png
index 4b0d763..f691ed3 100644
Binary files a/docs/images/wall-top.png and b/docs/images/wall-top.png differ
diff --git a/docs/images/wall-variable.png b/docs/images/wall-variable.png
new file mode 100644
index 0000000..e244522
Binary files /dev/null and b/docs/images/wall-variable.png differ
diff --git a/gridflock.scad b/gridflock.scad
index 6bed610..dd0135b 100644
--- a/gridflock.scad
+++ b/gridflock.scad
@@ -129,6 +129,10 @@ top_chamfer = [0, 0, 0, 0]; // 0.1
plate_wall_thickness = [0, 0, 0, 0]; // 0.5
// Plate wall height. The first value is the height above the plate, the second value the height below the plate
plate_wall_height = [0, 0];
+// Variable wall height, above the plate. Specified for each corner individually. Corners are SW, NW, NE, SE.
+plate_wall_above = [0, 0, 0, 0];
+// Variable wall height, below the plate. Specified for each corner individually. Corners are SW, NW, NE, SE.
+plate_wall_below = [0, 0, 0, 0];
/* [Vertical Screws] */
@@ -890,13 +894,32 @@ module segment_corner(posy=_NORTH, posx=_WEST, connector=[false, false, false, f
/**
* This is an "inverted" quarter-circle that is used to punch out the corner of a rounded rectangle.
*/
-module corner_punch() {
- difference() {
+module corner_punch(size) {
+ if (size.x != 0 && size.y != 0) scale(size) difference() {
square([1, 1]);
translate([1, 1]) circle(r=1);
}
}
+module segment_corner_punch(size, connector, include_wall) {
+ // wall thickness to cut off, by side
+ wall_t = function (side) include_wall || connector[side] ? 0 : plate_wall_thickness[side];
+ // corner radius by side
+ bounds_min = [
+ -size.x/2 + wall_t(_WEST),
+ -size.y/2 + wall_t(_SOUTH)
+ ];
+ bounds_max = [
+ size.x/2 - wall_t(_EAST),
+ size.y/2 - wall_t(_NORTH)
+ ];
+ compute_radius = function (side) plate_corner_radius - wall_t(side);
+ if (!connector[_SOUTH] && !connector[_WEST]) translate(bounds_min) corner_punch([compute_radius(_WEST), compute_radius(_SOUTH)]);
+ if (!connector[_NORTH] && !connector[_WEST]) translate([bounds_min.x, bounds_max.y]) rotate(-90) corner_punch([compute_radius(_NORTH), compute_radius(_WEST)]);
+ if (!connector[_SOUTH] && !connector[_EAST]) translate([bounds_max.x, bounds_min.y]) rotate(90) corner_punch([compute_radius(_SOUTH), compute_radius(_EAST)]);
+ if (!connector[_NORTH] && !connector[_EAST]) translate(bounds_max) rotate(180) corner_punch([compute_radius(_EAST), compute_radius(_NORTH)]);
+}
+
/**
* @Summary Draw the 2D shape of a segment, including rounded corners
* @param size The size of the segment
@@ -906,23 +929,12 @@ module corner_punch() {
module segment_rectangle(size, connector=[false, false, false, false], include_wall=false) {
// wall thickness to cut off, by side
wall_t = function (side) include_wall || connector[side] ? 0 : plate_wall_thickness[side];
- // corner radius by side
- compute_radius = function (side) max(0.01, plate_corner_radius - wall_t(side));
- bounds_offset = function (side) wall_t(side);
- bounds_min = [
- -size.x/2 + bounds_offset(_WEST),
- -size.y/2 + bounds_offset(_SOUTH)
- ];
- bounds_max = [
- size.x/2 - bounds_offset(_EAST),
- size.y/2 - bounds_offset(_NORTH)
- ];
difference() {
- translate(bounds_min) square([bounds_max.x - bounds_min.x, bounds_max.y - bounds_min.y]);
- if (!connector[_SOUTH] && !connector[_WEST]) translate(bounds_min) scale([compute_radius(_WEST), compute_radius(_SOUTH)]) corner_punch();
- if (!connector[_NORTH] && !connector[_WEST]) translate([bounds_min.x, bounds_max.y]) scale([compute_radius(_WEST), compute_radius(_NORTH)]) rotate(-90) corner_punch();
- if (!connector[_SOUTH] && !connector[_EAST]) translate([bounds_max.x, bounds_min.y]) scale([compute_radius(_EAST), compute_radius(_SOUTH)]) rotate(90) corner_punch();
- if (!connector[_NORTH] && !connector[_EAST]) translate(bounds_max) scale([compute_radius(_EAST), compute_radius(_NORTH)]) rotate(180) corner_punch();
+ translate([
+ -size.x/2 + wall_t(_WEST),
+ -size.y/2 + wall_t(_SOUTH)
+ ]) square([size.x - wall_t(_EAST) - wall_t(_WEST), size.y - wall_t(_NORTH) - wall_t(_SOUTH)]);
+ segment_corner_punch(size, connector, include_wall);
}
}
@@ -994,9 +1006,77 @@ module segment(trace=[[1], [1]], padding=[0, 0, 0, 0], connector=[false, false,
};
};
- if (plate_wall_thickness != [0,0,0,0]) translate([0, 0, -_extra_height-plate_wall_height[1]]) linear_extrude(_total_height + plate_wall_height[0] + plate_wall_height[1]) difference() {
- segment_rectangle(size, connector, include_wall=true);
- segment_rectangle(size, connector, include_wall=false);
+ // draw walls
+ if (plate_wall_thickness != [0,0,0,0]) {
+ // we draw an outer polyhedron with the walls, and then cut out an inner polyhedron without those walls (both rounded).
+ // the polyhedron has a bottom and a top surface. the top surface has, from above, this point numbering layout:
+ // ( 3) XX( 7)XXX(11)XX (15)
+ // X X
+ // ( 2) X ( 6) (10) X (14)
+ // X X
+ // ( 1) X ( 5) ( 9) X (13)
+ // X X
+ // ( 0) XX( 4)XXX( 8)XX (12)
+
+ corner_coordinate = function (i, bottom, include_wall=[true, true]) let(
+ side_x = i == 0 || i == 1 ? _WEST : _EAST,
+ side_y = i == 0 || i == 3 ? _SOUTH : _NORTH
+ ) [
+ (size.x/2 - (include_wall.x ? 0 : plate_wall_thickness[side_x])) * (side_x == _WEST ? -1 : 1),
+ (size.y/2 - (include_wall.y ? 0 : plate_wall_thickness[side_y])) * (side_y == _SOUTH ? -1 : 1),
+ (bottom ? -1 : 1) * ((bottom ? plate_wall_below : plate_wall_above)[i] + plate_wall_height[bottom ? 1 : 0] + (bottom ? _extra_height : _profile_height))
+ ];
+ wall_points = function(bottom) [
+ corner_coordinate(0, bottom, [true, true]),
+ corner_coordinate(0, bottom, [true, false]),
+ corner_coordinate(1, bottom, [true, false]),
+ corner_coordinate(1, bottom, [true, true]),
+ corner_coordinate(0, bottom, [false, true]),
+ corner_coordinate(0, bottom, [false, false]),
+ corner_coordinate(1, bottom, [false, false]),
+ corner_coordinate(1, bottom, [false, true]),
+ corner_coordinate(3, bottom, [false, true]),
+ corner_coordinate(3, bottom, [false, false]),
+ corner_coordinate(2, bottom, [false, false]),
+ corner_coordinate(2, bottom, [false, true]),
+ corner_coordinate(3, bottom, [true, true]),
+ corner_coordinate(3, bottom, [true, false]),
+ corner_coordinate(2, bottom, [true, false]),
+ corner_coordinate(2, bottom, [true, true]),
+ ];
+ wall_face_base = [
+ [0, 1, 5, 4],
+ [1, 2, 6, 5],
+ [2, 3, 7, 6],
+ [4, 5, 9, 8],
+ [5, 6, 10, 9],
+ [6, 7, 11, 10],
+ [8, 9, 13, 12],
+ [9, 10, 14, 13],
+ [10, 11, 15, 14]
+ ];
+ wall_bounds = [
+ max(plate_wall_below) + plate_wall_height[0],
+ max(plate_wall_above) + plate_wall_height[1]
+ ];
+ difference() {
+ polyhedron(points = [
+ each wall_points(false),
+ each wall_points(true)
+ ], faces = [
+ each wall_face_base,
+ for (face = wall_face_base) [for (i = [1:len(face)]) face[len(face) - i] + 16],
+ // side faces
+ [0, 16, 17, 18, 19, 3, 2, 1],
+ [3, 19, 23, 27, 31, 15, 11, 7],
+ [15, 31, 30, 29, 28, 12, 13, 14],
+ [0, 4, 8, 12, 28, 24, 20, 16]
+ ]);
+ translate([0, 0, -_extra_height - plate_wall_height[1] - max(plate_wall_below)]) linear_extrude(_total_height + plate_wall_height[0] + plate_wall_height[1] + max(plate_wall_below) + max(plate_wall_above)) {
+ segment_corner_punch(size, connector, include_wall = true);
+ segment_rectangle(size, connector, include_wall = false);
+ }
+ }
}
if (connector_intersection_puzzle) translate([0, 0, -_extra_height]) linear_extrude(height = _total_height) segment_intersection_connectors(true, trace, size, padding, connector);