@Yonas89 I did some improvements and it became a monster but it's working like a charm:
Main Method:
[CommandMethod("GetTangentArc")]
public static void GetTangentArc()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
var currentlayout = LayoutManager.Current.GetLayoutId(LayoutManager.Current.CurrentLayout);
ObjectId CurveId = ObjectId.Null, Lineid = ObjectId.Null;
Point3d CurveIdPickedPoint = new Point3d(), LineidPickedPoint = new Point3d();
int segmentIndex = 0;
Point3d CalculatedFilletCenter = new Point3d();
double Calculatedradius = 0, Calculatedstartangle = 0, Calculatedendangle = 0;
ObjectId DrawnArc = ObjectId.Null;
var peo = new PromptEntityOptions("\nSelect the line");
peo.SetRejectMessage("\nMust select a line!");
peo.AddAllowedClass(typeof(Autodesk.AutoCAD.DatabaseServices.Line), true);
var line_Gotten = ed.GetEntity(peo);
if (line_Gotten.Status == PromptStatus.Cancel || line_Gotten.Status == PromptStatus.Error || line_Gotten.Status == PromptStatus.None) return;
ed.WriteMessage(line_Gotten.StringResult + "\n");
Lineid = line_Gotten.ObjectId;
LineidPickedPoint = line_Gotten.PickedPoint;
var peo2 = new PromptEntityOptions("\nSelect the Circle, Arc or Polyline Segment");
peo2.SetRejectMessage("\nMust select a Circle, an Arc or a Polyline Arc Segment!");
peo2.AddAllowedClass(typeof(Autodesk.AutoCAD.DatabaseServices.Circle), true);
peo2.AddAllowedClass(typeof(Autodesk.AutoCAD.DatabaseServices.Polyline), true);
peo2.AddAllowedClass(typeof(Autodesk.AutoCAD.DatabaseServices.Arc), true);
bool isSelectionOk = false;
while (!isSelectionOk)
{
var SelectedCurve = ed.GetEntity(peo2);
if (SelectedCurve.Status == PromptStatus.Cancel || SelectedCurve.Status == PromptStatus.Error || SelectedCurve.Status == PromptStatus.None) return;
CurveId = SelectedCurve.ObjectId;
CurveIdPickedPoint = SelectedCurve.PickedPoint;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var curve = CurveId.GetObject(OpenMode.ForRead) as Curve;
if (curve as Polyline != null)
{
var gottenpl = curve as Polyline;
var pointOnPline = gottenpl.GetClosestPointTo(CurveIdPickedPoint, false);
segmentIndex = (int)gottenpl.GetParameterAtPoint(pointOnPline);
SegmentType type = gottenpl.GetSegmentType(segmentIndex);
if (type == SegmentType.Arc)
{
isSelectionOk = true;
ed.WriteMessage(SelectedCurve.StringResult + "\n");
}
else
ed.WriteMessage("\nYou Must Select Either a Polyline(arc segment), an Arc or a Circle\n");
}
else
{
isSelectionOk = true;
ed.WriteMessage(SelectedCurve.StringResult + "\n");
}
tr.Commit();
}
}
var radius = ed.GetDistance(new PromptDistanceOptions("Enter Radius of the Tangent."));
if (radius.Status == PromptStatus.Cancel) return;
ed.WriteMessage(radius.StringResult + "\n");
var sidepoint = ed.GetPoint(new PromptPointOptions("Pick your side to draw a fillet"));
if (sidepoint.Status == PromptStatus.Cancel) return;
ed.WriteMessage(sidepoint.StringResult + "\n");
using (Transaction tr = db.TransactionManager.StartTransaction())
{
var line = Lineid.GetObject(OpenMode.ForRead) as Autodesk.AutoCAD.DatabaseServices.Line;
var curve = CurveId.GetObject(OpenMode.ForRead);
Circle circle = null;
var isCircle = curve as Autodesk.AutoCAD.DatabaseServices.Circle;
if (isCircle != null)
{
circle = new Circle(isCircle.Center.X, isCircle.Center.Y, isCircle.Radius);
}
var isPoly = curve as Autodesk.AutoCAD.DatabaseServices.Polyline;
if (isPoly != null)
{
CircularArc2d arc2d = isPoly.GetArcSegment2dAt(segmentIndex);
Point2d ep2d = arc2d.EvaluatePoint(arc2d.GetInterval().UpperBound);
Point2d cp2d = arc2d.Center;
circle = new Circle(cp2d.X, cp2d.Y, arc2d.Radius);
}
var isArc = curve as Autodesk.AutoCAD.DatabaseServices.Arc;
if (isArc != null)
{
circle = new Circle(isArc.Center.X, isArc.Center.Y, isArc.Radius);
}
if (circle == null) throw new System.Exception("Shoudn't have happened");
Line PickedLine = null;
if (line.StartPoint.DistanceTo(CurveIdPickedPoint) < line.EndPoint.DistanceTo(CurveIdPickedPoint))
{
PickedLine = new Line( new Point(line.EndPoint.X, line.EndPoint.Y), new Point(line.StartPoint.X, line.StartPoint.Y));
}
else
{
PickedLine = new Line(new Point(line.StartPoint.X, line.StartPoint.Y), new Point(line.EndPoint.X, line.EndPoint.Y));
}
Point SidePoint = new Point(sidepoint.Value.X, sidepoint.Value.Y);
int LeftSideOffset = PickedLine.isPointToTheLeftSide(SidePoint) ?-1: 1;
Line Offset = PickedLine.Offset(LeftSideOffset * radius.Value );
Circle WholeCircle = new Circle(circle.Center.x, circle.Center.y, circle.Radius + radius.Value);
KeyValuePair<Point, Point> Interseptions = new KeyValuePair<Point, Point>();
if (!Offset.TryGetCircleIntersection(WholeCircle, out Interseptions))
{
ed.WriteMessage("\nNo fillet found!\n"); return;
}
var btr = (currentlayout.GetObject(OpenMode.ForRead) as Layout).BlockTableRecordId.GetObject(OpenMode.ForWrite) as BlockTableRecord;
Point2d CenterPoint2d = LineidPickedPoint.DistanceTo(Interseptions.Key.Point3d) < LineidPickedPoint.DistanceTo(Interseptions.Value.Point3d) ?
Interseptions.Key.Point2d :
Interseptions.Value.Point2d;
Point2d StartPoint2d = circle.intersections(new Circle(CenterPoint2d.X, CenterPoint2d.Y, radius.Value)).Key.Point2d;
if (double.IsNaN(StartPoint2d.X) || double.IsNaN(StartPoint2d.Y))
{
ed.WriteMessage("\nNo fillet found!\n"); return;
}
KeyValuePair<Point, Point> EndPointIntersection = new KeyValuePair<Point, Point>();
if (!PickedLine.TryGetCircleIntersection(new Circle(CenterPoint2d.X, CenterPoint2d.Y, radius.Value), out EndPointIntersection)) { throw new System.Exception("Shoudn't have happened"); }
Point2d EndPoint2d = EndPointIntersection.Key.Point2d;
double endAngle = 0;
double startAngle = 0;
switch (LeftSideOffset)
{
case 1:
endAngle = (new Line2d(CenterPoint2d, EndPoint2d)).Direction.Angle;
startAngle = (new Line2d(CenterPoint2d, StartPoint2d)).Direction.Angle;
break;
case -1:
endAngle = (new Line2d(CenterPoint2d, StartPoint2d)).Direction.Angle;
startAngle = (new Line2d(CenterPoint2d, EndPoint2d)).Direction.Angle;
break;
}
CalculatedFilletCenter = new Point3d(CenterPoint2d.X, CenterPoint2d.Y, 0);
Calculatedradius = radius.Value;
Calculatedstartangle = startAngle;
Calculatedendangle = endAngle;
;
using (Arc arc = new Arc(CalculatedFilletCenter, Calculatedradius, Calculatedstartangle, Calculatedendangle))
{
arc.SetDatabaseDefaults(db);
DrawnArc = btr.AppendEntity(arc);
tr.AddNewlyCreatedDBObject(arc, true);
}
tr.Commit();
}
PromptKeywordOptions pso = new PromptKeywordOptions("\nGet outer Arc [Yes/No]: ", "Yes No");
pso.Keywords.Default = "No";
pso.AppendKeywordsToMessage = true;
PromptResult pr = ed.GetKeywords(pso);
;
if (pr.StringResult == "Yes")
{
using (var tr = db.TransactionManager.StartTransaction())
{
DrawnArc.GetObject(OpenMode.ForWrite).Erase();
using (Arc arc = new Arc(CalculatedFilletCenter, Calculatedradius, Calculatedendangle, Calculatedstartangle))
{
var btr = (currentlayout.GetObject(OpenMode.ForRead) as Layout).BlockTableRecordId.GetObject(OpenMode.ForWrite) as BlockTableRecord;
arc.SetDatabaseDefaults(db);
DrawnArc = btr.AppendEntity(arc);
tr.AddNewlyCreatedDBObject(arc, true);
}
tr.Commit();
}
}
}
Nested Classes used for calculation:
internal class Point
{
public double x, y;
public Point3d Point3d { get { return new Point3d(x, y, 0); } }
public Point2d Point2d { get { return new Point2d(x, y); } }
public Point(double px, double py)
{
x = px;
y = py;
}
public Point sub(Point p2)
{
return new Point(x - p2.x, y - p2.y);
}
public Point add(Point p2)
{
return new Point(x + p2.x, y + p2.y);
}
public double distance(Point p2)
{
return Math.Sqrt((x - p2.x) * (x - p2.x) + (y - p2.y) * (y - p2.y));
}
public Point normal()
{
var length = Math.Sqrt(x * x + y * y);
return new Point(x / length, y / length);
}
public Point scale(double s)
{
return new Point(x * s, y * s);
}
public static Point operator -(Point p1, Point p2)
{
return new Point(p1.x - p2.x, p1.y - p2.y);
}
};
internal class Line
{
public Point p1, p2;
public Line(Point p1, Point p2)
{
this.p1 = p1;
this.p2 = p2;
}
public Line Offset(double distance)
{
var L = Math.Sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
var offsetPixels = distance;
// This is the second line
var x1p = p1.x + offsetPixels * (p2.y - p1.y) / L;
var x2p = p2.x + offsetPixels * (p2.y - p1.y) / L;
var y1p = p1.y + offsetPixels * (p1.x - p2.x) / L;
var y2p = p2.y + offsetPixels * (p1.x - p2.x) / L;
return new Line(new Point(x1p, y1p ), new Point(x2p, y2p));
}
public bool isPointToTheLeftSide(Point c)
{
return ((p2.x - p1.x) * (c.y - p1.y) - (p2.y - p1.y) * (c.x - p1.x)) > 0;
}
public bool TryGetCircleIntersection(Circle c, out KeyValuePair<Point,Point> Interseptions)
{
// compute the euclidean distance between A and B
var LAB = Math.Sqrt(Math.Pow(p2.x - p1.x, 2) + (Math.Pow(p2.y - p1.y, 2)));
// compute the direction vector D from A to B
var Dx = (p2.x - p1.x) / LAB;
var Dy = (p2.y - p1.y) / LAB;
// Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.
// compute the value t of the closest point to the circle center (Cx, Cy)
var t = Dx * (c.x - p1.x) + Dy * (c.y - p1.y);
// This is the projection of C on the line from A to B.
// compute the coordinates of the point E on line and closest to C
var Ex = t * Dx + p1.x;
var Ey = t * Dy + p1.y;
// compute the euclidean distance from E to C
var LEC = Math.Sqrt(Math.Pow(Ex - c.x, 2) + Math.Pow(Ey - c.y, 2));
// test if the line intersects the circle
if (LEC < c.r)
{
// compute distance from t to circle intersection point
var dt = Math.Sqrt(Math.Pow(c.r, 2) - Math.Pow(LEC, 2));
// compute first intersection point
var Fx = (t - dt) * Dx + p1.x;
var Fy = (t - dt) * Dy + p1.y;
// compute second intersection point
var Gx = (t + dt) * Dx + p1.x;
var Gy = (t + dt) * Dy + p1.y;
Interseptions = new KeyValuePair<Point, Point>(new Point(Fx, Fy), new Point(Gx, Gy));
return true;
}
// else test if the line is tangent to circle
else if (Math.Round(LEC,5) == Math.Round(c.r,5))
{
Interseptions = new KeyValuePair<Point, Point>(new Point(Ex, Ey), new Point(Ex, Ey));
return true;
}
// tangent point to circle is E
else
{
Interseptions = new KeyValuePair<Point, Point>(new Point(double.NaN, double.NaN), new Point(double.NaN, double.NaN));
return false;
}
// line doesn't touch circle
}
}
internal class Circle
{
public double x, y, r, left;
public Point Center { get { return new Point(x, y); } }
public double Radius { get { return r; } }
public Circle(double cx, double cy, double cr)
{
x = cx;
y = cy;
r = cr;
left = x - r;
}
public KeyValuePair<Point, Point> intersections(Circle c)
{
Point P0 = new Point(x, y);
Point P1 = new Point(c.x, c.y);
double d, a, h;
d = P0.distance(P1);
a = (r * r - c.r * c.r + d * d) / (2 * d);
h = Math.Sqrt(r * r - a * a);
Point P2 = P1.sub(P0).scale(a / d).add(P0);
double x3, y3, x4, y4;
x3 = P2.x + h * (P1.y - P0.y) / d;
y3 = P2.y - h * (P1.x - P0.x) / d;
x4 = P2.x - h * (P1.y - P0.y) / d;
y4 = P2.y + h * (P1.x - P0.x) / d;
return new KeyValuePair<Point, Point>(new Point(x3, y3), new Point(x4, y4));
}
};
Demonstration: