wobbl 0.1.0__tar.gz

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 (176) hide show
  1. wobbl-0.1.0/.gitattributes +13 -0
  2. wobbl-0.1.0/.gitignore +38 -0
  3. wobbl-0.1.0/CHANGELOG.md +64 -0
  4. wobbl-0.1.0/LICENSE +21 -0
  5. wobbl-0.1.0/PKG-INFO +343 -0
  6. wobbl-0.1.0/README.md +311 -0
  7. wobbl-0.1.0/docs/agents.md +11 -0
  8. wobbl-0.1.0/docs/architecture.md +11 -0
  9. wobbl-0.1.0/docs/bakeoff.md +10 -0
  10. wobbl-0.1.0/docs/circuits.md +9 -0
  11. wobbl-0.1.0/docs/cli.md +10 -0
  12. wobbl-0.1.0/docs/fix.md +10 -0
  13. wobbl-0.1.0/docs/getting-started.md +15 -0
  14. wobbl-0.1.0/docs/heat.md +10 -0
  15. wobbl-0.1.0/docs/limits.md +10 -0
  16. wobbl-0.1.0/docs/publishing.md +47 -0
  17. wobbl-0.1.0/docs/strength.md +14 -0
  18. wobbl-0.1.0/docs/validation-results.json +337 -0
  19. wobbl-0.1.0/docs/validation.md +12 -0
  20. wobbl-0.1.0/docs/verification.md +12 -0
  21. wobbl-0.1.0/examples/battery-pack/README.md +24 -0
  22. wobbl-0.1.0/examples/battery-pack/output/outcome.json +143 -0
  23. wobbl-0.1.0/examples/battery-pack/output/summary.txt +27 -0
  24. wobbl-0.1.0/examples/battery-pack/pack.py +11 -0
  25. wobbl-0.1.0/examples/battery-pack/run.sh +7 -0
  26. wobbl-0.1.0/examples/cast-motor-mount/README.md +35 -0
  27. wobbl-0.1.0/examples/cast-motor-mount/output/geometry.json +22 -0
  28. wobbl-0.1.0/examples/cast-motor-mount/output/outcome.json +3055 -0
  29. wobbl-0.1.0/examples/cast-motor-mount/output/study.json +44 -0
  30. wobbl-0.1.0/examples/cast-motor-mount/output/summary.txt +31 -0
  31. wobbl-0.1.0/examples/cast-motor-mount/run.sh +10 -0
  32. wobbl-0.1.0/examples/circuits/README.md +19 -0
  33. wobbl-0.1.0/examples/circuits/buck/netlist.cir +12 -0
  34. wobbl-0.1.0/examples/circuits/buck/outcome.json +87 -0
  35. wobbl-0.1.0/examples/circuits/buck/summary.txt +20 -0
  36. wobbl-0.1.0/examples/circuits/led/netlist.cir +7 -0
  37. wobbl-0.1.0/examples/circuits/led/outcome.json +90 -0
  38. wobbl-0.1.0/examples/circuits/led/summary.txt +21 -0
  39. wobbl-0.1.0/examples/circuits/mosfet/netlist.cir +8 -0
  40. wobbl-0.1.0/examples/circuits/mosfet/outcome.json +76 -0
  41. wobbl-0.1.0/examples/circuits/mosfet/summary.txt +17 -0
  42. wobbl-0.1.0/examples/circuits/run.sh +10 -0
  43. wobbl-0.1.0/examples/drone-arm/README.md +34 -0
  44. wobbl-0.1.0/examples/drone-arm/arm.py +41 -0
  45. wobbl-0.1.0/examples/drone-arm/output/geometry.json +24 -0
  46. wobbl-0.1.0/examples/drone-arm/output/outcome.json +2057 -0
  47. wobbl-0.1.0/examples/drone-arm/output/study.json +62 -0
  48. wobbl-0.1.0/examples/drone-arm/output/summary.txt +32 -0
  49. wobbl-0.1.0/examples/drone-arm/run.sh +8 -0
  50. wobbl-0.1.0/examples/enclosure/README.md +26 -0
  51. wobbl-0.1.0/examples/enclosure/enclosure.py +45 -0
  52. wobbl-0.1.0/examples/enclosure/output/geometry.json +24 -0
  53. wobbl-0.1.0/examples/enclosure/output/outcome.json +3063 -0
  54. wobbl-0.1.0/examples/enclosure/output/study.json +40 -0
  55. wobbl-0.1.0/examples/enclosure/output/summary.txt +28 -0
  56. wobbl-0.1.0/examples/enclosure/run.sh +6 -0
  57. wobbl-0.1.0/examples/heat-sink/README.md +23 -0
  58. wobbl-0.1.0/examples/heat-sink/heatsink.py +21 -0
  59. wobbl-0.1.0/examples/heat-sink/output/outcome.json +152 -0
  60. wobbl-0.1.0/examples/heat-sink/output/summary.txt +24 -0
  61. wobbl-0.1.0/examples/heat-sink/run.sh +7 -0
  62. wobbl-0.1.0/examples/motor-mount/README.md +34 -0
  63. wobbl-0.1.0/examples/motor-mount/mount.py +40 -0
  64. wobbl-0.1.0/examples/motor-mount/output/geometry.json +24 -0
  65. wobbl-0.1.0/examples/motor-mount/output/outcome.json +1984 -0
  66. wobbl-0.1.0/examples/motor-mount/output/study.json +44 -0
  67. wobbl-0.1.0/examples/motor-mount/output/summary.txt +27 -0
  68. wobbl-0.1.0/examples/motor-mount/output-noribs/geometry.json +24 -0
  69. wobbl-0.1.0/examples/motor-mount/output-noribs/outcome.json +1752 -0
  70. wobbl-0.1.0/examples/motor-mount/output-noribs/study.json +46 -0
  71. wobbl-0.1.0/examples/motor-mount/output-noribs/summary.txt +33 -0
  72. wobbl-0.1.0/examples/motor-mount/run.sh +9 -0
  73. wobbl-0.1.0/examples/run_all.sh +8 -0
  74. wobbl-0.1.0/examples/shelf-bracket/README.md +32 -0
  75. wobbl-0.1.0/examples/shelf-bracket/bracket.py +31 -0
  76. wobbl-0.1.0/examples/shelf-bracket/output/fix-fillet/geometry.json +24 -0
  77. wobbl-0.1.0/examples/shelf-bracket/output/fix-fillet/outcome.json +980 -0
  78. wobbl-0.1.0/examples/shelf-bracket/output/fix-fillet/study.json +48 -0
  79. wobbl-0.1.0/examples/shelf-bracket/output/fix-fillet/summary.txt +27 -0
  80. wobbl-0.1.0/examples/shelf-bracket/output/fix-gusset/geometry/gusset_t6.step +1258 -0
  81. wobbl-0.1.0/examples/shelf-bracket/output/fix-gusset/geometry.json +24 -0
  82. wobbl-0.1.0/examples/shelf-bracket/output/fix-gusset/outcome.json +1292 -0
  83. wobbl-0.1.0/examples/shelf-bracket/output/fix-gusset/study.json +46 -0
  84. wobbl-0.1.0/examples/shelf-bracket/output/fix-gusset/summary.txt +27 -0
  85. wobbl-0.1.0/examples/shelf-bracket/output/fix-thicken/geometry.json +24 -0
  86. wobbl-0.1.0/examples/shelf-bracket/output/fix-thicken/outcome.json +983 -0
  87. wobbl-0.1.0/examples/shelf-bracket/output/fix-thicken/study.json +48 -0
  88. wobbl-0.1.0/examples/shelf-bracket/output/fix-thicken/summary.txt +27 -0
  89. wobbl-0.1.0/examples/shelf-bracket/output/geometry.json +24 -0
  90. wobbl-0.1.0/examples/shelf-bracket/output/outcome.json +1075 -0
  91. wobbl-0.1.0/examples/shelf-bracket/output/study.json +46 -0
  92. wobbl-0.1.0/examples/shelf-bracket/output/summary.txt +33 -0
  93. wobbl-0.1.0/examples/shelf-bracket/run.sh +9 -0
  94. wobbl-0.1.0/pyproject.toml +57 -0
  95. wobbl-0.1.0/src/wobbl/__init__.py +16 -0
  96. wobbl-0.1.0/src/wobbl/__main__.py +4 -0
  97. wobbl-0.1.0/src/wobbl/ask.py +177 -0
  98. wobbl-0.1.0/src/wobbl/circuits/__init__.py +1 -0
  99. wobbl-0.1.0/src/wobbl/circuits/run.py +270 -0
  100. wobbl-0.1.0/src/wobbl/circuits/templates.py +184 -0
  101. wobbl-0.1.0/src/wobbl/circuits/tools.py +93 -0
  102. wobbl-0.1.0/src/wobbl/cli/__init__.py +1 -0
  103. wobbl-0.1.0/src/wobbl/cli/admin.py +177 -0
  104. wobbl-0.1.0/src/wobbl/cli/circuits.py +73 -0
  105. wobbl-0.1.0/src/wobbl/cli/main.py +438 -0
  106. wobbl-0.1.0/src/wobbl/cli/thermal.py +90 -0
  107. wobbl-0.1.0/src/wobbl/cli/ui.py +93 -0
  108. wobbl-0.1.0/src/wobbl/fe.py +141 -0
  109. wobbl-0.1.0/src/wobbl/fix/__init__.py +1 -0
  110. wobbl-0.1.0/src/wobbl/fix/apply.py +245 -0
  111. wobbl-0.1.0/src/wobbl/fix/cad_edit.py +115 -0
  112. wobbl-0.1.0/src/wobbl/fix/suggest.py +178 -0
  113. wobbl-0.1.0/src/wobbl/geometry/__init__.py +1 -0
  114. wobbl-0.1.0/src/wobbl/geometry/cad.py +350 -0
  115. wobbl-0.1.0/src/wobbl/geometry/faces.py +426 -0
  116. wobbl-0.1.0/src/wobbl/geometry/parametric.py +92 -0
  117. wobbl-0.1.0/src/wobbl/geometry/remap.py +67 -0
  118. wobbl-0.1.0/src/wobbl/geometry/select.py +270 -0
  119. wobbl-0.1.0/src/wobbl/materials.py +251 -0
  120. wobbl-0.1.0/src/wobbl/mcp/__init__.py +1 -0
  121. wobbl-0.1.0/src/wobbl/mcp/server.py +136 -0
  122. wobbl-0.1.0/src/wobbl/mcp/tools.py +284 -0
  123. wobbl-0.1.0/src/wobbl/mesh/__init__.py +1 -0
  124. wobbl-0.1.0/src/wobbl/mesh/isolate.py +90 -0
  125. wobbl-0.1.0/src/wobbl/mesh/mesher.py +696 -0
  126. wobbl-0.1.0/src/wobbl/outcome_generic.py +94 -0
  127. wobbl-0.1.0/src/wobbl/report/__init__.py +1 -0
  128. wobbl-0.1.0/src/wobbl/report/html.py +129 -0
  129. wobbl-0.1.0/src/wobbl/report/render.py +348 -0
  130. wobbl-0.1.0/src/wobbl/setup/__init__.py +1 -0
  131. wobbl-0.1.0/src/wobbl/setup/agents.py +240 -0
  132. wobbl-0.1.0/src/wobbl/setup/installer.py +247 -0
  133. wobbl-0.1.0/src/wobbl/skill/SKILL.md +64 -0
  134. wobbl-0.1.0/src/wobbl/solvers/__init__.py +1 -0
  135. wobbl-0.1.0/src/wobbl/solvers/base.py +115 -0
  136. wobbl-0.1.0/src/wobbl/solvers/calculix.py +334 -0
  137. wobbl-0.1.0/src/wobbl/solvers/locate.py +73 -0
  138. wobbl-0.1.0/src/wobbl/solvers/ngspice.py +74 -0
  139. wobbl-0.1.0/src/wobbl/structural/__init__.py +1 -0
  140. wobbl-0.1.0/src/wobbl/structural/budget.py +56 -0
  141. wobbl-0.1.0/src/wobbl/structural/build.py +286 -0
  142. wobbl-0.1.0/src/wobbl/structural/hotspots.py +183 -0
  143. wobbl-0.1.0/src/wobbl/structural/outcome.py +493 -0
  144. wobbl-0.1.0/src/wobbl/structural/pipeline.py +58 -0
  145. wobbl-0.1.0/src/wobbl/structural/report.py +101 -0
  146. wobbl-0.1.0/src/wobbl/structural/run.py +719 -0
  147. wobbl-0.1.0/src/wobbl/structural/spec.py +248 -0
  148. wobbl-0.1.0/src/wobbl/thermal/__init__.py +1 -0
  149. wobbl-0.1.0/src/wobbl/thermal/study.py +502 -0
  150. wobbl-0.1.0/src/wobbl/thermal/tools.py +85 -0
  151. wobbl-0.1.0/src/wobbl/units.py +212 -0
  152. wobbl-0.1.0/src/wobbl/validation/__init__.py +1 -0
  153. wobbl-0.1.0/src/wobbl/validation/cases.py +342 -0
  154. wobbl-0.1.0/src/wobbl/validation/geometry.py +93 -0
  155. wobbl-0.1.0/src/wobbl/validation/parts/README.md +16 -0
  156. wobbl-0.1.0/src/wobbl/validation/parts/cast_motor_mount.step +7204 -0
  157. wobbl-0.1.0/src/wobbl/validation/parts/drone_arm_truss.step +7849 -0
  158. wobbl-0.1.0/src/wobbl/validation/parts/enclosure_shell.step +5548 -0
  159. wobbl-0.1.0/src/wobbl/validation/parts/shelf_bracket_fillet.step +812 -0
  160. wobbl-0.1.0/src/wobbl/validation/parts/shelf_bracket_sharp.step +812 -0
  161. wobbl-0.1.0/src/wobbl/verify/__init__.py +1 -0
  162. wobbl-0.1.0/src/wobbl/verify/beam.py +118 -0
  163. wobbl-0.1.0/src/wobbl/verify/checks.py +184 -0
  164. wobbl-0.1.0/tests/conftest.py +68 -0
  165. wobbl-0.1.0/tests/test_cli_mcp.py +115 -0
  166. wobbl-0.1.0/tests/test_e2e.py +240 -0
  167. wobbl-0.1.0/tests/test_geometry.py +123 -0
  168. wobbl-0.1.0/tests/test_heat_balance.py +58 -0
  169. wobbl-0.1.0/tests/test_parametric_fix.py +106 -0
  170. wobbl-0.1.0/tests/test_render_report.py +32 -0
  171. wobbl-0.1.0/tests/test_result_vtu.py +28 -0
  172. wobbl-0.1.0/tests/test_setup_agents.py +80 -0
  173. wobbl-0.1.0/tests/test_skill_commands.py +118 -0
  174. wobbl-0.1.0/tests/test_spec_ask.py +138 -0
  175. wobbl-0.1.0/tests/test_units_materials.py +92 -0
  176. wobbl-0.1.0/tests/test_verify.py +135 -0
