I’m always amazed how AutoCAD Geometry namespace can be used to perform some vector based math very easy. On this post, the goal is get all contours lines from a Tin Surface and create label group object on each of them.
The main point is the SurfaceContourLabelGroup.Create method requires a list of points. Why? Because you can pass a sequence of points where Civil 3D will draw a line (or polyline) and every time it crosses (intersect) a contour, will add a label.
Just like in the UI, we can add this SurfaceContourLabelGroup object for each contour line but, just like in the UI, we need a small line that cross the contour line. Here is where the math enters: get a point at the middle of a contour line, get the first derivative vector (that points on the direction of the contour line), rotate by +90 and by -90 degrees, finally convert to a unit vector (to make it small enough) and add on both direction to the original point on the contour line. And with that we have the 2 points we need.
[CommandMethod("testAddLabelContour")]
public static void CmdAddLabelAtContour()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
// select a tin surface
PromptEntityOptions peo = new PromptEntityOptions(
"\nSelect a tin surface: ");
peo.SetRejectMessage("\nOnly surface");
peo.AddAllowedClass(typeof(TinSurface), true);
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK) return;
AddLabelAtContours(per.ObjectId);
}
private static void AddLabelAtContours(ObjectId surfaceId)
{
Database db = Application.DocumentManager.MdiActiveDocument.Database;
CivilDocument civilDoc = CivilApplication.ActiveDocument;
using (Transaction trans = db.TransactionManager.StartTransaction())
{
TinSurface surface = trans.GetObject(surfaceId,
OpenMode.ForRead) as TinSurface;
// finally call our custom method to add labels
// on each polyline extracted from the surface
AddContoursLabels(surfaceId,
surface.ExtractMajorContours(SurfaceExtractionSettingsType.Plan));
AddContoursLabels(surfaceId,
surface.ExtractMinorContours(SurfaceExtractionSettingsType.Plan));
trans.Commit();
}
}
private static void AddContoursLabels(ObjectId surfaceId,
ObjectIdCollection contourIds)
{
for (int i = 0; i < contourIds.Count; i++)
{
ObjectId contourId = contourIds[i];
// Contours are lightweight Polyline objects:
Polyline contour = contourId.GetObject(OpenMode.ForWrite) as Polyline;
// get a point at the middle of the pline
Point3d pointOnContour = contour.GetPointAtParameter(0.5);
// get the first derivative on this point
Vector3d firstDerivative = contour.GetFirstDerivative(pointOnContour);
// now do some math to get one point on each side, not far
// than 0.5 units...
Point3d leftPoint = pointOnContour
.Add(firstDerivative.RotateBy(Math.PI / 2, Vector3d.ZAxis) // rotate 90o
.DivideBy(firstDerivative.Length) // make unit vector
.MultiplyBy(0.5)); // size 0.5
Point3d rightPoint = pointOnContour
.Add(firstDerivative.RotateBy(-Math.PI / 2, Vector3d.ZAxis) // rotate -90o
.DivideBy(firstDerivative.Length) // make unit vector
.MultiplyBy(0.5)); // size 0.5
// create a list of points
Point2dCollection points = new Point2dCollection();
points.Add(leftPoint.Convert2d(new Plane()));
points.Add(rightPoint.Convert2d(new Plane()));
// and create the label group
SurfaceContourLabelGroup.Create(surfaceId, points);
// and erase the pline (don't need anymore...)
contour.Erase();
}
}
As a result, a label is added at the middle (parameter 0.5) of each contour line (major and minor).