雪山飞狐_lzh 发表于 2009-5-14 12:43:00

Kean专题(1)—Jigs

转载自http://through-the-interface.typepad.com/through_the_interface/jigs/
一、使用Jig动态旋转实体
2008年3月28日
通过 .NET 动态旋转AutoCAD图元
本文由明经通道 mccad 翻译,转载请注明出处
在上一文章中有一有趣的需求与另一文章中的问题相似。原来的问题是以矩形的中心旋转矩形多段线图元,并且之后还可继续旋转它。有几点我比较感兴趣:
矩形是由四个点组成的简单多段线,它并不存在中心点和旋转角度的概念。
至少对我来说,很明显需要计算中心点并将旋转角度做为XData保存在多段线上。
要形象化的修改最好是以动态方式。
大部分示例是演示如何使用动态来创建新图元,而非修改现有图元。
因此,考虑到这一点,我使用以下C#代码来解决这个问题:using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;

namespace RotatingRectangles
{
public class Commands
{
    // Define some constants we'll use to
    // store our XData

    // AppName is our RDS (TTIF, for
    // "Through The InterFace") plus an indicator
    // what it's for (ROTation)

    const string kRegAppName = "TTIF_ROT";
    const int kAppCode = 1001;
    const int kRotCode = 1040;

    class RotateJig : EntityJig
    {
      // Declare some internal state

      double m_baseAngle, m_deltaAngle;
      Point3d m_rotationPoint;
      Matrix3d m_ucs;

      // Constructor sets the state and clones
      // the entity passed in
      // (adequate for simple entities)

      public RotateJig(
      Entity ent,
      Point3d rotationPoint,
      double baseAngle,
      Matrix3d ucs)
      : base(ent.Clone() as Entity)
      {
      m_rotationPoint = rotationPoint;
      m_baseAngle = baseAngle;
      m_ucs = ucs;
      }

      protected override SamplerStatus Sampler(
      JigPrompts jp
      )
      {
      // We acquire a single angular value

      JigPromptAngleOptions jo =
          new JigPromptAngleOptions(
            "\nAngle of rotation: "
          );
      jo.BasePoint = m_rotationPoint;
      jo.UseBasePoint = true;

      PromptDoubleResult pdr =
          jp.AcquireAngle(jo);

      if (pdr.Status == PromptStatus.OK)
      {
          // Check if it has changed or not
          // (reduces flicker)

          if (m_deltaAngle == pdr.Value)
          {
            return SamplerStatus.NoChange;
          }
          else
          {
            // Set the change in angle to
            // the new value

            m_deltaAngle = pdr.Value;
            return SamplerStatus.OK;
          }
      }
      return SamplerStatus.Cancel;
      }

      protected override bool Update()
      {
      // We rotate the polyline by the change
      // minus the base angle

      Matrix3d trans =
          Matrix3d.Rotation(
            m_deltaAngle - m_baseAngle,
            m_ucs.CoordinateSystem3d.Zaxis,
            m_rotationPoint);
      Entity.TransformBy(trans);

      // The base becomes the previous delta
      // and the delta gets set to zero

      m_baseAngle = m_deltaAngle;
      m_deltaAngle = 0.0;

      return true;
      }

      public Entity GetEntity()
      {
      return Entity;
      }

      public double GetRotation()
      {
      // The overall rotation is the
      // base plus the delta

      return m_baseAngle + m_deltaAngle;
      }
    }

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

      // First we prompt for the entity to rotate

      PromptEntityOptions peo =
      new PromptEntityOptions(
          "\nSelect entity to rotate: "
      );
      PromptEntityResult per =
      ed.GetEntity(peo);

      if (per.Status == PromptStatus.OK)
      {
      Transaction tr =
          db.TransactionManager.StartTransaction();
      using (tr)
      {
          DBObject obj =
            tr.GetObject(per.ObjectId, OpenMode.ForRead);
          Entity ent = obj as Entity;

          // Use the origin as the default center

          Point3d rotationPoint = Point3d.Origin;

          // If the entity is a polyline,
          // assume it is rectangular and then
          // set the rotation point as its center

          Polyline pl = obj as Polyline;
          if (pl != null)
          {
            LineSegment3d ps0 =
            pl.GetLineSegmentAt(0);
            LineSegment3d ps1 =
            pl.GetLineSegmentAt(1);
            Vector3d vec =
            ((ps0.EndPoint - ps0.StartPoint) / 2.0) +
            ((ps1.EndPoint - ps1.StartPoint) / 2.0);
            rotationPoint = pl.StartPoint + vec;
          }

          // Get the base rotation angle stored with the
          // entity, if there was one (default is 0.0)

          double baseAngle = GetStoredRotation(obj);

          if (ent != null)
          {
            // Get the current UCS, to pass to the Jig

            Matrix3d ucs =
            ed.CurrentUserCoordinateSystem;

            // Create our jig object

            RotateJig jig =
            new RotateJig(
                ent,
                rotationPoint,
                baseAngle,
                ucs
            );

            PromptResult res = ed.Drag(jig);
            if (res.Status == PromptStatus.OK)
            {
            // Get the overall rotation angle
            // and dispose of the temp clone

            double newAngle = jig.GetRotation();
            jig.GetEntity().Dispose();

            // Rotate the original entity

            Matrix3d trans =
                Matrix3d.Rotation(
                  newAngle - baseAngle,
                  ucs.CoordinateSystem3d.Zaxis,
                  rotationPoint);

            ent.UpgradeOpen();
            ent.TransformBy(trans);

            // Store the new rotation as XData

            SetStoredRotation(ent, newAngle);
            }
          }
          tr.Commit();
      }
      }
    }

    // Helper function to create a RegApp

    static void AddRegAppTableRecord(string regAppName)
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      Database db = doc.Database;

      Transaction tr =
      doc.TransactionManager.StartTransaction();
      using (tr)
      {
      RegAppTable rat =
          (RegAppTable)tr.GetObject(
            db.RegAppTableId,
            OpenMode.ForRead,
            false
          );
      if (!rat.Has(regAppName))
      {
          rat.UpgradeOpen();
          RegAppTableRecord ratr =
            new RegAppTableRecord();
          ratr.Name = regAppName;
          rat.Add(ratr);
          tr.AddNewlyCreatedDBObject(ratr, true);
      }
      tr.Commit();
      }
    }

    // Store our rotation angle as XData

    private void SetStoredRotation(
      DBObject obj, double rotation)
    {
      AddRegAppTableRecord(kRegAppName);
      ResultBuffer rb = obj.XData;
      if (rb == null)
      {
      rb =
          new ResultBuffer(
            new TypedValue(kAppCode, kRegAppName),
            new TypedValue(kRotCode, rotation)
          );
      }
      else
      {
      // We can simply add our values - no need
      // to remove the previous ones, the new ones
      // are the ones that get stored

      rb.Add(new TypedValue(kAppCode, kRegAppName));
      rb.Add(new TypedValue(kRotCode, rotation));
      }
      obj.XData = rb;
      rb.Dispose();
    }

    // Retrieve the existing rotation angle from XData

    private double GetStoredRotation(DBObject obj)
    {
      double ret = 0.0;

      ResultBuffer rb = obj.XData;
      if (rb != null)
      {
      // If we find our group code, it means that on
      // the next iteration, we'll get our rotation

      bool bReadyForRot = false;
      foreach (TypedValue tv in rb)
      {
          if (bReadyForRot)
          {
            if (tv.TypeCode == kRotCode)
            ret = (double)tv.Value;
            bReadyForRot = false;
          }
          if (tv.TypeCode == kAppCode)
            bReadyForRot = true;
      }
      rb.Dispose();
      }
      return ret;
    }
}
}
试用代码前,可用 RECTANG 命令创建一水平方向矩形,然后使用自定义的ROT命令来旋转它
   
