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.


Messages - (gile)

Pages: [1] 2 3 ... 6
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 / Re: Circle tangents
« on: September 15, 2013, 04:31:56 PM »
As i encountered some issues with the CircularArc3d.IntersectWith() method in some randomly 3d rotated planes, I decided to try the CircularArc2d route it seems to work fine.

The two methods shown upper now call equivalent extension methods for the CircularArc2d type after converting the CircularArc3d instance into CircularArc2d ones.
The Point2d.Convert3d() extension method is extracted from the GeometryExtensions library.

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 Point2d structure.
    /// </summary>
    public static class Point2dExtensions
    {       
        /// <summary>
        /// Converts a 2d point into a 3d point according to the plane defined by
        /// the specified normal vector and elevation.
        /// </summary>
        /// <param name="pt">The instance to which the method applies.</param>
        /// <param name="normal">The normal vector of the plane which the point lies on.</param>
        /// <param name="elevation">The elevation of the plane which the point lies on.</param>
        /// <returns>The corresponding 3d point</returns>
        public static Point3d Convert3d(this Point2d pt, Vector3d normal, double elevation)
        {
            return new Point3d(pt.X, pt.Y, elevation).TransformBy(Matrix3d.PlaneToWorld(normal));
        }
    }

    /// <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 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 arc and point lies on the plane
            Vector3d normal = arc.Normal;
            Matrix3d WCS2OCS = Matrix3d.WorldToPlane(normal);
            double elevation = arc.Center.TransformBy(WCS2OCS).Z;
            if (Math.Abs(elevation - pt.TransformBy(WCS2OCS).Z) < Tolerance.Global.EqualPoint)
                throw new Autodesk.AutoCAD.Runtime.Exception(
                    Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);

            Plane plane = new Plane(Point3d.Origin, normal);
            Matrix3d OCS2WCS = Matrix3d.PlaneToWorld(plane);
            CircularArc2d ca2d = new CircularArc2d(arc.Center.Convert2d(plane), arc.Radius);
            LineSegment2d[] lines2d = ca2d.GetTangentsTo(pt.Convert2d(plane));

            if (lines2d == null)
                return null;

            LineSegment3d[] result = new LineSegment3d[lines2d.Length];
            for (int i = 0; i < lines2d.Length; i++)
            {
                LineSegment2d ls2d = lines2d[i];
                //Point3d p1 = new Point3d(ls2d.StartPoint.X, ls2d.StartPoint.Y, elevation);
                //Point3d p2 = new Point3d(ls2d.EndPoint.X, ls2d.EndPoint.Y, elevation);
                //result[i] = new LineSegment3d(p1.TransformBy(OCS2WCS), p2.TransformBy(OCS2WCS));
                result[i] = new LineSegment3d(ls2d.StartPoint.Convert3d(normal, elevation), ls2d.EndPoint.Convert3d(normal, elevation));
            }
            return result;
        }

        /// <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;
            Matrix3d WCS2OCS = Matrix3d.WorldToPlane(normal);
            double elevation = arc.Center.TransformBy(WCS2OCS).Z;
            if (!(normal.IsParallelTo(other.Normal) &&
                Math.Abs(elevation - other.Center.TransformBy(WCS2OCS).Z) < Tolerance.Global.EqualPoint))
                throw new Autodesk.AutoCAD.Runtime.Exception(
                    Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);

            Plane plane = new Plane(Point3d.Origin, normal);
            Matrix3d OCS2WCS = Matrix3d.PlaneToWorld(plane);
            CircularArc2d ca2d1 = new CircularArc2d(arc.Center.Convert2d(plane), arc.Radius);
            CircularArc2d ca2d2 = new CircularArc2d(other.Center.Convert2d(plane), other.Radius);
            LineSegment2d[] lines2d = ca2d1.GetTangentsTo(ca2d2, flags);

            if (lines2d == null)
                return null;

            LineSegment3d[] result = new LineSegment3d[lines2d.Length];
            for (int i = 0; i < lines2d.Length; i++)
            {
                LineSegment2d ls2d = lines2d[i];
                //Point3d p1 = new Point3d(ls2d.StartPoint.X, ls2d.StartPoint.Y, elevation);
                //Point3d p2 = new Point3d(ls2d.EndPoint.X, ls2d.EndPoint.Y, elevation);
                //result[i] = new LineSegment3d(p1.TransformBy(OCS2WCS), p2.TransformBy(OCS2WCS));
                result[i] = new LineSegment3d(ls2d.StartPoint.Convert3d(normal, elevation), ls2d.EndPoint.Convert3d(normal, elevation));
            }
            return result;
        }
    }

    /// <summary>
    /// Provides extension methods for the CircularArc2d class.
    /// </summary>
    public static class CircularArc2dExtensions
    {
        /// <summary>
        /// Returns the tangents between the active CircularArc2d 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 Point2d to which tangents are searched</param>
        /// <returns>An array of LineSegement2d representing the tangents (2) or null if there is none.</returns>
        public static LineSegment2d[] GetTangentsTo(this CircularArc2d arc, Point2d pt)
        {
            // check if the point is inside the circle
            Point2d center = arc.Center;
            if (pt.GetDistanceTo(center) <= arc.Radius)
                return null;

            Vector2d vec = center.GetVectorTo(pt) / 2.0;
            CircularArc2d tmp = new CircularArc2d(center + vec, vec.Length);
            Point2d[] inters = arc.IntersectWith(tmp);
            if (inters == null)
                return null;
            LineSegment2d[] result = new LineSegment2d[2];
            Vector2d v1 = inters[0] - center;
            Vector2d v2 = inters[1] - center;
            int i = vec.X * v1.Y - vec.Y - v1.X > 0 ? 0 : 1;
            int j = i ^ 1;
            result[i] = new LineSegment2d(inters[0], pt);
            result[j] = new LineSegment2d(inters[1], pt);
            return result;
        }

        /// <summary>
        /// Returns the tangents between the active CircularArc2d 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 CircularArc2d to which searched for tangents.</param>
        /// <param name="flags">An enum value specifying which type of tangent is returned.</param>
        /// <returns>An array of LineSegment2d representing the tangents (maybe 2 or 4) or null if there is none.</returns>
        public static LineSegment2d[] GetTangentsTo(this CircularArc2d arc, CircularArc2d other, TangentType flags)
        {
            // check if a circle is inside the other
            double dist = arc.Center.GetDistanceTo(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;

            CircularArc2d tmp1, tmp2;
            Point2d[] inters;
            Vector2d vec1, vec2, vec = other.Center - arc.Center;
            int i, j;
            LineSegment2d[] result = new LineSegment2d[(int)flags == 3 && !overlap ? 4 : 2];

            // outer tangents
            if (flags.HasFlag(TangentType.Outer))
            {
                if (arc.Radius == other.Radius)
                {
                    Line2d perp = new Line2d(arc.Center, vec.GetPerpendicularVector());
                    inters = arc.IntersectWith(perp);
                    if (inters == null)
                        return null;
                    vec1 = (inters[0] - arc.Center).GetNormal();
                    vec2 = (inters[1] - arc.Center).GetNormal();
                    i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 0 : 1;
                    j = i ^ 1;
                    result[i] = new LineSegment2d(inters[0], inters[0] + vec);
                    result[j] = new LineSegment2d(inters[1], inters[1] + vec);
                }
                else
                {
                    Point2d center = arc.Radius < other.Radius ? other.Center : arc.Center;
                    tmp1 = new CircularArc2d(center, Math.Abs(arc.Radius - other.Radius));
                    tmp2 = new CircularArc2d(arc.Center + vec / 2.0, dist / 2.0);
                    inters = tmp1.IntersectWith(tmp2);
                    if (inters == null)
                        return null;
                    vec1 = (inters[0] - center).GetNormal();
                    vec2 = (inters[1] - center).GetNormal();
                    i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 0 : 1;
                    j = i ^ 1;
                    result[i] = new LineSegment2d(arc.Center + vec1 * arc.Radius, other.Center + vec1 * other.Radius);
                    result[j] = new LineSegment2d(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 CircularArc2d(arc.Center + vec * ratio, dist * ratio);
                inters = arc.IntersectWith(tmp1);
                if (inters == null)
                    return null;
                vec1 = (inters[0] - arc.Center).GetNormal();
                vec2 = (inters[1] - arc.Center).GetNormal();
                i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 2 : 3;
                j = i == 2 ? 3 : 2;
                result[i] = new LineSegment2d(arc.Center + vec1 * arc.Radius, other.Center + vec1.Negate() * other.Radius);
                result[j] = new LineSegment2d(arc.Center + vec2 * arc.Radius, other.Center + vec2.Negate() * other.Radius);
            }
            return result;
        }
    }
}

A Test command which draws all tangents between the two selected circles. the lines are colored according to their order in the LineSegment3d array returned by GetTangentsTo() (1 red, 2 yellow, 3 green, 4 cyan).

Code: [Select]
        [CommandMethod("Test")]
        public void Test()
        {
            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.Inner | TangentType.Outer);
                    if (lines != null)
                    {
                        BlockTableRecord btr =
                            (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                        for (int i = 0; i < lines.Length; i++)
                        {
                            Line line = new Line(lines[i].StartPoint, lines[i].EndPoint);
                            line.ColorIndex = i + 1;
                            btr.AppendEntity(line);
                            tr.AddNewlyCreatedDBObject(line, true);
                        }
                    }
                    tr.Commit();
                }
            }
            catch (System.Exception exn)
            {
                ed.WriteMessage("\n" + exn.Message);
            }
        }


