雪山飞狐_lzh 发表于 2009-6-15 18:20:00

Kean专题(11)—Overrules

原帖:http://through-the-interface.typepad.com/through_the_interface/overrules/
一、使用F#自定义实体的显示
March 27, 2009
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:

Even in a 3D view – this time with the realistic visual style applied – you get the piping effect when you draw simple geometry:
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:

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…

雪山飞狐_lzh 发表于 2009-6-15 18:28:00

C#版本
April 08, 2009
Customizing the display of standard AutoCAD objects using .NET
The code in this post is a direct port of the F# code in this previous post, which was entered by Qun Lu in the recent F# programming contest. Someone – very validly - commented on the fact the post involved both a new language and a new API, which was probably pushing things a little from a learning perspective. :-)
Without repeating my various comments in the previous post, I will reiterate the fact that this API is extremely interesting for developers who wish to customize the appearance and behaviour of standard AutoCAD objects without going through the pain of implementing full custom objects.
Here’s the equivalent C# code that works with AutoCAD 2010:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Colors;

namespace DrawOverrule
{
public class DrawOverrule : DrawableOverrule
{
    static public DrawOverrule theOverrule =
      new DrawOverrule();

    static private double Radius = 0.5;

    private SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw (Drawable d, WorldDraw wd)
    {
      if (d is Line)
      {
      Line line = (Line)d;

      // Draw the line as is, with overruled attributes

      base.WorldDraw(line, wd);
      if (!line.Id.IsNull && line.Length > 0.0)
      {
          // Draw a pipe around the line

          EntityColor c =
            wd.SubEntityTraits.TrueColor;
          wd.SubEntityTraits.TrueColor =
            new EntityColor(0x00AfAfff);
          wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
          Circle clr =
            new Circle(
            line.StartPoint,
            line.EndPoint - line.StartPoint,
            DrawOverrule.Radius
            );
          ExtrudedSurface pipe = new ExtrudedSurface();
          try
          {
            pipe.CreateExtrudedSurface(
            clr, line.EndPoint-line.StartPoint, sweepOpts
            );
          }
          catch
          {
            Document doc =
            Application.DocumentManager.MdiActiveDocument;
            doc.Editor.WriteMessage(
            "\nFailed with CreateExtrudedSurface."
            );
          }
          clr.Dispose();
          pipe.WorldDraw(wd);
          pipe.Dispose();
          wd.SubEntityTraits.TrueColor = c;
      }
      return true;
      }
      else if (d is Circle)
      {
      Circle circle = (Circle)d;

      // Draw the circle as is, with overruled attributes

      base.WorldDraw(circle, wd);

      // Needed to avoid ill-formed swept surface

      if (circle.Radius > DrawOverrule.Radius)
      {
          // Draw a pipe around the cirle

          EntityColor c = wd.SubEntityTraits.TrueColor;
          wd.SubEntityTraits.TrueColor =
            new EntityColor(0x3fffe0e0);
          wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
          Vector3d normal =
            (circle.Center-circle.StartPoint).
            CrossProduct(circle.Normal);
          Circle clr =
            new Circle(
            circle.StartPoint, normal, DrawOverrule.Radius
            );
          SweptSurface pipe = new SweptSurface();
          pipe.CreateSweptSurface(clr, circle, sweepOpts);
          clr.Dispose();
          pipe.WorldDraw(wd);
          pipe.Dispose();
          wd.SubEntityTraits.TrueColor = c;
      }
      return true;
      }
      return base.WorldDraw(d, wd);
    }

    public override int SetAttributes (Drawable d, DrawableTraits t)
    {
      int b = base.SetAttributes(d, t);
      if (d is Line)
      {
      // If d is LINE, set color to index 6

      t.Color = 6;

      // and lineweight to .40 mm

      t.LineWeight = LineWeight.LineWeight040;
      }
      else if (d is Circle)
      {
      // If d is CIRCLE, set color to index 2

      t.Color = 2;

      // and lineweight to .60 mm

      t.LineWeight = LineWeight.LineWeight060;
      }
      return b;
    }
}

public class Commands
{
    public void Overrule(bool enable)
    {
      // Regen to see the effect
      // (turn on/off Overruling and LWDISPLAY)

      DrawableOverrule.Overruling = enable;
      if (enable)
      Application.SetSystemVariable("LWDISPLAY", 1);
      else
      Application.SetSystemVariable("LWDISPLAY", 0);

      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.SendStringToExecute("REGEN3\n", true, false, false);
      doc.Editor.Regen();
    }

   
    public void OverruleStart()
    {
      ObjectOverrule.AddOverrule
      (RXClass.GetClass(typeof(Drawable)),
      DrawOverrule.theOverrule, true);
      Overrule(true);
    }

   
    public void OverruleEnd()
    {
      Overrule(false);
    }
}
}

雪山飞狐_lzh 发表于 2009-6-16 08:44:00

二、图元炸开重定义
April 16, 2009
Overruling explode in AutoCAD 2010 using .NET
I am really starting to love the new Overrule API in AutoCAD 2010, and I still feel as though I’m just scratching the surface. This question came in overnight from Danny Polkinhorn (thanks, Danny! :-) :
It's exciting to see a very usable implementation of 'custom' objects in .NET. Obviously, this implementation protects what could be proprietary business intelligence from being sent around, but it brings up a question. What process would you use to 'explode' these elements so that you could send the drawing to someone without your code, but with the custom elements in it?
My first thought was to just see what happened when the EXPLODE command encountered the overruled lines & circles created with the code in the last post. At first I was a little discouraged:
Command: EXPLODE
Select objects: Specify opposite corner: 9 found
9 were not able to be exploded.
Select objects: *Cancel*
None found.My worry, at this stage, was that the EXPLODE command was excluding “simple” objects such as lines and circles during its selection process, which would mean that even if we were able to overrule the explode behaviour of our special lines & circles we would have difficulty getting our code called. Thankfully this is not the case, but I only found this out as I implemented a TransformOverrule and overruled Explode for my lines and circles.
Before looking into the specifics, here’s a quick list of the various Overrules available in AutoCAD 2010 with their overrideable methods:
Overrule (the base class)
GripOverrule
      GetGripPoints
      GetStretchPoints
      MoveGripPointsAt
      MoveStretchPointsAt
      OnGripStatusChanged
      OsnapOverrule
      GetObjectSnapPoints
      IsContentSnappable
SubentityOverrule
      AddSubentPaths
      DeleteSubentPaths
      GetCompoundObjectTransform
      GetGripPointsAtSubentPath
      GetGsMarkersAtSubentPath
      GetSubentClassId
      GetSubentPathGeomExtents
      GetSubentPathsAtGsMarker
      MoveGripPointsAtSubentPaths
      OnSubentGripStatusChanged
      SubentPtr
      TransformSubentPathsBy
TransformOverrule
      CloneMeForDragging
      Explode
      GetTransformedCopy
      HideMeForDragging
      TransformBy
GeometryOverrule
      GetGeomExtents
      IntersectWith
PropertiesOverrule
      GetClassID
      List
ObjectOverrule
      Cancel
      Close
      DeepClone
      Erase
      Open
      WblockClone
DrawableOverrule
      SetAttributes
      ViewportDraw
      ViewportDrawLogicalFlags
      WorldDraw
So far we’ve only actually used the DrawableOverrule, implementing SetAttributes() and WorldDraw().

It’s clear that to customize the explode for our lines and circles we’re going to need a TransformOverrule.
Here’s the C# code from the last post, with our additional TransformOverrules. I’ve changed a few of the class names to reflect the fact they are overrule draw behaviour vs. transform behaviour, but otherwise the code is basically the same as last time, apart from the addition of the LinePipeTransformOverrule and CirclePipeTransformOverrule classes (and the code to register/unregister them).
The Explode functions take pretty much the same geometry we used to draw the “enhanced” objects, but this time it gets returned to the system and is used to replace the original entities. This code doesn’t do anything fancy about the entity colour, etc., but we could certainly do so, if we chose.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Colors;

namespace Overrules
{
public abstract class PipeDrawOverrule : DrawableOverrule
{
    const string regAppName = "TTIF_PIPE";

    public PipeDrawOverrule()
    {
      // Tell AutoCAD to filter on our application name
      // (this means our overrule will only be called
      // on objects possessing XData with this name)

      SetXDataFilter(regAppName);
    }

    // Get the XData for a particular object
    // and return the "pipe radius" if it exists

