关于 FERS — structural FEM
FERS 是面向工程师和 AI Agent 的 Python SDK、命令行工具及本地编译求解器,用于结构有限元分析。它支持 3D 框架、桁架、板件和 Eurocode 3 验算,并提供 Python 与 JSON 模型及结果工作流。
主要功能
- 013D 框架、桁架和板件分析
- 02Eurocode 3 构件验算
- 03命令行求解、检查与验证
- 04Python 模型和结果 API
- 05JSON 模型与结果存储
- 06可选结果请求
适用场景
- 01在本地分析结构模型
- 02按 Eurocode 3 验算梁承载力
- 03从终端运行模型验证
- 04保存并重新加载分析结果
MCP 相关
FERS 提供 MCP 服务,供 MCP 客户端调用结构分析能力。README 还介绍了面向编码 Agent 的 FERS skill。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://ferscloud.com/api/mcpclaude mcp add --transport http com-ferscloud-fers https://ferscloud.com/api/mcp{"mcpServers":{"com-ferscloud-fers":{"type":"http","url":"https://ferscloud.com/api/mcp"}}}在 Cursor 中打开{"servers":{"com-ferscloud-fers":{"type":"http","url":"https://ferscloud.com/api/mcp"}}}在 VS Code 中打开{"mcpServers":{"com-ferscloud-fers":{"type":"http","url":"https://ferscloud.com/api/mcp"}}}{"mcpServers":{"com-ferscloud-fers":{"serverUrl":"https://ferscloud.com/api/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
FERS:使用 Python 进行结构分析
FERS:面向工程师和 AI Agent 的结构有限元分析(3D 框架、桁架、板、Eurocode 3 验算)。这是它的 Python SDK 和 fers 命令。求解器是一个在你的机器上运行的编译引擎,无需账户,也不会发起网络请求:非商业用途免费,最多支持 100 个构件。
pip install FERS
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 = True
model.run_analysis()
beam = model.resultsbundle.loadcases["Q"].member_results["1"]
x_frac, (_, dy, _) = beam.member_displacement_peak
ec3 = 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}")
M -45.0 kNm, sag -41.8 mm at x/L 0.50
EC3 utilization 2.85
挠度由 41.3 mm 的弯曲挠度(5wL⁴/384EI)和 0.4 mm 的剪切变形组成,后者也由引擎计算。此梁未通过验算:60.75 kNm 的 ULS 弯矩超过其 51.9 kNm 的抗弯承载力;由于没有构件约束其受压翼缘,侧扭屈曲使利用率升至 2.85。
- Agent 和脚本:
fers solve、fers check和fers validate接受模型 JSON 文件,打印一行结果(或使用--json),并通过退出代码说明这些数值是否可用;请参阅从终端和编码 Agent 调用。fers skill --install .可向编码 Agent 提供 FERS 技能,MCP 客户端可使用 FERS MCP 服务。在 Claude Code 中,两者集成在同一个插件中:/plugin install fers --marketplace Jeroen124/FERS_core。 - 附加功能: 绘图(
pip install "FERS[plot]")、根据你自己的尺寸网格划分生成截面属性("FERS[sections]")和 DXF 导入("FERS[dxf]");"FERS[all]"会安装 0.1.x 中的全部内容。目录中的截面(IPE200、HEB300、UB 457x191x67、W14X30等)无需安装附加组件。 - 许可证: SDK 采用 BSD-3-Clause 许可证;它安装的引擎可免费用于非商业用途(许可证)。
求解模型
手动构建模型时,先创建 FERS 对象,再创建节点、构件和荷载。创建 FERS 会重置 ID 计数器,因此,在它之前创建的节点会与之后创建的节点共享 ID;求解器也会拒绝不从 1 开始且存在间断的节点 ID。
读取结果
run_analysis() 会将结果存入 model.resultsbundle。荷载工况和荷载组合以名称为键。其中的节点和构件以字符串形式的 ID 为键,因为结果会经过 JSON 往返转换。
case = model.resultsbundle.loadcases["Load"] # or .loadcombinations["ULS 1"]
case.displacement_nodes["5"].dy # dx dy dz rx ry rz warp
case.reaction_nodes["1"].nodal_forces.fy # fx fy fz mx my mz bw
beam = case.member_results["4"]
beam.local_maximums.mz, beam.local_minimums.mz # envelope along the member
beam.local_start_forces.fy # end forces, member axes
start_node_forces、end_node_forces、maximums 和 minimums 使用全局坐标系;local_* 力、section_forces 和 internal_force_series 使用构件自身的坐标轴,其中 mz 为强轴弯矩。读取框架的弯矩时,应使用局部坐标系中的值:在 XY 平面以外,全局 mz 并非弯矩;而在 YZ 平面内的框架中,它的值为 0。
从 0.1.98 起,member_results 和 displacement_nodes 是只读表,而不再是由对象组成的字典。这样可以减少大型结果占用的内存。它们的用法类似字典:[]、get、in、len、.items(),并按求解器的顺序迭代。每次查找都会创建一个新对象,且对象内容不可更改:
beam.local_maximums.mz = 0.0 # AttributeError: read-only
editable = case.member_results.copy() # a plain dict of editable objects
editable["4"].local_maximums.mz = 0.0 # fine
section_forces、internal_force_series 和 member_displacements 返回的是元组。要检查某对象是否为表,请使用 isinstance(x, collections.abc.Mapping),而不是 dict。reaction_nodes 和 plate_results 仍是普通字典。
保存和加载
from fers_core import FERS
model.save_to_json("beam.json") # the model and its results
model = FERS.from_json("beam.json")
FERS.from_json 会逐条读取求解器或 SDK 写入的任何文件。加载典型结果所需的内存比文件本身还少,最多约为文件大小的两倍;在 0.1.98 之前,则需要文件大小的 15 到 20 倍。对于大型模型,请让求解器自行写入文件。这样,结果就不会以一个完整字符串的形式经过 Python:
model.run_analysis_to_file("beam_results.json")
solved = FERS.from_json("beam_results.json")
fers_core/examples/809_Reading_Results.py 会端到端运行上述全部流程。
保存已加载的模型时,会写回求解器读取的所有内容,并保留相同的 ID。这包括文件中定义但尚未使用的内容,例如编辑器保留在其库中的截面。加载后创建的节点、构件及其他对象会使用该模型未占用的 ID,因此保存时不会让新对象替换已加载的对象。
仅读取所需结果(engine 0.2.68)
AnalysisOptions(result_filter=[...]) 会保留或丢弃整个结果块。
result_requests 更进一步。每个 ResultRequest 会指定一个结果块,并将其范围缩小到所选成员或节点、荷载组合、分量以及一个构件端:
from fers_core import ResultBlock, ResultRequest
from fers_core.builders import check_beam
from fers_core.result_requests import nodes_of
from fers_core.unity_checks import classification
model = check_beam(5.0, "IPE300", udl=10000.0) # one "Beam", one ULS combination
beams = 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()
这些值会载入相同的 member_results、displacement_nodes 和
reaction_nodes,因此读取这些值的代码仍可正常工作。**任何未请求的值读取时都是
None,而不是零。**因此,读取未请求值的检查会失败,而不会因读到零而通过。
members接受统一性检查中的选择器:classification(...)、members([...])、member_sets([...])和all_members()。多个选择器会合并为一个并集。nodes接受nodes([...]),或者使用nodes_of(*selectors)获取所选成员的端节点。- 荷载组合会按照统一性检查匹配组合时使用的
limit_state进行匹配,也可以通过load_combination_ids匹配。若要按极限状态选择,请为每个LoadCombination设置limit_state;求解器不会读取situation。 - 未设置
limit_state或load_combination_ids的请求也会涵盖荷载工况。 end="start"或end="end"会仅保留端部力和端部位移块中的一端。- 求解器会在启动前拒绝无法满足的请求。这包括
section_forces、internal_force_series、结果块中不存在的分量、任何组合都未包含的极限状态,以及同时设置了result_filter的情况。
各个值位于 model.resultsbundle.selections 中,每个请求和结果集组对应一个条目。每个条目的
values 都是只读 numpy 数组,形状为 (result_sets, ids, fields, components),各轴的说明列在旁边:
sel = model.resultsbundle.selections[0]
sel["group"] # "loadcombinations" or "loadcases"
sel["result_sets"], sel["ids"] # combination names, member or node ids
sel["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 会按求解器写入时的形式保存这些选择,因此重新载入后结果保持不变。
fers_core/examples/810_Result_Requests.py 会端到端运行此流程。
剪刀铰:在其跨越的位置转动的连续梁(engine 0.2.68)
一根横跨梁并支承在每根梁上的导轨,沿长度方向连续,但可以在每根梁上自由转动。MemberHinge 会释放一个构件端,因此在某根梁处释放导轨的两端,就会把导轨分成两个跨度。将导轨的构件放入单独的构件集中,并为该集合设置 ScissorHinge。无需连接构件或额外节点:
from fers_core import FERS, DistributedLoad, Material, Member, MemberSet, NodalSupport, Node, ScissorHinge, Section
from 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.mz
print(f"{abs(mz):.1f} Nm") # 62.4; a knife edge gives qL²/16 = 62.5, a rigid joint 122.6
- **适用位置:**在该集合中的构件与集合外的构件或板相交的每个节点处。此时,集合中的构件彼此保持连接,并沿释放的轴移动、绕释放的轴转动。集合仅与自身或支座相交的位置不会释放。因此,连续梁的构件应放入单独的集合;如果一个集合包含所有构件,它就不会与任何对象相交,求解器会拒绝该模型。
- 仅限其中部分节点:
MemberSet(scissor_hinge_nodes=[...])用于列出铰接生效的位置,适用于导轨在与某些构件相交处转动、而在其余位置连接固定的情况。 - 各项数值的作用方式与
MemberHinge的释放相同:None表示保持,0.0表示自由,正值表示集合与节点之间的弹簧;对于平移(translational_release_x/_y/_z),单位为力/长度;对于转动(rotational_release_x/_y/_z),单位为力矩/弧度。 - 除非铰链指定自己的轴,否则轴均为全局轴:
ScissorHinge(..., axes=ReleaseAxes.user(x=..., y=...))将第一轴沿x,第二轴沿与其垂直的y,第三轴则为两者的叉积,适用于在平面内转向的桁架。 - 释放节点处的支座和节点荷载作用于节点,也就是说,作用于集合外的构件。节点报告的位移就是这些构件的位移。集合自身在该处的运动,记录在其构件的
local_displacement_start_node和local_displacement_end_node中。 - 两个铰接集合相交且没有其他对象时,每个集合都会与节点释放,因此任一集合释放的轴在它们之间都是自由的。
- 仅含一个构件的集合会在该构件的两端绕铰链轴释放。
- **二阶分析:**与构件铰链一样,只有指定
NonlinearMethod.COROTATIONAL时,包含剪刀铰的模型才会进行共转修正。
也可以让导轨在立柱上滑动,从而释放该方向的平移。此处导轨只在第一根立柱上滑动,因此第二根立柱会承受相应的拉力:
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_nodes
takes = [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
使用剪刀铰的模型会以 schema_version 3 写入,因此低于 0.2.68 的求解器会拒绝该模型,而不会将每个连接都作为刚接来求解。不使用剪刀铰时,模型的写入方式与之前完全相同。
fers_core/examples/054_Scissor_Hinge_Rail_On_Beam.py 对比了刚接、构件铰链、剪刀铰和弹簧;fers_core/examples/055_Scissor_Hinge_Sliding_Rail_And_Release_Axes.py 则让导轨滑动,将铰链限制在一根立柱上,并使桁架在平面内转向。
全局轴或用户轴下的构件铰链(engine 0.2.68)
MemberHinge 会释放绕构件自身轴的转动。对于平面内倾斜、并通过铰接悬挂在主梁上的横梁,需要绕主梁的轴设置铰链;该轴在横梁自身的轴系中会同时包含弯曲和扭转。rotation_axes 用于指定旋转释放、其刚度曲线以及力矩–转角图所采用的轴:
from fers_core import MemberHinge, ReleaseAxes
pin_about_x = MemberHinge(rotational_release_mx=0.0, rotation_axes=ReleaseAxes.GLOBAL)
ReleaseAxes.LOCAL(默认值)保留构件轴;ReleaseAxes.user(x, y) 则像上文一样指定一个坐标系。此类铰链上的曲线读取同一轴系下的端弯矩;构件的结果仍使用其局部轴系。平移释放仍使用构件局部轴系。构件铰链在其他轴系下释放的模型同样会以 schema_version 3 写入。释放轴在空间中固定,因此共转求解不会让这些轴随结构转动。
构件自重
求解器会计算每个构件的自重,即密度·面积·g,并将其放入 self_weight_load_case_id 指定的荷载工况中。只有包含该荷载工况的荷载组合才会计入自重,因此请将其指定为永久荷载工况:create_load_case("G", self_weight=True) 会创建 G,并设置这两个选项。
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 load
imposed = model.load_cases[0]
dead = model.create_load_case("G", self_weight=True) # G takes the self-weight
model.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
ULS 反力为 1.5 × 30 kN 加上梁自重 2.49 kN 的 1.35 倍。如果设置了 enable_self_weight,但没有设置 self_weight_load_case_id,求解器会将自重放入单独的荷载工况“Self-weight”中,而由你的荷载工况构建的荷载组合不会包含该工况。对于包含荷载组合的模型,run_analysis 会拒绝求解,而不是在不计自重的情况下逐一求解所有组合;求解器还会针对每个组合发出 self_weight_not_in_combinations 警告。没有荷载组合的模型可以求解,其“Self-weight”工况会作为单独的结果,如下所示。
求解器还会根据截面计算每个构件的质量,用于模态和地震分析。承载额外荷载(例如幕墙或管线)的构件可以覆盖这两项:
from fers_core.builders import create_beam
model = create_beam(5.0, "IPE180") # no load but its own weight
model.settings.analysis_options.enable_self_weight = True
for member in model.members:
member.weight_override = 1500.0 # N/m, gravity included
model.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
求解器会用 1500 N/m 替代截面的 184 N/m,并将 1500 / 9.81 kg/m 作为构件的质量。weight_override 使用模型的力/长度单位。Member.weight 则是另一回事:它是 SDK 计算的构件质量,即密度·面积·长度,不会发送给求解器。
地震分析
add_seismic_analysis 会请求求解器执行模态反应谱分析(默认)、侧向力法,或两者都执行。谱辅助函数会构建 EN 1998-1 设计谱。无论模型使用何种单位,加速度都以 m/s² 为单位,周期都以秒为单位;节点质量以 kg 为单位。
from fers_core import (
FERS, Member, MemberSet, MaterialLibrary, NodalMass, NodalSupport, Node, Section,
SeismicAnalysisSettings,
)
model = FERS() # first: it restarts the ids
steel = MaterialLibrary.S235()
column = Section.from_name("HEB200", steel)
nodes = [Node(0.0, float(height), 0.0) for height in range(7)] # a 6 m mast
nodes[0].nodal_support = NodalSupport() # fixed base
model.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)] 会将重力荷载工况转换为地震质量(永久荷载的 ψE = 1.0)。direct_spectrum 可直接指定 S、T_B、T_C 和 T_D,以符合国家附录的要求;custom_spectrum 则接受周期和加速度表。结果是 resultsbundle.seismic 中的普通字典,每种方法各有一项。fers_core/examples/151_Seismic_Response_Spectrum.py 会使用两种方法分析一个门式刚架。
利用率检查报告
求解器可以在结果中嵌入一份包含所有利用率检查的 HTML 报告:
from fers_core.builders import check_beam
model = check_beam(5.0, "IPE300", udl=10000.0)
model.settings.analysis_options.include_report_html = True
model.run_analysis()
html = model.unity_report_html() # a complete HTML page
render_unity_reports = True 还会根据每个已检查构件的 report_template 填充其 rendered_report,并将这些说明添加到 HTML 报告中。两者默认均为关闭,以减小结果体积。
高级版求解与超时
未提供 API 密钥时,求解器会按免费版限额运行(100 个构件),并且完全不会发起网络调用。免费版仅供非商业用途;无论模型大小,商业用途都需要 Pro 密钥。传入 api_key 可提高这些限额:
model.run_analysis(api_key=KEY)
使用密钥意味着每次求解前都会向 ferscloud.com 验证许可证,因此该握手有超时限制。如果许可证服务器在 license_timeout 秒内(默认 30 秒)没有响应,调用会在求解开始前抛出 FersTimeoutError,因此可以安全地重试:
import fers_calculations
try:
model.run_analysis(api_key=KEY, license_timeout=60)
except fers_calculations.FersTimeoutError:
... # transient stall — retry or skip this model
except RuntimeError:
... # definitive — bad key, non-premium account, or a rejected model
将这两种情况分开处理正是关键:重试 RuntimeError 仍会得到相同的失败,而将两者都合并到 except Exception 中,则会把可重试的短暂故障变成结果丢失。完整的无人值守批量处理模式请参阅 fers_core/examples/203_Premium_Batch_Solve.py。
FersTimeoutError 是内置 TimeoutError 的子类,因此普通的 except TimeoutError 也能捕获它。
设置 FERS_LICENSE_TIMEOUT(秒)即可更改默认值,无需修改任何调用代码。它必须是真正的环境变量——引擎不会读取 .env 文件。
license_timeout限制的是许可证握手时间,而不是求解时间。求解在原生代码中运行,没有可取消点,因此进程内超时无法中断求解。若要严格限制每个模型的总运行时间,请在可终止的子进程中运行每次求解——这样也能回收其内存。
需要引擎版本 fers_calculations >= 0.2.53。
从终端和编码 Agent 使用
pip install FERS 会在路径中添加 fers 命令:它是对上述对象和引擎的轻量封装,供 Shell、脚本、CI 作业和编码 Agent 使用。所有大型输出都会写入文件;标准输出每条命令只输出一行,或在使用 --json 时输出一个 JSON 对象。
fers validate model.json # the input schema: the failing field, and keys the engine ignores
fers solve model.json --out results.json # the engine streams the results to the file
fers check model.json # solve, print each unity check's utilization and a verdict
fers report results.json --out report.html
fers schema # the schema's definitions; `fers schema Member` prints one
fers sections get IPE200 # catalogue properties in SI; `sections list` names the series
fers materials list
fers examples list # the numbered examples; `examples show 001`, `examples run 001`
fers skill --install . # the skill for coding agents, into .claude/skills
fers skill --zip fers-skill.zip # the same skill as a zip to upload to Claude.ai
fers doctor # versions, and whether the key is accepted
fers solve 会将文件原样发送给引擎,并且只读取结果来生成摘要行:荷载工况、组合、记录的失败、未收敛的结果集和消息。即使模型会生成数百 MB 的内容,这个进程也不会因此增加负担。
退出代码表示是否可以使用这些数值:
| 代码 | 含义 |
|---|---|
| 0 | 已完成;对于 check,所有检查均通过 |
| 1 | 引擎拒绝或未能完成求解 |
| 2 | 输入错误:文件缺失、JSON 无效、模型不符合架构(使用 validate --strict 时,也包括架构不认识的键) |
| 3 | 许可证问题:需要密钥但未提供、密钥被拒绝、许可证超时 |
| 4 | 已求解,但请勿使用这些数值:结果集存在错误(机构为 singular_model)、未收敛,或记录了失败。输出会说明原因。 |
| 5 | check:无法得出结论:某项检查的结果为 Indeterminate、部分组合尚未求解,或模型没有检查项 |
| 6 | check:某项检查失败(利用率大于 1) |
如果一次 check 同时符合多个退出条件,将优先返回 4、6、5 中最先匹配的代码。使用 --json 时,solve 和 check 会添加 verdict 及其 reasons。出现 “DO NOT USE these results” 后会说明原因:求解器的首个错误,或荷载工况或组合在彻底失败时记录的错误。fers check 会指出每项检查的控制项,例如 governing member 4 (Column, HEA300) in "ULS 1" (combination 2);--json 会通过 governing_entity_label 和 governing_combination_name 返回相同信息。
如果未提供 --api-key,FERS_API_KEY 会用于提供 Premium 限制所需的密钥;没有密钥时,引擎会以 Free 层级限制运行,且不会进行网络调用,如上所述。--solve-token(或 FERS_SOLVE_TOKEN)则使用来自 FERS Cloud 的短期 Premium 令牌,引擎会离线验证该令牌:即使机器无法连接 ferscloud.com,也能使用 Premium 限制。在 Python 中,对应参数为 run_analysis(solve_token=...)。
对于 Agent,技能文件在一页中说明了完整工作循环:选择使用哪种方式(MCP 工具、Shell 或代码沙盒,或供 FERS Studio 使用的文件)、验证、求解、检查,以及会影响答案的约定;reference/api.md 中提供了可运行的模型。该技能随软件包一同发布(fers_core/skills/fers/);fers skill --install . 会将其复制到 .claude/skills/fers/,Claude Code 会在此查找;fers skill --zip fers-skill.zip 会将其打包,以便上传到 Claude.ai;fers skill --path 则会输出 SKILL.md 的路径,供其他 Agent 使用。MCP 客户端也可以改用本地 MCP 服务运行同一引擎(npx -y @ferscloud/fers-mcp,在用户的机器上进行求解);无法启动程序的客户端可使用托管服务 https://ferscloud.com/api/mcp,该服务会返回一个浏览器求解链接。各客户端的设置说明见:https://ferscloud.com/mcp。
fers_core/examples/811_Command_Line_Workflow.py 会端到端地运行整个工作循环。
许可证
本软件包 FERS 采用 BSD 3-Clause License(LICENSE)开源。
它安装的求解器引擎 fers_calculations 使用单独的许可证。从 0.2.70 版起,该引擎采用 PolyForm Strict License 1.0.0:任何非商业用途均可免费使用,例如学习、教学、研究和个人爱好项目。商业用途需要购买付费的 FERS Cloud 方案,其中包含商业许可证。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。