znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第02章:环境搭建与第一个程序

上一章我们从宏观上认识了 OpenCSG.NET。本章开始动手:安装开发环境、把 OpenCSG.NET 引入项目、看懂官方仓库的结构,并亲手写出、运行、导出你的第一个三维零件。

2.1 准备开发环境

2.1.1 安装 .NET SDK

OpenCSG.NET 的核心库目标框架是 netstandard2.0,这意味着几乎任何现代 .NET 都能引用它。但要编译示例、测试、或使用官方解决方案,建议安装较新的 SDK:

  • 最低要求:.NET 8 SDK(示例/测试/性能项目均为 net8.0)。
  • 推荐:.NET 9 SDK 或更高——因为官方解决方案文件采用了新的 .slnx(XML 格式),只有 .NET 9+ 的 dotnet CLI 才能直接构建它。

验证安装:

dotnet --version
dotnet --list-sdks

只要能看到 8.x 或更高版本即可。Windows、macOS、Linux 三平台通用。

2.1.2 选择编辑器

任何支持 C# 的编辑器都可以:

  • Visual Studio 2022(Windows,功能最全)
  • Visual Studio Code + C# Dev Kit 扩展(跨平台,轻量)
  • JetBrains Rider(跨平台,体验优秀)

2.2 三种引入 OpenCSG.NET 的方式

2.2.1 方式一:NuGet 包(最推荐)

绝大多数使用者应该直接引用 NuGet 上发布的 OpenCSG.NET 包。新建一个控制台项目并添加包:

dotnet new console -n MyCsgApp
cd MyCsgApp
dotnet add package OpenCSG.NET

这会在 MyCsgApp.csproj 中加入一行 PackageReference

<ItemGroup>
  <PackageReference Include="OpenCSG.NET" Version="*" />
</ItemGroup>

提示:OpenCSG.NET 核心库只对 System.Text.Json 有一个间接依赖,NuGet 会自动帮你还原,无需手动添加。

2.2.2 方式二:项目引用(源码集成)

如果你想跟踪最新源码、调试内核,或做二次开发,可以把 OpenCSG.NET 仓库克隆到本地,然后在你的项目里引用它的 csproj

<ItemGroup>
  <ProjectReference Include="..\OpenCSG.NET\src\OpenCSG.NET\OpenCSG.NET.csproj" />
</ItemGroup>

官方的三个示例/测试项目都是用这种方式引用核心库的。

2.2.3 方式三:直接拷贝源码

