Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The builder

This page shows the shortest way to fly a rocket of your own with hpr-sim. The hpr crate has four types for it: Environment, Motor, Rocket and Flight. You describe the rocket part by part from the nose back, put a motor in it, and fly it from a rail. The page runs four example programs and walks through them. It needs the setup from Getting started, and some Rust.

How far to trust it. The builder adds no physics. It writes the same design tree that Your own rocket writes by hand, and flies it with the same simulation. A test builds that page’s rocket with the builder and flies it: the mass properties and the whole flight come out the same, bit for bit (the_builder_makes_the_rocket_own_rocket_builds_by_hand). So what that page says about trusting its numbers holds here. The masses come from each part’s shape and a published density, so glue, paint and hardware are missing until you weigh the parts. This rocket has never been flown for real, so no flight checks it. Where it lands in a wind is the least certain number of all: on two of RocketPy’s example rockets, hpr’s drift and RocketPy’s differ by 11 to 43% (Getting started explains why).

Run it

cargo run --example build_and_fly -p hpr

This builds the small rocket of Your own rocket. Its airframe is 54 mm inside and 56.3 mm outside, and it takes 29 mm motors. It weighs the rocket, and finds its center of gravity (CG), its center of pressure (CP) and its stability margin. Then it flies the rocket on a Cesaroni H54 from a rail leaning into a light wind, with a parachute that opens at the motor’s ejection delay. It prints this:

My 54 mm rocket on a 168H54-10A
Not yet validated: see the Accuracy page before trusting these numbers.

                                 liftoff     spent
mass (kg)                          0.675     0.579
center of gravity (m from nose)    0.671     0.610
center of pressure (m from nose)   0.779     0.779
stability margin (calibres)         1.92      2.99

From a 1.8 m rail leaning 5° west, in 5 m/s of wind from the west:
Rail exit:  21.7 m/s
Apogee:     1106.6 m above the pad, at 13.69 s, 282 m west of it
Top speed:  186 m/s (Mach 0.56)
Landing:    877 m east of the pad, at 4.6 m/s, at 246.3 s

What the lines say:

  • Liftoff and spent. The rocket loses 96 g of propellant as the motor burns. That moves the CG 6 cm forward, and the margin grows from 1.92 to 2.99 calibres.
  • The CP is worked out by Barrowman’s method at Mach 0.3, a typical subsonic speed, with the air straight along the rocket. It depends on the shape alone, so it doesn’t move as the motor burns.
  • The flight. The rail exit speed is how fast the rocket leaves the rail. The rail leans 5° west, into the wind, and the rocket turns further into the wind as it climbs, a rocket’s usual weathercocking. The lean and the turn together put its apogee 282 m west of the pad. Under the parachute the wind carries it back, to land 877 m east of the pad.
  • Heights are the height of the rocket’s CG above the launch site. The CG starts above the ground, sitting on the rail, so the apogee includes that starting height.

The program

The program is crates/hpr/examples/build_and_fly.rs. It takes four steps: the motor, the rocket, the weighing and the flight.

The motor

Motor::from_catalog("H54") takes a motor from the catalog built into hpr-sim. It finds the motor by its designation, such as 168H54-10A, or by its common name, the short form such as H54, and ignores case, spaces and hyphens. Only the 32 motors that come with a thrust curve can be found; the motor page lists them. A name that matches two motors, as I175 does, is refused with both listed rather than guessed. The delay is never read from the designation: with_delay_s(10.0) sets it, and a parachute opened by the motor’s charge needs it. Motor::from_eng reads the text of a RASP .eng file, such as one downloaded from ThrustCurve.org, instead.

The rocket

Rocket::new(name, diameter_m) starts an empty rocket with an outer body diameter. Then each add_ call adds one part and returns the rocket, so the calls chain. Body parts stack from the nose tip in the order you add them. The parts that go inside or on the airframe attach to the last body tube you added:

partwhat it iswhere it goes
Nose::hollow, Nose::solidA nose cone of a shape and length, and its wall. with_shoulder adds the sleeve that fits inside the tube behind; with_capped_shoulder closes the sleeve’s aft end with a discFirst, at the tip. Its base takes the rocket’s diameter
Tube::newA body tube of a length and a wall. with_diameter_m gives it another diameterBehind the last body part, at its diameter
Transition::conicalA cone to a new diameter at its aft end: a boattail, or a step up or down in the airframeBehind the last body part, starting at its diameter
Fins::newA set of identical fins of a shape (FinPlanform, which names each dimension), square-edged unless with_cross_section says otherwiseOn the last tube, flush with its aft end unless at places them
MotorTube::newThe tube the motor goes in: its length, bore and wallIn the last tube, flush with its aft end unless at places it
Mass::newAnything else inside: a recovery bay, an altimeter, ballastIn the last tube, where its position puts it

