元素类型系统详解

第五章:元素类型系统详解

5.1 元素类型概述

5.1.1 ElementType类

ElementType是LightCAD中定义元素类型的核心类,每个自定义元素都需要对应一个ElementType实例:

public class ElementType
{
    /// <summary>
    /// 唯一标识符
    /// </summary>
    public LcGuid Guid { get; set; }
    
    /// <summary>
    /// 内部名称
    /// </summary>
    public string Name { get; set; }
    
    /// <summary>
    /// 显示名称
    /// </summary>
    public string DispalyName { get; set; }
    
    /// <summary>
    /// 元素类类型
    /// </summary>
    public Type ClassType { get; set; }
}

5.1.2 GUID的作用

GUID(全局唯一标识符)在元素类型系统中起到关键作用:

  1. 唯一性:确保每种元素类型在全球范围内唯一
  2. 持久化:文件保存和加载时识别元素类型
  3. 版本兼容:新版本软件能识别旧版本创建的元素
// GUID格式示例
Guid = Guid.ParseExact("{63A566EA-3702-A98C-7A6B-8DBEA6B3F41A}", "B").ToLcGuid()

// 生成新GUID
Guid = Guid.NewGuid().ToLcGuid()

5.1.3 FY_Layout元素类型定义

public static class LayoutElementType
{
    // 草坪
    public static ElementType Lawn = new ElementType
    {
        Guid = Guid.ParseExact("{63A566EA-3702-A98C-7A6B-8DBEA6B3F41A}", "B").ToLcGuid(),
        Name = "Lawn",
        DispalyName = "草坪",
        ClassType = typeof(QdLawn)
    };
    
    // 板房
    public static ElementType PlateBuilding = new ElementType
    {
        Guid = Guid.ParseExact("{63A523EA-3702-A98C-7A6B-8DBEA5C3F42B}", "B").ToLcGuid(),
        Name = "PlateBuilding",
        DispalyName = "板房",
        ClassType = typeof(PlateBuilding)
    };
    
    // 板房楼栋
    public static ElementType PlateBuildGroup = new ElementType
    {
        Guid = Guid.ParseExact("{C3A523EA-3702-A98C-7A6B-8DBEA2D2F4D4}", "B").ToLcGuid(),
        Name = "PlateBuildGroup",
        DispalyName = "板房楼栋",
        ClassType = typeof(PlateBuildGroup)
    };
    
    // 基坑
    public static ElementType FoundationPit = new ElementType
    {
        Guid = Guid.ParseExact("{C082E6EA-E099-94AB-50FA-50DF72D286D4}", "B").ToLcGuid(),
        Name = "FoundationPit",
        DispalyName = "基坑",
        ClassType = typeof(QdFoundationPit)
    };
    
    // 围栏
    public static ElementType Fence = new ElementType
    {
        Guid = Guid.ParseExact("{5A9EC5E6-09BF-4DB3-9AD2-DD0DA74F099A}", "B").ToLcGuid(),
        Name = "Fence",
        DispalyName = "围墙",
        ClassType = typeof(QdFence)
    };
    
    // 拟建建筑
    public static ElementType PlanBuild = new ElementType
    {
        Guid = Guid.ParseExact("{5A5EC5E6-06BF-4DB3-4AD2-DD0DA52F078A}", "B").ToLcGuid(),
        Name = "PlanBuild",
        DispalyName = "拟建建筑",
        ClassType = typeof(QdPlanBuild)
    };
    
    // 硬化地面/道路
    public static ElementType Road = new ElementType
    {
        Guid = Guid.ParseExact("{85AB36C8-FBB1-424B-C7C4-1F92576EC5BD}", "B").ToLcGuid(),
        Name = "Road",
        DispalyName = "硬化地面",
        ClassType = typeof(QdRoad)
    };
    
    // 土方回填
    public static ElementType Earthwork = new ElementType
    {
        Guid = Guid.ParseExact("{B20D7BB7-6209-B3DB-2761-E1197E59723E}", "B").ToLcGuid(),
        Name = "Earthwork",
        DispalyName = "土方回填",
        ClassType = typeof(QdEarthwork)
    };
    
    // 出土道路
    public static ElementType Berm = new ElementType
    {
        Guid = Guid.ParseExact("{982B310D-627F-9168-6A63-1257A26BDDF8}", "B").ToLcGuid(),
        Name = "Berm",
        DispalyName = "出土道路",
        ClassType = typeof(QdBerm)
    };
    