之后再调用 ROT命令运行也会正常,因为图元“记住”了旋转角度。如果用了其它的工具旋转了这个矩形,这种效果就会消失。一种方法是深层的分析矩形来测量“旋转角度”:确定矩形的长边,获得它的角度。另一种毫无意义的方法是执行命令让用户输入角度(选择两个点或直接输入)。


雪山飞狐_lzh 发表于 2009-5-14 13:22:00


二、在绘图界面动态显示坐标
December 12, 2008
Drawing text planar to the screen inside AutoCAD's drawing window using .NET
I've often seen the question, over the years, of how to draw text in the plane of the screen, even when the current view is not planar to the current UCS. This ability to "screen fix" text has been there, but has required a number of sometimes tricky transformations to get the right behaviour. Well, during a recent internal discussion I became aware of a really handy facility inside AutoCAD which allows you to dependably draw screen-fixed text without jumping through hoops.
In this simple example, we're implementing a DrawJig - a jig that doesn't host an entity but allows us to implement a WorldDraw() callback for something to be drawn - which draws text at a fixed screen location, with a fixed size and orientation. Our code takes the simple task of asking the user to select a point, and shows the current cursor location during the drag at an offset of 30, 30 from the bottom left corner of the drawing window.
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
namespace JigTextPlanarToScreen
{
public class TextJig : DrawJig
{
    private Point3d _position;
    public Point3d Position
    {
      get { return _position; }
    }
    // We'll keep our style alive rather than recreating it
    private TextStyle _style;
    public TextJig()
    {
      _style = new TextStyle();
      _style.Font =
      new FontDescriptor("Calibri", false, true, 0, 0);
      _style.TextSize = 10;
    }
    protected override SamplerStatus Sampler(JigPrompts prompts)
    {
      JigPromptPointOptions opts = new JigPromptPointOptions();
      opts.UserInputControls =
          UserInputControls.Accept3dCoordinates;
      opts.Message = "\nSelect point: ";
      PromptPointResult res = prompts.AcquirePoint(opts);
      if (res.Status == PromptStatus.OK)
      {
      if (_position == res.Value)
      {
          return SamplerStatus.NoChange;
      }
      else
      {
          _position = res.Value;
          return SamplerStatus.OK;
      }
      }
      return SamplerStatus.Cancel;
    }
    protected override bool WorldDraw(WorldDraw draw)
    {
      // We make use of another interface to push our transforms
      WorldGeometry2 wg2 = draw.Geometry as WorldGeometry2;
      if (wg2 != null)
      {
      // Push our transforms onto the stack
      wg2.PushOrientationTransform(
          OrientationBehavior.Screen
      );
      wg2.PushPositionTransform(
          PositionBehavior.Screen,
          new Point2d(30, 30)
      );
      // Draw our screen-fixed text
      wg2.Text(
          new Point3d(0, 0, 0),// Position
          new Vector3d(0, 0, 1), // Normal
          new Vector3d(1, 0, 0), // Direction
          _position.ToString(), // Text
          true,                  // Rawness
          _style                // TextStyle
      );
      // Remember to pop our transforms off the stack
      wg2.PopModelTransform();
      wg2.PopModelTransform();
      }
      return true;
    }
   
    static public void SelectPointWithJig()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      TextJig jig = new TextJig();
      PromptResult res = ed.Drag(jig);
      if (res.Status == PromptStatus.OK)
      {
      ed.WriteMessage(
          "\nPoint selected: {0}",
          jig.Position
      );
      }
    }
}
}
Now let's see our SELPT command in action.
First in a fairly standard view:         And now in an arbitrary 3D view:

OK, that's it for today. Right now I'm at our Developer Day event in Paris, and after this I'm taking a four-week break over the holiday season. Which means my blog output is likely to slow down (to a trickle, perhaps even stop completely) over the coming weeks. So - just in case - I'd like to wish all the readers of "Through the Interface" all the very best for the holiday season. Thank you for your continued support and readership over the last year, here's looking forward to a fun and productive 2009! :-)

雪山飞狐_lzh 发表于 2009-5-14 13:33:00

三、拖动一个属性块
March 18, 2009
Jigging an AutoCAD block with attributes using .NET
Thanks, once again, to Philippe Leefsma, a DevTech engineer based in Prague, for contributing the code for this post. While researching an issue he was working on Philippe stumbled across a comment on this previous post where I more-or-less said jigging attributes wasn’t possible. Ahem. Anyway, Philippe decided to – quite rightly – prove me wrong, and the result is today’s post. :-)
It turns out that the trick to jigging a block with attributes is to add the block reference to the database prior to running the jig. I’d been coming at this from another direction – working out how to call through to the right version of the ObjectARX function, the one that allows the block reference to be in-memory rather than db-resident – but Philippe’s approach means that’s no longer needed. I see this technique as potentially being useful when jigging other entities that benefit from being database resident (Solid3d objects spring to mind), so I really appreciate Philippe’s hard work on this.
Here’s the C# code which I’ve edited for posting:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;

namespace BlockJig
{
class CBlockJig : EntityJig
{
    private Point3d _pos;
    private Dictionary<string, Point3d> _attPos;
    private Transaction _tr;

    public CBlockJig(Transaction tr, BlockReference br)
      : base(br)
    {
      _pos = br.Position;

      // Initialize our dictionary with the tag /
      // AttributeDefinition position

      _attPos = new Dictionary<string, Point3d>();

      _tr = tr;

      BlockTableRecord btr =
      (BlockTableRecord)_tr.GetObject(
          br.BlockTableRecord,
          OpenMode.ForRead
      );

      if (btr.HasAttributeDefinitions)
      {
      foreach (ObjectId id in btr)
      {
          DBObject obj =
            tr.GetObject(id, OpenMode.ForRead);
          AttributeDefinition ad =
            obj as AttributeDefinition;

          if (ad != null)
          {
            _attPos.Add(ad.Tag, ad.Position);
          }
      }
      }
    }

    protected override bool Update()
    {
      BlockReference br = Entity as BlockReference;

      br.Position = _pos;

      if (br.AttributeCollection.Count != 0)
      {
      foreach (ObjectId id in br.AttributeCollection)
      {
          DBObject obj =
            _tr.GetObject(id, OpenMode.ForRead);
          AttributeReference ar =
            obj as AttributeReference;

          // Apply block transform to att def position

          if (ar != null)
          {
            ar.UpgradeOpen();
            ar.Position =
            _attPos.TransformBy(br.BlockTransform);
          }
      }
      }
      return true;
    }

    protected override SamplerStatus Sampler(JigPrompts prompts)
    {
      JigPromptPointOptions opts =
      new JigPromptPointOptions("\nSelect insertion point:");
      opts.BasePoint = new Point3d(0, 0, 0);
      opts.UserInputControls =
      UserInputControls.NoZeroResponseAccepted;

      PromptPointResult ppr = prompts.AcquirePoint(opts);

      if (_pos == ppr.Value)
      {
      return SamplerStatus.NoChange;
      }

      _pos = ppr.Value;

      return SamplerStatus.OK;
    }

    public PromptStatus Run()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      PromptResult promptResult = ed.Drag(this);
      return promptResult.Status;
    }
}

public class Commands
{
   
    static public void BlockJig()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      PromptStringOptions pso =
      new PromptStringOptions("\nEnter block name: ");
      PromptResult pr = ed.GetString(pso);

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

