Wednesday, July 29, 2026

A Workaround To The Drag&Drop API Operation in AutoCAD 2027/.NET 10.0

There is a recent discussion in Autodesk's AutoCAD .NET API discussion forum on the topic of broken Drag&Drop operation API in AutoCAD 2027 due to the change from .NET 8.0 to .NET 10.0. The root cause of the issue is the changed .NET support to System.Windows.Forms.IData interface in .NET 8.0 (including older .NET Core/Framework versions) and .NET 10.0. IData interface is used in the Drag$Drop operation as the data transfer "middle man" between the source (where the data is dragged) and the target (where the data is dropped). 

In a scenario like this: on a WinForm UI, user can enter radius and center point of a circle; then user can drag the data and drop onto AutoCAD's editor to have the circle drawn. As programmer, we need to derive a custom class from Autodesk.AutoCAD.Windows.DropTarget class to enable our custom data can be passed from the UI to AutoCAD when Application.DoDragDrop() method is called.

Bellow is the code of the Drag&Drop operation.

1. The WinForm UI:


2. The UI's code-behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace DragAndDrop2027
{
    public partial class DrawForm : Form
    {
        private Point3d _center = new Point3d(1000, 1000, 0);
        public DrawForm()
        {
            InitializeComponent();
            SetCenterTextBoxes();
        }
 
        private void CircleLabel_MouseMove(object senderMouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                try
                {
                    var radius = Convert.ToDouble(RadiusTextBox.Text);
                    CircleDropper.DropCircleEntity(thisradius, _center, CadHelper.CreateCircle);
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show($"Error:\n{ex.Message}");
                }
            }
        }
 
        private void PickPointButton_Click(object senderEventArgs e)
        {
            try
            {
                this.Visible = false;
                if (CadHelper.TrySelectCenterPoint(out Point3d center))
                {
                    _center = centerSetCenterTextBoxes();
                    SetCenterTextBoxes();
                }
            }
            finally
            {
                this.Visible = true;
            }
        }
 
        private void SetCenterTextBoxes()
        {
            XTextBox.Text = $"{_center.X}";
            YTextBox.Text = $"{_center.Y}";
            ZTextBox.Text = $"{_center.Z}";
        }
 
        private void DrawForm_FormClosing(object senderFormClosingEventArgs e)
        {
            e.Cancel = true;
            Visible = false;
        }
    }
}
 