4
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);
               }
           }

5
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);
        }
    }
}

6
AutoCAD talk / Re: AutoCAD Exchange apps now online
« on: August 14, 2013, 08:15:33 PM »
Hi,

You have to code your own limitation / timeout in your trial app.

7
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)

8
Math and Geometry / Re: K-d Tree for AutoCAD Point3d
« on: June 17, 2013, 06:24:41 PM »
Here's the F# version.

The main interest is that it allows parallel execution for building the tree while targeting the Framwork 2.0 so that it can be used with AutoCAD version from 2007 to 2011.

The F# specific constructs and types are only used internally.
The public methods (the same as in the C# version) are writen to be used with other .NET languages (C# or VB) they return Point3d arrays rather than Point3dCollections. A 'Pair' type (class) is used to replace the missing Tuple class in .NET Frameworks prior to 4.0.

The FsPoint3dTree.dll which can be referenced in C# or VB projects (it may be needed to install the F# runtime).

Code: [Select]
    namespace Gile.Point3dTree
     
    open System
    open Autodesk.AutoCAD.Geometry
     
    module private Array =
       let median (compare: 'a -> 'a -> int) (items: 'a[])=
           if items = null || items.Length = 0 then
               failwith "items"
           let l = items.Length
           let k = l / 2
     
           let rec loop f t =
               if f < t then swap f t items.[(f+t) / 2] f t
               else items.[.. k-1], items.[k], items.[k+1 ..]
           and swap a b c f t =
               if a < b then
                   if compare items.[a] c > -1 then
                       let tmp = items.[b]
                       items.[b] <- items.[a]
                       items.[a] <- tmp
                       swap a (b-1) c f t
                   else
                       swap (a+1) b c f t
               else
                   let n = if compare items.[a] c > 0 then a - 1 else a
                   if k <= n
                   then loop f n
                   else loop (n+1) t
     
           loop 0 (l-1)
     
    type private TreeNode =
       | Empty
       | Node of int * Point3d option * Point3d * TreeNode * TreeNode 
     
    /// <summary>
    /// Defines a tuple (double) to be used with versions of .NET Framework prior to 4.0
    /// </summary>
    /// <typeparam name="T1">Type of the first item.</typeparam>
    /// <typeparam name="T2">Type of the second item.</typeparam>
    /// <param name="item1">First item.</param>
    /// <param name="item2">Second item.</param>
    type Pair<'T1, 'T2>(item1, item2) =
     
       /// <summary>
       /// Gets the first item of the pair.
       /// </summary>
       member this.Item1 with get() = item1
     
       /// <summary>
       /// Gets the second item of the pair.
       /// </summary>
       member this.Item2 with get() = item2
     
       /// <summary>
       /// Creates a new instance of Pair.
       /// </summary>
       /// <param name="item1">First item of the pair.</param>
       /// <param name="item2">Second item of the pair.</param>
       /// <returns>a new Pair containing the items.</returns>
       static member Create(item1, item2) = Pair(item1, item2)
     
     
    /// <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>
    type Point3dTree (points: Point3d seq, ignoreZ: bool) =
       do if points = null then raise (System.ArgumentNullException("points"))
     
       let dimension = if ignoreZ then 2 else 3
       let sqrDist (p1: Point3d) (p2: Point3d) =
           if ignoreZ
           then (p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y)
           else (p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y) + (p1.Z - p2.Z) * (p1.Z - p2.Z)
     
       let rec shift n d =
           if n >>> d > 1 then shift n (d+1) else d
       let pDepth = shift System.Environment.ProcessorCount 0
     
       let create pts =
           let rec loop depth parent = function
               | [||] -> Empty
               | pts ->
                   let d = depth % dimension
                   let left, median, right =
                       pts |> Array.median(fun (p1: Point3d) p2 -> compare p1.[d] p2.[d])
                   let children =
                       if depth < pDepth then
                           [ async { return loop (depth + 1) (Some(median)) left };
                             async { return loop (depth + 1) (Some(median)) right } ]
                           |> Async.Parallel
                           |> Async.RunSynchronously
                       else
                           [| loop (depth + 1) (Some(median)) left;
                              loop (depth + 1) (Some(median)) right |]
                   Node(depth, parent, median, children.[0], children.[1])
           loop 0 None pts
     
       let root = points |> Seq.distinct |> Seq.toArray |> create
     
       let rec findNeighbour location node (current, bestDist) =
           match node with
           | Empty -> (current, bestDist)
           | Node(depth, _, point, left, right) ->
               let dist = sqrDist point location
               let d = depth % dimension
               let bestPair =
                   if dist < bestDist
                   then point, dist
                   else current, bestDist
               if bestDist < (location.[d] - point.[d]) * (location.[d] - point.[d]) then
                   findNeighbour location (if location.[d] < point.[d] then left else right) bestPair
               else
                   findNeighbour location left bestPair
                   |> findNeighbour location right
     
       let rec getNeighbours center radius node acc =
           match node with
           | Empty -> acc
           | Node(depth, _, point, left, right) ->
               let acc = if sqrDist center point <= radius then point :: acc else acc
               let d= depth % dimension;
               let coordCen, coordPt = center.[d], point.[d]
               if (coordCen - coordPt) * (coordCen - coordPt) > radius then
                   getNeighbours center radius (if coordCen < coordPt then left else right) acc
               else
                   getNeighbours center radius left acc
                   |> getNeighbours center radius right
     
       let rec getKNeighbours center number node (pairs: (float * Point3d) list) =
           match node with
           | Empty -> pairs
           | Node(depth, _, point, left, right) ->
               let dist = sqrDist center point
               let pairs =
                   match pairs.Length with
                   | 0 -> [ (dist, point) ]
                   | l when l < number ->
                       if (dist > fst pairs.Head)
                       then (dist, point) :: pairs
                       else pairs.Head :: (dist, point) :: pairs.Tail
                   | _ ->
                       if dist < fst pairs.Head
                       then ((dist, point) :: pairs.Tail) |> List.sortBy(fun p -> -fst p)
                       else pairs
               let d = depth % dimension
               let coordCen, coordCur = center.[d], point.[d]
               if (coordCen - coordCur) * (coordCen - coordCur) > fst pairs.Head then
                   getKNeighbours center number (if coordCen < coordCur then left else right) pairs
               else
                   getKNeighbours center number left pairs
                   |> getKNeighbours center number right
     
       let rec findRange (lowerLeft: Point3d) (upperRight: Point3d) node (acc: Point3d list) =
           match node with
           | Empty -> acc
           | Node(depth, _, point, left, right) ->
               let acc =
                   if ignoreZ then
                       if point.X >= lowerLeft.X && point.X <= upperRight.X &&
                          point.Y >= lowerLeft.Y && point.Y <= upperRight.Y
                       then point :: acc else acc
                   else
                       if point.X >= lowerLeft.X && point.X <= upperRight.X &&
                          point.Y >= lowerLeft.Y && point.Y <= upperRight.Y &&
                          point.Z >= lowerLeft.Z && point.Z <= upperRight.Z
                       then point :: acc else acc
               let d = depth % dimension
               if upperRight.[d] < point.[d]
               then findRange lowerLeft upperRight left acc
               elif lowerLeft.[d] > point.[d]
               then findRange lowerLeft upperRight right acc
               else findRange lowerLeft upperRight left acc
                    |> findRange lowerLeft upperRight right
     
       let rec getConnexions node radius (nodes, pairs) =
           match node with
           | Empty -> (nodes, pairs)
           | Node(depth, parent, point, left, right) ->
               let pairs =
                   nodes
                   |> List.fold(fun acc (n: TreeNode) ->
                       match n with
                       | Empty -> acc
                       | Node(dep, par, _, _, _) ->
                           let pt = par.Value
                           let d = (dep + 1) % dimension
                           if (point.[d] - pt.[d]) * (point.[d] - pt.[d]) <= radius
                           then getNeighbours point radius n acc
                           else acc)
                       (getNeighbours point radius left []
                       |> getNeighbours point radius right)
                   |> List.map(fun p -> Pair<Point3d, Point3d>(point, p))
                   |> List.fold(fun acc p -> p :: acc) pairs
               let nodes =
                   match nodes with
                   | [] -> if right = Empty then [] else [right]
                   | h :: t  ->
                       if right = Empty
                       then if left = Empty then t else nodes
                       else right :: nodes
               getConnexions left radius (nodes, pairs)
               |> getConnexions right radius           
     
       /// <summary>
       /// Creates an new instance of Point3dTree with ignoreZ = false (default).
       /// </summary>
       /// <param name="points">The Point3d collection to fill the tree.</param>
       new (points: Point3d seq) = Point3dTree(points, false)
     
       /// <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>
       member this.NearestNeighbour(location) =
           match root with
           | Empty -> raise (System.ArgumentNullException("root"))
           | Node(_, _, point, _, _) ->
               findNeighbour location root (point, Double.MaxValue)
               |> fst
     
       /// <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>
       member this.NearestNeighbours(center, radius) =
           getNeighbours center (radius * radius) root []
           |> List.toArray
     
       /// <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>
       member this.NearestNeighbours(center, number) =
           getKNeighbours center number root []
           |> List.map(fun p -> snd p)
           |> List.toArray
     
       /// <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>
       member this.BoxedRange(pt1: Point3d, pt2: Point3d) =
           let lowerLeft = Point3d(min pt1.X pt2.X, min pt1.Y pt2.Y, min pt1.Z pt2.Z)
           let upperRight = Point3d(max pt1.X pt2.X, max pt1.Y pt2.Y, max pt1.Z pt2.Z)
           findRange lowerLeft upperRight root []
           |> List.toArray
     
       /// <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>
       member this.ConnectAll(radius) =
           getConnexions root (radius * radius) ([], [])
           |> snd
           |> List.toArray

9
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];
           }
       }
    }
     

