znlgis 博客

GIS开发与技术分享

第五章:核心数据模型详解

5.1 Core模块概述

5.1.1 LightCAD.Core的定位

LightCAD.Core是整个CAD系统的数据基础层,定义了所有图元实体、文档结构和元素类型系统。它位于MathLib之上、RenderUtils之下,是连接数学计算和图形显示的关键桥梁。

5.1.2 核心职责

  • 实体定义:定义所有2D和3D图元的数据结构
  • 文档管理:提供CAD文档的容器和组织结构
  • 元素类型系统:支持自定义元素类型的注册和管理
  • 属性系统:为实体提供可扩展的属性机制
  • 变更通知:通过IUpdateObject接口实现变更追踪

5.1.3 依赖关系

LightCAD.Core
├── 依赖: LightCAD.MathLib(数学计算)
└── 依赖: ThreeJs4Net.dll(字体管理 FontManager)

架构说明:Core对ThreeJs4Net的依赖主要来自字体管理功能(FontManager),这也是设计RenderUtils中间层的原因之一——使得在某些场景下可以绕过这个依赖。

5.2 文档模型(LcDocument)

5.2.1 文档结构

LcDocument是LightCAD中最顶层的数据容器,代表一个完整的CAD文档:

public class LcDocument
{
    /// <summary>
    /// 文档唯一标识
    /// </summary>
    public LcGuid Guid { get; set; }

    /// <summary>
    /// 文档名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 文档中的所有实体
    /// </summary>
    public EntityCollection Entities { get; }

    /// <summary>
    /// 图层管理器
    /// </summary>
    public LayerManager Layers { get; }

    /// <summary>
    /// 块定义集合
    /// </summary>
    public BlockCollection Blocks { get; }

    /// <summary>
    /// 线型管理
    /// </summary>
    public LineTypeManager LineTypes { get; }

    /// <summary>
    /// 文字样式管理
    /// </summary>
    public TextStyleManager TextStyles { get; }

    /// <summary>
    /// 标注样式管理
    /// </summary>
    public DimStyleManager DimStyles { get; }

    /// <summary>
    /// 文档单位设置
    /// </summary>
    public UnitSettings Units { get; set; }

    /// <summary>
    /// 文档变量
    /// </summary>
    public DocumentVariables Variables { get; }
}

5.2.2 实体集合管理

public class EntityCollection : IEnumerable<LcEntity>
{
    private readonly List<LcEntity> entities = new();

    /// <summary>
    /// 添加实体
    /// </summary>
    public void Add(LcEntity entity)
    {
        entity.Document = ownerDocument;
        entity.Handle = GenerateHandle();
        entities.Add(entity);
        OnEntityAdded(entity);
    }

    /// <summary>
    /// 删除实体
    /// </summary>
    public bool Remove(LcEntity entity)
    {
        if (entities.Remove(entity))
        {
            entity.Document = null;
            OnEntityRemoved(entity);
            return true;
        }
        return false;
    }

    /// <summary>
    /// 按类型查询
    /// </summary>
    public IEnumerable<T> OfType<T>() where T : LcEntity
    {
        return entities.OfType<T>();
    }

    /// <summary>
    /// 空间查询(包围盒过滤)
    /// </summary>
    public IEnumerable<LcEntity> FindInBounds(BoundingBox2d bounds)
    {
        return entities.Where(e => e.Bounds.Intersects(bounds));
    }
}

5.2.3 图层系统

public class LayerManager
{
    private readonly Dictionary<string, Layer> layers = new();

    /// <summary>
    /// 默认图层 "0"
    /// </summary>
    public Layer DefaultLayer { get; }

    /// <summary>
    /// 当前活动图层
    /// </summary>
    public Layer CurrentLayer { get; set; }

    /// <summary>
    /// 创建新图层
    /// </summary>
    public Layer Create(string name)
    {
        var layer = new Layer(name);
        layers[name] = layer;
        return layer;
    }

    /// <summary>
    /// 获取图层
    /// </summary>
    public Layer Get(string name)
    {
        return layers.TryGetValue(name, out var layer) ? layer : null;
    }
}

public class Layer
{
    public string Name { get; set; }
    public Color Color { get; set; }
    public string LineType { get; set; }
    public double LineWeight { get; set; }
    public bool IsVisible { get; set; } = true;
    public bool IsLocked { get; set; } = false;
    public bool IsFrozen { get; set; } = false;
}

