Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - (gile)

Pages: [1] 2
1
Blocks / Synchronize attributes (ATTSYNC)
« on: November 23, 2013, 05:43:42 PM »
Hi,

This is a simple and 'brute force' method to mimic the ATTSYNC command. It erases the existing AttributeReferences from the inserted BlockReferences and adds them new ones from the BlockTableRecord AttributeDefinitions.

The SynchronizeAttributes() method is defined as an extension method for the BlockTableRecord type so that it can be called as an instance method of this type.
Using example (assuming btr is a BlocTableRecord instance) : btr.SynchronizeAttributes()

<EDIT: works now w/ mtext attributes>
<EDIT: corrected an issue w/ attributes in dynamic blocks>
<EDIT: corrected an issue w/ constant attributes>

 C# code
Code: [Select]
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using AcRx = Autodesk.AutoCAD.Runtime;

namespace Autodesk.AutoCAD.DatabaseServices
{
    public static class ExtensionMethods
    {
        static RXClass attDefClass = RXClass.GetClass(typeof(AttributeDefinition));

        public static void SynchronizeAttributes(this BlockTableRecord target)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            Transaction tr = target.Database.TransactionManager.TopTransaction;
            if (tr == null)
                throw new AcRx.Exception(ErrorStatus.NoActiveTransactions);
            List<AttributeDefinition> attDefs = target.GetAttributes(tr);
            foreach (ObjectId id in target.GetBlockReferenceIds(true, false))
            {
                BlockReference br = (BlockReference)tr.GetObject(id, OpenMode.ForWrite);
                br.ResetAttributes(attDefs, tr);
            }
            if (target.IsDynamicBlock)
            {
                target.UpdateAnonymousBlocks();
                foreach (ObjectId id in target.GetAnonymousBlockIds())
                {
                    BlockTableRecord btr = (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
                    attDefs = btr.GetAttributes(tr);
                    foreach (ObjectId brId in btr.GetBlockReferenceIds(true, false))
                    {
                        BlockReference br = (BlockReference)tr.GetObject(brId, OpenMode.ForWrite);
                        br.ResetAttributes(attDefs, tr);
                    }
                }
            }
        }

        private static List<AttributeDefinition> GetAttributes(this BlockTableRecord target, Transaction tr)
        {
            List<AttributeDefinition> attDefs = new List<AttributeDefinition>();
            foreach (ObjectId id in target)
            {
                if (id.ObjectClass == attDefClass)
                {
                    AttributeDefinition attDef = (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
                    attDefs.Add(attDef);
                }
            }
            return attDefs;
        }

        private static void ResetAttributes(this BlockReference br, List<AttributeDefinition> attDefs, Transaction tr)
        {
            Dictionary<string, string> attValues = new Dictionary<string, string>();
            foreach (ObjectId id in br.AttributeCollection)
            {
                if (!id.IsErased)
                {
                    AttributeReference attRef = (AttributeReference)tr.GetObject(id, OpenMode.ForWrite);
                    attValues.Add(attRef.Tag,
                        attRef.IsMTextAttribute ? attRef.MTextAttribute.Contents : attRef.TextString);
                    attRef.Erase();
                }
            }
            foreach (AttributeDefinition attDef in attDefs)
            {
                AttributeReference attRef = new AttributeReference();
                attRef.SetAttributeFromBlock(attDef, br.BlockTransform);
                if (attDef.Constant)
                {
                    attRef.TextString = attDef.IsMTextAttributeDefinition ?
                        attDef.MTextAttributeDefinition.Contents :
                        attDef.TextString;
                }
                else if (attValues.ContainsKey(attRef.Tag))
                {
                    attRef.TextString = attValues[attRef.Tag];
                }
                br.AttributeCollection.AppendAttribute(attRef);
                tr.AddNewlyCreatedDBObject(attRef, true);
            }
        }
    }
}

VB code (have to clear the Root Namespace in the project to add these extension methods to Autodesk.AutoCAD.DatabaseServices)
Code: [Select]
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Runtime

Namespace Autodesk.AutoCAD.DatabaseServices
    Public Module ExtensionMethods

        Dim attDefClass As RXClass = RXClass.GetClass(GetType(AttributeDefinition))

        <System.Runtime.CompilerServices.Extension> _
        Public Sub SynchronizeAttributes(target As BlockTableRecord)
            If target Is Nothing Then
                Throw New ArgumentNullException("target")
            End If

            Dim tr As Transaction = target.Database.TransactionManager.TopTransaction
            If tr Is Nothing Then
                Throw New Exception(ErrorStatus.NoActiveTransactions)
            End If

            Dim attDefs As List(Of AttributeDefinition) = target.GetAttributes(tr)
            For Each id As ObjectId In target.GetBlockReferenceIds(True, False)
                Dim br As BlockReference = _
                    DirectCast(tr.GetObject(id, OpenMode.ForWrite), BlockReference)
                br.ResetAttributes(attDefs, tr)
            Next
            If target.IsDynamicBlock Then
                target.UpdateAnonymousBlocks()
                For Each id As ObjectId In target.GetAnonymousBlockIds()
                    Dim btr As BlockTableRecord = _
                        DirectCast(tr.GetObject(id, OpenMode.ForRead), BlockTableRecord)
                    attDefs = btr.GetAttributes(tr)
                    For Each brId As ObjectId In btr.GetBlockReferenceIds(True, False)
                        Dim br As BlockReference = _
                            DirectCast(tr.GetObject(brId, OpenMode.ForWrite), BlockReference)
                        br.ResetAttributes(attDefs, tr)
                    Next
                Next
            End If
        End Sub

        <System.Runtime.CompilerServices.Extension> _
        Private Function GetAttributes(target As BlockTableRecord, tr As Transaction) As List(Of AttributeDefinition)
            Dim attdefs As List(Of AttributeDefinition) = New List(Of AttributeDefinition)
            For Each id As ObjectId In target
                If id.ObjectClass = attDefClass Then
                    Dim attDef As AttributeDefinition = _
                        DirectCast(tr.GetObject(id, OpenMode.ForRead), AttributeDefinition)
                    attdefs.Add(attDef)
                End If
            Next
            Return attdefs
        End Function

        <System.Runtime.CompilerServices.Extension> _
        Private Sub ResetAttributes(br As BlockReference, attDefs As List(Of AttributeDefinition), tr As Transaction)
            Dim attValues As New Dictionary(Of String, String)()
            For Each id As ObjectId In br.AttributeCollection
                If Not id.IsErased Then
                    Dim attRef As AttributeReference = _
                        DirectCast(tr.GetObject(id, OpenMode.ForWrite), AttributeReference)
                    attValues.Add( _
                        attRef.Tag, _
                        If(attRef.IsMTextAttribute, attRef.MTextAttribute.Contents, attRef.TextString))
                    attRef.Erase()
                End If
            Next
            For Each attDef As AttributeDefinition In attDefs
                Dim attRef As New AttributeReference()
                attRef.SetAttributeFromBlock(attDef, br.BlockTransform)
                If attDef.Constant Then
                    attRef.TextString = If(attDef.IsMTextAttributeDefinition, _
                                           attDef.MTextAttributeDefinition.Contents, _
                                           attDef.TextString)
                Else If attValues IsNot Nothing AndAlso attValues.ContainsKey(attDef.Tag) Then
                    attRef.TextString = attValues(attDef.Tag.ToUpper())
                End If
                br.AttributeCollection.AppendAttribute(attRef)
                tr.AddNewlyCreatedDBObject(attRef, True)
            Next
        End Sub

    End Module

End Namespace

2
Display / Block Count in a ToolTip
« on: November 08, 2013, 04:15:07 PM »
Hi,

Here's an example of Editor.PointMonitor event handling.
A toolTip displays the number of inserts in the current space of the block reference which the pickbox is hovering over.

C#
Code: [Select]
// (C) Copyright 2012 by Gilles Chanteau
//
using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: ExtensionApplication(typeof(Gile.BlockCountExtensionApplication.BlockCountToolTip))]

namespace Gile.BlockCountExtensionApplication
{
    public class BlockCountToolTip : IExtensionApplication
    {
        private static DocumentCollection docs;
        private static RXClass blockRefClass;

        public void Initialize()
        {
            docs = AcAp.DocumentManager;
            blockRefClass = RXClass.GetClass(typeof(BlockReference));
            docs.DocumentCreated += onDocumentCreated;
            foreach (Document doc in docs)
            {
                doc.Editor.PointMonitor += onPointMonitor;
            }
        }

        public void Terminate() { }

        void onDocumentCreated(object sender, DocumentCollectionEventArgs e)
        {
            if (e.Document != null)
                e.Document.Editor.PointMonitor += onPointMonitor;
        }

        void onPointMonitor(object sender, PointMonitorEventArgs e)
        {
            if ((e.Context.History & PointHistoryBits.FromKeyboard) == PointHistoryBits.FromKeyboard)
                return;

            FullSubentityPath[] paths = e.Context.GetPickedEntities();

            if (paths == null || paths.Length == 0)
                return;

            ObjectId[] ids = paths[0].GetObjectIds();

            if (ids == null || ids.Length == 0)
                return;

            ObjectId id = ids[0];

            if (id.IsValid && id.ObjectClass == blockRefClass)
            {
                Database db = id.Database;
                int cnt = 0;
                using (Transaction tr = db.TransactionManager.StartOpenCloseTransaction())
                {
                    BlockReference br = (BlockReference)tr.GetObject(id, OpenMode.ForRead);
                    bool isDyn = br.IsDynamicBlock;
                    BlockTableRecord btr = isDyn ?
                        (BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead) :
                        (BlockTableRecord)tr.GetObject(br.BlockTableRecord, OpenMode.ForRead);
                    string name = btr.Name;
                    ObjectId cSpaceId = db.CurrentSpaceId;
                    foreach (ObjectId brId in btr.GetBlockReferenceIds(true, false))
                    {
                        DBObject bRef = tr.GetObject(brId, OpenMode.ForRead);
                        if (bRef.OwnerId == cSpaceId)
                            cnt++;
                    }
                    if (isDyn)
                    {
                        foreach (ObjectId anonId in btr.GetAnonymousBlockIds())
                        {
                            BlockTableRecord anonBtr = (BlockTableRecord)tr.GetObject(anonId, OpenMode.ForRead);
                            foreach (ObjectId brId in anonBtr.GetBlockReferenceIds(true, false))
                            {
                                DBObject bRef = tr.GetObject(brId, OpenMode.ForRead);
                                if (bRef.OwnerId == cSpaceId)
                                    cnt++;
                            }
                        }
                    }
                    e.AppendToolTipText(String.Format("'{0}' {1}\n{2} insert{3}\n",
                        btr.Name,
                        isDyn ? "(dynamic)" : "",
                        cnt,
                        cnt > 1 ? "s" : ""));
                    tr.Commit();
                }
            }
        }
    }
}

F#
Code: [Select]
// (C) Copyright 2012 by Gilles Chanteau
//
module Gile.BlockCount

open System
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Runtime

let docMan = Application.DocumentManager
let blkRefClass = RXClass.GetClass(typeof<BlockReference>)

