Skip to main content

ANYfem

A shell and beam element Finite Element Method tool: geometry modelling, meshing, loads and boundary conditions, solving, and postprocessing.

ANYfem owns the application layer across the focused ecosystem packages:

  • ANYsolver — the headless FE solver (6 DOF per node, SI units, linear through nonlinear and transient).
  • ANYmaterial — validated, serializable isotropic and orthotropic material specifications.
  • ANYgeometry — the shared persistent-ID geometry model, curves, surfaces, topology and serialization.
  • ANYmesher — neutral meshes, mapped decomposition and meshing, refinement and coupling records.
  • ANYfileio — SESAM and CalculiX parsing/writing.
  • ANY3dView — shared viewer contracts and the optional ModernGL viewport.
  • ANYtk3D — the software Tk viewport.

The dependency direction is one-way. ANYfem never imports ANYstructure.

Qualified latest-only release graph

Distribution Qualified version Role
ANYmaterial 0.1.0 material definitions and nonlinear curves
ANYgeometry 0.2.2 feature/topology and structural ownership
ANYmesher 0.2.3 declared structural meshing and native quality optimization
ANYfileio 0.2.0 neutral and solver-file semantics
ANY3dView 0.5.0 viewer contract and ModernGL renderer
ANYtk3D 0.5.0 compatible software renderer
ANYsolver 0.3.0 analyses, outcomes, progress, reactions and quantities
ANYfem 0.3.0 workflow, persistence, jobs and results
ANYbuckling 0.1.0 compatible independent buckling adapter
ANYstructure 6.3.0 downstream application consumer

Only these latest package generations are qualified together. Historical ANYfem project formats 1--6 remain readable through the v7 migration path; that file compatibility does not qualify older installed package generations. ANYtimeseries remains independent of this release graph.

Status

The complete application workflow is implemented: model geometry, cut it into mappable pieces, mesh it, apply loads and supports, solve, and inspect the results.

Layer What works today
anyfem.geometry Temporary compatibility imports over ANYgeometry's shared GeometryModel, EntityRef, curves and topology; new code may import the same owner objects directly from anygeometry
anyfem.geometry.operations Compatibility imports: surface evaluation comes from ANYgeometry; historical axis/fraction splits, strips, butterfly decomposition and mappability checks come from ANYmesher
anyfem.mesh Compatibility surface over ANYmesher: edge seeding, Coons mapped meshing, conformal node sharing, refinement, shell/beam topology and couplings
anyfem.model Stable-ID materials/sections/assignments, reusable geometry/mesh regions, named Cartesian/cylindrical coordinates, SI-backed unit profiles, six-component supports/loads, masses, combinations and imperfections
anyfem.solve FEModel construction; linear static, modal, buckling, nonlinear static, arc-length, transient, rigid-sphere impact and the packaged capacity workflow; recovery and resource policy
anyfem.post Displacement and stress fields, probes, along-line extraction, envelopes, deformed shapes, mode and time-step browsing, history series, Markdown and CSV export
anyfem.io v7 project intent, schema-v4 feature geometry, portable SESAM embedding, atomic HDF5 mesh/result sidecars, validation, locks and autosave recovery
anyfem.commands Atomic document transactions and command-stack undo/redo, including feature edit/suppress/regenerate
anyfem.migration Reads ANYstructure's saved FE state without importing it; measures the migration gate
anyfem.selection Geometry and mesh domains; point/edge/plate/node/element/element-face filters; replace/add/toggle/remove and ordered picks
anyfem.ui Persistent Tree + Viewport + Details workspace, commercial mouse profile, contextual selection strip, FIFO jobs and lazy results

Verified, with dated evidence under reports/:

Check Reference Result
Cantilever tip deflection PL³/3EI 0.01%
Plate under pressure Timoshenko 0.00406 q a⁴/D 0.13%
Cantilever natural frequency 1.875² / 2πL² · √(EI/ρA) 0.17%
Euler strut buckling π²EI/L² 0.03%
Suddenly applied load undamped peak = 2 × static 2.04
Plate bending stress 6M/t², M = 0.0479 q a² 0.3%
Beam axial stress P/A exact
Eccentric stiffener neutral axis transformed section 0.20%
Q8 plate on 16 elements converged FE answer 1.07%
Graded element size at a zone the size asked for 3.5%
Quarter model with symmetry the same plate solved in full exact
Result written as FRD and read back the solution it came from exact

