Author Topic: More C# Extension Methods  (Read 10766 times)

0 Members and 1 Guest are viewing this topic.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
More C# Extension Methods
« on: November 22, 2007, 01:51:00 PM »
Maybe some of these ResultBuffer methods might be useful…if they work  :laugh:

Code: [Select]
namespace LispBase
{
  public static class ResultBufferExtension
  {
    //add
    public static void Add(this ResultBuffer m_this, int typecode)
    {
      m_this.Add(new TypedValue(typecode));
    }
    public static void Add(this ResultBuffer m_this, int typecode, object value)
    {
      m_this.Add(new TypedValue(typecode, value));
    }
    public static void Add(this ResultBuffer m_this, LispDataType type)
    {
      m_this.Add(new TypedValue((int)type));
    }
    public static void Add(this ResultBuffer m_this, LispDataType type, object value)
    {
      m_this.Add(new TypedValue((int)type, value));
    }
    public static void Add(this ResultBuffer m_this, DxfCode type)
    {
      m_this.Add(new TypedValue((int)type));
    }
    public static void Add(this ResultBuffer m_this, DxfCode type, object value)
    {
      m_this.Add(new TypedValue((int)type, value));
    }

    //AddRange
    public static void AddRange(this ResultBuffer m_this, ResultBuffer src)
    {
      if (src == null)
        throw new ArgumentNullException();

      foreach (TypedValue e in src)
        m_this.Add(e);

    }
    public static void AddRange(this ResultBuffer m_this, TypedValue[] src)
    {
      if (src == null)
        throw new ArgumentNullException();

      foreach (TypedValue e in src)
        m_this.Add(e);

    }
    public static void AddRange(this ResultBuffer m_this, IEnumerable<TypedValue> src)
    {
      if (src == null)
        throw new ArgumentNullException();

      foreach (TypedValue e in src)
        m_this.Add(e);

    }

    //Some Lisp stuff .. add what you need
    public static void AddLispT(this ResultBuffer m_this)
    {
      m_this.Add(new TypedValue((int)LispDataType.T_atom));
    }
    public static void AddLispNil(this ResultBuffer m_this)
    {
      m_this.Add(new TypedValue((int)LispDataType.Nil));
    }
    public static void AddLispString(this ResultBuffer m_this, string str)
    {
      m_this.Add(new TypedValue((int)LispDataType.Text, str));
    }
    public static void AddLispDouble(this ResultBuffer m_this, double num)
    {
      m_this.Add(new TypedValue((int)LispDataType.Double, num));
    }
    public static void AddLispInt(this ResultBuffer m_this, int num)
    {
      m_this.Add(new TypedValue((int)LispDataType.Int32, num));
    }
    public static void AddLispErr(this ResultBuffer m_this, string message)
    {
      m_this.Add(new TypedValue((int)LispDataType.Nil));
      m_this.Add(new TypedValue((int)LispDataType.DottedPair));
      m_this.Add(new TypedValue((int)LispDataType.Text, "Error: " + message));
    }

    //
    public static void ForEach(this ResultBuffer m_this, Action<TypedValue> action)
    {
      if (action == null)
        throw new ArgumentNullException();

      foreach (TypedValue e in m_this)
        action(e);
    }

    //
    public static TypedValue GetAt(this ResultBuffer m_this, int index)
    {
      return m_this.AsArray()[index];
    }

    //
    public static int GetCount(this ResultBuffer m_this)
    {
      int cnt = 0;

      foreach (TypedValue e in m_this)
        cnt += 1;

      return cnt;
    }

    //
    public static List<TypedValue> ToList(this ResultBuffer m_this)
    {
      return new List<TypedValue>(m_this.AsArray());
    }

    //add some LinQ
    public static IEnumerable<TypedValue> Where
      (this ResultBuffer m_this, Func<TypedValue, bool> predicate)
    {
      foreach (TypedValue element in m_this)
      {
        if (predicate(element) == true)
          yield return element;
      }
    }

    public static IEnumerable<TypedValue> Select
      (this ResultBuffer m_this, Func<TypedValue, TypedValue> selector)
    {
      foreach (TypedValue element in m_this)
      {
        yield return selector(element);
      }
    }
  }
}

Edit:  added lisp stuff
« Last Edit: November 24, 2007, 12:47:01 AM by Daniel »

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #1 on: November 22, 2007, 09:52:47 PM »
example

