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
解锁是必须遍历全图的,
若只遍历当前屏幕的图元,会呈现这样的效果,屏幕外没有退出淡显:

相关引用

cad.net 图元动画效果+图元刷新

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,然后用对比文件看,也能看到,并改...
这貌似是给普通用户的简便修改方式?

(完)

posted @   惊惊  阅读(928)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界
点击右上角即可分享
微信分享提示