      Transaction tr =
      doc.TransactionManager.StartTransaction();
      using (tr)
      {
      BlockTable bt =
          (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
          );
      BlockTableRecord space =
          (BlockTableRecord)tr.GetObject(
            db.CurrentSpaceId,
            OpenMode.ForRead
          );

      if (!bt.Has(pr.StringResult))
      {
          ed.WriteMessage(
            "\nBlock \"" + pr.StringResult + "\" not found.");
          return;
      }

      space.UpgradeOpen();

      BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            bt,
            OpenMode.ForRead);

      // Block needs to be inserted to current space before
      // being able to append attribute to it

      BlockReference br =
          new BlockReference(new Point3d(), btr.ObjectId);
      space.AppendEntity(br);
      tr.AddNewlyCreatedDBObject(br, true);

      if (btr.HasAttributeDefinitions)
      {
          foreach (ObjectId id in btr)
          {
            DBObject obj =
            tr.GetObject(id, OpenMode.ForRead);
            AttributeDefinition ad =
            obj as AttributeDefinition;

            if (ad != null && !ad.Constant)
            {
            AttributeReference ar =
                new AttributeReference();
            ar.SetAttributeFromBlock(ad, br.BlockTransform);
            ar.Position =
                ad.Position.TransformBy(br.BlockTransform);

            ar.TextString = ad.TextString;

            br.AttributeCollection.AppendAttribute(ar);
            tr.AddNewlyCreatedDBObject(ar, true);
            }
          }
      }

      // Run the jig

      CBlockJig myJig = new CBlockJig(tr, br);

      if (myJig.Run() != PromptStatus.OK)
          return;

      // Commit changes if user accepted, otherwise discard

      tr.Commit();
      }
    }
}
}When you run the BJ command (short for BlockJig) and specify the name of a block in the current drawing which contains attributes, you’ll now see the attributes with their default values shown as part of the block being jigged. Implementing the code to allow editing of those attributes after insertion is left as an exercise for the reader.
Update:
This code didn't work for a few situations, such as when using justification (attributes would end up at the origin after being dragged) or with MText attributes (which would start at the origin until the mouse was moved).
A big thanks to Roland Feletic from PAUSER ZT-GMBH for helping identify and diagnose the various cases.
Here's the updated C# code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;

namespace BlockJigApplication
{
class AttInfo
{
    private Point3d _pos;
    private Point3d _aln;
    private bool _aligned;

    public AttInfo(Point3d pos, Point3d aln, bool aligned)
    {
      _pos = pos;
      _aln = aln;
      _aligned = aligned;
    }

    public Point3d Position
    {
      set { _pos = value; }
      get { return _pos; }
    }

    public Point3d Alignment
    {
      set { _aln = value; }
      get { return _aln; }
    }

    public bool IsAligned
    {
      set { _aligned = value; }
      get { return _aligned; }
    }
}

class BlockJig : EntityJig
{
    private Point3d _pos;
    private Dictionary<ObjectId, AttInfo> _attInfo;
    private Transaction _tr;

    public BlockJig(
      Transaction tr,
      BlockReference br,
      Dictionary<ObjectId, AttInfo> attInfo
    ) : base(br)
    {
      _pos = br.Position;
      _attInfo = attInfo;
      _tr = tr;
    }

    protected override bool Update()
    {
      BlockReference br = Entity as BlockReference;

      br.Position = _pos;

      if (br.AttributeCollection.Count != 0)
      {
      foreach (ObjectId id in br.AttributeCollection)
      {
          DBObject obj =
            _tr.GetObject(id, OpenMode.ForRead);
          AttributeReference ar =
            obj as AttributeReference;

          // Apply block transform to att def position

          if (ar != null)
          {
            ar.UpgradeOpen();
            AttInfo ai = _attInfo;
            ar.Position =
            ai.Position.TransformBy(br.BlockTransform);
            if (ai.IsAligned)
            {
            ar.AlignmentPoint =
                ai.Alignment.TransformBy(br.BlockTransform);
            }
            if (ar.IsMTextAttribute)
            {
            ar.UpdateMTextAttribute();
            }
          }
      }
      }
      return true;
    }

    protected override SamplerStatus Sampler(JigPrompts prompts)
    {
      JigPromptPointOptions opts =
      new JigPromptPointOptions("\nSelect insertion point:");
      opts.BasePoint = new Point3d(0, 0, 0);
      opts.UserInputControls =
      UserInputControls.NoZeroResponseAccepted;

      PromptPointResult ppr = prompts.AcquirePoint(opts);

      if (_pos == ppr.Value)
      {
      return SamplerStatus.NoChange;
      }

      _pos = ppr.Value;

      return SamplerStatus.OK;
    }

    public PromptStatus Run()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      PromptResult promptResult = ed.Drag(this);
      return promptResult.Status;
    }
}

public class Commands
{
   
    static public void BlockJigCmd()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      PromptStringOptions pso =
      new PromptStringOptions("\nEnter block name: ");
      PromptResult pr = ed.GetString(pso);

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

      Transaction tr =
      doc.TransactionManager.StartTransaction();
      using (tr)
      {
      BlockTable bt =
          (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
          );

      if (!bt.Has(pr.StringResult))
      {
          ed.WriteMessage(
            "\nBlock \"" + pr.StringResult + "\" not found.");
          return;
      }

      BlockTableRecord space =
          (BlockTableRecord)tr.GetObject(
            db.CurrentSpaceId,
            OpenMode.ForWrite
          );

      BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            bt,
            OpenMode.ForRead);

      // Block needs to be inserted to current space before
      // being able to append attribute to it

      BlockReference br =
          new BlockReference(new Point3d(), btr.ObjectId);
      space.AppendEntity(br);
      tr.AddNewlyCreatedDBObject(br, true);

      Dictionary<ObjectId, AttInfo> attInfo =
          new Dictionary<ObjectId,AttInfo>();

      if (btr.HasAttributeDefinitions)
      {
          foreach (ObjectId id in btr)
          {
            DBObject obj =
            tr.GetObject(id, OpenMode.ForRead);
            AttributeDefinition ad =
            obj as AttributeDefinition;

            if (ad != null && !ad.Constant)
            {
            AttributeReference ar =
                new AttributeReference();

            ar.SetAttributeFromBlock(ad, br.BlockTransform);
            ar.Position =
                ad.Position.TransformBy(br.BlockTransform);

            if (ad.Justify != AttachmentPoint.BaseLeft)
            {
                ar.AlignmentPoint =
                  ad.AlignmentPoint.TransformBy(br.BlockTransform);
            }
            if (ar.IsMTextAttribute)
            {
                ar.UpdateMTextAttribute();
            }

            ar.TextString = ad.TextString;

            ObjectId arId =
                br.AttributeCollection.AppendAttribute(ar);
            tr.AddNewlyCreatedDBObject(ar, true);

            // Initialize our dictionary with the ObjectId of
            // the attribute reference + attribute definition info

            attInfo.Add(
                arId,
                new AttInfo(
                  ad.Position,
                  ad.AlignmentPoint,
                  ad.Justify != AttachmentPoint.BaseLeft
                )
            );
            }
          }
      }
      // Run the jig

      BlockJig myJig = new BlockJig(tr, br, attInfo);

      if (myJig.Run() != PromptStatus.OK)
          return;

      // Commit changes if user accepted, otherwise discard

      tr.Commit();
      }
    }
}
}A few comments on this code:
It's been refactored to make a single pass through the block definition to both create the block reference and collect the attribute information to store in our dictionary.
The attribute information is now held in a class, which allows us to store more than just the position in our dictionary (without using multiple dictionaries), I went to the effort of exposing public properties for the various private members, as this is generally a good technique to use (if a little redundant, here).
The dictionary now stores this attribute information against an ObjectId rather than the tag string. Roland made the excellent point that blocks can contain attributes with duplicate tags, so this is much safe. We also had to use the ObjectId of the AttributeReference, as later on inside the jig's Update() function we no longer have access to the AttributeDefinition.

