Here's a simple example of a .NET plugin defining a command to fillet the selected polylines you can build following this exercice first.
While netloaded in AutoCAD, the FILLETPOLYLINE command can be called as any other native command.
using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Core.Application;
[assembly: CommandClass(typeof(FilletPolylineSample.Commands))]
namespace FilletPolylineSample
{
public class Commands
{
[CommandMethod("FILLETPOLYLINE")]
public static void Test()
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// prompt the user to select polylines
var filter = new SelectionFilter(new[] { new TypedValue(0, "LWPOLYLINE") });
var selection = ed.GetSelection(filter);
if (selection.Status != PromptStatus.OK)
return;
// prompt the user to specify the radius
var pdo = new PromptDistanceOptions("\nSpecify the radius: ");
pdo.DefaultValue = (double)AcAp.GetSystemVariable("FILLETRAD");
var pdr = ed.GetDistance(pdo);
if (pdr.Status != PromptStatus.OK)
return;
AcAp.SetSystemVariable("FILLETRAD", pdr.Value);
// start a transaction to open and fillet the selected polylines
using (var tr = db.TransactionManager.StartTransaction())
{
foreach (SelectedObject selectedObject in selection.Value)
{
var pline = (Polyline)tr.GetObject(selectedObject.ObjectId, OpenMode.ForWrite);
pline.FilletAll(pdr.Value);
tr.Commit();
}
}
}
}
static class Extensions
{
/// <summary>
/// Adds an arc (fillet), if able, at each polyline vertex.
/// </summary>
/// <param name="pline">The instance to which the method applies.</param>
/// <param name="radius">The arc radius.</param>
public static void FilletAll(this Polyline pline, double radius)
{
int n = pline.Closed ? 0 : 1;
for (int i = n; i < pline.NumberOfVertices - n; i += 1 + pline.FilletAt(i, radius))
{ }
}
/// <summary>
/// Adds an arc (fillet) at the specified vertex.
/// </summary>
/// <param name="pline">The instance to which the method applies.</param>
/// <param name="index">The index of the vertex.</param>
/// <param name="radius">The arc radius.</param>
/// <returns>1 if the operation succeeded, 0 if it failed.</returns>
public static int FilletAt(this Polyline pline, int index, double radius)
{
int prev = index == 0 && pline.Closed ? pline.NumberOfVertices - 1 : index - 1;
if (pline.GetSegmentType(prev) != SegmentType.Line ||
pline.GetSegmentType(index) != SegmentType.Line)
{
return 0;
}
LineSegment2d seg1 = pline.GetLineSegment2dAt(prev);
LineSegment2d seg2 = pline.GetLineSegment2dAt(index);
Vector2d vec1 = seg1.StartPoint - seg1.EndPoint;
Vector2d vec2 = seg2.EndPoint - seg2.StartPoint;
double angle = (Math.PI - vec1.GetAngleTo(vec2)) / 2.0;
double dist = radius * Math.Tan(angle);
if (dist == 0.0 || dist > seg1.Length || dist > seg2.Length)
{
return 0;
}
Point2d pt1 = seg1.EndPoint + vec1.GetNormal() * dist;
Point2d pt2 = seg2.StartPoint + vec2.GetNormal() * dist;
double bulge = Math.Tan(angle / 2.0);
if (Clockwise(seg1.StartPoint, seg1.EndPoint, seg2.EndPoint))
{
bulge = -bulge;
}
pline.AddVertexAt(index, pt1, bulge, 0.0, 0.0);
pline.SetPointAt(index + 1, pt2);
return 1;
}
/// <summary>
/// Evaluates if the points are clockwise.
/// </summary>
/// <param name="p1">First point.</param>
/// <param name="p2">Second point</param>
/// <param name="p3">Third point</param>
/// <returns>true if points are clockwise, false otherwise.</returns>
private static bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
{
return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-8;
}
}
}
From your external application, you can netload this plugin and launch the FILLETPOLYLINE command with SendCommans. As SendCommand argument is a string (as a .scr file) to pass the command a selection set, you have to first select the polylines you want to fillet by code and use the "_Previous" keyword in the command string.
activeDocument.SendCommand("FILLETPOLYLINE\n_Previous\n\n0.1\n");