type BCountToolTip() =
    let pointMonitor (e: PointMonitorEventArgs) =
        if (e.Context.History &&& PointHistoryBits.FromKeyboard) <> PointHistoryBits.FromKeyboard then
            let paths = e.Context.GetPickedEntities()
            if paths <> null && paths.Length > 0 then
                let ids = paths.[0].GetObjectIds()
                if ids <> null && ids.Length > 0 then
                    let id  = ids.[0]
                    if id.IsValid && id.ObjectClass = blkRefClass then
                        let db = id.Database
                        let spaceId = db.CurrentSpaceId
                        use tr = db.TransactionManager.StartOpenCloseTransaction()

                        let countRefs (btr: BlockTableRecord) =
                            btr.GetBlockReferenceIds(true, false)
                            |> Seq.cast
                            |> Seq.filter(fun id -> tr.GetObject(id, OpenMode.ForRead).OwnerId = spaceId)
                            |> Seq.length

                        let br = tr.GetObject(id, OpenMode.ForRead) :?> BlockReference
                        let isDyn = br.IsDynamicBlock
                        let btrId = if isDyn then br.DynamicBlockTableRecord else br.BlockTableRecord
                        let btr = tr.GetObject(btrId, OpenMode.ForRead) :?> BlockTableRecord
                        let cnt =
                            countRefs btr +
                            if isDyn then
                                seq { for id in btr.GetAnonymousBlockIds() ->
                                        tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord |> countRefs }
                                |> Seq.sum
                            else 0
                        e.AppendToolTipText(String.Format("'{0}' {1}\n{2} insert{3}\n",
                                                            btr.Name,
                                                            (if isDyn then "(dynamic)" else ""),
                                                            cnt,
                                                            if cnt > 1 then "s" else ""))
                        tr.Commit()

    interface IExtensionApplication with
        member x.Initialize() =
            docMan.DocumentCreated.AddHandler(fun o e ->
                if e.Document <> null then
                    e.Document.Editor.PointMonitor.AddHandler(fun o e -> pointMonitor e))
            docMan.DocumentToBeDestroyed.AddHandler(fun o e ->
                if e.Document <> null then
                    e.Document.Editor.PointMonitor.RemoveHandler(fun o e -> pointMonitor e))
            docMan
            |> Seq.cast<Document>
            |> Seq.iter(fun doc -> doc.Editor.PointMonitor.AddHandler(fun o e -> pointMonitor e))
        member x.Terminate() = ()

3
Math and Geometry / Circle tangents
« on: September 14, 2013, 06:13:46 PM »
Hi,

Two methods which extents the CircularArc3d type to find tangents between to a point or another instance of CircularArc3d.

Code: [Select]
    using System;
    using Autodesk.AutoCAD.Geometry;
     
    namespace GeometryExtensions
    {
       /// <summary>
       /// Tangent type enum
       /// </summary>
       [Flags]
       public enum TangentType { Inner = 1, Outer = 2 }
     
       /// <summary>
       /// Provides extension methods for the CircularArc3d class.
       /// </summary>
       public static class CircularArc3dExtensions
       {
           /// <summary>
           /// Returns the tangents between the active CircularArc3d instance complete circle and another one.
           /// </summary>
           /// <remarks>
           /// Tangents start points are on the object to which this method applies, end points on the one passed as argument.
           /// Tangents are always returned in the same order: outer tangents before inner tangents, and for both,
           /// the tangent on the left side of the line from this circular arc center to the other one before the one on the right side.
           /// </remarks>
           /// <param name="arc">The object to which this method applies.</param>
           /// <param name="other">The CircularArc3d to which searched for tangents.</param>
           /// <param name="flags">An enum value specifying which type of tangent is returned.</param>
           /// <returns>An array of LineSegment3d representing the tangents (maybe 2 or 4) or null if there is none.</returns>
           /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
           public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, CircularArc3d other, TangentType flags)
           {
               // check if circles lies on the same plane
               Vector3d normal = arc.Normal;
               double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
               double elev2 = other.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
               if (!(normal.IsParallelTo(other.Normal) &&
                   Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint))
                   throw new Autodesk.AutoCAD.Runtime.Exception(
                       Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
     
               // check if a circle is inside the other
               double dist = arc.Center.DistanceTo(other.Center);
               if (dist - Math.Abs(arc.Radius - other.Radius) <= Tolerance.Global.EqualPoint)
                   return null;
     
               // check if circles overlap
               bool overlap = arc.Radius + other.Radius >= dist;
               if (overlap && flags == TangentType.Inner)
                   return null;
     
               CircularArc3d tmp1, tmp2;
               Point3d[] inters;
               Vector3d vec1, vec2, vec = other.Center - arc.Center;
               int i, j;
               LineSegment3d[] result = new LineSegment3d[(int)flags == 3 && !overlap ? 4 : 2];
     
               // outer tangents
               if (flags.HasFlag(TangentType.Outer))
               {
                   if (arc.Radius == other.Radius)
                   {
                       Line3d perp = new Line3d(arc.Center, vec.CrossProduct(normal));
                       inters = arc.IntersectWith(perp);
                       vec1 = (inters[0] - arc.Center).GetNormal();
                       vec2 = (inters[1] - arc.Center).GetNormal();
                       i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1;
                       j = i ^ 1;
                       result[i] = new LineSegment3d(inters[0], inters[0] + vec);
                       result[j] = new LineSegment3d(inters[1], inters[1] + vec);
                   }
                   else
                   {
                       Point3d center = arc.Radius < other.Radius ? other.Center : arc.Center;
                       tmp1 = new CircularArc3d(center, normal, Math.Abs(arc.Radius - other.Radius));
                       tmp2 = new CircularArc3d(arc.Center + vec / 2.0, normal, dist / 2.0);
                       inters = tmp1.IntersectWith(tmp2);
                       vec1 = (inters[0] - center).GetNormal();
                       vec2 = (inters[1] - center).GetNormal();
                       i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1;
                       j = i ^ 1;
                       result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1 * other.Radius);
                       result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2 * other.Radius);
                   }
               }
     
               // inner tangents
               if (flags.HasFlag(TangentType.Inner) && !overlap)
               {
                   double ratio = (arc.Radius / (arc.Radius + other.Radius)) / 2.0;
                   tmp1 = new CircularArc3d(arc.Center + vec * ratio, normal, dist * ratio);
                   inters = arc.IntersectWith(tmp1);
                   vec1 = (inters[0] - arc.Center).GetNormal();
                   vec2 = (inters[1] - arc.Center).GetNormal();
                   i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 2 : 3;
                   j = i == 2 ? 3 : 2;
                   result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1.Negate() * other.Radius);
                   result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2.Negate() * other.Radius);
               }
               return result;
           }
     
           /// <summary>
           /// Returns the tangents between the active CircularArc3d instance complete circle and a point.
           /// </summary>
           /// <remarks>
           /// Tangents start points are on the object to which this method applies, end points on the point passed as argument.
           /// Tangents are always returned in the same order: the tangent on the left side of the line from the circular arc center
           /// to the point before the one on the right side.
           /// </remarks>
           /// <param name="arc">The object to which this method applies.</param>
           /// <param name="pt">The Point3d to which tangents are searched</param>
           /// <returns>An array of LineSegement3d representing the tangents (2) or null if there is none.</returns>
           /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
           public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, Point3d pt)
           {
               // check if circle and point lies on the plane
               Vector3d normal = arc.Normal;
               double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
               double elev2 = pt.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
               if (Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint)
                   throw new Autodesk.AutoCAD.Runtime.Exception(
                       Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
     
               // check if the point is inside the circle
               Point3d center = arc.Center;
               if (pt.DistanceTo(center) <= arc.Radius)
                   return null;
     
               Vector3d vec = pt.GetVectorTo(center) / 2.0;
               CircularArc3d tmp = new CircularArc3d(pt + vec, arc.Normal, vec.Length);
               Point3d[] inters = arc.IntersectWith(tmp);
               LineSegment3d[] result = new LineSegment3d[2];
               int i = vec.GetAngleTo(inters[0] - center, normal) < vec.GetAngleTo(inters[1] - center, normal) ? 0 : 1;
               int j = i ^ 1;
               result[i] = new LineSegment3d(inters[0], pt);
               result[j] = new LineSegment3d(inters[1], pt);
               return result;
           }
       }
    }
     

A (maybe usefull) command example which draws a closed polyline along the outer tangents and the trimmed selected circles.

Code: [Select]
            [CommandMethod("JoinCircles", CommandFlags.Modal)]
           public void JoinCircles()
           {
               Document doc = Application.DocumentManager.MdiActiveDocument;
               Database db = doc.Database;
               Editor ed = doc.Editor;
     
               PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: ");
               peo.SetRejectMessage("Only a circle.");
               peo.AddAllowedClass(typeof(Circle), true);
               PromptEntityResult per = ed.GetEntity(peo);
               if (per.Status != PromptStatus.OK)
                   return;
               ObjectId id1 = per.ObjectId;
     
               peo.Message = "\nSelect another circle: ";
               ObjectId id2;
               while (true)
               {
                   per = ed.GetEntity(peo);
                   if (per.Status != PromptStatus.OK)
                       return;
                   id2 = per.ObjectId;
                   if (id1 == id2)
                       ed.WriteMessage("\nThe second circle is the same as the first one.");
                   else break;
               }
     
               try
               {
                   using (Transaction tr = db.TransactionManager.StartTransaction())
                   {
                       Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead);
                       Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead);
                       CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius);
                       CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius);
                       LineSegment3d[] lines = ca1.GetTangentsTo(ca2, TangentType.Outer);
                       if (lines != null)
                       {
                           BlockTableRecord btr =
                               (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                           Vector3d vec = c1.Center.GetVectorTo(c2.Center);
                           Plane plane = new Plane(Point3d.Origin, c1.Normal);
                           double a1 = vec.GetAngleTo(ca1.Center.GetVectorTo(lines[1].StartPoint), ca1.Normal) -
                               vec.GetAngleTo(ca1.Center.GetVectorTo(lines[0].StartPoint), ca1.Normal);
                           double a2 = vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[0].EndPoint), ca1.Normal) -
                               vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[1].EndPoint), ca1.Normal);
                           Polyline pline = new Polyline(4);
                           pline.AddVertexAt(0, lines[0].StartPoint.Convert2d(plane), Math.Tan(a1 / 4.0), 0.0, 0.0);
                           pline.AddVertexAt(1, lines[1].StartPoint.Convert2d(plane), 0.0, 0.0, 0.0);
                           pline.AddVertexAt(2, lines[1].EndPoint.Convert2d(plane), Math.Tan(a2 / 4.0), 0.0, 0.0);
                           pline.AddVertexAt(3, lines[0].EndPoint.Convert2d(plane), 0.0, 0.0, 0.0);
                           pline.Closed = true;
                           pline.Normal = c1.Normal;
                           pline.Elevation = c1.Center.TransformBy(Matrix3d.WorldToPlane(c1.Normal)).Z;
                           btr.AppendEntity(pline);
                           tr.AddNewlyCreatedDBObject(pline, true);
                           c1.UpgradeOpen();
                           c2.UpgradeOpen();
                           c1.Erase();
                           c2.Erase();
                       }
                       tr.Commit();
                   }
               }
               catch (System.Exception exn)
               {
                   ed.WriteMessage("\nError: " + exn.Message);
               }
           }

4
Display / predefined views
« on: August 14, 2013, 08:21:16 PM »
Hi,

Here's a method to set orthogonal or isometric views.
The zoom is equivalent to a zoom extents plus a zoom 0.8X.

The following code defines also 10 commands to set predefined views with the numpad.

Code: [Select]
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(ViewSample.CommandMethods))]

namespace ViewSample
{
    public class CommandMethods
    {
        enum ViewDirection { Top, Bottom, Front, Back, Left, Right, SeIso, SwIso, NeIso, NwIso }

        private void SetView(ViewDirection vDir)
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            Vector3d viewDir = new Vector3d();
            switch (vDir)
            {
                case ViewDirection.Top:
                    viewDir = Vector3d.ZAxis; break;
                case ViewDirection.Bottom:
                    viewDir = Vector3d.ZAxis.Negate(); break;
                case ViewDirection.Front:
                    viewDir = Vector3d.YAxis.Negate(); break;
                case ViewDirection.Back:
                    viewDir = Vector3d.YAxis; break;
                case ViewDirection.Left:
                    viewDir = Vector3d.XAxis.Negate(); break;
                case ViewDirection.Right:
                    viewDir = Vector3d.XAxis; break;
                case ViewDirection.SeIso:
                    viewDir = new Vector3d(1.0, -1.0, 1.0); break;
                case ViewDirection.SwIso:
                    viewDir = new Vector3d(-1.0, -1.0, 1.0); break;
                case ViewDirection.NeIso:
                    viewDir = new Vector3d(1.0, 1.0, 1.0); break;
                case ViewDirection.NwIso:
                    viewDir = new Vector3d(-1.0, 1.0, 1.0); break;
            }