雪山飞狐_lzh 发表于 2009-5-14 13:41:00

四、拖动三维实体
March 23, 2009
Jigging an AutoCAD solid using IronPython and .NET
After getting my feet wet in the last post with my first IronPython application running inside AutoCAD, I decided it was time to attack a slightly more challenging problem: jigging a database-resident Solid3d object. The idea had come after I’d received a question by email from David Wolfe, who wanted to have a fully rendered 3D view of a cylinder he was jigging.
I’d done something similar for a prototype application I worked on late last year (which was demoed at AU). The jig itself only collected the selection data I needed – the display of the Solid3d objects was handled either via the Transient Graphics subsystem or by modifying database-resident Solid3d objects (which were interconnected by a separate system of relationships and constraints). But anyway, the point is that only when the Solid3d objects were database-resident could I get rendered graphics to be generated for them via either the conceptual or realistic visual styles.
Which is why it occurred to me that the technique shown in this recent post for jigging db-resident blocks with attributes might also apply here, too.
And, just for fun, why not do the whole thing in Python? (Actually, which hindsight I can now think of a lot of reasons… :-)
Part of my rationale behind attempting this was that we were going to have to derive our jig class from EntityJig and make sure the appropriate methods were overridden for AutoCAD to then call our jig at the right moments. This is something I had doubts about being able to do, given my previous experience. IronPython is surprisingly good at allowing these methods to be implemented and called dynamically – something that I expect will grow on me – but the downside was that with the PYLOAD integration we used in the last post it is very hard to tell why things aren’t working. It took me a number of hours to work out that an __init__ function was needed – one which took an Entity and passed it to the constructor of the base class, EntityJig – and that without it the application would simply crash. A tighter integration of Python inside AutoCAD - and, I expect, overall better tooling for IronPython when working with .NET classes - would probably help avoid this kind of thrashing, but with what we have today it was pretty painful.
Before we look at the Python code, here’s an update version of the C# PythonLoader functionality: the only real difference being the try-catch block around the code which hosts our IronPython scripting engine and calls ExecuteFile():
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using System;

namespace PythonLoader
{
public class CommandsAndFunctions
{
   
    public static void PythonLoadCmdLine()
    {
      PythonLoad(true);
    }

   
    public static void PythonLoadUI()
    {
      PythonLoad(false);
    }

    public static void PythonLoad(bool useCmdLine)
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      short fd =
      (short)Application.GetSystemVariable("FILEDIA");

      // As the user to select a .py file

      PromptOpenFileOptions pfo =
          new PromptOpenFileOptions(
            "Select Python script to load"
          );
      pfo.Filter = "Python script (*.py)|*.py";
      pfo.PreferCommandLine =
      (useCmdLine || fd == 0);
      PromptFileNameResult pr =
      ed.GetFileNameForOpen(pfo);

      // And then try to load and execute it

      if (pr.Status == PromptStatus.OK)
      ExecutePythonScript(pr.StringResult);
    }

   
    public ResultBuffer PythonLoadLISP(ResultBuffer rb)
    {
      const int RTSTR = 5005;

      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      if (rb == null)
      {
      ed.WriteMessage("\nError: too few arguments\n");
      }
      else
      {
      // We're only really interested in the first argument

      Array args = rb.AsArray();
      TypedValue tv = (TypedValue)args.GetValue(0);

      // Which should be the filename of our script

      if (tv != null && tv.TypeCode == RTSTR)
      {
          // If we manage to execute it, let's return the
          // filename as the result of the function
          // (just as (arxload) does)

          bool success =
            ExecutePythonScript(Convert.ToString(tv.Value));
          return
            (success ?
            new ResultBuffer(
                new TypedValue(RTSTR, tv.Value)
            )
            : null);
      }
      }
      return null;
    }

    private static bool ExecutePythonScript(string file)
    {
      // If the file exists, let's load and execute it
      // (we could/should probably add some more robust
      // exception handling here)

      bool ret = System.IO.File.Exists(file);
      if (ret)
      {
      try
      {
          ScriptEngine engine = Python.CreateEngine();
          engine.ExecuteFile(file);
      }
      catch (System.Exception ex)
      {
          Document doc =
            Application.DocumentManager.MdiActiveDocument;
          Editor ed = doc.Editor;

          ed.WriteMessage(
            "\nProblem executing script: {0}", ex.Message
          );
      }
      }
      return ret;
    }
}
}And here’s the IronPython code for jigging a box inside AutoCAD:
import clr
path = 'C:\\Program Files\\Autodesk\\AutoCAD 2009\\'
clr.AddReferenceToFileAndPath(path + 'acdbmgd.dll')
clr.AddReferenceToFileAndPath(path + 'acmgd.dll')
clr.AddReferenceToFileAndPath(path + 'acmgdinternal.dll')

import Autodesk
import Autodesk.AutoCAD.Runtime as ar
import Autodesk.AutoCAD.ApplicationServices as aas
import Autodesk.AutoCAD.DatabaseServices as ads
import Autodesk.AutoCAD.EditorInput as aei
import Autodesk.AutoCAD.Geometry as ag
import Autodesk.AutoCAD.Internal as ai
from Autodesk.AutoCAD.Internal import Utils

# Function to register AutoCAD commands
# To be used via a function decorator

def autocad_command(function):

# First query the function name
n = function.__name__

# Create the callback and add the command
cc = ai.CommandCallback(function)
Utils.AddCommand('pycmds', n, n, ar.CommandFlags.Modal, cc)

# Let's now write a message to the command-line
doc = aas.Application.DocumentManager.MdiActiveDocument
ed = doc.Editor
ed.WriteMessage("\nRegistered Python command: {0}", n)

# A jig to create a Solid3d - in this case a box

class SolidJig(aei.EntityJig):

# Initialization function

def __init__(self, ent):

    # Store the object and call the base class

    self._sol = ent
    aei.EntityJig.__init__(self, ent)

# The function called to run the jig

def StartJig(self, ed, pt):

    # The start point is specific outside the jig

    self._start = pt
    self._end = pt
    return ed.Drag(self)

# The sampler function

def Sampler(self, prompts):

    # Set up our selection options

    jo = aei.JigPromptPointOptions()
    jo.UserInputControls = (
      aei.UserInputControls.Accept3dCoordinates |
      aei.UserInputControls.NoZeroResponseAccepted |
      aei.UserInputControls.NoNegativeResponseAccepted)
    jo.Message = "\nSelect end point: "      

    # Get the end point of our box

    res = prompts.AcquirePoint(jo)
    if self._end == res.Value:
      return aei.SamplerStatus.NoChange
    else:
      self._end = res.Value
    return aei.SamplerStatus.OK

# The update function

def Update(self):

    # Recreate our Solid3d box

    try:
      # Get the width (x) and depth (y)

      x = self._end.X - self._start.X
      y = self._end.Y - self._start.Y

      # We need a non-zero Z value, so we copy Y

      z = y

      # Create our box and move it to the right place

      self._sol.CreateBox(x,y,z)
      self._sol.TransformBy(
      ag.Matrix3d.Displacement(
          ag.Vector3d(
            self._start.X + x/2,
            self._start.Y + y/2,
            self._start.Z + z/2)))
    except:
      return False
    return True

# Create a box using a jig

@autocad_command
def boxjig():

