Re: Tangent Lines

Otfried Cheong <[email protected]> Sat, 02 Jul 2016 19:27:52 +0200
Newsgroups gmane.comp.graphics.ipe.general
Message-ID <1467480472.1835598.655081513.5C7109C2@webmail.messagingengine.com>
On Sat, Jul 2, 2016, at 15:38, Andrew Martchenko wrote:
> The ipelet works quite well, but I would like to improve it by having 
> some of the line segments be selected after the ipelet executes. 
> Specifically in my case, their can be up to four line segments that are 
> tangent to two circles (see attached pdf for example), the segments that 
> cross one another are rarely useful (to me) and I would like them to be 
> selected so that I can easily delete all such segments with a click of a 
> button. Is this something that can be done with an ipelet??

Yes, this can be done, but it's a bit more tricky.

An ipelet is not allowed to simply modify the current page - it must
cooperate with Ipe's undo mechanism.   One way to do this is to call
model:creation, as you do - this will create an entry for the undo stack
that inserts one new object.   You call this several times, which means
that if you try to undo your ipelet's effect, you'll see that you need
to press Ctrl+Z several times to remove the tangents one by one.

I'm attaching a modified version that has a new function create_objects
that shows how to create an undo item for several new objects.   It
selects only the ones that have the "select" attribute set.

By the way, there seems to be a problem when you make two disks with the
same radius and centers at equal y-coordinate:  the outer tangents are
wrong.

Cheers,
 Otfried

_______________________________________________
Ipe-discuss mailing list
Ipe-discuss-rGrgPyRx506NN8uzcEdRPYRWq/[email protected]
http://lists.science.uu.nl/mailman/listinfo/ipe-discuss
tangentlines.lua (text/x-lua, 5.7 KB)
----------------------------------------------------------------------
-- Tangent Lines ipelet
----------------------------------------------------------------------
--[[

   This file is part of the extensible drawing editor Ipe.
   Copyright (C) 1993-2016  Otfried Cheong

   Ipe is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   As a special exception, you have permission to link Ipe with the
   CGAL library and distribute executables, as long as you follow the
   requirements of the Gnu General Public License in regard to all of
   the software in the executable aside from CGAL.

   Ipe is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
   License for more details.

   You should have received a copy of the GNU General Public License
   along with Ipe; if not, you can find it at
   "http://www.gnu.org/copyleft/gpl.html", or write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   --]]

   label = "Tangent Lines"


   about = [[
Draws tangent segments from a primary selected marker/circle to all other selected markers and circles.
By Andrew Martchenko
]]





function circ_intersect(x1,y1,r1,x2,y2,r2)
   local dx = x2 - x1                          -- difference in x coordinates
   local dy = y2 - y1                          -- difference in y coordinates
   local dc = math.sqrt(dx*dx + dy*dy)         -- distance between centers
   local k = (dc*dc + r1*r1 - r2*r2)/(2*dc)    -- distance from center 1 to line
   -- joining points of intersection
   local p1 = ipe.Vector(x1 + dx*k/dc + (dy/dc)*math.sqrt(r1*r1 - k*k),
			 y1 + dy*k/dc - (dx/dc)*math.sqrt(r1*r1 - k*k))
   --OR
   local p2 = ipe.Vector(x1 + dx*k/dc - (dy/dc)*math.sqrt(r1*r1 - k*k),
			 y1 + dy*k/dc + (dx/dc)*math.sqrt(r1*r1 - k*k))
   return p1, p2
end