    // 路面硬化
    public static ElementType Harden = new ElementType
    {
        Guid = Guid.ParseExact("{897700F4-F50B-62EE-3900-59D967BEBC85}", "B").ToLcGuid(),
        Name = "Harden",
        DispalyName = "路面硬化",
        ClassType = typeof(QdHarden)
    };
    
    // 防护栏杆
    public static ElementType Barrier = new ElementType
    {
        Guid = Guid.ParseExact("{6A8E5173-8D53-4542-8272-5B1A6680AFD2}", "B").ToLcGuid(),
        Name = "Barrier",
        DispalyName = "防护栏杆",
        ClassType = typeof(QdBarrier)
    };
    
    // 场地
    public static ElementType Site = new ElementType
    {
        Guid = Guid.ParseExact("{15AC9FC1-9BC3-4D11-DFDB-3426FAE73336}", "B").ToLcGuid(),
        Name = "Site",
        DispalyName = "场地",
        ClassType = typeof(QdSite)
    };
    
    // 硬化地面
    public static ElementType Ground = new ElementType
    {
        Guid = Guid.ParseExact("{85AB36C8-FBB1-424B-C7C4-1F92576EC5BD}", "B").ToLcGuid(),
        Name = "Ground",
        DispalyName = "硬化地面",
        ClassType = typeof(QdGround)
    };
    
    // 用地红线
    public static ElementType PropertyLine = new ElementType
    {
        Guid = Guid.ParseExact("{6FF93D96-A3D3-DAC7-D720-8497DA8E3A9A}", "B").ToLcGuid(),
        Name = "PropertyLine",
        DispalyName = "用地红线",
        ClassType = typeof(QdPropertyLine)
    };
    
    // 开门边线
    public static ElementType OpenLine = new ElementType
    {
        Guid = Guid.ParseExact("{6FE950F0-9E13-FA8A-E216-AAE0D40BECBC}", "B").ToLcGuid(),
        Name = "OpenLine",
        DispalyName = "开门边线",
        ClassType = typeof(QdOpenLine)
    };
    
    // 场布设备
    public static ElementType LayoutEquipment = new ElementType
    {
        Guid = Guid.ParseExact("{552E316C-ADB2-43A7-AC77-9E0069ACEDC8}", "B").ToLcGuid(),
        Name = "LayoutEquipment",
        DispalyName = "场布设备",
        ClassType = typeof(QdLayoutEquipment)
    };
    
    // 注册所有元素类型
    public static ElementType[] All = new ElementType[]
    {
        Lawn, FoundationPit, Road, Earthwork, Berm, 
        Harden, Site, PropertyLine, Fence, PlateBuilding, 
        PlateBuildGroup, OpenLine
    };
}

5.2 元素基类

5.2.1 DirectComponent基类

FY_Layout的场布元素都继承自DirectComponent类:

public abstract class DirectComponent : LcElement
{
    // 组件定义
    public LcComponentDefinition Definition { get; protected set; }
    
    // 属性集
    public LcParameterSet Properties { get; protected set; }
    
    // 基础曲线(用于闭合区域元素)
    public Curve2d BaseCurve { get; set; }
    
    // 获取形状
    public virtual Curve2dGroupCollection GetShapes() { ... }
    
    // 初始化
    public virtual void Initilize(LcDocument doc) { ... }
    
    // 设置属性
    protected void SetProps(params (int propId, object value)[] props) { ... }
}

5.2.2 LcElement基类

所有元素的基类,提供核心功能:

public abstract class LcElement
{
    // 元素ID
    public LcGuid Id { get; set; }
    
    // 元素类型
    public ElementType Type { get; set; }
    
    // 所属文档
    public LcDocument Document { get; set; }
    
    // 图层名称
    public string Layer { get; set; }
    
    // 可见性
    public bool Visible { get; set; }
    
    // 包围盒
    public Box2 BoundingBox { get; protected set; }
    
    // 抽象方法
    public abstract Box2 GetBoundingBox();
    public abstract LcElement Clone();
    public virtual void Copy(LcElement src) { ... }
    
    // 平移
    public virtual void Translate(Vector2 offset) { ... }
    
    // 旋转
    public virtual void Rotate(Vector2 center, double angle) { ... }
    
    // 缩放
    public virtual void Scale(Vector2 center, double scale) { ... }
}

5.3 草坪元素实现

5.3.1 QdLawn类