    public static double PipeRadiusForObject(DBObject obj)
    {
      double res = 0.0;

      ResultBuffer rb = obj.XData;
      if (rb != null)
      {
      bool foundStart = false;

      foreach (TypedValue tv in rb)
      {
          if (tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName &&
            tv.Value.ToString() == regAppName)
            foundStart = true;
          else
          {
            if (foundStart == true)
            {
            if (tv.TypeCode == (int)DxfCode.ExtendedDataReal)
            {
                res = (double)tv.Value;
                break;
            }
            }
          }
      }
      rb.Dispose();
      }
      return res;
    }

    // Set the "pipe radius" in the XData of a particular object

    public static void SetPipeRadiusOnObject(
      Transaction tr, DBObject obj, double radius
    )
    {
      Database db = obj.Database;

      // Make sure the application is registered
      // (we could separate this out to be called
      // only once for a set of operations)

      RegAppTable rat =
      (RegAppTable)tr.GetObject(
          db.RegAppTableId,
          OpenMode.ForRead
      );

      if (!rat.Has(regAppName))
      {
      rat.UpgradeOpen();
      RegAppTableRecord ratr = new RegAppTableRecord();
      ratr.Name = regAppName;
      rat.Add(ratr);
      tr.AddNewlyCreatedDBObject(ratr, true);
      }

      // Create the XData and set it on the object

      ResultBuffer rb =
      new ResultBuffer(
          new TypedValue(
            (int)DxfCode.ExtendedDataRegAppName, regAppName
          ),
          new TypedValue(
            (int)DxfCode.ExtendedDataReal, radius
          )
      );
      obj.XData = rb;
      rb.Dispose();
    }
}

// An overrule to make a pipe out of line

public class LinePipeDrawOverrule : PipeDrawOverrule
{
    static public LinePipeDrawOverrule theOverrule =
      new LinePipeDrawOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw(Drawable d, WorldDraw wd)
    {
      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      Line line = d as Line;

      if (line != null)
      {
          // Draw the line as is, with overruled attributes

          base.WorldDraw(line, wd);
          if (!line.Id.IsNull && line.Length > 0.0)
          {
            // Draw a pipe around the line

            EntityColor c =
            wd.SubEntityTraits.TrueColor;
            wd.SubEntityTraits.TrueColor =
            new EntityColor(0x00AfAfff);
            wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
            Circle clr =
            new Circle(
                line.StartPoint,
                line.EndPoint - line.StartPoint,
                radius
            );
            ExtrudedSurface pipe = new ExtrudedSurface();
            try
            {
            pipe.CreateExtrudedSurface(
                clr, line.EndPoint - line.StartPoint, sweepOpts
            );
            }
            catch
            {
            Document doc =
                Application.DocumentManager.MdiActiveDocument;
            doc.Editor.WriteMessage(
                "\nFailed with CreateExtrudedSurface."
            );
            }
            clr.Dispose();
            pipe.WorldDraw(wd);
            pipe.Dispose();
            wd.SubEntityTraits.TrueColor = c;
          }
          return true;
      }
      }
      return base.WorldDraw(d, wd);
    }

    public override int SetAttributes(Drawable d, DrawableTraits t)
    {
      int b = base.SetAttributes(d, t);

      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      // Set color to index 6

      t.Color = 6;

      // and lineweight to .40 mm

      t.LineWeight = LineWeight.LineWeight040;
      }
      return b;
    }
}

// An overrule to make a pipe out of circle

public class CirclePipeDrawOverrule : PipeDrawOverrule
{
    static public CirclePipeDrawOverrule theOverrule =
      new CirclePipeDrawOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw(Drawable d, WorldDraw wd)
    {
      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      Circle circle = d as Circle;

      if (circle != null)
      {
          // Draw the circle as is, with overruled attributes

          base.WorldDraw(circle, wd);

          // Needed to avoid ill-formed swept surface

          if (circle.Radius > radius)
          {
            // Draw a pipe around the cirle

            EntityColor c = wd.SubEntityTraits.TrueColor;
            wd.SubEntityTraits.TrueColor =
            new EntityColor(0x3fffe0e0);
            wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
            Vector3d normal =
            (circle.Center - circle.StartPoint).
                CrossProduct(circle.Normal);
            Circle clr =
            new Circle(
                circle.StartPoint, normal, radius
            );
            SweptSurface pipe = new SweptSurface();
            pipe.CreateSweptSurface(clr, circle, sweepOpts);
            clr.Dispose();
            pipe.WorldDraw(wd);
            pipe.Dispose();
            wd.SubEntityTraits.TrueColor = c;
          }
          return true;
      }
      }
      return base.WorldDraw(d, wd);
    }

    public override int SetAttributes(Drawable d, DrawableTraits t)
    {
      int b = base.SetAttributes(d, t);

      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      // Set color to index 2

      t.Color = 2;

      // and lineweight to .60 mm

      t.LineWeight = LineWeight.LineWeight060;
      }
      return b;
    }
}

public class LinePipeTransformOverrule : TransformOverrule
{
    static public LinePipeTransformOverrule theOverrule =
      new LinePipeTransformOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override void Explode(Entity e, DBObjectCollection objs)
    {
      double radius = 0.0;

      if (e is DBObject)
      radius = PipeDrawOverrule.PipeRadiusForObject(e);

      if (radius > 0.0)
      {
      Line line = e as Line;

      if (line != null)
      {
          if (!line.Id.IsNull && line.Length > 0.0)
          {
            // Draw a pipe around the line

            Circle clr =
            new Circle(
                line.StartPoint,
                line.EndPoint - line.StartPoint,
                radius
            );
            ExtrudedSurface pipe = new ExtrudedSurface();
            try
            {
            pipe.CreateExtrudedSurface(
                clr, line.EndPoint - line.StartPoint, sweepOpts
            );
            }
            catch
            {
            Document doc =
                Application.DocumentManager.MdiActiveDocument;
            doc.Editor.WriteMessage(
                "\nFailed with CreateExtrudedSurface."
            );
            }
            clr.Dispose();
            objs.Add(pipe);
          }
          return;
      }
      }
      base.Explode(e, objs);
    }
}

public class CirclePipeTransformOverrule : TransformOverrule
{
    static public CirclePipeTransformOverrule theOverrule =
      new CirclePipeTransformOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override void Explode(Entity e, DBObjectCollection objs)
    {
      double radius = 0.0;

      if (e is DBObject)
      radius = PipeDrawOverrule.PipeRadiusForObject(e);

      if (radius > 0.0)
      {
      Circle circle = e as Circle;

      if (circle != null)
      {
          // Needed to avoid ill-formed swept surface

          if (circle.Radius > radius)
          {
            // Draw a pipe around the cirle

            Vector3d normal =
            (circle.Center - circle.StartPoint).
                CrossProduct(circle.Normal);
            Circle clr =
            new Circle(
                circle.StartPoint, normal, radius
            );
            SweptSurface pipe = new SweptSurface();
            pipe.CreateSweptSurface(clr, circle, sweepOpts);
            clr.Dispose();
            objs.Add(pipe);
          }
          return;
      }
      }
      base.Explode(e, objs);
    }
}

public class Commands
{
    private double _radius = 0.0;

    public void Overrule(bool enable)
    {
      // Regen to see the effect
      // (turn on/off Overruling and LWDISPLAY)

      DrawableOverrule.Overruling = enable;
      if (enable)
      Application.SetSystemVariable("LWDISPLAY", 1);
      else
      Application.SetSystemVariable("LWDISPLAY", 0);

      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.SendStringToExecute("REGEN3\n", true, false, false);
      doc.Editor.Regen();
    }

   
    public void OverruleStart()
    {
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeDrawOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeDrawOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeTransformOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeTransformOverrule.theOverrule,
      true
      );
      Overrule(true);
    }

   
    public void OverruleEnd()
    {
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeDrawOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeDrawOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeTransformOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeTransformOverrule.theOverrule
      );
      Overrule(false);
    }

   
    public void MakePipe()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      // Ask the user to select the entities to make into pipes

      PromptSelectionOptions pso =
      new PromptSelectionOptions();
      pso.AllowDuplicates = false;
      pso.MessageForAdding =
      "\nSelect objects to turn into pipes: ";

      PromptSelectionResult selRes =
      doc.Editor.GetSelection(pso);

      // If the user didn't make valid selection, we return

      if (selRes.Status != PromptStatus.OK)
      return;

      SelectionSet ss = selRes.Value;

      // Ask the user for the pipe radius to set

      PromptDoubleOptions pdo =
      new PromptDoubleOptions(
          "\nSpecify pipe radius:"
      );

      // Use the previous value, if if already called

      if (_radius > 0.0)
      {
      pdo.DefaultValue = _radius;
      pdo.UseDefaultValue = true;
      }
      pdo.AllowNegative = false;
      pdo.AllowZero = false;

      PromptDoubleResult pdr =
      ed.GetDouble(pdo);

      // Return if something went wrong

      if (pdr.Status != PromptStatus.OK)
      return;

      // Set the "last radius" value for when
      // the command is called next

      _radius = pdr.Value;

      // Use a transaction to edit our various objects

      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      // Lop through the selected objects

      foreach (SelectedObject o in ss)
      {
          // We could choose only to add XData to the objects
          // we know will use it (Lines and Circles, for now)

          DBObject obj =
            tr.GetObject(o.ObjectId, OpenMode.ForWrite);
          PipeDrawOverrule.SetPipeRadiusOnObject(tr, obj, _radius);
      }
      tr.Commit();
      }
    }
}
}
As before we can create a bunch of lines and circles, assign them radii using the MP command, and then display them using the OVERRULE1 command:


Now when we perform our EXPLODE, selecting the various objects, and instead of a rejection message we get a set of Solid3D objects replacing our lines and circles:

Too cool! :-)
As you can hopefully see, the more you look at the Overrule API in AutoCAD 2010, the more power you uncover. Keep the comments & questions coming – it’s fun to see how this little application is evolving based on your feedback.

雪山飞狐_lzh 发表于 2009-6-16 09:02:00

三、复制重定义
May 04, 2009
Using an AutoCAD 2010 overrule to control the copying of XData using .NET
It’s quite common for AutoCAD developers to use Extended Entity Data (XData) to tag objects their application cares about. This is certainly the approach taken in the recent series showing how to overrule the graphics display for an object – we store a “pipe radius” in XData attached to the Line or Circle we want to have a 3D profile.
That’s fine, but what if we’re storing a unique identifier with an object in XData that we do not want copied with the object? The Overrule API in AutoCAD 2010 allows you to hook into the DeepClone of an object and control what data gets copied with the object, whether via the COPY command or some other process that chooses to copy your object.
Here’s an example CopyOverrule class that makes use of the XData functions we previously defined as part of the PipeDrawOverrule class:
public class CopyOverrule : ObjectOverrule
{
static public CopyOverrule theOverrule =
    new CopyOverrule();

public override DBObject DeepClone(
    DBObject dbObject, DBObject ownerObject,
    IdMapping idMap, bool isPrimary
)
{
    // First we deep clone the object via the parent

    DBObject res =
      base.DeepClone(dbObject, ownerObject, idMap, isPrimary);

    // Then we check for our XData

    if (PipeDrawOverrule.PipeRadiusForObject(res) > 0.0)
    {
      // A transaction is needed by the set function to access
      // the RegApp table - we could also assume the app name
      // is registered and have a separate implementation
      // not taking the transaction...      
      // Just as we might also have chosen to remove the XData

      Transaction tr =
      dbObject.Database.TransactionManager.StartTransaction();
      using (tr)
      {
      PipeDrawOverrule.SetPipeRadiusOnObject(tr, res, 0.0);
      tr.Commit();
      }
    }
    return res;
}
}
In this code we call the DeepClone implementation in the parent class and set the result – the copy of our object – to the res variable. If there any additional objects were copied during the DeepClone process, there should be IdPair entries in the idMap variable referring to both the originator and the copy. In this code we just check the XData on the returned object, but we might also iterate through and check the contents of the idMap.
Also, rather than removing the XData – which some people might prefer to do, depending on the behaviour of their application – we just set it to 0. Which is enough to make sure the object’s graphic display doesn’t make use of a pipe radius.
Here’s the complete code, including the code to register and unregister the overrule in the OVERRULE0 and OVERRULE1 commands:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Colors;

namespace DrawOverrules
{
public abstract class PipeDrawOverrule : DrawableOverrule
{
    const string regAppName = "TTIF_PIPE";

    public PipeDrawOverrule()
    {
      // Tell AutoCAD to filter on our application name
      // (this means our overrule will only be called
      // on objects possessing XData with this name)

      SetXDataFilter(regAppName);
    }

    // Get the XData for a particular object
    // and return the "pipe radius" if it exists

    public static double PipeRadiusForObject(DBObject obj)
    {
      double res = 0.0;

      ResultBuffer rb = obj.XData;
      if (rb != null)
      {
      bool foundStart = false;

      foreach (TypedValue tv in rb)
      {
          if (tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName &&
            tv.Value.ToString() == regAppName)
            foundStart = true;
          else
          {
            if (foundStart == true)
            {
            if (tv.TypeCode == (int)DxfCode.ExtendedDataReal)
            {
                res = (double)tv.Value;
                break;
            }
            }
          }
      }
      rb.Dispose();
      }
      return res;
    }

    // Set the "pipe radius" in the XData of a particular object

    public static void SetPipeRadiusOnObject(
      Transaction tr, DBObject obj, double radius
    )
    {
      Database db = obj.Database;

      // Make sure the application is registered
      // (we could separate this out to be called
      // only once for a set of operations)

      RegAppTable rat =
      (RegAppTable)tr.GetObject(
          db.RegAppTableId,
          OpenMode.ForRead
      );

      if (!rat.Has(regAppName))
      {
      rat.UpgradeOpen();
      RegAppTableRecord ratr = new RegAppTableRecord();
      ratr.Name = regAppName;
      rat.Add(ratr);
      tr.AddNewlyCreatedDBObject(ratr, true);
      }

      // Create the XData and set it on the object

      ResultBuffer rb =
      new ResultBuffer(
          new TypedValue(
            (int)DxfCode.ExtendedDataRegAppName, regAppName
          ),
          new TypedValue(
            (int)DxfCode.ExtendedDataReal, radius
          )
      );
      obj.XData = rb;
      rb.Dispose();
    }
}

// An overrule to make a pipe out of line

public class LinePipeDrawOverrule : PipeDrawOverrule
{
    static public LinePipeDrawOverrule theOverrule =
      new LinePipeDrawOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw(Drawable d, WorldDraw wd)
    {
      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      Line line = d as Line;

      if (line != null)
      {
          // Draw the line as is, with overruled attributes

          base.WorldDraw(line, wd);
          if (!line.Id.IsNull && line.Length > 0.0)
          {
            // Draw a pipe around the line

            EntityColor c =
            wd.SubEntityTraits.TrueColor;
            wd.SubEntityTraits.TrueColor =
            new EntityColor(0x00AfAfff);
            wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
            Circle clr =
            new Circle(
                line.StartPoint,
                line.EndPoint - line.StartPoint,
                radius
            );
            ExtrudedSurface pipe = new ExtrudedSurface();
            try
            {
            pipe.CreateExtrudedSurface(
                clr, line.EndPoint - line.StartPoint, sweepOpts
            );
            }
            catch
            {
            Document doc =
                Application.DocumentManager.MdiActiveDocument;
            doc.Editor.WriteMessage(
                "\nFailed with CreateExtrudedSurface."
            );
            }
            clr.Dispose();
            pipe.WorldDraw(wd);
            pipe.Dispose();
            wd.SubEntityTraits.TrueColor = c;
          }
          return true;
      }
      }
      return base.WorldDraw(d, wd);
    }

    public override int SetAttributes(Drawable d, DrawableTraits t)
    {
      int b = base.SetAttributes(d, t);

      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      // Set color to index 6

      t.Color = 6;

      // and lineweight to .40 mm

      t.LineWeight = LineWeight.LineWeight040;
      }
      return b;
    }
}

// An overrule to make a pipe out of circle

public class CirclePipeDrawOverrule : PipeDrawOverrule
{
    static public CirclePipeDrawOverrule theOverrule =
      new CirclePipeDrawOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override bool WorldDraw(Drawable d, WorldDraw wd)
    {
      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      Circle circle = d as Circle;

      if (circle != null)
      {
          // Draw the circle as is, with overruled attributes

          base.WorldDraw(circle, wd);

          // Needed to avoid ill-formed swept surface

          if (circle.Radius > radius)
          {
            // Draw a pipe around the cirle

            EntityColor c = wd.SubEntityTraits.TrueColor;
            wd.SubEntityTraits.TrueColor =
            new EntityColor(0x3fffe0e0);
            wd.SubEntityTraits.LineWeight =
            LineWeight.LineWeight000;
            Vector3d normal =
            (circle.Center - circle.StartPoint).
                CrossProduct(circle.Normal);
            Circle clr =
            new Circle(
                circle.StartPoint, normal, radius
            );
            SweptSurface pipe = new SweptSurface();
            pipe.CreateSweptSurface(clr, circle, sweepOpts);
            clr.Dispose();
            pipe.WorldDraw(wd);
            pipe.Dispose();
            wd.SubEntityTraits.TrueColor = c;
          }
          return true;
      }
      }
      return base.WorldDraw(d, wd);
    }

    public override int SetAttributes(Drawable d, DrawableTraits t)
    {
      int b = base.SetAttributes(d, t);

      double radius = 0.0;

      if (d is DBObject)
      radius = PipeRadiusForObject((DBObject)d);

      if (radius > 0.0)
      {
      // Set color to index 2

      t.Color = 2;

      // and lineweight to .60 mm

      t.LineWeight = LineWeight.LineWeight060;
      }
      return b;
    }
}

