第14章:实战案例——冷弯 C 型钢檩条建模

理论学完,本章把前面所有知识揉进一个完整的工程实战:用命令式 API 建出一根冷弯薄壁 C 型钢檩条(Cold-formed C-purlin)——带卷边、圆角折弯、腰形冲孔、端部缺口的真实构件。这个案例直接取自 OpenCSG.NET 官方仓库的 samples/Runner.CPurlin 示例,是展示这个库工程能力的旗舰 demo。跟着走一遍,你会真正体会到”用代码造零件”的思维方式。

样例的现结构:几何构造已从 Program.cs 抽取为公共类 CpurlinModel(samples/Runner.CPurlin/CpurlinModel.cs),尺寸以 public const 常量给出(Web=152、Flange=64、Lip=18.5、Thickness=2.4、Radius=5、Height=1000 等);Program.cs 只负责调用 CpurlinModel.Build() 并导出文件。抽取的目的是让系统测试(第 15 章)能把同一份几何直接引为测试夹具。本章代码展示与抽取前保持逐字一致——只是 Punching、Corner、V3D 从局部函数变成了 private static 静态方法,内部另有一个 DebugMode 调试开关(默认 false,开启时各段柱高按倍率放大便于目视检查接缝)。

14.1 认识 C 型钢檩条

檩条(purlin) 是屋面/墙面结构里承托压型钢板的次级构件。冷弯 C 型钢是把一条薄钢板在常温下辊压折弯成”C”字截面:

  • 腹板(web):C 的竖直背板。
  • 翼缘(flange):上下两条水平边。
  • 卷边/唇边(lip):翼缘末端向内折的小边,用来提高抗屈曲能力。
  • 折弯圆角(radius):冷弯工艺决定了每个转角都是圆角而非尖角。

真实的檩条上还会冲孔(穿螺栓/管线,常用抗滑移的腰形孔)并在端部开缺口以便搭接。我们要把这些全建出来。

14.2 参数化:先定义尺寸

好的工程模型都是参数驱动的——把所有关键尺寸提取成变量,改一个数就是一个新规格。CpurlinModel 开头就是一组常量:

using Csg;
using static Csg.Solids;

// 截面尺寸
public const double Web = 152.0;        // 腹板高度
public const double Flange = 64.0;      // 翼缘宽度
public const double Lip = 18.5;         // 卷边长度
public const double Thickness = 2.4;    // 板厚
public const double Radius = 5.0;       // 折弯内圆角半径
public const double Height = 1000.0;    // 檩条长度(沿构件轴向)

// 冲孔尺寸(腰形孔:两端半圆 holeSize1 + 中间矩形差 holeSize2 − holeSize1)
public const double HoleSize1 = 18.0;   // 孔短径
public const double HoleSize2 = 22.0;   // 孔长径(> 短径 → 腰形孔)

// 冲孔位置(z 值;WebCenter 为腹板孔对相对中线的偏移)
public const double WebCenter = 60.0;
public const double PunchingWebZ = 35.0;     // 腹板孔位置
public const double PunchingFlangeZ = 70.0;  // 翼缘孔位置
public const double PunchingCenterZ = 105.0; // 腹板中心孔位置

一个小工具函数简化向量书写(类内私有静态方法):

private static Vector3D V3D(double x, double y, double z) => new(x, y, z);

建模思路:C 型钢的截面沿 Z 轴拉伸 height。我们不用第 10 章的 ExtrudeNode(它处理不了圆角和局部冲孔),而是把截面拆成若干”薄板长条” ——每条板都是一个薄 Cube,长度方向(Z)等于 height——再加上圆角转角,最后并成一体、局部挖孔。这是命令式布尔的用武之地。

14.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) 一步挖两个孔。

14.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 把孔移到沿构件长度的指定位置。

14.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 截面的五条直板(腹板、上下翼缘、上下卷边)都齐了。它们之间还差四个圆角转角来连成一体。

14.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);  // 移到对应转角
}

拆解:

  1. 外圆柱减内圆柱 = 一个圆管/环(annulus),环壁厚正好是 thickness——这就是折弯处的钢材。
  2. Intersect 一个象限方块:整根圆管是完整 360°,我们只要转角处的那 90° 圆弧。用一个偏置到某象限的方块(x*r/2, y*r/2)与圆管求交,切出四分之一弧。x/y 的正负号决定切哪个象限。
  3. Translate 就位:把这段圆弧移到对应的 web-flange 交角。

