Get List of all Object ID or Entities added between single transaction

Get List of all Object ID or Entities added between single transaction

vkpunique
Advocate Advocate
1,540 Views
16 Replies
Message 1 of 17

Get List of all Object ID or Entities added between single transaction

vkpunique
Advocate
Advocate

I need list of all new entities added to drawings so i can perform additional operations on them. 

 

Currently i have to create List<Entities> and have to add each entitiy manually. This is not problem for smaller drawings

but my drawing have hundreds of objects scattered throughout multiple files/class.

0 Likes
Accepted solutions (1)
1,541 Views
16 Replies
Replies (16)
Message 2 of 17

kerry_w_brown
Advisor
Advisor

My initial thought would be to "

Initially save a listing of 'start' entity handles to a file.
When ready to process the revised drawing ;
    iterate the database and make another list of handles that are in the current, but not in the start list.
Then proceed to process the 'new' entities as necessary.

You may (or not) need to consider new layers etc that were created.

 

Regards,

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
0 Likes
Message 3 of 17

ActivistInvestor
Mentor
Mentor

@vkpunique wrote:

I need list of all new entities added to drawings so i can perform additional operations on them. 

New entities added to drawings since when? Since the last time they were opened? Since the point when they were opened (and are currently open)?  Since the last command used was started?..........

 

Your problem description is too vague.

 

Message 4 of 17

_gile
Consultant
Consultant

As suggested by @kerry_w_brown , you can use the Database.Handseed property to find all the newly created entities from the start of a Transaction.

 

// instantiate a new collection for newly created entities
var newEntitiesIds = new ObjectIdCollection();
// Initialize the first available Handle
long handseed = db.Handseed.Value;
using (var tr = db.TransactionManager.StartTransaction())
{

    // Create some entities
    // ...
}

// Get the created entities
var entityClass = RXObject.GetClass(typeof(Entity));
for (long i = handseed; i < db.Handseed.Value; i++)
{
    if (db.TryGetObjectId(new Handle(i), out ObjectId id)
        && id.ObjectClass.IsDerivedFrom(entityClass))
        newEntitiesIds.Add(id);
}

 

 

But it will be much more efficient to simply add these entites (or the ObjectIds) to a list while creating them.

Here's an example with a local function.

 

// instantiate a new collection for newly created entities
var newEntitiesIds = new ObjectIdCollection();
using (var tr = db.TransactionManager.StartTransaction())
{
    var modelSpace = (BlockTableRecord)tr.GetObject(
        SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite);

    // local function to add entites to the model space and the newEntitiesIds collection
    void Add(Entity ent)
    {
        newEntitiesIds.Add(modelSpace.AppendEntity(ent));
        tr.AddNewlyCreatedDBObject(ent, true);
    }

    // Create some entities
    var entity = new Circle(Point3d.Origin, Vector3d.ZAxis, 10.0);
    Add(entity);
    // ...

    tr.Commit();
}

 

 

 

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 17

kerry_w_brown
Advisor
Advisor

@vkpunique 

@_gile 

@ActivistInvestor 

 

Yes, interpretation is important.
For some reason I may have misunderstood the time lapse for adding the additional entities.
I should have paid more attention to the post title  " . . . between single transaction"
now I assume that should have read plural transactions and am wondering if the transactions are in the same assembly . . . or even in the same class.  weird, hey!

 

added:

on fourth thought, perhaps it means "during a single transaction"

 

I should turn up my "vague identifier" I think.

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
0 Likes
Message 6 of 17

ActivistInvestor
Mentor
Mentor

@kerry_w_brown wrote:

@vkpunique 

@_gile 

@ActivistInvestor 

 

Yes, interpretation is important.
For some reason I may have misunderstood the time lapse for adding the additional entities.
I should have paid more attention to the post title  " . . . between single transaction"
now I assume that should have read plural transactions and am wondering if the transactions are in the same assembly . . . or even in the same class.  weird, hey!

 

added:

on fourth thought, perhaps it means "during a single transaction"

 

I should turn up my "vague identifier" I think.

 


I also didn't read the title, which partially-answers the question. The part not answered is whether the OP's code is creating the objects directly, or he is calling commands or some other way of indirectly creating new objects.

 

But, a constant point of frustration for me, is that I routinely see others hand the OP a fish, rather than endeavoring to help them learn how to catch fish. This thread may be another case in point.

 