namespace QdLayout
{
    public class QdLawn : DirectComponent
    {
        /// <summary>
        /// 底部高度
        /// </summary>
        public double Bottom
        {
            get => Properties.GetValue<double>("Bottom");
            set => SetProps((GetPropId(nameof(Bottom)), value));
        }
 
        /// <summary>
        /// 材质
        /// </summary>
        public MaterialInfo Material
        {
            get => Properties.GetValue<MaterialInfo>("Material");
            set => SetProps((GetPropId(nameof(Material)), value));
        }
        
        /// <summary>
        /// 轮廓
        /// </summary>
        public Polyline2d Outline
        {
            get => this.BaseCurve as Polyline2d;
            set => this.BaseCurve = value;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public QdLawn(QdLawnDef lawnDef) : base(lawnDef)
        {
            Type = LayoutElementType.Lawn;
            Outline = new Polyline2d();
        }

        /// <summary>
        /// 计算包围盒
        /// </summary>
        public override Box2 GetBoundingBox()
        {
            return new Box2().ExpandByPoints(
                GetShapes()[0].Curve2ds.SelectMany(n => n.GetPoints()).ToArray()
            );
        } 
        
        /// <summary>
        /// 批量插入后回调
        /// </summary>
        public override void OnBatchInsertAfter()
        {
            ResetBoundingBox();
        }
        
        /// <summary>
        /// 克隆元素
        /// </summary>
        public override LcElement Clone()
        {
            var clone = new QdLawn(Definition as QdLawnDef);
            clone.Copy(this);
            return clone;
        }

        /// <summary>
        /// 复制属性
        /// </summary>
        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var lawn = (QdLawn)src; 
            // 复制特有属性...
        } 
    }
}

5.3.2 QdLawnDef定义类

namespace QdLayout
{
    public class QdLawnDef : LcComponentDefinition
    {
        // 组件定义属性
        // 通常在构件库中定义
    }
}

5.4 基坑元素实现

5.4.1 QdFoundationPit类

基坑元素相比草坪有更多的专业属性:

namespace QdLayout
{
    public class QdFoundationPit : DirectComponent
    {
        /// <summary>
        /// 轮廓
        /// </summary>
        public Polyline2d Outline
        {
            get => this.BaseCurve as Polyline2d;
            set => this.BaseCurve = value;
        }
        
        /// <summary>
        /// 底部标高
        /// </summary>
        public double Bottom
        {
            get => Properties.GetValue<double>("Bottom");
            set
            {
                OnPropertyChangedBefore("Bottom", Properties.GetValue<double>("Bottom"), value);
                Properties.SetValue("Bottom", value);
                ResetCache();
                OnPropertyChangedAfter("Bottom", Properties.GetValue<double>("Bottom"), value);
            }
        }
        
        /// <summary>
        /// 顶部标高
        /// </summary>
        public double Elevation
        {
            get => Properties.GetValue<double>("Elevation");
            set
            {
                OnPropertyChangedBefore("Elevation", Properties.GetValue<double>("Elevation"), value);
                Properties.SetValue("Elevation", value);
                ResetCache();
                OnPropertyChangedAfter("Elevation", Properties.GetValue<double>("Elevation"), value);
            }
        }
        
        /// <summary>
        /// 放坡系数
        /// </summary>
        public double Factor
        {
            get => Properties.GetValue<double>("Factor");
            set
            {
                OnPropertyChangedBefore("Factor", Properties.GetValue<double>("Factor"), value);
                Properties.SetValue("Factor", value);
                ResetCache();
                OnPropertyChangedAfter("Factor", Properties.GetValue<double>("Factor"), value);
            }
        }
        
        /// <summary>
        /// 放坡模式:0-向外放坡,1-向内放坡
        /// </summary>
        public int Pattern
        {
            get => Properties.GetValue<int>("Pattern");
            set
            {
                OnPropertyChangedBefore("Pattern", Properties.GetValue<int>("Pattern"), value);
                Properties.SetValue("Pattern", value);
                ResetCache();
                OnPropertyChangedAfter("Pattern", Properties.GetValue<int>("Pattern"), value);
            }
        }

        public QdFoundationPit(QdFoundationPitDef def) : base(def)
        {
            Type = LayoutElementType.FoundationPit;
            Outline = new Polyline2d();
        }
        
        public override Box2 GetBoundingBox()
        {
            return new Box2().ExpandByPoints(
                GetShapes()[0].Curve2ds.SelectMany(n => n.GetPoints()).ToArray()
            );
        }
        
