Thursday, December 29, 2011

C#.Net: Circle MText Objects Matching String Entered By User

In my last post we created a C#.Net program to place a red circle around all MText entities in a drawing. Let's add some code to allow the user to enter a specific string value to place circles around. The descriptive inline comments that were in the first program are not duplicated here for clarity.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

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

namespace SLD_Demo
{
    public class MTextTools
    {
        [CommandMethod("SLDcirText")]
        public void SLDcirText()
        {
            //get current document
            Document acDoc =
                Application.DocumentManager.MdiActiveDocument;

            //get AutoCAD database
            Database acCurDb = acDoc.Database;

            //get string to search for
            String sldStrToSrch = SLDgetStringFromUser(acDoc);

            //start db transaction
            using (Transaction acTrans =
                acCurDb.TransactionManager.StartTransaction())
            {
                //open block table
                BlockTable bt = (BlockTable)acTrans.GetObject
                    (acCurDb.BlockTableId, OpenMode.ForRead);

                BlockTableRecord btr = (BlockTableRecord)
                    acTrans.GetObject(bt[BlockTableRecord.ModelSpace],
                    OpenMode.ForRead);

                //iterate through block table to locate mtext objects
                foreach (ObjectId id in btr)
                {
                    //open each object to read
                    DBObject obj = acTrans.GetObject
                        (id, OpenMode.ForRead);

                    //only deal with MText objects
                    MText dbMText = obj as MText;

                    //if current object is Mtext
                    if (dbMText != null)
                    {
                        //assign value of current MText object to a new
                        //String variable
                        String curTxtValue = dbMText.Contents;
                        //now compare to the string entered by user
                        /*I am using the Equals method and overriding to
                         not be case sensitive. This method is returning
                         a boolean value and we are looking for true.
                         There are several string comparison methods.
                         The Contains method would return true if the
                         string entered by user is a part of any Mtext
                         entities. The code for this is:
                           if (curTxtValue.Contains(sldStrToSrch))
                        */

                        if (string.Equals(sldStrToSrch, curTxtValue,
                            StringComparison.CurrentCultureIgnoreCase))
                        {
                            //draw a circle around the mtext entity
                            SLDdrawCir(acCurDb, acTrans, dbMText);
                        }
                    }
                }
                //commit the transaction so we do not leave the db open
                acTrans.Commit();
            }
        }

        //function to get text string from user
        public String SLDgetStringFromUser(Document acDoc)
        {
            PromptStringOptions pStrOpts = new PromptStringOptions(
                "\nFind What: ");

            //control the prompt string options to behave like we want
            //such as allow spaces to be entered
            pStrOpts.AllowSpaces = true;

            //retrieve the string entered by user
            PromptResult pStrRes = acDoc.Editor.GetString(pStrOpts);

            //and assign to a new string variable
            String sldStr = pStrRes.StringResult;

            //return the string variable to calling function
            return sldStr;
        }

        //function to draw circle around an MText object
        public void SLDdrawCir(Database acCurDb,
            Transaction acTrans, MText MtextObj)
        {
            //define a new point object for the center point of circle
            Point3d cenPoint = new Point3d();

            //use the location method of the Mtext object to define
            //the center point coordinates
            cenPoint = MtextObj.Location;

            //define a new Circle object
            /*we use the center point defined above, assign a diameter
             and assign a color using the ColorIndex method from the
             circle object*/
            Circle textCir = new Circle();
            textCir.Center = cenPoint;
            textCir.Diameter = 1.0;
            textCir.ColorIndex = 1;

            //now add the circle to the drawing (model space)
            /*We need to add it to the block table, this is the same
             basic operations when adding geometry to a drawing*/
            BlockTable acBlkTbl;
            acBlkTbl = acTrans.GetObject(
                acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;

            BlockTableRecord acBlkTblRec;
            acBlkTblRec = acTrans.GetObject(
                acBlkTbl[BlockTableRecord.ModelSpace],
                OpenMode.ForWrite)
                as BlockTableRecord;

            acBlkTblRec.AppendEntity(textCir);
            acTrans.AddNewlyCreatedDBObject(textCir, true);
        }
    }
}

To test this build the solution and NETLOAD the .dll into AutoCAD. Place some MText objects in the drawing and then type in SLDcirText at the command line. You will be prompted to enter a string to search for. Enter a string and regardless of case the matching strings should be circled.



Sunday, December 18, 2011

C#.Net: Circle All MText Objects in a Drawing

This program places a red circle around all MText objects in a drawing. The functionality is pretty much stripped down to a basic starting point. This should be a fun program to add additional features to in order to continue exploring C#.Net concepts.

The inline comments should explain each segment, a few things to point out are:

