Show Posts

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


Topics - (gile)

Pages: [1] 2
1
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);
            }
        }

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

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

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

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

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

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

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

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

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

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

            private Dictionary<string, TextInfo> _attInfos;

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

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

A little using example to insert a block

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

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

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

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

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

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

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

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

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

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

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

VB
Code: [Select]
    Module Extensions

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

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

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

    End Module

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

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

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

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

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

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

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

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

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

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


Using examples

Ellipses with incremented major axis.

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



Parabolas with decremented chords and incrmented sagittas

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


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

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

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



 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Code: [Select]
using System;

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

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

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

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

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

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

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

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

10
Blocks / Block attributes extraction
« on: November 06, 2011, 10:51:54 AM »
Hi,

Here's a little example for attributes extraction.

I try to write it in a "declarative" style using the Linq extension methods and some others defined in the Extension class (thanks to Thorsten 'kaefer' @ TheSwamp for its nice 'evil' ones).

The extraction is collected in a System.Data.DataTable so that it can be easily converted to an AutoCAD table, an Exel sheet and so on.
The DataTable.WriteXls() extension method uses late binding while interacting with Excel application to avoid version reference dependency.

The BlockAttribute class which defines a new Type which main properties are:
- Block:  the block (effective) name ;
- Attributes: a dictionary for the block attributes where Key is the tag and Value the text string.
The BlockAttributeEqualityComparer class implements the IEqualityComparer<BlockAttribute> interface so that BlockAttribute collections can be used with some Linq extension methods as GroupBy().
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace AttributeExtraction
{
    public class BlockAttribute
    {
        private string _name;
        private Dictionary<string, string> _atts;

        // Public read only properties
        public string Name
        {
            get { return _name; }
        }

        public Dictionary<string, string> Attributes
        {
            get { return _atts; }
        }

        public string this[string key]
        {
            get { return _atts[key.ToUpper()]; }
        }

        // Constructors
        public BlockAttribute(BlockReference br)
        {
            SetProperties(br);
        }

        public BlockAttribute(ObjectId id)
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            using (Transaction tr = doc.TransactionManager.StartTransaction())
            {
                SetProperties(tr.GetObject(id, OpenMode.ForRead) as BlockReference);
            }
        }

        // Public method
        new public string ToString()
        {
            if (_atts != null && _atts.Count > 0)
                return string.Format("{0}: {1}",
                    _name,
                    _atts.Select(a => string.Format("{0}={1}", a.Key, a.Value))
                        .Aggregate((a, b) => string.Format("{0}; {1}", a, b)));
            return _name;
        }

        // Private method
        private void SetProperties(BlockReference br)
        {
            if (br == null) return;
            _name = br.GetEffectiveName();
            _atts = new Dictionary<string, string>();
            br.AttributeCollection
                .GetObjects<AttributeReference>()
                .Iterate(att => _atts.Add(att.Tag.ToUpper(), att.TextString));
        }
    }

    public class BlockAttributeEqualityComparer : IEqualityComparer<BlockAttribute>
    {
        public bool Equals(BlockAttribute x, BlockAttribute y)
        {
            return
                x.Name.Equals(y.Name, StringComparison.CurrentCultureIgnoreCase) &&
                x.Attributes.SequenceEqual(y.Attributes);
        }

        public int GetHashCode(BlockAttribute obj)
        {
            return base.GetHashCode();
        }
    }
}

The Extensions class provides extension methods that allows to write code in a more "declarative" style
and methods to build a DataTable from a BlockAttribute collection and convert this DataTable into xls, csv files or AutoCAD Table.
Code: [Select]
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
using AcDataTable = Autodesk.AutoCAD.DatabaseServices.DataTable;
using Autodesk.AutoCAD.Runtime;

namespace AttributeExtraction
{
    public static class Extensions
    {
        // Thorsten's 'evil' extension methods (thanks again to him)
        public static T GetObject<T>(this ObjectId id) where T : DBObject
        {
            return id.GetObject<T>(OpenMode.ForRead);
        }

        public static T GetObject<T>(this ObjectId id, OpenMode openMode) where T : DBObject
        {
            return id.GetObject(openMode, false, false) as T;
        }

        public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
        {
            return ids.GetObjects<T>(OpenMode.ForRead);
        }