public class LinePipeTransformOverrule : TransformOverrule
{
    static public LinePipeTransformOverrule theOverrule =
      new LinePipeTransformOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override void Explode(Entity e, DBObjectCollection objs)
    {
      double radius = 0.0;

      if (e is DBObject)
      radius = PipeDrawOverrule.PipeRadiusForObject(e);

      if (radius > 0.0)
      {
      Line line = e as Line;

      if (line != null)
      {
          if (!line.Id.IsNull && line.Length > 0.0)
          {
            // Draw a pipe around the line

            Circle clr =
            new Circle(
                line.StartPoint,
                line.EndPoint - line.StartPoint,
                radius
            );
            ExtrudedSurface pipe = new ExtrudedSurface();
            try
            {
            pipe.CreateExtrudedSurface(
                clr, line.EndPoint - line.StartPoint, sweepOpts
            );
            }
            catch
            {
            Document doc =
                Application.DocumentManager.MdiActiveDocument;
            doc.Editor.WriteMessage(
                "\nFailed with CreateExtrudedSurface."
            );
            }
            clr.Dispose();
            objs.Add(pipe);
          }
          return;
      }
      }
      base.Explode(e, objs);
    }
}

public class CirclePipeTransformOverrule : TransformOverrule
{
    static public CirclePipeTransformOverrule theOverrule =
      new CirclePipeTransformOverrule();

    private SweepOptions sweepOpts = new SweepOptions();

    public override void Explode(Entity e, DBObjectCollection objs)
    {
      double radius = 0.0;

      if (e is DBObject)
      radius = PipeDrawOverrule.PipeRadiusForObject(e);

      if (radius > 0.0)
      {
      Circle circle = e as Circle;

      if (circle != null)
      {
          // Needed to avoid ill-formed swept surface

          if (circle.Radius > radius)
          {
            // Draw a pipe around the cirle

            Vector3d normal =
            (circle.Center - circle.StartPoint).
                CrossProduct(circle.Normal);
            Circle clr =
            new Circle(
                circle.StartPoint, normal, radius
            );
            SweptSurface pipe = new SweptSurface();
            pipe.CreateSweptSurface(clr, circle, sweepOpts);
            clr.Dispose();
            objs.Add(pipe);
          }
          return;
      }
      }
      base.Explode(e, objs);
    }
}

public class CopyOverrule : ObjectOverrule
{
    static public CopyOverrule theOverrule =
      new CopyOverrule();

    public override DBObject DeepClone(
      DBObject dbObject, DBObject ownerObject,
      IdMapping idMap, bool isPrimary
    )
    {
      // First we deep clone the object via the parent

      DBObject res =
      base.DeepClone(dbObject, ownerObject, idMap, isPrimary);

      // Then we check for our XData

      if (PipeDrawOverrule.PipeRadiusForObject(res) > 0.0)
      {
      // A transaction is needed by the set function to access
      // the RegApp table - we could also assume the app name
      // is registered and have a separate implementation
      // not taking the transaction...      
      // Just as we might also have chosen to remove the XData

      Transaction tr =
          dbObject.Database.TransactionManager.StartTransaction();
      using (tr)
      {
          PipeDrawOverrule.SetPipeRadiusOnObject(tr, res, 0.0);
          tr.Commit();
      }
      }
      return res;
    }
}

public class Commands
{
    private double _radius = 0.0;

    public void Overrule(bool enable)
    {
      // Regen to see the effect
      // (turn on/off Overruling and LWDISPLAY)

      DrawableOverrule.Overruling = enable;
      if (enable)
      Application.SetSystemVariable("LWDISPLAY", 1);
      else
      Application.SetSystemVariable("LWDISPLAY", 0);

      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.SendStringToExecute("REGEN3\n", true, false, false);
      doc.Editor.Regen();
    }

   
    public void OverruleStart()
    {
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeDrawOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeDrawOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeTransformOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeTransformOverrule.theOverrule,
      true
      );
      ObjectOverrule.AddOverrule(
      RXClass.GetClass(typeof(Entity)),
      CopyOverrule.theOverrule,
      true
      );
      Overrule(true);
    }

   
    public void OverruleEnd()
    {
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeDrawOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeDrawOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Line)),
      LinePipeTransformOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Circle)),
      CirclePipeTransformOverrule.theOverrule
      );
      ObjectOverrule.RemoveOverrule(
      RXClass.GetClass(typeof(Entity)),
      CopyOverrule.theOverrule
      );
      Overrule(false);
    }

   
    public void MakePipe()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      // Ask the user to select the entities to make into pipes

      PromptSelectionOptions pso =
      new PromptSelectionOptions();
      pso.AllowDuplicates = false;
      pso.MessageForAdding =
      "\nSelect objects to turn into pipes: ";

      PromptSelectionResult selRes =
      doc.Editor.GetSelection(pso);

      // If the user didn't make valid selection, we return

      if (selRes.Status != PromptStatus.OK)
      return;

      SelectionSet ss = selRes.Value;

      // Ask the user for the pipe radius to set

      PromptDoubleOptions pdo =
      new PromptDoubleOptions(
          "\nSpecify pipe radius:"
      );

      // Use the previous value, if if already called

      if (_radius > 0.0)
      {
      pdo.DefaultValue = _radius;
      pdo.UseDefaultValue = true;
      }
      pdo.AllowNegative = false;
      pdo.AllowZero = false;

      PromptDoubleResult pdr =
      ed.GetDouble(pdo);

      // Return if something went wrong

      if (pdr.Status != PromptStatus.OK)
      return;

      // Set the "last radius" value for when
      // the command is called next

      _radius = pdr.Value;

      // Use a transaction to edit our various objects

      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      // Loop through the selected objects

      foreach (SelectedObject o in ss)
      {
          // We could choose only to add XData to the objects
          // we know will use it (Lines and Circles, for now)

          DBObject obj =
            tr.GetObject(o.ObjectId, OpenMode.ForWrite);
          PipeDrawOverrule.SetPipeRadiusOnObject(tr, obj, _radius);
      }
      tr.Commit();
      }
    }
}
}
Let’s see what happens when we attempt to copy a circular pipe using the standard COPY command (after having first created it using MP and have it display using OVERRULE1). We can see that as we select the location, the circle is displayed with a pipe radius:

But once we specify the location, the circle’s radius is removed:

雪山飞狐_lzh 发表于 2009-6-16 09:10:00

四、在属性窗口中显示和修改自定义属性
May 06, 2009
Modifying an AutoCAD object’s state via a dynamic property defined using .NET
I’ve been meaning to get to this one for a while. This post takes the OPM .NET implementation and shows how to use it to allow modification of data persisted with an object: in this case we’re going to use the XData in which we store the “pipe radius” for the AutoCAD 2010 overrule sample we’ve recently been developing.
To start with, I needed to migrate the OPM .NET module to work with AutoCAD 2010, which meant installing Visual Studio 2008 SP1. Other than that the code migrated very easily, and the project (with the built asdkOPMNetExt.dll assembly) can be found here. I recommend placing the module in AutoCAD’s main program folder and having it demand-load on AutoCAD startup (if you choose to use it).

Here’s the C# code to add to our overrule application:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.OPM;
using Autodesk.AutoCAD.Interop.Common;
using System.Runtime.InteropServices;
using System.Reflection;
using System;
using DrawOverrules;

namespace PropertyEditing
{
#region Our Custom Property
[
    Guid("E64CAA14-EA92-46ea-82D6-420FA873F16F"),
    ProgId("OverruleSample.PipeRadius.1"),
    ClassInterface(ClassInterfaceType.None),
    ComDefaultInterface(typeof(IDynamicProperty2)),
    ComVisible(true)
]
public class CustomProp : IDynamicProperty2
{
    private IDynamicPropertyNotify2 m_pSink = null;

    // Unique property ID

    public void GetGUID(out Guid propGUID)
    {
      propGUID =
      new Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB");
    }

    // Property display name

    public void GetDisplayName(out string szName)
    {
      szName = "Pipe radius";
    }

    // Show/Hide property in the OPM, for this object instance

    public void IsPropertyEnabled(object pUnk, out int bEnabled)
    {
      bEnabled = 1;
    }

    // Is property showing but disabled

    public void IsPropertyReadOnly(out int bReadonly)
    {
      bReadonly = 0;
    }

    // Get the property description string