因为核心库零第三方依赖(除序列化用到的 System.Text.Json),源码文件也不多,你甚至可以把 src/OpenCSG.NET/*.cs 直接拷进你的工程。这在受限环境(如某些 Unity 场景)里很实用,但一般不推荐,因为难以跟踪上游更新。

2.3 看懂官方仓库结构

克隆官方仓库后,你会看到如下结构。理解它有助于你在需要时快速定位源码、示例和测试。

OpenCSG.NET/
├── OpenCSG.NET.slnx              # 解决方案(新版 XML 格式,需 .NET 9+)
├── README.md                     # 项目说明(英文 + 中文)
├── LICENSE                       # MIT 许可证
├── OpenJsCad_LICENSE.txt         # 上游 csg.js 的许可证(血统致谢)
├── .editorconfig                 # 代码风格约定
├── .github/workflows/
│   └── nuget-publish.yml         # 打 tag 时自动发布到 NuGet 的 CI
├── src/OpenCSG.NET/              # ★ 核心库(本教程主角)
│   ├── OpenCSG.NET.csproj
│   ├── Vector.cs                 # Vector3D/Vector2D/Matrix4x4/BoundingBox 等数学类型
│   ├── Vertex.cs                 # 顶点(位置 + 纹理坐标)
│   ├── Plane.cs                  # 平面与多边形分割
│   ├── Polygon.cs                # 凸多边形
│   ├── Solid.cs                  # ★ 实体:布尔运算、规范化、Tag 系统
│   ├── Solids.cs                 # ★ 形体工厂:Cube/Sphere/Cylinder + 静态布尔
│   ├── Tree.cs                   # BSP 树(迭代式)
│   ├── Formats.cs                # STL 导出(ASCII + 二进制)
│   ├── CsgNode.cs                # 声明式节点 record 定义
│   ├── CsgEvaluator.cs           # 声明式树的求值器
│   ├── CsgSerialization.cs       # CsgNode 的 JSON 序列化
│   └── Profiles.cs               # 参数化型材截面(H 型钢、槽钢等)
├── samples/
│   ├── Runner.Examples/          # 基础用法示例(形体/变换/布尔/杠铃/管道/Wiki 图标)
│   └── Runner.CPurlin/           # 进阶实战:冷弯 C 型钢檩条
├── tests/OpenCSG.NET.Tests/      # NUnit 测试(近似 STL 比对)
└── perf/OpenCSG.NET.PerfTest/    # BenchmarkDotNet 性能基准

核心库一共 13 个 .cs 文件,本教程会把它们逐一讲透。其中 Solid.csSolids.csTree.cs 是内核三剑客,CsgNode.cs/CsgEvaluator.cs/CsgSerialization.cs/Profiles.cs 组成声明式层。

2.4 构建与测试官方仓库

如果你克隆了源码,可以先跑通官方的构建与测试,确认环境无误。

使用 .NET 9+ SDK(直接构建整个解决方案):

dotnet build OpenCSG.NET.slnx
dotnet test  tests/OpenCSG.NET.Tests/

只有 .NET 8 SDK(单独构建核心库与测试):

dotnet build src/OpenCSG.NET/OpenCSG.NET.csproj -c Release
dotnet test  tests/OpenCSG.NET.Tests/

之所以要区分,是因为 .slnx 是 .NET 9 引入的新解决方案格式;而核心库本身是 netstandard2.0,用 .NET 8 SDK 也能单独编译。测试项目是 net8.0,NUnit 驱动,采用”近似比对”策略——把生成的 STL 与 tests/.../Results/ 下的已接受结果对比三角面数、总面积和包围盒(第 14 章详解)。

运行官方示例:

dotnet run --project samples/Runner.Examples
dotnet run --project samples/Runner.CPurlin

Runner.Examples 会在工作目录生成 cube.stlwiki.stlCsgExamples.stl 等文件,并尝试用系统默认的 STL 查看器打开(这一步在无 GUI 的服务器上可能失败,忽略即可,文件已经生成)。

2.5 你的第一个程序:一个带通孔的法兰盘

现在从零写一个完整程序。目标是做一个”法兰盘”雏形:一个扁圆柱盘体,中间挖一个大通孔,四周挖四个小螺栓孔。

新建控制台项目并添加包(见 2.2.1),然后把 Program.cs 替换为:

using Csg;
using static Csg.Solids;

// 参数(改这些数字即可生成不同规格的法兰)
double plateRadius = 30;   // 盘体半径
double plateHeight = 6;    // 盘体厚度
double boreRadius  = 10;   // 中心通孔半径
double boltRadius  = 3;    // 螺栓孔半径
double boltCircle  = 22;   // 螺栓孔分布圆半径

// 1) 盘体:一个扁圆柱。注意 Cylinder 默认沿 Y 轴,这里用 RotateX(90) 把它立起来沿 Z 轴
var plate = Cylinder(r: plateRadius, h: plateHeight, center: true).RotateX(90);

// 2) 中心通孔:一个略高于盘体的圆柱,保证完全穿透
var bore = Cylinder(r: boreRadius, h: plateHeight + 2, center: true).RotateX(90);

// 3) 盘体挖去中心孔
var flange = plate.Subtract(bore);

// 4) 四个螺栓孔:把一个小圆柱绕中心旋转到 4 个方位
for (int i = 0; i < 4; i++)
{
    double angleDeg = 90 * i;
    double rad = angleDeg * System.Math.PI / 180.0;
    double x = boltCircle * System.Math.Cos(rad);
    double y = boltCircle * System.Math.Sin(rad);

    var hole = Cylinder(r: boltRadius, h: plateHeight + 2, center: true)
                   .RotateX(90)                 // 立起来沿 Z
                   .Translate(x: x, y: y);      // 平移到分布圆上

    flange = flange.Subtract(hole);
}

// 5) 导出为 ASCII STL
using (var writer = new StreamWriter("flange.stl"))
{
    flange.WriteStl("flange", writer);
}

System.Console.WriteLine($"完成,共 {flange.Polygons.Count} 个多边形,已写入 flange.stl");

运行:

dotnet run

你会在项目目录里得到 flange.stl,控制台也会打印出结果实体的多边形数量。用 STL 查看器打开,就能看到一个中心带大孔、四周均布 4 个螺栓孔的法兰盘。

2.5.1 这个程序教会我们什么

尽管很短,这个程序已经涵盖了 OpenCSG.NET 命令式 API 的四大要素:

  1. 参数化——所有尺寸都是变量,改数字就换规格,这正是”代码即模型”的价值。
  2. 形体默认沿 Y 轴——Cylinder 沿 Y 轴生成,我们用 .RotateX(90) 把它转到沿 Z 轴。这是新手最常见的困惑,第 5、7 章会深入。
  3. 不可变对象 + 链式调用——每个返回 Solid 的方法都返回新对象,绝不修改原对象。所以我们能安全地复用同一个”孔模板”再平移到不同位置。
  4. 布尔运算做减法——Subtract 把孔从盘体里”挖掉”。为保证完全穿透,孔的高度总是比盘体略大(plateHeight + 2),这是 CSG 打孔的经典技巧,避免共面导致的数值问题(第 6 章详解)。

2.6 常见环境问题排查

现象 原因 解决
dotnet build OpenCSG.NET.slnx 报无法识别 .slnx SDK 版本过低 升级到 .NET 9+ SDK,或改为单独构建 src/.../OpenCSG.NET.csproj
找不到 Csg 命名空间 未添加包或未 using Csg; 执行 dotnet add package OpenCSG.NET,并加上 using Csg;
Cube(...) 报未定义 缺少静态引入 加上 using static Csg.Solids;
生成的 STL 打不开 / 面片穿插 孔与实体恰好共面 让挖孔实体比目标略高/略大,避免精确共面(第 6 章)
运行示例时报无法启动查看器 无 GUI 的服务器环境 忽略即可,STL 文件已经成功生成

2.7 本章小结

  • 安装 .NET 8 SDK(构建核心库/测试/示例的最低要求),如需构建 .slnx 解决方案则需 .NET 9+
  • 引入 OpenCSG.NET 有三种方式:NuGet 包(推荐)、项目引用(二次开发)、拷贝源码(受限环境)。使用时记得同时 using Csg;using static Csg.Solids;
  • 官方仓库分为 src(核心库 13 个文件)、samples(示例)、tests(NUnit 近似测试)、perf(基准测试)四大块。
  • 我们写出了第一个参数化程序——一个带通孔与螺栓孔的法兰盘,实践了”参数化、Y 轴默认朝向、不可变链式、布尔挖孔”四个关键点。

下一章我们暂时放下 API,深入理解 CSG 与 BSP 树的核心原理——只有理解了内核如何工作,你才能写出稳健、高效、可预测的建模代码。


← 上一章 目录 下一章 →