TheSwamp

Code Red => .NET => Topic started by: David Hall on January 03, 2008, 01:30:00 PM

Title: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 01:30:00 PM
What Im looking for is suggestions on how to code this better, or am I doing pretty well.  I went back to some really old code from when C# first started working with acad, and tried to improve what I did back then.  Anyway, comments are welcome

Code: [Select]
namespace LayerCode
{
    public class LayerCode
    {
        [CommandMethod("CL1")]
        public void CreateLayer1()
        {
            Database DB = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database;
            using (Transaction tr = DB.TransactionManager.StartTransaction())
            {
                LayerTable LT = (LayerTable)DB.LayerTableId.GetObject(Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);
                if (LT.Has("DSH"))
                {
                    tr.Abort();
                }
                else
                {
                    LayerTableRecord LTR = new Autodesk.AutoCAD.DatabaseServices.LayerTableRecord();
                    LTR.Name = "DSH";
                    LT.Add(LTR);
                    tr.AddNewlyCreatedDBObject(LTR, true);
                    tr.Commit();
                }
            }
        }
    }
}
Title: Re: create Layers, is there a better way
Post by: T.Willey on January 03, 2008, 01:33:42 PM
I think it looks good except for one thing.  Tony T. has pointed out that the Has() method will return items that are marked to be erased, meaning that they don't really exist in the drawing, and will be lost when the drawing is closed.  So what he did was use a sub function to search the whole table checking the name and the IsErased (I think) property, and if both were correct, it would return that item.  I will see if I can find the post, as it was posted here.