            db.UpdateExt(true);
            Extents3d extents = db.TileMode ?
                new Extents3d(db.Extmin, db.Extmax) :
                (int)Application.GetSystemVariable("CVPORT") == 1 ?
                    new Extents3d(db.Pextmin, db.Pextmax) :
                    new Extents3d(db.Extmin, db.Extmax);

            using (Transaction tr = db.TransactionManager.StartTransaction())
            using (ViewTableRecord view = ed.GetCurrentView())
            {
                Matrix3d viewTransform =
                    Matrix3d.PlaneToWorld(viewDir)
                    .PreMultiplyBy(Matrix3d.Displacement(view.Target - Point3d.Origin))
                    .PreMultiplyBy(Matrix3d.Rotation(-view.ViewTwist, view.ViewDirection, view.Target))
                    .Inverse();

                extents.TransformBy(viewTransform);

                view.ViewDirection = viewDir;
                view.Width = (extents.MaxPoint.X - extents.MinPoint.X) * 1.25;
                view.Height = (extents.MaxPoint.Y - extents.MinPoint.Y) * 1.25;
                view.CenterPoint = new Point2d(
                    (extents.MinPoint.X + extents.MaxPoint.X) / 2.0,
                    (extents.MinPoint.Y + extents.MaxPoint.Y) / 2.0);
                ed.SetCurrentView(view);
                tr.Commit();
            }
        }

        [CommandMethod("0")]
        public void ViewBottom()
        {
            SetView(ViewDirection.Bottom);
        }

        [CommandMethod("1")]
        public void ViewSouthWestIso()
        {
            SetView(ViewDirection.SwIso);
        }

        [CommandMethod("2")]
        public void ViewFront()
        {
            SetView(ViewDirection.Front);
        }

        [CommandMethod("3")]
        public void ViewSeIso()
        {
            SetView(ViewDirection.SeIso);
        }

        [CommandMethod("4")]
        public void ViewLeft()
        {
            SetView(ViewDirection.Left);
        }

        [CommandMethod("5")]
        public void ViewTop()
        {
            SetView(ViewDirection.Top);
        }

        [CommandMethod("6")]
        public void ViewSouthEastIso()
        {
            SetView(ViewDirection.Right);
        }

        [CommandMethod("7")]
        public void ViewNorthWestIso()
        {
            SetView(ViewDirection.NwIso);
        }

        [CommandMethod("8")]
        public void ViewBack()
        {
            SetView(ViewDirection.Back);
        }

        [CommandMethod("9")]
        public void ViewNorthEastIso()
        {
            SetView(ViewDirection.NeIso);
        }
    }
}

5
Selection sets / Change filtering criteria during the selection
« on: June 30, 2013, 10:04:34 AM »
This is just a example to show how it is possible to change the filtering criteria during the selection using Editor.SelectionAdded event.

In this example, the selection starts filtering lines and allows the user to change the filtered entities to circles or polylines by specifying keywords.

C#
Code: [Select]
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

namespace SelectionSample
{
    public class CommandMethods
    {
        private string keyWord;

        [CommandMethod("Test")]
        public void Test()
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

            this.keyWord = "LIne";

            PromptSelectionOptions opt = new PromptSelectionOptions();
            opt.SetKeywords("[CIrcle/LIne/POlyline]", "CIrcle LIne POlyline");
            opt.MessageForAdding = "\nSelect objects or " + opt.Keywords.GetDisplayString(true);
            opt.KeywordInput += onKeywordInput;

            ed.SelectionAdded += onSelectionAdded;
            PromptSelectionResult psr = ed.GetSelection(opt);
            ed.SelectionAdded -= onSelectionAdded;
            if (psr.Status == PromptStatus.OK)
                ed.SetImpliedSelection(psr.Value);
        }

        private void onKeywordInput(object sender, SelectionTextInputEventArgs e)
        {
            this.keyWord = e.Input;
        }

        private void onSelectionAdded(object sender, SelectionAddedEventArgs e)
        {
            RXClass rxc;
            switch (this.keyWord)
            {
                case "POlyline": rxc = RXClass.GetClass(typeof(Polyline)); break;
                case "CIrcle": rxc = RXClass.GetClass(typeof(Circle)); break;
                default: rxc = RXClass.GetClass(typeof(Line)); break;
            }
            ObjectId[] ids = e.AddedObjects.GetObjectIds();
            for (int i = 0; i < ids.Length; i++)
            {
                if (ids[i].ObjectClass != rxc)
                    e.Remove(i);
            }
        }
    }
}

VB
Code: [Select]
Imports System.Collections.Generic
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Runtime

Namespace SelectionSample

    Public Class CommandMethods

        Private keyWord As String

        <CommandMethod("Test")> _
        Public Sub Test()
            Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor

            Me.keyWord = "LIne"

            Dim opt As New PromptSelectionOptions()
            opt.SetKeywords("[CIrcle/LIne/POlyline]", "CIrcle LIne POlyline")
            opt.MessageForAdding = vbLf & "Select objects or " & opt.Keywords.GetDisplayString(True)
            AddHandler opt.KeywordInput, AddressOf onKeywordInput

            AddHandler ed.SelectionAdded, AddressOf onSelectionAdded
            Dim psr As PromptSelectionResult = ed.GetSelection(opt)
            RemoveHandler ed.SelectionAdded, AddressOf onSelectionAdded
            If psr.Status = PromptStatus.OK Then
                ed.SetImpliedSelection(psr.Value)
            End If
        End Sub

        Private Sub onKeywordInput(sender As Object, e As SelectionTextInputEventArgs)
            Me.keyWord = e.Input
        End Sub

        Private Sub onSelectionAdded(sender As Object, e As SelectionAddedEventArgs)
            Dim rxc As RXClass
            Select Case Me.keyWord
                Case "POlyline"
                    rxc = RXClass.GetClass(GetType(Polyline))
                    Exit Select
                Case "CIrcle"
                    rxc = RXClass.GetClass(GetType(Circle))
                    Exit Select
                Case Else
                    rxc = RXClass.GetClass(GetType(Line))
                    Exit Select
            End Select
            Dim ids As ObjectId() = e.AddedObjects.GetObjectIds()
            For i As Integer = 0 To ids.Length - 1
                If ids(i).ObjectClass <> rxc Then
                    e.Remove(i)
                End If
            Next
        End Sub

    End Class

End Namespace

F#
Code: [Select]
module SelectionSample

open System.Collections.Generic
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Runtime

[<CommandMethod("Test")>]
let test () =
    let ed = Application.DocumentManager.MdiActiveDocument.Editor

    let keyWord = ref "LIne"

    let opt = new PromptSelectionOptions()
    opt.SetKeywords("[CIrcle/LIne/POlyline]", "CIrcle LIne POlyline")
    opt.MessageForAdding <- "\nSelect objects or " + opt.Keywords.GetDisplayString(true)
    opt.KeywordInput.Add(fun (e: SelectionTextInputEventArgs) -> keyWord := e.Input)

    let onSelectionAdded =
        new SelectionAddedEventHandler(fun _ e ->
            let rxc =
                RXClass.GetClass(
                    match !keyWord with
                    | "CIrcle"   -> typeof<Circle>
                    | "POlyline" -> typeof<Polyline>
                    | _          -> typeof<Line>)
            e.AddedObjects.GetObjectIds()
            |> Array.iteri(fun i id -> if id.ObjectClass <> rxc then e.Remove(i)))

    ed.SelectionAdded.AddHandler(onSelectionAdded)
    let psr = ed.GetSelection(opt)
    ed.SelectionAdded.RemoveHandler(onSelectionAdded)
    if psr.Status = PromptStatus.OK then
        ed.SetImpliedSelection(psr.Value)

6
Math and Geometry / K-d Tree for AutoCAD Point3d
« on: June 17, 2013, 06:03:20 PM »
Hi

This is more related to 'Data Structure' than 'Geometry' but I didn't know where to post it.

This class may be used to improve performances in case of many queries in a quite large amount of points (see here).
It targets the Framwork 4.0 to use some parallelization features.

According to the value of the 'ignoreZ' constructor argument, the resulting tree is a 2d tree (ignoreZ = true) or a 3d tree (ignoreZ = false, default).
Use ignoreZ = true if all points in the input collection lie on a plane parallel to XY or if the points have to be considered as projected on the XY plane.

The Point3dTree public methods:
NearestNeighbour(Point3d) Gets the nearest neighbour.
NearestNeighbours(Point3d, int) Gets the n nearest neighbours.
NearestNeighbours(Point3d, double) Gets the nearest neighbours within the distance.
BoxedRange(Point3d, Point3d) Gets the points in a range.
ConnectAll(double) Gets all the pairs of points which distance is less or equal than the specified distance.
The last one was was almost used for performance tests (and to reply to this challenge).

