BUG : "Line Tangent Two Curves" not robust

Here’s a case of “Line Tangent Two Curves” playing with my nerves :

If there was a way to make it create the 4 possible lines, one could erase the un-wanted ones and move on whith his work.
This tool, along with “Line Tangent-Perpendicular”, and others are a mediocre excuse for not having proper dynamic sketch constraints in Rhino, but at least, they should be robust.

Hi Olivier -
I’m trying to reproduce that behavior here but am failing miserably. Would you be able to post those curves in a 3dm file?
-wim

from Rhino.Input.Custom import GetPoint
from Rhino.RhinoMath import Clamp
from Rhino.Commands import Result
from System.Drawing import Color
from scriptcontext import doc
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

class GeGuidePoint(GetPoint):
    def __init__(self, lines):
        self.m_lines = lines
        self.line = rg.Line.Unset
    def OnDynamicDraw(self, e):
        ds = []
        for line in self.m_lines:
            ds.append(line.DistanceTo(e.CurrentPoint, True))
        self.line = self.m_lines[ds.index(min(ds))]
        e.Display.DrawLine(self.line, Color.White)

def circleGeometryFilter(rhObject, geometry, componentIndex):
    return rs.coercecurve(geometry).TryGetCircle()[0]

def SolveTangents(cir0, cir1):
    tangents = []
    num1 = cir0.Center.DistanceTo(cir1.Center)
    cir2 = rg.Circle(rg.Plane.WorldXY, cir0.Radius)
    cir3 = rg.Circle(rg.Plane.WorldXY, cir1.Radius)
    cir3.Center = rg.Point3d(num1, 0.0, 0.0)
    if cir0.Radius + cir1.Radius < num1:
        d = Clamp((cir0.Radius + cir1.Radius) / num1, -1.0, 1.0)
        num2 = math.acos(d)
        tangents.append(rg.Line(cir2.PointAt(num2), cir3.PointAt(math.pi + num2)))
        tangents.append(rg.Line(cir2.PointAt(-num2), cir3.PointAt(math.pi - num2)))
    if num1 + cir0.Radius > cir1.Radius and num1 + cir1.Radius > cir0.Radius:
        if abs(cir0.Radius - cir1.Radius) < 1E-09:
          tangents.append(rg.Line(0.0, cir0.Radius, 0.0, num1, cir1.Radius, 0.0))
          tangents.append(rg.Line(0.0, -cir0.Radius, 0.0, num1, -cir1.Radius, 0.0))
        elif cir0.Radius < cir1.Radius:
          d = Clamp((cir1.Radius - cir0.Radius) / num1, -1.0, 1.0)
          num2 = math.acos(d)
          tangents.append(rg.Line(cir2.PointAt(math.pi - num2), cir3.PointAt(math.pi - num2)))
          tangents.append(rg.Line(cir2.PointAt(math.pi + num2), cir3.PointAt(math.pi + num2)))
        else:
          d = Clamp((cir0.Radius - cir1.Radius) / num1, -1.0, 1.0)
          num2 = math.acos(d)
          tangents.append(rg.Line(cir2.PointAt(num2), cir3.PointAt(num2)))
          tangents.append(rg.Line(cir2.PointAt(-num2), cir3.PointAt(-num2)))
    if cir0.Plane != rg.Plane.WorldXY:
        xform = rg.Transform.ChangeBasis(cir0.Plane, rg.Plane.WorldXY)
        for tan in tangents:
            tan.Transform(xform)
    plane = cir0.Plane
    rc, u, v = plane.ClosestParameter(cir1.Center)
    if u != 0.0 or v != 0.0:
        xform = rg.Transform.Rotation(math.atan2(v, u), cir0.Normal, cir0.Center)
        for tan in tangents:
            tan.Transform(xform)
    return tangents

def RunCommand():
    cir0id = rs.GetObject("Select first circle", rs.filter.curve, False, False, circleGeometryFilter)
    if not cir0id: return
    cir1id = rs.GetObject("Select second circle", rs.filter.curve, False, False, circleGeometryFilter)
    if not cir1id: return
    cir0 = rs.coercecurve(cir0id).TryGetCircle()[1]
    cir1 = rs.coercecurve(cir1id).TryGetCircle()[1]
    if cir0.Plane.DistanceTo(cir1.Center) > 0:
        print "Input circles must be co-planar"
        return
    tangents = SolveTangents(cir0, cir1)
    ggp = GeGuidePoint(tangents)
    ggp.SetCommandPrompt("Guide point")
    ggp.Get()
    if ggp.CommandResult() <> Result.Success:
        return ggp.CommandResult()
    doc.Objects.AddLine(ggp.line)
    doc.Views.Redraw()

if __name__=="__main__":
    RunCommand()

CircleTangents.py (3.4 KB)

4 Likes

Lost it , sorry.

Anyways, I think that the erogonomy of Mahdiyar’s script is much better.
It might take one click more, but I like the visual interaction.