znlgis 博客

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

第14章:组件化生成

第 02 章介绍了 ElementInstanceIsElementDefinition 配合实现的”定义-实例”模式——定义一个元素,然后在不同位置创建实例。这种模式对单一元素(如标准梁、标准柱)效果很好,但建筑项目中有大量复杂需求:如何定义一个由多种元素组合而成的可重用单元,并将其自适应地放置到不同尺寸、不同形状的区域内?

这就是 Elements.Components 要解决的问题。它提供了一个组件化生成框架,将”元素组合 + 放置规则”封装为可重用的 ComponentDefinition,然后通过锚点系统将其实例化到任意目标边界中。

14.1 组件化概念

Elements.Components 的核心模型由三个层次构成:

ComponentDefinition(组件定义)
    ├── ReferenceAnchors(参考锚点)  ← 定义阶段的"原型坐标系"
    ├── Rules(放置规则列表)         ← 决定如何生成子元素
    │   ├── PositionPlacementRule      ← 固定位置放置
    │   ├── ArrayPlacementRule         ← 阵列排列
    │   ├── GridPlacementRule          ← 网格排列
    │   ├── PolylinePlacementRule      ← 折线变形
    │   ├── SizeBasedPlacementRule     ← 尺寸驱动选型
    │   └── ComponentPlacementRule     ← 嵌套子组件
    └── OrientationGuide(方向引导)   ← 计算得出的旋转参考

ComponentInstance(组件实例)
    └── Instances(子元素列表)        ← Instantiate() 的输出

关键类定义:

namespace Elements.Components
{
    public class ComponentDefinition : Element
    {
        // 内部规则列表
        internal IList<IComponentPlacementRule> Rules { get; set; }

        // 参考锚点——定义阶段使用的"模板坐标"
        internal IList<Vector3> ReferenceAnchors { get; set; }

        // 锚点位移——Instantiate 时根据目标锚点计算得出
        internal IList<Vector3> AnchorDisplacements { get; set; }

        // 方向引导——Instantiate 时根据锚点计算得出的旋转参考
        internal Transform OrientationGuide { get; set; }

        // 核心方法:根据目标锚点创建组件实例
        public ComponentInstance Instantiate(
            IList<Vector3> anchors,
            Transform orientationGuide = null
        );
    }
}

14.1.1 锚点机制

锚点(Anchor)是连接”定义”与”实例”的桥梁。典型的工作流程是:

  1. 选择一个矩形边界作为参考区域,取其 5 个关键点作为参考锚点(4 个角 + 中心点)
  2. 在参考区域内排布各种元素,并记录每个元素”附着”在哪个锚点上
  3. 当给定一组新的目标锚点(例如更大、更小、旋转或扭曲的矩形),Instantiate 方法会:
    • 计算参考锚点与目标锚点之间的位移向量
    • 根据前两个锚点的方向变化计算旋转角度(OrientationGuide)
    • 将每个规则中的元素按对应锚点变换到目标位置

锚点的数量、顺序在定义和实例化阶段必须一致

14.2 ComponentDefinition 详解

14.2.1 构造函数

public ComponentDefinition(
    IList<IComponentPlacementRule> rules,   // 放置规则
    IList<Vector3> referenceAnchors         // 参考锚点
) : base(Guid.NewGuid(), null)

参数说明:

参数 类型 说明
rules IList<IComponentPlacementRule> 一组放置规则,决定组件如何生成子元素
referenceAnchors IList<Vector3> 定义阶段使用的参考锚点,通常为边界的角点和中心点

14.2.2 Instantiate 方法内部机制

Instantiate(anchors) 的执行流程:

Instantiate(anchors)
├── 1. 计算 AnchorDisplacements
│     对每个参考锚点 i,找到对应的目标锚点,
│     计算 displacement[i] = targetAnchors[i] - referenceAnchors[i]
│
├── 2. 计算 OrientationGuide
│     如果提供了外部 orientationGuide → 使用它
│     否则,根据前两个锚点的方向变化计算旋转角度
│     angle = Angle_2D(referenceVector, targetVector)
│
├── 3. 遍历所有 Rules,逐个调用 rule.Instantiate(this)
│     ├── PositionPlacementRule → 根据锚点位移放置单个元素
│     ├── ArrayPlacementRule → 基于变换后的路径排列阵列
│     ├── GridPlacementRule → 基于变换后的区域创建网格
│     ├── SizeBasedPlacementRule → 选择适合当前尺寸的元素
│     ├── PolylinePlacementRule → 创建变换后的折线
│     └── ComponentPlacementRule → 递归实例化子组件
│
└── 4. 返回 ComponentInstance(包含所有生成的元素)

14.2.3 锚点映射策略

源码中的锚点映射逻辑如下(简化):

for (int i = 0; i < ReferenceAnchors.Count; i++)
{
    var referenceAnchor = ReferenceAnchors[i];
    var matchingAnchor = anchors[i % anchors.Count];
    AnchorDisplacements.Add(matchingAnchor - referenceAnchor);
}

每个参考锚点按索引一一对应到目标锚点。如果目标锚点数量不足,会循环使用(i % anchors.Count),但最佳实践是保持数量一致。

14.2.4 OrientationGuide 的自动计算

当不手动提供 orientationGuide 时,Instantiate 会根据前两个锚点的方向自动计算旋转:

var referenceVector = ReferenceAnchors[1] - ReferenceAnchors[0];
var targetVector = anchors[1] - anchors[0];
var angle = Angle_2D(referenceVector, targetVector) * 180 / Math.PI;
OrientationGuide = new Transform(Vector3.Origin, angle);

这意味着:改变前两个锚点的方向就能旋转整个组件

14.3 放置规则体系

所有放置规则都实现 IComponentPlacementRule 接口:

public interface IComponentPlacementRule
{
    string Name { get; set; }
    List<Element> Instantiate(ComponentDefinition definition);
}

其中大部分规则还实现了 ICurveBasedComponentPlacementRule,表示它们基于一条可变形曲线:

public interface ICurveBasedComponentPlacementRule : IComponentPlacementRule
{
    Polyline Curve { get; set; }
    IList<int> AnchorIndices { get; set; }       // 每个顶点对应哪个锚点
    IList<Vector3> AnchorDisplacements { get; set; }  // 每个顶点与锚点的偏移
}

14.3.1 PositionPlacementRule:指定位置放置

将单个元素放置在相对于某个锚点的固定偏移位置。

public class PositionPlacementRule : IComponentPlacementRule
{
    public int AnchorIndex { get; set; }           // 绑定到第几个锚点
    public Transform AnchorTransform { get; set; } // 相对于锚点的偏移变换
    public GeometricElement Definition { get; set; } // 要放置的元素

    public PositionPlacementRule(
        string name,
        int anchorIndex,
        GeometricElement definition,
        Transform anchorOffset = null
    );
}

实例化逻辑Instantiate 时,根据该锚点的位移量和方向变换,计算出元素的最终 Transform,然后用 CreateInstance 生成一个实例。

工厂方法 FromClosestPoints 是最常用的创建方式——自动将每个元素分配给距离最近的锚点,并计算偏移量:

// 自动将 red_cube 和 blue_cube 绑定到最近的锚点
var rules = PositionPlacementRule.FromClosestPoints(
    new[] { red_cube, blue_cube },
    referencePoints
);

14.3.2 ArrayPlacementRule:阵列放置

沿一条折线等距排列元素,适用于栏杆、路灯、管线支架等场景。

public class ArrayPlacementRule : ICurveBasedComponentPlacementRule
{
    public Polyline Curve { get; set; }                // 阵列路径
    public GeometricElement ElementDefinition { get; set; } // 要排列的元素
    public SpacingConfiguration SpacingRule { get; set; }   // 间距配置
    public bool IsClosed { get; set; }                  // 路径是否闭合

    public ArrayPlacementRule(
        GeometricElement definition,
        Polyline arrayPath,
        SpacingConfiguration spacingRule,
        IList<int> anchorIndices,
        IList<Vector3> anchorDisplacements,
        string name
    );
}