Documentation:

Verification and parity

python -m anyfem.verification      # twenty-one cases, dated evidence
python -m anyfem.parity            # ANYstructure capability ledger
python -m anyfem.migration         # the migration gate, all five criteria

All three write reports under reports/, and are installed as anyfem-verify, anyfem-parity and anyfem-gate.

The parity ledger is the gate for replacing ANYstructure's FE application workflow. It tracks 49 capabilities across that GUI's 176 options and reports 92% covered with nothing blocking: every capability is either covered or named in OUT_OF_SCOPE with a reason. A capability counts as covered only when ANYfem can do the same job.

The ledger is one of five migration criteria, and the gate is still closed. Three are met — ledger clear, save_runtime_fem_state files importable, the headless API builds every model type. Two are not, and both need the same thing: a fixed set of models run through ANYstructure with the results recorded, so ANYfem's numbers and timings have something to be compared against. gate_report reports those unmet with the reason rather than passing them by default.

Install and run

Until the compatible ecosystem packages are published, bootstrap the complete latest-only graph with one command. The arguments are kept in dependency order so the same line is also printed by run_gui.py when metadata is stale:

python -m pip install --upgrade -e "C:\Github\ANYmaterial" -e "C:\Github\ANYgeometry[planar]" -e "C:\Github\ANYsolver\.compat_anymesher_023" -e "C:\Github\ANYio[semantics]" -e "C:\Github\ANY3dView[gpu]" -e "C:\Github\ANYtk3D" -e "C:\Github\ANYsolver" -e "C:\Github\ANYfem[gui]"

The launcher uses the sibling ANY3dView and ANYtk3D 0.5 source trees directly, so the application can switch between their coordinated GPU and software implementations without mixing installed generations.

The qualified ANYmesher source is selected the same way. The shared ANYmesh checkout is used only when it declares exactly 0.2.3; otherwise the tracked 0.2.3 snapshot is used. ANYMESHER_023_SOURCE can name another exact 0.2.3 checkout. This preserves unrelated changes in a dirty mesher worktree.

python -m anyfem.ui.app

From a source checkout, python run_gui.py first checks both imported module origins and installed distribution versions, then launches the application. It fails before importing Tk when source and editable metadata are mixed.

The default workspace keeps the model tree on the left, the retained 3D view in the centre, and contextual Details/tasks on the right. Geometry/features, materials/sections, coordinate systems, regions, meshes, load cases, analyses, jobs and results remain visible throughout the workflow. Common controls are shown first; solver-specific controls live under Advanced.

The toolbar's Renderer selector switches live between Automatic, GPU, and Tk. Automatic prefers ModernGL and falls back to Tk with the reason available in the status bar. A failed explicit switch leaves the current renderer, camera, scene, selection, clipping and construction preview intact.

The commercial interaction profile is implemented by both viewer backends and enabled by ANYfem:

  • LMB click selects; LMB drag uses box or lasso selection.
  • MMB drag pans, RMB drag orbits, the wheel zooms, and RMB click opens context actions.
  • No modifier replaces; Shift adds; Ctrl toggles; Alt removes.
  • Left-to-right windows enclose; right-to-left windows cross.
  • Esc cancels, Enter applies, Ctrl+A selects matching entities, F frames selection, and Delete invokes the contextual delete command.

The always-visible selection strip chooses geometry/mesh domain, entity filter, Single/Box/Lasso tool, Visible/Through depth and set operation. Hover prehighlight, repeated-click candidate cycling, geometry/mesh multi-owner picks, named regions, hide/isolate and tree synchronization use the same selection state. Every committed edit is one undo item.