A position (Position) is measured along the tube the part is on: Top places the part’s fore end a distance aft of the tube’s, Bottom its aft end from the tube’s aft end, Middle its middle from the tube’s middle, After its fore end behind the part before it, and Absolute its fore end from the nose tip. So a Mass given a size with packed has its center half that length from the end its position names, or at the point itself when placed by its Middle. In the example, packing the recovery bay 15 cm long, its top 7 cm down the tube, moves the rocket’s CG at liftoff 22 mm aft of where a point mass at the top puts it.

Every part names its material, and every hollow part its wall. material("abs") finds one of the built-in materials, each with the source of its density; the mass page explains how the masses are found. The builder has no default material or wall, because each would be a guess at your rocket’s mass. The defaults it does have change the drag, not the mass: fins have square edges, and every outer surface has the design’s default finish, mass-production paint, which sets the friction of the air on it (Drag). The builder can’t change the finish yet.

set_motor puts the motor in the motor tube, lit at launch. add_parachute adds a recovery device: here a 90 cm flat parachute opened by the ejection charge of motor number 0, the first, Trigger::MotorDelay { motor: 0 }. A device adds drag, not mass; the parachute’s mass is in the 200 g recovery bay. The recovery page explains the devices and what opens them.

Weighing it

mass_properties(t) gives the mass, CG and inertia t seconds after the motor lights. margin(t, mach) gives the CP and the margin, and static_margin_cal(t, mach) the margin alone. The CG is in the body frame, whose z axis points to the nose, so a point 0.671 m behind the nose tip is at z = −0.671. The CP is a station, meters aft of the tip, so the example prints it as it comes and the CG with its sign turned. Weighing runs the same checks on the design as a flight does, so a motor wider than its tube is refused by both.

Flying it

Environment::new(latitude, longitude, elevation) is the launch site, in degrees north, degrees east (so the Americas are negative) and meters. It has the standard atmosphere and no wind; with_constant_wind(5.0, 270.0) adds 5 m/s blowing from 270°, the west. The elevation is taken both as the height above sea level, where the air is read, and as the height above the ellipsoid.

Flight::builder(&rocket, &environment, 1.8) sets up a flight from a vertical 1.8 m rail. inclination_deg is the rail’s angle above the horizon: 90 is vertical, and 85 leans 5° off it. OpenRocket measures its launch rod angle from the vertical instead, so its 5° is 85 here. heading_deg is the way the rail leans, clockwise from true north (add the declination to a compass reading). A wind’s direction is where it comes from, so a rail leaning into a west wind has both at 270. fly() flies the rocket to the ground. The flight’s apogee_m, max_speed_m_s, rail_exit_speed_m_s and landing are the numbers most asked for; summary() has every metric the metrics page describes.

Which motor

The second example flies the same rocket on each 29 mm motor that comes with hpr-sim, from a vertical rail, with the parachute opening at apogee whatever the delay:

cargo run --example motor_choice -p hpr
My 54 mm rocket from a 1.8 m vertical rail, in 5 m/s of wind from the west
Not yet validated: see the Accuracy page before trusting these numbers.

motor        liftoff  margin  rail exit   apogee  top speed  best delay
               (kg)   (cal)     (m/s)      (m)     (m/s)        (s)
F15            0.569    3.00       10.0    206.9         55         4.6
F52C           0.548    3.32       18.5    438.6        106         8.0
168H54-10A     0.675    1.92       21.7   1134.6        186        10.5

set_motor swaps the motor in the tube, so one rocket flies on all three. The H54 reaches 1134.6 m here, from a vertical rail in the wind. The same rocket reaches 1144.5 m on Your own rocket, from a vertical rail in calm air, and 1106.6 m at the top of this page, from a leaning rail. The parachutes open at different times too: here at apogee, there at the motor’s charge.

The best delay is the time from burnout, the end of the thrust curve, to apogee, so the charge fires at the top. It is 10.5 s on the H54, near the 10 s delay that motor’s designation names. The H54 burns out at 3.5 s, so on the leaning rail at the top of this page its 10 s delay fires at 13.5 s, 0.2 s before its apogee. The F52 wants 8 s. The F15 leaves the rail at only 10 m/s, the slowest of the three, and a slow rocket’s fins have the least air to steer with.

Sizing fins