        public static IEnumerable<T> GetObjects<T>(this IEnumerable ids, OpenMode openMode) where T : DBObject
        {
            RXClass rxc = RXObject.GetClass(typeof(T));
            return ids
                .Cast<ObjectId>()
                .Where(id => id.ObjectClass == rxc || id.ObjectClass.IsDerivedFrom(rxc))
                .Select(id => (T)id.GetObject(openMode, false, false));
        }

        // Mimics the F# Seq.iter function
        public static void Iterate<T>(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection) action(item);
        }

        // Mimics the F# Seq.iteri function
        public static void Iterate<T>(this IEnumerable<T> collection, Action<T, int> action)
        {
            int i = 0;
            foreach (T item in collection) action(item, i++);
        }

        // Gets the block effective name (anonymous dynamic blocs)
        public static string GetEffectiveName(this BlockReference br)
        {
            if (br.IsDynamicBlock)
                return br.DynamicBlockTableRecord.GetObject<BlockTableRecord>().Name;
            return br.Name;
        }

        // Creates a Datatable from a BlockAttribute collection
        public static System.Data.DataTable ToDataTable(this IEnumerable<BlockAttribute> blockAtts, string name)
        {
            System.Data.DataTable dTable = new System.Data.DataTable(name);
            dTable.Columns.Add("Name", typeof(string));
            dTable.Columns.Add("Quantity", typeof(int));
            blockAtts
                .GroupBy(blk => blk, (blk, blks) => new { Block = blk, Count = blks.Count() }, new BlockAttributeEqualityComparer())
                .Iterate(row =>
                {
                    System.Data.DataRow dRow = dTable.Rows.Add(row.Block.Name, row.Count);
                    row.Block.Attributes.Iterate(att =>
                    {
                        if (!dTable.Columns.Contains(att.Key))
                            dTable.Columns.Add(att.Key);
                        dRow[att.Key] = att.Value;
                    });
                });
            return dTable;
        }

        // Converts a Datatble into a two dimensional object array
        public static object[,] ToArray(this System.Data.DataTable dTable)
        {
            int numRows = dTable.Rows.Count;
            int numColoumns = dTable.Columns.Count;
            object[,] result = new object[numRows + 1, numColoumns];
            string[] colNames = dTable.GetColumnNames().ToArray();
            for (int i = 0; i < numColoumns; i++)
            {
                result[0, i] = colNames[i];
            }
            for (int i = 0; i < numRows; i++)
            {
                for (int j = 0; j < numColoumns; j++)
                {
                    result[i + 1, j] = dTable.Rows[i][j];
                }
            }
            return result;
        }

        // Gets the column names collection of the Datatable
        public static IEnumerable<string> GetColumnNames(this System.Data.DataTable dataTbl)
        {
            return dataTbl.Columns.Cast<System.Data.DataColumn>().Select(col => col.ColumnName);
        }

        // Writes a xls file from the datatable
        public static void WriteXls(this System.Data.DataTable dataTbl, string filename, string sheetName, bool visible)
        {
            object mis = Type.Missing;
            object xlApp = LateBinding.GetOrCreateInstance("Excel.Application");
            xlApp.Set("DisplayAlerts", false);
            object workbooks = xlApp.Get("Workbooks");
            object workbook, worksheet;
            if (File.Exists(filename))
                workbook = workbooks.Invoke("Open", filename);
            else
                workbook = workbooks.Invoke("Add", mis);
            if (string.IsNullOrEmpty(sheetName))
                worksheet = workbook.Get("Activesheet");
            else
            {
                object worksheets = workbook.Get("Worksheets");
                try
                {
                    worksheet = worksheets.Get("Item", sheetName);
                    worksheet.Get("Cells").Invoke("Clear");
                }
                catch
                {
                    worksheet = worksheets.Invoke("Add", mis);
                    worksheet.Set("Name", sheetName);
                }
            }
            object range = worksheet.Get("Range",
                worksheet.Get("Cells", 1, 1),
                worksheet.Get("Cells", dataTbl.Rows.Count + 1, dataTbl.Columns.Count));
            range.Set("NumberFormat", "@");
            range.Set("Value2", dataTbl.ToArray());
            xlApp.Set("DisplayAlerts", true);
            if (visible)
            {
                xlApp.Set("Visible", true);
            }
            else
            {
                if (File.Exists(filename))
                    workbook.Invoke("Save");
                else
                {
                    int fileFormat =
                        string.Compare("11.0", (string)xlApp.Get("Version")) < 0 &&
                        filename.EndsWith(".xlsx", StringComparison.CurrentCultureIgnoreCase) ?
                        51 : -4143;
                    workbook.Invoke("Saveas", filename, fileFormat, string.Empty, string.Empty, false, false, 1, 1);
                }
                workbook.Invoke("Close");
                workbook = null;
                xlApp.Invoke("Quit");
                xlApp.ReleaseInstance();
                xlApp = null;
            }
        }

