Author Topic: Does the .NET API have an equivalent to ActiveX AcadEntity class?  (Read 366 times)

0 Members and 1 Guest are viewing this topic.

pjm8765

  • Newt
  • Posts: 46
I'm writing what I hope will be one routine to remove a circle/line/polyline etc from model space.  These objects are temporary objects my commands use for various reasons, which I need to get rid of when I'm cleaning up at the end of each command.  Rather than have a separate routine for each class of object or iterating through the entities in model space, I am hoping that this rather useful class that ActiveX provides has an equivalent.

If not I'll simply carry on writing separate routines.

gile

  • Water Moccasin
  • Posts: 2221
  • Marseille, France
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #1 on: May 03, 2019, 06:41:31 AM »
Hi,

You can use inheritance.
Circle, Line, Polyline classes are derived from Curve, which is drived from Entity, which is derived from DBObject which provide the Erase() method.
That means a DBObject instance, as any instance of the derived classes, can call the Erase() method.
Speaking English as a French Frog

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #2 on: May 03, 2019, 08:14:37 AM »
I've tried that and it results in an access violation.  Code :

Code: [Select]
                easiBaseOutline = Utility.AddCircleToDrawing(insertPoint, 600, LayerNames.TemporaryEntity);

                //Ask for the output pipe
                outputPipe = GetOutputPipe();

                if (outputPipe == null)
                {
                    //ESC pressed, so stop the command
                    return;
                }

                //Check that the outlet pipe intersects with the EasiBase outline

                //Ask for the inlet pipes

                //Open the EasiBase form

            }
            catch (Exception ex)
            {
                MessageBox.Show("EasiBase.Create" + ex.Message, MessageHeadings.ExceptionError);
            }
            finally
            {
                if (easiBaseOutline != null)
                {
                    easiBaseOutline.Erase();
                }
            }

The circle to be deleted is created by :

