#  JHS POWERBAR 2015   =  Credits Info at bottom of script   -   Max Coppoletta , Feb. 2015


require 'sketchup'

module Max_Coppoletta
	module JHS_powerbar

$jhs_memory_selectionx = []

module JHS_plode
def self.plode()
m=Sketchup.active_model
m.start_operation "Explode Selected Curves"
m=Sketchup.active_model
s=m.selection
e=m.entities
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Face}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Group}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::ComponentInstance}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Image}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::SectionPlane}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Text}
s.to_a.each { |ent| s.remove ent if ent.typename=="ConstructionLine"}
s.to_a.each { |ent| s.remove ent if ent.typename=="ConstructionPoint"}
s.to_a.each { |ent| s.remove ent if ent.typename=="DimensionLinear" }
s.to_a.each { |ent| s.remove ent if ent.typename=="DimensionRadial" }
s.each { |curve| curve.explode_curve}
s.clear
m.commit_operation
end


def self.div()
m=Sketchup.active_model
m.start_operation "Divide Selected Edges"
m=Sketchup.active_model
s=m.selection
e=m.entities
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Face}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Group}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::ComponentInstance}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Image}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::SectionPlane}
s.to_a.each { |ent| s.remove ent if ent.is_a? Sketchup::Text}
s.to_a.each { |ent| s.remove ent if ent.typename=="ConstructionLine"}
s.to_a.each { |ent| s.remove ent if ent.typename=="ConstructionPoint"}
s.to_a.each { |ent| s.remove ent if ent.typename=="DimensionLinear" }
s.to_a.each { |ent| s.remove ent if ent.typename=="DimensionRadial" }
s.each { |curve| curve.explode_curve}
s.each { |curve| curve.split 0.5}
m.commit_operation
end










end#mod
















=begin                                           RANDOR, ROTIX MIX - TBD CADFATHER
=end

module JHS_sr



def self.defz
# set defaults
@dir=File.dirname(__FILE__)
		@scalerotate = 	@dir+"/settings/scale_rotate.ini"

			if not File.exist? (@scalerotate)
								open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts   "1.18";  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
			else
							@scalemin = IO.readlines(@scalerotate)[0]
							if @scalemin ==nil ; open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end   ; end
							@scalemax = IO.readlines(@scalerotate)[1]
							if @scalemax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "1.18"  ;  end   ; end
							@rotmin = IO.readlines(@scalerotate)[2]
							if @rotmin ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end   ; end
							@rotmax = IO.readlines(@scalerotate)[3]
							if @rotmax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end   ; end

							@axr = IO.readlines(@scalerotate)[4]
							if @axr ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
							@axs = IO.readlines(@scalerotate)[5]
							if @axs ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
			end

	      @scalemin = IO.readlines(@scalerotate)[0].strip ; puts @scalemin
	      @scalemax = IO.readlines(@scalerotate)[1].strip ; puts @scalemax
	      @rotmin = IO.readlines(@scalerotate)[2].strip ; puts @rotmin
	      @rotmax = IO.readlines(@scalerotate)[3].strip ; puts @rotmax
	      @axr = IO.readlines(@scalerotate)[4].strip ; puts @axr	+ "   from file"
	      @axs = IO.readlines(@scalerotate)[5].strip ; puts @axs	+ "   from file"

			if @axr=="BC"
				@rotaxis = "Bottom Centre"
			else
				@rotaxis = "Object Axis"
			end

			if @axs=="BC"
				@scaxis = "Bottom Centre"
			else
				@scaxis = "Object Axis"
			end

	      @min_scale =@scalemin.to_f * 1000
	      @max_scale = @scalemax.to_f * 1000
	      @scale_frame = @max_scale - @min_scale
	      @min_rotate = @rotmin.to_f
	      @max_rotate = @rotmax.to_f
	      @rotate_frame = @max_rotate - @min_rotate

end#defs



def self.settingz

	model=Sketchup.active_model
	ents = Sketchup.active_model.selection

      retrymenu = 1
		  while retrymenu == 1
		  	prompts = ['Min Scale', 'Max Scale', 'Min Angle', 'Max Angle', 'Rotation Pivot', 'Scale Pivot']
			defaults = ["#{@scalemin}", "#{@scalemax}", "#{@rotmin}", "#{@rotmax}", "#{@rotaxis}", "#{@scaxis}"]
			axmenu=["","","","", "Bottom Centre|Object Axis","Bottom Centre|Object Axis"]
			 results = UI.inputbox(prompts, defaults, axmenu, 'Random Scale and Rotate')
						if results
								if results[0] > results[1]   ||  results[2] > results[3]
									retrymenu = 1
									UI.messagebox "Min cannot be higher than Max!"
								else
									retrymenu = 0
								end

						  @min_scale = results[0].to_f * 1000
						  @max_scale = results[1].to_f * 1000
						  @scale_frame = @max_scale - @min_scale
						  @min_rotate = results[2].to_f
						  @max_rotate = results[3].to_f
						  @rotate_frame = @max_rotate - @min_rotate

						  		open(@scalerotate, 'w') do |f|  ;  f.puts results[0]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[1]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[2]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[3]  ;  end

								if results[4]=="Bottom Centre"
									@axr = "BC"
								else
									@axr = "OA"
								end

								if results[5]=="Bottom Centre"
									@axs = "BC"
								else
									@axs = "OA"
								end

								open(@scalerotate, 'a') do |f|  ;  f.puts @axr  ;  end		#; puts @axr
								open(@scalerotate, 'a') do |f|  ;  f.puts @axs  ;  end		#; puts @axs
										@rotaxis = results[4]  #; puts @rotaxis
										@scaxis = results[5]	  #; puts @scaxis
					else return nil
					end
			  end
end  # settings


    #  scale
def self.scale(ent)

		if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)
				if @axs == "OA"
					  pt = ent.transformation.origin
				else
					  pt = ent.bounds.center
				end
					   p size = (rand(@scale_frame) + (@min_scale) ) / 1000.0
					  tr = Geom::Transformation.scaling pt, 1, 1, size
					  Sketchup.active_model.active_entities.transform_entities(tr,ent)
		  end#if

end#def



      #  rotate
def self.rotate(ent, angle)

		if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)
			if @axr == "OA"
				  pt = ent.transformation.origin
			else
				  pt = ent.bounds.center
			end
				  v = Geom::Vector3d.new(0,0,1)
				  tr = Geom::Transformation.rotation(pt,v,angle.degrees)
				  Sketchup.active_model.active_entities.transform_entities(tr,ent)
		end#if

end


    #  scale only routine
def self.gos
   		self.defz
      Sketchup.active_model.start_operation "Scale Only"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
         self.scale(e)
      end
      Sketchup.active_model.commit_operation
	  		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end


     #  rotate only routine
def self.gor
   		self.defz
      Sketchup.active_model.start_operation "Rotate Only"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
		     self.rotate(e,rand(@rotate_frame))
      end
      Sketchup.active_model.commit_operation
	  		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end


    #  rotate and scale routine
def self.gosr
   		self.defz
      Sketchup.active_model.start_operation "Scale-Rotate"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
         self.scale(e)
		     self.rotate(e,rand(@rotate_frame))
      end
      Sketchup.active_model.commit_operation
	  		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end



 #--------------------------------------------------------------------------------------------



class Rotixscale   #   SCALE ONLY

def activate
      @entities = []
      # show VCB and status info
      Sketchup::set_status_text("Click to Scale   (ALT = Settings)", SB_PROMPT)
      @add_selection = 0

	  @dir=File.dirname(__FILE__)
		@scalerotate = 	@dir+"/settings/scale_rotate.ini"

			if not File.exist? (@scalerotate)
								open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts   "1.18";  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
			else
							@scalemin = IO.readlines(@scalerotate)[0]
							if @scalemin ==nil ; open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end   ; end
							@scalemax = IO.readlines(@scalerotate)[1]
							if @scalemax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "1.18"  ;  end   ; end
							@rotmin = IO.readlines(@scalerotate)[2]
							if @rotmin ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end   ; end
							@rotmax = IO.readlines(@scalerotate)[3]
							if @rotmax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end   ; end

							@axr = IO.readlines(@scalerotate)[4]
							if @axr ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
							@axs = IO.readlines(@scalerotate)[5]
							if @axs ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
			end

	      @scalemin = IO.readlines(@scalerotate)[0].strip
	      @scalemax = IO.readlines(@scalerotate)[1].strip
	      @rotmin = IO.readlines(@scalerotate)[2].strip
	      @rotmax = IO.readlines(@scalerotate)[3].strip
	      @axr = IO.readlines(@scalerotate)[4].strip
	      @axs = IO.readlines(@scalerotate)[5].strip

			if @axr=="BC"
				@rotaxis = "Bottom Centre"
			else
				@rotaxis = "Object Axis"
			end

			if @axs=="BC"
				@scaxis = "Bottom Centre"
			else
				@scaxis = "Object Axis"
			end

	      @min_scale =@scalemin.to_f * 1000
	      @max_scale = @scalemax.to_f * 1000
	      @scale_frame = @max_scale - @min_scale
	      @min_rotate = @rotmin.to_f
	      @max_rotate = @rotmax.to_f
	      @rotate_frame = @max_rotate - @min_rotate

end#def



def settingz_s

	model=Sketchup.active_model
	ents = Sketchup.active_model.selection

      retrymenu = 1
		  while retrymenu == 1
		  	prompts = ['Min Scale', 'Max Scale', 'Min Angle', 'Max Angle', 'Rotation Pivot', 'Scale Pivot']
			defaults = ["#{@scalemin}", "#{@scalemax}", "#{@rotmin}", "#{@rotmax}", "#{@rotaxis}", "#{@scaxis}"]
			axmenu=["","","","", "Bottom Centre|Object Axis","Bottom Centre|Object Axis"]
			 results = UI.inputbox(prompts, defaults, axmenu, 'Random Scale and Rotate')
						if results
								if results[0] > results[1]   ||  results[2] > results[3]
									retrymenu = 1
									UI.messagebox "Min cannot be higher than Max!"
								else
									retrymenu = 0
								end

						  @min_scale = results[0].to_f * 1000
						  @max_scale = results[1].to_f * 1000
						  @scale_frame = @max_scale - @min_scale
						  @min_rotate = results[2].to_f
						  @max_rotate = results[3].to_f
						  @rotate_frame = @max_rotate - @min_rotate

						  		open(@scalerotate, 'w') do |f|  ;  f.puts results[0]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[1]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[2]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[3]  ;  end

								if results[4]=="Bottom Centre"
									@axr = "BC"
								else
									@axr = "OA"
								end

								if results[5]=="Bottom Centre"
									@axs = "BC"
								else
									@axs = "OA"
								end

								open(@scalerotate, 'a') do |f|  ;  f.puts @axr  ;  end		#; puts @axr
								open(@scalerotate, 'a') do |f|  ;  f.puts @axs  ;  end		#; puts @axs
										@rotaxis = results[4]  #; puts @rotaxis
										@scaxis = results[5]	  #; puts @scaxis
					else return nil
					end
			  end
end  # settings



def rotix_scale(ent)

          min = @min_scale
          max = @max_scale
          range = max - min

		  if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)

					if @axs == "OA"
						  pt = ent.transformation.origin
					else
						  pt = ent.bounds.center
					end
						  p size = (rand(range) + min ) / 1000.0
						  tr = Geom::Transformation.scaling pt, 1, 1, size
						  Sketchup.active_model.active_entities.transform_entities(tr,ent)

		  end#if

end#def


   # on MouseMove perform selection
   def onMouseMove(flags, x, y, view)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # do not add edges/faces to selection
         if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
   end


   def onLButtonDown(flags, x, y, view)
      Sketchup.active_model.start_operation "Randor Scale"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
         rotix_scale(e)
      end
      Sketchup.active_model.commit_operation
  end


   # if SHIFT is released back to normal selection (on mouse hover)
   def onKeyUp(key, repeat, flags, view)
      @add_selection = 0 if key == CONSTRAIN_MODIFIER_KEY
   end


   # process keyboard
   def onKeyDown(key, repeat, flags, view)
      case key
         when CONSTRAIN_MODIFIER_KEY then @add_selection = 1 if (repeat == 1)
			     when VK_ALT then 	 settingz_s
      end
   end


 def deactivate(view)
		view.invalidate
		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)
		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end# deactivate

end # class Rotixscale


####################################################################


class Rotixrotate   #   ROTIX ROTATE ONLY

def activate
      @entities = []
	        @add_selection = 0
      Sketchup::set_status_text("Click to Rotate   (ALT = Settings)", SB_PROMPT)
			@dir=File.dirname(__FILE__)
		@scalerotate = 	@dir+"/settings/scale_rotate.ini"

			if not File.exist? (@scalerotate)
								open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts   "1.18";  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
			else
							@scalemin = IO.readlines(@scalerotate)[0]
							if @scalemin ==nil ; open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end   ; end
							@scalemax = IO.readlines(@scalerotate)[1]
							if @scalemax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "1.18"  ;  end   ; end
							@rotmin = IO.readlines(@scalerotate)[2]
							if @rotmin ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end   ; end
							@rotmax = IO.readlines(@scalerotate)[3]
							if @rotmax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end   ; end

							@axr = IO.readlines(@scalerotate)[4]
							if @axr ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
							@axs = IO.readlines(@scalerotate)[5]
							if @axs ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
			end

	      @scalemin = IO.readlines(@scalerotate)[0].strip
	      @scalemax = IO.readlines(@scalerotate)[1].strip
	      @rotmin = IO.readlines(@scalerotate)[2].strip
	      @rotmax = IO.readlines(@scalerotate)[3].strip
	      @axr = IO.readlines(@scalerotate)[4].strip
	      @axs = IO.readlines(@scalerotate)[5].strip

			if @axr=="BC"
				@rotaxis = "Bottom Centre"
			else
				@rotaxis = "Object Axis"
			end

			if @axs=="BC"
				@scaxis = "Bottom Centre"
			else
				@scaxis = "Object Axis"
			end

	      @min_scale =@scalemin.to_f * 1000
	      @max_scale = @scalemax.to_f * 1000
	      @scale_frame = @max_scale - @min_scale
	      @min_rotate = @rotmin.to_f
	      @max_rotate = @rotmax.to_f
	      @rotate_frame = @max_rotate - @min_rotate

end	#def



def settingz_r

	model=Sketchup.active_model
	ents = Sketchup.active_model.selection

      retrymenu = 1
		  while retrymenu == 1
		  	prompts = ['Min Scale', 'Max Scale', 'Min Angle', 'Max Angle', 'Rotation Pivot', 'Scale Pivot']
			defaults = ["#{@scalemin}", "#{@scalemax}", "#{@rotmin}", "#{@rotmax}", "#{@rotaxis}", "#{@scaxis}"]
			axmenu=["","","","", "Bottom Centre|Object Axis","Bottom Centre|Object Axis"]
			 results = UI.inputbox(prompts, defaults, axmenu, 'Random Scale and Rotate')
						if results
								if results[0] > results[1]   ||  results[2] > results[3]
									retrymenu = 1
									UI.messagebox "Min cannot be higher than Max!"
								else
									retrymenu = 0
								end

						  @min_scale = results[0].to_f * 1000
						  @max_scale = results[1].to_f * 1000
						  @scale_frame = @max_scale - @min_scale
						  @min_rotate = results[2].to_f
						  @max_rotate = results[3].to_f
						  @rotate_frame = @max_rotate - @min_rotate

						  		open(@scalerotate, 'w') do |f|  ;  f.puts results[0]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[1]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[2]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[3]  ;  end

								if results[4]=="Bottom Centre"
									@axr = "BC"
								else
									@axr = "OA"
								end

								if results[5]=="Bottom Centre"
									@axs = "BC"
								else
									@axs = "OA"
								end

								open(@scalerotate, 'a') do |f|  ;  f.puts @axr  ;  end		#; puts @axr
								open(@scalerotate, 'a') do |f|  ;  f.puts @axs  ;  end		#; puts @axs
										@rotaxis = results[4]  #; puts @rotaxis
										@scaxis = results[5]	  #; puts @scaxis
					else return nil
					end
			  end
end  # settings



def rotix_rotate(ent,angle)

		if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)
					if @axr == "OA"
						  @pt = ent.transformation.origin
					else
						  @pt = ent.bounds.center
					end

						  v = Geom::Vector3d.new(0,0,1)
						  tr = Geom::Transformation.rotation(@pt,v,angle.degrees)
						  Sketchup.active_model.active_entities.transform_entities(tr,ent)
		end#if

end#def


   # on MouseMove perform selection
def onMouseMove(flags, x, y, view)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # do not add edges/faces to selection
         if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
end


   # random rotate only
def onLButtonDown(flags, x, y, view)
      Sketchup.active_model.start_operation "Rotate Only"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
		     rotix_rotate(e,rand(360))
      end
      Sketchup.active_model.commit_operation
end




   # if SHIFT is released back to normal selection (on mouse hover)
def onKeyUp(key, repeat, flags, view)
      @add_selection = 0 if key == CONSTRAIN_MODIFIER_KEY
end


   # process keyboard
def onKeyDown(key, repeat, flags, view)
      case key
         when CONSTRAIN_MODIFIER_KEY then @add_selection = 1 if (repeat == 1)
		 when VK_ALT then 	 settingz_r
      end
end


def deactivate(view)
		view.invalidate
		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)
		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end# deactivate


end # class RotixRotate




#############################################################################


class Rotixscalerotate   #   SCALE AND ROTATE

   def activate
      @entities = []
	        @add_selection = 0
      Sketchup::set_status_text("Click to Scale and Rotate   (ALT = Settings)", SB_PROMPT)
			@dir=File.dirname(__FILE__)
		@scalerotate = 	@dir+"/settings/scale_rotate.ini"

			if not File.exist? (@scalerotate)
								open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts   "1.18";  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end
			else
							@scalemin = IO.readlines(@scalerotate)[0]
							if @scalemin ==nil ; open(@scalerotate, 'w') do |f|  ;  f.puts "0.79"  ;  end   ; end
							@scalemax = IO.readlines(@scalerotate)[1]
							if @scalemax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "1.18"  ;  end   ; end
							@rotmin = IO.readlines(@scalerotate)[2]
							if @rotmin ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "0.0"  ;  end   ; end
							@rotmax = IO.readlines(@scalerotate)[3]
							if @rotmax ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "360"  ;  end   ; end

							@axr = IO.readlines(@scalerotate)[4]
							if @axr ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
							@axs = IO.readlines(@scalerotate)[5]
							if @axs ==nil ; open(@scalerotate, 'a') do |f|  ;  f.puts "BC"  ;  end   ; end
			end

	      @scalemin = IO.readlines(@scalerotate)[0].strip
	      @scalemax = IO.readlines(@scalerotate)[1].strip
	      @rotmin = IO.readlines(@scalerotate)[2].strip
	      @rotmax = IO.readlines(@scalerotate)[3].strip
	      @axr = IO.readlines(@scalerotate)[4].strip
	      @axs = IO.readlines(@scalerotate)[5].strip

			if @axr=="BC"
				@rotaxis = "Bottom Centre"
			else
				@rotaxis = "Object Axis"
			end

			if @axs=="BC"
				@scaxis = "Bottom Centre"
			else
				@scaxis = "Object Axis"
			end

	      @min_scale =@scalemin.to_f * 1000
	      @max_scale = @scalemax.to_f * 1000
	      @scale_frame = @max_scale - @min_scale
	      @min_rotate = @rotmin.to_f
	      @max_rotate = @rotmax.to_f
	      @rotate_frame = @max_rotate - @min_rotate

   end



def settingz_sr

	model=Sketchup.active_model
	ents = Sketchup.active_model.selection

      retrymenu = 1
		  while retrymenu == 1
		  	prompts = ['Min Scale', 'Max Scale', 'Min Angle', 'Max Angle', 'Rotation Pivot', 'Scale Pivot']
			defaults = ["#{@scalemin}", "#{@scalemax}", "#{@rotmin}", "#{@rotmax}", "#{@rotaxis}", "#{@scaxis}"]
			axmenu=["","","","", "Bottom Centre|Object Axis","Bottom Centre|Object Axis"]
			 results = UI.inputbox(prompts, defaults, axmenu, 'Random Scale and Rotate')
						if results
								if results[0] > results[1]   ||  results[2] > results[3]
									retrymenu = 1
									UI.messagebox "Min cannot be higher than Max!"
								else
									retrymenu = 0
								end

						  @min_scale = results[0].to_f * 1000
						  @max_scale = results[1].to_f * 1000
						  @scale_frame = @max_scale - @min_scale
						  @min_rotate = results[2].to_f
						  @max_rotate = results[3].to_f
						  @rotate_frame = @max_rotate - @min_rotate

						  		open(@scalerotate, 'w') do |f|  ;  f.puts results[0]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[1]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[2]  ;  end
								open(@scalerotate, 'a') do |f|  ;  f.puts results[3]  ;  end

								if results[4]=="Bottom Centre"
									@axr = "BC"
								else
									@axr = "OA"
								end

								if results[5]=="Bottom Centre"
									@axs = "BC"
								else
									@axs = "OA"
								end

								open(@scalerotate, 'a') do |f|  ;  f.puts @axr  ;  end		#; puts @axr
								open(@scalerotate, 'a') do |f|  ;  f.puts @axs  ;  end		#; puts @axs
										@rotaxis = results[4]  #; puts @rotaxis
										@scaxis = results[5]	  #; puts @scaxis
					else return nil
					end
			  end
end  # settings



def rotix_scale(ent)

          min = @min_scale
          max = @max_scale
          range = max - min

		  if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)

					if @axs == "OA"
						  pt = ent.transformation.origin
					else
						  pt = ent.bounds.center
					end

						  p size = (rand(range) + min ) / 1000.0
						  tr = Geom::Transformation.scaling pt, 1, 1, size
						  Sketchup.active_model.active_entities.transform_entities(tr,ent)
			end

end


   def rotix_rotate(ent,angle)

		if (ent.kind_of? Sketchup::Group) || (ent.kind_of? Sketchup::ComponentInstance) || (ent.kind_of? Sketchup::Image)
						if @axr == "OA"
							  pt = ent.transformation.origin
						else
							  pt = ent.bounds.center
						end

							  v = Geom::Vector3d.new(0,0,1)
							  tr = Geom::Transformation.rotation(pt,v,angle.degrees)
							  Sketchup.active_model.active_entities.transform_entities(tr,ent)
		end

	end



   # on MouseMove perform selection
   def onMouseMove(flags, x, y, view)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # do not add edges/faces to selection
         if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
   end



   # random scale rotate on click
   def onLButtonDown(flags, x, y, view)
      Sketchup.active_model.start_operation "Randor Scale and Rotate"
      ss=s=Sketchup.active_model.selection
      ss.each do |e|
	      rotix_rotate(e,rand(360))
         rotix_scale(e)
      end
      Sketchup.active_model.commit_operation
  end


   # if SHIFT is released back to normal selection (on mouse hover)
   def onKeyUp(key, repeat, flags, view)
      @add_selection = 0 if key == CONSTRAIN_MODIFIER_KEY
   end


   # process keyboard
   def onKeyDown(key, repeat, flags, view)
      case key
         when CONSTRAIN_MODIFIER_KEY then @add_selection = 1 if (repeat == 1)
	     when VK_ALT then 	 settingz_sr
      end
   end



def deactivate(view)
		view.invalidate
		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)
		Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end# deactivate


end # class Rotixscalerotate



#----------------------------------------------------------------------------------------------



# MENU PROCESSES

def self.goscale

Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=true

	    @model=Sketchup.active_model
        @ss=@model.selection
        if @ss.empty?
			Sketchup.active_model.select_tool Rotixscale.new  #---
        end
		begin
		self.gos   # ---
 end
end



def self.gorotate

Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=true

	    @model=Sketchup.active_model
        @ss=@model.selection
        if @ss.empty?
			Sketchup.active_model.select_tool Rotixrotate.new  #---
        end
		begin
		self.gor   # ---
 end
end




def self.goscalerotate

Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=true

	    @model=Sketchup.active_model
        @ss=@model.selection
        if @ss.empty?
			Sketchup.active_model.select_tool Rotixscalerotate.new  #---
        end
		begin

		self.gosr   # ---
 end
end





end    # module JHS_sr




=begin                                            ROTIX - TBD
=end



class Rotix

  # initialize on tool activation
  def activate
	#Sketchup.send_action "viewShowAxes:"
    @add_selection = 0
    @entities = []
    # show VCB and status info
    Sketchup::set_status_text("ARROWS:    LEFT= Cw,    RIGHT= Ccw,    UP= Change Plane,    DOWN= Flip,    ALT=Reverse Direction    CTRL= 45,    SHIFT= 1" , SB_PROMPT)
    Sketchup::set_status_text("Z:Angle", SB_VCB_LABEL)
    @v = Geom::Vector3d.new(0,0,1)
    # default value
    @angle = 15

	@width = Sketchup.active_model.active_view.vpwidth/26
@height = Sketchup.active_model.active_view.vpheight/16
@p1 = 0, 0, 0
@p2 = 100, 0, 0
@p3 = 100, 100, 0
@p4 = 0, 100, 0

#blue
@b1= 50,50, 0
@b2= 86, 66, 0
@b3= 50, 90, 0
@b4= 12, 66, 0

#red
@r1= 5,20,0
@r2= 50, 10, 0
@r3= 50, 50, 0
@r4= 12, 66, 0

#green
@g1= 50,10,0
@g2= 95, 20, 0
@g3= 86, 66, 0
@g4= 50, 50, 0

  end

  def change_axis_up
    if @v.z == 1
      Sketchup::set_status_text("X:Angle", SB_VCB_LABEL)
      @v = Geom::Vector3d.new(1,0,0)
    elsif @v.x == 1
      Sketchup::set_status_text("Y:Angle", SB_VCB_LABEL)
      @v = Geom::Vector3d.new(0,1,0)
    else
      Sketchup::set_status_text("Z:Angle", SB_VCB_LABEL)
      @v = Geom::Vector3d.new(0,0,1)
    end
  end



def draw(view)
model = Sketchup.active_model
view = model.active_view

if @v.z == 1
# BLUE
#blueplane
view.line_width = 3
view.drawing_color = "Blue"
view.draw2d GL_TRIANGLES, [@b1, @b2, @b4]
view.draw2d GL_TRIANGLES, [@b2, @b3, @b4]
# redplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@r1, @r2, @r4]
view.draw2d GL_TRIANGLES, [@r2, @r3, @r4]
# greenplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@g1, @g2, @g4]
view.draw2d GL_TRIANGLES, [@g2, @g3, @g4]
#edge
view.line_width = 1
view.drawing_color = "black"
view.draw2d GL_LINE_LOOP, [@b1, @b2, @b3, @b4]
view.draw2d GL_LINE_LOOP, [@g1, @g2, @g3, @g4]
view.draw2d GL_LINE_LOOP, [@r1, @r2, @r3, @r4]
    elsif @v.x == 1
# RED
#blueplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@b1, @b2, @b4]
view.draw2d GL_TRIANGLES, [@b2, @b3, @b4]
# redplane
view.line_width = 3
view.drawing_color = "red"
view.draw2d GL_TRIANGLES, [@r1, @r2, @r4]
view.draw2d GL_TRIANGLES, [@r2, @r3, @r4]
# greenplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@g1, @g2, @g4]
view.draw2d GL_TRIANGLES, [@g2, @g3, @g4]
#edge
view.line_width = 1
view.drawing_color = "black"
view.draw2d GL_LINE_LOOP, [@b1, @b2, @b3, @b4]
view.draw2d GL_LINE_LOOP, [@g1, @g2, @g3, @g4]
view.draw2d GL_LINE_LOOP, [@r1, @r2, @r3, @r4]
    else
# GREEN
#blueplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@b1, @b2, @b4]
view.draw2d GL_TRIANGLES, [@b2, @b3, @b4]
# redplane
view.line_width = 3
view.drawing_color = "DarkGray"
view.draw2d GL_TRIANGLES, [@r1, @r2, @r4]
view.draw2d GL_TRIANGLES, [@r2, @r3, @r4]
# greenplane
view.line_width = 3
view.drawing_color = "LawnGreen"
view.draw2d GL_TRIANGLES, [@g1, @g2, @g4]
view.draw2d GL_TRIANGLES, [@g2, @g3, @g4]
#edge
view.line_width = 1
view.drawing_color = "black"
view.draw2d GL_LINE_LOOP, [@b1, @b2, @b3, @b4]
view.draw2d GL_LINE_LOOP, [@g1, @g2, @g3, @g4]
view.draw2d GL_LINE_LOOP, [@r1, @r2, @r3, @r4]
end

# frame
view.line_width = 1
view.drawing_color = "black"
view.draw2d GL_LINE_LOOP, [@p1, @p2, @p3, @p4]
end




  #perform rotation
  def rotate(angle)
    Sketchup::set_status_text("#{@angle}", SB_VCB_VALUE)
    ss= Sketchup.active_model.selection
			ss.each do |e|
			  pt = e.bounds.center
			if (e.kind_of? Sketchup::Group) || (e.kind_of? Sketchup::ComponentInstance) || (e.kind_of? Sketchup::Image)
			  tr = Geom::Transformation.rotation(pt,@v,angle.degrees)
			  Sketchup.active_model.active_entities.transform_entities(tr,e)
			end#if
		end#do
  end





  def rotateflip(angle)

		Sketchup::set_status_text("#{@angle}", SB_VCB_VALUE)
		ss= Sketchup.active_model.selection
			if @v.z == 1
					ss.each do |e|
					@p = e.bounds.center
						if (e.kind_of? Sketchup::Group) || (e.kind_of? Sketchup::ComponentInstance) || (e.kind_of? Sketchup::Image)
							tr = Geom::Transformation.scaling( @p, -1, 1, 1 ) # x
							Sketchup.active_model.active_entities.transform_entities(tr,e)
						end#if
					end#do
			elsif @v.x == 1
					ss.each do |e|
					@p = e.bounds.center
						if (e.kind_of? Sketchup::Group) || (e.kind_of? Sketchup::ComponentInstance) || (e.kind_of? Sketchup::Image)
							tr = Geom::Transformation.scaling( @p, 1, -1, 1 ) # y
							Sketchup.active_model.active_entities.transform_entities(tr,e)
						end#if
					end#do
			else
					ss.each do |e|
					@p = e.bounds.center
						if (e.kind_of? Sketchup::Group) || (e.kind_of? Sketchup::ComponentInstance) || (e.kind_of? Sketchup::Image)
							tr = Geom::Transformation.scaling( @p, 1, 1, -1 ) # z
							Sketchup.active_model.active_entities.transform_entities(tr,e)
						end#if
					end#do
			end
  end


def cuber
end




  # rotate on click
  def onLButtonDown(flags, x, y, view)
    rotate(-@angle)
    # update the VCB value
    Sketchup::set_status_text("#{@angle}", SB_VCB_VALUE)

    ph = view.pick_helper
    found = ph.do_pick(x,y)
    picked = ph.best_picked

    if (picked != nil)
      # do not add edges/faces to selection
      if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
	Sketchup.active_model.selection.add(picked) if (picked != @entities)
	@entities = picked
      end
    end
  end


  # if SHIFT is released back to normal selection (on mouse hover)
  def onKeyUp(key, repeat, flags, view)

	@angle = 15 if key == CONSTRAIN_MODIFIER_KEY
	@angle = 15 if key == VK_CONTROL
	@angle = 15 if key == VK_ALT

  end

  # process keyboard
  def onKeyDown(key, repeat, flags, view)
    case key
    when VK_LEFT then rotate(@angle)
    when VK_RIGHT then rotate(-@angle)
    when VK_UP then  change_axis_up  ; view.refresh  # red
    when VK_DOWN then  self.rotateflip 180
    when VK_ALT then @angle = -@angle
    when VK_CONTROL then @angle =45
    when CONSTRAIN_MODIFIER_KEY then @angle = 1
    end
  end



def onMouseMove(flags, x, y, view)
view.invalidate
end



  # on user text in VCB modify angle
  def onUserText(text,view)
    begin
      value = text.to_i
    rescue
      # Error parsing the text
      UI.beep
      value = nil
      Sketchup::set_status_text "", SB_VCB_VALUE
    end
    return if !value

    @angle = value
  end



def deactivate(view)
		view.invalidate
		#Sketchup.send_action "viewShowAxes:"
		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)
		#Sketchup.active_model.rendering_options["DisplayInstanceAxes"]=false
end# deactivate



end # class Rotix



#   ###########################################################



module JHS_jsmover



