This question came in by email, last week:
I’m trying to reverse the direction of a polyline thought the API, but I didn’t find something in the documentation nor in the web. (Even nothing on your blog.) Can you help me?
I also didn’t find anything in the public API, although that doesn’t mean there isn’t something I’ve missed (I seem to be on a bit of a roll in that respect, lately :-S :-).
A couple of thoughts/comments on this problem:
- Ideally we don’t want to create a brand new object, as on the one hand there may be properties we forget to copy across (extension dictionary contents, XData, etc.) but also because we’d need to worry about identity (making sure we use HandOverTo() to maintain the object’s handle, etc., for other systems tham might depend upon it)
- This seems altogether far too much like hard work, so we’ll try to manipulate the vertex data directly
- In this implementation we’ll focus on the Polyline class, rather than creating a more general solution that handles Polyline2d and Polyline3d
- These “complex” objects have references to separate vertex objects, which actually means it may not be much more difficult to support them (perhaps it’s even easier?), but I just haven’t looked into it as it wasn’t part of the original request
- Please post a comment if this is something you’re interested in
Here’s some code that extracts vertex information from a Polyline (storing it in a list of PerVertexData struct instances – with hindsight I might have named that struct differently, although it’s harmless enough if you parse it using capital letters to separate words/parts of words :-) and then using that information in reverse to set the vertex information as we want it.
It’s worth bearing in mind that the bulge is held on the index of the segment rather than the vertex, so when gathering the bulge we need to pick up the bulge of the index before for it to work properly.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
namespace PolylineEditing
{
public class Commands
{
// Data to be stored for each polyline vertex
struct PerVertexData
{
public Point2d pt;
public double bulge;
public double startWidth;
public double endWidth;
}
[CommandMethod("RPD")]
static public void ReversePolylineDirection()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
var peo =
new PromptEntityOptions(
"\nSelect polyline to reverse"
);
peo.SetRejectMessage("Must be a polyline.");
peo.AddAllowedClass(typeof(Polyline), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
var tr = doc.TransactionManager.StartTransaction();
using (tr)
{
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var pl = obj as Polyline;
if (pl != null)
{
// Collect our per-vertex data
List<PerVertexData> vertData =
new List<PerVertexData>(pl.NumberOfVertices);
for (int i = 0; i < pl.NumberOfVertices; i++)
{
PerVertexData pvd = new PerVertexData();
pvd.bulge = (i > 0 ? pl.GetBulgeAt(i - 1) : 0);
pvd.startWidth = (i > 0 ? pl.GetStartWidthAt(i - 1) : 0);
pvd.endWidth = (i > 0 ? pl.GetEndWidthAt(i - 1) : 0);
pvd.pt = pl.GetPoint2dAt(i);
vertData.Add(pvd);
}
// Now let's make sure we can edit the polyline
pl.UpgradeOpen();
// Write the data back to the polyline, but in
// reverse order
for (int i = 0; i < pl.NumberOfVertices; i++)
{
PerVertexData pvd =
vertData[pl.NumberOfVertices - (i + 1)];
pl.SetPointAt(i, pvd.pt);
pl.SetBulgeAt(i, -pvd.bulge);
pl.SetStartWidthAt(i, pvd.endWidth);
pl.SetEndWidthAt(i, pvd.startWidth);
}
}
tr.Commit();
}
}
}
}
When running the code against a standard Polyline, you shouldn’t see any difference (unless it has a linetype that happens to indicate the direction), but you can double-check using the PEDIT command, and checking which vertex gets highlighted when you edit a vertex.
I think this implementation is solid enough, but there may yet be something I’ve missed. For instance: the ECS isn’t being updated – unless it’s happening behind the scenes – so I suppose the vertex data is always relative to the first vertex added, even when the direction is reversed and that happens to be the “last” vertex… it’s not clear this is necessarily an issue, but I can imagine it could be, depending on what assumptions have been made in people’s code.
If anyone has a situation this approach doesn’t work for, please do post a comment and I’ll look into it a bit more deeply.
Update:
Many thanks to Mark Dubbelaar for pointing out the Curve.ReverseCurve() method, which works perfectly on all manner of polyline.
Here’s the updated C# code which can now handle various types of Curve:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
namespace CurveEditing
{
public class Commands
{
[CommandMethod("RCD")]
static public void ReverseCurveDirection()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
var peo =
new PromptEntityOptions(
"\nSelect curve to reverse"
);
peo.SetRejectMessage("Must be a curve.");
peo.AddAllowedClass(typeof(Curve), false);
var per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
var tr = doc.TransactionManager.StartTransaction();
using (tr)
{
var obj = tr.GetObject(per.ObjectId, OpenMode.ForWrite);
var cur = obj as Curve;
if (cur != null)
{
try
{
cur.ReverseCurve();
}
catch
{
ed.WriteMessage(
"\nCould not reverse object of type {0}.",
cur.GetType().Name
);
}
}
tr.Commit();
}
}
}
}
The ReverseCurve() call is enclosed in a try-catch block because for some classes (such as Arc) this method remains unimplemented. You could also choose to restrict the classes that can be selected – to avoid it being called on classes that are derived from Curve but don’t contain an implementation of the method – but for simplicity I’ve handled it this way.