间距配置 SpacingConfiguration 支持三种模式:

public enum SpacingMode
{
    ByCount,             // 按数量:Value = 元素个数
    ByLength,            // 按固定间距:Value = 间距距离
    ByApproximateLength  // 按近似间距:Value = 目标间距(自动调整为整除)
}

public struct SpacingConfiguration
{
    public SpacingMode SpacingMode { get; set; }
    public double Value { get; set; }

    public SpacingConfiguration(SpacingMode mode, double value);
}

实例化逻辑:先通过 TransformPolyline 将参考路径变换到目标位置,然后使用 Grid1d 将路径按指定方式切分,在每个分隔点处创建元素实例。

14.3.3 GridPlacementRule:网格放置

在矩形区域内按网格排列元素,适用于幕墙面板、天花板、地板砖等场景。

public class GridPlacementRule : ICurveBasedComponentPlacementRule
{
    public Polyline Curve { get; set; }               // 网格区域(多边形)
    public GridCellDefinition CellDefinition { get; set; } // 单元格定义
    public Action<Grid2d> GridCreationRule { get; set; }   // 自定义网格划分
}

public struct GridCellDefinition
{
    public IEnumerable<GeometricElement> Elements { get; set; } // 放置在每格中的元素
    public double CellWidth { get; set; }     // 单元格宽度
    public double CellLength { get; set; }    // 单元格长度
}

实例化逻辑:先将边界多边形变换到目标位置,然后创建 Grid2d,按 GridCreationRule 划分网格。默认行为是 U 方向按 CellLength 划分,V 方向按 CellWidth 划分。最后在每个未裁剪的单元格中放置元素实例。

默认切片规则:

g.U.DivideByFixedLength(CellDefinition.CellLength, FixedDivisionMode.RemainderAtBothEnds);
g.V.DivideByFixedLength(CellDefinition.CellWidth, FixedDivisionMode.RemainderAtBothEnds);

14.3.4 PolylinePlacementRule:折线路径变形

创建一条随锚点变形的折线或多边形。它通常用于在组件中可视化边界轮廓(例如画出目标区域的边框),而非生成建筑元素。

public class PolylinePlacementRule : Element, ICurveBasedComponentPlacementRule
{
    public Polyline Curve { get; set; }
    public bool IsPolygon { get; set; }
}

TransformPolyline 静态方法是 所有基于曲线的规则的共用核心——它将参考多边形的每个顶点,按其绑定的锚点的位移量进行平移和旋转:

internal static List<Vector3> TransformPolyline(
    ICurveBasedComponentPlacementRule rule,
    ComponentDefinition definition
)
{
    var newVertices = new List<Vector3>();
    for (int i = 0; i < rule.Curve.Vertices.Count; i++)
    {
        var anchorIndex = rule.AnchorIndices[i];
        var anchorForVertex = definition.ReferenceAnchors[anchorIndex];
        var displacementForVertex = definition.AnchorDisplacements[anchorIndex];

        var transform = new Transform(
            definition.OrientationGuide.OfPoint(rule.AnchorDisplacements[i])
        );
        transform.Concatenate(new Transform(displacementForVertex));
        newVertices.Add(transform.OfPoint(anchorForVertex));
    }
    return newVertices;
}

14.3.5 SizeBasedPlacementRule:尺寸驱动选型

根据可用空间大小,从一组候选元素中选择能放得下的最大元素。适用于需要根据空间自适应选择尺寸的场景,如根据房间大小选择不同的家具布局。

public class SizeBasedPlacementRule : Element, ICurveBasedComponentPlacementRule
{
    public Polyline Curve { get; set; }
    public List<(GeometricElement element, Polygon clearance)>
        ElementsAndClearances { get; set; }
}

候选元素按 clearance 面积降序排列,实例化时遍历候选列表,找到第一个 clearance 多边形完全被目标边界覆盖的选项,即放置该元素。

14.3.6 ComponentPlacementRule:子组件嵌套

将另一个 ComponentDefinition 作为子组件嵌入当前组件,实现递归的组件树

