第16章:实战案例——综合建模项目
前面各章已系统讲解 Elements 的几何系统、建筑元素、CSG 布尔运算、材质渲染、序列化互操作、空间搜索与 MEP 机电系统。本章把这些知识整合为一个完整的实战项目:从零构建一座两层小型办公楼。
本项目涵盖 BIM 建模的全流程——结构框架、建筑围护、楼梯、材质、空间定义、机电管线示意,以及多格式导出。完整的项目代码按专业模块拆分,结构清晰,可直接作为实际项目的起点。
16.1 项目需求
16.1.1 建筑概况
| 项目 | 参数 |
|---|---|
| 建筑类型 | 两层小型办公楼 |
| 平面尺寸 | 18m × 12m(矩形平面) |
| 层高 | 首层 3.6m,二层 3.3m |
| 柱网 | 6m × 6m 网格(3 × 2 跨) |
| 总高度 | 7.5m(含 0.6m 女儿墙) |
16.1.2 需要创建的模型组件
- 结构框架:混凝土柱(400×400)、主梁(300×600)、次梁(250×400)、楼板(150mm 厚)
- 建筑围护:外墙(200mm 厚砌块墙)、内隔墙(100mm 厚轻质隔墙)、幕墙面板(首层南立面)
- 楼梯:双跑楼梯,位于建筑北侧中部
- 屋顶:平屋顶 + 女儿墙
- 门窗开洞:外墙门洞(首层)、窗洞(各层)
- 材质:混凝土、钢材、玻璃、砌块、木材
- 空间定义:办公室、走廊、卫生间、楼梯间
- 机电示意:给排水立管与水平支管
16.2 项目架构设计
在正式编码之前,先设计项目的代码组织方式。按专业领域拆分,每个模块负责一组相关元素的创建:
OfficeBuilding/
├── Program.cs 入口:组装模型并导出
├── ModelBuilder.cs 模型构建器(协调各模块)
├── Structural/
│ ├── StructuralFrame.cs 柱网、梁、楼板
│ └── StairsBuilder.cs 楼梯(梯段、平台、栏杆)
├── Architectural/
│ ├── WallsBuilder.cs 内外墙
│ ├── CurtainWallBuilder.cs 幕墙面板
│ └── OpeningsBuilder.cs 门窗开洞
├── MEP/
│ └── PlumbingLayout.cs 给排水示意管线
├── Materials/
│ └── MaterialLibrary.cs 材质库(单例)
├── Spaces/
│ └── SpaceLayout.cs 房间空间定义
└── Export/
└── ExportPipeline.cs 多格式导出(glTF/IFC/SVG)
16.2.1 ModelBuilder:核心协调器
ModelBuilder 是整个项目的入口协调器,负责按正确顺序调用各模块,以及管理共享的 Model 实例和全局参数:
public class ModelBuilder
{
public Model Model { get; } = new Model();
public BuildingParams Params { get; }
public ModelBuilder()
{
Params = new BuildingParams
{
LengthX = 18.0,
LengthY = 12.0,
GridSpacingX = 6.0,
GridSpacingY = 6.0,
GroundFloorHeight = 3.6,
UpperFloorHeight = 3.3,
ParapetHeight = 0.6,
ColumnSize = (0.4, 0.4),
MainBeamSize = (0.3, 0.6),
SecondaryBeamSize = (0.25, 0.4),
SlabThickness = 0.15,
ExteriorWallThickness = 0.2,
InteriorWallThickness = 0.1,
};
}
public void Build()
{
var frame = new StructuralFrame(this);
frame.CreateColumns();
frame.CreateBeams();
frame.CreateSlabs();
var walls = new WallsBuilder(this);
walls.CreateExteriorWalls();
walls.CreateInteriorWalls();
var curtainWall = new CurtainWallBuilder(this);
curtainWall.BuildFacade();
var openings = new OpeningsBuilder(this);
openings.CreateOpenings();
var stairs = new StairsBuilder(this);
stairs.BuildStairs();
var spaces = new SpaceLayout(this);
spaces.DefineSpaces();
var plumbing = new PlumbingLayout(this);
plumbing.CreateRisers();
}
}
public class BuildingParams
{
public double LengthX { get; set; }
public double LengthY { get; set; }
public double GridSpacingX { get; set; }
public double GridSpacingY { get; set; }
public double GroundFloorHeight { get; set; }
public double UpperFloorHeight { get; set; }
public double ParapetHeight { get; set; }
public (double w, double h) ColumnSize { get; set; }
public (double w, double h) MainBeamSize { get; set; }
public (double w, double h) SecondaryBeamSize { get; set; }
public double SlabThickness { get; set; }
public double ExteriorWallThickness { get; set; }
public double InteriorWallThickness { get; set; }
public double TotalHeight => GroundFloorHeight + UpperFloorHeight + ParapetHeight;
}
16.2.2 入口程序
// Program.cs
class Program
{
static void Main(string[] args)
{
var builder = new ModelBuilder();
builder.Build();
Console.WriteLine($"模型构建完成,共 {builder.Model.Elements.Count} 个元素");
var exporter = new ExportPipeline(builder.Model);
exporter.ExportAll("output/OfficeBuilding");
}
}
16.3 结构框架创建
结构框架是建筑的骨架。使用 Grid2d 定义柱网定位,然后依次创建柱、梁和楼板。
16.3.1 柱网定义与柱创建
Grid2d 提供规则的网格定位系统,非常适合柱网的参数化定义:
using Elements;
using Elements.Geometry;
using Elements.Spatial;
public class StructuralFrame
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public StructuralFrame(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void CreateColumns()
{
// 定义 X 方向网格:3 跨 × 6m = 18m
var grid1dX = new Grid1d(_p.LengthX);
grid1dX.SplitAtOffsets(new[]
{
_p.GridSpacingX,
_p.GridSpacingX * 2,
_p.GridSpacingX * 3
});
// 定义 Y 方向网格:2 跨 × 6m = 12m
var grid1dY = new Grid1d(_p.LengthY);
grid1dY.SplitAtOffsets(new[]
{
_p.GridSpacingY,
_p.GridSpacingY * 2
});
// 组合为二维网格
var grid2d = new Grid2d(grid1dX, grid1dY);
var columnProfile = new Profile(
Polygon.Rectangle(_p.ColumnSize.w, _p.ColumnSize.h)
);
var concrete = MaterialLibrary.Concrete;
// 在网格的每个节点处创建柱(共 4×3 = 12 根柱)
int colIndex = 1;
foreach (var cell in grid2d.GetCells())
{
// 取单元格的左下角作为柱位
var bottomLeft = cell.GetCellGeometry()[0].Start;
// 首层柱:地面到二层楼板底
var gfColumn = new Column(
bottomLeft,
_p.GroundFloorHeight,
columnProfile
)
{
Name = $"柱-GF-{colIndex}",
Material = concrete
};
// 二层柱:二层楼板面到屋顶
var ufColumn = new Column(
new Vector3(bottomLeft.X, bottomLeft.Y, _p.GroundFloorHeight),
_p.UpperFloorHeight,
columnProfile
)
{
Name = $"柱-UF-{colIndex}",
Material = concrete
};
_model.AddElement(gfColumn);
_model.AddElement(ufColumn);
colIndex++;
}
}
}
16.3.2 梁创建
梁沿柱网轴线布置。主梁沿 Y 方向(横向框架),次梁沿 X 方向:
public void CreateBeams()
{
var mainBeamProfile = new Profile(
Polygon.Rectangle(_p.MainBeamSize.w, _p.MainBeamSize.h)
);
var secBeamProfile = new Profile(
Polygon.Rectangle(_p.SecondaryBeamSize.w, _p.SecondaryBeamSize.h)
);
var steel = MaterialLibrary.Steel;
// 每层楼板标高处创建梁格
foreach (var elevation in new[] { _p.GroundFloorHeight, _p.GroundFloorHeight + _p.UpperFloorHeight })
{
// Y 方向主梁:沿 X 方向的每条柱线布置
for (double x = 0; x <= _p.LengthX; x += _p.GridSpacingX)
{
var line = new Line(
new Vector3(x, 0, elevation),
new Vector3(x, _p.LengthY, elevation)
);
var beam = new Beam(line, mainBeamProfile)
{
Name = $"主梁-X{x}-Z{elevation}",
Material = steel
};
_model.AddElement(beam);
}
// X 方向次梁:沿 Y 方向的每条柱线布置
for (double y = 0; y <= _p.LengthY; y += _p.GridSpacingY)
{
var line = new Line(
new Vector3(0, y, elevation),
new Vector3(_p.LengthX, y, elevation)
);
var beam = new Beam(line, secBeamProfile)
{
Name = $"次梁-Y{y}-Z{elevation}",
Material = steel
};
_model.AddElement(beam);
}
}
}
16.3.3 楼板创建
楼板使用矩形轮廓,在每层梁顶标高处创建:
public void CreateSlabs()
{
var concrete = MaterialLibrary.Concrete;
var slabOutline = Polygon.Rectangle(_p.LengthX, _p.LengthY);
// 二层楼板(标高 3.6m 处)
var slab2f = new Floor(
slabOutline,
_p.SlabThickness,
new Transform(0, 0, _p.GroundFloorHeight)
)
{
Name = "二层楼板",
Material = concrete
};
// 屋顶楼板(标高 6.9m 处)
var roofSlab = new Floor(
slabOutline,
_p.SlabThickness,
new Transform(0, 0, _p.GroundFloorHeight + _p.UpperFloorHeight)
)
{
Name = "屋顶楼板",
Material = concrete
};
_model.AddElement(slab2f);
_model.AddElement(roofSlab);
}
Elements 中楼板通过
Transform参数控制 Z 向标高,轮廓的 XY 坐标自动转换到指定位置。
16.4 建筑围护
建筑围护包括外墙、内隔墙、南立面幕墙,以及门洞和窗洞。开洞通过 Representation.SolidOperations 中添加 IsVoid = true 的 Extrude 来实现。
16.4.1 外墙
外墙沿建筑外轮廓布置,使用砌块材质,厚度 200mm:
using Elements;
using Elements.Geometry;
public class WallsBuilder
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public WallsBuilder(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void CreateExteriorWalls()
{
var extProfile = new Profile(
Polygon.Rectangle(_p.ExteriorWallThickness, _p.TotalHeight)
);
var masonry = MaterialLibrary.Masonry;
// 四面外墙:从原点逆时针围合
var exteriorLines = new[]
{
new Line(new Vector3(0, 0, 0), new Vector3(_p.LengthX, 0, 0)), // 南
new Line(new Vector3(_p.LengthX, 0, 0), new Vector3(_p.LengthX, _p.LengthY, 0)), // 东
new Line(new Vector3(_p.LengthX, _p.LengthY, 0), new Vector3(0, _p.LengthY, 0)), // 北
new Line(new Vector3(0, _p.LengthY, 0), new Vector3(0, 0, 0)), // 西
};
string[] wallNames = { "外墙-南", "外墙-东", "外墙-北", "外墙-西" };
for (int i = 0; i < exteriorLines.Length; i++)
{
var wall = new Wall(exteriorLines[i], extProfile, _p.TotalHeight)
{
Name = wallNames[i],
Material = masonry
};
_model.AddElement(wall);
}
}
}
16.4.2 内隔墙
内隔墙分隔办公空间,厚度 100mm。在 6m 柱距处沿 Y 方向、以及沿走廊中线沿 X 方向布置:
public void CreateInteriorWalls()
{
var intProfile = new Profile(
Polygon.Rectangle(_p.InteriorWallThickness, _p.TotalHeight)
);
var gypsum = MaterialLibrary.Gypsum;
// 横向隔墙:在 X = 6m 和 X = 12m 处
foreach (var x in new[] { _p.GridSpacingX, _p.GridSpacingX * 2 })
{
var line = new Line(
new Vector3(x, 0, 0),
new Vector3(x, _p.LengthY, 0)
);
var wall = new Wall(line, intProfile, _p.TotalHeight)
{
Name = $"内墙-横-X{x}",
Material = gypsum
};
_model.AddElement(wall);
}
// 纵向走廊隔墙:Y = 6m 处(在建筑中部)
var corridorWall = new Wall(
new Line(
new Vector3(0, _p.GridSpacingY, 0),
new Vector3(_p.LengthX, _p.GridSpacingY, 0)
),
intProfile,
_p.TotalHeight
)
{
Name = "内墙-走廊",
Material = gypsum
};
_model.AddElement(corridorWall);
}
16.4.3 幕墙面板
南立面使用幕墙面板替代实墙。Panel 元素适合创建玻璃面板、幕墙单元等平面构件:
using Elements;
using Elements.Geometry;
public class CurtainWallBuilder
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public CurtainWallBuilder(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void BuildFacade()
{
var glass = MaterialLibrary.Glass;
var panelWidth = 1.5; // 每块面板宽 1.5m
var panelCount = (int)(_p.LengthX / panelWidth);
// 首层幕墙面板:从 Y=0 到 Y=0(南立面外墙位置)
for (int i = 0; i < panelCount; i++)
{
double x = i * panelWidth;
var panelOutline = Polygon.Rectangle(panelWidth, _p.GroundFloorHeight);
// Panel 用矩形轮廓在 3D 空间中的平面放置
var panel = new Panel(
panelOutline,
new Transform(x, 0, 0, 0, 0, 0) // 放在南立面位置
)
{
Name = $"幕墙面板-首层-{i + 1}",
Material = glass
};
_model.AddElement(panel);
}
}
}
16.4.4 门洞与窗洞
门洞和窗洞通过 Opening 元素实现。Opening 是一个挖空元素,其 Representation.SolidOperations 中包含 IsVoid = true 的挤出操作:
using Elements;
using Elements.Geometry;
using Elements.Geometry.Solids;
public class OpeningsBuilder
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public OpeningsBuilder(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void CreateOpenings()
{
CreateDoorOpenings();
CreateWindowOpenings();
}
private void CreateDoorOpenings()
{
// 首层南立面主入口(双开门,宽 1.8m × 高 2.4m)
AddWallOpening(
"门洞-主入口",
_p.LengthX / 2 - 0.9, 0, 0.1, // X 中心偏移,Y 在南墙,Z 底部
1.8, 2.4 // 宽 × 高
);
// 首层北立面后勤入口
AddWallOpening(
"门洞-后门",
_p.LengthX / 2 - 0.9, _p.LengthY - 0.1, 0.1,
1.8, 2.4
);
// 二层走廊两端的门洞
AddWallOpening(
"门洞-东入口",
_p.LengthX - 0.1, _p.LengthY / 2 - 0.45, 3.6,
0.9, 2.1
);
}
private void CreateWindowOpenings()
{
var windowWidth = 1.5;
var windowHeight = 1.5;
// 首层窗洞(南立面主入口两侧,各一扇)
AddWallOpening("窗洞-GF-南-1", 1.5, 0.1, 1.2, windowWidth, windowHeight);
AddWallOpening("窗洞-GF-南-2", _p.LengthX - 3.0, 0.1, 1.2, windowWidth, windowHeight);
// 东立面窗洞(两层各两扇)
for (int floor = 0; floor < 2; floor++)
{
double zBase = floor == 0 ? 1.2 : _p.GroundFloorHeight + 1.2;
string label = floor == 0 ? "GF" : "UF";
AddWallOpening(
$"窗洞-{label}-东-1",
_p.LengthX - 0.1, 2.0, zBase,
windowWidth, windowHeight
);
AddWallOpening(
$"窗洞-{label}-东-2",
_p.LengthX - 0.1, _p.LengthY - 3.5, zBase,
windowWidth, windowHeight
);
}
// 北立面窗洞
for (int floor = 0; floor < 2; floor++)
{
double zBase = floor == 0 ? 1.2 : _p.GroundFloorHeight + 1.2;
string label = floor == 0 ? "GF" : "UF";
AddWallOpening(
$"窗洞-{label}-北-1",
3.0, _p.LengthY - 0.1, zBase,
windowWidth, windowHeight
);
AddWallOpening(
$"窗洞-{label}-北-2",
_p.LengthX - 4.5, _p.LengthY - 0.1, zBase,
windowWidth, windowHeight
);
}
}
private void AddWallOpening(
string name,
double x, double y, double z,
double width, double height)
{
var openingProfile = new Profile(Polygon.Rectangle(width, height));
// 开洞方向:沿墙厚方向(此处统一用 Y 轴方向做穿墙开洞,实际项目中需根据墙的朝向调整方向)
var opening = new Opening(
openingProfile,
x, y, z,
width, height
)
{
Name = name
};
// 在 Opening 的表示中添加 IsVoid 挤出
var voidExtrude = new Extrude(
openingProfile,
width, // 挤出深度 = 穿透墙厚
Vector3.XAxis, // 沿 X 方向穿透
true // IsVoid = true
);
opening.Representation.SolidOperations.Add(voidExtrude);
_model.AddElement(opening);
}
}
开洞原理:
Opening元素包含IsVoid = true的SolidOperation。当Model执行 CSG 布尔运算时,挖空操作会从与之相交的所有实体元素中减去,从而在墙上形成真实的洞口。Opening的尺寸应略大于门窗外框,以确保完全穿透墙体。
16.5 楼梯
双跑楼梯由梯段、中间平台和栏杆组成。使用 Beam 表示梯段斜梁,Extrude 创建踏步和平台板:
using Elements;
using Elements.Geometry;
using Elements.Geometry.Solids;
public class StairsBuilder
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
// 楼梯参数
private const double StairWidth = 1.5;
private const double RiserHeight = 0.15;
private const double TreadDepth = 0.28;
private const double LandingLength = 1.8;
private const int StepsPerFlight = 12;
public StairsBuilder(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void BuildStairs()
{
// 楼梯位于建筑北侧中部,X 方向居中
double stairStartX = _p.LengthX / 2 - StairWidth / 2;
double stairY = 2.0; // 距北墙 2m
BuildFlight(stairStartX, stairY, 0); // 首层 → 中间平台
BuildIntermediateLanding(stairStartX, stairY); // 中间平台(标高 1.8m)
BuildFlight(stairStartX, stairY + LandingLength, 1); // 中间平台 → 二层
BuildLanding(stairStartX, stairY, _p.GroundFloorHeight); // 二层楼面平台
BuildRailings(stairStartX, stairY);
}
private void BuildFlight(double x, double y, int direction)
{
var steel = MaterialLibrary.Steel;
double flightLength = StepsPerFlight * TreadDepth;
// Y 方向起始位置随 direction 偏移
double yStart = direction == 0 ? y : y + LandingLength;
double zStart = direction == 0 ? 0 : StepsPerFlight * RiserHeight;
// 梯段斜梁(矩形截面钢梁)
var beamProfile = new Profile(Polygon.Rectangle(0.15, 0.35));
var beamLine = new Line(
new Vector3(x, yStart, zStart),
new Vector3(x, yStart + flightLength, zStart + StepsPerFlight * RiserHeight)
);
var stringer = new Beam(beamLine, beamProfile)
{
Name = $"梯段斜梁-{(direction == 0 ? "下" : "上")}",
Material = steel
};
_model.AddElement(stringer);
// 逐级踏步
for (int i = 0; i < StepsPerFlight; i++)
{
double stepZ = zStart + i * RiserHeight;
double stepY = yStart + i * TreadDepth;
var treadProfile = new Profile(Polygon.Rectangle(StairWidth, TreadDepth));
var step = new Floor(
Polygon.Rectangle(StairWidth, TreadDepth),
RiserHeight,
new Transform(x, stepY, stepZ)
)
{
Name = $"踏步-{(direction == 0 ? "下" : "上")}-{i + 1}",
Material = MaterialLibrary.Concrete
};
_model.AddElement(step);
}
}
private void BuildIntermediateLanding(double x, double y)
{
double z = StepsPerFlight * RiserHeight; // 中间平台标高
var landing = new Floor(
Polygon.Rectangle(StairWidth, LandingLength),
0.15,
new Transform(x, y + StepsPerFlight * TreadDepth, z)
)
{
Name = "中间平台",
Material = MaterialLibrary.Concrete
};
_model.AddElement(landing);
}
private void BuildLanding(double x, double y, double elevation)
{
var landing = new Floor(
Polygon.Rectangle(StairWidth, LandingLength),
0.15,
new Transform(x, y + StepsPerFlight * TreadDepth + LandingLength, elevation)
)
{
Name = "二层平台",
Material = MaterialLibrary.Concrete
};
_model.AddElement(landing);
}
private void BuildRailings(double x, double y)
{
var steel = MaterialLibrary.Steel;
var railingHeight = 0.9;
var totalRun = 2 * StepsPerFlight * TreadDepth + LandingLength;
// 立面栏杆:用细柱表示
var postProfile = new Profile(Polygon.Rectangle(0.04, 0.04));
double zBase = 0;
for (double dist = 0; dist <= totalRun; dist += TreadDepth)
{
// 判断是梯段还是平台段
bool isLowerFlight = dist <= StepsPerFlight * TreadDepth;
bool isLanding = dist > StepsPerFlight * TreadDepth &&
dist <= StepsPerFlight * TreadDepth + LandingLength;
double postZ;
if (isLowerFlight)
{
postZ = (dist / TreadDepth) * RiserHeight;
}
else if (isLanding)
{
postZ = StepsPerFlight * RiserHeight;
}
else
{
double upperDist = dist - StepsPerFlight * TreadDepth - LandingLength;
postZ = StepsPerFlight * RiserHeight + (upperDist / TreadDepth) * RiserHeight;
}
// 左侧栏杆柱
var leftPost = new Column(
new Vector3(x, y + dist, postZ),
railingHeight,
postProfile
)
{
Name = $"栏杆柱-L-{dist:F1}",
Material = steel
};
_model.AddElement(leftPost);
// 右侧栏杆柱
var rightPost = new Column(
new Vector3(x + StairWidth, y + dist, postZ),
railingHeight,
postProfile
)
{
Name = $"栏杆柱-R-{dist:F1}",
Material = steel
};
_model.AddElement(rightPost);
}
}
}
16.6 材质赋予
将材质集中管理,各模块通过 MaterialLibrary 统一引用,确保一致性和可维护性:
using Elements;
using Elements.Geometry;
public static class MaterialLibrary
{
public static Material Concrete { get; } = new Material("混凝土")
{
Color = new Color(0.7, 0.7, 0.7, 1.0), // 浅灰色
SpecularFactor = 0.1,
GlossinessFactor = 0.2
};
public static Material Steel { get; } = new Material("钢材")
{
Color = new Color(0.55, 0.55, 0.6, 1.0), // 钢灰色
SpecularFactor = 0.5,
GlossinessFactor = 0.6
};
public static Material Glass { get; } = new Material("玻璃")
{
Color = new Color(0.6, 0.8, 0.9, 0.4), // 半透明浅蓝
SpecularFactor = 0.9,
GlossinessFactor = 0.95
};
public static Material Masonry { get; } = new Material("砌块")
{
Color = new Color(0.85, 0.78, 0.65, 1.0), // 暖灰色砌块
SpecularFactor = 0.05,
GlossinessFactor = 0.1
};
public static Material Gypsum { get; } = new Material("石膏板")
{
Color = new Color(0.95, 0.95, 0.93, 1.0), // 米白色
SpecularFactor = 0.02,
GlossinessFactor = 0.05
};
public static Material Wood { get; } = new Material("木材")
{
Color = new Color(0.55, 0.35, 0.15, 1.0), // 棕色木纹
SpecularFactor = 0.15,
GlossinessFactor = 0.3
};
public static Material Copper { get; } = new Material("铜管")
{
Color = new Color(0.8, 0.5, 0.3, 1.0), // 铜色
SpecularFactor = 0.4,
GlossinessFactor = 0.5
};
}
材质命名规范:使用中文名称可读性好,在导出 IFC 时映射到标准材质分类。
Material元素的Id自动生成 GUID,在多个元素间共享同一个Material实例即可。
16.7 空间定义
建筑空间(房间)用 Space 元素定义。每个 Space 包含一个轮廓多边形、高度和位置信息:
using Elements;
using Elements.Geometry;
public class SpaceLayout
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public SpaceLayout(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void DefineSpaces()
{
// 首层空间(沿 Y 方向分为:南侧办公区 + 走廊 + 北侧办公区)
DefineGroundFloorSpaces();
// 二层空间(相同布局)
DefineUpperFloorSpaces();
// 楼梯间(贯穿两层)
DefineStairwell();
}
private void DefineGroundFloorSpaces()
{
double z = 0;
// 南侧办公区:Y = 0 ~ 6m,整宽 18m
AddOfficeSpace("GF-南办公室", 0, 0, _p.LengthX, _p.GridSpacingY, z, _p.GroundFloorHeight);
// 走廊:Y = 6m ~ 8m(2m 宽走廊)
AddOfficeSpace("GF-走廊", 0, _p.GridSpacingY, _p.LengthX, 2.0, z, _p.GroundFloorHeight);
// 北侧办公区:Y = 8m ~ 12m
AddOfficeSpace("GF-北办公室", 0, _p.GridSpacingY + 2.0, _p.LengthX, _p.LengthY - _p.GridSpacingY - 2.0, z, _p.GroundFloorHeight);
// 卫生间(东北角,2m × 3m)
AddOfficeSpace("GF-卫生间", _p.LengthX - 3.0, _p.LengthY - 2.0, 3.0, 2.0, z, _p.GroundFloorHeight);
}
private void DefineUpperFloorSpaces()
{
double z = _p.GroundFloorHeight;
AddOfficeSpace("UF-南办公室", 0, 0, _p.LengthX, _p.GridSpacingY, z, _p.UpperFloorHeight);
AddOfficeSpace("UF-走廊", 0, _p.GridSpacingY, _p.LengthX, 2.0, z, _p.UpperFloorHeight);
AddOfficeSpace("UF-北办公室", 0, _p.GridSpacingY + 2.0, _p.LengthX, _p.LengthY - _p.GridSpacingY - 2.0, z, _p.UpperFloorHeight);
AddOfficeSpace("UF-卫生间", _p.LengthX - 3.0, _p.LengthY - 2.0, 3.0, 2.0, z, _p.UpperFloorHeight);
}
private void DefineStairwell()
{
double stairX = _p.LengthX / 2 - 0.75;
double stairY = 2.0;
var profile = new Profile(Polygon.Rectangle(1.5, 1.8));
var stairwell = new Space(
profile,
0, // 从 ±0.000 开始
_p.TotalHeight, // 贯穿全部高度
_p.GroundFloorHeight, // 底面标高
new Transform(stairX, stairY, 0)
)
{
Name = "楼梯间",
AdditionalProperties = new Dictionary<string, object>
{
{ "Area", 1.5 * 1.8 },
{ "Category", "VerticalCirculation" }
}
};
_model.AddElement(stairwell);
}
private void AddOfficeSpace(string name,
double x, double y, double width, double depth,
double z, double height)
{
var profile = new Profile(Polygon.Rectangle(width, depth));
var space = new Space(
profile,
0,
height,
z,
new Transform(x, y, 0)
)
{
Name = name,
AdditionalProperties = new Dictionary<string, object>
{
{ "Area", width * depth },
{ "Category", name.Contains("走廊") ? "Corridor" : "Office" }
}
};
_model.AddElement(space);
// 输出面积信息
Console.WriteLine($" {name}: {width * depth:F1} m²");
}
}
运行程序后,控制台输出各空间面积:
GF-南办公室: 108.0 m²
GF-走廊: 36.0 m²
GF-北办公室: 72.0 m²
GF-卫生间: 6.0 m²
...
16.8 机电管线示意
Elements 的 Elements.MEP 包提供了管路配件系统。本节用 MEP 中的 Fitting 概念创建给排水示意管线——立管与水平支管:
using Elements;
using Elements.Geometry;
using Elements.Geometry.Solids;
public class PlumbingLayout
{
private readonly ModelBuilder _b;
private readonly Model _model;
private readonly BuildingParams _p;
public PlumbingLayout(ModelBuilder builder)
{
_b = builder;
_model = builder.Model;
_p = builder.Params;
}
public void CreateRisers()
{
var copper = MaterialLibrary.Copper;
var pipeRadius = 0.05; // DN100 管径
// 卫生间位置:东北角
double toiletX = _p.LengthX - 1.5;
double toiletY = _p.LengthY - 1.0;
// 给水立管(从 ±0.000 到屋顶 +1m)
CreatePipe(
"给水立管",
new Vector3(toiletX, toiletY, 0),
new Vector3(toiletX, toiletY, _p.TotalHeight + 1.0),
pipeRadius,
copper
);
// 排水立管
CreatePipe(
"排水立管",
new Vector3(toiletX + 0.3, toiletY, 0),
new Vector3(toiletX + 0.3, toiletY, _p.TotalHeight + 1.0),
pipeRadius * 1.2,
copper
);
// 首层水平支管(从立管到卫生间洁具位置)
CreatePipe(
"给水支管-GF",
new Vector3(toiletX, toiletY, 0.5),
new Vector3(toiletX - 1.0, toiletY, 0.5),
pipeRadius * 0.6,
copper
);
// 二层水平支管
CreatePipe(
"给水支管-UF",
new Vector3(toiletX, toiletY, _p.GroundFloorHeight + 0.5),
new Vector3(toiletX - 1.0, toiletY, _p.GroundFloorHeight + 0.5),
pipeRadius * 0.6,
copper
);
}
private void CreatePipe(string name, Vector3 start, Vector3 end,
double radius, Material material)
{
var pipeLine = new Line(start, end);
var pipeProfile = new Profile(
new Circle(Vector3.Origin, radius).ToPolygon(24)
);
// 用 Beam 表示管道(圆柱截面沿线段拉伸)
var pipe = new Beam(pipeLine, pipeProfile)
{
Name = name,
Material = material
};
_model.AddElement(pipe);
}
}
完整 MEP 设计(弯头、三通、变径等)需使用
Elements.MEP包中的FittingTree、Fitting和AdaptiveGrid。上述示意代码用圆形截面Beam近似表示管道,对可视化示意已足够。
16.9 序列化输出
完成模型构建后,将其导出为三种格式,满足不同场景的需求:
using Elements;
using Elements.Serialization.JSON;
using Elements.Serialization.glTF;
using Elements.Serialization.IFC;
using Elements.Serialization.SVG;
public class ExportPipeline
{
private readonly Model _model;
private readonly string _basePath;
public ExportPipeline(Model model)
{
_model = model;
}
public void ExportAll(string basePath)
{
_basePath = basePath;
// 确保输出目录存在
Directory.CreateDirectory(basePath);
ExportToJson();
ExportToGlTF();
ExportToIFC();
ExportSectionToSVG();
}
private void ExportToJson()
{
string json = _model.ToJson();
string path = Path.Combine(_basePath, "OfficeBuilding.json");
File.WriteAllText(path, json);
Console.WriteLine($"[导出] JSON → {path} ({json.Length} 字符)");
}
private void ExportToGlTF()
{
string path = Path.Combine(_basePath, "OfficeBuilding.glb");
_model.ToGlTF(path);
Console.WriteLine($"[导出] glTF/GLB → {path}");
}
private void ExportToIFC()
{
string path = Path.Combine(_basePath, "OfficeBuilding.ifc");
_model.ToIFC(path);
Console.WriteLine($"[导出] IFC → {path}");
}
private void ExportSectionToSVG()
{
// 在 Y = 6m 处(走廊中线)生成剖面
var sectionPlane = new Plane(
new Vector3(0, 6.0, 0), // 平面上的点
Vector3.YAxis // 法线方向(Y 轴为法线 → 剖面垂直于 Y)
);
_model.Intersect(
sectionPlane,
out var polygons,
out var beyond,
out var lines
);
// 将剖面多边形导出为 SVG(需 Elements.Serialization.SVG 包)
string path = Path.Combine(_basePath, "OfficeBuilding_Section.svg");
var svgModel = new Model();
// ... 将剖面结果添加到 svgModel 中 ...
// svgModel.ToSVG(path);
Console.WriteLine($"[导出] 剖面 → {path} (多边形: {polygons.Count}, 线段: {lines.Count})");
}
}
16.9.1 导出格式对比
| 格式 | 用途 | 适用场景 |
|---|---|---|
| JSON | 存档与版本控制 | Git diff、参数化变体管理、CI/CD 验证 |
| glTF / GLB | Web 3D 预览 | 浏览器在线查看、Three.js/Babylon.js 集成 |
| IFC 2x3 / IFC 4 | BIM 协作 | 交付给 Revit/ArchiCAD/Tekla 深化设计 |
| SVG | 图纸输出 | 施工图剖面、技术文档插图 |
16.10 代码结构总结与最佳实践
16.10.1 项目结构回顾
OfficeBuilding/
├── Program.cs —— 入口,编排执行顺序
├── ModelBuilder.cs —— 协调器,持有 Model 和参数
├── BuildingParams.cs —— 全局参数(单一事实来源)
├── Structural/
│ ├── StructuralFrame.cs —— 柱网、梁、楼板
│ └── StairsBuilder.cs —— 梯段、平台、栏杆
├── Architectural/
│ ├── WallsBuilder.cs —— 外墙、内隔墙
│ ├── CurtainWallBuilder.cs —— 幕墙面板
│ └── OpeningsBuilder.cs —— 门洞、窗洞
├── MEP/
│ └── PlumbingLayout.cs —— 给排水管线
├── Materials/
│ └── MaterialLibrary.cs —— 材质单例
├── Spaces/
│ └── SpaceLayout.cs —— 房间空间定义
└── Export/
└── ExportPipeline.cs —— 多格式导出
16.10.2 从本项目学到的关键模式
-
参数集中管理:
BuildingParams类聚合了所有建筑参数(柱网间距、层高、构件尺寸等),避免了魔法数字分散在代码各处。修改设计只需改一个地方。 -
按专业领域拆分:结构、建筑、机电各自独立模块,互不干扰。每个模块通过构造函数接收
ModelBuilder引用,共享Model和Params。 -
材质集中定义:
MaterialLibrary确保所有构件使用一致的颜色和表面属性,修改材质时只需改一处。 -
Grid2d 驱动定位:柱网使用
Grid2d而非硬编码坐标,当柱距改变时所有柱、梁自动适应。 -
IsVoid 开洞模式:门洞和窗洞通过在
Representation.SolidOperations中添加IsVoid = true的Extrude实现,利用 CSG 布尔运算自动从墙体中减去。 -
ElementDefinition + ElementInstance:重复出现的构件(如相同尺寸的柱)可以定义为一次,通过
CreateInstance在不同位置实例化,减少数据冗余。 -
先结构后建筑再洞口:建模顺序很重要——先建结构框架,再加围护墙体和幕墙,最后放置开洞(因为开洞是对已有墙体的布尔减法操作)。
-
Space 用于面积统计:用
Space元素定义房间边界并自动计算面积,为面积指标、能耗分析提供数据基础。 -
导出与建模分离:
ExportPipeline独立于建模逻辑,支持在不修改建模代码的情况下增加新的导出格式。 -
代码即文档:
Name属性赋予每个元素有意义的名称(如"柱-GF-3"、"外墙-南"),在 IFC 导出和 JSON 查看时一眼可知其用途。
16.10.3 扩展建议
本项目可作为以下方向的起点:
- 参数化变体生成:将
BuildingParams的不同取值作为输入,生成多个方案变体用于方案比选 - 结构分析集成:将结构框架导出为 IFC,导入 SAP2000 / ETABS 进行结构计算
- 能耗模拟:利用
Space和围护结构信息,输出 gbXML 格式进行能耗分析 - 4D 施工模拟:为每个元素添加时间属性(开始/结束日期),按施工进度分组导出
- Web 审查平台:将 GLB 模型嵌入网页,配合 Three.js 实现剖切、测量、标注等审查功能
16.10.4 各章知识在本项目中的对应关系
| 章节 | 知识点 | 本项目中应用 |
|---|---|---|
| 第02章 | Element / Model / 类型体系 | 所有元素的基类、Model 容器、元素生命周期 |
| 第03-04章 | 向量、曲线、多边形、Profile | 柱网坐标、墙定位线、截面轮廓定义 |
| 第05章 | Wall / Beam / Column / Floor | 结构框架与建筑围护的核心构件 |
| 第08章 | CSG 布尔运算 / IsVoid | 通过 Opening 的 IsVoid 挤出在墙上开洞 |
| 第09章 | Material / Color | MaterialLibrary 统一管理所有材质 |
| 第10章 | 序列化:JSON / glTF / IFC / SVG | ExportPipeline 多格式导出 |
| 第11-12章 | Grid2d / 空间搜索 | 柱网定位、剖面查询 |
| 第13章 | MEP 管线 | 给排水立管与水平支管示意 |
| 第14章 | 组件化生成 | ElementDefinition + CreateInstance 减少冗余 |
| 第15章 | Schema 驱动开发 | BuildingParams 参数体系可 Schema 化 |
至此,Elements 全套教程已完结。从第一章的”Hello Wall”到本章的综合办公楼,你已掌握了用 C# 代码创建、管理、导出 BIM 模型的完整能力。Elements 的真正力量在于将建筑模型从静态文件转变为可计算的代码资产——自动化、版本化、可验证、可复用。希望本教程能成为你进入”编程式建筑设计”世界的可靠起点。