10
Polylines / Re: Offset curve toward centroid
« on: March 20, 2013, 07:11:04 PM »
According to Tony Tanzillo's advices at TheSwamp, here's a safer way because of the using of DisposableSet class to insure the newly created polylines to be diposed in case an exception is thrown.

Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;

// With the help of Tony Tanzillo's advices
// http://www.theswamp.org/index.php?topic=31862.msg494503#msg494503

namespace OffsetPolylineSample
{
    /// <summary>
    /// Provides the Offset() extension method for the Polyline type
    /// </summary>
    public static class PolylineExtension
    {
        /// <summary>
        /// Enumeration of offset side options
        /// </summary>
        public enum OffsetSide
        {
            In, Out, Left, Right, Both
        }

        /// <summary>
        /// Offset the source polyline to specified side(s).
        /// </summary>
        /// <param name="source">The polyline to be offseted.</param>
        /// <param name="offsetDist">The offset distance.</param>
        /// <param name="side">The offset side(s).</param>
        /// <returns>A polyline sequence resulting from the offset of the source polyline.</returns>
        public static IEnumerable<Polyline> Offset(this Polyline source, double offsetDist, OffsetSide side)
        {
            offsetDist = Math.Abs(offsetDist);
            using (var plines = new DisposableSet<Polyline>())
            {
                IEnumerable<Polyline> offsetRight = source.GetOffsetCurves(offsetDist).Cast<Polyline>();
                plines.UnionWith(offsetRight);
                IEnumerable<Polyline> offsetLeft = source.GetOffsetCurves(-offsetDist).Cast<Polyline>();
                plines.UnionWith(offsetLeft);
                double areaRight = offsetRight.Select(pline => pline.Area).Sum();
                double areaLeft = offsetLeft.Select(pline => pline.Area).Sum();
                switch (side)
                {
                    case OffsetSide.In:
                        return plines.RemoveRange(
                           areaRight < areaLeft ? offsetRight : offsetLeft);
                    case OffsetSide.Out:
                        return plines.RemoveRange(
                           areaRight < areaLeft ? offsetLeft : offsetRight);
                    case OffsetSide.Left:
                        return plines.RemoveRange(offsetLeft);
                    case OffsetSide.Right:
                        return plines.RemoveRange(offsetRight);
                    case OffsetSide.Both:
                        plines.Clear();
                        return offsetRight.Concat(offsetLeft);
                    default:
                        return null;
                }
            }
        }
    }