Quite a bit of code I've written over the years routinely operates on newly-created objects (created by executing commands, or via some other means), and I don't have to (or can't) build a list of new objects, and don't have to resort to incrementing handles to find them.

 

I just use an ObjectOverrule and override Close(); check to see if the object is a new object; and if so, act on it right then and there, while it's open for write and about to be closed. End of story. No need to build a list of objects...No need to open those objects after-the-fact, because the operation was already done in the overrule's Close() override.

 

When I see a question like this one posted, the first thing I do is look beyond the question, to understand if what the OP thinks they need to do, is really what they need to do, and in many cases, it is not. They may believe it is, only because they're unaware of other/better ways of solving a problem, using things like Overrules, or don't understand how they can be leveraged to solve problems.

 

So, if the OP does respond with a clarification, I might offer some advice on how to best solve their problem, but otherwise, this is a guessing-game.

Message 7 of 17

_gile
Consultant
Consultant

@ActivistInvestor  a écrit :

But, a constant point of frustration for me, is that I routinely see others hand the OP a fish, rather than endeavoring to help them learn how to catch fish. This thread may be another case in point.


I'm sorry, I'd also rather try to teach others to fish than give them a fish, but English isn't my first language and it's often easier for me to give a code snippet.

 

I'd be curious to see how you do it with an ObjectOverrule.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 8 of 17

ActivistInvestor
Mentor
Mentor

Hi @_gile. I've posted several times here, and on theswamp.org, code or links to code showing how overrules can be used to solve similar problems (operate on objects, both new and existing), rather than having collect and open their IDs later.

 

I wouldn't offer a code snippet showing the OP how to do what they claim they need to do, if I think it may not be the best way to solve their underlying problem, before first asking them to explain why they think they need to solve the problem that way.

 

Many times, a user might ask how to solve a problem in a certain way, only because they are not aware of other, better ways to solve it. So for me, the correct approach is not to answer with a code snippet, but instead ask the OP why they want to do what they're asking.

 

If in this case, the OP wants to clarify or provide more explanation into why they want to get the Ids of newly-created objects, I would most-likely suggest a different approach that involves the use of Overrules, as I've done several times in the past.

 

I've also made solutions that help simplify the use of ObjectOverrule for similar purposes, like this one.

 

I've found that Chrome is good at translating with a right-click on the content to be translated, if that helps.

0 Likes
Message 9 of 17

ActivistInvestor
Mentor
Mentor

I'd be curious to see how you do it with an ObjectOverrule.


This is a generic solution that doesn't account for specific conditions. It only makes the task of using an ObjectOverrule a bit easier by encapsulating the code logic needed to distinguish new objects from existing objects.

 

This code doesn't do what I've suggested may be possible, depending on what the OP needs to do with respect to 'additional operations', For example, if the additional operations can be performed directly from the Overrule's Close() override, then there is no purpose to collecting ObjectIds in the first place.

 

 

/// <summary>
/// A simplified version of the NewObjectCollection class
/// from AcMgdLib, that can be used to collect the ObjectIds 
/// of all newly-created objects of a specified type or 
/// derived type. This code has no dependence on AcMgdLib.
/// 
/// You typically create an instance of NewObjectIdCollection
/// when you want to begin collecting the Ids of newly-created
/// objects.
/// 
/// You can set the Enabled property to true or false to enable
/// or disable collection of the Ids of new objects. The elements
/// contained in the instance can be accessed as a collection and 
/// used with foreach(), or with Linq methods.
/// 
/// When finished using a NewObjectIdCollection, its Dispose()
/// method must be called.
///
/// For collecting the ObjectIds of all types of entities, use
/// NewObjectIdCollection<Entity>. You can specify any type as
/// the generic argument to restrict the collected ids to only
/// ids of the specified generic argument.
///
/// </summary>
/// <typeparam name="T">The type of DBObject whose ids are to be
/// collected.</typeparam>

public class NewObjectIdCollection<T> : ObjectOverrule, IReadOnlyCollection<ObjectId> 
   where T: DBObject
{
   static RXClass rxclass = RXObject.GetClass(typeof(T));
   HashSet<ObjectId> ids = new HashSet<ObjectId>();
   private bool enabled;

   public NewObjectIdCollection(bool enabled = true)
   {
      this.Enabled = enabled;
   }

   public override void Close(DBObject obj)
   {
      if(obj is T && obj.IsNewObject && obj.IsReallyClosing)
         ids.Add(obj.ObjectId);
   }

   public bool Enabled
   {
      get => enabled;
      set
      {
         if(value ^ enabled)
         {
            enabled = value;
            if(value)
               AddOverrule(rxclass, this, true);
            else
               RemoveOverrule(rxclass, this);
         }
      }
   }

   public void Clear() => ids.Clear();

   public int Count => ids.Count;

   protected override void Dispose(bool disposing)
   {
      if(disposing)
         Enabled = false;
      base.Dispose(disposing);
   }

   public IEnumerator<ObjectId> GetEnumerator()
   {
      return ids.GetEnumerator();
   }

   IEnumerator IEnumerable.GetEnumerator()
   {
      return this.GetEnumerator();
   }
}

 

/// <summary>
/// Example use of NewObjectIdCollection:
/// </summary>

public static class NewObjectIdCollectionExample
{
   /// Explodes a selected entity and 
   /// selects the resulting objects:

   [CommandMethod("EXPLODESEL")]
   public static void ExplodeAndSelect()
   {
      Document doc = Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      PromptEntityOptions peo = new PromptEntityOptions("\nSelect object: ");
      var per = ed.GetEntity(peo);
      if(per.Status != PromptStatus.OK)
         return;
      using(var ids = new NewObjectIdCollection<Entity>())
      {
         ed.Command("._EXPLODE", per.ObjectId, "");
         if(ids.Count > 0)
            ed.SetImpliedSelection(ids.ToArray());
      }
   }
}

 

0 Likes
Message 10 of 17

ActivistInvestor
Mentor
Mentor

@_gile wrote:

I'd be curious to see how you do it with an ObjectOverrule.


In French, courtesy of ChatGPT:

 

Ceci est une solution générique qui ne tient pas compte des conditions spécifiques. Elle rend simplement la tâche d'utiliser un ObjectOverrule un peu plus facile en encapsulant la logique de code nécessaire pour distinguer les nouveaux objets des objets existants.

 

Ce code ne fait pas ce que j'ai suggéré comme étant possible, en fonction de ce que l'OP a besoin de faire concernant les 'opérations supplémentaires'. Par exemple, si les opérations supplémentaires peuvent être effectuées directement depuis la surcharge de Close() de l'Overrule, il n'est alors pas nécessaire de collecter les ObjectIds.

 

 

/// <summary>
/// Une version simplifiée de la classe NewObjectCollection
/// provenant d'AcMgdLib, qui peut être utilisée pour 
/// collecter les ObjectIds de tous les objets nouvellement 
/// créés d'un type spécifié ou d'un type dérivé. Ce code 
/// n'a aucune dépendance envers AcMgdLib.
/// 
/// Vous créez généralement une instance de NewObjectIdCollection
/// lorsque vous souhaitez commencer à collecter les Ids des objets
/// nouvellement créés.
/// 
/// Vous pouvez définir la propriété Enabled sur true ou false
/// pour activer ou désactiver la collecte des Ids des nouveaux objets.
/// Les éléments contenus dans l'instance peuvent être accédés comme
/// une collection et utilisés avec foreach(), ou avec des méthodes Linq.
/// 
/// Lorsque vous avez fini d'utiliser un NewObjectIdCollection, 
/// sa méthode Dispose() doit être appelée.
/// 
/// Pour collecter les ObjectIds de tous types d'entités, utilisez
/// NewObjectIdCollection<Entity>. Vous pouvez spécifier n'importe quel
/// type comme argument générique pour restreindre la collecte des ids 
/// uniquement aux ids de l'argument générique spécifié.
/// </summary>
/// <typeparam name="T">Le type de DBObject dont les ids doivent être
/// collectés.</typeparam>

public class NewObjectIdCollection<T> : ObjectOverrule, IReadOnlyCollection<ObjectId> 
   where T: DBObject
{
   static RXClass rxclass = RXObject.GetClass(typeof(T));
   HashSet<ObjectId> ids = new HashSet<ObjectId>();
   private bool enabled;

   public NewObjectIdCollection(bool enabled = true)
   {
      this.Enabled = enabled;
   }

   public override void Close(DBObject obj)
   {
      if(obj is T && obj.IsNewObject && obj.IsReallyClosing)
         ids.Add(obj.ObjectId);
   }

   public bool Enabled
   {
      get => enabled;
      set
      {
         if(value ^ enabled)
         {
            enabled = value;
            if(value)
               AddOverrule(rxclass, this, true);
            else
               RemoveOverrule(rxclass, this);
         }
      }
   }

   public void Clear() => ids.Clear();

   public int Count => ids.Count;

   protected override void Dispose(bool disposing)
   {
      if(disposing)
         Enabled = false;
      base.Dispose(disposing);
   }

   public IEnumerator<ObjectId> GetEnumerator()
   {
      return ids.GetEnumerator();
   }

   IEnumerator IEnumerable.GetEnumerator()
   {
      return this.GetEnumerator();
   }
}

 

 

0 Likes
Message 11 of 17

vkpunique
Advocate
Advocate

@ActivistInvestor @kerry_w_brown 

Just adding bit more context for my problem

I am writing autocad code to generate few drawings for my client from data. Currently i am just generating drawing at user selected points but I want to use Jig to place generated drawing to avoid overlapping with existing drawing. 
I can use list<Entity> to keep track of newly added entity but it's not feasible for my project, it's large project with drawing code is distributed over hundreds of file. project code size is already close to 30K. 
So i was looking better way to do this without disturbing current code base. 

As per gile answer database Handseed Property should sholve my issue. I'll give it try and will update this post.

 

0 Likes
Message 12 of 17

ActivistInvestor
Mentor
Mentor

@vkpunique wrote:

@ActivistInvestor @kerry_w_brown 

Just adding bit more context for my problem

I am writing autocad code to generate few drawings for my client from data. Currently i am just generating drawing at user selected points but I want to use Jig to place generated drawing to avoid overlapping with existing drawing. 
I can use list<Entity> to keep track of newly added entity but it's not feasible for my project, it's large project with drawing code is distributed over hundreds of file. project code size is already close to 30K. 
So i was looking better way to do this without disturbing current code base. 

As per gile answer database Hook Property should sholve my issue. I'll give it try and will update this post.

 


Not sure what 'database Hook Property' you're referring to.

 

You can't use List<Entity> to keep track of anything. The elements in the list are only usable while the Transaction they were created in is active. Once the transaction ends, the entities in the list are no longer usable, so I don't understand your comment 'I can use list<Entity> to keep track of newly added entity', because you can't keep track of entities that way.

 

You have to use ObjectIds to keep track of entities for the reason I mentioned above, but since you really haven't described your use case (e.g., what do you intend to do with the tracked entities), I can't offer much advice in that regards.

 

 

 

0 Likes
Message 13 of 17

vkpunique
Advocate
Advocate
sorry Handseed property.
Also I am only interested in keeping track of entity in single transaction, I don't want to use it after that transaction is over.
If i ever need to reuse those entity after trasaction, i'll use object ID or Entity Handles.
0 Likes
Message 14 of 17

kerry_w_brown
Advisor
Advisor

 

 

@vkpunique wrote:
>>>
Also I am only interested in keeping track of entity in single transaction, I don't want to use it after that transaction is over. <<<

 

Why didn't you take an extra minute when you first asked your question to provide that information.

Do you realise how much time an effort (by others) you've wasted by not asking your question properly and by forcing those trying to assist you to try to drag out the information needed to resolve the issue.

Unfortunately this situation is happening more and more frequently in the forums  

. . . personally it dissapoints me enormously that some people don't make the effort to ask questions properly or don't seem to understand how important providing all the relevant information.

Sometimes it's like playing a bloody guessing game.

 

 


// Called Kerry or kdub in my other life.

Everything will work just as you expect it to, unless your expectations are incorrect. ~ kdub
Sometimes the question is more important than the answer. ~ kdub

NZST UTC+12 : class keyThumper<T> : Lazy<T>;      another  Swamper
0 Likes
Message 15 of 17

ActivistInvestor
Mentor
Mentor

@vkpunique wrote:
sorry Handseed property.
Also I am only interested in keeping track of entity in single transaction, I don't want to use it after that transaction is over.
If i ever need to reuse those entity after trasaction, i'll use object ID or Entity Handles.

Now I'm even more confused. 

 

I give up.

 

0 Likes
Message 16 of 17

ActivistInvestor
Mentor
Mentor
Accepted solution

Also I am only interested in keeping track of entity in single transaction, I don't want to use it after that transaction is over.

How much back-and-forth was needed to finally get you to divulge this one crucial detail, which changes everything.?

 

You really don't put much thought or effort into your questions, which is not good. Even after being pressed for more details, we still didn't know or understand what it was you needed (and I think the other parties to the thread will agree). And that's why I probably won't bother answering any more vague, wishy-washy questions that amount to a nothing but a guessing-game.

 

So, now that I've finally squeezed the blood from the rock, I can give you an answer:

 

Assuming that You want to get all newly-created entities that were all created within a single transaction, and you want to get and use them while that transaction is still active, there's no need for the handle kludge, or the code I posted (which doesn't work for your use case because the objects are not closed until the transaction is committed).

 

See the Transaction.GetAllObjects() method.  

 

In order to use this method to get only newly-created objects, you should open any existing objects first in the transaction using GetObject(), and then start another nested transaction to which you only add newly-created objects. If you do that, the nested transaction will contain only the newly-created objects, and that's what calling its GetAllObjects() method will return.

 

In the use case you're describing, using the incrementing handle kludge is like driving nails with a sledge hammer, because you must re-open the ObjectIds again to access the Entities. GetAllObjects() returns the already-open entities.

 

To put it another way, think of a Transaction as being the functional-equivalent of a List<Entity>, and its GetAllObjects() method how you access that list.

Message 17 of 17

vkpunique
Advocate
Advocate
Thank you. This is perfect for my needs.
Again, i am really sorry for not explaining what i want to do in detail.
0 Likes