Hi,
Have look here:
Break out the math (all that work in high school you never thought would be useful... who knew). A line that is tangent to a circle will be at 90 degrees to the radius of that circle. For a line to be tangent to two circles, you need to find a line that is perpendicular to the radius of each circle. Under this cirumstance, the radii of both circles will be parallel. From there its just a matter of having a line extending from each circles center to its respective radius, using the same angle for each, and the tangent line will connect them.
我的意思是想用C#实现
LINE <enter>
TAN <enter>
Pick the first point
TAN <enter>
Pick the second point
<enter>
这样的一个功能,而不是将两圆的所有公切线都画出来,仅画出自已所需的一条公切线。
本人初学对autocad一些函数用法不熟悉。希望老师用C#代码将上述命令模拟出来。
本人英语水水很菜,只能写中文了
Hi,
Here's an extension method for the CicularArc3d class.
It returns a List of LineSegment3d for the tangents between two circles (maybe 0, 2 or 4).
An exception is thrown if the circles do not lie on the same plane.
using System; using System.Collections.Generic; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; namespace TangentsToCircle { public static class Tangents { public static List<LineSegment3d> GetTangentsToCircle(this CircularArc3d circle, CircularArc3d other) { // check if circles lies on the same plane Vector3d normal = circle.Normal; Plane plane = new Plane(Point3d.Origin, normal); double elev1 = circle.Center.TransformBy(Matrix3d.WorldToPlane(plane)).Z; double elev2 = other.Center.TransformBy(Matrix3d.WorldToPlane(plane)).Z; if (!(normal.IsParallelTo(other.Normal) && Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint)) throw new Autodesk.AutoCAD.Runtime.Exception( Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry); List<LineSegment3d> result = new List<LineSegment3d>(); // check if a circle is not inside the other double dist = circle.Center.DistanceTo(other.Center); if (dist <= Math.Abs(circle.Radius - other.Radius)) return result; CircularArc3d tmp1, tmp2; Point3d center; Point3d[] inters; Vector3d vec, vec1, vec2; // external tangents if (circle.Radius == other.Radius) { center = circle.Center; normal = circle.Normal; vec = other.Center - center; Line3d perp = new Line3d(center, vec.CrossProduct(normal)); inters = circle.IntersectWith(perp); if (inters != null) { result.Add(new LineSegment3d(inters[0], inters[0] + vec)); result.Add(new LineSegment3d(inters[1], inters[1] + vec)); } } else { if (circle.Radius < other.Radius) { tmp1 = circle; circle = other; other = tmp1; } center = circle.Center; normal = circle.Normal; vec = other.Center - center; tmp1 = new CircularArc3d(circle.Center, normal, circle.Radius - other.Radius); tmp2 = new CircularArc3d(center + vec / 2.0, normal, dist / 2.0); inters = tmp1.IntersectWith(tmp2); if (inters != null) { vec1 = (inters[0] - center).GetNormal(); vec2 = (inters[1] - center).GetNormal(); result.Add(new LineSegment3d(center + vec1 * circle.Radius, other.Center + vec1 * other.Radius)); result.Add(new LineSegment3d(center + vec2 * circle.Radius, other.Center + vec2 * other.Radius)); } } // crossing tangents if (circle.Radius + other.Radius < dist) { double ratio = (circle.Radius / (circle.Radius + other.Radius)) / 2.0; tmp1 = new CircularArc3d(center + vec * ratio, normal, dist * ratio); inters = circle.IntersectWith(tmp1); if (inters != null) { vec1 = (inters[0] - center).GetNormal(); vec2 = (inters[1] - center).GetNormal(); result.Add(new LineSegment3d(center + vec1 * circle.Radius, other.Center + vec1.Negate() * other.Radius)); result.Add(new LineSegment3d(center + vec2 * circle.Radius, other.Center + vec2.Negate() * other.Radius)); } } return result; } } }
A test command:
using System.Collections.Generic; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; [assembly: CommandClass(typeof(TangentsToCircle.CommandMethods))] namespace TangentsToCircle { public class CommandMethods { [CommandMethod("Test", CommandFlags.Modal)] public void Test() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: "); peo.SetRejectMessage("Only a circle."); peo.AddAllowedClass(typeof(Circle), true); PromptEntityResult per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; ObjectId id1 = per.ObjectId; peo.Message = "\nSelect another circle: "; ObjectId id2; while (true) { per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; id2 = per.ObjectId; if (id1 == id2) ed.WriteMessage("\nThe second circle is the same as the first one."); else break; } try { using (Transaction tr = db.TransactionManager.StartTransaction()) { Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead); Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead); CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius); CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius); List<LineSegment3d> lines = ca1.GetTangentsToCircle(ca2); BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); foreach (LineSegment3d l in lines) { Line line = new Line(l.StartPoint, l.EndPoint); btr.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); } tr.Commit(); } } catch (Autodesk.AutoCAD.Runtime.Exception exn) { ed.WriteMessage("\n" + exn.Message); } catch (System.Exception exn) { ed.WriteMessage("\n" + exn.Message); } } } }
A better (more robust and xml documented) implementation.
using System; using Autodesk.AutoCAD.Geometry; namespace GeometryExtensions { /// <summary> /// Tangent type enum /// </summary> [Flags] public enum TangentType { Inner = 1, Outer = 2 } /// <summary> /// Provides extension methods for the CircularArc3d class. /// </summary> public static class CircularArc3dExtensions { /// <summary> /// Returns the tangents between the active CircularArc3d instance complete circle and another one. /// </summary> /// <remarks> /// Tangents start points are on the object to which this method applies, end points on the one passed as argument. /// Tangents are always returned in the same order: outer tangents before inner tangents, and for both, /// the tangent on the left side of the line from this circular arc center to the other one before the one on the right side. /// </remarks> /// <param name="arc">The object to which this method applies.</param> /// <param name="other">The CircularArc3d to which searched for tangents.</param> /// <param name="flags">An enum value specifying which type of tangent is returned.</param> /// <returns>An array of LineSegment3d representing the tangents (maybe 2 or 4) or null if there is none.</returns> /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on theme plane.</exception> public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, CircularArc3d other, TangentType flags) { // check if circles lies on the same plane Vector3d normal = arc.Normal; double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z; double elev2 = other.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z; if (!(normal.IsParallelTo(other.Normal) && Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint)) throw new Autodesk.AutoCAD.Runtime.Exception( Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry); // check if a circle is inside the other double dist = arc.Center.DistanceTo(other.Center); if (dist - Math.Abs(arc.Radius - other.Radius) <= Tolerance.Global.EqualPoint) return null; // check if circles overlap bool overlap = arc.Radius + other.Radius >= dist; if (overlap && flags == TangentType.Inner) return null; CircularArc3d tmp1, tmp2; Point3d[] inters; Vector3d vec1, vec2, vec = other.Center - arc.Center; int i, j; LineSegment3d[] result = new LineSegment3d[(int)flags == 3 && !overlap ? 4 : 2]; // outer tangents if (flags.HasFlag(TangentType.Outer)) { if (arc.Radius == other.Radius) { Line3d perp = new Line3d(arc.Center, vec.CrossProduct(normal)); inters = arc.IntersectWith(perp); vec1 = (inters[0] - arc.Center).GetNormal(); vec2 = (inters[1] - arc.Center).GetNormal(); i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1; j = i ^ 1; result[i] = new LineSegment3d(inters[0], inters[0] + vec); result[j] = new LineSegment3d(inters[1], inters[1] + vec); } else { Point3d center = arc.Radius < other.Radius ? other.Center : arc.Center; tmp1 = new CircularArc3d(center, normal, Math.Abs(arc.Radius - other.Radius)); tmp2 = new CircularArc3d(arc.Center + vec / 2.0, normal, dist / 2.0); inters = tmp1.IntersectWith(tmp2); vec1 = (inters[0] - center).GetNormal(); vec2 = (inters[1] - center).GetNormal(); i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1; j = i ^ 1; result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1 * other.Radius); result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2 * other.Radius); } } // inner tangents if (flags.HasFlag(TangentType.Inner) && !overlap) { double ratio = (arc.Radius / (arc.Radius + other.Radius)) / 2.0; tmp1 = new CircularArc3d(arc.Center + vec * ratio, normal, dist * ratio); inters = arc.IntersectWith(tmp1); vec1 = (inters[0] - arc.Center).GetNormal(); vec2 = (inters[1] - arc.Center).GetNormal(); i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 2 : 3; j = i == 2 ? 3 : 2; result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1.Negate() * other.Radius); result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2.Negate() * other.Radius); } return result; } /// <summary> /// Returns the tangents between the active CircularArc3d instance complete circle and a point. /// </summary> /// <remarks> /// Tangents start points are on the object to which this method applies, end points on the point passed as argument. /// Tangents are always returned in the same order: the tangent on the left side of the line from the circular arc center /// to the point before the one on the right side. /// </remarks> /// <param name="arc">The object to which this method applies.</param> /// <param name="pt">The Point3d to which tangents are searched</param> /// <returns>An array of LineSegement3d representing the tangents (2) or null if there is none.</returns> /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on theme plane.</exception> public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, Point3d pt) { // check if circle and point lies on the plane Vector3d normal = arc.Normal; double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z; double elev2 = pt.TransformBy(Matrix3d.WorldToPlane(normal)).Z; if (Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint) throw new Autodesk.AutoCAD.Runtime.Exception( Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry); // check if the point is inside the circle Point3d center = arc.Center; if (pt.DistanceTo(center) <= arc.Radius) return null; Vector3d vec = pt.GetVectorTo(center) / 2.0; CircularArc3d tmp = new CircularArc3d(pt + vec, arc.Normal, vec.Length); Point3d[] inters = arc.IntersectWith(tmp); LineSegment3d[] result = new LineSegment3d[2]; int i = vec.GetAngleTo(inters[0] - center, normal) < vec.GetAngleTo(inters[1] - center, normal) ? 0 : 1; int j = i ^ 1; result[i] = new LineSegment3d(inters[0], pt); result[j] = new LineSegment3d(inters[1], pt); return result; } } }
A test command which draws a closed polyline along the outer tangents and the trimmed selected circles.
[CommandMethod("JoinCircles", CommandFlags.Modal)] public void JoinCircles() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: "); peo.SetRejectMessage("Only a circle."); peo.AddAllowedClass(typeof(Circle), true); PromptEntityResult per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; ObjectId id1 = per.ObjectId; peo.Message = "\nSelect another circle: "; ObjectId id2; while (true) { per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; id2 = per.ObjectId; if (id1 == id2) ed.WriteMessage("\nThe second circle is the same as the first one."); else break; } try { using (Transaction tr = db.TransactionManager.StartTransaction()) { Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead); Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead); CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius); CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius); LineSegment3d[] lines = ca1.GetTangentsTo(ca2, TangentType.Outer); if (lines != null) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); Vector3d vec = c1.Center.GetVectorTo(c2.Center); Plane plane = new Plane(Point3d.Origin, c1.Normal); double a1 = vec.GetAngleTo(ca1.Center.GetVectorTo(lines[1].StartPoint), ca1.Normal) - vec.GetAngleTo(ca1.Center.GetVectorTo(lines[0].StartPoint), ca1.Normal); double a2 = vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[0].EndPoint), ca1.Normal) - vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[1].EndPoint), ca1.Normal); Polyline pline = new Polyline(4); pline.AddVertexAt(0, lines[0].StartPoint.Convert2d(plane), Math.Tan(a1 / 4.0), 0.0, 0.0); pline.AddVertexAt(1, lines[1].StartPoint.Convert2d(plane), 0.0, 0.0, 0.0); pline.AddVertexAt(2, lines[1].EndPoint.Convert2d(plane), Math.Tan(a2 / 4.0), 0.0, 0.0); pline.AddVertexAt(3, lines[0].EndPoint.Convert2d(plane), 0.0, 0.0, 0.0); pline.Closed = true; pline.Normal = c1.Normal; pline.Elevation = c1.Center.TransformBy(Matrix3d.WorldToPlane(c1.Normal)).Z; btr.AppendEntity(pline); tr.AddNewlyCreatedDBObject(pline, true); c1.UpgradeOpen(); c2.UpgradeOpen(); c1.Erase(); c2.Erase(); } tr.Commit(); } } catch (System.Exception exn) { ed.WriteMessage("\nError: " + exn.Message); } }
谢谢你的回复. 可能你没有理解我所表达的意思。虽然你的代码没有实现我所想要的功能,但是我可以根所据你的代码进行更改来实现。
Thank you for your reply. You may not understand what I mean. Although your code does not realize what I want, but I can change according to your code to achieve.. Then I will put out the modified code.
struct lineDist { public Line l; public double dist; } [CommandMethod("tt")] static public void test() { Point3d pickedP1; Point3d pickedP2; Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: "); peo.SetRejectMessage("Only a circle."); peo.AddAllowedClass(typeof(Circle), true); PromptEntityResult per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; ObjectId id1 = per.ObjectId; pickedP1 = per.PickedPoint; peo.Message = "\nSelect another circle: "; ObjectId id2; while (true) { per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) return; id2 = per.ObjectId; pickedP2 = per.PickedPoint; if (id1 == id2) ed.WriteMessage("\nThe second circle is the same as the first one."); else break; } try { using (Transaction tr = db.TransactionManager.StartTransaction()) { Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead); Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead); CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius); CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius); List<LineSegment3d> lines = ydsCommands.GetTangentsToCircle(ca1, ca2); BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); List<lineDist> ld = new List<lineDist>(lines.Count); double dist = 0; lineDist ldx = new lineDist(); foreach (LineSegment3d l in lines) { Line line = new Line(l.StartPoint, l.EndPoint); dist = line.GetClosestPointTo(pickedP1, true).DistanceTo(pickedP1) + line.GetClosestPointTo(pickedP2, true).DistanceTo(pickedP2); ldx.l = line; ldx.dist = dist; ld.Add(ldx); } foreach (lineDist d in ld) { if (d.dist < ldx.dist) { ldx = d; } } btr.AppendEntity(ldx.l); tr.AddNewlyCreatedDBObject(ldx.l, true); tr.Commit(); } } catch (Autodesk.AutoCAD.Runtime.Exception exn) { ed.WriteMessage("\n" + exn.Message); } catch (System.Exception exn) { ed.WriteMessage("\n" + exn.Message); } } public static List<LineSegment3d> GetTangentsToCircle(CircularArc3d circle, CircularArc3d other) { // check if circles lies on the same plane Vector3d normal = circle.Normal; Plane plane = new Plane(Point3d.Origin, normal); double elev1 = circle.Center.TransformBy(Matrix3d.WorldToPlane(plane)).Z; double elev2 = other.Center.TransformBy(Matrix3d.WorldToPlane(plane)).Z; if (!(normal.IsParallelTo(other.Normal) && Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint)) throw new Autodesk.AutoCAD.Runtime.Exception( Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry); List<LineSegment3d> result = new List<LineSegment3d>(); // check if a circle is not inside the other double dist = circle.Center.DistanceTo(other.Center); if (dist <= Math.Abs(circle.Radius - other.Radius)) return result; CircularArc3d tmp1, tmp2; Point3d center; Point3d[] inters; Vector3d vec, vec1, vec2; // external tangents if (circle.Radius == other.Radius) { center = circle.Center; normal = circle.Normal; vec = other.Center - center; Line3d perp = new Line3d(center, vec.CrossProduct(normal)); inters = circle.IntersectWith(perp); if (inters != null) { result.Add(new LineSegment3d(inters[0], inters[0] + vec)); result.Add(new LineSegment3d(inters[1], inters[1] + vec)); } } else { if (circle.Radius < other.Radius) { tmp1 = circle; circle = other; other = tmp1; } center = circle.Center; normal = circle.Normal; vec = other.Center - center; tmp1 = new CircularArc3d(circle.Center, normal, circle.Radius - other.Radius); tmp2 = new CircularArc3d(center + vec / 2.0, normal, dist / 2.0); inters = tmp1.IntersectWith(tmp2); if (inters != null) { vec1 = (inters[0] - center).GetNormal(); vec2 = (inters[1] - center).GetNormal(); result.Add(new LineSegment3d(center + vec1 * circle.Radius, other.Center + vec1 * other.Radius)); result.Add(new LineSegment3d(center + vec2 * circle.Radius, other.Center + vec2 * other.Radius)); } } // crossing tangents if (circle.Radius + other.Radius < dist) { double ratio = (circle.Radius / (circle.Radius + other.Radius)) / 2.0; tmp1 = new CircularArc3d(center + vec * ratio, normal, dist * ratio); inters = circle.IntersectWith(tmp1); if (inters != null) { vec1 = (inters[0] - center).GetNormal(); vec2 = (inters[1] - center).GetNormal(); result.Add(new LineSegment3d(center + vec1 * circle.Radius, other.Center + vec1.Negate() * other.Radius)); result.Add(new LineSegment3d(center + vec2 * circle.Radius, other.Center + vec2.Negate() * other.Radius)); } } return result; }
Can't find what you're looking for? Ask the community or share your knowledge.