loadpath 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. loadpath/README.md +294 -0
  2. loadpath/SKILL.md +584 -0
  3. loadpath/__init__.py +17 -0
  4. loadpath/__main__.py +1202 -0
  5. loadpath/am.py +316 -0
  6. loadpath/amgfast.py +326 -0
  7. loadpath/assembly.py +985 -0
  8. loadpath/benchmarks/__init__.py +0 -0
  9. loadpath/benchmarks/beams.py +266 -0
  10. loadpath/benchmarks/cases/ge-bracket.json +380 -0
  11. loadpath/benchmarks/evaluate.py +172 -0
  12. loadpath/benchmarks/mechanisms.py +416 -0
  13. loadpath/benchmarks/problems.py +140 -0
  14. loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
  15. loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
  16. loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
  17. loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
  18. loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
  19. loadpath/benchmarks/reference.py +138 -0
  20. loadpath/benchmarks/run.py +301 -0
  21. loadpath/brep.py +1470 -0
  22. loadpath/bucklingopt.py +300 -0
  23. loadpath/buildsim.py +1490 -0
  24. loadpath/cadimport.py +904 -0
  25. loadpath/calibration.py +885 -0
  26. loadpath/cavities.py +211 -0
  27. loadpath/checks/__init__.py +0 -0
  28. loadpath/checks/dfm.py +354 -0
  29. loadpath/checks/exchanger.py +7 -0
  30. loadpath/checks/fea.py +212 -0
  31. loadpath/checks/fit.py +475 -0
  32. loadpath/checks/networks.py +260 -0
  33. loadpath/checks/opening.py +67 -0
  34. loadpath/checks/sdf_kit.py +7 -0
  35. loadpath/conjugate.py +267 -0
  36. loadpath/constrained.py +329 -0
  37. loadpath/contact.py +691 -0
  38. loadpath/coopt.py +342 -0
  39. loadpath/cores.py +163 -0
  40. loadpath/crease.py +1138 -0
  41. loadpath/critic.py +212 -0
  42. loadpath/crosscheck.py +1107 -0
  43. loadpath/cutcell.py +510 -0
  44. loadpath/data/lattice_buckling.json +439 -0
  45. loadpath/data/lattice_tensors.json +27449 -0
  46. loadpath/data/published_experiments.json +71 -0
  47. loadpath/decimate.py +177 -0
  48. loadpath/delivery.py +190 -0
  49. loadpath/directions.py +262 -0
  50. loadpath/domain.py +630 -0
  51. loadpath/drains.py +615 -0
  52. loadpath/durability.py +413 -0
  53. loadpath/editor.html +3135 -0
  54. loadpath/editor.py +610 -0
  55. loadpath/escape.py +354 -0
  56. loadpath/evals/__init__.py +0 -0
  57. loadpath/evals/baselines/coarse.json +1543 -0
  58. loadpath/evals/baselines/full.json +1497 -0
  59. loadpath/evals/brief_kinds/__init__.py +1 -0
  60. loadpath/evals/brief_kinds/heatx.py +387 -0
  61. loadpath/evals/brief_kinds/lattice.py +571 -0
  62. loadpath/evals/brief_kinds/multimat.py +593 -0
  63. loadpath/evals/briefs/assets/bracket.step +1201 -0
  64. loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
  65. loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
  66. loadpath/evals/briefs/assets/motor-body.stl +0 -0
  67. loadpath/evals/briefs/assets/sketch.png +0 -0
  68. loadpath/evals/briefs/base-adapter-fdm.json +492 -0
  69. loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
  70. loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
  71. loadpath/evals/briefs/camera-boom-mjf.json +339 -0
  72. loadpath/evals/briefs/camera-mount-pla.json +203 -0
  73. loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
  74. loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
  75. loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
  76. loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
  77. loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
  78. loadpath/evals/briefs/forearm-fdm.json +146 -0
  79. loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
  80. loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
  81. loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
  82. loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
  83. loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
  84. loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
  85. loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
  86. loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
  87. loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
  88. loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
  89. loadpath/evals/briefs/lighten-step-bracket.json +220 -0
  90. loadpath/evals/briefs/nema17-mount-stl.json +429 -0
  91. loadpath/evals/briefs/pillow-block-sls.json +220 -0
  92. loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
  93. loadpath/evals/briefs/rod-hook-pla.json +154 -0
  94. loadpath/evals/briefs/sensor-arm-resin.json +150 -0
  95. loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
  96. loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
  97. loadpath/evals/briefs/sketch-clevis.json +229 -0
  98. loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
  99. loadpath/evals/briefs/tool-flange-ti64.json +280 -0
  100. loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
  101. loadpath/evals/briefs/turret-fdm.json +281 -0
  102. loadpath/evals/briefs/upper-arm-sls.json +179 -0
  103. loadpath/evals/briefs/urdf-forearm.json +233 -0
  104. loadpath/evals/briefs/wrist-link-mjf.json +278 -0
  105. loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
  106. loadpath/evals/briefs.py +393 -0
  107. loadpath/evals/cases/arm-link-cast.json +157 -0
  108. loadpath/evals/cases/arm-link-driven.json +171 -0
  109. loadpath/evals/cases/arm-link-sandwich.json +315 -0
  110. loadpath/evals/cases/arm-link.json +111 -0
  111. loadpath/evals/cases/bell-crank.json +116 -0
  112. loadpath/evals/cases/grip-turret.json +479 -0
  113. loadpath/evals/cases/grip-upper-arm.json +253 -0
  114. loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
  115. loadpath/evals/cases/grip-wrist-yoke.json +831 -0
  116. loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
  117. loadpath/evals/cases/mbb-beam.json +61 -0
  118. loadpath/evals/cases/motor-body.stl +0 -0
  119. loadpath/evals/cases/motor-mount.json +265 -0
  120. loadpath/evals/cases/printed-hook.json +77 -0
  121. loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
  122. loadpath/evals/cases/wall-bracket.json +126 -0
  123. loadpath/evals/corpus.py +179 -0
  124. loadpath/evals/pareto.py +90 -0
  125. loadpath/evals/run.py +860 -0
  126. loadpath/examples/arm-link.json +25 -0
  127. loadpath/examples/bumpered-bracket-compression.json +225 -0
  128. loadpath/examples/bumpered-bracket-lattice.json +204 -0
  129. loadpath/examples/bumpered-bracket.json +34 -0
  130. loadpath/examples/gripper-finger-mm.json +185 -0
  131. loadpath/examples/heat-exchanger-counterflow.json +21 -0
  132. loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
  133. loadpath/examples/heat-exchanger-crossflow.json +20 -0
  134. loadpath/examples/pedal-lattice.json +23 -0
  135. loadpath/examples/pressure-plenum.json +12 -0
  136. loadpath/examples/shelf-bracket.json +27 -0
  137. loadpath/exchanger.py +831 -0
  138. loadpath/fasteners.py +199 -0
  139. loadpath/fem.py +668 -0
  140. loadpath/femops.py +70 -0
  141. loadpath/fieldjit.py +981 -0
  142. loadpath/fieldops.py +119 -0
  143. loadpath/fields.py +3697 -0
  144. loadpath/fieldtorch.py +464 -0
  145. loadpath/flow.py +984 -0
  146. loadpath/flowtopo.py +300 -0
  147. loadpath/gpu.py +521 -0
  148. loadpath/heatx.py +1067 -0
  149. loadpath/heatx_thermal.py +432 -0
  150. loadpath/homogenized.py +886 -0
  151. loadpath/ibrep.py +552 -0
  152. loadpath/impact.py +693 -0
  153. loadpath/infill.py +376 -0
  154. loadpath/interface_library.py +340 -0
  155. loadpath/interlock.py +186 -0
  156. loadpath/isolator.py +149 -0
  157. loadpath/knn.py +204 -0
  158. loadpath/lattice.py +1065 -0
  159. loadpath/lattice3mf.py +760 -0
  160. loadpath/lattice_buckling.py +245 -0
  161. loadpath/lattice_grade.py +325 -0
  162. loadpath/lattice_orient.py +595 -0
  163. loadpath/lattice_print.py +159 -0
  164. loadpath/lattice_tensors.py +558 -0
  165. loadpath/latticestep.py +1210 -0
  166. loadpath/manufacture.py +446 -0
  167. loadpath/material_library.py +489 -0
  168. loadpath/materials.py +144 -0
  169. loadpath/mcp_server.py +786 -0
  170. loadpath/mechanism.py +218 -0
  171. loadpath/mechanism_nl.py +811 -0
  172. loadpath/meshops.py +452 -0
  173. loadpath/mg.py +853 -0
  174. loadpath/mma.py +214 -0
  175. loadpath/modal.py +210 -0
  176. loadpath/multimat.py +1788 -0
  177. loadpath/narrowband.py +418 -0
  178. loadpath/nlkernel.py +241 -0
  179. loadpath/nonlinear.py +1095 -0
  180. loadpath/orient.py +334 -0
  181. loadpath/porous.py +994 -0
  182. loadpath/pressure.py +171 -0
  183. loadpath/preview.py +335 -0
  184. loadpath/printcheck.py +263 -0
  185. loadpath/printsetup.py +207 -0
  186. loadpath/processcheck.py +376 -0
  187. loadpath/quick.py +113 -0
  188. loadpath/recall.py +406 -0
  189. loadpath/reconstruct.py +797 -0
  190. loadpath/render.py +436 -0
  191. loadpath/resolved.py +189 -0
  192. loadpath/schema.py +1701 -0
  193. loadpath/sdf_kit.py +843 -0
  194. loadpath/selfx.py +292 -0
  195. loadpath/shapes.py +584 -0
  196. loadpath/sharp.py +717 -0
  197. loadpath/simp.py +1170 -0
  198. loadpath/sizing.py +312 -0
  199. loadpath/slicer.py +321 -0
  200. loadpath/spec.py +1072 -0
  201. loadpath/step.py +806 -0
  202. loadpath/struts.py +639 -0
  203. loadpath/study.py +1937 -0
  204. loadpath/tasks.py +822 -0
  205. loadpath/thermal.py +1691 -0
  206. loadpath/threemf.py +487 -0
  207. loadpath/tune.py +437 -0
  208. loadpath/validation.py +145 -0
  209. loadpath/verify.py +1215 -0
  210. loadpath/viewer.html +694 -0
  211. loadpath/viewer.py +446 -0
  212. loadpath/viscoelastic.py +106 -0
  213. loadpath/voxelprint.py +582 -0
  214. loadpath/voxels.py +402 -0
  215. loadpath-0.1.1.dist-info/METADATA +319 -0
  216. loadpath-0.1.1.dist-info/RECORD +220 -0
  217. loadpath-0.1.1.dist-info/WHEEL +5 -0
  218. loadpath-0.1.1.dist-info/entry_points.txt +2 -0
  219. loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
  220. loadpath-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,489 @@
