第13章:实战案例——冷弯 C 型钢檩条建模
理论学完,本章把前面所有知识揉进一个完整的工程实战:用命令式 API 建出一根冷弯薄壁 C 型钢檩条(Cold-formed C-purlin)——带卷边、圆角折弯、腰形冲孔、端部缺口的真实构件。这个案例直接取自 OpenCSG.NET 官方仓库的 samples/Runner.CPurlin 示例,是展示这个库工程能力的旗舰 demo。跟着走一遍,你会真正体会到”用代码造零件”的思维方式。
13.1 认识 C 型钢檩条
檩条(purlin) 是屋面/墙面结构里承托压型钢板的次级构件。冷弯 C 型钢是把一条薄钢板在常温下辊压折弯成”C”字截面:
- 腹板(web):C 的竖直背板。
- 翼缘(flange):上下两条水平边。
- 卷边/唇边(lip):翼缘末端向内折的小边,用来提高抗屈曲能力。
- 折弯圆角(radius):冷弯工艺决定了每个转角都是圆角而非尖角。
真实的檩条上还会冲孔(穿螺栓/管线,常用抗滑移的腰形孔)并在端部开缺口以便搭接。我们要把这些全建出来。
13.2 参数化:先定义尺寸
好的工程模型都是参数驱动的——把所有关键尺寸提取成变量,改一个数就是一个新规格。示例开头就是一组参数:
using Csg;
using static Csg.Solids;
var web = 152.0; // 腹板高度
var flange = 64.0; // 翼缘宽度
var lip = 18.5; // 卷边长度
var thickness = 2.4; // 板厚
var height = 1000.0; // 檩条长度(沿构件轴向)
var r = 5.0; // 折弯内圆角半径
var holeSize1 = 18.0; // 孔短径
var holeSize2 = 22.0; // 孔长径(> 短径 → 腰形孔)
var webCenter = 60.0; // 腹板双孔的中心间距
var punchingsWeb = new List<double>() { 35 }; // 腹板孔沿长度的位置
var punchingsFlange = new List<double>() { 70 }; // 翼缘孔位置
var punchingsCenter = new List<double>() { 105 }; // 腹板中心孔位置
一个小工具函数简化向量书写(示例把它放在文件末尾,用局部函数):
static Vector3D V3D(double x, double y, double z) => new(x, y, z);
建模思路:C 型钢的截面沿 Z 轴拉伸 height。我们不用第 10 章的 ExtrudeNode(它处理不了圆角和局部冲孔),而是把截面拆成若干”薄板长条” ——每条板都是一个薄 Cube,长度方向(Z)等于 height——再加上圆角转角,最后并成一体、局部挖孔。这是命令式布尔的用武之地。
13.3 腹板与冲孔
先建腹板:一块厚 thickness(X 向)、高 web - 2r(Y 向,两端留出圆角)、长 height(Z 向) 的薄板,摆在截面左侧(X 负方向):
var sweb = Cube(
center: V3D(-flange / 2 + thickness / 2, 0, 0),
size: V3D(thickness, web - r * 2, height));
然后在腹板上冲孔。示例演示了两组孔——一组是上下对称的双孔(间距 webCenter),一组是中心单孔——都用 Subtract 从腹板上挖掉:
foreach (var p in punchingsWeb) // 上下对称双孔
{
var holeTop = Punching(p, onWeb: true).Translate(-flange / 2, +webCenter / 2, 0);
var holeBottom = Punching(p, onWeb: true).Translate(-flange / 2, -webCenter / 2, 0);
sweb = sweb.Subtract(holeTop, holeBottom);
}
foreach (var p in punchingsCenter) // 中心单孔
{
var holeCenter = Punching(p, onWeb: true).Translate(-flange / 2, 0, 0);
sweb = sweb.Subtract(holeCenter);
}
Subtract 可以一次减多个(params),所以 sweb.Subtract(holeTop, holeBottom) 一步挖两个孔。
13.4 腰形孔(obround)的构造
Punching 局部函数是这个案例的一个亮点——它构造一个腰形孔(也叫长圆孔/腰圆孔):当长径 holeSize2 大于短径 holeSize1 时,孔不是简单的圆柱,而是”两个半圆 + 中间矩形”的胶囊柱:
Solid Punching(double z, bool onWeb = false)
{
// 基础圆柱(贯穿板厚,取 4 倍厚以保证完全穿透)
var punching = Cylinder(holeSize1 / 2, thickness * 4, true);
if (holeSize2 > holeSize1) // 长径更大 → 做成腰形
{
var diff = holeSize2 - holeSize1;
punching = Union(
punching.Translate(z: -diff / 2), // 一端圆柱
punching.Translate(z: +diff / 2), // 另一端圆柱
Cube(V3D(holeSize1, thickness * 4, diff), center: true) // 中间连接段
);
}
if (onWeb)
punching = punching.RotateZ(90); // 腹板孔转 90° 摆正
return punching.Translate(0, 0, height / 2 - z); // 沿长度定位
}
要点:
- 贯穿保证:钻孔圆柱高度取
thickness * 4,远大于板厚,确保布尔差集完全穿透——留余量是挖孔的实用技巧,避免因共面精度问题在孔口留下薄膜。 - 腰形 = 两圆柱 ∪ 一方块:这正是第 6 章”并集造复合形状”的典型应用。
center: true:所有圆柱/方块都取居中,方便对齐。- 方向与定位:
onWeb决定是否RotateZ(90)摆正,末尾Translate把孔移到沿构件长度的指定位置。
13.5 翼缘、卷边与冲孔
上下翼缘是薄 thickness(Y 向)、宽 flange - 2r(X 向) 的水平板,摆在 y = ±(web/2 - thickness/2):
var sflangetop = Cube(
center: V3D(0, web / 2 - thickness / 2, 0),
size: V3D(flange - r * 2, thickness, height));
var sflangebottom = Cube(
center: V3D(0, -web / 2 + thickness / 2, 0),
size: V3D(flange - r * 2, thickness, height));
foreach (var p in punchingsFlange) // 翼缘上下各冲一孔
{
sflangetop = sflangetop.Subtract(Punching(p).Translate(0, +web / 2, 0));
sflangebottom = sflangebottom.Subtract(Punching(p).Translate(0, -web / 2, 0));
}
卷边是翼缘末端向内折的小竖边,薄 thickness(X 向)、高 lip - r(Y 向),摆在开口侧 x = flange/2 - thickness/2:
var sliptop = Cube(
center: V3D(flange / 2 - thickness / 2, web / 2 - r / 2 - lip / 2, 0),
size: V3D(thickness, lip - r, height));
var slipbottom = Cube(
center: V3D(flange / 2 - thickness / 2, -web / 2 + r / 2 + lip / 2, 0),
size: V3D(thickness, lip - r, height));
至此,C 截面的五条直板(腹板、上下翼缘、上下卷边)都齐了。它们之间还差四个圆角转角来连成一体。
13.6 圆角转角:管环 ∩ 象限
冷弯的转角是圆角。Corner(x, y) 用一个巧妙的三步布尔构造一个 90° 的圆角折弯(x、y 取 ±1 选择四个象限之一):
Solid Corner(int x, int y)
{
return Cylinder(r, height, true) // ① 外圆柱(半径 r)
.Subtract(Cylinder(r - thickness, height, true)) // ② 挖去内圆柱 → 得到"管环"(壁厚 thickness)
.RotateX(90) // 摆正到沿 Z 轴
.Intersect(Cube( // ③ 与一个象限方块求交 → 只留 1/4 圆弧
center: V3D(x * r / 2, y * r / 2, 0),
size: V3D(r, r, height)))
.Translate(x * flange / 2 - x * r, y * web / 2 - y * r, 0); // 移到对应转角
}
拆解:
- 外圆柱减内圆柱 = 一个圆管/环(annulus),环壁厚正好是
thickness——这就是折弯处的钢材。 Intersect一个象限方块:整根圆管是完整 360°,我们只要转角处的那 90° 圆弧。用一个偏置到某象限的方块(x*r/2, y*r/2)与圆管求交,切出四分之一弧。x/y的正负号决定切哪个象限。Translate就位:把这段圆弧移到对应的 web-flange 交角。
四次调用 Corner(-1,+1)、Corner(+1,+1)、Corner(-1,-1)、Corner(+1,-1) 生成四个转角。这个”管环 ∩ 象限”的手法是 CSG 建圆角的经典套路,值得记住。
13.7 总装、定向与端部缺口
把五条直板和四个圆角一次性并集,再做整体定向:
var s = Union(
sweb, sflangetop, sflangebottom, sliptop, slipbottom,
Corner(-1, +1), Corner(+1, +1), Corner(-1, -1), Corner(+1, -1))
.RotateX(90)
.Translate(0, height / 2, 0)
.RotateX(90);
末尾的 RotateX(90) → Translate → RotateX(90) 是把整根构件从”截面在 XY、长度沿 Z”的建模姿态,翻转/平移到期望的输出姿态。多步刚体变换组合定位,是装配建模的常规操作(回顾第 7 章)。
最后在一端挖一个缺口(notch) ——用一个大立方体做差集,模拟檩条端部为搭接而切掉的一角:
var notch = Cube(web, web, web, true)
.Translate(0, -web + lip * 1.5, height / 2);
s = s.Subtract(notch);
13.8 双格式导出
成品同时导出 ASCII 和 二进制 两种 STL(回顾第 8 章:TextWriter 走 ASCII,BinaryWriter 走二进制):
using (var fs = File.Create("CPurlin-text.stl"))
using (var sw = new StreamWriter(fs))
s.WriteStl("CPurlin", sw); // ASCII
using (var fs = File.Create("CPurlin-bin.stl"))
using (var bw = new BinaryWriter(fs))
s.WriteStl("CPurlin", bw); // 二进制(更小)
到此,一根带卷边、圆角、腰形冲孔和端部缺口的冷弯 C 型钢檩条就建好了,可直接送入切片器/查看器或后续 CAE 流程。
13.9 从这个案例学到的建模范式
这个实战浓缩了用 OpenCSG.NET 做工程建模的核心套路,值得提炼成可复用的方法论:
- 参数化优先:所有尺寸抽成变量,模型即”参数的函数”,改数即改型号。
- 化整为零:复杂截面拆成一组简单薄板长条(
Cube),而非追求一次成型。 - 辅助函数封装重复逻辑:
Punching、Corner、V3D把重复的构造收进局部函数,主流程清晰如”装配说明书”。 - 并集做加法、差集做减法、交集做裁剪:加料用
Union(拼板、拼腰形孔),减料用Subtract(冲孔、开缺口),取局部用Intersect(切象限做圆角)。 - 留穿透余量:挖孔的刀具比板厚大几倍,规避共面精度问题。
- 变换负责定位:
Translate/Rotate组合把各零件摆到正确位姿。
掌握这六条,你就能把任何冷弯型材、钣金件、机加工零件”翻译”成 OpenCSG.NET 代码。
13.10 本章小结
- 完整复现官方
Runner.CPurlin示例:一根带卷边、圆角折弯、腰形冲孔、端部缺口的冷弯 C 型钢檩条。 - 截面被拆成五条薄板长条(腹板/上下翼缘/上下卷边)+ 四个圆角转角,并集成一体。
- 腰形孔 = 两圆柱 ∪ 中间方块;圆角 = (外圆柱 − 内圆柱)∩ 象限方块;冲孔/缺口用差集,刀具留穿透余量。
- 末尾用
RotateX/Translate组合定向,导出 ASCII + 二进制两种 STL。 - 提炼出六条工程建模范式:参数化、化整为零、辅助函数、加/减/裁三类布尔、穿透余量、变换定位。
最后一章我们转向质量与扩展:官方如何用批准测试保证正确性、用 BenchmarkDotNet 量化性能,以及你如何给这个库做二次开发(补全圆锥、添加自定义截面、发布 NuGet)。