惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
F
Fortinet All Blogs
博客园_首页
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
D
DataBreaches.Net
aimingoo的专栏
aimingoo的专栏
爱范儿
爱范儿
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
Netflix TechBlog - Medium
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
D
Docker
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
罗磊的独立博客
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
量子位
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
The Blog of Author Tim Ferriss
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
小众软件
小众软件
Cloudbric
Cloudbric
博客园 - 司徒正美
H
Help Net Security
人人都是产品经理
人人都是产品经理
Application and Cybersecurity Blog
Application and Cybersecurity Blog
L
LangChain Blog
Latest news
Latest news
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
博客园 - Franky
S
Security Affairs
W
WeLiveSecurity
F
Full Disclosure
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
Cyberwarzone
Cyberwarzone
美团技术团队
PCI Perspectives
PCI Perspectives
C
Check Point Blog
Spread Privacy
Spread Privacy

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
场布元素实现详解
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

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

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中最复杂的功能模块。