Code: [Select]
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Autodesk.AutoCAD.Geometry;
     
    namespace PointKdTree
    {
       /// <summary>
       /// Node of a Point3dTree
       /// </summary>
       public class TreeNode
       {
           /// <summary>
           /// Creates a new instance of TreeNode
           /// </summary>
           /// <param name="value">The 3d point value of the node.</param>
           public TreeNode(Point3d value) { this.Value = value; }
     
           /// <summary>
           /// Gets the value (Point3d) of the node.
           /// </summary>
           public Point3d Value { get; internal set; }
     
           /// <summary>
           /// Gets the parent node.
           /// </summary>
           public TreeNode Parent { get; internal set; }
     
           /// <summary>
           /// Gets the left child node.
           /// </summary>
           public TreeNode LeftChild { get; internal set; }
     
           /// <summary>
           /// Gets the right child node.
           /// </summary>
           public TreeNode RightChild { get; internal set; }
     
           /// <summary>
           /// Gets the depth of the node in tree.
           /// </summary>
           public int Depth { get; internal set; }
     
           /// <summary>
           /// Gets a value indicating if the current node is a LeftChild node.
           /// </summary>
           public bool IsLeft { get; internal set; }
       }
     
       /// <summary>
       /// Provides methods to organize 3d points in a Kd tree structure to speed up the search of neighbours.
       /// A boolean constructor parameter (ignoreZ) indicates if the resulting Kd tree is a 3d tree or a 2d tree.
       /// Use ignoreZ = true if all points in the input collection lie on a plane parallel to XY
       /// or if the points have to be considered as projected on the XY plane.
       /// </summary>
       public class Point3dTree
       {
           #region Private fields
     
           private int dimension;
           private int parallelDepth;
           private bool ignoreZ;
           private Func<Point3d, Point3d, double> sqrDist;
     
           #endregion
     
           #region Constructor
     
           /// <summary>
           /// Creates an new instance of Point3dTree.
           /// </summary>
           /// <param name="points">The Point3d collection to fill the tree.</param>
           /// <param name="ignoreZ">A value indicating if the Z coordinate of points is ignored
           /// (as if all points were projected to the XY plane).</param>
           public Point3dTree(IEnumerable<Point3d> points, bool ignoreZ = false)
           {
               if (points == null)
                   throw new ArgumentNullException("points");
               this.ignoreZ = ignoreZ;
               this.dimension = ignoreZ ? 2 : 3;
               if (ignoreZ)
                   this.sqrDist = SqrDistance2d;
               else
                   this.sqrDist = SqrDistance3d;
               int numProc = System.Environment.ProcessorCount;
               this.parallelDepth = -1;
               while (numProc >> ++this.parallelDepth > 1) ;
               Point3d[] pts = points.Distinct().ToArray();
               this.Root = Create(pts, 0, null, false);
           }
     
           #endregion
     
           #region Public properties
     
           /// <summary>
           /// Gets the root node of the tree.
           /// </summary>
           public TreeNode Root { get; private set; }
     
           #endregion
     
           #region Public methods
     
           /// <summary>
           /// Gets the nearest neighbour.
           /// </summary>
           /// <param name="point">The point from which search the nearest neighbour.</param>
           /// <returns>The nearest point in the collection from the specified one.</returns>
           public Point3d NearestNeighbour(Point3d point)
           {
               return GetNeighbour(point, this.Root, this.Root.Value, double.MaxValue);
           }
     
           /// <summary>
           /// Gets the neighbours within the specified distance.
           /// </summary>
           /// <param name="point">The point from which search the nearest neighbours.</param>
           /// <param name="radius">The distance in which collect the neighbours.</param>
           /// <returns>The points which distance from the specified point is less or equal to the specified distance.</returns>
           public Point3dCollection NearestNeighbours(Point3d point, double radius)
           {
               Point3dCollection points = new Point3dCollection();
               GetNeighboursAtDistance(point, radius * radius, this.Root, points);
               return points;
           }
     
           /// <summary>
           /// Gets the given number of nearest neighbours.
           /// </summary>
           /// <param name="point">The point from which search the nearest neighbours.</param>
           /// <param name="number">The number of points to collect.</param>
           /// <returns>The n nearest neighbours of the specified point.</returns>
           public Point3dCollection NearestNeighbours(Point3d point, int number)
           {
               List<Tuple<double, Point3d>> pairs = new List<Tuple<double, Point3d>>(number);
               GetKNeighbours(point, number, this.Root, pairs);
               Point3dCollection points = new Point3dCollection();
               for (int i = 0; i < pairs.Count; i++)
               {
                   points.Add(pairs[i].Item2);
               }
               return points;
           }
     
           /// <summary>
           /// Gets the points in a range.
           /// </summary>
           /// <param name="pt1">The first corner of range.</param>
           /// <param name="pt2">The opposite corner of the range.</param>
           /// <returns>All points within the box.</returns>
           public Point3dCollection BoxedRange(Point3d pt1, Point3d pt2)
           {
               Point3d lowerLeft = new Point3d(
                   Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y), Math.Min(pt1.Z, pt2.Z));
               Point3d upperRight = new Point3d(
                   Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y), Math.Max(pt1.Z, pt2.Z));
               Point3dCollection points = new Point3dCollection();
               FindRange(lowerLeft, upperRight, this.Root, points);
               return points;
           }
     
           /// <summary>
           /// Gets all the pairs of points which distance is less or equal than the specified distance.
           /// </summary>
           /// <param name="radius">The maximum distance between two points. </param>
           /// <returns>The pairs of points which distance is less or equal than the specified distance.</returns>
           public List<Tuple<Point3d, Point3d>> ConnectAll(double radius)
           {
               List<Tuple<Point3d, Point3d>> connexions = new List<Tuple<Point3d, Point3d>>();
               GetConnexions(this.Root, radius * radius, connexions);
               return connexions;
           }
     
           #endregion
     
           #region Private methods
     
           private TreeNode Create(Point3d[] points, int depth, TreeNode parent, bool isLeft)
           {
               int length = points.Length;
               if (length == 0) return null;
               int d = depth % this.dimension;
               Point3d median = points.QuickSelectMedian((p1, p2) => p1[d].CompareTo(p2[d]));
               TreeNode node = new TreeNode(median);
               node.Depth = depth;
               node.Parent = parent;
               node.IsLeft = isLeft;
               int mid = length / 2;
               int rlen = length - mid - 1;
               Point3d[] left = new Point3d[mid];
               Point3d[] right = new Point3d[rlen];
               Array.Copy(points, 0, left, 0, mid);
               Array.Copy(points, mid + 1, right, 0, rlen);
               if (depth < this.parallelDepth)
               {
                   System.Threading.Tasks.Parallel.Invoke(
                      () => node.LeftChild = Create(left, depth + 1, node, true),
                      () => node.RightChild = Create(right, depth + 1, node, false)
                   );
               }
               else
               {
                   node.LeftChild = Create(left, depth + 1, node, true);
                   node.RightChild = Create(right, depth + 1, node, false);
               }
               return node;
           }
     
           private Point3d GetNeighbour(Point3d center, TreeNode node, Point3d currentBest, double bestDist)
           {
               if (node == null)
                   return currentBest;
               Point3d current = node.Value;
               int d = node.Depth % this.dimension;
               double coordCen = center[d];
               double coordCur = current[d];
               double dist = this.sqrDist(center, current);
               if (dist >= 0.0 && dist < bestDist)
               {
                   currentBest = current;
                   bestDist = dist;
               }
               dist = coordCen - coordCur;
               if (bestDist < dist * dist)
               {
                   currentBest = GetNeighbour(
                       center, coordCen < coordCur ? node.LeftChild : node.RightChild, currentBest, bestDist);
                   bestDist = this.sqrDist(center, currentBest);
               }
               else
               {
                   currentBest = GetNeighbour(center, node.LeftChild, currentBest, bestDist);
                   bestDist = this.sqrDist(center, currentBest);
                   currentBest = GetNeighbour(center, node.RightChild, currentBest, bestDist);
                   bestDist = this.sqrDist(center, currentBest);
               }
               return currentBest;
           }
     
           private void GetNeighboursAtDistance(Point3d center, double radius, TreeNode node, Point3dCollection points)
           {
               if (node == null) return;
               Point3d current = node.Value;
               double dist = this.sqrDist(center, current);
               if (dist <= radius)
               {
                   points.Add(current);
               }
               int d = node.Depth % this.dimension;
               double coordCen = center[d];
               double coordCur = current[d];
               dist = coordCen - coordCur;
               if (dist * dist > radius)
               {
                   if (coordCen < coordCur)
                   {
                       GetNeighboursAtDistance(center, radius, node.LeftChild, points);
                   }
                   else
                   {
                       GetNeighboursAtDistance(center, radius, node.RightChild, points);
                   }
               }
               else
               {
                   GetNeighboursAtDistance(center, radius, node.LeftChild, points);
                   GetNeighboursAtDistance(center, radius, node.RightChild, points);
               }
           }
     
           private void GetKNeighbours(Point3d center, int number, TreeNode node, List<Tuple<double, Point3d>> pairs)
           {
               if (node == null) return;
               Point3d current = node.Value;
               double dist = this.sqrDist(center, current);
               int cnt = pairs.Count;
               if (cnt == 0)
               {
                   pairs.Add(new Tuple<double, Point3d>(dist, current));
               }
               else if (cnt < number)
               {
                   if (dist > pairs[0].Item1)
                   {
                       pairs.Insert(0, new Tuple<double, Point3d>(dist, current));
                   }
                   else
                   {
                       pairs.Add(new Tuple<double, Point3d>(dist, current));
                   }
               }
               else if (dist < pairs[0].Item1)
               {
                   pairs[0] = new Tuple<double, Point3d>(dist, current);
                   pairs.Sort((p1, p2) => p2.Item1.CompareTo(p1.Item1));
               }
               int d = node.Depth % this.dimension;
               double coordCen = center[d];
               double coordCur = current[d];
               dist = coordCen - coordCur;
               if (dist * dist > pairs[0].Item1)
               {
                   if (coordCen < coordCur)
                   {
                       GetKNeighbours(center, number, node.LeftChild, pairs);
                   }
                   else
                   {
                       GetKNeighbours(center, number, node.RightChild, pairs);
                   }
               }
               else
               {
                   GetKNeighbours(center, number, node.LeftChild, pairs);
                   GetKNeighbours(center, number, node.RightChild, pairs);
               }
           }
     
           private void FindRange(Point3d lowerLeft, Point3d upperRight, TreeNode node, Point3dCollection points)
           {
               if (node == null)
                   return;
               Point3d current = node.Value;
               if (ignoreZ)
               {
                   if (current.X >= lowerLeft.X && current.X <= upperRight.X &&
                       current.Y >= lowerLeft.Y && current.Y <= upperRight.Y)
                       points.Add(current);
               }
               else
               {
                   if (current.X >= lowerLeft.X && current.X <= upperRight.X &&
                       current.Y >= lowerLeft.Y && current.Y <= upperRight.Y &&
                       current.Z >= lowerLeft.Z && current.Z <= upperRight.Z)
                       points.Add(current);
               }
               int d = node.Depth % this.dimension;
               if (upperRight[d] < current[d])
                   FindRange(lowerLeft, upperRight, node.LeftChild, points);
               else if (lowerLeft[d] > current[d])
                   FindRange(lowerLeft, upperRight, node.RightChild, points);
               else
               {
                   FindRange(lowerLeft, upperRight, node.LeftChild, points);
                   FindRange(lowerLeft, upperRight, node.RightChild, points);
               }
           }
     
           private void GetConnexions(TreeNode node, double radius, List<Tuple<Point3d, Point3d>> connexions)
           {
               if (node == null) return;
               Point3dCollection points = new Point3dCollection();
               Point3d center = node.Value;
               if (ignoreZ)
               GetRightParentsNeighbours(center, node, radius, points);
               GetNeighboursAtDistance(center, radius, node.LeftChild, points);
               GetNeighboursAtDistance(center, radius, node.RightChild, points);
               for (int i = 0; i < points.Count; i++)
               {
                   connexions.Add(new Tuple<Point3d, Point3d>(center, points[i]));
               }
               GetConnexions(node.LeftChild, radius, connexions);
               GetConnexions(node.RightChild, radius, connexions);
           }
     
           private void GetRightParentsNeighbours(Point3d center, TreeNode node, double radius, Point3dCollection points)
           {
               TreeNode parent = GetRightParent(node);
               if (parent == null) return;
               int d = parent.Depth % this.dimension;
               double dist = center[d] - parent.Value[d];
               if (dist * dist <= radius)
               {
                   GetNeighboursAtDistance(center, radius, parent.RightChild, points);
               }
               GetRightParentsNeighbours(center, parent, radius, points);
           }
     
           private TreeNode GetRightParent(TreeNode node)
           {
               TreeNode parent = node.Parent;
               if (parent == null) return null;
               if (node.IsLeft) return parent;
               return GetRightParent(parent);
           }
     
           private double SqrDistance2d(Point3d p1, Point3d p2)
           {
               return (p1.X - p2.X) * (p1.X - p2.X) +
                   (p1.Y - p2.Y) * (p1.Y - p2.Y);
           }
     
           private double SqrDistance3d(Point3d p1, Point3d p2)
           {
               return (p1.X - p2.X) * (p1.X - p2.X) +
                   (p1.Y - p2.Y) * (p1.Y - p2.Y) +
                   (p1.Z - p2.Z) * (p1.Z - p2.Z);
           }
     
           #endregion
       }
     
       static class Extensions
       {
           // Credit: Tony Tanzillo
           // http://www.theswamp.org/index.php?topic=44312.msg495808#msg495808
           public static T QuickSelectMedian<T>(this T[] items, Comparison<T> compare)
           {
               int l = items.Length;
               int k = l / 2;
               if (items == null || l == 0)
                   throw new ArgumentException("array");
               int from = 0;
               int to = l - 1;
               while (from < to)
               {
                   int r = from;
                   int w = to;
                   T current = items[(r + w) / 2];
                   while (r < w)
                   {
                       if (compare(items[r], current) > -1)
                       {
                           var tmp = items[w];
                           items[w] = items[r];
                           items[r] = tmp;
                           w--;
                       }
                       else
                       {
                           r++;
                       }
                   }
                   if (compare(items[r], current) > 0)
                   {
                       r--;
                   }
                   if (k <= r)
                   {
                       to = r;
                   }
                   else
                   {
                       from = r + 1;
                   }
               }
               return items[k];
           }
       }
    }
     

7
Layouts and printing / Plot to multi sheets PDF
« on: March 17, 2013, 11:22:01 AM »
Hi,

Here's a little class to be used to plot layouts to a single multi sheets PDF file.