    /// <summary>
    /// Represents a set of disposable values.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DisposableSet<T> : HashSet<T>, IDisposable
        where T : IDisposable
    {
        /// <summary>
        /// Disposes all items of the current DisposableSet object.
        /// </summary>
        public void Dispose()
        {
            if (base.Count > 0)
            {
                System.Exception last = null;
                foreach (T item in this)
                {
                    if (item != null)
                    {
                        try
                        {
                            item.Dispose();
                        }
                        catch (System.Exception ex)
                        {
                            last = last ?? ex;
                        }
                    }
                }
                this.Clear();
                if (last != null)
                    throw last;
            }
        }

        /// <summary>
        /// Removes all elements in the specified collection from the current DisposableSet object.
        /// </summary>
        /// <param name="items">The collection of items to remove from the current DisposableSet object.</param>
        /// <returns>The collection of items to remove.</returns>
        public IEnumerable<T> RemoveRange(IEnumerable<T> items)
        {
            base.ExceptWith(items);
            return items;
        }
    }
}

11
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);
            }
        }

12
Polylines / Re: Offset curve toward centroid
« on: March 17, 2013, 09:28:39 AM »
Hi,

To get a Polyline centroid, an easier (and faster) way is to add to your project the GeometryExtensions.dll and simply call the Polyline.Centroid() extension method.

To insure offseting inside a (closed) polyline, you can offset it on both sides and compare the resulting polylines areas.
Here's a little sample which uses this way.

In the PolylineExtension static class, the Polyline.Offset() extension method is defined. This method can be called as an instance method and requires an argument to specify the offset side. This argument is a member of the OffsetSide enum defined in the PolylineExtension class too.

The PolylineExtension class:
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;

namespace OffsetPolylineSample
{
    /// <summary>
    /// Provides a Offset() extension methode for the Polyline type
    /// </summary>
    public static class PolylineExtension
    {
        /// <summary>
        /// Enumeration of offset side options
        /// </summary>
        public enum OffsetSide { In, Out, Left, Right, Both }

        /// <summary>
        /// Offset the source polyline to specified side(s).
        /// </summary>
        /// <param name="source">The polyline to be offseted.</param>
        /// <param name="offsetDist">The offset distance.</param>
        /// <param name="side">The offset side(s).</param>
        /// <returns>A polyline sequence resulting from the offset of the source polyline.</returns>
        public static IEnumerable<Polyline> Offset(this Polyline source, double offsetDist, OffsetSide side)
        {
            offsetDist = Math.Abs(offsetDist);
            IEnumerable<Polyline> offsetRight = source.GetOffsetCurves(offsetDist).Cast<Polyline>();
            double areaRight = offsetRight.Select(pline => pline.Area).Sum();
            IEnumerable<Polyline> offsetLeft = source.GetOffsetCurves(-offsetDist).Cast<Polyline>();
            double areaLeft = offsetLeft.Select(pline => pline.Area).Sum();
            switch (side)
            {
                case OffsetSide.In:
                    if (areaRight < areaLeft)
                    {
                        offsetLeft.Dispose();
                        return offsetRight;
                    }
                    else
                    {
                        offsetRight.Dispose();
                        return offsetLeft;
                    }
                case OffsetSide.Out:
                    if (areaRight < areaLeft)
                    {
                        offsetRight.Dispose();
                        return offsetLeft;
                    }
                    else
                    {
                        offsetLeft.Dispose();
                        return offsetRight;
                    }
                case OffsetSide.Left:
                    offsetRight.Dispose();
                    return offsetLeft;
                case OffsetSide.Right:
                    offsetLeft.Dispose();
                    return offsetRight;
                case OffsetSide.Both:
                    return offsetRight.Concat(offsetLeft);
                default:
                    return null;
            }
        }

