August 2014

Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            








« Creating an associative, lofted surface in AutoCAD using .NET | Main | Misusing mutable state with F# Asynchronous Workflows »

November 19, 2010

Generating C# code to create associative, lofted surfaces between selected AutoCAD polylines using .NET

That has to be one of my favourite post titles, to-date: it’ll be interesting to see how Twitterfeed handles it. :-)

In this post we’re going to combine the approaches from a couple of previous posts to place source code to generate associative, lofted surfaces on the clipboard, ready for pasting into a C# project. When we did this before for polylines, we didn’t really care about grouping them: we could just select all of the polylines in a drawing and they would (hopefully) be reproduced when the generated code was executed in the target drawing.

This is a bit different: as we want to select sets of polylines to generate surfaces – and we want to be able to create multiple surfaces, each from a different set of polyline profiles – we will need to maintain some additional counters for the surface we’re editing as well as the profile (if we don’t keep these counters then the source code we generate for each surface will have repeated variable names). I’ve chosen to adopt the approach of running the CC command, selecting the polylines and pasting the results for each surface we want to generate.

Other than that, the approach is pretty comparable to the one we used for polylines.

Here’s the updated C# code for our CC command, which now generates code to create associative, lofted surfaces between selected polyline profiles and places it on the clipboard:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using System.Text;

using System;

 

namespace CopyCodeForEntities

{

  public class Commands

  {

    int surfCount = 0;

    int profCount = 0;

 

    // 0 - Surface counter

    // 1 - Number of profile polylines

 

    const string header =

      "// Starting surface {0} containing {1} profiles\r\n\r\n" +

      "LoftProfile[] lps{0} = new LoftProfile[{1}];\r\n" +

      "LoftOptions lo{0} = new LoftOptions();\r\n\r\n";

 

    // 0 - Profile counter

    // 1 - Vertex count

    // 2,3,4 - Normal - Vector3d(X,Y,Z)

 

    const string enthead =

      "Polyline pl{0} = new Polyline({1});\r\n" +

      "pl{0}.Normal = new Vector3d({2},{3},{4});\r\n";

 

    // 0 - Profile counter

    // 1 - Index

    // 2,3 - Point(X,Y)

    // 4 - Bulge

    // 5 - Start width

    // 6 - End width

 

    const string vert =

      "pl{0}.AddVertexAt({1}, new Point2d({2}, {3})," +

      "{4}, {5}, {6});\r\n";

 

    // 0 - Profile counter

 

    const string closed = "pl{0}.Closed = true;\r\n";

 

    // 0 - Profile counter

    // 1 - Elevation

 

    const string elev = "pl{0}.Elevation = {1};\r\n";

 

    // 0 - Profile counter

    // 1 - Local profile counter

    // 2 - Surface counter

 

    const string entfoot =

      "btr.AppendEntity(pl{0});\r\n" +

      "tr.AddNewlyCreatedDBObject(pl{0}, true);\r\n" +

      "LoftProfile lp{0} = new LoftProfile(pl{0});\r\n" +

      "lps{2}[{1}] = lp{0};\r\n\r\n";

 

    // 0 - Surface counter

 

    const string footer =

      "Autodesk.AutoCAD.DatabaseServices." +

      "Surface.CreateLoftedSurface(\r\n" +

      "  lps{0}, null, null, lo{0}, true\r\n);\r\n\r\n";

 

    // 0 - Surface counter

 

    const string footerNonAssoc =

      "Autodesk.AutoCAD.DatabaseServices.Surface ls{0} =\r\n" +

      "  Autodesk.AutoCAD.DatabaseServices." +

      "Surface.CreateLoftedSurface(\r\n" +

      "    lps{0}, null, null, lo{0}\r\n  );\r\n" +

      "btr.AppendEntity(ls{0});\r\n" +

      "tr.AddNewlyCreatedDBObject(ls{0}, true);\r\n\r\n";

 

    [CommandMethod("CC")]

    public void CopyCode()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      Database db =

        HostApplicationServices.WorkingDatabase;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Build a filter list so that only

        // polyline are selected

 

        TypedValue[] filList = new TypedValue[1] {

            new TypedValue((int)DxfCode.Start, "LWPOLYLINE")

          };

        SelectionFilter filter =

          new SelectionFilter(filList);

        PromptSelectionOptions opts =

          new PromptSelectionOptions();

        opts.MessageForAdding = "Select polylines: ";

        PromptSelectionResult res =

          ed.GetSelection(opts, filter);

 

        // Do nothing if selection is unsuccessful

 

        if (res.Status != PromptStatus.OK)

          return;

 

        SelectionSet selSet = res.Value;

        ObjectId[] ids = selSet.GetObjectIds();

        StringBuilder sb = new StringBuilder();

 

        sb.AppendFormat(header, surfCount, ids.Length);

 

        for (int i = 0; i < ids.Length; i++)

        {

          Polyline pl =

            (Polyline)tr.GetObject(

              ids[i],

              OpenMode.ForRead

            );

 

          sb.AppendFormat(

            enthead, profCount + i, pl.NumberOfVertices,

            ZeroIfTiny(pl.Normal.X),

            ZeroIfTiny(pl.Normal.Y),

            ZeroIfTiny(pl.Normal.Z)

          );

 

          if (!NearZero(pl.Elevation))

          {

            sb.AppendFormat(elev, profCount + i, pl.Elevation);

          }

 

          for (int j = 0; j < pl.NumberOfVertices; j++)

          {

            Point2d pt = pl.GetPoint2dAt(j);

            double bul = pl.GetBulgeAt(j),

                  sWid = pl.GetStartWidthAt(j),

                  eWid = pl.GetEndWidthAt(j);

            sb.AppendFormat(

              vert, profCount + i, j, pt.X, pt.Y, bul, sWid, eWid

            );

          }

 

          if (pl.Closed)

          {

            sb.AppendFormat(closed, profCount + i);

          }

 

          sb.AppendFormat(entfoot, profCount + i, i, surfCount);

        }

        sb.AppendFormat(footer, surfCount);

 

        ed.WriteMessage(

          "\nCopied C# code for {0} profiles to the clipboard.",

          ids.Length

        );

        System.Windows.Forms.Clipboard.SetText(sb.ToString());

 

        PromptKeywordOptions pko =

          new PromptKeywordOptions(

            "\nMaintain the object counters for more copying?"

          );

        pko.AllowNone = true;

        pko.Keywords.Add("Yes");

        pko.Keywords.Add("No");

        pko.Keywords.Default = "Yes";

 

        PromptResult pkr = ed.GetKeywords(pko);

 

        if (pkr.StringResult == "Yes")

        {

          profCount += ids.Length;

          surfCount++;

        }

        else

        {

          profCount = 0;

          surfCount = 0;

        }

        tr.Commit();

      }

    }

 

    bool NearZero(double x)

    {

      return (Math.Abs(x) <= Tolerance.Global.EqualPoint);

    }

 

    double ZeroIfTiny(double x)

    {

      return (NearZero(x) ? 0.0 : x);

    }

 

    [CommandMethod("PP")]

    public void CreateSurfaces()

    {

      Editor ed =

        Application.DocumentManager.MdiActiveDocument.Editor;

      Database db =

        HostApplicationServices.WorkingDatabase;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );

 

        // Paste clipboard contents here

 

 

        tr.Commit();

      }

    }

  }

}

The code that gets generated is a slightly different to the approach shown in the last post:

  • We don’t generate initialise the arrays of LoftProfile objects at once: we create them and populate them as we create the individual LoftProfiles
    • This is just a but easier to code
  • As people may want to choose different settings in their LoftOptions objects, this gets assigned to a variable rather than just been created and passed in

Also, you may have noticed that the footerNonAssoc string constant is defined but not used: you can use this instead of the footer constant is you want to create non-associative surfaces using the other version of CreateLoftedSurface() method (as discussed in the last post).

Let’s run this code against the various loops in our space shuttle drawing and see the output to the command-line as we do so:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines: 1 found, 3 total

Select polylines: 1 found, 4 total

Select polylines: 1 found, 5 total

Select polylines: 1 found, 6 total

Select polylines: 1 found, 7 total

Select polylines: 1 found, 8 total

Select polylines: 1 found, 9 total

Select polylines: 1 found, 10 total

Select polylines: 1 found, 11 total

Select polylines: 1 found, 12 total

Select polylines: 1 found, 13 total

Select polylines:

Copied C# code for 13 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines: 1 found, 3 total

Select polylines: 1 found, 4 total

Select polylines: 1 found, 5 total

Select polylines: 1 found, 6 total

Select polylines: 1 found, 7 total