Code: [Select]
        public static Circle AddCircleToDrawing(Point3d InsertPoint, double Radius, string LayerName)
        {
            Document thisDrawing;
            Circle newCircle = null;
            BlockTable blockTable;
            BlockTableRecord blockTableRecord;

            try
            {

                thisDrawing = GetDocument();

                if (thisDrawing == null)
                {
                    return newCircle;
                }

                // Start a transaction
                using (Transaction autoCADTransaction = thisDrawing.Database.TransactionManager.StartTransaction())
                {
                    // Open the Block table for read
                    blockTable = autoCADTransaction.GetObject(thisDrawing.Database.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // Open the Block table record Model space for write
                    blockTableRecord = autoCADTransaction.GetObject(blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    using (newCircle = new Circle())
                    {
                        // Create the circle entity
                        newCircle.Center = InsertPoint;
                        newCircle.Radius = Radius;
                        newCircle.Layer = LayerName;
                        newCircle.Visible = true;

                        // Add the new object to the block table record and the transaction
                        blockTableRecord.AppendEntity(newCircle);

                        autoCADTransaction.AddNewlyCreatedDBObject(newCircle, true);
                    }

                    // Save the new object to the database
                    autoCADTransaction.Commit();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Utility.AddCircleToDrawing" + ex.Message, MessageHeadings.ExceptionError);
            }

            return newCircle;
        }

Which is a copy of a pretty vanilla routine from the Autodesk help.

I had assumed that deleting this object would require a database transaction, as the create routine does.

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #3 on: May 03, 2019, 08:24:10 AM »
To be sure all of the examples of deleting objects I find either involve using the ObjectId to find the object in the model, which I assume is slow. ....

Or, like this (http://docs.autodesk.com/ACD/2011/ENU/filesMDG/WS1a9193826455f5ff2566ffd511ff6f8c7ca-3eef.htm) involve creating the object and then deleting it in a single transaction....which is no good to me in the long run.

I could create one single big fat transaction that covers the entire command, but that would involve some painful double checking as and when users exit the command and is simply bad programming all round.

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #4 on: May 03, 2019, 08:51:22 AM »
I thought I'd go for the ObjectId route using this:

Code: [Select]
        public void Erase(ObjectId id)
        {
            if (id.Database.TransactionManager.TopTransaction != null)
                id.GetObject(OpenMode.ForWrite).Erase();
            else
#pragma warning disable CS0618
                using (var obj = id.Open(OpenMode.ForWrite))
                    obj.Erase();
#pragma warning restore CS0618
        }

Anyway, it turns out that the ObjectId and most of the attributes of my circle are generating an access violation.  So something is going wrong with my create routine passing back a reference to the newly created circle entity.

This is pretty basic programming practice i.e. create an object in a method and return for use and abuse.  I've noticed a lot of the Autodesk help examples create thingy's and then return void.

Please don't tell me I've got to create an entity and then go and find it again...or am I back to the idea of having one big fat transaction?

What is happening here?


gile

  • Water Moccasin
  • Posts: 2221
  • Marseille, France
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #5 on: May 03, 2019, 09:39:39 AM »
You cannot use a DBObject instance outside of the transaction used to open it or which it has been added to.
So, you can use the ObjectId of the DBObject from one transaction to another one but typically you should start a transaction in the main method and pass it as argument to the called method (or get the top transaction from these methods).

Anyway, most of the time, you can avoid adding a temporary Entity to the database.
For geometical purposes you should use the non database resident objects from the Geometry namespace (LineSegment2d/3d, CircularArc2d/3d, ...).

For entities which are only use to create other entities, you can simply Dispose() of the entity instead of adding it and erase it.
Here's an example which creates a cylinder by extruding a circle. it needs to create a Circle and a Region with this circle to extrude Region. Neither the Circle nor the Region are added to the Database, both are disposed after the region is created (with a using statement).
Code - C#: [Select]
  1.         static ObjectId CreateCylinder(Database db, Point3d center, Vector3d normal, double radius, double height)
  2.         {
  3.             ObjectId id;
  4.             using (var tr = db.TransactionManager.StartTransaction())
  5.             {
  6.                 using (var circle = new Circle(center, normal, radius))
  7.                 {
  8.                     var curves = new DBObjectCollection();
  9.                     curves.Add(circle);
  10.                     var regions = Region.CreateFromCurves(curves);
  11.                     using (var region = (Region)regions[0])
  12.                     {
  13.                         var cylinder = new Solid3d();
  14.                         cylinder.Extrude(region, height, 0.0);
  15.                         var space = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  16.                         id = space.AppendEntity(cylinder);
  17.                         tr.AddNewlyCreatedDBObject(cylinder, true);
  18.                     } // <- disposing the region
  19.                 } // <- disposing the circle
  20.                 tr.Commit();
  21.             }
  22.             return id;
  23.         }
Speaking English as a French Frog

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #6 on: May 03, 2019, 10:28:06 AM »
Thanks Gile.  Really?  I can't say I'm surprised...I've seen more abysmal programming/design whilst working with design tools (Tekla as well as AutoCAD) in the last two years than in my entire previous career put together.  Not yours I might add, but from the programmers I have inherited work from (anyone for a 10K+ line procedure?).  Now I've got a new build project I wanted to not have to compromise, have a decent OOP design and to not have to go anywhere near COM.  This kind of puts a dent in that. 

But I can work with these ObjectId objects.  I'm not working with one big fat transaction if I can possibly avoid it...above all else I want readability (encapsulation) and want to avoid passing parameters around like sweeties at a kids party. 

In this case I need a visible set of objects, used during the command, mainly for selection purposes.  But, I get your point about being able to use these classes to get the job done without mucking about with the database.

I shall plough on until I hit the next brick wall.

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #7 on: May 03, 2019, 11:07:38 AM »
And it gets worse.  This ObjectId class is non nullable, so I can't set a default value to return.  So I now have to lose my error handler in my business class or the code won't compile.

I'm beginning to wonder if I'd be better off using the ActiveX API after all (with it's use of the detestable Object class and even worse, arrays).  It doesn't seem to matter which way I turn, working with Autodesk products involves some sort of fudge.

Atook

  • Swamp Rat
  • Posts: 932
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #8 on: May 03, 2019, 12:04:44 PM »
For ObjectID you can return ObjectID.Null

I've got an Erase function that looks like this:
Code - C#: [Select]
  1. /// <summary>
  2. /// Erase the object  from the current document
  3. /// </summary>
  4. /// <param name="id">ObjectID of the object to be erased</param>
  5. public static bool Erase(ObjectId id)
  6. {
  7.   if (id == ObjectId.Null) return false;
  8.   using (Active.Document.LockDocument())
  9.   {
  10.     using (Transaction acTr = Active.Document.TransactionManager.StartTransaction())
  11.     {
  12.       DBObject obj = acTr.GetObject(id, OpenMode.ForWrite, true);
  13.       if (obj != null)
  14.       {
  15.         obj.Erase(true);
  16.         acTr.Commit();
  17.         return true;
  18.       }
  19.       acTr.Commit();
  20.       return false;
  21.     }
  22.   }
  23. }
You could return the objectID from your database elements whenever you create one, maybe something like:
Code - C#: [Select]
  1. public ObjectID MakeCircle (pt center, double radius)
  2. {
  3.   ObjectID result = ObjectID.Null;
  4.   ...
  5.   result = curSpace.AppendEntity(br);
  6.   ...
  7.   return result;
  8. }

Also, to reduce transactions, you could pass the Erase function an ObjectIDCollection, and use one transaction to erase the whole collection (via iteration)

Now you've got something like this:
Code - C#: [Select]
  1. ObjectIdCollection tempObjects = new ObjectIdCollection();
  2. ...// Do your stuff, make your temp objects
  3. tempObjects.Add(MakeCircle(centerPt, 4));
  4. ...// Do more stuff
  5. //Erase them all at once
  6. EraseObjects(tempObjects);

As Gile said, better not to actually add to the database (maybe you can show your temp objects with a jig?), but this gets you started.
« Last Edit: May 03, 2019, 12:13:19 PM by Atook »

gile

  • Water Moccasin
  • Posts: 2221
  • Marseille, France
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #9 on: May 03, 2019, 12:10:13 PM »
And it gets worse.  This ObjectId class is non nullable, so I can't set a default value to return.
ObjectId has a Null static property and a IsNull instance property.

But I can work with these ObjectId objects.  I'm not working with one big fat transaction if I can possibly avoid it...above all else I want readability (encapsulation) and want to avoid passing parameters around like sweeties at a kids party. 
Using the top transaction allows you to work with DBObjects instead of ObjectId and passing the transaction to the called method is not mandatory.
By my side, I like to use Extension methods called from within the top transaction, here's a little example to replace your 'AddCircleToDrawing' method.

Code - C#: [Select]
  1.     static class Extension
  2.     {
  3.         public static T GetObject<T>(
  4.             this ObjectId id,
  5.             OpenMode mode = OpenMode.ForRead,
  6.             bool openErased = false,
  7.             bool forceOpenOnLockedLayer = false) where T : DBObject
  8.         {
  9.             if (id.IsNull) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NullObjectId);
  10.             Transaction tr = id.Database.TransactionManager.TopTransaction;
  11.             if (tr == null) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);
  12.             return (T)tr.GetObject(id, mode, openErased, forceOpenOnLockedLayer);
  13.         }
  14.  
  15.         public static Circle AddCircle(this BlockTableRecord owner, Point3d center, double radius, string layerName)
  16.         {
  17.             if (owner == null) throw new ArgumentNullException("owner");
  18.             var layerTable = owner.Database.LayerTableId.GetObject<LayerTable>();
  19.             if (!layerTable.Has(layerName)) throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.BadLayerName);
  20.             var tr = owner.Database.TransactionManager.TopTransaction;
  21.             var circle = new Circle() { Center = center, Radius = radius, Layer = layerName };
  22.             owner.AppendEntity(circle);
  23.             tr.AddNewlyCreatedDBObject(circle, true);
  24.             return circle;
  25.         }
  26.     }

In the main method
Code - C#: [Select]
  1.             using (var tr = db.TransactionManager.StartTransaction())
  2.             {
  3.                 var space = db.CurrentSpaceId.GetObject<BlockTableRecord>(OpenMode.ForWrite);
  4.                 var easiBaseOutline = space.AddCircle(insertPoint, 600, LayerNames.TemporaryEntity);
  5.                 // do your stuff with 'easiBaseOutline'
  6.                 tr.Commit();
  7.             }

Search this forum for GetObjects, you'll find more dicussions about such extension methods.
Speaking English as a French Frog

Atook

  • Swamp Rat
  • Posts: 932
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #10 on: May 03, 2019, 12:17:07 PM »
Quote
I like to use Extension methods called from within the top transaction, here's a little example...

Gile, thanks so much for your contributions, there's a gem in almost all of them!

pjm8765

  • Newt
  • Posts: 46
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #11 on: May 07, 2019, 03:34:01 AM »
Good morning Giles, Atook,

Thank you very much both.  These are very useful snippets of code and hopefully will keep me going for a few days.  I expect the next two weeks will see a few more "learning curve" questions like this.

Kind regards, Paul

MickD

  • Gator
  • Posts: 3286
  • (x-in)->[process]->(y-out)
Re: Does the .NET API have an equivalent to ActiveX AcadEntity class?
« Reply #12 on: May 07, 2019, 07:14:37 PM »
Hi Paul, this may or may not be relevant but the thing with the Acad db is it has to be 'free' before you can work on it. That is, you can't erase an object unless it has been 'committed' to the db proper therefore your transaction must be finalised to make the ObjectID available to erase the entity.

The reason for this lies in how Acad handles graphic redraws and other tasks. When you pan or zoom say, the whole db is queried for the objects within the view frustum and they are redrawn every redraw call message.
If the last transaction hasn't been committed then you could have what could amount to a 'race' condition where more than one thread is trying to read the db, same goes for erasing objects, they don't 'properly' exist yet so they cause an issue. Disposing of the entities might be a solution in this case but you would still need some pointer to the objects (DBObjectCollection??).

I would store the ObjectID's in a collection to be used after the transaction to create the temp objects has been committed then use another transaction to do the erasing work.
Using a stored list of ObjectID's for searching a drawing is as quick as it gets, remember you are searching an in memory database proper, not the modelspace view per se. Iterating the db then checking for the right one is definitely slow(er).

Transactions aren't that expensive in the scheme of things and they're more like a 'lock' wrapper object to ensure your method has full access rights to the db and no one else.
Wrapping the above 2 transactions _may_ cause the same issue even though you use two inner transactions, the main outermost one still has control of the db so you'd have to test I guess.

Hope that is of some help, I apologise if any of this is obvious but it may help others understand what can go wrong and why, cheers.
Debugging:
Being the detective in a crime movie where you're also the murderer.

“Someone's sitting in the shade today because someone planted a tree a long time ago.”
- Warren Buffet