        private static void Dispose(this IEnumerable<Polyline> plines)
        {
            foreach (Polyline pline in plines)
            {
                pline.Dispose();
            }
        }
    }
}

A testing command:
Code: [Select]
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

namespace OffsetPolylineSample
{
    public class CommandMethods
    {
        [CommandMethod("Test", CommandFlags.Modal)]
        public void Test()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            PromptDistanceOptions pdo =
                new PromptDistanceOptions("\nSpecify the offset distance: ");
            pdo.AllowZero = false;
            PromptDoubleResult pdr = ed.GetDistance(pdo);
            if (pdr.Status != PromptStatus.OK) return;
            double offsetDist = pdr.Value;

            PromptKeywordOptions pko =
                new PromptKeywordOptions("\nEnter the offset side [In/Out/Left/Right/Both]", "In Out Left Right Both");
            PromptResult pr = ed.GetKeywords(pko);
            if (pr.Status != PromptStatus.OK) return;
            PolylineExtension.OffsetSide side;
            switch (pr.StringResult)
            {
                case "In": side = PolylineExtension.OffsetSide.In; break;
                case "Out": side = PolylineExtension.OffsetSide.Out; break;
                case "Left": side = PolylineExtension.OffsetSide.Left; break;
                case "Right": side = PolylineExtension.OffsetSide.Right; break;
                default: side = PolylineExtension.OffsetSide.Both; break;
            }

