znlgis 博客

GIS开发与技术分享

第十五章:文件格式与数据交换

15.1 概述

15.1.1 FY_Layout的文件格式支持

飞扬集成设计平台(FY_Layout)作为一个开放的BIM设计平台,在文件格式和数据交换方面具有良好的兼容性。基于LightCAD框架,平台支持多种主流CAD/BIM文件格式的读写操作。

核心文件格式支持包括:

格式 扩展名 读取 写入 依赖库
DWG .dwg netDxf
DXF .dxf netDxf
SVG .svg Svg
JSON .json Newtonsoft.Json
SQLite .db Dapper + SQLite

15.1.2 依赖库说明

FY_Layout的文件格式支持依赖以下第三方库(在QdLayout.csproj中引用):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
    <!-- DXF/DWG格式支持 -->
    <Reference Include="netDxf">
      <HintPath>..\Libs\netDxf.dll</HintPath>
    </Reference>

    <!-- JSON序列化 -->
    <Reference Include="Newtonsoft.Json">
      <HintPath>..\Libs\Newtonsoft.Json.dll</HintPath>
    </Reference>

    <!-- SVG格式支持 -->
    <Reference Include="Svg">
      <HintPath>..\Libs\Svg.dll</HintPath>
    </Reference>

    <!-- 数据库访问 -->
    <Reference Include="Dapper">
      <HintPath>..\Libs\Dapper.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

15.2 DWG/DXF格式支持

15.2.1 netDxf库简介

FY_Layout使用netDxf开源库实现DWG和DXF格式的读写。netDxf是一个纯C#实现的DXF格式解析库,支持AutoCAD DXF格式从R12到最新版本。

15.2.2 DXF文件读取

// DXF文件读取示例
using netDxf;

public class DxfImporter
{
    public static void ImportDxfFile(string filePath, LcDocument document)
    {
        // 打开DXF文件
        var dxfDocument = DxfDocument.Load(filePath);
        if (dxfDocument == null)
        {
            throw new Exception($"无法打开DXF文件: {filePath}");
        }

        // 导入图层
        foreach (var layer in dxfDocument.Layers)
        {
            var lcLayer = document.CreateObject<LcLayer>();
            lcLayer.Name = layer.Name;
            lcLayer.Color = ConvertColor(layer.Color);
            document.Layers.Add(lcLayer);
        }

        // 导入线段
        foreach (var line in dxfDocument.Entities.Lines)
        {
            var lcLine = document.CreateObject<LcLine>();
            lcLine.Start = new Vector2(line.StartPoint.X, line.StartPoint.Y);
            lcLine.End = new Vector2(line.EndPoint.X, line.EndPoint.Y);
            lcLine.Layer = line.Layer.Name;
            document.Elements.Add(lcLine);
        }

        // 导入多段线
        foreach (var polyline in dxfDocument.Entities.Polylines2D)
        {
            var lcPoly = document.CreateObject<LcPolyLine>();
            var poly2d = new Polyline2d();
            var vertices = polyline.Vertexes.ToList();
            for (int i = 0; i < vertices.Count - 1; i++)
            {
                poly2d.Curve2ds.Add(new Line2d(
                    new Vector2(vertices[i].Position.X, vertices[i].Position.Y),
                    new Vector2(vertices[i+1].Position.X, vertices[i+1].Position.Y)
                ));
            }
            if (polyline.IsClosed && vertices.Count > 2)
            {
                poly2d.Curve2ds.Add(new Line2d(
                    new Vector2(vertices.Last().Position.X, vertices.Last().Position.Y),
                    new Vector2(vertices.First().Position.X, vertices.First().Position.Y)
                ));
                poly2d.IsClosed = true;
            }
            lcPoly.Curve = poly2d;
            lcPoly.Layer = polyline.Layer.Name;
            document.Elements.Add(lcPoly);
        }

        // 导入圆弧
        foreach (var arc in dxfDocument.Entities.Arcs)
        {
            var lcArc = document.CreateObject<LcArc>();
            lcArc.Curve = new Arc2d
            {
                Center = new Vector2(arc.Center.X, arc.Center.Y),
                Radius = arc.Radius,
                StartAngle = arc.StartAngle * Math.PI / 180,
                EndAngle = arc.EndAngle * Math.PI / 180
            };
            lcArc.Layer = arc.Layer.Name;
            document.Elements.Add(lcArc);
        }

        // 导入圆
        foreach (var circle in dxfDocument.Entities.Circles)
        {
            var lcCircle = document.CreateObject<LcCircle>();
            lcCircle.Center = new Vector2(circle.Center.X, circle.Center.Y);
            lcCircle.Radius = circle.Radius;
            lcCircle.Layer = circle.Layer.Name;
            document.Elements.Add(lcCircle);
        }
    }

