简介:FLAC3D 5.01 的插件文件集合,内含 FISH 脚本、Interface 接口、Lib 库及多种 Models 材料模型源代码,面向岩土与数值模拟领域需要二次开发的工程师和研究者。包内共 317 个文件,以 C++ 头文件(.h)和源文件(.cpp)为主,配以 DLL/LIB 动态链接库、Visual Studio 工程文件(sln/vcxproj)及少量 PDF 文档,压缩包约 4.99MB,结构清晰,便于定位核心代码。已有 553 人学习,适合具备一定编程基础并希望深入理解 FLAC3D 内部机制的读者。通过研读这些源码,可以掌握 FISH 语言与内核交互的方法、不同材料模型(如弹塑性、粘塑性等)的实现细节,并能基于现有库函数定制新本构模型或外部接口,为处理边坡、隧道、基坑等复杂工程问题提供灵活扩展能力。 FLAC3D 5.01 安装目录下的 pluginfiles 文件夹,很多岩土工程师用了好几年都没认真打开过。直到有一次项目需要模拟一种内置模型覆盖不了的软岩蠕变特性,我翻遍软件自带文档无果,最后是这个目录里的 fish、interface、lib 和 models 源代码救了我。FLAC3D 的插件体系比大多数人想象的要开放得多,Itasca 不只是给你一个能跑的计算内核,还顺手把模型开发的“半成品”直接放在了你的硬盘上。
这篇东西就是围绕 pluginfiles 的一次彻底拆解,讲清楚里面每个子目录是干什么的、自定义本构模型和 interface 的代码该怎么读、怎么改、怎么编译回 dll 让 FLAC3D 认账。适合正在用 FLAC3D 5.01 做岩土数值分析、又苦于内置模型不够用的工程师和研究人员,也适合刚接触自定义本构却被 C++ 代码劝退的朋友。我会尽量把原理讲透,把操作步骤写实,把我踩过的坑也一并交代了。
1. 拆开 pluginfiles:一个插件包就是一套“模型加工车间”
先别急着动代码,把目录结构摸清楚比什么都重要。我第一次打开 pluginfiles 的时候,以为是随便丢了些示例文件,后来对照 Itasca 的用户手册和编译日志才发现,这个文件夹其实是把整个本构模型开发包揉碎了放在你眼前。
1.1 每个子目录的真实职责一览
FLAC3D 5.01 的 pluginfiles 里,核心子目录无外乎这几类,我按实际用途整理了一张表:
| 目录/文件 | 里边装了什么 | 真实用途 |
|---|---|---|
| fish | 若干 .fis 和 .dat 脚本 | 配合模型的 FISH 函数、示例命令流、后处理宏 |
| interface | 界面单元相关源码 | 模拟断层、节理、衬砌接触面、锚杆滑移面的单元实现 |
| lib | 静态库/导入库 + 头文件 | 编译自定义模型时必须链接的 SDK 依赖 |
| models | 一系列内置本构模型的源码工程 | 摩尔-库仑、应变硬化/软化、双屈服、蠕变等的 C++ 实现 |
| 各类 .sln / .vcxproj | Visual Studio 工程文件 | Itasca 官方用来构建这些 dll 的工程配置 |
你可以把它理解成一套“模型加工车间”:models 和 interface 是各种零件的图纸,lib 是机床本身,fish 是操作手册,而最终的 dll 就是从这个车间里加工出来的成品零件。FLAC3D 主程序启动时会去扫描 pluginfiles 目录(以及它指定的其他插件目录),把每个 dll 里的模型注册进求解器,然后你就能在命令里直接model mohr、model ss这样调用。
1.2 插件加载时的“注册”机制
这里有个关键概念如果你不理解,后面看代码会一头雾水:dll 不是被主程序随便调用的,它必须通过一个约定的导出函数把自身“介绍”给 FLAC3D。在 pluginfiles 的源码里你会反复看到类似ProvideConstitutiveModel这样的导出符号,FLAC3D 启动时逐个加载 dll、调用这个导出函数、拿到模型类的实例指针,然后才能响应model xxx命令。
反过来,这也解释了为什么很多人改了源码、编译出 dll 放进目录后,一运行却报“模型未注册”或者直接闪退——十有八九是导出函数没写对,或者 dll 依赖的 lib 版本和主程序对不上。这个机制我在第 5 部分会专门展开讲,先记住这句话:pluginfiles 里的每一个文件,最终都是为了生成一个能被主程序“认出”的 dll。
2. models 和 lib:自定义本构模型的代码底座
ling 上回目录结构本身并不难,真正有含金量的是 models 里那些 C++ 源码。很多人一看到类继承、虚函数就头大,其实 Itasca 的模型代码框架非常统一,只要你读懂了一个模型,剩下的基本就是照葫芦画瓢。
2.1 一个本构模型类的标准五脏六腑
打开 models 目录下任意一个模型源码,你一定会看到这几个核心成员函数:
initialize():求解开始前被调用,做状态变量的初始化,比如把塑性应变清零、把硬化参数设成初值。run():每个时步都会调用的核心计算函数,根据当前应力、应变增量计算新的应力张量,并更新塑性标志位。setProperty()/getProperty():对应命令里的property xxx,负责把用户输入的参数(粘聚力、内摩擦角、剪胀角等)映射到类的成员变量里。getStress()/getStiffness():把计算完的应力和切线刚度矩阵交还给主程序。
这五个函数就是模型的骨架。Itasca 内部采用的是类似“显式时步 + 应力更新”的流程,你不需要自己搭有限元框架,只需要在run()里写好本构方程即可。拿摩尔-库仑模型举例,run()里干的事就是:先把总应变增量拆成弹性和塑性两部分,做一次弹性试算,然后判断应力点是否超过屈服面,超过就做塑性修正,把应力拉回屈服面上。
很多初看源码的人会问:为什么这个类是从一个自己不认识的基类继承过来的,那个基类里的纯虚函数是什么?答案在 lib 目录的头文件里。pluginfiles 的 lib 不只是给你链接用的,它更像一张“接口契约书”,定义了主程序和模型之间的全部约定。你写的模型类只需要把自己“填”进这些约定里,剩下的事情(比如单元形函数计算、节点力组装)主程序全包了。
2.2 从“改一个内置模型”入手的捷径
我不建议你一上来就写一个全新的本构模型,最现实的做法是:找 models 里和你需求最接近的那个,复制一份工程,改类名和导出函数名,然后只动本构方程那几行。
举个例子。我当时要模拟一种峰后有明显应变软化、且残余强度随围压变化的软岩。内置的 strain-hardening 模型只支持硬化,不支持软化,直接套用算出来完全不对。我的做法是:
- 把
strain-hardening整个工程复制一份,改名为softening-custom。 - 在
run()里找到硬化参数更新的位置,把屈服面随塑性应变的变化方向反过来。 - 增加一个代表围压影响的成员变量,在
setProperty()里接收用户输入的confining参数。 - 重新编译,把新 dll 放进 pluginfiles,重启软件,输入
model softening-custom。
整个过程大概一个下午就搞定了,因为骨架全是现成的,你改的只是物理规则本身。这也是为什么 pluginfiles 的价值被严重低估——Itasca 等于免费送了你一大批“接近成品”的模型。
2.3 命名规则:dll 文件名的背后有讲究
还有一个容易踩坑的细节:dll 文件名不能乱取。FLAC3D 5.01 在解析model xxx命令时,会基于 dll 文件名和导出函数里的描述信息去匹配模型名。也就是说,你如果把 dll 改成了my_model.dll,命令里的模型名并不一定是my_model,还要看源码里那个模型描述符字符串写的是什么。
正确做法是保持源码工程里的模型描述符和 dll 文件名一致,通常 Itasca 用的是小写缩写,比如cysoil.dll对应model cysoil。改完名建议先在工程里全局搜一下模型描述字符串,确认它和你预期在命令里输入的模型名完全一致,再动手编译。
3. interface 的源代码:读懂了它,就学会了处理结构面
如果你做过断层、节理或者衬砌和围岩接触面的模拟,那你一定知道 interface 在 FLAC3D 里的地位。这个目录里的源码,就是这些界面单元的底层实现。
3.1 interface 在 5.01 里到底包含哪些单元
pluginfiles 的 interface 源码至少覆盖了这么几类东西:
- 标准的库仑滑移界面单元:用于模拟岩体中的不连续面,允许法向受压传递、切向产生塑性滑移,超过抗剪强度后进入残余摩擦段。
- 锚杆/锚索相关的切向滑移模型:比如模拟锚杆与灌浆体界面的荷载传递,这可能直接对应到你项目中用到的 DASP 这类界面参数模型。
- 扩展的接触算法:处理单元法向嵌入、切向相对位移的检测和修正逻辑,防止两侧单元互相“穿透”。
这些源码有个共同点:你都能在里面看到非常直白的物理表达——法向刚度、切向刚度、粘聚力、摩擦角、抗拉强度,全部以成员变量的形式暴露在类里面。所以读 interface 源码,本质上是在看 Itasca 工程师如何把岩体力学里的接触理论落成代码。
3.2 从源码里能扒出来的三个工程细节
第一个细节是刚度的计算方式。很多人手动设 interface 参数时最头疼的就是法向刚度kn和切向刚度ks取多少合适。源码里其实有明确的逻辑:它基于相邻单元的等效模量和单元尺寸做自动估算,再把用户的手动指定值叠加进去。看懂这一段,你就能明白为什么很多老手建议kn取相邻单元等效模量的 10 倍左右——这不是拍脑袋,而是为了在接触面上近似“刚性”约束又不引起数值病态。
第二个细节是滑移状态的判断。interface 的应力更新不是简单套一个弹性-理想塑性,代码里通常用了一个屈服函数判断当前接触点是处于弹性粘着、切向滑移还是张拉分离三种状态之一。这个三态判定直接影响你计算结果的收敛性和精度,如果你发现模型里 interface 不收敛,多半是某些单元点的应力状态在滑移和非滑移之间来回振荡,需要查看屈服函数附近的收敛容差设置。
第三个细节是 interface 编号和数据结构的组织。很多人在初学阶段用interface 1 face或者interface 1 node手动指定节点分组,后来发现每次改网格都要重来。源码里其实展示了另一种组织方式——通过 FISH 函数批量构建 interface,按高程范围、按距离阈值自动匹配两侧节点,改网格时不用动命令流主体。如果你嫌单调手选太麻烦,这个思路非常值得借鉴。
4. fish 的本质:连接界面、单元与求解器的粘合剂
在 pluginfiles 里,fish 目录的存在感最低,但它反而是整个插件体系里最灵活的部分。FISH 是 FLAC3D 内嵌的脚本语言,插件的很多示例功能、参数初始化、结果提取都是靠 fish 脚本串起来的。
4.1 fish 在插件里的三个典型角色
第一个角色是预处理:生成带 interface 的网格分组、给不同的单元组批量赋参数、设置初始应力场。比如你要做一条断层切割矿体的模型,直接用鼠标一个个点 interface 节点会崩溃,但用 fish 写个循环遍历断层两侧的节点对,一段脚本就全搞定了。
第二个角色是运行时监视:在求解循环里每隔几步读取单元应力、塑性区标志、interface 滑移量这些中间量,判断是继续算还是提前终止。很多复杂模型不收敛不是参数错,而是中间过程已经出现了大范围塑性流动,看云图太晚,用 fish 在循环里做实时判断就直观多了。
第三个角色是后处理:把计算结果按自己需要的格式导出。pluginfiles 自带的 fish 脚本里有一些现成的数据导出宏,你可以直接改路径、改变量名,省去自己从头写 Excel 输出的功夫。
4.2 什么时候你会想把 fish 写进插件里
有个常见的误解是:fish 只能在命令流里跑,和 dll 里的 C++ 代码是两套世界。实际上 FLAC3D 5.01 提供了 C++ 代码调用 FISH 函数的通道,插件里也能注册自己的 FISH 函数。什么意思呢?就是你完全可以在自定义本构模型里,把某些状态变量通过一个导出的 FISH 函数暴露给命令流,让用户在求解过程中随时查看。
我举一个更具体的场景:做一个蠕变模型,C++ 代码里每天都会更新黏塑性应变,你想在计算时实时观察某个监测点的蠕变速率。没有插件注册的 FISH 函数,你只能先算完、再提取、再画曲线。而如果你在插件里注册一个creep_rate函数,命令流里直接用fish echo或者在zone history里调用它,就能盯着实时结果看。这就是 fish 目录里那些脚本存在的意义——它们是 C++ 模型对外的“接口”,只是这个接口以脚本的形式为你准备好了。
5. 从源码到自己编译:环境配置和最容易踩的坑
前面讲了这么多,最终你还是要过编译这一关。FLAC3D 5.01 的插件工程用的是 Visual Studio,我见过太多人在这一步失败,所以单独开一章把环境配置和常见问题说清楚。
5.1 搭建编译环境前必须确认的三件事
第一件事,确认你的 FLAC3D 主程序是 32 位还是 64 位。插件 dll 必须和主程序位数一致,否则加载时直接报错。当时我手头两台机器,一个装 32 位一个装 64 位,dll 串了之后折腾了我半天才反应过来。
第二件事,Visual Studio 的版本要和 Itasca 编译主程序时用的工具集匹配。5.01 年代的工程主要是 VS2010、VS2013 这一代,你拿 VS2022 直接打开老 .sln 文件,多半要经历漫长的工具集升级提示,升完也可能踩到 C++ 标准库改动带来的编译错误。稳妥的做法是先尝试用旧版工具集编译,别急着把整个工程迁移到新格式。
第三件事,检查 lib 目录里的库文件是否齐全。有些压缩包或者安装目录里 lib 文件不全,编译链接时就会报一堆LNK2019 无法解析的外部符号。遇到这种错误别急着检查自己代码,先把依赖环境捋一遍。
5.2 编译过程中我实测过的典型错误与对应解法
| 报错/现象 | 出现原因 | 解决办法 |
|---|---|---|
| 加载 dll 后提示模型未注册 | 导出函数名拼写错误或没写extern "C" | 对照 lib 头文件里的导出函数声明,原样复制函数签名 |
| 编译通过,但一跑就崩 | 类名或命名空间和另一个内置模型冲突 | 全局搜索类名,把自定义模型类放进独立命名空间 |
| LNK2019 无法解析的外部符号 | lib 链接不全,或版本与源码不匹配 | 把 pluginfiles 下 lib 目录的 .lib 文件逐一加入工程依赖 |
命令里model xxx找不到 | dll 文件名/模型描述符和预期不一致 | 查看源码里模型描述字符串,以其为准修改 dll 文件名 |
| 新模型计算结果与内置模型差异巨大 | 没改类名导致内置模型被覆盖 | 确定所有类和导出函数均使用新名称,避免同名符号冲突 |
这些坑看起来都是小问题,但每一个都能让你白耗半天。我的建议是:第一次做自定义模型,先别急着改本构方程,直接把原始工程编译一遍,把原始 dll 替换回去,确认整个流程能跑通。跑通了再去改代码,这样问题能立刻定位是编译环节还是模型逻辑环节。
5.3 调试技巧:善用initialize()里的日志输出
代码逻辑错乱的时候,C++ 调试器不一定好用,因为 FLAC3D 主程序里你没法轻易下断点。我更推荐在initialize()里临时写文件日志,把关键输入参数和初始状态量打出来,跑完看日志。这个方法虽然原始,但在排查“模型参数没传进去”“初始状态没设对”这两类问题的时候,比任何调试器都直白。我自己编自定义模型时,日志文件里会固定输出:用户输入的每一条 property、每个单元初始应力、以及屈服面初值。基本每回都能靠这几行日志定位到问题。
编译通过之后,最后再分享一个小经验:不管你是改了 models 里的本构,还是动了 interface 源码,先把原始 dll 备份到一个单独目录。插件开发是个试错过程,你永远不知道下一步改会引入什么诡异问题,有备份才能随时回滚。我在一个矿山边坡项目里就是把应变硬化模型改成带残余强度折减的版本,跑了无数遍验证,最后模型结果和现场监测位移对得很好。那个过程的起点,就是老老实实把 pluginfiles 从头到尾读了一遍。希望这篇拆解能让你少走点弯路,早日在这套源码上做出自己的模型。
本文还有配套的精品资源,点击获取