cad.net 扩展数据和记录

前提

你应该知道C#字典Dictionary<K,V>遍历时候,
返回的KeyValuePair<K,V>吧,它是一个值类型.
每次遍历都拷贝出来,为了保护容器内容,避免你更改内部造成hashcode计算出错,
因此它必须要是值类型.
这种结构微软官方叫键值对,民间叫KV结构.

Acad的 ResultBuffer 和 TypedValue 就是类似的关系.

粤语的打pair就是打扑克牌的意思.

结果缓冲区ResultBuffer

即 Autodesk.AutoCAD.DatabaseServices.ResultBuffer 类型.
使用它时需要提供一组键值对数组.
键值对类型是: Autodesk.AutoCAD.DatabaseServices.TypedValue
TypedValue<TypeCode: DxfCode, Value: object>

键名是TypeCode:
为什么不叫它Key呢?主要是凸显它也是一个组码吧.
是一个 16bit 整型数据,
类型为: Autodesk.AutoCAD.DatabaseServices.DxfCode;
ResultBuffer实例的使用位置决定了组码范围,
用于扩展记录(Data弱类型)范围,就不适合于扩展数据(XData)范围.

值名是Value:
是一个System.Object的实例,它可以包含任何类型的数据,
但是由于必须是键指明的类型,所以其实它约束了value的类型.

创建

有两种方法可以创建,总数据大小不能超过 128K:

1, 使用构造函数:

    var resBuf = new ResultBuffer(new TypedValue((int)DxfCode.Text, "我的扩展数据"));

2, 使用 Add 方法:

    var resBuf = new ResultBuffer();
    resBuf.Add(new TypedValue((int)DxfCode.Text, "我的扩展数据"));
    resBuf.Add(new TypedValue((int)DxfCode.Real, 20.0));
    resBuf.Add(new TypedValue((int)DxfCode.Int32, 5));

3, 推荐
由于它是非托管堆内存,
所以我们最好是先构造TypedValue[]值类型数组,会在栈帧内创建.
再最后给ResultBuffer的构造函数,避免出现需要Dispose情况.

    var tvs = TypedValue[] {
       new TypedValue((int)DxfCode.Text, "我的扩展数据"),
       new TypedValue((int)DxfCode.Real, 20.0),
       new TypedValue((int)DxfCode.Int32, 5),
    }
    obj.XData = new ResultBuffer(tvs);

注意:
你要把ResultBuffer想象成一段只读内存.
除了创建的时候你允许操作,
设置进入图元或许DBObject对象之后,就不能Add了.

你不能这样写,
桌子它没有把Data封装到内部,
此时它若封装得好,应该提供一个只读类型,或者封装步骤.

ent.Data.Add(...)

你要这样写:

var tvs = new TypedValue[]{...}
ent.Data = new ResultBuffer(tvs);

移除

它没有Remove方法,所以你需要过滤再设置回去.
例如:

public void Remove(ObjectId id, ObjectId removeTarget) {
    var tr = DBTrans.Top;
    var tar = removeTarget.Handle.ToString();
    var obj = tr.GetObject(id);
    var tvs = obj.Data?.AsArray();
    if (tvs is null) return;
    tvs = tvs.Where(x => !x.Value.Equals(tar)).ToArray();
    obj.Data = new ResultBuffer(tvs);
}

扩展数据DBObject.XData

AutoCAD 数据库对象 DBObject 都可以灵活地添加一定数量的自定义数据,供开发者使用.
这些数据的含义由开发者自行解释,
AutoCAD 只维护这些数据而不管其具体的含义,这些数据被称为扩展数据(XData).
扩展数据以 ResultBuffer 形式附加在实体上,
因此能够有效地利用存储空间,对于添加轻量的数据非常方便.

可以通过实体 DBObject 类及其派生类的 XData 属性获取或设置扩展数据.

// 不限定是图元entity,图层表记录,等等类型也可以...
var obj = tr.GetObject(id);
obj.XData

实体的扩展数据由应用程序创建,附着在实体的扩展数据可以包含一个或多个组.
每一组均以一个互不相同的注册应用程序名开头.

