cad.net 图层
合并图层
public class LayerCommands {
[CommandMethod(nameof(CmdLayerMerge))]
public void CmdLayerMerge() {
using DBTrans tr = new();
// 1,合并表<来源图层名,目标图层名>
// 数据可以多合一,Z字路径会扁平化,环形路径会报错.
// TODO: 自己从form/csv/txt,提取名称,此处没有表达....
var mergeNameMap = new Dictionary<string, string>();
mergeNameMap = CalculateFinalTargets(mergeNameMap);
// 2,获取数据库所有图层名称和ID
var dbLayerMap = new Dictionary<string, ObjectId>();
tr.LayerTable.ForEach(record => {
dbLayerMap[record.Name] = record.ObjectId;
});
// 都存在 => 遍历修改引用,并且删除来源.
// 都不存在 => 跳过
// 来源不存在,目标存在 => 跳过,目标都OK了
// 来源存在,目标不存在 => 改名
// 储存改名的图层对象
HashSet<string> changeNames = new();
// 3,name to id,查找就可以直接索引.
var mergeIdMap = new Dictionary<ObjectId, ObjectId>();
foreach (var pair in mergeNameMap) {
if (dbLayerMap.TryGetValue(pair.Key, out var sourceId) &&
dbLayerMap.TryGetValue(pair.Value, out var targetId)) {
mergeIdMap[sourceId] = targetId;
} else {
changeNames.Add(pair.Key);
}
}
// 4.0,遍历块表,修改引用
HashSet<ObjectId> dset = new();
tr.BlockTable.ForEach(record => {
foreach(var id in record) {
UpdateLayerReference(id, dset, mergeIdMap);
// ent.EntityRedraw(BrightEntity.RecordGraphicsModified);
// ent.RecordGraphicsModified(true);
}
});
// 4.1,递归主字典,修改引用
UpdateLayerReference(tr.NamedObjectsDict.ObjectId, dset, mergeIdMap);
// 4.2,图层记录的扩展数据,修改引用
// 尤其是0/Defpoints这些不能删除的图层.
foreach(var id in tr.LayerTable) {
UpdateLayerReference(id, dset, mergeIdMap);
}
// 5,没有合并目标,直接图层改名,重复改名导致报错.
// 多合一的时候存在重复改名,所以需要跳过.
// 改名后要更新缓存,也是防止跳过失败.
foreach(var oldName in changeNames) {
var newName = mergeNameMap[oldName];
if (dbLayerMap.ContainsKey(newName)) continue;
if (!dbLayerMap.TryGetValue(oldName, out var oldId)) continue;
using var ltr = (LayerTableRecord)tr.GetObject(oldId, openLockedLayer: true);
ltr.Name = newName;
dbLayerMap.Remove(oldName);
dbLayerMap[newName] = oldId;
}
// 6,删除来源图层,DWG结构的不能删.
var oldNames = mergeNameMap.Keys
.Where(oldName => !changeNames.Contains(oldName) &&
!SymbolUtilityServices.IsLayerZeroName(oldName) &&
!SymbolUtilityServices.IsLayerDefpointsName(oldName));
foreach(var oldName in oldNames) {
if (!dbLayerMap.TryGetValue(oldName, out var oldId)) continue;
using var ltr = (LayerTableRecord)tr.GetObject(oldId, openLockedLayer: true);
ltr.Erase();
dbLayerMap.Remove(oldName);
}
}
// 打开表记录或者图元修改图层引用,
// 扩展数据和扩展字典会被递归,
// 已经处理过的记录在dset,防止菱形引用(字典a内的字典b记录a)
// 函数头已经有dset.Add()判断了,为什么后面需要"先判断避免开辟栈帧",
// 判断不也开辟判断set的栈帧吗,
// 这虽然是冗余判断,但是它有利于函数计数和查看栈帧信息.
void UpdateLayerReference(ObjectId id, HashSet<ObjectId> dset,
Dictionary<ObjectId, ObjectId> mergeIdMap) {
if (!DBTrans.IsOk(id)) return;
if (!dset.Add(id)) return;
var tr = DBTrans.Top;
// 防止修改外部参照对象
if (tr.Database != id.Database) return;
using var obj = tr.GetObject(id, openLockedLayer: true);
// 递归数据字典类型,先修改引用再递归
if (obj is DBDictionary dict) {
UpdateDBDictionary(dict, mergeIdMap);
foreach (DictionaryEntry pair in dict) {
if (!dset.Contains((ObjectId)pair.Value)) // 先判断避免开辟栈帧
UpdateLayerReference((ObjectId)pair.Value, dset, mergeIdMap);
}
return;
}
// 递归扩展字典类型,先修改引用再递归
// 检查是否为对象指针类型(强弱引用)
if (obj is Xrecord xr) {
var typedValues = xr.Data?.AsArray();
if (typedValues is null) return;
var rb = ReplaceResultBuffer(typedValues, mergeIdMap);
if (rb != null) xr.Data = rb;
foreach (TypedValue pair in typedValues) {
if (pair.TypeCode == (int)DxfCode.SoftPointerId ||
pair.TypeCode == (int)DxfCode.HardPointerId) {
var pid = (ObjectId)pair.Value;
if (!dset.Contains(pid)) // 先判断避免开辟栈帧
UpdateLayerReference(pid, dset, mergeIdMap);
}
}
return;
}
// 更新图层的引用
// 关联标注的反应器,其他开发者记录的图元等等
// 更改图元的图层
if (obj is Entity ent && mergeIdMap.TryGetValue(ent.LayerId, out var targetId)) {
ent.LayerId = targetId;
}
// 更改扩展字典
// 它是DBDictionary,然后储存Xrecord的,而Xrecord也可以储存更多字典.
if (obj.ExtensionDictionary.IsValid) {
var pid = obj.ExtensionDictionary;
if (!dset.Contains(pid)) // 先判断避免开辟栈帧
UpdateLayerReference(pid, dset, mergeIdMap);
}
// 更改扩展数据
var typedValues2 = obj.XData?.AsArray();
if (typedValues2 is null) return;
var rb2 = ReplaceResultBuffer(typedValues2, mergeIdMap);
if (rb2 != null) obj.XData = rb2;
foreach (TypedValue pair2 in typedValues2) {
if (pair2.TypeCode == (int)DxfCode.SoftPointerId ||
pair2.TypeCode == (int)DxfCode.HardPointerId) {
var pid = (ObjectId)pair2.Value;
if (!dset.Contains(pid)) // 先判断避免开辟栈帧
UpdateLayerReference(pid, dset, mergeIdMap);
}
}
}
// 扩展字典Xrecord,替换数据而不是暴力删除.
// dict.GetAt返回是id,而dict.SetAt却要DBObject
// 可能是不允许设置跨数据库的Id,或者某种保护,需要打开对象再设置字典.
// 按照这个道理的话,那么都是强引用了,不存在弱引用的句柄字符串.
void UpdateDBDictionary(DBDictionary dict, Dictionary<ObjectId, ObjectId> mergeIdMap) {
var tr = DBTrans.Top;
foreach (DictionaryEntry pair in dict) {
if (mergeIdMap.TryGetValue((ObjectId)pair.Value, out var targetId)) {
dict.SetAt((string)pair.Key, tr.GetObject(targetId));
}
}
}
// 扩展数据XData,替换数据而不是暴力删除.
// 它支持弱引用:句柄字符串/临时指针(非持久化指针).
ResultBuffer? ReplaceResultBuffer(TypedValue[] typedValues, Dictionary<ObjectId, ObjectId> mergeIdMap) {
var tr = DBTrans.Top;
bool modified = false;
var tvs = new List<TypedValue>();
foreach(TypedValue pair in typedValues) {
// 1003记录图层名,它若不是强关联则不需要改
if (pair.TypeCode == 1003) {
}
// 句柄类型
else if (pair.TypeCode == (int)DxfCode.Handle) {
var sourceId = tr.GetObjectId(pair.Value.ToString());
if (mergeIdMap.TryGetValue(sourceId, out var targetId)) {
tvs.Add(new TypedValue(pair.TypeCode, targetId.Handle.ToString()));
modified = true;
continue;
}
}
// 对象指针类型(强弱引用)
else if (pair.TypeCode == (int)DxfCode.SoftPointerId ||
pair.TypeCode == (int)DxfCode.HardPointerId) {
if (mergeIdMap.TryGetValue((ObjectId)pair.Value, out var targetId)) {
tvs.Add(new TypedValue(pair.TypeCode, targetId));
modified = true;
continue;
}
}
tvs.Add(pair);
}
if (modified) return new ResultBuffer(tvs.ToArray());
return null;
}
}
是否成环
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static void Main() {
// Z字合并关系,计算最终路径.
var mapping = new Dictionary<string, string> {
["A"] = "B",
["B"] = "C",
["C"] = "D",
// 环形路径 ["D"] = "A",
// 自引用 ["D"] = "D",
};
try {
// 调用函数计算每个图层的最终合并目标
var finalTargets = CalculateFinalTargets(mapping);
// 输出结果
Console.WriteLine("图层合并路径");
foreach (var kvp in finalTargets) {
Console.WriteLine($"{kvp.Key} => {kvp.Value}");
}
} catch (InvalidOperationException ex) {
// 捕获循环检测异常并输出错误信息
Console.WriteLine($"错误: {ex.Message}");
}
}
// 计算每个图层的最终合并目标,并检测是否成环
public static Dictionary<TKey, TValue> CalculateFinalTargets<TKey, TValue>(
Dictionary<TKey, TValue> mapping) where TKey : notnull {
var cache = new Dictionary<TKey, TValue>();
var visited = new HashSet<TKey>();
return mapping
.Where(pair => !pair.Key.Equals(pair.Value))
.ToDictionary(pair => pair.Key, pair => {
if (cache.TryGetValue(pair.Key, out var cachedValue))
return cachedValue;
visited.Clear();
var value = pair.Value;
while (value is TKey key && mapping.TryGetValue(key, out var nextValue)) {
if (!visited.Add(key))
throw new InvalidOperationException($"环形路径: {pair.Key} => {value}");
if (cache.TryGetValue(key, out var cachedNextValue)) {
value = cachedNextValue;
break;
}
value = nextValue;
}
cache[pair.Key] = value;
return value;
});
}
}
图层解锁褪色度
影响图层显示的主要有:
关闭 isOff
冻结 IsFrozen
图层隐藏 isHidden
视口冻结 FreezeLayersInViewport
想跟acad的锁定/解锁一样拥有褪色度,那么你需要阅读此篇
代码
public class TestLayer {
[CommandMethod(nameof(JJCmd_LayerLock))]
public void JJCmd_LayerLock() {
var dm = Acap.DocumentManager;
var doc = dm.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
var pr = ed.GetEntity("\n请选择图元:锁定它的图层");
if (pr.Status != PromptStatus.OK)
return;
using DBTrans tr = new();
using var ent = (entity)tr.GetObject(pr.ObjectId);
SetLayerLock(ent.LayerId, true);
}
[CommandMethod(nameof(JJCmd_LayerUnLock))]
public void JJCmd_LayerUnLock() {
var dm = Acap.DocumentManager;
var doc = dm.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
var peo1 = new PromptEntityOptions("\n请选择图元:解锁它的图层") {
AllowObjectOnLockedLayer = true,
AllowNone = true
};
var pr = ed.GetEntity(peo1);
if (pr.Status != PromptStatus.OK)
return;
using DBTrans tr = new();
using var ent = (entity)tr.GetObject(pr.ObjectId);
SetLayerLock(ent.LayerId, false);
}
}
public static partial class LayerHelper {
static _isOff = false;
/// <summary>
/// 按层名锁定或解锁图层
/// </summary>
/// <param name="layerId">图层id</param>
/// <param name="locked"><see langword="true"/>锁定,<see langword="false"/>解锁</param>
/// <returns>图层原本锁定的状态</returns>
public static bool SetLayerLock(ObjectId layerId, bool locked = true) {
var tr = DBTrans.Top;
bool oldSetting = false;
// 打开层表记录,最后参数是打开锁定图元
using var ltr = (LayerTableRecord)tr.GetObject(layerId, openLockedLayer: true);
if (ltr is null) return false;
var oldSetting = ltr.IsLocked;
ltr.IsLocked = locked;
// 更新褪色度显示,并且确保不会被编译器优化
Volatile.Write(ref _isOff, ltr.IsOff);
// 必须自己设置自己才能更新显示
ltr.IsOff = Volatile.Read(ref _isOff);
return oldSetting;
}
/// <summary>
/// 设置图层褪色度显示
/// </summary>
/// <param name="layerId">图层id</param>
/// <param name="locked"><see langword="true"/>锁定,<see langword="false"/>解锁</param>
static void SetLayLockFadectl(ObjectId layerId, bool locked = true) {
var tr = DBTrans.Top;
const string str = "LayLockFadectl";
var value = int.Parse(CadSystem.Getvar(str));
// 锁定-绝对值褪色...设置了会自动刷新屏幕显示
value = Math.Abs(value);
if (!locked)
value *= -1;//解锁-负数.设置完之后要手动刷新
CadSystem.Setvar(str, value.ToString());
/*
// 方案一: 获取屏幕内的图元.缺少的函数见文章相关引用
// 成功是成功,但是不在屏幕内的褪色度没有变化
var ids = ed.SelectViewportObjectId();
foreach(var id in ids) {
using var ent = (Entity)tr.GetObject(id, openLockedLayer: true);
ent.EntityRedraw(BrightEntity.RecordGraphicsModified);
}
*/
/*
// 方案二: 这个刷新不成功,见文章相关引用的"动画"
tr.Editor?.UpdateScreenEx();
*/
// 方案三:遍历全图,加入刷新队列.
#if true
tr.BlockTable.ForEach(btRec => {
foreach(var id in btRec) {
using var ent = (Entity)tr.GetObject(id, openLockedLayer: true);
if (ent.LayerId != layerId) continue;
ent.EntityRedraw(BrightEntity.RecordGraphicsModified);
}
});
#else
// 以下代码仅用于测试:
// 请不要并行遍历块表,否则将导致块表迭代器失效!!!
// 查询图元不涉及数据库更改,改为并行查询.
// 并且应该限制当前空间,不过可能是布局视口内的模型,所以还是遍历全图吧.
// 限制并行操作为CPU核心数
int degreeOfParallelism = System.Environment.ProcessorCount;
// 获取块表
using var table = (BlockTable)tr.GetObject(tr.Database.BlockTableId);
// 并行查询
var entitys = table.Cast<ObjectId>()
.AsParallel() //.AsEnumerable() 顺序执行
.WithDegreeOfParallelism(degreeOfParallelism)
.Where(btRecordId => btRecordId.IsOk())
.Select(btRecordId => (BlockTableRecord)tr.GetObject(btRecordId))
.SelectMany(btr => btr.Cast<ObjectId>().Where(eid => eid.IsOk())) // 扁平化时候过滤条件,减少合并大小.
.Select(eid => (Entity)tr.GetObject(eid))
.Where(ent => ent.LayerId == layerId); // 得到相同图层的图元
// 顺序加入刷新队列
entitys.AsSequential().ForEach(ent => {
ent.EntityRedraw(BrightEntity.RecordGraphicsModified);
});
#endif
//解锁图层之后,有其他图层是锁定的,那么此时需要还原褪色度
if (!locked)
CadSystem.Setvar(str, (value * -1).ToString());
}
}
}
public static class EditorHelper
{
/// <summary>
/// 获取当前屏幕的所有图元
/// </summary>
/// <param name="ed"></param>
/// <param name="tr"></param>
/// <returns></returns>
public static ObjectId[] SelectViewportObjectId(this Editor ed)
{
var view = ed.GetCurrentView();
var cpt = view.CenterPoint;
var w = view.Width / 2;
var h = view.Height / 2;
var min = new Point3d(cpt.X - w, cpt.Y - h, 0);
var max = new Point3d(cpt.X + w, cpt.Y + h, 0);
var ssget = ed.SelectCrossingWindow(min, max);
if (ssget.Status != PromptStatus.OK)
return new();
return ssget.Value.GetObjectIds();
}
}
特别说明
0x01
e大告诉我这个神奇的地方,(我也在Adn博客看见了)
估计是触发了某些桌子内部机制,就好像WPF的数据绑定触发?
可以尝试注释掉这句再运行,会发现锁定褪色都不成功.acad08测试.
ltr.IsOff = ltr.IsOff;
这种狗屎设计,会被编译器优化杀掉的!!所以我们要用volatile读写.
0x02
解锁是必须遍历全图的,
若只遍历当前屏幕的图元,会呈现这样的效果,屏幕外没有退出淡显:
相关引用
BrightEntity.RecordGraphicsModified
高版本刷新褪色度(推荐)
https://www.cnblogs.com/d1742647821/p/17995075
下面的操作,Autodesk.AutoCAD.Internal命名空间是COM接口.
Acad08只能引用COM的DLL或者反射,但反射无函数提示,
并且引用COM的DLL需要程序域卸载.
原作者只从Acad2015开测:
Acad2015/2016是LayerUtilities类.
Acad2017(R21.0)版本及以上是CoreLayerUtilities类,
public static void RegenLayers(ObjectId[] layerIds) {
var type = Acap.Version.Major >= 21
? Assembly.Load("accoremgd")?.GetType("Autodesk.AutoCAD.Internal.CoreLayerUtilities")
: Assembly.Load("acmgd")?.GetType("Autodesk.AutoCAD.Internal.LayerUtilities");
var mi = type?.GetMethods().FirstOrDefault(e => e.Name == "RegenLayers");
var pi = type?.GetProperties().FirstOrDefault(e => e.Name == "RegenPending");
var regenPending = (int)(pi?.GetValue(null) ?? 0);
mi?.Invoke(null, new object[] { layerIds, regenPending });
}
隐藏图层
今天小博发现了一件事情,
无论怎么用 IsHidden
都会出现报错 eDuplicateRecordName
有问题的代码
public class Commands {
[CommandMethod(nameof(TestLayer))]
public void TestLayer() {
using DBTrans tr = new();
const string layerName = "test";
using var table = (LayerTable)tr.GetObject(tr.Database.LayerTableId);
if (!table.Has(layerName)) {
var layer = new LayerTableRecord() {
Name = layerName,
IsHidden = false
};
table.UpgradeOpen();
table.Add(layer);
tr.AddNewlyCreatedDBObject(layer, true);
return;
}
foreach (var item in table) {
using var layer = (LayerTableRecord)tr.GetObject(item);
if (layer.Name == layerName) {
layer.UpgradeOpen();
layer.IsHidden = true;
break;
}
}
}
}
acad2008和acad2019测试都是如下报错:
第一次遍历,生成一个
test
图层,
第二次遍历,修改隐藏(相当于删除了的感觉)
第三次遍历,又生成了一个test
图层
第四次遍历,修改隐藏报错了
原因
后来得到e大的帮助,说:迭代器上面没有!!
那么我就去看了层表,层表上面有个参数是IncludingHidden
,
所以代码要写这句,层表才会显示隐藏的图层:(所有表都有这个隐藏?😏)
using var layerTable = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForWrite);
layerTable = layerTable.IncludingHidden;
之后,我就发现了,报错的原因:
第一次遍历,生成一个
test
图层,
第二次遍历,修改隐藏(相当于把图层改成了*test
的名字)
第三次遍历,又生成了一个test
图层
第四次遍历,修改隐藏,相当于把test
改成*test
,
因为第二遍的时候隐藏的已经有这个名字了,就报错了!
修改好的代码
写了个例子:
尝试注释掉打开隐藏那个,反复执行命令.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
public enum LayerHiddenStatus {
[Description("无异常:更改成功")]
OK,
[Description("异常:没有图层,无法更改")]
Not,
[Description("异常:有带*图层,不能隐藏")]
HasStar,
[Description("异常:有不带*图层,不能取消隐藏")]
NonStar,
}
public class LayerCommands {
[CommandMethod(nameof(TestLayer))]
public void TestLayer() {
using DBTrans tr = new();
const string layerName = "test";
using var table = (LayerTable)tr.GetObject(tr.Database.LayerTableId);
table = table.IncludingHidden;
#if true
LayerTableRecord ltr = null;
if (!table.Has(layerName)) {
ltr = new { Name = layerName };
table.Add(ltr);
tr.AddNewlyCreatedDBObject(ltr, true);
}
#else
// IFox写法将不再导致此问题,推荐
tr.LayerTable.AddOrUpdate(layerName, layer => {
layer.Name = layerName,
layer.IsHidden = false
});
#endif
var error = SetLayerHidden(layerName);
var str = GetEnumDescription(error);
Env.Print(str);
error = SetLayerHidden(layerName, true);
str = GetEnumDescription(error);
Env.Print(str);
}
private static string GetEnumDescription(Enum value) {
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute = field.GetCustomAttribute<DescriptionAttribute>();
return attribute == null ? value.ToString() : attribute.Description;
}
/// <summary>
/// 设置隐藏图层
/// </summary>
/// <param name="layerName">不带*的图层名称</param>
/// <param name="isHidden">隐藏true,显示false</param>
/// <returns></returns>
LayerHiddenStatus SetLayerHidden(string layerName, bool isHidden = false) {
// 剔除前面是星
layerName = layerName?.TrimStart("*");
if (layerName is null)
throw new ArgumentNullException(nameof(layerName));
var tr = DBTrans.Top;
using var table = (LayerTable)tr.GetObject(tr.Database.LayerTableId);
table = table.IncludingHidden;
// 提取所有图层名称到字典中
var layerMap = new Dictionary<string, ObjectId>();
foreach (var id in table) {
using var ltr = (LayerTableRecord)tr.GetObject(id, OpenMode.ForRead);
layerMap[ltr.Name] = id;
}
if (isHidden) {
// 要求隐藏,但是有带*图层=>无法隐藏
if (layerMap.ContainsKey("*" + layerName))
return LayerHiddenStatus.HasStar;
} else {
// 要求显示,但是有不带*的图层=>不能取消隐藏
if (layerMap.ContainsKey(layerName))
return LayerHiddenStatus.NonStar;
layerName = "*" + layerName;
}
// 修改图层隐藏
if (layerMap.TryGetValue(layerName, out var targetId)) {
using var targetLayer = (LayerTableRecord)tr.GetObject(targetId, OpenMode.ForWrite);
targetLayer.IsHidden = isHidden;
return LayerHiddenStatus.OK;
}
// 不含有图层
return LayerHiddenStatus.Not;
}
}
因为*号
是保留关键字
,普通用户不给用的,
大家要判断层名的时候就要打开IncludingHidden
遍历的时候
同时判断"*"+layerName
和 layerName
普通cad用户只能干着急,嘻嘻
e大还说了,把dwg存成dxf,然后用对比文件看,也能看到,并改...
这貌似是给普通用户的简便修改方式?
(完)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界