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/critic.py ADDED
@@ -0,0 +1,212 @@
1
+ """
2
+ Vision critic: a model looks at the interface close-ups (render.render_interfaces)
3
+ and files what it sees as findings, so the defects Connor used to find by
4
+ zooming in become numbers an eval can gate on.
5
+
6
+ Why: GRIP's wrist yoke passed every metric with sawn-off faces, hard creases
7
+ at its bosses and material over its bolt seats (QA, 10/28). The crease and
8
+ boundary-cut metrics came out of that, one bug class at a time. A critic
9
+ reads the picture the way a person does and catches the next class before
10
+ anyone writes a metric for it.
11
+
12
+ Two backends:
13
+
14
+ anthropic calls the Claude API with the close-ups and a rubric and gets
15
+ structured findings back. Needs the `anthropic` package and a
16
+ credential (ANTHROPIC_API_KEY or an `ant auth login` profile).
17
+ Model: LOADPATH_CRITIC_MODEL (default claude-opus-5-5).
18
+ agent no API: writes <name>.critic-request.md next to the images
19
+ (rubric, region roles, output schema) for the agent running
20
+ the eval to look at and answer in <name>.critic.json. Claude
21
+ Code sessions read PNGs natively, so this is the free path.
22
+ `python -m loadpath.critic score <out>` folds answers into
23
+ the scorecard.
24
+
25
+ A finding has a kind (KINDS), a severity and the panel it is in. The score is
26
+ SEVERITY-weighted: minor 1, major 3, blocker 10. Evals gate on
27
+ `maxCriticBlockers` and `maxCriticScore`.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import base64
32
+ import json
33
+ import os
34
+ import sys
35
+
36
+ KINDS = {
37
+ "crease": "a sharp concave edge or notch where two surfaces meet (a stress raiser; should be a fillet)",
38
+ "sawn-face": "organic material cut dead flat where nothing mates, as if sawn off on the design-space box",
39
+ "seat-blocked": "material over a bolt head, nut, washer or bearing seat, or a bore closed over",
40
+ "thin-member": "a strut or wall visibly thinner than its neighbours, a knife edge, or a hairline bridge",
41
+ "floating": "a piece that does not connect, or hangs by a sliver",
42
+ "rough-surface": "stair steps, voxel terraces, holes or tearing in the surface",
43
+ "missing-preserve": "a boss, face or ring that should be kept is partly eaten away",
44
+ "other": "anything else a careful engineer would send back",
45
+ }
46
+ SEVERITY = {"minor": 1, "major": 3, "blocker": 10}
47
+
48
+ SCHEMA = {
49
+ "type": "object",
50
+ "properties": {
51
+ "findings": {"type": "array", "items": {
52
+ "type": "object",
53
+ "properties": {
54
+ "panel": {"type": "string", "description": "the panel title the finding is in"},
55
+ "kind": {"type": "string", "enum": list(KINDS)},
56
+ "severity": {"type": "string", "enum": list(SEVERITY)},
57
+ "note": {"type": "string", "description": "what and where, one sentence"},
58
+ },
59
+ "required": ["panel", "kind", "severity", "note"],
60
+ "additionalProperties": False}},
61
+ "summary": {"type": "string", "description": "one or two sentences on the finish overall"},
62
+ },
63
+ "required": ["findings", "summary"],
64
+ "additionalProperties": False,
65
+ }
66
+
67
+ RUBRIC = """You are reviewing close-up renders of a part designed by topology
68
+ optimization, before it is sent to print. Each panel is one place the part
69
+ meets the outside world, titled by region id. Grey shaded surface, lit from
70
+ above; the crop box edges cut the mesh, so straight cuts AT THE PANEL EDGE
71
+ are the crop, not the part.
72
+
73
+ Regions:
74
+ {regions}
75
+
76
+ Look for these defects, and only report what you can actually see:
77
+ {kinds}
78
+
79
+ Severity: blocker = the part cannot be assembled or will fail there (a seat
80
+ covered, a bore closed, a member nearly cut through); major = would be sent
81
+ back for rework (a sharp crease at a loaded boss, a sawn face, a knife edge);
82
+ minor = cosmetic or slight. A clean panel gets no finding. Organic, smoothly
83
+ blended shapes are intended; do not report them. Do not invent findings to
84
+ seem thorough: an empty list is a good answer for a good part."""
85
+
86
+
87
+ def _regions(study) -> str:
88
+ if study is None:
89
+ return "(not given)"
90
+ lines = []
91
+ for r in getattr(study, "preserve", []) or []:
92
+ lines.append(f"- {r.id}: preserved geometry (boss, ring or mounting face kept exactly; should be "
93
+ f"intact and blend smoothly into the part)")
94
+ for r in getattr(study, "keep_out", []) or []:
95
+ lines.append(f"- {r.id}: keep-out (must stay empty: bore, bolt or tool access, clearance)")
96
+ return "\n".join(lines) or "(none)"
97
+
98
+
99
+ def prompt(study=None) -> str:
100
+ kinds = "\n".join(f"- {k}: {v}" for k, v in KINDS.items())
101
+ return RUBRIC.format(regions=_regions(study), kinds=kinds)
102
+
103
+
104
+ def score(findings) -> dict:
105
+ """Totals from a findings list (critic JSON 'findings')."""
106
+ fs = [f for f in findings or [] if f.get("severity") in SEVERITY]
107
+ by_kind = {}
108
+ for f in fs:
109
+ by_kind[f["kind"]] = by_kind.get(f["kind"], 0) + 1
110
+ return {"criticScore": sum(SEVERITY[f["severity"]] for f in fs),
111
+ "criticBlockers": sum(f["severity"] == "blocker" for f in fs),
112
+ "criticMajors": sum(f["severity"] == "major" for f in fs),
113
+ "criticFindings": len(fs), "criticByKind": by_kind}
114
+
115
+
116
+ def critique_anthropic(images, study=None, model=None, effort="medium") -> dict:
117
+ import anthropic
118
+
119
+ client = anthropic.Anthropic()
120
+ model = model or os.environ.get("LOADPATH_CRITIC_MODEL", "claude-opus-5-5")
121
+ content = []
122
+ for p in images:
123
+ with open(p, "rb") as fh:
124
+ content.append({"type": "image", "source": {"type": "base64", "media_type": "image/png",
125
+ "data": base64.standard_b64encode(fh.read()).decode()}})
126
+ content.append({"type": "text", "text": prompt(study)})
127
+ resp = client.beta.messages.create(
128
+ model=model, max_tokens=16000,
129
+ output_config={"effort": effort, "format": {"type": "json_schema", "schema": SCHEMA}},
130
+ # a declined request is re-run on the fallback model in the same call
131
+ betas=["server-side-fallback-2026-07-01"], extra_body={"fallbacks": "default"},
132
+ messages=[{"role": "user", "content": content}])
133
+ if resp.stop_reason == "refusal":
134
+ raise RuntimeError(f"critic declined: {getattr(resp.stop_details, 'category', None)}")
135
+ text = next(b.text for b in resp.content if b.type == "text")
136
+ out = json.loads(text)
137
+ out["model"] = resp.model
138
+ return out
139
+
140
+
141
+ def request_agent(images, study, request_path, answer_path) -> str:
142
+ """Write the critic request for an agent to answer by hand."""
143
+ imgs = "\n".join(f"- {os.path.abspath(p)}" for p in images)
144
+ with open(request_path, "w") as fh:
145
+ fh.write(f"# Critic request\n\nLook at:\n{imgs}\n\n{prompt(study)}\n\n"
146
+ f"Write the answer as JSON to `{os.path.abspath(answer_path)}`, matching this schema:\n\n"
147
+ f"```json\n{json.dumps(SCHEMA, indent=1)}\n```\n")
148
+ return request_path
149
+
150
+
151
+ def critique(images, study=None, backend="anthropic", out_dir=None, name="part") -> dict | None:
152
+ """Findings + score for close-up images, or None when the agent backend
153
+ has written a request that nobody has answered yet."""
154
+ images = [p for p in images if p and os.path.exists(p)]
155
+ if not images:
156
+ return None
157
+ if backend == "anthropic":
158
+ res = critique_anthropic(images, study)
159
+ elif backend == "agent":
160
+ out_dir = out_dir or os.path.dirname(images[0])
161
+ answer = os.path.join(out_dir, f"{name}.critic.json")
162
+ if not os.path.exists(answer):
163
+ request_agent(images, study, os.path.join(out_dir, f"{name}.critic-request.md"), answer)
164
+ return None
165
+ with open(answer) as fh:
166
+ res = json.load(fh)
167
+ else:
168
+ raise ValueError(f"unknown critic backend {backend!r} (anthropic, agent)")
169
+ res["images"] = [os.path.basename(p) for p in images]
170
+ return res | score(res.get("findings"))
171
+
172
+
173
+ def fold_into_scorecard(out_dir) -> list:
174
+ """Read every <case>.critic.json in an eval output folder into
175
+ scorecard.json's rows (and their gates), rewrite scorecard.md."""
176
+ from .evals.run import _md, gate_critic
177
+
178
+ path = os.path.join(out_dir, "scorecard.json")
179
+ with open(path) as fh:
180
+ rows = json.load(fh)
181
+ for r in rows:
182
+ f = os.path.join(out_dir, f"{r['case']}.critic.json")
183
+ if not os.path.exists(f):
184
+ continue
185
+ with open(f) as fh:
186
+ res = json.load(fh)
187
+ r["critic"] = res | score(res.get("findings"))
188
+ gate_critic(r)
189
+ with open(path, "w") as fh:
190
+ json.dump(rows, fh, indent=2)
191
+ with open(os.path.join(out_dir, "scorecard.md"), "w") as fh:
192
+ fh.write(_md(rows))
193
+ return rows
194
+
195
+
196
+ def main(argv=None):
197
+ import argparse
198
+
199
+ p = argparse.ArgumentParser(prog="loadpath.critic")
200
+ sub = p.add_subparsers(dest="cmd", required=True)
201
+ s = sub.add_parser("score", help="fold <case>.critic.json answers into an eval scorecard")
202
+ s.add_argument("out")
203
+ a = p.parse_args(argv)
204
+ if a.cmd == "score":
205
+ rows = fold_into_scorecard(a.out)
206
+ print(open(os.path.join(a.out, "scorecard.md")).read())
207
+ return sum(0 if r.get("pass") else 1 for r in rows)
208
+ return 0
209
+
210
+
211
+ if __name__ == "__main__":
212
+ sys.exit(main())