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命令运行也会正常,因为图元“记住”了旋转角度。如果用了其它的工具旋转了这个矩形,这种效果就会消失。一种方法是深层的分析矩形来测量“旋转角度”:确定矩形的长边,获得它的角度。另一种毫无意义的方法是执行命令让用户输入角度(选择两个点或直接输入)。
二、在绘图界面动态显示坐标
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! :-)
三、拖动一个属性块
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.
四、拖动三维实体
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. :-)
<p>谢谢<strong><font face="Verdana" color="#da2549">lzh741206</font></strong></p><p><strong><font face="Verdana" color="#da2549">以后,就决定在明经学习.net了。。。</font></strong></p><p></p> 使用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
五、交互式创建多义线
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).
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.
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.
六、关键字设置
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