public class ComponentPlacementRule : ICurveBasedComponentPlacementRule
{
    public ComponentDefinition Component { get; set; } // 子组件定义
    public Polyline Curve { get; set; }                // 子组件的边界曲线
    public Func<List<Vector3>, List<Vector3>>
        AnchorTransformer { get; set; }                // 锚点变换函数
}

实例化时,先通过 TransformPolyline 变换曲线得到子组件的目标锚点,然后调用 Component.Instantiate(newVertices) 递归实例化。

14.4 ComponentInstance:组件实例

ComponentInstance 是一个轻量级的 Element,本质上是子元素的容器

public class ComponentInstance : Element
{
    public List<Element> Instances { get; set; }  // 所有生成的子元素

    public ComponentInstance(string name = null)
        : base(Guid.NewGuid(), name)
    {
        Instances = new List<Element>();
    }
}

Model.AddElement 的默认行为中,添加一个 ComponentInstance 时,其 Instances 中的元素会被递归收集并一并加入模型。

14.5 组件嵌套

ComponentPlacementRule 使 ComponentDefinition 可以包含其他 ComponentDefinition,形成组件树:

顶层 ComponentDefinition(办公室套间)
├── ArrayPlacementRule: 沿外墙排列办公桌
├── ComponentPlacementRule → 子组件:标准办公桌组
│   ├── PositionPlacementRule: 桌面
│   ├── PositionPlacementRule: 椅子
│   └── PositionPlacementRule: 侧柜
├── SizeBasedPlacementRule: 根据角落空间放置文件柜
└── PolylinePlacementRule: 画出边界轮廓

这种嵌套机制允许将常见的子布局封装为独立组件,在不同场景中复用。AnchorTransformer 回调提供了在锚点传入子组件前进行额外变换的能力,可用于镜像翻转、微调位置等需求。

14.6 实战示例

以下三个示例演示了组件化框架的核心用法。每个示例均为完整的可运行代码。

14.6.1 标准办公桌组件(ArrayPlacementRule)

创建一个”标准办公桌”的组件定义,包含桌面和椅子,然后沿一面墙等距排列 4 套:

using Elements;
using Elements.Components;
using Elements.Geometry;
using Elements.Geometry.Solids;

// --- 第 1 步:定义参考区域 ---
// 矩形区域:10m x 6m,使用 5 个参考锚点(4 角 + 中心)
var referenceAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),      // 左下角
    new Vector3(10, 0, 0),     // 右下角
    new Vector3(10, 6, 0),     // 右上角
    new Vector3(0, 6, 0),      // 左上角
    new Vector3(5, 3, 0)       // 中心点
};

// --- 第 2 步:创建桌面元素(1.6m x 0.8m 的桌面板)---
var deskProfile = new Profile(Polygon.Rectangle(1.6, 0.8));
var desk = new Mass(
    deskProfile,
    0.05,  // 桌面厚度 5cm
    new Material("橡木", new Color(0.6, 0.4, 0.2, 1.0)),
    new Transform(0, 0, 0.75)  // 离地 0.75m
);

// --- 第 3 步:创建椅子元素 ---
var chairSeatProfile = new Profile(Polygon.Rectangle(0.45, 0.45));
var chair = new Mass(
    chairSeatProfile,
    0.45,  // 椅面高度
    new Material("灰色织物", new Color(0.3, 0.3, 0.3, 1.0)),
    new Transform(0, -0.5, 0.45)  // 在桌面下方
);

// --- 第 4 步:定义阵列路径(沿 Y = 1 从 X=1 到 X=9)---
var arrayPath = new Polyline(new[]
{
    new Vector3(1, 1, 0),
    new Vector3(9, 1, 0)
});

// --- 第 5 步:创建放置规则 ---
// 5a. 桌面放在路径上每个节点处
var deskRule = ArrayPlacementRule.FromClosestPoints(
    desk,
    arrayPath,
    new SpacingConfiguration(SpacingMode.ByCount, 4),  // 排列 4 张
    referenceAnchors,
    "桌面阵列"
);