Code: [Select]
using System.Collections.Generic;
using System.IO;
using System.Text;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.PlottingServices;
using Autodesk.AutoCAD.Publishing;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace Plottings
{
    public class MultiSheetsPdf
    {
        private string dwgFile, pdfFile, dsdFile, outputDir;
        private int sheetNum;
        IEnumerable<Layout> layouts;

        private const string LOG = "publish.log";

        public MultiSheetsPdfPlot(string pdfFile, IEnumerable<Layout> layouts)
        {
            Database db = HostApplicationServices.WorkingDatabase;
            this.dwgFile = db.Filename;
            this.pdfFile = pdfFile;
            this.outputDir = Path.GetDirectoryName(this.pdfFile);
            this.dsdFile = Path.ChangeExtension(this.pdfFile, "dsd");
            this.layouts = layouts;
        }

        public void Publish()
        {
            if (TryCreateDSD())
            {
                Publisher publisher = AcAp.Publisher;
                PlotProgressDialog plotDlg = new PlotProgressDialog(false, this.sheetNum, true);
                publisher.PublishDsd(this.dsdFile, plotDlg);
                plotDlg.Destroy();
                File.Delete(this.dsdFile);
            }
        }

        private bool TryCreateDSD()
        {
            using (DsdData dsd = new DsdData())
            using (DsdEntryCollection dsdEntries = CreateDsdEntryCollection(this.layouts))
            {
                if (dsdEntries == null || dsdEntries.Count <= 0) return false;

                if (!Directory.Exists(this.outputDir))
                    Directory.CreateDirectory(this.outputDir);

                this.sheetNum = dsdEntries.Count;

                dsd.SetDsdEntryCollection(dsdEntries);

                dsd.SetUnrecognizedData("PwdProtectPublishedDWF", "FALSE");
                dsd.SetUnrecognizedData("PromptForPwd", "FALSE");
                dsd.SheetType = SheetType.MultiDwf;
                dsd.NoOfCopies = 1;
                dsd.DestinationName = this.pdfFile;
                dsd.IsHomogeneous = false;
                dsd.LogFilePath = Path.Combine(this.outputDir, LOG);

                PostProcessDSD(dsd);

                return true;
            }
        }

        private DsdEntryCollection CreateDsdEntryCollection(IEnumerable<Layout> layouts)
        {
            DsdEntryCollection entries = new DsdEntryCollection();

            foreach (Layout layout in layouts)
            {
                DsdEntry dsdEntry = new DsdEntry();
                dsdEntry.DwgName = this.dwgFile;
                dsdEntry.Layout = layout.LayoutName;
                dsdEntry.Title = Path.GetFileNameWithoutExtension(this.dwgFile) + "-" + layout.LayoutName;
                dsdEntry.Nps = layout.TabOrder.ToString();
                entries.Add(dsdEntry);
            }
            return entries;
        }

        private void PostProcessDSD(DsdData dsd)
        {
            string str, newStr;
            string tmpFile = Path.Combine(this.outputDir, "temp.dsd");

            dsd.WriteDsd(tmpFile);

            using (StreamReader reader = new StreamReader(tmpFile, Encoding.Default))
            using (StreamWriter writer = new StreamWriter(this.dsdFile, false, Encoding.Default))
            {
                while (!reader.EndOfStream)
                {
                    str = reader.ReadLine();
                    if (str.Contains("Has3DDWF"))
                    {
                        newStr = "Has3DDWF=0";
                    }
                    else if (str.Contains("OriginalSheetPath"))
                    {
                        newStr = "OriginalSheetPath=" + this.dwgFile;
                    }
                    else if (str.Contains("Type"))
                    {
                        newStr = "Type=6";
                    }
                    else if (str.Contains("OUT"))
                    {
                        newStr = "OUT=" + this.outputDir;
                    }
                    else if (str.Contains("IncludeLayer"))
                    {
                        newStr = "IncludeLayer=TRUE";
                    }
                    else if (str.Contains("PromptForDwfName"))
                    {
                        newStr = "PromptForDwfName=FALSE";
                    }
                    else if (str.Contains("LogFilePath"))
                    {
                        newStr = "LogFilePath=" + Path.Combine(this.outputDir, LOG);
                    }
                    else
                    {
                        newStr = str;
                    }
                    writer.WriteLine(newStr);
                }
            }
            File.Delete(tmpFile);
        }
    }
}

Using example which creates a new instance of MultiSheetsPdf passing to the constructor the current drawing file name with "pdf" extension and all layouts except "Model":
Code: [Select]
        [CommandMethod("PlotPdf")]
        public void PlotPdf()
        {
            Database db = HostApplicationServices.WorkingDatabase;
            short bgp = (short)AcAp.GetSystemVariable("BACKGROUNDPLOT");
            try
            {
                AcAp.SetSystemVariable("BACKGROUNDPLOT", 0);
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    List<Layout> layouts = new List<Layout>();
                    DBDictionary layoutDict =
                        (DBDictionary)db.LayoutDictionaryId.GetObject(OpenMode.ForRead);
                    foreach (DBDictionaryEntry entry in layoutDict)
                    {
                        if (entry.Key != "Model")
                        {
                            layouts.Add((Layout)tr.GetObject(entry.Value, OpenMode.ForRead));
                        }
                    }
                    layouts.Sort((l1, l2) => l1.TabOrder.CompareTo(l2.TabOrder));

                    string filename = Path.ChangeExtension(db.Filename, "pdf");

                    MultiSheetsPdf plotter = new MultiSheetsPdf(filename, layouts);
                    plotter.Publish();

                    tr.Commit();
                }
            }
            catch (System.Exception e)
            {
                Editor ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nError: {0}\n{1}", e.Message, e.StackTrace);
            }
            finally
            {
                AcAp.SetSystemVariable("BACKGROUNDPLOT", bgp);
            }
        }

8
DesignScript / Moebius strip
« on: February 24, 2013, 10:04:42 PM »
Code: [Select]
import("ProtoGeometry.dll");
import("Math.dll");

numPts = 60;
halfWid = 20;
angle = 0..360..#numPts;
radius = 100;
width = 2;

def pts(halfWid : double)
{
    x = (radius + halfWid * (Math.Cos(angle / 2))) * (Math.Cos(angle));
y = (radius + halfWid * (Math.Cos(angle / 2))) * (Math.Sin(angle));
z = halfWid * Math.Sin(angle / 2);
return = Point.ByCoordinates(x, y, z);
}

lines = Line.ByStartPointEndPoint(pts(halfWid), pts(-halfWid));
lines.SetVisibility(false);
surfs = Surface.LoftFromCrossSections({ lines[0..numPts / 2], lines[numPts / 2..numPts] });
surfs.Visible = false;
solids = surfs.Thicken(width, true);
solid = solids[0].Union(solids[1]);

9
Blocks / Drag (attributed) block reference
« on: January 26, 2013, 10:44:15 PM »
Dragging and rotating an (attributed) block.

BlockJig is to be used to drag (and possibly rotate) a simple block.
While dragging the block, it's possible to dynamically rotate the block by pressing Ctrl.
A click terminate the process keeping the current position and rotation for the block

BlockAttribJig inherits from BlockJig and handles the block attribute references.
In the constructor, each attribute definition informations (position, alignement and rotation) is stored in a structure.
The overrided Update() method sets the attributes geometry according to the attribute definition informations and the block reference position and rotation.

Code: (csharp) [Select]
        class BlockJig : EntityJig
        {
            protected BlockReference _br;
            protected Point3d _pos;
            protected double _rot, _ucsRot;

            public BlockJig(BlockReference br)
                : base(br)
            {
                _br = br;
                _pos = _br.Position;
                Editor ed = AcAp.DocumentManager.MdiActiveDocument.Editor;
                CoordinateSystem3d ucs = ed.CurrentUserCoordinateSystem.CoordinateSystem3d;
                Matrix3d ocsMat = Matrix3d.WorldToPlane(new Plane(Point3d.Origin, ucs.Zaxis));
                _ucsRot = Vector3d.XAxis.GetAngleTo(ucs.Xaxis.TransformBy(ocsMat), ucs.Zaxis);
                _rot = _br.Rotation - _ucsRot;
            }

            protected override SamplerStatus Sampler(JigPrompts prompts)
            {
                System.Windows.Forms.Keys mods = System.Windows.Forms.Control.ModifierKeys;
                if ((mods & System.Windows.Forms.Keys.Control) > 0)
                {
                    JigPromptAngleOptions jpao =
                        new JigPromptAngleOptions("\nSpecify the rotation: ");
                    jpao.UseBasePoint = true;
                    jpao.BasePoint = _br.Position;
                    jpao.Cursor = CursorType.RubberBand;
                    jpao.UserInputControls = (
                        UserInputControls.Accept3dCoordinates |
                        UserInputControls.UseBasePointElevation);
                    PromptDoubleResult pdr = prompts.AcquireAngle(jpao);

                    if (_rot == pdr.Value)
                    {
                        return SamplerStatus.NoChange;
                    }
                    else
                    {
                        _rot = pdr.Value;
                        return SamplerStatus.OK;
                    }
                }
                else
                {
                    JigPromptPointOptions jppo =
                        new JigPromptPointOptions("\nSpecify insertion point (or press Ctrl for rotation): ");
                    jppo.UserInputControls =
                      (UserInputControls.Accept3dCoordinates | UserInputControls.NullResponseAccepted);
                    PromptPointResult ppr = prompts.AcquirePoint(jppo);
                    if (_pos.DistanceTo(ppr.Value) < Tolerance.Global.EqualPoint)
                    {
                        return SamplerStatus.NoChange;
                    }
                    else
                    {
                        _pos = ppr.Value;
                    }
                    return SamplerStatus.OK;
                }
            }

            protected override bool Update()
            {
                _br.Position = _pos;
                _br.Rotation = _rot +_ucsRot;
                return true;
            }
        }

        class BlockAttribJig : BlockJig
        {
            struct TextInfo
            {
                public Point3d Position;
                public Point3d Alignment;
                public double Rotation;
                public bool IsAligned;
            }

            private Dictionary<string, TextInfo> _attInfos;

            public BlockAttribJig(BlockReference br)
                : base(br)
            {
                _attInfos = new Dictionary<string, TextInfo>();
                BlockTableRecord btr = (BlockTableRecord)br.BlockTableRecord.GetObject(OpenMode.ForRead);
                foreach (ObjectId id in btr)
                {
                    if (id.ObjectClass.Name == "AcDbAttributeDefinition")
                    {
                        AttributeDefinition attDef = (AttributeDefinition)id.GetObject(OpenMode.ForRead);
                        TextInfo ti = new TextInfo()
                        {
                            Position = attDef.Position,
                            Alignment = attDef.AlignmentPoint,
                            IsAligned = attDef.Justify != AttachmentPoint.BaseLeft,
                            Rotation = attDef.Rotation
                        };
                        _attInfos.Add(attDef.Tag.ToUpper(), ti);
                    }
                }
            }

            protected override bool Update()
            {
                base.Update();
                foreach (ObjectId id in _br.AttributeCollection)
                {
                    AttributeReference att = (AttributeReference)id.GetObject(OpenMode.ForWrite);
                    att.Rotation = _br.Rotation;
                    string tag = att.Tag.ToUpper();
                    if (_attInfos.ContainsKey(tag))
                    {
                        TextInfo ti = _attInfos[tag];
                        att.Position = ti.Position.TransformBy(_br.BlockTransform);
                        if (ti.IsAligned)
                        {
                            att.AlignmentPoint =
                                ti.Alignment.TransformBy(_br.BlockTransform);
                            att.AdjustAlignment(_br.Database);
                        }
                        if (att.IsMTextAttribute)
                        {
                            att.UpdateMTextAttribute();
                        }
                        att.Rotation = ti.Rotation + _br.Rotation;
                    }
                }
                return true;
            }
        }

A little using example to insert a block

