epwt 发表于 2024-3-19 19:46:24

在AutoCAD中多视口展示多张图纸(AutoCAD2018及以上版本可用)

真实场景中,用户存在多张图纸,假设是平面图、剖面图、三维图。用户需要在AutoCAD中修改某些图纸的细节,使其更符合真实情况。但是修改图纸需要其它图纸做参考,以防出错。因此,同一个窗口,显示多张图纸,方便用户校对数据、修改的需求就出现了。先上效果吧:一开始想用最简单的方案,直接调用Autocad的命令,把多张图纸的tab窗体显示出来,让用户自己修改。发现这个不友好,一是用户得处理开始窗体。开始界面一直显示,没法拿到它的句柄,把它最小化,又不能侵入性地把开始界面藏起来。二是这个命令只能平分,遇到个性化需求就无法满足了。给大家看看这个方案出来的效果:   查找资料,最后决定使用视口来解决这个问题。但是视口也有局限性,视口只能对一张图纸进行多视口操作,咱们多张图纸就显得力不从心了。于是就想了个能实现需求的办法,把多张图纸合并成一张,然后开3个视口,每个视口居中显示不同的范围,看起来就像在操作多张图纸了。
      但是也有一个局限,那就是其实三个视口显示的都是一张被合并的图纸,用户缩小或者拖拽,就找不到原先的视口了。于是我们加了个定位的按钮,记录下每个视口在整张图纸中的居中范围,用户找不到修改的图纸了,就可以点击这个按钮进行复原操作。
      还有最后一个问题,用户修改的图纸,怎么分成3张存回到对应的图纸上去?因为我们修改的是同一张图纸,虽然我们记录了原图在被合并后图纸中的范围,但是用户可能在任意范围添加图形元素。我们可以加个按钮,让用户在保存的时候框选区域,选择对应的图纸类型(屏幕、剖面、三维)就可以了。
      解决方案定了,剩下就是写代码的事情了。我们要做的事情如下:

[*]把3张dwg图纸合并成1张dwg,并记录下原始图纸在合并后图纸的范围,用于后期做视口居中操作。注意,由于是多图操作,记得CommandFlags.Session设置上。本文简化使用了块参照。需要注意的是,块插入的时候插入点需要计算一下,防止图纸重叠。
[*]按照比例创建想要的视口,并在不同视口对图纸进行不同范围的居中操作。


      事情定了,就看看是不是已经有代码实现了这些操作。拼一拼,改一改,看能不能快速实现这个需求。查找了编程宝典《AutoCAD VBA&VB.NET开发基础与实例教程》有关于视口的代码,但是不能完全满足需求,代码最终是靠命令实现的视口创建。也许在当时那个版本,还没有C#的接口开放吧。最后参考了AutoCAD的官方blog,这篇
https://adndevblog.typepad.com/autocad/2015/08/create-four-split-modelspace-viewports-and-set-different-orthographic-views.html


拿着代码改了改,很快就实现了需求。

上源码

public void SpliterThreeViewPort()
{
   string templateFile = @"acad.dwt";
   //把三张图纸变成块参照放到一张图纸中
   //todo:替换成你自己的图纸路径
   string sectionalView = "../剖面图.dwg";
   string threeDView = "../三维图.dwg";
   string planView = "../平面图.dwg";

   List<string> drawingNames = new List<string> { planView, sectionalView, threeDView };
   Dictionary<string, ObjectId> dicBlocks = new Dictionary<string, ObjectId>();

   //新建一张图纸
    //todo:替换成你自己的图纸路径
   string combinedPath = @"../combined.dwg";
   Document tempDoc = Application.DocumentManager.Add(templateFile);
   Application.DocumentManager.MdiActiveDocument = tempDoc;
   tempDoc.Database.SaveAs(combinedPath, DwgVersion.Current);

   Document combinDoc = Application.DocumentManager.Open(combinedPath);
   Application.DocumentManager.MdiActiveDocument = combinDoc;

   string blockName = string.Empty;
   List<string> blockNames = new List<string>();
   for (int i = 0; i < drawingNames.Count; i++)
   {
         blockName = Path.GetFileNameWithoutExtension(drawingNames);
         ImportDrawingAsBlkstoCurDoc(combinDoc, drawingNames, combinedPath);
         blockNames.Add(blockName);
   }

   Dictionary<string, Extents3d> dicBlockExtents = InsertBlocks(combinDoc, blockNames);
   Application.DocumentManager.MdiActiveDocument = combinDoc;
   //打开图纸,分成3个视口,并且按照块参照的名称分别居中每个视口的图纸   
   SplitAndSetViewModelViewports(combinDoc, dicBlockExtents.Values.ToList());
}