5.3 实体基类(LcEntity)

5.3.1 实体基类定义

LcEntity是所有CAD实体的基类,定义了实体的通用属性和行为:

public abstract class LcEntity
{
    /// <summary>
    /// 实体的唯一句柄
    /// </summary>
    public long Handle { get; internal set; }

    /// <summary>
    /// 全局唯一标识符
    /// </summary>
    public LcGuid Guid { get; set; }

    /// <summary>
    /// 所属文档
    /// </summary>
    public LcDocument Document { get; internal set; }

    /// <summary>
    /// 所在图层
    /// </summary>
    public string LayerName { get; set; } = "0";

    /// <summary>
    /// 颜色
    /// </summary>
    public LcColor Color { get; set; }

    /// <summary>
    /// 线型
    /// </summary>
    public string LineType { get; set; }

    /// <summary>
    /// 线宽
    /// </summary>
    public double LineWeight { get; set; }

    /// <summary>
    /// 是否可见
    /// </summary>
    public bool IsVisible { get; set; } = true;

    /// <summary>
    /// 包围盒
    /// </summary>
    public abstract BoundingBox2d Bounds { get; }

    /// <summary>
    /// 实体类型标识
    /// </summary>
    public abstract string TypeName { get; }

    /// <summary>
    /// 几何变换
    /// </summary>
    public abstract void TransformBy(Matrix4d matrix);

    /// <summary>
    /// 克隆实体
    /// </summary>
    public abstract LcEntity Clone();

    /// <summary>
    /// 计算与点的最近距离
    /// </summary>
    public abstract double DistanceTo(Point2d point);
}

5.3.2 实体类层次结构

LcEntity(抽象基类)
│
├── 二维基本图元
│   ├── LcLine           # 直线
│   ├── LcArc            # 圆弧
│   ├── LcCircle         # 圆
│   ├── LcEllipse        # 椭圆
│   ├── LcPolyline       # 多段线
│   ├── LcPolygon        # 多边形
│   └── LcSpline         # 样条曲线
│
├── 三维图元
│   ├── LcLine3d         # 三维直线
│   ├── LcArc3d          # 三维圆弧
│   ├── LcCircle3d       # 三维圆
│   ├── LcCurve3d        # 三维曲线
│   ├── LcProfile3d      # 三维轮廓
│   ├── LcWorkPlane3d    # 工作平面
│   ├── LcGroup3         # 三维组
│   ├── LcArray3         # 三维阵列
│   └── LcContianerRef3  # 容器引用
│
├── 实体建模
│   ├── LcSolid3d        # 实体基类(26KB)
│   ├── LcExtrude3d      # 拉伸体
│   ├── LcRevolve3d      # 旋转体
│   ├── LcLoft3d         # 放样体
│   ├── LcBlend3d        # 融合体
│   ├── LcSweptBlend3d   # 扫掠融合体
│   ├── LcPushPull3d     # 推拉体
│   ├── LcMesh3d         # 网格体
│   └── LcCombination    # 布尔运算组合体
│
├── 标注元素
│   ├── LcDimension      # 标注基类
│   ├── LcDimLinear      # 线性标注
│   ├── LcDimAngular     # 角度标注
│   ├── LcDimRadial      # 半径标注
│   ├── LcDimDiameter    # 直径标注
│   └── LcDim3d          # 三维标注
│
├── 文本和表格
│   ├── LcText           # 文本(15KB)
│   └── LcTable          # 表格
│
└── 引用元素
    ├── LcBlockRef       # 块引用
    └── LcComponentRef   # 组件引用

5.4 元素类型系统(ElementType)

5.4.1 ElementType类

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

public class ElementType
{
    /// <summary>
    /// 唯一标识符
    /// </summary>
    public LcGuid Guid { get; set; }

    /// <summary>
    /// 内部名称(用于代码引用)
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 显示名称(用于UI显示)
    /// </summary>
    public string DisplayName { get; set; }

    /// <summary>
    /// 元素类别
    /// </summary>
    public string Category { get; set; }

    /// <summary>
    /// 创建器(工厂方法)
    /// </summary>
    public Func<LcEntity> Creator { get; set; }