Edit:  Here you go. (http://www.theswamp.org/index.php?topic=12123.msg150999#msg150999)
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 01:40:43 PM
Here's how I'd do it:

Code: [Select]
[CommandMethod("CreateDuhLayer")]
static public void createduhlayer()
{
Document doc = acadApp.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;

using (Transaction tr = db.TransactionManager.StartTransaction()) {
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
if (lt.Has("Some LayerNameGoesHere"))
return;

LayerTableRecord newLTR = new LayerTableRecord();
newLTR.Name = "SomeLayerNameGoesHere";
// set any other properties

lt.UpgradeOpen();
lt.Add(newLTR);

tr.AddNewlyCreatedDBObject(newLTR, true);

tr.Commit();
}

}

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: Kerry on January 03, 2008, 01:41:53 PM
and another.. http://www.theswamp.org/index.php?topic=7918.msg100511#msg100511
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 01:53:40 PM
Thanks Kerry.  I saw that thread, and I have to admit, you had so much going on, I was lost  :-(

Glenn, In your IF statement, did you forget the {} or are they not needed?  Also, when you opened the Layertable for read, what is the false for?  I'm going to see if I can find that answer, on my own, just curious if you had a quick answer
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 01:55:28 PM
the LayerTable.UpgradeOpen is b/c you first accessed it ForRead, now you want to ForWrite - correct?
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 01:58:05 PM
Also, Glenn, the code you posted is being used as a sub, and the return; sends it back to another Sub/Funcion?  If not, im confused on what the return; is doing.  I didn't see you passing a "Name" to this function to check for existence, so I guess i'm just confused
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:00:53 PM
Duh,

Regarding the IF - you don't need {} if you're only executing one line - more than one and you need the {} (rather like lisp nad it's if statements and progn's)

The 'false' is "don't open erased records". Most code I've seen doesn't use the third argument whereas I always use it (habits from ARX I suppose).

WRT UpGradeOpen, yes you're right. The reason you do this is to minimise the time you have things open for write - other things will want to open for write as well, so always keep it as short as possible.

Dan mentioned something I was going to and what he said happens to ALL the SymbolTables and it occurs using the default indexer as well eg. lt["SomeLayerNameGoesHere"]

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:02:59 PM
Also, Glenn, the code you posted is being used as a sub, and the return; sends it back to another Sub/Funcion?  If not, im confused on what the return; is doing.  I didn't see you passing a "Name" to this function to check for existence, so I guess i'm just confused

You'll notice the method has been decorated with the CommandMethod attribute, so this is the command definition. The return will just return out of this method/function and hand control back to AutoCAD.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:05:21 PM
One other thing: This is C#, so stop thinking of them as Sub's and forget that VB drivel  :evil:  :-D
Curly braces rule  :-D
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:18:46 PM
BTW, happy birthday as well...get some code books did we ;)

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 02:24:11 PM
You'll notice the method has been decorated with the CommandMethod attribute, so this is the command definition. The return will just return out of this method/function and hand control back to AutoCAD.
COOL!! I learned something new today!!!  Dont read into this more than what Im saying, but its like an internal ESC key for the function.  Its there so quit the Command(CommandMethod).  VERY nice.  I love .Net
Title: Re: create Layers, is there a better way
Post by: HD on January 03, 2008, 02:25:05 PM
Quote
The 'false' is "don't open erased records". Most code I've seen doesn't use the third argument whereas I always use it (habits from ARX I suppose).

Hi,

I am curious about something. By setting the third argument to false, does that negate what Tim stated about the "Has" method will return items that are marked to be erased?
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:27:08 PM
You're welcome Duh - learning something is always good in my book.

I suppose, thinking about it, that the VB equivalent of return; (a blank return returning nothing) would be Exit Sub...

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:31:00 PM
Quote
The 'false' is "don't open erased records". Most code I've seen doesn't use the third argument whereas I always use it (habits from ARX I suppose).

Hi,

I am curious about something. By setting the third argument to false, does that negate what Tim stated about the "Has" method will return items that are marked to be erased?


Nick,

No it won't. To tell the truth, it was pretty superfluous in this case because if the LayerTable ITSELF was erased, your drawing would be cactus.

You would mainly use the construct I showed when, for instance, you were looping over the layertablerecords, which is totally different to just calling the LayerTable.Has() function, which internally does a loop, but will return erased records...it's a nasty little trap which they still haven't fixed.

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: sinc on January 03, 2008, 02:33:03 PM
In my testing, LayerTable.Has() returns the correct result.  It's just that if you then do LayerTable[layerName], you may get an erased version of the layer, and trying to use that layer will cause you problems.
Title: Re: create Layers, is there a better way
Post by: sinc on January 03, 2008, 02:47:11 PM
You'll notice the method has been decorated with the CommandMethod attribute, so this is the command definition. The return will just return out of this method/function and hand control back to AutoCAD.
COOL!! I learned something new today!!!  Dont read into this more than what Im saying, but its like an internal ESC key for the function.  Its there so quit the Command(CommandMethod).  VERY nice.  I love .Net

There is a school of thought that it is bad form to exit out of the middle of a method in this fashion.  There is a whole class of potential bugs that can created by doing that.

However, the alternative is usually to use a conditional statement, which adds a layer of braces, and makes the code a bit messier and harder to read.  A better solution also involves using a conditional statement, but then breaking the rest of the logic out into other methods.  Sometimes this provides a much-better and more-robust solution with more-generic applicability, but sometimes it's just more work for little gain.  So some people like to have the return in the middle of the method.

I tend to agree with the hardliners that it is "bad form" to return from the middle of a method, and usually try to return from the ends of methods.  But I'm not strict about it, and I occasionally return from the middle of a method, for one of the reasons I just mentioned.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 02:50:04 PM
One other thing: This is C#, so stop thinking of them as Sub's and forget that VB drivel  :evil:  :-D
Curly braces rule  :-D
So think of them as methods right?
Quote from: Glenn R
..get some code books did we
Kinda, I got a book at AU which I'm using/abusing to try and help me learn this stuff.  I am finding that while it does give me an example of "working" code, you guys are showing me better and faster ways of doing things.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 02:53:28 PM
Methods or functions - I tend to think of them as functions, but that's just personal preference.
Glad you're liking it and getting into the 'heavier' end - you can definately do more with .NET.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 03:08:15 PM
And while I'm asking tons of questions, why the AS ?
Code: [Select]
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;vs
Code: [Select]
LayerTable LT = (LayerTable)DB.LayerTableId.GetObject(Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);
Is it b/c your using the .GetObject of the transaction instead of the database.layertableid.getobject?  I saw a thread on Through-the-Interface talking about the AS, but I didn't understand what they said.
Title: Re: create Layers, is there a better way
Post by: sinc on January 03, 2008, 03:15:59 PM

This line will throw an exception if objId is the id for something that is not a layer table:

Code: [Select]
LayerTable lt = (LayerTable)tr.GetObject(objId, OpenMode.ForRead, false);
This line will result in lt == null if objId is the id for something that is not a layer table:

Code: [Select]
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
Title: Re: create Layers, is there a better way
Post by: sinc on January 03, 2008, 03:17:53 PM
Methods or functions - I tend to think of them as functions, but that's just personal preference.
Glad you're liking it and getting into the 'heavier' end - you can definately do more with .NET.


Well, you might say Methods belong to an object, while Functions do not.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 03:39:08 PM
And while I'm asking tons of questions, why the AS ?
Code: [Select]
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;vs
Code: [Select]
LayerTable LT = (LayerTable)DB.LayerTableId.GetObject(Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);
Is it b/c your using the .GetObject of the transaction instead of the database.layertableid.getobject?  I saw a thread on Through-the-Interface talking about the AS, but I didn't understand what they said.

Well, there's IS and AS. Is will test if an object IS some type of object, whereas AS will try and cast some object to another object.

When you do this:
Code: [Select]
(LayerTable)DB.LayerTableId blah blah

you are actually doing what's called a direct cast - casting directly from the return type of DbObject, in this example, to a LayerTable.

When you use AS, it will do the cast for you and either return a valid object or null and, here's the most important bit, AS will not throw an exception whereas a direct cast will.

Also, I would recommend not using the GetObject() method off an ObjectId and use GetObject() off the transaction you've started because the intent will be much clearer, but that's just my opinion.

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 03:50:52 PM
Quote from: Glenn R
Well, there's IS and AS. Is will test if an object IS some type of object, whereas AS will try and cast some object to another object.
When you do this:
Code: [Select]
(LayerTable)DB.LayerTableId blah blah

you are actually doing what's called a direct cast - casting directly from the return type of DbObject, in this example, to a LayerTable.
OK, good, I knew I was casting.  I didn't know this was "direct" cast which could fail.
Quote from: Glenn R
When you use AS, it will do the cast for you and either return a valid object or null and, here's the most important bit, AS will not throw an exception whereas a direct cast will.
So we need to test for Null?  How else would we know it worked?
Quote from: Glenn R
Also, I would recommend not using the GetObject() method off an ObjectId and use GetObject() off the transaction you've started because the intent will be much clearer, but that's just my opinion.

Cheers,
Glenn.
Noted, and appreciated!  I have a hard enough time reading my own drivel, let alone someone else trying to figure out what Im thinking.  On a side note, I had someone call me and ask what 'Spit the Dummy' meant b/c it popped up on their screen one day
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 03:56:56 PM
So we need to test for Null?  How else would we know it worked?

That's exactly right. So, revising what I posted earlier in this thread, you would do this:
Code: [Select]
[CommandMethod("CreateDuhLayer")]
static public void createduhlayer()
{
Document doc = acadApp.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;

using (Transaction tr = db.TransactionManager.StartTransaction()) {
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
if (lt == null)
return;
if (lt.Has("Some LayerNameGoesHere"))
return;

LayerTableRecord newLTR = new LayerTableRecord();
newLTR.Name = "SomeLayerNameGoesHere";
// set any other properties

lt.UpgradeOpen();
lt.Add(newLTR);

tr.AddNewlyCreatedDBObject(newLTR, true);

tr.Commit();
}

}

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 03:58:30 PM
On a side note, I had someone call me and ask what 'Spit the Dummy' meant b/c it popped up on their screen one day

 :lmao:
Title: Re: create Layers, is there a better way
Post by: David Hall on January 03, 2008, 04:37:02 PM
One last question and I'll stop beating this dead horse for today.  If in the new version you posted, if the return; is called, the transaction is aborted/disposed of properly because the function loses scope?  Should we use tr.Abort() or not, and if not, when would we use tr.Abort()  ?
Title: Re: create Layers, is there a better way
Post by: BeanBag on January 03, 2008, 06:54:44 PM
I found this thread on direct casting from Through the Interface:

http://through-the-interface.typepad.com/through_the_interface/2006/09/working_with_sp.html

Some thoughts from the Autodesk labs. All the code below is taken from Lab 5.

Direct casting is used through out for layertable, blocktable and blocktablerecord in the Labs. With these is there ever a chance it could not return these objects?

In the labs direct casting is used with try catch statements where the exception is used in similar way to if-else statement.

They use entity within a block here:

Code: [Select]
Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false);
                    if (ent is AttributeDefinition)