doc = aas.Application.DocumentManager.MdiActiveDocument
db = doc.Database
ed = doc.Editor

# Select the start point before entering the jig

ppr = ed.GetPoint("\nSelect start point: ")

if ppr.Status == aei.PromptStatus.OK:

    # We'll add our solid to the modelspace

    tr = doc.TransactionManager.StartTransaction()
    bt = tr.GetObject(db.BlockTableId, ads.OpenMode.ForRead)
    btr = tr.GetObject(
      bt, ads.OpenMode.ForWrite)

    # Make sure we're recording history to allow grip editing

    sol = ads.Solid3d()
    sol.RecordHistory = True

    # Now we add our solid

    btr.AppendEntity(sol)
    tr.AddNewlyCreatedDBObject(sol, True)

    # And call the jig before finishing

    sj = SolidJig(sol)
    ppr2 = sj.StartJig(ed, ppr.Value)

    # Only commit if all completed well

    if ppr2.Status == aei.PromptStatus.OK:
      tr.Commit()

    tr.Dispose()When we execute the PYLOAD command, load our .py file and execute the BOXJIG command, we’ll be able to jig a Solid3d in a non-wireframe 3D view:

Something to note: I haven’t spent time optimizing this code, so there’s a good chance it’s sub-optimal (and may well leak memory). The point was not to demonstrate a definitive approach to solving this particular problem but rather to see whether it was possible to attack a problem of non-trivial complexity with the current toolset and my general lack of knowledge of the Python language.
Well, it’s obviously possible – as I somehow managed to do it – but, to come clean, I did cheat somewhat: I had a C# project open at the same time which I regularly referred to for IntelliSense look-ups. I didn’t do my full development in C# and then convert it to Python, though, so I see this more as a stop-gap to help address some of the current limitations in the tools. That’s the story I’m going with, anyway. :-)

carrot1983 发表于 2009-5-22 20:40:00

<p>谢谢<strong><font face="Verdana" color="#da2549">lzh741206</font></strong></p><p><strong><font face="Verdana" color="#da2549">以后,就决定在明经学习.net了。。。</font></strong></p><p></p>

sailorcwx 发表于 2009-5-23 11:55:00

使用Jig动态旋转实体VB.NET版

#Region "命名空间"
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.Geometry
#End Region

Public Class commands
    Const kRegAppName As String = "TTIF_ROT"
    Const kAppCode As Integer = 1001
    Const kRotCode As Integer = 1040
    Public Class RotateJig
      Inherits EntityJig
      Dim m_baseAngle, m_deltaAngle As Double
      Dim m_rotationPoint As Point3d
      Dim m_ucs As Matrix3d
      Public Sub New(ByVal ent As Entity, ByVal rotationPoint As Point3d, ByVal baseAngle As Double, ByVal ucs As Matrix3d)
            MyBase.new(ent.Clone())
            m_rotationPoint = rotationPoint
            m_baseAngle = baseAngle
            m_ucs = ucs
      End Sub
      Protected Overrides Function Sampler(ByVal jp As Autodesk.AutoCAD.EditorInput.JigPrompts) As Autodesk.AutoCAD.EditorInput.SamplerStatus
            Dim jo As New JigPromptAngleOptions(vbNewLine + "旋转角度: ")
            jo.BasePoint = m_rotationPoint
            jo.UseBasePoint = True
            Dim pdr As PromptDoubleResult = jp.AcquireAngle(jo)
            If pdr.Status = PromptStatus.OK Then
                If m_deltaAngle = pdr.Value Then
                  Return SamplerStatus.NoChange
                Else
                  m_deltaAngle = pdr.Value
                  Return SamplerStatus.OK
                End If
            Else
                Return SamplerStatus.Cancel
            End If
      End Function
      Protected Overrides Function Update() As Boolean
            Dim trans As Matrix3d = Matrix3d.Rotation(m_deltaAngle - m_baseAngle, m_ucs.CoordinateSystem3d.Zaxis, m_rotationPoint)
            Entity.TransformBy(trans)
            m_baseAngle = m_deltaAngle
            m_deltaAngle = 0.0
            Return True
      End Function
      Public Function GetEntity() As Entity
            Return Entity
      End Function
      Public Function Getrotation() As Double
            Return m_baseAngle + m_deltaAngle
      End Function
    End Class
    <CommandMethod("ROT")> _
    Public Sub RotateEntity()
      Dim doc As Document = Application.DocumentManager.MdiActiveDocument
      Dim ed As Editor = doc.Editor
      Dim db As Database = doc.Database
      Dim peo As New PromptEntityOptions(vbNewLine + "选择要旋转的对象: ")
      Dim per As PromptEntityResult = ed.GetEntity(peo)
      If per.Status = PromptStatus.OK Then
            Using tr As Transaction = db.TransactionManager.StartTransaction()
                Dim obj As DBObject = tr.GetObject(per.ObjectId, OpenMode.ForRead)
                Dim ent As Entity = obj
                Dim rotationPoint As Point3d = Point3d.Origin
                Dim pl As Polyline = obj
                If pl <> Nothing Then
                  Dim ps0 As LineSegment3d = pl.GetLineSegmentAt(0)
                  Dim ps1 As LineSegment3d = pl.GetLineSegmentAt(1)
                  Dim vec As Vector3d = (ps0.EndPoint - ps0.StartPoint) / 2 + (ps1.EndPoint - ps1.StartPoint) / 2
                  rotationPoint = pl.StartPoint + vec
                  Dim baseangle As Double = GetStoredRotation(obj)
                  If ent <> Nothing Then
                        Dim ucs As Matrix3d = ed.CurrentUserCoordinateSystem
                        Dim jig As RotateJig = New RotateJig(ent, rotationPoint, baseangle, ucs)
                        Dim res As PromptResult = ed.Drag(jig)
                        If res.Status = PromptStatus.OK Then
                            Dim newAngle As Double = jig.Getrotation()
                            jig.GetEntity.Dispose()
                            Dim trans As Matrix3d = Matrix3d.Rotation(newAngle - baseangle, ucs.CoordinateSystem3d.Zaxis, rotationPoint)
                            ent.UpgradeOpen()
                            ent.TransformBy(trans)
                            SetStoredRotation(ent, newAngle)
                        End If
                  End If
                End If
                tr.Commit()
            End Using
      End If
    End Sub
    Public Sub AddRegAppTableRecord(ByRef regAppName As String)
      Dim doc As Document = Application.DocumentManager.MdiActiveDocument
      Dim ed As Editor = doc.Editor
      Dim db As Database = doc.Database
      Using tr As Transaction = doc.TransactionManager.StartTransaction
            Dim rat As RegAppTable = tr.GetObject(db.RegAppTableId, OpenMode.ForRead, False)
            If Not rat.Has(regAppName) Then
                rat.UpgradeOpen()
                Dim ratr As New RegAppTableRecord
                ratr.Name = regAppName
                rat.Add(ratr)
                tr.AddNewlyCreatedDBObject(ratr, True)
            End If
            tr.Commit()
      End Using
    End Sub
    Private Sub SetStoredRotation(ByRef obj As DBObject, ByRef rotation As Double)
      AddRegAppTableRecord(kRegAppName)
      Dim rb As ResultBuffer = obj.XData
      If rb = Nothing Then
            rb = New ResultBuffer(New TypedValue(kAppCode, kRegAppName), New TypedValue(kRotCode, rotation))
      Else
            rb.Add(New TypedValue(kAppCode, kRegAppName))
            rb.Add(New TypedValue(kRotCode, rotation))
      End If
      obj.XData = rb
      rb.Dispose()
    End Sub
    Private Function GetStoredRotation(ByRef obj As DBObject) As Double
      Dim ret As Double = 0.0
      Dim rb As ResultBuffer = obj.XData
      If rb <> Nothing Then
            Dim bReadyForRot As Boolean = False
            For Each tv As TypedValue In rb
                If (bReadyForRot) Then
                  If tv.TypeCode = kRotCode Then
                        ret = tv.Value
                        bReadyForRot = False
                  End If
                End If
                If tv.TypeCode = kAppCode Then
                  bReadyForRot = True
                End If
            Next
            rb.Dispose()
            Return ret
      End If
    End Function