    /// <summary>
    /// 属性定义集合
    /// </summary>
    public List<PropertyDefinition> Properties { get; set; }

    /// <summary>
    /// 图标资源路径
    /// </summary>
    public string IconPath { get; set; }

    /// <summary>
    /// 是否为三维元素
    /// </summary>
    public bool Is3D { get; set; }
}

5.4.2 元素类型注册

public class ElementTypeRegistry
{
    private static readonly Dictionary<LcGuid, ElementType> types = new();

    /// <summary>
    /// 注册元素类型
    /// </summary>
    public static void Register(ElementType type)
    {
        if (types.ContainsKey(type.Guid))
            throw new InvalidOperationException(
                $"元素类型 {type.Name} 已注册");

        types[type.Guid] = type;
    }

    /// <summary>
    /// 按GUID获取元素类型
    /// </summary>
    public static ElementType GetByGuid(LcGuid guid)
    {
        return types.TryGetValue(guid, out var type) ? type : null;
    }

    /// <summary>
    /// 按名称获取元素类型
    /// </summary>
    public static ElementType GetByName(string name)
    {
        return types.Values.FirstOrDefault(t => t.Name == name);
    }

    /// <summary>
    /// 获取某个类别下的所有类型
    /// </summary>
    public static IEnumerable<ElementType> GetByCategory(string category)
    {
        return types.Values.Where(t => t.Category == category);
    }
}

5.4.3 自定义元素类型示例

// 注册一个自定义的"门"元素类型
var doorType = new ElementType
{
    Guid = new LcGuid("door-standard-001"),
    Name = "Door",
    DisplayName = "标准门",
    Category = "建筑",
    Is3D = true,
    Creator = () => new DoorEntity(),
    Properties = new List<PropertyDefinition>
    {
        new PropertyDefinition("Width", typeof(double), 900.0),
        new PropertyDefinition("Height", typeof(double), 2100.0),
        new PropertyDefinition("OpenDirection", typeof(string), "左开"),
        new PropertyDefinition("Material", typeof(string), "木质")
    }
};

ElementTypeRegistry.Register(doorType);

5.5 属性系统

5.5.1 属性定义

public class PropertyDefinition
{
    /// <summary>
    /// 属性名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 属性类型
    /// </summary>
    public Type ValueType { get; set; }

    /// <summary>
    /// 默认值
    /// </summary>
    public object DefaultValue { get; set; }

    /// <summary>
    /// 显示名称
    /// </summary>
    public string DisplayName { get; set; }

    /// <summary>
    /// 属性分组
    /// </summary>
    public string Group { get; set; }

    /// <summary>
    /// 是否只读
    /// </summary>
    public bool IsReadOnly { get; set; }

    /// <summary>
    /// 值验证器
    /// </summary>
    public Func<object, bool> Validator { get; set; }

    public PropertyDefinition(string name, Type valueType, object defaultValue)
    {
        Name = name;
        ValueType = valueType;
        DefaultValue = defaultValue;
        DisplayName = name;
    }
}

5.5.2 属性值存储

public class PropertyBag
{
    private readonly Dictionary<string, object> values = new();
    private readonly List<PropertyDefinition> definitions;

    public PropertyBag(List<PropertyDefinition> definitions)
    {
        this.definitions = definitions;
        // 初始化默认值
        foreach (var def in definitions)
        {
            values[def.Name] = def.DefaultValue;
        }
    }

    /// <summary>
    /// 获取属性值
    /// </summary>
    public T Get<T>(string name)
    {
        if (values.TryGetValue(name, out var value))
            return (T)value;
        throw new KeyNotFoundException($"属性 {name} 不存在");
    }

    /// <summary>
    /// 设置属性值
    /// </summary>
    public void Set(string name, object value)
    {
        var def = definitions.Find(d => d.Name == name);
        if (def == null)
            throw new KeyNotFoundException($"属性 {name} 未定义");

        if (def.IsReadOnly)
            throw new InvalidOperationException($"属性 {name} 为只读");

        if (def.Validator != null && !def.Validator(value))
            throw new ArgumentException($"属性 {name} 的值无效");

        values[name] = value;
    }
}

5.6 变更通知系统

5.6.1 IUpdateObject接口

/// <summary>
/// 实体变更通知接口
/// </summary>
public interface IUpdateObject
{
    /// <summary>
    /// 当实体属性或几何发生变化时调用
    /// </summary>
    void OnUpdate(UpdateContext context);