Below code is called on items selected by the user: Is Gettype() and typeof() interchangeable with IS  as shown above?

Code: [Select]
Entity ent = (Entity)trans.GetObject(employeeId, OpenMode.ForRead, false); // Use it to open the current object!
                if (ent.GetType() == typeof(BlockReference)) // We use .NET's RTTI to establish type.


This is their layer create:

Code: [Select]
private ObjectId CreateLayer()
        {
            ObjectId layerId;
            Database db = HostApplicationServices.WorkingDatabase;

            using (Transaction trans = db.TransactionManager.StartTransaction())
            {
                //Get the layer table first...
                LayerTable lt = (LayerTable)trans.GetObject(db.LayerTableId, OpenMode.ForRead);
                //Check if EmployeeLayer exists...
                if (lt.Has("EmployeeLayer"))
                {
                    layerId = lt["EmployeeLayer"];
                }
                else
                {
                    //If not, create the layer here.
                    LayerTableRecord ltr = new LayerTableRecord();
                    ltr.Name = "EmployeeLayer"; // Set the layer name
                    ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);
                    // upgrade the open from read to write
                    lt.UpgradeOpen();
                    layerId = lt.Add(ltr);
                    trans.AddNewlyCreatedDBObject(ltr, true);
                }
                trans.Commit();
            }
            return layerId;
        }


******************EDIT***********************
I thought I would run a test on Lab 5
Ran the command and then purged and then ran command again  :evil:

It threw an exception here:

Code: [Select]
BlockTableRecord empBtr = (BlockTableRecord)trans.GetObject(bt["EmployeeBlock"], OpenMode.ForRead);
It detected that the block and layer had been deleted and created them again so not sure what is happening here.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 03, 2008, 07:39:58 PM
One last question and I'll stop beating this dead horse for today.  If in the new version you posted, if the return; is called, the transaction is aborted/disposed of properly because the function loses scope?  Should we use tr.Abort() or not, and if not, when would we use tr.Abort()  ?

Well, strictly speaking, the 'using' scope is exited first and that in turn calls Dispose() on whatever was being 'used'...in this case, a transaction. Now, if Dispose() is called on a transaction and it hasn't been commited, then an implicit Abort() is called on the transaction and then it's memory is cleaned up.

Then of course it bails out of the function.

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: BeanBag on January 03, 2008, 08:20:33 PM
I've been able to fix lab 5 by adding true on the end - letting it open deleted objects:

Code: [Select]
BlockTableRecord empBtr = (BlockTableRecord)trans.GetObject(bt["EmployeeBlock"], OpenMode.ForRead, true);
I find this strange - the block and layer are created and trans.commit() is called.  :|  Its a nested transaction though.

My guess here is that since the block and layer are created with in a nested transaction the database has not been updated and still registers it deleted until the very final trans.commit() is called? Nope I denested them and same problem but at least now the layer and block appear in drawing even though it throws the exception.

THis is probably a better fix - use the objectID instead of block name - it works to and don't need to let it use deleted objects:

Code: [Select]
BlockTableRecord empBtr = (BlockTableRecord)trans.GetObject(blockId, OpenMode.ForRead);
LOL well after all that you can purge layers/blocks and layertable.has("layername") and blocktable.has("blockname") will work to say they don't exisit