四次调用 Corner(-1,+1)、Corner(+1,+1)、Corner(-1,-1)、Corner(+1,-1) 生成四个转角。这个”管环 ∩ 象限”的手法是 CSG 建圆角的经典套路,值得记住。

14.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);

14.8 样例入口与双格式导出

CpurlinModel.Build() 返回成品实体;Program.cs 负责落盘——同时导出 ASCII 和 二进制 两种 STL,并带一个环境守卫(默认不弹窗,显式设置 OPENCSG_OPEN_VIEWER=1 才打开查看器):

// samples/Runner.CPurlin/Program.cs(要点摘录)
var s = CpurlinModel.Build();

using (var fs = File.Create("CPurlin-text.stl"))
using (var sw = new StreamWriter(fs))
    s.WriteStl("CPurlin", sw);          // ASCII(回顾第 8 章)

using (var fs = File.Create("CPurlin-bin.stl"))
using (var bw = new BinaryWriter(fs))
    s.WriteStl("CPurlin", bw);          // 二进制(更小)

// 环境守卫:CI/无头环境默认不弹窗
if (Environment.GetEnvironmentVariable("OPENCSG_OPEN_VIEWER") == "1")
{
    Process.Start(new ProcessStartInfo { FileName = "CPurlin-text.stl", UseShellExecute = true });
}

到此,一根带卷边、圆角、腰形冲孔和端部缺口的冷弯 C 型钢檩条就建好了,可直接送入切片器/查看器或后续 CAE 流程。它的几何随后也被第 15 章的系统测试直接引用(CPurlinModelTest),作为”真实模型端到端管线”的测试夹具。

14.9 从这个案例学到的建模范式

这个实战浓缩了用 OpenCSG.NET 做工程建模的核心套路,值得提炼成可复用的方法论:

  1. 参数化优先:所有尺寸抽成变量,模型即”参数的函数”,改数即改型号。
  2. 化整为零:复杂截面拆成一组简单薄板长条(Cube),而非追求一次成型。
  3. 辅助函数封装重复逻辑:Punching、Corner、V3D 把重复的构造收进局部函数,主流程清晰如”装配说明书”。
  4. 并集做加法、差集做减法、交集做裁剪:加料用 Union(拼板、拼腰形孔),减料用 Subtract(冲孔、开缺口),取局部用 Intersect(切象限做圆角)。
  5. 留穿透余量:挖孔的刀具比板厚大几倍,规避共面精度问题。
  6. 变换负责定位:Translate/Rotate 组合把各零件摆到正确位姿。

掌握这六条,你就能把任何冷弯型材、钣金件、机加工零件”翻译”成 OpenCSG.NET 代码。

14.10 本章小结

  • 完整复现官方 Runner.CPurlin 示例:一根带卷边、圆角折弯、腰形冲孔、端部缺口的冷弯 C 型钢檩条;几何已抽取为公共类 CpurlinModel(供测试复用),尺寸以 public const 常量给出。
  • 截面被拆成五条薄板长条(腹板/上下翼缘/上下卷边)+ 四个圆角转角,并集成一体。
  • 腰形孔 = 两圆柱 ∪ 中间方块;圆角 = (外圆柱 − 内圆柱)∩ 象限方块;冲孔/缺口用差集,刀具留穿透余量。
  • 末尾用 RotateX/Translate 组合定向;Program.cs 导出 ASCII + 二进制两种 STL,查看器打开由 OPENCSG_OPEN_VIEWER=1 环境变量显式控制。
  • 提炼出六条工程建模范式:参数化、化整为零、辅助函数、加/减/裁三类布尔、穿透余量、变换定位。

下一章转向质量与扩展:官方如何用测试(单元/契约/黄金/属性/端到端五层)保证正确性、用覆盖率门禁与 CI 守住质量、用 BenchmarkDotNet 量化性能,以及你如何给这个库做二次开发(自定义截面、扩展序列化、发布 NuGet)。


← 上一章 目录 下一章 →