        public override LcElement Clone()
        {
            var clone = new QdFoundationPit(Definition as QdFoundationPitDef);
            clone.Copy(this);
            return clone;
        }
    }
}

5.4.2 放坡计算

基坑的关键特性是支持放坡计算:

放坡宽度 = (顶标高 - 底标高) × 放坡系数

向外放坡示意图:
    ┌───────────────────┐ ← 顶部轮廓(外扩)
   /                     \
  /         ← 坡面        \
 /                         \
┼───────────────────────────┼ ← 底部轮廓(原轮廓)

向内放坡示意图:
┼───────────────────────────┼ ← 顶部轮廓(原轮廓)
 \                         /
  \        ← 坡面         /
   \                     /
    └───────────────────┘ ← 底部轮廓(内缩)

5.5 属性系统

5.5.1 LcParameterSet

属性集用于存储元素的参数化属性:

// 获取属性值
var value = Properties.GetValue<double>("Bottom");

// 设置属性值
Properties.SetValue("Bottom", -4000.0);

// 检查属性是否存在
if (Properties.ContainsKey("Factor"))
{
    // ...
}

5.5.2 属性变化通知

// 属性变化前
OnPropertyChangedBefore(string propName, object oldValue, object newValue);

// 属性变化后
OnPropertyChangedAfter(string propName, object oldValue, object newValue);

// 示例实现
public double MyProperty
{
    get => Properties.GetValue<double>("MyProperty");
    set
    {
        var oldValue = Properties.GetValue<double>("MyProperty");
        OnPropertyChangedBefore("MyProperty", oldValue, value);
        Properties.SetValue("MyProperty", value);
        ResetCache();  // 重置缓存,触发重绘
        OnPropertyChangedAfter("MyProperty", oldValue, value);
    }
}

5.5.3 PropertyObserver

属性观察者用于在属性面板中显示和编辑属性:

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))
                    return;
                (ele as QdFoundationPit).Bottom = bottom;
            }
        },
        new PropertyObserver
        {
            Name = "Pattern",
            DisplayName = "放坡方式",
            CategoryName = "Geometry",
            CategoryDisplayName = "几何图形",
            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;
            }
        }
    };
}

5.6 几何数据

5.6.1 Polyline2d

多段线是场布元素最常用的几何数据类型:

// 创建多段线
var poly = new Polyline2d();

// 添加线段
poly.AddCurve(new Line2d(new Vector2(0, 0), new Vector2(100, 0)));
poly.AddCurve(new Line2d(new Vector2(100, 0), new Vector2(100, 100)));
poly.AddCurve(new Line2d(new Vector2(100, 100), new Vector2(0, 100)));
poly.AddCurve(new Line2d(new Vector2(0, 100), new Vector2(0, 0)));

// 获取点列表
var points = poly.Curve2ds.SelectMany(c => c.GetPoints()).ToList();

// 克隆
var cloned = poly.Clone() as Polyline2d;

// 反向
poly.Reverse();

5.6.2 Curve2d基类

// 线段
public class Line2d : Curve2d
{
    public Vector2 Start { get; set; }
    public Vector2 End { get; set; }
    public Vector2 Dir => (End - Start).Normalize();
}

// 圆弧
public class Arc2d : Curve2d
{
    public Vector2 Center { get; set; }
    public double Radius { get; set; }
    public double StartAngle { get; set; }
    public double EndAngle { get; set; }
    public bool IsClockwise { get; set; }
}

5.6.3 Curve2dGroup

曲线组用于存储多条曲线:

var curveGroup = new Curve2dGroup
{
    Curve2ds = new ListEx<Curve2d>()
};

curveGroup.Curve2ds.Add(new Line2d(...));
curveGroup.Curve2ds.Add(new Arc2d(...));

5.7 元素注册

5.7.1 注册元素类型

在插件的Loaded()方法中注册:

public void Loaded()
{
    // 批量注册元素类型
    LcDocument.RegistElementTypes(LayoutElementType.All);
    
    // 或单独注册
    LcDocument.RegistElementType(LayoutElementType.Lawn);
    LcDocument.RegistElementType(LayoutElementType.FoundationPit);
}

5.7.2 注册元素操作

public void Loaded()
{
    // 注册二维操作类
    LcDocument.ElementActions.Add(LayoutElementType.Lawn, new LawnAction());
    LcDocument.ElementActions.Add(LayoutElementType.FoundationPit, new FoundationPitAction());
    
    // 注册三维操作类
    LcDocument.Element3dActions.Add(LayoutElementType.Lawn, new Lawn3dAction());
    LcDocument.Element3dActions.Add(LayoutElementType.FoundationPit, new FoundationPit3dAction());
}

