cad.net 文字偏移及符号表

动图演示

绑定参照后出现

img

代码克隆后出现

img

img

有问题的代码(不要抄我)

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

namespace BlockImport
{
    public class BlockImportClass
    {
        [CommandMethod("tt")]
        public void ImportBlocks()
        {
            DocumentCollection dm = Application.DocumentManager;
            Editor ed = dm.MdiActiveDocument.Editor;
            Database destDb = dm.MdiActiveDocument.Database;
            Database sourceDb = new Database(false, true);
            PromptResult sourceFileName;
            try
            {
                //从命令行要求用户输入以得到要导入的块所在的源 DWG 文件的名字 
#if false
                sourceFileName = ed.GetString("\n输入来源DWG的完整路径: "); //把源 DWG 读入辅助数据库  
                sourceDb.ReadDwgFile(sourceFileName.StringResult, System.IO.FileShare.Read, true, "");  
#else
                sourceDb.ReadDwgFile(@"D:\桌面\T.dwg", System.IO.FileShare.Read, true, "");
#endif
                //用集合变量来存储块 ID 的列表 
                var blockIds = new ObjectIdCollection(); 
                using (Transaction tr = sourceDb.TransactionManager.StartTransaction())
                {
                    //打开块表 
                    BlockTable bt = tr.GetObject(sourceDb.BlockTableId, OpenMode.ForRead, false) as BlockTable;
                    //在块表中检查每个块 
                    foreach (ObjectId btrId in bt)
                    { 
                        var btr = tr.GetObject(btrId, OpenMode.ForRead, false) as BlockTableRecord;
                        //只添加有名块和非 layout 块(layout 块是非 MS 和非 PS 的块)
                        if (!btr.IsAnonymous && !btr.IsLayout)
                            blockIds.Add(btrId);
                        btr.Dispose(); //释放块表记录引用变量所占用的资源 
                    }
                    bt.Dispose();//释放块表引用变量所占用的资源 //没有作改变,不需要提交事务 
                    tr.Dispose();
                }
                //用 WblockCloneObjects 把所有的块从源库拷贝块到目的库的块表中 
                //这只能实现导入块到指定的数据库中,但不是深度克隆,
                //若对块参照实行深度克隆的话,其所引用的块也会被克隆到指定的数据库中 
                IdMapping mapping = new IdMapping();
                sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId, mapping, DuplicateRecordCloning.Replace, false);
                ed.WriteMessage
                    ("\n复制 "
                    + blockIds.Count.ToString()
                    + " 个块的定义"
                    //+ sourceFileName.StringResult
                    + " 到当前绘图的块表记录."
                    );
            }
            catch (Autodesk.AutoCAD.Runtime.Exception ex)
            {
                ed.WriteMessage("\n复制过程中的错误: " + ex.Message);
            }
            sourceDb.Dispose();
        }
    }
}

这是一个kean在博客的dbx例子,也就是后台打开dwg.

使用它时,在某些情况下单行文字/属性会出现不在文字图形的基点位置,此问题我称为"字体偏移".

bug图元

单行文字或属性.

当克隆来源使用了:"新宋体.TTF",对齐样式为:"中间,正中,中"等...非"左"的对齐方式,这将导致偏移发生.

左: AttachmentPoint.BaseLeft

但是由于本人测试过少,不清楚此问题的出现是否存在于所有的.TTF文件下,便已经找到了解决方案了,对此并没有详尽的bug报告.

测试环境

测试环境: 中文版Acad2008~Acad2019

1: 使用外部参照,参照的dwg中有本文列明的bug图元,然后绑定,就会触发

2: 使用克隆命令,跟上面kean的代码一样

测试环境: 中文版Acad2020+

1: 同旧版本

2: 同旧版本

3: 由于新版本桌子重写了一个新的insert命令,也就是i这个命令,它会弹出一个带预览的小面板,这个命令也会导致此问题发生.

解决方案1:

一旦出现了字体偏移,那么此情况会保存到dwg内.

而当你不加载问题功能前提下,打开图纸,双击文字,它会从偏移态恢复为正常态.

遇到此情况不妨用代码:遍历全图,移动0,0到0,0.

解决方案2:

1: 来源数据库的文字样式表修改"新宋体"为任何一种大字体.
2: 再更改文字实体对齐方式为"左" AttachmentPoint.BaseLeft
3: 再进行克隆(WblockCloneObjects).
4: 最后在目标数据库还原样式.
(已经亲测,必须这样做,只改一项都不行)
5.插入了之后需要刷新块,否则显示还是不正确的,刷新的方法是块内图元移动0,0到0,0,不需要嵌套遍历仅需遍历一层.(此处无代码,自己干)

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;

namespace JoinBox
{
    /*调用*/
    public static partial class BlockTool
    {
        /// <summary>
        /// 跨图克隆,不允许事务包裹(克隆来源的某个块到目标数据库)
        /// </summary>
        /// <param name="ddb">目标数据库,通常是当前数据库</param>
        /// <param name="path">来源dwg的路径</param>
        /// <param name="blockName">拿到插入的块名</param>
        /// <returns></returns>
        public static IdMapping CloneBlock(this Database ddb, string path, string blockName = null)
        {
            var cb = new CloneBlock(ddb, path, blockName);
            return cb.Mapping;
        }
    }

    /*封装*/
    public class CloneBlock
    {
        //key是来源图块的id,value是克隆到新图块的id
        public IdMapping Mapping { get; private set; }

        //对齐方式
        AttachmentPoint _zuo = Attachment.Get("左");//AttachmentPoint.BaseLeft

        //引起偏移的字体
        string[] _fontsCausesOffset = new string[] { "新宋体", "宋体", "黑体" };