But don't use block names to get the block from blocktable because even if you create it again after purging it still says its erased until you open and close drawing. Weird.
Title: Re: create Layers, is there a better way
Post by: sinc on January 03, 2008, 08:30:42 PM
I would really recommend you don't do that.  If you try to use an erased layer for something, you might get some strange results.

It is far better to use the PInvoke solution Tony T. posted here (http://www.theswamp.org/index.php?topic=12123.msg151231#msg151231).

Alternatively, you could use the Com object.  This works fine, as well:

Code: [Select]
        public static ObjectId GetLayerObjectId(LayerTable table, string name)
        {
            AcadLayers layerTable = table.AcadObject as AcadLayers;
            AcadLayer layer = layerTable.Item(name);
            if (layer == null)
                return ObjectId.Null;
            else
                return DBObject.FromAcadObject(layer);
        }
Title: Re: create Layers, is there a better way
Post by: David Hall on January 04, 2008, 04:21:09 PM
Not to ask a dumb question, but here goes.  I thought we were trying to NOT use com?  Isn't it slower blah blah blah and stuff?
I never learned it b/c I have heard horror stories and the managed api is better.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 04, 2008, 04:28:06 PM
I thought we were trying to NOT use com?  Isn't it slower blah blah blah and stuff?

Correct :)
Title: Re: create Layers, is there a better way
Post by: sinc on January 04, 2008, 08:17:53 PM
Not to ask a dumb question, but here goes.  I thought we were trying to NOT use com?  Isn't it slower blah blah blah and stuff?
I never learned it b/c I have heard horror stories and the managed api is better.

Yeah, but this is to get around a bug in the managed API.

The REAL solution is for AUTODESK TO FIX THE API!!!!  But it's been like this for more than three years now, and it's a known problem, and they still haven't fixed it...    :ugly:
Title: Re: create Layers, is there a better way
Post by: sinc on January 04, 2008, 08:21:32 PM
Isn't it slower blah blah blah and stuff?

And this isn't right.  COM is actually faster.

The .NET API is not a real framework.  Maybe someday.  But right now, it is simply a managed wrapper around the COM code.  So it's actually SLOWER.  Underneath, it amounts to using COM indirectly.  It's just much easier to program in, and the performance hit isn't noticeable for most things.  Or for areas where the performance hit of the managed API is not acceptable, you can write something in C++ and call it from your managed code, getting "the best of both worlds".
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 04, 2008, 08:54:05 PM
Isn't it slower blah blah blah and stuff?
it is simply a managed wrapper around the COM code.  So it's actually SLOWER.  ...

This is not a correct statement. Managed code wraps native code directly,
without the use of COM, I am fairly certain that well written .NET is faster than COM.
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 05, 2008, 06:42:47 AM
So what we can do is drop down into C++/CLI and write our own managed wrapper for AcDbSymbolTable::GetAt()

Code: [Select]
ObjectId Utilities::SymbolTableGetAt(String ^name,SymbolTable ^table, bool getErased)
  {
   
    if(name == nullptr)
      throw gcnew System::ArgumentNullException("SymbolTableGetAt");

    if(table == nullptr)
      throw gcnew System::ArgumentNullException("SymbolTableGetAt");
   
    AcDbObjectId recordId;
    ObjectId id;

    pin_ptr<const ACHAR> _name = PtrToStringChars(name);
    pin_ptr<AcDbSymbolTable>_table = (AcDbSymbolTable*)table->UnmanagedObject.ToPointer();

    //Call the ObjectARX AcDbSymbolTable::GetAt();
    if(_table->getAt(_name,recordId,getErased)== Acad::eOk)
      id = (ObjectId)gcnew ObjectId(recordId.asOldId());
    else
      id = (ObjectId)gcnew ObjectId();

    return id;
  }

Then add an Extension Method to the SymbolTable class via C#
Code: [Select]
   public static ObjectId GetAt(this SymbolTable m_this, string name, bool getErased)
    {
      return AcMgdWrprs.Utilities.SymbolTableGetAt(name, m_this, getErased);//edited
    }


Now we can use this new method in our C# function.

Code: [Select]
   [CommandMethod("doit")]
    static public void OpenThePodBayDoorsHal()
    {
      Editor ed = AcAp.Application.DocumentManager.MdiActiveDocument.Editor;
      Database db = AcDb.HostApplicationServices.WorkingDatabase;

      string MyNewLayerName = "OhDannyBoy";

      LayerTableRecord newLTR = new LayerTableRecord();
      newLTR.Name = MyNewLayerName;

      using (Transaction tr = db.TransactionManager.StartTransaction())
      {
        try
        {
          DBObject dbObject = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false);

          if (dbObject != null && dbObject is LayerTable)
          {
            LayerTable lt = (LayerTable)dbObject;

            if (lt.GetAt(MyNewLayerName, false).IsNull) //<---------
            {
              lt.UpgradeOpen();
              lt.Add(newLTR);

              tr.AddNewlyCreatedDBObject(newLTR, true);
              tr.Commit();
            }
            else
            {
              ed.WriteMessage("Sorry Dave, I can't do that");
            }
          }
        }
        catch (System.Exception ex)
        {
          ed.WriteMessage(ex.Message);
        }
      }
    }