// 5b. 椅子随桌面偏移排列(在 Y 方向上偏移 -0.6m)
var chairArrayPath = new Polyline(new[]
{
    new Vector3(1, 0.4, 0),
    new Vector3(9, 0.4, 0)
});
var chairRule = ArrayPlacementRule.FromClosestPoints(
    chair,
    chairArrayPath,
    new SpacingConfiguration(SpacingMode.ByCount, 4),
    referenceAnchors,
    "椅子阵列"
);

// 5c. 边界可视化(用 PolylinePlacementRule 画出参考区域边框)
var boundaryRule = PolylinePlacementRule.FromClosestPoints(
    Polygon.Rectangle(new Vector3(0, 0, 0), new Vector3(10, 6)),
    referenceAnchors,
    "边界"
);

// --- 第 6 步:创建组件定义 ---
var officeDeskDef = new ComponentDefinition(
    new IComponentPlacementRule[] { deskRule, chairRule, boundaryRule },
    referenceAnchors
);

// --- 第 7 步:实例化到目标边界 ---
// 假设房间实际尺寸为 8m x 5m
var targetAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(8, 0, 0),
    new Vector3(8, 5, 0),
    new Vector3(0, 5, 0),
    new Vector3(4, 2.5, 0)
};

var instance = officeDeskDef.Instantiate(targetAnchors);

// --- 第 8 步:添加到模型并导出 ---
var model = new Model();
model.AddElement(instance);
model.ToGlTF("office_desk.glb");
Console.WriteLine($"生成了 {instance.Instances.Count} 个子元素");

14.6.2 幕墙面板组件(GridPlacementRule)

创建一个幕墙面板组件,在给定的幕墙区域内按网格排列玻璃面板:

using Elements;
using Elements.Components;
using Elements.Geometry;
using Elements.Geometry.Solids;

// --- 第 1 步:定义参考区域(30m x 20m 幕墙)---
var referenceAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(30, 0, 0),
    new Vector3(30, 20, 0),
    new Vector3(0, 20, 0),
    new Vector3(15, 10, 0)
};

// --- 第 2 步:定义幕墙面板元素 ---
// 标准面板:1.5m 宽 x 2m 高,厚度 0.1m
var panelProfile = new Profile(Polygon.Rectangle(1.5, 2.0));
var panel = new Mass(
    panelProfile,
    0.1,
    new Material("玻璃", new Color(0.68, 0.85, 0.9, 0.6)),
    new Transform()
)
{
    Name = "幕墙面板"
};

// --- 第 3 步:定义网格单元格 ---
// 在 Grid2d 坐标系中:U 方向 = 水平(X),V 方向 = 垂直(Y)
var cellDef = new GridCellDefinition(
    new[] { panel },  // 每个单元格中放置一个面板
    cellWidth: 2.0,   // V 方向(高)
    cellLength: 1.5   // U 方向(宽)
);

// --- 第 4 步:创建网格放置规则 ---
var targetArea = Polygon.Rectangle(
    new Vector3(1, 1, 0),   // 幕墙左下角
    new Vector3(29, 19, 0)  // 幕墙右上角
);

var gridRule = GridPlacementRule.FromClosestPoints(
    cellDef,
    targetArea,
    referenceAnchors,
    "幕墙网格"
);

// --- 第 5 步:边界可视化 ---
var boundaryRule = PolylinePlacementRule.FromClosestPoints(
    Polygon.Rectangle(new Vector3(0, 0, 0), new Vector3(30, 20)),
    referenceAnchors,
    "幕墙边界"
);

// --- 第 6 步:创建组件定义 ---
var curtainWallDef = new ComponentDefinition(
    new IComponentPlacementRule[] { gridRule, boundaryRule },
    referenceAnchors
);

// --- 第 7 步:实例化到不同尺寸的幕墙 ---
// 场景 A:原始尺寸(30m x 20m)
var anchorsA = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(30, 0, 0),
    new Vector3(30, 20, 0),
    new Vector3(0, 20, 0),
    new Vector3(15, 10, 0)
};

