Skip to main content

hpr_design/
materials.rs

1//! Built-in materials, each with the public source its density comes from.
2//!
3//! Values are nominal. The basis of each is recorded:
4//!
5//! - **Published** values are stated by the source: a manufacturer's data sheet, a handbook
6//!   table or a specification.
7//! - **Derived** values are computed from published numbers. Wood densities come from the Wood
8//!   Handbook's specific gravity at 12% moisture, `ρ₁₂ = 1000 G₁₂ (1 + 0.12)`, where `G₁₂` is
9//!   oven-dry mass over volume at 12% moisture (Forest Products Laboratory, FPL-GTR-190, 2010,
10//!   eq. 4-12, p. 4-10). The handbook's own example, white ash at `G₁₂ = 0.605`, gives 678 kg/m³.
11//!   Hobby tube densities are mass over wall volume, `m / (π/4 (OD² − ID²) L)`, from the maker's
12//!   published weights and sizes.
13//! - **Maximum** values are a specification's upper limit on weight; real material is often
14//!   lighter. Measured 9/16" tubular nylon is 12% under its limit.
15//! - **Vendor** values come from a seller's listing or measurements, where no specification
16//!   exists.
17//!
18//! Units: 1 oz/yd² = 33.9057 g/m², 1 oz/yd = 0.0310034 kg/m, and a minimum `L` ft/lb gives a
19//! maximum `1.488164 / L` kg/m. See `docs/physics/mass.md`.
20//!
21//! **Shear moduli.** [`SHEAR_MODULI`] gives some of these materials the in-plane shear modulus a
22//! fin's flutter speed needs (NACA TN 4197's `G_E`), each with its own source; the others have
23//! none. Among fin materials, no source found states one for G10/FR-4, eastern white pine, PLA,
24//! ABS, PETG, polycarbonate or acrylic.
25//! Where a source gives a range, the lower value is kept: a lower modulus gives a lower flutter
26//! speed.
27//!
28//! - Metals are stated by MIL-HDBK-5J, in 10³ ksi (1 psi = 6894.757 Pa).
29//! - A wood's is `G_LT = (G_LT/E_L) · 1.10 E_bend`: the Wood Handbook's elastic ratio (Table
30//!   5-1, p. 5-2) times its bending modulus at 12% moisture raised by 10%, as the table's
31//!   footnote a says for `E_L`. `G_LT` is the smaller of the two in-plane ratios for every wood
32//!   here: a fin whose grain runs along its span or chord shears in the L-T or L-R plane.
33//! - An unfilled plastic's is `E / (2 (1 + ν))`, taking it as isotropic, from its data sheet's
34//!   tensile modulus and Poisson's ratio; for nylon, the conditioned (moist) values.
35//! - The carbon laminate's is its unidirectional ply's `G₁₂`: a 0/90 laminate's in-plane shear
36//!   modulus, and less than one with ±45° plies.
37
38use serde::Serialize;
39
40use crate::material::{Density, Material};
41
42/// How a built-in value relates to its source.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
44#[serde(rename_all = "snake_case")]
45pub enum Basis {
46    /// Stated by the source.
47    Published,
48    /// Computed from the source's numbers, as the module docs describe.
49    Derived,
50    /// A specification's upper limit.
51    Maximum,
52    /// A seller's listing or measurement.
53    Vendor,
54}
55
56/// A built-in material.
57#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
58pub struct BuiltinMaterial {
59    /// Stable identifier.
60    pub id: &'static str,
61    /// Display name.
62    pub name: &'static str,
63    /// Density.
64    pub density: Density,
65    /// The source, with the table or page.
66    pub source: &'static str,
67    /// Where the source was read.
68    pub url: &'static str,
69    /// How the value relates to the source.
70    pub basis: Basis,
71}
72
73impl BuiltinMaterial {
74    /// The material as a design stores it.
75    pub fn material(&self) -> Material {
76        Material {
77            name: self.name.to_owned(),
78            density: self.density,
79        }
80    }
81}
82
83/// Wood density at 12% moisture from the Wood Handbook's specific gravity `G₁₂`.
84const fn wood(g12: f64) -> Density {
85    Density::Bulk {
86        kg_m3: 1000.0 * g12 * 1.12,
87    }
88}
89
90const fn bulk(kg_m3: f64) -> Density {
91    Density::Bulk { kg_m3 }
92}
93
94const fn surface(kg_m2: f64) -> Density {
95    Density::Surface { kg_m2 }
96}
97
98const fn line(kg_m: f64) -> Density {
99    Density::Line { kg_m }
100}
101
102const WOOD_HANDBOOK: &str = "https://www.fpl.fs.usda.gov/documnts/fplgtr/fpl_gtr190.pdf";
103const WOOD_TABLE: &str =
104    "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-3a";
105
106/// Every built-in material.
107pub const BUILTIN: &[BuiltinMaterial] = &[
108    // Hobby airframe tubes.
109    BuiltinMaterial {
110        id: "cardboard",
111        name: "Cardboard (spiral kraft tube)",
112        density: bulk(790.0),
113        source: "LOC Precision cardboard airframe weights and sizes (3.0 x 3.1 x 34 in, 7.8 oz; 5.38 x 5.54 x 24 in, 15 oz; 5.38 x 5.54 x 34 in, 20.4 oz): 828, 788, 756 kg/m3, mean",
114        url: "https://locprecision.com/products.json",
115        basis: Basis::Derived,
116    },
117    BuiltinMaterial {
118        id: "kraft_phenolic",
119        name: "Kraft phenolic tube",
120        density: bulk(950.0),
121        source: "Public Missiles phenolic tubing weight table, PT-1.1 to PT-7.5 (909 to 974 kg/m3), as published in LOC Precision's product data",
122        url: "https://locprecision.com/products.json",
123        basis: Basis::Derived,
124    },
125    BuiltinMaterial {
126        id: "blue_tube",
127        name: "Blue Tube 2.0 (vulcanized fibre)",
128        density: bulk(1250.0),
129        source: "Always Ready Rocketry, BTRSData.zip, MATERIAL.CSV: Vulcanized Fiber, 1.25 g/cm3",
130        url: "http://alwaysreadyrocketry.com/Downloads/BTRSData.zip",
131        basis: Basis::Published,
132    },
133    BuiltinMaterial {
134        id: "quantum_tube",
135        name: "Quantum tube",
136        density: bulk(1090.0),
137        source: "Public Missiles Quantum Airframe Tubing weight table, QT-2.1 to QT-3.9 (1051 to 1173 kg/m3, walls approximate), mean, as published in LOC Precision's product data",
138        url: "https://locprecision.com/products.json",
139        basis: Basis::Derived,
140    },
141    // Composites.
142    BuiltinMaterial {
143        id: "fiberglass_g10",
144        name: "Fiberglass laminate (G10/FR-4)",
145        density: bulk(1800.0),
146        source: "Norplex-Micarta NP130 (NEMA LI 1 grade FR-4) data sheet, 2022, p. 1: specific gravity (ASTM D792) 1.80",
147        url: "https://www.norplex-micarta.com/",
148        basis: Basis::Published,
149    },
150    BuiltinMaterial {
151        id: "fiberglass_filament_wound",
152        name: "Filament-wound E-glass/epoxy (G12)",
153        density: bulk(1990.0),
154        source: "Comptec Inc., filament wound technical data: density 0.072 lb/in3; consistent with MIL-HDBK-17 rule of mixtures at 59% fibre (E-glass 2.54 g/cm3)",
155        url: "https://comptecinc.com/filament-wound-technical-data/",
156        basis: Basis::Published,
157    },
158    BuiltinMaterial {
159        id: "carbon_fiber",
160        name: "Carbon fibre/epoxy laminate",
161        density: bulk(1580.0),
162        source: "Hexcel HexPly 8552 product data sheet (AS4 unidirectional, 57.4% fibre volume): nominal laminate density 1.58 g/cm3",
163        url: "https://www.hexcel.com/",
164        basis: Basis::Published,
165    },
166    // Metals.
167    BuiltinMaterial {
168        id: "aluminum_6061",
169        name: "Aluminium 6061",
170        density: bulk(2700.0),
171        source: "Kaiser Aluminum, 6061 Sheet, Coil and Plate data sheet, rev. 05/06: 2.70 Mg/m3",
172        url: "https://online.kaiseraluminum.com/depot/PublicProductInformation/Document/1015/Kaiser_Aluminum_6061_Sheet_Coil_and_Plate.pdf",
173        basis: Basis::Published,
174    },
175    BuiltinMaterial {
176        id: "aluminum_7075",
177        name: "Aluminium 7075",
178        density: bulk(2800.0),
179        source: "Kaiser Aluminum, 7075 Sheet, Coil and Plate data sheet, rev. 05/06: 2.80 Mg/m3",
180        url: "https://online.kaiseraluminum.com/depot/PublicProductInformation/Document/1017/Kaiser_Aluminum_7075_Sheet_Coil_and_Plate.pdf",
181        basis: Basis::Published,
182    },
183    BuiltinMaterial {
184        id: "steel",
185        name: "Steel (plain carbon)",
186        density: bulk(7850.0),
187        source: "MIL-HDBK-5J (2003), Table 2.2.1.0(b), AISI 1025, p. 2-8: 0.284 lb/in3 (7847 to 7875 kg/m3 at the printed rounding)",
188        url: "https://everyspec.com/MIL-HDBK/MIL-HDBK-0001-0099/download.php?spec=MIL_HDBK_5J.139.pdf",
189        basis: Basis::Published,
190    },
191    BuiltinMaterial {
192        id: "titanium_6al4v",
193        name: "Titanium Ti-6Al-4V",
194        density: bulk(4430.0),
195        source: "TIMET, TIMETAL 6-4 properties, p. 1: 0.160 lb/in3 (4.43 g/cm3)",
196        url: "https://www.timet.com/assets/local/documents/technicalmanuals/TIMETAL_6-4_Properties.pdf",
197        basis: Basis::Published,
198    },
199    BuiltinMaterial {
200        id: "brass",
201        name: "Brass (C36000)",
202        density: bulk(8500.0),
203        source: "Copper Development Association, alloy C36000: specific gravity 8.5",
204        url: "https://alloys.copper.org/alloy/C36000",
205        basis: Basis::Published,
206    },
207    // Woods, at 12% moisture.
208    BuiltinMaterial {
209        id: "balsa",
210        name: "Balsa",
211        density: bulk(180.0),
212        source: "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), p. 2-21: about 180 kg/m3 when dry, often as little as 100",
213        url: WOOD_HANDBOOK,
214        basis: Basis::Published,
215    },
216    BuiltinMaterial {
217        id: "basswood",
218        name: "Basswood (American)",
219        density: wood(0.37),
220        source: WOOD_TABLE,
221        url: WOOD_HANDBOOK,
222        basis: Basis::Derived,
223    },
224    BuiltinMaterial {
225        id: "birch",
226        name: "Birch (yellow)",
227        density: wood(0.62),
228        source: WOOD_TABLE,
229        url: WOOD_HANDBOOK,
230        basis: Basis::Derived,
231    },
232    BuiltinMaterial {
233        id: "spruce",
234        name: "Spruce (Sitka)",
235        density: wood(0.40),
236        source: WOOD_TABLE,
237        url: WOOD_HANDBOOK,
238        basis: Basis::Derived,
239    },
240    BuiltinMaterial {
241        id: "pine",
242        name: "Pine (eastern white)",
243        density: wood(0.35),
244        source: WOOD_TABLE,
245        url: WOOD_HANDBOOK,
246        basis: Basis::Derived,
247    },
248    BuiltinMaterial {
249        id: "maple",
250        name: "Maple (sugar)",
251        density: wood(0.63),
252        source: WOOD_TABLE,
253        url: WOOD_HANDBOOK,
254        basis: Basis::Derived,
255    },
256    BuiltinMaterial {
257        id: "oak",
258        name: "Oak (northern red)",
259        density: wood(0.63),
260        source: WOOD_TABLE,
261        url: WOOD_HANDBOOK,
262        basis: Basis::Derived,
263    },
264    BuiltinMaterial {
265        id: "birch_plywood",
266        name: "Birch plywood",
267        density: bulk(680.0),
268        source: "Riga Wood, Plywood Handbook (2022), section 3.2, p. 62: birch plywood density (EN 323, 20 C and 65% RH), 680 kg/m3 +-50",
269        url: "https://www.finieris.com/wp-content/uploads/2024/04/Riga-Wood_Plywood-Handbook_2022-1.pdf",
270        basis: Basis::Published,
271    },
272    // Plastics.
273    BuiltinMaterial {
274        id: "pla",
275        name: "PLA",
276        density: bulk(1240.0),
277        source: "NatureWorks, Ingeo 4043D technical data sheet, p. 1: density 1.24 g/cc (ASTM D1505)",
278        url: "https://natureworksllc.com/getContentAsset/64260fd8-bfe0-49a4-af38-3dfdc4ef7f5c/53ffd608-340f-457b-b656-6b8cc0000bd4/TechnicalDataSheet_4043D_films.pdf?language=en",
279        basis: Basis::Published,
280    },
281    BuiltinMaterial {
282        id: "abs",
283        name: "ABS",
284        density: bulk(1040.0),
285        source: "SABIC, Cycolac MG47 technical data sheet, p. 1: density 1.04 g/cm3 (ISO 1183)",
286        url: "https://pc-api-public.sabic.com/api/v1/services/DocumentInfoService/?id=9ac7102f-8df7-e611-819b-06b69393ae39&language=en",
287        basis: Basis::Published,
288    },
289    BuiltinMaterial {
290        id: "petg",
291        name: "PETG",
292        density: bulk(1270.0),
293        source: "Eastman, Eastar 6763 technical data sheet (2021): density 1.27 g/cm3 (ISO 1183)",
294        url: "https://productcatalog.eastman.com/tds/ProdDatasheet.aspx?product=71040786&pn=eastar-6763-copolyester",
295        basis: Basis::Published,
296    },
297    BuiltinMaterial {
298        id: "nylon",
299        name: "Nylon 6/6",
300        density: bulk(1140.0),
301        source: "Celanese, Zytel 101L NC010 data sheet (2023), p. 3: density 1140 kg/m3 (ISO 1183)",
302        url: "https://quickparts.com/wp-content/uploads/2023/04/zytel%C2%AE-101l-nc010-gb.pdf",
303        basis: Basis::Published,
304    },
305    BuiltinMaterial {
306        id: "polycarbonate",
307        name: "Polycarbonate",
308        density: bulk(1200.0),
309        source: "Covestro, Makrolon 2405 ISO data sheet (2015), p. 3: density 1200 kg/m3 (ISO 1183-1)",
310        url: "https://www.okw.com/en/PC/RW_Makrolon_2405_en.pdf",
311        basis: Basis::Published,
312    },
313    BuiltinMaterial {
314        id: "acrylic",
315        name: "Acrylic (PMMA)",
316        density: bulk(1190.0),
317        source: "Roehm, ACRYLITE cast sheet physical properties (1235G, 2024), p. 6: specific gravity 1.19 (ASTM D792)",
318        url: "https://www.acrylite.co/files/content/acrylite.co/documents/product-information/ACRYLITE-cast-Physical-Properties.pdf",
319        basis: Basis::Published,
320    },
321    BuiltinMaterial {
322        id: "acetal",
323        name: "Acetal (POM, Delrin)",
324        density: bulk(1420.0),
325        source: "Delrin 100P NC010 data sheet, p. 1: density 1.42 g/cm3 (ISO 1183)",
326        url: "https://quickparts.com/wp-content/uploads/2023/05/QP-Materials-Delrin-100P-NC010.pdf",
327        basis: Basis::Published,
328    },
329    BuiltinMaterial {
330        id: "polystyrene",
331        name: "Polystyrene",
332        density: bulk(1040.0),
333        source: "AmSty, STYRON 685D product information (2015), p. 1: specific gravity 1.04 (ASTM D792)",
334        url: "https://amsty.com/images/documents/gpps/styron685d-tech-en.pdf",
335        basis: Basis::Published,
336    },
337    BuiltinMaterial {
338        id: "pvc",
339        name: "PVC (rigid)",
340        density: bulk(1400.0),
341        source: "Charlotte Pipe, PVC Schedule 40 DWV submittal, p. 5: specific gravity 1.40 (ASTM D792)",
342        url: "https://www.charlottepipe.com/uploads/documents/technical/SUB-PAC-PVC-DWV.pdf",
343        basis: Basis::Published,
344    },
345    BuiltinMaterial {
346        id: "hdpe",
347        name: "Polyethylene (HDPE)",
348        density: bulk(955.0),
349        source: "Chevron Phillips, Marlex HHM 5502BN technical data sheet (2020), p. 1: density 0.955 g/cm3 (ASTM D1505)",
350        url: "https://www.cpchem.com/sites/default/files/2020-10/TDS%20-%20Marlex%C2%AE%20HHM%205502BN%20Polyethylene.pdf",
351        basis: Basis::Published,
352    },
353    BuiltinMaterial {
354        id: "epoxy",
355        name: "Epoxy (cured laminating resin)",
356        density: bulk(1180.0),
357        source: "Gougeon Brothers, West System 105/205 technical data sheet (2014), p. 1: cured specific gravity 1.18",
358        url: "https://www.westsystem.com/app/uploads/2022/09/105_205-207-Combined.pdf",
359        basis: Basis::Published,
360    },
361    BuiltinMaterial {
362        id: "depron",
363        name: "Depron foam (3 mm)",
364        density: bulk(40.0),
365        source: "Depron insulation tiles technical data sheet (2013): foam density 40 kg/m3 at 3 mm (DIN EN ISO 845)",
366        url: "https://www.depron-daemmplatte.de/site/assets/files/1044/pdb-depron-d_mmplatte_gb_20160301.pdf",
367        basis: Basis::Published,
368    },
369    BuiltinMaterial {
370        id: "paper_bulk",
371        name: "Paper (office, bulk)",
372        density: bulk(755.0),
373        source: "HP Office paper product data sheet (2023), p. 1: 80 g/m2 (ISO 536) at 106 um (ISO 534)",
374        url: "https://hp-papers.eu/wp-content/uploads/2024/01/HP-Office-Product-Data-Sheet-EN-301023.pdf",
375        basis: Basis::Derived,
376    },
377    // Fabrics and films.
378    BuiltinMaterial {
379        id: "ripstop_nylon",
380        name: "Ripstop nylon (1.1 oz/yd2)",
381        density: surface(1.1 * 0.033_905_7),
382        source: "MIL-C-7020H (1992), Table I, p. 5, types I and Ia: 1.1 oz/yd2 maximum",
383        url: "http://www.arbitrarytechnology.com/rigging/milspecs/mil-c-7020_cloth-nylon-parachute-ripstop.pdf",
384        basis: Basis::Maximum,
385    },
386    BuiltinMaterial {
387        id: "ripstop_nylon_heavy",
388        name: "Ripstop nylon (1.6 oz/yd2)",
389        density: surface(1.6 * 0.033_905_7),
390        source: "MIL-C-7020H (1992), Table I, p. 5, types III and IIIa: 1.6 oz/yd2 maximum",
391        url: "http://www.arbitrarytechnology.com/rigging/milspecs/mil-c-7020_cloth-nylon-parachute-ripstop.pdf",
392        basis: Basis::Maximum,
393    },
394    BuiltinMaterial {
395        id: "silnylon",
396        name: "Silicone-coated nylon",
397        density: surface(0.042),
398        source: "Ripstop by the Roll, 1.1 oz silnylon listing: finished weight about 42 g/m2",
399        url: "https://ripstopbytheroll.com/products/1-1-oz-silnylon",
400        basis: Basis::Vendor,
401    },
402    BuiltinMaterial {
403        id: "mylar",
404        name: "Mylar (polyester film, 1 mil)",
405        density: surface(1390.0 * 25.4e-6),
406        source: "DuPont Teijin Films, Mylar physical-thermal properties H-37232-3 (2003), Table 1: density 1.390 g/cm3 (ASTM D1505), at 25.4 um",
407        url: "https://stenbacka.fi/wp-content/uploads/sites/3/2016/07/mylar_a_fysikaaliset_ominaisuudet.pdf",
408        basis: Basis::Derived,
409    },
410    BuiltinMaterial {
411        id: "polyethylene_film",
412        name: "Polyethylene film (LDPE, 1 mil)",
413        density: surface(925.0 * 25.4e-6),
414        source: "Dow LDPE 352E data sheet (2011): specific gravity 0.925 (ASTM D792), at 25.4 um",
415        url: "https://stavianchem.com/sites/default/files/product-specs/352E.pdf",
416        basis: Basis::Derived,
417    },
418    BuiltinMaterial {
419        id: "paper",
420        name: "Paper (office, 80 g/m2)",
421        density: surface(0.080),
422        source: "HP Office paper product data sheet (2023), p. 1: 80 g/m2 (ISO 536)",
423        url: "https://hp-papers.eu/wp-content/uploads/2024/01/HP-Office-Product-Data-Sheet-EN-301023.pdf",
424        basis: Basis::Published,
425    },
426    BuiltinMaterial {
427        id: "nomex_cloth",
428        name: "Nomex cloth (6 oz/yd2)",
429        density: surface(6.0 * 0.033_905_7),
430        source: "MIL-C-83429B (1990), Table III, p. 7: 4.7 to 6.0 oz/yd2 for the aramid cloth types; 6.0 taken",
431        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-C/download.php?spec=MIL-C-83429B.015471.PDF",
432        basis: Basis::Maximum,
433    },
434    // Cords and lines.
435    BuiltinMaterial {
436        id: "nylon_cord_type_i",
437        name: "Nylon cord, type I (shroud line)",
438        density: line(1.488_164 / 950.0),
439        source: "MIL-C-5040H (1994), Table II, p. 6, type I: at least 950 ft/lb",
440        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-C/download.php?spec=MIL-C-5040H.031090.pdf",
441        basis: Basis::Maximum,
442    },
443    BuiltinMaterial {
444        id: "paracord_type_iii",
445        name: "Nylon cord, type III (550 paracord)",
446        density: line(1.488_164 / 225.0),
447        source: "MIL-C-5040H (1994), Table II, p. 6, type III: at least 225 ft/lb",
448        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-C/download.php?spec=MIL-C-5040H.031090.pdf",
449        basis: Basis::Maximum,
450    },
451    BuiltinMaterial {
452        id: "tubular_nylon_half_inch",
453        name: "Tubular nylon webbing, 1/2 in",
454        density: line(0.50 * 0.031_003_4),
455        source: "MIL-W-5625K (1991), Table I, p. 4: 1/2 in, 0.50 oz/yd maximum",
456        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-W/download.php?spec=MIL-W-5625K.014723.pdf",
457        basis: Basis::Maximum,
458    },
459    BuiltinMaterial {
460        id: "tubular_nylon_9_16_inch",
461        name: "Tubular nylon webbing, 9/16 in",
462        density: line(0.60 * 0.031_003_4),
463        source: "MIL-W-5625K (1991), Table I, p. 4: 9/16 in, 0.60 oz/yd maximum",
464        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-W/download.php?spec=MIL-W-5625K.014723.pdf",
465        basis: Basis::Maximum,
466    },
467    BuiltinMaterial {
468        id: "tubular_nylon_1_inch",
469        name: "Tubular nylon webbing, 1 in",
470        density: line(1.70 * 0.031_003_4),
471        source: "MIL-W-5625K (1991), Table I, p. 4: 1 in, 1.70 oz/yd maximum",
472        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-W/download.php?spec=MIL-W-5625K.014723.pdf",
473        basis: Basis::Maximum,
474    },
475    BuiltinMaterial {
476        id: "kevlar_cord_eighth_inch",
477        name: "Kevlar cord, 1/8 in",
478        density: line(1.43e-3 / 0.3048),
479        source: "Giant Leap Rocketry, Weights and Measures (2017), p. 3: 1/8 in Kevlar, 1.43 g/ft",
480        url: "https://cdn.shopify.com/s/files/1/0518/3809/1436/files/GLRWeightsMeasures_r1.pdf",
481        basis: Basis::Vendor,
482    },
483    BuiltinMaterial {
484        id: "kevlar_cord_quarter_inch",
485        name: "Kevlar cord, 1/4 in",
486        density: line(2.95e-3 / 0.3048),
487        source: "Giant Leap Rocketry, Weights and Measures (2017), p. 3: 1/4 in Kevlar, 2.95 g/ft",
488        url: "https://cdn.shopify.com/s/files/1/0518/3809/1436/files/GLRWeightsMeasures_r1.pdf",
489        basis: Basis::Vendor,
490    },
491    BuiltinMaterial {
492        id: "elastic_cord_quarter_inch",
493        name: "Elastic shock cord (bungee), 1/4 in",
494        density: line(2.4 * 0.453_592_37 / 30.48),
495        source: "MIL-C-5651D (1985), Table III, p. 16: 1/4 in, 2.4 lb/100 ft",
496        url: "https://everyspec.com/MIL-SPECS/MIL-SPECS-MIL-C/download.php?spec=MIL-C-5651D.013946.pdf",
497        basis: Basis::Published,
498    },
499    BuiltinMaterial {
500        id: "kevlar_thread",
501        name: "Kevlar thread (Tex 80)",
502        density: line(0.496_055 / 5000.0),
503        source: "A-A-55220 (1995), Table I, p. 2: Tex 80, at least 5000 yd/lb",
504        url: "https://everyspec.com/COMML_ITEM_DESC/A-A-55000_A-A-55999/download.php?spec=A-A-55220.012789.PDF",
505        basis: Basis::Maximum,
506    },
507];
508
509/// The built-in material with `id`.
510pub fn find(id: &str) -> Option<&'static BuiltinMaterial> {
511    BUILTIN.iter().find(|m| m.id == id)
512}
513
514/// A built-in material's in-plane shear modulus, for fin flutter.
515#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
516pub struct BuiltinShearModulus {
517    /// The [`BuiltinMaterial::id`] it belongs to.
518    pub id: &'static str,
519    /// Shear modulus, Pa.
520    pub shear_modulus_pa: f64,
521    /// The source, with the table or page.
522    pub source: &'static str,
523    /// Where the source was read.
524    pub url: &'static str,
525    /// How the value relates to the source.
526    pub basis: Basis,
527}
528
529/// One pound per square inch, Pa (NIST SP 811, 2008, B.9).
530const PSI_PA: f64 = 6_894.757;
531
532/// A modulus in 10⁶ psi, Pa.
533const fn msi(value: f64) -> f64 {
534    value * 1e6 * PSI_PA
535}
536
537/// A wood's `G_LT` from the Wood Handbook's ratio `G_LT/E_L` and bending modulus in MPa, Pa.
538const fn wood_shear(g_lt_over_e_l: f64, e_bend_mpa: f64) -> f64 {
539    g_lt_over_e_l * 1.10 * e_bend_mpa * 1e6
540}
541
542/// An isotropic material's `E / (2 (1 + ν))`, Pa.
543const fn isotropic_shear(e_pa: f64, poisson: f64) -> f64 {
544    e_pa / (2.0 * (1.0 + poisson))
545}
546
547const MIL_HDBK_5J: &str =
548    "https://everyspec.com/MIL-HDBK/MIL-HDBK-0001-0099/download.php?spec=MIL_HDBK_5J.139.pdf";
549
550/// Every built-in shear modulus.
551pub const SHEAR_MODULI: &[BuiltinShearModulus] = &[
552    BuiltinShearModulus {
553        id: "aluminum_6061",
554        shear_modulus_pa: msi(3.8),
555        source: "MIL-HDBK-5J (2003), Table 3.6.2.0(b1), 6061 sheet T4 to T62, p. 3-264: \
556                 G 3.8 x 10^3 ksi",
557        url: MIL_HDBK_5J,
558        basis: Basis::Published,
559    },
560    BuiltinShearModulus {
561        id: "aluminum_7075",
562        shear_modulus_pa: msi(3.9),
563        source: "MIL-HDBK-5J (2003), Table 3.7.6.0(b1), 7075-T6 sheet and T651 plate, p. 3-371: \
564                 G 3.9 x 10^3 ksi",
565        url: MIL_HDBK_5J,
566        basis: Basis::Published,
567    },
568    BuiltinShearModulus {
569        id: "steel",
570        shear_modulus_pa: msi(11.0),
571        source: "MIL-HDBK-5J (2003), Table 2.2.1.0(b), AISI 1025, p. 2-8: G 11.0 x 10^3 ksi",
572        url: MIL_HDBK_5J,
573        basis: Basis::Published,
574    },
575    BuiltinShearModulus {
576        id: "titanium_6al4v",
577        shear_modulus_pa: msi(6.2),
578        source: "MIL-HDBK-5J (2003), Table 5.4.1.0(b), Ti-6Al-4V annealed, p. 5-53: G 6.2 x 10^3 \
579                 ksi; TIMET's TIMETAL 6-4 properties (p. 15) give 6.2 and 6.66 x 10^6 psi",
580        url: MIL_HDBK_5J,
581        basis: Basis::Published,
582    },
583    BuiltinShearModulus {
584        id: "carbon_fiber",
585        shear_modulus_pa: msi(0.70),
586        source: "NCAMP NCP-RP-2010-008 Rev D (2011), Hexcel 8552 AS4 unitape, Table 3-3, p. 37: \
587                 in-plane shear modulus G12 0.70 Msi, room temperature dry",
588        url: "https://www.wichita.edu/industry_and_defense/NIAR/Research/hexcel-8552/AS4-Unitape-3.pdf",
589        basis: Basis::Published,
590    },
591    BuiltinShearModulus {
592        id: "balsa",
593        shear_modulus_pa: wood_shear(0.037, 3_400.0),
594        source: concat!(
595            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
596            "(G_LT/E_L 0.037) and Table 5-5a, p. 5-18 (modulus of elasticity 3,400 MPa at 12%)"
597        ),
598        url: WOOD_HANDBOOK,
599        basis: Basis::Derived,
600    },
601    BuiltinShearModulus {
602        id: "basswood",
603        shear_modulus_pa: wood_shear(0.046, 10_100.0),
604        source: concat!(
605            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
606            "(G_LT/E_L 0.046) and Table 5-3a, p. 5-4 (modulus of elasticity 10,100 MPa at 12%)"
607        ),
608        url: WOOD_HANDBOOK,
609        basis: Basis::Derived,
610    },
611    BuiltinShearModulus {
612        id: "birch",
613        shear_modulus_pa: wood_shear(0.068, 13_900.0),
614        source: concat!(
615            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
616            "(G_LT/E_L 0.068) and Table 5-3a, p. 5-4 (modulus of elasticity 13,900 MPa at 12%)"
617        ),
618        url: WOOD_HANDBOOK,
619        basis: Basis::Derived,
620    },
621    BuiltinShearModulus {
622        id: "spruce",
623        shear_modulus_pa: wood_shear(0.061, 10_800.0),
624        source: concat!(
625            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
626            "(G_LT/E_L 0.061) and Table 5-3a, p. 5-8 (modulus of elasticity 10,800 MPa at 12%)"
627        ),
628        url: WOOD_HANDBOOK,
629        basis: Basis::Derived,
630    },
631    BuiltinShearModulus {
632        id: "maple",
633        shear_modulus_pa: wood_shear(0.063, 12_600.0),
634        source: concat!(
635            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
636            "(G_LT/E_L 0.063) and Table 5-3a, p. 5-5 (modulus of elasticity 12,600 MPa at 12%)"
637        ),
638        url: WOOD_HANDBOOK,
639        basis: Basis::Derived,
640    },
641    BuiltinShearModulus {
642        id: "oak",
643        shear_modulus_pa: wood_shear(0.081, 12_500.0),
644        source: concat!(
645            "Forest Products Laboratory, Wood Handbook, FPL-GTR-190 (2010), Table 5-1, p. 5-2 ",
646            "(red oak, G_LT/E_L 0.081) and Table 5-3a, p. 5-5 (northern red, modulus of ",
647            "elasticity 12,500 MPa at 12%)"
648        ),
649        url: WOOD_HANDBOOK,
650        basis: Basis::Derived,
651    },
652    BuiltinShearModulus {
653        id: "birch_plywood",
654        shear_modulus_pa: 750e6,
655        source: "Riga Wood, Plywood Handbook (2022), Table 4.11, p. 89: mean modulus of rigidity \
656                 in panel shear (EN 789) 750 N/mm2, every thickness, both directions",
657        url: "https://www.finieris.com/wp-content/uploads/2024/04/Riga-Wood_Plywood-Handbook_2022-1.pdf",
658        basis: Basis::Published,
659    },
660    BuiltinShearModulus {
661        id: "nylon",
662        shear_modulus_pa: isotropic_shear(1_400e6, 0.43),
663        source: "Celanese, Zytel 101L NC010 data sheet (2023): conditioned tensile modulus \
664                 1400 MPa (p. 1) and Poisson's ratio 0.43 (p. 2)",
665        url: "https://quickparts.com/wp-content/uploads/2023/04/zytel%C2%AE-101l-nc010-gb.pdf",
666        basis: Basis::Derived,
667    },
668    BuiltinShearModulus {
669        id: "acetal",
670        shear_modulus_pa: isotropic_shear(421_000.0 * PSI_PA, 0.37),
671        source: "Delrin 100P NC010 data sheet: tensile modulus 421000 psi (p. 1) and Poisson's \
672                 ratio 0.37 (p. 2)",
673        url: "https://quickparts.com/wp-content/uploads/2023/05/QP-Materials-Delrin-100P-NC010.pdf",
674        basis: Basis::Derived,
675    },
676];
677
678/// The built-in shear modulus of the material with `id`, if a source gives one.
679pub fn shear_modulus(id: &str) -> Option<&'static BuiltinShearModulus> {
680    SHEAR_MODULI.iter().find(|m| m.id == id)
681}
682
683/// The built-in shear modulus of `material`, a design's copy of a built-in one (its name and
684/// density both match); `None` for a material that isn't built in or has no modulus.
685pub fn shear_modulus_of(material: &Material) -> Option<&'static BuiltinShearModulus> {
686    let builtin = BUILTIN
687        .iter()
688        .find(|m| m.name == material.name && m.density == material.density)?;
689    shear_modulus(builtin.id)
690}
691
692#[cfg(test)]
693mod tests {
694    use std::collections::BTreeSet;
695
696    use super::*;
697
698    #[test]
699    fn every_entry_has_a_unique_id_a_source_and_a_positive_density() {
700        let mut ids = BTreeSet::new();
701        for m in BUILTIN {
702            assert!(ids.insert(m.id), "duplicate id {}", m.id);
703            assert!(
704                m.id.bytes()
705                    .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_'),
706                "{}",
707                m.id
708            );
709            assert!(!m.name.is_empty() && m.source.len() > 20, "{}", m.id);
710            assert!(m.url.starts_with("http"), "{}", m.id);
711            let value = match m.density {
712                Density::Bulk { kg_m3 } => kg_m3,
713                Density::Surface { kg_m2 } => kg_m2,
714                Density::Line { kg_m } => kg_m,
715            };
716            assert!(value.is_finite() && value > 0.0, "{}", m.id);
717        }
718        assert_eq!(BUILTIN.len(), 49);
719    }
720
721    #[test]
722    fn conversions_reproduce_the_sources() {
723        let kg_m3 = |id: &str| find(id).unwrap().material().bulk_kg_m3("test").unwrap();
724        let kg_m2 = |id: &str| find(id).unwrap().material().surface_kg_m2("test").unwrap();
725        let kg_m = |id: &str| find(id).unwrap().material().line_kg_m("test").unwrap();
726        // The Wood Handbook's own example: white ash, G12 = 0.605, is 678 kg/m³ (p. 4-10).
727        let Density::Bulk { kg_m3: ash } = wood(0.605) else {
728            unreachable!()
729        };
730        assert_eq!(ash.round(), 678.0);
731        assert!((kg_m3("basswood") - 414.4).abs() < 1e-9);
732        assert!((kg_m3("spruce") - 448.0).abs() < 1e-9);
733        // 1.1 oz/yd² is 37.3 g/m²; type III paracord at 225 ft/lb is 6.61 g/m; 1/4" Kevlar at
734        // 2.95 g/ft is 9.68 g/m.
735        assert!((kg_m2("ripstop_nylon") - 0.037_296).abs() < 1e-6);
736        assert!((kg_m("paracord_type_iii") - 0.006_614).abs() < 1e-6);
737        assert!((kg_m("kevlar_cord_quarter_inch") - 0.009_678).abs() < 1e-6);
738        assert!((kg_m("elastic_cord_quarter_inch") - 0.035_72).abs() < 1e-5);
739        assert!((kg_m2("mylar") - 0.035_306).abs() < 1e-6);
740        assert!(find("unobtainium").is_none());
741    }
742
743    #[test]
744    fn every_shear_modulus_belongs_to_a_bulk_material_and_has_a_source() {
745        let mut ids = BTreeSet::new();
746        for m in SHEAR_MODULI {
747            assert!(ids.insert(m.id), "duplicate id {}", m.id);
748            let material = find(m.id).unwrap_or_else(|| panic!("no material {}", m.id));
749            assert!(matches!(material.density, Density::Bulk { .. }), "{}", m.id);
750            assert!(m.source.len() > 20 && m.url.starts_with("http"), "{}", m.id);
751            assert!(
752                m.shear_modulus_pa.is_finite() && m.shear_modulus_pa > 0.0,
753                "{}",
754                m.id
755            );
756        }
757        assert_eq!(SHEAR_MODULI.len(), 14);
758        for none in ["fiberglass_g10", "pine", "polycarbonate"] {
759            assert!(shear_modulus(none).is_none(), "{none}");
760        }
761        // A design's copy of a built-in material finds its modulus by name.
762        let plywood = find("birch_plywood").unwrap().material();
763        assert_eq!(shear_modulus_of(&plywood).unwrap().id, "birch_plywood");
764        assert!(shear_modulus_of(&Material::bulk("Birch plywood (mine)", 680.0)).is_none());
765        assert!(shear_modulus_of(&Material::bulk("Birch plywood", 500.0)).is_none());
766        let names: BTreeSet<_> = BUILTIN.iter().map(|m| m.name).collect();
767        assert_eq!(names.len(), BUILTIN.len(), "built-in names must be unique");
768        assert!(shear_modulus_of(&find("fiberglass_g10").unwrap().material()).is_none());
769    }
770
771    #[test]
772    fn shear_moduli_reproduce_the_sources() {
773        let gpa = |id: &str| shear_modulus(id).unwrap().shear_modulus_pa / 1e9;
774        // 3.8, 3.9, 11.0 and 6.2 × 10⁶ psi.
775        assert!((gpa("aluminum_6061") - 26.200_077).abs() < 1e-6);
776        assert!((gpa("aluminum_7075") - 26.889_552).abs() < 1e-6);
777        assert!((gpa("steel") - 75.842_327).abs() < 1e-6);
778        assert!((gpa("titanium_6al4v") - 42.747_493).abs() < 1e-6);
779        assert!((gpa("carbon_fiber") - 4.826_330).abs() < 1e-6);
780        // 0.046 · 1.10 · 10 100 MPa and 0.037 · 1.10 · 3400 MPa.
781        assert!((gpa("basswood") - 0.511_06).abs() < 1e-9);
782        assert!((gpa("balsa") - 0.138_38).abs() < 1e-9);
783        // 1400 / 2.86 MPa and 421 000 psi / 2.74.
784        assert!((gpa("nylon") - 0.489_510).abs() < 1e-6);
785        assert!((gpa("acetal") - 1.059_377).abs() < 1e-6);
786        assert!((gpa("birch_plywood") - 0.75).abs() < 1e-12);
787    }
788}