@@ -0,0 +1,13 @@
1
+ # Generated geometry and reports are data, not code
2
+ *.step binary
3
+ *.stp binary
4
+ *.stl binary
5
+ *.png binary
6
+ examples/**/report.html binary
7
+ docs/img/** binary
8
+ examples/**/outcome.json linguist-generated -diff
9
+ examples/**/study.json linguist-generated -diff
10
+ examples/**/geometry.json linguist-generated -diff
11
+ examples/**/summary.txt linguist-generated
12
+ examples/**/*.html linguist-generated -diff
13
+ examples/**/*.vtu binary
wobbl-0.1.0/.gitignore ADDED
@@ -0,0 +1,38 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
10
+ .DS_Store
11
+
12
+ # solver scratch output
13
+ wobbl-out/
14
+ *.frd
15
+ *.dat
16
+ *.sta
17
+ *.cvg
18
+ *.12d
19
+ spooles.out
20
+ *.vtu
21
+
22
+ # example runs keep their reports, summaries and images, not solver scratch
23
+ examples/**/work/
24
+ examples/**/*.npz
25
+ examples/**/*.brep
26
+ examples/**/geometry/*.json
27
+ examples/**/fix-*/report.html
28
+
29
+ # browser automation scratch
30
+ .playwright-cli/
31
+
32
+ # website build output (scripts/build_site.py)
33
+ _site/
34
+
35
+ # the website (Astro) builds into site/dist
36
+ site/node_modules/
37
+ site/dist/
38
+ site/.astro/
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-13)
4
+
5
+ First release.
6
+
7
+ **Strength**
8
+ - STEP, IGES, BREP, STL and parametric `.py` parts, read through OpenCASCADE with
9
+ healing only when a solid is broken, touching bodies bonded, tiny faces removed.
10
+ - Second order tetrahedral meshes (gmsh) with feature sizing, hotspot refinement,
11
+ automatic fallbacks (another algorithm, straight sided elements, a finer size),
12
+ and every mesh attempt isolated in a child process with a timeout.
13
+ - Loads can be forces, pressures, bolt bearing, gravity, or remote forces and
14
+ moments through distributing (RBE3 style) or rigid couplings. Supports can be
15
+ fixed (in all or some directions), bolted holes with washer rings, rollers,
16
+ or wall contact solved by an active set loop with a coarse pre solve and warm
17
+ starts.
18
+ - A face selector language picks sides, planes, normals, holes and fillets by
19
+ size, nearest faces, spherical and box patches, and ids.
20
+ - 23 sourced materials, including sand cast A356-T6, plus custom properties.
21
+ Each material says how it is made (printed, molded, cast or wrought), so its
22
+ assumptions fit, and printed parts can be checked across layers.
23
+ - A `bearing` keyword for a bolt or pin pressing on one side of a hole, as in
24
+ `--load "300 N -x bearing @ hole d=6.6"`. Without it the force spreads evenly
25
+ over the hole faces.
26
+
27
+ **Verification on every answer**
28
+ - Mesh convergence over at least three meshes, force and moment balance, a beam
29
+ theory hand calculation through the critical section, singularity detection
30
+ from exact CAD geometry, bolt bearing, units and deflection sanity, a large
31
+ deflection re-solve when a part bends a good fraction of its wall, and the
32
+ spread between rigid and distributed load attachment.
33
+ - Confidence (high, medium, low) with reasons, and never high after a failed
34
+ check.
35
+ - The same input gives the same numbers on every run, because meshing runs on
36
+ one thread with a fixed seed and every size comes from the CAD. When the
37
+ rigid and distributed load attachments disagree, the other one is converged
38
+ at its own peak before it is quoted.
39
+ - Bounded runtime, so the last finished level is returned when time or a
40
+ solver runs out.
41
+
42
+ **Seeing and fixing**
43
+ - Heatmaps with the failure point marked (VTK off screen, matplotlib fallback),
44
+ convergence plots, plain text summaries and a self contained HTML report.
45
+ - Fix suggestions with numbers. Fillets and gussets are applied to any STEP
46
+ through OpenCASCADE, along with thickness and fillet parameters of parametric
47
+ parts and material swaps, and each one is simulated again for a before and
48
+ after table.
49
+
50
+ **Heat and circuits**
51
+ - Steady and transient conduction with convection, radiation, heat sources and
52
+ held temperatures, with an energy balance on steady and transient runs and
53
+ lumped hand calculations.
54
+ - ngspice checks for LEDs, RC networks, buck converter ripple and MOSFET junction
55
+ temperature, plus arbitrary netlists, with textbook and time step checks.
56
+
57
+ **Agents**
58
+ - `wobbl setup` installs CalculiX and ngspice into `~/.wobbl` with a pinned
59
+ micromamba and registers an MCP server and skill for Claude Code, Codex, Cursor
60
+ and Gemini CLI, with backups and `wobbl uninstall`. A test parses every
61
+ command in the skill against the real CLI.
62
+ - A validation suite of 18 cases, with textbook solutions, NAFEMS LE1 and LE10,
63
+ and five real parts against converged reference models. It runs in about
64
+ 30 minutes on a 2018 six core laptop.
wobbl-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sohan Bhat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
wobbl-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,343 @@
1
+ Metadata-Version: 2.5
2
+ Name: wobbl
3
+ Version: 0.1.0
4
+ Summary: Simulate anything before you build it. Verified engineering simulation for AI coding agents and humans.
5
+ Project-URL: Homepage, https://github.com/sohan-bhat/wobbl
6
+ Project-URL: Issues, https://github.com/sohan-bhat/wobbl/issues
7
+ Author: Sohan Bhat
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: cad,calculix,engineering,fea,gmsh,mcp,ngspice,simulation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Manufacturing
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: click>=8.1
20
+ Requires-Dist: gmsh>=4.11
21
+ Requires-Dist: matplotlib>=3.7
22
+ Requires-Dist: mcp>=1.2
23
+ Requires-Dist: numpy>=1.24
24
+ Requires-Dist: pyvista>=0.43
25
+ Requires-Dist: rich>=13
26
+ Requires-Dist: scipy>=1.10
27
+ Requires-Dist: tomli>=2; python_version < '3.11'
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest-timeout>=2; extra == 'dev'
30
+ Requires-Dist: pytest>=7; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <p align="center">
34
+ <a href="https://sohan-bhat.github.io/wobbl/">
35
+ <picture>
36
+ <source media="(prefers-color-scheme: dark)" srcset="site/assets/logo-dark.svg">
37
+ <img alt="Wobbl" src="site/assets/logo-light.svg" width="280">
38
+ </picture>
39
+ </a>
40
+ </p>
41
+
42
+ <p align="center">
43
+ <strong>Simulate anything before you build it.</strong><br>
44
+ Verified strength, heat and circuit simulation for coding agents and the people who design physical things.
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://github.com/sohan-bhat/wobbl/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/sohan-bhat/wobbl/actions/workflows/ci.yml/badge.svg"></a>
49
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/github/license/sohan-bhat/wobbl"></a>
50
+ <a href="pyproject.toml"><img alt="Python 3.10 or newer" src="https://img.shields.io/badge/python-3.10%2B-3776ab"></a>
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="https://sohan-bhat.github.io/wobbl/">Website</a> &nbsp;|&nbsp;
55
+ <a href="https://sohan-bhat.github.io/wobbl/get-started/">Getting started</a> &nbsp;|&nbsp;
56
+ <a href="https://sohan-bhat.github.io/wobbl/guides/">Guides</a> &nbsp;|&nbsp;
57
+ <a href="https://sohan-bhat.github.io/wobbl/guides/cli/">CLI reference</a> &nbsp;|&nbsp;
58
+ <a href="https://sohan-bhat.github.io/wobbl/examples/">Examples</a>
59
+ </p>
60
+
61
+ ![Stress heatmap of a cast aluminum motor mount under a 2 kN belt pull, peak 124 MPa marked where the rib tip meets the upright](examples/cast-motor-mount/output/images/stress.png)
62
+
63
+ <p align="center"><sub>A real Wobbl output, a sand cast motor mount under a 2 kN belt pull, with the peak of 124 MPa at the rib tip and a safety factor of 1.33 against a target of 2.</sub></p>
64
+
65
+ ## What it is
66
+
67
+ Wobbl is a command line tool, an MCP server and an agent skill that run real
68
+ engineering solvers and check every answer before anyone acts on it. Point it at
69
+ a STEP, IGES, STL or parametric Python part, ask a question in plain words or
70
+ with explicit loads and supports, and you get back the number, where it happens,
71
+ a confidence level with its reasons, the assumptions, a heatmap with the failure
72
+ point marked and fixes with numbers.
73
+
74
+ - Strength with [CalculiX](http://www.calculix.de), on second order tetrahedral
75
+ meshes from [gmsh](https://gmsh.info) and OpenCASCADE that are refined around the
76
+ peaks until the stress settles.
77
+ - Heat, steady and transient, also with CalculiX.
78
+ - Circuits with [ngspice](https://ngspice.sourceforge.io), for LEDs, RC networks,
79
+ buck converters, MOSFETs or any netlist.
80
+
81
+ `wobbl fix --apply` edits the part, adding fillets and gussets to any STEP file,
82
+ and runs the whole study again. The same tools work from your terminal and from
83
+ Claude Code, Codex, Cursor and Gemini CLI.
84
+
85
+ ## Quick start
86
+
87
+ Wobbl needs Python 3.10 or newer, and until it is on PyPI you install it from GitHub.
88
+
89
+ ```sh
90
+ pipx install "git+https://github.com/sohan-bhat/wobbl"
91
+ # or: uv tool install "git+https://github.com/sohan-bhat/wobbl"
92
+
93
+ wobbl setup # solvers into ~/.wobbl, MCP server and skill for your agents
94
+ wobbl doctor # check every piece
95
+ ```
96
+
97
+ `wobbl setup` downloads a pinned micromamba and installs the solvers from
98
+ conda-forge into `~/.wobbl` (about 340 MB, no admin rights). Use
99
+ `wobbl setup --no-agents` for solvers only, or `--agents claude,cursor` to pick.
100
+
101
+ This first answer needs no files.
102
+
103
+ ```sh
104
+ wobbl "will a blue LED on 5V with a 47 ohm resistor fry?"
105
+ ```
106
+
107
+ Then try your own part.
108
+
109
+ ```sh
110
+ # holes, fillets, sharp corners and face ids
111
+ wobbl inspect part.step
112
+
113
+ # ask in plain words, or give the material, supports and loads yourself
114
+ wobbl part.step "will this 6061 bracket yield under 50 lbf at the tip?"
115
+ wobbl run part.step -m petg --fix "bolt holes d=5.5" \
116
+ --load "250 N down at 0,-15,31 @ holes d=3.4 rigid"
117
+
118
+ # build the fixes (fillets, gussets, thicker walls) and run each again
119
+ wobbl fix wobbl-out/part --apply
120
+ ```
121
+
122
+ Or run a bundled example, which takes a few minutes.
123
+
124
+ ```sh
125
+ git clone https://github.com/sohan-bhat/wobbl
126
+ cd wobbl/examples/shelf-bracket && ./run.sh
127
+ ```
128
+
129
+ | Platform | CalculiX | ngspice |
130
+ |---|---|---|
131
+ | macOS Apple Silicon | native | Intel build under Rosetta 2 |
132
+ | macOS Intel | native | native |
133
+ | Linux x86_64 | native | native |
134
+ | Linux arm64 | native | not packaged, so install it with your package manager and Wobbl finds it |
135
+ | Windows x64 | native | native |
136
+
137
+ ## Example output
138
+
139
+ This is a printed PLA shelf bracket, screwed to a wall and carrying its share of
140
+ an 8 kg shelf. Every run writes `summary.txt`, which is what an agent reads, along
141
+ with `report.html`, `outcome.json`, heatmaps and `result.vtu` for ParaView.
142
+
143
+ ```console
144
+ $ cd examples/shelf-bracket
145
+ $ wobbl run bracket.py -m pla --fix "bolt holes d=4.5" --fix "wall x=min" \
146
+ --load "40 N down @ top and within 12 of 65,12.5,6" -o output
147
+ $ cat output/summary.txt
148
+ FAILS. Peak 52.3 MPa on the 0.5 mm inside fillet (face 3) at (6.19, 12.6, 6.11). The part cracks at about 0.96x this load.
149
+
150
+ Confidence: high. passed mesh convergence, force balance, hand calculation, singularities, bolt bearing, units and sanity
151
+ Max deflection 3.47 mm at (90, 0, 6).
152
+
153
+ Checks:
154
+ [pass] mesh convergence: max principal stress settled: 43 -> 50.9 -> 53 -> 52.3 MPa over 4 meshes, last change 1.4%
155
+ [pass] force balance: reactions balance the loads to 0.00014% (forces) and 8.4e-06% (moments)
156
+ [pass] hand calculation: beam theory through the critical section gives 15.2 MPa nominal; FEA peak is 3.45x that, a stress concentration of about 3.4 from the local shape
157
+ [pass] singularities: no sharp corner or support artifact is above the reported stress
158
+ [pass] bolt bearing: worst hole of fix1 (d 4.5 mm) carries 2.7 N sideways and 45.7 N along its axis; bearing stress 0.1 MPa, safety factor 499.33 on bearing
159
+ [pass] units and sanity: part 123 mm across, peak strain 0.015, max deflection 3.47 mm (2.8% of size): all plausible
160
+
161
+ Suggested fixes:
162
+ - Stress piles up in the 0.5 mm fillet. A fillet of 3 mm (about half the 6 mm wall) spreads it out. ...
163
+ - The peak sits where two walls meet and bend. A gusset as thick as the wall ...
164
+ ```
165
+
166
+ `wobbl fix output --apply` then builds each fix and solves it again.
167
+
168
+ | Version | Peak stress | Safety factor (PLA, 50 MPa) | Verdict |
169
+ |---|---|---|---|
170
+ | As drawn, 0.5 mm corner | 52.3 MPa | 0.96 | fails |
171
+ | 3 mm fillet | 24.1 MPa | 2.08 | marginal (target 2.5) |
172
+ | Legs thickened to 10 mm | 21.0 MPa | 2.38 | marginal |
173
+ | Gusset across the corner | 9.82 MPa | 5.09 | holds |
174
+
175
+ <p>
176
+ <img alt="The bracket as drawn, with a thin line of high stress along the 0.5 mm inside corner peaking at 52.3 MPa" src="examples/shelf-bracket/output/images/stress.png" width="49%">
177
+ <img alt="The bracket with a gusset, where the peak drops to 9.82 MPa near the gusset toe" src="examples/shelf-bracket/output/fix-gusset/images/stress.png" width="49%">
178
+ </p>
179
+
180
+ ## An agent on its own versus Wobbl
181
+
182
+ Given a CAD file, a strength question and 15 minutes, a capable coding agent
183
+ working alone answered in 1 of 10 trials across six
184
+ parts. Its hand calculations on beam like parts were good, within 1 to 5%. On the
185
+ ribbed cast motor mount and the thin walled enclosure, the two parts that
186
+ actually fail, it delivered nothing in six trials, because it kept writing its own
187
+ finite element code and hitting the same traps, such as meshers folding thin curved
188
+ walls, singular peaks at bonded bolts and load idealizations that flip the verdict.
189
+
190
+ Wobbl answers the same parts in minutes and handles each of those traps by default.
191
+
192
+ | Part | Converged reference | Wobbl | Confidence |
193
+ |---|---|---|---|
194
+ | Cast motor mount, belt pull through four bolts | 124.3 MPa at the rib tip | 123.7 MPa, same spot | medium, because the verdict flips if the servo is treated as rigid (55.0 versus 124 MPa), and Wobbl says so |
195
+ | ABS enclosure shell pressed near a corner | 93 MPa, 1.93 mm deflection | 93.0 MPa, 1.92 mm | medium, because large deflection theory lowers the peak 11%, and Wobbl says so |
196
+ | PA12 truss drone arm, thrust and torque | 11.5 MPa, 4.09 mm rise | 11.49 MPa, 4.08 mm | high |
197
+ | PLA bracket, sharp inside corner | about 78 MPa | 80.5 MPa | high |
198
+
199
+ The full comparison, with times and every trap, is [on the website](https://sohan-bhat.github.io/wobbl/validation/bakeoff/).
200
+
201
+ ## Using it from agents
202
+
203
+ `wobbl setup` registers an MCP server and installs a skill for every coding
204
+ agent it finds, backing up each file it touches. `wobbl uninstall` removes
205
+ exactly what it added.
206
+
207
+ | Agent | MCP server | Skill or context |
208
+ |---|---|---|
209
+ | Claude Code | `claude mcp add --scope user wobbl` | `~/.claude/skills/wobbl/SKILL.md` |
210
+ | Codex | `[mcp_servers.wobbl]` in `~/.codex/config.toml` | `~/.codex/skills/wobbl/SKILL.md` |
211
+ | Cursor | `mcpServers.wobbl` in `~/.cursor/mcp.json` | `~/.cursor/skills/wobbl/SKILL.md` |
212
+ | Gemini CLI | extension at `~/.gemini/extensions/wobbl/` | `WOBBL.md` in the same extension |
213
+
214
+ The agent gets 13 tools, `inspect_geometry`, `mesh`, `new_study`,
215
+ `set_material`, `apply_load`, `add_fixture`, `solve_structural`, `verify`,
216
+ `render`, `suggest_fix`, `solve_thermal`, `solve_circuit` and `list_materials`.
217
+ Results are compact JSON with image and report paths. The skill teaches the
218
+ workflow and what never to skip, which means reading the checks before the number,
219
+ never quoting an excluded peak and making the supports match the real build.
220
+
221
+ ```text
222
+ > Will this printed PLA shelf bracket hold its share of an 8 kg shelf?
223
+
224
+ agent: solve_structural(path="bracket.py", material="pla",
225
+ fixtures=["bolt holes d=4.5", "wall x=min"],
226
+ loads=["40 N down @ top and within 12 of 65,12.5,6"])
227
+ verdict fails, 52.3 MPa on the 0.5 mm inside fillet, safety factor 0.96, confidence high
228
+ agent: suggest_fix(result_dir, apply=true)
229
+ 3 mm fillet 24.1 MPa (2.08), legs at 10 mm 21 MPa (2.38), gusset 9.82 MPa (5.09)
230
+ ```
231
+
232
+ Strength studies take one to seven minutes on a 2018 laptop. Each agent's setup, the
233
+ timeouts and every tool are [on the website](https://sohan-bhat.github.io/wobbl/guides/agents/).
234
+
235
+ ## What it checks
236
+
237
+ Every strength answer runs these checks and turns them into a confidence level,
238
+ high, medium or low, always with the reasons. A result that failed a check is
239
+ never reported as high confidence.
240
+
241
+ - Mesh convergence over at least three meshes, refined around the peaks that matter.
242
+ - Force and moment balance, with the reactions cancelling the loads to 0.5%.
243
+ - A hand calculation with beam theory through the critical section, and a check that the stress concentration is physical.
244
+ - Singularities, with sharp inside corners found from exact CAD normals and the peaks there and at idealized supports excluded, named and tracked.
245
+ - Load introduction, where off part loads are solved both rigid and distributed, each converged at its own peak, and the spread is reported.
246
+ - Bolt bearing at bolted holes, in place of the idealized peak.
247
+ - Units and sanity for the part size, peak strain and deflection.
248
+ - Large deflection, a nonlinear re-solve when the part bends a good fraction of its wall.
249
+
250
+ Heat answers check energy balance and a lumped hand calculation, and circuit
251
+ answers are compared with the textbook and run again at half the time step. Every
252
+ study has a time budget, and when it runs out the last finished mesh level comes
253
+ back with its checks. The same input gives the same answer on every run, because
254
+ meshing, wall thickness sampling and the budget are all deterministic. The website
255
+ explains every [check](https://sohan-bhat.github.io/wobbl/guides/results/checks/) and how the
256
+ [confidence](https://sohan-bhat.github.io/wobbl/guides/results/confidence/) is set.
257
+
258
+ ## Validation
259
+
260
+ Eighteen cases with known answers run through the same pipeline you get. Every
261
+ case passes its tolerance, and every case whose reference is exact for the
262
+ modeled setup lands within 2%.
263
+
264
+ | Group | Cases | Largest error |
265
+ |---|---|---|
266
+ | Textbook and benchmark strength (Timoshenko, Kirsch, Lame, NAFEMS LE1 and LE10, torsion, self weight) | 8 | 1.54%, plate with a hole |
267
+ | Real parts against converged reference models | 5 | 4.26%, PLA bracket with a 6 mm fillet |
268
+ | Heat (slab conduction, lumped cooling) | 2 | 0.44% |
269
+ | Circuits (LED, RC, buck ripple) | 3 | 7.70%, buck ripple against an ideal formula that ignores switch and diode drops |
270
+
271
+ The full table is [on the website](https://sohan-bhat.github.io/wobbl/validation/). Run it yourself with
272
+ `wobbl validate`, which takes about 30 minutes.
273
+
274
+ ## Examples
275
+
276
+ Each folder in [`examples/`](examples) is a real product with its real outputs, the
277
+ reports, heatmaps and summaries, and each has a page [on the website](https://sohan-bhat.github.io/wobbl/examples/).
278
+
279
+ | Example | Question | Domain |
280
+ |---|---|---|
281
+ | [cast-motor-mount](examples/cast-motor-mount) | Does a cast motor mount meet a safety factor of 2 under a 2 kN belt pull? | strength, remote loads |
282
+ | [shelf-bracket](examples/shelf-bracket) | Does a printed PLA bracket hold an 8 kg shelf, and what fixes it? | strength, fixes |
283
+ | [motor-mount](examples/motor-mount) | Do the ribs on a printed NEMA 17 mount earn their keep? | strength |
284
+ | [enclosure](examples/enclosure) | Does a PC enclosure survive a thumb press, and how far does it dent? | strength, thin walls |
285
+ | [drone-arm](examples/drone-arm) | Is a PA12 drone arm strong enough at full throttle? | strength, rigid loads |
286
+ | [heat-sink](examples/heat-sink) | Does a 15 W chip stay under 85 C without a fan? | heat |
287
+ | [battery-pack](examples/battery-pack) | When does a pack in a sealed box pass 60 C? | transient heat |
288
+ | [circuits](examples/circuits) | Will this LED fry? What is the ripple? How hot is the MOSFET? | circuits |
289
+
290
+ Each folder has a `run.sh` that regenerates its outputs.
291
+
292
+ ## Documentation
293
+
294
+ - [Getting started](https://sohan-bhat.github.io/wobbl/get-started/), to install, set up and run a first study
295
+ - [Guides](https://sohan-bhat.github.io/wobbl/guides/) for strength, heat and circuits, the inputs, the results and fixing parts
296
+ - [CLI reference](https://sohan-bhat.github.io/wobbl/guides/cli/) with every command and option
297
+ - [Agents and MCP](https://sohan-bhat.github.io/wobbl/guides/agents/) with what `wobbl setup` registers, the 13 tools and the skill
298
+ - [How it checks itself](https://sohan-bhat.github.io/wobbl/guides/results/checks/), every check and what sets the confidence
299
+ - [Validation](https://sohan-bhat.github.io/wobbl/validation/), the 18 cases and their setups
300
+ - [Bake-off](https://sohan-bhat.github.io/wobbl/validation/bakeoff/), a coding agent on its own versus Wobbl
301
+ - [Contributing](https://sohan-bhat.github.io/wobbl/get-started/contributing/), with the architecture and how a new solver plugs in
302
+ - [Publishing](docs/publishing.md), the release steps for PyPI
303
+ - [Changelog](CHANGELOG.md)
304
+
305
+ ## Limits
306
+
307
+ - Materials are linear elastic, so Wobbl reports whether yield is reached but not
308
+ what happens after, and large deflection is checked with a nonlinear re-solve.
309
+ - Loads are static, and fatigue, impact and vibration are not modeled yet.
310
+ - Contact is limited to rigid walls the part can push on but not pull, and bolted
311
+ joints are modeled as clamped holes without preload.
312
+ - Each study uses one material, and assemblies of touching bodies are bonded.
313
+ - Thin walls are slow, since 2 to 3 mm shells on 100 mm parts need large
314
+ tetrahedral meshes and take several minutes, and shell elements are on the roadmap.
315
+ - Printed plastics are treated as isotropic at 100 percent infill, with a layer
316
+ strength knockdown when you give the print direction.
317
+
318
+ Shell elements, fatigue estimates and bolt preload come next in 0.2, then CFD with
319
+ OpenFOAM in 0.3 and motion with MuJoCo in 0.4.
320
+
321
+ ## Contributing
322
+
323
+ Issues and pull requests are welcome, and this is how to work on Wobbl itself.
324
+
325
+ ```sh
326
+ git clone https://github.com/sohan-bhat/wobbl && cd wobbl
327
+ python -m pip install -e ".[dev]"
328
+ python -m pytest -m "not solver" # fast unit tests
329
+ wobbl setup --no-agents # CalculiX and ngspice
330
+ python -m pytest -m solver # end to end tests with the solvers
331
+ ```
332
+
333
+ A new solver plugs in as one adapter class, as
334
+ [contributing](https://sohan-bhat.github.io/wobbl/get-started/contributing/) explains. If you change the physics, add or update a
335
+ case in `src/wobbl/validation/cases.py` so `wobbl validate` covers it.
336
+
337
+ ## License
338
+
339
+ [MIT](LICENSE), copyright Sohan Bhat.
340
+
341
+ ---
342
+
343
+ <p align="center"><a href="https://sohan-bhat.github.io/wobbl/">sohan-bhat.github.io/wobbl</a></p>