29. Revit API:扩展存储(ExtensibleStorage)

一、前言

开发中,我们可能需要记录一些信息,用在后续的处理上。

存储有几种方式:

  1. 存在元素参数上。若使用项目参数,很方便,但是容易照成"污染"。若使用元素参数,则涉及到修改族,修改再载入比较慢,如果族复杂,载入覆盖的过程可能会出问题。
  2. 存在本地数据库中,如SQLite。这是个好方式,适用于大多数情况,按需封装好后使用也很方便。但也有不好的情况,比如我需要自己生成的一些模型,记录一些可能的 userdata,并将其绑定到其他元素上,我希望跨机也能保持这个记录,而在线数据库又不能接受时,本地数据库就不行了。
  3. 存在Revit文档里,Revit提供了这个功能,可以通使用扩展存储来做到。按照 API 文档描述,存储较大的信息时,会有性能问题。

二、API

扩展存储相关的类都在Autodesk.Revit.DB.ExtensibleStorage命名空间下。

  • DataStorage,一个放在 Document 中的数据图元,不存在实体,有点类似于过滤器中ParameterFilterElement

2.1. Field 的要求

概括:涉及到浮点类型的,需要指定单位;只有Entity类型,需要指定 SubSchemaGUID

浮点类型注意:① 创建时需要指定数据单位类型,②添加时需要指定显示单位类型,③读取时需要指定显示单位类型。

Ⅰ、最好让添加和读取时的显示单位类型一致,否则 Revit 会进行单位转换,导致读取的值和写入的不一致。

Ⅱ、如果想要 RevitLookup 查看的值和写入的值一样,就将写入时的显示单位设置为DisplayUnitType.DUT_DECIMAL_FEET(英尺)。

【写入的值】 -->(转为英尺)--> 存储的值 -->(转为读取时指定的单位)--> 【输出的值】

Field 可选类型 NeedsUnits NeedsSubSchemaGUID
bool
byte, int
float, double
string
GUID
ElementId
UV, XYZ
Entity

三、示例

3.1. 简单示例

由于方法比较简单,就先给一个简单的例子

csharp 复制代码
public void SimpleDemo()
{
    var guid = new Guid("F2BBCD7B-9624-4356-AA92-2D87EF25C006");

    SchemaBuilder builder = new SchemaBuilder(guid);
    builder.SetReadAccessLevel(AccessLevel.Public)  // 访问级别
        .SetWriteAccessLevel(AccessLevel.Public)
        .SetVendorId("YourCompany")                 // 供应商 ID
        .SetSchemaName("SampleDemo")
        .SetDocumentation("简单的例子");

    // 添加字段
    builder.AddSimpleField("age", typeof(int))
        .SetDocumentation("年龄");

    builder.AddArrayField("hobby", typeof(string))
        .SetDocumentation("爱好");

    builder.AddMapField("score", typeof(string), typeof(double)) // 浮点类型需要设置单位
        .SetUnitType(UnitType.UT_Length)
        .SetDocumentation("成绩");

    builder.Finish();

    // 添加数据

    using Transaction transaction = new Transaction(this.Document, "Add Data to Schema");
    transaction.Start();

    DataStorage dataStorage = DataStorage.Create(this.Document);
    dataStorage.Name = "SimpleDemoDataStorage";
    Schema schema = Schema.Lookup(guid);
    Entity entity = new Entity(schema);

    entity.Set<int>("age", 25);
    entity.Set<IList<string>>("hobby", ["Reading", "Traveling", "Coding"]);
    entity.Set<IDictionary<string, double>>("score", new Dictionary<string, double>
        {
            { "Math", 95 },
            { "English", 88.5 },
            { "Science", 90.5 }
        }, DisplayUnitType.DUT_METERS);     // 浮点类型 存储时指定单位

    dataStorage.SetEntity(entity);

    transaction.Commit();

    // 读取数据

    Entity retrievedEntity = dataStorage.GetEntity(schema);
    double age = retrievedEntity.Get<int>("age");
    IList<string> hobbies = retrievedEntity.Get<IList<string>>("hobby");
    // 浮点类型读取时指定单位
    IDictionary<string, double> scores = retrievedEntity.Get<IDictionary<string, double>>("score", DisplayUnitType.DUT_METERS);

    string msg = $"Age: {age}\n" +
        $"Hobbies: {string.Join(", ", hobbies)}\n" +
        $"Scores: {string.Join(", ", scores.Select(kv => $"{kv.Key}: {kv.Value}"))}";

    TaskDialog.Show("Retrieved Data", msg);
}

3.2. 嵌套 Entity 示例

如果要和数据库表那样的,允许"多行"数据,就需要嵌套 Entity 来模仿,内部是作为行容器,外部的作为表容器。

也就是将 Filed 的类型设置为 Entity。

最简单的方式,就是存一个 Json 字符串。

注意不要在扩展存储中保存太多数据,会影响 Revit 的性能(API 文档中提示)。

csharp 复制代码
[Transaction(TransactionMode.Manual)]
internal class Test_ExtensibleStorage : ExternalCommand
{
    // 数据结构定义
    public class ProjectionData
    {
        public int Number { get; set; }
        public string PlaneType { get; set; }
        public XYZ XAxis { get; set; }
    }

    // 主 Schema GUID(固定,不要改)
    private static readonly Guid MainSchemaGuid = new Guid("B804CCAC-C1E9-4A26-919B-000000000000"); // 用你自己的 GUID 替换

    // 子 Schema GUID(每个记录的结构)
    private static readonly Guid SubSchemaGuid = new Guid("B804CCAC-C1E9-4A26-919B-000000000001"); // 用你自己的 GUID 替换

    public override void Execute()
    {
        Document doc = this.Document;

        // 步骤1: 选一个 Element 作为宿主(例如一个墙)

        //var hostElement = DataStorage.Create(doc);
        var hostElementReference = this.UiDocument.Selection.PickObject(ObjectType.Element, "请选择一个元素作为数据存储宿主");
        var hostElement = doc.GetElement(hostElementReference.ElementId);

        // 步骤2: 创建或获取 Schema(只需运行一次)

        using (Transaction tx = new Transaction(doc, "Create Schemas"))
        {
            tx.Start();
            GetOrCreateMainSchema();
            GetOrCreateSubSchema();
            tx.Commit();
        }

        // 步骤3: 示例数据(多行)
        List<ProjectionData> sampleData = new List<ProjectionData>
        {
            new ProjectionData { Number = 1, PlaneType = "XY", XAxis = new XYZ(1, 0, 0) },
            new ProjectionData { Number = 2, PlaneType = "XZ", XAxis = new XYZ(0, 1, 0) },
            new ProjectionData { Number = 3, PlaneType = "Custom", XAxis = new XYZ(0.5, 0.5, 0) }
        };

        // 步骤4: 存储数据
        SaveData(hostElement, sampleData);

        // 步骤5: 读取数据并显示
        List<ProjectionData> readData = ReadData(hostElement);
        string readResult = "读取的数据:\n" + string.Join("\n", readData.Select(d => $"Number: {d.Number}, PlaneType: {d.PlaneType}, XAxis: ({d.XAxis.X}, {d.XAxis.Y}, {d.XAxis.Z})"));
        TaskDialog.Show("读取结果", readResult);

        // 步骤6: 修改数据(例如添加一行新数据)
        ProjectionData newItem = new ProjectionData { Number = 4, PlaneType = "YZ", XAxis = new XYZ(0, 0, 1) };
        AddNewItem(hostElement, newItem);

        // 读取修改后数据
        readData = ReadData(hostElement);
        readResult = "修改后数据:\n" + string.Join("\n", readData.Select(d => $"Number: {d.Number}, PlaneType: {d.PlaneType}, XAxis: ({d.XAxis.X}, {d.XAxis.Y}, {d.XAxis.Z})"));
        TaskDialog.Show("修改后结果", readResult);

        // 步骤7: 修改现有数据(例如更新第二行的 XAxis)
        UpdateItem(hostElement, 2, new XYZ(0.1, 0.9, 0));

        //// 读取最终数据
        readData = ReadData(hostElement);
        readResult = "最终数据:\n" + string.Join("\n", readData.Select(d => $"Number: {d.Number}, PlaneType: {d.PlaneType}, XAxis: ({d.XAxis.X}, {d.XAxis.Y}, {d.XAxis.Z})"));
        TaskDialog.Show("最终结果", readResult);
    }

    public static Schema GetOrCreateMainSchema()
    {
        Schema schema = Schema.Lookup(MainSchemaGuid);
        if (schema != null) return schema;

        SchemaBuilder builder = new SchemaBuilder(MainSchemaGuid);
        builder.SetReadAccessLevel(AccessLevel.Public)
            .SetWriteAccessLevel(AccessLevel.Public)
            .SetVendorId("YourCompany")
            .SetSchemaName("ProjectionDataMain")
            .SetDocumentation("存储多行投影数据的数组");

        // 主字段:一个 Entity 数组
        builder.AddArrayField("Projections", typeof(Entity))
           .SetSubSchemaGUID(SubSchemaGuid);

        return builder.Finish();
    }

    // 创建或获取子 Schema(单行结构)
    private static Schema GetOrCreateSubSchema()
    {
        Schema schema = Schema.Lookup(SubSchemaGuid);
        if (schema != null) return schema;

        SchemaBuilder builder = new SchemaBuilder(SubSchemaGuid);
        builder.SetReadAccessLevel(AccessLevel.Public);
        builder.SetWriteAccessLevel(AccessLevel.Public);
        builder.SetVendorId("YourCompany");
        builder.SetSchemaName("ProjectionDataSub");
        builder.SetDocumentation("单行投影数据");

        // 子字段
        builder.AddSimpleField("Number", typeof(int));
        builder.AddSimpleField("PlaneType", typeof(string));
        builder.AddSimpleField("XAxis", typeof(XYZ))
            .SetUnitType(UnitType.UT_Length);

        return builder.Finish();
    }

    // 存储数据(覆盖式写入)

    private static void SaveData(Element hostElement, List<ProjectionData> dataList)
    {
        Document doc = hostElement.Document;
        using (Transaction tx = new Transaction(doc, "Save Projection Data"))
        {
            tx.Start();

            Schema mainSchema = Schema.Lookup(MainSchemaGuid);
            Schema subSchema = Schema.Lookup(SubSchemaGuid);

            Entity mainEntity = new Entity(mainSchema);
            IList<Entity> subEntities = new List<Entity>();

            foreach (var data in dataList)
            {
                Entity subEntity = new Entity(subSchema);
                subEntity.Set<int>("Number", data.Number);
                subEntity.Set<string>("PlaneType", data.PlaneType);
                subEntity.Set<XYZ>("XAxis", data.XAxis, DisplayUnitType.DUT_METERS);
                subEntities.Add(subEntity);
            }

            mainEntity.Set<IList<Entity>>("Projections", subEntities);
            hostElement.SetEntity(mainEntity);

            tx.Commit();
        }
    }

    // 读取数据
    private static List<ProjectionData> ReadData(Element hostElement)
    {
        List<ProjectionData> result = new List<ProjectionData>();

        Schema mainSchema = Schema.Lookup(MainSchemaGuid);
        if (mainSchema == null) return result;

        Entity mainEntity = hostElement.GetEntity(mainSchema);
        if (!mainEntity.IsValid()) return result;

        IList<Entity> subEntities = mainEntity.Get<IList<Entity>>("Projections");
        if (subEntities == null) return result;

        Schema subSchema = Schema.Lookup(SubSchemaGuid);

        foreach (Entity sub in subEntities)
        {
            if (sub.Schema.GUID != subSchema.GUID) continue;

            result.Add(new ProjectionData
            {
                Number = sub.Get<int>("Number"),
                PlaneType = sub.Get<string>("PlaneType"),
                XAxis = sub.Get<XYZ>("XAxis", DisplayUnitType.DUT_METERS)
            });
        }

        return result;
    }

    // 修改:添加一行新数据
    private static void AddNewItem(Element hostElement, ProjectionData newItem)
    {
        List<ProjectionData> currentData = ReadData(hostElement);
        currentData.Add(newItem);
        SaveData(hostElement, currentData); // 覆盖写入
    }

    // 修改:更新特定一行(按 Number 找,更新 XAxis 示例)
    private static void UpdateItem(Element hostElement, int targetNumber, XYZ newXAxis)
    {
        List<ProjectionData> currentData = ReadData(hostElement);
        var itemToUpdate = currentData.FirstOrDefault(d => d.Number == targetNumber);
        if (itemToUpdate != null)
        {
            itemToUpdate.XAxis = newXAxis;
        }
        SaveData(hostElement, currentData); // 覆盖写入
    }
}

四、总结

注意浮点类型的数据需要指定单位(创建、写入、读取)。

Schema GUID 不能有重复的。

其它没什么了,如果数据复杂,转为 Json 塞进去是最方便的。

相关推荐
saoys2 小时前
Opencv 学习笔记:图像卷积操作(锐化核实战 + 数据类型避坑)
笔记·opencv·学习
Coisinilove3 小时前
MATLAB学习笔记——第三章
笔记·学习·matlab
小乔的编程内容分享站3 小时前
C语言笔记一维&二维数组
c语言·笔记
火红色祥云5 小时前
深度学习入门:基于Python的理论与实现笔记
笔记·python·深度学习
Aliex_git5 小时前
gzip 压缩实践笔记
前端·网络·笔记·学习
liuchangng5 小时前
BMAD-METHOD实战笔记_20260213112550
笔记
2501_901147835 小时前
打家劫舍问题的动态规划解法与性能优化笔记
笔记·算法·动态规划
像豆芽一样优秀6 小时前
Easy-Vibe Task02学习笔记
笔记·学习
wdfk_prog6 小时前
EWMA、加权平均与一次低通滤波的对比与选型
linux·笔记·学习·游戏·ssh