w00ty w00ty w00ty w00ty  :lol:

Title: Re: create Layers, is there a better way
Post by: Glenn R on January 05, 2008, 06:46:47 AM
Heh...nice one Dan.
Title: Re: create Layers, is there a better way
Post by: sinc on January 05, 2008, 10:42:36 AM
Isn't it slower blah blah blah and stuff?
it is simply a managed wrapper around the COM code.  So it's actually SLOWER.  ...

This is not a correct statement. Managed code wraps native code directly,
without the use of COM, I am fairly certain that well written .NET is faster than COM.


Hmmm.....  Maybe I'm just getting thrown again by the fact that there is no .NET API for Civil-3D, so everything I do is a horrible mish-mash of .NET and COM.... 
Title: Re: create Layers, is there a better way
Post by: sinc on January 05, 2008, 10:44:40 AM
So what we can do is drop down into C++/CLI and write our own managed wrapper for AcDbSymbolTable::GetAt()

You've seen that PInvoke solution from Tony T, right?

It's basically the same thing, but the whole thing is done in C#.NET, with no need to compile a C++ class.
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 05, 2008, 12:30:31 PM
Hi Sinc,
Yes, I have seen Tony’s code. I just prefer to write wrappers in C++/CLI as it helps me learn what’s going on behind the scenes.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 15, 2008, 03:53:40 PM
Next part of the question on creating layers.  What would be the best way to keep up with the list of layers and their respective properties?  As I see it, there are 2 ways I can do this.
If I go with option 2, what is the best type of file to maintain?  I'm thinking XML file or TXT file.  XML has its advantages in that when editing, all the properties are delineated by the XML tags.  If I use a TXT file, should I use comma delimited or tab delimited file?  Ideas? Other pros or cons I haven't thought of?

Edit:
Hard coding has obvious disadvantages in that I have to redistribute the DLL everytime there is an update.  Are there other disadvantages you can think of?
Title: Re: create Layers, is there a better way
Post by: David Hall on January 15, 2008, 03:55:15 PM
And to add to my previous post, I have some routines that freeze and thaw layers.  I currently use a TXT file to tell computer what layers to thaw.  The routine sets 0 current, freezes everything else, and thaws the layers on the TXT file list.  Ideas?
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 15, 2008, 03:56:25 PM
XML - no question.
Title: Re: create Layers, is there a better way
Post by: Kerry on January 15, 2008, 04:07:03 PM

David, I'd use XML as well.
The 'Linq' technology provided by .NET3.5 is worth spending a couple of days playing with.

Title: Re: create Layers, is there a better way
Post by: David Hall on January 15, 2008, 04:12:14 PM
Glenn, do you have an example of reading or writing XML?
Title: Re: create Layers, is there a better way
Post by: David Hall on January 15, 2008, 04:12:56 PM
Kerry thanks.  I'll start looking into that.  MSDN for info?
Title: Re: create Layers, is there a better way
Post by: Kerry on January 15, 2008, 04:30:51 PM
a simplistic starter
http://www.theswamp.org/index.php?topic=20033.0

.. and yes, MSDN !
there are heaps of articles and samples.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 15, 2008, 04:58:28 PM
Glenn, do you have an example of reading or writing XML?

Apart from Kerry's excellent link, you might try searching for XmlReader and XmlWriter, until you get accustomed to serialising your classes to and from XML.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 15, 2008, 05:02:08 PM
I'm reading Kerry's post now.  Thanks guys
Title: Re: create Layers, is there a better way
Post by: Kerry on January 16, 2008, 08:34:30 PM
David, have a play ...
don't let the using statements scare you, they are not ALL needed :-)

// CodeHimBelongaKwb ©  Dec 2008

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

using System.Runtime.InteropServices;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using AcAp = Autodesk.AutoCAD.ApplicationServices;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
using AcEd = Autodesk.AutoCAD.EditorInput;
using AcGe = Autodesk.AutoCAD.Geometry;
using AcRx = Autodesk.AutoCAD.Runtime;
//
using AcCm = Autodesk.AutoCAD.Colors;
using AcGi = Autodesk.AutoCAD.GraphicsInterface;
using AcLy = Autodesk.AutoCAD.LayerManager;
using AcPl = Autodesk.AutoCAD.PlottingServices;
using AcUi = Autodesk.AutoCAD.Windows;

using WinForms = System.Windows.Forms;

[assembly: AcRx.CommandClass(typeof(LayersFromXML.TestCommands))]

namespace LayersFromXML
{
    public class TestCommands   
    {
        [AcRx.CommandMethod("Cmd2")]
        static public void test2()
        {
            Util.GetLayerXMLData();
        }

