### Fillet Tool
#
module Tools2D
class FilletTool2D

def db(string)
  dir=File.dirname(__FILE__) ### adjust folder to suit tool
  toolname="2Dtools"  ### adjust xxx to suit tool...
  locale=Sketchup.get_locale.upcase
  path=File.join(dir, toolname+locale+".lingvo")
  unless File.exist?(path)
     return string
  else
    deBabelizer(string,path)
  end 
end#def

def getExtents
	bbox=Sketchup.active_model.bounds
	bbox.add(@ip.position)if @ip and @ip.valid?
	bbox.add(@ip1.position)if @ip1 and @ip1.valid?
	bbox.add(@ip2.position)if @ip2 and @ip2.valid?
	bbox.add(@ip3.position)if @ip3 and @ip3.valid?
	return bbox
end

@@cursor=nil

def initialize()
	@ip = nil
	@ip1 = nil
	@ip2 = nil
	@xdown = 0
	@ydown = 0
	@chamfers=[0.0,0.0] if ! @chamfers
	@radius=100.0.mm if ! @radius
	@segs=18 if ! @segs
	@@cursor=nil
	curpath=File.join(File.dirname(__FILE__), "Icons", "2Dfillet_cursor.png")
	@@cursor=UI::create_cursor(curpath, 0, 31) if File.exist?(curpath)
end

def activate
	### get/set reference plane 'z'
	@z=Sketchup.active_model.get_attribute("2Dtools","z",nil)
	Sketchup.active_model.set_attribute("2Dtools","z",0.0.mm)if not @z
	@z=Sketchup.active_model.get_attribute("2Dtools","z",nil)
	###
	@ip1 = Sketchup::InputPoint.new
	@ip2 = Sketchup::InputPoint.new
	@ip = Sketchup::InputPoint.new
	@alt=false
	### get/set radius/chamfer
	@radius=Sketchup.active_model.get_attribute("2Dtools","fillet_radius",0.0.mm)
	@chamfers=Sketchup.active_model.get_attribute("2Dtools","fillet_chamfers",[0.mm,0.mm])
	@segs=Sketchup.active_model.get_attribute("2Dtools","fillet_segments",18)
	@drawn = false
	@state=0
	@edge1=nil
	@edge2=nil
	self.reset(nil)
end

def deactivate(view)
	view.invalidate if @drawn && view
	Sketchup::set_status_text("",SB_PROMPT)
	Sketchup::set_status_text("",SB_VCB_LABEL)
	Sketchup::set_status_text("",SB_VCB_VALUE)
end

def onSetCursor()
   #return nil if RUBY_PLATFORM =~ /darwin/
	UI::set_cursor(@@cursor)if @@cursor
end

def onReturn(view)
	view.invalidate
	Sketchup::set_status_text("",SB_PROMPT)
	Sketchup.send_action("selectSelectionTool:")
end