class MoveTool



    @@leftArrow  = VK_LEFT  # Arrow Left Key
    @@upArrow    = VK_UP    # Arrow Up Key
    @@rightArrow = VK_RIGHT # Arrow Right Key
    @@downArrow  = VK_DOWN  # Arrow Down Key
    @@altKey     = VK_ALT   # Alt/Option Key
    @@shiftKey   = VK_SHIFT	# Shift Key



	if (RUBY_PLATFORM.downcase =~ /darwin/) != nil # Mac OSX
		@@controlKey = VK_COMMAND # Command (Apple) Key
	else # WIN PC
		@@controlKey = VK_CONTROL # Control Key
	end #if

	@@value = 100.mm

	def initialize()
		@@value = 0.0.mm unless @@value
		Sketchup::set_status_text(@@value.to_s, SB_VCB_VALUE)
		@msg="JS MoveTool: Type in Move Distance + <enter>: Press Arrows Left/Right = X, Up/Down = Y, Holding Alt/Opt + Up/Down = Z: Holding Cmd/Ctrl = x0.1 or Shift = x10"
		Sketchup::status_text=@msg
		@count = 0
	end

	def resume(view)
		Sketchup::status_text=@msg
		Sketchup::set_status_text(@@value.to_s, SB_VCB_VALUE)
	end

	def deactivate(view)
		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)
	end

    def onCancel(flag, view)
		if flag == 2 && @count > 0
			###
			@count-=1
		else
			Sketchup::status_text=""
			Sketchup::set_status_text("", SB_VCB_LABEL)
			Sketchup::set_status_text("", SB_VCB_VALUE)
			Sketchup.send_action("selectSelectionTool:")
			return nil
		end
	end

	def activate
		# This sets the label for the VCB
		Sketchup::set_status_text("Distance", SB_VCB_LABEL)
		@model = Sketchup.active_model
		@entities = @model.active_entities
		@ss = @model.selection
		unless @ss[0]
			UI.messagebox("JS MoveTool:\n\nNo Selection to Move !")
			self.onCancel(nil, nil)
		end
		Sketchup::status_text=@msg
		Sketchup::set_status_text(@@value.to_s, SB_VCB_VALUE)
	end

	def enableVCB?
		return true
	end

    def onUserText(text, view)
		# The user may type in something that we can't parse as a length
		# so we set up some exception handling to trap that
		begin
			@@value = text.to_l
		rescue
			# Error parsing the text
			UI.beep
			puts "Cannot convert #{text} to a Length"
			@@value = 0.0.mm
			Sketchup::set_status_text("", SB_VCB_VALUE)
		end
		###
		Sketchup::set_status_text(@@value.to_s, SB_VCB_VALUE)
		###
    end


    def onKeyDown(key, repeat, flags, view)
        # puts key   # For debug - finding the right keycodes

        if key == @@altKey
         	@altDown = true
        end #if
        if key == @@shiftKey
         	@shiftDown = true
        end #if
        if key == @@controlKey
         	@controlDown = true
        end #if

		distance = 0.0.mm
		distance = @@value
		vector = Geom::Vector3d.new(distance, 0, 0)
		#
		# X axis
		if key == @@rightArrow # Right
			vector = Geom::Vector3d.new(distance, 0, 0)
		end#if
		if @controlDown && key == @@rightArrow # Right * 0.1
			vector = Geom::Vector3d.new(distance*0.1, 0, 0)
		end#if
		if @shiftDown && key == @@rightArrow # Right * 10
			vector = Geom::Vector3d.new(distance*10, 0, 0)
		end#if
		if key == @@leftArrow # Left
			vector = Geom::Vector3d.new(-distance, 0, 0)
		end#if
		if @controlDown && key == @@leftArrow # Left * 0.1
			vector = Geom::Vector3d.new(-distance*0.1, 0, 0)
		end#if
		if @shiftDown && key == @@leftArrow # Left * 10
			vector = Geom::Vector3d.new(-distance*10, 0, 0)
		end #if



		# Y axis
		if key == @@upArrow # Up
			vector = Geom::Vector3d.new(0, distance, 0)
		end #if
		if @controlDown && key == @@upArrow && !@altDown # Up * 0.1
			vector = Geom::Vector3d.new(0, distance*0.1, 0)
		end #if
		if @shiftDown && key == @@upArrow && !@altDown # Up * 10
			vector = Geom::Vector3d.new(0, distance*10, 0)
		end #if
		if key == @@downArrow # Down
			vector = Geom::Vector3d.new(0, -distance, 0)
		end #if
		if @controlDown && key == @@downArrow && !@altDown # Down * 0.1
			vector = Geom::Vector3d.new(0, -distance*0.1, 0)
		end #if
		if @shiftDown && key == @@downArrow && !@altDown # Down * 10
			vector = Geom::Vector3d.new(0, -distance*10, 0)
		end #if



		# Z axis
		if @altDown && key == @@upArrow # Alt + Up
			vector = Geom::Vector3d.new(0, 0, distance)
		end #if
		if @controlDown && @altDown && key == @@upArrow # Alt + Up * 0.1
			vector = Geom::Vector3d.new(0, 0, distance*0.1)
		end #if
		if @shiftDown && @altDown && key == @@upArrow # Alt + Up * 10
			vector = Geom::Vector3d.new(0, 0, distance*10)
		end #if
		if @altDown && key == @@downArrow # Alt + Down
			vector = Geom::Vector3d.new(0, 0, -distance)
		end #if
		if @controlDown && @altDown && key == @@downArrow # Alt + Down * 0.1
			vector = Geom::Vector3d.new(0, 0, -distance*0.1)
		end #if
		if @shiftDown && @altDown && key == @@downArrow # Alt + Down * 10
			vector = Geom::Vector3d.new(0, 0, -distance*10)
		end #if

		# Now move selection !
		if [@@upArrow,@@downArrow,@@rightArrow,@@leftArrow].include?(key) && vector.length != 0
			begin
				@model.start_operation("JS MoveTool", true)
			rescue
				@model.start_operation("JS MoveTool")
			end
			###
			tr = Geom::Transformation.translation(vector)
			@entities.transform_entities(tr, @ss.to_a)
			###
			@model.commit_operation
			@count+=1
		end
		#

    end #onKeyDown

    def onKeyUp(key, repeat, flags, view)
         if key == @@altKey
         	@altDown = false
         end #if
          if key == @@shiftKey
         	@shiftDown = false
         end #if
         if key == @@controlKey
         	@controlDown = false
         end #if

    end #def


end # end of js MoveTool


end#JHS_jsmover
































=begin

																		WELD (TIG)

=end

module JHS_TIG_W

# MENU for superweld

def self.superwelder
m=Sketchup.active_model
s=m.selection
if s.empty?
	Sketchup.active_model.select_tool Welder.new  #---
else
	self.weld()
end
end


  def self.weld(rep=false)
	model=Sketchup.active_model
	sel=model.selection
	verts=[]
	newVerts=[]
	startEdge=startVert=nil
#GET EDGES & VERTICES
	edges=sel.grep(Sketchup::Edge)
    return nil if edges.length < 2
    ents=edges[0].parent.entities
    edges.each{|e|verts << e.vertices}
	verts.flatten!
#FIND AN END VERTEX
	vertsShort=[]
	vertsLong=[]
	verts.each{|v|
		if vertsLong.include?(v)
			vertsShort << v
		else
			vertsLong << v
		end
	}
	if (startVert=(vertsLong-vertsShort)[0])==nil
        startVert=vertsLong[0]
        closed=true
	    startEdge=startVert.edges[0]
	else
	    closed=false
	    startEdge=(edges & startVert.edges)[0]
	end
#SORT VERTICES, LIMITING TO THOSE IN THE SELECTION SET
	if startVert==startEdge.start
		newVerts=[startVert]
		counter=0
		while newVerts.length < verts.length
			edges.each{|edge|
				if edge.end==newVerts[-1]
					newVerts << edge.start
				elsif edge.start==newVerts[-1]
					newVerts << edge.end
				end
			}
			counter+=1
			if counter > verts.length
				newVerts.reverse!
				reversed=true
			end
		end
	else
		newVerts=[startVert]
		counter=0
		while newVerts.length < verts.length
			edges.each{|edge|
				if edge.end==newVerts[-1]
					newVerts << edge.start
				elsif edge.start==newVerts[-1]
					newVerts << edge.end
				end
			}
			counter+=1
			if counter > verts.length
				newVerts.reverse!
				reversed=true
			end
		end
	end
	newVerts.uniq!
    newnewVerts=[]
    newVerts.each_with_index{|v, i|
      break if i==newVerts.length-1
      newnewVerts << v
      edged=false
      edges.each{|e|
        if e.start.position==v.position && e.end.position==newVerts[1+i].position
          newnewVerts << newVerts[1+i]
          edged=true
          break
        elsif e.end.position==v.position && e.start.position==newVerts[1+i].position
          newnewVerts << newVerts[1+i]
          edged=true
          break
        end
      }
      break unless edged
    }
    return nil unless newnewVerts[1]
	newnewVerts.reverse! if reversed
    newnewVerts << newnewVerts[0] if closed
#CREATE THE CURVE
	begin
      model.start_operation("Weld", true) unless rep
    rescue
      model.start_operation("Weld") unless rep
    end
    ###
	edges.each{|e|e.explode_curve if e.curve && newnewVerts.include?(e.start)}
	gents=(gp=ents.add_group).entities
	curve=gents.add_curve(newnewVerts)
	$jhs_memory_selectionx = curve.to_a
	gp.explode
    ###
    model.commit_operation unless rep
    ###
		sel.clear
    ###
    return curve ### returns an array of the welded edges.
	sel.add curve
    ###
  end#def


class Welder

def activate
@entities = []
@add_selection = 0
Sketchup::set_status_text("Click on Edge.  All Connected edges will be Welded into a Curve.", SB_PROMPT)
end#activate

def onMouseMove(flags, x, y, view)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # add edges/faces to selection
         if (!picked.kind_of? Sketchup::Group and !picked.kind_of? Sketchup::ComponentInstance and !picked.kind_of? Sketchup::Image)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
end

def onLButtonDown(flags, x, y, view)
m=Sketchup.active_model
s=m.selection
e=m.entities
x=s[0].all_connected
s.add x
JHS_powerbar::JHS_TIG_W.weld()
s.add $jhs_memory_selectionx
Sketchup.send_action "selectSelectionTool:"
end#onLbuttondown



def deactivate(view)
	view.refresh
end# deactivate

end#welder

end#module  TIG_JHS  WELD






=begin                                            SPLITUP - TIG
=end

module JHS_SplitUp
  def self.new(num=0)
    @type=num
    @model=Sketchup.active_model
    @ents=@model.active_entities
    @ss=@model.selection
    return nil if not self.valid_selection?()
	if num==0
      self.process() if self.dialog()
      return nil###
	else
      @num=num
      self.process()
      return [@faces.length, @tfaces.length*@num*@num]
    end
  end
  def self.valid_selection?()
    if @ss.empty?
      if @type==0
        UI.messagebox("SplitUp: No selection!")
      else
        UI.beep
        puts "SplitUp: No selection!"
      end
      return nil
    end
    @faces=[]
    @ss.each{|e|@faces << e if e.class==Sketchup::Face}
    if not @faces[0]
      if @type==0
        UI.messagebox("SplitUp: No faces in selection!")
      else
        UI.beep
        puts "SplitUp: No faces in selection!"
      end
      return nil
    end
    @faces.each{|face|
      if face.outer_loop.edges.length!=4 or face.loops.length>2 or (face.loops.length!=1 and (face.loops-[face.outer_loop])[0].edges.length!=4)
        if @type==0
          UI.messagebox("SplitUp: Some faces in selection do not have 4 edges!\nSuggest you use 'Quadrilateralizer' on all required faces.")
        else
          UI.beep
          puts "SplitUp: Some faces in selection do not have 4 edges!\nSuggest you use 'Quadrilateralizer' on all required faces."
        end
        break
      end#if
    }
    return true
  end
  def self.dialog()
    @num=10 if not @num
    results=UI.inputbox(["Divisions: "],[@num],"SplitUp")
    return nil if not results
    @num=results[0]
    return @num
  end
  def self.process()
    begin
      @model.start_operation("SplitUp: "+@num.to_s, true)
    rescue
      @model.start_operation("SplitUp: "+@num.to_s)
    end
    @ss.clear
    ###
    self.clone_faces()
    @tfaces.each{|gp|
      face=nil
      gp.entities.each{|e|face=e if e.class==Sketchup::Face}
      self.splitter(face)
      begin
       @model.active_view.refresh
      rescue
        ###
      end
    }
    @ents.erase_entities(@tfaces)
    ###
    @model.commit_operation
  end
  def self.clone_faces()
    @divs=[]
    @tfaces=[]
    @faces.each{|face|
      next if face.outer_loop.edges.length!=4 or face.loops.length>2 or (face.loops.length!=1 and (face.loops-[face.outer_loop])[0].edges.length!=4)
      gp=@ents.add_group()
      hole=nil
      if face.loops.length>1
        hole=gp.entities.add_face((face.loops-[face.outer_loop])[0].vertices)
      end
      fo=gp.entities.add_face(face.outer_loop.vertices)
      if hole and hole.valid?
        hole.erase!
        ### divide into 4 faces
        ol=fo.outer_loop
        il=(fo.loops-[fo.outer_loop])[0]
        olvs=ol.vertices
        ilvs=il.vertices
        olvs.each{|v|
          p0=v.position
          p1=ilvs[0].position
          di=p0.distance(p1)
          vi=ilvs[0]
          ilvs.each{|i|
            if p0.distance(i.position) < di
              p1=i.position
              di=p0.distance(p1)
              vi=i
            end
          }
          gp.entities.add_line(p0,p1)
          ll=@ents.add_line(p0,p1)
          ll.smooth=false
          ll.soft=false
          @divs << [p0.to_a, p1.to_a]
          ilvs=ilvs-[vi]
        }
        @divs.uniq!
        gps=[]
        hfaces=[]
        gp.entities.each{|e|hfaces << e if e.class==Sketchup::Face}
        ###
        hfaces.each{|f|
          g=gp.entities.add_group()
          g.entities.add_face(f.vertices)
          gps << g
        }
        edges2go=[]
        hfaces.each{|f|edges2go << f.edges}
        edges2go.flatten!
        edges2go.uniq!
        gp.entities.erase_entities(edges2go)
        gps.each{|g|@tfaces << g}
        gp.explode
      else
        @tfaces << gp
      end
    }
  end
  def self.splitter(face)
    pts=[]
    edges=face.edges
    edges.each{|e|
      ps=e.start.position
      pe=e.end.position
      ve=e.line[1]
      di=e.length
      ds=di/@num
      ptx=[]
      1.upto(@num-1){|i|
        pt=ps.offset(ve, ds*i)
        e=e.split(pt)
        ptx << pt
      }
      pts << ptx
    }
    edges=face.edges
    nedges=[]
    begin
      (@num-1).times{|i|
        nedges << @ents.add_line(pts[0][i], pts[2][-i-1])
        nedges << @ents.add_line(pts[1][i], pts[3][-i-1])
      }
    rescue Exception => e
      puts e ### should never happen!
    end
    tr=Geom::Transformation.new()
    nnedges=@ents.intersect_with(true, tr, @ents, tr, true, nedges)
    sedges=nedges+nnedges
    sedges.uniq!
    sedges.each{|e|
      next unless e.valid?
      e.smooth=false
      e.soft=false
    }
  end

end  # module splitup

=begin														MIRROR TOOL - TIG
=end

module JHS_MIR

class MirrorTool

#### BugSplat limiter: Outliner rollup tool after Jim's code - only for Windows...
begin
	if RUBY_PLATFORM =~ /mswin|mingw/ && Sketchup.version.to_i < 14 && File.exist?(File.join(File.dirname(__FILE__),"toggleWindows.txt")) ### = a Windows machine and script there...
	  begin load File.join(File.dirname(__FILE__),"toggleWindows.txt");rescue;end
	  def hide_Outliner_MI()
		@the_Outliner_was_Open_MI=!isRolledUp("Outliner")
		toggleRollUp("Outliner")if @the_Outliner_was_Open_MI
	  end
	  def restore_Outliner_MI()
		toggleRollUp("Outliner")if @the_Outliner_was_Open_MI
	  end
	else ### tidy up > v13
	    UI.start_timer(2){ ### wait till safe...
		  if File.exist?(File.join(File.dirname(__FILE__),"toggleWindows.rb"))
			File.delete(File.join(File.dirname(__FILE__),"toggleWindows.rb"))
		  end
		  if File.exist?(File.join(File.dirname(__FILE__),"Win32API.so"))
			File.delete(File.join(File.dirname(__FILE__),"Win32API.so"))
		  end
		  if File.exist?(File.join(File.dirname(__FILE__),"toggleWindows.txt"))
			File.delete(File.join(File.dirname(__FILE__),"toggleWindows.txt"))
		  end
	    }
	end#if
rescue
end

    def initialize
        @MIRROR_AT_POINT = 1
        @MIRROR_AT_LINE  = 2
        @MIRROR_AT_PLANE = 3
    end

    def reset()
        @pts = []
        @state = 0
        @transformationType = nil
        @trans = nil
        @point = nil
        @line = nil
        @linev = nil
        @plane = nil
        @planeNormal = nil
        @pointOnPlane = nil
        @ip = Sketchup::InputPoint.new
        @ip1 = Sketchup::InputPoint.new
        @ip2 = Sketchup::InputPoint.new
        @ip3 = Sketchup::InputPoint.new
        @msg = "Mirror:  Pick First Point"
        Sketchup::set_status_text(@msg)###
        @drawn = false
    end

    def activate
        @model=Sketchup.active_model
        @ss=@model.selection
		@locked=[]
		@ss.each{|e|
		  next unless e.is_a?(Sketchup::Group) || e.is_a?(Sketchup::ComponentInstance)
		  @locked << e if e.locked?
		}
		@ss.remove(@locked) if @locked[0]
        if @ss.empty?
			@ss.add(@locked) if @locked[0]
            Sketchup::set_status_text("Mirror: NO Valid Selection !")###
    		UI.messagebox("Select Something Valid BEFORE Using the Mirror Tool.")###v2.8
            Sketchup.send_action("selectSelectionTool:")
            return nil
        end
		begin
        self.hide_Outliner_MI() if RUBY_PLATFORM =~ /mswin|mingw/ && Sketchup.version.to_i < 14 && File.exist?(File.join(File.dirname(__FILE__),"toggleWindows.rb"))
		rescue
		end
        self.reset()
    end

    def deactivate(view)
        view.invalidate if @drawn
        @ip1 = nil
        @ip2 = nil
        @ip3 = nil
		begin
        self.restore_Outliner_MI() if RUBY_PLATFORM =~ /mswin|mingw/ && Sketchup.version.to_i < 14 && File.exist?(File.join(File.dirname(__FILE__),"toggleWindows.rb"))
		rescue
		end
    end

    def resume(view=nil)
        Sketchup::set_status_text(@msg)
    end

    def getExtents
      bb=Geom::BoundingBox.new
      case @state
       when 0 # We are getting the first point
         if @ip.valid? && @ip.display?
            bb.add(@ip.position)
         end
       when 1
         bb.add(@pts[0]) if @pts[0]
       when 2
         bb.add(@pts[1]) if @pts[1]
      end
      return bb
    end

    def onMouseMove(flags, x, y, view)
        case @state
        when 0 # getting the first end point
            @ip.pick(view, x, y)
            if @ip.valid? && @ip != @ip1
                @ip1.copy!(@ip)
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        when 1 # getting the second end point
            @ip.pick(view, x, y, @ip1)
            if @ip.valid? && @ip != @ip2
                @ip2.copy!(@ip)
                @pts[1] = @ip2.position
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        when 2 # getting the third end point
            @ip.pick(view, x, y, @ip2)
            if @ip.valid? && @ip != @ip3
                @ip3.copy!(@ip)### v3.4
                @pts[1] = @ip3.position
            end
            view.invalidate
            view.tooltip = @ip.tooltip if @ip.valid?
        end
    end

    def onLButtonDown(flags, x, y, view)
        @ip.pick(view, x, y)
        if @ip.valid?
            case @state
            when 0
                if @ip.valid?
                    @pts[0] = @ip.position
                    @msg = "Mirror:  Hit RETURN to Mirror at Point or Pick Point 2"
                    Sketchup::set_status_text(@msg)
                    @state = 1
                end
            when 1
                if @ip.valid? && @ip != @ip1
                    @pts[1] = @ip.position
                    @state = 2
                    @msg = "Mirror:  Hit RETURN to Mirror at Line or Pick Point 3"
                    Sketchup::set_status_text(@msg)
                end
            when 2
                if @ip.valid? && @ip != @ip1 && @ip != @ip2
                    @pts[2] = @ip.position
                    @state = 3
                    view.invalidate
                    self.mirror()
                end
            end
        end
    end

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

    def draw(view)
	    return unless @ip
        if @ip.valid? && @ip.display?
            @ip.draw(view)
            @drawn = true
        end
        if @state == 1
            view.set_color_from_line(@ip1, @ip)
            view.draw(GL_LINE_STRIP, @ip1.position, @ip.position)
            @drawn = true
        elsif @state == 2
            view.drawing_color = "gray"
            view.draw(GL_LINE_STRIP, @ip1.position, @ip2.position)
            view.set_color_from_line(@ip2, @ip)
            view.draw(GL_LINE_STRIP, @ip2.position, @ip.position)
            @drawn = true
        end
    end

    def onKeyDown(key, repeat, flags, view)
        if key == CONSTRAIN_MODIFIER_KEY && repeat == 1
            @shift_down_time = Time.now
            if view.inference_locked?
                view.lock_inference
            elsif @ip.valid?
                view.lock_inference(@ip)### v3.4
            end
        end
    end

    def onKeyUp(key, repeat, flags, view)
        if key == CONSTRAIN_MODIFIER_KEY && view.inference_locked? && (Time.now - @shift_down_time) > 0.5
            view.lock_inference
        end
        if key == 13
            self.mirror()
            self.reset()
        end
    end

    def mirror()
        ###
       case @state
        when 1
            @transformationType = @MIRROR_AT_POINT
            @point = @ip1.position.clone
            @trans = Geom::Transformation.scaling(@point, -1.0)
            @model.start_operation("Mirror at Point")
        when 2
            @transformationType = @MIRROR_AT_LINE
            @line = [@ip1.position, @ip2.position]
            @linev = Geom::Vector3d.new(@ip2.position.x-@ip1.position.x, @ip2.position.y-@ip1.position.y, @ip2.position.z-@ip1.position.z)
            @model.start_operation("Mirror at Line")
        when 3
            @transformationType = @MIRROR_AT_PLANE
            @plane = Geom.fit_plane_to_points(@ip1.position, @ip2.position, @ip3.position)
            @planeNormal = Geom::Vector3d.new(@plane[0], @plane[1], @plane[2])
            @planeNormal.normalize!
            @pointOnPlane = @ip1.position
            @model.start_operation("Mirror at Plane")
       end
       @model.active_view.invalidate
    	### fix group glitch v3.1
      def group_miner(ents)
        ents.each{|e|
          next if not e.class==Sketchup::Group### v3.4
          if e.entities.parent.instances[1]### v3.4
            begin ### v3.4
              e.make_unique
              self.group_miner(e.entities.to_a)
            rescue
              ###
            end ### v3.4
          end#if
        }#end each
      end#def
      sents=@ss.to_a### v3.4
      ents=sents[0].parent.entities
      @ss.clear
      self.group_miner(sents)
	  ###
	  ### get ONLY glued instances
	  @is2ds=[]
	  sents.each{|e|
	    next unless e.class==Sketchup::ComponentInstance
		next unless e.definition.behavior.is2d?
		@is2ds << e.definition
	  }
	  @is2ds.uniq!
      ###
	  ### make group
      copy_group=ents.add_group(sents)
      ###
      ###new_group = copy_group.copy          ###v3.4/v3.6
      gdef=copy_group.entities.parent ###3.6
      gtr=copy_group.transformation ###3.6
      new_group=ents.add_instance(gdef, gtr)###3.6
      self.group_miner(new_group.entities.to_a) ###v3.4
      ###
        ###nents=new_group.entities.to_a### v3.4
        new_center = new_group.bounds.center.clone
        mirror_point(new_center)
        t = nil
        t = Geom::Transformation.scaling(new_center,-1,-1,-1)if @state==1
        t = Geom::Transformation.rotation(new_center,@linev,180.degrees)if @state==2
        tt=nil
        if @state==3
           xxx=@planeNormal.normalize.to_a[0].abs*-1
           yyy=@planeNormal.normalize.to_a[1].abs*-1
           zzz=@planeNormal.normalize.to_a[2].abs*-1
           xxx=1 if xxx>0
           yyy=1 if yyy>0
           zzz=1 if zzz>0
           xxx=-1 if xxx<=0
           yyy=-1 if yyy<=0
           zzz=-1 if zzz<=0
           t = Geom::Transformation.scaling(new_center,xxx,yyy,zzz)
           tt= Geom::Transformation.rotation(new_center,@planeNormal,180.degrees)
        end#if
        new_group.transform!(t)if t###mirror
        new_group.transform!(tt)if tt###
        transVec = Geom::Vector3d.new(new_center.x-new_group.bounds.center.x, new_center.y-new_group.bounds.center.y, new_center.z-new_group.bounds.center.z)
        t = Geom::Transformation.translation(transVec)
        new_group.transform!(t)###then move
        ### ending dialog...
#        @msg = "Mirror: Erase Original Selection ?"
#    	Sketchup::set_status_text(@msg)
		### v3.7
		### we explode the copy always
		xents=[]; xents=new_group.explode if new_group.valid?
		nents=[]; xents.each{|e|nents << e if e.is_a?(Sketchup::Drawingelement)}
		@faces=[]
	    ents.each{|e|@faces << e if e.valid? && e.class==Sketchup::Face}
	    self.gluer(nents)
		@ss.clear
		nents.each{|e|@ss.add(e) if e.valid?}
		### v3.7
#    	if UI.messagebox("Erase Original Selection ? ",MB_YESNO,"")==6 ### 6=YES 7=NO
#            copy_group.erase! if copy_group.valid?
#      	else ### NO
           copy_group.explode if copy_group.valid?
	        @faces=[]
	        ents.each{|e|@faces << e if e.valid? && e.class==Sketchup::Face}
			self.gluer(sents)
			@ss.clear
          sents.each{|e|@ss.add(e) if e.valid?}
#    	end#if
		###
		@ss.add(@locked) if @locked[0]
		###
       @model.commit_operation###v3.7
    	###
        self.reset()
        Sketchup.send_action("selectSelectionTool:")
    end#def

	def gluer(gents) ### v3.7
	  return nil unless @is2ds[0]
	  return nil unless @faces[0]
	  gents.each{|instance|
	    next unless instance.valid?
	    next unless instance.class==Sketchup::ComponentInstance
		next unless @is2ds.include?(instance.definition)
	    tr=instance.transformation
  	    co=tr.origin
	    cz=tr.zaxis
	    @faces.to_a.each{|face|
          next unless face.valid?
		  next unless face.parent==instance.parent
          if face.classify_point(co)==Sketchup::Face::PointInside && face.normal.parallel?(cz)
	        instance.glued_to=face
            break
	      end#if
	    }
	  }
	end### gluer v3.7

    def mirror_point(new_p)
        if @transformationType == @MIRROR_AT_PLANE
            if !new_p.on_plane?(@plane)
                mirrorp = new_p.project_to_plane(@plane)
                @trans = Geom::Transformation.scaling(mirrorp, -1.0)
                new_p.transform!(@trans)
            end
        elsif @transformationType == @MIRROR_AT_LINE
            if !new_p.on_line?(@line)
                mirrorp = new_p.project_to_line(@line)
                @trans = Geom::Transformation.scaling(mirrorp, -1.0)
                new_p.transform!(@trans)
            end
        elsif @transformationType == @MIRROR_AT_POINT
            if !(new_p == @point)
                new_p.transform!(@trans)
            end
        end
    end#def

end # class MirrorTool

 def self.Mirror_Tig
    Sketchup.active_model.select_tool (MirrorTool.new())
 end

end   #module   tig mirror

=begin														PIPE ALONG PATH - TIG
=end

module JHS_PIP

def self.pipe_along_path()

model = Sketchup.active_model
entities = model.active_entities
ss = model.selection
layers=model.layers
###
### show VCB and status info
Sketchup::set_status_text("Pipe Along Path... PARAMETERS...", SB_PROMPT)
Sketchup::set_status_text(" ", SB_VCB_LABEL)
Sketchup::set_status_text(" ", SB_VCB_VALUE)
###
if ss.grep(Sketchup::Edge).length<1
  Sketchup::set_status_text("Pipe Along Path... NO SELECTED EDGES ! ", SB_PROMPT)
  UI.messagebox("No Edges Selected to Extrude Pipe.")
  return nil
end
### trap for small radius arcs
aflag=false
arads=[]
for s in ss
  if s.is_a?(Sketchup::Edge)
    if s.curve
      if s.curve.is_a?(Sketchup::ArcCurve)
        if s.curve.radius < 200.mm
          aflag=true
          arads << s.curve.radius
        end
      else
        for e in s.curve.edges
          if e.curve.is_a?(Sketchup::ArcCurve)
            if e.curve.radius < 200.mm
              aflag=true
              arads << e.curve.radius
            end
          end
        end#for
      end
    end
  end
end#for
### ####################################################################
### remembered
@diam_outer = nil
@diam_inner = nil
@segments   = nil
@cpoints    = nil
@cline_layer= nil
@in_group   = nil
###
diam_outer = Sketchup.read_default('PipeAlongPath', 'diam_outer')
diam_inner = Sketchup.read_default('PipeAlongPath', 'diam_inner')
segments   = Sketchup.read_default('PipeAlongPath', 'segments')
cpoints    = Sketchup.read_default('PipeAlongPath', 'cpoints')
cline_layer= Sketchup.read_default('PipeAlongPath', 'cline_layer')
in_group   = Sketchup.read_default('PipeAlongPath', 'in_group')
###
@diam_outer = diam_outer.to_f.inch unless diam_outer==nil
@diam_inner = diam_inner.to_f.inch unless diam_inner==nil
@segments   = segments.to_i unless segments==nil
@cpoints    = cpoints.to_s unless cpoints==nil
@cline_layer= cline_layer.to_s unless cline_layer==nil
@in_group   = in_group.to_s unless in_group==nil
###
### dialog #############################################################
pops = ["","","","Yes|No","","Yes|No"]
###
if model.options["UnitsOptions"]["LengthUnit"]<=1
	@diam_outer = 4.0.inch unless @diam_outer
	@diam_inner = 3.5.inch unless @diam_inner
else #metric
	@diam_outer = 110.0.mm unless @diam_outer
	@diam_inner = 100.0.mm unless @diam_inner
end
###
@segments    = 24 unless @segments
@cpoints     = "Yes" if @cpoints==nil
@cline_layer = "XCLINE" if @cline_layer==nil
@in_group    = "Yes" if @in_group==nil
###
values = [@diam_outer, @diam_inner, @segments, @cpoints, @cline_layer, @in_group]
prompts = ["Outside Diameter: ", "Inside Diameter: ", "Number of Segments: ", "Cpoints at Nodes? ", "Cline Layer Name: ", "Move Path into Pipe Group? "]
###
results = inputbox(prompts, values, pops, "Pipe Parameters")
###
return nil unless results ### i.e. the user cancelled the operation
###
diam_outer, diam_inner, segments, cpoints, cline_layer, in_group = results
###
if diam_outer == 0 && diam_inner == 0 ### can't BOTH be 0 ###
   UI.messagebox("Two Zero Diameters NOT allowed ! ")
   return nil
end
if diam_outer == diam_inner ### can't be same so Inner=0 ###
   diam_inner = 0.0.mm
   UI.messagebox("Equal Diameters NOT allowed !\nInner Diameter is reset to Zero. ")
end

if diam_inner > diam_outer ### Inner can't be bigger ###
   diam_inner_temp = diam_inner
   diam_inner = diam_outer
   diam_outer = diam_inner_temp
   UI.messagebox("Inner Diameter NOT allowed to be bigger than Outer!\nInner is reset to be the smaller value. ")
end
###
if segments < 3
   segments = 24
   UI.messagebox("Fewer than 3 Faces NOT allowed !\nDefaulting to 24. ")
end
###
@diam_outer, @diam_inner, @segments, @cpoints, @cline_layer, @in_group = diam_outer, diam_inner, segments, cpoints, cline_layer, in_group
###
### remember
Sketchup.write_default('PipeAlongPath', 'diam_outer', "#{@diam_outer.to_f.to_s}")
Sketchup.write_default('PipeAlongPath', 'diam_inner', "#{@diam_inner.to_f.to_s}")
Sketchup.write_default('PipeAlongPath', 'segments', "#{@segments.to_s}")
Sketchup.write_default('PipeAlongPath', 'cpoints', @cpoints)
Sketchup.write_default('PipeAlongPath', 'cline_layer', @cline_layer)
Sketchup.write_default('PipeAlongPath', 'in_group', @in_group)
###
###
radius_outer = @diam_outer / 2.0
radius_inner = @diam_inner / 2.0
###
Sketchup::set_status_text("Pipe Along Path... MAKING PIPE...", SB_PROMPT)
begin
	model.start_operation("Pipe Along Path", true)
rescue
	model.start_operation("Pipe Along Path")
end
@sel=ss.to_a
if @in_group=="Yes"
	group=entities.add_group(@sel)
	@sel=group.entities.to_a
else
	group=entities.add_group()
end
###
def self.get_vertices()
### this next bit is mainly thanks to Rick Wilson's weld.rb ###
	@error=0 ### 20070512
	model=Sketchup.active_model
	ents=model.active_entities
	sl=@sel.length
	verts=[]
	edges=[]
	@new_pts=[]
	startEdge=startVert=nil
#DELETE NON-EDGES, GET THE VERTICES
	edges = @sel.grep(Sketchup::Edge)
	edges.each{|e| verts << e.vertices }
	verts.flatten!
#FIND AN END VERTEX
	vertsShort=[]
	vertsLong=[]
	vertsEnds=[] ### to ensure array is only ends ###
	verts.each{|v|
		if vertsLong.include?(v)
			vertsShort << v
		else
			vertsLong << v
		end
	}
	vertsLong.each{|v| ### to ensure array is only ends ###
		unless vertsShort.include?(v)
			vertsEnds << v
		end
	} ### 17/8/5 ### to ensure array is only ends ###
	if vertsEnds.length==0 ### i.e. it's looped ###
 		### path start or end ?
		if @theEnd==0
		  startVert=vertsLong.first
		  @startVert=@endVert=nil ###1.2
		  startEdge=startVert.edges.first
		else
		  startVert=vertsLong.last
		  @startVert=@endVert=nil ###1.2
		  startEdge=startVert.edges.first
		end
		###
		closed=true
	else
		if vertsEnds.length != 2
			Sketchup::set_status_text("Pipe Along Path... PATH ERROR ! ", SB_PROMPT)
			UI.messagebox("The selected Path either branches or is not continuous ! \nCannot make a Pipe Extrusion ! \nRe-select a single continuous path... ")
			@error=1
			model.abort_operation
			return nil
		else
			### path start or end ?
			if @theEnd==0
			  startVert=vertsEnds.first
		      @startVert=startVert ###1.2
		      @endVert=vertsLong.last ###1.2
			else
			  startVert=vertsEnds.last
  		      @startVert=startVert ###1.2
			  @endVert=vertsLong.first ###1.2
			end
			###
			closed=false
			startEdge=startVert.edges.first
		end
	end
	model.selection.clear
#SORT VERTICES, LIMITING TO THOSE IN THE SELECTION SET
	if startVert==startEdge.start
		@new_pts=[startVert]
		counter=0
		while @new_pts.length < verts.length
			edges.each{|edge|
				if edge.end==@new_pts.last
					@new_pts << edge.start
				elsif edge.start==@new_pts.last
					@new_pts << edge.end
				end
			}
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Pipe Along Path... ERROR ! ", SB_PROMPT)
				if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
					model.abort_operation
					return nil
				end
				@new_pts.reverse!
				reversed=true
			end
		end
	else
		@new_pts=[startVert]
		counter=0
		while @new_pts.length < verts.length
			edges.each{|edge|
				if edge.end==@new_pts.last
					@new_pts << edge.start
				elsif edge.start==@new_pts.last
					@new_pts << edge.end
				end
			}
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Pipe Along Path... ERROR ! ", SB_PROMPT)
				if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
					model.abort_operation
					return nil
				end
				@new_pts.reverse!
				reversed=true
			end
		end
	end
	@new_pts.reverse! if reversed
#CONVERT VERTICES TO POINT3Ds >> ARRAY
	@new_pts.collect!{|x| x.position.to_a }
	@new_pts.uniq!
	@new_pts << @new_pts[0] if closed
	@new_pts.reverse! if closed
	###
	@closed = closed
	@edges = edges
	### now have an array of vertices in order with NO forced closed loop ...