End Class

雪山飞狐_lzh 发表于 2009-5-25 15:53:00

五、交互式创建多义线
November 08, 2006
Controlling interactive polyline creation - Part 1
I received this interesting question through by email over the weekend:
“How can I ask AutoCAD to let the user to draw a Polyline (just like the user pushed Polyline button) and then just after finishing drawing get the ObjectID of that entity? Is it possible?”
This is a fun one, as there are a number of different approaches to take. I’m going to outline (or just go ahead and implement, depending on the complexity) the various possibilities – taking the first two today and the others in (a) subsequent post(s).
The idea is to define our own command, say MYPOLY, and make sure we’re left with execution control – and the object ID/entity name – after the user has defined a polyline in the drawing window.
There are two basic ways to solve this problem, and each of these has two variants. The initial (and major) choice is whether to let the standard AutoCAD PLINE command provide the user-interface for creating the polyline. Doing so is certainly simpler, assuming you want the user to have access to all the polyline options. That said, you may actually prefer to limit the user’s options (for example, not to allow width or arc segments), in which case the approach to implement the UI yourself would be better suited.
So, now to tackle the first two options...
From the MYPOLY command, we want to call the PLINE command. Once the command has completed, we want to make sure our code is being executed, which will allow us to get the polyline's object ID.
This is where we get our next choice: how to find out when the PLINE command has ended. The first option (and the one typically used from Visual LISP for this type of task) is to loop until the command is finished, checking either CMDACTIVE or CMDNAMES. This is important, as polylines can have an arbitrary number of vertices, so we don’t know exactly how long the command will take to complete (in terms of how many “pauses” the command will have, requesting a point selection from the user).
Here’s how I’d do this in LISP (the technique is published on the ADN site in this DevNote: Waiting for (command) to finish in AutoLISP):
(defun C:MYPOLY()
(command "_.PLINE")
(while (= (getvar "CMDNAMES") "PLINE")
    (command pause)
)
(princ "\nEntity name of polyline: ")
(princ (entlast))
(princ)
)
And here's what happens when we execute this code:
Command: mypoly
_.PLINE
Specify start point:
Current line-width is 0.0000
Specify next point or :
Specify next point or :
Specify next point or : a
Specify endpoint of arc or
:
Specify endpoint of arc or
:
Command:
Entity name of polyline: <Entity name: 7ef90048>The second option to wait for the command to complete is to register a callback handling the CommandEnded() event.
Here’s some C# code showing this approach:
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
namespace MyPlineApp
{
public class MyPlineCmds : IExtensionApplication
{
    // Flag used to check whether it's our command
    // that launched PLINE
    private static bool myCommandStarted = false;
    public void Initialize()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.CommandEnded += new
      CommandEventHandler(
          plineCommandEnded
      );
    }
    public void Terminate()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.CommandEnded -= new
      CommandEventHandler(
          plineCommandEnded
      );
    }
   
    public void MyPoly()
    {
      // Set the flag and launch PLINE
      myCommandStarted = true;
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      doc.SendStringToExecute("_PLINE ",false,false,false);
    }
    private void plineCommandEnded(
      object sender,
      CommandEventArgs e)
    {
      if (myCommandStarted
      && e.GlobalCommandName.ToUpper() == "PLINE")
      {
      // We're just performing a simple check, so OK..
      // We could launch a follow-on command, if needed
      Document doc =
          Application.DocumentManager.MdiActiveDocument;
      PromptSelectionResult lastRes =
          doc.Editor.SelectLast();
      if (lastRes.Value != null
          && lastRes.Value.Count == 1)
      {
          doc.Editor.WriteMessage(
            "\nPolyline entity is: "
            + lastRes.Value.ObjectId
          );
      }
      myCommandStarted = false;
      }
    }
}
}
And here's what happens when we execute this code:
Command: mypoly
Command:
Specify start point:
Current line-width is 0.0000
Specify next point or :
Specify next point or :
Specify next point or : a
Specify endpoint of arc or
:
Polyline entity is: (2130247840)
That's where I'll stop it there for now… the other two options I want to look at both revolve around defining your own user-interface. The first will simply collect a sequence of points from the user using GetPoint(), the second uses a Jig to do the same thing (I haven’t yet decided whether to actually implement this last one or not – we’ll see how much time I have later in the week).

雪山飞狐_lzh 发表于 2009-5-25 15:55:00

November 10, 2006
Controlling interactive polyline creation - Part 2
During the first part of this series, we looked at ways to drive the PLINE command while retaining (or regaining) the thread of execution in your application.
During this and the next post (yes, I've decided to spread the series a little thinner :-) we're going to look at how to completely replace the user-interface to the polyline command, a very useful technique in certain situations. This post focuses on the simple use of GetPoint() to request vertex information from the user; the next post will look at a more advanced technique, the Jig.
Even the "simple" user-interface implemented in the below code takes some effort. To keep things as simple as possible, the below UI code only allows the user to define zero-width, linear polyline segments - no arcs, widths, etc. As mentioned in the previous post, this might well be an advantage in your application, depending on whether you want to hide certain options from the user. This approach is certainly not ideal if you do want to allow interactive selection of arc segments; the two approaches suggested last time, or the one shown in the next entry, would work better in that case.
A few notes on the implementation:
Temporary graphics are used to draw each polyline segment as soon as both its vertices have been defined
The actual polyline entity is only created once all the vertices have been selected
Point selection happens in the User Coordinate System, so we need to do some work to transform selected points to the Entity Coordinate System (or Object Coordinate System) belonging to the polyline. 2-dimensional polylines are planar entities and have their vertices defined as 2D points relative to the origin and normal of the polyline, so we use a "Plane" object to help us get the 2D points to feed to the polyline's AddVertexAt() function
Here's the code in C#:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Colors;
namespace MyPlineApp
{
public class MyPlineCmds
{
   
    public void MyPoly()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // Get the current color, for our temp graphics
      Color col = doc.Database.Cecolor;
      // Create a point collection to store our vertices
      Point3dCollection pts = new Point3dCollection();
      // Set up the selection options
      // (used for all vertices)
      PromptPointOptions opt =
      new PromptPointOptions(
          "\nSelect polyline vertex: "
      );
      opt.AllowNone = true;
      // Get the start point for the polyline
      PromptPointResult res = ed.GetPoint(opt);
      while (res.Status == PromptStatus.OK)
      {
      // Add the selected point to the list
      pts.Add(res.Value);
      // Drag a temp line during selection
      // of subsequent points
      opt.UseBasePoint = true;
      opt.BasePoint = res.Value;
      res = ed.GetPoint(opt);
      if (res.Status == PromptStatus.OK)
      {
          // For each point selected,
          // draw a temporary segment
          ed.DrawVector(
            pts, // start point
            res.Value,          // end point
            col.ColorIndex,   // current color
            false);             // highlighted?
      }
      }
      if (res.Status == PromptStatus.None)
      {
      // Get the current UCS
      Matrix3d ucs =
          ed.CurrentUserCoordinateSystem;
      Point3d origin = new Point3d(0, 0, 0);
      Vector3d normal = new Vector3d(0, 0, 1);
      normal = normal.TransformBy(ucs);
      // Create a temporary plane, to help with calcs
      Plane plane = new Plane(origin, normal);
      // Create the polyline, specifying
      // the number of vertices up front
      Polyline pline = new Polyline(pts.Count);
      pline.Normal = normal;
      foreach (Point3d pt in pts)
      {
          Point3d transformedPt =
            pt.TransformBy(ucs);
          pline.AddVertexAt(
            pline.NumberOfVertices,
            plane.ParameterOf(transformedPt),
            0, 0, 0
          );
      }
      // Now let's add the polyline to the modelspace
      Transaction tr =
          db.TransactionManager.StartTransaction();
      using (tr)
      {
          BlockTable bt =
            (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
            );
          BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
            bt,
            OpenMode.ForWrite
            );
          ObjectId plineId = btr.AppendEntity(pline);
          tr.AddNewlyCreatedDBObject(pline, true);
          tr.Commit();
          ed.WriteMessage("\nPolyline entity is: " +
            plineId.ToString()
          );
      }
      }
      // Clear the temp graphics (polyline should be in
      // the same location, if selection was not cancelled)
      // We could "redraw" instead of "regen" here
      ed.Regen();
    }
}
}
Here's what happens when we execute this code:
Command: mypoly
Select polyline vertex:
Select polyline vertex:
Select polyline vertex:
Regenerating model.
Polyline entity is: (2130239560)
Next time we'll look at how we can use a Jig for this task.