        /// <summary>
        /// 跨图克隆,不允许事务包裹(克隆来源的某个块到目标数据库)
        /// </summary>
        /// <param name="ddb">目标数据库,通常是当前数据库</param>
        /// <param name="path">来源dwg的路径</param>
        /// <param name="blockName">拿到插入的块名</param>
        /// <returns></returns>
        public CloneBlock(Database ddb, string path, string blockName = null)
        {
            if (ddb == null)
            {
                throw new ArgumentNullException(nameof(ddb));
            }
            if (!File.Exists(path))
            {
                throw new ArgumentNullException(nameof(path));
            }
            Mapping = new IdMapping();

            //记录将要修改了对齐的图元
            List<JustifyInfo> justifyOfLeft = new();

            //来源的块list
            ObjectIdCollection btRecIds = new();

            //key用户定义的名称,value字体本名
            Dictionary<string, FontDescriptor> fonstNameAndFile = new();

            //打开来源数据库,有引起错误偏移的字体,就修改为大字体
            OpendDwg.Read(path, sdb =>
            {
                // 设置所有的实体从"非左"到"左",并记录图元对齐点信息,
                // 要在修改文字样式前,否则点信息被改了
                sdb.Action(tr =>
                {
                    sdb.TraverseEntitys(tr, ent =>
                    {
                        var entType = ent.GetType();
                        var entJustify = entType.GetProperty("Justify");//反射获取属性
                        if (entJustify != null)
                        {
                            var justifyObj = entJustify.GetValue(ent, null);
                            var justify = (AttachmentPoint)justifyObj;
                            if (justify != _zuo)
                            {
                                var entPosition = entType.GetProperty("Position");//反射获取属性
                                var pt3 = (Point3d)entPosition.GetValue(ent, null);

                                var entAlignmentPoint = entType.GetProperty("AlignmentPoint");//反射获取属性
                                var ptAl = (Point3d)entAlignmentPoint.GetValue(ent, null);

                                var jinfo = new JustifyInfo()
                                {
                                    EntityName = entType.Name,
                                    ObjectId = ent.ObjectId,
                                    Justify = justify,
                                    Position = pt3,
                                    AlignmentPoint = ptAl
                                };
                                justifyOfLeft.Add(jinfo);

                                entJustify.SetValue(ent, _zuo, null); // txt.Justify = _zuo;
                            }
                        }
                        return false;
                    });
                });

                sdb.Action(tr =>
                {
                    // 修改文字样式
                    sdb.TraverseTextStyleTable(tr, tsRec =>
                    {
                        if (_fontsCausesOffset.Contains(tsRec.Font.TypeFace))
                        {
                            fonstNameAndFile.Add(tsRec.Name, tsRec.Font);
                            tr.AddTextStyle(sdb, tsRec.Name, "txt.shx", "gbcbig.shx");
                        }
                        return false;
                    });
                });

                // 打开块表,找到指定块
                sdb.Action(tr =>
                {
                    sdb.TraverseBlockTable(tr, btRec =>
                    {
                        //有名块 && 非layout块
                        if (!btRec.IsAnonymous && !btRec.IsLayout)
                        {
                            if (btRec.Name == blockName)//指定块的时候就中断
                            {
                                btRecIds.Clear();
                                btRecIds.Add(btRec.ObjectId);
                                return true;
                            }
                            else //没有来源块就记录所有的
                            {
                                btRecIds.Add(btRec.ObjectId);
                            }
                        }
                        btRec.Dispose(); //释放块表记录引用变量所占用的资源
                        return false;
                    });
                });

                //克隆到目标块表内
                sdb.WblockCloneObjects(btRecIds, ddb.BlockTableId, Mapping, DuplicateRecordCloning.Replace, false);
            });

            //还原文字样式
            ddb.Action(tr =>
            {
                foreach (var item in fonstNameAndFile)
                {
                    tr.AddTextStyle(ddb, item.Key, item.Value);
                }
            });

            //"左"到"非左",还原对齐方式
            ddb.Action(tr =>
            {
                var kv = Mapping.ToDictionary();
                foreach (var changInfo in justifyOfLeft.ToArray())
                {
                    if (kv.Keys.Contains(changInfo.ObjectId))
                    {
                        var ent = kv[changInfo.ObjectId].ToEntity(tr);
                        ent.UpgradeOpen();

                        //和反射等效的
                        //if (ent is DBText txt)//因为属性继承DBText,所以这句参与属性的.
                        //{
                        //    txt.Justify = changInfo.Justify;
                        //    txt.Position = changInfo.Position;
                        //    txt.AlignmentPoint = changInfo.AlignmentPoint;//设置对齐点
                        //    justifyOfLeft.Remove(changInfo);
                        //}

                        //利用反射设置
                        var entType = ent.GetType();
                        var entJustify = entType.GetProperty("Justify");//反射获取属性
                        if (entJustify != null)
                        {
                            entJustify.SetValue(ent, changInfo.Justify, null);
                        }

                        var entPosition = entType.GetProperty("Position");//反射获取属性
                        if (entPosition != null)
                        {
                            entPosition.SetValue(ent, changInfo.Position, null);
                        }

                        var entAlignmentPoint = entType.GetProperty("AlignmentPoint");//反射获取属性
                        if (entAlignmentPoint != null)
                        {
                            entAlignmentPoint.SetValue(ent, changInfo.AlignmentPoint, null);
                        }

                        justifyOfLeft.Remove(changInfo);//并不会处理完集合内所有的图元,因为有些是来源图非插入块的,了解之后可以删除本句.
                        ent.DowngradeOpen();
                    }
                }
            });
            //之后再插入和刷新克隆过来的块,需要遍历块内并且移动0
        }

        /// <summary>
        /// 简易的文字类
        /// </summary>
        public class JustifyInfo
        {
            /// <summary>
            /// 名称
            /// </summary>
            public string EntityName;
            /// <summary>
            /// 文字的实体id
            /// </summary>
            public ObjectId ObjectId;
            /// <summary>
            /// 文字的对齐方式
            /// </summary>
            public AttachmentPoint Justify;
            /// <summary>
            /// 文字的基点
            /// </summary>
            public Point3d Position;
            /// <summary>
            /// 文字的对齐点
            /// </summary>
            public Point3d AlignmentPoint;
        }
    }
}

子方法:

OpendDwg.Read和遍历全图的方法

获取真实块名

public static partial class BlockTool
{
    /// <summary>
    /// 转换为词典
    /// </summary>
    /// <param name="mapping"></param>
    /// <returns></returns>
    public static Dictionary<ObjectId, ObjectId> ToDictionary(this IdMapping mapping)
    {
        var keyValuePairs = new Dictionary<ObjectId, ObjectId>();
        foreach (IdPair item in mapping)
        {
            keyValuePairs.Add(item.Key, item.Value);
        }
        return keyValuePairs;
    }
}