end ### get_vertices
	###
	@theEnd=0
	###
	self.get_vertices()
	###
	if @error==1
		model.abort_operation
		return nil
	end
	### NOW - do main stuff 1 ###
	### check for small arcs and == diameter bug-splatters 1.3
	dflag=false
	for r in arads
	  dflag=true if r == radius_outer
	end
	if aflag && dflag ###1.3
	  Sketchup::set_status_text("Pipe Along Path... RADIUS ? ! ", SB_PROMPT)
	  UI.messagebox("The Radius of at least one Arc in the Path is < 200mm(8\") \nand it also matches the Pipe's Outer Radius (#{radius_outer}) ! \nThis might cause SketchUp to Bug-Splat and Crash !! \nHowever it CAN be done this way - \nScale up the Path and Diameters by x10, \nuse this Tool and then Scale down the Path and Pipe by x0.1...\n\nExiting...")
	  model.abort_operation
	  return nil
	end
	###
#=begin	### trap for slightly skewed paths...
	skewed=false
	if @new_pts[2] ### more that one edge
		0.upto(@new_pts.length-2){|i|
			pt1=@new_pts[i]
			pt2=@new_pts[i+1]
			pt2z = pt2.clone
			unless pt1.x==pt2.x && pt1.y==pt2.y ### vertical ?
				pt2z.z = pt1.z
			end
			vec  = pt2.vector_to(pt1)
			vecz = pt2z.vector_to(pt1)
			next if vecz.length==0
			next if vec.parallel?(vecz)
			ang = vec.angle_between(vecz)
			#p ang.radians
			if ang > 0 && ang < 0.38.degrees
				skewed=true
				break
			end
		}
	end
	#old_pts=@new_pts.dup
	if skewed
		Sketchup::set_status_text("Pipe Along Path... SLIGHT SKEW ? ! ", SB_PROMPT)
		UI.beep
		yn=UI.messagebox("Parts of the selected path have a very slight 'skew'.\nThere is a remote chance that it might cause SketchUp to hang when it is doing the FollowMe.\n\nFix the issue?\n\nYes \t=\t Try to readjust the path\nNo \t=\t Use path anyway!\nCancel \t=\t Abort!!\n\nIf it hangs and you haven't just saved, then you will loose work!", MB_YESNOCANCEL )
		if yn==6 #Yes
			0.upto(@new_pts.length-2){|i|
				pt1=@new_pts[i]
				pt2=@new_pts[i+1]
				pt2z = pt2.clone
				unless pt1.x==pt2.x && pt1.y==pt2.y ### vertical ?
					pt2z.z = pt1.z
				end
				vec  = pt2.vector_to(pt1)
				vecz = pt2z.vector_to(pt1)
				next if vecz.length==0
				next if vec.parallel?(vecz)
				ang = vec.angle_between(vecz)
				@new_pts[i+1]=pt2z if ang > 0 && ang < 0.38.degrees
			}
		elsif yn==7 #No
			### continue !
		else ### 2 #Cancel
			model.abort_operation
			return nil
		end
	end
	#puts old_pts || @new_pts
#=end	###
	pt1 = @new_pts[0]
	pt2 = @new_pts[1]
	vec = pt2.vector_to(pt1)
	###
	ents=group.entities
	unless radius_inner == 0 ###1.1
	  circle_inner = ents.add_circle(pt1, vec, radius_inner, segments)
	  face_inner   = ents.add_face(circle_inner)
	end
	circle_outer = ents.add_circle(pt1, vec, radius_outer, segments)
	the_face     = ents.add_face(circle_outer)
	unless radius_inner == 0 ###1.1
	   face_inner.erase!
	end
	###
	### stop inside-out extrusion
	the_face.reverse! if pt1.z==0 && vec.normalize==Z_AXIS
	###
	ants = model.active_entities ###1.1
	the_edges = []
	0.upto(@new_pts.length-2){|i|
		if @in_group=="Yes"
			the_edges[i] = ents.add_line(@new_pts[i], @new_pts[i+1])
		else
			the_edges[i] = ants.add_line(@new_pts[i], @new_pts[i+1])
		end
	}
	### follow me along ordered edges
	extrusion = the_face.followme(the_edges)
	###
	### fix graphics glitch
	if skewed==true
		tgrp=ants.add_group(group)
		tr=group.transformation
		group.explode
		group=tgrp
		ents=group.entities
	else
		tr=Geom::Transformation.new()
	end
	###
	### add group name
	if @diam_inner == 0
		group.name="D=#{@diam_outer}"
	else
		group.name="OD=#{@diam_outer} ID=#{@diam_inner}"
	end
	### layer cline
	unless @cline_layer.empty?
		xlayer = layers.add(@cline_layer)
		@edges.each{|e| next unless e.valid?; e.layer = xlayer }
	end
	###
	model.selection.clear
	### select edges if not moved into group
	if @in_group=="No"
		model.selection.add(@edges)
	end
	###
	model.commit_operation
	###
	### add cpoints
	if @cpoints=="Yes"
		begin
			model.start_operation("Pipe's Cpoints", true)
		rescue
			model.start_operation("Pipe's Cpoints")
		end
		  @new_pts.each{|pt| ents.add_cpoint(pt.transform(tr)) }
		model.commit_operation ###1.3
	end#if
	###
end ### end def ###

end  # module  tig pipe along path

=begin                                            ADD VERTEX - TIG
=end

module JHS_TIGvtx
class JHS_TIGvtx::AddVertexPoint
###
def initialize()
  @edges=[]
  @vertices=[]
  @cpoints=[]
  @key=nil
  @status="Add Guide-Point: Pick a Point on Edge, Face etc: Hold Key to Modify [in Context], SHIFT+Edge=Split_Edge: ALT+Face=Split_Face..."
end#def

def reset()
  @edges=[]
  @vertices=[]
  @ip=Sketchup::InputPoint.new()
end#def

def activate
  self.reset()
  @model=Sketchup.active_model
  @entities=@model.active_entities
  @ss=@model.selection
  @edges=@ss.grep(Sketchup::Edge)
  @model.start_operation("Add Guide-Point Markers")
  @edges.each{|e|e.vertices.each{|v|@vertices << v}}
  @vertices.uniq!
  @vertices.each{|v|
	cp=@entities.add_cpoint(v.position)
	@cpoints << cp
  }
  @ss.clear
  @cpoints.each{|cp|@ss.add(cp)}
  @model.commit_operation
  Sketchup.set_status_text(@status)
end#def

def onKeyDown(key, repeat, flags, view)
  @key="shift" if key==CONSTRAIN_MODIFIER_KEY ###
  @key="alt"   if key==ALT_MODIFIER_KEY ###
  view.tooltip=("Add Guide-Point: "+@ip.tooltip)
end#def

def onKeyUp(key, repeat, flags, view)
  @key=nil
  view.tooltip=("Add Guide-Point: "+@ip.tooltip)
end#def

def onMouseMove(flags, x, y, view)
  @ip.pick(view, x, y)
  view.invalidate
  if @ip.valid?
   if @ip.tooltip=="" || @ip.tooltip=="From Point"
    view.tooltip=""
   else
    view.tooltip=("Add Guide-Point: "+@ip.tooltip)
   end#if
  end#if
  Sketchup.set_status_text(@status)
end#def

def deactivate(view)
  #@ss.clear
  view.invalidate
end#def

def resume(view)
  Sketchup.set_status_text(@status)
  view.tooltip=("Add Guide-Point: "+@ip.tooltip)
end

def onLButtonDown(flags, x, y, view)
  @model.start_operation("Add Guide-Point")
  ph=@model.active_view.pick_helper
  ph.do_pick(x, y)
  picked_edge   = ph.picked_edge
  picked_face   = ph.picked_face
  picked_element= ph.picked_element
  @ip.pick(view, x, y)
  if @ip.valid?
    @pt = @ip.position
  else
    picked_edge    = nil
    picked_face    = nil
    picked_element = nil
  end#if
  if picked_edge || picked_face || picked_element
    cp=@entities.add_cpoint(@pt)
    @cpoints << cp
    @ss.add(cp)
  else
    UI.beep
  end#if


  if @entities.to_a.include?(picked_edge) && picked_edge && @key=="shift" ###
    picked_edge.split(@pt) ###split select edge at picked point
  end#if
  if @entities.to_a.include?(picked_face) && picked_face && @key=="alt" ###
      picked_face.edges.each{|e|
		@entities.add_line(@pt, e.start)
		@entities.add_line(@pt, e.end)
	  } ###split face with new edges
  end#if
  Sketchup.set_status_text(@status)
  @model.commit_operation
end#def
###

end#class

end#module JHS_TIGvtx




=begin
                                               ALIGN  - PIXERO

=end



class Sketchup::Selection
def vertices
	selverts = [];
	self.each {|e|
		selverts.push e.vertices if (e.respond_to?(:vertices));
	}
	return selverts.flatten.uniq;
end ;  # method vertices
end ;  # class Sketchup::Selection

###############		"RED"

def self.jsAlign_red_centre
	model = Sketchup.active_model
	entities = model.active_entities
	sel = model.selection
	if sel.empty?
     		UI.beep
     		UI.messagebox("Nothing Selected!")
     		return nil
  	end#if

  	### Get all edges in selection
	edges=[]
	for e in sel
  		if e.typename=="Edge" and e.valid?
    			edges.push(e)
  		end#if
	end#for

	### Align

	selverts = Sketchup.active_model.selection.vertices;
	bb = Geom::BoundingBox.new;
	Sketchup.active_model.selection.each {|e| bb.add(e.bounds); }


		basepoint = Geom::Point3d.new (bb.center);
		distances = [];
	groupstomove = [];
	groupdistances = [];

		# Aligning groups
		Sketchup.active_model.selection.each {|e|
		if (e.kind_of? Sketchup::Group) or (e.kind_of? Sketchup::ComponentInstance) or (e.kind_of? Sketchup::Image)
		groupstomove.push e

			point = Geom::Point3d.new (e.bounds.center);

			dist = Geom::Point3d.new [basepoint.x-point.x, 0, 0];
     	groupdistances.push dist;
	end #if
	}
		Sketchup.active_model.active_entities.transform_by_vectors groupstomove, groupdistances;
		# Aligning edges
	selverts.each {|v|
		point = Geom::Point3d.new [v.position.x, v.position.y, v.position.z];
		dist = Geom::Point3d.new [basepoint.x-point.x, 0, 0];
		distances.push dist;
	}
	Sketchup.active_model.active_entities.transform_by_vectors selverts, distances;
end

###############		"GREEN"

def self.jsAlign_green_centre
	model = Sketchup.active_model
	entities = model.active_entities
	sel = model.selection
	if sel.empty?
     		UI.beep
     		UI.messagebox("Nothing Selected!")
     		return nil
  	end#if

  	### Get all edges in selection
	edges=[]
	for e in sel
  		if e.typename=="Edge" and e.valid?
    			edges.push(e)
  		end#if
	end#for

	  	### Align

	selverts = Sketchup.active_model.selection.vertices;
	bb = Geom::BoundingBox.new;
	Sketchup.active_model.selection.each {|e| bb.add(e.bounds); }

		basepoint = Geom::Point3d.new (bb.center);

		distances = [];
	groupstomove = [];
	groupdistances = [];

		# Aligning groups
		Sketchup.active_model.selection.each {|e|
		if (e.kind_of? Sketchup::Group) or (e.kind_of? Sketchup::ComponentInstance)
		groupstomove.push e
			point = Geom::Point3d.new (e.bounds.center);


#		"GREEN"
		 dist = Geom::Point3d.new [0, basepoint.y-point.y, 0];
     	groupdistances.push dist;
	end #if
	}
		Sketchup.active_model.active_entities.transform_by_vectors groupstomove, groupdistances;
		# Aligning edges
	selverts.each {|v|
		point = Geom::Point3d.new [v.position.x, v.position.y, v.position.z];
		dist = Geom::Point3d.new [0, basepoint.y-point.y, 0];
		distances.push dist;
	}
	Sketchup.active_model.active_entities.transform_by_vectors selverts, distances;
end

###############		"BLUE"

def self.jsAlign_blue_centre
	model = Sketchup.active_model
	entities = model.active_entities
	sel = model.selection
	if sel.empty?
     		UI.beep
     		UI.messagebox("Nothing Selected!")
     		return nil
  	end#if

  	### Get all edges in selection
	edges=[]
	for e in sel
  		if e.typename=="Edge" and e.valid?
    			edges.push(e)
  		end#if
	end#for

	  	### Align

	selverts = Sketchup.active_model.selection.vertices;
	bb = Geom::BoundingBox.new;
	Sketchup.active_model.selection.each {|e| bb.add(e.bounds); }

		basepoint = Geom::Point3d.new (bb.center);
		distances = [];
	groupstomove = [];
	groupdistances = [];
		# Aligning groups
		Sketchup.active_model.selection.each {|e|
		if (e.kind_of? Sketchup::Group) or (e.kind_of? Sketchup::ComponentInstance)
		groupstomove.push e

			point = Geom::Point3d.new (e.bounds.center);
			dist = Geom::Point3d.new [0, 0, basepoint.z-point.z];
     	groupdistances.push dist;
	end #if
	}
		Sketchup.active_model.active_entities.transform_by_vectors groupstomove, groupdistances;
		# Aligning edges
	selverts.each {|v|
		point = Geom::Point3d.new [v.position.x, v.position.y, v.position.z];

			dist = Geom::Point3d.new [0, 0, basepoint.z-point.z];
		distances.push dist;
	}
	Sketchup.active_model.active_entities.transform_by_vectors selverts, distances;
end






=begin

											COMPONENT ARRAY - SD MITCH


=end

module JHS_SDM_CA

	def self.component_array
		mod = Sketchup.active_model
		@ent = mod.active_entities
		sel = mod.selection
		@Spacing = 10.m if !@Spacing
		@Angle = 0 if @Angle
		@G_type = "Rec" if !@G_type
		@Orient = "Vert" if !@Orient
		@face=nil
		@face=sel.first if sel.first.is_a?(Sketchup::Face)
		if @face
			cd=[];@ent.each{|d| cd << d.definition if d.is_a?(Sketchup::ComponentInstance)}
			cn=[];cd.each{|d| cn << d.name}
			comps = cn.join("|");@C_name=cn[-1] if !@C_name || !comps.include?(@C_name)
			prompts=["Component:","Grid Spacing:","Grid Angle:","Grid Type:","Orientation:"]
			defaults=[@C_name,@Spacing,@Angle,@G_type,@Orient]
			options=[comps,nil,nil,"Rec|Tri","Vert|Perp"]
			inp_data=inputbox(prompts,defaults,options,"Component Array")
			if !inp_data then;return;end
			@C_name=inp_data[0].to_s;ci=cn.index(@C_name);@Comp=cd[ci];
			@Spacing=inp_data[1].to_l;@Angle=inp_data[2].to_i;@G_type=inp_data[3];@Orient=inp_data[4]
			pl=@face.plane;ctr=@face.bounds.center; norm=@face.normal; norm.reverse! if norm.z < 0;
			ctr=ctr.project_to_plane pl if !ctr.on_plane? pl; @xaxis,@yaxis,@zaxis=norm.axes
			tr = Geom::Transformation.rotation(ctr,norm,@Angle.degrees); @zaxis=Z_AXIS if @Orient=="Vert"
			@xaxis.transform! tr; @yaxis.transform! tr; maxd = @face.bounds.diagonal*2
			@pt0=ctr.offset(@xaxis.reverse,maxd/2).offset(@yaxis.reverse,maxd/2)
			nx=(maxd/@Spacing).ceil;ny=(maxd/@Spacing).ceil
			mod.start_operation "Component Array", true
			case @G_type
				when "Rec";self.rectangular_array(nx,ny)
				when "Tri";self.triangular_array(nx,ny)
			end
			mod.commit_operation
		else
			UI.messagebox "No face pre-selected"
		end
	end

	def self.rectangular_array(nx,ny)
		for i in 0..ny
			pt1=@pt0.clone
			for j in 0..nx
				if @face.classify_point(pt1)==1
					# @ent.add_cpoint(pt1)
					tr=Geom::Transformation.new pt1,@zaxis
					@ent.add_instance(@Comp,tr)
				end
				pt1.offset!(@xaxis,@Spacing);
			end
			@pt0.offset!(@yaxis,@Spacing)
		end
	end

	def self.triangular_array(nx,ny)
			odd_row=true
			for i in 0..ny
				pt1=@pt0.clone
				for j in 0..nx
					if @face.classify_point(pt1)==1
						# @ent.add_cpoint(pt1)
						tr=Geom::Transformation.new pt1,@zaxis
						@ent.add_instance(@Comp,tr)
					end
					pt1.offset!(@xaxis,@Spacing);
				end
				@pt0.offset!(@yaxis,@Spacing*0.866);#puts"next @pt0=#{@pt0}"
				if odd_row
					@pt0.offset!(@xaxis,@Spacing*0.5);odd_row=false
				else
					@pt0.offset!(@xaxis,-@Spacing*0.5);odd_row=true
				end
			end
	end
end   # module sdm_ca


=begin														MESH MAKER - SD MITCH
=end

module JHS_Mesh_Gen

	def self.mesh_maker
		mod=Sketchup.active_model
		ent=mod.entities
		sel=mod.selection
		$Mesh_CS = 25.cm if !$Mesh_CS
		$Mesh_Ang = 0.0 if !$Mesh_Ang
		if sel.first.is_a? Sketchup::Face
			ans=UI.inputbox([" Cell Size:","Mesh Angle:"],[$Mesh_CS,$Mesh_Ang],"Mesh Generator")
			if !ans then return end
			mod.start_operation "Mesh Maker"
			$Mesh_CS=ans[0].to_l; $Mesh_Ang=ans[1].to_f; face=sel.first; edges=[]; edges=face.edges
			pts=[]; pts=face.outer_loop.vertices.collect {|v| v.position}
			pl=face.plane;ctr=face.bounds.center; norm=face.normal; norm.reverse! if norm.z < 0;
			ctr=ctr.project_to_plane pl if !ctr.on_plane? pl; xa,ya,za=norm.axes
			if $Mesh_Ang != 0.0
				tr = Geom::Transformation.rotation(ctr,norm,$Mesh_Ang.degrees)
				xa.transform! tr; ya.transform! tr
			end
			maxd=0.0; pts.each {|p| d=ctr.distance(p); maxd=d if maxd<d}
			pt0=ctr.offset(xa.reverse,maxd).offset(ya.reverse,maxd)
			dx=maxd*2;dy=maxd*2; nx=(dx/$Mesh_CS).ceil; ny=(dy/$Mesh_CS).ceil; data=[]; row=[]
			# Generate the grid pattern
			for i in 0..ny
				row[0] = pt0
				for j in 1..nx
					row[j]=row[j-1].offset(xa,$Mesh_CS)
					# ent.add_cpoint(row[j-1])
				end
				pt0=pt0.offset(ya,$Mesh_CS)
				data[i]=row; row=[]
			end
			grp = ent.add_group; @ge = grp.entities;    # create a group for c_line entities
			# Draw the horizontal lines
			for i in 0..ny
				for j in 1..nx
					start_pt_outside = (face.classify_point(data[i][j-1]) == Sketchup::Face::PointOutside)
					end_pt_outside = (face.classify_point(data[i][j]) == Sketchup::Face::PointOutside)
					if !(start_pt_outside && end_pt_outside)
						if !(start_pt_outside || end_pt_outside)
							@ge.add_cline(data[i][j-1],data[i][j])
						else
							poi=self.find_intersection(data[i][j-1],data[i][j],edges)
							if poi
								@ge.add_cline(poi,data[i][j]) if start_pt_outside
								@ge.add_cline(data[i][j-1],poi) if end_pt_outside
							end
						end
					end
				end
			end
			# Draw the vertical lines
			for j in 0..nx
				for i in 1..ny
					start_pt_outside = (face.classify_point(data[i-1][j]) == Sketchup::Face::PointOutside)
					end_pt_outside = (face.classify_point(data[i][j]) == Sketchup::Face::PointOutside)
					if !(start_pt_outside && end_pt_outside)
						if !(start_pt_outside || end_pt_outside)
							@ge.add_cline(data[i-1][j],data[i][j])
						else
							poi=self.find_intersection(data[i-1][j],data[i][j],edges)
							if poi
								@ge.add_cline(poi,data[i][j]) if start_pt_outside
								@ge.add_cline(data[i-1][j],poi) if end_pt_outside
							end
						end
					end
				end
			end
			# Draw the diagonal lines
			for i in 1..ny
				for j in 1..nx
					start_pt_outside = (face.classify_point(data[i][j-1]) == Sketchup::Face::PointOutside)
					end_pt_outside = (face.classify_point(data[i-1][j]) == Sketchup::Face::PointOutside)
					if !(start_pt_outside && end_pt_outside)
						if (start_pt_outside || end_pt_outside)
							poi=self.find_intersection(data[i][j-1],data[i-1][j],edges)
							if poi
								@ge.add_cline(poi,data[i-1][j]) if start_pt_outside
								@ge.add_cline(data[i][j-1],poi) if end_pt_outside
							end
						else
							@ge.add_cline(data[i][j-1],data[i-1][j])
						end
					end
				end
			end
	#		convert construction lines to soft and smooth edges and erase guide lines
			@ge.each {|e|edg=ent.add_line(e.start,e.end);edg.smooth=false;edg.soft=false}
			ent.erase_entities grp
			mod.commit_operation
		else
			UI.messagebox "No face selected"
		end
	end

	def self.find_intersection(p0,p1,edges)
		ln0=[p0,p0.vector_to(p1)]
		for e in edges
			ln1=e.line; pp=Geom.intersect_line_line(ln0,ln1)
			if pp
				if self.is_between(pp,e.start.position,e.end.position)
					if self.is_between(pp,p0,p1)
						return pp
					end
				end
			end
		end
		return nil
	end

	def  self.is_between(p0,p1,p2)
	# --------------------------------------------------------------------------------
	#	Determine if point p0 is on the line and between end points p1 and p2
	# --------------------------------------------------------------------------------
		d1=p0.distance p1
		d2=p0.distance p2
		d3=p1.distance p2
		return true if ([d1,d2].max <= d3)
		return false # p0 is not on line or between p1 and p2
	end
end    # module mesh maker


=begin														SUBDIVIDE FACES - SD MITCH
=end

module JHS_Sub_Divide_Faces

		def self.main
				mod=Sketchup.active_model
				ent=mod.active_entities
				sel=mod.selection
			if !sel.empty?
				mod.start_operation "sub_divide faces"
				faces=sel.find_all{|e| e.is_a?(Sketchup::Face)}
				long=nil;pts=[];pt=[];n=0
				ans=UI.inputbox(["Number of Divisions"],[1],"Sub-Divide Faces")
				if !ans then;return;end
				nod=ans[0].to_i
				for i in 1..nod
					for face in faces
						edges=[];edges=face.edges;
						if edges.length == 3
							long=edges[0]
							for i in 1...edges.length
								long = edges[i] if long.length < edges[i].length
							end
							pt[0]=long.bounds.center
							edges.delete_if{|e| e == long}
							pt[1]=Geom.intersect_line_line(edges[0].line,edges[1].line)
							pts[n]=pt;pt=[];n+=1
						elsif edges.length == 4
							pt[0]=face.vertices[0].position
							pt[1]=face.vertices[2].position
							pts[n]=pt;pt=[];n+=1
						end
					end
					faces=[];
					for i in 0...n
						line=ent.add_line(pts[i][0],pts[i][1])
						faces<<line.faces
						self.progress_bar(i+1,n)
					end
					faces.flatten!;faces.uniq!
				end
				ent.each{|e| e.soft=false if e.class==Sketchup::Edge && e.faces.length==2}
				mod.commit_operation
			else
				UI.messagebox "Nothing Selected"
			end
		end

		def self.progress_bar(cnt,max)
			if cnt == 1
				@pb =  "."*101
			else
				pct = cnt*100/max
				@pb[pct] = "|"
				Sketchup.set_status_text(@pb + " #{pct}%")
			end
		end

	end

=begin														COMPONENT STRING  (COPY) (SDMITCH)
=end

module  JHS_SDM_CS

		def self.maincs
			mod = Sketchup.active_model
			ent = mod.active_entities
			sel = mod.selection
			unless sel.empty?
				edge=sel.grep(Sketchup::Edge).reject{|e|!e.curve}[0]
				edge ? curve=edge.curve : curve=nil; @ct=[];comps=[]
				if curve
					mod.start_operation "Comp String"
					cis=sel.grep(Sketchup::ComponentInstance)
					unless cis.length>1
						cds=mod.definitions.reject{|cd|cd.image?}; rmv=[]
						cds.each{|cd| rmv<<cd.entities.grep(Sketchup::ComponentInstance)};
						rmv.flatten!; rmv.uniq!; rmv.each{|x| cds.delete(x.definition)}; rmv=[]
						cds.each{|cd| rmv<<cd.entities.grep(Sketchup::Group);rmv<<cd if cd.group?};
						rmv.flatten!; rmv.uniq!; rmv.each{|x| cds.delete(x)};
						cdn=cds.map{|cd|cd.name}; name=cdn[0]; name=cis[0].definition.name if cis.length==1;@spc||='50'.to_l; @ctr||="No"
						ans=inputbox(["Component:","    Spacing:","     Center?:"],[name,@spc,@ctr],[cdn.join("|"),nil,"Yes|No"],"Component String")
						return unless ans
						name=ans[0];n=cdn.index(name)
						comp = cds[n]; comps[0] = comp
						@spc=ans[1].to_l; @ctr=ans[2]; @order=nil
						@ctr=="No" ? i=2 : i=6; self.reset_axes(comp,i);
					else
						@order||="Random";@spc||='1'.to_l;@ctr||="Yes"
						ans=UI.inputbox(["Order:","Spacing:","Center?:"],[@order,@spc,@ctr],["Random|Sequence",nil,"Yes|No"],"Component String")
						return unless ans
						@order=ans[0];@spc=ans[1].to_l;@ctr=ans[2]; @ctr=="No" ? i=2 : i=6
						cis.each{|c| comp = c.definition; self.reset_axes(comp,i); comps<<comp}
					end
					edges=curve.edges; last = edges.length - 1; cnt=0; new_comp=true
					spt = edges[0].start.position; ept = edges[last].end.position
					for n in 0..last
						begin
							if @order && new_comp
								if @order=="Random"
									comp=comps[rand(comps.length)]
								else
									comp=comps[cnt]
									cnt+=1;cnt=0 if cnt==comps.length
								end
								new_comp=false
							end
							rad=comp.bounds.width + @spc; #puts "#{comp.name}, rad=#{rad}"
							pt=self.sdm_line_sphere_intersection(edges[n],spt,rad)
							if pt.length > 0
								if edges[n].bounds.contains?(pt[0])
									xa,ya,za=spt.vector_to(pt[0]).axes
									ci=ent.add_instance(comp,Geom::Transformation.new())
									ci.transform! Geom::Transformation.axes(spt,za,xa,ya)
									spt=pt[0];new_comp=true
								end
							else
								puts "no points on edge[#{n}]"
							end
						end until spt!=pt[0]
					end#for
					xa,ya,za=spt.vector_to(ept).axes
					ci=ent.add_instance(comp,Geom::Transformation.new())
					ci.transform! Geom::Transformation.axes(spt,za,xa,ya)
					comps.each.with_index{|d,n|
						t=@ct[n]; d.entities.transform_entities(t,d.entities.to_a)
						d.instances.each{|ci| ci.transformation = ci.transformation * t.inverse }
					}
					mod.commit_operation
				else
					UI.messagebox "Select Curve"
				end
			else
				UI.messagebox "Select Curve"
			end#if
		end

		def self.reset_axes(d,i)
			p = Geom::Point3d.linear_combination(0.5,d.bounds.corner(0),0.5,d.bounds.corner(i))
			t=Geom::Transformation.new(p); @ct<<t; #puts p,t
			d.entities.transform_entities(t.inverse,d.entities.to_a)
			d.instances.each{|ci| ci.transformation = ci.transformation * t }
		end

		def  self.sdm_line_sphere_intersection(edge,ctr,ros)
		# **********************************************************************
		# sphere_line_intersection C++ function adapted from:
		# http://astronomy.swin.edu.au/pbourke/geometry/sphereline
		# Paul Bourke pbourke@swin.edu.au
		# by atelier iebele abel - 2001.  Ruby Method by sdmitch 2011
		# Returns an array containing the point(s) of intersection.
		# **********************************************************************
			pt0 = edge.start.position
			pt1 = edge.end.position
			dx = pt1.x - pt0.x
			dy = pt1.y - pt0.y
			dz = pt1.z - pt0.z
			a =  dx**2 + dy**2 + dz**2
			b =  2*( dx*(pt0.x - ctr.x) + dy*(pt0.y - ctr.y) + dz*(pt0.z - ctr.z))
			c =  ctr.x**2 + ctr.y**2 + ctr.z**2 + pt0.x**2 + pt0.y**2 + pt0.z**2 - 2*( ctr.x*pt0.x + ctr.y*pt0.y + ctr.z*pt0.z ) - ros**2
			i =   b * b - 4 * a * c
			p = [] # initialize point array
			if ( i == 0.0 ) # one intersection
				mu = -b/(2*a) ;
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p.push Geom::Point3d.new(x,y,z)
			elsif ( i > 0.0 ) # two intersections
				# first intersection
				mu = (-b + Math.sqrt( (b**2) - 4*a*c )) / (2*a);
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p.push Geom::Point3d.new(x,y,z)
				# second intersection
				mu = (-b - Math.sqrt((b**2) - 4*a*c )) / (2*a);
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p.push Geom::Point3d.new(x,y,z)
			end
			return p;
		end

end   # module  sdm comp string

=begin                                            CONNECT GUIDE POINTS - SDMITCH
=end

	module JHS_Connect_Guide_Points

		def self.main
			mod = Sketchup.active_model
			ent = mod.active_entities
			sel = mod.selection
			unless sel.empty?
				cpts=sel.select{|cp| cp.is_a? Sketchup::ConstructionPoint}
				unless cpts.empty?
					bb=Geom::BoundingBox.new
					cpts.each{|p| bb.add p.position};
					pt = bb.min; pts=[]
					while cpts.length>1
						hash={};cpts.each{|p| hash[p]=pt.distance(p.position)};
						csrt = hash.sort{ |a,b| a[1] <=> b[1] }.map { |n| n[0] }
						pts<<csrt[0];pt=csrt[0].position;cpts.delete(csrt[0])
					end
					pts<<cpts[0]
					unless pts.length < 2
						mod.start_operation "Connect GPs"
						mod.layers.add "Profiles"; mod.active_layer = "Profiles"
						grp=ent.add_group;gent=grp.entities
						for i in 1...pts.length
							gent.add_line(pts[i-1].position,pts[i].position)
						end
						gent.add_line(pts[-1].position,pts[0].position) #if UI.messagebox("Close the loop?",MB_YESNO)==6
						mod.active_layer = "Layer0"
						mod.commit_operation
					else
						UI.messagebox "Less than 2 Guide Points in the selection"
					end
					sel.clear
				else
					UI.messagebox "No Guide Points in Selection"
				end
			else
				UI.messagebox  "Nothing Selected"
			end
		end
	end  # module connect guide points

=begin                                            OFFSET EDGE - SDMITCH
=end

module JHS_SDMof

	class Offset_Edge

		def initialize
			@mod=Sketchup.active_model
			@ent=@mod.active_entities
			@sel=@mod.selection
			@vue=@mod.active_view
			@ip=Sketchup::InputPoint.new
			@osd = 0
			self.reset
		end

		def reset
			@status = 0
			@status_text = "Select the single edge to offset."
		end

		def onMouseMove(flags, x, y, view)
			@ip.pick view,x,y; view.tooltip = @ip.tooltip; view.refresh
			Sketchup::set_status_text @status_text
			Sketchup::set_status_text "Offset:", SB_VCB_LABEL
		end

		def onLButtonDown(flags, x, y, view)
			ph = view.pick_helper; ph.do_pick x,y; best=ph.best_picked
			if @status==0 && best.is_a?(Sketchup::Edge)
				unless best.curve
					@edge = best
					@status=1; @status_text = "Define or Key in desired Offset."
				else
					UI.beep
				end
			elsif @status==1 && @ip.position.distance_to_line(@edge.line) > 0.0
				@ent.add_line(@pts)
				self.reset
			end
		end

		def onRButtonDown(flags, x, y, view)
			@status==0 ? onCancel(flags,view) : self.reset
		end

		def onCancel(flags,view)
			Sketchup.send_action "selectSelectionTool:"
		end

		def onUserText(text, view)
			begin
				value=text.to_l
				@osd=value
				view.refresh
			rescue
				UI.beep; puts "Invalid input"
			end
		end

		def draw(view)
			if( @ip.valid? && @ip.display? )
				@ip.draw(view)
			end

			if @status==0 && @ip.edge && !@ip.edge.curve
				view.line_width=3
				view.drawing_color='blue'
				view.draw_line @ip.edge.vertices.collect{|v| v.position}
			elsif @status==1 && @ip.position.distance_to_line(@edge.line) > 0.0
				pol=@ip.position.project_to_line(@edge.line)
				vec=pol.vector_to(@ip.position);
				dis=pol.distance(@ip.position); dis = @osd if @osd > 0
				Sketchup::set_status_text  Sketchup.format_length(dis),SB_VCB_VALUE
				vec.length=dis; @pts=[];
				@pts<<@edge.start.position.offset(vec)
				@pts<<@edge.end.position.offset(vec)
				view.line_width=3
				view.drawing_color='yellow'
				view.draw_line @pts
			end

		end

	end

end     #  JHS_SDMof




=begin                                            COMPONENT BY PROXY - SD MITCH
=end


module JHS_SDMpx


class Proxify   #



def initialize
  @key=nil
end


   def activate
       Sketchup::set_status_text("Click to Toogle Proxy. Alt= Contextual. Ctrl= All Instances.  Shift= De-Proxify ALL.", SB_PROMPT)
      @entities = []
      @add_selection = 0
   end





# place on layer
def self.layerize()
	model=Sketchup.active_model
	ents=model.active_entities
	model.start_operation("Place on Layer 'PROXIFIED'")
	proxyLayer=model.layers.add("- PROXIFIED")
			if e.layer != proxyLayer
				e.layer=proxyLayer
			end
	model.commit_operation
end



 # select all same comps
   def self.compoz
	i=0
	compo_name = []
	tt = Sketchup.active_model.selection
	for e in tt
			compo_name[i] = e.definition.name
			i += 1
	end
	ss = Array.new
	ss = Sketchup.active_model.active_entities
	tt.clear
	for e in ss
		i.times do |type|
			if (e.kind_of?(Sketchup::ComponentInstance) && e.definition.name == compo_name[type])
				tt.add(e)
			end
		end
	end
