Sunday, January 11, 2015

Getting objects in the modelspace by Type

So we're going to talk about getting objects from the modelspace. First things first, I'm not really a fan of getting objects from the modelspace utilizing "TypedValues" because they're a pain in the ass ;) (sorry to all of you "TypedValue" purists, but you know its true...), so we're going to explore a different way of doing it. We're also going to run some metrics on both of the possible solutions to see which way is actually more efficient. I say this like i don't already know, haha.

Getting started, lets talk about some of the more "advanced" features of my code usage in order to get an idea about what we are working with.And by "advanced" I mean relatively basic C# syntax, however, by comparison its not the run-of-the-mill AutoCAD dev code I see on the line. We will start at the top of the method:

NOTE: If you are familiar with the .net framework please feel free to skip the explanations. If not, you can use the links to review MSDN explanations and examples.

IEnumerable<T> : The most base type of  an object that can be iterated over.
params : You can pass a variable number of parameters to the method.
Func<T,T> : A Delegate type that takes a parameter and returns a value.


using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

public IEnumerable<ObjectID> GetIdsByTypeIteration(params Type[] types)
{
    // We will use this Delegate to return the Class of the RXObject
    Func<Type, RXClass> getClass = RXObject.GetClass;

    // Make a HashSet of the Types of Entities we want to get from the modelspace.
    var acceptableTypes = new HashSet<rxclass>(

    // Use a little linq with a method group to iterate through the array
    types.Select(getClass));

    var doc = Application.DocumentManager.MdiActiveDocument;

    // Create a transaction to grab the modelspace. Here you can use a regular transaction
    // "TransactionManager.StartTransaction()". I prefer StartOpenClose out of habit
    // because its a bit faster.
    using (var trans = doc.TransactionManager.StartOpenCloseTransaction())
    {
        var modelspace = (BlockTableRecord) 
            // Here we utilize "SymbolUtilityServices" to get the Modelspace id from our database.
            trans.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(doc.Database), OpenMode.ForRead);

        // We use a little more linq to cast the IEnumerable object and iterate thru its ObjectIds
        // we will check our hashset contains the RXClass of the ObjectId by accessing the "ObjectClass" 
        // property of the RXObject.
        var ids = modelspace.Cast<ObjectID>().Where(id => acceptableTypes.Contains(id.ObjectClass));

        // Commit the transaction.
        trans.Commit();

        return ids;
    }
}

So our implementation above is pretty basic, Analyzing the given code, we are iterating over a collection of ObjectIDs, which are structs (value types). During this process (in memory) we are copying the objects from unmanaged memory to the managed stack, which is good. Since we are working with a locally defined collection we will not be creating a really large collection on our LOH (Large Object Heap) and effectively avoiding LOH fragmentation. Because we iterate over the objects only one time, our Big O notation would be O(n), (given n is the count of available objectIds in the model space) which is the best case scenario. However, If you wanted to further check to see if the polyline was a certain color, you should change this implementation so that you don't have to iterate over a smaller collection again, ergo changing your big O notation and the effectiveness of your algorithm.

 That was probably to deep for the majority of people reading this, but it was meant to catch your attention.. Go read about Memory Management, It is interesting.:)

Moving forward. Our default implementation for the TypedValue approach would be a method that would have the functionality to get multiple types. The problem with this is types that are represented differently in the unmanaged code (C++). For instance, Polylines would be an issue since there is not a LWPolyline type in the managed API.

Compare the implementations of calling each method and think about best practices. Personally, I like to stay away from string literals as much as possible.

The iterative approach:
var ids = GetIdsByTypeIteration(typeof(Polyline), typeof(Polyline2d), typeof(Polyline3d));

The TypedValue approach:
var ids = GetIdsByTypeTypeValue("POLYLINE", "LWPOLYLINE", "POLYLINE2D", "POLYLINE3d");


Now, we are going to execute the same task, but using the TypedValue approach:
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;