  • We use two functions - one as our entry point with the AutoCAD defined command, and the other as a support function called by the main one.
  • We use some of the provided object methods from the AutoCAD API. For example, as we work with an MText object we use the predefined Location method to determine the coordinates.
Some of the features we will add on in future posts can be:
  • Prompt options to allow the user to change the circle color, size, etc.
  • Place circles around Text objects as well as MText.
  • Place circles around Text/MText objects that match a string provided by the user.
Here is the C# code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

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

namespace SLD
{
    public class SLDtools
    {
        [CommandMethod("SLDcirText")]
        public void SLDcirText()
        {
            //get current document
            Document acDoc =
                Application.DocumentManager.MdiActiveDocument;

            //get AutoCAD database
            Database acCurDb = acDoc.Database;

            //start db transaction
            using (Transaction acTrans =
                acCurDb.TransactionManager.StartTransaction())
            {
                //open block table
                /*all drawing objects are in the block table so in
                 order to look at each object we need to have the
                 block table open for read*/
                BlockTable bt = (BlockTable)acTrans.GetObject
                    (acCurDb.BlockTableId, OpenMode.ForRead);

                BlockTableRecord btr = (BlockTableRecord)
                    acTrans.GetObject(bt[BlockTableRecord.ModelSpace],
                    OpenMode.ForRead);

                //iterate through block table to locate mtext objects
                /*this foreach loop will loop through the block table
                 record looking at each object in the drawing.*/
                foreach (ObjectId id in btr)
                {
                    //open each object to read
                    DBObject obj = acTrans.GetObject
                        (id, OpenMode.ForRead);

                    //only deal with MText objects
                    MText dbMText = obj as MText;

                    //if current object is Mtext then draw a circle
                    //around it
                    if (dbMText != null)
                    {
                        //call separate function to draw circle
                        /*here we want to pass the acad db, the current
                         transaction, and the mtext object that we want
                         to draw a circle around*/
                        SLDdrawCir(acCurDb, acTrans, dbMText);
                    }
                }
                //commit the transaction so we do not leave the db open
                acTrans.Commit();
            }

        }