3. Custom class for Drag&Drop operation:
using Autodesk.AutoCAD.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace DragAndDrop2026
{
    public class CircleInfo
    {
        public double Radius { getset; }
        public Point3d Center { getset; }
    }
 
    public class CircleDropper
    {
        public static ObjectId DropCircleEntity(
            System.Windows.Forms.Form userUi,
            double radiusPoint3d center,
            Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            var target = new CircleDropTarget(entityCreation);
 
            CircleInfo circle = new() { Radius = radius, Center = center };
            IDataObject dataObject = new DataObject();
            dataObject.SetData(typeof(CircleInfo), circle);
 
            CadApp.DoDragDrop(userUidataObjectDragDropEffects.Copy, target);
 
            return target.EntityId;
        }
    }
 
    public class CircleDropTarget : DropTarget
    {
        private readonly Func<DocumentPoint3ddoubleObjectId> _createFunction;
 
        public CircleDropTarget(Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            _createFunction = entityCreation;
        }
 
        public ObjectId EntityId { private setget; } = ObjectId.Null;
        public override void OnDrop(DragEventArgs e)
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            // Get data passed from Application.DoDragDrop()
            // With .NET 10.0, this GetData(Type) method woul return null
            // if the Type argument is a object Type. This is where this
            // Drag&Drop operation fails
            var data = e.Data.GetData(typeof(CircleInfo));
 
            // The following code would do nothing because the data passed from
            // Application.DoDragDrop() is null, or raise exception if the "data" is
            // not tested for being null
            if (data != null)
            {
                var circle = (CircleInfo)data;
                //Create the entity
                EntityId = _createFunction(dwgcircle.Center, circle.Radius);
 
                CadApp.MainWindow.Focus();
                dwg.Editor.WriteMessage($"\nCircle {EntityId} has been created @{circle.Center}");
            }
        }
    }
 
    public class CadHelper
    {
        public static ObjectId CreateCircle(Document dwgPoint3d positiondouble radius)
        {
            var newId = ObjectId.Null;
            using (dwg.LockDocument())
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    var circle = new Circle();
                    circle.Center = position;
                    circle.Radius = radius;
                    circle.SetDatabaseDefaults();
 
                    var space = (BlockTableRecord)tran.GetObject(
                        dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                    newId = space.AppendEntity(circle);
                    tran.AddNewlyCreatedDBObject(circletrue);
 
                    tran.Commit();
                }
            }
 
            return newId;
        }
 
        public static bool TrySelectCenterPoint(out Point3d center)
        {
            center = Point3d.Origin;
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            var res = dwg.Editor.GetPoint("\nSelect circle center:");
            if (res.Status == PromptStatus.OK)
            {
                center = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

4. The command class to run the UI
private static DrawForm _circleForm = null;
 
[CommandMethod("CreateCircle")]
public static void RunMyCommand()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    if (_circleForm == null)
    {
        _circleForm = new DrawForm();
    }
    CadApp.ShowModelessDialog(CadApp.MainWindow.Handle, _circleForm);
}

Here is how AutoCAD behaves:

1. With AutoCAD 2026 or earlier (the code is based on .NET 8.0 or .NET Framework), there is no issue. Drag&Drop works as expected;

2. With the custom code built against either .NET 8.0 or .NET 10.0, loaded into AutoCAD 2027, the Drag&Drop operation would either do nothing or cause exception when Application.DoDragDrop() is called, depending on whether proper "try...catch..." or null test is applied.

In the aforementioned forum discussion thread, @ActivistInvestor pointed out the issue was caused by Microsoft's removal of BinaryFormatter from .NET 9, which is used in the Drag&Drop operation of Windows' desktop application. The removal of BinaryFormatter is out of security concerns (see this article).

While BinaryFormatter is removed from .NET 9/10, it can be added into custom development from NuGet package as separate reference, though, thus the suggested possible solution by @ActivistInvestor. However, I tried that and did not succeed. So, the code I showed here only works with AutoCAD 2026 and older, but not AutoCAD 2027. Since Autodesk is going to make AutoCAD 2025/2026 to support .NET 10 (i.e. the underline .NET runtime for AutoCAD 2025 or newer would required .NET 10.0), the Drag&Drop code currently works in AutoCAD2025/2026 would also stop once the .NET 10 upgrade service pack applied to AutoCAD 2025/2026. I suppose.

If some offices have custom AutoCAD plugins that use AutoCAD's Drag&Drop API, this is a bad news. Autodesk's support team has already known this issue being reported. I do not know whether/when fix/change to this broken API will be available. So, I kept exploring possible solution. Guess what, it turns out a rather simple fix/workaround is there to deal with this.

As the comments in the code I showed above (the overridden method OnDrop()), in AutoCAD 2027/.NET 10, the DragDropArgs.Data.GetData(Type t) method would return null if the data passed in is a object type. In Visual Studio's debugging mode, if examining the DragDropArgs' data, one would see that the data exists as "System_Com" object and its details are not available (because of the lack of BinaryFormatter in .NET 10). 

During the debugging, a bell suddenly rang in my head: why do I not try to pass a string value as IDataObject in the Application.DoDragDrop() is called? I went ahead and tried it. Voila, now the call to DragDropArgs.Data.GetData(typeof(string)) in OnDrop() method get the correct return - string value, not null.

So, the fix to this Drag&Drop issue becomes: serializing the data of a custom object into a string value and pass it into IDataObject when calling Application.DoDragDrop() and then deserializing the string value in IDataObject of the OnDrop() method back to the custom class object.

Here is my updated code (see the red lines):

using Autodesk.AutoCAD.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace DragAndDrop2027
{
    public class CircleInfo
    {
        public double Radius { getset; }
        public Point3d Center { getset; }
 
        public override string ToString()
        {
            return $"{Radius},{Center.X},{Center.Y},{Center.Z}";
        }
    }
 
    public static class CircleInfoExtension
    {
        extension(stringcircleInfoString)
        {
            public CircleInfoToCircleInfo()
            {
                if (circleInfoString != null)
                {
                    var data = circleInfoString.Split(';');
                    if (data.Length==4)
                    {
                        if (double.TryParse(data[0], out double radius))
                        {
                            var xOk = double.TryParse(data[1], out double x);
                            var yOk = double.TryParse(data[2], out double y);
                            var zOk = double.TryParse(data[3], out double z);
 
                            if (xOk && yOk && zOk)
                            {
                                Point3d ptnew Point3d(x,y,z);
                                return new CircleInfo() { Radius = radius, Center = pt };
                            }
                        }
                    }
                }
 
                return null;
            }
        }
    }
 
    public class CircleDropper
    {
        public static ObjectId DropCircleEntity(
            System.Windows.Forms.Form userUi,
            double radiusPoint3d center,
            Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            var target = new CircleDropTarget(entityCreation);
 
            CircleInfo circle = new() { Radius = radius, Center = center };
            IDataObject dataObject = new DataObject();
            //dataObject.SetData(typeof(CircleInfo), circle);
            dataObject.SetData($"{circle.ToString()}");
 
            CadApp.DoDragDrop(userUidataObjectDragDropEffects.Copy, target);
 
            return target.EntityId;
        }
    }
 
    public class CircleDropTarget : DropTarget
    {
        private readonly Func<DocumentPoint3ddoubleObjectId> _createFunction;
 
        public CircleDropTarget(Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            _createFunction = entityCreation;
        }
 
        public ObjectId EntityId { private setget; } = ObjectId.Null;
        public override void OnDrop(DragEventArgs e)
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            // Get data passed from Application.DoDragDrop()
            // With .NET 10.0, this GetData(Type) method would return null
            // if the Type argument is a object Type. This is where this
            // Drag&Drop operation fails
 
            // Remove this line
            //var data = e.Data.GetData(typeof(CircleInfo));
 
            // Add this line
            var data = e.Data.GetData(typeof(string));
 
            // The following code would do nothing because the data passed from
            // Application.DoDragDrop() is null, or raise exception if the "data" is
            // not tested for being null
            if (data != null)
            {
                // Remove this line
                //var circle = (CircleInfo)data;
 
                // Use the extension method to convert string value t CircleInfo object
                var circle = data.ToString().ToCircleInfo();
 
                //Create the entity
                EntityId = _createFunction(dwgcircle.Center, circle.Radius);
 
                CadApp.MainWindow.Focus();
                dwg.Editor.WriteMessage($"\nCircle {EntityId} has been created @{circle.Center}");
            }
        }
    }
 
    public class CadHelper
    {
        public static ObjectId CreateCircle(Document dwgPoint3d positiondouble radius)
        {
            var newId = ObjectId.Null;
            using (dwg.LockDocument())
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    var circle = new Circle();
                    circle.Center = position;
                    circle.Radius = radius;
                    circle.SetDatabaseDefaults();
 
                    var space = (BlockTableRecord)tran.GetObject(
                        dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                    newId = space.AppendEntity(circle);
                    tran.AddNewlyCreatedDBObject(circletrue);
 
                    tran.Commit();
                }
            }
 
            return newId;
        }
 
        public static bool TrySelectCenterPoint(out Point3d center)
        {
            center = Point3d.Origin;
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            var res = dwg.Editor.GetPoint("\nSelect circle center:");
            if (res.Status == PromptStatus.OK)
            {
                center = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

In my code, for simplicity, I just added a ToString()/ToCircleInfo() method to convert the custom class object to/from string. For more complicated custom class, I might serialize the object class as json string, which is quite common code practice for most programmers these days.

Anyways, with this simple workaround, the Drag&Drop API code now works again, regardless AutoCAD versions/.NET versions.

Monday, January 13, 2025

Determine Object Source When Using COPYCLIP[BASE]/PASTECLIP[ORIG] (Ctrl+C/Ctrl+V)

When using AutoCAD, users can use a standard Windows OS operation to copy/paste entities in the same drawing or across different drawings. The copy/paste operation can be done by simply pressing Ctrl+C/Ctrl+V (as users do with almost all Windows applications); or by entering command "COPYCLIP/COPYBASE" and then "PASTECLIP/PASTEORIG" at AutoCAD's command line; or by right-clicking in AutoCAD's editor and in the content menu, select "Clipboard".

There is an interesting question posted in AutoCAD .NET API forum, asking how to tell the objects being COPY/PASTE-ed are originated from the same drawing, or from different drawing (of course the different drawing has to be open in an AutoCAD session (but source and destination drawings are not necessarily be in the same AutoCAD session).

In order to answer this question, we need to understand how the COPY/PASTE commands work: 

1. When COPYCLIP[BASE] command executes, AutoCAD Wblocks the selected entities and saves it as a DWG file with a name like "A$Cxxxxxxxx.Dwg". The saving location is the location defined in "Temporary Drawing File Lcoation" of the current user profile. Then the temporary file name is somehow stored in Windows Clipboard (this is my guess!). Each time the Ctrl+C pressed, previous temporary file is erased and a new one is created and the Clipboard is updated.

2. When PASTECLIP[ORIG] command executes, AutoCAD locates the temporary file according to the file information in the Clipboard and import its content into the destination drawing (insert as a block, or WblockCloneObject()?). Also, even the source drawing is closed after Ctrl+C is pressed, the "copied" objects can still be pasted to drawings in running AutoCAD sessions. In fact, even AutoCAD session is closed, and opened again, the "copied" objects can still be pasted, as long as the Windows Clipboard has not been updated by pressing Ctrl+C in AutoCAD session, or other Windows applications.

Since the source data is saved as temporary drawing file, thus the source objects can be "pasted" to the same drawing, other drawing (in the same or different AutoCAD session).

Now back to the question of how to know the source objects are originated from which drawing. Now that we know Ctrl+C merely Wblock selected entities to a external drawing file, we can manage to know capture the source document/database information when the Wblocking occurs and make it available when Ctrl+V is pressed. 

Following code shows my attempt of telling where the source entities are originated before them being pasted:

using Autodesk.AutoCAD.DatabaseServices.Filters;
using DriveCadWithCode2025.AutoCADUtilities;
 
[assemblyCommandClass(typeof(AcadMicsTests.MyCommands))]
 
namespace AcadMicsTests
{
    public class MyCommands 
    {
        #region Determine COPY/PASTE source file
 
        private static IntPtr _copySourceDoc = IntPtr.Zero;
        private static bool _isCopyClip = false;
 
        [CommandMethod("HandlePaste")]
        public static void GetDataInClipboard()
        {
            foreach (Document dwg in CadApp.DocumentManager)
            {
                var db = dwg.Database;
 
                db.WblockNotice += Db_WblockNotice;
                dwg.CommandWillStart += Dwg_CommandWillStart;
                dwg.CommandEnded += Dwg_CommandEnded;
            }
 
            CadApp.DocumentManager.DocumentCreated += (oe) =>
            {
                var dwg = e.Document;
                var db = dwg.Database;
 
                db.WblockNotice += Db_WblockNotice;
                dwg.CommandWillStart += Dwg_CommandWillStart;
                dwg.CommandEnded += Dwg_CommandEnded;
            };
        }
 
        private static void Db_WblockNotice(object senderWblockNoticeEventArgs e)
        {
            if (_isCopyClip)
            {
                _copySourceDoc = CadApp.DocumentManager.MdiActiveDocument.UnmanagedObject;
            }
        }
 
        private static void Dwg_CommandEnded(object senderCommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("COPYCLIP") ||
                e.GlobalCommandName.ToUpper().Contains("COPYBASE"))
            {
                _isCopyClip = false;
            }
        }
 
        private static void Dwg_CommandWillStart(object senderCommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("COPYCLIP") ||
                e.GlobalCommandName.ToUpper().Contains("COPYBASE"))
            {
                _isCopyClip = true;
            }
            else if (e.GlobalCommandName.ToUpper().Contains("PASTECLIP") ||
                e.GlobalCommandName.ToUpper().Contains("PASTEORIG"))
            {
                var sourceFile = "";
                foreach (Document dwg in CadApp.DocumentManager)
                {
                    if (dwg.UnmanagedObject == _copySourceDoc)
                    {
                        sourceFile = dwg.Name;
                        break;
                    }
                }
 
                CadApp.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
                    $"\nYou are to paste objects copied from:\n{sourceFile}...\n");
            }
        }
 
        #endregion
    }
}

One thing to pay attention here. As explained, Ctrl+C exports the selected entities as a temporary drawing file linked to Windows Clipboard, which can be pasted to an open drawing in any running AutoCAD session. So, if the copied source is pasted to a different AutoCAD session, my code will not be able to tell where the source is from. Here my focus is to explain how Ctrl+C/Ctrl+V works in AutoCAD under the hood, and based on the understanding, I show that it is not too difficult to tell which file is the source of the copying, assuming the COPY/PASTE is operated in the same AutoCAD session.

I am no sure what the practical use of knowing which drawing file name is the copy source. Considering a drawing file can be renamed as many different file names while their contents remain the same, if knowing where the copy source is from is very critical, relying on file name might not be a good choice. It might be better to intercept into the COPYCLIP/BASE command (when command started and/or when selection is made) and add flags to the selected entities (XData, for example) before the COPYCLIP/BASE command does the WBlock to generate the temporary drawing. Then after pasting the source into a drawing, we can use code to check up the flag... Not sure if it is doable, just a thought. But one just want to casually know if a copy source is from the same drawing or from different open drawing in the AutoCAD session, the code here just does that. With minor modification by handling DocumentLockModeChanged event, the pasting can be vetoed if the copy source is not from the desired drawing (the same drawing or the different drawing).

See video clip below and pay attention to the command line when copied source is pasted:







Monday, December 30, 2024

Clipping Blocks

Can we clip a block (BlockReference) by using API code, as we use "CLIP" command? A recent post in AutoCAD .NET discussion forum asked this question. For whatever reasons, the post stayed quite a while without response.

There are 2 built-in commands in AutoCAD - "XClip" and "Clip", they basically does the same thing under the hood with slight differences of command-line options. In this post, I'll focus on clipping single block (BlockReference).

While this would be my first time coding "Clipping", there was a couple of articles from the most famous AutoCAD .NET API blogger Kean Walmsley looong time ago (more than 12 years ago!):

Adding a 2D spatial filter to perform a simple xclip on an external reference in AutoCAD using .NET

Querying for XCLIP information inside AutoCAD using .NET

So, my code here is mostly simple copy/paste of Kean's, with some necessary updates/changes. The code includes 2 CommandMethods: "ClipBlk" and "RemoveClip":

using Autodesk.AutoCAD.DatabaseServices.Filters;
using DriveCadWithCode2025.AutoCADUtilities;
 
[assemblyCommandClass(typeof(AcadMicsTests.MyCommands))]
 
namespace AcadMicsTests
{
    public class MyCommands 
    {
        private const string FILTER_DICT_NAME = "ACAD_FILTER";
        private const string SPATIAL_DICT_NAME = "SPATIAL";
 
        [CommandMethod("ClipBlk")]
        public static void ClipBlockReference()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var res = ed.GetEntity("\nSelect block reference to clip:");
            if (res.Status != PromptStatus.OK) return;
 
            if (RemoveClipFromBlockReference(res.ObjectId))
            {
                ed.Regen();
            }
 
            var opt = new PromptKeywordOptions("\nChoose clip boundary:");
            opt.AppendKeywordsToMessage = true;
            opt.Keywords.Add("Rectangle");
            opt.Keywords.Add("Polygon");
            opt.Keywords.Default = "Rectangle";
            var kres=ed.GetKeywords(opt);
            if (kres.Status != PromptStatus.OK) return;
 
            List<Point3d>? points = null;
            if (kres.StringResult == "Rectangle")
            {
                if (SelectClipWindow(edout Point3d pt1out Point3d pt2))
                {
                    points=new List<Point3d> { pt1pt2 };
                }
            }
            else
            {
                if (SelectClipPolygon(edout List<Point3d>? pts))
                {
                    if (pts != null)
                    {
                        points = pts;
                    }
                }
            }
 
            if (points == nullreturn;
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(res.ObjectId, OpenMode.ForRead);
                if (blk.ExtensionDictionary.IsNull)
                {
                    blk.UpgradeOpen();
                    blk.CreateExtensionDictionary();
                }
 
                var extDict = (DBDictionary)tran.GetObject(
                    blk.ExtensionDictionary, OpenMode.ForWrite);
                DBDictionary filterDict;
                if (!extDict.Contains(FILTER_DICT_NAME))
                {
                    filterDict = new DBDictionary();
                    extDict.SetAt(FILTER_DICT_NAME, filterDict);
                    tran.AddNewlyCreatedDBObject(filterDicttrue);
                }
                else
                {
                    filterDict=(DBDictionary)tran.GetObject(
                        extDict.GetAt(FILTER_DICT_NAME), OpenMode.ForWrite);
                }
 
                if (filterDict.Contains(SPATIAL_DICT_NAME))
                {
                    var id = filterDict.GetAt(SPATIAL_DICT_NAME);
                    filterDict.Remove(SPATIAL_DICT_NAME);
                    var spFilter = tran.GetObject(idOpenMode.ForWrite);
                    spFilter.Erase();
                }
 
                Point2dCollection clipPoints = GetPolygonBoundary(pointsblk.BlockTransform);
 
                var definition = new SpatialFilterDefinition(
                    clipPointsVector3d.ZAxis, 0.0,
                    double.PositiveInfinity, double.NegativeInfinity, true);
                var filter = new SpatialFilter();
                filter.Definition = definition;
                filterDict.SetAt(SPATIAL_DICT_NAME, filter);
                tran.AddNewlyCreatedDBObject(filtertrue);
 
                tran.Commit();
            }
 
            ed.Regen();
            ed.UpdateScreen();
        }
 
        [CommandMethod("RemoveClip")]
        public static void RemoveClip()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var res = ed.GetEntity("\nSelect a clipped block reference:");
            if (res.Status != PromptStatus.OK) return;
 
            if (RemoveClipFromBlockReference(res.ObjectId))
            {
                ed.Regen();
            }
        }
 
        private static bool SelectClipWindow(Editor edout Point3d pt1out Point3d pt2)
        {
            pt1 = Point3d.Origin;
            pt2 = Point3d.Origin;
 
            var res1 = ed.GetPoint("\nSelect lower-left corner:");
            if (res1.Status == PromptStatus.OK)
            {
                var res2 = ed.GetCorner("\nSelect upper-right corner:"res1.Value);
                if (res2.Status == PromptStatus.OK)
                {
                    pt1 = res1.Value;
                    pt2 = res2.Value;
                    return true;
                }
            }
 
            return false;
        }
 
        private static bool SelectClipPolygon(Editor edout List<Point3d>? points)
        {
            var picker = new PolygonAreaPicker(ed, 1, 25);
            if (picker.PickPolygon())
            {
                points = picker.BoundaryPoints;
                return true;
            }
            else
            {
                points = null;
                return false;
            }
        }
 
        private static bool RemoveClipFromBlockReference(ObjectId blkId)
        {
            var removed = false;
            var db = blkId.Database;
            using (var tran=db.TransactionManager.StartTransaction())
            {
                var blk = tran.GetObject(blkIdOpenMode.ForRead);
                if (!blk.ExtensionDictionary.IsNull)
                {
                    var extDict = (DBDictionary)tran.GetObject(
                        blk.ExtensionDictionary, OpenMode.ForRead);
                    if (extDict.Contains(FILTER_DICT_NAME))
                    {
                        var filterDict = (DBDictionary)tran.GetObject(
                            extDict.GetAt(FILTER_DICT_NAME), OpenMode.ForWrite);
                        if (filterDict.Contains(SPATIAL_DICT_NAME))
                        {
                            var filter = tran.GetObject(
                                filterDict.GetAt(SPATIAL_DICT_NAME), OpenMode.ForWrite);
                            filter.Erase();
                            filterDict.Remove(SPATIAL_DICT_NAME);
                        }
 
                        extDict.UpgradeOpen();
                        extDict.Remove(FILTER_DICT_NAME);
 
                        removed = true;
                    }
                }
 
                tran.Commit();
            }
            return removed;
        }
 
        private static Point2dCollection GetPolygonBoundary(
            IEnumerable<Point3dpointsMatrix3d blkTransform)
        {
            var pts = points
                .Select(pt => pt.TransformBy(blkTransform.Inverse()))
                .Select(pt => new Point2d(pt.X, pt.Y));
            Point2dCollection pt2ds = new Point2dCollection(pts.ToArray());
            return pt2ds;
        }
    }
}