public Dictionary<string, Extents3d> InsertBlocks(Document doc, List<string> blockNames)
{
   // 记录已插入块参照的外包围框
   Dictionary<string, Extents3d> dicBlockExtents = new Dictionary<string, Extents3d>();

   Database db = doc.Database;
   // 获取要插入的块名称列表

   using (DocumentLock docLock = doc.LockDocument())
   {
         db.UpdateExt(true);
         using (Transaction tr = db.TransactionManager.StartTransaction())
         {
             BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
             BlockTableRecord modelSpace = tr.GetObject(bt, OpenMode.ForWrite) as BlockTableRecord;


             Point3d insertPosition = Point3d.Origin;

             foreach (string blockName in blockNames)
             {
               // 获取块定义
               ObjectId blockId = bt;
               if (blockId.IsNull)
               {
                     continue;
               }

               // 创建块参照
               using (BlockReference blockRef = new BlockReference(insertPosition, blockId))
               {
                     // 计算块的外包围框
                     blockRef.TransformBy(Matrix3d.Scaling(1, insertPosition));
                     Extents3d blockExtents = blockRef.GeometricExtents;

                     // 将块参照插入模型空间
                     modelSpace.AppendEntity(blockRef);
                     tr.AddNewlyCreatedDBObject(blockRef, true);

                     // 更新已插入块参照的外包围框列表
                     dicBlockExtents.Add(blockName, blockExtents);
                     insertPosition = blockExtents.MaxPoint;
               }
             }
             tr.Commit();
         }
   }
   return dicBlockExtents;
}

public static void SplitAndSetViewModelViewports(Document doc, Dictionary<string, Extents3d> dicExtents)
{
    using (DocumentLock docLock = doc.LockDocument())
    {
      Database db = doc.Database;

      db.UpdateExt(true);
      Extents3d dbExtent = new Extents3d(db.Extmin, db.Extmax);

      using (Transaction tr = db.TransactionManager.StartTransaction())
      {
            ViewportTable vt = tr.GetObject(
                db.ViewportTableId, OpenMode.ForWrite)
                as ViewportTable;

            ViewportTableRecord vtr1 = tr.GetObject(
                doc.Editor.ActiveViewportId,
                OpenMode.ForWrite) as ViewportTableRecord;

            Point2d ll = vtr1.LowerLeftCorner;
            Point2d ur = vtr1.UpperRightCorner;

            List<string> extentsKeys = dicExtents.Keys.ToList();

            vtr1.LowerLeftCorner = ll;
            vtr1.UpperRightCorner = new Point2d(
                ll.X + (ur.X - ll.X) * 0.5,
                ll.Y + (ur.Y - ll.Y));
            vtr1.SetViewDirection(OrthographicView.TopView);
            ZoomExtents(vtr1, dicExtents]);

            ViewportTableRecord vtr2 =
            CreateVTR(vt, vtr1,
            new Point2d(ll.X + (ur.X - ll.X) * 0.5, ll.Y + (ur.Y - ll.Y) * 0.5),
            new Point2d(ll.X + (ur.X - ll.X), ll.Y + (ur.Y - ll.Y)),
            dicExtents], OrthographicView.TopView);
            vt.Add(vtr2);
            tr.AddNewlyCreatedDBObject(vtr2, true);

            ViewportTableRecord vtr3 =

                CreateVTR(vt, vtr1,
                  new Point2d(ll.X + (ur.X - ll.X) * 0.5, ll.Y),
                  new Point2d(ll.X + (ur.X - ll.X), ll.Y + (ur.Y - ll.Y) * 0.5),
                dicExtents], OrthographicView.TopView);
            vt.Add(vtr3);
            tr.AddNewlyCreatedDBObject(vtr3, true);

            // Update the display with new tiled viewports
            doc.Editor.UpdateTiledViewportsFromDatabase();

            // Commit the changes
            tr.Commit();
      }
    }

}

