Skip to content

fix(fem): resolve an element surface to the face it names, not every node - #375

Merged
Krande merged 2 commits into
Krande:mainfrom
oleandor:fix/abaqus-surface-face-resolution
Sep 22, 2026
Merged

Krande merged 2 commits into
Krande:mainfrom
oleandor:fix/abaqus-surface-face-resolution

Conversation

@oleandor

Copy link
Copy Markdown
Contributor

surface_nodes returned every node of every element in an element-based surface, ignoring which face the surface actually named. A C3D10 with one triangular face on a plate end contributed all ten of its nodes, dragging in interior nodes and nodes on adjoining faces.

On a 463k-element Abaqus-derived model the three coupling regions came back at roughly twice their true size:

region resolved intended
shell edge 1,473 742
solid plate end face 14,880 7,291
lip underside 3,286 1,619

The Sesam writer then tied about twice as many nodes rigidly through BLDEP as the model intended, silently stiffening the joint. With this change all three resolve to exactly the intended node sets — compared by node id, not merely by count.

The information was already in the deck and in the reader. An Abaqus element surface names its faces (_LIP_UNDERSIDE_S3, S3 for a solid, E1..E4 for a shell edge) and read/reader.py already stored them as el_face_index. Only the resolver ignored them. One finding worth flagging for review: for a multi-row surface the reader leaves fem_set and el_face_index as None and keeps each row's side label in id_refs[i][1], so that is where the side has to be read from.

What is added

Abaqus face-to-node maps including mid-side nodes for TETRA/TETRA10, HEX8/HEX20 and WEDGE/WEDGE15, and shell edge maps for TRI/TRI6/QUAD/QUAD8. SPOS/SNEG name a shell face — the whole element — and are handled explicitly rather than incidentally.

PYRAMID5, PYRAMID13 and HEX27 are deliberately absent: Abaqus has no pyramid continuum element and no published C3D27 face numbering to copy, so there is no authority to follow. A surface naming a face on one of those raises, naming the type and the side, rather than falling back to "every node" — which is the bug this fixes.

A second defect, same area

get_surface_from_nodes_on_solid_elements took an index into the visualisation face table and wrote it as an Abaqus face number. The two agree for tetrahedra and hexahedra by coincidence, but the viz table triangulates a wedge's three quad faces, so it holds six entries for a five-faced element: a wedge could be labelled with the wrong face, or with an S6 that no wedge has. It now matches against the Abaqus table, on corner nodes, so the number written is one the element actually has. elem_has_parallel_face stays for the shell path, which uses it only as a predicate and discards the index.

Validation

  • The four regions on the real model resolve to 742 / 7,291 / 1,619 / 1, node-id-identical to the sets derived independently from the Abaqus geometry.
  • 473 tests pass in tests/core/fem on current main. The 5 failures in that directory are pre-existing: import freetype raises RuntimeError: Freetype library not found in this environment, in image-rendering tests unrelated to this change.
  • black, ruff and isort clean.

Independent of #374, though both came out of the same Sesam export work.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv

…node

`surface_nodes` returned every node of every element in an element-based
surface, ignoring which face the surface actually named. A C3D10 with one
triangular face on a plate end contributed all ten of its nodes, dragging in
interior nodes and nodes on adjoining faces.

On a 463k-element Abaqus-derived model the three coupling regions came back
roughly twice their true size -- shell edge 1473 against 742, solid plate end
face 14880 against 7291, lip underside 3286 against 1619 -- so the Sesam writer
tied about twice as many nodes rigidly through BLDEP as the model intended,
silently stiffening the joint. With this change all three resolve to exactly the
intended node sets, compared by node id and not merely by count.

The information was already there: an Abaqus deck names the face (`_SURF_S3, S3`
for a solid, `E1..E4` for a shell edge) and the reader already stored it as
`el_face_index`. Only the resolver ignored it.

Adds Abaqus face-to-node maps with mid-side nodes for TETRA/TETRA10, HEX8/HEX20
and WEDGE/WEDGE15, and shell edge maps for TRI/TRI6/QUAD/QUAD8. PYRAMID5,
PYRAMID13 and HEX27 are deliberately absent -- Abaqus has no pyramid continuum
element and no published C3D27 face numbering to copy -- and a surface naming a
face on one of those now raises instead of silently returning every node.

Also fixes `get_surface_from_nodes_on_solid_elements`, which took an index into
the *visualisation* face table and wrote it as an Abaqus face number. The two
agree for tetrahedra and hexahedra by coincidence, but the viz table
triangulates a wedge's three quad faces, so it holds six entries for a
five-faced element: a wedge could be labelled with the wrong face, or with an
"S6" no wedge has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nej3bWxJevs9GkH6tg1WKv
@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown

PR Review

👋 I checked your PR and found no issues. Thanks!

  • ✅ PR title is ok
  • ✅ Exactly one release label
  • ✅ SOURCE_KEY secret is set
  • ✅ Calculated next version: "0.81.2"

@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown

🚀 Profiling Results (Top 20 most expensive calls)

Function Calls Duration (s)
<built-in method builtins.exec> (~:0) 2016 16.8485
<module> (<string>:1) 1 16.8485
test_bench_batch_tessellate (tests/profiling/test_cad_backend_bench.py:63) 1 5.1825
run (tests/profiling/test_cad_backend_bench.py:73) 6 4.9950
batch_tessellate (src/ada/visit/tessellate.py:1102) 1446 4.9941
test_build_big_ifc_pipe (tests/profiling/test_ifc_creation.py:48) 1 4.6236
tessellate_geom (src/ada/visit/tessellate.py:830) 1440 4.5344
__truediv__ (src/ada/api/spatial/part.py:2042) 8 4.3622
tessellate_occ_geom (src/ada/visit/tessellate.py:684) 1440 4.1243
tessellate_shape (src/ada/visit/tessellate.py:554) 1440 4.1128
to_ifc (src/ada/api/spatial/assembly.py:292) 5 3.5648
tessellate (src/ada/cad/__init__.py:1163) 1440 3.3394
sync (src/ada/cadit/ifc/store.py:200) 5 3.2712
sync_added_physical_objects (src/ada/cadit/ifc/write/write_ifc.py:92) 5 3.2267
add (src/ada/cadit/ifc/write/write_ifc.py:582) 2200 3.1653
add_object (src/ada/api/spatial/part.py:309) 1201 3.1375
add_pipe (src/ada/api/spatial/part.py:164) 200 3.1231
add_section (src/ada/api/spatial/part.py:294) 801 2.6297
add (src/ada/api/containers/sections.py:139) 805 2.6287
equal_props (src/ada/sections/concept.py:100) 241399 2.3923

@Krande
Krande merged commit b752a72 into Krande:main Sep 22, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants