cad.net Attsync命令更新属性位置

原因

Acad2008和Acad2019使用Attsync命令,
处理镜像的属性块时会有不同的效果.
而桌子并不是重写新命令或者拓展函数,而是修改了原本的函数.
因为我用代码实现,依然会发生这样的事情.

演示

制作测试:
在Acad08和Acad19用旋转和镜像属性块用Attsync命令.

01,旋转的属性块会发现文字是正的.
img

02,镜像的属性块会发现文字是反的.
img

动图演示
img

函数更新了什么?

桌子的想法也很简单:
1,考虑了法向量.
2,对齐方式是调整,桌子还会位移了.
这就给我们二开很不好的地方是,我们需要自行去处理不同呈现的结果.

处理方式是写一个新命令来替换掉acad自带的命令,
然后对旋转或者镜像分别处理,某些情况不调用SetAttributeFromBlock.

出问题的函数

// 从块设置属性
at.SetAttributeFromBlock(attdef, brf.BlockTransform);                                     

下面仅仅是注释一下参数意义:

public static class Helper {
    /// <summary>
    /// 设置块属性
    /// </summary>
    /// <param name="at">属性参照(块外)</param>
    /// <param name="blockTransform">块的矩阵变换</param> 
    /// <param name="definition">属性定义(块内)</param>  
    public static void SetAttributeFromBlockEx(this AttributeReference at, 
        Matrix3d blockTransform,  AttributeDefinition definition = null) {
        // 为了解决SetAttributeFromBlock的新旧版本不同的问题,所以这里写一个替换函数
        // 强行将acad2008的旋转方式修改成与acad2019一样.
        // 因为acad2019留意到法向量的问题
        // 原本这个函数会令TextString丢失,所以后面要重设(这里考虑不改变它
        // 旋转矩阵则不要用它,不然会设置两次旋转
        // 平移矩阵则不要用它,不然会设置两次平移

        // null不对比块内信息,就直接执行矩阵.
        if (definition is null) 
            at.SetAttributeFromBlock(blockTransform);
        else
            at.SetAttributeFromBlock(definition, blockTransform);
    }

    // 标记Tag/提示Prompt/默认TextString;
    // 可以重复Tag但cad会爆红提示
    // 定义(块内def)
    public static Dictionary<string, AttInfo> ToDefTagMap(this BlockReference brf) {
        var map = new Dictionary<string, AttInfo>();
        var tr = DBTrans.Top;
        using var btRec = (BlockTableRecord)tr.GetObject(brf.BlockTableRecord, OpenMode.ForRead);
        foreach(var id in btRec) {
            using var def = (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
            if(def is null || map.ContainsKey(def.Tag)) continue;
            map.Add(def.Tag, new AttInfo(def));
            // map.Add(def.Tag, def); 释放对象不能偷出去
        }
        return map;
    }

    // 参照(块外att)
    public static Dictionary<string, AttInfo> ToTagMap(this BlockReference brf) {
        var map = new Dictionary<string, AttInfo>();
        var tr = DBTrans.Top;
        foreach (ObjectId id in brf.AttributeCollection) {
            using var att = (AttributeReference)tr.GetObject(id, OpenMode.ForRead);
            if(att is null || map.ContainsKey(att.Tag)) continue;
            map.Add(att.Tag, new AttInfo(att));
        }
        return map;
    }
}

命令

public class Command_JJAttsync {
    [CommandMethod(nameof(JJ_Attsync),
        CommandFlags.Modal | CommandFlags.UsePickSet | CommandFlags.Redraw)]
    public void JJ_Attsync() {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        var ed = doc.Editor;
        ed.WriteMessage(Environment.NewLine + "****惊惊连盒-翻转属性块更新");

        // 选择块名相同
#if jing
        var ids = Command_jjak.AllSelectByBlockName(ed, db, BlockTool.EnumAttIdentical.AllBlockName);
        if (ids.Length == 0) return;
#else
        PromptEntityOptions options = new("\n 选择属性块");
        options.SetRejectMessage("\n请选择图块");
        options.AddAllowedClass(typeof(BlockReference),false);
        var res = ed.GetEntity(options);
        var ssIdArray = new ObjectId[] { res.ObjectId };
        if (ssIdArray.Length == 0) return;
#endif

        using var docLocker = doc.LockDocument();
        using (DBTrans tr = new()) { 
            ids.ForEach(id => CreateAndErase(id));
        } 
        using (DBTrans tr = new()) {
            ids.ForEach(id => MiRo(id));
        } 
        ed.SetImpliedSelection(new ObjectId[] { });
    }

    void CreateAndErase(ObjectId id) {
        var tr = DBTrans.Top;
        using var ent = tr.GetObject(id, OpenMode.ForRead);
        if (ent is not BlockReference brf) return;

        var attMap = brf.ToTagMap();
        var attdefMap = brf.ToDefTagMap();

        // 标记Tag/提示Prompt/默认TextString;
        // 根据内部def创建外部att,
        // 如果外部att缺失就创建并同步矩阵
        attdefMap.ForEach(attdefkv => {
            var attdef = attdefkv.Value;
            // 巧妙的处理释放问题,
            // 1,先构造tagMap利用自动释放,因为托管类型由GC释放
            // 2,using智能指针释放读取的,记住命令层从不手动释放.
            // 3,新创建的对象不释放,而是写入数据库中.
            // 4,2和3的后续逻辑用函数封装,这样就不会释放了新创建的.
            if(attMap.ContainsKey(attdef.Tag)) {
                using var att = (AttributeReference)tr.GetObject(attMap[attdef.Tag].ObjectId, OpenMode.ForWrite);
                SetRefInfo(brf, att, attdef.ObjectId, attMap[attdef.Tag].TextString);
            } else {
                var att = attdef.CreateReference();
                SetRefInfo(brf, att, attdef.ObjectId, attdef.TextString);
            }
        });

        // 删除孤立的外部,和AS命令一样
        foreach (ObjectId idatt in brf.AttributeCollection) {
            using var att2 = (AttributeReference)tr.GetObject(idatt, OpenMode.ForRead);
            if (attdefMap.ContainsKey(att2.Tag)) continue;
            att2.UpgradeOpen();
            att2.Erase();
        }
    }

    void SetRefInfo(BlockReference brf,
        AttributeReference att, ObjectId defId, string str) {
        var tr = DBTrans.Top;
        // 设置矩阵
        using var defent = (AttributeDefinition)tr.GetObject(defId, OpenMode.ForRead);
       if(defent is null) return;
        att.SetAttributeFromBlockEx(brf.BlockTransform, defent);
        // 和单行文字一样,先调整对齐,才能设置内容
        att.AdjustAlignment(brf.Database);
        att.TextString = str;
        if (att.IsNewObject) {
            // 向块参照添加属性对象
            brf.UpgradeOpen();
            brf.AttributeCollection.AppendAttribute(att);
            tr.Transaction.AddNewlyCreatedDBObject(att, true);
        }
    }
         
    void MiRo(ObjectId id){
        var tr = DBTrans.Top;
        using var ent = tr.GetObject(id, OpenMode.ForRead);
        if (ent is not BlockReference brf) return;
        // 旋转到当前鼠标坐标系 CoordinateSystem3d          
        // 储存属性参照
        var attMap = brf.ToTagMap();

        // 症结点在于整体缩放时候无法通过X<1来判断块的镜像
        // 缩放或者变形
        // 没有镜像的图元,法向量正常=> 
        if (brf.ScaleFactors.X >= 0 && brf.Normal.Z == 1) {
            // 旋转
            foreach (var attkv in attMap) {
                using var at = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
                if (isRo(brf)) RotateAttributeReference(brf, at);
            }
        } // 没有镜像的图元,法向量不正常=>
        else if (brf.ScaleFactors.X >= 0 && brf.Normal.Z == -1) {
            // 镜像并旋转 
            foreach (var attkv in attMap) {
                using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
                MirrorAttributeReference(brf, att, att.Rotation);
            }
        } // 镜像的图元,法向量正常=>
        else if (brf.ScaleFactors.X < 0 && brf.Normal.Z == 1) {
            foreach (var attkv in attMap) {
                using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
                // Acad年份获取https://www.cnblogs.com/JJBox/p/18511807
                if(VersionTool.CurrentYear == 2008) {
                    // 镜像并旋转
                    MirrorAttributeReference(brf, att, -att.Rotation);
                } else if(VersionTool.CurrentYear >= 2019) {
                    Alignment2019Mr1(brf, att);
                } else throw new("该版本没有测试");
            }
        } // 镜像的图元,法向量不正常=>
        else if (brf.ScaleFactors.X < 0 && brf.Normal.Z == -1) {
            foreach (var attkv in attMap) {
                using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
                if(VersionTool.CurrentYear == 2008) {
                    // 旋转
                    if (isRo(brf)) RotateAttributeReference(brf, at);
                } else if(VersionTool.CurrentYear >= 2019) {
                    Alignment2019Mr2(brf, att);
                } else throw new("该版本没有测试");
            }
        }
    }

    // IFox的函数 我的EntityAdd.TxtAlignment
    bool IsTargetJustify(AttributeReference at){
        return at.Justify == AttachmentPointHelper.Get("铺满") ||
        at.Justify == AttachmentPointHelper.Get("调整")) {
    }

    /// <summary>
    /// 处理2019cad的属性块2
    /// </summary>
    /// <param name="brf">块参照</param>
    /// <param name="at">属性</param> 
    void Alignment2019Mr2(BlockReference brf, AttributeReference at) {
        var ro = at.Rotation;
        if (IsTargetJustify(at)) {
            // 这个角度是不会设置到图元中的,
            // 但是之后求包围盒都是0角度的了,很有趣.
            at.Rotation = 0;
#if jing
            // 求AABB包围盒
            var boxEdge = new GeometricExtents(at).Edge3;
            var boxEdge = at.GeometricExtents;
            boxEdge.RotateBy(ro, at.Normal, at.Position);
            var ro_RightUpper = boxEdge.RightUpper;//右上
            var ro_RightDown = boxEdge.RightDown;//右下 
            var ro_MidstUpper = boxEdge.MidstUpper;//中上
            var ro_MidstDown = boxEdge.MidstDown; //中下
            var ro_Midst = boxEdge.Midst;//中间
            // 先平移,不然用上下镜像会有误差的
            at.EntityMove(ro_RightUpper, ro_RightDown);
            // 上下镜像  
            var mt = at.EntityMirror(ro_MidstUpper, ro_MidstDown, out _);
            at.SetAttributeFromBlockEx(mt);

            if (!isRo(brf)) {
                ro_Midst += ro_RightUpper.GetVectorTo(ro_RightDown);
                at.EntityRotate(Math.PI, at.Normal, ro_Midst);
            }
#else
            var boxEdge = at.GeometricExtents;
            boxEdge.TransformBy(Matrix3d.Rotation(ro, at.Normal, at.Position));
            var ro_RightUpper = boxEdge.MaxPoint;//右上
            var ro_RightDown = new Point3d(boxEdge.MaxPoint.X, boxEdge.MinPoint.Y, 0);//右下 
            var ro_MidstUpper = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MaxPoint.Y, 0);// 中上
            var ro_MidstDown = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y, 0); //中下
            var ro_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间

            // 先平移,不然用上下镜像会有误差的
            at.TransformBy(Matrix3d.Displacement(ro_RightDown - ro_RightUpper));
            // 上下镜像  
            var mt = Matrix3d.Mirroring(new Line3d(ro_MidstUpper, ro_MidstDown));
            at.SetAttributeFromBlockEx(mt);

            if (!isRo(brf)) {
                ro_Midst += ro_RightUpper.GetVectorTo(ro_RightDown);
                at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, ro_Midst));
            }
#endif
            at.AdjustAlignment(brf.Database);
            return;
        }

        MirrorAttributeReference(brf, at, ro);
        // 保证右边的旋转是对的.
        // 这里是镜像过,所以要判断brf.Rotation>=的
        if (isRo(brf, true)) {
#if jing
            var boxEdge = new GeometricExtents(at).Edge3;
            at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
#else
            var boxEdge = at.GeometricExtents;
            var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
            at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
#endif
        }
        at.AdjustAlignment(brf.Database);
    }

    /// <summary>
    /// 处理2019cad的属性块1
    /// </summary>
    /// <param name="brf">块参照</param>
    /// <param name="at">属性</param>
#if jing
    void Alignment2019Mr1(BlockReference brf, AttributeReference at) {
        if (IsTargetJustify(at)) {
            // 求AABB包围盒          
            var boxEdge = new GeometricExtents(at).Edge3;
            if (isRo(brf)) {
                // 这个角度是不会设置到图元中的,
                // 但是之后求包围盒都是0角度的了,很有趣.
                var ro = at.Rotation;
                at.Rotation = 0;
               // 求AABB包围盒
                boxEdge = new GeometricExtents(at).Edge3;
                var leftUpper = boxEdge.LeftUpper.RotateBy(ro, at.Normal, at.Position);
                at.EntityMove(leftUpper, at.Position);
            }
            else {
                at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
                at.AdjustAlignment(brf.Database);//调整对齐

                // 这个角度是不会设置到图元中的,
                // 但是之后求包围盒都是0角度的了,很有趣.
                var ro = at.Rotation;
                at.Rotation = 0;
               // 求AABB包围盒
                boxEdge = new GeometricExtents(at).Edge3;
                var leftUpper = boxEdge.LeftUpper.RotateBy(ro, at.Normal, at.Position);
                at.EntityMove(at.Position, leftUpper);
            }
            return;
        }

        if (brf.Rotation == Math.PI / 2)
            RotateAttributeReference(brf, at);
    }
#else
     void Alignment2019Mr1(BlockReference brf, AttributeReference at) {
         // 求AABB包围盒          
         var boxEdge = at.GeometricExtents;
         var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
         var boxEdge_LeftUpper = new Point3d(boxEdge.MinPoint.X, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0);//左上                                                                                                                 
         if (IsTargetJustify(at)) {
             if (isRo(brf)) {
                 var ro = at.Rotation;
                 at.Rotation = 0;
                 boxEdge = at.GeometricExtents;                   
                 var leftUpper = boxEdge_LeftUpper.RotateBy(ro, at.Normal, at.Position);
                 at.TransformBy(Matrix3d.Displacement(at.Position- leftUpper));                 
             }
             else {
                 at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
                 at.AdjustAlignment(brf.Database);//调整对齐
                 var ro = at.Rotation;
                 at.Rotation = 0;
                 // 求AABB包围盒
                 boxEdge = at.GeometricExtents;
                 var leftUpper = boxEdge_LeftUpper.RotateBy(ro, at.Normal, at.Position);
                at.TransformBy(Matrix3d.Displacement(leftUpper - at.Position));
            }
            return;
        }
        if (brf.Rotation == Math.PI / 2)
            RotateAttributeReference(brf, at);
    }
#endif

    /// <summary>
    /// 镜像块的属性参照
    /// </summary>
    /// <param name="brf">块参照</param>
    /// <param name="at">属性</param>
    /// <param name="rotation">旋转角度,因为法向量不同而选择块的正值或负值</param>
#if jing
    void MirrorAttributeReference(BlockReference brf, AttributeReference at, double rotation) {
        // 克隆一份属性来旋转,计算它的最小包围盒
        using var atClone = at.Clone() as AttributeReference;
        atClone.Rotation = 0;

        // 求AABB包围盒
        var boxEdge = new GeometricExtents(atClone).Edge3; 
        // 将包围盒上下两个点逆旋转到文字的位置,提供给镜像使用.
        var muPt = boxEdge.MidstUpper.RotateBy(rotation,
            brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
        var mdPt = boxEdge.MidstDown.RotateBy(rotation,
            brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);

        // 镜像矩阵
        var mt = at.EntityMirror(muPt, mdPt, out _);
        at.SetAttributeFromBlockEx(mt);
        if (isRo(brf)) RotateAttributeReference(brf, at);
    }
#else
    void MirrorAttributeReference(BlockReference brf, AttributeReference at, double rotation)        {
        // 克隆一份属性来旋转,计算它的最小包围盒
        using var atClone = at.Clone() as AttributeReference;
        atClone.Rotation = 0;
        // 求AABB包围盒
        var boxEdge = atClone.GeometricExtents;
        var boxEdge_MidstUpper = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MaxPoint.Y, 0);// 中上
        var boxEdge_MidstDown = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y, 0); //中下
        // 将包围盒上下两个点逆旋转到文字的位置,提供给镜像使用.
        var muPt = boxEdge_MidstUpper.RotateBy(rotation,
            brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
        var mdPt = boxEdge_MidstDown.RotateBy(rotation,
            brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
        // 镜像矩阵
        var mt = Matrix3d.Mirroring(new Line3d(muPt, mdPt));
        at.SetAttributeFromBlockEx(mt);
        if (isRo(brf)) RotateAttributeReference(brf, at);
    }
#endif

    /// <summary>
    /// 旋转块的属性参照
    /// </summary>
    /// <param name="brf"></param>
    /// <param name="at"></param>
#if jing
    void RotateAttributeReference(BlockReference brf, AttributeReference at) {
        // 求AABB包围盒
        var boxEdge = new GeometricExtents(at).Edge3;
        at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
        // 此处若用at.AttributeFromBlock(mt)会set两次角度...很奇怪耶
        // 调整对齐,不然08和19不一样显示
        at.AdjustAlignment(brf.Database);
    }
#else
     void RotateAttributeReference(BlockReference brf, AttributeReference at) {
         // 求AABB包围盒
         var boxEdge = at.GeometricExtents;
         var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
         at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
         at.AdjustAlignment(brf.Database);
     }
#endif

    // 旋转要求,
    // 90~270度就改成:块旋转+180度,
    // 处理镜像判断<=,那么其他的呢?引起出错吗?
    bool IsRo(BlockReference brf, bool isMr = false) {
        const double half = Math.PI / 2;
        if(isMr) {
            return half <= brf.Rotation && brf.Rotation < half * 3;
        }
        return half < brf.Rotation && brf.Rotation < half * 3;
    }

}