end




# proxify    create a wireframe cage to bounding box
def self.sdm_to_proxy
	mod = Sketchup.active_model
	ent = mod.active_entities
	sel = mod.selection
	mod.start_operation "To Proxy"
	cmps = sel.grep(Sketchup::ComponentInstance)
	cmps.each{|cmp|
		trn = cmp.transformation
		cdn = cmp.definition.name
		next if cdn[0..9]=="Proxyfied-"
		cmp.transform! trn.inverse
		pcn = "Proxyfied-"+cdn
		proxy = mod.definitions[pcn]
		unless proxy
			proxy = mod.definitions.add(pcn)
			pents = proxy.entities; pts = []
			for i in 0..7
				pts[i] = cmp.bounds.corner(i)
			end

			line = pents.add_line pts[0],pts[1],pts[3],pts[2],pts[0],pts[4],pts[6],pts[7],pts[5],pts[4],pts[6],pts[2],pts[3],pts[1],pts[5],pts[7],pts[3]

		end
		cmp.definition=proxy
		cmp.transform! trn
	}
	mod.commit_operation


    view.invalidate

end


#de-proxify  -  restore components
def self.sdm_from_proxy
	mod = Sketchup.active_model
	ent = mod.active_entities
	sel = mod.selection
	mod.start_operation "From Proxy"
	cmps = sel.grep(Sketchup::ComponentInstance)
	cmps.each{|cmp|
	cdn = cmp.definition.name
	next unless cdn[0..9]=="Proxyfied-"
	cmp.definition = mod.definitions[cdn[10..-1]]
	}
	mod.commit_operation

    view.invalidate

end






#----------------------------------------------------------------------------------------------------





def self.get_all_proxies
n="Proxyfied-"
m=Sketchup.active_model
s=m.selection
s.clear
m.definitions.each{|d|s.add d.instances if d.name=~/#{n}/}
end#def



def self.get_all_comps

m=Sketchup.active_model
s=m.selection
m.start_operation('!')
ds=[]
m.selection.grep(Sketchup::ComponentInstance).each{|i|ds<<i.definition}
ds.uniq.each{|d|s.add d.instances}
m.commit_operation

end#def









#-------------------------------------------------------------------------------#   mouse and keyboard



# on MouseMove perform selection
   def onMouseMove(flags, x, y, view)
          Sketchup::set_status_text("Click to Toogle Proxy. Alt= Contextual. Ctrl= All Instances.  Shift= De-Proxify ALL.", SB_PROMPT)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # do not add edges/faces to selection
         if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
   end



 #	set mod keys
def onKeyDown(key,repeat,flags,view)
       Sketchup::set_status_text("Click to Toogle Proxy. Alt= Contextual. Ctrl= All Instances.  Shift= De-Proxify ALL.", SB_PROMPT)
   @key="control" if key==COPY_MODIFIER_KEY
   @key="alt" if key==ALT_MODIFIER_KEY
   @key="shift" if key==CONSTRAIN_MODIFIER_KEY
end

 # clear keys
def onKeyUp(key,repeat,flags,view)
  @key=nil
end




# PROXIFY ON  CLICK  -----------------------------------------------------------------------------------------------------------------------------------------------------
def onLButtonDown(flags, x, y, view)
       Sketchup::set_status_text("Click to Toogle Proxy. Alt= Contextual. Ctrl= All Instances.  Shift= De-Proxify ALL.", SB_PROMPT)

						if @key=="control"  	# CTRL ONLY	-  all instances across model

									if Sketchup.active_model.selection.first.definition.name[0..9]=="Proxyfied-"
																		Proxify::get_all_comps
																		Proxify::sdm_from_proxy
																		else
																		Proxify::get_all_comps
																		Proxify::sdm_to_proxy
#																		Proxify::layerize()
																		end
							else

						if @key=="alt"    	   #  ALT ONLY		 -   contextual

									if Sketchup.active_model.selection.first.definition.name[0..9]=="Proxyfied-"
																		Proxify::compoz
																		Proxify::sdm_from_proxy
																		else
																		Proxify::compoz
																		Proxify::sdm_to_proxy
#																		Proxify::layerize()
																		end

							else

						if @key=="shift"    	   #  SHIFT ONLY		 -   RESTORE ALL COMPONENTS


									if Sketchup.active_model.selection.first.definition.name[0..9]=="Proxyfied-"
																		Proxify::get_all_proxies
																		Proxify::sdm_from_proxy
																		else
																		nil
																		end



							else								#   NO KEY  -  single instances

									if Sketchup.active_model.selection.first.definition.name[0..9]=="Proxyfied-"
																		Proxify::sdm_from_proxy
																		else
																		Proxify::sdm_to_proxy
#																		Proxify::layerize()
																		end



							end  #  if shift
									end  # if alt
											end  # if control

end #def



#--------------------------------------------------------------- #  prepares two way menu  ( if selected )


def self.goproxify

	    @model=Sketchup.active_model
        @ss=@model.selection
        if @ss.empty?
			Sketchup.active_model.select_tool (Proxify.new)  #
        end
		begin
		if Sketchup.active_model.selection.first.definition.name[0..9]=="Proxyfied-"
																Proxify::get_all_comps
																Proxify::sdm_from_proxy
																else
																Proxify::get_all_comps
																Proxify::sdm_to_proxy
																end
end
end

end # class PROXIFY

end   # mod JHS_SDMpx











=begin

														EQUAL SEGMENTS - SDMITCH

=end





	module Equal_Segment_Curve

		def self.main
			mod = Sketchup.active_model
			sel = mod.selection
			ent = mod.entities
			@typ = "Number" unless @typ
			@amt = 64 unless @amt
			#if sel.empty? || !sel[0].curve then UI.messagebox "Select a Curve";return;end
			if sel.empty? then UI.messagebox "Select an Edge";return;end
			if !sel[0].curve

				sel.each { |k| k.split 0.5}
				x=sel[0].all_connected
				sel.add x
				JHS_powerbar::JHS_TIG_W.weld()
				#puts $jhs_memory_selectionx
				sel.add $jhs_memory_selectionx
			end
			#ans=UI.inputbox(["Divide By:"," Num/Len:","Mark Vertices?"],[@typ,@amt,"Yes"],["Number|Length","","Yes|No"],"Equal Segmented Curve")
			ans=UI.inputbox(["Divide By:"," Num/Len:"],[@typ,@amt],["Number|Length"],"Equal Segmented Curve")
			if !ans then return end
			mod.start_operation "Equal Segment Curve"
			curve=sel[0].curve; ocl=curve.length; @typ=ans[0];
			@typ=="Number" ? (@amt=ans[1].to_i;num=@amt) : (@amt=ans[1].to_l;num=(ocl/@amt).ceil)
			edges=curve.edges; rad=ocl/num; last = edges.length - 1; iter = 0
			spt = edges[0].start.position; ept=edges[last].end.position
			vec = edges[last].line[1];  vec.length=rad
			# add extra edge in case of an "over shoot"
			xpt=ept.offset(vec);xedge=ent.add_line(ept,xpt)
			#	compute the end points of the equal segments
			begin
				pts = []; ncl=0; pts<<spt
				for n in 0..last
					begin
						pt=self.line_sphere_intersection(edges[n],pts[-1],rad)
						if pt.length > 0
							if edges[n].bounds.contains?(pt[0]) || xedge.bounds.contains?(pt[0])
								ncl += pt[0].distance(pts[-1])
								pts<< pt[0]; #puts pt[0]
							end
						end
					end until pts[-1] != pt[0]
				end#for
				err=ocl-ncl; chg=err/num;
				rad += chg; iter += 1
			end until (err < 0.001 || iter > 3)
			edges<<xedge; ent.erase_entities(edges)
			ent.add_curve(pts)

			$jhs_memory_selectionx =[]

		end

		def  self.line_sphere_intersection(edge,ctr,ros)
		# **********************************************************************
		# sphere_line_intersection C++ function adapted from:
		# http://astronomy.swin.edu.au/pbourke/geometry/sphereline
		# Paul Bourke pbourke@swin.edu.au
		# by atelier iebele abel - 2001.  Ruby Method by sdmitch 2011
		# Returns an array containing the point(s) of intersection.
		# **********************************************************************
			pt0 = edge.start.position
			pt1 = edge.end.position
			dx = pt1.x - pt0.x
			dy = pt1.y - pt0.y
			dz = pt1.z - pt0.z
			a =  dx**2 + dy**2 + dz**2
			b =  2*( dx*(pt0.x - ctr.x) + dy*(pt0.y - ctr.y) + dz*(pt0.z - ctr.z))
			c =  ctr.x**2 + ctr.y**2 + ctr.z**2 + pt0.x**2 + pt0.y**2 + pt0.z**2 - 2*( ctr.x*pt0.x + ctr.y*pt0.y + ctr.z*pt0.z ) - ros**2
			i =   b * b - 4 * a * c
			p = [] # initialize point array
			if ( i == 0.0 ) # one intersection
				mu = -b/(2*a) ;
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p<< Geom::Point3d.new(x,y,z)
			elsif ( i > 0.0 ) # two intersections
				# first intersection
				mu = (-b + Math.sqrt( (b**2) - 4*a*c )) / (2*a);
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p<< Geom::Point3d.new(x,y,z)
				# second intersection
				mu = (-b - Math.sqrt((b**2) - 4*a*c )) / (2*a);
				x = pt0.x + mu*dx;
				y = pt0.y + mu*dy;
				z = pt0.z + mu*dz;
				p<< Geom::Point3d.new(x,y,z)
			end
			return p;
		end

end#module

























=begin

												DROP AT INTERSECTION - RICK WILSON


=end

module JHS_drop

class Drop

   # Y value for the drop, convert to Length
   @@level = 0.m

   # validate the level entity (menu) - allow only Face entities
   def Drop::validate_level_entity
      ss = Sketchup.active_model.selection
      return nil if ss.empty?
      return true if (ss.first.kind_of? Sketchup::Face and ss.count==1)
   end

   # validate the drop entity (menu)  - allow only Groups and Components
   def Drop::validate_drop_entity
      ss = Sketchup.active_model.selection
      return nil if ss.empty?

      is_valid = nil
      ss.each do |e|
         if (e.kind_of? Sketchup::Group) or (e.kind_of? Sketchup::ComponentInstance)
            is_valid = true
         else
            is_valid = false
         end
      end

      return is_valid
   end

   # perform the drop
   def Drop::perform_drop (entity)
      position = entity.transformation.to_a
      # Y value at position 14, the rest are orientation and some view values (not sure)
      position[14] = @@level
      entity.transformation = position

      # invalidate the view to see the move action
      Sketchup.active_model.active_view.invalidate
   end

   # get the level from user and perform drop
   def Drop::drop_at
      prompts = ["enter drop level: "]
      values = [@@level]
      results = inputbox prompts, values, "Drop level"
      return if not results

      @@level = results[0]
      ss = Sketchup.active_model.selection
      ss.each do |e|
         Drop.perform_drop e
      end
   end

   # drop at intersection
   def Drop::drop_intersection
      dir = [0,0,-1]
      ss = Sketchup.active_model.selection

      ss.each do |e|
         position = e.transformation.origin
         rt = Sketchup.active_model.raytest(position,dir)
         dir_rt = [position.x, position.y, -1]
         if rt == nil
            Sketchup.active_model.active_entities.add_cline position,dir_rt
         else
            pt2 = rt[0]
            @@level = pt2[2]
            Drop.perform_drop e
         end
      end
   end

   # get face Y position for drop level
   def Drop::get_plane_position
      ss = Sketchup.active_model.selection

      if ss.first.kind_of? Sketchup::Face
         @@level = ss.first.vertices.first.position[2]
         return @@level.to_s
      end
   end

end # class Drop


end  # module JHS_drop

=begin                                            COPY ALONG PATH - RICK WILSON
=end

module JHS_Smustard

class PathCopy

	@@nCursor = 0
	@@type ||= "Spacing"
	@@count = 0
	@@spacing = 78.740157480315.inch   # 2meters
	@@menuitem = nil


	def initialize
		@path = nil
		@group = nil
		@state = 0
		@points = nil
		@madecopies = false
		if @@spacing
			@spacing = @@spacing
		else
			@spacing = 120.inch
		end
		if(@@nCursor == 0)
			path = Sketchup.find_support_file("pathcopy.png, _CURSORS")
			@@nCursor = UI::create_cursor(path,0,30) if path
		end
	end


	def activate
		if @@type=="Spacing"
			Sketchup::set_status_text("Distance between:", SB_VCB_LABEL)
			Sketchup::set_status_text(@spacing, SB_VCB_VALUE)
		else
			Sketchup::set_status_text("Copy to Vertices", SB_VCB_LABEL)
		end
		puts "activating..."
		sel = Sketchup.active_model.selection
		if sel.length>0
			if sel.first.kind_of?(Sketchup::Edge)
				if sel.first.curve
					@state = 1
					@path = sel.first.curve
					puts "path selected"
				else
					@state = 1
					@path = sel.first
					puts "path selected"
				end
			end
		    Sketchup::set_status_text("Select group/component to copy", SB_PROMPT)
		else
		    Sketchup::set_status_text("Select path", SB_PROMPT)
		end
	end



	def flat_angle(vec1)
		if vec1.kind_of?(Geom::Vector3d)
			vec=Geom::Vector3d.new(vec1.x,vec1.y,0)
			if vec.y < 0
				return 2*Math::PI-vec.angle_between(Geom::Vector3d.new(1,0,0))
			elsif vec.y > 0
				return vec.angle_between(Geom::Vector3d.new(1,0,0))
			elsif vec.y == 0
				if vec.samedirection?(Geom::Vector3d.new(1,0,0))
					return 0
				else
					return Math::PI
				end
			end
		else
			return nil
		end
	end



	def onSetCursor()
		cursor = UI::set_cursor(@@nCursor)
	end


	def onLButtonDown(flags,x,y,view)
		if @state>1
			if @@type == "Node"
				begin
					Sketchup.active_model.commit_operation
				rescue
					puts "nothing to commit"
				end
			end
			@state = 0
			@path = nil
			@group = nil
			@@count = 0
		end

		ph = view.pick_helper
		num = ph.do_pick x,y
		item = ph.best_picked
	#	puts item

		if item
			if @path==nil
				get_path(item)
				@state = 1
			else
				@group = item
				if @@type=="Spacing"
					points,rotation = getPoints(@path)	# SPACING
				else
					points = @path.vertices		# NODE
					points.collect!{|p| p.position}	# NODE
					rotation = []				# NODE
					points.each {|e| rotation<<0}	# NODE
				end

				if item.kind_of?(Sketchup::ComponentInstance)
					pathCopyComponent(item,points,rotation)
				elsif item.kind_of?(Sketchup::Group)
					pathCopyGroup(item,points,rotation)
				end

			end
		end
	end


	def get_path(item)
		if item
			if item.kind_of?(Sketchup::Edge)
				if item.curve
					@state=1
					@path=item.curve
					puts "path selected"
				else
					@state=1
					@path=item
					puts "path selected"
				end
				Sketchup::set_status_text("Select group/component to copy", SB_PROMPT)
			end
		end
		Sketchup.active_model.selection.clear
		Sketchup.active_model.selection.add(@path.edges) if @path.kind_of?(Sketchup::Curve) || @path.kind_of?(Sketchup::ArcCurve)
		Sketchup.active_model.selection.add(@path) if @path.kind_of?(Sketchup::Edge)
	end


	def onUserText(text, view)
		begin
			value = text.to_l
		rescue
			# Error parsing the text
			UI.beep
			puts "Cannot convert #{text} to a Length"
			value = nil
			Sketchup::set_status_text @spacing, SB_VCB_VALUE
		end
		return if !value

		if value == 0
			@@type = "Node"
		else
			@@type = "Spacing"
			@spacing = value
		end

		puts "onUserText: #{@@type}"

		if @state==2
			puts "need to undo last copy operation"
			Sketchup.undo if @madecopies
			@madecopies = false

			if @@type == "Spacing"
				@@count+=1
				points,rotation = getPoints(@path)
			else
				points = @path.vertices		# NODE
				points.collect!{|p| p.position}	# NODE
				rotation = []				# NODE
				points.each {|e| rotation<<0}	# NODE
			end

			pathCopyComponent(@group,points,rotation) if @group.kind_of?(Sketchup::ComponentInstance)
			pathCopyGroup(@group,points,rotation) if @group.kind_of?(Sketchup::Group)

		end
		@@spacing=@spacing
	end



	def getPoints(path)
		#puts "Getting points from path"
		runningLength = @spacing
		points = []
		rotation = []
		if path && path.kind_of?(Sketchup::Edge)
			#puts "Straight edge"
			startpoint = path.start.position
			endpoint = path.end.position
			points << startpoint
			r = flat_angle(endpoint-startpoint)
			rotation<<r
			if runningLength > path.length
				runningLength -= path.length
				#puts "edge is only #{path.length.to_f} long, runningLength is now #{runningLength}"
			elsif runningLength==path.length
				#puts "edge exactly right, adding point #{path.end.position}"
				points << endpoint
				rotation<< flat_angle(path.line[1])
				runningLength = @spacing
			else
				#puts "edge is #{path.length.to_f}, longer than runningLength #{runningLength}."
				vec = endpoint-startpoint
				vec.length = runningLength
				points<<startpoint+vec
				r = flat_angle(path.line[1])
				rotation<<r
				runningLength = @spacing-(startpoint+vec).distance(endpoint)
				#puts "subtracting #{(path.start.position+vec).distance(path.end.position).to_f} from edge, runningLength is now #{runningLength}"
				if runningLength<0
					while runningLength<0
						vec.length = @spacing
						points<<points.last+vec
						r = flat_angle(path.line[1])
						rotation<<r
						runningLength = @spacing-points.last.distance(endpoint)
						#puts "remaining edge length is #{points.last.distance(path.end.position).to_f}"
						#puts "runningLength #{runningLength}."
						#return if UI.messagebox("Continue?",MB_YESNO)==7
					end
				end
			end
		else
			#puts "curve"

			firstedge = path.edges.first
			pfirstedge = path.edges[1]
			if firstedge.start.position == pfirstedge.start.position || firstedge.start.position == pfirstedge.end.position
				startpoint = firstedge.end.position
				endpoint = firstedge.start.position
			else
				startpoint = firstedge.start.position
				endpoint = firstedge.end.position
			end

			points<<startpoint
			r = flat_angle(endpoint-startpoint)
			rotation<<r
			last_end = startpoint
			reversed_edge = false
			edgearray = path.edges
			edgearray.each do |edge|
				#puts edge

				#	ADDED THIS CHECK TO VERIFY CORRECT PLACEMENT OF POINTS ALONG CURVE - HAD BUGS WITH INCORRECT LOCATION OF OBJECTS DUE TO REVERSED EDGES IN CURVES
				if last_end == edge.start.position
					startpoint = edge.start.position
					endpoint = edge.end.position
					reversed_edge = false
					#puts "0. normal edge"
				else
					startpoint = edge.end.position
					endpoint = edge.start.position
					reversed_edge = true
					#puts "0. reversed edge"
				end
				#	END REVERSED EDGE CHECKING

				if runningLength>edge.length
					runningLength -= edge.length
					#puts "1. edge is only #{edge.length.to_f} long, runningLength is now #{runningLength}"
				elsif runningLength==edge.length
					#puts "2. edge exactly right, adding point #{endpoint}"
					points << endpoint
					rotation << flat_angle(edge.line[1])
					runningLength = @spacing
				else
					#puts "3. edge is #{edge.length.to_f}, longer than runningLength #{runningLength}"
					vec = endpoint-startpoint
					vec.length = runningLength
					pt = startpoint+vec
					points << pt
					#puts "4. added point at #{pt}; vector length is #{vec.length}"
					(reversed_edge) ?  (rotation << flat_angle(edge.line[1].reverse)) : (rotation << flat_angle(edge.line[1]))
					runningLength = @spacing-(startpoint+vec).distance(endpoint)
					#puts "5. subtracting #{(startpoint+vec).distance(endpoint).to_f} of remaining edge from runningLength; runningLength is now #{runningLength}"
					if runningLength < 0
						while runningLength < 0
							vec.length = @spacing
							points << points.last+vec
							(reversed_edge) ?  (rotation << flat_angle(edge.line[1].reverse)) : (rotation << flat_angle(edge.line[1]))
							runningLength = @spacing-points.last.distance(endpoint)
							#puts "6. remaining edge length is #{points.last.distance(endpoint).to_f}"
							#puts "7. runningLength #{runningLength}."
							#return if UI.messagebox("Continue?",MB_YESNO)==7
						end
					end
				end
				last_end = endpoint
			end
	#		puts points
		end
		@points = points
		@rotation = rotation
		return points,rotation
	end



	def pathCopyComponent(item,points,rotation)
		@@count+=1
		Sketchup.active_model.selection.clear
		model = Sketchup.active_model
		ents = model.active_entities
		puts "Copy component instance along path"
		model.start_operation("pathCopyComponent")
		0.upto(points.length-1) do |i|
			t=Geom::Transformation.new(points[i])
			tr=Geom::Transformation.rotation(points[i],Geom::Vector3d.new(0,0,1),rotation[i])
			instance = ents.add_instance(item.definition,t).transform!(tr)
		end
		model.commit_operation
		@madecopies = true
		@state=2
	end



	def pathCopyGroup(item,points,rotation)
		@@count+=1
		Sketchup.active_model.selection.clear
		model=Sketchup.active_model
		ents=model.active_entities
		puts "Copy group along path"
		model.start_operation("pathCopyGroup")
		0.upto(points.length-1) do |i|
			t1=Geom::Point3d.new(0,0,0)-item.transformation.origin
			t2=Geom::Transformation.new(points[i])
			tr=Geom::Transformation.rotation(points[i],Geom::Vector3d.new(0,0,1),rotation[i])
			gp=item.copy
			gp.transformation=item.transformation
			gp.transform!(t1)
			gp.transform!(t2)
			gp.transform!(tr)
		end
		model.commit_operation
		@madecopies = true
		@state=2
	end



	def deactivate(view)
		view.invalidate if @drawn
	end



	def onCancel(flag, view)
		self.reset(view)
	end



	def reset(view)
		@path=nil
		Sketchup::set_status_text("Select path", SB_PROMPT)
		if( view )
			view.tooltip = nil
			view.invalidate
		end

	end

end #class PathCopy

end #module jhs_Smustard




=begin        				      EXTRUDE ALONG PATH - RICK WILSON
=end

module JHSx

def self.extrude_along_path
@error=0 ### 17/8/5 ###
model = Sketchup.active_model
model.start_operation "extrude_along_path"
entities = model.active_entities
ss = model.selection
### show VCB and status info
Sketchup::set_status_text("Extrude Along Path... PARAMETERS...", SB_PROMPT)
Sketchup::set_status_text(" ", SB_VCB_LABEL)
Sketchup::set_status_text(" ", SB_VCB_VALUE)
###
if ss.empty?
  Sketchup::set_status_text("Extrude Along Path... NO SELECTION ! ", SB_PROMPT)
  UI.messagebox("No Selection to Extrude.")
  return nil
end
### #################################################################
def self.get_vertices
### this next bit is mainly thanks to Rick Wilson's weld.rb ###
	model=Sketchup.active_model
	ents=model.active_entities
	@sel=model.selection
	sl=@sel.length
	verts=[]
	edges=[]
	@newVerts=[]
	startEdge=startVert=nil
#DELETE NON-EDGES, GET THE VERTICES
	@sel.each {|item| edges.push(item) if item.typename=="Edge"}
	edges.each {|edge| verts.push(edge.vertices)}
	verts.flatten!
#FIND AN END VERTEX
	vertsShort=[]
	vertsLong=[]
	vertsEnds=[] ### 17/8/5 ### to ensure array is only ends ###
	verts.each do |v|
		if vertsLong.include?(v)
			vertsShort.push(v)
		else
			vertsLong.push(v)
		end
	end
	vertsLong.each do |v| ### 17/8/5 ### to ensure array is only ends ###
		if not vertsShort.include?(v)
			vertsEnds.push(v)
		end
	end ### 17/8/5 ### to ensure array is only ends ###
	if vertsEnds.length==0 ### i.e. it's looped ###
 		### path start or end ? ### 17/8/5 ###
		if @theEnd==0
		  startVert=vertsLong.first
		  startEdge=startVert.edges.first
		else
		  startVert=vertsLong.last
		  startEdge=startVert.edges.first
		end
		###
		closed=true
	else
		if vertsEnds.length != 2
			Sketchup::set_status_text("Extrude Along Path... PATH ERROR ! ", SB_PROMPT)
			UI.messagebox("The selected Path either branches or is not continuous ! \nCannot make an Extrusion ! \nRe-select a single continuous path... ") ### 17/8/5 ###
			@error=1
			return nil
		else
			### path start or end ? ### 17/8/5 ###
			if @theEnd==0
			  startVert=vertsEnds.first
			else
			  startVert=vertsEnds.last
			end
			###
			closed=false
			startEdge=startVert.edges.first
		end
	end
	@sel.clear
#SORT VERTICES, LIMITING TO THOSE IN THE SELECTION SET
	if startVert==startEdge.start
		@newVerts=[startVert]
		counter=0
		while @newVerts.length < verts.length
			edges.each do |edge|
				if edge.end==@newVerts.last
					@newVerts.push(edge.start)
				elsif edge.start==@newVerts.last
					@newVerts.push(edge.end)
				end
			end
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Extrude Along Path... ERROR ! ", SB_PROMPT)
				return nil if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
				@newVerts.reverse!
				reversed=true
			end
		end
	else
		@newVerts=[startVert]
		counter=0
		while @newVerts.length < verts.length
			edges.each do |edge|
				if edge.end==@newVerts.last
					@newVerts.push(edge.start)
				elsif edge.start==@newVerts.last
					@newVerts.push(edge.end)
				end
			end
			counter+=1
			if counter > verts.length
				Sketchup::set_status_text("Extrude Along Path... ERROR ! ", SB_PROMPT)
				return nil if UI.messagebox("There seems to be a problem. Try again?", MB_YESNO)!=6
				@newVerts.reverse!
				reversed=true
			end
		end
	end
	@newVerts.reverse! if reversed
#CONVERT VERTICES TO POINT3Ds
	@newVerts.collect!{|x| x.position}
### now have an array of vertices in order with NO forced closed loop ...
end ### get_vertices
### near or far ?
@theEnd=0
get_vertices
if @error==1
  return nil
end
### dialog #################################################################
alignments = ["Edge","Central(Plan)","Centroid","Central(Side)"]
enums = [alignments.join("|")]
prompts = ["Alignment: ","Width: ","Height: "]
if not $widthIn
   values = ["Central(Plan)",30.cm,100.cm]
else
   values = [$alignment,$widthIn,$heightIn]
end
results = inputbox prompts, values, enums, "Face Parameters (cm)"
return nil if not results ### i.e. the user cancelled the operation
$alignment,$widthIn,$heightIn = results
### #################################################################
### #################################################################
### restore selection set of edges and display them
def self.edge_reselector (xnewVerts)
 model = Sketchup.active_model
 ents=model.active_entities
 theEdgeX = []
 0.upto(xnewVerts.length-2) do |i| ### 1/8/5
   theEdgeX[i] = ents.add_line(xnewVerts[i],xnewVerts[i+1])  ### make vertices into edges
 end
 model.selection.clear
 model.selection.add theEdgeX
end
###def
### do main stuff #################################################################
pt1 = @newVerts[0]
pt2 = @newVerts[1]
width = $widthIn
if $widthIn == 0.mm ### can't be 0 ###
   $widthIn = 100.mm
   edge_reselector(@newVerts)
   UI.messagebox("Zero Width NOT allowed ! ")
   return nil
end
###
if $heightIn == 0.mm ### can't be 0 ###
   $heightIn = 1000.mm
   edge_reselector(@newVerts)
   UI.messagebox("Zero Height NOT allowed ! ")
   return nil
end
###
if (pt1.x == pt2.x) and (pt1.y == pt2.y) ### vertical 1st path ###
   vflag = 1
   height = $heightIn
else
   vflag = 0
   height = (($heightIn * (pt1.distance pt2)) / (pt1.distance [pt2.x,pt2.y,pt1.z]))
end
###
if $alignment == "Edge"
   offL = width
   offR = 0
   heightUp = height
   heightDn = 0
   cpatt = "__" ### 17/8/5 ###
end
if $alignment == "Central(Plan)"
   offL = width / 2
   offR = width / 2
   heightUp = height
   heightDn = 0
   cpatt = "." ### 17/8/5 ###
end
if $alignment == "Central(Side)"
   offL = width
   offR = 0
   heightUp = height / 2
   heightDn = height / 2
   cpatt = "_" ### 17/8/5 ###
end
if $alignment == "Centroid"
   offL = width / 2
   offR = width / 2
   heightUp = height / 2
   heightDn = height / 2
   cpatt = "-.-" ### 17/8/5 ###
end
### #################################################################
if vflag == 1 ### vertical ### 7/8/5
  vec = pt1.vector_to [(pt1.x + 1), pt1.y, pt1.z]
else
  vec = pt1.vector_to [pt2.x, pt2.y, pt1.z]
end
### #################################################################
piBy2 = 1.5707963267948965 ### a radian right-angle for calculating vector offset to edges
rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
pt1_leftC = pt1.offset(rotated_vecL, offL)
rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
pt1_rightC = pt1.offset(rotated_vecR, offR)
if vflag == 1 ### 1st path is vertical ### 7/8/5
  pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
  pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
  pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
  pt2_right = pt1_rightC.offset([1,0,0], heightUp)
else
  pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
  pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
  pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
  pt2_right = pt1_rightC.offset([0,0,1], heightUp)
end
###
ents=entities
### #################################################################
### add construction line along first vector ### 7/8/5
cline1 = ents.add_cline(pt1,pt2)
cline1.end=nil
cline1.start=nil
cline1.stipple=cpatt ### 17/8/5 ###
### group the extrusion... ### 7/8/5 #################################################################
group=entities.add_group
entities=group.entities
theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right) ### 7/8/5
if vflag == 1 and height > 0
   theFace.reverse!
end
if vflag == 1 and width < 0
   theFace.reverse!
end
if vflag == 1 and pt1.z > pt2.z
   theFace.reverse!
end
### check start /end of path ###
keypress = 6 ### = YES
### ask if face starts at correct side ? ### 17/8/5 ###
model.selection.clear
model.selection.add [theFace]
model.selection.add [theFace.edges]
###
Sketchup::set_status_text("Extrude Along Path... END ? ", SB_PROMPT)
keypress = UI.messagebox("Start the Face at this End ? ", MB_YESNO) ###
if keypress != 6
  ### get rid of old stuff first ###
  entities.erase_entities cline1
  entities.erase_entities model.selection
  edge_reselector(@newVerts)
  @theEnd=1
  get_vertices
  ###
  pt1 = @newVerts[0]
  pt2 = @newVerts[1]
  if (pt1.x == pt2.x) and (pt1.y == pt2.y) ### vertical 1st path ###
     vflag = 1
     height = $heightIn
  else
     vflag = 0
     height = (($heightIn * (pt1.distance pt2)) / (pt1.distance [pt2.x,pt2.y,pt1.z]))
  end
  if vflag == 1 ### vertical ### 7/8/5
    vec = pt1.vector_to [(pt1.x + 1), pt1.y, pt1.z]
  else
    vec = pt1.vector_to [pt2.x, pt2.y, pt1.z]
  end
  rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
  pt1_leftC = pt1.offset(rotated_vecL, offL)
  rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
  pt1_rightC = pt1.offset(rotated_vecR, offR)
  if vflag == 1 ### 1st path is vertical ### 7/8/5
    pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
    pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
    pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
    pt2_right = pt1_rightC.offset([1,0,0], heightUp)
  else
    pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
    pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
    pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
    pt2_right = pt1_rightC.offset([0,0,1], heightUp)
  end
  ### add NEW construction line along NEW first vector ### 17/8/5
  cline1 = ents.add_cline(pt1,pt2)
  cline1.end=nil
  cline1.start=nil
  cline1.stipple=cpatt
  ###
  theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right) ###
if vflag == 1 and height > 0
   theFace.reverse!
end
if vflag == 1 and width < 0
   theFace.reverse!
end
if vflag == 1 and pt1.z > pt2.z
   theFace.reverse!
end
end
### end sorting start / end of path ### 17/8/5 ###
###
keypress = 6 ### = YES
### ask if face starts at correct side ? ### 7/8/5
if $alignment == "Edge" or  $alignment == "Central(Side)"
  model.selection.clear
  model.selection.add [theFace]
  model.selection.add [theFace.edges]
  Sketchup::set_status_text("Extrude Along Path... SIDE ? ", SB_PROMPT)
  keypress = UI.messagebox("Start the Face at this Side ? ", MB_YESNO) ###
end
### if NO then reverse face side ### 7/8/5
if keypress != 6
  entities.erase_entities model.selection
  offR = offR * -1
  offL = offL * -1
  rotated_vecL = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 + piBy2))) ### 7/8/5
  pt1_leftC = pt1.offset(rotated_vecL, offL)
  rotated_vecR = vec.transform(Geom::Transformation.rotation(pt1, [0,0,1], (0 - piBy2))) ### 7/8/5
  pt1_rightC = pt1.offset(rotated_vecR, offR)
 if vflag == 1 ### 1st path is vertical ### 7/8/5
  pt1_left  = pt1_leftC.offset([-1,0,0], heightDn)
  pt1_right = pt1_rightC.offset([-1,0,0], heightDn)
  pt2_left  = pt1_leftC.offset([1,0,0], heightUp)
  pt2_right = pt1_rightC.offset([1,0,0], heightUp)
 else
  pt1_left  = pt1_leftC.offset([0,0,-1], heightDn)
  pt1_right = pt1_rightC.offset([0,0,-1], heightDn)
  pt2_left  = pt1_leftC.offset([0,0,1], heightUp)
  pt2_right = pt1_rightC.offset([0,0,1], heightUp)
 end
  theFace = entities.add_face(pt1_right, pt1_left, pt2_left, pt2_right)
  theFace.reverse!
 if vflag == 1 and height < 0
   theFace.reverse!
 end
 if vflag == 1 and width < 0
   theFace.reverse!
 end
end
### #################################################################
@@theEdges= []
  0.upto(@newVerts.length-2) do |i| ### 1/8/5
  @@theEdges[i] = ents.add_line(@newVerts[i],@newVerts[i+1]) ### make vertices into edges
end
### follow me along selected edges
if height > 0
  if width > 0
    theExtrusion=theFace.reverse!.followme @@theEdges ###
  end
  if width < 0
    theExtrusion=theFace.followme @@theEdges ###
  end