Code: [Select]
[LispFunction("test")]
    static public ResultBuffer test(ResultBuffer Args)
    {
      ResultBuffer RetRb = new ResultBuffer();

      try
      {
        Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;

        if (Args == null)
        {
          RetRb.AddLispErr("Null Argument");
          return RetRb;
        }
        if (Args.GetCount() == 0)
        {
          RetRb.AddLispErr("To Few Arguments");
          return RetRb;
        }

        Args.ForEach(p => ed.WriteMessage("\n" + p.Value.ToString()));

        RetRb.AddLispT();

      }
      catch (System.Exception ex)
      {
        RetRb.AddLispErr(ex.Message);
      }
      return RetRb;
    }

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: More C# Extension Methods
« Reply #2 on: November 23, 2007, 04:43:34 PM »

good food for thought Daniel ..
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #3 on: November 24, 2007, 12:54:17 AM »

good food for thought Daniel ..

Thanks Kerry

I added a couple of LinQ methods (original source) so we can query a Resultbuffer  :laugh:

Sample:

Code: [Select]
amespace LispBase
{
  public class Commands
  {
    //(test1 1 1 "Hello")
    [LispFunction("test1")]
    public static ResultBuffer test1(ResultBuffer args)
    {
      ResultBuffer retList = new ResultBuffer();
      try
      {
        Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;

        if (args == null)
        {
          retList.AddLispErr("Null Argument");
          return retList;
        }
        if (args.GetCount() == 0)
        {
          retList.AddLispErr("To Few Arguments");
          return retList;
        }

        Func<TypedValue, bool>
          filter = x => x.TypeCode == (int)LispDataType.Text;

        var rlist = args.Where(filter);

        foreach (var e in rlist)
          retList.AddLispString(e.Value.ToString());

    
        if (retList.GetCount() == 0)
        {
          retList.AddLispNil();
          return retList;
        }

      }
      catch (System.Exception ex)
      {
        retList.AddLispErr(ex.Message);
      }
      return retList;
    }


    //(test2 "s" "a" "s" "a" "ad")
    [LispFunction("test2")]
    public static ResultBuffer test2(ResultBuffer args)
    {
      ResultBuffer retList = new ResultBuffer();
      try
      {

        Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;

        if (args == null)
        {
          retList.AddLispErr("Null Argument");
          return retList;
        }
        if (args.GetCount() == 0)
        {
          retList.AddLispErr("To Few Arguments");
          return retList;
        }

        var query = from f in args
                    where f == new TypedValue((int)LispDataType.Text, "a")
                    select f;

        foreach (var e in query)
          retList.AddLispString((string)e.Value);

        if (retList.GetCount() == 0)
        {
          retList.AddLispNil();
          return retList;
        }

      }
      catch (System.Exception ex)
      {
        retList.AddLispErr(ex.Message);
      }
      return retList;
    }
  }
}

added: All while eating Aussie Black Opal licorice  :|
« Last Edit: November 24, 2007, 01:09:52 AM by Daniel »

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: More C# Extension Methods
« Reply #4 on: November 24, 2007, 02:07:37 AM »
..............
added: All while eating Aussie Black Opal licorice  :|

hmmmmm , yum  !

though I have a preference for Darell Lea Liquorice myself

.. I have a pack on my desk as we speak :-)

exemplifies 'coding for food' hey ?
« Last Edit: November 24, 2007, 02:10:20 AM by Kerry Brown »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #5 on: November 24, 2007, 08:00:13 PM »
Here is another sample, gets the object id’s of the Entities in a selection set with
XData containing a specific value, using our new LinQ enabled Resultbuffer

Code: [Select]
    [CommandMethod("test")]
    static public void test()
    {
      Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

      TypedValue[] values = new TypedValue[]
                     {
                        new TypedValue((short)DxfCode.Start, "LINE") ,
                        new TypedValue((short)DxfCode.XDataStart) ,
                        new TypedValue((short)DxfCode.ExtendedDataRegAppName, "Dan")
                     };

      SelectionFilter filter = new SelectionFilter(values);
      PromptSelectionOptions selopts = new PromptSelectionOptions();
      selopts.MessageForAdding = "Select";
      selopts.AllowDuplicates = false;
      PromptSelectionResult result = ed.GetSelection(selopts, filter);

      if (result.Status == PromptStatus.OK)
      {
        ObjectId[] idarray = result.Value.GetObjectIds();

        List<ObjectId> rlist = new List<ObjectId>();

        Autodesk.AutoCAD.DatabaseServices.TransactionManager tm =
           Application.DocumentManager.MdiActiveDocument.Database.TransactionManager;

        //our filter
        TypedValue Xdatafilter = new TypedValue((int)DxfCode.ExtendedDataAsciiString, "a");

        Transaction tr = tm.StartTransaction();
        try
        {
          foreach (ObjectId id1 in idarray)
          {

            Entity ent = (Entity)tm.GetObject(id1, OpenMode.ForRead, true);
            ResultBuffer xRb = ent.XData;

            //using our Extension method here
            var query = xRb.Where(p => p.Equals(Xdatafilter));

            //or

            // a LinQ Style query
            //var query = from f in xRb
            //            where f == Xdatafilter
            //            select f;

            if (query.Count() != 0)
            {
              rlist.Add(ent.ObjectId);
            }
          }

          //using our Extension method here
          rlist.ForEach(p => ed.WriteMessage("\n" + p.ToString()));
        }
        catch (System.Exception ex)
        {
          ed.WriteMessage(ex.Message);
        }
        finally
        {
          tr.Dispose();
        }
      }
    }

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: More C# Extension Methods
« Reply #6 on: November 27, 2007, 07:48:27 PM »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #7 on: December 03, 2007, 02:36:56 AM »
Just a quick bunch of extension methods attached (still under construction)
.NET 3.5

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: More C# Extension Methods
« Reply #8 on: December 03, 2007, 02:45:56 AM »