public ObjectIdCollection GetIdsByTypeTypeValue(params string[] types)
{
    // Get the document
    var doc = Application.DocumentManager.MdiActiveDocument;

    // Get the editor to make the selection
    Editor oEd = doc.Editor;

    // Add our or operators so we can grab multiple types.
    IList<TypedValue> typedValueSelection =  new List<TypedValue> {
                    new TypedValue(Convert.ToInt32(DxfCode.Operator), "<or"),
                    new TypedValue(Convert.ToInt32(DxfCode.Operator), "or>")
                }; 

    // We will need to insert our requested types into the collection.
    // Since we knew we would have to insert they types inbetween the operators..
    // I used a Enumerable type which gave me that functionallity. (IListf<T>)
    foreach (var type in types)
        typedValueSelection.Insert(1,new TypedValue(Convert.ToInt32(DxfCode.Start),type));
    
    SelectionFilter selectionFilter = new SelectionFilter(typedValueSelection.ToArray());

    // because we have to.. Not really sure why, I assume this is our only access point
    // to grab the entities that we want. (I am open to being corrected)
    PromptSelectionResult promptSelectionResult = oEd.SelectAll(selectionFilter);

    // return our new ObjectIdCollection that is "Hopefully" full of the types that we want.
    return new ObjectIdCollection(promptSelectionResult.Value.GetObjectIds());
}

As you can see they're both almost the same amount of code, which is cool. There is a new collection type in each. In the TypedValue implementation, you have to use some custom Acad Types.. but that is cool too...

To get these metrics, I made a drawing with > 200k objects comprised of Polylines, circles and lines. During each test, I roughly halved the amount of objects in the modelspace. Here are the metrics strait from the command line.. I'll let you be the judge... Feel free to leave feed back and/or download the solution for this post.



Saturday, January 10, 2015

Setting Up Your Development Environment

So, today’s topic is going to be setting up your development environment. We will go through where to download the AutoCAD ObjectARX SDK, finding a good location for our DLLs, setting up our first AutoCAD development project and building our first AutoCAD .Net plug-in… Yay.. on a side note. I’m incredibly sick. Right at this very instant, my amazing wife is rubbing my chest, upper lip, and feet with “Thieves oil”. It’s made out of real thieves, that way you know it’s legit.
Ok, so where exactly do we download this  from? Good question? Answer. <- That is the answer…
Now on this page Autodesk is going to ask you for a lot of information, muscle through it. Give them what they ask for. Right below the text box where they’re requesting your bank account information, there is going to be a few radio buttons where you can select what year version of the ObjectARX SDK you would like to download. Don’t be stupid and not download the correct one. Read the text. I am working in AutoCAD 2012, so obviously I will be downloading the 2012 version. Congratulations!!! You have located the ObjectARX SDK, downloaded it, and are about to install it. Yay!!
Now navigate to where ever your browser of choice stores the files you download “on the line” and double click shit. After clicking “Run” you will get this screen

Finding a good location for your files to live. If you notice the text box above the progress bar, the text reads "C:\ObjectARX 2012". Just put a backslash in place of the space. this is where i like mine to live. "C:\ObjectARX\2012".  Please put them where you are not going to forget where they’re at.

After clicking install and the files are done being unpacked to your directory of choice, they should look like the directory structure below.

What is important to us today is the “..\inc” directory. Our two main development DLLs are called “AcDbMgd.dll” and “AcMgd.dll”.  They are located in the "C:\ObjectARX\2012\inc"directory.



Moving on, Let's open an instance of Visual Studio and create a project. I am going to name my project “ExploitingAutoCAD”.






Next we are going to add the references to our project. We right click on the references tree view item and
click "Add Reference...".


Which will then give us the Reference Manager. We will click on "Browse" and then navigate to "c:\ObjectARX\2012\inc" where "AcMgd.dll" and "AcDbMgd.dll" live. After selecting them and clicking Ok, your reference manager should look like the one below.  We click "OK" and we are off. 





This next part is very important, so please pay very close attention. We MUST select our references and and view the properties of our reference. You will see in the data grid the property of "Copy Local" or if you are using SharpDevelop "Local Copy", you must set this to false. If we do not, when we compile our project it will copy these .DLLs to the "..\bin\debug" if you are debugging or "..\bin\release" if you are debugging the release version of your project. AutoCAD will load these and not the .DLLs that are located in the install folder of AutoCAD. Which in turn will generate an error. This would not be a good thing.

One last thing, There is an issue that happens sometimes when developing an AutoCAD plugin while debugging, Some times the text objects will not show up in your drawing. If you run into this error its probably because as setting in the options is not set correctly. on the MenuBar of Visual Studio, go to: Tools > Options > select the "Debugging" ListViewItem on the left and scroll all the way down. You will see at the very bottom an option that says "Use Managed Compatibility Mode", make sure that it is checked. See the image below.