Read this model as text
A wall holder for two paper rolls above each other. Two identical brackets screw to the wall, each with two arms that carry an open-top slot. A rod goes through the core of the roll and drops into the slots from above, guided by a funnel at the top of each slot. Printed end caps keep the rod from wandering sideways. Sized so a bracket for a 100 mm roll prints on a 180 mm bed (Bambu A1 mini).
The image shows the solid OpenSCAD renders at part assembly · roll_d 100 · paper_width 1000 · rod_d 20 · rod_fit 0.6 · width 22 · back_t 8 · under 12 · lip 10 · rise 6 · funnel 5 · wall_gap 10 · roll_gap 10 · screws 2 · screw_d 4.5 · head_d 9 · cap_wall 5 · cap_depth 12 · cap_fit 0.3 · cap_screw_d 3.5 · round_r 3 · chamfer 1.2 · smoothness 48 · color_bracket White · color_cap DimGray.
Double wall holder for paper rolls
mick/double-roll-holder@1.0.0
Report
For a legal or policy problem: a license violation, personal data, somebody else’s work republished as their own. A model that prints badly is a comment, not a report.
Parameters
from sourceDescription
What is this
A wall holder for two paper rolls above each other. Two identical brackets screw to the wall, each with two arms that carry an open-top slot. A rod goes through the core of the roll and drops into the slots from above, guided by a funnel at the top of each slot. Printed end caps keep the rod from wandering sideways. Sized so a bracket for a 100 mm roll prints on a 180 mm bed (Bambu A1 mini).
What you use it for
Keeping two rolls of brown paper (kraft paper) ready on the wall, for workshops, wrapping or the workbench. Changing a roll takes no tools: lift the rod out, slide the new roll on and drop it back in. The lower roll is lifted in over the front lip, below the upper roll, and the spacing between the arms leaves room for exactly that.
Design notes
The bracket is one 2D profile of axis-aligned squares: a back plate and two arms, each with a slot cut from the top. Rounding never shapes the design, it only polishes it: a closing and opening offset pair rounds every profile corner at round_r, and the extrusion gets a 45 degree chamfer on both faces, built from thin slices so it stays fast to render. The chamfer on the bed side also takes care of elephant foot.
Print the bracket on its side. The layers then run along the arms instead of across them, which is what makes the cantilever strong. Left and right are the same part, print it twice.
The slot sits at wall_gap plus half of roll_d from the wall, so a full roll just clears the wall. The arm spacing is roll_d plus the slot depth plus roll_gap: enough for the lower roll to pass over its lip while a full roll hangs above it. The roll hangs on the rod by its core, so it sits a little lower than the rod, equally for both rolls.
The rod is not printed. Use a wooden dowel or a metal tube from the hardware store. Rod length is paper_width + 2 x 5 mm side gap + 2 x width + 2 x 2 mm play + 2 x cap_depth, which is 1082 mm at the defaults. For 1 m rolls a metal tube or a 25 mm dowel sags less than a 20 mm dowel. The caps take an axial wood screw into a dowel, on a tube a drop of glue does the same.
Colour is display only and changes nothing about the shape or the STL.
Parameters
part picks the assembly, one bracket laid down for printing, or one cap. roll_d is the largest full roll, paper_width only sizes the preview and the rod, rod_d and rod_fit size the slot. width, back_t, under and lip size the bracket, rise is how far the arm stands above the rod, funnel widens the slot entry. wall_gap and roll_gap are the spare room behind and between the rolls. screws, screw_d and head_d set the countersunk wall screws. cap_wall, cap_depth, cap_fit and cap_screw_d shape the end caps, zero on cap_screw_d removes the screw hole. round_r and chamfer are the polish, zero turns it off. color_bracket and color_cap pick the colours.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/double-roll-holder@1.0.0")Returns the manifest, the source, the import closure and the license obligations. Full MCP setup.
Parameter table
Parsed from the Customizer annotations in main.scad| Name | Default | Range | Unit | Description |
|---|---|---|---|---|
| part | "assembly" | assembly, bracket, cap | ||
| roll_d | 100 | 40 to 140 step 5 | ||
| paper_width | 1000 | 200 to 1600 step 10 | ||
| rod_d | 20 | 8 to 35 step 1 | ||
| rod_fit | 0.6 | 0.2 to 2 step 0.1 | ||
| width | 22 | 14 to 40 step 1 | ||
| back_t | 8 | 5 to 14 step 0.5 | ||
| under | 12 | 8 to 25 step 1 | ||
| lip | 10 | 8 to 20 step 1 | ||
| rise | 6 | 0 to 20 step 1 | ||
| funnel | 5 | 0 to 10 step 1 | ||
| wall_gap | 10 | 5 to 40 step 1 | ||
| roll_gap | 10 | 5 to 40 step 1 | ||
| screws | 2 | 2 to 4 step 1 | ||
| screw_d | 4.5 | 3 to 6 step 0.5 | ||
| head_d | 9 | 6 to 14 step 0.5 | ||
| cap_wall | 5 | 3 to 8 step 0.5 | ||
| cap_depth | 12 | 6 to 25 step 1 | ||
| cap_fit | 0.3 | 0 to 1 step 0.05 | ||
| cap_screw_d | 3.5 | 0 to 5 step 0.5 | ||
| round_r | 3 | 0 to 4 step 0.5 | ||
| chamfer | 1.2 | 0 to 2.4 step 0.2 | ||
| smoothness | 48 | 16 to 96 step 8 | ||
| color_bracket | "White" | White, Gainsboro, Silver, DimGray, Black, SlateGray, FireBrick, SteelBlue, ForestGreen, Goldenrod | ||
| color_cap | "DimGray" | DimGray, Black, SlateGray, Silver, Gainsboro, White, FireBrick, SteelBlue, ForestGreen, Goldenrod |
Lineage
0 models depend on this oneOnly what this model came from and what came from it.
License
Whoever downloads this takes these obligations along- mick/double-roll-holder@1.0.0 is MIT
Source
main.scad is the entrypointmain.scad
// A double wall holder for paper rolls (brown paper / kraft paper). Two identical brackets,
// each with two arms carrying an open-top slot. A rod through the roll core drops into the
// slots from above. Built from straight rectangles; corners and edges are polished
// afterwards as a final step, never as part of the construction.
/* [Parts] */
// Render the assembly, or one part laid down for printing.
part = "assembly"; // [assembly, bracket, cap]
/* [Roll] */
// Largest diameter of a full roll.
roll_d = 100; // [40:5:140]
// Width of the paper. Only sizes the preview and the rod length.
paper_width = 1000; // [200:10:1600]
// Diameter of the rod through the roll core.
rod_d = 20; // [8:1:35]
// Spare room for the rod in the slot.
rod_fit = 0.6; // [0.2:0.1:2]
/* [Bracket] */
// Width of the bracket along the wall. This is the print height.
width = 22; // [14:1:40]
// Thickness of the back plate against the wall.
back_t = 8; // [5:0.5:14]
// Material under the rod.
under = 12; // [8:1:25]
// Thickness of the lip in front of the slot.
lip = 10; // [8:1:20]
// How far the arm stands above the top of the rod.
rise = 6; // [0:1:20]
// Size of the funnel at the slot entry. Zero removes it.
funnel = 5; // [0:1:10]
// Room between a full roll and the wall.
wall_gap = 10; // [5:1:40]
// Spare room between the rolls while lifting the lower one in.
roll_gap = 10; // [5:1:40]
/* [Screws] */
// Number of wall screws per bracket.
screws = 2; // [2:1:4]
// Hole diameter for the wall screws.
screw_d = 4.5; // [3:0.5:6]
// Diameter of the countersunk screw head.
head_d = 9; // [6:0.5:14]
/* [Cap] */
// Wall thickness of the rod end cap.
cap_wall = 5; // [3:0.5:8]
// How far the rod goes into the cap.
cap_depth = 12; // [6:1:25]
// Spare room for the rod in the cap.
cap_fit = 0.3; // [0:0.05:1]
// Axial screw hole to fix the cap to a wooden dowel. Zero removes it.
cap_screw_d = 3.5; // [0:0.5:5]
/* [Polish] */
// Radius the profile corners are rounded to. Zero keeps them sharp.
round_r = 3; // [0:0.5:4]
// Chamfer on the two faces of the bracket and on the cap. Zero keeps them sharp.
chamfer = 1.2; // [0:0.2:2.4]
/* [Quality] */
// Facets on the rounded parts. More is smoother and slower.
smoothness = 48; // [16:8:96]
/* [Colour] */
// Colour the brackets are shown in. Display only, the STL carries no colour.
color_bracket = "White"; // [White, Gainsboro, Silver, DimGray, Black, SlateGray, FireBrick, SteelBlue, ForestGreen, Goldenrod]
// Colour the end caps are shown in.
color_cap = "DimGray"; // [DimGray, Black, SlateGray, Silver, Gainsboro, White, FireBrick, SteelBlue, ForestGreen, Goldenrod]
/* [Hidden] */
slot_w = rod_d + rod_fit;
slot_depth = rod_d + rise;
arm_h = under + slot_depth;
// The slot never cuts into the back plate, however small the roll.
seat_x = max(wall_gap + roll_d / 2, back_t + slot_w / 2 + 6);
reach = seat_x + slot_w / 2 + lip;
// The lower roll passes over its lip while a full roll hangs above it.
pitch = max(roll_d + slot_depth + roll_gap, arm_h + rod_d + roll_gap);
height = pitch + arm_h;
fun = min(funnel, lip - 3);
side_gap = 5;
play = 2;
cap_end = 4;
bracket_y = paper_width / 2 + side_gap + width / 2;
rod_len = 2 * (bracket_y + width / 2 + play + cap_depth);
core_id = max(rod_d + 2, min(rod_d + 20, roll_d * 0.5));
function seat_z(i) = i * pitch + under + rod_d / 2;
echo(str("Rod length: ", rod_len, " mm"));
echo(str("Bracket: ", reach, " x ", height, " x ", width, " mm"));
// The polish: a closing pass rounds the concave corners, an opening pass the convex
// ones. Applied over the finished straight shape, never during it.
module polish2d(r) {
if (r > 0)
offset(r = r, $fn = smoothness)
offset(r = -2 * r, $fn = smoothness)
offset(r = r, $fn = smoothness)
children();
else
children();
}
// Extrude with a 45 degree chamfer on both faces, built from thin slices so it stays
// fast. One slice per 0.2 mm, the same as a print layer.
module polish_extrude(h, c) {
if (c > 0) {
n = ceil(c / 0.2);
s = c / n;
translate([0, 0, c]) linear_extrude(height = h - 2 * c) children();
for (i = [0 : n - 1]) {
inset = c - (i + 0.5) * s;
translate([0, 0, i * s])
linear_extrude(height = s + 0.001) offset(delta = -inset) children();
translate([0, 0, h - (i + 1) * s - 0.001])
linear_extrude(height = s + 0.001) offset(delta = -inset) children();
}
} else {
linear_extrude(height = h) children();
}
}
// ---- Bracket ----
// One 2D profile of axis-aligned squares, in the XZ plane.
// Origin: the wall at x = 0, the underside of the lower arm at z = 0.
module arm_raw(z0) {
difference() {
translate([0, z0]) square([reach, arm_h]);
// The slot, open at the top, with a round seat for the rod.
translate([seat_x, z0 + under + slot_w / 2]) {
circle(d = slot_w, $fn = smoothness);
translate([-slot_w / 2, 0]) square([slot_w, arm_h]);
}
// The funnel, so the rod finds the slot when it drops in from above.
if (fun > 0)
translate([seat_x, z0 + arm_h])
polygon([[-slot_w / 2 - fun, 1], [slot_w / 2 + fun, 1],
[slot_w / 2 + fun, 0], [slot_w / 2, -fun],
[-slot_w / 2, -fun], [-slot_w / 2 - fun, 0]]);
}
}
module profile_raw() {
square([back_t, height]);
arm_raw(0);
arm_raw(pitch);
}
module profile() {
polish2d(round_r) profile_raw();
}
module bracket_body() {
translate([0, width / 2, 0])
rotate([90, 0, 0])
polish_extrude(width, chamfer) profile();
}
module wall_screw(z) {
cs = (head_d - screw_d) / 2;
translate([-1, 0, z]) rotate([0, 90, 0])
cylinder(d = screw_d, h = back_t + 2, $fn = 32);
translate([back_t - cs, 0, z]) rotate([0, 90, 0])
cylinder(d1 = screw_d, d2 = head_d, h = cs + 0.01, $fn = 32);
translate([back_t, 0, z]) rotate([0, 90, 0])
cylinder(d = head_d, h = 2, $fn = 32);
}
module bracket() {
z_lo = arm_h + head_d / 2 + 5;
z_hi = pitch - head_d / 2 - 5;
difference() {
bracket_body();
for (i = [0 : screws - 1])
wall_screw(z_lo + i * (z_hi - z_lo) / (screws - 1));
}
}
// ---- Rod end cap ----
// A cup over the rod end, wider than the slot, so the rod cannot wander sideways.
module cap() {
R = rod_d / 2 + cap_wall;
rb = (rod_d + cap_fit) / 2;
H = cap_end + cap_depth;
c = min(chamfer, cap_wall / 2);
sr = cap_screw_d / 2;
difference() {
rotate_extrude($fn = smoothness)
polygon([[sr, 0], [R - c, 0], [R, c], [R, H - c], [R - c, H],
[rb, H], [rb, cap_end], [sr, cap_end]]);
if (cap_screw_d > 0)
translate([0, 0, -0.01])
cylinder(d1 = 2 * cap_screw_d, d2 = cap_screw_d, h = cap_screw_d / 2, $fn = 32);
}
}
// ---- Preview only: rod and rolls ----
module rod(i) {
translate([seat_x, rod_len / 2, seat_z(i)]) rotate([90, 0, 0])
cylinder(d = rod_d, h = rod_len, $fn = smoothness);
}
module roll(i, d) {
// The roll hangs on the rod by its core, so it sits a little lower than the rod.
translate([seat_x, paper_width / 2, seat_z(i) - (core_id - rod_d) / 2])
rotate([90, 0, 0])
difference() {
cylinder(d = d, h = paper_width, $fn = smoothness);
translate([0, 0, -1]) cylinder(d = core_id, h = paper_width + 2, $fn = smoothness);
}
}
// ---- Layout ----
if (part == "bracket") {
// On its side, ready to print. Print it twice, left and right are the same.
color(color_bracket) translate([0, 0, width / 2]) rotate([90, 0, 0]) bracket();
} else if (part == "cap") {
color(color_cap) cap();
} else {
for (s = [-1, 1])
color(color_bracket) translate([0, s * bracket_y, 0]) bracket();
for (i = [0, 1]) {
color("SaddleBrown") rod(i);
color(color_cap) translate([seat_x, rod_len / 2 + cap_end, seat_z(i)])
rotate([90, 0, 0]) cap();
color(color_cap) translate([seat_x, -(rod_len / 2 + cap_end), seat_z(i)])
rotate([-90, 0, 0]) cap();
}
// A full roll above, a partly used one below.
color("Tan") roll(1, roll_d);
color("Tan") roll(0, max(roll_d * 0.7, core_id + 6));
}
Comments
Context, not a score. Comments never move a model up or down the results.No comments yet
Printed it? A note about your material and the clearance you landed on saves the next person a failed print.