The the private method SelectClipPolygon(), I used a class PolygonAreaPicker for selecting a polygon area. I posted the code of this class in my previously posted article here.

See the video below showing how the code works:



Here are somethings to point out:

1. While the clip boundary seems being created correctly, the boundary created by the code has a noticeable difference from the one created by "CLIP" command: the clip boundary generated by "CLIP" command has a grip, similar to the ones used for control dynamic properties of a dynamic BlockReference. By clicking this grip, the clipping can be flipped to show either external clipping, or internal clipping. However, the clip boundary created by my code does not have this "clipping-flip" grip. Obviously, command "CLIP" does more than just clipping the target BlockReference. Fortunately, the SpatialFilter has a read-write property "Inverted" that we can set by code. Maybe, it is possible to define a grip overrule to mimic the clip boundary created by "CLIP" command. I am not going to dig into it for now.

2. When a clip is applied to a BlockReference, the SpatialFilter that creates the clipping effect is persisted as ExtensionDictionary of the Blockreference. That is, we can examine a BlockReference's ExtensionDictionary to see if it contains a "ACAD_FILTER" dictionary and a DBDictionaryEntry keyed as "SPATIAL". So, theoretically, we can open the existing SpatialFilter object in a Transaction with the SpatialFilter's ObjectId stored in the DBDictionaryEntry. Then I thought, if I want to change the boundary of an existing clipped BlockReference, I could simply open the SpatialFilter in transaction and define a new SpatialFilterDefinition and set it to the SpatialFilter.Definition property, which is set/write-able. Well, as it turns out: yes, I can open the SpatialFilter object for read/write with the ObjectId from the ExtensionDictionary, but it seems the opened SpatialFilter object is read-only, that is, we can look up its "Definition" property to get the boundary's points; as soon as we try to assign the property a newly defined SpatialFilterDefinition, AutoCAD crashes. Therefore, if I want to updated an existing BlockReference's clipping boundary, I need to first delete the attached "ACAD_FILTER" ExtensionDictionary, and then create a new one with a newly defined SpatialFilterDefinition.

3. As described in 2, when updating an existing BlockReference clipping boundary, we need to remove existing one and then create a new one. I found out the 2 steps have to be done in separate Transactions. Or, the clipped BlockReference may not displayed properly.

All in all, the code of the 2 commands showed here basically demonstrates how to use spatial filter API to clip a BlockReference.











Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.