    private static int ConvertColor(AciColor color)
    {
        // ACI颜色转换为RGB整数
        var rgb = color.ToColor();
        return (rgb.R << 16) | (rgb.G << 8) | rgb.B;
    }
}

15.2.3 DXF文件导出

// DXF文件导出示例
public class DxfExporter
{
    public static void ExportToDxf(LcDocument document, string filePath)
    {
        var dxfDoc = new DxfDocument();

        // 导出图层
        foreach (var layer in document.Layers)
        {
            var dxfLayer = new netDxf.Tables.Layer(layer.Name)
            {
                Color = new AciColor(
                    (byte)((layer.Color >> 16) & 0xFF),
                    (byte)((layer.Color >> 8) & 0xFF),
                    (byte)(layer.Color & 0xFF))
            };
            dxfDoc.Layers.Add(dxfLayer);
        }

        // 导出场布元素
        foreach (var element in document.Elements)
        {
            if (element is QdLawn lawn)
            {
                ExportLawn(dxfDoc, lawn);
            }
            else if (element is QdFoundationPit pit)
            {
                ExportFoundationPit(dxfDoc, pit);
            }
            else if (element is QdFence fence)
            {
                ExportFence(dxfDoc, fence);
            }
            // ... 其他元素类型
        }

        dxfDoc.Save(filePath);
    }

    private static void ExportLawn(DxfDocument dxfDoc, QdLawn lawn)
    {
        // 将草坪轮廓导出为DXF多段线
        var vertices = new List<netDxf.Entities.Polyline2DVertex>();
        foreach (var curve in lawn.Outline.Curve2ds)
        {
            if (curve is Line2d line)
            {
                vertices.Add(new netDxf.Entities.Polyline2DVertex(
                    line.Start.X, line.Start.Y));
            }
        }
        var polyline = new netDxf.Entities.Polyline2D(vertices, true);
        polyline.Layer = new netDxf.Tables.Layer("Layout_Lawn");
        dxfDoc.Entities.Add(polyline);
    }
}

15.3 JSON数据序列化

15.3.1 元素序列化机制

FY_Layout使用Newtonsoft.Json进行数据的序列化和反序列化,特别是在元素属性的持久化和配置管理中。

// 元素属性序列化
public class ElementSerializer
{
    // 序列化场布元素到JSON
    public static string SerializeElement(DirectComponent element)
    {
        var data = new Dictionary<string, object>
        {
            ["Type"] = element.Type.Name,
            ["Guid"] = element.Type.Guid.ToString(),
            ["Layer"] = element.Layer,
            ["Properties"] = SerializeProperties(element.Properties)
        };

        // 序列化几何数据
        if (element.BaseCurve is Polyline2d outline)
        {
            data["Outline"] = SerializePolyline(outline);
        }

        return JsonConvert.SerializeObject(data, Formatting.Indented);
    }

    // 序列化属性集
    private static Dictionary<string, object> SerializeProperties(
        LcParameterSet properties)
    {
        var dict = new Dictionary<string, object>();
        foreach (var key in properties.Keys)
        {
            var value = properties[key];
            if (value is MaterialInfo material)
            {
                dict[key] = new
                {
                    Uuid = material.Uuid,
                    Name = material.Name
                };
            }
            else
            {
                dict[key] = value;
            }
        }
        return dict;
    }

    // 序列化多段线
    private static List<double[]> SerializePolyline(Polyline2d polyline)
    {
        var points = new List<double[]>();
        foreach (var curve in polyline.Curve2ds)
        {
            foreach (var point in curve.GetPoints())
            {
                points.Add(new[] { point.X, point.Y });
            }
        }
        return points;
    }
}

15.3.2 反序列化元素

// 从JSON反序列化场布元素
public static DirectComponent DeserializeElement(
    string json, LcDocument document, DocumentRuntime docRt)
{
    var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
    var typeName = data["Type"].ToString();

    DirectComponent element = null;

    switch (typeName)
    {
        case "Lawn":
            var lawnDef = docRt.GetUseComDef(
                "绿色文明", "草坪", null) as QdLawnDef;
            var lawn = new QdLawn(lawnDef);
            lawn.Initilize(document);
            element = lawn;
            break;

        case "FoundationPit":
            var pitDef = docRt.GetUseComDef(
                "基坑工程", "基坑", null) as QdFoundationPitDef;
            var pit = new QdFoundationPit(pitDef);
            pit.Initilize(document);
            element = pit;
            break;

        // ... 其他元素类型
    }

    if (element != null)
    {
        // 恢复属性
        var properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(
            data["Properties"].ToString());
        foreach (var kvp in properties)
        {
            element.Properties.SetValue(kvp.Key, kvp.Value);
        }

        // 恢复几何数据
        if (data.ContainsKey("Outline"))
        {
            var points = JsonConvert.DeserializeObject<List<double[]>>(
                data["Outline"].ToString());
            var outline = new Polyline2d();
            for (int i = 0; i < points.Count - 1; i++)
            {
                outline.Curve2ds.Add(new Line2d(
                    new Vector2(points[i][0], points[i][1]),
                    new Vector2(points[i+1][0], points[i+1][1])));
            }
            element.BaseCurve = outline;
        }

        element.Layer = data["Layer"].ToString();
        element.ResetBoundingBox();
    }

    return element;
}

15.3.3 配置文件管理

FY_Layout使用JSON格式存储场布配置信息:

// 场布配置类 - QdLayoutCategorySettings
public class QdLayoutCategorySettings
{
    // 默认图层颜色配置
    public Dictionary<string, int> LayerColors { get; set; }
        = new Dictionary<string, int>
    {
        ["Layout_Lawn"] = 0x00FF00,      // 绿色
        ["Layout_Fence"] = 0x808080,      // 灰色
        ["Layout_FoundationPit"] = 0xFFFF00, // 黄色
        ["Layout_Road"] = 0x808080,       // 灰色
        ["Layout_Berm"] = 0x8B4513,       // 棕色
        ["Layout_Barrier"] = 0xFF0000,    // 红色
    };

    // 默认材质配置
    public Dictionary<string, string> DefaultMaterials { get; set; }
        = new Dictionary<string, string>
    {
        ["Lawn"] = "Lawn",
        ["Fence"] = "Concrete",
        ["FoundationPit"] = "Metal1",
        ["Road"] = "Concrete",
    };

    // 保存配置
    public void Save(string filePath)
    {
        var json = JsonConvert.SerializeObject(this, Formatting.Indented);
        File.WriteAllText(filePath, json);
    }

    // 加载配置
    public static QdLayoutCategorySettings Load(string filePath)
    {
        if (!File.Exists(filePath))
            return new QdLayoutCategorySettings();
        var json = File.ReadAllText(filePath);
        return JsonConvert.DeserializeObject<QdLayoutCategorySettings>(json);
    }
}

15.4 SQLite数据存储

15.4.1 Dapper ORM集成

FY_Layout通过Dapper和SQLite实现结构化数据的持久化存储:

// 使用Dapper进行数据库操作
using Dapper;
using System.Data.SQLite;

public class LayoutDataStore
{
    private readonly string connectionString;

    public LayoutDataStore(string dbPath)
    {
        connectionString = $"Data Source={dbPath};Version=3;";
        InitializeDatabase();
    }

    // 初始化数据库表结构
    private void InitializeDatabase()
    {
        using var conn = new SQLiteConnection(connectionString);
        conn.Open();
        conn.Execute(@"
            CREATE TABLE IF NOT EXISTS LayoutElements (
                Id TEXT PRIMARY KEY,
                TypeName TEXT NOT NULL,
                LayerName TEXT,
                Properties TEXT,
                GeometryData TEXT,
                CreateTime DATETIME DEFAULT CURRENT_TIMESTAMP,
                UpdateTime DATETIME DEFAULT CURRENT_TIMESTAMP
            );

            CREATE TABLE IF NOT EXISTS PlateRoomConfig (
                Id INTEGER PRIMARY KEY AUTOINCREMENT,
                RoomName TEXT NOT NULL,
                Width REAL NOT NULL,
                Height REAL NOT NULL,
                Depth REAL NOT NULL,
                Description TEXT
            );
        ");
    }

    // 保存场布元素
    public void SaveElement(DirectComponent element)
    {
        using var conn = new SQLiteConnection(connectionString);
        conn.Open();
        var json = ElementSerializer.SerializeElement(element);
        conn.Execute(@"
            INSERT OR REPLACE INTO LayoutElements
            (Id, TypeName, LayerName, Properties, GeometryData, UpdateTime)
            VALUES (@Id, @TypeName, @LayerName, @Properties, @GeometryData,
                    CURRENT_TIMESTAMP)",
            new
            {
                Id = element.Id.ToString(),
                TypeName = element.Type.Name,
                LayerName = element.Layer,
                Properties = json,
                GeometryData = ""
            });
    }

    // 加载所有场布元素
    public List<dynamic> LoadElements()
    {
        using var conn = new SQLiteConnection(connectionString);
        conn.Open();
        return conn.Query("SELECT * FROM LayoutElements").ToList();
    }
}

15.4.2 板房配置数据库

板房系统使用SQLite存储房间配置信息:

// 板房房间配置管理
public class PlateRoomConfigManager
{
    private readonly LayoutDataStore dataStore;

    public PlateRoomConfigManager(LayoutDataStore store)
    {
        dataStore = store;
    }

    // 获取预定义的房间配置
    public List<PlateRoomConfig> GetRoomConfigs()
    {
        using var conn = new SQLiteConnection(dataStore.ConnectionString);
        conn.Open();
        return conn.Query<PlateRoomConfig>(
            "SELECT * FROM PlateRoomConfig ORDER BY RoomName").ToList();
    }

    // 保存房间配置
    public void SaveRoomConfig(PlateRoomConfig config)
    {
        using var conn = new SQLiteConnection(dataStore.ConnectionString);
        conn.Open();
        conn.Execute(@"
            INSERT INTO PlateRoomConfig
            (RoomName, Width, Height, Depth, Description)
            VALUES (@RoomName, @Width, @Height, @Depth, @Description)",
            config);
    }
}

// 房间配置数据类
public class PlateRoomConfig
{
    public int Id { get; set; }
    public string RoomName { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public double Depth { get; set; }
    public string Description { get; set; }
}

15.5 SVG格式导出

15.5.1 二维图形SVG导出

FY_Layout支持将二维设计图导出为SVG格式,便于在网页中展示:

// SVG导出工具
using Svg;

public class SvgExporter
{
    public static void ExportToSvg(LcDocument document, string filePath)
    {
        // 计算文档范围
        var bounds = CalculateBounds(document);
        var svgDoc = new SvgDocument
        {
            Width = new SvgUnit(SvgUnitType.Pixel, (float)bounds.Width),
            Height = new SvgUnit(SvgUnitType.Pixel, (float)bounds.Height),
            ViewBox = new SvgViewBox(
                (float)bounds.Min.X, (float)bounds.Min.Y,
                (float)bounds.Width, (float)bounds.Height)
        };

        // 导出各图层元素
        foreach (var element in document.Elements)
        {
            if (element is QdLawn lawn)
            {
                ExportLawnToSvg(svgDoc, lawn);
            }
            else if (element is QdFence fence)
            {
                ExportFenceToSvg(svgDoc, fence);
            }
            // ... 其他元素
        }

        svgDoc.Write(filePath);
    }

    private static void ExportLawnToSvg(SvgDocument svgDoc, QdLawn lawn)
    {
        var polygon = new SvgPolygon();
        var points = new SvgPointCollection();
        foreach (var curve in lawn.Outline.Curve2ds)
        {
            if (curve is Line2d line)
            {
                points.Add(new SvgUnit(SvgUnitType.Pixel, (float)line.Start.X));
                points.Add(new SvgUnit(SvgUnitType.Pixel, (float)line.Start.Y));
            }
        }
        polygon.Points = points;
        polygon.Fill = new SvgColourServer(System.Drawing.Color.Green);
        polygon.FillOpacity = 0.3f;
        polygon.Stroke = new SvgColourServer(System.Drawing.Color.DarkGreen);
        polygon.StrokeWidth = new SvgUnit(1);
        svgDoc.Children.Add(polygon);
    }
}

15.6 三维模型数据格式

15.6.1 GeometryData结构

FY_Layout的三维模型使用自定义的GeometryData结构存储几何数据:

// LightCAD.MathLib.GeometryData 结构
public class GeometryData
{
    // 顶点数组(x,y,z交替存储)
    public double[] Verteics { get; set; }

    // 索引数组(三角面片索引)
    public int[] Indics { get; set; }

    // 几何分组(用于多材质分配)
    public GeometryGroup[] Groups { get; set; }
}

public class GeometryGroup
{
    public string Name { get; set; }
    public int Start { get; set; }     // 起始索引
    public int Count { get; set; }     // 索引数量
    public int MaterialIndex { get; set; } // 材质索引
}

15.6.2 Solid3d与三维导出

// 三维实体数据结构
public class Solid3d
{
    public string Name { get; set; }
    public GeometryData Geometry { get; set; }
}

public class Solid3dCollection : List<Solid3d> { }

// Provider生成三维数据示例(草坪)
private static Solid3dCollection GetSolid_草坪(
    LcComponentDefinition definition,
    LcParameterSet pset,
    SolidCreator creator)
{
    var outline = pset.GetValue<Polyline2d>("Outline");
    var bottom = pset.GetValue<double>("Bottom");

    // 使用ThreeJs4Net的Shape创建几何体
    var shape = new Shape(outline.GetPoints().ToListEx());
    var coodMat = new Matrix4();
    coodMat.MakeBasis(
        new Vector3(1, 0, 0),
        new Vector3(0, 1, 0),
        new Vector3(0, 0, 1));

    // 拉伸几何体
    var platgeo = GeoModelUtil.GetStretchGeometryData(
        shape, coodMat, 0, -1).GetBufferGeometry();
    platgeo.translate(0, 0, bottom);

    return new Solid3dCollection()
    {
        new Solid3d()
        {
            Name = "Lawn",
            Geometry = new GeometryData()
            {
                Verteics = platgeo.attributes.position.array,
                Indics = platgeo.index.intArray,
                Groups = new GeometryGroup[1]
                {
                    new GeometryGroup
                    {
                        Name = "Geometry",
                        Start = 0,
                        Count = platgeo.index.intArray.Length,
                        MaterialIndex = 0
                    }
                }
            }
        }
    };
}

15.6.3 三维数据导出到通用格式

// 将三维数据导出为OBJ格式
public class ObjExporter
{
    public static void ExportToObj(
        Solid3dCollection solids, string filePath)
    {
        using var writer = new StreamWriter(filePath);
        writer.WriteLine("# FY_Layout 3D Export");
        writer.WriteLine($"# Generated: {DateTime.Now}");

        int vertexOffset = 0;
        foreach (var solid in solids)
        {
            writer.WriteLine($"o {solid.Name}");

            var vertices = solid.Geometry.Verteics;
            var indices = solid.Geometry.Indics;

            // 写入顶点
            for (int i = 0; i < vertices.Length; i += 3)
            {
                writer.WriteLine($"v {vertices[i]:F6} " +
                    $"{vertices[i+1]:F6} {vertices[i+2]:F6}");
            }

            // 写入面片(OBJ索引从1开始)
            for (int i = 0; i < indices.Length; i += 3)
            {
                writer.WriteLine($"f " +
                    $"{indices[i] + 1 + vertexOffset} " +
                    $"{indices[i+1] + 1 + vertexOffset} " +
                    $"{indices[i+2] + 1 + vertexOffset}");
            }

            vertexOffset += vertices.Length / 3;
        }
    }
}

15.7 材质系统与数据交换

15.7.1 MaterialInfo结构

// 材质信息类
public class MaterialInfo
{
    public string Uuid { get; set; }    // 材质唯一标识
    public string Name { get; set; }    // 材质名称
    public int Color { get; set; }      // 颜色值
    public float Opacity { get; set; }  // 不透明度
    public string TexturePath { get; set; } // 贴图路径
}

// 材质管理器 - 提供预定义材质
public static class MaterialManager
{
    public static readonly string LawnUuid = "lawn-material-uuid";
    public static readonly string ConcreteUuid = "concrete-material-uuid";
    public static readonly string Metal1Uuid = "metal1-material-uuid";

    // 获取预定义材质
    public static MaterialInfo GetMaterial(string uuid)
    {
        // 根据UUID返回对应的材质信息
        return MaterialStore.Get(uuid);
    }
}

15.7.2 材质在Provider中的应用

每个Provider都实现了GetSolidMats方法来指定三维模型的材质:

// 草坪Provider的材质获取
private static MaterialInfo[] GetSolidMats(
    LcComponentDefinition definition,
    LcParameterSet pset,
    SolidCreator creator,
    Solid3d solid)
{
    // 返回元素的Material属性作为三维材质
    return new MaterialInfo[] {
        pset.GetValue<MaterialInfo>("Material")
    };
}

// 基坑Provider的材质获取(多材质)
private static MaterialInfo[] GetSolidMats(
    LcComponentDefinition definition,
    LcParameterSet pset,
    SolidCreator creator,
    Solid3d solid)
{
    // 基坑底面使用金属材质,坡面使用混凝土材质
    return new MaterialInfo[] {
        MaterialManager.GetMaterial(MaterialManager.Metal1Uuid),
        MaterialManager.GetMaterial(MaterialManager.ConcreteUuid)
    };
}

15.8 数据交换最佳实践

15.8.1 导入工作流

DWG/DXF文件 → netDxf解析 → LightCAD元素 → 场布元素转换
     ↓                                         ↓
  读取图层     →    创建LcLayer    →    分配到对应图层
  读取线段     →    创建LcLine     →    转换为场布元素
  读取多段线   →    创建LcPolyLine →    识别闭合区域
  读取圆弧     →    创建LcArc      →    组合为复合轮廓

15.8.2 导出工作流

场布元素 → 几何数据提取 → 格式转换 → 文件写入
   ↓
 QdLawn      → Outline  → DXF Polyline → .dxf
 QdFence     → BaseCurve→ DXF Polyline → .dxf
 QdFoundation→ Outline  → DXF Polyline → .dxf
   ↓
 所有元素 → 3D Solid → OBJ/GLTF → .obj/.gltf

15.8.3 注意事项

  1. 坐标系统一致性:FY_Layout使用毫米为单位,导入DWG/DXF时需注意单位转换
  2. 图层映射:导入时需将外部图层映射到FY_Layout的标准图层命名
  3. 闭合性检查:导入的多段线需要检查闭合性,可使用LcCurveChangeLoop工具
  4. 材质兼容:导入的颜色信息需要映射到MaterialInfo系统
  5. 性能考虑:大文件导入时应使用异步操作,避免界面卡顿

15.9 本章小结

本章详细介绍了FY_Layout的文件格式支持和数据交换机制:

  1. DWG/DXF格式:通过netDxf库实现AutoCAD文件的读写兼容
  2. JSON序列化:使用Newtonsoft.Json实现元素属性的持久化和配置管理
  3. SQLite存储:通过Dapper ORM实现结构化数据的本地存储
  4. SVG导出:支持二维设计图的矢量图格式导出
  5. 三维数据:GeometryData结构支持三维模型的存储和导出
  6. 材质系统:MaterialInfo和MaterialManager提供统一的材质管理
  7. 最佳实践:导入导出工作流的规范化处理

良好的文件格式支持是CAD/BIM平台的基础能力。FY_Layout通过开放的文件格式和标准化的数据交换接口,实现了与主流设计工具的互通互联。


上一章:设备与模板排布系统 下一章:调试测试与性能优化