/// <summary>
/// 将图纸作为一个块整个插入导数据库
/// </summary>
/// <param name="destDb"></param>
/// <param name="FileName">图纸的完整路径,dxf或者dwg都可以</param>
/// <returns></returns>
///
public static void ImportDrawingAsBlkstoCurDoc(Document destDoc, string strFileName, string savePath)
{
    using (DocumentLock docLock = destDoc.LockDocument())
    {
      using (Database db = new Database(false, true))
      {
            // Read the DWG into our side database
            db.ReadDwgFile(strFileName, FileShare.Read, true, "");
            // Create a list of block identifiers (will only contain one entry, the modelspace ObjectId)
            ObjectIdCollection ids = new ObjectIdCollection();
            // Start a transaction on the source database

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                // Open the block table
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

                // Add modelspace to list of blocks to import
                ids.Add(bt);

                // Committing is cheaper than aborting
                tr.Commit();
            }
            // Copy our modelspace block from the source to

            // destination database

            // (will also copy required, referenced objects)

            IdMapping im = new IdMapping();

            db.WblockCloneObjects(
            ids,
            destDoc.Database.BlockTableId,
            im,
            DuplicateRecordCloning.MangleName,
            false
            );

            using (Transaction tr2 = destDoc.Database.TransactionManager.StartTransaction())
            {
                // Work through the results of the WblockClone
                foreach (IdPair ip in im)
                {
                  // Open each new destination object, checking for

                  // BlockTableRecords

                  BlockTableRecord btr = tr2.GetObject(ip.Value, OpenMode.ForRead) as BlockTableRecord;
                  if (btr != null)
                  {
                        // If the name starts with the modelspace string

                        if (btr.Name.StartsWith(BlockTableRecord.ModelSpace, StringComparison.InvariantCultureIgnoreCase))
                        {
                            // Get write access to it and change the name

                            // to that of the source drawing

                            btr.UpgradeOpen();
                            btr.Name = Path.GetFileNameWithoutExtension(strFileName);
                        }
                  }
                }
                db.UpdateExt(true);
                // We need to commit, as we've made changes
                tr2.Commit();
            }
            db.SaveAs(savePath, DwgVersion.Current);
      }
    }
}

public static ViewportTableRecord CreateVTR(
   ViewportTable vt, ViewportTableRecord refVTR,
   Point2d ll, Point2d ur, Extents3d dbExtent,
   OrthographicView ov)
{
    ViewportTableRecord newVTR = new ViewportTableRecord();

    newVTR.LowerLeftCorner = ll;
    newVTR.UpperRightCorner = ur;
    newVTR.Name = "*Active";

    newVTR.ViewDirection = refVTR.ViewDirection;
    newVTR.ViewTwist = refVTR.ViewTwist;
    newVTR.Target = refVTR.Target;
    newVTR.BackClipEnabled = refVTR.BackClipEnabled;
    newVTR.BackClipDistance = refVTR.BackClipDistance;
    newVTR.FrontClipEnabled = refVTR.FrontClipEnabled;
    newVTR.FrontClipDistance = refVTR.FrontClipDistance;
    newVTR.Elevation = refVTR.Elevation;
    newVTR.SetViewDirection(ov);

    ZoomExtents(newVTR, dbExtent);

    return newVTR;
}