/// <summary>
/// 修改文字样式
/// </summary>
/// <param name="tr"></param>
/// <param name="db"></param>
/// <param name="name"></param>
/// <param name="fontDescriptor">已有字体数据</param>
/// <returns></returns>
public static ObjectId AddTextStyle(this Transaction tr, Database db, string name, FontDescriptor fontDescriptor)
{
    ObjectId ida = ObjectId.Null;
    if (string.IsNullOrEmpty(name))
    {
        throw new ArgumentNullException("字体参数空");
    }
    //已有就修改内容
    db.TraverseTextStyleTable(tr, teRec =>
                              {
                                  if (teRec.Name == name)
                                  {
                                      teRec.UpgradeOpen();
                                      teRec.Font = fontDescriptor;
                                      teRec.DowngradeOpen();
                                      ida = teRec.ObjectId;
                                      return true;
                                  }
                                  return false;
                              });
    return ida;
}

/// <summary>
/// 新建文字样式
/// </summary>
/// <param name="db"></param>
/// <param name="name">样式名</param>
/// <param name="smallfont">字体名</param>
/// <param name="bigfont">大字体</param>
/// <param name="height">高度</param>
/// <param name="xscale">宽度因子</param>
/// <returns></returns>
public static ObjectId AddTextStyle(this Transaction tr, Database db, string name, string smallfont,
                                    string bigfont = null, double xscale = 1, double height = 0)
{
    ObjectId ida = ObjectId.Null;
    if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(smallfont))//如果字体不设置,就只返回
    {
        throw new ArgumentNullException("字体参数空");
    }
    #if HC2020
        if (!string.IsNullOrEmpty(smallfont))
        {
            smallfont = smallfont.Split('.')[0]; //奇葩的浩辰不支持后缀名
        }
    #endif
        //获取已有这个名字的文字样式表记录
        var st = tr.GetObject(db.TextStyleTableId, OpenMode.ForRead) as TextStyleTable;

    //已有就修改内容
    db.TraverseTextStyleTable(tr, teRec =>
                              {
                                  if (teRec.Name == name)
                                  {
                                      teRec.UpgradeOpen();
                                      teRec.FileName = smallfont;
                                      if (string.IsNullOrEmpty(bigfont))
                                      {
                                          teRec.BigFontFileName = "";
                                      }
                                      else
                                      {
                                          teRec.BigFontFileName = bigfont;
                                      }
                                      teRec.TextSize = height;
                                      teRec.XScale = xscale;
                                      teRec.DowngradeOpen();
                                      ida = teRec.ObjectId;
                                      return true;
                                  }
                                  return false;
                              });

    //没有就新建一个
    if (ida == ObjectId.Null)
    {
        var tstr = new TextStyleTableRecord
        {
            Name = name,
            FileName = smallfont,
            TextSize = height,
            XScale = xscale
        };

        if (string.IsNullOrEmpty(bigfont))
        {
            tstr.BigFontFileName = "";
        }
        else
        {
            tstr.BigFontFileName = bigfont;
        }
        st.UpgradeOpen();
        ida = st.Add(tstr);
        tr.AddNewlyCreatedDBObject(tstr, true);
        st.DowngradeOpen();
    }
    return ida;
}

后记

我的配置是32位修改的Acad2008安装到64位的win10上,但是我的Acad2019也会出现同样的情况,而e大用英文的2019却没有复现成功,

所以我没能正确告诉桌子如何修改的bug(毕竟他们是歪果仁),

后来群里有人说过,如果使用了开图复制,那么文字矩阵不会出错.

Acad2008字体偏移在外部参照绑定的时候也会出现,但是insert却不会,

而Acad2020里面的insert小面板也会,

可见桌子的代码传承有问题,(这个时候没有说我为什么喜欢用旧版本了吧)

bug:Acad2008符号表NetAPI

Acad2008.NetAPI 如果你建一个块,再删除它,再添加一个同名块,用名称获取块定义会得到删除了的那个,然后爆了,

这在2008所有符号表上均存在,所以要循环获取并容错处理,或检测id是否已经被删除,

而高版本已经修复了这个问题.

参考文章

cad.net 所有的克隆方式

(完)

posted @ 2019-01-05 21:01  惊惊  阅读(1249)  评论(0编辑  收藏  举报