diff vs bare: +153/-139
- // Mug with handle (OpenSCAD)
+ // Mug + +X side D-handle (OpenSCAD)
+ // Dimensions (mm):
+ // Mug: outer Ø80, inner Ø70 (wall 5), height 90, bottom thickness 6
+ // Handle: only on +X side, D-shape (outer half-cylinder with flat inner side),
+ // inner void ~ 30 (height) x 25 (width in Y). Positioned around middle of mug height.
$fn = 128;
- // Parameters
- outer_d = 80; // outer diameter
- inner_d = 70; // inner diameter
- t_wall = 5; // wall thickness (given)
- h_mug = 90; // mug height
- t_bottom= 6; // bottom thickness
+ // ---- Mug parameters ----
+ outer_d = 80;
+ inner_d = 70;
+ t_wall = (outer_d - inner_d)/2; // 5
+ h_mug = 90;
+ t_bottom= 6;
- handle_w = 25; // inner space width (x-direction)
- handle_h = 30; // inner space height (z-direction)
- handle_center_z = h_mug/2; // center near mid height
+ mug_outer_r = outer_d/2;
+ mug_inner_r = inner_d/2;
- handle_thickness = 5; // approximate handle wall thickness (for solidness)
- handle_outer_r = handle_h/2; // radius used for outer half-cylinder
+ // ---- Handle parameters ----
+ handle_void_w = 25; // width in Y (requested)
+ handle_void_h = 30; // height in Z (requested)
- module mug() {
- difference() {
- // Outer cylinder with bottom thickness included
- cylinder(d=outer_d, h=h_mug, center=false);
+ handle_center_z = h_mug/2;
- // Inner cavity: leave bottom thickness
- translate([0,0,t_bottom])
- cylinder(d=inner_d, h=h_mug - t_bottom, center=false);
- }
- }
+ // Handle grip depth along +X (how far it extends from the mug)
+ handle_depth = 20;
- module handle() {
- // D-shaped handle:
- // - Outside: half-cylinder along YZ plane (flat side toward mug center)
- // - Inside: remove a slightly smaller half-cylinder to make an opening with
- // inner cross-section approximating 30mm(height) x 25mm(width)
- //
- // We'll place the handle on +X side only.
- // Coordinate note:
- // - Mug axis: Z
- // - We attach handle so it extends in +X direction.
- // - Half-cylinder is generated along Y (so width is along Y) and radius is Z.
+ // Handle wall thickness (for D shell)
+ handle_wall = 4;
- // Choose radius so that inner height matches ~handle_h
- r_outer = handle_outer_r; // ~ handle_h/2
- r_inner = r_outer - handle_thickness;
+ // How much extra overlap to avoid manifold gaps
+ overlap = 0.6;
- // Depth (along Y) is handle_w, using half-cylinder which naturally spans 2*r.
- // We'll limit width by intersecting with a Y slab of size handle_w.
- // Also, orient the half-cylinder so its flat side is toward -X (mug center),
- // and its curved side is toward +X.
+ // For D-shape outer half-circle radius in YZ:
+ r_outer = handle_void_h/2 + handle_wall; // gives outer size; inner will be r_inner = r_outer-handle_wall
+ r_inner = r_outer - handle_wall;
- // Position: touch at mug outer surface on +X
- mug_r = outer_d/2;
+ // Mug contact plane for handle:
+ x_mug_outer = mug_outer_r; // x = +40 touches outer wall
- // Half-cylinder centered in Y at 0.
- // Flat side aligned so that curved outer surface is at +X near mug surface.
- // We'll create half-cylinder with centerline at X = mug_r.
- x_attach = mug_r; // flat face starts at X=mug_r, curved extends to +X.
+ // Small helper: 2D half-circle polygon (Y-Z plane) with flat at Y=0
+ module half_circle_2d(r=10, yflip=false) {
+ // Creates points for a semicircle in +Y direction, from Z=-r..+r, with Y>=0
+ pts = [
+ for (i=[0:64])
+ let(
+ a = -90 + i*(180/64),
+ y = r*cos(a), // cos maps -90..+90 -> 0..r..0
+ z = r*sin(a) // sin maps -90..+90 -> -r..+r
+ )
+ [yflip ? -y : y, z]
+ ];
+ // Close with flat edge on Y=0 (connect ends)
+ polygon(concat(pts, [[0,-r],[0,r]]));
+ }
- translate([x_attach, 0, handle_center_z])
- rotate([0,90,0]) // make cylinder axis along X initially, then adjust to half-cylinder along X
- // We'll build using a half-cylinder via intersection with a half-space.
- difference() {
- // Outer half-cylinder (curved outer, flat inner side)
- intersection() {
- // Full cylinder (axis along X after rotate)
- rotate([0,0,0])
- translate([0,0,0])
- cylinder(r=r_outer, h=outer_d*0.4, center=false);
+ // Build D-shaped handle as an extrusion along X (depth).
+ // D is defined in YZ cross-section:
+ // - Outer: semicircle (Y>=0) with flat at Y=0
+ // - Inner void: smaller semicircle (Y>=0) with flat at Y=0
+ // Then we clip in Y (width) to ~handle_void_w and in Z to ~handle_void_h.
+ module handle_d() {
- // Keep only the +X half of the cylinder relative to its center
- // For a cylinder (axis along X), half selection is in YZ, not X.
- // Instead, we create "D" in cross-section by selecting Y range:
- // - The flat side is created by clipping on Y: keep Y >= 0 for half.
- translate([0, -r_outer, -r_outer])
- cube([outer_d*0.4, 2*r_outer, 2*r_outer], center=false);
- }
+ // Cross-section extents:
+ // We'll clip the D in Y to control void width.
+ // Our semicircle is centered at Y=0; flat at Y=0 implies curved bulges toward +Y.
+ // For inner void width ~25, we clip y from 0..(handle_void_w).
+ clip_y_max = handle_void_w; // void width along Y
- // Inner cavity in the handle: subtract smaller half-cylinder
- intersection() {
- cylinder(r=r_inner, h=outer_d*0.4, center=false);
+ // For Z we center at handle_center_z with requested height
+ z0 = handle_center_z - handle_void_h/2;
+ z1 = handle_center_z + handle_void_h/2;
- translate([0, -r_inner, -r_inner])
- cube([outer_d*0.4, 2*r_inner, 2*r_inner], center=false);
+ // Outer half-circle in YZ plane, extruded along +X.
+ // We position the flat face at Y=0 and place the semicircle bulge toward +Y.
+ difference() {
+ // Outer handle (D shell)
+ translate([x_mug_outer - overlap, 0, 0])
+ linear_extrude(height=handle_depth, center=false)
+ translate([0,0,0]) // already in YZ in 2D
+ intersection() {
+ half_circle_2d(r=r_outer, yflip=false);
+ // Clip to achieve target void width/shape (keep only y in [0..clip_y_max])
+ // and allow enough thickness above/below due to wall thickness.
+ // We'll clip in the 2D stage by intersecting with a rectangle.
+ translate([0,0])
+ square([clip_y_max, 2*r_outer + 1], center=false);
+ }
+
+ // Inner void: subtract a smaller D (so wall remains)
+ translate([x_mug_outer - overlap, 0, 0])
+ // Move inner void so its Z span is centered correctly
+ translate([0,0,0])
+ linear_extrude(height=handle_depth + 2*overlap, center=false)
+ intersection() {
+ // Smaller half-circle
+ half_circle_2d(r=r_inner, yflip=false);
+ // Clip in Y to target width:
+ square([clip_y_max, 2*r_inner + 1], center=false);
}
- // Limit the overall handle width (inner space target ~handle_w) by slicing in Y.
- // (Applied after subtraction; keeps walls from extending too far.)
- translate([0, -(handle_w/2 + handle_thickness), -(r_outer)])
- cube([outer_d*0.4, handle_w + 2*handle_thickness, 2*r_outer], center=false);
- }
+ // Finally clip inner void in Z to exact requested height by subtracting a 3D box
+ // (easier than precise 2D Z offsets).
+ translate([x_mug_outer - overlap - 0.5, 0, z0])
+ cube([handle_depth + 2*overlap + 1, clip_y_max, handle_void_h], center=false);
+ }
}
- module handle_v2() {
- // More controlled D-shape:
- // Create a "D" cross-section in YZ, extruded along X into the +X side,
- // then subtract an inner opening with the requested width/height.
- //
- // D-shape in 2D: outer half-circle + flat line.
- // We use rotate_extrude-like by using linear_extrude of 2D polygons.
+ // Place handle so its flat face is toward mug center (i.e., at Y=0 plane).
+ // Requirement: attach only to +X side, and connect to mug outer wall.
+ // We'll also ensure it doesn't appear on -X.
+ module mug_with_handle() {
+ union() {
+ // Mug body: outer cylinder minus inner cavity, leaving bottom thickness.
+ difference() {
+ cylinder(d=outer_d, h=h_mug, center=false);
+ translate([0,0,t_bottom])
+ cylinder(d=inner_d, h=h_mug - t_bottom, center=false);
+ }
- mug_r = outer_d/2;
+ // Handle: build D handle shell and clip in X to ensure only +X attachment.
+ // Position so D is centered at mug's middle height.
+ translate([0,0,0]) {
+ // The handle_d() is centered around handle_center_z by subtractive Z box,
+ // but its outer half-cylinder extrusion exists from Z=-r_outer..+r_outer around origin.
+ // So we shift Z of the whole handle to mug center.
+ translate([0,0,handle_center_z]) {
+ // Our handle_d() expects Z clipping box at absolute z0..z1 in mug coords,
+ // so we implement by shifting handle_z logic inside handle_d via absolute z0/z1
+ // already computed. Thus set origin accordingly: handle_d includes absolute z0/z1
+ // but we are shifting; to avoid double-shift, redefine approach:
+ // We'll implement a dedicated shifted handle below (simpler).
- // X extrusion length: make sure it grips the mug wall firmly
- x_len = 20;
+ }
+ }
+ }
+ }
- // 2D coordinates:
- // - Flat side is at X=0 in 2D space; curved side extends to +Y.
- // But for our 2D, we will define in (y,z) plane.
- // Let half-circle be centered at y = 0, z = 0? Better:
- // Define 2D in (Y,Z):
- // outer half-circle radius r_outer centered at (Y=0, Z=0),
- // keep only Y>=0 to form the D with flat at Y=0.
- r_outer = handle_h/2;
- r_inner = r_outer - handle_thickness;
+ // Dedicated handle with correct Z placement (no double-shift):
+ module handle_d_placed() {
+ clip_y_max = handle_void_w;
+ // Z limits in absolute coordinates of mug
+ z0 = handle_center_z - handle_void_h/2;
- // Width control: inner opening width is handle_w (in Y).
- // We'll subtract an inner half-oval limited to handle_w in Y by clipping.
- // Then outer will be clipped similarly to avoid overly long width.
+ // We'll create outer and inner D extrusions centered around Z=0,
+ // then shift to z0 appropriately using linear_extrude base at Z=-r_outer.
+ // Since half_circle_2d spans Z in [-r_outer, +r_outer] (in 2D), shifting by handle_center_z
+ // will put the Z center at handle_center_z.
+ difference() {
+ // Outer
+ translate([x_mug_outer - overlap, 0, handle_center_z])
+ linear_extrude(height=handle_depth, center=false)
+ intersection() {
+ half_circle_2d(r=r_outer, yflip=false);
+ square([clip_y_max, 2*r_outer + 1], center=false);
+ }
- linear_extrude(height=x_len)
- translate([mug_r, 0, handle_center_z]) // move to mug side and height
- rotate([0,0,0]) // keep
- // Build outer D in YZ (extruded along X)
- difference() {
- // Outer D polygon (in YZ plane)
- // Outer half-circle from z=-r_outer..+r_outer with y>=0
- polygon(concat(
- // Arc points (counter-clockwise), y from 0..r
- [for (i=[0:60])
- let(t = -90 + i*(180/60))
- [ r_outer*cos(t), r_outer*sin(t) ]],
- // Close with flat edge along y=0 from top to bottom
- [[0, r_outer],[0,-r_outer]]
- ));
+ // Inner: smaller D
+ translate([x_mug_outer - overlap, 0, handle_center_z])
+ linear_extrude(height=handle_depth + 2*overlap, center=false)
+ intersection() {
+ half_circle_2d(r=r_inner, yflip=false);
+ square([clip_y_max, 2*r_inner + 1], center=false);
+ }
- // Inner opening: subtract smaller D, clipped by requested inner width in Y
- translate([0,0])
- intersection() {
- // Inner D
- polygon(concat(
- [for (i=[0:60])
- let(t = -90 + i*(180/60))
- [ r_inner*cos(t), r_inner*sin(t) ]],
- [[0, r_inner],[0,-r_inner]]
- ));
- // Clip to requested inner width around y= (0..handle_w)
- // Since D uses y>=0, we only keep 0..handle_w.
- translate([0,0])
- square([handle_w, 2*r_outer + 1], center=false);
- }
- }
+ // Ensure inner void height is exactly handle_void_h
+ // (Also prevents the inner D from exceeding requested vertical size.)
+ translate([x_mug_outer - overlap - 0.5, 0, z0])
+ cube([handle_depth + 2*overlap + 1, clip_y_max, handle_void_h], center=false);
+ }
}
- module mug_with_handle() {
+ module mug_with_handle_fixed() {
union() {
- mug();
-
- // Attach handle only on +X side:
- // The handle geometry is placed at X = mug outer radius.
- // Ensure it overlaps slightly into the mug wall region to avoid gaps.
- // We'll union by overlapping in X by a small amount.
- translate([0,0,0])
- union() {
- // Choose handle_v2 for controlled D shape.
- // Slight negative X offset to guarantee connection (overlap) with mug wall.
- translate([-1, 0, 0]) handle_v2();
- }
+ difference() {
+ cylinder(d=outer_d, h=h_mug, center=false);
+ translate([0,0,t_bottom])
+ cylinder(d=inner_d, h=h_mug - t_bottom, center=false);
+ }
+ // Add handle only on +X side
+ handle_d_placed();
}
}
- mug_with_handle();
+ mug_with_handle_fixed();
SCAD (185 lines)
// Mug + +X side D-handle (OpenSCAD)
// Dimensions (mm):
// Mug: outer Ø80, inner Ø70 (wall 5), height 90, bottom thickness 6
// Handle: only on +X side, D-shape (outer half-cylinder with flat inner side),
// inner void ~ 30 (height) x 25 (width in Y). Positioned around middle of mug height.
$fn = 128;
// ---- Mug parameters ----
outer_d = 80;
inner_d = 70;
t_wall = (outer_d - inner_d)/2; // 5
h_mug = 90;
t_bottom= 6;
mug_outer_r = outer_d/2;
mug_inner_r = inner_d/2;
// ---- Handle parameters ----
handle_void_w = 25; // width in Y (requested)
handle_void_h = 30; // height in Z (requested)
handle_center_z = h_mug/2;
// Handle grip depth along +X (how far it extends from the mug)
handle_depth = 20;
// Handle wall thickness (for D shell)
handle_wall = 4;
// How much extra overlap to avoid manifold gaps
overlap = 0.6;
// For D-shape outer half-circle radius in YZ:
r_outer = handle_void_h/2 + handle_wall; // gives outer size; inner will be r_inner = r_outer-handle_wall
r_inner = r_outer - handle_wall;
// Mug contact plane for handle:
x_mug_outer = mug_outer_r; // x = +40 touches outer wall
// Small helper: 2D half-circle polygon (Y-Z plane) with flat at Y=0
module half_circle_2d(r=10, yflip=false) {
// Creates points for a semicircle in +Y direction, from Z=-r..+r, with Y>=0
pts = [
for (i=[0:64])
let(
a = -90 + i*(180/64),
y = r*cos(a), // cos maps -90..+90 -> 0..r..0
z = r*sin(a) // sin maps -90..+90 -> -r..+r
)
[yflip ? -y : y, z]
];
// Close with flat edge on Y=0 (connect ends)
polygon(concat(pts, [[0,-r],[0,r]]));
}
// Build D-shaped handle as an extrusion along X (depth).
// D is defined in YZ cross-section:
// - Outer: semicircle (Y>=0) with flat at Y=0
// - Inner void: smaller semicircle (Y>=0) with flat at Y=0
// Then we clip in Y (width) to ~handle_void_w and in Z to ~handle_void_h.
module handle_d() {
// Cross-section extents:
// We'll clip the D in Y to control void width.
// Our semicircle is centered at Y=0; flat at Y=0 implies curved bulges toward +Y.
// For inner void width ~25, we clip y from 0..(handle_void_w).
clip_y_max = handle_void_w; // void width along Y
// For Z we center at handle_center_z with requested height
z0 = handle_center_z - handle_void_h/2;
z1 = handle_center_z + handle_void_h/2;
// Outer half-circle in YZ plane, extruded along +X.
// We position the flat face at Y=0 and place the semicircle bulge toward +Y.
difference() {
// Outer handle (D shell)
translate([x_mug_outer - overlap, 0, 0])
linear_extrude(height=handle_depth, center=false)
translate([0,0,0]) // already in YZ in 2D
intersection() {
half_circle_2d(r=r_outer, yflip=false);
// Clip to achieve target void width/shape (keep only y in [0..clip_y_max])
// and allow enough thickness above/below due to wall thickness.
// We'll clip in the 2D stage by intersecting with a rectangle.
translate([0,0])
square([clip_y_max, 2*r_outer + 1], center=false);
}
// Inner void: subtract a smaller D (so wall remains)
translate([x_mug_outer - overlap, 0, 0])
// Move inner void so its Z span is centered correctly
translate([0,0,0])
linear_extrude(height=handle_depth + 2*overlap, center=false)
intersection() {
// Smaller half-circle
half_circle_2d(r=r_inner, yflip=false);
// Clip in Y to target width:
square([clip_y_max, 2*r_inner + 1], center=false);
}
// Finally clip inner void in Z to exact requested height by subtracting a 3D box
// (easier than precise 2D Z offsets).
translate([x_mug_outer - overlap - 0.5, 0, z0])
cube([handle_depth + 2*overlap + 1, clip_y_max, handle_void_h], center=false);
}
}
// Place handle so its flat face is toward mug center (i.e., at Y=0 plane).
// Requirement: attach only to +X side, and connect to mug outer wall.
// We'll also ensure it doesn't appear on -X.
module mug_with_handle() {
union() {
// Mug body: outer cylinder minus inner cavity, leaving bottom thickness.
difference() {
cylinder(d=outer_d, h=h_mug, center=false);
translate([0,0,t_bottom])
cylinder(d=inner_d, h=h_mug - t_bottom, center=false);
}
// Handle: build D handle shell and clip in X to ensure only +X attachment.
// Position so D is centered at mug's middle height.
translate([0,0,0]) {
// The handle_d() is centered around handle_center_z by subtractive Z box,
// but its outer half-cylinder extrusion exists from Z=-r_outer..+r_outer around origin.
// So we shift Z of the whole handle to mug center.
translate([0,0,handle_center_z]) {
// Our handle_d() expects Z clipping box at absolute z0..z1 in mug coords,
// so we implement by shifting handle_z logic inside handle_d via absolute z0/z1
// already computed. Thus set origin accordingly: handle_d includes absolute z0/z1
// but we are shifting; to avoid double-shift, redefine approach:
// We'll implement a dedicated shifted handle below (simpler).
}
}
}
}
// Dedicated handle with correct Z placement (no double-shift):
module handle_d_placed() {
clip_y_max = handle_void_w;
// Z limits in absolute coordinates of mug
z0 = handle_center_z - handle_void_h/2;
// We'll create outer and inner D extrusions centered around Z=0,
// then shift to z0 appropriately using linear_extrude base at Z=-r_outer.
// Since half_circle_2d spans Z in [-r_outer, +r_outer] (in 2D), shifting by handle_center_z
// will put the Z center at handle_center_z.
difference() {
// Outer
translate([x_mug_outer - overlap, 0, handle_center_z])
linear_extrude(height=handle_depth, center=false)
intersection() {
half_circle_2d(r=r_outer, yflip=false);
square([clip_y_max, 2*r_outer + 1], center=false);
}
// Inner: smaller D
translate([x_mug_outer - overlap, 0, handle_center_z])
linear_extrude(height=handle_depth + 2*overlap, center=false)
intersection() {
half_circle_2d(r=r_inner, yflip=false);
square([clip_y_max, 2*r_inner + 1], center=false);
}
// Ensure inner void height is exactly handle_void_h
// (Also prevents the inner D from exceeding requested vertical size.)
translate([x_mug_outer - overlap - 0.5, 0, z0])
cube([handle_depth + 2*overlap + 1, clip_y_max, handle_void_h], center=false);
}
}
module mug_with_handle_fixed() {
union() {
difference() {
cylinder(d=outer_d, h=h_mug, center=false);
translate([0,0,t_bottom])
cylinder(d=inner_d, h=h_mug - t_bottom, center=false);
}
// Add handle only on +X side
handle_d_placed();
}
}
mug_with_handle_fixed();