雪山飞狐_lzh 发表于 2009-5-25 15:57:00

November 14, 2006
Controlling interactive polyline creation - Part 3
During the first two parts of this series we looked at different techniques for creating polylines programmatically in AutoCAD (while allowing the user to select the various vertices).
In this post look at the most advanced technique yet, the use of a "Jig". A Jig is a special construct in AutoCAD that hosts an object - in our case a polyline - and feeds user input information into this object, which then determines what graphics gets displayed. This is especially useful when dealing with complex objects, such as polylines with arc segments.
We're going to start with a fairly basic Jig, one that simply collects vertices and creates straight-line segments, but I'd quite like to take this further to support a number of different advanced capabilities: arc segments (which will require keyword implementation), dynamic dimensions, and possibly even undo (a request that just came in as a comment to the previous post).
Anyway, here's the C# code of the basic Jig implementation:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace MyPlineApp
{
public class MyPlineCmds
{
    class PlineJig : EntityJig
    {
      // Maintain a list of vertices...
      // Not strictly necessary, as these will be stored in the
      // polyline, but will not adversely impact performance
      Point3dCollection m_pts;
      // Use a separate variable for the most recent point...
      // Again, not strictly necessary, but easier to reference
      Point3d m_tempPoint;
      Plane m_plane;
      public PlineJig(Matrix3d ucs)
      : base(new Polyline())
      {
      // Create a point collection to store our vertices
      m_pts = new Point3dCollection();
      // Create a temporary plane, to help with calcs
      Point3d origin = new Point3d(0, 0, 0);
      Vector3d normal = new Vector3d(0, 0, 1);
      normal = normal.TransformBy(ucs);
      m_plane = new Plane(origin, normal);
      // Create polyline, set defaults, add dummy vertex
      Polyline pline = Entity as Polyline;
      pline.SetDatabaseDefaults();
      pline.Normal = normal;
      pline.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0);
      }
      protected override SamplerStatus Sampler(JigPrompts prompts)
      {
      JigPromptPointOptions jigOpts =
          new JigPromptPointOptions();
      jigOpts.UserInputControls =
          (UserInputControls.Accept3dCoordinates |
          UserInputControls.NullResponseAccepted |
          UserInputControls.NoNegativeResponseAccepted
          );
      if (m_pts.Count == 0)
      {
          // For the first vertex, just ask for the point
          jigOpts.Message =
            "\nStart point of polyline: ";
      }
      else if (m_pts.Count > 0)
      {
          // For subsequent vertices, use a base point
          jigOpts.BasePoint = m_pts;
          jigOpts.UseBasePoint = true;
          jigOpts.Message = "\nPolyline vertex: ";
      }
      else // should never happen
          return SamplerStatus.Cancel;
      // Get the point itself
      PromptPointResult res =
          prompts.AcquirePoint(jigOpts);
      // Check if it has changed or not
      // (reduces flicker)
      if (m_tempPoint == res.Value)
      {
          return SamplerStatus.NoChange;
      }
      else if (res.Status == PromptStatus.OK)
      {
          m_tempPoint = res.Value;
          return SamplerStatus.OK;
      }
      return SamplerStatus.Cancel;
      }
      protected override bool Update()
      {
      // Update the dummy vertex to be our
      // 3D point projected onto our plane
      Polyline pline = Entity as Polyline;
      pline.SetPointAt(
          pline.NumberOfVertices - 1,
          m_tempPoint.Convert2d(m_plane)
      );
      return true;
      }
      public Entity GetEntity()
      {
      return Entity;
      }
      public void AddLatestVertex()
      {
      // Add the latest selected point to
      // our internal list...
      // This point will already be in the
      // most recently added pline vertex
      m_pts.Add(m_tempPoint);
      Polyline pline = Entity as Polyline;
      // Create a new dummy vertex...
      // can have any initial value
      pline.AddVertexAt(
          pline.NumberOfVertices,
          new Point2d(0,0),
          0,0,0
      );
      }
      public void RemoveLastVertex()
      {
      // Let's remove our dummy vertex
      Polyline pline = Entity as Polyline;
      pline.RemoveVertexAt(m_pts.Count);
      }
    }
   
    public void MyPolyJig()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      // Get the current UCS, to pass to the Jig
      Matrix3d ucs =
      ed.CurrentUserCoordinateSystem;
      // Create our Jig object
      PlineJig jig = new PlineJig(ucs);
      // Loop to set the vertices directly on the polyline
      bool bSuccess = true, bComplete = false;
      do
      {
      PromptResult res = ed.Drag(jig);
      bSuccess =
          (res.Status == PromptStatus.OK);
      // A new point was added
      if (bSuccess)
          jig.AddLatestVertex();
      // Null input terminates the command
      bComplete =
          (res.Status == PromptStatus.None);
      if (bComplete)
          // Let's clean-up the polyline before adding it
          jig.RemoveLastVertex();
      } while (bSuccess && !bComplete);
      // If the jig completed successfully, add the polyline
      if (bComplete)
      {
      // Append entity
      Database db = doc.Database;
      Transaction tr =
          db.TransactionManager.StartTransaction();
      using (tr)
      {
          BlockTable bt =
            (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead,
            false
            );
          BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
            bt,
            OpenMode.ForWrite,
            false
            );
          btr.AppendEntity(jig.GetEntity());
          tr.AddNewlyCreatedDBObject(jig.GetEntity(), true);
          tr.Commit();
      }
      }
    }
}
}That's it for the basic Jig implementation. For further information on Jigs, I suggest taking a look at the "EllipseJig" sample on the ObjectARX SDK (under samples/dotNet) and the ObjectARX Developer's Guide (the C++ documentation is much more extensive for now). I'll also take a closer look in future posts, so please keep your questions coming.

雪山飞狐_lzh 发表于 2009-5-25 16:03:00

