// Lovcad public example: six-axis articulated robot arm in a working pose. // Six revolute joints: base yaw (J1), shoulder (J2), elbow (J3), forearm roll // (J4), wrist pitch (J5) and flange roll (J6). Links are built in their own // frame along +X, rotated about the joint axis (Y) and moved to the joint. // Links hang off the side faces of the joint drums, as on collaborative arms, // so neighbouring solids touch but never overlap. Joint housings carry the // accent colour; every moving body is a separate solid. 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; } // A link plate with round ends: length between joint centres, width, thickness // (centred on the XZ plane), lying along +X from the origin. function link(R, length, width, thickness) { return R.drawRoundedRectangle(length + width, width, width / 2) .translate(length / 2, 0) .sketchOnPlane('XZ', -thickness / 2) .extrude(thickness); } // Joint drum on the Y axis (pitch joints) between y0 and y1. const drum = (R, radius, y0, y1) => R.makeCylinder(radius, y1 - y0, [0, y0, 0], [0, 1, 0]); // Point at distance `length` from `origin` in the XZ plane, `angle` above +X. const polar = (origin, angle, length) => [ origin[0] + length * Math.cos(angle), origin[1], origin[2] + length * Math.sin(angle), ]; // Turn a part built along +X by `deg` above the horizontal, then move it to `at`. const place = (shape, deg, at) => shape.rotate(-deg, [0, 0, 0], [0, 1, 0]).translate(at); export function build(R, p) { const rad = Math.PI / 180; const parts = []; const add = (shape, name, color) => parts.push({ shape, name, color }); const paper = '#ece7dc'; const accent = '#e0432b'; const dark = '#4b4f52'; const halfShoulder = p.shoulderWidth / 2; // Base: bolted floor flange and column. let base = R.makeCylinder(p.baseRadius + 14, 12, [0, 0, 0]); const column = R.makeCylinder(p.baseRadius, p.baseHeight - 12, [0, 0, 12]); base = fuseAll([base, column]); const holes = Array.from({ length: 8 }, (_, i) => { const a = (i * Math.PI) / 4 + Math.PI / 8; return R.makeCylinder(4.5, 14, [ (p.baseRadius + 7) * Math.cos(a), (p.baseRadius + 7) * Math.sin(a), -1, ]); }); add(cutAll(base, holes), 'Base', dark); // J1 turret: rotating table, pedestal and the shoulder drum (J2 motor). const top = p.baseHeight + p.turretHeight; const shoulder = [0, 0, top + p.shoulderRadius + 6]; const turret = fuseAll([ R.makeCylinder(p.baseRadius - 4, p.turretHeight, [0, 0, p.baseHeight]), R.makeBox( [-p.shoulderRadius * 0.9, -halfShoulder, top], [p.shoulderRadius * 0.9, halfShoulder, shoulder[2]] ), drum(R, p.shoulderRadius, -halfShoulder, halfShoulder).translate(shoulder), ]); add(turret, 'J1 turret and J2 drive', accent); // J2 upper arm on the +Y face of the shoulder drum. const upperY = halfShoulder + p.upperThickness / 2; add( place(link(R, p.upperLength, p.upperWidth, p.upperThickness), p.shoulder, [ 0, upperY, shoulder[2], ]), 'Upper arm', paper ); // J3 elbow drum on the -Y face of the upper arm; forearm on the drum's -Y face. const elbow = polar(shoulder, p.shoulder * rad, p.upperLength); const elbowY0 = halfShoulder - p.elbowWidth; add( drum(R, p.elbowRadius, elbowY0, halfShoulder).translate(elbow), 'J3 elbow drive', accent ); const foreAngle = p.shoulder + p.elbow; const foreY = elbowY0 - p.foreThickness / 2; add( place(link(R, p.foreLength, p.foreWidth, p.foreThickness), foreAngle, [ elbow[0], foreY, elbow[2], ]), 'Forearm', paper ); // J4 roll housing beyond the forearm end, carrying the J5 knuckle. const wrist = polar(elbow, foreAngle * rad, p.foreLength); const rollStart = p.foreWidth / 2 + 1; const knuckleAt = rollStart + p.rollLength + p.knuckleRadius - 4; const roll = fuseAll([ R.makeCylinder(p.rollRadius, p.rollLength, [rollStart, 0, 0], [1, 0, 0]), drum(R, p.knuckleRadius, -p.rollRadius, p.rollRadius).translate([ knuckleAt, 0, 0, ]), ]); add( place(roll, foreAngle, [wrist[0], foreY, wrist[2]]), 'J4 roll and J5 wrist', accent ); // J6 tool flange with four bolt holes, then a parallel gripper. const knuckle = polar( [wrist[0], foreY, wrist[2]], foreAngle * rad, knuckleAt ); const toolAngle = foreAngle + p.wrist; const f0 = p.knuckleRadius + 1; let flange = R.makeCylinder(17, 20, [f0, 0, 0], [1, 0, 0]); const flangeHoles = [0, 1, 2, 3].map((i) => { const a = (i * Math.PI) / 2 + Math.PI / 4; return R.makeCylinder( 2.2, 8, [f0 + 14, 11 * Math.cos(a), 11 * Math.sin(a)], [1, 0, 0] ); }); flange = cutAll(flange, flangeHoles); add(place(flange, toolAngle, knuckle), 'J6 tool flange', dark); const g0 = f0 + 20; const gripper = fuseAll([ R.makeBox([g0, -20, -9], [g0 + 10, 20, 9]), R.makeBox([g0 + 10, -18, -7], [g0 + 42, -10, 7]), R.makeBox([g0 + 10, 10, -7], [g0 + 42, 18, 7]), ]); add(place(gripper, toolAngle, knuckle), 'Parallel gripper', paper); return parts; }