The third example builds the same rocket with fins of five spans, the fin’s height from the body tube to its tip, and flies each from a vertical rail, in calm air and in 5 m/s of wind from the west. The parachute opens at apogee here, not at the motor’s charge, so the charge doesn’t cut the climb short:

cargo run --example fin_sizing -p hpr
My 54 mm rocket on an H54, fins of five spans, in calm air and a 5 m/s west wind
Not yet validated: see the Accuracy page before trusting these numbers.

fin span  liftoff  margin   apogee (m)       in the wind: drift (m)
    (mm)     (kg)   (cal)   calm   wind   apogee upwind  landing downwind
      25    0.666   -2.11   too little margin to fly
      35    0.671    0.33   too little margin to fly
      45    0.675    1.92   1145   1135             119              1068
      55    0.680    2.97   1126   1110             156              1001
      65    0.684    3.67   1107   1088             176               954

Because a rocket is a value built by a function, a design study is a loop. The example’s rocket(span_m) builds the rocket with fins of that span, and the loop weighs and flies each.

  • The margin, at liftoff and at Mach 0.3, grows fast with the span: from −2.11 calibres, a CP ahead of the CG, to 3.67. The usual rule of thumb asks for at least one calibre (stability margin), so the program doesn’t fly the two smallest; the example’s own fins are the 45 mm ones.
  • Bigger fins cost height. In calm air the 65 mm fins reach 38 m less than the 45 mm ones: that is their drag and their extra mass, about 9 g. In the wind they lose 47 m, since they also turn the rocket further into it, as the apogee drift shows: 119 m upwind with the 45 mm fins, 176 m with the 65 mm.
  • The landing is closer with bigger fins, 114 m closer from the 45 mm to the 65 mm: the parachute opens further upwind, and lower, so it drifts for less time.

Parts from a catalog

A rocket can also be built from a maker’s parts, as sold, from a parts catalog. hpr-sim bundles the one OpenRocket ships: 3,449 parts from Estes, LOC Precision and a dozen more makers, read as the .orc page explains. The fourth example finds LOC Precision’s 2.56 in (65 mm) airframe parts in it by maker and part number, builds the rocket from them, and flies it on an AeroTech H170:

cargo run --example catalog_rocket -p hpr
LOC 2.56 in from the catalog, with motor H170M
Not yet validated: see the Accuracy page before trusting these numbers.

part                                     mass (g)
LOC Precision PNC-2.56                       87.7
LOC Precision BT-2.56                       120.0
LOC Precision BT-1.52, MMT-1.52              43.6
LOC Precision CR-2.56-38mm                    4.2
LOC Precision CR-2.56-38mm                    4.2
fins                                         99.7
LOC Precision LP-36-2022                     70.7
structure                                   430.1

At liftoff: 0.760 kg, center of gravity 0.728 m from the nose,
stability margin 1.07 calibres at Mach 0.3.

Rail exit:  30.0 m/s
Apogee:     1119.3 m above the pad, at 11.77 s
Top speed:  302 m/s (Mach 0.91)
Landing:    at 4.6 m/s, at 250.8 s

Each part is named by its maker and its part number, as the design keeps it. LOC’s numbers say what the part is and its size in inches: PNC-2.56 is a plastic nose cone for the 2.56 in tube, BT-2.56 the body tube, MMT-1.52 the 1.52 in (38 mm) motor tube, CR-2.56-38mm a centering ring between the two, and LP-36-2022 a 36 in parachute. Structure is all the parts together, without the motor.

How far to trust these numbers. Nearly every part weighs what OpenRocket 24.12 weighs when it builds the same catalog part: within 0.1%, with its center of mass within 0.1% of its length. The few exceptions are counted in “How far to trust it” below. One is deliberate, and this nose has it: its shoulder, the short tube that slides into the body, has the nose’s own plastic wall here and weighs nothing in OpenRocket. OpenRocket weighs the nose at 61.5 g; here it weighs 87.7 g, 26.1 g of it the shoulder. That is 6% of the structure. Nothing checks the flight itself; the note at the top of this page applies. The top speed, Mach 0.91, is close to the speed of sound, where drag rises steeply and is least certain (Drag through Mach 1). The stability margin, 1.07 calibres, is taken at Mach 0.3, about 100 m/s, more than three times the speed the rocket leaves the rail at.

The program is catalog_rocket.rs. Its steps:

  • Find the part. hpr::hpr_io::orc::bundled().find("LOC Precision", "PNC-2.56") returns the parts with that maker and number. Use the maker’s name as the file writes it (“LOC Precision”, where OpenRocket shows “LOC/Precision”).
  • Make the builder’s part from it. Nose::from_catalog(part), Tube::from_catalog, Transition::from_catalog and MotorTube::from_catalog (a body tube used as the motor tube) take the catalog’s sizes, material and density. A catalog nose or transition states its own diameters, so the tube behind it takes the nose’s diameter, not the one given to Rocket::new.
  • Cut a tube. Catalog tubes are sold long (LOC’s motor tube is 34 in). with_length_m cuts one to length, and its mass follows.
  • Fittings. A fitting is a part that goes in or on the last body tube: a coupler, an engine block, a centering ring, a bulkhead, a launch lug, or a packed parachute or streamer. Fitting::from_catalog(part) makes one from a catalog part, and Rocket::add_fitting adds it, flush with the tube’s aft end unless at places it. Fitting::centering_ring, bulkhead, coupler and launch_lug make one to your own sizes.
  • A parachute is two things. As a fitting it is its weight: canopy and shroud lines, at its place in the tube. Its drag is a recovery device, as in the first example. The example gives that device the catalog’s diameter.
  • The fins are made by hand: the catalog has none.

229 of the 3,449 parts state their mass in the catalog, and each weighs that mass. The builder sets the part’s density so that the part, as the catalog sizes it, weighs the stated mass, and the material’s name gains “, density set by the part’s stated mass”. A part changed afterwards, such as a tube cut shorter, keeps that density, so its mass follows the change. OpenRocket gives a rigid part its stated mass the same way. It gives a parachute its stated mass as an override, which comes to the same for a parachute left as it is. It ignores a streamer’s.

What the catalog leaves unsaid

A catalog leaves some sizes out. The builder fills each one in the way OpenRocket 24.12 does when it builds the part, with one exception, a hollow part’s shoulder. The shapes are explained in Shapes:

Left unsaidThe builder’s choiceOpenRocket’s
An ogive’s shapetangentthe same
A parabola’s parameter K′1the same
A Haack series’ C0, the von Kármánthe same
A power series’ exponent½the same
Whether a transition is clippedyes for elliptical, Haack and power seriesthe same
A filled part’s shouldersolidthe same
A parachute’s line material, when the file names none or one it doesn’t defineweightless linesthe same
A hollow part’s shoulder wallthe part’s own wallzero: it weighs nothing

A clipped transition is cut from a whole nose cone, rather than stretched from one (Shapes). A molded plastic nose cone’s shoulder is a tube of the same plastic, so a shoulder that weighs nothing would be wrong. The catalog’s stated masses agree: on the 74 hollow parts with a shoulder that state their mass, the file’s density weighs nearer the stated mass with the shoulder’s wall than without it on 46. The median stated mass is 0.97 of the mass with the wall, and 1.30 of the mass without it.

How far to trust it

tests/catalog_openrocket.rs builds every part in the catalog with the builder. It holds each part’s mass and center of mass to what OpenRocket builds from it, run as an oracle and recorded by orc_built.py. The tolerances were set before measuring. Every part is in one row:

PartsCountAllowedLargest difference found
Tubes, couplers, rings, bulkheads, lugs, parachutes, streamers2,2301e-12 of the mass; center 1e-12 of the lengthunder 1e-14 of the mass, or 8.8e-10 where it is stated in ounces (below); center under 1e-13 of the length
Filled nose cones and transitions1,0291e-3 of the mass; center 1e-3 of the length2.0e-4 of the mass, or 8.8e-10 in ounces; center 7.0e-5 of the length
Hollow nose cones and transitions, shoulders taken out181the same6.3e-4 of the mass, or 8.8e-10 in ounces; center 9.7e-4 of the length
Hollow elliptical nose cones whose walls differ4countedup to 0.48% heavier here; center up to 1.7e-3 of the length
A streamer that states its mass1countedhpr weighs the stated mass; OpenRocket ignores it
Refused by the builder4countedOpenRocket weighs each as nothing
All3,449

Where the masses differ, the test checks each cause:

  • OpenRocket’s volumes are close, not exact. Even a cone, whose volume has a formula, differs a little. The test holds hpr’s 85 filled cones to that formula, so the difference is OpenRocket’s.
  • Hollow shoulders. A hollow part’s shoulders are taken out of hpr’s mass and center, each worked out on its own as a tube of the part’s wall. That leaves the body, which is what OpenRocket weighs.
  • Two walls. hpr’s wall is every point within its thickness of the outer surface. OpenRocket’s masses follow a wall measured across each station instead, whose inner radius is r − t √(1 + r′²) (r the radius, t the thickness, r′ the slope). The test works out both on its own. All 185 hollow parts’ OpenRocket centers agree with the station-wise wall to 1.1e-4 of the length, and their masses to 2.5e-4, on the 111 that state no mass (a stated mass is OpenRocket’s whatever the wall). hpr’s 113 hollow conical, tangent-ogive and elliptical nose cones agree with integrals of its own wall, in volume and center, to 1e-9. The other 72 hollow parts (Haack and parabolic noses, and transitions) have no such integral: they are checked only against OpenRocket, within the tolerance. The two walls differ most on short, blunt nose cones. Four elliptical ones fall outside the tolerance: three up to 0.48% heavier here, their centers up to 1.7e-3 of their length apart, and one, which states its mass, by its center alone, 1.0e-3 of its length.
  • Masses stated in ounces, on 185 parts of every kind (a different 185 from the hollow parts above), differ by OpenRocket’s rounded ounce, 8.8e-10 of the mass.
  • The 4 refused parts. One nose cone names a material its file doesn’t define. Three tubes or rings have a bore no narrower than their outside.

The test compares mass and center of mass, not the moments of inertia. What it checks is each part as the catalog describes it. A catalog’s sizes and densities are the makers’ or the database’s, and none was weighed here. Glue, paint and hardware are still missing: weigh the finished parts when you can.

Beyond the builder

The builder covers a single-stage rocket with one motor. Two ways lead further, both into the crates the builder is made of.

  • Change the design. A rocket’s design() is its design tree, which a design file holds. Clone it, add what the builder can’t with the hpr_design crate (a cluster, pods, rail buttons, a stage), and make a rocket of it with Rocket::from_design(tree, configuration), which names the configuration, the motors, to fly. from_design also takes a design file, or an OpenRocket file read as the .ork page shows. A second stage also needs a separation, which the flight builder’s separation takes, and a recovery device on each part it makes. For a .ork, hpr::ork::separated_recovery puts the file’s own devices on their parts, as hpr sim does (Separation); the ork_two_stage example flies one, building its simulation with Simulation::new rather than through the builder.
  • Change the flight. A flight builder’s simulation() hands over the simulation fly() would run. The motor_choice example passes it to hpr_sim::metrics::optimum_delays. The simulation’s methods add a separation, events of your own, or moving or released masses. A drag model of your own, or another program’s drag table, needs no detour: the flight builder’s drag_model and drag_table take them (Models of your own). run(&mut ()) flies it, with no observer watching. A flight flown that way returns the simulation’s own result, without the builder’s Flight methods; hpr_sim::FlightMetrics gives the same metrics (Flight metrics).

What it refuses

Each part’s numbers are checked when the part is added: a negative length, a NaN, a zero diameter, zero fins, a shape parameter out of range. The error names the number, such as tube diameter, m is 0, outside its domain. So is a part in an order the tree can’t take: fins before any tube, a second motor tube, a nose behind a tube. The launch site and wind are checked when they are given, the rail when the flight is set up, and a design that comes in whole through from_design before it flies. Tests try each of these and pin which check refuses it (parts_out_of_order_are_refused, degenerate_designs_error_or_stay_finite). One rocket in them has no fins at all: it flies, tumbling, to finite numbers. That test is Loft lesson L95’s: a Loft lesson is a mistake of the project before this one, with the test that guards against it here.

A catalog part is refused when the builder can’t make it as the catalog describes it: a part of another kind than the from_catalog it is given (a body tube given to Nose::from_catalog), a material its file names but doesn’t define, a shape the builder doesn’t have, a nose cone or transition the file neither fills nor gives a wall thickness, a tube or ring whose bore is no narrower than its outside, or a part stating a mass with no volume to give it. The error names the part by maker, number and file. Rocket::add_fitting also refuses a fitting before any body tube, and a part that isn’t a fitting.

What it can’t do yet

  • One motor, one stage. Clusters, staging and pods go through the design, as above; the staging page explains how they fly.
  • Rail buttons go through the design. Without them the rocket leaves the rail when its aft end passes the rail’s top. A launch lug is a fitting (above), with its drag; without one, a lug’s drag is missing. With one, the rocket leaves the rail when the lug’s aft edge passes the rail’s top.
  • The finish. Every surface is painted, as above.

Where next

  • Models of your own flies a drag model and a wind of your own in hpr-sim’s place.
  • The API reference’s guide module is this page’s walk-through in five short chapters, each with code that CI runs.
  • The API reference documents every method, starting at the hpr crate.
  • Your own rocket builds the same rocket without the builder, and shows every field a part has.
  • Recording a trajectory keeps the flight’s path: pass a recorder to fly_with instead of calling fly.