FERS — structural FEM

com.ferscloud由 ferscloud.com 發布官方發布者v2.83.0更新於 Nov 7, 2026

Structural FEM for AI agents: frames, plates, buckling, seismic and Eurocode checks, solved locally.

已驗證Streamable HTTP可網頁執行檔案與儲存開發者工具資料與分析

概覽

AI 產生的概覽

FERS 是有限元素結構工程求解器,讓助理建立、驗證並求解梁、構架、桁架與板模型,並進行歐標檢核。

可以做什麼
  • 建立與編輯模型:梁、門型構架、任意節點/桿件圖、斷面、支承、桿端釋放與剪式鉸、板面與開孔
  • 定義載重與分析:載重工況、載重、分項組合、EN 1990 組合集、初始缺陷、二階、模態、挫屈與地震選項
  • 求解與檢核:求解位移、內力與反力,執行統一檢核,或一次呼叫完成鋼梁 EN 1993-1-1 檢核
  • 檢視結果:彙整板結果、產生 HTML 手算報告、查詢用量與版本層級
  • 查詢資料:型鋼目錄、材料預設、模型與檢核 schema、範本模型
  • 管理模型:以代號暫存模型,儲存、載入並列出已儲存的模型
適用情境
當助理需要真實結構數值而非估算時使用:梁柱檢核、構架或樓板分析、撓度、挫屈係數、自然頻率、地震反應,或具可追溯計算過程的歐標統一檢核。
執行需求
可作為遠端端點使用,也可作為本機 npm 套件透過 npx 啟動。遠端端點需要 OAuth 驗證;選用環境變數 FERS_API_KEY 可解鎖 Pro 限額。無金鑰時為免費層級:僅限非商業用途,最多 100 個桿件。使用金鑰時每次求解都會向 ferscloud.com 驗證授權。
安裝前請注意
免費層級僅限非商業用途且上限 100 個桿件,商業或客戶專案需要 Pro 金鑰。使用金鑰時每次求解都會連線 ferscloud.com 進行授權握手。建模工具會修改並回傳模型文件,save_model 會寫入本機 FERS 模型資料夾。結果為工程計算輸出,應由合格工程師複核。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 FERS — structural FEM,將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/com.ferscloud%2Ffers/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "fers-structural-fem": {
      "type": "http",
      "url": "https://ferscloud.com/api/mcp"
    }
  }
}

README

FERS: structural analysis in Python

FERS: structural finite-element analysis (3D frames, trusses, plates, Eurocode 3 checks) for engineers and AI agents. This is its Python SDK and the fers command. The solver is a compiled engine that runs on your machine, with no account and no network call: free for non-commercial use up to 100 members. This SDK is open source (BSD-3-Clause) at github.com/Jeroen124/FERS_core; the engine's licence is under Licence.

It computes linear and second-order (P-Delta) static analysis, natural frequencies, elastic buckling load factors (α_cr), response-spectrum seismic analysis, and EN 1993-1-1 steel member checks, flexural and lateral-torsional buckling included. A model is JSON in and JSON out, written from Python, by the fers command, or by an AI agent through the FERS MCP server, the Claude Code plugin or the agent skill.

bash
pip install FERS
python
from fers_core import check_beam
# IPE200 in S235, simply supported over 6 m, 10 kN/m. SI units: m, N, Pa.# check_beam adds an EN 1993-1-1 member check on a ULS combination of 1.35 x the load.model = check_beam(6.0, "IPE200", udl=10_000.0)model.settings.analysis_options.include_member_deflected_shape = Truemodel.run_analysis()
beam = model.resultsbundle.loadcases["Q"].member_results["1"]x_frac, (_, dy, _) = beam.member_displacement_peakec3 = model.unity_check_results()[0]print(f"M {beam.local_minimums.mz / 1e3:.1f} kNm, sag {dy * 1e3:.1f} mm at x/L {x_frac:.2f}")print(f"EC3 utilization {ec3['max_utilization']:.2f}")
text
M -45.0 kNm, sag -41.8 mm at x/L 0.50EC3 utilization 2.85

The sag is 41.3 mm of bending (5wL⁴/384EI) plus 0.4 mm of shear deformation, which the engine includes. The beam fails: the ULS moment of 60.75 kNm exceeds its bending resistance of 51.9 kNm, and with nothing restraining its compression flange, lateral-torsional buckling takes the utilization to 2.85.

  • Agents and scripts: fers solve, fers check and fers validate take a model JSON file, print one line (or --json) and say in their exit code whether the numbers can be used; see From the terminal and from coding agents. fers skill --install . gives a coding agent the FERS skill, and MCP clients can use the FERS MCP server. In Claude Code, both come as one plugin: /plugin install fers --marketplace Jeroen124/FERS_core.
  • Extras: plots (pip install "FERS[plot]"), section properties meshed from your own dimensions ("FERS[sections]") and DXF import ("FERS[dxf]"); "FERS[all]" installs everything 0.1.x did. Catalogue sections (IPE200, HEB300, UB 457x191x67, W14X30, ...) need no extra.
  • Licence: the SDK is BSD-3-Clause; the engine it installs is free for non-commercial use (Licence).

Solving a model

Building a model by hand, create the FERS object first, then its nodes, members and loads. Creating a FERS restarts the id counters, so a node made before it would share an id with one made after it, and the solver refuses node ids that do not run from 1 without gaps.

Reading results

run_analysis() puts the results on model.resultsbundle. Load cases and load combinations are keyed by name. Within each, nodes and members are keyed by their id as a string, because results round-trip through JSON.

python
case = model.resultsbundle.loadcases["Load"]    # or .loadcombinations["ULS 1"]
case.displacement_nodes["5"].dy                  # dx dy dz rx ry rz warpcase.reaction_nodes["1"].nodal_forces.fy         # fx fy fz mx my mz bwbeam = case.member_results["4"]beam.local_maximums.mz, beam.local_minimums.mz   # envelope along the memberbeam.local_start_forces.fy                       # end forces, member axes

start_node_forces, end_node_forces, maximums and minimums are in the global frame; the local_* forces, section_forces and internal_force_series are in the member's own axes, with mz the strong-axis moment. Read a frame's moments from the local ones: outside the XY plane the global mz is not the bending moment, and in a frame in the YZ plane it reads 0.

Since 0.1.98, member_results and displacement_nodes are read-only tables rather than dicts of objects. That is what keeps a large result small in memory. They read like dicts: [], get, in, len, .items(), and iteration in the solver's order. Each lookup builds a fresh object, and nothing in it can be changed:

python
beam.local_maximums.mz = 0.0                     # AttributeError: read-onlyeditable = case.member_results.copy()            # a plain dict of editable objectseditable["4"].local_maximums.mz = 0.0            # fine

section_forces, internal_force_series and member_displacements come back as tuples. To test for a table, use isinstance(x, collections.abc.Mapping), not dict. reaction_nodes and plate_results are plain dicts, as before.

Saving and loading

python
from fers_core import FERS
model.save_to_json("beam.json")                  # the model and its resultsmodel = FERS.from_json("beam.json")

FERS.from_json reads any file the solver or the SDK wrote, one entry at a time. A typical result needs less memory than its file to load, and at most about twice the file's size; before 0.1.98 it needed 15 to 20 times. For a large model, let the solver write the file itself. The result then never passes through Python as one string:

python
model.run_analysis_to_file("beam_results.json")solved = FERS.from_json("beam_results.json")

fers_core/examples/809_Reading_Results.py runs all of this end to end.

Saving a loaded model writes back everything the solver reads, with the same ids. That includes what the file defines but nothing uses yet, such as a section an editor keeps in its library. Nodes, members and the rest created after loading get ids the loaded model does not use, so saving never lets a new object replace a loaded one.

Only the results you read (engine 0.2.68)

AnalysisOptions(result_filter=[...]) keeps or drops whole result blocks. result_requests goes further. Each ResultRequest names a block and narrows it to chosen members or nodes, load combinations, components and one member end:

python
from fers_core import ResultBlock, ResultRequestfrom fers_core.builders import check_beamfrom fers_core.result_requests import nodes_offrom fers_core.unity_checks import classification
model = check_beam(5.0, "IPE300", udl=10000.0)    # one "Beam", one ULS combinationbeams = classification("Beam")model.settings.analysis_options.result_requests = [    ResultRequest(ResultBlock.LOCAL_ENVELOPES, members=[beams],                  limit_state="ULS", components=["fx", "my", "mz"]),    ResultRequest(ResultBlock.NODE_DISPLACEMENTS, nodes=nodes_of(beams),                  limit_state="ULS", components=["dy"]),    ResultRequest(ResultBlock.REACTIONS, limit_state="ULS"),]model.run_analysis()

The values load into the same member_results, displacement_nodes and reaction_nodes, so code that reads them keeps working. Anything that was not requested reads as None, not zero. A check that reads a value nobody asked for therefore fails instead of passing on a zero.

  • members takes the unity checks' selectors: classification(...), members([...]), member_sets([...]) and all_members(). Several selectors form a union.
  • nodes takes nodes([...]), or nodes_of(*selectors) for the end nodes of the selected members.
  • Load combinations are matched on their limit_state, as unity checks match them, or by load_combination_ids. To select by limit state, set limit_state on each LoadCombination; the solver does not read situation.
  • A request with neither limit_state nor load_combination_ids also covers the load cases.
  • end="start" or end="end" keeps one end of the end-force and end-displacement blocks.
  • The solver refuses a request it cannot meet, before it starts. That includes section_forces, internal_force_series, a component the block does not have, a limit state no combination carries, and result_filter set as well.

The values themselves are on model.resultsbundle.selections, one entry per request and group of result sets. Each entry's values is a read-only numpy array shaped (result_sets, ids, fields, components), with each axis listed beside it:

python
sel = model.resultsbundle.selections[0]sel["group"]                     # "loadcombinations" or "loadcases"sel["result_sets"], sel["ids"]   # combination names, member or node idssel["fields"], sel["components"] # e.g. ["local_maximums", "local_minimums"], ["fx", "my", "mz"]my = sel["values"][..., sel["components"].index("my")]   # every set, member and field

save_to_json writes the selections back as the solver wrote them, so a saved result reloads unchanged. fers_core/examples/810_Result_Requests.py runs this end to end.

Scissor hinges: a continuous beam that turns on what it crosses (engine 0.2.68)

A rail running over beams and resting on each one is continuous along its length, but free to turn on every beam. A MemberHinge releases one member end, so releasing both rail ends at a beam splits the rail into two spans. Put the rail's members in a member set of their own and give the set a ScissorHinge instead. No link member or extra node is needed:

python
from fers_core import FERS, DistributedLoad, Material, Member, MemberSet, NodalSupport, Node, ScissorHinge, Sectionfrom fers_core.supports.supportcondition import SupportCondition as C
model = FERS()steel = Material(name="S355", e_mod=210e9, g_mod=81e9, density=7850.0, yield_stress=355e6)rail_section = Section(name="rail", material=steel, i_y=1e-6, i_z=1e-6, j=1e-7, area=1e-3)beam_section = Section(name="beam", material=steel, i_y=1e-4, i_z=1e-4, j=1e-4, area=5e-3)
turns = {"X": C.fixed(), "Y": C.free(), "Z": C.free()}a, m, b = Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0)a.nodal_support = NodalSupport(rotation_conditions=turns)b.nodal_support = NodalSupport(    displacement_conditions={"X": C.free(), "Y": C.fixed(), "Z": C.fixed()}, rotation_conditions=turns)beam_start, beam_end = Node(1.0, 0.0, -0.5), Node(1.0, 0.0, 0.5)beam_start.nodal_support = beam_end.nodal_support = NodalSupport()  # clamped
rail_1 = Member(start_node=a, end_node=m, section=rail_section)rail_2 = Member(start_node=m, end_node=b, section=rail_section)model.add_member_set(MemberSet(    members=[rail_1, rail_2],    scissor_hinge=ScissorHinge(rotational_release_y=0.0, rotational_release_z=0.0),))model.add_member_set(MemberSet(members=[    Member(start_node=beam_start, end_node=m, section=beam_section),    Member(start_node=m, end_node=beam_end, section=beam_section),]))
span = model.create_load_case(name="span")DistributedLoad(member=rail_1, load_case=span, magnitude=1000.0, end_magnitude=1000.0, direction=(0, -1, 0))model.run_analysis()mz = model.resultsbundle.loadcases["span"].member_results[str(rail_1.id)].local_end_forces.mzprint(f"{abs(mz):.1f} Nm")  # 62.4; a knife edge gives qL²/16 = 62.5, a rigid joint 122.6
  • Where it applies: at every node where a member of the set meets a member outside the set, or a plate. There the set's members stay joined to each other and move relative to the node along and about the released axes. Nothing is released where the set meets only itself or a support. So the continuous beam's members belong in a set of their own; a set holding every member meets nothing, and the solver refuses it.
  • Only some of those nodes: MemberSet(scissor_hinge_nodes=[...]) lists where the hinge applies, for a beam that turns on some of the members it meets and is joined to the rest.
  • Each value works as a MemberHinge release does: None holds it, 0.0 frees it, and a positive value is a spring between the set and the node, in force per length for a translation (translational_release_x/_y/_z) and moment per radian for a rotation (rotational_release_x/_y/_z).
  • The axes are global unless the hinge names its own: ScissorHinge(..., axes=ReleaseAxes.user(x=..., y=...)) takes the first axis along x, the second along y made perpendicular to it, and the third as their cross product, as a rack turned in plan needs.
  • Supports and nodal loads at a released node act on the node, meaning the members outside the set. A node's reported displacement is theirs. The set's own motion there is in its members' local_displacement_start_node and local_displacement_end_node.
  • Two hinged sets crossing with nothing else are each released from the node, so every axis either one releases is free between them.
  • A set of one member gives a release about the hinge's axes at that member's ends.
  • Second order: as with member hinges, a model with scissor hinges takes the corotational correction only when NonlinearMethod.COROTATIONAL is named.

A rail that may also slide over its posts frees that translation. Here it slides over the first post only, so the second takes its share of the pull:

python
from fers_core import (    FERS, DistributedLoad, Material, Member, MemberPointLoad, MemberSet, NodalSupport, Node,    ScissorHinge, Section,)
model = FERS()steel = Material(name="S355", e_mod=210e9, g_mod=81e9, density=7850.0, yield_stress=355e6)frame = Section(name="frame", material=steel, i_y=4e-6, i_z=4e-6, j=1e-7, area=2e-3)anchor, top_1, top_2 = Node(0.0, 0.0, 0.0), Node(2.0, 0.0, 0.0), Node(4.0, 0.0, 0.0)foot_1, foot_2 = Node(2.0, -1.5, 0.0), Node(4.0, -1.5, 0.0)anchor.nodal_support = foot_1.nodal_support = foot_2.nodal_support = NodalSupport()
rail_1 = Member(start_node=anchor, end_node=top_1, section=frame)rail_2 = Member(start_node=top_1, end_node=top_2, section=frame)model.add_member_set(MemberSet(    members=[rail_1, rail_2],    scissor_hinge=ScissorHinge(translational_release_x=0.0),  # slides along X    scissor_hinge_nodes=[top_1],                               # over the first post only))for foot, top in ((foot_1, top_1), (foot_2, top_2)):    model.add_member_set(MemberSet(members=[Member(start_node=foot, end_node=top, section=frame)]))
pull = model.create_load_case(name="pull")DistributedLoad(member=rail_1, load_case=pull, magnitude=2000.0, end_magnitude=2000.0, direction=(0, -1, 0))MemberPointLoad(member=rail_2, load_case=pull, position=0.5, magnitude=1000.0, direction=(1, 0, 0))model.run_analysis()reactions = model.resultsbundle.loadcases["pull"].reaction_nodestakes = [abs(reactions[str(foot.id)].nodal_forces.fx) for foot in (foot_1, foot_2)]print(f"post 1 takes {takes[0]:.1f} N, post 2 {takes[1]:.1f} N")  # 0.0 N and 67.2 N

A model that uses scissor hinges is written with schema_version 3, so a solver older than 0.2.68 refuses it instead of solving every connection as rigid. Without them, a model is written exactly as before. fers_core/examples/054_Scissor_Hinge_Rail_On_Beam.py compares the rigid connection, member hinges, the scissor hinge and a spring, and fers_core/examples/055_Scissor_Hinge_Sliding_Rail_And_Release_Axes.py slides the rail, limits the hinge to one post, and turns the rack in plan.

Member hinges in global or user axes (engine 0.2.68)

A MemberHinge releases its rotations about its member's own axes. A beam at an angle in plan, pinned about the girder it hangs from, needs a pin about the girder's axis instead, which in the beam's own axes mixes its bending with its torsion. rotation_axes names the axes the rotational releases, their stiffness curves and their moment–rotation diagrams use:

python
from fers_core import MemberHinge, ReleaseAxes
pin_about_x = MemberHinge(rotational_release_mx=0.0, rotation_axes=ReleaseAxes.GLOBAL)

ReleaseAxes.LOCAL (the default) keeps the member's axes; ReleaseAxes.user(x, y) names a frame as above. A curve on such a hinge reads the end moment about the same axes; the member's results stay in its local axes. Translational releases stay member-local. A model whose member hinges release in other axes is written with schema_version 3 as well. Release axes are fixed in space, so a corotational solve does not turn them with the structure.

A member's self-weight

The solver works out each member's self-weight, density·area·g, and puts it in the load case that self_weight_load_case_id names. A load combination carries the self-weight only if it includes that case, so give it your permanent-load case: create_load_case("G", self_weight=True) creates G and sets both options.

python
from fers_core.builders import create_beam
model = create_beam(6.0, "IPE300", udl=5_000.0, load_case_name="Q")   # 5 kN/m imposed loadimposed = model.load_cases[0]dead = model.create_load_case("G", self_weight=True)                   # G takes the self-weightmodel.create_load_combination("ULS", {dead: 1.35, imposed: 1.5}, limit_state="ULS")model.run_analysis()
uls = model.resultsbundle.loadcombinations["ULS"]print(round(sum(r.nodal_forces.fy for r in uls.reaction_nodes.values()), 1))   # 48357.4

The ULS reactions are 1.5 × 30 kN plus 1.35 × the beam's 2.49 kN. With enable_self_weight set and no self_weight_load_case_id, the solver puts the self-weight in a load case of its own, "Self-weight", which combinations built from your load cases do not include. run_analysis refuses such a model when it has combinations, rather than solve every one of them without self-weight, and the solver warns with self_weight_not_in_combinations on each combination. A model without combinations solves, and its "Self-weight" case is a result of its own, as below.

The solver also works out each member's mass for modal and seismic analysis from the section. A member that carries more, such as cladding or a services run, can override both:

python
from fers_core.builders import create_beam
model = create_beam(5.0, "IPE180")                  # no load but its own weightmodel.settings.analysis_options.enable_self_weight = Truefor member in model.members:    member.weight_override = 1500.0                 # N/m, gravity includedmodel.run_analysis()
self_weight = model.resultsbundle.loadcases["Self-weight"]print(round(sum(r.nodal_forces.fy for r in self_weight.reaction_nodes.values()), 1))   # 7500.0

The solver applies 1500 N/m in place of the section's 184 N/m, and takes 1500 / 9.81 kg/m as the member's mass. weight_override is in the model's force/length units. Member.weight is a different thing: the member's mass as the SDK computes it, density·area·length, which is never sent to the solver.

Seismic analysis

add_seismic_analysis asks the solver for a modal response-spectrum analysis (the default), the lateral-force method, or both. The spectrum helpers build EN 1998-1 design spectra. Accelerations are in m/s² and periods in seconds, whatever the model's units; nodal masses are in kg.

python
from fers_core import (    FERS, Member, MemberSet, MaterialLibrary, NodalMass, NodalSupport, Node, Section,    SeismicAnalysisSettings,)
model = FERS()                                                    # first: it restarts the idssteel = MaterialLibrary.S235()column = Section.from_name("HEB200", steel)nodes = [Node(0.0, float(height), 0.0) for height in range(7)]    # a 6 m mastnodes[0].nodal_support = NodalSupport()                           # fixed basemodel.add_member_set(MemberSet(members=[Member(a, b, section=column) for a, b in zip(nodes, nodes[1:])]))model.add_nodal_mass(NodalMass(nodes[-1], 2000.0))                # 2 t of equipment on top
model.add_seismic_analysis(    SeismicAnalysisSettings.eurocode_spectrum(ag=2.5, ground_type="C", q=1.5),    directions=["X"],    num_modes=4,)model.run_analysis()
x = model.resultsbundle.seismic["modal_response_spectrum"]["per_direction"][0]print(f"{x['participating_mass_ratio']:.0%} of the mass, base shear {x['base_shear'] / 1000:.1f} kN")

mass_sources=[(load_case, psi)] turns gravity load cases into seismic mass (ψE = 1.0 for permanent loads). direct_spectrum takes S, T_B, T_C and T_D directly for a national annex, and custom_spectrum a table of periods and accelerations. The results are plain dicts in resultsbundle.seismic, one entry per method. fers_core/examples/151_Seismic_Response_Spectrum.py runs a portal frame through both methods.

Unity-check reports

The solver can embed one HTML report of every unity check in the result:

python
from fers_core.builders import check_beam
model = check_beam(5.0, "IPE300", udl=10000.0)model.settings.analysis_options.include_report_html = Truemodel.run_analysis()html = model.unity_report_html()                    # a complete HTML page

render_unity_reports = True also fills each checked member's rendered_report from its check's report_template, and adds those narratives to the HTML report. Both are off by default, which keeps the result small.

Premium solves and timeouts

Without an API key the solver runs at Free-tier limits (100 members) and makes no network call at all. The free tier is for non-commercial use; commercial use needs a Pro key at any model size. Passing api_key raises those limits, to 10,000 members with a Pro key and 1,000,000 with an Enterprise one (granted by agreement):

python
model.run_analysis(api_key=KEY)

A key means each solve first verifies the licence with ferscloud.com, so that handshake is bounded. If the licence server does not answer within license_timeout seconds (default 30) the call raises FersTimeoutError before the solve starts — so retrying is always safe:

python
import fers_calculations
try:    model.run_analysis(api_key=KEY, license_timeout=60)except fers_calculations.FersTimeoutError:    ...   # transient stall — retry or skip this modelexcept RuntimeError:    ...   # definitive — bad key, non-premium account, or a rejected model

Keeping those two arms apart is the point: retrying a RuntimeError fails identically, while collapsing both into except Exception turns a retryable blip into a lost result. See fers_core/examples/203_Premium_Batch_Solve.py for the full unattended-batch pattern.

FersTimeoutError subclasses the built-in TimeoutError, so a plain except TimeoutError catches it too.

Set FERS_LICENSE_TIMEOUT (seconds) to change the default without editing any call sites. It must be a real environment variable — .env files are not read by the engine.

license_timeout bounds the licence handshake, not the solve. A solve is native code with no cancellation point, so no in-process timeout can interrupt it. For a hard ceiling on total wall time per model, run each solve in a killable subprocess — that also reclaims its memory.

Requires engine fers_calculations >= 0.2.53.

From the terminal and from coding agents

pip install FERS puts a fers command on the path: a thin layer over the objects above and the engine, for shells, scripts, CI jobs and coding agents. Every large output goes to a file; stdout carries one line per command, or a JSON object with --json.

bash
fers validate model.json                 # the input schema: the failing field, and keys the engine ignoresfers solve model.json --out results.json # the engine streams the results to the filefers check model.json                    # solve, print each unity check's utilization and a verdictfers report results.json --out report.htmlfers schema                              # the schema's definitions; `fers schema Member` prints onefers sections get IPE200                 # catalogue properties in SI; `sections list` names the seriesfers materials listfers examples list                       # the numbered examples; `examples show 001`, `examples run 001`fers skill --install .                   # the skill for coding agents, into .claude/skillsfers skill --zip fers-skill.zip          # the same skill as a zip to upload to Claude.aifers doctor                              # versions, and whether the key is accepted

fers solve sends the file to the engine as it is, and reads the results back only for its summary line: load cases, combinations, recorded failures, non-converged result sets and messages. A model that writes hundreds of megabytes costs this process nothing.

The exit code says whether to use the numbers:

CodeMeaning
0done; for check, every check passes
1the engine refused or failed the solve
2bad input: a missing file, invalid JSON, a model that fails the schema (with validate --strict, also a key the schema does not know)
3licence: no key where one is needed, a rejected key, a licence timeout
4solved, but do not use the numbers: a result set has errors (a mechanism is singular_model), did not converge, or failures were recorded. The output says why.
5check: nothing decided: a check is Indeterminate, left combinations unsolved, or the model has no checks
6check: a check fails (utilization above 1)

A check that meets several of these exits with the first of 4, 6, 5. With --json, solve and check add a verdict and its reasons. After "DO NOT USE these results" comes the reason: the solver's first error, or the error a load case or combination recorded when it failed outright. fers check names what governs each check, governing member 4 (Column, HEA300) in "ULS 1" (combination 2), and --json carries the same as governing_entity_label and governing_combination_name.

FERS_API_KEY supplies the key for Premium limits when --api-key is not given; without a key the engine runs at Free-tier limits and makes no network call, as above. --solve-token (or FERS_SOLVE_TOKEN) takes a short-lived Premium token from FERS Cloud instead, which the engine checks offline: Premium limits on a machine that cannot reach ferscloud.com. In Python it is run_analysis(solve_token=...).

For an agent, the skill is the working loop on one page: which route to take (MCP tools, a shell or code sandbox, or a file for FERS Studio), validate, solve, check, and the conventions that change answers, with runnable models in reference/api.md. It ships in the package (fers_core/skills/fers/); fers skill --install . copies it to .claude/skills/fers/, where Claude Code looks, fers skill --zip fers-skill.zip packs it for upload to Claude.ai, and fers skill --path names SKILL.md for any other agent. MCP clients can run the same engine as a local MCP server instead (npx -y @ferscloud/fers-mcp, which solves on the user's machine); clients that cannot start a program use the hosted server at https://ferscloud.com/api/mcp, which returns a browser-solve link. Setup for each client: https://ferscloud.com/mcp.

fers_core/examples/811_Command_Line_Workflow.py runs the loop end to end.

Licence

This package, FERS, is open source under the BSD 3-Clause License (LICENSE).

The solver engine it installs, fers_calculations, is licensed separately. From 0.2.70 it is under the PolyForm Strict License 1.0.0: free for any non-commercial purpose, such as study, teaching, research and hobby projects. Commercial use needs a paid FERS Cloud plan, which includes the commercial licence.

來源:README.md,提交 30bd0e3

工具

36

solve_model

Solve a structural model

Run the FERS FEM solver on a structural model. Input is the full model JSON (as a string). Returns displacements, internal forces and reactions. Runs on this machine; nothing is metered. Results are returned at `detail: standard` by default — ask for 'summary' when you only need peak values, and 'full' only when you genuinely need every sample point.

輸入參數modeldetailmodel_refresult_filterinclude_reportidempotency_key

validate_model

Validate a model

Check a FERS model against the solver's own schema WITHOUT solving it — free. Reports field-level errors (missing or wrong-typed fields, invalid enum values), broken id references (a member pointing at a node that does not exist, a load on a deleted member, duplicate ids), and warns about unknown fields under analysis.* — which matter because the engine silently ignores those and gives you a default instead of an error. Always run this before solve_model on a hand-authored model.

輸入參數modelmodel_ref

run_unity_checks

Run code checks

Solve a model and return its code-check (unity-check) results: per-entity utilizations, status, and the calculation trace behind each rule. The model's analysis.unity_checks must already be defined (see add_unity_check). Runs on this machine; nothing is metered. Set include_report=true to also get the rendered HTML hand-calc report.

輸入參數modeldetailmodel_refresult_filterinclude_reportidempotency_key

check_beam

Check a steel beam (EN 1993-1-1)

Check a steel beam to EN 1993-1-1 (Eurocode 3). Builds the beam + a ULS load combination + an EC3 member check over one member, solves it, and returns the governing utilization (<= 1.0 = OK) with the per-rule breakdown: bending, shear, combined N+M, and lateral-torsional buckling, plus a short `advice` string, and `deflection`: the peak deflection under the unfactored (characteristic) load in mm, where it occurs (at_m) and span/deflection. The deflection is reported, not checked against a limit. Requires a named library section: European (IPE/HEA/HEB/HEM/UPE/RHS/SHS/CHS), UK (UB/UC/PFC) or US (W/HP/HSS/Pipe). Accepts common parameter aliases (span/length for span_m, load_kN_per_m/uniform_load for udl). Set restrained=true if the compression flange is continuously laterally restrained (skips the LTB check). Runs on this machine; nothing is metered. Units: m, kN, kN/m.

輸入參數udlspanlengthspan_msection必填supportmaterialpoint_loadrestraineduls_factoruniform_loadload_kN_per_m另有 2 個

create_beam

Build a beam model

Generate a solver-ready FERS model JSON for one beam. Use a named library section (IPE300, HEA200, 'RHS 100x50x5', 'UB 457x191x67', 'W10X22', …) or a solid rectangle. Returns model JSON to pass to solve_model. Accepts common parameter aliases (span/length for span_m, load/udl/uniform_load for load_kn; point_load implies load_type 'point'). Units: metres, kN, kN/m. The model carries two load combinations: 1 'SLS characteristic (auto)' at factor 1.0 and 2 'ULS (auto)' at 1.35, so ULS unity checks evaluate factored loads. Free.

輸入參數udlloadspanlengthspan_mload_knsectionsupportmaterialwidth_mmheight_mmload_type另有 5 個

create_frame

Build a portal frame

Generate a solver-ready FERS model JSON for a 2-D rectangular portal frame (columns + beams). Returns model JSON to pass to solve_model. Units: metres, kN, kN/m. Columns are turned 90° so their depth (column_height_mm) resists sway in the frame plane. Load combinations as create_beam: 1 'SLS characteristic (auto)' at 1.0, 2 'ULS (auto)' at 1.35. Free.

輸入參數basebaysstoreysmateriallateral_knbay_width_mbeam_width_mmbeam_height_mmcolumn_width_mmstorey_height_mcolumn_height_mmroof_udl_kn_per_m

stash_model

Park a model, get a handle

Park a model and get back a short handle (model_ref). Every authoring tool, and solve_model, accepts `model_ref` instead of `model` — so a multi-step build stops shipping the whole document through your context on every call, which on a real model is thousands of tokens per step. Tools called with a handle return the same handle, so the chain stays cheap. Handles are private to you and expire after 24 hours; they are NOT saved models and never appear in list_models (use save_model for that). Free.

輸入參數namemodel必填

set_analysis

Configure the analysis

Configure HOW a model is solved, and switch on the analyses the engine supports beyond plain statics. Writes analysis.options (first- vs second-order, P-Delta, self-weight, gravity, tolerances) and the three optional blocks: analysis.modal (natural frequencies), analysis.buckling (linear eigenvalue alpha_cr) and analysis.seismic (EN 1998-1 response spectrum / lateral force). Pass only what you want to change — each sub-object is merged into the existing block, so load cases, combinations and unity checks are preserved. Returns the updated model for solve_model. KEY FACTS: models from create_beam, create_frame and build_model are LINEAR (the engine itself has no default and requires order; the Python SDK defaults to NONLINEAR), so set order:"NONLINEAR" for second-order/P-Delta. Buckling needs exactly one reference form (reference / references / all_combinations) and reports alpha_cr as results.buckling.modes[].critical_load_factor. Seismic directions are GLOBAL AXES and these models are Y-up, so the two horizontal directions are usually X and Z. Unknown field names are REJECTED here on purpose: the solver would accept them, ignore them, and silently give you a default. Call get_schema("BucklingAnalysisSettings") etc. for the exact fields.

輸入參數clearmodalmodeloptionsseismicbucklingmodel_ref

add_load_case

Add a load case

Add an empty load case — a named bucket of UNFACTORED loads of one origin (dead, snow, wind, imposed). Put loads in it with add_load, then combine cases with partial factors using add_load_combination. A load case on its own never reaches a code check: checks envelope over COMBINATIONS only. Returns the model plus the new load_case_id. Free.

輸入參數name必填modelmodel_ref

add_load

Add a load

Add ONE load of any kind to an existing load case. Choose `kind`: "nodal_force" (force at a node), "nodal_moment" (moment at a node — `direction` is the moment AXIS, right-hand rule), "distributed" (line load along a member; covers uniform, TRAPEZOIDAL/TRIANGULAR via end_magnitude, and PARTIAL via start_frac/end_frac), "member_point_force" / "member_point_moment" (at a fractional position along a member, in global or local axes), "surface" (pressure over a convex planar polygon, distributed onto the coplanar members inside it), "plate_pressure" (pressure on a meshed plate surface). SIGN: direction_x/y/z is a GLOBAL vector — gravity is direction_y = -1. The direction is normalised for you and the whole load stays in `magnitude` (the raw engine multiplies component-wise, so an unnormalised [0,-2,0] would silently double the load). Distributed intensity is per unit of MEMBER length, not of horizontal projection. Units are the model's own (default N, m) EXCEPT plate_pressure, which must always be in SI pascals. Free.

輸入參數axeskind必填nodemodelmemberpolygonend_fracpositionmagnitudemodel_refprojectedstart_frac另有 10 個

add_load_combination

Add a load combination

Add ONE factored load combination — a weighted sum of load cases at a given limit state. This is what the solver designs against: unity checks envelope over COMBINATIONS, never over raw load cases, so a load case no combination references is invisible to every check. ALWAYS set limit_state: it is the only field of a combination the solver reads, and leaving it null makes every SLS deflection check return an empty result that reads as a pass. Free.

輸入參數name必填checkmodelfactors必填model_refsituationlimit_state

generate_load_combinations_ec0

Generate EN 1990 combinations

Generate a complete EN 1990 (Eurocode 0) combination set from the model's load cases in one call, instead of adding them one at a time. Classify each load case as an action type — G permanent, Q imposed, S snow, W wind — and this emits ULS expression 6.10 (each variable action taken as leading in turn, with psi_0 on the accompanying ones) and the SLS characteristic combinations, including the favourable-permanent (gamma_G = 1.0) variants that govern wind uplift. Every generated combination carries a limit_state, so unity checks actually evaluate against them. Any load case you do not classify is guessed from its name. Free.

輸入參數modelactions必填model_refinclude_slsinclude_ulsreplace_existinginclude_favorable

add_imperfection_case

Add a geometric imperfection

Add a geometric imperfection — a global sway (out-of-plumb lean, EN 1993-1-1 §5.3.2) and/or a member bow — bound to specific load combinations. The solver perturbs the geometry before solving those combinations, so it feeds both first-order and P-Delta results. Give the sway as a ratio denominator (200 means a 1/200 lean); it is stored in radians. IMPORTANT: the engine ignores an unknown combination id SILENTLY, so a typo would apply no imperfection at all — this tool rejects unknown ids instead. Free.

輸入參數modelmodel_refsway_axis_xsway_axis_ysway_axis_zheight_direction_xheight_direction_yheight_direction_zsway_magnitude_radtranslation_axis_xtranslation_axis_ytranslation_axis_z另有 4 個

build_model

Build a model from a node/member graph

Build a complete, solver-ready FERS model from an arbitrary node/member graph: 3-D frames, trusses, pitched roofs, bracing, towers — anything create_beam/create_frame cannot express. Returns a full FERS JSON document ready for solve_model. GEOMETRY (1-BASED). `nodes` is a JSON array of [X, Y, Z] triples in METRES, Y up (gravity is −Y). The index you use to refer to a node IS its solver node id: nodes[0] is node 1, so results (displacement_nodes, reaction_nodes) come back under exactly the numbers you wrote. Index 0 is rejected. `members` is a JSON array of [startNode, endNode] / [startNode, endNode, "SectionName"] / [startNode, endNode, "SectionName", {options}]; member ids are 1..N in the order given. Member options: rotation_angle (DEGREES, rolls the section about the member axis), type ("Truss" for a pin-jointed axial-only bar; also Tension/Compression/Cable/Rigid), hinge ("start"|"end"|"both" — releases both bending moments there; at a joint only ONE of the members may be released, because a node whose every member end is released has no rotational stiffness left and the solve then fails outright), classification (free-text group name; one member_set per distinct value, and unity checks can target it). COLUMN ORIENTATION — READ THIS. FERS's strong bending axis is the LOCAL z axis (i_z > i_y for an I-section), and the member local frame is built from a global-Y reference vector. A HORIZONTAL member therefore bends strong-axis under gravity automatically, but a VERTICAL member gets local y = global Z and local z = global X, so it resists sway along global X with its WEAK axis i_y. Verified: a 6 m cantilever column pushed 20 kN in +X deflects 1.1357 m at rotation_angle 0 (= PL³/3E·i_y) and 0.0820 m at rotation_angle 90 (= PL³/3E·i_z) — a 13.8× difference the solver will never warn about. In plane:"XY" this tool sets rotation_angle 90 on vertical bending members for you (reported in info.auto_rotation), because sway there is along X; in "3D" it only warns, since w

輸入參數loadsnodes必填orderplanemembers必填materialsectionssupportsself_weightproject_namedefault_sectionload_combinations

create_section

Add a cross-section

Add or replace a cross-section on an existing FERS model and optionally assign it to members. Takes the model JSON, mutates model.sections (and model.materials when a new preset is introduced), returns the model. THREE WAYS TO DEFINE IT: 1) LIBRARY — shape:"library" with library_name (or just `name`) set to a library profile — European IPE/HEA/HEB/HEM/UPE/RHS/SHS/CHS/L or UK UB/UC/PFC, e.g. "IPE300", "HEB240", "CHS 114.3x5", "RHS 100x50x5", "UB 457x191x67". This is the ONLY way to get exact rolled-profile properties (root fillets included) and it carries every EC3 modulus. Prefer it whenever a standard profile fits. 2) PARAMETRIC — shape:"rect" (h_mm, b_mm), "circle" (d_mm), "chs" (d_mm, t_mm), "rhs"/"shs"/"box" (h_mm, b_mm, t_mm) or "i" (h_mm, b_mm, tf_mm, tw_mm), dimensions in MILLIMETRES. rect/circle/chs are exact and rhs area+inertias are exact (its Bredt torsion J is ~2% low), but the "i" generator assumes SHARP fillets: against a real rolled IPE300 it is 3.6% low on area, 4.3% low on i_z and 22% low on J. Use "i" for WELDED plate girders, not for rolled profiles. 3) CUSTOM — shape:"custom" with your own SI numbers. area, i_y, i_z and j are all MANDATORY and strictly positive; a missing one is a hard parse error and a MISTYPED one is silently ignored (Section has no deny_unknown_fields), giving a wrong answer that still solves. AXIS CONVENTION: local z is the STRONG axis. For depth h and width b, i_z = b·h³/12 (strong, resists gravity on a horizontal member) and i_y = h·b³/12 (weak). Getting these the wrong way round is the most common authoring error and the solver cannot detect it. EC3 CHECKS NEED MORE THAN STIFFNESS. An Ec3Steel check on a section lacking wel_y/wpl_y/i_w does not fail — it returns utilization 0.0, status Green and an explanatory message, which reads as a clean pass. So also give wel_y, wel_z, wpl_y, wpl_z and i_w (warping constant, m⁶); the parametric shapes compute these for you. Optionally add ec3 {"section_class":1,"buckling_curve_y":"

輸入參數jec3i_wi_yi_za_sya_szareab_mmd_mmh_mmname必填另有 13 個

set_support

Set a support condition

Write a nodal support (boundary condition) and point one or more nodes at it: creates or overwrites an entry in model.nodal_supports and sets node.nodal_support on every node you name. Returns the updated model — feed it to solve_model. All 7 DOFs are set independently, in the GLOBAL frame: X/Y/Z (translations), RX/RY/RZ (rotations about the global axes) and warping (the 7th DOF, only live when analysis.options.include_warping is true and the section has i_w). Condition types are case-sensitive: Fixed (rigid), Free (no restraint), Spring (needs the matching <DOF>_stiffness > 0 — N/m for translations, N*m/rad for rotations), PositiveOnly (one-way: may only push in the +global direction, i.e. lift-off) and NegativeOnly (the mirror image). A one-way condition may also carry a <DOF>_stiffness, which from engine 0.2.65 is the bedding stiffness it acts through while in contact — a base plate on a slab is not infinitely stiff. Released, it is free regardless. On 0.2.64 and earlier that number was accepted, range-checked and then silently discarded, so the model solved as rigid contact. THERE IS NO "FREE BY OMISSION". Once a node references a support, any DOF you leave out is treated by the solver as FULLY FIXED — a support of literally {"id":1} is an encastre, and the OpenAPI text claiming an absent field means free is wrong. This tool therefore always writes all six translation/rotation conditions explicitly, and anything you do not name is written as Free. Use `preset` to fill them in one go: fixed (encastre), pinned (X/Y/Z fixed, rotations free), free, roller_x/roller_y/roller_z (ONLY that translation fixed, everything else free), ground_contact_y (X/Z fixed, Y PositiveOnly, rotations free — a base plate that can lift off). Per-DOF fields are applied on top of the preset. Warping is the one exception: leaving it out really does mean free. ONE-WAY SUPPORTS have a prerequisite this tool checks for you: analysis.options.rigid_strategy must be RigidMember (LinearMpc is a

輸入參數XYZRXRYRZmodelpresetwarpingnode_ids必填model_refsupport_id另有 10 個

set_member

Set member behaviour

Set structural behaviour on one or more existing members: element type, end releases (hinges), rigid end offsets, cross-section rotation/mirroring and cable parameters. Mutates model.members (and model.member_hinges when a release is authored) and returns the model. member_type: Normal (full 6-DOF beam), Truss (axial only — carries exactly zero moment), Tension (tie that drops out in compression), Compression (strut that drops out in tension), Rigid (near-rigid link; produces NO entry in results.member_results at all) or Cable (analytic elastic catenary; forces the whole solve onto the nonlinear path and only sags when analysis.options.enable_self_weight is true). Tension/Compression drop-out is governed by analysis.options.axial_slack in FORCE units — a member switches off when its axial force passes 0.5*axial_slack on the wrong side — so this tool lowers axial_slack to 1e-3 when it writes one (set keep_axial_slack to skip). With a coarse axial_slack the member never drops out and quietly behaves like a plain Truss. To build a truss use member_type Truss: two double-hinged Normal members meeting at a free node give a singular stiffness matrix. END RELEASES ARE A TRISTATE and that is the whole point: for each release DOF, null (or simply not naming it) = RIGID, 0.0 = FULLY RELEASED, and a positive number = a spring of that stiffness (force/length for vx/vy/vz, force*length/rad for mx/my/mz/warp). Release DOFs are in the MEMBER LOCAL frame. Verified on an encastre beam under UDL: mz null gives support moment wL^2/12 and midspan wL^2/24; mz 0.0 gives 0 and wL^2/8, i.e. exactly simply supported. Each release object fully defines that end's hinge — DOFs you do not name are written as rigid — and passing "rigid" clears the release. A rotational release is rejected when it would leave a node with NO rotational stiffness at all, because that is a hard "Stiffness matrix is singular or near-singular": either a dead-end node (no other member frames in and the support does n

輸入參數modelmirrormodel_refend_offsetmember_ids必填pretensionend_releasemember_typestart_offsetstart_releasekeep_axial_slackrotation_angle_deg另有 1 個

set_scissor_hinge

Let a continuous beam turn on what it crosses

Let a continuous beam turn or slide on the members it crosses (a scissor hinge, engine 0.2.68), with no link member or extra node: a rail resting on beams, a bracing pair crossing at a bolt. Name the beam by member_ids (or an existing member_set_id) and give the releases. Returns the model. releases is an object over tx, ty, tz (translations) and rx, ry, rz (rotations): 0.0 FREES that axis, a positive number is a spring between the beam and the node (force/length for t*, force*length/rad for r*), and an axis you do not name is HELD — the same tristate as a member hinge. {"rz":0.0} lets a beam in the XY plane turn on what it crosses; {"tx":0.0} lets it slide along X. The hinge goes on a MEMBER SET and applies wherever one of the set's members meets a member OUTSIDE the set (or a plate). Inside the set the members stay joined to each other, so the beam stays continuous. A set holding every member meets nothing and the solver refuses it, so when no set holds exactly these members they leave their sets for a new one; info lists the sets they were taken from. at_nodes limits the hinge to those nodes of the beam (a rail that slides over its first post only); omit it for every crossing. axes: "Global" (the default) or {"User":{"x":[...],"y":[...]}} for a rack turned in plan — the first axis runs along x, the second along y made square to it. "Local" is refused: the set's members need not share one. Supports and nodal loads at a released node act on the members outside the set, and a node's reported displacement is theirs; the beam's own motion there is in its members' local end displacements. The solver refuses a member point load or moment exactly at a released end of the beam. In second order, a model with scissor hinges takes the corotational correction only when analysis.options.nonlinear_method is COROTATIONAL. The model is written with schema_version 3, so an engine older than 0.2.68 refuses it instead of solving every crossing rigid. scissor_hinge_id reuses (and

輸入參數axesclearmodelat_nodesreleasesmodel_refmember_idsmember_set_idscissor_hinge_id

set_member_design

Set buckling / design data

Set the design (code-check) data that lives on a MemberSet — buckling restraints, buckling lengths, LTB length, effective-length factors — plus the per-member chi override. THESE FIELDS CHANGE NOTHING IN THE STATICS. Displacements, reactions and member forces come back bit-identical; the values only feed the EN 1993-1-1 unity-check evaluator. If the model has no Ec3Steel unity check and a matching load combination you will see no effect at all anywhere in the results, so add one with add_unity_check first. A MemberSet is one physical beam: the ordered chain of collinear FE members that make it up. Design lengths resolve per axis with this precedence: explicit buckling_length_* > effective_length_factor_* > buckling_restraints spacing > member length. WATCH THE TWO BASELINES — an explicit length and a restraint spacing are measured along the WHOLE chain, but effective_length_factor_y/z multiplies the length of the INDIVIDUAL FE member. Verified on a 6 m column split into 4 x 1.5 m members: buckling_length = 6.0 reproduces the default exactly, buckling_length = 12.0 quarters N_cr, but effective_length_factor = 2.0 gives L_cr = 3.0 m (2 x 1.5 m), not 12 m. buckling_restraints are named by the member LOCAL axis they hold (restrains_local_y / restrains_local_z / restrains_torsion), not strong/weak — which one is strong depends on the section orientation and rotation_angle. A restraint's node_id must actually lie on one of the set's own members or the whole model fails validation. chi is a per-MEMBER override of the EN 1993-1-1 flexural-buckling reduction factor. It replaces the section-curve chi for BOTH flexural axes (the slenderness stays physical) and is written onto every member of the targeted sets; pass "clear" to remove it.

輸入參數chimodelmodel_refltb_lengthclassificationmember_set_ids必填buckling_length_tbuckling_length_ybuckling_length_zset_classificationbuckling_restraintseffective_length_factor_y另有 1 個

add_plate_surface

Add a plate or slab

Add a meshed plate/shell surface (slab, wall, deck, panel) to a FERS model. You give the corner points; the tool creates the boundary nodes, optionally the full edge-node grid with a support, and appends the PlateSurface to model.plate_surfaces. The SOLVER meshes it at solve time — never author plate_elements for a surface yourself. Load it with add_load kind:"plate_pressure" (surface_id), not with distributed_loads. MESHING DECIDES WHETHER YOUR MODEL IS CORRECT. The mesher only ever creates NODES and PLATE ELEMENTS: it never splits members and never adds supports, so a plate connects to the rest of the structure ONLY through nodes it shares by exact coordinate. (1) Leave the mesh unset and the surface becomes TWO TRIANGLES spanning the corners — it still solves and reactions still equal q x A, so nothing looks wrong; this tool rejects that instead. (2) A supported or member-connected EDGE only exists where a node sits at an exact mesh-grid coordinate: structured meshing has predictable positions so edge_support can pre-create them, unstructured/Delaunay does not, so edge_support with unstructured meshing is rejected. (3) If a beam runs along a plate edge, split that beam into segments ending on the grid, or the plate hangs off its two end nodes only (measured 4.2x too flexible, beam moment 2.5x too low) with no warning. THEORY — THE ENGINE DEFAULT IS WRONG FOR THIN PLATES. PlateTheory "Auto" is resolved unconditionally to Mindlin (the OpenAPI text claiming otherwise is stale). Mindlin (DSG3) shear-locks: on a simply-supported square slab at span/thickness 80 the centre-deflection error was -86.1% / -55.2% / -8.1% / -0.4% at 4x4 / 8x8 / 16x16 / 32x32, where Kirchhoff (DKT, rewritten and patch-test-exact in 0.2.44) gave -5.5% / -1.4% / -0.3% / -0.03%. So: span/thickness >= 20 -> "kirchhoff"; span/thickness < 10 -> "mindlin" plus a fine mesh (it correctly adds the ~10% shear deflection Kirchhoff misses). theory:"auto" (the default) makes that choice from the SMALLER

輸入參數namemodeloffsettheorybehaviormodel_refthicknesssurface_iddivisions_udivisions_vmaterial_idmesh_method另有 12 個

add_plate_opening

Cut an opening in a plate

Cut a hole (stairwell, duct, riser) in an existing PlateSurface: creates the hole's boundary nodes and appends a PlateOpening to that surface's `openings`. The hole ring must be coplanar with, and lie strictly inside, the outer boundary; elements whose centroid falls in the hole are dropped, so the applied load drops with the removed area (verified: a 1x1 m hole in a 4x4 m slab under 10 kPa took the total reaction from 160000 N to exactly 150000 N). HARD CONSTRAINT: openings only work with the Delaunay mesher. The structured mesher rejects them outright ("PlateSurface N Structured meshing does not support openings."), so this tool switches the surface to mesh method "Unstructured" with a target_size and tells you it did. That makes the surface's edge-node positions unpredictable, which is why the tool REFUSES to convert a surface whose structured edge grid is carrying a support: those pre-created edge nodes would be stranded off the new mesh and the plate would silently fall back to hanging on its corners (measured 6x too flexible, with the load still balancing exactly). If you need both an opening and a supported edge, author the surface as unstructured from the start and support it by other means. TARGET SIZE IS A WHOLE-SURFACE SETTING, so a SMALL hole is the expensive case, not the cheap one: left to default (half the shortest hole edge) a 200 mm duct in a 6 m x 6 m slab asks for ~6100 mesh nodes and the engine aborts on that with a raw wasm trap. The default is therefore clamped to what the node budget allows — which means the hole is resolved coarsely and its stress concentration is NOT converged; model the region around the opening as its own smaller surface if you need it sharp. An explicit target_size that busts the budget is refused rather than clamped.

輸入參數modelmodel_refopening_idsurface_idtarget_sizecorner_pointsboundary_node_ids

add_plate_grid_elements

Add an explicit plate mesh

Author plate elements EXPLICITLY (model.plate_elements) as a regular quad grid over four corner points, instead of letting the solver mesh a PlateSurface. Creates the nodes, optionally supports the perimeter, and writes one 4-node PlateElement per cell. Verified to give bit-identical results to the equivalent structured surface mesh (16x16: max|dz| 4.31048e-3 m either way). USE THIS WHEN YOU NEED PLATE UNITY CHECKS. In engine 0.2.52 a unity check targeting plates (AllPlates / PlateSurfaces / PlateElements with a PlateStress variable) returns an EMPTY per_entity for anything meshed from a PlateSurface — status "Green", max_utilization 0, no warning — because the mesh is generated on an internal working copy while the checks run against the original document. Explicit plate elements survive in that document and are checked normally (verified 256/256 entities). ALSO USE IT when you need stable, known plate element ids: mesh-generated ids start at max(explicit id)+1 and are never echoed back, so an element-targeted pressure on a generated id is a silent no-op. COST: verbosity. An N x N grid emits (N+1)^2 nodes and N^2 elements inline, and the engine allocates a dense (7 x nodes)^2 block, so keep total model nodes under ~2000. Load the grid with ONE add_load kind:"surface" whose polygon covers it, rather than N^2 individual pressures. Same theory guidance as add_plate_surface: PlateTheory "Auto" resolves to Mindlin, which shear-locks (-55% at 8x8 on a span/thickness 80 slab), so prefer "kirchhoff" at span/thickness >= 20.

輸入參數modeltheorybehaviormodel_refthicknessdivisions_udivisions_vmaterial_idplane_stateedge_supportcorner_points必填first_element_id另有 1 個

summarize_plate_results

Summarize plate results

Reduce a solved FERS document to the plate numbers an engineer actually reads, and derive extreme-fibre stresses. READ-ONLY: it returns a summary, not a model. You need this because plate results cannot be read back through the model. The mesh is generated on an internal working copy, so the returned document has model.plate_elements = [] and plate_surfaces[].generated_plate_element_ids = [], and the mesh node ids in displacement_nodes have no coordinates anywhere in model.nodes. The only georeferenced plate output is results.<case|combination>.plate_results, keyed by element id, carrying centroid {X,Y,Z}, centroid_displacement_global/local and the stress resultants. UNITS AND CONVENTION, from the engine's own denormalisation: nx, ny, nxy, qx, qy are membrane forces and transverse shears per unit width, in force/length (N/m); mx, my, mxy are moments per unit width, in force (N.m/m, numerically N). mx is the moment PRODUCING sigma_x (conjugate to curvature about local y), not "the moment about x" — on a rectangular slab spanning the short way with local_x along the long side, the large resultant is my. The sign of mx/my/mxy follows the element normal, which follows the boundary/connectivity ordering, so reversing it flips all three; von Mises and |principal| are immune. qx and qy are identically ZERO for Kirchhoff/DKT plates by construction, so non-zero transverse shear is itself proof the Mindlin element was used. results.<case>.summary.total_plate_forces is a COUNT of plate results, not a force. Give `thickness` and the tool derives the extreme-fibre stresses exactly as the engine's own plate unity check does: sigma_x = nx/t +/- 6mx/t^2, sigma_y = ny/t +/- 6my/t^2, tau_xy = nxy/t +/- 6mxy/t^2, evaluated at both fibres with the governing (larger von Mises) one reported, then vonMises = sqrt(sx^2 - sx.sy + sy^2 + 3txy^2) and the in-plane principals. Add `yield_stress` for a utilisation.

輸入參數top_nresults必填thicknessquantitiesyield_stresscase_or_combination

render_report

Render the hand-calc report

Render the human-readable HTML hand-calc report from an ALREADY-SOLVED document (the full JSON you got back from solve_model or run_unity_checks, including its `results`). No re-solve, and free — solve lean with include_report=false, then call this only when you need a report to show someone.

輸入參數solved_model必填

list_sections

List steel sections

List the named steel sections FERS knows: European (IPE, HEA, HEB, HEM, UPE, RHS, SHS, CHS, L), UK (UB, UC, PFC) and US (W, HP, HSS, Pipe). check_beam and create_beam require one of these names, so call this rather than guessing. Returns names only — use get_section for a section's properties. Free.

輸入參數family

get_section

Section properties

Get a named section's full properties in SI units (m, m², m⁴): area, second moments i_y/i_z, torsion constant j, warping constant i_w, shear areas, and the elastic/plastic moduli wel_y/wel_z/wpl_y/wpl_z that an EN 1993-1-1 check needs. Free.

輸入參數name必填

list_materials

List materials

List the named material presets the model builders accept, with their E, G, density and yield stress in SI units. The solver itself accepts ANY isotropic material — see get_schema('Material') to author one inline. Free.

無需輸入參數。

get_schema

Look up the model schema

Look up the FERS model input contract — the authoritative JSON Schema the solver itself is generated from. Call with NO arguments to get an index of the useful roots (geometry, loads, analysis, checks) with a one-line description of each; then call again with a `root` to get that schema plus everything it references. Use this instead of guessing field names: the engine silently ignores unknown keys under analysis.*, so a typo becomes a wrong answer rather than an error. Free.

輸入參數root

get_check_schema

Code-check schema

Get the canonical schema for authoring unity (code) checks: the UnityCheckDefinition schema and everything it references, read straight from the solver's OpenAPI, plus a worked EN 1993 example. Call this before add_unity_check. Free.

無需輸入參數。

add_unity_check

Add a code check

Append a unity (code) check to a model's analysis.unity_checks. `check` is a UnityCheckDefinition JSON (see get_check_schema). Returns the updated model JSON to pass to run_unity_checks. Free.

輸入參數check必填modelmodel_ref

save_model

Save a model to your account

Save a model to this machine's FERS model folder. Solver results are never stored. Returns the model id.

輸入參數name必填modelmodel_refdescription

load_model

Load a saved model

Load one of your saved models' full JSON by id (owner only).

輸入參數id必填

list_models

List saved models

List your saved models (id, name, description, last-updated time).

無需輸入參數。

list_templates

List starter models

List all available template structural models. Returns name and description for each template.

無需輸入參數。

get_template

Get a starter model

Retrieve the full JSON of a template structural model by name. Use list_templates first to see available names.

輸入參數name必填

get_usage

Check usage

Check your FERS tier and how solves run on this server: member limits, licence state and where results come from.

無需輸入參數。

版本歷史

7
  1. v2.83.0最新Nov 4, 2026
  2. v2.82.0Nov 3, 2026
  3. v2.80.0Nov 3, 2026
  4. v2.79.0Nov 1, 2026
  5. v2.77.0Oct 30, 2026
  6. v2.76.0Oct 29, 2026
  7. v2.1.0Oct 21, 2026

評分

已滿足適用檢查項的 100%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分