The BulgeVertex extension looks handy Daniel.

Thanks, I'll have a peek when things settle down. Looks like fun.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #9 on: December 03, 2007, 03:43:08 AM »
My first goal is just to run through most of the collections and add AddRange(), ForEach(),  Where() and Select()  extension methods,
IMO very handy stuff.

SomeCallMeDave

  • Guest
Re: More C# Extension Methods
« Reply #10 on: December 08, 2007, 06:28:32 PM »
Daniel,
Very cool stuff, even for a C# beginner like me.

Here are my first feeble attempts to extend the polyline class

Code: [Select]
    public static class EntityExtentions
    {
        internal static Double GetRadiusFromParam(this Polyline pline, double pParam)
        {
            if (pline == null)
            {
                throw new ArgumentNullException();
            }//if
            double cbulge = 0.0;
            if (pParam < pline.EndParam)
            {
                cbulge = pline.GetBulgeAt((int)pParam);
            }
            else
            {
                return 0.0;
            }
           
            if (cbulge == 0.0)
            {
                return 0.0;
            }
            // bulge is not Zero and the vertexindex is not at the end so continue...
            Point2d p1 = pline.GetPoint2dAt((int)pParam);
            Point2d p2 = pline.GetPoint2dAt((int)pParam + 1);
            double delta = 4 * Math.Atan(Math.Abs(cbulge));
            double chord = Math.Sqrt(Math.Pow((p1.X-p2.X),2) + Math.Pow((p1.Y-p2.Y),2));
            double radius = Math.Abs(chord / (2 * Math.Sin(delta / 2)));

            return radius;
        }//GetRadiusFromParam

        internal static Point3d GetRadiusPointFromParam(this Polyline pline, double pParam)
        {
            if (pline == null)
            {
                throw new ArgumentNullException();
            }//if
            double cbulge = 0.0;
            if (pParam < pline.EndParam)
            {
                cbulge = pline.GetBulgeAt((int)pParam);
            }
            else
            {
                //not the best choice of return value
                return new Point3d(0, 0, 0);
            }

            if (cbulge == 0.0)
            {
                //not the best choice of return value
                return new Point3d(0, 0, 0);
            }

            // bulge is not Zero and the vertexindex is not at the end so continue...
            Point2d p1 = pline.GetPoint2dAt((int)pParam);
            Point2d p2 = pline.GetPoint2dAt((int)pParam + 1);
            double delta = 4 * Math.Atan(Math.Abs(cbulge));
            double chord = p1.GetDistanceTo(p2);
            double radius = Math.Abs(chord / (2 * Math.Sin(delta / 2)));
            Point3d midp = pline.GetPointAtParameter((int)pParam + 0.5);
            Line l1 = new Line(new Point3d(p1.X, p1.Y, 0), new Point3d(p2.X, p2.Y, 0));
            Point3d midc = l1.GetPointAtParameter(chord/2.0);
            Line l2 = new Line(midp, midc);
            double sf = radius/l2.GetDistanceAtParameter(l2.EndParam);
            Vector3d v1 = midp.GetAsVector();
            Vector3d v2 = midc.GetAsVector();

            Vector3d v3 = v2- v1;
            Vector3d v4 = v3 * sf;


            Vector3d vRadPt = v4.Add(v1);

            Point3d radPt = new Point3d(vRadPt.ToArray());

            return radPt;
        }//GetRadiusPointFromParam
  }


I have a lot to learn, but it's going to be fun. 

