TheSwamp

Code Red => .NET => Topic started by: AARYAN on December 03, 2014, 12:29:19 AM

Title: Insert Existing Block with attributes
Post by: AARYAN on December 03, 2014, 12:29:19 AM
Hi All,

Please help.

After trying a lot from Lisp to insert existing block with attributes from entmake function to get the maximum speed I failed to get the good result. So I am keen to get a c#.net code for the same. Since I am very basic in .net world I have no idea about its speed.
Please guide.

I am actually looking for a code which is callable from lisp with arguments as follows:

Blockname (already exists in a drawing)
InsertionPoint
BlockScale (X,Y,Z same)
BlockLayer
BlockColor (Already having a ByBlock Property so that it can change after insert)
BlockLinetype (Already having a ByBlock Property so that it can change after insert)

and finally the attributes

1. "Test1"
2. "Test2"
3. ""....etc (if more than 2 attributes found.

Any help would be highly appreciable.

Thanks in Advance.

Regards
Title: Re: Insert Existing Block with attributes
Post by: gile on December 03, 2014, 01:23:23 AM
Hi,

You'll probably get some inspiration from Kerry's tutorial (http://www.theswamp.org/index.php?topic=37686.0).
Title: Re: Insert Existing Block with attributes
Post by: AARYAN on December 03, 2014, 03:37:25 AM
Thanks for the link Mr. Gile and also to Mr. Kerry. Very nice tutorial.

But there is no Lispfunction method available only commandmethods are there. I am Extremely sorry to say but I am very new to the .net world and will not able to write the lispfunction method on my own.

Any help would be highly appreciable.

Thanks
Title: Re: Insert Existing Block with attributes
Post by: gile on December 03, 2014, 11:46:51 AM
Hi,

IMO the main issue when building .NET LispFunction is how to deal with arguments.
A LispFunction method always have ResultBuffer parameter which contains the arguments of the LISP function.
So, we have to deal with the arguments of a dynamic (un)typed langage as LISP wrapped in TypedValueS in a Resultbuffer and manage this in a staticly/strongly typed langage as C#.

Another question is how to handle wrong arguments (number or type).
Should we don't care and let AutoCAD handle it ? (with the risk AutoCAD crashes)
Should we make the function return nil (or some other value) ? (and let the calling LISP deal with this returned value)
Should we throw a .NET exception ? (to alert the user and stop evaluation)

Here an example which shows different ways to create a LISP function to insert a block in the current space and returns the newly inserted block ename.
The LISP function signature :
(insert blockName inspoint layer colorIndex lineType [attribs])
where:
- blockName is a string
- inspoint is a LISP point (list of 2 or 3 numbers)
- scale is LISP number (even an integer or a real)
- layer is a string
- colorIndex is an integer
- lineType is a string
- attribs (optional) is a list of strings

Each LispFunction will call a private method (InsertBlock) with the upper arguments converted in .NET types
Code - C#: [Select]
  1. private ObjectId InsertBlock(
  2.             string blockName,
  3.             Point3d insPoint,
  4.             double scale,
  5.             string layer,
  6.             int colorIndex,
  7.             string lineType,
  8.             string[] attribs)
  9.         {
  10.             Document doc = AcAp.DocumentManager.MdiActiveDocument;
  11.             Database db = doc.Database;
  12.             ObjectId retVal;
  13.             using (Transaction tr = db.TransactionManager.StartTransaction())
  14.             {
  15.                 var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  16.                 if (!bt.Has(blockName))
  17.                     throw new ArgumentException("Block '" + blockName + "' not found", "blockName");
  18.                 var space = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  19.                 var br = new BlockReference(insPoint, bt[blockName]);
  20.                 br.ScaleFactors = new Scale3d(scale);
  21.                 br.Layer = layer;
  22.                 br.ColorIndex = colorIndex;
  23.                 br.Linetype = lineType;
  24.                 retVal = space.AppendEntity(br);
  25.                 tr.AddNewlyCreatedDBObject(br, true);
  26.                 var btr = (BlockTableRecord)tr.GetObject(bt[blockName], OpenMode.ForRead);
  27.                 int i = 0;
  28.                 RXClass attDefClass = RXClass.GetClass(typeof(AttributeDefinition));
  29.                 foreach (ObjectId id in btr)
  30.                 {
  31.                     if (id.ObjectClass == attDefClass)
  32.                     {
  33.                         AttributeDefinition attDef = (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
  34.                         AttributeReference attRef = new AttributeReference();
  35.                         attRef.SetAttributeFromBlock(attDef, br.BlockTransform);
  36.                         if (i < attribs.Length)
  37.                             attRef.TextString = attribs[i++];
  38.                         br.AttributeCollection.AppendAttribute(attRef);
  39.                         tr.AddNewlyCreatedDBObject(attRef, true);
  40.                     }
  41.                 }
  42.                 tr.Commit();
  43.             }
  44.             return retVal;
  45.         }

The simplest, but not safest, way is to let AutoCAD handle the argument exceptions.
AutoCAD will crash if resbuf is null (function called with none argument) or, in case of wrong argument type or number, throw a System exception (InvalidCastException or IndexOutOfRangeException) with a non explicit message at command line.
Code - C#: [Select]
  1.         [LispFunction("insert")]
  2.         public ObjectId UnsafeInsert(ResultBuffer resbuf)
  3.         {
  4.             TypedValue[] args = resbuf.AsArray();
  5.  
  6.             string blockName = (string)args[0].Value;
  7.  
  8.             Point3d insPoint = (Point3d)args[1].Value;
  9.  
  10.             double scale = Convert.ToDouble(args[2].Value);
  11.  
  12.             string layer = (string)args[3].Value;
  13.  
  14.             int colorIndex = Convert.ToInt32(args[4].Value);
  15.  
  16.             string lineType = (string)args[5].Value;
  17.  
  18.             List<string> attribs = new List<string>();
  19.  
  20.             if (args.Length > 6)
  21.             {
  22.                 for (int i = 7; i < args.Length - 1; i++)
  23.                 {
  24.                     attribs.Add((string)args[i].Value);
  25.                 }
  26.             }
  27.  
  28.             return InsertBlock(blockName, insPoint, scale, layer, colorIndex, lineType, attribs.ToArray());
  29.         }

Another way is to check the arguments contained in the ResultBuffer and make the function return nil in case of a wrong argument.
Here wrong arguments won't throw an error and won't stop the LISP evaluation, so the LISP function which calls (insert ...) should take care of the return value.
Code - C#: [Select]
  1.         [LispFunction("insert")]
  2.         public object InsertOrReturnNil(ResultBuffer resbuf)
  3.         {
  4.             if (resbuf == null)
  5.                 return null;
  6.  
  7.             TypedValue[] args = resbuf.AsArray();
  8.             if (args.Length < 6)
  9.                 return null;
  10.  
  11.             TypedValue tv = args[0];
  12.             if (tv.TypeCode != (int)LispDataType.Text)
  13.                 return null;
  14.             string blockName = (string)tv.Value;
  15.  
  16.             tv = args[1];
  17.             Point3d insPoint;
  18.             if (tv.TypeCode == (int)LispDataType.Point2d)
  19.             {
  20.                 Point2d pt = (Point2d)tv.Value;
  21.                 insPoint = new Point3d(pt.X, pt.Y, 0.0);
  22.             }
  23.             else if (tv.TypeCode == (int)LispDataType.Point3d)
  24.                 insPoint = (Point3d)tv.Value;
  25.             else
  26.                 return null;
  27.  
  28.             tv = args[2];
  29.             double scale;
  30.             if (tv.TypeCode == (int)LispDataType.Int16 ||
  31.                 tv.TypeCode == (int)LispDataType.Int32 ||
  32.                 tv.TypeCode == (int)LispDataType.Double)
  33.                 scale = Convert.ToDouble(tv.Value);
  34.             else
  35.                 return null;
  36.  
  37.             tv = args[3];
  38.             if (tv.TypeCode != (int)LispDataType.Text)
  39.                 return null;
  40.             string layer = (string)tv.Value;
  41.  
  42.             tv = args[4];
  43.             int colorIndex;
  44.             if (tv.TypeCode == (int)LispDataType.Int16 ||
  45.                 tv.TypeCode == (int)LispDataType.Int32)
  46.                 colorIndex = Convert.ToInt32(tv.Value);
  47.             else
  48.                 return null;
  49.  
  50.             tv = args[5];
  51.             if (tv.TypeCode != (int)LispDataType.Text)
  52.                 return null;
  53.             string lineType = (string)tv.Value;
  54.  
  55.             List<string> attribs = new List<string>();
  56.             if (args.Length > 6)
  57.             {
  58.                 tv = args[6];
  59.                 if (tv.TypeCode != (int)LispDataType.ListBegin)
  60.                     return null;
  61.                 for (int i = 7; i < args.Length - 1; i++)
  62.                 {
  63.                     tv = args[i];
  64.                     if (tv.TypeCode != (int)LispDataType.Text)
  65.                         return null;
  66.                     attribs.Add((string)tv.Value);
  67.                 }
  68.             }
  69.  
  70.             return InsertBlock(blockName, insPoint, scale, layer, colorIndex, lineType, attribs.ToArray());
  71.         }

The way I prefer, even I didn't found how to cleanly throw an exception from LispFunction (http://forums.autodesk.com/t5/net/how-to-cleanly-throw-an-exception-from-lispfunction/m-p/4697127/highlight/true#M38204), is to throw a custom .NET Exception, catch it to alert the user and rethrow to stop evaluation.
This could be done by replacing the 'return null' statements the upper code with a 'throw Exception' one and wrapping the whole in a Try/Catch block to manage the exceptions.
A thread  (http://www.theswamp.org/index.php?topic=45841.0)started by Luis 'LE' Esquivel who purposed to build a .NET LispFunction library, brings me to build some wrappers/helpers to make this process more easy to code. You can find the specific LispException definitions and some extension methods further in this thread (here  (http://www.theswamp.org/index.php?topic=45841.msg509867#msg509867)and there (http://www.theswamp.org/index.php?topic=45841.msg510047#msg510047)).
Using these classes allows to write a more safe/concise/readable code.
Code - C#: [Select]
  1.         [LispFunction("insert")]
  2.         public ObjectId SafeInsert(ResultBuffer resbuf)
  3.         {
  4.             return resbuf.CatchApply(rb =>
  5.             {
  6.                 TypedValue[] args = rb.GetAsArray(6, int.MaxValue);
  7.  
  8.                 string blockName = args[0].GetAsString();
  9.  
  10.                 Point3d insPoint = args[1].GetAsPoint();
  11.  
  12.                 double scale = args[2].GetAsNumber();
  13.  
  14.                 string layer = args[3].GetAsString();
  15.  
  16.                 int colorIndex = args[4].GetAsShort();
  17.  
  18.                 string lineType = args[5].GetAsString();
  19.  
  20.                 string[] attribs = null;
  21.  
  22.                 if (args.Length > 6)
  23.                     attribs = args.GetListContent(6).Select(tv => tv.GetAsString()).ToArray();
  24.                 else
  25.                     attribs = new string[0];
  26.  
  27.                 return InsertBlock(blockName, insPoint, scale, layer, colorIndex, lineType, attribs);
  28.             });
  29.         }
Title: Re: Insert Existing Block with attributes
Post by: Kerry on December 03, 2014, 11:54:32 AM

Excellent response gile .. well done.
Title: Re: Insert Existing Block with attributes
Post by: gile on December 03, 2014, 01:37:23 PM
Thanks Kerry.
Title: Re: Insert Existing Block with attributes
Post by: AARYAN on December 03, 2014, 10:26:07 PM
WOW. Thank You So much Mr.Gile.

Since I myself writing a lisp function and passing the correct arguments to it, I believe no error will arise but the way you replied is truly appreciable.

Best Regards.