        // Writes a csv file from the datatable
        public static void WriteCsv(this System.Data.DataTable dataTbl, string filename)
        {
            using (StreamWriter writer = new StreamWriter(filename))
            {
                writer.WriteLine(dataTbl.GetColumnNames().Aggregate((s1, s2) => string.Format("{0},{1}", s1, s2)));
                dataTbl.Rows
                    .Cast<DataRow>()
                    .Select(row => row.ItemArray.Aggregate((s1, s2) => string.Format("{0},{1}", s1, s2)))
                    .Iterate(line => writer.WriteLine(line));
            }
        }

        // Creates an AutoCAD Table from the datatable
        public static Table ToAcadTable(this System.Data.DataTable dataTbl, double rowHeight, double columnWidth)
        {
            Table tbl = new Table();
            tbl.Rows[0].Height = rowHeight;
            tbl.Columns[0].Width = columnWidth;
            tbl.InsertColumns(0, columnWidth, dataTbl.Columns.Count - 1);
            tbl.InsertRows(0, rowHeight, dataTbl.Rows.Count + 1);
            tbl.Cells[0, 0].Value = dataTbl.TableName;
            dataTbl.GetColumnNames()
                .Iterate((name, i) => tbl.Cells[1, i].Value = name);
            dataTbl.Rows
                .Cast<DataRow>()
                .Iterate((row, i) =>
                    row.ItemArray.Iterate((item, j) =>
                        tbl.Cells[i + 2, j].Value = item));
            return tbl;
        }
    }
}

The LateBinding class provides helpers to write late binding instructions in a more friendly style (thanks to Thorsten and Tony T).
Code: [Select]
using BF = System.Reflection.BindingFlags;

namespace AttributeExtraction
{
    public static class LateBinding
    {
        public static object GetInstance(string appName)
        {
            return System.Runtime.InteropServices.Marshal.GetActiveObject(appName);
        }

        public static object CreateInstance(string appName)
        {
            return System.Activator.CreateInstance(System.Type.GetTypeFromProgID(appName));
        }

        public static object GetOrCreateInstance(string appName)
        {
            try { return GetInstance(appName); }
            catch { return CreateInstance(appName); }
        }

        public static object Get(this object obj, string propName, params object[] parameter)
        {
            return obj.GetType().InvokeMember(propName, BF.GetProperty, null, obj, parameter);
        }

        public static void Set(this object obj, string propName, params object[] parameter)
        {
            obj.GetType().InvokeMember(propName, BF.SetProperty, null, obj, parameter);
        }

        public static object Invoke(this object obj, string methName, params object[] parameter)
        {
            return obj.GetType().InvokeMember(methName, BF.InvokeMethod, null, obj, parameter);
        }

        public static void ReleaseInstance(this object obj)
        {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        }
    }
}

A testing command
Code: [Select]
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace AttributeExtraction
{
    public class Commands
    {
        [CommandMethod("Test")]
        public void Test()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            TypedValue[] filter = { new TypedValue(0, "INSERT") };
            PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
            if (psr.Status != PromptStatus.OK) return;
            PromptPointResult ppr = ed.GetPoint("\nInsertion point: ");
            if (ppr.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                System.Data.DataTable dataTable = psr.Value.GetObjectIds()
                    .Select(id => new BlockAttribute(id.GetObject<BlockReference>()))
                    .ToDataTable("Extraction");
                Table tbl = dataTable.ToAcadTable(9.0, 40.0);
                tbl.Position = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
                BlockTableRecord btr = db.CurrentSpaceId.GetObject<BlockTableRecord>(OpenMode.ForWrite);
                btr.AppendEntity(tbl);
                tr.AddNewlyCreatedDBObject(tbl, true);
                try
                {
                    string filename = (string)AcAp.GetSystemVariable("dwgprefix") + "Extraction.xls";
                    dataTable.WriteXls(filename, null, true);
                }
                catch
                {
                    AcAp.ShowAlertDialog("Failed to open Excel");
                }
                tr.Commit();
            }
        }
    }
}