Thanks in advance for fielding all my up-coming questions  :)

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #11 on: December 09, 2007, 06:15:57 AM »
Wow! Welcome to the party David!  :laugh:
Nice Coding!

sinc

  • Guest
Re: More C# Extension Methods
« Reply #12 on: December 09, 2007, 10:20:39 AM »
Hey, Dave, check this out.  It is similar to the routine you wrote.  This routine returns a negative radius if the radius is on the "left" side of the polyline.

Code: [Select]
    public struct RadiusPoint
    {
        public double Radius;
        public Point3d Point;
    }

    public class CurveUtil
    {
        public static RadiusPoint SegmentRadiusPoint(Polyline pline, double param)
        {
            RadiusPoint rp = new RadiusPoint();
            double buldge = pline.GetBulgeAt(Convert.ToInt32(param));
            if (Math.Abs(buldge) > 0)
            {
                Vector3d vec = pline.GetSecondDerivative(param);
                int buldgeDir = (buldge > 0) ? -1 : 1;
                rp.Radius = vec.Length * buldgeDir;
                rp.Point = pline.GetPointAtParameter(param).Subtract(vec * buldgeDir);
            }
            return rp;
        }
    }

SomeCallMeDave

  • Guest
Re: More C# Extension Methods
« Reply #13 on: December 10, 2007, 07:41:57 AM »
Wow! Welcome to the party David!  :laugh:
Nice Coding!

Thanks Daniel.  I'm having fun!!!.  But the coding is mostly yours with some of my LISP functions translated to C#  :)

Sinc,  nice function!!!  Much more succinct than mine.   One question -   is Convert.ToInt32(param) preferred over a cast (int)param ?  Are there benefits and/or costs associated with either?

sinc

  • Guest
Re: More C# Extension Methods
« Reply #14 on: December 10, 2007, 08:15:13 AM »
Oh, I doubt it.

I'm still pretty new to C# and .NET, and sometimes I do silly things.   :-D

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #15 on: December 10, 2007, 11:10:36 AM »
Hey, Dave, check this out.  It is similar to the routine you wrote.  This routine returns a negative radius if the radius is on the "left" side of the polyline.

Nice Sinc! here's a full struct for ya (i was board)... :laugh:

Code: [Select]

public struct RadiusPoint
  {
    private Point3d m_point;
    private double m_radius;

    RadiusPoint(Point3d Point, double Radius)
    {
      this.m_point = Point;
      this.m_radius = Radius;
    }

    public Point3d Point
    {
      get
      {
        return this.m_point;
      }
      set
      {
        this.m_point = value;
      }
    }

    public double Radius
    {
      get
      {
        return this.m_radius;
      }
      set
      {
        this.m_radius = value;
      }
    }

    public override int GetHashCode()
    {
      return base.GetHashCode();
    }

    public override bool Equals(object obj)
    {
      return ((obj is RadiusPoint) && (this == ((RadiusPoint)obj)));
    }

    public static bool operator ==(RadiusPoint a, RadiusPoint b)
    {
      if (a.m_point == b.m_point && a.m_radius == b.m_radius)
        return true;
      else
        return false;
    }

    public static bool operator !=(RadiusPoint a, RadiusPoint b)
    {
      if (a.m_point != b.m_point && a.m_radius != b.m_radius)
        return true;
      else
        return false;
    }

    public override string ToString()
    {
      return this.ToString(null);
    }

    public string ToString(IFormatProvider provider)
    {
      object[] args = new object[] { this.m_point, this.m_radius};
      return string.Format(provider, "({0},{1})", args);
    }
  }



Glenn R

  • Guest
Re: More C# Extension Methods
« Reply #16 on: December 10, 2007, 11:10:48 AM »
SCMD,

Neither Convert or an explicit cast is really that different, but you tend to see C types use the explicit cast syntax mostly over the use of Convert.

Be warned though, that explicit casting can cause exceptions to be thrown, but not in this case. What sinc was doing is sometimes called 'data narrowing' - stuffing a larger data type into a smaller one.

For instance, this will give a compiler error:

Code: [Select]
int myint = 30.2;

It will tell you you need an explicit cast and that one exists.
This will not give any warning or error:

Code: [Select]
int myint = (int)30.2;

The explicit cast is as if you're telling the compiler - 'hey compiler! I think I know what I'm doing!'

Cheers,
Glenn.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #17 on: December 10, 2007, 11:38:07 AM »
Actually you will get different answers in some cases, as Convert.ToInt32() will attempt to round where a explicit cast won’t

int i = (int)30.7;                     // = 30
int j = Convert.ToInt32(30.7); // = 31

SomeCallMeDave

  • Guest