function create_objects(model, objects)
  local t = { label="tangent segments", 
	      pno=model.pno, 
	      vno=model.vno, 
	      layer=model:page():active(model.vno), 
	      objects=objects
	    }
  t.undo = function (t, doc) 
	     for i = 1,#t.objects do
	       doc[t.pno]:remove(#doc[t.pno]) 
	     end
	   end
  t.redo = function (t, doc) 
	     doc[t.pno]:deselectAll()
	     for _,obj in ipairs(t.objects) do
	       doc[t.pno]:insert(nil, obj.obj, obj.select, t.layer) 
	     end
	     doc[t.pno]:ensurePrimarySelection()
	   end
  model:register(t)
end

function create_segment(model, seg)
   local shape = {type="curve", closed=false;
		  {type="segment"; seg.from, seg.to}}
   return ipe.Path(model.attributes,  {shape} )
end


function get_circ(obj)
   if obj:type()=="path" then
      if obj:shape()[1].type == "ellipse" then
	 local circ = obj:matrix()*obj:shape()[1][1]
	 return ipe.Vector(circ:elements()[5],circ:elements()[6]),circ:elements()[1]
      end
   end
end


function get_mark(obj)
  if obj:type()=="reference" then
    return obj:matrix()*obj:position()
  end
end

function mark_circ_tangent_points(m,c,r)
   local d = c-m
   local r2 = math.sqrt(d:len()*d:len() - r*r)
   if(d:len()<=r) then return end -- there are no tagent lines in this case

   return circ_intersect(m.x, m.y, r2, c.x, c.y, r)
end

function print_selection_warning(model)
   model:warning("Must select either:\nA marker and circle\nOR\nTwo circles")
end

function run(model)


   local c1,r1,c2,r2,p1,p2
   local p = model:page()
   local prim = p:primarySelection()
   if not prim then print_selection_warning(model) return end
   local obj = p[prim]
   local pc=nil -- primary circle center, radius, marker
   local pr=nil
   local pm=nil
   
   pc,pr = get_circ(obj)
   pm = get_mark(obj)

   local segs = {}


   for i, obj, sel, layer in p:objects() do

      c=nil
      r=nil
      m=nil
      if sel and i~=prim then




	 c,r = get_circ(obj)
	 m = get_mark(obj)

	 if pm and m  then
	    segs[#segs+1] = {from=pm,to=m,selected=false}
	    
	 elseif pm and c then
	    p1,p2 = mark_circ_tangent_points(pm,c,r)
	    if(p1) then -- if there are tangent point
	       segs[#segs+1] = {from=pm,to=p1,selected=false}
	       segs[#segs+1] = {from=pm,to=p2,selected=false}
	    end

	 elseif pc and m then
	    p1,p2 = mark_circ_tangent_points(m,pc,pr)
	    if(p1) then -- if there are tangent point
	       segs[#segs+1] = {from=p1,to=m,selected=false}
	       segs[#segs+1] = {from=p2,to=m,selected=false}
	    end
	    
	 elseif pc and c then

	    
	    
	    if pr < r then
	       c1,r1=pc,pr
	       c2,r2=c,r
	    else
	       c2,r2=pc,pr
	       c1,r1=c,r
	    end


	        p1,p2 = mark_circ_tangent_points(c1,c2,r2-r1)

	       if(p1) then 
		  local v1 = p1-c2
		  local v2 = p2-c2
		  v1 = v1:normalized()*r1
		  v2 = v2:normalized()*r1
		  p1 = p1+v1
		  p2 = p2+v2
		  local p3=c1+v1
		  local p4=c1+v2
		  -- outer tangent segments
		  segs[#segs+1] = {from=p1,to=p3,selected=false}
		  segs[#segs+1] = {from=p2,to=p4,selected=false}

		  p1,p2 = mark_circ_tangent_points(c1,c2,r2+r1)
		  if(p1) then 
		     v1 = c2-p1
		     v2 = c2-p2
		     v1 = v1:normalized()*r1
		     v2 = v2:normalized()*r1
		     p1 = p1+v1
		     p2 = p2+v2
		     p3=c1+v1
		     p4=c1+v2
		     -- inner/crisscross tangent segments
		     segs[#segs+1] = {from=p1,to=p3,selected=true}
		     segs[#segs+1] = {from=p2,to=p4,selected=true}
		  end
	       end
	    end

	    
	 end
      end


   local segments = {}
   for i,seg in ipairs(segs) do
     segments[#segments+1] = { obj = create_segment(model, seg),
			       select = seg.selected and 2 or nil }
   end
   create_objects(model, segments)
      
end

shortcuts.ipelet_1_tangentlines = "Alt+t"