The Loads & BC viewport toggle works in geometry, mesh and result views. Pressure, force, moment, translational/rotational restraint, mass and acceleration each have a directional symbol and color listed in the tab's Viewport key. On dense assignments the display samples at most 256 symbols per category, spread across the complete model; this limits canvas work only and never removes loads or boundary conditions from the analysis.

Modelling paradigm

Bottom-up and point-driven: place points, connect them with lines, bound plates with line loops, and carry beams on lines.

from anyfem import Project, pinned, solve_linear_static, steel

project = Project(name="plate")
project.add_material(steel("S355", thickness=0.010))
project.add_plate_section("deck", thickness=0.010, material="S355")

geometry = project.geometry
points = geometry.add_points([(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)])
edges = geometry.add_polyline(points, close=True)
face = geometry.add_face(edges)

project.assign_plate(face, "deck")
for edge in edges:
    project.add_support(pinned(project.edge(edge)))
project.load_case().add_pressure(project.face(face), 10_000.0)

solution = solve_linear_static(project, target_size=1 / 16)
print(solution.summary())

Curved geometry comes from arcs, and surfaces from sweeping:

import numpy as np

r = 2.0
start = geometry.add_point(r, 0, 0)
via = geometry.add_point(r / np.sqrt(2), r / np.sqrt(2), 0)
end = geometry.add_point(0, r, 0)
arc = geometry.add_arc(start, via, end)

geometry.extrude([arc], (0, 0, 3.0))                     # a cylindrical panel
geometry.revolve([line], (0, 0, 0), (0, 0, 1), 2*np.pi)  # a closed cylinder

The Geometry Details page also exposes ANYgeometry's owner operations through the undoable command stack: translated copy, mirror, linear/circular pattern, edge/face orientation reversal, and typed position/distance/angle/length/area/ perimeter/normal measurements. Structural generators are editable feature records with semantic outputs and groups:

from anyfem import DocumentSession, commands

feature = commands.AddStiffenedPanel(
    length=12.0,
    width=6.0,
    longitudinal_spacing=0.6,
    transverse_spacing=3.0,
    semantic_group="deck",
)
session = DocumentSession(project)
session.execute(feature)  # one atomic undo item

Interactive point, line and polyline construction uses a session-owned Workplane resolved from Global or a named Cartesian/cylindrical coordinate system. The Geometry Details controls expose plane offset, unit-aware grid and snap tolerance, grid/axis snapping, and endpoint/midpoint/projected-edge intersection snapping. LMB collects a working preview, Enter/Apply commits one undo item, and Escape/Cancel leaves the live model unchanged. The same deterministic, Tk-free contracts are available to scripts:

from anyfem import ConstructionTask, SnapEngine, Workplane

workplane = Workplane("global", grid_spacing=0.25, snap_tolerance=0.05)
frame = workplane.resolve(project.coordinate_systems)
snap = SnapEngine().snap((0.49, 0.51, 0.0), workplane, frame)

task = ConstructionTask("line")
task.add(snap)
task.add((1.0, 0.5, 0.0))
task.apply(session.execute)  # no project mutation occurred before this line

There are two intentionally separate circular-opening tools. Neutral trim hole keeps one structural face and adds an inner ANYgeometry boundary; butterfly mesh decomposition replaces the plate with mapped patches for element control. The labels and status messages always identify which one is being applied.

General topology belongs to ANYgeometry; decomposition whose purpose is to produce mapped quadrilateral regions belongs to ANYmesher:

from anygeometry import strip_face
from anymesher.decomposition import punch_circular_hole, triangle_to_quads

# A plate with a hole becomes the four-patch butterfly decomposition.
patches, hole_arcs = punch_circular_hole(geometry, face, (2, 1.5, 0), 0.6)

# Split a plate into strips; the dividing lines are shared, so giving one a
# beam section makes a stiffener on that line.
strips, dividers = strip_face(geometry, face, axis=0, count=3)

# A three-sided region becomes three real quads, never a degenerate one.
faces = triangle_to_quads(geometry, three_edges)

ANYgeometry records the intended surface explicitly: Plane, Cylinder, Cone, RuledSurface, or CoonsSurface. Coons patches provide mapped transfinite interpolation where that is the selected surface; cylinders and cones use their analytical surfaces rather than a faceted approximation.