Code: (csharp) [Select]
        [CommandMethod("TEST")]
        public void Test()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            PromptResult pr = ed.GetString("\nBlock name: ");
            if (pr.Status != PromptStatus.OK) return;
            string blockName = pr.StringResult;

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (!bt.Has(blockName))
                {
                    ed.WriteMessage("\nNone block '{0}' in the document block table.", blockName);
                    return;
                }
                BlockTableRecord curSpace =
                    (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);

                // Add the block reference to Database first
                BlockReference br = new BlockReference(Point3d.Origin, bt[blockName]);
                br.TransformBy(ed.CurrentUserCoordinateSystem);
                curSpace.AppendEntity(br);
                tr.AddNewlyCreatedDBObject(br, true);

                // Get the block definition
                BlockTableRecord btr =
                    (BlockTableRecord)tr.GetObject(bt[blockName], OpenMode.ForRead);
                BlockJig jig;
                if (btr.HasAttributeDefinitions)
                {
                    // Add attribute references to the block reference
                    foreach (ObjectId id in btr)
                    {
                        if (id.ObjectClass.Name == "AcDbAttributeDefinition")
                        {
                            AttributeDefinition attDef =
                                (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
                            AttributeReference attRef = new AttributeReference();
                            attRef.SetAttributeFromBlock(attDef, br.BlockTransform);
                            ObjectId attId = br.AttributeCollection.AppendAttribute(attRef);
                            tr.AddNewlyCreatedDBObject(attRef, true);
                        }
                    }
                    // Create a BlockAttribJig instance
                    jig = new BlockAttribJig(br);
                }
                else
                {
                    // Create a BlockJig instance
                    jig = new BlockJig(br);
                }
                // Drag the block reference
                pr = ed.Drag(jig);
                if (pr.Status != PromptStatus.OK) br.Erase();
                tr.Commit();
            }
        }

10
Polylines / Fillet
« on: December 24, 2012, 05:05:41 PM »
Hi,

Here're two extension methods for the Polyline class to add an arc (fillet) at the specified vertex or at each vertex.

The main difference between C#, VB and F# is the way to loop for each vertex while adding some new ones: with C# a 'for' loop can be used because the condition is checked at each loop, this must be done with a 'while' loop in VB and the 'natural' way in F# is a tail recursive function.

C#
Code: [Select]
    public static class Extensions
    {
        // Adds an arc (fillet) at each vertex, if able.
        public static void FilletAll(this Polyline pline, double radius)
        {
            int i = pline.Closed ? 0 : 1;
            for (int j = 0; j < pline.NumberOfVertices - i; j += 1 + pline.FilletAt(j, radius))
            { }
        }

       // Adds an arc (fillet) at the specified vertex. Returns 1 if the operation succeeded, 0 if it failed.
        public static int FilletAt(this Polyline pline, int index, double radius)
        {
            int prev = index == 0 && pline.Closed ? pline.NumberOfVertices - 1 : index - 1;
            if (pline.GetSegmentType(prev) != SegmentType.Line ||
                pline.GetSegmentType(index) != SegmentType.Line)
                return 0;
            LineSegment2d seg1 = pline.GetLineSegment2dAt(prev);
            LineSegment2d seg2 = pline.GetLineSegment2dAt(index);
            Vector2d vec1 = seg1.StartPoint - seg1.EndPoint;
            Vector2d vec2 = seg2.EndPoint - seg2.StartPoint;
            double angle = (Math.PI - vec1.GetAngleTo(vec2)) / 2.0;
            double dist = radius * Math.Tan(angle);
            if (dist > seg1.Length || dist > seg2.Length)
                return 0;
            Point2d pt1 = seg1.EndPoint + vec1.GetNormal() * dist;
            Point2d pt2 = seg2.StartPoint + vec2.GetNormal() * dist;
            double bulge = Math.Tan(angle / 2.0);
            if (Clockwise(seg1.StartPoint, seg1.EndPoint, seg2.EndPoint))
                bulge = -bulge;
            pline.AddVertexAt(index, pt1, bulge, 0.0, 0.0);
            pline.SetPointAt(index + 1, pt2);
            return 1;
        }

        // Evaluates if the points are clockwise.
        private static bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
        {
            return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-8;
        }
    }

VB
Code: [Select]
    Module Extensions

        ' Adds an arc (fillet) at each polyline vertex, if able.
        <System.Runtime.CompilerServices.Extension> _
        Public Sub FilletAll(pline As Polyline, radius As Double)
            Dim i As Integer = If(pline.Closed, 0, 1)
            While i < If(pline.Closed, pline.NumberOfVertices, pline.NumberOfVertices - 1)
                i += 1 + pline.FilletAt(i, radius)
            End While
        End Sub

        ' Adds an arc (fillet) at the specified vertex. Retuns 1 if the operation succeeded, 0 if it failed.
        <System.Runtime.CompilerServices.Extension> _
        Public Function FilletAt(pline As Polyline, index As Integer, radius As Double) As Integer
            Dim prev As Integer = If(index = 0 AndAlso pline.Closed, pline.NumberOfVertices - 1, index - 1)
            If pline.GetSegmentType(prev) <> SegmentType.Line OrElse _
                pline.GetSegmentType(index) <> SegmentType.Line Then
                Return 0
            End If
            Dim seg1 As LineSegment2d = pline.GetLineSegment2dAt(prev)
            Dim seg2 As LineSegment2d = pline.GetLineSegment2dAt(index)
            Dim vec1 As Vector2d = seg1.StartPoint - seg1.EndPoint
            Dim vec2 As Vector2d = seg2.EndPoint - seg2.StartPoint
            Dim angle As Double = (Math.PI - vec1.GetAngleTo(vec2)) / 2.0
            Dim dist As Double = radius * Math.Tan(angle)
            If dist > seg1.Length OrElse dist > seg2.Length Then
                Return 0
            End If
            Dim pt1 As Point2d = seg1.EndPoint + vec1.GetNormal() * dist
            Dim pt2 As Point2d = seg2.StartPoint + vec2.GetNormal() * dist
            Dim bulge As Double = Math.Tan(angle / 2.0)
            If Clockwise(seg1.StartPoint, seg1.EndPoint, seg2.EndPoint) Then
                bulge = -bulge
            End If
            pline.AddVertexAt(index, pt1, bulge, 0.0, 0.0)
            pline.SetPointAt(index + 1, pt2)
            Return 1
        End Function

        ' Evaluates if the points are clockwise.
        Private Function Clockwise(p1 As Point2d, p2 As Point2d, p3 As Point2d) As Boolean
            Return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 0.00000001
        End Function

    End Module

F#
Code: [Select]
// Evaluates if the points are clockwise.
let clockwise (p1: Point2d) (p2: Point2d) (p3: Point2d) =
    ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-8

type Polyline with
    // Adds an arc (fillet) at the specified vertex. Retuns 1 if the operation succeeded, 0 if it failed.
    member pl.FilletAt index radius =
        let prev = (if index = 0 && pl.Closed then pl.NumberOfVertices else index) - 1
        if pl.GetSegmentType(prev) <> SegmentType.Line ||
            pl.GetSegmentType(index) <> SegmentType.Line then
            0
        else
            let seg1 = pl.GetLineSegment2dAt(prev)
            let seg2 = pl.GetLineSegment2dAt(index)
            let vec1 = seg1.StartPoint - seg1.EndPoint
            let vec2 = seg2.EndPoint - seg2.StartPoint
            let angle = (Math.PI - vec1.GetAngleTo(vec2)) / 2.
            let dist = radius * tan(angle)
            if dist > seg1.Length || dist > seg2.Length then
                0
            else
                let pt1 = seg1.EndPoint + vec1.GetNormal() * dist
                let pt2 = seg2.StartPoint + vec2.GetNormal() * dist
                let mutable bulge = tan(angle / 2.)
                if clockwise pt1 seg1.EndPoint pt2 then bulge <- -bulge
                pl.AddVertexAt(index, pt1, bulge, 0., 0.)
                pl.SetPointAt(index + 1, pt2)
                1

    // Adds an arc (fillet) at each vertex, if able.
    member pl.FilletAll radius =
        let rec loop i =
            if i < pl.NumberOfVertices then
                loop (i + 1 + pl.FilletAt i radius)
        loop (if pl.Closed then 0 else 1)

11
DesignScript / Some more curves
« on: October 24, 2012, 02:50:13 PM »
Hi,

DesignScript is relatively poor in native curves (Line, Arc, Circle and BSplineCurve).
I thought it might be interesting to define some others.
I tried to do this as classes with constructors, these classes may be required to evolve with the addition of new features.
The generated curves are spline whose control points or smoothing are calculated from the equations of these curves.

The Catenary, Ellipse, EllpticalArc and Parabola classes:
Code: [Select]
import("ProtoGeometry.dll");
import("Math.dll");
   
class Catenary
{
    Curve : Curve;
    Chord : double;
    Param : double;
    Sagitta : double;
    Length : double;
   
    def argCosh(x : double)
    {
        return = Math.Log(x + Math.Sqrt(x * x - 1));
    }

    constructor FromChordParam(chord : double, param : double, numPts : int)
    {
        Chord = chord;
        Param = param;
        Sagitta = param * Math.Cosh(chord / (2 * param)) - param;
        Length = 2 * param * Math.Sinh(chord / (2 * param));
        x = chord / -2..chord / 2..#numPts;
        y = param * Math.Cosh(x / param);
        Curve = BSplineCurve.ByPoints(Point.ByCoordinates(x, y, 0));
    }
   
    constructor FromLenthSagitta(length : double, sagitta : double, numPts : int)
    {
        Length = length;
        Sagitta = sagitta;
        Param = (length * length - 4 * sagitta * sagitta) / (8 * sagitta);
        Chord = 2 * Param * argCosh((sagitta + Param) / Param);
        x = Chord / -2..Chord / 2..#numPts;
        y = Param * Math.Cosh(x / Param);
        Curve = BSplineCurve.ByPoints(Point.ByCoordinates(x, y, 0));
    }
}

class Ellipse
{
    Curve : Curve;
   
    constructor FromRadii(majRadius : double, minRadius : double)
    {
        Curve = Circle.ByCenterPointRadius(Point.ByCoordinates(0, 0, 0), 1)
        .Transform(CoordinateSystem.WCS,
            CoordinateSystem.Identity().Scale(majRadius, minRadius, 1));
    }
}

class EllipticalArc
{
    Curve : Curve;
   
    constructor FromRadii(majRadius : double, minRadius : double, startAngle : double, sweepAngle : double)
    {
        endAngle = startAngle + sweepAngle;
        startParam = Math.Atan(majRadius * Math.Tan(startAngle) / minRadius);
        endParam = Math.Atan(majRadius * Math.Tan(endAngle) / minRadius);
        [Imperative]
        {
            if (startAngle == 0 || startAngle == 90 || startAngle == 180 || startAngle == 270 || startAngle == 360)
                startParam = startAngle;
            else if (startAngle > 90 && startAngle < 270)
                startParam = startParam + 180;
            if (endAngle == 0 || endAngle == 90 || endAngle == 180 || endAngle == 270 || endAngle == 360)
                endParam = endAngle;
        else if (endAngle > 90 && endAngle < 270)
                endParam = endParam + 180;
            if (endParam <= startParam)
                endParam = endParam + 360;
        }
        Curve = Arc.ByCenterPointRadiusAngle(Point.ByCoordinates(0, 0, 0),
            1, startParam, endParam - startParam, Vector.ByCoordinates(0, 0, 1))
            .Transform(CoordinateSystem.WCS,
            CoordinateSystem.Identity().Scale(majRadius, minRadius, 1));
    }
}

class Parabola
{
    Curve : Curve;
    Focus : Point;
   
    constructor FromChordSagitta(chord : double, sagitta : double, numPts : int, focusVisible : bool)
    {
        a = 4 * sagitta / (chord * chord);
        Focus = Point.ByCoordinates(0, 1 / (4 * a), 0).SetVisibility(focusVisible);
        x = chord / - 2..chord / 2..#numPts;
        y = a * x * x;
        Curve = BSplineCurve.ByPoints(Point.ByCoordinates(x, y, 0));
    }
}


Using examples

Ellipses with incremented major axis.

Code: [Select]
el = Ellipse.FromRadii(30..80..5, 30);



Parabolas with decremented chords and incrmented sagittas