    public void GetDescription(out string szName)
    {
      szName =
      "Radius of the pipe profile applied to this linear entity.";
    }

    // OPM will typically display these in an edit field
    // optional: meta data representing property type name,
    // ex. ACAD_ANGLE

    public void GetCurrentValueName(out string szName)
    {
      throw new System.NotImplementedException();
    }

    // What is the property type, ex. VT_R8

    public void GetCurrentValueType(out ushort varType)
    {
      // The Property Inspector supports the following data
      // types for dynamic properties:
      // VT_I2, VT_I4, VT_R4, VT_R8,VT_BSTR, VT_BOOL
      // and VT_USERDEFINED.

      varType = 4; // VT_R4?
    }

    // Get the property value, passes the specific object
    // we need the property value for.

    public void GetCurrentValueData(object pUnk, ref object pVarData)
    {
      // Get the value and return it to AutoCAD

      AcadObject obj = pUnk as AcadObject;
      if (obj != null)
      {
      Document doc =
          Application.DocumentManager.MdiActiveDocument;
      Transaction tr =
          doc.TransactionManager.StartTransaction();
      using (tr)
      {
          DBObject o =
            tr.GetObject(
            new ObjectId((IntPtr)obj.ObjectID),
            OpenMode.ForRead
            );
          pVarData =
            PipeDrawOverrule.PipeRadiusForObject(o);
      }
      }
      else
      pVarData = 0.0;
    }

    // Set the property value, passes the specific object we
    // want to set the property value for

    public void SetCurrentValueData(object pUnk, object varData)
    {
      // Save the value returned to you

      AcadObject obj = pUnk as AcadObject;
      if (obj != null)
      {
      Document doc =
          Application.DocumentManager.MdiActiveDocument;
      DocumentLock dl =
          doc.LockDocument(
            DocumentLockMode.ProtectedAutoWrite,
            null, null, true
          );      
      using (dl)
      {
          Transaction tr =
            doc.TransactionManager.StartTransaction();
          using (tr)
          {
            DBObject o =
            tr.GetObject(
                new ObjectId((IntPtr)obj.ObjectID),
                OpenMode.ForWrite
            );
            PipeDrawOverrule.SetPipeRadiusOnObject(
            tr, o, (float)varData
            );
            tr.Commit();
          }
      }
      }
    }

    // OPM passes its implementation of IDynamicPropertyNotify, you
    // cache it and call it to inform OPM your property has changed

    public void Connect(object pSink)
    {
      m_pSink = (IDynamicPropertyNotify2)pSink;
    }

    public void Disconnect()
    {
      m_pSink = null;
    }
}
#endregion

#region Application Entry Point
public class MyEntryPoint : IExtensionApplication
{
    protected internal CustomProp custProp = null;

    public void Initialize()
    {
      Assembly.LoadFrom("asdkOPMNetExt.dll");

      // Add the Dynamic Property to Lines and Circles
      // (might add it at the Entity level, instead)

      Dictionary classDict = SystemObjects.ClassDictionary;
      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
      RXClass cirDesc = (RXClass)classDict.At("AcDbCircle");
      custProp = new CustomProp();
      IPropertyManager2 pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
      pPropMan.AddProperty((object)custProp);
      pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(cirDesc);
      pPropMan.AddProperty((object)custProp);
    }

    public void Terminate()
    {
      // Remove the Dynamic Property

      Dictionary classDict = SystemObjects.ClassDictionary;
      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
      RXClass cirDesc = (RXClass)classDict.At("AcDbCircle");
      IPropertyManager2 pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);
      pPropMan.RemoveProperty((object)custProp);
      pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(cirDesc);
      pPropMan.RemoveProperty((object)custProp);
      custProp = null;
    }
}
#endregion
}
Some comments on the implementation:
GetCurrentValueData() and SetCurrentValueData() both have to open the object to access it’s .NET protocol
We might also have used COM to access the XData, but this approach reuses previously-developed code
To modify the object we need to lock the current document
We use the ProtectedAutoWrite locking mode for this, so that all our property edits are grouped into a single undo group
We use the “protected” version of the locking mode as there’s a lock needed elsewhere, probably in the drawing code. If we use the standard AutoWrite lock we get an eLockViolation message
We’re using a new transaction for each read/modification
This feels like overkill, but then as we’re in an UI-bound operation it’s unlikely to have a perceived performance impact
We’re also using the static protocol from the DrawOverrule class for the XData retrieval/setting
With hindsight this probably should live in its own helper class, which is the original way I had it :-S :-)
Here’s a model we’re going to modify using the Properties Palette:

Now we select one of our overruled objects – a circle – and see it's new dynamic property:

When we select all our objects, we see the property varies:

Now we modify the property to be the same for all our objects:

And we can see the result of our modification:

You can see that the property currently isn’t categorised: as mentioned previously, we would have to implement ICategorizedProperty in our OPM .NET module for this to be possible. Which I will attempt, one day.

雪山飞狐_lzh 发表于 2009-6-16 09:16:00

五、使用重定义亮显特定名字的块参照
June 15, 2009
Highlighting named blocks using AutoCAD 2010’s overrule API from .NET
This is a nice sample provided by Stephen Preston, who manages DevTech’s Americas team. Stephen has put this together in anticipation of his upcoming AU class on the overrule API introduced in AutoCAD 2010.
The sample allows the user to enter a text string that it uses to highlight any block containing that string in its name. This is quite handy for identifying the instances of a particular block in a drawing, but it might also be modified to highlight other objects (you might want to highlight mis-spelt words or standards violations, for instance).
Here’s the C# code, reformatted for this blog:
using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.GraphicsInterface;

namespace MyCustomFilterOverrule
{
// This is our custom DrawableOverrule class. We're just
// overruling WorldDraw and IsApplicable.
// This class is implemented as a singleton class, and
// includes subroutines that are called by the CommandMethods
// in another class

public class MyDrawOverrule : DrawableOverrule
{
    // Where properties have been defined, use the property rather
    // than the raw variable.
    // I'm using properties where I need some additional logic to
    // run as I get/set the variable.

    // The text we'll search for in our block name.

    private string mTxt;

    // Color Index of block highlight

    private short mColor = 3;

    // Used to track whether this Overrule has been registered
    // (so we don't try to register it more than once).

    private bool mRegistered = false;

    // Used to store one and only instance of our singleton class

    private static MyDrawOverrule mSingleton;

    // Used to reset Overruling value to the value it had before
    // we switched them on. (There may be other overrules in place)

    private static bool mOldOverruleValue;

    // The color we highlight blocks with
    private short HighlightColor
    {
      get { return mColor; }
      set { if (value >= 0 && value <= 127) mColor = value; }
    }

    // The text we'll search for in the block name

    private string SearchText
    {
      get { return mTxt; }
      set { mTxt = value; }
    }

    // Private constructor because its a singleton

    private MyDrawOverrule()
    {
      // Do nothing
    }

    // Shared propery to return our singleton instance
    // (and instantiate new instance on first call)

    public static MyDrawOverrule GetInstance
    {
      get
      {
      if (mSingleton == null)
      {
          mSingleton = new MyDrawOverrule();
      }
      return mSingleton;
      }
    }

    private void InitOverrule()
    {
      if (!mRegistered)
      {
      Overrule.AddOverrule(
          RXObject.GetClass(typeof(BlockReference)), this, false
      );
      SetCustomFilter();
      mOldOverruleValue = Overrule.Overruling;
      mRegistered = true;
      }
      Overrule.Overruling = true;
    }

    // Prompts user to select the color index they want to
    // highlight blocks with

    public void SetColor()
    {
      Editor ed =
      Application.DocumentManager.MdiActiveDocument.Editor;

      PromptIntegerOptions opts =
      new PromptIntegerOptions(
          "\nEnter block finder color index: "
      );
      opts.DefaultValue = HighlightColor;
      opts.LowerLimit = 0;
      opts.UpperLimit = 127;
      opts.UseDefaultValue = true;
      PromptIntegerResult res = ed.GetInteger(opts);

      // If requested highlight color is a new color,
      // then we want to change it

      if (res.Status == PromptStatus.OK &&
          HighlightColor != res.Value)
      {
      HighlightColor = (short)res.Value;

      // Regen is required to update changes on screen

      ed.Regen();
      }
    }