    //==========================================================
    public class Util
    {
        //---------------------------------------------------------
        public static void GetLayerXMLData()
        {
            Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;

            PromptStringOptions ops = new PromptStringOptions("\nLayer name: ");
            ops.AllowSpaces = true;

            PromptResult res = ed.GetString(ops);
           
            if (!(res.Status == PromptStatus.OK && res.StringResult != string.Empty))
            {
                return;
            }
            string layerName = res.StringResult;
            DataSet dsLayerData = new DataSet();
            dsLayerData.ReadXml(@"c:\LayerDefinitions.xml");
            DataView dsLayerDefinition = new DataView(dsLayerData.Tables["Layer"]);
            dsLayerDefinition.Sort = "LayerName";

            int rowIndex = dsLayerDefinition.Find(layerName);
            if (rowIndex == -1)
            {
                // The LayerName was not found in our LayerData
                ed.WriteMessage("\nSpecified layer not found in the file.");               
                return;
            }
            ed.WriteMessage("\nLayerName : {0}", dsLayerDefinition[rowIndex]["LayerName"]);
            ed.WriteMessage("\nDesc      : {0}", dsLayerDefinition[rowIndex]["Description"]);
            ed.WriteMessage("\nColor     : {0}", dsLayerDefinition[rowIndex]["Color"]);
            ed.WriteMessage("\nLineType  : {0}", dsLayerDefinition[rowIndex]["LineType"]);
            ed.WriteMessage("\nAlias     : {0}", dsLayerDefinition[rowIndex]["Alias"]);
        }
    }
}
 
 

and the XML file   c:\LayerDefinitions.xml
<?xml version="1.0" encoding="utf-8"?>
<LayerData>
  <Layer>
    <Description>Outlines 0.90mm</Description>
    <LayerName>ST90</LayerName>
    <Color>5</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST90</Alias>
  </Layer>
  <Layer>
    <LayerName>ST70</LayerName>
    <Description>Outlines 0.70mm</Description>
    <Color>3</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST70</Alias>
  </Layer>
  <Layer>
    <LayerName>ST50</LayerName>
    <Description>Outlines 0.50mm</Description>
    <Color>5</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST50</Alias>
  </Layer>
  <Layer>
    <LayerName>ST35</LayerName>
    <Description>Outlines 0.35mm</Description>
    <Color>2</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST35</Alias>
  </Layer>
  <Layer>
    <LayerName>ST25</LayerName>
    <Description>Outlines 0.25mm</Description>
    <Color>1</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST25</Alias>
  </Layer>
  <Layer>
    <LayerName>ST18</LayerName>
    <Description>Outlines 0.18mm</Description>
    <Color>4</Color>
    <LineType>CONTINUOUS</LineType>
    <Alias>ST18</Alias>
  </Layer>
</LayerData>
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 03:40:25 AM
Nicely done Kerry - DataView's can be very handy.

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 17, 2008, 01:13:04 PM
How about using a DWG file to hold your layer info? Don’t get me wrong, XML is a great idea. I just wanted to see if I could … ^-^
Title: Re: create Layers, is there a better way
Post by: David Hall on January 17, 2008, 01:17:01 PM
I thought about that, but I was trying to come up with files that nobody would want to edit (read screw up) except me, and a dwg is too easy to open and tweak by some of my wanna-be power users.
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 17, 2008, 01:24:13 PM
.. too easy to open and tweak by some of my wanna-be power users.

Oh God, wanna-be power users, may I recommend a 1024 bit encryption then  :-D
Title: Re: create Layers, is there a better way
Post by: David Hall on January 17, 2008, 01:25:04 PM
yyyeeesss please!!!
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 01:31:11 PM
How about using a DWG file to hold your layer info? Don’t get me wrong, XML is a great idea. I just wanted to see if I could … ^-^


I use a DWS file myself which has the added bonus of being able to be used for cad standards checking.
Title: Re: create Layers, is there a better way
Post by: It's Alive! on January 17, 2008, 01:33:07 PM
Glenn R,  could this have been done with Clone() or DeepClone()?
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 01:36:44 PM
Dan,

I would have to look up what I did exactly, but I think I used WblockClone from memory.

Cheers,
Glenn.
Title: Re: create Layers, is there a better way
Post by: SomeCallMeDave on January 17, 2008, 01:55:58 PM
I thought about that, but I was trying to come up with files that nobody would want to edit (read screw up) except me, and a dwg is too easy to open and tweak by some of my wanna-be power users.

If you open a file using db.ReadDwgFile like Daniel did,  the file does not have to have a .dwg extension.   I just tested if (briefly, but I did test it)  and it works. 

You could 'hide' your dwg so no one would poke at it.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 02:32:47 PM
Done that before also SCMD (hide a dwg from casual scrutiny) and you can do it with VBA DBX as well too :)
Title: Re: create Layers, is there a better way
Post by: SomeCallMeDave on January 17, 2008, 02:35:57 PM
Glenn,

Do you know of any drawbacks to reading, via .NET, a database from a non-.dwg file?

Just for my general edification.  :)

Thanks
Title: Re: create Layers, is there a better way
Post by: David Hall on January 17, 2008, 02:39:45 PM
WOW, I have hidden stuff in files by changing the extension, but those were just txt files.  I didn't know you could change the extension of "database" files and still have it work.  I guess it makes sense, just never thought of it that way
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 04:17:30 PM
Glenn,

Do you know of any drawbacks to reading, via .NET, a database from a non-.dwg file?

Just for my general edification.  :)

Thanks

Just to clarify, it STILL is a dwg file, it just doesn't have the DWG extension...so in answer to your question, no, I haven't experienced any drawbacks as such.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 04:18:14 PM
Just make sure you destroy it.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 17, 2008, 04:38:02 PM
Just make sure you destroy it.
???
You mean when Im done "using" it in code right?  The word destroy is what threw me.  I thought you would have said dispose or unload or close.
Title: Re: create Layers, is there a better way
Post by: Glenn R on January 17, 2008, 04:49:16 PM
Sorry - my C++ coming out again. Yes, you're right - 'use it and dispose it'.