        //function to draw circle around an MText object
        public void SLDdrawCir(Database acCurDb,
            Transaction acTrans, MText MtextObj)
        {
            //define a new point object for the center point of circle
            Point3d cenPoint = new Point3d();

            //use the location method of the Mtext object to define
            //the center point coordinates
            cenPoint = MtextObj.Location;

            //define a new Circle object
            /*we use the center point defined above, assign a diameter
             and assign a color using the ColorIndex method from the
             circle object*/
            Circle textCir = new Circle();
            textCir.Center = cenPoint;
            textCir.Diameter = 1.0;
            textCir.ColorIndex = 1;

            //now add the circle to the drawing (model space)
            /*We need to add it to the block table, this is the same
             basic operations when adding geometry to a drawing*/
            BlockTable acBlkTbl;
            acBlkTbl = acTrans.GetObject(
                acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;

            BlockTableRecord acBlkTblRec;
            acBlkTblRec = acTrans.GetObject(
                acBlkTbl[BlockTableRecord.ModelSpace],
                OpenMode.ForWrite)
                as BlockTableRecord;

            acBlkTblRec.AppendEntity(textCir);
            acTrans.AddNewlyCreatedDBObject(textCir, true);
        }
    }
}

To test this build the solution and NETLOAD the .dll into AutoCAD. Place some MText objects in the drawing and then type in SLDcirText at the command line.

Friday, December 2, 2011

C#.Net : Add Geometry to AutoCAD

I am fresh off a great AU experience and as usual I was very inspired by several different speakers and products. A lot of inspiration came from the AutoCAD programming side and I want to start providing more C#.Net samples on this blog. It fits nicely with the Streamlined concept of managing a CAD environment and it will help me advance my knowledge in the subject.

I have posted a few previous C#.Net posts: getting started, and a layer application. However, if I am going to provide more code samples I decided I need a better method for getting my source code from Visual Studio into a blog post other than manually modifying the HTML. I installed CopySourceAsHtml for this exact purpose and it works great! (very quick to setup). A couple of things to note though:
  • I do not believe this add-in works with Visual Studio Express editions.
  • The current version of CopySourceAsHtml does not appear to be available for VS2010 but it was very simple to modify the add-in file after installing.
  • To use the add-in just select the source code then select Copy As HTML... from the Edit pulldown.
  • I pasted my code into Word then just did another select all and pasted that into the blogger editor (for some reason going directly from VS to Blogger I lost the coloring).
So here is my first result of using this add-in, a simple program to add some geometry into AutoCAD (line).



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

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


namespace SLD_Demo
{
    public class SLD_DrawTools
    {
        [CommandMethod("SLDdrawLine")]
        public void SLDdrawLine()
        {
            //initiate drawing objects
            /*In order to work with AutoCAD from .Net we do that through
            the object hierarchy that AutoCAD is built from. The
            Application is the root object. We then dig into that to get
            to the current document. We will create a new Document object
            called acDoc. From there we go one level further to access
            the AutoCAD database for the current document.
            */
            Document acDoc = Application.DocumentManager.MdiActiveDocument;
            Database acCurDb = acDoc.Database;

            //start db transaction
            /*reading and creating entities within AutoCAD requires a
            transaction with the AutoCAD document database (objects
            defined above)*/
            using (Transaction
                acTrans = acCurDb.TransactionManager.StartTransaction())
            {
                //define line
                /*when a new line object is created there are a few
                constructor methods that can be used. In this example we
                will define 2 new 3D point objects and then pass those
                to the new line object as paramaters*/
                Point3d startPoint = new Point3d(0,0,0);
                Point3d endPoint = new Point3d(0,5,0);
                Line sldLine = new Line(startPoint, endPoint);

                //open block table and create new record
                /*Note, at this point our new line is only defined within
                our program, it is not in the AutoCAD drawing yet. In order
                to add the defined line to the drawing we need to create
                a new record in the AutoCAD document database.*/
                BlockTable acBlkTbl;
                acBlkTbl = acTrans.GetObject(
                    acCurDb.BlockTableId, OpenMode.ForRead) as BlockTable;

                BlockTableRecord acBlkTblRec;
                acBlkTblRec = acTrans.GetObject(
                    acBlkTbl[BlockTableRecord.ModelSpace],
                    OpenMode.ForWrite)
                    as BlockTableRecord;

                //add line to drawing
                /*now that we have created a new block table record we
                can add our new line to it.*/
                acBlkTblRec.AppendEntity(sldLine);
                acTrans.AddNewlyCreatedDBObject(sldLine, true);

                //end the transaction with the AutoCAD database
                /*This is a very important step. When we begin a
                transaction with the AutoCAD database it has to be
                terminated (commited). Otherwise future AutoCAD
                commands will not function and the drawing will likely
                crash*/
                acTrans.Commit();
            }
        }
    }
}


Saturday, November 26, 2011

Updating a Network User Profile

Maintaining network based AutoCAD user profiles is a simple, organized way of delivering any content you need your users to have. Once the main architecture is in place you can make updates to a profile and all users will instantly get the update...or will they?
If we have an AutoCAD profile on a network location that everyone is pointing to (maybe from an AutoCAD desktop shortcut) the first time the profile is loaded it will define the profile name on the computer's registry under the AutoCAD Profiles path. This then ensures that all users have the same settings that are defined in the profile.
The way this behaves after the profile has been loaded in the registry is when the icon is launched again AutoCAD checks to see if the profile being pointed to in the desktop shortcut is already defined on that computer. If so, AutoCAD does not re-define the profile it just makes sure to set that profile current. The downside to this is if you make a change to the profile (for example add a support path) this does not automatically get deployed to machines that already have the profile defined.
There are two ways to make sure everyone gets the update:
1. Delete the profile from the users' local machines then when the desktop icon that points to the network profile is launched, the profile is re-defined with the most recent updates.
2. Change the actual profile name. This will then tell AutoCAD that this is a new profile that is not defined on any one's machine yet. This method works without having to update the custom AutoCAD desktop icon because changing the profile name happens within the .arg file - the filename of the .arg file is irrelevant.
Let's walk through this from the beginning to make sense of this:


I have created a network based AutoCAD user profile that I want to load for all users. My new profile only has an additional support path and an enterprise menu file definition.







My choice of deployment was to create a new AutoCAD desktop icon that points to the custom profile (.arg file). Note: this location typically would reside on a shared network drive.







Once this icon is on each desktop (maybe automated via SharePoint) we are set to manage our custom network environment.


Note, at this point I have copied the desktop icon to my desktop but the AutoCAD profile <<SLD_2011>> does not exist on my machine yet.






When I launch the icon for the first time the AutoCAD Profile is defined in my local computer registry






Here is how the Options dialog looks in AutoCAD with the custom profile now loaded on my computer.






So now, we are to the real topic of this post where we want to make a modification to our shared network user profile. Let's say we want to add an additional support path (Support2).




This is great for anyone that has not yet loaded this profile but what about for machines that already have the profile defined? When these users launch AutoCAD into the custom profile their profile on the local machine does not get re-defined so therefore they do not get the additional support path. The key here is the profile name. AutoCAD checks if the profile is already defined within the registry and if so the profile is not updated. But if we change the actual profile name after making the change then AutoCAD will recognize this as a brand new profile and we can be assured that the change will be deployed to all users because they will actually be creating a new AutoCAD user profile.


To do this we do not need to change the filename of our .arg which also prevents the need for changing or modifying the desktop icon that is already on user's desktops. The change is made only within the .arg file itself.






Now, even though I have the original profile already defined on my computer when I launch the custom AutoCAD shortcut again the new profile is created (and made current) and I now have the additional support path that was added.






You might notice I updated my profile name with a new version. This might be a strategy you want to adopt from the beginning. If so, you can include a version indicator in your original network profile.


This provides a streamlined method of managing and updating a network based AutoCAD profile. The power of this method is everything is controlled from a single network location and the local workstations do not need to be touched in order to deploy updates to AutoCAD user profiles.