Project format v7 stores editable intent, exact structural-ownership intent, compatibility-recovery diagnostics, and an artifact index; embedded ANYgeometry documents use canonical schema v4. Meshes and results are immutable, checksummed HDF5 sidecars under model.anyfem-data/meshes and model.anyfem-data/results. Results are read frame-by-frame and unavailable quantities are never manufactured. Imported SESAM source semantics are embedded with the mesh, so reopening does not depend on the original file. Legacy ANYfem formats 1--6 and ANYgeometry schemas v1--v3 remain readable and are migrated deterministically on the next save. The qualified release therefore requires ANYgeometry 0.2.2 or newer within the 0.2 generation; older readers must reject newer schema-v4 materializations rather than guessing at their feature or structural identity.

Symmetry

Half and quarter models, with the condition checked rather than assumed:

project.add_symmetry(project.edge(cut_edge), "x")               # symmetry
project.add_symmetry(project.edge(other), "y", antisymmetric=True)

A symmetry plane restrains the normal translation and the two in-plane rotations, leaving the rotation about the normal free. A quarter plate built this way matches the same plate solved in full to nine figures.

Two things are refused rather than approximated, because both fail silently. A plane not normal to a global axis: the solver applies boundary conditions in global axes with no nodal transformation, so a tilted plane could only be approximated, and a half model with slightly wrong symmetry still solves and still looks reasonable. An entity that does not lie in the plane: a symmetry condition on an edge that crosses the plane restrains the wrong degrees of freedom everywhere it touches.

Element order and local refinement

Both are project settings, saved with the model:

from anyfem.mesh import refine_around, refine_at

project.set_element_order("quadratic")   # Q8 shells and 3-node beams
project.add_refinement(refine_around(project.point(corner), size=0.02, radius=0.1))
project.add_refinement(refine_at((2.0, 1.5, 0.0), size=0.02, radius=0.1))

A zone binds to a point, line or plate and asks for a smaller element size within a radius, growing back to the global target outside it. Seeding integrates the resulting size field along each edge rather than dividing length by target, and node placement follows the same field, so counts and positions cannot disagree.

Q8 is a large accuracy win: 1.07% on a 16-element plate, where Q4 needs 256 elements for the same 2%. Mid-side nodes sit on the curve, so a Q8 on a cylinder stays exactly on the cylinder. The 3-node beam is a different matter and worth being plain about — ANYsolver's 2-node Timoshenko beam is already exact for a tip-loaded cantilever, which a parabola cannot be, so B3 exists here so a stiffener can share the mid-side nodes of a Q8 shell edge, not because it is more accurate. A quadratic beam on a curved line is refused: the solver's B3 is straight-sided.

One limit is inherent to mapped meshing rather than to this implementation. A Coons patch interior is the blend of its four sides, so a zone in the middle of a plate refines nothing — there is no interior degree of freedom to refine. Refining locally means decomposing locally, which is the same answer this mesher gives to every other awkward region:

from anyfem.commands import CommandStack, RefineForImpact

# Cuts the struck plate to bracket the contact patch, then refines it.
CommandStack(project).run(
    RefineForImpact(collision=collision, target_size=0.125, elements_per_radius=4)
)

Every impact result reports info["contact_resolution"] — the element size at the contact point and how many elements lie across the sphere radius — because a contact patch spread over one element gives a peak force that belongs to the mesh rather than to the structure.

Automatic structural connectivity

Meshing works on a temporary geometry clone. Crossing plates are imprinted into conformal shell edges; independently drawn coincident beam ends share a mesh node; a beam ending on or crossing another straight beam splits the receiving span; and a beam meeting a shell away from an existing shell node receives an exact interpolation MPC. The editable model geometry and its IDs are unchanged. The Mesh details report how many plate intersections and beam connections were created. Remesh after changing any of these intersections.

Editable sketches on plates