    /// <summary>
    /// 是否需要更新
    /// </summary>
    bool NeedsUpdate { get; }
}

public class UpdateContext
{
    /// <summary>
    /// 更新类型
    /// </summary>
    public UpdateType Type { get; set; }

    /// <summary>
    /// 更新来源
    /// </summary>
    public object Source { get; set; }

    /// <summary>
    /// 更新数据
    /// </summary>
    public Dictionary<string, object> Data { get; set; }
}

public enum UpdateType
{
    GeometryChanged,    // 几何数据改变
    PropertyChanged,    // 属性改变
    StyleChanged,       // 样式改变
    TransformChanged,   // 变换改变
    VisibilityChanged,  // 可见性改变
    ParentChanged       // 父级改变
}

5.6.2 实体变更通知流程

public class LcLine : LcEntity, IUpdateObject
{
    private Point2d startPoint;
    private Point2d endPoint;

    public Point2d StartPoint
    {
        get => startPoint;
        set
        {
            if (!startPoint.IsEqualTo(value))
            {
                startPoint = value;
                NeedsUpdate = true;
                OnUpdate(new UpdateContext
                {
                    Type = UpdateType.GeometryChanged,
                    Source = this,
                    Data = new Dictionary<string, object>
                    {
                        ["Property"] = nameof(StartPoint)
                    }
                });
            }
        }
    }

    public bool NeedsUpdate { get; private set; }

    public void OnUpdate(UpdateContext context)
    {
        // 重新计算包围盒
        RecalculateBounds();

        // 通知文档
        Document?.OnEntityChanged(this, context);

        NeedsUpdate = false;
    }
}

5.7 LcGuid标识系统

5.7.1 LcGuid设计

LightCAD使用自定义的LcGuid类型来标识实体和元素类型,而不是直接使用System.Guid:

public struct LcGuid : IEquatable<LcGuid>
{
    private readonly string value;

    public LcGuid(string value)
    {
        this.value = value ?? throw new ArgumentNullException(nameof(value));
    }

    public static LcGuid NewGuid()
    {
        return new LcGuid(Guid.NewGuid().ToString());
    }

    public bool Equals(LcGuid other)
    {
        return string.Equals(value, other.value,
            StringComparison.OrdinalIgnoreCase);
    }

    public override int GetHashCode()
    {
        return value?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0;
    }

    public override string ToString() => value;
}

5.8 颜色系统

5.8.1 LcColor

public struct LcColor
{
    public byte R { get; set; }
    public byte G { get; set; }
    public byte B { get; set; }
    public byte A { get; set; }

    /// <summary>
    /// 颜色来源
    /// </summary>
    public ColorSource Source { get; set; }

    /// <summary>
    /// AutoCAD颜色索引
    /// </summary>
    public int AciIndex { get; set; }

    // 预定义颜色
    public static readonly LcColor Red = new LcColor(255, 0, 0);
    public static readonly LcColor Green = new LcColor(0, 255, 0);
    public static readonly LcColor Blue = new LcColor(0, 0, 255);
    public static readonly LcColor White = new LcColor(255, 255, 255);
    public static readonly LcColor Black = new LcColor(0, 0, 0);
    public static readonly LcColor ByLayer = new LcColor { Source = ColorSource.ByLayer };
    public static readonly LcColor ByBlock = new LcColor { Source = ColorSource.ByBlock };
}

public enum ColorSource
{
    Explicit,   // 直接指定颜色
    ByLayer,    // 随层颜色
    ByBlock     // 随块颜色
}

5.9 坐标系与单位

5.9.1 坐标系统

LightCAD使用标准的右手坐标系:

    Y
    ↑
    |
    |
    +------→ X
   /
  /
 Z(指向屏幕外)

5.9.2 单位系统

public class UnitSettings
{
    /// <summary>
    /// 长度单位
    /// </summary>
    public LengthUnit LengthUnit { get; set; } = LengthUnit.Millimeter;

    /// <summary>
    /// 角度单位
    /// </summary>
    public AngleUnit AngleUnit { get; set; } = AngleUnit.Degree;

    /// <summary>
    /// 显示精度(小数位数)
    /// </summary>
    public int Precision { get; set; } = 4;