The golden rule in C++ - if you 'new' it, delete/destroy it...good rule to live by even in the managed world.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 17, 2008, 05:21:52 PM
thats OK, just asking for claritys sake.  Now that you mention it, RR taught me a little C++ back in the day, and we had to destroy our objects we created.  I just forgot the term.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 18, 2008, 05:17:04 PM
Thanks guys for the pointers.  I now have 3 methods for creating layers, ea building on the last.  I am now working on loading linetypes.  Kerry your LibTools are definetly helping point me in the direction.
Title: Re: create Layers, is there a better way
Post by: David Hall on January 18, 2008, 05:18:15 PM
Not that this is anywhere near done, but for those following along, here is where Im at
Code: [Select]
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Colors;
using Autodesk.AutoCAD.EditorInput;

namespace LayerCode
{
    public class LayerCode
    {

        [CommandMethod("CD3")]
        static public void createduhlayer()
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
                if (lt == null)
                    return;
                if (lt.Has("DSH"))
                    return;
                LayerTableRecord newLTR = new LayerTableRecord();
                newLTR.Name = "DSH";
                newLTR.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);
                // set any other properties

                lt.UpgradeOpen();
                lt.Add(newLTR);

                tr.AddNewlyCreatedDBObject(newLTR, true);

                tr.Commit();
            }
            //createLayer("Duh2");
            createLayerXML("Duh3");
        }

        static public void createLayer(string Lname)
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {

                LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
                if (lt == null)
                    return;
                if (lt.Has(Lname))
                    return;
                LayerTableRecord newLTR = new LayerTableRecord();
                newLTR.Name = Lname;
                newLTR.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);
                // set any other properties

                lt.UpgradeOpen();
                lt.Add(newLTR);

                tr.AddNewlyCreatedDBObject(newLTR, true);

                tr.Commit();

            }
        }

        static public void createLayerXML(string Lname)
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {

                LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
                if (lt == null)
                    return;
                if (lt.Has(Lname))
                {
                    return;
                }
                else
                {
                    DataSet dsLayerData = new DataSet();
                    dsLayerData.ReadXml(@"c:\LayerDefinitions.xml");
                    DataView dsLayerDefinition = new DataView(dsLayerData.Tables["Layer"]);
                    dsLayerDefinition.Sort = "LayerName";

                    int rowIndex = dsLayerDefinition.Find(Lname);
                    if (rowIndex == -1)
                    {
                        ed.WriteMessage("Bogus Layer Name");
                    }
                    else
                    {
                        short lC = Convert.ToInt16(dsLayerDefinition[rowIndex]["Color"]);
                        string lnT = dsLayerDefinition[rowIndex]["LineType"].ToString();
                        LinetypeTable LTT = tr.GetObject(db.LinetypeTableId, OpenMode.ForRead, false) as LinetypeTable;
                        if (LTT == null)
                            return;
                        if (LTT.Has(lnT))
                            return;
                        else
                        {
                            LinetypeTableRecord newLTTR = new LinetypeTableRecord();
                            newLTTR.Name = lnT;
                            LTT.UpgradeOpen();
                            LTT.Add(newLTTR);
                            tr.AddNewlyCreatedDBObject(newLTTR, true);
                           

                        }
                        //string layerColor = dsLayerDefinition[rowIndex]["Color"].ToString();
                        //short  lC = Convert.ToInt16(layerColor);
                        LayerTableRecord newLTR = new LayerTableRecord();
                        newLTR.Name = Lname;
                        newLTR.Color = Color.FromColorIndex(ColorMethod.ByAci, lC);
                        newLTR.LinetypeObjectId = newLTTR;
                        lt.UpgradeOpen();
                        lt.Add(newLTR);
                        tr.AddNewlyCreatedDBObject(newLTR, true);
                    }
                }

                // set any other properties
                tr.Commit();
            }
        }}
Title: Re: create Layers, is there a better way
Post by: David Hall on January 18, 2008, 05:20:08 PM
The linetype piece doesn't work yet so thats what i will work on next.
Title: Re: create Layers, is there a better way
Post by: David Hall on July 29, 2008, 12:44:15 PM
this was all going so well, when I came across a new problem.  What if you need to rename a layer?  Is it better to create a new layer and move all objects from the old layer to the new and delete the old layer or can  you just rename a layer in the LayerTable?
Title: Re: create Layers, is there a better way
Post by: Glenn R on July 29, 2008, 03:18:06 PM
AFAIK, you can rename, as long as the new name DOES not exist in the dbase already...obviously  :-)
Title: Re: create Layers, is there a better way
Post by: David Hall on July 30, 2008, 10:19:11 AM
Thanks Glenn.  I meant to come back and update this.  I was making it harder than it needed to be, and I was banging my head against the wall tring to find the "updateDB" option.  Finally i said, lets just see what blows up, and it worked
Title: Re: create Layers, is there a better way
Post by: Glenn R on July 30, 2008, 10:35:41 AM
...lets just see what blows up,

Words to live by! :)
Title: Re: create Layers, is there a better way
Post by: MickD on July 30, 2008, 06:26:59 PM
Yep, that's how I handle most projects :D