In Geometry → Guiding geometry, select one flat Model Geometry Plate and choose Start sketch on selected plate. The plate becomes a face-aligned grid. Clicks may be inside or outside the plate; clicks near its vertices or edges receive persistent coincidence constraints. Any two numbered sketch points can receive a distance or coincidence constraint. Apply creates one editable geometry.sketch.extrude feature and extrudes its profile along the plate normal. Right-click the sketch feature in the model tree and choose Edit to reopen its points, constraints, closed/open state, and extrusion distance.

Stiffener eccentricity

A stiffener with its neutral axis in the plate midsurface is a different structure from one standing proud of the plating, so eccentricity is a section property rather than an afterthought:

from anyfem.model import BeamSection

project.add_beam_section(BeamSection(
    name="stiffener", profile="T-bar", material="S355",
    web_height=0.20, web_thickness=0.010,
    flange_width=0.10, flange_thickness=0.012,
    web_direction=(0, 0, 1),
    eccentricity=0.104,          # neutral axis offset along the plate normal
))
project.assign_beam(dividers[0], "stiffener")

With zero eccentricity the beam shares the plating nodes. With an offset the mesher generates its own nodes along the plate normal and ties every station back to the plating with the solver's MPC, so the section picks up its transfer terms. For the verification strip that is a factor of 2.4 in stiffness — not a detail. Supports on the plate edge stay on the plating: a prescribed degree of freedom on a slaved node is a contradiction, and it is avoided rather than discovered at solve time.

Scripting

The GUI is a thin layer over the headless core. Buttons build the same command objects a script does, so anything the application can do is scriptable — and anything scripted is undoable.

from anyfem import Project, steel
from anyfem.commands import AddPlate, AddPoint, AssignPlate, CommandStack

project = Project()
project.add_material(steel("S355", 0.010))
project.add_plate_section("deck", thickness=0.010, material="S355")

stack = CommandStack(project)
points = [stack.run(AddPoint(x, y)) for x, y in ((0, 0), (2, 0), (2, 1), (0, 1))]
face = stack.run(AddPlate(points))
stack.run(AssignPlate(face, "deck"))

stack.undo()          # the plate goes away
stack.redo()          # and comes back with the same IDs

Undo restores IDs exactly, which is what keeps loads and sections pointing at the things the user attached them to.

Loads

Loads attach to geometry and live in named cases, which combine with factors:

from anyfem.model import Mass, plate_mode, prescribed

dead = project.load_case("dead")
dead.add_pressure(project.face(face), 10_000.0)     # follows the plate normal
dead.set_gravity()                                   # consistent inertial load

live = project.load_case("live")
live.add_surface_traction(project.face(face), (500, 0, 0))   # fixed direction
live.set_follower_pressure(True)     # pressures act on the deformed shape

project.add_support(prescribed(project.edge(edge), uz=0.005))  # push, not hold
project.add_mass(Mass(ref=project.face(face), value=1_000.0))
project.add_imperfection(plate_mode(project.face(face), amplitude=0.004))

project.add_combination("ULS", {"dead": 1.2, "live": 1.5})
solution = solve_linear_static(project, target_size=0.25, combination="ULS")

An imperfection moves the stress-free geometry — the shape the structure would have with no load on it — not the result. Follower pressure is a property of a case rather than of one load, because that is how the solver models it; combining a follower case with a dead one is refused rather than quietly resolved.

Analyses

from anyfem import (solve_modal, solve_buckling, solve_nonlinear_static,
                    solve_arc_length, solve_transient, solve_capacity,
                    solve_impact, eigenmode_imperfection, steel)
from anyfem.model import fracture

modal = solve_modal(project, target_size=0.1, num_modes=6)
print(modal.frequencies)                 # Hz, one per mode

buckling = solve_buckling(project, target_size=0.1, num_modes=3)
print(buckling.critical_factor)          # multiplies its reference load case

nonlinear = solve_nonlinear_static(project, target_size=0.1, num_steps=10)
print(nonlinear.history()["load_factor"])

# Material nonlinearity is asked for, not assumed: without a hardening curve a
# nonlinear solve is geometrically nonlinear and elastically linear, which is a
# different analysis. Plasticity is the layered-shell path — beams stay elastic.
project.add_material(steel("S355", 0.008, nonlinear=True))
eroded = solve_nonlinear_static(project, target_size=0.1, num_steps=10,
                                fracture=fracture(0.05))