5.8 创建自定义元素类型

5.8.1 完整示例

下面是创建一个自定义"停车位"元素的完整示例:

1. 定义ElementType

// 在LayoutElementType.cs中添加
public static ElementType ParkingSpace = new ElementType
{
    Guid = Guid.NewGuid().ToLcGuid(), // 生产环境使用固定GUID
    Name = "ParkingSpace",
    DispalyName = "停车位",
    ClassType = typeof(QdParkingSpace)
};

2. 创建元素类

// ParkingSpace/QdParkingSpace.cs
namespace QdLayout
{
    public class QdParkingSpace : DirectComponent
    {
        /// <summary>
        /// 轮廓
        /// </summary>
        public Polyline2d Outline
        {
            get => this.BaseCurve as Polyline2d;
            set => this.BaseCurve = value;
        }
        
        /// <summary>
        /// 停车位宽度
        /// </summary>
        public double Width
        {
            get => Properties.GetValue<double>("Width");
            set => SetProps((GetPropId(nameof(Width)), value));
        }
        
        /// <summary>
        /// 停车位长度
        /// </summary>
        public double Length
        {
            get => Properties.GetValue<double>("Length");
            set => SetProps((GetPropId(nameof(Length)), value));
        }
        
        /// <summary>
        /// 停车位编号
        /// </summary>
        public string Number
        {
            get => Properties.GetValue<string>("Number");
            set => SetProps((GetPropId(nameof(Number)), value));
        }
        
        public QdParkingSpace(QdParkingSpaceDef def) : base(def)
        {
            Type = LayoutElementType.ParkingSpace;
            Outline = new Polyline2d();
            Width = 2500;   // 默认宽度2.5米
            Length = 5000;  // 默认长度5米
        }
        
        public override Box2 GetBoundingBox()
        {
            if (Outline?.Curve2ds?.Count > 0)
            {
                return new Box2().ExpandByPoints(
                    Outline.Curve2ds.SelectMany(n => n.GetPoints()).ToArray()
                );
            }
            return new Box2();
        }
        
        public override LcElement Clone()
        {
            var clone = new QdParkingSpace(Definition as QdParkingSpaceDef);
            clone.Copy(this);
            return clone;
        }
        
        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var ps = (QdParkingSpace)src;
            this.Width = ps.Width;
            this.Length = ps.Length;
            this.Number = ps.Number;
        }
    }
    
    public class QdParkingSpaceDef : LcComponentDefinition
    {
        // 定义类
    }
}

3. 更新注册列表

// 在LayoutElementType.All中添加
public static ElementType[] All = new ElementType[]
{
    Lawn, FoundationPit, Road, ... , ParkingSpace
};

4. 在LayoutPlugin中注册操作类

public void Loaded()
{
    // ...
    LcDocument.ElementActions.Add(LayoutElementType.ParkingSpace, new ParkingSpaceAction());
}

5.9 元素的序列化

5.9.1 属性序列化

元素通过Properties自动序列化:

// 属性会自动参与序列化
public double Width
{
    get => Properties.GetValue<double>("Width");
    set => SetProps((GetPropId(nameof(Width)), value));
}

5.9.2 自定义序列化

对于复杂数据,可以实现自定义序列化:

public override void WriteToStream(BinaryWriter writer)
{
    base.WriteToStream(writer);
    
    // 写入自定义数据
    writer.Write(CustomData.Length);
    writer.Write(CustomData);
}

public override void ReadFromStream(BinaryReader reader)
{
    base.ReadFromStream(reader);
    
    // 读取自定义数据
    var length = reader.ReadInt32();
    CustomData = reader.ReadBytes(length);
}

5.10 本章小结

本章详细介绍了FY_Layout的元素类型系统:

  1. ElementType定义:GUID、名称、类型的配置
  2. 元素基类:DirectComponent和LcElement的继承关系
  3. 草坪实现:QdLawn的完整实现
  4. 基坑实现:专业属性(标高、放坡)的处理
  5. 属性系统:LcParameterSet和PropertyObserver
  6. 几何数据:Polyline2d、Curve2d的使用
  7. 元素注册:类型和操作类的注册
  8. 自定义元素:创建新元素类型的完整流程

理解元素类型系统后,下一章我们将学习场布元素的具体实现细节。


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