11
.NET newbies / New AutoCAD .NET Training
« on: May 07, 2011, 10:49:08 AM »
Hi,

Autodesk provides a new 'AutoCAD .NET Training' for A2011 and A2012 (usable with Visual Studio 2008 or 2010).

Anouncement on Kean's blog: Through the Interface.

Download on Autodesk Developer Center.

12
Math and Geometry / Convex Hull
« on: April 16, 2011, 04:43:08 PM »
Here're a C# and a F# implementation of the 'Graham's scan' algorythm to get the convex hull of a 2d points collection.

[EDIT: added a C# implementation using Linq]

C# (targeting NET Framework 2.0 which is the default for A2007 -> A2009).
Code: [Select]
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace ConvexHull
{
    public class Commands
    {
        private Point2d _p0;

        private bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
        {
            return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-9;
        }

        private int ComparePoints(Point2d p1, Point2d p2)
        {
            if (p1.IsEqualTo(p2)) return 0;
            double d1 = _p0.GetDistanceTo(p1);
            double d2 = _p0.GetDistanceTo(p2);
            if (d1 == 0.0) return -1;
            if (d2 == 0.0) return 1;
            double cos = (p2.X - _p0.X) / d2 - (p1.X - _p0.X) / d1;
            if (cos < -1e-9) return -1;
            if (cos > 1e-9) return 1;
            return d1.CompareTo(d2);
        }

        private List<Point2d> ConvexHull(List<Point2d> pts)
        {
            _p0 = pts[0];
            for (int i = 1; i < pts.Count; i++)
            {
                Point2d pt = pts[i];
                if (pt.Y < _p0.Y || (pt.Y == _p0.Y && pt.X < _p0.X))
                    _p0 = pt;
            }
            pts.Sort(ComparePoints);
            for (int i = 1; i < pts.Count - 1; i++)
            {
                while (i > 0 && Clockwise(pts[i - 1], pts[i], pts[i + 1]))
                {
                    pts.RemoveAt(i);
                    i--;
                }
            }
            return pts;
        }

        [CommandMethod("ch", CommandFlags.UsePickSet)]
        public void testCh()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            TypedValue[] filter = new TypedValue[1] { new TypedValue(0, "POINT") };
            PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
            if (psr.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            using (Polyline pline = new Polyline())
            {
                List<Point2d> pts = new List<Point2d>();
                foreach (SelectedObject so in psr.Value)
                {
                    DBPoint dbPt = (DBPoint)tr.GetObject(so.ObjectId, OpenMode.ForRead);
                    pts.Add(new Point2d(dbPt.Position.X, dbPt.Position.Y));
                }
                for (int i = 0; i < ConvexHull(pts).Count; i++)
                {
                    pline.AddVertexAt(i, pts[i], 0.0, 0.0, 0.0);
                }
                pline.Closed = true;
                pline.SetDatabaseDefaults();
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                btr.AppendEntity(pline);
                tr.AddNewlyCreatedDBObject(pline, true);
                tr.Commit();
            }
        }
    }
}

C# (using Linq, NET Framework 3 or upper required)
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace LinqConvexHull
{
    public class Commands
    {
        private Point2d _p0;

        private bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
        {
            return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-9;
        }

        private double Cosine(Point2d pt)
        {
            double d = _p0.GetDistanceTo(pt);
            return d == 0.0 ? 1.0 : Math.Round((pt.X - _p0.X) / d, 9);
        }

        private List<Point2d> ConvexHull(List<Point2d> pts)
        {
            _p0 = pts.OrderBy(p => p.Y).ThenBy(p => p.X).First();
            pts = pts.OrderByDescending(p => Cosine(p)).ThenBy(p => _p0.GetDistanceTo(p)).ToList();
            for (int i = 1; i < pts.Count - 1; i++)
            {
                while (i > 0 && Clockwise(pts[i - 1], pts[i], pts[i + 1]))
                {
                    pts.RemoveAt(i);
                    i--;
                }
            }
            return pts;
        }

        [CommandMethod("Test")]
        public void Test()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            TypedValue[] filter = new TypedValue[1] { new TypedValue(0, "POINT") };
            PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
            if (psr.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            using (Polyline pline = new Polyline())
            {
                List<Point2d> pts = new List<Point2d>();
                foreach (SelectedObject so in psr.Value)
                {
                    DBPoint dbPt = (DBPoint)tr.GetObject(so.ObjectId, OpenMode.ForRead);
                    pts.Add(new Point2d(dbPt.Position.X, dbPt.Position.Y));
                }
                pts = ConvexHull(pts);
                for (int i = 0; i < pts.Count; i++)
                {
                    pline.AddVertexAt(i, pts[i], 0.0, 0.0, 0.0);
                }
                pline.Closed = true;
                pline.SetDatabaseDefaults();
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                btr.AppendEntity(pline);
                tr.AddNewlyCreatedDBObject(pline, true);
                tr.Commit();
            }
        }
    }
}

F#
Code: [Select]
module ConvexHull

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

type AcAp = Autodesk.AutoCAD.ApplicationServices.Application

let clockwise (p1:Point2d) (p2:Point2d) (p3:Point2d) =
    (p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X) < 1e-9
   
let convexHull (pts : Point2d seq) =
    let rec fill acc pt =
        match acc with
        | a :: b :: _ when clockwise b a pt -> fill acc.Tail pt
        | _ -> pt :: acc
    let p0 = pts
             |> Seq.reduce (fun p1 p2 ->
                if p2.Y < p1.Y || (p1.Y = p2.Y && p2.X < p1.X) then p2 else p1)
    pts
    |> List.ofSeq
    |> List.sortBy (fun p ->
        let d = p0.GetDistanceTo(p)
        (Math.Round((p0.X - p.X) / d, 8), d))
    |> List.fold fill []
    |> List.rev

[<CommandMethod("ch")>]
let Test() =
    let doc = AcAp.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor
    let psr = ed.GetSelection(new SelectionFilter([| new TypedValue(0, "POINT") |]))
    if psr.Status = PromptStatus.OK then
        use tr = db.TransactionManager.StartTransaction()
        use pl = new Polyline()
        psr.Value
        |> Seq.cast<_>
        |> Seq.map (fun (so : SelectedObject) ->
            let pt = tr.GetObject(so.ObjectId, OpenMode.ForRead) :?> DBPoint
            new Point2d(pt.Position.X, pt.Position.Y))
        |> convexHull
        |> List.fold(fun i p ->  pl.AddVertexAt(i, p, 0.0, 0.0, 0.0); i + 1) 0
        |> ignore
        pl.Closed <- true
        pl.SetDatabaseDefaults()
        let btr = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) :?> BlockTableRecord
        btr.AppendEntity(pl) |> ignore
        tr.AddNewlyCreatedDBObject(pl, true)
        tr.Commit()