print(eroded.deleted_elements)           # element erosion, if any triggered

# DNV properties depend on product thickness. This factory gives each
# grade/thickness combination a deterministic material identity, so several
# S355 thicknesses safely coexist and identical specifications are reused.
from anyfem import dnv_steel_material
dnv_10 = project.add_material(dnv_steel_material("S355", 0.010))
dnv_20 = project.add_material(dnv_steel_material("S355", 0.020))
project.add_plate_section("deck 10", 0.010, dnv_10.name)
project.add_plate_section("deck 20", 0.020, dnv_20.name)

# The GUI's Sections page performs this automatically for new plate sections.
# Clear "Auto DNV nonlinear material from thickness" to choose a custom
# ANYmaterial specification instead.

# The same page shows the complete selected material law (elastic constants,
# density, yield, DNV thickness and sampled flow stresses) and a section-usage
# table listing the Model Plates/Lines assigned to every definition. Assigning
# a section commits the displayed definition before applying its scope.

# A buckling-shaped imperfection, then trace past the limit point.
imperfection = eigenmode_imperfection(buckling, 1, amplitude=0.004)
path = solve_arc_length(project, target_size=0.1, imperfection=imperfection)
print(path.peak_load_factor)

# The whole assessment in one call: static, prestress, buckling, imperfection,
# collapse. The solver packages the sequence, so ANYfem does not re-chain it.
# The amplitude is the setting that matters: too small and it is a perfect-shape
# analysis, too large and the return mapping stops converging.
capacity = solve_capacity(project, target_size=0.1, imperfection_amplitude=span / 500)
print(capacity.summary())            # capacity and elastic critical, separately
print(capacity.capacity_ratio)       # <1 imperfection sensitive, >1 post-buckling reserve

transient = solve_transient(project, target_size=0.1, dt=2e-4, t_end=0.02)
print(transient.node_history(transient.peak_node, "uz"))

# A rigid sphere. The contact penalty and time step are computed, not guessed.
from anyfem.model import Collision
impact = solve_impact(project, target_size=0.1, collision=Collision(
    mass=200.0, radius=0.15, start=(0.5, 0.5, 0.6), direction=(0, 0, -1), speed=4.0
))
print(impact.summary(), impact.energy()["absorbed"])

An impact is the one analysis where the settings decide whether the answer means anything. The contact penalty comes from the solver's own recommendation, and the time step resolves the contact period 2π√(m/k) into twenty increments — a step near that period makes the contact iteration fail outright rather than merely lose accuracy. The free-flight approach is skipped (it is exact), and a sphere that would miss the structure is refused rather than run to a clean-looking nothing.

Every result is made of shapes — a deflection, a mode, a time step — and they all share one interface, so anything that can display a static result displays a mode or a time instant unchanged. Long analyses take a progress callable and can be cancelled from the GUI.

Postprocessing

Everything is a field — one object the contour, the probe, the path plot, the envelope and the report all agree on:

from anyfem.post import (evaluate_field, probe, along_line, envelope,
                         report_markdown, write_report, field_to_csv)

stress = evaluate_field(solution, "von_mises")     # per element
print(stress.range(), stress.extreme())

reading = probe(solution, project.face(face))      # every component at once
print(reading.text())

path = along_line(solution, project.edge(edge), "uz")   # distance vs value
print(path.to_csv())

worst = envelope(transient, "von_mises")           # over every time step
print(worst.field.extreme(), worst.worst_shape())

write_report(solution, "report.md")

A displacement lives at nodes and a stress lives at elements; Field populates exactly one of the two rather than pretending otherwise. An element that cannot carry a component — torsion in a shell, membrane in a beam — is left out rather than reported as zero.

A history is the same idea one dimension over: a transient, an impact and an incremental solve all reduce to one Series type, so the plot never asks which analysis produced it.

from anyfem.post import history_series

for curve in history_series(transient):        # peak node by default
    print(curve.name, len(curve), curve.peak())

