Customizing the display of standard AutoCAD objects using F#

This post is one of the winning entries of the F# programming contest started at the beginning of the year. It was submitted by an old friend of mine, Qun Lu, who also happens to be a member of the AutoCAD engineering team, and makes use of a new API in AutoCAD 2010: the somewhat ominously-named Overrule API.

The Overrule API is really (and I mean really, really) cool. Yes, I know: another really cool API in AutoCAD 2010? Well, I'm honestly not one to hype things up, but I do have a tendency to get excited by technology that has incredibly interesting capabilities with a relatively low barrier of entry. And the Overrule API is one of those APIs. It's the answer to the question posed in this previous post, which raises concerns about translating the power and complexity of custom objects to the world of .NET:

So what's the right thing to do? Clearly we could just go ahead and expose the mechanism as it is today in ObjectARX. And yet here we are with a technology we know to be highly complex and difficult to implement, and an ideal opportunity to redesign it – enabling more people to harness it effectively at lower effort. The more favoured approach (at least from our perspective) would be to investigate further how better to meet developers' needs for enabling custom graphics/behaviour (a.k.a. stylization) in AutoCAD – in a way that could be supported technically for many releases to come.

The Overrule API allows you to hook into the display and other aspects of the behaviour of entities inside AutoCAD. The below example is a great example: when enabled, the code overrules the display of lines and circles, to make them into coloured pipes. And all with very little code (which would also be true if the code were in C# or VB.NET).

Here's the F# code:

#light

 

module DrawOverrule.Commands

 

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.Geometry

open Autodesk.AutoCAD.GraphicsInterface

open Autodesk.AutoCAD.Colors

 

type public DrawOverrule public () as this =

  inherit DrawableOverrule()

 

  static member public theOverrule =

    new DrawOverrule()

 

  static member private Radius = 0.5

 

  member private this.sweepOpts = new SweepOptions()

 

  override this.WorldDraw (d : Drawable, wd : WorldDraw) =

    match d with

      // Type-test and cast. If succeeds, cast to "line"

      | πŸ˜• Line as line ->

        // Draw the line as is, with overruled attributes

        base.WorldDraw(line, wd) |> ignore

        if not line.Id.IsNull && line.Length > 0.0 then

          // Draw a pipe around the line

          let c = wd.SubEntityTraits.TrueColor

          wd.SubEntityTraits.TrueColor <-

            new EntityColor(0x00AfAfff)

          wd.SubEntityTraits.LineWeight <-

            LineWeight.LineWeight000

          let clr =

            new Circle

              (line.StartPoint, line.EndPoint-line.StartPoint,

              DrawOverrule.Radius)

          let pipe = new ExtrudedSurface()

          try

            pipe.CreateExtrudedSurface

              (clr, line.EndPoint-line.StartPoint, this.sweepOpts)

          with

            | e -> printfn("Failed with CreateExtrudedSurface")

          clr.Dispose()

          pipe.WorldDraw(wd) |> ignore

          pipe.Dispose()

          wd.SubEntityTraits.TrueColor <- c

        true

      | πŸ˜• Circle as circle ->

        // Draw the circle as is, with overruled attributes

        base.WorldDraw(circle, wd) |> ignore

 

        // needed to avoid ill-formed swept surface

        if circle.Radius > DrawOverrule.Radius then

          // draw a pipe around the cirle

          let c = wd.SubEntityTraits.TrueColor

          wd.SubEntityTraits.TrueColor <-

            new EntityColor(0x3fffe0e0)

          wd.SubEntityTraits.LineWeight <-

            LineWeight.LineWeight000

          let normal =

            (circle.Center-circle.StartPoint).

              CrossProduct(circle.Normal)

          let clr =

            new Circle

              (circle.StartPoint, normal, DrawOverrule.Radius)

          let pipe = new SweptSurface()

          pipe.CreateSweptSurface(clr, circle, this.sweepOpts)

          clr.Dispose()

          pipe.WorldDraw(wd) |> ignore

          pipe.Dispose()

          wd.SubEntityTraits.TrueColor <- c

        true

      | _ ->

        base.WorldDraw(d, wd)

 

  override this.SetAttributes (d : Drawable, t : DrawableTraits) =

    let b = base.SetAttributes(d, t)

    match d with

      | πŸ˜• Line ->

        // If d is LINE, set color to index 6

        t.Color <- 6s

        // and lineweight to .40 mm

        t.LineWeight <- LineWeight.LineWeight040

      | πŸ˜• Circle ->   

        // If d is CIRCLE, set color to index 2

        t.Color <- 2s

        // and lineweight to .60 mm

        t.LineWeight <- LineWeight.LineWeight060

      | _ -> ()

    b

 

let Overrule enable =

  // Regen to see the effect

  // (turn on/off Overruling and LWDISPLAY)

  DrawableOverrule.Overruling <- enable

  match enable with

    | true -> Application.SetSystemVariable("LWDISPLAY", 1)

    | false -> Application.SetSystemVariable("LWDISPLAY", 0)

  let doc =

    Application.DocumentManager.MdiActiveDocument

  doc.SendStringToExecute("REGEN3\n", true, false, false)

  doc.Editor.Regen()

 

// Now we declare our commands

 

[<CommandMethod("overrule1")>]

let OverruleStart() =

  // Targeting all Drawables, but only affects Lines and Circles

  ObjectOverrule.AddOverrule

    (RXClass.GetClass(typeof<Drawable>),

    DrawOverrule.theOverrule, true)

  Overrule(true)

 

[<CommandMethod("overrule0")>]

let OverruleEnd() =

  Overrule(false)

Here's what happens when we load the application, turn the overrule on using the OVERRULE1 command (OVERRULE0 is the command to turn the overrule off – it's details like this that tell you Qun's a real programmer… πŸ˜‰ and draw some lines and circles:

Overruled 2D wireframe display of lines and circles

Even in a 3D view – this time with the realistic visual style applied – you get the piping effect when you draw simple geometry:

Overruled 3D display of lines and circles

To be clear: these are standard AutoCAD lines and circles. When you use the OVERRULE0 command to disable the overrule, they revert to their original form:

Standard display of lines and circles

I expect to follow this post – in time – with various more harnessing the power of this very cool API. If you have questions or ideas about how it might be used, be sure to post a comment.

Thanks & congratulations, Qun! Your copy of "Expert F#" is on its way to you via inter-office mail. πŸ™‚ More soon on the other winning entry…

5 responses to “Customizing the display of standard AutoCAD objects using F#”

  1. Stephen Preston Avatar

    Congratulations Qun πŸ™‚

  2. Example of a new API in a new language?
    It's all a little too much for my tiny little brain. One or the other would have been more compelling for me. But with BOTH I really didn't get much out of this one.
    Never-the-less, keep it coming. I'll pick the ones I can grasp.

  3. Kean Walmsley Avatar

    Fair point. It was probably a little unfair of me to use a programming contest entry to showcase a new API. Trying to kill a flock of birds with one stone. πŸ™‚

    I'll post a C# version when I get the chance.

    Kean

  4. I have written a C# version:
    public class DrawOverrule: DrawableOverrule

    {

    static DrawOverrule theOverrule = new DrawOverrule();

    static double radius = 0.5;

    SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw(Drawable drawable, WorldDraw wd)

    {

    if (drawable is Line)

    {

    Line line = drawable as Line;

    base.WorldDraw(line, wd);

    if (!line.Id.IsNull && line.Length>0.0)

    {

    var c = wd.SubEntityTraits.TrueColor;

    wd.SubEntityTraits.TrueColor = new EntityColor(0x00AfAfff);

    wd.SubEntityTraits.LineWeight = LineWeight.LineWeight000;

    var cir = new Circle(line.StartPoint, line.EndPoint - line.StartPoint, DrawOverrule.radius);

    var pipe = new ExtrudedSurface();

    pipe.CreateExtrudedSurface(cir, line.EndPoint - line.StartPoint, sweepOpts);

    cir.Dispose();

    pipe.WorldDraw(wd);

    pipe.Dispose();

    wd.SubEntityTraits.TrueColor = c;

    }

    return true;

    }

    else if(drawable is Circle)

    {

    Circle circle = drawable as Circle;

    base.WorldDraw(circle, wd);

    if (circle.Radius > DrawOverrule.radius)

    {

    var c = wd.SubEntityTraits.TrueColor;

    wd.SubEntityTraits.TrueColor = new EntityColor(0x3fffe0e0);

    wd.SubEntityTraits.LineWeight = LineWeight.LineWeight000;

    var normal = (circle.Center - circle.StartPoint).CrossProduct(circle.Normal);

    var clr = new Circle(circle.StartPoint, normal, DrawOverrule.radius);

    var pipeSweptSurface = new SweptSurface();

    pipeSweptSurface.CreateSweptSurface(clr, circle, this.sweepOpts);

    clr.Dispose();

    pipeSweptSurface.WorldDraw(wd);

    pipeSweptSurface.Dispose();

    wd.SubEntityTraits.TrueColor = c;

    }

    return true;

    }

    return base.WorldDraw(drawable, wd);

    }

    public override int SetAttributes(Drawable drawable, DrawableTraits traits)

    {

    var b = base.SetAttributes(drawable, traits);

    if (drawable is Line)

    {

    traits.Color = 6;

    traits.LineWeight = LineWeight.LineWeight040;

    }

    else if (drawable is Circle)

    {

    traits.Color = 2;

    traits.LineWeight = LineWeight.LineWeight060;

    }

    return b;

    }

    [CommandMethod("overrule1")]

    public void OverruleStart()

    {

    try

    {

    ObjectOverrule.AddOverrule(RXClass.GetClass(typeof(Drawable)), DrawOverrule.theOverrule, true);

    Overrule.Overruling = true;

    }

    catch (System.Exception e)

    {

    }

    }

    [CommandMethod("overrule0")]

    public void OverruleEnd()

    {

    Overrule.Overruling = false;

    }

    }

  5. Thanks, csharpbird.

    I'd already converted the code and queued up this post for publication by the time I saw your comment.

    Kean

Leave a Reply to csharpbird Cancel reply

Your email address will not be published. Required fields are marked *