var model = new Model();
model.AddElement(curtainWallDef.Instantiate(anchorsA));
model.ToGlTF("curtain_wall_A.glb");
Console.WriteLine($"幕墙 A 生成完成");

// 场景 B:旋转后的幕墙(30m x 15m,旋转 90 度)
var anchorsB = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(0, 30, 0),     // 前两个锚点改为垂直方向
    new Vector3(15, 30, 0),
    new Vector3(15, 0, 0),
    new Vector3(7.5, 15, 0)
};

var modelB = new Model();
modelB.AddElement(curtainWallDef.Instantiate(anchorsB));
modelB.ToGlTF("curtain_wall_B.glb");
Console.WriteLine("幕墙 B 生成完成");

14.6.3 楼梯栏杆组件(PolylinePlacementRule + ArrayPlacementRule)

创建一个楼梯栏杆组件,沿楼梯的折线轮廓放置栏杆立柱和扶手:

using Elements;
using Elements.Components;
using Elements.Geometry;
using Elements.Geometry.Solids;

// --- 第 1 步:定义参考锚点(楼梯的简化轮廓)---
// 楼梯从 (0,0) 到 (3,4) 到 (5,4),高程从 0 到 3m
var referenceAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),       // 楼梯起点
    new Vector3(3, 0, 1.5),     // 第一段中间转折
    new Vector3(5, 0, 3.0),     // 楼梯终点
    new Vector3(0, 1, 0),       // 栏杆外侧偏移参考
    new Vector3(5, 1, 3.0)      // 栏杆外侧偏移参考
};

// --- 第 2 步:创建栏杆立柱元素 ---
var postProfile = new Profile(Polygon.Rectangle(0.05, 0.05));
var post = new Mass(
    postProfile,
    1.1,  // 立柱高度 1.1m
    new Material("不锈钢", new Color(0.75, 0.75, 0.75, 1.0)),
    new Transform()
)
{
    Name = "栏杆立柱"
};

// --- 第 3 步:创建扶手元素(水平管)---
var railProfile = new Profile(
    new Circle(Vector3.Origin, 0.03).ToPolygon(16)
);
var rail = new Mass(
    railProfile,
    0.2,  // 横截面厚度
    new Material("不锈钢", new Color(0.75, 0.75, 0.75, 1.0)),
    new Transform()
)
{
    Name = "扶手管"
};

// --- 第 4 步:定义栏杆的折线路径 ---
// 栏杆沿楼梯外侧布置
var railingPath = new Polyline(new[]
{
    new Vector3(0.1, 0.8, 0.2),    // 起点,偏移到楼梯外侧
    new Vector3(3.0, 0.8, 1.7),    // 中间转折
    new Vector3(4.9, 0.8, 3.2)     // 终点
});

// --- 第 5 步:创建阵列规则(立柱沿折线排列)---
var postRule = ArrayPlacementRule.FromClosestPoints(
    post,
    railingPath,
    new SpacingConfiguration(SpacingMode.ByApproximateLength, 0.3), // 每30cm一根
    referenceAnchors,
    "立柱阵列"
);

// --- 第 6 步:创建折线规则(扶手管随楼梯折线变形)---
var railRule = PolylinePlacementRule.FromClosestPoints(
    railingPath,
    referenceAnchors,
    "扶手路径"
);

// --- 第 7 步:边界可视化 ---
var boundaryRule = PolylinePlacementRule.FromClosestPoints(
    new Polyline(new[]
    {
        new Vector3(0, 0, 0),
        new Vector3(3, 0, 1.5),
        new Vector3(5, 0, 3),
        new Vector3(5, 1, 3),
        new Vector3(0, 1, 0)
    }),
    referenceAnchors,
    "楼梯轮廓"
);

// --- 第 8 步:创建组件定义 ---
var railingDef = new ComponentDefinition(
    new IComponentPlacementRule[] { postRule, railRule, boundaryRule },
    referenceAnchors
);