Select polylines: 1 found, 8 total

Select polylines: 1 found, 9 total

Select polylines:

Copied C# code for 9 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines: 1 found, 3 total

Select polylines: 1 found, 4 total

Select polylines: 1 found, 5 total

Select polylines:

Copied C# code for 5 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines: 1 found, 3 total

Select polylines:

Copied C# code for 3 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines: 1 found, 3 total

Select polylines:

Copied C# code for 3 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines:

Copied C# code for 2 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>:

Command: CC

Select polylines: 1 found

Select polylines: 1 found, 2 total

Select polylines:

Copied C# code for 2 profiles to the clipboard.

Maintain the object counters for more copying? [Yes/No] <Yes>: N

You’ll see I’ve picked the polylines each individually: the order of addition to the surface is the same as the order of selection, so it pays to be careful with that (at least I assume it does: I haven’t tried with arbitrary window selection, in this case).

Going through this process it’s important to paste the results into your Visual Studio project after every use of the CC command, of course. If you want to see the full source file created by pasting the various results into the above PP command definition, you can get it from here (it’s too long to post directly).

That said, here’s the last of the surface definitions it contains – as it’s one of the smallest – formatted to fit the width of this blog:

// Starting surface 6 containing 2 profiles

 

LoftProfile[] lps6 = new LoftProfile[2];

LoftOptions lo6 = new LoftOptions();

 

Polyline pl35 = new Polyline(6);

pl35.Normal = new Vector3d(0, 1, 0);

pl35.Elevation = -7.9299;

pl35.AddVertexAt(

  0, new Point2d(8.63199798845721, -0.686726686955384),

  -0.353751441976449, 0, 0

);

pl35.AddVertexAt(

  1, new Point2d(8.53590664190271, -0.543497077638589),

  -0.377301974085308, 0, 0

);

pl35.AddVertexAt(

  2, new Point2d(8.76365541365589, -0.287546676233059),

  -0.662509422802581, 0, 0

);

pl35.AddVertexAt(

  3, new Point2d(8.91218279151725, -0.484783816541924),

  -0.125740268521058, 0, 0

);

pl35.AddVertexAt(

  4, new Point2d(8.82084101054244, -0.624805216693829),

  -0.186498059551109, 0, 0

);

pl35.AddVertexAt(

  5, new Point2d(8.63199798845721, -0.686726686955384), 0, 0, 0

);

pl35.Closed = true;

btr.AppendEntity(pl35);

tr.AddNewlyCreatedDBObject(pl35, true);

LoftProfile lp35 = new LoftProfile(pl35);

lps6[0] = lp35;

 

Polyline pl36 = new Polyline(6);

pl36.Normal = new Vector3d(0, 1, 0);

pl36.Elevation = -7.37467156810075;

pl36.AddVertexAt(

  0, new Point2d(8.63199798845721, -0.68672667318999),

  -0.353751441976449, 0, 0

);

pl36.AddVertexAt(

  1, new Point2d(8.53590664190271, -0.543497063873194),

  -0.377301974085308, 0, 0

);

pl36.AddVertexAt(

  2, new Point2d(8.76365541365589, -0.287546662467665),

  -0.662509422802581, 0, 0

);

pl36.AddVertexAt(

  3, new Point2d(8.91218279151725, -0.484783802776529),

  -0.125740268521058, 0, 0

);

pl36.AddVertexAt(

  4, new Point2d(8.82084101054244, -0.624805202928434),

  -0.186498059551109, 0, 0

);

pl36.AddVertexAt(

  5, new Point2d(8.63199798845721, -0.68672667318999), 0, 0, 0

);

pl36.Closed = true;

btr.AppendEntity(pl36);

tr.AddNewlyCreatedDBObject(pl36, true);

LoftProfile lp36 = new LoftProfile(pl36);

lps6[1] = lp36;

 

Autodesk.AutoCAD.DatabaseServices.Surface.CreateLoftedSurface(

  lps6, null, null, lo6, true

);

Here are the results of running the PP command in a fresh drawing (and then orbiting and setting the Visual Style to Realistic):

Our space shuttle shell

And Houston, we are associative!

Proof of surface associativity There are still some revolved surfaces I want to create, as well as a few patches, here and there. If I have the courage and the energy I’ll take a look at adding those, at some point.

blog comments powered by Disqus

Feed/Share

10 Random Posts