class AttInfo {
    public string Tag { get; }
    public string Prompt { get; }
    public string TextString { get; }
    public ObjectId TextStyleId { get; }
    public ObjectId ObjectId { get; }

    public AttInfo(AttributeDefinition attdef) {
        ObjectId = attdef.ObjectId;
        Tag = attdef.Tag;
        Prompt = attdef.Prompt;
        TextString = attdef.TextString;
#if NET35
        TextStyleId = attdef.TextStyle;
#else
        TextStyleId = attdef.TextStyleId;
#endif
    }

    public AttInfo(AttributeReference att) {
        ObjectId = att.ObjectId;
        Tag = att.Tag;
        // Prompt = att.Prompt;
        TextString = att.TextString;
#if NET35
        TextStyleId = att.TextStyle;
#else
        TextStyleId = att.TextStyleId;
#endif
    }

    public AttributeReference CreateReference() {
        return new AttributeReference {
            Tag = this.Tag,
#if NET35
            TextStyle = this.TextStyleId,
#else
            TextStyleId = this.TextStyleId,
#endif
        };
    }
}

using的操作

CAD的类用的是DisposableWrapper接口,
不是IDisposable接口,造成没有自动析构.
如果通过一个事务栈,GetObject时候记录到栈内,然后统一释放,
这样就满足要求了,就可以记录在字典中,而不需要用一个数据类做代理.
释放的时候CAD会根据ent.IsNewObject来自动处理的,
加入数据库的就不释放,没加就会释放.
http://bbs.mjtd.com/forum.php?mod=viewthread&tid=191722

using System;
using System.Collections.Generic;

// 定义一个实现了IDisposable接口的类
class MyClass : IDisposable {
    private bool isDisposed = false;
    public bool IsDisposed => isDisposed;
    // 构造函数
    public MyClass() {
        Console.WriteLine("MyClass instance created.");
    }

    // 实现IDisposable接口的Dispose方法
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // 受保护的Dispose方法,用于实际的资源释放
    protected virtual void Dispose(bool disposing) {
        if (isDisposed) return;
        isDisposed = true;
        if (disposing){
            // 释放托管资源
            Console.WriteLine("Managed resources disposed.");
        }
        // 释放非托管资源
        Console.WriteLine("Unmanaged resources disposed.");
    }
}

class Program {
    public static Dictionary<string, MyClass> GetDictionary() {
        var dict = new Dictionary<string, MyClass>();
        using (MyClass my = new MyClass()){
            dict.Add("key", my);
        } // 会在此处就释放了资源
        return dict;
    }

    static void Main()   {
        var map = GetDictionary();
        Console.WriteLine(map["key"].IsDisposed);
    }
}

(完)

posted @   惊惊  阅读(2182)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示