场布元素实现详解
第六章:场布元素实现详解
6.1 场布元素概述
6.1.1 元素分类
FY_Layout的场布元素按功能可分为以下几类:
| 分类 | 元素 | 说明 |
|---|---|---|
| 区域类 | 草坪、场地、用地红线 | 闭合多边形区域 |
| 坑槽类 | 基坑、土方回填 | 带标高和放坡的区域 |
| 道路类 | 出土道路、城市道路、硬化地面 | 线性或带状区域 |
| 设施类 | 围栏、防护栏杆 | 线性安全设施 |
| 建筑类 | 拟建建筑、板房 | 建筑物表示 |
6.1.2 通用实现模式
每个场布元素都遵循以下实现模式:
QdXxx.cs - 元素数据类
QdXxxDef.cs - 元素定义类
XxxAction.cs - 二维操作类(绘制、编辑、显示)
Xxx3dAction.cs - 三维操作类(三维模型生成)
6.2 草坪元素(Lawn)
6.2.1 功能特点
- 支持任意多边形绘制
- 支持矩形快速绘制
- 支持现有多段线转换
- 显示草坪填充图案
- 可设置底部标高
6.2.2 LawnAction详解
namespace QdLayout
{
public class LawnAction : DirectComponentAction
{
// 创建方法定义
private static readonly LcCreateMethod[] CreateMethods;
private PointInputer pointInputer;
private CmdTextInputer cmdTextInputer;
private LcPolyLine OutLoop;
public LawnAction() { }
public LawnAction(IDocumentEditor docEditor) : base(docEditor)
{
commandCtrl.WriteInfo("命令:Lawn");
}
static LawnAction()
{
CreateMethods = new LcCreateMethod[1];
CreateMethods[0] = new LcCreateMethod()
{
Name = "CreateLawn",
Description = "创建草坪",
Steps = new LcCreateStep[]
{
new LcCreateStep { Name = "Step0", Options = "指定轮廓第一个点:" },
new LcCreateStep { Name = "Step1", Options = "指定轮廓下一点或 [结束(E)]" },
}
};
}
/// <summary>
/// 任意多边形绘制
/// </summary>
public async void ExecCreatePoly(string[] args = null)
{
OutLoop = null;
commandCtrl.WriteInfo("绘制草坪轮廓中...");
var plAc = new PolyLineAction(docEditor);
await plAc.StartCreating();
if (!await plAc.OtherActionCreating())
{
goto End;
}
else
{
OutLoop = plAc.CurrentPoly;
}
CreateLawn();
End:
if (OutLoop != null)
{
vportRt.ActiveElementSet.RemoveElement(OutLoop);
}
plAc.EndCreating();
EndCreating();
}
/// <summary>
/// 矩形绘制
/// </summary>
public async void ExecCreateRec(string[] args = null)
{
EndCreating();
OutLoop = null;
commandCtrl.WriteInfo("绘制草坪轮廓中...");
var plAc = new PolyLineAction(docEditor);
await plAc.StartCreating();
if (!await plAc.OtherActionCreatingRect())
{
goto End;
}
else
{
OutLoop = plAc.CurrentPoly;
}
CreateLawn();
End:
if (OutLoop != null)
{
vportRt.ActiveElementSet.RemoveElement(OutLoop);
}
plAc.EndCreating();
EndCreating();
}
/// <summary>
/// 从现有线段转换
/// </summary>
public async void ExecCreate(string[] args = null)
{
var elementInputer = new ElementSetInputer(this.docEditor);
Step0:
var result = await elementInputer.Execute("请选择已有闭合线段创建草坪:");
if (elementInputer.isCancelled || result == null)
{
Cancel();
return;
}
if (result.ValueX != null)
{
var eles = result.ValueX as List<LcElement>;
var lines = new List<LcCurve2d>();
foreach (var ele in eles)
{
if (ele is LcLine line) lines.Add(line);
else if (ele is LcPolyLine polyLine) lines.Add(polyLine);
else if (ele is LcArc arc) lines.Add(arc);
}
// 检查闭合环
var polys = LcCurveChangeLoop.CheckLoops(lines);
foreach (var line in polys)
{
OutLoop = line;
CreateLawn();
}
}
else if (result.Option != null)
{
return;
}
else
{
goto Step0;
}
}
/// <summary>
/// 创建草坪元素
/// </summary>
public void CreateLawn()
{
var doc = docRt.Document;
// 获取组件定义
var lawnDef = docRt.GetUseComDef($"{NamespaceKey}.绿色文明", "草坪", null) as QdLawnDef;
// 创建草坪实例
var lawn = new QdLawn(lawnDef);
lawn.Initilize(doc);
// 设置轮廓
var poly = OutLoop.Clone() as LcPolyLine;
lawn.Outline = poly.Curve.Clone() as Polyline2d;
lawn.ResetBoundingBox();
// 设置属性
lawn.Layer = GetLayer().Name;
lawn.Bottom = 0;
lawn.Material = MaterialManager.GetMaterial(MaterialManager.LawnUuid);
// 插入到文档
vportRt.ActiveElementSet.InsertElement(lawn);
docRt.Action.ClearSelects();
}
/// <summary>
/// 绘制草坪(二维显示)
/// </summary>
public override void Draw(LcCanvas2d canvas, LcElement element, Matrix3 matrix)
{
var lawn = element as QdLawn;
var pen = GetDrawPen(lawn);
DrawLawn(canvas, lawn, matrix, pen);
}
private void DrawLawn(LcCanvas2d canvas, QdLawn lawn, Matrix3 matrix, LcPaint pen)
{
// 绘制轮廓线
foreach (var curve in lawn.GetShapes()[0].Curve2ds)
{
canvas.DrawCurve(pen, curve, matrix);
}
// 绘制文字标注
var textPaint = new LcTextPaint
{
Color = new Color().Set(GetLayer().Color),
FontName = "仿宋",
FontName2 = "仿宋",
Size = 800,
WordSpace = 5,
WidthFactor = 1
};
textPaint.Position = lawn.BoundingBox.Center;
canvas.DrawText(textPaint, "草坪", new Matrix3(), out var charBoxs);
}
/// <summary>
/// 获取控制夹点
/// </summary>
public override ControlGrip[] GetControlGrips(LcElement element)
{
var lawn = element as QdLawn;
var grips = new List<ControlGrip>();
// 中心夹点(用于移动)
grips.Add(new ControlGrip
{
Element = lawn,
Name = "Center",
Position = lawn.BoundingBox.Center.Clone()
});
// 轮廓顶点夹点(用于编辑形状)
var points = lawn.GetShapes()[0].Curve2ds.Select(n => n.GetPoints(1)[0]).ToList();
for (int i = 0; i < points.Count; i++)
{
grips.Add(new ControlGrip
{
Element = lawn,
Name = $"Outline_{i}",
Position = points[i]
});
}
return grips.ToArray();
}
/// <summary>
/// 处理夹点拖动
/// </summary>
public override void SetDragGrip(LcElement element, ControlGrip grip, Vector2 position, bool isEnd)
{
var lawn = element as QdLawn;
if (isEnd)
{
if (grip.Name == "Center")
{
// 移动整个元素
var offset = position - grip.Position;
lawn.Translate(offset);
}
else if (grip.Name.StartsWith("Outline"))
{
// 编辑轮廓点
var idx = int.Parse(grip.Name.Split('_')[1]);
var poly = lawn.Outline.Clone() as Polyline2d;
var offset = position - grip.Position;
(poly.Curve2ds[idx] as Line2d).Start.Add(offset);
if (idx == 0)
(poly.Curve2ds.Last() as Line2d).End.Add(offset);
else
(poly.Curve2ds[idx - 1] as Line2d).End.Add(offset);
lawn.Outline = poly;
}
}
}
/// <summary>
/// 获取或创建图层
/// </summary>
private LcLayer GetLayer()
{
var layer = docRt.Document.Layers.FirstOrDefault(n => n.Name == "Layout_Lawn");
if (layer == null)
{
layer = docRt.Document.CreateObject<LcLayer>();
layer.Name = "Layout_Lawn";
layer.Color = 0x00FF00; // 绿色
layer.SetLineType(new LcLineType("ByLayer"));
layer.Transparency = 0;
docRt.Document.Layers.Add(layer);
}
return layer;
}
}
}
6.3 基坑元素(FoundationPit)
6.3.1 功能特点
- 支持放坡计算
- 可设置底标高和顶标高
- 支持向内/向外放坡
- 自动生成坡面图案
6.3.2 FoundationPitAction关键代码
public class FoundationPitAction : DirectComponentAction
{
public void CreateFoundationPit()
{
var doc = docRt.Document;
// 获取基坑定义
var fdPitDef = docRt.GetUseComDef($"{NamespaceKey}.土方基坑", "基坑", null) as QdFoundationPitDef;
// 创建基坑实例
var fdPit = new QdFoundationPit(fdPitDef);
fdPit.Initilize(doc);
// 设置轮廓
var poly = OutLoop.Clone() as LcPolyLine;
fdPit.Outline = poly.Curve.Clone() as Polyline2d;
// 设置默认参数
fdPit.Pattern = 0; // 向外放坡
fdPit.Bottom = -4000; // 底标高-4米
fdPit.Elevation = 0; // 顶标高0
fdPit.Factor = 0.4; // 放坡系数0.4
fdPit.ResetBoundingBox();
fdPit.Layer = GetLayer().Name;
vportRt.ActiveElementSet.InsertElement(fdPit);
docRt.Action.ClearSelects();
}
/// <summary>
/// 获取属性编辑器
/// </summary>
public override List<PropertyObserver> GetPropertyObservers()
{
return new List<PropertyObserver>
{
new PropertyObserver
{
Name = "Bottom",
DisplayName = "土方底绝对标高",
CategoryName = "Geometry",
CategoryDisplayName = "几何图形",
PropType = PropertyType.Double,
Getter = (ele) => (ele as QdFoundationPit).Bottom,
Setter = (ele, value) =>
{
if (double.TryParse(value.ToString(), out var bottom))
(ele as QdFoundationPit).Bottom = bottom;
}
},
new PropertyObserver
{
Name = "Elevation",
DisplayName = "土方顶绝对标高",
PropType = PropertyType.Double,
Getter = (ele) => (ele as QdFoundationPit).Elevation,
Setter = (ele, value) =>
{
if (double.TryParse(value.ToString(), out var elevation))
(ele as QdFoundationPit).Elevation = elevation;
}
},
new PropertyObserver
{
Name = "Factor",
DisplayName = "放坡系数",
PropType = PropertyType.Double,
Getter = (ele) => (ele as QdFoundationPit).Factor,
Setter = (ele, value) =>
{
if (double.TryParse(value.ToString(), out var factor))
(ele as QdFoundationPit).Factor = factor;
}
},
new PropertyObserver
{
Name = "Pattern",
DisplayName = "放坡方式",
PropType = PropertyType.Array,
Source = (ele) => new string[] { "向外放坡", "向内放坡" },
Getter = (ele) => (ele as QdFoundationPit).Pattern == 0 ? "向外放坡" : "向内放坡",
Setter = (ele, value) =>
{
var pattern = value?.ToString() == "向外放坡" ? 0 : 1;
(ele as QdFoundationPit).Pattern = pattern;
}
}
};
}
}
6.4 围栏元素(Fence)
6.4.1 功能特点
- 沿路径绘制围栏
- 支持自定义围栏样式
- 显示围栏立柱和横杆
- 支持门的设置
6.4.2 QdFence元素类
public class QdFence : DirectComponent
{
/// <summary>
/// 路径
/// </summary>
public Polyline2d Path
{
get => BaseCurve as Polyline2d;
set => BaseCurve = value;
}
/// <summary>
/// 围栏高度
/// </summary>
public double Height
{
get => Properties.GetValue<double>("Height");
set => SetProps((GetPropId(nameof(Height)), value));
}
/// <summary>
/// 立柱间距
/// </summary>
public double PostSpacing
{
get => Properties.GetValue<double>("PostSpacing");
set => SetProps((GetPropId(nameof(PostSpacing)), value));
}
/// <summary>
/// 门位置列表
/// </summary>
public List<FenceGate> Gates { get; set; } = new List<FenceGate>();
public QdFence(QdFenceDef def) : base(def)
{
Type = LayoutElementType.Fence;
Path = new Polyline2d();
Height = 2000; // 默认2米高
PostSpacing = 3000; // 默认3米间距
}
}
public class FenceGate
{
public double Position { get; set; } // 沿路径的位置
public double Width { get; set; } // 门宽度
public string Type { get; set; } // 门类型
}
6.5 出土道路(Berm)
6.5.1 功能特点
- 双向道路绘制
- 支持道路宽度设置
- 自动生成路肩
- 计算运输长度
6.5.2 QdBerm元素类
public class QdBerm : DirectComponent
{
/// <summary>
/// 道路中心线
/// </summary>
public Polyline2d CenterLine
{
get => BaseCurve as Polyline2d;
set => BaseCurve = value;
}
/// <summary>
/// 道路宽度
/// </summary>
public double Width
{
get => Properties.GetValue<double>("Width");
set
{
Properties.SetValue("Width", value);
ResetCache();
}
}
/// <summary>
/// 道路长度(计算属性)
/// </summary>
public double Length
{
get
{
double totalLength = 0;
foreach (var curve in CenterLine.Curve2ds)
{
totalLength += curve.Length;
}
return totalLength;
}
}
/// <summary>
/// 获取道路边界
/// </summary>
public override Curve2dGroupCollection GetShapes()
{
var shapes = new Curve2dGroupCollection();
// 生成左边界
var leftBoundary = OffsetCurve(CenterLine, Width / 2);
// 生成右边界
var rightBoundary = OffsetCurve(CenterLine, -Width / 2);
var group = new Curve2dGroup();
group.Curve2ds.AddRange(leftBoundary);
group.Curve2ds.AddRange(rightBoundary);
shapes.Add(group);
return shapes;
}
private List<Curve2d> OffsetCurve(Polyline2d poly, double offset)
{
// 曲线偏移算法实现
var result = new List<Curve2d>();
foreach (var curve in poly.Curve2ds)
{
if (curve is Line2d line)
{
var dir = line.Dir.Clone().RotateAround(new Vector2(), -Math.PI / 2);
var newLine = new Line2d(
line.Start.Clone().AddScaledVector(dir, offset),
line.End.Clone().AddScaledVector(dir, offset)
);
result.Add(newLine);
}
}
return result;
}
}
6.6 拟建建筑(PlanBuild)
6.6.1 功能特点
- 绘制建筑平面轮廓
- 设置建筑高度
- 显示建筑名称
- 支持多层建筑
6.6.2 QdPlanBuild元素类
public class QdPlanBuild : DirectComponent
{
/// <summary>
/// 建筑轮廓
/// </summary>
public Polyline2d Outline
{
get => BaseCurve as Polyline2d;
set => BaseCurve = value;
}
/// <summary>
/// 建筑名称
/// </summary>
public string BuildingName
{
get => Properties.GetValue<string>("BuildingName");
set => SetProps((GetPropId(nameof(BuildingName)), value));
}
/// <summary>
/// 建筑高度
/// </summary>
public double Height
{
get => Properties.GetValue<double>("Height");
set => SetProps((GetPropId(nameof(Height)), value));
}
/// <summary>
/// 层数
/// </summary>
public int FloorCount
{
get => Properties.GetValue<int>("FloorCount");
set => SetProps((GetPropId(nameof(FloorCount)), value));
}
/// <summary>
/// 底部标高
/// </summary>
public double BaseElevation
{
get => Properties.GetValue<double>("BaseElevation");
set => SetProps((GetPropId(nameof(BaseElevation)), value));
}
public QdPlanBuild(QdPlanBuildDef def) : base(def)
{
Type = LayoutElementType.PlanBuild;
Outline = new Polyline2d();
BuildingName = "建筑";
Height = 30000; // 默认30米
FloorCount = 10; // 默认10层
BaseElevation = 0;
}
/// <summary>
/// 计算建筑面积
/// </summary>
public double GetArea()
{
// 使用多边形面积公式
var points = Outline.Curve2ds.Select(c => c.GetPoints(1)[0]).ToList();
double area = 0;
for (int i = 0; i < points.Count; i++)
{
var j = (i + 1) % points.Count;
area += points[i].X * points[j].Y;
area -= points[j].X * points[i].Y;
}
return Math.Abs(area) / 2;
}
}
6.7 用地红线(PropertyLine)
6.7.1 功能特点
- 显示用地边界
- 特殊的红线样式
- 支持面积计算
- 作为其他元素的约束边界
6.7.2 PropertyLineAction绘制逻辑
public class PropertyLineAction : DirectComponentAction
{
public override void Draw(LcCanvas2d canvas, LcElement element, Matrix3 matrix)
{
var propLine = element as QdPropertyLine;
// 使用红色虚线样式
var pen = new LcPaint
{
Color = new Color(0xFF0000), // 红色
Width = 2,
StrokeStyle = StrokeStyle.Dashed,
DashPattern = new float[] { 10, 5 }
};
// 绘制边界线
foreach (var curve in propLine.GetShapes()[0].Curve2ds)
{
canvas.DrawCurve(pen, curve, matrix);
}
// 绘制顶点标记
var vertexPen = new LcPaint
{
Color = new Color(0xFF0000),
Width = 1
};
var points = propLine.Outline.Curve2ds.Select(c => c.GetPoints(1)[0]).ToList();
foreach (var point in points)
{
canvas.DrawCircle(vertexPen, point.ApplyMatrix3(matrix), 50);
}
}
}
6.8 曲线闭合工具
6.8.1 LcCurveChangeLoop类
这是一个工具类,用于将多条分散的线段转换为闭合多边形:
public static class LcCurveChangeLoop
{
/// <summary>
/// 检查并生成闭合环
/// </summary>
public static List<LcPolyLine> CheckLoops(List<LcCurve2d> curves)
{
var result = new List<LcPolyLine>();
// 查找所有可能的闭合环
var loops = FindClosedLoops(curves);
foreach (var loop in loops)
{
var polyline = new LcPolyLine();
foreach (var curve in loop)
{
polyline.AddCurve(curve);
}
polyline.IsClosed = true;
result.Add(polyline);
}
return result;
}
private static List<List<Curve2d>> FindClosedLoops(List<LcCurve2d> curves)
{
var loops = new List<List<Curve2d>>();
var used = new HashSet<int>();
for (int i = 0; i < curves.Count; i++)
{
if (used.Contains(i)) continue;
var loop = TryBuildLoop(curves, i, used);
if (loop != null && loop.Count >= 3)
{
loops.Add(loop);
}
}
return loops;
}
private static List<Curve2d> TryBuildLoop(List<LcCurve2d> curves, int startIdx, HashSet<int> used)
{
var loop = new List<Curve2d>();
var startCurve = curves[startIdx].Curve;
var currentEnd = startCurve.GetEndPoint();
var targetStart = startCurve.GetStartPoint();
loop.Add(startCurve.Clone());
used.Add(startIdx);
const double tolerance = 0.001;
int maxIterations = curves.Count;
int iterations = 0;
while (iterations++ < maxIterations)
{
// 检查是否闭合
if (currentEnd.DistanceTo(targetStart) < tolerance)
{
return loop;
}
// 查找下一条曲线
bool found = false;
for (int i = 0; i < curves.Count; i++)
{
if (used.Contains(i)) continue;
var curve = curves[i].Curve;
// 检查起点连接
if (curve.GetStartPoint().DistanceTo(currentEnd) < tolerance)
{
loop.Add(curve.Clone());
used.Add(i);
currentEnd = curve.GetEndPoint();
found = true;
break;
}
// 检查终点连接(需要反向)
if (curve.GetEndPoint().DistanceTo(currentEnd) < tolerance)
{
var reversed = curve.Clone();
reversed.Reverse();
loop.Add(reversed);
used.Add(i);
currentEnd = reversed.GetEndPoint();
found = true;
break;
}
}
if (!found) break;
}
return null;
}
}
6.9 图层管理
6.9.1 场布元素图层规范
| 元素类型 | 图层名称 | 颜色 |
|---|---|---|
| 草坪 | Layout_Lawn | 0x00FF00(绿色) |
| 基坑 | Layout_FoundationPit | 0xFFFF00(黄色) |
| 围栏 | Layout_Fence | 0x0000FF(蓝色) |
| 出土道路 | Layout_Berm | 0x808080(灰色) |
| 拟建建筑 | Layout_PlanBuild | 0x00FFFF(青色) |
| 用地红线 | Layout_PropertyLine | 0xFF0000(红色) |
6.9.2 图层管理通用代码
/// <summary>
/// 图层管理基类
/// </summary>
public abstract class LayoutLayerManager
{
protected LcDocument Document { get; }
protected LayoutLayerManager(LcDocument document)
{
Document = document;
}
/// <summary>
/// 获取或创建图层
/// </summary>
protected LcLayer GetOrCreateLayer(string name, int color, string lineType = "ByLayer")
{
var layer = Document.Layers.FirstOrDefault(l => l.Name == name);
if (layer == null)
{
layer = Document.CreateObject<LcLayer>();
layer.Name = name;
layer.Color = color;
layer.SetLineType(new LcLineType(lineType));
layer.Transparency = 0;
Document.Layers.Add(layer);
}
return layer;
}
}
6.10 本章小结
本章详细介绍了FY_Layout中各种场布元素的实现:
- 草坪元素:任意多边形绘制、矩形绘制、线段转换
- 基坑元素:放坡计算、标高设置、属性编辑
- 围栏元素:沿路径绘制、门的设置
- 出土道路:道路宽度、边界生成
- 拟建建筑:建筑属性、面积计算
- 用地红线:特殊样式、边界约束
- 曲线闭合工具:线段转换为闭合多边形
- 图层管理:规范的图层命名和颜色
下一章我们将学习板房系统的开发,这是FY_Layout中最复杂的功能模块。

浙公网安备 33010602011771号