end
if height < 0
  if width > 0
    theExtrusion=theFace.followme @@theEdges ###
  end
  if width < 0
    theExtrusion=theFace.reverse!.followme @@theEdges ###
  end
end
###
if not theExtrusion
  Sketchup::set_status_text("Extrude Along Path... EXTRUSION ERROR ! ", SB_PROMPT)
  UI.messagebox("NO Extrusion was made ! \nThe selected Path probably branches ! \nRe-select a single continuous path... ")
  entities.erase_entities theFace.edges
  entities.erase_entities cline1
  edge_reselector(@newVerts)
  return nil
end
###
model.commit_operation
### #################################################################
### restore selection set of edges and display them
edge_reselector(@newVerts)
end ### end def


end # mod





=begin                                            SWAPZ - RICK WILSON - CADFATHER
=end



module JHS_swapz

class Swapz   #


def initialize
  @key=nil
end


   def activate
       Sketchup::set_status_text("PRESS 'ALT' TO SELECT SOURCE COMPONENT.  CLICK TO REPLACE.  'CTRL' = ALL INSTANCES.", SB_PROMPT)
      @entities = []
      @add_selection = 0
   end


 # select all same comps
   def self.compoz
	i=0
	compo_name = []
	tt = Sketchup.active_model.selection
	for e in tt
			compo_name[i] = e.definition.name
			i += 1
	end
	ss = Array.new
	ss = Sketchup.active_model.active_entities
	tt.clear
	for e in ss
		i.times do |type|
			if (e.kind_of?(Sketchup::ComponentInstance) && e.definition.name == compo_name[type])
				tt.add(e)
			end
		end
	end
end



#----------------------------------------------------------------------------------------------------



def self.get_all_comps

m=Sketchup.active_model
s=m.selection
m.start_operation('!')
ds=[]
m.selection.grep(Sketchup::ComponentInstance).each{|i|ds<<i.definition}
ds.uniq.each{|d|s.add d.instances}
m.commit_operation

end#def



def self.get_compo
	model=Sketchup.active_model
	ents=model.active_entities
	ss=model.selection.first
	$compDef=ss.definition if ss.class==Sketchup::ComponentInstance
end



def self.swap_compo
	model=Sketchup.active_model
	ents=model.active_entities
	ss=model.selection
	model.start_operation "applyTo"
	if not defined? $compDef
		return UI.messagebox("Use ALT to sample source first.")
	end
	for ci in ss
		ci.definition=$compDef if ci.class==Sketchup::ComponentInstance
	end
	model.commit_operation
end


#-------------------------------------------------------------------------------#   mouse and keyboard


# on MouseMove perform selection
   def onMouseMove(flags, x, y, view)
       Sketchup::set_status_text("PRESS 'ALT' TO SELECT SOURCE COMPONENT.  CLICK TO REPLACE.  'CTRL' = ALL INSTANCES.", SB_PROMPT)
      ph = view.pick_helper
      found = ph.do_pick(x,y)
      picked = ph.best_picked

      if (picked != nil)
         # do not add edges/faces to selection
         if (!picked.kind_of? Sketchup::Edge and !picked.kind_of? Sketchup::Face)
            Sketchup.active_model.selection.add(picked) if (picked != @entities)
            @entities = picked
         end
      else
         if @add_selection == 0
            Sketchup.active_model.selection.clear
            @entities = nil
         end
      end
   end



 #	set mod keys
def onKeyDown(key,repeat,flags,view)
       Sketchup::set_status_text("PRESS 'ALT' TO SELECT SOURCE COMPONENT.  CLICK TO REPLACE.  'CTRL' = ALL INSTANCES.", SB_PROMPT)
   @key="control" if key==COPY_MODIFIER_KEY
   @key="alt" if key==ALT_MODIFIER_KEY
end

 # clear keys
def onKeyUp(key,repeat,flags,view)
  @key=nil
end


def onLButtonDown(flags, x, y, view)

if @key=="control"  	# CTRL ONLY	-  SWAP ALL DEFINITIONS
					Swapz::get_all_comps
					Swapz::swap_compo

							else

if @key=="alt"    	   #  ALT ONLY		 -   GET DEFINITION

					 Swapz::get_compo

							else							#   NO KEY  -  SWAP SINGLE INSTANCE

					Swapz::swap_compo

end  # if alt
end  # if control

end #def




def self.goswapify

	    @model=Sketchup.active_model
        @ss=@model.selection
        if @ss.empty?
			Sketchup.active_model.select_tool (Swapz.new)  #
        end
		begin
			Swapz::get_compo
			Sketchup.active_model.select_tool (Swapz.new)
end
end







def deactivate(view)

      view.invalidate

		Sketchup::status_text=""
		Sketchup::set_status_text("", SB_VCB_LABEL)
		Sketchup::set_status_text("", SB_VCB_VALUE)

end# deactivate










end  #class


end    # module  JHS_swapz

























=begin

															LINES TO TUBES - DIDIER BUR


=end

#module JHS_LC

class ProgressBar
@@err_total_notnumeric = "ProgressBar: Total must be a positive integer."
@@err_count_notnumeric = "ProgressBar: Iteration Count must be numeric"
@@end_time             = "End @ "
@@progresschar = ">" ;
@@initial_block = "-" * 50     # Default progress bar line sequence.
def initialize(total,phase=nil)
  if (!total.integer? or total < 0)
    raise(ArgumentError,@@err_total_notnumeric)
    return ;
    end ;
  @total = total.to_i ;
  @phase = phase ;
  @firsttime = true ;
  end ;
def update(iteration)
  if !iteration.integer?
   raise(ArgumentError,@@err_count_notnumeric)
    return ;
    end ;
  iteration = [iteration.abs,@total].min  # make sure we don't exceed the total count or have a value < 0
  pct = [1,(iteration*100)/@total].max    # Calculate percentage complete.
                                      # round up to 1% if anything less than 1%.
  end_time = "?" ;
  if @firsttime then ;
    # Get the current time of day.
    # set up an elapsed timer so we can calculate expected end time.
    @time1 = Time.now ;     # Get current time of day.
    @firsttime = false ;    # turn off switch
  else
    # divide the elapsed time by the pct complete, then multiple that by 100, then add that to the
    # start time, and that is the expected end time.
    end_time = Time.at(((((Time.now-@time1).to_f)/pct)*100 + @time1.to_f).to_i).strftime("%H:%M:%S")
    end ;
  pct_pos = [pct/2,1].max ;
  current_block = @@initial_block[0,pct_pos-1] << @@progresschar << @@initial_block[pct_pos,@@initial_block.length]
  #Sketchup.set_status_text(current_block << "   " << (pct.to_s) << "%. #{@@end_time} " << end_time << " #{@phase}")
  Sketchup.set_status_text(current_block << "   " << (pct.to_s) << "% #{@phase}")
  end ;
end ; # class ProgressBar ;

def self.tubeline2cyl
model = Sketchup.active_model
m_ents = model.active_entities
materials=model.materials
ss = model.selection
ignored = 0
ss_edges=[]
ss_alone = []
ss_curves = []
ss_connected = []
trunk=[]
$l2c_diam=10.cm if not $l2c_diam
$l2c_segs=10 if not $l2c_segs
$l2c_opt="Yes" if not $l2c_opt
$l2c_mat="Default" if not $l2c_mat
$l2c_grp1="Yes" if not $l2c_grp1
$l2c_grp2="No" if not $l2c_grp2
Sketchup.set_status_text("")
model.start_operation "Lines -> Cylinders"
# Selection error checking
if ss.empty?
  UI.messagebox("No selection.")
  return nil
end
# Dialog
list2=materials.collect { |m| m.name }
if list2.length==0
  list2=["Default"]
end
drops=[["Yes","No"].join("|"),["Yes","No"].join("|"),["Yes","No"].join("|"),list2.join("|")]
prompts = ["Follow me on curves: ","Create group: ","Each tube is a group: ","Material: ","Diameter: ", "Precision:  "]
values = [$l2c_opt,$l2c_grp1,$l2c_grp2,$l2c_mat,$l2c_diam,$l2c_segs]
results = inputbox prompts, values,drops, "Parameters"
return if not results
$l2c_opt = results[0]
$l2c_grp1 = results[1]
$l2c_grp2 = results[2]
$l2c_mat = results[3]
$l2c_diam = results[4]
$l2c_segs = results[5]
# Create group if needed
if $l2c_grp1=="Yes"
  grp=m_ents.add_group
  ents=grp.entities
  else
  ents=m_ents
end
# Dispatch selection
@pb = ProgressBar.new(ss.length,"Parsing selection...")
pbcount=0
#Sketchup.set_status_text("Parsing selection...")
ss.each do |x|
  ss_edges.push(x) if x.typename=="Edge"
  pbcount+=1
  @pb.update(pbcount)
end
ignored=ss.length-ss_edges.length
if $l2c_opt=="Yes"
#Sketchup.set_status_text("Dispatching edges...")
pbcount=0
@pb = ProgressBar.new(ss_edges.length,"Dispatching edges...")
ss_edges.each do |e|
  if e.curve
    ss_curves.push(e.curve)
  else
    ss_alone.push(e)
  end


  pbcount+=1
  @pb.update(pbcount)
end
ss_curves.uniq!
#ss_connected.uniq!
ss_alone.uniq!
#Sketchup.set_status_text("Generating cylinders...")
pbcount=0
@pb = ProgressBar.new((ss_curves.length+ss_alone.length),"Generating tubes...")
# Pushpull the standalone edges in their own group
ss_alone.each do |e|
  vec = e.line[1]
  tube_group=ents.add_group
  tube_ents=tube_group.entities
  circle = tube_ents.add_circle(e.vertices[0], vec, ($l2c_diam / 2.0), $l2c_segs)
  if circle
    base = tube_ents.add_face(circle)
    base.material=$l2c_mat if $l2c_mat!="Default"
    if base
      base.reverse! if base.normal != vec
      base.pushpull e.length
    end
    # Store tube group in a trunk
    trunk.push(tube_group)
  end
  pbcount+=1
  @pb.update(pbcount)
end
# Followme's on curves
ss_curves.each do |c|
  edge1 = c.first_edge
  vec = edge1.line[1]
  ext1 = edge1.start.position
  all_edges = c.edges
  tube_group=ents.add_group
  tube_ents=tube_group.entities
  circle = tube_ents.add_circle(ext1, vec, ($l2c_diam / 2.0), $l2c_segs)
  if circle
    base = tube_ents.add_face(circle)
    base.material=$l2c_mat if $l2c_mat!="Default"
    if base
      base.reverse!
      base.followme all_edges
    end
    # Store tube group in a trunk
    trunk.push(tube_group)
  end
  pbcount+=1
  @pb.update(pbcount)
end
ss_connected.each do |conn_e|
  # find first edge
  ext_edges=[]
  conn_e.each { |e| ext_edges.push(e) if e.start.edges.length==1;
                    ext_edges.push(e) if e.end.edges.length==1 }
  vec = ext_edges[0].line[1]
  tube_group=ents.add_group
  tube_ents=tube_group.entities
  circle = tube_ents.add_circle(ext1, vec, ($l2c_diam / 2.0), $l2c_segs)
  if circle
    base = tube_ents.add_face(circle)
    base.material=$l2c_mat if $l2c_mat!="Default"
    if base
      #base.reverse!
      base.followme conn_e
    end
    # Store tube group in a trunk
    trunk.push(tube_group)
  end
end
Sketchup.set_status_text("")
end
if $l2c_opt=="No"
  pbcount=0
  @pb = ProgressBar.new(ss_edges.length,"Generating tubes...")
  # Pushpull the standalone edges in their own group
  ss_edges.each do |e|
    vec = e.line[1]
    tube_group=ents.add_group
    tube_ents=tube_group.entities
    circle = tube_ents.add_circle(e.start.position, vec, ($l2c_diam / 2.0), $l2c_segs)
    if circle
      base = tube_ents.add_face(circle)
      if base
        base.material=$l2c_mat if $l2c_mat!="Default"
        base.reverse! if base.normal != vec
        base.pushpull e.length
      end
    # Store tube group in a trunk
    trunk.push(tube_group)
    end
    pbcount+=1
    @pb.update(pbcount)
  end
end
Sketchup.set_status_text("Displaying tubes, please wait...")
# Explode all tube groups
if $l2c_grp2=="No"
 trunk.each { |tube| tube.explode if tube.valid? }
end
# UI.messagebox(ss_edges.length.to_s+" tubes created,\n"+ignored.to_s+" objects were not lines and have been ignored.")
Sketchup.set_status_text("")
model.commit_operation
end #of def

#end    # module JHS_LC


=begin														EXTRUDE LINE TOOL   (DIDIER BUR + TIG)
=end

module JHS_EX

class ExtrudeLineTool###

class Sketchup::Face
 def orient_connected_faces
    @connected_faces=[]
	self.all_connected.each{|e|
	  if e.class==Sketchup::Face
		e.edges.each{|edge|
		  if edge.faces[1]
            @connected_faces << e
            break
          end#if
        }
	  end#if
	}
    @connected_faces=[self] + @connected_faces
    @connected_faces.uniq!
	@awaiting_faces=@connected_faces
    @processed_faces=[self]
    @done_faces=[]
    msg="Orienting Faces"
    ###
    timeout=1000
    timer=0
	while @awaiting_faces[0]
      msg=msg+"."
	  @processed_faces.each{|face|
        if not @done_faces.include?(face)
	      Sketchup::set_status_text(msg,SB_PROMPT)
		  @face=face
          face_flip
        end#if
	  }
      timer+=1
      @awaiting_faces=[] if timer=timeout
    end#while
	Sketchup::set_status_text((""),SB_PROMPT)
 end#def
 def face_flip
    @awaiting_faces=@awaiting_faces-[@face]
    @face.edges.each{|edge|
      rev1=edge.reversed_in?(@face)
      @common_faces=edge.faces-[@face]
      @common_faces.each{|face|
	    rev2=edge.reversed_in?(face)
        face.reverse! if @awaiting_faces.include?(face) and rev1==rev2
	    @awaiting_faces=@awaiting_faces-[face]
	    @processed_faces<<face
	  }
    }
    @done_faces<<@face
 end#def
end#class

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?
    return bbox
end

def initialize
    @ss = Sketchup.active_model.selection
    @ip1 = nil
    @ip2 = nil
    @xdown = 0
    @ydown = 0
end

def activate
    @ip1 = Sketchup::InputPoint.new
    @ip2 = Sketchup::InputPoint.new
    @ip = Sketchup::InputPoint.new
    @drawn = false

    self.reset(nil)
end

def deactivate(view)
    view.invalidate if @drawn
end

def onMouseMove(flags, x, y, view)
    if( @state == 0 )

        @ip.pick view, x, y
        if( @ip != @ip1 )
            view.invalidate if( @ip.display? or @ip1.display? )
            @ip1.copy! @ip
            view.tooltip = @ip1.tooltip
        end
    else
        @ip2.pick view, x, y, @ip1
        view.tooltip = @ip2.tooltip if( @ip2.valid? )
        view.invalidate

        if( @ip2.valid? )
            length = @ip1.position.distance(@ip2.position)
            Sketchup::set_status_text length.to_s, SB_VCB_VALUE
        end

        if( (x-@xdown).abs > 10 || (y-@ydown).abs > 10 )
            @dragging = true
        end
    end
end

def onLButtonDown(flags, x, y, view)
    if( @state == 0 )
        @ip1.pick view, x, y
        if( @ip1.valid? )
            @state = 1
            Sketchup::set_status_text "Extrude Lines: Second Point", SB_PROMPT
            @xdown = x
            @ydown = y
        end
    else
        if( @ip2.valid? )
            self.create_geometry(@ip1.position, @ip2.position,view)
            self.reset(view)
        end
    end
    view.lock_inference
end

# The onLButtonUp method is called when the user releases the left mouse button.
def onLButtonUp(flags, x, y, view)
    if( @dragging && @ip2.valid? )
        self.create_geometry(@ip1.position, @ip2.position,view)
        self.reset(view)
    end
end

def onKeyDown(key, repeat, flags, view)
    if( key == CONSTRAIN_MODIFIER_KEY && repeat == 1 )
        @shift_down_time = Time.now
        if( view.inference_locked? )
            view.lock_inference
        elsif( @state == 0 && @ip1.valid? )
            view.lock_inference @ip1
        elsif( @state == 1 && @ip2.valid? )
            view.lock_inference @ip2, @ip1
        end
    end
end

def onKeyUp(key, repeat, flags, view)
    if( key == CONSTRAIN_MODIFIER_KEY &&
        view.inference_locked? &&
        (Time.now - @shift_down_time) > 0.5 )
        view.lock_inference
    end
end


def onUserText(text, view)

    return if not @state == 1
    return if not @ip2.valid?

    begin
        value = text.to_l
    rescue
        # Error parsing the text
        puts "Extrude Lines: \n\nCannot Convert #{text} to a Length."
        value = nil
        Sketchup::set_status_text "", SB_VCB_VALUE
    end
    return if !value

    pt1 = @ip1.position
    vec = @ip2.position - pt1
    if( vec.length == 0.0 )
        return
    end

    vec.length = value
    #pt2 = pt1 + vec
    pt2=pt1.offset vec
    #puts (pt1.distance pt2).to_s

    self.create_geometry(pt1, pt2, view)
    self.reset(view)
end


def draw(view)
    if( @ip1.valid? )
        if( @ip1.display? )
            @ip1.draw(view)
            @drawn = true
            view.draw_points @ip1.position, 8, 1, "black"
        end

        if( @ip2.valid? )
            @ip2.draw(view) if( @ip2.display? )

            view.set_color_from_line(@ip1, @ip2)
            self.draw_geometry(@ip1.position, @ip2.position, view)
            @drawn = true
        end
    end
    view.draw_points @ip2.position, 8, 1, "black"
end

# onCancel is called when the user hits the escape key
def onCancel(flag, view)
    self.reset(view)
end


# Reset the tool back to its initial state
def reset(view)
    @state = 0

    Sketchup::set_status_text("Extrude Lines: First Point", SB_PROMPT)

    @ip1.clear
    @ip2.clear

    if( view )
        view.tooltip = nil
        view.invalidate if @drawn
    end

    @drawn = false
    @dragging = false
end

# Create new geometry when the user has selected two points.
def create_geometry(p1, p2, view)

model = Sketchup.active_model
model.start_operation "Extrude Lines"
entities = model.active_entities
ss = model.selection
indice_line = 0

if ss.empty?
  UI.messagebox("Extrude Lines: \n\nNo Selection.")
  return nil
end


dx = p2.x - p1.x
dy = p2.y - p1.y
dz = p2.z - p1.z

sel=model.selection.to_a
ssa=[]
model.selection.each{|e|ssa<< e if e.class == Sketchup::Edge}
model.selection.clear
### ssa is now just the edges

cverts=[]
edges=[]
ssa.each{|e|
  cverts<< e.curve.vertices if e.curve and not cverts.include?(e.curve.vertices)
  edges<< e if not e.curve
}
cpoints=[]
cverts.each{|verts|
  pts=[]
  verts.each{|vert|pts<<vert.position}
  cpoints<< pts
}
group=model.active_entities.add_group()
gp=group.entities.add_group()
edges.each{|e|gp.entities.add_line(e.start.position,e.end.position)}
cpoints.each{|pts|gp.entities.add_curve(pts)}
ssa=gp.entities.to_a
gp_top=gp.copy
tr=Geom::Transformation.translation(p1.vector_to(p2))
gp_top.entities.transform_entities(tr, gp_top.entities.to_a)
cpoints_top=[]
cpoints.each{|points|
  pts=[]
  points.each{|point|
    pt=point.clone
    pt.transform!(tr)
    pts << pt
  }
  cpoints_top<<pts
}
clones=gp_top.entities.to_a
cverts=[]
clones.each{|e|cverts<<[e.vertices[0].position,e.vertices[1].position]}
gp.explode
gp_top.explode
entities=group.entities

0.upto(ssa.length-1) do |i|
  edge=ssa[i]
 begin
  v1 = edge.vertices[0].position
  v2 = edge.vertices[1].position
  v1_top = Geom::Point3d.new(v1.x+dx, v1.y+dy, v1.z+dz)
  v2_top = Geom::Point3d.new(v2.x+dx, v2.y+dy, v2.z+dz)
  e1=entities.add_line(v1, v1_top)
  e2=entities.add_line(v2_top, v2)
  if edge.curve or (edge.smooth? or edge.soft?)
    mid=0
    e1.start.edges.each{|e|mid=mid+1 if e.curve}
    e2.start.edges.each{|e|mid=mid+1 if e.curve}
    if mid==4
      e1.smooth = true;e1.soft = true
      e2.smooth = true;e2.soft = true
    end#if
  end#if
  e1.find_faces
 rescue
  ###
 end#begin
  i = i + 1 ### next edge
end # of upto
###
face=nil
group.entities.each{|e|
  if e.class==Sketchup::Face
    face=e
    break
  end#if
}
face.orient_connected_faces if face ### orient faces
###
### reform curves
sel.each{|e|e.explode_curve if e.class==Sketchup::Edge and e.curve}
group.entities.each{|e|e.explode_curve if e.class==Sketchup::Edge}
group.explode
###
gpx=model.active_entities.add_group()
cpoints.each{|pts|cvs=gpx.entities.add_curve(pts)}
cpoints_top.each{|pts|cvs_top=gpx.entities.add_curve(pts)}
gpx.explode
### allow for 'merged' edges in selection...
clones.each{|e|model.selection.add(e) if e.valid?}
cverts.each{|verts|
  model.active_entities.to_a.each{|e|
    if e.valid? and e.class==Sketchup::Edge
      if (e.vertices[0].position==verts[0] or e.vertices[0].position==verts[1]) and (e.vertices[1].position==verts[0] or e.vertices[1].position==verts[1])
        model.selection.add(e)### as it's a matching edge
      end#if
    end#if
  }
}

end#def

# Draw the geometry
def draw_geometry(pt1, pt2, view)
    view.draw_line(pt1, pt2)
end

end # class ExtrudeLineTool

#-----------------------------------------------------------------------------
# This functions is just a shortcut for selecting the new tool
def self.ex_linetool
    Sketchup.active_model.select_tool ExtrudeLineTool.new
end


end   # module    JHS_EXTRUDE LINES


=begin                                            ALIGN TOOL - DIDIER BUR
=end


module Align
class AlignTool
def initialize
    @ip1 = nil
    @ip2 = nil
    @ip3 = nil
    @ip4 = nil
    @ip5 = nil
    @ip6 = nil
    @xdown = 0
    @ydown = 0
    $align_points=[]
    @sel = Sketchup.active_model.selection
    @state = 0

    if @sel.empty?
      UI.messagebox("No selection to align.")
      Sketchup.send_action "selectSelectionTool:"
    end

    if Align.component_selected and $comp == true
      @mro_axes = Sketchup.active_model.rendering_options["DisplayInstanceAxes"]
      Sketchup.active_model.rendering_options["DisplayInstanceAxes"] = true
      t = @sel[0].transformation
      $align_points[0]=t.origin
      $align_points[1]=t.origin.offset t.xaxis
      $align_points[2]=t.origin.offset t.yaxis
      @state = 3
    end

end
# When the tool is first selected.
def activate
    Sketchup.active_model.start_operation "Align"
    @ip1 = Sketchup::InputPoint.new
    @ip2 = Sketchup::InputPoint.new
    @ip3 = Sketchup::InputPoint.new
    @ip4 = Sketchup::InputPoint.new
    @ip5 = Sketchup::InputPoint.new
    @ip6 = Sketchup::InputPoint.new
    @ip = Sketchup::InputPoint.new
    @drawn = false
    self.reset(nil)
end
# When the tool is deactivated because a different tool was selected
def deactivate(view)
    view.invalidate if @drawn
end
# Whenever the user moves the mouse.
def onMouseMove(flags, x, y, view)
    #if( @state == 0 )
        # We are getting the first point (start origin)
        @ip.pick view, x, y
        if( @ip != @ip1 )
            view.invalidate if( @ip.display? or @ip1.display? )
            @ip1.copy! @ip
            # set the tooltip that should be displayed to this point
            #view.tooltip = @ip1.tooltip
        end
    #end

  case @state
    when 0
      view.tooltip = "Click start origin point"
    when 1
      view.tooltip = "Click start X axis orientation"
    when 2
      view.tooltip = "Click start Y axis orientation"
    when 3
      view.tooltip = "Click end origin point"
    when 4
      view.tooltip = "Click end X axis orientation"
    when 5
      view.tooltip = "Click end Y axis orientation"
  end

end
# When the user presses the left mouse button.
def onLButtonDown(flags, x, y, view)
    # When the user clicks the first time, we switch to getting the
    # second point.  When they click a second time we create the line

    #puts "State: " + @state.to_s
    @ip1.pick view, x, y
#if( @ip1.valid? )
  case @state
    when 0
      Sketchup::set_status_text("Origin", SB_PROMPT)
      @xdown = x
      @ydown = y
      view.tooltip = @ip1.tooltip if( @ip1.valid? )
      $align_points.push @ip1.position
      @state = 1
      view.invalidate
    when 1
      Sketchup::set_status_text("X Axis", SB_PROMPT)
      @xdown = x
      @ydown = y
      @ip2.pick view, x, y, @ip1
      view.tooltip = @ip2.tooltip if( @ip2.valid? )
      $align_points.push @ip2.position
      @state = 2
      view.invalidate
    when 2
      Sketchup::set_status_text("Y Axis", SB_PROMPT)
      @xdown = x
      @ydown = y
      @ip3.pick view, x, y, @ip1
      view.tooltip = @ip3.tooltip if( @ip3.valid? )
      $align_points.push @ip3.position
      @state = 3
      view.invalidate
    when 3
      Sketchup::set_status_text("End origin", SB_PROMPT)
      @xdown = x
      @ydown = y
      @ip4.pick view, x, y, @ip1
      view.tooltip = @ip4.tooltip if( @ip4.valid? )
      $align_points.push @ip4.position
      @state = 4
      view.invalidate
    when 4
      Sketchup::set_status_text("End X axis", SB_PROMPT)
      @xdown = x
      @ydown = y
      @ip5.pick view, x, y, @ip1
      view.tooltip = @ip5.tooltip if( @ip5.valid? )
      $align_points.push @ip5.position
      @state = 5
      view.invalidate
    when 5
      Sketchup::set_status_text("End Y axis", SB_PROMPT)
      @xdown = x
      @ydown = y
      @ip6.pick view, x, y, @ip1
      view.tooltip = @ip6.tooltip if( @ip6.valid? )
      $align_points.push @ip6.position
      @state = 6
      view.invalidate

    # Clear any inference lock
    view.lock_inference
  end #case
#end
end
# user releases the left mouse button.
def onLButtonUp(flags, x, y, view)
  self.reset(view)
  self.align_geometry(view) if @state == 6
end
# User presses a key on the keyboard.
# We are checking it here to see if the user pressed the shift key
# so that we can do inference locking
def onKeyDown(key, repeat, flags, view)
    if( key == CONSTRAIN_MODIFIER_KEY && repeat == 1 )
        @shift_down_time = Time.now
        # if we already have an inference lock, then unlock it
        if( view.inference_locked? )
        # calling lock_inference with no arguments actually unlocks
            view.lock_inference
        elsif( @state == 0 && @ip1.valid? )
            view.lock_inference @ip1
        elsif( @state == 1 && @ip2.valid? )
            view.lock_inference @ip2
        elsif( @state == 2 && @ip3.valid? )
            view.lock_inference @ip3
        elsif( @state == 3 && @ip4.valid? )
            view.lock_inference @ip4
        elsif( @state == 4 && @ip5.valid? )
            view.lock_inference @ip5
        elsif( @state == 5 && @ip6.valid? )
            view.lock_inference @ip6
        end
    end
end
# User releases the key
# We use this to unlock the interence
# If the user holds down the shift key for more than 1/2 second, then we
# unlock the inference on the release.  Otherwise, the user presses shift
# once to lock and a second time to unlock.
def onKeyUp(key, repeat, flags, view)
  return if not @state == 5
    if( key == CONSTRAIN_MODIFIER_KEY &&
        view.inference_locked? &&
        (Time.now - @shift_down_time) > 0.5 )
        view.lock_inference
    end
end
# onUserText is called when the user enters something into the VCB
def onUserText(text, view)
    return if not @state == 5
end
# The draw method is called whenever the view is refreshed.  It lets the
# tool draw any temporary geometry that it needs to.
def draw(view)
    if( @ip1.valid? )
        if( @ip1.display? )
            @ip1.draw(view)
            @drawn = true
        end
    end

  if $align_points.length == 1
    view.draw_points $align_points, 8, 2, "black"
  end

  if $align_points.length == 2
    view.line_width = 4
    view.draw_points $align_points[0], 8, 2, "black"
    #view.draw_points $align_points[1], 12, 7, "red"
    view.drawing_color = "red"
    view.draw_line $align_points[0], $align_points[1]
  end

  if $align_points.length == 3
    view.line_width = 4
    view.draw_points $align_points[0], 8, 2, "black"
    #view.draw_points $align_points[1], 12, 7, "red"
    #view.draw_points $align_points[2], 12, 7, "green"
    view.drawing_color = "red"
    view.draw_line $align_points[0], $align_points[1]
    view.drawing_color = "green"
    view.draw_line $align_points[0], $align_points[2]
    # Draw Z axis
    $v_start_x = $align_points[0].vector_to $align_points[1]
    $v_start_y = $align_points[0].vector_to $align_points[2]
    $v_start_z = $v_start_x.cross $v_start_y
    $v_start_z.length = ($v_start_x.length+$v_start_y.length)/2.0
    $p_start_z = $align_points[0].offset $v_start_z
    view.drawing_color = "blue"
    view.draw_line($align_points[0], $p_start_z)
  end

  if $align_points.length == 4
    view.line_width = 4
    view.draw_points $align_points[0], 8, 2, "black"
    #view.draw_points $align_points[1], 12, 7, "red"
    #view.draw_points $align_points[2], 12, 7, "green"
    view.draw_points $align_points[3], 8, 1, "black"
    view.drawing_color = "red"
    view.draw_line $align_points[0], $align_points[1]
    view.drawing_color = "green"
    view.draw_line $align_points[0], $align_points[2]
    view.drawing_color = "black"
    view.line_width = 1
    view.line_stipple = "-"
    view.draw_line $align_points[0], $align_points[3]
  end
  if $align_points.length == 5
    view.line_width = 4
    view.draw_points $align_points[0], 8, 2, "black"
    #view.draw_points $align_points[1], 12, 7, "red"
    #view.draw_points $align_points[2], 12, 7, "green"
    view.draw_points $align_points[3], 8, 1, "black"
    #view.draw_points $align_points[4], 12, 6, "red"
    view.drawing_color = "red"
    view.draw_line $align_points[0], $align_points[1]
    view.drawing_color = "green"
    view.draw_line $align_points[0], $align_points[2]
    view.drawing_color = "black"
    view.line_width = 1
    view.line_stipple = "-"
    view.draw_line $align_points[0], $align_points[3]
    view.line_stipple = "_"
    view.line_width = 4
    view.drawing_color = "red"
    view.draw_line $align_points[3], $align_points[4]
  end
  if $align_points.length == 6
    view.line_width = 4
    view.draw_points $align_points[0], 8, 2, "black"
    #view.draw_points $align_points[1], 12, 7, "red"
    #view.draw_points $align_points[2], 12, 7, "green"
    view.draw_points $align_points[3], 8, 1, "black"
    #view.draw_points $align_points[4], 12, 6, "red"
    #view.draw_points $align_points[5], 12, 6, "green"
    view.drawing_color = "red"
    view.draw_line $align_points[0], $align_points[1]
    view.drawing_color = "green"
    view.draw_line $align_points[0], $align_points[2]
    view.drawing_color = "black"
    view.line_width = 1
    view.line_stipple = "-"
    view.draw_line $align_points[0], $align_points[3]
    #view.line_stipple = ""
    view.line_width = 4
    view.line_stipple = "-"
    view.drawing_color = "red"
    view.draw_line $align_points[3], $align_points[4]
    view.drawing_color = "green"
    view.draw_line $align_points[3], $align_points[5]
    # Draw Z axis
    $v_end_x = $align_points[3].vector_to $align_points[4]
    $v_end_y = $align_points[3].vector_to $align_points[5]
    $v_end_z = $v_end_x.cross $v_end_y
    $v_end_z.length = ($v_end_x.length+$v_end_y.length)/2.0
    $p_end_z = $align_points[3].offset $v_end_z
    view.line_stipple = "_"
    view.drawing_color = "blue"
    view.draw_line $align_points[0], $p_start_z
    view.draw_line $align_points[3], $p_end_z

  end

  view.lock_inference
end
# onCancel is called when the user hits the escape key
def onCancel(flag, view)
    self.reset(view)
    self.align_geometry(view) if @state == 5
    if $align_points.length < 6
      # We got less than 6 points
      UI.messagebox($align_points.length.to_s + " points are not enough !")
      self.reset(view)
    end
end
# Internal methods that are used to support the other methods in this class.
# Reset the tool back to its initial state
def reset(view)
    # This variable keeps track of which point we are currently getting
    #@state = 0
    case @state
      when 0
        Sketchup::set_status_text("Start origin", SB_PROMPT)
      when 1
        Sketchup::set_status_text("Start X axis", SB_PROMPT)
      when 2
        Sketchup::set_status_text("Start Y axis", SB_PROMPT)
      when 3
        Sketchup::set_status_text("End origin", SB_PROMPT)
      when 4
        Sketchup::set_status_text("End X axis", SB_PROMPT)
      when 5
        Sketchup::set_status_text("End Y axis", SB_PROMPT)
      when 6
        Sketchup::set_status_text("", SB_PROMPT)
    end

    # clear the InputPoint
    @ip1.clear
    if( view )
        view.tooltip = nil
        view.invalidate if @drawn
    end
    @drawn = false
    @dragging = false