六、关键字设置
November 17, 2006
Advanced jigging with AutoCAD .NET - adding keywords
This post extends the polyline-creation jig shown in the previous entry to support the use of keywords both for arc segments and for undo.
A few notes:
I removed the use of a separate vertex list, as it proved to be less necessary than needed
This implementation supports Undo, and the toggling between Line segment and Arc segment entry
Arc segments have a fixed bulge of 1.0, which is actually quite useful if drawing a cloud, but not really useful for much else. Generally the bulge should be adjusted according to the position of the cursor relative to the previous point, which may be something I attempt in a future post
I've also streamlined some of the other parts of code (such as using the basepoint in the jig - which we don't actually need, as we allow the polyline to draw itself)
The code has become quite a bit more complex, and could probably do with some additional performance tuning, but it should allow you to get the idea of what can be done (and one way of approaching it). I should also say that - and this holds true for any of the code samples posted in this blog - it should be thoroughly tested before being integrated into your own application. Hopefully that goes without saying...
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace MyPlineApp
{
public class MyPlineCmds
{
    class PlineJig : EntityJig
    {
      // Use a separate variable for the most recent point...
      // Again, not strictly necessary, but easier to reference
      Point3d m_tempPoint;
      Plane m_plane;
      bool m_isArcSeg = false;
      bool m_isUndoing = false;
      // At this stage, weour arc segments will
      // have a fixed bulge of 1.0...
      // Later we may update the routine to determine
      // the bulge based on the relative location
      // of the cursor
      const double kBulge = 1.0;
      public PlineJig(Matrix3d ucs)
      : base(new Polyline())
      {
      // Create a temporary plane, to help with calcs
      Point3d origin = new Point3d(0, 0, 0);
      Vector3d normal = new Vector3d(0, 0, 1);
      normal = normal.TransformBy(ucs);
      m_plane = new Plane(origin, normal);
      // Create polyline, set defaults, add dummy vertex
      Polyline pline = Entity as Polyline;
      pline.SetDatabaseDefaults();
      pline.Normal = normal;
      AddDummyVertex();
      }
      protected override SamplerStatus Sampler(JigPrompts prompts)
      {
      JigPromptPointOptions jigOpts =
          new JigPromptPointOptions();
      jigOpts.UserInputControls =
          (UserInputControls.Accept3dCoordinates |
          UserInputControls.NullResponseAccepted |
          UserInputControls.NoNegativeResponseAccepted
          );
      m_isUndoing = false;
      Polyline pline = Entity as Polyline;
      if (pline.NumberOfVertices == 1)
      {
          // For the first vertex, just ask for the point
          jigOpts.Message =
            "\nStart point of polyline: ";
      }
      else if (pline.NumberOfVertices > 1)
      {
          // For subsequent vertices, use a base point
          if (m_isArcSeg)
          {
            jigOpts.SetMessageAndKeywords(
            "\nSpecify endpoint of arc or : ",
            "Line Undo"
            );
          }
          else
          {
            jigOpts.SetMessageAndKeywords(
            "\nSpecify next point or : ",
            "Arc Undo"
            );
          }
      }
      else // should never happen
          return SamplerStatus.Cancel;
      // Get the point itself
      PromptPointResult res =
          prompts.AcquirePoint(jigOpts);
      if (res.Status == PromptStatus.Keyword)
      {
          if (res.StringResult == "Arc")
          {
            m_isArcSeg = true;
          }
          else if (res.StringResult == "Line")
          {
            m_isArcSeg = false;
          }
          else if (res.StringResult == "Undo")
          {
            m_isUndoing = true;
          }
          return SamplerStatus.OK;
      }
      else if (res.Status == PromptStatus.OK)
      {
          // Check if it has changed or not
          // (reduces flicker)
          if (m_tempPoint == res.Value)
          {
            return SamplerStatus.NoChange;
          }
          else
          {
            m_tempPoint = res.Value;
            return SamplerStatus.OK;
          }
      }
      return SamplerStatus.Cancel;
      }
      protected override bool Update()
      {
      // Update the dummy vertex to be our
      // 3D point projected onto our plane
      Polyline pline = Entity as Polyline;
      pline.SetPointAt(
          pline.NumberOfVertices - 1,
          m_tempPoint.Convert2d(m_plane)
      );
      // If it's supposed to be an arc segment,
      // set the bulge appropriately
      if (m_isArcSeg)
      {
          pline.SetBulgeAt(
            pline.NumberOfVertices-1,
            kBulge
          );
      }
      // Otherwise, it's a line, so set the bulge
      // to zero
      else
      {
          pline.SetBulgeAt(
            pline.NumberOfVertices-1,
            0
          );
      }
      return true;
      }
      public Entity GetEntity()
      {
      return Entity;
      }
      public bool IsArcSegment()
      {
      return m_isArcSeg;
      }
      public bool IsUndoing()
      {
      return m_isUndoing;
      }
      public void AddDummyVertex()
      {
      // Create a new dummy vertex...
      // can have any initial value
      Polyline pline = Entity as Polyline;
      pline.AddVertexAt(
          pline.NumberOfVertices,
          new Point2d(0,0),
          0,0,0
      );
      }
      public void RemoveDummyVertex()
      {
      Polyline pline = Entity as Polyline;
      // Let's first remove our dummy vertex      
      if (pline.NumberOfVertices > 0)
      {
          pline.RemoveVertexAt(pline.NumberOfVertices - 1);
      }
      // And then check the type of the last segment
      if (pline.NumberOfVertices >= 2)
      {
          double blg =
            pline.GetBulgeAt(pline.NumberOfVertices - 2);
          m_isArcSeg = (blg != 0);
      }
      }
      public void AdjustSegmentType(bool isArc)
      {
      // Change the bulge of the previous segment,
      // if necessary
      double bulge = 0.0;
      if (isArc)
          bulge = kBulge;
      Polyline pline = Entity as Polyline;
      if (pline.NumberOfVertices >= 2)
          pline.SetBulgeAt(pline.NumberOfVertices - 2, bulge);
      }
    }
   
    public void MyPolyJig()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      // Get the current UCS, to pass to the Jig
      Matrix3d ucs =
      ed.CurrentUserCoordinateSystem;
      // Create our Jig object
      PlineJig jig = new PlineJig(ucs);
      // Loop to set the vertices directly on the polyline
      bool bPoint = false;
      bool bKeyword = false;
      bool bComplete = false;
      do
      {
      PromptResult res = ed.Drag(jig);
      bPoint =
          (res.Status == PromptStatus.OK);
      // A new point was added
      if (bPoint)
          jig.AddDummyVertex();
      bKeyword =
          (res.Status == PromptStatus.Keyword);
      if (bKeyword)
      {
          if (jig.IsUndoing())
          {
            jig.RemoveDummyVertex();
          }
          else
          {
            jig.AdjustSegmentType(jig.IsArcSegment());
          }
      }
      // Null input terminates the command
      bComplete =
          (res.Status == PromptStatus.None);
      if (bComplete)
          // Let's clean-up the polyline before adding it
          jig.RemoveDummyVertex();
      } while ((bPoint || bKeyword) && !bComplete);
      // If the jig completed successfully,
      // add the polyline
      if (bComplete)
      {
      Polyline pline = jig.GetEntity() as Polyline;
      if (pline.NumberOfVertices > 1)
      {
          // Append entity
          Database db = doc.Database;
          Transaction tr =
            db.TransactionManager.StartTransaction();
          using (tr)
          {
            BlockTable bt =
            (BlockTable)tr.GetObject(
                db.BlockTableId,
                OpenMode.ForRead,
                false
            );
            BlockTableRecord btr =
            (BlockTableRecord)tr.GetObject(
                bt,
                OpenMode.ForWrite,
                false
            );
            btr.AppendEntity(pline);
            tr.AddNewlyCreatedDBObject(pline, true);
            tr.Commit();
          }
      }
      }
    }
}
}

页: [1] 2
查看完整版本: Kean专题(1)—Jigs