Structure and code-CAD
A humanoid’s frame has to carry landing loads several times its weight, fit between motors, be made on a desktop mill or printer, and weigh as little as possible. Marvin Bot draws every part in Python code, computes each part’s mass straight into the robot model, and checks every load-carrying part with a hand calculation. This page explains how, and lets you size a plate and sweep a joint yourself.
The idea in one picture
Section titled “The idea in one picture”The structure is the robot’s skeleton: the metal and plastic parts that hold the motors in place and carry the robot’s weight from the feet up to the pelvis. A humanoid needs it to be strong enough not to bend or snap when it lands or falls, yet as light as possible, because every gram of frame is a gram the motors must carry (07 Mass budget). Code-CAD is how Marvin Bot draws those parts: instead of clicking shapes together in a drawing program, each part is written as a short Python program.
When Marvin Bot lands from a step, the ground pushes up on the foot with up to about three times the robot’s weight. That force has to travel through the foot plate, the ankle, the shin, the knee, the thigh, the hip and into the pelvis: that chain of parts is the load path. Wherever the force’s line does not pass straight through a part, it bends it, and bending is what breaks thin plates.
Try it with a plastic ruler: lying flat, it bends easily under a finger; stood on its edge, you can hardly bend it at all. A plate bent across its thickness is weak; the same plate bent across its width is strong. The trick of structural design is to arrange material so that every load bends parts the strong way, and to remove material where the stress is low. The first playground lets you see how much difference thickness, pockets and material make.
Playground 1: plate and beam strength
Section titled “Playground 1: plate and beam strength”The words in the readouts, in plain terms (the maths follows in From first principles):
- Bending moment M: how hard the load tries to bend the plate at the checked point; force × lever arm, in N·m.
- Second moment I: how well the plate’s cross-section resists bending, in mm⁴. It depends on shape only, not material; thicker is much better.
- Peak stress σ: the force per unit area inside the metal at its worst point, in MPa (newtons per square millimetre).
- Safety factor: the material’s yield strength (the stress at which it starts to bend permanently) divided by the peak stress. Below 1 the part is permanently bent; Marvin Bot wants at least 2 (green). Amber or red means too weak.
- Deflection: how far the loaded end moves, in mm. Smaller is stiffer.
A plate is clamped at one end (the station where we check it) and a load acts at a lever arm L from that station, like the thigh plate in a sideways fall onto the knee. Choose the cross-section, material and load; the readouts give the bending stress, safety factor, tip deflection and mass.
From first principles
Section titled “From first principles”Code-CAD: parts as programs
Section titled “Code-CAD: parts as programs”Most mechanical design is done in graphical CAD (computer-aided design) programs: you sketch and extrude with a mouse, and the model lives in a binary file. Code-CAD writes the same solids as a program: a plate is box(...), a motor seat is cyl(...), and a part is the union or difference of such shapes. Marvin Bot draws its machined parts in build123d, Python on the OpenCascade kernel, with exact outlines and curved surfaces and export to STEP, the standard file format machine shops accept.[2] The shells, covers and quick checks use manifold3d, a fast, robust library for boolean operations (join, subtract, intersect) on triangle meshes (surfaces made of many small triangles).[1] Alternatives in the same spirit: CadQuery (also on OpenCascade)[3] and OpenSCAD (its own language, mesh-based).[4]
Why code for a robot in its simulation phase:
- Parametric by construction. The parts read their sizes from
description/robot.yamlandskeleton.yaml. When the legs grew 50 mm on 2026-10-29, the thigh and shin regenerated; nobody redrew them. - Diffable. A change is a few lines in a pull request (a proposed change to the code, reviewed before it is accepted) (
thigh_t = 9.0), reviewable like any code, not an opaque binary. - One source of truth for mass.
cad/build.pycomputes each part’s mass, centre of mass and inertia and writes them intodescription/cad_model.yaml; the URDF (the robot model file the simulators read) and the mass budget take them from there (13 The robot description). The simulator therefore trains on the masses of the parts that will be machined. - Checks run on every change. Size against the mill and printer, strength, packaging and shell collisions are all scripts.
The cost: fine detail (fillets, fasteners) is slower to write than to click, and styled surfaces are hard to get right in code: today’s shells are generated as an offset hull round each link’s parts, so the frame sets their shape. They are to be redrawn in build123d as designed, lofted panels (docs/decisions.md, 2026-10-31). Fasteners are a budget item until a fastener pass.
# cad/leg.py, the thigh plate (simplified excerpt, build123d)tplate = rounded(hull(rect(-32, 32, -60, fl_bot), disc(124, xz(knee)))) # outline, rounded inside cornersk_clip = clip_spot(tplate, xz(knee), [stator_holes("RS04", knee, "y")], min_r=64) # harness cliptplate = cut(tplate, stator_holes("RS04", knee, "y"), # the motor's bolt circle lighten(tplate, keep=[ring_band("RS04", knee, "y"), disc(CLIP_PAD, k_clip)]), # pockets disc(CLIP_HOLE, k_clip)) # the push-in clip's holeout.append(made("left_thigh_plate", "left_thigh_link", "al7075", plate(tplate, "y", y, tt)))Machined aluminium, printed plastic
Section titled “Machined aluminium, printed plastic”| Material | Yield / strength (MPa) | Density (g/cm³) | Stiffness E (GPa) | Used for in Marvin Bot |
|---|---|---|---|---|
| 6061-T6 aluminium | 276 typical[5] (repo design value 240) | 2.70 | 68.9 | motor rings and hubs, spacers (37 of 237 CAD parts) |
| 7075-T6 aluminium | 503 typical[6] (repo 480) | 2.81 | 71.7 | most of the frame: plates, brackets, flanges, cranks, straps (125 parts) |
| PA6-CF, printed (carbon-fibre nylon) | 82 in layer, 64 across layers[7] (repo 25 across layers) | 1.17 (repo 1.10) | 5.7 / 4.7 | battery latch and guides, jigs (the body shells and head panels are white PETG) |
| PETG, printed | ≈ 47-50[8] | 1.27 | ≈ 2 | not used for structure: softens near warm motors |
| TPU, printed | flexible | ≈ 1.15 | — | 3 mm foot soles, harness edge clips |
How to read the table: yield is the stress at which the material starts to bend permanently (higher is stronger), density is mass per volume (lower is lighter), and stiffness E is how much it resists stretching (higher bends less). “T6” names a heat treatment that hardens the aluminium. PA6-CF is nylon filled with chopped carbon fibre; PETG and TPU are common 3D-printing plastics (TPU is rubbery).
Aluminium is about 2.3 times as dense as PA-CF but 3 to 10 times as strong and 12 to 15 times as stiff, so for parts that carry the robot’s weight it wins per gram. Printed parts win on shape (hollow shells, curves, internal cable runs) and cost, but nothing printed carries load any more: since 2026-10-30 the arms carry their load in 7075 straps and every load-carrying part is aluminium, and printing is kept for the shells and head panels (white PETG) and small fittings.
Printed parts are weakest between layers, much as plywood splits along its layers: a part printed standing up and bent so the layers peel apart may hold only 25-40 MPa, which is the value cad/fea.py uses.
The workshop sets the shapes. Every machined plate is flat (2.5D: an outline and pockets from one face), so the mill cuts it with 2D toolpaths. Machined parts must fit the desktop mill with its tool changer fitted: 190 × 210 or 240 × 160 mm, depending on which side the changer sits, and 60 mm stock (MILL_MM in cad/build.py). Printed parts must fit a 250 mm printer. Flat parts too large for the mill (the 230 mm foot plates, the 265 mm torso side frames) go to an online laser or water-jet service. This is why the thigh grew 25 mm above the hip yaw motor rather than below it on 2026-10-29: the thigh plate still fits the mill.
Load paths and far-side bearings
Section titled “Load paths and far-side bearings”Each joint on a quasi-direct-drive robot (see 04 Actuators) hangs the next link on the motor’s output. If the link is held on one side only, a sideways load bends the plate out of its plane and puts a moment on the motor’s output bearing. Think of a shelf held by one bracket versus a shelf held at both ends.
Marvin Bot’s first-pass leg did exactly that, and the strength check failed five parts at landing load: the foot base plate (SF 0.3), hip yoke back plate (0.3), hip roll front plate (0.6), shin knee flange (0.8) and hip yoke side plate (1.4) (docs/decisions.md, 2026-10-26).
The second pass added a far-side bearing at the knee and hip roll: the link becomes a yoke held on both sides, the load splits between two plates and the bending in each falls several-fold. The worst safety factor went to 2.4. Later, with finite elements (below), thin crossed-roller bearings went into rings at the hip yaw, the waist and the front of each hip roll motor, so those joints’ tilting loads reach the frame without passing through the motors’ own output bearings (2026-10-30).
Beam bending: σ = M·c / I
Section titled “Beam bending: σ = M·c / I”Cut a plate at a station. A force F acting a distance L away makes a bending moment M = F · L (N·mm if L is in mm). The moment stretches one face and squeezes the other; the stress grows linearly from zero at the neutral axis (through the section’s centroid) to its peak at the face furthest away, a distance c (the centroid is the section’s balance point; for a plain rectangle, its middle):[9]
σ = M · c / Iσ is stress in MPa (N/mm²). I is the section’s second moment of area (mm⁴): each bit of area times the square of its distance from the neutral axis, summed. For a solid rectangle of width b bent across its thickness t,
I = b · t³ / 12, c = t / 2so stress falls with thickness squared: double the thickness and the stress drops to a quarter. Bend the same plate across its width and t and b swap places: a 59 × 9 mm plate is about 43 times stiffer and 6.6 times stronger in plane than out of plane.
Pocketing. Material near the neutral axis does little, because it is multiplied by a small distance squared. Milling pockets from one face down to a thin floor removes that material but leaves full-depth ribs and rims, a section like a channel or an I-beam. Drilling holes right through removes the outer fibres too, which are the ones doing the work. The playground shows the difference: at the same mass, a pocketed plate has a much larger I than a thinner drilled one.
Safety factor. SF = yield strength / peak stress. SF 1 means the part just starts to bend permanently. Marvin Bot targets SF 2 on yield because the hand calculation leaves out torsion and stress concentrations at holes and corners, which typically raise local stress by 2-3 times[10], and because the load cases are estimates until falls are measured.
Playground 2: joint sweep and collisions
Section titled “Playground 2: joint sweep and collisions”A joint’s range is set by the first angle at which something hits. This matters because the robot’s controller must never command an angle that drives an arm into its own chest: the limits found here go into the robot model that the simulator and the trained controller use. This toy has the upper arm (a box) with the shoulder yaw motor (a cylinder) near its top, hanging from the shoulder roll axis beside a rounded torso and a head. Pick the shoulder pitch (arm forward) and sweep the shoulder roll inwards: the toy finds the first contact, and the chart repeats it for every pitch angle. It is a simplified version of what python -m cad.covers --check and tools/check_packaging.py do with the real meshes.
How Marvin Bot does it
Section titled “How Marvin Bot does it”Trade-offs and alternatives
Section titled “Trade-offs and alternatives”| Choice | Taken | Alternatives considered (repo) |
|---|---|---|
| Structure | all-aluminium flat plates on the load path (mostly 7075, pocketed), printed shells as an add-on | printed structural shells on the arms and chest (retired 2026-10-30); fully printed (load must still go through metal; heat near the motors softens PETG-class plastics) (2026-10-25) |
| Joint support | far-side bearings at the knee; crossed-roller bearings at the hip yaw, waist and hip roll; hip pitch as a closed box | single-sided joints, which need the RS04 output bearing’s moment rating (not taken, 2026-10-28); the hip roll’s rear bearing (about 300 g, replaced by the front carrier) |
| Thigh plate | 9 mm pocketed to 2 mm floor (+61 g a side) | 8 mm (SF 1.8); a topology-optimised outline could take back about 35 g |
| CAD | build123d for machined parts (exact outlines, STEP files), manifold3d for shells and covers today; shells to move to build123d lofted panels | CadQuery, OpenSCAD, graphical CAD; Blender or implicit modelling for the shells |
| Big flat parts | online laser or water-jet service; about 12 parts to online machining (~$1,200, not yet quoted) | a larger mill; splitting parts into pinned segments |
| Strength check | finite elements for every load-carrying part (cad/fea.py), SF 2.2 |
hand calculation on CAD sections (missed torsion; retired 2026-10-30) |
Decisions you can make here
Section titled “Decisions you can make here”- Plate thickness, pockets and material, given the worst load case and the SF 2.2 target. Trade: each millimetre of plate adds strength and mass;
python -m cad.fea --link <link> --whereshows where a part’s peak is. - Machined, printed or sent to a service. Trade: printed is cheaper, lighter and freer in shape but far weaker (especially between layers); machined is strong but limited to the mill’s area; a service handles bigger flat parts at extra cost and lead time.
- Where a joint needs a far-side bearing. Trade: an extra bearing, plate and a little mass, in exchange for several-fold less bending in each plate and no sideways load on the motor’s own bearing.
- Joint range versus body shape. Trade: a rounder chest or a slimmer motor gives the arm more room; otherwise accept a coupled limit that the controller must respect.
Built from your settings above. Paste it into an issue, a design discussion or an AI assistant session.
Next: 09 Power and battery starts the electrical stage, beginning with the battery, the single heaviest item in the mass budget that is not a motor or frame.
Further reading
Section titled “Further reading”- manifold3d on GitHub: the library behind Marvin Bot’s shells and mesh checks; its README explains why its booleans never produce broken meshes.
- build123d documentation: the best starting point if you want exact geometry and STEP files from Python.
- MIT OCW 2.001 Mechanics and Materials I: free lectures on stress, bending and beams.
- M. F. Ashby, Materials Selection in Mechanical Design: how to choose a material for strength or stiffness per kilogram.
- Pilkey et al., Peterson’s Stress Concentration Factors: the reference for Kt at holes, notches and fillets.
Sources
Section titled “Sources”- E. Lalish et al., Manifold: geometry library for topological robustness — used for: what manifold3d is (mesh booleans, Python bindings)
- build123d documentation — used for: the machined parts’ code-CAD (Python B-rep on OpenCascade)
- CadQuery documentation — used for: Python CAD alternative with STEP export
- OpenSCAD — used for: script-based CAD alternative
- Aerospace Metals, Aluminum 6061-T6 / T651 datasheet (ASM / Aluminum Association data) — used for: 6061-T6 yield 276 MPa, density 2.70, E 68.9 GPa
- Quickparts, Aluminum 7075 datasheet (ASM data) — used for: 7075-T6 yield 503 MPa, density 2.81, E 71.7 GPa
- Polymaker, PolyMide PA6-CF technical data sheet V4.1 — used for: printed PA6-CF tensile strength (XY 82, Z 64 MPa conditioned), modulus, density 1.17
- Prusa Polymers, Prusament PETG technical data sheet — used for: PETG printed yield 47-50 MPa, density 1.27
- MIT OpenCourseWare, 2.001 Mechanics and Materials I — used for: beam bending σ = M c / I, second moment of area, cantilever deflection
- W. D. Pilkey, D. F. Pilkey, Z. Bi, Peterson’s Stress Concentration Factors, 4th ed., Wiley 2020 — used for: typical stress concentration factors of 2-3 at holes and notches
- M. F. Ashby, Materials Selection in Mechanical Design, 4th ed., 2011 — used for: strength and stiffness per mass comparison