target audience:{advanced}

13
Blocks / Get block references in layouts only
« on: March 24, 2011, 11:05:28 PM »
Hi

The BlockTableRecord.GetBlockReferencesIds() method returns all inserted references, even nested in other blocks.

A way to get only references inserted in layout is to filter the ObjectIdCollection returned by GetBlockReferencesIds() with the reference owner.

The following F# and C# snippets define a function/method which require as arguments the database and the block name.
They return respectively an ObjectId array and an ObjectIdCollection which contain all block references directly inserted in layouts (even dynamic/anonymous blocks).

C#
Code: [Select]
        private ObjectIdCollection GetReferences(Database db, string bName)
        {
            ObjectIdCollection result = new ObjectIdCollection();
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt =
                    (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (bt.Has(bName))
                {
                    BlockTableRecord btr =
                        (BlockTableRecord)tr.GetObject(bt[bName], OpenMode.ForRead);
                    foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false))
                    {
                        BlockReference br =
                            (BlockReference)tr.GetObject(refId, OpenMode.ForRead);
                        BlockTableRecord owner =
                            (BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead);
                        if (owner.IsLayout)
                            result.Add(br.ObjectId);
                    }
                    foreach (ObjectId id in btr.GetAnonymousBlockIds())
                    {
                        BlockTableRecord anon =
                            (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
                        foreach (ObjectId refId in anon.GetBlockReferenceIds(true, false))
                        {
                            BlockReference br =
                                (BlockReference)tr.GetObject(refId, OpenMode.ForRead);
                            BlockTableRecord owner =
                                (BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead);
                            if (owner.IsLayout)
                                result.Add(br.ObjectId);
                        }
                    }
                }
                tr.Commit();
            }
            return result;
        }

