Read this model as text
A layout mock-up of a wheeled bipedal balancing robot with two straight legs. Each leg has a roll joint at the body (swings the leg in and out), a pitch joint below it (swings the leg forward and back), and a driven wheel at the bottom. Six actuators, a body box, and a sensor mount on top.
The image shows the solid OpenSCAD renders at hip_actuator STS3250 · wheel_actuator STS3215 · leg_length 140 · wheel_diameter 140 · wheel_width 26 · body_length 120 · body_width 90 · body_height 70 · mass_g 1200 · leg_pitch 0 · leg_roll 0 · sensor lidar.
Wheeled bipedal balancer layout
mick/wheeled-biped-balancer@0.2.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 layout mock-up of a wheeled bipedal balancing robot with two straight legs. Each leg has a roll joint at the body (swings the leg in and out), a pitch joint below it (swings the leg forward and back), and a driven wheel at the bottom. Six actuators, a body box, and a sensor mount on top.
Nothing here is a printable part. It is a sizing model: set the leg length, wheel size, body and actuators, pose the legs, and read the console for what the geometry implies.
What you use it for
Picking numbers before buying parts. Swing the legs to any roll and pitch and see stance height, overall height, and track width. The console reports the static torque each hip joint holds in that pose against the actuator's rated torque, the maximum pitch the hip can hold at all, wheel rim force against the robot's weight, and whether the largest part fits a 180 mm bed.
Design notes
The roll joint carries a moment even when the legs hang straight down, because the wheel sits outboard of the roll axis by the width of the pitch servo plus the wheel. That offset is the lever arm, and it never goes to zero. The console reports it so the roll actuator is sized for standing, not just for swinging.
The pitch joint only loads up as the leg swings away from vertical: torque is half the robot's weight times leg length times the sine of the pitch angle. That gives a hard maximum pitch angle per actuator, which is echoed.
The lidar sits on top of the body at the front, where the body itself does not block the scan and the legs are below the scan plane. The camera sits on the front face, tilted down so the ground ahead of the wheels is in frame.
Parameters
hip_actuator and wheel_actuator pick the servo, which sets case size and torque. leg_length, wheel_diameter and wheel_width size the leg. body_length, body_width and body_height size the electronics box, and mass_g feeds the torque check. leg_pitch and leg_roll pose both legs. sensor chooses none, a 2D lidar, a forward camera, or both.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/wheeled-biped-balancer@0.2.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 |
|---|---|---|---|---|
| hip_actuator | "STS3250" | STS3215, STS3250 | ||
| wheel_actuator | "STS3215" | STS3215, STS3250 | ||
| leg_length | 140 | 60 to 260 step 1 | ||
| wheel_diameter | 140 | 60 to 240 step 5 | ||
| wheel_width | 26 | 10 to 60 step 1 | ||
| body_length | 120 | 60 to 200 step 1 | ||
| body_width | 90 | 50 to 160 step 1 | ||
| body_height | 70 | 30 to 140 step 1 | ||
| mass_g | 1200 | 400 to 3000 step 50 | ||
| leg_pitch | 0 | -60 to 60 step 1 | ||
| leg_roll | 0 | -30 to 45 step 1 | ||
| sensor | "lidar" | none, lidar, camera, both |
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/wheeled-biped-balancer@0.2.0 is MIT
Source
main.scad is the entrypointmain.scad
// Layout mock-up of a wheeled bipedal balancer with two straight legs.
// Each leg: roll joint on the body, pitch joint below it, straight bar,
// driven wheel at the bottom. A sensor mount sits on top of the body.
// Shapes stand in for volume and reach; nothing is a printable part yet.
// Read the console for stance, track, hip torque and the bed check.
/* [Actuators] */
// Hip actuator, used for both the roll and the pitch joint.
hip_actuator = "STS3250"; // [STS3215, STS3250]
// Wheel drive actuator, one per wheel.
wheel_actuator = "STS3215"; // [STS3215, STS3250]
/* [Leg] */
// Pitch pivot to wheel axle.
leg_length = 140; // [60:1:260]
// Diameter measured over the tread.
wheel_diameter = 140; // [60:5:240]
// Width across the tyre.
wheel_width = 26; // [10:1:60]
/* [Body] */
// Fore-aft length of the electronics box.
body_length = 120; // [60:1:200]
// Width of the electronics box.
body_width = 90; // [50:1:160]
// Height of the electronics box.
body_height = 70; // [30:1:140]
// All-up mass for the torque check. Changes no geometry.
mass_g = 1200; // [400:50:3000]
/* [Pose] */
// Leg swung forward from vertical, in degrees. Negative is back.
leg_pitch = 0; // [-60:1:60]
// Leg swung outward from vertical, in degrees. Negative is in.
leg_roll = 0; // [-30:1:45]
/* [Sensor] */
// What sits on the body.
sensor = "lidar"; // [none, lidar, camera, both]
/* [Hidden] */
$fn = 40;
function act_size(n) = n == "STS3250" ? [45.2, 24.7, 35.0] : [44.8, 24.0, 35.5];
function act_stall(n) = n == "STS3250" ? 50 : 30; // kg.cm stall at 12 V
hip_size = act_size(hip_actuator);
whl_size = act_size(wheel_actuator);
hip_stall = act_stall(hip_actuator);
whl_stall = act_stall(wheel_actuator);
hip_rated = hip_stall * 0.25; // roughly what these servos hold continuously
wheel_r = wheel_diameter / 2;
hip_z = 25; // roll axis height above the body floor
mass_kg = mass_g / 1000;
// Lateral offsets inside the leg frame, measured from the roll axis.
pitch_y = hip_size[2] / 2 + 4; // pitch servo centre
bar_y = hip_size[2] + 10; // leg bar plane
wheel_y = bar_y + 6 + wheel_width / 2; // wheel centre
// Wheel centre in world coordinates for the right leg (left mirrors).
wc_x = leg_length * sin(leg_pitch);
wc_y = body_width / 2 + wheel_y * cos(leg_roll) + leg_length * cos(leg_pitch) * sin(leg_roll);
wc_z = hip_z + wheel_y * sin(leg_roll) - leg_length * cos(leg_pitch) * cos(leg_roll);
ground_z = wc_z - wheel_r * cos(leg_roll);
stance_h = -ground_z;
track_w = 2 * wc_y + wheel_width * cos(leg_roll);
sensor_h = sensor == "lidar" || sensor == "both" ? 33 : (sensor == "camera" ? 0 : 0);
total_h = stance_h + body_height + sensor_h;
// Static moments, kg.cm per joint, half the robot on each leg.
pitch_arm_cm = leg_length * sin(leg_pitch) / 10;
roll_arm_cm = (wheel_y * cos(leg_roll) + leg_length * cos(leg_pitch) * sin(leg_roll)) / 10;
pitch_need = abs(mass_kg / 2 * pitch_arm_cm);
roll_need = abs(mass_kg / 2 * roll_arm_cm);
// Largest pitch the hip can hold on rated torque.
arm_limit_cm = hip_rated / (mass_kg / 2);
pitch_max = arm_limit_cm >= leg_length / 10 ? 90 : asin(arm_limit_cm / (leg_length / 10));
rim_force_kg = whl_stall / (wheel_r / 10);
bed_max = max(body_length, wheel_diameter, leg_length);
echo(str("stance height, body floor to ground: ", round(stance_h), " mm"));
echo(str("overall height: ", round(total_h), " mm, track width: ", round(track_w), " mm"));
echo(str("pitch joint holds ", round(pitch_need*10)/10, " kg.cm now; can hold up to ", round(pitch_max), " deg of pitch on ", hip_rated, " kg.cm rated"));
echo(str("roll joint holds ", round(roll_need*10)/10, " kg.cm now (", round(mass_kg/2*wheel_y/10*10)/10, " kg.cm of that is the wheel offset, present even at zero roll)"));
echo(str("hip actuator ", hip_actuator, ": ", hip_stall, " kg.cm stall, about ", hip_rated, " kg.cm rated"));
echo(str("wheel rim force at stall: ", round(rim_force_kg*10)/10, " kg against a ", mass_kg, " kg robot"));
echo(str("step the wheel can mount unaided: about ", round(wheel_r*0.5), " mm"));
echo(str("largest single dimension: ", round(bed_max), " mm", bed_max > 180 ? " -- over the A1 mini bed" : " -- fits the A1 mini bed"));
module bar3(p, q, t) {
hull() {
translate(p) sphere(d = t, $fn = 20);
translate(q) sphere(d = t, $fn = 20);
}
}
module body() {
color("SteelBlue")
translate([0, 0, body_height / 2])
cube([body_length, body_width, body_height], center = true);
}
module sensors() {
if (sensor == "lidar" || sensor == "both")
color("DimGray")
translate([body_length / 4, 0, body_height])
cylinder(h = 33, d = 38);
if (sensor == "camera" || sensor == "both")
color("DimGray")
translate([body_length / 2, 0, body_height - 14])
rotate([0, 15, 0])
cube([12, 26, 26], center = true);
}
module leg(side) {
// Roll servo lives in the body wall; only its output turns.
color("Goldenrod")
translate([0, side * (body_width / 2 - hip_size[2] / 2 + 2), hip_z])
cube([hip_size[1], hip_size[2], hip_size[0]], center = true);
translate([0, side * body_width / 2, hip_z])
rotate([side * leg_roll, 0, 0]) {
// Pitch servo hangs on the roll output, shaft lateral.
color("Goldenrod")
translate([0, side * pitch_y, 0])
cube([hip_size[0], hip_size[2], hip_size[1]], center = true);
rotate([0, -leg_pitch, 0]) {
color("Gainsboro") {
bar3([0, side * bar_y, 0], [0, side * bar_y, -leg_length], 14);
bar3([0, side * bar_y, -leg_length], [0, side * wheel_y, -leg_length], 9);
}
color("Goldenrod")
translate([0, side * (bar_y - whl_size[2] / 2 - 9), -leg_length])
cube([whl_size[0], whl_size[2], whl_size[1]], center = true);
color("DimGray")
translate([0, side * wheel_y, -leg_length])
rotate([90, 0, 0])
cylinder(h = wheel_width, d = wheel_diameter, center = true);
}
}
}
module ground() {
color("Gainsboro")
translate([0, 0, ground_z - 3])
cube([500, track_w + 160, 6], center = true);
}
body();
sensors();
leg(-1);
leg(1);
ground();
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.