Code: [Select]
parabs = Parabola.FromChordSagitta(200..100..-10, 20..120..10, 19, false);


 
Catenaries with incremented params.
Each curve is moved so that start and end point lies ont the X axis. This is done using the Param and Sagitta properties of the Catenary instance.

Code: [Select]
def catenar(chord : double, param : double, numPts : int)
{
    cat = Catenary.FromChordParam(chord, param, numPts);
    return = cat.Curve.Translate(0, -cat.Param - cat.Sagitta, 0);
}

cats = catenar(160, 35..75..#10, 19);



 

12
Tables / Get the MText object in a cell
« on: October 13, 2012, 06:40:44 PM »
Hi,

Here's a little class which extends the Cell type with a method to get the MText object the cell contains (if any, returns null otherwise).

Code: [Select]
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

namespace TableSample
{
    static class Extensions
    {
        // Retourne l'objet MText contenu dans la cellule (ou null s'il nexiste pas).
        // Returns the MText object contained in the cell (or null if it don't exist).
        public static MText GetMtext(this Cell cell)
        {
            Table table = cell.ParentTable;
            Extents3d extents = cell.GetExtents().ToExtents3d();
            extents.TransformBy(table.BlockTransform.Inverse());
            BlockTableRecord btr = (BlockTableRecord)table.BlockTableRecord.GetObject(OpenMode.ForRead);
            RXClass mtextClass = RXClass.GetClass(typeof(MText));
            foreach (ObjectId id in btr)
            {
                if (id.ObjectClass == mtextClass)
                {
                    MText mtext = (MText)id.GetObject(OpenMode.ForRead);
                    if (mtext.Location.IsInside(extents))
                        return mtext;
                }
            }
            return null;
        }

        // Evalue si un point est à l'intérieur de la boite (BoundingBox).
        // Evaluates if the point is within the extents (BoundingBox).
        private static bool IsInside(this Point3d pt, Extents3d extents)
        {
            return
                pt.X >= extents.MinPoint.X &&
                pt.Y >= extents.MinPoint.Y &&
                pt.Z >= extents.MinPoint.Z &&
                pt.X <= extents.MaxPoint.X &&
                pt.Y <= extents.MaxPoint.Y &&
                pt.Z <= extents.MaxPoint.Z;
        }

        // Retourne la boite (BoundingBox) définie par la collection de points.
        // Returns the extents (BoundingBox) defined by the points collection
        private static Extents3d ToExtents3d(this Point3dCollection pts)
        {
            return pts.Cast<Point3d>()
                .Aggregate(new Extents3d(pts[0], pts[0]), (ext, pt) =>
                { ext.AddPoint(pt); return ext; });
        }
    }
}

Using example, assuming table is an AutoCAD Table object, row and column the indices of the cell row and column:
Code: [Select]
MText mtext = table.Cells[row, column].GetMtext();

13
External databases / Import SymbolTableRecords
« on: August 21, 2012, 08:59:35 PM »
Hi,

A generic extension method to import SymbolTableRecord objects from a dwg (or dwt) file into a Database.

The ImportSymbolTableRecords() method extends the Database type, that's to say it can be called with the dot(.) operator from a Database instance.
It is generic because it can target any SymbolTable using a Type parameter.
The requiered arguments are the source file full path and ParamArray of strings (one or more SymbolTableRecord names).
The function returns an ObjectIdCollection containing the ObjectId of the cloned SymbolTableRecords (null/Nothing if none have been imported).

C#
Extension methods have to be defined within a static class, they have to be static and their first argument must use the 'this' keyword and be the same type as the extended one.

Code: [Select]
    public static class Extension
    {
        public static ObjectIdCollection ImportSymbolTableRecords<T>(
            this Database targetDb,
            string sourceFile,
            params string[] recordNames)
            where T : SymbolTable
        {
            using (Database sourceDb = new Database())
            {
                sourceDb.ReadDwgFile(sourceFile, System.IO.FileShare.Read, false, "");
                ObjectId sourceTableId, targetTableId;
                switch (typeof(T).Name)
                {
                    case "BlockTable":
                        sourceTableId = sourceDb.BlockTableId;
                        targetTableId = targetDb.BlockTableId;
                        break;
                    case "DimStyleTable":
                        sourceTableId = sourceDb.DimStyleTableId;
                        targetTableId = targetDb.DimStyleTableId;
                        break;
                    case "LayerTable":
                        sourceTableId = sourceDb.LayerTableId;
                        targetTableId = targetDb.LayerTableId;
                        break;
                    case "LinetypeTable":
                        sourceTableId = sourceDb.LinetypeTableId;
                        targetTableId = targetDb.LinetypeTableId;
                        break;
                    case "RegAppTable":
                        sourceTableId = sourceDb.RegAppTableId;
                        targetTableId = targetDb.RegAppTableId;
                        break;
                    case "TextStyleTable":
                        sourceTableId = sourceDb.TextStyleTableId;
                        targetTableId = targetDb.TextStyleTableId;
                        break;
                    case "UcsTable":
                        sourceTableId = sourceDb.UcsTableId;
                        targetTableId = targetDb.UcsTableId;
                        break;
                    case "ViewTable":
                        sourceTableId = sourceDb.ViewportTableId;
                        targetTableId = targetDb.ViewportTableId;
                        break;
                    case "ViewportTable":
                        sourceTableId = sourceDb.ViewportTableId;
                        targetTableId = targetDb.ViewportTableId;
                        break;
                    default:
                        throw new ArgumentException("Requires a concrete type derived from SymbolTable");
                }

                using (Transaction tr = sourceDb.TransactionManager.StartTransaction())
                {
                    T sourceTable = (T)tr.GetObject(sourceTableId, OpenMode.ForRead);
                    ObjectIdCollection idCol = new ObjectIdCollection();
                    foreach (string name in recordNames)
                    {
                        if (sourceTable.Has(name))
                        {
                            idCol.Add(sourceTable[name]);
                        }
                    }
                    if (idCol.Count == 0)
                        return null;
                    IdMapping idMap = new IdMapping();
                    sourceDb.WblockCloneObjects(
                        idCol, targetTableId, idMap, DuplicateRecordCloning.Replace, false);
                    tr.Commit();
                    ObjectIdCollection retVal = new ObjectIdCollection();
                    foreach (ObjectId id in idCol)
                    {
                        if (idMap[id].IsCloned)
                        {
                            retVal.Add(idMap[id].Value);
                        }
                    }
                    return retVal.Count == 0 ? null : retVal;
                }
            }
        }
    }

Using example:
Code: [Select]
ObjectIdCollection result =
    db.ImportSymbolTableRecords<TextStyleTable>(@"F:\gile\Templates\Standard2010.dwt", "Arial_Std", "Romans_Std");

VB
Extension methods have to be defined within a module with the System.Runtime.CompilerServices.Extension() attribute. The first argument have to be the same type as the extended one.

Code: [Select]
    Module Extension
        <System.Runtime.CompilerServices.Extension()> _
        Public Function ImportSymbolTableRecords(Of T As SymbolTable) _
        (targetDb As Database, sourceFile As String, ParamArray recordNames As String()) _
        As ObjectIdCollection
            Using sourceDb As New Database()
                sourceDb.ReadDwgFile(sourceFile, System.IO.FileShare.Read, False, "")
                Dim sourceTableId As ObjectId, targetTableId As ObjectId
                Select Case GetType(T).Name
                    Case "BlockTable"
                        sourceTableId = sourceDb.BlockTableId
                        targetTableId = targetDb.BlockTableId
                        Exit Select
                    Case "DimStyleTable"
                        sourceTableId = sourceDb.DimStyleTableId
                        targetTableId = targetDb.DimStyleTableId
                        Exit Select
                    Case "LayerTable"
                        sourceTableId = sourceDb.LayerTableId
                        targetTableId = targetDb.LayerTableId
                        Exit Select
                    Case "LinetypeTable"
                        sourceTableId = sourceDb.LinetypeTableId
                        targetTableId = targetDb.LinetypeTableId
                        Exit Select
                    Case "RegAppTable"
                        sourceTableId = sourceDb.RegAppTableId
                        targetTableId = targetDb.RegAppTableId
                        Exit Select
                    Case "TextStyleTable"
                        sourceTableId = sourceDb.TextStyleTableId
                        targetTableId = targetDb.TextStyleTableId
                        Exit Select
                    Case "UcsTable"
                        sourceTableId = sourceDb.UcsTableId
                        targetTableId = targetDb.UcsTableId
                        Exit Select
                    Case "ViewTable"
                        sourceTableId = sourceDb.ViewportTableId
                        targetTableId = targetDb.ViewportTableId
                        Exit Select
                    Case "ViewportTable"
                        sourceTableId = sourceDb.ViewportTableId
                        targetTableId = targetDb.ViewportTableId
                        Exit Select
                    Case Else
                        Throw New ArgumentException("Requires a concrete type derived from SymbolTable")
                End Select

                Using tr As Transaction = sourceDb.TransactionManager.StartTransaction()
                    Dim sourceTable As T = DirectCast(tr.GetObject(sourceTableId, OpenMode.ForRead), T)
                    Dim idCol As New ObjectIdCollection()
                    For Each name As String In recordNames
                        If sourceTable.Has(name) Then
                            idCol.Add(sourceTable(name))
                        End If
                    Next
                    If idCol.Count = 0 Then
                        Return Nothing
                    End If
                    Dim idMap As New IdMapping()
                    sourceDb.WblockCloneObjects(idCol, targetTableId, idMap, DuplicateRecordCloning.Ignore, False)
                    tr.Commit()
                    Dim retVal As New ObjectIdCollection()
                    For Each id As ObjectId In idCol
                        If idMap(id).IsCloned Then
                            retVal.Add(idMap(id).Value)
                        End If
                    Next
                    If retVal.Count = 0 Then
                        Return Nothing
                    Else
                        Return retVal
                    End If
                End Using
            End Using
        End Function
    End Module

Using example:
Code: [Select]
Dim result As ObjectIdCollection = _
    db.ImportSymbolTableRecords(Of TextStyleTable)("F:\gile\Templates\Standard2010.dwt", "Arial_Std", "Romans_Std")

F#
F# type extensions allows to easily add any member to an existing type. I let you appreciate how much more concise is the code due to some specific features as tuple let bindings, pattern matching, aggregate operators ...

Code: [Select]
type Database with
    member db.ImportSymbolTableRecords<'T when 'T :> SymbolTable>(sourceFile, ([<ParamArray>] recordNames: string array)) =
        use sourceDb = new Database()
        sourceDb.ReadDwgFile(sourceFile, System.IO.FileShare.Read, false, "")
        let  sourceTableId, targetTableId =
            match typeof<'T>.Name with
            | "BlockTable" -> sourceDb.BlockTableId, db.BlockTableId
            | "DimStyleTable" -> sourceDb.DimStyleTableId, db.DimStyleTableId
            | "LayerTable" -> sourceDb.LayerTableId, db.LayerTableId
            | "LinetypeTable" -> sourceDb.LinetypeTableId, db.LinetypeTableId
            | "RegAppTable" -> sourceDb.RegAppTableId, db.RegAppTableId
            | "TextStyleTable" -> sourceDb.TextStyleTableId, db.TextStyleTableId
            | "UcsTable" -> sourceDb.UcsTableId, db.UcsTableId
            | "ViewTable" -> sourceDb.ViewTableId, db.ViewTableId
            | "ViewportTable" -> sourceDb.ViewportTableId, db.ViewportTableId
            | _ -> invalidArg typeof<'T>.Name "Requires a concrete type derived from SymbolTable"

        use tr = sourceDb.TransactionManager.StartTransaction()
        let sourceTable = tr.GetObject(sourceTableId, OpenMode.ForRead) :?> 'T
        let sourceIds = recordNames |> Array.choose(function
            | n when sourceTable.Has(n) -> Some(sourceTable.[n])
            | _ -> None)
        if sourceIds.Length = 0 then
            null
        else
            let idCol = new ObjectIdCollection(sourceIds)
            let idMap = new IdMapping()
            sourceDb.WblockCloneObjects(idCol, targetTableId, idMap, DuplicateRecordCloning.Replace, false)
            tr.Commit()
            let ids = sourceIds |> Array.choose(function
                | id when idMap.[id].IsCloned -> Some(idMap.[id].Value)
                | _ -> None)
            if ids.Length = 0 then
                null
            else
                new ObjectIdCollection(ids)

Using example:
Code: [Select]
let result =
    db.ImportSymbolTableRecords<TextStyleTable>(@"F:\gile\Templates\Standard2010.dwt", "Arial_Std", "Romans_Std")

14
Layouts and printing / Plot to 3d DWF
« on: July 15, 2012, 04:23:08 PM »
Code: [Select]
using System.IO;
using System.Text;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.PlottingServices;
 
namespace Plottings
{
    public class PlotTo3dDwf
    {
        private string dwgFile, dwfFile, dsdFile, title, outputDir;
 
        public PlotTo3dDwf()
        {
            outputDir = (string)Application.GetSystemVariable("DWGPREFIX");
            string name = (string)Application.GetSystemVariable("DWGNAME");
            dwgFile = Path.Combine(outputDir, name);
            title = Path.GetFileNameWithoutExtension(name);
            dwfFile = Path.ChangeExtension(dwgFile, "dwf");
            dsdFile = Path.ChangeExtension(dwfFile, ".dsd");
        }
 
        public PlotTo3dDwf(string outputDir)
            : this()
        {
            this.outputDir = outputDir;
        }
 
        public void Publish()
        {
            short bgPlot = (short)Application.GetSystemVariable("BACKGROUNDPLOT");
            Application.SetSystemVariable("BACKGROUNDPLOT", 0);
            try
            {
                if (!Directory.Exists(outputDir))
                    Directory.CreateDirectory(outputDir);
 
                using (DsdData dsd = new DsdData())
                using (DsdEntryCollection dsdEntries = new DsdEntryCollection())
                {
                    // add the Model layout to the entry collection
                    DsdEntry dsdEntry = new DsdEntry();
                    dsdEntry.DwgName = dwgFile;
                    dsdEntry.Layout = "Model";
                    dsdEntry.Title = title;
                    dsdEntry.Nps = "0";
                    dsdEntries.Add(dsdEntry);
                    dsd.SetDsdEntryCollection(dsdEntries);
 
                    // set DsdData
                    dsd.Dwf3dOptions.PublishWithMaterials = true;
                    dsd.Dwf3dOptions.GroupByXrefHierarchy = true;
                    dsd.SetUnrecognizedData("PwdProtectPublishedDWF", "FALSE");
                    dsd.SetUnrecognizedData("PromptForPwd", "FALSE");
                    dsd.SheetType = SheetType.SingleDwf;
                    dsd.NoOfCopies = 1;
                    dsd.ProjectPath = outputDir;
                    dsd.IsHomogeneous = true;
 
                    if (File.Exists(dsdFile))
                        File.Delete(dsdFile);
 
                    // write the DsdData file
                    dsd.WriteDsd(dsdFile);
 
                    // get the Dsd File contents
                    string str;
                    using (StreamReader sr = new StreamReader(dsdFile, Encoding.Default))
                    {
                        str = sr.ReadToEnd();
                    }
                    // edit the contents
                    str = str.Replace("Has3DDWF=0", "Has3DDWF=1");
                    str = str.Replace("PromptForDwfName=TRUE", "PromptForDwfName=FALSE");
                    // rewrite the Dsd file
                    using (StreamWriter sw = new StreamWriter(dsdFile, false, Encoding.Default))
                    {
                        sw.Write(str);
                    }
 
                    // import the Dsd file new contents in the DsdData
                    dsd.ReadDsd(dsdFile);
 
                    File.Delete(dsdFile);
 
                    PlotConfig pc = PlotConfigManager.SetCurrentConfig("DWF6 ePlot.pc3");
                    Application.Publisher.PublishExecute(dsd, pc);
                }
            }
            finally
            {
                Application.SetSystemVariable("BACKGROUNDPLOT", bgPlot);
            }
        }
    }
}

15
Windows forms / NumerixBox and IntegerBox
« on: January 08, 2012, 01:14:26 PM »
NumericBox and IntegerBox. (EDIT: added an AngleBox)

These two three little classes can be added to a project to add new controls.
They inhertit from TextBox and have some specfic properties and overload methods to insure valid values.
An ErrorProvider is used in case of non valid entry.
Specific properties are available in the Visual Studio Properties window for the control instance.

IntegerBox
Specific properties:
- ErrorMsg: the message displayed by the ErrorProvider for incorrect value (default: "Incorrect number").
- Maximum: the maximum allowed value (default: 2147483647)
- Minimum: the minimum allowed value (default: -2147483648)
- Value: the value as integer (default 0). Setting this value overwrite the Text property.

Code: [Select]
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace ControlLibrary
{
    /// <summary>
    /// Defines a control that can be used to display or edit an integer.
    /// </summary>
    public class IntegerBox : TextBox
    {
        private ErrorProvider _errorProvider;
        private int _value;

        /// <summary>
        /// Set default values.
        /// </summary>
        public IntegerBox()
        {
            _errorProvider = new ErrorProvider();
            ErrorMsg = "Incorrect number";
            Minimum = -2147483648;
            Maximum = 2147483647;
            _value = 0;
        }

        /// <summary>
        /// Get or set the error provider message.
        /// </summary>
        public string ErrorMsg { get; set; }

        /// <summary>
        /// Get or set the minimum allowed value.
        /// </summary>
        public int Minimum { get; set; }

        /// <summary>
        /// Get or set the maximum allowed value.
        /// </summary>
        public int Maximum { get; set; }

        /// <summary>
        /// Get or set the value as integer.
        /// </summary>
        public int Value
        {
            get { return _value; }
            set
            {
                _value = value;
                this.Text = _value.ToString();
                this.OnValidating(new CancelEventArgs());
            }
        }

        /// <summary>
        /// Evaluates if the value is a valid integer according to MinValue and MaxValue.
        /// If not, cancels the validation and displayd an error provider.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnValidating(System.ComponentModel.CancelEventArgs e)
        {
            base.OnValidating(e);
            int i;
            if (!int.TryParse(this.Text, out i) || i < Minimum || i > Maximum)
            {
                e.Cancel = true;
                this.Select(0, this.Text.Length);
                _errorProvider.SetError(this, ErrorMsg);
            }
        }

        /// <summary>
        /// Updates Value.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnValidated(EventArgs e)
        {
            base.OnValidated(e);
            _value = Convert.ToInt32(this.Text);
        }

        /// <summary>
        /// Hide the error provider.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            _errorProvider.SetError(this, "");
        }
    }
}