F#
Code: [Select]
let GetBlockReferences (db : Database) (bName : string) =
    use tr = db.TransactionManager.StartTransaction()
    let bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) :?> BlockTable
    if bt.Has(bName) then
        let btr = tr.GetObject(bt.[bName], OpenMode.ForRead) :?> BlockTableRecord
        btr.GetAnonymousBlockIds()
        |> Seq.cast<_>
        |> Seq.map (fun id -> tr.GetObject(id, OpenMode.ForRead) :?> BlockTableRecord)
        |> Seq.append (Seq.singleton btr)
        |> Seq.collect (fun b ->
            b.GetBlockReferenceIds(true, false)
            |> Seq.cast<_>
            |> Seq.filter (fun id ->
                let br = tr.GetObject(id, OpenMode.ForRead)
                let owner = tr.GetObject(br.OwnerId, OpenMode.ForRead) :?> BlockTableRecord
                owner.IsLayout))
        |> Seq.toArray
    else Array.empty

14
AutoCAD talk / LispFunction with autoCAD 2008
« on: December 06, 2010, 08:34:11 PM »
Hi,

A .NET defined LISP function (with the LispFunction attribute) can return any type of value available in LISP : string, int, double,Point3d,  SelectionSet, ObjectId (ename), TypedValue (T or nil), ResultBuffer (list)...

But it seems there's a bug with AutoCAD 2008 which only allows .NET defined LISP functions to return a ResulBuffer (a list or nil).

15
Selection sets / Selection Set Filters
« on: December 04, 2010, 10:24:53 PM »
In the .NET environment, a SelectionFilter is constructed with a TypedValue array.
A TypedValue can contain various Objects types in its Value property. The Object type is defined in the TypeCode property as an integer (or a DxfCode enum integer) which corresponds to the DXF group code of the Value.
For those who know AutoLISP, a TypedValue looks like a DXF dotted pair (even it is not the same thing, a dotted pair is a LISP specific data).
For the Values which type is String as entity type (DxfCode.Start = 0) or layer (DxfCode.Layer = 8), the Value can contain many patterns separted by commas and use wildcard patterns.
The filter can contain relational tests for the numerical values and logical grouping.

The more complete documentation in the AutoCAD Developer's Help for building sofisticated SelectionFilters is in the AutoLISP Help:
AutoLISP developer's Guide > Using the AutoLISP Language > Using AutoLISP to Manipulate AutoCAD Objects > Selection Set Handling > Selection Set Filter Lists
And the DXF group codes can be found in DXF Reference.

Here's an example to select closed polylines (lw, 2d and 3d) and closed ellipses on layers BASE and CONSTRUCTION

Code: [Select]
TypedValue[] filter =
            {   new TypedValue(-4, "<OR"),
                new TypedValue(-4, "<AND"),
                new TypedValue(0, "*POLYLINE"), // LWPOLYLINE and POLYLINE
                new TypedValue(-4, "&"),
                new TypedValue(70, 1), // Closed
                new TypedValue(-4, "<NOT"),
                new TypedValue(-4, "&"),
                new TypedValue(70, 112),  // Avoid meshes (bit codes: 16 + 32 + 64)
                new TypedValue(-4, "NOT>"),
                new TypedValue(-4, "AND>"),
                new TypedValue(-4, "<AND"),
                new TypedValue(0, "ELLIPSE"),
                new TypedValue(-4, "="),
                new TypedValue(41, 0.0), // Start parameter
                new TypedValue(-4, "="),
                new TypedValue(42, 6.283185307179586), // End parameter
                new TypedValue(-4, "AND>"),
                new TypedValue(-4, "OR>"),
                new TypedValue(8, "BASE,CONSTRUCTION")};
SelectionFilter = new SelectionFilter(filter);

target audience:{beginner}

Pages: [1] 2