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
loadpath/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # loadpath
2
+
3
+ Generative design for parts you are going to print. Describe what a part must
4
+ keep, what it must avoid, where it is held and how hard it is pushed; loadpath
5
+ removes material until only the load path is left, meshes the result as a
6
+ watertight printable part with exact bores and faces, and then checks that part
7
+ with a fresh finite element analysis in real units.
8
+
9
+ The methods are standard and published, and the code is meant to be read.
10
+ What each model has been checked against is in
11
+ [docs/VALIDATION.md](../docs/VALIDATION.md); nothing has been physically
12
+ tested yet.
13
+
14
+ - **SIMP topology optimization** on a voxel grid: density per cell, penalized
15
+ stiffness, a cone density filter for a minimum member size, Heaviside
16
+ projection with continuation for crisp 0/1 designs, optimality-criteria
17
+ updates, multiple load cases, symmetry planes.
18
+ - **Overhang control** for a chosen build direction (Langelaar's AM filter):
19
+ material that would need support earns nothing, so parts grow their own
20
+ 45° supports. Any direction, including tilted ones (`am.TiltedAMFilter`).
21
+ - **Print-aware design** (`process`): layered (transversely isotropic)
22
+ stiffness about the build direction in verification, with the layer bond
23
+ checked; the safety-factor search sizes the part against it, and the
24
+ optimizer can design with the layered stiffness too (`anisotropic`, off by
25
+ default: on the motor mount and hook it did not beat isotropic, see am.py). `"buildDirection": "auto"` compares the six directions on a coarse
26
+ grid by successive halving (all half-way, the best three finished) and keeps the strongest per gram
27
+ (`orient.py`). Every layered result
28
+ is sliced like a slicer would (`printcheck.py`: bridges up to 15 mm are
29
+ fine, mid-air islands, support volume, bed contact and tipping) and written
30
+ as a 3MF standing the way it was designed to print (`threemf.py`).
31
+ - **Durability for printed plastics** (`durability.py`): load cases with
32
+ `cycles` get a fatigue life (Ezeh and Susmel's design curve for printed
33
+ PLA, max stress in the cycle, Miner's rule across cases, the layer bond
34
+ checked in fatigue too), cases with `hours` get creep rupture and crept
35
+ deflection, and fasteners report how much preload relaxes away, all at the
36
+ service temperature through the modulus-retention tables. Optional
37
+ `durability.constrain` sizes the part for them.
38
+ - **Minimum member size** (robust formulation): stiffness is credited to the
39
+ eroded design and the delivered part is the opened one (robust
40
+ formulation), so no sub-minimum skins or webs.
41
+ - **MMA** (Svanberg's Method of Moving Asymptotes, implemented here from the
42
+ published method) for minimum mass subject to stress and deflection
43
+ limits, with relaxed, adaptively normalized P-mean stress aggregation and
44
+ adjoint sensitivities.
45
+ - **Linear elastic FEA** with 8-node hexahedra, solved by conjugate gradient
46
+ with a smoothed-aggregation multigrid preconditioner (pyamg). Checked against
47
+ Euler-Bernoulli beam theory in the tests.
48
+ - **Exact reconstruction**: marching cubes on the smoothed density, then
49
+ manifold3d booleans that add back the preserved geometry and cut the
50
+ keep-outs exactly, so a 22mm bearing seat is 22mm.
51
+ - **Verification of the delivered mesh**, not the density field: mass, peak
52
+ von Mises stress and safety factor per load case, deflection, keep-out
53
+ intrusion and preserve loss (exact booleans), body count, thinnest member.
54
+
55
+ Units are fixed: millimetres, newtons, megapascals, g/cm³.
56
+
57
+ ## Quick start
58
+
59
+ ```bash
60
+ pip install "loadpath[render]"
61
+ python -m loadpath check study.json # validate, show the voxel problem
62
+ python -m loadpath run study.json --out out # optimize, mesh, verify, render
63
+ ```
64
+
65
+ `out/` gets one STL and one render per outcome, and `report.json` with the
66
+ numbers. Progress streams to stderr as one JSON object per line.
67
+
68
+ ### For agents (MCP)
69
+
70
+ `pip install "loadpath[mcp]"`, then add a local server to any MCP client
71
+ (Claude Code, Cursor, Claude Desktop):
72
+
73
+ ```json
74
+ {"mcpServers": {"loadpath": {"command": "loadpath", "args": ["mcp"]}}}
75
+ ```
76
+
77
+ Tools: `loadpath_guide` (the study format), `loadpath_schema`,
78
+ `loadpath_examples`, `loadpath_check`, `loadpath_start`, `loadpath_status`
79
+ (outcomes, STL paths and renders), `loadpath_lattice`,
80
+ `loadpath_heat_exchanger`, `loadpath_size_heat_exchanger`, `loadpath_jobs`,
81
+ `loadpath_cancel`, and checks on any mesh file: `loadpath_check_printability`,
82
+ `loadpath_check_isolation`, `loadpath_check_fit`, and `loadpath_import_cad` (a STEP/STL part as a draft study). Everything runs on the agent's own machine; jobs live in
83
+ `~/.loadpath/jobs` (`LOADPATH_HOME`). See [docs/agents.md](../docs/agents.md).
84
+
85
+ From Python:
86
+
87
+ ```python
88
+ from loadpath.spec import study_from_dict
89
+ from loadpath.study import run_study
90
+
91
+ result = run_study(study_from_dict(study))
92
+ best = result.outcomes[result.best]
93
+ best.mesh.export("part.stl")
94
+ print(best.report["mass"], best.report["safetyFactor"])
95
+ ```
96
+
97
+ ### See it in 3D
98
+
99
+ `loadpath.viewer` writes one HTML file a person can open in any browser: orbit
100
+ the part, colour it by von Mises stress, safety factor or displacement for
101
+ each load case (or the worst of them), scrub or animate an exaggerated
102
+ deformation, cut it open with a section plane, hover to read the value under
103
+ the pointer, put two outcomes (or the part it replaces) side by side, and play
104
+ the optimization back iteration by iteration, every run of a safety-factor
105
+ search in order.
106
+
107
+ ```python
108
+ from loadpath.viewer import HistoryRecorder, study_viewer
109
+
110
+ rec = HistoryRecorder() # keeps the density field every 2 iterations
111
+ result = run_study(study, on_progress=rec)
112
+ study_viewer(result, "part.html", history=rec, outcomes="all",
113
+ baseline="old-part.stl") # optional: verified under the same loads
114
+ ```
115
+
116
+ The page carries its own data (a few MB) and loads three.js from a CDN.
117
+ `heat_exchanger_viewer(mesh, spaces, report, "hx.html")` does the same for
118
+ `design_heat_exchanger(..., fluid_meshes=True)`: each fluid is a layer, and
119
+ the section plane shows the two networks interleave. `Viewer` is the scene
120
+ underneath, for anything else (`add_part` with fields, `add_overlay`,
121
+ `add_arrow`, `add_study_context`).
122
+
123
+ ## The study
124
+
125
+ ```json
126
+ {
127
+ "name": "arm-link",
128
+ "designSpace": [{"type": "box", "min": [-16, -18, -12], "max": [156, 18, 12]}],
129
+ "preserve": [
130
+ {"id": "shoulder-boss", "shape": {"type": "cylinder", "center": [0, 0, 0], "axis": "z", "length": 24, "r": 16}},
131
+ {"id": "wrist-boss", "shape": {"type": "cylinder", "center": [140, 0, 0], "axis": "z", "length": 24, "r": 15}}
132
+ ],
133
+ "keepOut": [
134
+ {"id": "shoulder-bearing", "shape": {"type": "cylinder", "center": [0, 0, 0], "axis": "z", "length": 30, "r": 11}},
135
+ {"id": "wrist-bearing", "shape": {"type": "cylinder", "center": [140, 0, 0], "axis": "z", "length": 30, "r": 11}},
136
+ {"id": "cable-channel", "shape": {"type": "cylinder", "p0": [30, 0, 0], "p1": [110, 0, 0], "r": 5}}
137
+ ],
138
+ "supports": [{"region": "shoulder-bearing"}],
139
+ "loadCases": [
140
+ {"name": "payload", "loads": [{"region": "wrist-bearing", "force": [0, -20, 0]}]},
141
+ {"name": "side", "loads": [{"region": "wrist-bearing", "force": [0, 0, 8]}]}
142
+ ],
143
+ "material": "pa12",
144
+ "goal": {"safetyFactor": 3.0},
145
+ "minMember": 3.0,
146
+ "symmetry": ["z"],
147
+ "resolution": {"maxElements": 40000}
148
+ }
149
+ ```
150
+
151
+ | Field | Meaning |
152
+ | --- | --- |
153
+ | `designSpace` | Shapes material may occupy (their union). |
154
+ | `preserve` | Geometry kept exactly: bosses, mounting faces, tabs. Pinned solid in the optimization and unioned back exactly. |
155
+ | `keepOut` | Volume that must stay empty: bores, bolt access, cable runs, mating parts, a fluid's volume. Cut exactly at the end. `wall: 1.5` keeps 1.5 mm of material round it, pinned solid (a duct's or plenum's skin; see pressure below). |
156
+ | `fasteners` | Optional bolted joints as interfaces: `{id, size: "M5", seat, direction, grip, end: "face"\|"thread"\|"none", headOn: "this"\|"mating"}`. A screw driven from the mating part into this one is `headOn: "mating", end: "thread"`. Adds a seat ring and a face ring or thread sleeve (preserve), the bore and the head and driver path (keep-outs, `<id>-bore`, `<id>-head`), and a self-balanced `preload` case, so the optimizer grows the clamp path instead of keeping a solid boss. Sizes: `hole`, `head`, `wall` (seat ring beyond the head), `ring` (ring thickness), `driver` (head path length, default 60 mm), `threadLength`, `insert`, `sleeveWall`, `preload` (N); `loadpath schema study` describes each. See `fasteners.py`. |
157
+ | `supports` | Where the part is held: a region id or an inline shape, with `fix` = `"all"` (default), axes like `"xz"`, or three booleans. Optional when every load is a pressure on a sealed cavity. |
158
+ | `skin` | Optional skin-and-rib: `1.2` or `{thickness: 1.2, open: ["-z"]}` keeps that many mm of the design space's outer surface as a solid wall (except on the `open` faces) and designs only what is behind it, so the optimizer grows ribs under a skin instead of a free-form part. |
159
+ | `loadCases` | Each a list of forces (N) on regions. The optimizer stiffens for all cases together; verification reports each. Optional `cycles` (times applied over the part's life: fatigue), `hours` (time held: creep) and `temperature` (C). |
160
+ | `durability` | Optional `{temperature, safetyFactor, constrain, preloadHours, fatigueStrength, fatigueSlope, creepRatio, creepExponent}`: the service temperature (default 23 C), the fatigue/creep safety factor target (default 1.5), whether the safety-factor search sizes for it (default false), how long fasteners hold preload (default 8760 h), and overrides for the material's durability data. See `durability.py` for sources. |
161
+ | `material` | A name (`pla`, `petg`, `abs`, `asa`, `tpu`, `pa-cf`, `pa12`, `pa11`, `pa12-gb`, `tpu88a`, `resin`, `resin-tough`, `resin-rigid`, `alsi10mg`, `ti64`, `316l`) or `{E, nu, strength, density}`. A list solves the goal for each and pools the outcomes. |
162
+ | `goal` | `{"volumeFraction": 0.25}` keeps that share of the design space. `{"safetyFactor": 3}` and/or `{"maxDeflection": 0.5}` (mm, at the loads) search for the lightest part that meets them in every case. `outcomes: [0.2, 0.4]` adds a trade-off gallery. `"method": "constrained"` (experimental) minimizes mass with the limits as MMA constraints instead of searching over volume fractions. |
163
+ | `extrusion` | Optional axis (`x`, `y`, `z`): the part is a profile extruded along it, cuttable from plate (waterjet, laser, 2.5-axis milling). |
164
+ | `gravity` | Optional `[gx, gy, gz]` in m/s²: the part's own weight in every load case. |
165
+ | `thermal` | Optional heat in the part, in every load case: `{ambient, stressFree, temperatures: [{region\|shape, t}], heat: [{region\|shape, watts, spread}], convection: h or [{h, region\|shape, fluid}], conductivity, expansion, soften, specificHeat, cycle: {period, on, cycles}}` (C, W, W/(m² K)). The part's own temperature softens it (the card's retention curve) and its expansion, where supports or cooler material stop it, is a load; the optimizer designs for both and verification checks stress with the free expansion taken off and strength derated. With `cycle` the heat is on for `on` seconds of every `period`: the repeating temperature cycle is solved, the part designed for its hot moment, checked hot and cold, and `cycles` count toward thermal-cycling fatigue (`fatigueWeight`, experimental, also designs against it; it does not yet hold up on finer grids) (durability). Examples: `examples/thermal/motor-mount-hot.json`, `examples/thermal/motor-mount-cycle.json`. |
166
+ | `process` | Optional: `"fdm"`, `"sls"`, `"mjf"`, `"sla"`, `"dlp"`, `"volumetric"`, `"lpbf"` (powder-bed metal), or `{type, buildDirection, interlayer, stiffnessAcrossLayers, shearAcrossLayers, anisotropic (false \| true \| "bond"), minFeature, overhangs, escapeHole}`. `buildDirection` is `+x` … `-z`, a vector `[x, y, z]`, or `"auto"`. Sets a minimum member, overhang control (FDM), layered stiffness and the layer-bond check. SLS, MJF, SLA and DLP parts also get `report.process` (processcheck.py): walls, trapped powder or resin, warp-prone panels, resin supports, peel area and suction cups. |
167
+ | `printDirection` | Optional, `+x` … `-z` or a vector `[x, y, z]`: the direction layers stack. Turns on overhang control (Langelaar's AM filter), so the optimizer's own surfaces are self-supporting at 45°. |
168
+ | `minMember` | Thinnest member in mm (sets the filter radius). |
169
+ | `maxMember` | Optional thickest member in mm (at least 2 x `minMember`): a local volume limit, so thick members hollow out or split. |
170
+ | `symmetry` | Planes through the design-space centre, by normal axis. |
171
+ | `pattern` | Optional circular pattern: `{type: "circular", axis: "z", count: 6, center: [x, y, z]}` makes `count` identical sectors round the axis (a hub, a turret). |
172
+ | `milling` | Optional, the part is machined: `{axes: 3, directions: ["+z"], toolDiameter: 6}`. Every void must be reachable by a cutter coming straight in from one of the `directions` (default `+z`; `axes: 5` is 3+2 positional from all six sides; `axes: 2.5` also puts pocket floors on `stepDown` levels). Slots and inside corners narrower than `toolDiameter` are filled. |
173
+ | `casting` | Optional, the part is cast or molded: `"+z"` or `{draw: "+z", sides: 2}`. No undercuts along the draw: with 2 sides the mold halves pull both ways (every line along the draw crosses the part once); with 1, one half pulls off a flat parting plane. |
174
+ | `resolution` | `maxElements` (default 60,000) or an explicit `pitch` in mm. |
175
+ | `lattice` | Optional: also fill the best outcome with a stress-graded TPMS lattice, `{type: "gyroid"\|"diamond"\|"schwarz-p", cell, skin, minWall, maxWall, solidAbove, safetyFactor}`. `skin: 0` leaves it open so powder or resin drains. |
176
+
177
+ Shapes: `box` (`min`/`max` or `center`/`size`), `cylinder` (`p0`/`p1` or
178
+ `center`/`axis`/`length`, with `r` and optional `rInner` for a tube), `sphere`,
179
+ and `mesh` (a closed STL by `path`, relative to the study file, or inline as
180
+ `stlBase64`).
181
+
182
+ A load or support on a keep-out means "through the material lining it", which
183
+ is how a shaft pushes on a bore or a bolt holds a hole. Loads take a `force`
184
+ (N) and/or a `moment` (N·mm, about the region's centroid: a motor's torque);
185
+ a carried `mass` (kg, with `dynamic`, `gravity` and optional `at`, its centre
186
+ in mm) becomes its weight along gravity and that weight's moment about the
187
+ region; `"distribution": "bearing"` spreads a force over the half of a bore it pushes
188
+ on, as a shaft does. Supports take `"fix": "pin"` on a cylinder for a joint
189
+ that turns: held radially and axially, free to rotate.
190
+
191
+ A **pressure** acts in a keep-out, the fluid's volume:
192
+ `{"region": "plenum", "pressure": 0.25}` is 2.5 bar gauge (MPa; negative is a
193
+ vacuum) on every wall lining it, normal to the wall. The keep-out keeps a
194
+ `wall` (default `minMember`) that is pinned solid, so the pressure boundary
195
+ cannot be optimized open and the load does not depend on the design; the
196
+ optimizer then puts material behind the wall where it stiffens it: ribs.
197
+ A sealed cavity's pressure balances on itself, so a study whose only loads
198
+ are pressures needs no `supports` (it is held by a statically determinate
199
+ 3-2-1 support that takes no load); an open duct's or a flanged housing's
200
+ needs them where the part is held, because the thrust on its open ends goes
201
+ there. The report's `deflection` for a pressure-only case is the most any
202
+ wetted wall bulges. Checked against Lamé (tests/test_pressure.py): a thick
203
+ sphere reads 5-7% over the exact bore stress and 6-9% over its bulge; a
204
+ closed thin cylinder (3.7 cells through the wall) 8-11% over the hoop
205
+ stress. Verification resolves the thinnest wall or skin with four cells (up
206
+ to 400,000 cells): a wall voxelizes to a whole number of cells, and at two
207
+ or three a 2 mm plenum wall read 2.4 mm and 30% under its bending stress; at
208
+ four, the flat wall of a 90 x 60 mm plenum reads the clamped-plate centre
209
+ stress (50 MPa at 2.5 bar). Walls under two optimization cells are warned
210
+ about. The tetrahedral cross-check does not apply pressure yet.
211
+
212
+ ## Reading the report
213
+
214
+ - `safetyFactor` is the material strength over the peak von Mises stress,
215
+ worst case. Stress within one optimization cell of where a load or support
216
+ is applied is left out of the peak (`contactBand`): an idealized load
217
+ produces stress there that belongs to the idealization, not the part. The
218
+ analysis is linear and the material numbers are typical datasheet values;
219
+ printed parts are weaker across layers. Treat it as an engineering estimate
220
+ for comparing designs and catching an undersized part, not a certification.
221
+ No safety factor from loadpath has yet been compared with a load test.
222
+ - `keepOutIntrusion` and `preserveMissing` are mm³ from exact booleans and
223
+ should be zero to rounding.
224
+ - `p5Member` is the 5th-percentile member thickness on the medial ridge.
225
+ - `print` (layered processes): `supportVolume`/`supportArea` a slicer would
226
+ add, `bridges` and `maxBridgeSpan`, `islands` (starts in mid-air),
227
+ `bed` (`contactArea`, `feet`, `smallestFoot`, `comMargin` = how far the
228
+ centre of mass sits inside the contact, `aspect`), and `warnings`.
229
+ - `durability` (when a case has `cycles` or `hours`, or the study has a
230
+ `durability` block): per case the fatigue `life` and Miner `damage`
231
+ (and `interlayerLife` across layers), or the `ruptureStrength`,
232
+ `creepRatio` and `creepDeflection` of a held load; `fatigueSafetyFactor`
233
+ (the factor on the cyclic loads that uses up the life), `creepSafetyFactor`,
234
+ their minimum `safetyFactor`, `fatigueLimitedBy`, `joints` (each fastener's
235
+ `preloadRetention` and head `bearingSafetyFactor`), the material data used
236
+ with its source and quality, and `warnings`.
237
+ - `python -m loadpath orient part.stl [--study s.json] [--3mf part.3mf]`
238
+ ranks build orientations of any mesh (the axes and its own flat faces) by
239
+ layer-bond safety under the study's loads, support and bed risk.
240
+
241
+ ## Evals
242
+
243
+ `python -m loadpath.evals.run --out evals-out/` runs 15 parts with hard
244
+ requirements (robot-arm links, brackets, a motor mount, a bell crank, a
245
+ hook printed without support, the MBB beam, a metal heat exchanger) and
246
+ scores the delivered meshes. Exit code is the number of failing cases. Run
247
+ it after any change to the solver. Results and the case list are in
248
+ [docs/case-studies/evals.md](../docs/case-studies/evals.md).
249
+
250
+ - `--tier coarse` runs every case on a coarse grid with a short search and no
251
+ fine-grid confirm: a smoke run for each push, compared against a coarse
252
+ baseline (`--baseline old/scorecard.json --gate regressions`).
253
+ - Every case also gets a Pareto score (`pareto.py`): `massAtTarget`, the
254
+ mass the outcomes' mass/SF front needs at the target SF, which moves when
255
+ a part gets lighter at the same SF and pass/fail does not.
256
+ - `--corpus 8` (or `100:108`) adds generated cases (`corpus.py`): links,
257
+ brackets and three-point mounts from a seed, so a fix tuned on the
258
+ hand-written cases is also tried on parts nobody tuned on.
259
+ - `--critic anthropic|agent` renders interface close-ups of each pick and
260
+ has a vision model file findings (creases, sawn faces, blocked seats,
261
+ knife edges) as metrics (`loadpath/critic.py`). `agent` writes a request
262
+ for the agent running the evals to answer by looking at the images;
263
+ `python -m loadpath.critic score evals-out/` folds the answers in.
264
+
265
+ ## License
266
+
267
+ MIT.
268
+
269
+ ## Limits
270
+
271
+ See [docs/VALIDATION.md](../docs/VALIDATION.md) for the evidence behind each
272
+ model.
273
+
274
+ - Linear elastic, small deflection. Printed layers are modelled as
275
+ transversely isotropic stiffness plus a tension-across-layers strength
276
+ check; interlayer shear is not checked separately. Buckling is in
277
+ nonlinear.py; no contact.
278
+ - Fatigue and creep are estimates: one published design curve for printed
279
+ PLA, scaled by strength for PETG, ABS and ASA; rough values for TPU,
280
+ resins and metals; creep from typical creep-modulus data, warmed through
281
+ the modulus-retention tables. No frequency (self-heating), moisture or
282
+ variable-amplitude counting beyond Miner's rule.
283
+ - Overhang control is the 45° stencil on the voxel grid; off-axis directions
284
+ use the 26-neighbour cells within 45° of straight down, which is coarser.
285
+ Bridges are recognized by the print check, not designed for.
286
+ - Peak stress from a voxel model is refined at hotspots and carries a 1.15
287
+ allowance in the safety factor; it lands within about ±13% of the
288
+ textbook plate-with-hole value in the tests.
289
+ - Material cards are datasheet values until calibrated: `loadpath calibrate
290
+ kit` prints coupons, `loadpath calibrate fit` turns their measurements into
291
+ a card for your printer (calibration.py, docs/CALIBRATION.md); a saved card
292
+ is usable by name as the study's material.
293
+ - Pure Python and SciPy. A 40,000-cell study with two load cases takes a few
294
+ minutes on a laptop core; a safety-factor search runs several of those.