// --- 第 9 步:实例化到目标楼梯 ---
// 目标楼梯尺寸与参考一致(本例演示相同尺寸)
var targetAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(3, 0, 1.5),
    new Vector3(5, 0, 3.0),
    new Vector3(0, 1, 0),
    new Vector3(5, 1, 3.0)
};

var instance = railingDef.Instantiate(targetAnchors);

// --- 第 10 步:添加到模型并导出 ---
var model = new Model();
model.AddElement(instance);
model.ToGlTF("stair_railing.glb");

Console.WriteLine($"栏杆组件生成完成,共 {instance.Instances.Count} 个子元素");
// 第 6 步的 PolylinePlacementRule 会生成 ModelCurve 用于可视化
// 立柱的 ArrayPlacementRule 会在路径上排列 ElementInstance

14.6.4 组件嵌套:办公室套间

将”标准办公桌”组件作为子组件,嵌套到更大的”办公室套间”组件中:

using Elements;
using Elements.Components;
using Elements.Geometry;

// 假设 officeDeskDef 是之前的"标准办公桌"组件定义(见 14.6.1)

// --- 第 1 步:定义办公室套间的参考锚点 ---
var roomAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(10, 0, 0),
    new Vector3(10, 8, 0),
    new Vector3(0, 8, 0),
    new Vector3(5, 4, 0)
};

// --- 第 2 步:为每个子区域创建 ComponentPlacementRule ---
// 区域 A:左下角(4m x 5m)放办公桌组件
var subRegionA = Polygon.Rectangle(
    new Vector3(0.5, 0.5, 0),
    new Vector3(4.5, 5.5, 0)
);
var deskRule1 = ComponentPlacementRule.FromClosestPoints(
    officeDeskDef,   // 嵌套的办公桌组件定义
    subRegionA,
    roomAnchors,
    "办公桌区 A"
);

// 区域 B:右下角同样放置办公桌
var subRegionB = Polygon.Rectangle(
    new Vector3(5.5, 0.5, 0),
    new Vector3(9.5, 5.5, 0)
);
var deskRule2 = ComponentPlacementRule.FromClosestPoints(
    officeDeskDef,
    subRegionB,
    roomAnchors,
    "办公桌区 B"
);

// --- 第 3 步:边界可视化 ---
var boundaryRule = PolylinePlacementRule.FromClosestPoints(
    Polygon.Rectangle(new Vector3(0, 0, 0), new Vector3(10, 8)),
    roomAnchors,
    "房间边界"
);

// --- 第 4 步:创建办公室套间组件定义 ---
var officeSuiteDef = new ComponentDefinition(
    new IComponentPlacementRule[] { deskRule1, deskRule2, boundaryRule },
    roomAnchors
);

// --- 第 5 步:实例化到目标房间(14m x 10m)---
var targetRoomAnchors = new List<Vector3>
{
    new Vector3(0, 0, 0),
    new Vector3(14, 0, 0),
    new Vector3(14, 10, 0),
    new Vector3(0, 10, 0),
    new Vector3(7, 5, 0)
};

var roomInstance = officeSuiteDef.Instantiate(targetRoomAnchors);

var model = new Model();
model.AddElement(roomInstance);
model.ToGlTF("office_suite.glb");
Console.WriteLine($"办公室套间生成完成");

14.7 小结

本章深入解析了 Elements.Components 组件化生成框架:

  • ComponentDefinition 是组件的”蓝图”,由参考锚点和放置规则列表组成
  • 锚点机制通过一一对应的索引实现从”定义空间”到”目标空间”的映射
  • 六个标准放置规则覆盖了从简单定位到智能选型的各种布局需求
  • ComponentInstance 是生成的元素容器,可通过 Model.AddElement 一键加入模型
  • ComponentPlacementRule 支持递归嵌套,实现组件树的复用和组合
  • FromClosestPoints 工厂方法极大简化了规则的创建流程

组件化框架是 Elements 从”单个元素参数化”迈向”整体空间智能化”的关键一步。它将建筑空间的布局逻辑封装为可重用的数字化资产,为大规模自动化生成奠定了基础。


← 上一章 目录 下一章 →