    /// <summary>
    /// 单位转换
    /// </summary>
    public double ConvertTo(double value, LengthUnit targetUnit)
    {
        // 先转换为毫米,再转换为目标单位
        var mmValue = value * GetMillimeterFactor(LengthUnit);
        return mmValue / GetMillimeterFactor(targetUnit);
    }
}

public enum LengthUnit
{
    Millimeter,  // 毫米
    Centimeter,  // 厘米
    Meter,       // 米
    Inch,        // 英寸
    Foot         // 英尺
}

public enum AngleUnit
{
    Degree,      // 度
    Radian,      // 弧度
    Gradian      // 百分度
}

5.10 文档序列化

5.10.1 序列化框架

LightCAD的文档序列化支持将完整的文档模型保存到文件和从文件加载:

public class DocumentSerializer
{
    /// <summary>
    /// 保存文档到文件
    /// </summary>
    public void Save(LcDocument document, string filePath)
    {
        using var stream = File.Create(filePath);
        using var writer = new BinaryWriter(stream);

        // 写入文件头
        WriteHeader(writer, document);

        // 写入图层表
        WriteLayers(writer, document.Layers);

        // 写入实体
        WriteEntities(writer, document.Entities);

        // 写入块定义
        WriteBlocks(writer, document.Blocks);
    }

    /// <summary>
    /// 从文件加载文档
    /// </summary>
    public LcDocument Load(string filePath)
    {
        using var stream = File.OpenRead(filePath);
        using var reader = new BinaryReader(stream);

        var document = new LcDocument();

        // 读取文件头
        ReadHeader(reader, document);

        // 读取图层表
        ReadLayers(reader, document);

        // 读取实体
        ReadEntities(reader, document);

        // 读取块定义
        ReadBlocks(reader, document);

        return document;
    }
}

5.11 Core模块的扩展方式

5.11.1 创建自定义实体

要创建新的自定义实体,需要继承LcEntity并实现所有抽象方法:

public class LcCustomElement : LcEntity, IUpdateObject
{
    // 自定义几何数据
    public Point2d Position { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public double Rotation { get; set; }

    // 自定义属性
    public PropertyBag Properties { get; }

    public override string TypeName => "CustomElement";

    public override BoundingBox2d Bounds
    {
        get
        {
            // 计算旋转后的包围盒
            var corners = GetCorners();
            return BoundingBox2d.FromPoints(corners);
        }
    }

    public override void TransformBy(Matrix4d matrix)
    {
        var p3d = new Point3d(Position.X, Position.Y, 0);
        var transformed = matrix.Transform(p3d);
        Position = new Point2d(transformed.X, transformed.Y);
    }

    public override LcEntity Clone()
    {
        return new LcCustomElement
        {
            Position = this.Position,
            Width = this.Width,
            Height = this.Height,
            Rotation = this.Rotation,
            Color = this.Color,
            LayerName = this.LayerName
        };
    }

    public override double DistanceTo(Point2d point)
    {
        // 计算点到元素的最近距离
        return GeometryUtils.PointToRectDistance(
            point, Position, Width, Height, Rotation);
    }

    // IUpdateObject实现
    public bool NeedsUpdate { get; private set; }

    public void OnUpdate(UpdateContext context)
    {
        NeedsUpdate = false;
    }
}

5.11.2 注册自定义元素类型

// 在插件初始化时注册
public class MyPlugin
{
    public void Initialize()
    {
        var customType = new ElementType
        {
            Guid = new LcGuid("my-custom-element-type"),
            Name = "CustomElement",
            DisplayName = "自定义元素",
            Category = "自定义",
            Is3D = false,
            Creator = () => new LcCustomElement
            {
                Width = 100,
                Height = 50
            }
        };

        ElementTypeRegistry.Register(customType);
    }
}

5.12 本章小结

本章详细介绍了LightCAD.Core模块的核心数据模型,包括文档结构(LcDocument)、实体基类(LcEntity)、元素类型系统(ElementType)、属性系统、变更通知机制、颜色系统和坐标单位等。理解这些数据模型是后续学习二维图元、三维建模和渲染系统的基础。Core模块的设计体现了良好的面向对象原则,通过接口和抽象类提供了强大的扩展能力。


上一章第四章:数学库详解

下一章第六章:二维图元系统