path = history_series(capacity)[-1]            # load factor vs displacement

The Results panel draws whichever series a result has, on a hand-written Tk canvas — no matplotlib, so the GUI's dependency set stays Tk, the same choice ANYtk3D makes for the 3D viewport.

Result values remain stored in SI. The Display controls can switch contours, probes, summaries and histories instantly between SI (m/Pa) and engineering units (mm/MPa), and offer Cool-warm, Viridis, Plasma, Turbo and Grayscale colour maps with a matching viewport legend.

Retained sidecar fields can be exported directly to deterministic CSV without loading every frame. Persisted node/element association tables supply the IDs; if an artifact has no association, the export says row_index instead of inventing an ID. The visible result view can also be captured as PNG or as an asynchronously assembled GIF when Pillow is installed. Global tables and histories remain CSV-only, so an unavailable spatial view is never replaced by a screenshot of stale geometry. Section-plane controls appear automatically when the active ANY3dView or ANYtk3D backend exposes the shared clipping API.

Immutable result sidecars also produce reproducible Markdown or standalone HTML reports without loading every frame at once:

from anyfem.io import ArtifactStore
from anyfem.post import result_report_context, write_result_report

store = ArtifactStore("deck.anyfem")
dataset = store.open_result(project.artifacts[job.result_artifact_id])
context = result_report_context(dataset, project=project, job=job, stale=False)
write_result_report(dataset, "deck-result.html", context=context)

The report records submission hashes and producer versions, typed quantity descriptors, recovery/reduction/basis provenance, frame-wise extrema, histories, retained-table previews and diagnostics. Requested missing or malformed quantities stop export; they are never printed as zero.

Files

from anyfem.io import (save_project, load_project, import_sesam,
                       export_calculix_deck, import_calculix_results,
                       import_sesam_results)

save_project(project, "deck.anyfem")
project = load_project("deck.anyfem")          # identical IDs, solves the same

model = import_sesam("hull.FEM")               # nodes and elements, no geometry
solution = solve_linear_static(built=model.built(model.load_case()))

export_calculix_deck(built, "deck.inp")

# Solved elsewhere? Read the answers back onto the same model.
results = import_calculix_results("deck.frd")
imported = results.attach(built)               # matched by node ID
print(imported.summary())
stresses = import_sesam_results("hull.SIF")    # RVSTRESS shell stresses

An imported result goes through the same contours, probes, paths and reports as a solved one — but it is not pretending to be one. A CalculiX FRD carries three translation components and no rotations, so imported.component("rx") raises rather than returning a plausible zero, and the raw array holds NaN there so nothing that indexes it directly can mistake the gap either. Stresses arrive per node, already averaged by the writing solver, and stay node-valued rather than being passed off as an element recovery done here. Component names are the file's own. A result file for a different mesh is refused with the overlap rather than attached partially.

A project file stores the model, not its consequences — the mesh and results are regenerable. Entity IDs and their counters are part of the data, because loads reference geometry by ID and a round trip that renumbered anything would silently re-target them.

An imported model has no geometry behind it and says so. Inventing plates and lines under an imported mesh would be a guess dressed as a model; instead the mesh gets the same association an ANYfem mesh has — elements grouped from the file's own properties — which is all the analyses, fields, probes and reports need.

SESAM export is deliberately refused: the solver states that semantic export from an arbitrary model is outside its supported gate, and a file written anyway would look authoritative without being so.

Migrating from ANYstructure's FE GUI

ANYfem is meant to replace fem_integration.py, and anyfem.migration is the part of that which needs code. It reads the old GUI's saved runs — without importing ANYstructure, because a save_runtime_fem_state file is plain or gzipped JSON, so it is read as data:

from anyfem.migration import read_runtime_fem_state, gate_report

state = read_runtime_fem_state("run.anystructure.json.gz")
print(state.summary())
print(state.target_size, state.element_order)   # settings ANYfem can act on
print(state.buckling_factors)                   # what ANYstructure recorded
print(state.unmapped_options)                   # what ANYfem cannot honour yet
print(state.out_of_scope_options)               # and what it never will