def onMouseMove(flags, x, y, view)
	###
	ph = view.pick_helper
	ph.do_pick(x,y)
	@poss_edge = ph.picked_edge
	###
  if @state==0
    if @alt ### chamfer
      Sketchup::set_status_text((db("2D Chamfer: Pick First Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)
      Sketchup.set_status_text((db("Chamfer(s)")), SB_VCB_LABEL)
      if @chamfers[0]==@chamfers[1]
        Sketchup.set_status_text(@chamfers[0].to_s,SB_VCB_VALUE)
      else
        Sketchup.set_status_text(@chamfers[0].to_s+":"+@chamfers[1].to_s,SB_VCB_VALUE)
      end#if
    else ### fillet
      Sketchup::set_status_text((db("2D Fillet: Pick First Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)
      Sketchup.set_status_text((db("Fillet Radius")), SB_VCB_LABEL)
      Sketchup.set_status_text(@radius.to_s,SB_VCB_VALUE)
    end#if
    @ip.pick view, x, y
    if @ip != @ip1
        view.invalidate if @ip.display? || @ip1.display?
        @ip1.copy! @ip
        view.tooltip = db("Line 1")
    end#if
  else ###state==1
    if @alt
      Sketchup::set_status_text((db("2D Chamfer: Pick Second Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)
      Sketchup.set_status_text((db("Chamfer(s)")), SB_VCB_LABEL)
      if @chamfers[0]==@chamfers[1]
        Sketchup.set_status_text(@chamfers[0].to_s,SB_VCB_VALUE)
      else
        Sketchup.set_status_text(@chamfers[0].to_s+":"+@chamfers[1].to_s,SB_VCB_VALUE)
      end#if
    else
      Sketchup::set_status_text((db("2D Fillet: Pick Second Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)
      Sketchup.set_status_text((db("Fillet Radius")), SB_VCB_LABEL)
      Sketchup.set_status_text(@radius.to_s,SB_VCB_VALUE)
    end#if
    @ip.pick view, x, y
    if @ip != @ip2
        view.invalidate if @ip.display? || @ip2.display?
        @ip2.copy! @ip
        view.tooltip = db("Line 2")
    end#if
  end#if
	@started=true
end

def onLButtonDown(flags, x, y, view)
  view.invalidate
	if @state==0
    @ip1.pick(view, x, y)
    if @ip1.valid?
        p1=@ip1.position;p1.z=@z
        Sketchup::set_status_text((db("2D Chamfer: Pick Second Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)if @alt
        Sketchup::set_status_text((db("2D Fillet: Pick Second Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)if not @alt
        @xdown = x
        @ydown = y
    end
    ph=view.pick_helper
    ph.do_pick(x,y)
    picked_edge=ph.picked_edge
    @edge1=nil
    @edge1=picked_edge if picked_edge
    if @edge1
      view.model.selection.clear
      view.model.selection.add(@edge1)
      @state=1
    end#if
  else ### state==2
    if @ip2.valid?
      @xdown = x
      @ydown = y
    end#if
    @ip2.pick(view, x, y)
    ph=view.pick_helper
    ph.do_pick(x,y)
    picked_edge=ph.picked_edge
    @edge2=nil
    @edge2=picked_edge if picked_edge
    if @edge2
      crosspoint=nil
      crosspoint=Geom.intersect_line_line(@edge1.line,@edge2.line)if @edge1.valid? and @edge2.valid?
      if not crosspoint
        UI.beep
        puts db("No intersection!")
        @edge2=nil
        @ip2.clear
      end#if
      if @edge1 and @edge2 and @edge1.valid? and @edge2.valid? and @edge2!=@edge1
        self.create_geometry(view)
      elsif @edge1 and @edge2 and @edge1.valid? and @edge2.valid? and @edge2==@edge1
        UI.beep
        puts db("Line 2 CANNOT == Line 1 !")
        @edge2=nil
        @ip2.clear
      elsif @edge1 and @edge2 and (not @edge1.valid? or not @edge2.valid?)
        UI.beep
        puts db("Picked Edge(s) Error !")
        @edge2=nil
        @ip2.clear
      end#if
    end#if
  end#if
	view.invalidate
end

def onLButtonUp(flags, x, y, view)
   ###
end

def onKeyDown(key, repeat, flags, view)
  if key==VK_ALT
		if @alt
			@alt=false
			Sketchup.set_status_text((db("Fillet Radius")), SB_VCB_LABEL)
			Sketchup::set_status_text((db("2D Fillet: Pick First Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)if @state==0
			Sketchup::set_status_text((db("2D Fillet: Pick Second Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)if @state==1
			Sketchup.set_status_text(@radius.to_s,SB_VCB_VALUE)
		else
			@alt=true
			Sketchup.set_status_text((db("Chamfer(s)")), SB_VCB_LABEL)
			Sketchup::set_status_text((db("2D Chamfer: Pick First Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)if @state==0
			Sketchup::set_status_text((db("2D Chamfer: Pick Second Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)if @state==1
			if @chamfers[0]==@chamfers[1]
				Sketchup.set_status_text(@chamfers[0].to_s,SB_VCB_VALUE)
			else
				Sketchup.set_status_text(@chamfers[0].to_s+":"+@chamfers[1].to_s,SB_VCB_VALUE)
			end#if
		end#if
	end#if
  ### ALT toggles fillet / chamfer
end

def onKeyUp(key, repeat, flags, view)
  ### nothing
end

def enableVCB?
  return true
end

def onUserText(text, view)
  if @alt ### =chamfered
    Sketchup.set_status_text((db("Chamfer(s)")), SB_VCB_LABEL)
    @chamfers=text.split(":")
    if @chamfers.last!=text ###e.g. 100:200
      if not @chamfers[0]
        @chamfers=[0.0,0.0]
      elsif @chamfers[0]
        @chamfers[0]=@chamfers[0].to_l
        if @chamfers[1]
          @chamfers[1]=@chamfers[1].to_l
        else
          @chamfers[1]=0.0
        end#if
      end#if
    else
      @chamfers=[text.to_l ,text.to_l]### both ==
    end#if
    Sketchup.active_model.set_attribute("2Dtools","fillet_chamfers",@chamfers)
    if @chamfers[0]==@chamfers[1]
      Sketchup.set_status_text(@chamfers[0].to_s,SB_VCB_VALUE)
    else
      Sketchup.set_status_text(@chamfers[0].to_s+":"+@chamfers[1].to_s,SB_VCB_VALUE)
    end#if
  else ### it's a radius fillet
    Sketchup.set_status_text((db("Fillet Radius")), SB_VCB_LABEL)
    ### check if ends with 's' and set segs
    if text.split("").last=="s" ### e.g. 12s
      @lastseg = @segs
      @segs = text.to_i 
      if @segs < 2 or @segs > 999
        UI.beep 
        @segs = @lastseg 
      end
      Sketchup.set_status_text(@segs.to_s,SB_VCB_VALUE)
    else ### it's a radius
      @radius=text.to_l if text
      Sketchup.set_status_text(@radius.to_s,SB_VCB_VALUE)
      Sketchup.active_model.set_attribute("2Dtools","fillet_radius",@radius)
      Sketchup.active_model.set_attribute("2Dtools","fillet_segments",@segs)
    end#if
  end#if
  view.invalidate
end

def draw(view)
	if @poss_edge && @poss_edge.valid? && @started
		pt1 = @poss_edge.start.position
		pt2 = @poss_edge.end.position
		view.drawing_color = "darkorange"
		view.line_width = 7
		view.draw_line(pt1, pt2)
	end
	if @edge1 && @edge1.valid?
		p1 = @edge1.start.position
		p2 = @edge1.end.position
		view.drawing_color = "magenta"
		view.line_width = 7
		view.draw_line(p1, p2)
	end
end

def onCancel(flag, view)
	view.invalidate if @drawn
	view.model.selection.clear
	self.reset(view)
end

def reset(view)
	if @alt
		Sketchup::set_status_text((db("2D Chamfer: Pick First Line / Enter Chamfer(s) / 'Alt' for Fillet...")), SB_PROMPT)
		Sketchup.set_status_text((db("Chamfer(s)")), SB_VCB_LABEL)
	else
		Sketchup::set_status_text((db("2D Fillet: Pick First Line / Enter Fillet Radius or NNs Segments / 'Alt' for Chamfer...")), SB_PROMPT)
		Sketchup.set_status_text((db("Fillet Radius")), SB_VCB_LABEL)
	end#if
	@ip.clear
	@ip1.clear
	@ip2.clear
	@drawn=false
	@state=0
	@edge1=nil
	@edge2=nil
	@started=false
	if view 
		view.tooltip = nil
		view.invalidate
	end
end

def create_geometry(view)
	model=view.model
	model.selection.clear
	model.start_operation(db("2D Chamfer"), true) if @alt
	model.start_operation(db("2D Fillet"), true)  if ! @alt
	### using @edge1/@edge2 and @ip1/@ip2
	ents=model.active_entities
	### check intersection
	crosspoint=nil
	crosspoint=Geom.intersect_line_line(@edge1.line,@edge2.line) if @edge1.valid? && @edge2.valid?
	unless crosspoint
		UI.beep
		puts db("No intersection!")
		return nil
	end#if
	###
	if @alt ### ==chamfer
		e1=@edge1.vertices.sort_by{|i|i.position.distance crosspoint}.reverse[0].position
		ee1=@edge1.vertices.sort_by{|i|i.position.distance crosspoint}.reverse[1].position
		p1=e1.offset((ee1-e1),(e1.distance(crosspoint))-@chamfers[0])
		e2=@edge2.vertices.sort_by{|i|i.position.distance crosspoint}.reverse[0].position
		ee2=@edge2.vertices.sort_by{|i|i.position.distance crosspoint}.reverse[1].position
		p2=e2.offset((ee2-e2),(e2.distance(crosspoint))-@chamfers[1])
		@edge1.erase! if @edge1.valid? && ! @edge1.faces[0]
		@edge2.erase! if @edge2.valid? && ! @edge2.faces[0]
		ed1=ents.add_line(p1,e1)
		ed2=ents.add_line(p2,e2)
		ed1x=ents.add_line(p1,ee1)if e1.distance(p1)<e1.distance(ee1)
		ed2x=ents.add_line(p2,ee2)if e2.distance(p2)<e2.distance(ee2)
		### trim edges if got only one face and its 'common'
		if @chamfers[0]!=0
			ce=ents.add_line(p1,p2)
			ed1xfaces=[];ed1xfaces=ed1x.faces if ed1x
			ed2xfaces=[];ed2xfaces=ed2x.faces if ed2x
			crosspt=false;crosspt=true if e1==e2 || ee1==ee2 || e1==ee2 || ee1==e2
			togo=[]
			togo<<ed1x if ed1x && ed1x.valid? && ed1xfaces[0] && ! ed1xfaces[1] && ed2xfaces.include?(ed1xfaces[0]) && crosspt
			togo<<ed2x if ed2x && ed2x.valid? && ed2xfaces[0] && ! ed2xfaces[1] && ed1xfaces.include?(ed2xfaces[0]) && crosspt
			togo<<ed1x if ed1x && ! ed1x.faces[0]
			togo<<ed2x if ed2x && ! ed2x.faces[0]
			togo.uniq!
			togo.each{|e|e.erase! if e.valid?}
		end#if
		###
	else ### ==radiused fillet
		###
		e1=@edge1.vertices.sort_by{|i|i.position.distance(crosspoint)}.reverse[0].position
		ee1=@edge1.vertices.sort_by{|i|i.position.distance(crosspoint)}.reverse[1].position
		v1=e1-crosspoint
		e2=@edge2.vertices.sort_by{|i|i.position.distance(crosspoint)}.reverse[0].position
		ee2=@edge2.vertices.sort_by{|i|i.position.distance(crosspoint)}.reverse[1].position
		v2=e2-crosspoint
		v3=Geom::Vector3d.linear_combination(0.5,v1.normalize,0.5,v2.normalize)
		len5=(@radius/Math.sin(v2.angle_between(v1)/2))
		len4=(@radius/Math.tan(v2.angle_between(v1)/2))
		v1.length=len4
		v2.length=len4
		v3.length=len5
		p1=crosspoint.offset(v1)
		p2=crosspoint.offset(v2)
		p3=crosspoint.offset(v3)
		if @edge1.faces[0]
			faced1 = true
		else
			#@edge1.erase!
			faced1 = false
		end
		if @edge2.faces[0]
			faced2 = true
		else
			#@edge2.erase!
			faced2 = false
		end
		ed1=ents.add_line(p1,e1)
		ed2=ents.add_line(p2,e2)
		ed1x=ents.add_line(p1,ee1) if e1.distance(p1) < e1.distance(ee1)
		ed2x=ents.add_line(p2,ee2) if e2.distance(p2) < e2.distance(ee2)
		### make arc and trim edges if got only one face and its 'common'
		if @radius != 0
			center=p3
			xaxis=v3.reverse.normalize
			normal=(v1*v2).normalize
			radius=@radius
			sangle=  1*(v3.angle_between(p3-p1))
			eangle= -1*(v3.angle_between(p3-p1))
			segs=@segs
			###
			ents.add_cpoint(center) ### add arc centerpoint
			###
			### INtersect and keep ARC == WTF !?
			if faced1 && faced2
				arcedges = ents.add_arc(center, xaxis, normal, radius, sangle, eangle, segs)
				gp1 = ents.add_group(arcedges)
				gp1.explode
			else ### fillet is not on a face
				### remember existing faces
				xfaces = ents.grep(Sketchup::Face)
				### make temp face & edge
				apex = center.offset(v3.reverse, len5)
				tface = ents.add_face(p1, p2, apex)
				tedge = ents.add_line(p1, p2)
				###
				arcedges = ents.add_arc(center, xaxis, normal, radius, sangle, eangle, segs)
				gp1 = ents.add_group(arcedges)
				gp1.explode
				### tidy up temp face[s] etc
				cfaces = ents.grep(Sketchup::Face)
				nfaces = cfaces - xfaces ### new faces
				nfaces.each{|f|
					next unless f.valid?
					es = f.edges - arcedges
					f.erase!
					es.each{|e|
						next unless e.valid?
						next if arcedges.include?(e)
						next if e.curve ### part of fillet
						next if e.faces[0]
						e.erase!
					}
				}
				tface.erase! if tface && tface.valid? ### fallback position
				tedge.erase! if tedge && tedge.valid? ### fallback position
				###
			end
			### now tidy any loose edges...
			ed1x=ents.add_line(p1, ee1) if e1.distance(p1) < e1.distance(ee1)
			ed2x=ents.add_line(p2, ee2) if e2.distance(p2) < e2.distance(ee2)
			ed1xfaces=[]
			ed1xfaces=ed1x.faces if ed1x && ed1x.valid?
			ed2xfaces=[]
			ed2xfaces=ed2x.faces if ed2x && ed2x.valid?
			crosspt=false
			crosspt=true if e1==e2 || ee1==ee2 || e1==ee2 || ee1==e2
			###
			togos=[]
			togos << ed1x if ed1x && ed1x.valid? && ed1xfaces[0] && ed1xfaces[0].valid? && ! ed1xfaces[1] && ed2xfaces.include?(ed1xfaces[0]) && crosspt
			togos << ed2x if ed2x && ed2x.valid? && ed2xfaces[0] && ed2xfaces[0].valid? && ! ed2xfaces[1] && ed1xfaces.include?(ed2xfaces[0]) && crosspt
			togos << ed1x if ed1x && ed1x.valid? && ! ed1x.faces[0]
			togos << ed2x if ed2x && ed2x.valid? && ! ed2x.faces[0]
			togos.uniq!
			ents.erase_entities(togos) if togos[0]
			###
		end#if radius!=0
	end#if
	###
	view.invalidate
	model.commit_operation
	reset(view)
end

end # class
end#mod
=begin
#-----------------------------------------------------------------------------
# shortcut
def fillettool2D
    Sketchup.active_model.select_tool FilletTool2D.new
end
######-----------------------------------------------
=end