扩展数据 XData 所支持的 TypedValue.TypeCode 属性值(DXF 组码)
只能采用 1000~1071 之间的组码值,不同组码对应不同类型的信息,
各个组码的说明如下表所示:

DXF 组码值 扩展数据内容
1000~1009 字符串(最多不超过255个字符)
1001 XData 的应用程序名
1002 XData 的控制字符串
1003 图层名
1004 二进制数据
1005 数据库对象句柄
1010~1059 浮点数
1010,1020,1030 三维点(x, y, z)
1011,1021,1031 三维空间位置
1012,1022,1032 三维空间距离
1013,1023,1033 三维空间方向
1040 XData 中的浮点数
1041 XData 中的距离值
1042 XData 中的比例系数
1060~1070 16bit 整数
1071 32bit 整数

由于每个数据库对象可以附加多个应用程序的数据,所以在 ResultBuffer 中,
应用程序名是每段扩展数据的第一个数据,其后的结果缓冲数据都归此应用程序名所有.
(也可以使用其他的特殊标识作为第一个数据,总之目的是为了便于区分,方便后期查询此扩展数据)

注册应用程序名

AutoCAD 将注册的应用程序名称保存于数据库中的 RegAppTable 表中.
在使用之前必须首先检测是否已经存在,没有则需要注册,
也就是创建一个RegAppTableRecord表记录.
注册程序的名字最长可达 31 个字符.

using var regTable = (RegAppTable)trans.GetObject(db.RegAppTableId, OpenMode.ForWrite);
if (!regTable.Has("MyAppName")) {
    var record = new RegAppTableRecord();
    record.Name = "MyAppName";
    regTable.Add(record);
    trans.AddNewlyCreatedDBObject(record, true);
}

添加扩展数据到数据库对象上,
首先需要获取数据注册应用程序名,
然后通过DBObject类及其派生类的XData属性设置.

代码所示:

public void AddXData() {
    Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("添加扩充数据 XData \n");

    var entOps = new PromptEntityOptions("选择实体对象");
    var entRes = ed.GetEntity(entOps);
    if (entRes.Status != PromptStatus.OK) {
        ed.WriteMessage("选择对象失败,退出");
        return;
    }

    ObjectId objId = entRes.ObjectId;
    var db = HostApplicationServices.WorkingDatabase;
    using var trans = db.TransactionManager.StartTransaction();
    using var ent = (Entity)trans.GetObject(objId, OpenMode.ForWrite);
    ent.ColorIndex = 1;

    using var regTable = (RegAppTable)trans.GetObject(db.RegAppTableId, OpenMode.ForWrite);
    if (!regTable.Has("MyAppName")) {
        var record = new RegAppTableRecord();
        record.Name = "MyAppName";
        regTable.Add(record);
        trans.AddNewlyCreatedDBObject(record, true);
    }

    var tvs = new TypedValue[] {
        new TypedValue(1001, "我的扩展数据应用程序"),
        new TypedValue(1000, "作者:惊惊"),
    };
    ent.XData = new ResultBuffer(tvs);
    trans.Commit();
}

查:遍历

从指定的对象返回所附着的扩展数据,
通过DBObject类及其派生类的XData属性,
返回类型为ResultBuffer

public void GetXdata() {
    var ed = Acap.DocumentManager.MdiActiveDocument.Editor;
    ed.WriteMessage("获取扩充数据 XData \n");

    var entOps = new PromptEntityOptions("选择实体对象");
    var entRes = ed.GetEntity(entOps);
    if (entRes.Status != PromptStatus.OK) {
        ed.WriteMessage("选择对象失败,退出");
        return;
    }

    var db = HostApplicationServices.WorkingDatabase;
    using var tr = db.TransactionManager.StartTransaction();
    using var ent = (Entity)tr.GetObject(entRes.ObjectId, OpenMode.ForRead);

    // 使用自带方法转为数组: 推荐
    TypedValue[] tvs = ent.XData?.AsArray();
    if (tvs is null) return;
    foreach(var tv in tvs) {
        ed.WriteMessage(tv.TypeCode.ToString() + ": ");
        ed.WriteMessage(tv.Value.ToString() + "\n");
    }

/*
    // 使用迭代器: 不推荐
    ResultBuffer resBuf = ent.XData;
    if (resBuf is null) return;
    IEnumerator iter = resBuf.GetEnumerator();
    while (iter.MoveNext()) {
        var tv = (TypedValue)iter.Current;
        ed.WriteMessage(tv.TypeCode.ToString() + ": ");
        ed.WriteMessage(tv.Value.ToString() + "\n");
    }
*/
}

增:添加扩展数据

var tvs = new TypedValue[] {
    new TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc"),
    new TypedValue((int)DxfCode.ExtendedDataAsciiString, "123")
};
line.XData = new ResultBuffer(tvs);

虽然是用=等号赋值的,但是它并不一定会覆盖旧的扩展数据.
一个实体只有一个XData属性,
但是里面可以记录多个不同应用程序名的扩展数据.

每次用=等号赋值给实体的XData赋值时,
如果还没有这个应用程序的扩展数据,
那么新赋的这些值,会被添加到原有的XData结尾去.

比如上面已经给 line 对象添加了应用程序"abc"的扩展数据,
这个时候读取 line 的 XData,其内容应该是这样的:

TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc")
TypedValue((int)DxfCode.ExtendedDataAsciiString, "123")

这时候再给 line 添加一个新的应用程序 "xyz" 的扩展数据:

var tvs2 = new TypedValue[] {
    new TypedValue((int)DxfCode.ExtendedDataRegAppName, "xyz"),
    new TypedValue((int)DxfCode.ExtendedDataAsciiString, "0")
};
line.XData = new ResultBuffer(tvs2);

此时再读取 line 的 XData, 那么结果就会是:

TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc")
TypedValue((int)DxfCode.ExtendedDataAsciiString, "123")
TypedValue((int)DxfCode.ExtendedDataRegAppName, "xyz")
TypedValue((int)DxfCode.ExtendedDataAsciiString, "0")

查:过滤器

那如果同一个实体如果有多个应用程序的扩展数据,
那我怎么取其中某一个应用程序的扩展数据呢?

答案是使用 GetXDataForApplication 方法:

ResultBuffer res = line.GetXDataForApplication("abc");

这样取到的结果就是:

TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc")
TypedValue((int)DxfCode.ExtendedDataAsciiString, "123")

改:修改扩展数据

只需要对XData重新赋值就行,
比如现在我们修改应用程序 "abc" 的扩展数据:

var tvs = new TypedValue[] {
    new TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc"),
    new TypedValue((int)DxfCode.ExtendedDataAsciiString, "123"),
    new TypedValue((int)DxfCode.ExtendedDataAsciiString, "1")
};
line.XData = new ResultBuffer(tvs);

对 XData 重新赋值时,
已经有 "abc" 的扩展数据,那么会把原有的扩展数据替换掉.
还没有 "abc" 的扩展数据,那么会把新的扩展数据追加到末尾.

删:删除扩展数据

删除应用程序 "abc" 的扩展数据,只需要进行一个这样的赋值,
新值里只添加一个应用程序名,不添加其他值.
这样原有的扩展数据就会被删除.

var tvs = new TypedValue[] {
    new TypedValue((int)DxfCode.ExtendedDataRegAppName, "abc")
};
line.XData = new ResultBuffer(tvs);

删除注册程序名

尽量不要直接删除应用程序名.
如果图纸上的实体上添加了此应用程序的扩展数据,
那么扩展数据中对应的应用程序名会被删掉,
但它下面对应的其他数据值并不会被删除,这可能会引发问题.

// 直接删除符号表记录
using var regTable = (RegAppTable)trans.GetObject(db.RegAppTableId, OpenMode.ForWrite);
if (!regTable.Has("abc")) {
    var record = (RegAppTableRecord)tr.GetObject(regTable["abc"], OpenMode.ForWrite);
    record.Erase();
}

此时读取line的XData内容是这样:
(假设我们上一步,没有删除 line 中 "abc" 的扩展数据)

TypedValue((int)DxfCode.ExtendedDataAsciiString, "123")
TypedValue((int)DxfCode.ExtendedDataRegAppName, "xyz")
TypedValue((int)DxfCode.ExtendedDataAsciiString, "0")

所以在删除应用程序名前,
应先用 过滤选择器 获取添加了此应用程序扩展数据的实体,
然后将它们的 扩展数据 删除,
再去删除 RegAppTable 中的应用程序注册信息.

原作者: 尼克劳斯
https://www.cnblogs.com/bomb12138/p/3685577.html

数据字典DBDictionary

C#的Dictionary

首先你要具备字典类型知识,它是O(1)寻址速度,
原理就是求余数击中hash槽位.
例如:
array数组长度是3,
a的hashcode是7 => 7%3=1 然后把pair存放进去array[1].
b的hashcode是8 => 8%3=2 然后把pair存放进去array[2].

余数 = 被除数 - 除数*商

这个过程可以想象成用一条线(长度7)来绕一个瓶盖(周长3),
绕了几圈之后(圈数不关心),剩下1就是余下的,用来击中槽位.

有一个名词是归一化但此处貌似要叫归n化(n为数组长度).
计算机科学家们不遗余力地为了实现落在圈内数字都是均匀的,
创造了很多东西,例如hashcode采用质数31作为底数.

还有一些名词:
1,动态加入服务器节点的一致性哈希,
实际上就是int32作为虚拟槽,击中失败再向前找到可用节点.
2,哈希冲突的操作:
链地址法.
开放寻址,无碰撞最快,通过公共溢出区保存.
双哈希探索法/线性探索/二次探索.
3,数组是2次方长度可以用位运算代替除法器%求余数: hash & (n-1)
此时必须要每次扩容都是2次方,而2次方长度不能复用旧段内存,
所以c#选择1.5倍扩容,
c++的vector则是不同平台不一样的,
c/c++的realloc可以尝试扩展内存,也就是要后面一段连续内存的,Linux伙伴系统就是这样.
而Win是1.5倍扩容.

以下是C中所有字典类型的名称列表:

非泛型:
Hashtable
OrderedDictionary
ListDictionary
HybridDictionary

泛型:
Dictionary<TKey, TValue>
SortedDictionary<TKey, TValue>
SortedList<TKey, TValue>
ConcurrentDictionary<TKey, TValue>
ReadOnlyDictionary<TKey, TValue>

我很喜欢C#字典的一些方法,你会发现它封装很美.

var map = new Dictonary<string, int>();
map["A"] = 1; // 不存在key,就创建并设置value入容器.
map["A"] = 2; // 直接覆盖不提示冲突.

// 判断含有并提取:
if (map.TryGetValue("A", out var value)) {
    Env.Print(value);
}

Acad的DBDictionary

上面之所以铺垫那么久,就是为了说这个类型: 真奇怪.
它的key只允许字符串,但是内部做了泛型擦除.
可以说桌子就是不想让你学会.

foreach遍历的时候,
key是object的,但是要强转为string.
value是object的,也要强转为ObjectId.

索引获取倒是返回了ObjectId:
var id = dict.GetAt(string key);

设置不能直接设置回ObjectId,需要提供打开后的对象!!
dict.SetAt(string key, DBObject value);

可能是不允许设置跨数据库的Id,或者出于某种保护.
按照这个道理的话,那么都是强引用了,不存在弱引用的句柄字符串.

void UpdateDBDictionary(DBDictionary dict, string key) {
    var tr = DBTrans.Top;
    ObjectId valueId = dict.GetAt(key);
    dict.Add(key, tr.GetObject(valueId));
}

代码

public statuc class DBDictionaryHelper {
    // 和CurrentDictionary.AddOrUpdate()一样风格
    // 1,不含有旧对象,加入新对象,不执行func.
    // 2,含有旧对象,不加入新对象,而是执行func的返回值作为新对象.
    // 新对象和旧对象相等,不会去更新对象,避免触发数据库对象添加事件.
    public static void AddOrUpdate<TValue>(this DBDictionary dict, 
        string key, TValue newValue, Func<string, DBObject, TValue> func)
        where TValue : DBObject {
        if (dict == null) throw new ArgumentNullException(nameof(dict));
        if (key == null) throw new ArgumentNullException(nameof(key));
        if (newValue == null) throw new ArgumentNullException(nameof(newValue));
        if (func == null) throw new ArgumentNullException(nameof(func));

        var tr = DBTrans.GetTop(dict.Database);
        if (dict.Contains(key)) {
            using var oldValue = tr.GetObject(dict.GetAt(key));
            newValue = func(key, oldValue);
            if (oldValue.IsEquals(newValue))
                return;
        }
        if (newValue.IsNewObject && !newValue.IsPersistent)  {
            tr.AddNewlyCreatedDBObject(newValue, true);
        }
        // 青蛙:再次打开,否则引起eNotInDatabase.
        dict = (TValue)tr.GetObject(dict.ObjectId, OpenMode.ForWrite, false, true);
        dict.SetAt(key, newValue);
    }
}

此处调用

public static void NodAddOrUpdate(string subKey, string subValue) {
    var tr = DBTrans.GetTop();
    var nod = tr.NamedObjectsDict;

    // 有则获取,无则加入,不替换旧的
    nod.AddOrUpdate(DIC_NAME, new DBDictionary(), (key, currentValue) => currentValue);
    using var appDict = (DBDictionary)tr.GetObject(nod.GetAt(DIC_NAME));

    appDict.AddOrUpdate(DIC_NAME_SUB_ENTRY, new DBDictionary(), (key, currentValue) => currentValue);
    using var subDict = (DBDictionary)tr.GetObject(appDict.GetAt(DIC_NAME_SUB_ENTRY));

    // 有则删除并替换,无则直接加入
    // 这个创建放非托管堆的,若没有加入数据库就需要释放.
    // 内部应该:加入数据库就不释放
    using var rbf = new ResultBuffer(new TypedValue((int)DxfCode.ExtendedDataRegAppName, subValue));
    using var xr = new Xrecord() { Data = rbf };

    subDict.AddOrUpdate(subKey, xr,
        (key, currentValue) => {
            using var _ = currentValue.ForWrite();
            currentValue.Erase();
            return xr;
    });
}

命令调用

NodAddOrUpdate("基点信息文件名称", 
    Path.GetFileNameWithoutExtension(f.FileName));

扩展记录Xrecord.Data

单位 容量 不可用部分 可用数据(byte) 数量
行:TypedValue 128byte 1byte组码 127 1行
页:ResultBuffer 128Kib
=131072byte
128byte
(首行AppName)
(1024-1)*(128-1) 1024行
本:Xrecord 2Git
=2097152Kib
=2147483648byte
- 16384*(1024-1)*(128-1) 16384页
=16777216行

DBDictionary数据字典: 主字典/扩展字典.
主字典数量无限制,测试10w也可以.
DBObject.XData支持到页.
Xrecord.Data支持到本.
也就是可以10w个Xrecord,等于无限写入了.

写入:

// 行
var tvs = new TypedValue[] {
     new TypedValue(组码1byte, 数据127byte),
}

// 页: 1024行(首行AppName占一行)
var rb = new ResultBuffer(tvs);

// 本: 16384页
var xr = new Xrecord();
xr.Data.Add(rb);

// 数据字典,容量不详(如果不行就主字典写入)
var dmap = new DBDictionary();
dmap.SetAt("xRecordKey"+ 迭代数, xr);

// 主字典,容量10w以上
nod.SetAt("dmapKey"+ 迭代数, dmap);

读取:
obj.扩展字典id / db.主字典id
tr.GetObject(字典id);
=> DBDictionary?.AsArray();
=> 遍历得到Xrecord类型/或者其它DBDictionary
=> Xrecord.Data?.AsArray();

// 创建/获取实体的扩展字典
if (!entity.ExtensionDictionary.IsValid)
    entity.CreateExtensionDictionary();
var entityDict = (DBDictionary)tr.GetObject(entity.ExtensionDictionary, OpenMode.ForWrite);

https://www.cnblogs.com/JJBox/p/18534169

递归遍历全部字典

在合并图层里面: https://www.cnblogs.com/JJBox/p/15995259.html

扩展数据之强弱引用

https://www.cnblogs.com/JJBox/p/18758008

posted @ 2025-03-10 12:16  惊惊  阅读(263)  评论(0)    收藏  举报