            PromptEntityOptions peo = new PromptEntityOptions("\nSelect a polyline: ");
            peo.SetRejectMessage("Only a polyline !");
            peo.AddAllowedClass(typeof(Polyline), true);

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                while (true)
                {
                    PromptEntityResult per = ed.GetEntity(peo);
                    if (per.Status != PromptStatus.OK) break;

                    Polyline pline = (Polyline)tr.GetObject(per.ObjectId, OpenMode.ForRead);
                    foreach (Polyline pl in pline.Offset(offsetDist, side))
                    {
                        btr.AppendEntity(pl);
                        tr.AddNewlyCreatedDBObject(pl, true);
                    }
                    db.TransactionManager.QueueForGraphicsFlush();
                }
                tr.Commit();
            }
        }
    }
}

13
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]);

14
AutoCAD talk / Re: Autoload dll from a personal folder
« on: February 13, 2013, 10:29:59 PM »
Hi,

As far as I know, you cannot use the autoloader from a different folder of those provided by Autodesk:
- %appdata%\Autodesk\ApplicationPlugins
- %programfiles%\Autodesk\ApplicationPlugins
- %programdata%\Autodesk\ApplicationPlugins

If you have rights to write in the registry, you can register your application:
http://through-the-interface.typepad.com/through_the_interface/2006/09/automatic_loadi.html

15
Selection sets / Re: Selection Ordinate Dimensions with Filter (dxf code)
« on: February 02, 2013, 03:36:48 PM »
The upper method isn't very strict, even it will work fine with ordinate dimensions because it uses a bitwise test on values wich aren't binary codes: it will work with 6, 5 (3 points angular dimensions), 3 (diameter dimension) but not with 4, 2, 1 or 0.

Another way, better in my opinion, is to combinate a classical selection filter and a Editor.SelectionAdded event handler to filter the selected objects.

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

            TypedValue[] filter = { new TypedValue(0, "dimension"), new TypedValue(3, "ISO-25") };

            ed.SelectionAdded += ed_SelectionAdded;
            PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
            ed.SelectionAdded -= ed_SelectionAdded;
            if (psr.Status != PromptStatus.OK) return;
            ed.SetImpliedSelection(psr.Value);
        }

        void ed_SelectionAdded(object sender, SelectionAddedEventArgs e)
        {
            for (int i = 0; i < e.AddedObjects.Count; i++)
            {
                if (e.AddedObjects[i].ObjectId.ObjectClass.Name != "AcDbOrdinateDimension")
                    e.Remove(i);
            }
        }

VB
Code: [Select]
        <CommandMethod("Test")> _
        Public Sub test()
            Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor

            Dim filter As TypedValue() = { New TypedValue(0, "dimension"), new TypedValue(3, "ISO-25") }

            AddHandler ed.SelectionAdded, AddressOf ed_SelectionAdded
            Dim psr As PromptSelectionResult = ed.GetSelection(New SelectionFilter(filter))
            RemoveHandler ed.SelectionAdded, AddressOf ed_SelectionAdded
            If psr.Status <> PromptStatus.OK Then
                Return
            End If
            ed.SetImpliedSelection(psr.Value)
        End Sub

        Private Sub ed_SelectionAdded(sender As Object, e As SelectionAddedEventArgs)
            For i As Integer = 0 To e.AddedObjects.Count - 1
                If e.AddedObjects(i).ObjectId.ObjectClass.Name <> "AcDbOrdinateDimension" Then
                    e.Remove(i)
                End If
            Next
        End Sub

Pages: [1] 2 3 ... 6