// Lovcad public example: closed-back centrifugal impeller. // Hub and back plate are one revolved profile; backward-curved main and splitter // blades are extruded from their plan outline and trimmed by the revolved // blade envelope (hub line up to the shroud line), then fused to the hub. const DEG = Math.PI / 180; function fuseAll(shapes) { let out = shapes[0]; for (const shape of shapes.slice(1)) { const next = out.fuse(shape); out.delete(); shape.delete(); out = next; } return out; } function cutAll(shape, tools) { let out = shape; for (const tool of tools) { const next = out.cut(tool); out.delete(); tool.delete(); out = next; } return out; } // Plan outline of one blade: a logarithmic spiral from r0 to r1 with constant // blade angle, thickened to t. Each side is one cubic Bézier, the ends are lines. function bladePen(R, r0, r1, wrap, t) { const at = (r, a) => [r * Math.cos(a), r * Math.sin(a)]; const k = Math.log(r1 / r0) / wrap; // r = r0 · e^(k·θ) const point = (u) => { const theta = u * wrap; const r = r0 * Math.exp(k * theta); return { r, theta }; }; const side = (offset) => { const pts = [0, 1 / 3, 2 / 3, 1].map((u) => { const { r, theta } = point(u); // Offset along the spiral normal, approximated by a small angular shift. return at(r, -theta + offset / r); }); // Control points that reproduce the spiral at its thirds. const [p0, p1, p2, p3] = pts; const c1 = [ (-5 * p0[0] + 18 * p1[0] - 9 * p2[0] + 2 * p3[0]) / 6, (-5 * p0[1] + 18 * p1[1] - 9 * p2[1] + 2 * p3[1]) / 6, ]; const c2 = [ (2 * p0[0] - 9 * p1[0] + 18 * p2[0] - 5 * p3[0]) / 6, (2 * p0[1] - 9 * p1[1] + 18 * p2[1] - 5 * p3[1]) / 6, ]; return { p0, c1, c2, p3 }; }; const a = side(t / 2); const b = side(-t / 2); return R.draw(a.p0) .cubicBezierCurveTo(a.p3, a.c1, a.c2) .lineTo(b.p3) .cubicBezierCurveTo(b.p0, b.c2, b.c1) .close(); } export function build(R, p) { const ro = p.diameter / 2; const h = p.height; // Hub: back plate, then a concave hub line rising to the nose. const hub = R.draw([p.bore / 2, 0]) .lineTo([ro, 0]) .lineTo([ro, p.backPlate]) .cubicBezierCurveTo( [p.noseDiameter / 2, h], [ro * 0.55, p.backPlate + 1], [p.noseDiameter / 2, h * 0.55] ) .lineTo([p.bore / 2, h]) .close() .sketchOnPlane('XZ') .revolve([0, 0, 1]); // Blade envelope: from the axis out to the shroud line, which falls from the // eye height at the inlet to the outlet blade height at the rim. const envelope = R.draw([0, 0]) .lineTo([ro, 0]) .lineTo([ro, p.backPlate + p.outletHeight]) .cubicBezierCurveTo( [p.eyeDiameter / 2, h], [ro * 0.62, p.backPlate + p.outletHeight + 1], [p.eyeDiameter / 2, h * 0.7] ) .lineTo([0, h]) .close() .sketchOnPlane('XZ') .revolve([0, 0, 1]); // Main and splitter blades follow the same logarithmic spiral, so every // splitter is the outer part of a main blade turned by half a pitch and the // outlets are evenly spaced. const r0 = p.noseDiameter / 2 + 1; const r1 = ro - 0.2; const rs = ro * p.splitterStart; const wrapOf = (start) => (p.wrap * Math.log(r1 / start)) / Math.log(r1 / r0); const blades = []; for (let i = 0; i < p.blades * 2; i++) { const main = i % 2 === 0; const start = main ? r0 : rs; const wrap = wrapOf(start); const plan = bladePen(R, start, r1, wrap * DEG, p.bladeThickness) .sketchOnPlane('XY', p.backPlate - 0.5) .extrude(h); const envelopeCopy = envelope.clone(); const trimmed = plan.intersect(envelopeCopy); plan.delete(); envelopeCopy.delete(); const turn = (i * 180) / p.blades - (p.wrap - wrap); blades.push(trimmed.rotate(turn, [0, 0, 0], [0, 0, 1])); } envelope.delete(); let impeller = fuseAll([hub, ...blades]); // Bore with a keyway. const bore = R.makeCylinder(p.bore / 2, h + 2, [0, 0, -1]); const key = R.makeBox( [p.bore / 2 - 1, -p.key / 2, -1], [p.bore / 2 + p.key / 2, p.key / 2, h + 1] ); impeller = cutAll(impeller, [bore, key]); return [{ shape: impeller, name: 'Centrifugal impeller', color: '#b9bdbe' }]; }