(the sad thing is there's always a hint of truth in comedy  :|)
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 05:31:20 PM
ok, time for an update.  With help from Glenn and Kerry, I now have code that will move objects from a layer that is named incorrectly to a correctly named layer.  I am still having problems with my linetype, specifically its losing scope

Code: [Select]
        [CommandMethod("LTree")]
        static public void createLayerXML(string Lname)
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {

                LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
                if (lt == null)
                    return;
                if (lt.Has(Lname))
                {
                    return;
                }
                else
                {
                    DataSet dsLayerData = new DataSet();
                    dsLayerData.ReadXml(@"c:\CS_TEPLayers.xml");
                    DataView dsLayerDefinition = new DataView(dsLayerData.Tables["Layer"]);
                    dsLayerDefinition.Sort = "LayerName";

                    int rowIndex = dsLayerDefinition.Find(Lname);
                    if (rowIndex == -1)
                    {
                        ed.WriteMessage("Bogus Layer Name");
                    }
                    else
                    {
                        short lC = Convert.ToInt16(dsLayerDefinition[rowIndex]["Color"]);
                        string lnT = dsLayerDefinition[rowIndex]["LineType"].ToString();
                        LinetypeTable LTT = tr.GetObject(db.LinetypeTableId, OpenMode.ForRead, false) as LinetypeTable;
                        if (LTT == null)
                            return;
                        if (LTT.Has(lnT))
                            return;
                        else
                        {
                            LinetypeTableRecord newLTTR = new LinetypeTableRecord();
                            newLTTR.Name = lnT;
                            LTT.UpgradeOpen();
                            LTT.Add(newLTTR);
                            tr.AddNewlyCreatedDBObject(newLTTR, true);


                        }
                        //string layerColor = dsLayerDefinition[rowIndex]["Color"].ToString();
                        //short  lC = Convert.ToInt16(layerColor);
                        LayerTableRecord newLTR = new LayerTableRecord();
                        newLTR.Name = Lname;
                        newLTR.Color = Color.FromColorIndex(ColorMethod.ByAci, lC);
                        //newLTR.LinetypeObjectId = newLTTR; // Lost scope here from else above
                        lt.UpgradeOpen();
                        lt.Add(newLTR);
                        tr.AddNewlyCreatedDBObject(newLTR, true);
                    }
                }

                // set any other properties
                tr.Commit();
            }
        }
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 05:34:16 PM
BTW, the above code doesn't work as a command, I'm still working that part out.  This came from another piece of code further up in the thread.
Title: Re: create Layers, is there a better way
Post by: Glenn R on August 05, 2008, 05:59:02 PM
This is a classic example of why I don't like nested if,then,else statements.

Duh, remember, that like C/C++, curly braces denote a scope (although not as extensive as C/C++) and that is why your variable for newLTTR is going out of scope - it's DECLARED and INITIALISED in the above 'else' clause, which as luck would have it, has....you guessed it - curly braces.

In this sort of situation, at the begining of the function do this:

LinetypeTableRecord newLTTR = null;

That way, it is accessible for the rest of the 'nested' scopes defined by the curly brace code blocks.

Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 06:09:25 PM
Thanks Glenn,  I knew I would have to declare it outside of the else, I just wanted to make it the best possible way I could.
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 06:14:46 PM
I am rewriting all that code to be more user friendly.  Classic example of the drivel I put together, and come back to later and say What was I thinking.  I am going to have to do some reading up on XML and Datasets and maybe LINQ because I have no clue what the rowIndex of my table is doing.
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 06:18:43 PM
Here is what I have so far on the re-write
Code: [Select]
        [CommandMethod("LTree")]
        static public void createXMLLayer()
        {
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            DataSet dsLayerData = new DataSet();
            dsLayerData.ReadXml(@"c:\CS_TEPLayers.xml");
            DataView dsLayerDefinition = new DataView(dsLayerData.Tables["Layer"]);
            dsLayerDefinition.Sort = "LayerName";

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {

                LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead, false) as LayerTable;
                string LayerName = null;
                if (lt == null)
                    return;


                int rowIndex = 1;
                LayerName = (string)dsLayerDefinition[rowIndex]["LayerName"];
                ++rowIndex;
                LayerName = (string)dsLayerDefinition[rowIndex]["LayerName"];
I am trying to see how it reads the file, and how to use my rowindex.  I hard coded the index to 1 and then pulled the LayerName for that row.  It did not do what I thought it would do, It pulled from row 90ish.  Then the second time through, it pulled from 85ish.  Bottem line is I have no clue what Im doing, so I am going to have to read up on this tonight
Title: Re: create Layers, is there a better way
Post by: Glenn R on August 05, 2008, 06:19:10 PM
That's how you learn mate - an avenue you decide to go down to learn and you emerge a week later going 'WOW'....now what was I doing... :)
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 06:26:47 PM
Well, the lightbulb lit up for a second, but quickly went back off.  I thought that because I was doing a sort, that was why the rowindex gave me a result I didn't expect.  Turns out, if it did the sort it claims, I am still way off
Title: Re: create Layers, is there a better way
Post by: David Hall on August 05, 2008, 06:27:23 PM
huh, what were we talking about?   :wink:
Title: Re: create Layers, is there a better way
Post by: Kerry on August 05, 2008, 07:53:38 PM


Quote
<snip> an avenue you decide to go down to learn and you emerge .... <snip>

I wish I could make the time to take a trip down that road .... I'm overdue for an excursion ... :)