场布元素实现详解

第六章:场布元素实现详解

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中各种场布元素的实现:

  1. 草坪元素:任意多边形绘制、矩形绘制、线段转换
  2. 基坑元素:放坡计算、标高设置、属性编辑
  3. 围栏元素:沿路径绘制、门的设置
  4. 出土道路:道路宽度、边界生成
  5. 拟建建筑:建筑属性、面积计算
  6. 用地红线:特殊样式、边界约束
  7. 曲线闭合工具:线段转换为闭合多边形
  8. 图层管理:规范的图层命名和颜色

下一章我们将学习板房系统的开发,这是FY_Layout中最复杂的功能模块。


posted @ 2026-01-31 16:02  我才是银古  阅读(2)  评论(0)    收藏  举报