Of the 176 options, 144 map onto ANYfem settings, 24 are out of scope by decision and 8 are solver internals ANYfem does not surface. Reporting which is which is the point: a migration that silently dropped a setting would run a different analysis from the one the file asked for, and would look like it worked.

It restores settings and recorded numbers, not the model — the snapshot describes a parametric panel, which is out of scope, and the stored visualisation is a plotting grid rather than a mesh, so there is no topology in the file to rebuild from.

anyfem-gate run.json      # all five migration criteria

The gate is closed. Three criteria are met: the ledger is clear, saved state is importable, and the headless API builds every ANYstructure model type — a stiffened panel and a cylinder, meshed and solved with no Tk loaded. Two are not, and both need a fixed set of models run through ANYstructure with results and timings recorded. The gate reports those unmet with the reason and never passes them by default.

Two properties worth knowing

Attributes bind to geometry, not to the mesh. Loads, supports and sections reference persistent entity IDs; the mesh association map resolves them at build time. Re-meshing never loses a load.

Meshing method is an explicit project setting. Open Mesh and choose the prominent Meshing method control before Generate mesh:

  • Automatic (recommended) maps eligible four-sided plates and routes the remaining plates through ANYmesher's unstructured/native surface mesher.
  • Mapped quadrilateral requires every plate region to have four logical sides and no holes. The task explains which face must be partitioned when this requirement is not met.
  • Unstructured / native sends every plate through the native surface method and is the direct choice for holes and general boundaries.

The lower-level Triangulator control is shown only when an unstructured route can be used; it is not the mapped/native method selector. Shared model edges use one node sequence, so automatic mixed-method interfaces remain conformal without coincident-node merging. After completion, Mesh details show the requested method, the actual method used per face, native backend routes, intersection preparation, and ANYmesher 0.2.3 quality measures including scaled Jacobian, angle range, poor-element count, and optimization provenance.

Testing

python -m pytest tests -q

Real Tk tests are opt-in so an ordinary run never opens application windows. Set ANYFEM_RUN_GUI_TESTS=1 when deliberately exercising the desktop. They still skip rather than fail when no display is available. The current test count belongs in CI output, because extraction work moves tests to their owning repositories.

Ordinary tests cap native numerical libraries at one worker so a development run does not monopolize the workstation. Set ANYFEM_TEST_THREADS to an intentional higher value when needed. The 50,000-owner / 250,000-node scale qualification is not part of the normal regression run; enable it explicitly with ANYFEM_RUN_SCALE_GATES=1, and add ANYFEM_RUN_HARDWARE_GATES=1 only on the representative workstation used for timing acceptance.

License

GPL-3.0-or-later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

anyfem-0.3.0.tar.gz (600.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

anyfem-0.3.0-py3-none-any.whl (445.4 kB view details)

Uploaded Python 3

File details

Details for the file anyfem-0.3.0.tar.gz.

File metadata

  • Download URL: anyfem-0.3.0.tar.gz
  • Upload date:
  • Size: 600.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for anyfem-0.3.0.tar.gz
Algorithm Hash digest
SHA256 7d34319af108dd3d1b00d384e7b8ae491b152db6b7f11d971da708a24082e23d
MD5 ca23d5a7b8a3fb33259374bfee632517
BLAKE2b-256 760cb400da2ca453249a4d170daf198f6d3cbaf5e4e37e781a0dc6288cbfcb0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for anyfem-0.3.0.tar.gz:

Publisher: publish.yml on audunarn/ANYfem

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file anyfem-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: anyfem-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 445.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for anyfem-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3696fc3350412e93978b0a3261e5875bceda34f61538aef7688cbe77fe67c94
MD5 81c06cb80bf7706bf10b820d50bd0096
BLAKE2b-256 d72caa725b70e39e104bd667a5604dc29c1150db31342f0df48b172356f07833

See more details on using hashes here.

Provenance

The following attestation bundles were made for anyfem-0.3.0-py3-none-any.whl:

Publisher: publish.yml on audunarn/ANYfem

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page