Re: More C# Extension Methods
« Reply #18 on: December 10, 2007, 11:40:43 AM »
Glenn and Daniel,
Thanks for the explanation.

Daniel, I'm glad you pointed out the rounding.  Particularly when dealing with polyline params I would not want rounding.

Glenn R

  • Guest
Re: More C# Extension Methods
« Reply #19 on: December 10, 2007, 11:43:10 AM »
I forgot to mention the part apart rounding Dan - thanks for catching that.
The explicit cast is doing what I said though - narrowing the data field/type, so it will truncate the fractional part in this case, whereas Convert will round.

Cheers,
Glenn.

Glenn R

  • Guest
Re: More C# Extension Methods
« Reply #20 on: December 10, 2007, 12:01:16 PM »
sinc,

What does your function parameter 'double param' represent? Is it a previously derived 'curve parameter'?

Cheers,
Glenn.

sinc

  • Guest
Re: More C# Extension Methods
« Reply #21 on: December 10, 2007, 01:05:55 PM »
It is the parameter that starts the segment you want to get the radius for.

This is a utility routine that is used by other routines, and they only made the call as they "walked the polyline".  Therefore, in my code, it was always being called with a value that was an even integer (i.e., a parameter at a polyline vertex), even though the type is a double.  So I suspect I should change my code to use a cast to an int, yielding a truncation, rather than the Convert.Int32, which rounds the value.  Since my code never calls this routine with anything but integer values, I didn't notice this problem.

This routine is now part of my SincpacC3D's CurveUtil class, but I think it's a recent addition that isn't in the version posted to the web page.  I'm almost done with a major revamp of the SincpacC3D, and once it hits a relatively-stable point again, I'll post a new version of the source code that contains the revamped library classes.  The bulk of the new SincpacC3D commands will remain proprietary this time around, but I plan on releasing the base AutocadUtilites and Civil3DUtilities under a completely free license.  Eventually, I hope to break that out into a full-fledged free Autocad/Civil-3D SDK that removes much of the time-consuming "grunge" involved with working with Autodesk's low-level APIs.

And thanks for the update, Daniel...  I think I'll take what you did, and expand it a little.  Right now, there is no real test for a line segment.  Basically, for a line segment, the function returns a RadiusPoint with Radius==0.  I'd rather add an explicit flag for this, rather than having code which checks for Radius==0, which is kind of ambiguous.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8764
  • AKA Daniel
Re: More C# Extension Methods
« Reply #22 on: December 10, 2007, 01:41:24 PM »
And thanks for the update, Daniel...  I think I'll take what you did, and expand it a little.  Right now, there is no real test for a line segment.  Basically, for a line segment, the function returns a RadiusPoint with Radius==0.  I'd rather add an explicit flag for this, rather than having code which checks for Radius==0, which is kind of ambiguous.

Cool hope you can use some of it. The == and != operators are just to test the equality of the object,
so if in the future you need to do a if(RadiusPoint == RadiusPoint) nothing else

Glenn R

  • Guest
Re: More C# Extension Methods
« Reply #23 on: December 10, 2007, 03:54:12 PM »
sinc,

The reason I asked was because I didn't think the linear equation used for curves in AutoCAD, produced parameter's that directly matched a polyline vertex index count.

How about this as a basic talking point scenario:

  • ->straight line segment->[1]->curve->[2]->straight line segment->[3]


This is a 4 point polyline - maybe a simple road alignment. The numbers in brackets are the indices for the polyline.
So if you retrieved a parameter that lied between [1] and [2], even with converting to an int, I did not think that would give the correct bulge using this:

Code: [Select]
double buldge = pline.GetBulgeAt(Convert.ToInt32(param));

...as GetBulge wants a vertex index...does that make sense?

Cheers,
Glenn.

SomeCallMeDave

  • Guest
Re: More C# Extension Methods
« Reply #24 on: December 10, 2007, 04:09:12 PM »
Glenn,
All my testing shows that one gets the correct bulge (if the value was truncated and not rounded).  The parameter at vertex 1 is 1.000000.  I have yet to come across a situation where that relationship doesn't hold (at least for simple, planar lwpoly - road alignments to be precise).  I know that sort of evidence can be tricky to use.  And I have seen conversation (read arguments) elsewhere discussing this topic.


 Stacked vertices can be a problem. I always check for those before starting any parameter based calcs.

Glenn R

  • Guest
Re: More C# Extension Methods
« Reply #25 on: December 10, 2007, 04:39:34 PM »
SCMD,

Thanks for that - nice to know...I just didn't think the premise held true.

Cheers,
Glenn.