end
# Create new geometry when the user has selected enough points.
def align_geometry(view)
ents_array=[]
if $align_points.length == 6
  self.reset(view)
  # Build array of entities
  @sel.each do |e|
    ents_array.push e
  end



  $v_start_x2=$v_start_x
  py2=$align_points[2].project_to_line [$align_points[0],$align_points[1]]
  $v_start_y2 = py2.vector_to $align_points[2]
  $v_start_z2=$v_start_x2.cross $v_start_y2

  $v_end_x2=$v_end_x
  py3=$align_points[5].project_to_line [$align_points[3],$align_points[4]]
  $v_end_y2 = py3.vector_to $align_points[5]
  $v_end_z2=$v_end_x2.cross $v_end_y2



  x_angle = $v_start_y2.angle_between $v_end_y2
  y_angle = $v_start_z2.angle_between $v_end_z2
  z_angle = $v_start_x2.angle_between $v_end_x2

  answer = 7
  if $comp == true
    case @sel[0].typename
      when "ComponentInstance"
        answer = UI.messagebox("Copy " + @sel[0].definition.name +  " component ?", MB_YESNO, "Option")
      when "Group"
        answer = UI.messagebox("Duplicate this group ?" , MB_YESNO, "Option")
    end
  end


  transfo1 = Geom::Transformation.axes $align_points[0], $v_start_x2, $v_start_y2, $v_start_z2
  transfo2 = Geom::Transformation.axes $align_points[3], $v_end_x2, $v_end_y2, $v_end_z2
  case answer
    when 7
      Sketchup.active_model.entities.transform_entities(transfo1.invert!, ents_array)
      Sketchup.active_model.entities.transform_entities(transfo2, ents_array)
    when 6
      case @sel[0].typename
        when "Group"
          new_group = @sel[0].copy
          Sketchup.active_model.entities.transform_entities(transfo1.invert!, new_group)
          Sketchup.active_model.entities.transform_entities(transfo2, new_group)
        when "ComponentInstance"
          # Get transformation of original
          org_trans = @sel[0].transformation
          new_comp = Sketchup.active_model.entities.add_instance @sel[0].definition, org_trans
          #new_comp = Sketchup.active_model.entities.add_instance @sel[0].definition, transfo1
          #new_comp.transformation = org_trans
          #new_comp.transformation = org_trans*transfo2
          Sketchup.active_model.entities.transform_entities(transfo1.invert!, new_comp)
          Sketchup.active_model.entities.transform_entities(transfo2, new_comp)
      end
  end

  # reset points array, old display axes, and exit
  $align_points=[]
  Sketchup.active_model.rendering_options["DisplayInstanceAxes"] = @mro_axes
  Sketchup.send_action "selectSelectionTool:"
  Sketchup.active_model.commit_operation
end
end
end # class AlignTool
#---------------------------------------
# Check if there is something to align
#---------------------------------------
def Align.something_selected
  ss = Sketchup.active_model.selection
  if ss.empty?
    return nil
  else
    return true
  end
end
#---------------------------------------
# Check if selection is a group/component
#---------------------------------------
def Align.component_selected
  ss = Sketchup.active_model.selection
  if ss.empty? or ss.length > 1
    r = false
  elsif ss.length == 1 and (ss[0].typename == "Group" or ss[0].typename == "ComponentInstance")
    r = true
  end
  r
end
#---------------------------------------
# Select the Align tool
#---------------------------------------
def Align.tool
    $comp = false
    Sketchup.active_model.select_tool AlignTool.new
end
def Align.toolc
    $comp = true
    Sketchup.active_model.select_tool AlignTool.new
end
end # module Align
#-----------------------------------------------------------------------------
# Shortcut for selecting the new tool within the UI
#-----------------------------------------------------------------------------
def align_tool
  Sketchup.active_model.select_tool AlignTool.new
end

$align_loaded = true




=begin

														UPRIGHT EXTRUDER - ENEROTH


=end

module JHS_Ene_uprightExtruder

#Module variables
@vectorUpright = Geom::Vector3d.new(0,0,1)


#UI.start_timer(0.1, false){ tb.restore }#Use timer as workaround for bug 2902434.

def self.flattenVector(vector,plane)
	#Flatten vector to plane
	#plane's point does not affect output, only its normal does

	pointStart = plane[0]
	normal = plane[1]
	pointVectorEnd = pointStart.offset vector#Point at end of vector
	lineToPlane = [pointVectorEnd, normal]#Line from previous point towards plane
	pointIntesection = Geom.intersect_line_plane lineToPlane, plane#Point where previous line intersects vector
	outputVector = pointIntesection.- pointStart#vector from startpoint to intersection
	return outputVector
end#def

def self.removeOrSoftenSmooth(edge)
	#Removes edge if bounded faces are co-planar, otherwise soften
	if edge.faces.length == 2
		onPlane = true
		edge.faces[1].vertices.each do |i|
			#Use classify_point instead of normal vector to see if face is co-planar. More precise
			onPlane = false if (edge.faces[0].classify_point i.position) == Sketchup::Face::PointNotOnPlane
		end#each
		if onPlane
			edge.erase!
		else
			edge.soft = edge.smooth = true
		end#if
	end#if
end#def

def self.userInput
	#User interface
	#In its own definition in so extruder itself can be called from other scripts

	#Selection is used as input
	#Must contain one face that is to be extruded
	#Must contain connected lines used as path

	#Get input from selection
	edges = []#Edges forming path
	faces = []#Face to extrude
	Sketchup.active_model.selection.each do |i|
		edges << i if i.class == Sketchup::Edge
		faces << i if i.class == Sketchup::Face
	end#each

	#Validate input
	if faces.length == 0
		UI.messagebox("Please select a face to extrude.")
		return
	end#if
	if faces.length > 1
		UI.messagebox("Cannot extrude multiple faces at once.")
		return
	end#if
	edges.delete_if{|i| i.faces.include? faces[0]}
	if edges.length == 0
		UI.messagebox("Please select one or more connected edges to extrude face along.")
		return
	end#if

	#Path
	path = []
	#Indexes of edges already put to path array
	edgesUsed = []
	#Add coordinates of first edge to path
	path << edges[0].start.position
	path << edges[0].end.position
	edgesUsed << 0
	#Double loop edges
	(0..(edges.length-1)).each do
		(0..(edges.length-1)).each do |i|
			#If edge is not already used and connects to an end of path, add edge's other point to that end
			unless edgesUsed.include? i
				if edges[i].start.position == path[0]
					path.unshift edges[i].end.position
					edgesUsed << i
				elsif edges[i].end.position == path[0]
					path.unshift edges[i].start.position
					edgesUsed << i
				elsif edges[i].start.position == path[-1]
					path << edges[i].end.position
					edgesUsed << i
				elsif edges[i].end.position == path[-1]
					path << edges[i].start.position
					edgesUsed << i
				end#if elsif
			end#unless
		end#each
	end#each

	#Validate path
	unless edgesUsed.length == edges.length
		UI.messagebox("Please select a valid path of edges to extrude face along.")
		return
	end#unless

	#Start operation
	Sketchup.active_model.start_operation "Extrude Path"

	#Call extrude function
	self.extrude(faces[0], path, @vectorUpright)
	Sketchup.status_text= "Face has been extruded."

	#End operation
	Sketchup.active_model.commit_operation

end#def

def self.setUpright
	#Set "upright" vector by coordinates

	prompts = ["X (Red)", "Y (Green)", "Z (Blue)"]
	defaults = @vectorUpright.to_a
	title = "Set \"Upright\" Vector"
	input = UI.inputbox prompts, defaults, title
	return unless input
	vector = Geom::Vector3d.new
	(0..2).each do |i|
		vector[i] = input[i]
	end#each
	unless vector.valid?
		UI.messagebox("Vector cannot be zero length.")
		return
	end#unless
	@vectorUpright = vector
	Sketchup.status_text= "\"Upright\" vector has been set."
end#def

def self.setUptightFromEdge
	#Set "upright" vector from edge

	ss = Sketchup.active_model.selection
	unless ss.length == 1 and ss[0].class == Sketchup::Edge
		UI.messagebox("An edge must be selected.")
		return
	end#unless
	@vectorUpright = ss[0].line[1]
	Sketchup.status_text= "\"Upright\" vector has been set."
end#def

def self.extrude(extrusion, path, vectorUpright=nil)
	#Actual extrude function.
	#Either called from userInput or external script.
	#Extrusion is Face face about to be extruded, path an Array of Points3ds to follow, vectorUpright is the vector to align extrusion to (by default upright)
	#If path start and ends with same point it's considered closed.
	#If not closed, the face is extruded relative to the nearest end of the path.
	#If closed, the face is extruded relative to its nearest point in the path.

	#Set vectorUpright to vertical if not set
	vectorUpright = Geom::Vector3d.new(0,0,1) unless vectorUpright

	#Validate input
	if vectorUpright.class != Geom::Vector3d
		UI.messagebox("No valid vector set.")
		return
	end#if
	if extrusion.class != Sketchup::Face
		UI.messagebox("No valid extrusion face set.")
	end#if

	#Check whether path is closed
	closed = (path[0] == path[-1])

	#Get drawing context of extrusion face(group, componentdefinition or model)
	drwingContext = extrusion.parent
	ents = drwingContext.entities

	#Get material of extrusion face
	mat = extrusion.material
	matBack = extrusion.back_material

	#Prepare path
	if closed
		#Closed path, make the point along path closest to the face the start point (not limited to corners)
		#Closest to face means closest to first vertex returned #NOTE: use center of gravity?

		#Declare vars for closest point, distance and point index where to add the new point to path array
		#Start with first point
		indexOfClosest = 0
		pClosest = path[0]
		dClosest = extrusion.vertices[0].position.distance path[0]

		#Loop all segments
		(0..(path.length-2)).each do |i|
			#Get point on segment that is closest to face's first vertex
			p1 = path[i]
			p2 = path[i+1]
			line = [p1,(p2.-p1)]
			p = extrusion.vertices[0].position.project_to_line line
			#Check if point is on actual segment
			if(p1.distance(p) > p1.distance(p2))
				#Point is beyond p2, use p2 instead
				p = p2
			elsif(p2.distance(p) > p2.distance(p1))
				#Point is beyond p1, use p1 instead
				p = p1
			end#if elsif

			#Check if this point is closer than the closest one seen so far
			d = extrusion.vertices[0].position.distance p
			next if d > dClosest

			indexOfClosest = i
			pClosest = p
			dClosest = d
		end#each

		#Add closest point to path array if not already there (= if not a corner)
		unless(path.include? pClosest)
			indexOfClosest+= 1
			path.insert(indexOfClosest, pClosest)
		end#unless

		#Temporary remove last point in path since it's the same as the first
		path.pop
		#Rotate array so start point is first
		(1..indexOfClosest).each do
			path << path[0]
			path.shift
		end#each
		#Copy first point to end of path so it again start and ends at the same place
		path << path[0]
	else
		#Open path, make path end closest to extrusion the start point
		#Closest to face means closest to first vertex returned #NOTE: use center of gravity?
		if ((extrusion.vertices[0].position.distance path[-1]) < (extrusion.vertices[0].position.distance path[0]))
			path.reverse!
		end#if
	end#if

	#Points around face to extrude
	startProfilePoints = []
	extrusion.outer_loop.vertices.each do |i|
		startProfilePoints << i.position
	end#each

	#Smooth points, indexes of points between curve segments
	smoothPoints = []
	(0..(extrusion.outer_loop.vertices.length-1)).each do |i|
		vertex = extrusion.outer_loop.vertices[i]
		smoothPoints << i if vertex.edges[0].curve != nil and vertex.edges[1].curve != nil
	end#each

	#Points of previous profile, used when drawing edges between profiles
	previousProfilePoints = startProfilePoints

	#Edges of previous profile, will be removed if co-planar
	#Starts as empty array since edges of first profile shouldn't be soften
	previousProfileEdges = []

	#Find first vector of path, used for rotation
	if closed
		#Closed path, use flatten average of vectors along segment before and after point
		vector1 = flattenVector((path[1].- path[0]), [Geom::Point3d.new, vectorUpright]).normalize
		vector2 = flattenVector((path[0].- path[-2]), [Geom::Point3d.new, vectorUpright]).normalize
		vectorRotationStart = Geom::linear_combination 1, vector1, 1, vector2
	else
		#Not closed path, use flatten vector along first segment
		vectorRotationStart = flattenVector((path[1].- path[0]), [Geom::Point3d.new, vectorUpright]).normalize
	end#if elsif

	#Find angle in start point if closed oath, used for scaling
	if closed
		angeInStartPoint = flattenVector((path[0].- path[-2]), [Geom::Point3d.new, vectorUpright]).angle_between flattenVector((path[1].- path[0]), [Geom::Point3d.new, vectorUpright])
		scaleStart = 1/(Math.cos(angeInStartPoint/2))
	end

	#If closed path, remove extrusion face
	extrusion.erase! if closed

	#Unless closed path, remove extrusion face if all its edges bind other faces (consistency with push-pull & followme)
	unless closed
		removeStartFace = true
		extrusion.outer_loop.edges.each do |j|
			removeStartFace = false unless j.faces.length == 2
		end#each
		extrusion.erase! if removeStartFace
	end#unless

	#Loop path each point but first (it's profile is already drawn since it's the face being extruded)
	#Draw a profiled on each point aligned to both the path and the upright vector
	#Connect this profile with previous profile
	(1..(path.length-1)).each do |i|

		#Translate transformation
		vectorMove = path[i].- path[0]
		translation = Geom::Transformation.translation vectorMove

		#Rotate transformation
		if i == (path.length-1)
			#Last point
			if closed
				#Closed path
				vectorRotation = vectorRotationStart
			else
				#Not closed path
				vectorRotation = flattenVector((path[i].- path[i-1]), [Geom::Point3d.new, vectorUpright]).normalize
			end#if
		else
			#Not last nor first point
			vector1 = flattenVector((path[i].- path[i-1]), [Geom::Point3d.new, vectorUpright]).normalize
			vector2 = flattenVector((path[i+1].- path[i]), [Geom::Point3d.new, vectorUpright]).normalize
			vectorRotation = Geom::linear_combination 1, vector1, 1, vector2
		end#if
		#Find angle to rotate(positive engle returned)
		rotationAngle = vectorRotation.angle_between vectorRotationStart
		#Check if angle should be negative
		negAnglePointForward = Geom::Point3d.new.offset vectorRotationStart
		negAnglePointPos = negAnglePointForward.transform(Geom::Transformation.rotation(Geom::Point3d.new, vectorUpright, Math::PI/2))
		negAnglePointNeg = negAnglePointForward.transform(Geom::Transformation.rotation(Geom::Point3d.new, vectorUpright, -Math::PI/2))
		negAnglePointAlongPath = Geom::Point3d.new.offset vectorRotation
		rotationAngle*= -1 if (negAnglePointAlongPath.distance negAnglePointNeg) < (negAnglePointAlongPath.distance negAnglePointPos)
		#Create transformation
		rotation = Geom::Transformation.rotation path[i], vectorUpright, rotationAngle

		#Scale transformation
		#To keep the sides of the extrusion parallel (consistency with followme)
		#Done as several subtransformation that are later merged to the actual scaling transformation
		#angle path turns in this point, projected to plane perpendicular to vectorUprigh, doesn't matter if positive or negative
		if i == (path.length-1)
			#Last point
			if closed
				#Closed path
				angeInPoint = angeInStartPoint
			else
				#Not closed path
				angeInPoint = 0
			end#if
		else
			#Not last nor first point
			angeInPoint = flattenVector((path[i].- path[i-1]), [Geom::Point3d.new, vectorUpright]).angle_between flattenVector((path[i+1].- path[i]), [Geom::Point3d.new, vectorUpright])
		end
		scale = 1/(Math.cos(angeInPoint/2))
		#Divide scale with scale of first profile. If first profile is in corner its width wont be the with on extrusion between corners
		scale/= scaleStart if closed
		#create scale transformation from origin in x axis
		scaling = Geom::Transformation.scaling Geom::Point3d.new, scale, 1, 1
		#Rotate scale transformation
		rScaling = Geom::Transformation.axes path[i], (vectorRotation.cross vectorUpright), vectorRotation, vectorUpright
		scaling = rScaling.*scaling.*(rScaling.inverse)

		#Perform translation
		profilePoints = []
		startProfilePoints.each do |j|
			profilePoints << j.transform((scaling.*rotation).*(translation))
		end

		#Draw profile
		#NOTE: why draw faces?
		face = ents.add_face profilePoints
		pofilesEdges = face.edges
		face.erase!

		#Connect corresponding points to previous profile
		smoothExtrudeLines = []
		(0..(profilePoints.length-1)).each do |j|
			edgeStraight = ents.add_line profilePoints[j], previousProfilePoints[j]
			smoothExtrudeLines << edgeStraight if smoothPoints.include? j
		end#each

		#Draw diagonals and form faces towards previous profile
		diagonals = []
		(0..(profilePoints.length-1)).each do |j|
			#NOTE: better name for nextIndex
			nextIndex = j+1
			nextIndex = 0 if nextIndex == profilePoints.length
			edge = ents.add_line profilePoints[j], previousProfilePoints[nextIndex]
			diagonals << edge
			#Draw each face at a time instead of using edge.find_faces. The face connected to previous segment should be drawn first to prevent back face problems.
			ents.add_face profilePoints[j], previousProfilePoints[j], previousProfilePoints[nextIndex]
			ents.add_face profilePoints[j], profilePoints[nextIndex], previousProfilePoints[nextIndex]
			#Paint new faces
			edge.faces.each do |k|
				k.material = mat
				k.back_material = matBack
			end#each
		end#each

		#Soften and remove co-planar edges in diagonals, previous profile, and edges extruded from curve
		#NOTE: Add a separate array of what points in path should have its profile soften, soften if one of the edges connected to the point is a part of a curve (like native follow me)?
		#NOTE: extrude() could be called with second parameter for softening profiles. All hard, all soft or array of soft corners. userInput() could set this depending on if vertices holds edges that are part of curves. modifier keys for all/none soften.
		(diagonals + previousProfileEdges+ smoothExtrudeLines).each do |j|
			removeOrSoftenSmooth(j)
		end#each

		#Save data for next iteration
		previousProfilePoints = profilePoints
		previousProfileEdges = pofilesEdges
	end#each

	#If closed path, soften first/last profile's edges
	if closed
		previousProfileEdges.each do |i|
			removeOrSoftenSmooth(i)
		end#each
	end#if

	#Add end face unless all its edges bind an outer face or path is closed(consistency with push-pull & followme)
	unless closed
		drawEndFace = false
		previousProfileEdges.each do |i|
			drawEndFace = true if i.faces.length != 2
		end#each
		if drawEndFace
			endFace = ents.add_face previousProfileEdges
			endFace.material = mat
			endFace.back_material = matBack
		end#if
	end#unless

	#Return end face. Can be used by other plugins calling the extruder
	return endFace if endFace

end#def

end#module    ene upright extruder






=begin                                            3D ROTATE FIX - ENEROTH
=end

require 'extensions.rb'

module JHS_Ene3D


PLUGIN_ROOT = File.dirname(__FILE__) unless defined?(self::PLUGIN_ROOT)


def self.on_edge?(e, p, include_vertices)
	#Checks if point is between edge vertices
	#Doesn't check if on same line, just if closer to edge's vertices than they are to each other.
	#Definition might need a better name

	p_start = e.start.position
	p_end = e.end.position

	if include_vertices
		p_start.distance(p) <= p_start.distance(p_end) && p_end.distance(p) <= p_end.distance(p_start)
	else
		p_start.distance(p) < p_start.distance(p_end) && p_end.distance(p) < p_end.distance(p_start)
	end

end#def

def self.break_edge_on_vertices(entities1, entities2, also_break_on_edge = true)
	#Call this after a transformations is done
	#Very similar to what the native tools do but lacks API call to it

	#Turn entities objects into entity arrays
	if entities1.class == Sketchup::Entities
		entities = []
		entities1.each { |i| entities << i}
		entities1 = entities
	end
	if entities2.class == Sketchup::Entities
		entities = []
		entities2.each { |i| entities << i}
		entities2 = entities
	end

	#Add bounding edges of faces
	entities1edges = []
	entities1.each { |i| i.edges.each{|j| entities1edges << j} if i.class == Sketchup::Face}
	entities1+= entities1edges
	entities1.uniq!

	entities2edges = []
	entities2.each { |i| i.edges.each{|j| entities2edges << j} if i.class == Sketchup::Face}
	entities2+= entities2edges
	entities2.uniq!

	#Find intersections
	#Don't split edges here since that would prevent the new edges from being intersected with the part of the edge chopped off
	to_split = []
	#Each element of to_split is an array containing and edge and a point
	entities1.each do |e1|
		next unless e1.class == Sketchup::Edge
		entities2.each do |e2|
			next unless e2.class == Sketchup::Edge

			l1 = e1.line
			l2 = e2.line
			p = Geom.intersect_line_line l1, l2

			next unless p
			next if !on_edge?(e1, p, true) || !on_edge?(e2, p, true)

			if also_break_on_edge
				#Sketchup 7+ behavior. break intersecting edges

				if on_edge?(e1, p, false)
					#intersection is on edge 1 and not on one of its vertices
					to_split << [e1, p]
				end
				if on_edge?(e2, p, false)
					#intersection is on edge 2 and not on one of its vertices
					to_split << [e2, p]
				end
			else
				#Sketchup 6- behavior. Only break edges on endpoints

				e1.vertices.each do |v|
					to_split << [e2, p] if p == v.position
				end
				e2.vertices.each do |v|
					to_split << [e1, p] if p == v.position
				end

			end

		end
	end

	#Group edges in to_split
	#Each element in to_split should now be an array containing an edge and an array of points where to split it
	#Every time the edge is split the new edge is added to the edge sub-array
	#Later the points try splitting the split-off edges as well as the edge it originally were meant to split
	to_split_new = []
	to_split.each do |i|
		#Find element in new array whose edge is the one of this element in old array
		element_in_new = (to_split_new.select { |j| j[0][0] == i[0] } )[0]
		if element_in_new
			#If corresponding element were found, add intersection point from this
			element_in_new[1] << i[1]
		else
			#Otherwise, create a new element corresponding this element
			to_split_new << [[i[0]], [i[1]]]
		end

	end
	to_split = to_split_new

	#Split edges
	to_split.each do |i|
		i[1].each do |p|
			i[0].each do |e|
				new_e = e.split p
				if new_e
					i[0] << new_e#Save split off edge to array so other points meant to split the same edge can be tried on the split off edges too
					break
				end
			end#each
		end#each
	end#each

	#Split faces
	if also_break_on_edge

		#Simple 1d array of edges split
		edges_split = []
		to_split.each do |i|
			i[0].each { |j| edges_split << j}
		end#each

		#Find all faces connected to a split edge
		faces = []
		edges_split.each do |i|
			i.faces.each { |j| faces << j}
		end#each
		faces.uniq!

		#NOTE: only intersect faces in same plane

		#Find all bounding edges to faces
		faces_and_edges = []
		faces.each do |i|
			i.edges.each { |j| faces_and_edges << j }
		end#each
		faces_and_edges.uniq!

		ents = Sketchup.active_model.entities
		i_t = Geom::Transformation.new
		ents.intersect_with false, i_t, ents, i_t, true, faces_and_edges

	end#if

end#def

#end#module fix




=begin                                            3D ROTATE - ENEROTH
=end



#module JHS_Ene_3dRotate



class Tool3d

	def initialize
		#Declare vars
		@pAnchor	#Origin for rotation
		@pStart1	#Grab selection here...
		@pTarget1	#...and drag towards this point (yaw, pitch)
		@pStart2	#Optionally grab once again here...
		@pTarget2	#...and drag towards this point (roll)

		@cursor = UI.create_cursor File.join(PLUGIN_ROOT, "_CURSORS/3d_rotate.png"), 16, 14

	end#def

	def activate
		#Tool is selected

		#Initialize input points
		@pAnchor = Sketchup::InputPoint.new
		@pStart1 = Sketchup::InputPoint.new
		@pTarget1 = Sketchup::InputPoint.new
		@pStart2 = Sketchup::InputPoint.new
		@pTarget2 = Sketchup::InputPoint.new
		@ip = Sketchup::InputPoint.new

		@ph = Sketchup.active_model.active_view.pick_helper

		#If nothing is selected from the start, what's hovered on the first click becomes the selection
		@hasSelection = (Sketchup.active_model.selection.length != 0)

		#Needs edge breaking, changed to true when first rotation is performed. Not reset when tool is since edge breaking is needed until tool is exit
		@needs_e_b = false

		#Reset state counter
		self.reset(nil)

	end#def

	def onSetCursor
		UI.set_cursor @cursor
	end#def

	def deactivate(view)
		if(@needs_e_b)
			#Break edges on endpoints, and on other edges if Sketchup.break_edges? == true
			#Same as for several native tools

			#No API call for this :'(
			#Use own function (external file)

			Sketchup.active_model.start_operation("3D Rotate")

			ent = Sketchup.active_model.active_entities
			ent_array = []
			ent.each { | i| ent_array << i }
			ent_rotated_array = []
			Sketchup.active_model.selection.each { | i| ent_rotated_array << i }

			#ent_array-= ent_rotated_array

			Ene_3dRotate::break_edge_on_vertices ent_rotated_array, ent_array, Sketchup.break_edges?

			Sketchup.active_model.commit_operation
		end#if

		#Update screen on deactivation if rotation was performed
		view.invalidate if(@rotated)
	end#def

	def reset(view)
		#Clear input points
		@pAnchor.clear
		@pStart1.clear
		@pTarget1.clear
		@pStart2.clear
		@pTarget2.clear

		#Point currently being selected
		@state = 0

		#Status text for first input point
		#Status text is set as a class variable so it can be used in resume()
		@status_text = @hasSelection ? "Pick rotation origin." : "Pick entity and rotation origin."
		Sketchup::set_status_text(@status_text, SB_PROMPT)

		#Update view if necessary
		view.invalidate if(@rotated and view)

		#Set rotated flag to false
		@rotated = false
	end#def

	def onCancel(flag, view)
		#Reset tool when pressing ESC or reselecting it
		self.reset(view)
	end

	def resume(view)
		#Reset status text after tool has been temporarily deactivated
		Sketchup::set_status_text(@status_text, SB_PROMPT)
	end#def

	def draw(view)
		#Updates the view when necessary. Outlines input point and adds temporary guidelines
		#Achorpoint
		@pAnchor.draw(view) if(@pAnchor.valid? and@pAnchor.display?)
		#Start1 and line to anchorpoint
		if(@pStart1.valid?)
			#Draw point
			@pStart1.draw(view) if(@pStart1.display?)
			#Draw temporary line (anchor - start)
			view.line_stipple = "-"
			view.set_color_from_line @pAnchor.position, @pStart1.position
			view.draw_line(@pAnchor.position, @pStart1.position)
		end
		#target1 and line to anchorpoint and start1
		if(@pTarget1.valid?)
			#Draw point
			@pTarget1.draw(view) if(@pTarget1.display?)
			#Draw temporary line (anchor - target)
			view.drawing_color = Sketchup::Color.new(128, 128, 128)
			view.line_stipple = "_"
			view.draw_line(@pAnchor.position, @pTarget1.position)
			#Draw temporary line (start - target)
			view.line_stipple = "."
			view.draw_line(@pStart1.position, @pTarget1.position)
		end
		#Define plane roll is rotated in
		if(@pAnchor.valid? and @pTarget1.valid?)
			rollPlane = [@pAnchor.position, (@pTarget1.position.- @pAnchor.position)]
		end
		#Start2 and lines to anchorpoint
		if(@pStart2.valid?)
			#Draw point
			@pStart2.draw(view) if(@pStart2.display?)
			#Draw temporary line (anchor - start put to plane)
			view.line_stipple = "-"
			view.set_color_from_line @pAnchor.position, @pStart1.position.project_to_plane(rollPlane)
			view.draw_line(@pAnchor.position, @pStart2.position.project_to_plane(rollPlane))
			#Draw temporary line (start put to plane - start)
			view.line_stipple = "."
			view.drawing_color = Sketchup::Color.new(128, 128, 128)
			view.draw_line(@pStart2.position.project_to_plane(rollPlane), @pStart2.position)
		end
		#Target2 and lines to anchorpoint & start2
		if(@pTarget2.valid?)
			#Draw point
			@pTarget2.draw(view) if(@pTarget2.display?)
			#Draw temporary line (anchor - target put to plane)
			view.line_stipple = "_"
			view.drawing_color = Sketchup::Color.new(128, 128, 128)
			view.draw_line(@pAnchor.position, @pTarget2.position.project_to_plane(rollPlane))
			#Draw temporary line (target put to plane - target)
			view.line_stipple = "."
			view.draw_line(@pTarget2.position.project_to_plane(rollPlane), @pTarget2.position)
			#Draw temporary line (start put to plane - target put to plane)
			view.line_stipple = "."
			view.draw_line(@pStart2.position.project_to_plane(rollPlane), @pTarget2.position.project_to_plane(rollPlane))
		end
	end#def

	def onMouseMove(flags, x, y, view)
		#Temporarily select what's hovered if northing's selected
		#Temporary selection stays when tool first clicks
		unless(@hasSelection)
			#Clear selection
			Sketchup.active_model.selection.clear

			#Select what's hovered
			@ph.do_pick(x, y)
			picked = @ph.best_picked
			if(@ip.degrees_of_freedom == 0 and @ip.vertex and picked.class != Sketchup::Group and picked.class != Sketchup::ComponentInstance)
				#when hovering endpoints in this drawing context, select all edges it's binding just like native rotate tool
				picked = @ip.vertex.edges
			end#if
			Sketchup.active_model.selection.add picked if(picked)

		end#if

		#Find points when hovering
		if(@state == 0)
			#Anchorpoint

			#NOTE: possible awesome feature:
			#when pressing shift while selecting anchor point:
			#	rotate Yawpicth using center of gravity as start1 and downwards as target1
			#	set state to 3 (skipping manually selecting start1 and target1)
			#Statusbar saying "Shift = Hang from point."(?)

			@ip.pick view, x, y
			if( @ip != @pAnchor )
				#Input point has moved
				#Update view
				view.invalidate
				#Set anchorpoint to current hovered point
				@pAnchor.copy! @ip
			end#if

		elsif(@state == 1)
			#start1 point

			@ip.pick view, x, y, @pAnchor
			if( @ip != @pStart1 )
				#Input point has moved
				#Update view
				view.invalidate
				 #Set start1 point to current hovered point
				@pStart1.copy! @ip
			end#if

		elsif(@state == 2)
			#target1 point

			@ip.pick view, x, y, @pStart1
			if( @ip != @pTarget1 )
				#Input point has moved

				#Snap to radius and edge/c-line
				#(please please please, add this to the native Sketchup rotate tool)
				#Flag that changes tooltip
				smartEdgeSnap = false
				if(@ip.degrees_of_freedom == 1)
					#Point is on edge or c-line

					#Find the points on line at the same distance from anchor as start1 is
					#intersect line with a sphere with anchorPoint as center and length from anchor to start1 as radius

					if(@ip.edge)
						#Line from edge
						line = @ip.edge.line
						line.each{|i| i.transform!(@ip.transformation)}
					else
						#Line from c-line (I wish there was a c-line property for input points)
						@ph.do_pick(x, y)
						#NOTE: Returns entities in groups/components but not parent drawing context (and not inside groups in parent)
						picked = @ph.leaf_at(0)
						if(picked.class == Sketchup::ConstructionLine)
							line = [picked.position, picked.direction]
							line.each{|i| i.transform!(@ph.transformation_at(0))}
						end#if
					end#if
					if(line)
						center = @pAnchor.position
						radius = @pStart1.position.distance(@pAnchor.position)
						intersections = self.intersectLineSphere(line, center, radius)
						if(intersections)
							#Intersections found
							intersections.each do |i|
								iOnScreen = view.screen_coords i
								if(iOnScreen.x > x-10 and iOnScreen.x < x+10 and iOnScreen.y > y-10 and iOnScreen.y < y+10)
									#Mouse is within 10px from point, snap to it
									#Set smart edge snap flag to change tooltip
									smartEdgeSnap = true
									#Move input point to intersection
									@ip = Sketchup::InputPoint.new(i)
									@ip.pick view, x, y, @ip
								end#if
							end#each
						end#if
					end#if
				end

				#Update view
				view.invalidate
				#Set target1 point to current hovered point
				@pTarget1.copy! @ip
			end#if

		elsif(@state == 3)
			#start2 point (optional)

			@ip.pick view, x, y, @pAnchor
			if( @ip != @pStart2 )
				#Input point has moved
				#Update view
				view.invalidate
				#Set start2 point to current hovered point
				@pStart2.copy! @ip
			end#if

		elsif(@state == 4)
			#target2 point

			@ip.pick view, x, y, @pStart2
			if( @ip != @pTarget2 )
				#Input point has moved

				#Snap to radius and edge
				#(please please please, add this to the native Sketchup rotate tool)
				#Flag that changes tooltip
				smartEdgeSnap = false
				if(@ip.degrees_of_freedom == 1 and @ip.position.on_plane?([@pAnchor.position, @pTarget1.position.-(@pAnchor.position)]))
					#Point is on edge or c-line and in the plane of the rotation

					#Find the points on line at the same distance from anchor as start1 is
					#intersect line with a sphere with anchorPoint as center and length from anchor to start1 as radius

					if(@ip.edge)
						#Line from edge
						line = @ip.edge.line
						line.each{|i| i.transform!(@ip.transformation)}
					else
						#Line from c-line (I wish there was a c-line property for input points)
						@ph.do_pick(x, y)
						#NOTE: Returns entities in groups/components but not parent drawing context
						picked = @ph.leaf_at(0)
						if(picked.class == Sketchup::ConstructionLine)
							line = [picked.position, picked.direction]
							line.each{|i| i.transform!(@ph.transformation_at(0))}
						end#if
					end#if
					if(line)
						center = @pAnchor.position
						radius = @pStart2.position.distance(@pAnchor.position)

						intersections = self.intersectLineSphere(line, center, radius)

						if(intersections)
							intersections.each do |i|
								iOnScreen = view.screen_coords i
								if(iOnScreen.x > x-10 and iOnScreen.x < x+10 and iOnScreen.y > y-10 and iOnScreen.y < y+10)
									#Mouse is within 10px from point, snap to it
									#Set smart edge snap flag to change tooltip
									smartEdgeSnap = true
									#Move imput point to intersection
									@ip = Sketchup::InputPoint.new(i)
									@ip.pick view, x, y, @ip
								end#if
							end#each
						end#if
					end#if
				end

				#Update view
				view.invalidate
				#Set target1 point to current hovered point
				@pTarget2.copy! @ip
			end#if

		end#if elsif

		#Set point tooltip
		view.tooltip = smartEdgeSnap ? "From Radius" : @ip.tooltip
	end#def

	def onLButtonDown(flags, x, y, view)
		#Do nothing with this click if nothing is selected
		return nil if(Sketchup.active_model.selection.length == 0)

		#Keep what's currently selected.
		#Might be temporarily selected because its hovered if there was no selection when tool was selected
		@hasSelection = true

		#Select point when and draw when clicking
		#first 3 clicks select points and set jaw & pitch. 4th and 5th click are optional and sets roll
		if(@state == 0)
			#Select anchorpoint
			#@pAnchor.pick view, x, y
			if(@pAnchor.valid?)
				@state=1
				@status_text = "Grab entity."#first status text defined in reset()
				Sketchup::set_status_text(@status_text, SB_PROMPT)
			end#if

		elsif(@state == 1)
			#Select startpoint 1
			#@pStart1.pick view, x, y, @pAnchor
			if(@pStart1.valid?)
				@state=2
				@status_text = "Pick target to drag entity towards."
				Sketchup::set_status_text(@status_text, SB_PROMPT)
			end#if

		elsif(@state == 2)
			#Select targetpoint 1 and set yaw and pitch for selection
			#@pTarget1.pick view, x, y, @pAnchor
			if(@pTarget1.valid?)
				@state=3
				@status_text = "(Optional) Grab entity again."
				Sketchup::set_status_text(@status_text, SB_PROMPT)

				self.rotateYawPitch(@pAnchor.position, @pStart1.position, @pTarget1.position)

				#Set rotated flag so script knows whether to update view or not when leaving tool
				@rotated = true
			end#if
		elsif(@state == 3)
			#Select startpoint 2
			#@pStart2.pick view, x, y, @pAnchor
			if(@pStart1.valid?)
				@state=4
				@status_text = "Pick target to drag entity towards."
				Sketchup::set_status_text(@status_text, SB_PROMPT)
			end#if
		elsif(@state == 4)
			#Select targetpoint 2 and set roll for selection
			#@pTarget2.pick view, x, y, @pAnchor
			if(@pTarget2.valid?)

				self.rotateRoll(@pAnchor.position, @pStart2.position, @pTarget2.position,(@pTarget1.position).-(@pAnchor.position))

				self.reset(view)
			end#if
		end#if elsif
	end#def

	def flattenVector(plane, vector)
		#Flatten vector to plane
		#plane's point does not affect output, only its normal does

		pointStart = plane[0]
		normal = plane[1]

		#Point at end of vector
		pointVectorEnd = pointStart.offset vector
		#point projected to plane
		pointIntesection = pointVectorEnd.project_to_plane plane
		#vector from startpoint to intersection
		outputVector = pointIntesection.- pointStart
		return outputVector
	end#def

	def angle_between_in_Plane(plane, v1, v2)
		#Get angle between vectors in a given plane
		#Can return negative angles unlike built in angleBetween method

		#Flatten to plane
		v1 = self.flattenVector(plane,v1)
		v2 = self.flattenVector(plane,v2)

		#Get angle between
		a = v1.angle_between v2

		#Determine if angle is positive or negative (rotate v2 90 degrees cc and cw seen from above and see which is closest to v2)
		negAnglePointForward = Geom::Point3d.new.offset v2
		negAnglePointPos = negAnglePointForward.transform(Geom::Transformation.rotation(Geom::Point3d.new, plane[1], Math::PI/2))
		negAnglePointNeg = negAnglePointForward.transform(Geom::Transformation.rotation(Geom::Point3d.new, plane[1], -Math::PI/2))
		negAnglePointCheck = Geom::Point3d.new.offset v1
		a*= -1 if (negAnglePointCheck.distance negAnglePointNeg) < (negAnglePointCheck.distance negAnglePointPos)

		return a
	end#def

	def intersectLineSphere(line, center, radius)
		#Get intersection points between line and sphere

		lineStart = line[0]
		lineVector = line[1].normalize

		#Calculate distance from line's start along line to intersections

		firstTerm = -(lineVector.dot(lineStart.-(center)))
		secondTermSquared = (lineVector.dot(lineStart.-(center)))**2 - (lineStart.-(center)).dot(lineStart.-(center)) + radius**2

		#If root is less than 0 the line does not intersect the sphere and there's no point on the line to snap to
		return nil if(secondTermSquared < 0)

		secondTerm = Math.sqrt(secondTermSquared)

		sum1 = firstTerm+secondTerm
		sum2 = firstTerm-secondTerm

		#Find points
		lineVector.length = sum1
		point1 = lineStart.offset lineVector

		lineVector.length = sum2
		point2 = lineStart.offset lineVector

		#return points
		return [point1, point2]
	end#def

	def rotateYawPitch(pAnchor, pStart, pTarget)
		#Rotate geometry yaw & pitch

		#When tool is deselected edges needs to be broken
		@needs_e_b = true

		#Vector geometry is rotated around. Don't affect result if rotateRoll is called too
		vUp = Geom::Vector3d.new(0,0,1)

		#Create vectors from points
		vStart = pStart.- pAnchor
		vTarget = pTarget.- pAnchor

		#Create transformations

		#Yaw (only when not start nor target is directly above/under anchor)
		unless(vUp.parallel? vTarget or vUp.parallel? vStart)
			#z, upwards
			axis = vUp
			#angle between target and start in horizontal plane from anchorpoint
			angle = angle_between_in_Plane([Geom::Point3d.new,vUp], vTarget, vStart)
			yaw = Geom::Transformation.rotation pAnchor, axis, angle
		end

		#Pitch
		if(not vUp.parallel? vTarget)
			#Axis is perpendicular to vTarget and in horizontal plane unless target is directly above/below anchor
			axis = flattenVector([Geom::Point3d.new,vUp],vTarget).transform(Geom::Transformation.rotation(Geom::Point3d.new, vUp, Math::PI/2))
		elsif(not vUp.parallel? vStart)
			#Otherwise axis is perpendicular to vStart and in horizontal plane unless start too is directly above/below anchor
			axis = flattenVector([Geom::Point3d.new,vUp],vStart).transform(Geom::Transformation.rotation(Geom::Point3d.new, vUp, Math::PI/2))
		elsif(not vStart.samedirection? vTarget)
			#Otherwise, if both target and start is directly above/below anchor but on different sides,
			#just use a random axis in horizontal plane.
			#An additional roll will get rid of the randomness
			axis = Geom::Vector3d.new(1,0,0)
		else
			#If both points are on the same side of anchor and directly above/below it nothing needs to be done
			return nil
		end
		#pitch difference between vTarget and vStart
		angle = vUp.angle_between(vTarget) - vUp.angle_between(vStart)
		pitch = Geom::Transformation.rotation pAnchor, axis, angle

		#Combine translations
		trans = yaw ? (pitch.* yaw) : pitch

		Sketchup.active_model.start_operation("3D Rotate", true, true, false)
		#Rotate
		entsSelected = []
		Sketchup.active_model.selection.each{|i| entsSelected << i}
		Sketchup.active_model.entities.transform_entities trans, entsSelected
		Sketchup.active_model.commit_operation

	end#def

	def rotateRoll(pAnchor, pStart, pTarget, axis)

		#Create vectors from points
		vStart = pStart.- pAnchor
		vTarget = pTarget.- pAnchor

		plane = [Geom::Point3d.new,axis]
		angle = angle_between_in_Plane(plane, flattenVector(plane, vTarget), flattenVector(plane, vStart))
		trans = Geom::Transformation.rotation pAnchor, axis, angle

		Sketchup.active_model.start_operation("3D Rotate", true, true, false)
		#Rotate
		entsSelected = []
		Sketchup.active_model.selection.each{|i| entsSelected << i}
		Sketchup.active_model.entities.transform_entities trans, entsSelected
		Sketchup.active_model.commit_operation

	end#def

end#class
end     #module JHS_Ene3D












=begin

														FACE FINDER (ITHIL)


=end

module JHS_IT_FF

def self.ithil_facefinder
	model = Sketchup.active_model
	selection = model.selection
	selection = model.active_entities if selection.empty?
	model.start_operation ("FaceFinder")
	process_at_level_finder selection, {}
	model.commit_operation
end

def self.process_at_level_finder(entities, hcomp)
	entities.each do |entity|
		case entity.typename
		when 'Group'
			process_at_level_finder entity.entities, hcomp
		when 'ComponentInstance'
			edef = entity.definition
			next if hcomp[edef.to_s]
			hcomp[edef.to_s] = edef
			process_at_level_finder edef.entities, hcomp
		when 'Edge'
			entity.find_faces
		end
	end
end

end   # module







=begin

														SketchyFFD  (CHRIS, GLENN, TIG)


=end



module SketchyFFD ###

if Sketchup.version.to_f < 7.0
  UI.messagebox("SketchyFFD needs Sketchup >=7.0.\nVisit sketchup.google.com and upgrade.")
  return nil
end

@@debugFFD=false ### make true to get lots of messages!

# Show the Ruby Console at startup for debugFFDging
Sketchup.send_action("showRubyPanel:") if @@debugFFD

#Infinity = 1.0/0.0 ### NOT needed

# VARIABLE DEFINITIONS ### some set as @@
# grp = selected group to be deformed
 @@ffdGroup=nil ### used to access the group to be deformed
# numControlPoints = number of control points in x, y, z axis
# numControlSections = numControlPoints - 1 in each dimension
# bSubdivide = logical to determine if user wants to subdivide selected group
# size = size of group to be deformed
 @@latticeGroup=nil ###FFD Control Point lattice
 @@allVerts=[]
 @@allVertWeights=[]
 @@observer=nil

 @@w=4
 @@d=4
 @@h=4
 @@s="false"

#initialize the program

def self.initiate()
  #Right click UI menu...
 unless file_loaded?(__FILE__)###
  UI.add_context_menu_handler{|menu|
    puts("I'm creating the context menu...") if @@debugFFD
    menu=menu.add_submenu("- FFD...")
    if !Sketchup.active_model.selection.empty? && Sketchup.active_model.selection[0].is_a?(Sketchup::Group)
      menu.add_item("2x2 FFD"){self.startFFD(Sketchup.active_model.selection[0],[2,2,2],"false")}
      menu.add_item("3x3 FFD"){self.startFFD(Sketchup.active_model.selection[0],[3,3,3],"false")}
      menu.add_item("NxN FFD"){
        prompts = ["Width: ","Depth: ","Height: ","Subdivide: "]
        values = [@@w, @@d, @@h, @@s]
        results = inputbox(prompts, values,["","","","true|false"], "FFD Dimensions")
        if results
		  @@w,@@d,@@h,@@s=results
		  @@w=1 if @@w<1
		  @@d=1 if @@d<1
		  @@h=1 if @@h<1
          self.startFFD(Sketchup.active_model.selection[0],[@@w,@@d,@@h],@@s)
        end
      }
    end
    menu.add_item("Lock edges"){
      Sketchup.active_model.start_operation("Lock edges")
      Sketchup.active_model.selection.each{|ent|
        if ent.is_a?(Sketchup::Edge)
          ent.vertices.each{|v| v.set_attribute("SFFD","locked",true)}
        end
      }
      Sketchup.active_model.commit_operation
    }
    menu.add_item("Unlock edges"){
      Sketchup.active_model.start_operation("Unlock edges")
      Sketchup.active_model.selection.each{|ent|
        if ent.is_a?(Sketchup::Edge)
          ent.vertices.each{|v| v.set_attribute("SFFD","locked",false)}
        end
      }
      Sketchup.active_model.commit_operation
    }
    menu.add_item("Make patch..."){
      prompts = ["Width","Depth","Cell Width","Cell Depth"]
      values = [4, 4, 10, 10]
      results = UI.inputbox(prompts, values, "Patch FFD dimensions")
      Sketchup.active_model.start_operation("Create FFD patch")
      grp=Sketchup.active_model.active_entities.add_group() ###
      0.upto(results[0]-2){|w|
        0.upto(results[1]-2){|h|
          xform=Geom::Transformation.new([w*results[2],h*results[3],0])
          xform=xform * Geom::Transformation.scaling(results[2],results[3], 1.0)
          f=[[0,0,0].transform(xform),
             [0,1,0].transform(xform),
             [1,1,0].transform(xform),
             [1,0,0].transform(xform) ]
          grp.entities.add_face(f)
        }
      }
      Sketchup.active_model.commit_operation
      Sketchup.active_model.selection.clear
      Sketchup.active_model.selection.add(grp)
      if (results)
        self.startFFD(Sketchup.active_model.selection[0],[results[0],results[1],1])
      end
    }
  }
 end#unless
 file_loaded(__FILE__)###
end

#called from context menu to create a control lattice and calculate the vertex weights for a group.
def self.startFFD(grp, numControlPoints, bSubdivide)

    puts("in startFFD...") if @@debugFFD

  #put group to be deformed into global so all methods can access it
  @@ffdGroup=grp
  #@@ffdGroup.name="ffdGroup"
  #make sure this group is unique or chaos ensues.
  begin ###
    @@ffdGroup.make_unique if @@ffdGroup.entities.parent.instances[1]
  rescue
    ###
  end
    #calculate size of the Def group
    sizeDefG=([@@ffdGroup.bounds.width,@@ffdGroup.bounds.height,@@ffdGroup.bounds.depth])
    puts sizeDefG if @@debugFFD

  Sketchup.active_model.start_operation("Create FFD group",true)

    numControlSections=[numControlPoints[0]-1,numControlPoints[1]-1,numControlPoints[2]-1]

    #if user selected Subdivide, do it
    self.dice_group(sizeDefG,numControlSections) if(bSubdivide=="true")

    #calculate size of the Def group
    sizeDefG=([@@ffdGroup.bounds.width,@@ffdGroup.bounds.height,@@ffdGroup.bounds.depth])

      puts("after dice_group") if @@debugFFD
      puts sizeDefG if @@debugFFD

    #create CP lattice group
    @@latticeGroup=self.createControlLattice(sizeDefG,numControlSections)
    @@latticeGroup.name="FFD control points"

    #store the current transformation in an attribute
    @@latticeGroup.set_attribute("controlLattice","currentTransformation",@@ffdGroup.transformation.to_a)

    puts("in startFFD after createControlLattice") if @@debugFFD

    #calculate the D group's vertex weights
    self.initFFD(sizeDefG,numControlSections)

  Sketchup.active_model.commit_operation

end

  #called from startFFD to calculate the groups vertex weights.
  def self.initFFD(sizeDefG,numControlPoints)
      puts("in initFFD...") if @@debugFFD

    #create arrary of vertices
    @@allVerts=[]
    @@ffdGroup.entities.each{|ent|
      if ent.is_a?(Sketchup::Edge) && ent.curve
        ent.explode_curve #all curves need to be exploded for deform to work right.
      end
    }
    @@ffdGroup.entities.each{|ent|
      begin #guard against entities that dont have verts.
        ent.vertices.each{|vert|
          @@allVerts.push(vert)
        }
      rescue
        puts "Warning:entities of type '#{ent.typename}' can't be deformed."
      end
    }
    #allverts now contains redundant verts; remove duplicates.
    @@allVerts.uniq!

      puts @@allVerts.length if @@debugFFD

    #Global to hold the calculated weight for each vertex per control point.
    @@allVertWeights=[]

    #this loop could be dramaticly optimized.
    vi=0
    @@allVerts.each{|vert|
      stuv=vert.position
      stuv.x=stuv.x/sizeDefG[0];stuv.y=stuv.y/sizeDefG[1];stuv.z=stuv.z/sizeDefG[2]
      #calc weights.
      stuv.x=0.0 if(stuv.x.to_f.nan?)
      stuv.y=0.0 if(stuv.y.to_f.nan?)
      stuv.z=0.0 if(stuv.z.to_f.nan?)
      stuv.x=1.0 if(stuv.x.to_f.infinite?)
      stuv.y=1.0 if(stuv.y.to_f.infinite?)
      stuv.z=1.0 if(stuv.z.to_f.infinite?)

      puts stuv.inspect if @@debugFFD
      weights=[]
      0.upto(numControlPoints[0]){|x|
        bx=self.calcBernstein(x,numControlPoints[0],stuv.x)
        0.upto(numControlPoints[1]){|y|
          bxy=bx*self.calcBernstein(y,numControlPoints[1],stuv.y)
          0.upto(numControlPoints[2]){|z|
            weights.push(bxy * self.calcBernstein(z,numControlPoints[2],stuv.z))
          }
        }
      }
      @@allVertWeights.push(weights)
      vert.set_attribute("SFFD","locked",false)

      vi=vi+1
      Sketchup.set_status_text("Weighing  #{vi} of #{@@allVerts.length}") if(vi%100==0)

      }
  end

    #called by initFFD to calculate the Bernstein polynomial.
    #Uses a table for speed.
    #thanks to steven-arts for the idea.
    def self.calcBernstein(i, n, u) #Bernstein Polynomial
      binomialTable=[] #1 Feb 2010 initialize the array on each call
      begin
        binomial=binomialTable[i][n]
      rescue
        binomialTable[i]=[] if(binomialTable[i]==nil)
        binomialTable[i][n]=self.ffdfactorial(n).to_f / (self.ffdfactorial(n - i).to_f * self.ffdfactorial(i).to_f)
        binomial=binomialTable[i][n]
      end
      #binomial = factorial(n).to_f / (factorial(n - i).to_f * factorial(i).to_f)
      bernstein = binomial * (u**i) * ((1-u)**(n-i))
      return(bernstein)
    end

      #called by calcBernstein. Uses a table for speed.
      #thanks to steven-arts for the idea.
      def self.ffdfactorial(n)
        ffdfactorialTable = []
        sum=ffdfactorialTable[n]
        return sum if sum

        sum = 1
        sum.upto(n) { |i| sum *= i }
        ffdfactorialTable[n]=sum
        return sum
      end

  #Called from startFFD, subdivides goemetry when input parameter is true
  def self.dice_group(sizeDefG,numControlSections)

      puts("in dice_group") if @@debugFFD

    #find the origin of the D group
    ffdGroupOrigin=@@ffdGroup.bounds.min
    diceGroup=Sketchup.active_model.active_entities.add_group()
    diceGroup=diceGroup.move!(ffdGroupOrigin)
    diceGroup.name="diceGroup"

    #calculate the size of the edges for the diceGroup
    xstep=sizeDefG[0].to_f/numControlSections[0]
    ystep=sizeDefG[1].to_f/numControlSections[1]
    zstep=sizeDefG[2].to_f/numControlSections[2]

    #handle dim of 0 (1)
    xstep=0.0 if(xstep.to_f.nan?)
    ystep=0.0 if(ystep.to_f.nan?)
    zstep=0.0 if(zstep.to_f.nan?)
    xstep=1.0 if(xstep.to_f.infinite?)
    ystep=1.0 if(ystep.to_f.infinite?)
    zstep=1.0 if(zstep.to_f.infinite?)

    #create the diceGroup faces
    1.upto(numControlSections[0]-1){|x|
      diceGroup.entities.add_face([[x*xstep,0,0],[x*xstep,0,sizeDefG[2]],[x*xstep,sizeDefG[1],sizeDefG[2]],[x*xstep,sizeDefG[1],0]])
    }
    1.upto(numControlSections[1]-1){|y|
      diceGroup.entities.add_face([[0,y*ystep,0],[0,y*ystep,sizeDefG[2]],[sizeDefG[0],y*ystep,sizeDefG[2]],[sizeDefG[0],y*ystep,0]])
    }
    1.upto(numControlSections[2]-1){|z|
      diceGroup.entities.add_face([[0,0,z*zstep],[0,sizeDefG[1],z*zstep],[sizeDefG[0],sizeDefG[1],z*zstep],[sizeDefG[0],0,z*zstep]])
    }

      puts("@@ffdGroup.transformation:") if @@debugFFD
      puts @@ffdGroup.transformation.to_a if @@debugFFD

    #intersect the diceGroup with the D group
    @@ffdGroup.entities.intersect_with(true,@@ffdGroup.transformation,@@ffdGroup.entities,@@ffdGroup.transformation,false,[diceGroup])
    diceGroup.erase!

  end


  #Called by startFFD create a group to act as a control lattice.
  def self.createControlLattice(sizeDefG, numControlSections)
    #remove previous lattice (if any)
    if(@@latticeGroup)
      begin
        @@latticeGroup.erase!
        @@ffdGroup.remove_observer(@@observer)
      rescue
        #group was already deleted.
      end
    end

      puts("in createControlLattice") if @@debugFFD
      puts("numControlSections:") if @@debugFFD
      puts numControlSections if @@debugFFD

    latticeGroup=Sketchup.active_model.active_entities.add_group()

    #create control lattice group.
    entities=latticeGroup.entities

    #create an @@observer for events. Not used yet.
    @@observer=self::LatticeObserver.new(@@ffdGroup, latticeGroup)

    @@ffdGroup.add_observer(@@observer)

    xstep=sizeDefG[0].to_f/numControlSections[0]
    ystep=sizeDefG[1].to_f/numControlSections[1]
    zstep=sizeDefG[2].to_f/numControlSections[2]

    #handle dim of 0 (1)
	xstep=0.0 if(xstep.to_f.nan?)
    ystep=0.0 if(ystep.to_f.nan?)
    zstep=0.0 if(zstep.to_f.nan?)
    xstep=1.0 if(xstep.to_f.infinite?)
    ystep=1.0 if(ystep.to_f.infinite?)
    zstep=1.0 if(zstep.to_f.infinite?)

    #get the location of origin of the fffGroup
    ffdGroupOrigin=[]
    ffdGroupOrigin=@@ffdGroup.bounds.min.to_a

    controlPoints=[]
    0.upto(numControlSections[0]){|x|
      0.upto(numControlSections[1]){|y|
        0.upto(numControlSections[2]){|z|

          #NOTE: 0.001+ is because bug in sketchup will not allow construction point at 0,0,0
          controlPoints.push([ffdGroupOrigin[0]+0.001+(x*xstep),ffdGroupOrigin[1]+0.001+(y*ystep),ffdGroupOrigin[2]+0.001+(z*zstep)])
        }
      }
    }


    #create construction points.
    index=0
    controlPoints.each{|lcp|
      cpt=entities.add_cpoint(Geom::Point3d.new([lcp[0],lcp[1],lcp[2]]))
      cpt.set_attribute("controlPoint","originalPosition",cpt.position.to_a)
      cpt.set_attribute("controlPoint","index",index)
      index=index+1
      #trigger event any time the user moves a point.
      cpt.add_observer(@@observer)
    }

      puts("before moving latticeGroup to match ffdGroup") if @@debugFFD

    #move lattice group to match ffd group
    #t = @@ffdGroup.bounds.min
    #latticeGroup=latticeGroup.move!(t)

    latticeGroup.set_attribute("controlLattice","currentTransformation",latticeGroup.transformation.to_a)

    #monitor group open/close event. not used yet.
    latticeGroup.add_observer(@@observer)

    return(latticeGroup)
  end

### OBSERVER FIX!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#this class provides events when the user manipulates the control points
class self::LatticeObserver < Sketchup::EntityObserver ###
  ###
  def initialize(ffdGroup=nil, latticeGroup=nil) ### pass groups to observer!
    @ffdGroup=ffdGroup if ffdGroup
    @latticeGroup=latticeGroup if latticeGroup
  end
  ### ENTITIESobserver methods ONLY !
  #def onElementAdded(entity, xx)
  #end
  #def onContentsModified (entity)
  #end
  #called when lattice point is moved.
  def onChangeEntity(entity)
    return nil if not @latticeGroup or not @latticeGroup.valid?
    ###puts("in onChangeEntity") if @@debugFFD
    ###puts "changed "+entity.to_s if @@debugFFD
    if entity==@latticeGroup
      #entity.class==Sketchup::ComponentDefinition)
      ###puts("in onChangeEntity and entity.class==Sketchup::ComponentDefinition") if @@debugFFD
      ###puts entity.name if @@debugFFD
      entity.set_attribute("controlLattice","currentTransformation",entity.transformation.to_a)
    elsif entity.is_a?(Sketchup::ConstructionPoint) and entity.parent==@latticeGroup.entities.parent
      ###
      begin
        SketchyFFD.updateFFD() if @ffdGroup and @ffdGroup.valid?###
      rescue
        ###
      end
      ###
    end
  end
  def onEraseEntity(entity)
    begin
      @latticeGroup.erase! if @latticeGroup and @latticeGroup.valid? ###
    rescue
      ###
    end
  end
  ### INSTANCEobserver methods oNLY !
  #called when lattice group is opened
  #def onOpen(instance)
    #instance.set_attribute("controlLattice","isOpen",true)
  #end
  #called when lattice group is closed
  #def onClose(instance)
    #instance.set_attribute("controlLattice","isOpen",false)
  #end
end
### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


  #called from onChangeEntity event in LatticeObserver class to deform the group
  #based on the position changes of the control points.
  def self.updateFFD()
    ### trap for erased group etc...
    if not @@ffdGroup or not @@ffdGroup.valid?
      return nil
    end
    #disable UI, combine this operation with previous for Undo
    Sketchup.active_model.start_operation("Update FFD",true,false,true)

    puts("in updateFFD") if @@debugFFD
    puts("@@latticeGroup.tranformation, @@ffdGroup.transformation") if @@debugFFD
    puts @@latticeGroup.transformation if @@debugFFD
    puts @@ffdGroup.transformation if @@debugFFD

    xform=Geom::Transformation.new()
    ### disabled originally ??
    #if(@@latticeGroup.get_attribute("controlLattice","isOpen",false))
      #if group is open transform by inverse of saved xform (the real xform).

        #puts("@@latticeGroup isOpen, transform inverse of saved xform") if @@debugFFD

      #xform=Geom::Transformation.new(@@latticeGroup.get_attribute("controlLattice","currentTransformation",nil))
      #xform.invert!
    #end

    deltas=[]
    @@latticeGroup.entities[0].parent.entities.each{|cpt|
      op=cpt.get_attribute("controlPoint","originalPosition",nil)

        puts("op:") if @@debugFFD
        puts op if @@debugFFD

      if(op)#ent is a control point.
        #cp=cpt.position.transform(xform).to_a#get current position
        cp=cpt.position.to_a

          puts("cp, cp2:") if @@debugFFD
          puts cp if @@debugFFD

        if(cp!=op)#point moved?
          delta=[]#calculate change
          delta[0]=cp[0]-op[0]
          delta[1]=cp[1]-op[1]
          delta[2]=cp[2]-op[2]
          #update stored position with new position
          cpt.set_attribute("controlPoint","originalPosition",cp)
          index=cpt.get_attribute("controlPoint","index",nil)
          deltas.push([index,delta])#add to array of changes.
        end
      end
    }
    puts("deltas.inspect:") if @@debugFFD
    puts deltas.inspect if @@debugFFD
    #update mesh
    self.applyMultipleFFD(deltas) if (deltas.length>0)

    Sketchup.active_model.commit_operation
  end

    #called from updateFFD to deform the mesh based on the change in position of each control point.
    #deltas is an array.
    #each element in the deltas array is an array with 2 elements.
    #the first element is the index of the control point.
    #the second element is the change in postition of that point. (a array of 3 floats).
    #example:
    #deltas=[]
    #deltas.push([0,[0.0,0.0,1.0]])  {move control point 0 "up" 1.0}
    def self.applyMultipleFFD(deltas)
      #grp.entities.transform_entities(Geom::Transformation.new([0.0,0.0,0.001]),grp.entities.to_a)
      vi=0;#used to loop through @@allVertWeights in the same order as @@allVerts
      @@allVerts.each{|vert|
        weights=@@allVertWeights[vi] #get pre-calculated array of vertex weights (one per control point)
        vi=vi+1
        next if(vert.get_attribute("SFFD","locked",false))
        #accumulate the change in position of this vertex based on
        #each control points weighted movement.
        dvect=[0.0,0.0,0.0]
        deltas.each{|delta|
          weight=weights[delta[0]] #delta[0] = control point index.
          #puts weight
          vect=delta[1]#delta[1]=delta vector for control point
          dvect[0]+=vect[0]*weight
          dvect[1]+=vect[1]*weight
          dvect[2]+=vect[2]*weight
        }
        #move the vertex.
        begin
          @@ffdGroup.entities.transform_entities(Geom::Transformation.new(dvect),vert)
        rescue
         ###
        end
        #display progress
        Sketchup.set_status_text("Deforming #{vi} of #{@@allVerts.length}") if(vi%100==0)
        #puts("Deforming #{vi} of #{@@allVerts.length}") if(vi%100==0)
      }

  end

self.initiate()

end#module
#end







=begin
														SOFTEN UNSOFTEN  (MATT NOBLET)

=end



module JHS_Soften

def self.smooze(int)
	model = Sketchup.active_model
	sel = model.selection
	case int
		when 1;str = "Soft"
		when 2;str = "Soft entirely"
		when 3;str = "Unsoft all"
	end
	if sel.empty?
		if UI.messagebox(str + " edges on all the drawing? ", MB_YESNO, "Soft 2 face edges")==7
			return nil
		else
			sel = model.active_entities
		end
	end
	model.start_operation(str +" edges")
	JHS_Soften.boucle(sel,int)
	model.commit_operation
end

def self.boucle(ss,int)
	ss.each do |e|
		JHS_Soften.boucle(e.entities,int) if e.typename == "Group"
		JHS_Soften.boucle(e.definition.entities,int) if e.typename == "ComponentInstance"
		case int
			when 1

			if e.typename=="Edge" and e.faces.length>1
				if(e.faces[0].normal.angle_between(e.faces[1].normal) < 25.degrees)
					e.soft=true
					e.smooth=true
				end
			end

			when 2
			if e.typename=="Edge" and e.faces.length>1
				e.soft=true
				e.smooth=true
			end

			when 3
			if e.typename=="Edge"
				e.soft=false
				e.smooth=false
			elsif e.typename=="Face"
				e.edges.each{|edge|edge.soft=false;edge.smooth=false}
			end

		end
	end
end
end #module   SOFTEN UNSOFTEN















=begin

														INSERT COMPS TO CPOINTS - THOM THOM

=end


#module JHS_cp

 # Inserts an instance of the selected component at the selected CPoints.
  def self.insert_components_at_cpoints
    model = Sketchup.active_model

    # Collect all selected CPoints and one Component Instance.
    component = nil
    cpoints = []
    model.selection.each { |e|
      if component.nil? && e.is_a?(Sketchup::ComponentInstance)
        component = e.definition
      end
      cpoints << e if e.is_a?(Sketchup::ConstructionPoint)
    }
    if component.nil?
      UI.messagebox( 'No Component Instance selected.' )
      return
    end
    if cpoints.empty?
      UI.messagebox( 'No CPoints selected.' )
      return
    end

    model.start_operation('Insert Components at CPoint')
    cpoints.each { |c|
      t = Geom::Transformation.new(c.position)
      instance = model.active_entities.add_instance(component, t)
    }
    model.commit_operation
  end



#end # module







=begin

														ANTON SYNYTSIA  -  AMS SOFTEN EDGES

=end


#require 'ams_SoftenEdges.rb'