public static void ZoomExtents
    (ViewportTableRecord vtr, Extents3d dbExtent)
{
    //get the screen aspect ratio to
    // calculate the height and width
    double scrRatio = (vtr.Width / vtr.Height);

    //prepare Matrix for DCS to WCS transformation
    Matrix3d matWCS2DCS
      = Matrix3d.PlaneToWorld(vtr.ViewDirection);

    //for DCS target point is the origin
    matWCS2DCS = Matrix3d.Displacement
      (vtr.Target - Point3d.Origin) * matWCS2DCS;

    //WCS Xaxis is twisted by twist angle
    matWCS2DCS = Matrix3d.Rotation(-vtr.ViewTwist,
                                    vtr.ViewDirection,
                                    vtr.Target
                              ) * matWCS2DCS;

    matWCS2DCS = matWCS2DCS.Inverse();

    //tranform the extents to the DCS
    // defined by the viewdir
    dbExtent.TransformBy(matWCS2DCS);

    //width of the extents in current view
    double width
         = (dbExtent.MaxPoint.X - dbExtent.MinPoint.X);

    //height of the extents in current view
    double height
         = (dbExtent.MaxPoint.Y - dbExtent.MinPoint.Y);

    //get the view center point
    Point2d center = new Point2d(
         (dbExtent.MaxPoint.X + dbExtent.MinPoint.X) * 0.5,
         (dbExtent.MaxPoint.Y + dbExtent.MinPoint.Y) * 0.5);

    //check if the width' in current window
    //if not then get the new height as per the
    // viewports aspect ratio
    if (width > (height * scrRatio))
      height = width / scrRatio;

    vtr.Height = height;
    vtr.Width = height * scrRatio;
    vtr.CenterPoint = center;
}








你有种再说一遍 发表于 2024-3-19 20:47:28

早说要VTR,为什么不早说...阿惊啥东西没敲过(嘻)

lxl217114 发表于 2024-3-19 22:34:08

你有种再说一遍 发表于 2024-3-19 20:47
早说要VTR,为什么不早说...阿惊啥东西没敲过(嘻)

大家一起喊:“惊神”(破音)

yefei812678 发表于 2024-3-20 07:56:56

感谢分享感谢分享感谢分享

zilong136 发表于 2024-3-20 12:03:10

表示没看懂

lzspain 发表于 2024-3-20 12:55:50

只能膜拜大佬!

e2002 发表于 2024-3-21 16:12:30

我们实际工作中,通常对于这种多个文件互相参考的时候,基本还是直接tile显示全部打开的文档。如果打开的文件数量较多,确实想要一下就找到自己需要查看的那个文档是不太可能的。

所以在机器能带动的情况下,外部参照其他文件是最合适的做法。
多个视口的管理,视口的恢复等,内置的命令在交互上效率是比较低的,所以近年来的版本,在界面上加了 ViewCube, ViewControl等工具。

为了应对工作中的这类需求,之前也写了几个Vports的工具,例如双视口位置同步观察,多视口仿鹰眼,VportConfig快速切换等。

panliang9 发表于 2024-3-21 16:24:12

我都是在图纸上加链接,总装、部件、零件一级级的通过图纸编号链接起来。

如果有几张图,相互参照,可以直接插入一张总图来看。

OooCcc 发表于 2024-3-22 09:34:24

e2002 发表于 2024-3-21 16:12
我们实际工作中,通常对于这种多个文件互相参考的时候,基本还是直接tile显示全部打开的文档。如果打开的文 ...

个人以为还是外部参照比较现实,这里说的技术作为技术文档和实现还是很牛掰的。
但是个人觉得,实际应用时,简单图形还行,如果复杂图形,感觉会更难操作。

外部参照完全可以实现本文所要达到的功能。
页: [1]
查看完整版本: 在AutoCAD中多视口展示多张图纸(AutoCAD2018及以上版本可用)