1
+ """
2
+ The material library: one card per material, every number with its source.
3
+
4
+ A study names a material ("petg", "pa12", "ti64"); materials.MATERIALS is
5
+ built from these cards, and so are thermal.CONDUCTIVITY and the across-
6
+ layer factors a card supplies to a layered process (spec.study_from_dict).
7
+ Each value is (number, source id, note); `SOURCES` says what each id is and
8
+ where to read it. The datasheets were read on 2026-10-30.
9
+
10
+ How a card is chosen. Each generic name takes its values from one named
11
+ datasheet, so the numbers on a card are consistent with each other (the
12
+ same printer, slicer profile and test). Brands differ more than the
13
+ anisotropy does: PETG's in-plane strength reads 47 MPa on Prusament's
14
+ sheet and 32 MPa on Polymaker's, and PLA's across-layer strength is 33% of
15
+ in-plane on Prusament's (an interlayer adhesion test) and 93% on
16
+ Polymaker's (a Z-printed tensile bar). docs/materials.md lists the spread.
17
+ A part that matters gets coupons printed and a calibrated card
18
+ (calibration.py), which overrides the library card of the same name.
19
+
20
+ What the code reads:
21
+ E, nu, strength, density every analysis (materials.Material)
22
+ strengthZ across-layer strength -> process.interlayer
23
+ EZ across-layer modulus -> process stiffnessAcrossLayers
24
+ conductivity thermal.CONDUCTIVITY (heat paths, thermal TO)
25
+ specificHeat J/(kg K) -> thermal.SPECIFIC_HEAT (heat-up and cool-down)
26
+ hdt heat deflection temperature at 0.45 MPa: the
27
+ temperature checks warn above it (durability)
28
+ cte thermal expansion, um/(m K) -> thermal.EXPANSION
29
+ retention modulus kept vs temperature -> thermal.MODULUS_RETENTION
30
+ (and, as a proxy, strength: durability)
31
+ The rest (elongation, Tg, hdt18, fatigue) is carried for the report and
32
+ for people; fatigue and creep design values live in durability.DURABILITY,
33
+ which cites its own literature.
34
+
35
+ Values a datasheet does not give are marked with the source "estimate"
36
+ (Poisson's ratio for polymers, mostly) and say why that number.
37
+ """
38
+ from __future__ import annotations
39
+
40
+ from dataclasses import dataclass
41
+
42
+ __all__ = ["Source", "SOURCES", "LIBRARY", "card", "card_names", "value", "describe"]
43
+
44
+ ACCESSED = "2026-10-30"
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class Source:
49
+ title: str
50
+ url: str = ""
51
+ note: str = ""
52
+
53
+ def to_dict(self):
54
+ return {"title": self.title, "url": self.url, "note": self.note}
55
+
56
+
57
+ SOURCES = {
58
+ "prusament-pla": Source(
59
+ "Prusament PLA technical datasheet, v1.1 (27-07-2022), Prusa Polymers",
60
+ "https://www.matterhackers.com/r/xFvGl8",
61
+ "ISO 527-1 on specimens printed on an Original Prusa i3 MK3, 100% rectilinear infill; "
62
+ "'Vertical xz' is printed on edge, not across layers; interlayer adhesion is Prusa's own test"),
63
+ "prusament-petg": Source(
64
+ "Prusament PETG technical datasheet, v1.1 (16-02-2022), Prusa Polymers",
65
+ "https://www.3dprintergear.com.au/assets/files/technical-data-sheet%20(1).pdf",
66
+ "as Prusament PLA"),
67
+ "polylite-abs": Source(
68
+ "Polymaker PolyLite ABS TDS v5.2",
69
+ "https://cdn.shopify.com/s/files/1/0789/2979/files/PolyLite_ABS_TDS_V5.2.pdf",
70
+ "ISO 527 printed X-Y and Z specimens, 100% infill"),
71
+ "polylite-asa": Source(
72
+ "Polymaker PolyLite ASA TDS v5.1",
73
+ "https://cdn.shopify.com/s/files/1/0603/6609/files/PolyLite_ASA_TDS_V5.1.pdf",
74
+ "ISO 527 printed X-Y and Z specimens, 100% infill"),
75
+ "ultimaker-tpu95a": Source(
76
+ "UltiMaker TPU 95A technical data sheet v2.01 (15-09-2025)",
77
+ "https://um-support-files.ultimaker.com/materials/2.85mm/tds/TPU-95A/TDS-TPU95A-V2.01-250915.pdf",
78
+ "ASTM D3039 printed XY / YZ / Z specimens"),
79
+ "bambu-paht-cf": Source(
80
+ "Bambu Lab PAHT-CF technical data sheet v2",
81
+ "https://uwaterloo.ca/rapid-prototyping-centre/sites/default/files/uploads/documents/"
82
+ "bambu_paht-cf_technical_data_sheet_v2.pdf",
83
+ "ISO 527, dry state, printed X-Y and Z specimens, 100% infill"),
84
+ "hp-5200-whitepaper": Source(
85
+ "HP 3D Printing materials for the HP Jet Fusion 5200 Series: Mechanical Properties (white paper)",
86
+ "https://info.sculpteo.com/hubfs/Material%20documentation/PA11%20HP/"
87
+ "TDS%20HP%20pa12%20pa12gb%20pa11%20pp%20(1).pdf",
88
+ "ASTM D638 Type I, Balanced print profile, 349-part test job, XY and Z averages"),
89
+ "eos-pa3200gf": Source(
90
+ "EOS PA 3200 GF material data sheet, status 30.08.2026",
91
+ "https://www.indus3design.com/wp-content/uploads/2026/08/i3d-datasheets-eos-pa-3200-gf.pdf",
92
+ "laser sintered; ISO 527-1/-2 X and Z orientations, ISO 75 HDT"),
93
+ "basf-tpu88a": Source(
94
+ "BASF Forward AM Ultrasint TPU 88A technical data sheet v2.2 (08/2021)",
95
+ "https://3dprinting.rpamerica.us/hubfs/BASF/UltrasintTM%20TPU%20Line.pdf",
96
+ "laser sintered; DIN 53504 S2 tensile, ISO 527-2 1A modulus, X and Z"),
97
+ "formlabs-standard": Source(
98
+ "Formlabs Standard Resins (Clear, White, Grey, Black) material properties, Rev 02 (14-08-2023)",
99
+ "https://8baht.com/storage/product_dowloads/65d4b84c383c5.pdf",
100
+ "ASTM D638-14, Form 2 at 100 um, post-cured; Grey V4"),
101
+ "formlabs-tough2000": Source(
102
+ "Formlabs Tough 2000 Resin technical data sheet V1, Rev 01 (10-07-2020)",
103
+ "https://cdn.goengineer.com/formlabs-tough-2000-resin-technical-data-sheet.pdf",
104
+ "ASTM D638-14, Form 2 at 100 um, post-cured 120 min at 80 C"),
105
+ "formlabs-rigid10k": Source(
106
+ "Formlabs Rigid 10K Resin technical data sheet, Rev 01 (06-09-2020)",
107
+ "https://core-electronics.com.au/attachments/rigid10k-TDS-ENUS-0.pdf",
108
+ "ASTM D638-14, UV post-cure column (60 min at 70 C)"),
109
+ "stratasys-vero": Source(
110
+ "Stratasys PolyJet Vero (Rigid Opaque) material data sheet (VeroWhitePlus, VeroBlackPlus, VeroGray, "
111
+ "Vero PureWhite, VeroVivid...), hosted by GoEngineer",
112
+ "https://cdn.goengineer.com/stratasys_polyjet_vero-material-data-sheet.pdf",
113
+ "ASTM D638 / D790 / D648 ranges for the family; printed, as built"),
114
+ "stratasys-agilus30": Source(
115
+ "Stratasys PolyJet Agilus30 material data sheet (Black / Translucent / White), hosted by GoEngineer",
116
+ "https://cdn.goengineer.com/stratasys-polyjet-agilus30-material-data-sheet.pdf",
117
+ "ASTM D412 tensile, D2240 hardness, D792 density; ranges per colour"),
118
+ "addup-alsi10mg": Source(
119
+ "AddUp AlSi10Mg material data sheet, version US-10252023-0129 (FormUp 350, 50 um layers)",
120
+ "https://addupsolutions.com/wp-content/uploads/2023/10/AlSi10Mg-Material-Data-Sheet.pdf",
121
+ "ASTM E8, machined specimens, as built, >= 27 samples across the plate"),
122
+ "addup-316l": Source(
123
+ "AddUp 316L SS material data sheet, version US-07172023-0134 (FormUp 350, 40 um layers)",
124
+ "https://addupsolutions.com/wp-content/uploads/2023/09/AddUp-316L-SS-Materials-Data-Sheet.pdf",
125
+ "ASTM E8, machined specimens, as built, >= 39 samples across the plate"),
126
+ "eos-ti64": Source(
127
+ "EOS Titanium Ti64 material data sheet (EOS M 290 400 W), 07.2022",
128
+ "https://www.eos.info/05-datasheet-images/Assets_MDS_Metal/EOS_Titanium_Ti64/"
129
+ "Material_DataSheet_EOS_Titanium_Ti64_EOSM290_EOSM300-4_EOSM400_EOSM400-4_en.pdf",
130
+ "ISO 6892-1; heat treated 800 C / 4 h in argon (the usual delivered state)"),
131
+ "eos-ti64-grade5": Source(
132
+ "EOS Titanium Ti64 Grade 5 material data sheet, status 01/2024",
133
+ "https://EOS.info/05-datasheet-images/Assets_MDS_Metal/EOS_Titanium_Ti64_Grade5/"
134
+ "material_datasheet_eos_titanium_ti64_grade5_en_web.pdf",
135
+ "fatigue strength at 1e7 cycles, heat treated, ISO 12107 statistics"),
136
+ "tychanicz-2025": Source(
137
+ "Tychanicz-Kwiecien et al. 2025, thermal conductivity of FDM-printed PLA and PETG "
138
+ "(as used by heatx_thermal.WALL_CONDUCTIVITY)", "", "dense printed parts, in plane"),
139
+ "eos-in625": Source(
140
+ "EOS NickelAlloy IN625 material data sheet (EOS M 290), 06.2022",
141
+ "https://uk.eos.info/03_system-related-assets/material-related-contents/metal-materials-and-examples/"
142
+ "metal-material-datasheet/nickelalloy-inconel/niall-in625-m290_material_data_sheet_06-22_en.pdf",
143
+ "ISO 6892-1, as built; the modulus is from EOS's general IN625 sheet (heat treated, 204 GPa Z / 214 XY)"),
144
+ "eos-ph1": Source(
145
+ "EOS StainlessSteel PH1 (15-5 PH) material data sheet (EOS M 290), 07.2022",
146
+ "https://www.eos.info/05-datasheet-images/Assets_MDS_Metal/EOS_StainlessSteel_PH1/"
147
+ "ss-ph1-m290_material_data_sheet_07-22_en.pdf",
148
+ "ISO 6892-1 Annex C, 20 um layers, as built"),
149
+ "handbook": Source(
150
+ "typical wrought/cast handbook value for the alloy (ASM Handbook class data)", "",
151
+ "used where the AM datasheet gives none; AM parts sit within a few percent"),
152
+ "polymer-handbook": Source(
153
+ "typical value for the polymer family (J. E. Mark, Physical Properties of Polymers Handbook, "
154
+ "2nd ed., Springer 2007)", "https://doi.org/10.1007/978-0-387-69002-5",
155
+ "unfilled bulk polymer; printing does not change the specific heat"),
156
+ "estimate": Source(
157
+ "loadpath estimate: no datasheet value", "",
158
+ "typical of the polymer family; replace with a calibrated card where it matters"),
159
+ "dma-estimate": Source(
160
+ "loadpath estimate: read off typical DMA storage-modulus curves for the polymer family and its heat "
161
+ "deflection temperature", "",
162
+ "approximate; the cliff at the glass transition is where sources disagree most. Measure it "
163
+ "(DMA, or a heated cantilever) if a part runs warm"),
164
+ "durability": Source("durability.DURABILITY (fatigue and creep design values, cited there)"),
165
+ }
166
+
167
+ # (value, source id, note)
168
+ def _c(title, processes, measured=None, **values):
169
+ """A card. `measured`: the process the datasheet's specimens were made
170
+ by (default the first of `processes`); its across-layer values apply to
171
+ studies printed by that process only."""
172
+ return {"title": title, "processes": tuple(processes), "measured": measured or processes[0],
173
+ "values": values}
174
+
175
+
176
+ _RET = "(C, share of the 23 C modulus), piecewise linear; past the last point the last value holds"
177
+ _NU_POLY = (0.36, "estimate", "datasheets don't list it; 0.35-0.40 is typical of glassy thermoplastics")
178
+
179
+ LIBRARY = {
180
+ # -------------------------------------------------------------- FDM
181
+ "pla": _c("PLA (values from Prusament PLA)", ("fdm",),
182
+ E=(2300, "prusament-pla", "tensile modulus, horizontal"),
183
+ nu=_NU_POLY,
184
+ strength=(51, "prusament-pla", "tensile yield strength, horizontal"),
185
+ strengthZ=(17, "prusament-pla", "interlayer adhesion"),
186
+ density=(1.24, "prusament-pla", "ISO 1183"),
187
+ elongation=(2.9, "prusament-pla", "% at yield, horizontal"),
188
+ hdt=(55, "prusament-pla", "ISO 75, 0.45 MPa"),
189
+ hdt18=(55, "prusament-pla", "ISO 75, 1.80 MPa"),
190
+ conductivity=(0.19, "tychanicz-2025", "W/(m K)"),
191
+ specificHeat=(1800, "polymer-handbook", "J/(kg K) near 25 C; amorphous PLA 1.2-1.8 kJ/(kg K), DSC"),
192
+ fatigue=("pla", "durability", "Ezeh & Susmel design curve"),
193
+ cte=(68, "estimate", "um/(m K) near 23 C; typical printed PLA, in plane"),
194
+ retention=([(23, 1.0), (45, 0.92), (55, 0.6), (60, 0.2), (65, 0.04)], "dma-estimate", _RET)),
195
+ "petg": _c("PETG (values from Prusament PETG)", ("fdm",),
196
+ E=(1500, "prusament-petg", "tensile modulus, horizontal"),
197
+ nu=(0.38, "estimate", "copolyester; datasheets don't list it"),
198
+ strength=(47, "prusament-petg", "tensile yield strength, horizontal"),
199
+ strengthZ=(18, "prusament-petg", "interlayer adhesion"),
200
+ density=(1.27, "prusament-petg", "ISO 1183"),
201
+ elongation=(5.1, "prusament-petg", "% at yield, horizontal"),
202
+ hdt=(68, "prusament-petg", "ISO 75, 0.45 MPa"),
203
+ hdt18=(68, "prusament-petg", "ISO 75, 1.80 MPa"),
204
+ conductivity=(0.20, "tychanicz-2025", "W/(m K)"),
205
+ specificHeat=(1200, "polymer-handbook", "J/(kg K) near 25 C; PET copolyesters 1.1-1.3"),
206
+ fatigue=("petg", "durability", "PLA curve scaled by strength (assumed)"),
207
+ cte=(68, "estimate", "um/(m K) near 23 C; typical copolyester"),
208
+ retention=([(23, 1.0), (40, 0.93), (55, 0.8), (65, 0.6), (75, 0.25), (85, 0.05)], "dma-estimate", _RET)),
209
+ "abs": _c("ABS (values from Polymaker PolyLite ABS)", ("fdm",),
210
+ E=(2174, "polylite-abs", "Young's modulus X-Y"),
211
+ EZ=(1835, "polylite-abs", "Young's modulus Z"),
212
+ nu=(0.35, "estimate", "datasheets don't list it"),
213
+ strength=(33.3, "polylite-abs", "tensile strength X-Y"),
214
+ strengthZ=(25.4, "polylite-abs", "tensile strength Z"),
215
+ density=(1.12, "polylite-abs", "ISO 1183"),
216
+ elongation=(2.7, "polylite-abs", "% at break, X-Y"),
217
+ tg=(101.1, "polylite-abs", "DSC"),
218
+ hdt=(99.6, "polylite-abs", "ISO 75, 0.45 MPa"),
219
+ hdt18=(98.2, "polylite-abs", "ISO 75, 1.8 MPa"),
220
+ conductivity=(0.17, "estimate", "W/(m K), bulk ABS"),
221
+ specificHeat=(1400, "polymer-handbook", "J/(kg K) near 25 C; ABS 1.3-1.7"),
222
+ fatigue=("abs", "durability", "PLA curve scaled by strength (assumed)"),
223
+ cte=(90, "estimate", "um/(m K) near 23 C; typical ABS"),
224
+ retention=([(23, 1.0), (60, 0.85), (80, 0.65), (95, 0.25), (105, 0.04)], "dma-estimate", _RET)),
225
+ "asa": _c("ASA (values from Polymaker PolyLite ASA)", ("fdm",),
226
+ E=(2379, "polylite-asa", "Young's modulus X-Y"),
227
+ EZ=(1965, "polylite-asa", "Young's modulus Z"),
228
+ nu=(0.35, "estimate", "datasheets don't list it"),
229
+ strength=(43.8, "polylite-asa", "tensile strength X-Y"),
230
+ strengthZ=(32.0, "polylite-asa", "tensile strength Z"),
231
+ density=(1.13, "polylite-asa", "ISO 1183"),
232
+ elongation=(6.7, "polylite-asa", "% at break, X-Y"),
233
+ tg=(97.8, "polylite-asa", "DSC"),
234
+ hdt=(102.6, "polylite-asa", "ISO 75, 0.45 MPa"),
235
+ hdt18=(100.2, "polylite-asa", "ISO 75, 1.8 MPa"),
236
+ conductivity=(0.17, "estimate", "W/(m K), as ABS"),
237
+ specificHeat=(1300, "estimate", "J/(kg K), close to ABS"),
238
+ fatigue=("asa", "durability", "PLA curve scaled by strength (assumed)"),
239
+ cte=(95, "estimate", "um/(m K) near 23 C; typical ASA"),
240
+ retention=([(23, 1.0), (60, 0.85), (80, 0.65), (95, 0.25), (105, 0.04)], "dma-estimate", _RET)),
241
+ # 95A: linear analysis holds only to a few percent strain; flexures
242
+ # want verify_nonlinear (nonlinear.py)
243
+ "tpu": _c("TPU 95A (values from UltiMaker TPU 95A); small strains only", ("fdm",),
244
+ E=(67, "ultimaker-tpu95a", "tensile modulus XY"),
245
+ EZ=(56, "ultimaker-tpu95a", "tensile modulus Z"),
246
+ nu=(0.45, "estimate", "elastomers are nearly incompressible"),
247
+ strength=(23.7, "ultimaker-tpu95a", "tensile stress at break XY"),
248
+ strengthZ=(6.4, "ultimaker-tpu95a", "tensile stress at break Z"),
249
+ density=(1.21, "estimate", "the TDS gives none; typical TPU 95A filament 1.20-1.22"),
250
+ elongation=(560, "ultimaker-tpu95a", "% at break XY (> 560)"),
251
+ hdt=(50.3, "ultimaker-tpu95a", "ISO 75-2/B, 0.455 MPa"),
252
+ conductivity=(0.20, "estimate", "W/(m K)"),
253
+ specificHeat=(1700, "estimate", "J/(kg K), polyether TPUs 1.5-1.9"),
254
+ fatigue=("tpu", "durability", "rough estimate"),
255
+ cte=(150, "estimate", "um/(m K) near 23 C; typical TPU 95A"),
256
+ retention=([(23, 1.0), (40, 0.85), (60, 0.7), (80, 0.55), (100, 0.4)], "dma-estimate", _RET)),
257
+ "pa-cf": _c("carbon-filled nylon (values from Bambu PAHT-CF, dry)", ("fdm",),
258
+ E=(3860, "bambu-paht-cf", "Young's modulus X-Y, dry"),
259
+ EZ=(2180, "bambu-paht-cf", "Young's modulus Z, dry"),
260
+ nu=(0.35, "estimate", "datasheets don't list it"),
261
+ strength=(92, "bambu-paht-cf", "tensile strength X-Y, dry"),
262
+ strengthZ=(47, "bambu-paht-cf", "tensile strength Z, dry"),
263
+ density=(1.06, "bambu-paht-cf", "ISO 1183"),
264
+ tg=(70, "bambu-paht-cf", "DSC"),
265
+ hdt=(194, "bambu-paht-cf", "ISO 75, 0.45 MPa"),
266
+ hdt18=(170, "bambu-paht-cf", "ISO 75, 1.8 MPa"),
267
+ conductivity=(0.40, "estimate", "W/(m K), fibre-filled, in plane"),
268
+ specificHeat=(1300, "estimate", "J/(kg K); PA6 ~1.6 lowered for ~15% carbon fibre"),
269
+ fatigue=("pa-cf", "durability", "rough estimate"),
270
+ cte=(30, "estimate", "um/(m K) along the laid fibres; across them several times more"),
271
+ retention=([(23, 1.0), (50, 0.8), (80, 0.55), (120, 0.35), (150, 0.2)], "dma-estimate", _RET)),
272
+ "pp": _c("polypropylene, printed (generic values; no single datasheet)", ("fdm",),
273
+ E=(1100, "estimate", "printed isotactic PP homopolymer, 0.9-1.3 GPa; copolymer filaments far lower"),
274
+ nu=(0.42, "estimate", "typical PP"),
275
+ strength=(25, "estimate", "printed PP, tensile, in plane"),
276
+ strengthZ=(15, "estimate", "interlayer, roughly 0.6 of in-plane"),
277
+ density=(0.90, "estimate", "PP 0.90-0.91"),
278
+ conductivity=(0.20, "estimate", "W/(m K), PP"),
279
+ specificHeat=(1900, "polymer-handbook", "J/(kg K) near 25 C; PP 1.7-2.0"),
280
+ fatigue=("pp", "durability", "rough estimate"),
281
+ cte=(120, "estimate", "um/(m K) near 23 C; PP 100-150"),
282
+ retention=([(23, 1.0), (60, 0.6), (90, 0.35), (120, 0.15), (150, 0.03)], "dma-estimate", _RET)),
283
+ # ------------------------------------------------------- powder bed
284
+ "pa12": _c("PA 12 (values from HP 3D HR PA 12, MJF)", ("sls", "mjf", "fdm"), "mjf",
285
+ E=(1900, "hp-5200-whitepaper", "tensile modulus XY (95% of parts 1650-2200)"),
286
+ EZ=(1900, "hp-5200-whitepaper", "tensile modulus Z"),
287
+ nu=(0.39, "estimate", "nylon 12; datasheets don't list it"),
288
+ strength=(50, "hp-5200-whitepaper", "tensile strength XY (95% of parts 45-55)"),
289
+ strengthZ=(50, "hp-5200-whitepaper", "tensile strength Z"),
290
+ density=(1.01, "hp-5200-whitepaper", "ASTM D792"),
291
+ elongation=(17, "hp-5200-whitepaper", "% at break XY (Z: 9)"),
292
+ conductivity=(0.25, "estimate", "W/(m K), sintered PA12"),
293
+ specificHeat=(1650, "polymer-handbook", "J/(kg K) near 25 C; PA12 1.6-1.7"),
294
+ fatigue=("pa12", "durability", "Van Hooreweder & Kruth 2014"),
295
+ cte=(109, "estimate", "um/(m K) near 23 C; typical sintered PA12"),
296
+ retention=([(23, 1.0), (45, 0.75), (80, 0.45), (120, 0.25), (160, 0.1)], "dma-estimate", _RET)),
297
+ "pa11": _c("PA 11 (values from HP 3D HR PA 11, MJF): tougher than PA12", ("sls", "mjf"), "mjf",
298
+ E=(1700, "hp-5200-whitepaper", "tensile modulus XY"),
299
+ EZ=(1800, "hp-5200-whitepaper", "tensile modulus Z"),
300
+ nu=(0.39, "estimate", "as PA12"),
301
+ strength=(54, "hp-5200-whitepaper", "tensile strength XY"),
302
+ strengthZ=(54, "hp-5200-whitepaper", "tensile strength Z"),
303
+ density=(1.05, "hp-5200-whitepaper", "ASTM D792"),
304
+ elongation=(40, "hp-5200-whitepaper", "% at break XY (Z: 25)"),
305
+ conductivity=(0.25, "estimate", "W/(m K), as PA12"),
306
+ specificHeat=(1600, "estimate", "J/(kg K), as PA12"),
307
+ fatigue=("pa11", "durability", "as PA12 (assumed)"),
308
+ cte=(100, "estimate", "um/(m K) near 23 C; typical PA11"),
309
+ retention=([(23, 1.0), (45, 0.75), (80, 0.45), (120, 0.25), (160, 0.1)], "dma-estimate", _RET)),
310
+ "pa12-gb": _c("glass-bead PA 12 (values from HP 3D HR PA 12 GB): stiff, stable, brittle", ("sls", "mjf"), "mjf",
311
+ E=(2900, "hp-5200-whitepaper", "tensile modulus XY"),
312
+ EZ=(3000, "hp-5200-whitepaper", "tensile modulus Z"),
313
+ nu=(0.37, "estimate", "filled nylon"),
314
+ strength=(31, "hp-5200-whitepaper", "tensile strength XY"),
315
+ strengthZ=(30, "hp-5200-whitepaper", "tensile strength Z"),
316
+ density=(1.30, "hp-5200-whitepaper", "ASTM D792"),
317
+ elongation=(9, "hp-5200-whitepaper", "% at break XY"),
318
+ hdt=(170, "hp-5200-whitepaper", "ASTM D648, 0.45 MPa, XY"),
319
+ hdt18=(113, "hp-5200-whitepaper", "ASTM D648, 1.82 MPa, XY")),
320
+ "pa12-gf": _c("glass-filled PA 12 (values from EOS PA 3200 GF, SLS): stiff, wear resistant", ("sls",),
321
+ E=(3200, "eos-pa3200gf", "tensile modulus X"),
322
+ EZ=(2500, "eos-pa3200gf", "tensile modulus Z"),
323
+ nu=(0.37, "estimate", "filled nylon"),
324
+ strength=(51, "eos-pa3200gf", "tensile strength X"),
325
+ strengthZ=(47, "eos-pa3200gf", "tensile strength Z"),
326
+ density=(1.22, "eos-pa3200gf", "laser-sintered part density (EOS method)"),
327
+ elongation=(9, "eos-pa3200gf", "% at break X (Z: 5.5)"),
328
+ hdt=(157, "eos-pa3200gf", "ISO 75, 0.45 MPa, X"),
329
+ hdt18=(96, "eos-pa3200gf", "ISO 75, 1.80 MPa, X")),
330
+ "tpu88a": _c("TPU 88A (values from BASF Ultrasint TPU 88A, SLS): a linear analysis is a rough estimate "
331
+ "past a few percent strain; run verify_nonlinear", ("sls", "mjf"),
332
+ E=(75, "basf-tpu88a", "tensile modulus X (ISO 527-2 1A)"),
333
+ EZ=(75, "basf-tpu88a", "tensile modulus Z"),
334
+ nu=(0.45, "estimate", "elastomers are nearly incompressible"),
335
+ strength=(8, "basf-tpu88a", "tensile strength X (DIN 53504 S2)"),
336
+ strengthZ=(7, "basf-tpu88a", "tensile strength Z"),
337
+ density=(1.10, "basf-tpu88a", "printed part, ISO 1183-1"),
338
+ elongation=(270, "basf-tpu88a", "% at break X (Z: 130)")),
339
+ # ------------------------------------------------------------ resin
340
+ "resin": _c("standard resin (values from Formlabs Grey V4, post-cured)", ("sla", "dlp"),
341
+ E=(2800, "formlabs-standard", "tensile modulus, post-cured"),
342
+ nu=(0.38, "estimate", "acrylate photopolymer"),
343
+ strength=(65, "formlabs-standard", "ultimate tensile strength, post-cured"),
344
+ density=(1.18, "estimate", "the TDS gives none; cured acrylates 1.15-1.20"),
345
+ elongation=(6, "formlabs-standard", "% at break, post-cured"),
346
+ hdt=(73, "formlabs-standard", "ASTM D648, 0.45 MPa"),
347
+ hdt18=(58, "formlabs-standard", "ASTM D648, 1.8 MPa"),
348
+ conductivity=(0.18, "estimate", "W/(m K)"),
349
+ specificHeat=(1400, "estimate", "J/(kg K), cured acrylates 1.2-1.6"),
350
+ fatigue=("resin", "durability", "rough estimate"),
351
+ cte=(79, "formlabs-standard", "um/(m K), 0-150 C, post-cured (ASTM E831)"),
352
+ retention=([(23, 1.0), (40, 0.8), (50, 0.5), (60, 0.2), (70, 0.05)], "dma-estimate", _RET)),
353
+ "resin-tough": _c("tough resin (values from Formlabs Tough 2000, post-cured)", ("sla", "dlp"),
354
+ E=(2200, "formlabs-tough2000", "tensile modulus, post-cured"),
355
+ nu=(0.40, "estimate", "toughened photopolymer"),
356
+ strength=(46, "formlabs-tough2000", "ultimate tensile strength, post-cured"),
357
+ density=(1.15, "estimate", "the TDS gives none"),
358
+ elongation=(48, "formlabs-tough2000", "% at break, post-cured"),
359
+ hdt=(63, "formlabs-tough2000", "ASTM D648, 0.45 MPa"),
360
+ hdt18=(53, "formlabs-tough2000", "ASTM D648, 1.8 MPa"),
361
+ cte=(91, "formlabs-tough2000", "um/(m K), 0-150 C, post-cured (ASTM E831)"),
362
+ conductivity=(0.18, "estimate", "W/(m K)"),
363
+ specificHeat=(1400, "estimate", "J/(kg K), cured acrylates 1.2-1.6"),
364
+ fatigue=("resin-tough", "durability", "rough estimate"),
365
+ retention=([(23, 1.0), (40, 0.75), (50, 0.45), (60, 0.2), (70, 0.05)], "dma-estimate", _RET)),
366
+ "resin-rigid": _c("glass-filled rigid resin (values from Formlabs Rigid 10K, UV cure): brittle", ("sla", "dlp"),
367
+ E=(10000, "formlabs-rigid10k", "tensile modulus, UV post-cure"),
368
+ nu=(0.33, "estimate", "highly glass-filled photopolymer"),
369
+ strength=(65, "formlabs-rigid10k", "ultimate tensile strength, UV post-cure"),
370
+ density=(1.65, "estimate", "the TDS gives none; glass-filled acrylates 1.6-1.7"),
371
+ elongation=(1, "formlabs-rigid10k", "% at break, UV post-cure"),
372
+ cte=(47, "formlabs-rigid10k", "um/(m K), 0-150 C, UV post-cure (ASTM E831)")),
373
+ # ---------------------------------------------------------- jetting
374
+ # PolyJet (material jetting): rigid and rubber-like photopolymers jetted
375
+ # and UV-cured in the same layers; interface_library.py has their bond.
376
+ # The datasheets give ranges; the cards take the low end.
377
+ "vero": _c("rigid PolyJet photopolymer (values from Stratasys Vero / Rigid Opaque, low end of the "
378
+ "family's ranges)", ("jetting",),
379
+ E=(2000, "stratasys-vero", "modulus of elasticity, ASTM D638 (range 2000-3000)"),
380
+ nu=(0.38, "estimate", "acrylate photopolymer, as resin"),
381
+ strength=(50, "stratasys-vero", "tensile strength, ASTM D638 (range 50-65)"),
382
+ density=(1.17, "stratasys-vero", "polymerized density (range 1.17-1.18)"),
383
+ elongation=(10, "stratasys-vero", "% at break, ASTM D638 (range 10-25)"),
384
+ tg=(52, "stratasys-vero", "DMA (range 52-54)"),
385
+ hdt=(45, "stratasys-vero", "ASTM D648, 0.45 MPa (range 45-50)"),
386
+ hdt18=(45, "stratasys-vero", "ASTM D648, 1.82 MPa (range 45-50)"),
387
+ conductivity=(0.18, "estimate", "W/(m K), as other cured acrylates")),
388
+ "agilus30": _c("rubber-like PolyJet photopolymer, Shore 30A (values from Stratasys Agilus30): a linear "
389
+ "analysis is a rough estimate past a few percent strain; run verify_nonlinear", ("jetting",),
390
+ E=(1.1, "estimate", "the TDS gives none; Gent's Shore A relation at 30A gives ~1.1 MPa "
391
+ "(initial tangent modulus)"),
392
+ nu=(0.45, "estimate", "elastomers are nearly incompressible; 0.45 keeps the linear solve "
393
+ "well conditioned"),
394
+ strength=(2.4, "stratasys-agilus30", "tensile strength, ASTM D412, Black/Translucent "
395
+ "(range 2.4-3.1; White 2.1-2.6)"),
396
+ density=(1.14, "stratasys-agilus30", "polymerized density, ASTM D792 (range 1.14-1.15)"),
397
+ elongation=(220, "stratasys-agilus30", "% at break, ASTM D412, Black/Translucent "
398
+ "(range 220-270; White 185-230)"),
399
+ hardness=(30, "stratasys-agilus30", "Shore A, ASTM D2240 (range 30-35)")),
400
+ # ------------------------------------------------------------- LPBF
401
+ "alsi10mg": _c("LPBF AlSi10Mg, as built (values from AddUp)", ("lpbf",),
402
+ E=(72000, "addup-alsi10mg", "modulus XY, as built (Z 71 GPa)"),
403
+ EZ=(71000, "addup-alsi10mg", "modulus Z, as built"),
404
+ nu=(0.33, "handbook", "aluminium alloys"),
405
+ strength=(275, "addup-alsi10mg", "yield strength XY, as built"),
406
+ strengthZ=(239, "addup-alsi10mg", "yield strength Z, as built"),
407
+ density=(2.67, "addup-alsi10mg", "theoretical; parts 99.5% dense"),
408
+ elongation=(5, "addup-alsi10mg", "% at failure, XY and Z"),
409
+ conductivity=(125, "addup-alsi10mg", "W/(m K), low end of the sheet's 120-160 at 25 C"),
410
+ specificHeat=(900, "handbook", "J/(kg K) near 25 C; Al-Si casting alloys 880-920"),
411
+ fatigue=("alsi10mg", "durability", "rough, as-built surface"),
412
+ cte=(21, "addup-alsi10mg", "um/(m K), 20-100 C"),
413
+ retention=([(23, 1.0), (150, 0.92), (200, 0.85), (300, 0.6)], "dma-estimate", _RET)),
414
+ "ti64": _c("LPBF Ti-6Al-4V, heat treated (values from EOS)", ("lpbf",),
415
+ E=(110000, "eos-ti64", "modulus XY and Z, +-15 GPa"),
416
+ EZ=(110000, "eos-ti64", "modulus Z"),
417
+ nu=(0.34, "handbook", "Ti-6Al-4V"),
418
+ strength=(1000, "eos-ti64", "yield strength Rp0.2 XY, heat treated (min 860)"),
419
+ strengthZ=(1000, "eos-ti64", "yield strength Rp0.2 Z, heat treated"),
420
+ density=(4.41, "eos-ti64", "part density"),
421
+ elongation=(13.5, "eos-ti64", "% at break, heat treated"),
422
+ conductivity=(6.7, "handbook", "W/(m K), Ti-6Al-4V"),
423
+ specificHeat=(526, "handbook", "J/(kg K), Ti-6Al-4V at 25 C"),
424
+ fatigueStrength=(595, "eos-ti64-grade5", "MPa at 1e7 cycles, heat treated (Grade 5 sheet)"),
425
+ fatigue=("ti64", "durability", "rough, as-built surface"),
426
+ cte=(8.6, "handbook", "um/(m K), 20-100 C, Ti-6Al-4V"),
427
+ retention=([(23, 1.0), (300, 0.85), (500, 0.75)], "dma-estimate", _RET)),
428
+ "316l": _c("LPBF 316L stainless, as built (values from AddUp)", ("lpbf",),
429
+ E=(193000, "addup-316l", "modulus XY, as built"),
430
+ EZ=(160000, "addup-316l", "modulus Z, as built"),
431
+ nu=(0.30, "handbook", "austenitic stainless"),
432
+ strength=(699, "addup-316l", "yield strength XY, as built"),
433
+ strengthZ=(632, "addup-316l", "yield strength Z, as built"),
434
+ density=(8.0, "addup-316l", "theoretical; parts 99.9% dense"),
435
+ conductivity=(15, "addup-316l", "W/(m K) at 20 C"),
436
+ specificHeat=(500, "handbook", "J/(kg K), 316L at 25 C"),
437
+ fatigue=("316l", "durability", "rough, as-built surface"),
438
+ cte=(17.2, "addup-316l", "um/(m K), RT-200 C (the sheet's literature value)"),
439
+ retention=([(23, 1.0), (300, 0.9), (500, 0.82)], "dma-estimate", _RET)),
440
+ "in625": _c("LPBF Inconel 625, as built (values from EOS)", ("lpbf",),
441
+ E=(204000, "eos-in625", "modulus Z, heat treated (as built reads 10-20% lower on other sheets)"),
442
+ nu=(0.31, "handbook", "nickel alloy 625"),
443
+ strength=(720, "eos-in625", "yield strength Rp0.2 XY, as built"),
444
+ strengthZ=(630, "eos-in625", "yield strength Rp0.2 Z, as built"),
445
+ density=(8.44, "handbook", "alloy 625"),
446
+ conductivity=(9.8, "handbook", "W/(m K) at 20 C, alloy 625"),
447
+ cte=(12.8, "handbook", "um/(m K), 20-100 C, alloy 625"),
448
+ retention=([(23, 1.0), (300, 0.93), (500, 0.88)], "dma-estimate", _RET)),
449
+ "15-5ph": _c("LPBF 15-5 PH stainless, as built (values from EOS PH1)", ("lpbf",),
450
+ E=(196000, "handbook", "wrought 15-5 PH; the EOS sheet gives none"),
451
+ nu=(0.27, "handbook", "precipitation-hardening stainless"),
452
+ strength=(1025, "eos-ph1", "yield strength Rp0.2 XY, as built"),
453
+ strengthZ=(930, "eos-ph1", "yield strength Rp0.2 Z, as built"),
454
+ density=(7.8, "handbook", "15-5 PH"),
455
+ conductivity=(17.8, "handbook", "W/(m K) at 100 C, 15-5 PH"),
456
+ cte=(10.8, "handbook", "um/(m K), 20-100 C, 15-5 PH"),
457
+ retention=([(23, 1.0), (300, 0.9), (500, 0.8)], "dma-estimate", _RET)),
458
+ }
459
+
460
+
461
+ def card_names():
462
+ return sorted(LIBRARY)
463
+
464
+
465
+ def card(name):
466
+ """The library card (dict) for a material name, or None."""
467
+ return LIBRARY.get(str(name).strip().lower())
468
+
469
+
470
+ def value(name, key, default=None):
471
+ """A card's number for `key` (e.g. "strengthZ"), or default."""
472
+ c = card(name)
473
+ if c is None or key not in c["values"]:
474
+ return default
475
+ return c["values"][key][0]
476
+
477
+
478
+ def describe(name):
479
+ """A card as JSON: every value with its source's title and url."""
480
+ c = card(name)
481
+ if c is None:
482
+ return None
483
+ vals = {}
484
+ for k, (v, src, note) in c["values"].items():
485
+ s = SOURCES[src]
486
+ vals[k] = {"value": v, "note": note, "source": s.title, **({"url": s.url} if s.url else {})}
487
+ return {"name": str(name).strip().lower(), "title": c["title"], "processes": list(c["processes"]),
488
+ "measured": c["measured"],
489
+ "values": vals, "accessed": ACCESSED}
loadpath/materials.py ADDED
@@ -0,0 +1,144 @@
1
+ """
2
+ Material properties for the verification pass.
3
+
4
+ The optimizer itself does not need any of this: for a linear elastic
5
+ compliance problem the optimal layout is independent of Young's modulus, so
6
+ the solver runs on E = 1. Material only enters when we turn an outcome into
7
+ numbers a person acts on (stress against strength, deflection in mm, mass in
8
+ grams).
9
+
10
+ Values come from named datasheets of printed parts, each value cited in
11
+ material_library.py (`loadpath materials` lists them); they are typical
12
+ values, not certified allowables. Printed plastics are anisotropic (weaker across layers, often by
13
+ half for FDM) and the analysis is linear, so a safety factor computed from
14
+ these is an estimate to compare designs and to catch an undersized part, not
15
+ a sign-off.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+
21
+ from .material_library import LIBRARY
22
+
23
+ __all__ = ["Material", "MATERIALS", "material_from_spec"]
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Material:
28
+ name: str
29
+ E: float # Young's modulus, MPa
30
+ nu: float # Poisson's ratio
31
+ strength: float # MPa: yield for metals, tensile strength for polymers
32
+ density: float # g/cm^3
33
+ note: str = ""
34
+ # the processes (spec.PROCESSES) it is printed by; empty = any
35
+ processes: tuple = ()
36
+ # across layers, when the datasheet measured it (MPa); None = in-plane
37
+ # values with the process's own factors (spec.PROCESSES)
38
+ E_z: float | None = None
39
+ strength_z: float | None = None
40
+ # the process the datasheet's specimens were made by: E_z and
41
+ # strength_z apply to a study printed that way
42
+ measured: str = ""
43
+
44
+ def to_dict(self):
45
+ out = {"name": self.name, "E": self.E, "nu": self.nu,
46
+ "strength": self.strength, "density": self.density, "note": self.note,
47
+ "processes": list(self.processes)}
48
+ if self.E_z is not None:
49
+ out["EZ"] = self.E_z
50
+ if self.strength_z is not None:
51
+ out["strengthZ"] = self.strength_z
52
+ if self.measured:
53
+ out["measured"] = self.measured
54
+ return out
55
+
56
+ def across_layers(self):
57
+ """(strength ratio, modulus ratio) across layers from the datasheet,
58
+ each capped at 1, or None where it wasn't measured."""
59
+ s = min(1.0, self.strength_z / self.strength) if self.strength_z else None
60
+ e = min(1.0, self.E_z / self.E) if self.E_z else None
61
+ return s, e
62
+
63
+
64
+ def _from_card(name, c):
65
+ v = c["values"]
66
+ get = (lambda k: float(v[k][0]) if k in v else None)
67
+ return Material(name, get("E"), get("nu"), get("strength"), get("density"), c["title"],
68
+ c["processes"], E_z=get("EZ"), strength_z=get("strengthZ"),
69
+ measured=c.get("measured", c["processes"][0]))
70
+
71
+
72
+ # every value sourced: material_library.py (`loadpath materials <name>`)
73
+ MATERIALS = {k: _from_card(k, c) for k, c in LIBRARY.items()}
74
+
75
+ _ALIASES = {
76
+ "nylon": "pa12", "pa": "pa12", "pa 12": "pa12", "pa-12": "pa12",
77
+ "aluminium": "alsi10mg", "aluminum": "alsi10mg", "titanium": "ti64",
78
+ "stainless": "316l", "steel": "316l", "carbon nylon": "pa-cf",
79
+ "tpu95a": "tpu", "tpu 95a": "tpu", "flex": "tpu",
80
+ "pa 11": "pa11", "pa-11": "pa11", "pa12gb": "pa12-gb", "glass nylon": "pa12-gb",
81
+ "pa12gf": "pa12-gf", "pa 3200 gf": "pa12-gf", "pa3200gf": "pa12-gf",
82
+ "tpu 88a": "tpu88a", "tpu-88a": "tpu88a", "sla": "resin", "standard resin": "resin", "tough resin": "resin-tough",
83
+ "rigid resin": "resin-rigid",
84
+ "verowhite": "vero", "vero white": "vero", "agilus": "agilus30", "agilus 30": "agilus30",
85
+ }
86
+
87
+
88
+ def material_from_spec(spec) -> Material:
89
+ """A name from MATERIALS (or an alias), a calibrated card's name, or a
90
+ custom {"name"?, "E", "nu", "strength", "density"} object; a custom
91
+ object with "base": <name> starts from that material and changes only
92
+ the keys it gives. A calibrated card (calibration.py, measured on your
93
+ printer) named like a library material replaces it."""
94
+ if isinstance(spec, Material):
95
+ return spec
96
+ if isinstance(spec, str):
97
+ key = spec.strip().lower()
98
+ from .calibration import list_cards, load_card
99
+ cal = _calibrated(key, load_card)
100
+ if cal is not None:
101
+ return cal
102
+ key = _ALIASES.get(key, key)
103
+ if key in MATERIALS:
104
+ return MATERIALS[key]
105
+ raise ValueError(f"unknown material {spec!r}; known: {sorted(MATERIALS)}"
106
+ + (f", calibrated: {list_cards()}" if list_cards() else "")
107
+ + " or pass {E, nu, strength, density}")
108
+ if isinstance(spec, dict):
109
+ base = material_from_spec(spec["base"]) if spec.get("base") is not None else None
110
+ d = dict(base.to_dict()) if base is not None else {}
111
+ d.update({k: v for k, v in spec.items() if k != "base"})
112
+ if base is not None and "name" not in spec:
113
+ d["name"] = f"{base.name}-custom"
114
+ if base is not None and "note" not in spec:
115
+ d["note"] = f"{base.name} with {', '.join(sorted(k for k in spec if k not in ('base', 'name')))} changed"
116
+ try:
117
+ m = Material(str(d.get("name", "custom")), float(d["E"]), float(d.get("nu", 0.35)),
118
+ float(d["strength"]), float(d["density"]), str(d.get("note", "")),
119
+ tuple(d.get("processes") or ()),
120
+ E_z=float(d["EZ"]) if d.get("EZ") is not None else None,
121
+ strength_z=float(d["strengthZ"]) if d.get("strengthZ") is not None else None,
122
+ measured=str(d.get("measured") or ""))
123
+ except KeyError as exc:
124
+ raise ValueError(f"custom material is missing {exc.args[0]!r} (needs E, strength, density, "
125
+ "or a base material)") from None
126
+ if m.E <= 0 or m.strength <= 0 or m.density <= 0 or not (0 <= m.nu < 0.5):
127
+ raise ValueError("custom material needs E, strength, density > 0 and 0 <= nu < 0.5")
128
+ if (m.E_z is not None and m.E_z <= 0) or (m.strength_z is not None and m.strength_z <= 0):
129
+ raise ValueError("custom material EZ and strengthZ must be > 0")
130
+ return m
131
+ raise ValueError("material must be a name or {E, nu, strength, density}")
132
+
133
+
134
+ def _calibrated(key, load_card):
135
+ """The Material of a calibrated card named `key`, or None. Its
136
+ across-layer factors travel in the card's process block (spec.py)."""
137
+ card = load_card(key)
138
+ if card is None:
139
+ return None
140
+ m = card["material"]
141
+ base = MATERIALS.get(_ALIASES.get(card.get("basedOn", ""), card.get("basedOn", "")))
142
+ return Material(card["name"], float(m["E"]), float(m["nu"]), float(m["strength"]),
143
+ float(m["density"]), str(m.get("note", "")),
144
+ tuple(m.get("processes") or (base.processes if base else ())))