module JHS_AMS; end
module JHS_AMS::SoftenEdges

  @opts = {
    :angle                      => 40,
    :smooth_normals             => true,
    :soften_coplanar            => true,
    :soften_curves              => true,
    :soften_material_dividers   => false,
    :recursive                  => true
  }
  @dlg = nil
  @initialized = false

  class << self

    def soften_edges(ents, angle, smooth_normals = true, soften_coplanar = false, soften_curves = true, soften_material_dividers = true, recursive = true, depth = 0)
      model = Sketchup.active_model
      if depth == 0
        if Sketchup.version.to_i > 6
          model.start_operation('AMS Soften Edges', true, false, true)
        else
          model.start_operation('AMS Soften Edges')
        end
      end
      ents = [ents] unless ents.is_a?(Enumerable)
      if recursive || depth == 0
        ents.grep(Sketchup::Group).each { |e|
          soften_edges(e.entities, angle, smooth_normals, soften_coplanar, soften_curves, soften_material_dividers, recursive, depth + 1)
        }
        processed_definitions = []
        ents.grep(Sketchup::ComponentInstance).each { |e|
          definition = e.definition
          next if processed_definitions.include?(e.definition)
          processed_definitions << e.definition
          soften_edges(e.definition.entities, angle, smooth_normals, soften_coplanar, soften_curves, soften_material_dividers, recursive, depth + 1)
        }
        processed_definitions.clear
      end
      edges = []
      ents.grep(Sketchup::Face).each { |e|
        edges.concat(e.edges)
      }
      edges.concat( ents.grep(Sketchup::Edge) )
      edges.uniq!
      edges.each { |e|
        #~ e.visible = true if e.hidden?
        if e.faces.size != 2 || angle.zero? || (e.curve && !soften_curves)
          e.soft = false if e.soft?
          e.smooth = false if e.smooth?
          next
        end
        if e.faces[0].material != e.faces[1].material && !soften_material_dividers
          e.soft = false if e.soft?
          e.smooth = false if e.smooth?
          next
        end
        v1 = e.faces[0].normal
        v2 = e.faces[1].normal
        if v1.parallel?(v2) && !soften_coplanar
          e.soft = false if e.soft?
          e.smooth = false if e.smooth?
          next
        end
        theta = v1.angle_between(v2).radians
        if (theta < angle)
          e.soft = true unless e.soft?
          sn = smooth_normals ? true : false
          e.smooth = sn if e.smooth? != sn
        else
          e.soft = false if e.soft?
          e.smooth = false if e.smooth?
        end
      }
      model.commit_operation if depth == 0
      nil
    end



	#########





	#########


    def soften_edges_from_selection
      sel = Sketchup.active_model.selection
      return false if sel.empty?
      soften_edges(sel, @opts[:angle], @opts[:smooth_normals], @opts[:soften_coplanar], @opts[:soften_curves], @opts[:soften_material_dividers], @opts[:recursive])
      true
    end

    def reveal_quadrants(ents, smooth_normals = true, recursive = true, depth = 0)
      model = Sketchup.active_model
      if depth == 0
        if Sketchup.version.to_i > 6
          model.start_operation('AMS Soften Edges', true, false, true)
        else
          model.start_operation('AMS Soften Edges')
        end
      end
      if recursive || depth == 0
        ents.grep(Sketchup::Group).each { |e|
          reveal_quadrants(e.entities, smooth_normals, recursive, depth + 1)
        }
        processed_definitions = []
        ents.grep(Sketchup::ComponentInstance).each { |e|
          definition = e.definition
          next if processed_definitions.include?(e.definition)
          processed_definitions << e.definition
          reveal_quadrants(e.definition.entities, smooth_normals, recursive, depth + 1)
        }
        processed_definitions.clear
      end
      edges = []
      ents.grep(Sketchup::Face).each { |e|
        edges.concat(e.edges)
      }
      edges.concat( ents.grep(Sketchup::Edge) )
      edges.uniq!
      edges.each { |e|
        e.soft = false if e.soft?
        e.smooth = false if e.smooth?
      }
      edges.each { |e|
        e.start.edges.each { |edge1|
          next if edge1 == e
          vert = edge1.other_vertex(e.start)
          e.end.edges.each { |edge2|
            next if edge2 == e
            if edge2.other_vertex(e.end) == vert
              if e.length.to_f > edge1.length.to_f && e.length.to_f > edge2.length.to_f
                e.soft = true unless e.soft?
                e.smooth = true if smooth_normals && !e.smooth?
                edge1.soft = false if edge1.soft?
                edge1.smooth = false if edge1.smooth?
                edge2.soft = false if edge2.soft?
                edge2.smooth = false if edge2.smooth?
              end
            end
          }
        }
      }
      model.commit_operation if depth == 0
    end

    def reveal_quadrants_from_selection
      sel = Sketchup.active_model.selection
      return false if sel.empty?
      reveal_quadrants(sel, @opts[:smooth_normals], @opts[:recursive])
      true
    end

    def save_options
      Sketchup.write_default('Plugins_ams', 'Soften Edges', @opts.inspect.inspect[1..-2])
    end

    def load_options
      v = Sketchup.read_default('Plugins_ams', 'Soften Edges', '{}')
      begin
        res = eval(v)
        if res.is_a?(Hash) && res.size == @opts.size
          @opts.merge!(res)
        end
      rescue Exception => e
        # Do nothing; use default options.
      end
    end

    def options
      @opts.dup
    end

    def show_dialog(state)
      state = state ? true : false
      return false if state == dialog_visible?
      if state
        # Create a web dialog.
        w = 306
        h = 408
        bw = 0
        bh = 0
        @dlg = UI::WebDialog.new('AMS Soften Edges', false, 'ams_SoftenEdges', w, h, 400, 300, true)
        @dlg.set_size(w, h)
        # Attach callbacks
        @dlg.add_action_callback('init'){ |dlg, params|
          cmd = "$('#sel1').val(#{@opts[:angle]}).slider('refresh');"
          cmd << "$('#cb1').attr('checked', #{@opts[:smooth_normals]}).checkboxradio('refresh');"
          cmd << "$('#cb2').attr('checked', #{@opts[:soften_coplanar]}).checkboxradio('refresh');"
          cmd << "$('#cb3').attr('checked', #{@opts[:soften_curves]}).checkboxradio('refresh');"
          cmd << "$('#cb4').attr('checked', #{@opts[:recursive]}).checkboxradio('refresh');"
          cmd << "$('#cb5').attr('checked', #{@opts[:soften_material_dividers]}).checkboxradio('refresh');"
          @dlg.execute_script(cmd)
          next if @initialized
          @initialized = true
          cw,ch = eval(params)
          bw = w - cw
          bh = h - ch
          w = 290 + bw
          h = 370 + bh
          @dlg.set_size(w, h)
          if Sketchup.version.to_i > 6
            @dlg.min_width = w
            @dlg.min_height = h
          end
        }
        @dlg.set_on_close(){
          @dlg = nil
          @initialized = false
        }
        @dlg.add_action_callback('window_focus'){ |dlg, params|
          model = Sketchup.active_model
          next if model.selection.empty?
          if Sketchup.version.to_i > 6
            model.start_operation('AMS Soften Edges', true, true, false)
          else
            model.start_operation('AMS Soften Edges')
          end
        }
        @dlg.add_action_callback('window_blur'){ |dlg, params|
          model = Sketchup.active_model
          next if model.selection.empty?
          model.commit_operation
        }
        @dlg.add_action_callback('input_changed'){ |dlg, params|
          data = eval(params)
          case data[0]
            when :cb1
              @opts[:smooth_normals] = data[1]
            when :cb2
              @opts[:soften_coplanar] = data[1]
            when :cb3
              @opts[:soften_curves] = data[1]
            when :cb4
              @opts[:recursive] = data[1]
            when :cb5
              @opts[:soften_material_dividers] = data[1]
          end
          sel = Sketchup.active_model.selection
          next if sel.empty?
          soften_edges(sel, @opts[:angle], @opts[:smooth_normals], @opts[:soften_coplanar], @opts[:soften_curves], @opts[:soften_material_dividers], @opts[:recursive])
          save_options
        }
        @dlg.add_action_callback('button_clicked'){ |dlg, params|
          case params
            when 'btn1'
              reveal_quadrants_from_selection
            when 'btn2'
              soften_edges_from_selection
          end
        }
        @dlg.add_action_callback('slider_slide_start'){ |dlg, params|
        }
        @dlg.add_action_callback('slider_slide_stop'){ |dlg, params|
          save_options
        }
        @dlg.add_action_callback('slider_slide'){ |dlg, params|
          @opts[:angle] = params.to_f
          sel = Sketchup.active_model.selection
          next if sel.empty?
          soften_edges(sel, @opts[:angle], @opts[:smooth_normals], @opts[:soften_coplanar], @opts[:soften_curves], @opts[:soften_material_dividers], @opts[:recursive])
        }
        # Set content
        dir = File.dirname(__FILE__)
        url = File.join(dir, 'soften_edges.html')
        @dlg.set_file(url)
        # Show dialog
        RUBY_PLATFORM =~ /mswin|mingw/i ? @dlg.show : @dlg.show_modal
      else
        @dlg.close
      end
      true
    end

    def dialog_visible?
      @dlg ? true : false
    end

  end # class << self
end # module AMS::SoftenEdges












end # JHS powerbar module
end # Max_Coppoletta




















=begin

														CREDIT INFO

=end




#     jhs powerbar by  Max Coppoletta  (who actually did quite a bit of coding!)   : ))


#		Toolbar Authors:    Listed per toolbar position


#     To all of them a big wave of grateful energy!







=begin                                            #  ANTON SYNYTSIA - SOFTEN EDGES
=end

# ------------------------------------------------------------------------------
#
# **AMS Soften Edges**
#
# Allows you to soften and smooth edges with extended options.
#
# Usage
#   Select entities you want to smooth and apply the menu option.
#
# Access
#   - (Menu) Plugins ? AMS Soften Edges
#   - (Edit Context Menu) ? AMS Soften Edges
#
# Version
#   1.0.0
#
# Release Date
#   November 06, 2014
#
# Author
#   Anton Synytsia
#
# ------------------------------------------------------------------------------



=begin                                            #  MATT NOBLET - SOFTEN UNSOFTEN
=end

# ------------------------------------------------------------------------------

#    Copyright (C) 2008 Matt666  and CadFather V1.2
#    03/01/2009
#    This program is free software: you can gna gna gna gna...
#
#    CadFather's note:   "Matt, you're mad...!"   ; ))

# ------------------------------------------------------------------------------




=begin                                        JULIA CHRISTINA ENEROTH - UPRIGHT EXTRUDER
=end


#Eneroth Upright Extruder

#Author:	Julia Christina Eneroth, Eneroth3, eneroth3@gmail.com

#Usage
#	Menu: Plugins
#		Upright Extrude: Extrude selected face along selected edges

#Copyright Julia Christina Eneroth (eneroh3)
#	Free to use both professional and unprofessional.
#	Free to spread as long as credit goes to me.

#Known bugs and issues
#	*	Profile's with in corners is constant but between corners the sides aren't parallel (unless the angle is 0 on both sides).
#		Scale profiles before drawing them

#Changelog
#	1.0.0 2013-05-30
#		*First release
#	1.1.0 2013-06-02
#		*Fixed small icons
#		*Extrude closed paths
#		*Fixed co-planar face bug. Use classify_point instead of comparing vectors to see if faces really really really are co-planar.
#	1.1.1 2013-06-06
#		*Called tb.show to fix hidden toolbar issue.
#		*Script cannot extrude along edges binding extrusion face. Just ignore these edges instead of raising an error and aborting.
#		*Fixed back face problem.
#		*Soften/Smooth edges extruded from curves
#	1.2.0 2013-08-06
#		*Using Sketchup::Face::PointNotOnPlane instead of 32 to classify points
#		*Cleaned up code
#		*Use point along path closest to extrusion as start point for closed path, not closest corner
#		*parallel sides of extrusion rather than fixed with in corners
#	1.2.1 2012-12-15
#		*Moved start_operation and commit_operation to userInput function to make extruder work better when called from other plugin
#   *Spelling
#   *Can now be loaded from other folder than plugin default

#Load the normal support files







=begin                                            ITHIL - FACE FINDER
=end

# no info in the original script but, plgin made by Ithil

#   http://i-t-h-i-l.deviantart.com/
#   https://vk.com/id979360




=begin                                            SDMITCH - OFFSET EDGE
=end

#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#------------------------------------------------------------------------------------------------
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#------------------------------------------------------------------------------------------------
# IT WAS CREATED AND TESTED IN A WINDOWS ENVIRONMENT ONLY AND MAY NOT FUNCTION ON A MAC.
#------------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
#------------------------------------------------------------------------------------------------
#    Name:	Offset Edge
#      By:	sdmitch
#   Usage:	To offset a single edge by a defined or Keyed in value.
#		 Note:
#
#    Date:	Feb 2013
#------------------------------------------------------------------------------------------------


=begin                                            DIDIER BUR - EXTRUDE LINE TOOL
=end

# Copyright 2004, @Last Software, Inc., modified by D. Bur and TIG

# This software is provided as an example of using the Ruby interface
# to SketchUp.

# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#-----------------------------------------------------------------------------
# Name        :   extrudelinetool
# Description :   A script to extrude lines along a vector given with 2 clicks
# Menu Item   :   Plugins->Extrude lines (vector by 2 points)
# Usage       :   Select lines, select "Extrude lines (vector by 2 points)" from the plugins Menu,
#                 then click 2 points on your model
# Date        :   10/18/2004
# Type        :   tool
#-----------------------------------------------------------------------------
### TIG tweaked so faces orient and smoothness and curves kept etc...



=begin                                        RICK WILSON - EXTRUDE ALONG PATH
=end


#    CAN'T FIND ANY INFO - OR EVEN THE SCRIPT ON LINE.  THIS IS FROM AN OLD ARCHIVE AND REMEMBER IT BEING RW's


=begin                                        RICK WILSON - PIPE ALONG PATH
=end

# PipeAlongPath
# TIG (c) 2005 - 2014
# Description : Creates circular faced 'followme' pipe extrusions along a path -
# Use it to make pipes, ducts etc, starting from a selection of joined edges
# Usage :
# Select joined lines, arcs, circles, curves, etc.
# Select "Pipe Along Path" from the Plugins menu.
# In the dialog choose:-
# Outside diameter:   in current units or use suffix for other, default 110mm/4"
# Inside diameter:    in current units or use suffix for other, default 110mm/4"
# Number of segments: default 24
# Cpoints?:    [Yes/No, default=true to add cppoints at the pipe's vertices]
# Cline layer: [layer for the path, default="XCLINE", make blank for 'none']:
# In group?:   [Yes/No to move selected path into pipe's group, default=true]
# OK.
# Last used setting are remembered across sessions.
# The pipe-extrusion is made*.
# The pipe-extrusion is grouped so it does not interact with adjacent surfaces -
# afterwards just explode it if appropriate.
# Edit it to intesect with model and tidy up to make tees etc...
# The pipe also has construction points added at vertices if you set Cpoints=true.
# These can be used for snapping, if not wanted 'erase construction geometry'
# will remove them globally or just within a group that you are editing.
# One Undo to remove the construction points, a second to Undo the Pipe itself.
# The default for extrusion face segments is 24, the minimum is 3.
# The alignment is always 'Centroid' - along the pipe's centre line.
# The diameter is always measured square to the vector of the
# first path's line.  A diameter that is less than the length of pieces of path
# edge might give correct but unexpected extrusions, which might need manually
# tidying...
# Closed loop paths are fully extruded in a loop.
# Multiple arcs and other complex 3D paths might give unexpected results.
# Note that SketchUp can't handle very small faces in its FollowMe mode - so any
# Arc bends of 8" radius or less that have the same radius for the pipe (o/d)
# applied will almost certainly cause a crash / "bug splat" and are trapped out
# BUT note that similar radii in 'welded' Curves are NOT easily trappable and so
# they may always cause a crash with a "bug-splat", so avoid using these type of
# path with small radii bends/pipes - or you can keep it but use 'scale' as
# explained below...
# IF you must have this matching small diameter pipe and arc or welded elbow
# bends then to get it to work you can make the path a
# group, scale it by a factor so it's larger (say x10), then
# edit it and use this tool within it applying the diameters x10,
# after it's all made scale the group back down by x0.1 and
# explode it and it'll all be OK.

# Both diameters cannot be zero and will return an error.
# If one of the diamaters is zero then you get a 'tube' rather
# than a pipe.
# If the diamaters entered are equal the inside one is taken as
# zero and you will then get a 'tube' rather than a pipe.
# If the inside's diameter is greater than the outside's then
# they are swapped.
# A branching path returns an error as 'FollowMe' can't decide
# which path to take.
# Selected edges that are not joined (i.e. they don't all have
# common ends-starts) will not be extruded - only the first edge
# or joined group of edges will extrude.

# Version :
# 1.0  18/9/5	first release.
# 1.1  18/9/5	visual segmenting of arced sections etc addressed.
# 1.2  9/2/6  centrepoints added at vertices of non-looped paths,
			# occasional reversed faces on single 'up' line fixed.
# 1.3  16/2/6 undo of cpoints fixed, diam <8" with matching arc
			# elbow bend radius bug splat trapped (but not curves).
# 1.4  2/5/6	group name -> OD=xx ID=nn with " or mm as units.
# 1.5  3/5/6	Transposed ID/OD fixed (sorry!).
# 1.6 12/5/7  @error=0 ### fix
# 1.7 20121019 Rehashed to modern standards, dialog based options to add cpoints
			 # at nodes, put selected path onto a specified layer and move the
			 # path inside the pipe-group.
# 1.8 20130115 Fixed typo glitch when path was lone vertical downward line.
# 1.9 20140303 Lockup weirdness trapped.  Any units format now allowed.
			 # Last used units remembered across sessions and globally.
# 2.0 20140304 Lockup skewed warning improved. Graphics glitch fixed.
			 # Name reflects if interal_diam == 0.




=begin                                        DIDIER BUR - LINES TO TUBES
=end


# Name:           line2cyl
# Author :        Didier Bur
# Description:    Create cylinders with selected edges
# Usage:          select lines, enter diameter and precision
#
# Type:           Tool
# Date:           2005,13,01
# Revised:        2005,17,01: add the arcs,curves, and circles followme's



=begin                                       RICK WILSON - COPY ALONG PATH
=end


# = pathcopy.rb
# Copyright 2005-2014 by Rick Wilson - All Rights Reserved

# == Disclaimer
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# == License
# This software is distributed under the Smustard End User License Agreement
# http://www.smustard.com/eula

# == Information
# Author:: Rick Wilson
# Organization:: Smustard
# Name:: PathCopy
# Version:: 2.200
# SU Version:: 4.0
# Date:: 2014-03-10
# Description:: copy group or component along a path

# Usage::
# * 1:: Install into the plugins directory or into the Plugins/examples directory and manually load from the ruby console "load 'examples/weld.rb'"
# * 2:: Run the script (Plugins>PathCopy>Copy to Nodes or Plugins>PathCopy>Copy to Spacing) and follow the prompts in the status bar
# * 3:: Selected group/component will be copied along the curve

# History::
# * 1.000:: 2005-11-29
	# * first version
# * 2.000:: 2005-12-05
	# * updated; planned improvements include specifying distance between copies, specifying a divisor
# * 2.100:: 2005-12-07
	# * implemented specifying distance between copies
	# * fixed bug where groups would not copy/rotate properly
# * 2.200:: 2014-03-10
	# * fixed bug where reversed edges would cause copies to be improperly placed along curve
	# * consolidated both functions (node copy and spacing copy) into a single UI::Command

# ToDoList::
	# * Specify a divisor



=begin                                      SD MITCH - COMPONENT STRING
=end

#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#------------------------------------------------------------------------------------------------
#    Name:	Component_String
#      By:	sdmitch
#   Usage:	Plugins>SDM Tools>CorG Tool>Component_String
#	 	 Note:	Places selected component(s) along a selected path in random or a fixed sequence.
#						Spacing refers to the distance between components.
#
#    Date:	Dec 2014
#------------------------------------------------------------------------------------------------




=begin                                            JAN SANDSTROM - JS ALIGN
=end


# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# Name:           jsAlign.rb 1.6 updated for v2014 compatibility 20140306 TIG
# Author:         Jan Sandstrom   www.pixero.com
# Date:           23 June 2009
# Changes:	Added functionality to also work on groups/components.
# Description:		Use for aligning edges or groups/components.
#                		If using custom value with groups also use Align Group's: Max, Min or Center.




=begin                                      SD MITCH - COMPONENT ARRAY
=end


#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN WAS DEVELOPED AND TESTED UNDER WINDOWS VISTA ONLY AND MAY OR MAY NOT WORK ON A MAC.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
# -----------------------------------------------------------------------------------------------
#    Name:	Component Array
#      By:	sdmitch
#   Usage:	Place selected component on a equilateral triangular or rectangular grid
#	 	 Note:	Faces do not have to be on the XY plane.  Option to group array and erase face.
#
#    Date:	Sep 2011
#------------------------------------------------------------------------------------------------



=begin                                            RICK WILSON - DROP CG
=end


# = dropGC.rb
# Copyright 2012-2014 by Rick Wilson - All Rights Reserved

# == Disclaimer
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# == License
# This software is distributed under the Smustard End User License Agreement
# http://www.smustard.com/eula

# == Information
# Author:: Rick Wilson
# Organization:: Smustard
# Name:: dropGC.rb
# Version:: 1.0.3
# SU Version:: 4.0
# Date:: 2014-06-20
# Description:: drop selected groups/components to the first face below each instance

# Usage::
# * 1:: Plugins>DropGC or right-click>DropGC or SmustardToolbar>DropGC activates the tool

# History::
# * 1.0.0:: 2012-02-20
	# * first version
# * 1.0.1::	2014-05-02
	# * updated the SmustardToolbar reference from a global to a Smustard module constant
# * 1.0.2:: 2014-05-16
	# * version 1.0.1 works in SU 2014, but a code block didn't work in earlier versions (due to changes in Ruby).  Fixed this to work in all versions.
# * 1.0.3:: 2014-06-20
	# * fixed a typo in my previous fix
# * 1.0.4:: 2014-10-15
	# * changed .count to .length for compatibility with earlier versions of SketchUp (pre-2013)

# ToDoList::
	# * add options & settings for: show z offset from face below (or from xy plane if no face below)
	# * add options & settings for: use hidden geometry (SU8m1 and up)
	# * add options & settings for: drop by set amount
	# * add options & settings for: drop to Z
	# * add options & settings for: drop to selected
	# * add options & settings for: drop to selected (virtual extended plane)
	# * add options & settings for: drop to intersection plus additional offset up or down
	# * add options & settings for: keep G/C above face below
	# * add options & settings for: match G/C XY plane to intersected plane below




=begin                                           TIG - MIRROR
=end


# Name :          Mirror
# Description :   Mirrors Selection at Point, Line or Plane
# Author[s] :     From an original idea by Frank Wiesner:
#		          but from v2.6 onwards it's been fully TIG tweaked,
#                 and completely updated and rewritten...###
# Usage :         1. Make Selection (Any type[s] of Entites are allowed)###
#                 2. Select 'Mirror Selection' from Plugins Menu
#					 or from the equivalent on the Right-Click Context-Menu
#                    or using your favorite ShortCut Key [e.g.Shift+R] ###
#                    or from the 'Mirror' Toolbar [this toolbar will be
#                    available for activation from the View>Toolbars
#                    Menu IF the button icon image MI.png is in the
#                    same folder as Mirror.rb file (usually Plugins)] ###v3.2
#                 3. If there's no Selection then dialog informs you ###
#                 4. Pick a Point and RETURN to Mirror at the Point
#                 5. OR Pick a Second Point and RETURN to Mirror at the Line
#                 6. OR Pick a Third Point to Mirror at the Plane [usual]
#                 7. Final Option is Erase Original ? Yes/No ###
#					 If Yes then Highlighting of Selection passes to Copies
# Type :      	Script - Mirror.rb
# History:
# 3.9  20131128 Future proofed required files.
# 3.8  09 Aug 2012 ### TIG, locked objects are no longer mirrored, code tidying.
# 3.7  05 Feb 2012 ### TIG, mirroring glued instances now [re]glues them.
# 3.6  13 Dec 2010 ### TIG, group.copy methods recast to avoid glitches.
# 3.5  10 Nov 2010 ### TIG, context-menu re-added.
# 3.4  9 Nov 2010 ### TIG, Recoded to avoid clashes and crashes...
# 3.3  3 Oct 2010 ### TIG, togglewindows catch for Outliner added and set zipped.
# 3.2  19 Feb  2010 ### TIG, optional toolbar added
#      [this doesn't load is the MI.png file is NOT found].
# 3.1  21 Feb  2007 ### TIG make_unique tries to avoid rare bugsplats.
# 3.0  05 Aug  2006 ### TIG, Ending rewritten to avoid rare bugsplats.
# 2.9  03 Aug  2006 ### TIG, Start:commit loops to avoid rare bugsplats.
# 2.8  26 July 2006 ### TIG, re-tweaked - Rewritten to be MUCH simpler.
#      Mirroring of ANY Entity Selection works properly.
# 2.7  23 July 2006 ### TIG's second tweak - Rewritten generally.
#      Now processes Multiple selections of Groups and/or
#      Components, but warns if any 'loose' (UnGrouped)
#      Faces in Selection as they might lose any RePostioned
#      Texture data, interfere etc.
# 2.6  22 July 2006 ### TIG first tweak ### - Context menu added IF ONE
#      group/compo.
#      Warns about selection IF NOT ONE group/compo.
#      Materials now preserved in mirrored copy BUT you will
#      need to group selection to preserve material positioning
#      - although this is define-able it is not find-able from
#      the orignal face info !
#      Final option to Erase of Original Group/Compo.
# 2.5  (8.Jul.2oo5)  - bugfix: faces with holes reduce now to a single face
#      - major code rewrite (dropped @clondedObject hash, heavy
#      use of groups during construction,...
#      - more redundant constructions, but seems to be more robust
#      - WARNING: POLYGONS DO NOT WORK VERY WELL
# 2.4  (5.Jul.2oo5)  - all unnecessary edges created by the add_faces_from_mesh()
#      method are erased! faces with holes mirror now perfectly.
# 2.3  (4.Jul.2oo5)  - fixed "mirror at plane" error
# 2.2  (2.Jul.2oo5)  - works in group editting mode - prevents picking of identical points
# 2.1  (1.Jul.2oo5)  - fixed "undo group" bug - code cleanup
# 2.0  (30.Jun.2oo5) - new tool-like interface
# 1.4  (29.Jun.2oo5) - arc bug fixed - code simplified (clone_selection() removed)
# 1.3  (24.Jun.2oo5) - can handle faces with holes in it (although more edges are drawn than nessesary) - correct orientation of mirrored faces - supports arcs (at least works for circles), curves and groups
# 1.2  (20.Jun.2oo5) - axis and origin selection algorithm does not rely on tool tips any more.
# 1.1  (19.Jun.2oo5) - fix coplanar point bug - better tooltips - supports mirror at lines (edges, axis, construction lines) - mirror-at-plane code simplified
# 1.o  (10.Jun.2oo5) - first version



=begin                                              TIG - WELD
=end

# Copyright 2004-2005 by Rick Wilson [in parts], and 2012-2013 (c) TIG.
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# Name:-
# TIG-weld.rb

# Description:-
# Joins selected edges into a 'curve' (~"polyline").

# Author:-
# TIG - based somewhat on RickW's 'weld.rb'...
# [but it has no close/face options, and also made suitable to be called from
# other methods].

# Usage:-
# Make a selection which includes the edges to be joined.
# Run the script from the menu:
# Tools > Weld
# or type in the Ruby Console:
# TIG.weld +<enter>
# or [recommended] set up a shortcut-key, say J [==Join?]

# Selected edges will then be 'welded' into a curve wherever possible.
# Disconnected edges or branching edges might give unexpected results.
# A curve will be split where any 'branching' edge intersects it.
# The process is one step undoable.
# To use the tool run within another method include...
# require('TIG-weld.rb')
# and later in the code use
# TIG.weld(true)
# to supress 'undo' complications.
# It returns an array of the welded edges or 'nil' if none.
# The calling method must make a selection of potential edges to
# process, even if inside another context; perhaps iterated in turn,
# see scripts like "TIG-weldall.rb" for an example of this...

# Version:-
# 1.0 20120305 First public issue.


=begin                                            JAN SANDSTROM - JS MOVE
=end


# Name: 		jsMoveTool
# Author: 	Jan Sandstrom   www.pixero.com
# Description: 	Moves a selection with the arrow keys.
# Usage: 	1. Select a object or group of objects.
# 		2. Select the JS MoveTool and enter a distance in the VCB. Press Return/Enter.
# 		3. Now move with arrow keys.
# 		4. Use Alt + Up/Down to move in Z axis.
# 		5. You can enter a new distance at any time.
#
#		Version 1.1
#		Added:
#		6. Press Ctrl (Apple Key on Mac) for distance * 0.1
#		6. Press Shift for distance * 10
### 20140319 1.2 TIG updated to suit v2014 & in JS module




=begin                                            DIDIER BUR - ALIGN TOOL
=end

# Copyright 2007, Didier Bur
#
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#----------------------------------------------------------------------------
# Name : Align Tool
# Description : A tool to create translate and rotate a selection of entities.
# Menu Item : Tools / Align
# Usage : 1. Make a selection first
#         2. Click a start origin, X direction, and Y direction
#         3. Click a target origin, X direction, and Y direction
# Date : 08 May 2007
# Type : Tool
# History: 10 May 2007: added a context menu added (align or align group/component)
#                       added the align from group/component axis
#                       supressed confirmation dialog box
#                       Mac problem solved (commit_operation), thanks to R. Wilson
#----------------------------------------------------------------------------






=begin                                        JULIA CHRISTINA ENEROTH - 3D ROTATE
=end


#Eneroth 3D Rotate Tool

#Author:	Julia Christina Eneroth, Eneroth3, eneroth3@gmail.com

#Usage
#	Menu: Tools
#		3D Rotate:	Rotate entities freely around point
#		(I prefer using ALT+Q as shortcut)

#Copyright Julia Christina Eneroth (eneroh3)
#	Free to use both professional and unprofessional.
#	Free to spread as long as credit goes to me.

#Known bugs and issues
#	Can't snap to c-lines in parent drawing contexts or its other children

#Changelog
#	1.0.0 2013-08-05
#		*First release
#	1.0.1 2013-08-09
#		*Fixed error with 4th input point not being selected. turned out tool stopped working either on Sketchup.start_operation or grouping/exploding group.
#	1.0.2
#		*Transparent operations so both rotations can be undone at once
#		*Typos
#	1.0.3 2013-08-15
#		*Fixed edge breaking after rotation in a way that shouldn't cause Sketchup to crash, even with observers attached to the entities
#	1.0.4 2013-08-20
#		*Reinvented edge breaking/splitting since  previous method also could cause the application to crash
#	1.0.5 2013-08-20
#		*Spelling
# 1.0.6
#   *Can now be loaded from other dir than Sketchup's default plugin folder

#Load the normal support files




=begin                                      TBD - ROTIX
=end

#        Name : Rotix 1.1
# Description : rotate objects with 90, custom and random degrees
#      Author : TBD
#       Usage : select from the Plugins menu, hover mouse over object (SHIFT to add to current selection)
#               and use left/right for 90 degrees, up/down to rotate with VCB value, left click to random rotate
#        Date : 25.Jul.2oo4
#        Type : tool
#    Modified : 19.Aug.2004 by Glenn M. Lewis - hit 'Home' or 'End' key to change the axis of rotation






=begin                                       CADFATHER SCALE ROTATE - WITH CODE FROM TBD RANDOR - ROTIX SCRIPTS
=end


#        Name : Randor 1.0
# Description : random rotate and scale objects - good for landscaping
#      Author : TBD
#       Usage : Plugins | [TBD] Randor
#        Date : 15.Jul.2oo4
#        Type : tool


=begin                                       SDMITCH - PROXIFY (CADFATHER TWEAKS)
=end
#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#------------------------------------------------------------------------------------------------
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
# WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#------------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
#------------------------------------------------------------------------------------------------
# IT WAS CREATED AND ONLY TESTED IN A WINDOWS ENVIRONMENT AND MAY NOT FUNCTION ON A MAC.
#------------------------------------------------------------------------------------------------
#    Name:	Component by Proxy
#      By:	sdmitch
#   Usage:	Context Menu
#		 Note:
#
#    Date:	Nov 2014
#------------------------------------------------------------------------------------------------




=begin                                       CADFATHER - COMPONENT SWAPPER
=end


#  Grandmaster of one neuron coding!



=begin                                       CHRIS PHILLIPS (TIG) - SKETCHY FFD
=end


#SketchyFFD
#Copyright 2010-2013 Chris Phillips

#Update 4 Feb 2010 by Glenn Babcock
#change log
# - fixed issues with control point groups and dicegroups sometimes being out of position

#Update 2 Feb 2010 by Glenn Babcock
#change log
#  - added feature that combines move and transform into one operation so it can be undone all at once
#  - disabled screen refreshes during heavy operations to improve performance
#  - reorganized code
#  - began to rationalize variables
#  - added $debugFFB global

#Update 1 Feb 2010 by Glenn Babcock
#change log
#  - fixed bug where control group sizes could not be changed or where all dimensions had to be equal

### 20110810...
### TIG tweaked to module and observers sorted and $ > @@ etc...
### 20130203...
### TIG NxN issues with NaN & Infinity fixed, when selected group is 2d & height=1
### NxN now remembers last used values for w/d/h/subdivide...





=begin                                            SD MITCH - MESH MAKER
=end



#####################################################################  MESH MAKER

#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#------------------------------------------------------------------------------------------------
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN WAS DEVELOPED AND TESTED UNDER WINDOWS VISTA ONLY AND MAY OR MAY NOT WORK ON A MAC.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
# -----------------------------------------------------------------------------------------------
#    Name:	Mesh Maker
#      By:	sdmitch
#   Usage:	Creat a regular trianglated network of a given cell size of a selected face
#	 		Rev:	Made cell size a global variable, modified for tilted faces, added angle.
#    Date:	Jun 2011
#------------------------------------------------------------------------------------------------



=begin                                            SD MITCH - SUBDIVIDE FACES
=end




#####################################################################  SUBDIVIDE FACES

#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#------------------------------------------------------------------------------------------------
# Developed and tested in a Windows Vista environment.  May not function on a Mac.
#------------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
# -----------------------------------------------------------------------------------------------
#    Name:	sub_divide_faces
#      By:	sdmitch
#   Usage:	Divide selected Quad or Triangular faces.
#		 Note:
#    Date:	Aug 2011
#------------------------------------------------------------------------------------------------


=begin                                            TIG - SPLITUP
=end


#-----------------------------------------------------------------------
# Copyright 2011-2012 TIG (c)
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.
# This software is provided "as is" and without any express or
# implied warranties, including, without limitation, the implied
# warranties of merchantability and fitness for a particular purpose.
# Name:
  # SplitUp.rb
# Usage:
  # Splits selected 'quad faces' into the specified number of parts.
  # Use in Ruby Console or other code, thus:
  # SplitUp.new(2) [or more sloppily SplitUp.new 2]
  # as shown here 2 is the number of divisions wanted.
  # OR use Tools > Split Tools... > submenu item SplitUp and enter the
  # division number in the dialog [default=1 >> NO splits]
  # [The Plugins menu item is now disabled by default from v1.5 but see end
  # of the main code IF you want to re-enable it...]
  # If any selected face is not a quad OR a quad with one quad-hole -
  # then the face and probably all of its connected neighbours should first be
  # 'Quadrilateralized' - i.e. split up into quads - a message tells you this,
  # but you may still continue...
  # New edges are softened/smoothed.
  # It returns the number of faces processed and made [n,nn]
  # It's one step undoable.
# Donations:
  # By PayPal.com to info @ revitrev.org
# Version:
  # 1.0 20110717 First issue.
  # 1.1 20110718 Now works on quads and quads with one quad hole.
               # Plugins menu item and dialog added.
  # 1.2 20110718 Softened/smoothed new edges.
  # 1.3 20110719 Glitches with smoothing etc fixed.
  # 1.4 20111111 Overhauled and less error prone.
  # 1.5 20120219 Plugins menu item disabled;
               # now in Tools > Split Tools submenu.




=begin                                       SD MITCH - CONNECT GUIDE POINTS
=end

#------------------------------------------------------------------------------------------------
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted.
#------------------------------------------------------------------------------------------------
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN WAS DEVELOPED AND TESTED UNDER WINDOWS VISTA ONLY AND MAY OR MAY NOT WORK ON A MAC.
# -----------------------------------------------------------------------------------------------
# THIS PLUGIN IS ANOTHER FIGMENT OF MY IMAGENATION AND IS NOT BASED ON OR COMPLY WITH ANYTHING!
# -----------------------------------------------------------------------------------------------
#    Name:	Connect Guide Points
#      By:	sdmitch
#   Usage:	Connect guide points defining a profile of an object.
#			Note:	Profiles must be selected one at a time.
#
#    Date:	Jun 2012
#------------------------------------------------------------------------------------------------





=begin                                           TIG - ADD VERTEX PLUS
=end


# Copyright 2007-2013, TIG (c)

# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the
# copyright notice appear in all copies.
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

# Name: AddVertex+.rb

# Usage: Menu: "Add Guide-Point"
# Marks All Vertices [if any] in PreSelection with Guide-Points.
# Then Adds Guide-Point at the Picked Point on Edge, Face, cLine,
# Groups/Components etc, using normal inferences.
# Hold Key to Modify [in Context]: SHIFT+Edge=Split_Edge & ALT+Face=Split_Face.
# Undoes each step.

# Version:
# 1.0 20070331 First Issue.
# 1.1 20070331 Tool Tips Added.
# 1.2 20070401 Edge+Shift=Split-Edge, Face+Alt=Split-Face. Renamed in menu and class.
# 1.3 20070403 Key mapping now allows for Macs too.
# 1.4 20131017 Reissued, modularized, renamed "Add Guide-Point"...





=begin                                            THOM THOM - COMPS 2 POINTS
=end

#-----------------------------------------------------------------------------
# Version: 1.0.0
# Compatible: SketchUp 7 (PC)
#             (other versions untested)
#-----------------------------------------------------------------------------
#
# CHANGELOG
# 1.0.0 - 30.08.2010
#		 * Initial release.
#
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------





=begin                                           JIM FOLTZ - DAN RATHBURN
=end



#####################################################################   VARIOUSBITS

#  Some of the toolbar code is from the tweaked script by DAN RATHBURN and JIM FOLTZ



#  ==========================================================================
#
#  Custom Toolbars Extension Registration Script
#
#  by Jim Foltz, <jim.foltz@gmail.com>
#
#  Version : 1.2.0  (patch)  :  by Dan Rathbun
#  Release : 2011-02-15
#
#  Version : 1.3.0 Jim Foltz
#  Release : 2011-09-17
#
#  --------------------------------------------------------------------------
#  THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
#  IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#  ==========================================================================





