demo video: a horizontal cantilever, checked against beam theory

The demo used the corpus I-beam, which is extruded along z and so stood
upright. A new corpus part, cantilever-i-beam, is the same section on the
YZ plane extruded along x, web upright: a sweep along a horizontal axis
for the meshers, and a cantilever that bends downward on screen. The
i-beam part is unchanged (its corpus row and the meshing tests keep it).

The demo clamps it at x = 0 and loads the free end with 1000 N in -z
through a kinematic coupling, and now checks the solution against beam
theory and stops when it is off: tip deflection (bending plus shear)
within 3 %, root bending stress (stress_xx along the top flange,
extrapolated to the root, nodes from the Abaqus deck) within 2 %.

docs/meshing/corpus.md regenerated with the new part.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 00:15:50 -04:00
1 parent 025c826dd4
commit 357de3f7a5
5 files changed
+121 -40

No files matched your search

+25 -23
View File
@@ -10,40 +10,41 @@ clock on the machine that ran it; every other column is deterministic.
| part | categories | bodies | faces | triangles | tol. mm | build ms | enclosed mm³ | SolidCraft mm³ | rel. diff | exact mm³ | boundary | genus | feature edges | check ms | CDT tets | Steiner | cavities | tet gate | CDT ms | hex | hex elements | hex8 min SJ | hex20 min SJ | hex ms |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bent-tube | sweepable, thin-wall, genus | 1 | 18 | 4296 | 0.121 | 454 | 4213.1 | 4159.4 | 1.3e-2 | 4159.8 | ok, 2148 vertices | 1 | 268 | 11.5 | 15550 | 2338 | 0 | pass | 606 | sweep | 192 | 0.859 | 0.997 | 63 |
| bolted-assembly | assembly, blocky | 3 | 22 | 284 | 0.117 | 206 | 33004.7 | 32999.0 | 1.7e-4 | 32998.9 | ok, 144 vertices | 0, 1, 1 | 104 | 0.7 | 690 | 181 | 0 | pass | 34 | extrude | 357 | 0.398 | 0.222 | 40 |
| cross-cylinders | boolean, sharp | 1 | 10 | 1395 | 0.087 | 7920 | 32197.4 | 32357.8 | -5.0e-3 | 32365.8 | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | |
| filleted-bracket | fillet, genus, sweepable | 1 | 13 | 540 | 0.088 | 388 | 32162.3 | 32145.0 | 5.4e-4 | 32144.8 | ok, 268 vertices | 2 | 135 | 1.4 | 5582 | 1613 | 0 | pass | 208 | no: Bracket: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 1: linking face 6 has 2 boundary loops (periodic or holed linking faces are not swept); from face 6: | | | | 5 |
| i-beam | sweepable, thin-wall | 1 | 14 | 44 | 0.208 | 3 | 61600.0 | 61600.0 | 0.0e0 | 61600.0 | ok, 24 vertices | 0 | 36 | 0.1 | 94 | 24 | 0 | pass | 4 | extrude | 336 | 0.707 | 0.707 | 34 |
| loft-transition | organic | 1 | 6 | 292 | 0.075 | 57 | 56390.2 | 56450.6 | -1.1e-3 | – | ok, 148 vertices | 0 | 40 | 0.7 | 1329 | 256 | 1 | pass | 38 | sweep | 2873 | 0.189 | 0.189 | 328 |
| perforated-plate | genus, thin-wall, sweepable | 1 | 54 | 1644 | 0.144 | 273 | 26822.2 | 26764.6 | 2.2e-3 | 26764.2 | ok, 776 vertices | 24 | 828 | 4.8 | 33611 | 9798 | 0 | pass | 1345 | extrude | 467 | 0.304 | 0.033 | 98 |
| pinhole-block | small-feature, genus | 1 | 38 | 4044 | 0.073 | 201 | 49919.4 | 49909.6 | 2.0e-4 | 49909.5 | ok, 1992 vertices | 16 | 1548 | 12.9 | 96411 | 30221 | 0 | pass | 8142 | extrude | 5236 | 0.408 | 0.392 | 735 |
| rounded-box | fillet | 1 | 26 | 2540 | 0.054 | 235 | 23325.7 | 23340.5 | -6.3e-4 | 23340.8 | ok, 1272 vertices | 0 | 216 | 8.4 | 11663 | 1916 | 0 | pass | 469 | no: Box: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 0: 12 faces are not on the sweep path; from face 5: 12 faces are not on the sweep path; revolve: plan | | | | 15 |
| shallow-groove | near-tangent, boolean | 1 | 8 | 62 | 0.075 | 11 | 47795.2 | 47782.3 | 2.7e-4 | 47781.5 | ok, 33 vertices | 0 | 24 | 0.2 | 267 | 87 | 0 | pass | 13 | extrude | 1960 | 0.991 | 0.986 | 201 |
| sharp-wedge | sharp, thin-wall, sweepable | 1 | 5 | 8 | 0.083 | 2 | 3200.0 | 3200.0 | 0.0e0 | 3200.0 | ok, 6 vertices | 0 | 9 | 0.0 | 11 | 3 | 0 | pass | 0 | extrude | 294 | 0.050 | 0.050 | 30 |
| sphere-on-plate | near-tangent, boolean | 1 | 12 | 2826 | 0.098 | 974 | 69353.3 | 69509.0 | -2.2e-3 | 69509.7 | ok, 1415 vertices | 0 | 124 | 7.2 | 4898 | 309 | 9 | pass | 260 | no: Plate: not supported yet: no structured hex method fits this body (extrude: face 6 is curved across the direction; sweep: from face 0: 6 faces are not on the sweep path; from face 5: linking face 11 has 2 boundary loops (periodic or holed linking | | | | 11 |
| spline-vase | organic | 1 | 11 | 3890 | 0.106 | 226 | 94891.2 | 94948.5 | -6.0e-4 | – | ok, 1947 vertices | 0 | 243 | 9.0 | 12461 | 1512 | 0 | pass | 456 | sweep | 4554 | 0.649 | 0.649 | 547 |
| stepped-block | blocky, sharp | 1 | 14 | 96 | 0.078 | 264 | 47000.0 | 47000.0 | 0.0e0 | 47000.0 | ok, 48 vertices | 1 | 60 | 0.4 | 223 | 41 | 0 | pass | 7 | extrude | 1416 | 0.999 | 0.999 | 142 |
| thin-ribs | thin-wall, blocky | 1 | 26 | 92 | 0.119 | 1004 | 32000.0 | 32000.0 | -4.5e-16 | 32000.0 | ok, 48 vertices | 0 | 72 | 0.6 | 171 | 33 | 0 | pass | 8 | extrude | 777 | 0.844 | 0.844 | 100 |
| thin-wall-enclosure | thin-wall, blocky | 1 | 11 | 36 | 0.108 | 340 | 15564.0 | 15564.0 | 0.0e0 | 15564.0 | ok, 20 vertices | 0 | 28 | 0.2 | 191 | 47 | 2 | pass | 11 | extrude | 894 | 0.297 | 0.297 | 112 |
| analytic-sphere | analytic | 1 | 6 | 2784 | 0.069 | 719 | 33353.4 | 33507.9 | -4.6e-3 | 33510.3 | ok, 1394 vertices | 0 | 96 | 12.6 | 4211 | 58 | 0 | pass | 440 | no: Sphere: not supported yet: no structured hex method fits this body (extrude: no direction to try; sweep: no planar face to sweep from; revolve: no axis to try; mapped: meshing failed: meshing failed: mesh validity gate: 8 inverted or degenerate ele | | | | 31 |
| analytic-torus | analytic, genus | 1 | 6 | 7230 | 0.109 | 944 | 37644.1 | 37893.7 | -6.6e-3 | 37899.3 | ok, 3615 vertices | 1 | 180 | 25.6 | 19790 | 466 | 0 | pass | 1412 | revolve | 1134 | 0.706 | 0.706 | 280 |
| analytic-cylinder | analytic, sweepable | 1 | 4 | 1004 | 0.049 | 190 | 12533.1 | 12564.7 | -2.5e-3 | 12566.4 | ok, 504 vertices | 0 | 106 | 3.4 | 7437 | 1568 | 0 | pass | 452 | extrude | 3200 | 0.707 | 0.707 | 585 |
| analytic-pipe | analytic, genus, sweepable | 1 | 6 | 1448 | 0.104 | 336 | 94150.9 | 94238.0 | -9.3e-4 | 94247.8 | ok, 724 vertices | 1 | 220 | 9.0 | 6238 | 1340 | 0 | pass | 479 | extrude | 1288 | 0.998 | 0.998 | 290 |
| analytic-plate-hole | analytic, genus, sweepable | 1 | 8 | 112 | 0.224 | 231 | 98449.1 | 98429.5 | 2.0e-4 | 98429.2 | ok, 56 vertices | 1 | 62 | 0.9 | 1868 | 543 | 0 | pass | 72 | extrude | 321 | 0.320 | 0.142 | 55 |
| bent-tube | sweepable, thin-wall, genus | 1 | 18 | 4296 | 0.121 | 2726 | 4213.1 | 4159.4 | 1.3e-2 | 4159.8 | ok, 2148 vertices | 1 | 268 | 19.8 | 15550 | 2338 | 0 | pass | 4070 | sweep | 192 | 0.859 | 0.997 | 535 |
| bolted-assembly | assembly, blocky | 3 | 22 | 284 | 0.117 | 1141 | 33004.7 | 32999.0 | 1.7e-4 | 32998.9 | ok, 144 vertices | 0, 1, 1 | 104 | 1.6 | 690 | 181 | 0 | pass | 211 | extrude | 357 | 0.398 | 0.222 | 139 |
| cantilever-i-beam | sweepable, thin-wall | 1 | 14 | 44 | 0.208 | 483 | 61600.0 | 61600.0 | 0.0e0 | 61600.0 | ok, 24 vertices | 0 | 36 | 0.2 | 90 | 24 | 0 | pass | 5 | extrude | 336 | 0.707 | 0.707 | 47 |
| cross-cylinders | boolean, sharp | 1 | 10 | 1395 | 0.087 | 59348 | 32197.4 | 32357.8 | -5.0e-3 | 32365.8 | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | | error: Cross: invalid boundary surface: 28 defects; triangles 142 and 184 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 184 and 239 are oriented inconsistently across edge 0–554 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); edge 103–108 at (-0.16007784063084018, 0.03984236936862052, 9.998639270596865) is shared by 3 triangles; … | | | | |
| filleted-bracket | fillet, genus, sweepable | 1 | 13 | 540 | 0.088 | 3706 | 32162.3 | 32145.0 | 5.4e-4 | 32144.8 | ok, 268 vertices | 2 | 135 | 2.1 | 5582 | 1613 | 0 | pass | 1769 | no: Bracket: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 1: linking face 6 has 2 boundary loops (periodic or holed linking faces are not swept); from face 6: | | | | 10 |
| i-beam | sweepable, thin-wall | 1 | 14 | 44 | 0.208 | 280 | 61600.0 | 61600.0 | 0.0e0 | 61600.0 | ok, 24 vertices | 0 | 36 | 0.3 | 94 | 24 | 0 | pass | 7 | extrude | 336 | 0.707 | 0.707 | 52 |
| loft-transition | organic | 1 | 6 | 292 | 0.075 | 397 | 56390.2 | 56450.6 | -1.1e-3 | – | ok, 148 vertices | 0 | 40 | 1.0 | 1329 | 256 | 1 | pass | 172 | sweep | 2873 | 0.189 | 0.189 | 2056 |
| perforated-plate | genus, thin-wall, sweepable | 1 | 54 | 1644 | 0.144 | 2864 | 26822.2 | 26764.6 | 2.2e-3 | 26764.2 | ok, 776 vertices | 24 | 828 | 10.6 | 33611 | 9798 | 0 | pass | 6513 | extrude | 467 | 0.304 | 0.033 | 480 |
| pinhole-block | small-feature, genus | 1 | 38 | 4044 | 0.073 | 2053 | 49919.4 | 49909.6 | 2.0e-4 | 49909.5 | ok, 1992 vertices | 16 | 1548 | 41.8 | 96411 | 30221 | 0 | pass | 28180 | extrude | 5236 | 0.408 | 0.392 | 3838 |
| rounded-box | fillet | 1 | 26 | 2540 | 0.054 | 1069 | 23325.7 | 23340.5 | -6.3e-4 | 23340.8 | ok, 1272 vertices | 0 | 216 | 11.5 | 11653 | 1914 | 0 | pass | 2265 | no: Box: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 0: 12 faces are not on the sweep path; from face 5: 12 faces are not on the sweep path; revolve: plan | | | | 67 |
| shallow-groove | near-tangent, boolean | 1 | 8 | 62 | 0.075 | 218 | 47795.2 | 47782.3 | 2.7e-4 | 47781.5 | ok, 33 vertices | 0 | 24 | 0.2 | 267 | 87 | 0 | pass | 24 | extrude | 1960 | 0.991 | 0.986 | 1078 |
| sharp-wedge | sharp, thin-wall, sweepable | 1 | 5 | 8 | 0.083 | 72 | 3200.0 | 3200.0 | 0.0e0 | 3200.0 | ok, 6 vertices | 0 | 9 | 0.0 | 11 | 3 | 0 | pass | 1 | extrude | 294 | 0.050 | 0.050 | 87 |
| sphere-on-plate | near-tangent, boolean | 1 | 12 | 2826 | 0.098 | 3775 | 69353.3 | 69509.0 | -2.2e-3 | 69509.7 | ok, 1415 vertices | 0 | 124 | 8.3 | 4898 | 309 | 9 | pass | 1265 | no: Plate: not supported yet: no structured hex method fits this body (extrude: face 6 is curved across the direction; sweep: from face 0: 6 faces are not on the sweep path; from face 5: linking face 11 has 2 boundary loops (periodic or holed linking | | | | 75 |
| spline-vase | organic | 1 | 11 | 3890 | 0.106 | 1097 | 94891.2 | 94948.5 | -6.0e-4 | – | ok, 1947 vertices | 0 | 243 | 23.5 | 12461 | 1512 | 0 | pass | 2487 | sweep | 4554 | 0.649 | 0.649 | 3030 |
| stepped-block | blocky, sharp | 1 | 14 | 96 | 0.078 | 438 | 47000.0 | 47000.0 | 0.0e0 | 47000.0 | ok, 48 vertices | 1 | 60 | 0.6 | 223 | 41 | 0 | pass | 11 | extrude | 1416 | 0.999 | 0.999 | 906 |
| thin-ribs | thin-wall, blocky | 1 | 26 | 92 | 0.119 | 1085 | 32000.0 | 32000.0 | -4.5e-16 | 32000.0 | ok, 48 vertices | 0 | 72 | 0.7 | 171 | 33 | 0 | pass | 10 | extrude | 777 | 0.844 | 0.844 | 236 |
| thin-wall-enclosure | thin-wall, blocky | 1 | 11 | 36 | 0.108 | 189 | 15564.0 | 15564.0 | 0.0e0 | 15564.0 | ok, 20 vertices | 0 | 28 | 0.2 | 191 | 47 | 2 | pass | 8 | extrude | 894 | 0.297 | 0.297 | 349 |
| analytic-sphere | analytic | 1 | 6 | 2784 | 0.069 | 1340 | 33353.4 | 33507.9 | -4.6e-3 | 33510.3 | ok, 1394 vertices | 0 | 96 | 8.9 | 4211 | 58 | 0 | pass | 1430 | no: Sphere: not supported yet: no structured hex method fits this body (extrude: no direction to try; sweep: no planar face to sweep from; revolve: no axis to try; mapped: meshing failed: meshing failed: mesh validity gate: 8 inverted or degenerate ele | | | | 159 |
| analytic-torus | analytic, genus | 1 | 6 | 7230 | 0.109 | 1725 | 37644.1 | 37893.7 | -6.6e-3 | 37899.3 | ok, 3615 vertices | 1 | 180 | 31.7 | 19790 | 466 | 0 | pass | 4946 | revolve | 1134 | 0.706 | 0.706 | 1155 |
| analytic-cylinder | analytic, sweepable | 1 | 4 | 1004 | 0.049 | 368 | 12533.1 | 12564.7 | -2.5e-3 | 12566.4 | ok, 504 vertices | 0 | 106 | 9.3 | 7437 | 1568 | 0 | pass | 1797 | extrude | 3200 | 0.707 | 0.707 | 2316 |
| analytic-pipe | analytic, genus, sweepable | 1 | 6 | 1448 | 0.104 | 506 | 94150.9 | 94238.0 | -9.3e-4 | 94247.8 | ok, 724 vertices | 1 | 220 | 9.5 | 6238 | 1340 | 0 | pass | 1734 | extrude | 1288 | 0.998 | 0.998 | 1059 |
| analytic-plate-hole | analytic, genus, sweepable | 1 | 8 | 112 | 0.224 | 524 | 98449.1 | 98429.5 | 2.0e-4 | 98429.2 | ok, 56 vertices | 1 | 62 | 0.6 | 1868 | 543 | 0 | pass | 78 | extrude | 321 | 0.320 | 0.142 | 248 |
## Categories
| category | stresses | parts |
|---|---|---|
| thin-wall | walls thin against the part's size | 6 |
| thin-wall | walls thin against the part's size | 7 |
| small-feature | features much smaller than the part | 1 |
| fillet | blends between faces | 2 |
| genus | through holes (genus > 0) | 7 |
| sharp | sharp edges and acute dihedral angles | 3 |
| near-tangent | faces meeting at a shallow angle | 2 |
| boolean | intersection curves from booleans | 3 |
| sweepable | extrusions and sweeps (one-to-one sweep meshing) | 8 |
| sweepable | extrusions and sweeps (one-to-one sweep meshing) | 9 |
| blocky | axis-aligned blocks (polycube-friendly) | 4 |
| organic | free-form, lofted or revolved spline surfaces | 2 |
| assembly | several bodies placed by component transforms | 1 |
@@ -53,6 +54,7 @@ clock on the machine that ran it; every other column is deterministic.
- `bent-tube`: A 10 mm tube with a 1 mm wall, bent through R30: a thin-walled sweep along a line-arc-line path.
- `bolted-assembly`: A base plate with a clamp block component placed twice, once turned 90 degrees: bodies placed by component transforms.
- `cantilever-i-beam`: The 200 mm I-beam of `i-beam` lying along X with its web upright (Z up): a sweep along a horizontal axis, and the cantilever of the README demo.
- `cross-cylinders`: Two orthogonal R10 cylinders joined: equal radii, so the intersection curves cross at two singular points.
- `filleted-bracket`: An 8 mm L-bracket with an R6 inside fillet and a hole through each leg: a blend beside holes.
- `i-beam`: A 200 mm I-beam with 3 mm flanges and a 2 mm web: a long sweepable prism with thin walls.
+1
View File
@@ -0,0 +1 @@
The 200 mm I-beam of `i-beam` lying along X with its web upright (Z up): a sweep along a horizontal axis, and the cantilever of the README demo.
@@ -0,0 +1,7 @@
{"corpus": {"categories": ["sweepable", "thin-wall"], "volume_mm3": 61600},
"commands": [
{"command": "sketch.create", "params": {"plane": "YZ", "name": "Section"}},
{"command": "sketch.line", "params": {"points": [[-20, 0], [20, 0], [20, 3], [1, 3], [1, 37], [20, 37], [20, 40], [-20, 40], [-20, 37], [-1, 37], [-1, 3], [-20, 3]], "closed": true}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"distance": 200, "body_name": "Beam"}}
]}
+14 -7
View File
@@ -17,20 +17,27 @@ It honours `CARGO_TARGET_DIR`.
What it does:
1. Builds `fieldcraft`, `fieldcraft-cli` and the testkit's `corpus` tool (release).
2. Writes the corpus I-beam (`tests/corpus/i-beam`) as a `.solidcraft` design to a temporary
folder with `corpus --part i-beam --design`.
2. Writes the corpus cantilever I-beam (`tests/corpus/cantilever-i-beam`: 40 x 40 mm, 3 mm
flanges, a 2 mm web upright, 200 mm long along x) as a `.solidcraft` design to a temporary
folder with `corpus --part cantilever-i-beam --design`.
3. Starts the app with `--control PORT` and `fieldcraft-cli mcp --connect 127.0.0.1:PORT`, and
speaks MCP (JSON-RPC over stdio) to it. Every step is an MCP tool call: `open` the design,
`mesh` (`hex_sweep`, 4 mm), `batch` (steel, a solid section, the root clamp, a kinematic
coupling of the free end to a reference point loaded with 1000 N), `check_model`, `solve`
coupling of the free end at x = 200 mm to a reference point loaded with 1000 N down, −z), `check_model`, `solve`
(`static`), and `show_results` for the von Mises contours and the deformed shape, its `scale`
stepped from 1 to 150 and back. Every picture is the MCP `screenshot` tool with
`source: window` (the whole window drawn offscreen, `ui.capture`), turned to the `iso` or
`right` view by its `view` argument.
`front` view by its `view` argument.
4. Encodes the pictures at 10 frames per second, each held for its stage, into the MP4 (H.264,
1280 px) and the GIF (960 px, its own palette), and prints the tip deflection and the peak
von Mises stress.
The theme is the app's default (dark). Beam theory for the same beam gives a tip deflection of
0.143 mm in bending plus about 0.034 mm in shear (0.177 mm) and a root bending stress of 45 MPa;
the clamped root adds a local peak above that.
The theme is the app's default (dark).
The script checks the solution against beam theory and stops when it is off. Tip deflection:
P L³ / 3 E I = 0.1429 mm in bending plus P L / G A_web = 0.0335 mm in shear, 0.1763 mm; the
solution gives 0.1764 mm (within 3 %). Root bending stress: P L c / I = 45.0 MPa
(I = 88 871 mm⁴); the solution's stress_xx on the top flange, fitted along the beam between one
depth from either end and extrapolated to the root, gives 44.8 MPa (within 2 %). At the root
itself the clamp, which stops the section's Poisson contraction, adds a local peak (62 MPa
stress_xx, 56 MPa von Mises) that beam theory does not have.
+74 -10
View File
@@ -22,13 +22,20 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
# The analysis: the corpus I-beam (tests/corpus/i-beam, 40 x 40 mm, 3 mm flanges, 2 mm web,
# 200 mm long along z) clamped at z = 0 with 1000 N down (-y) on its free end.
PART = "i-beam"
# The analysis: a cantilever I-beam lying along x (tests/corpus/cantilever-i-beam: 40 x 40 mm,
# 3 mm flanges, 2 mm web upright, 200 mm long), clamped at x = 0 with 1000 N down (-z) on its free
# end, so it bends downward on screen (z is up).
PART = "cantilever-i-beam"
BODY = "Beam"
ROOT_FACE = "F2:start"
TIP_FACE = "F2:top"
LOAD_N = 1000.0
LENGTH_MM = 200.0
DEPTH_MM = 40.0 # overall; the flanges are 40 wide and 3 thick, the web 2 thick
FLANGE_MM = 3.0
WEB_MM = 2.0
YOUNG_MPA = 210000.0
POISSON = 0.3
SIZE_MM = 4.0
STEP = "Load"
@@ -159,13 +166,69 @@ class Film:
return str(folder / "%05d.png"), n
def hand_calculation():
"""Beam theory for the cantilever: tip deflection in bending (P L^3 / 3 E I) plus shear
(P L / G A_web, the web carrying the shear) and the bending stress at the root (P L c / I)."""
b, h, tf, tw = 40.0, DEPTH_MM, FLANGE_MM, WEB_MM
inertia = (b * h**3 - (b - tw) * (h - 2 * tf) ** 3) / 12.0
shear_modulus = YOUNG_MPA / (2.0 * (1.0 + POISSON))
bending = LOAD_N * LENGTH_MM**3 / (3.0 * YOUNG_MPA * inertia)
shear = LOAD_N * LENGTH_MM / (shear_modulus * (h - tf) * tw)
return {"bending_mm": bending, "shear_mm": shear, "tip_mm": bending + shear, "root_stress_mpa": LOAD_N * LENGTH_MM * (h / 2) / inertia}
def root_bending_stress(mcp, work):
"""The bending stress at the root from the solution: stress_xx along the top flange's upper
face, averaged across it at each station, fitted by a line between one beam depth from either
end (where beam theory holds) and extrapolated to x = 0. At the root itself the clamp, which
stops the section's Poisson contraction, adds a local peak beam theory does not have.
The node coordinates come from the Abaqus deck (`write_deck`); the stresses from `probe`."""
deck = work / "ibeam.inp"
mcp.call("write_deck", {"format": "abaqus", "path": str(deck)})
stations = {}
in_nodes = False
for line in deck.read_text().splitlines():
if line.startswith("*"):
in_nodes = line.upper().split(",")[0].strip() == "*NODE"
continue
fields = [f.strip() for f in line.split(",")]
if in_nodes and len(fields) >= 4:
label, x, z = int(fields[0]), float(fields[1]), float(fields[3])
if abs(z - DEPTH_MM) < 1e-6 and DEPTH_MM <= x <= LENGTH_MM - DEPTH_MM:
stations.setdefault(x, []).append(label)
if len(stations) < 2:
raise SystemExit(f"root stress: {len(stations)} stations on the top flange in {deck}")
probed = mcp.call("probe", {"step": STEP, "nodes": [n for nodes in stations.values() for n in nodes]})
sxx = {r["node"]: r["stress"][0] for r in probed["nodes"]}
xs = sorted(stations)
ys = [sum(sxx[n] for n in stations[x]) / len(stations[x]) for x in xs]
mx, my = sum(xs) / len(xs), sum(ys) / len(ys)
slope = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / sum((x - mx) ** 2 for x in xs)
return my - slope * mx
def against_beam_theory(result):
"""The solution against beam theory: tip deflection within 3 %, root stress within 2 %."""
hand = hand_calculation()
result["hand_calculation"] = hand
for got, want, tol, what in (
(result["tip_deflection_mm"], hand["tip_mm"], 0.03, "tip deflection (mm)"),
(result["root_stress_mpa"], hand["root_stress_mpa"], 0.02, "root bending stress (MPa)"),
):
log(f"{what}: {got:.4f}, beam theory {want:.4f} ({(got - want) / want:+.1%})")
if abs(got - want) > tol * want:
raise SystemExit(f"{what} {got:.4f} is more than {tol:.0%} from beam theory's {want:.4f}")
return result
def batch(mcp, commands):
reply = mcp.call("batch", {"commands": commands})
if not reply.get("ok"):
raise SystemExit(f"batch failed: {reply.get('failed')}")
def analyse(mcp, film, design):
def analyse(mcp, film, design, work):
"""The analysis, every step an MCP tool call, with the pictures a viewer follows."""
# 1. Open the part.
mcp.call("open", {"path": str(design)})
@@ -181,7 +244,7 @@ def analyse(mcp, film, design):
batch(mcp, [
{"command": "model.units", "params": {"system": "mm-t-s-N-MPa"}},
{"command": "model.set.create", "params": {"name": "beam", "geometry": [{"body": BODY}]}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 210000, "poisson": 0.3}, "density": 7.85e-9}},
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": YOUNG_MPA, "poisson": POISSON}, "density": 7.85e-9}},
{"command": "model.section.create", "params": {"name": "solid", "set": "beam", "material": "Steel", "kind": {"type": "solid"}, "formulation": {"incompatible_modes": True}}},
])
film.shoot(HOLD_SHORT)
@@ -194,9 +257,9 @@ def analyse(mcp, film, design):
# The end section stays plane: a kinematic coupling to a point on the beam's axis carries
# the load, so no load-introduction peak hides the root stress.
{"command": "model.set.create", "params": {"name": "tip", "geometry": [{"body": BODY, "face": TIP_FACE}]}},
{"command": "model.reference_point.create", "params": {"name": "end", "xyz": [0, 20, 200]}},
{"command": "model.reference_point.create", "params": {"name": "end", "xyz": [LENGTH_MM, 0, DEPTH_MM / 2]}},
{"command": "model.coupling.create", "params": {"name": "end_plate", "reference_point": "end", "set": "tip", "kind": "kinematic"}},
{"command": "model.load.create", "params": {"name": "tip_load", "type": "force", "target": {"reference_point": "end"}, "vector": [0, -LOAD_N, 0]}},
{"command": "model.load.create", "params": {"name": "tip_load", "type": "force", "target": {"reference_point": "end"}, "vector": [0, 0, -LOAD_N]}},
{"command": "model.step.create", "params": {"name": STEP, "procedure": {"type": "static"}, "boundary_conditions": ["clamp"], "loads": ["tip_load"]}},
])
check = mcp.call("check_model")
@@ -211,6 +274,7 @@ def analyse(mcp, film, design):
# 5. Results: the tip deflection, then von Mises contours on the true (scale 1) shape.
disp = mcp.call("show_results", {"step": STEP, "field": "displacement"})
tip = disp["contour"]["range"]["max"]["value"]
root_stress = root_bending_stress(mcp, work)
vm = mcp.call("show_results", {"step": STEP, "field": "von_mises", "bands": 12, "deformed": True, "scale": 1})
peak = vm["contour"]["range"]["max"]["value"]
log(f"tip deflection {tip:.4f} mm, peak von Mises {peak:.1f} MPa")
@@ -219,12 +283,12 @@ def analyse(mcp, film, design):
# 6. The deformed shape, swung from the true shape to an exaggeration and back, in the iso
# view and then from the side, ending held at the exaggeration.
ramp = [max(1.0, MAX_SCALE * i / SWING) for i in range(SWING + 1)]
for view, scales in (("iso", ramp[1:] + ramp[-2::-1]), ("right", ramp[1:])):
for view, scales in (("iso", ramp[1:] + ramp[-2::-1]), ("front", ramp[1:])):
for i, scale in enumerate(scales):
mcp.call("show_results", {"step": STEP, "deformed": True, "scale": scale})
film.shoot(1, view=view if i == 0 else None)
film.shoot(HOLD_LONG)
return {"tip_deflection_mm": tip, "peak_von_mises_mpa": peak, "elements": mesh["mesh"]["elements"], "nodes": mesh["mesh"]["nodes"]}
return against_beam_theory({"tip_deflection_mm": tip, "root_stress_mpa": root_stress, "peak_von_mises_mpa": peak, "elements": mesh["mesh"]["elements"], "nodes": mesh["mesh"]["nodes"]})
def encode(film, frames_dir, mp4, gif):
@@ -272,7 +336,7 @@ def main():
wait_for_port(port, app)
mcp = Mcp(bin_dir / "fieldcraft-cli", port)
film = Film(mcp, frames_dir)
result = analyse(mcp, film, design)
result = analyse(mcp, film, design, work)
encode(film, frames_dir, args.out / "ibeam-analysis.mp4", args.out / "ibeam-analysis.gif")
print(json.dumps(result, indent=2))
finally: