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