NumericBox
Specific properties:
- DecimalPlaces: the number of digits displayed in the box after validation (default 0)
- ErrorMsg: the message displayed by the ErrorProvider for incorrect value (default: "Incorrect number").
- Value: the value as double (default 0.0). Setting this value overwrite the Text property. This value may be more accurate than the displayed one.

Code: [Select]
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace ControlLibrary
{
    /// <summary>
    /// Defines a control that can be used to display or edit a real number (double).
    /// </summary>
    public class NumericBox : TextBox
    {
        private double _value;
        private ErrorProvider _errorProvider;
        private int _decimalPlaces;
        protected string _stringFormat;

        /// <summary>
        /// Set default values.
        /// </summary>
        public NumericBox()
        {
            _value = 0;
            _errorProvider = new ErrorProvider();
            ErrorMsg = "Incorrect number";
            SetFormat();
        }

        /// <summary>
        /// Get or set the number of digits diplayed in the box.
        /// </summary>
        public int DecimalPlaces
        {
            get { return _decimalPlaces; }
            set { _decimalPlaces = value; SetFormat(); }
        }

        /// <summary>
        /// Get or set the ErrorProvider message.
        /// </summary>
        public string ErrorMsg { get; set; }

        /// <summary>
        /// Get or set the number value as double (may be more accurate than the displayed one).
        /// Updates the Text according to DecimalPlaces.
        /// </summary>
        public virtual double Value
        {
            get { return _value; }
            set {  _value = value;  this.Text = _value.ToString(_stringFormat); }
        }

        /// <summary>
        /// Evaluates if the value is a valid real number.
        /// If not, cancels the validation and displayd the ErrorProvider icon.
        /// </summary>
        /// <param name="e">The event data</param>
        protected override void OnValidating(CancelEventArgs e)
        {
            double d;
            if (!double.TryParse(this.Text, out d))
            {
                e.Cancel = true;
                this.Select(0, this.Text.Length);
                _errorProvider.SetError(this, ErrorMsg);
            }
            base.OnValidating(e);
        }

        /// <summary>
        /// Updates Text and Value.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnValidated(EventArgs e)
        {
            _value = Convert.ToDouble(this.Text);
            this.Text = _value.ToString(_stringFormat);
            base.OnValidated(e);
        }

        /// <summary>
        /// Hide the ErrorProvider icon.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnTextChanged(EventArgs e)
        {
            _errorProvider.SetError(this, "");
            base.OnTextChanged(e);
        }

        /// <summary>
        /// Creates a format string according to DecimalPlaces value.
        /// Updates the Format property.
        /// </summary>
        private void SetFormat()
        {
            if (DecimalPlaces < 1)
                _stringFormat = "0";
            else
            {
                _stringFormat = "0.";
                for (int i = 0; i < DecimalPlaces; i++) _stringFormat += "0";
            }
        }
    }
}

AngleBox (inherits from NumericBox)
Specific Property:
- UnitFormat: the unit format for the Text displayed. The Value property is always expressed in radians.

Code: [Select]
using System;

namespace ControlLibrary
{
    /// <summary>
    /// Defines a control that can be used to display or edit an angular value (double).
    /// </summary>
    public class AngleBox : NumericBox
    {
        private double _value;

        /// <summary>
        /// Angular units enumeration.
        /// </summary>
        public enum Unit
        {
            Degree,
            Grade,
            Radian
        }

        /// <summary>
        /// Define the default angular unit.
        /// </summary>
        public AngleBox()
        {
            this.UnitFormat = Unit.Degree;
        }

        /// <summary>
        /// Get or set the angular format (unit).
        /// </summary>
        public Unit UnitFormat { get; set; }

        /// <summary>
        /// Get or set the angle value in radians.
        /// Updates the Text property according to UnitFormat and DecimalPlaces.
        /// </summary>
        public override double Value
        {
            get { return _value; }
            set { _value = value; this.Text = AngleToString(_value); }
        }

        /// <summary>
        /// Updates Text and Value properties.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnValidated(EventArgs e)
        {
            _value = StringToAngle(this.Text);
            this.Text = AngleToString(_value);
            base.OnValidated(e);
        }

        /// <summary>
        /// Converts a string into an angle value in radians.
        /// </summary>
        /// <param name="txt">The angle expressed according to UnitFormat.</param>
        /// <returns>The angle value in radians.</returns>
        private double StringToAngle(string txt)
        {
            switch (UnitFormat)
            {
                case Unit.Degree:
                    return Convert.ToDouble(txt) * Math.PI / 180.0;
                case Unit.Grade:
                    return Convert.ToDouble(txt) * Math.PI / 200.0;
                default: //Radian
                    return Convert.ToDouble(txt);
            }
        }

        /// <summary>
        /// Converts an angle value in radians into a string.
        /// </summary>
        /// <param name="val">The angle value in radians..</param>
        /// <returns>The angle expressed according to UnitFormat</returns>
        private string AngleToString(double val)
        {
            switch (UnitFormat)
            {
                case Unit.Degree:
                    return (val * 180.0 / Math.PI).ToString(_stringFormat);
                case Unit.Grade:
                    return (val * 200.0 / Math.PI).ToString(_stringFormat);
                default: // Radian
                    return val.ToString(_stringFormat);
            }
        }
    }
}

Pages: [1] 2