    public void FindText()
    {
      Editor ed =
      Application.DocumentManager.MdiActiveDocument.Editor;

      ed.WriteMessage(
      "\nCurrent block search text is \"{0}\".", SearchText
      );
      PromptStringOptions opts =
      new PromptStringOptions(
          "\nEnter new block search text: "
      );
      PromptResult res = ed.GetString(opts);

      // If the user cancelled then we exit the command

      if (res.Status != PromptStatus.OK)
      return;

      // If the user didn't type any text then we remove
      // the overrule and exit

      if (res.StringResult == "")
      {
      SearchText = "";
      ResetBlocks();
      }
      else
      {
      // Set search text for Overrule to that entered by user

      SearchText = res.StringResult.ToUpper();
      InitOverrule();

      // Turn Overruling on

      Overrule.Overruling = true;

      // Regen is required to update changes on screen.

      ed.Regen();
      }
    }

    // Removes our overrules

    public void ResetBlocks()
    {
      Editor ed =
      Application.DocumentManager.MdiActiveDocument.Editor;

      Overrule.Overruling = mOldOverruleValue;
      if (mRegistered)
      {
      Overrule.RemoveOverrule(
          RXObject.GetClass(typeof(BlockReference)), this
      );
      mRegistered = false;
      ed.Regen();
      }
    }

    // Overrule WorldDraw so we can draw our additional
    // graphics

    public override bool WorldDraw(Drawable drawable, WorldDraw wd)
    {
      // Better safe than sorry - check it really is a
      // BlockReference before continuing.

      BlockReference br = drawable as BlockReference;
      if (br != null)
      {
      // Now we want to draw a green box around the attributes
      // extents

      Extents3d ext = (Extents3d)br.Bounds;
      Point3d maxPt = ext.MaxPoint;
      Point3d minPt = ext.MinPoint;
      Point3dCollection pts = new Point3dCollection();

      // These are the vertices of the highlight box

      pts.Add(new Point3d(minPt.X, minPt.Y, minPt.Z));
      pts.Add(new Point3d(minPt.X, maxPt.Y, minPt.Z));
      pts.Add(new Point3d(maxPt.X, maxPt.Y, minPt.Z));
      pts.Add(new Point3d(maxPt.X, minPt.Y, minPt.Z));

      // Store current filltype and set to FillAlways

      FillType oldFillType = wd.SubEntityTraits.FillType;
      wd.SubEntityTraits.FillType = FillType.FillAlways;

      // Store old graphics color and set to the color we want

      short oldColor = wd.SubEntityTraits.Color;
      wd.SubEntityTraits.Color = HighlightColor;

      // Draw the filled polygon

      wd.Geometry.Polygon(pts);

      // Restore old settings

      wd.SubEntityTraits.FillType = oldFillType;
      wd.SubEntityTraits.Color = oldColor;
      }

      // Let the overruled Drawable draw itself.

      return base.WorldDraw(drawable, wd);
    }

    // This function is called if we call SetCustomFilter on our
    // custom overrule.
    // We add our own code to return true if the BlockReference
    // passed in is one we want to highlight.

    public override bool IsApplicable(RXObject overruledSubject)
    {
      // If it's a BlockReference, we check if the Block Name
      // contains our string

      BlockReference br = overruledSubject as BlockReference;
      if (br != null && SearchText != "")
      {
      // Returns whether the filter is applicable to this object

      return br.Name.Contains(SearchText);
      }

      // Only get to here if object isn't a BlockReference

      return false;
    }
}

// Our command class, which relays commands to MyDrawOverrule.

public class myPlugin
{
   
    public static void FindText()
    {
      MyDrawOverrule.GetInstance.FindText();
    }

   
    public static void SetColor()
    {
      MyDrawOverrule.GetInstance.SetColor();
    }
}
}
Here’s what happens when we OPEN the “Mechanical – Multileaders.dwg” sample drawing, NETLOAD our application and use SHOWBLOCKS to look for the “M045” text string:

Here’s what we see if we broaden the search to include all blocks with the string “M0” in their name and change the highlight colour to 1 using SHOWCOLOR:

To clear the selection, the user simply has to run SHOWBLOCKS and specify an empty string as the search term.
Stephen will be presenting both C# and VB.NET versions of this sample application during his class at this year’s AU. If you find overrules interesting, then I strongly recommend signing up for the session (I’ll let you know when registrations are open). I’m sure that during the class Stephen will be demonstrating other interesting capabilities made available to AutoCAD .NET developers by this very cool API.

雪山飞狐_lzh 发表于 2009-9-6 20:05:00

六、捕获夹点操作时的图元
August 03, 2009
Knowing when an AutoCAD object is grip-edited using overrules in .NET
This week I will mostly be posting about Overrules.
Aside from this post, Stephen Preston has sent me the samples he’s put together for his AU class on this topic, so expect some serious plagiarism (although now that I’ve given him credit I suppose it’s not really plagiarism :-).
Here’s a question I received recently by email:
    Is there some posibility to write something in some of your next blogs about how to get coordinate of grip when user move on screen in realtime. By using grip_stretch command...
    Example: we first draw polyline, then select it and move grip point...how we can know current position for grip coordinate before user pick point...in realtime...
Coincidentally I’ve been working on an internal project that uses Overrules to achieve this (or something quite similar). As I expect I’ve said before, AutoCAD 2010’s Overrule API is an incredibly powerful mechanism for hooking into and controlling object behaviour: it’s essentially our approach for providing the equivalent of custom object support to .NET programmers (which was the top item for a number of years on AutoCAD’s API wishlist).
To hook into object modification inside AutoCAD, we have a couple of options:
    * A TransformOverrule allows us to hook into an object’s TransformBy(), which tells use when it’s rotated, scaled or moved.
          o This is effective at trapping object-level transformations, whether via grips or commands such as MOVE, but won’t tell you when a specific vertex is modified (for instance)
    * A GripOverrule allows us to hook into GetGripPointsAt() and MoveGripPointsAt(), which can tell use when a grip-stretch is performed on our object.
          o This gives us more fine-grained information on a per-grip basis, but clearly won’t be called for commands such as MOVE which bypass the use of grips.
In this particular instance it makes more sense to use a GripOverrule, as we need to know when a particular polyline vertex is edited.
Here’s some C# code that implements a GripOverrule for AutoCAD entities (it works just as well for any entity with grips implemented via GetGripPointsAt() & MoveGripPointsAt(), so there’s no need to limit it just to Polylines, even if we could very easily).
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;

namespace GripOverruleTest
{
public class GripVectorOverrule : GripOverrule
{
    // A static pointer to our overrule instance

    static public GripVectorOverrule theOverrule =
      new GripVectorOverrule();

    // A flag to indicate whether we're overruling

    static bool overruling = false;

    // A single set of grips would not have worked in
    // the case where multiple objects were selected.

    static Dictionary<string, Point3dCollection> _gripDict =
      new Dictionary<string, Point3dCollection>();

    public GripVectorOverrule()
    {
    }

    private string GetKey(Entity e)
    {
      // Generate a key based on the name of the object's type
      // and its geometric extents

      // (We cannot use the ObjectId, as this is null during
      // grip-stretch operations.)

      return e.GetType().Name + ":" + e.GeometricExtents.ToString();
    }

    // Save the locations of the grips for a particular entity

    private void StoreGripInfo(Entity e, Point3dCollection grips)
    {
      string key = GetKey(e);
      if (_gripDict.ContainsKey(key))
      {
      // Clear the grips if any already associated

      Point3dCollection grps = _gripDict;
      using (grps)
      {
          grps.Clear();
      }
      _gripDict.Remove(key);
      }

      // Now we add our grips

      Point3d[] pts = new Point3d;
      grips.CopyTo(pts, 0);
      Point3dCollection gps = new Point3dCollection(pts);
      _gripDict.Add(key, gps);
    }

    // Get the locations of the grips for an entity

    private Point3dCollection RetrieveGripInfo(Entity e)
    {
      Point3dCollection grips = null;
      string key = GetKey(e);
      if (_gripDict.ContainsKey(key))
      {
      grips = _gripDict;
      }
      return grips;
    }

    public override void GetGripPoints(
      Entity e,
      Point3dCollection grips,
      IntegerCollection snaps,
      IntegerCollection geomIds
    )
    {
      base.GetGripPoints(e, grips, snaps, geomIds);

      StoreGripInfo(e, grips);
    }

    public override void MoveGripPointsAt(
      Entity e,
      IntegerCollection indices,
      Vector3d offset
    )
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      Point3dCollection grips = RetrieveGripInfo(e);
      if (grips != null)
      {
      // Could get multiple points moved at once,
      // hence the integer collection

      foreach (int i in indices)
      {
          // Get the grip point from our internal state

          Point3d pt = grips;

          // Draw a vector from the grip point to the newly
          // offset location, using the index into the
          // grip array as the color (excluding colours 0 and 7).

          // These vectors don't getting cleared, which makes
          // for a fun effect.

          ed.DrawVector(
            pt,
            pt + offset,
            (i >= 6 ? i + 2 : i + 1), // exclude colours 0 and 7
            false
          );
      }
      }
      base.MoveGripPointsAt(e, indices, offset);
    }

   
    public void GripOverruleOnOff()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      if (overruling)
      {
      ObjectOverrule.RemoveOverrule(
          RXClass.GetClass(typeof(Entity)),
          GripVectorOverrule.theOverrule
      );
      }
      else
      {
      ObjectOverrule.AddOverrule(
          RXClass.GetClass(typeof(Entity)),
          GripVectorOverrule.theOverrule,
          true
      );
      }
      overruling = !overruling;
      GripOverrule.Overruling = overruling;

      ed.WriteMessage(
      "\nGrip overruling turned {0}.",
      (overruling ? "on" : "off")
      );
    }
}
}
One important thing to bear in mind about this sample: we have to store the grips for a particular entity during the GetGripPointsAt() call and then use them during the MoveGripPointsAt() call. This is complicated for a number of reasons...
Firstly, we can’t just store the points in a single point collection, as there could be multiple calls to GetGripPointsAt() (while the grips are retrieved and displayed by AutoCAD) followed by multiple calls to MoveGripPointsAt() (while the grips are used to manipulate the objects). So we really need to map a set of points to a particular object.
The really tricky thing is that we have an entity passed into both GetGripPointsAt() and MoveGripPointsAt(), but – and here’s the rub – the GetGripPointsAt() entity is typically the original, database-dependent entity, while the entities passed into MoveGripPointsAt() are temporary clones. The temporary clones do not have ObjectIds, which is the best way to identify objects and associate data with them in a map (for instance).
The default cloning behaviour can be overruled using a TransformOverrule – by returning false from the CloneMeForDragging() callback – but assuming we don’t do that (and we don’t really want to do that, as it opens another can of worms), we need to find another way to identify an object that is passed into GetGripPointsAt() with its clone passed in to MoveGripPointsAt().
The solution I ended up going for was to generate a key based on the class-name of the object followed by its geometric extents. This isn’t perfect, but it’s as good as I could find. There is a possibility that entities of the same type could exist at the same spot and still have different grips (which is the main problem – if they have the same grip locations then it doesn’t matter), but that will cause problems with this implementation. If people want to implement a more robust technique, they will probably need to do so for specific entity types, where they have access to more specific information that will help identify their objects: we are sticking to generic entity-level information, which makes it a little hard to be 100% certain we have the right object.
One other point… as we’re using the geometric extents to identify an object, it’s hard to clear the grip information from our dictionary: by the time the OnGripStatusChanged() method is called, the entity’s geometry has typically changed, which means we can no longer find it in the dictionary. So for a cleaner solution it’s worth clearing the dictionary at an appropriate moment (probably using some kind of command- or document locking-event).
OK, now let’s see what happens when we NETLOAD our application and run our GOO command, which toggles the use of the grip overrule:
Command: GOO
Grip overruling turned on.
OK, so far so good. Let’s create a standard AutoCAD polyline containing both line and arc segments:

When we select it, we see its grips along with the quick properties panel:

And when we use its various grips, we see temporary vectors drawn during each MoveGripPointsAt() call (which will disappear at the next REGEN):

Fun stuff! I’m looking forward to diving further into Overrules over the next week or two… :-)

雪山飞狐_lzh 发表于 2009-9-7 12:03:00

七、简单的改变直线显示
August 17, 2009
A simple overrule to change the way AutoCAD lines are displayed using .NET
Thanks to Stephen Preston, who manages our DevTech Americas team, for donating the samples from his upcoming AU class for posting on this blog.
Let’s start the week with a nice simple sample: the first from Stephen’s AU class. Looking back even to the first C# overrule sample I posted here, I can see that most have been quite complex, mainly because they’ve performed complicated things. Today’s code implements a very simple DrawableOverrule which changes the way lines are displayed in AutoCAD:
Here’s Stephen’s C# code, reformatted to fit the blog:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.GraphicsInterface;

namespace MyFirstOverrule
{
// This is our custom DrawableOverrule class.
// In this case we're just overruling WorldDraw

public class MyDrawOverrule : DrawableOverrule
{
    public override bool WorldDraw(Drawable drawable, WorldDraw wd)
    {
      // Cast Drawable to Line so we can access its methods and
      // properties
      Line ln = (Line)drawable;

      // Draw some graphics primitives
      wd.Geometry.Circle(
      ln.StartPoint + 0.5 * ln.Delta,
      ln.Length / 5,
      ln.Normal
      );

      // In this case we don't want the line to draw itself, nor do
      // we want ViewportDraw called
      return true;
    }
}

public class Commands
{
    //Shared member variable to store our Overrule instance
    private static MyDrawOverrule _drawOverrule;

   
    public static void ToggleOverrule()
    {
      // Initialize Overrule if first time run

      if (_drawOverrule == null)
      {
      _drawOverrule = new MyDrawOverrule();
      Overrule.AddOverrule(
          RXObject.GetClass(
            typeof(Line)),
            _drawOverrule,
            false
          );
      Overrule.Overruling = true;
      }
      else
      {
      // Toggle Overruling on/off
      Overrule.Overruling = !Overrule.Overruling;
      }

      // Regen is required to update changes on screen
      Application.DocumentManager.MdiActiveDocument.Editor.Regen();
    }
}
}
Some points to note:
    * This code chooses to replace the way lines are currently drawn
          o Rather than drawing a line, we draw a circles at the line’s mid-point with a radius relative to the line’s length
    * We’ve deliberately kept the code very simple: a single command (TOG) is used to toggle the use of the overrule
    * Rather than always relying on toggling the overall Overruling state, we force it to true when we run for the first time
          o I have another application loaded that implements overrules: the first time the TOG command was run previously, overruling was actually turned off if we don’t do it this way
To try the code, build the application against AutoCAD 2010 (or even higher, if you’re visiting us from the future :-) and draw some lines:

Now let’s NETLOAD our application and run the TOG command:

Each line has been “replaced” by a circle. But when we select one of the circles, we see it’s really just a line, and shows the grips a line would:

When we grip-edit an end-point of one of our lines, we can see it changing the line, but the graphics displayed continue to be circular:

And finally, if we finish the editing operation and run the TOG command again we see the lines has been modified:

That’s it for today’s post. I’ll probably be delving further into Stephen’s material for posts later in the week.


雪山飞狐_lzh 发表于 2009-9-12 20:17:00

八、把点附着到曲线
August 24, 2009
Gluing a point to an AutoCAD curve using overrules from .NET – Part 1
Over the weekend I put together a little prototype to prove a concept for an internal project I’m working on. The idea was to force a point onto a curve (meaning anything inheriting from Curve in AutoCAD, such as Arc, Circle, Ellipse, Leader, Line, Polyline, Polyline2d, Polyline3d, Ray, Spline, Xline…), so that when the point is moved it snaps onto the curve to which it’s assigned. The solution I’ve put together is far from being complete – which is partly why I’m planning on making this a series, so I can flesh it out a little further in further posts – but it does demonstrate a reasonable technique for addressing the requirement.
The approach I chose was to use a TransformOverrule to modify the standard AutoCAD point’s TransformBy() behaviour (see this previous post for some commentary on using a TransformOverrule vs. a GripOverrule). Our TransformOverrule stores a list of curves that have had points attached to them, and during TransformBy() we check each one to see which curve this point was on. We then get the transformed point (i.e. the one being chosen by the user) and from there we get the closest point on that curve, which becomes the point’s new location.
TransformBy() is a pretty handy operation to overrule: it’s used by grip-editing and by the MOVE command, so you know these operations will lead to your object’s positional integrity being maintained (direct modification of properties, such as via the Properties Palette, won’t lead to it being called, however, so it’s not enough if you need to maintain complete control).
Some comments on the choice of storing a list of curves rather than some other association between the point and the curve:
    * Storing a map between the point (via its ObjectId) and the curve wouldn’t work, as TransformBy() often has to work on a temporary copy of an object, rather than the object itself (and the copy’s ObjectId will therefore be Null).
          o We might also have looked at an approach such as the one used previously in our GripOverrule.
    * It might be possible to attach data (perhaps XData) to the point identifying the curve it’s attached to, but this would need to be available on the temporary clone (and is something that I’d need to check works).
    * It’s possible that working through a list of curves, checking each one, could become perceptibly slow if working with huge sets of data, but at that point a more efficient spatial indexing technique could be adopted.
    * Using a list of curves also allows us to modify the implementation to allow a point to travel along a network of curves (we’ll go through this modification in a future post).
    * One drawback of this approach is that once we move the curve independently from the point, they become detached as the point is no longer on any of the curves. We’ve put a specific clause in to allow points not on curves to be moved, but it would also be good to have the point be transformed along with the curve, so they stay together (another potential future modification). In the meantime the user will have to move the point back onto the curve (using the NEAr object snap, to make sure it’s precise) for the overrule to work for it, again.
One last point: we’re not persisting the curve list, in any way, so don’t expect points to reattach to lines when a session is restarted (until the POC command is used to create further points on curves and therefore add the curves to the list of those being managed by our system).
OK, enough blather, let’s get on with looking at the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;

namespace PointOnCurveTest
{
public class PtTransOverrule : TransformOverrule
{
    // A static pointer to our overrule instance

    static public PtTransOverrule theOverrule =
      new PtTransOverrule();

    // A list of the curves that have had points
    // attached to

    static internal List<ObjectId> _curves =
      new List<ObjectId>();

    // A flag to indicate whether we're overruling

    static bool overruling = false;

    public PtTransOverrule() {}

    // Out primary overruled function

    public override void TransformBy(Entity e, Matrix3d mat)
    {
      // We only care about points

      DBPoint pt = e as DBPoint;
      if (pt != null)
      {
      Database db = HostApplicationServices.WorkingDatabase;

      // For each curve, let's check whether our point is on it

      bool found = false;

      // We're using an Open/Close transaction, to avoid problems
      // with us using transactions in an event handler

      OpenCloseTransaction tr =
          db.TransactionManager.StartOpenCloseTransaction();
      using (tr)
      {
          foreach (ObjectId curId in _curves)
          {
            DBObject obj = tr.GetObject(curId, OpenMode.ForRead);
            Curve cur = obj as Curve;
            if (cur != null)
            {
            Point3d ptOnCurve =
                cur.GetClosestPointTo(pt.Position, false);
            Vector3d dist = ptOnCurve - pt.Position;
            if (dist.IsZeroLength(Tolerance.Global))
            {
                Point3d pos =
                  cur.GetClosestPointTo(
                  pt.Position.TransformBy(mat),
                  false
                  );
                pt.Position = pos;
                found = true;
                break;
            }
            }
          }
          // If the point isn't on any curve, let the standard
          // TransformBy() do its thing

          if (!found)
          {
            base.TransformBy(e, mat);
          }
      }
      }
    }

   
    public void CreatePointOnCurve()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      // Ask the user to select a curve

      PromptEntityOptions opts =
      new PromptEntityOptions(
          "\nSelect curve at the point to create: "
      );
      opts.SetRejectMessage(
      "\nEntity must be a curve."
      );
      opts.AddAllowedClass(typeof(Curve), false);

      PromptEntityResult per = ed.GetEntity(opts);

      ObjectId curId = per.ObjectId;
      if (curId != ObjectId.Null)
      {
      // Let's make sure we'll be able to see our point

      db.Pdmode = 97;// square with a circle
      db.Pdsize = -10; // relative to the viewport size

      Transaction tr =
          doc.TransactionManager.StartTransaction();
      using (tr)
      {
          DBObject obj =
            tr.GetObject(curId, OpenMode.ForRead);
          Curve cur = obj as Curve;
          if (cur != null)
          {
            // Out initial point should be the closest point
            // on the curve to the one picked

            Point3d pos =
            cur.GetClosestPointTo(per.PickedPoint, false);
            DBPoint pt = new DBPoint(pos);

            // Add it to the same space as the curve

            BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
                cur.BlockId,
                OpenMode.ForWrite
            );
            ObjectId ptId = btr.AppendEntity(pt);
            tr.AddNewlyCreatedDBObject(pt, true);
          }
          tr.Commit();

          // And add the curve to our central list

          _curves.Add(curId);
      }

      // Turn on the transform overrule if it isn't already

      if (!overruling)
      {
          ObjectOverrule.AddOverrule(
            RXClass.GetClass(typeof(DBPoint)),
            PtTransOverrule.theOverrule,
            true
          );
          overruling = true;
          TransformOverrule.Overruling = true;
      }
      }
    }
}
}
Now let’s see what happens when we run the POC command (for Point On Curve, but then it’s also a Proof Of Concept – geddit? :-).
The POC command will create a point at the selected location on a curve, at which point we can grip-edit it:

We can see that as we move the grip point around the drawing, the closest point to the attached curve is always used:

The point is always on the original curve, even if we try to select a point on another curve (even one that may be on the list of curves maintained by our PtTransOverrule class, if the POC command has been used on other curves):

That’s enough to get us started. Later in the week we’ll look at extending the code to create a stronger attachment between the point and the curve, but also to allow the point to travel along a network of curves. Fun, fun, fun! :-)

雪山飞狐_lzh 发表于 2009-9-12 20:20:00

August 26, 2009
Gluing a point to an AutoCAD curve using overrules from .NET – Part 2
In the last post we looked at some code to create a point on a curve, and make sure it stays on that curve when edited.
In this post we’re extending that code (albeit slightly) to work with a network of curves: the idea is that any curve which has a point created on it becomes a candidate for any point to snap onto as it moves around. This could clearly be extended to provided a better way of specifying the curves forming the network, of course.
Here’s the updated C# code
sing Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
namespace PointOnCurveTest
{
public class PtTransOverrule : TransformOverrule
{
    // A static pointer to our overrule instance
    static public PtTransOverrule theOverrule =
      new PtTransOverrule();
    // A list of the curves that have had points
    // attached to
    static internal List<ObjectId> _curves =
      new List<ObjectId>();
    // A flag to indicate whether we're overruling
    static bool overruling = false;
    public PtTransOverrule() {}
    // Out primary overruled function
    public override void TransformBy(Entity e, Matrix3d mat)
    {
      // We only care about points
      DBPoint pt = e as DBPoint;
      if (pt != null)
      {
      Database db = HostApplicationServices.WorkingDatabase;
      // Work through the curves to find the closest to our
      // transformed point
      double min = 0.0;
      Point3d bestPt = Point3d.Origin;
      bool first = true;
      // We're using an Open/Close transaction, to avoid
      // problems with us using transactions in an event
      // handler
      OpenCloseTransaction tr =
          db.TransactionManager.StartOpenCloseTransaction();
      using (tr)
      {
          foreach (ObjectId curId in _curves)
          {
            DBObject obj =
            tr.GetObject(curId, OpenMode.ForRead);
            Curve cur = obj as Curve;
            if (cur != null)
            {
            Point3d ptLoc =
                pt.Position.TransformBy(mat);
            Point3d ptOnCurve =
                cur.GetClosestPointTo(ptLoc, false);
            Vector3d dist = ptOnCurve - ptLoc;
            if (first || dist.Length < min)
            {
                first = false;
                min = dist.Length;
                bestPt = ptOnCurve;
            }
            }
          }
          pt.Position = bestPt;
      }
      }
    }
   
    public void CreatePointOnCurve()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
    // Ask the user to select a curve
      PromptEntityOptions opts =
      new PromptEntityOptions(
      "\nSelect curve at the point to create: "
      );
      opts.SetRejectMessage(
      "\nEntity must be a curve."
      );
      opts.AddAllowedClass(typeof(Curve), false);
      PromptEntityResult per = ed.GetEntity(opts);
   
      ObjectId curId = per.ObjectId;
      if (curId != ObjectId.Null)
      {
      // Let's make sure we'll be able to see our point
      db.Pdmode = 97;// square with a circle
      db.Pdsize = -10; // relative to the viewport size
      Transaction tr =
          doc.TransactionManager.StartTransaction();
      using (tr)
      {
          DBObject obj =
            tr.GetObject(curId, OpenMode.ForRead);
          Curve cur = obj as Curve;
          if (cur != null)
          {
          // Our initial point should be the closest point
          // on the curve to the one picked
            Point3d pos =
            cur.GetClosestPointTo(per.PickedPoint, false);
            DBPoint pt = new DBPoint(pos);
          // Add it to the same space as the curve
            BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
                cur.BlockId,
                OpenMode.ForWrite
            );
            ObjectId ptId = btr.AppendEntity(pt);
            tr.AddNewlyCreatedDBObject(pt, true);
          }
          tr.Commit();
      
      // And add the curve to our central list
      
          _curves.Add(curId);
      }
      // Turn on the transform overrule if it isn't already
      if (!overruling)
      {
          ObjectOverrule.AddOverrule(
            RXClass.GetClass(typeof(DBPoint)),
          PtTransOverrule.theOverrule,
            true
          );
          overruling = true;
          TransformOverrule.Overruling = true;
      }
      }
    }
}
}Now after having run our POC command to add points to a number of curves – effectively adding them to the “network” – we can grip-move a point, and should another curve in the network be closer, the point being edited will snap across to that one:
页: [1] 2
查看完整版本: Kean专题(11)—Overrules